#!/usr/bin/env bun import { config as dotenv } from "dotenv"; import { existsSync } from "fs"; import { rm } from "fs/promises"; import path from "path"; import { fileURLToPath } from "url"; import { $ } from "bun"; import { buildConfig } from "./helpers"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const envPath = path.resolve(__dirname, "../../.env"); dotenv({ path: envPath }); if (process.argv.includes("--help") || process.argv.includes("-h")) { console.log(` šŸ—ļø Runtime Build Script Usage: bun run scripts/build/runtime.ts [options] Builds pertento-runtime to src/apps/runtime/dist Options: --help, -h Show this help message --core Build only runtime-core (default) --experiments Build only runtime-experiments --tracking Build only runtime-tracking --editor-setup Build only editor-setup --all Build all runtimes Example: bun run scripts/build/runtime.ts bun run scripts/build/runtime.ts --experiments bun run scripts/build/runtime.ts --tracking bun run scripts/build/runtime.ts --editor-setup bun run scripts/build/runtime.ts --all `); process.exit(0); } const formatFileSize = (bytes: number): string => { const units = ["B", "KB", "MB", "GB"]; let size = bytes; let unitIndex = 0; while (size >= 1024 && unitIndex < units.length - 1) { size /= 1024; unitIndex++; } return `${size.toFixed(2)} ${units[unitIndex]}`; }; interface RuntimeBuildConfig { name: string; srcDir: string; configPath: string; outFilename: string; format: "iife" | "esm"; entry?: string; } const runtimeConfigs: Record = { core: { name: "runtime-core", srcDir: "src/apps/runtime/runtime-core/src", configPath: "src/apps/runtime/runtime-core/src/config.ts", outFilename: "pertentoRuntime5.js", format: "iife", }, experiments: { name: "runtime-experiments", srcDir: "src/apps/runtime/runtime-experiments/src", configPath: "src/apps/runtime/runtime-experiments/src/config.ts", outFilename: "pertentoRuntime5Experiments.js", format: "esm", }, tracking: { name: "runtime-tracking", srcDir: "src/apps/runtime/runtime-tracking/src", configPath: "src/apps/runtime/runtime-tracking/src/config.ts", outFilename: "pertentoRuntime5Tracking.js", format: "iife", }, editorSetup: { name: "editor-setup", srcDir: "src/apps/runtime/runtime-tracking/src", configPath: "src/apps/runtime/runtime-tracking/src/config.ts", outFilename: "pertentoEditorSetup.js", format: "iife", entry: "setup-for-editor-extension.ts", }, }; let buildTargets: string[] = ["core"]; if (process.argv.includes("--experiments")) { buildTargets = ["experiments"]; } else if (process.argv.includes("--tracking")) { buildTargets = ["tracking"]; } else if (process.argv.includes("--editor-setup")) { buildTargets = ["editorSetup"]; } else if (process.argv.includes("--all")) { buildTargets = ["core", "experiments", "tracking", "editorSetup"]; } console.log("\nšŸš€ Starting runtime build process...\n"); const { BUILD_ENV } = process.env; const isProduction = BUILD_ENV === "production"; console.log( `šŸ“‹ Build environment: ${BUILD_ENV || "development"} ${isProduction ? "(minified)" : "(development)"}\n`, ); const outdir = "src/apps/runtime/dist"; if (existsSync(outdir)) { console.log(`šŸ—‘ļø Cleaning previous build at ${outdir}`); await rm(outdir, { recursive: true, force: true }); } const start = performance.now(); async function buildRuntime(target: string) { const config = runtimeConfigs[target]; if (!config) { console.error(`Unknown runtime target: ${target}`); process.exit(1); } // Generate config with environment values console.log(`šŸ”§ Generating config for ${config.name}...\n`); buildConfig(config.configPath, target); console.log(`šŸ“¦ Building ${config.name}...\n`); const entryFile = config.entry || "index.ts"; const result = await Bun.build({ entrypoints: [`${config.srcDir}/${entryFile}`], outdir, minify: isProduction, target: "browser", format: config.format, naming: config.outFilename, define: { "process.env.NODE_ENV": JSON.stringify( isProduction ? "production" : "development", ), }, }); if (!result.success) { console.error(`Build failed for ${config.name}!`); process.exit(1); } // Compress the output files using Bun shell const jsFile = path.join(outdir, config.outFilename); if (existsSync(jsFile)) { // Create gzip version await $`gzip -k -f ${jsFile}`; // Create brotli version await $`brotli -k -f ${jsFile}`; } return result; } // Build all targets let allOutputs: { path: string; kind: string }[] = []; for (const target of buildTargets) { const result = await buildRuntime(target); allOutputs = allOutputs.concat(result.outputs); } const end = performance.now(); // Collect all compressed files const allCompressed = buildTargets.flatMap((target) => { const config = runtimeConfigs[target]; if (!config) return []; return [ { path: path.join(outdir, `${config.outFilename}.gz`), kind: "compressed" }, { path: path.join(outdir, `${config.outFilename}.br`), kind: "compressed" }, ]; }); const outputTable = allOutputs .concat(allCompressed) .filter((output) => existsSync(output.path)) .map((output) => ({ File: path.relative(process.cwd(), output.path), Size: formatFileSize(Bun.file(output.path).size), })); console.table(outputTable); const buildTime = (end - start).toFixed(2); // Copy all output files to runtime-scripts directory const runtimeScriptsDir = "runtime-scripts"; if (!existsSync(runtimeScriptsDir)) { await $`mkdir -p ${runtimeScriptsDir}`; } console.log(`\nšŸ“ Copying files to ${runtimeScriptsDir}...`); for (const output of allOutputs.concat(allCompressed)) { if (existsSync(output.path)) { const filename = path.basename(output.path); await $`cp ${output.path} ${runtimeScriptsDir}/${filename}`; } } console.log(`\nāœ… Runtime build completed in ${buildTime}ms\n`);