From 3003400c05044cffe09426c231bd2a867451ae23 Mon Sep 17 00:00:00 2001 From: jariy17 Date: Mon, 31 Aug 2026 20:03:08 +0000 Subject: [PATCH 1/7] feat(update): port CLI self-updater command to refactor architecture Ports the `agentcore update` command from the old CLI into the refactored Handler/Router (Bun) architecture. It checks the npm registry for a newer @aws/agentcore and runs `npm install -g` (`--check` reports without installing). - src/handlers/update/action.ts: fetchLatestVersion + compareVersions + handleUpdate, with an injectable ProcessRunner (defaults to the shared runProcess) so the install path is testable without spawning npm. - src/handlers/update/index.tsx: createUpdateHandler, always renders the UpdateResult as JSON (resource-command convention); npm progress streams to stderr so stdout stays pipeable. - Mounted in src/handlers/index.tsx. - 21 bun tests (compareVersions table, fetch spy, injected runner); tsc clean. --- src/handlers/index.tsx | 2 + src/handlers/root.test.tsx | 1 + src/handlers/update/action.ts | 106 ++++++++++++++++++++++++++++ src/handlers/update/index.tsx | 24 +++++++ src/handlers/update/update.test.ts | 109 +++++++++++++++++++++++++++++ 5 files changed, 242 insertions(+) create mode 100644 src/handlers/update/action.ts create mode 100644 src/handlers/update/index.tsx create mode 100644 src/handlers/update/update.test.ts diff --git a/src/handlers/index.tsx b/src/handlers/index.tsx index a2ff49e03..dde648a9d 100644 --- a/src/handlers/index.tsx +++ b/src/handlers/index.tsx @@ -8,6 +8,7 @@ import { createRuntimeHandler } from "./runtime/index.tsx"; import { DebugKey, EndpointKey, JsonKey, RegionKey } from "./keys.tsx"; import { createConfigHandler } from "./config/"; import { createProjectHandler } from "./project/index.ts"; +import { createUpdateHandler } from "./update/index.tsx"; import { renderTui } from "../tui"; import { withRegion, withJsonRenderer, withLogging, withGlobalConfigAccessor } from "../middleware"; import type { AppIO } from "../io"; @@ -55,6 +56,7 @@ export function createRootHandler(core: Core, config: RootHandlerConfig): Router root.handler(createEvalHandler(core, io)); root.handler(createConfigHandler()); root.handler(createProjectHandler({ core, io })); + root.handler(createUpdateHandler(io)); // Invoking with no subcommand launches the interactive TUI. root.default(renderTui(core, io)); diff --git a/src/handlers/root.test.tsx b/src/handlers/root.test.tsx index b3f4e3386..8fcf36279 100644 --- a/src/handlers/root.test.tsx +++ b/src/handlers/root.test.tsx @@ -19,6 +19,7 @@ describe("createRootHandler", () => { "eval", "config", "project", + "update", ]); }); }); diff --git a/src/handlers/update/action.ts b/src/handlers/update/action.ts new file mode 100644 index 000000000..c73c9a9d1 --- /dev/null +++ b/src/handlers/update/action.ts @@ -0,0 +1,106 @@ +import { runProcess, type ProcessRunner } from "../../io"; +import { PACKAGE_VERSION } from "../../constants"; + +const PACKAGE_NAME = "@aws/agentcore"; +const REGISTRY_URL = "https://registry.npmjs.org"; + +function distTag(): string { + return PACKAGE_VERSION.includes("-") ? "preview" : "latest"; +} + +export function installArgv(): string[] { + return ["npm", "install", "-g", `${PACKAGE_NAME}@${distTag()}`]; +} + +export async function fetchLatestVersion(): Promise { + const response = await fetch(`${REGISTRY_URL}/${PACKAGE_NAME}/latest`); + if (!response.ok) { + throw new Error(`Failed to fetch latest version: ${response.statusText}`); + } + const data = (await response.json()) as { version: string }; + return data.version; +} + +export function compareVersions(current: string, latest: string): number { + const parse = (v: string) => { + const [core = "", ...prereleaseParts] = v.split("-"); + const nums = core.split(".").map(Number); + const prerelease = prereleaseParts.join("-"); + return { nums, prerelease }; + }; + + const curr = parse(current); + const lat = parse(latest); + + for (let i = 0; i < 3; i++) { + const c = curr.nums[i] ?? 0; + const l = lat.nums[i] ?? 0; + if (l > c) return 1; + if (l < c) return -1; + } + + if (!curr.prerelease && !lat.prerelease) return 0; + if (!curr.prerelease) return -1; + if (!lat.prerelease) return 1; + + const currSegments = curr.prerelease.split("."); + const latSegments = lat.prerelease.split("."); + const len = Math.max(currSegments.length, latSegments.length); + + for (let i = 0; i < len; i++) { + const cs = currSegments[i]; + const ls = latSegments[i]; + if (cs === undefined) return 1; + if (ls === undefined) return -1; + const cn = Number(cs); + const ln = Number(ls); + if (!isNaN(cn) && !isNaN(ln)) { + if (ln > cn) return 1; + if (ln < cn) return -1; + } else { + if (ls > cs) return 1; + if (ls < cs) return -1; + } + } + + return 0; +} + +export type UpdateStatus = + "up-to-date" | "newer-local" | "update-available" | "updated" | "update-failed"; + +export interface UpdateResult { + status: UpdateStatus; + currentVersion: string; + latestVersion: string; +} + +export interface HandleUpdateOptions { + runner?: ProcessRunner; + onOutput?: (chunk: string) => void; +} + +export async function handleUpdate( + checkOnly: boolean, + { runner = runProcess, onOutput }: HandleUpdateOptions = {}, +): Promise { + const latestVersion = await fetchLatestVersion(); + const comparison = compareVersions(PACKAGE_VERSION, latestVersion); + + if (comparison === 0) { + return { status: "up-to-date", currentVersion: PACKAGE_VERSION, latestVersion }; + } + if (comparison < 0) { + return { status: "newer-local", currentVersion: PACKAGE_VERSION, latestVersion }; + } + if (checkOnly) { + return { status: "update-available", currentVersion: PACKAGE_VERSION, latestVersion }; + } + + try { + await runner(installArgv(), { cwd: process.cwd(), onOutput }); + return { status: "updated", currentVersion: PACKAGE_VERSION, latestVersion }; + } catch { + return { status: "update-failed", currentVersion: PACKAGE_VERSION, latestVersion }; + } +} diff --git a/src/handlers/update/index.tsx b/src/handlers/update/index.tsx new file mode 100644 index 000000000..d07a12b28 --- /dev/null +++ b/src/handlers/update/index.tsx @@ -0,0 +1,24 @@ +import z from "zod"; +import { createHandler, flag } from "../../router"; +import { SilentCLIError } from "../../errors"; +import { JsonRendererKey } from "../../tui"; +import type { AppIO } from "../../io"; +import { handleUpdate } from "./action"; + +export const createUpdateHandler = (io: AppIO) => + createHandler({ + name: "update", + description: "Check for and install CLI updates", + flags: [flag("check", "check for updates without installing", z.boolean().default(false))], + handle: async (ctx, flags) => { + const result = await handleUpdate(flags.check, { + onOutput: (chunk) => io.stderr.write(chunk), + }); + + ctx.require(JsonRendererKey).renderJson(result); + + if (result.status === "update-failed") { + throw new SilentCLIError("failed to install update", { exitCode: 1 }); + } + }, + }); diff --git a/src/handlers/update/update.test.ts b/src/handlers/update/update.test.ts new file mode 100644 index 000000000..f11f62418 --- /dev/null +++ b/src/handlers/update/update.test.ts @@ -0,0 +1,109 @@ +import { afterEach, describe, expect, mock, spyOn, test } from "bun:test"; +import { compareVersions, fetchLatestVersion, handleUpdate } from "./action"; +import type { ProcessRunner } from "../../io"; + +// No golden/fixture tests here: the repo's *.fixture.test.tsx harness records and +// replays AWS SDK responses through CoreClient, but `update` makes no AWS calls — +// it queries the npm registry (fetch) and shells out to `npm install -g` +// (runProcess). There is nothing for that harness to record, so a fetch spy plus +// an injected fake runner is the right, hermetic way to cover this command. + +describe("compareVersions", () => { + const cases: Array<[string, string, number]> = [ + ["1.2.3", "1.2.3", 0], + ["1.0.0", "2.0.0", 1], + ["2.0.0", "1.0.0", -1], + ["1.1.0", "1.2.0", 1], + ["1.2.0", "1.1.0", -1], + ["1.2.3", "1.2.4", 1], + ["1.2.4", "1.2.3", -1], + ["1.0", "1.0.0", 0], + ["1.0.0-preview", "1.0.0", 1], + ["1.0.0", "1.0.0-preview", -1], + ["1.0.0-preview.1", "1.0.0-preview.2", 1], + ["1.0.0-preview.2", "1.0.0-preview.1", -1], + ["1.0.0-alpha", "1.0.0-beta", 1], + ["1.0.0-beta", "1.0.0-alpha", -1], + ]; + test.each(cases)("compareVersions(%p, %p) === %p", (current, latest, expected) => { + expect(compareVersions(current, latest)).toBe(expected); + }); +}); + +describe("fetchLatestVersion", () => { + afterEach(() => { + spyOn(globalThis, "fetch").mockRestore(); + }); + + test("returns the version from the npm registry", async () => { + const fetchSpy = spyOn(globalThis, "fetch").mockResolvedValue( + new Response(JSON.stringify({ version: "9.9.9" }), { status: 200 }), + ); + expect(await fetchLatestVersion()).toBe("9.9.9"); + expect(fetchSpy).toHaveBeenCalledWith("https://registry.npmjs.org/@aws/agentcore/latest"); + }); + + test("throws when the registry responds non-OK", async () => { + spyOn(globalThis, "fetch").mockResolvedValue( + new Response("", { status: 404, statusText: "Not Found" }), + ); + await expect(fetchLatestVersion()).rejects.toThrow("Failed to fetch latest version: Not Found"); + }); +}); + +describe("handleUpdate", () => { + afterEach(() => { + spyOn(globalThis, "fetch").mockRestore(); + }); + + const mockLatest = (version: string) => + spyOn(globalThis, "fetch").mockResolvedValue( + new Response(JSON.stringify({ version }), { status: 200 }), + ); + const okRunner: ProcessRunner = mock(async () => {}); + const failRunner: ProcessRunner = mock(async () => { + throw new Error("npm exploded"); + }); + + test("up-to-date when versions match, without invoking the runner", async () => { + mockLatest("1.0.0"); + const runner: ProcessRunner = mock(async () => {}); + expect(await handleUpdate(false, { runner })).toEqual({ + status: "up-to-date", + currentVersion: "1.0.0", + latestVersion: "1.0.0", + }); + expect(runner).not.toHaveBeenCalled(); + }); + + test("newer-local when local is ahead of the registry", async () => { + mockLatest("0.9.0"); + expect((await handleUpdate(false)).status).toBe("newer-local"); + }); + + test("update-available when newer exists and checkOnly is set (no install)", async () => { + mockLatest("2.0.0"); + const runner: ProcessRunner = mock(async () => {}); + expect(await handleUpdate(true, { runner })).toEqual({ + status: "update-available", + currentVersion: "1.0.0", + latestVersion: "2.0.0", + }); + expect(runner).not.toHaveBeenCalled(); + }); + + test("updated when the install runner succeeds", async () => { + mockLatest("2.0.0"); + const result = await handleUpdate(false, { runner: okRunner }); + expect(result.status).toBe("updated"); + expect(okRunner).toHaveBeenCalledWith( + ["npm", "install", "-g", "@aws/agentcore@latest"], + expect.objectContaining({ cwd: expect.any(String) }), + ); + }); + + test("update-failed when the install runner throws", async () => { + mockLatest("2.0.0"); + expect((await handleUpdate(false, { runner: failRunner })).status).toBe("update-failed"); + }); +}); From 1d6e8216a9d13bfd6e6a1d46420d238d5858285e Mon Sep 17 00:00:00 2001 From: jariy17 Date: Tue, 1 Sep 2026 16:24:50 +0000 Subject: [PATCH 2/7] refactor(update): use semver for version comparison Replace the hand-rolled compareVersions with semver.compare (semver was already resolved transitively; added as a direct dep + @types/semver). Fixes the edge cases the hand-rolled parser mishandled (build metadata, v-prefix/non-numeric -> NaN treated as equal, 4th segment ignored, mixed numeric/alphanumeric prerelease precedence). Addresses review feedback. --- bun.lock | 45 +++++++++++++++++----------- package.json | 2 ++ src/handlers/update/action.ts | 48 ++---------------------------- src/handlers/update/update.test.ts | 24 +-------------- 4 files changed, 33 insertions(+), 86 deletions(-) diff --git a/bun.lock b/bun.lock index a27d2b9f5..e99604441 100644 --- a/bun.lock +++ b/bun.lock @@ -1,5 +1,5 @@ { - "lockfileVersion": 3, + "lockfileVersion": 1, "configVersion": 1, "workspaces": { "": { @@ -30,6 +30,7 @@ "react": "^19.2.7", "react-devtools-core": "^7.0.1", "react-router": "^8.3.0", + "semver": "^7.8.5", "string-width": "^8.2.2", "winston": "^3.19.0", "winston-daily-rotate-file": "^5.0.0", @@ -39,6 +40,7 @@ "@secretlint/secretlint-rule-preset-recommend": "^12.2.0", "@types/bun": "latest", "@types/react": "^19.2.17", + "@types/semver": "^7.8.0", "husky": "^9.1.7", "ink-testing-library": "^4.0.0", "lint-staged": "^17.0.8", @@ -52,9 +54,6 @@ }, }, "overrides": { - "@aws-cdk/toolkit-lib": { - "yaml": "^1", - }, "@opentelemetry/core": "^2.10.0", }, "packages": { @@ -262,7 +261,7 @@ "@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.213.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-zRM5/Qj6G84Ej3F1yt33xBVY/3tnMxtL1fiDIxYbDWYaZ/eudVw3/PBiZ8G7JwUxXxjW8gU4g6LnOyfGKYHYgw=="], - "@opentelemetry/core": ["@opentelemetry/core@2.10.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-/wNZ8twnEQQA4HoHu22+vcsdru6pWPWxW+7w+FlxT6Id7PE/WIbZmVKkte+PF72e0F2dnImFeHD2syyE1Mw6MQ=="], + "@opentelemetry/core": ["@opentelemetry/core@2.11.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-7YP44XH0tV6+Mb54x2YGf84i7yi+31MBZlE8JwvozkxyTvXbSp10X7cI7YE49ChJ3shMJoBmCJF3+1QFBJctGA=="], "@opentelemetry/exporter-metrics-otlp-http": ["@opentelemetry/exporter-metrics-otlp-http@0.221.0", "", { "dependencies": { "@opentelemetry/core": "2.10.0", "@opentelemetry/otlp-exporter-base": "0.221.0", "@opentelemetry/otlp-transformer": "0.221.0", "@opentelemetry/resources": "2.10.0", "@opentelemetry/sdk-metrics": "2.10.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-sRfCKbOzgy8xZQV2as0RzIZlnCmCseCKZGLfRcrpo2CBngJDr+rPtX0zkG0+oUCV5kfQPUoW3W3C96Ag3Y/Clg=="], @@ -270,11 +269,11 @@ "@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.213.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.213.0", "@opentelemetry/core": "2.6.0", "@opentelemetry/resources": "2.6.0", "@opentelemetry/sdk-logs": "0.213.0", "@opentelemetry/sdk-metrics": "2.6.0", "@opentelemetry/sdk-trace-base": "2.6.0", "protobufjs": "^7.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-RSuAlxFFPjeK4d5Y6ps8L2WhaQI6CXWllIjvo5nkAlBpmq2XdYWEBGiAbOF4nDs8CX4QblJDv5BbMUft3sEfDw=="], - "@opentelemetry/resources": ["@opentelemetry/resources@2.10.0", "", { "dependencies": { "@opentelemetry/core": "2.10.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-q6MMm2zhggzsHVNbabYwut+a6nbuQQe3URUoxaojM/8K1IBfwwPzvxIjNi2/lI1TFe+fMHMW9MWhrtDLEXEnkA=="], + "@opentelemetry/resources": ["@opentelemetry/resources@2.11.0", "", { "dependencies": { "@opentelemetry/core": "2.11.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-Ie7+8q8MDF4FAEQCKVMTx3ReUvxiIAgIiiW3c9JdmP8+HMcDy20puT+AHjexnExgnbvBxjQ9fjkFDWrikJ2jQA=="], "@opentelemetry/sdk-logs": ["@opentelemetry/sdk-logs@0.213.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.213.0", "@opentelemetry/core": "2.6.0", "@opentelemetry/resources": "2.6.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, "sha512-00xlU3GZXo3kXKve4DLdrAL0NAFUaZ9appU/mn00S/5kSUdAvyYsORaDUfR04Mp2CLagAOhrzfUvYozY/EZX2g=="], - "@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.10.0", "", { "dependencies": { "@opentelemetry/core": "2.10.0", "@opentelemetry/resources": "2.10.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-t6r1VSvXNtSDnPXU1FbZeetJb7yyovHmgu0wRSoftxtE0g2rSNhQZQUy69sRUCL+iioJpX8SN/S6wq6ZtvLySQ=="], + "@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.11.0", "", { "dependencies": { "@opentelemetry/core": "2.11.0", "@opentelemetry/resources": "2.11.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-7GXXcObyHyDUUSG+L+kJoquty01bzm7ivE7+SSgXXJcHuPzGviptxwARmI2c+bnnxjexGQbJnyNlN8HxBP/Y7A=="], "@opentelemetry/sdk-trace": ["@opentelemetry/sdk-trace@2.10.0", "", { "dependencies": { "@opentelemetry/core": "2.10.0", "@opentelemetry/resources": "2.10.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-MfQGq3GRmTh5fM/y+OjaO0vj6+luCB1XO2gfXCalKCfgKw0eHL++sm75DNweC6ohlp+aFvACqeE0fYayqdRaoQ=="], @@ -376,7 +375,7 @@ "@smithy/node-config-provider": ["@smithy/node-config-provider@4.6.2", "", { "dependencies": { "@smithy/core": "^3.33.2", "tslib": "^2.6.2" } }, "sha512-zMrXu/O5tPa7GLtra8L4wFG6DACcXT9QV4Ay+WEAjUhXm1dVq7c/q9Qv9gkJZNLY8hmQKg08778kDcxpKNMqOA=="], - "@smithy/node-http-handler": ["@smithy/node-http-handler@4.11.3", "", { "dependencies": { "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-2jY1tSpERfPfWqyBV2pH+iGFaghVsIJszJNsT7hxtQYhVJpWDyc0LqOWI+nXOxOAHaEfZ4PXXtp1wW1TGpHhkA=="], + "@smithy/node-http-handler": ["@smithy/node-http-handler@4.12.0", "", { "dependencies": { "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-0mq1pHadfyXCYCqm2cNpbjNIT+fbaUpNxewZb/YNr2L0IrEVMOb8gM/Fl4K6XvHCW3uSNDFwPl/+iKm0bx9jYg=="], "@smithy/property-provider": ["@smithy/property-provider@4.5.2", "", { "dependencies": { "@smithy/core": "^3.33.2", "tslib": "^2.6.2" } }, "sha512-SPJCSCCGpHf5g5b8244ig3WVKIdv2DS+X6cfmy4bpKEYo4VlLYM+bGHpHbfbmp76vegMKQBvs7DEpwQ5YuhKLA=="], @@ -430,23 +429,25 @@ "@types/react": ["@types/react@19.2.18", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w=="], + "@types/semver": ["@types/semver@7.8.0", "", {}, "sha512-1mAINjtQCXXeLkJ9ehXkwOcBpqtLxiVtKhpUf83DdRNdQKV0iXZpaHYqRr7nj+wvxuJzoAmAwXI+sCNMv1CzLQ=="], + "@types/triple-beam": ["@types/triple-beam@1.3.5", "", {}, "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw=="], "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], - "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.68.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.68.0", "@typescript-eslint/types": "^8.68.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-5GQtWZCXFcFYux955pvoS02WLc49pXNlvIxocKjS0clvwo3in1RdlzVKyiqQH9vE5AKWFLTaUgeQkOrTS+0Qxw=="], + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.69.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.69.0", "@typescript-eslint/types": "^8.69.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-yi4obFrHMmnsesWehHbkg9zMA7Jt8cXT+mKM08G999pH1yT6nqgsHx7MYm0uY1wAj8CqiBXYRJ7WAT0QdQHQXg=="], - "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.68.0", "", { "dependencies": { "@typescript-eslint/types": "8.68.0", "@typescript-eslint/visitor-keys": "8.68.0" } }, "sha512-T5eXpcaJNg8bhjHJ8Rjp68Vq/QBteYtTKY8TZqVNPaUbuz0f6jI9t6aDkylwvalpAB9XTTFeFOjrjXAZ3YvmVA=="], + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.69.0", "", { "dependencies": { "@typescript-eslint/types": "8.69.0", "@typescript-eslint/visitor-keys": "8.69.0" } }, "sha512-ewfspqWvSxKSOaplqAUNbaSFO0eB6w1EtQ+esfYFRm3614Ty4uNtExkcbgd6nWsXphbqKyf9ZYdbZdv2xEoWEQ=="], - "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.68.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-F7zrGQfiJHojPwi8vhxZQC1tWtJzvL74cK/nqri2lk8YUXvYaYwl263xOJ69jDWPUk1hmcdoayFwk9lX09npVw=="], + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.69.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-xNqK7YTDZsLniQMV/4rpFR8Z5JlqeRvVjuG1YgF/mdPVH84HSD19L8CczMA0qg2RfwEV231GHH3VnToJDo4MfQ=="], - "@typescript-eslint/types": ["@typescript-eslint/types@8.68.0", "", {}, "sha512-9RnpsGJjrAllCMefGVVsImJM24YurhC0Q1h4UbvivtvOqXmR/vEJge2OoE++z9m6hyg8T1Q8t5SNT6tHSbrxcg=="], + "@typescript-eslint/types": ["@typescript-eslint/types@8.69.0", "", {}, "sha512-K3VrubUPhlo9VDBS6QdI8YB5j7ClpqLRdefcz6PFrhnwicehBweqQ9Evhl4l+FYz0HdDmMqIiSX0aldGRYtDCA=="], - "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.68.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.68.0", "@typescript-eslint/tsconfig-utils": "8.68.0", "@typescript-eslint/types": "8.68.0", "@typescript-eslint/visitor-keys": "8.68.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-OKKsD0tYmoNiU5PW2zehO1yO56jYOm1ShYlxon/Z0SJNidAkdVg86eg9ruRuoXf8xfnuWZGbwDsStkoXbZtIIA=="], + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.69.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.69.0", "@typescript-eslint/tsconfig-utils": "8.69.0", "@typescript-eslint/types": "8.69.0", "@typescript-eslint/visitor-keys": "8.69.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-AdFkgqck3Vudb/kWnxlyafU/4aBhHrbQ9locP2N4psXTy5mOBg0SHJumnLvx7r6g1gV4DKvUFwV2nJZBoqOD8w=="], - "@typescript-eslint/utils": ["@typescript-eslint/utils@8.68.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.68.0", "@typescript-eslint/types": "8.68.0", "@typescript-eslint/typescript-estree": "8.68.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-PB5gJMMOg0Q5P1tsgWtEAqQacJXq0qEqRHDX/YJ4FaTMLfZPpHB3gjl2EJuiZyPABxmj4ZQYiY9m1bdAJ5y7tQ=="], + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.69.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.69.0", "@typescript-eslint/types": "8.69.0", "@typescript-eslint/typescript-estree": "8.69.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-tUbx60BBqQa31kXF5MCsOOLL5E/WzUuxIn7YpAvq+eaUlqvk8/NXnXMBNAdLCr0icjkzem7iUA5QqWHe/hJ1aw=="], - "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.68.0", "", { "dependencies": { "@typescript-eslint/types": "8.68.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-YR65gGdGvTUAWLldC3xLOvOzamdGzB4A5/N8rehEaHs3Zvoe39BhgY+u0SPch1OvrVTfLcc55wsSgK2NcnTS/A=="], + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.69.0", "", { "dependencies": { "@typescript-eslint/types": "8.69.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-+rmdgPA+EXkNgKYvHvFfhrs35utXbwaC5PGpDquSXcoXQDKUA5UjV0LmTucG/4JXkM31BTu4TilHtrN8IVBe8w=="], "@ungap/structured-clone": ["@ungap/structured-clone@1.4.0", "", {}, "sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ=="], @@ -602,7 +603,7 @@ "editions": ["editions@6.22.0", "", { "dependencies": { "version-range": "^4.15.0" } }, "sha512-UgGlf8IW75je7HZjNDpJdCv4cGJWIi6yumFdZ0R7A8/CIhQiWUjyGLCxdHpd8bmyD1gnkfUNK0oeOXqUS2cpfQ=="], - "electron-to-chromium": ["electron-to-chromium@1.5.416", "", {}, "sha512-K6bvB2BjnNrugtIih6ewlbBI9DXa976jIdiIlRLHhBoEI9a4JaQjjHyF+A1IQI543aQYR4LnmOrT/K5fZj0aPA=="], + "electron-to-chromium": ["electron-to-chromium@1.5.417", "", {}, "sha512-4T+DTDWuMPM4aHlHwWdAVCVWwp7LDilnhzkj+c/Lbj91XSQrLuOmZSLtS9Q4iIqjlPUbPOnC624zDVVHCHaolQ=="], "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], @@ -768,7 +769,7 @@ "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], - "ignore": ["ignore@7.0.7", "", {}, "sha512-dML0wP6oak21rsNYCJpJB6O1BJIEwNpGrTw0URPfAk4hm0e3pRfCtzkfB6olBcXcVlU2rouCyz7lCyRB0OMVCA=="], + "ignore": ["ignore@7.0.8", "", {}, "sha512-YYNsSlXBjMk92SKnkwvB5LOVSa6OznlFUGcsvrFgNJbJCd0M1XKeFVRc8ZByeCqz32FivYNHJVooLmdqrmvp/Q=="], "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], @@ -1498,6 +1499,10 @@ "@opentelemetry/exporter-metrics-otlp-http/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.221.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.221.0", "@opentelemetry/core": "2.10.0", "@opentelemetry/resources": "2.10.0", "@opentelemetry/sdk-logs": "0.221.0", "@opentelemetry/sdk-metrics": "2.10.0", "@opentelemetry/sdk-trace": "2.10.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-lg6lkOU08Az23jVcn/0Els9HP+V8PnR4Km6p0KgpTggS0n/WuhnmY64rSh83Of9iR9nD+dpWr6adlcX8KzAwjg=="], + "@opentelemetry/exporter-metrics-otlp-http/@opentelemetry/resources": ["@opentelemetry/resources@2.10.0", "", { "dependencies": { "@opentelemetry/core": "2.10.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-q6MMm2zhggzsHVNbabYwut+a6nbuQQe3URUoxaojM/8K1IBfwwPzvxIjNi2/lI1TFe+fMHMW9MWhrtDLEXEnkA=="], + + "@opentelemetry/exporter-metrics-otlp-http/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.10.0", "", { "dependencies": { "@opentelemetry/core": "2.10.0", "@opentelemetry/resources": "2.10.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-t6r1VSvXNtSDnPXU1FbZeetJb7yyovHmgu0wRSoftxtE0g2rSNhQZQUy69sRUCL+iioJpX8SN/S6wq6ZtvLySQ=="], + "@opentelemetry/otlp-exporter-base/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.221.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.221.0", "@opentelemetry/core": "2.10.0", "@opentelemetry/resources": "2.10.0", "@opentelemetry/sdk-logs": "0.221.0", "@opentelemetry/sdk-metrics": "2.10.0", "@opentelemetry/sdk-trace": "2.10.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-lg6lkOU08Az23jVcn/0Els9HP+V8PnR4Km6p0KgpTggS0n/WuhnmY64rSh83Of9iR9nD+dpWr6adlcX8KzAwjg=="], "@opentelemetry/otlp-transformer/@opentelemetry/resources": ["@opentelemetry/resources@2.6.0", "", { "dependencies": { "@opentelemetry/core": "2.6.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-D4y/+OGe3JSuYUCBxtH5T9DSAWNcvCb/nQWIga8HNtXTVPQn59j0nTBAgaAXxUVBDl40mG3Tc76b46wPlZaiJQ=="], @@ -1506,6 +1511,8 @@ "@opentelemetry/sdk-logs/@opentelemetry/resources": ["@opentelemetry/resources@2.6.0", "", { "dependencies": { "@opentelemetry/core": "2.6.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-D4y/+OGe3JSuYUCBxtH5T9DSAWNcvCb/nQWIga8HNtXTVPQn59j0nTBAgaAXxUVBDl40mG3Tc76b46wPlZaiJQ=="], + "@opentelemetry/sdk-trace/@opentelemetry/resources": ["@opentelemetry/resources@2.10.0", "", { "dependencies": { "@opentelemetry/core": "2.10.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-q6MMm2zhggzsHVNbabYwut+a6nbuQQe3URUoxaojM/8K1IBfwwPzvxIjNi2/lI1TFe+fMHMW9MWhrtDLEXEnkA=="], + "@opentelemetry/sdk-trace-base/@opentelemetry/resources": ["@opentelemetry/resources@2.6.0", "", { "dependencies": { "@opentelemetry/core": "2.6.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-D4y/+OGe3JSuYUCBxtH5T9DSAWNcvCb/nQWIga8HNtXTVPQn59j0nTBAgaAXxUVBDl40mG3Tc76b46wPlZaiJQ=="], "@secretlint/formatter/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], @@ -1616,8 +1623,12 @@ "@opentelemetry/otlp-exporter-base/@opentelemetry/otlp-transformer/@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.221.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-OlanaW1vv7ufTqQ3/fPLI4arGt5ZoM+P8abOMki6uEYnpRazepSWDwDnnw+la7kE26SHVC18//SMccrDvLKOXQ=="], + "@opentelemetry/otlp-exporter-base/@opentelemetry/otlp-transformer/@opentelemetry/resources": ["@opentelemetry/resources@2.10.0", "", { "dependencies": { "@opentelemetry/core": "2.10.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-q6MMm2zhggzsHVNbabYwut+a6nbuQQe3URUoxaojM/8K1IBfwwPzvxIjNi2/lI1TFe+fMHMW9MWhrtDLEXEnkA=="], + "@opentelemetry/otlp-exporter-base/@opentelemetry/otlp-transformer/@opentelemetry/sdk-logs": ["@opentelemetry/sdk-logs@0.221.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.221.0", "@opentelemetry/core": "2.10.0", "@opentelemetry/resources": "2.10.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, "sha512-FaDcazjyMp7TZZZAsqbo4IkovP0UegoCu0EBkiNt+qCqvUf7FPAsfcrZ3+ZEkKgXZ/jHafop+JoGPDk3A0SmLg=="], + "@opentelemetry/otlp-exporter-base/@opentelemetry/otlp-transformer/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.10.0", "", { "dependencies": { "@opentelemetry/core": "2.10.0", "@opentelemetry/resources": "2.10.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-t6r1VSvXNtSDnPXU1FbZeetJb7yyovHmgu0wRSoftxtE0g2rSNhQZQUy69sRUCL+iioJpX8SN/S6wq6ZtvLySQ=="], + "@textlint/linter-formatter/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], "@textlint/linter-formatter/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], diff --git a/package.json b/package.json index 1232e5277..ec7259ada 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "@secretlint/secretlint-rule-preset-recommend": "^12.2.0", "@types/bun": "latest", "@types/react": "^19.2.17", + "@types/semver": "^7.8.0", "husky": "^9.1.7", "ink-testing-library": "^4.0.0", "lint-staged": "^17.0.8", @@ -80,6 +81,7 @@ "react": "^19.2.7", "react-devtools-core": "^7.0.1", "react-router": "^8.3.0", + "semver": "^7.8.5", "string-width": "^8.2.2", "winston": "^3.19.0", "winston-daily-rotate-file": "^5.0.0", diff --git a/src/handlers/update/action.ts b/src/handlers/update/action.ts index c73c9a9d1..80bbe7095 100644 --- a/src/handlers/update/action.ts +++ b/src/handlers/update/action.ts @@ -1,3 +1,4 @@ +import semver from "semver"; import { runProcess, type ProcessRunner } from "../../io"; import { PACKAGE_VERSION } from "../../constants"; @@ -21,51 +22,6 @@ export async function fetchLatestVersion(): Promise { return data.version; } -export function compareVersions(current: string, latest: string): number { - const parse = (v: string) => { - const [core = "", ...prereleaseParts] = v.split("-"); - const nums = core.split(".").map(Number); - const prerelease = prereleaseParts.join("-"); - return { nums, prerelease }; - }; - - const curr = parse(current); - const lat = parse(latest); - - for (let i = 0; i < 3; i++) { - const c = curr.nums[i] ?? 0; - const l = lat.nums[i] ?? 0; - if (l > c) return 1; - if (l < c) return -1; - } - - if (!curr.prerelease && !lat.prerelease) return 0; - if (!curr.prerelease) return -1; - if (!lat.prerelease) return 1; - - const currSegments = curr.prerelease.split("."); - const latSegments = lat.prerelease.split("."); - const len = Math.max(currSegments.length, latSegments.length); - - for (let i = 0; i < len; i++) { - const cs = currSegments[i]; - const ls = latSegments[i]; - if (cs === undefined) return 1; - if (ls === undefined) return -1; - const cn = Number(cs); - const ln = Number(ls); - if (!isNaN(cn) && !isNaN(ln)) { - if (ln > cn) return 1; - if (ln < cn) return -1; - } else { - if (ls > cs) return 1; - if (ls < cs) return -1; - } - } - - return 0; -} - export type UpdateStatus = "up-to-date" | "newer-local" | "update-available" | "updated" | "update-failed"; @@ -85,7 +41,7 @@ export async function handleUpdate( { runner = runProcess, onOutput }: HandleUpdateOptions = {}, ): Promise { const latestVersion = await fetchLatestVersion(); - const comparison = compareVersions(PACKAGE_VERSION, latestVersion); + const comparison = semver.compare(latestVersion, PACKAGE_VERSION); if (comparison === 0) { return { status: "up-to-date", currentVersion: PACKAGE_VERSION, latestVersion }; diff --git a/src/handlers/update/update.test.ts b/src/handlers/update/update.test.ts index f11f62418..ea5a4d495 100644 --- a/src/handlers/update/update.test.ts +++ b/src/handlers/update/update.test.ts @@ -1,5 +1,5 @@ import { afterEach, describe, expect, mock, spyOn, test } from "bun:test"; -import { compareVersions, fetchLatestVersion, handleUpdate } from "./action"; +import { fetchLatestVersion, handleUpdate } from "./action"; import type { ProcessRunner } from "../../io"; // No golden/fixture tests here: the repo's *.fixture.test.tsx harness records and @@ -8,28 +8,6 @@ import type { ProcessRunner } from "../../io"; // (runProcess). There is nothing for that harness to record, so a fetch spy plus // an injected fake runner is the right, hermetic way to cover this command. -describe("compareVersions", () => { - const cases: Array<[string, string, number]> = [ - ["1.2.3", "1.2.3", 0], - ["1.0.0", "2.0.0", 1], - ["2.0.0", "1.0.0", -1], - ["1.1.0", "1.2.0", 1], - ["1.2.0", "1.1.0", -1], - ["1.2.3", "1.2.4", 1], - ["1.2.4", "1.2.3", -1], - ["1.0", "1.0.0", 0], - ["1.0.0-preview", "1.0.0", 1], - ["1.0.0", "1.0.0-preview", -1], - ["1.0.0-preview.1", "1.0.0-preview.2", 1], - ["1.0.0-preview.2", "1.0.0-preview.1", -1], - ["1.0.0-alpha", "1.0.0-beta", 1], - ["1.0.0-beta", "1.0.0-alpha", -1], - ]; - test.each(cases)("compareVersions(%p, %p) === %p", (current, latest, expected) => { - expect(compareVersions(current, latest)).toBe(expected); - }); -}); - describe("fetchLatestVersion", () => { afterEach(() => { spyOn(globalThis, "fetch").mockRestore(); From 9857cda6c44015d43f5b491ad0528f5ce2fe5108 Mon Sep 17 00:00:00 2001 From: jariy17 Date: Tue, 1 Sep 2026 16:42:24 +0000 Subject: [PATCH 3/7] fix(update): classify registry failures and surface install errors - fetchLatestVersion throws the existing NetworkingError on both a non-OK registry response and a fetch reject (offline), instead of a raw Error / undici TypeError. - update-failed now carries the caught error message in UpdateResult.error, so the always-JSON output explains the failure even when npm produced no stderr (e.g. npm missing -> ENOENT before any output streams). Addresses review feedback. --- src/handlers/update/action.ts | 23 +++++++++++++++++++---- src/handlers/update/update.test.ts | 16 ++++++++++++---- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/src/handlers/update/action.ts b/src/handlers/update/action.ts index 80bbe7095..033fc160e 100644 --- a/src/handlers/update/action.ts +++ b/src/handlers/update/action.ts @@ -1,5 +1,6 @@ import semver from "semver"; import { runProcess, type ProcessRunner } from "../../io"; +import { NetworkingError } from "../../errors"; import { PACKAGE_VERSION } from "../../constants"; const PACKAGE_NAME = "@aws/agentcore"; @@ -14,9 +15,17 @@ export function installArgv(): string[] { } export async function fetchLatestVersion(): Promise { - const response = await fetch(`${REGISTRY_URL}/${PACKAGE_NAME}/latest`); + let response: Response; + try { + response = await fetch(`${REGISTRY_URL}/${PACKAGE_NAME}/latest`); + } catch (cause) { + throw new NetworkingError( + `Could not reach the npm registry: ${cause instanceof Error ? cause.message : String(cause)}`, + { cause }, + ); + } if (!response.ok) { - throw new Error(`Failed to fetch latest version: ${response.statusText}`); + throw new NetworkingError(`Failed to fetch latest version: ${response.statusText}`); } const data = (await response.json()) as { version: string }; return data.version; @@ -29,6 +38,7 @@ export interface UpdateResult { status: UpdateStatus; currentVersion: string; latestVersion: string; + error?: string; } export interface HandleUpdateOptions { @@ -56,7 +66,12 @@ export async function handleUpdate( try { await runner(installArgv(), { cwd: process.cwd(), onOutput }); return { status: "updated", currentVersion: PACKAGE_VERSION, latestVersion }; - } catch { - return { status: "update-failed", currentVersion: PACKAGE_VERSION, latestVersion }; + } catch (cause) { + return { + status: "update-failed", + currentVersion: PACKAGE_VERSION, + latestVersion, + error: cause instanceof Error ? cause.message : String(cause), + }; } } diff --git a/src/handlers/update/update.test.ts b/src/handlers/update/update.test.ts index ea5a4d495..b0d8e845d 100644 --- a/src/handlers/update/update.test.ts +++ b/src/handlers/update/update.test.ts @@ -1,5 +1,6 @@ import { afterEach, describe, expect, mock, spyOn, test } from "bun:test"; import { fetchLatestVersion, handleUpdate } from "./action"; +import { NetworkingError } from "../../errors"; import type { ProcessRunner } from "../../io"; // No golden/fixture tests here: the repo's *.fixture.test.tsx harness records and @@ -21,11 +22,16 @@ describe("fetchLatestVersion", () => { expect(fetchSpy).toHaveBeenCalledWith("https://registry.npmjs.org/@aws/agentcore/latest"); }); - test("throws when the registry responds non-OK", async () => { + test("throws a NetworkingError when the registry responds non-OK", async () => { spyOn(globalThis, "fetch").mockResolvedValue( new Response("", { status: 404, statusText: "Not Found" }), ); - await expect(fetchLatestVersion()).rejects.toThrow("Failed to fetch latest version: Not Found"); + await expect(fetchLatestVersion()).rejects.toBeInstanceOf(NetworkingError); + }); + + test("wraps a fetch failure (offline) as a NetworkingError", async () => { + spyOn(globalThis, "fetch").mockRejectedValue(new TypeError("fetch failed")); + await expect(fetchLatestVersion()).rejects.toBeInstanceOf(NetworkingError); }); }); @@ -80,8 +86,10 @@ describe("handleUpdate", () => { ); }); - test("update-failed when the install runner throws", async () => { + test("update-failed carries the error reason when the install runner throws", async () => { mockLatest("2.0.0"); - expect((await handleUpdate(false, { runner: failRunner })).status).toBe("update-failed"); + const result = await handleUpdate(false, { runner: failRunner }); + expect(result.status).toBe("update-failed"); + expect(result.error).toBe("npm exploded"); }); }); From e5974d9fcce96be21f11b041fc9dee80b216af13 Mon Sep 17 00:00:00 2001 From: jariy17 Date: Tue, 1 Sep 2026 16:43:48 +0000 Subject: [PATCH 4/7] refactor(update): inline action.ts into index.tsx Collapse the update command into a single file per review feedback; the logic stays unit-testable via the injected ProcessRunner and a fetch spy. Tests import from ./index. --- src/handlers/update/action.ts | 77 ---------------------------- src/handlers/update/index.tsx | 80 ++++++++++++++++++++++++++++-- src/handlers/update/update.test.ts | 2 +- 3 files changed, 78 insertions(+), 81 deletions(-) delete mode 100644 src/handlers/update/action.ts diff --git a/src/handlers/update/action.ts b/src/handlers/update/action.ts deleted file mode 100644 index 033fc160e..000000000 --- a/src/handlers/update/action.ts +++ /dev/null @@ -1,77 +0,0 @@ -import semver from "semver"; -import { runProcess, type ProcessRunner } from "../../io"; -import { NetworkingError } from "../../errors"; -import { PACKAGE_VERSION } from "../../constants"; - -const PACKAGE_NAME = "@aws/agentcore"; -const REGISTRY_URL = "https://registry.npmjs.org"; - -function distTag(): string { - return PACKAGE_VERSION.includes("-") ? "preview" : "latest"; -} - -export function installArgv(): string[] { - return ["npm", "install", "-g", `${PACKAGE_NAME}@${distTag()}`]; -} - -export async function fetchLatestVersion(): Promise { - let response: Response; - try { - response = await fetch(`${REGISTRY_URL}/${PACKAGE_NAME}/latest`); - } catch (cause) { - throw new NetworkingError( - `Could not reach the npm registry: ${cause instanceof Error ? cause.message : String(cause)}`, - { cause }, - ); - } - if (!response.ok) { - throw new NetworkingError(`Failed to fetch latest version: ${response.statusText}`); - } - const data = (await response.json()) as { version: string }; - return data.version; -} - -export type UpdateStatus = - "up-to-date" | "newer-local" | "update-available" | "updated" | "update-failed"; - -export interface UpdateResult { - status: UpdateStatus; - currentVersion: string; - latestVersion: string; - error?: string; -} - -export interface HandleUpdateOptions { - runner?: ProcessRunner; - onOutput?: (chunk: string) => void; -} - -export async function handleUpdate( - checkOnly: boolean, - { runner = runProcess, onOutput }: HandleUpdateOptions = {}, -): Promise { - const latestVersion = await fetchLatestVersion(); - const comparison = semver.compare(latestVersion, PACKAGE_VERSION); - - if (comparison === 0) { - return { status: "up-to-date", currentVersion: PACKAGE_VERSION, latestVersion }; - } - if (comparison < 0) { - return { status: "newer-local", currentVersion: PACKAGE_VERSION, latestVersion }; - } - if (checkOnly) { - return { status: "update-available", currentVersion: PACKAGE_VERSION, latestVersion }; - } - - try { - await runner(installArgv(), { cwd: process.cwd(), onOutput }); - return { status: "updated", currentVersion: PACKAGE_VERSION, latestVersion }; - } catch (cause) { - return { - status: "update-failed", - currentVersion: PACKAGE_VERSION, - latestVersion, - error: cause instanceof Error ? cause.message : String(cause), - }; - } -} diff --git a/src/handlers/update/index.tsx b/src/handlers/update/index.tsx index d07a12b28..4891b5d0f 100644 --- a/src/handlers/update/index.tsx +++ b/src/handlers/update/index.tsx @@ -1,9 +1,83 @@ import z from "zod"; +import semver from "semver"; import { createHandler, flag } from "../../router"; -import { SilentCLIError } from "../../errors"; +import { NetworkingError, SilentCLIError } from "../../errors"; +import { runProcess, type ProcessRunner, type AppIO } from "../../io"; import { JsonRendererKey } from "../../tui"; -import type { AppIO } from "../../io"; -import { handleUpdate } from "./action"; +import { PACKAGE_VERSION } from "../../constants"; + +const PACKAGE_NAME = "@aws/agentcore"; +const REGISTRY_URL = "https://registry.npmjs.org"; + +function distTag(): string { + return PACKAGE_VERSION.includes("-") ? "preview" : "latest"; +} + +export function installArgv(): string[] { + return ["npm", "install", "-g", `${PACKAGE_NAME}@${distTag()}`]; +} + +export async function fetchLatestVersion(): Promise { + let response: Response; + try { + response = await fetch(`${REGISTRY_URL}/${PACKAGE_NAME}/latest`); + } catch (cause) { + throw new NetworkingError( + `Could not reach the npm registry: ${cause instanceof Error ? cause.message : String(cause)}`, + { cause }, + ); + } + if (!response.ok) { + throw new NetworkingError(`Failed to fetch latest version: ${response.statusText}`); + } + const data = (await response.json()) as { version: string }; + return data.version; +} + +export type UpdateStatus = + "up-to-date" | "newer-local" | "update-available" | "updated" | "update-failed"; + +export interface UpdateResult { + status: UpdateStatus; + currentVersion: string; + latestVersion: string; + error?: string; +} + +export interface HandleUpdateOptions { + runner?: ProcessRunner; + onOutput?: (chunk: string) => void; +} + +export async function handleUpdate( + checkOnly: boolean, + { runner = runProcess, onOutput }: HandleUpdateOptions = {}, +): Promise { + const latestVersion = await fetchLatestVersion(); + const comparison = semver.compare(latestVersion, PACKAGE_VERSION); + + if (comparison === 0) { + return { status: "up-to-date", currentVersion: PACKAGE_VERSION, latestVersion }; + } + if (comparison < 0) { + return { status: "newer-local", currentVersion: PACKAGE_VERSION, latestVersion }; + } + if (checkOnly) { + return { status: "update-available", currentVersion: PACKAGE_VERSION, latestVersion }; + } + + try { + await runner(installArgv(), { cwd: process.cwd(), onOutput }); + return { status: "updated", currentVersion: PACKAGE_VERSION, latestVersion }; + } catch (cause) { + return { + status: "update-failed", + currentVersion: PACKAGE_VERSION, + latestVersion, + error: cause instanceof Error ? cause.message : String(cause), + }; + } +} export const createUpdateHandler = (io: AppIO) => createHandler({ diff --git a/src/handlers/update/update.test.ts b/src/handlers/update/update.test.ts index b0d8e845d..e979a23bd 100644 --- a/src/handlers/update/update.test.ts +++ b/src/handlers/update/update.test.ts @@ -1,5 +1,5 @@ import { afterEach, describe, expect, mock, spyOn, test } from "bun:test"; -import { fetchLatestVersion, handleUpdate } from "./action"; +import { fetchLatestVersion, handleUpdate } from "./index"; import { NetworkingError } from "../../errors"; import type { ProcessRunner } from "../../io"; From 1b7f64272bf37a594ac99c9c569b82f4234dccf7 Mon Sep 17 00:00:00 2001 From: jariy17 Date: Tue, 1 Sep 2026 16:47:40 +0000 Subject: [PATCH 5/7] fix(update): report a failed install as a non-silent error Throw a plain AgentCoreCLIError carrying the captured failure reason instead of SilentCLIError, so a failed self-update always prints why (incl. npm missing) and exits non-zero. --- src/handlers/update/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/handlers/update/index.tsx b/src/handlers/update/index.tsx index 4891b5d0f..a34d7c494 100644 --- a/src/handlers/update/index.tsx +++ b/src/handlers/update/index.tsx @@ -1,7 +1,7 @@ import z from "zod"; import semver from "semver"; import { createHandler, flag } from "../../router"; -import { NetworkingError, SilentCLIError } from "../../errors"; +import { AgentCoreCLIError, NetworkingError } from "../../errors"; import { runProcess, type ProcessRunner, type AppIO } from "../../io"; import { JsonRendererKey } from "../../tui"; import { PACKAGE_VERSION } from "../../constants"; @@ -92,7 +92,7 @@ export const createUpdateHandler = (io: AppIO) => ctx.require(JsonRendererKey).renderJson(result); if (result.status === "update-failed") { - throw new SilentCLIError("failed to install update", { exitCode: 1 }); + throw new AgentCoreCLIError(result.error ?? "failed to install update", { exitCode: 1 }); } }, }); From 6c3a6d0d80b9c811f54d90507b0d8a60216804f0 Mon Sep 17 00:00:00 2001 From: jariy17 Date: Tue, 1 Sep 2026 16:59:58 +0000 Subject: [PATCH 6/7] refactor(update): let ProcessFailedError propagate on install failure Drop the catch/rethrow around the install. runProcess already throws a ProcessFailedError (an AgentCoreCLIError carrying the command, exit code, and captured npm output), so let it bubble to the root instead of flattening it into a generic error. Removes the update-failed status and error field. --- src/handlers/update/index.tsx | 23 ++++------------------- src/handlers/update/update.test.ts | 6 ++---- 2 files changed, 6 insertions(+), 23 deletions(-) diff --git a/src/handlers/update/index.tsx b/src/handlers/update/index.tsx index a34d7c494..d4b296003 100644 --- a/src/handlers/update/index.tsx +++ b/src/handlers/update/index.tsx @@ -1,7 +1,7 @@ import z from "zod"; import semver from "semver"; import { createHandler, flag } from "../../router"; -import { AgentCoreCLIError, NetworkingError } from "../../errors"; +import { NetworkingError } from "../../errors"; import { runProcess, type ProcessRunner, type AppIO } from "../../io"; import { JsonRendererKey } from "../../tui"; import { PACKAGE_VERSION } from "../../constants"; @@ -34,14 +34,12 @@ export async function fetchLatestVersion(): Promise { return data.version; } -export type UpdateStatus = - "up-to-date" | "newer-local" | "update-available" | "updated" | "update-failed"; +export type UpdateStatus = "up-to-date" | "newer-local" | "update-available" | "updated"; export interface UpdateResult { status: UpdateStatus; currentVersion: string; latestVersion: string; - error?: string; } export interface HandleUpdateOptions { @@ -66,17 +64,8 @@ export async function handleUpdate( return { status: "update-available", currentVersion: PACKAGE_VERSION, latestVersion }; } - try { - await runner(installArgv(), { cwd: process.cwd(), onOutput }); - return { status: "updated", currentVersion: PACKAGE_VERSION, latestVersion }; - } catch (cause) { - return { - status: "update-failed", - currentVersion: PACKAGE_VERSION, - latestVersion, - error: cause instanceof Error ? cause.message : String(cause), - }; - } + await runner(installArgv(), { cwd: process.cwd(), onOutput }); + return { status: "updated", currentVersion: PACKAGE_VERSION, latestVersion }; } export const createUpdateHandler = (io: AppIO) => @@ -90,9 +79,5 @@ export const createUpdateHandler = (io: AppIO) => }); ctx.require(JsonRendererKey).renderJson(result); - - if (result.status === "update-failed") { - throw new AgentCoreCLIError(result.error ?? "failed to install update", { exitCode: 1 }); - } }, }); diff --git a/src/handlers/update/update.test.ts b/src/handlers/update/update.test.ts index e979a23bd..f5126e75a 100644 --- a/src/handlers/update/update.test.ts +++ b/src/handlers/update/update.test.ts @@ -86,10 +86,8 @@ describe("handleUpdate", () => { ); }); - test("update-failed carries the error reason when the install runner throws", async () => { + test("propagates the install failure instead of swallowing it", async () => { mockLatest("2.0.0"); - const result = await handleUpdate(false, { runner: failRunner }); - expect(result.status).toBe("update-failed"); - expect(result.error).toBe("npm exploded"); + await expect(handleUpdate(false, { runner: failRunner })).rejects.toThrow("npm exploded"); }); }); From 079ed692ed540a851e12996740c2488e98bb94c2 Mon Sep 17 00:00:00 2001 From: jariy17 Date: Tue, 1 Sep 2026 22:55:58 +0000 Subject: [PATCH 7/7] fix(deps): restore bun.lock integrity for the semver addition Regenerate bun.lock from the base branch so it keeps lockfileVersion 3, the nested @aws-cdk/toolkit-lib yaml override, and the pinned @opentelemetry/core 2.10.0. An older bun had rewritten all three while adding semver, which broke 'bun install --frozen-lockfile' in CI. Net change is now just the semver and @types/semver entries. --- bun.lock | 41 +++++++++++++++++------------------------ 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/bun.lock b/bun.lock index e99604441..750bb041c 100644 --- a/bun.lock +++ b/bun.lock @@ -1,5 +1,5 @@ { - "lockfileVersion": 1, + "lockfileVersion": 3, "configVersion": 1, "workspaces": { "": { @@ -54,6 +54,9 @@ }, }, "overrides": { + "@aws-cdk/toolkit-lib": { + "yaml": "^1", + }, "@opentelemetry/core": "^2.10.0", }, "packages": { @@ -261,7 +264,7 @@ "@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.213.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-zRM5/Qj6G84Ej3F1yt33xBVY/3tnMxtL1fiDIxYbDWYaZ/eudVw3/PBiZ8G7JwUxXxjW8gU4g6LnOyfGKYHYgw=="], - "@opentelemetry/core": ["@opentelemetry/core@2.11.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-7YP44XH0tV6+Mb54x2YGf84i7yi+31MBZlE8JwvozkxyTvXbSp10X7cI7YE49ChJ3shMJoBmCJF3+1QFBJctGA=="], + "@opentelemetry/core": ["@opentelemetry/core@2.10.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-/wNZ8twnEQQA4HoHu22+vcsdru6pWPWxW+7w+FlxT6Id7PE/WIbZmVKkte+PF72e0F2dnImFeHD2syyE1Mw6MQ=="], "@opentelemetry/exporter-metrics-otlp-http": ["@opentelemetry/exporter-metrics-otlp-http@0.221.0", "", { "dependencies": { "@opentelemetry/core": "2.10.0", "@opentelemetry/otlp-exporter-base": "0.221.0", "@opentelemetry/otlp-transformer": "0.221.0", "@opentelemetry/resources": "2.10.0", "@opentelemetry/sdk-metrics": "2.10.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-sRfCKbOzgy8xZQV2as0RzIZlnCmCseCKZGLfRcrpo2CBngJDr+rPtX0zkG0+oUCV5kfQPUoW3W3C96Ag3Y/Clg=="], @@ -269,11 +272,11 @@ "@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.213.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.213.0", "@opentelemetry/core": "2.6.0", "@opentelemetry/resources": "2.6.0", "@opentelemetry/sdk-logs": "0.213.0", "@opentelemetry/sdk-metrics": "2.6.0", "@opentelemetry/sdk-trace-base": "2.6.0", "protobufjs": "^7.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-RSuAlxFFPjeK4d5Y6ps8L2WhaQI6CXWllIjvo5nkAlBpmq2XdYWEBGiAbOF4nDs8CX4QblJDv5BbMUft3sEfDw=="], - "@opentelemetry/resources": ["@opentelemetry/resources@2.11.0", "", { "dependencies": { "@opentelemetry/core": "2.11.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-Ie7+8q8MDF4FAEQCKVMTx3ReUvxiIAgIiiW3c9JdmP8+HMcDy20puT+AHjexnExgnbvBxjQ9fjkFDWrikJ2jQA=="], + "@opentelemetry/resources": ["@opentelemetry/resources@2.10.0", "", { "dependencies": { "@opentelemetry/core": "2.10.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-q6MMm2zhggzsHVNbabYwut+a6nbuQQe3URUoxaojM/8K1IBfwwPzvxIjNi2/lI1TFe+fMHMW9MWhrtDLEXEnkA=="], "@opentelemetry/sdk-logs": ["@opentelemetry/sdk-logs@0.213.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.213.0", "@opentelemetry/core": "2.6.0", "@opentelemetry/resources": "2.6.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, "sha512-00xlU3GZXo3kXKve4DLdrAL0NAFUaZ9appU/mn00S/5kSUdAvyYsORaDUfR04Mp2CLagAOhrzfUvYozY/EZX2g=="], - "@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.11.0", "", { "dependencies": { "@opentelemetry/core": "2.11.0", "@opentelemetry/resources": "2.11.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-7GXXcObyHyDUUSG+L+kJoquty01bzm7ivE7+SSgXXJcHuPzGviptxwARmI2c+bnnxjexGQbJnyNlN8HxBP/Y7A=="], + "@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.10.0", "", { "dependencies": { "@opentelemetry/core": "2.10.0", "@opentelemetry/resources": "2.10.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-t6r1VSvXNtSDnPXU1FbZeetJb7yyovHmgu0wRSoftxtE0g2rSNhQZQUy69sRUCL+iioJpX8SN/S6wq6ZtvLySQ=="], "@opentelemetry/sdk-trace": ["@opentelemetry/sdk-trace@2.10.0", "", { "dependencies": { "@opentelemetry/core": "2.10.0", "@opentelemetry/resources": "2.10.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-MfQGq3GRmTh5fM/y+OjaO0vj6+luCB1XO2gfXCalKCfgKw0eHL++sm75DNweC6ohlp+aFvACqeE0fYayqdRaoQ=="], @@ -375,7 +378,7 @@ "@smithy/node-config-provider": ["@smithy/node-config-provider@4.6.2", "", { "dependencies": { "@smithy/core": "^3.33.2", "tslib": "^2.6.2" } }, "sha512-zMrXu/O5tPa7GLtra8L4wFG6DACcXT9QV4Ay+WEAjUhXm1dVq7c/q9Qv9gkJZNLY8hmQKg08778kDcxpKNMqOA=="], - "@smithy/node-http-handler": ["@smithy/node-http-handler@4.12.0", "", { "dependencies": { "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-0mq1pHadfyXCYCqm2cNpbjNIT+fbaUpNxewZb/YNr2L0IrEVMOb8gM/Fl4K6XvHCW3uSNDFwPl/+iKm0bx9jYg=="], + "@smithy/node-http-handler": ["@smithy/node-http-handler@4.11.3", "", { "dependencies": { "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-2jY1tSpERfPfWqyBV2pH+iGFaghVsIJszJNsT7hxtQYhVJpWDyc0LqOWI+nXOxOAHaEfZ4PXXtp1wW1TGpHhkA=="], "@smithy/property-provider": ["@smithy/property-provider@4.5.2", "", { "dependencies": { "@smithy/core": "^3.33.2", "tslib": "^2.6.2" } }, "sha512-SPJCSCCGpHf5g5b8244ig3WVKIdv2DS+X6cfmy4bpKEYo4VlLYM+bGHpHbfbmp76vegMKQBvs7DEpwQ5YuhKLA=="], @@ -435,19 +438,19 @@ "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], - "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.69.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.69.0", "@typescript-eslint/types": "^8.69.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-yi4obFrHMmnsesWehHbkg9zMA7Jt8cXT+mKM08G999pH1yT6nqgsHx7MYm0uY1wAj8CqiBXYRJ7WAT0QdQHQXg=="], + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.68.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.68.0", "@typescript-eslint/types": "^8.68.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-5GQtWZCXFcFYux955pvoS02WLc49pXNlvIxocKjS0clvwo3in1RdlzVKyiqQH9vE5AKWFLTaUgeQkOrTS+0Qxw=="], - "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.69.0", "", { "dependencies": { "@typescript-eslint/types": "8.69.0", "@typescript-eslint/visitor-keys": "8.69.0" } }, "sha512-ewfspqWvSxKSOaplqAUNbaSFO0eB6w1EtQ+esfYFRm3614Ty4uNtExkcbgd6nWsXphbqKyf9ZYdbZdv2xEoWEQ=="], + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.68.0", "", { "dependencies": { "@typescript-eslint/types": "8.68.0", "@typescript-eslint/visitor-keys": "8.68.0" } }, "sha512-T5eXpcaJNg8bhjHJ8Rjp68Vq/QBteYtTKY8TZqVNPaUbuz0f6jI9t6aDkylwvalpAB9XTTFeFOjrjXAZ3YvmVA=="], - "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.69.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-xNqK7YTDZsLniQMV/4rpFR8Z5JlqeRvVjuG1YgF/mdPVH84HSD19L8CczMA0qg2RfwEV231GHH3VnToJDo4MfQ=="], + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.68.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-F7zrGQfiJHojPwi8vhxZQC1tWtJzvL74cK/nqri2lk8YUXvYaYwl263xOJ69jDWPUk1hmcdoayFwk9lX09npVw=="], - "@typescript-eslint/types": ["@typescript-eslint/types@8.69.0", "", {}, "sha512-K3VrubUPhlo9VDBS6QdI8YB5j7ClpqLRdefcz6PFrhnwicehBweqQ9Evhl4l+FYz0HdDmMqIiSX0aldGRYtDCA=="], + "@typescript-eslint/types": ["@typescript-eslint/types@8.68.0", "", {}, "sha512-9RnpsGJjrAllCMefGVVsImJM24YurhC0Q1h4UbvivtvOqXmR/vEJge2OoE++z9m6hyg8T1Q8t5SNT6tHSbrxcg=="], - "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.69.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.69.0", "@typescript-eslint/tsconfig-utils": "8.69.0", "@typescript-eslint/types": "8.69.0", "@typescript-eslint/visitor-keys": "8.69.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-AdFkgqck3Vudb/kWnxlyafU/4aBhHrbQ9locP2N4psXTy5mOBg0SHJumnLvx7r6g1gV4DKvUFwV2nJZBoqOD8w=="], + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.68.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.68.0", "@typescript-eslint/tsconfig-utils": "8.68.0", "@typescript-eslint/types": "8.68.0", "@typescript-eslint/visitor-keys": "8.68.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-OKKsD0tYmoNiU5PW2zehO1yO56jYOm1ShYlxon/Z0SJNidAkdVg86eg9ruRuoXf8xfnuWZGbwDsStkoXbZtIIA=="], - "@typescript-eslint/utils": ["@typescript-eslint/utils@8.69.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.69.0", "@typescript-eslint/types": "8.69.0", "@typescript-eslint/typescript-estree": "8.69.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-tUbx60BBqQa31kXF5MCsOOLL5E/WzUuxIn7YpAvq+eaUlqvk8/NXnXMBNAdLCr0icjkzem7iUA5QqWHe/hJ1aw=="], + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.68.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.68.0", "@typescript-eslint/types": "8.68.0", "@typescript-eslint/typescript-estree": "8.68.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-PB5gJMMOg0Q5P1tsgWtEAqQacJXq0qEqRHDX/YJ4FaTMLfZPpHB3gjl2EJuiZyPABxmj4ZQYiY9m1bdAJ5y7tQ=="], - "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.69.0", "", { "dependencies": { "@typescript-eslint/types": "8.69.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-+rmdgPA+EXkNgKYvHvFfhrs35utXbwaC5PGpDquSXcoXQDKUA5UjV0LmTucG/4JXkM31BTu4TilHtrN8IVBe8w=="], + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.68.0", "", { "dependencies": { "@typescript-eslint/types": "8.68.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-YR65gGdGvTUAWLldC3xLOvOzamdGzB4A5/N8rehEaHs3Zvoe39BhgY+u0SPch1OvrVTfLcc55wsSgK2NcnTS/A=="], "@ungap/structured-clone": ["@ungap/structured-clone@1.4.0", "", {}, "sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ=="], @@ -603,7 +606,7 @@ "editions": ["editions@6.22.0", "", { "dependencies": { "version-range": "^4.15.0" } }, "sha512-UgGlf8IW75je7HZjNDpJdCv4cGJWIi6yumFdZ0R7A8/CIhQiWUjyGLCxdHpd8bmyD1gnkfUNK0oeOXqUS2cpfQ=="], - "electron-to-chromium": ["electron-to-chromium@1.5.417", "", {}, "sha512-4T+DTDWuMPM4aHlHwWdAVCVWwp7LDilnhzkj+c/Lbj91XSQrLuOmZSLtS9Q4iIqjlPUbPOnC624zDVVHCHaolQ=="], + "electron-to-chromium": ["electron-to-chromium@1.5.416", "", {}, "sha512-K6bvB2BjnNrugtIih6ewlbBI9DXa976jIdiIlRLHhBoEI9a4JaQjjHyF+A1IQI543aQYR4LnmOrT/K5fZj0aPA=="], "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], @@ -769,7 +772,7 @@ "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], - "ignore": ["ignore@7.0.8", "", {}, "sha512-YYNsSlXBjMk92SKnkwvB5LOVSa6OznlFUGcsvrFgNJbJCd0M1XKeFVRc8ZByeCqz32FivYNHJVooLmdqrmvp/Q=="], + "ignore": ["ignore@7.0.7", "", {}, "sha512-dML0wP6oak21rsNYCJpJB6O1BJIEwNpGrTw0URPfAk4hm0e3pRfCtzkfB6olBcXcVlU2rouCyz7lCyRB0OMVCA=="], "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], @@ -1499,10 +1502,6 @@ "@opentelemetry/exporter-metrics-otlp-http/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.221.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.221.0", "@opentelemetry/core": "2.10.0", "@opentelemetry/resources": "2.10.0", "@opentelemetry/sdk-logs": "0.221.0", "@opentelemetry/sdk-metrics": "2.10.0", "@opentelemetry/sdk-trace": "2.10.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-lg6lkOU08Az23jVcn/0Els9HP+V8PnR4Km6p0KgpTggS0n/WuhnmY64rSh83Of9iR9nD+dpWr6adlcX8KzAwjg=="], - "@opentelemetry/exporter-metrics-otlp-http/@opentelemetry/resources": ["@opentelemetry/resources@2.10.0", "", { "dependencies": { "@opentelemetry/core": "2.10.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-q6MMm2zhggzsHVNbabYwut+a6nbuQQe3URUoxaojM/8K1IBfwwPzvxIjNi2/lI1TFe+fMHMW9MWhrtDLEXEnkA=="], - - "@opentelemetry/exporter-metrics-otlp-http/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.10.0", "", { "dependencies": { "@opentelemetry/core": "2.10.0", "@opentelemetry/resources": "2.10.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-t6r1VSvXNtSDnPXU1FbZeetJb7yyovHmgu0wRSoftxtE0g2rSNhQZQUy69sRUCL+iioJpX8SN/S6wq6ZtvLySQ=="], - "@opentelemetry/otlp-exporter-base/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.221.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.221.0", "@opentelemetry/core": "2.10.0", "@opentelemetry/resources": "2.10.0", "@opentelemetry/sdk-logs": "0.221.0", "@opentelemetry/sdk-metrics": "2.10.0", "@opentelemetry/sdk-trace": "2.10.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-lg6lkOU08Az23jVcn/0Els9HP+V8PnR4Km6p0KgpTggS0n/WuhnmY64rSh83Of9iR9nD+dpWr6adlcX8KzAwjg=="], "@opentelemetry/otlp-transformer/@opentelemetry/resources": ["@opentelemetry/resources@2.6.0", "", { "dependencies": { "@opentelemetry/core": "2.6.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-D4y/+OGe3JSuYUCBxtH5T9DSAWNcvCb/nQWIga8HNtXTVPQn59j0nTBAgaAXxUVBDl40mG3Tc76b46wPlZaiJQ=="], @@ -1511,8 +1510,6 @@ "@opentelemetry/sdk-logs/@opentelemetry/resources": ["@opentelemetry/resources@2.6.0", "", { "dependencies": { "@opentelemetry/core": "2.6.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-D4y/+OGe3JSuYUCBxtH5T9DSAWNcvCb/nQWIga8HNtXTVPQn59j0nTBAgaAXxUVBDl40mG3Tc76b46wPlZaiJQ=="], - "@opentelemetry/sdk-trace/@opentelemetry/resources": ["@opentelemetry/resources@2.10.0", "", { "dependencies": { "@opentelemetry/core": "2.10.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-q6MMm2zhggzsHVNbabYwut+a6nbuQQe3URUoxaojM/8K1IBfwwPzvxIjNi2/lI1TFe+fMHMW9MWhrtDLEXEnkA=="], - "@opentelemetry/sdk-trace-base/@opentelemetry/resources": ["@opentelemetry/resources@2.6.0", "", { "dependencies": { "@opentelemetry/core": "2.6.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-D4y/+OGe3JSuYUCBxtH5T9DSAWNcvCb/nQWIga8HNtXTVPQn59j0nTBAgaAXxUVBDl40mG3Tc76b46wPlZaiJQ=="], "@secretlint/formatter/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], @@ -1623,12 +1620,8 @@ "@opentelemetry/otlp-exporter-base/@opentelemetry/otlp-transformer/@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.221.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-OlanaW1vv7ufTqQ3/fPLI4arGt5ZoM+P8abOMki6uEYnpRazepSWDwDnnw+la7kE26SHVC18//SMccrDvLKOXQ=="], - "@opentelemetry/otlp-exporter-base/@opentelemetry/otlp-transformer/@opentelemetry/resources": ["@opentelemetry/resources@2.10.0", "", { "dependencies": { "@opentelemetry/core": "2.10.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-q6MMm2zhggzsHVNbabYwut+a6nbuQQe3URUoxaojM/8K1IBfwwPzvxIjNi2/lI1TFe+fMHMW9MWhrtDLEXEnkA=="], - "@opentelemetry/otlp-exporter-base/@opentelemetry/otlp-transformer/@opentelemetry/sdk-logs": ["@opentelemetry/sdk-logs@0.221.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.221.0", "@opentelemetry/core": "2.10.0", "@opentelemetry/resources": "2.10.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, "sha512-FaDcazjyMp7TZZZAsqbo4IkovP0UegoCu0EBkiNt+qCqvUf7FPAsfcrZ3+ZEkKgXZ/jHafop+JoGPDk3A0SmLg=="], - "@opentelemetry/otlp-exporter-base/@opentelemetry/otlp-transformer/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.10.0", "", { "dependencies": { "@opentelemetry/core": "2.10.0", "@opentelemetry/resources": "2.10.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-t6r1VSvXNtSDnPXU1FbZeetJb7yyovHmgu0wRSoftxtE0g2rSNhQZQUy69sRUCL+iioJpX8SN/S6wq6ZtvLySQ=="], - "@textlint/linter-formatter/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], "@textlint/linter-formatter/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],