import { $ } from 'bun' import { Console, Effect } from 'effect' // ============================================================================ // Domain Models // ============================================================================ const targetMap = { 'bun-windows-x64': 'x86_64-pc-windows-msvc', 'bun-darwin-arm64': 'aarch64-apple-darwin', 'bun-darwin-x64': 'x86_64-apple-darwin', 'bun-linux-x64': 'x86_64-unknown-linux-gnu', 'bun-linux-arm64': 'aarch64-unknown-linux-gnu', } as const type BunTarget = keyof typeof targetMap type BuildConfig = Readonly<{ entrypoint: string outputDir: string targets: ReadonlyArray }> type BuildResult = Readonly<{ target: BunTarget outputs: ReadonlyArray }> // ============================================================================ // Configuration // ============================================================================ const defaultConfig: BuildConfig = { entrypoint: './.output/server/index.mjs', outputDir: './out', targets: ['bun-windows-x64', 'bun-darwin-arm64', 'bun-linux-x64'], } // ============================================================================ // Effects // ============================================================================ /** * 清理输出目录 */ const cleanOutputDir = (dir: string) => Effect.tryPromise({ try: async () => { await $`rm -rf ${dir}` return undefined }, catch: (error: unknown) => ({ _tag: 'CleanError' as const, message: `无法清理目录 ${dir}`, cause: error, }), }).pipe(Effect.tap(() => Console.log(`✓ 已清理输出目录: ${dir}`))) /** * 为单个目标平台构建 */ const buildForTarget = ( config: BuildConfig, target: BunTarget, ): Effect.Effect< BuildResult, { _tag: 'BuildError'; target: BunTarget; message: string; cause: unknown } > => Effect.gen(function* () { yield* Console.log(`🔨 开始构建: ${target}`) const output = yield* Effect.tryPromise({ try: () => Bun.build({ entrypoints: [config.entrypoint], compile: { outfile: `server-${targetMap[target]}`, target: target, }, outdir: config.outputDir, }), catch: (error: unknown) => ({ _tag: 'BuildError' as const, target, message: `构建失败: ${target}`, cause: error, }), }) const paths = output.outputs.map((item: { path: string }) => item.path) return { target, outputs: paths, } satisfies BuildResult }) /** * 并行构建所有目标平台 */ const buildAllTargets = (config: BuildConfig) => { const effects = config.targets.map((target) => buildForTarget(config, target)) return Effect.all(effects, { concurrency: 'unbounded' }) } /** * 输出构建结果摘要 */ const printBuildSummary = (results: ReadonlyArray) => Effect.gen(function* () { yield* Console.log('\n📦 构建完成:') for (const result of results) { yield* Console.log(` ${result.target}:`) for (const path of result.outputs) { yield* Console.log(` - ${path}`) } } }) // ============================================================================ // Main Program // ============================================================================ const program = Effect.gen(function* () { const config = defaultConfig // 1. 清理输出目录 yield* cleanOutputDir(config.outputDir) // 2. 并行构建所有目标 const results = yield* buildAllTargets(config) // 3. 输出构建摘要 yield* printBuildSummary(results) return results }) // ============================================================================ // Runner // ============================================================================ const main = program.pipe( Effect.catchAllCause((cause) => Console.error('❌ 构建失败:', cause).pipe( Effect.flatMap(() => Effect.fail(cause)), ), ), ) Effect.runPromise(main).catch(() => { process.exit(1) })