From 5e24b22adb1da0775a3de819c0ae414902693d25 Mon Sep 17 00:00:00 2001 From: Caitlin Pinn Date: Thu, 11 Dec 2025 14:33:38 -0800 Subject: [PATCH 1/5] add option for esm bundling --- js/src/cli-util/bundle.ts | 1 + js/src/cli-util/types.ts | 3 ++ js/src/cli.ts | 70 ++++++++++++++++++++++++++------- js/src/functions/load-module.ts | 34 ++++++++++++++++ 4 files changed, 94 insertions(+), 14 deletions(-) diff --git a/js/src/cli-util/bundle.ts b/js/src/cli-util/bundle.ts index 36e2cc561..a263b4e87 100644 --- a/js/src/cli-util/bundle.ts +++ b/js/src/cli-util/bundle.ts @@ -34,6 +34,7 @@ export async function bundleCommand(args: BundleArgs) { files: args.files, tsconfig: args.tsconfig, externalPackages: args.external_packages, + bundleFormat: args.bundle_format, }); try { diff --git a/js/src/cli-util/types.ts b/js/src/cli-util/types.ts index dd6faefc4..f4b2ac2b6 100644 --- a/js/src/cli-util/types.ts +++ b/js/src/cli-util/types.ts @@ -1,5 +1,7 @@ import { type IfExistsType as IfExists } from "../generated_types"; +export type BundleFormat = "cjs" | "esm"; + export interface CommonArgs { verbose: boolean; } @@ -15,6 +17,7 @@ export interface CompileArgs { tsconfig?: string; terminate_on_failure: boolean; external_packages?: string[]; + bundle_format?: BundleFormat; } export interface RunArgs extends CommonArgs, AuthArgs, CompileArgs { diff --git a/js/src/cli.ts b/js/src/cli.ts index ec5bea45c..5724f4086 100755 --- a/js/src/cli.ts +++ b/js/src/cli.ts @@ -45,9 +45,9 @@ import { configureNode } from "./node"; import { isEmpty } from "./util"; import { loadEnvConfig } from "@next/env"; import { uploadHandleBundles } from "./functions/upload"; -import { loadModule } from "./functions/load-module"; +import { loadModule, loadModuleEsm } from "./functions/load-module"; import { bundleCommand } from "./cli-util/bundle"; -import { RunArgs } from "./cli-util/types"; +import { RunArgs, type BundleFormat } from "./cli-util/types"; import { pullCommand } from "./cli-util/pull"; import { runDevServer } from "../dev/server"; @@ -66,6 +66,8 @@ const INCLUDE_BUNDLE = ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"]; const EXCLUDE = ["**/node_modules/**", "**/dist/**", "**/build/**"]; const OUT_EXT = "js"; +const DEFAULT_BUNDLE_FORMAT: BundleFormat = "cjs"; + configureNode(); export interface BuildSuccess { @@ -92,14 +94,18 @@ export interface FileHandle { destroy: () => Promise; } -function evaluateBuildResults( +async function evaluateBuildResults( inFile: string, buildResult: esbuild.BuildResult, -): EvaluatorFile | null { - if (!buildResult.outputFiles) { + bundleFormat: BundleFormat, +): Promise { + if (!buildResult.outputFiles || buildResult.outputFiles.length === 0) { return null; } const moduleText = buildResult.outputFiles[0].text; + if (bundleFormat === "esm") { + return await loadModuleEsm({ inFile, moduleText }); + } return loadModule({ inFile, moduleText }); } @@ -212,7 +218,11 @@ function buildWatchPluginForEvaluator( return; } - const evalResult = evaluateBuildResults(inFile, result); + const evalResult = await evaluateBuildResults( + inFile, + result, + opts.bundleFormat ?? DEFAULT_BUNDLE_FORMAT, + ); if (!evalResult) { return; } @@ -304,6 +314,7 @@ async function initFile({ tsconfig, plugins, externalPackages, + bundleFormat, }: { inFile: string; outFile: string; @@ -311,6 +322,7 @@ async function initFile({ tsconfig?: string; plugins?: PluginMaker[]; externalPackages?: string[]; + bundleFormat: BundleFormat; }): Promise { const buildOptions = buildOpts({ fileName: inFile, @@ -318,6 +330,7 @@ async function initFile({ tsconfig, plugins, externalPackages, + bundleFormat, }); const ctx = await esbuild.context(buildOptions); @@ -335,7 +348,11 @@ async function initFile({ sourceFile: inFile, }; } - const evaluator = evaluateBuildResults(inFile, result) || { + const evaluator = (await evaluateBuildResults( + inFile, + result, + bundleFormat, + )) || { functions: [], prompts: [], evaluators: {}, @@ -355,6 +372,7 @@ async function initFile({ tsconfig, plugins, externalPackages, + bundleFormat, }), external: [], write: true, @@ -398,6 +416,7 @@ interface EvaluatorOpts { jsonl: boolean; filters: Filter[]; progressReporter: ProgressReporter; + bundleFormat: BundleFormat; } export function handleBuildFailure({ @@ -794,18 +813,27 @@ function buildOpts({ tsconfig, plugins: argPlugins, externalPackages, + bundleFormat, }: { fileName: string; outFile: string; tsconfig?: string; plugins?: PluginMaker[]; externalPackages?: string[]; + bundleFormat?: BundleFormat; }): esbuild.BuildOptions { - const plugins = [ - nativeNodeModulesPlugin, - createMarkKnownPackagesExternalPlugin(externalPackages), - ...(argPlugins || []).map((fn) => fn(fileName)), - ]; + const effectiveFormat = bundleFormat ?? DEFAULT_BUNDLE_FORMAT; + const plugins = + effectiveFormat === "esm" + ? [ + nativeNodeModulesPlugin, + ...(argPlugins || []).map((fn) => fn(fileName)), + ] + : [ + nativeNodeModulesPlugin, + createMarkKnownPackagesExternalPlugin(externalPackages), + ...(argPlugins || []).map((fn) => fn(fileName)), + ]; return { entryPoints: [fileName], bundle: true, @@ -816,8 +844,10 @@ function buildOpts({ // Remove the leading "v" from process.version target: `node${process.version.slice(1)}`, tsconfig, - external: ["node_modules/*", "fsevents"], - plugins: plugins, + external: + effectiveFormat === "esm" ? ["fsevents"] : ["node_modules/*", "fsevents"], + plugins, + format: effectiveFormat, }; } @@ -827,12 +857,14 @@ export async function initializeHandles({ plugins, tsconfig, externalPackages, + bundleFormat, }: { files: string[]; mode: "eval" | "bundle"; plugins?: PluginMaker[]; tsconfig?: string; externalPackages?: string[]; + bundleFormat?: BundleFormat; }): Promise> { const files: Record = {}; const inputPaths = inputFiles.length > 0 ? inputFiles : ["."]; @@ -876,6 +908,7 @@ export async function initializeHandles({ plugins, tsconfig, externalPackages, + bundleFormat: bundleFormat ?? DEFAULT_BUNDLE_FORMAT, }), ); } @@ -901,6 +934,9 @@ async function run(args: RunArgs) { } } + const bundleFormat: BundleFormat = + args.bundle_format ?? DEFAULT_BUNDLE_FORMAT; + const evaluatorOpts: EvaluatorOpts = { verbose: args.verbose, apiKey: args.api_key, @@ -917,6 +953,7 @@ async function run(args: RunArgs) { : new BarProgressReporter(), filters: args.filter ? parseFilters(args.filter) : [], list: !!args.list, + bundleFormat, }; if (args.list && args.watch) { @@ -937,6 +974,7 @@ async function run(args: RunArgs) { tsconfig: args.tsconfig, plugins, externalPackages: args.external_packages, + bundleFormat, }); if (args.dev) { @@ -1012,6 +1050,10 @@ function addCompileArgs(parser: ArgumentParser) { nargs: "*", help: "Additional packages to mark as external during bundling. These packages will not be included in the bundle and must be available at runtime. Use this to resolve bundling errors with native modules or problematic dependencies. Example: --external-packages sqlite3 fsevents @mapbox/node-pre-gyp", }); + parser.add_argument("--bundle-format", { + choices: ["cjs", "esm"], + help: "Module format to use when bundling code. Defaults to cjs.", + }); } async function main() { diff --git a/js/src/functions/load-module.ts b/js/src/functions/load-module.ts index 89d38b3e2..8557ad156 100644 --- a/js/src/functions/load-module.ts +++ b/js/src/functions/load-module.ts @@ -40,3 +40,37 @@ export function loadModule({ return { ...globalThis._evals }; }); } + +// Use dynamic import with a data URL to execute ESM output (e.g., for top-level await) +// while keeping the caller in CJS. This avoids TypeScript downleveling of `import()`. +async function dynamicImport(specifier: string): Promise { + // eslint-disable-next-line @typescript-eslint/no-implied-eval + const importer = new Function("specifier", "return import(specifier);") as ( + specifier: string, + ) => Promise; + return await importer(specifier); +} + +export async function loadModuleEsm({ + inFile, + moduleText, +}: { + inFile: string; + moduleText: string; +}): Promise { + return await evalWithModuleContext(inFile, async () => { + globalThis._evals = { + functions: [], + prompts: [], + evaluators: {}, + reporters: {}, + }; + globalThis._lazy_load = true; + globalThis.__inherited_braintrust_state = _internalGetGlobalState(); + + const base64 = Buffer.from(moduleText, "utf8").toString("base64"); + const url = `data:text/javascript;base64,${base64}`; + await dynamicImport(url); + return { ...globalThis._evals }; + }); +} From c00b78ecb9c7d866272cde2750420f940ca2f4d4 Mon Sep 17 00:00:00 2001 From: Caitlin Pinn Date: Thu, 11 Dec 2025 15:22:56 -0800 Subject: [PATCH 2/5] more fixes --- js/src/cli.ts | 35 +++++++++++++++++---------------- js/src/functions/load-module.ts | 12 +++++------ 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/js/src/cli.ts b/js/src/cli.ts index 5724f4086..80a36a39c 100755 --- a/js/src/cli.ts +++ b/js/src/cli.ts @@ -45,7 +45,7 @@ import { configureNode } from "./node"; import { isEmpty } from "./util"; import { loadEnvConfig } from "@next/env"; import { uploadHandleBundles } from "./functions/upload"; -import { loadModule, loadModuleEsm } from "./functions/load-module"; +import { loadModule, loadModuleEsmFromFile } from "./functions/load-module"; import { bundleCommand } from "./cli-util/bundle"; import { RunArgs, type BundleFormat } from "./cli-util/types"; import { pullCommand } from "./cli-util/pull"; @@ -104,7 +104,11 @@ async function evaluateBuildResults( } const moduleText = buildResult.outputFiles[0].text; if (bundleFormat === "esm") { - return await loadModuleEsm({ inFile, moduleText }); + const srcDir = path.dirname(inFile); + const srcBase = path.basename(inFile, path.extname(inFile)); + const runtimePath = path.join(srcDir, `.braintrust-eval-${srcBase}.mjs`); + await fs.promises.writeFile(runtimePath, moduleText, "utf8"); + return await loadModuleEsmFromFile({ inFile, modulePath: runtimePath }); } return loadModule({ inFile, moduleText }); } @@ -823,17 +827,11 @@ function buildOpts({ bundleFormat?: BundleFormat; }): esbuild.BuildOptions { const effectiveFormat = bundleFormat ?? DEFAULT_BUNDLE_FORMAT; - const plugins = - effectiveFormat === "esm" - ? [ - nativeNodeModulesPlugin, - ...(argPlugins || []).map((fn) => fn(fileName)), - ] - : [ - nativeNodeModulesPlugin, - createMarkKnownPackagesExternalPlugin(externalPackages), - ...(argPlugins || []).map((fn) => fn(fileName)), - ]; + const plugins = [ + nativeNodeModulesPlugin, + createMarkKnownPackagesExternalPlugin(externalPackages), + ...(argPlugins || []).map((fn) => fn(fileName)), + ]; return { entryPoints: [fileName], bundle: true, @@ -844,8 +842,7 @@ function buildOpts({ // Remove the leading "v" from process.version target: `node${process.version.slice(1)}`, tsconfig, - external: - effectiveFormat === "esm" ? ["fsevents"] : ["node_modules/*", "fsevents"], + external: ["node_modules/*", "fsevents"], plugins, format: effectiveFormat, }; @@ -889,8 +886,12 @@ export async function initializeHandles({ process.exit(0); } - const tmpDir = path.join(os.tmpdir(), `btevals-${uuidv4().slice(0, 8)}`); - // fs.mkdirSync(tmpDir, { recursive: true }); + const tmpDir = path.join( + process.cwd(), + ".braintrust-evals", + `btevals-${uuidv4().slice(0, 8)}`, + ); + fs.mkdirSync(tmpDir, { recursive: true }); const initPromises = []; for (const file of Object.keys(files)) { diff --git a/js/src/functions/load-module.ts b/js/src/functions/load-module.ts index 8557ad156..84e6ec0c7 100644 --- a/js/src/functions/load-module.ts +++ b/js/src/functions/load-module.ts @@ -1,5 +1,6 @@ import nodeModulesPaths from "../jest/nodeModulesPaths"; import path, { dirname } from "path"; +import { pathToFileURL } from "url"; import { _internalGetGlobalState } from "../logger"; import { EvaluatorFile } from "../framework"; @@ -41,7 +42,7 @@ export function loadModule({ }); } -// Use dynamic import with a data URL to execute ESM output (e.g., for top-level await) +// Use dynamic import to execute ESM output (e.g., for top-level await) // while keeping the caller in CJS. This avoids TypeScript downleveling of `import()`. async function dynamicImport(specifier: string): Promise { // eslint-disable-next-line @typescript-eslint/no-implied-eval @@ -51,12 +52,12 @@ async function dynamicImport(specifier: string): Promise { return await importer(specifier); } -export async function loadModuleEsm({ +export async function loadModuleEsmFromFile({ inFile, - moduleText, + modulePath, }: { inFile: string; - moduleText: string; + modulePath: string; }): Promise { return await evalWithModuleContext(inFile, async () => { globalThis._evals = { @@ -68,8 +69,7 @@ export async function loadModuleEsm({ globalThis._lazy_load = true; globalThis.__inherited_braintrust_state = _internalGetGlobalState(); - const base64 = Buffer.from(moduleText, "utf8").toString("base64"); - const url = `data:text/javascript;base64,${base64}`; + const url = pathToFileURL(modulePath).href; await dynamicImport(url); return { ...globalThis._evals }; }); From 380b5da10040b8915fba57dd59f2fa5dabc97b54 Mon Sep 17 00:00:00 2001 From: Caitlin Pinn Date: Thu, 11 Dec 2025 16:16:26 -0800 Subject: [PATCH 3/5] remove the addition of mkdir, ensure tmp dir used only change to experimental bundling flag --- js/src/cli-util/bundle.ts | 2 +- js/src/cli-util/types.ts | 2 +- js/src/cli.ts | 20 ++++++++------------ 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/js/src/cli-util/bundle.ts b/js/src/cli-util/bundle.ts index a263b4e87..779ed03c3 100644 --- a/js/src/cli-util/bundle.ts +++ b/js/src/cli-util/bundle.ts @@ -34,7 +34,7 @@ export async function bundleCommand(args: BundleArgs) { files: args.files, tsconfig: args.tsconfig, externalPackages: args.external_packages, - bundleFormat: args.bundle_format, + bundleFormat: args.experimental_bundle_format, }); try { diff --git a/js/src/cli-util/types.ts b/js/src/cli-util/types.ts index f4b2ac2b6..00496a749 100644 --- a/js/src/cli-util/types.ts +++ b/js/src/cli-util/types.ts @@ -17,7 +17,7 @@ export interface CompileArgs { tsconfig?: string; terminate_on_failure: boolean; external_packages?: string[]; - bundle_format?: BundleFormat; + experimental_bundle_format?: BundleFormat; } export interface RunArgs extends CommonArgs, AuthArgs, CompileArgs { diff --git a/js/src/cli.ts b/js/src/cli.ts index 80a36a39c..66a52a031 100755 --- a/js/src/cli.ts +++ b/js/src/cli.ts @@ -104,9 +104,10 @@ async function evaluateBuildResults( } const moduleText = buildResult.outputFiles[0].text; if (bundleFormat === "esm") { - const srcDir = path.dirname(inFile); - const srcBase = path.basename(inFile, path.extname(inFile)); - const runtimePath = path.join(srcDir, `.braintrust-eval-${srcBase}.mjs`); + const runtimePath = path.join( + os.tmpdir(), + `.braintrust-eval-${uuidv4().slice(0, 8)}.mjs`, + ); await fs.promises.writeFile(runtimePath, moduleText, "utf8"); return await loadModuleEsmFromFile({ inFile, modulePath: runtimePath }); } @@ -886,12 +887,7 @@ export async function initializeHandles({ process.exit(0); } - const tmpDir = path.join( - process.cwd(), - ".braintrust-evals", - `btevals-${uuidv4().slice(0, 8)}`, - ); - fs.mkdirSync(tmpDir, { recursive: true }); + const tmpDir = path.join(os.tmpdir(), `btevals-${uuidv4().slice(0, 8)}`); const initPromises = []; for (const file of Object.keys(files)) { @@ -936,7 +932,7 @@ async function run(args: RunArgs) { } const bundleFormat: BundleFormat = - args.bundle_format ?? DEFAULT_BUNDLE_FORMAT; + args.experimental_bundle_format ?? DEFAULT_BUNDLE_FORMAT; const evaluatorOpts: EvaluatorOpts = { verbose: args.verbose, @@ -1051,9 +1047,9 @@ function addCompileArgs(parser: ArgumentParser) { nargs: "*", help: "Additional packages to mark as external during bundling. These packages will not be included in the bundle and must be available at runtime. Use this to resolve bundling errors with native modules or problematic dependencies. Example: --external-packages sqlite3 fsevents @mapbox/node-pre-gyp", }); - parser.add_argument("--bundle-format", { + parser.add_argument("--experimental-bundle-format", { choices: ["cjs", "esm"], - help: "Module format to use when bundling code. Defaults to cjs.", + help: "Experimental: module format to use when bundling code. Defaults to cjs.", }); } From f1f3850b2f4134481fdf4b703d2c97a4c7413bdb Mon Sep 17 00:00:00 2001 From: Caitlin Pinn Date: Thu, 11 Dec 2025 20:03:30 -0800 Subject: [PATCH 4/5] more attempts to have experimental esm bundling --- js/src/cli-util/bundle.ts | 1 + js/src/cli.ts | 31 ++++++++++------- js/src/functions/infer-source.ts | 59 +++++++++++++++++++++++++++++--- js/src/functions/upload.ts | 6 ++++ 4 files changed, 81 insertions(+), 16 deletions(-) diff --git a/js/src/cli-util/bundle.ts b/js/src/cli-util/bundle.ts index 779ed03c3..3548714c5 100644 --- a/js/src/cli-util/bundle.ts +++ b/js/src/cli-util/bundle.ts @@ -70,6 +70,7 @@ export async function bundleCommand(args: BundleArgs) { setCurrent: true, verbose: args.verbose, defaultIfExists: args.if_exists, + bundleFormat: args.experimental_bundle_format, }); if (numFailed > 0) { diff --git a/js/src/cli.ts b/js/src/cli.ts index 66a52a031..5ab021dbf 100755 --- a/js/src/cli.ts +++ b/js/src/cli.ts @@ -104,9 +104,11 @@ async function evaluateBuildResults( } const moduleText = buildResult.outputFiles[0].text; if (bundleFormat === "esm") { + const srcDir = path.dirname(inFile); + const srcBase = path.basename(inFile, path.extname(inFile)); const runtimePath = path.join( - os.tmpdir(), - `.braintrust-eval-${uuidv4().slice(0, 8)}.mjs`, + srcDir, + `.braintrust-eval-${srcBase}-${uuidv4().slice(0, 8)}.mjs`, ); await fs.promises.writeFile(runtimePath, moduleText, "utf8"); return await loadModuleEsmFromFile({ inFile, modulePath: runtimePath }); @@ -370,18 +372,22 @@ async function initFile({ } }, bundle: async () => { + const isEsmBundle = bundleFormat === "esm"; + const baseOptions = buildOpts({ + fileName: inFile, + outFile: bundleFile, + tsconfig, + plugins, + externalPackages, + bundleFormat: isEsmBundle ? "esm" : "cjs", + }); const buildOptions: esbuild.BuildOptions = { - ...buildOpts({ - fileName: inFile, - outFile: bundleFile, - tsconfig, - plugins, - externalPackages, - bundleFormat, - }), - external: [], + ...baseOptions, + // For CJS bundles, we historically inlined everything. For ESM bundles, + // keep externals so CJS deps (node_modules) are not inlined. + external: isEsmBundle ? baseOptions.external : [], write: true, - plugins: [], + plugins: isEsmBundle ? baseOptions.plugins : [], minify: true, sourcemap: true, }; @@ -644,6 +650,7 @@ async function runOnce( setCurrent: opts.setCurrent, defaultIfExists: "replace", verbose: opts.verbose, + bundleFormat: opts.bundleFormat, }); } diff --git a/js/src/functions/infer-source.ts b/js/src/functions/infer-source.ts index 983aeaaa9..0ab9d028b 100644 --- a/js/src/functions/infer-source.ts +++ b/js/src/functions/infer-source.ts @@ -1,7 +1,7 @@ import { SourceMapConsumer } from "source-map"; import * as fs from "fs/promises"; import { EvaluatorFile, warning } from "../framework"; -import { loadModule } from "./load-module"; +import { loadModule, loadModuleEsmFromFile } from "./load-module"; import { type CodeBundleType as CodeBundle } from "../generated_types"; import path from "path"; import type { Node } from "typescript"; @@ -14,27 +14,78 @@ interface SourceMapContext { sourceMap: SourceMapConsumer; } +type BundleFormat = "cjs" | "esm"; + +async function findNearestNodeModules( + startDir: string, +): Promise { + let dir = startDir; + // eslint-disable-next-line no-constant-condition + while (true) { + const candidate = path.join(dir, "node_modules"); + try { + // eslint-disable-next-line no-await-in-loop + const stat = await fs.stat(candidate); + if (stat.isDirectory()) { + return candidate; + } + } catch { + // ignore + } + const parent = path.dirname(dir); + if (parent === dir) { + return null; + } + dir = parent; + } +} + export async function makeSourceMapContext({ inFile, outFile, sourceMapFile, + bundleFormat = "cjs", }: { inFile: string; outFile: string; sourceMapFile: string; + bundleFormat?: BundleFormat; }): Promise { const [inFileContents, outFileContents, sourceMap] = await Promise.all([ fs.readFile(inFile, "utf8"), fs.readFile(outFile, "utf8"), (async () => { - const sourceMap = await fs.readFile(sourceMapFile, "utf8"); - const sourceMapJSON = JSON.parse(sourceMap); + const sourceMapText = await fs.readFile(sourceMapFile, "utf8"); + const sourceMapJSON = JSON.parse(sourceMapText); return new SourceMapConsumer(sourceMapJSON); })(), ]); + + let outFileModule: EvaluatorFile; + if (bundleFormat === "esm") { + const runtimeDir = path.dirname(outFile); + const nodeModulesSrc = await findNearestNodeModules(path.dirname(inFile)); + if (nodeModulesSrc) { + const nodeModulesDest = path.join(runtimeDir, "node_modules"); + try { + await fs.mkdir(runtimeDir, { recursive: true }); + // Best-effort: create a symlink to the real node_modules so ESM imports like "braintrust" resolve. + await fs.symlink(nodeModulesSrc, nodeModulesDest, "junction"); + } catch { + // Ignore symlink errors; resolution may still work via global paths. + } + } + outFileModule = await loadModuleEsmFromFile({ + inFile, + modulePath: outFile, + }); + } else { + outFileModule = loadModule({ inFile, moduleText: outFileContents }); + } + return { inFiles: { [inFile]: inFileContents.split("\n") }, - outFileModule: loadModule({ inFile, moduleText: outFileContents }), + outFileModule, outFileLines: outFileContents.split("\n"), sourceMapDir: path.dirname(sourceMapFile), sourceMap, diff --git a/js/src/functions/upload.ts b/js/src/functions/upload.ts index 3cdf6bab7..db585f3f1 100644 --- a/js/src/functions/upload.ts +++ b/js/src/functions/upload.ts @@ -62,6 +62,7 @@ export async function uploadHandleBundles({ setCurrent, verbose, defaultIfExists, + bundleFormat = "cjs", }: { buildResults: BuildSuccess[]; evalToExperiment?: Record>; @@ -72,6 +73,7 @@ export async function uploadHandleBundles({ verbose: boolean; setCurrent: boolean; defaultIfExists: IfExists; + bundleFormat?: "cjs" | "esm"; }) { console.error( `Processing ${buildResults.length} ${pluralize("file", buildResults.length)}...`, @@ -208,6 +210,7 @@ export async function uploadHandleBundles({ handles, defaultIfExists, verbose, + bundleFormat, }); }); @@ -238,6 +241,7 @@ async function uploadBundles({ handles, defaultIfExists, verbose, + bundleFormat = "cjs", }: { sourceFile: string; prompts: FunctionEvent[]; @@ -248,6 +252,7 @@ async function uploadBundles({ handles: Record; defaultIfExists: IfExists; verbose: boolean; + bundleFormat?: "cjs" | "esm"; }): Promise { const orgId = _internalGetGlobalState().orgId; if (!orgId) { @@ -270,6 +275,7 @@ async function uploadBundles({ inFile: sourceFile, outFile: bundleFileName, sourceMapFile: bundleFileName + ".map", + bundleFormat, }); let pathInfo: z.infer | undefined = undefined; From 8fbf2bad4ae688b2cf576bd4e6e1386e38277613 Mon Sep 17 00:00:00 2001 From: Caitlin Pinn Date: Thu, 11 Dec 2025 20:18:23 -0800 Subject: [PATCH 5/5] add detection banner --- js/src/cli.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/js/src/cli.ts b/js/src/cli.ts index 5ab021dbf..9ff480eff 100755 --- a/js/src/cli.ts +++ b/js/src/cli.ts @@ -388,6 +388,11 @@ async function initFile({ external: isEsmBundle ? baseOptions.external : [], write: true, plugins: isEsmBundle ? baseOptions.plugins : [], + banner: isEsmBundle + ? { + js: "// @bt-esm\n", + } + : baseOptions.banner, minify: true, sourcemap: true, };