Skip to content

Commit 06fe3ba

Browse files
authored
fix(workboard): preserve json list archive visibility
1 parent d561242 commit 06fe3ba

5 files changed

Lines changed: 68 additions & 38 deletions

File tree

apps/ios/fastlane/Fastfile

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -925,17 +925,40 @@ end
925925

926926
def verify_app_store_screenshot_set!(app_screenshot_set:, screenshots:, locale:, display_type:)
927927
expected = app_store_screenshot_expected_rows(screenshots)
928-
actual = app_store_screenshot_actual_rows(app_screenshot_set)
929-
actual_identity = actual.map { |row| { checksum: row[:checksum], file_name: row[:file_name] } }
930-
incomplete = actual.reject { |row| row[:state] == "COMPLETE" }
928+
timeout_seconds = app_store_screenshot_processing_timeout_seconds
929+
deadline = Time.now + timeout_seconds
930+
actual = []
931931

932-
return if actual_identity == expected && incomplete.empty?
932+
loop do
933+
app_screenshot_set = Spaceship::ConnectAPI::AppScreenshotSet.get(app_screenshot_set_id: app_screenshot_set.id)
934+
actual = app_store_screenshot_actual_rows(app_screenshot_set)
935+
actual_identity = actual.map { |row| { checksum: row[:checksum], file_name: row[:file_name] } }
936+
incomplete = actual.reject { |row| row[:state] == "COMPLETE" }
933937

934-
UI.user_error!(
935-
"App Store Connect screenshot verification failed for #{locale} #{display_type}. " \
936-
"Expected: #{format_app_store_screenshot_rows(expected)}. " \
937-
"Actual: #{format_app_store_screenshot_rows(actual)}."
938-
)
938+
return if actual_identity == expected && incomplete.empty?
939+
940+
if actual.length > expected.length
941+
UI.user_error!(
942+
"App Store Connect screenshot verification failed for #{locale} #{display_type}. " \
943+
"Expected: #{format_app_store_screenshot_rows(expected)}. " \
944+
"Actual: #{format_app_store_screenshot_rows(actual)}."
945+
)
946+
end
947+
948+
if Time.now >= deadline
949+
UI.user_error!(
950+
"Timed out after #{timeout_seconds}s waiting for App Store Connect screenshot verification for #{locale} #{display_type}. " \
951+
"Expected: #{format_app_store_screenshot_rows(expected)}. " \
952+
"Actual: #{format_app_store_screenshot_rows(actual)}."
953+
)
954+
end
955+
956+
UI.verbose(
957+
"Waiting for App Store Connect screenshot verification for #{locale} #{display_type}: " \
958+
"#{format_app_store_screenshot_rows(actual)}."
959+
)
960+
sleep(APP_STORE_SCREENSHOT_PROCESSING_POLL_SECONDS)
961+
end
939962
end
940963

941964
def replace_app_store_screenshot_set!(localization:, display_type:, screenshots:)

scripts/qa-lab-up.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,9 @@ Options:
3333
}
3434

3535
function parseQaLabUpArgs(argv: readonly string[]) {
36+
const args = argv[0] === "--" ? argv.slice(1) : argv;
3637
return parseArgs({
37-
args: [...argv],
38+
args: [...args],
3839
options,
3940
allowPositionals: false,
4041
}).values;

src/cli/ports.ts

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { execFileSync } from "node:child_process";
33
import { createServer } from "node:net";
44
import { formatErrorMessage } from "../infra/errors.js";
55
import { resolveLsofCommandSync } from "../infra/ports-lsof.js";
6-
import { tryListenOnPort } from "../infra/ports-probe.js";
6+
import { probePortUsage } from "../infra/ports-probe.js";
77
import { getWindowsSystem32ExePath } from "../infra/windows-install-roots.js";
88
import { resolvePositiveTimerTimeoutMs, resolveTimerTimeoutMs } from "../shared/number-coercion.js";
99
import { sleep } from "../utils.js";
@@ -131,16 +131,12 @@ function killPortWithFuser(port: number, signal: "SIGTERM" | "SIGKILL"): PortPro
131131
}
132132

133133
async function isPortBusy(port: number): Promise<boolean> {
134-
try {
135-
await tryListenOnPort({ port, exclusive: true });
136-
return false;
137-
} catch (err: unknown) {
138-
const code = (err as NodeJS.ErrnoException).code;
139-
if (code === "EADDRINUSE") {
140-
return true;
141-
}
142-
throw err instanceof Error ? err : new Error(String(err));
143-
}
134+
// Route through probePortUsage which probes all four endpoints
135+
// (127.0.0.1, 0.0.0.0, ::1, ::) instead of a single hostless bind
136+
// that defaults to IPv6 wildcard and misses IPv4-only occupants.
137+
// Treat "unknown" as busy — inconclusive probe failures must not cause
138+
// forceFreePortAndWait to exit early before lsof/fuser can inspect.
139+
return (await probePortUsage(port)) !== "free";
144140
}
145141

146142
export function parseLsofOutput(output: string): PortProcess[] {

src/cli/program.force.test.ts

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ vi.mock("node:child_process", async () => {
99
};
1010
});
1111

12-
const tryListenOnPortMock = vi.hoisted(() => vi.fn());
12+
const probePortUsageMock = vi.hoisted(() => vi.fn());
1313

1414
vi.mock("../infra/ports-probe.js", () => ({
15-
tryListenOnPort: (...args: unknown[]) => tryListenOnPortMock(...args),
15+
probePortUsage: (...args: unknown[]) => probePortUsageMock(...args),
1616
}));
1717

1818
import { execFileSync } from "node:child_process";
@@ -33,10 +33,8 @@ describe("gateway --force helpers", () => {
3333
vi.clearAllMocks();
3434
originalKill = process.kill.bind(process);
3535
originalPlatform = process.platform;
36-
tryListenOnPortMock.mockReset();
37-
tryListenOnPortMock.mockRejectedValue(
38-
Object.assign(new Error("in use"), { code: "EADDRINUSE" }),
39-
);
36+
probePortUsageMock.mockReset();
37+
probePortUsageMock.mockResolvedValue("busy");
4038
// Pin to linux so all lsof tests are platform-invariant.
4139
Object.defineProperty(process, "platform", { value: "linux", configurable: true });
4240
});
@@ -65,7 +63,7 @@ describe("gateway --force helpers", () => {
6563
});
6664

6765
it("skips lsof when the port is already bindable", async () => {
68-
tryListenOnPortMock.mockResolvedValue(undefined);
66+
probePortUsageMock.mockResolvedValue("free");
6967

7068
const result = await forceFreePortAndWait(18789, { timeoutMs: 500, intervalMs: 100 });
7169

@@ -199,9 +197,7 @@ describe("gateway --force helpers", () => {
199197
}
200198
return "18789/tcp: 4242\n";
201199
});
202-
tryListenOnPortMock
203-
.mockRejectedValueOnce(Object.assign(new Error("in use"), { code: "EADDRINUSE" }))
204-
.mockResolvedValue(undefined);
200+
probePortUsageMock.mockResolvedValueOnce("busy").mockResolvedValue("free");
205201

206202
const result = await forceFreePortAndWait(18789, { timeoutMs: 500, intervalMs: 100 });
207203

@@ -231,13 +227,12 @@ describe("gateway --force helpers", () => {
231227
return "";
232228
});
233229

234-
const busyErr = Object.assign(new Error("in use"), { code: "EADDRINUSE" });
235-
tryListenOnPortMock
236-
.mockRejectedValueOnce(busyErr)
237-
.mockRejectedValueOnce(busyErr)
238-
.mockRejectedValueOnce(busyErr)
239-
.mockRejectedValueOnce(busyErr)
240-
.mockResolvedValueOnce(undefined);
230+
probePortUsageMock
231+
.mockResolvedValueOnce("busy")
232+
.mockResolvedValueOnce("busy")
233+
.mockResolvedValueOnce("busy")
234+
.mockResolvedValueOnce("busy")
235+
.mockResolvedValue("free");
241236

242237
const promise = forceFreePortAndWait(18789, {
243238
timeoutMs: 300,
@@ -258,6 +253,8 @@ describe("gateway --force helpers", () => {
258253
});
259254

260255
it("throws when lsof is unavailable and fuser is missing", async () => {
256+
// An inconclusive four-host probe must continue into the cleanup tools.
257+
probePortUsageMock.mockResolvedValue("unknown");
261258
(execFileSync as unknown as Mock).mockImplementation((cmd: string) => {
262259
const err = new Error(`spawnSync ${cmd} ENOENT`) as NodeJS.ErrnoException;
263260
err.code = "ENOENT";

test/scripts/qa-lab-up.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,19 @@ describe("scripts/qa-lab-up", () => {
3434
);
3535
});
3636

37+
it("accepts the pnpm run argument separator", async () => {
38+
const runQaDockerUpCommand = vi.fn(async () => {});
39+
const loadRuntime = vi.fn(async () => ({ runQaDockerUpCommand }));
40+
41+
await expect(
42+
qaLabUpTesting.runQaLabUp(["--", "--gateway-port", "4100"], { loadRuntime }),
43+
).resolves.toBe(0);
44+
45+
expect(runQaDockerUpCommand).toHaveBeenCalledWith(
46+
expect.objectContaining({ gatewayPort: 4100 }),
47+
);
48+
});
49+
3750
it("accepts the maximum TCP port before loading the Docker runtime", async () => {
3851
const runQaDockerUpCommand = vi.fn(async () => {});
3952
const loadRuntime = vi.fn(async () => ({ runQaDockerUpCommand }));

0 commit comments

Comments
 (0)