




















@@ -11,6 +11,7 @@ const BASH_BIN = process.platform === "win32" ? "bash" : "/bin/bash";
1111const BASH_ARGS = process.platform === "win32" ? [SCRIPT] : ["--noprofile", "--norc", SCRIPT];
1212const BASE_PATH = process.env.PATH ?? "/usr/bin:/bin";
1313const BASE_LANG = process.env.LANG ?? "C";
14+const CANONICAL_TEAM_ID = "FWJYW4S8P8";
1415let fixtureRoot = "";
1516let sharedBinDir = "";
1617let sharedHomeDir = "";
@@ -50,10 +51,20 @@ function parseTeamCandidateRows(raw: string): TeamCandidate[] {
50515152function pickTeamIdFromCandidates(params: {
5253candidates: TeamCandidate[];
54+canonicalTeamId?: string;
5355preferredTeamId?: string;
5456preferredTeamName?: string;
5557preferNonFreeTeam?: boolean;
58+requireCanonical?: boolean;
5659}): string | undefined {
60+const canonicalTeamId = (params.canonicalTeamId ?? CANONICAL_TEAM_ID).trim();
61+if (canonicalTeamId) {
62+const canonical = params.candidates.find((candidate) => candidate.teamId === canonicalTeamId);
63+if (canonical || params.requireCanonical) {
64+return canonical?.teamId;
65+}
66+}
67+5768const preferredTeamId = (params.preferredTeamId ?? "").trim();
5869if (preferredTeamId) {
5970const preferred = params.candidates.find((candidate) => candidate.teamId === preferredTeamId);
@@ -90,6 +101,7 @@ async function writeExecutable(filePath: string, body: string): Promise<void> {
90101function runScript(
91102homeDir: string,
92103extraEnv: Record<string, string> = {},
104+scriptArgs: string[] = [],
93105): {
94106ok: boolean;
95107stdout: string;
@@ -99,7 +111,7 @@ function runScript(
99111.toSorted((a, b) => a.localeCompare(b))
100112.map((key) => `${key}=${extraEnv[key] ?? ""}`)
101113.join("\u0001");
102-const cacheKey = `${homeDir}\u0000${extraEnvKey}`;
114+const cacheKey = `${homeDir}\u0000${extraEnvKey}\u0000${scriptArgs.join("\u0001")}`;
103115const cached = runScriptCache.get(cacheKey);
104116if (cached) {
105117return cached;
@@ -112,7 +124,7 @@ function runScript(
112124 ...extraEnv,
113125};
114126try {
115-const stdout = execFileSync(BASH_BIN, BASH_ARGS, {
127+const stdout = execFileSync(BASH_BIN, [...BASH_ARGS, ...scriptArgs], {
116128 env,
117129encoding: "utf8",
118130stdio: ["ignore", "pipe", "pipe"],
@@ -218,19 +230,93 @@ printf 'BBBBB22222\\t0\\tBeta Team\\r\\n'`,
218230});
219231expect(preferred).toBe("BBBBB22222");
220232233+const missingCanonical = pickTeamIdFromCandidates({
234+candidates: rows,
235+requireCanonical: true,
236+});
237+expect(missingCanonical).toBeUndefined();
238+221239const fallback = pickTeamIdFromCandidates({
222240candidates: rows,
223241preferredTeamId: "CCCCCC3333",
224242});
225243expect(fallback).toBe("BBBBB22222");
226244});
227245246+it("prefers the canonical OpenClaw iOS team when it is present", async () => {
247+const homeDir = makeTempDir(tempDirs, "openclaw-ios-team-id-canonical-");
248+const binDir = path.join(homeDir, "bin");
249+await mkdir(path.join(homeDir, "Library", "Preferences"), { recursive: true });
250+await mkdir(binDir, { recursive: true });
251+await writeFile(
252+path.join(homeDir, "Library", "Preferences", "com.apple.dt.Xcode.plist"),
253+"",
254+"utf8",
255+);
256+const fakePythonPath = path.join(binDir, "fake-python");
257+await writeExecutable(
258+fakePythonPath,
259+`#!/usr/bin/env bash
260+printf 'AAAAA11111\\t0\\tAlpha Team\\r\\n'
261+printf '${CANONICAL_TEAM_ID}\\t0\\tOpenClaw\\r\\n'`,
262+);
263+264+const result = runScript(homeDir, { IOS_PYTHON_BIN: fakePythonPath });
265+expect(result.ok).toBe(true);
266+expect(result.stdout).toBe(CANONICAL_TEAM_ID);
267+});
268+269+it("loads teams from Xcode account identifier team metadata", async () => {
270+const homeDir = makeTempDir(tempDirs, "openclaw-ios-team-id-by-identifier-");
271+const binDir = path.join(homeDir, "bin");
272+await mkdir(path.join(homeDir, "Library", "Preferences"), { recursive: true });
273+await mkdir(binDir, { recursive: true });
274+await writeFile(
275+path.join(homeDir, "Library", "Preferences", "com.apple.dt.Xcode.plist"),
276+"",
277+"utf8",
278+);
279+await writeExecutable(
280+path.join(binDir, "plutil"),
281+`#!/usr/bin/env bash
282+if [[ "$1" == "-extract" && "$2" == "IDEProvisioningTeamByIdentifier" ]]; then
283+ cat <<'JSON'
284+{"account-id":[{"teamID":"FWJYW4S8P8","teamName":"OpenClaw Foundation","isFreeProvisioningTeam":false,"teamType":"Company"}]}
285+JSON
286+ exit 0
287+fi
288+echo '{}'`,
289+);
290+291+const result = runScript(homeDir, { IOS_PYTHON_BIN: "python3" }, ["--require-canonical"]);
292+expect(result.ok).toBe(true);
293+expect(result.stdout).toBe(CANONICAL_TEAM_ID);
294+});
295+228296it("resolves a fallback team ID from Xcode team listings (smoke)", () => {
229297const fallbackResult = runScript(sharedHomeDir, { IOS_PYTHON_BIN: sharedFakePythonPath });
230298expect(fallbackResult.ok).toBe(true);
231299expect(fallbackResult.stdout).toBe("AAAAA11111");
232300});
233301302+it("fails canonical-only resolution when only fallback teams are available", () => {
303+const result = runScript(sharedHomeDir, { IOS_PYTHON_BIN: sharedFakePythonPath }, [
304+"--require-canonical",
305+]);
306+expect(result.ok).toBe(false);
307+expect(result.stderr).toContain(
308+`Canonical OpenClaw iOS Team ID '${CANONICAL_TEAM_ID}' is not available`,
309+);
310+});
311+312+it("rejects explicit non-canonical teams in canonical-only mode", () => {
313+const result = runScript(sharedHomeDir, { IOS_DEVELOPMENT_TEAM: "BBBBB22222" }, [
314+"--require-canonical",
315+]);
316+expect(result.ok).toBe(false);
317+expect(result.stderr).toContain("is not the canonical OpenClaw iOS team");
318+});
319+234320it("prints actionable guidance when Xcode account exists but no Team ID is resolvable", () => {
235321const result = runScript(sharedHomeDir);
236322expect(result.ok).toBe(false);
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。