vibeview 0.11.0 → 0.12.0
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/dist/{chunk-4UGLSIZM.js → chunk-NJU4AKHN.js} +609 -73
- package/dist/{chunk-AG3RZCPW.js → chunk-OHRTLUBD.js} +123 -8
- package/dist/cli.js +246 -21
- package/dist/{server-S6OCJERW.js → server-LZRNKGVA.js} +3 -3
- package/dist/{upload-app-JIOFLMS3.js → upload-app-G5SEYBTU.js} +1 -1
- package/package.json +2 -1
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
ApiClient,
|
|
4
|
+
createSpinner,
|
|
4
5
|
loadConfig,
|
|
5
6
|
resolveAuth,
|
|
6
7
|
uploadAppBuild
|
|
7
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-OHRTLUBD.js";
|
|
8
9
|
|
|
9
10
|
// src/lib/dev-state.ts
|
|
10
11
|
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
@@ -72,6 +73,7 @@ function clearDevState(cwd) {
|
|
|
72
73
|
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
73
74
|
import { join as join2 } from "path";
|
|
74
75
|
var DEV_PLATFORMS = ["ios", "android", "tvos", "androidtv", "roku"];
|
|
76
|
+
var ENV_KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
75
77
|
var PLATFORM_NEEDS_METRO = {
|
|
76
78
|
ios: true,
|
|
77
79
|
android: true,
|
|
@@ -165,6 +167,26 @@ function loadDevConfig(cwd) {
|
|
|
165
167
|
}
|
|
166
168
|
return cfg;
|
|
167
169
|
}
|
|
170
|
+
function validateBuildEnv(env, platformKey) {
|
|
171
|
+
if (env === void 0) return;
|
|
172
|
+
if (typeof env !== "object" || env === null || Array.isArray(env)) {
|
|
173
|
+
throw new Error(
|
|
174
|
+
`vibeview.json: platforms.${platformKey}.build.env must be an object of string values`
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
for (const [envKey, envValue] of Object.entries(env)) {
|
|
178
|
+
if (typeof envValue !== "string") {
|
|
179
|
+
throw new Error(
|
|
180
|
+
`vibeview.json: platforms.${platformKey}.build.env must be an object of string values`
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
if (!ENV_KEY_RE.test(envKey)) {
|
|
184
|
+
throw new Error(
|
|
185
|
+
`vibeview.json: platforms.${platformKey}.build.env key "${envKey}" is not a valid environment variable name (must match ${ENV_KEY_RE})`
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
168
190
|
function saveDevConfig(cwd, cfg) {
|
|
169
191
|
writeFileSync2(join2(cwd, FILE), JSON.stringify(cfg, null, 2) + "\n");
|
|
170
192
|
}
|
|
@@ -198,7 +220,7 @@ function buildSavedConfig(input2) {
|
|
|
198
220
|
[platform]: {
|
|
199
221
|
appId,
|
|
200
222
|
deviceCategory: isTv ? void 0 : deviceCategory,
|
|
201
|
-
...existing?.platforms?.[platform]?.build ? { build: existing.platforms[platform].build } : {},
|
|
223
|
+
...existing?.platforms?.[platform]?.build ? { build: { ...existing.platforms[platform].build } } : {},
|
|
202
224
|
...isFirstRun && firstRunBuild ? { build: firstRunBuild } : {},
|
|
203
225
|
// Never invented — absent means "no extra ignores", and first-run
|
|
204
226
|
// setup does not ask for it. Only ever carried forward.
|
|
@@ -224,7 +246,7 @@ async function checkMetro(port, timeoutMs = 1500) {
|
|
|
224
246
|
}
|
|
225
247
|
|
|
226
248
|
// src/commands/build.ts
|
|
227
|
-
import { spawn } from "child_process";
|
|
249
|
+
import { spawn as spawn2 } from "child_process";
|
|
228
250
|
import { createInterface } from "readline";
|
|
229
251
|
import { select as select2 } from "@inquirer/prompts";
|
|
230
252
|
import chalk from "chalk";
|
|
@@ -436,7 +458,187 @@ async function promptMetroCommand(cwd, platform) {
|
|
|
436
458
|
return input({ message: "Metro command", required: true });
|
|
437
459
|
}
|
|
438
460
|
|
|
461
|
+
// src/lib/source-pack.ts
|
|
462
|
+
import { execFile, spawn } from "child_process";
|
|
463
|
+
import { lstatSync, mkdtempSync as mkdtempSync2, readFileSync as readFileSync4, rmSync as rmSync3, statSync as statSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
464
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
465
|
+
import { join as join5 } from "path";
|
|
466
|
+
import ignore from "ignore";
|
|
467
|
+
var NotAGitRepoError = class extends Error {
|
|
468
|
+
constructor(message) {
|
|
469
|
+
super(message);
|
|
470
|
+
this.name = "NotAGitRepoError";
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
var SourceTooLargeError = class extends Error {
|
|
474
|
+
constructor(message, largest) {
|
|
475
|
+
super(message);
|
|
476
|
+
this.largest = largest;
|
|
477
|
+
this.name = "SourceTooLargeError";
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
var FORCE_EXCLUDE_PREFIXES = ["node_modules/", "ios/Pods/", ".git/"];
|
|
481
|
+
function isForceExcluded(relPath) {
|
|
482
|
+
for (const prefix of FORCE_EXCLUDE_PREFIXES) {
|
|
483
|
+
if (relPath.startsWith(prefix) || relPath.includes(`/${prefix}`)) return true;
|
|
484
|
+
}
|
|
485
|
+
return false;
|
|
486
|
+
}
|
|
487
|
+
async function listSourceFiles(projectRoot) {
|
|
488
|
+
const stdout = await runGitLsFiles(projectRoot);
|
|
489
|
+
const rawPaths = stdout.length ? stdout.split("\0").filter((p) => p.length > 0) : [];
|
|
490
|
+
let filtered = rawPaths;
|
|
491
|
+
const vvignorePath = join5(projectRoot, ".vvignore");
|
|
492
|
+
if (statOrNull(vvignorePath)?.isFile()) {
|
|
493
|
+
const ig = ignore().add(readFileSync4(vvignorePath, "utf-8"));
|
|
494
|
+
filtered = filtered.filter((p) => !ig.ignores(p));
|
|
495
|
+
}
|
|
496
|
+
const result = [];
|
|
497
|
+
for (const relPath of filtered) {
|
|
498
|
+
if (isForceExcluded(relPath)) continue;
|
|
499
|
+
const st = statOrNull(join5(projectRoot, relPath));
|
|
500
|
+
if (!st) continue;
|
|
501
|
+
result.push(relPath);
|
|
502
|
+
}
|
|
503
|
+
return result;
|
|
504
|
+
}
|
|
505
|
+
function statOrNull(path) {
|
|
506
|
+
try {
|
|
507
|
+
return lstatSync(path);
|
|
508
|
+
} catch {
|
|
509
|
+
return null;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
function runGitLsFiles(projectRoot) {
|
|
513
|
+
return new Promise((resolve, reject) => {
|
|
514
|
+
const child = spawn("git", ["ls-files", "--cached", "--others", "--exclude-standard", "-z"], {
|
|
515
|
+
cwd: projectRoot,
|
|
516
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
517
|
+
});
|
|
518
|
+
const stdoutChunks = [];
|
|
519
|
+
const stderrChunks = [];
|
|
520
|
+
child.stdout.on("data", (c) => stdoutChunks.push(c));
|
|
521
|
+
child.stderr.on("data", (c) => stderrChunks.push(c));
|
|
522
|
+
child.on("error", (err) => {
|
|
523
|
+
reject(
|
|
524
|
+
new NotAGitRepoError(
|
|
525
|
+
`failed to run git in ${projectRoot}: ${err.message} \u2014 is git installed and on PATH?`
|
|
526
|
+
)
|
|
527
|
+
);
|
|
528
|
+
});
|
|
529
|
+
child.on("close", (code) => {
|
|
530
|
+
const stderr = Buffer.concat(stderrChunks).toString("utf-8");
|
|
531
|
+
if (code !== 0) {
|
|
532
|
+
if (/not a git repository/i.test(stderr)) {
|
|
533
|
+
reject(new NotAGitRepoError(`${projectRoot} is not a git repository`));
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
reject(new Error(`git ls-files failed (exit ${code}) in ${projectRoot}: ${stderr.trim()}`));
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
resolve(Buffer.concat(stdoutChunks).toString("utf-8"));
|
|
540
|
+
});
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
var DEFAULT_CAP_BYTES = 2 * 1024 * 1024 * 1024;
|
|
544
|
+
async function packSource(projectRoot, files, opts = {}) {
|
|
545
|
+
const capBytes = opts.capBytes ?? DEFAULT_CAP_BYTES;
|
|
546
|
+
let totalBytes = 0;
|
|
547
|
+
const sized = [];
|
|
548
|
+
for (const relPath of files) {
|
|
549
|
+
const bytes = lstatSync(join5(projectRoot, relPath)).size;
|
|
550
|
+
totalBytes += bytes;
|
|
551
|
+
sized.push({ path: relPath, bytes });
|
|
552
|
+
}
|
|
553
|
+
if (totalBytes > capBytes) {
|
|
554
|
+
const largest = sized.slice().sort((a, b) => b.bytes - a.bytes).slice(0, 5);
|
|
555
|
+
throw new SourceTooLargeError(
|
|
556
|
+
`source exceeds the ${formatBytes(capBytes)} pack limit (${formatBytes(totalBytes)} across ${files.length} files)`,
|
|
557
|
+
largest
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
const tmpDir = mkdtempSync2(join5(tmpdir2(), "vibeview-source-pack-"));
|
|
561
|
+
const cleanup = () => rmSync3(tmpDir, { recursive: true, force: true });
|
|
562
|
+
try {
|
|
563
|
+
const supportsNull = await probeTarNullSupport();
|
|
564
|
+
const tarPath = join5(tmpDir, "source.tgz");
|
|
565
|
+
if (supportsNull) {
|
|
566
|
+
const listPath = join5(tmpDir, "files.list");
|
|
567
|
+
writeFileSync3(listPath, files.map((f) => `${f}\0`).join(""));
|
|
568
|
+
await runTar(["-czf", tarPath, "-C", projectRoot, "--null", "-T", listPath]);
|
|
569
|
+
} else {
|
|
570
|
+
const badName = files.find((f) => f.includes("\n"));
|
|
571
|
+
if (badName) {
|
|
572
|
+
throw new Error(
|
|
573
|
+
`cannot pack source: filename contains a newline (${JSON.stringify(badName)}) and the installed tar doesn't support --null -T; rename the file or install a tar that supports --null`
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
const listPath = join5(tmpDir, "files.list");
|
|
577
|
+
writeFileSync3(listPath, files.map((f) => `${f}
|
|
578
|
+
`).join(""));
|
|
579
|
+
await runTar(["-czf", tarPath, "-C", projectRoot, "-T", listPath]);
|
|
580
|
+
}
|
|
581
|
+
const sizeBytes = statSync3(tarPath).size;
|
|
582
|
+
return { tarPath, sizeBytes, cleanup };
|
|
583
|
+
} catch (err) {
|
|
584
|
+
cleanup();
|
|
585
|
+
throw err;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
function formatBytes(bytes) {
|
|
589
|
+
const mb = bytes / (1024 * 1024);
|
|
590
|
+
if (mb >= 1024) return `${(mb / 1024).toFixed(2)} GiB`;
|
|
591
|
+
return `${mb.toFixed(1)} MiB`;
|
|
592
|
+
}
|
|
593
|
+
var nullSupportCache = null;
|
|
594
|
+
function probeTarNullSupport() {
|
|
595
|
+
if (!nullSupportCache) {
|
|
596
|
+
nullSupportCache = new Promise((resolve) => {
|
|
597
|
+
const tmp = mkdtempSync2(join5(tmpdir2(), "vibeview-tar-probe-"));
|
|
598
|
+
const listPath = join5(tmp, "empty.list");
|
|
599
|
+
writeFileSync3(listPath, "");
|
|
600
|
+
const out = join5(tmp, "probe.tgz");
|
|
601
|
+
execFile("tar", ["-czf", out, "-C", tmp, "--null", "-T", listPath], (err) => {
|
|
602
|
+
rmSync3(tmp, { recursive: true, force: true });
|
|
603
|
+
resolve(!err);
|
|
604
|
+
});
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
return nullSupportCache;
|
|
608
|
+
}
|
|
609
|
+
function runTar(args) {
|
|
610
|
+
return new Promise((resolve, reject) => {
|
|
611
|
+
const child = spawn("tar", args, {
|
|
612
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
613
|
+
env: { ...process.env, COPYFILE_DISABLE: "1" }
|
|
614
|
+
});
|
|
615
|
+
const stderrChunks = [];
|
|
616
|
+
child.stderr.on("data", (c) => stderrChunks.push(c));
|
|
617
|
+
child.on("error", (err) => {
|
|
618
|
+
reject(new Error(`failed to run tar: ${err.message} \u2014 is tar installed and on PATH?`));
|
|
619
|
+
});
|
|
620
|
+
child.on("close", (code) => {
|
|
621
|
+
if (code !== 0) {
|
|
622
|
+
const stderr = Buffer.concat(stderrChunks).toString("utf-8").trim();
|
|
623
|
+
reject(new Error(`tar failed (exit ${code}): ${stderr}`));
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
resolve();
|
|
627
|
+
});
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
|
|
439
631
|
// src/commands/build.ts
|
|
632
|
+
var CLOUD_PLATFORMS = ["ios", "android", "tvos", "androidtv"];
|
|
633
|
+
function isCloudPlatform(p) {
|
|
634
|
+
return CLOUD_PLATFORMS.includes(p);
|
|
635
|
+
}
|
|
636
|
+
var BuildFailError = class extends Error {
|
|
637
|
+
constructor(message) {
|
|
638
|
+
super(message);
|
|
639
|
+
this.name = "BuildFailError";
|
|
640
|
+
}
|
|
641
|
+
};
|
|
440
642
|
var TAIL_LINES = 30;
|
|
441
643
|
async function runBuildForPlatform(cwd, platform, build, opts, log, deps = { upload: uploadAppBuild }) {
|
|
442
644
|
const tail = await runShell(build.command, cwd, log);
|
|
@@ -463,7 +665,7 @@ async function runBuildForPlatform(cwd, platform, build, opts, log, deps = { upl
|
|
|
463
665
|
}
|
|
464
666
|
function runShell(command, cwd, log) {
|
|
465
667
|
return new Promise((resolve) => {
|
|
466
|
-
const child =
|
|
668
|
+
const child = spawn2(command, { cwd, shell: true, stdio: ["ignore", "pipe", "pipe"] });
|
|
467
669
|
const lines = [];
|
|
468
670
|
const onLine = (line) => {
|
|
469
671
|
lines.push(line);
|
|
@@ -476,9 +678,7 @@ function runShell(command, cwd, log) {
|
|
|
476
678
|
child.on("error", () => resolve({ code: 127, lines }));
|
|
477
679
|
});
|
|
478
680
|
}
|
|
479
|
-
async function
|
|
480
|
-
const cwd = process.cwd();
|
|
481
|
-
const cfg = loadDevConfig(cwd);
|
|
681
|
+
async function resolveBarePlatform(cwd, cfg, opts) {
|
|
482
682
|
if (opts.platform && !isDevPlatform(opts.platform)) {
|
|
483
683
|
console.error(chalk.red(`error: --platform must be one of ${DEV_PLATFORMS.join(", ")}`));
|
|
484
684
|
process.exit(2);
|
|
@@ -491,35 +691,42 @@ async function buildCommand(opts) {
|
|
|
491
691
|
"no platform: pass --platform or run `vibeview dev` once to set up vibeview.json",
|
|
492
692
|
"config_required"
|
|
493
693
|
);
|
|
494
|
-
return;
|
|
495
694
|
}
|
|
496
695
|
platform = await select2({
|
|
497
696
|
message: "Platform to run on",
|
|
498
697
|
choices: platformChoices(cwd)
|
|
499
698
|
});
|
|
500
699
|
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
);
|
|
509
|
-
return;
|
|
510
|
-
}
|
|
511
|
-
const answered = await promptBuildConfig(cwd, platform);
|
|
512
|
-
if (!answered) {
|
|
513
|
-
fail(opts, "build config is required to run vibeview build", "config_required");
|
|
700
|
+
return platform;
|
|
701
|
+
}
|
|
702
|
+
async function buildCommand(opts, cloudDeps) {
|
|
703
|
+
const cwd = process.cwd();
|
|
704
|
+
const cfg = loadDevConfig(cwd);
|
|
705
|
+
try {
|
|
706
|
+
if (opts.cloud) {
|
|
707
|
+
await runCloudBuildCommand(cwd, cfg, opts, cloudDeps);
|
|
514
708
|
return;
|
|
515
709
|
}
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
710
|
+
const platform = await resolveBarePlatform(cwd, cfg, opts);
|
|
711
|
+
let build = cfg?.platforms?.[platform]?.build;
|
|
712
|
+
if (!build) {
|
|
713
|
+
if (opts.json || !process.stdin.isTTY) {
|
|
714
|
+
fail(
|
|
715
|
+
opts,
|
|
716
|
+
`no build config for ${platform}: add platforms.${platform}.build {command, artifact} to vibeview.json`,
|
|
717
|
+
"config_required"
|
|
718
|
+
);
|
|
719
|
+
}
|
|
720
|
+
const answered = await promptBuildConfig(cwd, platform);
|
|
721
|
+
if (!answered) {
|
|
722
|
+
fail(opts, "build config is required to run vibeview build", "config_required");
|
|
723
|
+
}
|
|
724
|
+
build = answered;
|
|
725
|
+
if (cfg?.platforms?.[platform]) {
|
|
726
|
+
cfg.platforms[platform].build = build;
|
|
727
|
+
saveDevConfig(cwd, cfg);
|
|
728
|
+
}
|
|
520
729
|
}
|
|
521
|
-
}
|
|
522
|
-
try {
|
|
523
730
|
const result = await runBuildForPlatform(
|
|
524
731
|
cwd,
|
|
525
732
|
platform,
|
|
@@ -549,17 +756,333 @@ async function buildCommand(opts) {
|
|
|
549
756
|
);
|
|
550
757
|
}
|
|
551
758
|
} catch (err) {
|
|
552
|
-
|
|
759
|
+
if (!(err instanceof BuildFailError)) {
|
|
760
|
+
printFailure(opts, err instanceof Error ? err.message : String(err));
|
|
761
|
+
}
|
|
762
|
+
process.exitCode = 1;
|
|
553
763
|
}
|
|
554
764
|
}
|
|
555
|
-
function
|
|
765
|
+
function printFailure(opts, message, code) {
|
|
556
766
|
if (opts.json) console.log(JSON.stringify({ event: "error", code, message }));
|
|
557
767
|
console.error(chalk.red(`error: ${message}`));
|
|
558
|
-
|
|
768
|
+
}
|
|
769
|
+
function fail(opts, message, code) {
|
|
770
|
+
printFailure(opts, message, code);
|
|
771
|
+
throw new BuildFailError(message);
|
|
772
|
+
}
|
|
773
|
+
function emitCloud(json, evt) {
|
|
774
|
+
if (json) console.log(JSON.stringify(evt));
|
|
775
|
+
}
|
|
776
|
+
async function resolveCloudPlatforms(cwd, cfg, opts) {
|
|
777
|
+
if (opts.platform === "all") {
|
|
778
|
+
const configured = Object.entries(cfg?.platforms ?? {});
|
|
779
|
+
const withBuild = configured.filter(([p, entry]) => isCloudPlatform(p) && entry?.build);
|
|
780
|
+
if (withBuild.length === 0) {
|
|
781
|
+
fail(
|
|
782
|
+
opts,
|
|
783
|
+
`--platform all: ${describeAllExclusions(configured)} \u2014 add platforms.<platform>.build {command, artifact} to vibeview.json`,
|
|
784
|
+
"config_required"
|
|
785
|
+
);
|
|
786
|
+
}
|
|
787
|
+
return withBuild.map(([p]) => p);
|
|
788
|
+
}
|
|
789
|
+
const platform = opts.platform ? validateExplicitPlatform(opts.platform) : await resolveBarePlatform(cwd, cfg, opts);
|
|
790
|
+
requireCloudCapable(opts, platform);
|
|
791
|
+
requireBuildBlock(opts, cfg, platform);
|
|
792
|
+
return [platform];
|
|
793
|
+
}
|
|
794
|
+
function validateExplicitPlatform(raw) {
|
|
795
|
+
if (!isDevPlatform(raw)) {
|
|
796
|
+
console.error(
|
|
797
|
+
chalk.red(`error: --platform must be one of ${DEV_PLATFORMS.join(", ")}, or "all"`)
|
|
798
|
+
);
|
|
799
|
+
process.exit(2);
|
|
800
|
+
}
|
|
801
|
+
return raw;
|
|
802
|
+
}
|
|
803
|
+
function requireCloudCapable(opts, platform) {
|
|
804
|
+
if (!isCloudPlatform(platform)) {
|
|
805
|
+
fail(
|
|
806
|
+
opts,
|
|
807
|
+
`cloud builds don't support ${platform} \u2014 pass one of ${CLOUD_PLATFORMS.join(", ")}`
|
|
808
|
+
);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
function requireBuildBlock(opts, cfg, platform) {
|
|
812
|
+
if (!cfg?.platforms?.[platform]?.build) {
|
|
813
|
+
fail(
|
|
814
|
+
opts,
|
|
815
|
+
`no build config for ${platform}: add platforms.${platform}.build {command, artifact} to vibeview.json`,
|
|
816
|
+
"config_required"
|
|
817
|
+
);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
function describeAllExclusions(configured) {
|
|
821
|
+
if (configured.length === 0) return "no platforms are configured";
|
|
822
|
+
const unsupported = configured.filter(([p]) => !isCloudPlatform(p)).map(([p]) => p);
|
|
823
|
+
const missingBuild = configured.filter(([p, entry]) => isCloudPlatform(p) && !entry?.build).map(([p]) => p);
|
|
824
|
+
const parts = [];
|
|
825
|
+
if (unsupported.length > 0) {
|
|
826
|
+
parts.push(
|
|
827
|
+
`${unsupported.join(", ")} ${unsupported.length === 1 ? "isn't" : "aren't"} supported for cloud builds`
|
|
828
|
+
);
|
|
829
|
+
}
|
|
830
|
+
if (missingBuild.length > 0) {
|
|
831
|
+
parts.push(
|
|
832
|
+
`${missingBuild.join(", ")} ${missingBuild.length === 1 ? "has" : "have"} no build block`
|
|
833
|
+
);
|
|
834
|
+
}
|
|
835
|
+
return parts.join("; ");
|
|
836
|
+
}
|
|
837
|
+
var POLL_INTERVAL_MS = 2e3;
|
|
838
|
+
var POLL_SAFETY_MARGIN_MS = 10 * 60 * 1e3;
|
|
839
|
+
function defaultCloudBuildDeps(client) {
|
|
840
|
+
return {
|
|
841
|
+
listSourceFiles,
|
|
842
|
+
packSource,
|
|
843
|
+
createCloudBuilds: client.createCloudBuilds.bind(client),
|
|
844
|
+
getCloudBuildLogs: client.getCloudBuildLogs.bind(client),
|
|
845
|
+
getCloudBuild: client.getCloudBuild.bind(client),
|
|
846
|
+
downloadCloudBuildArtifact: client.downloadCloudBuildArtifact.bind(client)
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
function formatMB(bytes) {
|
|
850
|
+
return (bytes / (1024 * 1024)).toFixed(1);
|
|
851
|
+
}
|
|
852
|
+
function formatDuration(startedAt, finishedAt) {
|
|
853
|
+
if (!startedAt || !finishedAt) return null;
|
|
854
|
+
const ms = new Date(finishedAt).getTime() - new Date(startedAt).getTime();
|
|
855
|
+
if (!Number.isFinite(ms) || ms < 0) return null;
|
|
856
|
+
if (ms < 1e3) return `${ms}ms`;
|
|
857
|
+
return `${(ms / 1e3).toFixed(1)}s`;
|
|
858
|
+
}
|
|
859
|
+
function formatCost(costMicro) {
|
|
860
|
+
return `$${(costMicro / 1e6).toFixed(2)}`;
|
|
861
|
+
}
|
|
862
|
+
async function runCloudBuildCommand(cwd, cfg, opts, deps) {
|
|
863
|
+
const platforms = await resolveCloudPlatforms(cwd, cfg, opts);
|
|
864
|
+
for (const p of platforms) {
|
|
865
|
+
try {
|
|
866
|
+
validateBuildEnv(cfg?.platforms?.[p]?.build?.env, p);
|
|
867
|
+
} catch (err) {
|
|
868
|
+
fail(opts, err instanceof Error ? err.message : String(err));
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
const { token, baseUrl } = resolveAuth(opts);
|
|
872
|
+
const client = new ApiClient(baseUrl, token);
|
|
873
|
+
const d = deps ?? defaultCloudBuildDeps(client);
|
|
874
|
+
let cleanupTar;
|
|
875
|
+
try {
|
|
876
|
+
let files;
|
|
877
|
+
try {
|
|
878
|
+
files = await d.listSourceFiles(cwd);
|
|
879
|
+
} catch (err) {
|
|
880
|
+
if (err instanceof NotAGitRepoError) {
|
|
881
|
+
fail(
|
|
882
|
+
opts,
|
|
883
|
+
`${err.message} \u2014 cloud builds pack your project source via git; run this from inside a git repository`
|
|
884
|
+
);
|
|
885
|
+
}
|
|
886
|
+
throw err;
|
|
887
|
+
}
|
|
888
|
+
const packSpinner = !opts.json ? createSpinner(`packing source (${files.length} files)`) : null;
|
|
889
|
+
packSpinner?.start();
|
|
890
|
+
let tarPath;
|
|
891
|
+
let sizeBytes;
|
|
892
|
+
try {
|
|
893
|
+
const packed = await d.packSource(cwd, files);
|
|
894
|
+
tarPath = packed.tarPath;
|
|
895
|
+
sizeBytes = packed.sizeBytes;
|
|
896
|
+
cleanupTar = packed.cleanup;
|
|
897
|
+
} catch (err) {
|
|
898
|
+
packSpinner?.stop();
|
|
899
|
+
if (err instanceof SourceTooLargeError) {
|
|
900
|
+
const offenders = err.largest.map((f) => ` ${f.path} (${formatMB(f.bytes)} MB)`).join("\n");
|
|
901
|
+
fail(opts, `${err.message}
|
|
902
|
+
Largest files:
|
|
903
|
+
${offenders}`);
|
|
904
|
+
}
|
|
905
|
+
throw err;
|
|
906
|
+
}
|
|
907
|
+
packSpinner?.succeed(`packed source (${files.length} files, ${formatMB(sizeBytes)} MB)`);
|
|
908
|
+
const manifestEntries = platforms.map((p) => {
|
|
909
|
+
const entry = cfg.platforms[p];
|
|
910
|
+
const build = entry.build;
|
|
911
|
+
return {
|
|
912
|
+
platform: p,
|
|
913
|
+
app_public_id: entry.appId,
|
|
914
|
+
command: build.command,
|
|
915
|
+
artifact_path: build.artifact,
|
|
916
|
+
...build.env ? { env: build.env } : {}
|
|
917
|
+
};
|
|
918
|
+
});
|
|
919
|
+
const uploadSpinner = !opts.json ? createSpinner("uploading source...") : null;
|
|
920
|
+
uploadSpinner?.start();
|
|
921
|
+
let created;
|
|
922
|
+
try {
|
|
923
|
+
const resp = await d.createCloudBuilds({ builds: manifestEntries }, tarPath);
|
|
924
|
+
created = resp.builds;
|
|
925
|
+
} catch (err) {
|
|
926
|
+
uploadSpinner?.stop();
|
|
927
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
928
|
+
fail(opts, message);
|
|
929
|
+
}
|
|
930
|
+
uploadSpinner?.succeed(`uploaded \u2014 ${created.length} build(s) started`);
|
|
931
|
+
for (const b of created) {
|
|
932
|
+
emitCloud(opts.json, { event: "build_created", id: b.id, platform: b.platform });
|
|
933
|
+
}
|
|
934
|
+
if (!opts.json) {
|
|
935
|
+
for (const b of created) {
|
|
936
|
+
console.error(` ${chalk.cyan(b.id)} ${chalk.dim(`[${b.platform}]`)}`);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
const shouldWait = opts.wait !== false || opts.download;
|
|
940
|
+
if (!shouldWait) {
|
|
941
|
+
process.exitCode = 0;
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
944
|
+
const finished = await waitForCloudBuilds(created, opts, d);
|
|
945
|
+
let downloadFailed = false;
|
|
946
|
+
if (opts.download) {
|
|
947
|
+
for (const b of finished.values()) {
|
|
948
|
+
if (b.status !== "succeeded") continue;
|
|
949
|
+
try {
|
|
950
|
+
const result = await d.downloadCloudBuildArtifact(b.id, cwd);
|
|
951
|
+
emitCloud(opts.json, {
|
|
952
|
+
event: "artifact_saved",
|
|
953
|
+
id: b.id,
|
|
954
|
+
filename: result.filename,
|
|
955
|
+
path: result.path
|
|
956
|
+
});
|
|
957
|
+
if (!opts.json) {
|
|
958
|
+
console.error(chalk.green(`\u2713 saved ${result.filename}`));
|
|
959
|
+
}
|
|
960
|
+
} catch (err) {
|
|
961
|
+
downloadFailed = true;
|
|
962
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
963
|
+
emitCloud(opts.json, {
|
|
964
|
+
event: "error",
|
|
965
|
+
message: `error downloading ${b.id}: ${message}`
|
|
966
|
+
});
|
|
967
|
+
if (!opts.json) console.error(chalk.red(`error downloading ${b.id}: ${message}`));
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
const allSucceeded = Array.from(finished.values()).every((b) => b.status === "succeeded");
|
|
972
|
+
process.exitCode = allSucceeded && !downloadFailed ? 0 : 1;
|
|
973
|
+
} finally {
|
|
974
|
+
cleanupTar?.();
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
var BUILD_TERMINAL_STATUSES = /* @__PURE__ */ new Set([
|
|
978
|
+
"succeeded",
|
|
979
|
+
"failed",
|
|
980
|
+
"canceled",
|
|
981
|
+
"timed_out",
|
|
982
|
+
"infra_failed"
|
|
983
|
+
]);
|
|
984
|
+
var MAX_CONSECUTIVE_POLL_FAILURES = 5;
|
|
985
|
+
async function waitForCloudBuilds(builds, opts, deps) {
|
|
986
|
+
const now = deps.now ?? Date.now;
|
|
987
|
+
const pollIntervalMs = deps.pollIntervalMs ?? POLL_INTERVAL_MS;
|
|
988
|
+
const multi = builds.length > 1;
|
|
989
|
+
const state = new Map(builds.map((b) => [b.id, b]));
|
|
990
|
+
const offsets = new Map(builds.map((b) => [b.id, 0]));
|
|
991
|
+
const failures = new Map(builds.map((b) => [b.id, 0]));
|
|
992
|
+
const abandoned = /* @__PURE__ */ new Set();
|
|
993
|
+
let lastError;
|
|
994
|
+
const maxTimeoutS = Math.max(...builds.map((b) => b.timeout_s), 0);
|
|
995
|
+
const deadline = now() + maxTimeoutS * 1e3 + POLL_SAFETY_MARGIN_MS;
|
|
996
|
+
const printLine = (id, platform, line) => {
|
|
997
|
+
const prefix = multi ? `[${platform}] ` : "";
|
|
998
|
+
if (!opts.json) console.error(prefix + line);
|
|
999
|
+
};
|
|
1000
|
+
const stillWaiting = () => Array.from(state.values()).some(
|
|
1001
|
+
(b) => !BUILD_TERMINAL_STATUSES.has(b.status) && !abandoned.has(b.id)
|
|
1002
|
+
);
|
|
1003
|
+
while (stillWaiting()) {
|
|
1004
|
+
if (now() > deadline) {
|
|
1005
|
+
throw new Error(
|
|
1006
|
+
`cloud build poll timed out after exceeding the ${Math.round((maxTimeoutS * 1e3 + POLL_SAFETY_MARGIN_MS) / 1e3)}s safety limit \u2014 the backend never reported a terminal status`
|
|
1007
|
+
);
|
|
1008
|
+
}
|
|
1009
|
+
for (const build of state.values()) {
|
|
1010
|
+
if (BUILD_TERMINAL_STATUSES.has(build.status) || abandoned.has(build.id)) continue;
|
|
1011
|
+
try {
|
|
1012
|
+
const offset = offsets.get(build.id) ?? 0;
|
|
1013
|
+
const logs = await deps.getCloudBuildLogs(build.id, offset);
|
|
1014
|
+
if (logs.content) {
|
|
1015
|
+
emitCloud(opts.json, { event: "build_log", id: build.id, content: logs.content });
|
|
1016
|
+
for (const line of logs.content.split("\n")) {
|
|
1017
|
+
if (line.length === 0) continue;
|
|
1018
|
+
printLine(build.id, build.platform, line);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
offsets.set(build.id, logs.offset);
|
|
1022
|
+
const updated = { ...build, status: logs.status };
|
|
1023
|
+
state.set(build.id, updated);
|
|
1024
|
+
failures.set(build.id, 0);
|
|
1025
|
+
if (BUILD_TERMINAL_STATUSES.has(logs.status)) {
|
|
1026
|
+
const final = await fetchFinalSummary(build.id, updated, deps);
|
|
1027
|
+
state.set(build.id, final);
|
|
1028
|
+
emitCloud(opts.json, {
|
|
1029
|
+
event: "build_finished",
|
|
1030
|
+
id: final.id,
|
|
1031
|
+
status: final.status,
|
|
1032
|
+
cost_micro: final.cost_micro,
|
|
1033
|
+
error_summary: final.error_summary
|
|
1034
|
+
});
|
|
1035
|
+
if (!opts.json) {
|
|
1036
|
+
printOutcomeLine(final, multi);
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
} catch (err) {
|
|
1040
|
+
lastError = err;
|
|
1041
|
+
const count = (failures.get(build.id) ?? 0) + 1;
|
|
1042
|
+
failures.set(build.id, count);
|
|
1043
|
+
if (count >= MAX_CONSECUTIVE_POLL_FAILURES) {
|
|
1044
|
+
abandoned.add(build.id);
|
|
1045
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1046
|
+
const giveUp = `giving up polling ${build.id} after ${count} consecutive failures: ${message} \u2014 the build may still be running server-side; resume watching it with \`vibeview builds logs ${build.id} --follow\``;
|
|
1047
|
+
emitCloud(opts.json, { event: "error", message: giveUp });
|
|
1048
|
+
if (!opts.json) console.error(chalk.red(giveUp));
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
if (stillWaiting()) {
|
|
1053
|
+
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
if (abandoned.size > 0 && !Array.from(state.values()).some((b) => BUILD_TERMINAL_STATUSES.has(b.status))) {
|
|
1057
|
+
const message = lastError instanceof Error ? lastError.message : String(lastError);
|
|
1058
|
+
throw new Error(
|
|
1059
|
+
`cloud build poll failed ${MAX_CONSECUTIVE_POLL_FAILURES} times in a row, giving up: ${message}
|
|
1060
|
+
The build may still be running server-side \u2014 resume watching it with \`vibeview builds logs <id> --follow\`.`
|
|
1061
|
+
);
|
|
1062
|
+
}
|
|
1063
|
+
return state;
|
|
1064
|
+
}
|
|
1065
|
+
async function fetchFinalSummary(id, fallback, deps) {
|
|
1066
|
+
try {
|
|
1067
|
+
return await deps.getCloudBuild(id);
|
|
1068
|
+
} catch {
|
|
1069
|
+
return fallback;
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
function printOutcomeLine(build, multi) {
|
|
1073
|
+
const prefix = multi ? `[${build.platform}] ` : "";
|
|
1074
|
+
const ok = build.status === "succeeded";
|
|
1075
|
+
const mark = ok ? chalk.green("\u2713") : chalk.red("\u2717");
|
|
1076
|
+
const duration = formatDuration(build.started_at, build.finished_at);
|
|
1077
|
+
let line = `${prefix}${mark} ${build.status}`;
|
|
1078
|
+
if (duration) line += ` (${duration})`;
|
|
1079
|
+
if (build.error_summary) line += ` \u2014 ${build.error_summary}`;
|
|
1080
|
+
if (build.cost_micro > 0) line += ` \u2014 cost: ${formatCost(build.cost_micro)} usage credit`;
|
|
1081
|
+
console.error(line);
|
|
559
1082
|
}
|
|
560
1083
|
|
|
561
1084
|
// src/commands/dev.ts
|
|
562
|
-
import { spawn as
|
|
1085
|
+
import { spawn as spawn4 } from "child_process";
|
|
563
1086
|
import { createWriteStream, mkdirSync as mkdirSync3 } from "fs";
|
|
564
1087
|
import { dirname as dirname5 } from "path";
|
|
565
1088
|
import { createInterface as createInterface3 } from "readline";
|
|
@@ -732,19 +1255,19 @@ var TunnelClient = class {
|
|
|
732
1255
|
};
|
|
733
1256
|
|
|
734
1257
|
// src/lib/metro-manager.ts
|
|
735
|
-
import { spawn as
|
|
1258
|
+
import { spawn as spawn3 } from "child_process";
|
|
736
1259
|
import { createInterface as createInterface2 } from "readline";
|
|
737
1260
|
|
|
738
1261
|
// src/lib/metro-state.ts
|
|
739
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as
|
|
740
|
-
import { dirname as dirname3, join as
|
|
1262
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync5, rmSync as rmSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
1263
|
+
import { dirname as dirname3, join as join6 } from "path";
|
|
741
1264
|
var STATE_DIR2 = ".vibeview";
|
|
742
1265
|
var STATE_FILE2 = "metro-state.json";
|
|
743
1266
|
function statePath2(cwd) {
|
|
744
|
-
return
|
|
1267
|
+
return join6(cwd, STATE_DIR2, STATE_FILE2);
|
|
745
1268
|
}
|
|
746
1269
|
function metroLogPath(cwd) {
|
|
747
|
-
return
|
|
1270
|
+
return join6(cwd, STATE_DIR2, "metro.log");
|
|
748
1271
|
}
|
|
749
1272
|
function isPidAlive2(pid) {
|
|
750
1273
|
try {
|
|
@@ -757,12 +1280,12 @@ function isPidAlive2(pid) {
|
|
|
757
1280
|
function writeMetroState(cwd, state) {
|
|
758
1281
|
const path = statePath2(cwd);
|
|
759
1282
|
mkdirSync2(dirname3(path), { recursive: true });
|
|
760
|
-
|
|
1283
|
+
writeFileSync4(path, JSON.stringify(state, null, 2) + "\n");
|
|
761
1284
|
}
|
|
762
1285
|
function readMetroState(cwd) {
|
|
763
1286
|
let parsed;
|
|
764
1287
|
try {
|
|
765
|
-
parsed = JSON.parse(
|
|
1288
|
+
parsed = JSON.parse(readFileSync5(statePath2(cwd), "utf-8"));
|
|
766
1289
|
} catch {
|
|
767
1290
|
return null;
|
|
768
1291
|
}
|
|
@@ -774,7 +1297,7 @@ function readMetroState(cwd) {
|
|
|
774
1297
|
}
|
|
775
1298
|
function clearMetroState(cwd) {
|
|
776
1299
|
const path = statePath2(cwd);
|
|
777
|
-
if (existsSync4(path))
|
|
1300
|
+
if (existsSync4(path)) rmSync4(path, { force: true });
|
|
778
1301
|
}
|
|
779
1302
|
function killOwnedMetro(cwd) {
|
|
780
1303
|
const state = readMetroState(cwd);
|
|
@@ -832,7 +1355,7 @@ If Metro uses another port, pass --metro-port <port>.`
|
|
|
832
1355
|
return { status: "spawned", warnings };
|
|
833
1356
|
}
|
|
834
1357
|
async function spawnMetro(cwd, command, port, log, timeoutMs) {
|
|
835
|
-
const child =
|
|
1358
|
+
const child = spawn3(command, {
|
|
836
1359
|
cwd,
|
|
837
1360
|
shell: true,
|
|
838
1361
|
detached: true,
|
|
@@ -1157,6 +1680,10 @@ function lateWarningVerdict(info, reportedWarnings) {
|
|
|
1157
1680
|
return { toReport, blocking };
|
|
1158
1681
|
}
|
|
1159
1682
|
var ROKU_LIVENESS_INTERVAL_MS = 1e4;
|
|
1683
|
+
var ROKU_INSTALL_PENDING_INTERVAL_MS = 2e3;
|
|
1684
|
+
function rokuLivenessDelayMs(info) {
|
|
1685
|
+
return info?.app_install_pending ? ROKU_INSTALL_PENDING_INTERVAL_MS : ROKU_LIVENESS_INTERVAL_MS;
|
|
1686
|
+
}
|
|
1160
1687
|
function rokuKeyAction(char) {
|
|
1161
1688
|
if (char === "") return "interrupt";
|
|
1162
1689
|
switch (char.toLowerCase()) {
|
|
@@ -1619,36 +2146,42 @@ async function runDevInProcess(opts) {
|
|
|
1619
2146
|
}
|
|
1620
2147
|
const sessionId = handle.sessionId;
|
|
1621
2148
|
const reportedWarnings = handle.reportedWarnings;
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
info =
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
reportedWarnings
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
2149
|
+
let keepAlive;
|
|
2150
|
+
const scheduleLiveness = (delayMs) => {
|
|
2151
|
+
keepAlive = setTimeout(() => {
|
|
2152
|
+
void (async () => {
|
|
2153
|
+
if (stopping) return;
|
|
2154
|
+
let info = null;
|
|
2155
|
+
try {
|
|
2156
|
+
info = await api.getSession(sessionId);
|
|
2157
|
+
} catch {
|
|
2158
|
+
}
|
|
2159
|
+
const verdict = sessionLivenessVerdict(info);
|
|
2160
|
+
if (verdict.end && !stopping) {
|
|
2161
|
+
console.error(chalk2.red(verdict.message));
|
|
2162
|
+
emit(opts.json, { event: "session_ended", session_id: sessionId, code: 1 });
|
|
2163
|
+
endedEmitted = true;
|
|
2164
|
+
await stopSession(1);
|
|
2165
|
+
return;
|
|
2166
|
+
}
|
|
2167
|
+
const warningVerdict = lateWarningVerdict(info, reportedWarnings);
|
|
2168
|
+
for (const w of warningVerdict.toReport) {
|
|
2169
|
+
reportedWarnings.add(w);
|
|
2170
|
+
console.error(chalk2.yellow(`\u26A0 ${w}`));
|
|
2171
|
+
emit(opts.json, { event: "warning", message: w });
|
|
2172
|
+
}
|
|
2173
|
+
if (warningVerdict.blocking && !stopping) {
|
|
2174
|
+
emit(opts.json, { event: "session_ended", session_id: sessionId, code: 1 });
|
|
2175
|
+
endedEmitted = true;
|
|
2176
|
+
await stopSession(1);
|
|
2177
|
+
return;
|
|
2178
|
+
}
|
|
2179
|
+
if (!stopping) scheduleLiveness(rokuLivenessDelayMs(info));
|
|
2180
|
+
})();
|
|
2181
|
+
}, delayMs);
|
|
2182
|
+
};
|
|
2183
|
+
scheduleLiveness(ROKU_INSTALL_PENDING_INTERVAL_MS);
|
|
2184
|
+
process.on("exit", () => clearTimeout(keepAlive));
|
|
1652
2185
|
}
|
|
1653
2186
|
} catch (err) {
|
|
1654
2187
|
const message = err.message;
|
|
@@ -1662,7 +2195,7 @@ async function dev(opts) {
|
|
|
1662
2195
|
return runDevInProcess(opts);
|
|
1663
2196
|
}
|
|
1664
2197
|
const rawArgs = process.argv.slice(2).filter((a) => a !== "--detach");
|
|
1665
|
-
const child =
|
|
2198
|
+
const child = spawn4(process.execPath, [process.argv[1], ...rawArgs, "--json", "--no-open"], {
|
|
1666
2199
|
detached: true,
|
|
1667
2200
|
stdio: ["ignore", "pipe", "ignore"]
|
|
1668
2201
|
});
|
|
@@ -2194,7 +2727,7 @@ function agentExitCode(env) {
|
|
|
2194
2727
|
}
|
|
2195
2728
|
|
|
2196
2729
|
// src/commands/agent.ts
|
|
2197
|
-
import { writeFileSync as
|
|
2730
|
+
import { writeFileSync as writeFileSync5 } from "fs";
|
|
2198
2731
|
var REF_RE = /^@?(e\d+)$/;
|
|
2199
2732
|
var COORDS_RE = /^(-?\d+),\s*(-?\d+)$/;
|
|
2200
2733
|
var FlagError = class extends Error {
|
|
@@ -2395,7 +2928,7 @@ async function runAgentVerb(cmd, positionals, opts) {
|
|
|
2395
2928
|
`);
|
|
2396
2929
|
process.exit(1);
|
|
2397
2930
|
}
|
|
2398
|
-
|
|
2931
|
+
writeFileSync5(outPath, imageBytes);
|
|
2399
2932
|
if (opts.json) {
|
|
2400
2933
|
console.log(JSON.stringify(envelope));
|
|
2401
2934
|
} else {
|
|
@@ -2423,6 +2956,9 @@ export {
|
|
|
2423
2956
|
readMetroState,
|
|
2424
2957
|
killOwnedMetro,
|
|
2425
2958
|
buildCommand,
|
|
2959
|
+
BUILD_TERMINAL_STATUSES,
|
|
2960
|
+
fetchFinalSummary,
|
|
2961
|
+
printOutcomeLine,
|
|
2426
2962
|
startDevSession,
|
|
2427
2963
|
PLATFORM_DEVICE,
|
|
2428
2964
|
dev,
|