mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
Support 'bun init <folder>' (#17743)
This commit is contained in:
@@ -1798,7 +1798,7 @@ pub const Command = struct {
|
||||
.DiscordCommand => return try DiscordCommand.exec(allocator),
|
||||
.HelpCommand => return try HelpCommand.exec(allocator),
|
||||
.ReservedCommand => return try ReservedCommand.exec(allocator),
|
||||
.InitCommand => return try InitCommand.exec(allocator, bun.argv),
|
||||
.InitCommand => return try InitCommand.exec(allocator, bun.argv[@min(2, bun.argv.len)..]),
|
||||
.BuildCommand => {
|
||||
if (comptime bun.fast_debug_build_mode and bun.fast_debug_build_cmd != .BuildCommand) unreachable;
|
||||
const ctx = try Command.init(allocator, log, .BuildCommand);
|
||||
@@ -2364,13 +2364,14 @@ pub const Command = struct {
|
||||
|
||||
.InitCommand => {
|
||||
const intro_text =
|
||||
\\<b>Usage<r>: <b><green>bun init<r> <cyan>[flags]<r> <blue>[\<entrypoints\>]<r>
|
||||
\\<b>Usage<r>: <b><green>bun init<r> <cyan>[flags]<r> <blue>[\<folder\>]<r>
|
||||
\\ Initialize a Bun project in the current directory.
|
||||
\\ Creates a package.json, tsconfig.json, and bunfig.toml if they don't exist.
|
||||
\\
|
||||
\\<b>Flags<r>:
|
||||
\\ <cyan>--help<r> Print this menu
|
||||
\\ <cyan>-y, --yes<r> Accept all default options
|
||||
\\ <cyan>-m, --minimal<r> Only initialize type definitions
|
||||
\\
|
||||
\\<b>Examples:<r>
|
||||
\\ <b><green>bun init<r>
|
||||
|
||||
@@ -366,19 +366,45 @@ pub const InitCommand = struct {
|
||||
private: bool = true,
|
||||
};
|
||||
|
||||
pub fn exec(alloc: std.mem.Allocator, argv: [][:0]const u8) !void {
|
||||
const print_help = brk: {
|
||||
for (argv) |arg| {
|
||||
pub fn exec(alloc: std.mem.Allocator, init_args: [][:0]const u8) !void {
|
||||
// --minimal is a special preset to create only empty package.json + tsconfig.json
|
||||
var minimal = false;
|
||||
var auto_yes = false;
|
||||
var parse_flags = true;
|
||||
var initialize_in_folder: ?[]const u8 = null;
|
||||
for (init_args) |arg_| {
|
||||
const arg = bun.span(arg_);
|
||||
if (parse_flags and arg.len > 0 and arg[0] == '-') {
|
||||
if (strings.eqlComptime(arg, "--help") or strings.eqlComptime(arg, "-h")) {
|
||||
break :brk true;
|
||||
CLI.Command.Tag.printHelp(.InitCommand, true);
|
||||
Global.exit(0);
|
||||
} else if (strings.eqlComptime(arg, "-m") or strings.eqlComptime(arg, "--minimal")) {
|
||||
minimal = true;
|
||||
} else if (strings.eqlComptime(arg, "-y") or strings.eqlComptime(arg, "--yes")) {
|
||||
auto_yes = true;
|
||||
} else if (strings.eqlComptime(arg, "--")) {
|
||||
parse_flags = false;
|
||||
} else {
|
||||
// invalid flag; ignore
|
||||
}
|
||||
} else {
|
||||
if (initialize_in_folder == null) {
|
||||
initialize_in_folder = arg;
|
||||
} else {
|
||||
// invalid positional; ignore
|
||||
}
|
||||
}
|
||||
break :brk false;
|
||||
};
|
||||
}
|
||||
|
||||
if (print_help) {
|
||||
CLI.Command.Tag.printHelp(.InitCommand, true);
|
||||
Global.exit(0);
|
||||
if (initialize_in_folder) |ifdir| {
|
||||
std.fs.cwd().makePath(ifdir) catch |err| {
|
||||
Output.prettyErrorln("Failed to create directory {s}: {s}", .{ ifdir, @errorName(err) });
|
||||
Global.exit(1);
|
||||
};
|
||||
bun.sys.chdir("", ifdir).unwrap() catch |err| {
|
||||
Output.prettyErrorln("Failed to change directory to {s}: {s}", .{ ifdir, @errorName(err) });
|
||||
Global.exit(1);
|
||||
};
|
||||
}
|
||||
|
||||
var fs = try Fs.FileSystem.init(null);
|
||||
@@ -464,17 +490,6 @@ pub const InitCommand = struct {
|
||||
}
|
||||
}
|
||||
|
||||
// --minimal is a special preset to create only empty package.json + tsconfig.json
|
||||
const minimal = brk: {
|
||||
for (argv) |arg_| {
|
||||
const arg = bun.span(arg_);
|
||||
if (strings.eqlComptime(arg, "-m") or strings.eqlComptime(arg, "--minimal")) {
|
||||
break :brk true;
|
||||
}
|
||||
}
|
||||
break :brk false;
|
||||
};
|
||||
|
||||
if (fields.entry_point.len == 0 and !minimal) infer: {
|
||||
fields.entry_point = "index.ts";
|
||||
|
||||
@@ -526,16 +541,6 @@ pub const InitCommand = struct {
|
||||
).data.e_object;
|
||||
}
|
||||
|
||||
const auto_yes = Output.stdout_descriptor_type != .terminal or minimal or brk: {
|
||||
for (argv) |arg_| {
|
||||
const arg = bun.span(arg_);
|
||||
if (strings.eqlComptime(arg, "-y") or strings.eqlComptime(arg, "--yes")) {
|
||||
break :brk true;
|
||||
}
|
||||
}
|
||||
break :brk false;
|
||||
};
|
||||
|
||||
var template: Template = .blank;
|
||||
|
||||
if (!auto_yes) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import fs from "fs";
|
||||
import { bunEnv, bunExe, tmpdirSync } from "harness";
|
||||
import fs, { readdirSync } from "fs";
|
||||
import { bunEnv, bunExe, tempDirWithFiles, tmpdirSync } from "harness";
|
||||
import path from "path";
|
||||
|
||||
test("bun init works", () => {
|
||||
@@ -76,3 +76,143 @@ test("bun init with piped cli", () => {
|
||||
expect(fs.existsSync(path.join(temp, "node_modules"))).toBe(true);
|
||||
expect(fs.existsSync(path.join(temp, "tsconfig.json"))).toBe(true);
|
||||
}, 30_000);
|
||||
|
||||
test("bun init in folder", () => {
|
||||
const temp = tmpdirSync();
|
||||
const out = Bun.spawnSync({
|
||||
cmd: [bunExe(), "init", "-y", "mydir"],
|
||||
cwd: temp,
|
||||
stdio: ["ignore", "inherit", "inherit"],
|
||||
env: bunEnv,
|
||||
});
|
||||
expect(out.exitCode).toBe(0);
|
||||
expect(readdirSync(temp).sort()).toEqual(["mydir"]);
|
||||
expect(readdirSync(path.join(temp, "mydir")).sort()).toMatchInlineSnapshot(`
|
||||
[
|
||||
".gitignore",
|
||||
"README.md",
|
||||
"bun.lock",
|
||||
"index.ts",
|
||||
"node_modules",
|
||||
"package.json",
|
||||
"tsconfig.json",
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test("bun init error rather than overwriting file", async () => {
|
||||
const temp = tempDirWithFiles("mytmp", {
|
||||
"mydir": "don't delete me!!!",
|
||||
});
|
||||
const out = Bun.spawnSync({
|
||||
cmd: [bunExe(), "init", "-y", "mydir"],
|
||||
cwd: temp,
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
env: bunEnv,
|
||||
});
|
||||
expect(out.stdout.toString()).toBe("");
|
||||
expect(out.stderr.toString()).toBe("Failed to create directory mydir: NotDir\n");
|
||||
expect(out.exitCode).not.toBe(0);
|
||||
expect(readdirSync(temp).sort()).toEqual(["mydir"]);
|
||||
expect(await Bun.file(path.join(temp, "mydir")).text()).toBe("don't delete me!!!");
|
||||
});
|
||||
|
||||
test("bun init utf-8", async () => {
|
||||
const temp = tempDirWithFiles("mytmp", {});
|
||||
const out = Bun.spawnSync({
|
||||
cmd: [bunExe(), "init", "-y", "u t f ∞™/subpath"],
|
||||
cwd: temp,
|
||||
stdio: ["ignore", "inherit", "inherit"],
|
||||
env: bunEnv,
|
||||
});
|
||||
expect(out.exitCode).toBe(0);
|
||||
expect(readdirSync(temp).sort()).toEqual(["u t f ∞™"]);
|
||||
expect(readdirSync(path.join(temp, "u t f ∞™")).sort()).toEqual(["subpath"]);
|
||||
expect(readdirSync(path.join(temp, "u t f ∞™/subpath")).sort()).toMatchInlineSnapshot(`
|
||||
[
|
||||
".gitignore",
|
||||
"README.md",
|
||||
"bun.lock",
|
||||
"index.ts",
|
||||
"node_modules",
|
||||
"package.json",
|
||||
"tsconfig.json",
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test("bun init twice", async () => {
|
||||
const temp = tempDirWithFiles("mytmp", {});
|
||||
const out = Bun.spawnSync({
|
||||
cmd: [bunExe(), "init", "-y", "mydir"],
|
||||
cwd: temp,
|
||||
stdio: ["ignore", "inherit", "inherit"],
|
||||
env: bunEnv,
|
||||
});
|
||||
expect(out.exitCode).toBe(0);
|
||||
expect(readdirSync(temp).sort()).toEqual(["mydir"]);
|
||||
expect(readdirSync(path.join(temp, "mydir")).sort()).toMatchInlineSnapshot(`
|
||||
[
|
||||
".gitignore",
|
||||
"README.md",
|
||||
"bun.lock",
|
||||
"index.ts",
|
||||
"node_modules",
|
||||
"package.json",
|
||||
"tsconfig.json",
|
||||
]
|
||||
`);
|
||||
await Bun.write(path.join(temp, "mydir/index.ts"), "my edited index.ts");
|
||||
await Bun.write(path.join(temp, "mydir/README.md"), "my edited README.md");
|
||||
await Bun.write(path.join(temp, "mydir/.gitignore"), "my edited .gitignore");
|
||||
await Bun.write(
|
||||
path.join(temp, "mydir/package.json"),
|
||||
JSON.stringify({
|
||||
...(await Bun.file(path.join(temp, "mydir/package.json")).json()),
|
||||
name: "my edited package.json",
|
||||
}),
|
||||
);
|
||||
await Bun.write(path.join(temp, "mydir/tsconfig.json"), `my edited tsconfig.json`);
|
||||
const out2 = Bun.spawnSync({
|
||||
cmd: [bunExe(), "init", "mydir"],
|
||||
cwd: temp,
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
env: bunEnv,
|
||||
});
|
||||
expect(out2.stdout.toString()).toMatchInlineSnapshot(`""`);
|
||||
expect(out2.stderr.toString()).toMatchInlineSnapshot(`
|
||||
"note: package.json already exists, configuring existing project
|
||||
"
|
||||
`);
|
||||
expect(out2.exitCode).toBe(0);
|
||||
expect(readdirSync(temp).sort()).toEqual(["mydir"]);
|
||||
expect(readdirSync(path.join(temp, "mydir")).sort()).toMatchInlineSnapshot(`
|
||||
[
|
||||
".gitignore",
|
||||
"README.md",
|
||||
"bun.lock",
|
||||
"index.ts",
|
||||
"node_modules",
|
||||
"package.json",
|
||||
"tsconfig.json",
|
||||
]
|
||||
`);
|
||||
expect(await Bun.file(path.join(temp, "mydir/index.ts")).text()).toMatchInlineSnapshot(`"my edited index.ts"`);
|
||||
expect(await Bun.file(path.join(temp, "mydir/README.md")).text()).toMatchInlineSnapshot(`"my edited README.md"`);
|
||||
expect(await Bun.file(path.join(temp, "mydir/.gitignore")).text()).toMatchInlineSnapshot(`"my edited .gitignore"`);
|
||||
expect(await Bun.file(path.join(temp, "mydir/package.json")).json()).toMatchInlineSnapshot(`
|
||||
{
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest",
|
||||
},
|
||||
"module": "index.ts",
|
||||
"name": "my edited package.json",
|
||||
"peerDependencies": {
|
||||
"typescript": "^5",
|
||||
},
|
||||
"private": true,
|
||||
"type": "module",
|
||||
}
|
||||
`);
|
||||
expect(await Bun.file(path.join(temp, "mydir/tsconfig.json")).text()).toMatchInlineSnapshot(`"my edited tsconfig.json"`);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user