This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
java-caller is a lightweight cross-platform npm library to call Java commands from Node.js. Its headline feature is auto-installing a matching JDK/JRE (8–21) via njre when the host machine has no compatible Java, then spawning the java process. It ships as a published npm package (main: ./lib/index.js, types: ./lib/index.d.ts), so only lib/ is distributed.
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 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):
npx mocha "test/**/*.test.js" --grep "should call JavaCallerTester.class attached"There is no build step for the library — lib/*.js is the shipped source.
The tests run against compiled .class files and jars under test/java/. These are checked in; regenerate only when changing JavaCallerTester.java:
npm run java:compile # javac --release 8 -> test/java/dist
npm run java:jar # builds JavaCallerTester.jar + JavaCallerTesterRunnable.jar from the manifestsThree modules in lib/, re-exported from index.js:
java-caller.js— theJavaCallerclass, all the real logic.cli.js—JavaCallerCli, a thin wrapper that readsjava-caller-config.json(next to the caller'sbaseDir) and forwardsprocess.argvtoJavaCaller.run(). This is how consumers ship Java executables as their own npm packages (seeexamples/cli_app).index.d.ts— hand-maintained TypeScript types; keep in sync with any option/signature change.
- Resolve the java executable: an explicit
javaExecutable/JAVA_CALLER_JAVA_EXECUTABLEis used as-is; otherwise the literal"java"/"javaw"triggersmanageJavaInstall(). manageJavaInstall()builds a semver rule fromminimumJavaVersion/maximumJavaVersion, checks the systemjava -version, and if it doesn't satisfy the rule: looks for an already-installed match under~/.java-caller/, else installs one withnjre, then recurses. On success it prepends the chosen javabintoPATHand setsJAVA_HOME.buildArguments()partitions user args: anything starting with-D/-X(or listed inadditionalJavaArgs/runOptions.javaArgs) is a JVM arg placed before-jar <jar>or-cp <classPath> <mainClass>; everything else is a program arg placed after.jartakes precedence overclassPath+mainClass.- Spawns via
child_process.spawn, collects stdout/stderr (unlessoutput: "console"→stdio: inherit, ordetached→ignore), and resolves{ status, stdout, stderr, childJavaProcess }. - Always restores the previous
PATHandJAVA_HOMEafter the run — the env mutation is scoped to the call.
- PATH/JAVA_HOME mutation is temporary and must be reverted.
prevPath/prevJavaHomeare captured inaddJavaInPath()and restored at the end ofrun(). Don't add early returns that skip the restore. - Java-version resolution is cached on
globalThis.JAVA_CALLER_VERSIONS_CACHEto avoid repeated lookups across instances in one process. Tests reset this intest/helpers/init.js; if you add caching state, reset it there too. - Platform branching lives in
getPlatformBinPath()(darwin =Contents/Home/bin) and severalos.platform() === "win32"checks. Windows also handles arg quoting (windowsVerbatimArguments),javawfor windowless, andwindowsHide. Any new behavior must be validated on win32/darwin/linux. classPathaccepts a string (split on:, converted to the OS delimiter) or a string array; resolved againstrootPathunlessuseAbsoluteClassPathsis set.
- Tests are mocha +
node:assert, with shared helpers intest/helpers/common.js(checkStatus,checkStdOutIncludes, etc.) and per-run init intest/helpers/init.js(loaded via themocha.requireconfig inpackage.json). test/java-install.test.jsexercises the realnjredownload/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. - macOS defaults
minimumJavaVersionto 11 (no Java 8 there); keep that branch intact.