aboutsummaryrefslogtreecommitdiff
path: root/src/cli.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/cli.zig')
-rw-r--r--src/cli.zig1057
1 files changed, 453 insertions, 604 deletions
diff --git a/src/cli.zig b/src/cli.zig
index 5c1be01de..b7cbc97ba 100644
--- a/src/cli.zig
+++ b/src/cli.zig
@@ -1,6 +1,6 @@
usingnamespace @import("global.zig");
-
usingnamespace @import("./http.zig");
+
const std = @import("std");
const lex = @import("js_lexer.zig");
const logger = @import("logger.zig");
@@ -28,679 +28,528 @@ const Router = @import("./router.zig");
const NodeModuleBundle = @import("./node_module_bundle.zig").NodeModuleBundle;
+const BunCommand = @import("./cli/bun_command.zig").BunCommand;
+const DevCommand = @import("./cli/dev_command.zig").DevCommand;
+const DiscordCommand = @import("./cli/discord_command.zig").DiscordCommand;
+const BuildCommand = @import("./cli/build_command.zig").BuildCommand;
+const RunCommand = @import("./cli/run_command.zig").RunCommand;
+
+var start_time: i128 = undefined;
+
pub const Cli = struct {
- const LoaderMatcher = strings.ExactSizeMatcher(4);
- pub fn ColonListType(comptime t: type, value_resolver: anytype) type {
- return struct {
- pub fn init(allocator: *std.mem.Allocator, count: usize) !@This() {
- var keys = try allocator.alloc(string, count);
- var values = try allocator.alloc(t, count);
-
- return @This(){ .keys = keys, .values = values };
- }
- keys: []string,
- values: []t,
-
- pub fn load(self: *@This(), input: []const string) !void {
- for (input) |str, i| {
- // Support either ":" or "=" as the separator, preferring whichever is first.
- // ":" is less confusing IMO because that syntax is used with flags
- // but "=" is what esbuild uses and I want this to be somewhat familiar for people using esbuild
- const midpoint = std.math.min(strings.indexOfChar(str, ':') orelse std.math.maxInt(usize), strings.indexOfChar(str, '=') orelse std.math.maxInt(usize));
- if (midpoint == std.math.maxInt(usize)) {
- return error.InvalidSeparator;
- }
+ var wait_group: sync.WaitGroup = undefined;
+ pub fn startTransform(allocator: *std.mem.Allocator, args: Api.TransformOptions, log: *logger.Log) anyerror!void {}
+ pub fn start(allocator: *std.mem.Allocator, stdout: anytype, stderr: anytype, comptime MainPanicHandler: type) anyerror!void {
+ start_time = std.time.nanoTimestamp();
+ var log = logger.Log.init(allocator);
+ var panicker = MainPanicHandler.init(&log);
+ MainPanicHandler.Singleton = &panicker;
+
+ try Command.start(allocator, &log);
+ std.mem.doNotOptimizeAway(&log);
+ }
+};
+
+const LoaderMatcher = strings.ExactSizeMatcher(4);
+pub fn ColonListType(comptime t: type, value_resolver: anytype) type {
+ return struct {
+ pub fn init(allocator: *std.mem.Allocator, count: usize) !@This() {
+ var keys = try allocator.alloc(string, count);
+ var values = try allocator.alloc(t, count);
- self.keys[i] = str[0..midpoint];
- self.values[i] = try value_resolver(str[midpoint + 1 .. str.len]);
+ return @This(){ .keys = keys, .values = values };
+ }
+ keys: []string,
+ values: []t,
+
+ pub fn load(self: *@This(), input: []const string) !void {
+ for (input) |str, i| {
+ // Support either ":" or "=" as the separator, preferring whichever is first.
+ // ":" is less confusing IMO because that syntax is used with flags
+ // but "=" is what esbuild uses and I want this to be somewhat familiar for people using esbuild
+ const midpoint = std.math.min(strings.indexOfChar(str, ':') orelse std.math.maxInt(usize), strings.indexOfChar(str, '=') orelse std.math.maxInt(usize));
+ if (midpoint == std.math.maxInt(usize)) {
+ return error.InvalidSeparator;
}
- }
- pub fn resolve(allocator: *std.mem.Allocator, input: []const string) !@This() {
- var list = try init(allocator, input.len);
- try list.load(input);
- return list;
- }
- };
- }
- pub const LoaderColonList = ColonListType(Api.Loader, Arguments.loader_resolver);
- pub const DefineColonList = ColonListType(string, Arguments.noop_resolver);
-
- pub const Arguments = struct {
- pub fn loader_resolver(in: string) !Api.Loader {
- const Matcher = strings.ExactSizeMatcher(4);
- switch (Matcher.match(in)) {
- Matcher.case("jsx") => return Api.Loader.jsx,
- Matcher.case("js") => return Api.Loader.js,
- Matcher.case("ts") => return Api.Loader.ts,
- Matcher.case("tsx") => return Api.Loader.tsx,
- Matcher.case("css") => return Api.Loader.css,
- Matcher.case("file") => return Api.Loader.file,
- Matcher.case("json") => return Api.Loader.json,
- else => {
- return error.InvalidLoader;
- },
+ self.keys[i] = str[0..midpoint];
+ self.values[i] = try value_resolver(str[midpoint + 1 .. str.len]);
}
}
- pub fn noop_resolver(in: string) !string {
- return in;
+ pub fn resolve(allocator: *std.mem.Allocator, input: []const string) !@This() {
+ var list = try init(allocator, input.len);
+ try list.load(input);
+ return list;
}
-
- pub fn fileReadError(err: anyerror, stderr: anytype, filename: string, kind: string) noreturn {
- stderr.writer().print("Error reading file \"{s}\" for {s}: {s}", .{ filename, kind, @errorName(err) }) catch {};
- std.process.exit(1);
+ };
+}
+pub const LoaderColonList = ColonListType(Api.Loader, Arguments.loader_resolver);
+pub const DefineColonList = ColonListType(string, Arguments.noop_resolver);
+
+pub const Arguments = struct {
+ pub fn loader_resolver(in: string) !Api.Loader {
+ const Matcher = strings.ExactSizeMatcher(4);
+ switch (Matcher.match(in)) {
+ Matcher.case("jsx") => return Api.Loader.jsx,
+ Matcher.case("js") => return Api.Loader.js,
+ Matcher.case("ts") => return Api.Loader.ts,
+ Matcher.case("tsx") => return Api.Loader.tsx,
+ Matcher.case("css") => return Api.Loader.css,
+ Matcher.case("file") => return Api.Loader.file,
+ Matcher.case("json") => return Api.Loader.json,
+ else => {
+ return error.InvalidLoader;
+ },
}
+ }
- pub fn readFile(
- allocator: *std.mem.Allocator,
- cwd: string,
- filename: string,
- ) ![]u8 {
- var paths = [_]string{ cwd, filename };
- const outpath = try std.fs.path.resolve(allocator, &paths);
- defer allocator.free(outpath);
- var file = try std.fs.openFileAbsolute(outpath, std.fs.File.OpenFlags{ .read = true, .write = false });
- defer file.close();
- const stats = try file.stat();
- return try file.readToEndAlloc(allocator, stats.size);
- }
+ pub fn noop_resolver(in: string) !string {
+ return in;
+ }
- pub fn parse(allocator: *std.mem.Allocator, stdout: anytype, stderr: anytype) !Api.TransformOptions {
- @setEvalBranchQuota(9999);
- const params = comptime [_]clap.Param(clap.Help){
- clap.parseParam("-h, --help Display this help and exit. ") catch unreachable,
- clap.parseParam("-r, --resolve <STR> Determine import/require behavior. \"disable\" ignores. \"dev\" bundles node_modules and builds everything else as independent entry points") catch unreachable,
- clap.parseParam("-d, --define <STR>... Substitute K:V while parsing, e.g. --define process.env.NODE_ENV:development") catch unreachable,
- clap.parseParam("-l, --loader <STR>... Parse files with .ext:loader, e.g. --loader .js:jsx. Valid loaders: jsx, js, json, tsx (not implemented yet), ts (not implemented yet), css (not implemented yet)") catch unreachable,
- clap.parseParam("-o, --outdir <STR> Save output to directory (default: \"out\" if none provided and multiple entry points passed)") catch unreachable,
- clap.parseParam("-e, --external <STR>... Exclude module from transpilation (can use * wildcards). ex: -e react") catch unreachable,
- clap.parseParam("-i, --inject <STR>... Inject module at the top of every file") catch unreachable,
- clap.parseParam("--cwd <STR> Absolute path to resolve entry points from.") catch unreachable,
- clap.parseParam("--origin <STR> Rewrite import paths to start with --origin. Useful for web browsers. Default: \"/\"") catch unreachable,
- clap.parseParam("--serve Start a local dev server. This also sets resolve to \"lazy\".") catch unreachable,
- clap.parseParam("--static-dir <STR> Top-level directory for .html files, fonts or anything external. Defaults to \"<cwd>/public\", to match create-react-app and Next.js") catch unreachable,
- clap.parseParam("--jsx-factory <STR> Changes the function called when compiling JSX elements using the classic JSX runtime") catch unreachable,
- clap.parseParam("--jsx-fragment <STR> Changes the function called when compiling JSX fragments using the classic JSX runtime") catch unreachable,
- clap.parseParam("--jsx-import-source <STR> Declares the module specifier to be used for importing the jsx and jsxs factory functions. Default: \"react\"") catch unreachable,
- clap.parseParam("--jsx-runtime <STR> \"automatic\" (default) or \"classic\"") catch unreachable,
- clap.parseParam("--jsx-production Use jsx instead of jsxDEV (default) for the automatic runtime") catch unreachable,
- clap.parseParam("--extension-order <STR>... defaults to: .tsx,.ts,.jsx,.js,.json ") catch unreachable,
- clap.parseParam("--disable-react-fast-refresh Disable React Fast Refresh. Enabled if --serve is set and --jsx-production is not set. Otherwise, it's a noop.") catch unreachable,
- clap.parseParam("--tsconfig-override <STR> Load tsconfig from path instead of cwd/tsconfig.json") catch unreachable,
- clap.parseParam("--platform <STR> \"browser\" or \"node\". Defaults to \"browser\"") catch unreachable,
- clap.parseParam("--main-fields <STR>... Main fields to lookup in package.json. Defaults to --platform dependent") catch unreachable,
- clap.parseParam("--scan Instead of bundling or transpiling, print a list of every file imported by an entry point, recursively") catch unreachable,
- clap.parseParam("--new-jsb Generate a new node_modules.jsb file from node_modules and entry point(s)") catch unreachable,
- clap.parseParam("--jsb <STR> Use a Bun JavaScript Bundle (default: \"./node_modules.jsb\" if exists)") catch unreachable,
- clap.parseParam("--jsb-for-server <STR> Use a server-only Bun JavaScript Bundle (default: \"./node_modules.server.jsb\" if exists)") catch unreachable,
- clap.parseParam("--use <STR> Use a JavaScript framework (package name or path to package)") catch unreachable,
- clap.parseParam("--production This sets the defaults to production. Applies to jsx & framework") catch unreachable,
-
- clap.parseParam("<POS>... Entry point(s) to use. Can be individual files, npm packages, or one directory. If one directory, it will auto-detect entry points using a filesystem router. If you're using a framework, passing entry points are optional.") catch unreachable,
- };
+ pub fn fileReadError(err: anyerror, stderr: anytype, filename: string, kind: string) noreturn {
+ stderr.writer().print("Error reading file \"{s}\" for {s}: {s}", .{ filename, kind, @errorName(err) }) catch {};
+ std.process.exit(1);
+ }
- var diag = clap.Diagnostic{};
+ pub fn readFile(
+ allocator: *std.mem.Allocator,
+ cwd: string,
+ filename: string,
+ ) ![]u8 {
+ var paths = [_]string{ cwd, filename };
+ const outpath = try std.fs.path.resolve(allocator, &paths);
+ defer allocator.free(outpath);
+ var file = try std.fs.openFileAbsolute(outpath, std.fs.File.OpenFlags{ .read = true, .write = false });
+ defer file.close();
+ const stats = try file.stat();
+ return try file.readToEndAlloc(allocator, stats.size);
+ }
- var args = clap.parse(clap.Help, &params, .{ .diagnostic = &diag }) catch |err| {
- // Report useful error and exit
- diag.report(stderr.writer(), err) catch {};
- return err;
- };
+ pub fn resolve_jsx_runtime(str: string) !Api.JsxRuntime {
+ if (strings.eqlComptime(str, "automatic")) {
+ return Api.JsxRuntime.automatic;
+ } else if (strings.eqlComptime(str, "fallback")) {
+ return Api.JsxRuntime.classic;
+ } else {
+ return error.InvalidJSXRuntime;
+ }
+ }
- if (args.flag("--help")) {
- try clap.help(stderr.writer(), &params);
- std.process.exit(1);
- }
+ const ParamType = clap.Param(clap.Help);
+
+ const params: [25]ParamType = brk: {
+ @setEvalBranchQuota(9999);
+ break :brk [_]ParamType{
+ clap.parseParam("-r, --resolve <STR> Determine import/require behavior. \"disable\" ignores. \"dev\" bundles node_modules and builds everything else as independent entry points") catch unreachable,
+ clap.parseParam("-h, --help Display this help and exit. ") catch unreachable,
+ clap.parseParam("-d, --define <STR>... Substitute K:V while parsing, e.g. --define process.env.NODE_ENV:development") catch unreachable,
+ clap.parseParam("-l, --loader <STR>... Parse files with .ext:loader, e.g. --loader .js:jsx. Valid loaders: jsx, js, json, tsx (not implemented yet), ts (not implemented yet), css (not implemented yet)") catch unreachable,
+ clap.parseParam("-o, --outdir <STR> Save output to directory (default: \"out\" if none provided and multiple entry points passed)") catch unreachable,
+ clap.parseParam("-e, --external <STR>... Exclude module from transpilation (can use * wildcards). ex: -e react") catch unreachable,
+ clap.parseParam("-i, --inject <STR>... Inject module at the top of every file") catch unreachable,
+ clap.parseParam("--cwd <STR> Absolute path to resolve files & entry points from. This just changes the process' cwd.") catch unreachable,
+ clap.parseParam("--use <STR> Choose a framework, e.g. \"--use next\". It checks first for a package named \"bun-framework-packagename\" and then \"packagename\".") catch unreachable,
+ clap.parseParam("--origin <STR> Rewrite import paths to start with --origin. Default: \"/\"") catch unreachable,
+ clap.parseParam("--static-dir <STR> Top-level directory for .html files, fonts or anything external. Defaults to \"<cwd>/public\", to match create-react-app and Next.js") catch unreachable,
+ clap.parseParam("--extension-order <STR>... defaults to: .tsx,.ts,.jsx,.js,.json ") catch unreachable,
+ clap.parseParam("--tsconfig-override <STR> Load tsconfig from path instead of cwd/tsconfig.json") catch unreachable,
+ clap.parseParam("--platform <STR> \"browser\" or \"node\". Defaults to \"browser\"") catch unreachable,
+ clap.parseParam("--main-fields <STR>... Main fields to lookup in package.json. Defaults to --platform dependent") catch unreachable,
+ clap.parseParam("--disable-react-fast-refresh Disable React Fast Refresh") catch unreachable,
+ clap.parseParam("--jsx-factory <STR> Changes the function called when compiling JSX elements using the classic JSX runtime") catch unreachable,
+ clap.parseParam("--jsx-fragment <STR> Changes the function called when compiling JSX fragments using the classic JSX runtime") catch unreachable,
+ clap.parseParam("--jsx-import-source <STR> Declares the module specifier to be used for importing the jsx and jsxs factory functions. Default: \"react\"") catch unreachable,
+ clap.parseParam("--jsx-runtime <STR> \"automatic\" (default) or \"classic\"") catch unreachable,
+ clap.parseParam("--jsx-production Use jsx instead of jsxDEV (default) for the automatic runtime") catch unreachable,
+ clap.parseParam("--bunfile <STR> Use a .bun file (default: node_modules.bun)") catch unreachable,
+ clap.parseParam("--server-bunfile <STR> Use a .server.bun file (default: node_modules.server.bun)") catch unreachable,
+ clap.parseParam("--production   [work in progress] generate production code") catch unreachable,
+
+ clap.parseParam("<POS>... ") catch unreachable,
+ };
+ };
- var cwd_paths = [_]string{args.option("--cwd") orelse try std.process.getCwdAlloc(allocator)};
- var cwd = try std.fs.path.resolve(allocator, &cwd_paths);
- var tsconfig_override = if (args.option("--tsconfig-override")) |ts| (Arguments.readFile(allocator, cwd, ts) catch |err| fileReadError(err, stderr, ts, "tsconfig.json")) else null;
- var origin = args.option("--origin");
- var defines_tuple = try DefineColonList.resolve(allocator, args.options("--define"));
- var loader_tuple = try LoaderColonList.resolve(allocator, args.options("--define"));
-
- var define_keys = defines_tuple.keys;
- var define_values = defines_tuple.values;
- var loader_keys = loader_tuple.keys;
- var loader_values = loader_tuple.values;
- var entry_points = args.positionals();
- var inject = args.options("--inject");
- var output_dir = args.option("--outdir");
- const serve = args.flag("--serve");
-
- const production = args.flag("--production");
-
- var write = entry_points.len > 1 or output_dir != null;
- if (write and output_dir == null) {
- var _paths = [_]string{ cwd, "out" };
- output_dir = try std.fs.path.resolve(allocator, &_paths);
- }
- var externals = std.mem.zeroes([][]u8);
- if (args.options("--external").len > 0) {
- externals = try allocator.alloc([]u8, args.options("--external").len);
- for (args.options("--external")) |external, i| {
- externals[i] = constStrToU8(external);
- }
- }
+ pub fn parse(allocator: *std.mem.Allocator, comptime cmd: Command.Tag) !Api.TransformOptions {
+ var diag = clap.Diagnostic{};
- var jsx_factory = args.option("--jsx-factory");
- var jsx_fragment = args.option("--jsx-fragment");
- var jsx_import_source = args.option("--jsx-import-source");
- var jsx_runtime = args.option("--jsx-runtime");
- var jsx_production = args.flag("--jsx-production") or production;
- var react_fast_refresh = false;
+ var args = clap.parse(clap.Help, &params, .{ .diagnostic = &diag }) catch |err| {
+ // Report useful error and exit
+ diag.report(Output.errorWriter(), err) catch {};
+ return err;
+ };
- var framework_entry_point = args.option("--use");
+ var cwd_paths = [_]string{args.option("--cwd") orelse try std.process.getCwdAlloc(allocator)};
+ var cwd = try std.fs.path.resolve(allocator, &cwd_paths);
- if (serve or args.flag("--new-jsb")) {
- react_fast_refresh = true;
- if (args.flag("--disable-react-fast-refresh") or jsx_production) {
- react_fast_refresh = false;
- }
+ var defines_tuple = try DefineColonList.resolve(allocator, args.options("--define"));
+ var loader_tuple = try LoaderColonList.resolve(allocator, args.options("--define"));
+ var externals = std.mem.zeroes([][]u8);
+ if (args.options("--external").len > 0) {
+ externals = try allocator.alloc([]u8, args.options("--external").len);
+ for (args.options("--external")) |external, i| {
+ externals[i] = constStrToU8(external);
}
+ }
- var main_fields = args.options("--main-fields");
-
- var node_modules_bundle_path = args.option("--jsb") orelse brk: {
- if (args.flag("--new-jsb")) {
- break :brk null;
- }
+ var opts = Api.TransformOptions{
+ .tsconfig_override = if (args.option("--tsconfig-override")) |ts| (Arguments.readFile(allocator, cwd, ts) catch |err| fileReadError(err, Output.errorStream(), ts, "tsconfig.json")) else null,
+ .external = externals,
+ .absolute_working_dir = cwd,
+ .origin = args.option("--origin"),
+ .define = .{
+ .keys = defines_tuple.keys,
+ .values = defines_tuple.values,
+ },
+ .loaders = .{
+ .extensions = loader_tuple.keys,
+ .loaders = loader_tuple.values,
+ },
- const node_modules_bundle_path_absolute = resolve_path.joinAbs(cwd, .auto, "node_modules.jsb");
+ .serve = cmd == .DevCommand,
+ .main_fields = args.options("--main-fields"),
+ .generate_node_module_bundle = cmd == .BunCommand,
+ .inject = args.options("--inject"),
+ .extension_order = args.options("--extension-order"),
+ .entry_points = undefined,
+ };
- break :brk std.fs.realpathAlloc(allocator, node_modules_bundle_path_absolute) catch null;
- };
+ const print_help = args.flag("--help");
+ if (print_help) {
+ clap.help(Output.errorWriter(), &params) catch {};
+ std.os.exit(0);
+ }
- var node_modules_bundle_path_server = args.option("--jsb-for-server") orelse brk: {
- if (args.flag("--new-jsb")) {
- break :brk null;
+ var output_dir = args.option("--outdir");
+
+ var define_keys = defines_tuple.keys;
+ var define_values = defines_tuple.values;
+ var loader_keys = loader_tuple.keys;
+ var loader_values = loader_tuple.values;
+ var entry_points = args.positionals();
+
+ switch (comptime cmd) {
+ .BunCommand => {
+ if (entry_points.len > 0 and (strings.eqlComptime(
+ entry_points[0],
+ "bun",
+ ))) {
+ entry_points = entry_points[1..];
}
+ },
+ .DevCommand => {
+ if (entry_points.len > 0 and (strings.eqlComptime(
+ entry_points[0],
+ "dev",
+ ) or strings.eqlComptime(
+ entry_points[0],
+ "d",
+ ))) {
+ entry_points = entry_points[1..];
+ }
+ },
+ .BuildCommand => {
+ if (entry_points.len > 0 and (strings.eqlComptime(
+ entry_points[0],
+ "build",
+ ) or strings.eqlComptime(
+ entry_points[0],
+ "b",
+ ))) {
+ entry_points = entry_points[1..];
+ }
+ },
+ .RunCommand => {
+ if (entry_points.len > 0 and (strings.eqlComptime(
+ entry_points[0],
+ "run",
+ ) or strings.eqlComptime(
+ entry_points[0],
+ "r",
+ ))) {
+ entry_points = entry_points[1..];
+ }
+ },
+ else => {},
+ }
- const node_modules_bundle_path_absolute = resolve_path.joinAbs(cwd, .auto, "node_modules.server.jsb");
+ const production = args.flag("--production");
- break :brk std.fs.realpathAlloc(allocator, node_modules_bundle_path_absolute) catch null;
- };
+ var write = entry_points.len > 1 or output_dir != null;
+ if (write and output_dir == null) {
+ var _paths = [_]string{ cwd, "out" };
+ output_dir = try std.fs.path.resolve(allocator, &_paths);
+ }
+ opts.write = write;
+ opts.entry_points = entry_points;
+
+ var jsx_factory = args.option("--jsx-factory");
+ var jsx_fragment = args.option("--jsx-fragment");
+ var jsx_import_source = args.option("--jsx-import-source");
+ var jsx_runtime = args.option("--jsx-runtime");
+ var jsx_production = args.flag("--jsx-production") or production;
+ const react_fast_refresh = switch (comptime cmd) {
+ .BunCommand, .DevCommand => !(args.flag("--disable-react-fast-refresh") or jsx_production),
+ else => true,
+ };
- if (args.flag("--new-jsb")) {
- node_modules_bundle_path = null;
- node_modules_bundle_path_server = null;
- }
+ opts.node_modules_bundle_path = args.option("--bunfile") orelse brk: {
+ const node_modules_bundle_path_absolute = resolve_path.joinAbs(cwd, .auto, "node_modules.bun");
- var route_config: ?Api.RouteConfig = null;
- if (args.option("--static-dir")) |public_dir| {
- route_config = route_config orelse Api.RouteConfig{ .extensions = &.{}, .dir = &.{} };
+ break :brk std.fs.realpathAlloc(allocator, node_modules_bundle_path_absolute) catch null;
+ };
- route_config.?.static_dir = public_dir;
- }
+ opts.node_modules_bundle_path_server = args.option("--server-bunfile") orelse brk: {
+ const node_modules_bundle_path_absolute = resolve_path.joinAbs(cwd, .auto, "node_modules.server.bun");
- const PlatformMatcher = strings.ExactSizeMatcher(8);
- const ResoveMatcher = strings.ExactSizeMatcher(8);
-
- var resolve = Api.ResolveMode.lazy;
- if (args.option("--resolve")) |_resolve| {
- switch (PlatformMatcher.match(_resolve)) {
- PlatformMatcher.case("disable") => {
- resolve = Api.ResolveMode.disable;
- },
- PlatformMatcher.case("bundle") => {
- resolve = Api.ResolveMode.bundle;
- },
- PlatformMatcher.case("dev") => {
- resolve = Api.ResolveMode.dev;
- },
- PlatformMatcher.case("lazy") => {
- resolve = Api.ResolveMode.lazy;
- },
- else => {
- diag.name.long = "--resolve";
- diag.arg = _resolve;
- try diag.report(stderr.writer(), error.InvalidResolveOption);
- std.process.exit(1);
- },
- }
- }
+ break :brk std.fs.realpathAlloc(allocator, node_modules_bundle_path_absolute) catch null;
+ };
- var platform: ?Api.Platform = null;
-
- if (args.option("--platform")) |_platform| {
- switch (PlatformMatcher.match(_platform)) {
- PlatformMatcher.case("browser") => {
- platform = Api.Platform.browser;
- },
- PlatformMatcher.case("node") => {
- platform = Api.Platform.node;
- },
- else => {
- diag.name.long = "--platform";
- diag.arg = _platform;
- try diag.report(stderr.writer(), error.InvalidPlatform);
- std.process.exit(1);
- },
+ switch (comptime cmd) {
+ .DevCommand, .BuildCommand => {
+ if (args.option("--static-dir")) |public_dir| {
+ opts.router = Api.RouteConfig{ .extensions = &.{}, .dir = &.{}, .static_dir = public_dir };
}
- }
+ },
+ else => {},
+ }
- var jsx: ?Api.Jsx = null;
- if (jsx_factory != null or
- jsx_fragment != null or
- jsx_import_source != null or
- jsx_runtime != null or
- jsx_production or react_fast_refresh)
- {
- var default_factory = "".*;
- var default_fragment = "".*;
- var default_import_source = "".*;
- jsx = Api.Jsx{
- .factory = constStrToU8(jsx_factory orelse &default_factory),
- .fragment = constStrToU8(jsx_fragment orelse &default_fragment),
- .import_source = constStrToU8(jsx_import_source orelse &default_import_source),
- .runtime = if (jsx_runtime != null) try resolve_jsx_runtime(jsx_runtime.?) else Api.JsxRuntime.automatic,
- .development = !jsx_production,
- .react_fast_refresh = react_fast_refresh,
- };
- }
+ const ResolveMatcher = strings.ExactSizeMatcher(8);
- var javascript_framework: ?Api.FrameworkConfig = null;
+ opts.resolve = Api.ResolveMode.lazy;
- if (framework_entry_point) |entry| {
- javascript_framework = Api.FrameworkConfig{
- .package = entry,
- .development = !production,
- };
- }
+ switch (comptime cmd) {
+ .BuildCommand => {
+ if (args.option("--resolve")) |_resolve| {
+ switch (ResolveMatcher.match(_resolve)) {
+ ResolveMatcher.case("disable") => {
+ opts.resolve = Api.ResolveMode.disable;
+ },
+ ResolveMatcher.case("bundle") => {
+ opts.resolve = Api.ResolveMode.bundle;
+ },
+ ResolveMatcher.case("dev") => {
+ opts.resolve = Api.ResolveMode.dev;
+ },
+ ResolveMatcher.case("lazy") => {
+ opts.resolve = Api.ResolveMode.lazy;
+ },
+ else => {
+ diag.name.long = "--resolve";
+ diag.arg = _resolve;
+ try diag.report(Output.errorWriter(), error.InvalidResolveOption);
+ std.process.exit(1);
+ },
+ }
+ }
+ },
+ else => {},
+ }
- if (entry_points.len == 0 and javascript_framework == null and node_modules_bundle_path == null) {
- try clap.help(stderr.writer(), &params);
- try diag.report(stderr.writer(), error.MissingEntryPoint);
- std.process.exit(1);
- }
+ const PlatformMatcher = strings.ExactSizeMatcher(8);
- return Api.TransformOptions{
- .jsx = jsx,
- .output_dir = output_dir,
- .resolve = resolve,
- .external = externals,
- .absolute_working_dir = cwd,
- .tsconfig_override = tsconfig_override,
- .origin = origin,
- .define = .{
- .keys = define_keys,
- .values = define_values,
+ if (args.option("--platform")) |_platform| {
+ switch (PlatformMatcher.match(_platform)) {
+ PlatformMatcher.case("browser") => {
+ opts.platform = Api.Platform.browser;
},
- .loaders = .{
- .extensions = loader_keys,
- .loaders = loader_values,
+ PlatformMatcher.case("node") => {
+ opts.platform = Api.Platform.node;
},
- .node_modules_bundle_path = node_modules_bundle_path,
- .node_modules_bundle_path_server = node_modules_bundle_path_server,
- .write = write,
- .router = route_config,
- .serve = serve,
- .inject = inject,
- .entry_points = entry_points,
- .extension_order = args.options("--extension-order"),
- .main_fields = args.options("--main-fields"),
- .platform = platform,
- .only_scan_dependencies = if (args.flag("--scan")) Api.ScanDependencyMode.all else Api.ScanDependencyMode._none,
- .generate_node_module_bundle = if (args.flag("--new-jsb")) true else false,
- .framework = javascript_framework,
- };
- }
- };
- pub fn resolve_jsx_runtime(str: string) !Api.JsxRuntime {
- if (strings.eql(str, "automatic")) {
- return Api.JsxRuntime.automatic;
- } else if (strings.eql(str, "fallback")) {
- return Api.JsxRuntime.classic;
- } else {
- return error.InvalidJSXRuntime;
+ else => {
+ diag.name.long = "--platform";
+ diag.arg = _platform;
+ try diag.report(Output.errorWriter(), error.InvalidPlatform);
+ std.process.exit(1);
+ },
+ }
}
- }
- pub fn printScanResults(scan_results: bundler.ScanResult.Summary, allocator: *std.mem.Allocator) !void {
- var stdout = std.io.getStdOut();
- const print_start = std.time.nanoTimestamp();
- try std.json.stringify(scan_results.list(), .{}, stdout.writer());
- Output.printError("\nJSON printing took: {d}\n", .{std.time.nanoTimestamp() - print_start});
- }
- var wait_group: sync.WaitGroup = undefined;
- pub fn startTransform(allocator: *std.mem.Allocator, args: Api.TransformOptions, log: *logger.Log) anyerror!void {}
- pub fn start(allocator: *std.mem.Allocator, stdout: anytype, stderr: anytype, comptime MainPanicHandler: type) anyerror!void {
- const start_time = std.time.nanoTimestamp();
- var log = logger.Log.init(allocator);
- var panicker = MainPanicHandler.init(&log);
- MainPanicHandler.Singleton = &panicker;
-
- var args = try Arguments.parse(alloc.static, stdout, stderr);
- if ((args.entry_points.len == 1 and args.entry_points[0].len > ".jsb".len and args.entry_points[0][args.entry_points[0].len - ".jsb".len] == '.' and strings.eqlComptime(args.entry_points[0][args.entry_points[0].len - "jsb".len ..], "jsb"))) {
- var out_buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;
- var input = try std.fs.openFileAbsolute(try std.os.realpath(args.entry_points[0], &out_buffer), .{ .read = true });
- const params = comptime [_]clap.Param(clap.Help){
- clap.parseParam("--summary Print a summary") catch unreachable,
- clap.parseParam("<POS>... ") catch unreachable,
+ if (jsx_factory != null or
+ jsx_fragment != null or
+ jsx_import_source != null or
+ jsx_runtime != null or
+ jsx_production or react_fast_refresh)
+ {
+ var default_factory = "".*;
+ var default_fragment = "".*;
+ var default_import_source = "".*;
+ opts.jsx = Api.Jsx{
+ .factory = constStrToU8(jsx_factory orelse &default_factory),
+ .fragment = constStrToU8(jsx_fragment orelse &default_fragment),
+ .import_source = constStrToU8(jsx_import_source orelse &default_import_source),
+ .runtime = if (jsx_runtime != null) try resolve_jsx_runtime(jsx_runtime.?) else Api.JsxRuntime.automatic,
+ .development = !jsx_production,
+ .react_fast_refresh = react_fast_refresh,
};
+ }
- var jsBundleArgs = clap.parse(clap.Help, &params, .{ .allocator = allocator }) catch |err| {
- try NodeModuleBundle.printBundle(std.fs.File, input, @TypeOf(stdout), stdout);
- return;
+ if (args.option("--use")) |entry| {
+ opts.framework = Api.FrameworkConfig{
+ .package = entry,
+ .development = !production,
};
+ }
- if (jsBundleArgs.flag("--summary")) {
- try NodeModuleBundle.printSummaryFromDisk(std.fs.File, input, @TypeOf(stdout), stdout, allocator);
- } else {
- try NodeModuleBundle.printBundle(std.fs.File, input, @TypeOf(stdout), stdout);
- }
-
- return;
+ if (entry_points.len == 0 and opts.framework == null and opts.node_modules_bundle_path == null) {
+ return error.MissingEntryPoint;
}
- if (args.serve orelse false) {
- try Server.start(allocator, args);
+ opts.output_dir = output_dir;
+ return opts;
+ }
+};
- return;
- }
+const AutoCommand = struct {
+ pub fn exec(allocator: *std.mem.Allocator) !void {
+ try HelpCommand.execWithReason(allocator, .invalid_command);
+ }
+};
+const InitCommand = struct {
+ pub fn exec(allocator: *std.mem.Allocator) !void {}
+};
+const HelpCommand = struct {
+ pub fn exec(allocator: *std.mem.Allocator) !void {
+ @setCold(true);
+ execWithReason(allocator, .explicit);
+ }
- // if ((args.only_scan_dependencies orelse ._none) == .all) {
- // return try printScanResults(try bundler.Bundler.scanDependencies(allocator, &log, args), allocator);
- // }
+ pub const Reason = enum {
+ explicit,
+ invalid_command,
+ };
+ pub fn execWithReason(allocator: *std.mem.Allocator, comptime reason: Reason) void {
+ @setCold(true);
+ var cwd_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
+ const cwd = std.os.getcwd(&cwd_buf) catch unreachable;
+ const dirname = std.fs.path.basename(cwd);
+ const fmt =
+ \\> <r> <b><white>init<r> Setup Bun in \"{s}\"
+ \\> <r> <b><green>dev <r><d> ./a.ts ./b.jsx<r> Start a Bun Dev Server
+ \\<d>*<r> <b><cyan>build <r><d> ./a.ts ./b.jsx<r> Make JavaScript-like code runnable & bundle CSS
+ \\> <r> <b><magenta>bun <r><d> ./a.ts ./b.jsx<r> Bundle dependencies of input files into a <r><magenta>.bun<r>
+ \\> <r> <green>run <r><d> ./a.ts <r> Run a JavaScript-like file with Bun.js
+ \\> <r> <b><blue>discord<r> Open Bun's Discord server
+ \\> <r> <b><d>help <r> Print this help menu
+ \\
+ ;
+
+ switch (reason) {
+ .explicit => Output.pretty("Bun: a fast bundler & transpiler for web software.\n\n" ++ fmt, .{dirname}),
+ .invalid_command => Output.prettyError("<r><red>Uh-oh<r> not sure what to do with that command.\n\n" ++ fmt, .{dirname}),
+ }
- if ((args.generate_node_module_bundle orelse false)) {
- var log_ = try allocator.create(logger.Log);
- log_.* = log;
+ Output.flush();
- var this_bundler = try bundler.ServeBundler.init(allocator, log_, args, null, null);
- this_bundler.configureLinker();
- var filepath: [*:0]const u8 = "node_modules.jsb";
- var server_bundle_filepath: [*:0]const u8 = "node_modules.server.jsb";
- try this_bundler.configureRouter(true);
+ if (reason == .invalid_command) {
+ std.process.exit(1);
+ }
+ }
+};
- var loaded_route_config: ?Api.LoadedRouteConfig = brk: {
- if (this_bundler.options.routes.routes_enabled) {
- break :brk this_bundler.options.routes.toAPI();
- }
- break :brk null;
- };
- var loaded_framework: ?Api.LoadedFramework = brk: {
- if (this_bundler.options.framework) |*conf| {
- break :brk conf.toAPI(allocator, this_bundler.fs.top_level_dir, true);
- }
- break :brk null;
+pub const Command = struct {
+ pub const Context = struct {
+ start_time: i128,
+ args: Api.TransformOptions = std.mem.zeroes(Api.TransformOptions),
+ log: *logger.Log,
+ allocator: *std.mem.Allocator,
+
+ pub fn create(allocator: *std.mem.Allocator, log: *logger.Log, comptime command: Command.Tag) !Context {
+ return Command.Context{
+ .args = try Arguments.parse(allocator, command),
+ .log = log,
+ .start_time = start_time,
+ .allocator = allocator,
};
- var env_loader = this_bundler.env;
- wait_group = sync.WaitGroup.init();
- var server_bundler_generator_thread: ?std.Thread = null;
- var generated_server = false;
- if (this_bundler.options.framework) |*framework| {
- if (framework.toAPI(allocator, this_bundler.fs.top_level_dir, false)) |_server_conf| {
- const ServerBundleGeneratorThread = struct {
- inline fn _generate(
- logs: *logger.Log,
- env_loader_: *DotEnv.Loader,
- allocator_: *std.mem.Allocator,
- transform_args: Api.TransformOptions,
- _filepath: [*:0]const u8,
- server_conf: Api.LoadedFramework,
- route_conf_: ?Api.LoadedRouteConfig,
- router: ?Router,
- ) !void {
- var server_bundler = try bundler.ServeBundler.init(
- allocator_,
- logs,
- try configureTransformOptionsForBun(allocator_, transform_args),
- null,
- env_loader_,
- );
- server_bundler.configureLinker();
- server_bundler.router = router;
- try server_bundler.configureDefines();
- _ = try bundler.ServeBundler.GenerateNodeModuleBundle.generate(
- &server_bundler,
- allocator_,
- server_conf,
- route_conf_,
- _filepath,
- );
- std.mem.doNotOptimizeAway(&server_bundler);
- }
- pub fn generate(
- logs: *logger.Log,
- env_loader_: *DotEnv.Loader,
- transform_args: Api.TransformOptions,
- _filepath: [*:0]const u8,
- server_conf: Api.LoadedFramework,
- route_conf_: ?Api.LoadedRouteConfig,
- router: ?Router,
- ) void {
- if (FeatureFlags.parallel_jsb) {
- try alloc.setup(std.heap.c_allocator);
- var stdout_ = std.io.getStdOut();
- var stderr_ = std.io.getStdErr();
- var output_source = Output.Source.init(stdout_, stderr_);
- Output.Source.set(&output_source);
-
- Output.enable_ansi_colors = stderr_.isTty();
- }
-
- defer Output.flush();
- defer {
- if (FeatureFlags.parallel_jsb) {
- wait_group.done();
- }
- }
-
- _generate(logs, env_loader_, std.heap.c_allocator, transform_args, _filepath, server_conf, route_conf_, router) catch return;
- }
- };
-
- if (FeatureFlags.parallel_jsb) {
- wait_group.add();
- server_bundler_generator_thread = try std.Thread.spawn(
- .{},
- ServerBundleGeneratorThread.generate,
- .{
- log_,
- env_loader,
- args,
- server_bundle_filepath,
- _server_conf,
- loaded_route_config,
- this_bundler.router,
- },
- );
- generated_server = true;
- } else {
- ServerBundleGeneratorThread.generate(
- log_,
- env_loader,
- args,
- server_bundle_filepath,
- _server_conf,
- loaded_route_config,
- this_bundler.router,
- );
- generated_server = true;
- }
- }
- }
-
- defer {
- if (server_bundler_generator_thread) |thread| {
- thread.join();
- }
- }
+ }
+ };
- {
- // Always generate the client-only bundle
- // we can revisit this decision if people ask
- var node_modules_ = try bundler.ServeBundler.GenerateNodeModuleBundle.generate(
- &this_bundler,
- allocator,
- loaded_framework,
- loaded_route_config,
- filepath,
- );
-
- if (server_bundler_generator_thread) |thread| {
- wait_group.wait();
- }
+ pub fn which(allocator: *std.mem.Allocator) Tag {
+ var args_iter = std.process.args();
+ // first one is the executable name
+ const skipped = args_iter.skip();
- if (node_modules_) |node_modules| {
- if (log_.errors > 0) {
- try log_.print(Output.errorWriter());
- } else {
- var elapsed = @divTrunc(std.time.nanoTimestamp() - start_time, @as(i128, std.time.ns_per_ms));
- var bundle = NodeModuleBundle.init(node_modules, allocator);
- bundle.printSummary();
- const indent = comptime " ";
- Output.prettyln(indent ++ "<d>{d:6}ms elapsed", .{@intCast(u32, elapsed)});
-
- if (generated_server) {
- Output.prettyln(indent ++ "<r>Saved to ./{s}, ./{s}", .{ filepath, server_bundle_filepath });
- } else {
- Output.prettyln(indent ++ "<r>Saved to ./{s}", .{filepath});
- }
-
- try log_.printForLogLevel(Output.errorWriter());
- }
- } else {
- try log_.print(Output.errorWriter());
- }
- }
- return;
+ if (!skipped) {
+ return .AutoCommand;
}
- var result: options.TransformResult = undefined;
- switch (args.resolve orelse Api.ResolveMode.dev) {
- Api.ResolveMode.disable => {
- result = try bundler.Transformer.transform(
- allocator,
- &log,
- args,
- );
- },
- .lazy => {
- result = try bundler.ServeBundler.bundle(
- allocator,
- &log,
- args,
- );
- },
- else => {
- result = try bundler.Bundler.bundle(
- allocator,
- &log,
- args,
- );
- },
- }
- var did_write = false;
- var stderr_writer = stderr.writer();
- var buffered_writer = std.io.bufferedWriter(stderr_writer);
- defer buffered_writer.flush() catch {};
- var writer = buffered_writer.writer();
- var err_writer = writer;
-
- var open_file_limit: usize = 32;
- if (args.write) |write| {
- if (write) {
- const root_dir = result.root_dir orelse unreachable;
- if (std.os.getrlimit(.NOFILE)) |limit| {
- open_file_limit = limit.cur;
- } else |err| {}
-
- var all_paths = try allocator.alloc([]const u8, result.output_files.len);
- var max_path_len: usize = 0;
- var max_padded_size: usize = 0;
- for (result.output_files) |f, i| {
- all_paths[i] = f.input.text;
- }
+ const next_arg = (args_iter.next(allocator) orelse return .AutoCommand) catch unreachable;
- var from_path = resolve_path.longestCommonPath(all_paths);
+ const first_arg_name = std.mem.span(next_arg);
+ const RootCommandMatcher = strings.ExactSizeMatcher(8);
- for (result.output_files) |f, i| {
- max_path_len = std.math.max(
- std.math.max(from_path.len, f.input.text.len) + 2 - from_path.len,
- max_path_len,
- );
- }
+ return switch (RootCommandMatcher.match(first_arg_name)) {
+ RootCommandMatcher.case("init") => .InitCommand,
+ RootCommandMatcher.case("bun") => .BunCommand,
+ RootCommandMatcher.case("discord") => .DiscordCommand,
+
+ RootCommandMatcher.case("b"), RootCommandMatcher.case("build") => .BuildCommand,
+ RootCommandMatcher.case("r"), RootCommandMatcher.case("run") => .RunCommand,
+ RootCommandMatcher.case("d"), RootCommandMatcher.case("dev") => .DevCommand,
- did_write = true;
+ RootCommandMatcher.case("help") => .HelpCommand,
+ else => .AutoCommand,
+ };
+ }
- // On posix, file handles automatically close on process exit by the OS
- // Closing files shows up in profiling.
- // So don't do that unless we actually need to.
- const do_we_need_to_close = !FeatureFlags.store_file_descriptors or (@intCast(usize, root_dir.fd) + open_file_limit) < result.output_files.len;
+ pub fn start(allocator: *std.mem.Allocator, log: *logger.Log) !void {
+ const tag = which(allocator);
+ switch (tag) {
+ .DiscordCommand => return try DiscordCommand.exec(allocator),
+ .HelpCommand => return try HelpCommand.exec(allocator),
+ .InitCommand => return try InitCommand.exec(allocator),
+ else => {},
+ }
- var filepath_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
- filepath_buf[0] = '.';
- filepath_buf[1] = '/';
+ switch (tag) {
+ .BunCommand => {
+ const ctx = try Command.Context.create(allocator, log, .BunCommand);
- for (result.output_files) |f, i| {
- var rel_path: []const u8 = undefined;
- switch (f.value) {
- // easy mode: write the buffer
- .buffer => |value| {
- rel_path = resolve_path.relative(from_path, f.input.text);
+ try BunCommand.exec(ctx);
+ },
+ .DevCommand => {
+ const ctx = try Command.Context.create(allocator, log, .DevCommand);
- try root_dir.writeFile(rel_path, value);
- },
- .move => |value| {
- // const primary = f.input.text[from_path.len..];
- // std.mem.copy(u8, filepath_buf[2..], primary);
- // rel_path = filepath_buf[0 .. primary.len + 2];
- rel_path = value.pathname;
+ try DevCommand.exec(ctx);
+ },
+ .BuildCommand => {
+ const ctx = try Command.Context.create(allocator, log, .BuildCommand);
- // try f.moveTo(result.outbase, constStrToU8(rel_path), root_dir.fd);
- },
- .copy => |value| {
- rel_path = value.pathname;
+ try BuildCommand.exec(ctx);
+ },
+ .RunCommand => {
+ const ctx = try Command.Context.create(allocator, log, .RunCommand);
- try f.copyTo(result.outbase, constStrToU8(rel_path), root_dir.fd);
+ try RunCommand.exec(ctx);
+ },
+ .AutoCommand => {
+ const ctx = Command.Context.create(allocator, log, .AutoCommand) catch |e| {
+ switch (e) {
+ error.MissingEntryPoint => {
+ HelpCommand.execWithReason(allocator, .explicit);
+ return;
},
- .noop => {},
- .pending => |value| {
- unreachable;
+ else => {
+ return e;
},
}
+ };
- // Print summary
- _ = try writer.write("\n");
- const padding_count = 2 + (std.math.max(rel_path.len, max_path_len) - rel_path.len);
- try writer.writeByteNTimes(' ', 2);
- try writer.writeAll(rel_path);
- try writer.writeByteNTimes(' ', padding_count);
- const size = @intToFloat(f64, f.size) / 1000.0;
- try std.fmt.formatFloatDecimal(size, .{ .precision = 2 }, writer);
- try writer.writeAll(" KB\n");
- }
- }
- }
-
- if (isDebug) {
- err_writer.print("\nExpr count: {d}\n", .{js_ast.Expr.icount}) catch {};
- err_writer.print("Stmt count: {d}\n", .{js_ast.Stmt.icount}) catch {};
- err_writer.print("Binding count: {d}\n", .{js_ast.Binding.icount}) catch {};
- err_writer.print("File Descriptors: {d} / {d}\n", .{
- fs.FileSystem.max_fd,
- open_file_limit,
- }) catch {};
- }
-
- for (result.errors) |err| {
- try err.writeFormat(err_writer);
- _ = try err_writer.write("\n");
- }
-
- for (result.warnings) |err| {
- try err.writeFormat(err_writer);
- _ = try err_writer.write("\n");
- }
-
- const duration = std.time.nanoTimestamp() - start_time;
-
- if (did_write and duration < @as(i128, @as(i128, std.time.ns_per_s) * @as(i128, 2))) {
- var elapsed = @divTrunc(duration, @as(i128, std.time.ns_per_ms));
- try err_writer.print("\nCompleted in {d}ms", .{elapsed});
+ try BuildCommand.exec(ctx);
+ },
+ else => unreachable,
}
}
+
+ pub const Tag = enum {
+ InitCommand,
+ BunCommand,
+ DevCommand,
+ DiscordCommand,
+ BuildCommand,
+ RunCommand,
+ AutoCommand,
+ HelpCommand,
+ };
};