cli-node-command.test.ts 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. /**
  2. * `codegraph node` argument handling (#1044).
  3. *
  4. * File-read mode (`codegraph node -f <file>`) carries no symbol name, but the
  5. * command was defined with a REQUIRED `<name>` positional, so commander.js
  6. * rejected the call with "missing required argument 'name'" before the action
  7. * ever ran — making file mode unreachable from the CLI. `name` is now optional
  8. * (`[name]`); the action validates that a symbol OR a file is supplied.
  9. *
  10. * Exercised end-to-end against the built binary.
  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 os from 'os';
  16. import * as path from 'path';
  17. import { CodeGraph } from '../src';
  18. const BIN = path.resolve(__dirname, '../dist/bin/codegraph.js');
  19. function runNode(cwd: string, extraArgs: string[]): { stdout: string; stderr: string; code: number } {
  20. try {
  21. const stdout = execFileSync(process.execPath, [BIN, 'node', ...extraArgs, '-p', cwd], {
  22. encoding: 'utf-8',
  23. env: { ...process.env, CODEGRAPH_NO_DAEMON: '1', CODEGRAPH_WASM_RELAUNCHED: '1' },
  24. stdio: ['ignore', 'pipe', 'pipe'],
  25. });
  26. return { stdout, stderr: '', code: 0 };
  27. } catch (err: any) {
  28. return { stdout: err.stdout ?? '', stderr: err.stderr ?? '', code: err.status ?? 1 };
  29. }
  30. }
  31. describe('codegraph node — argument handling (#1044)', () => {
  32. let tempDir: string;
  33. beforeEach(async () => {
  34. tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-node-cmd-'));
  35. fs.mkdirSync(path.join(tempDir, 'src'));
  36. fs.writeFileSync(path.join(tempDir, 'src/util.ts'), 'export function util(x: number){ return x + 1; }\n');
  37. const cg = CodeGraph.initSync(tempDir);
  38. await cg.indexAll();
  39. cg.close();
  40. });
  41. afterEach(() => {
  42. fs.rmSync(tempDir, { recursive: true, force: true });
  43. });
  44. it('file mode via -f reads the file (was rejected as "missing required argument")', () => {
  45. const { stdout, code } = runNode(tempDir, ['-f', 'src/util.ts']);
  46. expect(code).toBe(0);
  47. expect(stdout).toContain('src/util.ts');
  48. expect(stdout).toContain('export function util');
  49. // The line-numbered Read-parity shape.
  50. expect(stdout).toMatch(/1\s+export function util/);
  51. });
  52. it('a path-like positional still routes to file mode', () => {
  53. const { stdout, code } = runNode(tempDir, ['src/util.ts']);
  54. expect(code).toBe(0);
  55. expect(stdout).toContain('src/util.ts');
  56. expect(stdout).toContain('export function util');
  57. });
  58. it('a bare symbol positional still routes to symbol mode', () => {
  59. const { stdout, code } = runNode(tempDir, ['util']);
  60. expect(code).toBe(0);
  61. expect(stdout).toContain('util');
  62. expect(stdout).toContain('Location:');
  63. });
  64. it('neither symbol nor file gives a usage error, not commander\'s cryptic one', () => {
  65. const { stderr, code } = runNode(tempDir, []);
  66. expect(code).not.toBe(0);
  67. expect(stderr).toMatch(/symbol name|file/i);
  68. expect(stderr).not.toMatch(/missing required argument/);
  69. });
  70. });
  71. describe('codegraph node — symbol pinned to a file includes the body (#1284)', () => {
  72. let tempDir: string;
  73. beforeEach(async () => {
  74. tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-node-pin-'));
  75. fs.mkdirSync(path.join(tempDir, 'a'));
  76. fs.mkdirSync(path.join(tempDir, 'b'));
  77. // Two same-named definitions, so `-f` is genuinely disambiguating.
  78. fs.writeFileSync(
  79. path.join(tempDir, 'a', 'state.ts'),
  80. 'export function setState(x: number): void {\n console.log("A", x);\n}\n'
  81. );
  82. fs.writeFileSync(
  83. path.join(tempDir, 'b', 'state.ts'),
  84. 'export function setState(y: string): void {\n console.log("B", y);\n}\n'
  85. );
  86. const cg = CodeGraph.initSync(tempDir);
  87. await cg.indexAll();
  88. cg.close();
  89. });
  90. afterEach(() => {
  91. fs.rmSync(tempDir, { recursive: true, force: true });
  92. });
  93. it('`node <symbol> -f <file>` prints the pinned definition WITH its source body', () => {
  94. // The exact #1284 shape: `-f` narrowed the overload correctly but printed
  95. // only Location + trail — no code fence — so the user had nothing to read.
  96. const { stdout, code } = runNode(tempDir, ['setState', '-f', 'a/state.ts']);
  97. expect(code).toBe(0);
  98. expect(stdout).toContain('a/state.ts');
  99. // The body is present (line-numbered fence), and it's the pinned overload.
  100. expect(stdout).toMatch(/1\s+export function setState\(x: number\)/);
  101. expect(stdout).toContain('console.log("A", x)');
  102. // The other file's overload is not what was pinned.
  103. expect(stdout).not.toContain('console.log("B", y)');
  104. });
  105. });