mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
Implement bun init subcommand
This commit is contained in:
27
README.md
27
README.md
@@ -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.
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -89,6 +89,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)
|
||||
|
||||
|
||||
@@ -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 => {},
|
||||
}
|
||||
|
||||
|
||||
15
src/cli/README-for-init.md
Normal file
15
src/cli/README-for-init.md
Normal file
@@ -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.
|
||||
169
src/cli/gitignore-for-init
Normal file
169
src/cli/gitignore-for-init
Normal file
@@ -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.\*
|
||||
402
src/cli/init_command.zig
Normal file
402
src/cli/init_command.zig
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
};
|
||||
14
src/cli/tsconfig-for-init.json
Normal file
14
src/cli/tsconfig-for-init.json
Normal file
@@ -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"]
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user