From 79f2d9f1cab2e19ad35fb99f4e215da8f9d5fdfa Mon Sep 17 00:00:00 2001 From: Forge Date: Tue, 22 Sep 2026 03:10:06 +0000 Subject: [PATCH] Preserve extension version pins through lazy CLI parsing --- __tests__/cli-version.test.ts | 13 +++++++++++++ marketplace/public/app.js | 3 ++- scripts/validation/extensions-compatibility.cjs | 2 +- src/bin/codegraph.ts | 6 +++++- 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/__tests__/cli-version.test.ts b/__tests__/cli-version.test.ts index 7a72fed7e..0ad724df5 100644 --- a/__tests__/cli-version.test.ts +++ b/__tests__/cli-version.test.ts @@ -38,6 +38,19 @@ describe('codegraph version affordances', () => { }); } + it('keeps an extension exact-version pin out of the root version flag', () => { + // No registry request is needed: --version without --registry must fail, + // not print the engine version and exit zero. + let result; + try { result = { exit: 0, output: run(['extensions', 'install', 'missing.cgext', '--version', '9.0.0']) }; } + catch (error) { + const e = error as { status: number; stdout: string; stderr: string }; + result = { exit: e.status, output: `${e.stdout ?? ''}${e.stderr ?? ''}` }; + } + expect(result.exit).toBe(1); + expect(result.output).toContain('--version requires --registry'); + }); + it('lists the `version` subcommand in --help', () => { expect(run(['--help'])).toContain('version'); }); diff --git a/marketplace/public/app.js b/marketplace/public/app.js index da75acd5c..91d058d42 100644 --- a/marketplace/public/app.js +++ b/marketplace/public/app.js @@ -132,13 +132,14 @@ window.addEventListener('message', event => { if (message.type === 'connected' || message.type === 'status') { const before = JSON.stringify(state.snapshot); const wasBusy = state.snapshot?.busy; + const beforeProjects = JSON.stringify(state.snapshot?.projects); state.snapshot = message.data; if (wasBusy && !state.snapshot.busy && state.notice) toast(state.snapshot.progress.message); document.querySelector('#connect').classList.add('connected'); document.querySelector('#connection-label').textContent = 'CodeGraph connected'; if (message.type === 'connected') { toast('Connected. Select your project and install an extension.'); render(); void refreshSelections(); } else if (JSON.stringify(state.snapshot) !== before && location.pathname !== '/publish') render(); - if (wasBusy && !state.snapshot.busy) void refreshSelections(); + if (message.type !== 'connected' && !state.snapshot.busy && (wasBusy || JSON.stringify(state.snapshot.projects) !== beforeProjects)) void refreshSelections(); } if (message.type === 'disconnected') disconnect(); }); diff --git a/scripts/validation/extensions-compatibility.cjs b/scripts/validation/extensions-compatibility.cjs index 6ec3b5a7a..d3d59691a 100644 --- a/scripts/validation/extensions-compatibility.cjs +++ b/scripts/validation/extensions-compatibility.cjs @@ -8,7 +8,7 @@ const { createExtensionProject, CodeGraph } = require('../../dist'); const { packExtension } = require('../../dist/plugins/package'); const { startMarketplaceServer } = require('../../dist/plugins/marketplace'); const { startExtensionBridge } = require('../../dist/plugins/bridge'); -const repo = path.resolve(__dirname, '../..'), out = path.join(repo, '.qa/recovery/compatibility'); +const repo = path.resolve(__dirname, '../..'), out = path.resolve(process.env.COMPAT_OUTPUT || path.join(repo, '.qa/recovery/compatibility')); fs.mkdirSync(out, { recursive: true }); const lab = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-compatible-')); const author = path.join(lab, 'author'), checks = [], commands = [], releases = []; diff --git a/src/bin/codegraph.ts b/src/bin/codegraph.ts index fcb511145..acdf9c278 100644 --- a/src/bin/codegraph.ts +++ b/src/bin/codegraph.ts @@ -2807,12 +2807,16 @@ program * the version affordance in `codegraph --help`. */ // Command registration is lightweight; heavy implementation loads only when invoked. +// Preserve extension --version pins for the lazily registered subcommands; +// the root's engine-version flag must not consume them first. +if (process.argv[2] === 'extensions') program.enablePositionalOptions(); const extensionCommands = program.command('extensions').description('Install and manage framework extensions').allowUnknownOption().allowExcessArguments(); extensionCommands.action(async () => { const { registerExtensionCommands } = await import('../plugins/cli'); const cli = new Command().name('codegraph'); registerExtensionCommands(cli); - await cli.parseAsync(process.argv); + try { await cli.parseAsync(process.argv); } + catch (error) { console.error(error instanceof Error ? error.message : String(error)); process.exitCode = 1; } }); program