diff --git a/package.json b/package.json index bed1b9f..a71d557 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,11 @@ "version": "1.0.4", "type": "module", "scripts": { - "dev": "vite", + "dev": "pnpx tsx scripts/dev-all.ts", + "dev:frontend": "vite", + "dev:backend": "pnpx tsx backend/app.ts", "build": "vite build", - "build_backend": "tsup && ./llrt compile ./dist_backend/app.cjs ./dist_backend/app.lrt", + "build_backend": "pnpx tsx scripts/build-backend.ts", "docker_build": "docker build -t llej0/web-font:${npm_package_version} -t llej0/web-font:latest .", "docker_push": "docker push llej0/web-font:${npm_package_version} && docker push llej0/web-font:latest", "preview": "vite preview", diff --git a/scripts/build-backend.ts b/scripts/build-backend.ts new file mode 100644 index 0000000..2870f06 --- /dev/null +++ b/scripts/build-backend.ts @@ -0,0 +1,114 @@ +/** + * 构建后端脚本 + * 1. 检测并下载 LLRT 二进制文件 + * 2. 运行 tsup 编译 + * 3. 使用 LLRT compile 生成 .lrt 文件 + */ +import { execSync } from "node:child_process"; +import { existsSync } from "node:fs"; +import { mkdir, writeFile, rm } from "node:fs/promises"; +import { arch, platform } from "node:os"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const ROOT_DIR = join(__dirname, ".."); +const LLRT_BIN = join(ROOT_DIR, "llrt"); + +/** 映射 node arch 到 LLRT arch */ +function getLlrtArch() { + const a = arch(); + if (a === "x64") return "x64"; + if (a === "arm64") return "arm64"; + throw new Error(`Unsupported architecture: ${a}`); +} + +/** 映射 node platform 到 LLRT platform */ +function getLlrtPlatform() { + const p = platform(); + if (p === "linux") return "linux"; + if (p === "darwin") return "darwin"; + if (p === "win32") return "windows"; + throw new Error(`Unsupported platform: ${p}`); +} + +/** 确保 llrt 二进制文件存在,不存在则下载 */ +async function ensureLlrt() { + if (existsSync(LLRT_BIN)) { + console.log("LLRT binary found, skipping download."); + return; + } + + const llrtArch = getLlrtArch(); + const llrtPlatform = getLlrtPlatform(); + + console.log("Fetching latest LLRT release version..."); + const res = await fetch("https://api.github.com/repos/awslabs/llrt/releases/latest"); + /** 响应数据 */ + const data = await res.json() as { tag_name: string }; + const version = data.tag_name; + + if (!version) { + throw new Error("Failed to fetch latest LLRT version from GitHub"); + } + + console.log(`Latest LLRT version: ${version}`); + + const downloadUrl = `https://github.com/awslabs/llrt/releases/download/${version}/llrt-${llrtPlatform}-${llrtArch}.zip`; + console.log(`Downloading from ${downloadUrl} ...`); + + const zipRes = await fetch(downloadUrl); + if (!zipRes.ok) { + throw new Error(`Failed to download LLRT: ${zipRes.status} ${zipRes.statusText}`); + } + + /** 下载 zip 到临时文件 */ + const tmpDir = join(ROOT_DIR, ".tmp-llrt"); + await mkdir(tmpDir, { recursive: true }); + const zipPath = join(tmpDir, "llrt.zip"); + const arrayBuffer = await zipRes.arrayBuffer(); + await writeFile(zipPath, Buffer.from(arrayBuffer)); + + /** 使用 unzip 命令解压(Node 内置没有 zip 解压) */ + const binaryName = platform() === "win32" ? "llrt.exe" : "llrt"; + execSync(`unzip -o -j "${zipPath}" "${binaryName}" -d "${ROOT_DIR}"`, { + stdio: "inherit", + }); + + /** linux/mac 需要可执行权限 */ + if (platform() !== "win32") { + execSync(`chmod +x "${LLRT_BIN}"`); + } + + await rm(tmpDir, { recursive: true }); + + console.log(`LLRT ${version} installed successfully.`); +} + +/** 运行 tsup 编译 */ +function runTsup() { + console.log("\n--- Running tsup build ---"); + execSync("pnpm tsup", { stdio: "inherit", cwd: ROOT_DIR }); +} + +/** 使用 LLRT compile 生成 .lrt 文件 */ +function runLlrtCompile() { + console.log("\n--- Running LLRT compile ---"); + execSync(`${LLRT_BIN} compile ./dist_backend/app.cjs ./dist_backend/app.lrt`, { + stdio: "inherit", + cwd: ROOT_DIR, + }); + console.log("\nBackend build completed successfully!"); +} + +/** 主流程 */ +async function main() { + await ensureLlrt(); + runTsup(); + runLlrtCompile(); +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/scripts/dev-all.ts b/scripts/dev-all.ts new file mode 100644 index 0000000..931a955 --- /dev/null +++ b/scripts/dev-all.ts @@ -0,0 +1,45 @@ +/** + * 同时启动前端 (vite) 和后端 (tsx backend/app.ts) 的开发服务器 + * Ctrl+C 会同时终止两个进程 + */ +import { spawn } from "node:child_process"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const ROOT_DIR = join(__dirname, ".."); + +/** 子进程列表,用于退出时统一清理 */ +const children: ReturnType[] = []; + +/** 清理所有子进程 */ +function cleanup() { + for (const child of children) { + child.kill("SIGTERM"); + } +} + +process.on("SIGINT", () => { + cleanup(); + process.exit(0); +}); +process.on("SIGTERM", () => { + cleanup(); + process.exit(0); +}); + +console.log("Starting frontend and backend dev servers...\n"); + +const backend = spawn("pnpx", ["tsx", "backend/app.ts"], { + cwd: ROOT_DIR, + stdio: "inherit", + shell: true, +}); +children.push(backend); + +const frontend = spawn("pnpm", ["dev:frontend"], { + cwd: ROOT_DIR, + stdio: "inherit", + shell: true, +}); +children.push(frontend); diff --git a/tsconfig.json b/tsconfig.json index f70380e..f049896 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,6 +11,9 @@ }, { "path": "./tsconfig.node.json" + }, + { + "path": "./tsconfig.scripts.json" } ] } \ No newline at end of file diff --git a/tsconfig.scripts.json b/tsconfig.scripts.json new file mode 100644 index 0000000..3c372db --- /dev/null +++ b/tsconfig.scripts.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "types": ["node"], + "esModuleInterop": true, + "strict": true + }, + "include": ["scripts"] +}