From cb68af5a0f1d3aeb14aafbc13900a77efec5a941 Mon Sep 17 00:00:00 2001 From: yu <191972707@qq.com> Date: Tue, 27 Oct 2026 16:43:28 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=8F=90=E5=8D=87Windows=E5=88=86?= =?UTF-8?q?=E5=8F=91=E5=8C=85=E5=90=AF=E5=8A=A8=E5=85=BC=E5=AE=B9=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/package.mjs | 54 +++++++++++++++++++++++++++++++++++++++++---- src/main/index.ts | 29 +++++++++++++++++++++++- src/main/logger.ts | 14 ++++++++++++ 3 files changed, 92 insertions(+), 5 deletions(-) create mode 100644 src/main/logger.ts diff --git a/scripts/package.mjs b/scripts/package.mjs index 546269c..98081cb 100644 --- a/scripts/package.mjs +++ b/scripts/package.mjs @@ -13,6 +13,8 @@ const releaseRoot = path.resolve(root, "release"); const productFolderName = `RPA-Studio-${version}-win-x64`; const productFolder = path.join(releaseRoot, productFolderName); const zipPath = path.join(releaseRoot, `${productFolderName}.zip`); +const setupPath = path.join(releaseRoot, `RPA-Studio-Setup-${version}-win-x64.exe`); +const setupArchive = path.join(releaseRoot, `${productFolderName}.7z`); function assertInsideRelease(target) { const relative = path.relative(releaseRoot, path.resolve(target)); @@ -27,7 +29,7 @@ function run(command, args) { } fs.mkdirSync(releaseRoot, { recursive: true }); -for (const target of [productFolder, zipPath]) { +for (const target of [productFolder, zipPath, setupPath, setupArchive]) { assertInsideRelease(target); fs.rmSync(target, { recursive: true, force: true }); } @@ -97,17 +99,61 @@ fs.writeFileSync(path.join(productFolder, "使用说明.txt"), [ "3. 任务、流程和运行记录默认保存在当前 Windows 用户的 AppData\\Roaming\\rpa-studio。", "4. Word 自动化需要本机安装 Microsoft Word;其他文件级能力无需安装 Office。", "5. 首次启动如被 Windows SmartScreen 提示,请核对同目录 SHA256SUMS.txt;当前构建未进行商业代码签名。", + "6. 如果双击没有窗口,请运行 启动诊断.cmd,并把自动打开的 startup.log 发给开发人员。", "", `版本:${version}`, `源码提交:${revision}`, `构建时间:${builtAt}` ].join("\r\n"), "utf8"); +fs.writeFileSync(path.join(productFolder, "系统要求.txt"), [ + "RPA Studio 系统要求", + "", + "- Windows 10 64 位或 Windows 11 64 位", + "- 建议至少 4 GB 内存和 500 MB 可用磁盘空间", + "- 必须先完整解压,不能直接在 ZIP 压缩包中双击 exe", + "- Word 自动化需要本机安装 Microsoft Word", + "- 当前版本未购买商业代码签名证书,SmartScreen 可能显示未知发布者", + "- 软件默认关闭 GPU 硬件加速,以兼容远程桌面、虚拟机和旧显卡驱动" +].join("\r\n"), "utf8"); +fs.writeFileSync(path.join(productFolder, "启动诊断.cmd"), [ + "@echo off", + "chcp 65001 >nul", + "set ELECTRON_ENABLE_LOGGING=1", + "set LOG=%APPDATA%\\rpa-studio\\logs\\startup.log", + "start \"RPA Studio\" \"%~dp0RPA Studio.exe\" --enable-logging --v=1", + "timeout /t 8 /nobreak >nul", + "if exist \"%LOG%\" (start notepad.exe \"%LOG%\") else (echo 未生成启动日志,请检查安全软件拦截记录。 & pause)" +].join("\r\n"), "utf8"); +fs.writeFileSync(path.join(productFolder, "安装到本机.cmd"), [ + "@echo off", + "chcp 65001 >nul", + "set \"TARGET=%LOCALAPPDATA%\\Programs\\RPA Studio\"", + "if not exist \"%TARGET%\" mkdir \"%TARGET%\"", + "robocopy \"%~dp0\" \"%TARGET%\" /E /R:2 /W:1 /NFL /NDL /NJH /NJS /NP >nul", + "powershell -NoProfile -ExecutionPolicy Bypass -Command \"$w=New-Object -ComObject WScript.Shell;$s=$w.CreateShortcut([IO.Path]::Combine([Environment]::GetFolderPath('Desktop'),'RPA Studio.lnk'));$s.TargetPath=[IO.Path]::Combine($env:LOCALAPPDATA,'Programs','RPA Studio','RPA Studio.exe');$s.WorkingDirectory=[IO.Path]::Combine($env:LOCALAPPDATA,'Programs','RPA Studio');$s.Save()\"", + "start \"RPA Studio\" \"%TARGET%\\RPA Studio.exe\"", + "exit /b 0" +].join("\r\n"), "utf8"); console.log("[package] zip"); const zip = new AdmZip(); zip.addLocalFolder(productFolder, productFolderName); zip.writeZip(zipPath); -const sha256 = createHash("sha256").update(fs.readFileSync(zipPath)).digest("hex"); -fs.writeFileSync(path.join(releaseRoot, "SHA256SUMS.txt"), `${sha256} ${path.basename(zipPath)}\r\n`, "utf8"); +const zipSha256 = createHash("sha256").update(fs.readFileSync(zipPath)).digest("hex"); -console.log(JSON.stringify({ productFolder, zipPath, sha256 }, null, 2)); +let setupSha256 = ""; +const sevenZip = "C:/Program Files/7-Zip/7z.exe"; +const sfxModule = "C:/Program Files/7-Zip/7z.sfx"; +if (fs.existsSync(sevenZip) && fs.existsSync(sfxModule)) { + console.log("[package] setup sfx"); + const archived = spawnSync(sevenZip, ["a", "-t7z", setupArchive, ".", "-mx=9"], { cwd: productFolder, stdio: "inherit" }); + if (archived.status !== 0) throw new Error(`7-Zip 打包失败,退出码 ${archived.status}`); + const config = Buffer.from(`;!@Install@!UTF-8!\nTitle=\"RPA Studio ${version}\"\nBeginPrompt=\"将 RPA Studio 安装到当前用户并创建桌面快捷方式?\"\nRunProgram=\"安装到本机.cmd\"\nGUIMode=\"1\"\n;!@InstallEnd@!\n`, "utf8"); + fs.writeFileSync(setupPath, Buffer.concat([fs.readFileSync(sfxModule), config, fs.readFileSync(setupArchive)])); + setupSha256 = createHash("sha256").update(fs.readFileSync(setupPath)).digest("hex"); +} +const sums = [`${zipSha256} ${path.basename(zipPath)}`]; +if (setupSha256) sums.push(`${setupSha256} ${path.basename(setupPath)}`); +fs.writeFileSync(path.join(releaseRoot, "SHA256SUMS.txt"), `${sums.join("\r\n")}\r\n`, "utf8"); + +console.log(JSON.stringify({ productFolder, zipPath, setupPath: setupSha256 ? setupPath : null, zipSha256, setupSha256: setupSha256 || null }, null, 2)); diff --git a/src/main/index.ts b/src/main/index.ts index f80fb10..c033775 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -7,11 +7,17 @@ import { createDemoFlow, createExampleFlows, createFlow, deleteFlow, getFlow, li import { capabilityCount, capabilityIds } from "./engine/registry"; import { closeBrowserSession, startRun, stopRun } from "./run/manager"; import { fixtureUrl, startFixtureServer, stopFixtureServer } from "./fixture/server"; +import { logMain } from "./logger"; let mainWindow: BrowserWindow | null = null; +if (process.env.RPA_ENABLE_GPU !== "1") app.disableHardwareAcceleration(); +process.on("uncaughtException", (error) => logMain("uncaughtException", error.stack || error.message)); +process.on("unhandledRejection", (reason) => logMain("unhandledRejection", reason instanceof Error ? reason.stack || reason.message : String(reason))); + function createWindow(): void { const e2e = process.env.RPA_E2E === "1"; + logMain("createWindow", { packaged: app.isPackaged, appPath: app.getAppPath(), userData: app.getPath("userData") }); mainWindow = new BrowserWindow({ width: 1440, height: 900, @@ -34,7 +40,18 @@ function createWindow(): void { } }); - mainWindow.on("ready-to-show", () => { if (!e2e) mainWindow?.show(); }); + let shown = false; + const showWindow = (source: string) => { + if (e2e || shown || !mainWindow || mainWindow.isDestroyed()) return; + shown = true; + logMain("showWindow", source); + mainWindow.show(); + mainWindow.focus(); + }; + const showFallback = setTimeout(() => showWindow("fallback-timeout"), 5000); + mainWindow.on("ready-to-show", () => { clearTimeout(showFallback); showWindow("ready-to-show"); }); + mainWindow.on("unresponsive", () => logMain("window-unresponsive")); + mainWindow.on("responsive", () => logMain("window-responsive")); mainWindow.on("closed", () => { mainWindow = null; }); @@ -42,6 +59,14 @@ function createWindow(): void { if (/^https?:/i.test(url)) void shell.openExternal(url); return { action: "deny" }; }); + mainWindow.webContents.on("did-fail-load", (_event, code, description, url) => { + logMain("did-fail-load", { code, description, url }); + showWindow("did-fail-load"); + }); + mainWindow.webContents.on("render-process-gone", (_event, details) => logMain("render-process-gone", details)); + mainWindow.webContents.on("console-message", (_event, level, message, line, sourceId) => { + if (level >= 2) logMain("renderer-console", { level, message, line, sourceId }); + }); const devUrl = process.env.ELECTRON_RENDERER_URL; if (devUrl) { @@ -121,10 +146,12 @@ if (!gotLock) { }); app.whenReady().then(async () => { + logMain("app-ready", { electron: process.versions.electron, chrome: process.versions.chrome, node: process.versions.node }); try { await startFixtureServer(Number(process.env.RPA_FIXTURE_PORT || 17863)); } catch (error) { console.warn("local fixture server unavailable", error); + logMain("fixture-server-failed", error instanceof Error ? error.stack || error.message : String(error)); } registerIpc(); createWindow(); diff --git a/src/main/logger.ts b/src/main/logger.ts new file mode 100644 index 0000000..30ec7c4 --- /dev/null +++ b/src/main/logger.ts @@ -0,0 +1,14 @@ +import { app } from "electron"; +import fs from "node:fs"; +import path from "node:path"; + +export function logMain(event: string, detail: unknown = ""): void { + try { + const directory = path.join(app.getPath("userData"), "logs"); + fs.mkdirSync(directory, { recursive: true }); + const content = typeof detail === "string" ? detail : JSON.stringify(detail); + fs.appendFileSync(path.join(directory, "startup.log"), `${new Date().toISOString()} [${event}] ${content}\r\n`, "utf8"); + } catch { + // Logging must never prevent the app from starting. + } +}