From 72c64994e553b7496ccfb43c1df742a9a5a13b73 Mon Sep 17 00:00:00 2001 From: Daniel Draper Date: Fri, 19 Dec 2025 16:18:32 +0100 Subject: [PATCH 1/3] update command # Conflicts: # package.json --- README.md | 10 +++++++- package.json | 6 +++-- pnpm-lock.yaml | 26 +++++++++++++++++++++ src/cli.ts | 8 +++++++ src/tools/update.ts | 37 +++++++++++++++++++++++++++++ tests/tools/update.spec.ts | 48 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 132 insertions(+), 3 deletions(-) create mode 100644 src/tools/update.ts create mode 100644 tests/tools/update.spec.ts diff --git a/README.md b/README.md index 425129d..e813153 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ This way even entityIds like environmentIds or testCaseIds will be autocompleted # octomind -Octomind cli tool. Version: 3.7.0. Additional documentation see https://octomind.dev/docs/api-reference/ +Octomind cli tool. Version: 3.8.0. Additional documentation see https://octomind.dev/docs/api-reference/ **Usage:** `octomind [options] [command]` @@ -478,6 +478,14 @@ List all test targets |:-------|:----------|:---------|:--------| | `-j, --json` | Output raw JSON response | No | | +## Update + +## update + +update your local cli version + +**Usage:** `update [options]` + ## Output Formats diff --git a/package.json b/package.json index 00cc8db..878d004 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@octomind/octomind", - "version": "3.7.1", + "version": "3.8.0", "description": "a command line client for octomind apis", "main": "./dist/index.js", "packageManager": "pnpm@10.26.0+sha512.3b3f6c725ebe712506c0ab1ad4133cf86b1f4b687effce62a9b38b4d72e3954242e643190fc51fa1642949c735f403debd44f5cb0edd657abe63a8b6a7e1e402", @@ -45,8 +45,9 @@ "shell-quote": "1.8.3", "simple-git": "3.30.0", "tabtab": "3.0.2", - "yaml": "2.8.2", "unzipper": "0.12.3", + "which": "6.0.0", + "yaml": "2.8.2", "zod": "4.2.1" }, "devDependencies": { @@ -55,6 +56,7 @@ "@types/node": "25.0.3", "@types/tabtab": "3.0.4", "@types/unzipper": "0.10.11", + "@types/which": "3.0.4", "genversion": "3.2.0", "jest": "30.2.0", "jest-mock-extended": "4.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d974f7e..12ebe77 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,6 +35,9 @@ importers: unzipper: specifier: 0.12.3 version: 0.12.3 + which: + specifier: 6.0.0 + version: 6.0.0 yaml: specifier: 2.8.2 version: 2.8.2 @@ -57,6 +60,9 @@ importers: '@types/unzipper': specifier: 0.10.11 version: 0.10.11 + '@types/which': + specifier: 3.0.4 + version: 3.0.4 genversion: specifier: 3.2.0 version: 3.2.0 @@ -1063,6 +1069,9 @@ packages: '@types/urijs@1.19.26': resolution: {integrity: sha512-wkXrVzX5yoqLnndOwFsieJA7oKM8cNkOKJtf/3vVGSUFkWDKZvFHpIl9Pvqb/T9UsawBBFMTTD8xu7sK5MWuvg==} + '@types/which@3.0.4': + resolution: {integrity: sha512-liyfuo/106JdlgSchJzXEQCVArk0CvevqPote8F8HgWgJ3dRCcTHgJIsLDuee0kxk/mhbInzIZk3QWSZJ8R+2w==} + '@types/yargs-parser@21.0.3': resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} @@ -2031,6 +2040,10 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + isexe@3.1.1: + resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==} + engines: {node: '>=16'} + istanbul-lib-coverage@3.2.2: resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} engines: {node: '>=8'} @@ -3145,6 +3158,11 @@ packages: engines: {node: '>= 8'} hasBin: true + which@6.0.0: + resolution: {integrity: sha512-f+gEpIKMR9faW/JgAgPK1D7mekkFoqbmiwvNzuhsHetni20QSgzg9Vhn0g2JSJkkfehQnqdUAx7/e15qS1lPxg==} + engines: {node: ^20.17.0 || >=22.9.0} + hasBin: true + wordwrap@1.0.0: resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} @@ -4392,6 +4410,8 @@ snapshots: '@types/urijs@1.19.26': {} + '@types/which@3.0.4': {} + '@types/yargs-parser@21.0.3': {} '@types/yargs@17.0.33': @@ -5429,6 +5449,8 @@ snapshots: isexe@2.0.0: {} + isexe@3.1.1: {} + istanbul-lib-coverage@3.2.2: {} istanbul-lib-instrument@6.0.3: @@ -6846,6 +6868,10 @@ snapshots: dependencies: isexe: 2.0.0 + which@6.0.0: + dependencies: + isexe: 3.1.1 + wordwrap@1.0.0: {} wrap-ansi@7.0.0: diff --git a/src/cli.ts b/src/cli.ts index 6be5942..59ddf3e 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -37,6 +37,7 @@ import { updateEnvironment, } from "./tools"; import { init, switchTestTarget } from "./tools/init"; +import { update } from "./tools/update"; import { version } from "./version"; export const BINARY_NAME = "octomind"; @@ -446,5 +447,12 @@ export const buildCmd = (): CompletableCommand => { .allowExcessArguments(true) .action(() => tabCompletion(program)); + program + .completableCommand("update") + .description("update your local cli version") + .helpGroup("update") + .allowExcessArguments(false) + .action(update); + return program; }; diff --git a/src/tools/update.ts b/src/tools/update.ts new file mode 100644 index 0000000..fdd9ea6 --- /dev/null +++ b/src/tools/update.ts @@ -0,0 +1,37 @@ +import * as child_process from "node:child_process"; +import fs from "fs"; +import path from "path"; +import { homedir } from "os" + +import which from "which"; + +export const update = async (): Promise => { + const pathToRoot = path.posix.normalize(path.join(homedir(), "/.local/packages")) + const pathToPackageJson = path.join(pathToRoot, "package.json"); + + if (!fs.existsSync(pathToPackageJson)) { + console.error( + "\x1b[31mcannot find package.json, cannot update, please update manually", + ); + process.exit(1); + } + + if (!(await which("npm"))) { + console.error( + `\x1b[31mcannot determine location of npm, cannot update, please update manually, package location: ${pathToPackageJson}`, + ); + process.exit(1); + } + + console.log(`updating package.json at ${pathToPackageJson}`); + + const result = child_process.execSync( + "npm install @octomind/octomind@latest", + { + cwd: path.dirname(pathToRoot), + }, + ); + + console.log(`${result}`); + console.log("\x1b[32m\n✔ update complete"); +}; diff --git a/tests/tools/update.spec.ts b/tests/tools/update.spec.ts new file mode 100644 index 0000000..a8d787c --- /dev/null +++ b/tests/tools/update.spec.ts @@ -0,0 +1,48 @@ +import * as child_process from "node:child_process"; +import fs from "fs"; +import path from "path"; +import {homedir} from "os"; +import which from "which"; +import {update} from "../../src/tools/update"; + +jest.mock("node:child_process"); +jest.mock("fs"); +jest.mock("which"); + +describe("update", () => { + const mockPathToRoot = path.posix.normalize(path.join(homedir(), "/.local/packages")); + + beforeEach(() => { + jest.mocked(fs.existsSync).mockReturnValue(true); + jest.mocked(which).mockResolvedValue("/usr/bin/npm"); + jest.mocked(child_process.execSync).mockReturnValue(Buffer.from("installed @octomind/octomind@latest")); + console.log = jest.fn(); + console.error = jest.fn(); + process.exit = jest.fn(() => { throw new Error("Process exit") }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it("updates successfully when package.json exists and npm is available", async () => { + await update(); + + expect(child_process.execSync).toHaveBeenCalledWith( + "npm install @octomind/octomind@latest", + { cwd: path.dirname(mockPathToRoot) } + ); + }); + + it("exits with error if package.json does not exist", async () => { + jest.mocked(fs.existsSync).mockReturnValue(false); + + await expect(update()).rejects.toThrow("Process exit"); + }); + + it("exits with error if npm is not available", async () => { + jest.mocked(which).mockResolvedValue(""); + + await expect(update()).rejects.toThrow("Process exit"); + }); +}); From c30c70f0ad68542d73f493d5beb47badd37f377d Mon Sep 17 00:00:00 2001 From: Daniel Draper Date: Thu, 18 Dec 2025 15:47:05 +0100 Subject: [PATCH 2/3] biome --- src/tools/update.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/tools/update.ts b/src/tools/update.ts index fdd9ea6..607cca9 100644 --- a/src/tools/update.ts +++ b/src/tools/update.ts @@ -1,12 +1,14 @@ import * as child_process from "node:child_process"; import fs from "fs"; +import { homedir } from "os"; import path from "path"; -import { homedir } from "os" import which from "which"; export const update = async (): Promise => { - const pathToRoot = path.posix.normalize(path.join(homedir(), "/.local/packages")) + const pathToRoot = path.posix.normalize( + path.join(homedir(), "/.local/packages"), + ); const pathToPackageJson = path.join(pathToRoot, "package.json"); if (!fs.existsSync(pathToPackageJson)) { From a7e2e128da42beaaf5cbaa4c53c0afe7ddc38b44 Mon Sep 17 00:00:00 2001 From: Daniel Draper Date: Fri, 19 Dec 2025 16:22:13 +0100 Subject: [PATCH 3/3] remove colors for now --- src/tools/update.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tools/update.ts b/src/tools/update.ts index 607cca9..97a1dc7 100644 --- a/src/tools/update.ts +++ b/src/tools/update.ts @@ -13,14 +13,14 @@ export const update = async (): Promise => { if (!fs.existsSync(pathToPackageJson)) { console.error( - "\x1b[31mcannot find package.json, cannot update, please update manually", + "cannot find package.json, cannot update, please update manually", ); process.exit(1); } if (!(await which("npm"))) { console.error( - `\x1b[31mcannot determine location of npm, cannot update, please update manually, package location: ${pathToPackageJson}`, + `cannot determine location of npm, cannot update, please update manually, package location: ${pathToPackageJson}`, ); process.exit(1); } @@ -35,5 +35,5 @@ export const update = async (): Promise => { ); console.log(`${result}`); - console.log("\x1b[32m\n✔ update complete"); + console.log("\n✔ update complete"); };