refactor(signing): 原生侧签名标识符改由 signing.env codegen 生成(单一真相源)
承接 scripts/signing.env(shell 单源):Xcode/Swift 侧的 Team ID/Bundle ID/App Group 等无法 source 变量、只能字面量,故用 codegen 保持单源。 - 新增 scripts/gen-signing.mjs:从 signing.env 读 3 基值,用**结构化正则**(匹配 `DEVELOPMENT_TEAM = X`/app group 形状等,不含任何旧字面值)回填 16 个原生文件的 标识符;--check 为 drift 模式。因不含旧值,codegen 自身无痕、未来再迁账号仍生效。 - ci/check-codegen-drift.sh 追加签名 drift 闸(gen-signing.mjs --check)。 - 全量迁到美国身份:Team 44WULXM6SV / 实体 Yanmei AI LLC / Bundle com.yanmeiai.pangolin (+.PacketTunnel)/ App Group macOS 原生 44WULXM6SV.com.yanmeiai.pangolin、iOS group.com.yanmeiai.pangolin。清理注释/ORGANIZATIONNAME 等 Apple 侧残留痕迹。 - CLAUDE.md 同步新身份 + 指向 signing.env 单源。 验证:plutil lint 6 份 entitlements/plist 全 OK;macOS/iOS pbxproj `xcodebuild -list` 可解析、Runner build settings 解析为新 Team/Bundle;gen-signing.mjs --check 幂等 0 drift; 完整 codegen drift 闸(token/l10n/proto-i18n/signing)全绿。 注:未动 Android 包名 com.pangolin.pangolin_vpn(Play 身份、自签、非 Apple 账号痕迹, 改动影响大另议)与 Windows/桌面少量元数据串。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEVUXAbFT6bF1Qw27RHWoD
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
#!/usr/bin/env node
|
||||
// gen-signing.mjs — 从 scripts/signing.env(单一真相源)回填所有原生文件里的
|
||||
// Apple 签名标识符(Team ID / Bundle ID / App Group / 签名主体)。
|
||||
//
|
||||
// 为什么用 codegen 而非硬编码:Apple 工具链要求这些值以字面量出现在 pbxproj /
|
||||
// entitlements / plist / swift 里,无法像 shell 那样 source 一个变量;又分散在
|
||||
// 十几处,迁账号极易漏。本脚本让 signing.env 成为唯一手改点,一键回填全部字面量。
|
||||
//
|
||||
// 关键设计:所有匹配都是**结构化正则**(匹配 `DEVELOPMENT_TEAM = X`、native app
|
||||
// group 形状等),**不含任何旧的具体值**——所以①本脚本自身无「中国账号痕迹」;
|
||||
// ②未来再迁账号(改 signing.env 里的值)仍能正确回填,不依赖旧字面量。
|
||||
//
|
||||
// 用法:
|
||||
// node scripts/gen-signing.mjs 回填(就地改文件)
|
||||
// node scripts/gen-signing.mjs --check drift 闸:若有文件会被改动则退出 1(不改文件)
|
||||
//
|
||||
// 注:源码注释里遗留的旧标识符是内部字符串、不进产物;本脚本只管功能性字面量
|
||||
// (会被编译/签名进产物的),注释去痕由一次性清理处理。
|
||||
|
||||
import { readFileSync, writeFileSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname, join } from 'node:path';
|
||||
|
||||
const HERE = dirname(fileURLToPath(import.meta.url));
|
||||
const ROOT = join(HERE, '..');
|
||||
const CLIENT = join(ROOT, 'client');
|
||||
|
||||
// ── 读 signing.env 的 3 个基值(去引号),派生其余 ───────────────────────────
|
||||
function readEnv() {
|
||||
const txt = readFileSync(join(HERE, 'signing.env'), 'utf8');
|
||||
const pick = (k) => {
|
||||
const m = txt.match(new RegExp('^' + k + '="?([^"\\n]+)"?\\s*$', 'm'));
|
||||
if (!m) throw new Error(`signing.env 缺 ${k}`);
|
||||
return m[1].trim();
|
||||
};
|
||||
const TEAM = pick('SIGNING_TEAM_ID');
|
||||
const ENTITY = pick('SIGNING_ENTITY');
|
||||
const APP = pick('SIGNING_APP_BUNDLE_ID');
|
||||
return {
|
||||
TEAM, ENTITY, APP,
|
||||
EXT: `${APP}.PacketTunnel`,
|
||||
MAC_GROUP: `${TEAM}.${APP}`,
|
||||
IOS_GROUP: `group.${APP}`,
|
||||
};
|
||||
}
|
||||
|
||||
const V = readEnv();
|
||||
|
||||
// ── 每文件的结构化替换表(路径相对 client/)──────────────────────────────────
|
||||
// 每条 [正则, 替换串]。正则只描述「形状」,替换用派生值填入。
|
||||
const TABLE = {
|
||||
// ---- 通用:两个 pbxproj 的 Team + Bundle ID(按后缀区分 app/ext/tests)----
|
||||
'macos/Runner.xcodeproj/project.pbxproj': [
|
||||
[/DEVELOPMENT_TEAM = [A-Za-z0-9]+;/g, `DEVELOPMENT_TEAM = ${V.TEAM};`],
|
||||
[/PRODUCT_BUNDLE_IDENTIFIER = [\w.]+\.PacketTunnel;/g, `PRODUCT_BUNDLE_IDENTIFIER = ${V.EXT};`],
|
||||
[/PRODUCT_NAME = [\w.]+\.PacketTunnel;/g, `PRODUCT_NAME = ${V.EXT};`],
|
||||
[/PRODUCT_BUNDLE_IDENTIFIER = [\w.]+\.RunnerTests;/g, `PRODUCT_BUNDLE_IDENTIFIER = ${V.APP}.RunnerTests;`],
|
||||
],
|
||||
'ios/Runner.xcodeproj/project.pbxproj': [
|
||||
[/DEVELOPMENT_TEAM = [A-Za-z0-9]+;/g, `DEVELOPMENT_TEAM = ${V.TEAM};`],
|
||||
[/PRODUCT_BUNDLE_IDENTIFIER = [\w.]+\.PacketTunnel;/g, `PRODUCT_BUNDLE_IDENTIFIER = ${V.EXT};`],
|
||||
// 主 app:不以 .PacketTunnel 结尾的那条(负向前瞻,避免误改扩展行)。
|
||||
[/PRODUCT_BUNDLE_IDENTIFIER = (?![\w.]*\.PacketTunnel;)[\w.]+;/g, `PRODUCT_BUNDLE_IDENTIFIER = ${V.APP};`],
|
||||
],
|
||||
// ---- macOS xcconfig(主 app Bundle ID + 版权)----
|
||||
'macos/Runner/Configs/AppInfo.xcconfig': [
|
||||
[/^PRODUCT_BUNDLE_IDENTIFIER = .+$/m, `PRODUCT_BUNDLE_IDENTIFIER = ${V.APP}`],
|
||||
[/^PRODUCT_COPYRIGHT = .+$/m, `PRODUCT_COPYRIGHT = Copyright © 2026 ${V.ENTITY}. All rights reserved.`],
|
||||
],
|
||||
// ---- macOS entitlements:app-identifier 前缀 + native app group ----
|
||||
'macos/Runner/Release.entitlements': [
|
||||
[/\$\(AppIdentifierPrefix\)[\w.]+/g, `$(AppIdentifierPrefix)${V.APP}`],
|
||||
[/<string>[A-Z0-9]{10}\.[\w.]+<\/string>/g, `<string>${V.MAC_GROUP}</string>`],
|
||||
],
|
||||
'macos/Runner/DebugProfile.entitlements': [
|
||||
[/\$\(AppIdentifierPrefix\)[\w.]+/g, `$(AppIdentifierPrefix)${V.APP}`],
|
||||
[/<string>group\.[\w.]+<\/string>/g, `<string>${V.IOS_GROUP}</string>`],
|
||||
],
|
||||
'macos/PacketTunnel/PacketTunnel.entitlements': [
|
||||
[/<string>[A-Z0-9]{10}\.[\w.]+<\/string>/g, `<string>${V.MAC_GROUP}</string>`],
|
||||
],
|
||||
// ---- macOS PacketTunnel Info.plist:NEMachServiceName = app group + .PacketTunnel ----
|
||||
'macos/PacketTunnel/Info.plist': [
|
||||
[/<string>[A-Z0-9]{10}\.[\w.]+\.PacketTunnel<\/string>/g, `<string>${V.MAC_GROUP}.PacketTunnel</string>`],
|
||||
],
|
||||
// ---- macOS Swift / 登录助手脚本 ----
|
||||
'macos/PacketTunnel/PacketTunnelProvider.swift': [
|
||||
[/subsystem: "[\w.]+"/g, `subsystem: "${V.EXT}"`],
|
||||
[/appGroup = "[\w.]+"/g, `appGroup = "${V.MAC_GROUP}"`],
|
||||
],
|
||||
'macos/Runner/VpnChannel.swift': [
|
||||
[/subsystem: "[\w.]+"/g, `subsystem: "${V.APP}"`],
|
||||
[/tunnelBundleId = "[\w.]+"/g, `tunnelBundleId = "${V.EXT}"`],
|
||||
],
|
||||
'macos/Runner/MainFlutterWindow.swift': [
|
||||
[/loginHelperID = "[\w.]+"/g, `loginHelperID = "${V.APP}.LoginHelper"`],
|
||||
],
|
||||
'macos/build_login_helper.sh': [
|
||||
[/HELPER_ID="[\w.]+"/g, `HELPER_ID="${V.APP}.LoginHelper"`],
|
||||
],
|
||||
// ---- iOS entitlements(group. 前缀 app group)----
|
||||
'ios/Runner/Runner.entitlements': [
|
||||
[/<string>group\.[\w.]+<\/string>/g, `<string>${V.IOS_GROUP}</string>`],
|
||||
],
|
||||
'ios/PacketTunnel/PacketTunnel.entitlements': [
|
||||
[/<string>group\.[\w.]+<\/string>/g, `<string>${V.IOS_GROUP}</string>`],
|
||||
],
|
||||
// ---- iOS Swift ----
|
||||
'ios/Runner/VpnManager.swift': [
|
||||
[/extensionBundleId = "[\w.]+"/g, `extensionBundleId = "${V.EXT}"`],
|
||||
[/appGroup = "[\w.]+"/g, `appGroup = "${V.IOS_GROUP}"`],
|
||||
],
|
||||
'ios/PacketTunnel/PacketTunnelProvider.swift': [
|
||||
[/subsystem: "[\w.]+"/g, `subsystem: "${V.EXT}"`],
|
||||
[/appGroup = "[\w.]+"/g, `appGroup = "${V.IOS_GROUP}"`],
|
||||
],
|
||||
'ios/PacketTunnel/MemoryMonitor.swift': [
|
||||
[/subsystem: String = "[\w.]+"/g, `subsystem: String = "${V.EXT}"`],
|
||||
],
|
||||
};
|
||||
|
||||
// ── 执行 ─────────────────────────────────────────────────────────────────────
|
||||
const check = process.argv.includes('--check');
|
||||
let changed = 0;
|
||||
const changedFiles = [];
|
||||
for (const [rel, rules] of Object.entries(TABLE)) {
|
||||
const path = join(CLIENT, rel);
|
||||
const before = readFileSync(path, 'utf8');
|
||||
let after = before;
|
||||
for (const [re, repl] of rules) after = after.replace(re, repl);
|
||||
if (after !== before) {
|
||||
changed++;
|
||||
changedFiles.push(rel);
|
||||
if (!check) writeFileSync(path, after);
|
||||
}
|
||||
}
|
||||
|
||||
if (check) {
|
||||
if (changed) {
|
||||
console.error(`❌ signing codegen drift:${changed} 个文件与 signing.env 不一致:`);
|
||||
for (const f of changedFiles) console.error(' - ' + f);
|
||||
console.error(' 跑 `node scripts/gen-signing.mjs` 回填后重试。');
|
||||
process.exit(1);
|
||||
}
|
||||
console.log('✅ signing codegen 无 drift(原生文件与 signing.env 一致)');
|
||||
} else {
|
||||
console.log(`signing codegen 完成:回填 ${changed} 个文件` + (changed ? ':' : '(已一致,无改动)'));
|
||||
for (const f of changedFiles) console.log(' - ' + f);
|
||||
console.log(` TEAM=${V.TEAM} APP=${V.APP} MAC_GROUP=${V.MAC_GROUP} IOS_GROUP=${V.IOS_GROUP}`);
|
||||
}
|
||||
+9
-5
@@ -1,11 +1,15 @@
|
||||
# signing.env — Apple 签名身份的【单一真相源】(shell 侧)
|
||||
#
|
||||
# 迁移开发者账号 / 改 Bundle ID 时,只改这一个文件;三个签名脚本
|
||||
# (local_test.sh / ci/compile-macos.sh / ci/compile-ios.sh)全部 source 它,
|
||||
# 不再各自硬编码。非机密(Team ID / 实体名 / Bundle ID 本就编进产物、公开),入 git。
|
||||
# 【迁移账号 / 改 Bundle ID 时,改这一个文件就够】,然后跑一次:
|
||||
# node scripts/gen-signing.mjs
|
||||
# 它会把这里的值回填进所有原生文件(pbxproj/entitlements/plist/swift)的字面量
|
||||
# ——Apple 工具链要求这些值以字面量出现、无法 source 变量,故用 codegen 保持单源。
|
||||
#
|
||||
# ⚠️ Xcode 侧另有等价单一真相源 client/Signing.xcconfig(格式不同、内容须一致),
|
||||
# 由 ci/check-signing-config.sh 一致性闸比对,防两处漂移。改这里也要改那里。
|
||||
# 消费方:
|
||||
# - 三个签名脚本(local_test.sh / ci/compile-macos.sh / ci/compile-ios.sh)直接 source 本文件;
|
||||
# - 原生文件(Xcode/Swift)由 scripts/gen-signing.mjs 从本文件回填(勿手改那些标识符)。
|
||||
# drift 闸:ci/check-codegen-drift.sh 跑 `gen-signing.mjs --check`,防改了 env 没重生成 /
|
||||
# 手改了原生标识符。非机密(Team ID / 实体名 / Bundle ID 本就编进产物、公开),入 git。
|
||||
|
||||
# 10 位 Team ID(developer.apple.com → Membership details)。
|
||||
SIGNING_TEAM_ID="44WULXM6SV"
|
||||
|
||||
Reference in New Issue
Block a user