diff --git a/.gitignore b/.gitignore
index 78d7e13..6b8ca7e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,4 +7,5 @@ node_modules/
octomind.config.json
octomind-cli-debug/
openapi.yaml
+vhs/
diff --git a/README-template.md b/README-template.md
index 97378be..2273bb4 100644
--- a/README-template.md
+++ b/README-template.md
@@ -6,6 +6,8 @@ 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/)
+
+
## Usage / Installation
1. To install the package globally do **NOT** just a `npm i -g @octomind/octomind` but instead
diff --git a/README.md b/README.md
index 4441198..9619df2 100644
--- a/README.md
+++ b/README.md
@@ -6,6 +6,8 @@ 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/)
+
+
## Usage / Installation
1. To install the package globally do **NOT** just a `npm i -g @octomind/octomind` but instead
@@ -40,20 +42,16 @@ This way even entityIds like environmentIds or testCaseIds will be autocompleted
# octomind
-Octomind cli tool. Version: 1.3.0. Additional documentation see https://octomind.dev/docs/api-reference/
+Octomind cli tool. Version: 1.3.1. Additional documentation see https://octomind.dev/docs/api-reference/
**Usage:** `octomind [options] [command]`
### Options
| Option | Description | Required | Default |
-|--------|-------------|----------|--------|
+|:-------|:----------|:---------|:--------|
| `-V, --version` | output the version number | No | |
-# octomind CLI Documentation
-
-Octomind cli tool. Version: 1.3.0. Additional documentation see https://octomind.dev/docs/api-reference/
-
## Setup
## init
@@ -65,7 +63,7 @@ Initialize configuration by setting up API key. This will create a config file i
### Options
| Option | Description | Required | Default |
-|--------|-------------|----------|--------|
+|:-------|:----------|:---------|:--------|
| `-t, --test-target-id ` | Test target ID | Yes | |
| `-k, --api-key ` | the api key for authentication | Yes | |
| `-f, --force` | Force overwrite existing configuration | No | |
@@ -107,7 +105,7 @@ List all environments
### Options
| Option | Description | Required | Default |
-|--------|-------------|----------|--------|
+|:-------|:----------|:---------|:--------|
| `-j, --json` | Output raw JSON response | No | |
| `-t, --test-target-id [id]` | Test target ID, if not provided will use the test target id from the config | No | |
@@ -120,7 +118,7 @@ Create a new environment
### Options
| Option | Description | Required | Default |
-|--------|-------------|----------|--------|
+|:-------|:----------|:---------|:--------|
| `-j, --json` | Output raw JSON response | No | |
| `-n, --name ` | Environment name | Yes | |
| `-d, --discovery-url ` | Discovery URL | Yes | |
@@ -141,7 +139,7 @@ Get an environment
### Options
| Option | Description | Required | Default |
-|--------|-------------|----------|--------|
+|:-------|:----------|:---------|:--------|
| `-j, --json` | Output raw JSON response | No | |
| `-e, --environment-id ` | Environment ID | Yes | |
| `-t, --test-target-id [id]` | Test target ID, if not provided will use the test target id from the config | No | |
@@ -155,7 +153,7 @@ Update an existing environment
### Options
| Option | Description | Required | Default |
-|--------|-------------|----------|--------|
+|:-------|:----------|:---------|:--------|
| `-j, --json` | Output raw JSON response | No | |
| `-e, --environment-id ` | Environment ID | Yes | |
| `-t, --test-target-id [id]` | Test target ID, if not provided will use the test target id from the config | No | |
@@ -177,7 +175,7 @@ Delete an environment
### Options
| Option | Description | Required | Default |
-|--------|-------------|----------|--------|
+|:-------|:----------|:---------|:--------|
| `-j, --json` | Output raw JSON response | No | |
| `-e, --environment-id ` | Environment ID | Yes | |
| `-t, --test-target-id [id]` | Test target ID, if not provided will use the test target id from the config | No | |
@@ -193,7 +191,7 @@ run test cases against local build
### Options
| Option | Description | Required | Default |
-|--------|-------------|----------|--------|
+|:-------|:----------|:---------|:--------|
| `-j, --json` | Output raw JSON response | No | |
| `-u, --url ` | url the tests should run against | Yes | |
| `-c, --test-case-id [uuid]` | id of the test case you want to run, if not provided will run all test cases in the test target | No | |
@@ -212,7 +210,7 @@ Execute test cases to create a test report
### Options
| Option | Description | Required | Default |
-|--------|-------------|----------|--------|
+|:-------|:----------|:---------|:--------|
| `-j, --json` | Output raw JSON response | No | |
| `-u, --url ` | URL to test | Yes | |
| `-t, --test-target-id [id]` | Test target ID, if not provided will use the test target id from the config | No | |
@@ -230,7 +228,7 @@ Create a new test case discovery
### Options
| Option | Description | Required | Default |
-|--------|-------------|----------|--------|
+|:-------|:----------|:---------|:--------|
| `-j, --json` | Output raw JSON response | No | |
| `-n, --name ` | Discovery name | Yes | |
| `-p, --prompt ` | Discovery prompt | Yes | |
@@ -252,7 +250,7 @@ Get notifications for a test target
### Options
| Option | Description | Required | Default |
-|--------|-------------|----------|--------|
+|:-------|:----------|:---------|:--------|
| `-j, --json` | Output raw JSON response | No | |
| `-t, --test-target-id [id]` | Test target ID, if not provided will use the test target id from the config | No | |
@@ -267,7 +265,7 @@ Register a private location
### Options
| Option | Description | Required | Default |
-|--------|-------------|----------|--------|
+|:-------|:----------|:---------|:--------|
| `-j, --json` | Output raw JSON response | No | |
| `-n, --name ` | Location name | Yes | |
| `-p, --password ` | Proxy password | Yes | |
@@ -283,7 +281,7 @@ Unregister a private location
### Options
| Option | Description | Required | Default |
-|--------|-------------|----------|--------|
+|:-------|:----------|:---------|:--------|
| `-j, --json` | Output raw JSON response | No | |
| `-n, --name ` | Location name | Yes | |
@@ -296,7 +294,7 @@ List all private locations
### Options
| Option | Description | Required | Default |
-|--------|-------------|----------|--------|
+|:-------|:----------|:---------|:--------|
| `-j, --json` | Output raw JSON response | No | |
## start-private-location
@@ -308,7 +306,7 @@ Start a private location worker, see https://octomind.dev/docs/proxy/private-loc
### Options
| Option | Description | Required | Default |
-|--------|-------------|----------|--------|
+|:-------|:----------|:---------|:--------|
| `-n, --name [name]` | Location name | No | |
| `-u, --username [username]` | Proxy user | No | |
| `-p, --password [password]` | Proxy password | No | |
@@ -331,7 +329,7 @@ Delete a test case
### Options
| Option | Description | Required | Default |
-|--------|-------------|----------|--------|
+|:-------|:----------|:---------|:--------|
| `-j, --json` | Output raw JSON response | No | |
| `-c, --test-case-id ` | Test case ID | Yes | |
| `-t, --test-target-id [id]` | Test target ID, if not provided will use the test target id from the config | No | |
@@ -345,7 +343,7 @@ Get details of a specific test case
### Options
| Option | Description | Required | Default |
-|--------|-------------|----------|--------|
+|:-------|:----------|:---------|:--------|
| `-j, --json` | Output raw JSON response | No | |
| `-c, --test-case-id ` | Test case ID | Yes | |
| `-t, --test-target-id [id]` | Test target ID, if not provided will use the test target id from the config | No | |
@@ -359,7 +357,7 @@ List all test cases
### Options
| Option | Description | Required | Default |
-|--------|-------------|----------|--------|
+|:-------|:----------|:---------|:--------|
| `-j, --json` | Output raw JSON response | No | |
| `-t, --test-target-id [id]` | Test target ID, if not provided will use the test target id from the config | No | |
@@ -374,7 +372,7 @@ Get test report details
### Options
| Option | Description | Required | Default |
-|--------|-------------|----------|--------|
+|:-------|:----------|:---------|:--------|
| `-j, --json` | Output raw JSON response | No | |
| `-r, --test-report-id ` | Test report ID | Yes | |
| `-t, --test-target-id [id]` | Test target ID, if not provided will use the test target id from the config | No | |
@@ -390,7 +388,7 @@ List all test targets
### Options
| Option | Description | Required | Default |
-|--------|-------------|----------|--------|
+|:-------|:----------|:---------|:--------|
| `-j, --json` | Output raw JSON response | No | |
diff --git a/assets/octomind-cli-opt.gif b/assets/octomind-cli-opt.gif
new file mode 100644
index 0000000..57f798b
Binary files /dev/null and b/assets/octomind-cli-opt.gif differ
diff --git a/package.json b/package.json
index 8c5af45..09dbcaf 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@octomind/octomind",
- "version": "1.3.0",
+ "version": "1.3.1",
"description": "a command line client for octomind apis",
"main": "./dist/index.js",
"packageManager": "pnpm@10.13.1",
diff --git a/scripts/generate-docs.ts b/scripts/generate-docs.ts
index ff66fb6..c859a91 100755
--- a/scripts/generate-docs.ts
+++ b/scripts/generate-docs.ts
@@ -28,7 +28,7 @@ export function generateCommandDocs(command: Command, level = 1): string {
if (options && options.length > 0) {
docs += '### Options\n\n';
docs += '| Option | Description | Required | Default |\n';
- docs += '|--------|-------------|----------|--------|\n';
+ docs += '|:-------|:----------|:---------|:--------|\n';
options.forEach(option => {
const flags = option.flags;
@@ -46,9 +46,6 @@ export function generateCommandDocs(command: Command, level = 1): string {
const subcommands = command.commands;
if (subcommands && subcommands.length > 0) {
if (level === 1) {
- docs += `# ${command.name()} CLI Documentation\n\n`;
- docs += `${command.description()}\n\n`;
-
const commandsByGroup = new Map();
const ungroupedCommands: Command[] = [];
diff --git a/src/cli.ts b/src/cli.ts
index b08e8c4..40900dd 100644
--- a/src/cli.ts
+++ b/src/cli.ts
@@ -37,7 +37,8 @@ import {
unregisterLocation,
updateEnvironment,
} from "./tools";
-import { getTestTargets, listTestTargets } from "./tools/test-targets";
+import { init, switchTestTarget } from "./tools/init";
+import { listTestTargets } from "./tools/test-targets";
import { version } from "./version";
export const BINARY_NAME = "octomind";
@@ -60,38 +61,6 @@ const addTestTargetWrapper =
void fn({ ...options, testTargetId: resolvedTestTargetId });
};
-const selectTestTarget = async (): Promise => {
- const testTargets = await getTestTargets();
- await listTestTargets({});
-
- if (testTargets.length === 1) {
- console.log(
- `Only one test target found, using it: ${testTargets[0].app} (${testTargets[0].id})`,
- );
- return testTargets[0].id;
- }
-
- const testTargetIndex = await promptUser(
- "Enter number of the test target you want to use (optional, press Enter to skip): ",
- );
- const testTargetIndexAsInt = Number.parseInt(testTargetIndex);
-
- if (
- Number.isNaN(testTargetIndexAsInt) ||
- testTargetIndexAsInt < 1 ||
- testTargetIndexAsInt > testTargets.length
- ) {
- console.log("ā could not find a test target with the index you provided");
- process.exit(1);
- }
- const testTargetId = testTargets[testTargetIndexAsInt - 1].id;
- if (!testTargetId) {
- console.log("ā could not find a test target with the index you provided");
- process.exit(1);
- }
-
- return testTargetId;
-};
const testTargetIdOption = new Option(
"-t, --test-target-id [id]",
"Test target ID, if not provided will use the test target id from the config",
@@ -127,71 +96,7 @@ export const buildCmd = (): CompletableCommand => {
.option("-t, --test-target-id ", "Test target ID")
.option("-k, --api-key ", "the api key for authentication")
.option("-f, --force", "Force overwrite existing configuration")
- .action(
- async (options: {
- testTargetId?: string;
- apiKey: string;
- force?: boolean;
- }) => {
- try {
- console.log("š Initializing configuration...\n");
-
- const existingConfig = await loadConfig(options.force);
-
- if (existingConfig.apiKey && !options.force) {
- console.log("ā ļø Configuration already exists.");
- const overwrite = await promptUser(
- "Do you want to overwrite it? (y/N): ",
- );
-
- if (
- overwrite.toLowerCase() !== "y" &&
- overwrite.toLowerCase() !== "yes"
- ) {
- console.log("Configuration unchanged.");
- return;
- }
- }
-
- let apiKey: string = "";
- if (!options.apiKey) {
- apiKey = await promptUser(
- "Enter your API key. Go to https://octomind.dev/docs/run-tests/execution-curl#create-an-api-key to learn how to generate one: ",
- );
- if (!apiKey) {
- console.log("ā API key is required.");
- process.exit(1);
- }
- }
- // saving here to be able to use the api key for the test targets
- const newApiKeyConfig = {
- ...existingConfig,
-
- apiKey: options.apiKey,
- };
-
- await saveConfig(newApiKeyConfig);
-
- const testTargetId = await selectTestTarget();
-
- const newConfig: Config = {
- ...existingConfig,
- apiKey: options.apiKey,
- testTargetId: options.testTargetId ?? testTargetId,
- };
-
- await saveConfig(newConfig);
-
- console.log("\n⨠Initialization complete!");
- } catch (error) {
- console.error(
- "ā Error during initialization:",
- (error as Error).message,
- );
- process.exit(1);
- }
- },
- );
+ .action(init);
program
.completableCommand("switch-test-target")
@@ -199,16 +104,7 @@ export const buildCmd = (): CompletableCommand => {
"Switch to a different test target. This will list all available test targets and update the config file in ~/.config/octomind.json",
)
.helpGroup("setup")
- .action(async () => {
- const testTargetId = await selectTestTarget();
- const existingConfig = await loadConfig();
- const newConfig: Config = {
- ...existingConfig,
- testTargetId,
- };
- await saveConfig(newConfig);
- console.log(`⨠Switched to test target: ${testTargetId}`);
- });
+ .action(switchTestTarget);
createCommandWithCommonOptions(program, "debug")
.completer(environmentIdCompleter)
diff --git a/src/config.ts b/src/config.ts
index c39ce49..ebc42f4 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -59,6 +59,7 @@ export async function saveConfig(newConfig: Config): Promise {
const configPath = await getConfigPath(true);
await fs.writeFile(configPath, JSON.stringify(newConfig, null, 2), "utf8");
console.log(`ā
Configuration saved to ${configPath}`);
+ configLoaded = false;
} catch (error) {
console.error("ā Error saving configuration:", (error as Error).message);
process.exit(1);
diff --git a/src/tools/init.ts b/src/tools/init.ts
new file mode 100644
index 0000000..a815fd2
--- /dev/null
+++ b/src/tools/init.ts
@@ -0,0 +1,107 @@
+import { Config, loadConfig, saveConfig } from "../config";
+import { promptUser } from "../helpers";
+import { getTestTargets, listTestTargets } from "./test-targets";
+
+const selectTestTarget = async (): Promise => {
+ const testTargets = await getTestTargets();
+ await listTestTargets({});
+
+ if (testTargets.length === 1) {
+ console.log(
+ `Only one test target found, using it: ${testTargets[0].app} (${testTargets[0].id})`,
+ );
+ return testTargets[0].id;
+ }
+
+ const testTargetIndex = await promptUser(
+ "Enter number of the test target you want to use (optional, press Enter to skip): ",
+ );
+ const testTargetIndexAsInt = Number.parseInt(testTargetIndex);
+
+ if (
+ Number.isNaN(testTargetIndexAsInt) ||
+ testTargetIndexAsInt < 1 ||
+ testTargetIndexAsInt > testTargets.length
+ ) {
+ console.log("ā could not find a test target with the index you provided");
+ process.exit(1);
+ }
+ const testTargetId = testTargets[testTargetIndexAsInt - 1].id;
+ if (!testTargetId) {
+ console.log("ā could not find a test target with the index you provided");
+ process.exit(1);
+ }
+
+ return testTargetId;
+};
+
+export const switchTestTarget = async () => {
+ const testTargetId = await selectTestTarget();
+ const existingConfig = await loadConfig();
+ const newConfig: Config = {
+ ...existingConfig,
+ testTargetId,
+ };
+ await saveConfig(newConfig);
+ console.log(`⨠Switched to test target: ${testTargetId}`);
+};
+
+export const init = async (options: {
+ testTargetId?: string;
+ apiKey: string;
+ force?: boolean;
+}) => {
+ try {
+ console.log("š Initializing configuration...\n");
+
+ const existingConfig = await loadConfig(options.force);
+
+ if (existingConfig.apiKey && !options.force) {
+ console.log("ā ļø Configuration already exists.");
+ const overwrite = await promptUser(
+ "Do you want to overwrite it? (y/N): ",
+ );
+
+ if (
+ overwrite.toLowerCase() !== "y" &&
+ overwrite.toLowerCase() !== "yes"
+ ) {
+ console.log("Configuration unchanged.");
+ return;
+ }
+ }
+
+ if (!options.apiKey) {
+ options.apiKey = await promptUser(
+ "Enter your API key. Go to https://octomind.dev/docs/run-tests/execution-curl#create-an-api-key to learn how to generate one: ",
+ );
+ if (!options.apiKey) {
+ console.log("ā API key is required.");
+ process.exit(1);
+ }
+ }
+ // saving here to be able to use the api key for the test targets
+ const newApiKeyConfig = {
+ ...existingConfig,
+
+ apiKey: options.apiKey,
+ };
+
+ await saveConfig(newApiKeyConfig);
+
+ const testTargetId = await selectTestTarget();
+
+ const newConfig: Config = {
+ ...existingConfig,
+ apiKey: options.apiKey,
+ testTargetId: options.testTargetId ?? testTargetId,
+ };
+
+ await saveConfig(newConfig);
+
+ console.log("\n⨠Initialization complete!");
+ } catch (error) {
+ console.error("ā Error during initialization:", (error as Error).message);
+ process.exit(1);
+ }
+};
diff --git a/src/tools/test-cases.ts b/src/tools/test-cases.ts
index 19ef1fa..9f4242e 100644
--- a/src/tools/test-cases.ts
+++ b/src/tools/test-cases.ts
@@ -125,6 +125,9 @@ export const getTestCases = async (
export const listTestCases = async (
options: GetTestCasesOptions & ListOptions,
): Promise => {
+ if (!options.status) {
+ options.status = "ENABLED";
+ }
const testCases = await getTestCases(options);
if (options.json) {
diff --git a/tests/scripts/generate-docs.spec.ts b/tests/scripts/generate-docs.spec.ts
index 7025302..e9da33d 100644
--- a/tests/scripts/generate-docs.spec.ts
+++ b/tests/scripts/generate-docs.spec.ts
@@ -2,20 +2,23 @@ import { generateCommandDocs } from "../../scripts/generate-docs";
import { Command } from "commander";
describe("generateCommandDocs", () => {
- const mockCommand = new Command();
+ const mockCommand = new Command("test-command");
+ mockCommand.version("1.0.0");
mockCommand.command("test").description("test").option("-t, --test", "test");
mockCommand.command("test2").description("test2").helpGroup("test");
mockCommand.command("test3").description("test3").helpGroup("setup");
it("should generate documentation", () => {
const docs = generateCommandDocs(mockCommand);
expect(docs).toMatchInlineSnapshot(`
-"#
+"# test-command
-**Usage:** \` [options] [command]\`
-
-# CLI Documentation
+**Usage:** \`test-command [options] [command]\`
+### Options
+| Option | Description | Required | Default |
+|:-------|:----------|:---------|:--------|
+| \`-V, --version\` | output the version number | No | |
## Setup
@@ -44,7 +47,7 @@ test
### Options
| Option | Description | Required | Default |
-|--------|-------------|----------|--------|
+|:-------|:----------|:---------|:--------|
| \`-t, --test\` | test | No | |
"
diff --git a/tests/tools/init.spec.ts b/tests/tools/init.spec.ts
new file mode 100644
index 0000000..3a3a892
--- /dev/null
+++ b/tests/tools/init.spec.ts
@@ -0,0 +1,45 @@
+import { init } from "../../src/tools/init";
+import { loadConfig, saveConfig } from "../../src/config";
+import { promptUser } from "../../src/helpers";
+import { getTestTargets } from "../../src/tools/test-targets";
+jest.mock("../../src/config");
+jest.mock("../../src/helpers");
+jest.mock("../../src/tools/test-targets");
+describe("init", () => {
+ const originalConsoleLog = console.log;
+ beforeAll(() => {
+ jest.clearAllMocks();
+ console.log = jest.fn();
+ });
+ afterAll(() => {
+ console.log = originalConsoleLog;
+ });
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+ const loadConfigMock = loadConfig as jest.Mock;
+ loadConfigMock.mockResolvedValue({ apiKey: "apiKey" });
+ const promptUserMock = promptUser as jest.Mock;
+ promptUserMock.mockResolvedValue("1");
+ const getTestTargetsMock = getTestTargets as jest.Mock;
+ getTestTargetsMock.mockResolvedValue([{ id: "testTargetId", app: "testTargetApp" }]);
+
+ it("should initialize the configuration with one test target", async () => {
+ await init({ apiKey: "newApiKey", force: true });
+ expect(loadConfigMock).toHaveBeenCalledWith(true);
+ expect(saveConfig).toHaveBeenCalledWith({ apiKey: "newApiKey", testTargetId: "testTargetId" });
+ expect(console.log).toHaveBeenCalledWith("Only one test target found, using it: testTargetApp (testTargetId)");
+ expect(console.log).toHaveBeenNthCalledWith(3, "\n⨠Initialization complete!");
+ });
+
+ it("should initialize the configuration with multiple test targets", async () => {
+ getTestTargetsMock.mockResolvedValue([
+ { id: "testTargetId1", app: "testTargetApp1" },
+ { id: "testTargetId2", app: "testTargetApp2" },
+ ]);
+ await init({ apiKey: "newApiKey", force: true });
+ expect(loadConfigMock).toHaveBeenCalledWith(true);
+ expect(saveConfig).toHaveBeenCalledWith({ apiKey: "newApiKey", testTargetId: "testTargetId1" });
+ expect(console.log).toHaveBeenNthCalledWith(2, "\n⨠Initialization complete!");
+ });
+});
\ No newline at end of file