diff --git a/.gitignore b/.gitignore index 3c99c15bb..244e09b48 100644 --- a/.gitignore +++ b/.gitignore @@ -81,6 +81,9 @@ node_modules/ # Bundle files *.bundle +# Generated per-package patches dirs (populated by scripts/copy-patches.cjs at publish time) +packages/*/patches/ + # Playwright reports playwright-report diff --git a/.yarn/patches/@chainsafe-libp2p-yamux-npm-7.0.4-284c2f6812.patch b/.yarn/patches/@chainsafe-libp2p-yamux-npm-7.0.4-284c2f6812.patch deleted file mode 100644 index 5b7707857..000000000 --- a/.yarn/patches/@chainsafe-libp2p-yamux-npm-7.0.4-284c2f6812.patch +++ /dev/null @@ -1,41 +0,0 @@ -diff --git a/dist/src/decode.js b/dist/src/decode.js -index 58728fddc7355906bcb8d8273d41e73ecbbc9d67..7aa39de8fc9268d632be340d10f87a2522f18c45 100644 ---- a/dist/src/decode.js -+++ b/dist/src/decode.js -@@ -111,14 +111,14 @@ export class Decoder { - export function returnlessSource(source) { - if (source[Symbol.iterator] !== undefined) { - const iterator = source[Symbol.iterator](); -- iterator.return = undefined; -+ Object.defineProperty(iterator, 'return', {}); - return { - [Symbol.iterator]() { return iterator; } - }; - } - else if (source[Symbol.asyncIterator] !== undefined) { - const iterator = source[Symbol.asyncIterator](); -- iterator.return = undefined; -+ Object.defineProperty(iterator, 'return', {}); - return { - [Symbol.asyncIterator]() { return iterator; } - }; -diff --git a/src/decode.ts b/src/decode.ts -index 971ca58722443afa9b046058a3ef76e9fdbead44..3e92f65aa5e38ef1e3fef41955c5fee8151137a4 100644 ---- a/src/decode.ts -+++ b/src/decode.ts -@@ -128,13 +128,13 @@ export class Decoder { - export function returnlessSource (source: Source): Source { - if ((source as Iterable)[Symbol.iterator] !== undefined) { - const iterator = (source as Iterable)[Symbol.iterator]() -- iterator.return = undefined -+ Object.defineProperty(iterator, 'return', {}) - return { - [Symbol.iterator] () { return iterator } - } - } else if ((source as AsyncIterable)[Symbol.asyncIterator] !== undefined) { - const iterator = (source as AsyncIterable)[Symbol.asyncIterator]() -- iterator.return = undefined -+ Object.defineProperty(iterator, 'return', {}) - return { - [Symbol.asyncIterator] () { return iterator } - } diff --git a/README.md b/README.md index cedd53d65..914f27cce 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,30 @@ See [`packages/create-package/README.md`](packages/create-package/README.md). For information on creating releases, see the [MetaMask/core release documentation](https://github.com/MetaMask/core/blob/main/docs/contributing.md#releasing-changes). +### Patches + +Some third-party dependencies require patches for SES/lockdown compatibility. The root +`patches/` directory is the single source of truth for all patches, applied automatically +on `yarn install` via `patch-package`. + +Published packages that ship patches to consumers are called "sinks". Sinks are determined +by analyzing the dependency graph: a non-private package that directly depends on a patched +dependency is a sink if none of its transitive internal dependencies also depend on that +patched dependency. Only `dependencies` are considered for sink analysis (not +`peerDependencies` or `devDependencies`). + +Sink packages include `patches/` in their `files` field, declare `patch-package` as a +`peerDependency`, and have a `postinstall` script that runs `patch-package --patch-dir patches`. +The `scripts/copy-patches.cjs` script copies root patches into each sink at publish time, +and `yarn constraints` enforces the correct configuration. + +**Adding a patch:** Place the `.patch` file in the root `patches/` directory. Run +`yarn constraints --fix` to update sink packages, and verify with +`node scripts/copy-patches.cjs`. + +**Removing a patch:** Delete the `.patch` file from the root `patches/` directory and run +`yarn constraints --fix` to clean up sink packages. + ## References - [Glossary](./docs/glossary.md) diff --git a/package.json b/package.json index fd8f370ac..4f9110629 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "lint:eslint": "yarn eslint . --cache", "lint:fix": "yarn constraints --fix && yarn lint:eslint --fix && yarn lint:misc --write && yarn lint:dependencies:fix", "lint:misc": "prettier --no-error-on-unmatched-pattern '**/*.json' '**/*.md' '**/*.html' '**/*.yml' '!**/CHANGELOG.old.md' '!.yarnrc.yml' '!CLAUDE.md' '!merged-packages/**' --ignore-path .gitignore --log-level error", - "postinstall": "simple-git-hooks && yarn rebuild:native", + "postinstall": "patch-package && simple-git-hooks && yarn rebuild:native", "prepack": "./scripts/prepack.sh", "rebuild:native": "./scripts/rebuild-native.sh", "test": "vitest run", @@ -89,6 +89,7 @@ "globals": "^16.0.0", "lint-staged": "^15.5.0", "lodash": "^4.17.21", + "patch-package": "^8.0.0", "playwright": "^1.58.2", "prettier": "^3.5.3", "prettier-plugin-packagejson": "^2.5.10", @@ -126,7 +127,8 @@ "@metamask/kernel-cli>@metamask/kernel-node-runtime>@libp2p/webrtc>@ipshipyard/node-datachannel": false, "@metamask/kernel-cli>@metamask/kernel-node-runtime>@metamask/kernel-store>better-sqlite3": false, "@metamask/kernel-cli>@metamask/kernel-node-runtime>@metamask/streams": false, - "@metamask/kernel-cli>@metamask/kernel-shims>@libp2p/webrtc>@ipshipyard/node-datachannel": false + "@metamask/kernel-cli>@metamask/kernel-shims>@libp2p/webrtc>@ipshipyard/node-datachannel": false, + "@metamask/kernel-cli>@metamask/kernel-utils": false } }, "resolutions": { diff --git a/packages/brow-2-brow/package.json b/packages/brow-2-brow/package.json index ef5a38fac..76d85b0ae 100644 --- a/packages/brow-2-brow/package.json +++ b/packages/brow-2-brow/package.json @@ -30,7 +30,7 @@ }, "dependencies": { "@chainsafe/libp2p-noise": "^16.1.3", - "@chainsafe/libp2p-yamux": "patch:@chainsafe/libp2p-yamux@npm%3A7.0.4#~/.yarn/patches/@chainsafe-libp2p-yamux-npm-7.0.4-284c2f6812.patch", + "@chainsafe/libp2p-yamux": "7.0.4", "@libp2p/autonat": "2.0.38", "@libp2p/bootstrap": "11.0.47", "@libp2p/circuit-relay-v2": "3.2.24", diff --git a/packages/kernel-browser-runtime/package.json b/packages/kernel-browser-runtime/package.json index 8af3abab8..a686b5510 100644 --- a/packages/kernel-browser-runtime/package.json +++ b/packages/kernel-browser-runtime/package.json @@ -73,7 +73,7 @@ "@metamask/logger": "workspace:^", "@metamask/ocap-kernel": "workspace:^", "@metamask/rpc-errors": "^7.0.3", - "@metamask/snaps-utils": "^11.7.1", + "@metamask/snaps-utils": "^12.1.0", "@metamask/streams": "workspace:^", "@metamask/superstruct": "^3.2.1", "@metamask/utils": "^11.9.0", diff --git a/packages/kernel-ui/package.json b/packages/kernel-ui/package.json index 588fcc3f7..e82d1dd7c 100644 --- a/packages/kernel-ui/package.json +++ b/packages/kernel-ui/package.json @@ -60,7 +60,7 @@ "test:dev:quiet": "yarn test:dev --reporter @ocap/repo-tools/vitest-reporters/silent" }, "dependencies": { - "@metamask/design-system-react": "^0.6.0", + "@metamask/design-system-react": "^0.9.0", "@metamask/design-system-tailwind-preset": "^0.6.1", "@metamask/design-tokens": "^8.1.1", "@metamask/kernel-browser-runtime": "workspace:^", diff --git a/packages/kernel-utils/README.md b/packages/kernel-utils/README.md index a7d134efc..7bc7e8cf2 100644 --- a/packages/kernel-utils/README.md +++ b/packages/kernel-utils/README.md @@ -10,6 +10,22 @@ or `npm install @metamask/kernel-utils` +## SES/Lockdown Compatibility + +This package is designed to run under [SES](https://github.com/endojs/endo/tree/master/packages/ses) (Secure ECMAScript lockdown). Some of its dependencies require patches to work in a locked-down environment. The required patch files are included in the `patches/` directory of this package and are applied automatically via the `postinstall` script using [`patch-package`](https://github.com/ds300/patch-package). + +Add `patch-package` as a development dependency of your project: + +```sh +yarn add --dev patch-package +``` + +or + +```sh +npm install --save-dev patch-package +``` + ## Contributing This package is part of a monorepo. Instructions for contributing can be found in the [monorepo README](https://github.com/MetaMask/ocap-kernel#readme). diff --git a/packages/kernel-utils/package.json b/packages/kernel-utils/package.json index 1e13e4e04..03c0d3eae 100644 --- a/packages/kernel-utils/package.json +++ b/packages/kernel-utils/package.json @@ -75,7 +75,8 @@ "module": "./dist/index.mjs", "types": "./dist/index.d.cts", "files": [ - "dist/" + "dist/", + "patches/" ], "scripts": { "build": "ts-bridge --project tsconfig.build.json --no-references --clean", @@ -88,6 +89,7 @@ "lint:eslint": "eslint . --cache", "lint:fix": "yarn lint:eslint --fix && yarn lint:misc --write && yarn constraints --fix && yarn lint:dependencies", "lint:misc": "prettier --no-error-on-unmatched-pattern '**/*.json' '**/*.md' '**/*.html' '!**/CHANGELOG.old.md' '**/*.yml' '!.yarnrc.yml' '!merged-packages/**' --ignore-path ../../.gitignore --log-level error", + "postinstall": "[ ! -d patches ] || patch-package --patch-dir patches", "publish:preview": "yarn npm publish --tag preview", "test": "vitest run --config vitest.config.ts", "test:clean": "yarn test --no-cache --coverage.clean", @@ -98,7 +100,7 @@ }, "dependencies": { "@chainsafe/libp2p-noise": "^16.1.3", - "@chainsafe/libp2p-yamux": "patch:@chainsafe/libp2p-yamux@npm%3A7.0.4#~/.yarn/patches/@chainsafe-libp2p-yamux-npm-7.0.4-284c2f6812.patch", + "@chainsafe/libp2p-yamux": "7.0.4", "@endo/captp": "^4.4.8", "@endo/errors": "^1.2.13", "@endo/exo": "^1.5.12", @@ -153,6 +155,7 @@ }, "peerDependencies": { "acorn": "^8.15.0", + "patch-package": "^8.0.0", "vite": "^7.3.0" }, "peerDependenciesMeta": { diff --git a/packages/ocap-kernel/README.md b/packages/ocap-kernel/README.md index 473a5e2ea..2a3b52e5d 100644 --- a/packages/ocap-kernel/README.md +++ b/packages/ocap-kernel/README.md @@ -10,6 +10,34 @@ or `npm install @metamask/ocap-kernel` +## SES/Lockdown Compatibility + +This package is designed to run under [SES](https://github.com/endojs/endo/tree/master/packages/ses) (Secure ECMAScript lockdown). One of its dependencies, `@chainsafe/libp2p-yamux`, requires a patch to work in a locked-down environment. The required patches are listed in the `patchedDependencies` field of this package's `package.json`, and the patch files are included in the `patches/` directory of this package. + +Apply them using [`patch-package`](https://github.com/ds300/patch-package): + +1. Install `patch-package`: + + ```sh + npm install --save-dev patch-package + ``` + +2. Copy the patch file(s) to your project's `patches/` directory: + + ```sh + cp node_modules/@metamask/ocap-kernel/patches/* patches/ + ``` + +3. Add a `postinstall` script to your `package.json`: + + ```json + "scripts": { + "postinstall": "patch-package" + } + ``` + +4. Run `npm install` (or your package manager's equivalent) to apply the patches. + ## Contributing This package is part of a monorepo. Instructions for contributing can be found in the [monorepo README](https://github.com/MetaMask/ocap-kernel#readme). diff --git a/packages/ocap-kernel/package.json b/packages/ocap-kernel/package.json index 7694bc7c5..f268891ef 100644 --- a/packages/ocap-kernel/package.json +++ b/packages/ocap-kernel/package.json @@ -70,7 +70,7 @@ "dependencies": { "@agoric/swingset-liveslots": "0.10.3-u21.0.1", "@chainsafe/libp2p-noise": "^16.1.3", - "@chainsafe/libp2p-yamux": "patch:@chainsafe/libp2p-yamux@npm%3A7.0.4#~/.yarn/patches/@chainsafe-libp2p-yamux-npm-7.0.4-284c2f6812.patch", + "@chainsafe/libp2p-yamux": "7.0.4", "@endo/errors": "^1.2.13", "@endo/eventual-send": "^1.3.4", "@endo/marshal": "^1.8.0", diff --git a/patches/@chainsafe+libp2p-yamux+7.0.4.patch b/patches/@chainsafe+libp2p-yamux+7.0.4.patch new file mode 100644 index 000000000..2a362e932 --- /dev/null +++ b/patches/@chainsafe+libp2p-yamux+7.0.4.patch @@ -0,0 +1,21 @@ +diff --git a/node_modules/@chainsafe/libp2p-yamux/dist/src/decode.js b/node_modules/@chainsafe/libp2p-yamux/dist/src/decode.js +index 58728fddc7355906bcb8d8273d41e73ecbbc9d67..7aa39de8fc9268d632be340d10f87a2522f18c45 100644 +--- a/node_modules/@chainsafe/libp2p-yamux/dist/src/decode.js ++++ b/node_modules/@chainsafe/libp2p-yamux/dist/src/decode.js +@@ -111,14 +111,14 @@ export class Decoder { + export function returnlessSource(source) { + if (source[Symbol.iterator] !== undefined) { + const iterator = source[Symbol.iterator](); +- iterator.return = undefined; ++ Object.defineProperty(iterator, 'return', {}); + return { + [Symbol.iterator]() { return iterator; } + }; + } + else if (source[Symbol.asyncIterator] !== undefined) { + const iterator = source[Symbol.asyncIterator](); +- iterator.return = undefined; ++ Object.defineProperty(iterator, 'return', {}); + return { + [Symbol.asyncIterator]() { return iterator; } + }; diff --git a/scripts/copy-patches.cjs b/scripts/copy-patches.cjs new file mode 100644 index 000000000..a427f070b --- /dev/null +++ b/scripts/copy-patches.cjs @@ -0,0 +1,190 @@ +'use strict'; + +const { + readdir, + readFile, + mkdir, + copyFile, + access, +} = require('node:fs/promises'); +const path = require('node:path'); + +const { + parsePatchFilename, + getTransitiveInternalDeps, +} = require('./patch-utils.cjs'); + +const ROOT = path.resolve(__dirname, '..'); +const ROOT_PATCHES_DIR = path.join(ROOT, 'patches'); + +/** + * Check whether a file exists. + * + * @param {string} filepath - The file path. + * @returns {Promise} Whether the file exists. + */ +async function fileExists(filepath) { + try { + await access(filepath); + return true; + } catch { + return false; + } +} + +/** + * Read all non-private workspaces from packages/*. + * + * @returns {Promise<{ name: string, dir: string, deps: Record, files: string[] }[]>} The non-private workspaces. + */ +async function getWorkspaces() { + const packagesDir = path.join(ROOT, 'packages'); + const entries = await readdir(packagesDir); + const workspaces = []; + + for (const entry of entries) { + const pkgJsonPath = path.join(packagesDir, entry, 'package.json'); + if (!(await fileExists(pkgJsonPath))) { + continue; + } + + const pkg = JSON.parse(await readFile(pkgJsonPath, 'utf8')); + if (pkg.private === true) { + continue; + } + + workspaces.push({ + name: pkg.name, + dir: path.join(packagesDir, entry), + deps: { ...(pkg.dependencies ?? {}) }, + files: pkg.files ?? [], + }); + } + + return workspaces; +} + +/** + * Build a map from workspace name to workspace object. + * + * @param {{ name: string, dir: string, deps: Record, files: string[] }[]} workspaces - The workspaces to index. + * @returns {Map, files: string[] }>} The workspace name map. + */ +function buildWorkspaceMap(workspaces) { + const map = new Map(); + for (const ws of workspaces) { + map.set(ws.name, ws); + } + return map; +} + +/** + * Find the sink workspaces for a given patched dependency. + * + * Sinks are workspaces in the set of direct dependents (non-private packages + * that directly depend on the patched dep) that do not transitively depend on + * any other workspace in that set. Installing a sink always brings along any + * non-sink, so only sinks need to ship the patch. + * + * @param {string} patchedPkgName - The patched package name. + * @param {{ name: string, dir: string, deps: Record, files: string[] }[]} workspaces - All non-private workspaces. + * @param {Map, files: string[] }>} workspaceMap - The workspace name map. + * @returns {{ name: string, dir: string, deps: Record, files: string[] }[]} The sink workspaces. + */ +function findSinks(patchedPkgName, workspaces, workspaceMap) { + const directDeps = workspaces.filter((ws) => + Object.prototype.hasOwnProperty.call(ws.deps, patchedPkgName), + ); + + if (directDeps.length === 0) { + return []; + } + + const directDepNames = new Set(directDeps.map((ws) => ws.name)); + const workspaceNames = new Set(workspaceMap.keys()); + /** + * Get the dependency names for a workspace. + * + * @param {string} name - The workspace name. + * @returns {string[]} The dependency names. + */ + const getDeps = (name) => Object.keys(workspaceMap.get(name)?.deps ?? {}); + const cache = new Map(); + + return directDeps.filter((ws) => { + const transitiveDeps = getTransitiveInternalDeps( + ws.name, + workspaceNames, + getDeps, + cache, + ); + return ![...transitiveDeps].some((dep) => directDepNames.has(dep)); + }); +} + +/** + * Copy patch files from the root patches directory to each sink workspace. + */ +async function main() { + let patchFiles; + try { + const entries = await readdir(ROOT_PATCHES_DIR); + patchFiles = entries.filter((patchFile) => patchFile.endsWith('.patch')); + } catch (error) { + if ( + typeof error === 'object' && + error !== null && + 'code' in error && + error.code === 'ENOENT' + ) { + console.log('No patches directory found at root, nothing to do.'); + return; + } + throw error; + } + + if (patchFiles.length === 0) { + console.log('No patch files found in root patches directory.'); + return; + } + + const workspaces = await getWorkspaces(); + const workspaceMap = buildWorkspaceMap(workspaces); + + for (const patchFile of patchFiles) { + const { pkgName } = parsePatchFilename(patchFile); + console.log(`Processing patch: ${patchFile} (for ${pkgName})`); + + const sinks = findSinks(pkgName, workspaces, workspaceMap); + + if (sinks.length === 0) { + console.warn( + `Warning: No sinks found for patched dep "${pkgName}". The patch won't be shipped.`, + ); + continue; + } + + for (const sink of sinks) { + if (!sink.files.includes('patches/')) { + console.error( + `Error: Sink package "${sink.name}" must have "patches/" in its "files" field. Add it to ship the patch.`, + ); + process.exitCode = 1; + continue; + } + + const destDir = path.join(sink.dir, 'patches'); + await mkdir(destDir, { recursive: true }); + + const srcPath = path.join(ROOT_PATCHES_DIR, patchFile); + const destPath = path.join(destDir, patchFile); + await copyFile(srcPath, destPath); + console.log(` Copied to ${path.relative(ROOT, destPath)}`); + } + } +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/scripts/patch-utils.cjs b/scripts/patch-utils.cjs new file mode 100644 index 000000000..52819d6af --- /dev/null +++ b/scripts/patch-utils.cjs @@ -0,0 +1,57 @@ +'use strict'; + +/** + * Parse a patch filename to extract the package name and version. + * + * @param {string} filename - The patch filename. + * @returns {{ pkgName: string, version: string }} The parsed package name and version. + */ +function parsePatchFilename(filename) { + const withoutExt = filename.replace(/\.patch$/u, ''); + const lastPlusIdx = withoutExt.lastIndexOf('+'); + const version = withoutExt.slice(lastPlusIdx + 1); + const pkgRaw = withoutExt.slice(0, lastPlusIdx); + const pkgName = pkgRaw.replace(/\+/gu, '/'); + return { pkgName, version }; +} + +/** + * Get the transitive closure of internal workspace dependencies for a package. + * + * @param {string} pkgName - The package name. + * @param {Set} workspaceNames - Set of all workspace names. + * @param {(name: string) => string[]} getDeps - Returns dependency names for a workspace. + * @param {Map>} cache - Memoization cache. + * @returns {Set} Set of transitive internal dep names. + */ +function getTransitiveInternalDeps(pkgName, workspaceNames, getDeps, cache) { + if (cache.has(pkgName)) { + return cache.get(pkgName); + } + + if (!workspaceNames.has(pkgName)) { + cache.set(pkgName, new Set()); + return new Set(); + } + + const result = new Set(); + cache.set(pkgName, result); // set before recursing to break cycles + + for (const depName of getDeps(pkgName)) { + if (workspaceNames.has(depName)) { + result.add(depName); + for (const transitiveDep of getTransitiveInternalDeps( + depName, + workspaceNames, + getDeps, + cache, + )) { + result.add(transitiveDep); + } + } + } + + return result; +} + +module.exports = { parsePatchFilename, getTransitiveInternalDeps }; diff --git a/scripts/prepack.sh b/scripts/prepack.sh index 7f2ece375..372db00b0 100755 --- a/scripts/prepack.sh +++ b/scripts/prepack.sh @@ -10,3 +10,4 @@ if [[ -n $SKIP_PREPACK ]]; then fi yarn build +node scripts/copy-patches.cjs diff --git a/yarn.config.cjs b/yarn.config.cjs index 25d6cacb8..6fc15b0f7 100644 --- a/yarn.config.cjs +++ b/yarn.config.cjs @@ -9,11 +9,16 @@ const { defineConfig } = require('@yarnpkg/types'); const { get } = require('lodash'); -const { readFile } = require('node:fs/promises'); +const { readFile, readdir } = require('node:fs/promises'); const { basename, resolve } = require('node:path'); const { inspect } = require('node:util'); const semver = require('semver'); +const { + parsePatchFilename, + getTransitiveInternalDeps, +} = require('./scripts/patch-utils.cjs'); + // Packages that do not have an entrypoint, types, or sideEffects const entrypointExceptions = ['shims', 'streams']; // Packages that do not have typedoc @@ -35,6 +40,7 @@ const exportsExceptions = ['kernel-cli', 'kernel-shims']; const filesExceptions = [ 'kernel-browser-runtime', 'kernel-store', + 'kernel-utils', 'ocap-kernel', 'streams', ]; @@ -60,6 +66,8 @@ module.exports = defineConfig({ '', ); + const patchData = await computeAllPatchData(Yarn); + for (const workspace of Yarn.workspaces()) { const workspaceBasename = getWorkspaceBasename(workspace); const isChildWorkspace = workspace.cwd !== '.'; @@ -129,6 +137,10 @@ module.exports = defineConfig({ if (!isPrivate) { // Non-private packages must not depend on private packages. expectNoPrivateWorkspaceProductionDependencies(Yarn, workspace); + // Non-private packages must not use the patch: protocol in production deps. + expectNoPatchProtocolProductionDependencies(Yarn, workspace); + // Non-private packages must correctly ship patches (sinks only). + expectPatchShippingIsCorrect(workspace, patchData.sinkWorkspaces); } if (!isPrivate && !exportsExceptions.includes(workspaceBasename)) { @@ -304,6 +316,13 @@ module.exports = defineConfig({ // development-only scripts, as otherwise the // `node/no-unpublished-require` ESLint rule will disallow it.) expectWorkspaceField(workspace, 'files', []); + + // Root patches/ must not contain patches for deps removed from all workspaces. + expectNoPatchesForRemovedDependencies( + Yarn, + workspace, + patchData.pkgNameByPatch, + ); } // Ensure all dependency ranges are recognizable @@ -862,6 +881,216 @@ function expectNoPrivateWorkspaceProductionDependencies(Yarn, workspace) { } } +/** + * Expect that non-private workspace packages do not have production + * dependencies using the `patch:` protocol. Patches are workspace-local and + * are not bundled with published packages, making them unresolvable for + * consumers. Use `patch-package` with a root `patches/` directory instead. + * + * @param {Yarn} Yarn - The Yarn "global". + * @param {Workspace} workspace - The workspace to check. + */ +function expectNoPatchProtocolProductionDependencies(Yarn, workspace) { + for (const dependency of Yarn.dependencies({ workspace })) { + if (dependency.type === 'devDependencies') { + continue; + } + if (dependency.range.startsWith('patch:')) { + dependency.error( + `Non-private package "${workspace.manifest.name}" must not use the "patch:" protocol for "${dependency.ident}" in "${dependency.type}". Use patch-package with a root patches/ directory instead.`, + ); + } + } +} + +/** + * Compute which non-private workspaces are "sinks" for each patched dependency. + * + * A sink is a workspace in U (non-private packages directly depending on the + * patched dep) that does not transitively depend on any other workspace in U. + * Only sinks need to ship the patch; non-sinks always have a sink installed + * alongside them. + * + * @param {Yarn} Yarn - The Yarn "global". + * @returns {Promise<{ + * sinkWorkspaces: Map, + * pkgNameByPatch: Map + * }>} The computed sink workspaces and patch-to-package-name mapping. + */ +async function computeAllPatchData(Yarn) { + const patchesDir = resolve(__dirname, 'patches'); + let patchFiles; + try { + const entries = await readdir(patchesDir); + patchFiles = entries.filter((patchFile) => patchFile.endsWith('.patch')); + } catch (error) { + if ( + typeof error === 'object' && + error !== null && + 'code' in error && + error.code === 'ENOENT' + ) { + patchFiles = []; + } else { + throw error; + } + } + + const workspaceNameMap = new Map(); + for (const ws of Yarn.workspaces()) { + if (ws.manifest.name) { + workspaceNameMap.set(ws.manifest.name, ws); + } + } + + // pkgName -> Set of sinks + const sinksByPkg = new Map(); + const pkgNameByPatch = new Map(); + + for (const patchFile of patchFiles) { + const { pkgName } = parsePatchFilename(patchFile); + pkgNameByPatch.set(patchFile, pkgName); + + // directDeps = non-private workspaces with pkgName as a direct dependency + const directDeps = Yarn.workspaces().filter( + (ws) => + ws.manifest.private !== true && + Object.prototype.hasOwnProperty.call( + ws.manifest.dependencies ?? {}, + pkgName, + ), + ); + + const directDepNames = new Set(directDeps.map((ws) => ws.manifest.name)); + const workspaceNames = new Set(workspaceNameMap.keys()); + const getDeps = (name) => + Object.keys(workspaceNameMap.get(name)?.manifest.dependencies ?? {}); + const cache = new Map(); + const sinks = new Set(); + + for (const ws of directDeps) { + const transitiveDeps = getTransitiveInternalDeps( + ws.manifest.name, + workspaceNames, + getDeps, + cache, + ); + if (![...transitiveDeps].some((dep) => directDepNames.has(dep))) { + sinks.add(ws.manifest.name); + } + } + + sinksByPkg.set(pkgName, sinks); + } + + // Build sinkWorkspaces: wsName -> boolean + const sinkWorkspaces = new Map(); + for (const ws of Yarn.workspaces()) { + if (!ws.manifest.name || ws.manifest.private === true) { + continue; + } + const wsName = ws.manifest.name; + const isSink = [...sinksByPkg.values()].some((sinks) => sinks.has(wsName)); + sinkWorkspaces.set(wsName, isSink); + } + + return { sinkWorkspaces, pkgNameByPatch }; +} + +/** + * The canonical postinstall script for patch sink packages. Guards with + * `[ -d patches ]` so the script is a no-op in the monorepo where the + * `patches/` directory is only populated at publish time. + */ +const PATCH_POSTINSTALL = + '[ ! -d patches ] || patch-package --patch-dir patches'; + +/** + * Expect that a non-private workspace correctly ships patches based on whether + * it is a sink in the patch dependency graph. + * + * Sinks must have `patches/` in `files`, `patch-package: "^8.0.0"` in + * `peerDependencies`, and a `postinstall` script that runs patch-package. + * Non-sinks must have none of these. + * + * @param {Workspace} workspace - The workspace to check. + * @param {Map} sinkWorkspaces - Map of workspace name to isSink. + */ +function expectPatchShippingIsCorrect(workspace, sinkWorkspaces) { + const wsName = workspace.manifest.name; + const isSink = sinkWorkspaces.get(wsName) ?? false; + const files = workspace.manifest.files ?? []; + const peerDeps = workspace.manifest.peerDependencies ?? {}; + const postinstall = workspace.manifest.scripts?.postinstall ?? ''; + + if (isSink) { + if (!files.includes('patches/')) { + workspace.error( + `Package "${wsName}" is a patch sink but does not have "patches/" in "files". Add it to ship the patch.`, + ); + } + workspace.set('peerDependencies["patch-package"]', '^8.0.0'); + workspace.unset('peerDependenciesMeta["patch-package"]'); + if (!postinstall) { + workspace.set('scripts.postinstall', PATCH_POSTINSTALL); + } else if (!postinstall.includes('patch-package --patch-dir patches')) { + workspace.error( + `Package "${wsName}" is a patch sink but its "postinstall" script does not include "patch-package --patch-dir patches".`, + ); + } + } else { + if (files.includes('patches/')) { + workspace.set( + 'files', + files.filter((file) => file !== 'patches/'), + ); + } + if (Object.prototype.hasOwnProperty.call(peerDeps, 'patch-package')) { + workspace.unset('peerDependencies["patch-package"]'); + } + workspace.unset('peerDependenciesMeta["patch-package"]'); + if (postinstall === PATCH_POSTINSTALL) { + workspace.unset('scripts.postinstall'); + } else if (postinstall.includes('patch-package')) { + workspace.error( + `Package "${wsName}" is not a patch sink but its "postinstall" script references "patch-package". Remove it manually.`, + ); + } + } +} + +/** + * Expect that the root `patches/` directory does not contain patches for + * packages that are no longer dependencies of any workspace. + * + * @param {Yarn} Yarn - The Yarn "global". + * @param {Workspace} rootWorkspace - The root workspace. + * @param {Map} pkgNameByPatch - Map of patch filename to package name. + */ +function expectNoPatchesForRemovedDependencies( + Yarn, + rootWorkspace, + pkgNameByPatch, +) { + const allDeps = new Set(); + for (const ws of Yarn.workspaces()) { + for (const dep of Object.keys(ws.manifest.dependencies ?? {})) { + allDeps.add(dep); + } + for (const dep of Object.keys(ws.manifest.devDependencies ?? {})) { + allDeps.add(dep); + } + } + + for (const [patchFile, pkgName] of pkgNameByPatch) { + if (!allDeps.has(pkgName)) { + rootWorkspace.error( + `Root patch "${patchFile}" targets "${pkgName}" which is not a dependency of any workspace. Delete the patch file.`, + ); + } + } +} + /** * Expect that the workspace has a README.md file, and that it is a non-empty * string. The README.md is expected to: diff --git a/yarn.lock b/yarn.lock index ba942628a..bd2208b3e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -583,22 +583,6 @@ __metadata: languageName: node linkType: hard -"@chainsafe/libp2p-yamux@patch:@chainsafe/libp2p-yamux@npm%3A7.0.4#~/.yarn/patches/@chainsafe-libp2p-yamux-npm-7.0.4-284c2f6812.patch": - version: 7.0.4 - resolution: "@chainsafe/libp2p-yamux@patch:@chainsafe/libp2p-yamux@npm%3A7.0.4#~/.yarn/patches/@chainsafe-libp2p-yamux-npm-7.0.4-284c2f6812.patch::version=7.0.4&hash=622865" - dependencies: - "@libp2p/interface": "npm:^2.0.0" - "@libp2p/utils": "npm:^6.0.0" - get-iterator: "npm:^2.0.1" - it-foreach: "npm:^2.0.6" - it-pushable: "npm:^3.2.3" - it-stream-types: "npm:^2.0.1" - race-signal: "npm:^1.1.3" - uint8arraylist: "npm:^2.4.8" - checksum: 10/2561c7818832987584d0fd025f3c19b962f5ea47a3132e68b2496fd7cebe392c54cbfad0b0abc032f77cae3ff2c93d9fde8fe9c8b5de354517782a0eeb60af40 - languageName: node - linkType: hard - "@chainsafe/netmask@npm:^2.0.0": version: 2.0.0 resolution: "@chainsafe/netmask@npm:2.0.0" @@ -2207,11 +2191,11 @@ __metadata: languageName: node linkType: hard -"@metamask/design-system-react@npm:^0.6.0": - version: 0.6.0 - resolution: "@metamask/design-system-react@npm:0.6.0" +"@metamask/design-system-react@npm:^0.9.0": + version: 0.9.0 + resolution: "@metamask/design-system-react@npm:0.9.0" dependencies: - "@metamask/design-system-shared": "npm:^0.1.1" + "@metamask/design-system-shared": "npm:^0.2.0" "@metamask/jazzicon": "npm:^2.0.0" "@radix-ui/react-slot": "npm:^1.1.0" blo: "npm:^2.0.0" @@ -2219,19 +2203,19 @@ __metadata: peerDependencies: "@metamask/design-system-tailwind-preset": ^0.6.0 "@metamask/design-tokens": ^8.1.0 - "@metamask/utils": ^11.8.1 + "@metamask/utils": ^11.10.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - checksum: 10/6aec4789b5ee62a79074f49f32f7a6e18ceec156b4ee08e3d372094323fb4943b9956b72696a9b28928ee45138e16116c239b3d194406354432a95a61eda7428 + checksum: 10/344d16745f3924f8a9f10f4293e611238c16fe452eb151117ee6711565e081bc97d9a792047e50903a88a7f0bf1c64bb483bfe791b90fa0a7214646a519d8085 languageName: node linkType: hard -"@metamask/design-system-shared@npm:^0.1.1": - version: 0.1.1 - resolution: "@metamask/design-system-shared@npm:0.1.1" +"@metamask/design-system-shared@npm:^0.2.0": + version: 0.2.0 + resolution: "@metamask/design-system-shared@npm:0.2.0" dependencies: - "@metamask/utils": "npm:^11.8.0" - checksum: 10/8e8dab9ca59f83da76078a9f644d73d90cdb309ce5549acb915fbe2fdfb03803b3d73533e3d64a1bdca4ac2a19514d6885fe5c727832353a5691d310f06735d9 + "@metamask/utils": "npm:^11.10.0" + checksum: 10/8b7436e4c1882495faa1ca402456dfb82247e517c718da0bed0c4daf7ec06ed0e6bfa7fa3c9fa6930b30e4e5f3f8576436f0de27d9a86f97169cab0423285612 languageName: node linkType: hard @@ -2393,7 +2377,7 @@ __metadata: "@metamask/logger": "workspace:^" "@metamask/ocap-kernel": "workspace:^" "@metamask/rpc-errors": "npm:^7.0.3" - "@metamask/snaps-utils": "npm:^11.7.1" + "@metamask/snaps-utils": "npm:^12.1.0" "@metamask/streams": "workspace:^" "@metamask/superstruct": "npm:^3.2.1" "@metamask/utils": "npm:^11.9.0" @@ -2745,7 +2729,7 @@ __metadata: dependencies: "@arethetypeswrong/cli": "npm:^0.17.4" "@metamask/auto-changelog": "npm:^5.3.0" - "@metamask/design-system-react": "npm:^0.6.0" + "@metamask/design-system-react": "npm:^0.9.0" "@metamask/design-system-tailwind-preset": "npm:^0.6.1" "@metamask/design-tokens": "npm:^8.1.1" "@metamask/eslint-config": "npm:^15.0.0" @@ -2807,7 +2791,7 @@ __metadata: dependencies: "@arethetypeswrong/cli": "npm:^0.17.4" "@chainsafe/libp2p-noise": "npm:^16.1.3" - "@chainsafe/libp2p-yamux": "patch:@chainsafe/libp2p-yamux@npm%3A7.0.4#~/.yarn/patches/@chainsafe-libp2p-yamux-npm-7.0.4-284c2f6812.patch" + "@chainsafe/libp2p-yamux": "npm:7.0.4" "@endo/captp": "npm:^4.4.8" "@endo/errors": "npm:^1.2.13" "@endo/exo": "npm:^1.5.12" @@ -2858,6 +2842,7 @@ __metadata: vitest: "npm:^4.0.16" peerDependencies: acorn: ^8.15.0 + patch-package: ^8.0.0 vite: ^7.3.0 peerDependenciesMeta: acorn: @@ -2954,7 +2939,7 @@ __metadata: dependencies: "@agoric/swingset-liveslots": "npm:0.10.3-u21.0.1" "@chainsafe/libp2p-noise": "npm:^16.1.3" - "@chainsafe/libp2p-yamux": "patch:@chainsafe/libp2p-yamux@npm%3A7.0.4#~/.yarn/patches/@chainsafe-libp2p-yamux-npm-7.0.4-284c2f6812.patch" + "@chainsafe/libp2p-yamux": "npm:7.0.4" "@endo/errors": "npm:^1.2.13" "@endo/eventual-send": "npm:^1.3.4" "@endo/marshal": "npm:^1.8.0" @@ -3084,10 +3069,10 @@ __metadata: languageName: node linkType: hard -"@metamask/slip44@npm:^4.3.0": - version: 4.3.0 - resolution: "@metamask/slip44@npm:4.3.0" - checksum: 10/508983a48911f2be8d9de117d390ecfb5b949a6032f5d6c5cc63f7f23302b87468be6ff08dee4881d39e8f5f66b5545eab15e6fc0511acea10fd4c99852a8212 +"@metamask/slip44@npm:^4.4.0": + version: 4.4.0 + resolution: "@metamask/slip44@npm:4.4.0" + checksum: 10/296ac7c578bd35792c7e3942a9a0b7d9d7af76cf98358b97403c1ed483faa3c2fe6c71b1c3f8c7719fbfcf9fc73e5fa8707c89ac277ee9ce6c8bc4c694b2059d languageName: node linkType: hard @@ -3103,23 +3088,23 @@ __metadata: languageName: node linkType: hard -"@metamask/snaps-sdk@npm:^10.3.0": - version: 10.3.0 - resolution: "@metamask/snaps-sdk@npm:10.3.0" +"@metamask/snaps-sdk@npm:^10.4.0": + version: 10.4.0 + resolution: "@metamask/snaps-sdk@npm:10.4.0" dependencies: "@metamask/key-tree": "npm:^10.1.1" "@metamask/providers": "npm:^22.1.1" "@metamask/rpc-errors": "npm:^7.0.3" "@metamask/superstruct": "npm:^3.2.1" - "@metamask/utils": "npm:^11.8.1" + "@metamask/utils": "npm:^11.9.0" luxon: "npm:^3.5.0" - checksum: 10/24efa7af91557d2365cd2199e0572566675efadbcc51a350938cb57e4b5c0861188c30d26a9ec3a42cb9059b37bba0f44a5486ec3ed32ac2e956cf48213d6485 + checksum: 10/215a73f41f5043ca9767241da83308bb25b3faae96521ed71a63a9d29078461bcf278a01799e014bb20b49e34d96a05ea1822648c9bcf81e92f22e8c9b6c0f94 languageName: node linkType: hard -"@metamask/snaps-utils@npm:^11.7.1": - version: 11.7.1 - resolution: "@metamask/snaps-utils@npm:11.7.1" +"@metamask/snaps-utils@npm:^12.1.0": + version: 12.1.0 + resolution: "@metamask/snaps-utils@npm:12.1.0" dependencies: "@babel/core": "npm:^7.23.2" "@babel/types": "npm:^7.23.0" @@ -3127,9 +3112,9 @@ __metadata: "@metamask/messenger": "npm:^0.3.0" "@metamask/permission-controller": "npm:^12.2.0" "@metamask/rpc-errors": "npm:^7.0.3" - "@metamask/slip44": "npm:^4.3.0" + "@metamask/slip44": "npm:^4.4.0" "@metamask/snaps-registry": "npm:^4.0.0" - "@metamask/snaps-sdk": "npm:^10.3.0" + "@metamask/snaps-sdk": "npm:^10.4.0" "@metamask/superstruct": "npm:^3.2.1" "@metamask/utils": "npm:^11.9.0" "@scure/base": "npm:^1.1.1" @@ -3137,14 +3122,14 @@ __metadata: cron-parser: "npm:^4.5.0" fast-deep-equal: "npm:^3.1.3" fast-json-stable-stringify: "npm:^2.1.0" - fast-xml-parser: "npm:^4.4.1" + fast-xml-parser: "npm:^5.3.4" luxon: "npm:^3.5.0" marked: "npm:^12.0.1" rfdc: "npm:^1.3.0" semver: "npm:^7.5.4" ses: "npm:^1.14.0" validate-npm-package-name: "npm:^5.0.0" - checksum: 10/4f1ebf14df5eef4344aef8367923176b02f68c58a76c2776aef3015e36f1f889f6463a6b991c78e5810424cf117e20972511a12fcc3436839b0f6100cbb8f09e + checksum: 10/f81a66a0f0d7710f18cfcebceacf2ec7ca9c468a4839b8cc15652cc046a15455c55a3fff846061ff7d1ecd4368c182566db4f73b3c8b4ed4b2a09adc57e2e711 languageName: node linkType: hard @@ -3203,9 +3188,9 @@ __metadata: languageName: node linkType: hard -"@metamask/utils@npm:^11.0.1, @metamask/utils@npm:^11.1.0, @metamask/utils@npm:^11.4.0, @metamask/utils@npm:^11.4.2, @metamask/utils@npm:^11.8.0, @metamask/utils@npm:^11.8.1, @metamask/utils@npm:^11.9.0": - version: 11.9.0 - resolution: "@metamask/utils@npm:11.9.0" +"@metamask/utils@npm:^11.0.1, @metamask/utils@npm:^11.1.0, @metamask/utils@npm:^11.10.0, @metamask/utils@npm:^11.4.0, @metamask/utils@npm:^11.4.2, @metamask/utils@npm:^11.8.1, @metamask/utils@npm:^11.9.0": + version: 11.10.0 + resolution: "@metamask/utils@npm:11.10.0" dependencies: "@ethereumjs/tx": "npm:^4.2.0" "@metamask/superstruct": "npm:^3.1.0" @@ -3218,7 +3203,7 @@ __metadata: pony-cause: "npm:^2.1.10" semver: "npm:^7.5.4" uuid: "npm:^9.0.1" - checksum: 10/f8f5e99ba6c6de0395ed4e0acc82ee9c0dca26991ea6a8f10b3896e72745790966a8eded8c42be905d9f01fa99c1fd29a7f68541e2ef9854fc14984a0b514ad3 + checksum: 10/691a268af66593b60e9807a069127993cea3cdc941f99d5d7ca4664868754f08945821f1787b2f3e99e4497df63ceb0af37a2419ad494da29a1fddffe94f5797 languageName: node linkType: hard @@ -3559,7 +3544,7 @@ __metadata: resolution: "@ocap/brow-2-brow@workspace:packages/brow-2-brow" dependencies: "@chainsafe/libp2p-noise": "npm:^16.1.3" - "@chainsafe/libp2p-yamux": "patch:@chainsafe/libp2p-yamux@npm%3A7.0.4#~/.yarn/patches/@chainsafe-libp2p-yamux-npm-7.0.4-284c2f6812.patch" + "@chainsafe/libp2p-yamux": "npm:7.0.4" "@libp2p/autonat": "npm:2.0.38" "@libp2p/bootstrap": "npm:11.0.47" "@libp2p/circuit-relay-v2": "npm:3.2.24" @@ -3960,6 +3945,7 @@ __metadata: globals: "npm:^16.0.0" lint-staged: "npm:^15.5.0" lodash: "npm:^4.17.21" + patch-package: "npm:^8.0.0" playwright: "npm:^1.58.2" prettier: "npm:^3.5.3" prettier-plugin-packagejson: "npm:^2.5.10" @@ -6312,6 +6298,13 @@ __metadata: languageName: node linkType: hard +"@yarnpkg/lockfile@npm:^1.1.0": + version: 1.1.0 + resolution: "@yarnpkg/lockfile@npm:1.1.0" + checksum: 10/cd19e1114aaf10a05126aeea8833ef4ca8af8a46e88e12884f8359d19333fd19711036dbc2698dbe937f81f037070cf9a8da45c2e8c6ca19cafd7d15659094ed + languageName: node + linkType: hard + "@yarnpkg/types@npm:^4.0.1": version: 4.0.1 resolution: "@yarnpkg/types@npm:4.0.1" @@ -7224,6 +7217,13 @@ __metadata: languageName: node linkType: hard +"ci-info@npm:^3.7.0": + version: 3.9.0 + resolution: "ci-info@npm:3.9.0" + checksum: 10/75bc67902b4d1c7b435497adeb91598f6d52a3389398e44294f6601b20cfef32cf2176f7be0eb961d9e085bb333a8a5cae121cb22f81cf238ae7f58eb80e9397 + languageName: node + linkType: hard + "cjs-module-lexer@npm:^1.2.3, cjs-module-lexer@npm:^1.3.1": version: 1.4.3 resolution: "cjs-module-lexer@npm:1.4.3" @@ -9109,14 +9109,22 @@ __metadata: languageName: node linkType: hard -"fast-xml-parser@npm:^4.4.1": - version: 4.5.3 - resolution: "fast-xml-parser@npm:4.5.3" +"fast-xml-builder@npm:^1.0.0": + version: 1.0.0 + resolution: "fast-xml-builder@npm:1.0.0" + checksum: 10/06c04d80545e5c9f4d1d6cca00567b5cc09953a92c6328fa48cfb4d7f42630313b8c2bb62e9cb81accee7bb5e1c5312fcae06c3d20dbe52d969a5938233316da + languageName: node + linkType: hard + +"fast-xml-parser@npm:^5.3.4": + version: 5.4.2 + resolution: "fast-xml-parser@npm:5.4.2" dependencies: - strnum: "npm:^1.1.1" + fast-xml-builder: "npm:^1.0.0" + strnum: "npm:^2.1.2" bin: fxparser: src/cli/cli.js - checksum: 10/ca22bf9d65c10b8447c1034c13403e90ecee210e2b3852690df3d8a42b8a46ec655fae7356096abd98a15b89ddaf11878587b1773e0c3be4cbc2ac4af4c7bf95 + checksum: 10/12585d5dd77113411d01cf41818cfecbbaf8f3d9e8448b1c35f50a7eb51205408bc8db27af5733173a77f96f72d7e121d9e675674f71334569157c77845aba39 languageName: node linkType: hard @@ -9207,6 +9215,15 @@ __metadata: languageName: node linkType: hard +"find-yarn-workspace-root@npm:^2.0.0": + version: 2.0.0 + resolution: "find-yarn-workspace-root@npm:2.0.0" + dependencies: + micromatch: "npm:^4.0.2" + checksum: 10/7fa7942849eef4d5385ee96a0a9a5a9afe885836fd72ed6a4280312a38690afea275e7d09b343fe97daf0412d833f8ac4b78c17fc756386d9ebebf0759d707a7 + languageName: node + linkType: hard + "findup-sync@npm:^5.0.0": version: 5.0.0 resolution: "findup-sync@npm:5.0.0" @@ -9283,6 +9300,17 @@ __metadata: languageName: node linkType: hard +"fs-extra@npm:^10.0.0": + version: 10.1.0 + resolution: "fs-extra@npm:10.1.0" + dependencies: + graceful-fs: "npm:^4.2.0" + jsonfile: "npm:^6.0.1" + universalify: "npm:^2.0.0" + checksum: 10/05ce2c3b59049bcb7b52001acd000e44b3c4af4ec1f8839f383ef41ec0048e3cfa7fd8a637b1bddfefad319145db89be91f4b7c1db2908205d38bf91e7d1d3b7 + languageName: node + linkType: hard + "fs-extra@npm:^11.1.0, fs-extra@npm:~11.3.0": version: 11.3.0 resolution: "fs-extra@npm:11.3.0" @@ -9653,7 +9681,7 @@ __metadata: languageName: node linkType: hard -"graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6": +"graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6": version: 4.2.11 resolution: "graceful-fs@npm:4.2.11" checksum: 10/bf152d0ed1dc159239db1ba1f74fdbc40cb02f626770dcd5815c427ce0688c2635a06ed69af364396da4636d0408fcf7d4afdf7881724c3307e46aff30ca49e2 @@ -10135,6 +10163,15 @@ __metadata: languageName: node linkType: hard +"is-docker@npm:^2.0.0": + version: 2.2.1 + resolution: "is-docker@npm:2.2.1" + bin: + is-docker: cli.js + checksum: 10/3fef7ddbf0be25958e8991ad941901bf5922ab2753c46980b60b05c1bf9c9c2402d35e6dc32e4380b980ef5e1970a5d9d5e5aa2e02d77727c3b6b5e918474c56 + languageName: node + linkType: hard + "is-docker@npm:^3.0.0": version: 3.0.0 resolution: "is-docker@npm:3.0.0" @@ -10407,6 +10444,15 @@ __metadata: languageName: node linkType: hard +"is-wsl@npm:^2.1.1": + version: 2.2.0 + resolution: "is-wsl@npm:2.2.0" + dependencies: + is-docker: "npm:^2.0.0" + checksum: 10/20849846ae414997d290b75e16868e5261e86ff5047f104027026fd61d8b5a9b0b3ade16239f35e1a067b3c7cc02f70183cb661010ed16f4b6c7c93dad1b19d8 + languageName: node + linkType: hard + "is-wsl@npm:^3.1.0": version: 3.1.0 resolution: "is-wsl@npm:3.1.0" @@ -10903,6 +10949,19 @@ __metadata: languageName: node linkType: hard +"json-stable-stringify@npm:^1.0.2": + version: 1.3.0 + resolution: "json-stable-stringify@npm:1.3.0" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.4" + isarray: "npm:^2.0.5" + jsonify: "npm:^0.0.1" + object-keys: "npm:^1.1.1" + checksum: 10/6661e9704733d2826b2012fea7b152ca216c82d8c725c8d390ee6434eabdf43c66fa6e6b423cce9bf95f8fec0ef52004c09a99043c7daf6e58595a0cff204629 + languageName: node + linkType: hard + "json5@npm:^2.2.3": version: 2.2.3 resolution: "json5@npm:2.2.3" @@ -10925,6 +10984,13 @@ __metadata: languageName: node linkType: hard +"jsonify@npm:^0.0.1": + version: 0.0.1 + resolution: "jsonify@npm:0.0.1" + checksum: 10/7b86b6f4518582ff1d8b7624ed6c6277affd5246445e864615dbdef843a4057ac58587684faf129ea111eeb80e01c15f0a4d9d03820eb3f3985fa67e81b12398 + languageName: node + linkType: hard + "jsx-ast-utils@npm:^2.4.1 || ^3.0.0": version: 3.3.5 resolution: "jsx-ast-utils@npm:3.3.5" @@ -10946,6 +11012,15 @@ __metadata: languageName: node linkType: hard +"klaw-sync@npm:^6.0.0": + version: 6.0.0 + resolution: "klaw-sync@npm:6.0.0" + dependencies: + graceful-fs: "npm:^4.1.11" + checksum: 10/0da397f8961313c3ef8f79fb63af9002cde5a8fb2aeb1a37351feff0dd6006129c790400c3f5c3b4e757bedcabb13d21ec0a5eaef5a593d59515d4f2c291e475 + languageName: node + linkType: hard + "klona@npm:^2.0.6": version: 2.0.6 resolution: "klona@npm:2.0.6" @@ -11372,7 +11447,7 @@ __metadata: languageName: node linkType: hard -"micromatch@npm:^4.0.4, micromatch@npm:^4.0.5, micromatch@npm:^4.0.8": +"micromatch@npm:^4.0.2, micromatch@npm:^4.0.4, micromatch@npm:^4.0.5, micromatch@npm:^4.0.8": version: 4.0.8 resolution: "micromatch@npm:4.0.8" dependencies: @@ -11503,7 +11578,7 @@ __metadata: languageName: node linkType: hard -"minimist@npm:^1.2.0, minimist@npm:^1.2.3": +"minimist@npm:^1.2.0, minimist@npm:^1.2.3, minimist@npm:^1.2.6": version: 1.2.8 resolution: "minimist@npm:1.2.8" checksum: 10/908491b6cc15a6c440ba5b22780a0ba89b9810e1aea684e253e43c4e3b8d56ec1dcdd7ea96dde119c29df59c936cde16062159eae4225c691e19c70b432b6e6f @@ -12171,6 +12246,16 @@ __metadata: languageName: node linkType: hard +"open@npm:^7.4.2": + version: 7.4.2 + resolution: "open@npm:7.4.2" + dependencies: + is-docker: "npm:^2.0.0" + is-wsl: "npm:^2.1.1" + checksum: 10/4fc02ed3368dcd5d7247ad3566433ea2695b0713b041ebc0eeb2f0f9e5d4e29fc2068f5cdd500976b3464e77fe8b61662b1b059c73233ccc601fe8b16d6c1cd6 + languageName: node + linkType: hard + "optionator@npm:^0.9.3": version: 0.9.4 resolution: "optionator@npm:0.9.4" @@ -12415,6 +12500,30 @@ __metadata: languageName: node linkType: hard +"patch-package@npm:^8.0.0": + version: 8.0.1 + resolution: "patch-package@npm:8.0.1" + dependencies: + "@yarnpkg/lockfile": "npm:^1.1.0" + chalk: "npm:^4.1.2" + ci-info: "npm:^3.7.0" + cross-spawn: "npm:^7.0.3" + find-yarn-workspace-root: "npm:^2.0.0" + fs-extra: "npm:^10.0.0" + json-stable-stringify: "npm:^1.0.2" + klaw-sync: "npm:^6.0.0" + minimist: "npm:^1.2.6" + open: "npm:^7.4.2" + semver: "npm:^7.5.3" + slash: "npm:^2.0.0" + tmp: "npm:^0.2.4" + yaml: "npm:^2.2.2" + bin: + patch-package: index.js + checksum: 10/920a9fb0001ea67a66d79ea696e6d74e3040b0f282e09addae913978b6d0e8cb5ef9a4030eb77df8a5497e8822fa8449c2dfdc668ecff7fb9ff8bfef0c897c6e + languageName: node + linkType: hard + "path-browserify@npm:^1.0.1": version: 1.0.1 resolution: "path-browserify@npm:1.0.1" @@ -13809,6 +13918,13 @@ __metadata: languageName: node linkType: hard +"slash@npm:^2.0.0": + version: 2.0.0 + resolution: "slash@npm:2.0.0" + checksum: 10/512d4350735375bd11647233cb0e2f93beca6f53441015eea241fe784d8068281c3987fbaa93e7ef1c38df68d9c60013045c92837423c69115297d6169aa85e6 + languageName: node + linkType: hard + "slice-ansi@npm:^5.0.0": version: 5.0.0 resolution: "slice-ansi@npm:5.0.0" @@ -14198,10 +14314,10 @@ __metadata: languageName: node linkType: hard -"strnum@npm:^1.1.1": - version: 1.1.2 - resolution: "strnum@npm:1.1.2" - checksum: 10/ccd6297a1fdaf0fc8ea0ea904acdae76878d49a4b0d98a70155df4bc081fd88eac5ec99fb150f3d1d1af065c1898d38420705259ba6c39aa850c671bcd54e35d +"strnum@npm:^2.1.2": + version: 2.2.0 + resolution: "strnum@npm:2.2.0" + checksum: 10/2969dbc8441f5af1b55db1d2fcea64a8f912de18515b57f85574e66bdb8f30ae76c419cf1390b343d72d687e2aea5aca82390f18b9e0de45d6bcc6d605eb9385 languageName: node linkType: hard @@ -14465,6 +14581,13 @@ __metadata: languageName: node linkType: hard +"tmp@npm:^0.2.4": + version: 0.2.5 + resolution: "tmp@npm:0.2.5" + checksum: 10/dd4b78b32385eab4899d3ae296007b34482b035b6d73e1201c4a9aede40860e90997a1452c65a2d21aee73d53e93cd167d741c3db4015d90e63b6d568a93d7ec + languageName: node + linkType: hard + "to-regex-range@npm:^5.0.1": version: 5.0.1 resolution: "to-regex-range@npm:5.0.1"