diff options
author | 2021-10-14 22:10:08 -0700 | |
---|---|---|
committer | 2021-10-14 22:10:08 -0700 | |
commit | 7e159cb5cdfc288c1aab7b0f810f5b54438b9f19 (patch) | |
tree | 106e5c4f0069ea05d3d1ef536ef20176f6ff9e5b /src | |
parent | 74cda61c88b42c4505e8ce39dd395d1006787c43 (diff) | |
download | bun-7e159cb5cdfc288c1aab7b0f810f5b54438b9f19.tar.gz bun-7e159cb5cdfc288c1aab7b0f810f5b54438b9f19.tar.zst bun-7e159cb5cdfc288c1aab7b0f810f5b54438b9f19.zip |
:sparkle:
Diffstat (limited to 'src')
-rw-r--r-- | src/cli.zig | 4 | ||||
-rw-r--r-- | src/cli/create_command.zig | 409 | ||||
-rw-r--r-- | src/libarchive/libarchive.zig | 8 | ||||
-rw-r--r-- | src/main.zig | 14 | ||||
-rw-r--r-- | src/which_npm_client.zig | 72 |
5 files changed, 286 insertions, 221 deletions
diff --git a/src/cli.zig b/src/cli.zig index 7a20cc3b0..f6c7217af 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -444,7 +444,7 @@ const HelpCommand = struct { const fmt = \\> <r> <b><green>dev <r><d> ./a.ts ./b.jsx<r> Start a Bun Dev Server \\> <r> <b><magenta>bun <r><d> ./a.ts ./b.jsx<r> Bundle dependencies of input files into a <r><magenta>.bun<r> - \\> <r> <b><cyan>create <r><d> next<r> <r> Start a new project from a template <d>(shorthand: c)<r> + \\> <r> <b><cyan>create <r><d> next ./app<r> Start a new project from a template <d>(shorthand: c)<r> \\> <r> <b><blue>discord <r> Open Bun's Discord server \\> <r> <b><d>help <r> Print this help menu \\ @@ -519,7 +519,7 @@ pub const Command = struct { pub fn create(allocator: *std.mem.Allocator, log: *logger.Log, comptime command: Command.Tag) anyerror!Context { return Command.Context{ - .args = if (comptime command != Command.Tag.CreateCommand) + .args = if (comptime command != Command.Tag.CreateCommand) try Arguments.parse(allocator, command) else std.mem.zeroes(Api.TransformOptions), diff --git a/src/cli/create_command.zig b/src/cli/create_command.zig index d2a780430..b70512f6f 100644 --- a/src/cli/create_command.zig +++ b/src/cli/create_command.zig @@ -41,6 +41,17 @@ const skip_files = &[_]string{ "pnpm-lock.yaml", }; +const never_conflict = &[_]string{ + "README.md", + "gitignore", + ".gitignore", + ".git/", +}; + +const npm_task_args = &[_]string{ + "exec", +}; + var bun_path: ?[:0]const u8 = null; fn execTask(allocator: *std.mem.Allocator, task_: string, cwd: string, PATH: string, npm_client: NPMClient) void { const task = std.mem.trim(u8, task_, " \n\r\t"); @@ -52,13 +63,17 @@ fn execTask(allocator: *std.mem.Allocator, task_: string, cwd: string, PATH: str count += 1; } - var argv = allocator.alloc(string, count + 2) catch return; + const npm_args = 2; + const total = count + npm_args; + var argv = allocator.alloc(string, total) catch return; defer allocator.free(argv); argv[0] = npm_client.bin; - argv[1] = "exec"; + argv[1] = npm_task_args[0]; + { var i: usize = 2; + splitter = std.mem.split(u8, task, " "); while (splitter.next()) |split| { argv[i] = split; @@ -67,9 +82,10 @@ fn execTask(allocator: *std.mem.Allocator, task_: string, cwd: string, PATH: str } if (strings.startsWith(task, "bun ")) { + // TODO: use self exe if (bun_path orelse which(&bun_path_buf, PATH, cwd, "bun")) |bun_path_| { bun_path = bun_path_; - argv = argv[2..]; + argv = argv[npm_args..]; argv[0] = std.mem.span(bun_path_); } } @@ -98,6 +114,23 @@ fn execTask(allocator: *std.mem.Allocator, task_: string, cwd: string, PATH: str _ = proc.spawnAndWait() catch undefined; } +// We don't want to allocate memory each time +// But we cannot print over an existing buffer or weird stuff will happen +// so we keep two and switch between them +pub const ProgressBuf = struct { + var bufs: [2][1024]u8 = [2][1024]u8{ + @as([1024]u8, undefined), + @as([1024]u8, undefined), + }; + + var buf_index: usize = 0; + + pub fn print(comptime fmt: string, args: anytype) !string { + buf_index += 1; + return try std.fmt.bufPrint(std.mem.span(&bufs[buf_index % 2]), fmt, args); + } +}; + const CreateOptions = struct { npm_client: ?NPMClient.Tag = null, skip_install: bool = false, @@ -131,6 +164,7 @@ const CreateOptions = struct { } Output.prettyln("<r><b>bun create<r> flags:\n", .{}); + Output.flush(); clap.help(Output.writer(), params[1..]) catch {}; Output.flush(); std.os.exit(0); @@ -169,7 +203,7 @@ const BUN_CREATE_DIR = ".bun-create"; var home_dir_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; pub const CreateCommand = struct { var client: HTTPClient = undefined; - var extracting_name_buf: [1024]u8 = undefined; + pub fn exec(ctx: Command.Context, positionals: []const []const u8) !void { var create_options = try CreateOptions.parse(ctx.allocator, false); @@ -226,10 +260,8 @@ pub const CreateCommand = struct { const destination = try filesystem.dirname_store.append([]const u8, resolve_path.joinAbs(filesystem.top_level_dir, .auto, dirname)); var progress = std.Progress{}; - - var node_ = try progress.start(try std.fmt.bufPrint(&extracting_name_buf, "Loading {s}", .{template}), 0); + var node = try progress.start(try ProgressBuf.print("Loading {s}", .{template}), 0); progress.supports_ansi_escape_codes = Output.enable_ansi_colors; - var node = node_.start("Downloading", 0); // alacritty is fast if (env_loader.map.get("ALACRITTY_LOG") != null) { @@ -237,7 +269,6 @@ pub const CreateCommand = struct { } defer { - progress.root.end(); progress.refresh(); } @@ -247,14 +278,31 @@ pub const CreateCommand = struct { const is_remote_template = !std.fs.path.isAbsolute(template); if (is_remote_template) { - var tarball_bytes: MutableString = try Example.fetch(ctx, template, &progress, &node); + var tarball_bytes: MutableString = Example.fetch(ctx, template, &progress, node) catch |err| { + switch (err) { + error.HTTPForbidden, error.ExampleNotFound => { + node.end(); + progress.refresh(); - node.end(); + Output.prettyError("\n<r><red>error:<r> <b>\"{s}\"<r> was not found. Here are templates you can use:\n\n", .{ + template, + }); + Output.flush(); + const examples = try Example.fetchAllLocalAndRemote(ctx, &env_loader, filesystem); + Example.print(examples.items, dirname); + Output.flush(); + std.os.exit(1); + }, + else => { + return err; + }, + } + }; - node = progress.root.start(try std.fmt.bufPrint(&extracting_name_buf, "Decompressing {s}", .{template}), 0); + node.name = try ProgressBuf.print("Decompressing {s}", .{template}); node.setCompletedItems(0); node.setEstimatedTotalItems(0); - node.activate(); + progress.refresh(); var file_buf = try ctx.allocator.alloc(u8, 16384); @@ -264,12 +312,10 @@ pub const CreateCommand = struct { try gunzip.readAll(); gunzip.deinit(); - node.end(); - - node = progress.root.start(try std.fmt.bufPrint(&extracting_name_buf, "Extracting {s}", .{template}), 0); + node.name = try ProgressBuf.print("Extracting {s}", .{template}); node.setCompletedItems(0); node.setEstimatedTotalItems(0); - node.activate(); + progress.refresh(); var pluckers = [_]Archive.Plucker{ @@ -292,9 +338,12 @@ pub const CreateCommand = struct { 1, ); + inline for (never_conflict) |never_conflict_path| { + _ = archive_context.overwrite_list.swapRemove(never_conflict_path); + } + if (archive_context.overwrite_list.count() > 0) { node.end(); - progress.root.end(); progress.refresh(); // Thank you create-react-app for this copy (and idea) @@ -322,31 +371,32 @@ pub const CreateCommand = struct { &archive_context, 1, false, + false, ); var plucker = pluckers[0]; if (!plucker.found or plucker.fd == 0) { node.end(); - progress.root.end(); + Output.prettyErrorln("package.json not found. This package is corrupt. Please try again or file an issue if it keeps happening.", .{}); Output.flush(); std.os.exit(1); } - node.end(); - node = progress.root.start(try std.fmt.bufPrint(&extracting_name_buf, "Updating package.json", .{}), 0); - - node.activate(); + node.name = "Updating package.json"; progress.refresh(); package_json_contents = plucker.contents; package_json_file = std.fs.File{ .handle = plucker.fd }; } else { var template_parts = [_]string{template}; + + node.name = "Copying files"; + progress.refresh(); + const template_dir = std.fs.openDirAbsolute(filesystem.abs(&template_parts), .{ .iterate = true }) catch |err| { node.end(); - progress.root.end(); progress.refresh(); Output.prettyErrorln("<r><red>{s}<r>: opening dir {s}", .{ @errorName(err), template }); @@ -357,7 +407,7 @@ pub const CreateCommand = struct { std.fs.deleteTreeAbsolute(destination) catch {}; const destination_dir = std.fs.cwd().makeOpenPath(destination, .{ .iterate = true }) catch |err| { node.end(); - progress.root.end(); + progress.refresh(); Output.prettyErrorln("<r><red>{s}<r>: creating dir {s}", .{ @errorName(err), destination }); @@ -369,6 +419,8 @@ pub const CreateCommand = struct { 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..... @@ -379,7 +431,7 @@ pub const CreateCommand = struct { } break :brk destination_dir.createFile(entry.path, .{}) catch |err| { node.end(); - progress.root.end(); + progress.refresh(); Output.prettyErrorln("<r><red>{s}<r>: copying file {s}", .{ @errorName(err), entry.path }); @@ -388,13 +440,14 @@ pub const CreateCommand = struct { }; }; 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(); - progress.root.end(); + progress.refresh(); Output.prettyErrorln("<r><red>{s}<r>: copying file {s}", .{ @errorName(err), entry.path }); @@ -408,7 +461,7 @@ pub const CreateCommand = struct { package_json_file = destination_dir.openFile("package.json", .{ .read = true, .write = true }) catch |err| { node.end(); - progress.root.end(); + progress.refresh(); Output.prettyErrorln("Failed to open package.json due to error <r><red>{s}", .{@errorName(err)}); @@ -417,7 +470,7 @@ pub const CreateCommand = struct { }; const stat = package_json_file.stat() catch |err| { node.end(); - progress.root.end(); + progress.refresh(); Output.prettyErrorln("Failed to stat package.json due to error <r><red>{s}", .{@errorName(err)}); @@ -427,7 +480,7 @@ pub const CreateCommand = struct { if (stat.kind != .File or stat.size == 0) { node.end(); - progress.root.end(); + progress.refresh(); Output.prettyErrorln("package.json must be a file with content", .{}); @@ -439,7 +492,7 @@ pub const CreateCommand = struct { _ = package_json_file.preadAll(package_json_contents.list.items, 0) catch |err| { node.end(); - progress.root.end(); + progress.refresh(); Output.prettyErrorln("Error reading package.json: <r><red>{s}", .{@errorName(err)}); @@ -453,22 +506,17 @@ pub const CreateCommand = struct { js_ast.Stmt.Data.Store.create(default_allocator); } + node.end(); + progress.refresh(); + var source = logger.Source.initPathString("package.json", package_json_contents.list.items); var package_json_expr = ParseJSON(&source, ctx.log, ctx.allocator) catch |err| { - node.end(); - progress.root.end(); - progress.refresh(); - Output.prettyErrorln("package.json failed to parse with error: {s}", .{@errorName(err)}); Output.flush(); std.os.exit(1); }; if (ctx.log.errors > 0) { - node.end(); - - progress.refresh(); - if (Output.enable_ansi_colors) { try ctx.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), true); } else { @@ -481,11 +529,6 @@ pub const CreateCommand = struct { if (package_json_expr.asProperty("name")) |name_expr| { if (name_expr.expr.data != .e_string) { - node.end(); - progress.root.end(); - - progress.refresh(); - Output.prettyErrorln("package.json failed to parse correctly. its missing a name. it shouldnt be missing a name.", .{}); Output.flush(); std.os.exit(1); @@ -494,11 +537,6 @@ pub const CreateCommand = struct { var basename = std.fs.path.basename(destination); name_expr.expr.data.e_string.utf8 = @intToPtr([*]u8, @ptrToInt(basename.ptr))[0..basename.len]; } else { - node.end(); - progress.root.end(); - - progress.refresh(); - Output.prettyErrorln("package.json failed to parse correctly. its missing a name. it shouldnt be missing a name.", .{}); Output.flush(); std.os.exit(1); @@ -569,9 +607,6 @@ pub const CreateCommand = struct { } } - node.name = "Saving package.json"; - progress.maybeRefresh(); - var package_json_writer = JSPrinter.NewFileWriter(package_json_file); _ = JSPrinter.printJSON(@TypeOf(package_json_writer), package_json_writer, package_json_expr, &source) catch |err| { @@ -591,10 +626,10 @@ pub const CreateCommand = struct { var realpath_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; if (create_options.npm_client) |tag| { - if (which(&realpath_buf, PATH, filesystem.top_level_dir, @tagName(tag))) |bin| { + if (which(&realpath_buf, PATH, destination, @tagName(tag))) |bin| { npm_client_ = NPMClient{ .tag = tag, .bin = try ctx.allocator.dupe(u8, bin) }; } - } else if (try NPMClient.detect(ctx.allocator, &realpath_buf, PATH, filesystem.top_level_dir, true)) |npmclient| { + } else if (try NPMClient.detect(ctx.allocator, &realpath_buf, PATH, destination, true)) |npmclient| { npm_client_ = NPMClient{ .bin = try ctx.allocator.dupe(u8, npmclient.bin), .tag = npmclient.tag, @@ -604,31 +639,35 @@ pub const CreateCommand = struct { } if (npm_client_ != null and preinstall_tasks.items.len > 0) { - node.end(); - node = progress.root.start("Running pre-install tasks", preinstall_tasks.items.len); - node.setCompletedItems(0); - progress.refresh(); - for (preinstall_tasks.items) |task, i| { execTask(ctx.allocator, task, destination, PATH, npm_client_.?); - - node.setCompletedItems(i); - progress.refresh(); } } - node.end(); - if (npm_client_) |npm_client| { const start_time = std.time.nanoTimestamp(); - var install_args = [_]string{ npm_client.bin, "install" }; - Output.printError("\n", .{}); + const install_args_ = [_]string{ npm_client.bin, "install", "--loglevel=error", "--no-fund", "--no-audit" }; + const len: usize = switch (npm_client.tag) { + .npm => install_args_.len, + else => 2, + }; + + const install_args = install_args_[0..len]; Output.flush(); + Output.pretty("\n<r><d>$ <b><cyan>{s}<r><d> install", .{@tagName(npm_client.tag)}); + var writer = Output.writer(); - Output.prettyln("\n<r><d>$ <b><cyan>{s}<r><d> install<r>", .{@tagName(npm_client.tag)}); + if (install_args.len > 2) { + for (install_args[2..]) |arg| { + Output.pretty(" ", .{}); + Output.pretty("{s}", .{arg}); + } + } + + Output.pretty("<r>\n", .{}); Output.flush(); - var process = try std.ChildProcess.init(&install_args, ctx.allocator); + var process = try std.ChildProcess.init(install_args, ctx.allocator); process.cwd = destination; defer { @@ -649,30 +688,22 @@ pub const CreateCommand = struct { progress.log("Failed to detect npm client. Tried pnpm, yarn, and npm.\n", .{}); } - progress.refresh(); - if (npm_client_ != null and !create_options.skip_install and postinstall_tasks.items.len > 0) { - node.end(); - node = progress.root.start("Running post-install tasks", postinstall_tasks.items.len); - node.setCompletedItems(0); - progress.refresh(); - for (postinstall_tasks.items) |task, i| { execTask(ctx.allocator, task, destination, PATH, npm_client_.?); - - node.setCompletedItems(i); - progress.refresh(); } } - 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(); + { + 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| { @@ -702,6 +733,48 @@ pub const CreateCommand = struct { Output.printStartEnd(ctx.start_time, std.time.nanoTimestamp()); Output.prettyErrorln(" <r><d>bun create {s}<r>", .{template}); Output.flush(); + + Output.pretty( + \\ + \\<r><d>-----<r> + \\ + , .{}); + + if (!create_options.skip_git and !create_options.skip_install) { + Output.pretty( + \\ + \\<d>A local git repository was created for you and dependencies were installed automatically.<r> + \\ + , .{}); + } else if (!create_options.skip_git) { + Output.pretty( + \\ + \\<d>A local git repository was created for you.<r> + \\ + , .{}); + } else if (!create_options.skip_install) { + Output.pretty( + \\ + \\<d>Dependencies were installed automatically.<r> + \\ + , .{}); + } + + Output.pretty( + \\ + \\<b>Created <green>{s}<r> project successfully + \\ + \\<d>#<r><b> To get started, run:<r> + \\ + \\ <b><cyan>cd {s}<r> + \\ <b><cyan>bun<r> + \\ + , .{ + std.fs.path.basename(template), + filesystem.relativeTo(destination), + }); + + Output.flush(); } }; const Commands = .{ @@ -760,18 +833,19 @@ pub const Example = struct { var url: URL = undefined; pub const timeout: u32 = 6000; - pub fn print(examples: []const Example) void { + var app_name_buf: [512]u8 = undefined; + pub fn print(examples: []const Example, default_app_name: ?string) void { for (examples) |example, i| { - var app_name = example.name; + var app_name = default_app_name orelse (std.fmt.bufPrint(&app_name_buf, "./{s}-app", .{example.name[0..std.math.min(example.name.len, 492)]}) catch unreachable); if (example.description.len > 0) { - Output.pretty(" <r># {s}<r>\n <b>bun create <cyan>{s}<r><b> ./{s}-app<r>\n<d> \n\n", .{ + Output.pretty(" <r># {s}<r>\n <b>bun create <cyan>{s}<r><b> {s}<r>\n<d> \n\n", .{ example.description, example.name, app_name, }); } else { - Output.pretty(" <r><b>bun create <cyan>{s}<r><b> ./{s}-app<r>\n\n", .{ + Output.pretty(" <r><b>bun create <cyan>{s}<r><b> {s}<r>\n\n", .{ example.name, app_name, }); @@ -779,31 +853,76 @@ pub const Example = struct { } } - pub fn fetchFromDisk(ctx: Command.Context, absolute_path: string, refresher: *std.Progress, progress: *std.Progress.Node) !MutableString { - progress.name = "Reading local package"; - refresher.refresh(); + pub fn fetchAllLocalAndRemote(ctx: Command.Context, env_loader: *DotEnv.Loader, filesystem: *fs.FileSystem) !std.ArrayList(Example) { + const remote_examples = try Example.fetchAll(ctx); - var package = try std.fs.openFileAbsolute(absolute_path, .{ .read = true }); - var stat = try package.stat(); - if (stat.kind != .File) { - progress.end(); - Output.prettyErrorln("<r>{s} is not a file", .{absolute_path}); - Output.flush(); - std.os.exit(1); - } + var examples = std.ArrayList(Example).fromOwnedSlice(ctx.allocator, remote_examples); + { + var folders = [3]std.fs.Dir{ std.fs.Dir{ .fd = 0 }, std.fs.Dir{ .fd = 0 }, std.fs.Dir{ .fd = 0 } }; + if (env_loader.map.get("BUN_CREATE_DIR")) |home_dir| { + var parts = [_]string{home_dir}; + var outdir_path = filesystem.absBuf(&parts, &home_dir_buf); + folders[0] = std.fs.openDirAbsolute(outdir_path, .{ .iterate = true }) catch std.fs.Dir{ .fd = 0 }; + } - if (stat.size == 0) { - progress.end(); - Output.prettyErrorln("<r>{s} is an empty file", .{absolute_path}); - Output.flush(); - std.os.exit(1); + { + var parts = [_]string{ filesystem.top_level_dir, BUN_CREATE_DIR }; + var outdir_path = filesystem.absBuf(&parts, &home_dir_buf); + folders[1] = std.fs.openDirAbsolute(outdir_path, .{ .iterate = true }) catch std.fs.Dir{ .fd = 0 }; + } + + if (env_loader.map.get("HOME")) |home_dir| { + var parts = [_]string{ home_dir, BUN_CREATE_DIR }; + var outdir_path = filesystem.absBuf(&parts, &home_dir_buf); + folders[2] = std.fs.openDirAbsolute(outdir_path, .{ .iterate = true }) catch std.fs.Dir{ .fd = 0 }; + } + + // subfolders with package.json + for (folders) |folder_| { + if (folder_.fd != 0) { + const folder: std.fs.Dir = folder_; + var iter = folder.iterate(); + + loop: while (iter.next() catch null) |entry_| { + const entry: std.fs.Dir.Entry = entry_; + + switch (entry.kind) { + .Directory => { + inline for (skip_dirs) |skip_dir| { + if (strings.eqlComptime(entry.name, skip_dir)) { + continue :loop; + } + } + + std.mem.copy(u8, &home_dir_buf, entry.name); + home_dir_buf[entry.name.len] = std.fs.path.sep; + std.mem.copy(u8, home_dir_buf[entry.name.len + 1 ..], "package.json"); + home_dir_buf[entry.name.len + 1 + "package.json".len] = 0; + + var path: [:0]u8 = home_dir_buf[0 .. entry.name.len + 1 + "package.json".len :0]; + + folder.accessZ(path, .{ + .read = true, + }) catch continue :loop; + + try examples.append( + Example{ + .name = try filesystem.filename_store.append(@TypeOf(entry.name), entry.name), + .version = "", + .local = true, + .description = "", + }, + ); + continue :loop; + }, + else => continue, + } + } + } + } } - var mutable_string = try MutableString.init(ctx.allocator, stat.size); - mutable_string.list.expandToCapacity(); - var bytes = try package.readAll(mutable_string.list.items); - try mutable_string.inflate(bytes); - return mutable_string; + return examples; } pub fn fetch(ctx: Command.Context, name: string, refresher: *std.Progress, progress: *std.Progress.Node) !MutableString { @@ -1020,79 +1139,13 @@ pub const CreateListExamplesCommand = struct { env_loader.loadProcess(); const time = std.time.nanoTimestamp(); - const remote_examples = try Example.fetchAll(ctx); - - var examples = std.ArrayList(Example).fromOwnedSlice(ctx.allocator, remote_examples); - { - var folders = [3]std.fs.Dir{ std.fs.Dir{ .fd = 0 }, std.fs.Dir{ .fd = 0 }, std.fs.Dir{ .fd = 0 } }; - if (env_loader.map.get("BUN_CREATE_DIR")) |home_dir| { - var parts = [_]string{home_dir}; - var outdir_path = filesystem.absBuf(&parts, &home_dir_buf); - folders[0] = std.fs.openDirAbsolute(outdir_path, .{ .iterate = true }) catch std.fs.Dir{ .fd = 0 }; - } - - { - var parts = [_]string{ filesystem.top_level_dir, BUN_CREATE_DIR }; - var outdir_path = filesystem.absBuf(&parts, &home_dir_buf); - folders[1] = std.fs.openDirAbsolute(outdir_path, .{ .iterate = true }) catch std.fs.Dir{ .fd = 0 }; - } - - if (env_loader.map.get("HOME")) |home_dir| { - var parts = [_]string{ home_dir, BUN_CREATE_DIR }; - var outdir_path = filesystem.absBuf(&parts, &home_dir_buf); - folders[2] = std.fs.openDirAbsolute(outdir_path, .{ .iterate = true }) catch std.fs.Dir{ .fd = 0 }; - } - - // subfolders with package.json - for (folders) |folder_| { - if (folder_.fd != 0) { - const folder: std.fs.Dir = folder_; - var iter = folder.iterate(); - - loop: while (iter.next() catch null) |entry_| { - const entry: std.fs.Dir.Entry = entry_; - - switch (entry.kind) { - .Directory => { - inline for (skip_dirs) |skip_dir| { - if (strings.eqlComptime(entry.name, skip_dir)) { - continue :loop; - } - } - - std.mem.copy(u8, &home_dir_buf, entry.name); - home_dir_buf[entry.name.len] = std.fs.path.sep; - std.mem.copy(u8, home_dir_buf[entry.name.len + 1 ..], "package.json"); - home_dir_buf[entry.name.len + 1 + "package.json".len] = 0; - - var path: [:0]u8 = home_dir_buf[0 .. entry.name.len + 1 + "package.json".len :0]; - - folder.accessZ(path, .{ - .read = true, - }) catch continue :loop; - - try examples.append( - Example{ - .name = try filesystem.filename_store.append(@TypeOf(entry.name), entry.name), - .version = "", - .local = true, - .description = "", - }, - ); - continue :loop; - }, - else => continue, - } - } - } - } - } + const examples = try Example.fetchAllLocalAndRemote(ctx, &env_loader, filesystem); Output.printStartEnd(time, std.time.nanoTimestamp()); Output.prettyln(" <d>Fetched examples<r>", .{}); Output.prettyln("Welcome to Bun! Create a new project by pasting any of the following:\n\n", .{}); Output.flush(); - Example.print(examples.items); + Example.print(examples.items, null); if (env_loader.map.get("HOME")) |homedir| { Output.prettyln( diff --git a/src/libarchive/libarchive.zig b/src/libarchive/libarchive.zig index ebce0cba4..117f2809f 100644 --- a/src/libarchive/libarchive.zig +++ b/src/libarchive/libarchive.zig @@ -460,6 +460,9 @@ pub const Archive = struct { Status.eof => break :loop, Status.failed, Status.fatal, Status.retry => return error.Fail, else => { + // do not use the utf8 name there + // it will require us to pull in libiconv + // though we should probably validate the utf8 here nonetheless var pathname: [:0]const u8 = std.mem.sliceTo(lib.archive_entry_pathname(entry).?, 0); var tokenizer = std.mem.tokenize(u8, std.mem.span(pathname), std.fs.path.sep_str); comptime var depth_i: usize = 0; @@ -511,6 +514,7 @@ pub const Archive = struct { ctx: ?*Archive.Context, comptime depth_to_skip: usize, comptime close_handles: bool, + comptime log: bool, ) !u32 { var entry: *lib.archive_entry = undefined; var ext: *lib.archive = undefined; @@ -559,7 +563,9 @@ pub const Archive = struct { const mask = lib.archive_entry_filetype(entry); const size = @intCast(usize, std.math.max(lib.archive_entry_size(entry), 0)); if (size > 0) { - Output.prettyln(" {s}", .{pathname}); + if (comptime log) { + Output.prettyln(" {s}", .{pathname}); + } const file = dir.createFileZ(pathname, .{ .truncate = true }) catch |err| brk: { switch (err) { diff --git a/src/main.zig b/src/main.zig index 7b86f7360..1635f5ad1 100644 --- a/src/main.zig +++ b/src/main.zig @@ -37,7 +37,19 @@ pub fn main() anyerror!void { Output.Source.set(&output_source); defer Output.flush(); - try cli.Cli.start(default_allocator, stdout, stderr, MainPanicHandler); + cli.Cli.start(default_allocator, stdout, stderr, MainPanicHandler) catch |err| { + switch (err) { + error.CurrentWorkingDirectoryUnlinked => { + Output.prettyError( + "\n<r><red>error: <r>The current working directory was deleted, so that command didn't work. Please cd into a different directory and try again.", + .{}, + ); + Output.flush(); + std.os.exit(1); + }, + else => return err, + } + }; std.mem.doNotOptimizeAway(JavaScriptVirtualMachine.fetch); std.mem.doNotOptimizeAway(JavaScriptVirtualMachine.init); diff --git a/src/which_npm_client.zig b/src/which_npm_client.zig index 86683a368..25e5b2ea1 100644 --- a/src/which_npm_client.zig +++ b/src/which_npm_client.zig @@ -13,25 +13,20 @@ pub const NPMClient = struct { pnpm, }; - pub fn isYarnBerry(allocator: *std.mem.Allocator, yarn_path: string) bool { + // This check adds around 150ms + // so...if we do do this, we should do it in a separate thread + pub fn isYarnBerry(allocator: *std.mem.Allocator, cwd_dir: string, yarn_path: string) bool { var args = [_]string{ yarn_path, "--version" }; - var child_process = std.ChildProcess.init(&args, allocator) catch return true; - defer child_process.deinit(); - child_process.cwd_dir = std.fs.cwd(); - child_process.expand_arg0 = .no_expand; - child_process.stdout_behavior = .Pipe; - child_process.stderr_behavior = .Pipe; - child_process.spawn() catch return true; - defer _ = child_process.kill() catch undefined; - - var path_buf: [512]u8 = undefined; - var path_len = child_process.stdout.?.read(&path_buf) catch return true; - - if (path_len == 0) { - return true; - } - - return path_buf[0] != '1'; + var term = std.ChildProcess.exec(.{ + .argv = &args, + .allocator = allocator, + .cwd = if (cwd_dir.len > 1) std.mem.trimRight(u8, cwd_dir, "/") else cwd_dir, + }) catch return true; + defer allocator.free(term.stderr); + defer allocator.free(term.stdout); + + if (term.stdout.len == 0) return true; + return term.stdout[0] != '1'; } pub fn detect(allocator: *std.mem.Allocator, realpath_buf: *[std.fs.MAX_PATH_BYTES]u8, PATH: string, cwd: string, comptime allow_yarn: bool) !?NPMClient { @@ -40,11 +35,12 @@ pub const NPMClient = struct { // - pnpm if it exists, is the default. its most esoteric, so if you have it installed, you prob want it. // - yarn if it exists and it is yarn 1, its the default (yarn 2 or later is not supported) // - else npm - var path_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; - const path: [:0]const u8 = brk: { - if (comptime allow_yarn) { - break :brk which( + const out_path = brk: { + var path_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; + + const path: [:0]const u8 = if (comptime allow_yarn) + which( &path_buf, PATH, cwd, @@ -59,9 +55,9 @@ pub const NPMClient = struct { PATH, cwd, "npm", - ) orelse ""; - } else { - break :brk which( + ) orelse "" + else + which( &path_buf, PATH, cwd, @@ -72,24 +68,22 @@ pub const NPMClient = struct { cwd, "npm", ) orelse ""; - } - unreachable; + + var file = std.fs.openFileAbsoluteZ(path, .{ .read = true }) catch return null; + defer file.close(); + break :brk std.os.getFdPath(file.handle, realpath_buf) catch return null; }; - var basename = std.fs.path.basename(path); + const basename = std.fs.path.basename(std.mem.span(out_path)); if (basename.len == 0) return null; - if (comptime allow_yarn) { - if (std.mem.indexOf(u8, basename, "yarn") != null) { - if (isYarnBerry(allocator, path)) { - return try detect(allocator, realpath_buf, PATH, cwd, false); - } - } - } - - var file = std.fs.openFileAbsoluteZ(path, .{ .read = true }) catch return null; - defer file.close(); - const out_path = std.os.getFdPath(file.handle, realpath_buf) catch return null; + // if (comptime allow_yarn) { + // if (std.mem.indexOf(u8, basename, "yarn") != null) { + // if (isYarnBerry(allocator, cwd, out_path)) { + // return try detect(allocator, realpath_buf, PATH, cwd, false); + // } + // } + // } if (strings.contains(basename, "pnpm")) { return NPMClient{ .bin = out_path, .tag = .pnpm }; |