From 8444313fdfc3fbd6ff5394f2438cd658cbf94912 Mon Sep 17 00:00:00 2001 From: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com> Date: Tue, 1 Sep 2026 20:33:52 +0800 Subject: [PATCH] test(cli): prove lifecycle warning outputs Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com> --- cli/src/commands/sync.ts | 2 +- cli/src/commands/upgrade.ts | 2 +- cli/src/services/upgrade-service.ts | 8 +++- cli/test/integration/sync-command.test.ts | 3 ++ cli/test/integration/upgrade-command.test.ts | 45 ++++++++++++++++++++ 5 files changed, 56 insertions(+), 4 deletions(-) diff --git a/cli/src/commands/sync.ts b/cli/src/commands/sync.ts index da63ff3a..1c91806e 100644 --- a/cli/src/commands/sync.ts +++ b/cli/src/commands/sync.ts @@ -135,7 +135,7 @@ async function resolveSyncContext(options: SyncCommonOptions): Promise<{ return { client: new SkillHubClient(registry, token), registry, token, namespace, rootDir } } -function renderPullResult(result: PullResult, json: boolean, check: boolean): string { +export function renderPullResult(result: PullResult, json: boolean, check: boolean): string { if (json) { return JSON.stringify({ ok: result.failures.length === 0, check, ...result }) } diff --git a/cli/src/commands/upgrade.ts b/cli/src/commands/upgrade.ts index 446d2647..ca0000fc 100644 --- a/cli/src/commands/upgrade.ts +++ b/cli/src/commands/upgrade.ts @@ -96,7 +96,7 @@ function renderUpgradePlan( ].join('\n') } -function renderUpgradeResult(plan: UpgradePlan, result: UpgradeExecutionResult, json: boolean): string { +export function renderUpgradeResult(plan: UpgradePlan, result: UpgradeExecutionResult, json: boolean): string { const executionByCoordinate = new Map(result.items.map(item => [item.coordinate, item])) const items = plan.items.map(item => ({ coordinate: item.coordinate, diff --git a/cli/src/services/upgrade-service.ts b/cli/src/services/upgrade-service.ts index db0e98ac..968602e5 100644 --- a/cli/src/services/upgrade-service.ts +++ b/cli/src/services/upgrade-service.ts @@ -64,6 +64,10 @@ export interface UpgradeExecutionResult { notAttempted: number } +type UpgradeExecutionOptions = Pick & { + installSkillFn?: typeof installSkill +} + export async function planSkillUpgrades(options: UpgradeSelectionOptions): Promise { if (options.coordinates.length === 0) { throw new CliError('provide at least one installed skill coordinate', EXIT.usage) @@ -223,7 +227,7 @@ export async function planSkillUpgrades(options: UpgradeSelectionOptions): Promi export async function executeSkillUpgradePlan( plan: UpgradePlan, - options: Pick + options: UpgradeExecutionOptions ): Promise { if (plan.blocked > 0) { throw new CliError('upgrade plan contains blocked skills', EXIT.validation) @@ -244,7 +248,7 @@ export async function executeSkillUpgradePlan( try { const token = await options.tokenForRegistry(item.registry) - const installed = await installSkill({ + const installed = await (options.installSkillFn ?? installSkill)({ registry: item.registry, token, namespace: item.inventoryItem.namespace, diff --git a/cli/test/integration/sync-command.test.ts b/cli/test/integration/sync-command.test.ts index 91da1da1..607b2d5a 100644 --- a/cli/test/integration/sync-command.test.ts +++ b/cli/test/integration/sync-command.test.ts @@ -8,6 +8,7 @@ import { runCli } from '../helpers/run-cli' import { createTempHome } from '../helpers/temp-env' import { SkillHubClient } from '../../src/clients/skillhub-client' import { pullNamespace } from '../../src/services/sync-service' +import { renderPullResult } from '../../src/commands/sync' function makeSkill(body: string): { zipBytes: Uint8Array; fingerprint: string } { const content = strToU8(body) @@ -47,6 +48,8 @@ describe('sync command', () => { slug: 'demo', message: 'target lock cleanup failed: simulated release failure' }]) + expect(JSON.parse(renderPullResult(result, true, false)).warnings).toEqual(result.warnings) + expect(renderPullResult(result, false, false)).toContain('warning demo: target lock cleanup failed') } finally { registry.stop() } diff --git a/cli/test/integration/upgrade-command.test.ts b/cli/test/integration/upgrade-command.test.ts index d5448c36..8e514b97 100644 --- a/cli/test/integration/upgrade-command.test.ts +++ b/cli/test/integration/upgrade-command.test.ts @@ -6,6 +6,8 @@ import { createTempHome } from '../helpers/temp-env' import { startFakeRegistry } from '../helpers/fake-registry' import { runCli } from '../helpers/run-cli' import { executeSkillUpgradePlan, planSkillUpgrades } from '../../src/services/upgrade-service' +import { installSkill } from '../../src/services/install-service' +import { renderUpgradeResult } from '../../src/commands/upgrade' let registries: Array>> = [] @@ -575,6 +577,49 @@ describe('upgrade command', () => { expect(registry.received.downloads).toBe(5) }) + test('a committed upgrade keeps success and renders a post-commit warning', async () => { + const env = await createTempHome() + const skill = { namespace: 'global', slug: 'warned', version: '1.0.0', fingerprint: 'v1', zipBytes: makeSkillZip('# v1') } + const registry = await startFakeRegistry({ skills: [skill] }) + registries.push(registry) + const rootDir = join(env.cwd, 'skills') + await mkdir(rootDir, { recursive: true }) + await runCli(['install', '@global/warned', '--dir', rootDir, '--registry', registry.url], { + HOME: env.home, + USERPROFILE: env.home + }) + skill.version = '1.1.0' + skill.fingerprint = 'v2' + skill.zipBytes = makeSkillZip('# v2') + const tokenForRegistry = async () => undefined + const plan = await planSkillUpgrades({ + coordinates: ['@global/warned'], + registry: registry.url, + force: false, + home: env.home, + tokenForRegistry + }) + + const result = await executeSkillUpgradePlan(plan, { + home: env.home, + tokenForRegistry, + installSkillFn: options => installSkill({ + ...options, + acquireTargetLock: async () => async () => { throw new Error('simulated release failure') } + }) + }) + + expect(result).toMatchObject({ upgraded: 1, failed: 0 }) + expect(result.items[0]).toMatchObject({ action: 'upgraded' }) + expect(result.items[0]?.warnings).toEqual(['target lock cleanup failed: simulated release failure']) + expect(JSON.parse(renderUpgradeResult(plan, result, true)).items[0].warnings).toHaveLength(1) + expect(renderUpgradeResult(plan, result, false)).toContain('upgraded') + expect(renderUpgradeResult(plan, result, false)).toContain('[warning: target lock cleanup failed') + expect(await readFile(join(rootDir, 'warned', 'SKILL.md'), 'utf-8')).toBe('# v2') + const inventory = JSON.parse(await readFile(join(env.home, '.skillhub', 'inventory.json'), 'utf-8')) + expect(inventory.items[0]).toMatchObject({ version: '1.1.0', fingerprint: 'v2' }) + }) + test('legacy metadata without a file baseline requires explicit force migration', async () => { const env = await createTempHome() const skill = { namespace: 'global', slug: 'legacy', version: '1.0.0', fingerprint: 'v1', zipBytes: makeSkillZip('# v1') }