diff options
author | 2023-02-16 15:06:35 -0800 | |
---|---|---|
committer | 2023-02-16 15:06:35 -0800 | |
commit | 5007c6b218ec0e42938eac9c8a9698e313dc9952 (patch) | |
tree | 4a00d7b0ffceb5dd4be0b6971d82dde1e0e8d2f5 | |
parent | d95404fd7a096c58bf7c3d844e6fac13dd9c7d22 (diff) | |
download | bun-5007c6b218ec0e42938eac9c8a9698e313dc9952.tar.gz bun-5007c6b218ec0e42938eac9c8a9698e313dc9952.tar.zst bun-5007c6b218ec0e42938eac9c8a9698e313dc9952.zip |
Support yarn-like `"workspaces"."packages": string[]` (#2086)
* [workspaces] Support yarn-like `"workspaces"."packages": string[]`
* Add a test
* :scissors:
---------
Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
-rw-r--r-- | src/install/lockfile.zig | 301 | ||||
-rw-r--r-- | test/bun.js/install/bun-install.test.ts | 49 |
2 files changed, 253 insertions, 97 deletions
diff --git a/src/install/lockfile.zig b/src/install/lockfile.zig index 8ff745272..d48d2b13c 100644 --- a/src/install/lockfile.zig +++ b/src/install/lockfile.zig @@ -2446,6 +2446,128 @@ pub const Package = extern struct { return this_dep; } + fn processWorkspaceNamesArray( + workspace_names_ptr: *[]string, + allocator: std.mem.Allocator, + log: *logger.Log, + arr: *JSAst.E.Array, + source: *const logger.Source, + loc: logger.Loc, + string_builder: *StringBuilder, + ) !u32 { + var workspace_names = try allocator.alloc(string, arr.items.len); + defer workspace_names_ptr.* = workspace_names; + + var fallback = std.heap.stackFallback(1024, allocator); + var workspace_allocator = fallback.get(); + + const orig_msgs_len = log.msgs.items.len; + + for (arr.slice()) |item, i| { + defer fallback.fixed_buffer_allocator.reset(); + const path = item.asString(allocator) orelse { + log.addErrorFmt(source, item.loc, allocator, + \\Workspaces expects an array of strings, e.g. + \\"workspaces": [ + \\ "path/to/package" + \\] + , .{}) catch {}; + return error.InvalidPackageJSON; + }; + + var workspace_dir = std.fs.cwd().openIterableDir(path, .{}) catch |err| { + if (err == error.FileNotFound) { + log.addErrorFmt( + source, + item.loc, + allocator, + "Workspace not found \"{s}\" in \"{s}\"", + .{ + path, + std.os.getcwd(allocator.alloc(u8, bun.MAX_PATH_BYTES) catch unreachable) catch unreachable, + }, + ) catch {}; + } else log.addErrorFmt( + source, + item.loc, + allocator, + "{s} opening workspace package \"{s}\" from \"{s}\"", + .{ + @errorName(err), + path, + std.os.getcwd(allocator.alloc(u8, bun.MAX_PATH_BYTES) catch unreachable) catch unreachable, + }, + ) catch {}; + workspace_names[i] = ""; + // report errors for multiple workspaces + continue; + }; + defer workspace_dir.close(); + + var workspace_file = workspace_dir.dir.openFile("package.json", .{ .mode = .read_only }) catch |err| { + if (err == error.FileNotFound) { + log.addErrorFmt( + source, + item.loc, + allocator, + "package.json not found in workspace package \"{s}\" from \"{s}\"", + .{ path, std.os.getcwd(allocator.alloc(u8, bun.MAX_PATH_BYTES) catch unreachable) catch unreachable }, + ) catch {}; + } else log.addErrorFmt( + source, + item.loc, + allocator, + "{s} opening package.json for workspace package \"{s}\" from \"{s}\"", + .{ @errorName(err), path, std.os.getcwd(allocator.alloc(u8, bun.MAX_PATH_BYTES) catch unreachable) catch unreachable }, + ) catch {}; + workspace_names[i] = ""; + // report errors for multiple workspaces + continue; + }; + defer workspace_file.close(); + + const workspace_bytes = workspace_file.readToEndAlloc(workspace_allocator, std.math.maxInt(usize)) catch |err| { + log.addErrorFmt( + source, + item.loc, + allocator, + "{s} reading package.json for workspace package \"{s}\" from \"{s}\"", + .{ @errorName(err), path, std.os.getcwd(allocator.alloc(u8, bun.MAX_PATH_BYTES) catch unreachable) catch unreachable }, + ) catch {}; + workspace_names[i] = ""; + // report errors for multiple workspaces + continue; + }; + defer workspace_allocator.free(workspace_bytes); + const workspace_source = logger.Source.initPathString(path, workspace_bytes); + + var workspace_json = try json_parser.PackageJSONVersionChecker.init(allocator, &workspace_source, log); + + _ = try workspace_json.parseExpr(); + if (!workspace_json.has_found_name) { + log.addErrorFmt( + source, + loc, + allocator, + "Missing \"name\" from package.json in {s}", + .{workspace_source.path.text}, + ) catch {}; + // report errors for multiple workspaces + continue; + } + + const workspace_name = workspace_json.found_name; + + string_builder.count(workspace_name); + string_builder.count(path); + string_builder.cap += bun.MAX_PATH_BYTES; + workspace_names[i] = try allocator.dupe(u8, workspace_name); + } + + if (orig_msgs_len != log.msgs.items.len) return error.InstallFailed; + return @truncate(u32, arr.items.len); + } + fn parseWithJSON( package: *Lockfile.Package, lockfile: *Lockfile, @@ -2601,105 +2723,44 @@ pub const Package = extern struct { } if (arr.items.len == 0) break :brk; - workspace_names = try allocator.alloc(string, arr.items.len); - - var fallback = std.heap.stackFallback(1024, allocator); - var workspace_allocator = fallback.get(); - - const orig_msgs_len = log.msgs.items.len; - - for (arr.slice()) |item, i| { - defer fallback.fixed_buffer_allocator.reset(); - const path = item.asString(allocator) orelse { - log.addErrorFmt(&source, item.loc, allocator, - \\Workspaces expects an array of strings, e.g. - \\"workspaces": [ - \\ "path/to/package" - \\] - , .{}) catch {}; - return error.InvalidPackageJSON; - }; + total_dependencies_count += try processWorkspaceNamesArray( + &workspace_names, + allocator, + log, + arr, + &source, + dependencies_q.loc, + &string_builder, + ); + }, + .e_object => |obj| { + if (group.behavior.isWorkspace()) { - var workspace_dir = std.fs.cwd().openIterableDir(path, .{}) catch |err| { - if (err == error.FileNotFound) { - log.addErrorFmt( - &source, - item.loc, + // yarn workspaces expects a "workspaces" property shaped like this: + // + // "workspaces": { + // "packages": [ + // "path/to/package" + // ] + // } + // + if (obj.get("packages")) |packages_query| { + if (packages_query.data == .e_array) { + if (packages_query.data.e_array.items.len == 0) break :brk; + + total_dependencies_count += try processWorkspaceNamesArray( + &workspace_names, allocator, - "Workspace not found \"{s}\" in \"{s}\"", - .{ - path, - std.os.getcwd(allocator.alloc(u8, bun.MAX_PATH_BYTES) catch unreachable) catch unreachable, - }, - ) catch {}; - } else log.addErrorFmt( - &source, - item.loc, - allocator, - "{s} opening workspace package \"{s}\" from \"{s}\"", - .{ - @errorName(err), - path, - std.os.getcwd(allocator.alloc(u8, bun.MAX_PATH_BYTES) catch unreachable) catch unreachable, - }, - ) catch {}; - workspace_names[i] = ""; - // report errors for multiple workspaces - continue; - }; - defer workspace_dir.close(); - - var workspace_file = workspace_dir.dir.openFile("package.json", .{ .mode = .read_only }) catch |err| { - log.addErrorFmt( - &source, - item.loc, - allocator, - "{s} opening package.json for workspace package \"{s}\" from \"{s}\"", - .{ @errorName(err), path, std.os.getcwd(allocator.alloc(u8, bun.MAX_PATH_BYTES) catch unreachable) catch unreachable }, - ) catch {}; - workspace_names[i] = ""; - // report errors for multiple workspaces - continue; - }; - defer workspace_file.close(); - - const workspace_bytes = workspace_file.readToEndAlloc(workspace_allocator, std.math.maxInt(usize)) catch |err| { - log.addErrorFmt(&source, item.loc, allocator, "{s} reading package.json for workspace package \"{s}\" from \"{s}\"", .{ @errorName(err), path, std.os.getcwd(allocator.alloc(u8, bun.MAX_PATH_BYTES) catch unreachable) catch unreachable }) catch {}; - workspace_names[i] = ""; - // report errors for multiple workspaces - continue; - }; - defer workspace_allocator.free(workspace_bytes); - const workspace_source = logger.Source.initPathString(path, workspace_bytes); - - var workspace_json = try json_parser.PackageJSONVersionChecker.init(allocator, &workspace_source, log); - - _ = try workspace_json.parseExpr(); - if (!workspace_json.has_found_name) { - log.addErrorFmt( - &source, - dependencies_q.loc, - allocator, - "Missing \"name\" from package.json in {s}", - .{workspace_source.path.text}, - ) catch {}; - // report errors for multiple workspaces - continue; + log, + packages_query.data.e_array, + &source, + packages_query.loc, + &string_builder, + ); + break :brk; + } } - const workspace_name = workspace_json.found_name; - - string_builder.count(workspace_name); - string_builder.count(path); - string_builder.cap += bun.MAX_PATH_BYTES; - workspace_names[i] = try allocator.dupe(u8, workspace_name); - } - - if (orig_msgs_len != log.msgs.items.len) return error.InstallFailed; - total_dependencies_count += @truncate(u32, arr.items.len); - }, - .e_object => |obj| { - if (group.behavior.isWorkspace()) { log.addErrorFmt(&source, dependencies_q.loc, allocator, \\Workspaces expects an array of strings, e.g. \\"workspaces": [ @@ -2927,6 +2988,56 @@ pub const Package = extern struct { allocator.free(workspace_names); }, .e_object => |obj| { + if (group.behavior.isWorkspace()) { + // yarn workspaces expects a "workspaces" property shaped like this: + // + // "workspaces": { + // "packages": [ + // "path/to/package" + // ] + // } + // + if (obj.get("packages")) |packages_q| { + if (packages_q.data == .e_array) { + var arr = packages_q.data.e_array; + if (arr.items.len == 0) break :brk; + + for (arr.slice()) |item, i| { + const name = workspace_names[i]; + defer allocator.free(name); + + const external_name = string_builder.append(ExternalString, name); + const path = item.asString(allocator).?; + + if (try parseDependency( + lockfile, + allocator, + log, + source, + group, + &string_builder, + features, + package_dependencies, + dependencies, + in_workspace, + .workspace, + null, + external_name, + path, + item, + item, + )) |dep| { + dependencies[0] = dep; + dependencies = dependencies[1..]; + } + } + + allocator.free(workspace_names); + break :brk; + } + } + } + for (obj.properties.slice()) |item| { const key = item.key.?; const value = item.value.?; diff --git a/test/bun.js/install/bun-install.test.ts b/test/bun.js/install/bun-install.test.ts index 7de46869f..5e21fd394 100644 --- a/test/bun.js/install/bun-install.test.ts +++ b/test/bun.js/install/bun-install.test.ts @@ -16,6 +16,7 @@ import { root_url, setHandler, } from "./dummy.registry"; +import { rmSync } from "fs"; beforeAll(dummyBeforeAll); afterAll(dummyAfterAll); @@ -188,6 +189,50 @@ it("should handle workspaces", async () => { await access(join(package_dir, "bun.lockb")); }); +it("should handle workspaces with packages array", async () => { + await writeFile( + join(package_dir, "package.json"), + JSON.stringify({ + name: "Foo", + version: "0.0.1", + workspaces: { packages: ["bar"] }, + }), + ); + await mkdir(join(package_dir, "bar")); + await writeFile( + join(package_dir, "bar", "package.json"), + JSON.stringify({ + name: "Bar", + version: "0.0.2", + }), + ); + const { stdout, stderr, exited } = spawn({ + cmd: [bunExe(), "install", "--config", import.meta.dir + "/basic.toml"], + cwd: package_dir, + stdout: null, + stdin: "pipe", + stderr: "pipe", + env, + }); + expect(stderr).toBeDefined(); + const err = await new Response(stderr).text(); + expect(err).toContain("Saved lockfile"); + + expect(stdout).toBeDefined(); + const out = await new Response(stdout).text(); + + expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ + " + Bar@workspace:bar", + "", + " 1 packages installed", + ]); + expect(await exited).toBe(0); + expect(requested).toBe(0); + expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "Bar"]); + expect(await readlink(join(package_dir, "node_modules", "Bar"))).toBe(join("..", "bar")); + await access(join(package_dir, "bun.lockb")); +}); + it("should handle inter-dependency between workspaces", async () => { await writeFile( join(package_dir, "package.json"), @@ -1948,7 +1993,7 @@ it("should report error on invalid format for workspaces", async () => { name: "foo", version: "0.0.1", workspaces: { - packages: ["bar"], + packages: { bar: true }, }, }), ); @@ -1970,7 +2015,7 @@ it("should report error on invalid format for workspaces", async () => { '"workspaces": [', ' "path/to/package"', "]", - '{"name":"foo","version":"0.0.1","workspaces":{"packages":["bar"]}}', + '{"name":"foo","version":"0.0.1","workspaces":{"packages":{"bar":true}}}', " ^", `${package_dir}/package.json:1:33 32`, "", |