aboutsummaryrefslogtreecommitdiff
path: root/src/cli.zig
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2021-05-11 18:39:00 -0700
committerGravatar Jarred Sumner <jarred@jarredsumner.com> 2021-05-11 18:39:00 -0700
commit324784cd6ebe0bd17deef74a982a51941ee5ae25 (patch)
tree30b65daa11568715e633813c98061b5cc482acb4 /src/cli.zig
parent0c951bd012290eecca5eb6a3ccbdb3e3449af0ce (diff)
downloadbun-324784cd6ebe0bd17deef74a982a51941ee5ae25.tar.gz
bun-324784cd6ebe0bd17deef74a982a51941ee5ae25.tar.zst
bun-324784cd6ebe0bd17deef74a982a51941ee5ae25.zip
update
Former-commit-id: a5f1670e92fbe9080a0c1c7c744483933b117fe1
Diffstat (limited to 'src/cli.zig')
-rw-r--r--src/cli.zig306
1 files changed, 306 insertions, 0 deletions
diff --git a/src/cli.zig b/src/cli.zig
new file mode 100644
index 000000000..a650aad75
--- /dev/null
+++ b/src/cli.zig
@@ -0,0 +1,306 @@
+usingnamespace @import("global.zig");
+
+const std = @import("std");
+const lex = @import("js_lexer.zig");
+const logger = @import("logger.zig");
+const alloc = @import("alloc.zig");
+const options = @import("options.zig");
+const js_parser = @import("js_parser.zig");
+const json_parser = @import("json_parser.zig");
+const js_printer = @import("js_printer.zig");
+const js_ast = @import("js_ast.zig");
+const linker = @import("linker.zig");
+usingnamespace @import("ast/base.zig");
+usingnamespace @import("defines.zig");
+const panicky = @import("panic_handler.zig");
+const Api = @import("api/schema.zig").Api;
+
+const clap = @import("clap");
+
+const bundler = @import("bundler.zig");
+
+pub fn constStrToU8(s: string) []u8 {
+ return @intToPtr([*]u8, @ptrToInt(s.ptr))[0..s.len];
+}
+
+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;
+ }
+
+ self.keys[i] = str[0..midpoint];
+ self.values[i] = try value_resolver(str[midpoint + 1 .. str.len]);
+ }
+ }
+
+ 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;
+ },
+ }
+ }
+
+ pub fn noop_resolver(in: string) !string {
+ return in;
+ }
+
+ 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 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 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. Defaults to cwd") catch unreachable,
+ clap.parseParam("--public-url <STR> Rewrite import paths to start with --public-url. Useful for web browsers.") 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("--react-fast-refresh Enable React Fast Refresh (not implemented yet)") 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("<POS>... Entry points to use") catch unreachable,
+ };
+
+ var diag = clap.Diagnostic{};
+
+ var args = clap.parse(clap.Help, &params, .{ .diagnostic = &diag }) catch |err| {
+ // Report useful error and exit
+ diag.report(stderr.writer(), err) catch {};
+ return err;
+ };
+
+ if (args.flag("--help")) {
+ try clap.help(stderr.writer(), &params);
+ std.process.exit(1);
+ }
+
+ 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 public_url = args.option("--public-url");
+ 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 write = entry_points.len > 1;
+ var inject = args.options("--inject");
+ var output_dir = args.option("--outdir");
+ 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);
+ }
+ }
+
+ 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");
+ var react_fast_refresh = args.flag("--react-fast-refresh");
+ var main_fields = args.options("--main-fields");
+
+ comptime const PlatformMatcher = strings.ExactSizeMatcher(8);
+ comptime 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.InvalidPlatform);
+ std.process.exit(1);
+ },
+ }
+ }
+
+ 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);
+ },
+ }
+ }
+
+ 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,
+ };
+ }
+
+ if (entry_points.len == 0) {
+ try clap.help(stderr.writer(), &params);
+ try diag.report(stderr.writer(), error.MissingEntryPoint);
+ std.process.exit(1);
+ }
+
+ return Api.TransformOptions{
+ .jsx = jsx,
+ .output_dir = output_dir,
+ .resolve = resolve,
+ .external = externals,
+ .absolute_working_dir = cwd,
+ .tsconfig_override = tsconfig_override,
+ .public_url = public_url,
+ .define_keys = define_keys,
+ .define_values = define_values,
+ .loader_keys = loader_keys,
+ .loader_values = loader_values,
+ .write = write,
+ .inject = inject,
+ .entry_points = entry_points,
+ .main_fields = args.options("--main-fields"),
+ .platform = platform,
+ };
+ }
+ };
+ 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;
+ }
+ }
+ 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 {
+ var log = logger.Log.init(alloc.dynamic);
+ var panicker = MainPanicHandler.init(&log);
+ MainPanicHandler.Singleton = &panicker;
+
+ const args = try Arguments.parse(alloc.static, stdout, stderr);
+ var result: options.TransformResult = undefined;
+ switch (args.resolve orelse Api.ResolveMode.dev) {
+ Api.ResolveMode.disable => {
+ result = try bundler.Transformer.transform(
+ allocator,
+ &log,
+ args,
+ );
+ },
+ else => {
+ result = try bundler.Bundler.bundle(
+ allocator,
+ &log,
+ args,
+ );
+ },
+ }
+
+ for (result.output_files) |file| {
+ try stdout.writer().writeAll(file.contents);
+ }
+ }
+};