feat(cli): add codegraph upgrade self-update + stale-index re-index hint (#710)

`codegraph upgrade [version]` detects how the CLI was installed — the standalone
install.sh/install.ps1 bundle, npm-global, npx, or a source checkout — and
updates in place: re-running the canonical install.sh on macOS/Linux, an
in-place rename-and-extract swap on Windows (a running node.exe can't be
deleted, only renamed, so the detached-helper approach is avoided), and
npm/npx/source-specific guidance otherwise. Flags: `--check` (report only),
`--force`, and a positional version to pin.

Each full index is now stamped with the engine's EXTRACTION_VERSION in
project_metadata; `codegraph status` (and `--json`) flags an index built by an
older engine and recommends re-indexing, and `upgrade` prints the same reminder.
Gated on EXTRACTION_VERSION so it never nags on extraction-neutral releases.

Validated end-to-end on macOS (real bundle upgrade), Linux (Docker, real
curl|sh) and Windows (Parallels VM, real in-place swap). 32 new unit tests.

Closes #679

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-06-06 13:38:38 -04:00
committed by GitHub
co-authored by Claude Opus 4.8
parent 07af3db6c7
commit 4e5cf2de56
9 changed files with 1053 additions and 3 deletions
+57
View File
@@ -20,6 +20,7 @@
* codegraph callees <symbol> Find what a function/method calls
* codegraph impact <symbol> Analyze what code is affected by changing a symbol
* codegraph affected [files] Find test files affected by changes
* codegraph upgrade [version] Update CodeGraph to the latest release
*/
import { Command } from 'commander';
@@ -32,6 +33,7 @@ import { getGlyphs } from '../ui/glyphs';
import { buildNode25BlockBanner, buildNodeTooOldBanner, MIN_NODE_MAJOR } from './node-version-check';
import { relaunchWithWasmRuntimeFlagsIfNeeded } from '../extraction/wasm-runtime-flags';
import { EXTRACTION_VERSION } from '../extraction/extraction-version';
// Lazy-load heavy modules (CodeGraph, runInstaller) to keep CLI startup fast.
async function loadCodeGraph(): Promise<typeof import('../index')> {
@@ -699,6 +701,9 @@ program
const backend = cg.getBackend();
const journalMode = cg.getJournalMode();
const buildInfo = cg.getIndexBuildInfo();
const reindexRecommended = cg.isIndexStale();
// JSON output mode
if (options.json) {
const lastIndexedMs = cg.getLastIndexedAt();
@@ -724,6 +729,12 @@ program
worktreeMismatch: worktreeMismatch
? { worktreeRoot: worktreeMismatch.worktreeRoot, indexRoot: worktreeMismatch.indexRoot }
: null,
index: {
builtWithVersion: buildInfo.version,
builtWithExtractionVersion: buildInfo.extractionVersion,
currentExtractionVersion: EXTRACTION_VERSION,
reindexRecommended,
},
}));
cg.destroy();
return;
@@ -797,6 +808,15 @@ program
}
console.log();
// Re-index hint: the index was built by an older engine than the one now
// running, so a rebuild would add data a migration can't backfill.
if (reindexRecommended) {
const builtWith = buildInfo.version ? `v${buildInfo.version.replace(/^v/, '')}` : 'an earlier version';
warn(`Index was built by ${builtWith}; re-index to pick up this engine's improvements.`);
info('Run "codegraph index -f" (full rebuild) or "codegraph sync"');
console.log();
}
cg.destroy();
} catch (err) {
error(`Failed to get status: ${err instanceof Error ? err.message : String(err)}`);
@@ -1664,6 +1684,43 @@ program
}
});
/**
* codegraph upgrade [version]
*
* Self-update, however CodeGraph was installed (bundle via install.sh/.ps1,
* npm-global, npx, or a source checkout). See ../upgrade for the detection and
* per-method upgrade logic.
*/
program
.command('upgrade [version]')
.description('Update CodeGraph to the latest release (or a specific version)')
.option('--check', 'Check whether an update is available without installing')
.option('-f, --force', 'Reinstall even if already on the target version')
.action(async (versionArg: string | undefined, options: { check?: boolean; force?: boolean }) => {
const up = await import('../upgrade');
const method = up.detectInstallMethod({
filename: __filename,
platform: process.platform,
cwd: process.cwd(),
});
const pin = versionArg || process.env.CODEGRAPH_VERSION || undefined;
const code = await up.runUpgrade(
{ version: pin, check: options.check, force: options.force },
{
currentVersion: packageJson.version,
method,
resolveLatest: () => up.resolveLatestVersion(),
run: up.defaultRun,
hasCommand: up.hasCommand,
log: (m: string) => console.log(m),
warn: (m: string) => warn(m),
error: (m: string) => error(m),
platform: process.platform,
}
);
process.exit(code);
});
// Parse and run
program.parse();