aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Alex Lam S.L <alexlamsl@gmail.com> 2023-01-24 21:52:47 +0200
committerGravatar GitHub <noreply@github.com> 2023-01-24 11:52:47 -0800
commite47fe2ca00a5d3cbf9710fedc1440aa25025317d (patch)
treeeec2c318509d964611a9863e1a938162c12cbb05
parent2edbf4d0ec7340d40f5e51b3b7d262499b5bc131 (diff)
downloadbun-e47fe2ca00a5d3cbf9710fedc1440aa25025317d.tar.gz
bun-e47fe2ca00a5d3cbf9710fedc1440aa25025317d.tar.zst
bun-e47fe2ca00a5d3cbf9710fedc1440aa25025317d.zip
support `bun link` of scoped packages (#1892)
-rw-r--r--src/install/install.zig14
-rw-r--r--test/bun.js/install/bun-install.test.ts4
-rw-r--r--test/bun.js/install/bun-link.test.ts201
3 files changed, 215 insertions, 4 deletions
diff --git a/src/install/install.zig b/src/install/install.zig
index aff7a3fec..7195e1ac5 100644
--- a/src/install/install.zig
+++ b/src/install/install.zig
@@ -2653,7 +2653,7 @@ pub const PackageManager = struct {
};
const not_found_fmt =
- \\package \"{[name]s}\" is not linked
+ \\package "{[name]s}" is not linked
\\
\\To install a linked package:
\\ <cyan>bun link my-pkg-name-from-package-json<r>
@@ -4491,6 +4491,18 @@ pub const PackageManager = struct {
// delete it if it exists
node_modules.dir.deleteTree(name) catch {};
+ // create scope if specified
+ if (name[0] == '@') {
+ if (std.mem.indexOfScalar(u8, name, '/')) |i| {
+ node_modules.dir.makeDir(name[0..i]) catch |err| brk: {
+ if (err == error.PathAlreadyExists) break :brk;
+ if (manager.options.log_level != .silent)
+ Output.prettyErrorln("<r><red>error:<r> failed to create scope in global dir due to error {s}", .{@errorName(err)});
+ Global.crash();
+ };
+ }
+ }
+
// create the symlink
node_modules.dir.symLink(Fs.FileSystem.instance.topLevelDirWithoutTrailingSlash(), name, .{ .is_directory = true }) catch |err| {
if (manager.options.log_level != .silent)
diff --git a/test/bun.js/install/bun-install.test.ts b/test/bun.js/install/bun-install.test.ts
index d176f5dbe..de9fb5755 100644
--- a/test/bun.js/install/bun-install.test.ts
+++ b/test/bun.js/install/bun-install.test.ts
@@ -8,10 +8,10 @@ import {
it,
} from "bun:test";
import { bunExe } from "bunExe";
+import { bunEnv as env } from "bunEnv";
import { access, mkdir, mkdtemp, readdir, readlink, rm, writeFile } from "fs/promises";
import { join } from "path";
import { tmpdir } from "os";
-import { bunEnv } from "bunEnv";
let handler, package_dir, requested, server;
@@ -25,8 +25,6 @@ function resetHanlder() {
handler = () => new Response("Tea Break~", { status: 418 });
}
-const env = bunEnv;
-
beforeAll(() => {
server = Bun.serve({
async fetch(request) {
diff --git a/test/bun.js/install/bun-link.test.ts b/test/bun.js/install/bun-link.test.ts
new file mode 100644
index 000000000..32d7b91fc
--- /dev/null
+++ b/test/bun.js/install/bun-link.test.ts
@@ -0,0 +1,201 @@
+import { file, spawn } from "bun";
+import {
+ afterEach,
+ beforeEach,
+ expect,
+ it,
+} from "bun:test";
+import { bunExe } from "bunExe";
+import { bunEnv as env } from "bunEnv";
+import { access, mkdir, mkdtemp, readdir, readlink, rm, writeFile } from "fs/promises";
+import { basename, join } from "path";
+import { tmpdir } from "os";
+
+let package_dir, link_dir;
+
+beforeEach(async () => {
+ link_dir = await mkdtemp(join(tmpdir(), "bun-link.test"));
+ package_dir = await mkdtemp(join(tmpdir(), "bun-link.pkg"));
+});
+afterEach(async () => {
+ await rm(link_dir, { force: true, recursive: true });
+ await rm(package_dir, { force: true, recursive: true });
+});
+
+it("should link package", async () => {
+ var link_name = basename(link_dir).slice("bun-link.".length);
+ await writeFile(join(link_dir, "package.json"), JSON.stringify({
+ name: link_name,
+ version: "0.0.1",
+ }));
+ await writeFile(join(package_dir, "package.json"), JSON.stringify({
+ name: "foo",
+ version: "0.0.2",
+ }));
+
+ const { stdout: stdout1, stderr: stderr1, exited: exited1 } = spawn({
+ cmd: [bunExe(), "link"],
+ cwd: link_dir,
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr1).toBeDefined();
+ const err1 = await new Response(stderr1).text();
+ expect(err1.replace(/^(.*?) v[^\n]+/, "$1").split(/\r?\n/)).toEqual([
+ "bun link",
+ "",
+ ]);
+ expect(stdout1).toBeDefined();
+ expect(await new Response(stdout1).text()).toContain(`Success! Registered "${link_name}"`);
+ expect(await exited1).toBe(0);
+
+ const { stdout: stdout2, stderr: stderr2, exited: exited2 } = spawn({
+ cmd: [bunExe(), "link", link_name],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr2).toBeDefined();
+ const err2 = await new Response(stderr2).text();
+ expect(err2.replace(/^(.*?) v[^\n]+/, "$1").split(/\r?\n/)).toEqual([
+ "bun link",
+ "",
+ ]);
+ expect(stdout2).toBeDefined();
+ const out2 = await new Response(stdout2).text();
+ expect(out2.replace(/\s*\[[0-9\.]+ms\]\s*$/, "").split(/\r?\n/)).toEqual([
+ "",
+ ` installed ${link_name}@link:${link_name}`,
+ "",
+ "",
+ " 1 packages installed",
+ ]);
+ expect(await exited2).toBe(0);
+
+ const { stdout: stdout3, stderr: stderr3, exited: exited3 } = spawn({
+ cmd: [bunExe(), "unlink"],
+ cwd: link_dir,
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr3).toBeDefined();
+ const err3 = await new Response(stderr3).text();
+ expect(err3.replace(/^(.*?) v[^\n]+/, "$1").split(/\r?\n/)).toEqual([
+ "bun unlink",
+ "",
+ ]);
+ expect(stdout3).toBeDefined();
+ expect(await new Response(stdout3).text()).toContain(`success: unlinked package "${link_name}"`);
+ expect(await exited3).toBe(0);
+
+ const { stdout: stdout4, stderr: stderr4, exited: exited4 } = spawn({
+ cmd: [bunExe(), "link", link_name],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr4).toBeDefined();
+ const err4 = await new Response(stderr4).text();
+ expect(err4).toContain(`error: package "${link_name}" is not linked`);
+ expect(stdout4).toBeDefined();
+ const out4 = await new Response(stdout4).text();
+ expect(await new Response(stdout4).text()).toBe("");
+ expect(await exited4).toBe(1);
+});
+
+it("should link scoped package", async () => {
+ var link_name = `@${basename(link_dir).slice("bun-link.".length)}/foo`;
+ await writeFile(join(link_dir, "package.json"), JSON.stringify({
+ name: link_name,
+ version: "0.0.1",
+ }));
+ await writeFile(join(package_dir, "package.json"), JSON.stringify({
+ name: "bar",
+ version: "0.0.2",
+ }));
+
+ const { stdout: stdout1, stderr: stderr1, exited: exited1 } = spawn({
+ cmd: [bunExe(), "link"],
+ cwd: link_dir,
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr1).toBeDefined();
+ const err1 = await new Response(stderr1).text();
+ expect(err1.replace(/^(.*?) v[^\n]+/, "$1").split(/\r?\n/)).toEqual([
+ "bun link",
+ "",
+ ]);
+ expect(stdout1).toBeDefined();
+ expect(await new Response(stdout1).text()).toContain(`Success! Registered "${link_name}"`);
+ expect(await exited1).toBe(0);
+
+ const { stdout: stdout2, stderr: stderr2, exited: exited2 } = spawn({
+ cmd: [bunExe(), "link", link_name],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr2).toBeDefined();
+ const err2 = await new Response(stderr2).text();
+ expect(err2.replace(/^(.*?) v[^\n]+/, "$1").split(/\r?\n/)).toEqual([
+ "bun link",
+ "",
+ ]);
+ expect(stdout2).toBeDefined();
+ const out2 = await new Response(stdout2).text();
+ expect(out2.replace(/\s*\[[0-9\.]+ms\]\s*$/, "").split(/\r?\n/)).toEqual([
+ "",
+ ` installed ${link_name}@link:${link_name}`,
+ "",
+ "",
+ " 1 packages installed",
+ ]);
+ expect(await exited2).toBe(0);
+
+ const { stdout: stdout3, stderr: stderr3, exited: exited3 } = spawn({
+ cmd: [bunExe(), "unlink"],
+ cwd: link_dir,
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr3).toBeDefined();
+ const err3 = await new Response(stderr3).text();
+ expect(err3.replace(/^(.*?) v[^\n]+/, "$1").split(/\r?\n/)).toEqual([
+ "bun unlink",
+ "",
+ ]);
+ expect(stdout3).toBeDefined();
+ expect(await new Response(stdout3).text()).toContain(`success: unlinked package "${link_name}"`);
+ expect(await exited3).toBe(0);
+
+ const { stdout: stdout4, stderr: stderr4, exited: exited4 } = spawn({
+ cmd: [bunExe(), "link", link_name],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr4).toBeDefined();
+ const err4 = await new Response(stderr4).text();
+ expect(err4).toContain(`error: package "${link_name}" is not linked`);
+ expect(stdout4).toBeDefined();
+ const out4 = await new Response(stdout4).text();
+ expect(await new Response(stdout4).text()).toBe("");
+ expect(await exited4).toBe(1);
+});