vibeview 0.27.1 → 0.28.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-FTTWLCTE.js → chunk-2JLTC67A.js} +115 -11
- package/dist/{chunk-7IOGMM5W.js → chunk-OKOAWGWV.js} +135 -117
- package/dist/cli.js +364 -86
- package/dist/{server-ZIUZ7U5T.js → server-6SRFOQ6B.js} +298 -53
- package/dist/{upload-app-MDKTSG4O.js → upload-app-WROTRZCA.js} +1 -1
- package/package.json +2 -2
- package/skills/vibeview-agent/SKILL.md +10 -5
|
@@ -8,10 +8,12 @@ import {
|
|
|
8
8
|
errorExitCode,
|
|
9
9
|
formatUtcTimestamp,
|
|
10
10
|
loadConfig,
|
|
11
|
+
prepareUploadArtifact,
|
|
11
12
|
queueReasonCopy,
|
|
13
|
+
resolveArtifact,
|
|
12
14
|
resolveAuth,
|
|
13
15
|
uploadAppBuild
|
|
14
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-2JLTC67A.js";
|
|
15
17
|
|
|
16
18
|
// src/lib/dev-config.ts
|
|
17
19
|
import { readFileSync, writeFileSync } from "fs";
|
|
@@ -166,6 +168,30 @@ function validateBuildEnv(env, platformKey) {
|
|
|
166
168
|
function saveDevConfig(cwd, cfg) {
|
|
167
169
|
writeFileSync(join(cwd, FILE), JSON.stringify(cfg, null, 2) + "\n");
|
|
168
170
|
}
|
|
171
|
+
function describeConfigChanges(before, after) {
|
|
172
|
+
const flat = (value, prefix, out) => {
|
|
173
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
174
|
+
for (const [k, v] of Object.entries(value)) {
|
|
175
|
+
flat(v, prefix ? `${prefix}.${k}` : k, out);
|
|
176
|
+
}
|
|
177
|
+
} else if (value !== void 0) {
|
|
178
|
+
out.set(prefix, typeof value === "string" ? value : JSON.stringify(value));
|
|
179
|
+
}
|
|
180
|
+
return out;
|
|
181
|
+
};
|
|
182
|
+
const a = flat(before, "", /* @__PURE__ */ new Map());
|
|
183
|
+
const b = flat(after, "", /* @__PURE__ */ new Map());
|
|
184
|
+
const changes = [];
|
|
185
|
+
for (const [key, next] of b) {
|
|
186
|
+
const prev = a.get(key);
|
|
187
|
+
if (prev === void 0) changes.push(`${key} (added) ${next}`);
|
|
188
|
+
else if (prev !== next) changes.push(`${key} ${prev} \u2192 ${next}`);
|
|
189
|
+
}
|
|
190
|
+
for (const [key, prev] of a) {
|
|
191
|
+
if (!b.has(key)) changes.push(`${key} (removed, was ${prev})`);
|
|
192
|
+
}
|
|
193
|
+
return changes;
|
|
194
|
+
}
|
|
169
195
|
function resolveMetro(cfg, platform, portFlag) {
|
|
170
196
|
const entry = cfg?.platforms?.[platform];
|
|
171
197
|
return {
|
|
@@ -223,7 +249,7 @@ function buildKind(isDebug) {
|
|
|
223
249
|
if (isDebug === false) return "release";
|
|
224
250
|
return "unknown";
|
|
225
251
|
}
|
|
226
|
-
function checkDevBuild(app, buildId, platform) {
|
|
252
|
+
function checkDevBuild(app, buildId, platform, opts = {}) {
|
|
227
253
|
if (platform === "roku") {
|
|
228
254
|
throw new Error(
|
|
229
255
|
"a build id cannot be pinned on roku \u2014 `vibeview dev` packages and sideloads the channel itself"
|
|
@@ -240,7 +266,7 @@ function checkDevBuild(app, buildId, platform) {
|
|
|
240
266
|
`build ${buildId} is not a build of app ${app.public_id} \u2014 see \`vibeview list-builds ${app.public_id}\``
|
|
241
267
|
);
|
|
242
268
|
}
|
|
243
|
-
if (build.is_debug !== true) {
|
|
269
|
+
if (!opts.standalone && build.is_debug !== true) {
|
|
244
270
|
const why = build.is_debug === false ? "it runs standalone (a release build, or one with an embedded JS bundle)" : "it could not be identified as a Metro debug build";
|
|
245
271
|
throw new Error(
|
|
246
272
|
`build ${buildId} is not a debug build \u2014 ${why}. \`vibeview dev\` needs ${platformWithArticle(platform)} debug simulator/emulator build without an embedded JS bundle (see \`vibeview list-builds ${app.public_id}\`)`
|
|
@@ -383,81 +409,24 @@ import { createInterface } from "readline";
|
|
|
383
409
|
import { select as select2 } from "@inquirer/prompts";
|
|
384
410
|
import chalk3 from "chalk";
|
|
385
411
|
|
|
386
|
-
// src/lib/artifact.ts
|
|
387
|
-
import { existsSync as existsSync2, mkdtempSync, readdirSync, rmSync as rmSync2, statSync } from "fs";
|
|
388
|
-
import { spawnSync } from "child_process";
|
|
389
|
-
import { tmpdir } from "os";
|
|
390
|
-
import { basename, dirname as dirname2, isAbsolute, join as join3 } from "path";
|
|
391
|
-
function resolveArtifact(cwd, pattern) {
|
|
392
|
-
const abs = isAbsolute(pattern) ? pattern : join3(cwd, pattern);
|
|
393
|
-
if (!basename(abs).includes("*")) {
|
|
394
|
-
if (!existsSync2(abs)) {
|
|
395
|
-
throw new Error(
|
|
396
|
-
`artifact not found: ${pattern} (looked at ${abs}) \u2014 did the build produce it?`
|
|
397
|
-
);
|
|
398
|
-
}
|
|
399
|
-
return abs;
|
|
400
|
-
}
|
|
401
|
-
const dir = dirname2(abs);
|
|
402
|
-
const re = new RegExp("^" + basename(abs).split("*").map(escapeRegExp).join(".*") + "$");
|
|
403
|
-
let entries;
|
|
404
|
-
try {
|
|
405
|
-
entries = readdirSync(dir);
|
|
406
|
-
} catch {
|
|
407
|
-
throw new Error(`artifact not found: ${pattern} (directory ${dir} does not exist)`);
|
|
408
|
-
}
|
|
409
|
-
const matches = entries.filter((e) => re.test(e)).map((e) => join3(dir, e)).sort((a, b) => statSync(b).mtimeMs - statSync(a).mtimeMs);
|
|
410
|
-
if (matches.length === 0) {
|
|
411
|
-
throw new Error(`artifact not found: no match for ${pattern} in ${dir}`);
|
|
412
|
-
}
|
|
413
|
-
return matches[0];
|
|
414
|
-
}
|
|
415
|
-
function escapeRegExp(s) {
|
|
416
|
-
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
417
|
-
}
|
|
418
|
-
function prepareUploadArtifact(path) {
|
|
419
|
-
if (!(statSync(path).isDirectory() && path.endsWith(".app"))) {
|
|
420
|
-
return { uploadPath: path, cleanup: () => {
|
|
421
|
-
} };
|
|
422
|
-
}
|
|
423
|
-
const tmp = mkdtempSync(join3(tmpdir(), "vibeview-zip-"));
|
|
424
|
-
const out = join3(tmp, `${basename(path)}.zip`);
|
|
425
|
-
const res = spawnSync("zip", ["-qry", out, basename(path)], { cwd: dirname2(path) });
|
|
426
|
-
if (res.error) {
|
|
427
|
-
rmSync2(tmp, { recursive: true, force: true });
|
|
428
|
-
const errorCode = res.error.code;
|
|
429
|
-
const errorMsg = res.error.message;
|
|
430
|
-
throw new Error(
|
|
431
|
-
`failed to zip ${path}: spawn zip ${errorCode} \u2014 ${errorMsg} \u2014 is the \`zip\` command installed?`
|
|
432
|
-
);
|
|
433
|
-
}
|
|
434
|
-
if (res.status !== 0) {
|
|
435
|
-
rmSync2(tmp, { recursive: true, force: true });
|
|
436
|
-
throw new Error(
|
|
437
|
-
`failed to zip ${path}: ${res.stderr?.toString().trim() || `zip exited ${res.status}`}`
|
|
438
|
-
);
|
|
439
|
-
}
|
|
440
|
-
return { uploadPath: out, cleanup: () => rmSync2(tmp, { recursive: true, force: true }) };
|
|
441
|
-
}
|
|
442
|
-
|
|
443
412
|
// src/lib/dev-setup.ts
|
|
444
413
|
import chalk2 from "chalk";
|
|
445
414
|
import { input, select } from "@inquirer/prompts";
|
|
446
415
|
|
|
447
416
|
// src/lib/project-detect.ts
|
|
448
|
-
import { existsSync as
|
|
449
|
-
import { join as
|
|
417
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3, readdirSync, statSync } from "fs";
|
|
418
|
+
import { join as join3 } from "path";
|
|
450
419
|
import ignore from "ignore";
|
|
451
420
|
function readJson(cwd, name) {
|
|
452
421
|
try {
|
|
453
|
-
return JSON.parse(readFileSync3(
|
|
422
|
+
return JSON.parse(readFileSync3(join3(cwd, name), "utf-8"));
|
|
454
423
|
} catch {
|
|
455
424
|
return null;
|
|
456
425
|
}
|
|
457
426
|
}
|
|
458
427
|
function isFile(path) {
|
|
459
428
|
try {
|
|
460
|
-
return
|
|
429
|
+
return statSync(path).isFile();
|
|
461
430
|
} catch {
|
|
462
431
|
return false;
|
|
463
432
|
}
|
|
@@ -465,19 +434,19 @@ function isFile(path) {
|
|
|
465
434
|
function detectFlavor(cwd) {
|
|
466
435
|
let deps = {};
|
|
467
436
|
try {
|
|
468
|
-
const pkg = JSON.parse(readFileSync3(
|
|
437
|
+
const pkg = JSON.parse(readFileSync3(join3(cwd, "package.json"), "utf-8"));
|
|
469
438
|
deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
470
439
|
} catch {
|
|
471
440
|
}
|
|
472
441
|
if ("expo" in deps) return "expo";
|
|
473
|
-
if (
|
|
474
|
-
if (isFile(
|
|
442
|
+
if (existsSync2(join3(cwd, "android", "gradlew")) || iosWorkspaceName(cwd)) return "bare-rn";
|
|
443
|
+
if (isFile(join3(cwd, "manifest"))) return "roku";
|
|
475
444
|
return "unknown";
|
|
476
445
|
}
|
|
477
446
|
function nativeDirShipped(cwd, dir) {
|
|
478
|
-
if (!
|
|
447
|
+
if (!existsSync2(join3(cwd, dir))) return false;
|
|
479
448
|
try {
|
|
480
|
-
const ig = ignore().add(readFileSync3(
|
|
449
|
+
const ig = ignore().add(readFileSync3(join3(cwd, ".gitignore"), "utf-8"));
|
|
481
450
|
return !ig.ignores(`${dir}/`);
|
|
482
451
|
} catch {
|
|
483
452
|
return true;
|
|
@@ -493,7 +462,7 @@ function isManagedExpo(cwd, platform) {
|
|
|
493
462
|
}
|
|
494
463
|
function iosWorkspaceName(cwd) {
|
|
495
464
|
try {
|
|
496
|
-
const found =
|
|
465
|
+
const found = readdirSync(join3(cwd, "ios")).find((f) => f.endsWith(".xcworkspace"));
|
|
497
466
|
return found ? found.replace(/\.xcworkspace$/, "") : null;
|
|
498
467
|
} catch {
|
|
499
468
|
return null;
|
|
@@ -502,9 +471,9 @@ function iosWorkspaceName(cwd) {
|
|
|
502
471
|
var IOS_ARTIFACT = "ios/build/Build/Products/Debug-iphonesimulator/*.app";
|
|
503
472
|
var ANDROID_ARTIFACT = "android/app/build/outputs/apk/debug/*.apk";
|
|
504
473
|
function installCommand(cwd) {
|
|
505
|
-
if (
|
|
506
|
-
if (
|
|
507
|
-
if (
|
|
474
|
+
if (existsSync2(join3(cwd, "yarn.lock"))) return "yarn install --immutable";
|
|
475
|
+
if (existsSync2(join3(cwd, "pnpm-lock.yaml"))) return "pnpm install --frozen-lockfile";
|
|
476
|
+
if (existsSync2(join3(cwd, "package-lock.json"))) return "npm ci";
|
|
508
477
|
return "yarn install --immutable";
|
|
509
478
|
}
|
|
510
479
|
function managedExpoAndroidCommand(cwd) {
|
|
@@ -518,7 +487,7 @@ var ROKU_ZIP_COMMAND = "zip -r out/channel.zip manifest source components images
|
|
|
518
487
|
function rokuDefaults(cwd) {
|
|
519
488
|
const pkg = readJson(cwd, "package.json");
|
|
520
489
|
const scripts = pkg?.scripts ?? {};
|
|
521
|
-
const runner =
|
|
490
|
+
const runner = existsSync2(join3(cwd, "yarn.lock")) ? "yarn" : "npm run";
|
|
522
491
|
const packageScript = Object.entries(scripts).find(
|
|
523
492
|
([name, body]) => !name.startsWith("deploy") && (name.startsWith("package") || body.includes("--package-only") || body.includes("zipPackage"))
|
|
524
493
|
);
|
|
@@ -576,7 +545,7 @@ function expoSanitizedName(name) {
|
|
|
576
545
|
return name.replace(/[\W_]+/g, "").normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
577
546
|
}
|
|
578
547
|
function managedExpoIosName(cwd) {
|
|
579
|
-
if (
|
|
548
|
+
if (existsSync2(join3(cwd, "app.config.js")) || existsSync2(join3(cwd, "app.config.ts"))) {
|
|
580
549
|
return null;
|
|
581
550
|
}
|
|
582
551
|
const raw = readJson(cwd, "app.config.json") ?? readJson(cwd, "app.json");
|
|
@@ -721,9 +690,9 @@ async function promptMetroCommand(cwd, platform) {
|
|
|
721
690
|
|
|
722
691
|
// src/lib/source-pack.ts
|
|
723
692
|
import { execFile, spawn } from "child_process";
|
|
724
|
-
import { lstatSync, mkdtempSync
|
|
725
|
-
import { tmpdir
|
|
726
|
-
import { join as
|
|
693
|
+
import { lstatSync, mkdtempSync, readFileSync as readFileSync4, rmSync as rmSync2, statSync as statSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
694
|
+
import { tmpdir } from "os";
|
|
695
|
+
import { join as join4 } from "path";
|
|
727
696
|
import ignore2 from "ignore";
|
|
728
697
|
var NotAGitRepoError = class extends Error {
|
|
729
698
|
constructor(message) {
|
|
@@ -749,7 +718,7 @@ async function listSourceFiles(projectRoot) {
|
|
|
749
718
|
const stdout = await runGitLsFiles(projectRoot);
|
|
750
719
|
const rawPaths = stdout.length ? stdout.split("\0").filter((p) => p.length > 0) : [];
|
|
751
720
|
let filtered = rawPaths;
|
|
752
|
-
const vvignorePath =
|
|
721
|
+
const vvignorePath = join4(projectRoot, ".vvignore");
|
|
753
722
|
if (statOrNull(vvignorePath)?.isFile()) {
|
|
754
723
|
const ig = ignore2().add(readFileSync4(vvignorePath, "utf-8"));
|
|
755
724
|
filtered = filtered.filter((p) => !ig.ignores(p));
|
|
@@ -757,7 +726,7 @@ async function listSourceFiles(projectRoot) {
|
|
|
757
726
|
const result = [];
|
|
758
727
|
for (const relPath of filtered) {
|
|
759
728
|
if (isForceExcluded(relPath)) continue;
|
|
760
|
-
const st = statOrNull(
|
|
729
|
+
const st = statOrNull(join4(projectRoot, relPath));
|
|
761
730
|
if (!st) continue;
|
|
762
731
|
result.push(relPath);
|
|
763
732
|
}
|
|
@@ -807,7 +776,7 @@ async function packSource(projectRoot, files, opts = {}) {
|
|
|
807
776
|
let totalBytes = 0;
|
|
808
777
|
const sized = [];
|
|
809
778
|
for (const relPath of files) {
|
|
810
|
-
const bytes = lstatSync(
|
|
779
|
+
const bytes = lstatSync(join4(projectRoot, relPath)).size;
|
|
811
780
|
totalBytes += bytes;
|
|
812
781
|
sized.push({ path: relPath, bytes });
|
|
813
782
|
}
|
|
@@ -818,13 +787,13 @@ async function packSource(projectRoot, files, opts = {}) {
|
|
|
818
787
|
largest
|
|
819
788
|
);
|
|
820
789
|
}
|
|
821
|
-
const tmpDir =
|
|
822
|
-
const cleanup = () =>
|
|
790
|
+
const tmpDir = mkdtempSync(join4(tmpdir(), "vibeview-source-pack-"));
|
|
791
|
+
const cleanup = () => rmSync2(tmpDir, { recursive: true, force: true });
|
|
823
792
|
try {
|
|
824
793
|
const supportsNull = await probeTarNullSupport();
|
|
825
|
-
const tarPath =
|
|
794
|
+
const tarPath = join4(tmpDir, "source.tgz");
|
|
826
795
|
if (supportsNull) {
|
|
827
|
-
const listPath =
|
|
796
|
+
const listPath = join4(tmpDir, "files.list");
|
|
828
797
|
writeFileSync3(listPath, files.map((f) => `${f}\0`).join(""));
|
|
829
798
|
await runTar(["-czf", tarPath, "-C", projectRoot, "--null", "-T", listPath]);
|
|
830
799
|
} else {
|
|
@@ -834,12 +803,12 @@ async function packSource(projectRoot, files, opts = {}) {
|
|
|
834
803
|
`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`
|
|
835
804
|
);
|
|
836
805
|
}
|
|
837
|
-
const listPath =
|
|
806
|
+
const listPath = join4(tmpDir, "files.list");
|
|
838
807
|
writeFileSync3(listPath, files.map((f) => `${f}
|
|
839
808
|
`).join(""));
|
|
840
809
|
await runTar(["-czf", tarPath, "-C", projectRoot, "-T", listPath]);
|
|
841
810
|
}
|
|
842
|
-
const sizeBytes =
|
|
811
|
+
const sizeBytes = statSync2(tarPath).size;
|
|
843
812
|
return { tarPath, sizeBytes, cleanup };
|
|
844
813
|
} catch (err) {
|
|
845
814
|
cleanup();
|
|
@@ -855,12 +824,12 @@ var nullSupportCache = null;
|
|
|
855
824
|
function probeTarNullSupport() {
|
|
856
825
|
if (!nullSupportCache) {
|
|
857
826
|
nullSupportCache = new Promise((resolve) => {
|
|
858
|
-
const tmp =
|
|
859
|
-
const listPath =
|
|
827
|
+
const tmp = mkdtempSync(join4(tmpdir(), "vibeview-tar-probe-"));
|
|
828
|
+
const listPath = join4(tmp, "empty.list");
|
|
860
829
|
writeFileSync3(listPath, "");
|
|
861
|
-
const out =
|
|
830
|
+
const out = join4(tmp, "probe.tgz");
|
|
862
831
|
execFile("tar", ["-czf", out, "-C", tmp, "--null", "-T", listPath], (err) => {
|
|
863
|
-
|
|
832
|
+
rmSync2(tmp, { recursive: true, force: true });
|
|
864
833
|
resolve(!err);
|
|
865
834
|
});
|
|
866
835
|
});
|
|
@@ -1543,7 +1512,9 @@ async function waitForCloudBuilds(builds, opts, deps) {
|
|
|
1543
1512
|
id: final.id,
|
|
1544
1513
|
status: final.status,
|
|
1545
1514
|
cost_micro: final.cost_micro,
|
|
1546
|
-
error_summary: final.error_summary
|
|
1515
|
+
error_summary: final.error_summary,
|
|
1516
|
+
app_build_id: final.app_build_id ?? null,
|
|
1517
|
+
app_public_id: final.app_public_id ?? null
|
|
1547
1518
|
});
|
|
1548
1519
|
finalized.add(build.id);
|
|
1549
1520
|
}
|
|
@@ -1731,7 +1702,7 @@ your build command must produce a zip \u2014 use the package/zip step, not deplo
|
|
|
1731
1702
|
// src/commands/dev.ts
|
|
1732
1703
|
import { spawn as spawn4 } from "child_process";
|
|
1733
1704
|
import { createWriteStream, mkdirSync as mkdirSync3 } from "fs";
|
|
1734
|
-
import { dirname as
|
|
1705
|
+
import { dirname as dirname4 } from "path";
|
|
1735
1706
|
import { createInterface as createInterface3 } from "readline";
|
|
1736
1707
|
import { select as select3 } from "@inquirer/prompts";
|
|
1737
1708
|
import chalk4 from "chalk";
|
|
@@ -1926,15 +1897,15 @@ import { spawn as spawn3 } from "child_process";
|
|
|
1926
1897
|
import { createInterface as createInterface2 } from "readline";
|
|
1927
1898
|
|
|
1928
1899
|
// src/lib/metro-state.ts
|
|
1929
|
-
import { existsSync as
|
|
1930
|
-
import { dirname as
|
|
1900
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync5, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
1901
|
+
import { dirname as dirname2, join as join5 } from "path";
|
|
1931
1902
|
var STATE_DIR2 = ".vibeview";
|
|
1932
1903
|
var STATE_FILE2 = "metro-state.json";
|
|
1933
1904
|
function statePath2(cwd) {
|
|
1934
|
-
return
|
|
1905
|
+
return join5(cwd, STATE_DIR2, STATE_FILE2);
|
|
1935
1906
|
}
|
|
1936
1907
|
function metroLogPath(cwd) {
|
|
1937
|
-
return
|
|
1908
|
+
return join5(cwd, STATE_DIR2, "metro.log");
|
|
1938
1909
|
}
|
|
1939
1910
|
function isPidAlive2(pid) {
|
|
1940
1911
|
try {
|
|
@@ -1946,7 +1917,7 @@ function isPidAlive2(pid) {
|
|
|
1946
1917
|
}
|
|
1947
1918
|
function writeMetroState(cwd, state) {
|
|
1948
1919
|
const path = statePath2(cwd);
|
|
1949
|
-
mkdirSync2(
|
|
1920
|
+
mkdirSync2(dirname2(path), { recursive: true });
|
|
1950
1921
|
writeFileSync4(path, JSON.stringify(state, null, 2) + "\n");
|
|
1951
1922
|
}
|
|
1952
1923
|
function readMetroState(cwd) {
|
|
@@ -1964,7 +1935,7 @@ function readMetroState(cwd) {
|
|
|
1964
1935
|
}
|
|
1965
1936
|
function clearMetroState(cwd) {
|
|
1966
1937
|
const path = statePath2(cwd);
|
|
1967
|
-
if (
|
|
1938
|
+
if (existsSync3(path)) rmSync3(path, { force: true });
|
|
1968
1939
|
}
|
|
1969
1940
|
function killOwnedMetro(cwd) {
|
|
1970
1941
|
const state = readMetroState(cwd);
|
|
@@ -2072,7 +2043,7 @@ async function waitFor(cond, timeoutMs, everyMs) {
|
|
|
2072
2043
|
|
|
2073
2044
|
// src/lib/reload-loop.ts
|
|
2074
2045
|
import { watch } from "fs";
|
|
2075
|
-
import { basename
|
|
2046
|
+
import { basename, dirname as dirname3 } from "path";
|
|
2076
2047
|
function createReloadLoop(deps) {
|
|
2077
2048
|
let inFlight = null;
|
|
2078
2049
|
let pendingFollowUp = false;
|
|
@@ -2153,16 +2124,16 @@ function isIgnoredWatchPath(relPath, artifact, userIgnore = []) {
|
|
|
2153
2124
|
if (IGNORED_FILES.includes(rel)) return true;
|
|
2154
2125
|
if (userIgnore.some((entry) => isUnderPath(rel, entry))) return true;
|
|
2155
2126
|
const artifactPattern = artifact.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
2156
|
-
const artifactDir =
|
|
2127
|
+
const artifactDir = dirname3(artifactPattern);
|
|
2157
2128
|
if (artifactDir !== "." && (rel === artifactDir || rel.startsWith(`${artifactDir}/`))) {
|
|
2158
2129
|
return true;
|
|
2159
2130
|
}
|
|
2160
|
-
if (
|
|
2161
|
-
const name =
|
|
2162
|
-
const re = new RegExp(`^${name.split("*").map(
|
|
2163
|
-
return re.test(
|
|
2131
|
+
if (dirname3(rel) !== artifactDir) return false;
|
|
2132
|
+
const name = basename(artifactPattern);
|
|
2133
|
+
const re = new RegExp(`^${name.split("*").map(escapeRegExp).join(".*")}$`);
|
|
2134
|
+
return re.test(basename(rel));
|
|
2164
2135
|
}
|
|
2165
|
-
function
|
|
2136
|
+
function escapeRegExp(s) {
|
|
2166
2137
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2167
2138
|
}
|
|
2168
2139
|
function watchProjectFiles(cwd, artifact, userIgnore = []) {
|
|
@@ -2198,7 +2169,7 @@ function emit(json, evt) {
|
|
|
2198
2169
|
async function startDevSession(cfg, onEvent = () => {
|
|
2199
2170
|
}, onTunnelState) {
|
|
2200
2171
|
const api = new ApiClient(cfg.baseUrl, cfg.token);
|
|
2201
|
-
const usesTunnel = PLATFORM_NEEDS_METRO[cfg.platform];
|
|
2172
|
+
const usesTunnel = PLATFORM_NEEDS_METRO[cfg.platform] && !cfg.standalone;
|
|
2202
2173
|
const pool = cfg.model ? resolveDeviceModel(await api.listDeviceModels(), cfg.platform, cfg.model) : null;
|
|
2203
2174
|
const created = await api.createSession(
|
|
2204
2175
|
devSessionCreateBody(pool ? { ...cfg, deviceCategory: pool.category, model: pool.model } : cfg)
|
|
@@ -2286,7 +2257,7 @@ async function startDevSession(cfg, onEvent = () => {
|
|
|
2286
2257
|
reportedWarnings.add(w);
|
|
2287
2258
|
onEvent({ event: "warning", message: w });
|
|
2288
2259
|
}
|
|
2289
|
-
return { sessionId, pageUrl, stop: () => stopSession(0), reportedWarnings };
|
|
2260
|
+
return { sessionId, pageUrl, stop: () => stopSession(0), reportedWarnings, session: active };
|
|
2290
2261
|
} catch (err) {
|
|
2291
2262
|
await stopSession(1);
|
|
2292
2263
|
throw err;
|
|
@@ -2297,8 +2268,9 @@ function devSessionCreateBody(cfg) {
|
|
|
2297
2268
|
device_type: PLATFORM_DEVICE[cfg.platform].deviceType,
|
|
2298
2269
|
device_category: cfg.deviceCategory,
|
|
2299
2270
|
app_id: cfg.appId,
|
|
2300
|
-
// Roku has no JS bundle to serve, so there is nothing to tunnel
|
|
2301
|
-
|
|
2271
|
+
// Roku has no JS bundle to serve, so there is nothing to tunnel; a
|
|
2272
|
+
// standalone session runs the build as it is, without Metro.
|
|
2273
|
+
tunnel: PLATFORM_NEEDS_METRO[cfg.platform] && !cfg.standalone,
|
|
2302
2274
|
...cfg.model ? { model: cfg.model } : {},
|
|
2303
2275
|
...cfg.buildId ? { build_id: cfg.buildId } : {}
|
|
2304
2276
|
};
|
|
@@ -2312,10 +2284,36 @@ function resolveDeviceModel(pools, platform, model) {
|
|
|
2312
2284
|
const match = candidates.find((p) => p.model === wanted) ?? candidates.find((p) => p.model.toLowerCase() === wanted.toLowerCase());
|
|
2313
2285
|
if (match) return match;
|
|
2314
2286
|
const names = [...new Set(candidates.map((p) => p.model))].sort();
|
|
2287
|
+
const guess = closestModelName(wanted, names);
|
|
2315
2288
|
throw new Error(
|
|
2316
|
-
names.length ? `no ${platform} device model named "${model}" \u2014 available: ${names.join(", ")} (see \`vibeview list-devices --models\`)` : `no ${platform} devices are available right now`
|
|
2289
|
+
names.length ? `no ${platform} device model named "${model}"` + (guess ? ` (did you mean "${guess}"?)` : "") + ` \u2014 available: ${names.join(", ")} (see \`vibeview list-devices --models\`)` : `no ${platform} devices are available right now`
|
|
2317
2290
|
);
|
|
2318
2291
|
}
|
|
2292
|
+
function closestModelName(wanted, names) {
|
|
2293
|
+
const squash = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
2294
|
+
const words = (s) => new Set(s.toLowerCase().match(/[a-z0-9]+/g) ?? []);
|
|
2295
|
+
const w = squash(wanted);
|
|
2296
|
+
if (!w) return null;
|
|
2297
|
+
const unique = (list) => list.length === 1 ? list[0] : null;
|
|
2298
|
+
const same = names.filter((n) => squash(n) === w);
|
|
2299
|
+
if (same.length) return unique(same);
|
|
2300
|
+
const containing = names.filter((n) => squash(n).includes(w) || w.includes(squash(n)));
|
|
2301
|
+
if (containing.length) return unique(containing);
|
|
2302
|
+
const wantedWords = words(wanted);
|
|
2303
|
+
let best = 0;
|
|
2304
|
+
let bestNames = [];
|
|
2305
|
+
for (const n of names) {
|
|
2306
|
+
let shared = 0;
|
|
2307
|
+
for (const word of words(n)) if (wantedWords.has(word)) shared++;
|
|
2308
|
+
if (shared > best) {
|
|
2309
|
+
best = shared;
|
|
2310
|
+
bestNames = [n];
|
|
2311
|
+
} else if (shared === best && shared > 0) {
|
|
2312
|
+
bestNames.push(n);
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2315
|
+
return best >= 2 ? unique(bestNames) : null;
|
|
2316
|
+
}
|
|
2319
2317
|
var BLOCKING_WARNING_PREFIXES = ["No debug build", "App install failed", "App not found"];
|
|
2320
2318
|
function findBlockingWarning(info) {
|
|
2321
2319
|
return (info.warnings ?? []).find((w) => BLOCKING_WARNING_PREFIXES.some((p) => w.includes(p)));
|
|
@@ -2607,6 +2605,12 @@ ${hint}`);
|
|
|
2607
2605
|
});
|
|
2608
2606
|
const commitConfig = () => {
|
|
2609
2607
|
saveDevConfig(process.cwd(), cfg);
|
|
2608
|
+
if (existing) {
|
|
2609
|
+
const changes = describeConfigChanges(existing, cfg);
|
|
2610
|
+
if (changes.length) {
|
|
2611
|
+
console.error(chalk4.dim(`Updated vibeview.json: ${changes.join(", ")}`));
|
|
2612
|
+
}
|
|
2613
|
+
}
|
|
2610
2614
|
if (isFirstRun) {
|
|
2611
2615
|
console.error(chalk4.dim(`Created vibeview.json (platform: ${platform}, app: ${appId}).`));
|
|
2612
2616
|
const hint = fillLaterHint(
|
|
@@ -2726,7 +2730,7 @@ async function runDevInProcess(opts) {
|
|
|
2726
2730
|
const captureMetroLine = (line) => {
|
|
2727
2731
|
try {
|
|
2728
2732
|
if (!metroLogStream) {
|
|
2729
|
-
mkdirSync3(
|
|
2733
|
+
mkdirSync3(dirname4(metroLogPath(process.cwd())), { recursive: true });
|
|
2730
2734
|
metroLogStream = createWriteStream(metroLogPath(process.cwd()), { flags: "w" });
|
|
2731
2735
|
}
|
|
2732
2736
|
metroLogStream.write(line.replace(/^\[metro\] /, "") + "\n");
|
|
@@ -3078,20 +3082,34 @@ function filterDeviceModels(models, opts) {
|
|
|
3078
3082
|
os_version: m.os_version,
|
|
3079
3083
|
device_type: m.device_type,
|
|
3080
3084
|
category: m.category,
|
|
3081
|
-
form_factor: m.form_factor ?? null
|
|
3085
|
+
form_factor: m.form_factor ?? null,
|
|
3086
|
+
// MCP only: an agent has no other way to see whether a model is free.
|
|
3087
|
+
...opts.availability && typeof m.free === "number" && typeof m.total === "number" ? { free: m.free, total: m.total } : {}
|
|
3082
3088
|
}));
|
|
3083
3089
|
}
|
|
3084
3090
|
var MODEL_HEADERS = ["MODEL", "OS", "TYPE", "CATEGORY", "FORM FACTOR"];
|
|
3085
|
-
function formatDeviceModelsHuman(models) {
|
|
3091
|
+
function formatDeviceModelsHuman(models, opts = {}) {
|
|
3086
3092
|
if (models.length === 0) {
|
|
3087
3093
|
return "No device models available right now.\n";
|
|
3088
3094
|
}
|
|
3089
|
-
const
|
|
3095
|
+
const headers = [...MODEL_HEADERS, ...opts.availability ? ["FREE", "BUSY"] : []];
|
|
3096
|
+
const rows = models.map((m) => [
|
|
3097
|
+
m.model,
|
|
3098
|
+
m.os_version,
|
|
3099
|
+
m.device_type,
|
|
3100
|
+
m.category,
|
|
3101
|
+
m.form_factor ?? "-",
|
|
3102
|
+
...opts.availability ? availabilityCells(m) : []
|
|
3103
|
+
]).sort(
|
|
3090
3104
|
(a, b) => a[2].localeCompare(b[2]) || a[3].localeCompare(b[3]) || a[0].localeCompare(b[0]) || a[1].localeCompare(b[1])
|
|
3091
3105
|
);
|
|
3092
|
-
const widths =
|
|
3106
|
+
const widths = headers.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
3093
3107
|
const fmt = (cells) => cells.map((c, i) => c.padEnd(widths[i])).join(" ").trimEnd();
|
|
3094
|
-
return [chalk5.bold(fmt(
|
|
3108
|
+
return [chalk5.bold(fmt(headers)), ...rows.map(fmt)].join("\n") + "\n";
|
|
3109
|
+
}
|
|
3110
|
+
function availabilityCells(m) {
|
|
3111
|
+
if (typeof m.free !== "number" || typeof m.total !== "number") return ["?", "?"];
|
|
3112
|
+
return [String(m.free), String(Math.max(0, m.total - m.free))];
|
|
3095
3113
|
}
|
|
3096
3114
|
|
|
3097
3115
|
// src/agent/registry.ts
|