@pushpalsdev/cli 1.0.97 → 1.0.99
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -53,6 +53,12 @@ function ensureSandboxGitConfig(homeDir: string): void {
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
function withNodeDnsIpv4First(value: string | undefined): string {
|
|
57
|
+
const existing = (value ?? "").trim();
|
|
58
|
+
if (/(^|\s)--dns-result-order=/.test(existing)) return existing;
|
|
59
|
+
return [existing, "--dns-result-order=ipv4first"].filter(Boolean).join(" ");
|
|
60
|
+
}
|
|
61
|
+
|
|
56
62
|
function resolveOriginalHome(env: Record<string, string>): string {
|
|
57
63
|
return env.HOME || env.USERPROFILE || homedir();
|
|
58
64
|
}
|
|
@@ -100,6 +106,8 @@ export function buildWorkerSandboxWritableEnv(
|
|
|
100
106
|
EXPO_NO_INTERACTIVE: env.EXPO_NO_INTERACTIVE ?? "1",
|
|
101
107
|
CI: env.CI ?? "1",
|
|
102
108
|
BROWSER: env.BROWSER ?? "none",
|
|
109
|
+
NODE_OPTIONS: withNodeDnsIpv4First(env.NODE_OPTIONS),
|
|
110
|
+
REACT_NATIVE_PACKAGER_HOSTNAME: env.REACT_NATIVE_PACKAGER_HOSTNAME ?? "127.0.0.1",
|
|
103
111
|
EXPO_DEV_SERVER_PORT: env.EXPO_DEV_SERVER_PORT ?? defaultExpoPort,
|
|
104
112
|
RCT_METRO_PORT: env.RCT_METRO_PORT ?? defaultExpoPort,
|
|
105
113
|
PUSHPALS_VALIDATION_REPO: repo,
|
|
@@ -647,7 +647,49 @@ async function terminateValidationProcessTree(
|
|
|
647
647
|
}
|
|
648
648
|
}
|
|
649
649
|
|
|
650
|
-
|
|
650
|
+
function captureValidationStream(stream: ReadableStream<Uint8Array> | null) {
|
|
651
|
+
let text = "";
|
|
652
|
+
let done = false;
|
|
653
|
+
const reader = stream?.getReader();
|
|
654
|
+
const promise = reader
|
|
655
|
+
? (async () => {
|
|
656
|
+
try {
|
|
657
|
+
while (true) {
|
|
658
|
+
const result = await reader.read();
|
|
659
|
+
if (result.done) break;
|
|
660
|
+
text += Buffer.from(result.value).toString("utf8");
|
|
661
|
+
}
|
|
662
|
+
} catch {
|
|
663
|
+
// Stream cancellation after process exit is expected when descendants
|
|
664
|
+
// inherit pipes from failed browser/dev-server launchers.
|
|
665
|
+
} finally {
|
|
666
|
+
done = true;
|
|
667
|
+
try {
|
|
668
|
+
reader.releaseLock();
|
|
669
|
+
} catch {
|
|
670
|
+
// ignore
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
})()
|
|
674
|
+
: Promise.resolve().then(() => {
|
|
675
|
+
done = true;
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
return {
|
|
679
|
+
cancel: async () => {
|
|
680
|
+
try {
|
|
681
|
+
await reader?.cancel();
|
|
682
|
+
} catch {
|
|
683
|
+
// ignore
|
|
684
|
+
}
|
|
685
|
+
},
|
|
686
|
+
isDone: () => done,
|
|
687
|
+
promise,
|
|
688
|
+
text: () => text,
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
export async function runValidationArgv(
|
|
651
693
|
repo: string,
|
|
652
694
|
command: string,
|
|
653
695
|
argv: string[],
|
|
@@ -665,31 +707,52 @@ async function runValidationArgv(
|
|
|
665
707
|
stderr: "pipe",
|
|
666
708
|
detached: process.platform !== "win32",
|
|
667
709
|
});
|
|
710
|
+
const stdoutCapture = captureValidationStream(proc.stdout);
|
|
711
|
+
const stderrCapture = captureValidationStream(proc.stderr);
|
|
668
712
|
let timedOut = false;
|
|
669
|
-
const
|
|
670
|
-
|
|
713
|
+
const timeout = Math.max(1_000, timeoutMs);
|
|
714
|
+
let timeoutTimer: ReturnType<typeof setTimeout> | null = null;
|
|
715
|
+
const timeoutPromise = new Promise<"timeout">((resolveTimeout) => {
|
|
716
|
+
timeoutTimer = setTimeout(() => {
|
|
671
717
|
timedOut = true;
|
|
672
718
|
void terminateValidationProcessTree(proc);
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
);
|
|
719
|
+
resolveTimeout("timeout");
|
|
720
|
+
}, timeout);
|
|
721
|
+
});
|
|
722
|
+
const exitOrTimeout = await Promise.race([
|
|
723
|
+
proc.exited.then((code) => ({ type: "exit" as const, code })),
|
|
724
|
+
timeoutPromise,
|
|
725
|
+
]);
|
|
726
|
+
if (timeoutTimer) clearTimeout(timeoutTimer);
|
|
727
|
+
const exitCode = exitOrTimeout === "timeout" ? 124 : exitOrTimeout.code;
|
|
728
|
+
|
|
729
|
+
if (!timedOut) {
|
|
730
|
+
await Promise.race([
|
|
731
|
+
Promise.all([stdoutCapture.promise, stderrCapture.promise]),
|
|
732
|
+
Bun.sleep(1_000),
|
|
733
|
+
]);
|
|
734
|
+
if (!stdoutCapture.isDone() || !stderrCapture.isDone()) {
|
|
735
|
+
await terminateValidationProcessTree(proc);
|
|
736
|
+
await Promise.all([stdoutCapture.cancel(), stderrCapture.cancel()]);
|
|
737
|
+
}
|
|
738
|
+
} else {
|
|
739
|
+
await Promise.all([stdoutCapture.cancel(), stderrCapture.cancel()]);
|
|
740
|
+
}
|
|
676
741
|
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
proc.exited,
|
|
742
|
+
await Promise.race([
|
|
743
|
+
Promise.all([stdoutCapture.promise, stderrCapture.promise]),
|
|
744
|
+
Bun.sleep(500),
|
|
681
745
|
]);
|
|
682
|
-
clearTimeout(timer);
|
|
683
746
|
|
|
684
747
|
return {
|
|
685
748
|
step: command,
|
|
686
749
|
command,
|
|
687
750
|
ok: !timedOut && exitCode === 0,
|
|
688
|
-
exitCode
|
|
689
|
-
stdout: compactJobOutput(
|
|
751
|
+
exitCode,
|
|
752
|
+
stdout: compactJobOutput(stdoutCapture.text().trim(), outputPolicy),
|
|
690
753
|
stderr: compactJobOutput(
|
|
691
754
|
[
|
|
692
|
-
|
|
755
|
+
stderrCapture.text().trim(),
|
|
693
756
|
timedOut ? timeoutMessage : "",
|
|
694
757
|
]
|
|
695
758
|
.filter(Boolean)
|
|
@@ -883,13 +946,69 @@ export function shouldEnsurePlaywrightBrowserRuntime(repo: string, command: stri
|
|
|
883
946
|
);
|
|
884
947
|
}
|
|
885
948
|
|
|
886
|
-
|
|
887
|
-
|
|
949
|
+
const PLAYWRIGHT_BROWSER_INSTALL_TARGETS = new Set([
|
|
950
|
+
"chromium",
|
|
951
|
+
"chrome",
|
|
952
|
+
"chrome-beta",
|
|
953
|
+
"chrome-dev",
|
|
954
|
+
"chrome-canary",
|
|
955
|
+
"msedge",
|
|
956
|
+
"msedge-beta",
|
|
957
|
+
"msedge-dev",
|
|
958
|
+
"msedge-canary",
|
|
959
|
+
"firefox",
|
|
960
|
+
"webkit",
|
|
961
|
+
]);
|
|
962
|
+
|
|
963
|
+
function addPlaywrightInstallTarget(targets: Set<string>, rawValue: string): void {
|
|
964
|
+
const value = rawValue.trim().toLowerCase();
|
|
965
|
+
if (!value) return;
|
|
966
|
+
const normalized = value === "edge" ? "msedge" : value;
|
|
967
|
+
if (PLAYWRIGHT_BROWSER_INSTALL_TARGETS.has(normalized)) {
|
|
968
|
+
targets.add(normalized);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
export function inferPlaywrightBrowserInstallTargets(repo: string, command: string): string[] {
|
|
973
|
+
const targets = new Set<string>(["chromium"]);
|
|
974
|
+
const script = resolvePackageScriptForValidationCommand(repo, command);
|
|
975
|
+
const scriptText = script
|
|
976
|
+
? `${script.script}\n${readReferencedValidationScriptText(script.cwd, script.script)}`
|
|
977
|
+
: "";
|
|
978
|
+
const text = `${command}\n${scriptText}`;
|
|
979
|
+
|
|
980
|
+
for (const match of text.matchAll(/\bchannel\s*:\s*["'`]([^"'`]+)["'`]/gi)) {
|
|
981
|
+
addPlaywrightInstallTarget(targets, match[1] ?? "");
|
|
982
|
+
}
|
|
983
|
+
for (const match of text.matchAll(/\bbrowserName\s*:\s*["'`]([^"'`]+)["'`]/gi)) {
|
|
984
|
+
addPlaywrightInstallTarget(targets, match[1] ?? "");
|
|
985
|
+
}
|
|
986
|
+
for (const match of text.matchAll(/(?:^|\s)(?:--browser|--browser-name|--channel)[=\s]+["'`]?([A-Za-z0-9_-]+)/gi)) {
|
|
987
|
+
addPlaywrightInstallTarget(targets, match[1] ?? "");
|
|
988
|
+
}
|
|
989
|
+
for (const target of PLAYWRIGHT_BROWSER_INSTALL_TARGETS) {
|
|
990
|
+
const escaped = target.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
991
|
+
if (new RegExp(`\\b${escaped}\\s*\\.\\s*launch\\b`, "i").test(text)) {
|
|
992
|
+
addPlaywrightInstallTarget(targets, target);
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
return Array.from(targets).sort((a, b) => {
|
|
997
|
+
if (a === "chromium") return -1;
|
|
998
|
+
if (b === "chromium") return 1;
|
|
999
|
+
return a.localeCompare(b);
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
export function playwrightBrowserInstallArgv(targets: string[] = ["chromium"]): string[] {
|
|
1004
|
+
const installTargets = Array.from(new Set(targets.map((target) => target.trim()).filter(Boolean)));
|
|
1005
|
+
return ["bunx", "playwright", "install", ...(installTargets.length > 0 ? installTargets : ["chromium"])];
|
|
888
1006
|
}
|
|
889
1007
|
|
|
890
1008
|
async function runPlaywrightBrowserRuntimePreflight(
|
|
891
1009
|
repo: string,
|
|
892
1010
|
command: string,
|
|
1011
|
+
targets: string[],
|
|
893
1012
|
timeoutMs: number,
|
|
894
1013
|
outputPolicy: Partial<OutputCompactionPolicy>,
|
|
895
1014
|
): Promise<ValidationExecutionResult> {
|
|
@@ -898,11 +1017,11 @@ async function runPlaywrightBrowserRuntimePreflight(
|
|
|
898
1017
|
return runValidationArgv(
|
|
899
1018
|
repo,
|
|
900
1019
|
command,
|
|
901
|
-
playwrightBrowserInstallArgv(),
|
|
1020
|
+
playwrightBrowserInstallArgv(targets),
|
|
902
1021
|
env,
|
|
903
1022
|
timeout,
|
|
904
1023
|
outputPolicy,
|
|
905
|
-
`Browser runtime preflight timed out after ${timeout}ms while ensuring Playwright
|
|
1024
|
+
`Browser runtime preflight timed out after ${timeout}ms while ensuring Playwright browser target(s): ${targets.join(", ")}. Captured output is the process output emitted before PushPals terminated the installer process tree.`,
|
|
906
1025
|
);
|
|
907
1026
|
}
|
|
908
1027
|
|
|
@@ -1186,6 +1305,10 @@ function detectValidationBlocker(runs: ValidationExecutionResult[]): ValidationB
|
|
|
1186
1305
|
combined.includes("network access") ||
|
|
1187
1306
|
combined.includes("connection refused") ||
|
|
1188
1307
|
combined.includes("getaddrinfo") ||
|
|
1308
|
+
combined.includes("err_socket_bad_port") ||
|
|
1309
|
+
combined.includes("expo exited early") ||
|
|
1310
|
+
combined.includes("eperm") ||
|
|
1311
|
+
combined.includes("operation not permitted") ||
|
|
1189
1312
|
combined.includes("eacces")
|
|
1190
1313
|
) {
|
|
1191
1314
|
return {
|
|
@@ -1772,7 +1895,7 @@ async function runDeterministicQualityGate(
|
|
|
1772
1895
|
)}`,
|
|
1773
1896
|
);
|
|
1774
1897
|
}
|
|
1775
|
-
|
|
1898
|
+
const playwrightBrowserRuntimeReadyTargets = new Set<string>();
|
|
1776
1899
|
for (const command of commandsToRun) {
|
|
1777
1900
|
const commandMissingTools = requirementsForValidationCommand(toolchainPlan, command).filter(
|
|
1778
1901
|
(requirement) =>
|
|
@@ -1801,17 +1924,25 @@ async function runDeterministicQualityGate(
|
|
|
1801
1924
|
repo,
|
|
1802
1925
|
command,
|
|
1803
1926
|
);
|
|
1927
|
+
const playwrightBrowserTargets = commandNeedsPlaywrightBrowserRuntime
|
|
1928
|
+
? inferPlaywrightBrowserInstallTargets(repo, command)
|
|
1929
|
+
: [];
|
|
1930
|
+
const missingPlaywrightBrowserTargets = playwrightBrowserTargets.filter(
|
|
1931
|
+
(target) => !playwrightBrowserRuntimeReadyTargets.has(target),
|
|
1932
|
+
);
|
|
1804
1933
|
let commandBrowserRuntimeEnsured =
|
|
1805
|
-
|
|
1806
|
-
|
|
1934
|
+
commandNeedsPlaywrightBrowserRuntime &&
|
|
1935
|
+
missingPlaywrightBrowserTargets.length === 0;
|
|
1936
|
+
if (missingPlaywrightBrowserTargets.length > 0) {
|
|
1807
1937
|
const browserEnv = buildWorkerSandboxWritableEnv(repo);
|
|
1808
1938
|
onLog?.(
|
|
1809
1939
|
"stdout",
|
|
1810
|
-
`[ValidationGate] Browser runtime preflight: ensuring Playwright
|
|
1940
|
+
`[ValidationGate] Browser runtime preflight: ensuring Playwright browser target(s) ${missingPlaywrightBrowserTargets.join(", ")} for "${command}" at ${browserEnv.PLAYWRIGHT_BROWSERS_PATH ?? "(default browser cache)"}`,
|
|
1811
1941
|
);
|
|
1812
1942
|
const browserPreflight = await runPlaywrightBrowserRuntimePreflight(
|
|
1813
1943
|
repo,
|
|
1814
1944
|
command,
|
|
1945
|
+
missingPlaywrightBrowserTargets,
|
|
1815
1946
|
resolveValidationCommandTimeoutMs(command, qualityValidationStepTimeoutMs),
|
|
1816
1947
|
outputPolicy,
|
|
1817
1948
|
);
|
|
@@ -1820,7 +1951,7 @@ async function runDeterministicQualityGate(
|
|
|
1820
1951
|
validationRuns.push({
|
|
1821
1952
|
...browserPreflight,
|
|
1822
1953
|
stderr: [
|
|
1823
|
-
`Browser runtime preflight failed before validation command "${command}". WorkerPals could not ensure Playwright
|
|
1954
|
+
`Browser runtime preflight failed before validation command "${command}". WorkerPals could not ensure Playwright browser target(s) ${missingPlaywrightBrowserTargets.join(", ")} in PLAYWRIGHT_BROWSERS_PATH=${browserEnv.PLAYWRIGHT_BROWSERS_PATH ?? "(default)"}.`,
|
|
1824
1955
|
browserPreflight.stderr,
|
|
1825
1956
|
]
|
|
1826
1957
|
.filter(Boolean)
|
|
@@ -1832,10 +1963,12 @@ async function runDeterministicQualityGate(
|
|
|
1832
1963
|
);
|
|
1833
1964
|
continue;
|
|
1834
1965
|
}
|
|
1835
|
-
|
|
1966
|
+
for (const target of missingPlaywrightBrowserTargets) {
|
|
1967
|
+
playwrightBrowserRuntimeReadyTargets.add(target);
|
|
1968
|
+
}
|
|
1836
1969
|
onLog?.(
|
|
1837
1970
|
"stdout",
|
|
1838
|
-
`[ValidationGate] Browser runtime preflight passed for "${command}"`,
|
|
1971
|
+
`[ValidationGate] Browser runtime preflight passed for "${command}" (${missingPlaywrightBrowserTargets.join(", ")})`,
|
|
1839
1972
|
);
|
|
1840
1973
|
commandBrowserRuntimeEnsured = true;
|
|
1841
1974
|
}
|