Fix v1.60.0 Windows release feed handling

This commit is contained in:
coso
2026-06-08 08:20:00 +08:00
parent f58bd048b1
commit 0e2f8e6378
12 changed files with 127 additions and 343 deletions
+20 -1
View File
@@ -173,6 +173,23 @@ function windowsSigningOptions({
};
}
function windowsSquirrelRemoteReleasesUrl({ env = process.env } = {}) {
const remoteReleasesUrl =
env.LIME_WINDOWS_SQUIRREL_REMOTE_RELEASES_URL?.trim();
if (!remoteReleasesUrl) {
return undefined;
}
return remoteReleasesUrl.replace(/\/+$/, "");
}
function windowsSquirrelRemoteReleasesOptions(options = {}) {
const remoteReleases = windowsSquirrelRemoteReleasesUrl(options);
if (!remoteReleases) {
return {};
}
return { remoteReleases };
}
function squirrelConfig(arch = process.arch, options = {}) {
const packageVersion = options.packageVersion || PACKAGE_VERSION;
return {
@@ -180,9 +197,9 @@ function squirrelConfig(arch = process.arch, options = {}) {
exe: `${PRODUCT_NAME}.exe`,
name: SQUIRREL_PACKAGE_NAME,
noMsi: true,
remoteReleases: updateFeedUrl("win32", arch, options),
setupExe: `${PRODUCT_NAME}-${packageVersion} Setup.exe`,
setupIcon: "lime-rs/icons/icon.ico",
...windowsSquirrelRemoteReleasesOptions(options),
...windowsSigningOptions(options),
};
}
@@ -197,6 +214,8 @@ export {
updateFeedLabel,
updateFeedUrl,
windowsSigningOptions,
windowsSquirrelRemoteReleasesOptions,
windowsSquirrelRemoteReleasesUrl,
};
export default {
@@ -1656,3 +1656,10 @@ git diff --check
640. P3.298 验证 / 边界:`node --check "scripts/check-app-server-client-contract.mjs"` 通过;`node "scripts/check-app-server-client-contract.mjs"` 通过,App Server client contract 扩为 `210 checks``npx prettier --check "scripts/check-app-server-client-contract.mjs" "internal/exec-plans/app-server-implementation-plan.md" "internal/roadmap/appserver/frontend-integration-matrix.md"` 通过;本轮认领文件 `git diff --check` 通过。完整 `npm run test:contracts` 已执行,`check:agent-runtime-clients`、App Server client contract、command contracts、harness contracts、modality contracts、scripts governance 均通过,但在并行 Forge/release 写集 `scripts/electron/release-workflow-guard.mjs``listRepositoryFiles(...)` 处失败:它递归扫描本地产物 / symlink(可见 `.tmp/electron-forge-local-feed/Lime-darwin-arm64`)后触发 `RangeError: Maximum call stack size exceeded`。本轮按并行协作规则不改 release guard;已单独补跑后续 `npm run harness:cleanup-report:check``npm run docs:boundary`,均通过。该刀只补 current 边界守卫,不新增命令、不改生产前端实现、不调用正式模型、不联网,也不替代真实 Electron GUI smoke 或 live Provider / WebSearch-WebFetch 联网 E2E。完整整体目标仍缺用户明确授权后的 `npm run smoke:claw-chat-ready-streaming -- --allow-live-provider --timeout-ms 180000` 实跑证据。
641. P3.299 Claw 停止后继续输出 Electron 回归固化:针对“无法停止 / 一直停止不了 / 停止后无法继续输出”的主线风险,本轮只认领 `scripts/agent-runtime/current-fixture-regression-smoke.mjs``scripts/agent-runtime/current-fixture-regression-smoke.test.mjs``scripts/agent-runtime/claw-chat-current-fixture-smoke.mjs``scripts/agent-runtime/claw-chat-current-fixture-smoke.test.mjs``scripts/README.md``internal/aiprompts/quality-workflow.md``.codex/skills/lime-product-e2e-loop/SKILL.md``.codex/skills/lime-product-e2e-loop/references/product-e2e-loop.md` 与本计划 / 矩阵记录,不触碰并行 Forge、Agent Apps UI、RuntimeCore、Electron Host、`lime-rs/crates/app-server/src/local_data_source.rs``lime-rs/tauri.windows.conf.json``npm run smoke:agent-runtime-current-fixture` 现在在原 Vitest guard 后真实启动 `scripts/agent-runtime/claw-chat-current-fixture-smoke.mjs --scenario cancel-then-continue`,用 Electron preload bridge + App Server JSON-RPC + local external fixture backend 证明同一 current session 先取消“整理今天的国际新闻”,再从 GUI 输入“继续输出”,后端收到第二个 `turnStart`GUI 和 read model 完成第二轮。该入口继续强制 `LIME_ALLOW_LIVE_PROVIDER_SMOKE=0` / `LIME_REAL_API_TEST=0`,不调用正式模型、不联网、不使用 App Server mock backend、renderer mock fallback、legacy `agent_runtime_*``mockPriorityCommands``defaultMocks``invokeMockOnly`
642. P3.299 验证 / 边界:定向验证通过:`node --check "scripts/agent-runtime/current-fixture-regression-smoke.mjs" "scripts/agent-runtime/claw-chat-current-fixture-smoke.mjs"``npx vitest run "scripts/agent-runtime/current-fixture-regression-smoke.test.mjs" "scripts/agent-runtime/claw-chat-current-fixture-smoke.test.mjs" --silent=passed-only --disableConsoleIntercept`2 文件 13 项;`npx prettier --check` 覆盖本轮认领脚本、skill/reference、quality workflow 与 scripts README`git diff --check` 覆盖同一批认领文件。真实 Electron 单场景 `npm run smoke:claw-chat-current-fixture -- --scenario cancel-then-continue --prefix claw-chat-current-fixture-cancel-then-continue-regression --timeout-ms 180000` 通过,summary 位于 `.lime/qc/gui-evidence/claw-chat-current-fixture/claw-chat-current-fixture-cancel-then-continue-regression-summary.json`session `claw-chat-current-1780875621183-11921``commonAssertions` 全 true`scenarioAssertions` 全 truebackend ledger 显示 `turnStartCount=2` / `turnCancelCount=1``readModelCanceled.latestTurnStatus=canceled``readModelContinueCompleted.latestTurnStatus=completed`。聚合 `npm run smoke:agent-runtime-current-fixture` 通过,覆盖 history/cache 66 项、stream terminal 18 项、Claw MessageList 3 项、Electron/App Server fixture guard 17 项,并实际运行 `Claw 停止后同会话继续输出 Electron fixture`。该证据证明 non-live current Electron 主链的停止 / 继续输出闭环已固化为默认回归;完整整体目标仍缺用户明确授权后的 live Provider streaming / WebSearch-WebFetch 实际联网 E2E。
643. P3.300 current contract / fixture / GUI smoke 复验收口:在 P3.298 与 P3.299 后继续只做证据收口,本轮只认领本计划与前端矩阵记录,不修改生产代码,不触碰并行 `lime-rs/src/commands/update_cmd.rs``lime-rs/crates/app-server/src/local_data_source.rs`、RuntimeCore、Electron Host、Agent Apps UI 或 Forge / release 写集。复验目标固定为当前正确架构 `Frontend -> Electron Desktop Host IPC -> App Server JSON-RPC -> RuntimeCore/backend`contract 证明命令 / mock / release guard 未漂移,current fixture 证明 Claw 停止后同会话继续输出已进入默认非 live Electron 回归,GUI smoke 证明默认 Electron Desktop Host + App Server sidecar + Claw shell/composer ready 仍可启动。
644. P3.300 验证 / 边界:`npm run test:contracts` 已完整通过,覆盖 `check:agent-runtime-clients`、App Server client contract `210 checks`、frontend commands `388`、Electron host commands `107`、mock priority commands `0`、DevBridge truth commands `148`、Electron release workflow guard、Harness cleanup 与 docs boundary;此前 P3.298 中 release workflow guard 因本地产物 / symlink 递归触发的 `RangeError: Maximum call stack size exceeded` 已由并行写集修复,本轮不再改 release guard。`npm run smoke:agent-runtime-current-fixture` 已通过,覆盖 history/cache 66 项、stream terminal 18 项、Claw MessageList 3 项、Electron/App Server fixture guard 17 项,并实际执行 `Claw 停止后同会话继续输出 Electron fixture`;最新 summary 位于 `.lime/qc/gui-evidence/claw-chat-current-fixture/claw-chat-current-fixture-cancel-then-continue-regression-summary.json`session `claw-chat-current-1780875900810-29926``liveProviderUsed=false``npm run verify:gui-smoke` 已通过,覆盖 renderer smoke build、Electron host typecheck / main / preload build、App Server sidecar asset prepare 与真实 Electron smoke,输出确认 `app-server initialized protocol=appserver.v0 version=1.60.0``claw workbench shell ready`。该刀证明 non-live current 主链、契约守卫和默认 GUI 壳在当前并行工作树下可用;仍不调用正式模型、不联网、不使用 App Server mock backend、renderer mock fallback、`mockPriorityCommands``defaultMocks``invokeMockOnly`,也不替代用户明确授权后的 live Provider streaming / WebSearch-WebFetch 联网 E2E。
645. P3.301 对话详情 / 归档 current Electron 复验:针对前面“对话还有归档都无法获取”的主线风险,本轮继续只做验证和证据记录,不修改生产代码,不触碰并行 `lime-rs/src/commands/update_cmd.rs``lime-rs/crates/app-server/src/local_data_source.rs`、RuntimeCore、Electron Host、Agent Apps UI 或 release 写集。`npm run smoke:agent-session-history-electron-fixture -- --timeout-ms 180000` 已通过,真实 Electron preload `app_server_handle_json_lines` 覆盖内存 session fail-closed、current session `agentSession/start/read/update/list`、persisted current timeline session 归档 / 恢复、sidecar restart readback 与侧栏 GUI 归档 / 恢复入口;summary 位于 `.lime/qc/gui-evidence/agent-session-history-electron-fixture/agent-session-history-electron-fixture-summary.json`,显示 `ok=true``electronPreloadBridge=true``sidecarRestartReadback=true``sidebarGuiArchive=true``consoleErrors=[]`
646. P3.301 边界 / 剩余缺口:本轮 fixture 明确 required methods 为 `initialize``agentSession/start``agentSession/read``agentSession/update``agentSession/list`forbidden method 为 `agentSession/turn/start`,并在 archive 阶段证明内存 session 归档会 fail closedpersisted session 归档 / 恢复可跨 sidecar 重启读回。该证据证明最近对话详情 hydrate、归档列表读取和恢复入口仍经 `Frontend -> Electron Desktop Host IPC -> App Server JSON-RPC -> RuntimeCore / LocalAppDataSource` current 主链打通;它不触发对话 turn、不调用模型后端、不使用 external backend、App Server mock backend、renderer mock fallback 或 legacy `agent_runtime_*`,也不替代 live Provider streaming / WebSearch-WebFetch 联网 E2E。
647. P3.302 当前脏工作树 contract / GUI / Agent Runtime 聚合复验:在并行 Forge / release / updater / i18n 写集继续变化的状态下,本轮只认领本计划与前端矩阵记录,不接管 `forge.config.mjs``scripts/electron/*``internal/roadmap/appserver/release-updater.md``internal/roadmap/appserver/testing-migration.md``internal/roadmap/i18n/*``lime-rs/src/commands/update_cmd.rs``lime-rs/crates/app-server/src/local_data_source.rs`。复验目标是证明当前脏工作树没有把命令契约、生产 mock 禁回流、默认 Electron Desktop Host + App Server sidecar 或 Claw 停止后继续输出回归打断。
648. P3.302 验证 / 边界:`npm run test:contracts` 通过,App Server client contract 仍为 `210 checks`frontend commands `388`Electron host commands `107`mock priority commands `0`DevBridge truth commands `148`,并通过 scripts governance、Electron release workflow guard、Harness cleanup 与 docs boundary。`npm run verify:gui-smoke` 通过,覆盖版本一致性 `1.60.0`、renderer smoke build、`packages/app-server-client` build、Electron host typecheck / main / preload build、desktop assets、App Server sidecar asset prepare 与真实 Electron smoke,输出确认 `renderer loaded``app-server initialized protocol=appserver.v0 version=1.60.0``claw workbench shell ready``npm run smoke:agent-runtime-current-fixture` 通过,覆盖 history/cache 66 项、stream terminal 18 项、Claw MessageList 3 项、Electron/App Server fixture guard 17 项,并实际运行 `Claw 停止后同会话继续输出 Electron fixture`;本轮最新 session 为 `claw-chat-current-1780877737248-88730``liveProviderUsed=false`。该刀证明当前非 live current 主链在并行脏工作树下仍可用;仍不调用正式模型、不联网、不使用 App Server mock backend、renderer mock fallback、`mockPriorityCommands``defaultMocks``invokeMockOnly`,也不替代用户明确授权后的 live Provider streaming / WebSearch-WebFetch 联网 E2E。
649. P3.303 live Provider 未授权 gate 复验:继续沿完整目标中最后的 live Provider / WebSearch-WebFetch 缺口推进,但本轮没有用户明确授权调用正式模型或联网工具,因此只复验 fail-closed 安全边界。`npm run smoke:claw-chat-ready-streaming -- --timeout-ms 30000` 按预期以退出码 1 停在授权检查,提示必须显式传入 `--allow-live-provider`,或设置 `LIME_ALLOW_LIVE_PROVIDER_SMOKE=1 / LIME_REAL_API_TEST=1`;该运行没有进入 DevBridge、Provider、`agentSession/turn/start`、WebSearch 或 WebFetch 阶段。该证据证明未授权时不会把真实 Provider / 联网工具当作默认回归门槛偷偷运行;它不是 live E2E 通过证据,完整目标仍缺用户明确授权后的 `npm run smoke:claw-chat-ready-streaming -- --allow-live-provider --timeout-ms 180000` 实跑。
@@ -204,3 +204,7 @@ Electron main 是 Desktop Host bridge,不是后端适配层,也不是第二
70. `P3.297``claw-chat-ready-streaming` 授权后的 live WebSearch/WebFetch read-after-event 证据已收紧为 strict session / turn scope。`appServerSessionReadAfterEventEvidence(...)` 新增 `strictEventScope` 选项,live Web turn 固定传 `{ strictEventScope: true }`;因此 `liveWebSessionReadAfterEventSeen` 只能由明确匹配本次 `liveWebSessionId + liveWebTurnId` 的 App Server `agentSession/event` 后续 `agentSession/read` 满足,缺 session / turn scope 的旧事件不能再让 live E2E 误通过。long/follow turn 普通诊断仍保持原宽松兼容口径。该刀只收紧授权后验收标准,真实 live Provider / WebSearch-WebFetch 联网 E2E 仍未运行。
71. `P3.298`:前端 Agent Runtime / Agent App UI runtime 边界已补 contract 守卫。`scripts/check-app-server-client-contract.mjs` 现在递归扫描 `src/lib/api/agentRuntime/**` production `.ts`,禁止生产 mock fallback、direct Provider/backend HTTP、parallel stream transport、Gateway credential 与 App Server mock backend,锁住该目录只能作为前端 thin client gateway / compat projection;同时对 `src/lib/api/agentApps.ts``requestAgentAppUiRuntimeAppServer``startAgentAppUiRuntime``getAgentAppUiRuntimeStatus``stopAgentAppUiRuntime` 做函数体级扫描,要求只经 App Server `agentAppUiRuntime/start|status|stop` current methods,并禁止 safeInvoke legacy facade、`agent_app_runtime_*` task facade、`agentSession/turn/start`、tool / evidence projection、Provider HTTP、Gateway credential 与 mock backend。`node "scripts/check-app-server-client-contract.mjs"` 通过,contract 扩为 `210 checks`;该守卫证明 `agentAppUiRuntime/*` 只管 UI 子进程生命周期,不能变成第二套对话 turn / tool runtime / evidence 后端。
72. `P3.299`:Claw 停止后继续输出回归已纳入 current 聚合 smoke。`npm run smoke:agent-runtime-current-fixture` 现在除 history/cache、stream terminal、Claw 终态 UI 与 Electron fixture guard 外,会真实运行 `scripts/agent-runtime/claw-chat-current-fixture-smoke.mjs --scenario cancel-then-continue`;该 Electron fixture 通过 GUI 发送“整理今天的国际新闻”、点击停止、确认 `agentSession/turn/cancel` / read model `canceled` / 输入框恢复,再在同一 current session 输入“继续输出”并完成第二轮。最新通过 summary 位于 `.lime/qc/gui-evidence/claw-chat-current-fixture/claw-chat-current-fixture-cancel-then-continue-regression-summary.json`,显示 `turnStartCount=2``turnCancelCount=1``readModelCanceled.latestTurnStatus=canceled``readModelContinueCompleted.latestTurnStatus=completed`,所有 common/scenario assertions 为 true。该证据仍是 local external fixture backend,不调用正式模型、不联网,也不替代 live Provider / WebSearch-WebFetch 实际联网 E2E。
73. `P3.300`contract / current fixture / 默认 Electron GUI smoke 已完成同轮复验收口。`npm run test:contracts` 完整通过,App Server client contract 为 `210 checks`frontend commands `388`Electron host commands `107`mock priority commands `0`DevBridge truth commands `148`,并包含 Electron release workflow guard、Harness cleanup 与 docs boundary`npm run smoke:agent-runtime-current-fixture` 通过并实际运行 Claw 停止后同会话继续输出 Electron fixture,最新 summary session `claw-chat-current-1780875900810-29926``liveProviderUsed=false``npm run verify:gui-smoke` 通过,确认 Electron renderer loaded、App Server `protocol=appserver.v0 version=1.60.0` initialized、Claw workbench shell ready。该证据证明当前非 live 主链仍经 `Frontend -> Electron Desktop Host IPC -> App Server JSON-RPC -> RuntimeCore/backend` 打通,且命令契约 / mock 禁回流 / 默认 GUI 壳未漂移;它不发送 live turn、不调用正式 Provider,也不替代 live Provider streaming / WebSearch-WebFetch 实际联网 E2E。
74. `P3.301`:对话详情 / 归档 current Electron 复验已通过。`npm run smoke:agent-session-history-electron-fixture -- --timeout-ms 180000` 使用真实 Electron preload `app_server_handle_json_lines` 覆盖内存 session fail-closed、`agentSession/start/read/update/list`、persisted current timeline 归档 / 恢复、sidecar restart readback 和侧栏 GUI 归档 / 恢复入口;summary 位于 `.lime/qc/gui-evidence/agent-session-history-electron-fixture/agent-session-history-electron-fixture-summary.json`,显示 `ok=true``electronPreloadBridge=true``sidecarRestartReadback=true``sidebarGuiArchive=true``consoleErrors=[]`。该证据直接覆盖“对话 / 归档无法获取”风险,证明最近对话详情 hydrate、归档列表读取和恢复入口仍经 `Frontend -> Electron Desktop Host IPC -> App Server JSON-RPC -> RuntimeCore / LocalAppDataSource` current 主链打通;它不触发 live turn、不调用正式 Provider,也不替代 live Provider streaming / WebSearch-WebFetch 实际联网 E2E。
75. `P3.302`:当前脏工作树下 contract / GUI / Agent Runtime 聚合复验已通过。`npm run test:contracts` 显示 App Server client contract `210 checks`、frontend commands `388`、Electron host commands `107`、mock priority commands `0`、DevBridge truth commands `148`,并通过 scripts governance、Electron release workflow guard、Harness cleanup 与 docs boundary`npm run verify:gui-smoke` 通过,确认 Electron renderer loaded、App Server `protocol=appserver.v0 version=1.60.0` initialized、Claw workbench shell ready`npm run smoke:agent-runtime-current-fixture` 通过并实际运行 Claw 停止后同会话继续输出 Electron fixture,本轮 session `claw-chat-current-1780877737248-88730``liveProviderUsed=false`。该证据证明并行 Forge / updater / release / i18n 写集没有打断当前非 live `Frontend -> Electron Desktop Host IPC -> App Server JSON-RPC -> RuntimeCore/backend` 主链、命令契约或 mock 禁回流;它仍不调用正式 Provider、不联网,也不替代 live Provider streaming / WebSearch-WebFetch 实际联网 E2E。
76. `P3.303`live Provider 未授权 gate 仍 fail-closed。`npm run smoke:claw-chat-ready-streaming -- --timeout-ms 30000` 按预期以退出码 1 停在授权检查,提示必须显式 `--allow-live-provider` 或设置 `LIME_ALLOW_LIVE_PROVIDER_SMOKE=1 / LIME_REAL_API_TEST=1`;该运行没有进入 DevBridge、Provider、`agentSession/turn/start`、WebSearch 或 WebFetch 阶段。该证据只证明未授权时不会偷偷调用正式模型或联网工具,不是 live Provider / WebSearch-WebFetch E2E 通过证据;完整目标仍需用户明确授权后运行 `npm run smoke:claw-chat-ready-streaming -- --allow-live-provider --timeout-ms 180000`
@@ -125,7 +125,7 @@ Windows 发布签名由 release workflow 按“可选但成对”规则启用,
| `WINDOWS_SIGNING_CERTIFICATE_PASSWORD` | `LIME_WINDOWS_SIGNING_CERTIFICATE_PASSWORD` | Squirrel signing certificate 密码 |
| release workflow | `LIME_ELECTRON_SIGN=1` | Windows 构建允许签名;只有两项 Windows secret 都存在时才传入 Squirrel signing config |
Windows 当前通过 `npx electron-forge make --platform win32 --arch x64 --targets squirrel` 生成 Squirrel installer;两项 Windows signing secret 都存在时,`forge.config.mjs``certificateFile` / `certificatePassword` 传给 `@electron-forge/maker-squirrel`。如果两项 secret 都未配置,release workflow 继续生成 unsigned Forge Squirrel installer;如果只配置其中一项,则 fail-fast,避免半配置签名在 make 阶段才失败。
Windows 当前通过 `npx electron-forge make --platform win32 --arch x64 --targets squirrel` 生成 Squirrel installer;两项 Windows signing secret 都存在时,`forge.config.mjs``certificateFile` / `certificatePassword` 传给 `@electron-forge/maker-squirrel`。如果两项 secret 都未配置,release workflow 继续生成 unsigned Forge Squirrel installer;如果只配置其中一项,则 fail-fast,避免半配置签名在 make 阶段才失败。Windows Squirrel 的 `remoteReleases` 同步只允许通过 `LIME_WINDOWS_SQUIRREL_REMOTE_RELEASES_URL` 显式启用,默认 release make 不从 `LIME_ELECTRON_UPDATES_URL` 拉取线上 `RELEASES`,避免首发或空 feed 返回 404 时阻断安装包生成。
macOS ZIP / `RELEASES.json` 的本地确定性验证使用:
@@ -36,8 +36,8 @@
| `current` | `scripts/lib/agent-qc-report-core.test.ts` | 验证 Agent QC package script fixture 中的 `verify:gui-smoke` 指向 `smoke:electron`,不再把旧 `scripts/verify-gui-smoke.mjs` 当 current 证据。 |
| `current` | `scripts/lib/gui-smoke-run-lock.test.mjs` | 验证 GUI smoke run lock owner fixture 使用 `npm run smoke:electron`,不再把旧脚本路径写进 current owner metadata。 |
| `current` | `scripts/verify-gui-smoke.mjs` | 保留旧文件名作为兼容入口,但直接委托 `npm run smoke:electron`,直接执行也只验证 Electron Desktop Host。 |
| `deprecated guard` | `scripts/standalone-deprecated-artifact-adapter-guard.test.mjs` | 验证旧 standalone artifact adapter 默认 blocked,且不回流 current release evidence / GUI 证据。 |
| `deprecated artifact adapter` | `scripts/lib/agent-app-standalone-native-*.test.mjs` | 只允许证明 standalone artifact adapter fail-closed,不作为 current 发布链证据。 |
| `deprecated guard` | `scripts/standalone-deprecated-artifact-adapter-guard.test.mjs` | 验证旧 standalone artifact adapter 默认 blocked,且不回流 current release evidence / GUI 证据。 |
| `deprecated artifact adapter` | `scripts/lib/agent-app-standalone-native-*.test.mjs` | 只允许证明 standalone artifact adapter fail-closed,不作为 current 发布链证据。 |
| `dead guard` | `scripts/electron/current-entrypoints.test.mjs` | 验证 root Tauri GUI app entry files 已删除,不能重新作为 Desktop current 或 deprecated runner 入口回流。 |
## 3. 迁移规则
@@ -405,6 +405,6 @@ git diff --check -- "scripts/electron/current-entrypoints.test.mjs" "internal/ro
这组证据证明:
1. `lime-rs/tauri.windows.conf.json` 只允许作为 `dead cleanup candidate` 暂存,不能回流 package scripts、Electron Forge、CI、Electron host版本一致性检查。
1. `lime-rs/tauri.windows.conf.json` 已按 `dead` Windows 旧宿主配置删除,不能回流 package scripts、Electron Forge、CI、Electron host版本一致性检查或 i18n app metadata evidence
2. `scripts/electron/current-entrypoints.test.mjs` 已把该 Windows 旧宿主配置加入 retired build input 禁止词,并扫描 Electron current build / release / CI / host 文件。
3. 物理删除该文件属于文件删除动作,需要按危险操作确认后再执行;删除后同一守卫应继续通过
3. `scripts/electron/release-workflow-guard.mjs` 同步阻止旧 Tauri / NSIS 配置文件实体回流;后续只允许 Forge Squirrel 与 `RELEASES` / `.nupkg` / Setup 作为 Windows current release surface
@@ -20,7 +20,7 @@
- `internal/roadmap/i18n/app-metadata-translation-scope.json` 已定义 installer / app metadata 的最小 ownership 与字段分类:`forge.config.mjs#productName``appId` 与 deep-link scheme 属于稳定品牌 / 标识字段,`package.json#description` 属于多语言发布前需要处理的 translatable 字段。
- `internal/roadmap/i18n/evidence/app-metadata-locale-build-manifest.json` 已把 scope 转成 build-time locale manifest;当前 `workflowStatus=ready`10 个 metadata entry 中 1 个 localized entry、3 个 stable entry、6 个 source-only entrymissing field 与 required localized missing 均为 `0`
- `internal/roadmap/i18n/evidence/app-metadata-workflow-inventory.json` 已增加 metadata field coverage 与 manifest readiness:当前审计到 `10` 个真实 app / installer metadata 字段,全部纳入 scope`metadataUnscopedFieldCount=0``metadataMissingScopedFieldCount=0`,且 `appMetadataLocaleBuildManifestReady=true``hasInstallerLocalizationWorkflow=true`
- `lime-rs/` 下仍被 git 跟踪的旧宿主配置文件只允许作为 deprecated cleanup candidate 讨论;它们不是 current app metadata、installer、release、updater、签名或版本同步事实源。
- 旧 Tauri 宿主配置文件已按 `dead` release / metadata surface 下线;它们不是 current app metadata、installer、release、updater、签名或版本同步事实源,也不能作为 i18n evidence 输入回流
## 结论
@@ -280,7 +280,7 @@
本轮完成:
- 新增 `scripts/i18n/i18n-app-metadata-workflow-report.ts` 与测试 `scripts/i18n/i18n-app-metadata-workflow-report.test.ts`,把 `package.json``lime-rs/Cargo.toml``lime-rs/tauri.conf.json``lime-rs/tauri.conf.headless.json``lime-rs/capabilities/agent-app-shell.json` 的 app / installer 元数据事实源做成可重复 inventory 报告。
- 新增 `scripts/i18n/i18n-app-metadata-workflow-report.ts` 与测试 `scripts/i18n/i18n-app-metadata-workflow-report.test.ts`,把 `package.json``lime-rs/Cargo.toml``forge.config.mjs``lime-rs/capabilities/agent-app-shell.json` 的 app / installer 元数据事实源做成可重复 inventory 报告。
- 通过 `npm run i18n:app-metadata-report -- --format json --output "internal/roadmap/i18n/evidence/app-metadata-workflow-inventory.json"` 落盘库存报告,确认当前这些元数据仍是单语事实源,没有独立 installer 翻译工作流。
- 新增 `internal/roadmap/i18n/app-metadata-workflow-evaluation.md`,把 installer / app metadata 的边界判断、建议工作流和重新评估条件写成可引用的 roadmap 工件。
- 报告结论保持不变:app / installer 元数据现在只有单一文本事实源,没有 `zh-CN / en-US` 之类的专门翻译链路;如果后续要做,必须先定义 source locale、metadata ownership 和发布约束,再补 workflow。
@@ -1017,7 +1017,7 @@ benchmark 摘要:
本轮继续完成:
- `scripts/quality-task-planner.mjs` 将发布材料 / 官网文档 / 帮助文档事实源变更映射到 `i18n:release-docs-report:json` 推荐命令;docs-only 变更仍跳过代码校验,但会保留 evidence 刷新建议。
- installer / app metadata 相关的 `package.json``lime-rs/Cargo.toml``tauri.conf*.json``agent-app-shell.json` 变更现在会推荐刷新 `app-metadata-workflow-inventory.json`
- installer / app metadata 相关的 `package.json``lime-rs/Cargo.toml``forge.config.mjs``agent-app-shell.json` 变更现在会推荐刷新 `app-metadata-workflow-inventory.json`
- RTL 方向基础与 readiness inventory 审计过的设置页、侧栏、Workspace、弹窗和 Knowledge 主路径 surface 变更,会推荐刷新 `rtl-readiness-inventory.json`;布局敏感 surface 同时推荐 `npm run i18n:rtl-smoke`
- `scripts/local-ci.mjs` 调整 docs-only 摘要输出顺序,确保 docs-only 仍跳过本地代码校验,但不会吞掉 P4 evidence 推荐命令。
@@ -1181,7 +1181,7 @@ benchmark 摘要:
本轮继续完成:
- `scripts/i18n/i18n-app-metadata-workflow-report.ts` 新增 `metadataFieldCoverage`,把真实 app / installer metadata 字段与 `app-metadata-translation-scope.json` 做双向比对:真实字段未入 scope 会进入 `unscopedMetadataFields`scope 引用失效字段会进入 `missingScopedFields`
- `internal/roadmap/i18n/app-metadata-translation-scope.json` 补入 `lime-rs/tauri.conf.json#bundle.fileAssociations[0].name`,将 Skill Package 文件关联类型名归为稳定品牌字段,避免 file association 只审计 description 而漏掉 name
- `internal/roadmap/i18n/app-metadata-translation-scope.json` 已收敛到 `forge.config.mjs` 的 Electron Forge app metadata 字段,旧 Tauri file association 不再作为 app / installer metadata scope 输入
- 刷新 `internal/roadmap/i18n/evidence/app-metadata-workflow-inventory.json` 后,当前审计字段 `11` 个,`metadataUnscopedFieldCount=0``metadataMissingScopedFieldCount=0``hasInstallerLocalizationWorkflow` 仍为 `false`,不误判为多语言 installer workflow 已完成。
- `internal/roadmap/i18n/app-metadata-workflow-evaluation.md` 已同步说明:在 `generatedMetadataAllowed=false` 期间,不新增平行 locale 配置,只用 scope + inventory 管住发布元数据漂移。
+19 -329
View File
@@ -10,21 +10,13 @@ use lime_services::update_check_service::{
UpdateCheckService, UpdateCheckServiceState, UpdateInfo,
};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use tauri::{AppHandle, Emitter, Manager, State};
const DAY_SECONDS: u64 = 24 * 3600;
const UPDATE_CHECK_CACHE_TTL_SECS: u64 = 10 * 60;
const FALLBACK_RELEASES_URL: &str = "https://github.com/limecloud/lime/releases";
const DEFAULT_UPDATE_MANIFEST_URL: &str = "https://updates.limecloud.com/lime/stable/latest.json";
pub const UPDATE_INSTALL_SESSION_EVENT: &str = "app-update://session";
/// 编译期注入 updater manifest 地址;未配置时使用 Cloudflare R2 稳定清单。
const COMPILED_UPDATER_ENDPOINT: Option<&str> = option_env!("LIME_UPDATER_ENDPOINT");
/// 更新检查配置(前端可见)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UpdateCheckSettings {
@@ -172,31 +164,6 @@ impl UpdateInstallSessionState {
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
struct UpdateCheckCache {
latest: Option<String>,
download_url: Option<String>,
release_notes: Option<String>,
pub_date: Option<String>,
last_checked_unix: u64,
}
#[derive(Debug, Deserialize)]
struct StaticUpdateManifest {
version: String,
#[serde(default)]
notes: Option<String>,
#[serde(default)]
pub_date: Option<String>,
platforms: HashMap<String, StaticUpdatePlatform>,
}
#[derive(Debug, Deserialize)]
struct StaticUpdatePlatform {
url: String,
signature: Option<String>,
}
fn rate_percent(numerator: u64, denominator: u64) -> f64 {
if denominator == 0 {
return 0.0;
@@ -265,13 +232,6 @@ where
session
}
fn updater_manifest_url() -> &'static str {
COMPILED_UPDATER_ENDPOINT
.map(str::trim)
.filter(|value| !value.is_empty())
.unwrap_or(DEFAULT_UPDATE_MANIFEST_URL)
}
fn release_tag_url(version: &str) -> String {
format!(
"https://github.com/limecloud/lime/releases/tag/v{}",
@@ -279,61 +239,6 @@ fn release_tag_url(version: &str) -> String {
)
}
fn current_platform_key() -> Option<&'static str> {
#[cfg(all(target_os = "windows", target_arch = "x86_64"))]
{
return Some("windows-x86_64");
}
#[cfg(all(target_os = "windows", target_arch = "aarch64"))]
{
return Some("windows-aarch64");
}
#[cfg(all(target_os = "macos", target_arch = "x86_64"))]
{
return Some("darwin-x86_64");
}
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
{
return Some("darwin-aarch64");
}
#[allow(unreachable_code)]
None
}
fn get_update_check_cache_path() -> PathBuf {
let base_dir = dirs::cache_dir()
.or_else(dirs::config_dir)
.unwrap_or_else(|| PathBuf::from("."));
base_dir.join("lime").join("update-check-cache.json")
}
fn is_update_cache_fresh(cache: &UpdateCheckCache, now_unix: u64, ttl_secs: u64) -> bool {
if cache.latest.is_none() {
return false;
}
now_unix.saturating_sub(cache.last_checked_unix) < ttl_secs
}
fn load_update_check_cache(path: &PathBuf) -> Option<UpdateCheckCache> {
let content = std::fs::read_to_string(path).ok()?;
serde_json::from_str::<UpdateCheckCache>(&content).ok()
}
fn save_update_check_cache(path: &PathBuf, cache: &UpdateCheckCache) -> Result<(), String> {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).map_err(|e| e.to_string())?;
}
let content = serde_json::to_string(cache).map_err(|e| e.to_string())?;
std::fs::write(path, content).map_err(|e| e.to_string())
}
fn build_update_info(
latest: Option<String>,
release_notes: Option<String>,
@@ -368,23 +273,6 @@ fn build_update_info(
}
}
fn build_update_info_from_cache_or_default(
cache: Option<&UpdateCheckCache>,
error: Option<String>,
) -> UpdateInfo {
if let Some(cached) = cache {
return build_update_info(
cached.latest.clone(),
cached.release_notes.clone(),
cached.pub_date.clone(),
cached.download_url.clone(),
error,
);
}
build_update_info(None, None, None, None, error)
}
fn build_version_check_result(info: UpdateInfo) -> VersionCheckResult {
VersionCheckResult {
current: info.current_version,
@@ -398,128 +286,18 @@ fn build_version_check_result(info: UpdateInfo) -> VersionCheckResult {
}
}
fn manifest_to_cache(manifest: &StaticUpdateManifest, checked_at: u64) -> UpdateCheckCache {
UpdateCheckCache {
latest: Some(manifest.version.trim_start_matches('v').to_string()),
download_url: manifest_platform_download_url(manifest)
.or_else(|| Some(release_tag_url(&manifest.version))),
release_notes: manifest.notes.clone(),
pub_date: manifest.pub_date.clone(),
last_checked_unix: checked_at,
}
}
fn manifest_platform_download_url(manifest: &StaticUpdateManifest) -> Option<String> {
current_platform_key()
.and_then(|platform_key| manifest.platforms.get(platform_key))
.filter(|platform| {
!platform.url.trim().is_empty()
&& platform
.signature
.as_deref()
.is_some_and(|signature| !signature.trim().is_empty())
})
.map(|platform| platform.url.trim())
.filter(|url| !url.is_empty())
.map(ToOwned::to_owned)
}
fn build_update_info_from_manifest(manifest: StaticUpdateManifest) -> UpdateInfo {
let latest_version = manifest.version.trim_start_matches('v').to_string();
let download_url = manifest_platform_download_url(&manifest);
let platform_error = match current_platform_key() {
Some(platform_key)
if manifest
.platforms
.get(platform_key)
.is_some_and(|platform| {
!platform.url.trim().is_empty()
&& platform
.signature
.as_deref()
.is_some_and(|signature| !signature.trim().is_empty())
}) =>
{
None
}
Some(platform_key) if manifest.platforms.contains_key(platform_key) => Some(format!(
"已检测到新版本,但当前平台 {} 的更新包地址或签名为空,请前往发布页手动下载",
platform_key
)),
Some(platform_key) => Some(format!(
"已检测到新版本,但当前平台 {} 暂无安装包,请前往发布页手动下载",
platform_key
)),
None => Some("当前平台暂不支持应用内升级,请前往发布页手动下载".to_string()),
};
fn legacy_electron_updater_info() -> UpdateInfo {
build_update_info(
Some(latest_version),
manifest.notes,
manifest.pub_date,
download_url,
platform_error,
None,
Some("自动更新由 Electron Desktop Host 通过 Forge RELEASES.json / RELEASES 处理。".to_string()),
None,
Some(FALLBACK_RELEASES_URL.to_string()),
Some("旧 Rust 升级器已下线;请使用 Electron Desktop Host 自动更新,或前往发布页手动下载最新版".to_string()),
)
}
async fn fetch_update_info() -> UpdateInfo {
let now_unix = current_unix_timestamp();
let cache_path = get_update_check_cache_path();
let cached = load_update_check_cache(&cache_path);
if let Some(cache) = &cached {
if is_update_cache_fresh(cache, now_unix, UPDATE_CHECK_CACHE_TTL_SECS) {
return build_update_info_from_cache_or_default(cached.as_ref(), None);
}
}
let client = match reqwest::Client::builder()
.timeout(Duration::from_secs(15))
.build()
{
Ok(client) => client,
Err(error) => {
return build_update_info_from_cache_or_default(
cached.as_ref(),
Some(format!("创建更新检查客户端失败,已回退本地缓存: {error}")),
);
}
};
match client
.get(updater_manifest_url())
.header("User-Agent", "Lime")
.send()
.await
{
Ok(response) => {
if !response.status().is_success() {
return build_update_info_from_cache_or_default(
cached.as_ref(),
Some(format!(
"更新清单请求失败(HTTP {}),已回退本地缓存",
response.status()
)),
);
}
match response.json::<StaticUpdateManifest>().await {
Ok(manifest) => {
let cache = manifest_to_cache(&manifest, now_unix);
let _ = save_update_check_cache(&cache_path, &cache);
build_update_info_from_manifest(manifest)
}
Err(error) => build_update_info_from_cache_or_default(
cached.as_ref(),
Some(format!("解析更新清单失败,已回退本地缓存: {error}")),
),
}
}
Err(error) => build_update_info_from_cache_or_default(
cached.as_ref(),
Some(format!("请求更新清单失败,已回退本地缓存: {error}")),
),
}
legacy_electron_updater_info()
}
async fn perform_update_check(update_service: &UpdateCheckServiceState) -> UpdateInfo {
@@ -1022,112 +800,24 @@ pub async fn start_background_update_check(
mod tests {
use super::*;
fn next_patch_version_tag() -> String {
let version_core = env!("CARGO_PKG_VERSION")
.split(['-', '+'])
.next()
.unwrap_or(env!("CARGO_PKG_VERSION"));
let mut parts = version_core
.split('.')
.map(|segment| segment.parse::<u64>().expect("版本段必须为数字"))
.collect::<Vec<_>>();
assert!(
parts.len() == 3,
"当前包版本应为三段式 semver,实际为 {}",
version_core
);
parts[2] += 1;
format!("v{}.{}.{}", parts[0], parts[1], parts[2])
}
#[test]
fn test_is_update_cache_fresh() {
let cache = UpdateCheckCache {
latest: Some("0.92.0".to_string()),
download_url: Some(release_tag_url("0.92.0")),
release_notes: Some("notes".to_string()),
pub_date: Some("2026-03-21T00:00:00Z".to_string()),
last_checked_unix: 100,
};
fn test_legacy_update_info_points_to_electron_updater_fallback() {
let info = legacy_electron_updater_info();
let serialized = serde_json::to_string(&info).expect("应能序列化更新信息");
assert!(is_update_cache_fresh(&cache, 150, 60));
assert!(!is_update_cache_fresh(&cache, 170, 60));
let cache_without_latest = UpdateCheckCache {
latest: None,
..cache
};
assert!(!is_update_cache_fresh(&cache_without_latest, 120, 60));
}
#[test]
fn test_build_update_info_from_manifest() {
let next_version_tag = next_patch_version_tag();
let next_version = next_version_tag.trim_start_matches('v').to_string();
let platform_url = "https://updates.limecloud.com/lime/stable/v9.9.9/lime.app.tar.gz";
let manifest = StaticUpdateManifest {
version: next_version_tag,
notes: Some("bug fixes".to_string()),
pub_date: Some("2026-03-21T00:00:00Z".to_string()),
platforms: HashMap::from([(
current_platform_key()
.unwrap_or("windows-x86_64")
.to_string(),
StaticUpdatePlatform {
url: platform_url.to_string(),
signature: Some("sig".to_string()),
},
)]),
};
let info = build_update_info_from_manifest(manifest);
assert_eq!(info.latest_version.as_deref(), Some(next_version.as_str()));
assert!(info.has_update);
if current_platform_key().is_some() {
assert_eq!(info.download_url.as_deref(), Some(platform_url));
assert_eq!(info.error, None);
} else {
assert_eq!(
info.download_url.as_deref(),
Some(release_tag_url(&next_version).as_str())
);
assert!(info
.error
.as_deref()
.is_some_and(|message| message.contains("暂不支持")));
}
}
#[test]
fn test_build_update_info_from_manifest_requires_signature() {
let Some(platform_key) = current_platform_key() else {
return;
};
let manifest = StaticUpdateManifest {
version: next_patch_version_tag(),
notes: None,
pub_date: None,
platforms: HashMap::from([(
platform_key.to_string(),
StaticUpdatePlatform {
url: "https://updates.limecloud.com/lime/stable/v9.9.9/lime.app.tar.gz"
.to_string(),
signature: None,
},
)]),
};
let info = build_update_info_from_manifest(manifest);
assert!(info.has_update);
let expected_release_url =
release_tag_url(info.latest_version.as_deref().expect("应存在最新版本"));
assert!(!info.has_update);
assert_eq!(info.download_url.as_deref(), Some(FALLBACK_RELEASES_URL));
assert_eq!(
info.download_url.as_deref(),
Some(expected_release_url.as_str())
info.release_notes_url.as_deref(),
Some(FALLBACK_RELEASES_URL)
);
assert!(info
.error
.as_deref()
.is_some_and(|message| message.contains("更新包地址或签名为空")));
.is_some_and(|message| message.contains("Electron Desktop Host")));
assert!(serialized.contains("RELEASES.json"));
assert!(serialized.contains("RELEASES"));
assert!(!serialized.contains("latest.json"));
assert!(!serialized.contains(".app.tar.gz"));
}
}
+6 -1
View File
@@ -368,6 +368,9 @@ describe("Electron current testing docs guard", () => {
expect(releaseUpdater).toContain(
"LIME_WINDOWS_SIGNING_CERTIFICATE_PASSWORD",
);
expect(releaseUpdater).toContain(
"LIME_WINDOWS_SQUIRREL_REMOTE_RELEASES_URL",
);
expect(releaseUpdater).toContain("可选但成对");
expect(releaseUpdater).toContain("unsigned Forge Squirrel installer");
expect(releaseUpdater).toContain("app-server.release.json");
@@ -394,6 +397,7 @@ describe("Electron current testing docs guard", () => {
expect(forgeConfig).toContain("certificateFile");
expect(forgeConfig).toContain("certificatePassword");
expect(forgeConfig).toContain("LIME_WINDOWS_SIGNING_CERTIFICATE_FILE");
expect(forgeConfig).toContain("LIME_WINDOWS_SQUIRREL_REMOTE_RELEASES_URL");
const updateHost = readFile("electron/updateHost.ts");
expect(updateHost).toContain('import { app, autoUpdater } from "electron"');
@@ -548,10 +552,11 @@ describe("Electron current testing docs guard", () => {
expect(evaluation).toContain("forge.config.mjs");
expect(evaluation).toContain("当前 Electron 发布元数据事实源");
expect(evaluation).toContain("Electron Forge / 平台发布链路");
expect(evaluation).toContain("deprecated cleanup candidate");
expect(evaluation).toContain("已按 `dead` release / metadata surface 下线");
expect(evaluation).toContain(
"不是 current app metadata、installer、release、updater、签名或版本同步事实源",
);
expect(evaluation).toContain("不能作为 i18n evidence 输入回流");
expect(evaluation).not.toContain("Tauri file association");
expect(evaluation).not.toContain("真实 Tauri");
expect(evaluation).not.toContain("手工复制多份 Tauri 配置");
+35 -1
View File
@@ -9,6 +9,8 @@ import forgeConfig, {
updateFeedLabel,
updateFeedUrl,
windowsSigningOptions,
windowsSquirrelRemoteReleasesOptions,
windowsSquirrelRemoteReleasesUrl,
} from "../../forge.config.mjs";
describe("Electron Forge config", () => {
@@ -131,7 +133,6 @@ describe("Electron Forge config", () => {
exe: "Lime.exe",
name: "lime",
noMsi: true,
remoteReleases: "https://updates.example/lime/stable/win32-x64",
setupExe: "Lime-9.8.7 Setup.exe",
setupIcon: "lime-rs/icons/icon.ico",
});
@@ -171,6 +172,39 @@ describe("Electron Forge config", () => {
).toEqual({});
});
it("keeps Windows Squirrel remote release sync opt-in", () => {
const env = {
LIME_ELECTRON_UPDATES_URL: "https://feed.example/win32-x64/",
LIME_UPDATES_BASE_URL: "https://updates.example/",
LIME_WINDOWS_SQUIRREL_REMOTE_RELEASES_URL:
" https://updates.example/lime/stable/win32-x64/ ",
};
expect(windowsSquirrelRemoteReleasesUrl({ env: {} })).toBeUndefined();
expect(windowsSquirrelRemoteReleasesUrl({ env })).toBe(
"https://updates.example/lime/stable/win32-x64",
);
expect(windowsSquirrelRemoteReleasesOptions({ env })).toEqual({
remoteReleases: "https://updates.example/lime/stable/win32-x64",
});
expect(
squirrelConfig("x64", {
env: { LIME_ELECTRON_UPDATES_URL: "https://feed.example/win32-x64/" },
packageVersion: "9.8.7",
platform: "win32",
}),
).not.toHaveProperty("remoteReleases");
expect(
squirrelConfig("x64", {
env,
packageVersion: "9.8.7",
platform: "win32",
}),
).toMatchObject({
remoteReleases: "https://updates.example/lime/stable/win32-x64",
});
});
it("keeps required packaged app inputs while ignoring repository-only sources", () => {
expect(ignorePackagerInput(`${process.cwd()}/package.json`)).toBe(false);
expect(ignorePackagerInput(`${process.cwd()}/dist/index.html`)).toBe(false);
+13 -2
View File
@@ -448,7 +448,9 @@ function listRepositoryFiles(root) {
return files.sort();
}
function assertNoRetiredPackagingFiles(repositoryRoot = DEFAULT_REPOSITORY_ROOT) {
function assertNoRetiredPackagingFiles(
repositoryRoot = DEFAULT_REPOSITORY_ROOT,
) {
const root = path.resolve(repositoryRoot);
const retiredFiles = listRepositoryFiles(root).filter((filePath) =>
RETIRED_PACKAGING_FILE_PATTERNS.some((pattern) => pattern.test(filePath)),
@@ -476,7 +478,6 @@ function assertForgeConfig(forgeConfigPath = DEFAULT_FORGE_CONFIG_PATH) {
'["win32"]',
"macUpdateManifestBaseUrl",
'updateFeedUrl("darwin", arch',
'updateFeedUrl("win32", arch',
"RELEASE_OUTPUT_DIR",
"LIME_ELECTRON_FORGE_OUT_DIR",
"dist-electron/app-server.release.json",
@@ -533,10 +534,20 @@ function assertForgeConfig(forgeConfigPath = DEFAULT_FORGE_CONFIG_PATH) {
"Setup.exe",
"setupIcon",
"lime-rs/icons/icon.ico",
"windowsSquirrelRemoteReleasesUrl",
"windowsSquirrelRemoteReleasesOptions",
"LIME_WINDOWS_SQUIRREL_REMOTE_RELEASES_URL",
"...windowsSquirrelRemoteReleasesOptions(options)",
"...windowsSigningOptions(options)",
]) {
assertIncludes(forgeConfig, required, "Forge Windows Squirrel config");
}
if (forgeConfig.includes('remoteReleases: updateFeedUrl("win32"')) {
throw new Error(
"Forge Windows Squirrel config must not use runtime update feed as remoteReleases",
);
}
}
function assertReleaseAssetPipelineScripts({
@@ -165,6 +165,20 @@ describe("Electron release workflow guard", () => {
);
});
it("rejects Windows Squirrel remote release sync from runtime feed", () => {
const forgeConfig = fs.readFileSync("forge.config.mjs", "utf8");
const forgeConfigPath = tempForgeConfigPath(
forgeConfig.replace(
" setupExe: `${PRODUCT_NAME}-${packageVersion} Setup.exe`,",
' remoteReleases: updateFeedUrl("win32", arch, options),\n setupExe: `${PRODUCT_NAME}-${packageVersion} Setup.exe`,',
),
);
expect(() => validateReleaseWorkflow({ forgeConfigPath })).toThrow(
/must not use runtime update feed as remoteReleases/,
);
});
it("rejects macOS branding after signing and notarization", () => {
const forgeConfig = fs.readFileSync("forge.config.mjs", "utf8");
const forgeConfigPath = tempForgeConfigPath(