aboutsummaryrefslogtreecommitdiff
path: root/src/cli/create_command.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/cli/create_command.zig')
-rw-r--r--src/cli/create_command.zig654
1 files changed, 545 insertions, 109 deletions
diff --git a/src/cli/create_command.zig b/src/cli/create_command.zig
index b70512f6f..a96d48624 100644
--- a/src/cli/create_command.zig
+++ b/src/cli/create_command.zig
@@ -30,10 +30,112 @@ const DotEnv = @import("../env_loader.zig");
const NPMClient = @import("../which_npm_client.zig").NPMClient;
const which = @import("../which.zig").which;
const clap = @import("clap");
+const Lock = @import("../lock.zig").Lock;
const CopyFile = @import("../copy_file.zig");
var bun_path_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
+const libgit2_basename = switch (std.builtin.os.tag) {
+ .linux => "libgit2.so.26",
+ .macos => "libgit2.dylib",
+ .windows => "libgit2.dll",
+ else => "libgit2.so",
+};
+
+const global_libgit2_paths = switch (std.builtin.os.tag) {
+ .macos => if (Environment.isAarch64)
+ &[][:0]const u8{
+ "/opt/homebrew/lib/libgit2.dylib",
+ "/opt/homebrew/libgit2/lib/libgit2.dylib",
+ "/usr/local/lib/libgit2.dylib",
+ "/usr/local/opt/libgit2/lib/libgit2.dylib",
+ }
+ else
+ &[_][:0]const u8{
+ "/usr/local/lib/libgit2.dylib",
+ "/usr/local/opt/libgit2/lib/libgit2.dylib",
+ },
+
+ else => &[_][:0]const u8{},
+};
+
+// the standard library function for this returns false when statically linking
+fn getSelfExeSharedLibPaths(allocator: *std.mem.Allocator) error{OutOfMemory}![][:0]u8 {
+ const List = std.ArrayList([:0]u8);
+ const os = std.os;
+ const Allocator = std.mem.Allocator;
+ const builtin = std.builtin;
+ const mem = std.mem;
+
+ switch (builtin.os.tag) {
+ .linux,
+ .freebsd,
+ .netbsd,
+ .dragonfly,
+ .openbsd,
+ => {
+ var paths = List.init(allocator);
+ errdefer {
+ const slice = paths.toOwnedSlice();
+ for (slice) |item| {
+ allocator.free(item);
+ }
+ allocator.free(slice);
+ }
+ try os.dl_iterate_phdr(&paths, error{OutOfMemory}, struct {
+ fn callback(info: *os.dl_phdr_info, size: usize, list: *List) !void {
+ _ = size;
+ const name = info.dlpi_name orelse return;
+ if (name[0] == '/') {
+ const item = try list.allocator.dupeZ(u8, mem.spanZ(name));
+ errdefer list.allocator.free(item);
+ try list.append(item);
+ }
+ }
+ }.callback);
+ return paths.toOwnedSlice();
+ },
+ .macos, .ios, .watchos, .tvos => {
+ var paths = List.init(allocator);
+ errdefer {
+ const slice = paths.toOwnedSlice();
+ for (slice) |item| {
+ allocator.free(item);
+ }
+ allocator.free(slice);
+ }
+ const img_count = std.c._dyld_image_count();
+ var i: u32 = 0;
+ while (i < img_count) : (i += 1) {
+ const name = std.c._dyld_get_image_name(i);
+ const item = try allocator.dupeZ(u8, mem.spanZ(name));
+ errdefer allocator.free(item);
+ try paths.append(item);
+ }
+ return paths.toOwnedSlice();
+ },
+ // revisit if Haiku implements dl_iterat_phdr (https://dev.haiku-os.org/ticket/15743)
+ .haiku => {
+ var paths = List.init(allocator);
+ errdefer {
+ const slice = paths.toOwnedSlice();
+ for (slice) |item| {
+ allocator.free(item);
+ }
+ allocator.free(slice);
+ }
+
+ var b = "/boot/system/runtime_loader";
+ const item = try allocator.dupeZ(u8, mem.spanZ(b));
+ errdefer allocator.free(item);
+ try paths.append(item);
+
+ return paths.toOwnedSlice();
+ },
+ else => @compileError("getSelfExeSharedLibPaths unimplemented for this target"),
+ }
+}
+
const skip_dirs = &[_]string{ "node_modules", ".git" };
const skip_files = &[_]string{
"package-lock.json",
@@ -136,6 +238,7 @@ const CreateOptions = struct {
skip_install: bool = false,
overwrite: bool = false,
skip_git: bool = false,
+ verbose: bool = false,
const params = [_]clap.Param(clap.Help){
clap.parseParam("--help Print this menu") catch unreachable,
@@ -145,6 +248,7 @@ const CreateOptions = struct {
clap.parseParam("--force Overwrite existing files") catch unreachable,
clap.parseParam("--no-install Don't install node_modules") catch unreachable,
clap.parseParam("--no-git Don't create a git repository") catch unreachable,
+ clap.parseParam("--verbose Too many logs") catch unreachable,
clap.parseParam("<POS>... ") catch unreachable,
};
@@ -183,17 +287,10 @@ const CreateOptions = struct {
opts.npm_client = NPMClient.Tag.pnpm;
}
- if (args.flag("--no-install")) {
- opts.skip_install = true;
- }
-
- if (args.flag("--no-git")) {
- opts.skip_git = true;
- }
-
- if (args.flag("--force")) {
- opts.overwrite = true;
- }
+ opts.verbose = args.flag("--verbose");
+ opts.skip_install = args.flag("--no-install");
+ opts.skip_git = args.flag("--no-git");
+ opts.overwrite = args.flag("--force");
return opts;
}
@@ -228,6 +325,9 @@ pub const CreateCommand = struct {
home_dir_buf[outdir_path.len] = 0;
var outdir_path_ = home_dir_buf[0..outdir_path.len :0];
std.fs.accessAbsoluteZ(outdir_path_, .{}) catch break :outer;
+ if (create_options.verbose) {
+ Output.prettyErrorln("reading from {s}", .{outdir_path});
+ }
break :brk outdir_path;
}
}
@@ -238,6 +338,9 @@ pub const CreateCommand = struct {
home_dir_buf[outdir_path.len] = 0;
var outdir_path_ = home_dir_buf[0..outdir_path.len :0];
std.fs.accessAbsoluteZ(outdir_path_, .{}) catch break :outer;
+ if (create_options.verbose) {
+ Output.prettyErrorln("reading from {s}", .{outdir_path});
+ }
break :brk outdir_path;
}
@@ -248,6 +351,9 @@ pub const CreateCommand = struct {
home_dir_buf[outdir_path.len] = 0;
var outdir_path_ = home_dir_buf[0..outdir_path.len :0];
std.fs.accessAbsoluteZ(outdir_path_, .{}) catch break :outer;
+ if (create_options.verbose) {
+ Output.prettyErrorln("reading from {s}", .{outdir_path});
+ }
break :brk outdir_path;
}
}
@@ -266,6 +372,10 @@ pub const CreateCommand = struct {
// alacritty is fast
if (env_loader.map.get("ALACRITTY_LOG") != null) {
progress.refresh_rate_ns = std.time.ns_per_ms * 8;
+
+ if (create_options.verbose) {
+ Output.prettyErrorln("your using alacritty", .{});
+ }
}
defer {
@@ -277,6 +387,10 @@ pub const CreateCommand = struct {
const is_remote_template = !std.fs.path.isAbsolute(template);
+ if (create_options.verbose) {
+ Output.prettyErrorln("is_remote_template {d}", .{@boolToInt(is_remote_template)});
+ }
+
if (is_remote_template) {
var tarball_bytes: MutableString = Example.fetch(ctx, template, &progress, node) catch |err| {
switch (err) {
@@ -288,7 +402,8 @@ pub const CreateCommand = struct {
template,
});
Output.flush();
- const examples = try Example.fetchAllLocalAndRemote(ctx, &env_loader, filesystem);
+
+ const examples = try Example.fetchAllLocalAndRemote(ctx, null, &env_loader, filesystem);
Example.print(examples.items, dirname);
Output.flush();
std.os.exit(1);
@@ -325,6 +440,7 @@ pub const CreateCommand = struct {
var archive_context = Archive.Context{
.pluckers = &pluckers,
+ .all_files = undefined,
.overwrite_list = std.StringArrayHashMap(void).init(ctx.allocator),
};
@@ -348,14 +464,15 @@ pub const CreateCommand = struct {
// Thank you create-react-app for this copy (and idea)
Output.prettyErrorln(
- "<r><red>error<r><d>: <r>The directory <b><green>{s}<r> contains files that could conflict:",
+ "<r><red>error<r><d>: <r>The directory <b><blue>{s}<r>/ contains files that could conflict:\n\n",
.{
std.fs.path.basename(destination),
},
);
for (archive_context.overwrite_list.keys()) |path| {
if (strings.endsWith(path, std.fs.path.sep_str)) {
- Output.prettyErrorln("<r> <cyan>{s}<r>", .{path});
+ Output.prettyError("<r> <blue>{s}<r>", .{path[0 .. std.math.max(path.len, 1) - 1]});
+ Output.prettyErrorln(std.fs.path.sep_str, .{});
} else {
Output.prettyErrorln("<r> {s}", .{path});
}
@@ -369,6 +486,8 @@ pub const CreateCommand = struct {
tarball_buf_list.items,
destination,
&archive_context,
+ void,
+ void{},
1,
false,
false,
@@ -416,48 +535,63 @@ pub const CreateCommand = struct {
};
const Walker = @import("../walker_skippable.zig");
- var walker = try Walker.walk(template_dir, ctx.allocator, skip_files, skip_dirs);
- defer walker.deinit();
+ var walker_ = try Walker.walk(template_dir, ctx.allocator, skip_files, skip_dirs);
+ defer walker_.deinit();
var count: usize = 0;
- while (try walker.next()) |entry| {
- // TODO: make this not walk these folders entirely
- // rather than checking each file path.....
- if (entry.kind != .File) continue;
- var outfile = destination_dir.createFile(entry.path, .{}) catch brk: {
- if (std.fs.path.dirname(entry.path)) |entry_dirname| {
- destination_dir.makePath(entry_dirname) catch {};
- }
- break :brk destination_dir.createFile(entry.path, .{}) catch |err| {
- node.end();
+ const FileCopier = struct {
+ pub fn copy(
+ destination_dir_: std.fs.Dir,
+ walker: *Walker,
+ node_: *std.Progress.Node,
+ progress_: *std.Progress,
+ ) !void {
+ while (try walker.next()) |entry| {
+ // TODO: make this not walk these folders entirely
+ // rather than checking each file path.....
+ if (entry.kind != .File) continue;
+
+ var outfile = destination_dir_.createFile(entry.path, .{}) catch brk: {
+ if (std.fs.path.dirname(entry.path)) |entry_dirname| {
+ destination_dir_.makePath(entry_dirname) catch {};
+ }
+ break :brk destination_dir_.createFile(entry.path, .{}) catch |err| {
+ node_.end();
- progress.refresh();
+ progress_.refresh();
- Output.prettyErrorln("<r><red>{s}<r>: copying file {s}", .{ @errorName(err), entry.path });
- Output.flush();
- std.os.exit(1);
- };
- };
- defer outfile.close();
- defer node.completeOne();
-
- var infile = try entry.dir.openFile(entry.basename, .{ .read = true });
- defer infile.close();
- CopyFile.copy(infile.handle, outfile.handle) catch {
- entry.dir.copyFile(entry.basename, destination_dir, entry.path, .{}) catch |err| {
- node.end();
+ Output.prettyErrorln("<r><red>{s}<r>: copying file {s}", .{ @errorName(err), entry.path });
+ Output.flush();
+ std.os.exit(1);
+ };
+ };
+ defer outfile.close();
+ defer node_.completeOne();
- progress.refresh();
+ var infile = try entry.dir.openFile(entry.basename, .{ .read = true });
+ defer infile.close();
- Output.prettyErrorln("<r><red>{s}<r>: copying file {s}", .{ @errorName(err), entry.path });
- Output.flush();
- std.os.exit(1);
- };
- };
- var stat = outfile.stat() catch continue;
- _ = C.fchmod(outfile.handle, stat.mode);
- }
+ // Assumption: you only really care about making sure something that was executable is still executable
+ const stat = infile.stat() catch continue;
+ _ = C.fchmod(outfile.handle, stat.mode);
+
+ CopyFile.copy(infile.handle, outfile.handle) catch {
+ entry.dir.copyFile(entry.basename, destination_dir_, entry.path, .{}) catch |err| {
+ node_.end();
+
+ progress_.refresh();
+
+ Output.prettyErrorln("<r><red>{s}<r>: copying file {s}", .{ @errorName(err), entry.path });
+ Output.flush();
+ std.os.exit(1);
+ };
+ };
+ }
+ }
+ };
+
+ try FileCopier.copy(destination_dir, &walker_, node, &progress);
package_json_file = destination_dir.openFile("package.json", .{ .read = true, .write = true }) catch |err| {
node.end();
@@ -547,6 +681,8 @@ pub const CreateCommand = struct {
var preinstall_tasks = std.mem.zeroes(std.ArrayListUnmanaged([]const u8));
var postinstall_tasks = std.mem.zeroes(std.ArrayListUnmanaged([]const u8));
+ var has_dependencies: bool = false;
+
{
var i: usize = 0;
var property_i: usize = 0;
@@ -554,6 +690,13 @@ pub const CreateCommand = struct {
const property = package_json_expr.data.e_object.properties[i];
const key = property.key.?.asString(ctx.allocator).?;
+ has_dependencies = has_dependencies or
+ ((strings.eqlComptime(key, "dependencies") or
+ strings.eqlComptime(key, "devDependencies") or
+ std.mem.indexOf(u8, key, "optionalDependencies") != null or
+ strings.eqlComptime(key, "peerDependencies")) and
+ (property.value.?.data == .e_object and property.value.?.data.e_object.properties.len > 0));
+
if (key.len == 0 or !strings.eqlComptime(key, "bun-create")) {
package_json_expr.data.e_object.properties[property_i] = property;
property_i += 1;
@@ -605,6 +748,11 @@ pub const CreateCommand = struct {
}
}
}
+ package_json_expr.data.e_object.properties = package_json_expr.data.e_object.properties[0..property_i];
+ }
+
+ if (create_options.verbose) {
+ Output.prettyErrorln("Has dependencies? {d}", .{@boolToInt(has_dependencies)});
}
var package_json_writer = JSPrinter.NewFileWriter(package_json_file);
@@ -615,9 +763,38 @@ pub const CreateCommand = struct {
std.os.exit(1);
};
+ {
+ var parent_dir = try std.fs.openDirAbsolute(destination, .{});
+ defer parent_dir.close();
+ std.os.linkat(parent_dir.fd, "gitignore", parent_dir.fd, ".gitignore", 0) catch {};
+ std.os.unlinkat(
+ parent_dir.fd,
+ "gitignore",
+ 0,
+ ) catch {};
+ std.os.unlinkat(
+ parent_dir.fd,
+ ".npmignore",
+ 0,
+ ) catch {};
+ }
+
const PATH = env_loader.map.get("PATH") orelse "";
var npm_client_: ?NPMClient = null;
+ create_options.skip_install = create_options.skip_install or !has_dependencies;
+
+ if (!create_options.skip_git) {
+ if (!create_options.skip_install) {
+ GitHandler.spawn(destination, PATH, create_options.verbose);
+ } else {
+ if (create_options.verbose) {
+ create_options.skip_git = GitHandler.run(destination, PATH, true) catch false;
+ } else {
+ create_options.skip_git = GitHandler.run(destination, PATH, false) catch false;
+ }
+ }
+ }
if (!create_options.skip_install) {
if (env_loader.map.get("NPM_CLIENT")) |npm_client_bin| {
@@ -694,39 +871,8 @@ pub const CreateCommand = struct {
}
}
- {
- var parent_dir = try std.fs.openDirAbsolute(destination, .{});
- std.os.linkat(parent_dir.fd, "gitignore", parent_dir.fd, ".gitignore", 0) catch {};
- std.os.unlinkat(
- parent_dir.fd,
- "gitignore",
- 0,
- ) catch {};
- parent_dir.close();
- }
-
- if (!create_options.skip_git) {
- if (which(&bun_path_buf, PATH, destination, "git")) |git| {
- const git_commands = .{
- &[_]string{ std.mem.span(git), "init", "--quiet" },
- &[_]string{ std.mem.span(git), "add", "-A", destination, "--ignore-errors" },
- &[_]string{ std.mem.span(git), "commit", "-am", "\"Initial Commit\"", "--quiet" },
- };
- // same names, just comptime known values
-
- inline for (comptime std.meta.fieldNames(@TypeOf(Commands))) |command_field| {
- const command: []const string = @field(git_commands, command_field);
- var process = try std.ChildProcess.init(command, ctx.allocator);
- process.cwd = destination;
- process.stdin_behavior = .Inherit;
- process.stdout_behavior = .Inherit;
- process.stderr_behavior = .Inherit;
- defer process.deinit();
-
- var term = try process.spawnAndWait();
- _ = process.kill() catch undefined;
- }
- }
+ if (!create_options.skip_install and !create_options.skip_git) {
+ create_options.skip_git = !GitHandler.wait();
}
Output.printError("\n", .{});
@@ -734,11 +880,13 @@ pub const CreateCommand = struct {
Output.prettyErrorln(" <r><d>bun create {s}<r>", .{template});
Output.flush();
- Output.pretty(
- \\
- \\<r><d>-----<r>
- \\
- , .{});
+ if (!create_options.skip_install) {
+ Output.pretty(
+ \\
+ \\<r><d>-----<r>
+ \\
+ , .{});
+ }
if (!create_options.skip_git and !create_options.skip_install) {
Output.pretty(
@@ -800,7 +948,7 @@ const PackageDownloadThread = struct {
std.Thread.Futex.wake(&this.done, 1);
}
- pub fn spawn(allocator: *std.mem.Allocator, tarball_url: string) !*PackageDownloadThread {
+ pub fn spawn(allocator: *std.mem.Allocator, tarball_url: string, progress_node: *std.Progress.Node) !*PackageDownloadThread {
var download = try allocator.create(PackageDownloadThread);
download.* = PackageDownloadThread{
.allocator = allocator,
@@ -811,6 +959,10 @@ const PackageDownloadThread = struct {
.thread = undefined,
};
+ if (Output.enable_ansi_colors) {
+ download.client.progress_node = progress_node;
+ }
+
download.thread = try std.Thread.spawn(.{}, threadHandler, .{download});
return download;
@@ -853,8 +1005,9 @@ pub const Example = struct {
}
}
- pub fn fetchAllLocalAndRemote(ctx: Command.Context, env_loader: *DotEnv.Loader, filesystem: *fs.FileSystem) !std.ArrayList(Example) {
- const remote_examples = try Example.fetchAll(ctx);
+ pub fn fetchAllLocalAndRemote(ctx: Command.Context, node: ?*std.Progress.Node, env_loader: *DotEnv.Loader, filesystem: *fs.FileSystem) !std.ArrayList(Example) {
+ const remote_examples = try Example.fetchAll(ctx, node);
+ if (node) |node_| node_.end();
var examples = std.ArrayList(Example).fromOwnedSlice(ctx.allocator, remote_examples);
{
@@ -936,6 +1089,7 @@ pub const Example = struct {
url = URL.parse(try std.fmt.bufPrint(&url_buf, "https://registry.npmjs.org/@bun-examples/{s}/latest", .{name}));
client = HTTPClient.init(ctx.allocator, .GET, url, .{}, "");
client.timeout = timeout;
+ client.progress_node = progress;
var response = try client.send("", &mutable);
switch (response.status_code) {
@@ -1007,23 +1161,11 @@ pub const Example = struct {
progress.name = "Downloading tarball";
refresher.refresh();
- var thread: *PackageDownloadThread = try PackageDownloadThread.spawn(ctx.allocator, tarball_url);
-
- std.Thread.Futex.wait(&thread.done, 1, std.time.ns_per_ms * 100) catch {};
-
- progress.setEstimatedTotalItems(thread.client.body_size);
- progress.setCompletedItems(thread.client.read_count);
+ var thread: *PackageDownloadThread = try PackageDownloadThread.spawn(ctx.allocator, tarball_url, progress);
refresher.maybeRefresh();
- if (thread.done.load(.Acquire) == 0) {
- while (true) {
- std.Thread.Futex.wait(&thread.done, 1, std.time.ns_per_ms * 100) catch {};
- progress.setEstimatedTotalItems(thread.client.body_size);
- progress.setCompletedItems(thread.client.read_count);
- refresher.maybeRefresh();
- if (thread.done.load(.Acquire) == 1) {
- break;
- }
- }
+
+ while (thread.done.load(.Acquire) == 0) {
+ std.Thread.Futex.wait(&thread.done, 1, std.time.ns_per_ms * 10000) catch {};
}
refresher.maybeRefresh();
@@ -1042,10 +1184,15 @@ pub const Example = struct {
return thread.buffer;
}
- pub fn fetchAll(ctx: Command.Context) ![]Example {
+ pub fn fetchAll(ctx: Command.Context, progress_node: ?*std.Progress.Node) ![]Example {
url = URL.parse(examples_url);
client = HTTPClient.init(ctx.allocator, .GET, url, .{}, "");
client.timeout = timeout;
+
+ if (Output.enable_ansi_colors) {
+ client.progress_node = progress_node;
+ }
+
var mutable: MutableString = try MutableString.init(ctx.allocator, 1024);
var response = client.send("", &mutable) catch |err| {
switch (err) {
@@ -1139,9 +1286,13 @@ pub const CreateListExamplesCommand = struct {
env_loader.loadProcess();
const time = std.time.nanoTimestamp();
- const examples = try Example.fetchAllLocalAndRemote(ctx, &env_loader, filesystem);
+ var progress = std.Progress{};
+ var node = try progress.start("Fetching manifest", 0);
+ progress.refresh();
+
+ const examples = try Example.fetchAllLocalAndRemote(ctx, node, &env_loader, filesystem);
Output.printStartEnd(time, std.time.nanoTimestamp());
- Output.prettyln(" <d>Fetched examples<r>", .{});
+ Output.prettyln(" <d>Fetched manifest<r>", .{});
Output.prettyln("Welcome to Bun! Create a new project by pasting any of the following:\n\n", .{});
Output.flush();
@@ -1162,3 +1313,288 @@ pub const CreateListExamplesCommand = struct {
Output.flush();
}
};
+
+const GitHandler = struct {
+ var success: std.atomic.Atomic(u32) = undefined;
+ var thread: std.Thread = undefined;
+ pub fn spawn(
+ destination: string,
+ PATH: string,
+ verbose: bool,
+ ) void {
+ success = std.atomic.Atomic(u32).init(0);
+
+ thread = std.Thread.spawn(.{}, spawnThread, .{ destination, PATH, verbose }) catch |err| {
+ Output.prettyErrorln("<r><red>{s}<r>", .{@errorName(err)});
+ Output.flush();
+ std.os.exit(1);
+ };
+ }
+
+ fn spawnThread(
+ destination: string,
+ PATH: string,
+ verbose: bool,
+ ) void {
+ Output.Source.configureThread();
+ std.Thread.setName(thread, "git") catch {};
+ defer Output.flush();
+ const outcome = if (verbose)
+ run(destination, PATH, true) catch false
+ else
+ run(destination, PATH, false) catch false;
+
+ @fence(.Acquire);
+ success.store(
+ if (outcome)
+ 1
+ else
+ 2,
+ .Release,
+ );
+ std.Thread.Futex.wake(&success, 1);
+ }
+
+ pub fn wait() bool {
+ @fence(.Release);
+
+ while (success.load(.Acquire) == 0) {
+ std.Thread.Futex.wait(&success, 0, 1000) catch continue;
+ }
+
+ const outcome = success.load(.Acquire) == 1;
+ thread.join();
+ return outcome;
+ }
+
+ pub fn run(
+ destination: string,
+ PATH: string,
+ comptime verbose: bool,
+ ) !bool {
+ const git_start = std.time.nanoTimestamp();
+
+ // This feature flag is disabled.
+ // using libgit2 is slower than the CLI.
+ // [481.00ms] git
+ // [89.00ms] git
+ // if (comptime FeatureFlags.use_libgit2) {
+ // try_to_use_libgit2: {
+ // if (comptime Environment.isWindows) @compileError("TODO win32");
+
+ // // since we link libc, it always goes through dlopen unless windows
+ // const DlDynlib = std.DynLib;
+ // var libgit2_dylib: std.DynLib = brk: {
+ // for (global_libgit2_paths) |lib_path| {
+ // break :brk DlDynlib.openZ(lib_path) catch continue;
+ // }
+
+ // var lib_paths = getSelfExeSharedLibPaths(default_allocator) catch break :try_to_use_libgit2;
+
+ // // this is a list of every dynamically loaded library on the user's computer?
+ // for (lib_paths) |lib_path| {
+ // const basename = std.fs.path.basename(std.mem.span(lib_path));
+ // if (basename.len == (comptime libgit2_basename.len) and
+ // std.hash.Wyhash.hash(10, basename) == comptime std.hash.Wyhash.hash(10, libgit2_basename))
+ // {
+ // break :brk DlDynlib.openZ(lib_path) catch continue;
+ // }
+ // }
+ // break :try_to_use_libgit2;
+ // };
+
+ // const libgit2 = @import("../deps/libgit2.zig");
+ // var repo: ?*libgit2.git_repository = null;
+ // // https://libgit2.org/docs/examples/init/
+ // // Note: git_threads_init was renamed to git_libgit2_init
+ // // https://mail.gnome.org/archives/commits-list/2015-January/msg03703.html
+ // // ...in 2015 and that doc is still out of date
+
+ // const git_repository_init = libgit2_dylib.lookup(libgit2.git_repository_init, "git_repository_init") orelse break :try_to_use_libgit2;
+ // const git_repository_free = libgit2_dylib.lookup(libgit2.git_repository_free, "git_repository_free") orelse break :try_to_use_libgit2;
+ // const git_signature_free = libgit2_dylib.lookup(libgit2.git_signature_free, "git_signature_free") orelse break :try_to_use_libgit2;
+ // const git_signature_default = libgit2_dylib.lookup(libgit2.git_signature_default, "git_signature_default") orelse break :try_to_use_libgit2;
+ // const git_repository_index = libgit2_dylib.lookup(libgit2.git_repository_index, "git_repository_index") orelse break :try_to_use_libgit2;
+ // const git_index_read_tree = libgit2_dylib.lookup(libgit2.git_index_read_tree, "git_index_read_tree") orelse break :try_to_use_libgit2;
+ // const git_index_write_tree = libgit2_dylib.lookup(libgit2.git_index_write_tree, "git_index_write_tree") orelse break :try_to_use_libgit2;
+ // const git_index_write = libgit2_dylib.lookup(libgit2.git_index_write, "git_index_write") orelse break :try_to_use_libgit2;
+
+ // const git_index_add_all = libgit2_dylib.lookup(libgit2.git_index_add_all, "git_index_add_all") orelse break :try_to_use_libgit2;
+ // const git_tree_lookup = libgit2_dylib.lookup(libgit2.git_tree_lookup, "git_tree_lookup") orelse break :try_to_use_libgit2;
+ // const git_commit_create_v = libgit2_dylib.lookup(libgit2.git_commit_create_v, "git_commit_create_v") orelse break :try_to_use_libgit2;
+ // const git_index_free = libgit2_dylib.lookup(libgit2.git_index_free, "git_index_free") orelse break :try_to_use_libgit2;
+ // const git_tree_free = libgit2_dylib.lookup(libgit2.git_tree_free, "git_tree_free") orelse break :try_to_use_libgit2;
+ // const git_index_add_bypath = libgit2_dylib.lookup(libgit2.git_index_add_bypath, "git_index_add_bypath") orelse break :try_to_use_libgit2;
+ // const git_status_should_ignore = libgit2_dylib.lookup(libgit2.git_status_should_ignore, "git_status_should_ignore") orelse break :try_to_use_libgit2;
+
+ // var sig: ?*libgit2.git_signature = null;
+ // // var destination_z = destination.ptr[0..destination.len :0];
+ // // const original_cwd = std.os.getcwd(&bun_path_buf) catch break :try_to_use_libgit2;
+ // // bun_path_buf[original_cwd.len] = 0;
+ // // std.os.chdirZ(destination_z) catch {};
+ // // var origin_z = bun_path_buf[0..original_cwd.len :0];
+ // // defer std.os.chdirZ(origin_z) catch {};
+
+ // if (libgit2_dylib.lookup(libgit2.git_libgit2_init, "git_libgit2_init")) |git_libgit2_init| {
+ // std.debug.assert(git_libgit2_init() > -1);
+ // }
+
+ // var rc: c_int = 0;
+ // rc = git_repository_init(&repo, destination.ptr, 0);
+ // if (rc < 0) break :try_to_use_libgit2;
+ // var repo_ = repo orelse break :try_to_use_libgit2;
+ // // defer git_repository_free(repo_);
+
+ // rc = git_signature_default(&sig, repo_);
+ // // this fails if they never ran git config
+ // // the child process will fail too, so we just skip
+ // if (rc < 0) {
+ // return false;
+ // }
+ // // defer git_signature_free(sig.?);
+
+ // var index: ?*libgit2.git_index = null;
+ // var tree_id: libgit2.git_oid = undefined;
+ // var commit_id: libgit2.git_oid = undefined;
+ // var second_commit_id: libgit2.git_oid = undefined;
+ // var git_tree: *libgit2.git_tree = undefined;
+
+ // rc = git_repository_index(&index, repo_);
+ // if (rc < 0) break :try_to_use_libgit2;
+
+ // // // add gitignore file first
+ // // // technically, we should add every gitignore first but i'll leave that as a future TODO
+ // // if (files_list.containsAdapted(comptime std.hash.Wyhash.hash(0, "gitignore"), Archive.Context.U64Context{})) {
+ // // rc = git_index_add_bypath(index.?, ".gitignore");
+ // // if (rc < 0) break :try_to_use_libgit2;
+ // // } else if (files_list.containsAdapted(comptime std.hash.Wyhash.hash(0, ".gitignore"), Archive.Context.U64Context{})) {
+ // // rc = git_index_add_bypath(index.?, ".gitignore");
+ // // if (rc < 0) break :try_to_use_libgit2;
+ // // }
+
+ // var files = files_list.values();
+
+ // // without git
+ // // [6.00ms] bun create /Users/jarred/.bun-create/react2
+
+ // // with git:
+ // // [44.00ms] bun create /Users/jarred/.bun-create/react2
+
+ // var strs = std.mem.zeroes(libgit2.git_strarray);
+ // // var star = [_]u8{ '*', 0 };
+ // // var star_ptr: [*c]u8 = &star;
+ // strs.strings = files.ptr;
+ // strs.count = files.len;
+ // rc = git_index_add_all(index, &strs, libgit2.GIT_INDEX_ADD_DISABLE_PATHSPEC_MATCH, null, null);
+ // if (rc < 0) break :try_to_use_libgit2;
+
+ // // if (comptime verbose) {
+ // // for (files) |file| {
+ // // var ignored: c_int = 0;
+ // // rc = git_status_should_ignore(&ignored, repo_, file);
+ // // if (rc < 0) break :try_to_use_libgit2;
+ // // if (ignored > 0) {
+ // // Output.prettyErrorln("[libgit2]: ignored {s}", .{std.mem.sliceTo(file, 0)});
+ // // continue;
+ // // }
+
+ // // // this can fail for a variety of reasons
+ // // // if it does, better to get most of the files than stop
+ // // rc = git_index_add_bypath(index.?, file);
+ // // switch (rc) {
+ // // libgit2.GIT_OK => {
+ // // Output.prettyErrorln("[libgit2]: Added {s}", .{std.mem.sliceTo(file, 0)});
+ // // },
+ // // libgit2.GIT_ENOTFOUND => {
+ // // Output.prettyErrorln("[libgit2]: not found {s}", .{std.mem.sliceTo(file, 0)});
+ // // },
+ // // else => {
+ // // Output.prettyErrorln("[libgit2]: error {d} for {s}", .{ rc, std.mem.sliceTo(file, 0) });
+ // // },
+ // // }
+ // // }
+ // // } else {
+ // // for (files) |file| {
+ // // var ignored: c_int = 0;
+ // // rc = git_status_should_ignore(&ignored, repo_, file);
+ // // if (rc < 0) break :try_to_use_libgit2;
+ // // if (ignored > 0) continue;
+
+ // // // this can fail for a variety of reasons
+ // // // if it does, better to get most of the files than stop
+ // // _ = git_index_add_bypath(index.?, file);
+ // // }
+ // // }
+
+ // // if you don't call git_index_write, git status will look very strange.
+ // rc = git_index_write(index.?);
+ // if (rc < 0) break :try_to_use_libgit2;
+
+ // rc = git_index_write_tree(&tree_id, index);
+ // if (rc < 0) break :try_to_use_libgit2;
+
+ // rc = git_tree_lookup(&git_tree, repo_, &tree_id);
+ // if (rc < 0) break :try_to_use_libgit2;
+ // // defer git_tree_free(git_tree);
+
+ // rc = git_commit_create_v(
+ // &commit_id,
+ // repo_,
+ // "HEAD",
+ // sig.?,
+ // sig.?,
+ // null,
+ // "Initial commit (via bun create)",
+ // git_tree,
+ // 0,
+ // );
+ // if (rc < 0) break :try_to_use_libgit2;
+
+ // if (comptime verbose) {
+ // Output.prettyErrorln("git backend: libgit2", .{});
+ // }
+
+ // Output.prettyError("\n", .{});
+ // Output.printStartEnd(git_start, std.time.nanoTimestamp());
+ // Output.prettyError(" <d>git<r>\n", .{});
+
+ // // success!
+ // return true;
+ // }
+ // }
+
+ if (which(&bun_path_buf, PATH, destination, "git")) |git| {
+ const git_commands = .{
+ &[_]string{ std.mem.span(git), "init", "--quiet" },
+ &[_]string{ std.mem.span(git), "add", destination, "--ignore-errors" },
+ &[_]string{ std.mem.span(git), "commit", "-am", "\"Initial commit (via bun create)\"", "--quiet" },
+ };
+
+ if (comptime verbose) {
+ Output.prettyErrorln("git backend: {s}", .{git});
+ }
+
+ // same names, just comptime known values
+
+ inline for (comptime std.meta.fieldNames(@TypeOf(Commands))) |command_field| {
+ const command: []const string = @field(git_commands, command_field);
+ var process = try std.ChildProcess.init(command, default_allocator);
+ process.cwd = destination;
+ process.stdin_behavior = .Inherit;
+ process.stdout_behavior = .Inherit;
+ process.stderr_behavior = .Inherit;
+ defer process.deinit();
+
+ var term = try process.spawnAndWait();
+ _ = process.kill() catch undefined;
+ }
+
+ Output.prettyError("\n", .{});
+ Output.printStartEnd(git_start, std.time.nanoTimestamp());
+ Output.prettyError(" <d>git<r>\n", .{});
+ return true;
+ }
+
+ return false;
+ }
+};