diff options
author | 2022-08-04 21:42:49 -0700 | |
---|---|---|
committer | 2022-08-04 21:42:49 -0700 | |
commit | d034c004f99cb660b88ed95a851d92291c45b7cb (patch) | |
tree | 8b6122581cf859cecf7e303fd9ba2a7c968faa8a | |
parent | 7ba61bc98341c1a357a8b0a9323643d6162976e0 (diff) | |
download | bun-d034c004f99cb660b88ed95a851d92291c45b7cb.tar.gz bun-d034c004f99cb660b88ed95a851d92291c45b7cb.tar.zst bun-d034c004f99cb660b88ed95a851d92291c45b7cb.zip |
Implement `bun init` subcommand
-rw-r--r-- | README.md | 27 | ||||
-rw-r--r-- | completions/bun.bash | 2 | ||||
-rw-r--r-- | completions/bun.fish | 20 | ||||
-rw-r--r-- | completions/bun.zsh | 13 | ||||
-rw-r--r-- | src/cli.zig | 7 | ||||
-rw-r--r-- | src/cli/README-for-init.md | 15 | ||||
-rw-r--r-- | src/cli/gitignore-for-init | 169 | ||||
-rw-r--r-- | src/cli/init_command.zig | 402 | ||||
-rw-r--r-- | src/cli/tsconfig-for-init.json | 14 | ||||
-rw-r--r-- | src/fs.zig | 8 | ||||
-rw-r--r-- | src/js_ast.zig | 17 | ||||
-rw-r--r-- | src/string_immutable.zig | 2 |
12 files changed, 676 insertions, 20 deletions
@@ -19,7 +19,6 @@ All in one fast & easy-to-use tool. Instead of 1,000 node_modules for develo **bun is experimental software**. Join [bun’s Discord](https://bun.sh/discord) for help and have a look at [things that don’t work yet](#not-implemented-yet). - Today, bun's primary focus is bun.js: bun's JavaScript runtime. ## Install @@ -98,6 +97,7 @@ If using Linux, kernel version 5.6 or higher is strongly recommended, but the mi - [Testing your new template](#testing-your-new-template) - [Config](#config) - [How `bun create` works](#how-bun-create-works) + - [`bun init`](#bun-init) - [`bun bun`](#bun-bun) - [Why bundle?](#why-bundle) - [What is `.bun`?](#what-is-bun) @@ -1586,7 +1586,7 @@ bun is distributed as a single binary file, so you can also do this manually: ### Canary builds -[Canary](https://github.com/oven-sh/bun/releases/tag/canary) builds are generated on every commit. At the time of writing, only Linux x64 & Linux arm64 are generated. +[Canary](https://github.com/oven-sh/bun/releases/tag/canary) builds are generated on every commit. To install a [canary](https://github.com/oven-sh/bun/releases/tag/canary) build of bun, run: @@ -1604,6 +1604,29 @@ To revert to the latest published version of bun, run: bun upgrade ``` +### `bun init` + +`bun init` is a quick way to start a blank project with Bun. It guesses with sane defaults and is non-destructive when run multiple times. + + + +It creates: + +- a `package.json` file with a name that defaults to the current directory name +- a `tsconfig.json` file or a `jsconfig.json` file, depending if the entry point is a TypeScript file or not +- an entry point which defaults to `index.ts` unless any of `index.{tsx, jsx, js, mts, mjs}` exist or the `package.json` specifies a `module` or `main` field +- a `README.md` file + +If you pass `-y` or `--yes`, it will assume you want to continue without asking questions. + +At the end, it runs `bun install` to install `bun-types`. + +Added in Bun v0.1.7. + +#### How is `bun init` different than `bun create`? + +`bun init` is for blank projects. `bun create` applies templates. + ### `bun completions` This command installs completions for `zsh` and/or `fish`. It runs automatically on every `bun upgrade` and on install. It reads from `$SHELL` to determine which shell to install for. It tries several common shell completion directories for your shell and OS. diff --git a/completions/bun.bash b/completions/bun.bash index 057ffc725..bba078366 100644 --- a/completions/bun.bash +++ b/completions/bun.bash @@ -82,7 +82,7 @@ _bun_completions() { declare -A GLOBAL_OPTIONS; declare -A PACKAGE_OPTIONS; - local SUBCOMMANDS="dev bun create run install add remove upgrade completions discord help"; + local SUBCOMMANDS="dev bun create run install add remove upgrade completions discord help init"; GLOBAL_OPTIONS[LONG_OPTIONS]="--use --cwd --bunfile --server-bunfile --config --disable-react-fast-refresh --disable-hmr --extension-order --jsx-factory --jsx-fragment --extension-order --jsx-factory --jsx-fragment --jsx-import-source --jsx-production --jsx-runtime --main-fields --no-summary --version --platform --public-dir --tsconfig-override --define --external --help --inject --loader --origin --port --dump-environment-variables --dump-limits --disable-bun-js"; GLOBAL_OPTIONS[SHORT_OPTIONS]="-c -v -d -e -h -i -l -u -p"; diff --git a/completions/bun.fish b/completions/bun.fish index db2ac60a6..60bb21361 100644 --- a/completions/bun.fish +++ b/completions/bun.fish @@ -53,14 +53,14 @@ end set -l bun_install_boolean_flags yarn production optional development no-save dry-run force no-cache silent verbose global set -l bun_install_boolean_flags_descriptions "Write a yarn.lock file (yarn v1)" "Don't install devDependencies" "Add dependency to optionalDependencies" "Add dependency to devDependencies" "Don't install devDependencies" "Don't install anything" "Always request the latest versions from the registry & reinstall all dependenices" "Ignore manifest cache entirely" "Don't output anything" "Excessively verbose logging" "Use global folder" -set -l bun_builtin_cmds dev create help bun upgrade discord run install remove add -set -l bun_builtin_cmds_without_run dev create help bun upgrade discord install remove add -set -l bun_builtin_cmds_without_bun dev create help upgrade run discord install remove add -set -l bun_builtin_cmds_without_create dev help bun upgrade discord run install remove add -set -l bun_builtin_cmds_without_install create dev help bun upgrade discord run remove add -set -l bun_builtin_cmds_without_remove create dev help bun upgrade discord run install add -set -l bun_builtin_cmds_without_add create dev help bun upgrade discord run remove install -set -l bun_builtin_cmds_without_pm create dev help bun upgrade discord run +set -l bun_builtin_cmds dev create help bun upgrade discord run install remove add init +set -l bun_builtin_cmds_without_run dev create help bun upgrade discord install remove add init +set -l bun_builtin_cmds_without_bun dev create help upgrade run discord install remove add init +set -l bun_builtin_cmds_without_create dev help bun upgrade discord run install remove add init +set -l bun_builtin_cmds_without_install create dev help bun upgrade discord run remove add init +set -l bun_builtin_cmds_without_remove create dev help bun upgrade discord run install add init +set -l bun_builtin_cmds_without_add create dev help bun upgrade discord run remove install init +set -l bun_builtin_cmds_without_pm create dev help bun upgrade discord run init complete -c bun \ -n "not __fish_seen_subcommand_from $bun_builtin_cmds_without_run; and not __fish_seen_subcommand_from (__fish__get_bun_bins) (__fish__get_bun_scripts); and __fish_use_subcommand" -a '(__fish__get_bun_scripts)' -d 'script' @@ -85,7 +85,7 @@ complete -c bun \ complete -c bun \ -n "bun_fish_is_nth_token 1; and not __fish_seen_subcommand_from $bun_builtin_cmds; and not __fish_seen_subcommand_from (__fish__get_bun_bins) (__fish__get_bun_scripts) and __fish_use_subcommand" -a 'dev' -d 'Start dev server' complete -c bun \ - -n "bun_fish_is_nth_token 1; and not __fish_seen_subcommand_from $bun_builtin_cmds; and not __fish_seen_subcommand_from (__fish__get_bun_bins) (__fish__get_bun_scripts) and __bun_command_count 1 and __fish_use_subcommand" -a 'create' -f -d 'Create a new project' + -n "bun_fish_is_nth_token 1; and not __fish_seen_subcommand_from $bun_builtin_cmds; and not __fish_seen_subcommand_from (__fish__get_bun_bins) (__fish__get_bun_scripts) and __bun_command_count 1 and __fish_use_subcommand" -a 'create' -f -d 'Create a new project from a template' complete -c bun \ -n "not __fish_seen_subcommand_from $bun_builtin_cmds_without_create next react; and not __fish_seen_subcommand_from (__fish__get_bun_bins) (__fish__get_bun_scripts); and __fish_seen_subcommand_from create;" -a 'next' -d 'new Next.js project' @@ -115,6 +115,8 @@ complete -c bun \ -n "not __fish_seen_subcommand_from $bun_builtin_cmds_without_create; and not __fish_seen_subcommand_from (__fish__get_bun_bins); and not __fish_seen_subcommand_from (__fish__get_bun_scripts); and __fish_seen_subcommand_from react; or __fish_seen_subcommand_from next" -F -d "Create in directory" +complete -c bun \ + -n "bun_fish_is_nth_token 1; and not __fish_seen_subcommand_from $bun_builtin_cmds; and not __fish_seen_subcommand_from (__fish__get_bun_bins) (__fish__get_bun_scripts) and __bun_command_count 1 and __fish_use_subcommand" -a 'init' -F -d 'Start an empty Bun project' complete -c bun \ -n "bun_fish_is_nth_token 1; and not __fish_seen_subcommand_from $bun_builtin_cmds; and not __fish_seen_subcommand_from (__fish__get_bun_bins) (__fish__get_bun_scripts) and __bun_command_count 1 and __fish_use_subcommand" -a 'install' -f -d 'Install packages from package.json' diff --git a/completions/bun.zsh b/completions/bun.zsh index 91becac35..af2c89e4a 100644 --- a/completions/bun.zsh +++ b/completions/bun.zsh @@ -1,10 +1,10 @@ _bun() { zstyle ':completion:*:*:bun:*' group-name '' zstyle ':completion:*:*:bun-grouped:*' group-name '' - + zstyle ':completion:*:*:bun::descriptions' format '%F{green}-- %d --%f' zstyle ':completion:*:*:bun-grouped:*' format '%F{green}-- %d --%f' - + local program=bun typeset -A opt_args local curcontext="$curcontext" state line context @@ -90,6 +90,15 @@ _bun() { esac ;; + init) + # ---- Command: init + _arguments -s -C \ + '1: :->cmd' \ + '-y[Answer yes to all prompts]' \ + '--yes[Answer yes to all prompts]' && + ret=0 + + ;; create) # ---- Command: create diff --git a/src/cli.zig b/src/cli.zig index e849d258f..5dc954be9 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -645,9 +645,8 @@ const AutoCommand = struct { try HelpCommand.execWithReason(allocator, .invalid_command); } }; -const InitCommand = struct { - pub fn exec(_: std.mem.Allocator) !void {} -}; +const InitCommand = @import("./cli/init_command.zig").InitCommand; + pub const HelpCommand = struct { pub fn exec(allocator: std.mem.Allocator) !void { @setCold(true); @@ -909,7 +908,7 @@ pub const Command = struct { switch (tag) { .DiscordCommand => return try DiscordCommand.exec(allocator), .HelpCommand => return try HelpCommand.exec(allocator), - .InitCommand => return try InitCommand.exec(allocator), + .InitCommand => return try InitCommand.exec(allocator, std.os.argv), else => {}, } diff --git a/src/cli/README-for-init.md b/src/cli/README-for-init.md new file mode 100644 index 000000000..7a0dced5d --- /dev/null +++ b/src/cli/README-for-init.md @@ -0,0 +1,15 @@ +# {[name]s} + +To install dependencies: + +```bash +bun install +``` + +To run: + +```bash +bun run {[entryPoint]s} +``` + +This project was created using `bun init` in bun v{[bunVersion]any}. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime. diff --git a/src/cli/gitignore-for-init b/src/cli/gitignore-for-init new file mode 100644 index 000000000..f81d56eaa --- /dev/null +++ b/src/cli/gitignore-for-init @@ -0,0 +1,169 @@ +# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore + +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +\*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + +coverage +\*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +\*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +\*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.cache +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +.cache/ + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp +.cache + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.\* diff --git a/src/cli/init_command.zig b/src/cli/init_command.zig new file mode 100644 index 000000000..fe5164110 --- /dev/null +++ b/src/cli/init_command.zig @@ -0,0 +1,402 @@ +const bun = @import("../global.zig"); +const string = bun.string; +const Output = bun.Output; +const Global = bun.Global; +const Environment = bun.Environment; +const strings = bun.strings; +const MutableString = bun.MutableString; +const stringZ = bun.stringZ; +const default_allocator = bun.default_allocator; +const C = bun.C; +const std = @import("std"); +const open = @import("../open.zig"); +const CLI = @import("../cli.zig"); +const Fs = @import("../fs.zig"); +const ParseJSON = @import("../json_parser.zig").ParseJSONUTF8; +const js_parser = @import("../js_parser.zig"); +const js_ast = @import("../js_ast.zig"); +const linker = @import("../linker.zig"); +const options = @import("../options.zig"); +const initializeStore = @import("./create_command.zig").initializeStore; +const lex = @import("../js_lexer.zig"); +const logger = @import("../logger.zig"); +const JSPrinter = @import("../js_printer.zig"); + +fn exists(path: anytype) bool { + if (@TypeOf(path) == [:0]const u8 or @TypeOf(path) == [:0]u8) { + if (std.os.accessZ(path, 0)) { + return true; + } else |_| { + return false; + } + } else { + if (std.os.access(path, 0)) { + return true; + } else |_| { + return false; + } + } +} +pub const InitCommand = struct { + fn prompt( + alloc: std.mem.Allocator, + comptime label: string, + default: []const u8, + _: bool, + ) ![]const u8 { + Output.pretty(label, .{}); + if (default.len > 0) { + Output.pretty("<d>({s}):<r> ", .{default}); + } + + Output.flush(); + + const input = try std.io.getStdIn().reader().readUntilDelimiterAlloc(alloc, '\n', 1024); + if (input.len > 0) { + return input; + } else { + return default; + } + } + + const default_gitignore = @embedFile("gitignore-for-init"); + const default_tsconfig = @embedFile("tsconfig-for-init.json"); + const README = @embedFile("README-for-init.md"); + + // TODO: unicode case folding + fn normalizePackageName(allocator: std.mem.Allocator, input: []const u8) ![]const u8 { + // toLowerCase + const needs_normalize = brk: { + for (input) |c| { + if ((c >= 'A' and c <= 'Z') or c == ' ' or c == '"' or c == '\'') { + break :brk true; + } + } + break :brk false; + }; + + if (!needs_normalize) { + return input; + } + + var new = try allocator.alloc(u8, input.len); + for (new) |c, i| { + if (c >= 'A' and c <= 'Z') { + new[i] = c + ('a' - 'A'); + } else if (c == ' ' or c == '"' or c == '\'') { + new[i] = '-'; + } else { + new[i] = c; + } + } + + return new; + } + + const PackageJSONFields = struct { + name: string = "project", + @"type": string = "module", + object: *js_ast.E.Object = undefined, + entry_point: string = "", + }; + + pub fn exec(alloc: std.mem.Allocator, argv: [][*:0]u8) !void { + var fs = try Fs.FileSystem.init1(alloc, null); + const pathname = Fs.PathName.init(fs.topLevelDirWithoutTrailingSlash()); + const destination_dir = std.fs.cwd(); + + var fields = PackageJSONFields{}; + + var package_json_file = destination_dir.openFile("package.json", .{ .mode = .read_write }) catch null; + var package_json_contents: MutableString = MutableString.initEmpty(alloc); + initializeStore(); + read_package_json: { + if (package_json_file) |pkg| { + const stat = pkg.stat() catch break :read_package_json; + + if (stat.kind != .File or stat.size == 0) { + break :read_package_json; + } + package_json_contents = try MutableString.init(alloc, stat.size); + package_json_contents.list.expandToCapacity(); + + _ = pkg.preadAll(package_json_contents.list.items, 0) catch { + package_json_file = null; + break :read_package_json; + }; + } + } + + fields.name = brk: { + if (normalizePackageName(alloc, if (pathname.filename.len > 0) pathname.filename else "")) |name| { + if (name.len > 0) { + break :brk name; + } + } else |_| {} + + break :brk "project"; + }; + var did_load_package_json = false; + if (package_json_contents.list.items.len > 0) { + process_package_json: { + var source = logger.Source.initPathString("package.json", package_json_contents.list.items); + var log = logger.Log.init(alloc); + var package_json_expr = ParseJSON(&source, &log, alloc) catch { + package_json_file = null; + break :process_package_json; + }; + + if (package_json_expr.data != .e_object) { + package_json_file = null; + break :process_package_json; + } + + fields.object = package_json_expr.data.e_object; + + if (package_json_expr.get("name")) |name| { + if (name.asString(alloc)) |str| { + fields.name = str; + } + } + + if (package_json_expr.get("module") orelse package_json_expr.get("main")) |name| { + if (name.asString(alloc)) |str| { + fields.entry_point = str; + } + } + + did_load_package_json = true; + } + } + + if (fields.entry_point.len == 0) { + infer: { + const paths_to_try = [_][:0]const u8{ + @as([:0]const u8, "index.mts"), + @as([:0]const u8, "index.tsx"), + @as([:0]const u8, "index.ts"), + @as([:0]const u8, "index.jsx"), + @as([:0]const u8, "index.mjs"), + @as([:0]const u8, "index.js"), + }; + + for (paths_to_try) |path| { + if (exists(path)) { + fields.entry_point = std.mem.span(path); + break :infer; + } + } + + fields.entry_point = "index.ts"; + } + } + + if (!did_load_package_json) { + fields.object = js_ast.Expr.init( + js_ast.E.Object, + .{}, + logger.Loc.Empty, + ).data.e_object; + } + + const auto_yes = 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; + }; + + if (!auto_yes) { + Output.prettyln("<r><b>bun init<r> helps you get started with a minimal project and tries to guess sensible defaults. <d>Press ^C anytime to quit<r>\n\n", .{}); + Output.flush(); + + fields.name = try normalizePackageName(alloc, try prompt( + alloc, + "<r><cyan>package name<r> ", + fields.name, + Output.enable_ansi_colors_stdout, + )); + fields.entry_point = try prompt( + alloc, + "<r><cyan>entry point<r> ", + fields.entry_point, + Output.enable_ansi_colors_stdout, + ); + try Output.writer().writeAll("\n"); + Output.flush(); + } + + const Steps = struct { + write_gitignore: bool = true, + write_package_json: bool = true, + write_tsconfig: bool = true, + write_readme: bool = true, + }; + + var steps = Steps{}; + + steps.write_gitignore = brk: { + if (exists(".gitignore")) { + break :brk false; + } + + break :brk true; + }; + + steps.write_readme = !exists("README.md") and !exists("README") and !exists("README.txt") and !exists("README.mdx"); + + steps.write_tsconfig = brk: { + if (exists("tsconfig.json")) { + break :brk false; + } + + if (exists("jsconfig.json")) { + break :brk false; + } + + break :brk true; + }; + + { + try fields.object.putString(alloc, "name", fields.name); + if (fields.entry_point.len > 0) { + if (fields.object.hasProperty("module")) { + try fields.object.putString(alloc, "module", fields.entry_point); + try fields.object.putString(alloc, "type", "module"); + } else if (fields.object.hasProperty("main")) { + try fields.object.putString(alloc, "main", fields.entry_point); + } else { + try fields.object.putString(alloc, "module", fields.entry_point); + try fields.object.putString(alloc, "type", "module"); + } + } + + const needs_dev_dependencies = brk: { + if (fields.object.get("devDependencies")) |deps| { + if (deps.hasAnyPropertyNamed(&.{"bun-types"})) { + break :brk false; + } + } + + break :brk true; + }; + + if (needs_dev_dependencies) { + var dev_dependencies = fields.object.get("devDependencies") orelse js_ast.Expr.init(js_ast.E.Object, js_ast.E.Object{}, logger.Loc.Empty); + const version = comptime brk: { + var base = Global.version; + base.patch = 0; + break :brk base; + }; + + try dev_dependencies.data.e_object.putString(alloc, "bun-types", comptime std.fmt.comptimePrint("^{any}", .{version.fmt("")})); + try fields.object.put(alloc, "devDependencies", dev_dependencies); + } + } + + write_package_json: { + if (package_json_file == null) { + package_json_file = try std.fs.cwd().createFileZ("package.json", .{}); + } + var package_json_writer = JSPrinter.NewFileWriter(package_json_file.?); + + const written = JSPrinter.printJSON( + @TypeOf(package_json_writer), + package_json_writer, + js_ast.Expr{ .data = .{ .e_object = fields.object }, .loc = logger.Loc.Empty }, + &logger.Source.initEmptyFile("package.json"), + ) catch |err| { + Output.prettyErrorln("package.json failed to write due to error {s}", .{@errorName(err)}); + package_json_file = null; + break :write_package_json; + }; + + std.os.ftruncate(package_json_file.?.handle, written + 1) catch {}; + package_json_file.?.close(); + } + + if (package_json_file != null) { + Output.prettyln("<r><green>Done!<r> A package.json file was saved in the current directory.", .{}); + } + + if (fields.entry_point.len > 0 and !exists(fields.entry_point)) { + var entry = try std.fs.cwd().createFile(fields.entry_point, .{ .truncate = true }); + entry.writeAll("console.log(\"Hello via Bun!\");") catch {}; + entry.close(); + Output.prettyln(" + <r><d>{s}<r>", .{fields.entry_point}); + Output.flush(); + } + + if (steps.write_gitignore) { + brk: { + var file = std.fs.cwd().createFileZ(".gitignore", .{ .truncate = true }) catch break :brk; + defer file.close(); + file.writeAll(default_gitignore) catch break :brk; + Output.prettyln(" + <r><d>.gitignore<r>", .{}); + Output.flush(); + } + } + + if (steps.write_tsconfig) { + brk: { + const extname = std.fs.path.extension(fields.entry_point); + const loader = options.defaultLoaders.get(extname) orelse options.Loader.ts; + const filename = if (loader.isTypeScript()) + "tsconfig.json" + else + "jsconfig.json"; + var file = std.fs.cwd().createFileZ(filename, .{ .truncate = true }) catch break :brk; + defer file.close(); + file.writeAll(default_tsconfig) catch break :brk; + Output.prettyln(" + <r><d>{s}<r><d> (for editor auto-complete)<r>", .{filename}); + Output.flush(); + } + } + + if (steps.write_readme) { + brk: { + const filename = "README.md"; + var file = std.fs.cwd().createFileZ(filename, .{ .truncate = true }) catch break :brk; + defer file.close(); + file.writer().print(README, .{ + .name = fields.name, + .bunVersion = Global.version.fmt(""), + .entryPoint = fields.entry_point, + }) catch break :brk; + Output.prettyln(" + <r><d>{s}<r>", .{filename}); + Output.flush(); + } + } + + if (fields.entry_point.len > 0) { + Output.prettyln("\nTo get started, run:", .{}); + if (strings.containsAny( + " \"'", + fields.entry_point, + )) { + Output.prettyln(" <r><cyan>bun run {any}<r>", .{JSPrinter.formatJSONString(fields.entry_point)}); + } else { + Output.prettyln(" <r><cyan>bun run {s}<r>", .{fields.entry_point}); + } + } + + Output.flush(); + + if (exists("package.json")) { + var process = std.ChildProcess.init( + &.{ + try std.fs.selfExePathAlloc(alloc), + "install", + }, + alloc, + ); + process.stderr_behavior = .Pipe; + process.stdin_behavior = .Pipe; + process.stdout_behavior = .Pipe; + _ = try process.spawnAndWait(); + } + } +}; diff --git a/src/cli/tsconfig-for-init.json b/src/cli/tsconfig-for-init.json new file mode 100644 index 000000000..feee4b584 --- /dev/null +++ b/src/cli/tsconfig-for-init.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "lib": ["ESNext"], + "module": "esnext", + "target": "esnext", + "moduleResolution": "node", + + // so that if your project isn't using TypeScript, it still has autocomplete + "allowJs": true, + + // "bun-types" is the important part + "types": ["bun-types"] + } +} diff --git a/src/fs.zig b/src/fs.zig index 78888dcd4..e54c7e0e6 100644 --- a/src/fs.zig +++ b/src/fs.zig @@ -93,6 +93,14 @@ pub const FileSystem = struct { threadlocal var tmpdir_handle: ?std.fs.Dir = null; + pub fn topLevelDirWithoutTrailingSlash(this: *const FileSystem) []const u8 { + if (this.top_level_dir.len > 1 and this.top_level_dir[this.top_level_dir.len - 1] == std.fs.path.sep) { + return this.top_level_dir[0 .. this.top_level_dir.len - 1]; + } else { + return this.top_level_dir; + } + } + pub fn tmpdir(fs: *FileSystem) std.fs.Dir { if (tmpdir_handle == null) { tmpdir_handle = fs.fs.openTmpDir() catch unreachable; diff --git a/src/js_ast.zig b/src/js_ast.zig index eb28091c9..aa53efadd 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -1335,6 +1335,21 @@ pub const E = struct { return if (asProperty(self, key)) |query| query.expr else @as(?Expr, null); } + pub fn put(self: *Object, allocator: std.mem.Allocator, key: string, expr: Expr) !void { + if (asProperty(self, key)) |query| { + self.properties.ptr[query.i].value = expr; + } else { + try self.properties.push(allocator, .{ + .key = Expr.init(E.String, E.String.init(key), expr.loc), + .value = expr, + }); + } + } + + pub fn putString(self: *Object, allocator: std.mem.Allocator, key: string, value: string) !void { + return try put(self, allocator, key, Expr.init(E.String, E.String.init(value), logger.Loc.Empty)); + } + pub const SetError = error{ OutOfMemory, Clobber }; pub fn set(self: *const Object, key: Expr, allocator: std.mem.Allocator, value: Expr) SetError!void { @@ -1502,7 +1517,7 @@ pub const E = struct { for (obj.properties.slice()) |prop| { const key = prop.key orelse continue; if (std.meta.activeTag(key.data) != .e_string) continue; - if (key.eql(string, name)) return true; + if (key.data.e_string.eql(string, name)) return true; } return false; } diff --git a/src/string_immutable.zig b/src/string_immutable.zig index 3a2bf4fc1..4d856b966 100644 --- a/src/string_immutable.zig +++ b/src/string_immutable.zig @@ -78,7 +78,7 @@ pub inline fn containsComptime(self: string, comptime str: string) bool { pub const includes = contains; pub inline fn containsAny(in: anytype, target: string) bool { - for (in) |str| if (contains(bun.span(str), target)) return true; + for (in) |str| if (contains(if (@TypeOf(str) == u8) &[1]u8{str} else bun.span(str), target)) return true; return false; } |