import { mkdir, readdir, rm, writeFile } from "node:fs/promises"; import { dirname, relative, sep } from "node:path"; import { fileURLToPath } from "node:url"; import { $ } from "bun"; const rootDir = fileURLToPath(new URL("../", import.meta.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/gateway-checker", 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, { recursive: true, force: 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({ entrypoints: [generatedEntryPath], compile: target ? { target: target as Bun.Build.CompileTarget, outfile: executablePath, autoloadDotenv: true, autoloadBunfig: true, } : { outfile: executablePath, autoloadDotenv: true, autoloadBunfig: true, }, minify: true, sourcemap: "linked", }); if (!result.success) { await rm(executablePath, { force: true }); throw new Error("Bun executable 构建失败"); } console.log(`Built executable: ${executablePath}`); 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(); } 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 { readRuntimeConfig } from "../src/server/config"; import { startServer } from "../src/server/server"; import { staticAssets } from "./static-assets"; startServer({ config: readRuntimeConfig(), mode: "production", staticAssets, }); `, ); } function toImportPath(path: string): string { const rel = normalize(relative(buildDir, path)); return rel.startsWith(".") ? rel : `./${rel}`; } function normalize(path: string): string { return path.split(sep).join("/"); }