mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
chore: prefer canonical ios signing team
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
// Shared iOS signing defaults for local development + CI.
|
||||
#include "Version.xcconfig"
|
||||
|
||||
OPENCLAW_IOS_DEFAULT_TEAM = Y5PE65HELJ
|
||||
OPENCLAW_IOS_DEFAULT_TEAM = FWJYW4S8P8
|
||||
OPENCLAW_IOS_SELECTED_TEAM = $(OPENCLAW_IOS_DEFAULT_TEAM)
|
||||
OPENCLAW_DEVELOPMENT_TEAM = $(OPENCLAW_IOS_SELECTED_TEAM)
|
||||
OPENCLAW_CODE_SIGN_STYLE = Automatic
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include "Config/Version.xcconfig"
|
||||
|
||||
OPENCLAW_CODE_SIGN_STYLE = Manual
|
||||
OPENCLAW_DEVELOPMENT_TEAM = Y5PE65HELJ
|
||||
OPENCLAW_DEVELOPMENT_TEAM = FWJYW4S8P8
|
||||
|
||||
OPENCLAW_APP_BUNDLE_ID = ai.openclawfoundation.app
|
||||
OPENCLAW_SHARE_BUNDLE_ID = ai.openclawfoundation.app.share
|
||||
|
||||
@@ -53,7 +53,7 @@ Code signing variable (optional in `.env`):
|
||||
IOS_DEVELOPMENT_TEAM=YOUR_TEAM_ID
|
||||
```
|
||||
|
||||
Tip: run `scripts/ios-team-id.sh` from repo root to print a Team ID for `.env`. The helper prefers the canonical OpenClaw team (`Y5PE65HELJ`) when present locally; otherwise it prefers the first non-personal team from your Xcode account (then personal team if needed). Fastlane uses this helper automatically if `IOS_DEVELOPMENT_TEAM` is missing.
|
||||
Tip: run `scripts/ios-team-id.sh --require-canonical` from repo root to verify the canonical OpenClaw iOS team (`FWJYW4S8P8`) is available locally. Fastlane uses the same canonical-only path when `IOS_DEVELOPMENT_TEAM` is missing, and rejects non-canonical teams for beta archives.
|
||||
|
||||
For local/manual iOS builds that stay on direct APNs, configure the gateway host separately with `OPENCLAW_APNS_TEAM_ID`, `OPENCLAW_APNS_KEY_ID`, and either `OPENCLAW_APNS_PRIVATE_KEY_P8` or `OPENCLAW_APNS_PRIVATE_KEY_PATH`. Those gateway runtime env vars are separate from Fastlane's `.env`.
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ TEAM_HELPER="${ROOT_DIR}/scripts/ios-team-id.sh"
|
||||
VERSION_HELPER="${ROOT_DIR}/scripts/ios-write-version-xcconfig.sh"
|
||||
IOS_VERSION_HELPER="${ROOT_DIR}/scripts/ios-version.ts"
|
||||
VERSION_SYNC_HELPER="${ROOT_DIR}/scripts/ios-sync-versioning.ts"
|
||||
CANONICAL_TEAM_ID="FWJYW4S8P8"
|
||||
|
||||
BUILD_NUMBER=""
|
||||
TEAM_ID="${IOS_DEVELOPMENT_TEAM:-}"
|
||||
@@ -114,7 +115,7 @@ if [[ -z "${BUILD_NUMBER}" ]]; then
|
||||
fi
|
||||
|
||||
if [[ -z "${TEAM_ID}" ]]; then
|
||||
TEAM_ID="$(IOS_ALLOW_KEYCHAIN_TEAM_FALLBACK=1 bash "${TEAM_HELPER}")"
|
||||
TEAM_ID="$(IOS_ALLOW_KEYCHAIN_TEAM_FALLBACK=1 bash "${TEAM_HELPER}" --require-canonical)"
|
||||
fi
|
||||
|
||||
if [[ -z "${TEAM_ID}" ]]; then
|
||||
@@ -122,6 +123,11 @@ if [[ -z "${TEAM_ID}" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${TEAM_ID}" != "${CANONICAL_TEAM_ID}" ]]; then
|
||||
echo "iOS beta release must use canonical OpenClaw Team ID ${CANONICAL_TEAM_ID}; got ${TEAM_ID}." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
validate_push_relay_base_url "${PUSH_RELAY_BASE_URL}"
|
||||
|
||||
# `.xcconfig` treats `//` as a comment opener. Break the URL with a helper setting
|
||||
|
||||
+76
-5
@@ -1,12 +1,56 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage:
|
||||
scripts/ios-team-id.sh [--require-canonical]
|
||||
|
||||
Prints an Apple Developer Team ID for iOS signing.
|
||||
|
||||
Default behavior:
|
||||
- return IOS_DEVELOPMENT_TEAM when set
|
||||
- prefer the canonical OpenClaw iOS team when available in Xcode
|
||||
- otherwise fall back to a local Xcode team for local development builds
|
||||
|
||||
Options:
|
||||
--require-canonical fail unless the resolved team is FWJYW4S8P8
|
||||
EOF
|
||||
}
|
||||
|
||||
canonical_team="FWJYW4S8P8"
|
||||
require_canonical="0"
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--require-canonical)
|
||||
require_canonical="1"
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown argument: $1" >&2
|
||||
usage >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
canonical_team="${canonical_team//$'\r'/}"
|
||||
|
||||
if [[ -n "${IOS_DEVELOPMENT_TEAM:-}" ]]; then
|
||||
printf '%s\n' "${IOS_DEVELOPMENT_TEAM}"
|
||||
explicit_team="${IOS_DEVELOPMENT_TEAM//$'\r'/}"
|
||||
if [[ "$require_canonical" == "1" && "$explicit_team" != "$canonical_team" ]]; then
|
||||
echo "Resolved iOS Team ID '${explicit_team}' is not the canonical OpenClaw iOS team '${canonical_team}'." >&2
|
||||
exit 1
|
||||
fi
|
||||
printf '%s\n' "$explicit_team"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
preferred_team="${IOS_PREFERRED_TEAM_ID:-${OPENCLAW_IOS_DEFAULT_TEAM_ID:-Y5PE65HELJ}}"
|
||||
preferred_team="${IOS_PREFERRED_TEAM_ID:-}"
|
||||
preferred_team_name="${IOS_PREFERRED_TEAM_NAME:-}"
|
||||
allow_keychain_fallback="${IOS_ALLOW_KEYCHAIN_TEAM_FALLBACK:-0}"
|
||||
prefer_non_free_team="${IOS_PREFER_NON_FREE_TEAM:-1}"
|
||||
@@ -53,7 +97,8 @@ append_team() {
|
||||
team_names+=("$candidate_name")
|
||||
}
|
||||
|
||||
load_teams_from_xcode_preferences() {
|
||||
load_teams_from_xcode_team_key() {
|
||||
local key="$1"
|
||||
local plist_path="${HOME}/Library/Preferences/com.apple.dt.Xcode.plist"
|
||||
[[ -f "$plist_path" ]] || return 0
|
||||
[[ -n "$python_cmd" ]] || return 0
|
||||
@@ -62,7 +107,7 @@ load_teams_from_xcode_preferences() {
|
||||
[[ -z "$team_id" ]] && continue
|
||||
append_team "$team_id" "${is_free:-0}" "${team_name:-}"
|
||||
done < <(
|
||||
plutil -extract IDEProvisioningTeams json -o - "$plist_path" 2>/dev/null \
|
||||
plutil -extract "$key" json -o - "$plist_path" 2>/dev/null \
|
||||
| "$python_cmd" -c '
|
||||
import json
|
||||
import sys
|
||||
@@ -91,6 +136,11 @@ for teams in data.values():
|
||||
)
|
||||
}
|
||||
|
||||
load_teams_from_xcode_preferences() {
|
||||
load_teams_from_xcode_team_key IDEProvisioningTeamByIdentifier
|
||||
load_teams_from_xcode_team_key IDEProvisioningTeams
|
||||
}
|
||||
|
||||
load_teams_from_legacy_defaults_key() {
|
||||
while IFS= read -r team; do
|
||||
[[ -z "$team" ]] && continue
|
||||
@@ -156,6 +206,12 @@ if [[ ${#team_ids[@]} -eq 0 && "$allow_keychain_fallback" == "1" ]]; then
|
||||
fi
|
||||
|
||||
if [[ ${#team_ids[@]} -eq 0 ]]; then
|
||||
if [[ "$require_canonical" == "1" ]]; then
|
||||
echo "Canonical OpenClaw iOS Team ID '${canonical_team}' is not available in Xcode on this machine." >&2
|
||||
echo "Sign into the Apple Developer account that owns the canonical team, or set IOS_DEVELOPMENT_TEAM=${canonical_team}." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if has_xcode_account; then
|
||||
echo "An Apple account is signed in to Xcode, but no Team ID could be resolved." >&2
|
||||
echo "" >&2
|
||||
@@ -178,12 +234,27 @@ if [[ ${#team_ids[@]} -eq 0 ]]; then
|
||||
fi
|
||||
|
||||
for i in "${!team_ids[@]}"; do
|
||||
if [[ "${team_ids[$i]}" == "$preferred_team" ]]; then
|
||||
if [[ "${team_ids[$i]}" == "$canonical_team" ]]; then
|
||||
printf '%s\n' "${team_ids[$i]}"
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$require_canonical" == "1" ]]; then
|
||||
echo "Canonical OpenClaw iOS Team ID '${canonical_team}' is not available in Xcode on this machine." >&2
|
||||
echo "Sign into the Apple Developer account that owns the canonical team, or set IOS_DEVELOPMENT_TEAM=${canonical_team}." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -n "$preferred_team" ]]; then
|
||||
for i in "${!team_ids[@]}"; do
|
||||
if [[ "${team_ids[$i]}" == "$preferred_team" ]]; then
|
||||
printf '%s\n' "${team_ids[$i]}"
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [[ -n "$preferred_team_name" ]]; then
|
||||
preferred_team_name_lc="$(printf '%s' "$preferred_team_name" | tr '[:upper:]' '[:lower:]')"
|
||||
for i in "${!team_ids[@]}"; do
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
// iOS beta prepare tests cover release-signing guardrails.
|
||||
import { execFileSync } from "node:child_process";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const SCRIPT = path.join(process.cwd(), "scripts", "ios-beta-prepare.sh");
|
||||
const BASH_BIN = process.platform === "win32" ? "bash" : "/bin/bash";
|
||||
const BASH_ARGS = process.platform === "win32" ? [SCRIPT] : ["--noprofile", "--norc", SCRIPT];
|
||||
|
||||
function runPrepare(extraArgs: string[]): { ok: boolean; stdout: string; stderr: string } {
|
||||
try {
|
||||
const stdout = execFileSync(BASH_BIN, [...BASH_ARGS, ...extraArgs], {
|
||||
env: {
|
||||
...process.env,
|
||||
IOS_DEVELOPMENT_TEAM: "Y3YUZP442G",
|
||||
},
|
||||
encoding: "utf8",
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
return { ok: true, stdout: stdout.trim(), stderr: "" };
|
||||
} catch (error) {
|
||||
const e = error as { stdout?: unknown; stderr?: unknown };
|
||||
const stdout =
|
||||
typeof e.stdout === "string"
|
||||
? e.stdout
|
||||
: Buffer.isBuffer(e.stdout)
|
||||
? e.stdout.toString("utf8")
|
||||
: "";
|
||||
const stderr =
|
||||
typeof e.stderr === "string"
|
||||
? e.stderr
|
||||
: Buffer.isBuffer(e.stderr)
|
||||
? e.stderr.toString("utf8")
|
||||
: "";
|
||||
return { ok: false, stdout: stdout.trim(), stderr: stderr.trim() };
|
||||
}
|
||||
}
|
||||
|
||||
describe("scripts/ios-beta-prepare.sh", () => {
|
||||
it("rejects non-canonical beta signing teams before generating release inputs", () => {
|
||||
const result = runPrepare(["--build-number", "7"]);
|
||||
|
||||
expect(result.ok).toBe(false);
|
||||
expect(result.stderr).toContain(
|
||||
"iOS beta release must use canonical OpenClaw Team ID FWJYW4S8P8",
|
||||
);
|
||||
expect(result.stderr).toContain("got Y3YUZP442G");
|
||||
});
|
||||
});
|
||||
@@ -11,6 +11,7 @@ const BASH_BIN = process.platform === "win32" ? "bash" : "/bin/bash";
|
||||
const BASH_ARGS = process.platform === "win32" ? [SCRIPT] : ["--noprofile", "--norc", SCRIPT];
|
||||
const BASE_PATH = process.env.PATH ?? "/usr/bin:/bin";
|
||||
const BASE_LANG = process.env.LANG ?? "C";
|
||||
const CANONICAL_TEAM_ID = "FWJYW4S8P8";
|
||||
let fixtureRoot = "";
|
||||
let sharedBinDir = "";
|
||||
let sharedHomeDir = "";
|
||||
@@ -50,10 +51,20 @@ function parseTeamCandidateRows(raw: string): TeamCandidate[] {
|
||||
|
||||
function pickTeamIdFromCandidates(params: {
|
||||
candidates: TeamCandidate[];
|
||||
canonicalTeamId?: string;
|
||||
preferredTeamId?: string;
|
||||
preferredTeamName?: string;
|
||||
preferNonFreeTeam?: boolean;
|
||||
requireCanonical?: boolean;
|
||||
}): string | undefined {
|
||||
const canonicalTeamId = (params.canonicalTeamId ?? CANONICAL_TEAM_ID).trim();
|
||||
if (canonicalTeamId) {
|
||||
const canonical = params.candidates.find((candidate) => candidate.teamId === canonicalTeamId);
|
||||
if (canonical || params.requireCanonical) {
|
||||
return canonical?.teamId;
|
||||
}
|
||||
}
|
||||
|
||||
const preferredTeamId = (params.preferredTeamId ?? "").trim();
|
||||
if (preferredTeamId) {
|
||||
const preferred = params.candidates.find((candidate) => candidate.teamId === preferredTeamId);
|
||||
@@ -90,6 +101,7 @@ async function writeExecutable(filePath: string, body: string): Promise<void> {
|
||||
function runScript(
|
||||
homeDir: string,
|
||||
extraEnv: Record<string, string> = {},
|
||||
scriptArgs: string[] = [],
|
||||
): {
|
||||
ok: boolean;
|
||||
stdout: string;
|
||||
@@ -99,7 +111,7 @@ function runScript(
|
||||
.toSorted((a, b) => a.localeCompare(b))
|
||||
.map((key) => `${key}=${extraEnv[key] ?? ""}`)
|
||||
.join("\u0001");
|
||||
const cacheKey = `${homeDir}\u0000${extraEnvKey}`;
|
||||
const cacheKey = `${homeDir}\u0000${extraEnvKey}\u0000${scriptArgs.join("\u0001")}`;
|
||||
const cached = runScriptCache.get(cacheKey);
|
||||
if (cached) {
|
||||
return cached;
|
||||
@@ -112,7 +124,7 @@ function runScript(
|
||||
...extraEnv,
|
||||
};
|
||||
try {
|
||||
const stdout = execFileSync(BASH_BIN, BASH_ARGS, {
|
||||
const stdout = execFileSync(BASH_BIN, [...BASH_ARGS, ...scriptArgs], {
|
||||
env,
|
||||
encoding: "utf8",
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
@@ -218,6 +230,12 @@ printf 'BBBBB22222\\t0\\tBeta Team\\r\\n'`,
|
||||
});
|
||||
expect(preferred).toBe("BBBBB22222");
|
||||
|
||||
const missingCanonical = pickTeamIdFromCandidates({
|
||||
candidates: rows,
|
||||
requireCanonical: true,
|
||||
});
|
||||
expect(missingCanonical).toBeUndefined();
|
||||
|
||||
const fallback = pickTeamIdFromCandidates({
|
||||
candidates: rows,
|
||||
preferredTeamId: "CCCCCC3333",
|
||||
@@ -225,12 +243,80 @@ printf 'BBBBB22222\\t0\\tBeta Team\\r\\n'`,
|
||||
expect(fallback).toBe("BBBBB22222");
|
||||
});
|
||||
|
||||
it("prefers the canonical OpenClaw iOS team when it is present", async () => {
|
||||
const homeDir = makeTempDir(tempDirs, "openclaw-ios-team-id-canonical-");
|
||||
const binDir = path.join(homeDir, "bin");
|
||||
await mkdir(path.join(homeDir, "Library", "Preferences"), { recursive: true });
|
||||
await mkdir(binDir, { recursive: true });
|
||||
await writeFile(
|
||||
path.join(homeDir, "Library", "Preferences", "com.apple.dt.Xcode.plist"),
|
||||
"",
|
||||
"utf8",
|
||||
);
|
||||
const fakePythonPath = path.join(binDir, "fake-python");
|
||||
await writeExecutable(
|
||||
fakePythonPath,
|
||||
`#!/usr/bin/env bash
|
||||
printf 'AAAAA11111\\t0\\tAlpha Team\\r\\n'
|
||||
printf '${CANONICAL_TEAM_ID}\\t0\\tOpenClaw\\r\\n'`,
|
||||
);
|
||||
|
||||
const result = runScript(homeDir, { IOS_PYTHON_BIN: fakePythonPath });
|
||||
expect(result.ok).toBe(true);
|
||||
expect(result.stdout).toBe(CANONICAL_TEAM_ID);
|
||||
});
|
||||
|
||||
it("loads teams from Xcode account identifier team metadata", async () => {
|
||||
const homeDir = makeTempDir(tempDirs, "openclaw-ios-team-id-by-identifier-");
|
||||
const binDir = path.join(homeDir, "bin");
|
||||
await mkdir(path.join(homeDir, "Library", "Preferences"), { recursive: true });
|
||||
await mkdir(binDir, { recursive: true });
|
||||
await writeFile(
|
||||
path.join(homeDir, "Library", "Preferences", "com.apple.dt.Xcode.plist"),
|
||||
"",
|
||||
"utf8",
|
||||
);
|
||||
await writeExecutable(
|
||||
path.join(binDir, "plutil"),
|
||||
`#!/usr/bin/env bash
|
||||
if [[ "$1" == "-extract" && "$2" == "IDEProvisioningTeamByIdentifier" ]]; then
|
||||
cat <<'JSON'
|
||||
{"account-id":[{"teamID":"FWJYW4S8P8","teamName":"OpenClaw Foundation","isFreeProvisioningTeam":false,"teamType":"Company"}]}
|
||||
JSON
|
||||
exit 0
|
||||
fi
|
||||
echo '{}'`,
|
||||
);
|
||||
|
||||
const result = runScript(homeDir, { IOS_PYTHON_BIN: "python3" }, ["--require-canonical"]);
|
||||
expect(result.ok).toBe(true);
|
||||
expect(result.stdout).toBe(CANONICAL_TEAM_ID);
|
||||
});
|
||||
|
||||
it("resolves a fallback team ID from Xcode team listings (smoke)", () => {
|
||||
const fallbackResult = runScript(sharedHomeDir, { IOS_PYTHON_BIN: sharedFakePythonPath });
|
||||
expect(fallbackResult.ok).toBe(true);
|
||||
expect(fallbackResult.stdout).toBe("AAAAA11111");
|
||||
});
|
||||
|
||||
it("fails canonical-only resolution when only fallback teams are available", () => {
|
||||
const result = runScript(sharedHomeDir, { IOS_PYTHON_BIN: sharedFakePythonPath }, [
|
||||
"--require-canonical",
|
||||
]);
|
||||
expect(result.ok).toBe(false);
|
||||
expect(result.stderr).toContain(
|
||||
`Canonical OpenClaw iOS Team ID '${CANONICAL_TEAM_ID}' is not available`,
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects explicit non-canonical teams in canonical-only mode", () => {
|
||||
const result = runScript(sharedHomeDir, { IOS_DEVELOPMENT_TEAM: "BBBBB22222" }, [
|
||||
"--require-canonical",
|
||||
]);
|
||||
expect(result.ok).toBe(false);
|
||||
expect(result.stderr).toContain("is not the canonical OpenClaw iOS team");
|
||||
});
|
||||
|
||||
it("prints actionable guidance when Xcode account exists but no Team ID is resolvable", () => {
|
||||
const result = runScript(sharedHomeDir);
|
||||
expect(result.ok).toBe(false);
|
||||
|
||||
Reference in New Issue
Block a user