feat(core): add support for pnpm lockfile v9 (#22906)

- [X] Fix parenthesis separator detection
- [x] Fix leading dash detection
- [x] Migrate existing pnpm normalizer to latest code
- [X] Add unit tests for v9
- [X] Dogfooding Pnpm v9 to Nx repo and agents

Fixes regression with alias packages introduced via
https://github.com/nrwl/nx/pull/23017

## Benchmarks

PNPM v9 Branch (migrated to branch's code)
```
  Time (mean ± σ):      3.526 s ±  0.081 s    [User: 0.717 s, System: 0.948 s]
  Range (min … max):    3.390 s …  3.714 s    20 runs
```
Master (running nx 19.1.0-beta.3)
```
  Time (mean ± σ):     11.160 s ±  0.112 s    [User: 0.799 s, System: 0.979 s]
  Range (min … max):   10.955 s … 11.379 s    20 runs
```

## 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 #22850
Fixes #23256

---------

Co-authored-by: James Henry <james@henry.sc>
This commit is contained in:
Miroslav Jonaš 2024-05-27 22:12:18 +02:00 committed by GitHub
parent c9f3c05ac9
commit 8403f03822
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 23076 additions and 572 deletions

View File

@ -39,7 +39,7 @@ jobs:
- name: Install PNPM - name: Install PNPM
run: | run: |
npm install -g @pnpm/exe@8.14 npm install -g @pnpm/exe@8
- name: Set node - name: Set node
uses: actions/setup-node@v3 uses: actions/setup-node@v3
@ -285,7 +285,7 @@ jobs:
- name: Install PNPM - name: Install PNPM
run: | run: |
npm install -g @pnpm/exe@8.7.4 npm install -g @pnpm/exe@8
- name: Use Node.js ${{ matrix.node_version }} - name: Use Node.js ${{ matrix.node_version }}
uses: actions/setup-node@v3 uses: actions/setup-node@v3

View File

@ -25,7 +25,7 @@ jobs:
uses: pnpm/action-setup@v2 uses: pnpm/action-setup@v2
id: pnpm-install id: pnpm-install
with: with:
version: 7 version: 8
run_install: false run_install: false
- name: Get pnpm store directory - name: Get pnpm store directory

View File

@ -20,7 +20,7 @@ jobs:
- uses: pnpm/action-setup@v2 - uses: pnpm/action-setup@v2
with: with:
version: 8.2 version: 8
- name: Use Node.js ${{ matrix.node_version }} - name: Use Node.js ${{ matrix.node_version }}
uses: actions/setup-node@v3 uses: actions/setup-node@v3

View File

@ -19,7 +19,7 @@ jobs:
- name: Install PNPM - name: Install PNPM
run: | run: |
npm install -g @pnpm/exe@8.3.1 npm install -g @pnpm/exe@8
- name: Run a security audit - name: Run a security audit
run: pnpm dlx audit-ci --critical --report-type summary run: pnpm dlx audit-ci --critical --report-type summary

View File

@ -28,7 +28,7 @@ launch-templates:
sudo apt-get install -y ca-certificates lsof sudo apt-get install -y ca-certificates lsof
- name: Install Pnpm - name: Install Pnpm
script: | script: |
npm install -g pnpm@8.15.5 npm install -g pnpm@8
- name: Pnpm Install - name: Pnpm Install
script: | script: |

View File

@ -82,7 +82,7 @@
"@phenomnomnominal/tsquery": "~5.0.1", "@phenomnomnominal/tsquery": "~5.0.1",
"@playwright/test": "^1.36.1", "@playwright/test": "^1.36.1",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.7", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.7",
"@pnpm/lockfile-types": "^5.0.0", "@pnpm/lockfile-types": "^6.0.0",
"@reduxjs/toolkit": "1.9.0", "@reduxjs/toolkit": "1.9.0",
"@remix-run/dev": "^2.8.1", "@remix-run/dev": "^2.8.1",
"@remix-run/node": "^2.8.1", "@remix-run/node": "^2.8.1",

View File

@ -3,7 +3,7 @@ export default `lockfileVersion: 5.4
specifiers: specifiers:
'@nrwl/devkit': 15.0.13 '@nrwl/devkit': 15.0.13
eslint-plugin-disable-autofix: npm:@mattlewis92/eslint-plugin-disable-autofix@3.0.0 eslint-plugin-disable-autofix: npm:@mattlewis92/eslint-plugin-disable-autofix@3.0.0
postgres: github.com/charsleysa/postgres/3b1a01b2da3e2fafb1a79006f838eff11a8de3cb postgres: https://codeload.github.com/charsleysa/postgres/tar.gz/3b1a01b2da3e2fafb1a79006f838eff11a8de3cb
react: 18.2.0 react: 18.2.0
typescript: 4.8.4 typescript: 4.8.4
yargs: 17.6.2 yargs: 17.6.2
@ -11,7 +11,7 @@ specifiers:
dependencies: dependencies:
'@nrwl/devkit': 15.0.13_nx@15.3.0+typescript@4.8.4 '@nrwl/devkit': 15.0.13_nx@15.3.0+typescript@4.8.4
eslint-plugin-disable-autofix: /@mattlewis92/eslint-plugin-disable-autofix/3.0.0 eslint-plugin-disable-autofix: /@mattlewis92/eslint-plugin-disable-autofix/3.0.0
postgres: github.com/charsleysa/postgres/3b1a01b2da3e2fafb1a79006f838eff11a8de3cb postgres: https://codeload.github.com/charsleysa/postgres/tar.gz/3b1a01b2da3e2fafb1a79006f838eff11a8de3cb
typescript: 4.8.4 typescript: 4.8.4
yargs: 17.6.2 yargs: 17.6.2
@ -1469,7 +1469,7 @@ packages:
engines: {node: '>=10'} engines: {node: '>=10'}
dev: false dev: false
github.com/charsleysa/postgres/3b1a01b2da3e2fafb1a79006f838eff11a8de3cb: https://codeload.github.com/charsleysa/postgres/tar.gz/3b1a01b2da3e2fafb1a79006f838eff11a8de3cb:
resolution: {tarball: https://codeload.github.com/charsleysa/postgres/tar.gz/3b1a01b2da3e2fafb1a79006f838eff11a8de3cb} resolution: {tarball: https://codeload.github.com/charsleysa/postgres/tar.gz/3b1a01b2da3e2fafb1a79006f838eff11a8de3cb}
name: postgres name: postgres
prepare: true prepare: true

View File

@ -0,0 +1,160 @@
export default `lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
dependencies:
'@isaacs/cliui':
specifier: ^8.0.2
version: 8.0.2
cliui:
specifier: ^8.0.1
version: 8.0.1
packages:
'@isaacs/cliui@8.0.2':
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
engines: {node: '>=12'}
ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
ansi-regex@6.0.1:
resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==}
engines: {node: '>=12'}
ansi-styles@4.3.0:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
ansi-styles@6.2.1:
resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
engines: {node: '>=12'}
cliui@8.0.1:
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
engines: {node: '>=12'}
color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
emoji-regex@9.2.2:
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
is-fullwidth-code-point@3.0.0:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
engines: {node: '>=8'}
string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
string-width@5.1.2:
resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
engines: {node: '>=12'}
strip-ansi@6.0.1:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'}
strip-ansi@7.1.0:
resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==}
engines: {node: '>=12'}
wrap-ansi@7.0.0:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
engines: {node: '>=10'}
wrap-ansi@8.1.0:
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
engines: {node: '>=12'}
snapshots:
'@isaacs/cliui@8.0.2':
dependencies:
string-width: 5.1.2
string-width-cjs: string-width@4.2.3
strip-ansi: 7.1.0
strip-ansi-cjs: strip-ansi@6.0.1
wrap-ansi: 8.1.0
wrap-ansi-cjs: wrap-ansi@7.0.0
ansi-regex@5.0.1: {}
ansi-regex@6.0.1: {}
ansi-styles@4.3.0:
dependencies:
color-convert: 2.0.1
ansi-styles@6.2.1: {}
cliui@8.0.1:
dependencies:
string-width: 4.2.3
strip-ansi: 6.0.1
wrap-ansi: 7.0.0
color-convert@2.0.1:
dependencies:
color-name: 1.1.4
color-name@1.1.4: {}
eastasianwidth@0.2.0: {}
emoji-regex@8.0.0: {}
emoji-regex@9.2.2: {}
is-fullwidth-code-point@3.0.0: {}
string-width@4.2.3:
dependencies:
emoji-regex: 8.0.0
is-fullwidth-code-point: 3.0.0
strip-ansi: 6.0.1
string-width@5.1.2:
dependencies:
eastasianwidth: 0.2.0
emoji-regex: 9.2.2
strip-ansi: 7.1.0
strip-ansi@6.0.1:
dependencies:
ansi-regex: 5.0.1
strip-ansi@7.1.0:
dependencies:
ansi-regex: 6.0.1
wrap-ansi@7.0.0:
dependencies:
ansi-styles: 4.3.0
string-width: 4.2.3
strip-ansi: 6.0.1
wrap-ansi@8.1.0:
dependencies:
ansi-styles: 6.2.1
string-width: 5.1.2
strip-ansi: 7.1.0
`;

View File

@ -0,0 +1,288 @@
export default `hoistPattern:
- '*'
hoistedDependencies:
/@nodelib/fs.scandir/2.1.5:
'@nodelib/fs.scandir': private
/@nodelib/fs.stat/2.0.5:
'@nodelib/fs.stat': private
/@nodelib/fs.walk/1.2.8:
'@nodelib/fs.walk': private
/@nrwl/devkit/15.4.5_nx@15.4.5+typescript@4.8.4:
'@nrwl/devkit': private
/@nrwl/linter/15.4.5_nx@15.4.5+typescript@4.8.4:
'@nrwl/linter': private
/@nrwl/tao/15.4.5:
'@nrwl/tao': private
/@parcel/watcher/2.0.4:
'@parcel/watcher': private
/@phenomnomnominal/tsquery/4.1.1_typescript@4.8.4:
'@phenomnomnominal/tsquery': private
/@yarnpkg/lockfile/1.1.0:
'@yarnpkg/lockfile': private
/@yarnpkg/parsers/3.0.0-rc.35:
'@yarnpkg/parsers': private
/@zkochan/js-yaml/0.0.6:
'@zkochan/js-yaml': private
/ansi-colors/4.1.3:
ansi-colors: private
/ansi-regex/5.0.1:
ansi-regex: private
/ansi-styles/4.3.0:
ansi-styles: private
/anymatch/3.1.3:
anymatch: private
/argparse/2.0.1:
argparse: private
/async/3.2.4:
async: private
/asynckit/0.4.0:
asynckit: private
/axios/1.2.2:
axios: private
/balanced-match/1.0.2:
balanced-match: private
/base64-js/1.5.1:
base64-js: private
/binary-extensions/2.2.0:
binary-extensions: private
/bl/4.1.0:
bl: private
/brace-expansion/1.1.11:
brace-expansion: private
/braces/3.0.2:
braces: private
/buffer/5.7.1:
buffer: private
/chalk/4.1.0:
chalk: private
/chokidar/3.5.3:
chokidar: private
/cli-cursor/3.1.0:
cli-cursor: private
/cli-spinners/2.6.1:
cli-spinners: private
/cliui/7.0.4:
cliui: private
/color-convert/2.0.1:
color-convert: private
/color-name/1.1.4:
color-name: private
/combined-stream/1.0.8:
combined-stream: private
/concat-map/0.0.1:
concat-map: private
/define-lazy-prop/2.0.0:
define-lazy-prop: private
/delayed-stream/1.0.0:
delayed-stream: private
/dotenv/10.0.0:
dotenv: private
/duplexer/0.1.2:
duplexer: private
/ejs/3.1.8:
ejs: private
/emoji-regex/8.0.0:
emoji-regex: private
/end-of-stream/1.4.4:
end-of-stream: private
/enquirer/2.3.6:
enquirer: private
/escalade/3.1.1:
escalade: private
/escape-string-regexp/1.0.5:
escape-string-regexp: private
/esprima/4.0.1:
esprima: private
/esquery/1.4.0:
esquery: private
/estraverse/5.3.0:
estraverse: private
/fast-glob/3.2.7:
fast-glob: private
/fastq/1.15.0:
fastq: private
/figures/3.2.0:
figures: private
/filelist/1.0.4:
filelist: private
/fill-range/7.0.1:
fill-range: private
/flat/5.0.2:
flat: private
/follow-redirects/1.15.2:
follow-redirects: private
/form-data/4.0.0:
form-data: private
/fs-constants/1.0.0:
fs-constants: private
/fs-extra/10.1.0:
fs-extra: private
/fs.realpath/1.0.0:
fs.realpath: private
/fsevents/2.3.2:
fsevents: private
/get-caller-file/2.0.5:
get-caller-file: private
/glob-parent/5.1.2:
glob-parent: private
/glob/7.1.4:
glob: private
/graceful-fs/4.2.10:
graceful-fs: private
/has-flag/4.0.0:
has-flag: private
/ieee754/1.2.1:
ieee754: private
/ignore/5.2.4:
ignore: private
/inflight/1.0.6:
inflight: private
/inherits/2.0.4:
inherits: private
/is-binary-path/2.1.0:
is-binary-path: private
/is-docker/2.2.1:
is-docker: private
/is-extglob/2.1.1:
is-extglob: private
/is-fullwidth-code-point/3.0.0:
is-fullwidth-code-point: private
/is-glob/4.0.3:
is-glob: private
/is-number/7.0.0:
is-number: private
/is-wsl/2.2.0:
is-wsl: private
/jake/10.8.5:
jake: private
/js-yaml/4.1.0:
js-yaml: private
/json5/2.2.3:
json5: private
/jsonc-parser/3.2.0:
jsonc-parser: private
/jsonfile/6.1.0:
jsonfile: private
/lru-cache/6.0.0:
lru-cache: private
/merge2/1.4.1:
merge2: private
/micromatch/4.0.5:
micromatch: private
/mime-db/1.52.0:
mime-db: private
/mime-types/2.1.35:
mime-types: private
/mimic-fn/2.1.0:
mimic-fn: private
/minimatch/3.0.5:
minimatch: private
/minimist/1.2.7:
minimist: private
/node-addon-api/3.2.1:
node-addon-api: private
/node-gyp-build/4.6.0:
node-gyp-build: private
/normalize-path/3.0.0:
normalize-path: private
/npm-run-path/4.0.1:
npm-run-path: private
/once/1.4.0:
once: private
/onetime/5.1.2:
onetime: private
/open/8.4.0:
open: private
/path-is-absolute/1.0.1:
path-is-absolute: private
/path-key/3.1.1:
path-key: private
/picomatch/2.3.1:
picomatch: private
/proxy-from-env/1.1.0:
proxy-from-env: private
/queue-microtask/1.2.3:
queue-microtask: private
/readable-stream/3.6.0:
readable-stream: private
/readdirp/3.6.0:
readdirp: private
/require-directory/2.1.1:
require-directory: private
/restore-cursor/3.1.0:
restore-cursor: private
/reusify/1.0.4:
reusify: private
/rimraf/3.0.2:
rimraf: private
/run-parallel/1.2.0:
run-parallel: private
/rxjs/6.6.7:
rxjs: private
/safe-buffer/5.2.1:
safe-buffer: private
/semver/7.3.4:
semver: private
/signal-exit/3.0.7:
signal-exit: private
/sprintf-js/1.0.3:
sprintf-js: private
/string_decoder/1.3.0:
string_decoder: private
/strip-ansi/6.0.1:
strip-ansi: private
/strip-bom/3.0.0:
strip-bom: private
/strong-log-transformer/2.1.0:
strong-log-transformer: private
/supports-color/7.2.0:
supports-color: private
/tar-stream/2.2.0:
tar-stream: private
/through/2.3.8:
through: private
/tmp/0.2.1:
tmp: private
/to-regex-range/5.0.1:
to-regex-range: private
/tsconfig-paths/4.1.2:
tsconfig-paths: private
/tslib/2.4.1:
tslib: private
/universalify/2.0.0:
universalify: private
/util-deprecate/1.0.2:
util-deprecate: private
/v8-compile-cache/2.3.0:
v8-compile-cache: private
/wrap-ansi/7.0.0:
wrap-ansi: private
/wrappy/1.0.2:
wrappy: private
/y18n/5.0.8:
y18n: private
/yallist/4.0.0:
yallist: private
/yargs-parser/21.1.1:
yargs-parser: private
/yargs/17.6.2:
yargs: private
included:
dependencies: true
devDependencies: true
optionalDependencies: true
injectedDeps: {}
layoutVersion: 5
nodeLinker: isolated
packageManager: pnpm@7.16.0
pendingBuilds: []
prunedAt: Wed, 11 Jan 2023 18:55:07 GMT
publicHoistPattern:
- '*eslint*'
- '*prettier*'
registries:
default: https://registry.npmjs.org/
skipped: []
storeDir: /Users/jdoe/Library/pnpm/store/v3
virtualStoreDir: .pnpm
`;

View File

@ -0,0 +1,23 @@
{
"name": "@teve/task4s-firebase",
"version": "0.0.1",
"engines": {
"node": "20"
},
"type": "module",
"main": "index.js",
"module": "./index.js",
"private": true,
"dependencies": {
"@azure/msal-node": "2.8.1",
"@google-cloud/functions-framework": "*",
"@microsoft/microsoft-graph-client": "3.0.7",
"date-fns": "3.6.0",
"firebase-admin": "12.1.0",
"firebase-functions": "5.0.1",
"gaxios": "6.6.0",
"google-auth-library": "9.10.0",
"googleapis": "137.1.0",
"lru-cache": "10.2.2"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,25 @@
export default `lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
dependencies:
typescript:
specifier: 4.8.4
version: 4.8.4
packages:
typescript@4.8.4:
resolution: {integrity: sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==}
engines: {node: '>=4.2.0'}
hasBin: true
snapshots:
typescript@4.8.4: {}
`;

View File

@ -412,7 +412,7 @@ describe('pnpm LockFile utility', () => {
"data": { "data": {
"hash": "https://codeload.github.com/charsleysa/postgres/tar.gz/3b1a01b2da3e2fafb1a79006f838eff11a8de3cb", "hash": "https://codeload.github.com/charsleysa/postgres/tar.gz/3b1a01b2da3e2fafb1a79006f838eff11a8de3cb",
"packageName": "postgres", "packageName": "postgres",
"version": "github.com/charsleysa/postgres/3b1a01b2da3e2fafb1a79006f838eff11a8de3cb", "version": "https://codeload.github.com/charsleysa/postgres/tar.gz/3b1a01b2da3e2fafb1a79006f838eff11a8de3cb",
}, },
"name": "npm:postgres", "name": "npm:postgres",
"type": "npm", "type": "npm",
@ -452,7 +452,7 @@ describe('pnpm LockFile utility', () => {
'eslint-plugin-disable-autofix': 'eslint-plugin-disable-autofix':
'npm:@mattlewis92/eslint-plugin-disable-autofix@3.0.0', 'npm:@mattlewis92/eslint-plugin-disable-autofix@3.0.0',
postgres: postgres:
'github.com/charsleysa/postgres/3b1a01b2da3e2fafb1a79006f838eff11a8de3cb', 'https://codeload.github.com/charsleysa/postgres/tar.gz/3b1a01b2da3e2fafb1a79006f838eff11a8de3cb',
yargs: '17.6.2', yargs: '17.6.2',
}, },
devDependencies: { devDependencies: {
@ -573,7 +573,6 @@ describe('pnpm LockFile utility', () => {
); );
} }
graph = builder.getUpdatedProjectGraph(); graph = builder.getUpdatedProjectGraph();
expect(Object.keys(graph.externalNodes).length).toEqual(370); expect(Object.keys(graph.externalNodes).length).toEqual(370);
expect(Object.keys(graph.dependencies).length).toEqual(213); expect(Object.keys(graph.dependencies).length).toEqual(213);
expect(graph.dependencies['npm:@nrwl/devkit'].length).toEqual(6); expect(graph.dependencies['npm:@nrwl/devkit'].length).toEqual(6);
@ -833,6 +832,91 @@ describe('pnpm LockFile utility', () => {
); );
}); });
}); });
describe('v9.0', () => {
beforeEach(() => {
lockFile = require(joinPathFragments(
__dirname,
'__fixtures__/pruning/pnpm-lock-v9.yaml'
)).default;
lockFileHash = '__fixtures__/pruning/pnpm-lock-v9.yaml';
const externalNodes = getPnpmLockfileNodes(lockFile, lockFileHash);
graph = {
nodes: {},
dependencies: {},
externalNodes,
};
const ctx: CreateDependenciesContext = {
projects: {},
externalNodes,
fileMap: {
nonProjectFiles: [],
projectFileMap: {},
},
filesToProcess: {
nonProjectFiles: [],
projectFileMap: {},
},
nxJsonConfiguration: null,
workspaceRoot: '/virtual',
};
const dependencies = getPnpmLockfileDependencies(
lockFile,
lockFileHash,
ctx
);
const builder = new ProjectGraphBuilder(graph);
for (const dep of dependencies) {
builder.addDependency(
dep.source,
dep.target,
dep.type,
'sourceFile' in dep ? dep.sourceFile : null
);
}
graph = builder.getUpdatedProjectGraph();
});
it('should prune single package', () => {
const typescriptPackageJson = require(joinPathFragments(
__dirname,
'__fixtures__/pruning/typescript/package.json'
));
const prunedGraph = pruneProjectGraph(graph, typescriptPackageJson);
const result = stringifyPnpmLockfile(
prunedGraph,
lockFile,
typescriptPackageJson
);
expect(result).toEqual(
require(joinPathFragments(
__dirname,
'__fixtures__/pruning/typescript/pnpm-lock-v9.yaml'
)).default
);
});
it('should prune multi packages', () => {
const multiPackageJson = require(joinPathFragments(
__dirname,
'__fixtures__/pruning/devkit-yargs/package.json'
));
const prunedGraph = pruneProjectGraph(graph, multiPackageJson);
const result = stringifyPnpmLockfile(
prunedGraph,
lockFile,
multiPackageJson
);
expect(result).toEqual(
require(joinPathFragments(
__dirname,
'__fixtures__/pruning/devkit-yargs/pnpm-lock-v9.yaml'
)).default
);
});
});
}); });
describe('workspaces', () => { describe('workspaces', () => {
@ -878,7 +962,7 @@ describe('pnpm LockFile utility', () => {
'{"version": "3.0.0"}', '{"version": "3.0.0"}',
'node_modules/string-width/package.json': '{"version": "5.1.2"}', 'node_modules/string-width/package.json': '{"version": "5.1.2"}',
'node_modules/string-width-cjs/package.json': '{"version": "4.2.3"}', 'node_modules/string-width-cjs/package.json': '{"version": "4.2.3"}',
'node_modules/strip-ansi/package.json': '{"version": "7.0.1"}', 'node_modules/strip-ansi/package.json': '{"version": "7.1.0"}',
'node_modules/strip-ansi-cjs/package.json': '{"version": "6.0.1"}', 'node_modules/strip-ansi-cjs/package.json': '{"version": "6.0.1"}',
'node_modules/wrap-ansi/package.json': '{"version": "8.1.0"}', 'node_modules/wrap-ansi/package.json': '{"version": "8.1.0"}',
'node_modules/wrap-ansi-cjs/package.json': '{"version": "7.0.0"}', 'node_modules/wrap-ansi-cjs/package.json': '{"version": "7.0.0"}',
@ -888,15 +972,15 @@ describe('pnpm LockFile utility', () => {
)).default, )).default,
}; };
vol.fromJSON(fileSys, '/root'); vol.fromJSON(fileSys, '/root');
});
it('should parse classic and prune packages with mixed keys (v6)', () => {
lockFile = require(joinPathFragments( lockFile = require(joinPathFragments(
__dirname, __dirname,
'__fixtures__/mixed-keys/pnpm-lock.yaml' '__fixtures__/mixed-keys/pnpm-lock.yaml'
)).default; )).default;
lockFileHash = '__fixtures__/mixed-keys/pnpm-lock.yaml'; lockFileHash = '__fixtures__/mixed-keys/pnpm-lock.yaml';
});
it('should parse classic and prune packages with mixed keys', () => {
const packageJson = require(joinPathFragments( const packageJson = require(joinPathFragments(
__dirname, __dirname,
'__fixtures__/mixed-keys/package.json' '__fixtures__/mixed-keys/package.json'
@ -1076,6 +1160,15 @@ describe('pnpm LockFile utility', () => {
"name": "npm:string-width@4.2.3", "name": "npm:string-width@4.2.3",
"type": "npm", "type": "npm",
}, },
"npm:strip-ansi": {
"data": {
"hash": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
"packageName": "strip-ansi",
"version": "7.1.0",
},
"name": "npm:strip-ansi",
"type": "npm",
},
"npm:strip-ansi-cjs": { "npm:strip-ansi-cjs": {
"data": { "data": {
"hash": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "hash": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
@ -1094,13 +1187,252 @@ describe('pnpm LockFile utility', () => {
"name": "npm:strip-ansi@6.0.1", "name": "npm:strip-ansi@6.0.1",
"type": "npm", "type": "npm",
}, },
"npm:strip-ansi@7.1.0": { "npm:wrap-ansi": {
"data": {
"hash": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"packageName": "wrap-ansi",
"version": "8.1.0",
},
"name": "npm:wrap-ansi",
"type": "npm",
},
"npm:wrap-ansi-cjs": {
"data": {
"hash": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"packageName": "wrap-ansi-cjs",
"version": "npm:wrap-ansi@7.0.0",
},
"name": "npm:wrap-ansi-cjs",
"type": "npm",
},
"npm:wrap-ansi@7.0.0": {
"data": {
"hash": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"packageName": "wrap-ansi",
"version": "7.0.0",
},
"name": "npm:wrap-ansi@7.0.0",
"type": "npm",
},
}
`);
const prunedGraph = pruneProjectGraph(graph, packageJson);
const result = stringifyPnpmLockfile(prunedGraph, lockFile, packageJson);
expect(result).toEqual(lockFile);
});
it('should parse classic and prune packages with mixed keys (v9)', () => {
lockFile = require(joinPathFragments(
__dirname,
'__fixtures__/mixed-keys/pnpm-lock-v9.yaml'
)).default;
lockFileHash = '__fixtures__/mixed-keys/pnpm-lock-v9.yaml';
const packageJson = require(joinPathFragments(
__dirname,
'__fixtures__/mixed-keys/package.json'
));
const externalNodes = getPnpmLockfileNodes(lockFile, lockFileHash);
let graph: ProjectGraph = {
nodes: {},
dependencies: {},
externalNodes,
};
const ctx: CreateDependenciesContext = {
projects: {},
externalNodes,
fileMap: {
nonProjectFiles: [],
projectFileMap: {},
},
filesToProcess: {
nonProjectFiles: [],
projectFileMap: {},
},
nxJsonConfiguration: null,
workspaceRoot: '/virtual',
};
const dependencies = getPnpmLockfileDependencies(
lockFile,
lockFileHash,
ctx
);
const builder = new ProjectGraphBuilder(graph);
for (const dep of dependencies) {
builder.addDependency(
dep.source,
dep.target,
dep.type,
'sourceFile' in dep ? dep.sourceFile : null
);
}
graph = builder.getUpdatedProjectGraph();
expect(graph.externalNodes).toMatchInlineSnapshot(`
{
"npm:@isaacs/cliui": {
"data": {
"hash": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
"packageName": "@isaacs/cliui",
"version": "8.0.2",
},
"name": "npm:@isaacs/cliui",
"type": "npm",
},
"npm:ansi-regex": {
"data": {
"hash": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"packageName": "ansi-regex",
"version": "5.0.1",
},
"name": "npm:ansi-regex",
"type": "npm",
},
"npm:ansi-regex@6.0.1": {
"data": {
"hash": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
"packageName": "ansi-regex",
"version": "6.0.1",
},
"name": "npm:ansi-regex@6.0.1",
"type": "npm",
},
"npm:ansi-styles": {
"data": {
"hash": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"packageName": "ansi-styles",
"version": "4.3.0",
},
"name": "npm:ansi-styles",
"type": "npm",
},
"npm:ansi-styles@6.2.1": {
"data": {
"hash": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
"packageName": "ansi-styles",
"version": "6.2.1",
},
"name": "npm:ansi-styles@6.2.1",
"type": "npm",
},
"npm:cliui": {
"data": {
"hash": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
"packageName": "cliui",
"version": "8.0.1",
},
"name": "npm:cliui",
"type": "npm",
},
"npm:color-convert": {
"data": {
"hash": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"packageName": "color-convert",
"version": "2.0.1",
},
"name": "npm:color-convert",
"type": "npm",
},
"npm:color-name": {
"data": {
"hash": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"packageName": "color-name",
"version": "1.1.4",
},
"name": "npm:color-name",
"type": "npm",
},
"npm:eastasianwidth": {
"data": {
"hash": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"packageName": "eastasianwidth",
"version": "0.2.0",
},
"name": "npm:eastasianwidth",
"type": "npm",
},
"npm:emoji-regex": {
"data": {
"hash": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"packageName": "emoji-regex",
"version": "8.0.0",
},
"name": "npm:emoji-regex",
"type": "npm",
},
"npm:emoji-regex@9.2.2": {
"data": {
"hash": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"packageName": "emoji-regex",
"version": "9.2.2",
},
"name": "npm:emoji-regex@9.2.2",
"type": "npm",
},
"npm:is-fullwidth-code-point": {
"data": {
"hash": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"packageName": "is-fullwidth-code-point",
"version": "3.0.0",
},
"name": "npm:is-fullwidth-code-point",
"type": "npm",
},
"npm:string-width": {
"data": {
"hash": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"packageName": "string-width",
"version": "5.1.2",
},
"name": "npm:string-width",
"type": "npm",
},
"npm:string-width-cjs": {
"data": {
"hash": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"packageName": "string-width-cjs",
"version": "npm:string-width@4.2.3",
},
"name": "npm:string-width-cjs",
"type": "npm",
},
"npm:string-width@4.2.3": {
"data": {
"hash": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"packageName": "string-width",
"version": "4.2.3",
},
"name": "npm:string-width@4.2.3",
"type": "npm",
},
"npm:strip-ansi": {
"data": { "data": {
"hash": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "hash": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
"packageName": "strip-ansi", "packageName": "strip-ansi",
"version": "7.1.0", "version": "7.1.0",
}, },
"name": "npm:strip-ansi@7.1.0", "name": "npm:strip-ansi",
"type": "npm",
},
"npm:strip-ansi-cjs": {
"data": {
"hash": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"packageName": "strip-ansi-cjs",
"version": "npm:strip-ansi@6.0.1",
},
"name": "npm:strip-ansi-cjs",
"type": "npm",
},
"npm:strip-ansi@6.0.1": {
"data": {
"hash": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"packageName": "strip-ansi",
"version": "6.0.1",
},
"name": "npm:strip-ansi@6.0.1",
"type": "npm", "type": "npm",
}, },
"npm:wrap-ansi": { "npm:wrap-ansi": {
@ -1138,4 +1470,76 @@ describe('pnpm LockFile utility', () => {
expect(result).toEqual(lockFile); expect(result).toEqual(lockFile);
}); });
}); });
describe('regression check', () => {
let lockFile, lockFileHash, prunedLockFile;
beforeEach(() => {
const fileSys = {
'node_modules/.modules.yaml': require(joinPathFragments(
__dirname,
'__fixtures__/pnpm-regression/.modules.yaml'
)).default,
};
vol.fromJSON(fileSys, '/root');
});
it('should correctly prune the lock file', () => {
lockFile = require(joinPathFragments(
__dirname,
'__fixtures__/pnpm-regression/pnpm-lock.yaml'
)).default;
prunedLockFile = require(joinPathFragments(
__dirname,
'__fixtures__/pnpm-regression/pruned-pnpm-lock.yaml'
)).default;
lockFileHash = '__fixtures__/pnpm-regression/pnpm-lock.yaml';
const packageJson = require(joinPathFragments(
__dirname,
'__fixtures__/pnpm-regression/package.json'
));
const externalNodes = getPnpmLockfileNodes(lockFile, lockFileHash);
let graph: ProjectGraph = {
nodes: {},
dependencies: {},
externalNodes,
};
const ctx: CreateDependenciesContext = {
projects: {},
externalNodes,
fileMap: {
nonProjectFiles: [],
projectFileMap: {},
},
filesToProcess: {
nonProjectFiles: [],
projectFileMap: {},
},
nxJsonConfiguration: null,
workspaceRoot: '/virtual',
};
const dependencies = getPnpmLockfileDependencies(
lockFile,
lockFileHash,
ctx
);
const builder = new ProjectGraphBuilder(graph);
for (const dep of dependencies) {
builder.addDependency(
dep.source,
dep.target,
dep.type,
'sourceFile' in dep ? dep.sourceFile : null
);
}
graph = builder.getUpdatedProjectGraph();
const prunedGraph = pruneProjectGraph(graph, packageJson);
const result = stringifyPnpmLockfile(prunedGraph, lockFile, packageJson);
expect(result).toEqual(prunedLockFile);
});
});
}); });

View File

@ -5,7 +5,7 @@ import type {
ProjectSnapshot, ProjectSnapshot,
} from '@pnpm/lockfile-types'; } from '@pnpm/lockfile-types';
import { import {
isV6Lockfile, isV5Syntax,
loadPnpmHoistedDepsDefinition, loadPnpmHoistedDepsDefinition,
parseAndNormalizePnpmLockfile, parseAndNormalizePnpmLockfile,
stringifyToPnpmYaml, stringifyToPnpmYaml,
@ -28,11 +28,14 @@ import { hashArray } from '../../../hasher/file-hasher';
import { CreateDependenciesContext } from '../../../project-graph/plugins'; import { CreateDependenciesContext } from '../../../project-graph/plugins';
// we use key => node map to avoid duplicate work when parsing keys // we use key => node map to avoid duplicate work when parsing keys
let keyMap = new Map<string, ProjectGraphExternalNode>(); let keyMap = new Map<string, Set<ProjectGraphExternalNode>>();
let currentLockFileHash: string; let currentLockFileHash: string;
let parsedLockFile: Lockfile; let parsedLockFile: Lockfile;
function parsePnpmLockFile(lockFileContent: string, lockFileHash: string) { function parsePnpmLockFile(
lockFileContent: string,
lockFileHash: string
): Lockfile {
if (lockFileHash === currentLockFileHash) { if (lockFileHash === currentLockFileHash) {
return parsedLockFile; return parsedLockFile;
} }
@ -47,11 +50,15 @@ function parsePnpmLockFile(lockFileContent: string, lockFileHash: string) {
export function getPnpmLockfileNodes( export function getPnpmLockfileNodes(
lockFileContent: string, lockFileContent: string,
lockFileHash: string lockFileHash: string
) { ): Record<string, ProjectGraphExternalNode> {
const data = parsePnpmLockFile(lockFileContent, lockFileHash); const data = parsePnpmLockFile(lockFileContent, lockFileHash);
const isV6 = isV6Lockfile(data); if (+data.lockfileVersion.toString() >= 10) {
console.warn(
return getNodes(data, keyMap, isV6); 'Nx was tested only with pnpm lockfile version 5-9. If you encounter any issues, please report them and downgrade to older version of pnpm.'
);
}
const isV5 = isV5Syntax(data);
return getNodes(data, keyMap, isV5);
} }
export function getPnpmLockfileDependencies( export function getPnpmLockfileDependencies(
@ -60,9 +67,13 @@ export function getPnpmLockfileDependencies(
ctx: CreateDependenciesContext ctx: CreateDependenciesContext
) { ) {
const data = parsePnpmLockFile(lockFileContent, lockFileHash); const data = parsePnpmLockFile(lockFileContent, lockFileHash);
const isV6 = isV6Lockfile(data); if (+data.lockfileVersion.toString() >= 10) {
console.warn(
return getDependencies(data, keyMap, isV6, ctx); 'Nx was tested only with pnpm lockfile version 5-9. If you encounter any issues, please report them and downgrade to older version of pnpm.'
);
}
const isV5 = isV5Syntax(data);
return getDependencies(data, keyMap, isV5, ctx);
} }
function matchPropValue( function matchPropValue(
@ -107,66 +118,107 @@ function createHashFromSnapshot(snapshot: PackageSnapshot) {
); );
} }
function isLockFileKey(depVersion: string) { function isAliasVersion(depVersion: string) {
return depVersion.startsWith('/'); return depVersion.startsWith('/') || depVersion.includes('@');
} }
function getNodes( function getNodes(
data: Lockfile, data: Lockfile,
keyMap: Map<string, ProjectGraphExternalNode>, keyMap: Map<string, Set<ProjectGraphExternalNode>>,
isV6: boolean isV5: boolean
): Record<string, ProjectGraphExternalNode> { ): Record<string, ProjectGraphExternalNode> {
const nodes: Map<string, Map<string, ProjectGraphExternalNode>> = new Map(); const nodes: Map<string, Map<string, ProjectGraphExternalNode>> = new Map();
const maybeAliasedPackageVersions = new Map<string, string>(); // <version, alias> const maybeAliasedPackageVersions = new Map<string, string>(); // <version, alias>
if (data.importers['.'].optionalDependencies) {
for (const [depName, depVersion] of Object.entries(
data.importers['.'].optionalDependencies
)) {
if (isAliasVersion(depVersion)) {
maybeAliasedPackageVersions.set(depVersion, depName);
}
}
}
if (data.importers['.'].devDependencies) {
for (const [depName, depVersion] of Object.entries(
data.importers['.'].devDependencies
)) {
if (isAliasVersion(depVersion)) {
maybeAliasedPackageVersions.set(depVersion, depName);
}
}
}
if (data.importers['.'].dependencies) {
for (const [depName, depVersion] of Object.entries(
data.importers['.'].dependencies
)) {
if (isAliasVersion(depVersion)) {
maybeAliasedPackageVersions.set(depVersion, depName);
}
}
}
const packageNames = new Set<{ const packageNames = new Set<{
key: string; key: string;
packageName: string; packageName: string;
hash?: string; hash?: string;
alias?: boolean;
}>(); }>();
let packageNameObj;
for (const [key, snapshot] of Object.entries(data.packages)) { for (const [key, snapshot] of Object.entries(data.packages)) {
const originalPackageName = extractNameFromKey(key); const originalPackageName = extractNameFromKey(key, isV5);
if (!originalPackageName) { if (!originalPackageName) {
continue; continue;
} }
const hash = createHashFromSnapshot(snapshot);
// snapshot already has a name // snapshot already has a name
if (snapshot.name) { if (snapshot.name) {
packageNames.add({ packageNameObj = {
key, key,
packageName: snapshot.name, packageName: snapshot.name,
hash: createHashFromSnapshot(snapshot), hash,
}); };
} }
const rootDependencyName = const rootDependencyName =
matchedDependencyName(data.importers['.'], key, originalPackageName) || matchedDependencyName(data.importers['.'], key, originalPackageName) ||
matchedDependencyName(
data.importers['.'],
`/${key}`,
originalPackageName
) ||
// only root importers have devDependencies // only root importers have devDependencies
matchPropValue( matchPropValue(
data.importers['.'].devDependencies, data.importers['.'].devDependencies,
key, key,
originalPackageName originalPackageName
) ||
matchPropValue(
data.importers['.'].devDependencies,
`/${key}`,
originalPackageName
); );
if (rootDependencyName) { if (rootDependencyName) {
packageNames.add({ packageNameObj = {
key, key,
packageName: rootDependencyName, packageName: rootDependencyName,
hash: createHashFromSnapshot(snapshot), hash: createHashFromSnapshot(snapshot),
}); };
} }
if (!snapshot.name && !rootDependencyName) { if (!snapshot.name && !rootDependencyName) {
packageNames.add({ packageNameObj = {
key, key,
packageName: originalPackageName, packageName: originalPackageName,
hash: createHashFromSnapshot(snapshot), hash: createHashFromSnapshot(snapshot),
}); };
} }
if (snapshot.peerDependencies) { if (snapshot.peerDependencies) {
for (const [depName, depVersion] of Object.entries( for (const [depName, depVersion] of Object.entries(
snapshot.peerDependencies snapshot.peerDependencies
)) { )) {
if (isLockFileKey(depVersion)) { if (isAliasVersion(depVersion)) {
maybeAliasedPackageVersions.set(depVersion, depName); maybeAliasedPackageVersions.set(depVersion, depName);
} }
} }
@ -175,7 +227,7 @@ function getNodes(
for (const [depName, depVersion] of Object.entries( for (const [depName, depVersion] of Object.entries(
snapshot.optionalDependencies snapshot.optionalDependencies
)) { )) {
if (isLockFileKey(depVersion)) { if (isAliasVersion(depVersion)) {
maybeAliasedPackageVersions.set(depVersion, depName); maybeAliasedPackageVersions.set(depVersion, depName);
} }
} }
@ -184,28 +236,40 @@ function getNodes(
for (const [depName, depVersion] of Object.entries( for (const [depName, depVersion] of Object.entries(
snapshot.dependencies snapshot.dependencies
)) { )) {
if (isLockFileKey(depVersion)) { if (isAliasVersion(depVersion)) {
maybeAliasedPackageVersions.set(depVersion, depName); maybeAliasedPackageVersions.set(depVersion, depName);
} }
} }
} }
const aliasedDep = maybeAliasedPackageVersions.get(key); const aliasedDep = maybeAliasedPackageVersions.get(`/${key}`);
if (aliasedDep) { if (aliasedDep) {
packageNames.add({ packageNameObj = {
key, key,
packageName: aliasedDep, packageName: aliasedDep,
hash: createHashFromSnapshot(snapshot), hash,
}); alias: true,
};
}
packageNames.add(packageNameObj);
const localAlias = maybeAliasedPackageVersions.get(key);
if (localAlias) {
packageNameObj = {
key,
packageName: localAlias,
hash,
alias: true,
};
packageNames.add(packageNameObj);
} }
} }
for (const { key, packageName, hash } of packageNames) { for (const { key, packageName, hash, alias } of packageNames) {
const rawVersion = findVersion(key, packageName); const rawVersion = findVersion(key, packageName, isV5, alias);
if (!rawVersion) { if (!rawVersion) {
continue; continue;
} }
const version = parseBaseVersion(rawVersion, isV6); const version = parseBaseVersion(rawVersion, isV5);
if (!version) { if (!version) {
continue; continue;
} }
@ -217,7 +281,10 @@ function getNodes(
if (!nodes.get(packageName).has(version)) { if (!nodes.get(packageName).has(version)) {
const node: ProjectGraphExternalNode = { const node: ProjectGraphExternalNode = {
type: 'npm', type: 'npm',
name: version ? `npm:${packageName}@${version}` : `npm:${packageName}`, name:
version && !version.startsWith('npm:')
? `npm:${packageName}@${version}`
: `npm:${packageName}`,
data: { data: {
version, version,
packageName, packageName,
@ -225,9 +292,18 @@ function getNodes(
}, },
}; };
nodes.get(packageName).set(version, node); nodes.get(packageName).set(version, node);
keyMap.set(key, node); if (!keyMap.has(key)) {
keyMap.set(key, new Set([node]));
} else { } else {
keyMap.set(key, nodes.get(packageName).get(version)); keyMap.get(key).add(node);
}
} else {
const node = nodes.get(packageName).get(version);
if (!keyMap.has(key)) {
keyMap.set(key, new Set([node]));
} else {
keyMap.get(key).add(node);
}
} }
} }
@ -239,7 +315,7 @@ function getNodes(
if (versionMap.size === 1) { if (versionMap.size === 1) {
hoistedNode = versionMap.values().next().value; hoistedNode = versionMap.values().next().value;
} else { } else {
const hoistedVersion = getHoistedVersion(hoistedDeps, packageName, isV6); const hoistedVersion = getHoistedVersion(hoistedDeps, packageName, isV5);
hoistedNode = versionMap.get(hoistedVersion); hoistedNode = versionMap.get(hoistedVersion);
} }
if (hoistedNode) { if (hoistedNode) {
@ -256,16 +332,16 @@ function getNodes(
function getHoistedVersion( function getHoistedVersion(
hoistedDependencies: Record<string, any>, hoistedDependencies: Record<string, any>,
packageName: string, packageName: string,
isV6: boolean isV5: boolean
): string { ): string {
let version = getHoistedPackageVersion(packageName); let version = getHoistedPackageVersion(packageName);
if (!version) { if (!version) {
const key = Object.keys(hoistedDependencies).find((k) => const key = Object.keys(hoistedDependencies).find((k) =>
k.startsWith(`/${packageName}/`) k.startsWith(isV5 ? `/${packageName}/` : `/${packageName}@`)
); );
if (key) { if (key) {
version = parseBaseVersion(getVersion(key, packageName), isV6); version = parseBaseVersion(getVersion(key.slice(1), packageName), isV5);
} else { } else {
// pnpm might not hoist every package // pnpm might not hoist every package
// similarly those packages will not be available to be used via import // similarly those packages will not be available to be used via import
@ -278,20 +354,21 @@ function getHoistedVersion(
function getDependencies( function getDependencies(
data: Lockfile, data: Lockfile,
keyMap: Map<string, ProjectGraphExternalNode>, keyMap: Map<string, Set<ProjectGraphExternalNode>>,
isV6: boolean, isV5: boolean,
ctx: CreateDependenciesContext ctx: CreateDependenciesContext
): RawProjectGraphDependency[] { ): RawProjectGraphDependency[] {
const results: RawProjectGraphDependency[] = []; const results: RawProjectGraphDependency[] = [];
Object.entries(data.packages).forEach(([key, snapshot]) => { Object.entries(data.packages).forEach(([key, snapshot]) => {
const node = keyMap.get(key); const nodes = keyMap.get(key);
nodes.forEach((node) => {
[snapshot.dependencies, snapshot.optionalDependencies].forEach( [snapshot.dependencies, snapshot.optionalDependencies].forEach(
(section) => { (section) => {
if (section) { if (section) {
Object.entries(section).forEach(([name, versionRange]) => { Object.entries(section).forEach(([name, versionRange]) => {
const version = parseBaseVersion( const version = parseBaseVersion(
findVersion(versionRange, name), findVersion(versionRange, name, isV5),
isV6 isV5
); );
const target = const target =
ctx.externalNodes[`npm:${name}@${version}`] || ctx.externalNodes[`npm:${name}@${version}`] ||
@ -310,12 +387,13 @@ function getDependencies(
} }
); );
}); });
});
return results; return results;
} }
function parseBaseVersion(rawVersion: string, isV6: boolean): string { function parseBaseVersion(rawVersion: string, isV5: boolean): string {
return isV6 ? rawVersion.split('(')[0] : rawVersion.split('_')[0]; return isV5 ? rawVersion.split('_')[0] : rawVersion.split('(')[0];
} }
export function stringifyPnpmLockfile( export function stringifyPnpmLockfile(
@ -326,14 +404,25 @@ export function stringifyPnpmLockfile(
const data = parseAndNormalizePnpmLockfile(rootLockFileContent); const data = parseAndNormalizePnpmLockfile(rootLockFileContent);
const { lockfileVersion, packages } = data; const { lockfileVersion, packages } = data;
const rootSnapshot = mapRootSnapshot(
packageJson,
packages,
graph.externalNodes,
+lockfileVersion
);
const snapshots = mapSnapshots(
data.packages,
graph.externalNodes,
+lockfileVersion
);
const output: Lockfile = { const output: Lockfile = {
...data,
lockfileVersion, lockfileVersion,
importers: { importers: {
'.': mapRootSnapshot(packageJson, packages, graph.externalNodes), '.': rootSnapshot,
}, },
packages: sortObjectByKeys( packages: sortObjectByKeys(snapshots),
mapSnapshots(data.packages, graph.externalNodes)
),
}; };
return stringifyToPnpmYaml(output); return stringifyToPnpmYaml(output);
@ -341,45 +430,116 @@ export function stringifyPnpmLockfile(
function mapSnapshots( function mapSnapshots(
packages: PackageSnapshots, packages: PackageSnapshots,
nodes: Record<string, ProjectGraphExternalNode> nodes: Record<string, ProjectGraphExternalNode>,
lockfileVersion: number
): PackageSnapshots { ): PackageSnapshots {
const result: PackageSnapshots = {}; const result: PackageSnapshots = {};
Object.values(nodes).forEach((node) => { Object.values(nodes).forEach((node) => {
const matchedKeys = findOriginalKeys(packages, node, { const matchedKeys = findOriginalKeys(packages, node, lockfileVersion, {
returnFullKey: true, returnFullKey: true,
}); });
// the package manager doesn't check for types of dependencies // the package manager doesn't check for types of dependencies
// so we can safely set all to prod // so we can safely set all to prod
matchedKeys.forEach(([key, snapshot]) => { matchedKeys.forEach(([key, snapshot]) => {
snapshot.dev = false; if (lockfileVersion >= 9) {
delete snapshot['dev'];
result[key] = snapshot; result[key] = snapshot;
} else {
snapshot['dev'] = false; // all dependencies are prod
remapDependencies(snapshot);
if (snapshot.resolution?.['tarball']) {
// tarballs are not prefixed with /
result[key] = snapshot;
} else {
result[`/${key}`] = snapshot;
}
}
}); });
}); });
return result; return result;
} }
function remapDependencies(snapshot: PackageSnapshot) {
[
'dependencies',
'optionalDependencies',
'devDependencies',
'peerDependencies',
].forEach((depType) => {
if (snapshot[depType]) {
for (const [packageName, version] of Object.entries(
snapshot[depType] as Record<string, string>
)) {
if (version.match(/^[a-zA-Z]+.*/)) {
// remap packageName@version to packageName/version
snapshot[depType][packageName] = `/${version.replace(
/([a-zA-Z].+)@/,
'$1/'
)}`;
}
}
}
});
}
function findOriginalKeys( function findOriginalKeys(
packages: PackageSnapshots, packages: PackageSnapshots,
{ data: { packageName, version } }: ProjectGraphExternalNode, { data: { packageName, version } }: ProjectGraphExternalNode,
lockfileVersion: number,
{ returnFullKey }: { returnFullKey?: boolean } = {} { returnFullKey }: { returnFullKey?: boolean } = {}
): Array<[string, PackageSnapshot]> { ): Array<[string, PackageSnapshot]> {
const matchedKeys = []; const matchedKeys = [];
for (const key of Object.keys(packages)) { for (const key of Object.keys(packages)) {
const snapshot = packages[key]; const snapshot = packages[key];
// tarball package
if (
key.startsWith(`${packageName}@${version}`) &&
snapshot.resolution?.['tarball']
) {
matchedKeys.push([getVersion(key, packageName), snapshot]);
}
// standard package // standard package
if (key.startsWith(`/${packageName}/${version}`)) { if (lockfileVersion < 6 && key.startsWith(`${packageName}/${version}`)) {
matchedKeys.push([ matchedKeys.push([
returnFullKey ? key : getVersion(key, packageName), returnFullKey ? key : getVersion(key, packageName),
snapshot, snapshot,
]); ]);
} }
// tarball package if (
if (key === version) { lockfileVersion >= 6 &&
matchedKeys.push([version, snapshot]); lockfileVersion < 9 &&
key.startsWith(`${packageName}@${version}`)
) {
matchedKeys.push([
// we need to replace the @ with / for v5-7 syntax because the dpParse function expects old format
returnFullKey
? key.replace(
`${packageName}@${version}`,
`${packageName}/${version}`
)
: getVersion(key, packageName),
snapshot,
]);
}
if (lockfileVersion >= 9 && key.startsWith(`${packageName}@${version}`)) {
matchedKeys.push([
returnFullKey ? key : getVersion(key, packageName),
snapshot,
]);
} }
// alias package // alias package
if (versionIsAlias(key, version)) { if (versionIsAlias(key, version, lockfileVersion)) {
if (lockfileVersion >= 9) {
// no postprocessing needed for v9
matchedKeys.push([key, snapshot]); matchedKeys.push([key, snapshot]);
} else {
// for root specifiers we need to ensure alias is prefixed with /
const prefixedKey = returnFullKey ? key : `/${key}`;
const mappedKey = prefixedKey.replace(/(\/?..+)@/, '$1/');
matchedKeys.push([mappedKey, snapshot]);
}
} }
} }
return matchedKeys; return matchedKeys;
@ -387,20 +547,28 @@ function findOriginalKeys(
// check if version has a form of npm:packageName@version and // check if version has a form of npm:packageName@version and
// key starts with /packageName/version // key starts with /packageName/version
function versionIsAlias(key: string, versionExpr: string): boolean { function versionIsAlias(
key: string,
versionExpr: string,
lockfileVersion: number
): boolean {
const PREFIX = 'npm:'; const PREFIX = 'npm:';
if (!versionExpr.startsWith(PREFIX)) return false; if (!versionExpr.startsWith(PREFIX)) return false;
const indexOfVersionSeparator = versionExpr.indexOf('@', PREFIX.length + 1); const indexOfVersionSeparator = versionExpr.indexOf('@', PREFIX.length + 1);
const packageName = versionExpr.slice(PREFIX.length, indexOfVersionSeparator); const packageName = versionExpr.slice(PREFIX.length, indexOfVersionSeparator);
const version = versionExpr.slice(indexOfVersionSeparator + 1); const version = versionExpr.slice(indexOfVersionSeparator + 1);
return key.startsWith(`/${packageName}/${version}`);
return lockfileVersion < 6
? key.startsWith(`${packageName}/${version}`)
: key.startsWith(`${packageName}@${version}`);
} }
function mapRootSnapshot( function mapRootSnapshot(
packageJson: NormalizedPackageJson, packageJson: NormalizedPackageJson,
packages: PackageSnapshots, packages: PackageSnapshots,
nodes: Record<string, ProjectGraphExternalNode> nodes: Record<string, ProjectGraphExternalNode>,
lockfileVersion: number
): ProjectSnapshot { ): ProjectSnapshot {
const snapshot: ProjectSnapshot = { specifiers: {} }; const snapshot: ProjectSnapshot = { specifiers: {} };
[ [
@ -418,7 +586,11 @@ function mapRootSnapshot(
// peer dependencies are mapped to dependencies // peer dependencies are mapped to dependencies
let section = depType === 'peerDependencies' ? 'dependencies' : depType; let section = depType === 'peerDependencies' ? 'dependencies' : depType;
snapshot[section] = snapshot[section] || {}; snapshot[section] = snapshot[section] || {};
snapshot[section][packageName] = findOriginalKeys(packages, node)[0][0]; snapshot[section][packageName] = findOriginalKeys(
packages,
node,
lockfileVersion
)[0][0];
}); });
} }
}); });
@ -430,38 +602,50 @@ function mapRootSnapshot(
return snapshot; return snapshot;
} }
function findVersion(key: string, packageName: string): string { function findVersion(
if (key.startsWith(`/${packageName}/`)) { key: string,
packageName: string,
isV5: boolean,
alias?: boolean
): string {
if (isV5 && key.startsWith(`${packageName}/`)) {
return getVersion(key, packageName); return getVersion(key, packageName);
} }
// for alias packages prepend with "npm:" // this matches v6 syntax and tarball packages
if (key.startsWith('/')) { if (key.startsWith(`${packageName}@`)) {
const aliasName = key.slice(1, key.lastIndexOf('/')); return getVersion(key, packageName);
}
if (alias) {
const aliasName = isV5
? key.slice(0, key.lastIndexOf('/'))
: key.slice(0, key.indexOf('@', 2)); // we use 2 to ensure we don't catch the first @
const version = getVersion(key, aliasName); const version = getVersion(key, aliasName);
return `npm:${aliasName}@${version}`; return `npm:${aliasName}@${version}`;
} }
// for tarball package the entire key is the version spec // for tarball package the entire key is the version spec
return key; return key;
} }
function getVersion(key: string, packageName: string): string { function getVersion(key: string, packageName: string): string {
const KEY_NAME_SEPARATOR_LENGTH = 2; // leading and trailing slash return key.slice(packageName.length + 1);
return key.slice(packageName.length + KEY_NAME_SEPARATOR_LENGTH);
} }
function extractNameFromKey(key: string): string { function extractNameFromKey(key: string, isV5: boolean): string {
// if package name contains org e.g. "/@babel/runtime/7.12.5" // if package name contains org e.g. "@babel/runtime@7.12.5"
// we want slice until the third slash if (key.startsWith('@')) {
if (key.startsWith('/@')) { if (isV5) {
// find the position of the '/' after org name const startFrom = key.indexOf('/');
const startFrom = key.indexOf('/', 1); return key.slice(0, key.indexOf('/', startFrom + 1));
return key.slice(1, key.indexOf('/', startFrom + 1)); } else {
// find the position of the '@'
return key.slice(0, key.indexOf('@', 1));
} }
if (key.startsWith('/')) {
// if package has just a name e.g. "/react/7.12.5..."
return key.slice(1, key.indexOf('/', 1));
} }
return key; if (isV5) {
// if package has just a name e.g. "react/7.12.5..."
return key.slice(0, key.indexOf('/', 1));
} else {
// if package has just a name e.g. "react@7.12.5..."
return key.slice(0, key.indexOf('@', 1));
}
} }

File diff suppressed because it is too large Load Diff

18
pnpm-lock.yaml generated
View File

@ -321,8 +321,8 @@ devDependencies:
specifier: ^0.5.7 specifier: ^0.5.7
version: 0.5.8(react-refresh@0.10.0)(webpack-dev-server@4.11.1)(webpack@5.88.0) version: 0.5.8(react-refresh@0.10.0)(webpack-dev-server@4.11.1)(webpack@5.88.0)
'@pnpm/lockfile-types': '@pnpm/lockfile-types':
specifier: ^5.0.0 specifier: ^6.0.0
version: 5.0.0 version: 6.0.0
'@reduxjs/toolkit': '@reduxjs/toolkit':
specifier: 1.9.0 specifier: 1.9.0
version: 1.9.0(react-redux@8.0.5)(react@18.3.1) version: 1.9.0(react-redux@8.0.5)(react@18.3.1)
@ -11410,16 +11410,16 @@ packages:
webpack-dev-server: 4.11.1(webpack-cli@5.1.4)(webpack@5.88.0) webpack-dev-server: 4.11.1(webpack-cli@5.1.4)(webpack@5.88.0)
dev: true dev: true
/@pnpm/lockfile-types@5.0.0: /@pnpm/lockfile-types@6.0.0:
resolution: {integrity: sha512-2M82hciNNIczVtWmQF3eSXPFVWvGWBvq+vssBkSIP0tZW/izYyvkUf2NN8ktNrB/w0jDCVEzujC6RXiRR9b1bg==} resolution: {integrity: sha512-a4/ULIPLZIIq8Qmi2HEoFgRTtEouGU5RNhuGDxnSmkxu1BjlNMNjLJeEI5jzMZCGOjBoML+AirY/XOO3bcEQ/w==}
engines: {node: '>=16.14'} engines: {node: '>=18.12'}
dependencies: dependencies:
'@pnpm/types': 9.0.0 '@pnpm/types': 10.0.0
dev: true dev: true
/@pnpm/types@9.0.0: /@pnpm/types@10.0.0:
resolution: {integrity: sha512-+nNqpNvqb1u3WW/cHDo7tGjqJBWWe4GAHEdELrz4QMQwGAtG/1GF6NMc0cewdwgU2k67CI3JHGu4quZJnMEUJg==} resolution: {integrity: sha512-P608MRTOExt5BkIN2hsrb/ycEchwaPW/x80ujJUAqxKZSXNVAOrlEu3KJ+2+jTCunyWmo/EcE01ZdwCw8jgVrQ==}
engines: {node: '>=16.14'} engines: {node: '>=18.12'}
dev: true dev: true
/@polka/url@1.0.0-next.24: /@polka/url@1.0.0-next.24: