From 58ce044607ec30ba43d7602cbe994bec80d6395d Mon Sep 17 00:00:00 2001 From: Pierre CM <114198641+pierre-cm@users.noreply.github.com> Date: Thu, 26 Oct 2023 01:54:36 +0200 Subject: [PATCH] fix cli create from local template (#6670) * fix #4766 * fix cli create command for local folders * zig fmt * rm comment --- docs/cli/bun-create.md | 2 +- src/cli.zig | 11 +- src/cli/create_command.zig | 203 ++++++++++++++-------------- test/cli/install/bun-create.test.ts | 24 +++- 4 files changed, 135 insertions(+), 105 deletions(-) diff --git a/docs/cli/bun-create.md b/docs/cli/bun-create.md index c5cc602b62..2e436cd6f5 100644 --- a/docs/cli/bun-create.md +++ b/docs/cli/bun-create.md @@ -207,7 +207,7 @@ After cloning a template, `bun create` will automatically remove the `"bun-creat --- -- `GITHUB_API_TOKEN` +- `GITHUB_ACCESS_TOKEN` - This lets `bun create` work with private repositories or if you get rate-limited {% /table %} diff --git a/src/cli.zig b/src/cli.zig index a05fb37683..24e142eb9e 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -1220,6 +1220,7 @@ pub const Command = struct { const AddCommand = @import("./cli/add_command.zig").AddCommand; const CreateCommand = @import("./cli/create_command.zig").CreateCommand; + const CreateCommandExample = @import("./cli/create_command.zig").Example; const CreateListExamplesCommand = @import("./cli/create_command.zig").CreateListExamplesCommand; const DiscordCommand = @import("./cli/discord_command.zig").DiscordCommand; const InstallCommand = @import("./cli/install_command.zig").InstallCommand; @@ -1512,7 +1513,6 @@ pub const Command = struct { } } } - var positionals_ = positionals[0..positional_i]; const template_name = positionals[0]; @@ -1538,9 +1538,14 @@ pub const Command = struct { return; } + const create_command_info = try CreateCommand.extractInfo(ctx); + const template = create_command_info.template; + var example_tag = create_command_info.example_tag; + const use_bunx = !HardcodedNonBunXList.has(template_name) and (!strings.containsComptime(template_name, "/") or - strings.startsWithChar(template_name, '@')); + strings.startsWithChar(template_name, '@')) and + example_tag != CreateCommandExample.Tag.local_folder; if (use_bunx) { const bunx_args = try allocator.alloc([*:0]const u8, args.len - template_name_start); @@ -1553,7 +1558,7 @@ pub const Command = struct { return; } - try CreateCommand.exec(ctx, positionals_); + try CreateCommand.exec(ctx, example_tag, template); return; }, .RunCommand => { diff --git a/src/cli/create_command.zig b/src/cli/create_command.zig index b11a85b1e1..f95d8efff1 100644 --- a/src/cli/create_command.zig +++ b/src/cli/create_command.zig @@ -247,7 +247,7 @@ const CreateOptions = struct { const BUN_CREATE_DIR = ".bun-create"; var home_dir_buf: [bun.MAX_PATH_BYTES]u8 = undefined; pub const CreateCommand = struct { - pub fn exec(ctx: Command.Context, _: []const []const u8) !void { + pub fn exec(ctx: Command.Context, example_tag: Example.Tag, template: []const u8) !void { @setCold(true); Global.configureAllocator(.{ .long_running = false }); @@ -270,104 +270,6 @@ pub const CreateCommand = struct { env_loader.loadProcess(); - var example_tag = Example.Tag.unknown; - - // var unsupported_packages = UnsupportedPackages{}; - 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; - if (create_options.verbose) { - Output.prettyErrorln("reading from {s}", .{outdir_path}); - } - example_tag = Example.Tag.local_folder; - 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; - if (create_options.verbose) { - Output.prettyErrorln("reading from {s}", .{outdir_path}); - } - example_tag = Example.Tag.local_folder; - 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; - if (create_options.verbose) { - Output.prettyErrorln("reading from {s}", .{outdir_path}); - } - example_tag = Example.Tag.local_folder; - break :brk outdir_path; - } - } - - if (std.fs.path.isAbsolute(positional)) { - example_tag = Example.Tag.local_folder; - break :brk positional; - } - - var repo_begin: usize = std.math.maxInt(usize); - // "https://github.com/foo/bar" - if (strings.startsWith(positional, "github.com/")) { - repo_begin = "github.com/".len; - } - - if (strings.startsWith(positional, "https://github.com/")) { - repo_begin = "https://github.com/".len; - } - - if (repo_begin == std.math.maxInt(usize) and positional[0] != '/') { - if (std.mem.indexOfScalar(u8, positional, '/')) |first_slash_index| { - if (std.mem.indexOfScalar(u8, positional, '/')) |last_slash_index| { - if (first_slash_index == last_slash_index and - positional[last_slash_index..].len > 0 and - last_slash_index > 0) - { - repo_begin = 0; - } - } - } - } - - if (repo_begin != std.math.maxInt(usize)) { - const remainder = positional[repo_begin..]; - if (std.mem.indexOfScalar(u8, remainder, '/')) |i| { - if (i > 0 and remainder[i + 1 ..].len > 0) { - if (std.mem.indexOfScalar(u8, remainder[i + 1 ..], '/')) |last_slash| { - example_tag = Example.Tag.github_repository; - break :brk std.mem.trim(u8, remainder[0 .. i + 1 + last_slash], "# \r\t"); - } else { - example_tag = Example.Tag.github_repository; - break :brk std.mem.trim(u8, remainder, "# \r\t"); - } - } - } - } - } - - example_tag = Example.Tag.official; - break :brk positional; - }; - const dirname: string = brk: { if (positionals.len == 1) { break :brk std.fs.path.basename(template); @@ -1673,6 +1575,109 @@ pub const CreateCommand = struct { } } } + pub fn extractInfo(ctx: Command.Context) !struct { example_tag: Example.Tag, template: []const u8 } { + var example_tag = Example.Tag.unknown; + var filesystem = try fs.FileSystem.init(null); + + var create_options = try CreateOptions.parse(ctx, false); + const positionals = create_options.positionals; + + 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(); + + // var unsupported_packages = UnsupportedPackages{}; + 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; + example_tag = Example.Tag.local_folder; + 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; + example_tag = Example.Tag.local_folder; + 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; + example_tag = Example.Tag.local_folder; + break :brk outdir_path; + } + } + + if (std.fs.path.isAbsolute(positional)) { + example_tag = Example.Tag.local_folder; + break :brk positional; + } + + var repo_begin: usize = std.math.maxInt(usize); + // "https://github.com/foo/bar" + if (strings.startsWith(positional, "github.com/")) { + repo_begin = "github.com/".len; + } + + if (strings.startsWith(positional, "https://github.com/")) { + repo_begin = "https://github.com/".len; + } + + if (repo_begin == std.math.maxInt(usize) and positional[0] != '/') { + if (std.mem.indexOfScalar(u8, positional, '/')) |first_slash_index| { + if (std.mem.indexOfScalar(u8, positional, '/')) |last_slash_index| { + if (first_slash_index == last_slash_index and + positional[last_slash_index..].len > 0 and + last_slash_index > 0) + { + repo_begin = 0; + } + } + } + } + + if (repo_begin != std.math.maxInt(usize)) { + const remainder = positional[repo_begin..]; + if (std.mem.indexOfScalar(u8, remainder, '/')) |i| { + if (i > 0 and remainder[i + 1 ..].len > 0) { + if (std.mem.indexOfScalar(u8, remainder[i + 1 ..], '/')) |last_slash| { + example_tag = Example.Tag.github_repository; + break :brk std.mem.trim(u8, remainder[0 .. i + 1 + last_slash], "# \r\t"); + } else { + example_tag = Example.Tag.github_repository; + break :brk std.mem.trim(u8, remainder, "# \r\t"); + } + } + } + } + } + example_tag = Example.Tag.official; + break :brk positional; + }; + return .{ .example_tag = example_tag, .template = template }; + } }; const Commands = .{ &[_]string{""}, diff --git a/test/cli/install/bun-create.test.ts b/test/cli/install/bun-create.test.ts index af8d16d1df..95540717da 100644 --- a/test/cli/install/bun-create.test.ts +++ b/test/cli/install/bun-create.test.ts @@ -1,7 +1,7 @@ import { spawn } from "bun"; import { afterEach, beforeEach, expect, it } from "bun:test"; import { bunExe, bunEnv as env } from "harness"; -import { mkdtemp, realpath, rm, writeFile } from "fs/promises"; +import { mkdtemp, realpath, rm, mkdir, stat } from "fs/promises"; import { tmpdir } from "os"; import { join } from "path"; @@ -15,7 +15,7 @@ afterEach(async () => { }); it("should create selected template with @ prefix", async () => { - const { stdout, stderr, exited } = spawn({ + const { stderr } = spawn({ cmd: [bunExe(), "create", "@quick-start/some-template"], cwd: x_dir, stdout: null, @@ -29,3 +29,23 @@ it("should create selected template with @ prefix", async () => { `error: package "@quick-start/create-some-template" not found registry.npmjs.org/@quick-start%2fcreate-some-template 404`, ); }); + +it("should create template from local folder", async () => { + const bunCreateDir = join(x_dir, "bun-create"); + const testTemplate = "test-template"; + + await mkdir(`${bunCreateDir}/${testTemplate}`, { recursive: true }); + const { exited } = spawn({ + cmd: [bunExe(), "create", testTemplate], + cwd: x_dir, + stdout: null, + stdin: "pipe", + stderr: "pipe", + env: { ...env, BUN_CREATE_DIR: bunCreateDir }, + }); + + await exited; + + const dirStat = await stat(`${x_dir}/${testTemplate}`); + expect(dirStat.isDirectory()).toBe(true); +});