From 92c3305cec965a96cc35c78dbb9ce31703e97b8a Mon Sep 17 00:00:00 2001 From: Roger Chappel Date: Wed, 22 Jul 2026 22:00:21 +1000 Subject: [PATCH] fix: execute installed CLI entrypoint --- src/index.js | 15 +++++++++++++-- test/cli.test.js | 44 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/src/index.js b/src/index.js index a505681..23c9bed 100755 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,7 @@ #!/usr/bin/env node import { spawnSync } from 'node:child_process'; -import { pathToFileURL } from 'node:url'; +import { realpathSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; const STATUS_LABELS = { A: 'added', @@ -307,6 +308,16 @@ export function main(argv = process.argv.slice(2), cwd = process.cwd()) { return 0; } -if (import.meta.url === pathToFileURL(process.argv[1]).href) { +function isMainModule(moduleUrl, argvPath = process.argv[1]) { + if (!argvPath) return false; + + try { + return realpathSync(fileURLToPath(moduleUrl)) === realpathSync(argvPath); + } catch { + return false; + } +} + +if (isMainModule(import.meta.url)) { process.exitCode = main(); } diff --git a/test/cli.test.js b/test/cli.test.js index 2551553..0c0f9b6 100644 --- a/test/cli.test.js +++ b/test/cli.test.js @@ -1,9 +1,10 @@ import { test } from 'node:test'; import assert from 'node:assert/strict'; import { execFileSync, execSync } from 'node:child_process'; -import { mkdtempSync, writeFileSync } from 'node:fs'; +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; +import { pathToFileURL } from 'node:url'; test('atomcommit plan test - CLI should handle --help', () => { try { @@ -27,3 +28,44 @@ test('parseNumstat handles binary markers', async () => { const result = parseNumstat('-\t-\tbinary.png\n'); assert.ok(result.get('binary.png').binary, 'should detect binary files'); }); + +test('packed CLI executes through the npm bin symlink and remains import-safe', (t) => { + const packageRoot = process.cwd(); + const parent = mkdtempSync(join(tmpdir(), 'atomcommit-packed-')); + const consumer = join(parent, 'consumer'); + t.after(() => rmSync(parent, { recursive: true, force: true })); + + const tarballName = execFileSync('npm', ['pack', '--silent', '--pack-destination', parent], { + cwd: packageRoot, + encoding: 'utf8', + }).trim(); + + mkdirSync(join(consumer, 'src'), { recursive: true }); + writeFileSync(join(consumer, 'package.json'), '{"private":true}\n'); + writeFileSync(join(consumer, 'src/app.js'), 'console.log("before");\n'); + execFileSync('git', ['init', '--initial-branch', 'main'], { cwd: consumer }); + execFileSync('git', ['config', 'user.email', 'test@example.com'], { cwd: consumer }); + execFileSync('git', ['config', 'user.name', 'Test User'], { cwd: consumer }); + execFileSync('git', ['add', 'package.json', 'src/app.js'], { cwd: consumer }); + execFileSync('git', ['commit', '-m', 'initial'], { cwd: consumer }); + execFileSync('npm', ['install', '--ignore-scripts', '--no-audit', '--no-fund', '--no-save', join(parent, tarballName)], { + cwd: consumer, + }); + writeFileSync(join(consumer, 'src/app.js'), 'console.log("after");\n'); + + const binPath = join(consumer, 'node_modules', '.bin', 'atomcommit'); + const version = execFileSync(binPath, ['--version'], { cwd: consumer, encoding: 'utf8' }); + const plan = execFileSync(binPath, [], { cwd: consumer, encoding: 'utf8' }); + + assert.equal(version.trim(), '0.1.0'); + assert.match(plan, /^# Atomic Commit Plan/); + assert.match(plan, /Files changed: 1/); + + const moduleUrl = pathToFileURL(join(consumer, 'node_modules', 'atomcommit', 'src/index.js')).href; + assert.doesNotThrow(() => { + execFileSync(process.execPath, ['--input-type=module', '--eval', `import(${JSON.stringify(moduleUrl)})`], { + cwd: tmpdir(), + stdio: 'pipe', + }); + }); +});