diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f2ab66bf..94bb86a3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,7 @@ Thanks for your interest. This document covers what you need to know to send a w ## Prerequisites -- Node.js 22.20 or newer (`engines.node` in `package.json`). +- Node.js 22.13.0 or newer (`engines.node` in `package.json`). - npm 10 or newer (ships with recent Node). - macOS or Linux for full provider coverage. Windows works for most providers but Cursor / Antigravity development is easier on macOS. - Optional: Swift 6 toolchain if you are touching the macOS menubar (`mac/`). @@ -18,7 +18,19 @@ cd codeburn npm install ``` -There is no separate build step required to run the dev CLI. `npm run dev` runs `tsx` against `src/cli.ts` directly. +There is no separate build step required to run the dev CLI. `npm run dev` loads +`tsx` through Node's `--import` flag and runs `src/cli.ts` directly. The command +works with npm's default shell on Windows, macOS, and Linux; no shell configuration +or extra environment assignment is required. Deprecation warnings are suppressed +for this invocation, and any existing `NODE_OPTIONS` are retained. + +Use `--` to pass CLI arguments through npm. For a quick setup check that does not +scan your session history, run: + +```bash +npm run dev -- --version +npm run dev -- status --help +``` ## Common Commands diff --git a/package.json b/package.json index 228d8de4..dfabca87 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "build:dash": "cd dash && npm ci --no-audit --no-fund --silent && npm run build", "build:vscode": "npm --prefix vscode run build", "package:vscode": "npm --prefix vscode run package", - "dev": "NODE_OPTIONS=--no-deprecation tsx src/cli.ts", + "dev": "node --no-deprecation --import tsx src/cli.ts", "test": "vitest run tests --exclude \"tests/cache-refresh-lock*\"", "test:locks": "vitest run tests/cache-refresh-lock.test.ts tests/cache-refresh-lock-corrupt-body.test.ts tests/cache-refresh-lock-process.test.ts tests/cache-refresh-lock-status-snapshot.test.ts --poolOptions.forks.singleFork=true", "test:watch": "vitest tests --exclude \"tests/cache-refresh-lock*\"", diff --git a/tests/dev-command.test.ts b/tests/dev-command.test.ts new file mode 100644 index 00000000..413fb5c9 --- /dev/null +++ b/tests/dev-command.test.ts @@ -0,0 +1,73 @@ +import { spawnSync } from 'node:child_process' +import { mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { dirname, join } from 'node:path' +import { pathToFileURL } from 'node:url' +import { afterAll, beforeAll, describe, expect, it } from 'vitest' + +const root = process.cwd() +const manifest = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')) as { + version: string + scripts: { dev: string } +} +let fixture: string + +function runDev(args: string, extraEnv: NodeJS.ProcessEnv = {}) { + // npm runs scripts through the platform's default shell and adds .bin to + // PATH. Exercise those same semantics, including cmd.exe on Windows. + return spawnSync(`${manifest.scripts.dev} ${args}`, { + cwd: fixture, + shell: true, + encoding: 'utf8', + timeout: 20_000, + env: { + ...process.env, + PATH: [dirname(process.execPath), join(fixture, 'node_modules', '.bin'), process.env.PATH].join(process.platform === 'win32' ? ';' : ':'), + ...extraEnv, + }, + }) +} + +describe('development CLI command', () => { + beforeAll(() => { + // Both the checkout and executable paths can contain spaces on Windows. + fixture = mkdtempSync(join(tmpdir(), 'codeburn dev command ')) + const directoryLink = process.platform === 'win32' ? 'junction' : 'dir' + symlinkSync(join(root, 'src'), join(fixture, 'src'), directoryLink) + symlinkSync(join(root, 'node_modules'), join(fixture, 'node_modules'), directoryLink) + writeFileSync(join(fixture, 'package.json'), '{"type":"module"}\n') + }) + + afterAll(() => rmSync(fixture, { recursive: true, force: true })) + + it('runs --version through the default shell from a checkout path with spaces', () => { + const result = runDev('--version') + expect(result.error).toBeUndefined() + expect(result.status, result.stderr).toBe(0) + expect(result.stdout.trim()).toBe(manifest.version) + }) + + it('forwards command arguments to the CLI', () => { + const result = runDev('status --help') + expect(result.status, result.stderr).toBe(0) + expect(result.stdout).toContain('Usage: codeburn status') + expect(result.stdout).toContain('--format') + }) + + it('suppresses deprecation warnings while retaining inherited NODE_OPTIONS', () => { + const preload = join(fixture, 'warning.mjs') + const marker = join(fixture, 'preload-marker') + writeFileSync(preload, [ + 'import { writeFileSync } from "node:fs"', + `writeFileSync(${JSON.stringify(marker)}, "loaded")`, + 'process.emitWarning("dev-command-fixture", "DeprecationWarning")', + ].join('\n')) + const result = runDev('--version', { + NODE_OPTIONS: `--import="${pathToFileURL(preload).href}"`, + }) + expect(result.status, result.stderr).toBe(0) + expect(result.stdout.trim()).toBe(manifest.version) + expect(readFileSync(marker, 'utf8')).toBe('loaded') + expect(result.stderr).not.toContain('dev-command-fixture') + }) +})