Expected Behavior
Installing @ui5/cli@4.0.65 produces a package-lock.json that is internally consistent, so that a subsequent npm ci succeeds (as it does with @ui5/cli@4.0.64).
Specifically, @ui5/cli's transitive dependency express@4.22.2 declares qs: ~6.15.1. The lockfile generated by npm install should contain a qs node that satisfies this range and is placed where npm ci expects it.
Current Behavior
With @ui5/cli@4.0.65, npm install writes a package-lock.json in which the nested qs for @ui5/cli's bundled express is missing. The tree hoists qs@6.16.0 to the top level, which does not satisfy express's ~6.15.1 range, and no nested qs@6.15.3 node is written under node_modules/@ui5/cli/node_modules/express.
As a result, npm ci fails the sync check with:
Missing: qs@6.15.3 from lock file
The npm resolver confirms it wants a qs@6.15.3 node it cannot find in the lock:
silly placeDep node_modules/@ui5/cli/node_modules/express qs@6.15.3 OK for: express@4.22.2 want: ~6.15.1
verbose stack Missing: qs@6.15.3 from lock file
@ui5/cli@4.0.64 does not exhibit this — its lockfile contains the required node_modules/@ui5/cli/node_modules/express/node_modules/qs@6.15.3 entry, and npm ci succeeds. The direct dependency ranges declared by @ui5/cli are identical between 4.0.64 and 4.0.65 (same @ui5/server: ^4.0.16, etc.), so the regression is in the resolved/generated lock tree, not in a manifest change.
This is especially impactful in CI pipelines that run npm ci after installing dependencies (e.g. SAP Piper npmExecuteScripts with install: true), where the build fails hard.
Steps to Reproduce the Issue
Minimal, fully isolated reproduction (single dependency, no .npmrc, no other packages):
- Create a project with only
@ui5/cli@4.0.65:
{
"name": "repro",
"version": "1.0.0",
"devDependencies": {
"@ui5/cli": "4.0.65"
}
}
- Generate the lockfile:
npm install --package-lock-only
- Run a clean install:
→ fails with
Missing: qs@6.15.3 from lock file.
- Repeat steps 1–3 with
"@ui5/cli": "4.0.64" → npm ci succeeds.
Observed lock difference (@ui5/cli bundled express subtree):
| Lock entry |
4.0.64 |
4.0.65 |
node_modules/@ui5/cli/node_modules/express |
4.22.2 (declares qs: ~6.15.1) |
4.22.2 (declares qs: ~6.15.1) |
node_modules/@ui5/cli/node_modules/express/node_modules/qs |
6.15.3 present ✓ |
missing ✗ |
node_modules/@ui5/cli/node_modules/qs |
6.16.0 |
6.16.0 |
node_modules/qs (hoisted) |
6.16.0 |
6.16.0 |
Context
- UI5 Module Version (output of
ui5 --version when using the CLI): 4.0.65 (broken); 4.0.64 (works)
- Node.js Version:
v24.21.0
- npm Version:
11.19.0
- OS/Platform:
macOS 26.6.2 (Darwin 25.6.0, arm64)
- Browser (if relevant):
unknown
- Other information regarding your environment (optional): Also reproduced in CI using the
node:24-bookworm Docker image (SAP Piper npmExecuteScripts step with install: true, which runs npm ci).
Log Output / Stack Trace
running command: npm ci
npm error code EUSAGE
npm error
npm error `npm ci` can only install packages when your package.json and package-lock.json or npm-shrinkwrap.json are in sync. Please update your lock file with `npm install` before continuing.
npm error
npm error Missing: qs@6.15.3 from lock file
npm error
npm error Clean install a project
npm error
npm error npm ci
npm resolver (verbose):
silly placeDep node_modules/@ui5/cli/node_modules/express qs@6.15.3 OK for: express@4.22.2 want: ~6.15.1
verbose stack Missing: qs@6.15.3 from lock file
error Missing: qs@6.15.3 from lock file
Expected Behavior
Installing
@ui5/cli@4.0.65produces apackage-lock.jsonthat is internally consistent, so that a subsequentnpm cisucceeds (as it does with@ui5/cli@4.0.64).Specifically,
@ui5/cli's transitive dependencyexpress@4.22.2declaresqs: ~6.15.1. The lockfile generated bynpm installshould contain aqsnode that satisfies this range and is placed wherenpm ciexpects it.Current Behavior
With
@ui5/cli@4.0.65,npm installwrites apackage-lock.jsonin which the nestedqsfor@ui5/cli's bundledexpressis missing. The tree hoistsqs@6.16.0to the top level, which does not satisfyexpress's~6.15.1range, and no nestedqs@6.15.3node is written undernode_modules/@ui5/cli/node_modules/express.As a result,
npm cifails the sync check with:The npm resolver confirms it wants a
qs@6.15.3node it cannot find in the lock:@ui5/cli@4.0.64does not exhibit this — its lockfile contains the requirednode_modules/@ui5/cli/node_modules/express/node_modules/qs@6.15.3entry, andnpm cisucceeds. The direct dependency ranges declared by@ui5/cliare identical between 4.0.64 and 4.0.65 (same@ui5/server: ^4.0.16, etc.), so the regression is in the resolved/generated lock tree, not in a manifest change.This is especially impactful in CI pipelines that run
npm ciafter installing dependencies (e.g. SAP PipernpmExecuteScriptswithinstall: true), where the build fails hard.Steps to Reproduce the Issue
Minimal, fully isolated reproduction (single dependency, no
.npmrc, no other packages):@ui5/cli@4.0.65:{ "name": "repro", "version": "1.0.0", "devDependencies": { "@ui5/cli": "4.0.65" } }Missing: qs@6.15.3 from lock file."@ui5/cli": "4.0.64"→npm cisucceeds.Observed lock difference (
@ui5/clibundled express subtree):node_modules/@ui5/cli/node_modules/express4.22.2(declaresqs: ~6.15.1)4.22.2(declaresqs: ~6.15.1)node_modules/@ui5/cli/node_modules/express/node_modules/qs6.15.3present ✓node_modules/@ui5/cli/node_modules/qs6.16.06.16.0node_modules/qs(hoisted)6.16.06.16.0Context
ui5 --versionwhen using the CLI):4.0.65(broken);4.0.64(works)v24.21.011.19.0macOS 26.6.2 (Darwin 25.6.0, arm64)unknownnode:24-bookwormDocker image (SAP PipernpmExecuteScriptsstep withinstall: true, which runsnpm ci).Log Output / Stack Trace