From 43b564c4d2de2849757ee968efd018d91f079408 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Mon, 11 May 2026 13:47:36 -0700 Subject: [PATCH 1/3] feat: single executable (SEA) --- .mise.toml => mise.toml | 2 +- package.json | 1 + packages/braintrust-wizard/package.json | 5 +- .../braintrust-wizard/rolldown.config.mjs | 2 +- packages/braintrust-wizard/src/cli.ts | 86 ++-- packages/braintrust-wizard/src/instrument.ts | 120 ++++- packages/bt-wizard-harness/package.json | 2 +- pnpm-lock.yaml | 483 +++++++++++++++--- scripts/build-sea.mjs | 57 +++ sea-config.json | 9 + 10 files changed, 634 insertions(+), 133 deletions(-) rename .mise.toml => mise.toml (59%) create mode 100644 scripts/build-sea.mjs create mode 100644 sea-config.json diff --git a/.mise.toml b/mise.toml similarity index 59% rename from .mise.toml rename to mise.toml index e4a1848..3bb5a34 100644 --- a/.mise.toml +++ b/mise.toml @@ -1,3 +1,3 @@ [tools] -node = "24.15.0" +node = "26.1.0" pnpm = "10.33.3" diff --git a/package.json b/package.json index d99b846..b0ff009 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "scripts": { "start": "pnpm --filter braintrust-wizard start", "build": "pnpm --filter braintrust-wizard build", + "build:sea": "pnpm build && rm -rf dist-sea dist-sea-build && mkdir -p dist-sea dist-sea-build && pnpm --filter @braintrust/bt-wizard-harness deploy --prod --legacy dist-sea-build/bt-wizard-harness && tar -czf dist-sea-build/harness.tgz -C dist-sea-build bt-wizard-harness && node --build-sea sea-config.json && mv crank dist-sea/crank && codesign --sign - dist-sea/crank", "start:beau": "pnpm --filter braintrust-wizard start:beau", "build:beau": "pnpm --filter braintrust-wizard build:beau", "typings": "pnpm -r run typings", diff --git a/packages/braintrust-wizard/package.json b/packages/braintrust-wizard/package.json index 91a0601..d1f6276 100644 --- a/packages/braintrust-wizard/package.json +++ b/packages/braintrust-wizard/package.json @@ -21,14 +21,15 @@ }, "dependencies": { "@braintrust/bt-wizard-harness": "workspace:*", - "@inquirer/search": "4.1.8", "@clack/prompts": "1.3.0", + "@inquirer/search": "4.1.8", "@tanstack/react-query": "5.100.9", "ignore": "^7.0.5", "ink": "7.0.2", "open": "^11.0.0", "react": "19.2.5", "react-devtools-core": "7.0.1", + "tar": "^7.5.15", "yargs": "^18.0.0" }, "devDependencies": { @@ -43,7 +44,7 @@ "eslint-plugin-react-hooks": "7.1.1", "globals": "17.6.0", "prettier": "3.8.3", - "rolldown": "1.0.0-rc.18", + "rolldown": "1.0.1", "typescript": "6.0.3", "typescript-eslint": "8.59.2", "vitest": "4.1.5" diff --git a/packages/braintrust-wizard/rolldown.config.mjs b/packages/braintrust-wizard/rolldown.config.mjs index ef8d49b..faf3770 100644 --- a/packages/braintrust-wizard/rolldown.config.mjs +++ b/packages/braintrust-wizard/rolldown.config.mjs @@ -5,7 +5,7 @@ export default defineConfig({ output: { banner: "#!/usr/bin/env node", codeSplitting: false, - file: "dist/cli.js", + file: "dist/cli.mjs", format: "esm", sourcemap: true, }, diff --git a/packages/braintrust-wizard/src/cli.ts b/packages/braintrust-wizard/src/cli.ts index e33b0df..f9001cb 100644 --- a/packages/braintrust-wizard/src/cli.ts +++ b/packages/braintrust-wizard/src/cli.ts @@ -1,4 +1,5 @@ import { spawnSync } from "node:child_process"; +import { createRequire } from "node:module"; import * as prompts from "@clack/prompts"; @@ -7,44 +8,61 @@ import { runClackWizard, WizardCancelledError, } from "./clack-wizard"; +import { + HARNESS_SENTINEL_ARG, + resolveHarnessBinPath, + resolveHarnessBootstrapPath, +} from "./instrument"; import { parseArgs } from "./options"; -const options = await parseArgs(process.argv.slice(2), process.env); +// SEA binaries always run the embedded main, so to launch the harness from a +// SEA we re-exec ourselves with this sentinel. The injected main's +// built-in-only import restriction does not apply to file:// URLs loaded via +// dynamic import, so the extracted harness .mjs (and its native node-pty +// dependency) resolves normally. +if (process.argv[2] === HARNESS_SENTINEL_ARG) { + const harnessBin = resolveHarnessBinPath(); + const bootstrap = resolveHarnessBootstrapPath(); + process.argv = [process.execPath, harnessBin, ...process.argv.slice(3)]; + createRequire(bootstrap)(bootstrap); +} else { + const options = await parseArgs(process.argv.slice(2), process.env); -// `NODE_EXTRA_CA_CERTS` is read once at Node startup, so we can't apply it -// in-process. If --ca-cert (or BRAINTRUST_CA_CERT / SSL_CERT_FILE) was set -// and the env var isn't already pointing at the same file, re-exec with it -// applied. The guard env var prevents an infinite re-exec loop. -const REEXEC_GUARD = "BT_WIZARD_REEXECED_FOR_CA"; -if ( - options.caCertPath && - process.env[REEXEC_GUARD] !== "1" && - process.env["NODE_EXTRA_CA_CERTS"] !== options.caCertPath -) { - const result = spawnSync(process.execPath, process.argv.slice(1), { - stdio: "inherit", - env: { - ...process.env, - NODE_EXTRA_CA_CERTS: options.caCertPath, - [REEXEC_GUARD]: "1", - }, - }); - process.exit(result.status ?? 1); -} + // `NODE_EXTRA_CA_CERTS` is read once at Node startup, so we can't apply it + // in-process. If --ca-cert (or BRAINTRUST_CA_CERT / SSL_CERT_FILE) was set + // and the env var isn't already pointing at the same file, re-exec with it + // applied. The guard env var prevents an infinite re-exec loop. + const REEXEC_GUARD = "BT_WIZARD_REEXECED_FOR_CA"; + if ( + options.caCertPath && + process.env[REEXEC_GUARD] !== "1" && + process.env["NODE_EXTRA_CA_CERTS"] !== options.caCertPath + ) { + const result = spawnSync(process.execPath, process.argv.slice(1), { + stdio: "inherit", + env: { + ...process.env, + NODE_EXTRA_CA_CERTS: options.caCertPath, + [REEXEC_GUARD]: "1", + }, + }); + process.exit(result.status ?? 1); + } -const deps = buildDefaultDeps({ - options, - prompts: prompts as unknown as Parameters< - typeof buildDefaultDeps - >[0]["prompts"], -}); + const deps = buildDefaultDeps({ + options, + prompts: prompts as unknown as Parameters< + typeof buildDefaultDeps + >[0]["prompts"], + }); -try { - await runClackWizard(deps); -} catch (error) { - if (error instanceof WizardCancelledError) { - process.exit(0); + try { + await runClackWizard(deps); + } catch (error) { + if (error instanceof WizardCancelledError) { + process.exit(0); + } + process.stderr.write(`${(error as Error).message}\n`); + process.exit(1); } - process.stderr.write(`${(error as Error).message}\n`); - process.exit(1); } diff --git a/packages/braintrust-wizard/src/instrument.ts b/packages/braintrust-wizard/src/instrument.ts index 4fe2ca8..7b1f027 100644 --- a/packages/braintrust-wizard/src/instrument.ts +++ b/packages/braintrust-wizard/src/instrument.ts @@ -1,22 +1,123 @@ import { spawn } from "node:child_process"; -import { mkdtempSync, readFileSync, writeFileSync } from "node:fs"; -import { tmpdir, platform } from "node:os"; +import { createHash } from "node:crypto"; +import { + existsSync, + mkdirSync, + mkdtempSync, + readFileSync, + renameSync, + rmSync, + writeFileSync, +} from "node:fs"; +import { homedir, tmpdir, platform } from "node:os"; import { join } from "node:path"; +import { getAsset, isSea } from "node:sea"; import { fileURLToPath } from "node:url"; +import * as tar from "tar"; + import type { DetectedLanguage } from "./language-detect"; -const HARNESS_BIN_PATH = fileURLToPath( - import.meta - .resolve("@braintrust/bt-wizard-harness/bin/bt-wizard-harness.mjs"), -); +function extractHarnessFromAsset(): string { + const asset = Buffer.from(getAsset("harness.tgz")); + const hash = createHash("sha256").update(asset).digest("hex").slice(0, 16); + const cacheRoot = join(homedir(), ".cache", "crank"); + const cacheDir = join(cacheRoot, hash); + const binPath = join( + cacheDir, + "bt-wizard-harness", + "bin", + "bt-wizard-harness.mjs", + ); + if (existsSync(binPath)) return binPath; + + mkdirSync(cacheRoot, { recursive: true }); + const staging = mkdtempSync(join(cacheRoot, `.tmp-${hash}-`)); + try { + // Cross-platform replacement for `tar -xzf`: the `tar` package's sync + // file-based extract works on Windows where no system `tar` is guaranteed. + const tgzPath = join(staging, "harness.tgz"); + writeFileSync(tgzPath, asset); + tar.x({ sync: true, file: tgzPath, cwd: staging, gzip: true }); + rmSync(tgzPath); + try { + renameSync(staging, cacheDir); + } catch (err) { + if (existsSync(binPath)) { + rmSync(staging, { recursive: true, force: true }); + return binPath; + } + throw err; + } + } catch (err) { + rmSync(staging, { recursive: true, force: true }); + throw err; + } + return binPath; +} + +export function resolveHarnessBinPath(): string { + const override = process.env.BT_WIZARD_HARNESS_BIN; + if (override) return override; + if (isSea()) { + return extractHarnessFromAsset(); + } + return fileURLToPath( + import.meta + .resolve("@braintrust/bt-wizard-harness/bin/bt-wizard-harness.mjs"), + ); +} + +/** + * The SEA injected main can only `import` built-in modules — dynamic `import()` + * of a file URL also hits that restriction. The documented escape hatch is + * `createRequire`, which loads CJS only, so we drop a tiny CJS shim next to + * the extracted harness whose only job is to dynamic-`import()` the .mjs + * (which works because the shim is a real file on disk, not the injected main). + */ +export function resolveHarnessBootstrapPath(): string { + const binPath = resolveHarnessBinPath(); + const bootstrapPath = binPath.replace(/\.mjs$/, ".bootstrap.cjs"); + if (existsSync(bootstrapPath)) return bootstrapPath; + const contents = + "const { pathToFileURL } = require('node:url');\n" + + "const { join, dirname } = require('node:path');\n" + + "const target = pathToFileURL(join(dirname(__filename), 'bt-wizard-harness.mjs')).href;\n" + + "import(target).catch((err) => { console.error(err); process.exit(1); });\n"; + writeFileSync(bootstrapPath, contents); + return bootstrapPath; +} + +/** + * Sentinel argv that the SEA main script dispatches on to launch the harness + * instead of the wizard. See cli.ts. + */ +export const HARNESS_SENTINEL_ARG = "__harness"; + +const HARNESS_BIN_PATH = resolveHarnessBinPath(); + +type HarnessLauncher = { + readonly command: string; + readonly leadingArgs: readonly string[]; +}; + +function harnessLauncher(): HarnessLauncher { + // In a SEA, `node` may not exist on PATH — re-exec ourselves with the + // sentinel and let cli.ts dispatch into the harness. + if (isSea()) { + return { command: process.execPath, leadingArgs: [HARNESS_SENTINEL_ARG] }; + } + return { command: "node", leadingArgs: [HARNESS_BIN_PATH] }; +} /** * Build the shell command a user can copy-paste to re-run the harness against * a saved prompt file. */ export function buildHarnessCommand(promptFilePath: string): string { - return `node ${JSON.stringify(HARNESS_BIN_PATH)} --prompt-file ${JSON.stringify(promptFilePath)}`; + const { command, leadingArgs } = harnessLauncher(); + const parts = [command, ...leadingArgs, "--prompt-file", promptFilePath]; + return parts.map((p) => JSON.stringify(p)).join(" "); } export type InstallBtResult = @@ -130,9 +231,10 @@ export async function runHarness(args: { // a missing file vs. an empty file are distinguishable. writeFileSync(args.resultFilePath, ""); return new Promise((resolve) => { + const { command, leadingArgs } = harnessLauncher(); const child = spawn( - "node", - [HARNESS_BIN_PATH, "--prompt-file", promptFile], + command, + [...leadingArgs, "--prompt-file", promptFile], { cwd: args.cwd, env: { diff --git a/packages/bt-wizard-harness/package.json b/packages/bt-wizard-harness/package.json index 0ed336c..37cdf52 100644 --- a/packages/bt-wizard-harness/package.json +++ b/packages/bt-wizard-harness/package.json @@ -15,7 +15,7 @@ "dependencies": { "@earendil-works/pi-coding-agent": "*", "@earendil-works/pi-tui": "*", - "node-pty": "^1.1.0", + "node-pty": "1.2.0-beta.13", "@sinclair/typebox": "^0.34.49" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c35a11c..10ebf15 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -41,6 +41,9 @@ importers: react-devtools-core: specifier: 7.0.1 version: 7.0.1 + tar: + specifier: ^7.5.15 + version: 7.5.15 yargs: specifier: ^18.0.0 version: 18.0.0 @@ -79,8 +82,8 @@ importers: specifier: 3.8.3 version: 3.8.3 rolldown: - specifier: 1.0.0-rc.18 - version: 1.0.0-rc.18 + specifier: 1.0.1 + version: 1.0.1 typescript: specifier: 6.0.3 version: 6.0.3 @@ -89,7 +92,7 @@ importers: version: 8.59.2(eslint@10.3.0(jiti@2.7.0))(typescript@6.0.3) vitest: specifier: 4.1.5 - version: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(vite@8.0.10(@types/node@24.12.2)(jiti@2.7.0)(yaml@2.9.0)) + version: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) packages/bt-wizard-harness: dependencies: @@ -103,8 +106,8 @@ importers: specifier: ^0.34.49 version: 0.34.49 node-pty: - specifier: ^1.1.0 - version: 1.1.0 + specifier: 1.2.0-beta.13 + version: 1.2.0-beta.13 devDependencies: '@eslint/js': specifier: 10.0.1 @@ -429,6 +432,162 @@ packages: '@emnapi/wasi-threads@1.2.1': resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + '@esbuild/aix-ppc64@0.27.7': + resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.27.7': + resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.27.7': + resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.27.7': + resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.27.7': + resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.7': + resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.27.7': + resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.7': + resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.27.7': + resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.27.7': + resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.27.7': + resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.27.7': + resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.27.7': + resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.27.7': + resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.7': + resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.27.7': + resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.27.7': + resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.27.7': + resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.7': + resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.27.7': + resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.7': + resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.7': + resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.27.7': + resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.27.7': + resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.27.7': + resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.27.7': + resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.9.1': resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -536,6 +695,10 @@ packages: '@types/node': optional: true + '@isaacs/fs-minipass@4.0.1': + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -664,8 +827,8 @@ packages: '@oxc-project/types@0.127.0': resolution: {integrity: sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==} - '@oxc-project/types@0.128.0': - resolution: {integrity: sha512-huv1Y/LzBJkBVHt3OlC7u0zHBW9qXf1FdD7sGmc1rXc2P1mTwHssYv7jyGx5KAACSCH+9B3Bhn6Z9luHRvf7pQ==} + '@oxc-project/types@0.130.0': + resolution: {integrity: sha512-ibD2usx9JRu7f5pu2tMKMI4cpA4NgXJQoYRP4pQ7Pxmn1l6k/53qWtQWZayhYy3X4QZkt90Ot+mJEaeXouio6Q==} '@protobufjs/aspromise@1.1.2': resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} @@ -703,8 +866,8 @@ packages: cpu: [arm64] os: [android] - '@rolldown/binding-android-arm64@1.0.0-rc.18': - resolution: {integrity: sha512-lIDyUAfD7U3+BWKzdxMbJcsYHuqXqmGz40aeRqvuAm3y5TkJSYTBW2RDrn65DJFPQqVjUAUqq5uz8urzQ8aBdQ==} + '@rolldown/binding-android-arm64@1.0.1': + resolution: {integrity: sha512-fJI3I0r3C3Oj/zdBCpaCmBRZYf07xpaq4yCfDDoSFm+beWNzbIl26puW8RraUdugoJw/95zerNOn6jasAhzSmg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] @@ -715,8 +878,8 @@ packages: cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-arm64@1.0.0-rc.18': - resolution: {integrity: sha512-apJq2ktnGp27nSInMR5Vcj8kY6xJzDAvfdIFlpDcAK/w4cDO58qVoi1YQsES/SKiFNge/6e4CUzgjfHduYqWpQ==} + '@rolldown/binding-darwin-arm64@1.0.1': + resolution: {integrity: sha512-cKnAhWEsV7TPcA/5EAteDp6KcJZBQ2G+BqE7zayMMi7kMvwRsbv7WT9aOnn0WNl4SKEIf43vjS31iUPu80nzXg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] @@ -727,8 +890,8 @@ packages: cpu: [x64] os: [darwin] - '@rolldown/binding-darwin-x64@1.0.0-rc.18': - resolution: {integrity: sha512-5Ofot8xbs+pxRHJqm9/9N/4sTQOvdrwEsmPE9pdLEEoAbdZtG6F2LMDfO1sp6ZAtXJuJV/21ew2srq3W8NXB5g==} + '@rolldown/binding-darwin-x64@1.0.1': + resolution: {integrity: sha512-YKrVwQjIRBPo+5G/u03wGjbdy4q7pyzCe93DK9VJ7zkVmeg8LJ7GbgsiHWdR4xSoe4CAXRD7Bcjgbtr64bkXNg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] @@ -739,8 +902,8 @@ packages: cpu: [x64] os: [freebsd] - '@rolldown/binding-freebsd-x64@1.0.0-rc.18': - resolution: {integrity: sha512-7h8eeOTT1eyqJyx64BFCnWZpNm486hGWt2sqeLLgDxA0xI1oGZ9H7gK1S85uNGmBhkdPwa/6reTxfFFKvIsebw==} + '@rolldown/binding-freebsd-x64@1.0.1': + resolution: {integrity: sha512-z/oBsREo46SsFqBwYtFe0kpJeBijAT48O/WXLI4suiCLBkr03RTtTJMCzSdDd2znlh8VJizL09XVkQgk8IZonw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] @@ -751,8 +914,8 @@ packages: cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.18': - resolution: {integrity: sha512-eRcm/HVt9U/JFu5RKAEKwGQYtDCKWLiaH6wOnsSEp6NMBb/3Os8LgHZlNyzMpFVNmiiMFlfb2zEnebfzJrHFmg==} + '@rolldown/binding-linux-arm-gnueabihf@1.0.1': + resolution: {integrity: sha512-ik8q7GM11zxvYxFc2PeDcT6TBvhCQMaUxfph/M5l9sKuTs/Sjg3L+Byw0F7w0ZVLBZmx30P+gG0ECzzN+MFcmQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] @@ -764,8 +927,8 @@ packages: os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.18': - resolution: {integrity: sha512-SOrT/cT4ukTmgnrEz/Hg3m7LBnuCLW9psDeMKrimRWY4I8DmnO7Lco8W2vtqPmMkbVu8iJ+g4GFLVLLOVjJ9DQ==} + '@rolldown/binding-linux-arm64-gnu@1.0.1': + resolution: {integrity: sha512-QoSx2EkyrrdZ6kcyE8stqZ62t0Yra8Fs5ia9lOxJrh6TMQJK7gQKmscdTHf7pOXKREKrVwOtJcQG3qVSfc866A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] @@ -778,8 +941,8 @@ packages: os: [linux] libc: [musl] - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.18': - resolution: {integrity: sha512-QWjdxN1HJCpBTAcZ5N5F7wju3gVPzRzSpmGzx7na0c/1qpN9CFil+xt+l9lV/1M6/gqHSNXCiqPfwhVJPeLnug==} + '@rolldown/binding-linux-arm64-musl@1.0.1': + resolution: {integrity: sha512-uwNwFpwKeNiZawfAWBgg0VIztPTV3ihhh1vV334h9ivnNLorxnQMU6Fz8wG1Zb4Qh9LC1/MkcyT3YlDXG3Rsgg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] @@ -792,8 +955,8 @@ packages: os: [linux] libc: [glibc] - '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.18': - resolution: {integrity: sha512-ugCOyj7a4d9h3q9B+wXmf6g3a68UsjGh6dob5DHevHGMwDUbhsYNbSPxJsENcIttJZ9jv7qGM2UesLw5jqIhdg==} + '@rolldown/binding-linux-ppc64-gnu@1.0.1': + resolution: {integrity: sha512-zY1bul7OWr7DFBiJ++wofXvnr8B45ce3QsQUhKrIhXsygAh7bTkwyeM1bi1a2g5C/yC/N8TZyGDEoMfm/l9mpg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] @@ -806,8 +969,8 @@ packages: os: [linux] libc: [glibc] - '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.18': - resolution: {integrity: sha512-kKWRhbsotpXkGbcd5dllUWg5gEXcDAa8u5YnP9AV5DYNbvJHGzzuwv7dpmhc8NqKMJldl0a+x76IHbspEpEmdA==} + '@rolldown/binding-linux-s390x-gnu@1.0.1': + resolution: {integrity: sha512-0frlsT/f4Ft6I7SMESTKnF3cZsdicQn1dCMkF/jT9wDLE+gGoiQfv1nmT9e+s7s/fekvvy6tZM2jHvI2tkbJDQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] @@ -820,8 +983,8 @@ packages: os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.18': - resolution: {integrity: sha512-uCo8ElcCIAMyYAZyuIZ81oFkhTSIllNvUCHCAlbhlN4ji3uC28h7IIdlXyIvGO7HsuqnV9p3rD/bpH7XhIyhRw==} + '@rolldown/binding-linux-x64-gnu@1.0.1': + resolution: {integrity: sha512-XABVmGp9Tg0WspTVvwduTc4fpqy6JnAUrSQe6OuyqD/03nI7r0O9OWUkMIwFrjKAIqolvqoA4ZrJppgwE0Gxmw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] @@ -834,8 +997,8 @@ packages: os: [linux] libc: [musl] - '@rolldown/binding-linux-x64-musl@1.0.0-rc.18': - resolution: {integrity: sha512-XNOQZtuE6yUIvx4rwGemwh8kpL1xvU41FXy/s9K7T/3JVcqGzo3NfKM2HrbrGgfPYGFW42f07Wk++aOC6B9NWA==} + '@rolldown/binding-linux-x64-musl@1.0.1': + resolution: {integrity: sha512-bV4fzswuzVcKD90o/VM6QqKxnxlDq0g2BISDLNVmxrnhpv1DDbyPhCIjYfvzYLV+MvkKKnQt2Q6AO86SEBULUQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] @@ -847,8 +1010,8 @@ packages: cpu: [arm64] os: [openharmony] - '@rolldown/binding-openharmony-arm64@1.0.0-rc.18': - resolution: {integrity: sha512-tSn/kzrfa7tNOXr7sEacDBN4YsIqTyLqh45IO0nHDwtpKIDNDJr+VFojt+4klSpChxB29JLyduSsE0MKEwa65A==} + '@rolldown/binding-openharmony-arm64@1.0.1': + resolution: {integrity: sha512-/Mh0Zhq3OP7fVs0kcQHZP6lZEthMGTaSf8UBQYSFEZDWGXXlEC+nJ6EqenaK2t4LBXMe3A+K/G2BVXXdtOr4PQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] @@ -858,8 +1021,8 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [wasm32] - '@rolldown/binding-wasm32-wasi@1.0.0-rc.18': - resolution: {integrity: sha512-+J9YGmc+czgqlhYmwun3S3O0FIZhsH8ep2456xwjAdIOmuJxM7xz4P4PtrxU+Bz17a/5bqPA8o3HAAoX0teUdg==} + '@rolldown/binding-wasm32-wasi@1.0.1': + resolution: {integrity: sha512-+1xc9X45l8ufsBAm6Gjvx2qDRIY9lTVt0cgWNcJ+1gdhXvkbxePA60yRTwSTuXL09CMhyJmjpV7E3NoyxbqFQQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [wasm32] @@ -869,8 +1032,8 @@ packages: cpu: [arm64] os: [win32] - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.18': - resolution: {integrity: sha512-zsu47DgU0FQzSwi6sU9dZoEdUv7pc1AptSEz/Z8HBg54sV0Pbs3N0+CrIbTsgiu6EyoaNN9CHboqbLaz9lhOyQ==} + '@rolldown/binding-win32-arm64-msvc@1.0.1': + resolution: {integrity: sha512-1D+UqZdfnuR+Jy1GgMJwi85bD40H21uNmOPRWQhw4oRSuolZ/B5rixZ45DK2KXOTCvmVCecauWgEhbw8bI7tOw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] @@ -881,8 +1044,8 @@ packages: cpu: [x64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.18': - resolution: {integrity: sha512-7H+3yqGgmnlDTRRhw/xpYY9J1kf4GC681nVc4GqKhExZTDrVVrV2tsOR9kso0fvgBdcTCcQShx4SLLoHgaLwhg==} + '@rolldown/binding-win32-x64-msvc@1.0.1': + resolution: {integrity: sha512-INAycaWuhlOK3wk4mRHGsdgwYWmd9cChdPdE9bwWmy6rn9VqVNYNFGhOdXrofXUxwHIncSiPNb8tNm8knDVIeQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -890,8 +1053,8 @@ packages: '@rolldown/pluginutils@1.0.0-rc.17': resolution: {integrity: sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==} - '@rolldown/pluginutils@1.0.0-rc.18': - resolution: {integrity: sha512-CUY5Mnhe64xQBGZEEXQ5WyZwsc1JU3vAZLIxtrsBt3LO6UOb+C8GunVKqe9sT8NeWb4lqSaoJtp2xo6GxT1MNw==} + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} '@silvia-odwyer/photon-node@0.3.4': resolution: {integrity: sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA==} @@ -1447,6 +1610,10 @@ packages: resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + chownr@3.0.0: + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} + cli-boxes@4.0.1: resolution: {integrity: sha512-5IOn+jcCEHEraYolBPs/sT4BxYCe2nHg374OPiItB1O96KZFseS2gthU4twyYzeDcFew4DaUM/xwc5BQf08JJw==} engines: {node: '>=18.20 <19 || >=20.10'} @@ -1662,6 +1829,11 @@ packages: es-toolkit@1.46.1: resolution: {integrity: sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ==} + esbuild@0.27.7: + resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -2428,6 +2600,10 @@ packages: resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} engines: {node: '>=16 || 14 >=14.17'} + minizlib@3.1.0: + resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} + engines: {node: '>= 18'} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -2480,8 +2656,8 @@ packages: resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - node-pty@1.1.0: - resolution: {integrity: sha512-20JqtutY6JPXTUnL0ij1uad7Qe1baT46lyolh2sSENDd4sTzKZ4nmAFkeAARDKwmlLjPx6XKRlwRUxwjOy+lUg==} + node-pty@1.2.0-beta.13: + resolution: {integrity: sha512-ZbbJ7aJdmvRA53bw30D6YSJJKqo1IXTojD0kJeHZ/xZIxr7p1DCmvOmrOnjUo/rn1z4MDwKQGpx0C7K+cRKETw==} node-releases@2.0.38: resolution: {integrity: sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==} @@ -2729,8 +2905,8 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - rolldown@1.0.0-rc.18: - resolution: {integrity: sha512-phmyKBpuBdRYDf4hgyynGAYn/rDDe+iZXKVJ7WX5b1zQzpLkP5oJRPGsfJuHdzPMlyyEO/4sPW6yfSx2gf7lVg==} + rolldown@1.0.1: + resolution: {integrity: sha512-X0KQHljNnEkWNqqiz9zJrGunh1B0HgOxLXvnFpCOcadzcy5qohZ3tqMEUg00vncoRovXuK3ZqCT9KnnKzoInFQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -2960,6 +3136,10 @@ packages: resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} engines: {node: '>=20'} + tar@7.5.15: + resolution: {integrity: sha512-dzGK0boVlC4W5QFuQN1EFSl3bIDYsk7Tj40U6eIBnK2k/8ml7TZ5agbI5j5+qnoVcAA+rNtBml8SEiLxZpNqRQ==} + engines: {node: '>=18'} + terminal-size@4.0.1: resolution: {integrity: sha512-avMLDQpUI9I5XFrklECw1ZEUPJhqzcwSWsyyI8blhRLT+8N1jLJWLWWYQpB2q2xthq8xDvjZPISVh53T/+CLYQ==} engines: {node: '>=18'} @@ -3279,6 +3459,10 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yallist@5.0.0: + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} + yaml@2.9.0: resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} engines: {node: '>= 14.6'} @@ -4042,6 +4226,84 @@ snapshots: tslib: 2.8.1 optional: true + '@esbuild/aix-ppc64@0.27.7': + optional: true + + '@esbuild/android-arm64@0.27.7': + optional: true + + '@esbuild/android-arm@0.27.7': + optional: true + + '@esbuild/android-x64@0.27.7': + optional: true + + '@esbuild/darwin-arm64@0.27.7': + optional: true + + '@esbuild/darwin-x64@0.27.7': + optional: true + + '@esbuild/freebsd-arm64@0.27.7': + optional: true + + '@esbuild/freebsd-x64@0.27.7': + optional: true + + '@esbuild/linux-arm64@0.27.7': + optional: true + + '@esbuild/linux-arm@0.27.7': + optional: true + + '@esbuild/linux-ia32@0.27.7': + optional: true + + '@esbuild/linux-loong64@0.27.7': + optional: true + + '@esbuild/linux-mips64el@0.27.7': + optional: true + + '@esbuild/linux-ppc64@0.27.7': + optional: true + + '@esbuild/linux-riscv64@0.27.7': + optional: true + + '@esbuild/linux-s390x@0.27.7': + optional: true + + '@esbuild/linux-x64@0.27.7': + optional: true + + '@esbuild/netbsd-arm64@0.27.7': + optional: true + + '@esbuild/netbsd-x64@0.27.7': + optional: true + + '@esbuild/openbsd-arm64@0.27.7': + optional: true + + '@esbuild/openbsd-x64@0.27.7': + optional: true + + '@esbuild/openharmony-arm64@0.27.7': + optional: true + + '@esbuild/sunos-x64@0.27.7': + optional: true + + '@esbuild/win32-arm64@0.27.7': + optional: true + + '@esbuild/win32-ia32@0.27.7': + optional: true + + '@esbuild/win32-x64@0.27.7': + optional: true + '@eslint-community/eslint-utils@4.9.1(eslint@10.3.0(jiti@2.7.0))': dependencies: eslint: 10.3.0(jiti@2.7.0) @@ -4143,6 +4405,10 @@ snapshots: optionalDependencies: '@types/node': 24.12.2 + '@isaacs/fs-minipass@4.0.1': + dependencies: + minipass: 7.1.3 + '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -4249,7 +4515,7 @@ snapshots: '@oxc-project/types@0.127.0': {} - '@oxc-project/types@0.128.0': {} + '@oxc-project/types@0.130.0': {} '@protobufjs/aspromise@1.1.2': {} @@ -4277,73 +4543,73 @@ snapshots: '@rolldown/binding-android-arm64@1.0.0-rc.17': optional: true - '@rolldown/binding-android-arm64@1.0.0-rc.18': + '@rolldown/binding-android-arm64@1.0.1': optional: true '@rolldown/binding-darwin-arm64@1.0.0-rc.17': optional: true - '@rolldown/binding-darwin-arm64@1.0.0-rc.18': + '@rolldown/binding-darwin-arm64@1.0.1': optional: true '@rolldown/binding-darwin-x64@1.0.0-rc.17': optional: true - '@rolldown/binding-darwin-x64@1.0.0-rc.18': + '@rolldown/binding-darwin-x64@1.0.1': optional: true '@rolldown/binding-freebsd-x64@1.0.0-rc.17': optional: true - '@rolldown/binding-freebsd-x64@1.0.0-rc.18': + '@rolldown/binding-freebsd-x64@1.0.1': optional: true '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.18': + '@rolldown/binding-linux-arm-gnueabihf@1.0.1': optional: true '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17': optional: true - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.18': + '@rolldown/binding-linux-arm64-gnu@1.0.1': optional: true '@rolldown/binding-linux-arm64-musl@1.0.0-rc.17': optional: true - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.18': + '@rolldown/binding-linux-arm64-musl@1.0.1': optional: true '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.18': + '@rolldown/binding-linux-ppc64-gnu@1.0.1': optional: true '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17': optional: true - '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.18': + '@rolldown/binding-linux-s390x-gnu@1.0.1': optional: true '@rolldown/binding-linux-x64-gnu@1.0.0-rc.17': optional: true - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.18': + '@rolldown/binding-linux-x64-gnu@1.0.1': optional: true '@rolldown/binding-linux-x64-musl@1.0.0-rc.17': optional: true - '@rolldown/binding-linux-x64-musl@1.0.0-rc.18': + '@rolldown/binding-linux-x64-musl@1.0.1': optional: true '@rolldown/binding-openharmony-arm64@1.0.0-rc.17': optional: true - '@rolldown/binding-openharmony-arm64@1.0.0-rc.18': + '@rolldown/binding-openharmony-arm64@1.0.1': optional: true '@rolldown/binding-wasm32-wasi@1.0.0-rc.17': @@ -4353,7 +4619,7 @@ snapshots: '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) optional: true - '@rolldown/binding-wasm32-wasi@1.0.0-rc.18': + '@rolldown/binding-wasm32-wasi@1.0.1': dependencies: '@emnapi/core': 1.10.0 '@emnapi/runtime': 1.10.0 @@ -4363,18 +4629,18 @@ snapshots: '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17': optional: true - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.18': + '@rolldown/binding-win32-arm64-msvc@1.0.1': optional: true '@rolldown/binding-win32-x64-msvc@1.0.0-rc.17': optional: true - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.18': + '@rolldown/binding-win32-x64-msvc@1.0.1': optional: true '@rolldown/pluginutils@1.0.0-rc.17': {} - '@rolldown/pluginutils@1.0.0-rc.18': {} + '@rolldown/pluginutils@1.0.1': {} '@silvia-odwyer/photon-node@0.3.4': {} @@ -4754,13 +5020,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.5(vite@8.0.10(@types/node@24.12.2)(jiti@2.7.0)(yaml@2.9.0))': + '@vitest/mocker@4.1.5(vite@8.0.10(@types/node@24.12.2)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0))': dependencies: '@vitest/spy': 4.1.5 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.0.10(@types/node@24.12.2)(jiti@2.7.0)(yaml@2.9.0) + vite: 8.0.10(@types/node@24.12.2)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0) '@vitest/pretty-format@4.1.5': dependencies: @@ -5042,6 +5308,8 @@ snapshots: chalk@5.6.2: {} + chownr@3.0.0: {} + cli-boxes@4.0.1: {} cli-cursor@4.0.0: @@ -5319,6 +5587,36 @@ snapshots: es-toolkit@1.46.1: {} + esbuild@0.27.7: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.7 + '@esbuild/android-arm': 0.27.7 + '@esbuild/android-arm64': 0.27.7 + '@esbuild/android-x64': 0.27.7 + '@esbuild/darwin-arm64': 0.27.7 + '@esbuild/darwin-x64': 0.27.7 + '@esbuild/freebsd-arm64': 0.27.7 + '@esbuild/freebsd-x64': 0.27.7 + '@esbuild/linux-arm': 0.27.7 + '@esbuild/linux-arm64': 0.27.7 + '@esbuild/linux-ia32': 0.27.7 + '@esbuild/linux-loong64': 0.27.7 + '@esbuild/linux-mips64el': 0.27.7 + '@esbuild/linux-ppc64': 0.27.7 + '@esbuild/linux-riscv64': 0.27.7 + '@esbuild/linux-s390x': 0.27.7 + '@esbuild/linux-x64': 0.27.7 + '@esbuild/netbsd-arm64': 0.27.7 + '@esbuild/netbsd-x64': 0.27.7 + '@esbuild/openbsd-arm64': 0.27.7 + '@esbuild/openbsd-x64': 0.27.7 + '@esbuild/openharmony-arm64': 0.27.7 + '@esbuild/sunos-x64': 0.27.7 + '@esbuild/win32-arm64': 0.27.7 + '@esbuild/win32-ia32': 0.27.7 + '@esbuild/win32-x64': 0.27.7 + optional: true + escalade@3.2.0: {} escape-string-regexp@2.0.0: {} @@ -6144,6 +6442,10 @@ snapshots: minipass@7.1.3: {} + minizlib@3.1.0: + dependencies: + minipass: 7.1.3 + ms@2.1.3: {} mute-stream@3.0.0: {} @@ -6183,7 +6485,7 @@ snapshots: fetch-blob: 3.2.0 formdata-polyfill: 4.0.10 - node-pty@1.1.0: + node-pty@1.2.0-beta.13: dependencies: node-addon-api: 7.1.1 @@ -6494,26 +6796,26 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.17 '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.17 - rolldown@1.0.0-rc.18: + rolldown@1.0.1: dependencies: - '@oxc-project/types': 0.128.0 - '@rolldown/pluginutils': 1.0.0-rc.18 + '@oxc-project/types': 0.130.0 + '@rolldown/pluginutils': 1.0.1 optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.0-rc.18 - '@rolldown/binding-darwin-arm64': 1.0.0-rc.18 - '@rolldown/binding-darwin-x64': 1.0.0-rc.18 - '@rolldown/binding-freebsd-x64': 1.0.0-rc.18 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.18 - '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.18 - '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.18 - '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.18 - '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.18 - '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.18 - '@rolldown/binding-linux-x64-musl': 1.0.0-rc.18 - '@rolldown/binding-openharmony-arm64': 1.0.0-rc.18 - '@rolldown/binding-wasm32-wasi': 1.0.0-rc.18 - '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.18 - '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.18 + '@rolldown/binding-android-arm64': 1.0.1 + '@rolldown/binding-darwin-arm64': 1.0.1 + '@rolldown/binding-darwin-x64': 1.0.1 + '@rolldown/binding-freebsd-x64': 1.0.1 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.1 + '@rolldown/binding-linux-arm64-gnu': 1.0.1 + '@rolldown/binding-linux-arm64-musl': 1.0.1 + '@rolldown/binding-linux-ppc64-gnu': 1.0.1 + '@rolldown/binding-linux-s390x-gnu': 1.0.1 + '@rolldown/binding-linux-x64-gnu': 1.0.1 + '@rolldown/binding-linux-x64-musl': 1.0.1 + '@rolldown/binding-openharmony-arm64': 1.0.1 + '@rolldown/binding-wasm32-wasi': 1.0.1 + '@rolldown/binding-win32-arm64-msvc': 1.0.1 + '@rolldown/binding-win32-x64-msvc': 1.0.1 run-applescript@7.1.0: {} @@ -6785,6 +7087,14 @@ snapshots: tagged-tag@1.0.0: {} + tar@7.5.15: + dependencies: + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.3 + minizlib: 3.1.0 + yallist: 5.0.0 + terminal-size@4.0.1: {} thenify-all@1.6.0: @@ -6911,7 +7221,7 @@ snapshots: uuid@9.0.1: {} - vite@8.0.10(@types/node@24.12.2)(jiti@2.7.0)(yaml@2.9.0): + vite@8.0.10(@types/node@24.12.2)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 @@ -6920,14 +7230,15 @@ snapshots: tinyglobby: 0.2.16 optionalDependencies: '@types/node': 24.12.2 + esbuild: 0.27.7 fsevents: 2.3.3 jiti: 2.7.0 yaml: 2.9.0 - vitest@4.1.5(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(vite@8.0.10(@types/node@24.12.2)(jiti@2.7.0)(yaml@2.9.0)): + vitest@4.1.5(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.5 - '@vitest/mocker': 4.1.5(vite@8.0.10(@types/node@24.12.2)(jiti@2.7.0)(yaml@2.9.0)) + '@vitest/mocker': 4.1.5(vite@8.0.10(@types/node@24.12.2)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) '@vitest/pretty-format': 4.1.5 '@vitest/runner': 4.1.5 '@vitest/snapshot': 4.1.5 @@ -6944,7 +7255,7 @@ snapshots: tinyexec: 1.1.2 tinyglobby: 0.2.16 tinyrainbow: 3.1.0 - vite: 8.0.10(@types/node@24.12.2)(jiti@2.7.0)(yaml@2.9.0) + vite: 8.0.10(@types/node@24.12.2)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.9.1 @@ -7064,6 +7375,8 @@ snapshots: yallist@3.1.1: {} + yallist@5.0.0: {} + yaml@2.9.0: {} yargs-parser@20.2.9: {} diff --git a/scripts/build-sea.mjs b/scripts/build-sea.mjs new file mode 100644 index 0000000..24a1fa8 --- /dev/null +++ b/scripts/build-sea.mjs @@ -0,0 +1,57 @@ +#!/usr/bin/env node +import { execFileSync, spawnSync } from "node:child_process"; +import { mkdirSync, renameSync, rmSync } from "node:fs"; +import { platform } from "node:os"; + +const isWindows = platform() === "win32"; +const isMac = platform() === "darwin"; + +const run = (cmd, args, opts = {}) => { + const result = spawnSync(cmd, args, { + stdio: "inherit", + // Windows: `pnpm` is a `.cmd` shim — CreateProcess can't exec .cmd + // directly, so we need the shell to resolve it. + shell: isWindows, + ...opts, + }); + if (result.status !== 0) { + throw new Error( + `${cmd} ${args.join(" ")} failed with status ${result.status}`, + ); + } +}; + +// `--build-sea` writes to the literal `output` field from sea-config.json +// (always "crank"), even on Windows. We add `.exe` only at the final location. +const sourceBinName = "crank"; +const destBinName = isWindows ? "crank.exe" : "crank"; + +rmSync("dist-sea", { recursive: true, force: true }); +rmSync("dist-sea-build", { recursive: true, force: true }); +mkdirSync("dist-sea", { recursive: true }); +mkdirSync("dist-sea-build", { recursive: true }); + +run("pnpm", [ + "--filter", + "@braintrust/bt-wizard-harness", + "deploy", + "--prod", + "--legacy", + "dist-sea-build/bt-wizard-harness", +]); + +run("tar", [ + "-czf", + "dist-sea-build/harness.tgz", + "-C", + "dist-sea-build", + "bt-wizard-harness", +]); + +run(process.execPath, ["--build-sea", "sea-config.json"]); + +renameSync(sourceBinName, `dist-sea/${destBinName}`); + +if (isMac) { + run("codesign", ["--sign", "-", `dist-sea/${destBinName}`]); +} diff --git a/sea-config.json b/sea-config.json new file mode 100644 index 0000000..efa1732 --- /dev/null +++ b/sea-config.json @@ -0,0 +1,9 @@ +{ + "main": "packages/braintrust-wizard/dist/cli.mjs", + "output": "crank", + "mainFormat": "module", + "disableExperimentalSEAWarning": true, + "assets": { + "harness.tgz": "dist-sea-build/harness.tgz" + } +} From d4cf2a21dfc058c760ec1cce22eeb1bf89d6a670 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Fri, 15 May 2026 17:18:05 -0700 Subject: [PATCH 2/3] feat: github action to build the artifacts --- .github/actions/setup/action.yml | 21 +++++++ .github/workflows/ci.yml | 104 +++++++++++++------------------ .gitignore | 2 +- package.json | 2 +- 4 files changed, 67 insertions(+), 62 deletions(-) create mode 100644 .github/actions/setup/action.yml diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml new file mode 100644 index 0000000..8bf8d03 --- /dev/null +++ b/.github/actions/setup/action.yml @@ -0,0 +1,21 @@ +name: Setup +description: Checkout, install pnpm + Node, and install dependencies. +runs: + using: composite + steps: + - name: Set up pnpm + # pnpm/action-setup@v4.4.0 + uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 + with: + version: 10.33.3 + + - name: Set up Node.js + # actions/setup-node@v6.4.0 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e + with: + node-version: 26.1.0 + cache: pnpm + + - name: Install dependencies + shell: bash + run: pnpm install --frozen-lockfile diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7e293ea..26bc08f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,21 +22,7 @@ jobs: # actions/checkout@v6.0.2 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - - name: Set up pnpm - # pnpm/action-setup@v4.4.0 - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 - with: - version: 10.33.3 - - - name: Set up Node.js - # actions/setup-node@v6.4.0 - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e - with: - node-version: 24.15.0 - cache: pnpm - - - name: Install dependencies - run: pnpm install --frozen-lockfile + - uses: ./.github/actions/setup - name: Build default CLI run: pnpm build @@ -44,29 +30,55 @@ jobs: - name: Build beau CLI run: pnpm build:beau - test: - name: Test - runs-on: ubuntu-24.04 + build-binary: + name: Build-binary (${{ matrix.artifact }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-24.04 + artifact: crank-linux-x64 + path: dist-sea/crank + - os: ubuntu-24.04-arm + artifact: crank-linux-arm64 + path: dist-sea/crank + - os: macos-26 + artifact: crank-darwin-arm64 + path: dist-sea/crank + - os: macos-15-intel + artifact: crank-darwin-x64 + path: dist-sea/crank + - os: windows-2022 + artifact: crank-win-x64 + path: dist-sea/crank.exe steps: - name: Checkout # actions/checkout@v6.0.2 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - - name: Set up pnpm - # pnpm/action-setup@v4.4.0 - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 - with: - version: 10.33.3 + - uses: ./.github/actions/setup + + - name: Build SEA binary + run: pnpm build:sea - - name: Set up Node.js - # actions/setup-node@v6.4.0 - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e + - name: Upload SEA binary + # actions/upload-artifact@v4.4.3 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 with: - node-version: 24.15.0 - cache: pnpm + name: ${{ matrix.artifact }} + path: ${{ matrix.path }} + if-no-files-found: error - - name: Install dependencies - run: pnpm install --frozen-lockfile + test: + name: Test + runs-on: ubuntu-24.04 + steps: + - name: Checkout + # actions/checkout@v6.0.2 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + + - uses: ./.github/actions/setup - name: Run tests run: pnpm test @@ -79,21 +91,7 @@ jobs: # actions/checkout@v6.0.2 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - - name: Set up pnpm - # pnpm/action-setup@v4.4.0 - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 - with: - version: 10.33.3 - - - name: Set up Node.js - # actions/setup-node@v6.4.0 - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e - with: - node-version: 24.15.0 - cache: pnpm - - - name: Install dependencies - run: pnpm install --frozen-lockfile + - uses: ./.github/actions/setup - name: Run ESLint run: pnpm lint @@ -106,21 +104,7 @@ jobs: # actions/checkout@v6.0.2 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - - name: Set up pnpm - # pnpm/action-setup@v4.4.0 - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 - with: - version: 10.33.3 - - - name: Set up Node.js - # actions/setup-node@v6.4.0 - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e - with: - node-version: 24.15.0 - cache: pnpm - - - name: Install dependencies - run: pnpm install --frozen-lockfile + - uses: ./.github/actions/setup - name: Check formatting run: pnpm format:check diff --git a/.gitignore b/.gitignore index 2857239..3152ee2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ node_modules/ -dist/ +dist* coverage/ .DS_Store .env diff --git a/package.json b/package.json index b0ff009..9c91960 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "scripts": { "start": "pnpm --filter braintrust-wizard start", "build": "pnpm --filter braintrust-wizard build", - "build:sea": "pnpm build && rm -rf dist-sea dist-sea-build && mkdir -p dist-sea dist-sea-build && pnpm --filter @braintrust/bt-wizard-harness deploy --prod --legacy dist-sea-build/bt-wizard-harness && tar -czf dist-sea-build/harness.tgz -C dist-sea-build bt-wizard-harness && node --build-sea sea-config.json && mv crank dist-sea/crank && codesign --sign - dist-sea/crank", + "build:sea": "pnpm build && node scripts/build-sea.mjs", "start:beau": "pnpm --filter braintrust-wizard start:beau", "build:beau": "pnpm --filter braintrust-wizard build:beau", "typings": "pnpm -r run typings", From 47f629f2fdab27e494db55b8b8a0aee99f2d2ef7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Halber?= Date: Fri, 15 May 2026 18:54:51 -0700 Subject: [PATCH 3/3] feat: adapt wizard to wizard-session backend Endpoints moved from /api/cli/wizard-signin/* to /api/cli/wizard-session/*; create now returns opaque session/poll JWTs and the complete payload is flat (org_id, org_name, project_id, project_name). Login URL is constructed client-side until the backend returns it. --- packages/braintrust-wizard/package.json | 1 + packages/braintrust-wizard/src/auth.ts | 170 ++++++------------ .../braintrust-wizard/src/clack-wizard.ts | 20 ++- .../test/clack-wizard.test.ts | 37 ++-- pnpm-lock.yaml | 3 + 5 files changed, 80 insertions(+), 151 deletions(-) diff --git a/packages/braintrust-wizard/package.json b/packages/braintrust-wizard/package.json index d1f6276..9a3506b 100644 --- a/packages/braintrust-wizard/package.json +++ b/packages/braintrust-wizard/package.json @@ -27,6 +27,7 @@ "ignore": "^7.0.5", "ink": "7.0.2", "open": "^11.0.0", + "picocolors": "1.1.1", "react": "19.2.5", "react-devtools-core": "7.0.1", "tar": "^7.5.15", diff --git a/packages/braintrust-wizard/src/auth.ts b/packages/braintrust-wizard/src/auth.ts index f017e21..2a4fa58 100644 --- a/packages/braintrust-wizard/src/auth.ts +++ b/packages/braintrust-wizard/src/auth.ts @@ -1,35 +1,19 @@ -export type WizardSigninCreateResponse = { - readonly id: string; +export type WizardSessionCreateResponse = { + readonly session_token: string; readonly poll_token: string; - readonly login_path: string; - readonly login_url: string; readonly expires_at: string; + readonly login_path: string; }; -export type WizardSigninOrgInfo = { - readonly id: string; - readonly name: string; - readonly api_url?: string | null; - readonly proxy_url?: string | null; - readonly realtime_url?: string | null; - readonly is_universal_api?: boolean | null; - readonly git_metadata?: unknown; -}; - -export type WizardSigninProject = { - readonly id: string; - readonly name: string; - readonly org_id: string; - readonly description?: string | null; -}; - -export type WizardSigninCompleteResult = { +export type WizardSessionCompleteResult = { readonly apiKey: string; - readonly orgInfo: WizardSigninOrgInfo; - readonly project: WizardSigninProject; + readonly orgId: string; + readonly orgName: string; + readonly projectId: string; + readonly projectName: string; }; -export type WizardSigninEvents = { +export type WizardSessionEvents = { readonly onLoginUrl: (info: { readonly loginUrl: string; readonly expiresAt: string; @@ -44,55 +28,40 @@ const POLL_HARD_TIMEOUT_MS = 3 * 60 * 1000; const CREATE_REQUEST_TIMEOUT_MS = 15_000; const POLL_REQUEST_TIMEOUT_MS = 30_000; -/** - * Browser-mediated wizard sign-in. - * - * Endpoints (added by the braintrust-wizard-login-flow PR): - * POST {appUrl}/api/cli/wizard-signin/create - * GET {appUrl}/api/cli/wizard-signin/poll?id=... - * (Authorization: Bearer ) - * - * The poll response is one of: - * { status: "pending", expires_at } - * { status: "expired" } - * { status: "claimed" } - * { status: "complete", api_key, org_info, project } - */ -export class WizardSigninAuthClient { - constructor( - private readonly appUrl: string, - private readonly clientName: string = "crank", - ) {} +export class WizardSessionAuthClient { + constructor(private readonly appUrl: string) {} - async createSession(): Promise { - const res = await fetch(`${this.appUrl}/api/cli/wizard-signin/create`, { + async createSession(): Promise { + const res = await fetch(`${this.appUrl}/api/cli/wizard-session/create`, { method: "POST", headers: { - "Content-Type": "application/json", Accept: "application/json", }, - body: JSON.stringify({ client_name: this.clientName }), signal: AbortSignal.timeout(CREATE_REQUEST_TIMEOUT_MS), }); if (!res.ok) { throw new Error( - `Wizard sign-in create failed: ${res.status} ${await res.text()}`, + `Wizard session create failed: ${res.status} ${await res.text()}`, ); } - return (await res.json()) as WizardSigninCreateResponse; + return (await res.json()) as WizardSessionCreateResponse; + } + + buildLoginUrl(session: WizardSessionCreateResponse): string { + return new URL(session.login_path, this.appUrl).toString(); } async pollSession(args: { - readonly id: string; + readonly sessionToken: string; readonly pollToken: string; readonly sleep?: (ms: number) => Promise; - }): Promise { + }): Promise { const sleep = args.sleep ?? defaultSleep; let interval = POLL_INTERVAL_MS; const deadline = Date.now() + POLL_HARD_TIMEOUT_MS; while (Date.now() < deadline) { await sleep(interval); - const url = `${this.appUrl}/api/cli/wizard-signin/poll?id=${encodeURIComponent(args.id)}`; + const url = `${this.appUrl}/api/cli/wizard-session/poll?session_token=${encodeURIComponent(args.sessionToken)}`; const res = await fetch(url, { method: "GET", headers: { @@ -115,7 +84,7 @@ export class WizardSigninAuthClient { >; if (!res.ok) { throw new Error( - `Wizard sign-in poll failed: ${res.status} ${JSON.stringify(json)}`, + `Wizard session poll failed: ${res.status} ${JSON.stringify(json)}`, ); } const status = json["status"]; @@ -123,31 +92,34 @@ export class WizardSigninAuthClient { case "pending": continue; case "expired": - throw new Error("Wizard sign-in session expired before approval."); + throw new Error("Wizard session expired before approval."); case "claimed": throw new Error( - "Wizard sign-in session was already claimed by another client.", + "Wizard session was already claimed by another client.", ); case "complete": return parseCompleteResponse(json); default: throw new Error( - `Unexpected wizard sign-in status: ${JSON.stringify(json)}`, + `Unexpected wizard session status: ${JSON.stringify(json)}`, ); } } - throw new Error("Wizard sign-in session timed out."); + throw new Error("Wizard session timed out."); } - async login(events: WizardSigninEvents): Promise { + async login( + events: WizardSessionEvents, + ): Promise { const session = await this.createSession(); + const loginUrl = this.buildLoginUrl(session); events.onLoginUrl({ - loginUrl: session.login_url, + loginUrl, expiresAt: session.expires_at, }); - await events.onTryOpenBrowser(session.login_url); + await events.onTryOpenBrowser(loginUrl); return this.pollSession({ - id: session.id, + sessionToken: session.session_token, pollToken: session.poll_token, }); } @@ -155,70 +127,28 @@ export class WizardSigninAuthClient { function parseCompleteResponse( json: Record, -): WizardSigninCompleteResult { +): WizardSessionCompleteResult { const apiKey = json["api_key"]; - if (typeof apiKey !== "string" || apiKey.length === 0) { - throw new Error("Wizard sign-in completed without an api_key"); - } - const orgInfo = parseOrgInfo(json["org_info"]); - const project = parseProject(json["project"]); - return { apiKey, orgInfo, project }; -} - -function parseOrgInfo(value: unknown): WizardSigninOrgInfo { - if (!isObject(value)) { - throw new Error("Wizard sign-in completed without org_info"); - } - const id = value["id"]; - const name = value["name"]; - if (typeof id !== "string" || typeof name !== "string") { - throw new Error("Wizard sign-in org_info missing id/name"); - } - return { - id, - name, - api_url: optionalString(value["api_url"]), - proxy_url: optionalString(value["proxy_url"]), - realtime_url: optionalString(value["realtime_url"]), - is_universal_api: - typeof value["is_universal_api"] === "boolean" - ? value["is_universal_api"] - : null, - git_metadata: value["git_metadata"], - }; -} - -function parseProject(value: unknown): WizardSigninProject { - if (!isObject(value)) { - throw new Error("Wizard sign-in completed without project"); - } - const id = value["id"]; - const name = value["name"]; - const orgId = value["org_id"]; + const orgId = json["org_id"]; + const orgName = json["org_name"]; + const projectId = json["project_id"]; + const projectName = json["project_name"]; if ( - typeof id !== "string" || - typeof name !== "string" || - typeof orgId !== "string" + !isNonEmptyString(apiKey) || + !isNonEmptyString(orgId) || + !isNonEmptyString(orgName) || + !isNonEmptyString(projectId) || + !isNonEmptyString(projectName) ) { - throw new Error("Wizard sign-in project missing id/name/org_id"); - } - return { - id, - name, - org_id: orgId, - description: optionalString(value["description"]), - }; -} - -function optionalString(value: unknown): string | null { - if (typeof value === "string") { - return value; + throw new Error( + `Wizard session complete response missing required fields: ${JSON.stringify(json)}`, + ); } - return null; + return { apiKey, orgId, orgName, projectId, projectName }; } -function isObject(value: unknown): value is Record { - return typeof value === "object" && value !== null && !Array.isArray(value); +function isNonEmptyString(value: unknown): value is string { + return typeof value === "string" && value.length > 0; } function defaultSleep(ms: number): Promise { diff --git a/packages/braintrust-wizard/src/clack-wizard.ts b/packages/braintrust-wizard/src/clack-wizard.ts index 1874b39..269d037 100644 --- a/packages/braintrust-wizard/src/clack-wizard.ts +++ b/packages/braintrust-wizard/src/clack-wizard.ts @@ -1,6 +1,8 @@ import { cwd as processCwd } from "node:process"; -import { WizardSigninAuthClient } from "./auth"; +import pc from "picocolors"; + +import { WizardSessionAuthClient } from "./auth"; import { openBrowser } from "./browser"; import { buildLogsPermalink, buildCleanupMessage } from "./cleanup"; import { findGitRoot, isGitRepo, writeEnvBraintrust } from "./git"; @@ -72,7 +74,7 @@ export type WizardDeps = { readonly env: NodeJS.ProcessEnv; readonly options: WizardOptions; readonly prompts: ClackWizardPrompts; - readonly authClient: WizardSigninAuthClient; + readonly authClient: WizardSessionAuthClient; readonly openBrowser: (url: string) => Promise; }; @@ -112,6 +114,10 @@ export async function runClackWizard(deps: WizardDeps): Promise { onTryOpenBrowser: (url) => deps.openBrowser(url), }); + prompts.log.success( + `Browser setup complete.\n org: ${pc.greenBright(session.orgName)}\n project: ${pc.greenBright(session.projectName)}`, + ); + const provider = await selectProvider(deps); let providerCredentials: Record | undefined; if (!provider.custom) { @@ -149,8 +155,8 @@ export async function runClackWizard(deps: WizardDeps): Promise { ); if (runIt) { const result = await runInstrumentation(deps, { - org: session.orgInfo.name, - project: session.project.name, + org: session.orgName, + project: session.projectName, apiKey: session.apiKey, providerCredentials, languages, @@ -179,8 +185,8 @@ export async function runClackWizard(deps: WizardDeps): Promise { ); return { - orgName: session.orgInfo.name, - projectName: session.project.name, + orgName: session.orgName, + projectName: session.projectName, braintrustApiKey: session.apiKey, }; } @@ -298,7 +304,7 @@ export type DefaultDepsArgs = { export function buildDefaultDeps(args: DefaultDepsArgs): WizardDeps { const cwd = args.cwd ?? processCwd(); const env = args.env ?? process.env; - const authClient = new WizardSigninAuthClient(args.options.appUrl); + const authClient = new WizardSessionAuthClient(args.options.appUrl); return { cwd, env, diff --git a/packages/braintrust-wizard/test/clack-wizard.test.ts b/packages/braintrust-wizard/test/clack-wizard.test.ts index 7570a61..a7e919e 100644 --- a/packages/braintrust-wizard/test/clack-wizard.test.ts +++ b/packages/braintrust-wizard/test/clack-wizard.test.ts @@ -5,9 +5,9 @@ import { join } from "node:path"; import { describe, expect, it } from "vitest"; import { - type WizardSigninAuthClient, - type WizardSigninCompleteResult, - type WizardSigninEvents, + type WizardSessionAuthClient, + type WizardSessionCompleteResult, + type WizardSessionEvents, } from "../src/auth"; import { type ClackWizardPrompts, @@ -97,45 +97,34 @@ function createPrompts(inputs: FakePromptInputs) { return { prompts, events }; } -const DEFAULT_LOGIN_RESULT: WizardSigninCompleteResult = { +const DEFAULT_LOGIN_RESULT: WizardSessionCompleteResult = { apiKey: "bt-secret-key", - orgInfo: { - id: "o1", - name: "acme", - api_url: "https://api.test", - proxy_url: null, - realtime_url: null, - is_universal_api: null, - git_metadata: null, - }, - project: { - id: "p1", - name: "demo", - org_id: "o1", - description: null, - }, + orgId: "o1", + orgName: "acme", + projectId: "p1", + projectName: "demo", }; function buildDeps(args: { readonly prompts: ClackWizardPrompts; - readonly authClient?: WizardSigninAuthClient; + readonly authClient?: WizardSessionAuthClient; readonly cwd?: string; }): WizardDeps { const cwd = args.cwd ?? mkdtempSync(join(tmpdir(), "bt-wizard-test-")); const stubAuth = args.authClient ?? ({ - login: async (events: WizardSigninEvents) => { + login: async (events: WizardSessionEvents) => { events.onLoginUrl({ - loginUrl: "https://app.test/app/cli-login/test-session", + loginUrl: "https://app.test/app/cli-login?session_token=test", expiresAt: "2099-01-01T00:00:00.000Z", }); await events.onTryOpenBrowser( - "https://app.test/app/cli-login/test-session", + "https://app.test/app/cli-login?session_token=test", ); return DEFAULT_LOGIN_RESULT; }, - } as unknown as WizardSigninAuthClient); + } as unknown as WizardSessionAuthClient); return { cwd, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 10ebf15..52e5709 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,6 +35,9 @@ importers: open: specifier: ^11.0.0 version: 11.0.0 + picocolors: + specifier: 1.1.1 + version: 1.1.1 react: specifier: 19.2.5 version: 19.2.5