@pushpalsdev/cli 1.0.98 → 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.98",
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)
@@ -1242,6 +1305,10 @@ function detectValidationBlocker(runs: ValidationExecutionResult[]): ValidationB
1242
1305
  combined.includes("network access") ||
1243
1306
  combined.includes("connection refused") ||
1244
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") ||
1245
1312
  combined.includes("eacces")
1246
1313
  ) {
1247
1314
  return {