Skip to content

Commit 4dbd137

Browse files
authored
Implements a pnpm lockfile migration & more (#197)
This PR adds some basic logic to import pnpm lockfile into Yarn. It's not perfect because the pnpm lockfile is unfortunately a little bare - for example it doesn't contain the original ranges. The import process works like this: - We run `pnpm list -r --depth 3` to try and get the list of packages in the dependency tree, and their path + tarball - `--depth 3` is arbitrary; more than that crashes the process, and less than that means we'd miss more ranges - Then we read the `package.json` for each path - We iterate the `dependencies` field from the `package.json` to obtain the original range, and we attach it to whatever version we have in the `pnpm list` output <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Introduces pnpm installation/migration support and improves registry fetching/auth. > > - Adds lockfile import from existing `pnpm` installs by parsing `pnpm list --json` and package manifests; falls back to this when no lockfile exists > - Switches pnpm virtual store from `node_modules/.store` to `node_modules/.pnpm`; updates config schema (`pnpmStoreFolder`), docs, tests, and linker paths/hoisting pattern type (`IdentGlob`) > - Extends `npm:` references with optional tarball URL; preserve shorthand only when URL absent; adjust descriptor resolution/alias handling accordingly > - Implements registry auth resolution (scopes, per-registry, `npmAlwaysAuth`), sends Authorization headers for npm and URL fetches; adds stricter auth requirement helper and scope normalization > - Tweaks install graph for inner dependencies (alias vs patch) to resolve/refresh vs fetch appropriately > - Minor utils: `UrlEncoded` deref, string serialization error type, case-insensitive %2f decoding; new error for pnpm node_modules read > - New acceptance tests for pnpm migration and custom pnpm store location > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 1aaebb5. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 8dca647 commit 4dbd137

44 files changed

Lines changed: 782 additions & 280 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/settings.local.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
"Bash(git push)",
1616
"Bash(yarn test:integration:*)",
1717
"Bash(cargo check:*)",
18-
"Bash(git stash:*)"
18+
"Bash(git stash:*)",
19+
"Bash(RUST_BACKTRACE=1 ~/Repositories/zpm/target/release/yarn-bin:*)"
1920
],
2021
"deny": [],
2122
"ask": []

documentation/src/content/docs/concepts/core/linkers.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ Yarn Plug'n'Play has been the default installation strategy in Yarn since 2019,
3939

4040
## `nodeLinker: pnpm`
4141

42-
Under this mode, a flat folder is generated in `node_modules/.store` containing one folder for each dependency in the project. Each dependency folder is populated with hardlinks obtained from a central store common to all projects on the system (by default `$HOME/.yarn/berry/index`). Finally, symlinks to the relevant folders from the flat store are placed into the `node_modules` folders.
42+
Under this mode, a flat folder is generated in `node_modules/.pnpm` containing one folder for each dependency in the project. Each dependency folder is populated with hardlinks obtained from a central store common to all projects on the system (by default `$HOME/.yarn/berry/index`). Finally, symlinks to the relevant folders from the flat store are placed into the `node_modules` folders.
4343

4444
<div class="[&_table]:table-fixed [&_th]:w-[50%]">
4545

documentation/src/utils/configuration/yarnrc.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -482,10 +482,10 @@
482482
"pnpmStoreFolder": {
483483
"_package": "@yarnpkg/plugin-pnpm",
484484
"title": "Path where the pnpm store will be stored",
485-
"description": "By default, the store is stored in the `node_modules/.store` of the project. Sometimes in CI scenario's it is convenient to store this in a different location so it can be cached and reused.",
485+
"description": "By default, the store is stored in the `node_modules/.pnpm` of the project. Sometimes in CI scenario's it is convenient to store this in a different location so it can be cached and reused.",
486486
"type": "string",
487487
"format": "uri-reference",
488-
"examples": [".cache/.store"]
488+
"examples": [".cache/.pnpm"]
489489
},
490490
"winLinkType": {
491491
"_package": "@yarnpkg/core",

packages/zpm-config/schema.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -282,23 +282,23 @@
282282
},
283283
"pnpmHoistPatterns": {
284284
"type": "array",
285-
"description": "Patterns specifying which packages should be hoisted to node_modules/.store/node_modules. By default, all packages are hoisted. Use empty array to disable hoisting.",
285+
"description": "Patterns specifying which packages should be hoisted to node_modules/.pnpm/node_modules. By default, all packages are hoisted. Use empty array to disable hoisting.",
286286
"items": {
287-
"type": "zpm_utils::Glob"
287+
"type": "zpm_primitives::IdentGlob"
288288
},
289289
"default": ["*"]
290290
},
291291
"pnpmPublicHoistPatterns": {
292292
"type": "array",
293293
"description": "Patterns specifying which packages should be hoisted to the root node_modules directory. Unlike pnpmHoistPatterns which hoists to the hidden modules directory inside the virtual store, this hoists to the public root modules directory.",
294294
"items": {
295-
"type": "zpm_utils::Glob"
295+
"type": "zpm_primitives::IdentGlob"
296296
}
297297
},
298298
"pnpmStoreFolder": {
299299
"type": "string",
300300
"description": "The folder to use for the Pnpm store",
301-
"default": "node_modules/.store"
301+
"default": "node_modules/.pnpm"
302302
},
303303
"preferDeferredVersions": {
304304
"type": "boolean",

packages/zpm-config/src/exts.rs

Lines changed: 0 additions & 14 deletions
This file was deleted.

packages/zpm-config/src/lib.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -931,9 +931,6 @@ impl Configuration {
931931
}
932932
}
933933

934-
mod exts;
935-
pub use exts::*;
936-
937934
mod fns;
938935
pub use fns::*;
939936

@@ -954,6 +951,7 @@ merge_settings!(zpm_formats::CompressionAlgorithm, |s: &str| FromFileString::fro
954951
merge_settings!(zpm_primitives::Descriptor, |s: &str| FromFileString::from_file_string(s).unwrap());
955952
merge_settings!(zpm_primitives::FilterDescriptor, |s: &str| FromFileString::from_file_string(s).unwrap());
956953
merge_settings!(zpm_primitives::Ident, |s: &str| FromFileString::from_file_string(s).unwrap());
954+
merge_settings!(zpm_primitives::IdentGlob, |s: &str| FromFileString::from_file_string(s).unwrap());
957955
merge_settings!(zpm_primitives::Locator, |s: &str| FromFileString::from_file_string(s).unwrap());
958956
merge_settings!(zpm_primitives::PeerRange, |s: &str| FromFileString::from_file_string(s).unwrap());
959957
merge_settings!(zpm_primitives::Range, |s: &str| FromFileString::from_file_string(s).unwrap());

packages/zpm-primitives/src/descriptor.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ impl Descriptor {
8686
};
8787

8888
let reference = match reference {
89-
Reference::Registry(params) if params.ident == self.ident => reference::ShorthandReference {
89+
Reference::Registry(params) if params.ident == self.ident && params.url.is_none() => reference::ShorthandReference {
9090
version: params.version,
9191
}.into(),
9292

packages/zpm-primitives/src/ident_glob.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@ pub struct IdentGlob {
1111

1212
impl IdentGlob {
1313
pub fn new(raw: &str) -> Result<Self, globset::Error> {
14-
let glob = GlobBuilder::new(raw)
14+
let actual_raw = match raw {
15+
"*" => "**",
16+
_ => raw,
17+
};
18+
19+
let glob = GlobBuilder::new(actual_raw)
1520
.literal_separator(false)
1621
.build()?;
1722

packages/zpm-primitives/src/reference.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@ fn format_patch(inner: &UrlEncoded<Locator>, path: &str, checksum: &Option<Hash6
1313
}
1414
}
1515

16+
fn format_registry(ident: &Ident, version: &zpm_semver::Version, url: Option<&String>) -> String {
17+
match url {
18+
Some(url) => format!("npm:{}@{}#{}", ident.to_file_string(), version.to_file_string(), url.to_file_string()),
19+
None => format!("npm:{}@{}", ident.to_file_string(), version.to_file_string()),
20+
}
21+
}
22+
1623
fn format_workspace_path(path: &Path) -> String {
1724
if path.is_empty() {
1825
"workspace:.".to_string()
@@ -47,12 +54,13 @@ pub enum Reference {
4754
version: zpm_semver::Version,
4855
},
4956

50-
#[pattern(r"npm:(?<ident>.*)@(?<version>.*)")]
51-
#[to_file_string(|params| format!("npm:{}@{}", params.ident.to_file_string(), params.version.to_file_string()))]
52-
#[to_print_string(|params| DataType::Reference.colorize(&format!("npm:{}@{}", params.ident.to_file_string(), params.version.to_file_string())))]
57+
#[pattern(r"npm:(?<ident>(?:@[^#@]+/)?[^#@]+)@(?<version>[^#]*)(?:#(?<url>.*))?")]
58+
#[to_file_string(|params| format_registry(&params.ident, &params.version, params.url.as_deref()))]
59+
#[to_print_string(|params| DataType::Reference.colorize(&format_registry(&params.ident, &params.version, params.url.as_deref())))]
5360
Registry {
5461
ident: Ident,
5562
version: zpm_semver::Version,
63+
url: Option<UrlEncoded<String>>,
5664
},
5765

5866
#[pattern(r"file:(?<path>.*\.(?:tgz|tar\.gz))")]

packages/zpm-utils/src/serialization.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::fmt;
1+
use std::{fmt, string::FromUtf8Error};
22

33
use colored::Colorize;
44
use erased_serde::serialize_trait_object;
@@ -209,7 +209,7 @@ impl ToHumanString for std::time::Duration {
209209
}
210210

211211
impl FromFileString for String {
212-
type Error = std::convert::Infallible;
212+
type Error = FromUtf8Error;
213213

214214
fn from_file_string(s: &str) -> Result<Self, Self::Error> {
215215
Ok(s.to_string())

0 commit comments

Comments
 (0)