fix bun link in workspace package (#6631)

* link workspace package

* add test

* more complete test
This commit is contained in:
Dylan Conway
2023-10-20 22:38:46 -07:00
committed by Jarred Sumner
parent c900d08bea
commit 7dbe01b822
2 changed files with 221 additions and 54 deletions

View File

@@ -5512,63 +5512,65 @@ pub const PackageManager = struct {
const child_cwd = this_cwd;
// Check if this is a workspace; if so, use root package
var found = false;
if (!created_package_json) {
while (std.fs.path.dirname(this_cwd)) |parent| : (this_cwd = parent) {
const parent_without_trailing_slash = strings.withoutTrailingSlash(parent);
var buf2: [bun.MAX_PATH_BYTES + 1]u8 = undefined;
@memcpy(buf2[0..parent_without_trailing_slash.len], parent_without_trailing_slash);
buf2[parent_without_trailing_slash.len..buf2.len][0.."/package.json".len].* = "/package.json".*;
buf2[parent_without_trailing_slash.len + "/package.json".len] = 0;
if (comptime subcommand != .link) {
if (!created_package_json) {
while (std.fs.path.dirname(this_cwd)) |parent| : (this_cwd = parent) {
const parent_without_trailing_slash = strings.withoutTrailingSlash(parent);
var buf2: [bun.MAX_PATH_BYTES + 1]u8 = undefined;
@memcpy(buf2[0..parent_without_trailing_slash.len], parent_without_trailing_slash);
buf2[parent_without_trailing_slash.len..buf2.len][0.."/package.json".len].* = "/package.json".*;
buf2[parent_without_trailing_slash.len + "/package.json".len] = 0;
const json_file = std.fs.cwd().openFileZ(
buf2[0 .. parent_without_trailing_slash.len + "/package.json".len :0].ptr,
.{ .mode = .read_write },
) catch {
continue;
};
defer if (!found) json_file.close();
const json_stat_size = try json_file.getEndPos();
const json_buf = try ctx.allocator.alloc(u8, json_stat_size + 64);
defer ctx.allocator.free(json_buf);
const json_len = try json_file.preadAll(json_buf, 0);
const json_path = try bun.getFdPath(json_file.handle, &package_json_cwd_buf);
const json_source = logger.Source.initPathString(json_path, json_buf[0..json_len]);
initializeStore();
const json = try json_parser.ParseJSONUTF8(&json_source, ctx.log, ctx.allocator);
if (json.asProperty("workspaces")) |prop| {
const json_array = switch (prop.expr.data) {
.e_array => |arr| arr,
.e_object => |obj| if (obj.get("packages")) |packages| switch (packages.data) {
.e_array => |arr| arr,
else => break,
} else break,
else => break,
const json_file = std.fs.cwd().openFileZ(
buf2[0 .. parent_without_trailing_slash.len + "/package.json".len :0].ptr,
.{ .mode = .read_write },
) catch {
continue;
};
var log = logger.Log.init(ctx.allocator);
defer log.deinit();
const workspace_packages_count = Package.processWorkspaceNamesArray(
&workspace_names,
ctx.allocator,
&log,
json_array,
&json_source,
prop.loc,
null,
) catch break;
_ = workspace_packages_count;
for (workspace_names.keys()) |path| {
if (strings.eql(child_cwd, path)) {
fs.top_level_dir = parent;
if (comptime subcommand == .install) {
found = true;
child_json.close();
break :brk json_file;
} else {
break :brk child_json;
defer if (!found) json_file.close();
const json_stat_size = try json_file.getEndPos();
const json_buf = try ctx.allocator.alloc(u8, json_stat_size + 64);
defer ctx.allocator.free(json_buf);
const json_len = try json_file.preadAll(json_buf, 0);
const json_path = try bun.getFdPath(json_file.handle, &package_json_cwd_buf);
const json_source = logger.Source.initPathString(json_path, json_buf[0..json_len]);
initializeStore();
const json = try json_parser.ParseJSONUTF8(&json_source, ctx.log, ctx.allocator);
if (json.asProperty("workspaces")) |prop| {
const json_array = switch (prop.expr.data) {
.e_array => |arr| arr,
.e_object => |obj| if (obj.get("packages")) |packages| switch (packages.data) {
.e_array => |arr| arr,
else => break,
} else break,
else => break,
};
var log = logger.Log.init(ctx.allocator);
defer log.deinit();
const workspace_packages_count = Package.processWorkspaceNamesArray(
&workspace_names,
ctx.allocator,
&log,
json_array,
&json_source,
prop.loc,
null,
) catch break;
_ = workspace_packages_count;
for (workspace_names.keys()) |path| {
if (strings.eql(child_cwd, path)) {
fs.top_level_dir = parent;
if (comptime subcommand == .install) {
found = true;
child_json.close();
break :brk json_file;
} else {
break :brk child_json;
}
}
}
break;
}
break;
}
}
}

View File

@@ -1,7 +1,7 @@
import { spawn } from "bun";
import { spawn, file } from "bun";
import { afterAll, afterEach, beforeAll, beforeEach, expect, it } from "bun:test";
import { bunExe, bunEnv as env } from "harness";
import { access, mkdtemp, readlink, realpath, rm, writeFile } from "fs/promises";
import { access, mkdtemp, readlink, realpath, rm, writeFile, mkdir } from "fs/promises";
import { basename, join } from "path";
import { tmpdir } from "os";
import {
@@ -27,6 +27,171 @@ afterEach(async () => {
await dummyAfterEach();
});
it("should link and unlink workspace package", async () => {
await writeFile(
join(link_dir, "package.json"),
JSON.stringify({
name: "foo",
version: "1.0.0",
workspaces: ["packages/*"],
}),
);
await mkdir(join(link_dir, "packages", "moo"), { recursive: true });
await mkdir(join(link_dir, "packages", "boba"), { recursive: true });
await writeFile(
join(link_dir, "packages", "moo", "package.json"),
JSON.stringify({
name: "moo",
version: "0.0.1",
}),
);
await writeFile(
join(link_dir, "packages", "boba", "package.json"),
JSON.stringify({
name: "boba",
version: "0.0.1",
}),
);
var { stdout, stderr, exited } = spawn({
cmd: [bunExe(), "install"],
cwd: link_dir,
stdout: null,
stdin: "pipe",
stderr: "pipe",
env,
});
expect(stderr).toBeDefined();
var err = await new Response(stderr).text();
expect(err.replace(/^(.*?) v[^\n]+/, "$1").split(/\r?\n/)).toEqual(["bun install", " Saved lockfile", ""]);
expect(stdout).toBeDefined();
var out = await new Response(stdout).text();
expect(out.replace(/\s*\[[0-9\.]+ms\]\s*$/, "").split(/\r?\n/)).toEqual([
" + boba@workspace:packages/boba",
" + moo@workspace:packages/moo",
"",
" 2 packages installed",
]);
expect(await exited).toBe(0);
({ stdout, stderr, exited } = spawn({
cmd: [bunExe(), "link"],
cwd: join(link_dir, "packages", "moo"),
stdout: null,
stdin: "pipe",
stderr: "pipe",
env,
}));
expect(stderr).toBeDefined();
err = await new Response(stderr).text();
expect(err.replace(/^(.*?) v[^\n]+/, "$1").split(/\r?\n/)).toEqual(["bun link", ""]);
expect(stdout).toBeDefined();
expect(await new Response(stdout).text()).toContain(`Success! Registered "moo"`);
expect(await exited).toBe(0);
({ stdout, stderr, exited } = spawn({
cmd: [bunExe(), "link", "moo"],
cwd: join(link_dir, "packages", "boba"),
stdout: null,
stdin: "pipe",
stderr: "pipe",
env,
}));
expect(stderr).toBeDefined();
err = await new Response(stderr).text();
expect(err.replace(/^(.*?) v[^\n]+/, "$1").split(/\r?\n/)).toEqual(["bun link", ""]);
expect(stdout).toBeDefined();
expect((await new Response(stdout).text()).replace(/\s*\[[0-9\.]+ms\]\s*$/, "").split(/\r?\n/)).toEqual([
"",
` installed moo@link:moo`,
"",
"",
" 1 package installed",
]);
expect(await exited).toBe(0);
expect(await file(join(link_dir, "packages", "boba", "node_modules", "moo", "package.json")).json()).toEqual({
name: "moo",
version: "0.0.1",
});
({ stdout, stderr, exited } = spawn({
cmd: [bunExe(), "unlink"],
cwd: join(link_dir, "packages", "moo"),
stdout: null,
stdin: "pipe",
stderr: "pipe",
env,
}));
expect(stderr).toBeDefined();
err = await new Response(stderr).text();
expect(err.replace(/^(.*?) v[^\n]+/, "$1").split(/\r?\n/)).toEqual(["bun unlink", ""]);
expect(stdout).toBeDefined();
expect(await new Response(stdout).text()).toContain(`success: unlinked package "moo"`);
expect(await exited).toBe(0);
// link the workspace root package to a workspace package
({ stdout, stderr, exited } = spawn({
cmd: [bunExe(), "link"],
cwd: link_dir,
stdout: null,
stdin: "pipe",
stderr: "pipe",
env,
}));
expect(stderr).toBeDefined();
err = await new Response(stderr).text();
expect(err.replace(/^(.*?) v[^\n]+/, "$1").split(/\r?\n/)).toEqual(["bun link", ""]);
expect(stdout).toBeDefined();
expect(await new Response(stdout).text()).toContain(`Success! Registered "foo"`);
expect(await exited).toBe(0);
({ stdout, stderr, exited } = spawn({
cmd: [bunExe(), "link", "foo"],
cwd: join(link_dir, "packages", "boba"),
stdout: null,
stdin: "pipe",
stderr: "pipe",
env,
}));
expect(stderr).toBeDefined();
err = await new Response(stderr).text();
expect(err.replace(/^(.*?) v[^\n]+/, "$1").split(/\r?\n/)).toEqual(["bun link", ""]);
expect(stdout).toBeDefined();
expect((await new Response(stdout).text()).replace(/\s*\[[0-9\.]+ms\]\s*$/, "").split(/\r?\n/)).toEqual([
"",
` installed foo@link:foo`,
"",
"",
" 1 package installed",
]);
expect(await file(join(link_dir, "packages", "boba", "node_modules", "foo", "package.json")).json()).toEqual({
name: "foo",
version: "1.0.0",
workspaces: ["packages/*"],
});
expect(await exited).toBe(0);
({ stdout, stderr, exited } = spawn({
cmd: [bunExe(), "unlink"],
cwd: link_dir,
stdout: null,
stdin: "pipe",
stderr: "pipe",
env,
}));
expect(stderr).toBeDefined();
err = await new Response(stderr).text();
expect(err.replace(/^(.*?) v[^\n]+/, "$1").split(/\r?\n/)).toEqual(["bun unlink", ""]);
expect(stdout).toBeDefined();
expect(await new Response(stdout).text()).toContain(`success: unlinked package "foo"`);
expect(await exited).toBe(0);
});
it("should link package", async () => {
const link_name = basename(link_dir).slice("bun-link.".length);
await writeFile(