@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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushpalsdev/cli",
3
- "version": "1.0.97",
3
+ "version": "1.0.99",
4
4
  "description": "PushPals terminal CLI for LocalBuddy -> RemoteBuddy orchestration",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -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
- async function runValidationArgv(
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 timer = setTimeout(
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
- Math.max(1_000, timeoutMs),
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
- const [stdout, stderr, exitCode] = await Promise.all([
678
- new Response(proc.stdout).text(),
679
- new Response(proc.stderr).text(),
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: timedOut ? 124 : exitCode,
689
- stdout: compactJobOutput(stdout.trim(), outputPolicy),
751
+ exitCode,
752
+ stdout: compactJobOutput(stdoutCapture.text().trim(), outputPolicy),
690
753
  stderr: compactJobOutput(
691
754
  [
692
- stderr.trim(),
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
- export function playwrightBrowserInstallArgv(): string[] {
887
- return ["bunx", "playwright", "install", "chromium"];
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 Chromium. Captured output is the process output emitted before PushPals terminated the installer process tree.`,
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
- let playwrightBrowserRuntimeReady = false;
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
- playwrightBrowserRuntimeReady && commandNeedsPlaywrightBrowserRuntime;
1806
- if (!playwrightBrowserRuntimeReady && commandNeedsPlaywrightBrowserRuntime) {
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 Chromium for "${command}" at ${browserEnv.PLAYWRIGHT_BROWSERS_PATH ?? "(default browser cache)"}`,
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 Chromium in PLAYWRIGHT_BROWSERS_PATH=${browserEnv.PLAYWRIGHT_BROWSERS_PATH ?? "(default)"}.`,
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
- playwrightBrowserRuntimeReady = true;
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
  }