Compare commits

...

6 Commits

Author SHA1 Message Date
Jarred-Sumner
fd7d29b1b5 bun run prettier 2025-06-10 05:29:15 +00:00
Cursor Agent
a0be66099f Changes from background composer bc-4a83a85f-ff0b-409f-b3a3-d6b6435a437a 2025-06-10 05:24:40 +00:00
Cursor Agent
06927923d7 Fix handling of escaped @scoped package names on Windows 2025-06-10 05:23:28 +00:00
Cursor Agent
27ec16b47e Add Windows-specific test for escaped @scoped package names 2025-06-10 05:09:13 +00:00
Cursor Agent
b571c91d26 Checkpoint before follow-up message 2025-06-10 05:05:04 +00:00
Cursor Agent
adfbdf3eb9 Fix Windows package name parsing and remove unnecessary blank lines 2025-06-10 04:56:13 +00:00
3 changed files with 113 additions and 23 deletions

View File

@@ -447,7 +447,7 @@ const NetworkTask = struct {
this.unsafe_http_client.client.flags.reject_unauthorized = this.package_manager.tlsRejectUnauthorized();
if (PackageManager.verbose_install) {
this.unsafe_http_client.client.verbose = .headers;
this.unsafe_http_client.verbose = .headers;
}
this.callback = .{
@@ -619,7 +619,6 @@ pub const PreinstallState = enum(u4) {
apply_patch,
applying_patch,
};
/// Schedule long-running callbacks for a task
/// Slow stuff is broken into tasks, each can run independently without locks
pub const Task = struct {
@@ -1731,7 +1730,6 @@ pub fn NewPackageInstall(comptime kind: PkgInstallKind) type {
}
};
}
const HardLinkWindowsInstallTask = struct {
bytes: []u16,
src: [:0]bun.OSPathChar,
@@ -2511,7 +2509,6 @@ const TaskCallbackContext = union(enum) {
root_dependency: DependencyID,
root_request_id: PackageID,
};
const TaskCallbackList = std.ArrayListUnmanaged(TaskCallbackContext);
const TaskDependencyQueue = std.HashMapUnmanaged(u64, TaskCallbackList, IdentityContext(u64), 80);
@@ -3249,7 +3246,6 @@ pub const PackageManager = struct {
not_found: void,
failure: anyerror,
};
pub fn enqueueDependencyToRoot(
this: *PackageManager,
name: []const u8,
@@ -4038,7 +4034,6 @@ pub const PackageManager = struct {
pub fn isFolderInCache(this: *PackageManager, folder_path: stringZ) bool {
return bun.sys.directoryExistsAt(.fromStdDir(this.getCacheDirectory()), folder_path).unwrap() catch false;
}
pub fn pathForCachedNPMPath(
this: *PackageManager,
buf: *bun.PathBuffer,
@@ -4588,7 +4583,6 @@ pub const PackageManager = struct {
return false;
}
fn getOrPutResolvedPackage(
this: *PackageManager,
name_hash: PackageNameHash,
@@ -5181,7 +5175,6 @@ pub const PackageManager = struct {
else => .{ original_name, original_name_hash },
};
}
/// Q: "What do we do with a dependency in a package.json?"
/// A: "We enqueue it!"
fn enqueueDependencyWithMainAndSuccessFn(
@@ -5918,7 +5911,6 @@ pub const PackageManager = struct {
manager.patch_calc_hash_batch = .{};
return count;
}
pub fn enqueueDependencyList(
this: *PackageManager,
dependencies_list: Lockfile.DependencySlice,
@@ -6344,7 +6336,6 @@ pub const PackageManager = struct {
var fallback_parts = [_]string{"node_modules/.bun-cache"};
return CacheDir{ .is_node_modules = true, .path = Fs.FileSystem.instance.abs(&fallback_parts) };
}
pub fn runTasks(
manager: *PackageManager,
comptime ExtractCompletionContext: type,
@@ -6790,7 +6781,6 @@ pub const PackageManager = struct {
else => unreachable,
}
}
var resolve_tasks_batch = manager.resolve_tasks.popBatch();
var resolve_tasks_iter = resolve_tasks_batch.iterator();
while (resolve_tasks_iter.next()) |task| {
@@ -7295,7 +7285,6 @@ pub const PackageManager = struct {
}
Global.crash();
}
pub fn init(
ctx: Command.Context,
cli: CommandLineArguments,
@@ -8079,7 +8068,6 @@ pub const PackageManager = struct {
try manager.updatePackageJSONAndInstallWithManager(ctx, original_cwd);
}
}
pub fn unlink(ctx: Command.Context) !void {
const cli = try PackageManager.CommandLineArguments.parse(ctx.allocator, .unlink);
var manager, const original_cwd = PackageManager.init(ctx, cli, .unlink) catch |err| brk: {
@@ -8320,7 +8308,8 @@ pub const PackageManager = struct {
// add
// remove
outer: for (positionals) |positional| {
var input: []u8 = bun.default_allocator.dupe(u8, std.mem.trim(u8, positional, " \n\r\t")) catch bun.outOfMemory();
var input: []u8 = bun.default_allocator.dupe(u8, std.mem.trim(u8, positional, " \n\r\t\\")) catch bun.outOfMemory();
{
var temp: [2048]u8 = undefined;
const len = std.mem.replace(u8, input, "\\\\", "/", &temp);
@@ -9507,7 +9496,6 @@ pub const PackageManager = struct {
return;
}
fn overwritePackageInNodeModulesFolder(
manager: *PackageManager,
cache_dir: std.fs.Dir,
@@ -10297,7 +10285,6 @@ pub const PackageManager = struct {
Global.exit(1);
}
}
pub const LazyPackageDestinationDir = union(enum) {
dir: std.fs.Dir,
node_modules_path: struct {
@@ -11018,7 +11005,6 @@ pub const PackageManager = struct {
fn getPatchfileHash(patchfile_path: []const u8) ?u64 {
_ = patchfile_path; // autofix
}
fn installPackageWithNameAndResolution(
this: *PackageInstaller,
dependency_id: DependencyID,
@@ -11791,7 +11777,6 @@ pub const PackageManager = struct {
}
const EnqueuePackageForDownloadError = NetworkTask.ForTarballError;
pub fn enqueuePackageForDownload(
this: *PackageManager,
name: []const u8,
@@ -12394,7 +12379,6 @@ pub const PackageManager = struct {
}
}
}
fn installWithManager(
manager: *PackageManager,
ctx: Command.Context,
@@ -13182,7 +13166,6 @@ pub const PackageManager = struct {
if (needs_new_lockfile) {
manager.summary.add = @as(u32, @truncate(manager.lockfile.packages.len));
}
if (manager.options.do.save_yarn_lock) {
var node: *Progress.Node = undefined;
if (log_level.showProgress()) {

View File

@@ -441,6 +441,48 @@ it("should handle @scoped names", async () => {
}
});
it.skipIf(process.platform !== "win32")("should handle escaped @scoped names on Windows", async () => {
const urls: string[] = [];
setHandler(async request => {
expect(request.method).toBe("GET");
expect(request.headers.get("accept")).toBe(
"application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*",
);
expect(request.headers.get("npm-auth-type")).toBe(null);
expect(await request.text()).toBe("");
urls.push(request.url);
return new Response("not to be found", { status: 404 });
});
await writeFile(
join(package_dir, "package.json"),
JSON.stringify({
name: "foo",
version: "0.0.1",
}),
);
// Test with escaped @ symbol, as Windows shells might pass it
const { stdout, stderr, exited } = spawn({
cmd: [bunExe(), "add", "\\@bar/baz"],
cwd: package_dir,
stdout: "pipe",
stdin: "pipe",
stderr: "pipe",
env,
});
const err = await new Response(stderr).text();
expect(err.split(/\r?\n/)).toContain(`error: GET http://localhost:${port}/@bar%2fbaz - 404`);
expect(await new Response(stdout).text()).toEqual(expect.stringContaining("bun add v1."));
expect(await exited).toBe(1);
expect(urls.sort()).toEqual([`${root_url}/@bar%2fbaz`]);
expect(requested).toBe(1);
try {
await access(join(package_dir, "bun.lockb"));
expect(() => {}).toThrow();
} catch (err: any) {
expect(err.code).toBe("ENOENT");
}
});
it("should add dependency with capital letters", async () => {
const urls: string[] = [];
setHandler(dummyRegistry(urls));
@@ -601,7 +643,7 @@ it("should add to devDependencies with --dev", async () => {
);
await access(join(package_dir, "bun.lockb"));
});
it.only("should add to optionalDependencies with --optional", async () => {
it("should add to optionalDependencies with --optional", async () => {
const urls: string[] = [];
setHandler(dummyRegistry(urls));
await writeFile(
@@ -2269,3 +2311,69 @@ it("should add local tarball dependency", async () => {
expect(await file(join(package_dir, "package.json")).text()).toInclude('"baz-0.0.3.tgz"'),
await access(join(package_dir, "bun.lockb"));
});
it("should handle escaped @scoped names on Windows", async () => {
const urls: string[] = [];
setHandler(
dummyRegistry(urls, {
"1.0.0": {},
}),
);
await writeFile(
join(package_dir, "package.json"),
JSON.stringify({
name: "foo",
version: "0.0.1",
}),
);
// On Windows, the shell may escape the @ symbol as \@
// This test verifies the fix for the bug where escaped @ symbols
// would cause "Do not pass posix paths to Windows APIs" error
const { stdout, stderr, exited } = spawn({
cmd: [bunExe(), "add", isWindows ? "\\@types/test" : "@types/test"],
cwd: package_dir,
stdout: "pipe",
stdin: "pipe",
stderr: "pipe",
env,
});
const err = await new Response(stderr).text();
expect(err).not.toContain("panic");
expect(err).not.toContain("Do not pass posix paths to Windows APIs");
expect(err).not.toContain("error:");
expect(err).toContain("Saved lockfile");
const out = await new Response(stdout).text();
expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
expect.stringContaining("bun add v1."),
"",
"installed @types/test@1.0.0",
"",
"1 package installed",
]);
expect(await exited).toBe(0);
expect(urls.sort()).toEqual([`${root_url}/@types%2ftest`, `${root_url}/@types/test-1.0.0.tgz`]);
expect(requested).toBe(2);
expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "@types"]);
expect(await readdirSorted(join(package_dir, "node_modules", "@types"))).toEqual(["test"]);
expect(await readdirSorted(join(package_dir, "node_modules", "@types", "test"))).toEqual(["package.json"]);
expect(await file(join(package_dir, "node_modules", "@types", "test", "package.json")).json()).toEqual({
name: "@types/test",
version: "1.0.0",
});
expect(await file(join(package_dir, "package.json")).json()).toEqual({
name: "foo",
version: "0.0.1",
dependencies: {
"@types/test": "^1.0.0",
},
});
await access(join(package_dir, "bun.lockb"));
});

View File

@@ -1,8 +1,7 @@
import { file, spawn, write } from "bun";
import { install_test_helpers } from "bun:internal-for-testing";
import { afterAll, beforeAll, beforeEach, describe, expect, test, afterEach } from "bun:test";
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test";
import { mkdirSync, rmSync, writeFileSync } from "fs";
import { readlink } from "fs/promises";
import { cp, exists, mkdir, rm } from "fs/promises";
import {
assertManifestsPopulated,