import { $ } from "bun"; import { mkdir, readdir, rm, writeFile } from "node:fs/promises"; import { dirname, relative, sep } from "node:path"; import { fileURLToPath } from "node:url"; const buildDir = fileURLToPath(new URL("../.build/", import.meta.url)); const webDistDir = fileURLToPath(new URL("../dist/web/", import.meta.url)); const executablePath = fileURLToPath(new URL("../dist/dial-server", import.meta.url)); const generatedAssetsPath = fileURLToPath(new URL("../.build/static-assets.ts", import.meta.url)); const generatedEntryPath = fileURLToPath(new URL("../.build/server-entry.ts", import.meta.url)); await rm(buildDir, { force: true, recursive: true }); await rm(executablePath, { force: true }); await mkdir(buildDir, { recursive: true }); await $`bunx --bun vite build`; const files = await listFiles(webDistDir); const indexPath = files.find((file) => normalize(relative(webDistDir, file)) === "index.html"); if (!indexPath) { throw new Error("Vite build 未生成 dist/web/index.html"); } const assetFiles = files.filter((file) => file !== indexPath); await writeGeneratedAssets(indexPath, assetFiles); await writeGeneratedEntry(); const target = process.env["BUN_TARGET"] ?? process.env["BUILD_TARGET"]; const result = await Bun.build({ compile: target ? { autoloadBunfig: true, autoloadDotenv: true, outfile: executablePath, target: target as Bun.Build.CompileTarget, } : { autoloadBunfig: true, autoloadDotenv: true, outfile: executablePath, }, entrypoints: [generatedEntryPath], minify: true, sourcemap: "linked", }); if (!result.success) { await rm(executablePath, { force: true }); throw new Error("Bun executable 构建失败"); } console.log(`Built executable: ${executablePath}`); await rm(buildDir, { force: true, recursive: true }); async function listFiles(directory: string): Promise { const entries = await readdir(directory, { withFileTypes: true }); const files = await Promise.all( entries.map(async (entry) => { const path = `${directory.replace(/\/$/, "")}/${entry.name}`; if (entry.isDirectory()) { return listFiles(path); } return [path]; }), ); return files.flat().sort((left, right) => normalize(left).localeCompare(normalize(right))); } function normalize(path: string): string { return path.split(sep).join("/"); } function toImportPath(path: string): string { const rel = normalize(relative(buildDir, path)); return rel.startsWith(".") ? rel : `./${rel}`; } async function writeGeneratedAssets(indexPath: string, assetFiles: string[]) { const imports = [ `import type { StaticAssets } from "../src/server/app";`, `import indexPath from "${toImportPath(indexPath)}" with { type: "file" };`, ...assetFiles.map((file, index) => `import asset${index}Path from "${toImportPath(file)}" with { type: "file" };`), ]; const assetEntries = assetFiles.map((file, index) => { const urlPath = `/${normalize(relative(webDistDir, file))}`; return ` ${JSON.stringify(urlPath)}: Bun.file(asset${index}Path),`; }); const source = `${imports.join("\n")} export const staticAssets: StaticAssets = { indexHtml: Bun.file(indexPath), files: { ${assetEntries.join("\n")} }, }; `; await mkdir(dirname(generatedAssetsPath), { recursive: true }); await writeFile(generatedAssetsPath, source); } async function writeGeneratedEntry() { await writeFile( generatedEntryPath, `import { bootstrap } from "../src/server/bootstrap"; import { readRuntimeConfig } from "../src/server/config"; import { staticAssets } from "./static-assets"; async function main() { const { configPath } = readRuntimeConfig(); await bootstrap({ configPath, mode: "production", staticAssets }); } void main().catch((error) => { console.error("启动失败:", error instanceof Error ? error.message : error); process.exit(1); }); `, ); }