vibeview 0.29.0 → 0.30.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-T6EQQV6E.js → chunk-NOZGZT3G.js} +219 -63
- package/dist/{chunk-PBMB6TG4.js → chunk-ZZK7YQSP.js} +33 -6
- package/dist/cli.js +76 -8
- package/dist/{server-IGY2VPYT.js → server-2JNYWF5L.js} +8 -4
- package/dist/{upload-app-FEZG23NU.js → upload-app-2LVG4LMB.js} +1 -1
- package/package.json +1 -1
- package/skills/vibeview-agent/SKILL.md +9 -5
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
resolveArtifact,
|
|
14
14
|
resolveAuth,
|
|
15
15
|
uploadAppBuild
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-ZZK7YQSP.js";
|
|
17
17
|
|
|
18
18
|
// src/lib/dev-config.ts
|
|
19
19
|
import { readFileSync, renameSync, rmSync, writeFileSync } from "fs";
|
|
@@ -479,6 +479,108 @@ async function checkMetro(port, timeoutMs = 1500) {
|
|
|
479
479
|
}
|
|
480
480
|
}
|
|
481
481
|
|
|
482
|
+
// src/lib/local-config.ts
|
|
483
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, renameSync as renameSync2, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
484
|
+
import { join as join3 } from "path";
|
|
485
|
+
var DIR = ".vibeview";
|
|
486
|
+
var FILE2 = "local.json";
|
|
487
|
+
function localConfigPath(cwd) {
|
|
488
|
+
return join3(cwd, DIR, FILE2);
|
|
489
|
+
}
|
|
490
|
+
function loadLocalDevConfig(cwd) {
|
|
491
|
+
let parsed;
|
|
492
|
+
try {
|
|
493
|
+
parsed = JSON.parse(readFileSync3(localConfigPath(cwd), "utf-8"));
|
|
494
|
+
} catch {
|
|
495
|
+
return {};
|
|
496
|
+
}
|
|
497
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
498
|
+
const raw = parsed;
|
|
499
|
+
const out = {};
|
|
500
|
+
if (isDevPlatform(raw.defaultPlatform)) out.defaultPlatform = raw.defaultPlatform;
|
|
501
|
+
if (raw.platforms && typeof raw.platforms === "object") {
|
|
502
|
+
for (const [key, value] of Object.entries(raw.platforms)) {
|
|
503
|
+
if (!isDevPlatform(key) || !value || typeof value !== "object") continue;
|
|
504
|
+
const v = value;
|
|
505
|
+
const entry = {};
|
|
506
|
+
if (typeof v.appId === "string" && v.appId) entry.appId = v.appId;
|
|
507
|
+
if (typeof v.deviceCategory === "string" && v.deviceCategory) {
|
|
508
|
+
entry.deviceCategory = v.deviceCategory;
|
|
509
|
+
}
|
|
510
|
+
if (typeof v.metroPort === "number" && Number.isInteger(v.metroPort) && v.metroPort > 0) {
|
|
511
|
+
entry.metroPort = v.metroPort;
|
|
512
|
+
}
|
|
513
|
+
if (Object.keys(entry).length) (out.platforms ??= {})[key] = entry;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
return out;
|
|
517
|
+
}
|
|
518
|
+
function prune(cfg) {
|
|
519
|
+
const out = {};
|
|
520
|
+
if (cfg.defaultPlatform) out.defaultPlatform = cfg.defaultPlatform;
|
|
521
|
+
for (const [key, entry] of Object.entries(cfg.platforms ?? {})) {
|
|
522
|
+
const kept = Object.fromEntries(
|
|
523
|
+
Object.entries(entry ?? {}).filter(([, v]) => v !== void 0)
|
|
524
|
+
);
|
|
525
|
+
if (Object.keys(kept).length) (out.platforms ??= {})[key] = kept;
|
|
526
|
+
}
|
|
527
|
+
return out;
|
|
528
|
+
}
|
|
529
|
+
function saveLocalDevConfig(cwd, cfg) {
|
|
530
|
+
const pruned = prune(cfg);
|
|
531
|
+
const target = localConfigPath(cwd);
|
|
532
|
+
if (!pruned.defaultPlatform && !pruned.platforms) {
|
|
533
|
+
rmSync3(target, { force: true });
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
mkdirSync2(join3(cwd, DIR), { recursive: true });
|
|
537
|
+
const tmp = `${target}.${process.pid}.${Date.now()}.tmp`;
|
|
538
|
+
try {
|
|
539
|
+
writeFileSync3(tmp, JSON.stringify(pruned, null, 2) + "\n");
|
|
540
|
+
renameSync2(tmp, target);
|
|
541
|
+
} catch (err) {
|
|
542
|
+
rmSync3(tmp, { force: true });
|
|
543
|
+
throw err;
|
|
544
|
+
}
|
|
545
|
+
ensureGitignored(cwd);
|
|
546
|
+
}
|
|
547
|
+
function effectiveDevConfig(committed, local) {
|
|
548
|
+
if (!committed && !local.defaultPlatform && !local.platforms) return null;
|
|
549
|
+
const base = committed ? { ...committed, platforms: { ...committed.platforms } } : { defaultPlatform: local.defaultPlatform ?? "ios", platforms: {} };
|
|
550
|
+
if (local.defaultPlatform) base.defaultPlatform = local.defaultPlatform;
|
|
551
|
+
for (const [key, entry] of Object.entries(local.platforms ?? {})) {
|
|
552
|
+
const platform = key;
|
|
553
|
+
const merged = { ...base.platforms[platform] };
|
|
554
|
+
if (entry?.appId) merged.appId = entry.appId;
|
|
555
|
+
if (entry?.deviceCategory) merged.deviceCategory = entry.deviceCategory;
|
|
556
|
+
if (entry?.metroPort !== void 0) merged.metroPort = entry.metroPort;
|
|
557
|
+
base.platforms[platform] = merged;
|
|
558
|
+
}
|
|
559
|
+
return base;
|
|
560
|
+
}
|
|
561
|
+
function describeLocalChanges(before, after) {
|
|
562
|
+
const changes = [];
|
|
563
|
+
const a = prune(before);
|
|
564
|
+
const b = prune(after);
|
|
565
|
+
if (a.defaultPlatform !== b.defaultPlatform) {
|
|
566
|
+
changes.push(
|
|
567
|
+
b.defaultPlatform ? `defaultPlatform ${b.defaultPlatform}` : `defaultPlatform (removed, was ${a.defaultPlatform})`
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(a.platforms ?? {}), ...Object.keys(b.platforms ?? {})]);
|
|
571
|
+
for (const key of keys) {
|
|
572
|
+
const pa = a.platforms?.[key] ?? {};
|
|
573
|
+
const pb = b.platforms?.[key] ?? {};
|
|
574
|
+
for (const field of ["appId", "deviceCategory", "metroPort"]) {
|
|
575
|
+
if (pa[field] === pb[field]) continue;
|
|
576
|
+
changes.push(
|
|
577
|
+
pb[field] === void 0 ? `${key}.${field} (removed, was ${String(pa[field])})` : `${key}.${field} ${String(pb[field])}`
|
|
578
|
+
);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
return changes;
|
|
582
|
+
}
|
|
583
|
+
|
|
482
584
|
// src/commands/build.ts
|
|
483
585
|
import { spawn as spawn2 } from "child_process";
|
|
484
586
|
import { createInterface } from "readline";
|
|
@@ -490,12 +592,12 @@ import chalk2 from "chalk";
|
|
|
490
592
|
import { input, select } from "@inquirer/prompts";
|
|
491
593
|
|
|
492
594
|
// src/lib/project-detect.ts
|
|
493
|
-
import { existsSync as existsSync2, readFileSync as
|
|
494
|
-
import { join as
|
|
595
|
+
import { existsSync as existsSync2, readFileSync as readFileSync4, readdirSync, statSync } from "fs";
|
|
596
|
+
import { join as join4 } from "path";
|
|
495
597
|
import ignore from "ignore";
|
|
496
598
|
function readJson(cwd, name) {
|
|
497
599
|
try {
|
|
498
|
-
return JSON.parse(
|
|
600
|
+
return JSON.parse(readFileSync4(join4(cwd, name), "utf-8"));
|
|
499
601
|
} catch {
|
|
500
602
|
return null;
|
|
501
603
|
}
|
|
@@ -510,19 +612,19 @@ function isFile(path) {
|
|
|
510
612
|
function detectFlavor(cwd) {
|
|
511
613
|
let deps = {};
|
|
512
614
|
try {
|
|
513
|
-
const pkg = JSON.parse(
|
|
615
|
+
const pkg = JSON.parse(readFileSync4(join4(cwd, "package.json"), "utf-8"));
|
|
514
616
|
deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
515
617
|
} catch {
|
|
516
618
|
}
|
|
517
619
|
if ("expo" in deps) return "expo";
|
|
518
|
-
if (existsSync2(
|
|
519
|
-
if (isFile(
|
|
620
|
+
if (existsSync2(join4(cwd, "android", "gradlew")) || iosWorkspaceName(cwd)) return "bare-rn";
|
|
621
|
+
if (isFile(join4(cwd, "manifest"))) return "roku";
|
|
520
622
|
return "unknown";
|
|
521
623
|
}
|
|
522
624
|
function nativeDirShipped(cwd, dir) {
|
|
523
|
-
if (!existsSync2(
|
|
625
|
+
if (!existsSync2(join4(cwd, dir))) return false;
|
|
524
626
|
try {
|
|
525
|
-
const ig = ignore().add(
|
|
627
|
+
const ig = ignore().add(readFileSync4(join4(cwd, ".gitignore"), "utf-8"));
|
|
526
628
|
return !ig.ignores(`${dir}/`);
|
|
527
629
|
} catch {
|
|
528
630
|
return true;
|
|
@@ -538,7 +640,7 @@ function isManagedExpo(cwd, platform) {
|
|
|
538
640
|
}
|
|
539
641
|
function iosWorkspaceName(cwd) {
|
|
540
642
|
try {
|
|
541
|
-
const found = readdirSync(
|
|
643
|
+
const found = readdirSync(join4(cwd, "ios")).find((f) => f.endsWith(".xcworkspace"));
|
|
542
644
|
return found ? found.replace(/\.xcworkspace$/, "") : null;
|
|
543
645
|
} catch {
|
|
544
646
|
return null;
|
|
@@ -547,9 +649,9 @@ function iosWorkspaceName(cwd) {
|
|
|
547
649
|
var IOS_ARTIFACT = "ios/build/Build/Products/Debug-iphonesimulator/*.app";
|
|
548
650
|
var ANDROID_ARTIFACT = "android/app/build/outputs/apk/debug/*.apk";
|
|
549
651
|
function installCommand(cwd) {
|
|
550
|
-
if (existsSync2(
|
|
551
|
-
if (existsSync2(
|
|
552
|
-
if (existsSync2(
|
|
652
|
+
if (existsSync2(join4(cwd, "yarn.lock"))) return "yarn install --immutable";
|
|
653
|
+
if (existsSync2(join4(cwd, "pnpm-lock.yaml"))) return "pnpm install --frozen-lockfile";
|
|
654
|
+
if (existsSync2(join4(cwd, "package-lock.json"))) return "npm ci";
|
|
553
655
|
return "yarn install --immutable";
|
|
554
656
|
}
|
|
555
657
|
function managedExpoAndroidCommand(cwd) {
|
|
@@ -563,7 +665,7 @@ var ROKU_ZIP_COMMAND = "zip -r out/channel.zip manifest source components images
|
|
|
563
665
|
function rokuDefaults(cwd) {
|
|
564
666
|
const pkg = readJson(cwd, "package.json");
|
|
565
667
|
const scripts = pkg?.scripts ?? {};
|
|
566
|
-
const runner = existsSync2(
|
|
668
|
+
const runner = existsSync2(join4(cwd, "yarn.lock")) ? "yarn" : "npm run";
|
|
567
669
|
const packageScript = Object.entries(scripts).find(
|
|
568
670
|
([name, body]) => !name.startsWith("deploy") && (name.startsWith("package") || body.includes("--package-only") || body.includes("zipPackage"))
|
|
569
671
|
);
|
|
@@ -621,7 +723,7 @@ function expoSanitizedName(name) {
|
|
|
621
723
|
return name.replace(/[\W_]+/g, "").normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
622
724
|
}
|
|
623
725
|
function managedExpoIosName(cwd) {
|
|
624
|
-
if (existsSync2(
|
|
726
|
+
if (existsSync2(join4(cwd, "app.config.js")) || existsSync2(join4(cwd, "app.config.ts"))) {
|
|
625
727
|
return null;
|
|
626
728
|
}
|
|
627
729
|
const raw = readJson(cwd, "app.config.json") ?? readJson(cwd, "app.json");
|
|
@@ -766,9 +868,9 @@ async function promptMetroCommand(cwd, platform) {
|
|
|
766
868
|
|
|
767
869
|
// src/lib/source-pack.ts
|
|
768
870
|
import { execFile, spawn } from "child_process";
|
|
769
|
-
import { lstatSync, mkdtempSync, readFileSync as
|
|
871
|
+
import { lstatSync, mkdtempSync, readFileSync as readFileSync5, rmSync as rmSync4, statSync as statSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
770
872
|
import { tmpdir } from "os";
|
|
771
|
-
import { join as
|
|
873
|
+
import { join as join5 } from "path";
|
|
772
874
|
import ignore2 from "ignore";
|
|
773
875
|
var NotAGitRepoError = class extends Error {
|
|
774
876
|
constructor(message) {
|
|
@@ -794,15 +896,15 @@ async function listSourceFiles(projectRoot) {
|
|
|
794
896
|
const stdout = await runGitLsFiles(projectRoot);
|
|
795
897
|
const rawPaths = stdout.length ? stdout.split("\0").filter((p) => p.length > 0) : [];
|
|
796
898
|
let filtered = rawPaths;
|
|
797
|
-
const vvignorePath =
|
|
899
|
+
const vvignorePath = join5(projectRoot, ".vvignore");
|
|
798
900
|
if (statOrNull(vvignorePath)?.isFile()) {
|
|
799
|
-
const ig = ignore2().add(
|
|
901
|
+
const ig = ignore2().add(readFileSync5(vvignorePath, "utf-8"));
|
|
800
902
|
filtered = filtered.filter((p) => !ig.ignores(p));
|
|
801
903
|
}
|
|
802
904
|
const result = [];
|
|
803
905
|
for (const relPath of filtered) {
|
|
804
906
|
if (isForceExcluded(relPath)) continue;
|
|
805
|
-
const st = statOrNull(
|
|
907
|
+
const st = statOrNull(join5(projectRoot, relPath));
|
|
806
908
|
if (!st) continue;
|
|
807
909
|
result.push(relPath);
|
|
808
910
|
}
|
|
@@ -852,7 +954,7 @@ async function packSource(projectRoot, files, opts = {}) {
|
|
|
852
954
|
let totalBytes = 0;
|
|
853
955
|
const sized = [];
|
|
854
956
|
for (const relPath of files) {
|
|
855
|
-
const bytes = lstatSync(
|
|
957
|
+
const bytes = lstatSync(join5(projectRoot, relPath)).size;
|
|
856
958
|
totalBytes += bytes;
|
|
857
959
|
sized.push({ path: relPath, bytes });
|
|
858
960
|
}
|
|
@@ -863,14 +965,14 @@ async function packSource(projectRoot, files, opts = {}) {
|
|
|
863
965
|
largest
|
|
864
966
|
);
|
|
865
967
|
}
|
|
866
|
-
const tmpDir = mkdtempSync(
|
|
867
|
-
const cleanup = () =>
|
|
968
|
+
const tmpDir = mkdtempSync(join5(tmpdir(), "vibeview-source-pack-"));
|
|
969
|
+
const cleanup = () => rmSync4(tmpDir, { recursive: true, force: true });
|
|
868
970
|
try {
|
|
869
971
|
const supportsNull = await probeTarNullSupport();
|
|
870
|
-
const tarPath =
|
|
972
|
+
const tarPath = join5(tmpDir, "source.tgz");
|
|
871
973
|
if (supportsNull) {
|
|
872
|
-
const listPath =
|
|
873
|
-
|
|
974
|
+
const listPath = join5(tmpDir, "files.list");
|
|
975
|
+
writeFileSync4(listPath, files.map((f) => `${f}\0`).join(""));
|
|
874
976
|
await runTar(["-czf", tarPath, "-C", projectRoot, "--null", "-T", listPath]);
|
|
875
977
|
} else {
|
|
876
978
|
const badName = files.find((f) => f.includes("\n"));
|
|
@@ -879,8 +981,8 @@ async function packSource(projectRoot, files, opts = {}) {
|
|
|
879
981
|
`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`
|
|
880
982
|
);
|
|
881
983
|
}
|
|
882
|
-
const listPath =
|
|
883
|
-
|
|
984
|
+
const listPath = join5(tmpDir, "files.list");
|
|
985
|
+
writeFileSync4(listPath, files.map((f) => `${f}
|
|
884
986
|
`).join(""));
|
|
885
987
|
await runTar(["-czf", tarPath, "-C", projectRoot, "-T", listPath]);
|
|
886
988
|
}
|
|
@@ -900,12 +1002,12 @@ var nullSupportCache = null;
|
|
|
900
1002
|
function probeTarNullSupport() {
|
|
901
1003
|
if (!nullSupportCache) {
|
|
902
1004
|
nullSupportCache = new Promise((resolve) => {
|
|
903
|
-
const tmp = mkdtempSync(
|
|
904
|
-
const listPath =
|
|
905
|
-
|
|
906
|
-
const out =
|
|
1005
|
+
const tmp = mkdtempSync(join5(tmpdir(), "vibeview-tar-probe-"));
|
|
1006
|
+
const listPath = join5(tmp, "empty.list");
|
|
1007
|
+
writeFileSync4(listPath, "");
|
|
1008
|
+
const out = join5(tmp, "probe.tgz");
|
|
907
1009
|
execFile("tar", ["-czf", out, "-C", tmp, "--null", "-T", listPath], (err) => {
|
|
908
|
-
|
|
1010
|
+
rmSync4(tmp, { recursive: true, force: true });
|
|
909
1011
|
resolve(!err);
|
|
910
1012
|
});
|
|
911
1013
|
});
|
|
@@ -1777,7 +1879,7 @@ your build command must produce a zip \u2014 use the package/zip step, not deplo
|
|
|
1777
1879
|
|
|
1778
1880
|
// src/commands/dev.ts
|
|
1779
1881
|
import { spawn as spawn4 } from "child_process";
|
|
1780
|
-
import { createWriteStream, mkdirSync as
|
|
1882
|
+
import { createWriteStream, mkdirSync as mkdirSync4 } from "fs";
|
|
1781
1883
|
import { dirname as dirname4 } from "path";
|
|
1782
1884
|
import { createInterface as createInterface3 } from "readline";
|
|
1783
1885
|
import { select as select3 } from "@inquirer/prompts";
|
|
@@ -1973,15 +2075,15 @@ import { spawn as spawn3 } from "child_process";
|
|
|
1973
2075
|
import { createInterface as createInterface2 } from "readline";
|
|
1974
2076
|
|
|
1975
2077
|
// src/lib/metro-state.ts
|
|
1976
|
-
import { existsSync as existsSync3, mkdirSync as
|
|
1977
|
-
import { dirname as dirname2, join as
|
|
2078
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync6, rmSync as rmSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
2079
|
+
import { dirname as dirname2, join as join6 } from "path";
|
|
1978
2080
|
var STATE_DIR2 = ".vibeview";
|
|
1979
2081
|
var STATE_FILE2 = "metro-state.json";
|
|
1980
2082
|
function statePath2(cwd) {
|
|
1981
|
-
return
|
|
2083
|
+
return join6(cwd, STATE_DIR2, STATE_FILE2);
|
|
1982
2084
|
}
|
|
1983
2085
|
function metroLogPath(cwd) {
|
|
1984
|
-
return
|
|
2086
|
+
return join6(cwd, STATE_DIR2, "metro.log");
|
|
1985
2087
|
}
|
|
1986
2088
|
function isPidAlive2(pid) {
|
|
1987
2089
|
try {
|
|
@@ -1993,13 +2095,13 @@ function isPidAlive2(pid) {
|
|
|
1993
2095
|
}
|
|
1994
2096
|
function writeMetroState(cwd, state) {
|
|
1995
2097
|
const path = statePath2(cwd);
|
|
1996
|
-
|
|
1997
|
-
|
|
2098
|
+
mkdirSync3(dirname2(path), { recursive: true });
|
|
2099
|
+
writeFileSync5(path, JSON.stringify(state, null, 2) + "\n");
|
|
1998
2100
|
}
|
|
1999
2101
|
function readMetroState(cwd) {
|
|
2000
2102
|
let parsed;
|
|
2001
2103
|
try {
|
|
2002
|
-
parsed = JSON.parse(
|
|
2104
|
+
parsed = JSON.parse(readFileSync6(statePath2(cwd), "utf-8"));
|
|
2003
2105
|
} catch {
|
|
2004
2106
|
return null;
|
|
2005
2107
|
}
|
|
@@ -2011,7 +2113,7 @@ function readMetroState(cwd) {
|
|
|
2011
2113
|
}
|
|
2012
2114
|
function clearMetroState(cwd) {
|
|
2013
2115
|
const path = statePath2(cwd);
|
|
2014
|
-
if (existsSync3(path))
|
|
2116
|
+
if (existsSync3(path)) rmSync5(path, { force: true });
|
|
2015
2117
|
}
|
|
2016
2118
|
function killOwnedMetro(cwd) {
|
|
2017
2119
|
const state = readMetroState(cwd);
|
|
@@ -2524,17 +2626,19 @@ function requireNonInteractive(json, message) {
|
|
|
2524
2626
|
}
|
|
2525
2627
|
async function ensureConfig(api, opts) {
|
|
2526
2628
|
const existing = loadDevConfig(process.cwd());
|
|
2629
|
+
const local = loadLocalDevConfig(process.cwd());
|
|
2630
|
+
const effective = effectiveDevConfig(existing, local);
|
|
2527
2631
|
if (opts.platform && !isDevPlatform(opts.platform)) {
|
|
2528
2632
|
console.error(chalk4.red(`error: --platform must be one of ${DEV_PLATFORMS.join(", ")}`));
|
|
2529
2633
|
process.exit(2);
|
|
2530
2634
|
}
|
|
2531
|
-
if (!opts.platform && !
|
|
2635
|
+
if (!opts.platform && !effective?.defaultPlatform) {
|
|
2532
2636
|
requireNonInteractive(
|
|
2533
2637
|
opts.json,
|
|
2534
2638
|
`--platform is required (no vibeview.json default yet) \u2014 pass --platform <${DEV_PLATFORMS.join("|")}>, or run once interactively to set a default.`
|
|
2535
2639
|
);
|
|
2536
2640
|
}
|
|
2537
|
-
const platform = opts.platform ||
|
|
2641
|
+
const platform = opts.platform || effective?.defaultPlatform || await select3({
|
|
2538
2642
|
message: "Platform to run on",
|
|
2539
2643
|
choices: platformChoices(process.cwd())
|
|
2540
2644
|
});
|
|
@@ -2571,7 +2675,7 @@ async function ensureConfig(api, opts) {
|
|
|
2571
2675
|
}
|
|
2572
2676
|
}
|
|
2573
2677
|
}
|
|
2574
|
-
const entry =
|
|
2678
|
+
const entry = effective?.platforms?.[platform];
|
|
2575
2679
|
let allApps;
|
|
2576
2680
|
try {
|
|
2577
2681
|
allApps = await api.listApps();
|
|
@@ -2591,6 +2695,7 @@ ${hint}`);
|
|
|
2591
2695
|
process.exit(2);
|
|
2592
2696
|
}
|
|
2593
2697
|
let appId = choice.appId;
|
|
2698
|
+
const appFromFlag = Boolean(opts.app) && appId === opts.app;
|
|
2594
2699
|
let builtThisRun = false;
|
|
2595
2700
|
if (choice.staleCached) {
|
|
2596
2701
|
console.error(
|
|
@@ -2663,22 +2768,51 @@ ${hint}`);
|
|
|
2663
2768
|
});
|
|
2664
2769
|
}
|
|
2665
2770
|
const deviceCategory = isTv ? "tv" : opts.deviceCategory || entry?.deviceCategory || "phone";
|
|
2666
|
-
const
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
);
|
|
2771
|
+
const metroPortFlag = opts.metroPort ? parseInt(opts.metroPort, 10) : void 0;
|
|
2772
|
+
const metroResolved = resolveMetro(effective, platform, metroPortFlag);
|
|
2773
|
+
const committedEntry = existing?.platforms?.[platform];
|
|
2774
|
+
const nextLocal = JSON.parse(JSON.stringify(local));
|
|
2775
|
+
const localEntry = (nextLocal.platforms ??= {})[platform] ??= {};
|
|
2776
|
+
let committedAppId = committedEntry?.appId;
|
|
2777
|
+
if (!committedAppId) {
|
|
2778
|
+
committedAppId = appId;
|
|
2779
|
+
} else if (appId !== committedAppId) {
|
|
2780
|
+
if (appFromFlag || local.platforms?.[platform]?.appId !== void 0) {
|
|
2781
|
+
localEntry.appId = appId;
|
|
2782
|
+
} else {
|
|
2783
|
+
committedAppId = appId;
|
|
2784
|
+
}
|
|
2785
|
+
}
|
|
2786
|
+
if (appId === committedAppId) delete localEntry.appId;
|
|
2787
|
+
let committedCategory = committedEntry?.deviceCategory;
|
|
2788
|
+
if (!isTv) {
|
|
2789
|
+
if (!committedCategory) committedCategory = deviceCategory;
|
|
2790
|
+
if (deviceCategory !== committedCategory) localEntry.deviceCategory = deviceCategory;
|
|
2791
|
+
else delete localEntry.deviceCategory;
|
|
2792
|
+
}
|
|
2793
|
+
const committedDefault = existing?.defaultPlatform ?? platform;
|
|
2794
|
+
if (opts.platform) {
|
|
2795
|
+
if (platform !== committedDefault) nextLocal.defaultPlatform = platform;
|
|
2796
|
+
else delete nextLocal.defaultPlatform;
|
|
2797
|
+
}
|
|
2671
2798
|
const cfg = buildSavedConfig({
|
|
2672
2799
|
existing,
|
|
2673
2800
|
platform,
|
|
2674
|
-
appId,
|
|
2675
|
-
deviceCategory,
|
|
2801
|
+
appId: committedAppId,
|
|
2802
|
+
deviceCategory: committedCategory ?? deviceCategory,
|
|
2676
2803
|
isTv,
|
|
2677
|
-
|
|
2804
|
+
// Never the flag: vibeview.json keeps (or, on the first run, seeds) the
|
|
2805
|
+
// shared default; the flag's port is saved locally below.
|
|
2806
|
+
metroPortFlag: void 0,
|
|
2678
2807
|
isFirstRun,
|
|
2679
2808
|
firstRunMetro,
|
|
2680
2809
|
firstRunBuild
|
|
2681
2810
|
});
|
|
2811
|
+
cfg.defaultPlatform = committedDefault;
|
|
2812
|
+
if (metroPortFlag !== void 0 && PLATFORM_NEEDS_METRO[platform]) {
|
|
2813
|
+
if (metroPortFlag !== resolveMetro(cfg, platform).port) localEntry.metroPort = metroPortFlag;
|
|
2814
|
+
else delete localEntry.metroPort;
|
|
2815
|
+
}
|
|
2682
2816
|
const commitConfig = () => {
|
|
2683
2817
|
updateDevConfig(process.cwd(), platform, (fresh) => {
|
|
2684
2818
|
applyConfigChanges(fresh, existing, cfg);
|
|
@@ -2689,6 +2823,15 @@ ${hint}`);
|
|
|
2689
2823
|
console.error(chalk4.dim(`Updated vibeview.json: ${changes.join(", ")}`));
|
|
2690
2824
|
}
|
|
2691
2825
|
}
|
|
2826
|
+
const localChanges = describeLocalChanges(local, nextLocal);
|
|
2827
|
+
if (localChanges.length) {
|
|
2828
|
+
saveLocalDevConfig(process.cwd(), nextLocal);
|
|
2829
|
+
console.error(
|
|
2830
|
+
chalk4.dim(
|
|
2831
|
+
`Saved to .vibeview/local.json (yours only, not committed): ${localChanges.join(", ")}`
|
|
2832
|
+
)
|
|
2833
|
+
);
|
|
2834
|
+
}
|
|
2692
2835
|
if (isFirstRun) {
|
|
2693
2836
|
console.error(chalk4.dim(`Created vibeview.json (platform: ${platform}, app: ${appId}).`));
|
|
2694
2837
|
const hint = fillLaterHint(
|
|
@@ -2698,7 +2841,7 @@ ${hint}`);
|
|
|
2698
2841
|
if (hint) console.error(chalk4.dim(hint));
|
|
2699
2842
|
}
|
|
2700
2843
|
};
|
|
2701
|
-
const finalMetro = isFirstRun ? resolveMetro(cfg,
|
|
2844
|
+
const finalMetro = isFirstRun ? resolveMetro(effectiveDevConfig(cfg, nextLocal), platform, metroPortFlag) : metroResolved;
|
|
2702
2845
|
return {
|
|
2703
2846
|
platform,
|
|
2704
2847
|
appId,
|
|
@@ -2793,12 +2936,21 @@ async function runDevInProcess(opts) {
|
|
|
2793
2936
|
(l) => console.error(chalk4.dim(l))
|
|
2794
2937
|
);
|
|
2795
2938
|
cfg.appId = result.public_id;
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2939
|
+
const localNow = loadLocalDevConfig(process.cwd());
|
|
2940
|
+
const localApp = localNow.platforms?.[cfg.platform];
|
|
2941
|
+
if (localApp?.appId !== void 0) {
|
|
2942
|
+
const committedApp = loadDevConfig(process.cwd())?.platforms?.[cfg.platform]?.appId;
|
|
2943
|
+
if (result.public_id === committedApp) delete localApp.appId;
|
|
2944
|
+
else localApp.appId = result.public_id;
|
|
2945
|
+
saveLocalDevConfig(process.cwd(), localNow);
|
|
2946
|
+
} else {
|
|
2947
|
+
updateDevConfig(process.cwd(), cfg.platform, (fresh) => {
|
|
2948
|
+
const entry = fresh.platforms[cfg.platform];
|
|
2949
|
+
if (!entry || entry.appId === result.public_id) return false;
|
|
2950
|
+
entry.appId = result.public_id;
|
|
2951
|
+
return true;
|
|
2952
|
+
});
|
|
2953
|
+
}
|
|
2802
2954
|
} catch (err) {
|
|
2803
2955
|
const message = err.message;
|
|
2804
2956
|
console.error(chalk4.red(`error: ${message}`));
|
|
@@ -2824,7 +2976,7 @@ async function runDevInProcess(opts) {
|
|
|
2824
2976
|
const captureMetroLine = (line) => {
|
|
2825
2977
|
try {
|
|
2826
2978
|
if (!metroLogStream) {
|
|
2827
|
-
|
|
2979
|
+
mkdirSync4(dirname4(metroLogPath(process.cwd())), { recursive: true });
|
|
2828
2980
|
metroLogStream = createWriteStream(metroLogPath(process.cwd()), { flags: "w" });
|
|
2829
2981
|
}
|
|
2830
2982
|
metroLogStream.write(line.replace(/^\[metro\] /, "") + "\n");
|
|
@@ -3807,7 +3959,7 @@ function agentExitCode(env) {
|
|
|
3807
3959
|
}
|
|
3808
3960
|
|
|
3809
3961
|
// src/commands/agent.ts
|
|
3810
|
-
import { writeFileSync as
|
|
3962
|
+
import { writeFileSync as writeFileSync6 } from "fs";
|
|
3811
3963
|
var REF_RE = /^@?(e\d+)$/;
|
|
3812
3964
|
var COORDS_RE = /^(-?\d+),\s*(-?\d+)$/;
|
|
3813
3965
|
var FlagError = class extends Error {
|
|
@@ -4017,7 +4169,7 @@ async function runAgentVerb(cmd, positionals, opts) {
|
|
|
4017
4169
|
`);
|
|
4018
4170
|
process.exit(1);
|
|
4019
4171
|
}
|
|
4020
|
-
|
|
4172
|
+
writeFileSync6(outPath, imageBytes);
|
|
4021
4173
|
if (opts.json) {
|
|
4022
4174
|
console.log(JSON.stringify(envelope));
|
|
4023
4175
|
} else {
|
|
@@ -4040,8 +4192,10 @@ export {
|
|
|
4040
4192
|
DEV_PLATFORMS,
|
|
4041
4193
|
PLATFORM_NEEDS_METRO,
|
|
4042
4194
|
isDevPlatform,
|
|
4195
|
+
platformLabel,
|
|
4043
4196
|
platformWithArticle,
|
|
4044
4197
|
loadDevConfig,
|
|
4198
|
+
resolveMetro,
|
|
4045
4199
|
parseBuildId,
|
|
4046
4200
|
checkDevBuild,
|
|
4047
4201
|
buildRows,
|
|
@@ -4052,6 +4206,8 @@ export {
|
|
|
4052
4206
|
readMetroState,
|
|
4053
4207
|
killOwnedMetro,
|
|
4054
4208
|
checkMetro,
|
|
4209
|
+
loadLocalDevConfig,
|
|
4210
|
+
effectiveDevConfig,
|
|
4055
4211
|
buildCommand,
|
|
4056
4212
|
formatDuration,
|
|
4057
4213
|
BUILD_TERMINAL_STATUSES,
|
|
@@ -74,6 +74,25 @@ var AuthError = class extends Error {
|
|
|
74
74
|
this.name = "AuthError";
|
|
75
75
|
}
|
|
76
76
|
};
|
|
77
|
+
var TokenScopeError = class extends AuthError {
|
|
78
|
+
constructor(message) {
|
|
79
|
+
super(message);
|
|
80
|
+
this.name = "TokenScopeError";
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
var SCOPE_REFUSAL_PREFIX = "This API token has the 'ci' scope";
|
|
84
|
+
function credentialError(status, text) {
|
|
85
|
+
const detail = extractDetail(text);
|
|
86
|
+
if (status === 401) {
|
|
87
|
+
return detail && /expired/i.test(detail) ? new AuthError(detail) : new AuthError();
|
|
88
|
+
}
|
|
89
|
+
if (status === 403 && detail?.startsWith(SCOPE_REFUSAL_PREFIX)) {
|
|
90
|
+
return new TokenScopeError(
|
|
91
|
+
`${detail} Set VIBEVIEW_API_TOKEN (or --token) to a full-access token for this command.`
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
return void 0;
|
|
95
|
+
}
|
|
77
96
|
function errorExitCode(err, otherwise = 1) {
|
|
78
97
|
return err instanceof AuthError ? 2 : otherwise;
|
|
79
98
|
}
|
|
@@ -168,7 +187,8 @@ var ApiClient = class {
|
|
|
168
187
|
});
|
|
169
188
|
if (!resp.ok) {
|
|
170
189
|
const text = await resp.text().catch(() => "");
|
|
171
|
-
|
|
190
|
+
const credErr = credentialError(resp.status, text);
|
|
191
|
+
if (credErr) throw credErr;
|
|
172
192
|
if (resp.status === 404) throw new NotFoundError(`Not found: ${extractDetail(text) ?? path}`);
|
|
173
193
|
throw new ApiStatusError(
|
|
174
194
|
resp.status,
|
|
@@ -197,7 +217,9 @@ var ApiClient = class {
|
|
|
197
217
|
device_category: options?.device ? void 0 : options?.deviceCategory,
|
|
198
218
|
device_id: options?.device,
|
|
199
219
|
build_id: options?.build,
|
|
200
|
-
metadata: options?.metadata
|
|
220
|
+
metadata: options?.metadata,
|
|
221
|
+
platform: options?.platform,
|
|
222
|
+
app_id: options?.app
|
|
201
223
|
});
|
|
202
224
|
}
|
|
203
225
|
async runTest(testId, options) {
|
|
@@ -210,7 +232,9 @@ var ApiClient = class {
|
|
|
210
232
|
device_category: options?.device ? void 0 : options?.deviceCategory,
|
|
211
233
|
device_id: options?.device,
|
|
212
234
|
build_id: options?.build,
|
|
213
|
-
metadata: options?.metadata
|
|
235
|
+
metadata: options?.metadata,
|
|
236
|
+
platform: options?.platform,
|
|
237
|
+
app_id: options?.app
|
|
214
238
|
});
|
|
215
239
|
}
|
|
216
240
|
/** Cancel a still-queued CI run by its queue entry id. Idempotent best-effort. */
|
|
@@ -244,7 +268,8 @@ var ApiClient = class {
|
|
|
244
268
|
});
|
|
245
269
|
if (!resp.ok) {
|
|
246
270
|
const text = await resp.text().catch(() => "");
|
|
247
|
-
|
|
271
|
+
const credErr = credentialError(resp.status, text);
|
|
272
|
+
if (credErr) throw credErr;
|
|
248
273
|
if (resp.status === 404) throw new NotFoundError(`Not found: ${extractDetail(text) ?? path}`);
|
|
249
274
|
throw new ApiStatusError(
|
|
250
275
|
resp.status,
|
|
@@ -267,7 +292,8 @@ var ApiClient = class {
|
|
|
267
292
|
});
|
|
268
293
|
if (!resp.ok) {
|
|
269
294
|
const text = await resp.text().catch(() => "");
|
|
270
|
-
|
|
295
|
+
const credErr = credentialError(resp.status, text);
|
|
296
|
+
if (credErr) throw credErr;
|
|
271
297
|
if (resp.status === 404) throw new NotFoundError(`Not found: ${extractDetail(text) ?? path}`);
|
|
272
298
|
throw new ApiStatusError(
|
|
273
299
|
resp.status,
|
|
@@ -429,7 +455,8 @@ var ApiClient = class {
|
|
|
429
455
|
const resp = await this.fetchOrExplain(url, { method: "GET", headers });
|
|
430
456
|
if (!resp.ok) {
|
|
431
457
|
const text = await resp.text().catch(() => "");
|
|
432
|
-
|
|
458
|
+
const credErr = credentialError(resp.status, text);
|
|
459
|
+
if (credErr) throw credErr;
|
|
433
460
|
if (resp.status === 404)
|
|
434
461
|
throw new NotFoundError(`Not found: ${extractDetail(text) ?? `/builds/${id}/artifact`}`);
|
|
435
462
|
throw new ApiStatusError(
|
package/dist/cli.js
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
listBuilds,
|
|
16
16
|
loadDevConfig,
|
|
17
17
|
newTransitionState,
|
|
18
|
+
platformLabel,
|
|
18
19
|
platformWithArticle,
|
|
19
20
|
pressButtonsHelp,
|
|
20
21
|
printOutcomeLine,
|
|
@@ -22,7 +23,7 @@ import {
|
|
|
22
23
|
readDevState,
|
|
23
24
|
readMetroState,
|
|
24
25
|
runAgentVerb
|
|
25
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-NOZGZT3G.js";
|
|
26
27
|
import {
|
|
27
28
|
ApiClient,
|
|
28
29
|
ApiStatusError,
|
|
@@ -43,7 +44,7 @@ import {
|
|
|
43
44
|
resolveAuth,
|
|
44
45
|
saveConfig,
|
|
45
46
|
uploadApp
|
|
46
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-ZZK7YQSP.js";
|
|
47
48
|
|
|
48
49
|
// src/cli.ts
|
|
49
50
|
import { createRequire } from "module";
|
|
@@ -570,6 +571,29 @@ function mapExitCode(status) {
|
|
|
570
571
|
return EXIT_INFRA_ERROR;
|
|
571
572
|
}
|
|
572
573
|
|
|
574
|
+
// src/lib/run-target.ts
|
|
575
|
+
var RUN_PLATFORMS = ["ios", "android", "tvos", "androidtv"];
|
|
576
|
+
function parseRunPlatform(value) {
|
|
577
|
+
if (value === void 0) return void 0;
|
|
578
|
+
if (!RUN_PLATFORMS.includes(value)) {
|
|
579
|
+
throw new Error(`--platform must be one of ${RUN_PLATFORMS.join(", ")}, got "${value}"`);
|
|
580
|
+
}
|
|
581
|
+
return value;
|
|
582
|
+
}
|
|
583
|
+
function describeRunTarget(response, options) {
|
|
584
|
+
const asked = Boolean(options.platform || options.app);
|
|
585
|
+
if (!response.app_id) {
|
|
586
|
+
if (asked) {
|
|
587
|
+
throw new Error(
|
|
588
|
+
"this VibeView server does not support --platform/--app on test runs yet; the run was stopped rather than run on the test\u2019s own platform"
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
return null;
|
|
592
|
+
}
|
|
593
|
+
if (!asked) return null;
|
|
594
|
+
return `${response.app_id} (${platformLabel(response.platform ?? "")})`;
|
|
595
|
+
}
|
|
596
|
+
|
|
573
597
|
// src/commands/run-suite.ts
|
|
574
598
|
async function runSuite(suiteId, options) {
|
|
575
599
|
const { token, baseUrl } = resolveAuth(options);
|
|
@@ -635,14 +659,18 @@ ${signal} received.
|
|
|
635
659
|
deviceCategory: options.deviceCategory,
|
|
636
660
|
device: options.device,
|
|
637
661
|
build: options.build,
|
|
638
|
-
metadata: options.metadata
|
|
662
|
+
metadata: options.metadata,
|
|
663
|
+
platform: options.platform,
|
|
664
|
+
app: options.app
|
|
639
665
|
});
|
|
640
666
|
sessionId = response.session_id;
|
|
641
667
|
runId = response.run_id;
|
|
668
|
+
const target = describeRunTarget(response, options);
|
|
642
669
|
spinner.stop();
|
|
643
670
|
process.stderr.write(
|
|
644
671
|
`${chalk3.dim("Suite:")} ${suiteId}
|
|
645
|
-
|
|
672
|
+
` + (target ? `${chalk3.dim("App:")} ${target}
|
|
673
|
+
` : "") + `${chalk3.dim("Run:")} ${response.run_id}
|
|
646
674
|
` + (sessionId ? `${chalk3.dim("Session:")} ${sessionId}
|
|
647
675
|
` : "")
|
|
648
676
|
);
|
|
@@ -1009,16 +1037,20 @@ ${signal} received.
|
|
|
1009
1037
|
deviceCategory: options.deviceCategory,
|
|
1010
1038
|
device: options.device,
|
|
1011
1039
|
build: options.build,
|
|
1012
|
-
metadata: options.metadata
|
|
1040
|
+
metadata: options.metadata,
|
|
1041
|
+
platform: options.platform,
|
|
1042
|
+
app: options.app
|
|
1013
1043
|
});
|
|
1014
1044
|
sessionId = response.session_id;
|
|
1015
1045
|
runId = response.run_id;
|
|
1046
|
+
const target = describeRunTarget(response, options);
|
|
1016
1047
|
spinner.stop();
|
|
1017
1048
|
process.stderr.write(
|
|
1018
1049
|
`${chalk5.dim("Session:")} ${sessionId || "unknown"}
|
|
1019
1050
|
${chalk5.dim("Device:")} ${response.device_name || "allocating..."}
|
|
1020
1051
|
${chalk5.dim("Test:")} ${testId}
|
|
1021
|
-
|
|
1052
|
+
` + (target ? `${chalk5.dim("App:")} ${target}
|
|
1053
|
+
` : "") + `${chalk5.dim("Run:")} ${response.run_id}
|
|
1022
1054
|
`
|
|
1023
1055
|
);
|
|
1024
1056
|
spinner.start("Waiting for device...");
|
|
@@ -3568,11 +3600,24 @@ program.command("run-suite <suite-id>").description("Run all test cases in a sui
|
|
|
3568
3600
|
).option(
|
|
3569
3601
|
"--device <device_id>",
|
|
3570
3602
|
"Pin run to a specific device (use `vibeview list-devices` to find one). Conflicts with --device-category."
|
|
3603
|
+
).option(
|
|
3604
|
+
"--platform <platform>",
|
|
3605
|
+
"Run the suite on another platform (ios, android, tvos, androidtv): runs your app there with the same bundle id / package"
|
|
3606
|
+
).option(
|
|
3607
|
+
"--app <app-id>",
|
|
3608
|
+
"Run the suite against this app instead of its own (e.g. another platform build with a different bundle id)"
|
|
3571
3609
|
).option("--build <id>", "Specific AppBuild id to run against (defaults to latest)").option("--metadata <json>", "JSON object passed through to webhooks (e.g. PR context)").option("--metadata-file <path>", "Read JSON object from a file (use for nested JSON in CI)").action((suiteId, opts) => {
|
|
3572
3610
|
if (opts.device && opts.deviceCategory) {
|
|
3573
3611
|
process.stderr.write("error: --device cannot be combined with --device-category\n");
|
|
3574
3612
|
process.exit(2);
|
|
3575
3613
|
}
|
|
3614
|
+
try {
|
|
3615
|
+
parseRunPlatform(opts.platform);
|
|
3616
|
+
} catch (err) {
|
|
3617
|
+
process.stderr.write(`error: ${err.message}
|
|
3618
|
+
`);
|
|
3619
|
+
process.exit(2);
|
|
3620
|
+
}
|
|
3576
3621
|
const metadata = resolveMetadata(opts);
|
|
3577
3622
|
return runSuite(suiteId, {
|
|
3578
3623
|
...opts,
|
|
@@ -3601,6 +3646,13 @@ program.command("run-suites").description("Run all suites matching a tag filter"
|
|
|
3601
3646
|
process.stderr.write("error: --device cannot be combined with --device-category\n");
|
|
3602
3647
|
process.exit(2);
|
|
3603
3648
|
}
|
|
3649
|
+
try {
|
|
3650
|
+
parseRunPlatform(opts.platform);
|
|
3651
|
+
} catch (err) {
|
|
3652
|
+
process.stderr.write(`error: ${err.message}
|
|
3653
|
+
`);
|
|
3654
|
+
process.exit(2);
|
|
3655
|
+
}
|
|
3604
3656
|
const metadata = resolveMetadata(opts);
|
|
3605
3657
|
return runSuites({
|
|
3606
3658
|
...opts,
|
|
@@ -3616,11 +3668,24 @@ program.command("run-test <test-id>").description("Run a single test case").opti
|
|
|
3616
3668
|
).option(
|
|
3617
3669
|
"--device <device_id>",
|
|
3618
3670
|
"Pin run to a specific device (use `vibeview list-devices` to find one). Conflicts with --device-category."
|
|
3671
|
+
).option(
|
|
3672
|
+
"--platform <platform>",
|
|
3673
|
+
"Run the test on another platform (ios, android, tvos, androidtv): runs your app there with the same bundle id / package"
|
|
3674
|
+
).option(
|
|
3675
|
+
"--app <app-id>",
|
|
3676
|
+
"Run the test against this app instead of its own (e.g. another platform build with a different bundle id)"
|
|
3619
3677
|
).option("--build <id>", "Specific AppBuild id to run against (defaults to latest)").option("--metadata <json>", "JSON object passed through to webhooks (e.g. PR context)").option("--metadata-file <path>", "Read JSON object from a file (use for nested JSON in CI)").action((testId, opts) => {
|
|
3620
3678
|
if (opts.device && opts.deviceCategory) {
|
|
3621
3679
|
process.stderr.write("error: --device cannot be combined with --device-category\n");
|
|
3622
3680
|
process.exit(2);
|
|
3623
3681
|
}
|
|
3682
|
+
try {
|
|
3683
|
+
parseRunPlatform(opts.platform);
|
|
3684
|
+
} catch (err) {
|
|
3685
|
+
process.stderr.write(`error: ${err.message}
|
|
3686
|
+
`);
|
|
3687
|
+
process.exit(2);
|
|
3688
|
+
}
|
|
3624
3689
|
const metadata = resolveMetadata(opts);
|
|
3625
3690
|
return runTest(testId, {
|
|
3626
3691
|
...opts,
|
|
@@ -3669,7 +3734,10 @@ program.command("dev").description(
|
|
|
3669
3734
|
).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--web-url <url>", "Dashboard URL if different from the API URL (local dev)").option(
|
|
3670
3735
|
"--platform <platform>",
|
|
3671
3736
|
"ios, android, tvos, androidtv, or roku (first run is interactive otherwise)"
|
|
3672
|
-
).option("--app <app_id>", "Uploaded debug build to run (see `vibeview list-apps`)").option(
|
|
3737
|
+
).option("--app <app_id>", "Uploaded debug build to run (see `vibeview list-apps`)").option(
|
|
3738
|
+
"--metro-port <port>",
|
|
3739
|
+
"Local Metro port (default: 8081, or vibeview.json); remembered for you in .vibeview/local.json"
|
|
3740
|
+
).option(
|
|
3673
3741
|
"--device-category <category>",
|
|
3674
3742
|
"phone or tablet (default: phone; TV platforms always use tv)",
|
|
3675
3743
|
parseDeviceCategory
|
|
@@ -3831,7 +3899,7 @@ program.command("dev-reload").description(
|
|
|
3831
3899
|
program.command("mcp").description(
|
|
3832
3900
|
"Run an MCP stdio server exposing the agent verbs (tap, swipe, type, ...) plus dev_start/dev_stop/dev_reload as tools"
|
|
3833
3901
|
).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").action(async (opts) => {
|
|
3834
|
-
const { runMcpServer } = await import("./server-
|
|
3902
|
+
const { runMcpServer } = await import("./server-2JNYWF5L.js");
|
|
3835
3903
|
await runMcpServer({ ...opts, version: pkg.version });
|
|
3836
3904
|
});
|
|
3837
3905
|
program.command("agent-setup").description(
|
|
@@ -11,22 +11,25 @@ import {
|
|
|
11
11
|
checkMetro,
|
|
12
12
|
createRokuPush,
|
|
13
13
|
decodeScreenshotResult,
|
|
14
|
+
effectiveDevConfig,
|
|
14
15
|
filterDeviceModels,
|
|
15
16
|
formatBuildRowsHuman,
|
|
16
17
|
formatDeviceModelsHuman,
|
|
17
18
|
isDevPlatform,
|
|
18
19
|
loadDevConfig,
|
|
20
|
+
loadLocalDevConfig,
|
|
19
21
|
parseBuildId,
|
|
20
22
|
platformWithArticle,
|
|
21
23
|
readDevState,
|
|
22
24
|
renderEnvelope,
|
|
25
|
+
resolveMetro,
|
|
23
26
|
startDevSession
|
|
24
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-NOZGZT3G.js";
|
|
25
28
|
import {
|
|
26
29
|
ApiClient,
|
|
27
30
|
NotFoundError,
|
|
28
31
|
resolveAuth
|
|
29
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-ZZK7YQSP.js";
|
|
30
33
|
|
|
31
34
|
// src/mcp/server.ts
|
|
32
35
|
import { writeFileSync } from "fs";
|
|
@@ -539,7 +542,7 @@ async function handleUploadApp(args, deps) {
|
|
|
539
542
|
}
|
|
540
543
|
const name = typeof args.name === "string" && args.name ? args.name : void 0;
|
|
541
544
|
const upload = deps.uploadApp ?? (async (f, n) => {
|
|
542
|
-
const { uploadAppBuild } = await import("./upload-app-
|
|
545
|
+
const { uploadAppBuild } = await import("./upload-app-2LVG4LMB.js");
|
|
543
546
|
const { result: result2 } = await uploadAppBuild(f, { ...deps.auth, name: n });
|
|
544
547
|
return result2;
|
|
545
548
|
});
|
|
@@ -624,6 +627,7 @@ function createDevSessionController(auth, webUrl) {
|
|
|
624
627
|
} catch {
|
|
625
628
|
devConfig = null;
|
|
626
629
|
}
|
|
630
|
+
devConfig = effectiveDevConfig(devConfig, loadLocalDevConfig(process.cwd()));
|
|
627
631
|
const entry = devConfig?.platforms?.[cfg.platform];
|
|
628
632
|
const appId = cfg.app || entry?.appId;
|
|
629
633
|
if (!appId) {
|
|
@@ -649,7 +653,7 @@ function createDevSessionController(auth, webUrl) {
|
|
|
649
653
|
}
|
|
650
654
|
const standalone = cfg.standalone === true;
|
|
651
655
|
const usesMetro = PLATFORM_NEEDS_METRO[cfg.platform] && !standalone;
|
|
652
|
-
const metroPort = cfg.
|
|
656
|
+
const metroPort = resolveMetro(devConfig, cfg.platform, cfg.metroPort).port;
|
|
653
657
|
if (usesMetro) {
|
|
654
658
|
const metro = await checkMetro(metroPort);
|
|
655
659
|
if (metro === "down") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vibeview",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.0",
|
|
4
4
|
"description": "VibeView CLI -- run your app on cloud iOS simulators, Android emulators, Apple TV, Android TV and Roku (beta) devices: a React Native dev loop, device control for you or your coding agent (CLI or MCP), and tests from CI",
|
|
5
5
|
"mcpName": "io.github.vibeview/vibeview",
|
|
6
6
|
"type": "module",
|
|
@@ -64,11 +64,15 @@ https://vibeview.io/docs/preparing-your-build.
|
|
|
64
64
|
the page is `https://vibeview.io/sandbox/<session_id>`.)
|
|
65
65
|
|
|
66
66
|
Running `dev` writes two things into the project root: a `.vibeview/`
|
|
67
|
-
directory (live session state
|
|
68
|
-
`
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
67
|
+
directory (live session state, plus `local.json` with this developer's own
|
|
68
|
+
flag overrides such as `--metro-port` — local machine state, never commit)
|
|
69
|
+
and a `vibeview.json` (the platform/app/build setup, which teammates DO
|
|
70
|
+
want). Flags never rewrite a value `vibeview.json` already has; they are
|
|
71
|
+
saved to `.vibeview/local.json` instead, and the CLI prints what it saved.
|
|
72
|
+
Make sure the project's `.gitignore` has `.vibeview/*` and
|
|
73
|
+
`!.vibeview/test-spec.json`, adding them if missing. Leave `vibeview.json`
|
|
74
|
+
tracked, but tell the developer you created it rather than letting it
|
|
75
|
+
appear unexplained in their `git status`.
|
|
72
76
|
|
|
73
77
|
## 3. Verifying
|
|
74
78
|
|