1
0

cli-version.test.ts 3.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980
  1. /**
  2. * Tests for the `codegraph version` affordances.
  3. *
  4. * The version should be reachable however a user reaches for it — the bare
  5. * `version` subcommand, lowercase `-v`, single-dash `-version`, plus
  6. * commander's stock `--version` / `-V`. All of them print the exact
  7. * package.json version and nothing else.
  8. *
  9. * Exercised end-to-end against the built binary (same approach as
  10. * status-json.test.ts) so the spellings survive future CLI refactors.
  11. */
  12. import { describe, it, expect } from 'vitest';
  13. import { execFileSync } from 'child_process';
  14. import * as fs from 'fs';
  15. import * as os from 'os';
  16. import * as path from 'path';
  17. const BIN = path.resolve(__dirname, '../dist/bin/codegraph.js');
  18. const PKG_VERSION = JSON.parse(
  19. fs.readFileSync(path.resolve(__dirname, '../package.json'), 'utf-8'),
  20. ).version as string;
  21. function run(args: string[]): string {
  22. return execFileSync(process.execPath, [BIN, ...args], {
  23. encoding: 'utf-8',
  24. // Skip the daemon and the wasm-flag re-exec so the command resolves in a
  25. // single fast process (no graph work happens for a version print anyway).
  26. env: { ...process.env, CODEGRAPH_NO_DAEMON: '1', CODEGRAPH_WASM_RELAUNCHED: '1' },
  27. stdio: ['ignore', 'pipe', 'pipe'],
  28. }).trim();
  29. }
  30. describe('codegraph version affordances', () => {
  31. for (const spelling of ['version', '-v', '-version', '--version', '-V']) {
  32. it(`\`codegraph ${spelling}\` prints exactly the package version`, () => {
  33. expect(run([spelling])).toBe(PKG_VERSION);
  34. });
  35. }
  36. it('lists the `version` subcommand in --help', () => {
  37. expect(run(['--help'])).toContain('version');
  38. });
  39. it('`codegraph help` prints usage and the command list', () => {
  40. const out = run(['help']);
  41. expect(out).toContain('Usage: codegraph');
  42. expect(out).toContain('Commands:');
  43. });
  44. it('hides the internal `serve` command from --help', () => {
  45. // `serve --mcp` is the stdio entry point an AI agent launches for itself,
  46. // not a human command — it must not appear in the listing. (It stays fully
  47. // invocable; the mcp-initialize suite covers that the agent path works.)
  48. expect(run(['--help'])).not.toMatch(/^\s+serve\b/m);
  49. });
  50. it('a trailing `-v` is still the subcommand\'s --verbose, not the version intercept', () => {
  51. // A fresh temp dir outside any indexed project: `index -v` parses `-v` as
  52. // the index command's --verbose, then short-circuits at "not initialized"
  53. // and exits non-zero. The point is it must NOT print the bare version,
  54. // which would mean the top-level intercept swallowed a subcommand flag.
  55. const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-version-test-'));
  56. let combined = '';
  57. try {
  58. combined = execFileSync(process.execPath, [BIN, 'index', '-v', tempDir], {
  59. encoding: 'utf-8',
  60. env: { ...process.env, CODEGRAPH_NO_DAEMON: '1', CODEGRAPH_WASM_RELAUNCHED: '1' },
  61. stdio: ['ignore', 'pipe', 'pipe'],
  62. });
  63. } catch (err: unknown) {
  64. const e = err as { stdout?: string; stderr?: string };
  65. combined = `${e.stdout ?? ''}${e.stderr ?? ''}`;
  66. } finally {
  67. fs.rmSync(tempDir, { recursive: true, force: true });
  68. }
  69. expect(combined.trim()).not.toBe(PKG_VERSION);
  70. expect(combined).toContain('not initialized');
  71. });
  72. });