| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142 |
- #!/usr/bin/env node
- /**
- * 打包脚本
- *
- * 用法:
- * node scripts/build.mjs # 完整打包(Next.js + Electron)
- * node scripts/build.mjs --web # 仅打包 Web(Next.js standalone)
- * node scripts/build.mjs --electron # 仅打包 Electron 桌面应用
- * node scripts/build.mjs --no-build # 跳过编译,直接用上次产物打包
- *
- * 环境变量:
- * NO_BUILD=1 等同于 --no-build
- */
- import { spawn } from 'child_process';
- import fs from 'fs/promises';
- import path from 'path';
- import { fileURLToPath } from 'url';
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
- const root = path.resolve(__dirname, '..');
- // ── CLI flags ──────────────────────────────────────────────────────────────
- const args = process.argv.slice(2);
- const skipBuild = args.includes('--no-build') || process.env.NO_BUILD === '1';
- const webOnly = args.includes('--web');
- const electronOnly = args.includes('--electron');
- const buildBoth = !webOnly && !electronOnly;
- // ── Helpers ────────────────────────────────────────────────────────────────
- function run(cmd, cmdArgs, opts = {}) {
- return new Promise((resolve, reject) => {
- console.log(`\n$ ${cmd} ${cmdArgs.join(' ')}`);
- const p = spawn(cmd, cmdArgs, {
- stdio: 'inherit',
- cwd: root,
- shell: true,
- env: { ...process.env, PYTHONIOENCODING: 'utf-8', LANG: 'zh_CN.UTF-8' },
- ...opts
- });
- p.on('close', (code) => {
- if (code === 0) resolve();
- else reject(new Error(`命令失败(exit ${code}): ${cmd} ${cmdArgs.join(' ')}`));
- });
- p.on('error', reject);
- });
- }
- async function exists(p) {
- try { await fs.access(p); return true; } catch { return false; }
- }
- function log(msg) {
- console.log(`\n\x1b[36m▶ ${msg}\x1b[0m`);
- }
- function success(msg) {
- console.log(`\x1b[32m✔ ${msg}\x1b[0m`);
- }
- // ── Web (Next.js standalone) ───────────────────────────────────────────────
- async function buildWeb() {
- log('构建 Next.js...');
- await run('yarn', ['build']);
- const standaloneDir = path.join(root, '.next', 'standalone');
- if (!(await exists(standaloneDir))) {
- throw new Error('未找到 .next/standalone,请确认 next.config.mjs 中已设置 output: "standalone"');
- }
- const outDir = path.join(root, 'dist');
- log('组装 Web 产物到 dist/...');
- await fs.rm(outDir, { recursive: true, force: true });
- await fs.mkdir(outDir, { recursive: true });
- await fs.cp(standaloneDir, outDir, { recursive: true });
- // 复制 .next/static 和 public
- const nextStatic = path.join(root, '.next', 'static');
- if (await exists(nextStatic)) {
- await fs.cp(nextStatic, path.join(outDir, '.next', 'static'), { recursive: true });
- }
- const publicDir = path.join(root, 'public');
- if (await exists(publicDir)) {
- await fs.cp(publicDir, path.join(outDir, 'public'), { recursive: true });
- }
- success('Web 构建完成 → dist/');
- }
- // ── Electron ───────────────────────────────────────────────────────────────
- async function buildElectron() {
- log('编译 Electron 主进程 TypeScript...');
- await run('yarn', ['electron:compile']);
- log('运行 electron-builder 打包...');
- await run('yarn', ['electron-builder']);
- success('Electron 打包完成 → dist/');
- }
- // ── Main ───────────────────────────────────────────────────────────────────
- async function main() {
- const startTime = Date.now();
- console.log('\x1b[1m\x1b[35m');
- console.log('╔══════════════════════════════╗');
- console.log('║ 小说创作助手 打包工具 ║');
- console.log('╚══════════════════════════════╝');
- console.log('\x1b[0m');
- if (skipBuild) {
- console.log('⚠️ 已跳过编译步骤(--no-build)');
- }
- try {
- if (buildBoth || webOnly) {
- if (!skipBuild) {
- await buildWeb();
- } else {
- log('跳过 Next.js 构建');
- }
- }
- if (buildBoth || electronOnly) {
- if (!skipBuild) {
- await buildElectron();
- } else {
- log('跳过编译,直接运行 electron-builder...');
- await run('yarn', ['electron-builder']);
- }
- }
- const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
- console.log(`\n\x1b[1m\x1b[32m✅ 打包完成!耗时 ${elapsed}s\x1b[0m`);
- console.log('\x1b[33m输出目录:dist/\x1b[0m\n');
- } catch (err) {
- console.error(`\n\x1b[31m❌ 打包失败:${err.message}\x1b[0m\n`);
- process.exit(1);
- }
- }
- main();
|