-
Notifications
You must be signed in to change notification settings - Fork 5
chore: generate and publish npm-shrinkwrap.json to lock full dependency tree #563
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
f369ed1
ec29121
87169eb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,122 @@ | ||
| #!/usr/bin/env node | ||
| /** | ||
| * generate-shrinkwrap.js | ||
| * | ||
| * Generates npm-shrinkwrap.json while preserving yarn.lock intact. | ||
| * | ||
| * Why the yarn.lock protection is needed: | ||
| * `npm install --package-lock-only` rewrites yarn.lock with a Yarn v1 | ||
| * format file, corrupting the Yarn Berry lockfile that the project uses | ||
| * for development. We save and restore it around the npm operation. | ||
| */ | ||
|
|
||
| 'use strict' | ||
|
|
||
| const { execSync } = require('child_process') | ||
| const fs = require('fs') | ||
| const path = require('path') | ||
|
|
||
| const ROOT = path.resolve(__dirname, '..') | ||
| const YARN_LOCK = path.join(ROOT, 'yarn.lock') | ||
| const YARN_LOCK_BAK = path.join(ROOT, 'yarn.lock.shrinkwrap-bak') | ||
| const SHRINKWRAP_PATH = path.join(ROOT, 'npm-shrinkwrap.json') | ||
|
|
||
| // --------------------------------------------------------------------------- | ||
| // Save yarn.lock before npm touches it | ||
| // --------------------------------------------------------------------------- | ||
|
|
||
| if (!fs.existsSync(YARN_LOCK)) { | ||
| console.error( | ||
| 'Error: yarn.lock not found. Cannot protect it from npm overwrite.', | ||
| ) | ||
| process.exit(1) | ||
| } | ||
|
Comment on lines
+28
to
+33
|
||
|
|
||
| const yarnLockContent = fs.readFileSync(YARN_LOCK) | ||
|
|
||
| function restoreYarnLock() { | ||
| fs.writeFileSync(YARN_LOCK, yarnLockContent) | ||
| if (fs.existsSync(YARN_LOCK_BAK)) fs.unlinkSync(YARN_LOCK_BAK) | ||
| } | ||
|
|
||
| // Write a physical backup too, so a crash mid-run doesn't lose the file. | ||
| fs.writeFileSync(YARN_LOCK_BAK, yarnLockContent) | ||
|
|
||
| // --------------------------------------------------------------------------- | ||
| // Run npm operations, always restoring yarn.lock afterwards | ||
| // --------------------------------------------------------------------------- | ||
|
|
||
| try { | ||
| console.log('Generating package-lock.json from installed node_modules...') | ||
| 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 --ignore-scripts', { | ||
| cwd: ROOT, | ||
| stdio: 'inherit', | ||
| }) | ||
| } catch (err) { | ||
| console.error('Error during shrinkwrap generation:', err.message) | ||
| // Use exitCode instead of process.exit() so the finally block runs first. | ||
| process.exitCode = 1 | ||
| } finally { | ||
| // Always restore yarn.lock. npm operations rewrite it to Yarn v1 format, | ||
| // which corrupts the Yarn Berry lockfile used for development. | ||
| restoreYarnLock() | ||
jonathannorris marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| if (process.exitCode) process.exit() | ||
|
|
||
| // --------------------------------------------------------------------------- | ||
| // Strip workspace entries from npm-shrinkwrap.json | ||
| // | ||
| // Workspace entries arise because npm reads the `workspaces` field from | ||
| // package.json. They take two forms: | ||
| // - Keys without a "node_modules/" prefix (e.g. "mcp-worker") | ||
| // - Symlink entries with { "link": true } (e.g. "node_modules/@devcycle/mcp-worker") | ||
| // | ||
| // These entries reference a local directory that does not exist when a | ||
| // consumer installs the published package, so they must be removed. | ||
| // --------------------------------------------------------------------------- | ||
|
|
||
| const shrinkwrap = JSON.parse(fs.readFileSync(SHRINKWRAP_PATH, 'utf8')) | ||
| const allPackages = shrinkwrap.packages || {} | ||
|
|
||
| let strippedCount = 0 | ||
| const cleanedPackages = {} | ||
|
|
||
| for (const [key, value] of Object.entries(allPackages)) { | ||
| // Always keep the root entry (empty string key) | ||
| if (key === '') { | ||
| cleanedPackages[key] = value | ||
| continue | ||
| } | ||
|
|
||
| // Drop workspace root entries (not under node_modules/) | ||
| if (!key.startsWith('node_modules/')) { | ||
| strippedCount++ | ||
| continue | ||
| } | ||
|
|
||
| // Drop workspace symlink entries | ||
| if (value.link === true) { | ||
| strippedCount++ | ||
| continue | ||
| } | ||
|
|
||
| cleanedPackages[key] = value | ||
| } | ||
|
|
||
| if (strippedCount > 0) { | ||
| console.log( | ||
| `Stripped ${strippedCount} workspace entries from npm-shrinkwrap.json.`, | ||
| ) | ||
| shrinkwrap.packages = cleanedPackages | ||
| fs.writeFileSync( | ||
| SHRINKWRAP_PATH, | ||
| JSON.stringify(shrinkwrap, null, 2) + '\n', | ||
| ) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,21 +1,21 @@ | ||
| { | ||
| "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", | ||
| "name": "com.devcycle/mcp", | ||
| "description": "DevCycle MCP server for feature flag management", | ||
| "version": "6.3.0", | ||
| "repository": { | ||
| "url": "https://github.com/DevCycleHQ/cli", | ||
| "source": "github" | ||
| }, | ||
| "websiteUrl": "https://docs.devcycle.com/cli-mcp/mcp-getting-started", | ||
| "remotes": [ | ||
| { | ||
| "type": "streamable-http", | ||
| "url": "https://mcp.devcycle.com/mcp" | ||
| "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", | ||
jonathannorris marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| "name": "com.devcycle/mcp", | ||
| "description": "DevCycle MCP server for feature flag management", | ||
| "version": "6.3.0", | ||
| "repository": { | ||
| "url": "https://github.com/DevCycleHQ/cli", | ||
| "source": "github" | ||
| }, | ||
| { | ||
| "type": "sse", | ||
| "url": "https://mcp.devcycle.com/sse" | ||
| } | ||
| ] | ||
| "websiteUrl": "https://docs.devcycle.com/cli-mcp/mcp-getting-started", | ||
| "remotes": [ | ||
| { | ||
| "type": "streamable-http", | ||
| "url": "https://mcp.devcycle.com/mcp" | ||
| }, | ||
| { | ||
| "type": "sse", | ||
| "url": "https://mcp.devcycle.com/sse" | ||
| } | ||
| ] | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.