Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 60 additions & 1 deletion src/install/PackageManager/PackageJSONEditor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -582,11 +582,30 @@ fn for_each_catalog_object(
Ok(())
}

/// Is `name` defined in any `catalog`/`catalogs` group?
fn catalog_defines_package(root_package_json: &Expr, name: &[u8]) -> bool {
let mut found = false;
let _ = for_each_catalog_object(root_package_json, |_catalog_name, catalog_expr| {
if !found {
if let Some(obj) = catalog_expr.data.e_object() {
if obj.has_property(name) {
found = true;
}
}
}
Ok(())
});
found
}

/// Records the original version of every catalog entry and, with `--latest`,
/// rewrites each to `latest` in memory so the resolver fetches it.
/// `names_filter` restricts recording to the named targets (every group
/// defining a name, as in the interactive updater); `None` records everything.
Comment thread
robobun marked this conversation as resolved.
pub(crate) fn edit_catalogs_before_update(
manager: &mut PackageManager,
root_package_json: &Expr,
names_filter: Option<&[Box<[u8]>]>,
) -> Result<bool, bun_alloc::AllocError> {
// see note in `edit_update_no_args` — always avoid the store
let _guard = ExprDisabler::scope();
Expand Down Expand Up @@ -618,6 +637,15 @@ pub(crate) fn edit_catalogs_before_update(
continue;
}

if let Some(filter) = names_filter {
let key_str = key
.as_utf8_string_literal()
.unwrap_or_else(|| bun_core::out_of_memory());
if !filter.iter().any(|n| strings::eql_long(n, key_str, true)) {
continue;
}
}

let version_literal = value
.as_utf8_string_literal()
.unwrap_or_else(|| bun_core::out_of_memory());
Expand Down Expand Up @@ -904,6 +932,7 @@ pub(crate) fn edit(
let mut i: usize = 0;
'loop_: while i < updates.len() {
let request = &mut updates[i];
let mut matched_catalog_reference = false;
// order-insensitive scan: `FOUR` is fine here
'dependency_group: for list in DependencyGroup::FOUR.map(|g| g.prop) {
if let Some(query) = current_package_json.as_property(list) {
Expand All @@ -922,6 +951,11 @@ pub(crate) fn edit(
},
);

// The kept reference points at a catalog
// entry; that entry is the update target.
Comment thread
robobun marked this conversation as resolved.
matched_catalog_reference |=
keep_catalog_reference && options.before_install;

if request.package_id != INVALID_PACKAGE_ID
&& strings::eql_long(list, dependency_list, true)
&& !keep_catalog_reference
Expand Down Expand Up @@ -1070,11 +1104,36 @@ pub(crate) fn edit(
}
}
}
if matched_catalog_reference {
updates[i].is_catalog = true;
}
i += 1;
}
}
}

// Catalog named targets are handled by `edit_catalogs_*`, not the append
// block below. Classify only before install: post-install, a root-group
// match takes the `replacing` branch, which leaves `e_string` unset and
// must not be reclassified here.
Comment thread
robobun marked this conversation as resolved.
if manager.subcommand == Subcommand::Update {
for request in updates.iter_mut() {
if options.before_install
&& !request.is_catalog
&& request.e_string.is_none()
&& request.package_id == INVALID_PACKAGE_ID
&& catalog_defines_package(current_package_json, request.get_name())
{
request.is_catalog = true;
}
// A catalog-only target occupies no dependency-group slot; a kept
// `catalog:` reference was already counted when it matched.
Comment thread
robobun marked this conversation as resolved.
if request.is_catalog && request.e_string.is_none() {
remaining -= 1;
}
Comment thread
robobun marked this conversation as resolved.
Comment thread
robobun marked this conversation as resolved.
}
}
Comment thread
robobun marked this conversation as resolved.

if remaining != 0 {
let mut new_dependencies: Vec<G::Property> = {
let mut dependencies: Vec<G::Property> = Vec::new();
Expand Down Expand Up @@ -1149,7 +1208,7 @@ pub(crate) fn edit(
};

for request in updates.iter_mut() {
if request.e_string.is_some() {
if request.e_string.is_some() || request.is_catalog {
continue;
}

Expand Down
4 changes: 4 additions & 0 deletions src/install/PackageManager/UpdateRequest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ pub struct UpdateRequest {
pub(crate) package_id: PackageID,
pub(crate) is_aliased: bool,
pub failed: bool,
/// The target lives only in a `catalog`/`catalogs` map; its catalog entry
/// is updated instead of adding a root dependency.
Comment thread
robobun marked this conversation as resolved.
pub(crate) is_catalog: bool,
/// This must be cloned to handle when the AST store resets.
/// ARENA-owned (AST `Expr.Data` store) — raw pointer per LIFETIMES.tsv;
/// only valid while the store that allocated it is alive.
Expand All @@ -49,6 +52,7 @@ impl Default for UpdateRequest {
package_id: INVALID_PACKAGE_ID,
is_aliased: false,
failed: false,
is_catalog: false,
e_string: None,
}
}
Expand Down
50 changes: 33 additions & 17 deletions src/install/PackageManager/updatePackageJSONAndInstall.rs
Original file line number Diff line number Diff line change
Expand Up @@ -596,12 +596,28 @@ fn update_package_json_and_install_with_manager_with_updates(
.as_deref()
.is_none_or(|t| t.iter().any(|w| w.is_root));

let catalog_request_names: Vec<Box<[u8]>> = manager
.update_requests
.iter()
.filter(|r| r.is_catalog)
.map(|r| Box::<[u8]>::from(r.get_name()))
.collect();

if subcommand == Subcommand::Update
&& manager.update_requests.is_empty()
&& (manager.update_requests.is_empty() || !catalog_request_names.is_empty())
&& root_is_targeted
{
let names_filter = if manager.update_requests.is_empty() {
None
} else {
Some(&catalog_request_names[..])
};
let root_package_json_root: bun_ast::Expr = root_package_json.root;
if PackageJSONEditor::edit_catalogs_before_update(manager, &root_package_json_root)? {
if PackageJSONEditor::edit_catalogs_before_update(
manager,
&root_package_json_root,
names_filter,
)? {
editing_catalogs = true;

if manager.options.do_.contains(Do::UPDATE_TO_LATEST) {
Expand Down Expand Up @@ -671,21 +687,6 @@ fn update_package_json_and_install_with_manager_with_updates(
},
)?;
}

if editing_catalogs
&& manager.workspace_name_hash.is_none()
&& manager.update_target_workspaces.is_none()
{
// running from root: catalogs live in this file.
let _ = PackageJSONEditor::edit_catalogs_after_update(
manager,
&new_package_json,
EditOptions {
exact_versions: manager.options.enable.exact_versions(),
..Default::default()
},
)?;
}
} else {
let mut updates_slice: &mut [UpdateRequest] = &mut updates[..];
PackageJSONEditor::edit(
Expand All @@ -703,6 +704,21 @@ fn update_package_json_and_install_with_manager_with_updates(
},
)?;
}

if editing_catalogs
&& manager.workspace_name_hash.is_none()
&& manager.update_target_workspaces.is_none()
{
// running from root: catalogs live in this file.
let _ = PackageJSONEditor::edit_catalogs_after_update(
manager,
&new_package_json,
EditOptions {
exact_versions: manager.options.enable.exact_versions(),
..Default::default()
},
)?;
}
let mut buffer_writer_two = js_printer::BufferWriter::init();
buffer_writer_two.buffer.list.reserve(
(source.contents.len() + 1).saturating_sub(buffer_writer_two.buffer.list.len()),
Expand Down
6 changes: 6 additions & 0 deletions src/install/lockfile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -727,8 +727,11 @@ impl Lockfile {
continue;
}
let res = resolutions_of_yore[old_resolution as usize];
// a `catalog:` reference keeps its literal; the
// catalog definition is updated instead
Comment thread
robobun marked this conversation as resolved.
if res.tag != ResolutionTag::Npm
|| update.version.tag != dependency::Tag::DistTag
|| dep.version.tag == dependency::Tag::Catalog
{
continue;
}
Expand Down Expand Up @@ -782,8 +785,11 @@ impl Lockfile {
continue;
}
let res = resolutions_of_yore[old_resolution as usize];
// a `catalog:` reference keeps its literal; the
// catalog definition is updated instead
Comment thread
robobun marked this conversation as resolved.
if res.tag != ResolutionTag::Npm
|| update.version.tag != dependency::Tag::DistTag
|| dep.version.tag == dependency::Tag::Catalog
{
continue;
}
Expand Down
Loading