diff --git a/Cargo.lock b/Cargo.lock index 3f386b432a26..d8a78adc4672 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -162,15 +162,6 @@ dependencies = [ "libc", ] -[[package]] -name = "bun_api" -version = "0.0.0" -dependencies = [ - "bun_alloc", - "bun_options_types", - "bun_url", -] - [[package]] name = "bun_ast" version = "0.0.0" @@ -399,12 +390,12 @@ dependencies = [ "bstr", "bun_alloc", "bun_analytics", - "bun_api", "bun_ast", "bun_bundler", "bun_clap", "bun_collections", "bun_core", + "bun_dotenv", "bun_install_types", "bun_io", "bun_js_parser", @@ -884,7 +875,6 @@ dependencies = [ "bitflags", "bstr", "bun_alloc", - "bun_api", "bun_ast", "bun_base64", "bun_collections", @@ -892,6 +882,7 @@ dependencies = [ "bun_dotenv", "bun_install_types", "bun_js_parser", + "bun_options_types", "bun_parsers", "bun_sys", "bun_url", @@ -969,7 +960,6 @@ dependencies = [ "bitflags", "bstr", "bun_alloc", - "bun_api", "bun_ast", "bun_ast_jsc", "bun_core", @@ -978,6 +968,7 @@ dependencies = [ "bun_install", "bun_js_parser_jsc", "bun_jsc", + "bun_options_types", "bun_paths", "bun_semver", "bun_sys", @@ -1136,7 +1127,6 @@ dependencies = [ "bstr", "bun_alloc", "bun_analytics", - "bun_api", "bun_ast", "bun_base64", "bun_boringssl", diff --git a/Cargo.toml b/Cargo.toml index 3c3f997ad213..8469980a5303 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,6 @@ resolver = "2" members = [ "src/opaque", "src/analytics", - "src/api", "src/base64", "src/bundler", "src/collections", @@ -360,7 +359,6 @@ itoa = "1" lol_html = { path = "vendor/lolhtml" } bun_opaque = { path = "src/opaque" } bun_analytics = { path = "src/analytics" } -bun_api = { path = "src/api" } bun_base64 = { path = "src/base64" } bun_bundler = { path = "src/bundler" } bun_collections = { path = "src/collections" } diff --git a/docs/runtime/bunfig.mdx b/docs/runtime/bunfig.mdx index af34fadf6f59..bf775df1c485 100644 --- a/docs/runtime/bunfig.mdx +++ b/docs/runtime/bunfig.mdx @@ -61,10 +61,10 @@ smol = true ### `logLevel` -Set the log level: `"debug"`, `"warn"`, or `"error"`. +Set the log level: `"debug"`, `"info"`, `"warn"`, or `"error"`. ```toml title="bunfig.toml" icon="settings" -logLevel = "debug" # "debug" | "warn" | "error" +logLevel = "debug" # "debug" | "info" | "warn" | "error" ``` ### `define` diff --git a/packages/bun-native-bundler-plugin-api/bundler_plugin.h b/packages/bun-native-bundler-plugin-api/bundler_plugin.h index a4a15fa08aec..ba7c1bb2f3aa 100644 --- a/packages/bun-native-bundler-plugin-api/bundler_plugin.h +++ b/packages/bun-native-bundler-plugin-api/bundler_plugin.h @@ -12,14 +12,20 @@ typedef enum { BUN_LOADER_CSS = 4, BUN_LOADER_FILE = 5, BUN_LOADER_JSON = 6, - BUN_LOADER_TOML = 7, - BUN_LOADER_WASM = 8, - BUN_LOADER_NAPI = 9, - BUN_LOADER_BASE64 = 10, - BUN_LOADER_DATAURL = 11, - BUN_LOADER_TEXT = 12, + BUN_LOADER_JSONC = 7, + BUN_LOADER_TOML = 8, + BUN_LOADER_WASM = 9, + BUN_LOADER_NAPI = 10, + BUN_LOADER_BASE64 = 11, + BUN_LOADER_DATAURL = 12, + BUN_LOADER_TEXT = 13, + BUN_LOADER_BUNSH = 14, + BUN_LOADER_SQLITE = 15, + BUN_LOADER_SQLITE_EMBEDDED = 16, BUN_LOADER_HTML = 17, BUN_LOADER_YAML = 18, + BUN_LOADER_JSON5 = 19, + BUN_LOADER_MD = 20, BUN_LOADER_XML = 21, } BunLoader; diff --git a/packages/bun-native-plugin-rs/headers/bun-native-bundler-plugin-api/bundler_plugin.h b/packages/bun-native-plugin-rs/headers/bun-native-bundler-plugin-api/bundler_plugin.h index ff10c27ccd62..ba7c1bb2f3aa 100644 --- a/packages/bun-native-plugin-rs/headers/bun-native-bundler-plugin-api/bundler_plugin.h +++ b/packages/bun-native-plugin-rs/headers/bun-native-bundler-plugin-api/bundler_plugin.h @@ -12,15 +12,24 @@ typedef enum { BUN_LOADER_CSS = 4, BUN_LOADER_FILE = 5, BUN_LOADER_JSON = 6, - BUN_LOADER_TOML = 7, - BUN_LOADER_WASM = 8, - BUN_LOADER_NAPI = 9, - BUN_LOADER_BASE64 = 10, - BUN_LOADER_DATAURL = 11, - BUN_LOADER_TEXT = 12, + BUN_LOADER_JSONC = 7, + BUN_LOADER_TOML = 8, + BUN_LOADER_WASM = 9, + BUN_LOADER_NAPI = 10, + BUN_LOADER_BASE64 = 11, + BUN_LOADER_DATAURL = 12, + BUN_LOADER_TEXT = 13, + BUN_LOADER_BUNSH = 14, + BUN_LOADER_SQLITE = 15, + BUN_LOADER_SQLITE_EMBEDDED = 16, + BUN_LOADER_HTML = 17, + BUN_LOADER_YAML = 18, + BUN_LOADER_JSON5 = 19, + BUN_LOADER_MD = 20, + BUN_LOADER_XML = 21, } BunLoader; -const BunLoader BUN_LOADER_MAX = BUN_LOADER_TEXT; +const BunLoader BUN_LOADER_MAX = BUN_LOADER_XML; typedef struct BunLogOptions { size_t __struct_size; diff --git a/packages/bun-native-plugin-rs/src/sys.rs b/packages/bun-native-plugin-rs/src/sys.rs index 3fc9cb612296..53651ab6d382 100644 --- a/packages/bun-native-plugin-rs/src/sys.rs +++ b/packages/bun-native-plugin-rs/src/sys.rs @@ -108,12 +108,21 @@ pub enum BunLoader { BUN_LOADER_CSS = 4, BUN_LOADER_FILE = 5, BUN_LOADER_JSON = 6, - BUN_LOADER_TOML = 7, - BUN_LOADER_WASM = 8, - BUN_LOADER_NAPI = 9, - BUN_LOADER_BASE64 = 10, - BUN_LOADER_DATAURL = 11, - BUN_LOADER_TEXT = 12, + BUN_LOADER_JSONC = 7, + BUN_LOADER_TOML = 8, + BUN_LOADER_WASM = 9, + BUN_LOADER_NAPI = 10, + BUN_LOADER_BASE64 = 11, + BUN_LOADER_DATAURL = 12, + BUN_LOADER_TEXT = 13, + BUN_LOADER_BUNSH = 14, + BUN_LOADER_SQLITE = 15, + BUN_LOADER_SQLITE_EMBEDDED = 16, + BUN_LOADER_HTML = 17, + BUN_LOADER_YAML = 18, + BUN_LOADER_JSON5 = 19, + BUN_LOADER_MD = 20, + BUN_LOADER_XML = 21, } extern "C" { pub static BUN_LOADER_MAX: BunLoader; diff --git a/packages/bun-types/bun.d.ts b/packages/bun-types/bun.d.ts index 4c756a6d4c31..391d664b7364 100644 --- a/packages/bun-types/bun.d.ts +++ b/packages/bun-types/bun.d.ts @@ -5547,9 +5547,11 @@ declare module "bun" { | "tsx" | "json" | "jsonc" + | "json5" | "toml" | "yaml" | "xml" + | "md" | "file" | "napi" | "wasm" diff --git a/scripts/build/codegen.ts b/scripts/build/codegen.ts index 966b4c1098d7..9e939edbbb3e 100644 --- a/scripts/build/codegen.ts +++ b/scripts/build/codegen.ts @@ -707,6 +707,8 @@ function emitJsModules({ n, cfg, sources, o, dirStamp }: Ctx): void { // ($makeErrorWithCode(N, ...)); without this dep an ErrorCode.ts edit leaves // stale error numbers in the JS bundles while the C++ enum regenerates. const errorCodeInput = resolve(cfg.cwd, "src", "jsc", "bindings", "ErrorCode.ts"); + // replacements.ts derives the $Loader*/$ImportKind* id tables from these Rust enums. + const rustEnumInputs = [resolve(cfg.cwd, "src", "ast", "loader.rs"), resolve(cfg.cwd, "src", "ast", "lib.rs")]; const outputs = [ resolve(cfg.codegenDir, "WebCoreJSBuiltins.cpp"), @@ -733,7 +735,7 @@ function emitJsModules({ n, cfg, sources, o, dirStamp }: Ctx): void { n.build({ outputs, rule: "codegen", - inputs: [script, ...sources.js, ...sources.jsCodegen, extraInput, errorCodeInput], + inputs: [script, ...sources.js, ...sources.jsCodegen, extraInput, errorCodeInput, ...rustEnumInputs], orderOnlyInputs: [dirStamp], vars: { cwd: cfg.cwd, diff --git a/src/api/Cargo.toml b/src/api/Cargo.toml deleted file mode 100644 index 890d219c438a..000000000000 --- a/src/api/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "bun_api" -version.workspace = true -edition.workspace = true - -[lib] -path = "lib.rs" - -[lints] -workspace = true - -[dependencies] -bun_alloc.workspace = true -bun_options_types.workspace = true -bun_url.workspace = true diff --git a/src/api/lib.rs b/src/api/lib.rs deleted file mode 100644 index 14249de1ba5f..000000000000 --- a/src/api/lib.rs +++ /dev/null @@ -1,61 +0,0 @@ -#![allow(non_snake_case, non_camel_case_types, non_upper_case_globals)] -#![warn(unused_must_use)] -//! Re-exports of the install config types (`BunInstall`, `NpmRegistry`, …) -//! whose canonical definitions live in `bun_options_types::schema::api`, plus -//! the registry-URL parser shared by the bunfig and npmrc loaders. - -// ────────────────────────────────────────────────────────────────────────── -// Re-exports — canonical definitions live in `bun_options_types::schema::api`. -// ────────────────────────────────────────────────────────────────────────── - -pub use bun_options_types::schema::api::{ - BunInstall, Ca, NodeLinker, NpmRegistry, NpmRegistryMap, PnpmMatcher, -}; - -// ────────────────────────────────────────────────────────────────────────── -// npm_registry — module path for the nested `NpmRegistry::Parser` -// ────────────────────────────────────────────────────────────────────────── - -/// `Parser` lives in a sibling module of `NpmRegistry`; the canonical path -/// is `bun_api::npm_registry::Parser`. -pub mod npm_registry { - use bun_url::URL; - - pub use super::NpmRegistry; - - // `Parser` stays generic over `L` (Log) / `S` (Source) so this leaf - // schema crate doesn't need to name `bun_logger`. The lone live body - // (`parse_registry_url_string_impl`) doesn't touch log/source — only - // `parse_registry_object` / `parse_registry` would, and those need - // `js_ast::Expr` so they belong upstream in the bunfig parser anyway. - pub struct Parser<'a, L, S> { - pub log: &'a mut L, - pub source: &'a S, - } - - impl<'a, L, S> Parser<'a, L, S> { - pub fn parse_registry_url_string_impl( - &mut self, - str: &[u8], - ) -> Result { - let url = URL::parse(str); - let mut registry = NpmRegistry::default(); - - // Token - if url.username.is_empty() && !url.password.is_empty() { - registry.token = Box::<[u8]>::from(url.password); - registry.url = url.href_without_auth(); - } else if !url.username.is_empty() && !url.password.is_empty() { - registry.username = Box::<[u8]>::from(url.username); - registry.password = Box::<[u8]>::from(url.password); - - registry.url = url.href_without_auth(); - } else { - // Do not include a trailing slash. There might be parameters at the end. - registry.url = Box::<[u8]>::from(url.href); - } - - Ok(registry) - } - } -} diff --git a/src/ast/lib.rs b/src/ast/lib.rs index 9cdb38c89acd..b455cd360a52 100644 --- a/src/ast/lib.rs +++ b/src/ast/lib.rs @@ -75,13 +75,9 @@ pub enum ImportKind { Internal = 11, } -// E0015: EnumMap indexing isn't const; the lookup table is folded into match -// arms inside label()/error_label() below — zero runtime init (PORTING.md §Concurrency: prefer no-lock over OnceLock -// when the data is pure const). -// -// If these are changed, make sure to update -// - src/js/builtins/codegen/replacements.ts -// - packages/bun-types/bun.d.ts +// src/codegen/replacements.ts derives the JS builtins' `$ImportKindIdToLabel` +// from the discriminants above and `label()` below; keep `ImportKind` in +// packages/bun-types/bun.d.ts in sync by hand. impl ImportKind { #[inline] diff --git a/src/ast/loader.rs b/src/ast/loader.rs index fbb7dcbb46b8..0f4715ab5341 100644 --- a/src/ast/loader.rs +++ b/src/ast/loader.rs @@ -1,16 +1,18 @@ //! `Loader` + `SideEffects`. //! -//! Data-only enum + pure predicates. `to_api()` / `from_api()` / `API_NAMES` -//! live in `bun_options_types::LoaderExt` (would back-edge into the schema -//! crate). `to_mime_type` / `from_mime_type` live in `bun_http_types` (would -//! back-edge into `bun_http::MimeType`). +//! Data-only enum + pure predicates. `to_mime_type` / `from_mime_type` live in +//! `bun_http_types` (would back-edge into `bun_http::MimeType`). use enum_map::Enum; -/// The max integer value in this enum can only be appended to. -/// It has dependencies in several places: -/// - bun-native-bundler-plugin-api/bundler_plugin.h -/// - src/jsc/bindings/headers-handwritten.h +/// The discriminants are the one loader numbering used everywhere a loader +/// crosses a language boundary; values can only be appended. Kept in sync +/// (see test/internal/source-lints/loader-numbering.test.ts) with: +/// - packages/bun-native-bundler-plugin-api/bundler_plugin.h (`BUN_LOADER_*`, public) +/// - packages/bun-native-plugin-rs/src/sys.rs (`BunLoader`) +/// - src/jsc/bindings/headers-handwritten.h (`BunLoaderType*`) +/// - `$LoaderLabelToId` / `$LoaderIdToLabel` in the JS builtins, which +/// src/codegen/replacements.ts derives from this file. #[repr(u8)] #[derive( Copy, @@ -23,6 +25,7 @@ use enum_map::Enum; Enum, strum::IntoStaticStr, strum::VariantNames, + strum::FromRepr, )] // The lower_snake names are exposed to JS (HTMLImportManifest // `"loader":`, BuildArtifact.loader) so the strum serialization must match exactly. @@ -55,9 +58,7 @@ pub enum Loader { // Crosses FFI as `uint8_t default_loader` / `uint8_t loader` in // `OnBeforeParseArguments` / `OnBeforeParseResult` (`bundler_plugin.h`); lock -// the discriminant width and the values native plugins observe. NB: the C -// header's `BUN_LOADER_TOML = 7` etc. predate `Jsonc`'s insertion at 7 and are -// known-stale — this enum is the source of truth. +// the discriminant width and the values native plugins observe. bun_core::assert_ffi_discr!( Loader, u8; Jsx = 0, Js = 1, Ts = 2, Tsx = 3, Css = 4, File = 5, Json = 6, @@ -95,6 +96,7 @@ bun_core::comptime_string_map! { b"txt" => Loader::Text, b"text" => Loader::Text, b"sh" => Loader::Bunsh, + b"bunsh" => Loader::Bunsh, b"sqlite" => Loader::Sqlite, b"sqlite_embedded" => Loader::SqliteEmbedded, b"html" => Loader::Html, diff --git a/src/ast/runtime.rs b/src/ast/runtime.rs index 03d9977501f1..0ec8d42121f3 100644 --- a/src/ast/runtime.rs +++ b/src/ast/runtime.rs @@ -3,10 +3,8 @@ // REFACTOR_BUN_AST: this module holds only the data-shaped runtime pieces // that the AST crate (and `bun_js_printer::Options`) need: // `Runtime::source_code`, `Imports`, `ReplaceableExport*`, `ServerComponentsMode`. -// The `Features` struct (carries `&mut RuntimeTranspilerCache`) and -// `Fallback` HTML rendering (needs `bun_options_types::schema`, `bun_io`, -// `bun_base64`) live in `bun_js_parser::parser::Runtime` to avoid the -// `bun_options_types → bun_ast → bun_options_types` cycle. +// The `Features` struct (carries `&mut RuntimeTranspilerCache`) lives in +// `bun_js_parser::parser::Runtime`. use bun_collections::StringArrayHashMap; diff --git a/src/ast/target.rs b/src/ast/target.rs index 4a79dcfc09cb..d6938c1f60a1 100644 --- a/src/ast/target.rs +++ b/src/ast/target.rs @@ -1,7 +1,4 @@ //! Bundle target platform. -//! -//! Data-only enum + pure predicates. `to_api()` / `from(api::Target)` live in -//! `bun_options_types::TargetExt` (would back-edge into the schema crate). use enum_map::Enum; @@ -34,7 +31,6 @@ impl Target { pub const MAP: __ComptimeStringMap_TARGET_MAP = __ComptimeStringMap_TARGET_MAP(()); // `from_js` lives in bundler_jsc as an extension trait — see PORTING.md. - // `to_api`/`from(api)` live in `bun_options_types::TargetExt`. #[inline] pub fn is_server_side(self) -> bool { diff --git a/src/bun.js.rs b/src/bun.js.rs index 8abe54ac9bcb..fafdb1f27db9 100644 --- a/src/bun.js.rs +++ b/src/bun.js.rs @@ -15,11 +15,11 @@ pub(crate) fn apply_standalone_runtime_flags( b: &mut bun_bundler::Transpiler, graph: &StandaloneModuleGraph, ) { - use bun_options_types::schema::api::DotEnvBehavior; + use bun_dotenv::DotEnvBehavior; let disable_env = graph.flags.contains(GraphFlags::DISABLE_DEFAULT_ENV_FILES); b.options.env.disable_default_env_files = disable_env; b.options.env.behavior = if disable_env { - DotEnvBehavior::disable + DotEnvBehavior::Disable } else { DotEnvBehavior::LoadAllWithoutInlining }; diff --git a/src/bun_core/util.rs b/src/bun_core/util.rs index 4bd44f0741ca..bcff6fa4e811 100644 --- a/src/bun_core/util.rs +++ b/src/bun_core/util.rs @@ -3041,11 +3041,9 @@ macro_rules! __runtime_embed_impl { // `StringPointer` stays here as the layered #[repr(C)] ABI type re-exported by // `bun_string` et al. -/// `bun.schema.api.StringPointer` — `(offset, length)` span into an external -/// buffer. Canonical definition; re-exported by `bun_string`, `bun_http_types`, -/// and `bun_url` (formerly each had a structurally-identical copy). Layout MUST -/// match `extern struct { offset: u32, length: u32 }` — C++ (`WebCore::FetchHeaders`) -/// and on-disk formats (lockfile, npm manifest cache) read it directly. +/// `(offset, length)` span into an external buffer. Layout MUST match +/// `struct { uint32_t offset, length; }` — C++ (`WebCore::FetchHeaders`) and +/// on-disk formats (lockfile, npm manifest cache) read it directly. #[repr(C)] #[derive(Clone, Copy, Default, Debug, PartialEq, Eq)] pub struct StringPointer { diff --git a/src/bundler/ParseTask.rs b/src/bundler/ParseTask.rs index 2910838fa900..9be5d50059a8 100644 --- a/src/bundler/ParseTask.rs +++ b/src/bundler/ParseTask.rs @@ -2425,14 +2425,7 @@ pub mod parse_worker { let output_format = topts.output_format; - // D042: `crate::options::jsx::Pragma` IS `bun_js_parser::options::JSX::Pragma` - // (both re-export `bun_options_types::jsx::Pragma`). `to_parser_jsx_pragma` - // applies the `_None → Automatic` runtime fold the old `From` bridge did so - // parser-side `== Automatic` checks keep their semantics. - let mut opts = ParserOptions::init( - crate::transpiler::to_parser_jsx_pragma(task.jsx.clone()), - loader, - ); + let mut opts = ParserOptions::init(task.jsx.clone(), loader); opts.bundle = true; opts.warn_about_unbundled_modules = false; // `AllowUnresolved` is the same nominal type on diff --git a/src/bundler/lib.rs b/src/bundler/lib.rs index 20338efed2b8..fcb3e8584263 100644 --- a/src/bundler/lib.rs +++ b/src/bundler/lib.rs @@ -245,7 +245,6 @@ pub mod options { pub use super::output_file::Value as OutputFileValue; /// `options.Format` — many ported call-sites spell this `OutputFormat`. pub use bun_options_types::Format as OutputFormat; - pub use bun_options_types::schema::api::DotEnvBehavior as EnvBehavior; /// Output kind of a build artifact (`OutputFile.output_kind`). /// diff --git a/src/bundler/options.rs b/src/bundler/options.rs index 66e0c833cd85..96daebfebc03 100644 --- a/src/bundler/options.rs +++ b/src/bundler/options.rs @@ -6,8 +6,9 @@ use bun_collections::{StringArrayHashMap, StringHashMap}; use bun_core::strings; use bun_core::{Global, Output}; use bun_dotenv as DotEnv; +use bun_dotenv::DotEnvBehavior; use bun_js_parser::parser::Runtime; -use bun_options_types::schema::api; +use bun_options_types::{BunInstall, TransformOptions}; use bun_resolver::fs as Fs; use bun_resolver::fs::PathResolverExt as _; use bun_resolver::package_json::{MacroMap as MacroRemap, PackageJSON}; @@ -333,10 +334,8 @@ pub use bun_options_types::WindowsOptions; // same nominal type. pub(crate) use bun_ast::Loader; -pub use bun_options_types::LOADER_API_NAMES; - /// Bundler-only `Loader` methods. Extension trait per PORTING.md crate-tier -/// rule — the canonical `Loader` lives in `bun_options_types` (lower tier) and +/// rule — the canonical `Loader` lives in `bun_ast` (lower tier) and /// cannot depend on `bun_http_types::MimeType`. Re-exported through /// `bun_bundler::options` so `use bun_bundler::options::LoaderExt;` makes /// `.to_mime_type()` etc. available on the single canonical type. @@ -828,13 +827,7 @@ pub(crate) mod default_user_defines { pub(crate) fn defines_from_transform_options( log: &mut bun_ast::Log, - // PERF: borrowed, not owned — the caller (`load_defines`) holds - // `transform_options` behind an `Arc`, so taking the `StringMap` by value - // forced a full deep clone of the `--define` map *every* VM init even though - // each value gets cloned again below on insert. Reading it through `&` keeps - // the per-value clone (the owned `RawDefines` map needs `Box<[u8]>`s) but - // drops the redundant outer `keys.clone() + values.clone()`. - maybe_input_define: Option<&api::StringMap>, + input_define: &[(Box<[u8]>, Box<[u8]>)], target: Target, env_loader: Option<&mut DotEnv::Loader>, framework_env: Option<&Env>, @@ -843,20 +836,15 @@ pub(crate) fn defines_from_transform_options( omit_unused_global_calls: bool, bump: &bun_alloc::Arena, ) -> Result, crate::Error> { - let (input_keys, input_values): (&[Box<[u8]>], &[Box<[u8]>]) = match maybe_input_define { - Some(m) => (&m.keys, &m.values), - None => (&[], &[]), - }; - let mut user_defines: defines::RawDefines = defines::RawDefines::default(); - user_defines.reserve(input_keys.len() + 4); - for (i, key) in input_keys.iter().enumerate() { - user_defines.insert(key.as_ref(), input_values[i].clone()); + user_defines.reserve(input_define.len() + 4); + for (key, value) in input_define { + user_defines.insert(key.as_ref(), value.clone()); } let mut environment_defines = defines::UserDefinesArray::default(); - let mut behavior = api::DotEnvBehavior::disable; + let mut behavior = DotEnvBehavior::Disable; 'load_env: { let Some(env) = env_loader else { @@ -866,11 +854,8 @@ pub(crate) fn defines_from_transform_options( break 'load_env; }; - debug_assert!(framework.behavior != api::DotEnvBehavior::None); - behavior = framework.behavior; - if behavior == api::DotEnvBehavior::LoadAllWithoutInlining - || behavior == api::DotEnvBehavior::disable + if behavior == DotEnvBehavior::LoadAllWithoutInlining || behavior == DotEnvBehavior::Disable { break 'load_env; } @@ -884,7 +869,7 @@ pub(crate) fn defines_from_transform_options( )?; } - if behavior != api::DotEnvBehavior::LoadAllWithoutInlining { + if behavior != DotEnvBehavior::LoadAllWithoutInlining { let quoted_node_env: Box<[u8]> = 'brk: { if let Some(node_env) = node_env { if !node_env.is_empty() { @@ -1010,20 +995,10 @@ impl Default for ResolveFileExtensionsGroup { } pub fn loaders_from_transform_options( - loaders: Option<&api::LoaderMap>, + input_loaders: &[(Box<[u8]>, Loader)], target: Target, ) -> Result, bun_alloc::AllocError> { - // Borrow the caller's `LoaderMap` (a `Vec` + `Vec>`); this fn - // only reads from it, so there's no need to clone it on every call. - let empty = api::LoaderMap::default(); - let input_loaders = loaders.unwrap_or(&empty); - let mut loader_values: Vec = Vec::with_capacity(input_loaders.loaders.len()); - - for input in &input_loaders.loaders { - loader_values.push(::from_api(*input)); - } - - let total_capacity = input_loaders.extensions.len() + let total_capacity = input_loaders.len() + if target.is_bun() { DEFAULT_LOADER_EXT_BUN.len() } else { @@ -1038,8 +1013,8 @@ pub fn loaders_from_transform_options( let mut loaders = StringArrayHashMap::::default(); loaders.reserve(u32::try_from(total_capacity).expect("int cast") as usize); - for (i, ext) in input_loaders.extensions.iter().enumerate() { - loaders.insert(ext, loader_values[i]); + for (ext, loader) in input_loaders { + loaders.insert(ext, *loader); } // contains+insert (only when absent); `Loader` is not `Default` @@ -1070,48 +1045,9 @@ pub fn loaders_from_transform_options( Ok(loaders) } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub enum SourceMapOption { - #[default] - None, - Inline, - External, - Linked, -} - -impl SourceMapOption { - pub fn from_api(source_map: Option) -> SourceMapOption { - match source_map.unwrap_or(api::SourceMapMode::None) { - api::SourceMapMode::External => SourceMapOption::External, - api::SourceMapMode::Inline => SourceMapOption::Inline, - api::SourceMapMode::Linked => SourceMapOption::Linked, - _ => SourceMapOption::None, - } - } - - pub fn to_api(source_map: Option) -> api::SourceMapMode { - match source_map.unwrap_or(SourceMapOption::None) { - SourceMapOption::External => api::SourceMapMode::External, - SourceMapOption::Inline => api::SourceMapMode::Inline, - SourceMapOption::Linked => api::SourceMapMode::Linked, - SourceMapOption::None => api::SourceMapMode::None, - } - } - - pub(crate) fn has_external_files(self) -> bool { - matches!(self, SourceMapOption::Linked | SourceMapOption::External) - } -} - -// hoisted from `impl SourceMapOption` — Rust forbids `static` in inherent impls. -bun_core::comptime_string_map! { - pub static SOURCE_MAP_OPTION_MAP: SourceMapOption = { - b"none" => SourceMapOption::None, - b"inline" => SourceMapOption::Inline, - b"external" => SourceMapOption::External, - b"linked" => SourceMapOption::Linked, - }; -} +pub use bun_options_types::bundle_enums::{ + PACKAGES_OPTION_MAP, PackagesOption, SOURCE_MAP_OPTION_MAP, SourceMapOption, +}; /// What `--compile` resolved to for this bundle. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] @@ -1133,36 +1069,6 @@ impl CompileMode { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum PackagesOption { - Bundle, - External, -} - -impl PackagesOption { - pub(crate) fn from_api(packages: Option) -> PackagesOption { - match packages.unwrap_or(api::PackagesMode::Bundle) { - api::PackagesMode::External => PackagesOption::External, - api::PackagesMode::Bundle => PackagesOption::Bundle, - } - } - - pub fn to_api(packages: Option) -> api::PackagesMode { - match packages.unwrap_or(PackagesOption::Bundle) { - PackagesOption::External => api::PackagesMode::External, - PackagesOption::Bundle => api::PackagesMode::Bundle, - } - } -} - -// hoisted from `impl PackagesOption` — Rust forbids `static` in inherent impls. -bun_core::comptime_string_map! { - pub static PACKAGES_OPTION_MAP: PackagesOption = { - b"external" => PackagesOption::External, - b"bundle" => PackagesOption::Bundle, - }; -} - /// BundleOptions is effectively webpack + babel pub struct BundleOptions<'a> { pub footer: Cow<'static, [u8]>, @@ -1232,13 +1138,13 @@ pub struct BundleOptions<'a> { pub import_path_format: ImportPathFormat, pub(crate) defines_loaded: bool, pub env: Env, - /// The raw `TransformOptions` as passed to `from_api`. Kept around because a + /// The raw `TransformOptions` as passed to `from_transform_options`. Kept around because a /// handful of places (jsx auto-detect, resolver `main_fields_is_default`, /// `configure_defines`, runtime VM/server config) re-read the original /// user-supplied flags after projection. `Arc` so `for_worker` is a /// pointer-clone instead of a deep clone of the (large) struct — /// workers never mutate it. - pub transform_options: std::sync::Arc, + pub transform_options: std::sync::Arc, pub(crate) polyfill_node_globals: bool, pub transform_only: bool, pub load_tsconfig_json: bool, @@ -1267,7 +1173,7 @@ pub struct BundleOptions<'a> { /// lifetime-extension cast at every call site (PORTING.md §Forbidden). /// The sole consumer (`PackageManager::init_with_runtime` via the resolver's /// `BundleOptions.install`) only reads through it. - pub install: Option>, + pub install: Option>, pub inlining: bool, pub inline_entrypoint_import_meta_main: bool, @@ -1489,14 +1395,14 @@ impl<'a> BundleOptions<'a> { /// /// SAFETY: `self.log` is non-null: `Transpiler::init_in_place` validates /// the pointer via `NonNull::new(log).expect(..)` before storing it and - /// before calling `from_api` (which has no other callers). + /// before calling `from_transform_options` (which has no other callers). /// The pointee is the caller-owned arena `Log` which outlives `self`. The /// same allocation is aliased into `Transpiler.log` / `Resolver.log` / /// `Linker.log` as raw `*mut`; a `&` here is sound so long as no caller /// holds a live `&mut Log` from one of those aliases concurrently. #[inline] pub(crate) fn log(&self) -> &bun_ast::Log { - // SAFETY: `self.log` is non-null after `from_api` and the caller-owned + // SAFETY: `self.log` is non-null after `from_transform_options` and the caller-owned // arena `Log` it points to outlives `self`; see method doc. unsafe { &*self.log } } @@ -1579,7 +1485,7 @@ impl<'a> BundleOptions<'a> { // No other `&mut Log` is live across this call (see `log_mut` // caller contract). self.log_mut(), - self.transform_options.define.as_ref(), + &self.transform_options.define, self.target, loader_, Some(&self.env), @@ -1598,10 +1504,10 @@ impl<'a> BundleOptions<'a> { self.loaders.get(ext).copied().unwrap_or(Loader::File) } - pub(crate) fn from_api( + pub(crate) fn from_transform_options( fs: &mut Fs::FileSystem, log: *mut bun_ast::Log, - transform: api::TransformOptions, + transform: TransformOptions, ) -> Result, crate::Error> { use core::sync::atomic::Ordering; @@ -1611,8 +1517,8 @@ impl<'a> BundleOptions<'a> { // than recursive `drop_in_place` over every `Box<[u8]>`/`Vec`. let transform = std::sync::Arc::new(transform); - let target = ::from_api(transform.target); - let loaders = loaders_from_transform_options(transform.loaders.as_ref(), target)?; + let target = transform.target.unwrap_or(Target::Browser); + let loaders = loaders_from_transform_options(&transform.loaders, target)?; let bundler_feature_flags = Runtime::Features::init_bundler_feature_flags( &transform .feature_flags @@ -1730,9 +1636,11 @@ impl<'a> BundleOptions<'a> { { analytics::features::define - .fetch_add(usize::from(transform.define.is_some()), Ordering::Relaxed); - analytics::features::loaders - .fetch_add(usize::from(transform.loaders.is_some()), Ordering::Relaxed); + .fetch_add(usize::from(!transform.define.is_empty()), Ordering::Relaxed); + analytics::features::loaders.fetch_add( + usize::from(!transform.loaders.is_empty()), + Ordering::Relaxed, + ); } opts.serve_plugins = transform @@ -1754,7 +1662,7 @@ impl<'a> BundleOptions<'a> { } if let Some(jsx_opts) = &transform.jsx { - opts.jsx = jsx::Pragma::from_api(jsx_opts.clone())?; + opts.jsx = jsx::Pragma::from_options(jsx_opts.clone())?; } if !transform.extension_order.is_empty() { @@ -1766,7 +1674,7 @@ impl<'a> BundleOptions<'a> { } if let Some(t) = transform.target { - opts.target = ::from_api(Some(t)); + opts.target = t; opts.main_fields = owned_string_list(Target::default_main_fields_map()[opts.target]); } @@ -1799,7 +1707,7 @@ impl<'a> BundleOptions<'a> { ImportPathFormat::AbsolutePath }; - opts.env.behavior = api::DotEnvBehavior::LoadAll; + opts.env.behavior = DotEnvBehavior::LoadAll; if transform.extension_order.is_empty() { // we must also support require'ing .node files static EXT_WITH_NODE: &[&[u8]] = &[ @@ -1841,9 +1749,9 @@ impl<'a> BundleOptions<'a> { ); opts.out_extensions = opts.target.out_extensions(); - opts.source_map = SourceMapOption::from_api(transform.source_map); + opts.source_map = transform.source_map.unwrap_or_default(); - opts.packages = PackagesOption::from_api(transform.packages); + opts.packages = transform.packages.unwrap_or_default(); opts.tree_shaking = opts.target.is_bun() || opts.production; opts.inlining = opts.tree_shaking; @@ -1997,7 +1905,7 @@ impl TransformResult { #[derive(Clone, Debug)] pub struct Env { - pub behavior: api::DotEnvBehavior, + pub behavior: DotEnvBehavior, pub prefix: Box<[u8]>, /// List of explicit env files to load (e..g specified by --env-file args) pub(crate) files: Box<[Box<[u8]>]>, @@ -2009,7 +1917,7 @@ pub struct Env { impl Default for Env { fn default() -> Self { Env { - behavior: api::DotEnvBehavior::disable, + behavior: DotEnvBehavior::Disable, prefix: Box::default(), files: Box::default(), disable_default_env_files: false, diff --git a/src/bundler/transpiler.rs b/src/bundler/transpiler.rs index 52ecde0c646c..ae690440941c 100644 --- a/src/bundler/transpiler.rs +++ b/src/bundler/transpiler.rs @@ -534,7 +534,7 @@ impl<'a> Transpiler<'a> { } if self.options.target == options::Target::BunMacro { - self.options.env.behavior = bun_options_types::schema::api::DotEnvBehavior::Prefix; + self.options.env.behavior = bun_dotenv::DotEnvBehavior::Prefix; self.options.env.prefix = Box::from(b"BUN_".as_slice()); } @@ -547,16 +547,8 @@ impl<'a> Transpiler<'a> { // explicit sources first so that default isn't mistaken for user intent // and `force_node_env` stays `Unspecified` (tsconfig jsx stays in control). let had_explicit_node_env = env_loader.get_node_env().is_some() - || self - .options - .transform_options - .define - .as_ref() - .is_some_and(|m| { - m.keys - .iter() - .any(|k| &**k == options::default_user_defines::node_env::KEY) - }); + || (self.options.transform_options.define.iter()) + .any(|(k, _)| &**k == options::default_user_defines::node_env::KEY); // `parse_env_json` needs a thread-local AST store to build // `E::String` nodes in. That work @@ -717,15 +709,15 @@ impl<'a> Transpiler<'a> { /// Load `.env` files into the env loader according to /// `options.env.behavior`. pub fn run_env_loader(&mut self, skip_default_env: bool) -> crate::Result<()> { - use bun_options_types::schema::api::DotEnvBehavior; + use bun_dotenv::DotEnvBehavior; // Derived once up front; no other live `&mut` to this `Loader` exists // for the duration of this call. let env: &mut dot_env::Loader = self.env_mut(); match self.options.env.behavior { - DotEnvBehavior::prefix - | DotEnvBehavior::load_all - | DotEnvBehavior::load_all_without_inlining => { + DotEnvBehavior::Prefix + | DotEnvBehavior::LoadAll + | DotEnvBehavior::LoadAllWithoutInlining => { // Process always has highest priority. Load process env vars // unconditionally before attempting directory traversal, so // that inherited environment variables are always available @@ -783,7 +775,7 @@ impl<'a> Transpiler<'a> { }; env.load(dir, &env_files, suffix, skip_default_env)?; } - DotEnvBehavior::disable => { + DotEnvBehavior::Disable => { env.load_process()?; if env.is_production() { self.options.set_production(true); @@ -791,7 +783,6 @@ impl<'a> Transpiler<'a> { self.resolver.opts.set_production(true); } } - DotEnvBehavior::_none => {} } if env.get(b"BUN_DISABLE_TRANSPILER").unwrap_or(b"0") == b"1" { @@ -966,7 +957,7 @@ pub struct ParseOptions<'a, 'b> { pub allow_bytecode_cache: bool, } -use bun_options_types::schema::api; +use bun_options_types::TransformOptions; // ── type unification (parse_maybe Js/Ts arm) ───────────────────────────── // `ModuleType`, `Define`, `RuntimeTranspilerCache` are single nominal types @@ -974,23 +965,7 @@ use bun_options_types::schema::api; // lower-tier crate; bundler re-exports). There are no by-value conversion // shims — `to_parser_module_type` is an identity fn and `parse_maybe` // threads `self.options.define` / `runtime_transpiler_cache` directly. -// -// D042 UNIFIED: `crate::options_impl::jsx::Pragma` IS -// `js_ast::parser::options::JSX::Pragma` (both re-export -// `bun_options_types::jsx::Pragma`). Only the `_None → Automatic` fold is -// applied so parser-side `== Automatic` checks in visitExpr/parseJSXElement -// keep their pre-unification semantics (parser only ever sees a resolved -// runtime). -#[inline] -pub(crate) fn to_parser_jsx_pragma( - mut p: crate::options_impl::jsx::Pragma, -) -> js_ast::parser::options::JSX::Pragma { - use crate::options_impl::jsx::Runtime; - if p.runtime == Runtime::_None { - p.runtime = Runtime::Automatic; - } - p -} +// `crate::options_impl::jsx::Pragma` IS `js_ast::parser::options::JSX::Pragma`. // `crate::options_impl::ModuleType` IS `js_ast::parser::options::ModuleType` // (both re-export `bun_options_types::bundle_enums::ModuleType`). Identity shim @@ -1059,7 +1034,7 @@ fn resolver_bundle_options_subset( jsx: src.jsx.clone(), // Spec `options.ResolveFileExtensions` — clone all four owned slices so // the resolver honours user `--extension-order` and the per-target - // `.node` augmentation `from_api` applied. + // `.node` augmentation `from_transform_options` applied. extension_order: ropts::ExtensionOrder { default: ropts::ExtensionOrderGroup { default: src.extension_order.default.default.clone(), @@ -1102,15 +1077,13 @@ fn resolver_bundle_options_subset( } }), global_cache: src.global_cache, - // Both sides store - // `Option>`, so this is a straight copy. install: src.install, load_package_json: src.load_package_json, load_tsconfig_json: src.load_tsconfig_json, main_field_extension_order: ropts::owned_string_list(src.main_field_extension_order), // `auto_main` is projected as a // bool: it's "default" iff the user did not pass `--main-fields` - // (`from_api` overwrites `main_fields` only when + // (`from_transform_options` overwrites `main_fields` only when // `transform.main_fields` is non-empty — options.rs:2231). main_fields: src.main_fields.clone(), main_fields_is_default: src.transform_options.main_fields.is_empty(), @@ -1138,7 +1111,7 @@ fn resolver_bundle_options_subset( impl<'a> Transpiler<'a> { /// Called by [`init_runtime_state`](../runtime/jsc_hooks.rs) /// to write `vm.transpiler`. Builds on: - /// * [`options::BundleOptions::from_api`] — `bun_bundler::options` + /// * [`options::BundleOptions::from_transform_options`] — `bun_bundler::options` /// * [`Resolver::init1`] — `bun_resolver` /// /// `log` / `env_loader_` are raw pointers (not `&'a mut`) to @@ -1147,7 +1120,7 @@ impl<'a> Transpiler<'a> { pub fn init( arena: &'a Arena, log: *mut bun_ast::Log, - opts: api::TransformOptions, + opts: TransformOptions, env_loader_: Option<*mut dot_env::Loader>, ) -> crate::Result> { let mut slot = core::mem::MaybeUninit::>::uninit(); @@ -1170,7 +1143,7 @@ impl<'a> Transpiler<'a> { dst: &mut core::mem::MaybeUninit>, arena: &'a Arena, log: *mut bun_ast::Log, - opts: api::TransformOptions, + opts: TransformOptions, env_loader_: Option<*mut dot_env::Loader>, ) -> crate::Result<()> { // Caller contract: `log` is the freshly-boxed per-VM `Log` from @@ -1182,7 +1155,7 @@ impl<'a> Transpiler<'a> { bun_ast::stmt::data::Store::create(); // These two `create()`s are eager (not deferred to the first `parse()`) // because option setup below needs the AST stores *unconditionally*: - // `from_api` → `defines_from_transform_options` always materialises at + // `from_transform_options` → `defines_from_transform_options` always materialises at // least `process.env.NODE_ENV` via `parse_env_json`, whose `E::String` // payload lands in the thread-local Expr store (then a `StoreResetGuard` // resets it — which `expect()`s the store exists). So there is no @@ -1244,15 +1217,16 @@ impl<'a> Transpiler<'a> { // .arena = arena, // }); - // `log` stays raw — `from_api` stores it in `BundleOptions.log: *mut` + // `log` stays raw — `from_transform_options` stores it in `BundleOptions.log: *mut` // and the same pointer is aliased into `Resolver::init1` / `Linker` // / the struct field below. No `&'a // mut Log` is materialized here, so the sibling raw pointers don't // invalidate a long-lived unique borrow under stacked borrows. // SAFETY: `fs` is the process-lifetime `Fs::FileSystem` singleton from // `init_file_system` above; this short `&mut *fs` is the only live - // borrow for the duration of `from_api`. - let bundle_options = options::BundleOptions::from_api(unsafe { &mut *fs }, log, opts)?; + // borrow for the duration of `from_transform_options`. + let bundle_options = + options::BundleOptions::from_transform_options(unsafe { &mut *fs }, log, opts)?; // `Resolver.opts` is the resolver-crate subset // (`bun_resolver::options::BundleOptions`), nominally distinct from this @@ -1531,7 +1505,7 @@ impl<'a> Transpiler<'a> { use js_ast::parser::options as p_opts; let mut opts = js_ast::ParserOptions::<'_> { ts: loader.is_typescript(), - jsx: to_parser_jsx_pragma(jsx), + jsx, keep_names: true, ignore_dce_annotations: self.options.ignore_dce_annotations, preserve_unused_imports_ts: false, @@ -2677,7 +2651,7 @@ impl<'a> Transpiler<'a> { pub fn transform( &mut self, log: *mut bun_ast::Log, - _opts: api::TransformOptions, + _opts: TransformOptions, ) -> crate::Result { let _ = self.enqueue_entry_points::(); diff --git a/src/bundler_jsc/lib.rs b/src/bundler_jsc/lib.rs index ad322194fe4b..b1603cda9a0f 100644 --- a/src/bundler_jsc/lib.rs +++ b/src/bundler_jsc/lib.rs @@ -7,9 +7,6 @@ // ────────────────────────────────────────────────────────────────────────── pub use bun_jsc::{ErrorableString, JSGlobalObject, JSValue, JsError, JsResult, VM}; -#[path = "source_map_mode_jsc.rs"] -pub mod source_map_mode_jsc; - #[path = "options_jsc.rs"] pub mod options_jsc; diff --git a/src/bundler_jsc/source_map_mode_jsc.rs b/src/bundler_jsc/source_map_mode_jsc.rs deleted file mode 100644 index 77f025b5b221..000000000000 --- a/src/bundler_jsc/source_map_mode_jsc.rs +++ /dev/null @@ -1,28 +0,0 @@ -//! `from_js` for `bun.schema.api.SourceMapMode` — kept here so the schema -//! module has no `JSGlobalObject`/`JSValue` references. - -use crate::{JSGlobalObject, JSValue, JsResult}; -use bun_options_types::schema::api::SourceMapMode; - -pub fn source_map_mode_from_js( - global: &JSGlobalObject, - value: JSValue, -) -> JsResult> { - if value.is_string() { - let str = value.to_slice_or_null(global)?; - let utf8 = str.slice(); - if utf8 == b"none" { - return Ok(Some(SourceMapMode::None)); - } - if utf8 == b"inline" { - return Ok(Some(SourceMapMode::Inline)); - } - if utf8 == b"external" { - return Ok(Some(SourceMapMode::External)); - } - if utf8 == b"linked" { - return Ok(Some(SourceMapMode::Linked)); - } - } - Ok(None) -} diff --git a/src/bunfig/Cargo.toml b/src/bunfig/Cargo.toml index 217101f938a1..c74dc25a98c2 100644 --- a/src/bunfig/Cargo.toml +++ b/src/bunfig/Cargo.toml @@ -25,7 +25,6 @@ libc.workspace = true bitflags.workspace = true thiserror.workspace = true bun_alloc.workspace = true -bun_api.workspace = true bun_bundler.workspace = true bun_clap.workspace = true bun_collections.workspace = true @@ -38,6 +37,7 @@ bun_resolver.workspace = true bun_url.workspace = true bun_ast.workspace = true bun_options_types.workspace = true +bun_dotenv.workspace = true bun_paths.workspace = true bun_standalone_graph.workspace = true bun_sys.workspace = true diff --git a/src/bunfig/bunfig.rs b/src/bunfig/bunfig.rs index 9d44523a5a87..3306968647ac 100644 --- a/src/bunfig/bunfig.rs +++ b/src/bunfig/bunfig.rs @@ -3,7 +3,7 @@ //! `Bunfig::parse` and the inner `Parser` route through the real //! `bun_parsers::{toml,json}` parsers (which produce the value-shaped //! `bun_ast::Expr` tree) and write into `ctx.args` -//! (`api::TransformOptions`), `ctx.install` (`api::BunInstall`), and the rest +//! (`TransformOptions`), `ctx.install` (`BunInstall`), and the rest //! of `ContextData`. #![allow(clippy::collapsible_if, clippy::needless_return)] @@ -16,13 +16,15 @@ use bun_ast::{E, Expr, ExprTag, expr::Data as ExprData}; use bun_parsers::json as json_parser; use bun_parsers::toml::TOML; +use bun_dotenv::DotEnvBehavior; use bun_install_types::NodeLinker::FromExprError; -use bun_options_types::LoaderExt as _; +use bun_install_types::NodeLinker::{NodeLinker, PnpmMatcher}; use bun_options_types::code_coverage_options::Reporters as CoverageReporters; use bun_options_types::context::{MacroImportReplacementMap, MacroMap, MacroOptions}; use bun_options_types::global_cache::GlobalCache; +use bun_options_types::jsx; use bun_options_types::offline_mode::PREFER as OFFLINE_PREFER; -use bun_options_types::schema::api; +use bun_options_types::{BunInstall, Ca, NpmRegistry, StringPairs}; use bun_options_types::command_tag::Tag as CommandTag; use bun_options_types::context::ContextData; @@ -228,14 +230,14 @@ impl<'a> Parser<'a> { fn load_log_level(&mut self, expr: &Expr) -> crate::Result<()> { self.expect_string(expr)?; let level = match expr.as_string(self.bump).unwrap_or(b"") { - b"debug" => api::MessageLevel::Debug, - b"error" => api::MessageLevel::Err, - b"warn" => api::MessageLevel::Warn, - b"info" => api::MessageLevel::Info, + b"debug" => bun_ast::Level::Debug, + b"error" => bun_ast::Level::Err, + b"warn" => bun_ast::Level::Warn, + b"info" => bun_ast::Level::Info, _ => { return self.add_error( expr.loc, - b"Invalid log level, must be one of debug, error, or warn", + b"Invalid log level, must be one of debug, info, warn, or error", ); } }; @@ -312,7 +314,7 @@ impl<'a> Parser<'a> { Ok(()) } - fn parse_define_map(&mut self, expr: &Expr) -> crate::Result { + fn parse_define_map(&mut self, expr: &Expr) -> crate::Result { self.expect(expr, ExprTag::EObject)?; let obj = expr.data.e_object().expect("infallible: variant checked"); let properties = obj.properties.slice(); @@ -320,8 +322,7 @@ impl<'a> Parser<'a> { .iter() .filter(|p| matches!(p.value.as_ref().unwrap().data, ExprData::EString(_))) .count(); - let mut keys: Vec> = Vec::with_capacity(valid_count); - let mut values: Vec> = Vec::with_capacity(valid_count); + let mut map = StringPairs::with_capacity(valid_count); for prop in properties { let ExprData::EString(v) = &prop .value @@ -335,10 +336,12 @@ impl<'a> Parser<'a> { else { continue; }; - keys.push(estring_to_owned(k, self.bump)); - values.push(estring_to_owned(v, self.bump)); + map.push(( + estring_to_owned(k, self.bump), + estring_to_owned(v, self.bump), + )); } - Ok(api::StringMap { keys, values }) + Ok(map) } // `cmd` is a runtime arg rather than a const generic — @@ -360,7 +363,7 @@ impl<'a> Parser<'a> { } if let Some(expr) = json.get(b"define") { - self.ctx.args.define = Some(self.parse_define_map(&expr)?); + self.ctx.args.define = self.parse_define_map(&expr)?; } if let Some(expr) = json.get(b"origin") { @@ -717,9 +720,8 @@ impl<'a> Parser<'a> { { if let Some(install_obj) = json.get_object(b"install") { // Ensure ctx.install is allocated so later passes can write into it - // once api::BunInstall fields land. if self.ctx.install.is_none() { - self.ctx.install = Some(Box::new(api::BunInstall::default())); + self.ctx.install = Some(Box::new(BunInstall::default())); } if let Some(auto_install_expr) = install_obj.get(b"auto") { @@ -947,20 +949,20 @@ impl<'a> Parser<'a> { let mut jsx_factory: Box<[u8]> = Box::default(); let mut jsx_fragment: Box<[u8]> = Box::default(); let mut jsx_import_source: Box<[u8]> = Box::default(); - let mut jsx_runtime = api::JsxRuntime::Automatic; + let mut jsx_runtime = jsx::Runtime::Automatic; let mut jsx_dev = true; if let Some(expr) = json.get(b"jsx") { if let Some(value) = expr.as_string(self.bump) { if value == b"react" { - jsx_runtime = api::JsxRuntime::Classic; + jsx_runtime = jsx::Runtime::Classic; } else if value == b"solid" { - jsx_runtime = api::JsxRuntime::Solid; + jsx_runtime = jsx::Runtime::Solid; } else if value == b"react-jsx" { - jsx_runtime = api::JsxRuntime::Automatic; + jsx_runtime = jsx::Runtime::Automatic; jsx_dev = false; } else if value == b"react-jsxDEV" { - jsx_runtime = api::JsxRuntime::Automatic; + jsx_runtime = jsx::Runtime::Automatic; jsx_dev = true; } else { self.add_error( @@ -999,7 +1001,7 @@ impl<'a> Parser<'a> { jsx.runtime = jsx_runtime; jsx.development = jsx_dev; } else { - self.ctx.args.jsx = Some(api::Jsx { + self.ctx.args.jsx = Some(jsx::Options { factory: jsx_factory, fragment: jsx_fragment, import_source: jsx_import_source, @@ -1055,8 +1057,7 @@ impl<'a> Parser<'a> { self.expect(&expr, ExprTag::EObject)?; let obj = expr.data.e_object().expect("infallible: variant checked"); let properties = obj.properties.slice(); - let mut loader_names: Vec> = Vec::with_capacity(properties.len()); - let mut loader_values: Vec = Vec::with_capacity(properties.len()); + self.ctx.args.loaders = Vec::with_capacity(properties.len()); for item in properties { let key_expr = item.key.as_ref().expect("infallible: prop has key"); let key = key_expr @@ -1081,13 +1082,8 @@ impl<'a> Parser<'a> { self.add_error(value.loc, b"Invalid loader")?; continue; }; - loader_names.push(key.into()); - loader_values.push(loader.to_api()); + self.ctx.args.loaders.push((key.into(), loader)); } - self.ctx.args.loaders = Some(api::LoaderMap { - extensions: loader_names, - loaders: loader_values, - }); } Ok(()) @@ -1181,20 +1177,12 @@ impl Bunfig { // ───────────────────────────────────────────────────────────────────────────── impl<'a> Parser<'a> { - fn parse_registry_url_string(&mut self, str: &E::EString) -> crate::Result { - // Dedup D009: body is the canonical port in `bun_api::npm_registry`. - // The api `Parser` is generic over log/source and never reads them for - // this path, so we just hand it our reborrowed handles. - let bytes = str.string(self.bump)?; - Ok(bun_api::npm_registry::Parser { - log: &mut *self.log, - source: self.source, - } - .parse_registry_url_string_impl(bytes)?) + fn parse_registry_url_string(&mut self, str: &E::EString) -> crate::Result { + Ok(NpmRegistry::from_url(str.string(self.bump)?)) } - fn parse_registry_object(&mut self, obj: &E::Object) -> crate::Result { - let mut registry = api::NpmRegistry::default(); + fn parse_registry_object(&mut self, obj: &E::Object) -> crate::Result { + let mut registry = NpmRegistry::default(); if let Some(url) = obj.get(b"url") { self.expect_string(&url)?; @@ -1228,7 +1216,7 @@ impl<'a> Parser<'a> { Ok(registry) } - fn parse_registry(&mut self, expr: &Expr) -> crate::Result { + fn parse_registry(&mut self, expr: &Expr) -> crate::Result { match &expr.data { ExprData::EString(s) => self.parse_registry_url_string(s), ExprData::EObject(o) => self.parse_registry_object(o), @@ -1237,7 +1225,7 @@ impl<'a> Parser<'a> { expr.loc, b"Expected registry to be a URL string or an object", )?; - Ok(api::NpmRegistry::default()) + Ok(NpmRegistry::default()) } } } @@ -1256,7 +1244,7 @@ impl<'a> Parser<'a> { fn parse_install_inner( &mut self, - install: &mut api::BunInstall, + install: &mut BunInstall, install_obj: &Expr, ) -> crate::Result<()> { if let Some(cafile) = install_obj.get(b"cafile") { @@ -1283,10 +1271,10 @@ impl<'a> Parser<'a> { } } } - install.ca = Some(api::Ca::List(list.into())); + install.ca = Some(Ca::List(list.into())); } ExprData::EString(s) => { - install.ca = Some(api::Ca::Str(estring_to_owned(s, self.bump))); + install.ca = Some(Ca::Str(estring_to_owned(s, self.bump))); } _ => { self.add_error( @@ -1357,7 +1345,7 @@ impl<'a> Parser<'a> { if let Some(node_linker_expr) = install_obj.get(b"linker") { self.expect_string(&node_linker_expr)?; if let Some(s) = node_linker_expr.as_string(self.bump) { - install.node_linker = api::NodeLinker::from_str(s); + install.node_linker = NodeLinker::from_str(s); if install.node_linker.is_none() { self.add_error( node_linker_expr.loc, @@ -1388,18 +1376,6 @@ impl<'a> Parser<'a> { if let Some(v) = lockfile_expr.get(b"save").and_then(|e| e.as_bool()) { install.save_lockfile = Some(v); } - if let Some(v) = lockfile_expr - .get(b"path") - .and_then(|e| e.as_string(self.bump)) - { - install.lockfile_path = Some(v.into()); - } - if let Some(v) = lockfile_expr - .get(b"savePath") - .and_then(|e| e.as_string(self.bump)) - { - install.save_lockfile_path = Some(v.into()); - } } if let Some(v) = install_obj.get(b"optional").and_then(|e| e.as_bool()) { @@ -1536,13 +1512,13 @@ impl<'a> Parser<'a> { }; if let Some(public_hoist_pattern_expr) = install_obj.get(b"publicHoistPattern") { install.public_hoist_pattern = Some( - api::PnpmMatcher::from_expr(&public_hoist_pattern_expr, self.log, self.source) + PnpmMatcher::from_expr(&public_hoist_pattern_expr, self.log, self.source) .map_err(remap)?, ); } if let Some(hoist_pattern_expr) = install_obj.get(b"hoistPattern") { install.hoist_pattern = Some( - api::PnpmMatcher::from_expr(&hoist_pattern_expr, self.log, self.source) + PnpmMatcher::from_expr(&hoist_pattern_expr, self.log, self.source) .map_err(remap)?, ); } @@ -1615,7 +1591,7 @@ impl<'a> Parser<'a> { } if let Some(expr) = serve_obj.get(b"define") { - self.ctx.args.serve_define = Some(self.parse_define_map(&expr)?); + self.ctx.args.serve_define = self.parse_define_map(&expr)?; } self.ctx.args.bunfig_path = Box::<[u8]>::from(self.source.path.text); @@ -1628,23 +1604,23 @@ impl<'a> Parser<'a> { if let Some(env) = serve_obj.get(b"env") { match &env.data { ExprData::ENull(_) => { - self.ctx.args.serve_env_behavior = api::DotEnvBehavior::disable; + self.ctx.args.serve_env_behavior = Some(DotEnvBehavior::Disable); } ExprData::EBoolean(b) => { - self.ctx.args.serve_env_behavior = if b.value { - api::DotEnvBehavior::load_all + self.ctx.args.serve_env_behavior = Some(if b.value { + DotEnvBehavior::LoadAll } else { - api::DotEnvBehavior::disable - }; + DotEnvBehavior::Disable + }); } ExprData::EString(str) => { let slice = str.string(self.bump)?; - match api::DotEnvBehavior::parse_str(slice) { + match DotEnvBehavior::parse_str(slice) { Ok((behavior, prefix)) => { if let Some(prefix) = prefix { self.ctx.args.serve_env_prefix = Some(Box::<[u8]>::from(prefix)); } - self.ctx.args.serve_env_behavior = behavior; + self.ctx.args.serve_env_behavior = Some(behavior); } Err(()) => { self.add_error( diff --git a/src/codegen/replacements.ts b/src/codegen/replacements.ts index af31730db055..8bdccedb36e0 100644 --- a/src/codegen/replacements.ts +++ b/src/codegen/replacements.ts @@ -1,3 +1,5 @@ +import { readFileSync } from "node:fs"; +import { join } from "node:path"; import NodeErrors from "../jsc/bindings/ErrorCode.ts"; import jsclasses from "./../jsc/bindings/js_classes"; import { sliceSourceCode } from "./builtin-parser"; @@ -88,46 +90,42 @@ replacements.push({ to: "extends __no_intrinsic__%1", }); -// These enums map to $IdToLabel and $LabelToId (ids start at 1) +/** + * The labels of a `#[repr(u8)]` Rust enum, indexed by discriminant. Each + * variant's label comes from `labelOf`, or by default from the variant name the + * way strum's `serialize_all = "snake_case"` spells it. + */ +function rustEnumLabels(file: string, enumName: string, labelOf?: (variant: string) => string): string[] { + const source = readFileSync(join(import.meta.dir, file), "utf8"); + const body = source.match(new RegExp(`pub enum ${enumName} \\{([^}]*)\\}`))?.[1]; + if (!body) throw new Error(`replacements.ts: could not find \`pub enum ${enumName}\` in ${file}`); + labelOf ??= variant => variant.replace(/(?<=[a-z0-9])([A-Z])/g, "_$1").toLowerCase(); + const labels: string[] = []; + for (const [, variant, id] of body.matchAll(/^\s*([A-Z][A-Za-z0-9]*)\s*=\s*(\d+),/gm)) { + labels[Number(id)] = labelOf(variant); + } + if (labels.length === 0 || labels.includes(undefined!)) { + throw new Error(`replacements.ts: \`${enumName}\` discriminants in ${file} must be dense and start at 0`); + } + return labels; +} + +/** `bun_ast::ImportKind::label()`: `ImportKind::Stmt => b"import-statement"`, … */ +function importKindLabel(variant: string): string { + const source = readFileSync(join(import.meta.dir, "../ast/lib.rs"), "utf8"); + const arms = source.match(/pub fn label\(self\) -> &'static \[u8\] \{\s*match self \{([^}]*)\}/)?.[1]; + const label = arms?.match(new RegExp(`ImportKind::${variant} => b"([^"]*)"`))?.[1]; + if (label === undefined) + throw new Error(`replacements.ts: no ImportKind::label() arm for ${variant} in src/ast/lib.rs`); + return label; +} + +// These enums map to $IdToLabel and $LabelToId, id == Rust discriminant. // Make sure to define in ./builtins.d.ts export const enums = { - // Ids are the `bun_options_types::schema::api::Loader` discriminants - // (JSBundler passes those numbers to BundlerPlugin.ts). - Loader: [ - "jsx", - "js", - "ts", - "tsx", - "css", - "file", - "json", - "jsonc", - "toml", - "wasm", - "napi", - "base64", - "dataurl", - "text", - "bunsh", - "sqlite", - "sqlite_embedded", - "html", - "yaml", - "json5", - "md", - "xml", - ], - ImportKind: [ - "entry-point-run", - "entry-point-build", - "import-statement", - "require-call", - "dynamic-import", - "require-resolve", - "import-rule", - "url-token", - "internal", - ], + // JSBundler passes these to BundlerPlugin.ts (and gets loaders back) as numbers. + Loader: rustEnumLabels("../ast/loader.rs", "Loader"), + ImportKind: rustEnumLabels("../ast/lib.rs", "ImportKind", importKindLabel), }; // These are passed to --define to the bundler @@ -151,7 +149,7 @@ export const define: Record = { for (const [name, keys] of Object.entries(enums)) { define[`$${name}IdToLabel`] = "[" + keys.map(k => `"${k}"`).join(", ") + "]"; - define[`$${name}LabelToId`] = "{" + keys.map((k, i) => `"${k}": ${i + 1}`).join(", ") + "}"; + define[`$${name}LabelToId`] = "{" + keys.map((k, i) => `"${k}": ${i}`).join(", ") + "}"; } for (const name of globalsToPrefix) { diff --git a/src/dotenv/env_loader.rs b/src/dotenv/env_loader.rs index 84222b703430..aa53dd999761 100644 --- a/src/dotenv/env_loader.rs +++ b/src/dotenv/env_loader.rs @@ -37,49 +37,37 @@ pub trait DirEntryProbe { // is provided there — see src/resolver/lib.rs. No impl here; that would be a // dep-cycle. -/// Canonical definition; re-exported as -/// `bun_options_types::schema::api::DotEnvBehavior` for higher tiers. -#[repr(u32)] +/// How `process.env.*` values are exposed to bundled code. #[derive(Copy, Clone, Eq, PartialEq, Debug, Default)] -#[allow(non_camel_case_types)] pub enum DotEnvBehavior { #[default] - _none = 0, - disable = 1, - prefix = 2, - load_all = 3, - load_all_without_inlining = 4, + Disable, + /// Only inline variables starting with a configured prefix. + Prefix, + LoadAll, + LoadAllWithoutInlining, } -#[allow(non_upper_case_globals)] impl DotEnvBehavior { - // PascalCase aliases — downstream callers (bundler/options.rs, bundler/defines.rs, - // runtime/api/JSBundler.rs) name the variants both ways. - pub const None: Self = Self::_none; - pub const Disable: Self = Self::disable; - pub const Prefix: Self = Self::prefix; - pub const LoadAll: Self = Self::load_all; - pub const LoadAllWithoutInlining: Self = Self::load_all_without_inlining; - /// String-branch classifier shared by bunfig (serve.env) and /// JSBundler (Bun.build env). Only the *string* arm is common to /// both specs — the surrounding null/bool/number dispatch and the error /// reporting intentionally diverge per call site, so they stay inline there. /// /// Returns `Ok((behavior, prefix))` where `prefix` is `Some(&s[..idx])` only for - /// `DotEnvBehavior::prefix`; `Err(())` means the string is none of + /// `DotEnvBehavior::Prefix`; `Err(())` means the string is none of /// `"inline"` / `"disable"` / contains-`*`, and the caller emits its own /// site-specific diagnostic. pub fn parse_str(s: &[u8]) -> Result<(Self, Option<&[u8]>), ()> { if s == b"inline" { - Ok((Self::load_all, None)) + Ok((Self::LoadAll, None)) } else if s == b"disable" { - Ok((Self::disable, None)) + Ok((Self::Disable, None)) } else if let Some(asterisk) = strings::index_of_char_usize(s, b'*') { if asterisk > 0 { - Ok((Self::prefix, Some(&s[..asterisk]))) + Ok((Self::Prefix, Some(&s[..asterisk]))) } else { - Ok((Self::load_all, None)) + Ok((Self::LoadAll, None)) } } else { Err(()) diff --git a/src/http/HeaderBuilder.rs b/src/http/HeaderBuilder.rs index 1de5b666a697..d4433d230c7f 100644 --- a/src/http/HeaderBuilder.rs +++ b/src/http/HeaderBuilder.rs @@ -1,7 +1,8 @@ use bun_alloc::AllocError; use bun_core::StringBuilder; -use crate::headers::{Entry, EntryList, api}; +use crate::headers::{Entry, EntryList}; +use bun_core::StringPointer; #[derive(Default)] pub struct HeaderBuilder { @@ -27,14 +28,14 @@ impl HeaderBuilder { pub fn append(&mut self, name: impl AsRef<[u8]>, value: impl AsRef<[u8]>) { let name = name.as_ref(); let value = value.as_ref(); - let name_ptr = api::StringPointer { + let name_ptr = StringPointer { offset: self.content.len as u32, length: name.len() as u32, }; let _ = self.content.append(name); - let value_ptr = api::StringPointer { + let value_ptr = StringPointer { offset: self.content.len as u32, length: value.len() as u32, }; @@ -52,13 +53,13 @@ impl HeaderBuilder { /// would desync the byte length pre-reserved by `count`. pub fn append_bytes_value(&mut self, name: impl AsRef<[u8]>, prefix: &[u8], value: &[u8]) { let name = name.as_ref(); - let name_ptr = api::StringPointer { + let name_ptr = StringPointer { offset: self.content.len as u32, length: name.len() as u32, }; let _ = self.content.append(name); - let value_ptr = api::StringPointer { + let value_ptr = StringPointer { offset: self.content.len as u32, length: (prefix.len() + value.len()) as u32, }; @@ -72,7 +73,7 @@ impl HeaderBuilder { pub fn append_fmt(&mut self, name: impl AsRef<[u8]>, args: core::fmt::Arguments<'_>) { let name = name.as_ref(); - let name_ptr = api::StringPointer { + let name_ptr = StringPointer { offset: self.content.len as u32, length: name.len() as u32, }; @@ -83,7 +84,7 @@ impl HeaderBuilder { // builder buffer; capture its length, then re-read `content.len`. let value_len = self.content.fmt(args).len(); - let value_ptr = api::StringPointer { + let value_ptr = StringPointer { offset: (self.content.len - value_len) as u32, length: value_len as u32, }; diff --git a/src/http/Headers.rs b/src/http/Headers.rs index aed96bbed638..eb8dd23b3cbb 100644 --- a/src/http/Headers.rs +++ b/src/http/Headers.rs @@ -1,11 +1,6 @@ use bun_picohttp as picohttp; -// `bun.schema.api.StringPointer` — canonical type is `bun_core::StringPointer`; -// `bun_http_types` re-exports it. Public: downstream crates (e.g. -// bun_install::NetworkTask) build raw `Entry` records and need the field type. -pub mod api { - pub use bun_http_types::ETag::StringPointer; -} +use bun_core::StringPointer; // LAYERING: `Headers` (and its tier-safe inherent methods: `memory_cost`, // `get`, `append`, `get_content_*`, `as_str`, `Clone`) is owned by @@ -54,11 +49,11 @@ impl HeadersExt for Headers { // Capacity was reserved above so `append_assume_capacity` is safe. result.entries.append_assume_capacity(Entry { - name: api::StringPointer { + name: StringPointer { offset: name_offset, length: name.len() as u32, }, - value: api::StringPointer { + value: StringPointer { offset: value_offset, length: value.len() as u32, }, diff --git a/src/http_jsc/headers_jsc.rs b/src/http_jsc/headers_jsc.rs index f23dd12bdcb8..fa5c5dbb90a3 100644 --- a/src/http_jsc/headers_jsc.rs +++ b/src/http_jsc/headers_jsc.rs @@ -6,7 +6,7 @@ use core::sync::atomic::Ordering; use bun_core::{StringPointer, ZigString}; use bun_http::Headers; -use bun_http::headers::{EntryList, api}; +use bun_http::headers::EntryList; use bun_jsc::{CallFrame, FetchHeaders, HTTPHeaderName, JSGlobalObject, JSValue, JsResult}; /// Moved up from `bun_http` so it can @@ -68,9 +68,9 @@ pub fn from_fetch_headers( let sliced = headers.entries.slice(); // SAFETY: `Name`/`Value` columns are both `StringPointer`; `Slice::items_raw` // contract is satisfied. Disjoint backing memory ⇒ no aliasing. - let names_ptr: *mut api::StringPointer = sliced.items_raw::<"name", api::StringPointer>(); + let names_ptr: *mut StringPointer = sliced.items_raw::<"name", StringPointer>(); // SAFETY: same `items_raw` contract as above; `value` column is a disjoint allocation. - let values_ptr: *mut api::StringPointer = sliced.items_raw::<"value", api::StringPointer>(); + let values_ptr: *mut StringPointer = sliced.items_raw::<"value", StringPointer>(); // Zero-init so any slot `copy_to` fails to write (iterator skip, count // desync) reads as `{0, 0}` — a valid empty slice — rather than garbage. // SAFETY: both columns hold exactly `header_count` `StringPointer` slots. @@ -91,7 +91,7 @@ pub fn from_fetch_headers( // SAFETY: header_count >= 1 (incremented above); names_ptr points to a // live column of `header_count` slots. unsafe { - *names_ptr.add(header_count as usize - 1) = api::StringPointer { + *names_ptr.add(header_count as usize - 1) = StringPointer { offset: buf_len_before_content_type, length: u32::try_from(ct.len()).unwrap(), }; @@ -101,7 +101,7 @@ pub fn from_fetch_headers( .copy_from_slice(body_ct); // SAFETY: see above. unsafe { - *values_ptr.add(header_count as usize - 1) = api::StringPointer { + *values_ptr.add(header_count as usize - 1) = StringPointer { offset: buf_len_before_content_type + u32::try_from(ct.len()).unwrap(), length: u32::try_from(body_ct.len()).unwrap(), }; diff --git a/src/http_types/ETag.rs b/src/http_types/ETag.rs index 0206898525c0..1e0cbfebd6c8 100644 --- a/src/http_types/ETag.rs +++ b/src/http_types/ETag.rs @@ -174,9 +174,6 @@ pub fn if_none_match( // - `to_fetch_headers` — extension-trait in bun_http_jsc // ═══════════════════════════════════════════════════════════════════════ -/// `bun.schema.api.StringPointer` — canonical definition lives in `bun_core` -/// (T0, already a dep). Re-exported so `HeaderEntry`'s field type and -/// `bun_http::headers::api::StringPointer` keep resolving. pub use bun_core::StringPointer; #[derive(Copy, Clone, Default)] diff --git a/src/ini/Cargo.toml b/src/ini/Cargo.toml index de98bc6a9fac..24a7def1e8b7 100644 --- a/src/ini/Cargo.toml +++ b/src/ini/Cargo.toml @@ -19,7 +19,7 @@ enumset.workspace = true libc.workspace = true bitflags.workspace = true thiserror.workspace = true -bun_api.workspace = true +bun_options_types.workspace = true bun_base64.workspace = true bun_dotenv.workspace = true bun_install_types.workspace = true diff --git a/src/ini/lib.rs b/src/ini/lib.rs index a498336ad8f7..a9ad23bb2860 100644 --- a/src/ini/lib.rs +++ b/src/ini/lib.rs @@ -199,7 +199,6 @@ mod draft { use core::ptr; use bun_alloc::{AllocError, Arena, ArenaVec, ArenaVecExt as _}; - use bun_api::{self, BunInstall, NpmRegistry, npm_registry}; use bun_ast::E::Rope; use bun_ast::{E, Expr, ExprData, StoreRef}; use bun_ast::{Loc, Log, Source}; @@ -207,6 +206,7 @@ mod draft { use bun_core::ZStr; use bun_core::{Global, Output}; use bun_dotenv::Loader as DotEnvLoader; + use bun_options_types::{BunInstall, Ca, NpmRegistry}; use bun_url::URL; use super::{ @@ -1152,9 +1152,6 @@ mod draft { pub struct ScopeIterator<'a> { pub(crate) config: &'a E::Object, - pub(crate) source: &'a Source, - pub(crate) log: &'a mut Log, - pub(crate) prop_idx: usize, pub(crate) count: bool, } @@ -1165,9 +1162,9 @@ mod draft { } impl<'a> ScopeIterator<'a> { - pub(crate) fn next(&mut self) -> OOM>> { + pub(crate) fn next(&mut self) -> Option> { if self.prop_idx >= self.config.properties.len_u32() as usize { - return Ok(None); + return None; } let prop_idx = self.prop_idx; self.prop_idx += 1; @@ -1183,25 +1180,21 @@ mod draft { let registry = 'brk: { if let Some(value) = prop.value { if let Some(str_) = value.as_utf8_string_literal() { - let mut parser = npm_registry::Parser { - log: &mut *self.log, - source: self.source, - }; - break 'brk parser.parse_registry_url_string_impl(str_)?; + break 'brk NpmRegistry::from_url(str_); } } - return Ok(Some(IniOption::None)); + return Some(IniOption::None); }; - return Ok(Some(IniOption::Some(ScopeItem { + return Some(IniOption::Some(ScopeItem { scope: Box::<[u8]>::from(&key[1..key.len() - b":registry".len()]), registry, - }))); + })); } } } } - Ok(Some(IniOption::None)) + Some(IniOption::None) } } @@ -1285,12 +1278,7 @@ mod draft { if let Some(query) = out.as_property(b"registry") { if let Some(str_) = query.expr.as_utf8_string_literal() { - let mut p = bun_api::npm_registry::Parser { - log: &mut *log, - source, - }; - install.default_registry = - Some(p.parse_registry_url_string_impl(&Box::<[u8]>::from(str_))?); + install.default_registry = Some(NpmRegistry::from_url(str_)); } } @@ -1312,7 +1300,7 @@ mod draft { if let Some(query) = out.as_property(b"ca") { if let Some(str_) = query.expr.as_utf8_string_literal() { - install.ca = Some(bun_api::Ca::Str(Box::<[u8]>::from(str_))); + install.ca = Some(Ca::Str(Box::<[u8]>::from(str_))); } else if let ExprData::EArray(arr) = &query.expr.data { let mut list: Vec> = Vec::with_capacity(arr.items.len_u32() as usize); for item in arr.items.slice() { @@ -1320,7 +1308,7 @@ mod draft { list.push(Box::<[u8]>::from(s)); } } - install.ca = Some(bun_api::Ca::List(list.into_boxed_slice())); + install.ca = Some(Ca::List(list.into_boxed_slice())); } } @@ -1471,14 +1459,12 @@ mod draft { let mut iter = ScopeIterator { config: out_obj, count: true, - source, - log, prop_idx: 0, }; let scope_count = { let mut count: usize = 0; - while let Some(o) = iter.next()? { + while let Some(o) = iter.next() { if matches!(o, IniOption::Some(_)) { count += 1; } @@ -1494,7 +1480,7 @@ mod draft { iter.prop_idx = 0; iter.count = false; - while let Some(val) = iter.next()? { + while let Some(val) = iter.next() { if let Some(result) = val.get() { let registry = result.registry.clone(); registry_map.scopes.put(&*result.scope, registry)?; diff --git a/src/install/NetworkTask.rs b/src/install/NetworkTask.rs index e1ea90a189e5..e9921393015c 100644 --- a/src/install/NetworkTask.rs +++ b/src/install/NetworkTask.rs @@ -620,11 +620,11 @@ impl NetworkTask { DEFAULT_HEADERS_BUF }; header_builder.entries.append(http::headers::Entry { - name: http::headers::api::StringPointer { + name: bun_core::StringPointer { offset: 0, length: "Accept".len() as u32, }, - value: http::headers::api::StringPointer { + value: bun_core::StringPointer { offset: "Accept".len() as u32, length: (header_buf.len() - "Accept".len()) as u32, }, diff --git a/src/install/PackageManager.rs b/src/install/PackageManager.rs index ee11c66510a1..11d52a7d8676 100644 --- a/src/install/PackageManager.rs +++ b/src/install/PackageManager.rs @@ -7,7 +7,6 @@ use crate::Error; use crate::bun_fs as fs; use crate::bun_fs::FileSystem; use crate::bun_progress::{Node as ProgressNode, Progress}; -use crate::bun_schema::api as Api; use bun_alloc::AllocError; use bun_collections::linear_fifo::{DynamicBuffer, StaticBuffer}; use bun_collections::{ArrayHashMap, HashMap, HiveArrayFallback, LinearFifo, StringArrayHashMap}; @@ -20,6 +19,7 @@ use bun_event_loop::MiniEventLoop::MiniEventLoop; use bun_event_loop::{self, AnyEventLoop, EventLoopHandle}; use bun_http as http; use bun_ini as ini; +use bun_options_types::BunInstall; use bun_paths::resolve_path::{self, PosixToWinNormalizer, platform}; use bun_paths::{DELIMITER, PathBuffer, SEP, SEP_STR}; use bun_semver as Semver; @@ -1755,11 +1755,9 @@ pub fn init( let mut buf = PathBuffer::uninit(); let parts = [b"./.npmrc" as &[u8]]; - let install_ref = ctx.install.get_or_insert_with(|| { - // `Api::BunInstall` derives `Default` (all fields `None`/empty). - // Own via `Box` — never `Box::leak`. - Box::new(Api::BunInstall::default()) - }); + let install_ref = ctx + .install + .get_or_insert_with(|| Box::new(BunInstall::default())); let npmrc_local = ZBox::from_bytes(b".npmrc"); ini::load_npmrc_config( &mut **install_ref, @@ -1771,11 +1769,9 @@ pub fn init( ], ); } else { - let install_ref = ctx.install.get_or_insert_with(|| { - // `Api::BunInstall` derives `Default` (all fields `None`/empty). - // Own via `Box` — never `Box::leak`. - Box::new(Api::BunInstall::default()) - }); + let install_ref = ctx + .install + .get_or_insert_with(|| Box::new(BunInstall::default())); let npmrc_local = ZBox::from_bytes(b".npmrc"); ini::load_npmrc_config(&mut **install_ref, env, true, &[&*npmrc_local]); } @@ -2178,10 +2174,10 @@ pub fn init( pub(crate) fn init_with_runtime( log: &mut bun_ast::Log, // Used read-only (`Options::load` only ever reads `config.*`). - // Upstream storage is `Option>` (bundler + resolver + // Upstream storage is `Option>` (bundler + resolver // opts); taking `&mut` here would force a const→mut provenance launder at // the resolver call site. - bun_install: Option<&Api::BunInstall>, + bun_install: Option<&BunInstall>, cli: CommandLineArguments, env: &mut dot_env::Loader, ) -> crate::Result<*mut PackageManager> { @@ -2205,7 +2201,7 @@ pub(crate) fn init_with_runtime( fn init_with_runtime_once( log: &mut bun_ast::Log, - bun_install: Option<&Api::BunInstall>, + bun_install: Option<&BunInstall>, cli: CommandLineArguments, env: &mut dot_env::Loader, ) -> crate::Result<()> { diff --git a/src/install/PackageManager/PackageManagerOptions.rs b/src/install/PackageManager/PackageManagerOptions.rs index 800be1444fb2..afba5b0cd007 100644 --- a/src/install/PackageManager/PackageManagerOptions.rs +++ b/src/install/PackageManager/PackageManagerOptions.rs @@ -1,6 +1,7 @@ -use crate::bun_schema::api as Api; use bun_core::ZStr; use bun_core::{Output, env_var}; +use bun_install_types::NodeLinker::PnpmMatcher; +use bun_options_types::{BunInstall, Ca, NpmRegistry}; use bun_paths::PathBuffer; use super::Subcommand; @@ -72,8 +73,8 @@ pub struct Options { /// isolated installs (pnpm-like) or hoisted installs (yarn-like, original) pub(crate) node_linker: NodeLinker, - pub(crate) public_hoist_pattern: Option, - pub(crate) hoist_pattern: Option, + pub(crate) public_hoist_pattern: Option, + pub(crate) hoist_pattern: Option, /// Isolated linker: `false` skips the `node_modules/.bun/node_modules` /// fallback (pnpm's `hoist=false`); takes precedence over `hoist_pattern`. @@ -339,7 +340,7 @@ pub fn open_global_dir(explicit_global_dir: &[u8]) -> crate::Result Err(crate::Error::NoGlobalDirectoryFound) } -pub(crate) fn open_global_bin_dir(opts_: Option<&Api::BunInstall>) -> crate::Result { +pub(crate) fn open_global_bin_dir(opts_: Option<&BunInstall>) -> crate::Result { use bun_paths::{platform, resolve_path::join_abs_string_buf}; use bun_sys::{Dir, OpenDirOptions}; @@ -403,11 +404,11 @@ impl Options { maybe_cli: Option, // Every access below is a read of `config.*`; no field is ever written. // Taking `&` (not `&mut`) keeps provenance coherent with the bundler/ - // resolver storage (`Option>`). - bun_install_: Option<&Api::BunInstall>, + // resolver storage (`Option>`). + bun_install_: Option<&BunInstall>, subcommand: Subcommand, ) -> Result<(), bun_alloc::AllocError> { - let mut base = Api::NpmRegistry::default(); + let mut base = NpmRegistry::default(); let bun_install_ref = bun_install_; if let Some(config) = bun_install_ref { if let Some(registry) = &config.default_registry { @@ -423,7 +424,7 @@ impl Options { } // Clone so the // `base.url` fallback below in the scoped-registry loop stays valid. - self.scope = Npm::registry::Scope::from_api(b"", base.clone(), env)?; + self.scope = Npm::registry::Scope::from_registry(b"", base.clone(), env)?; // `did_override_default_scope` is set at the end of this fn; // on the OOM error path the field is irrelevant (process aborts). @@ -441,17 +442,17 @@ impl Options { } self.registries.put( Npm::registry::Scope::hash(name), - Npm::registry::Scope::from_api(name, registry, env)?, + Npm::registry::Scope::from_registry(name, registry, env)?, )?; } } if let Some(ca) = &config.ca { match ca { - Api::Ca::List(ca_list) => { + Ca::List(ca_list) => { self.ca.clone_from(ca_list); } - Api::Ca::Str(ca_str) => { + Ca::Str(ca_str) => { // Single-element slice; own it (no `Box::leak`). self.ca = vec![ca_str.clone()].into_boxed_slice(); } @@ -459,7 +460,6 @@ impl Options { } if let Some(node_linker) = config.node_linker { - // `Api::NodeLinker` is a re-export of `bun_install_types::NodeLinker`. self.node_linker = node_linker; } @@ -614,13 +614,12 @@ impl Options { } else { Box::default() }; - // Default (empty strings) is the zero value for Api::NpmRegistry. - let api_registry = Api::NpmRegistry { + let registry = NpmRegistry { url: registry_.into(), token, ..Default::default() }; - self.scope = Npm::registry::Scope::from_api(b"", api_registry, env)?; + self.scope = Npm::registry::Scope::from_registry(b"", registry, env)?; break; } } diff --git a/src/install/auto_installer.rs b/src/install/auto_installer.rs index 774a05069b6f..ae119befe488 100644 --- a/src/install/auto_installer.rs +++ b/src/install/auto_installer.rs @@ -451,7 +451,7 @@ impl hooks::AutoInstaller for PackageManager { #[unsafe(no_mangle)] unsafe fn __bun_resolver_init_package_manager( mut log: core::ptr::NonNull, - install: Option>, + install: Option>, mut env: core::ptr::NonNull, ) -> core::result::Result, bun_errno::SystemErrno> { // ABI: the resolver-side `extern "Rust"` declaration names @@ -462,9 +462,9 @@ unsafe fn __bun_resolver_init_package_manager( // Idempotent. bun_http::http_thread::init(&Default::default()); - // SAFETY: when `Some`, `install` points at a live `Api::BunInstall` + // SAFETY: when `Some`, `install` points at a live `BunInstall` // (see `run_command::wire_transpiler_from_ctx`); read-only borrow. - let bun_install: Option<&crate::bun_schema::api::BunInstall> = + let bun_install: Option<&bun_options_types::BunInstall> = install.map(|p| unsafe { p.as_ref() }); // SAFETY: caller guarantees `log` / `env` point at process-lifetime // Transpiler-owned storage with no aliasing `&mut` live across this call. diff --git a/src/install/lib.rs b/src/install/lib.rs index 11258ad52a84..1d5a936764b7 100644 --- a/src/install/lib.rs +++ b/src/install/lib.rs @@ -16,11 +16,6 @@ extern crate self as bun_install; extern crate bun_analytics as analytics; extern crate bun_core as bun_output; -/// `bun_schema::api` → schema lives in `bun_options_types::schema::api`. -pub(crate) mod bun_schema { - pub(crate) use bun_options_types::schema::api; -} - /// `bun_json` → JSON parser lives in `bun_parsers::json`; AST nodes /// (`Expr`, `ExprData`, `E*` variants) live in `bun_ast::js_ast`. pub(crate) mod bun_json { @@ -803,8 +798,7 @@ impl RunCommand { )?); // SAFETY: fully written on the line above. let this_transpiler = unsafe { this_transpiler.assume_init_mut() }; - this_transpiler.options.env.behavior = - bun_options_types::schema::api::DotEnvBehavior::load_all; + this_transpiler.options.env.behavior = bun_dotenv::DotEnvBehavior::LoadAll; this_transpiler.resolver.care_about_bin_folder = true; this_transpiler.resolver.care_about_scripts = true; this_transpiler.resolver.store_fd = store_root_fd; diff --git a/src/install/npm.rs b/src/install/npm.rs index 7c1b77b4a08d..a4aef41524ca 100644 --- a/src/install/npm.rs +++ b/src/install/npm.rs @@ -3,13 +3,13 @@ use std::io::Write as _; use crate::Error; use crate::bun_json as JSON; -use crate::bun_schema::api; use bun_alloc::AllocError; use bun_collections::{HashMap, IdentityContext, StringSet}; use bun_core::{Global, Output, fmt as bun_fmt}; use bun_core::{MutableString, strings}; use bun_dotenv::Loader as DotEnv; use bun_http::{self as http, AsyncHTTP, HeaderBuilder}; +use bun_options_types::NpmRegistry; use bun_picohttp as picohttp; use bun_semver::{self as Semver, ExternalString, SlicedString, String as SemverString}; use bun_sys::{self, Fd, File}; @@ -334,9 +334,9 @@ pub mod registry { &name[1..] } - pub(crate) fn from_api( + pub(crate) fn from_registry( name: &[u8], - registry_: api::NpmRegistry, + registry_: NpmRegistry, env: &mut DotEnv, ) -> Result { let mut registry = registry_; diff --git a/src/install_jsc/Cargo.toml b/src/install_jsc/Cargo.toml index ed5c0dcef967..9f35757900fe 100644 --- a/src/install_jsc/Cargo.toml +++ b/src/install_jsc/Cargo.toml @@ -19,7 +19,7 @@ enumset.workspace = true libc.workspace = true bitflags.workspace = true bun_alloc.workspace = true -bun_api.workspace = true +bun_options_types.workspace = true bun_core.workspace = true bun_dotenv.workspace = true bun_ini.workspace = true diff --git a/src/install_jsc/ini_jsc.rs b/src/install_jsc/ini_jsc.rs index ce6729ca685b..04bf47db967d 100644 --- a/src/install_jsc/ini_jsc.rs +++ b/src/install_jsc/ini_jsc.rs @@ -25,12 +25,12 @@ impl IniTestingAPIs { global: &JSGlobalObject, frame: &CallFrame, ) -> JsResult { - use bun_api::BunInstall; use bun_ast::{Log, Source}; use bun_core::String as BunString; use bun_dotenv as dotenv; use bun_ini::{config_iterator, load_npmrc}; use bun_install::npm::Registry; + use bun_options_types::BunInstall; let arg = frame.argument(0); let npmrc_contents = bun_core::OwnedString::new(arg.to_bun_string(global)?); diff --git a/src/js_parser/parser.rs b/src/js_parser/parser.rs index cf8248bf4b40..2c841d3ed4bf 100644 --- a/src/js_parser/parser.rs +++ b/src/js_parser/parser.rs @@ -160,10 +160,8 @@ pub use crate::scan::scan_side_effects::SideEffects; pub(crate) use bun_ast::base::Ref; -// `runtime.rs` (full port) is path-gated in lib.rs as `runtime_full`. Until -// its bun_core/bun_schema deps are wired, the *real* type surface — the parts -// `P`/`visitStmt`/`visitExpr` actually consume — lives here so dependents can -// drop their bool-placeholder guards. +// The data-only runtime pieces live in `bun_ast::runtime`; the parts that +// `P`/`visitStmt`/`visitExpr` consume (`Features` etc.) live here. #[allow(non_snake_case)] pub mod Runtime { use bun_collections::StringSet; diff --git a/src/jsc/Cargo.toml b/src/jsc/Cargo.toml index d29c67cade25..09f9ffb43933 100644 --- a/src/jsc/Cargo.toml +++ b/src/jsc/Cargo.toml @@ -23,7 +23,6 @@ libc.workspace = true bitflags.workspace = true bytemuck = "1" bun_analytics.workspace = true -bun_api.workspace = true bun_base64.workspace = true bun_boringssl.workspace = true bun_alloc.workspace = true diff --git a/src/jsc/ModuleLoader.rs b/src/jsc/ModuleLoader.rs index 9bec7978ed80..f1c1e2553683 100644 --- a/src/jsc/ModuleLoader.rs +++ b/src/jsc/ModuleLoader.rs @@ -9,7 +9,6 @@ use core::ffi::c_void; use core::ptr::NonNull; use bun_alloc::Arena as ArenaAllocator; -use bun_options_types::LoaderExt as _; use crate::virtual_machine::VirtualMachine; use crate::{ @@ -240,7 +239,7 @@ pub struct LoaderHooks { specifier: *const bun_core::String, referrer: *const bun_core::String, source_code: *mut bun_core::ZigString, - loader: bun_options_types::schema::api::Loader, + loader: BunLoaderType, ret: *mut ErrorableResolvedSource, ) -> bool, /// `Bun__transpileFile` body — needs `options.getLoaderAndVirtualSource`, @@ -256,10 +255,32 @@ pub struct LoaderHooks { ret: *mut ErrorableResolvedSource, allow_promise: bool, is_commonjs_require: bool, - force_loader: u8, + force_loader: BunLoaderType, ) -> *mut c_void, } +/// A [`Loader`](bun_ast::Loader) as it crosses the C++ boundary (`BunLoaderType` +/// in headers-handwritten.h): the enum's discriminant, or [`Self::NONE`]. +#[repr(transparent)] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub struct BunLoaderType(pub u8); + +impl BunLoaderType { + pub const NONE: Self = Self(255); + + #[inline] + pub fn get(self) -> Option { + bun_ast::Loader::from_repr(self.0) + } +} + +impl From for BunLoaderType { + #[inline] + fn from(loader: bun_ast::Loader) -> Self { + Self(loader as u8) + } +} + unsafe extern "Rust" { /// The single `&'static` instance, defined `#[no_mangle]` in /// `bun_runtime::jsc_hooks`. Link-time resolved — no `AtomicPtr`, no @@ -343,7 +364,7 @@ unsafe extern "C" fn Bun__transpileFile( ret: *mut ErrorableResolvedSource, allow_promise: bool, is_commonjs_require: bool, - force_loader_type: u8, // bun.schema.api.Loader — passed as raw u8 across the cycle + force_loader_type: BunLoaderType, ) -> *mut c_void { jsc::mark_binding(); let Some(hooks) = loader_hooks() else { @@ -536,21 +557,17 @@ use bun_bundler::transpiler::PluginRunner; extern "C" fn Bun__getDefaultLoader( global: &JSGlobalObject, str: &bun_core::String, -) -> bun_options_types::schema::api::Loader { - use bun_options_types::schema::api; - // SAFETY: C++ passed the live JS-thread global; `bun_vm()` is the - // per-thread VM pointer (never null on this path). +) -> BunLoaderType { let jsc_vm = global.bun_vm(); let filename = str.to_utf8(); let loader = jsc_vm .transpiler .options - .loader(bun_resolver::fs::PathName::init(filename.slice()).ext) - .to_api(); - if loader == api::Loader::file { - return api::Loader::js; + .loader(bun_resolver::fs::PathName::init(filename.slice()).ext); + match loader { + bun_ast::Loader::File | bun_ast::Loader::Bunsh => bun_ast::Loader::Js.into(), + _ => loader.into(), } - loader } /// C++ entry point: transpiles a plugin-provided virtual module's source, writing the result into `ret`. @@ -560,7 +577,7 @@ unsafe extern "C" fn Bun__transpileVirtualModule( specifier: *const bun_core::String, referrer: *const bun_core::String, source_code: *mut bun_core::ZigString, - loader: bun_options_types::schema::api::Loader, + loader: BunLoaderType, ret: *mut ErrorableResolvedSource, ) -> bool { jsc::mark_binding(); diff --git a/src/jsc/NodeModuleModule.rs b/src/jsc/NodeModuleModule.rs index 47cc4e5c7d35..a056d764c3ae 100644 --- a/src/jsc/NodeModuleModule.rs +++ b/src/jsc/NodeModuleModule.rs @@ -5,30 +5,8 @@ use crate::{ use bun_ast::Loader; use bun_bundler::options::DEFAULT_LOADERS; use bun_core::{OwnedString, String as BunString, strings}; -use bun_options_types::LoaderExt as _; -use bun_options_types::schema::api; -// `bun.schema.api.Loader` — bindgen-emitted schema enum. -// Mirrored as a transparent `u8` because the schema enum is *open* -// and the FFI caller may hand us discriminants outside -// the closed Rust `api::Loader` set; transmuting an unknown tag would be UB. -#[repr(transparent)] -#[derive(Copy, Clone, Eq, PartialEq, Debug)] -pub(crate) struct ApiLoader(pub u8); -impl ApiLoader { - /// `_none = 254`. - const NONE: Self = Self(api::Loader::_none as u8); - - /// Reconstruct the closed schema enum. Only valid when `self != NONE` is - /// already established and the C++ caller honoured the `BunLoaderType` - /// contract (headers-handwritten.h keeps the discriminants in sync). - fn to_schema(self) -> api::Loader { - debug_assert_ne!(self, Self::NONE); - // C++ caller passes a valid `BunLoaderType` discriminant per - // headers-handwritten.h; `from_raw` maps unknowns to `_none`. - api::Loader::from_raw(self.0) - } -} +use crate::module_loader::BunLoaderType; // The C++ caller (NodeModuleModule.cpp // `jsFunctionFindPath`) does the CallFrame → (BunString, JSArray*) extraction itself and @@ -159,7 +137,7 @@ unsafe extern "C" { fn on_require_extension_modify( global: &JSGlobalObject, str: &[u8], - loader: ApiLoader, + loader: BunLoaderType, value: JSValue, ) -> Result<(), bun_alloc::AllocError> { // global; we are on the JS thread so a `&mut` view is sound for this scope. @@ -174,14 +152,14 @@ fn on_require_extension_modify( vm.has_mutated_built_in_extensions += 1; } - *gop.value_ptr = if loader != ApiLoader::NONE { - CustomLoader::Loader(Loader::from_api(loader.to_schema())) + *gop.value_ptr = if let Some(loader) = loader.get() { + CustomLoader::Loader(loader) } else { CustomLoader::Custom(Strong::create(value, global)) }; - } else if loader != ApiLoader::NONE { + } else if let Some(loader) = loader.get() { // Replacing with a built-in loader: drop any held Strong via assignment. - *gop.value_ptr = CustomLoader::Loader(Loader::from_api(loader.to_schema())); + *gop.value_ptr = CustomLoader::Loader(loader); } else { match gop.value_ptr { CustomLoader::Loader(_) => { @@ -250,7 +228,7 @@ pub fn find_longest_registered_extension<'a>( extern "C" fn NodeModuleModule__onRequireExtensionModify( global: &JSGlobalObject, str: &BunString, - loader: ApiLoader, + loader: BunLoaderType, value: JSValue, ) { let str_slice = str.to_utf8(); diff --git a/src/jsc/VirtualMachine.rs b/src/jsc/VirtualMachine.rs index b5a58a65b564..6927515fca0d 100644 --- a/src/jsc/VirtualMachine.rs +++ b/src/jsc/VirtualMachine.rs @@ -80,9 +80,9 @@ pub struct EntryPointResult { /// live in `bun_options_types` (already a dep of `bun_jsc`), so they thread /// through here instead of being dropped at the CLI call-site. pub struct InitOptions { - /// The CLI's `api.TransformOptions`. Consumed by `RuntimeHooks::init_runtime_state` + /// The CLI's `TransformOptions`. Consumed by `RuntimeHooks::init_runtime_state` /// → `Transpiler::init(.., configureTransformOptionsForBunVM(args), ..)`. - pub transform_options: bun_options_types::schema::api::TransformOptions, + pub transform_options: bun_options_types::TransformOptions, /// Consumed by `RuntimeHooks::init_runtime_state` → `configureDebugger`. pub debugger: bun_options_types::context::Debugger, /// When `Some`, [`init`] adopts @@ -2775,7 +2775,7 @@ impl<'a> bun_js_printer::OnSourceMapChunk for SourceMapHandlerGetter<'a> { /// `allocator` dropped per §Allocators (global mimalloc). #[derive(Default)] pub struct Options { - pub args: bun_options_types::schema::api::TransformOptions, + pub args: bun_options_types::TransformOptions, pub log: Option>, // BORROW_PARAM (`&'a mut bun_dotenv::Loader`) — caller-owned; the loader // outlives the VM, so the inner lifetime is erased to `'static`. @@ -3054,15 +3054,13 @@ impl VirtualMachine { /// Whether to warn when a previously-unhandled rejection later gains a handler. #[unsafe(export_name = "Bun__VM__allowRejectionHandledWarning")] pub(crate) extern "C" fn allow_rejection_handled_warning(this: &VirtualMachine) -> bool { - use bun_options_types::schema::api::UnhandledRejections; + use bun_options_types::UnhandledRejections; this.unhandled_rejections_mode() != UnhandledRejections::Bun } /// The configured `--unhandled-rejections` mode (defaults to Bun's behavior). - pub(crate) fn unhandled_rejections_mode( - &self, - ) -> bun_options_types::schema::api::UnhandledRejections { - use bun_options_types::schema::api::UnhandledRejections; + pub(crate) fn unhandled_rejections_mode(&self) -> bun_options_types::UnhandledRejections { + use bun_options_types::UnhandledRejections; self.transpiler .options .transform_options @@ -3233,7 +3231,7 @@ impl VirtualMachine { reason: JSValue, promise: JSValue, ) { - use bun_options_types::schema::api::UnhandledRejections as Mode; + use bun_options_types::UnhandledRejections as Mode; if self.is_shutting_down() { bun_core::debug_warn!("unhandledRejection during shutdown."); diff --git a/src/jsc/bindings/headers-handwritten.h b/src/jsc/bindings/headers-handwritten.h index 6cb74be5243e..d8be6580c84d 100644 --- a/src/jsc/bindings/headers-handwritten.h +++ b/src/jsc/bindings/headers-handwritten.h @@ -252,23 +252,32 @@ inline constexpr JSErrorCode JSErrorCodeOutOfMemoryError = 8; inline constexpr JSErrorCode JSErrorCodeStackOverflow = 253; inline constexpr JSErrorCode JSErrorCodeUserErrorCode = 254; -// Must be kept in sync with Loader in src/options_types/schema.rs +// `bun_ast::Loader` discriminants (src/ast/loader.rs); `bun_jsc::BunLoaderType` +// on the Rust side. Checked by test/internal/source-lints/loader-numbering.test.ts. typedef uint8_t BunLoaderType; -inline constexpr BunLoaderType BunLoaderTypeNone = 254; -inline constexpr BunLoaderType BunLoaderTypeJSX = 1; -inline constexpr BunLoaderType BunLoaderTypeJS = 2; -inline constexpr BunLoaderType BunLoaderTypeTS = 3; -inline constexpr BunLoaderType BunLoaderTypeTSX = 4; -inline constexpr BunLoaderType BunLoaderTypeCSS = 5; -inline constexpr BunLoaderType BunLoaderTypeFILE = 6; -inline constexpr BunLoaderType BunLoaderTypeJSON = 7; -inline constexpr BunLoaderType BunLoaderTypeJSONC = 8; -inline constexpr BunLoaderType BunLoaderTypeTOML = 9; -inline constexpr BunLoaderType BunLoaderTypeWASM = 10; -inline constexpr BunLoaderType BunLoaderTypeNAPI = 11; -inline constexpr BunLoaderType BunLoaderTypeYAML = 19; -inline constexpr BunLoaderType BunLoaderTypeMD = 21; -inline constexpr BunLoaderType BunLoaderTypeXML = 22; +inline constexpr BunLoaderType BunLoaderTypeNone = 255; +inline constexpr BunLoaderType BunLoaderTypeJSX = 0; +inline constexpr BunLoaderType BunLoaderTypeJS = 1; +inline constexpr BunLoaderType BunLoaderTypeTS = 2; +inline constexpr BunLoaderType BunLoaderTypeTSX = 3; +inline constexpr BunLoaderType BunLoaderTypeCSS = 4; +inline constexpr BunLoaderType BunLoaderTypeFILE = 5; +inline constexpr BunLoaderType BunLoaderTypeJSON = 6; +inline constexpr BunLoaderType BunLoaderTypeJSONC = 7; +inline constexpr BunLoaderType BunLoaderTypeTOML = 8; +inline constexpr BunLoaderType BunLoaderTypeWASM = 9; +inline constexpr BunLoaderType BunLoaderTypeNAPI = 10; +inline constexpr BunLoaderType BunLoaderTypeBASE64 = 11; +inline constexpr BunLoaderType BunLoaderTypeDATAURL = 12; +inline constexpr BunLoaderType BunLoaderTypeTEXT = 13; +inline constexpr BunLoaderType BunLoaderTypeBUNSH = 14; +inline constexpr BunLoaderType BunLoaderTypeSQLITE = 15; +inline constexpr BunLoaderType BunLoaderTypeSQLITE_EMBEDDED = 16; +inline constexpr BunLoaderType BunLoaderTypeHTML = 17; +inline constexpr BunLoaderType BunLoaderTypeYAML = 18; +inline constexpr BunLoaderType BunLoaderTypeJSON5 = 19; +inline constexpr BunLoaderType BunLoaderTypeMD = 20; +inline constexpr BunLoaderType BunLoaderTypeXML = 21; #pragma mark - Stream diff --git a/src/jsc/lib.rs b/src/jsc/lib.rs index a4ddb912d61b..2cd4e444570c 100644 --- a/src/jsc/lib.rs +++ b/src/jsc/lib.rs @@ -1321,6 +1321,7 @@ pub use self::virtual_machine::InitOptions as VirtualMachineInitOptions; #[path = "ModuleLoader.rs"] pub mod module_loader; pub use self::module_loader as ModuleLoader; +pub use self::module_loader::BunLoaderType; pub type ErrorableResolvedSource = Errorable; pub type ErrorableString = Errorable; diff --git a/src/options_types/bundle_enums.rs b/src/options_types/bundle_enums.rs index ba7eb1429491..0323d4c546d6 100644 --- a/src/options_types/bundle_enums.rs +++ b/src/options_types/bundle_enums.rs @@ -1,13 +1,6 @@ //! Pure enum/struct bundler option types, kept here so //! `cli/` and other tiers can reference them without depending on `bundler/`. //! Aliased back at original locations — call sites unchanged. -//! -//! `Loader` / `Target` / `SideEffects` / `Index` are now canonical in -//! `bun_ast`; only the `schema::api`-coupled extension methods (`to_api`, -//! `from_api`, `API_NAMES`) remain here as sealed extension traits. - -use crate::schema::api; -use bun_ast::{Loader, Target}; #[repr(u8)] #[derive(Copy, Clone, Eq, PartialEq, Debug)] @@ -122,135 +115,44 @@ bun_core::comptime_string_map! { }; } -// ─── Target: schema-coupled extension methods ───────────────────────────── - -mod sealed { - pub trait Sealed {} - impl Sealed for bun_ast::Target {} - impl Sealed for bun_ast::Loader {} -} - -/// `schema::api`-coupled methods on [`bun_ast::Target`]. Import alongside -/// `Target` where `to_api`/`from(api)` are needed. -pub trait TargetExt: sealed::Sealed { - fn to_api(self) -> api::Target; - fn from_api(plat: Option) -> Target; +/// `--sourcemap` / `sourcemap:` setting. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum SourceMapOption { + #[default] + None, + Inline, + External, + Linked, } -impl TargetExt for Target { - fn to_api(self) -> api::Target { - match self { - Target::Node => api::Target::node, - Target::Browser => api::Target::browser, - Target::Bun | Target::ServerComponentsSsr => api::Target::bun, - Target::BunMacro => api::Target::bun_macro, - } - } - - fn from_api(plat: Option) -> Target { - match plat.unwrap_or(api::Target::_none) { - api::Target::node => Target::Node, - api::Target::browser => Target::Browser, - api::Target::bun => Target::Bun, - api::Target::bun_macro => Target::BunMacro, - _ => Target::Browser, - } +impl SourceMapOption { + pub fn has_external_files(self) -> bool { + matches!(self, SourceMapOption::Linked | SourceMapOption::External) } } -// ─── Loader: schema-coupled extension methods ───────────────────────────── - bun_core::comptime_string_map! { -pub static LOADER_API_NAMES: api::Loader = { - b"js" => api::Loader::js, - b"mjs" => api::Loader::js, - b"cjs" => api::Loader::js, - b"cts" => api::Loader::ts, - b"mts" => api::Loader::ts, - b"jsx" => api::Loader::jsx, - b"ts" => api::Loader::ts, - b"tsx" => api::Loader::tsx, - b"css" => api::Loader::css, - b"file" => api::Loader::file, - b"json" => api::Loader::json, - b"jsonc" => api::Loader::json, - b"toml" => api::Loader::toml, - b"yaml" => api::Loader::yaml, - b"json5" => api::Loader::json5, - b"xml" => api::Loader::xml, - b"wasm" => api::Loader::wasm, - b"node" => api::Loader::napi, - b"dataurl" => api::Loader::dataurl, - b"base64" => api::Loader::base64, - b"txt" => api::Loader::text, - b"text" => api::Loader::text, - b"sh" => api::Loader::file, - b"sqlite" => api::Loader::sqlite, - b"html" => api::Loader::html, - b"md" => api::Loader::md, - b"markdown" => api::Loader::md, -}; + pub static SOURCE_MAP_OPTION_MAP: SourceMapOption = { + b"none" => SourceMapOption::None, + b"inline" => SourceMapOption::Inline, + b"external" => SourceMapOption::External, + b"linked" => SourceMapOption::Linked, + }; } -/// `schema::api`-coupled methods on [`bun_ast::Loader`]. -pub trait LoaderExt: sealed::Sealed { - fn to_api(self) -> api::Loader; - fn from_api(loader: api::Loader) -> Loader; +/// `--packages` / `packages:` setting. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum PackagesOption { + #[default] + Bundle, + External, } -impl LoaderExt for Loader { - fn to_api(self) -> api::Loader { - match self { - Loader::Jsx => api::Loader::jsx, - Loader::Js => api::Loader::js, - Loader::Ts => api::Loader::ts, - Loader::Tsx => api::Loader::tsx, - Loader::Css => api::Loader::css, - Loader::Html => api::Loader::html, - Loader::File | Loader::Bunsh => api::Loader::file, - Loader::Json => api::Loader::json, - Loader::Jsonc => api::Loader::json, - Loader::Toml => api::Loader::toml, - Loader::Yaml => api::Loader::yaml, - Loader::Json5 => api::Loader::json5, - Loader::Xml => api::Loader::xml, - Loader::Wasm => api::Loader::wasm, - Loader::Napi => api::Loader::napi, - Loader::Base64 => api::Loader::base64, - Loader::Dataurl => api::Loader::dataurl, - Loader::Text => api::Loader::text, - Loader::SqliteEmbedded | Loader::Sqlite => api::Loader::sqlite, - Loader::Md => api::Loader::md, - } - } - - fn from_api(loader: api::Loader) -> Loader { - match loader { - api::Loader::_none => Loader::File, - api::Loader::jsx => Loader::Jsx, - api::Loader::js => Loader::Js, - api::Loader::ts => Loader::Ts, - api::Loader::tsx => Loader::Tsx, - api::Loader::css => Loader::Css, - api::Loader::file => Loader::File, - api::Loader::json => Loader::Json, - api::Loader::jsonc => Loader::Jsonc, - api::Loader::toml => Loader::Toml, - api::Loader::yaml => Loader::Yaml, - api::Loader::json5 => Loader::Json5, - api::Loader::xml => Loader::Xml, - api::Loader::wasm => Loader::Wasm, - api::Loader::napi => Loader::Napi, - api::Loader::base64 => Loader::Base64, - api::Loader::dataurl => Loader::Dataurl, - api::Loader::text => Loader::Text, - api::Loader::bunsh => Loader::Bunsh, - api::Loader::html => Loader::Html, - api::Loader::sqlite => Loader::Sqlite, - api::Loader::sqlite_embedded => Loader::SqliteEmbedded, - api::Loader::md => Loader::Md, - } - } +bun_core::comptime_string_map! { + pub static PACKAGES_OPTION_MAP: PackagesOption = { + b"external" => PackagesOption::External, + b"bundle" => PackagesOption::Bundle, + }; } // ─── move-in: TYPE_ONLY from bun_runtime::bake::framework ────────────────────────── diff --git a/src/options_types/context.rs b/src/options_types/context.rs index 32068217f6fb..40744da07f8d 100644 --- a/src/options_types/context.rs +++ b/src/options_types/context.rs @@ -5,21 +5,22 @@ //! `create()` (which calls `Arguments.parse`) and the `global_cli_ctx`/ //! `context_data` storage stay in `cli.rs`. -use crate::schema::api; use bun_collections::ArrayHashMap; +use bun_dotenv::DotEnvBehavior; use crate::bundle_enums; use crate::code_coverage_options::CodeCoverageOptions; use crate::compile_target::CompileTarget; use crate::global_cache::GlobalCache; use crate::offline_mode::OfflineMode; +use crate::{BunInstall, TransformOptions}; // Every `Box<[u8]>` / `Vec>` struct field below is a proc-lifetime // CLI string: populated once from argv/bunfig during startup and never freed. pub struct ContextData { pub start_time: i128, - pub args: api::TransformOptions, + pub args: TransformOptions, /// Raw pointer (not `&mut`) so `Default` works and so the /// process-global `CONTEXT_DATA` static can be zero-initialized before /// `create_context_data()` writes the real `&mut Log` into it. @@ -28,7 +29,7 @@ pub struct ContextData { pub log: *mut bun_ast::Log, pub positionals: Vec>, pub passthrough: Vec>, - pub install: Option>, + pub install: Option>, pub debug: DebugOptions, pub test_options: TestOptions, @@ -67,7 +68,7 @@ impl Default for ContextData { fn default() -> Self { Self { start_time: 0, - args: api::TransformOptions::default(), + args: TransformOptions::default(), log: core::ptr::null_mut(), positionals: Vec::new(), passthrough: Vec::new(), @@ -216,7 +217,7 @@ pub struct BundlerOptions { pub production: bool, - pub env_behavior: api::DotEnvBehavior, + pub env_behavior: DotEnvBehavior, pub env_prefix: Box<[u8]>, pub elide_lines: Option, // Compile options @@ -268,7 +269,7 @@ impl Default for BundlerOptions { bake_debug_dump_server: false, bake_debug_disable_minify: false, production: false, - env_behavior: api::DotEnvBehavior::disable, + env_behavior: DotEnvBehavior::Disable, env_prefix: Box::default(), elide_lines: None, compile: false, diff --git a/src/options_types/install_config.rs b/src/options_types/install_config.rs new file mode 100644 index 000000000000..703c2a4c10b0 --- /dev/null +++ b/src/options_types/install_config.rs @@ -0,0 +1,85 @@ +//! `[install]` configuration collected from `bunfig.toml` and `.npmrc`. + +use bun_url::URL; + +use bun_install_types::NodeLinker::{NodeLinker, PnpmMatcher}; + +#[derive(Clone, Debug, Default)] +pub struct NpmRegistry { + pub url: Box<[u8]>, + pub username: Box<[u8]>, + pub password: Box<[u8]>, + pub token: Box<[u8]>, + pub email: Box<[u8]>, +} + +impl NpmRegistry { + /// Splits credentials embedded in a registry URL (`https://user:pass@host/` + /// or `https://:token@host/`) out into their own fields. + pub fn from_url(url: &[u8]) -> NpmRegistry { + let url = URL::parse(url); + let mut registry = NpmRegistry::default(); + + if url.username.is_empty() && !url.password.is_empty() { + registry.token = Box::from(url.password); + registry.url = url.href_without_auth(); + } else if !url.username.is_empty() && !url.password.is_empty() { + registry.username = Box::from(url.username); + registry.password = Box::from(url.password); + registry.url = url.href_without_auth(); + } else { + // Do not include a trailing slash. There might be parameters at the end. + registry.url = Box::from(url.href); + } + + registry + } +} + +/// Per-scope npm registry overrides, keyed by scope name. +#[derive(Default)] +pub struct NpmRegistryMap { + pub scopes: bun_collections::StringArrayHashMap, +} + +/// Value of `BunInstall.ca`. +#[derive(Clone, Debug)] +pub enum Ca { + Str(Box<[u8]>), + List(Box<[Box<[u8]>]>), +} + +#[derive(Default)] +pub struct BunInstall { + pub default_registry: Option, + pub scoped: Option, + pub cache_directory: Option>, + pub dry_run: Option, + pub force: Option, + pub save_dev: Option, + pub save_optional: Option, + pub save_peer: Option, + pub save_lockfile: Option, + pub production: Option, + pub save_yarn_lockfile: Option, + pub disable_cache: Option, + pub disable_manifest_cache: Option, + pub global_dir: Option>, + pub global_bin_dir: Option>, + pub frozen_lockfile: Option, + pub exact: Option, + pub concurrent_scripts: Option, + pub cafile: Option>, + pub save_text_lockfile: Option, + pub ca: Option, + pub ignore_scripts: Option, + pub link_workspace_packages: Option, + pub node_linker: Option, + pub global_store: Option, + pub security_scanner: Option>, + pub minimum_release_age_ms: Option, + pub minimum_release_age_excludes: Option>>, + pub public_hoist_pattern: Option, + pub hoist_pattern: Option, + pub hoist: Option, +} diff --git a/src/options_types/jsx.rs b/src/options_types/jsx.rs index 1e955f543209..c1ae9d8d9129 100644 --- a/src/options_types/jsx.rs +++ b/src/options_types/jsx.rs @@ -1,39 +1,35 @@ -//! JSX options (`Runtime`, `ImportSource`, `Pragma`, `RuntimeDevelopmentPair`, -//! `RuntimeMap`, `Defaults`). +//! JSX options: the raw user-facing [`Options`] (CLI flags, bunfig, +//! `Bun.build`) and the resolved [`Pragma`] the parser consumes, plus +//! `Runtime`, `ImportSource`, `RuntimeDevelopmentPair`, `RuntimeMap`, `Defaults`. //! -//! Canonical home (D042): previously triplicated across -//! `bundler/options.rs`, `js_parser/parser.rs`, and -//! `resolver/tsconfig_json.rs` with hand-rolled `From<>` bridges between the -//! nominal copies. All three crates already depend on `bun_options_types`, -//! and `api::Jsx`/`api::JsxRuntime` (the only upward refs) live in this -//! crate's `schema` module — so the type sits cleanly at this tier. - -use crate::schema::api; +//! Shared by `bundler/options.rs`, `js_parser/parser.rs` and +//! `resolver/tsconfig_json.rs`, which all depend on this crate. + use bun_core::strings; use std::borrow::Cow; -/// 4-state including `_None` so `Pragma.runtime` preserves the zero value -/// when an `api.Jsx` arrives with `runtime == _none`. `#[default]` is -/// `Automatic`. #[repr(u8)] #[derive(Clone, Copy, PartialEq, Eq, Debug, Default)] pub enum Runtime { - _None, + // Discriminants feed `Pragma::hash_for_runtime_transpiler`; keep them stable. #[default] - Automatic, - Classic, - Solid, + Automatic = 1, + Classic = 2, + Solid = 3, } -impl From for Runtime { - fn from(r: api::JsxRuntime) -> Self { - match r { - api::JsxRuntime::_none => Runtime::_None, - api::JsxRuntime::Classic => Runtime::Classic, - api::JsxRuntime::Solid => Runtime::Solid, - api::JsxRuntime::Automatic => Runtime::Automatic, - } - } +/// JSX settings as given on the command line, in bunfig.toml or to `Bun.build`, +/// before validation. [`Pragma::from_options`] resolves them. +#[derive(Clone, Debug, Default)] +pub struct Options { + /// e.g. `React.createElement` + pub factory: Box<[u8]>, + /// e.g. `React.Fragment` + pub fragment: Box<[u8]>, + pub runtime: Runtime, + pub development: bool, + pub import_source: Box<[u8]>, + pub side_effects: bool, } /// Port of `options.JSX.RuntimeDevelopmentPair`. @@ -295,7 +291,7 @@ impl Pragma { Ok(MemberList::Owned(out.into_boxed_slice())) } - pub fn from_api(jsx: api::Jsx) -> Result { + pub fn from_options(jsx: Options) -> Result { let mut pragma = Pragma::default(); if !jsx.fragment.is_empty() { @@ -312,7 +308,7 @@ impl Pragma { )?; } - pragma.runtime = Runtime::from(jsx.runtime); + pragma.runtime = jsx.runtime; pragma.side_effects = jsx.side_effects; if !jsx.import_source.is_empty() { diff --git a/src/options_types/lib.rs b/src/options_types/lib.rs index 41e3962eae45..187a91ea10a8 100644 --- a/src/options_types/lib.rs +++ b/src/options_types/lib.rs @@ -1,5 +1,4 @@ #![feature(adt_const_params)] -#![allow(non_snake_case, non_camel_case_types, non_upper_case_globals)] #![warn(unused_must_use)] pub mod bundle_enums; pub mod code_coverage_options; @@ -8,23 +7,19 @@ pub mod compile_target; pub mod context; pub mod error; pub mod global_cache; +pub mod install_config; pub mod jsx; pub mod offline_mode; -pub mod schema; +pub mod transform_options; pub use error::{Error, Result}; -pub use jsx as JSX; - -// ─── crate-root re-exports for dependents ──────────────────────────────── -// `ImportKind` / `ImportRecord` / `Loader` / `Target` / `Index` / `SideEffects` -// are now canonical in `bun_ast` — callers import from there directly. -// Only the `schema::api`-coupled extension traits and option-only types -// (`Format`, `ModuleType`, …) are surfaced from this crate. pub use bundle_enums::{ - BuiltInModule, BundlePackage, ForceNodeEnv, Format, LOADER_API_NAMES, LoaderExt, ModuleType, - TargetExt, WindowsOptions, + BuiltInModule, BundlePackage, ForceNodeEnv, Format, ModuleType, PackagesOption, + SourceMapOption, WindowsOptions, }; +pub use install_config::{BunInstall, Ca, NpmRegistry, NpmRegistryMap}; +pub use transform_options::{StringPairs, TransformOptions, UnhandledRejections}; /// Compiled-standalone-binary virtual filesystem path prefix + predicate. /// diff --git a/src/options_types/schema.rs b/src/options_types/schema.rs deleted file mode 100644 index 8ab5f8950398..000000000000 --- a/src/options_types/schema.rs +++ /dev/null @@ -1,380 +0,0 @@ -//! Option/config structs shared by the CLI, bunfig, bundler and runtime -//! (`TransformOptions`, `BunInstall`, …). - -pub mod api { - /// Canonical definition lives in bun_dotenv (lower tier). - pub use bun_dotenv::DotEnvBehavior; - - #[repr(u32)] - #[derive(Copy, Clone, Eq, PartialEq, Debug, Default)] - pub enum MessageLevel { - #[default] - _none = 0, - Err = 1, - Warn = 2, - Note = 3, - Info = 4, - Debug = 5, - } - - #[repr(u8)] - #[derive(Copy, Clone, Eq, PartialEq, Debug, Default)] - pub enum UnhandledRejections { - Strict = 0, - Throw = 1, - Warn = 2, - None = 3, - WarnWithErrorCode = 4, - #[default] - Bun = 5, - } - - bun_core::comptime_string_map! { - #[doc(hidden)] - pub static UNHANDLED_REJECTIONS_MAP: UnhandledRejections = { - b"strict" => UnhandledRejections::Strict, - b"throw" => UnhandledRejections::Throw, - b"warn" => UnhandledRejections::Warn, - b"none" => UnhandledRejections::None, - b"warn-with-error-code" => UnhandledRejections::WarnWithErrorCode, - }; - } - - impl UnhandledRejections { - /// `UnhandledRejections.map` — `bun.ComptimeStringMap`. - /// Note: deliberately omits `"bun"` (it's the implicit default). - pub const MAP: __ComptimeStringMap_UNHANDLED_REJECTIONS_MAP = - __ComptimeStringMap_UNHANDLED_REJECTIONS_MAP(()); - } - - /// The CLI/bunfig-populated option bag that `BundleOptions::from_api` - /// projects into bundler options. - /// - /// `Default` is all-zero: every Option `None`, every slice empty, every - /// scalar `0`/`false`. - /// - /// LIFECYCLE: `BundleOptions::from_api` parks this in an `Arc` whose final ref - /// lives on the process-lifetime `Transpiler` (LSan-rooted in build_command.rs). - #[derive(Clone, Debug, Default)] - pub struct TransformOptions { - /// jsx - pub jsx: Option, - /// tsconfig_override - pub tsconfig_override: Option>, - /// origin - pub origin: Option>, - /// absolute_working_dir - pub absolute_working_dir: Option>, - /// define - pub define: Option, - /// drop - pub drop: Vec>, - /// feature_flags — DCE via `import { feature } from "bun:bundle"` - pub feature_flags: Vec>, - /// preserve_symlinks - pub preserve_symlinks: Option, - /// entry_points - pub entry_points: Vec>, - /// write - pub write: Option, - /// inject - pub inject: Vec>, - /// output_dir - pub output_dir: Option>, - /// external - pub external: Vec>, - /// loaders - pub loaders: Option, - /// main_fields - pub main_fields: Vec>, - /// target - pub target: Option, - /// serve - pub serve: Option, - /// env_files - pub env_files: Vec>, - /// disable_default_env_files - pub disable_default_env_files: bool, - /// extension_order - pub extension_order: Vec>, - /// no_summary - pub no_summary: Option, - /// disable_hmr - pub disable_hmr: bool, - /// port - pub port: Option, - /// logLevel - pub log_level: Option, - /// source_map - pub source_map: Option, - /// conditions - pub conditions: Vec>, - /// packages - pub packages: Option, - /// ignore_dce_annotations - pub ignore_dce_annotations: bool, - - /// e.g. `[serve.static] plugins = ["tailwindcss"]` - pub serve_plugins: Option>>, - pub serve_minify_syntax: Option, - pub serve_minify_whitespace: Option, - pub serve_minify_identifiers: Option, - pub serve_env_behavior: DotEnvBehavior, - pub serve_env_prefix: Option>, - pub serve_splitting: bool, - pub serve_public_path: Option>, - pub serve_hmr: Option, - pub serve_define: Option, - - /// from `--no-addons`. `None` == `true`. - pub allow_addons: Option, - /// from `--unhandled-rejections`; default is `Bun`. - pub unhandled_rejections: Option, - - pub bunfig_path: Box<[u8]>, - } - - // ─── BunInstall + supporting types ─────────────────────────────────────── - - /// `Default` is empty slices. - #[derive(Clone, Debug, Default)] - pub struct NpmRegistry { - /// url - pub url: Box<[u8]>, - /// username - pub username: Box<[u8]>, - /// password - pub password: Box<[u8]>, - /// token - pub token: Box<[u8]>, - /// email - pub email: Box<[u8]>, - } - - /// Per-scope npm registry overrides, keyed by scope name. - #[derive(Default)] - pub struct NpmRegistryMap { - pub scopes: bun_collections::StringArrayHashMap, - } - - /// Value of `BunInstall.ca`; hoisted to a named type so callers can - /// construct it. - #[derive(Clone, Debug)] - pub enum Ca { - Str(Box<[u8]>), - List(Box<[Box<[u8]>]>), - } - - /// `NodeLinker` / `PnpmMatcher` are canonical in `bun_install_types` - /// (lower crate). Re-export so `BunInstall.node_linker` / - /// `BunInstall.hoist_pattern` and `bun_ini`'s callers all name the - /// same type. - pub use bun_install_types::NodeLinker::{NodeLinker, PnpmMatcher}; - - /// Full field set. - /// `Default` is every field `None`/empty. - /// - /// No `Debug`/`Clone` derive: `NpmRegistryMap` wraps `StringArrayHashMap` - /// which currently provides neither. - #[derive(Default)] - pub struct BunInstall { - /// default_registry - pub default_registry: Option, - /// scoped - pub scoped: Option, - /// lockfile_path - pub lockfile_path: Option>, - /// save_lockfile_path - pub save_lockfile_path: Option>, - /// cache_directory - pub cache_directory: Option>, - /// dry_run - pub dry_run: Option, - /// force - pub force: Option, - /// save_dev - pub save_dev: Option, - /// save_optional - pub save_optional: Option, - /// save_peer - pub save_peer: Option, - /// save_lockfile - pub save_lockfile: Option, - /// production - pub production: Option, - /// save_yarn_lockfile - pub save_yarn_lockfile: Option, - /// disable_cache - pub disable_cache: Option, - /// disable_manifest_cache - pub disable_manifest_cache: Option, - /// global_dir - pub global_dir: Option>, - /// global_bin_dir - pub global_bin_dir: Option>, - /// frozen_lockfile - pub frozen_lockfile: Option, - /// exact - pub exact: Option, - /// concurrent_scripts - pub concurrent_scripts: Option, - - pub cafile: Option>, - pub save_text_lockfile: Option, - pub ca: Option, - pub ignore_scripts: Option, - pub link_workspace_packages: Option, - pub node_linker: Option, - pub global_store: Option, - pub security_scanner: Option>, - pub minimum_release_age_ms: Option, - pub minimum_release_age_excludes: Option>>, - pub public_hoist_pattern: Option, - pub hoist_pattern: Option, - pub hoist: Option, - } - - #[repr(u8)] - #[derive(Copy, Clone, Eq, PartialEq, Debug, Default)] - pub enum SourceMapMode { - #[default] - None, - Inline, - External, - Linked, - } - - #[repr(u8)] - #[derive(Copy, Clone, Eq, PartialEq, Debug, Default)] - pub enum Target { - #[default] - _none = 0, - browser = 1, - node = 2, - bun = 3, - bun_macro = 4, - } - - impl Target { - // PascalCase aliases — `runtime/cli/Arguments.rs` writes - // `api::Target::Bun` while the enum body keeps the snake_case tags - // that `bundle_enums.rs` matches on. - pub const Browser: Self = Self::browser; - pub const Node: Self = Self::node; - pub const Bun: Self = Self::bun; - pub const BunMacro: Self = Self::bun_macro; - } - - #[repr(u8)] - #[derive(Copy, Clone, Eq, PartialEq, Debug, Default)] - pub enum Loader { - #[default] - _none = 254, - jsx = 1, - js = 2, - ts = 3, - tsx = 4, - css = 5, - file = 6, - json = 7, - jsonc = 8, - toml = 9, - wasm = 10, - napi = 11, - base64 = 12, - dataurl = 13, - text = 14, - bunsh = 15, - sqlite = 16, - sqlite_embedded = 17, - html = 18, - yaml = 19, - json5 = 20, - md = 21, - xml = 22, - } - - impl Loader { - /// Converts a raw discriminant to the schema `Loader`. - /// Unknown discriminants fall back to `_none`, matching how - /// `BundleEnums::Loader::from_api` already guards the open tail. - #[inline] - pub const fn from_raw(n: u8) -> Loader { - match n { - 1 => Loader::jsx, - 2 => Loader::js, - 3 => Loader::ts, - 4 => Loader::tsx, - 5 => Loader::css, - 6 => Loader::file, - 7 => Loader::json, - 8 => Loader::jsonc, - 9 => Loader::toml, - 10 => Loader::wasm, - 11 => Loader::napi, - 12 => Loader::base64, - 13 => Loader::dataurl, - 14 => Loader::text, - 15 => Loader::bunsh, - 16 => Loader::sqlite, - 17 => Loader::sqlite_embedded, - 18 => Loader::html, - 19 => Loader::yaml, - 20 => Loader::json5, - 21 => Loader::md, - 22 => Loader::xml, - _ => Loader::_none, - } - } - } - - #[repr(u8)] - #[derive(Copy, Clone, Eq, PartialEq, Debug, Default)] - pub enum JsxRuntime { - #[default] - _none = 0, - Automatic = 1, - Classic = 2, - Solid = 3, - } - - /// JSX transform configuration (factory, fragment, runtime, …). - #[derive(Clone, Debug, Default)] - pub struct Jsx { - pub factory: Box<[u8]>, - pub runtime: JsxRuntime, - pub fragment: Box<[u8]>, - pub development: bool, - pub import_source: Box<[u8]>, - pub side_effects: bool, - } - - /// Parallel-array string→string map as transmitted on the wire. - #[derive(Clone, Debug, Default)] - pub struct StringMap { - pub keys: Vec>, - pub values: Vec>, - } - - impl StringMap { - pub const EMPTY: StringMap = StringMap { - keys: Vec::new(), - values: Vec::new(), - }; - } - - /// Parallel-array map from file extension to [`Loader`]. - #[derive(Clone, Debug, Default)] - pub struct LoaderMap { - pub extensions: Vec>, - pub loaders: Vec, - } - - #[repr(u8)] - #[derive(Copy, Clone, Eq, PartialEq, Debug, Default)] - pub enum PackagesMode { - #[default] - Bundle = 0, - External = 1, - } -} diff --git a/src/options_types/transform_options.rs b/src/options_types/transform_options.rs new file mode 100644 index 000000000000..80d3ea36f983 --- /dev/null +++ b/src/options_types/transform_options.rs @@ -0,0 +1,90 @@ +//! The option bag the CLI (`Arguments.rs`) and `bunfig.toml` populate and +//! `BundleOptions::from_transform_options` projects into bundler options. + +use bun_ast::{Loader, Target}; +use bun_dotenv::DotEnvBehavior; + +use crate::bundle_enums::{PackagesOption, SourceMapOption}; +use crate::jsx; + +/// Ordered `(name, value)` pairs, e.g. `--define` entries. +pub type StringPairs = Vec<(Box<[u8]>, Box<[u8]>)>; + +/// LIFECYCLE: `BundleOptions::from_transform_options` parks this in an `Arc` whose final ref +/// lives on the process-lifetime `Transpiler` (LSan-rooted in build_command.rs). +#[derive(Clone, Debug, Default)] +pub struct TransformOptions { + pub jsx: Option, + pub tsconfig_override: Option>, + pub origin: Option>, + pub absolute_working_dir: Option>, + pub define: StringPairs, + pub drop: Vec>, + /// DCE via `import { feature } from "bun:bundle"` + pub feature_flags: Vec>, + pub preserve_symlinks: Option, + pub entry_points: Vec>, + pub write: Option, + pub output_dir: Option>, + pub external: Vec>, + /// `(".ext", loader)` pairs from `--loader` / bunfig `[loader]`. + pub loaders: Vec<(Box<[u8]>, Loader)>, + pub main_fields: Vec>, + pub target: Option, + pub env_files: Vec>, + pub disable_default_env_files: bool, + pub extension_order: Vec>, + pub port: Option, + pub log_level: Option, + pub source_map: Option, + pub conditions: Vec>, + pub packages: Option, + pub ignore_dce_annotations: bool, + + /// e.g. `[serve.static] plugins = ["tailwindcss"]` + pub serve_plugins: Option>>, + pub serve_minify_syntax: Option, + pub serve_minify_whitespace: Option, + pub serve_minify_identifiers: Option, + pub serve_env_behavior: Option, + pub serve_env_prefix: Option>, + pub serve_splitting: bool, + pub serve_public_path: Option>, + pub serve_hmr: Option, + pub serve_define: StringPairs, + + /// from `--no-addons`. `None` == `true`. + pub allow_addons: Option, + /// from `--unhandled-rejections`; default is `Bun`. + pub unhandled_rejections: Option, + + pub bunfig_path: Box<[u8]>, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)] +pub enum UnhandledRejections { + Strict, + Throw, + Warn, + None, + WarnWithErrorCode, + #[default] + Bun, +} + +bun_core::comptime_string_map! { + #[doc(hidden)] + pub static UNHANDLED_REJECTIONS_MAP: UnhandledRejections = { + b"strict" => UnhandledRejections::Strict, + b"throw" => UnhandledRejections::Throw, + b"warn" => UnhandledRejections::Warn, + b"none" => UnhandledRejections::None, + b"warn-with-error-code" => UnhandledRejections::WarnWithErrorCode, + }; +} + +impl UnhandledRejections { + /// Deliberately omits `"bun"` (it's the implicit default). + pub const MAP: __ComptimeStringMap_UNHANDLED_REJECTIONS_MAP = + __ComptimeStringMap_UNHANDLED_REJECTIONS_MAP(()); +} diff --git a/src/resolver/options.rs b/src/resolver/options.rs index 6000dfd81eb7..2915d1ab3831 100644 --- a/src/resolver/options.rs +++ b/src/resolver/options.rs @@ -214,10 +214,9 @@ pub struct BundleOptions { pub extra_cjs_extensions: Box<[Box<[u8]>]>, pub framework: Option, pub global_cache: bun_options_types::global_cache::GlobalCache, - // The bundler - // projects this from its own `Option>` field - // (CLI-owned `Box`, process-lifetime). - pub install: Option>, + // The bundler projects this from its own `Option>` + // field (CLI-owned `Box`, process-lifetime). + pub install: Option>, pub load_package_json: bool, pub load_tsconfig_json: bool, pub main_field_extension_order: Box<[Box<[u8]>]>, diff --git a/src/resolver/resolver.rs b/src/resolver/resolver.rs index 32b0d6efcb04..b0f40598d8af 100644 --- a/src/resolver/resolver.rs +++ b/src/resolver/resolver.rs @@ -34,7 +34,7 @@ unsafe extern "Rust" { /// unreadable); the failure is sticky across calls. fn __bun_resolver_init_package_manager( log: NonNull, - install: Option>, + install: Option>, env: NonNull, ) -> core::result::Result, bun_errno::SystemErrno>; } diff --git a/src/runtime/api/JSBundler.rs b/src/runtime/api/JSBundler.rs index fff3cd6b6371..422522034bf1 100644 --- a/src/runtime/api/JSBundler.rs +++ b/src/runtime/api/JSBundler.rs @@ -1,6 +1,5 @@ //! `Bun.build()` plugin host + `BuildArtifact` JS wrapper. -use bun_options_types::LoaderExt as _; use core::ffi::c_void; use crate::webcore::Blob; @@ -12,10 +11,10 @@ use bun_collections::{StringMap, StringSet}; use bun_core::MutableString; use bun_core::Output; use bun_core::{String as BunString, ZigString}; +use bun_dotenv::DotEnvBehavior; use bun_jsc::ConcurrentTask::ConcurrentTask; use bun_jsc::{self as jsc, CallFrame, JSGlobalObject, JSValue, JsError, JsResult}; use bun_options_types::compile_target::CompileTarget; -use bun_options_types::schema::api; // bun.schema.api use bun_standalone_graph::StandaloneModuleGraph; // `CompileTarget.fromJS` / `.fromSlice` are JSC-aware option parsers shared @@ -30,17 +29,6 @@ pub mod js_bundler { type OwnedString = MutableString; - /// `options::JSX::Runtime` → `api::JsxRuntime` (only the reverse `From` - /// exists upstream). - fn jsx_runtime_to_api(r: options::JSX::Runtime) -> api::JsxRuntime { - match r { - options::JSX::Runtime::_None => api::JsxRuntime::_none, - options::JSX::Runtime::Automatic => api::JsxRuntime::Automatic, - options::JSX::Runtime::Classic => api::JsxRuntime::Classic, - options::JSX::Runtime::Solid => api::JsxRuntime::Solid, - } - } - /// A map of file paths to their in-memory contents. /// LAYERING: the data-only struct (`map: StringHashMap>`) and /// `get`/`contains`/`resolve` live in `bun_bundler::bundle_v2` so the @@ -120,11 +108,11 @@ pub mod js_bundler { pub(crate) react_compiler_parse_test_pragmas: bool, pub(crate) react_compiler_output_mode: Option, pub(crate) define: StringMap, - pub(crate) loaders: Option, + pub(crate) loaders: Vec<(Box<[u8]>, bun_ast::Loader)>, pub(crate) dir: OwnedString, pub(crate) outdir: OwnedString, pub(crate) rootdir: OwnedString, - pub(crate) jsx: api::Jsx, + pub(crate) jsx: options::jsx::Options, pub(crate) force_node_env: options::ForceNodeEnv, pub(crate) code_splitting: bool, pub(crate) minify: Minify, @@ -151,7 +139,7 @@ pub mod js_bundler { pub(crate) drop: StringSet, pub(crate) features: StringSet, pub(crate) throw_on_error: bool, - pub(crate) env_behavior: api::DotEnvBehavior, + pub(crate) env_behavior: DotEnvBehavior, pub(crate) env_prefix: OwnedString, pub(crate) compile: Option, /// In-memory files that can be used as entrypoints or imported. @@ -175,15 +163,11 @@ pub mod js_bundler { react_compiler_parse_test_pragmas: false, react_compiler_output_mode: None, define: StringMap::init(false), - loaders: None, + loaders: Vec::new(), dir: OwnedString::default(), outdir: OwnedString::default(), rootdir: OwnedString::default(), - jsx: api::Jsx { - factory: Box::default(), - fragment: Box::default(), - runtime: api::JsxRuntime::Automatic, - import_source: Box::default(), + jsx: options::jsx::Options { development: true, // Default to development mode like old Pragma ..Default::default() }, @@ -211,7 +195,7 @@ pub mod js_bundler { drop: StringSet::default(), features: StringSet::default(), throw_on_error: true, - env_behavior: api::DotEnvBehavior::Disable, + env_behavior: DotEnvBehavior::Disable, env_prefix: OwnedString::default(), compile: None, files: FileMap::default(), @@ -671,12 +655,12 @@ pub mod js_bundler { || env == JSValue::FALSE || (env.is_number() && env.as_number() == 0.0) { - this.env_behavior = api::DotEnvBehavior::Disable; + this.env_behavior = DotEnvBehavior::Disable; } else if env == JSValue::TRUE || (env.is_number() && env.as_number() == 1.0) { - this.env_behavior = api::DotEnvBehavior::LoadAll; + this.env_behavior = DotEnvBehavior::LoadAll; } else if env.is_string() { let slice = env.to_slice(global_this)?; - match api::DotEnvBehavior::parse_str(slice.slice()) { + match DotEnvBehavior::parse_str(slice.slice()) { Ok((behavior, prefix)) => { this.env_behavior = behavior; if let Some(prefix) = prefix { @@ -719,7 +703,7 @@ pub mod js_bundler { let _ = bun_core::copy_lowercase(&slice.slice()[0..len], &mut str_lower[0..len]); if let Some(runtime) = options::JSX::RUNTIME_MAP.get(&str_lower[0..len]) { - this.jsx.runtime = jsx_runtime_to_api(runtime.runtime); + this.jsx.runtime = runtime.runtime; if let Some(dev) = runtime.development { this.jsx.development = dev; } @@ -1099,18 +1083,7 @@ pub mod js_bundler { }, )?; - // `loader_iter.i` is the property position, not a dense index of yielded - // entries. With `skip_empty_name = true` (or a skipped property getter), - // writing at `loader_iter.i` would leave earlier slots uninitialized and - // later freed as garbage. Use ArrayLists so the stored slice is always - // exactly what was appended. - let mut loader_names: Vec> = Vec::new(); - // errdefer: Vec> drops automatically - let mut loader_values: Vec = Vec::new(); - - loader_names.reserve_exact(loader_iter.len); - loader_values.reserve_exact(loader_iter.len); - + this.loaders.reserve_exact(loader_iter.len); while let Some(prop) = loader_iter.next()? { let prop_slice = prop.to_utf8(); if !prop_slice.slice().starts_with(b".") || prop.length() < 2 { @@ -1120,19 +1093,15 @@ pub mod js_bundler { } drop(prop_slice); - loader_values.push(loader_iter.value.to_enum_from_map( + let loader = loader_iter.value.to_enum_from_map( global_this, "loader", - &options::LOADER_API_NAMES, - "\"js\", \"jsx\", \"ts\", \"tsx\", \"css\", \"file\", \"json\", \"toml\", \"wasm\", \"napi\", \"base64\", \"dataurl\", \"text\", \"html\"", - )?); - loader_names.push(prop.to_owned_slice().into_boxed_slice()); + &bun_ast::loader::LOADER_NAMES, + "\"js\", \"jsx\", \"ts\", \"tsx\", \"css\", \"file\", \"json\", \"jsonc\", \"json5\", \"toml\", \"yaml\", \"xml\", \"md\", \"html\", \"wasm\", \"napi\", \"base64\", \"dataurl\", \"text\", or \"sqlite\"", + )?; + this.loaders + .push((prop.to_owned_slice().into_boxed_slice(), loader)); } - - this.loaders = Some(api::LoaderMap { - extensions: loader_names, - loaders: loader_values, - }); } if let Some(flag) = config.get_boolean_strict(global_this, "throw")? { @@ -1597,7 +1566,10 @@ pub mod js_bundler { return; } } else { - let loader = api::Loader::from_raw(loader_as_int.as_int32() as u8); + let loader = u8::try_from(loader_as_int.as_int32()) + .ok() + .and_then(bun_ast::Loader::from_repr) + .unwrap_or(bun_ast::Loader::File); let global = bv2_plugin(this.bv2).global_object(); let source_code = match crate::node::StringOrBuffer::from_js_to_owned_slice( global, @@ -1614,7 +1586,7 @@ pub mod js_bundler { } }; this.value = LoadValue::Success(LoadSuccess { - loader: bun_ast::Loader::from_api(loader), + loader, source_code: source_code.into(), }); } diff --git a/src/runtime/api/JSTranspiler.rs b/src/runtime/api/JSTranspiler.rs index 7c643d8aa462..70e9de6b8f18 100644 --- a/src/runtime/api/JSTranspiler.rs +++ b/src/runtime/api/JSTranspiler.rs @@ -1,7 +1,6 @@ //! `Bun.Transpiler` — single-file transform/scan over the JS parser. use bun_alloc::ArenaVecExt as _; -use bun_options_types::TargetExt as _; use std::io::Write as _; use crate::Error; @@ -10,9 +9,11 @@ use bun_alloc::{Arena, ArenaVec}; // bumpalo::Bump / bumpalo::collections::Vec r use bun_ast::Expr; use bun_ast::Loader; use bun_ast::{ImportRecord, ImportRecordFlags}; -use bun_bundler::options::{self, PackagesOption, SourceMapOption}; +use bun_bundler::options::{self, SourceMapOption}; use bun_bundler::transpiler::{MacroJSCtx, ParseOptions, ParseResult}; use bun_bundler::{self as Transpiler}; +use bun_collections::ArrayHashMapExt; +use bun_core::{OwnedString, String as BunString, ZigString}; use bun_js_parser::lexer as JSLexer; use bun_js_parser::parser::Runtime; use bun_js_parser::parser::ScanPassResult; @@ -26,12 +27,9 @@ use bun_jsc::{ JSPromise, JSPropertyIterator, JSPropertyIteratorOptions, JSValue, JsCell, JsResult, LogJsc, StringJsc, }; +use bun_options_types::TransformOptions; use bun_resolver::package_json::{MacroMap, PackageJSON}; use bun_resolver::tsconfig_json::TSConfigJSON; -// `bun_schema::api` → schema lives in `bun_options_types::schema::api`. -use bun_collections::ArrayHashMapExt; -use bun_core::{OwnedString, String as BunString, ZigString}; -use bun_options_types::schema::api; // Host-fn re-entrancy: every JS-exposed method takes `&self`; per-field // interior mutability via `JsCell` (= `UnsafeCell` projector). `JsCell` is @@ -56,16 +54,15 @@ pub struct JSTranspiler { pub(crate) ref_count: bun_ptr::RefCount, } -fn default_transform_options() -> api::TransformOptions { - api::TransformOptions { - disable_hmr: true, - target: Some(api::Target::Browser), +fn default_transform_options() -> TransformOptions { + TransformOptions { + target: Some(bun_ast::Target::Browser), ..Default::default() } } pub struct Config { - pub(crate) transform: api::TransformOptions, + pub(crate) transform: TransformOptions, pub(crate) default_loader: Loader, pub(crate) macro_map: MacroMap, pub(crate) tsconfig: Option>, @@ -188,14 +185,7 @@ impl Config { JSPropertyIterator::init(global, define_obj_ref, PROP_ITER_OPTS)?; // `defer define_iter.deinit()` → Drop - // `define_iter.i` is the property position, not a dense index of yielded - // entries. With `skip_empty_name = true` (or a skipped property getter), - // writing at `define_iter.i` would leave earlier slots uninitialized. - // Use Vecs so the stored slice is always exactly what was appended. - let mut names: Vec> = Vec::new(); - let mut values: Vec> = Vec::new(); - names.reserve_exact(define_iter.len); - values.reserve_exact(define_iter.len); + let mut define = bun_options_types::StringPairs::with_capacity(define_iter.len); while let Some(prop) = define_iter.next()? { let property_value = define_iter.value; @@ -208,7 +198,7 @@ impl Config { ))); } - names.push(prop.to_owned_slice().into()); + let name: Box<[u8]> = prop.to_owned_slice().into(); let mut val = ZigString::init(b""); property_value.to_zig_string(&mut val, global)?; if val.len == 0 { @@ -216,13 +206,10 @@ impl Config { } let mut buf = Vec::new(); write!(&mut buf, "{}", val).expect("unreachable"); - values.push(buf.into_boxed_slice()); + define.push((name, buf.into_boxed_slice())); } - self.transform.define = Some(api::StringMap { - keys: names, - values, - }); + self.transform.define = define; } } @@ -292,7 +279,7 @@ impl Config { if let Some(target) = object.get(global, "target")? { if let Some(resolved) = target_from_js(global, target)? { - self.transform.target = Some(resolved.to_api()); + self.transform.target = Some(resolved); } } @@ -431,13 +418,13 @@ impl Config { if let Some(flag) = object.get(global, "sourcemap")? { if flag.is_boolean() || flag.is_undefined_or_null() { if flag.to_boolean() { - self.transform.source_map = Some(api::SourceMapMode::Inline); + self.transform.source_map = Some(SourceMapOption::Inline); } else { - self.transform.source_map = Some(api::SourceMapMode::None); + self.transform.source_map = Some(SourceMapOption::None); } } else { if let Some(source) = source_map_option_from_js(global, flag)? { - self.transform.source_map = Some(SourceMapOption::to_api(Some(source))); + self.transform.source_map = Some(source); } else { return Err(global.throw_invalid_arguments(format_args!( "sourcemap must be one of \"inline\", \"linked\", \"external\", or \"none\"", @@ -452,7 +439,7 @@ impl Config { &options::PACKAGES_OPTION_MAP, "\"bundle\" or \"external\"", )? { - self.transform.packages = Some(PackagesOption::to_api(Some(packages))); + self.transform.packages = Some(packages); } let mut tree_shaking: Option = None; @@ -1049,7 +1036,7 @@ impl JSTranspiler { transpiler.options.no_macros = config.no_macros; transpiler.configure_linker_with_auto_jsx(false); - transpiler.options.env.behavior = options::EnvBehavior::disable; + transpiler.options.env.behavior = bun_dotenv::DotEnvBehavior::Disable; if let Err(err) = transpiler.configure_defines() { let log = &mut config.log; if (log.warnings + log.errors) > 0 { diff --git a/src/runtime/api/js_bundle_completion_task.rs b/src/runtime/api/js_bundle_completion_task.rs index dfb79bb5c37e..8ce8ecde12e5 100644 --- a/src/runtime/api/js_bundle_completion_task.rs +++ b/src/runtime/api/js_bundle_completion_task.rs @@ -8,9 +8,7 @@ //! through the `bun_bundler::bundle_v2::CompletionStruct` trait //! (layout-agnostic). -use bun_options_types::TargetExt as _; use core::ptr::{self, NonNull}; -use std::io::Write as _; use bun_alloc::Arena; use bun_bundler::bundle_v2::{ @@ -26,8 +24,8 @@ use bun_io::KeepAlive; use bun_jsc::WorkPool; use bun_jsc::event_loop::EventLoop; use bun_jsc::{self as jsc, JSGlobalObject, JSPromise, JSValue}; +use bun_options_types::TransformOptions; use bun_options_types::WindowsOptions; -use bun_options_types::schema::api; use bun_paths::resolve_path::{join_abs_string, join_abs_string_buf, platform}; use bun_paths::{self as paths, PathBuffer, SEP}; use bun_ptr::BackRef; @@ -814,59 +812,9 @@ impl CompletionStruct for JSBundleCompletionTask { } transpiler.options.entry_points = config.entry_points.keys().to_vec().into_boxed_slice(); - // Convert API JSX config back to options.JSX.Pragma - let jsx_import = &config.jsx.import_source; - transpiler.options.jsx = options::jsx::Pragma { - factory: if !config.jsx.factory.is_empty() { - options::jsx::Pragma::member_list_to_components_if_different( - options::jsx::MemberList::Static(options::jsx::defaults::FACTORY), - &config.jsx.factory, - )? - } else { - options::jsx::MemberList::Static(options::jsx::defaults::FACTORY) - }, - fragment: if !config.jsx.fragment.is_empty() { - options::jsx::Pragma::member_list_to_components_if_different( - options::jsx::MemberList::Static(options::jsx::defaults::FRAGMENT), - &config.jsx.fragment, - )? - } else { - options::jsx::MemberList::Static(options::jsx::defaults::FRAGMENT) - }, - runtime: options::jsx::Runtime::from(config.jsx.runtime), - development: config.jsx.development, - package_name: if !jsx_import.is_empty() { - std::borrow::Cow::Owned(jsx_import.to_vec()) - } else { - std::borrow::Cow::Borrowed(b"react".as_slice()) - }, - classic_import_source: if !jsx_import.is_empty() { - std::borrow::Cow::Owned(jsx_import.to_vec()) - } else { - std::borrow::Cow::Borrowed(b"react".as_slice()) - }, - side_effects: config.jsx.side_effects, - parse: true, - import_source: options::jsx::ImportSource { - development: if !jsx_import.is_empty() { - let mut v = Vec::with_capacity(jsx_import.len() + 16); - let _ = write!(&mut v, "{}/jsx-dev-runtime", bstr::BStr::new(jsx_import)); - std::borrow::Cow::Owned(v) - } else { - std::borrow::Cow::Borrowed(options::jsx::defaults::IMPORT_SOURCE_DEV) - }, - production: if !jsx_import.is_empty() { - let mut v = Vec::with_capacity(jsx_import.len() + 12); - let _ = write!(&mut v, "{}/jsx-runtime", bstr::BStr::new(jsx_import)); - std::borrow::Cow::Owned(v) - } else { - std::borrow::Cow::Borrowed(options::jsx::defaults::IMPORT_SOURCE) - }, - }, - }; transpiler.options.no_macros = config.no_macros; transpiler.options.loaders = - options::loaders_from_transform_options(config.loaders.as_ref(), config.target)?; + options::loaders_from_transform_options(&config.loaders, config.target)?; transpiler .options .entry_naming @@ -1032,32 +980,22 @@ impl CompletionStruct for JSBundleCompletionTask { bump: &'a Arena, ) -> bun_bundler::Result<&'a mut Transpiler<'a>> { let config = &self.config; - let opts = api::TransformOptions { - define: if config.define.count() > 0 { - Some(api::StringMap { - keys: config.define.keys().to_vec(), - values: config.define.values().to_vec(), - }) - } else { - None - }, + let opts = TransformOptions { + define: (config.define.keys().iter().cloned()) + .zip(config.define.values().iter().cloned()) + .collect(), entry_points: config.entry_points.keys().to_vec(), - target: Some(config.target.to_api()), + target: Some(config.target), absolute_working_dir: if !config.dir.list.is_empty() { Some(Box::from(config.dir.list.as_slice())) } else { None }, - inject: Vec::new(), external: config.external.keys().to_vec(), - main_fields: Vec::new(), - extension_order: Vec::new(), - env_files: Vec::new(), conditions: config.conditions.keys().to_vec(), // Use the config value, which `configure_bundler` reapplies anyway. ignore_dce_annotations: config.ignore_dce_annotations, drop: config.drop.keys().to_vec(), - bunfig_path: Box::default(), jsx: Some(config.jsx.clone()), ..Default::default() }; diff --git a/src/runtime/bake/bake_body.rs b/src/runtime/bake/bake_body.rs index cdd8ab2dadcd..1e371abe246f 100644 --- a/src/runtime/bake/bake_body.rs +++ b/src/runtime/bake/bake_body.rs @@ -11,8 +11,9 @@ use bun_alloc::Arena; // = bumpalo::Bump use bun_collections::ArrayHashMap; use bun_core::Output; use bun_core::{ZStr, strings}; -use bun_jsc::{JSGlobalObject, JSValue, JsError, JsResult, ZigStringSlice}; -use bun_options_types::schema as bun_schema; +use bun_jsc::{ + ComptimeStringMapExt as _, JSGlobalObject, JSValue, JsError, JsResult, ZigStringSlice, +}; use bun_paths::{self as paths, PathBuffer}; // `jsc.API.JSBundler.Plugin` — opaque FFI handle for the C++ JSBundlerPlugin. @@ -92,8 +93,6 @@ fn get_function( } } -use bun_bundler_jsc::source_map_mode_jsc::source_map_mode_from_js; - /// Convert a `crate::Error` into a thrown JS exception in a `JsResult` /// context. #[inline] @@ -390,10 +389,10 @@ pub struct BuildConfigSubset { pub ignore_dce_annotations: Option, pub conditions: ArrayHashMap<&'static [u8], ()>, pub drop: ArrayHashMap<&'static [u8], ()>, - pub env: bun_schema::api::DotEnvBehavior, + pub env: Option, pub env_prefix: Option<&'static [u8]>, - pub define: bun_schema::api::StringMap, - pub source_map: bun_schema::api::SourceMapMode, + pub define: bun_options_types::StringPairs, + pub source_map: bun_bundler::options::SourceMapOption, pub minify_syntax: Option, pub minify_identifiers: Option, @@ -408,7 +407,9 @@ impl BuildConfigSubset { let Some(val) = get_optional_value(js_options, global, b"sourcemap")? else { break 'brk; }; - if let Some(sourcemap) = source_map_mode_from_js(global, val)? { + if let Some(sourcemap) = + bun_bundler::options::SOURCE_MAP_OPTION_MAP.from_js(global, val)? + { options.source_map = sourcemap; break 'brk; } @@ -455,10 +456,10 @@ impl Default for BuildConfigSubset { ignore_dce_annotations: None, conditions: ArrayHashMap::new(), drop: ArrayHashMap::new(), - env: bun_schema::api::DotEnvBehavior::_none, + env: None, env_prefix: None, - define: bun_schema::api::StringMap::EMPTY, - source_map: bun_schema::api::SourceMapMode::External, + define: bun_options_types::StringPairs::new(), + source_map: bun_bundler::options::SourceMapOption::External, minify_syntax: None, minify_identifiers: None, @@ -1159,9 +1160,7 @@ impl Framework { let out: &mut bun_bundler::Transpiler = out.write(bun_bundler::Transpiler::init( arena, log, - // `TransformOptions::default()`: every `Option` is `None`, every - // slice empty, every scalar zero/false. - bun_schema::api::TransformOptions::default(), + bun_options_types::TransformOptions::default(), None, )?); @@ -1230,8 +1229,8 @@ impl Framework { } out.options.source_map = source_map; - if bundler_options.env != bun_schema::api::DotEnvBehavior::_none { - out.options.env.behavior = bundler_options.env; + if let Some(env) = bundler_options.env { + out.options.env.behavior = env; out.options.env.prefix = bundler_options.env_prefix.unwrap_or(b"").into(); } // The resolver crate carries a FORWARD_DECL subset of @@ -1253,18 +1252,9 @@ impl Framework { }, )?; - if (bundler_options.define.keys.len() + bundler_options.drop.count()) > 0 { - debug_assert_eq!( - bundler_options.define.keys.len(), - bundler_options.define.values.len() - ); + if (bundler_options.define.len() + bundler_options.drop.count()) > 0 { use bun_bundler::DefineDataExt; - for (k, v) in bundler_options - .define - .keys - .iter() - .zip(bundler_options.define.values.iter()) - { + for (k, v) in &bundler_options.define { let parsed = bun_bundler::defines::DefineData::parse(k, v, false, false, log, arena)?; out.options.define.insert(k, parsed)?; diff --git a/src/runtime/bake/mod.rs b/src/runtime/bake/mod.rs index 12136842c27d..ee2e8a61e897 100644 --- a/src/runtime/bake/mod.rs +++ b/src/runtime/bake/mod.rs @@ -190,12 +190,9 @@ impl Framework { ) } - /// Sets up a per-graph - /// `Transpiler` in place. The full body lives in + /// Sets up a per-graph `Transpiler` in place. The full body lives in /// `bake_body::Framework::init_transpiler_with_options`; this keystone - /// version operates on the keystone `BuildConfigSubset` (which omits - /// `conditions`/`env`/`define`/`drop` until the schema types are - /// const-constructible — those paths default). + /// version operates on the keystone `BuildConfigSubset`. /// Returns the arena slot for the `bake_types::Framework` projection; caller must `drop_in_place` it. pub(crate) fn init_transpiler<'a>( &mut self, @@ -206,15 +203,13 @@ impl Framework { out: &mut core::mem::MaybeUninit>, bundler_options: &BuildConfigSubset, ) -> crate::Result<*mut bun_bundler::bake_types::Framework> { - use bun_options_types::schema as bun_schema; - let mut ast_memory_allocator = bun_ast::ASTMemoryAllocator::borrowing(arena); let _ast_scope = ast_memory_allocator.enter(); let out: &mut bun_bundler::Transpiler = out.write(bun_bundler::Transpiler::init( arena, log, - bun_schema::api::TransformOptions::default(), + bun_options_types::TransformOptions::default(), None, )?); @@ -288,8 +283,8 @@ impl Framework { bun_bundler::options::SourceMapOption::None } }; - if bundler_options.env != bun_schema::api::DotEnvBehavior::_none { - out.options.env.behavior = bundler_options.env; + if let Some(env) = bundler_options.env { + out.options.env.behavior = env; out.options.env.prefix = bundler_options.env_prefix.unwrap_or(b"").into(); } // The resolver crate carries a FORWARD_DECL subset of `BundleOptions`, so @@ -309,18 +304,9 @@ impl Framework { }, )?; - if (bundler_options.define.keys.len() + bundler_options.drop.count()) > 0 { - debug_assert_eq!( - bundler_options.define.keys.len(), - bundler_options.define.values.len() - ); + if (bundler_options.define.len() + bundler_options.drop.count()) > 0 { use bun_bundler::DefineDataExt; - for (k, v) in bundler_options - .define - .keys - .iter() - .zip(bundler_options.define.values.iter()) - { + for (k, v) in &bundler_options.define { let parsed = bun_bundler::defines::DefineData::parse(k, v, false, false, log, arena)?; out.options.define.insert(k, parsed)?; @@ -580,9 +566,9 @@ pub struct BuildConfigSubset { pub(crate) ignore_dce_annotations: Option, pub(crate) conditions: bun_collections::ArrayHashMap<&'static [u8], ()>, pub(crate) drop: bun_collections::ArrayHashMap<&'static [u8], ()>, - pub(crate) env: bun_options_types::schema::api::DotEnvBehavior, + pub(crate) env: Option, pub(crate) env_prefix: Option<&'static [u8]>, - pub(crate) define: bun_options_types::schema::api::StringMap, + pub(crate) define: bun_options_types::StringPairs, // `source_map` intentionally omitted — only // `init_transpiler_with_options` (bake_body) honours it, and DevServer // never calls that path. diff --git a/src/runtime/bake/production.rs b/src/runtime/bake/production.rs index dc586fbe917c..ecec4f8dde6a 100644 --- a/src/runtime/bake/production.rs +++ b/src/runtime/bake/production.rs @@ -16,7 +16,7 @@ use crate::bake::framework_router::{self, FrameworkRouter, OpaqueFileId}; use bun_alloc::Arena; use bun_bundler::BundleV2; use bun_bundler::Transpiler; -use bun_bundler::options::{self as bundler_options, OutputFile, SourceMapOption}; +use bun_bundler::options::OutputFile; use bun_bundler::output_file::Index as OutputFileIndex; use bun_collections::{AutoBitSet, StringArrayHashMap}; @@ -159,7 +159,7 @@ pub fn build_command(ctx: Context) -> crate::Result<()> { // Note: `bun_resolver::options::BundleOptions` has no // `minify_identifiers`/`minify_whitespace` fields; resolver.opts does // not carry them (the resolver never reads them). - b.options.env.behavior = bundler_options::EnvBehavior::LoadAllWithoutInlining; + b.options.env.behavior = bun_dotenv::DotEnvBehavior::LoadAllWithoutInlining; } vm.event_loop_ref().ensure_waker(); match &ctx.debug.macros { @@ -429,7 +429,7 @@ fn build_with_vm(ctx: Context, cwd: &[u8], pt: &mut PerThread) -> crate::Result< bake_body::Graph::Server, &mut server_transpiler, &options.bundler_options.server, - SourceMapOption::from_api(Some(options.bundler_options.server.source_map)), + options.bundler_options.server.source_map, options.bundler_options.server.minify_whitespace, options.bundler_options.server.minify_syntax, options.bundler_options.server.minify_identifiers, @@ -441,7 +441,7 @@ fn build_with_vm(ctx: Context, cwd: &[u8], pt: &mut PerThread) -> crate::Result< bake_body::Graph::Client, &mut client_transpiler, &options.bundler_options.client, - SourceMapOption::from_api(Some(options.bundler_options.client.source_map)), + options.bundler_options.client.source_map, options.bundler_options.client.minify_whitespace, options.bundler_options.client.minify_syntax, options.bundler_options.client.minify_identifiers, @@ -454,7 +454,7 @@ fn build_with_vm(ctx: Context, cwd: &[u8], pt: &mut PerThread) -> crate::Result< bake_body::Graph::Ssr, &mut ssr_transpiler, &options.bundler_options.ssr, - SourceMapOption::from_api(Some(options.bundler_options.ssr.source_map)), + options.bundler_options.ssr.source_map, options.bundler_options.ssr.minify_whitespace, options.bundler_options.ssr.minify_syntax, options.bundler_options.ssr.minify_identifiers, diff --git a/src/runtime/cli/Arguments.rs b/src/runtime/cli/Arguments.rs index e5b2e4f8b30f..47b4985f6a43 100644 --- a/src/runtime/cli/Arguments.rs +++ b/src/runtime/cli/Arguments.rs @@ -1,24 +1,25 @@ //! `parse()` runs `clap::parse()` against the per-tag table, handles -//! `--help`/`-v`/`--revision`, and populates the full `api::TransformOptions` +//! `--help`/`-v`/`--revision`, and populates the full `TransformOptions` //! / `Context` from every recognised flag. All param tables — leaf and //! concatenated — are const `&'static [ParamType]` via the //! `bun_clap::parse_param!` proc-macro (compile-time spec parsing) plus a //! const-fn slice concat (`bun_clap::concat_params!`). -use bun_options_types::LoaderExt as _; - use bstr::BStr; +use bun_ast::Target; use bun_bundler::options; use bun_clap as clap; use bun_clap::parse_param; use bun_core::env::OperatingSystem; use bun_core::strings; use bun_core::{self, FeatureFlags, Global, Output, env_var}; +use bun_dotenv::DotEnvBehavior; use bun_jsc::RegularExpression; use bun_jsc::regular_expression::Flags as RegexFlags; use bun_options_types::code_coverage_options::Reporters as CoverageReporters; use bun_options_types::context::{Debugger, DebuggerEnable, HotReload, MacroOptions, Shard}; -use bun_options_types::schema::api; +use bun_options_types::jsx; +use bun_options_types::{PackagesOption, SourceMapOption, TransformOptions, UnhandledRejections}; use bun_paths::resolve_path; use bun_paths::{PathBuffer, platform}; @@ -29,24 +30,19 @@ use crate::cli::concat_params; use crate::cli::{DefineColonList, LoaderColonList}; /// Clone borrowed argv slices into the owning `Vec>` shape used by -/// `api::TransformOptions` / `Context` fields. +/// `TransformOptions` / `Context` fields. #[inline] fn slice_to_owned(input: &[&[u8]]) -> Vec> { input.iter().map(|s| Box::<[u8]>::from(*s)).collect() } -pub(crate) fn loader_resolver(input: &[u8]) -> crate::Result { - let option_loader = bun_ast::Loader::from_string(input).ok_or(crate::Error::InvalidLoader)?; - Ok(option_loader.to_api()) -} - -fn resolve_jsx_runtime(s: &[u8]) -> crate::Result { +fn resolve_jsx_runtime(s: &[u8]) -> crate::Result { if s == b"automatic" { - Ok(api::JsxRuntime::Automatic) + Ok(jsx::Runtime::Automatic) } else if s == b"fallback" || s == b"classic" { - Ok(api::JsxRuntime::Classic) + Ok(jsx::Runtime::Classic) } else if s == b"solid" { - Ok(api::JsxRuntime::Solid) + Ok(jsx::Runtime::Solid) } else { Err(crate::Error::InvalidJSXRuntime) } @@ -739,12 +735,12 @@ pub(crate) static Bun__Node__UseSystemCA: core::sync::atomic::AtomicBool = // `crate::cli::arguments::load_config*` callers are unaffected. pub use bun_bunfig::arguments::{load_config, load_config_path, load_config_with_cmd_args}; -/// Parse `argv` into `api::TransformOptions` for the given subcommand. +/// Parse `argv` into `TransformOptions` for the given subcommand. /// /// `command::tag_params(cmd)` does a runtime lookup of the per-subcommand /// param table, and the per-`cmd` blocks below are guarded by /// `if matches!(cmd, …)`. -pub(crate) fn parse(cmd: CommandTag, ctx: Context<'_>) -> crate::Result { +pub(crate) fn parse(cmd: CommandTag, ctx: Context<'_>) -> crate::Result { let mut diag = clap::Diagnostic::default(); let table = tag_table(cmd); @@ -789,7 +785,7 @@ pub(crate) fn parse(cmd: CommandTag, ctx: Context<'_>) -> crate::Result>`, + // `TransformOptions.absolute_working_dir` is `Option>`, // so we dupe into a plain `Box<[u8]>`. let cwd: Box<[u8]> = if let Some(cwd_arg) = args.option(b"--cwd") { let mut outbuf = PathBuffer::uninit(); @@ -862,23 +858,14 @@ pub(crate) fn parse(cmd: CommandTag, ctx: Context<'_>) -> crate::Result::from(*s)) - .collect(), - values: defines_tuple - .values - .iter() - .map(|s| Box::<[u8]>::from(*s)) - .collect(), - }); + opts.define = (defines_tuple.keys.iter().zip(&defines_tuple.values)) + .map(|(k, v)| (Box::<[u8]>::from(*k), Box::<[u8]>::from(*v))) + .collect(); } opts.drop = slice_to_owned(args.options(b"--drop")); @@ -896,14 +883,9 @@ pub(crate) fn parse(cmd: CommandTag, ctx: Context<'_>) -> crate::Result::from(*s)) - .collect(), - loaders: loader_tuple.values, - }); + opts.loaders = (loader_tuple.keys.iter().zip(loader_tuple.values)) + .map(|(ext, loader)| (Box::<[u8]>::from(*ext), loader)) + .collect(); } opts.tsconfig_override = if let Some(ts) = args.option(b"--tsconfig-override") { @@ -1028,9 +1010,7 @@ pub(crate) fn parse(cmd: CommandTag, ctx: Context<'_>) -> crate::Result Some(*v), None => { Output::err_generic( @@ -1468,11 +1448,9 @@ pub(crate) fn parse(cmd: CommandTag, ctx: Context<'_>) -> crate::Result) -> crate::Result::from).unwrap_or(prev.factory), fragment: jsx_fragment.map(Box::<[u8]>::from).unwrap_or(prev.fragment), import_source: jsx_import_source @@ -1560,12 +1538,7 @@ pub(crate) fn parse(cmd: CommandTag, ctx: Context<'_>) -> crate::Result bun_ast::Level::Debug, - api::MessageLevel::Err => bun_ast::Level::Err, - api::MessageLevel::Warn => bun_ast::Level::Warn, - _ => bun_ast::Level::Err, - }); + bun_ast::DEFAULT_LOG_LEVEL.store(log_level); // SAFETY: `ctx.log` is the CLI log, owned by the caller and not yet // shared with another thread. unsafe { @@ -1925,7 +1898,7 @@ fn parse_test_command_options(args: &clap::Args, ctx: Context<'_>) { fn parse_build_command_options( cmd: CommandTag, args: &clap::Args, - opts: &mut api::TransformOptions, + opts: &mut TransformOptions, ctx: Context<'_>, diag: &mut clap::Diagnostic, ) { @@ -1952,7 +1925,7 @@ fn parse_build_command_options( if ctx.bundler_options.bytecode { ctx.bundler_options.output_format = options::Format::Cjs; - ctx.args.target = Some(api::Target::Bun); + ctx.args.target = Some(Target::Bun); } if let Some(public_path) = args.option(b"--public-path") { @@ -2005,9 +1978,9 @@ fn parse_build_command_options( if let Some(packages) = args.option(b"--packages") { if packages == b"bundle" { - opts.packages = Some(api::PackagesMode::Bundle); + opts.packages = Some(PackagesOption::Bundle); } else if packages == b"external" { - opts.packages = Some(api::PackagesMode::External); + opts.packages = Some(PackagesOption::External); } else { bun_core::pretty_errorln!( "error: Invalid packages setting: \"{}\"", @@ -2020,15 +1993,15 @@ fn parse_build_command_options( if let Some(env) = args.option(b"--env") { if let Some(asterisk) = strings::index_of_char(env, b'*') { if asterisk == 0 { - ctx.bundler_options.env_behavior = options::EnvBehavior::LoadAll; + ctx.bundler_options.env_behavior = DotEnvBehavior::LoadAll; } else { - ctx.bundler_options.env_behavior = options::EnvBehavior::Prefix; + ctx.bundler_options.env_behavior = DotEnvBehavior::Prefix; ctx.bundler_options.env_prefix = Box::<[u8]>::from(&env[..asterisk as usize]); } } else if env == b"inline" || env == b"1" { - ctx.bundler_options.env_behavior = options::EnvBehavior::LoadAll; + ctx.bundler_options.env_behavior = DotEnvBehavior::LoadAll; } else if env == b"disable" || env == b"0" { - ctx.bundler_options.env_behavior = options::EnvBehavior::LoadAllWithoutInlining; + ctx.bundler_options.env_behavior = DotEnvBehavior::LoadAllWithoutInlining; } else { bun_core::pretty_errorln!( "error: Expected 'env' to be 'inline', 'disable', or a prefix with a '*' character" @@ -2051,38 +2024,33 @@ fn parse_build_command_options( ); Global::exit(1); } - opts.target = Some(api::Target::Bun); + opts.target = Some(Target::Bun); break 'brk; } } } opts.target = Some(opts.target.unwrap_or_else(|| match target { - b"browser" => api::Target::Browser, - b"node" => api::Target::Node, + b"browser" => Target::Browser, + b"node" => Target::Node, b"macro" => { if cmd == CommandTag::BuildCommand { - api::Target::BunMacro + Target::BunMacro } else { - api::Target::Bun + Target::Bun } } - b"bun" => api::Target::Bun, + b"bun" => Target::Bun, _ => cli::invalid_target(diag, target), })); - if opts.target.unwrap() == api::Target::Bun { - ctx.debug.run_in_bun = opts.target.unwrap() == api::Target::Bun; + if opts.target.unwrap() == Target::Bun { + ctx.debug.run_in_bun = opts.target.unwrap() == Target::Bun; } else { if ctx.bundler_options.bytecode { Output::err_generic( "target must be 'bun' when bytecode is true. Received: {}", - format_args!( - "{:?}", - ::from_api( - opts.target - ) - ), + format_args!("{}", BStr::new(target)), ); Global::exit(1); } @@ -2090,12 +2058,7 @@ fn parse_build_command_options( if ctx.bundler_options.bake { Output::err_generic( "target must be 'bun' when using --app. Received: {}", - format_args!( - "{:?}", - ::from_api( - opts.target - ) - ), + format_args!("{}", BStr::new(target)), ); } } @@ -2406,7 +2369,7 @@ fn parse_build_command_options( } options::Format::Cjs => { if ctx.args.target.is_none() { - ctx.args.target = Some(api::Target::Node); + ctx.args.target = Some(Target::Node); } } _ => {} @@ -2464,19 +2427,17 @@ fn parse_build_command_options( if args.flag(b"--server-components") { ctx.bundler_options.server_components = true; if let Some(target) = opts.target { - if !::from_api(Some(target)) - .is_server_side() - { + if !target.is_server_side() { Output::err_generic( "Cannot use client-side --target={} with --server-components", format_args!( - "{:?}", - ::from_api(Some(target)) + "{}", + BStr::new(args.option(b"--target").unwrap_or(b"browser")) ), ); Global::crash(); } else { - opts.target = Some(api::Target::Bun); + opts.target = Some(Target::Bun); } } } @@ -2492,15 +2453,15 @@ fn parse_build_command_options( if let Some(setting) = args.option(b"--sourcemap") { if setting.is_empty() { // In the future, Bun is going to make this default to .linked - opts.source_map = Some(api::SourceMapMode::Linked); + opts.source_map = Some(SourceMapOption::Linked); } else if setting == b"inline" { - opts.source_map = Some(api::SourceMapMode::Inline); + opts.source_map = Some(SourceMapOption::Inline); } else if setting == b"none" { - opts.source_map = Some(api::SourceMapMode::None); + opts.source_map = Some(SourceMapOption::None); } else if setting == b"external" { - opts.source_map = Some(api::SourceMapMode::External); + opts.source_map = Some(SourceMapOption::External); } else if setting == b"linked" { - opts.source_map = Some(api::SourceMapMode::Linked); + opts.source_map = Some(SourceMapOption::Linked); } else { bun_core::pretty_errorln!( "error: Invalid sourcemap setting: \"{}\"", diff --git a/src/runtime/cli/build_command.rs b/src/runtime/cli/build_command.rs index 92003b6f7c64..438c2405a239 100644 --- a/src/runtime/cli/build_command.rs +++ b/src/runtime/cli/build_command.rs @@ -1,6 +1,7 @@ use std::io::Write as _; use crate::cli::command::{Context, HotReload}; +use bun_ast::Target; use bun_bundler::bundle_v2::{self, BundleV2}; use bun_bundler::linker_context::metafile_builder as MetafileBuilder; use bun_bundler::options; @@ -9,8 +10,8 @@ use bun_core::env::OperatingSystem; use bun_core::strings; use bun_core::{Global, Output, fmt as bun_fmt}; use bun_js_parser::parser::Runtime; +use bun_options_types::PackagesOption; use bun_options_types::context::MacroOptions; -use bun_options_types::schema::api; use bun_paths::{PathBuffer, resolve_path}; use bun_sys::{self, Fd, FdExt as _}; @@ -66,11 +67,10 @@ impl BuildCommand { // SAFETY: `ctx.log` is a long-lived `*mut Log` set up during CLI init // and never freed for the duration of the command body. let log_ref: &mut bun_ast::Log = unsafe { &mut *log }; - let user_requested_browser_target = - ctx.args.target.is_some() && ctx.args.target.unwrap() == api::Target::Browser; + let user_requested_browser_target = ctx.args.target == Some(Target::Browser); if ctx.bundler_options.compile || ctx.bundler_options.bytecode { // set this early so that externals are set up correctly and define is right - ctx.args.target = Some(api::Target::Bun); + ctx.args.target = Some(Target::Bun); } if ctx.bundler_options.bake { @@ -78,40 +78,20 @@ impl BuildCommand { } if fetcher.is_some() { - ctx.args.packages = Some(api::PackagesMode::External); + ctx.args.packages = Some(PackagesOption::External); ctx.bundler_options.compile = false; } let compile_target = &ctx.bundler_options.compile_target; if ctx.bundler_options.compile { - let compile_define_keys = compile_target.define_keys(); - let compile_define_values = compile_target.define_values(); - - if let Some(define) = ctx.args.define.as_mut() { - let mut keys: Vec> = - Vec::with_capacity(compile_define_keys.len() + define.keys.len()); - keys.extend(compile_define_keys.iter().map(|s| Box::<[u8]>::from(*s))); - keys.append(&mut define.keys); - let mut values: Vec> = - Vec::with_capacity(compile_define_values.len() + define.values.len()); - values.extend(compile_define_values.iter().map(|s| Box::<[u8]>::from(*s))); - values.append(&mut define.values); - - define.keys = keys; - define.values = values; - } else { - ctx.args.define = Some(api::StringMap { - keys: compile_define_keys - .iter() - .map(|s| Box::<[u8]>::from(*s)) - .collect(), - values: compile_define_values - .iter() - .map(|s| Box::<[u8]>::from(*s)) - .collect(), - }); - } + // Compile-target defines go first so user `--define`s override them. + let user_defines = core::mem::take(&mut ctx.args.define); + ctx.args.define = (compile_target.define_keys().iter()) + .zip(compile_target.define_values()) + .map(|(k, v)| (Box::<[u8]>::from(*k), Box::<[u8]>::from(v))) + .collect(); + ctx.args.define.extend(user_defines); } // Note: `Transpiler::init` now takes an arena. Process-lifetime — @@ -151,8 +131,7 @@ impl BuildCommand { .cloned() .unwrap_or_default(); - this_transpiler.options.source_map = - options::SourceMapOption::from_api(ctx.args.source_map); + this_transpiler.options.source_map = ctx.args.source_map.unwrap_or_default(); this_transpiler.options.compile_mode = if ctx.bundler_options.compile { options::CompileMode::Executable @@ -274,7 +253,7 @@ impl BuildCommand { if user_requested_browser_target && has_all_html_entrypoints { // --compile --target=browser with all HTML entrypoints: produce self-contained HTML - ctx.args.target = Some(api::Target::Browser); + ctx.args.target = Some(Target::Browser); if ctx.bundler_options.code_splitting { bun_core::pretty_errorln!( "error: cannot use --compile --target browser with --splitting" @@ -520,19 +499,18 @@ impl BuildCommand { use bun_bundler::DefineExt as _; // Feed `--define` entries into // the client transpiler's Define table. - let user_defines = match &ctx.args.define { - Some(input) => { - let mut raw = bun_bundler::defines::RawDefines::default(); - raw.reserve(input.keys.len() + 4); - for (key, value) in input.keys.iter().zip(input.values.iter()) { - raw.insert(key.as_ref(), value.clone()); - } - let drop: Vec<&[u8]> = ctx.args.drop.iter().map(|d| d.as_ref()).collect(); - Some(bun_bundler::defines::DefineData::from_input( - &raw, &drop, log_ref, arena, - )?) + let user_defines = if ctx.args.define.is_empty() { + None + } else { + let mut raw = bun_bundler::defines::RawDefines::default(); + raw.reserve(ctx.args.define.len() + 4); + for (key, value) in &ctx.args.define { + raw.insert(key.as_ref(), value.clone()); } - None => None, + let drop: Vec<&[u8]> = ctx.args.drop.iter().map(|d| d.as_ref()).collect(); + Some(bun_bundler::defines::DefineData::from_input( + &raw, &drop, log_ref, arena, + )?) }; ct.options.define = options::Define::init( user_defines, diff --git a/src/runtime/cli/colon_list_type.rs b/src/runtime/cli/colon_list_type.rs index 951b4fcd5a95..8756f8f2e1b7 100644 --- a/src/runtime/cli/colon_list_type.rs +++ b/src/runtime/cli/colon_list_type.rs @@ -4,13 +4,12 @@ use bun_core::strings; use bun_core::{Global, pretty_errorln}; // The value type and its resolver fn collapse into one trait that the -// value type implements. Each `T` declares its own resolver and whether it is the -// schema Loader. +// value type implements. Each `T` declares its own resolver and whether it is +// `Loader` (for the error message). pub(crate) trait ColonListValue: Sized { /// Parses one value from its string form. fn resolve_value(input: &[u8]) -> Result; - /// Whether `T` is the schema `Loader` type. const IS_LOADER: bool = false; } diff --git a/src/runtime/cli/exec_command.rs b/src/runtime/cli/exec_command.rs index 01952db50bcc..07f3fe788cbf 100644 --- a/src/runtime/cli/exec_command.rs +++ b/src/runtime/cli/exec_command.rs @@ -2,7 +2,6 @@ use bstr::BStr; use bun_bundler::Transpiler; use bun_core::{Global, Output}; -use bun_options_types::schema::api; use crate::shell::Interpreter; use bun_paths::{self, PathBuffer}; @@ -44,7 +43,7 @@ impl ExecCommand { { let mut args = ctx.args.clone(); args.write = Some(false); - args.target = Some(api::Target::Bun); + args.target = Some(bun_ast::Target::Bun); args }, None, diff --git a/src/runtime/cli/mod.rs b/src/runtime/cli/mod.rs index 5aae85d24132..cbe3992f93ce 100644 --- a/src/runtime/cli/mod.rs +++ b/src/runtime/cli/mod.rs @@ -511,14 +511,13 @@ static IS_BUNX_EXE: core::sync::atomic::AtomicBool = core::sync::atomic::AtomicB bun_core::declare_scope!(CLI, hidden); -pub(crate) type LoaderColonList = - colon_list_type::ColonListType; +pub(crate) type LoaderColonList = colon_list_type::ColonListType; pub(crate) type DefineColonList = colon_list_type::ColonListType<&'static [u8]>; -impl colon_list_type::ColonListValue for bun_options_types::schema::api::Loader { +impl colon_list_type::ColonListValue for bun_ast::Loader { const IS_LOADER: bool = true; fn resolve_value(input: &[u8]) -> crate::Result { - arguments::loader_resolver(input) + bun_ast::Loader::from_string(input).ok_or(crate::Error::InvalidLoader) } } impl colon_list_type::ColonListValue for &'static [u8] { @@ -1215,7 +1214,7 @@ pub mod command { // (`which()` + its `RootCommandMatcher` name table / rodata) or walk // the per-tag dispatch `match`. `bun --version` also skips // `create_context_data` entirely (`arguments::parse` builds-and-drops - // a full `api::TransformOptions` and forces two `LazyLock`s for what + // a full `TransformOptions` and forces two `LazyLock`s for what // is a no-op). Keeps `command::which`'s code/rodata and `arguments`'s // clap tables out of the `--version` / `bun ` working set. // @@ -1398,7 +1397,7 @@ pub mod command { break 'brk write_context_no_parse(log); }; - ctx.args.target = Some(bun_options_types::schema::api::Target::Bun); + ctx.args.target = Some(bun_ast::Target::Bun); use bun_options_types::global_cache::GlobalCache; if ctx.debug.global_cache == GlobalCache::auto { ctx.debug.global_cache = GlobalCache::disable; @@ -1432,7 +1431,7 @@ pub mod command { } Err(e) => return Err(e), }; - ctx.args.target = Some(bun_options_types::schema::api::Target::Bun); + ctx.args.target = Some(bun_ast::Target::Bun); if ctx.parallel || ctx.sequential { // Result: if this returns at all, it's Err. diff --git a/src/runtime/cli/repl_command.rs b/src/runtime/cli/repl_command.rs index 061ac4de2b1a..001101a341ba 100644 --- a/src/runtime/cli/repl_command.rs +++ b/src/runtime/cli/repl_command.rs @@ -112,7 +112,7 @@ impl ReplCommand { b.options.global_cache = b.resolver.opts.global_cache; b.options.install_preference = offline; b.resolver.env_loader = NonNull::new(b.env); - b.options.env.behavior = EnvBehavior::LoadAllWithoutInlining; + b.options.env.behavior = DotEnvBehavior::LoadAllWithoutInlining; b.options.dead_code_elimination = false; // REPL needs all code if b.configure_defines().is_err() { @@ -293,5 +293,5 @@ unsafe extern "C" { ) -> bool; } -use bun_bundler::options::EnvBehavior; +use bun_dotenv::DotEnvBehavior; use bun_options_types::offline_mode::OfflineMode; diff --git a/src/runtime/cli/run_command.rs b/src/runtime/cli/run_command.rs index a5aca35765ee..40c6bdebf9b1 100644 --- a/src/runtime/cli/run_command.rs +++ b/src/runtime/cli/run_command.rs @@ -14,11 +14,11 @@ use bun_collections::{ArrayHashMap, StringHashMap}; use bun_core::{self as core, Environment, Global, Output, ZStr}; use bun_core::{pretty, pretty_errorln, prettyln}; use bun_dotenv as DotEnv; +use bun_dotenv::DotEnvBehavior; use bun_jsc::js_promise::Status as PromiseStatus; use bun_jsc::virtual_machine::{InitOptions as VmInitOptions, VirtualMachine}; use bun_jsc::{JSGlobalObject, JSValue}; use bun_md::root as md; -use bun_options_types::schema::api; #[cfg(windows)] use bun_paths::WPathBuffer; use bun_paths::strings; @@ -591,7 +591,7 @@ Full documentation is available at https://bun.com/docs/cli/run this_transpiler.write(Transpiler::init(arena, ctx.log, args, env)?); // SAFETY: fully written on the line above. let this_transpiler = unsafe { this_transpiler.assume_init_mut() }; - this_transpiler.options.env.behavior = api::DotEnvBehavior::LoadAll; + this_transpiler.options.env.behavior = DotEnvBehavior::LoadAll; let env_loader = this_transpiler.env_mut(); env_loader.quiet = true; this_transpiler.options.env.prefix = Box::default(); @@ -879,7 +879,7 @@ Full documentation is available at https://bun.com/docs/cli/run // Dummy transpiler so we can load .env. let mut args = ctx.args.clone(); args.write = Some(false); - args.target = Some(api::Target::Bun); + args.target = Some(bun_ast::Target::Bun); let mut bundle = Transpiler::init(runner_arena(), ctx.log, args, None)?; bundle.run_env_loader(bundle.options.env.disable_default_env_files)?; @@ -1037,7 +1037,7 @@ Full documentation is available at https://bun.com/docs/cli/run let defines_ok = { let b = &mut vm.transpiler; Self::wire_transpiler_from_ctx(b, ctx); - b.options.env.behavior = api::DotEnvBehavior::LoadAllWithoutInlining; + b.options.env.behavior = DotEnvBehavior::LoadAllWithoutInlining; b.configure_defines().is_ok() }; if !defines_ok { @@ -3594,7 +3594,7 @@ impl RunCommand { let Ok(mut this_transpiler) = Transpiler::init(runner_arena(), ctx.log, args, None) else { return Ok(shell_out); }; - this_transpiler.options.env.behavior = api::DotEnvBehavior::LoadAll; + this_transpiler.options.env.behavior = DotEnvBehavior::LoadAll; this_transpiler.options.env.prefix = Box::default(); // SAFETY: `Transpiler::env` is a non-null process-lifetime `*mut Loader`. unsafe { (*this_transpiler.env).quiet = true }; diff --git a/src/runtime/cli/test/parallel/runner.rs b/src/runtime/cli/test/parallel/runner.rs index 6e1efa7a63c6..46ea4cbdd8e2 100644 --- a/src/runtime/cli/test/parallel/runner.rs +++ b/src/runtime/cli/test/parallel/runner.rs @@ -328,27 +328,21 @@ fn build_worker_argv(ctx: &Command::ContextData) -> crate::Result::from(loader) + ))?); } if let Some(tsconfig) = &ctx.args.tsconfig_override { argv.push(lit(b"--tsconfig-override\0")); @@ -427,45 +421,13 @@ fn build_worker_argv(ctx: &Command::ContextData) -> crate::Result for &str` impl upstream. -fn api_loader_tag_name(l: bun_options_types::schema::api::Loader) -> &'static str { - use bun_options_types::schema::api::Loader as L; - match l { - L::jsx => "jsx", - L::js => "js", - L::ts => "ts", - L::tsx => "tsx", - L::css => "css", - L::file => "file", - L::json => "json", - L::jsonc => "jsonc", - L::toml => "toml", - L::wasm => "wasm", - L::napi => "napi", - L::base64 => "base64", - L::dataurl => "dataurl", - L::text => "text", - L::bunsh => "bunsh", - L::sqlite => "sqlite", - L::sqlite_embedded => "sqlite_embedded", - L::html => "html", - L::yaml => "yaml", - L::json5 => "json5", - L::md => "md", - L::xml => "xml", - L::_none => "_none", - } -} - /// Local shim for `@tagName(jsx.runtime)`. -fn jsx_runtime_tag_name(r: bun_options_types::schema::api::JsxRuntime) -> &'static str { - use bun_options_types::schema::api::JsxRuntime as J; +fn jsx_runtime_tag_name(r: bun_options_types::jsx::Runtime) -> &'static str { + use bun_options_types::jsx::Runtime as J; match r { J::Automatic => "automatic", J::Classic => "classic", J::Solid => "solid", - J::_none => "_none", } } diff --git a/src/runtime/cli/test_command.rs b/src/runtime/cli/test_command.rs index be81fc85e8b6..7cc6e2a08292 100644 --- a/src/runtime/cli/test_command.rs +++ b/src/runtime/cli/test_command.rs @@ -2319,8 +2319,7 @@ impl TestCommand { ctx.runtime_options.experimental_http3_fetch, core::sync::atomic::Ordering::Relaxed, ); - vm.transpiler.options.env.behavior = - bun_bundler::options::EnvBehavior::LoadAllWithoutInlining; + vm.transpiler.options.env.behavior = bun_dotenv::DotEnvBehavior::LoadAllWithoutInlining; let node_env_entry = env_loader.map.get_or_put_without_value(b"NODE_ENV")?; if !node_env_entry.found_existing { diff --git a/src/runtime/jsc_hooks.rs b/src/runtime/jsc_hooks.rs index cd6e9a438d65..09d54f5ccd44 100644 --- a/src/runtime/jsc_hooks.rs +++ b/src/runtime/jsc_hooks.rs @@ -21,14 +21,14 @@ //! `__bun_http_sync_download_*` — low-tier extern impls. use bun_core::WTFStringImplExt as _; -use bun_options_types::LoaderExt as _; use core::cell::Cell; use core::ffi::c_void; use core::ptr; use bun_jsc::js_promise::Status as PromiseStatus; use bun_jsc::module_loader::{ - ArenaResetGuard, FetchBuiltinResult, FetchFlags, LoaderHooks, TranspileArgs, TranspileExtra, + ArenaResetGuard, BunLoaderType, FetchBuiltinResult, FetchFlags, LoaderHooks, TranspileArgs, + TranspileExtra, }; use bun_jsc::resolved_source::OwnedResolvedSource; use bun_jsc::virtual_machine::{ @@ -377,7 +377,6 @@ unsafe fn init_runtime_state( // cwd → `getcwd` ENOENT). The `ptr::write` shape is load-bearing: do not // replace with `(*vm).transpiler = ...` (drops zeroed bytes → UB). { - use bun_options_types::schema::api; // Move (not clone) the caller's `TransformOptions` into the // `Transpiler::init` call. `InitOptions` is consumed once per VM and // the only post-hook reader of `transform_options` is the @@ -388,7 +387,7 @@ unsafe fn init_runtime_state( let mut args = core::mem::take(&mut opts.transform_options); let preserve_symlinks = args.preserve_symlinks.unwrap_or(false); args.write = Some(false); - args.target = Some(api::Target::Bun); + args.target = Some(bun_ast::Target::Bun); // The arena lives on // `RuntimeState` (boxed above) so `deinit_runtime_state` reclaims it // alongside `timer`/`entry_point` on Worker teardown. The `Box` @@ -3819,46 +3818,9 @@ export default db; // `options.getLoaderAndVirtualSource`. // // Porting the body inline here lets us name `VirtualMachine` directly (no -// vtable) and look the loader up in `transpiler.options.loaders` (which is -// already -// `StringArrayHashMap`), so no inter-enum bridge is required. +// vtable) and look the loader up in `transpiler.options.loaders`. // ──────────────────────────────────────────────────────────────────────────── -/// Maps the wire `Api::Loader` (`#[repr(u8)]`, `_none = 254`) discriminant that -/// crosses the C++ boundary as `force_loader: u8` to the runtime -/// `bun_ast::Loader`. Exhaustive match (any unknown tag — including 0, which -/// `api::Loader` never uses — collapses to `None`). -#[inline] -fn force_loader_from_api_u8(api_loader: u8) -> Option { - use Loader as L; - match api_loader { - 1 => Some(L::Jsx), - 2 => Some(L::Js), - 3 => Some(L::Ts), - 4 => Some(L::Tsx), - 5 => Some(L::Css), - 6 => Some(L::File), - 7 => Some(L::Json), - 8 => Some(L::Jsonc), - 9 => Some(L::Toml), - 10 => Some(L::Wasm), - 11 => Some(L::Napi), - 12 => Some(L::Base64), - 13 => Some(L::Dataurl), - 14 => Some(L::Text), - 15 => Some(L::Bunsh), - 16 => Some(L::Sqlite), - 17 => Some(L::SqliteEmbedded), - 18 => Some(L::Html), - 19 => Some(L::Yaml), - 20 => Some(L::Json5), - 21 => Some(L::Md), - 22 => Some(L::Xml), - // 254 = `_none`; everything else is open-tail. - _ => None, - } -} - /// `Fs.Path.loader(&jsc_vm.transpiler.options.loaders)` — re-spelt against /// `bun_ast::LoaderHashTable` (= `StringArrayHashMap`). fn loader_for_path(path: &Fs::Path<'_>, loaders: &bun_ast::LoaderHashTable) -> Option { @@ -4171,14 +4133,14 @@ unsafe fn transpile_file( ret: *mut ErrorableResolvedSource, allow_promise: bool, is_commonjs_require: bool, - force_loader: u8, + force_loader: BunLoaderType, ) -> *mut c_void { use bun_jsc::resolved_source::Tag as ResolvedSourceTag; // SAFETY: per fn contract. let global_ref = unsafe { &*global }; - let force_loader_type: Option = force_loader_from_api_u8(force_loader); + let force_loader_type: Option = force_loader.get(); // Create a fresh parse log. // Note: per §Allocators the explicit allocator threads are dropped. @@ -4561,11 +4523,9 @@ unsafe fn transpile_virtual_module( specifier_ptr: *const bun_core::String, referrer_ptr: *const bun_core::String, source_code: *mut bun_core::ZigString, - loader_: bun_options_types::schema::api::Loader, + loader_: BunLoaderType, ret: *mut ErrorableResolvedSource, ) -> bool { - use bun_options_types::schema::api; - // SAFETY: per fn contract — `global` is the live JS-thread global. let global_ref = unsafe { &*global }; // Note: `bun_vm_ptr()` returns the FFI `*mut VirtualMachine` directly; @@ -4592,11 +4552,9 @@ unsafe fn transpile_virtual_module( // `specifier_slice` drops). Same erasure as `transpile_file` above. let path: Fs::Path<'static> = unsafe { Fs::Path::init(specifier).into_static() }; - // Pick the loader: the explicit API loader if given, else by file - // extension, else `.js` for the main module / `.file` otherwise. - let loader = if loader_ != api::Loader::_none { - Loader::from_api(loader_) - } else { + // Pick the loader: the explicit one if given, else by file extension, + // else `.js` for the main module / `.file` otherwise. + let loader = loader_.get().unwrap_or_else(|| { // SAFETY: `jsc_vm` is the live per-thread VM. let opt = unsafe { &*jsc_vm } .transpiler @@ -4612,7 +4570,7 @@ unsafe fn transpile_virtual_module( Loader::File } }) - }; + }); // Reset the module loader's arena on scope exit. // `jsc_vm` is the live per-thread VM (BackRef invariant). diff --git a/src/runtime/server/HTMLBundle.rs b/src/runtime/server/HTMLBundle.rs index 62428735c64f..af0d9875ebbb 100644 --- a/src/runtime/server/HTMLBundle.rs +++ b/src/runtime/server/HTMLBundle.rs @@ -429,9 +429,9 @@ impl Route { config.public_path.append_char(b'/')?; } - if xform.serve_env_behavior != bun_options_types::schema::api::DotEnvBehavior::_none { - config.env_behavior = xform.serve_env_behavior; - if config.env_behavior == bun_options_types::schema::api::DotEnvBehavior::Prefix { + if let Some(env_behavior) = xform.serve_env_behavior { + config.env_behavior = env_behavior; + if config.env_behavior == bun_dotenv::DotEnvBehavior::Prefix { config .env_prefix .append_slice(xform.serve_env_prefix.as_deref().unwrap_or(b""))?; @@ -478,13 +478,8 @@ impl Route { config.define.put(b"import.meta.env.SSR", b"false")?; config.define.put(b"import.meta.env.STATIC", b"false")?; - if let Some(define) = &cli.args.serve_define { - debug_assert_eq!(define.keys.len(), define.values.len()); - // `StringMap` exposes only put/insert (no bulk re-index); - // profile if hot. - for (k, v) in define.keys.iter().zip(define.values.iter()) { - config.define.put(k, v)?; - } + for (k, v) in &cli.args.serve_define { + config.define.put(k, v)?; } if !is_development { diff --git a/src/runtime/server/ServerConfig.rs b/src/runtime/server/ServerConfig.rs index 1b83c3c08c0a..63f73356dbaa 100644 --- a/src/runtime/server/ServerConfig.rs +++ b/src/runtime/server/ServerConfig.rs @@ -1047,7 +1047,7 @@ impl ServerConfig { { if args.development.is_hmr_enabled() { use crate::bake::bake_body as bb; - use bun_options_types::schema::api::DotEnvBehavior; + use bun_dotenv::DotEnvBehavior; // NOTE: the arena is created here and moved into // `UserOptions` (lives until `args.bake` is dropped). @@ -1088,7 +1088,7 @@ impl ServerConfig { let o = &vm.transpiler.options.transform_options; match o.serve_env_behavior { - DotEnvBehavior::prefix => { + Some(DotEnvBehavior::Prefix) => { // NOTE: `serve_env_prefix` is `Option>` // owned by the long-lived `transform_options`; dupe // into the arena so the `&'static [u8]` field is @@ -1097,21 +1097,19 @@ impl ServerConfig { .serve_env_prefix .as_deref() .map(|p| bb::arena_dupe_z(&user_options.arena, p).as_bytes()); - user_options.bundler_options.client.env = DotEnvBehavior::prefix; + user_options.bundler_options.client.env = Some(DotEnvBehavior::Prefix); } - DotEnvBehavior::load_all => { - user_options.bundler_options.client.env = DotEnvBehavior::load_all; + Some(behavior @ (DotEnvBehavior::LoadAll | DotEnvBehavior::Disable)) => { + user_options.bundler_options.client.env = Some(behavior); } - DotEnvBehavior::disable => { - user_options.bundler_options.client.env = DotEnvBehavior::disable; - } - _ => {} + Some(DotEnvBehavior::LoadAllWithoutInlining) | None => {} } - if let Some(define) = &o.serve_define { - user_options.bundler_options.client.define = define.clone(); - user_options.bundler_options.server.define = define.clone(); - user_options.bundler_options.ssr.define = define.clone(); + if !o.serve_define.is_empty() { + let bundler_options = &mut user_options.bundler_options; + bundler_options.client.define.clone_from(&o.serve_define); + bundler_options.server.define.clone_from(&o.serve_define); + bundler_options.ssr.define.clone_from(&o.serve_define); } args.bake = Some(user_options); diff --git a/src/runtime/server/StaticRoute.rs b/src/runtime/server/StaticRoute.rs index 67ca041bafc9..76700b2edb8e 100644 --- a/src/runtime/server/StaticRoute.rs +++ b/src/runtime/server/StaticRoute.rs @@ -5,7 +5,7 @@ use core::cell::Cell; use core::mem::size_of; use crate::Error; -use bun_http::headers::api::StringPointer; +use bun_core::StringPointer; use bun_http::headers::append_etag; use bun_http::{Headers, Method}; use bun_http_types::ETag; diff --git a/src/url/lib.rs b/src/url/lib.rs index 94860e3f1070..9c26f3f474f0 100644 --- a/src/url/lib.rs +++ b/src/url/lib.rs @@ -11,12 +11,7 @@ use bun_core::{String as BunString, Tag as BunStringTag, strings}; use bun_paths::resolve_path::{self, platform}; use bun_wyhash::hash as wyhash; -// `bun.schema.api.StringPointer` — canonical definition lives in `bun_core` -// (T0, already a dep). Re-exported under `api::` so `QueryStringMap` / -// `CombinedScanner` field types keep resolving. -pub mod api { - pub use bun_core::StringPointer; -} +use bun_core::StringPointer; use bun_core::io::Write as _; @@ -862,9 +857,9 @@ impl<'a> URL<'a> { #[derive(Clone, Copy)] pub struct Param { - pub(crate) name: api::StringPointer, + pub(crate) name: StringPointer, pub(crate) name_hash: u64, - pub(crate) value: api::StringPointer, + pub(crate) value: StringPointer, } // Vec (AoS); SoA would be a perf optimization only. @@ -931,7 +926,7 @@ impl QueryStringMap { Iterator::init(self) } - pub(crate) fn str(&self, ptr: api::StringPointer) -> &[u8] { + pub(crate) fn str(&self, ptr: StringPointer) -> &[u8] { // SAFETY: `slice` is valid for the lifetime of `self` (either borrows // `self.buffer` or an external query_string the caller keeps alive). let slice = unsafe { &*self.slice }; @@ -1443,8 +1438,8 @@ impl PercentEncoding { struct ScannerResult { pub(crate) name_needs_decoding: bool, pub(crate) value_needs_decoding: bool, - pub(crate) name: api::StringPointer, - pub(crate) value: api::StringPointer, + pub(crate) name: StringPointer, + pub(crate) value: StringPointer, } impl ScannerResult { @@ -1491,25 +1486,25 @@ impl<'a> CombinedScanner<'a> { } } -fn string_pointer_from_strings(parent: &[u8], in_: &[u8]) -> api::StringPointer { +fn string_pointer_from_strings(parent: &[u8], in_: &[u8]) -> StringPointer { if in_.is_empty() || parent.is_empty() { - return api::StringPointer::default(); + return StringPointer::default(); } if let Some([offset, length]) = bun_core::range_of_slice_in_buffer(in_, parent) { - return api::StringPointer { offset, length }; + return StringPointer { offset, length }; } else { if let Some(i) = strings::index_of(parent, in_) { debug_assert!(strings::eql_long(&parent[i..][..in_.len()], in_, false)); - return api::StringPointer { + return StringPointer { offset: u32::try_from(i).unwrap(), length: u32::try_from(in_.len()).unwrap(), }; } } - api::StringPointer::default() + StringPointer::default() } pub struct PathnameScanner<'a> { @@ -1604,11 +1599,11 @@ impl<'a> Scanner<'a> { let slice = &self.query_string[self.i..]; relative_i = 0; - let mut name = api::StringPointer { + let mut name = StringPointer { offset: u32::try_from(self.i).unwrap(), length: 0, }; - let mut value = api::StringPointer { + let mut value = StringPointer { offset: 0, length: 0, }; diff --git a/test/bundler/bundler_loader.test.ts b/test/bundler/bundler_loader.test.ts index 72dfc0e353dd..4b1cff5fc086 100644 --- a/test/bundler/bundler_loader.test.ts +++ b/test/bundler/bundler_loader.test.ts @@ -193,6 +193,29 @@ describe("bundler", async () => { run: { stdout: '[true,true,null,"{\\"__proto__\\":{\\"x\\":1},\\"a\\":2}"]' }, }); + // A `loader` map entry naming "jsonc" / "json5" must select that loader for + // the extension (it used to silently degrade to strict "json"). + for (const backend of ["api", "cli"] as const) { + itBundled(`bun/loader-map-jsonc-by-extension-${backend}`, { + target: "bun", + backend, + loader: { ".data": "jsonc", ".data5": "json5" }, + files: { + "/entry.ts": /* js */ ` + import c from './conf.data'; + import c5 from './conf.data5'; + console.write(JSON.stringify([c, c5])); + `, + "/conf.data": `{ + // comment + "a": 1, + }`, + "/conf.data5": `{a: 2, /* comment */}`, + }, + run: { stdout: '[{"a":1},{"a":2}]' }, + }); + } + itBundled("bun/loader-json-nested-proto-key-is-own-property", { target: "bun", files: { diff --git a/test/bundler/bundler_plugin.test.ts b/test/bundler/bundler_plugin.test.ts index 0b9ff951d5dd..0d6111de4ac5 100644 --- a/test/bundler/bundler_plugin.test.ts +++ b/test/bundler/bundler_plugin.test.ts @@ -276,6 +276,43 @@ describe("bundler", () => { }, }; }); + itBundled("plugin/ResolveKind", () => { + const kinds: Record = {}; + return { + files: { + "index.ts": /* ts */ ` + import "./styles.css"; + const c = require("./c.cjs"); + import("./d.js").then(d => console.log(c, d.default)); + `, + "styles.css": /* css */ ` + @import "./other.css"; + .a { background: url("./img.png"); } + `, + "other.css": `.b { color: red; }`, + "img.png": `png`, + "c.cjs": `module.exports = 1;`, + "d.js": `export default 2;`, + }, + outdir: "/out", + plugins(builder) { + builder.onResolve({ filter: /.*/ }, args => { + kinds[path.basename(args.path)] = args.kind; + return undefined; + }); + }, + onAfterBundle() { + expect(kinds).toEqual({ + "index.ts": "entry-point-build", + "styles.css": "import-statement", + "c.cjs": "require-call", + "d.js": "dynamic-import", + "other.css": "import-rule", + "img.png": "url-token", + }); + }, + }; + }); itBundled("plugin/ResolveNamespaceFilterIgnored", ({ root }) => { let onResolveCountBad = 0; diff --git a/test/bundler/cli.test.ts b/test/bundler/cli.test.ts index eaca7232abc3..f84e787b9fad 100644 --- a/test/bundler/cli.test.ts +++ b/test/bundler/cli.test.ts @@ -18,6 +18,30 @@ describe.concurrent( ); }); + test.each([ + ["error", false], + ["warn", true], + ["info", true], + ["debug", true], + ])(`bunfig logLevel = "%s" shows build warnings: %p`, async (logLevel, showsWarnings) => { + using dir = tempDir("bun-build-loglevel", { + "bunfig.toml": `logLevel = "${logLevel}"`, + "index.jsx": `console.log(
);`, + }); + await using proc = Bun.spawn({ + cmd: [bunExe(), "build", "index.jsx", "--packages=external"], + env: bunEnv, + cwd: String(dir), + stdout: "ignore", + stderr: "pipe", + }); + const [stderr, exitCode] = await Promise.all([proc.stderr.text(), proc.exited]); + const warning = 'warn: "key" prop after a {...spread} is deprecated in JSX.'; + if (showsWarnings) expect(stderr).toContain(warning); + else expect(stderr).not.toContain(warning); + expect(exitCode).toBe(0); + }); + async function testCompile(outfile: string) { const { exited } = Bun.spawn({ cmd: [ diff --git a/test/bundler/expectBundled.ts b/test/bundler/expectBundled.ts index 8e3bb2db791d..72a3b1679daf 100644 --- a/test/bundler/expectBundled.ts +++ b/test/bundler/expectBundled.ts @@ -6,16 +6,7 @@ import { callerSourceOrigin } from "bun:jsc"; import type { Matchers } from "bun:test"; import * as esbuild from "esbuild"; import filenamify from "filenamify"; -import { - existsSync, - mkdirSync, - mkdtempSync, - readdirSync, - readFileSync, - realpathSync, - rmSync, - writeFileSync, -} from "fs"; +import { existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from "fs"; import { bunEnv, bunExe, isCI, isDebug } from "harness"; import { tmpdir } from "os"; import path from "path"; @@ -617,7 +608,9 @@ function expectBundled( } if (!ESBUILD && loader) { const loaderValues = [...new Set(Object.values(loader))]; - const supportedLoaderTypes = ["js", "jsx", "ts", "tsx", "css", "json", "text", "file", "wtf", "toml"]; + const supportedLoaderTypes = ["js", "jsx", "ts", "tsx", "css", "text", "file", "wtf"].concat( + ["json", "jsonc", "json5", "toml", "yaml", "xml", "md"], // data formats + ); const unsupportedLoaderTypes = loaderValues.filter(x => !supportedLoaderTypes.includes(x)); if (unsupportedLoaderTypes.length > 0) { throw new Error(`loader '${unsupportedLoaderTypes.join("', '")}' not implemented in bun build`); diff --git a/test/internal/source-lints/loader-numbering.test.ts b/test/internal/source-lints/loader-numbering.test.ts new file mode 100644 index 000000000000..98cabb269ede --- /dev/null +++ b/test/internal/source-lints/loader-numbering.test.ts @@ -0,0 +1,56 @@ +import { expect, test } from "bun:test"; +import { readFileSync } from "node:fs"; +import path from "node:path"; + +// `bun_ast::Loader` (src/ast/loader.rs) is the one loader numbering. Its +// discriminants cross language boundaries as plain bytes in a few places that +// cannot include a Rust enum, so each keeps a hand-written copy of the values. +// This test fails when any copy drifts from the Rust enum. +const repoRoot = path.resolve(import.meta.dir, "..", "..", ".."); +const read = (rel: string) => readFileSync(path.join(repoRoot, rel), "utf8"); + +/** `{ JSX: 0, JS: 1, …, SQLITE_EMBEDDED: 16, … }` from `pub enum Loader { Jsx = 0, … }`. */ +function rustLoaders(): Record { + const body = read("src/ast/loader.rs").match(/pub enum Loader \{([^}]*)\}/)?.[1]; + expect(body).toBeDefined(); + const loaders: Record = {}; + for (const [, name, id] of body!.matchAll(/^\s*([A-Z][A-Za-z0-9]*)\s*=\s*(\d+),/gm)) { + loaders[name.replace(/(?<=[a-z0-9])([A-Z])/g, "_$1").toUpperCase()] = Number(id); + } + expect(Object.keys(loaders).length).toBeGreaterThan(10); + return loaders; +} + +function constants(source: string, pattern: RegExp): Record { + const found: Record = {}; + for (const [, name, value] of source.matchAll(pattern)) found[name] = Number(value); + expect(Object.keys(found).length).toBeGreaterThan(10); + return found; +} + +const expected = rustLoaders(); + +test("src/jsc/bindings/headers-handwritten.h BunLoaderType* match bun_ast::Loader", () => { + const actual = constants( + read("src/jsc/bindings/headers-handwritten.h"), + /^inline constexpr BunLoaderType BunLoaderType([A-Za-z0-9_]+) = (\d+);/gm, + ); + const { None, ...loaders } = actual; + // `bun_jsc::BunLoaderType::NONE`; must not collide with a real loader. + expect(None).toBe(255); + expect(Object.values(expected)).not.toContain(None); + expect(loaders).toEqual(expected); +}); + +test.each([ + "packages/bun-native-bundler-plugin-api/bundler_plugin.h", + "packages/bun-native-plugin-rs/headers/bun-native-bundler-plugin-api/bundler_plugin.h", +])("%s BUN_LOADER_* match bun_ast::Loader", file => { + expect(constants(read(file), /^\s*BUN_LOADER_([A-Z0-9_]+) = (\d+),/gm)).toEqual(expected); +}); + +test("packages/bun-native-plugin-rs/src/sys.rs BunLoader matches bun_ast::Loader", () => { + const body = read("packages/bun-native-plugin-rs/src/sys.rs").match(/pub enum BunLoader \{([^}]*)\}/)?.[1]; + expect(body).toBeDefined(); + expect(constants(body!, /^\s*BUN_LOADER_([A-Z0-9_]+) = (\d+),/gm)).toEqual(expected); +});