Library maintainers who claim “works on Node 20/22/24” often only smoke-import on whatever Node is on PATH — regressions on older majors slip through until a consumer opens an issue. cross-runtime-smoke builds the package once, then dynamic-imports the configured entry under each listed Node major (skipping missing runtimes locally; failing them in CI via an Action matrix). Unlike nvm use && node -e scripts or a single-job setup-node, it centralizes config, skip/fail policy, and a copy-paste matrix workflow.
# from the repository root
npm install
npm test
npx cross-runtime-smoke --help
npx cross-runtime-smoke --config fixtures/configs/ok.json# from the repository root
npm install
npm test
npx cross-runtime-smoke --help
npx cross-runtime-smoke --config .\fixtures\configs\ok.jsonExpect PASS for majors you have installed and SKIP for missing ones (default). Broken import:
npx cross-runtime-smoke --config fixtures/configs/broken.json
# exit code 1 — RESULT: FAIL- Load config —
cross-runtime-smoke.jsonor CLI flags (--versions,--entry,--build, …) - Build once — runs
npm run build(or your command) inpackageDir - Resolve runtimes — binaries map →
node20/node-22→ nvm → currentprocess.execPath - Smoke-import — spawn each Node binary to
import()the entry file - Skip or fail — missing majors are skipped by default (
--skip-missing); use--fail-missingin CI
cross-runtime-smoke.json:
{
"packageDir": ".",
"build": "npm run build",
"entry": "dist/index.js",
"versions": [20, 22, 24],
"skipMissing": true,
"binaries": {
"20": "/usr/local/bin/node20"
}
}| Field | Meaning |
|---|---|
packageDir |
Package root (relative to config file or cwd) |
build |
Shell build command; "" skips build |
entry |
Path relative to packageDir to import |
versions |
Node majors to exercise |
skipMissing |
Skip (true) or fail (false) when a major is not installed |
binaries |
Optional major → absolute node binary path |
cross-runtime-smoke [options]
-c, --config <file> Config JSON
-p, --package-dir <dir> Package directory
-e, --entry <path> Entry under packageDir
-b, --build <cmd> Build command ("" to skip)
-V, --versions <list> e.g. 20,22,24
--skip-missing Skip missing runtimes (default)
--fail-missing Fail when a major is missing
-h, --help
--version
Action inputs (see action.yml): config, package-dir, entry, build, versions, fail-missing.
Copy examples/matrix.yml into your repo. Each matrix job installs one Node major via actions/setup-node, then runs this action with versions: ${{ matrix.node }} and fail-missing: true so that job cannot silently skip.
| Fixture | Expected |
|---|---|
fixtures/configs/ok.json → ok-lib |
PASS on installed majors; SKIP others |
fixtures/configs/broken.json → broken-lib |
FAIL (import throws) |
fixtures/configs/skip-missing.json |
SKIP only (Node 99) — overall PASS |
import { runCrossRuntimeSmoke, loadConfigFromPath } from "cross-runtime-smoke";
const config = loadConfigFromPath("./cross-runtime-smoke.json");
const report = await runCrossRuntimeSmoke({ config });
if (!report.ok) process.exit(report.exitCode);- Node.js 20+ to run the CLI itself
- Target Node majors installed locally or provided via Action matrix /
binaries
- Does not download or install Node versions (use nvm, volta, or Actions
setup-node) - Does not smoke-test native addons across OS arches beyond “does
import()load” - Conditional exports (
browser/require/import) are exercised only as the target Node resolves them - Build command is a single shell string, not a task graph
- Does not replace full integration test suites — this is import smoke only
MIT