用 electron-builder 打包成 Windows exe
摘要: 加上 electron-builder 設定,`npm run dist` 產出 NSIS 安裝檔與免安裝版,並修正打包後才會出現的路徑問題。 根本原因: 原本三處路徑都假設程式是攤在資料夾裡跑的,打包成 asar 後會壞: 1. hook 腳本路徑會落在 app.asar 內,而 Claude Code 是用 `node <路徑>` 執行它,node 讀不到 asar 裡的檔案,hook 會直接失敗。 2. pets 資料夾同樣在 asar 內,使用者無法自己丟寵物進去,選單「開啟寵物資料夾」也開不起來。 3. 開機自動啟動寫入的是 `electron.exe + 專案路徑`,打包後沒有 node_modules,登錄值會指向不存在的檔案。 影響: 沒有可散佈的執行檔;直接打包的話 hook、換寵物、開機啟動三個功能都會壞掉。 修法: - 新增 lib/paths.js:unpacked() 把 app.asar 換成 app.asar.unpacked,isPackaged() 以 process.defaultApp 判斷是否為打包版。 - build.asarUnpack 把 hook/ 與 pets/ 解到 asar 外,hooks-installer 與 main.js 的 PETS_DIR 都改走 unpacked()。 - autostart 打包後改用 process.execPath,並跳過開發版才需要的 electron.exe 存在檢查。 - NSIS 設為 per-user、可改安裝路徑、建立桌面與開始選單捷徑,解除安裝不刪 ~/.claude-pet。 - 新增 build/icon.ico(16–256,七種尺寸),取自小念 spritesheet 第 0 列第 6 欄的頭肩方形裁切。 - dist/ 加入 .gitignore;README 補上「打包成 exe」一節與安裝檔的使用方式。 驗證: 以 ELECTRON_RUN_AS_NODE 執行打包後的 exe(不開視窗)載入打包內的 lib,確認 isPackaged 為 true、hook 路徑指到 app.asar.unpacked 且檔案存在、autostart 指到 exe 本身、pets 掃得到 xiao-nian、renderer 讀得到 asar 內的 index.html;另確認 exe 版本資訊與七種尺寸圖示皆已嵌入。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+4
-1
@@ -3,6 +3,7 @@
|
||||
const path = require("node:path");
|
||||
const fs = require("node:fs");
|
||||
const { spawnSync } = require("node:child_process");
|
||||
const { isPackaged } = require("./paths");
|
||||
|
||||
const APP_DIR = path.resolve(__dirname, "..");
|
||||
const RUN_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
|
||||
@@ -13,6 +14,8 @@ function electronExe() {
|
||||
}
|
||||
|
||||
function command() {
|
||||
// 打包後就是那顆 exe 本身;開發時要用 node_modules 裡的 electron 帶上專案路徑
|
||||
if (isPackaged()) return `"${process.execPath}"`;
|
||||
return `"${electronExe()}" "${APP_DIR}"`;
|
||||
}
|
||||
|
||||
@@ -35,7 +38,7 @@ function currentValue() {
|
||||
|
||||
function install() {
|
||||
if (process.platform !== "win32") throw new Error("只支援 Windows");
|
||||
if (!fs.existsSync(electronExe())) throw new Error(`找不到 Electron:${electronExe()}(先執行 npm install)`);
|
||||
if (!isPackaged() && !fs.existsSync(electronExe())) throw new Error(`找不到 Electron:${electronExe()}(先執行 npm install)`);
|
||||
const r = reg(["add", RUN_KEY, "/v", VALUE_NAME, "/t", "REG_SZ", "/d", command(), "/f"]);
|
||||
if (!r.ok) throw new Error(r.stderr || r.stdout || "reg add 失敗");
|
||||
return command();
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
const fs = require("node:fs");
|
||||
const os = require("node:os");
|
||||
const path = require("node:path");
|
||||
const { unpacked } = require("./paths");
|
||||
|
||||
const APP_DIR = path.resolve(__dirname, "..");
|
||||
const CLAUDE_DIR = path.join(os.homedir(), ".claude");
|
||||
const SETTINGS_PATH = path.join(CLAUDE_DIR, "settings.json");
|
||||
const BACKUP_DIR = path.join(CLAUDE_DIR, "backups");
|
||||
const HOOK_SCRIPT = path.join(APP_DIR, "hook", "claude-pet-hook.js");
|
||||
// node 執行不了 asar 內的檔案,打包後要指到解開的那份
|
||||
const HOOK_SCRIPT = unpacked(path.join(APP_DIR, "hook", "claude-pet-hook.js"));
|
||||
const MARKER = "claude-pet-hook";
|
||||
|
||||
// Claude Code 2.1.x 支援的 hook 事件(對應到寵物狀態)
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
"use strict";
|
||||
// 開發時直接跑資料夾;打包後程式碼會在 resources/app.asar 裡面。
|
||||
// 有些東西不能待在 asar 內:
|
||||
// - hook/claude-pet-hook.js:Claude Code 會用 `node <路徑>` 執行它,node 讀不到 asar 內的檔案
|
||||
// - pets/:使用者要能自己丟寵物進去,選單也要能開這個資料夾
|
||||
// 這兩個在 build 設定裡以 asarUnpack 解開到 resources/app.asar.unpacked,路徑要跟著換過去。
|
||||
const path = require("node:path");
|
||||
|
||||
const PACKED = `${path.sep}app.asar${path.sep}`;
|
||||
const UNPACKED = `${path.sep}app.asar.unpacked${path.sep}`;
|
||||
|
||||
function unpacked(p) {
|
||||
return p.includes(PACKED) ? p.replace(PACKED, UNPACKED) : p;
|
||||
}
|
||||
|
||||
// 在 Electron 裡跑,而且不是 `electron .` 的開發模式(開發模式 process.defaultApp 為 true)
|
||||
function isPackaged() {
|
||||
return Boolean(process.versions.electron) && !process.defaultApp;
|
||||
}
|
||||
|
||||
module.exports = { unpacked, isPackaged };
|
||||
Reference in New Issue
Block a user