From 5855398250844c5dca38eccc0d51b2579c30303a Mon Sep 17 00:00:00 2001 From: imbytecat Date: Sun, 18 Jan 2026 14:18:52 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BD=BF=E7=94=A8=20Effect=20=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E9=87=8D=E6=9E=84=E6=9E=84=E5=BB=BA=E8=84=9A=E6=9C=AC?= =?UTF-8?q?=E5=B9=B6=E6=94=AF=E6=8C=81=E5=A4=9A=E5=B9=B3=E5=8F=B0=E5=B9=B6?= =?UTF-8?q?=E8=A1=8C=E6=9E=84=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 使用 Effect 模块重构构建脚本,引入类型安全的配置与错误处理,实现并行构建多平台目标并输出结构化构建摘要。 - 添加 effect 库及其依赖项以支持函数式编程特性 - 添加 effect 依赖以支持函数式响应式编程特性 --- build.ts | 157 ++++++++++++++++++++++++++++++++++++++++++++------- bun.lock | 7 +++ package.json | 1 + 3 files changed, 144 insertions(+), 21 deletions(-) diff --git a/build.ts b/build.ts index 33d8fa0..87b1697 100644 --- a/build.ts +++ b/build.ts @@ -1,6 +1,9 @@ import { $ } from 'bun' +import { Console, Effect } from 'effect' -await $`rm -rf ./out` +// ============================================================================ +// Domain Models +// ============================================================================ const targetMap = { 'bun-windows-x64': 'x86_64-pc-windows-msvc', @@ -12,27 +15,139 @@ const targetMap = { type BunTarget = keyof typeof targetMap -const targets: BunTarget[] = [ - 'bun-windows-x64', - 'bun-darwin-arm64', - 'bun-linux-x64', -] +type BuildConfig = Readonly<{ + entrypoint: string + outputDir: string + targets: ReadonlyArray +}> -const buildTasks = targets.map((bunTarget) => - Bun.build({ - entrypoints: ['./.output/server/index.mjs'], - compile: { - outfile: `server-${targetMap[bunTarget]}`, - target: bunTarget, +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 }, - outdir: './out', - }), + 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)), + ), + ), ) -const outputs = await Promise.all(buildTasks) - -for (const [index, output] of outputs.entries()) { - const task = buildTasks[index] - if (!task) continue - console.log(output.outputs.map((item) => item.path)) -} +Effect.runPromise(main).catch(() => { + process.exit(1) +}) diff --git a/bun.lock b/bun.lock index ae61c92..4c3b01a 100644 --- a/bun.lock +++ b/bun.lock @@ -33,6 +33,7 @@ "@vitejs/plugin-react": "^5.1.2", "babel-plugin-react-compiler": "^1.0.0", "drizzle-kit": "^0.31.8", + "effect": "^3.19.14", "nitro": "npm:nitro-nightly@latest", "tailwindcss": "^4.1.18", "typescript": "^5.9.3", @@ -628,6 +629,8 @@ "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="], + "effect": ["effect@3.19.14", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-3vwdq0zlvQOxXzXNKRIPKTqZNMyGCdaFUBfMPqpsyzZDre67kgC1EEHDV4EoQTovJ4w5fmJW756f86kkuz7WFA=="], + "electron-to-chromium": ["electron-to-chromium@1.5.267", "", {}, "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw=="], "encoding-sniffer": ["encoding-sniffer@0.2.1", "", { "dependencies": { "iconv-lite": "^0.6.3", "whatwg-encoding": "^3.1.1" } }, "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw=="], @@ -648,6 +651,8 @@ "exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="], + "fast-check": ["fast-check@3.23.2", "", { "dependencies": { "pure-rand": "^6.1.0" } }, "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A=="], + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], @@ -800,6 +805,8 @@ "prettier": ["prettier@3.8.0", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-yEPsovQfpxYfgWNhCfECjG5AQaO+K3dp6XERmOepyPDVqcJm+bjyCVO3pmU+nAPe0N5dDvekfGezt/EIiRe1TA=="], + "pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="], + "radash": ["radash@12.1.1", "", {}, "sha512-h36JMxKRqrAxVD8201FrCpyeNuUY9Y5zZwujr20fFO77tpUtGa6EZzfKw/3WaiBX95fq7+MpsuMLNdSnORAwSA=="], "react": ["react@19.2.3", "", {}, "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA=="], diff --git a/package.json b/package.json index 8ca92f8..f58b1ba 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "@vitejs/plugin-react": "^5.1.2", "babel-plugin-react-compiler": "^1.0.0", "drizzle-kit": "^0.31.8", + "effect": "^3.19.14", "nitro": "npm:nitro-nightly@latest", "tailwindcss": "^4.1.18", "typescript": "^5.9.3",