修复 AGC dev 渠道改名后旧快捷方式仍指向旧安装
Project CI / AI game creator shell Rust crates (push) Successful in 1m33s
Project CI / AI game creator shell Rust smoke (push) Successful in 2m6s
Project CI / Backend tests (push) Failing after 3m7s
Project CI / Frontend tests (push) Successful in 1m40s
Project CI / Native shell tests (push) Successful in 5m43s
Project CI / Repository checks (push) Successful in 2m28s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 8m50s
Project CI / AI game creator shell Rust lane 1/2 (push) Successful in 8m53s
Project CI / AI game creator shell web tests (push) Successful in 1m37s
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust smoke (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (push) Successful in 1m33s
Project CI / AI game creator shell Rust smoke (push) Successful in 2m6s
Project CI / Backend tests (push) Failing after 3m7s
Project CI / Frontend tests (push) Successful in 1m40s
Project CI / Native shell tests (push) Successful in 5m43s
Project CI / Repository checks (push) Successful in 2m28s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 8m50s
Project CI / AI game creator shell Rust lane 1/2 (push) Successful in 8m53s
Project CI / AI game creator shell web tests (push) Successful in 1m37s
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust smoke (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
- 新增 src-tauri/windows/installer-hooks.nsh:安装结束时按旧展示名(陶泥儿、Genarrative AI Game Creator)清理旧快捷方式(先用 IsShortcutTarget 校验目标)、旧安装目录与旧卸载项 - 同一钩子在用户原本就有旧桌面图标时补建当前身份的桌面图标、并始终补建开始菜单图标,解决 /UPDATE 下模板不创建快捷方式导致「点旧快捷方式→更新→自动启动新版」的循环 - build-release.mjs 只在 dev 渠道 + Windows 目标注入 bundle.windows.nsis.installerHooks,并让 node runtime 资源映射与钩子共存 - build-release.test.mjs 新增用例:只有 dev+Windows 注入、钩子文件存在且旧身份表完整、JSON Merge Patch 后钩子不被资源映射顶掉 - pitfalls 与 decision-log 记录改名迁移口径、删除边界与「再改展示名必须追加旧身份」的纪律 - AGC 渠道安装身份隔离里程碑补 2026-09-29 修正小节与真机升级验收项
This commit is contained in:
@@ -14,6 +14,7 @@ import {
|
||||
withDefaultCargoFeatures,
|
||||
} from './cargo-features.mjs';
|
||||
import {
|
||||
AGC_DEFAULT_CHANNEL,
|
||||
resolveChannelInstallIdentity,
|
||||
resolveReleaseChannel,
|
||||
} from './channel-identity.mjs';
|
||||
@@ -21,6 +22,16 @@ import { prepareNsisToolsetForRelease } from './nsis-toolset.mjs';
|
||||
import { stageNodeRuntime } from './stage-node-runtime.mjs';
|
||||
|
||||
const appRoot = fileURLToPath(new URL('..', import.meta.url));
|
||||
/**
|
||||
* dev 渠道改名迁移钩子(NSIS installer hooks)。
|
||||
*
|
||||
* dev 渠道展示名改过两次(Genarrative AI Game Creator → 陶泥儿 → 陶泥儿开发版),
|
||||
* identifier 没变:更新路径不会重建快捷方式,改名后的包必须自己清理旧身份,
|
||||
* 否则旧快捷方式会一直指向旧安装目录里的旧 exe。只有 dev 渠道注入这份钩子。
|
||||
*/
|
||||
const devChannelInstallerHooks = fileURLToPath(
|
||||
new URL('../src-tauri/windows/installer-hooks.nsh', import.meta.url),
|
||||
);
|
||||
// Git 命令用于读取 release revision,必须在仓库根执行,不能在应用目录里执行。
|
||||
const repoRoot = path.resolve(appRoot, '..', '..');
|
||||
const defaultReleaseTarget = 'x86_64-pc-windows-msvc';
|
||||
@@ -422,7 +433,7 @@ export function createChannelConfig(
|
||||
baseClientWindow = readBaseClientWindow(),
|
||||
) {
|
||||
const { productName, identifier } = resolveChannelInstallIdentity(channel);
|
||||
return {
|
||||
const config = {
|
||||
productName,
|
||||
identifier,
|
||||
app: {
|
||||
@@ -434,6 +445,12 @@ export function createChannelConfig(
|
||||
},
|
||||
},
|
||||
};
|
||||
// 只有 dev 渠道改过展示名:它的包需要自带旧身份迁移;其它渠道不能拿这份旧名表去删安装。
|
||||
if (channel === AGC_DEFAULT_CHANNEL && target.includes('windows'))
|
||||
config.bundle = {
|
||||
windows: { nsis: { installerHooks: devChannelInstallerHooks } },
|
||||
};
|
||||
return config;
|
||||
}
|
||||
|
||||
function writeChannelConfigFile(channel, target, includeNodeRuntime = false) {
|
||||
@@ -445,6 +462,7 @@ function writeChannelConfigFile(channel, target, includeNodeRuntime = false) {
|
||||
// 普通 cargo test/dev 不要求发行资源;只有完成 staging 的发行构建加入映射。
|
||||
if (includeNodeRuntime)
|
||||
config.bundle = {
|
||||
...config.bundle,
|
||||
resources: { 'resources/node-runtime': 'game-runtime/node' },
|
||||
};
|
||||
fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import {
|
||||
existsSync,
|
||||
mkdtempSync,
|
||||
readdirSync,
|
||||
readFileSync,
|
||||
@@ -263,6 +264,52 @@ test('channel install identity is baked into the same build-time config as the e
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* dev 渠道展示名改过两次(Genarrative AI Game Creator → 陶泥儿 → 陶泥儿开发版),而更新路径
|
||||
* (/UPDATE)不会重建快捷方式:改名后的包必须自带旧身份迁移,否则桌面快捷方式会一直指向旧安装。
|
||||
*/
|
||||
test('dev 渠道的 Windows 包注入改名迁移钩子,其它渠道与目标不注入', () => {
|
||||
withEnv({ AGC_UPDATE_OSS_BASE_URL: undefined }, () => {
|
||||
const devWindows = createChannelConfig('dev', windowsTarget);
|
||||
const hooksPath = devWindows.bundle.windows.nsis.installerHooks;
|
||||
assert.equal(existsSync(hooksPath), true);
|
||||
assert.match(hooksPath, /src-tauri[\\/]windows[\\/]installer-hooks\.nsh$/u);
|
||||
|
||||
const hooksSource = readFileSync(hooksPath, 'utf8');
|
||||
assert.match(hooksSource, /NSIS_HOOK_POSTINSTALL/u);
|
||||
// 改名史必须逐条留在钩子里:漏掉一条,那批机器升级后旧快捷方式就继续指向旧安装。
|
||||
for (const legacyIdentity of ['陶泥儿', 'Genarrative AI Game Creator']) {
|
||||
assert.ok(
|
||||
hooksSource.includes(legacyIdentity),
|
||||
`迁移钩子缺少旧身份:${legacyIdentity}`,
|
||||
);
|
||||
}
|
||||
|
||||
// 改名只发生在 dev:其它渠道注入会拿 dev 的旧名表去删别人的安装。
|
||||
assert.equal(
|
||||
createChannelConfig('release', windowsTarget).bundle,
|
||||
undefined,
|
||||
);
|
||||
// macOS 包没有 NSIS 安装器,不能带上 Windows 钩子。
|
||||
assert.equal(
|
||||
createChannelConfig('dev', 'aarch64-apple-darwin').bundle,
|
||||
undefined,
|
||||
);
|
||||
|
||||
// 渠道配置与发行资源一起走 JSON Merge Patch:加 node runtime 资源不能顶掉钩子。
|
||||
const base = readBaseTauriConfig();
|
||||
const merged = applyJsonMergePatch(base, {
|
||||
...devWindows,
|
||||
version: base.version,
|
||||
bundle: {
|
||||
...devWindows.bundle,
|
||||
resources: { 'resources/node-runtime': 'game-runtime/node' },
|
||||
},
|
||||
});
|
||||
assert.equal(merged.bundle.windows.nsis.installerHooks, hooksPath);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* RFC 7386(tauri-utils 用 `json_patch::merge`)语义:对象递归合并,数组整体替换。
|
||||
* 这里按同样语义复现 Tauri CLI 的 `--config` 合并,用来守住"渠道配置不得丢窗口契约"。
|
||||
@@ -836,6 +883,10 @@ test('release stages Node before Tauri and injects its resource mapping only for
|
||||
assert.deepEqual(config.bundle.resources, {
|
||||
'resources/node-runtime': 'game-runtime/node',
|
||||
});
|
||||
assert.match(
|
||||
config.bundle.windows.nsis.installerHooks,
|
||||
/installer-hooks\.nsh$/u,
|
||||
);
|
||||
return { status: 0 };
|
||||
},
|
||||
});
|
||||
@@ -848,7 +899,12 @@ test('release stages Node before Tauri and injects its resource mapping only for
|
||||
const config = JSON.parse(
|
||||
readFileSync(args[args.lastIndexOf('--config') + 1], 'utf8'),
|
||||
);
|
||||
assert.equal(config.bundle, undefined);
|
||||
// --no-bundle 只是不加发行资源;渠道安装身份(dev 改名钩子)不属于资源映射。
|
||||
assert.equal(config.bundle.resources, undefined);
|
||||
assert.match(
|
||||
config.bundle.windows.nsis.installerHooks,
|
||||
/installer-hooks\.nsh$/u,
|
||||
);
|
||||
return { status: 0 };
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
; AGC dev 渠道安装身份改名的迁移钩子(NSIS installer hooks,随 Windows 包体编译)。
|
||||
;
|
||||
; 背景:dev 渠道的展示名改过两次(Genarrative AI Game Creator → 陶泥儿 → 陶泥儿开发版),
|
||||
; identifier 保持不变。Tauri 的 NSIS 模板在更新模式(/UPDATE)与 /NS 下**不创建快捷方式**,
|
||||
; 只会把「同名」快捷方式的目标改写回主程序名(见 installer.nsi 的 CreateOrUpdate*Shortcut)。
|
||||
; 展示名一改,旧快捷方式既不会被改写、也不会被新建,于是升级后:
|
||||
; - 旧安装目录与旧快捷方式留在原地,桌面快捷方式继续拉起旧 exe;
|
||||
; - 旧 exe 的更新器仍然指向同一份 latest.json,又一次把新版本装进新目录并自动启动。
|
||||
; 用户看到的就是「更新后自动启动的是新版,但桌面快捷方式打开的还是旧的」这个循环。
|
||||
;
|
||||
; 因此安装结束时做两件事:
|
||||
; 1. 旧身份(改名前的展示名)的快捷方式、安装目录与卸载项整体清理,旧 exe 不再能被拉起;
|
||||
; 2. 被迁移过的机器按当前身份补建快捷方式:桌面图标只在用户原本就有旧图标时迁移,
|
||||
; 不替用户新增;开始菜单图标始终补建,避免自更新后应用失去入口。
|
||||
;
|
||||
; 该文件只注入 dev 渠道(改名发生在 dev 渠道)的 Windows 包;其它渠道的展示名从未变过,
|
||||
; 不能拿这份旧身份表去删它们的安装。
|
||||
|
||||
; 旧展示名表:以后再改展示名,必须在这里追加旧名,否则升级后旧快捷方式会继续指向旧安装。
|
||||
!define AGC_LEGACY_IDENTITY_1 "陶泥儿"
|
||||
!define AGC_LEGACY_IDENTITY_2 "Genarrative AI Game Creator"
|
||||
|
||||
Var AgcLegacyInstallDir
|
||||
Var AgcMigratedLegacyDesktopShortcut
|
||||
|
||||
; 清理一个旧身份。调用方把旧展示名压栈(Push),本函数不返回错误。
|
||||
; 只在「旧展示名目录下确实存在我们的主程序」时才动手,避免误删同名目录;
|
||||
; 当前安装目录 $INSTDIR 永不进入删除路径。
|
||||
Function AgcMigrateLegacyIdentity
|
||||
Pop $R0
|
||||
StrCpy $AgcLegacyInstallDir ""
|
||||
|
||||
${If} ${FileExists} "$LOCALAPPDATA\$R0\${MAINBINARYNAME}.exe"
|
||||
StrCpy $AgcLegacyInstallDir "$LOCALAPPDATA\$R0"
|
||||
${ElseIf} ${FileExists} "$PROGRAMFILES64\$R0\${MAINBINARYNAME}.exe"
|
||||
StrCpy $AgcLegacyInstallDir "$PROGRAMFILES64\$R0"
|
||||
${Else}
|
||||
Return
|
||||
${EndIf}
|
||||
|
||||
${If} $AgcLegacyInstallDir == $INSTDIR
|
||||
Return
|
||||
${EndIf}
|
||||
|
||||
; 桌面图标:只有目标正好指向旧目录的主程序时才删,避免误删同名的用户快捷方式。
|
||||
!insertmacro IsShortcutTarget "$DESKTOP\$R0.lnk" "$AgcLegacyInstallDir\${MAINBINARYNAME}.exe"
|
||||
Pop $0
|
||||
${If} $0 = 1
|
||||
StrCpy $AgcMigratedLegacyDesktopShortcut 1
|
||||
!insertmacro UnpinShortcut "$DESKTOP\$R0.lnk"
|
||||
Delete "$DESKTOP\$R0.lnk"
|
||||
${EndIf}
|
||||
|
||||
; 开始菜单图标:必须先删掉,否则它会在当前身份图标补建后继续指向旧 exe。
|
||||
!insertmacro IsShortcutTarget "$SMPROGRAMS\$R0.lnk" "$AgcLegacyInstallDir\${MAINBINARYNAME}.exe"
|
||||
Pop $0
|
||||
${If} $0 = 1
|
||||
!insertmacro UnpinShortcut "$SMPROGRAMS\$R0.lnk"
|
||||
Delete "$SMPROGRAMS\$R0.lnk"
|
||||
${EndIf}
|
||||
|
||||
; 旧安装目录与卸载项整体清理:旧 exe 留着就会继续把新版本装进新目录。
|
||||
; 先结束残留的旧实例(同名主程序):更新路径下调用方通常已退出,这里兜住手动启动的情况。
|
||||
!if "${INSTALLMODE}" == "currentUser"
|
||||
nsis_tauri_utils::KillProcessCurrentUser "${MAINBINARYNAME}.exe"
|
||||
Pop $0
|
||||
!else
|
||||
nsis_tauri_utils::KillProcess "${MAINBINARYNAME}.exe"
|
||||
Pop $0
|
||||
!endif
|
||||
RMDir /r "$AgcLegacyInstallDir"
|
||||
; 卸载项按展示名登记,旧名与当前名不同,必须按旧名清掉,否则「应用和功能」里会留下孤儿条目。
|
||||
DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\$R0"
|
||||
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\$R0"
|
||||
DeleteRegKey HKCU "Software\${MANUFACTURER}\$R0"
|
||||
FunctionEnd
|
||||
|
||||
!macro NSIS_HOOK_POSTINSTALL
|
||||
Push "${AGC_LEGACY_IDENTITY_1}"
|
||||
Call AgcMigrateLegacyIdentity
|
||||
Push "${AGC_LEGACY_IDENTITY_2}"
|
||||
Call AgcMigrateLegacyIdentity
|
||||
|
||||
; 迁移过的机器补建当前身份的快捷方式(更新模式下模板不会创建)。
|
||||
StrCpy $R0 "$INSTDIR\${MAINBINARYNAME}.exe"
|
||||
${IfNot} ${FileExists} "$SMPROGRAMS\${PRODUCTNAME}.lnk"
|
||||
CreateShortCut "$SMPROGRAMS\${PRODUCTNAME}.lnk" "$R0"
|
||||
!insertmacro SetLnkAppUserModelId "$SMPROGRAMS\${PRODUCTNAME}.lnk"
|
||||
${EndIf}
|
||||
${If} $AgcMigratedLegacyDesktopShortcut = 1
|
||||
CreateShortCut "$DESKTOP\${PRODUCTNAME}.lnk" "$R0"
|
||||
!insertmacro SetLnkAppUserModelId "$DESKTOP\${PRODUCTNAME}.lnk"
|
||||
${EndIf}
|
||||
!macroend
|
||||
@@ -73,3 +73,13 @@
|
||||
- **两个已发布安装包的资源串互不串身份**:`dev-win` 的 `陶泥儿开发版_0.1.158_x64-setup.exe` 只命中 `陶泥儿开发版`,`release-win` 的 `陶泥儿 Release_0.1.159_x64-setup.exe` 只命中 `陶泥儿 Release`(安装包里的应用载荷是压缩的,所以这一层只能证安装器自身的身份串,应用载荷的身份由上一轮的 PE `RT_VERSION` 与 mac `Info.plist` 断言覆盖)。
|
||||
- **本条的真机条件已经具备**:本机装着 dev `0.1.154`,`dev-win` 已发到 `0.1.158`;`release-win` 在 `0.1.159`,且 OSS 上保留 `agc/release-win/0.1.150/…_x64-setup.exe`(HEAD 200)可用来造出「release 有更新可装」的状态。也就是说「dev + release 同时装、各自原地更新」现在只差一次桌面操作。
|
||||
- **环境约束(本轮实测,会影响后续排期)**:本次 Codex 会话**无法启动用户可写目录下的可执行文件**——`%LOCALAPPDATA%`、`%TEMP%` 与工作区里的 exe 都被执行策略拒绝(`Start-Process` 只放行系统目录,如 `C:\Windows\System32\cmd.exe`),因此「装包 → 起客户端 → 点更新 → 看版本」这一段必须在桌面会话里执行,不能由本会话代跑。
|
||||
|
||||
## 本轮修正(2026-09-29,改名后的旧身份迁移)
|
||||
|
||||
上面那节核对确认了本机同时存在两个 dev 身份:改名前的 `陶泥儿`(0.1.109/0.1.126)与改名后的 `陶泥儿开发版`。这个状态本身暴露了一个升级链缺陷:
|
||||
|
||||
- **缺陷**:Tauri 的 NSIS 模板在更新模式(`/UPDATE`)与 `/NS` 下不创建快捷方式,只改写「同名」快捷方式的目标;dev 渠道展示名改过两次(`Genarrative AI Game Creator` → `陶泥儿` → `陶泥儿开发版`)而 `identifier` 不变。结果是升级到改名后的包时,旧快捷方式(桌面 `陶泥儿.lnk`、开始菜单 `陶泥儿.lnk`)继续指向 `%LOCALAPPDATA%\陶泥儿\genarrative-ai-game-creator-shell.exe`,新版一份快捷方式都没有;旧 exe 的更新器又指向同一份 `latest.json`,于是「点旧快捷方式 → 更新 → 自动启动新版」会反复发生。
|
||||
- **修正**:dev 渠道的 Windows 包注入 NSIS installer hooks(`apps/ai-game-creator-shell/src-tauri/windows/installer-hooks.nsh`,由 `createChannelConfig` 在 `channel === 'dev'` 且目标含 `windows` 时下发 `bundle.windows.nsis.installerHooks`)。安装结束时按旧展示名清理旧快捷方式(`IsShortcutTarget` 校验目标)、旧安装目录与旧卸载项,并在用户原本就有旧桌面图标时补建当前身份的桌面图标、总是补建开始菜单图标。
|
||||
- **验证**:`node --test apps/ai-game-creator-shell/scripts/build-release.test.mjs` → 41 passed(新增用例断言只有 dev+Windows 注入、钩子文件存在且旧身份表完整、渠道配置经 JSON Merge Patch 后钩子不被 node runtime 资源映射顶掉)。
|
||||
- **仍未验证**:钩子只在真实 NSIS 打包 + 真机升级时执行。本条需要一次 dev 渠道发布 + 真机升级,才能勾掉下面这条验收项:
|
||||
- [ ] 真机:改名前的 dev 安装(`%LOCALAPPDATA%\陶泥儿`)升级到改名后的包后,旧安装目录、旧快捷方式与旧卸载项被清理,`陶泥儿开发版` 的桌面/开始菜单图标出现且指向新安装。
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
# 决策记录
|
||||
|
||||
## 2026-09-29 dev 渠道改名迁移放进安装器钩子,且只注入 dev
|
||||
|
||||
- 背景:dev 渠道展示名从 `陶泥儿` 改成 `陶泥儿开发版`(`identifier` 不变)后,更新路径不会重建快捷方式,旧桌面图标继续指向旧安装目录里的旧 exe,旧 exe 的更新器又把新版本装进新目录,于是用户看到「更新后自动启动新版、桌面快捷方式打开的还是旧的」。
|
||||
- 决策(迁移位置):改名迁移由**安装器**承担,不做运行期清理。Tauri 的 NSIS 模板在更新模式(`/UPDATE`)与 `/NS` 下跳过快捷方式创建(`CreateOrUpdate{StartMenu,Desktop}Shortcut` 都在 `$UpdateMode = 1` 时提前返回),只有安装器能在「新版刚落地、应用还没启动」的时机把旧身份清干净并补齐当前身份的图标;旧包已经发出去,改不了旧客户端的运行期行为。
|
||||
- 决策(注入范围):只有 `dev` 渠道 + Windows 目标注入 `bundle.windows.nsis.installerHooks`。旧身份表(`陶泥儿`、`Genarrative AI Game Creator`)属于 dev 的改名史,其它渠道注入会删掉 dev 的安装;macOS 包没有 NSIS 安装器,不下发。
|
||||
- 决策(删除边界):只清理「旧展示名目录下确实存在我们的主程序」且不等于 `$INSTDIR` 的安装;旧快捷方式先用 `IsShortcutTarget` 校验目标命中旧安装目录再删,不按文件名裸删;桌面图标只为原本就有旧图标的用户迁移,不替用户新增桌面图标。
|
||||
- 决策(改名纪律):dev 渠道以后再改展示名,必须同步往钩子的旧身份表追加旧名,否则升级后旧快捷方式继续指向旧安装。
|
||||
- 影响范围:`apps/ai-game-creator-shell/src-tauri/windows/installer-hooks.nsh`(新增)、`apps/ai-game-creator-shell/scripts/build-release.mjs`(`createChannelConfig` / `writeChannelConfigFile`)、`apps/ai-game-creator-shell/scripts/build-release.test.mjs`、pitfalls、AGC 渠道安装身份隔离里程碑。
|
||||
- 验证:`node --test apps/ai-game-creator-shell/scripts/build-release.test.mjs` → 41 passed(含新增「dev 渠道的 Windows 包注入改名迁移钩子」);`npm run check:encoding`、`npm run check:doc-index`、`git diff --check`。
|
||||
- 边界(未验证):钩子只在真实 NSIS 打包 + 真机升级时执行,本轮既没有出包也没有执行安装;下一次 dev 渠道发布后才能验证「旧目录被清、当前身份的图标出现、旧图标消失」。
|
||||
|
||||
## 2026-09-28 速览卡只承载概览、范围与设计入口
|
||||
|
||||
- 速览卡帮助读者快速理解游戏及本次范围,分类、支柱、循环与目标玩家可融入概述;不复制系统拆分、具体参数、素材数量、完整排除清单或验证计划,不设固定字数或必填章节。
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
# 踩坑与排障记录
|
||||
|
||||
## 2026-09-29 dev 渠道改名后,更新路径不会重建快捷方式(旧图标一直指向旧安装)
|
||||
|
||||
- **现象**:改名前的 dev 客户端(展示名 `陶泥儿`,0.1.126)点更新后自动启动的是新版(`陶泥儿开发版`,0.1.160),但桌面/开始菜单里的 `陶泥儿.lnk` 打开的还是旧版。`%LOCALAPPDATA%` 下同时留着 `陶泥儿` 与 `陶泥儿开发版` 两个安装目录,`HKCU\Software\Microsoft\Windows\CurrentVersion\Uninstall` 下同时留着 `陶泥儿`(0.1.109)与 `陶泥儿开发版`(0.1.160)两个卸载项,而新版一份快捷方式都没有。
|
||||
- **原因**:Tauri 的 NSIS 模板在更新模式(`/UPDATE`)与 `/NS` 下**不创建快捷方式**,只会把「同名」快捷方式的目标改回主程序名(`installer.nsi` 里的 `CreateOrUpdate{StartMenu,Desktop}Shortcut`,两者都在 `$UpdateMode = 1` 时 `Return`)。dev 渠道展示名改过两次(`Genarrative AI Game Creator` → `陶泥儿` → `陶泥儿开发版`)而 `identifier` 保持不变,换成新名后旧快捷方式既不会被改写也不会被替代,旧安装目录同样留在原地;旧 exe 的更新器又指向同一份 `latest.json`,于是变成「点旧快捷方式 → 更新 → 自动启动新版」的循环。本机取证:`E:\Desktop\陶泥儿.lnk` 与开始菜单 `陶泥儿.lnk` 的目标都是 `%LOCALAPPDATA%\陶泥儿\genarrative-ai-game-creator-shell.exe`,全盘不存在 `陶泥儿开发版.lnk`。
|
||||
- **处理(现行口径)**:dev 渠道的 Windows 包注入 NSIS installer hooks(`apps/ai-game-creator-shell/src-tauri/windows/installer-hooks.nsh`;由 `createChannelConfig` 只在 `channel === 'dev'` 且目标含 `windows` 时下发 `bundle.windows.nsis.installerHooks`)。安装结束时:按旧展示名清理旧快捷方式(先用 `IsShortcutTarget` 校验目标命中旧安装目录)、旧安装目录与旧卸载项;用户原本就有旧桌面图标时按当前身份补建桌面图标,开始菜单图标始终补建。**以后再改展示名,必须往钩子的旧身份表里追加旧名**,否则那批机器升级后旧快捷方式继续指向旧安装。
|
||||
- **不要踩的坑**:① 其它渠道不能注入这份钩子——旧名表属于 dev 的改名史,注进去等于删别人的安装;② 旧快捷方式必须先校验目标再删,不能按文件名裸删;③ 当前安装目录 `$INSTDIR` 永不进入删除路径;④ 只在检测到旧身份时补建图标,不要替用户新增桌面图标;⑤ 钩子文件里有中文身份字面量,必须存成 **UTF-8 with BOM**,否则 makensis 按 ANSI 解码会得到错误的旧安装路径。
|
||||
- **判据/取证**:`node --test apps/ai-game-creator-shell/scripts/build-release.test.mjs`(新增「dev 渠道的 Windows 包注入改名迁移钩子,其它渠道与目标不注入」用例,含钩子文件存在性、旧身份表完整性、JSON Merge Patch 后钩子不被资源映射顶掉)。本机真实安装状态见 `docs/project-memory/plans/【里程碑】AGC渠道安装身份隔离-2026-09-21.md` 的 2026-09-29 核对小节。
|
||||
- **关联**:`apps/ai-game-creator-shell/src-tauri/windows/installer-hooks.nsh`、`apps/ai-game-creator-shell/scripts/build-release.mjs`、`apps/ai-game-creator-shell/scripts/build-release.test.mjs`、`apps/ai-game-creator-shell/src-tauri/tauri.conf.json`(`productName = 陶泥儿开发版`)、`apps/ai-game-creator-shell/scripts/channel-identity.mjs`。
|
||||
|
||||
## 2026-09-29 历史 release 与 Jenkins 构建暂存必须显式设置保留上限
|
||||
|
||||
- **现象**:dev 服务器根盘写满(`/dev/vda2` 47G/50G,可用 960M,99%)。两侧都只写不回收:`Genarrative-Api-Deploy` / `Genarrative-Web-Deploy` / `Genarrative-Stdb-Module-Publish` 三个 Job 工作区的 `build/<n>/` 暂存 9.8G,`/opt/genarrative/releases/` 下 68 个历史发布目录 9.2G;另有 journald 归档 700M 和轮转 syslog 437M。
|
||||
|
||||
Reference in New Issue
Block a user