统一客户端渠道产品名称
Project CI / AI game creator shell Rust smoke (push) Successful in 1m28s
Project CI / AI game creator shell Rust crates (push) Successful in 59s
Project CI / Backend tests (push) Successful in 4m50s
Project CI / Native shell tests (push) Successful in 5m43s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 7m23s
Project CI / AI game creator shell Rust lane 1/2 (push) Successful in 8m55s
Project CI / Frontend tests (push) Successful in 2m8s
Project CI / AI game creator shell web tests (push) Failing after 1m52s
Project CI / Repository checks (push) Successful in 2m29s

dev 渠道显示为陶泥儿开发版并保持既有 identifier 与升级链。

将产品名注入 Tauri、原生窗口、前端标题栏和关于页面,统一快捷方式与注册表展示来源。

补充渠道配置门禁、发布脚本测试和渠道身份文档。
This commit is contained in:
2026-09-23 18:49:44 +08:00
parent 661867c307
commit ebb288a6a4
9 changed files with 57 additions and 24 deletions
@@ -386,6 +386,9 @@ export function createChannelConfig(
return {
productName,
identifier,
app: {
windows: [{ title: productName }],
},
plugins: {
updater: {
endpoints: [updateManifestUrl(channel, target)],
@@ -452,6 +455,8 @@ export function runTauriBuild(
// Vite embeds the platform API origin in the packaged renderer. The
// release channel and updater channel therefore cannot drift apart.
VITE_AGC_PLATFORM_CHANNEL: channel,
VITE_AGC_PRODUCT_NAME:
resolveChannelInstallIdentity(channel).productName,
},
},
);
@@ -177,8 +177,11 @@ test('channel manifest URL and build-time endpoint follow the channel', () => {
'https://agc-dev.oss-rg-china-mainland.aliyuncs.com/agc/dev-win/latest.json',
);
assert.deepEqual(createChannelConfig('dev', 'aarch64-apple-darwin'), {
productName: AGC_PRODUCT_NAME,
productName: `${AGC_PRODUCT_NAME}开发版`,
identifier: AGC_APP_IDENTIFIER,
app: {
windows: [{ title: `${AGC_PRODUCT_NAME}开发版` }],
},
plugins: {
updater: {
endpoints: [
@@ -202,7 +205,7 @@ test('channel manifest URL and build-time endpoint follow the channel', () => {
test('channel install identity isolates co-installed builds and keeps the default channel stable', () => {
// 默认渠道必须保持已发布客户端身份:改身份等于换一个 App,升级链会断。
assert.deepEqual(resolveChannelInstallIdentity('dev'), {
productName: AGC_PRODUCT_NAME,
productName: `${AGC_PRODUCT_NAME}开发版`,
identifier: AGC_APP_IDENTIFIER,
});
assert.deepEqual(resolveChannelInstallIdentity('release'), {
@@ -277,6 +280,10 @@ test('packaged renderer receives the same channel as the updater manifest', () =
},
});
assert.equal(spawnOptions?.env?.VITE_AGC_PLATFORM_CHANNEL, 'release');
assert.equal(
spawnOptions?.env?.VITE_AGC_PRODUCT_NAME,
`${AGC_PRODUCT_NAME} Release`,
);
});
test('macOS manifests advertise exactly the architectures actually built', () => {
@@ -10,7 +10,7 @@
* 因此不同渠道的包体在同一台设备上并存时互不顶掉,也不会共享登录态、
* 本地项目与运行锁。
*
* 默认渠道 `dev` 保持已发布客户端身份不变:升级链路与既有安装不能断。
* 默认渠道 `dev` 保持已发布客户端标识不变:升级链路与既有安装不能断;展示名显式标记为开发版
*/
export const AGC_DEFAULT_CHANNEL = 'dev';
@@ -46,8 +46,9 @@ export function resolveReleaseChannel(env = process.env) {
return validateReleaseChannel(env.AGC_UPDATE_CHANNEL?.trim() ?? 'dev');
}
/** 安装身份里的展示后缀:`release` → `Release``beta-2` → `Beta-2`。 */
/** 安装身份里的展示后缀:`dev` → `开发版``release` → `Release``beta-2` → `Beta-2`。 */
export function channelDisplaySuffix(channel) {
if (channel === AGC_DEFAULT_CHANNEL) return '开发版';
return validateReleaseChannel(channel)
.split('-')
.map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))
@@ -55,19 +56,19 @@ export function channelDisplaySuffix(channel) {
}
/**
* 渠道对应的安装身份。默认渠道返回基线身份,其它渠道派生渠道后缀
* 保证同一台设备上不同渠道互不覆盖
* 渠道对应的安装身份。默认渠道保持既有 identifier 以兼容已安装客户端
* 但展示名明确标记为开发版;其它渠道派生独立 identifier,保证同一台设备上并存
*/
export function resolveChannelInstallIdentity(channel = AGC_DEFAULT_CHANNEL) {
validateReleaseChannel(channel);
if (channel === AGC_DEFAULT_CHANNEL) {
return Object.freeze({
productName: AGC_PRODUCT_NAME,
identifier: AGC_APP_IDENTIFIER,
});
}
return Object.freeze({
productName: `${AGC_PRODUCT_NAME} ${channelDisplaySuffix(channel)}`,
identifier: `${AGC_APP_IDENTIFIER}.${channel}`,
productName:
channel === AGC_DEFAULT_CHANNEL
? `${AGC_PRODUCT_NAME}开发版`
: `${AGC_PRODUCT_NAME} ${channelDisplaySuffix(channel)}`,
identifier:
channel === AGC_DEFAULT_CHANNEL
? AGC_APP_IDENTIFIER
: `${AGC_APP_IDENTIFIER}.${channel}`,
});
}
@@ -25,7 +25,6 @@ execFileSync(
import {
AGC_APP_IDENTIFIER,
AGC_PRODUCT_NAME,
resolveChannelInstallIdentity,
} from './channel-identity.mjs';
import {
@@ -1313,7 +1312,7 @@ if (
// 基线配置必须等于默认渠道的安装身份:默认渠道不能改身份,否则已发布客户端
// 的升级链路与既有安装目录都会断开。
const defaultChannelIdentity = resolveChannelInstallIdentity('dev');
if (tauriConfig.productName !== AGC_PRODUCT_NAME) {
if (tauriConfig.productName !== defaultChannelIdentity.productName) {
throw new Error('AI game creator shell productName drifted');
}
@@ -1486,7 +1485,7 @@ if (
!Array.isArray(tauriConfig.app?.windows) ||
tauriConfig.app.windows.length !== 1 ||
tauriConfig.app.windows[0]?.label !== 'client' ||
tauriConfig.app.windows[0]?.title !== '陶泥儿' ||
tauriConfig.app.windows[0]?.title !== defaultChannelIdentity.productName ||
tauriConfig.app.windows[0]?.url !== 'index.html'
) {
throw new Error(
@@ -1,6 +1,6 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "陶泥儿",
"productName": "陶泥儿开发版",
"version": "0.1.67",
"identifier": "world.genarrative.ai-game-creator",
"build": {
@@ -14,7 +14,7 @@
"windows": [
{
"label": "client",
"title": "陶泥儿",
"title": "陶泥儿开发版",
"url": "index.html",
"width": 1280,
"height": 800,
@@ -1,5 +1,15 @@
import appPackage from '../../package.json';
const DEFAULT_APP_NAME = '陶泥儿开发版';
/**
* 产品名由构建期注入;本地 Vite 开发没有注入时沿用 dev 渠道产品名。
*
* 发布构建可通过 `VITE_AGC_PRODUCT_NAME` 注入渠道产品名,避免 UI 自己
* 根据渠道推导名称,保证标题栏、关于页等前端展示与安装身份保持一致。
*/
const injectedAppName = import.meta.env.VITE_AGC_PRODUCT_NAME?.trim();
/** Product metadata shared by the client UI and release bundle. */
export const APP_NAME = 'Genarrative AI Game Creator';
export const APP_NAME = injectedAppName || DEFAULT_APP_NAME;
export const APP_VERSION = appPackage.version;
@@ -9,6 +9,7 @@ import {
} from 'react';
import brandIcon from '../../../../packages/shared/src/icons/taonier-product-ip.png';
import { APP_NAME } from '../app/appMetadata';
import { appUpdateCheckEnabled } from '../app/featureFlags';
import { ActiveProjectRunsPanel } from '../features/app-shell/ActiveProjectRunsPanel';
import { subscribeTauriEvent } from '../services/tauriEventSubscription';
@@ -150,12 +151,15 @@ export function WindowChrome({ children }: WindowChromeProps) {
{appUpdateCheckEnabled ? <AppUpdateNotice /> : null}
<header className="window-chrome__bar" aria-label="窗口标题栏">
<div className="window-chrome__leading">
<div className="window-chrome__brand" aria-label="陶泥儿 GameAgent">
<div
className="window-chrome__brand"
aria-label={`${APP_NAME} GameAgent`}
>
<span className="window-chrome__brand-mark">
<img src={brandIcon} alt="" />
</span>
<span className="window-chrome__brand-copy">
<strong></strong>
<strong>{APP_NAME}</strong>
<span>GameAgent</span>
</span>
</div>
@@ -1,6 +1,6 @@
import fs from 'node:fs';
import { APP_VERSION } from '../../src/app/appMetadata';
import { APP_NAME, APP_VERSION } from '../../src/app/appMetadata';
import { repoPath } from '../repoPath';
import {
act,
@@ -348,7 +348,7 @@ export function registerRuntimeSettingsTests() {
fireEvent.click(screen.getByRole('button', { name: /关于/ }));
expect(screen.getByText('Genarrative AI Game Creator')).not.toBeNull();
expect(screen.getByText(APP_NAME)).not.toBeNull();
expect(
screen.getByTestId('runtime-settings-app-logo').getAttribute('src'),
).toContain('taonier-product-ip.png');
@@ -47,3 +47,10 @@
- 风险:非默认渠道首次以新身份安装,老 `dev` 用户不会自动迁移本地数据。回滚点:渠道身份只影响非默认渠道构建,撤销该渠道的构建产物即可,仓库侧无数据迁移。
- 风险:窗口标题改为构建期产品名后,标题不再等于配置里的字面量。回滚点:去掉 `main.rs` 的标题覆盖调用,行为回到配置标题。
- 风险:ACL managed 识别放宽到前缀族。回滚点:`is_game_creator_packaged_app_data_leaf` 收紧回单一直线值,但非默认渠道的提权修复会重新失败关闭。
## 2026-09-23 追加:dev 渠道展示名统一
- `dev` 渠道继续复用 `world.genarrative.ai-game-creator`,保证既有安装、升级链和 AppData 路径不变;展示名统一为 `陶泥儿开发版`
- `channel-identity.mjs` 是展示名单一来源。发布构建将同一 `productName` 同时注入 Tauri 安装配置、原生窗口标题和 `VITE_AGC_PRODUCT_NAME`;React 自绘标题栏及关于/运行时配置展示从该注入值读取。
- 因此 Windows NSIS 默认生成的快捷方式、开始菜单/卸载注册表展示名随 Tauri `productName` 变为 `陶泥儿开发版`;未新增自定义注册表或快捷方式实现。
- 静态 Tauri 基线配置同步为 `陶泥儿开发版`,本地壳与正式 `dev` 包的显示名保持一致。