feat(core): handle yarn resolutions and patches when parsing and pruning lock file (#30546)
This PR introduces support for `resolutions` and `patches` for yarn lock file parsing and pruning. Additionally, it fixes the correct dependency matching when the version has one of the two forms: - fluid version range (e.g. `npm:*`) - union version range (e.g. `1 || 2 || 3`) ## Current Behavior <!-- This is the behavior we have today --> ## Expected Behavior <!-- This is the behavior we should expect with the changes in this PR --> ## Related Issue(s) <!-- Please link the issue being fixed so it gets closed when this is merged. --> Fixes #
This commit is contained in:
parent
028b5768ff
commit
093b13fed9
@ -0,0 +1,92 @@
|
||||
{
|
||||
"name": "demo-app",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"@emotion/cache": "11.14.0",
|
||||
"@emotion/react": "11.14.0",
|
||||
"@emotion/server": "11.11.0",
|
||||
"@emotion/styled": "11.14.0",
|
||||
"@formatjs/intl-getcanonicallocales": "2.5.4",
|
||||
"@internationalized/date": "3.7.0",
|
||||
"@material-ui/core": "4.12.4",
|
||||
"@module-federation/nextjs-mf": "8.2.0",
|
||||
"@mui/material": "5.16.14",
|
||||
"@mui/material-nextjs": "5.16.14",
|
||||
"@nx/devkit": "20.3.1",
|
||||
"@popperjs/core": "2.11.8",
|
||||
"@ps/isp-web-services-types": "1.19.0",
|
||||
"@ps/platform-message-framework": "0.4.69",
|
||||
"@react-aria/focus": "3.19.1",
|
||||
"@rematch/core": "2.2.0",
|
||||
"@rematch/select": "3.1.2",
|
||||
"@sentry/react": "8.55.0",
|
||||
"@snowplow/browser-tracker": "3.24.6",
|
||||
"@snowplow/webview-tracker": "0.2.1",
|
||||
"@stomp/stompjs": "4.0.8",
|
||||
"class-variance-authority": "0.7.1",
|
||||
"clsx": "1.2.1",
|
||||
"date-fns": "2.30.0",
|
||||
"debounce-promise": "3.1.2",
|
||||
"debug": "4.3.7",
|
||||
"email-validator": "2.0.4",
|
||||
"final-form": "4.20.2",
|
||||
"flatpickr": "4.6.13",
|
||||
"fs-extra": "11.3.0",
|
||||
"get-scrollbar-width": "1.0.5",
|
||||
"history": "4.10.1",
|
||||
"hoist-non-react-statics": "3.3.2",
|
||||
"immer": "9.0.21",
|
||||
"json-schema": "0.4.0",
|
||||
"jssha": "3.3.1",
|
||||
"jwt-decode": "3.1.2",
|
||||
"libphonenumber-js": "1.11.20",
|
||||
"lodash": "4.17.21",
|
||||
"next": "14.2.3",
|
||||
"next-redux-wrapper": "8.1.0",
|
||||
"node-fetch": "2.7.0",
|
||||
"normalize.css": "8.0.1",
|
||||
"object-hash": "2.2.0",
|
||||
"openapi-fetch": "0.9.8",
|
||||
"prettier": "2.8.8",
|
||||
"prom-client": "15.1.3",
|
||||
"react": "18.2.0",
|
||||
"react-aria": "3.37.0",
|
||||
"react-aria-components": "1.6.0",
|
||||
"react-body-classname": "1.3.1",
|
||||
"react-dom": "18.2.0",
|
||||
"react-final-form": "6.5.9",
|
||||
"react-flatpickr": "3.10.13",
|
||||
"react-ga": "3.3.1",
|
||||
"react-intl": "7.1.6",
|
||||
"react-popper": "2.3.0",
|
||||
"react-redux": "9.2.0",
|
||||
"react-router": "5.3.4",
|
||||
"react-router-dom": "5.3.4",
|
||||
"react-stately": "3.35.0",
|
||||
"redux": "4.2.1",
|
||||
"reselect": "4.1.8",
|
||||
"rxjs": "7.8.1",
|
||||
"sharp": "0.33.5",
|
||||
"sockjs-client": "1.6.1",
|
||||
"style-dictionary": "4.3.3",
|
||||
"svgo": "3.3.2",
|
||||
"tua-body-scroll-lock": "1.5.3",
|
||||
"typescript": "5.4.2",
|
||||
"universal-cookie": "4.0.4",
|
||||
"url-parse": "1.5.3",
|
||||
"uuid": "8.3.2"
|
||||
},
|
||||
"packageManager": "yarn@4.6.0",
|
||||
"resolutions": {
|
||||
"@babel/core": "<8",
|
||||
"@babel/preset-typescript": "<8",
|
||||
"@babel/register": "<8",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "patch:react-dom@18.2.0#.yarn/patches/react-dom.patch::locator=demo-app%40workspace%3A.",
|
||||
"@types/react": "18.2.60",
|
||||
"@types/react-router": "patch:@types/react-router@5.1.20#.yarn/patches/types-react-router.patch",
|
||||
"sass": "1.58.3",
|
||||
"@vitejs/plugin-react@4.0.1": "patch:@vitejs/plugin-react@npm%3A4.0.1#.yarn/patches/@vitejs-plugin-react-npm-4.0.1-13fe9aab7e.patch",
|
||||
"storybook-design-token@^3.1.0": "patch:storybook-design-token@npm%3A3.1.0#.yarn/patches/storybook-design-token-npm-3.1.0-5e1cc47002.patch"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,286 @@
|
||||
{
|
||||
"name": "test",
|
||||
"dependencies": {
|
||||
"@date-io/date-fns": "^1.3.11",
|
||||
"@emotion/cache": "^11.11.0",
|
||||
"@emotion/react": "^11.11.4",
|
||||
"@emotion/server": "^11.11.0",
|
||||
"@emotion/styled": "^11.11.5",
|
||||
"@formatjs/intl-getcanonicallocales": "^2.5.4",
|
||||
"@jscutlery/semver": "5.2.2",
|
||||
"@material-ui/core": "^4.12.4",
|
||||
"@material-ui/icons": "^4.11.3",
|
||||
"@material-ui/lab": "4.0.0-alpha.61",
|
||||
"@module-federation/nextjs-mf": "8.1.10",
|
||||
"@nx/devkit": "20.3.1",
|
||||
"@nx/plugin": "20.3.1",
|
||||
"@popperjs/core": "^2.9.3",
|
||||
"@rematch/core": "^2.2.0",
|
||||
"@rematch/select": "^3.1.2",
|
||||
"@stomp/stompjs": "4.0.8",
|
||||
"@storybook/addon-interactions": "^8.4.5",
|
||||
"@tokens-studio/sd-transforms": "^1.2.6",
|
||||
"akamai-http-api": "^0.6.0",
|
||||
"bitset": "^5.1.1",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^1.2.1",
|
||||
"cors": "~2.8.5",
|
||||
"date-fns": "^2.14.0",
|
||||
"date-fns-tz": "^2.0.0",
|
||||
"debug": "^4.3.4",
|
||||
"email-validator": "^2.0.4",
|
||||
"final-form": "4.20.2",
|
||||
"get-scrollbar-width": "^1.0.5",
|
||||
"hoist-non-react-statics": "^3.3.2",
|
||||
"into-stream": "^5.1.1",
|
||||
"isomorphic-fetch": "^3.0.0",
|
||||
"jssha": "^3.3.1",
|
||||
"jwt-decode": "^3.1.2",
|
||||
"libphonenumber-js": "^1.10.39",
|
||||
"lodash": "^4.17.21",
|
||||
"next": "14.2.3",
|
||||
"next-redux-wrapper": "^8.1.0",
|
||||
"normalize.css": "^8.0.1",
|
||||
"object-hash": "^2.2.0",
|
||||
"openapi-fetch": "^0.9.7",
|
||||
"path-to-regexp": "^6.2.1",
|
||||
"playwright": "^1.40.0",
|
||||
"prettier": "^2.6.2",
|
||||
"prom-client": "^15.1.0",
|
||||
"react": "18.2.0",
|
||||
"react-aria": "^3.35.1",
|
||||
"react-aria-components": "^1.4.1",
|
||||
"react-body-classname": "^1.3.1",
|
||||
"react-dom": "18.2.0",
|
||||
"react-dropzone": "^14.3.5",
|
||||
"react-final-form": "6.5.9",
|
||||
"react-flatpickr": "^3.10.7",
|
||||
"react-ga": "^3.3.0",
|
||||
"react-intersection-observer": "^9.13.1",
|
||||
"react-intl": "^7.0.4",
|
||||
"react-popper": "^2.2.5",
|
||||
"react-redux": "^9.1.0",
|
||||
"react-router": "^5.3.4",
|
||||
"react-router-dom": "^5.3.4",
|
||||
"redux": "^4.2.1",
|
||||
"rimraf": "6.0.1",
|
||||
"rxjs": "^7.8.1",
|
||||
"semver": "7.5.3",
|
||||
"sockjs-client": "1.6.1",
|
||||
"style-dictionary": "^4.0.1",
|
||||
"style-dictionary-utils": "^3.1.1",
|
||||
"svgo": "^3.3.2",
|
||||
"tslib": "^2.3.0",
|
||||
"tua-body-scroll-lock": "^1.2.1",
|
||||
"universal-cookie": "^4.0.4",
|
||||
"url-parse": "1.5.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@apollo/client": "^3.10.8",
|
||||
"@babel/core": "^7.22.6",
|
||||
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
||||
"@babel/plugin-proposal-private-methods": "^7.18.6",
|
||||
"@babel/preset-react": "^7.18.6",
|
||||
"@babel/preset-typescript": "7.12.13",
|
||||
"@babel/types": "^7.25.0",
|
||||
"@badeball/cypress-cucumber-preprocessor": "^20.0.4",
|
||||
"@bahmutov/cypress-esbuild-preprocessor": "^2.2.0",
|
||||
"@chromatic-com/storybook": "^3.2.2",
|
||||
"@commitlint/cli": "^17.6.3",
|
||||
"@commitlint/config-conventional": "^17.6.3",
|
||||
"@nx-tools/container-metadata": "^6.0.1",
|
||||
"@nx-tools/nx-container": "6.0.1",
|
||||
"@nx/cypress": "20.3.1",
|
||||
"@nx/eslint": "20.3.1",
|
||||
"@nx/eslint-plugin": "20.3.1",
|
||||
"@nx/jest": "20.3.1",
|
||||
"@nx/js": "20.3.1",
|
||||
"@nx/next": "20.3.1",
|
||||
"@nx/playwright": "20.3.1",
|
||||
"@nx/react": "20.3.1",
|
||||
"@nx/storybook": "20.3.1",
|
||||
"@nx/vite": "20.3.1",
|
||||
"@nx/web": "20.3.1",
|
||||
"@playwright/test": "^1.36.0",
|
||||
"@rollup/plugin-typescript": "^11.1.6",
|
||||
"@sentry/tracing": "^7.120.3",
|
||||
"@storybook/addon-a11y": "^8.4.5",
|
||||
"@storybook/addon-designs": "^8.0.4",
|
||||
"@storybook/addon-essentials": "^8.4.5",
|
||||
"@storybook/addon-interactions": "^8.4.5",
|
||||
"@storybook/addon-jest": "^8.4.5",
|
||||
"@storybook/addon-storysource": "^8.4.5",
|
||||
"@storybook/core-common": "^8.4.5",
|
||||
"@storybook/core-server": "^8.4.5",
|
||||
"@storybook/manager-api": "^8.4.5",
|
||||
"@storybook/nextjs": "^8.4.5",
|
||||
"@storybook/react": "^8.4.5",
|
||||
"@storybook/test-runner": "^0.21.0",
|
||||
"@storybook/theming": "^8.4.5",
|
||||
"@svgr/webpack": "8.0.1",
|
||||
"@swc-node/register": "1.9.2",
|
||||
"@swc/cli": "0.3.12",
|
||||
"@swc/core": "1.5.7",
|
||||
"@swc/helpers": "0.5.12",
|
||||
"@testing-library/dom": "^8.20.0",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "15.0.6",
|
||||
"@testing-library/user-event": "^14.5.2",
|
||||
"@tokens-studio/sd-transforms": "^1.2.6",
|
||||
"@total-typescript/shoehorn": "^0.1.1",
|
||||
"@total-typescript/ts-reset": "^0.5.1",
|
||||
"@types/cors": "~2.8.12",
|
||||
"@types/debounce-promise": "^3.1.4",
|
||||
"@types/debug": "^4.1.10",
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/jest-image-snapshot": "^6.1.0",
|
||||
"@types/json-schema": "^7.0.12",
|
||||
"@types/loader-utils": "^2.0.4",
|
||||
"@types/node": "^20.16.1",
|
||||
"@types/node-fetch": "^2.6.11",
|
||||
"@types/object-hash": "2.1.1",
|
||||
"@types/pixl-xml": "^1.0.5",
|
||||
"@types/react": "^18.2.60",
|
||||
"@types/react-body-classname": "^1.1.6",
|
||||
"@types/react-dom": "18.2.9",
|
||||
"@types/react-flatpickr": "^3.8.11",
|
||||
"@types/react-router": "^5.1.20",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@types/redux-mock-store": "^1.0.3",
|
||||
"@types/sockjs-client": "^1.5.3",
|
||||
"@types/swagger-schema-official": "^2.0.25",
|
||||
"@types/url-parse": "1.4.4",
|
||||
"@types/webpack-env": "^1.18.5",
|
||||
"@typescript-eslint/eslint-plugin": "^8.15.0",
|
||||
"@typescript-eslint/parser": "^8.15.0",
|
||||
"@vitejs/plugin-react": "4.2.1",
|
||||
"@vitest/coverage-c8": "^0.33.0",
|
||||
"@vitest/coverage-v8": "^1.5.2",
|
||||
"@vitest/ui": "0.34.7",
|
||||
"babel-jest": "29.7.0",
|
||||
"babel-loader": "8.1.0",
|
||||
"bats": "^1.11.0",
|
||||
"browserslist": "^4.23.0",
|
||||
"commitlint": "^17.6.3",
|
||||
"concurrently": "^8.2.2",
|
||||
"css-loader": "^6.8.1",
|
||||
"cypress": "13.13.2",
|
||||
"debounce-promise": "^3.1.2",
|
||||
"dotenv": "^16.4.5",
|
||||
"dts-css-modules-loader": "^2.0.1",
|
||||
"env-cmd": "^10.1.0",
|
||||
"esbuild": "^0.20.1",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-config-next": "14.2.3",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-check-file": "^2.8.0",
|
||||
"eslint-plugin-cypress": "4.1.0",
|
||||
"eslint-plugin-formatjs": "^4.9.1",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-jest-dom": "^5.4.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.10.2",
|
||||
"eslint-plugin-playwright": "^0.15.3",
|
||||
"eslint-plugin-prefer-arrow": "^1.2.3",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-react": "^7.32.2",
|
||||
"eslint-plugin-react-hooks": "^5.0.0",
|
||||
"eslint-plugin-restrict-imports": "^0.0.3",
|
||||
"eslint-plugin-sort-keys-fix": "^1.1.2",
|
||||
"eslint-plugin-storybook": "^0.11.1",
|
||||
"eslint-plugin-testing-library": "^6.4.0",
|
||||
"eslint-plugin-typescript-sort-keys": "^3.3.0",
|
||||
"git-notify": "^0.2.3",
|
||||
"glob": "^8.0.3",
|
||||
"glob-promise": "^6.0.5",
|
||||
"html-entities": "^2.4.0",
|
||||
"html-loader": "^4.2.0",
|
||||
"husky": "^8.0.3",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"ignore": "^5.2.4",
|
||||
"jest": "29.7.0",
|
||||
"jest-axe": "^7.0.0",
|
||||
"jest-diff": "^29.5.0",
|
||||
"jest-environment-jsdom": "29.7.0",
|
||||
"jest-environment-node": "^29.4.1",
|
||||
"jest-fetch-mock": "^3.0.3",
|
||||
"jest-image-snapshot": "^6.1.0",
|
||||
"jest-localstorage-mock": "^2.4.26",
|
||||
"jest-runner-eslint": "^1.1.0",
|
||||
"jest-runner-stylelint": "^2.3.7",
|
||||
"jest-specific-snapshot": "^7.0.0",
|
||||
"jest-watch-select-projects": "^2.0.0",
|
||||
"jest-watch-typeahead": "^2.2.2",
|
||||
"jsdom": "22.1.0",
|
||||
"json-schema-to-typescript": "^13.0.2",
|
||||
"loader-utils": "^3.2.1",
|
||||
"mini-css-extract-plugin": "^2.7.5",
|
||||
"msw": "^2.7.0",
|
||||
"node-polyfill-webpack-plugin": "^2.0.1",
|
||||
"nx": "20.3.1",
|
||||
"openapi-typescript": "^6.7.6",
|
||||
"pixl-xml": "^1.0.13",
|
||||
"postcss": "^8.4.21",
|
||||
"postcss-flexbugs-fixes": "^5.0.2",
|
||||
"postcss-loader": "^7.1.0",
|
||||
"postcss-normalize": "^10.0.1",
|
||||
"postcss-preset-env": "^8.0.1",
|
||||
"postcss-pxtorem": "^6.0.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-app-polyfill": "^3.0.0",
|
||||
"react-dev-utils": "^12.0.1",
|
||||
"react-modal": "^3.16.1",
|
||||
"redux-mock-store": "^1.5.4",
|
||||
"resolve-url-loader": "^5.0.0",
|
||||
"rollup": "^4.18.1",
|
||||
"sass": "1.62.1",
|
||||
"sass-loader": "^13.2.1",
|
||||
"slugify": "^1.6.5",
|
||||
"storybook": "^8.4.5",
|
||||
"storybook-dark-mode": "^4.0.2",
|
||||
"storybook-design-token": "^3.1.0",
|
||||
"stylelint": "^15.3.0",
|
||||
"stylelint-config-concentric-order": "^5.1.0",
|
||||
"stylelint-config-sass-guidelines": "^10.0.0",
|
||||
"stylelint-config-standard-scss": "^7.0.1",
|
||||
"stylelint-prettier": "^3.0.0",
|
||||
"stylelint-scss": "^4.6.0",
|
||||
"swagger-typescript-api": "^13.0.3",
|
||||
"text-encoding": "^0.7.0",
|
||||
"ts-jest": "^29.1.0",
|
||||
"ts-morph": "^24.0.0",
|
||||
"ts-node": "10.9.1",
|
||||
"tsconfig-paths-webpack-plugin": "^4.0.1",
|
||||
"typescript": "5.4.2",
|
||||
"typescript-parser": "^2.6.1",
|
||||
"url-loader": "^4.1.1",
|
||||
"utility-types": "^3.10.0",
|
||||
"verdaccio": "^5.0.4",
|
||||
"vite": "5.4.2",
|
||||
"vite-plugin-dts": "^4.0.3",
|
||||
"vite-plugin-eslint": "^1.8.1",
|
||||
"vite-plugin-externalize-deps": "^0.7.0",
|
||||
"vite-plugin-sass-dts": "1.3.21",
|
||||
"vite-plugin-static-copy": "^1.0.0",
|
||||
"vite-plugin-svgr": "^4.2.0",
|
||||
"vite-tsconfig-paths": "~4.2.0",
|
||||
"vitest": "1.3.1",
|
||||
"wait-on": "^7.2.0",
|
||||
"webpack": "^5.90.3"
|
||||
},
|
||||
"packageManager": "yarn@4.6.0",
|
||||
"resolutions": {
|
||||
"@babel/core": "<8",
|
||||
"@babel/preset-typescript": "<8",
|
||||
"@babel/register": "<8",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "patch:react-dom@18.2.0#.yarn/patches/react-dom.patch",
|
||||
"@types/react": "18.2.60",
|
||||
"@types/react-router": "patch:@types/react-router@5.1.20#.yarn/patches/types-react-router.patch",
|
||||
"sass": "1.58.3",
|
||||
"@vitejs/plugin-react@4.0.1": "patch:@vitejs/plugin-react@npm%3A4.0.1#.yarn/patches/@vitejs-plugin-react-npm-4.0.1-13fe9aab7e.patch",
|
||||
"@snowplow/webview-tracker@^0.2.1": "patch:@snowplow/webview-tracker@npm%3A0.2.1#.yarn/patches/@snowplow-webview-tracker-npm-0.2.1-9d395f3487.patch",
|
||||
"storybook-design-token@^3.1.0": "patch:storybook-design-token@npm%3A3.1.0#.yarn/patches/storybook-design-token-npm-3.1.0-5e1cc47002.patch"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -26,6 +26,7 @@ export type NormalizedPackageJson = Pick<
|
||||
| 'peerDependenciesMeta'
|
||||
| 'optionalDependencies'
|
||||
| 'packageManager'
|
||||
| 'resolutions'
|
||||
>;
|
||||
|
||||
/**
|
||||
@ -44,6 +45,7 @@ export function normalizePackageJson(
|
||||
peerDependenciesMeta,
|
||||
optionalDependencies,
|
||||
packageManager,
|
||||
resolutions,
|
||||
} = packageJson;
|
||||
|
||||
return {
|
||||
@ -56,5 +58,6 @@ export function normalizePackageJson(
|
||||
peerDependenciesMeta,
|
||||
optionalDependencies,
|
||||
packageManager,
|
||||
resolutions,
|
||||
};
|
||||
}
|
||||
|
||||
@ -2923,6 +2923,98 @@ __metadata:
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolutions and patches', () => {
|
||||
it('should parse yarn.lock with resolutions and patches', () => {
|
||||
const lockFile = require(joinPathFragments(
|
||||
__dirname,
|
||||
'__fixtures__/resolutions-and-patches/yarn.lock'
|
||||
)).default;
|
||||
const packageJson = require(joinPathFragments(
|
||||
__dirname,
|
||||
'__fixtures__/resolutions-and-patches/package.json'
|
||||
));
|
||||
const appLockFile = require(joinPathFragments(
|
||||
__dirname,
|
||||
'__fixtures__/resolutions-and-patches/app/yarn.lock'
|
||||
)).default;
|
||||
const appPackageJson = require(joinPathFragments(
|
||||
__dirname,
|
||||
'__fixtures__/resolutions-and-patches/app/package.json'
|
||||
));
|
||||
|
||||
const hash = uniq('mock-hash');
|
||||
const externalNodes = getYarnLockfileNodes(lockFile, hash, packageJson);
|
||||
const pg = {
|
||||
nodes: {},
|
||||
dependencies: {},
|
||||
externalNodes,
|
||||
};
|
||||
const ctx: CreateDependenciesContext = {
|
||||
projects: {},
|
||||
externalNodes,
|
||||
fileMap: {
|
||||
nonProjectFiles: [],
|
||||
projectFileMap: {},
|
||||
},
|
||||
filesToProcess: {
|
||||
nonProjectFiles: [],
|
||||
projectFileMap: {},
|
||||
},
|
||||
nxJsonConfiguration: null,
|
||||
workspaceRoot: '/virtual',
|
||||
};
|
||||
const dependencies = getYarnLockfileDependencies(lockFile, hash, ctx);
|
||||
const builder = new ProjectGraphBuilder(pg);
|
||||
for (const dep of dependencies) {
|
||||
builder.addDependency(
|
||||
dep.source,
|
||||
dep.target,
|
||||
dep.type,
|
||||
'sourceFile' in dep ? dep.sourceFile : null
|
||||
);
|
||||
}
|
||||
const graph = builder.getUpdatedProjectGraph();
|
||||
const prunedGraph = pruneProjectGraph(graph, appPackageJson);
|
||||
// @types/react is only used as peer dependency with `*` or `16 | 17 | 18` as a range
|
||||
// so we should check if parsing version ranges works correctly
|
||||
expect(prunedGraph.externalNodes['npm:@types/react'])
|
||||
.toMatchInlineSnapshot(`
|
||||
{
|
||||
"data": {
|
||||
"hash": "10/5f2f6091623f13375a5bbc7e5c222cd212b5d6366ead737b76c853f6f52b314db24af5ae3f688d2d49814c668c216858a75433f145311839d8989d46bb3cbecf",
|
||||
"packageName": "@types/react",
|
||||
"version": "18.2.60",
|
||||
},
|
||||
"name": "npm:@types/react",
|
||||
"type": "npm",
|
||||
}
|
||||
`);
|
||||
// react-dom has a patch, so this check helps us to see if patch has been properly parsed
|
||||
expect(prunedGraph.externalNodes['npm:react-dom']).toMatchInlineSnapshot(`
|
||||
{
|
||||
"data": {
|
||||
"hash": "10/ca5e7762ec8c17a472a3605b6f111895c9f87ac7d43a610ab7024f68cd833d08eda0625ce02ec7178cc1f3c957cf0b9273cdc17aa2cd02da87544331c43b1d21",
|
||||
"packageName": "react-dom",
|
||||
"version": "18.2.0",
|
||||
},
|
||||
"name": "npm:react-dom",
|
||||
"type": "npm",
|
||||
}
|
||||
`);
|
||||
const result = stringifyYarnLockfile(
|
||||
prunedGraph,
|
||||
lockFile,
|
||||
appPackageJson
|
||||
);
|
||||
// resulting lockfile should contain flexible ranges and patches with modified paths applied
|
||||
expect(result).toEqual(appLockFile);
|
||||
expect(result).toContain('"@types/react@npm:18.2.60"');
|
||||
expect(result).toContain(
|
||||
'"react-dom@patch:react-dom@18.2.0#.yarn/patches/react-dom.patch::locator=demo-app%40workspace%3A."'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function uniq(str: string) {
|
||||
|
||||
@ -306,9 +306,30 @@ function getDependencies(
|
||||
(section) => {
|
||||
if (section) {
|
||||
Object.entries(section).forEach(([name, versionRange]) => {
|
||||
const target =
|
||||
let target =
|
||||
keyMap.get(`${name}@npm:${versionRange}`) ||
|
||||
keyMap.get(`${name}@${versionRange}`);
|
||||
if (!target) {
|
||||
const shortRange = versionRange.replace(/^npm:/, '');
|
||||
// for range like 'npm:*' the above will not be a match
|
||||
if (shortRange === '*') {
|
||||
const foundKey = Array.from(keyMap.keys()).find((k) =>
|
||||
k.startsWith(`${name}@`)
|
||||
);
|
||||
if (foundKey) {
|
||||
target = keyMap.get(foundKey);
|
||||
}
|
||||
} else if (shortRange.includes('||')) {
|
||||
// when range is a union of ranges, we need to treat it as an array
|
||||
const ranges = shortRange.split('||').map((r) => r.trim());
|
||||
target = Object.values(keyMap).find((n) => {
|
||||
return (
|
||||
n.data.packageName === name &&
|
||||
ranges.some((r) => satisfies(n.data.version, r))
|
||||
);
|
||||
})?.[1];
|
||||
}
|
||||
}
|
||||
if (target) {
|
||||
const dep: RawProjectGraphDependency = {
|
||||
source: node.name,
|
||||
@ -400,13 +421,13 @@ function addPackageVersion(
|
||||
collection.set(packageName, new Set());
|
||||
}
|
||||
collection.get(packageName).add(`${packageName}@${version}`);
|
||||
if (isBerry && !version.startsWith('npm:')) {
|
||||
if (isBerry && !version.startsWith('npm:') && !version.startsWith('patch:')) {
|
||||
collection.get(packageName).add(`${packageName}@npm:${version}`);
|
||||
}
|
||||
}
|
||||
|
||||
function mapSnapshots(
|
||||
dependencies: Record<string, YarnDependency>,
|
||||
rootDependencies: Record<string, YarnDependency>,
|
||||
nodes: Record<string, ProjectGraphExternalNode>,
|
||||
packageJson: NormalizedPackageJson,
|
||||
isBerry: boolean
|
||||
@ -421,9 +442,12 @@ function mapSnapshots(
|
||||
...packageJson.optionalDependencies,
|
||||
...packageJson.peerDependencies,
|
||||
};
|
||||
const resolutions = {
|
||||
...packageJson.resolutions,
|
||||
};
|
||||
|
||||
// yarn classic splits keys when parsing so we need to stich them back together
|
||||
const groupedDependencies = groupDependencies(dependencies, isBerry);
|
||||
const groupedDependencies = groupDependencies(rootDependencies, isBerry);
|
||||
|
||||
// collect snapshots and their matching keys
|
||||
Object.values(nodes).forEach((node) => {
|
||||
@ -461,10 +485,32 @@ function mapSnapshots(
|
||||
snapshotMap.get(snapshot).add(requestedKey);
|
||||
}
|
||||
}
|
||||
const requestedResolutionsVersion = getPackageJsonVersion(
|
||||
resolutions,
|
||||
node
|
||||
);
|
||||
if (requestedResolutionsVersion) {
|
||||
addPackageVersion(
|
||||
node.data.packageName,
|
||||
requestedResolutionsVersion,
|
||||
existingKeys,
|
||||
isBerry
|
||||
);
|
||||
const requestedKey = isBerry
|
||||
? reverseMapBerryKey(node, requestedResolutionsVersion, snapshot)
|
||||
: `${node.data.packageName}@${requestedResolutionsVersion}`;
|
||||
if (!snapshotMap.get(snapshot).has(requestedKey)) {
|
||||
snapshotMap.get(snapshot).add(requestedKey);
|
||||
}
|
||||
}
|
||||
|
||||
if (isBerry) {
|
||||
// look for patched versions
|
||||
const patch = findPatchedKeys(groupedDependencies, node);
|
||||
const patch = findPatchedKeys(
|
||||
groupedDependencies,
|
||||
node,
|
||||
resolutions[node.data.packageName]
|
||||
);
|
||||
if (patch) {
|
||||
const [matchedKeys, snapshot] = patch;
|
||||
snapshotMap.set(snapshot, new Set(matchedKeys));
|
||||
@ -473,7 +519,7 @@ function mapSnapshots(
|
||||
});
|
||||
|
||||
// remove keys that match version ranges that have been pruned away
|
||||
snapshotMap.forEach((snapshotValue, snapshotKey) => {
|
||||
snapshotMap.forEach((snapshotValue, snapshot) => {
|
||||
for (const key of snapshotValue.values()) {
|
||||
const packageName = key.slice(0, key.indexOf('@', 1));
|
||||
let normalizedKey = key;
|
||||
@ -513,8 +559,8 @@ function reverseMapBerryKey(
|
||||
snapshot: YarnDependency
|
||||
): string {
|
||||
// alias packages already have version
|
||||
if (version.startsWith('npm:')) {
|
||||
`${node.data.packageName}@${version}`;
|
||||
if (version.startsWith('npm:') || version.startsWith('patch:')) {
|
||||
return `${node.data.packageName}@${version}`;
|
||||
}
|
||||
// check for berry tarball packages
|
||||
if (
|
||||
@ -528,21 +574,41 @@ function reverseMapBerryKey(
|
||||
}
|
||||
|
||||
function getPackageJsonVersion(
|
||||
combinedDependencies: Record<string, string>,
|
||||
dependencies: Record<string, string>,
|
||||
node: ProjectGraphExternalNode
|
||||
): string {
|
||||
const { packageName, version } = node.data;
|
||||
|
||||
if (combinedDependencies[packageName]) {
|
||||
if (
|
||||
combinedDependencies[packageName] === version ||
|
||||
satisfies(version, combinedDependencies[packageName])
|
||||
) {
|
||||
return combinedDependencies[packageName];
|
||||
if (dependencies[packageName]) {
|
||||
const patchRegex = new RegExp(`^patch:${packageName}@(.*)|#.*$`);
|
||||
// extract the version from the patch or use the full version
|
||||
const versionRange =
|
||||
dependencies[packageName].match(patchRegex)?.[1] ||
|
||||
dependencies[packageName];
|
||||
if (versionRange === version || satisfies(version, versionRange)) {
|
||||
return dependencies[packageName];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isStandardPackage(snapshot: YarnDependency, version: string): boolean {
|
||||
return snapshot.version === version;
|
||||
}
|
||||
|
||||
function isBerryAlias(snapshot: YarnDependency, version: string): boolean {
|
||||
return snapshot.resolution && `npm:${snapshot.resolution}` === version;
|
||||
}
|
||||
|
||||
function isClassicAlias(
|
||||
node: ProjectGraphExternalNode,
|
||||
keys: string[]
|
||||
): boolean {
|
||||
return (
|
||||
node.data.version.startsWith('npm:') &&
|
||||
keys.some((k) => k === `${node.data.packageName}@${node.data.version}`)
|
||||
);
|
||||
}
|
||||
|
||||
function findOriginalKeys(
|
||||
dependencies: Record<string, YarnDependency>,
|
||||
node: ProjectGraphExternalNode
|
||||
@ -553,21 +619,10 @@ function findOriginalKeys(
|
||||
if (!keys.some((k) => k.startsWith(`${node.data.packageName}@`))) {
|
||||
continue;
|
||||
}
|
||||
// standard package
|
||||
if (snapshot.version === node.data.version) {
|
||||
return [keys, snapshot];
|
||||
}
|
||||
// berry alias package
|
||||
if (
|
||||
snapshot.resolution &&
|
||||
`npm:${snapshot.resolution}` === node.data.version
|
||||
) {
|
||||
return [keys, snapshot];
|
||||
}
|
||||
// classic alias
|
||||
if (
|
||||
node.data.version.startsWith('npm:') &&
|
||||
keys.some((k) => k === `${node.data.packageName}@${node.data.version}`)
|
||||
isStandardPackage(snapshot, node.data.version) ||
|
||||
isBerryAlias(snapshot, node.data.version) ||
|
||||
isClassicAlias(node, keys)
|
||||
) {
|
||||
return [keys, snapshot];
|
||||
}
|
||||
@ -583,7 +638,8 @@ function findOriginalKeys(
|
||||
|
||||
function findPatchedKeys(
|
||||
dependencies: Record<string, YarnDependency>,
|
||||
node: ProjectGraphExternalNode
|
||||
node: ProjectGraphExternalNode,
|
||||
resolutionVersion: string
|
||||
): [string[], YarnDependency] | void {
|
||||
for (const keyExpr of Object.keys(dependencies)) {
|
||||
const snapshot = dependencies[keyExpr];
|
||||
@ -591,10 +647,20 @@ function findPatchedKeys(
|
||||
if (!keys[0].startsWith(`${node.data.packageName}@patch:`)) {
|
||||
continue;
|
||||
}
|
||||
// local patches are currently not supported
|
||||
if (keys[0].includes('.yarn/patches')) {
|
||||
if (keyExpr.includes('.yarn/patches')) {
|
||||
if (!resolutionVersion) {
|
||||
continue;
|
||||
}
|
||||
const key = `${node.data.packageName}@${resolutionVersion}`;
|
||||
// local patches can have different location from than the root lock file
|
||||
// use the one from local package.json as the source of truth as long as the rest of the patch matches
|
||||
// this obviously doesn't cover the case of patch over a patch, but that's a super rare case and one can argue one can just join those two patches
|
||||
if (key.split('::locator')[0] !== keyExpr.split('::locator')[0]) {
|
||||
continue;
|
||||
} else {
|
||||
return [[key], { ...snapshot, resolution: key }];
|
||||
}
|
||||
}
|
||||
if (snapshot.version === node.data.version) {
|
||||
return [keys, snapshot];
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user