chore: generate and publish npm-shrinkwrap.json to lock full dependency tree#563
chore: generate and publish npm-shrinkwrap.json to lock full dependency tree#563jonathannorris wants to merge 2 commits intomainfrom
Conversation
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
devcycle-mcp-server | 8b3a95f | Apr 03 2026, 09:11 PM |
There was a problem hiding this comment.
Pull request overview
Adds automated npm-shrinkwrap.json generation during publish to fully lock the transitive dependency tree shipped to npm consumers, and pins direct production dependencies to exact versions.
Changes:
- Add a
prepackstep to generatenpm-shrinkwrap.json(and adjustpostpackcleanup). - Pin production dependencies in
package.json(and updateyarn.lockaccordingly). - Add helper scripts for shrinkwrap generation and (future) verification; ignore generated artifacts in
.gitignore.
Reviewed changes
Copilot reviewed 3 out of 5 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| yarn.lock | Updates lock entries to reflect newly pinned dependency versions/ranges. |
| scripts/verify-shrinkwrap.js | New verifier/cleaner for shrinkwrap contents vs installed node_modules (not wired yet). |
| scripts/generate-shrinkwrap.js | New generator invoked from prepack to create shrinkwrap while protecting yarn.lock. |
| package.json | Adds shrinkwrap generation to prepack, expands postpack cleanup, pins dependencies, adds shrinkwrap script. |
| .gitignore | Ignores generated shrinkwrap/lock artifacts and yarn.lock backup file. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } catch (err) { | ||
| console.error('Error during shrinkwrap generation:', err.message) | ||
| process.exit(1) | ||
| } finally { | ||
| // Always restore yarn.lock. npm operations rewrite it to Yarn v1 format, | ||
| // which corrupts the Yarn Berry lockfile used for development. | ||
| restoreYarnLock() |
There was a problem hiding this comment.
process.exit(1) inside the catch prevents the finally block from running, so yarn.lock may remain in the corrupted Yarn v1 format if npm install/npm shrinkwrap fails. Instead, capture the error, restore yarn.lock in finally, then exit (e.g., set process.exitCode = 1 and return, or rethrow after finally).
| execSync('npm install --package-lock-only --ignore-scripts', { | ||
| cwd: ROOT, | ||
| stdio: 'inherit', | ||
| }) | ||
|
|
||
| console.log('Converting package-lock.json to npm-shrinkwrap.json...') | ||
| execSync('npm shrinkwrap', { |
There was a problem hiding this comment.
This script claims to generate shrinkwrap for production deps only (and verify-shrinkwrap.js assumes --omit=dev), but the npm commands here don't omit dev dependencies. That will include dev packages in package-lock.json/npm-shrinkwrap.json and can publish a larger-than-intended dependency tree. Add --omit=dev (and ensure it applies to both the lock generation and shrinkwrap conversion).
| execSync('npm install --package-lock-only --ignore-scripts', { | |
| cwd: ROOT, | |
| stdio: 'inherit', | |
| }) | |
| console.log('Converting package-lock.json to npm-shrinkwrap.json...') | |
| execSync('npm shrinkwrap', { | |
| execSync('npm install --package-lock-only --ignore-scripts --omit=dev', { | |
| cwd: ROOT, | |
| stdio: 'inherit', | |
| }) | |
| console.log('Converting package-lock.json to npm-shrinkwrap.json...') | |
| execSync('npm shrinkwrap --omit=dev', { |
scripts/verify-shrinkwrap.js
Outdated
| // Skip @types/* packages: pure TypeScript type definitions, stripped at | ||
| // compile time and absent from the distributed dist/ output. Version | ||
| // differences have no runtime or security impact. | ||
| if (pkgName.startsWith('@types/')) continue | ||
|
|
There was a problem hiding this comment.
The script skips @types/* packages while asserting they have “no runtime or security impact”. Even though they’re type-only at runtime, they are still installed by consumers and could theoretically contain install scripts or malicious payloads. Consider including @types/* in verification, or at minimum adjust the comment/output so it doesn’t claim there is no security impact.
Summary
npm-shrinkwrap.jsongeneration to the publish pipeline via a newprepackstep, locking the full transitive dependency tree to the exact versions resolved at build time^and~ranges) as defense-in-depthscripts/generate-shrinkwrap.jshandles the full flow: generates the lockfile vianpm install --package-lock-only, converts it to shrinkwrap, strips workspace entries (mcp-worker), and protectsyarn.lockfrom npm's Berry-incompatible rewrite via a save/restore in afinallyblockMotivation
package.jsonalready listed/npm-shrinkwrap.jsonin thefilesarray but the file was never generated, so consumers runningnpm install -g @devcycle/cliresolved all caret-ranged transitive deps to whatever was latest at install time. This is the same class of exposure that let the maliciousaxios@1.14.1reach users on March 31. Pinning axios directly in6.3.0only covers one package; the shrinkwrap covers the full tree.