From 62e72cdad8c46d50547129685b3267986073e2e2 Mon Sep 17 00:00:00 2001 From: Nicolas Vuillamy Date: Sun, 9 Aug 2026 13:18:03 +0200 Subject: [PATCH 1/5] Cut runtime dependencies from 17 packages to 10 Remove fs-extra and debug from the runtime dependencies, and which from the dev dependencies: - fs-extra: 3 of its 7 call sites (existsSync, statSync) were plain node:fs re-exports; the rest map to fs.promises.mkdir/writeFile/readdir and JSON.parse(fs.readFileSync()). Drops fs-extra, graceful-fs, jsonfile and universalify. - debug: replaced by a ~10 line local helper. util.debuglog cannot be used because it only reads NODE_DEBUG from the launch environment, so it would have broken the documented DEBUG=java-caller contract, which this helper keeps working unchanged. Drops debug and ms. - which: used once, to find the java binary in a test, and its engines field (^22.22.2 || ^24.15.0 || >=26) excludes the Node 18/20 versions the CI matrix tests. Replaced by a findInPath test helper. - Bump yauzl so it no longer pulls buffer-crc32. Also fixes a few things found on the way: - engines said node >=12 while njre@3 requires >=18 and every dev tool needs 18+, so consumers on Node 12-16 installed a broken tree. - npm run lint was already broken on main: eslint 10 no longer hoists @eslint/js and globals, which eslint.config.js requires. They are now declared explicitly, and a Lint job runs npm run lint in CI so the script cannot silently rot again. - Drop the @babel/core and uuid overrides, which matched no package in the tree. --- .github/workflows/test.yml | 18 ++++++ CLAUDE.md | 3 +- lib/cli.js | 4 +- lib/java-caller.js | 32 +++++++--- package-lock.json | 126 +++++++++++++------------------------ package.json | 13 ++-- test/helpers/common.js | 18 ++++++ test/helpers/init.js | 4 +- test/java-caller.test.js | 8 +-- 9 files changed, 115 insertions(+), 111 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 92286e9..a2f431c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,6 +11,24 @@ concurrency: cancel-in-progress: true jobs: + lint: + name: Lint + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout Code + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Install node + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: "24" + - name: Install dependencies + run: npm ci + - name: Run lint + run: npm run lint + test: if: github.event_name != 'push' || github.ref_name == github.event.repository.default_branch strategy: diff --git a/CLAUDE.md b/CLAUDE.md index 3827e8b..ca2baae 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -50,10 +50,11 @@ Three modules in `lib/`, re-exported from `index.js`: - **Java-version resolution is cached on `globalThis.JAVA_CALLER_VERSIONS_CACHE`** to avoid repeated lookups across instances in one process. Tests reset this in `test/helpers/init.js`; if you add caching state, reset it there too. - **Platform branching** lives in `getPlatformBinPath()` (darwin = `Contents/Home/bin`) and several `os.platform() === "win32"` checks. Windows also handles arg quoting (`windowsVerbatimArguments`), `javaw` for windowless, and `windowsHide`. Any new behavior must be validated on win32/darwin/linux. - **`classPath`** accepts a string (split on `:`, converted to the OS delimiter) or a string array; resolved against `rootPath` unless `useAbsoluteClassPaths` is set. +- **Runtime dependencies are deliberately minimal** (`njre` + `semver` only): prefer `node:` built-ins over adding a package. The `debug()` helper at the top of `java-caller.js` is a local ~10-line replacement for the `debug` package and keeps the documented `DEBUG=java-caller` contract — `util.debuglog` can't, since it only reads `NODE_DEBUG` from the launch environment. ## Testing notes - Tests are mocha + `node:assert`, with shared helpers in `test/helpers/common.js` (`checkStatus`, `checkStdOutIncludes`, etc.) and per-run init in `test/helpers/init.js` (loaded via the `mocha.require` config in `package.json`). - `test/java-install.test.js` exercises the real `njre` download/install path, which is why the mocha timeout is 5 minutes. -- CI (`.github/workflows/test.yml`) runs the matrix Node 18/20/24 × Java 8/11/17/21/25 × ubuntu/macos/windows, plus a no-Java job (`Test - No Java`) that runs the suite in a container without a system JDK. +- CI (`.github/workflows/test.yml`) runs the matrix Node 18/20/24 × Java 8/11/17/21/25 × ubuntu/macos/windows, plus a no-Java job (`Test - No Java`) that runs the suite in a container without a system JDK, plus a `Lint` job running `npm run lint`. - macOS defaults `minimumJavaVersion` to 11 (no Java 8 there); keep that branch intact. diff --git a/lib/cli.js b/lib/cli.js index 20c2aec..9bdca79 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -1,6 +1,6 @@ #! /usr/bin/env node const { JavaCaller } = require("./java-caller"); -const fse = require("fs-extra"); +const fs = require("fs"); const path = require("path"); class JavaCallerCli { @@ -11,7 +11,7 @@ class JavaCallerCli { constructor(baseDir) { // Use user-defined JSON file to read configuration const configFile = path.resolve(`${baseDir}/java-caller-config.json`); - const options = fse.readJSONSync(configFile); + const options = JSON.parse(fs.readFileSync(configFile, "utf8")); // Default output is console with CLI if (options.output == null) { options.output = "console"; diff --git a/lib/java-caller.js b/lib/java-caller.js index fbce75c..f4cb157 100644 --- a/lib/java-caller.js +++ b/lib/java-caller.js @@ -1,11 +1,25 @@ #! /usr/bin/env node -const debug = require("debug")("java-caller"); -const fse = require("fs-extra"); +const fs = require("fs"); const os = require("os"); const path = require("path"); +const util = require("util"); const { spawn } = require("child_process"); const semver = require("semver"); +// Traces are activated with DEBUG=java-caller, as before: util.debuglog is not usable here +// because it only reads NODE_DEBUG from the environment the process was launched with. +// Read at call time so DEBUG can be set after this module is loaded. +const isDebugEnabled = () => + (process.env.DEBUG || "").split(",").some((entry) => { + const namespace = entry.trim(); + return namespace === "java-caller" || namespace === "*"; + }); +const debug = (...args) => { + if (isDebugEnabled()) { + console.error(`java-caller ${util.format(...args)}`); + } +}; + class JavaCaller { "use strict"; minimumJavaVersion = os.platform() === "darwin" ? 11 : 8; // Mac starts at 11 @@ -334,15 +348,15 @@ class JavaCaller { console.log(`Installing Java ${javaTypeToInstall} ${javaVersionToInstall} in ${this.javaCallerSupportDir}...`); // Create a directory for installing Java and ensure it contains a dummy package.json - await fse.ensureDir(this.javaCallerSupportDir, { mode: "0777" }); + await fs.promises.mkdir(this.javaCallerSupportDir, { recursive: true, mode: "0777" }); const packageJson = `${this.javaCallerSupportDir + path.sep}package.json`; - if (!fse.existsSync(packageJson)) { + if (!fs.existsSync(packageJson)) { const packageJsonContent = { name: "java-caller-support", version: "1.0.0", description: "Java installations by java-caller (https://github.com/nvuillam/node-java-caller)", }; - await fse.writeFile(packageJson, JSON.stringify(packageJsonContent), "utf8"); + await fs.promises.writeFile(packageJson, JSON.stringify(packageJsonContent), "utf8"); } // Install appropriate java version using njre @@ -464,15 +478,15 @@ class JavaCaller { // check if one matches with javaType , minimumJavaVersion and maximumJavaVersion async findJavaVersionHome() { const javaInstallsTopDir = path.join(this.javaCallerSupportDir, "jre"); - if (!fse.existsSync(javaInstallsTopDir)) { + if (!fs.existsSync(javaInstallsTopDir)) { return {}; } - return await fse + return await fs.promises .readdir(javaInstallsTopDir) .then((items) => items - .filter((item) => fse.statSync(path.join(javaInstallsTopDir, item)).isDirectory()) + .filter((item) => fs.statSync(path.join(javaInstallsTopDir, item)).isDirectory()) .map((folder) => { const version = semver.coerce(folder); return { version, folder }; @@ -483,7 +497,7 @@ class JavaCaller { const bin = path.join(home, this.getPlatformBinPath()); return { version, folder, home, bin }; }) - .find(({ bin }) => fse.existsSync(bin)), + .find(({ bin }) => fs.existsSync(bin)), ) .then((match) => { if (!match) return {}; diff --git a/package-lock.json b/package-lock.json index b1e886e..22a9a35 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,21 +9,20 @@ "version": "5.0.0", "license": "MIT", "dependencies": { - "debug": "^4.3.4", - "fs-extra": "^11.1.1", "njre": "^3.0.0", "semver": "^7.5.4" }, "devDependencies": { + "@eslint/js": "^10.0.1", "@types/node": "^25.2.0", "eslint": "^10.0.0", + "globals": "^17.9.0", "mocha": "^11.0.0", "prettier": "^3.1.0", - "typescript": "^7.0.0", - "which": "^7.0.0" + "typescript": "^7.0.0" }, "engines": { - "node": ">=12.0.0" + "node": ">=18.0.0" } }, "node_modules/@eslint-community/eslint-utils": { @@ -125,6 +124,27 @@ "node": "^20.19.0 || ^22.13.0 || >=24" } }, + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, "node_modules/@eslint/object-schema": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", @@ -719,15 +739,6 @@ "dev": true, "license": "ISC" }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -910,6 +921,7 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -1247,20 +1259,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/fs-extra": { - "version": "11.4.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.0.tgz", - "integrity": "sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -1321,11 +1319,18 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" + "node_modules/globals": { + "version": "17.9.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.9.0.tgz", + "integrity": "sha512-m/MvAW61QVU5VDNF1Vj8axt016h8w7L5TU1e9zlab7XIttAT2YAlCwl75K1fOqvMM9apmD7lbCIRhpfkhmxhCg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/has-flag": { "version": "4.0.0", @@ -1433,16 +1438,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/isexe": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", - "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=20" - } - }, "node_modules/jackspeak": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", @@ -1503,18 +1498,6 @@ "dev": true, "license": "MIT" }, - "node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -1666,6 +1649,7 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, "license": "MIT" }, "node_modules/natural-compare": { @@ -2130,15 +2114,6 @@ "dev": true, "license": "MIT" }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -2149,22 +2124,6 @@ "punycode": "^2.1.0" } }, - "node_modules/which": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-7.0.0.tgz", - "integrity": "sha512-RancgH2dmbLdHl6LRhEqvklWMgl/Hdnun0Y90KhBOLkMefg8Qa7/Zel8Sm+8HEcP6DEjzsWzpkuBQEZok58isA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^4.0.0" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^22.22.2 || ^24.15.0 || >=26.0.0" - } - }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -2404,12 +2363,11 @@ } }, "node_modules/yauzl": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.2.1.tgz", - "integrity": "sha512-k1isifdbpNSFEHFJ1ZY4YDewv0IH9FR61lDetaRMD3j2ae3bIXGV+7c+LHCqtQGofSd8PIyV4X6+dHMAnSr60A==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.4.0.tgz", + "integrity": "sha512-jIH9yLR9wqr0wOS0TpBvo/g/2UgZH5qePVbjgRliiF0BYvOZyaBknKsF+x9Iht0O6sqgnB93rCICdOZFecJuDw==", "license": "MIT", "dependencies": { - "buffer-crc32": "~0.2.3", "pend": "~1.2.0" }, "engines": { diff --git a/package.json b/package.json index b07ef7b..3c88274 100644 --- a/package.json +++ b/package.json @@ -37,29 +37,26 @@ }, "homepage": "https://github.com/nvuillam/node-java-caller#readme", "dependencies": { - "debug": "^4.3.4", - "fs-extra": "^11.1.1", "njre": "^3.0.0", "semver": "^7.5.4" }, "devDependencies": { + "@eslint/js": "^10.0.1", "@types/node": "^25.2.0", "eslint": "^10.0.0", + "globals": "^17.9.0", "mocha": "^11.0.0", "prettier": "^3.1.0", - "typescript": "^7.0.0", - "which": "^7.0.0" + "typescript": "^7.0.0" }, "overrides": { - "@babel/core": "^8.0.0", "brace-expansion": "^5.0.6", "diff": "^9.0.0", "js-yaml": "^5.0.0", - "serialize-javascript": "^7.0.5", - "uuid": "^14.0.0" + "serialize-javascript": "^7.0.5" }, "engines": { - "node": ">=12.0.0" + "node": ">=18.0.0" }, "mocha": { "require": [ diff --git a/test/helpers/common.js b/test/helpers/common.js index 2023010..69322ea 100644 --- a/test/helpers/common.js +++ b/test/helpers/common.js @@ -1,5 +1,22 @@ #! /usr/bin/env node const assert = require("assert"); +const fs = require("fs"); +const os = require("os"); +const path = require("path"); + +// Locate an executable in PATH, honoring PATHEXT on Windows. Returns null when not found. +function findInPath(command) { + const extensions = os.platform() === "win32" ? (process.env.PATHEXT || ".EXE").split(path.delimiter) : [""]; + for (const dir of (process.env.PATH || "").split(path.delimiter).filter(Boolean)) { + for (const extension of extensions) { + const candidate = path.join(dir, command + extension); + if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) { + return candidate; + } + } + } + return null; +} // Reset codeNarcCallsCounter before each test const beforeEachTestCase = function () { @@ -26,6 +43,7 @@ function checkStdErrIncludes(textToCheck, stdout, stderr) { module.exports = { beforeEachTestCase, + findInPath, checkStatus, checkStdOutIncludes, checkStdOutIncludesOneOf, diff --git a/test/helpers/init.js b/test/helpers/init.js index 8d863da..263fba7 100644 --- a/test/helpers/init.js +++ b/test/helpers/init.js @@ -4,8 +4,8 @@ console.log("npm run test initialized"); // Activate debug log if we are in debug mode const debug = typeof v8debug === "object" || /--debug|--inspect|--inspect-brk/.test(process.execArgv.join(" ")); -if (debug) { - require("debug").enable("java-caller"); +if (debug && !(process.env.DEBUG || "").includes("java-caller")) { + process.env.DEBUG = process.env.DEBUG ? `${process.env.DEBUG},java-caller` : "java-caller"; } // Reinitialize cache diff --git a/test/java-caller.test.js b/test/java-caller.test.js index 2c2c6d0..270f79d 100644 --- a/test/java-caller.test.js +++ b/test/java-caller.test.js @@ -2,11 +2,11 @@ "use strict"; const { JavaCaller } = require('../lib/index'); const os = require("os"); -const which = require("which"); const path = require('path'); const { beforeEachTestCase, + findInPath, checkStatus, checkStdOutIncludes, checkStdErrIncludes @@ -169,10 +169,8 @@ describe("Call with classes", () => { }); it("should call JavaCallerTester.class in JavaCallerTester.jar (override java)", async () => { - let javaPath; - try { - javaPath = which.sync("java"); - } catch { + const javaPath = findInPath("java"); + if (!javaPath) { console.log("Java not found: ignore test method"); } if (javaPath) { From d345c2c116592c92fe856d5ce0d6474614c1cf58 Mon Sep 17 00:00:00 2001 From: Nicolas Vuillamy Date: Sun, 9 Aug 2026 13:23:08 +0200 Subject: [PATCH 2/5] Add debuglog and PATHEXT to the cspell dictionary --- .cspell.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.cspell.json b/.cspell.json index cad8ba9..456227c 100644 --- a/.cspell.json +++ b/.cspell.json @@ -20,6 +20,7 @@ "Gowans", "Inclusivity", "KICS", + "PATHEXT", "PROSELINT", "Perso", "SIGINT", @@ -34,6 +35,7 @@ "customarg", "cvfm", "danunafig", + "debuglog", "desync", "distrib", "djukxe", From f85cc1bd50deedd58caf2894d2571111972d81c2 Mon Sep 17 00:00:00 2001 From: Nicolas Vuillamy Date: Sun, 9 Aug 2026 13:26:12 +0200 Subject: [PATCH 3/5] Replace the debug helper with util.debuglog (breaking) Traces are now activated with NODE_DEBUG=java-caller instead of DEBUG=java-caller. Keeping the old contract required a hand-rolled namespace matcher, since util.debuglog only reads NODE_DEBUG from the environment the process was launched with; using the built-in directly drops that custom code, at the cost of a documented breaking change. Consequences, documented in CHANGELOG and README: - the env var is NODE_DEBUG, and it must be set at launch: assigning process.env.NODE_DEBUG at runtime no longer enables traces, so test/helpers/init.js can no longer turn them on under a debugger - output format becomes "JAVA-CALLER : " Updated the CI workflow, test:debug script and both example apps. --- .claude/agents/pr-fix.md | 2 +- .github/workflows/test.yml | 4 ++-- CHANGELOG.md | 6 ++++++ CLAUDE.md | 7 ++++--- README.md | 8 +++++--- examples/cli_app/package.json | 4 ++-- examples/module_app/package.json | 4 ++-- lib/java-caller.js | 18 ++++-------------- package.json | 2 +- test/helpers/init.js | 8 +++----- 10 files changed, 30 insertions(+), 33 deletions(-) diff --git a/.claude/agents/pr-fix.md b/.claude/agents/pr-fix.md index a0c63ce..d5e63dd 100644 --- a/.claude/agents/pr-fix.md +++ b/.claude/agents/pr-fix.md @@ -58,7 +58,7 @@ Do not edit anything when returning this block. - Edit sources: `lib/*.js` (and keep `lib/index.d.ts` in sync when you change an option, a signature, or an export); tests in `test/`; config files at the repo root (`package.json`, `.eslintrc.js`, `.mega-linter.yml`, etc.); workflows in `.github/workflows/`. - Keep the existing code style (ESLint `eslint:recommended` + Prettier). Use the existing `debug("java-caller")` logging pattern. -- Run local validation that needs no network where possible: `npm run lint:fix`, then `npm run test` (set `DEBUG=java-caller` to mirror CI; the mocha timeout is 5 min because real JRE installs run). Coverage runs under `npm run test:coverage`. +- Run local validation that needs no network where possible: `npm run lint:fix`, then `npm run test` (set `NODE_DEBUG=java-caller` to mirror CI; the mocha timeout is 5 min because real JRE installs run). Coverage runs under `npm run test:coverage`. - Do NOT introduce defensive hacks (skip-on-fail, retries, `|| true`, weakened assertions, broad eslint ignores) to force green - fix the root cause. - **npm only**, never `yarn` (it would desync `package-lock.json`). diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a2f431c..9253015 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -68,7 +68,7 @@ jobs: run: npm ci - name: Run tests env: - DEBUG: java-caller,njre + NODE_DEBUG: java-caller run: npm run test test-no-java: @@ -91,5 +91,5 @@ jobs: run: npm ci - name: Run tests env: - DEBUG: "java-caller" + NODE_DEBUG: "java-caller" run: npm run test diff --git a/CHANGELOG.md b/CHANGELOG.md index 57904d4..ae8554b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ ## Unreleased +- **Breaking**: Debug traces are now activated with `NODE_DEBUG=java-caller` instead of `DEBUG=java-caller`. The `debug` package has been replaced by Node's built-in `util.debuglog`, which only reads `NODE_DEBUG` from the environment the process is launched with (setting `process.env.NODE_DEBUG` at runtime no longer enables traces). The output format changes accordingly, from `java-caller +1ms` to `JAVA-CALLER : `. +- **Breaking**: Minimum supported Node.js version is now 18 (`engines` previously declared `>=12`, while `njre` v3 already required `>=18`) +- Reduce runtime dependencies from 17 packages to 10: remove `fs-extra` (replaced by `node:fs`) and `debug` (replaced by `node:util`), and upgrade `yauzl` so it no longer pulls `buffer-crc32`. Only `njre` and `semver` remain as direct dependencies. +- Remove the `which` dev dependency, whose `engines` excluded the Node versions tested in CI +- Fix `npm run lint`, broken since the ESLint v10 upgrade: `@eslint/js` and `globals` are required by `eslint.config.js` but were no longer installed transitively. They are now explicit dev dependencies, and CI runs `npm run lint` so the script cannot silently break again. + ## [5.0.0] 2026-06-30 - **Breaking**: Upgrade `njre` to v2.0.0: auto-installed JDK/JRE now comes from Eclipse Temurin (`api.adoptium.net`) instead of the deprecated AdoptOpenJDK endpoint (which hung under Node 24) diff --git a/CLAUDE.md b/CLAUDE.md index ca2baae..5b3f58a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -9,8 +9,8 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Commands ```shell -npm run test # Run all mocha tests (timeout 300000ms; CI sets DEBUG=java-caller,njre) -npm run test:debug # Tests with DEBUG=java-caller enabled +npm run test # Run all mocha tests (timeout 300000ms; CI sets NODE_DEBUG=java-caller) +npm run test:debug # Tests with NODE_DEBUG=java-caller enabled npm run lint:fix # eslint --fix on **/*.js, then prettier on lib (tab-width 4, print-width 150) # Run a single test by name (mocha grep on the it()/describe() title): @@ -50,7 +50,8 @@ Three modules in `lib/`, re-exported from `index.js`: - **Java-version resolution is cached on `globalThis.JAVA_CALLER_VERSIONS_CACHE`** to avoid repeated lookups across instances in one process. Tests reset this in `test/helpers/init.js`; if you add caching state, reset it there too. - **Platform branching** lives in `getPlatformBinPath()` (darwin = `Contents/Home/bin`) and several `os.platform() === "win32"` checks. Windows also handles arg quoting (`windowsVerbatimArguments`), `javaw` for windowless, and `windowsHide`. Any new behavior must be validated on win32/darwin/linux. - **`classPath`** accepts a string (split on `:`, converted to the OS delimiter) or a string array; resolved against `rootPath` unless `useAbsoluteClassPaths` is set. -- **Runtime dependencies are deliberately minimal** (`njre` + `semver` only): prefer `node:` built-ins over adding a package. The `debug()` helper at the top of `java-caller.js` is a local ~10-line replacement for the `debug` package and keeps the documented `DEBUG=java-caller` contract — `util.debuglog` can't, since it only reads `NODE_DEBUG` from the launch environment. +- **Runtime dependencies are deliberately minimal** (`njre` + `semver` only): prefer `node:` built-ins over adding a package. +- **Debug traces use `util.debuglog`**, activated with `NODE_DEBUG=java-caller` (not `DEBUG=`, which was the pre-v6 `debug` package contract). `debuglog` reads `NODE_DEBUG` only from the environment the process was launched with, so tests and helpers cannot turn traces on at runtime — `npm run test:debug` sets it on the command line. ## Testing notes diff --git a/README.md b/README.md index b95f028..1323899 100644 --- a/README.md +++ b/README.md @@ -170,13 +170,15 @@ You can see **more examples in** [**test methods**](https://github.com/nvuillam/ ## TROUBLESHOOTING -Set environment variable `DEBUG=java-caller` before calling your code using java-caller module, and you will see the java commands executed. +Set environment variable `NODE_DEBUG=java-caller` before calling your code using java-caller module, and you will see the java commands executed. + +> Since v6.0.0 traces use Node's built-in [`util.debuglog`](https://nodejs.org/api/util.html#utildebuglogsection-callback) instead of the `debug` package, so the variable is `NODE_DEBUG` and not `DEBUG` anymore. It must be set in the environment the process is launched with: setting `process.env.NODE_DEBUG` at runtime has no effect. Example debug log: ```shell -java-caller Found Java version 1.80131 +1s -java-caller Java command: java -Xms256m -Xmx2048m -cp C:\Work\gitPerso\node-java-caller\test\java\dist com.nvuillam.javacaller.JavaCallerTester -customarg nico +1ms +JAVA-CALLER 12345: Found Java version 1.80131 +JAVA-CALLER 12345: Java command: java -Xms256m -Xmx2048m -cp C:\Work\gitPerso\node-java-caller\test\java\dist com.nvuillam.javacaller.JavaCallerTester -customarg nico ``` ## CONTRIBUTE diff --git a/examples/cli_app/package.json b/examples/cli_app/package.json index f51064e..56dd4c1 100644 --- a/examples/cli_app/package.json +++ b/examples/cli_app/package.json @@ -12,9 +12,9 @@ "scripts": { "install-local-cli": "npm install && npm link --force", "run:source": "node lib/index.js -a list --of arguments", - "run:source:verbose": "env DEBUG=java-caller node lib/index.js -a list --of arguments", + "run:source:verbose": "env NODE_DEBUG=java-caller node lib/index.js -a list --of arguments", "run:cli": "java-caller-example-cli -a list --of arguments", - "run:verbose": "env DEBUG=java-caller java-caller-example-cli -a list --of arguments" + "run:verbose": "env NODE_DEBUG=java-caller java-caller-example-cli -a list --of arguments" }, "keywords": [ "java-caller", diff --git a/examples/module_app/package.json b/examples/module_app/package.json index 780492b..dbb2352 100644 --- a/examples/module_app/package.json +++ b/examples/module_app/package.json @@ -12,9 +12,9 @@ "scripts": { "install-local-cli": "npm install && npm link --force", "run:source": "node lib/index.js", - "run:source:verbose": "env DEBUG=java-caller node lib/index.js", + "run:source:verbose": "env NODE_DEBUG=java-caller node lib/index.js", "run:cli": "java-caller-example-module", - "run:cli:verbose": "env DEBUG=java-caller java-caller-example-module", + "run:cli:verbose": "env NODE_DEBUG=java-caller java-caller-example-module", "test": "echo \"I strongly encourage you to implement test cases and code coverage, with mocha and nyc for example :)\"" }, "keywords": [ diff --git a/lib/java-caller.js b/lib/java-caller.js index f4cb157..609ff9f 100644 --- a/lib/java-caller.js +++ b/lib/java-caller.js @@ -2,23 +2,13 @@ const fs = require("fs"); const os = require("os"); const path = require("path"); -const util = require("util"); +const { debuglog } = require("util"); const { spawn } = require("child_process"); const semver = require("semver"); -// Traces are activated with DEBUG=java-caller, as before: util.debuglog is not usable here -// because it only reads NODE_DEBUG from the environment the process was launched with. -// Read at call time so DEBUG can be set after this module is loaded. -const isDebugEnabled = () => - (process.env.DEBUG || "").split(",").some((entry) => { - const namespace = entry.trim(); - return namespace === "java-caller" || namespace === "*"; - }); -const debug = (...args) => { - if (isDebugEnabled()) { - console.error(`java-caller ${util.format(...args)}`); - } -}; +// NODE_DEBUG must be set in the environment the process is launched with: +// debuglog never re-reads it, so setting process.env.NODE_DEBUG at runtime has no effect. +const debug = debuglog("java-caller"); class JavaCaller { "use strict"; diff --git a/package.json b/package.json index 3c88274..3e4f018 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "java:compile": "javac -d test/java/dist --release 8 test/java/src/com/nvuillam/javacaller/JavaCallerTester.java", "java:jar": "cd test/java/dist && jar -cvfm ./../jar/JavaCallerTester.jar ./../jar/manifest/Manifest.txt com/nvuillam/javacaller/*.class && jar -cvfm ./../jar/JavaCallerTesterRunnable.jar ./../jar/manifest-runnable/Manifest.txt com/nvuillam/javacaller/*.class", "test": "mocha \"test/**/*.test.js\"", - "test:debug": "env DEBUG=java-caller mocha --reporter spec \"test/**/*.test.js\"" + "test:debug": "env NODE_DEBUG=java-caller mocha --reporter spec \"test/**/*.test.js\"" }, "repository": { "type": "git", diff --git a/test/helpers/init.js b/test/helpers/init.js index 263fba7..e6dcd77 100644 --- a/test/helpers/init.js +++ b/test/helpers/init.js @@ -2,11 +2,9 @@ "use strict"; console.log("npm run test initialized"); -// Activate debug log if we are in debug mode -const debug = typeof v8debug === "object" || /--debug|--inspect|--inspect-brk/.test(process.execArgv.join(" ")); -if (debug && !(process.env.DEBUG || "").includes("java-caller")) { - process.env.DEBUG = process.env.DEBUG ? `${process.env.DEBUG},java-caller` : "java-caller"; -} +// Traces can no longer be enabled from here: util.debuglog only reads NODE_DEBUG from the +// environment the process was launched with. Run `npm run test:debug`, or launch your +// debugger with NODE_DEBUG=java-caller set. // Reinitialize cache globalThis.JAVA_CALLER_VERSIONS_CACHE = null; From 1fef2beb4dffae9e94d77b831e206a50d3ff1401 Mon Sep 17 00:00:00 2001 From: Nicolas Vuillamy Date: Sun, 9 Aug 2026 17:17:06 +0200 Subject: [PATCH 4/5] Disable MegaLinter custom flavor suggestions The javascript flavor is a deliberate choice, so the suggestion to build a custom flavor is noise on every run. --- .mega-linter.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.mega-linter.yml b/.mega-linter.yml index cde84e0..a685c10 100644 --- a/.mega-linter.yml +++ b/.mega-linter.yml @@ -1,6 +1,8 @@ # Flavor & version used by mega-linter-runner and the MegaLinter agent skills MEGALINTER_FLAVOR: javascript MEGALINTER_VERSION: v10 +# The javascript flavor is a deliberate choice: skip the custom-flavor suggestions +FLAVOR_SUGGESTIONS: false DISABLE_LINTERS: - TYPESCRIPT_STANDARD - TYPESCRIPT_PRETTIER From fad36f5bdbcaad4975aac0658362fd15b6b808ac Mon Sep 17 00:00:00 2001 From: Nicolas Vuillamy Date: Sun, 9 Aug 2026 17:22:05 +0200 Subject: [PATCH 5/5] Prepare v6.0.0 Breaking changes: NODE_DEBUG replaces DEBUG for traces, and the minimum supported Node.js version moves from 12 to 18. --- CHANGELOG.md | 2 ++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae8554b..8fb0da2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## [6.0.0] 2026-08-09 + - **Breaking**: Debug traces are now activated with `NODE_DEBUG=java-caller` instead of `DEBUG=java-caller`. The `debug` package has been replaced by Node's built-in `util.debuglog`, which only reads `NODE_DEBUG` from the environment the process is launched with (setting `process.env.NODE_DEBUG` at runtime no longer enables traces). The output format changes accordingly, from `java-caller +1ms` to `JAVA-CALLER : `. - **Breaking**: Minimum supported Node.js version is now 18 (`engines` previously declared `>=12`, while `njre` v3 already required `>=18`) - Reduce runtime dependencies from 17 packages to 10: remove `fs-extra` (replaced by `node:fs`) and `debug` (replaced by `node:util`), and upgrade `yauzl` so it no longer pulls `buffer-crc32`. Only `njre` and `semver` remain as direct dependencies. diff --git a/package-lock.json b/package-lock.json index 22a9a35..1bc514a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "java-caller", - "version": "5.0.0", + "version": "6.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "java-caller", - "version": "5.0.0", + "version": "6.0.0", "license": "MIT", "dependencies": { "njre": "^3.0.0", diff --git a/package.json b/package.json index 3e4f018..d97502d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "java-caller", - "version": "5.0.0", + "version": "6.0.0", "description": "Library to easily call java from node sources. Automatically installs java if not present", "main": "./lib/index.js", "types": "./lib/index.d.ts",