forked from oven-sh/bun
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlockfile.rs
More file actions
3479 lines (3077 loc) · 139 KB
/
Copy pathlockfile.rs
File metadata and controls
3479 lines (3077 loc) · 139 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
//! Lockfile — in-memory representation of bun.lock / bun.lockb
use core::cmp::Ordering;
use core::fmt;
use std::io::Write as _;
use bun_alloc::AllocError;
use bun_collections::{
ArrayHashMap, ArrayIdentityContext, ArrayIdentityContextU64, DynamicBitSet,
HashMap as BunHashMap, IdentityContext, LinearFifo, linear_fifo::DynamicBuffer,
};
use bun_core::fmt::PathSep;
use bun_core::{Error as BunError, Global, Output, err};
use bun_paths::{MAX_PATH_BYTES, PathBuffer, SEP, SEP_STR, platform, resolve_path};
// `bun_install` sits above `bun_resolver` in the crate graph (no cycle), so use
// the real resolver `FileSystem` directly — same as `PackageManager.rs`.
use crate::bun_json as JSON;
use bun_core::zstr;
use bun_core::{ZStr, strings};
use bun_dotenv as DotEnv;
use bun_perf::system_timer::Timer;
use bun_resolver::fs::{self as Fs, FileSystem};
use bun_semver::{self as Semver, ExternalString, String as SemverString};
use bun_sha_hmac as Crypto;
use bun_sys::{self as sys, Fd, File};
use crate::config_version::ConfigVersion;
use crate::migration;
use crate::package_manager::WorkspaceFilter;
use crate::package_manager_real::{
Options as PackageManagerOptions, options::LogLevel, populate_manifest_cache,
};
use crate::resolution_real::{self as resolution, Resolution};
use crate::string_builder;
use crate::update_request::UpdateRequest;
use crate::{
self as Install, DependencyID, ExternalSlice, Features, PackageID, PackageManager,
PackageNameAndVersionHash, PackageNameHash, TruncatedPackageNameHash, dependency,
dependency::Dependency, initialize_store, invalid_dependency_id, invalid_package_id,
npm as Npm,
};
// ────────────────────────────────────────────────────────────────────────────
// Sub-module declarations — explicit #[path] attrs for PascalCase / dotted
// file names.
// ────────────────────────────────────────────────────────────────────────────
#[path = "lockfile/Buffers.rs"]
pub mod buffers;
#[path = "lockfile/bun.lock.rs"]
pub mod bun_lock;
#[path = "lockfile/bun.lockb.rs"]
pub mod bun_lockb;
#[path = "lockfile/CatalogMap.rs"]
pub mod catalog_map;
#[path = "lockfile/lockfile_json_stringify_for_debugging.rs"]
pub mod lockfile_json_stringify_for_debugging;
#[path = "lockfile/OverrideMap.rs"]
pub mod override_map;
#[path = "lockfile/Package.rs"]
pub mod package;
#[path = "lockfile/Tree.rs"]
pub mod tree;
#[path = "lockfile/printer"]
pub mod printer_mods {
#[path = "tree_printer.rs"]
pub mod tree_printer;
#[path = "Yarn.rs"]
pub mod yarn;
}
// Sub-module re-exports
pub use self::buffers::Buffers;
use self::bun_lock as TextLockfile;
pub use self::bun_lockb as Serializer;
pub use self::catalog_map::CatalogMap;
pub use self::lockfile_json_stringify_for_debugging::json_stringify;
pub use self::override_map::OverrideMap;
pub use self::package::Package;
pub use self::tree::Tree;
pub use crate::padding_checker::assert_no_uninitialized_padding;
// Bring the derive-generated `items_*` column accessors (`PackageColumns` for
// `MultiArrayList<Package>`, `PackageColumns` for `Slice<Package>`) into scope.
use self::package::PackageColumns as _;
// Local aliases for module-level types.
type DependencyVersion = dependency::Version;
type ResolutionTag = resolution::Tag;
type SemverStringBuf<'a> = bun_semver::semver_string::Buf<'a>;
type SemverStringBuilder = bun_semver::semver_string::Builder;
// ────────────────────────────────────────────────────────────────────────────
// Type aliases / collection types
// ────────────────────────────────────────────────────────────────────────────
pub(crate) type PackageIDSlice = ExternalSlice<PackageID>;
pub type DependencySlice = ExternalSlice<Dependency>;
pub(crate) type DependencyIDSlice = ExternalSlice<DependencyID>;
pub(crate) type PackageIDList = Vec<PackageID>;
pub(crate) type DependencyList = Vec<Dependency>;
pub(crate) type DependencyIDList = Vec<DependencyID>;
pub(crate) type StringBuffer = Vec<u8>;
pub(crate) type ExternalStringBuffer = Vec<ExternalString>;
pub(crate) type NameHashMap = ArrayHashMap<PackageNameHash, SemverString, ArrayIdentityContextU64>;
/// Value is the exact byte string the key hash was computed from; lookups must
/// compare it since truncated hashes can collide. An empty value is the legacy
/// `bun.lockb` sentinel ("name unknown, hash-only match").
pub(crate) type TrustedDependenciesSet =
ArrayHashMap<TruncatedPackageNameHash, Box<[u8]>, ArrayIdentityContext>;
pub(crate) type VersionHashMap =
ArrayHashMap<PackageNameHash, Semver::Version, ArrayIdentityContextU64>;
pub(crate) type PatchedDependenciesMap =
ArrayHashMap<PackageNameAndVersionHash, PatchedDep, ArrayIdentityContextU64>;
pub(crate) type StringPool = bun_semver::string::StringPool;
pub(crate) type MetaHash = [u8; 32]; // Sha512T256.digest_length
pub(crate) const ZERO_HASH: MetaHash = [0u8; 32];
/// Result of `maybe_clone_filtering_root_packages`: either the input lockfile was
/// returned unchanged (borrowed), or a freshly-allocated cleaned lockfile is returned
/// (owned), so the caller can drop the `Box` when done.
pub enum Cleaned<'a> {
/// No changes needed — caller's lockfile is returned as-is.
Same(&'a mut Lockfile),
/// A new lockfile was allocated by `clean`; caller owns it.
New(Box<Lockfile>),
}
// The stream owns its backing `Vec<u8>` — every load path hands the file
// contents to the stream anyway,
// which avoids threading a `&mut [u8]` lifetime through the load call graph.
pub type Stream = bun_io::FixedBufferStream<Vec<u8>>;
/// Duck-typed surface that `Buffers::write_array`/`save` and
/// `Package::Serializer::save` expect of their `stream` parameter. Expressed
/// as a trait to stay generic over the borrowck-reshaped `StreamType` in
/// `bun.lockb.rs` (which collapses stream + writer into one `&mut`).
pub trait PositionalStream {
/// Current write position.
fn get_pos(&self) -> Result<usize, BunError>;
/// Positional write — returns bytes written (always `data.len()` for
/// in-memory buffers).
fn pwrite(&mut self, data: &[u8], index: usize) -> usize;
}
impl<'a> PositionalStream for Serializer::StreamType<'a> {
#[inline]
fn get_pos(&self) -> Result<usize, BunError> {
Serializer::StreamType::get_pos(self)
}
#[inline]
fn pwrite(&mut self, data: &[u8], index: usize) -> usize {
Serializer::StreamType::pwrite(self, data, index)
}
}
// ────────────────────────────────────────────────────────────────────────────
// Lockfile struct
// ────────────────────────────────────────────────────────────────────────────
pub struct Lockfile {
/// The version of the lockfile format, intended to prevent data corruption for format changes.
pub format: FormatVersion,
pub text_lockfile_version: bun_lock::Version,
pub meta_hash: MetaHash,
pub packages: PackageList,
pub buffers: Buffers,
/// name -> PackageID || [*]PackageID
/// Not for iterating.
pub package_index: PackageIndexMap,
pub string_pool: StringPool,
pub scratch: Scratch,
pub scripts: Scripts,
pub workspace_paths: NameHashMap,
pub workspace_versions: VersionHashMap,
/// Optional because `trustedDependencies` in package.json might be an
/// empty list or it might not exist
pub trusted_dependencies: Option<TrustedDependenciesSet>,
pub patched_dependencies: PatchedDependenciesMap,
pub overrides: OverrideMap,
pub catalogs: CatalogMap,
pub saved_config_version: Option<ConfigVersion>,
/// `packages.len()` at the moment lockfile load (including npm/pnpm/yarn
/// migration) finished. Packages with `id < this` were carried in from a
/// lockfile and represent a user-pinned resolution; packages with
/// `id >= this` were appended by manifest fetches in the current
/// resolve session. `get_package_id` uses this to keep its
/// order-independence guard from overriding lockfile pins. Set by
/// `mark_loaded_packages`; defaults to `invalid_package_id` (no lockfile
/// loaded → guard applies to nothing, equivalent to "all entries are
/// session-appended").
///
/// Runtime-only — never serialised.
pub loaded_package_count: PackageID,
/// `bit[id] == true` ⇔ package `id` was appended for a dependency whose
/// version range was an exact `=X.Y.Z` (i.e. the user — root or workspace
/// — pinned this exact version somewhere in the tree). `get_package_id`'s
/// order-independence guard never blocks deduping to one of these: an
/// exact pin is a deliberate choice, not an artifact of which manifest
/// happened to land first. Runtime-only — never serialised; sized lazily
/// in `mark_exact_pin`.
pub exact_pinned: DynamicBitSet,
}
pub(crate) type PackageList = self::package::List<u64>;
// ────────────────────────────────────────────────────────────────────────────
// DepSorter
// ────────────────────────────────────────────────────────────────────────────
pub(crate) struct DepSorter<'a> {
pub lockfile: &'a Lockfile,
}
impl<'a> DepSorter<'a> {
pub(crate) fn is_less_than(&self, l: DependencyID, r: DependencyID) -> bool {
let deps_buf = self.lockfile.buffers.dependencies.as_slice();
let string_buf = self.lockfile.buffers.string_bytes.as_slice();
let l_dep = &deps_buf[l as usize];
let r_dep = &deps_buf[r as usize];
match l_dep.behavior.cmp(r_dep.behavior) {
Ordering::Less => true,
Ordering::Greater => false,
Ordering::Equal => {
strings::order(l_dep.name.slice(string_buf), r_dep.name.slice(string_buf))
== Ordering::Less
}
}
}
}
// ────────────────────────────────────────────────────────────────────────────
// Scripts
// ────────────────────────────────────────────────────────────────────────────
#[derive(Default)]
pub struct Scripts {
pub preinstall: Vec<Box<[u8]>>,
pub install: Vec<Box<[u8]>>,
pub postinstall: Vec<Box<[u8]>>,
pub preprepare: Vec<Box<[u8]>>,
pub prepare: Vec<Box<[u8]>>,
pub postprepare: Vec<Box<[u8]>>,
}
impl Scripts {
pub const NAMES: [&'static str; 6] = [
"preinstall",
"install",
"postinstall",
"preprepare",
"prepare",
"postprepare",
];
/// Indexed mutable access matching `NAMES` order.
pub fn hook_mut(&mut self, i: usize) -> &mut Vec<Box<[u8]>> {
match i {
0 => &mut self.preinstall,
1 => &mut self.install,
2 => &mut self.postinstall,
3 => &mut self.preprepare,
4 => &mut self.prepare,
5 => &mut self.postprepare,
_ => unreachable!(),
}
}
/// (name, &entries) in `NAMES` order — single source of truth for the name half.
/// The field-ref half stays hand-listed (no field-by-name reflection),
/// but the string half is derived from `NAMES` so the literals exist exactly once.
fn fields(&self) -> [(&'static str, &Vec<Box<[u8]>>); 6] {
[
(Self::NAMES[0], &self.preinstall),
(Self::NAMES[1], &self.install),
(Self::NAMES[2], &self.postinstall),
(Self::NAMES[3], &self.preprepare),
(Self::NAMES[4], &self.prepare),
(Self::NAMES[5], &self.postprepare),
]
}
pub fn has_any(&self) -> bool {
for (_, list) in self.fields() {
if !list.is_empty() {
return true;
}
}
false
}
pub fn count(&self) -> usize {
let mut res: usize = 0;
for (_, list) in self.fields() {
res += list.len();
}
res
}
}
// `deinit` becomes `Drop` — body only frees owned fields → delete entirely; Vec<Box<[u8]>> drops automatically.
// ────────────────────────────────────────────────────────────────────────────
// LoadResult
// ────────────────────────────────────────────────────────────────────────────
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum LockfileFormat {
Text,
Binary,
}
impl LockfileFormat {
pub fn filename(self) -> &'static ZStr {
match self {
LockfileFormat::Text => zstr!("bun.lock"),
LockfileFormat::Binary => zstr!("bun.lockb"),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum LoadStep {
OpenFile,
ReadFile,
ParseFile,
Migrating,
}
#[derive(Clone, Copy, PartialEq, Eq, Default)]
pub enum Migrated {
#[default]
None,
Npm,
Yarn,
Pnpm,
}
pub struct LoadResultErr {
pub step: LoadStep,
pub value: BunError,
pub lockfile_path: &'static ZStr,
pub format: LockfileFormat,
}
pub struct LoadResultOk<'a> {
pub lockfile: &'a mut Lockfile,
pub loaded_from_binary_lockfile: bool,
pub migrated: Migrated,
pub serializer_result: Serializer::SerializerLoadResult,
pub format: LockfileFormat,
}
pub enum LoadResult<'a> {
NotFound,
Err(LoadResultErr),
Ok(LoadResultOk<'a>),
}
impl<'a> LoadResult<'a> {
pub fn loaded_from_text_lockfile(&self) -> bool {
match self {
LoadResult::NotFound => false,
LoadResult::Err(err) => err.format == LockfileFormat::Text,
LoadResult::Ok(ok) => ok.format == LockfileFormat::Text,
}
}
pub fn loaded_from_binary_lockfile(&self) -> bool {
match self {
LoadResult::NotFound => false,
LoadResult::Err(err) => err.format == LockfileFormat::Binary,
LoadResult::Ok(ok) => ok.format == LockfileFormat::Binary,
}
}
pub fn migrated_from_npm(&self) -> bool {
match self {
LoadResult::Ok(ok) => ok.migrated == Migrated::Npm,
_ => false,
}
}
pub fn save_format(&self, options: &PackageManagerOptions) -> LockfileFormat {
match self {
LoadResult::NotFound => {
// saving a lockfile for a new project. default to text lockfile
// unless saveTextLockfile is false in bunfig
let save_text_lockfile = options.save_text_lockfile.unwrap_or(true);
if save_text_lockfile {
LockfileFormat::Text
} else {
LockfileFormat::Binary
}
}
LoadResult::Err(err) => {
// an error occurred, but we still loaded from an existing lockfile
if let Some(save_text_lockfile) = options.save_text_lockfile {
if save_text_lockfile {
return LockfileFormat::Text;
}
}
err.format
}
LoadResult::Ok(ok) => {
// loaded from an existing lockfile
if let Some(save_text_lockfile) = options.save_text_lockfile {
if save_text_lockfile {
return LockfileFormat::Text;
}
if ok.migrated != Migrated::None {
return LockfileFormat::Binary;
}
}
if ok.migrated != Migrated::None {
return LockfileFormat::Text;
}
ok.format
}
}
}
/// configVersion and boolean for if the configVersion previously existed/needs to be saved to lockfile
pub fn choose_config_version(&self) -> (ConfigVersion, bool) {
match self {
LoadResult::NotFound | LoadResult::Err(_) => (ConfigVersion::CURRENT, true),
LoadResult::Ok(ok) => match ok.migrated {
Migrated::None => {
if let Some(config_version) = ok.lockfile.saved_config_version {
return (config_version, false);
}
// existing bun project without configVersion
(ConfigVersion::V0, true)
}
Migrated::Pnpm => (ConfigVersion::V1, true),
Migrated::Npm => (ConfigVersion::V0, true),
Migrated::Yarn => (ConfigVersion::V0, true),
},
}
}
// Callers reach this only after `handleLoadLockfileErrors` has exited on
// the `NotFound`/`Err` arms, so the variant is known-`Ok`.
bun_core::enum_unwrap!(pub LoadResult, Ok => fn ok / ok_mut -> LoadResultOk<'a>);
}
// ────────────────────────────────────────────────────────────────────────────
// InstallResult
// ────────────────────────────────────────────────────────────────────────────
// ────────────────────────────────────────────────────────────────────────────
// Lockfile impl — load / clone / hoist / etc.
// ────────────────────────────────────────────────────────────────────────────
impl Lockfile {
pub fn is_empty(&self) -> bool {
self.packages.len() == 0
|| (self.packages.len() == 1 && self.packages.get(0).resolutions.len == 0)
}
pub fn load_from_cwd<'a, const ATTEMPT_LOADING_FROM_OTHER_LOCKFILE: bool>(
&'a mut self,
manager: Option<&mut PackageManager>,
log: &mut bun_ast::Log,
) -> LoadResult<'a> {
self.load_from_dir::<ATTEMPT_LOADING_FROM_OTHER_LOCKFILE>(Fd::cwd(), manager, log)
}
pub fn load_from_dir<'a, const ATTEMPT_LOADING_FROM_OTHER_LOCKFILE: bool>(
&'a mut self,
dir: Fd,
mut manager: Option<&mut PackageManager>,
log: &mut bun_ast::Log,
) -> LoadResult<'a> {
debug_assert!(Fs::INSTANCE_LOADED.load(core::sync::atomic::Ordering::Relaxed));
let mut lockfile_format = LockfileFormat::Text;
let file: File = 'file: {
match File::openat(dir, zstr!("bun.lock"), sys::O::RDONLY, 0) {
sys::Result::Ok(f) => break 'file f,
sys::Result::Err(text_open_err) => {
if text_open_err.errno != sys::SystemErrno::ENOENT as u16 {
return LoadResult::Err(LoadResultErr {
step: LoadStep::OpenFile,
value: BunError::from(text_open_err),
lockfile_path: zstr!("bun.lock"),
format: LockfileFormat::Text,
});
}
lockfile_format = LockfileFormat::Binary;
match File::openat(dir, zstr!("bun.lockb"), sys::O::RDONLY, 0) {
sys::Result::Ok(f) => break 'file f,
sys::Result::Err(binary_open_err) => {
if binary_open_err.errno != sys::SystemErrno::ENOENT as u16 {
return LoadResult::Err(LoadResultErr {
step: LoadStep::OpenFile,
value: BunError::from(binary_open_err),
lockfile_path: zstr!("bun.lockb"),
format: LockfileFormat::Binary,
});
}
if ATTEMPT_LOADING_FROM_OTHER_LOCKFILE {
if let Some(pm) = manager {
// The format is carried inside the `LoadResult` itself.
return migration::detect_and_load_other_lockfile(
self, dir, pm, log,
);
}
}
return LoadResult::NotFound;
}
}
}
}
};
// `bun_sys::File::read_to_end` returns `Maybe<Vec<u8>>`
// (fstat-presized, pread-from-0); map the error arm to `.read_file`.
let buf = match file.read_to_end() {
Ok(bytes) => bytes,
Err(e) => {
return LoadResult::Err(LoadResultErr {
step: LoadStep::ReadFile,
value: BunError::from(e),
lockfile_path: if lockfile_format == LockfileFormat::Text {
zstr!("bun.lock")
} else {
zstr!("bun.lockb")
},
format: lockfile_format,
});
}
};
if lockfile_format == LockfileFormat::Text {
let source = bun_ast::Source::init_path_string(b"bun.lock", buf.as_slice());
initialize_store();
let parsed = match JSON::ParsedJson::parse_package_json(&source, log) {
Ok(j) => j,
Err(e) => {
return LoadResult::Err(LoadResultErr {
step: LoadStep::ParseFile,
value: e,
lockfile_path: zstr!("bun.lock"),
format: lockfile_format,
});
}
};
if let Err(e) =
TextLockfile::parse_into_binary_lockfile(self, parsed.root, &source, log, manager)
{
if matches!(e, TextLockfile::ParseError::OutOfMemory) {
bun_core::out_of_memory();
}
return LoadResult::Err(LoadResultErr {
step: LoadStep::ParseFile,
value: BunError::from(e),
lockfile_path: zstr!("bun.lock"),
format: lockfile_format,
});
}
bun_core::analytics::Features::text_lockfile_inc();
return LoadResult::Ok(LoadResultOk {
lockfile: self,
serializer_result: Serializer::SerializerLoadResult::default(),
loaded_from_binary_lockfile: false,
migrated: Migrated::None,
format: lockfile_format,
});
}
let mut result = self.load_from_bytes(manager.as_deref_mut(), buf, log);
// When BUN_DEBUG_TEST_TEXT_LOCKFILE is set, convert
// the freshly loaded binary lockfile into a text lockfile in memory,
// then parse it back into a binary lockfile, so the two codepaths
// cross-check each other in tests.
if bun_core::env_var::BUN_DEBUG_TEST_TEXT_LOCKFILE
.get()
.unwrap_or(false)
{
if let (LoadResult::Ok(ok), Some(manager)) = (&mut result, manager) {
let mut writer_buf: Vec<u8> = Vec::new();
// `save_from_binary` reads only `loaded_from_binary_lockfile()`
// from its `load_result` parameter. `result` itself cannot be
// passed while `ok.lockfile` is mutably borrowed, so hand it
// a stand-in that answers
// `format == Binary` the same way the real `Ok` result does.
let binary_origin = LoadResult::Err(LoadResultErr {
step: LoadStep::ParseFile,
value: err!("DebugTextLockfileRoundTrip"),
lockfile_path: zstr!("bun.lockb"),
format: LockfileFormat::Binary,
});
if let Err(e) = TextLockfile::Stringifier::save_from_binary(
&mut *ok.lockfile,
&binary_origin,
&manager.options,
&mut writer_buf,
) {
Output::panic(format_args!(
"failed to convert binary lockfile to text lockfile: {}",
e.name()
));
}
let source = bun_ast::Source::init_path_string(b"bun.lock", writer_buf.as_slice());
initialize_store();
let parsed = match JSON::ParsedJson::parse_package_json(&source, log) {
Ok(j) => j,
Err(e) => Output::panic(format_args!(
"failed to print valid json from binary lockfile: {}",
e.name()
)),
};
if let Err(e) = TextLockfile::parse_into_binary_lockfile(
&mut *ok.lockfile,
parsed.root,
&source,
log,
Some(manager),
) {
Output::panic(format_args!(
"failed to parse text lockfile converted from binary lockfile: {}",
<&'static str>::from(e)
));
}
bun_core::analytics::Features::text_lockfile_inc();
}
}
result
}
pub fn load_from_bytes<'a>(
&'a mut self,
pm: Option<&mut PackageManager>,
buf: Vec<u8>,
log: &mut bun_ast::Log,
) -> LoadResult<'a> {
let mut stream = Stream::new(buf);
self.format = FormatVersion::current();
self.scripts = Scripts::default();
self.trusted_dependencies = None;
self.workspace_paths = NameHashMap::default();
self.workspace_versions = VersionHashMap::default();
self.overrides = OverrideMap::default();
self.catalogs = CatalogMap::default();
self.patched_dependencies = PatchedDependenciesMap::default();
let load_result = match Serializer::load(self, &mut stream, log, pm) {
Ok(r) => r,
Err(e) => {
return LoadResult::Err(LoadResultErr {
step: LoadStep::ParseFile,
value: e,
lockfile_path: zstr!("bun.lockb"),
format: LockfileFormat::Binary,
});
}
};
if cfg!(debug_assertions) {
self.verify_data().expect("lockfile data is corrupt");
}
LoadResult::Ok(LoadResultOk {
lockfile: self,
serializer_result: load_result,
loaded_from_binary_lockfile: true,
migrated: Migrated::None,
format: LockfileFormat::Binary,
})
}
pub fn is_resolved_dependency_disabled(
&self,
dep_id: DependencyID,
features: Features,
meta: &package::Meta,
cpu: Npm::Architecture,
os: Npm::OperatingSystem,
) -> bool {
if meta.is_disabled(cpu, os) {
return true;
}
let dep = &self.buffers.dependencies[dep_id as usize];
dep.behavior.is_bundled() || !dep.behavior.is_enabled(features)
}
/// This conditionally clones the lockfile with root packages marked as non-resolved
/// that do not satisfy `Features`. The package may still end up installed even
/// if it was e.g. in "devDependencies" and its a production install. In that case,
/// it would be installed because another dependency or transient dependency needed it.
///
/// Warning: This potentially modifies the existing lockfile in-place. That is
/// safe to do because at this stage, the lockfile has already been saved to disk.
/// Our in-memory representation is all that's left.
pub fn maybe_clone_filtering_root_packages<'a>(
old: &'a mut Lockfile,
manager: &'a mut PackageManager,
features: Features,
exact_versions: bool,
log_level: LogLevel,
) -> Result<Cleaned<'a>, BunError> {
let old_packages = old.packages.slice();
let old_dependencies_lists = old_packages.items_dependencies();
let old_resolutions_lists = old_packages.items_resolutions();
let old_resolutions = old_packages.items_resolution();
let mut any_changes = false;
let end: PackageID = old.packages.len() as PackageID;
// set all disabled dependencies of workspaces to `invalid_package_id`
for package_id in 0..end as usize {
if package_id != 0 && old_resolutions[package_id].tag != ResolutionTag::Workspace {
continue;
}
let old_workspace_dependencies_list = old_dependencies_lists[package_id];
let old_workspace_resolutions_list = old_resolutions_lists[package_id];
let old_workspace_dependencies =
old_workspace_dependencies_list.get(old.buffers.dependencies.as_slice());
let old_workspace_resolutions =
old_workspace_resolutions_list.mut_(old.buffers.resolutions.as_mut_slice());
debug_assert_eq!(
old_workspace_dependencies.len(),
old_workspace_resolutions.len()
);
for (dependency, resolution) in old_workspace_dependencies
.iter()
.zip(old_workspace_resolutions.iter_mut())
{
if !dependency.behavior.is_enabled(features) && *resolution < end {
*resolution = invalid_package_id;
any_changes = true;
}
}
}
if !any_changes {
return Ok(Cleaned::Same(old));
}
old.clean(manager, &mut [], exact_versions, log_level)
.map(Cleaned::New)
}
fn preprocess_update_requests(
old: &mut Lockfile,
manager: &mut PackageManager,
updates: &mut [UpdateRequest],
exact_versions: bool,
) -> Result<(), BunError> {
let workspace_package_id = manager
.root_package_id
.get(old, manager.workspace_name_hash);
let root_deps_list: DependencySlice =
old.packages.items_dependencies()[workspace_package_id as usize];
if (root_deps_list.off as usize) < old.buffers.dependencies.len() {
// Split-borrow: `string_builder!` only takes
// `old.buffers.string_bytes` + `old.string_pool`, leaving
// `old.packages` / `old.buffers.{dependencies,resolutions}` free.
let mut string_builder = string_builder!(old);
{
let root_deps: &[Dependency] =
root_deps_list.get(old.buffers.dependencies.as_slice());
let old_resolutions_list =
old.packages.items_resolutions()[workspace_package_id as usize];
let old_resolutions: &[PackageID] =
old_resolutions_list.get(old.buffers.resolutions.as_slice());
let resolutions_of_yore: &[Resolution] = old.packages.items_resolution();
let packages_len = old.packages.len();
for update in updates.iter() {
if update.package_id == invalid_package_id {
debug_assert_eq!(root_deps.len(), old_resolutions.len());
for (dep, &old_resolution) in root_deps.iter().zip(old_resolutions.iter()) {
if dep.name_hash == SemverStringBuilder::string_hash(update.name) {
if old_resolution as usize >= packages_len {
continue;
}
let res = resolutions_of_yore[old_resolution as usize];
if res.tag != ResolutionTag::Npm
|| update.version.tag != dependency::Tag::DistTag
{
continue;
}
// TODO(dylan-conway): this will need to handle updating dependencies (exact, ^, or ~) and aliases
let npm_ver = res.npm().version;
let len = bun_core::fmt::count(format_args!(
"{}{}",
if exact_versions { "" } else { "^" },
npm_ver.fmt(string_builder.string_bytes.as_slice()),
));
if len >= SemverString::MAX_INLINE_LEN {
string_builder.cap += len;
}
}
}
}
}
}
string_builder.allocate()?;
// `string_builder.clamp()` must run once after the entire second
// loop completes. A scopeguard would mutably capture
// `string_builder`, conflicting with the `append` calls below. Call `clamp()`
// explicitly at the end of this block instead (the inner loop has no `?` exits;
// the only fallible call above is `allocate()`, which precedes this point).
{
let mut temp_buf = [0u8; 513];
let root_deps: &mut [Dependency] =
root_deps_list.mut_(old.buffers.dependencies.as_mut_slice());
let old_resolutions_list_lists = old.packages.items_resolutions();
let old_resolutions_list =
old_resolutions_list_lists[workspace_package_id as usize];
let old_resolutions: &[PackageID] =
old_resolutions_list.get(old.buffers.resolutions.as_slice());
let resolutions_of_yore: &[Resolution] = old.packages.items_resolution();
let packages_len = old.packages.len();
for update in updates.iter_mut() {
if update.package_id == invalid_package_id {
debug_assert_eq!(root_deps.len(), old_resolutions.len());
for (dep, &old_resolution) in
root_deps.iter_mut().zip(old_resolutions.iter())
{
if dep.name_hash == SemverStringBuilder::string_hash(update.name) {
if old_resolution as usize >= packages_len {
continue;
}
let res = resolutions_of_yore[old_resolution as usize];
if res.tag != ResolutionTag::Npm
|| update.version.tag != dependency::Tag::DistTag
{
continue;
}
// TODO(dylan-conway): this will need to handle updating dependencies (exact, ^, or ~) and aliases
let npm_ver = res.npm().version;
let buf = {
let mut cursor: &mut [u8] = &mut temp_buf[..];
let start_len = cursor.len();
if write!(
cursor,
"{}{}",
if exact_versions { "" } else { "^" },
npm_ver.fmt(string_builder.string_bytes.as_slice()),
)
.is_err()
{
break;
}
let written = start_len - cursor.len();
&temp_buf[..written]
};
let external_version = string_builder.append::<ExternalString>(buf);
let sliced = external_version
.value
.sliced(string_builder.string_bytes.as_slice());
dep.version = dependency::parse(
dep.name,
dep.name_hash,
sliced.slice,
&sliced,
None,
&mut *manager,
)
.unwrap_or_default();
}
}
}
update.e_string = None;
}
}
string_builder.clamp();
}
Ok(())
}
pub fn clean(
&mut self,
manager: &mut PackageManager,
updates: &mut [UpdateRequest],
exact_versions: bool,
log_level: LogLevel,
) -> Result<Box<Lockfile>, BunError> {
// This is wasteful, but we rarely log anything so it's fine.
let mut log = bun_ast::Log::init();
// defer { for (...) item.deinit(); log.deinit(); } — handled by Drop
self.clean_with_logger(manager, updates, &mut log, exact_versions, log_level)
}
pub fn resolve_catalog_dependency(&self, dep: &Dependency) -> Option<DependencyVersion> {
if dep.version.tag != dependency::Tag::Catalog {
return Some(dep.version.clone());
}
let catalog_name = *dep.version.catalog();
let catalog_dep = self.catalogs.get(self, catalog_name, dep.name)?;
Some(catalog_dep.version)
}
/// Is this a direct dependency of the workspace root package.json?
pub fn is_workspace_root_dependency(&self, id: DependencyID) -> bool {
self.packages.items_dependencies()[0].contains(id)
}
/// Is this a direct dependency of the workspace the install is taking place in?
pub fn is_root_dependency(&self, manager: &mut PackageManager, id: DependencyID) -> bool {
// `RootPackageId::get` caches into `manager`.
let root_id = manager
.root_package_id
.get(self, manager.workspace_name_hash);
self.packages.items_dependencies()[root_id as usize].contains(id)
}
/// Is this a direct dependency of any workspace (including workspace root)?
/// TODO make this faster by caching the workspace package ids
pub fn is_workspace_dependency(&self, id: DependencyID) -> bool {
self.get_workspace_pkg_if_workspace_dep(id) != invalid_package_id
}
pub fn get_workspace_pkg_if_workspace_dep(&self, id: DependencyID) -> PackageID {
let packages = self.packages.slice();
let resolutions = packages.items_resolution();
let dependencies_lists = packages.items_dependencies();
for (pkg_id, (resolution, dependencies)) in resolutions
.iter()
.zip(dependencies_lists.iter())
.enumerate()
{
if resolution.tag != ResolutionTag::Workspace && resolution.tag != ResolutionTag::Root {
continue;
}
if dependencies.contains(id) {
return PackageID::try_from(pkg_id).expect("int cast");
}
}
invalid_package_id
}
/// Does this tree id belong to a workspace (including workspace root)?
/// TODO(dylan-conway) fix!
pub fn is_workspace_tree_id(&self, id: tree::Id) -> bool {
id == 0
|| self.buffers.dependencies[self.buffers.trees[id as usize].dependency_id as usize]
.behavior
.is_workspace()
}
/// Is the package whose `node_modules` this tree represents resolved from a
/// local `file:` folder? Its `Resolution::Folder` dependencies were normalized