Skip to content
Open
88 changes: 80 additions & 8 deletions src/install/PackageManager/workspace_manifests.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use core::fmt;

use bstr::BStr;
use bun_collections::HashMap;
use bun_core::{Global, Output};
Expand All @@ -11,6 +13,7 @@ use super::add_remove_with_filter::{WorkspaceTarget, fetch_entry, root_package_j
use super::workspace_selection::WorkspaceGraph;

/// Root + member package.json files parsed the way `bun install` parses them, into a throw-away lockfile.
/// Errors the parse only logs (an invalid catalog range, say) fail it here, as they fail `bun install`.
pub(crate) struct ScratchManifests {
pub(crate) lockfile: Lockfile,
pub(crate) log: bun_ast::Log,
Expand Down Expand Up @@ -47,7 +50,8 @@ impl ScratchManifests {
root_json,
&mut resolver,
Features::main(),
)
)?;
self.fail_on_logged_errors()
}

pub(crate) fn parse_member(
Expand All @@ -71,8 +75,67 @@ impl ScratchManifests {
&mut resolver,
Features::WORKSPACE,
)?;
self.fail_on_logged_errors()?;
Ok(pkg)
}

fn fail_on_logged_errors(&self) -> crate::Result<()> {
if self.log.has_errors() {
return Err(crate::Error::InstallFailed);
}
Ok(())
}
}

/// The workspace versions and catalogs `bun pm pack` / `bun publish` substitute, read from the
/// package.json files as they are now. Not from bun.lock: it has the versions of the last install,
/// and releases bump versions between that install and the publish.
Comment thread
robobun marked this conversation as resolved.
pub struct WorkspaceManifests {
lockfile: Lockfile,
root_package_json_path: Box<[u8]>,
}

impl WorkspaceManifests {
/// Exits with `bun install`'s errors when the root package.json or a workspace does not parse.
pub fn load(manager: &mut PackageManager) -> WorkspaceManifests {
let mut scratch = ScratchManifests::new();
if let Err(err) = scratch.parse_root(manager) {
crash(
&mut scratch.log,
err,
format_args!("failed to read the workspace's package.json files"),
);
}
WorkspaceManifests {
lockfile: scratch.lockfile,
root_package_json_path: root_package_json_path(),
}
}
Comment thread
claude[bot] marked this conversation as resolved.

/// The package.json whose `workspaces` and catalogs these are: the workspace root's when the
/// package being packed is one of its workspaces, otherwise the package's own.
Comment thread
robobun marked this conversation as resolved.
pub fn root_package_json_path(&self) -> &[u8] {
&self.root_package_json_path
}

/// The `version` in the package.json of the workspace named `name`. `None` when no workspace
/// has that name or its package.json has no (semver) version.
Comment thread
robobun marked this conversation as resolved.
pub fn workspace_version(&self, name: &[u8]) -> Option<impl fmt::Display + '_> {
let name_hash: PackageNameHash = bun_semver::string::Builder::string_hash(name);
let version = self.lockfile.workspace_versions.get(&name_hash)?;
Some(version.fmt(self.lockfile.buffers.string_bytes.as_slice()))
}

/// The range catalog `catalog_name` (`""` and `"default"` both name the default catalog)
/// declares for `dependency_name`, as written in the root package.json.
Comment thread
robobun marked this conversation as resolved.
pub fn catalog_version(&self, catalog_name: &[u8], dependency_name: &[u8]) -> Option<&[u8]> {
let string_buf = self.lockfile.buffers.string_bytes.as_slice();
let dependency = self
.lockfile
.catalogs
.find(string_buf, catalog_name, dependency_name)?;
Some(dependency.version.literal.slice(string_buf))
}
}

/// Graph index i == `targets[i]`; the target whose `name_hash` is `None` is the root.
Expand All @@ -83,7 +146,7 @@ pub(crate) fn relation_graph(
) -> WorkspaceGraph {
let mut scratch = ScratchManifests::new();
if let Err(err) = scratch.parse_root(manager) {
crash(&mut scratch.log, pattern, err);
crash_for_filter(&mut scratch.log, pattern, err);
}

let mut parsed: Vec<(u32, Package)> = Vec::with_capacity(targets.len());
Expand All @@ -94,7 +157,7 @@ pub(crate) fn relation_graph(
}
match scratch.parse_member(manager, target) {
Ok(pkg) => parsed.push((i as u32, pkg)),
Err(err) => crash(&mut scratch.log, pattern, err),
Err(err) => crash_for_filter(&mut scratch.log, pattern, err),
}
}

Expand Down Expand Up @@ -150,14 +213,23 @@ pub(crate) fn relation_graph(
WorkspaceGraph::from_edges(targets.len(), edges)
}

fn crash(log: &mut bun_ast::Log, pattern: &[u8], err: crate::Error) -> ! {
fn crash_for_filter(log: &mut bun_ast::Log, pattern: &[u8], err: crate::Error) -> ! {
crash(
log,
err,
format_args!(
"failed to read the workspace dependencies for --filter \"{}\"",
BStr::new(pattern)
),
)
}

/// The parse errors explain the failure when there are any; `what` and `err` are the fallback.
fn crash(log: &mut bun_ast::Log, err: crate::Error, what: fmt::Arguments<'_>) -> ! {
if log.has_errors() {
let _ = log.print(std::ptr::from_mut(Output::error_writer()));
} else {
Output::err_generic(
"failed to read the workspace dependencies for --filter \"{}\": {}",
(BStr::new(pattern), err.name()),
);
Output::err_generic("{}: {}", (what, err.name()));
}
Global::crash();
}
Loading
Loading