Skip to content
12 changes: 9 additions & 3 deletions src/install/PackageManager/PackageManagerOptions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -287,16 +287,22 @@ impl Options {
/// `pm.lockfile` can disjointly borrow `pm.options` instead of needing the
/// whole `&PackageManager`.
pub fn scope_for_package_name(&self, name: &[u8]) -> &Npm::registry::Scope {
self.named_scope_for_package_name(name)
.unwrap_or(&self.scope)
}

/// The `@scope:registry=` entry for a scoped package name, if any.
pub fn named_scope_for_package_name(&self, name: &[u8]) -> Option<&Npm::registry::Scope> {
if name.is_empty() || name[0] != b'@' {
Comment thread
robobun marked this conversation as resolved.
Resolved
return &self.scope;
return None;
}
let scope_name = Npm::registry::Scope::get_name(name);
// Compare the stored scope name, not just its hash: a different scope
// whose hash collides must not inherit this scope's registry or token.
// Fall back to the default registry on a mismatch.
match self.registries.get(&Npm::registry::Scope::hash(scope_name)) {
Some(scope) if *scope.name == *scope_name => scope,
_ => &self.scope,
Some(scope) if *scope.name == *scope_name => Some(scope),
_ => None,
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/install/extract_tarball.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ pub fn build_url_with_printer<R, E>(
let full_name = full_name_.slice();

let mut name = full_name;
if name[0] == b'@' {
if name.first() == Some(&b'@') {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
if let Some(i) = strings::index_of_char(name, b'/') {
name = &name[i as usize + 1..];
}
Expand Down
1 change: 1 addition & 0 deletions src/install/lockfile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2057,6 +2057,7 @@ impl Lockfile {
if let Err(_e) = TextLockfile::Stringifier::save_from_binary(
self,
load_result,
options,
config_version,
&mut writer_buf,
) {
Expand Down
88 changes: 83 additions & 5 deletions src/install/lockfile/bun.lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use crate::config_version::ConfigVersion;
use crate::extract_tarball as ExtractTarball;
use crate::integrity::Integrity;
use crate::npm::Negatable;
use crate::package_manager_real::Options as PackageManagerOptions;
use crate::repository::RepositoryExt as _;
use crate::{
DependencyID, Npm, Origin, PackageID, PackageManager, PackageNameHash, Repository, Resolution,
Expand Down Expand Up @@ -61,6 +62,77 @@ pub(crate) fn url_is_under_registry(url: &[u8], registry: &[u8]) -> bool {
&& (url.len() == registry.len() || url[registry.len()] == b'/')
}

/// Decides when the writer replaces an npm tarball URL with `""`.
struct NpmUrlWriter<'a> {
options: &'a PackageManagerOptions,
registry: Vec<u8>,
scratch: Vec<u8>,
}

impl<'a> NpmUrlWriter<'a> {
fn new(options: &'a PackageManagerOptions) -> Self {
Self {
options,
registry: crate::yarn_berry::registry_without_auth(options.scope.url.href()),
Comment thread
robobun marked this conversation as resolved.
Resolved
scratch: Vec::new(),
}
}

/// `true` when the writer serializes `url` as `""`.
fn writes_empty(
&mut self,
url: &[u8],
name: &[u8],
version: Semver::Version,
has_integrity: bool,
buf: &[u8],
) -> bool {
if self
.options
.named_scope_for_package_name(name)
.is_some_and(|scope| scope.url_hash != self.options.scope.url_hash)
{
return false;
}
if url_is_under_registry(url, Npm::Registry::DEFAULT_URL.as_bytes()) {
Comment thread
robobun marked this conversation as resolved.
Resolved
return true;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
if !has_integrity {
return false;
}
Comment thread
robobun marked this conversation as resolved.
Resolved
if url_is_under_registry(url, &self.registry) {
return self.is_canonical(url, name, version, buf);
}
// `Scope::set_url` normalizes the registry href; `dist.tarball` is verbatim.
let normalized = bun_url::whatwg::href_from_string(&bun_core::StringView::borrow_utf8(url));
if normalized.tag() == bun_core::string::Tag::Dead {
return false;
}
let normalized = normalized.to_utf8();
url_is_under_registry(&normalized, &self.registry)
&& self.is_canonical(&normalized, name, version, buf)
Comment thread
robobun marked this conversation as resolved.
Resolved
}

/// `url` is exactly `ExtractTarball::build_url` for the top-level registry.
fn is_canonical(
&mut self,
url: &[u8],
name: &[u8],
version: Semver::Version,
buf: &[u8],
) -> bool {
self.scratch.clear();
let printed = ExtractTarball::build_url_with_printer(
&self.registry,
&strings::StringOrTinyString::init(name),
version,
buf,
|args| std::io::Write::write_fmt(&mut self.scratch, args),
);
printed.is_ok() && self.scratch == url
}
}
Comment thread
robobun marked this conversation as resolved.
Resolved

// A single `lockfile.string_buf()` held for the whole parser would lock out
// every other `lockfile.*` access (the `string_buf()` method borrows the whole
// receiver). Construct a fresh `Buf` at each append site so the disjoint
Expand Down Expand Up @@ -269,9 +341,11 @@ impl Stringifier {
pub(crate) fn save_from_binary(
lockfile: &mut BinaryLockfile,
load_result: &LoadResult,
options: &PackageManagerOptions,
config_version: ConfigVersion,
writer: &mut Writer,
) -> Result<(), WriteError> {
let mut npm_url = NpmUrlWriter::new(options);
let buf = lockfile.buffers.string_bytes.as_slice();
let extern_strings = lockfile.buffers.extern_strings.as_slice();
let deps_buf = lockfile.buffers.dependencies.as_slice();
Expand Down Expand Up @@ -718,7 +792,7 @@ impl Stringifier {
// INFO = { prod/dev/optional/peer dependencies, os, cpu, libc, hasInstallScript, bin, binDir }

// first index is resolution for each type of package
// npm -> [ "name@version", registry (TODO: remove if default), INFO, integrity]
// npm -> [ "name@version", registry ("" when it is the configured one), INFO, integrity]
// symlink -> [ "name@link:path", INFO ]
// folder -> [ "name@file:path", INFO ]
// workspace -> [ "name@workspace:path" ] // workspace is only path
Expand Down Expand Up @@ -825,16 +899,20 @@ impl Stringifier {
res.npm().version.fmt(buf),
)?;

// only write the registry if it's not the default. empty string means default registry
let url = res.npm().url;
// Empty string means the registry the reader has configured.
let npm = res.npm();
let url = npm.url;
let url_slice = url.slice(buf);
write!(
writer,
"\"{}\", ",
bun_core::fmt::format_json_string_utf8(
if url_is_under_registry(
if npm_url.writes_empty(
url_slice,
Npm::Registry::DEFAULT_URL.as_bytes(),
pkg_name.slice(buf),
npm.version,
pkg_meta.integrity.tag.is_supported(),
buf,
) {
b"" as &[u8]
} else {
Expand Down
24 changes: 12 additions & 12 deletions test/cli/install/__snapshots__/bun-install-registry.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ exports[`text lockfile workspace sorting 1`] = `

"c": ["c@workspace:packages/c"],

"no-deps": ["no-deps@1.0.0", "http://localhost:1234/no-deps/-/no-deps-1.0.0.tgz", {}, "sha512-v4w12JRjUGvfHDUP8vFDwu0gUWu04j0cv9hLb1Abf9VdaXu4XcrddYFTMVBVvmldKViGWH7jrb6xPJRF0wq6gw=="],
"no-deps": ["no-deps@1.0.0", "", {}, "sha512-v4w12JRjUGvfHDUP8vFDwu0gUWu04j0cv9hLb1Abf9VdaXu4XcrddYFTMVBVvmldKViGWH7jrb6xPJRF0wq6gw=="],
}
}
"
Expand Down Expand Up @@ -72,7 +72,7 @@ exports[`text lockfile workspace sorting 2`] = `

"c": ["c@workspace:packages/c"],

"no-deps": ["no-deps@1.0.0", "http://localhost:1234/no-deps/-/no-deps-1.0.0.tgz", {}, "sha512-v4w12JRjUGvfHDUP8vFDwu0gUWu04j0cv9hLb1Abf9VdaXu4XcrddYFTMVBVvmldKViGWH7jrb6xPJRF0wq6gw=="],
"no-deps": ["no-deps@1.0.0", "", {}, "sha512-v4w12JRjUGvfHDUP8vFDwu0gUWu04j0cv9hLb1Abf9VdaXu4XcrddYFTMVBVvmldKViGWH7jrb6xPJRF0wq6gw=="],
}
}
"
Expand All @@ -98,13 +98,13 @@ exports[`text lockfile --frozen-lockfile 1`] = `
},
},
"packages": {
"a-dep": ["a-dep@1.0.10", "http://localhost:1234/a-dep/-/a-dep-1.0.10.tgz", {}, "sha512-NeQ6Ql9jRW8V+VOiVb+PSQAYOvVoSimW+tXaR0CoJk4kM9RIk/XlAUGCsNtn5XqjlDO4hcH8NcyaL507InevEg=="],
"a-dep": ["a-dep@1.0.10", "", {}, "sha512-NeQ6Ql9jRW8V+VOiVb+PSQAYOvVoSimW+tXaR0CoJk4kM9RIk/XlAUGCsNtn5XqjlDO4hcH8NcyaL507InevEg=="],

"no-deps": ["no-deps@1.1.0", "http://localhost:1234/no-deps/-/no-deps-1.1.0.tgz", {}, "sha512-ebG2pipYAKINcNI3YxdsiAgFvNGp2gdRwxAKN2LYBm9+YxuH/lHH2sl+GKQTuGiNfCfNZRMHUyyLPEJD6HWm7w=="],
"no-deps": ["no-deps@1.1.0", "", {}, "sha512-ebG2pipYAKINcNI3YxdsiAgFvNGp2gdRwxAKN2LYBm9+YxuH/lHH2sl+GKQTuGiNfCfNZRMHUyyLPEJD6HWm7w=="],

"package1": ["package1@workspace:packages/pkg1"],

"peer-deps-too": ["peer-deps-too@1.0.0", "http://localhost:1234/peer-deps-too/-/peer-deps-too-1.0.0.tgz", { "peerDependencies": { "no-deps": "*" } }, "sha512-sBx0TKrsB8FkRN2lzkDjMuctPGEKn1TmNUBv3dJOtnZM8nd255o5ZAPRpAI2XFLHZAavBlK/e73cZNwnUxlRog=="],
"peer-deps-too": ["peer-deps-too@1.0.0", "", { "peerDependencies": { "no-deps": "*" } }, "sha512-sBx0TKrsB8FkRN2lzkDjMuctPGEKn1TmNUBv3dJOtnZM8nd255o5ZAPRpAI2XFLHZAavBlK/e73cZNwnUxlRog=="],
}
}
"
Expand All @@ -131,9 +131,9 @@ exports[`it should ignore peerDependencies within workspaces 1`] = `
"packages": {
"Baz": ["Baz@workspace:packages/baz"],

"a-dep": ["a-dep@1.0.10", "http://localhost:1234/a-dep/-/a-dep-1.0.10.tgz", {}, "sha512-NeQ6Ql9jRW8V+VOiVb+PSQAYOvVoSimW+tXaR0CoJk4kM9RIk/XlAUGCsNtn5XqjlDO4hcH8NcyaL507InevEg=="],
"a-dep": ["a-dep@1.0.10", "", {}, "sha512-NeQ6Ql9jRW8V+VOiVb+PSQAYOvVoSimW+tXaR0CoJk4kM9RIk/XlAUGCsNtn5XqjlDO4hcH8NcyaL507InevEg=="],

"no-deps": ["no-deps@2.0.0", "http://localhost:1234/no-deps/-/no-deps-2.0.0.tgz", {}, "sha512-W3duJKZPcMIG5rA1io5cSK/bhW9rWFz+jFxZsKS/3suK4qHDkQNxUTEXee9/hTaAoDCeHWQqogukWYKzfr6X4g=="],
"no-deps": ["no-deps@2.0.0", "", {}, "sha512-W3duJKZPcMIG5rA1io5cSK/bhW9rWFz+jFxZsKS/3suK4qHDkQNxUTEXee9/hTaAoDCeHWQqogukWYKzfr6X4g=="],
}
}
"
Expand Down Expand Up @@ -183,7 +183,7 @@ exports[`binaries root resolution bins 1`] = `
"packages": {
"fooooo": ["fooooo@root:", { "bin": "fooooo.js" }],

"no-deps": ["no-deps@1.0.0", "http://localhost:1234/no-deps/-/no-deps-1.0.0.tgz", {}, "sha512-v4w12JRjUGvfHDUP8vFDwu0gUWu04j0cv9hLb1Abf9VdaXu4XcrddYFTMVBVvmldKViGWH7jrb6xPJRF0wq6gw=="],
"no-deps": ["no-deps@1.0.0", "", {}, "sha512-v4w12JRjUGvfHDUP8vFDwu0gUWu04j0cv9hLb1Abf9VdaXu4XcrddYFTMVBVvmldKViGWH7jrb6xPJRF0wq6gw=="],
}
}
"
Expand All @@ -204,13 +204,13 @@ exports[`hoisting text lockfile is hoisted 1`] = `
},
},
"packages": {
"hoist-lockfile-1": ["hoist-lockfile-1@1.0.0", "http://localhost:1234/hoist-lockfile-1/-/hoist-lockfile-1-1.0.0.tgz", { "dependencies": { "hoist-lockfile-shared": "*" } }, "sha512-E2nwR7egMFDoYjeRno7CAa59kiwkLGfhTFy2Q335JWp2r2bDkwoAt1LdChd5PdGYkbo7SfViHkW44ga+WXA+eA=="],
"hoist-lockfile-1": ["hoist-lockfile-1@1.0.0", "", { "dependencies": { "hoist-lockfile-shared": "*" } }, "sha512-E2nwR7egMFDoYjeRno7CAa59kiwkLGfhTFy2Q335JWp2r2bDkwoAt1LdChd5PdGYkbo7SfViHkW44ga+WXA+eA=="],

"hoist-lockfile-2": ["hoist-lockfile-2@1.0.0", "http://localhost:1234/hoist-lockfile-2/-/hoist-lockfile-2-1.0.0.tgz", { "dependencies": { "hoist-lockfile-shared": "^1.0.1" } }, "sha512-7iNRBJF/U078n9oZW7aDvVLkA7+076a2ONEFvITpjKdhT07KWaBei0SzHkFYW4f3foGZPNlHsv0aAgk949TPJg=="],
"hoist-lockfile-2": ["hoist-lockfile-2@1.0.0", "", { "dependencies": { "hoist-lockfile-shared": "^1.0.1" } }, "sha512-7iNRBJF/U078n9oZW7aDvVLkA7+076a2ONEFvITpjKdhT07KWaBei0SzHkFYW4f3foGZPNlHsv0aAgk949TPJg=="],

"hoist-lockfile-3": ["hoist-lockfile-3@1.0.0", "http://localhost:1234/hoist-lockfile-3/-/hoist-lockfile-3-1.0.0.tgz", { "dependencies": { "hoist-lockfile-shared": ">=1.0.1" } }, "sha512-iGz7jH7jxz/zq4OZM8hhT7kUX2Ye1m+45SoyMVcWTM7ZB+cY306Ff1mQePKTjkn84/pJMITMdRgDv/qF8PuQUw=="],
"hoist-lockfile-3": ["hoist-lockfile-3@1.0.0", "", { "dependencies": { "hoist-lockfile-shared": ">=1.0.1" } }, "sha512-iGz7jH7jxz/zq4OZM8hhT7kUX2Ye1m+45SoyMVcWTM7ZB+cY306Ff1mQePKTjkn84/pJMITMdRgDv/qF8PuQUw=="],

"hoist-lockfile-shared": ["hoist-lockfile-shared@1.0.2", "http://localhost:1234/hoist-lockfile-shared/-/hoist-lockfile-shared-1.0.2.tgz", {}, "sha512-p7IQ/BbkTRLG/GUx6j2cDQ+vTUc/v9OW9Ss9igh/GFysbr0Qjriz/DiETnISkxYaTFitqOkUSOUkEKyeLNJsfQ=="],
"hoist-lockfile-shared": ["hoist-lockfile-shared@1.0.2", "", {}, "sha512-p7IQ/BbkTRLG/GUx6j2cDQ+vTUc/v9OW9Ss9igh/GFysbr0Qjriz/DiETnISkxYaTFitqOkUSOUkEKyeLNJsfQ=="],
}
}
"
Expand Down
Loading
Loading