From 547255b0d474b522155562b80cbb22e8e339fc7d Mon Sep 17 00:00:00 2001 From: Vault Automation Date: Tue, 7 Apr 2026 16:30:08 -0600 Subject: [PATCH] adds script to generate report for dependency overrides (#13179) (#13731) Co-authored-by: Jordan Reimer --- ui/DEP_OVERRIDE_REPORT.md | 271 ++++++++++++++++++++++++++ ui/scripts/gen-dep-override-report.js | 198 +++++++++++++++++++ 2 files changed, 469 insertions(+) create mode 100644 ui/DEP_OVERRIDE_REPORT.md create mode 100644 ui/scripts/gen-dep-override-report.js diff --git a/ui/DEP_OVERRIDE_REPORT.md b/ui/DEP_OVERRIDE_REPORT.md new file mode 100644 index 0000000000..4506b50abd --- /dev/null +++ b/ui/DEP_OVERRIDE_REPORT.md @@ -0,0 +1,271 @@ +# ๐Ÿ›ก๏ธ PNPM Override Audit Report + +Generated on: 2026-03-19, 10:45:45 a.m. + +## `@babel/runtime` +**Target Override:** `7.27.0` + +โš ๏ธ **REQUIRED** + +> These packages will continue to receive the overridden version until they are updated to naturally resolve to >= 7.27.0. + +| Parent Package | Naturally Resolved Version | +| :--- | :--- | +| `ember-cli-babel@7.26.11` | `7.12.18` | +| `ember-cli-babel@8.2.0` | `7.12.18` | + +--- +## `@embroider/macros` +**Target Override:** `1.15.0` + +โœ… **SAFE TO REMOVE** + +> All packages naturally resolve to >= 1.15.0 without the override. + +--- +## `@messageformat/runtime` +**Target Override:** `3.0.2` + +โœ… **SAFE TO REMOVE** + +> All packages naturally resolve to >= 3.0.2 without the override. + +--- +## `ajv@6.12.6` +**Target Override:** `6.14.0` + +โœ… **SAFE TO REMOVE** + +> All packages naturally resolve to >= 6.14.0 without the override. + +--- +## `ajv@8.17.1` +**Target Override:** `8.18.0` + +โœ… **SAFE TO REMOVE** + +> All packages naturally resolve to >= 8.18.0 without the override. + +--- +## `ansi-html` +**Target Override:** `0.0.8` + +โš ๏ธ **REQUIRED** + +> These packages will continue to receive the overridden version until they are updated to naturally resolve to >= 0.0.8. + +| Parent Package | Naturally Resolved Version | +| :--- | :--- | +| `broccoli-middleware@2.1.1` | `0.0.7` | +| `broccoli@3.5.2` | `0.0.7` | + +--- +## `async` +**Target Override:** `2.6.4` + +โš ๏ธ **REQUIRED** + +> These packages will continue to receive the overridden version until they are updated to naturally resolve to >= 2.6.4. + +| Parent Package | Naturally Resolved Version | +| :--- | :--- | +| `fireworm@0.7.2` | `0.2.10` | + +--- +## `braces` +**Target Override:** `3.0.3` + +โš ๏ธ **REQUIRED** + +> These packages will continue to receive the overridden version until they are updated to naturally resolve to >= 3.0.3. + +| Parent Package | Naturally Resolved Version | +| :--- | :--- | +| `micromatch@3.1.10` | `2.3.2` | + +--- +## `eslint-utils` +**Target Override:** `1.4.3` + +โœ… **SAFE TO REMOVE** + +> All packages naturally resolve to >= 1.4.3 without the override. + +--- +## `express` +**Target Override:** `4.22.1` + +โœ… **SAFE TO REMOVE** + +> All packages naturally resolve to >= 4.22.1 without the override. + +--- +## `https-proxy-agent` +**Target Override:** `2.2.4` + +โœ… **SAFE TO REMOVE** + +> All packages naturally resolve to >= 2.2.4 without the override. + +--- +## `ini` +**Target Override:** `1.3.8` + +โœ… **SAFE TO REMOVE** + +> All packages naturally resolve to >= 1.3.8 without the override. + +--- +## `json5` +**Target Override:** `1.0.2` + +โ“ **UNKNOWN (Error)** + +> The script encountered an error resolving this package: +> `Command failed: pnpm list "json5" --recursive --depth Infinity --json` + +--- +## `kind-of` +**Target Override:** `6.0.3` + +โš ๏ธ **REQUIRED** + +> These packages will continue to receive the overridden version until they are updated to naturally resolve to >= 6.0.3. + +| Parent Package | Naturally Resolved Version | +| :--- | :--- | +| `has-values@1.0.0` | `4.0.0` | +| `is-number@3.0.0` | `3.2.2` | +| `object-copy@0.1.0` | `3.2.2` | +| `snapdragon-util@3.0.1` | `3.2.2` | +| `to-object-path@0.3.0` | `3.2.2` | + +--- +## `markdown-it` +**Target Override:** `14.1.1` + +โš ๏ธ **REQUIRED** + +> These packages will continue to receive the overridden version until they are updated to naturally resolve to >= 14.1.1. + +| Parent Package | Naturally Resolved Version | +| :--- | :--- | +| `ember-cli@5.8.1` | `13.0.2` | +| `markdown-it-terminal@0.4.0` | `13.0.2` | + +--- +## `micromatch` +**Target Override:** `4.0.8` + +โš ๏ธ **REQUIRED** + +> These packages will continue to receive the overridden version until they are updated to naturally resolve to >= 4.0.8. + +| Parent Package | Naturally Resolved Version | +| :--- | :--- | +| `anymatch@2.0.0` | `3.1.10` | +| `findup-sync@2.0.0` | `3.1.10` | +| `sane@4.1.0` | `3.1.10` | + +--- +## `minimatch@<3.1.3` +**Target Override:** `3.1.5` + +โœ… **SAFE TO REMOVE** + +> All packages naturally resolve to >= 3.1.5 without the override. + +--- +## `minimatch@>=5.0.0 <5.1.7` +**Target Override:** `5.1.9` + +โœ… **SAFE TO REMOVE** + +> All packages naturally resolve to >= 5.1.9 without the override. + +--- +## `minimatch@>=7.0.0 <7.4.7` +**Target Override:** `7.4.9` + +โœ… **SAFE TO REMOVE** + +> All packages naturally resolve to >= 7.4.9 without the override. + +--- +## `minimatch@>=8.0.0 <8.0.5` +**Target Override:** `8.0.7` + +โœ… **SAFE TO REMOVE** + +> All packages naturally resolve to >= 8.0.7 without the override. + +--- +## `minimatch@>=9.0.0 <9.0.6` +**Target Override:** `9.0.9` + +โœ… **SAFE TO REMOVE** + +> All packages naturally resolve to >= 9.0.9 without the override. + +--- +## `prismjs` +**Target Override:** `1.30.0` + +โœ… **SAFE TO REMOVE** + +> All packages naturally resolve to >= 1.30.0 without the override. + +--- +## `qs` +**Target Override:** `6.14.1` + +โš ๏ธ **REQUIRED** + +> These packages will continue to receive the overridden version until they are updated to naturally resolve to >= 6.14.1. + +| Parent Package | Naturally Resolved Version | +| :--- | :--- | +| `body-parser@1.20.3` | `6.13.0` | + +--- +## `rollup` +**Target Override:** `2.80.0` + +โš ๏ธ **REQUIRED** + +> These packages will continue to receive the overridden version until they are updated to naturally resolve to >= 2.80.0. + +| Parent Package | Naturally Resolved Version | +| :--- | :--- | +| `@rollup/plugin-replace@2.3.0` | `1.32.1` | +| `broccoli-rollup@4.0.0` | `1.32.1` | + +--- +## `serialize-javascript` +**Target Override:** `3.1.0` + +โœ… **SAFE TO REMOVE** + +> All packages naturally resolve to >= 3.1.0 without the override. + +--- +## `socket.io` +**Target Override:** `4.8.1` + +โœ… **SAFE TO REMOVE** + +> All packages naturally resolve to >= 4.8.1 without the override. + +--- +## `underscore` +**Target Override:** `1.13.7` + +โš ๏ธ **REQUIRED** + +> These packages will continue to receive the overridden version until they are updated to naturally resolve to >= 1.13.7. + +| Parent Package | Naturally Resolved Version | +| :--- | :--- | +| `nomnom@1.8.1` | `1.6.0` | + +--- diff --git a/ui/scripts/gen-dep-override-report.js b/ui/scripts/gen-dep-override-report.js new file mode 100644 index 0000000000..bd09593509 --- /dev/null +++ b/ui/scripts/gen-dep-override-report.js @@ -0,0 +1,198 @@ +/** + * Copyright IBM Corp. 2016, 2025 + * SPDX-License-Identifier: BUSL-1.1 + */ + +/* eslint-env node */ +/* eslint-disable no-console */ + +const fs = require('fs'); +const path = require('path'); +const { execSync } = require('child_process'); + +const ROOT_DIR = path.join(__dirname, '..'); +const PKG_PATH = path.join(ROOT_DIR, 'package.json'); +const LOCK_PATH = path.join(ROOT_DIR, 'pnpm-lock.yaml'); + +/** + * Simple exact-version comparison. + * Returns true if the resolved version is older than the target override. + */ +function isOlder(found, target) { + if (!found || !target) return false; + const f = found + .replace(/[^0-9.]/g, '') + .split('.') + .map((n) => parseInt(n || 0)); + const t = target + .replace(/[^0-9.]/g, '') + .split('.') + .map((n) => parseInt(n || 0)); + for (let i = 0; i < Math.max(f.length, t.length); i++) { + if ((f[i] || 0) < (t[i] || 0)) return true; + if ((f[i] || 0) > (t[i] || 0)) return false; + } + return false; +} + +function writeReport(report, error) { + const outPath = path.join(ROOT_DIR, 'DEP_OVERRIDE_REPORT.md'); + fs.writeFileSync(outPath, report); + const message = + error || 'โœ… Dependency override audit complete! Project has been restored to its original state.'; + console.log(message); + console.log('๐Ÿ“„ See DEP_OVERRIDE_REPORT.md for details.'); +} + +function genOverrideReport() { + console.log('๐Ÿš€ Starting Dependency Override Audit. Backing up package.json...'); + + let report = '# ๐Ÿ›ก๏ธ PNPM Override Audit Report\n\n'; + report += `Generated on: ${new Date().toLocaleString()}\n\n`; + + if (!fs.existsSync(PKG_PATH)) { + report += `โŒ **Error:** package.json not found at ${PKG_PATH}\n`; + writeReport(report, 'โŒ package.json not found.'); + return; + } + + // 1. Read and backup the original state + const originalPkgStr = fs.readFileSync(PKG_PATH, 'utf8'); + const originalLockStr = fs.existsSync(LOCK_PATH) ? fs.readFileSync(LOCK_PATH, 'utf8') : null; + + const pkgJson = JSON.parse(originalPkgStr); + const overrides = pkgJson.pnpm?.overrides || {}; + + if (Object.keys(overrides).length === 0) { + report += 'โœ… **No overrides found in package.json.**\n'; + writeReport(report); + return; + } + + try { + // 2. Strip overrides and save the modified package.json + const tempPkgJson = JSON.parse(originalPkgStr); + delete tempPkgJson.pnpm.overrides; + fs.writeFileSync(PKG_PATH, JSON.stringify(tempPkgJson, null, 2)); + + // 3. Reinstall dependencies to recalculate lockfile AND physically update node_modules + console.log('โณ Relinking node_modules to their natural state (this may take a minute)...'); + execSync('pnpm install --no-frozen-lockfile --ignore-scripts', { + cwd: ROOT_DIR, + stdio: 'ignore', + }); + + // 4. Audit each removed override using pnpm list + for (const [overrideName, targetVersion] of Object.entries(overrides)) { + console.log(`๐Ÿ”Ž Auditing natural resolution for ${overrideName}...`); + const culprits = new Map(); + + let rawJson; + try { + rawJson = execSync(`pnpm list "${overrideName}" --recursive --depth Infinity --json`, { + cwd: ROOT_DIR, + maxBuffer: 1024 * 1024 * 100, + }).toString(); + } catch (e) { + console.error(`โ””โ”€โ”€โš ๏ธ Could not fetch tree for ${overrideName}.`); + // execSync attaches stdout and stderr to the error object when a command fails + const stdout = e.stdout ? e.stdout.toString().trim() : ''; + const stderr = e.stderr ? e.stderr.toString().trim() : ''; + + report += `## \`${overrideName}\`\n**Target Override:** \`${targetVersion}\`\n\n`; + + // If pnpm exited with 1 but output an empty JSON array, it means "Not Found" + if (stdout === '[]') { + report += `โœ… **SAFE TO REMOVE (Orphaned)**\n\n`; + report += `> This package does not exist anywhere in the naturally resolved dependency tree. It was likely removed by an upstream dependency update.\n`; + } else if (e.code === 'ENOBUFS') { + report += `โ“ **UNKNOWN (Buffer Overflow)**\n\n`; + report += `> The dependency tree is too large for the allocated memory.\n`; + } else { + const errorMsg = stderr || e.message; + report += `โ“ **UNKNOWN (Error)**\n\n`; + report += `> The script encountered an error resolving this package:\n> \`${errorMsg}\`\n`; + } + + report += `\n---\n`; + continue; // Immediately jump to the next override in the loop + } + + const data = JSON.parse(rawJson); + + const scanTree = (parentName, parentVersion, depsObject) => { + if (!depsObject) return; + + for (const [depName, depInfo] of Object.entries(depsObject)) { + if (!depInfo) continue; + + if (depName === overrideName && depInfo.version) { + const resolvedVersion = depInfo.version; + + if (isOlder(resolvedVersion, targetVersion)) { + culprits.set(`${parentName}@${parentVersion}`, resolvedVersion); + } + } + + // If this dependency has its own dependencies, it becomes the new parent + if (depInfo.dependencies) { + scanTree(depName, depInfo.version, depInfo.dependencies); + } + } + }; + + // Start the scan from the top-level workspaces/projects + data.forEach((project) => { + const projectName = project.name || 'Root Project'; + const projectVersion = project.version || 'unknown'; + const allDeps = { + ...project.dependencies, + ...project.devDependencies, + ...project.optionalDependencies, + }; + + scanTree(projectName, projectVersion, allDeps); + }); + + // Generate markdown segment + report += `## \`${overrideName}\`\n**Target Override:** \`${targetVersion}\`\n\n`; + if (culprits.size > 0) { + report += `โš ๏ธ **REQUIRED**\n\n`; + report += `> These packages will continue to receive the overridden version until they are updated to naturally resolve to >= ${targetVersion}.\n\n`; + report += `| Parent Package | Naturally Resolved Version |\n| :--- | :--- |\n`; + const sortedCulprits = Array.from(culprits.entries()).sort(); + for (const [parent, resolved] of sortedCulprits) { + report += `| \`${parent}\` | \`${resolved}\` |\n`; + } + } else { + report += `โœ… **SAFE TO REMOVE**\n\n`; + report += `> All packages naturally resolve to >= ${targetVersion} without the override.\n`; + } + report += `\n---\n`; + } + } finally { + // 5. Restore original package.json, lockfile, and node_modules + console.log('๐Ÿงน Cleaning up: Restoring package.json, lockfile, and node_modules...'); + + // Put the files back + fs.writeFileSync(PKG_PATH, originalPkgStr); + if (originalLockStr) { + fs.writeFileSync(LOCK_PATH, originalLockStr); + } + + // Run a full install again to force pnpm to re-apply overrides to node_modules + try { + execSync('pnpm install --no-frozen-lockfile --ignore-scripts', { + cwd: ROOT_DIR, + stdio: 'ignore', + }); + } catch (e) { + console.error("โš ๏ธ Cleanup failed, you may need to run 'pnpm install' manually."); + } + + // Write the report + writeReport(report); + } +} + +genOverrideReport();