diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 83cd314..803768c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,6 +31,11 @@ jobs: - name: Build application run: yarn build + - name: Install sharp platform binaries + run: | + npm install --os=win32 --cpu=x64 sharp + npm install --os=win32 --cpu=arm64 sharp + - name: Build Windows installer run: yarn dist:win env: @@ -63,6 +68,11 @@ jobs: - name: Build application run: yarn build + - name: Install sharp platform binaries + run: | + npm install --os=darwin --cpu=x64 sharp + npm install --os=darwin --cpu=arm64 sharp + - name: Build macOS installer run: yarn dist:mac env: @@ -95,6 +105,11 @@ jobs: - name: Build application run: yarn build + - name: Install sharp platform binaries + run: | + npm install --os=linux --cpu=x64 sharp + npm install --os=linux --cpu=arm64 sharp + - name: Build Linux installer run: yarn dist:linux env: diff --git a/electron-builder.yml b/electron-builder.yml index 66d87d0..9f2cb9b 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -21,6 +21,9 @@ files: - "!scripts/*.ts" asar: true +asarUnpack: + - "**/node_modules/sharp/**" + - "**/node_modules/@img/**" extraResources: - from: data diff --git a/env/.env.dev b/env/.env.dev deleted file mode 100644 index 584000d..0000000 --- a/env/.env.dev +++ /dev/null @@ -1,4 +0,0 @@ -NODE_ENV=dev -PORT=10588 -OSSURL=http://127.0.0.1:10588/ - diff --git a/env/.env.prod b/env/.env.prod deleted file mode 100644 index db28ae4..0000000 --- a/env/.env.prod +++ /dev/null @@ -1,4 +0,0 @@ -NODE_ENV=prod -PORT=10588 -OSSURL=http://127.0.0.1:10588/ - diff --git a/scripts/build.ts b/scripts/build.ts index 3cd22bf..097d927 100644 --- a/scripts/build.ts +++ b/scripts/build.ts @@ -28,6 +28,7 @@ const external = [ "vm2", "sqlite3", "better-sqlite3", + "sharp", "mysql", "mysql2", "pg", diff --git a/scripts/main.ts b/scripts/main.ts index d4472a8..0b63644 100644 --- a/scripts/main.ts +++ b/scripts/main.ts @@ -15,20 +15,26 @@ declare const __APP_VERSION__: string | undefined; function getVersionFromUpdateJson(filePath: string): string | null { try { - if (fs.existsSync(filePath)) { - const data = JSON.parse(fs.readFileSync(filePath, "utf8")); - return data.version ?? null; - } + return JSON.parse(fs.readFileSync(filePath, "utf8")).version ?? null; } catch {} return null; } -function copyDirForce(src: string, dest: string): void { +const SKIP_ENTRIES = new Set(["logs", "oss", "db2.sqlite"]); + +function copyDir(src: string, dest: string, overwrite = false): void { if (!fs.existsSync(src)) return; - if (fs.existsSync(dest)) { - fs.rmSync(dest, { recursive: true, force: true }); + fs.mkdirSync(dest, { recursive: true }); + for (const entry of fs.readdirSync(src, { withFileTypes: true })) { + if (SKIP_ENTRIES.has(entry.name)) continue; + const srcPath = path.join(src, entry.name); + const destPath = path.join(dest, entry.name); + if (entry.isDirectory()) { + copyDir(srcPath, destPath, overwrite); + } else if (overwrite || !fs.existsSync(destPath)) { + fs.copyFileSync(srcPath, destPath); + } } - copyDirRecursive(src, dest); } function initializeData(): void { @@ -38,34 +44,23 @@ function initializeData(): void { const currentVersion = typeof __APP_VERSION__ !== "undefined" ? __APP_VERSION__ : "0.0.0"; const userVersion = getVersionFromUpdateJson(updateJsonFile); - // 首次安装或无update.json,直接全量拷贝 - if (!fs.existsSync(destDir) || !userVersion) { - copyDirRecursive(srcDir, destDir); + if (!userVersion) { + copyDir(srcDir, destDir); return; } - // 版本号不同则覆盖 serve 和 web 目录 if (userVersion !== currentVersion) { - copyDirForce(path.join(srcDir, "serve"), path.join(destDir, "serve")); - copyDirForce(path.join(srcDir, "web"), path.join(destDir, "web")); - } -} - -function copyDirRecursive(src: string, dest: string): void { - if (!fs.existsSync(src)) return; - if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true }); - for (const entry of fs.readdirSync(src, { withFileTypes: true })) { - // 跳过 oss 文件夹和 db2.sqlite 文件 - if (entry.isDirectory() && entry.name === "logs") continue; - if (entry.isDirectory() && entry.name === "oss") continue; - if (!entry.isDirectory() && entry.name === "db2.sqlite") continue; - const srcPath = path.join(src, entry.name); - const destPath = path.join(dest, entry.name); - if (entry.isDirectory()) { - copyDirRecursive(srcPath, destPath); - } else if (!fs.existsSync(destPath)) { - fs.copyFileSync(srcPath, destPath); + for (const dir of ["serve", "web"]) { + const dest = path.join(destDir, dir); + if (fs.existsSync(dest)) fs.rmSync(dest, { recursive: true, force: true }); + copyDir(path.join(srcDir, dir), dest); } + copyDir(srcDir, destDir); + try { + const data = JSON.parse(fs.readFileSync(updateJsonFile, "utf8")); + data.version = currentVersion; + fs.writeFileSync(updateJsonFile, JSON.stringify(data, null, 4), "utf8"); + } catch {} } } diff --git a/src/app.ts b/src/app.ts index 4cb5e0c..48b87f6 100644 --- a/src/app.ts +++ b/src/app.ts @@ -12,7 +12,6 @@ import fs from "fs"; import u from "@/utils"; import jwt from "jsonwebtoken"; import socketInit from "@/socket/index"; -import path from "path"; const app = express(); const server = http.createServer(app); @@ -52,7 +51,7 @@ export default async function startServe(randomPort: Boolean = false) { express.static(skillsDir), ); - // assets 静态资源 + // assets 静态资源 const assetsDir = u.getPath("assets"); if (!fs.existsSync(assetsDir)) { fs.mkdirSync(assetsDir, { recursive: true }); @@ -105,7 +104,7 @@ export default async function startServe(randomPort: Boolean = false) { res.status(err.status || 500).send(err); }); - const port = randomPort ? 0 : parseInt(process.env.PORT || "10588"); + const port = randomPort ? 0 : 10588; return await new Promise((resolve) => { server.listen(port, async () => { const address = server.address(); diff --git a/src/env.ts b/src/env.ts index d1af1b4..db8f322 100644 --- a/src/env.ts +++ b/src/env.ts @@ -1,12 +1,3 @@ -import { readFileSync, existsSync, writeFileSync, mkdirSync } from "fs"; -import path from "path"; - -// 默认环境变量(当 env 文件不存在时自动创建) -const defaultEnvValues: Record = { - dev: `NODE_ENV=dev\nPORT=10588\nOSSURL=http://127.0.0.1:10588/oss/`, - prod: `NODE_ENV=prod\nPORT=10588\nOSSURL=http://127.0.0.1:10588/oss/`, -}; - // 判断是否为打包后的 Electron 环境 const isElectron = typeof process.versions?.electron !== "undefined"; let isPackaged = false; @@ -16,35 +7,9 @@ if (isElectron) { } //加载环境变量(打包环境默认使用 prod) -const env = process.env.NODE_ENV ?? (isPackaged ? "prod" : "dev"); +const env = process.env.NODE_ENV; if (!env) { - console.log("[环境变量为空]"); - process.exit(1); -} else { - // Electron 打包环境使用 userData 目录,开发环境使用项目根目录 - let envDir: string; - if (isElectron) { - const { app } = require("electron"); - envDir = path.join(app.getPath("userData"), "env"); - } else { - envDir = path.resolve("env"); - } - const envFilePath = path.join(envDir, `.env.${env}`); - - // 自动创建 env 目录和文件(.gitignore 可能忽略了这些文件) - if (!existsSync(envDir)) { - mkdirSync(envDir, { recursive: true }); - } - if (!existsSync(envFilePath)) { - const content = defaultEnvValues[env] ?? defaultEnvValues.prod; - writeFileSync(envFilePath, content, "utf8"); - console.log(`[环境变量] 自动创建 ${envFilePath}`); - } - - const text = readFileSync(envFilePath, "utf8"); - for (const line of text.split("\n")) { - const idx = line.indexOf("="); - if (idx > 0) process.env[line.slice(0, idx).trim()] = line.slice(idx + 1).trim(); - } - console.log(`[环境变量] ${env}`); + if (isElectron) process.env.NODE_ENV = "prod"; + else process.env.NODE_ENV = "dev"; + console.log(`[环境变量:${process.env.NODE_ENV}]`); } diff --git a/src/routes/production/exportImage.ts b/src/routes/production/exportImage.ts index 8789b0f..71a5e99 100644 --- a/src/routes/production/exportImage.ts +++ b/src/routes/production/exportImage.ts @@ -33,19 +33,8 @@ export default router.post( const absPath = path.join(getPath("oss"), item.filePath!); zipStream.addEntry(absPath, { relativePath: `${index}.${ext}` }); }); - - const fileName = `分镜.zip`; - const zipFilePath = getPath(["oss", "temp", fileName]); - await new Promise((resolve, reject) => { - const fs = require("fs"); - fs.mkdirSync(getPath(["oss", "temp"]), { recursive: true }); - const writeStream = fs.createWriteStream(zipFilePath); - zipStream.pipe(writeStream); - writeStream.on("finish", resolve); - writeStream.on("error", reject); - }); - - const downloadUrl = `${process.env.OSSURL || "http://127.0.0.1:10588/"}temp/${fileName}`; - res.json(success({ url: downloadUrl })); + res.setHeader("Content-Type", "application/zip"); + res.setHeader("Content-Disposition", "attachment; filename=export.zip"); + zipStream.pipe(res); }, ); diff --git a/src/utils/oss.ts b/src/utils/oss.ts index 370bac7..a5088f5 100644 --- a/src/utils/oss.ts +++ b/src/utils/oss.ts @@ -50,7 +50,7 @@ class OSS { await this.ensureInit(); const safePath = normalizeUserPath(userRelPath); // URL 始终使用 /,所以这里需要将系统分隔符转回 / - let url = `${process.env.OSSURL}${prefix}/` || `http://127.0.0.1:10588/${prefix}/`; + let url = `http://127.0.0.1:10588/${prefix}/`; if (isEletron()) url = `http://localhost:${process.env.PORT}/${prefix}/`; return `${url}${safePath.split(path.sep).join("/")}`; }