





















11// iOS IPA validation tests cover the App Store upload gate without real signing assets.
22import { execFileSync } from "node:child_process";
3-import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3+import {
4+chmodSync,
5+mkdirSync,
6+mkdtempSync,
7+readdirSync,
8+readFileSync,
9+rmSync,
10+writeFileSync,
11+} from "node:fs";
412import os from "node:os";
513import path from "node:path";
14+import JSZip from "jszip";
615import { afterEach, describe, expect, it } from "vitest";
716817const SCRIPT = path.join(process.cwd(), "scripts", "ios-validate-app-store-ipa.sh");
@@ -104,15 +113,77 @@ if (Array.isArray(current)) {
104113);
105114}
106115107-function writeValidFixture(
116+function writeFakeUnzip(filePath: string): void {
117+writeExecutable(
118+filePath,
119+`#!/usr/bin/env node
120+const { mkdirSync, readFileSync, writeFileSync } = require("node:fs");
121+const { createRequire } = require("node:module");
122+const path = require("node:path");
123+const requireFromRepo = createRequire(path.join(process.cwd(), "package.json"));
124+const JSZip = requireFromRepo("jszip");
125+126+const args = process.argv.slice(2);
127+let ipaPath = "";
128+let outputDir = "";
129+for (let i = 0; i < args.length; i++) {
130+ const arg = args[i];
131+ if (arg === "-d") {
132+ outputDir = args[++i] || "";
133+ } else if (!arg.startsWith("-")) {
134+ ipaPath = arg;
135+ }
136+}
137+if (!ipaPath || !outputDir) process.exit(2);
138+139+(async () => {
140+ const zip = await JSZip.loadAsync(readFileSync(ipaPath));
141+ for (const [entryPath, entry] of Object.entries(zip.files)) {
142+ const outputPath = path.join(outputDir, entryPath);
143+ if (entry.dir) {
144+ mkdirSync(outputPath, { recursive: true });
145+ continue;
146+ }
147+ mkdirSync(path.dirname(outputPath), { recursive: true });
148+ writeFileSync(outputPath, await entry.async("nodebuffer"));
149+ }
150+})().catch(() => process.exit(1));
151+`,
152+);
153+}
154+155+async function writeIpaFixture(root: string): Promise<string> {
156+const zip = new JSZip();
157+158+function addTree(dirPath: string, zipPath: string): void {
159+for (const entry of readdirSync(dirPath, { withFileTypes: true })) {
160+const sourcePath = path.join(dirPath, entry.name);
161+const entryZipPath = `${zipPath}/${entry.name}`;
162+if (entry.isDirectory()) {
163+addTree(sourcePath, entryZipPath);
164+} else if (entry.isFile()) {
165+zip.file(entryZipPath, readFileSync(sourcePath), { date: new Date(0) });
166+}
167+}
168+}
169+170+addTree(path.join(root, "Payload"), "Payload");
171+const ipaPath = path.join(root, "OpenClaw.ipa");
172+const buffer = await zip.generateAsync({ type: "nodebuffer", compression: "DEFLATE" });
173+writeFileSync(ipaPath, buffer);
174+return ipaPath;
175+}
176+177+async function writeValidFixture(
108178root: string,
109179options: { pushMode?: string; legacyKey?: boolean } = {},
110-): {
180+): Promise<{
111181ipaPath: string;
112182plistBuddy: string;
113183codesign: string;
114184security: string;
115-} {
185+unzip: string;
186+}> {
116187const binDir = path.join(root, "bin");
117188const payloadDir = path.join(root, "Payload");
118189const appDir = path.join(payloadDir, "OpenClaw.app");
@@ -172,6 +243,8 @@ function writeValidFixture(
172243173244const plistBuddy = path.join(binDir, "plistbuddy");
174245writeFakePlistBuddy(plistBuddy);
246+const unzip = path.join(binDir, "unzip");
247+writeFakeUnzip(unzip);
175248const codesign = path.join(binDir, "codesign");
176249writeExecutable(
177250codesign,
@@ -189,16 +262,16 @@ cat "${profilePath}"
189262`,
190263);
191264192-const ipaPath = path.join(root, "OpenClaw.ipa");
193-execFileSync("zip", ["-qry", ipaPath, "Payload"], { cwd: root });
194-return { ipaPath, plistBuddy, codesign, security };
265+const ipaPath = await writeIpaFixture(root);
266+return { ipaPath, plistBuddy, codesign, security, unzip };
195267}
196268197269function runValidator(fixture: {
198270ipaPath: string;
199271plistBuddy: string;
200272codesign: string;
201273security: string;
274+unzip: string;
202275}): { ok: boolean; stdout: string; stderr: string } {
203276try {
204277const stdout = execFileSync(BASH_BIN, [...bashArgs(SCRIPT), "--ipa", fixture.ipaPath], {
@@ -208,6 +281,7 @@ function runValidator(fixture: {
208281IOS_VALIDATE_PLIST_BUDDY_BIN: fixture.plistBuddy,
209282IOS_VALIDATE_CODESIGN_BIN: fixture.codesign,
210283IOS_VALIDATE_SECURITY_BIN: fixture.security,
284+IOS_VALIDATE_UNZIP_BIN: fixture.unzip,
211285},
212286encoding: "utf8",
213287stdio: ["ignore", "pipe", "pipe"],
@@ -228,32 +302,32 @@ describe("scripts/ios-validate-app-store-ipa.sh", () => {
228302}
229303});
230304231-it("accepts an App Store IPA with appStore mode and production entitlements", () => {
305+it("accepts an App Store IPA with appStore mode and production entitlements", async () => {
232306const root = mkdtempSync(path.join(os.tmpdir(), "openclaw-ios-ipa-"));
233307tempDirs.push(root);
234-const fixture = writeValidFixture(root);
308+const fixture = await writeValidFixture(root);
235309236310const result = runValidator(fixture);
237311238312expect(result.ok).toBe(true);
239313expect(result.stdout).toContain("Validated iOS App Store IPA");
240314});
241315242-it("rejects an IPA that was exported with a non-App-Store push mode", () => {
316+it("rejects an IPA that was exported with a non-App-Store push mode", async () => {
243317const root = mkdtempSync(path.join(os.tmpdir(), "openclaw-ios-ipa-"));
244318tempDirs.push(root);
245-const fixture = writeValidFixture(root, { pushMode: "localProduction" });
319+const fixture = await writeValidFixture(root, { pushMode: "localProduction" });
246320247321const result = runValidator(fixture);
248322249323expect(result.ok).toBe(false);
250324expect(result.stderr).toContain("push mode mismatch");
251325});
252326253-it("rejects legacy independently selectable production push keys", () => {
327+it("rejects legacy independently selectable production push keys", async () => {
254328const root = mkdtempSync(path.join(os.tmpdir(), "openclaw-ios-ipa-"));
255329tempDirs.push(root);
256-const fixture = writeValidFixture(root, { legacyKey: true });
330+const fixture = await writeValidFixture(root, { legacyKey: true });
257331258332const result = runValidator(fixture);
259333此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。