cli-install-init.test.ts 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. /**
  2. * `codegraph install --init` and `codegraph init --yes` (#1578): the one-shot,
  3. * non-interactive "wire agents + build this project's index" bootstrap a fresh
  4. * container / CI job needs.
  5. *
  6. * Exercised end-to-end against the built binary so the CLI wiring (the shared
  7. * `runInit` flow, the flag plumbing, exit codes) is what's covered. Every run
  8. * uses `--target none`, so the installer touches no agent config on the
  9. * machine running the suite; the only side effect is the temp project's
  10. * `.codegraph/`.
  11. */
  12. import { describe, it, expect, beforeEach, afterEach } from 'vitest';
  13. import { execFileSync } from 'child_process';
  14. import * as fs from 'fs';
  15. import * as path from 'path';
  16. import * as os from 'os';
  17. const BIN = path.resolve(__dirname, '../dist/bin/codegraph.js');
  18. interface RunResult {
  19. status: number;
  20. stdout: string;
  21. stderr: string;
  22. }
  23. /** Run the CLI with stdin closed — a prompt that blocks would hang / fail here. */
  24. function runCodegraph(args: string[], cwd: string): RunResult {
  25. try {
  26. const stdout = execFileSync(process.execPath, [BIN, ...args], {
  27. cwd,
  28. encoding: 'utf-8',
  29. env: {
  30. ...process.env,
  31. CODEGRAPH_NO_DAEMON: '1',
  32. CODEGRAPH_TELEMETRY: '0',
  33. DO_NOT_TRACK: '1',
  34. NO_COLOR: '1',
  35. },
  36. stdio: ['ignore', 'pipe', 'pipe'],
  37. timeout: 120_000,
  38. });
  39. return { status: 0, stdout, stderr: '' };
  40. } catch (err) {
  41. const e = err as { status?: number | null; stdout?: string | Buffer; stderr?: string | Buffer };
  42. return {
  43. status: e.status ?? -1,
  44. stdout: String(e.stdout ?? ''),
  45. stderr: String(e.stderr ?? ''),
  46. };
  47. }
  48. }
  49. describe('codegraph install --init / init --yes (#1578)', () => {
  50. let tempDir: string;
  51. beforeEach(() => {
  52. tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-install-init-'));
  53. fs.writeFileSync(
  54. path.join(tempDir, 'a.ts'),
  55. `export function greet(name: string) { return hello(name); }\n` +
  56. `export function hello(n: string) { return 'hi ' + n; }\n`,
  57. );
  58. });
  59. afterEach(() => {
  60. fs.rmSync(tempDir, { recursive: true, force: true });
  61. });
  62. it('install --yes --target none --init builds the current project\'s index in one command', () => {
  63. const r = runCodegraph(['install', '--yes', '--target', 'none', '--init'], tempDir);
  64. expect(r.status, r.stdout + r.stderr).toBe(0);
  65. // The installer ran (and had nothing to wire) …
  66. expect(r.stdout).toContain('No agent targets selected');
  67. // … and the init ran afterwards, in cwd.
  68. expect(r.stdout).toContain(`Initialized in ${fs.realpathSync(tempDir)}`);
  69. expect(fs.existsSync(path.join(tempDir, '.codegraph', 'codegraph.db'))).toBe(true);
  70. });
  71. it('install --init on an already-initialized project reports that and still exits 0', () => {
  72. expect(runCodegraph(['init', '--yes'], tempDir).status).toBe(0);
  73. const r = runCodegraph(['install', '--yes', '--target', 'none', '--init'], tempDir);
  74. expect(r.status, r.stdout + r.stderr).toBe(0);
  75. expect(r.stdout).toContain('Already initialized');
  76. });
  77. it('install --init refuses an unsafe root (filesystem root) with exit code 1, like init does', () => {
  78. // `/` (or the drive root on Windows) is the canonical unsafe root: the
  79. // refusal fires before anything is created, so nothing is written there.
  80. const root = path.parse(process.cwd()).root;
  81. const r = runCodegraph(['install', '--yes', '--target', 'none', '--init'], root);
  82. expect(r.status).toBe(1);
  83. expect(r.stdout).toContain('Refusing to initialize');
  84. expect(fs.existsSync(path.join(root, '.codegraph'))).toBe(false);
  85. });
  86. it('init --yes runs non-interactively with stdin closed and builds the index', () => {
  87. const r = runCodegraph(['init', '--yes'], tempDir);
  88. expect(r.status, r.stdout + r.stderr).toBe(0);
  89. expect(r.stdout).toContain('Initialized in');
  90. expect(fs.existsSync(path.join(tempDir, '.codegraph', 'codegraph.db'))).toBe(true);
  91. });
  92. it('documents the new flags in --help', () => {
  93. expect(runCodegraph(['init', '--help'], tempDir).stdout).toMatch(/-y, --yes\b/);
  94. expect(runCodegraph(['install', '--help'], tempDir).stdout).toMatch(/-i, --init\b/);
  95. });
  96. });