/** * AGC 渠道 → 安装身份。 * * 渠道同时决定两件事: * - 更新端点:OSS 分区 `-win` / `-mac` 的清单地址; * - 安装身份:`productName` 与 `identifier`。 * * 安装身份决定 Windows 安装目录与卸载项、macOS `.app` 名字与 bundle id、 * Windows WebView2 数据目录以及 `%APPDATA%\` 客户端数据目录。 * 因此不同渠道的包体在同一台设备上并存时互不顶掉,也不会共享登录态、 * 本地项目与运行锁。 * * 默认渠道 `dev` 保持已发布客户端标识不变:升级链路与既有安装不能断;展示名显式标记为开发版。 */ export const AGC_DEFAULT_CHANNEL = 'dev'; export const AGC_PRODUCT_NAME = '陶泥儿'; export const AGC_APP_IDENTIFIER = 'world.genarrative.ai-game-creator'; const reservedChannelNames = new Set([ 'win', 'mac', 'windows', 'macos', 'darwin', 'linux', ]); /** 校验渠道名:小写字母开头,允许数字与连字符,系统名不属于渠道。 */ export function validateReleaseChannel(channel) { if ( typeof channel !== 'string' || !/^[a-z][a-z0-9-]{0,31}$/u.test(channel) || channel.endsWith('-') || reservedChannelNames.has(channel) || /-(win|mac)$/u.test(channel) ) { throw new Error( '发布渠道无效:请使用 dev、release 或最多 32 位的小写字母、数字和连字符名称,系统名称不属于渠道', ); } return channel; } export function resolveReleaseChannel(env = process.env) { return validateReleaseChannel(env.AGC_UPDATE_CHANNEL?.trim() ?? 'dev'); } /** 安装身份里的展示后缀:`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)) .join('-'); } /** * 渠道对应的安装身份。默认渠道保持既有 identifier 以兼容已安装客户端, * 但展示名明确标记为开发版;其它渠道派生独立 identifier,保证同一台设备上并存。 */ export function resolveChannelInstallIdentity(channel = AGC_DEFAULT_CHANNEL) { validateReleaseChannel(channel); return Object.freeze({ 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}`, }); }