- CheckerDefinition 泛型化,HTTP/Command checker 移除 resolved target 断言 - 新增 ProbeStore.getAllRecentSamples 消除 targets 路由 N+1 查询 - 统一 getAllTargetStats 与 getTargetStats 的 availability 精度 - Engine rejected 结果写入 internal error 记录,提升可观测性 - 新增 bootstrap.ts 统一 dev/production 启动序列 - dataDir 相对路径改为基于配置文件目录解析 - validatePagination 增加 pageSize 上限 200 校验 - 修复 ErrorBoundary override 标记 - 更新 README/DEVELOPMENT 文档,新增完整测试覆盖
126 lines
3.9 KiB
TypeScript
126 lines
3.9 KiB
TypeScript
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<string[]> {
|
|
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);
|
|
});
|
|
`,
|
|
);
|
|
}
|