Subject of the issue/enhancement/features
Since v5.56.0, grunt build fails on Node 16 and Node 18 with crypto is not defined. This includes Node 18, which is the framework's own declared minimum in package.json (engines.node: ">=18").
v5.56.2 is still the current release, so anyone running adapt create course today on Node 16 or 18 gets a course that will not build. This affects command-line and CI users as well as authoring tool installs. It has been broken since 2026-04-13.
Your environment
- Framework v5.56.0 through v5.56.2 (current release)
- Node 16.14.0, 16.20.2, 18.20.8 — all fail. Node 20.20.0, 22.22.0, 24.13.0 — all pass
- Not browser or device specific; the build never completes
Steps to reproduce
Two commands, no authoring tool involved:
nvm use 18
npx adapt-cli@3.4.0 create course my-course v5.56.2 true
cd my-course && npx grunt build
adapt create course itself succeeds. The failure is on the very next step the CLI tells you to run.
Switching to Node 20 or above in the same directory, with nothing else changed, then builds successfully.
Expected behaviour
The build completes and produces a working adapt.min.js, as it does on v5.55.2 and as it does on Node 20+.
Actual behaviour
Exit code 3:
Warning: crypto is not defined Use --force to continue.
Aborted due to warnings.
Every build mode is affected — build, build:dev, build:prod, server-build, dev and server — because the error happens when the task loads, before any minification decision is made. Tasks that do not pull in the javascript task (help, check-json, clean, less) still work.
Cause. #3791 (Dependabot, merged 2026-04-13, shipped in v5.56.0) bumped @rollup/plugin-terser from ^0.4.0 to ^1.0.0, which pulls in serialize-javascript@7. That version calls the global crypto.getRandomValues() at module scope, where version 6 used require('randombytes') instead. The global crypto object does not exist in Node before version 19/20. grunt/tasks/javascript.js:15 requires the plugin when the task loads, so it throws immediately.
Why it went unnoticed for nearly four months. CI pins node-version: 'lts/*' in both test.yml and build-and-deploy.yml, so Node 16 and 18 are never exercised despite the declared minimum of 18. And npm install only warns about the mismatch, then exits successfully:
npm warn EBADENGINE package: '@rollup/plugin-terser@1.0.0',
npm warn EBADENGINE required: { node: '>=20.0.0' },
npm warn EBADENGINE current: { node: 'v18.20.8' }
npm warn EBADENGINE package: 'serialize-javascript@7.0.5',
npm warn EBADENGINE required: { node: '>=20.0.0' },
Nothing fails until build time.
Please do not use --force
Grunt suggests it, and it makes things worse. grunt build:prod --force exits 0 with "Done, but with warnings" and writes a build folder that looks complete — index.html, compiled LESS, scriptLoader.js — but with no adapt.min.js. scriptLoader.js still asks for it, so the course returns a 404 and never starts, while CI reports success and the authoring tool reports a successful publish. That is worse than the visible failure, so it is worth flagging to anyone triaging this.
Suggested fix
Keep the security bump and raise engines.node to >=22.
A revert is not free. #3791 was a security fix: serialize-javascript 6.0.2 carries GHSA-5c6j-r48x-rmvq (high, remote code execution, patched in 7.0.3) and GHSA-qj8w-gfj5-8c6v (moderate, denial of service, patched in 7.0.5). Pinning back reintroduces both, and Dependabot would simply reopen the PR.
Raising the floor to >=20 is not the answer either, because Node 20 reached end of life in March 2026. Every Node version this bug breaks is already unsupported — v16 since August 2023, v18 since March 2025, v20 since March 2026. The current LTS lines are 22 and 24, so >=22 is the floor that matches what Node actually supports.
There is no way to fix this from inside the framework. @rollup/plugin-terser@1.0.0 runs terser in a worker thread pool, and each worker requires serialize-javascript again in its own global context. Setting globalThis.crypto at the top of Gruntfile.js does not reach the workers — tested, and it just moves the same error from load time into javascript:compile. Pushing --require onto process.execArgv does not reach them either. Only a process-level NODE_OPTIONS=--require preload propagates, which has to come from outside the repo.
Bumping forward does not help either: serialize-javascript 7.0.6 and 7.0.7 both still use the global, and @rollup/plugin-terser has no release above 1.0.0.
If a transitional patch is wanted for people still on older Node, "overrides": { "serialize-javascript": "^6.0.2" } does work — verified building successfully on Node 16.14.0 — at the cost of reintroducing both advisories.
Separately, it is worth replacing node-version: 'lts/*' in CI with an explicit matrix on the supported lines. That is the change that stops this recurring: lts/* silently tracks whatever is newest, which is exactly why a declared minimum of 18 went untested for nearly four months.
Surfaced via adaptlearning/adapt_authoring#2847.
Posted via collaboration with Claude Code
Subject of the issue/enhancement/features
Since v5.56.0,
grunt buildfails on Node 16 and Node 18 withcrypto is not defined. This includes Node 18, which is the framework's own declared minimum in package.json (engines.node: ">=18").v5.56.2 is still the current release, so anyone running
adapt create coursetoday on Node 16 or 18 gets a course that will not build. This affects command-line and CI users as well as authoring tool installs. It has been broken since 2026-04-13.Your environment
Steps to reproduce
Two commands, no authoring tool involved:
adapt create courseitself succeeds. The failure is on the very next step the CLI tells you to run.Switching to Node 20 or above in the same directory, with nothing else changed, then builds successfully.
Expected behaviour
The build completes and produces a working adapt.min.js, as it does on v5.55.2 and as it does on Node 20+.
Actual behaviour
Exit code 3:
Every build mode is affected —
build,build:dev,build:prod,server-build,devandserver— because the error happens when the task loads, before any minification decision is made. Tasks that do not pull in thejavascripttask (help,check-json,clean,less) still work.Cause. #3791 (Dependabot, merged 2026-04-13, shipped in v5.56.0) bumped
@rollup/plugin-terserfrom^0.4.0to^1.0.0, which pulls inserialize-javascript@7. That version calls the globalcrypto.getRandomValues()at module scope, where version 6 usedrequire('randombytes')instead. The globalcryptoobject does not exist in Node before version 19/20.grunt/tasks/javascript.js:15requires the plugin when the task loads, so it throws immediately.Why it went unnoticed for nearly four months. CI pins
node-version: 'lts/*'in both test.yml and build-and-deploy.yml, so Node 16 and 18 are never exercised despite the declared minimum of 18. Andnpm installonly warns about the mismatch, then exits successfully:Nothing fails until build time.
Please do not use
--forceGrunt suggests it, and it makes things worse.
grunt build:prod --forceexits 0 with "Done, but with warnings" and writes a build folder that looks complete — index.html, compiled LESS, scriptLoader.js — but with no adapt.min.js. scriptLoader.js still asks for it, so the course returns a 404 and never starts, while CI reports success and the authoring tool reports a successful publish. That is worse than the visible failure, so it is worth flagging to anyone triaging this.Suggested fix
Keep the security bump and raise
engines.nodeto>=22.A revert is not free. #3791 was a security fix:
serialize-javascript6.0.2 carries GHSA-5c6j-r48x-rmvq (high, remote code execution, patched in 7.0.3) and GHSA-qj8w-gfj5-8c6v (moderate, denial of service, patched in 7.0.5). Pinning back reintroduces both, and Dependabot would simply reopen the PR.Raising the floor to
>=20is not the answer either, because Node 20 reached end of life in March 2026. Every Node version this bug breaks is already unsupported — v16 since August 2023, v18 since March 2025, v20 since March 2026. The current LTS lines are 22 and 24, so>=22is the floor that matches what Node actually supports.There is no way to fix this from inside the framework.
@rollup/plugin-terser@1.0.0runs terser in a worker thread pool, and each worker requiresserialize-javascriptagain in its own global context. SettingglobalThis.cryptoat the top of Gruntfile.js does not reach the workers — tested, and it just moves the same error from load time intojavascript:compile. Pushing--requireontoprocess.execArgvdoes not reach them either. Only a process-levelNODE_OPTIONS=--requirepreload propagates, which has to come from outside the repo.Bumping forward does not help either:
serialize-javascript7.0.6 and 7.0.7 both still use the global, and@rollup/plugin-terserhas no release above 1.0.0.If a transitional patch is wanted for people still on older Node,
"overrides": { "serialize-javascript": "^6.0.2" }does work — verified building successfully on Node 16.14.0 — at the cost of reintroducing both advisories.Separately, it is worth replacing
node-version: 'lts/*'in CI with an explicit matrix on the supported lines. That is the change that stops this recurring:lts/*silently tracks whatever is newest, which is exactly why a declared minimum of 18 went untested for nearly four months.Surfaced via adaptlearning/adapt_authoring#2847.
Posted via collaboration with Claude Code