Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(install): automatically migrate package-lock.json to bun.lockb #6352

Merged
merged 43 commits into from
Oct 11, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
5847b41
work so far
paperclover Oct 3, 2023
77d7ae9
Merge remote-tracking branch 'origin/main' into dave/npm-migrate
paperclover Oct 3, 2023
311662a
stuff
paperclover Oct 4, 2023
639c131
a
paperclover Oct 4, 2023
94fc3bd
basics work
paperclover Oct 4, 2023
f91be35
Merge remote-tracking branch 'origin/main' into dave/npm-migrate
paperclover Oct 4, 2023
155887e
stuff
paperclover Oct 4, 2023
42426df
yoo
paperclover Oct 5, 2023
5a8e7dd
Merge remote-tracking branch 'origin/main' into dave/npm-migrate
paperclover Oct 5, 2023
b4be6ac
build lockfile
paperclover Oct 6, 2023
9fb00e0
correct
paperclover Oct 6, 2023
7ccbfde
Merge remote-tracking branch 'origin/main' into dave/npm-migrate
paperclover Oct 7, 2023
e91e977
f
paperclover Oct 7, 2023
f176be0
Merge remote-tracking branch 'origin/main' into dave/npm-migrate
paperclover Oct 7, 2023
4fc1585
Merge remote-tracking branch 'origin/main' into dave/npm-migrate
paperclover Oct 9, 2023
aea5c2b
a
paperclover Oct 9, 2023
0e1edcc
install fixture havent tested
paperclover Oct 10, 2023
90ca75d
i made it worse
paperclover Oct 10, 2023
0a4c386
lol
paperclover Oct 10, 2023
0d62e46
be more reasonable
paperclover Oct 10, 2023
a191b3f
make the test easier to pass because bun install doesn't handle obscu…
paperclover Oct 10, 2023
f041109
a
paperclover Oct 10, 2023
c7efa8f
works now
paperclover Oct 10, 2023
06b4834
ok
paperclover Oct 10, 2023
337616b
a
paperclover Oct 10, 2023
f6ab751
a
paperclover Oct 10, 2023
dcbe6cc
cool
paperclover Oct 10, 2023
6e42b48
nah
paperclover Oct 10, 2023
9cb75e9
Merge remote-tracking branch 'origin/main' into dave/npm-migrate
paperclover Oct 10, 2023
f4f3826
fix stuff
paperclover Oct 11, 2023
8498f26
l
paperclover Oct 11, 2023
aab0b9e
a
paperclover Oct 11, 2023
4bbc448
idfk
paperclover Oct 11, 2023
d91e628
LAME
paperclover Oct 11, 2023
e85621c
prettier errors
paperclover Oct 11, 2023
eaeb71e
Merge remote-tracking branch 'origin/main' into dave/npm-migrate
paperclover Oct 11, 2023
042f1d7
does this fix tests?
paperclover Oct 11, 2023
f97f363
Add more safety checks to Integrity
Jarred-Sumner Oct 11, 2023
49278a8
Add another check
Jarred-Sumner Oct 11, 2023
34b1264
More careful lifetime handling
Jarred-Sumner Oct 11, 2023
f0d27dc
Fix linux debugger issue
Jarred-Sumner Oct 11, 2023
1b01abc
a
paperclover Oct 11, 2023
543a3a1
tmp dir and snapshot test
paperclover Oct 11, 2023
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
Next Next commit
work so far
  • Loading branch information
paperclover committed Oct 3, 2023
commit 5847b417bfc25b1a1045d1ed37914604ee6dbb59
2 changes: 1 addition & 1 deletion .vscode/launch.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion src/install/dependency.zig
Original file line number Diff line number Diff line change
Expand Up @@ -648,7 +648,8 @@ pub inline fn parse(
sliced: *const SlicedString,
log: ?*logger.Log,
) ?Version {
return parseWithOptionalTag(allocator, alias, dependency, null, sliced, log);
const dep = std.mem.trimLeft(u8, dependency, " \t\n\r");
return parseWithTag(allocator, alias, dep, Version.Tag.infer(dep), sliced, log);
}

pub fn parseWithOptionalTag(
Expand Down
3 changes: 3 additions & 0 deletions src/install/install.zig
Original file line number Diff line number Diff line change
Expand Up @@ -7625,6 +7625,9 @@ pub const PackageManager = struct {
.read_file => Output.prettyError("<r><red>error<r> reading lockfile:<r> {s}\n<r>", .{
@errorName(cause.value),
}),
.migrating => Output.prettyError("<r><red>error<r> migrating lockfile:<r> {s}\n<r>", .{
@errorName(cause.value),
}),
}

if (manager.options.enable.fail_early) {
Expand Down
22 changes: 20 additions & 2 deletions src/install/lockfile.zig
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const json_parser = bun.JSON;
const JSPrinter = bun.js_printer;

const linker = @import("../linker.zig");
const migration = @import("./migration.zig");

const sync = @import("../sync.zig");
const Api = @import("../api/schema.zig").Api;
Expand Down Expand Up @@ -167,7 +168,7 @@ pub const LoadFromDiskResult = union(Tag) {
},
ok: *Lockfile,

pub const Step = enum { open_file, read_file, parse_file };
pub const Step = enum { open_file, read_file, parse_file, migrating };

pub const Tag = enum {
not_found,
Expand All @@ -183,7 +184,16 @@ pub fn loadFromDisk(this: *Lockfile, allocator: Allocator, log: *logger.Log, fil
if (filename.len > 0)
file = std.fs.cwd().openFileZ(filename, .{ .mode = .read_only }) catch |err| {
return switch (err) {
error.FileNotFound, error.AccessDenied, error.BadPathName => LoadFromDiskResult{ .not_found = {} },
error.FileNotFound => {
// Attempt to load from "package-lock.json", "yarn.lock", etc.
return migration.detectAndLoadOtherLockfile(
this,
allocator,
log,
filename[0 .. strings.lastIndexOfChar(filename, std.fs.path.sep) orelse 0],
);
},
error.AccessDenied, error.BadPathName => LoadFromDiskResult{ .not_found = {} },
else => LoadFromDiskResult{ .err = .{ .step = .open_file, .value = err } },
};
};
Expand Down Expand Up @@ -936,6 +946,9 @@ pub const Printer = struct {
.read_file => Output.prettyErrorln("<r><red>error<r> reading lockfile:<r> {s}", .{
@errorName(cause.value),
}),
.migrating => Output.prettyErrorln("<r><red>error<r> while migrating lockfile:<r> {s}", .{
@errorName(cause.value),
}),
}
if (log.errors > 0) {
switch (Output.enable_ansi_colors) {
Expand Down Expand Up @@ -1824,8 +1837,13 @@ pub const Package = extern struct {
dependencies: DependencySlice = .{},

/// The resolved package IDs for the dependencies
/// Each index in this array corresponds to the same index in dependencies
/// So this is how you say "what package ID for lodash does this other package depend on?"
/// By default, its "invalid_id" which means the package ID was not resolved
resolutions: DependencyIDSlice = .{},

// "dependencies_resolutions" { int dependency_id, int resolved_package_id, int package_id }

meta: Meta = .{},
bin: Bin = .{},
scripts: Package.Scripts = .{},
Expand Down
190 changes: 190 additions & 0 deletions src/install/migration.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
const std = @import("std");
const Allocator = std.mem.Allocator;

const bun = @import("root").bun;
const string = bun.string;
const Output = bun.Output;
const Global = bun.Global;
const Environment = bun.Environment;
const strings = bun.strings;
const MutableString = bun.MutableString;
const stringZ = bun.stringZ;
const logger = bun.logger;

const install = @import("./install.zig");
const Resolution = @import("./resolution.zig").Resolution;
const Dependency = @import("./dependency.zig");
const VersionedURL = @import("./versioned_url.zig");
const Semver = @import("./semver.zig");

const Lockfile = @import("./lockfile.zig");
const LoadFromDiskResult = Lockfile.LoadFromDiskResult;

const JSAst = bun.JSAst;
const Expr = JSAst.Expr;
const B = JSAst.B;
const E = JSAst.E;
const G = JSAst.G;
const S = JSAst.S;

const debug = Output.scoped(.migrate, false);

pub fn detectAndLoadOtherLockfile(this: *Lockfile, allocator: Allocator, log: *logger.Log, dirname: string) LoadFromDiskResult {
// check for package-lock.json, yarn.lock, etc...
// if it exists, do an in-memory migration
var buf: [bun.MAX_PATH_BYTES]u8 = undefined;
@memcpy(buf[0..dirname.len], dirname);

npm: {
const npm_lockfile_name = "package-lock.json";
@memcpy(buf[dirname.len .. dirname.len + npm_lockfile_name.len], npm_lockfile_name);
buf[dirname.len + npm_lockfile_name.len] = 0;
const lockfile_path = buf[0 .. dirname.len + npm_lockfile_name.len :0];
const file = std.fs.cwd().openFileZ(lockfile_path, .{ .mode = .read_only }) catch break :npm;
defer file.close();
var data = file.readToEndAlloc(allocator, std.math.maxInt(usize)) catch |err| {
return LoadFromDiskResult{ .err = .{ .step = .migrating, .value = err } };
};
return migrateNPMLockfile(this, allocator, log, data, lockfile_path) catch |err| {
return LoadFromDiskResult{ .err = .{ .step = .migrating, .value = err } };
};
}

return LoadFromDiskResult{ .not_found = {} };
}

const IdMap = std.StringHashMapUnmanaged(u32);

pub fn migrateNPMLockfile(this: *Lockfile, allocator: Allocator, log: *logger.Log, data: string, path: string) !LoadFromDiskResult {
debug("begin lockfile migration", .{});

try this.initEmpty(allocator);
install.initializeStore();

const json_src = logger.Source.initPathString(path, data);
const json = try bun.JSON.ParseJSON(&json_src, log, allocator);

if (json.data != .e_object) {
return error.InvalidNPMPackageLockfile;
}
if (json.get("lockfileVersion")) |version| {
if (!(version.data == .e_number and version.data.e_number.value == 3)) {
return error.InvalidNPMPackageLockfile;
}
} else {
return error.InvalidNPMPackageLockfile;
}

// Count pass
var builder_ = this.stringBuilder();
var builder = &builder_;
const name = (if (json.get("name")) |expr| expr.asString(allocator) else null) orelse "";
builder.count(name);

var packages_properties = brk: {
const obj = json.get("packages") orelse return error.InvalidNPMPackageLockfile;
if (obj.data != .e_object) return error.InvalidNPMPackageLockfile;
if (obj.data.e_object.properties.len == 0) return error.InvalidNPMPackageLockfile;
// if (obj.data.e_object.properties.at(0).key) |k| {
// if (k.data != .e_string) return error.InvalidNPMPackageLockfile;
// // first key must be the "", self reference
// if (k.data.e_string.data.len == 0) return error.InvalidNPMPackageLockfile;
// } else return error.InvalidNPMPackageLockfile;
break :brk obj.data.e_object.properties;
};

// for faster lookups
var id_map = IdMap{};
try id_map.ensureTotalCapacity(allocator, packages_properties.len);
var num_deps: u32 = 0;
for (packages_properties.slice(), 0..) |prop, i| {
id_map.putAssumeCapacity(prop.key.?.asString(allocator).?, @truncate(i));

if (prop.value.?.data != .e_object) {
return error.InvalidNPMPackageLockfile;
}
if (prop.value.?.data.e_object.get("dependencies")) |deps| {
if (deps.data != .e_object) {
return error.InvalidNPMPackageLockfile;
}
num_deps +|= @as(u32, deps.data.e_object.properties.len);
}
}
if (num_deps == std.math.maxInt(u32)) return error.TooManyDependencies; // lol

debug("num_deps: {d}", .{num_deps});

try this.buffers.dependencies.ensureUnusedCapacity(allocator, num_deps);
try this.buffers.resolutions.ensureUnusedCapacity(allocator, num_deps);

try recursiveWalk(
this,
log,
&id_map,
&packages_properties,
packages_properties.at(0).value.?,
name,
"",
.{},
);

return error.NotImplementedYet;
}

fn recursiveWalk(
this: *Lockfile,
log: *logger.Log,
id_map: *IdMap,
all_packages_properties: *G.Property.List,
value: Expr,
pkg_name: string,
pkg_path: string,
resolution: Dependency.Version,
) !void {
debug("recursiveWalk {s} @ {s}", .{ pkg_name, pkg_path });
_ = resolution;

if (value.get("dependencies")) |deps| {
if (value.data == .e_object) {
var name_checking_buf: [bun.MAX_PATH_BYTES]u8 = undefined;
dep_loop: for (deps.data.e_object.properties.slice(), 0..) |prop, i| {
_ = i;
const name = prop.key.?.asString(this.allocator).?;
const dependency = prop.value.?;
const sliced = Semver.SlicedString.init(name, name);
const version = Dependency.parse(this.allocator, Semver.String.init(name, name), dependency.asString(this.allocator).?, &sliced, log) orelse {
return error.InvalidLockfileSemver;
};

// TODO: this buffer can totally be avoided
bun.copy(u8, name_checking_buf[0..pkg_name.len], pkg_name);
const str_node_modules = if (pkg_path.len == 0) "node_modules/" else "/node_modules/";
bun.copy(u8, name_checking_buf[pkg_name.len .. pkg_name.len + str_node_modules.len], str_node_modules);
const suffix_len = str_node_modules.len + name.len;
bun.copy(u8, name_checking_buf[pkg_name.len + str_node_modules.len .. pkg_name.len + suffix_len], name);
var j: u32 = @as(u32, @intCast(pkg_name.len + suffix_len));
while (true) {
if (id_map.get(name_checking_buf[0..j])) |existing| {
try recursiveWalk(
this,
log,
id_map,
all_packages_properties,
all_packages_properties.at(existing).value.?,
pkg_path[0..j],
name_checking_buf[0..j],
version,
);
continue :dep_loop;
}
if (strings.lastIndexOf(name_checking_buf[0 .. j - suffix_len], "node_modules/")) |idx| {
j = @intCast(idx - "node_modules/".len + name.len);
bun.copy(u8, name_checking_buf[j .. j + name.len], name);
} else {
return error.MissingLockfileDependency;
}
}
}
}
}
}