use crate::lockfile::package::PackageColumns as _;
use core::cmp::Ordering;
use bun_collections::HashMap;
use bun_core::strings;
use bun_semver::String as SemverString;
use crate::lockfile_real::package::Alphabetizer;
use bun_install::Dependency;
use bun_install::PackageID;
use bun_install::Resolution;
use bun_install::dependency::{self, Behavior, VersionExt as _};
use bun_install::lockfile::package;
use crate::integrity;
use crate::lockfile_real::Printer;
pub fn print(this: &mut Printer, writer: &mut impl bun_io::Write) -> Result<(), bun_core::Error> {
#[cfg(debug_assertions)]
{
let _ = &this.lockfile;
}
writer.write_all(
b"# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.\n\
# yarn lockfile v1\n\
# bun ./bun.lockb --hash:",
)?;
write!(writer, " {}\n\n", this.lockfile.fmt_meta_hash())?;
packages(this, writer)
}
fn packages(this: &mut Printer, writer: &mut impl bun_io::Write) -> Result<(), bun_core::Error> {
let slice = this.lockfile.packages.slice();
let names: &[SemverString] = slice.items_name();
let resolved: &[Resolution] = slice.items_resolution();
let metas: &[package::Meta] = slice.items_meta();
if names.is_empty() {
return Ok(());
}
let dependency_lists = slice.items_dependencies();
let resolutions_buffer: &[PackageID] = this.lockfile.buffers.resolutions.as_slice();
let dependencies_buffer: &[Dependency] = this.lockfile.buffers.dependencies.as_slice();
let mut requested_versions: HashMap<PackageID, (usize, usize)> = HashMap::default();
let mut all_requested_versions_buf: Vec<dependency::Version> =
Vec::with_capacity(resolutions_buffer.len());
let package_count = names.len() as PackageID;
let mut alphabetized_names: Vec<PackageID> = vec![0; (package_count - 1) as usize];
let string_buf: &[u8] = this.lockfile.buffers.string_bytes.as_slice();
{
let mut i: PackageID = 1;
while i < package_count {
alphabetized_names[(i - 1) as usize] = i;
let mut resolutions = resolutions_buffer;
let mut dependencies = dependencies_buffer;
let mut j: usize = 0;
let requested_version_start = all_requested_versions_buf.len();
while let Some(k) = resolutions.iter().position(|&r| r == i) {
j += 1;
all_requested_versions_buf.push(dependencies[k].version.clone());
dependencies = &dependencies[k + 1..];
resolutions = &resolutions[k + 1..];
}
let dependency_versions = &mut all_requested_versions_buf[requested_version_start..];
if dependency_versions.len() > 1 {
dependency_versions.sort_by(|a, b| {
if dependency::Version::is_less_than_with_tag(string_buf, a, b) {
Ordering::Less
} else if dependency::Version::is_less_than_with_tag(string_buf, b, a) {
Ordering::Greater
} else {
Ordering::Equal
}
});
}
requested_versions.insert(i, (requested_version_start, j));
i += 1;
}
}
{
let alphabetizer = Alphabetizer::<u64> {
names: names.into(),
buf: string_buf.into(),
resolutions: resolved.into(),
};
alphabetized_names.sort_unstable_by(|&a, &b| alphabetizer.order(a, b));
}
for &i in alphabetized_names.iter() {
let name: &[u8] = names[i as usize].slice(string_buf);
let resolution = &resolved[i as usize];
let meta = &metas[i as usize];
let dependencies: &[Dependency] = dependency_lists[i as usize].get(dependencies_buffer);
let version_formatter = resolution.fmt(string_buf, bun_core::fmt::PathSep::Posix);
{
writer.write_all(b"\n")?;
let (rv_start, rv_len) = *requested_versions.get(&i).unwrap();
let dependency_versions = &all_requested_versions_buf[rv_start..rv_start + rv_len];
let always_needs_quote = strings::must_escape_yaml_string(name);
let mut prev_dependency_version: Option<&dependency::Version> = None;
let mut needs_comma = false;
for dependency_version in dependency_versions {
if needs_comma {
if let Some(prev) = prev_dependency_version {
if prev.eql(dependency_version, string_buf, string_buf) {
continue;
}
}
writer.write_all(b", ")?;
}
let version_name: &[u8] = dependency_version.literal.slice(string_buf);
let needs_quote = always_needs_quote
|| strings::index_of_any(version_name, b" |\t-/!:\"\\,\n\r").is_some()
|| version_name.starts_with(b"npm:");
if needs_quote {
writer.write_all(b"\"")?;
write!(
writer,
"{}",
bun_core::fmt::format_json_string_utf8(
name,
bun_core::fmt::JSONFormatterUTF8Options { quote: false }
),
)?;
} else {
writer.write_all(name)?;
}
writer.write_all(b"@")?;
if version_name.is_empty() {
writer.write_all(b"^")?;
version_formatter.write_to(writer)?;
} else if needs_quote {
write!(
writer,
"{}",
bun_core::fmt::format_json_string_utf8(
version_name,
bun_core::fmt::JSONFormatterUTF8Options { quote: false }
),
)?;
} else {
writer.write_all(version_name)?;
}
if needs_quote {
writer.write_all(b"\"")?;
}
prev_dependency_version = Some(dependency_version);
needs_comma = true;
}
writer.write_all(b":\n")?;
}
{
let mut quoted_buf: Vec<u8> = Vec::new();
writer.write_all(b" version ")?;
version_formatter.write_to(&mut quoted_buf)?;
writeln!(
writer,
"{}",
bun_core::fmt::format_json_string_utf8("ed_buf, Default::default()),
)?;
writer.write_all(b" resolved ")?;
let url_formatter = resolution.fmt_url(string_buf);
quoted_buf.clear();
url_formatter.write_to(&mut quoted_buf)?;
writeln!(
writer,
"{}",
bun_core::fmt::format_json_string_utf8("ed_buf, Default::default()),
)?;
if meta.integrity.tag != integrity::Tag::UNKNOWN {
writeln!(writer, " integrity {}", meta.integrity)?;
}
if !dependencies.is_empty() {
let mut behavior = Behavior::default();
let mut dependency_behavior_change_count: u8 = 0;
for dep in dependencies {
if !dep.behavior.eq(&behavior) {
if dep.behavior.is_optional() {
writer.write_all(b" optionalDependencies:\n")?;
if cfg!(debug_assertions) {
dependency_behavior_change_count += 1;
}
} else if dep.behavior.is_prod() {
writer.write_all(b" dependencies:\n")?;
if cfg!(debug_assertions) {
dependency_behavior_change_count += 1;
}
} else if dep.behavior.is_dev() {
writer.write_all(b" devDependencies:\n")?;
if cfg!(debug_assertions) {
dependency_behavior_change_count += 1;
}
} else {
continue;
}
behavior = dep.behavior;
if cfg!(debug_assertions) {
debug_assert!(dependency_behavior_change_count < 3);
}
}
writer.write_all(b" ")?;
let dependency_name: &[u8] = dep.name.slice(string_buf);
let needs_quote = strings::must_escape_yaml_string(dependency_name);
if needs_quote {
write!(
writer,
"{}",
bun_core::fmt::format_json_string_utf8(
dependency_name,
Default::default()
),
)?;
} else {
writer.write_all(dependency_name)?;
}
writeln!(
writer,
" {}",
bun_core::fmt::format_json_string_utf8(
dep.version.literal.slice(string_buf),
Default::default()
),
)?;
}
let _ = dependency_behavior_change_count;
}
}
}
Ok(())
}