diff --git a/bin/check-and-run.cjs b/bin/check-and-run.cjs new file mode 100644 index 00000000..4214ff4c --- /dev/null +++ b/bin/check-and-run.cjs @@ -0,0 +1,24 @@ +#!/usr/bin/env node + +"use strict"; + +const { createRequire } = require("node:module"); +const localRequire = createRequire(__filename); + +const manifest = localRequire("../package.json"); +const semver = localRequire("semver"); + +const currentVersion = process.versions.node; +const requiredRange = manifest.engines.node; + +if (!semver.satisfies(currentVersion, requiredRange)) { + console.error( + `\n @nodesecure/cli requires Node.js ${requiredRange}.` + + `\n Current version: v${currentVersion}\n` + ); + process.exit(1); +} + +if (require.main === module) { + import("./index.js"); +} diff --git a/package.json b/package.json index 75220e5e..06d658eb 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,8 @@ "description": "Node.js security CLI", "main": "./bin/index.js", "bin": { - "node-secure": "./bin/index.js", - "nsecure": "./bin/index.js" + "node-secure": "./bin/check-and-run.cjs", + "nsecure": "./bin/check-and-run.cjs" }, "type": "module", "engines": { diff --git a/test/commands/bin.test.js b/test/commands/bin.test.js new file mode 100644 index 00000000..b0fae5f8 --- /dev/null +++ b/test/commands/bin.test.js @@ -0,0 +1,51 @@ +// Import Node.js Dependencies +import assert from "node:assert"; +import { createRequire } from "node:module"; +import { describe, it, mock, afterEach } from "node:test"; + +const require = createRequire(import.meta.url); + +describe("bin/check-and-run.cjs", () => { + afterEach(() => { + mock.restoreAll(); + // Clear the require cache so the file executes again for each test + delete require.cache[require.resolve("../../bin/check-and-run.cjs")]; + }); + + it("should exit with code 1 if semver.satisfies returns false", (ctx) => { + const logs = []; + ctx.mock.method(console, "error", (msg) => logs.push(msg)); + + ctx.mock.method(process, "exit", (code) => { + throw new Error(`process.exit(${code}) called`); + }); + + // Mock semver to simulate a failed version check + const semver = require("semver"); + ctx.mock.method(semver, "satisfies", () => false); + + assert.throws( + () => require("../../bin/check-and-run.cjs"), + { message: "process.exit(1) called" }, + "should halt execution and exit with code 1" + ); + + const allLogs = logs.join(""); + assert.ok(allLogs.includes("@nodesecure/cli requires Node.js"), "should print required Node.js version message"); + }); + + it("should not exit if semver.satisfies returns true", (ctx) => { + let exitCode; + ctx.mock.method(process, "exit", (code) => { + exitCode = code; + }); + + // Mock semver to simulate a successful version check + const semver = require("semver"); + ctx.mock.method(semver, "satisfies", () => true); + + require("../../bin/check-and-run.cjs"); + + assert.strictEqual(exitCode, undefined, "should not call process.exit"); + }); +});