diff --git a/.github/workflows/bun-mac-aarch64.yml b/.github/workflows/bun-mac-aarch64.yml index d247923971..52ceb4f532 100644 --- a/.github/workflows/bun-mac-aarch64.yml +++ b/.github/workflows/bun-mac-aarch64.yml @@ -112,7 +112,7 @@ jobs: run: | brew install sccache ccache rust llvm@$LLVM_VERSION pkg-config coreutils libtool cmake libiconv automake openssl@1.1 ninja gnu-sed pkg-config --force echo "$(brew --prefix ccache)/bin" >> $GITHUB_PATH - echo "$(brew --prefix sccache)/bin" >> $GITHUB_PATH + # echo "$(brew --prefix sccache)/bin" >> $GITHUB_PATH echo "$(brew --prefix coreutils)/libexec/gnubin" >> $GITHUB_PATH echo "$(brew --prefix llvm@$LLVM_VERSION)/bin" >> $GITHUB_PATH brew link --overwrite llvm@$LLVM_VERSION @@ -185,8 +185,8 @@ jobs: HOMEBREW_NO_INSTALL_CLEANUP: 1 run: | brew install sccache ccache rust llvm@$LLVM_VERSION pkg-config coreutils libtool cmake libiconv automake openssl@1.1 ninja gnu-sed pkg-config --force - echo "$(brew --prefix sccache)/bin" >> $GITHUB_PATH echo "$(brew --prefix ccache)/bin" >> $GITHUB_PATH + # echo "$(brew --prefix sccache)/bin" >> $GITHUB_PATH echo "$(brew --prefix coreutils)/libexec/gnubin" >> $GITHUB_PATH echo "$(brew --prefix llvm@$LLVM_VERSION)/bin" >> $GITHUB_PATH brew link --overwrite llvm@$LLVM_VERSION diff --git a/.github/workflows/bun-mac-x64-baseline.yml b/.github/workflows/bun-mac-x64-baseline.yml index 5cc585b22e..61e6ea7f86 100644 --- a/.github/workflows/bun-mac-x64-baseline.yml +++ b/.github/workflows/bun-mac-x64-baseline.yml @@ -124,7 +124,7 @@ jobs: run: | brew install sccache ccache rust llvm@$LLVM_VERSION pkg-config coreutils libtool cmake libiconv automake openssl@1.1 ninja gnu-sed pkg-config --force echo "$(brew --prefix ccache)/bin" >> $GITHUB_PATH - echo "$(brew --prefix sccache)/bin" >> $GITHUB_PATH + # echo "$(brew --prefix sccache)/bin" >> $GITHUB_PATH echo "$(brew --prefix coreutils)/libexec/gnubin" >> $GITHUB_PATH echo "$(brew --prefix llvm@$LLVM_VERSION)/bin" >> $GITHUB_PATH brew link --overwrite llvm@$LLVM_VERSION @@ -197,8 +197,8 @@ jobs: HOMEBREW_NO_INSTALL_CLEANUP: 1 run: | brew install sccache ccache rust llvm@$LLVM_VERSION pkg-config coreutils libtool cmake libiconv automake openssl@1.1 ninja gnu-sed pkg-config --force - echo "$(brew --prefix sccache)/bin" >> $GITHUB_PATH echo "$(brew --prefix ccache)/bin" >> $GITHUB_PATH + # echo "$(brew --prefix sccache)/bin" >> $GITHUB_PATH echo "$(brew --prefix coreutils)/libexec/gnubin" >> $GITHUB_PATH echo "$(brew --prefix llvm@$LLVM_VERSION)/bin" >> $GITHUB_PATH brew link --overwrite llvm@$LLVM_VERSION diff --git a/.github/workflows/bun-mac-x64.yml b/.github/workflows/bun-mac-x64.yml index f8da412ce5..7210b9596c 100644 --- a/.github/workflows/bun-mac-x64.yml +++ b/.github/workflows/bun-mac-x64.yml @@ -122,7 +122,7 @@ jobs: run: | brew install sccache ccache rust llvm@$LLVM_VERSION pkg-config coreutils libtool cmake libiconv automake openssl@1.1 ninja gnu-sed pkg-config --force echo "$(brew --prefix ccache)/bin" >> $GITHUB_PATH - echo "$(brew --prefix sccache)/bin" >> $GITHUB_PATH + # echo "$(brew --prefix sccache)/bin" >> $GITHUB_PATH echo "$(brew --prefix coreutils)/libexec/gnubin" >> $GITHUB_PATH echo "$(brew --prefix llvm@$LLVM_VERSION)/bin" >> $GITHUB_PATH brew link --overwrite llvm@$LLVM_VERSION @@ -195,7 +195,7 @@ jobs: HOMEBREW_NO_INSTALL_CLEANUP: 1 run: | brew install sccache ccache rust llvm@$LLVM_VERSION pkg-config coreutils libtool cmake libiconv automake openssl@1.1 ninja gnu-sed pkg-config --force - echo "$(brew --prefix sccache)/bin" >> $GITHUB_PATH + # echo "$(brew --prefix sccache)/bin" >> $GITHUB_PATH echo "$(brew --prefix ccache)/bin" >> $GITHUB_PATH echo "$(brew --prefix coreutils)/libexec/gnubin" >> $GITHUB_PATH echo "$(brew --prefix llvm@$LLVM_VERSION)/bin" >> $GITHUB_PATH diff --git a/.vscode/launch.json b/.vscode/launch.json index b617320c31..ece9a9fb51 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -124,7 +124,7 @@ "request": "launch", "name": "bun run [file]", "program": "bun-debug", - "args": ["run", "${file}", "${file}"], + "args": ["run", "${file}"], "cwd": "${fileDirname}", "env": { "FORCE_COLOR": "1", diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 86a7ccb72b..ace44c23cc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,7 +18,7 @@ The JavaScript transpiler & module resolver is mostly independent from the runti ## Getting started -Please refer to [Bun's Development Guide](https://bun.sh/docs/project/development) to get your dev environment setup! +Please refer to [Bun's Development Guide](https://bun.sh/docs/project/contributing) to get your dev environment setup! ## Memory management in Bun diff --git a/README.md b/README.md index b30fed5096..8f7fb59e41 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ bun upgrade --canary ## Contributing -Refer to the [Project > Development](https://bun.sh/docs/project/development) guide to start contributing to Bun. +Refer to the [Project > Contributing](https://bun.sh/docs/project/contributing) guide to start contributing to Bun. ## License diff --git a/bun.lockb b/bun.lockb index 855e72d2b8..f87c3c4a5b 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/packages/bun-polyfills/tsconfig.json b/packages/bun-polyfills/tsconfig.json index e901406785..1bf957d972 100644 --- a/packages/bun-polyfills/tsconfig.json +++ b/packages/bun-polyfills/tsconfig.json @@ -15,5 +15,5 @@ "outDir": "dist", "types": ["node"] }, - "include": ["src", "lib", "../bun-types/index.d.ts"], + "include": ["src", "lib", "../bun-types/index.d.ts"] } diff --git a/src/bun.js/bindings/ExposeNodeModuleGlobals.cpp b/src/bun.js/bindings/ExposeNodeModuleGlobals.cpp new file mode 100644 index 0000000000..d8723e2ba9 --- /dev/null +++ b/src/bun.js/bindings/ExposeNodeModuleGlobals.cpp @@ -0,0 +1,105 @@ +// clang-format off +#include "root.h" + +#include +#include +#include + +#include "ZigGlobalObject.h" +#include "InternalModuleRegistry.h" + +#undef assert + +#define FOREACH_EXPOSED_BUILTIN_IMR(v) \ + v(ffi, Bun::InternalModuleRegistry::BunFFI) \ + v(assert, Bun::InternalModuleRegistry::NodeAssert) \ + v(async_hooks, Bun::InternalModuleRegistry::NodeAsyncHooks) \ + v(child_process, Bun::InternalModuleRegistry::NodeChildProcess) \ + v(cluster, Bun::InternalModuleRegistry::NodeCluster) \ + v(dgram, Bun::InternalModuleRegistry::NodeDgram) \ + v(diagnostics_channel, Bun::InternalModuleRegistry::NodeDiagnosticsChannel) \ + v(dns, Bun::InternalModuleRegistry::NodeDNS) \ + v(domain, Bun::InternalModuleRegistry::NodeDomain) \ + v(events, Bun::InternalModuleRegistry::NodeEvents) \ + v(fs, Bun::InternalModuleRegistry::NodeFS) \ + v(http, Bun::InternalModuleRegistry::NodeHttp) \ + v(http2, Bun::InternalModuleRegistry::NodeHttp2) \ + v(https, Bun::InternalModuleRegistry::NodeHttps) \ + v(inspector, Bun::InternalModuleRegistry::NodeInspector) \ + v(net, Bun::InternalModuleRegistry::NodeNet) \ + v(os, Bun::InternalModuleRegistry::NodeOS) \ + v(path, Bun::InternalModuleRegistry::NodePath) \ + v(perf_hooks, Bun::InternalModuleRegistry::NodePerfHooks) \ + v(punycode, Bun::InternalModuleRegistry::NodePunycode) \ + v(querystring, Bun::InternalModuleRegistry::NodeQuerystring) \ + v(readline, Bun::InternalModuleRegistry::NodeReadline) \ + v(stream, Bun::InternalModuleRegistry::NodeStream) \ + v(sys, Bun::InternalModuleRegistry::NodeUtil) \ + v(timers, Bun::InternalModuleRegistry::NodeTimers) \ + v(tls, Bun::InternalModuleRegistry::NodeTLS) \ + v(trace_events, Bun::InternalModuleRegistry::NodeTraceEvents) \ + v(tty, Bun::InternalModuleRegistry::NodeTty) \ + v(url, Bun::InternalModuleRegistry::NodeUrl) \ + v(util, Bun::InternalModuleRegistry::NodeUtil) \ + v(v8, Bun::InternalModuleRegistry::NodeV8) \ + v(vm, Bun::InternalModuleRegistry::NodeVM) \ + v(wasi, Bun::InternalModuleRegistry::NodeWasi) \ + v(sqlite, Bun::InternalModuleRegistry::BunSqlite) \ + v(worker_threads, Bun::InternalModuleRegistry::NodeWorkerThreads) \ + v(zlib, Bun::InternalModuleRegistry::NodeZlib) \ + +#define FOREACH_EXPOSED_BUILTIN_NATIVE(v) \ + v(constants, SyntheticModuleType::NodeConstants) \ + v(string_decoder, SyntheticModuleType::NodeStringDecoder) \ + v(buffer, SyntheticModuleType::NodeBuffer) \ + v(jsc, SyntheticModuleType::BunJSC) \ + +namespace ExposeNodeModuleGlobalGetters { + +#define DECL_GETTER(id, field) \ + JSC_DEFINE_CUSTOM_GETTER(id, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName)) \ + { \ + Zig::GlobalObject* thisObject = JSC::jsCast(lexicalGlobalObject); \ + JSC::VM& vm = thisObject->vm(); \ + return JSC::JSValue::encode(thisObject->internalModuleRegistry()->requireId(thisObject, vm, field)); \ + } +FOREACH_EXPOSED_BUILTIN_IMR(DECL_GETTER) +#undef DECL_GETTER + +#define DECL_GETTER(id, field) \ + JSC_DEFINE_CUSTOM_GETTER(id, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName)) \ + { \ + Zig::GlobalObject* globalObject = jsCast(lexicalGlobalObject); \ + JSC::VM& vm = globalObject->vm(); \ + auto& builtinNames = WebCore::builtinNames(vm); \ + JSC::JSFunction* function = jsCast(globalObject->getDirect(vm, builtinNames.requireNativeModulePrivateName())); \ + JSC::MarkedArgumentBuffer arguments = JSC::MarkedArgumentBuffer(); \ + arguments.append(JSC::jsString(vm, WTF::String(#id##_s))); \ + auto callData = JSC::getCallData(function); \ + return JSC::JSValue::encode(call(globalObject, function, callData, JSC::jsUndefined(), arguments)); \ + } +FOREACH_EXPOSED_BUILTIN_NATIVE(DECL_GETTER) +#undef DECL_GETTER + +} // namespace ExposeNodeModuleGlobalGetters + +extern "C" void Bun__ExposeNodeModuleGlobals(Zig::GlobalObject* globalObject) +{ + + JSC::VM& vm = globalObject->vm(); +#define PUT_CUSTOM_GETTER_SETTER(id, field) \ + globalObject->putDirectCustomAccessor( \ + vm, \ + JSC::Identifier::fromString(vm, #id##_s), \ + JSC::CustomGetterSetter::create( \ + vm, \ + ExposeNodeModuleGlobalGetters::id, \ + nullptr), \ + 0 | JSC::PropertyAttribute::CustomAccessorOrValue \ + ); + + FOREACH_EXPOSED_BUILTIN_IMR(PUT_CUSTOM_GETTER_SETTER) + // FOREACH_EXPOSED_BUILTIN_NATIVE(PUT_CUSTOM_GETTER_SETTER) +#undef PUT_CUSTOM_GETTER_SETTER + +} diff --git a/src/bun.js/bindings/exports.zig b/src/bun.js/bindings/exports.zig index f6fae9160d..99e0bc304b 100644 --- a/src/bun.js/bindings/exports.zig +++ b/src/bun.js/bindings/exports.zig @@ -68,10 +68,6 @@ pub const ZigGlobalObject = extern struct { JSC.markBinding(@src()); @call(.always_inline, Interface.resolve, .{ res, global, specifier, source, query }); } - pub fn fetch(ret: *ErrorableResolvedSource, global: *JSGlobalObject, specifier: *bun.String, source: *bun.String) callconv(.C) void { - JSC.markBinding(@src()); - @call(.always_inline, Interface.fetch, .{ ret, global, specifier, source }); - } pub fn promiseRejectionTracker(global: *JSGlobalObject, promise: *JSPromise, rejection: JSPromiseRejectionOperation) callconv(.C) JSValue { JSC.markBinding(@src()); @@ -92,8 +88,6 @@ pub const ZigGlobalObject = extern struct { .{ .import = import, .resolve = resolve, - .fetch = fetch, - // .@"eval" = eval, .promiseRejectionTracker = promiseRejectionTracker, .reportUncaughtException = reportUncaughtException, .onCrash = onCrash, @@ -105,10 +99,9 @@ pub const ZigGlobalObject = extern struct { comptime { @export(import, .{ .name = Export[0].symbol_name }); @export(resolve, .{ .name = Export[1].symbol_name }); - @export(fetch, .{ .name = Export[2].symbol_name }); - @export(promiseRejectionTracker, .{ .name = Export[3].symbol_name }); - @export(reportUncaughtException, .{ .name = Export[4].symbol_name }); - @export(onCrash, .{ .name = Export[5].symbol_name }); + @export(promiseRejectionTracker, .{ .name = Export[2].symbol_name }); + @export(reportUncaughtException, .{ .name = Export[3].symbol_name }); + @export(onCrash, .{ .name = Export[4].symbol_name }); } }; diff --git a/src/bun.js/javascript.zig b/src/bun.js/javascript.zig index 107396eb7b..bedb08acf6 100644 --- a/src/bun.js/javascript.zig +++ b/src/bun.js/javascript.zig @@ -583,7 +583,7 @@ pub const VirtualMachine = struct { modules: ModuleLoader.AsyncModule.Queue = .{}, aggressive_garbage_collection: GCLevel = GCLevel.none, - parser_arena: ?*@import("root").bun.ArenaAllocator = null, + module_loader: ModuleLoader = .{}, gc_controller: JSC.GarbageCollectionController = .{}, worker: ?*JSC.WebWorker = null, @@ -1176,7 +1176,6 @@ pub const VirtualMachine = struct { .ref_strings_mutex = Lock.init(), .file_blobs = JSC.WebCore.Blob.Store.Map.init(allocator), .standalone_module_graph = opts.graph.?, - .parser_arena = null, }; vm.source_mappings = .{ .map = &vm.saved_source_map_table }; vm.regular_event_loop.tasks = EventLoop.Queue.init( @@ -1285,7 +1284,6 @@ pub const VirtualMachine = struct { .ref_strings = JSC.RefString.Map.init(allocator), .ref_strings_mutex = Lock.init(), .file_blobs = JSC.WebCore.Blob.Store.Map.init(allocator), - .parser_arena = null, }; vm.source_mappings = .{ .map = &vm.saved_source_map_table }; vm.regular_event_loop.tasks = EventLoop.Queue.init( @@ -1422,7 +1420,6 @@ pub const VirtualMachine = struct { .ref_strings = JSC.RefString.Map.init(allocator), .ref_strings_mutex = Lock.init(), .file_blobs = JSC.WebCore.Blob.Store.Map.init(allocator), - .parser_arena = null, .standalone_module_graph = worker.parent.standalone_module_graph, .worker = worker, }; @@ -1558,7 +1555,6 @@ pub const VirtualMachine = struct { _specifier: String, referrer: String, log: *logger.Log, - ret: *ErrorableResolvedSource, comptime flags: FetchFlags, ) anyerror!ResolvedSource { std.debug.assert(VirtualMachine.isLoaded()); @@ -1566,6 +1562,9 @@ pub const VirtualMachine = struct { if (try ModuleLoader.fetchBuiltinModule(jsc_vm, _specifier)) |builtin| { return builtin; } + + var virtual_source: ?*logger.Source = null; + var display_specifier = _specifier.toUTF8(bun.default_allocator); defer display_specifier.deinit(); var specifier_clone = _specifier.toUTF8(bun.default_allocator); @@ -1575,7 +1574,7 @@ pub const VirtualMachine = struct { const referrer_clone = referrer.toUTF8(bun.default_allocator); defer referrer_clone.deinit(); var path = Fs.Path.init(specifier_clone.slice()); - const loader = jsc_vm.bundler.options.loaders.get(path.name.ext) orelse brk: { + var loader = jsc_vm.bundler.options.loaders.get(path.name.ext) orelse brk: { if (strings.eqlLong(specifier, jsc_vm.main, true)) { break :brk options.Loader.js; } @@ -1583,6 +1582,15 @@ pub const VirtualMachine = struct { break :brk options.Loader.file; }; + if (jsc_vm.module_loader.eval_script) |eval_script| { + if (strings.endsWithComptime(specifier, bun.pathLiteral("/[eval]"))) { + virtual_source = eval_script; + loader = .tsx; + } + } + + defer jsc_vm.module_loader.resetArena(jsc_vm); + return try ModuleLoader.transpileSourceCode( jsc_vm, specifier_clone.slice(), @@ -1592,8 +1600,7 @@ pub const VirtualMachine = struct { path, loader, log, - null, - ret, + virtual_source, null, VirtualMachine.source_code_printer.?, globalObject, @@ -1621,7 +1628,6 @@ pub const VirtualMachine = struct { threadlocal var specifier_cache_resolver_buf: [bun.MAX_PATH_BYTES]u8 = undefined; fn _resolve( ret: *ResolveFunctionResult, - _: *JSGlobalObject, specifier: string, source: string, is_esm: bool, @@ -1652,6 +1658,10 @@ pub const VirtualMachine = struct { ret.result = null; ret.path = result.path; return; + } else if (jsc_vm.module_loader.eval_script != null and strings.endsWithComptime(specifier, bun.pathLiteral("/[eval]"))) { + ret.result = null; + ret.path = specifier; + return; } const is_special_source = strings.eqlComptime(source, main_file_name) or js_ast.Macro.isMacroPath(source); @@ -1846,7 +1856,7 @@ pub const VirtualMachine = struct { jsc_vm.bundler.linker.log = old_log; jsc_vm.bundler.resolver.log = old_log; } - _resolve(&result, global, specifier_utf8.slice(), normalizeSource(source_utf8.slice()), is_esm, is_a_file_path) catch |err_| { + _resolve(&result, specifier_utf8.slice(), normalizeSource(source_utf8.slice()), is_esm, is_a_file_path) catch |err_| { var err = err_; const msg: logger.Msg = brk: { var msgs: []logger.Msg = log.msgs.items; @@ -1903,65 +1913,6 @@ pub const VirtualMachine = struct { pub const main_file_name: string = "bun:main"; - pub fn fetch(ret: *ErrorableResolvedSource, global: *JSGlobalObject, specifier: bun.String, source: bun.String) callconv(.C) void { - var jsc_vm: *VirtualMachine = if (comptime Environment.isLinux) - VirtualMachine.get() - else - global.bunVM(); - - var log = logger.Log.init(jsc_vm.bundler.allocator); - - const result = switch (!jsc_vm.bundler.options.disable_transpilation) { - inline else => |is_disabled| fetchWithoutOnLoadPlugins(jsc_vm, global, specifier, source, &log, ret, if (comptime is_disabled) .print_source_and_clone else .transpile) catch |err| { - processFetchLog(global, specifier, source, &log, ret, err); - return; - }, - }; - - if (log.errors > 0) { - processFetchLog(global, specifier, source, &log, ret, error.LinkError); - return; - } - - if (log.warnings > 0) { - var writer = Output.errorWriter(); - if (Output.enable_ansi_colors) { - for (log.msgs.items) |msg| { - if (msg.kind == .warn) { - msg.writeFormat(writer, true) catch {}; - } - } - } else { - for (log.msgs.items) |msg| { - if (msg.kind == .warn) { - msg.writeFormat(writer, false) catch {}; - } - } - } - } - - ret.result.value = result; - var vm = get(); - - if (vm.blobs) |blobs| { - const spec = specifier.toUTF8(bun.default_allocator); - const specifier_blob = brk: { - if (strings.hasPrefix(spec.slice(), VirtualMachine.get().bundler.fs.top_level_dir)) { - break :brk spec.slice()[VirtualMachine.get().bundler.fs.top_level_dir.len..]; - } - break :brk spec.slice(); - }; - - if (vm.has_loaded) { - blobs.temporary.put(specifier_blob, .{ .ptr = result.source_code.byteSlice().ptr, .len = result.source_code.length() }) catch {}; - } else { - blobs.persistent.put(specifier_blob, .{ .ptr = result.source_code.byteSlice().ptr, .len = result.source_code.length() }) catch {}; - } - } - - ret.success = true; - } - pub fn drainMicrotasks(this: *VirtualMachine) void { this.eventLoop().drainMicrotasks(); } @@ -2672,8 +2623,7 @@ pub const VirtualMachine = struct { @max(top.position.column_start, 0), )) |mapping| { var log = logger.Log.init(default_allocator); - var errorable: ErrorableResolvedSource = undefined; - var original_source = fetchWithoutOnLoadPlugins(this, this.global, top.source_url, bun.String.empty, &log, &errorable, .print_source) catch return; + var original_source = fetchWithoutOnLoadPlugins(this, this.global, top.source_url, bun.String.empty, &log, .print_source) catch return; const code = original_source.source_code.toUTF8(bun.default_allocator); defer code.deinit(); diff --git a/src/bun.js/module_loader.zig b/src/bun.js/module_loader.zig index 07c868acb0..1d4754a0b2 100644 --- a/src/bun.js/module_loader.zig +++ b/src/bun.js/module_loader.zig @@ -584,7 +584,23 @@ pub const RuntimeTranspilerStore = struct { }; pub const ModuleLoader = struct { + transpile_source_code_arena: ?*bun.ArenaAllocator = null, + eval_script: ?*logger.Source = null, + const debug = Output.scoped(.ModuleLoader, true); + + /// This must be called after calling transpileSourceCode + pub fn resetArena(this: *ModuleLoader, jsc_vm: *VirtualMachine) void { + std.debug.assert(&jsc_vm.module_loader == this); + if (this.transpile_source_code_arena) |arena| { + if (jsc_vm.smol) { + _ = arena.reset(.free_all); + } else { + _ = arena.reset(.{ .retain_with_limit = 8 * 1024 * 1024 }); + } + } + } + pub const AsyncModule = struct { // This is all the state used by the printer to print the module @@ -1351,7 +1367,6 @@ pub const ModuleLoader = struct { loader: options.Loader, log: *logger.Log, virtual_source: ?*const logger.Source, - ret: *ErrorableResolvedSource, promise_ptr: ?*?*JSC.JSInternalPromise, source_code_printer: *js_printer.BufferPrinter, globalObject: ?*JSC.JSGlobalObject, @@ -1368,16 +1383,17 @@ pub const ModuleLoader = struct { jsc_vm.main_hash == hash and strings.eqlLong(jsc_vm.main, path.text, false); - var arena: ?*bun.ArenaAllocator = brk: { + var arena_: ?*bun.ArenaAllocator = brk: { // Attempt to reuse the Arena from the parser when we can // This code is potentially re-entrant, so only one Arena can be reused at a time // That's why we have to check if the Arena is null // // Using an Arena here is a significant memory optimization when loading many files - if (jsc_vm.parser_arena) |shared| { - jsc_vm.parser_arena = null; + if (jsc_vm.module_loader.transpile_source_code_arena) |shared| { + jsc_vm.module_loader.transpile_source_code_arena = null; break :brk shared; } + // we must allocate the arena so that the pointer it points to is always valid. var arena = try jsc_vm.allocator.create(bun.ArenaAllocator); arena.* = bun.ArenaAllocator.init(bun.default_allocator); @@ -1387,22 +1403,23 @@ pub const ModuleLoader = struct { var give_back_arena = true; defer { if (give_back_arena) { - if (jsc_vm.parser_arena == null) { + if (jsc_vm.module_loader.transpile_source_code_arena == null) { if (jsc_vm.smol) { - _ = arena.?.reset(.free_all); + _ = arena_.?.reset(.free_all); } else { - _ = arena.?.reset(.{ .retain_with_limit = 8 * 1024 * 1024 }); + _ = arena_.?.reset(.{ .retain_with_limit = 8 * 1024 * 1024 }); } - jsc_vm.parser_arena = arena; + jsc_vm.module_loader.transpile_source_code_arena = arena_; } else { - arena.?.deinit(); - jsc_vm.allocator.destroy(arena.?); + arena_.?.deinit(); + jsc_vm.allocator.destroy(arena_.?); } } } - var allocator = arena.?.allocator(); + var arena = arena_.?; + var allocator = arena.allocator(); var fd: ?StoredFileDescriptorType = null; var package_json: ?*PackageJSON = null; @@ -1507,6 +1524,7 @@ pub const ModuleLoader = struct { } } + give_back_arena = false; return error.ParseError; }; }, @@ -1523,7 +1541,6 @@ pub const ModuleLoader = struct { .wasm, log, &parse_result.source, - ret, promise_ptr, source_code_printer, globalObject, @@ -1551,6 +1568,7 @@ pub const ModuleLoader = struct { } if (jsc_vm.bundler.log.errors > 0) { + give_back_arena = false; return error.ParseError; } @@ -1628,10 +1646,9 @@ pub const ModuleLoader = struct { .promise_ptr = promise_ptr, .specifier = specifier, .referrer = referrer, - .arena = arena.?, + .arena = arena, }, ); - arena = null; give_back_arena = false; return error.AsyncModule; } @@ -1814,7 +1831,6 @@ pub const ModuleLoader = struct { .file, log, virtual_source, - ret, promise_ptr, source_code_printer, globalObject, @@ -1949,9 +1965,18 @@ pub const ModuleLoader = struct { ); const path = Fs.Path.init(specifier); + var virtual_source: ?*logger.Source = null; + // Deliberately optional. // The concurrent one only handles javascript-like loaders right now. - const loader: ?options.Loader = jsc_vm.bundler.options.loaders.get(path.name.ext); + var loader: ?options.Loader = jsc_vm.bundler.options.loaders.get(path.name.ext); + + if (jsc_vm.module_loader.eval_script) |eval_script| { + if (strings.endsWithComptime(specifier, bun.pathLiteral("/[eval]"))) { + virtual_source = eval_script; + loader = .tsx; + } + } // We only run the transpiler concurrently when we can. // Today, that's: @@ -1993,6 +2018,8 @@ pub const ModuleLoader = struct { } }; + defer jsc_vm.module_loader.resetArena(jsc_vm); + var promise: ?*JSC.JSInternalPromise = null; ret.* = ErrorableResolvedSource.ok( ModuleLoader.transpileSourceCode( @@ -2004,8 +2031,7 @@ pub const ModuleLoader = struct { path, synchronous_loader, &log, - null, - ret, + virtual_source, if (allow_promise) &promise else null, VirtualMachine.source_code_printer.?, globalObject, @@ -2204,6 +2230,8 @@ pub const ModuleLoader = struct { }; defer log.deinit(); + defer jsc_vm.module_loader.resetArena(jsc_vm); + ret.* = ErrorableResolvedSource.ok( ModuleLoader.transpileSourceCode( jsc_vm, @@ -2215,7 +2243,6 @@ pub const ModuleLoader = struct { loader, &log, &virtual_source, - ret, null, VirtualMachine.source_code_printer.?, globalObject, diff --git a/src/bun_js.zig b/src/bun_js.zig index 6b807cf46e..5698ba474e 100644 --- a/src/bun_js.zig +++ b/src/bun_js.zig @@ -168,6 +168,14 @@ pub const Run = struct { vm.arena = &run.arena; vm.allocator = arena.allocator(); + if (ctx.runtime_options.eval_script.len > 0) { + vm.module_loader.eval_script = ptr: { + var v = try bun.default_allocator.create(logger.Source); + v.* = logger.Source.initPathString(entry_path, ctx.runtime_options.eval_script); + break :ptr v; + }; + } + b.options.install = ctx.install; b.resolver.opts.install = ctx.install; b.resolver.opts.global_cache = ctx.debug.global_cache; @@ -238,11 +246,17 @@ pub const Run = struct { run.any_unhandled = true; } + extern fn Bun__ExposeNodeModuleGlobals(*JSC.JSGlobalObject) void; + pub fn start(this: *Run) void { var vm = this.vm; vm.hot_reload = this.ctx.debug.hot_reload; vm.onUnhandledRejection = &onUnhandledRejectionBeforeClose; + if (this.ctx.runtime_options.eval_script.len > 0) { + Bun__ExposeNodeModuleGlobals(vm.global); + } + switch (this.ctx.debug.hot_reload) { .hot => JSC.HotReloader.enableHotModuleReloading(vm), .watch => JSC.WatchReloader.enableHotModuleReloading(vm), diff --git a/src/cli.zig b/src/cli.zig index 4b9dc74a1a..712ef81bb9 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -31,6 +31,8 @@ const BunJS = @import("./bun_js.zig"); const Install = @import("./install/install.zig"); const bundler = bun.bundler; const DotEnv = @import("./env_loader.zig"); +const RunCommand_ = @import("./cli/run_command.zig").RunCommand; +const CreateCommand_ = @import("./cli/create_command.zig").CreateCommand; const fs = @import("fs.zig"); const Router = @import("./router.zig"); @@ -55,45 +57,7 @@ pub const Cli = struct { MainPanicHandler.Singleton = &panicker; Command.start(allocator, log) catch |err| { switch (err) { - error.MissingEntryPoint => { - Output.prettyErrorln("bun build v" ++ Global.package_json_version_with_sha ++ "", .{}); - Output.prettyErrorln( - \\error: Missing entrypoints. What would you like to bundle? - \\ - \\Usage: bun build [flags] [...entrypoints] - \\ - \\Common Flags: - \\ --outfile Write the output to a specific file (default: stdout) - \\ --outdir Write the output to a directory (required for splitting) - \\ --minify Enable all minification flags - \\ --minify-whitespace Remove unneeded whitespace - \\ --minify-syntax Transform code to use less syntax - \\ --minify-identifiers Shorten variable names - \\ --sourcemap Generate sourcemaps - \\ ("none", "inline", or "external") - \\ --target The intended execution environment for the bundle. - \\ ("browser", "bun" or "node") - \\ --splitting Enable code splitting (requires --outdir) - \\ --watch Run bundler in watch mode - \\ - \\Examples: - \\ Frontend web apps: - \\ bun build ./src/index.ts --outfile=bundle.js - \\ bun build --minify --splitting --outdir=out ./index.jsx ./lib/worker.ts - \\ - \\ Bundle code to be run in Bun (reduces server startup time) - \\ bun build --target=bun ./server.ts --outfile=server.js - \\ - \\ Creating a standalone executable (see https://bun.sh/docs/bundler/executables) - \\ bun build --compile ./cli.ts --outfile=my-app - \\ - \\A full list of flags is available at https://bun.sh/docs/bundler - \\ - , .{}); - Global.exit(1); - }, else => { - // Always dump the logs if (Output.enable_ansi_colors_stderr) { log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), true) catch {}; } else { @@ -164,79 +128,89 @@ pub const Arguments = struct { pub const ParamType = clap.Param(clap.Help); - const shared_public_params = [_]ParamType{ - clap.parseParam("-h, --help Display this help and exit.") catch unreachable, - clap.parseParam("-b, --bun Force a script or package to use Bun's runtime instead of Node.js (via symlinking node)") catch unreachable, - clap.parseParam("--cwd Absolute path to resolve files & entry points from. This just changes the process' cwd.") catch unreachable, - clap.parseParam("-c, --config ? Config file to load Bun from (e.g. -c bunfig.toml") catch unreachable, + const base_params_ = [_]ParamType{ clap.parseParam("--env-file ... Load environment variables from the specified file(s)") catch unreachable, + clap.parseParam("--cwd Absolute path to resolve files & entry points from. This just changes the process' cwd.") catch unreachable, + clap.parseParam("-c, --config ? Specify path to Bun config file. Default $cwd/bunfig.toml") catch unreachable, + clap.parseParam("-h, --help Display this menu and exit") catch unreachable, + clap.parseParam("...") catch unreachable, + }; + + const transpiler_params_ = [_]ParamType{ + clap.parseParam("--main-fields ... Main fields to lookup in package.json. Defaults to --target dependent") catch unreachable, clap.parseParam("--extension-order ... Defaults to: .tsx,.ts,.jsx,.js,.json ") catch unreachable, + clap.parseParam("--tsconfig-override Specify custom tsconfig.json. Default $cwd/tsconfig.json") catch unreachable, + clap.parseParam("-d, --define ... Substitute K:V while parsing, e.g. --define process.env.NODE_ENV:\"development\". Values are parsed as JSON.") catch unreachable, + clap.parseParam("-l, --loader ... Parse files with .ext:loader, e.g. --loader .js:jsx. Valid loaders: js, jsx, ts, tsx, json, toml, text, file, wasm, napi") catch unreachable, + clap.parseParam("--no-macros Disable macros from being executed in the bundler, transpiler and runtime") catch unreachable, clap.parseParam("--jsx-factory Changes the function called when compiling JSX elements using the classic JSX runtime") catch unreachable, clap.parseParam("--jsx-fragment Changes the function called when compiling JSX fragments") catch unreachable, clap.parseParam("--jsx-import-source Declares the module specifier to be used for importing the jsx and jsxs factory functions. Default: \"react\"") catch unreachable, clap.parseParam("--jsx-runtime \"automatic\" (default) or \"classic\"") catch unreachable, - clap.parseParam("-r, --preload ... Import a module before other modules are loaded") catch unreachable, - clap.parseParam("--main-fields ... Main fields to lookup in package.json. Defaults to --target dependent") catch unreachable, - clap.parseParam("--no-summary Don't print a summary (when generating .bun)") catch unreachable, - clap.parseParam("-v, --version Print version and exit") catch unreachable, - clap.parseParam("--revision Print version with revision and exit") catch unreachable, - clap.parseParam("--tsconfig-override Load tsconfig from path instead of cwd/tsconfig.json") catch unreachable, - clap.parseParam("-d, --define ... Substitute K:V while parsing, e.g. --define process.env.NODE_ENV:\"development\". Values are parsed as JSON.") catch unreachable, - clap.parseParam("-e, --external ... Exclude module from transpilation (can use * wildcards). ex: -e react") catch unreachable, - clap.parseParam("-l, --loader ... Parse files with .ext:loader, e.g. --loader .js:jsx. Valid loaders: js, jsx, ts, tsx, json, toml, text, file, wasm, napi") catch unreachable, - clap.parseParam("-u, --origin Rewrite import URLs to start with --origin. Default: \"\"") catch unreachable, - clap.parseParam("-p, --port Port to serve Bun's dev server on. Default: \"3000\"") catch unreachable, - clap.parseParam("--smol Use less memory, but run garbage collection more often") catch unreachable, - clap.parseParam("--minify Minify (experimental)") catch unreachable, - clap.parseParam("--minify-syntax Minify syntax and inline data (experimental)") catch unreachable, - clap.parseParam("--minify-whitespace Minify whitespace (experimental)") catch unreachable, - clap.parseParam("--minify-identifiers Minify identifiers") catch unreachable, - clap.parseParam("--no-macros Disable macros from being executed in the bundler, transpiler and runtime") catch unreachable, - clap.parseParam("--target The intended execution environment for the bundle. \"browser\", \"bun\" or \"node\"") catch unreachable, - clap.parseParam("--inspect ? Activate Bun's Debugger") catch unreachable, - clap.parseParam("--inspect-wait ? Activate Bun's Debugger, wait for a connection before executing") catch unreachable, - clap.parseParam("--inspect-brk ? Activate Bun's Debugger, set breakpoint on first line of code and wait") catch unreachable, - clap.parseParam("--if-present Exit if the entrypoint does not exist") catch unreachable, - clap.parseParam("... ") catch unreachable, }; - - // note: we are keeping --port and --origin as it can be reused for bun - // build and elsewhere - pub const not_bun_dev_flags = [_]ParamType{ - clap.parseParam("--hot Enable auto reload in the Bun runtime, test runner, or bundler") catch unreachable, + const runtime_params_ = [_]ParamType{ clap.parseParam("--watch Automatically restart the process on file change") catch unreachable, + clap.parseParam("--hot Enable auto reload in the Bun runtime, test runner, or bundler") catch unreachable, + clap.parseParam("--smol Use less memory, but run garbage collection more often") catch unreachable, + clap.parseParam("-r, --preload ... Import a module before other modules are loaded") catch unreachable, + clap.parseParam("--inspect ? Activate Bun's debugger") catch unreachable, + clap.parseParam("--inspect-wait ? Activate Bun's debugger, wait for a connection before executing") catch unreachable, + clap.parseParam("--inspect-brk ? Activate Bun's debugger, set breakpoint on first line of code and wait") catch unreachable, + clap.parseParam("--if-present Exit without an error if the entrypoint does not exist") catch unreachable, clap.parseParam("--no-install Disable auto install in the Bun runtime") catch unreachable, - clap.parseParam("-i Automatically install dependencies and use global cache in Bun's runtime, equivalent to --install=fallback") catch unreachable, - clap.parseParam("--install Install dependencies automatically when no node_modules are present, default: \"auto\". \"force\" to ignore node_modules, fallback to install any missing") catch unreachable, + clap.parseParam("--install Configure auto-install behavior. One of \"auto\" (default, auto-installs when no node_modules), \"fallback\" (missing packages only), \"force\" (always).") catch unreachable, + clap.parseParam("-i Auto-install dependencies during execution. Equivalent to --install=fallback.") catch unreachable, + clap.parseParam("-e, --eval Evaluate argument as a script") catch unreachable, clap.parseParam("--prefer-offline Skip staleness checks for packages in the Bun runtime and resolve from disk") catch unreachable, clap.parseParam("--prefer-latest Use the latest matching versions of packages in the Bun runtime, always checking npm") catch unreachable, - clap.parseParam("--silent Don't repeat the command for bun run") catch unreachable, + clap.parseParam("-p, --port Set the default port for Bun.serve") catch unreachable, + clap.parseParam("-u, --origin ") catch unreachable, }; - const public_params = shared_public_params ++ not_bun_dev_flags; - - const debug_params = [_]ParamType{ - clap.parseParam("--dump-environment-variables Dump environment variables from .env and process as JSON and quit. Useful for debugging") catch unreachable, - clap.parseParam("--dump-limits Dump system limits. Useful for debugging") catch unreachable, + const auto_only_params = [_]ParamType{ + // clap.parseParam("--all") catch unreachable, + clap.parseParam("-b, --bun Force a script or package to use Bun's runtime instead of Node.js (via symlinking node)") catch unreachable, + clap.parseParam("--silent Don't print the script command") catch unreachable, + clap.parseParam("-v, --version Print version and exit") catch unreachable, + clap.parseParam("--revision Print version with revision and exit") catch unreachable, }; + const auto_params = auto_only_params ++ runtime_params_ ++ transpiler_params_ ++ base_params_; - pub const params = public_params ++ debug_params; + const run_only_params = [_]ParamType{ + clap.parseParam("--silent Don't print the script command") catch unreachable, + clap.parseParam("-b, --bun Force a script or package to use Bun's runtime instead of Node.js (via symlinking node)") catch unreachable, + }; + pub const run_params = run_only_params ++ runtime_params_ ++ transpiler_params_ ++ base_params_; + + const bunx_commands = [_]ParamType{ + clap.parseParam("--silent Don't print the script command") catch unreachable, + clap.parseParam("-b, --bun Force a script or package to use Bun's runtime instead of Node.js (via symlinking node)") catch unreachable, + }; const build_only_params = [_]ParamType{ - clap.parseParam("--format Specifies the module format to build to. Only esm is supported.") catch unreachable, + clap.parseParam("--format Specifies the module format to build to. Only \"esm\" is supported.") catch unreachable, + clap.parseParam("--target The intended execution environment for the bundle. \"browser\", \"bun\" or \"node\"") catch unreachable, clap.parseParam("--outdir Default to \"dist\" if multiple files") catch unreachable, clap.parseParam("--outfile Write to a file") catch unreachable, + clap.parseParam("--watch Automatically restart the process on file change") catch unreachable, clap.parseParam("--root Root directory used for multiple entry points") catch unreachable, clap.parseParam("--splitting Enable code splitting") catch unreachable, clap.parseParam("--public-path A prefix to be appended to any import paths in bundled code") catch unreachable, clap.parseParam("--sourcemap ? Build with sourcemaps - 'inline', 'external', or 'none'") catch unreachable, + clap.parseParam("-e, --external ... Exclude module from transpilation (can use * wildcards). ex: -e react") catch unreachable, clap.parseParam("--entry-naming Customize entry point filenames. Defaults to \"[dir]/[name].[ext]\"") catch unreachable, clap.parseParam("--chunk-naming Customize chunk filenames. Defaults to \"[name]-[hash].[ext]\"") catch unreachable, clap.parseParam("--asset-naming Customize asset filenames. Defaults to \"[name]-[hash].[ext]\"") catch unreachable, clap.parseParam("--server-components Enable React Server Components (experimental)") catch unreachable, clap.parseParam("--no-bundle Transpile file only, do not bundle") catch unreachable, - clap.parseParam("--compile Generate a standalone Bun executable containing your bundled code") catch unreachable, + clap.parseParam("--compile Generate a standalone Bun executable containing your bundled code") catch unreachable, + clap.parseParam("--minify Enable all minification flags") catch unreachable, + clap.parseParam("--minify-syntax Minify syntax and inline data") catch unreachable, + clap.parseParam("--minify-whitespace Minify whitespace") catch unreachable, + clap.parseParam("--minify-identifiers Minify identifiers") catch unreachable, + clap.parseParam("--dump-environment-variables") catch unreachable, }; + pub const build_params = build_only_params ++ transpiler_params_ ++ base_params_; // TODO: update test completions const test_only_params = [_]ParamType{ @@ -249,10 +223,7 @@ pub const Arguments = struct { clap.parseParam("--bail ? Exit the test suite after failures. If you do not specify a number, it defaults to 1.") catch unreachable, clap.parseParam("-t, --test-name-pattern Run only tests with a name that matches the given regex.") catch unreachable, }; - - const build_params_public = public_params ++ build_only_params; - pub const build_params = build_params_public ++ debug_params; - pub const test_params = params ++ test_only_params; + pub const test_params = test_only_params ++ runtime_params_ ++ transpiler_params_ ++ base_params_; fn printVersionAndExit() noreturn { @setCold(true); @@ -380,29 +351,38 @@ pub const Arguments = struct { pub fn parse(allocator: std.mem.Allocator, ctx: *Command.Context, comptime cmd: Command.Tag) !Api.TransformOptions { var diag = clap.Diagnostic{}; - const params_to_use = comptime cmd.params(); + const params_to_parse = comptime cmd.params(); - var args = clap.parse(clap.Help, params_to_use, .{ + var args = clap.parse(clap.Help, params_to_parse, .{ .diagnostic = &diag, .allocator = allocator, - .stop_after_positional_at = if (cmd == .RunCommand) 2 else if (cmd == .AutoCommand) - 1 - else - 0, + .stop_after_positional_at = switch (cmd) { + .RunCommand => 2, + .AutoCommand => 1, + else => 0, + }, }) catch |err| { // Report useful error and exit - clap.help(Output.errorWriter(), params_to_use) catch {}; - Output.errorWriter().writeAll("\n") catch {}; diag.report(Output.errorWriter(), err) catch {}; + cmd.printHelp(false); Global.exit(1); }; - if (args.flag("--version")) { - printVersionAndExit(); + const print_help = args.flag("--help"); + if (print_help) { + cmd.printHelp(true); + Output.flush(); + Global.exit(0); } - if (args.flag("--revision")) { - printRevisionAndExit(); + if (cmd == .AutoCommand) { + if (args.flag("--version")) { + printVersionAndExit(); + } + + if (args.flag("--revision")) { + printRevisionAndExit(); + } } var cwd: []u8 = undefined; @@ -502,26 +482,11 @@ pub const Arguments = struct { }; } - if (args.options("--external").len > 0) { - var externals = try allocator.alloc([]u8, args.options("--external").len); - for (args.options("--external"), 0..) |external, i| { - externals[i] = constStrToU8(external); - } - opts.external = externals; - } - opts.tsconfig_override = if (args.option("--tsconfig-override")) |ts| (Arguments.readFile(allocator, cwd, ts) catch |err| fileReadError(err, Output.errorStream(), ts, "tsconfig.json")) else null; - if (args.option("--origin")) |origin| { - opts.origin = origin; - } - - if (args.option("--port")) |port_str| { - opts.port = std.fmt.parseInt(u16, port_str, 10) catch return error.InvalidPort; - } opts.serve = false; // TODO opts.main_fields = args.options("--main-fields"); // we never actually supported inject. @@ -531,10 +496,49 @@ pub const Arguments = struct { ctx.passthrough = args.remaining(); - opts.no_summary = args.flag("--no-summary"); - + // runtime commands if (cmd == .AutoCommand or cmd == .RunCommand or cmd == .TestCommand) { const preloads = args.options("--preload"); + + if (args.flag("--hot")) { + ctx.debug.hot_reload = .hot; + } else if (args.flag("--watch")) { + ctx.debug.hot_reload = .watch; + bun.auto_reload_on_crash = true; + } + + if (args.option("--origin")) |origin| { + opts.origin = origin; + } + + if (args.option("--port")) |port_str| { + opts.port = std.fmt.parseInt(u16, port_str, 10) catch return error.InvalidPort; + } + + ctx.debug.offline_mode_setting = if (args.flag("--prefer-offline")) + Bunfig.OfflineMode.offline + else if (args.flag("--prefer-latest")) + Bunfig.OfflineMode.latest + else + Bunfig.OfflineMode.online; + + if (args.flag("--no-install")) { + ctx.debug.global_cache = .disable; + } else if (args.flag("-i")) { + ctx.debug.global_cache = .fallback; + } else if (args.option("--install")) |enum_value| { + // -i=auto --install=force, --install=disable + if (options.GlobalCache.Map.get(enum_value)) |result| { + ctx.debug.global_cache = result; + // -i, --install + } else if (enum_value.len == 0) { + ctx.debug.global_cache = options.GlobalCache.force; + } else { + Output.prettyErrorln("Invalid value for --install: \"{s}\". Must be either \"auto\", \"fallback\", \"force\", or \"disable\"\n", .{enum_value}); + Global.exit(1); + } + } + if (ctx.preloads.len > 0 and preloads.len > 0) { var all = std.ArrayList(string).initCapacity(ctx.allocator, ctx.preloads.len + preloads.len) catch unreachable; all.appendSliceAssumeCapacity(ctx.preloads); @@ -544,6 +548,7 @@ pub const Arguments = struct { ctx.preloads = preloads; } + ctx.runtime_options.eval_script = args.option("--eval") orelse ""; ctx.runtime_options.if_present = args.flag("--if-present"); ctx.runtime_options.smol = args.flag("--smol"); if (args.option("--inspect")) |inspect_flag| { @@ -582,29 +587,43 @@ pub const Arguments = struct { opts.origin = try std.fmt.allocPrint(allocator, "http://localhost:{d}/", .{opts.port.?}); } - const print_help = args.flag("--help"); - if (print_help) { - clap.help(Output.writer(), params_to_use[0..params_to_use.len]) catch {}; - Output.prettyln("\n-------\n\n", .{}); - Output.flush(); - HelpCommand.printWithReason(.explicit); - Global.exit(0); - } - - ctx.debug.dump_environment_variables = args.flag("--dump-environment-variables"); - ctx.debug.dump_limits = args.flag("--dump-limits"); - var output_dir: ?string = null; var output_file: ?string = null; - const minify_flag = args.flag("--minify"); - ctx.bundler_options.minify_syntax = minify_flag or args.flag("--minify-syntax"); - ctx.bundler_options.minify_whitespace = minify_flag or args.flag("--minify-whitespace"); - ctx.bundler_options.minify_identifiers = minify_flag or args.flag("--minify-identifiers"); - if (cmd == .BuildCommand) { ctx.bundler_options.transform_only = args.flag("--no-bundle"); + const minify_flag = args.flag("--minify"); + ctx.bundler_options.minify_syntax = minify_flag or args.flag("--minify-syntax"); + ctx.bundler_options.minify_whitespace = minify_flag or args.flag("--minify-whitespace"); + ctx.bundler_options.minify_identifiers = minify_flag or args.flag("--minify-identifiers"); + + if (args.options("--external").len > 0) { + var externals = try allocator.alloc([]u8, args.options("--external").len); + for (args.options("--external"), 0..) |external, i| { + externals[i] = constStrToU8(external); + } + opts.external = externals; + } + + const TargetMatcher = strings.ExactSizeMatcher(8); + if (args.option("--target")) |_target| { + opts.target = opts.target orelse switch (TargetMatcher.match(_target)) { + TargetMatcher.case("browser") => Api.Target.browser, + TargetMatcher.case("node") => Api.Target.node, + TargetMatcher.case("macro") => if (cmd == .BuildCommand) Api.Target.bun_macro else Api.Target.bun, + TargetMatcher.case("bun") => Api.Target.bun, + else => invalidTarget(&diag, _target), + }; + + ctx.debug.run_in_bun = opts.target.? == .bun; + } + + if (args.flag("--watch")) { + ctx.debug.hot_reload = .watch; + bun.auto_reload_on_crash = true; + } + if (args.flag("--compile")) { ctx.bundler_options.compile = true; } @@ -717,57 +736,13 @@ pub const Arguments = struct { var jsx_runtime = args.option("--jsx-runtime"); const react_fast_refresh = true; - if (args.flag("--hot")) { - ctx.debug.hot_reload = .hot; - } else if (args.flag("--watch")) { - ctx.debug.hot_reload = .watch; - bun.auto_reload_on_crash = true; + if (cmd == .AutoCommand or cmd == .RunCommand) { + ctx.debug.silent = args.flag("--silent"); + ctx.debug.run_in_bun = args.flag("--bun") or ctx.debug.run_in_bun; } - ctx.debug.offline_mode_setting = if (args.flag("--prefer-offline")) - Bunfig.OfflineMode.offline - else if (args.flag("--prefer-latest")) - Bunfig.OfflineMode.latest - else - Bunfig.OfflineMode.online; - - if (args.flag("--no-install")) { - ctx.debug.global_cache = .disable; - } else if (args.flag("-i")) { - ctx.debug.global_cache = .fallback; - } else if (args.option("--install")) |enum_value| { - // -i=auto --install=force, --install=disable - if (options.GlobalCache.Map.get(enum_value)) |result| { - ctx.debug.global_cache = result; - // -i, --install - } else if (enum_value.len == 0) { - ctx.debug.global_cache = options.GlobalCache.force; - } else { - Output.prettyErrorln("Invalid value for --install: \"{s}\". Must be either \"auto\", \"fallback\", \"force\", or \"disable\"\n", .{enum_value}); - Global.exit(1); - } - } - - ctx.debug.silent = args.flag("--silent"); - opts.resolve = Api.ResolveMode.lazy; - const TargetMatcher = strings.ExactSizeMatcher(8); - - if (args.option("--target")) |_target| { - opts.target = opts.target orelse switch (TargetMatcher.match(_target)) { - TargetMatcher.case("browser") => Api.Target.browser, - TargetMatcher.case("node") => Api.Target.node, - TargetMatcher.case("macro") => if (cmd == .BuildCommand) Api.Target.bun_macro else Api.Target.bun, - TargetMatcher.case("bun") => Api.Target.bun, - else => invalidTarget(&diag, _target), - }; - - ctx.debug.run_in_bun = opts.target.? == .bun; - } - - ctx.debug.run_in_bun = args.flag("--bun") or ctx.debug.run_in_bun; - if (jsx_factory != null or jsx_fragment != null or jsx_import_source != null or @@ -800,7 +775,13 @@ pub const Arguments = struct { if (cmd == .BuildCommand) { if (opts.entry_points.len == 0 and opts.framework == null) { - return error.MissingEntryPoint; + Output.prettyErrorln("bun build v" ++ Global.package_json_version_with_sha ++ "", .{}); + Output.prettyError("error: Missing entrypoints. What would you like to bundle?\n\n", .{}); + Output.flush(); + Output.pretty("Usage:\n $ bun build \\ [...\\] [...flags] \n", .{}); + Output.pretty("\nTo see full documentation:\n $ bun build --help\n", .{}); + Output.flush(); + Global.exit(1); } } @@ -890,35 +871,41 @@ pub const HelpCommand = struct { "elysia", }; - pub fn printWithReason(comptime reason: Reason) void { - // the spacing between commands here is intentional - const fmt = - \\ run ./my-script.ts Run JavaScript with Bun, a package.json script, or a bin - \\ test Run unit tests with Bun - \\ x {s:<16} Install and execute a package bin (bunx) - \\ repl Start a REPL session with Bun - \\ - \\ init Start an empty Bun project from a blank template - \\ create {s:<16} Create a new project from a template (bun c) - \\ - \\ install Install dependencies for a package.json (bun i) - \\ add {s:<16} Add a dependency to package.json (bun a) - \\ remove {s:<16} Remove a dependency from package.json (bun rm) - \\ update {s:<16} Update outdated dependencies - \\ link Link an npm package globally - \\ unlink Globally unlink an npm package - \\ pm More commands for managing packages - \\ - \\ build ./a.ts ./b.jsx Bundle TypeScript & JavaScript into a single file - \\ - \\ upgrade Get the latest version of Bun - \\ bun --help Show all supported flags and commands - \\ - \\ Learn more about Bun: https://bun.sh/docs - \\ Join our Discord community: https://bun.sh/discord - \\ - ; + // the spacing between commands here is intentional + pub const cli_helptext_fmt = + \\Usage: bun \ [...flags] [...args] + \\ + \\Commands: + \\ run ./my-script.ts Execute a file with Bun + \\ lint Run a package.json script + \\ test Run unit tests with Bun + \\ x {s:<16} Execute a package binary (CLI), installing if needed (bunx) + \\ repl Start a REPL session with Bun + \\ + \\ install Install dependencies for a package.json (bun i) + \\ add {s:<16} Add a dependency to package.json (bun a) + \\ remove {s:<16} Remove a dependency from package.json (bun rm) + \\ update {s:<16} Update outdated dependencies + \\ link [\] Register or link a local npm package + \\ unlink Unregister a local npm package + \\ pm \ Additional package management utilities + \\ + \\ build ./a.ts ./b.jsx Bundle TypeScript & JavaScript into a single file + \\ + \\ init Start an empty Bun project from a blank template + \\ create {s:<16} Create a new project from a template (bun c) + \\ upgrade Upgrade to latest version of Bun. + \\ \ --help Print help text for command. + \\ + ; + const cli_helptext_footer = + \\ + \\Learn more about Bun: https://bun.sh/docs + \\Join our Discord community: https://bun.sh/discord + \\ + ; + pub fn printWithReason(comptime reason: Reason, show_all_flags: bool) void { var rand_state = std.rand.DefaultPrng.init(@as(u64, @intCast(@max(std.time.milliTimestamp(), 0)))); const rand = rand_state.random(); @@ -936,12 +923,25 @@ pub const HelpCommand = struct { }; switch (reason) { - .explicit => Output.pretty( - "Bun: a fast JavaScript runtime, package manager, bundler and test runner. (" ++ Global.package_json_version_with_revision ++ ")\n\n" ++ fmt, - args, - ), + .explicit => { + Output.pretty( + "Bun is a fast JavaScript runtime, package manager, bundler, and test runner. (" ++ + Global.package_json_version_with_sha ++ + ")\n\n" ++ + cli_helptext_fmt, + args, + ); + if (show_all_flags) { + Output.pretty("\nFlags:", .{}); + + const flags = Arguments.runtime_params_ ++ Arguments.auto_only_params ++ Arguments.base_params_; + clap.simpleHelpBunTopLevel(comptime &flags); + Output.pretty("\n\n(more flags in bun install --help, bun test --help, and bun build --help)\n", .{}); + } + Output.pretty(cli_helptext_footer, .{}); + }, .invalid_command => Output.prettyError( - "Uh-oh not sure what to do with that command.\n\n" ++ fmt, + "Uh-oh not sure what to do with that command.\n\n" ++ cli_helptext_fmt, args, ), } @@ -950,7 +950,7 @@ pub const HelpCommand = struct { } pub fn execWithReason(_: std.mem.Allocator, comptime reason: Reason) void { @setCold(true); - printWithReason(reason); + printWithReason(reason, false); if (reason == .invalid_command) { std.process.exit(1); @@ -1030,6 +1030,7 @@ pub const Command = struct { smol: bool = false, debugger: Debugger = .{ .unspecified = {} }, if_present: bool = false, + eval_script: []const u8 = "", }; pub const Context = struct { @@ -1083,7 +1084,6 @@ pub const Command = struct { if (comptime Command.Tag.uses_global_options.get(command)) { ctx.args = try Arguments.parse(allocator, &ctx, command); } - return ctx; } }; @@ -1109,7 +1109,6 @@ pub const Command = struct { pub fn which() Tag { var args_iter = ArgsIterator{ .buf = bun.argv() }; - // first one is the executable name const argv0 = args_iter.next() orelse return .HelpCommand; @@ -1117,13 +1116,18 @@ pub const Command = struct { if (strings.endsWithComptime(argv0, "bunx")) return .BunxCommand; + if (strings.endsWithComptime(std.mem.span(bun.argv()[0]), "node")) { + std.debug.print("", .{}); + @import("./deps/zig-clap/clap/streaming.zig").warn_on_unrecognized_flag = false; + } + if (comptime Environment.isDebug) { if (strings.endsWithComptime(argv0, "bunx-debug")) return .BunxCommand; } var next_arg = ((args_iter.next()) orelse return .AutoCommand); - while (next_arg[0] == '-') { + while (next_arg[0] == '-' and !(next_arg.len > 1 and next_arg[1] == 'e')) { next_arg = ((args_iter.next()) orelse return .AutoCommand); } @@ -1191,6 +1195,8 @@ pub const Command = struct { RootCommandMatcher.case("list") => .ReservedCommand, RootCommandMatcher.case("why") => .ReservedCommand, + RootCommandMatcher.case("-e") => .AutoCommand, + else => .AutoCommand, }; } @@ -1300,16 +1306,11 @@ pub const Command = struct { switch (tag) { .DiscordCommand => return try DiscordCommand.exec(allocator), .HelpCommand => return try HelpCommand.exec(allocator), - .InitCommand => return try InitCommand.exec(allocator, bun.argv()), .ReservedCommand => return try ReservedCommand.exec(allocator), - else => {}, - } - - switch (tag) { + .InitCommand => return try InitCommand.exec(allocator, bun.argv()), .BuildCommand => { if (comptime bun.fast_debug_build_mode and bun.fast_debug_build_cmd != .BuildCommand) unreachable; const ctx = try Command.Context.create(allocator, log, .BuildCommand); - try BuildCommand.exec(ctx); }, .InstallCompletionsCommand => { @@ -1493,22 +1494,28 @@ pub const Command = struct { var args = try std.process.argsAlloc(allocator); if (args.len <= 2) { - Output.prettyErrorln( - \\bun create: create a new project from a template - \\ - \\Usage: bun create [template] [...args] - \\ bun create [username/repo] [name] - \\ - \\If given a GitHub repository name, Bun will download it and use it as a template, - \\otherwise it will run bunx create-[template] with the given arguments. - \\ - \\Learn more about creating new projects: https://bun.sh/docs/templates - \\ - , .{}); + Command.Tag.printHelp(.CreateCommand, false); Global.exit(1); return; } + // iterate over args + // if --help, print help and exit + const print_help = brk: { + for (bun.argv()) |arg_| { + const arg = bun.span(arg_); + if (strings.eqlComptime(arg, "--help")) { + break :brk true; + } + } + break :brk false; + }; + if (print_help) { + Command.Tag.printHelp(.CreateCommand, true); + Global.exit(0); + return; + } + var template_name_start: usize = 0; var positionals: [2]string = undefined; @@ -1540,11 +1547,11 @@ pub const Command = struct { \\ \\To create a project using Create React App, run \\ - \\ bun create react-app + \\ bun create react-app \\ \\To create a React project using Vite, run \\ - \\ bun create vite + \\ bun create vite \\ \\Then select "React" from the list of frameworks. \\ @@ -1553,6 +1560,18 @@ pub const Command = struct { return; } + // if template_name is "next" + // print message telling user to use "bun create next-app" instead + if (strings.eqlComptime(template_name, "next")) { + Output.prettyErrorln( + \\warn: No template create-next found. + \\To create a project with the official Next.js scaffolding tool, run + \\ bun create next-app [destination] + , .{}); + Global.exit(1); + return; + } + const create_command_info = try CreateCommand.extractInfo(ctx); const template = create_command_info.template; var example_tag = create_command_info.example_tag; @@ -1607,6 +1626,15 @@ pub const Command = struct { } }; + if (ctx.runtime_options.eval_script.len > 0) { + const trigger = bun.pathLiteral("/[eval]"); + var entry_point_buf: [bun.MAX_PATH_BYTES + trigger.len]u8 = undefined; + const cwd = try std.os.getcwd(&entry_point_buf); + @memcpy(entry_point_buf[cwd.len..][0..trigger.len], trigger); + try BunJS.Run.boot(ctx, entry_point_buf[0 .. cwd.len + trigger.len]); + return; + } + const extension: []const u8 = if (ctx.args.entry_points.len > 0) std.fs.path.extension(ctx.args.entry_points[0]) else @@ -1705,9 +1733,14 @@ pub const Command = struct { Global.exit(1); } + // if we get here, the command was not parsed + // or the user just ran `bun` with no arguments + if (ctx.positionals.len > 0) { + Output.prettyWarnln("warn: failed to parse command\n", .{}); + } + Output.flush(); try HelpCommand.exec(allocator); }, - else => unreachable, } } @@ -1802,12 +1835,209 @@ pub const Command = struct { pub fn params(comptime cmd: Tag) []const Arguments.ParamType { return &comptime switch (cmd) { - Command.Tag.BuildCommand => Arguments.build_params, - Command.Tag.TestCommand => Arguments.test_params, - else => Arguments.params, + .AutoCommand => Arguments.auto_params, + .RunCommand => Arguments.run_params, + .BuildCommand => Arguments.build_params, + .TestCommand => Arguments.test_params, + .BunxCommand => Arguments.run_params, + else => Arguments.base_params_ ++ Arguments.runtime_params_ ++ Arguments.transpiler_params_, }; } + pub fn printHelp(comptime cmd: Tag, show_all_flags: bool) void { + switch (cmd) { + + // these commands do not use Context + // .DiscordCommand => return try DiscordCommand.exec(allocator), + // .HelpCommand => return try HelpCommand.exec(allocator), + // .ReservedCommand => return try ReservedCommand.exec(allocator), + + // these commands are implemented in install.zig + // Command.Tag.InstallCommand => {}, + // Command.Tag.AddCommand => {}, + // Command.Tag.RemoveCommand => {}, + // Command.Tag.UpdateCommand => {}, + // Command.Tag.PackageManagerCommand => {}, + // Command.Tag.LinkCommand => {}, + // Command.Tag.UnlinkCommand => {}, + + // fall back to HelpCommand.printWithReason + Command.Tag.AutoCommand => { + HelpCommand.printWithReason(.explicit, show_all_flags); + }, + Command.Tag.RunCommand => { + RunCommand_.printHelp(null); + }, + + .InitCommand => { + const intro_text = + \\Usage: bun init [...flags] [\ ...] + \\ Initialize a Bun project in the current directory. + \\ Creates a package.json, tsconfig.json, and bunfig.toml if they don't exist. + \\ + \\Flags: + \\ --help Print this menu + \\ -y, --yes Accept all default options + \\ + \\Examples: + \\ bun init + \\ bun init --yes + ; + + Output.pretty(intro_text ++ "\n", .{}); + Output.flush(); + }, + + Command.Tag.BunxCommand => { + Output.prettyErrorln( + \\Usage: bunx [...flags] \[@version] [...flags and arguments] + \\Execute an npm package executable (CLI), automatically installing into a global shared cache if not installed in node_modules. + \\ + \\Flags: + \\ --bun Force the command to run with Bun instead of Node.js + \\ + \\Examples: + \\ bunx prisma migrate + \\ bunx prettier foo.js + \\ bunx --bun vite dev foo.js + \\ + , .{}); + }, + Command.Tag.BuildCommand => { + const intro_text = + \\Usage: + \\ Transpile and bundle one more more files. + \\ bun build [...flags] [...entrypoints] + ; + + const outro_text = + \\Examples: + \\ Frontend web apps: + \\ bun build ./src/index.ts --outfile=bundle.js + \\ bun build ./index.jsx ./lib/worker.ts --minify --splitting --outdir=out + \\ + \\ Bundle code to be run in Bun (reduces server startup time) + \\ bun build ./server.ts --target=bun --outfile=server.js + \\ + \\ Creating a standalone executable (see https://bun.sh/docs/bundler/executables) + \\ bun build ./cli.ts --compile --outfile=my-app + \\ + \\A full list of flags is available at https://bun.sh/docs/bundler + \\ + ; + + Output.pretty(intro_text ++ "\n\n", .{}); + Output.flush(); + Output.pretty("Flags:", .{}); + Output.flush(); + clap.simpleHelp(&Arguments.build_only_params); + Output.pretty("\n\n" ++ outro_text, .{}); + Output.flush(); + }, + Command.Tag.TestCommand => { + const intro_text = + \\Usage: bun test [...flags] [\...] + \\ Run all matching test files and print the results to stdout + ; + const outro_text = + \\Examples: + \\ Run all test files + \\ bun test + \\ + \\ Run all test files with "foo" or "bar" in the file name + \\ bun test foo bar + \\ + \\ Run all test files, only including tests whose names includes "baz" + \\ bun test --test-name-pattern baz + \\ + \\Full documentation is available at https://bun.sh/docs/cli/test + \\ + ; + // Output.pretty("\n", .{}); + Output.pretty(intro_text, .{}); + Output.flush(); + Output.pretty("\n\nFlags:", .{}); + Output.flush(); + clap.simpleHelp(&Arguments.test_only_params); + Output.pretty("\n\n", .{}); + Output.pretty(outro_text, .{}); + Output.flush(); + }, + Command.Tag.CreateCommand => { + const intro_text = + \\Usage: + \\ bun create \ [...flags] [dest] + \\ bun create \ [...flags] [dest] + \\ + \\Environment variables: + \\ GITHUB_ACCESS_TOKEN Supply a token to download code from GitHub with a higher rate limit + \\ GITHUB_API_DOMAIN Configure custom/enterprise GitHub domain. Default \"api.github.com\". + \\ NPM_CLIENT Absolute path to the npm client executable + ; + + const outro_text = + \\If given a GitHub repository name, Bun will download it and use it as a template, + \\otherwise it will run bunx create-\ with the given arguments. + \\ + \\Learn more about creating new projects: https://bun.sh/docs/cli/bun-create + \\ + ; + + Output.pretty(intro_text, .{}); + Output.pretty("\n\n", .{}); + Output.pretty(outro_text, .{}); + Output.flush(); + }, + Command.Tag.HelpCommand => { + HelpCommand.printWithReason(.explicit); + }, + Command.Tag.UpgradeCommand => { + const intro_text = + \\Usage: bun upgrade [...flags] + \\ Upgrade Bun + ; + const outro_text = + \\Examples: + \\ Install the latest stable version + \\ bun upgrade + \\ + \\ Install the most recent canary version of Bun + \\ bun upgrade --canary + \\ + \\Full documentation is available at https://bun.sh/docs/installation#upgrading + \\ + ; + Output.pretty(intro_text, .{}); + Output.pretty("\n\n", .{}); + Output.flush(); + Output.pretty(outro_text, .{}); + Output.flush(); + }, + Command.Tag.ReplCommand => { + const intro_text = + \\Usage: bun repl [...flags] + \\ Open a Bun REPL + \\ + ; + + Output.pretty(intro_text, .{}); + Output.flush(); + }, + + Command.Tag.GetCompletionsCommand => { + Output.pretty("Usage: bun getcompletes", .{}); + Output.flush(); + }, + Command.Tag.InstallCompletionsCommand => { + Output.pretty("Usage: bun completions", .{}); + Output.flush(); + }, + else => { + HelpCommand.printWithReason(.explicit); + }, + } + } + pub fn readGlobalConfig(this: Tag) bool { return switch (this) { .BunxCommand, .PackageManagerCommand, .InstallCommand, .AddCommand, .RemoveCommand, .UpdateCommand => true, diff --git a/src/cli/bunx_command.zig b/src/cli/bunx_command.zig index e7f35d10dc..9e7c1567d5 100644 --- a/src/cli/bunx_command.zig +++ b/src/cli/bunx_command.zig @@ -9,7 +9,7 @@ const stringZ = bun.stringZ; const default_allocator = bun.default_allocator; const C = bun.C; const std = @import("std"); - +const Command = @import("../cli.zig").Command; const Run = @import("./run_command.zig").RunCommand; pub const BunxCommand = struct { @@ -142,21 +142,7 @@ pub const BunxCommand = struct { } fn exit_with_usage() noreturn { - Output.prettyErrorln( - \\usage: bunx [--bun] package[@version] [...flags or arguments to pass through] - \\ - \\bunx runs an npm package executable, automatically installing into a global shared cache if not installed in node_modules. - \\ - \\example: - \\ - \\ bunx bun-repl - \\ bunx prettier foo.js - \\ - \\The --bun flag forces the package to run in Bun's JavaScript runtime, even when it tries to use Node.js. - \\ - \\ bunx --bun tsc --version - \\ - , .{}); + Command.Tag.printHelp(.BunxCommand, false); Global.exit(1); } diff --git a/src/cli/create_command.zig b/src/cli/create_command.zig index cac4d296de..dfdc14145c 100644 --- a/src/cli/create_command.zig +++ b/src/cli/create_command.zig @@ -198,7 +198,7 @@ const CreateOptions = struct { clap.parseParam("... ") catch unreachable, }; - pub fn parse(ctx: Command.Context, comptime print_flags_only: bool) !CreateOptions { + pub fn parse(ctx: Command.Context) !CreateOptions { var diag = clap.Diagnostic{}; var args = clap.parse(clap.Help, ¶ms, .{ .diagnostic = &diag, .allocator = ctx.allocator }) catch |err| { @@ -207,25 +207,6 @@ const CreateOptions = struct { return err; }; - if (args.flag("--help") or comptime print_flags_only) { - if (comptime print_flags_only) { - clap.help(Output.writer(), params[1..]) catch {}; - return undefined; - } - - Output.prettyln("bun create\n\n flags:\n", .{}); - Output.flush(); - clap.help(Output.writer(), params[1..]) catch {}; - Output.pretty("\n", .{}); - Output.prettyln(" environment variables:\n\n", .{}); - Output.prettyln(" GITHUB_ACCESS_TOKEN Downloading code from GitHub with a higher rate limit", .{}); - Output.prettyln(" GITHUB_API_DOMAIN Change \"api.github.com\", useful for GitHub Enterprise\n", .{}); - Output.prettyln(" NPM_CLIENT Absolute path to the npm client executable", .{}); - Output.flush(); - - Global.exit(0); - } - var opts = CreateOptions{ .positionals = args.positionals() }; if (opts.positionals.len >= 1 and (strings.eqlComptime(opts.positionals[0], "c") or strings.eqlComptime(opts.positionals[0], "create"))) { @@ -253,7 +234,7 @@ pub const CreateCommand = struct { Global.configureAllocator(.{ .long_running = false }); try HTTP.HTTPThread.init(); - var create_options = try CreateOptions.parse(ctx, false); + var create_options = try CreateOptions.parse(ctx); const positionals = create_options.positionals; if (positionals.len == 0) { @@ -1584,7 +1565,7 @@ pub const CreateCommand = struct { var example_tag = Example.Tag.unknown; var filesystem = try fs.FileSystem.init(null); - var create_options = try CreateOptions.parse(ctx, false); + var create_options = try CreateOptions.parse(ctx); const positionals = create_options.positionals; var env_loader: DotEnv.Loader = brk: { diff --git a/src/cli/init_command.zig b/src/cli/init_command.zig index bc0aad069c..b64438b1d7 100644 --- a/src/cli/init_command.zig +++ b/src/cli/init_command.zig @@ -87,6 +87,21 @@ pub const InitCommand = struct { }; pub fn exec(alloc: std.mem.Allocator, argv: [][*:0]u8) !void { + const print_help = brk: { + for (argv) |arg_| { + const arg = bun.span(arg_); + if (strings.eqlComptime(arg, "--help")) { + break :brk true; + } + } + break :brk false; + }; + + if (print_help) { + CLI.Command.Tag.printHelp(.InitCommand, true); + Global.exit(0); + } + var fs = try Fs.FileSystem.init(null); const pathname = Fs.PathName.init(fs.topLevelDirWithoutTrailingSlash()); const destination_dir = std.fs.cwd(); diff --git a/src/cli/package_manager_command.zig b/src/cli/package_manager_command.zig index 91f33f1273..c35103a96d 100644 --- a/src/cli/package_manager_command.zig +++ b/src/cli/package_manager_command.zig @@ -44,7 +44,6 @@ const ByName = struct { }; pub const PackageManagerCommand = struct { - pub fn printHelp(_: std.mem.Allocator) void {} pub fn printHash(ctx: Command.Context, lockfile_: []const u8) !void { @setCold(true); var lockfile_buffer: [bun.MAX_PATH_BYTES]u8 = undefined; @@ -83,6 +82,26 @@ pub const PackageManagerCommand = struct { return subcommand; } + pub fn printHelp() void { + Output.prettyln( + \\bun pm: Package manager utilities + \\ + \\ bun pm bin print the path to bin folder + \\ bun pm -g bin print the global path to bin folder + \\ bun pm ls list the dependency tree according to the current lockfile + \\ bun pm ls --all list the entire dependency tree according to the current lockfile + \\ bun pm hash generate & print the hash of the current lockfile + \\ bun pm hash-string print the string used to hash the lockfile + \\ bun pm hash-print print the hash stored in the current lockfile + \\ bun pm cache print the path to the cache folder + \\ bun pm cache rm clear the cache + \\ bun pm migrate migrate another package manager's lockfile without installing anything + \\ + \\Learn more about these at https://bun.sh/docs/cli/pm + \\ + , .{}); + } + pub fn exec(ctx: Command.Context) !void { var args = try std.process.argsAlloc(ctx.allocator); args = args[1..]; @@ -272,23 +291,7 @@ pub const PackageManagerCommand = struct { Global.exit(0); } - Output.prettyln( - \\bun pm: package manager related commands - \\ - \\ bun pm bin print the path to bin folder - \\ bun pm -g bin print the global path to bin folder - \\ bun pm ls list the dependency tree according to the current lockfile - \\ bun pm ls --all list the entire dependency tree according to the current lockfile - \\ bun pm hash generate & print the hash of the current lockfile - \\ bun pm hash-string print the string used to hash the lockfile - \\ bun pm hash-print print the hash stored in the current lockfile - \\ bun pm cache print the path to the cache folder - \\ bun pm cache rm clear the cache - \\ bun pm migrate migrate another package manager's lockfile without installing anything - \\ - \\Learn more about these at https://bun.sh/docs/cli/pm - \\ - , .{}); + printHelp(); if (subcommand.len > 0) { Output.prettyErrorln("\nerror: \"{s}\" unknown command\n", .{subcommand}); diff --git a/src/cli/run_command.zig b/src/cli/run_command.zig index 9b855156ef..fbd17bbf50 100644 --- a/src/cli/run_command.zig +++ b/src/cli/run_command.zig @@ -12,6 +12,8 @@ const std = @import("std"); const lex = bun.js_lexer; const logger = @import("root").bun.logger; +const clap = @import("root").bun.clap; +const Arguments = @import("../cli.zig").Arguments; const options = @import("../options.zig"); const js_parser = bun.js_parser; @@ -37,7 +39,7 @@ const NpmArgs = struct { pub const package_name: string = "npm_package_name"; pub const package_version: string = "npm_package_version"; }; - +const PackageJSON = @import("../resolver/package_json.zig").PackageJSON; const yarn_commands: []u64 = @import("./list-of-yarn-commands.zig").all_yarn_commands; const ShellCompletions = @import("./shell_completions.zig"); @@ -871,6 +873,66 @@ pub const RunCommand = struct { return shell_out; } + pub fn printHelp(package_json: ?*PackageJSON) void { + const intro_text = + \\Usage: bun run [flags] \ + ; + + const outro_text = + \\Examples: + \\ Run a JavaScript or TypeScript file + \\ bun run ./index.js + \\ bun run ./index.tsx + \\ + \\ Run a package.json script + \\ bun run dev + \\ bun run lint + \\ + \\Full documentation is available at https://bun.sh/docs/cli/run + \\ + ; + + Output.pretty(intro_text ++ "\n\n", .{}); + Output.flush(); + Output.pretty("Flags:", .{}); + Output.flush(); + clap.simpleHelp(&Arguments.run_params); + + if (package_json) |pkg| { + if (pkg.scripts) |scripts| { + var display_name = pkg.name; + + if (display_name.len == 0) { + display_name = std.fs.path.basename(pkg.source.path.name.dir); + } + + var iterator = scripts.iterator(); + + if (scripts.count() > 0) { + Output.pretty("\n\npackage.json scripts ({d} found):", .{scripts.count()}); + // Output.prettyln("{s} scripts:\n", .{display_name}); + while (iterator.next()) |entry| { + Output.prettyln("\n", .{}); + Output.prettyln(" $ bun run {s}\n", .{entry.key_ptr.*}); + Output.prettyln(" {s}\n", .{entry.value_ptr.*}); + } + + // Output.prettyln("\n{d} scripts", .{scripts.count()}); + + Output.flush(); + + // return true; + } else { + Output.prettyln("No \"scripts\" found in package.json.", .{}); + Output.flush(); + // return true; + } + } + } + Output.pretty("\n\n" ++ outro_text, .{}); + Output.flush(); + } + pub fn exec(ctx_: Command.Context, comptime bin_dirs_only: bool, comptime log_errors: bool) !bool { var ctx = ctx_; // Step 1. Figure out what we're trying to run @@ -1016,7 +1078,6 @@ pub const RunCommand = struct { Global.configureAllocator(.{ .long_running = false }); - var did_print = false; var ORIGINAL_PATH: string = ""; var this_bundler: bundler.Bundler = undefined; var root_dir_info = try configureEnvForRun(ctx, &this_bundler, null, &ORIGINAL_PATH, log_errors, force_using_bun); @@ -1025,34 +1086,9 @@ pub const RunCommand = struct { if (package_json.scripts) |scripts| { switch (script_name_to_search.len) { 0 => { - var display_name = package_json.name; - - if (display_name.len == 0) { - display_name = std.fs.path.basename(package_json.source.path.name.dir); - } - - var iterator = scripts.iterator(); - - if (scripts.count() > 0) { - did_print = true; - - Output.prettyln("{s} scripts:\n", .{display_name}); - while (iterator.next()) |entry| { - Output.prettyln("\n", .{}); - Output.prettyln(" bun run {s}\n", .{entry.key_ptr.*}); - Output.prettyln(" {s}\n", .{entry.value_ptr.*}); - } - - Output.prettyln("\n{d} scripts", .{scripts.count()}); - - Output.flush(); - - return true; - } else { - Output.prettyln("{s} has no \"scripts\" in package.json.", .{display_name}); - Output.flush(); - return true; - } + // naked "bun run" + RunCommand.printHelp(package_json); + return true; }, else => { if (scripts.get(script_name_to_search)) |script_content| { diff --git a/src/deps/zig-clap/clap.zig b/src/deps/zig-clap/clap.zig index 37cc4814e1..f74034220a 100644 --- a/src/deps/zig-clap/clap.zig +++ b/src/deps/zig-clap/clap.zig @@ -5,6 +5,7 @@ const heap = std.heap; const io = std.io; const mem = std.mem; const testing = std.testing; +const Output = @import("../../output.zig"); pub const args = @import("clap/args.zig"); @@ -208,27 +209,36 @@ pub const Diagnostic = struct { /// Default diagnostics reporter when all you want is English with no colors. /// Use this as a reference for implementing your own if needed. - pub fn report(diag: Diagnostic, stream: anytype, err: anyerror) !void { - const Arg = struct { - prefix: []const u8, - name: []const u8, - }; - const a = if (diag.name.short) |*c| - Arg{ .prefix = "-", .name = @as(*const [1]u8, c)[0..] } - else if (diag.name.long) |l| - Arg{ .prefix = "--", .name = l } - else - Arg{ .prefix = "", .name = diag.arg }; + pub fn report(diag: Diagnostic, _: anytype, err: anyerror) !void { + var name_buf: [1024]u8 = undefined; + const name = if (diag.name.short) |s| short: { + name_buf[0] = '-'; + name_buf[1] = s; + name_buf[2] = 0; + break :short name_buf[0..2]; + } else if (diag.name.long) |l| long: { + name_buf[0] = '-'; + name_buf[1] = '-'; + const long = l[0..@min(l.len, name_buf.len - 2)]; + @memcpy(name_buf[2..][0..long.len], long); + break :long name_buf[0 .. 2 + long.len]; + } else diag.arg; switch (err) { - error.DoesntTakeValue => try stream.print("The argument '{s}{s}' does not take a value\n", .{ a.prefix, a.name }), - error.MissingValue => try stream.print("The argument '{s}{s}' requires a value but none was supplied\n", .{ a.prefix, a.name }), - error.InvalidArgument => if (a.prefix.len > 0 and a.name.len > 0) - try stream.print("Invalid argument '{s}{s}'\n", .{ a.prefix, a.name }) - else - try stream.print("Failed to parse argument due to unexpected single dash\n", .{}), - else => try stream.print("Error while parsing arguments: {s}\n", .{@errorName(err)}), + error.DoesntTakeValue => { + Output.prettyErrorln("error: The argument '{s}' does not take a value.", .{name}); + }, + error.MissingValue => { + Output.prettyErrorln("error: The argument '{s}' requires a value but none was supplied.", .{name}); + }, + error.InvalidArgument => { + Output.prettyErrorln("error: Invalid Argument '{s}'", .{name}); + }, + else => { + Output.prettyErrorln("error: {s} while parsing argument '{s}'", .{ @errorName(err), name }); + }, } + Output.flush(); } }; @@ -353,11 +363,15 @@ pub fn helpFull( if (param.names.short == null and param.names.long == null) continue; - var cs = io.countingWriter(stream); - try stream.print("\t", .{}); - try printParam(cs.writer(), Id, param, Error, context, valueText); - try stream.writeByteNTimes(' ', max_spacing - @as(usize, @intCast(cs.bytes_written))); - try stream.print("\t{s}\n", .{try helpText(context, param)}); + const help_text = try helpText(context, param); + // only print flag if description is defined + if (help_text.len > 0) { + var cs = io.countingWriter(stream); + try stream.print("\t", .{}); + try printParam(cs.writer(), Id, param, Error, context, valueText); + try stream.writeByteNTimes(' ', max_spacing - @as(usize, @intCast(cs.bytes_written))); + try stream.print("\t{s}\n", .{try helpText(context, param)}); + } } } @@ -428,6 +442,99 @@ pub fn helpEx( ); } +pub fn simplePrintParam(param: Param(Help)) !void { + Output.pretty("\n", .{}); + if (param.names.short) |s| { + Output.pretty(" -{c}", .{s}); + } else { + Output.pretty(" ", .{}); + } + if (param.names.long) |l| { + if (param.names.short) |_| { + Output.pretty(", ", .{}); + } else { + Output.pretty(" ", .{}); + } + + Output.pretty("--{s}", .{l}); + } else { + Output.pretty(" ", .{}); + } +} + +pub fn simpleHelp( + params: []const Param(Help), +) void { + const max_spacing = blk: { + var res: usize = 2; + for (params) |param| { + var flags_len = if (param.names.long) |l| l.len else 0; + if (res < flags_len) + res = flags_len; + } + + break :blk res; + }; + + for (params) |param| { + if (param.names.short == null and param.names.long == null) + continue; + + const desc_text = getHelpSimple(param); + if (desc_text.len == 0) continue; + + // create a string with spaces_len spaces + const default_allocator = @import("root").bun.default_allocator; + + const flags_len = if (param.names.long) |l| l.len else 0; + const num_spaces_after = max_spacing - flags_len; + var spaces_after = default_allocator.alloc(u8, num_spaces_after) catch unreachable; + defer default_allocator.free(spaces_after); + for (0..num_spaces_after) |i| { + spaces_after[i] = ' '; + } + + simplePrintParam(param) catch unreachable; + Output.pretty(" {s} {s}", .{ spaces_after, desc_text }); + } +} + +pub fn simpleHelpBunTopLevel( + comptime params: []const Param(Help), +) void { + const max_spacing = 25; + const space_buf: *const [max_spacing]u8 = " " ** max_spacing; + + const computed_max_spacing = comptime blk: { + var res: usize = 2; + for (params) |param| { + var flags_len = if (param.names.long) |l| l.len else 0; + if (res < flags_len) + res = flags_len; + } + + break :blk res; + }; + + if (computed_max_spacing > max_spacing) { + @compileError("a parameter is too long to be nicely printed in `bun --help`"); + } + + inline for (params) |param| { + if (!(param.names.short == null and param.names.long == null)) { + const desc_text = comptime getHelpSimple(param); + if (desc_text.len != 0) { + simplePrintParam(param) catch unreachable; + + const flags_len = if (param.names.long) |l| l.len else 0; + const num_spaces_after = max_spacing - flags_len; + + Output.pretty(space_buf[0..num_spaces_after] ++ desc_text, .{}); + } + } + } +} + pub const Help = struct { msg: []const u8 = "", value: []const u8 = "", diff --git a/src/deps/zig-clap/clap/comptime.zig b/src/deps/zig-clap/clap/comptime.zig index d32a60f112..62d007c43b 100644 --- a/src/deps/zig-clap/clap/comptime.zig +++ b/src/deps/zig-clap/clap/comptime.zig @@ -70,6 +70,7 @@ pub fn ComptimeClap( var stream = clap.StreamingClap(usize, @typeInfo(@TypeOf(iter)).Pointer.child){ .params = converted_params, .iter = iter, + .diagnostic = opt.diagnostic, }; while (try stream.next()) |arg| { diff --git a/src/deps/zig-clap/clap/streaming.zig b/src/deps/zig-clap/clap/streaming.zig index 1611a41aeb..4d6c80c6c6 100644 --- a/src/deps/zig-clap/clap/streaming.zig +++ b/src/deps/zig-clap/clap/streaming.zig @@ -1,6 +1,7 @@ const builtin = @import("builtin"); const clap = @import("../clap.zig"); const std = @import("std"); +const Output = @import("root").bun.Output; const args = clap.args; const debug = std.debug; @@ -10,6 +11,7 @@ const mem = std.mem; const os = std.os; const testing = std.testing; +pub var warn_on_unrecognized_flag = true; /// The result returned from StreamingClap.next pub fn Arg(comptime Id: type) type { return struct { @@ -36,6 +38,8 @@ pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type { }; }; + const ArgError = error{ DoesntTakeValue, MissingValue, InvalidArgument }; + params: []const clap.Param(Id), iter: *ArgIterator, state: State = .normal, @@ -43,7 +47,7 @@ pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type { diagnostic: ?*clap.Diagnostic = null, /// Get the next Arg that matches a Param. - pub fn next(parser: *@This()) !?Arg(Id) { + pub fn next(parser: *@This()) ArgError!?Arg(Id) { switch (parser.state) { .normal => return try parser.normal(), .chaining => |state| return try parser.chainging(state), @@ -55,7 +59,7 @@ pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type { } } - fn normal(parser: *@This()) !?Arg(Id) { + fn normal(parser: *@This()) ArgError!?Arg(Id) { const ArgType = Arg(Id); const arg_info = (try parser.parseNextArg()) orelse return null; const arg = arg_info.arg; @@ -64,6 +68,7 @@ pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type { .long => { const eql_index = mem.indexOfScalar(u8, arg, '='); const name = if (eql_index) |i| arg[0..i] else arg; + const maybe_value = if (eql_index) |i| arg[i + 1 ..] else null; for (parser.params) |*param| { @@ -92,6 +97,22 @@ pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type { return ArgType{ .param = param, .value = value }; } + // unrecognized command + // if flag else arg + if (arg_info.kind == .long or arg_info.kind == .short) { + if (warn_on_unrecognized_flag) { + Output.prettyWarnln("warn: unrecognized flag: {s}{s}\n", .{ if (arg_info.kind == .long) "--" else "-", name }); + Output.flush(); + } + + // continue parsing after unrecognized flag + return parser.next(); + } + + if (warn_on_unrecognized_flag) { + Output.prettyWarnln("warn: unrecognized argument: {s}\n", .{name}); + Output.flush(); + } return null; }, .short => return try parser.chainging(.{ @@ -104,6 +125,7 @@ pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type { // arguments. if (mem.eql(u8, arg, "--")) { parser.state = .rest_are_positional; + // return null to terminate arg parsing const value = parser.iter.next() orelse return null; return Arg(Id){ .param = param, .value = value }; } @@ -115,7 +137,7 @@ pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type { } } - fn chainging(parser: *@This(), state: State.Chaining) !?Arg(Id) { + fn chainging(parser: *@This(), state: State.Chaining) ArgError!?Arg(Id) { const arg = state.arg; const index = state.index; const next_index = index + 1; @@ -188,7 +210,7 @@ pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type { }, }; - fn parseNextArg(parser: *@This()) !?ArgInfo { + fn parseNextArg(parser: *@This()) ArgError!?ArgInfo { const full_arg = parser.iter.next() orelse return null; if (mem.eql(u8, full_arg, "--") or mem.eql(u8, full_arg, "-")) return ArgInfo{ .arg = full_arg, .kind = .positional }; diff --git a/src/install/install.zig b/src/install/install.zig index 5578941635..e56a19d591 100644 --- a/src/install/install.zig +++ b/src/install/install.zig @@ -48,6 +48,7 @@ const Bitset = bun.bit_set.DynamicBitSetUnmanaged; const z_allocator = @import("../memory_allocator.zig").z_allocator; const Syscall = bun.sys; const RunCommand = @import("../cli/run_command.zig").RunCommand; +const PackageManagerCommand = @import("../cli/package_manager_command.zig").PackageManagerCommand; threadlocal var initialized_store = false; const Futex = @import("../futex.zig"); @@ -6377,11 +6378,11 @@ pub const PackageManager = struct { "Possible values: \"hardlink\" (default), \"symlink\", \"copyfile\""; const install_params_ = [_]ParamType{ - clap.parseParam("-c, --config ? Load config (bunfig.toml)") catch unreachable, + clap.parseParam("-c, --config ? Specify path to config file (bunfig.toml)") catch unreachable, clap.parseParam("-y, --yarn Write a yarn.lock file (yarn v1)") catch unreachable, clap.parseParam("-p, --production Don't install devDependencies") catch unreachable, - clap.parseParam("--no-save Don't save a lockfile") catch unreachable, - clap.parseParam("--save Save to package.json") catch unreachable, + clap.parseParam("--no-save Don't update package.json or save a lockfile") catch unreachable, + clap.parseParam("--save Save to package.json (true by default)") catch unreachable, clap.parseParam("--dry-run Don't install anything") catch unreachable, clap.parseParam("--frozen-lockfile Disallow changes to lockfile") catch unreachable, clap.parseParam("-f, --force Always request the latest versions from the registry & reinstall all dependencies") catch unreachable, @@ -6397,14 +6398,12 @@ pub const PackageManager = struct { clap.parseParam("--cwd Set a specific cwd") catch unreachable, clap.parseParam("--backend Platform-specific optimizations for installing dependencies. " ++ platform_specific_backend_label) catch unreachable, clap.parseParam("--link-native-bins ... Link \"bin\" from a matching platform-specific \"optionalDependencies\" instead. Default: esbuild, turbo") catch unreachable, - // clap.parseParam("--omit ... Skip installing dependencies of a certain type. \"dev\", \"optional\", or \"peer\"") catch unreachable, // clap.parseParam("--no-dedupe Disable automatic downgrading of dependencies that would otherwise cause unnecessary duplicate package versions ($BUN_CONFIG_NO_DEDUPLICATE)") catch unreachable, - clap.parseParam("--help Print this help menu") catch unreachable, }; - const install_params = install_params_ ++ [_]ParamType{ + pub const install_params = install_params_ ++ [_]ParamType{ clap.parseParam("-d, --dev Add dependency to \"devDependencies\"") catch unreachable, clap.parseParam("-D, --development") catch unreachable, clap.parseParam("--optional Add dependency to \"optionalDependencies\"") catch unreachable, @@ -6412,15 +6411,15 @@ pub const PackageManager = struct { clap.parseParam(" ... ") catch unreachable, }; - const update_params = install_params_ ++ [_]ParamType{ + pub const update_params = install_params_ ++ [_]ParamType{ clap.parseParam(" ... \"name\" of packages to update") catch unreachable, }; - const pm_params = install_params_ ++ [_]ParamType{ + pub const pm_params = install_params_ ++ [_]ParamType{ clap.parseParam(" ... ") catch unreachable, }; - const add_params = install_params_ ++ [_]ParamType{ + pub const add_params = install_params_ ++ [_]ParamType{ clap.parseParam("-d, --dev Add dependency to \"devDependencies\"") catch unreachable, clap.parseParam("-D, --development") catch unreachable, clap.parseParam("--optional Add dependency to \"optionalDependencies\"") catch unreachable, @@ -6428,15 +6427,15 @@ pub const PackageManager = struct { clap.parseParam(" ... \"name\" or \"name@version\" of package(s) to install") catch unreachable, }; - const remove_params = install_params_ ++ [_]ParamType{ + pub const remove_params = install_params_ ++ [_]ParamType{ clap.parseParam(" ... \"name\" of package(s) to remove from package.json") catch unreachable, }; - const link_params = install_params_ ++ [_]ParamType{ + pub const link_params = install_params_ ++ [_]ParamType{ clap.parseParam(" ... \"name\" install package as a link") catch unreachable, }; - const unlink_params = install_params_ ++ [_]ParamType{ + pub const unlink_params = install_params_ ++ [_]ParamType{ clap.parseParam(" ... \"name\" uninstall package as a link") catch unreachable, }; @@ -6491,6 +6490,147 @@ pub const PackageManager = struct { } }; + pub fn printHelp(comptime subcommand: Subcommand) void { + switch (subcommand) { + // fall back to HelpCommand.printWithReason + Subcommand.install => { + const intro_text = + \\Usage: bun install [flags] [...\] + \\Alias: bun i + \\ Install the dependencies listed in package.json + ; + const outro_text = + \\Examples: + \\ Install the dependencies for the current project + \\ bun install + \\ + \\ Skip devDependencies + \\ bun install --production + \\ + \\Full documentation is available at https://bun.sh/docs/cli/install + ; + Output.pretty("\n" ++ intro_text, .{}); + Output.flush(); + Output.pretty("\n\nFlags:", .{}); + Output.flush(); + clap.simpleHelp(&PackageManager.add_params); + Output.pretty("\n\n" ++ outro_text ++ "\n", .{}); + Output.flush(); + }, + Subcommand.update => { + const intro_text = + \\Usage: bun update [flags] + \\ Update all dependencies to most recent versions within the version range in package.json + \\ + ; + const outro_text = + \\Examples: + \\ Update all dependencies: + \\ bun update + \\ + \\Full documentation is available at https://bun.sh/docs/cli/update + ; + Output.pretty("\n" ++ intro_text, .{}); + Output.flush(); + Output.pretty("\nFlags:", .{}); + Output.flush(); + clap.simpleHelp(&PackageManager.add_params); + Output.pretty("\n\n" ++ outro_text ++ "\n", .{}); + Output.flush(); + }, + Subcommand.pm => { + PackageManagerCommand.printHelp(); + }, + Subcommand.add => { + const intro_text = + \\Usage: bun add [flags] \ [...\] + \\Alias: bun a + ; + const outro_text = + \\Examples: + \\ Add a dependency from the npm registry + \\ bun add zod + \\ bun add zod@next + \\ bun add zod@3.0.0 + \\ + \\ Add a dev, optional, or peer dependency + \\ bun add -d typescript + \\ bun add -o lodash + \\ bun add --peer esbuild + \\ + \\Full documentation is available at https://bun.sh/docs/cli/add + ; + Output.pretty("\n" ++ intro_text, .{}); + Output.flush(); + Output.pretty("\n\nFlags:", .{}); + Output.flush(); + clap.simpleHelp(&PackageManager.add_params); + Output.pretty("\n\n" ++ outro_text ++ "\n", .{}); + Output.flush(); + }, + Subcommand.remove => { + const intro_text = + \\Usage: bun remove [flags] \ [...\] + \\Alias: bun r + \\ Remove a package from package.json and uninstall from node_modules + \\ + ; + const outro_text = + \\Examples: + \\ Remove a dependency + \\ bun remove ts-node + \\ + \\Full documentation is available at https://bun.sh/docs/cli/remove + ; + Output.pretty("\n" ++ intro_text, .{}); + Output.flush(); + Output.pretty("\nFlags:", .{}); + Output.flush(); + clap.simpleHelp(&PackageManager.remove_params); + Output.pretty("\n\n" ++ outro_text ++ "\n", .{}); + Output.flush(); + }, + Subcommand.link => { + const intro_text = + \\Usage: bun link [flags] [\] + \\ + ; + const outro_text = + \\Examples: + \\ Register the current directory as a linkable package. + \\ Directory should contain a package.json. + \\ bun link + \\ + \\ Add a previously-registered linkable package as a dependency of the current project. + \\ bun link \ + \\ + \\Full documentation is available at https://bun.sh/docs/cli/link + ; + Output.pretty("\n" ++ intro_text, .{}); + Output.flush(); + Output.pretty("\nFlags:", .{}); + Output.flush(); + clap.simpleHelp(&PackageManager.link_params); + Output.pretty("\n\n" ++ outro_text ++ "\n", .{}); + Output.flush(); + }, + Subcommand.unlink => { + const intro_text = + \\Usage: bun unlink [flags] + \\ + \\Examples: + \\ Unregister the current directory as a linkable package. + \\ bun unlink + \\ + \\Full documentation is available at https://bun.sh/docs/cli/link + ; + + Output.pretty("\n" ++ intro_text ++ "\n", .{}); + Output.flush(); + }, + } + } + pub fn parse(allocator: std.mem.Allocator, comptime subcommand: Subcommand) !CommandLineArguments { comptime var params: []const ParamType = &switch (subcommand) { .install => install_params, @@ -6515,11 +6655,7 @@ pub const PackageManager = struct { }; if (args.flag("--help")) { - Output.prettyln("\nbun (package manager) flags:\n\n", .{}); - Output.flush(); - - clap.help(Output.writer(), params) catch {}; - + printHelp(subcommand); Global.exit(0); } @@ -6775,87 +6911,23 @@ pub const PackageManager = struct { if (manager.options.positionals.len <= 1) { var examples_to_print: [3]string = undefined; + _ = examples_to_print; const off = @as(u64, @intCast(std.time.milliTimestamp())); + _ = off; switch (op) { .add => { - const filler = @import("../cli.zig").HelpCommand.packages_to_add_filler; + Output.prettyWarnln("error: no package specified to add", .{}); + Output.flush(); + PackageManager.CommandLineArguments.printHelp(.add); - examples_to_print[0] = filler[@as(usize, @intCast((off) % filler.len))]; - examples_to_print[1] = filler[@as(usize, @intCast((off + 1) % filler.len))]; - examples_to_print[2] = filler[@as(usize, @intCast((off + 2) % filler.len))]; - - Output.prettyErrorln( - \\ - \\Usage: - \\ - \\ bun add package-name@version - \\ bun add package-name - \\ bun add package-name a-second-package - \\ - \\Examples: - \\ - \\ bun add -g {s} - \\ bun add {s} - \\ bun add {s} - \\ - , .{ examples_to_print[0], examples_to_print[1], examples_to_print[2] }); - - if (manager.options.global) { - Output.prettyErrorln( - \\ - \\Shorthand: bun a -g - \\ - , .{}); - } else { - Output.prettyErrorln( - \\ - \\Shorthand: bun a - \\ - , .{}); - } Global.exit(0); }, .remove => { - const filler = @import("../cli.zig").HelpCommand.packages_to_remove_filler; - - examples_to_print[0] = filler[@as(usize, @intCast((off) % filler.len))]; - examples_to_print[1] = filler[@as(usize, @intCast((off + 1) % filler.len))]; - examples_to_print[2] = filler[@as(usize, @intCast((off + 2) % filler.len))]; - - Output.prettyErrorln( - \\ - \\Usage: - \\ - \\ bun remove package-name - \\ bun remove package-name a-second-package - \\ - \\Examples: - \\ - \\ bun remove {s} {s} - \\ bun remove {s} - \\ - , .{ - examples_to_print[0], - examples_to_print[1], - examples_to_print[2], - }); - if (manager.options.global) { - Output.prettyErrorln( - \\ - \\Shorthand: bun rm -g - \\ - , .{}); - } else { - Output.prettyErrorln( - \\ - \\Shorthand: bun rm - \\ - , .{}); - } - + Output.prettyWarnln("error: no package specified to remove", .{}); Output.flush(); + PackageManager.CommandLineArguments.printHelp(.remove); Global.exit(0); }, diff --git a/src/js_parser.zig b/src/js_parser.zig index 92363b24e1..441623e575 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -8664,12 +8664,12 @@ fn NewParser_( .s_function => |func_container| { if (func_container.func.name) |name| { break :default_name_getter LocRef{ .loc = name.loc, .ref = name.ref }; - } else {} + } }, .s_class => |class| { if (class.class.class_name) |name| { break :default_name_getter LocRef{ .loc = name.loc, .ref = name.ref }; - } else {} + } }, else => {}, } @@ -8709,12 +8709,12 @@ fn NewParser_( .s_function => |func_container| { if (func_container.func.name) |_name| { break :default_name_getter LocRef{ .loc = defaultLoc, .ref = _name.ref }; - } else {} + } }, .s_class => |class| { if (class.class.class_name) |_name| { break :default_name_getter LocRef{ .loc = defaultLoc, .ref = _name.ref }; - } else {} + } }, else => {}, } diff --git a/src/logger.zig b/src/logger.zig index c7846ab622..3ef18043b5 100644 --- a/src/logger.zig +++ b/src/logger.zig @@ -370,7 +370,7 @@ pub const Data = struct { if (comptime enable_ansi_colors) { if (!is_note and kind == .err) { try to.writeAll(comptime Output.color_map.get("b").?); - } else {} + } } try std.fmt.format(to, comptime Output.prettyFmt("{s}", enable_ansi_colors), .{ diff --git a/src/report.zig b/src/report.zig index c049541c5d..421bb52f21 100644 --- a/src/report.zig +++ b/src/report.zig @@ -610,9 +610,6 @@ pub noinline fn globalError(err: anyerror, trace_: @TypeOf(@errorReturnTrace())) Global.exit(1); }, - error.MissingValue => { - Global.exit(1); - }, else => {}, } diff --git a/src/toml/toml_parser.zig b/src/toml/toml_parser.zig index 34220cbe06..327408ef80 100644 --- a/src/toml/toml_parser.zig +++ b/src/toml/toml_parser.zig @@ -268,7 +268,7 @@ pub const TOML = struct { else => return err, } }; - } else {} + } p.lexer.allow_double_bracket = true; } diff --git a/test/cli/install/bunx.test.ts b/test/cli/install/bunx.test.ts index b8baceb1bb..a52965ea90 100644 --- a/test/cli/install/bunx.test.ts +++ b/test/cli/install/bunx.test.ts @@ -83,7 +83,7 @@ it("should output usage if no arguments are passed", async () => { expect(stderr).toBeDefined(); const err = await new Response(stderr).text(); - expect(err).toContain("usage: "); + expect(err).toContain("Usage: "); expect(stdout).toBeDefined(); const out = await new Response(stdout).text(); expect(out).toHaveLength(0); diff --git a/test/cli/run/run-eval.test.ts b/test/cli/run/run-eval.test.ts new file mode 100644 index 0000000000..3ddcd27070 --- /dev/null +++ b/test/cli/run/run-eval.test.ts @@ -0,0 +1,42 @@ +import { describe, expect, test } from "bun:test"; +import { mkdirSync, realpathSync } from "fs"; +import { bunEnv, bunExe } from "harness"; +import { tmpdir } from "os"; +import { join } from "path"; + +describe("bun -e", () => { + test("it works", async () => { + let { stdout } = Bun.spawnSync({ + cmd: [bunExe(), "-e", 'console.log("hello world")'], + env: bunEnv, + }); + expect(stdout.toString("utf8")).toEqual("hello world\n"); + }); + + test("import, tsx, require in esm, import.meta", async () => { + const ref = await import("react"); + let { stdout } = Bun.spawnSync({ + cmd: [ + bunExe(), + "-e", + 'import {version} from "react"; console.log(JSON.stringify({version,file:import.meta.path,require:require("react").version})); console.log(world);', + ], + env: bunEnv, + }); + const json = { + version: ref.version, + file: join(process.cwd(), "[eval]"), + require: ref.version, + }; + expect(stdout.toString("utf8")).toEqual(JSON.stringify(json) + "\nworld\n"); + }); + + test("error has source map info 1", async () => { + let { stdout, stderr } = Bun.spawnSync({ + cmd: [bunExe(), "-e", '(throw new Error("hi" as 2))'], + env: bunEnv, + }); + expect(stderr.toString("utf8")).toInclude('"hi" as 2'); + expect(stderr.toString("utf8")).toInclude("Unexpected throw"); + }); +});