Compare commits

...

8 Commits

Author SHA1 Message Date
autofix-ci[bot]
1cbe3b7972 [autofix.ci] apply automated fixes 2025-11-08 01:05:01 +00:00
Marko Vejnovic
8aee635c70 i dont even know 2025-11-07 17:02:14 -08:00
Marko Vejnovic
25118a26e8 fix bun add 2025-11-07 16:41:37 -08:00
Marko Vejnovic
1642417414 A very hacky attempt at ssh 2025-11-07 16:14:35 -08:00
Marko Vejnovic
3c9db0dd52 Fix failing test 2025-11-07 13:34:33 -08:00
autofix-ci[bot]
42e77fe86b [autofix.ci] apply automated fixes 2025-11-07 20:26:18 +00:00
Marko Vejnovic
0a641308b7 fix(ENG-21492): Fix private git+ssh installs 2025-11-07 12:23:50 -08:00
Marko Vejnovic
7ed587b8c8 install(ENG-21492): Add test for private git+ssh repos 2025-11-07 11:54:39 -08:00
3 changed files with 102 additions and 8 deletions

View File

@@ -526,7 +526,7 @@ pub fn isGitHubShorthand(npa_str: []const u8) bool {
return seen_slash and does_not_end_with_slash;
}
const UrlProtocol = union(enum) {
pub const UrlProtocol = union(enum) {
well_formed: WellDefinedProtocol,
// A protocol which is not known by the library. Includes the : character, but not the
@@ -545,7 +545,7 @@ const UrlProtocol = union(enum) {
}
};
const UrlProtocolPair = struct {
pub const UrlProtocolPair = struct {
const Self = @This();
url: union(enum) {
@@ -723,11 +723,10 @@ fn normalizeProtocol(npa_str: []const u8) UrlProtocolPair {
return .{ .url = .{ .unmanaged = npa_str }, .protocol = .unknown };
}
/// Attempt to correct an scp-style URL into a proper URL, parsable with jsc.URL. Potentially
/// mutates the original input.
/// Attempt to correct an scp-style URL into a proper URL, parsable with jsc.URL.
///
/// This function assumes that the input is an scp-style URL.
fn correctUrl(
pub fn correctUrl(
url_proto_pair: *const UrlProtocolPair,
allocator: std.mem.Allocator,
) error{OutOfMemory}!UrlProtocolPair {

View File

@@ -395,10 +395,35 @@ pub const Repository = extern struct {
return null;
}
if (strings.hasPrefixComptime(url, "git@") or strings.hasPrefixComptime(url, "ssh://")) {
if (strings.hasPrefixComptime(url, "git@")) {
return url;
}
if (strings.hasPrefixComptime(url, "ssh://")) {
// TODO(markovejnovic): This is a stop-gap. One of the problems with the implementation
// here is that we should integrate hosted_git_info more thoroughly into the codebase
// to avoid the allocation and copy here. For now, the thread-local buffer is a good
// enough solution to avoid having to handle init/deinit.
// Fix malformed ssh:// URLs with colons using hosted_git_info.correctUrl
// ssh://git@github.com:user/repo -> ssh://git@github.com/user/repo
var pair = hosted_git_info.UrlProtocolPair{
.url = .{ .unmanaged = url },
.protocol = .{ .well_formed = .git_plus_ssh },
};
var corrected = hosted_git_info.correctUrl(&pair, bun.default_allocator) catch {
return url; // If correction fails, return original
};
defer corrected.deinit();
// Copy corrected URL to thread-local buffer
const corrected_str = corrected.urlSlice();
const result = ssh_path_buf[0..corrected_str.len];
bun.copy(u8, result, corrected_str);
return result;
}
if (Dependency.isSCPLikePath(url)) {
ssh_path_buf[0.."ssh://git@".len].* = "ssh://git@".*;
var rest = ssh_path_buf["ssh://git@".len..];
@@ -675,6 +700,7 @@ const string = []const u8;
const Dependency = @import("./dependency.zig");
const DotEnv = @import("../env_loader.zig");
const Environment = @import("../env.zig");
const hosted_git_info = @import("./hosted_git_info.zig");
const std = @import("std");
const FileSystem = @import("../fs.zig").FileSystem;

View File

@@ -1,7 +1,18 @@
import { file, spawn } from "bun";
import { $, file, spawn } from "bun";
import { afterAll, afterEach, beforeAll, beforeEach, expect, it, setDefaultTimeout } from "bun:test";
import { access, appendFile, copyFile, mkdir, readlink, rm, writeFile } from "fs/promises";
import { bunExe, bunEnv as env, readdirSorted, tmpdirSync, toBeValidBin, toBeWorkspaceLink, toHaveBins } from "harness";
import {
bunExe,
bunEnv as env,
isCI,
isMacOS,
readdirSorted,
tempDir,
tmpdirSync,
toBeValidBin,
toBeWorkspaceLink,
toHaveBins,
} from "harness";
import { join, relative, resolve } from "path";
import {
check_npm_auth_type,
@@ -1349,6 +1360,64 @@ it("git dep without package.json and with default branch", async () => {
});
});
it.skipIf(!(isCI && isMacOS))("should successfully install private git+ssh dependency with commit hash", async () => {
await writeFile(
join(package_dir, "package.json"),
JSON.stringify({
name: "test-private-git",
version: "1.0.0",
}),
);
// This is quite a trick that we employ, but it allows us to test private git+ssh installs, relatively well.
// Assuming that there's an `sshd` server running on the current machine, and assuming that the current user can ssh
// into itself, we can use a git+ssh URL that points to localhost.
const user = process.env.USER || process.env.USERNAME;
await using repo = tempDir("lolcat-repo", {});
// Initialize a git repo with a commit
await $`git config --global user.name "Automatic CI Tester"`;
await $`git config --global user.email "noreply@bun.com"`;
await $`git init`.cwd(repo);
await writeFile(
join(repo, "package.json"),
JSON.stringify({
name: "private-install-test-repo",
version: "1.2.3",
}),
);
await $`git add package.json`.cwd(repo);
await $`git commit -m "initial commit"`.cwd(repo);
const commitHash = (await $`git rev-parse HEAD`.cwd(repo)).text().trim();
// Now, we can attempt to install this repo as a git+ssh dependency, using the commit hash
console.log();
const { stdout, stderr, exited } = spawn({
cmd: [bunExe(), "add", `git+ssh://${user}@localhost:${repo}#${commitHash}`],
cwd: package_dir,
stdout: "pipe",
stdin: "ignore",
stderr: "pipe",
env,
});
const out = await stdout.text();
const err = await stderr.text();
// Assert stdout before exit code (per CLAUDE.md)
expect(out).toContain("bun add v1.");
expect(err).not.toContain("error:");
// Finally check exit code
expect(await exited).toBe(0);
// Verify package.json was updated with the dependency
const pkg = await file(join(package_dir, "package.json")).json();
expect(pkg.dependencies).toBeDefined();
expect(pkg.dependencies["private-install-test-repo"]).toBeDefined();
});
it("should let you add the same package twice", async () => {
const urls: string[] = [];
setHandler(dummyRegistry(urls, { "0.0.3": {} }));