diff options
author | 2021-10-14 18:55:41 -0700 | |
---|---|---|
committer | 2021-10-14 18:55:41 -0700 | |
commit | bbc1bcbed125e4aeacac0c374f717f65adb838ea (patch) | |
tree | a3ae72a500afc507231d3f97c7d0762c76614a51 /src/cli/create_command.zig | |
parent | 3ed824fe0fc14d21a5c035d84891b8ecf28e3c44 (diff) | |
download | bun-bbc1bcbed125e4aeacac0c374f717f65adb838ea.tar.gz bun-bbc1bcbed125e4aeacac0c374f717f65adb838ea.tar.zst bun-bbc1bcbed125e4aeacac0c374f717f65adb838ea.zip |
Support local templates
Diffstat (limited to 'src/cli/create_command.zig')
-rw-r--r-- | src/cli/create_command.zig | 193 |
1 files changed, 176 insertions, 17 deletions
diff --git a/src/cli/create_command.zig b/src/cli/create_command.zig index d501e0473..d2a780430 100644 --- a/src/cli/create_command.zig +++ b/src/cli/create_command.zig @@ -31,7 +31,16 @@ const NPMClient = @import("../which_npm_client.zig").NPMClient; const which = @import("../which.zig").which; const clap = @import("clap"); +const CopyFile = @import("../copy_file.zig"); var bun_path_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; + +const skip_dirs = &[_]string{ "node_modules", ".git" }; +const skip_files = &[_]string{ + "package-lock.json", + "yarn.lock", + "pnpm-lock.yaml", +}; + 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"); @@ -156,6 +165,8 @@ const CreateOptions = struct { } }; +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; @@ -172,7 +183,44 @@ pub const CreateCommand = struct { env_loader.loadProcess(); - const template = positionals[0]; + const template = brk: { + var positional = positionals[0]; + + if (!std.fs.path.isAbsolute(positional)) { + outer: { + if (env_loader.map.get("BUN_CREATE_DIR")) |home_dir| { + var parts = [_]string{ home_dir, positional }; + var outdir_path = filesystem.absBuf(&parts, &home_dir_buf); + 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; + break :brk outdir_path; + } + } + + outer: { + var parts = [_]string{ filesystem.top_level_dir, BUN_CREATE_DIR, positional }; + var outdir_path = filesystem.absBuf(&parts, &home_dir_buf); + 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; + break :brk outdir_path; + } + + outer: { + if (env_loader.map.get("HOME")) |home_dir| { + var parts = [_]string{ home_dir, BUN_CREATE_DIR, positional }; + var outdir_path = filesystem.absBuf(&parts, &home_dir_buf); + 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; + break :brk outdir_path; + } + } + } + + break :brk positional; + }; const dirname = positionals[1]; var filename_writer = filesystem.dirname_store; const destination = try filesystem.dirname_store.append([]const u8, resolve_path.joinAbs(filesystem.top_level_dir, .auto, dirname)); @@ -196,7 +244,9 @@ pub const CreateCommand = struct { var package_json_contents: MutableString = undefined; var package_json_file: std.fs.File = undefined; - if (!std.fs.path.isAbsolute(template)) { + const is_remote_template = !std.fs.path.isAbsolute(template); + + if (is_remote_template) { var tarball_bytes: MutableString = try Example.fetch(ctx, template, &progress, &node); node.end(); @@ -293,7 +343,8 @@ pub const CreateCommand = struct { package_json_contents = plucker.contents; package_json_file = std.fs.File{ .handle = plucker.fd }; } else { - const template_dir = std.fs.openDirAbsolute(template, .{ .iterate = true }) catch |err| { + var template_parts = [_]string{template}; + const template_dir = std.fs.openDirAbsolute(filesystem.abs(&template_parts), .{ .iterate = true }) catch |err| { node.end(); progress.root.end(); progress.refresh(); @@ -314,19 +365,33 @@ pub const CreateCommand = struct { std.os.exit(1); }; - var walker = try template_dir.walk(ctx.allocator); + const Walker = @import("../walker_skippable.zig"); + var walker = try Walker.walk(template_dir, ctx.allocator, skip_files, skip_dirs); defer walker.deinit(); + while (try walker.next()) |entry| { // TODO: make this not walk these folders entirely // rather than checking each file path..... - if (entry.kind != .File or - std.mem.indexOf(u8, entry.path, "node_modules") != null or - std.mem.indexOf(u8, entry.path, ".git") != null) continue; - - entry.dir.copyFile(entry.basename, destination_dir, entry.path, .{}) catch { + 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.root.end(); + progress.refresh(); + + Output.prettyErrorln("<r><red>{s}<r>: copying file {s}", .{ @errorName(err), entry.path }); + Output.flush(); + std.os.exit(1); + }; + }; + defer outfile.close(); + + 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(); @@ -337,6 +402,8 @@ pub const CreateCommand = struct { std.os.exit(1); }; }; + var stat = outfile.stat() catch continue; + _ = C.fchmod(outfile.handle, stat.mode); } package_json_file = destination_dir.openFile("package.json", .{ .read = true, .write = true }) catch |err| { @@ -368,7 +435,9 @@ pub const CreateCommand = struct { std.os.exit(1); } package_json_contents = try MutableString.init(ctx.allocator, stat.size); - package_json_contents.inflate(package_json_file.readAll(package_json_contents.list.items) catch |err| { + package_json_contents.list.expandToCapacity(); + + _ = package_json_file.preadAll(package_json_contents.list.items, 0) catch |err| { node.end(); progress.root.end(); progress.refresh(); @@ -376,7 +445,12 @@ pub const CreateCommand = struct { Output.prettyErrorln("Error reading package.json: <r><red>{s}", .{@errorName(err)}); Output.flush(); std.os.exit(1); - }) catch unreachable; + }; + // The printer doesn't truncate, so we must do so manually + std.os.ftruncate(package_json_file.handle, 0) catch {}; + + js_ast.Expr.Data.Store.create(default_allocator); + js_ast.Stmt.Data.Store.create(default_allocator); } var source = logger.Source.initPathString("package.json", package_json_contents.list.items); @@ -679,6 +753,7 @@ pub const Example = struct { name: string, version: string, description: string, + local: bool = false, var client: HTTPClient = undefined; const examples_url: string = "https://registry.npmjs.org/bun-examples-all/latest"; @@ -848,7 +923,7 @@ pub const Example = struct { return thread.buffer; } - pub fn fetchAll(ctx: Command.Context) ![]const Example { + pub fn fetchAll(ctx: Command.Context) ![]Example { url = URL.parse(examples_url); client = HTTPClient.init(ctx.allocator, .GET, url, .{}, ""); client.timeout = timeout; @@ -934,19 +1009,103 @@ pub const Example = struct { pub const CreateListExamplesCommand = struct { pub fn exec(ctx: Command.Context) !void { + var filesystem = try fs.FileSystem.init1(ctx.allocator, null); + var env_loader: DotEnv.Loader = brk: { + var map = try ctx.allocator.create(DotEnv.Map); + map.* = DotEnv.Map.init(ctx.allocator); + + break :brk DotEnv.Loader.init(map, ctx.allocator); + }; + + env_loader.loadProcess(); + const time = std.time.nanoTimestamp(); - const examples = try Example.fetchAll(ctx); + 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, + } + } + } + } + } 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); + Example.print(examples.items); - _ = try CreateOptions.parse(ctx.allocator, true); + if (env_loader.map.get("HOME")) |homedir| { + Output.prettyln( + "<d>This command is completely optional. To add a new local template, create a folder in {s}/.bun-create/. To publish a new template, git clone https://github.com/jarred-sumner/bun, add a new folder to the \"examples\" folder, and submit a PR.<r>", + .{homedir}, + ); + } else { + Output.prettyln( + "<d>This command is completely optional. To add a new local template, create a folder in $HOME/.bun-create/. To publish a new template, git clone https://github.com/jarred-sumner/bun, add a new folder to the \"examples\" folder, and submit a PR.<r>", + .{}, + ); + } - Output.pretty("<d>To add a new template, git clone https://github.com/jarred-sumner/bun, add a new folder to the \"examples\" folder, and submit a PR.<r>", .{}); Output.flush(); } }; |