diff --git a/bin/excod.cjs b/bin/excod.cjs index 21e8382..081eebe 100755 --- a/bin/excod.cjs +++ b/bin/excod.cjs @@ -1,2 +1,16 @@ #!/usr/bin/env node -import('../dist/cli.js'); +import('../dist/cli.js').catch((error) => { + const missingBuiltCli = + error?.code === 'ERR_MODULE_NOT_FOUND' && + typeof error?.message === 'string' && + /[\\/]dist[\\/]cli\.js/.test(error.message); + + if (missingBuiltCli) { + console.error('Error: dist/cli.js not found. Run `npm run build` first.'); + console.error(error.message); + } else { + console.error('Error: CLI startup failed.'); + console.error(error?.stack ?? error); + } + process.exit(1); +}); diff --git a/tests/bin.test.ts b/tests/bin.test.ts new file mode 100644 index 0000000..dff7ead --- /dev/null +++ b/tests/bin.test.ts @@ -0,0 +1,51 @@ +import { describe, expect, it } from 'vitest'; +import { spawnSync } from 'node:child_process'; +import { copyFileSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { dirname, join } from 'node:path'; + +describe('bin/excod.cjs', () => { + it('explains how to recover when dist/cli.js is missing', () => { + const tempDir = mkdtempSync(join(tmpdir(), 'excod-bin-missing-dist-')); + const tempBin = join(tempDir, 'bin', 'excod.cjs'); + mkdirSync(dirname(tempBin), { recursive: true }); + copyFileSync('bin/excod.cjs', tempBin); + + try { + const result = spawnSync(process.execPath, [tempBin], { + cwd: tempDir, + encoding: 'utf8', + }); + + expect(result.status).toBe(1); + expect(result.stderr).toContain('Run `npm run build` first'); + expect(result.stderr).toContain('dist/cli.js'); + } finally { + rmSync(tempDir, { recursive: true, force: true }); + } + }); + + it('does not report missing dist for CLI runtime failures', () => { + const tempDir = mkdtempSync(join(tmpdir(), 'excod-bin-runtime-error-')); + const tempBin = join(tempDir, 'bin', 'excod.cjs'); + const tempCli = join(tempDir, 'dist', 'cli.js'); + mkdirSync(dirname(tempBin), { recursive: true }); + mkdirSync(dirname(tempCli), { recursive: true }); + copyFileSync('bin/excod.cjs', tempBin); + writeFileSync(tempCli, "throw new Error('boom from cli');\n", 'utf8'); + + try { + const result = spawnSync(process.execPath, [tempBin], { + cwd: tempDir, + encoding: 'utf8', + }); + + expect(result.status).toBe(1); + expect(result.stderr).toContain('CLI startup failed'); + expect(result.stderr).toContain('boom from cli'); + expect(result.stderr).not.toContain('Run `npm run build` first'); + } finally { + rmSync(tempDir, { recursive: true, force: true }); + } + }); +});