From b555dfed96a3343e98b512a2662160f1d7527bf3 Mon Sep 17 00:00:00 2001 From: Daniel Draper Date: Tue, 25 Feb 2025 13:49:36 +0100 Subject: [PATCH] cosmetic fixes --- .eslintrc.json | 3 +- .github/workflows/publish.yml | 2 +- .gitignore | 3 +- LICENSE | 21 ++++++++ README.md | 51 ++++++++----------- package.json | 8 +-- src/api.ts | 94 +++++++++++++++++++---------------- src/cli.ts | 11 ++-- tests/api.spec.ts | 25 ++++------ 9 files changed, 119 insertions(+), 99 deletions(-) create mode 100644 LICENSE diff --git a/.eslintrc.json b/.eslintrc.json index 37a41aa..b266243 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -4,7 +4,8 @@ "plugin:github/recommended", "plugin:prettier/recommended", "plugin:import/recommended", - "plugin:import/typescript" + "plugin:import/typescript", + "plugin:@typescript-eslint/strict" ], "parser": "@typescript-eslint/parser", "parserOptions": { diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index b424497..c33dd7f 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -16,7 +16,7 @@ jobs: - uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 registry-url: "https://registry.npmjs.org" cache: "pnpm" diff --git a/.gitignore b/.gitignore index 4e42bdb..5c13444 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ notes.txt dist/ -node_modules/ \ No newline at end of file +node_modules/ +.idea diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cfbc8bb --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2018 GitHub, Inc. and contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md index 6519441..8bc8adf 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,15 @@ # Octomind CLI +![Continuous Integration](https://github.com/octomind-dev/cli/actions/workflows/ts.yml/badge.svg) + A command-line interface for interacting with the Octomind API. This CLI allows you to execute tests, retrieve test reports, and manage private locations as well as environments. See [API documentation](https://octomind.dev/docs/api-reference/) -## Installation +## Usage -1. Clone the repository -2. Install dependencies: -```bash -pnpm install -``` +1. Install the package - `npm i -g @octomind/cli` and use it directly e.g. `@octomind/cli -h` +2. Use the cli through npx e.g. `npx @octomind/cli -h` ## Commands @@ -19,7 +18,7 @@ pnpm install Run test cases against a specified URL. ```bash -tsx src/index.ts execute \ +npx @octomind/cli execute \ --api-key \ --test-target-id \ --url \ @@ -41,7 +40,7 @@ Options: Retrieve details about a specific test report. ```bash -tsx src/index.ts report \ +npx @octomind/cli report \ --api-key \ --test-target-id \ --report-id \ @@ -98,7 +97,7 @@ Example JSON output: Register a new private location worker. If you use the [private location worker](https://github.com/OctoMind-dev/private-location-worker) it will register itself on startup automatically. ```bash -tsx src/index.ts register-location \ +npx @octomind/cli register-location \ --api-key \ --name \ --proxypass \ @@ -120,7 +119,7 @@ Options: Remove a registered private location worker. If you use the [private location worker](https://github.com/OctoMind-dev/private-location-worker) it will unregister itself when going offline automatically. ```bash -tsx src/index.ts unregister-location \ +npx @octomind/cli unregister-location \ --api-key \ --name \ [--json] @@ -136,7 +135,7 @@ Options: List all registered private locations. ```bash -tsx src/index.ts list-private-locations \ +npx @octomind/cli list-private-locations \ --api-key \ [--json] ``` @@ -161,7 +160,7 @@ Private Locations: List all available environments. ```bash -tsx src/index.ts list-environments \ +npx @octomind/cli list-environments \ --api-key \ --test-target-id \ [--json] @@ -177,7 +176,7 @@ Options: Create a new environment for a test target. ```bash -tsx src/index.ts create-environment \ +npx @octomind/cli create-environment \ --api-key \ --test-target-id \ --name \ @@ -211,7 +210,7 @@ Options: Update an existing environment. ```bash -tsx src/index.ts update-environment \ +npx @octomind/cli update-environment \ --api-key \ --test-target-id \ --environment-id \ @@ -247,7 +246,7 @@ Options: Delete an existing environment. ```bash -tsx src/index.ts delete-environment \ +npx @octomind/cli delete-environment \ --api-key \ --test-target-id \ --environment-id \ @@ -290,25 +289,19 @@ Example of JSON output: } ``` -## Error Handling - -The CLI will: -- Validate required parameters before making API calls -- Display clear error messages for missing or invalid parameters -- Show API error responses with details when available -- Exit with status code 1 on errors - ## Development +1. Clone the repository +2. Install dependencies: +```bash +pnpm install +``` + The CLI is written in TypeScript and uses the following dependencies: - `commander`: For command-line argument parsing - `axios`: For making HTTP requests To build from source: ```bash -npm run build -``` - -## License - -This project is licensed under the MIT License - see the LICENSE file for details. \ No newline at end of file +pnpm run build +``` \ No newline at end of file diff --git a/package.json b/package.json index eab4840..17eb4b8 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,9 @@ { - "name": "octomind-cli", - "version": "1.0.1", + "name": "@octomind/cli", + "version": "1.0.0", "description": "a command line client for octomind apis", "main": "./dist/index.js", - "packageManager": "pnpm@9.5.0+sha256.dbdf5961c32909fb030595a9daa1dae720162e658609a8f92f2fa99835510ca5", + "packageManager": "pnpm@9.15.6+sha512.139cab068fdf0b751268179ac5f909b5be72afb4a75c513d1905d151befc8977b593d3cf8671ed83d4d6637c5c94b98ffbce108125de4a5a27a31233601a99de", "engines": { "node": ">=20.0.0" }, @@ -16,7 +16,7 @@ }, "keywords": [], "author": "", - "license": "ISC", + "license": "MIT", "dependencies": { "axios": "^1.7.9", "commander": "^13.1.0", diff --git a/src/api.ts b/src/api.ts index c2f31a1..e37d20c 100644 --- a/src/api.ts +++ b/src/api.ts @@ -22,12 +22,12 @@ import { const BASE_URL = "https://app.octomind.dev/api"; // Helper function for API calls -async function apiCall( +const apiCall = async ( method: "get" | "post" | "put" | "delete" | "patch", endpoint: string, apiKey: string, - data?: any, -): Promise { + data?: unknown, +): Promise => { try { const response = await axios({ method, @@ -47,19 +47,15 @@ async function apiCall( } process.exit(1); } -} +}; -// Output helper function -function outputResult(result: any, json: boolean) { - if (json) { - console.log(JSON.stringify(result, null, 2)); - return; - } - return result; -} +const outputResult = (result: unknown): void => { + console.log(JSON.stringify(result, null, 2)); +}; -// Command implementations -export async function executeTests(options: ExecuteTestsOptions) { +export const executeTests = async ( + options: ExecuteTestsOptions, +): Promise => { if (!options.apiKey) { console.error("API key is required"); process.exit(1); @@ -88,7 +84,7 @@ export async function executeTests(options: ExecuteTestsOptions) { ); if (options.json) { - outputResult(response, true); + outputResult(response); return; } @@ -108,9 +104,11 @@ export async function executeTests(options: ExecuteTestsOptions) { } }); } -} +}; -export async function getTestReport(options: GetTestReportOptions) { +export const getTestReport = async ( + options: GetTestReportOptions, +): Promise => { if (!options.apiKey) { console.error("API key is required"); process.exit(1); @@ -123,7 +121,7 @@ export async function getTestReport(options: GetTestReportOptions) { ); if (options.json) { - outputResult(response, true); + outputResult(response); return; } @@ -143,9 +141,11 @@ export async function getTestReport(options: GetTestReportOptions) { } }); } -} +}; -export async function registerLocation(options: RegisterLocationOptions) { +export const registerLocation = async ( + options: RegisterLocationOptions, +): Promise => { if (!options.apiKey) { console.error("API key is required"); process.exit(1); @@ -168,14 +168,16 @@ export async function registerLocation(options: RegisterLocationOptions) { ); if (options.json) { - outputResult(response, true); + outputResult(response); return; } console.log("Registration result:", response.success ? "Success" : "Failed"); -} +}; -export async function unregisterLocation(options: UnregisterLocationOptions) { +export const unregisterLocation = async ( + options: UnregisterLocationOptions, +): Promise => { if (!options.apiKey) { console.error("API key is required"); process.exit(1); @@ -193,7 +195,7 @@ export async function unregisterLocation(options: UnregisterLocationOptions) { ); if (options.json) { - outputResult(response, true); + outputResult(response); return; } @@ -201,11 +203,11 @@ export async function unregisterLocation(options: UnregisterLocationOptions) { "Unregistration result:", response.success ? "Success" : "Failed", ); -} +}; -export async function listPrivateLocations( +export const listPrivateLocations = async ( options: ListPrivateLocationsOptions, -) { +): Promise => { if (!options.apiKey) { console.error("API key is required"); process.exit(1); @@ -218,7 +220,7 @@ export async function listPrivateLocations( ); if (options.json) { - outputResult(response, true); + outputResult(response); return; } @@ -228,9 +230,11 @@ export async function listPrivateLocations( console.log(` Status: ${location.status}`); console.log(` Address: ${location.address}`); }); -} +}; -export async function listEnvironments(options: ListEnvironmentsOptions) { +export const listEnvironments = async ( + options: ListEnvironmentsOptions, +): Promise => { if (!options.apiKey) { console.error("API key is required"); process.exit(1); @@ -243,7 +247,7 @@ export async function listEnvironments(options: ListEnvironmentsOptions) { ); if (options.json) { - outputResult(response, true); + outputResult(response); return; } @@ -254,9 +258,11 @@ export async function listEnvironments(options: ListEnvironmentsOptions) { console.log(` Discovery URL: ${environment.discoveryUrl}`); console.log(` Updated At: ${environment.updatedAt}`); }); -} +}; -export async function createEnvironment(options: CreateEnvironmentOptions) { +export const createEnvironment = async ( + options: CreateEnvironmentOptions, +): Promise => { if (!options.apiKey) { console.error("API key is required"); process.exit(1); @@ -279,7 +285,7 @@ export async function createEnvironment(options: CreateEnvironmentOptions) { ); if (options.json) { - outputResult(response, true); + outputResult(response); return; } @@ -288,9 +294,11 @@ export async function createEnvironment(options: CreateEnvironmentOptions) { console.log(` ID: ${response.id}`); console.log(` Discovery URL: ${response.discoveryUrl}`); console.log(` Updated At: ${response.updatedAt}`); -} +}; -export async function updateEnvironment(options: UpdateEnvironmentOptions) { +export const updateEnvironment = async ( + options: UpdateEnvironmentOptions, +): Promise => { if (!options.apiKey) { console.error("API key is required"); process.exit(1); @@ -313,7 +321,7 @@ export async function updateEnvironment(options: UpdateEnvironmentOptions) { ); if (options.json) { - outputResult(response, true); + outputResult(response); return; } @@ -322,24 +330,26 @@ export async function updateEnvironment(options: UpdateEnvironmentOptions) { console.log(` ID: ${response.id}`); console.log(` Discovery URL: ${response.discoveryUrl}`); console.log(` Updated At: ${response.updatedAt}`); -} +}; -export async function deleteEnvironment(options: DeleteEnvironmentOptions) { +export const deleteEnvironment = async ( + options: DeleteEnvironmentOptions, +): Promise => { if (!options.apiKey) { console.error("API key is required"); process.exit(1); } - await apiCall( + await apiCall( "delete", `/apiKey/v2/test-targets/${options.testTargetId}/environments/${options.environmentId}`, options.apiKey, ); if (options.json) { - outputResult({ success: true }, true); + outputResult({ success: true }); return; } console.log("Environment deleted successfully!"); -} +}; diff --git a/src/cli.ts b/src/cli.ts index 74b9e09..4d339fb 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,4 +1,4 @@ -import { program, Option } from "commander"; +import { program, Option, Command } from "commander"; import { createEnvironment, deleteEnvironment, @@ -18,15 +18,14 @@ const apiKeyOption = new Option( .env("API_KEY") .makeOptionMandatory(); -function createCommandWithCommonOptions(command: string) { +const createCommandWithCommonOptions = (command: string): Command => { return program .command(command) .addOption(apiKeyOption) .option("-j, --json", "Output raw JSON response"); -} +}; -export function run() { - // CLI program setup +export const run = (): void => { program .name("octomind-cli") .description( @@ -112,4 +111,4 @@ export function run() { .action(deleteEnvironment); program.parse(); -} +}; diff --git a/tests/api.spec.ts b/tests/api.spec.ts index 0515c1a..5669d04 100644 --- a/tests/api.spec.ts +++ b/tests/api.spec.ts @@ -1,34 +1,29 @@ -import axios, { AxiosStatic } from "axios"; +import axios from "axios"; import { + createEnvironment, + deleteEnvironment, executeTests, getTestReport, + listEnvironments, + listPrivateLocations, registerLocation, unregisterLocation, - listPrivateLocations, - listEnvironments, - createEnvironment, updateEnvironment, - deleteEnvironment, } from "../src/api"; import { + CreateEnvironmentOptions, + DeleteEnvironmentOptions, ExecuteTestsOptions, GetTestReportOptions, + ListEnvironmentsOptions, + ListPrivateLocationsOptions, RegisterLocationOptions, UnregisterLocationOptions, - ListPrivateLocationsOptions, - ListEnvironmentsOptions, - CreateEnvironmentOptions, UpdateEnvironmentOptions, - DeleteEnvironmentOptions, } from "../src/types"; -interface AxiosMock extends AxiosStatic { - mockResolvedValue: Function; - mockRejectedValue: Function; -} - jest.mock("axios"); -const mockedAxios = axios as AxiosMock; +const mockedAxios = jest.mocked(axios); describe("CLI Commands", () => { const apiKey = "test-api-key";