Skip to content
22 changes: 18 additions & 4 deletions src/install/lockfile/Package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -996,6 +996,15 @@ pub struct DiffSummary {
ArrayHashMap<TruncatedPackageNameHash, AddedTrustedDependency, ArrayIdentityContext>,
pub removed_trusted_dependencies: TrustedDependenciesSet,

/// Flags a `None → Some(empty)` transition on `trusted_dependencies`
/// (i.e. the user just added `"trustedDependencies": []` to package.json
/// on an existing project). Case 2/3/case-4-with-entries already flow
/// into `added_` / `removed_trusted_dependencies` so `has_diffs()`
/// reports them; the one remaining transition that has no elements to
/// put in either set is `None → Some({})`, which still needs a save so
/// the lockfile persists the empty array.
pub trusted_dependencies_changed: bool,

pub patched_dependencies_changed: bool,
}

Expand All @@ -1009,6 +1018,7 @@ impl DiffSummary {
|| self.catalogs_changed
|| self.added_trusted_dependencies.count() > 0
|| self.removed_trusted_dependencies.count() > 0
|| self.trusted_dependencies_changed
|| self.patched_dependencies_changed
}
}
Expand Down Expand Up @@ -1336,11 +1346,15 @@ impl Diff {
)?;
}

{
// removed
// none
}
// Transitioning from "use defaults" (None) to an explicit set
// — even an empty one — is itself a semantic diff: the user
// just opted out of the default allow list. With an empty
// `to`, the loop above records zero additions, so flag the
// transition here so `has_diffs()` returns true and the
// lockfile is rewritten to persist the `[]`.
summary.trusted_dependencies_changed = true;

// removed: none
break 'trusted_dependencies;
}
}
Expand Down
29 changes: 20 additions & 9 deletions src/install/lockfile/bun.lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -410,17 +410,28 @@ impl Stringifier {
tree_sort_buf.sort_by(tree_sort_is_less_than);
// PERF(port): std.sort.pdq

if found_trusted_dependencies.len() > 0 {
// Emit `trustedDependencies` whenever the user defined it in
// package.json, even as `[]` or an array of packages that aren't
// installed. The lockfile's `trusted_dependencies` is `Some(set)`
// iff the key was present; `None` means "use Bun's default allow
// list". Dropping the key when the set didn't match any installed
// dep would make a later reload silently fall back to the
// defaults, re-enabling postinstall for packages the user meant
// to block.
if lockfile.trusted_dependencies.is_some() {
Self::write_indent(writer, *indent)?;
writer.write_all(b"\"trustedDependencies\": [\n")?;
*indent += 1;
for dep_name in found_trusted_dependencies.values() {
Self::write_indent(writer, *indent)?;
writeln!(writer, "\"{}\",", bstr::BStr::new(dep_name.slice(buf)))?;
if found_trusted_dependencies.is_empty() {
writer.write_all(b"\"trustedDependencies\": [],\n")?;
} else {
writer.write_all(b"\"trustedDependencies\": [\n")?;
*indent += 1;
for dep_name in found_trusted_dependencies.values() {
Self::write_indent(writer, *indent)?;
writeln!(writer, "\"{}\",", bstr::BStr::new(dep_name.slice(buf)))?;
}
Self::dec_indent(writer, indent)?;
writer.write_all(b"],\n")?;
}

Self::dec_indent(writer, indent)?;
writer.write_all(b"],\n")?;
}

if found_patched_dependencies.len() > 0 {
Expand Down
Loading
Loading