mirror of
https://github.com/aiclientproxy/proxycast.git
synced 2026-09-01 15:30:52 +08:00
Fix v1.60.0 Windows release feed handling
This commit is contained in:
+20
-1
@@ -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` 全 true,backend 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 closed,persisted 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 entry,missing 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 管住发布元数据漂移。
|
||||
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 配置");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user