vibeview 0.24.0 → 0.26.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/README.md +2 -0
- package/dist/{chunk-MVNMGB6I.js → chunk-JK3T3AMW.js} +327 -184
- package/dist/{chunk-2WNJYNDW.js → chunk-RFFCCPGZ.js} +5 -0
- package/dist/cli.js +178 -126
- package/dist/{server-KIJCEN6T.js → server-KYDTXPRZ.js} +92 -8
- package/dist/{upload-app-BXXKH65S.js → upload-app-E32EZXYQ.js} +1 -1
- package/package.json +1 -1
- package/skills/vibeview-agent/SKILL.md +40 -14
|
@@ -6,73 +6,11 @@ import {
|
|
|
6
6
|
loadConfig,
|
|
7
7
|
resolveAuth,
|
|
8
8
|
uploadAppBuild
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
|
|
11
|
-
// src/lib/dev-state.ts
|
|
12
|
-
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
13
|
-
import { dirname, join } from "path";
|
|
14
|
-
var STATE_DIR = ".vibeview";
|
|
15
|
-
var STATE_FILE = "dev-state.json";
|
|
16
|
-
function statePath(cwd) {
|
|
17
|
-
return join(cwd, STATE_DIR, STATE_FILE);
|
|
18
|
-
}
|
|
19
|
-
function isPidAlive(pid) {
|
|
20
|
-
try {
|
|
21
|
-
process.kill(pid, 0);
|
|
22
|
-
return true;
|
|
23
|
-
} catch (err) {
|
|
24
|
-
return err.code === "EPERM";
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
function ensureGitignored(cwd) {
|
|
28
|
-
try {
|
|
29
|
-
const gitignore = join(cwd, ".gitignore");
|
|
30
|
-
if (!existsSync(gitignore)) return;
|
|
31
|
-
const content = readFileSync(gitignore, "utf-8");
|
|
32
|
-
const covered = content.split("\n").map((l) => l.trim()).some((l) => l === ".vibeview" || l === ".vibeview/" || l === "/.vibeview");
|
|
33
|
-
if (covered) return;
|
|
34
|
-
const prefix = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
|
|
35
|
-
writeFileSync(gitignore, `${content}${prefix}
|
|
36
|
-
# VibeView local session state
|
|
37
|
-
.vibeview/
|
|
38
|
-
`);
|
|
39
|
-
} catch {
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
function writeDevState(cwd, state) {
|
|
43
|
-
const path = statePath(cwd);
|
|
44
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
45
|
-
writeFileSync(path, JSON.stringify(state, null, 2) + "\n");
|
|
46
|
-
ensureGitignored(cwd);
|
|
47
|
-
}
|
|
48
|
-
function readDevState(cwd) {
|
|
49
|
-
let raw;
|
|
50
|
-
try {
|
|
51
|
-
raw = readFileSync(statePath(cwd), "utf-8");
|
|
52
|
-
} catch {
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
|
-
let parsed;
|
|
56
|
-
try {
|
|
57
|
-
parsed = JSON.parse(raw);
|
|
58
|
-
} catch {
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
if (typeof parsed.pid !== "number" || !isPidAlive(parsed.pid)) {
|
|
62
|
-
return null;
|
|
63
|
-
}
|
|
64
|
-
return parsed;
|
|
65
|
-
}
|
|
66
|
-
function clearDevState(cwd) {
|
|
67
|
-
const path = statePath(cwd);
|
|
68
|
-
if (existsSync(path)) {
|
|
69
|
-
rmSync(path, { force: true });
|
|
70
|
-
}
|
|
71
|
-
}
|
|
9
|
+
} from "./chunk-RFFCCPGZ.js";
|
|
72
10
|
|
|
73
11
|
// src/lib/dev-config.ts
|
|
74
|
-
import { readFileSync
|
|
75
|
-
import { join
|
|
12
|
+
import { readFileSync, writeFileSync } from "fs";
|
|
13
|
+
import { join } from "path";
|
|
76
14
|
var DEV_PLATFORMS = ["ios", "android", "tvos", "androidtv", "roku"];
|
|
77
15
|
var ENV_KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
78
16
|
var PLATFORM_NEEDS_METRO = {
|
|
@@ -86,10 +24,25 @@ var FILE = "vibeview.json";
|
|
|
86
24
|
function isDevPlatform(p) {
|
|
87
25
|
return typeof p === "string" && DEV_PLATFORMS.includes(p);
|
|
88
26
|
}
|
|
27
|
+
var PLATFORM_LABELS = {
|
|
28
|
+
ios: "iOS",
|
|
29
|
+
android: "Android",
|
|
30
|
+
tvos: "tvOS",
|
|
31
|
+
androidtv: "Android TV",
|
|
32
|
+
roku: "Roku"
|
|
33
|
+
};
|
|
34
|
+
function platformLabel(platform) {
|
|
35
|
+
return isDevPlatform(platform) ? PLATFORM_LABELS[platform] : platform;
|
|
36
|
+
}
|
|
37
|
+
function platformWithArticle(platform) {
|
|
38
|
+
const label = platformLabel(platform);
|
|
39
|
+
const article = /^[aeiou]/i.test(label) ? "an" : "a";
|
|
40
|
+
return `${article} ${label}`;
|
|
41
|
+
}
|
|
89
42
|
function loadDevConfig(cwd) {
|
|
90
43
|
let raw;
|
|
91
44
|
try {
|
|
92
|
-
raw =
|
|
45
|
+
raw = readFileSync(join(cwd, FILE), "utf-8");
|
|
93
46
|
} catch {
|
|
94
47
|
return null;
|
|
95
48
|
}
|
|
@@ -206,7 +159,7 @@ function validateBuildEnv(env, platformKey) {
|
|
|
206
159
|
}
|
|
207
160
|
}
|
|
208
161
|
function saveDevConfig(cwd, cfg) {
|
|
209
|
-
|
|
162
|
+
writeFileSync(join(cwd, FILE), JSON.stringify(cfg, null, 2) + "\n");
|
|
210
163
|
}
|
|
211
164
|
function resolveMetro(cfg, platform, portFlag) {
|
|
212
165
|
const entry = cfg?.platforms?.[platform];
|
|
@@ -250,6 +203,162 @@ function buildSavedConfig(input2) {
|
|
|
250
203
|
};
|
|
251
204
|
}
|
|
252
205
|
|
|
206
|
+
// src/lib/dev-build.ts
|
|
207
|
+
function parseBuildId(raw) {
|
|
208
|
+
const text = typeof raw === "number" ? String(raw) : typeof raw === "string" ? raw.trim() : "";
|
|
209
|
+
if (!/^\d+$/.test(text) || Number(text) < 1) {
|
|
210
|
+
throw new Error(
|
|
211
|
+
`--build expects a build id (a positive number, see \`vibeview list-builds <app>\`), got "${String(raw)}"`
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
return Number(text);
|
|
215
|
+
}
|
|
216
|
+
function buildKind(isDebug) {
|
|
217
|
+
if (isDebug === true) return "debug";
|
|
218
|
+
if (isDebug === false) return "release";
|
|
219
|
+
return "unknown";
|
|
220
|
+
}
|
|
221
|
+
function checkDevBuild(app, buildId, platform) {
|
|
222
|
+
if (platform === "roku") {
|
|
223
|
+
throw new Error(
|
|
224
|
+
"a build id cannot be pinned on roku \u2014 `vibeview dev` packages and sideloads the channel itself"
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
if (app.platform !== platform) {
|
|
228
|
+
throw new Error(
|
|
229
|
+
`app ${app.public_id} is ${platformWithArticle(app.platform)} app, not ${platformLabel(platform)}`
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
const build = app.builds.find((b) => b.id === buildId);
|
|
233
|
+
if (!build) {
|
|
234
|
+
throw new Error(
|
|
235
|
+
`build ${buildId} is not a build of app ${app.public_id} \u2014 see \`vibeview list-builds ${app.public_id}\``
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
if (build.is_debug !== true) {
|
|
239
|
+
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";
|
|
240
|
+
throw new Error(
|
|
241
|
+
`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}\`)`
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
return build;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// src/commands/list-builds.ts
|
|
248
|
+
import chalk from "chalk";
|
|
249
|
+
function buildRows(app) {
|
|
250
|
+
return app.builds.map((b) => ({
|
|
251
|
+
id: b.id,
|
|
252
|
+
version: b.version ?? null,
|
|
253
|
+
build_number: b.build_number ?? null,
|
|
254
|
+
platform: app.platform,
|
|
255
|
+
kind: buildKind(b.is_debug),
|
|
256
|
+
is_debug: b.is_debug ?? null,
|
|
257
|
+
uploaded_at: b.uploaded_at ?? null,
|
|
258
|
+
note: b.note ?? null
|
|
259
|
+
}));
|
|
260
|
+
}
|
|
261
|
+
var HEADERS = ["ID", "VERSION", "PLATFORM", "KIND", "UPLOADED", "NOTE"];
|
|
262
|
+
function formatBuildRowsHuman(app, rows) {
|
|
263
|
+
if (rows.length === 0) {
|
|
264
|
+
return `No builds uploaded for ${app.name} (${app.public_id}) \u2014 upload one with \`vibeview upload-app\`.
|
|
265
|
+
`;
|
|
266
|
+
}
|
|
267
|
+
const cells = rows.map((r) => [
|
|
268
|
+
String(r.id),
|
|
269
|
+
r.version ? `${r.version}${r.build_number ? ` (${r.build_number})` : ""}` : r.build_number ?? "",
|
|
270
|
+
r.platform,
|
|
271
|
+
r.kind,
|
|
272
|
+
r.uploaded_at ?? "",
|
|
273
|
+
(r.note ?? "").split("\n")[0]
|
|
274
|
+
]);
|
|
275
|
+
const widths = HEADERS.map((h, i) => Math.max(h.length, ...cells.map((c) => c[i].length)));
|
|
276
|
+
const pad = (row) => row.map((c, i) => c.padEnd(widths[i])).join(" ").trimEnd();
|
|
277
|
+
const lines = [chalk.bold(pad(HEADERS)), ...cells.map(pad)];
|
|
278
|
+
return lines.join("\n") + "\n";
|
|
279
|
+
}
|
|
280
|
+
async function listBuilds(appId, opts, deps) {
|
|
281
|
+
const { token, baseUrl } = resolveAuth(opts);
|
|
282
|
+
const d = deps ?? { getApp: (id) => new ApiClient(baseUrl, token).getApp(id) };
|
|
283
|
+
try {
|
|
284
|
+
const app = await d.getApp(appId);
|
|
285
|
+
const rows = buildRows(app);
|
|
286
|
+
if (opts.json) {
|
|
287
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
288
|
+
} else {
|
|
289
|
+
process.stdout.write(formatBuildRowsHuman(app, rows));
|
|
290
|
+
}
|
|
291
|
+
process.exitCode = 0;
|
|
292
|
+
} catch (err) {
|
|
293
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
294
|
+
if (opts.json) console.log(JSON.stringify({ event: "error", message }));
|
|
295
|
+
console.error(chalk.red(`error: ${message}`));
|
|
296
|
+
process.exitCode = 1;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// src/lib/dev-state.ts
|
|
301
|
+
import { existsSync, mkdirSync, readFileSync as readFileSync2, rmSync, writeFileSync as writeFileSync2 } from "fs";
|
|
302
|
+
import { dirname, join as join2 } from "path";
|
|
303
|
+
var STATE_DIR = ".vibeview";
|
|
304
|
+
var STATE_FILE = "dev-state.json";
|
|
305
|
+
function statePath(cwd) {
|
|
306
|
+
return join2(cwd, STATE_DIR, STATE_FILE);
|
|
307
|
+
}
|
|
308
|
+
function isPidAlive(pid) {
|
|
309
|
+
try {
|
|
310
|
+
process.kill(pid, 0);
|
|
311
|
+
return true;
|
|
312
|
+
} catch (err) {
|
|
313
|
+
return err.code === "EPERM";
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
function ensureGitignored(cwd) {
|
|
317
|
+
try {
|
|
318
|
+
const gitignore = join2(cwd, ".gitignore");
|
|
319
|
+
if (!existsSync(gitignore)) return;
|
|
320
|
+
const content = readFileSync2(gitignore, "utf-8");
|
|
321
|
+
const covered = content.split("\n").map((l) => l.trim()).some((l) => l === ".vibeview" || l === ".vibeview/" || l === "/.vibeview");
|
|
322
|
+
if (covered) return;
|
|
323
|
+
const prefix = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
|
|
324
|
+
writeFileSync2(gitignore, `${content}${prefix}
|
|
325
|
+
# VibeView local session state
|
|
326
|
+
.vibeview/
|
|
327
|
+
`);
|
|
328
|
+
} catch {
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
function writeDevState(cwd, state) {
|
|
332
|
+
const path = statePath(cwd);
|
|
333
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
334
|
+
writeFileSync2(path, JSON.stringify(state, null, 2) + "\n");
|
|
335
|
+
ensureGitignored(cwd);
|
|
336
|
+
}
|
|
337
|
+
function readDevState(cwd) {
|
|
338
|
+
let raw;
|
|
339
|
+
try {
|
|
340
|
+
raw = readFileSync2(statePath(cwd), "utf-8");
|
|
341
|
+
} catch {
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
344
|
+
let parsed;
|
|
345
|
+
try {
|
|
346
|
+
parsed = JSON.parse(raw);
|
|
347
|
+
} catch {
|
|
348
|
+
return null;
|
|
349
|
+
}
|
|
350
|
+
if (typeof parsed.pid !== "number" || !isPidAlive(parsed.pid)) {
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
return parsed;
|
|
354
|
+
}
|
|
355
|
+
function clearDevState(cwd) {
|
|
356
|
+
const path = statePath(cwd);
|
|
357
|
+
if (existsSync(path)) {
|
|
358
|
+
rmSync(path, { force: true });
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
253
362
|
// src/lib/metro-check.ts
|
|
254
363
|
async function checkMetro(port, timeoutMs = 1500) {
|
|
255
364
|
try {
|
|
@@ -267,7 +376,7 @@ async function checkMetro(port, timeoutMs = 1500) {
|
|
|
267
376
|
import { spawn as spawn2 } from "child_process";
|
|
268
377
|
import { createInterface } from "readline";
|
|
269
378
|
import { select as select2 } from "@inquirer/prompts";
|
|
270
|
-
import
|
|
379
|
+
import chalk3 from "chalk";
|
|
271
380
|
|
|
272
381
|
// src/lib/artifact.ts
|
|
273
382
|
import { existsSync as existsSync2, mkdtempSync, readdirSync, rmSync as rmSync2, statSync } from "fs";
|
|
@@ -327,7 +436,7 @@ function prepareUploadArtifact(path) {
|
|
|
327
436
|
}
|
|
328
437
|
|
|
329
438
|
// src/lib/dev-setup.ts
|
|
330
|
-
import
|
|
439
|
+
import chalk2 from "chalk";
|
|
331
440
|
import { input, select } from "@inquirer/prompts";
|
|
332
441
|
|
|
333
442
|
// src/lib/project-detect.ts
|
|
@@ -556,7 +665,7 @@ async function promptBuildConfig(cwd, platform) {
|
|
|
556
665
|
return { command: defaults.buildCommand, artifact: defaults.artifact };
|
|
557
666
|
}
|
|
558
667
|
console.log(
|
|
559
|
-
|
|
668
|
+
chalk2.dim(
|
|
560
669
|
" Tip: include your install step \u2014 dependencies are not uploaded.\n e.g. `yarn install --immutable && yarn build:android`"
|
|
561
670
|
)
|
|
562
671
|
);
|
|
@@ -582,7 +691,7 @@ async function promptProductionBuildConfig(cwd, platform) {
|
|
|
582
691
|
};
|
|
583
692
|
}
|
|
584
693
|
console.log(
|
|
585
|
-
|
|
694
|
+
chalk2.dim(
|
|
586
695
|
" Tip: include your install step \u2014 dependencies are not uploaded.\n e.g. `yarn install --immutable && cd android && ./gradlew bundleRelease`"
|
|
587
696
|
)
|
|
588
697
|
);
|
|
@@ -779,7 +888,7 @@ function runTar(args) {
|
|
|
779
888
|
var CLOUD_PLATFORMS = ["ios", "android", "tvos", "androidtv"];
|
|
780
889
|
var PRODUCTION_SIGNING_PLATFORMS = ["ios", "android", "androidtv", "tvos"];
|
|
781
890
|
function productionUnsupportedReason(platform) {
|
|
782
|
-
return `--production doesn't support ${platform} \u2014 production signing supports ${PRODUCTION_SIGNING_PLATFORMS.join(", ")}`;
|
|
891
|
+
return `--production doesn't support ${platformLabel(platform)} \u2014 production signing supports ${PRODUCTION_SIGNING_PLATFORMS.join(", ")}`;
|
|
783
892
|
}
|
|
784
893
|
function isCloudPlatform(p) {
|
|
785
894
|
return CLOUD_PLATFORMS.includes(p);
|
|
@@ -806,7 +915,7 @@ async function runBuildForPlatform(cwd, platform, build, opts, log, deps = { upl
|
|
|
806
915
|
const { result } = await deps.upload(uploadPath, opts);
|
|
807
916
|
if (result.platform !== platform) {
|
|
808
917
|
throw new Error(
|
|
809
|
-
`you built
|
|
918
|
+
`you built ${platformWithArticle(result.platform)} app (${result.name}) but the target platform is ${platformLabel(platform)} \u2014 check platforms.${platform}.build in vibeview.json`
|
|
810
919
|
);
|
|
811
920
|
}
|
|
812
921
|
return result;
|
|
@@ -831,7 +940,7 @@ function runShell(command, cwd, log) {
|
|
|
831
940
|
}
|
|
832
941
|
async function resolveBarePlatform(cwd, cfg, opts) {
|
|
833
942
|
if (opts.platform && !isDevPlatform(opts.platform)) {
|
|
834
|
-
console.error(
|
|
943
|
+
console.error(chalk3.red(`error: --platform must be one of ${DEV_PLATFORMS.join(", ")}`));
|
|
835
944
|
process.exit(2);
|
|
836
945
|
}
|
|
837
946
|
let platform = opts.platform || cfg?.defaultPlatform;
|
|
@@ -946,9 +1055,9 @@ async function buildCommand(opts, cloudDeps) {
|
|
|
946
1055
|
platform,
|
|
947
1056
|
build,
|
|
948
1057
|
opts,
|
|
949
|
-
(l) => console.error(
|
|
1058
|
+
(l) => console.error(chalk3.dim(l))
|
|
950
1059
|
);
|
|
951
|
-
for (const w of result.warnings ?? []) console.error(
|
|
1060
|
+
for (const w of result.warnings ?? []) console.error(chalk3.yellow(`\u26A0 ${w}`));
|
|
952
1061
|
persistUploadedAppId(cwd, cfg, platform, build, result.public_id);
|
|
953
1062
|
if (opts.json) {
|
|
954
1063
|
console.log(
|
|
@@ -956,8 +1065,8 @@ async function buildCommand(opts, cloudDeps) {
|
|
|
956
1065
|
);
|
|
957
1066
|
} else {
|
|
958
1067
|
console.error(
|
|
959
|
-
|
|
960
|
-
${
|
|
1068
|
+
chalk3.green(`\u2713 built and uploaded ${result.name}`) + `
|
|
1069
|
+
${chalk3.cyan(result.public_id)} ${chalk3.dim(`[${result.platform}]`)}`
|
|
961
1070
|
);
|
|
962
1071
|
}
|
|
963
1072
|
} catch (err) {
|
|
@@ -969,7 +1078,7 @@ async function buildCommand(opts, cloudDeps) {
|
|
|
969
1078
|
}
|
|
970
1079
|
function printFailure(opts, message, code) {
|
|
971
1080
|
if (opts.json) console.log(JSON.stringify({ event: "error", code, message }));
|
|
972
|
-
console.error(
|
|
1081
|
+
console.error(chalk3.red(`error: ${message}`));
|
|
973
1082
|
}
|
|
974
1083
|
function fail(opts, message, code) {
|
|
975
1084
|
printFailure(opts, message, code);
|
|
@@ -992,7 +1101,7 @@ async function resolveCloudPlatforms(cwd, cfgRef, opts) {
|
|
|
992
1101
|
for (const p of unsupported) {
|
|
993
1102
|
const reason = productionUnsupportedReason(p);
|
|
994
1103
|
emitCloud(opts.json, { event: "skipped", platform: p, reason });
|
|
995
|
-
if (!opts.json) console.error(
|
|
1104
|
+
if (!opts.json) console.error(chalk3.yellow(`\u26A0 skipping ${p}: ${reason}`));
|
|
996
1105
|
}
|
|
997
1106
|
chosenPlatforms = chosenPlatforms.filter((p) => !unsupported.includes(p));
|
|
998
1107
|
}
|
|
@@ -1033,7 +1142,7 @@ async function resolveCloudPlatforms(cwd, cfgRef, opts) {
|
|
|
1033
1142
|
function validateExplicitPlatform(raw) {
|
|
1034
1143
|
if (!isDevPlatform(raw)) {
|
|
1035
1144
|
console.error(
|
|
1036
|
-
|
|
1145
|
+
chalk3.red(`error: --platform must be one of ${DEV_PLATFORMS.join(", ")}, or "all"`)
|
|
1037
1146
|
);
|
|
1038
1147
|
process.exit(2);
|
|
1039
1148
|
}
|
|
@@ -1043,7 +1152,7 @@ function requireCloudCapable(opts, platform) {
|
|
|
1043
1152
|
if (!isCloudPlatform(platform)) {
|
|
1044
1153
|
fail(
|
|
1045
1154
|
opts,
|
|
1046
|
-
`cloud builds don't support ${platform} \u2014 pass one of ${CLOUD_PLATFORMS.join(", ")}`
|
|
1155
|
+
`cloud builds don't support ${platformLabel(platform)} \u2014 pass one of ${CLOUD_PLATFORMS.join(", ")}`
|
|
1047
1156
|
);
|
|
1048
1157
|
}
|
|
1049
1158
|
}
|
|
@@ -1093,7 +1202,7 @@ async function resolveCloudAppIds(cwd, cfgRef, platforms, opts, listApps) {
|
|
|
1093
1202
|
createFromArtifact.add(platform);
|
|
1094
1203
|
if (!opts.json) {
|
|
1095
1204
|
console.error(
|
|
1096
|
-
|
|
1205
|
+
chalk3.dim(
|
|
1097
1206
|
opts.production ? `no ${platform} apps yet \u2014 production builds aren't linked to an app; the signed artifact will be available from the build itself` : `no ${platform} apps yet \u2014 this build will create one from the artifact`
|
|
1098
1207
|
)
|
|
1099
1208
|
);
|
|
@@ -1270,7 +1379,7 @@ ${offenders}`);
|
|
|
1270
1379
|
const autoIncrement = build.autoIncrement === true;
|
|
1271
1380
|
if (autoIncrement && !production && !opts.json) {
|
|
1272
1381
|
console.error(
|
|
1273
|
-
|
|
1382
|
+
chalk3.yellow(
|
|
1274
1383
|
`note: platforms.${p}.build.autoIncrement only applies to production builds (--production) \u2014 ignoring it for this simulator build`
|
|
1275
1384
|
)
|
|
1276
1385
|
);
|
|
@@ -1307,7 +1416,7 @@ ${offenders}`);
|
|
|
1307
1416
|
}
|
|
1308
1417
|
if (!opts.json) {
|
|
1309
1418
|
for (const b of created) {
|
|
1310
|
-
console.error(` ${
|
|
1419
|
+
console.error(` ${chalk3.cyan(b.id)} ${chalk3.dim(`[${b.platform}]`)}`);
|
|
1311
1420
|
}
|
|
1312
1421
|
}
|
|
1313
1422
|
const shouldWait = opts.wait !== false || opts.download;
|
|
@@ -1332,7 +1441,7 @@ ${offenders}`);
|
|
|
1332
1441
|
path: result.path
|
|
1333
1442
|
});
|
|
1334
1443
|
if (!opts.json) {
|
|
1335
|
-
console.error(
|
|
1444
|
+
console.error(chalk3.green(`\u2713 saved ${result.filename}`));
|
|
1336
1445
|
}
|
|
1337
1446
|
} catch (err) {
|
|
1338
1447
|
downloadFailed = true;
|
|
@@ -1341,7 +1450,7 @@ ${offenders}`);
|
|
|
1341
1450
|
event: "error",
|
|
1342
1451
|
message: `error downloading ${b.id}: ${message}`
|
|
1343
1452
|
});
|
|
1344
|
-
if (!opts.json) console.error(
|
|
1453
|
+
if (!opts.json) console.error(chalk3.red(`error downloading ${b.id}: ${message}`));
|
|
1345
1454
|
}
|
|
1346
1455
|
}
|
|
1347
1456
|
}
|
|
@@ -1398,7 +1507,7 @@ async function waitForCloudBuilds(builds, opts, deps) {
|
|
|
1398
1507
|
const giveUpLocally = (id, message) => {
|
|
1399
1508
|
abandoned.add(id);
|
|
1400
1509
|
emitCloud(opts.json, { event: "error", message });
|
|
1401
|
-
if (!opts.json) console.error(
|
|
1510
|
+
if (!opts.json) console.error(chalk3.red(message));
|
|
1402
1511
|
};
|
|
1403
1512
|
const pollAndProcess = async (build) => {
|
|
1404
1513
|
const offset = offsets.get(build.id) ?? 0;
|
|
@@ -1559,21 +1668,21 @@ function printTransition(id, platform, status, state, json, printLine) {
|
|
|
1559
1668
|
if (json) {
|
|
1560
1669
|
emitCloud(json, { event: "status", id, status: "queued" });
|
|
1561
1670
|
} else {
|
|
1562
|
-
printLine(id, platform,
|
|
1671
|
+
printLine(id, platform, chalk3.dim("\u23F3 queued \u2014 waiting for a build slot"));
|
|
1563
1672
|
}
|
|
1564
1673
|
} else if (status === "running" && !state.printedRunning) {
|
|
1565
1674
|
state.printedRunning = true;
|
|
1566
1675
|
if (json) {
|
|
1567
1676
|
emitCloud(json, { event: "status", id, status: "running" });
|
|
1568
1677
|
} else {
|
|
1569
|
-
printLine(id, platform, `${
|
|
1678
|
+
printLine(id, platform, `${chalk3.cyan("\u25B6")} building`);
|
|
1570
1679
|
}
|
|
1571
1680
|
}
|
|
1572
1681
|
}
|
|
1573
1682
|
function printOutcomeLine(build, multi) {
|
|
1574
1683
|
const prefix = multi ? `[${build.platform}] ` : "";
|
|
1575
1684
|
const ok = build.status === "succeeded";
|
|
1576
|
-
const mark = ok ?
|
|
1685
|
+
const mark = ok ? chalk3.green("\u2713") : chalk3.red("\u2717");
|
|
1577
1686
|
const duration = formatDuration(build.started_at, build.finished_at);
|
|
1578
1687
|
let line = `${prefix}${mark} ${build.status}`;
|
|
1579
1688
|
if (duration) line += ` (${duration})`;
|
|
@@ -1620,7 +1729,7 @@ import { createWriteStream, mkdirSync as mkdirSync3 } from "fs";
|
|
|
1620
1729
|
import { dirname as dirname5 } from "path";
|
|
1621
1730
|
import { createInterface as createInterface3 } from "readline";
|
|
1622
1731
|
import { select as select3 } from "@inquirer/prompts";
|
|
1623
|
-
import
|
|
1732
|
+
import chalk4 from "chalk";
|
|
1624
1733
|
import openBrowser from "open";
|
|
1625
1734
|
|
|
1626
1735
|
// src/lib/tunnel-client.ts
|
|
@@ -2178,7 +2287,8 @@ function devSessionCreateBody(cfg) {
|
|
|
2178
2287
|
app_id: cfg.appId,
|
|
2179
2288
|
// Roku has no JS bundle to serve, so there is nothing to tunnel.
|
|
2180
2289
|
tunnel: PLATFORM_NEEDS_METRO[cfg.platform],
|
|
2181
|
-
...cfg.model ? { model: cfg.model } : {}
|
|
2290
|
+
...cfg.model ? { model: cfg.model } : {},
|
|
2291
|
+
...cfg.buildId ? { build_id: cfg.buildId } : {}
|
|
2182
2292
|
};
|
|
2183
2293
|
}
|
|
2184
2294
|
function resolveDeviceModel(pools, platform, model) {
|
|
@@ -2300,23 +2410,22 @@ function buildAppStrategy(apps, buildFlag, json, isTTY) {
|
|
|
2300
2410
|
if (json || !isTTY) return "pick";
|
|
2301
2411
|
return "ask";
|
|
2302
2412
|
}
|
|
2303
|
-
function requireNonInteractive(json) {
|
|
2413
|
+
function requireNonInteractive(json, message) {
|
|
2304
2414
|
if (!json) return;
|
|
2305
|
-
emit(json, {
|
|
2306
|
-
event: "error",
|
|
2307
|
-
code: "config_required",
|
|
2308
|
-
message: "pass --platform and run once interactively, or commit vibeview.json"
|
|
2309
|
-
});
|
|
2415
|
+
emit(json, { event: "error", code: "config_required", message });
|
|
2310
2416
|
process.exit(1);
|
|
2311
2417
|
}
|
|
2312
2418
|
async function ensureConfig(api, opts) {
|
|
2313
2419
|
const existing = loadDevConfig(process.cwd());
|
|
2314
2420
|
if (opts.platform && !isDevPlatform(opts.platform)) {
|
|
2315
|
-
console.error(
|
|
2421
|
+
console.error(chalk4.red(`error: --platform must be one of ${DEV_PLATFORMS.join(", ")}`));
|
|
2316
2422
|
process.exit(2);
|
|
2317
2423
|
}
|
|
2318
2424
|
if (!opts.platform && !existing?.defaultPlatform) {
|
|
2319
|
-
requireNonInteractive(
|
|
2425
|
+
requireNonInteractive(
|
|
2426
|
+
opts.json,
|
|
2427
|
+
`--platform is required (no vibeview.json default yet) \u2014 pass --platform <${DEV_PLATFORMS.join("|")}>, or run once interactively to set a default.`
|
|
2428
|
+
);
|
|
2320
2429
|
}
|
|
2321
2430
|
const platform = opts.platform || existing?.defaultPlatform || await select3({
|
|
2322
2431
|
message: "Platform to run on",
|
|
@@ -2325,21 +2434,21 @@ async function ensureConfig(api, opts) {
|
|
|
2325
2434
|
const isTv = PLATFORM_DEVICE[platform].tv;
|
|
2326
2435
|
if (isTv && opts.deviceCategory) {
|
|
2327
2436
|
console.error(
|
|
2328
|
-
|
|
2437
|
+
chalk4.red(`error: --device-category does not apply to ${platform} (always a tv device)`)
|
|
2329
2438
|
);
|
|
2330
2439
|
process.exit(2);
|
|
2331
2440
|
}
|
|
2332
2441
|
if (!isTv && opts.deviceCategory === "tv") {
|
|
2333
|
-
console.error(
|
|
2442
|
+
console.error(chalk4.red(`error: use --platform tvos or --platform androidtv for TV devices`));
|
|
2334
2443
|
process.exit(2);
|
|
2335
2444
|
}
|
|
2336
2445
|
if (opts.model !== void 0 && opts.deviceCategory) {
|
|
2337
|
-
console.error(
|
|
2446
|
+
console.error(chalk4.red("error: pass --model or --device-category, not both"));
|
|
2338
2447
|
process.exit(2);
|
|
2339
2448
|
}
|
|
2340
2449
|
if (opts.model !== void 0 && !opts.model.trim()) {
|
|
2341
2450
|
console.error(
|
|
2342
|
-
|
|
2451
|
+
chalk4.red("error: --model needs a model name (see `vibeview list-devices --models`)")
|
|
2343
2452
|
);
|
|
2344
2453
|
process.exit(2);
|
|
2345
2454
|
}
|
|
@@ -2348,9 +2457,7 @@ async function ensureConfig(api, opts) {
|
|
|
2348
2457
|
let isFirstRun = false;
|
|
2349
2458
|
if (!existing) {
|
|
2350
2459
|
isFirstRun = true;
|
|
2351
|
-
if (opts.json) {
|
|
2352
|
-
requireNonInteractive(opts.json);
|
|
2353
|
-
} else if (process.stdin.isTTY) {
|
|
2460
|
+
if (!opts.json && process.stdin.isTTY) {
|
|
2354
2461
|
firstRunBuild = await promptBuildConfig(process.cwd(), platform);
|
|
2355
2462
|
if (PLATFORM_NEEDS_METRO[platform]) {
|
|
2356
2463
|
firstRunMetro = await promptMetroCommand(process.cwd(), platform);
|
|
@@ -2363,7 +2470,7 @@ async function ensureConfig(api, opts) {
|
|
|
2363
2470
|
allApps = await api.listApps();
|
|
2364
2471
|
} catch (err) {
|
|
2365
2472
|
const message = `could not fetch your app list \u2014 ${err instanceof Error ? err.message : String(err)}`;
|
|
2366
|
-
console.error(
|
|
2473
|
+
console.error(chalk4.red(`error: ${message}`));
|
|
2367
2474
|
emit(opts.json, { event: "error", message });
|
|
2368
2475
|
process.exit(1);
|
|
2369
2476
|
}
|
|
@@ -2372,7 +2479,7 @@ async function ensureConfig(api, opts) {
|
|
|
2372
2479
|
if (choice.staleFlag) {
|
|
2373
2480
|
const message = `app "${choice.staleFlag}" not found \u2014 it may have been deleted.`;
|
|
2374
2481
|
console.error(
|
|
2375
|
-
|
|
2482
|
+
chalk4.red(`error: ${message}`) + `
|
|
2376
2483
|
Run \`vibeview dev\` without --app to pick from the list.`
|
|
2377
2484
|
);
|
|
2378
2485
|
emit(opts.json, { event: "error", message });
|
|
@@ -2382,12 +2489,12 @@ Run \`vibeview dev\` without --app to pick from the list.`
|
|
|
2382
2489
|
let builtThisRun = false;
|
|
2383
2490
|
if (choice.staleCached) {
|
|
2384
2491
|
console.error(
|
|
2385
|
-
|
|
2492
|
+
chalk4.yellow(`\u26A0 app "${choice.staleCached}" no longer exists \u2014 pick your debug build again`)
|
|
2386
2493
|
);
|
|
2387
2494
|
}
|
|
2388
2495
|
const buildConfig = firstRunBuild ?? entry?.build ?? null;
|
|
2389
|
-
if (!appId && buildConfig) {
|
|
2390
|
-
const strategy = buildAppStrategy(apps,
|
|
2496
|
+
if (!appId && buildConfig && typeof opts.build !== "string") {
|
|
2497
|
+
const strategy = buildAppStrategy(apps, opts.build === true, opts.json, process.stdin.isTTY);
|
|
2391
2498
|
let action = strategy === "ask" ? "pick" : strategy;
|
|
2392
2499
|
if (strategy === "ask") {
|
|
2393
2500
|
action = await select3({
|
|
@@ -2399,20 +2506,20 @@ Run \`vibeview dev\` without --app to pick from the list.`
|
|
|
2399
2506
|
});
|
|
2400
2507
|
}
|
|
2401
2508
|
if (action === "build") {
|
|
2402
|
-
console.error(
|
|
2509
|
+
console.error(chalk4.dim(`Building ${platform}...`));
|
|
2403
2510
|
try {
|
|
2404
2511
|
const result = await runBuildForPlatform(
|
|
2405
2512
|
process.cwd(),
|
|
2406
2513
|
platform,
|
|
2407
2514
|
buildConfig,
|
|
2408
2515
|
opts,
|
|
2409
|
-
(l) => console.error(
|
|
2516
|
+
(l) => console.error(chalk4.dim(l))
|
|
2410
2517
|
);
|
|
2411
2518
|
appId = result.public_id;
|
|
2412
2519
|
builtThisRun = true;
|
|
2413
2520
|
} catch (err) {
|
|
2414
2521
|
const message = err.message;
|
|
2415
|
-
console.error(
|
|
2522
|
+
console.error(chalk4.red(`error: ${message}`));
|
|
2416
2523
|
emit(opts.json, { event: "error", message });
|
|
2417
2524
|
process.exit(1);
|
|
2418
2525
|
}
|
|
@@ -2421,20 +2528,23 @@ Run \`vibeview dev\` without --app to pick from the list.`
|
|
|
2421
2528
|
if (!appId) {
|
|
2422
2529
|
if (apps.length === 0) {
|
|
2423
2530
|
const message = noAppsMessage(platform, entry?.build !== void 0);
|
|
2424
|
-
console.error(
|
|
2531
|
+
console.error(chalk4.red(message));
|
|
2425
2532
|
emit(opts.json, { event: "error", code: "no_apps", message });
|
|
2426
2533
|
process.exit(1);
|
|
2427
2534
|
}
|
|
2428
2535
|
if (choice.staleCached) {
|
|
2429
2536
|
const message = `app "${choice.staleCached}" for ${platform} no longer exists \u2014 pass --app <id> (see \`vibeview list-apps\`) or run interactively to pick one.`;
|
|
2430
|
-
console.error(
|
|
2537
|
+
console.error(chalk4.red(`error: ${message}`));
|
|
2431
2538
|
emit(opts.json, { event: "error", code: "app_deleted", message });
|
|
2432
2539
|
process.exit(1);
|
|
2433
2540
|
}
|
|
2434
|
-
requireNonInteractive(
|
|
2541
|
+
requireNonInteractive(
|
|
2542
|
+
opts.json,
|
|
2543
|
+
`--app is required \u2014 no app configured for ${platform} yet; pass --app <id> (see \`vibeview list-apps\`), or run interactively to pick one.`
|
|
2544
|
+
);
|
|
2435
2545
|
if (!process.stdin.isTTY) {
|
|
2436
2546
|
console.error(
|
|
2437
|
-
|
|
2547
|
+
chalk4.red(
|
|
2438
2548
|
`error: no valid app configured for ${platform} \u2014 run interactively to pick one, or pass --app <id>`
|
|
2439
2549
|
)
|
|
2440
2550
|
);
|
|
@@ -2466,11 +2576,12 @@ Run \`vibeview dev\` without --app to pick from the list.`
|
|
|
2466
2576
|
});
|
|
2467
2577
|
saveDevConfig(process.cwd(), cfg);
|
|
2468
2578
|
if (isFirstRun) {
|
|
2579
|
+
console.error(chalk4.dim(`Created vibeview.json (platform: ${platform}, app: ${appId}).`));
|
|
2469
2580
|
const hint = fillLaterHint(
|
|
2470
2581
|
{ build: !firstRunBuild, metro: PLATFORM_NEEDS_METRO[platform] && !firstRunMetro },
|
|
2471
2582
|
platform
|
|
2472
2583
|
);
|
|
2473
|
-
if (hint) console.error(
|
|
2584
|
+
if (hint) console.error(chalk4.dim(hint));
|
|
2474
2585
|
}
|
|
2475
2586
|
const finalMetro = isFirstRun ? resolveMetro(cfg, platform, opts.metroPort ? parseInt(opts.metroPort, 10) : void 0) : metroResolved;
|
|
2476
2587
|
return {
|
|
@@ -2490,10 +2601,21 @@ async function runDevInProcess(opts) {
|
|
|
2490
2601
|
watchSeconds = parseWatchSeconds(opts.watch);
|
|
2491
2602
|
} catch (err) {
|
|
2492
2603
|
const message = err.message;
|
|
2493
|
-
console.error(
|
|
2604
|
+
console.error(chalk4.red(`error: ${message}`));
|
|
2494
2605
|
emit(opts.json, { event: "error", code: "invalid_flag", message });
|
|
2495
2606
|
process.exit(2);
|
|
2496
2607
|
}
|
|
2608
|
+
let pinnedBuildId;
|
|
2609
|
+
if (typeof opts.build === "string") {
|
|
2610
|
+
try {
|
|
2611
|
+
pinnedBuildId = parseBuildId(opts.build);
|
|
2612
|
+
} catch (err) {
|
|
2613
|
+
const message = err.message;
|
|
2614
|
+
console.error(chalk4.red(`error: ${message}`));
|
|
2615
|
+
emit(opts.json, { event: "error", code: "invalid_flag", message });
|
|
2616
|
+
process.exit(2);
|
|
2617
|
+
}
|
|
2618
|
+
}
|
|
2497
2619
|
if (opts.json) {
|
|
2498
2620
|
const hasToken = !!(opts.token || process.env.VIBEVIEW_API_TOKEN || loadConfig().token);
|
|
2499
2621
|
if (!hasToken) {
|
|
@@ -2506,26 +2628,36 @@ async function runDevInProcess(opts) {
|
|
|
2506
2628
|
}
|
|
2507
2629
|
}
|
|
2508
2630
|
const { token, baseUrl } = resolveAuth(opts);
|
|
2509
|
-
console.error(
|
|
2631
|
+
console.error(chalk4.dim(`Using ${baseUrl}`));
|
|
2510
2632
|
const api = new ApiClient(baseUrl, token);
|
|
2511
2633
|
const cfg = await ensureConfig(api, opts);
|
|
2512
|
-
if (
|
|
2634
|
+
if (pinnedBuildId !== void 0) {
|
|
2635
|
+
try {
|
|
2636
|
+
checkDevBuild(await api.getApp(cfg.appId), pinnedBuildId, cfg.platform);
|
|
2637
|
+
} catch (err) {
|
|
2638
|
+
const message = err.message;
|
|
2639
|
+
console.error(chalk4.red(`error: ${message}`));
|
|
2640
|
+
emit(opts.json, { event: "error", code: "invalid_build", message });
|
|
2641
|
+
process.exit(1);
|
|
2642
|
+
}
|
|
2643
|
+
}
|
|
2644
|
+
if (opts.build === true && !cfg.builtThisRun) {
|
|
2513
2645
|
const reloaded = loadDevConfig(process.cwd());
|
|
2514
2646
|
const build = reloaded?.platforms?.[cfg.platform]?.build;
|
|
2515
2647
|
if (!build) {
|
|
2516
2648
|
const message = `no build config for ${cfg.platform} \u2014 run vibeview build once to set it up`;
|
|
2517
|
-
console.error(
|
|
2649
|
+
console.error(chalk4.red(`error: ${message}`));
|
|
2518
2650
|
emit(opts.json, { event: "error", code: "config_required", message });
|
|
2519
2651
|
process.exit(1);
|
|
2520
2652
|
}
|
|
2521
|
-
console.error(
|
|
2653
|
+
console.error(chalk4.dim(`Building ${cfg.platform}...`));
|
|
2522
2654
|
try {
|
|
2523
2655
|
const result = await runBuildForPlatform(
|
|
2524
2656
|
process.cwd(),
|
|
2525
2657
|
cfg.platform,
|
|
2526
2658
|
build,
|
|
2527
2659
|
{ token, baseUrl },
|
|
2528
|
-
(l) => console.error(
|
|
2660
|
+
(l) => console.error(chalk4.dim(l))
|
|
2529
2661
|
);
|
|
2530
2662
|
cfg.appId = result.public_id;
|
|
2531
2663
|
if (reloaded && reloaded.platforms[cfg.platform] && reloaded.platforms[cfg.platform].appId !== result.public_id) {
|
|
@@ -2534,7 +2666,7 @@ async function runDevInProcess(opts) {
|
|
|
2534
2666
|
}
|
|
2535
2667
|
} catch (err) {
|
|
2536
2668
|
const message = err.message;
|
|
2537
|
-
console.error(
|
|
2669
|
+
console.error(chalk4.red(`error: ${message}`));
|
|
2538
2670
|
emit(opts.json, { event: "error", message });
|
|
2539
2671
|
process.exit(1);
|
|
2540
2672
|
}
|
|
@@ -2556,7 +2688,7 @@ async function runDevInProcess(opts) {
|
|
|
2556
2688
|
metroLogStream.write(line.replace(/^\[metro\] /, "") + "\n");
|
|
2557
2689
|
} catch {
|
|
2558
2690
|
}
|
|
2559
|
-
if (shouldEchoMetroLine(line, opts.debug === true)) console.error(
|
|
2691
|
+
if (shouldEchoMetroLine(line, opts.debug === true)) console.error(chalk4.dim(line));
|
|
2560
2692
|
};
|
|
2561
2693
|
metroResult = await ensureMetro(
|
|
2562
2694
|
process.cwd(),
|
|
@@ -2569,15 +2701,15 @@ async function runDevInProcess(opts) {
|
|
|
2569
2701
|
);
|
|
2570
2702
|
} catch (err) {
|
|
2571
2703
|
const message = err.message;
|
|
2572
|
-
console.error(
|
|
2704
|
+
console.error(chalk4.red(`error: ${message}`));
|
|
2573
2705
|
emit(opts.json, { event: "error", code: "metro_down", message });
|
|
2574
2706
|
process.exit(1);
|
|
2575
2707
|
}
|
|
2576
|
-
for (const w of metroResult.warnings) console.error(
|
|
2708
|
+
for (const w of metroResult.warnings) console.error(chalk4.yellow(`\u26A0 ${w}`));
|
|
2577
2709
|
if (metroResult.status === "spawned" || metroResult.status === "respawned") {
|
|
2578
|
-
console.error(
|
|
2710
|
+
console.error(chalk4.green(`\u2713 started Metro (${cfg.metroCommand})`));
|
|
2579
2711
|
console.error(
|
|
2580
|
-
|
|
2712
|
+
chalk4.dim(` full Metro output: ${metroLogPath(process.cwd())} (--debug echoes it here)`)
|
|
2581
2713
|
);
|
|
2582
2714
|
}
|
|
2583
2715
|
metroOwnedThisRun = metroResult.status === "spawned" || metroResult.status === "respawned";
|
|
@@ -2587,19 +2719,23 @@ async function runDevInProcess(opts) {
|
|
|
2587
2719
|
watchIgnore = rokuEntry?.watchIgnore ?? [];
|
|
2588
2720
|
if (!rokuBuild) {
|
|
2589
2721
|
const message = `no build config for ${cfg.platform} \u2014 run \`vibeview build --platform ${cfg.platform}\` once to set it up`;
|
|
2590
|
-
console.error(
|
|
2722
|
+
console.error(chalk4.red(`error: ${message}`));
|
|
2591
2723
|
emit(opts.json, { event: "error", code: "config_required", message });
|
|
2592
2724
|
process.exit(1);
|
|
2593
2725
|
}
|
|
2594
2726
|
}
|
|
2595
|
-
console.error(
|
|
2727
|
+
console.error(
|
|
2728
|
+
chalk4.dim(
|
|
2729
|
+
`Starting ${platformWithArticle(cfg.platform)} dev session for ${cfg.appId}${pinnedBuildId !== void 0 ? ` (build ${pinnedBuildId})` : ""}...`
|
|
2730
|
+
)
|
|
2731
|
+
);
|
|
2596
2732
|
let stopping = false;
|
|
2597
2733
|
let handle = null;
|
|
2598
2734
|
let reloadLoop = null;
|
|
2599
2735
|
const stopSession = async (code) => {
|
|
2600
2736
|
if (stopping) return;
|
|
2601
2737
|
stopping = true;
|
|
2602
|
-
console.error(
|
|
2738
|
+
console.error(chalk4.dim("\nStopping session..."));
|
|
2603
2739
|
reloadLoop?.stop();
|
|
2604
2740
|
await handle?.stop();
|
|
2605
2741
|
if (metroOwnedThisRun) killOwnedMetro(process.cwd());
|
|
@@ -2622,6 +2758,7 @@ async function runDevInProcess(opts) {
|
|
|
2622
2758
|
appId: cfg.appId,
|
|
2623
2759
|
deviceCategory: cfg.deviceCategory,
|
|
2624
2760
|
model: cfg.model,
|
|
2761
|
+
buildId: pinnedBuildId,
|
|
2625
2762
|
metroPort: cfg.metroPort ?? 8081,
|
|
2626
2763
|
// startDevSession's own browser-open happens before we've written the
|
|
2627
2764
|
// state file / emitted session_ready — open it ourselves afterward
|
|
@@ -2633,26 +2770,26 @@ async function runDevInProcess(opts) {
|
|
|
2633
2770
|
if (evt.event === "warning") {
|
|
2634
2771
|
if (nextWarningIsMetroUnreachable) {
|
|
2635
2772
|
nextWarningIsMetroUnreachable = false;
|
|
2636
|
-
console.error(
|
|
2773
|
+
console.error(chalk4.red(`\u2717 ${evt.message}`));
|
|
2637
2774
|
} else {
|
|
2638
|
-
console.error(
|
|
2775
|
+
console.error(chalk4.yellow(`\u26A0 ${evt.message}`));
|
|
2639
2776
|
}
|
|
2640
2777
|
} else if (evt.event === "queued") {
|
|
2641
2778
|
const where = evt.position ? ` (position ${evt.position})` : "";
|
|
2642
2779
|
console.error(
|
|
2643
|
-
|
|
2780
|
+
chalk4.yellow(
|
|
2644
2781
|
`\u23F3 every device is busy \u2014 waiting in the queue${where}, Ctrl-C to give up`
|
|
2645
2782
|
)
|
|
2646
2783
|
);
|
|
2647
2784
|
} else if (evt.event === "tunnel_connected") {
|
|
2648
2785
|
everConnected = true;
|
|
2649
|
-
console.error(
|
|
2786
|
+
console.error(chalk4.green("\u2713 tunnel connected \u2014 edit away, Fast Refresh is live"));
|
|
2650
2787
|
} else if (evt.event === "installing") {
|
|
2651
2788
|
console.error(
|
|
2652
|
-
|
|
2789
|
+
chalk4.dim(" Putting the channel on the Roku \u2014 the link appears once it is up.")
|
|
2653
2790
|
);
|
|
2654
2791
|
} else if (evt.event === "session_ended" && !stopping && handle) {
|
|
2655
|
-
console.error(
|
|
2792
|
+
console.error(chalk4.red("tunnel closed by server \u2014 session ended"));
|
|
2656
2793
|
stopping = true;
|
|
2657
2794
|
endedEmitted = true;
|
|
2658
2795
|
emit(opts.json, evt);
|
|
@@ -2671,20 +2808,20 @@ async function runDevInProcess(opts) {
|
|
|
2671
2808
|
if (!waitingPrinted) {
|
|
2672
2809
|
waitingPrinted = true;
|
|
2673
2810
|
console.error(
|
|
2674
|
-
|
|
2811
|
+
chalk4.dim("\u2026 waiting for the device to come up (tunnel not ready yet)")
|
|
2675
2812
|
);
|
|
2676
2813
|
}
|
|
2677
2814
|
return;
|
|
2678
2815
|
}
|
|
2679
|
-
console.error(
|
|
2816
|
+
console.error(chalk4.yellow("\u2026 tunnel dropped, reconnecting"));
|
|
2680
2817
|
} else if (s === "metro-unreachable") {
|
|
2681
2818
|
nextWarningIsMetroUnreachable = true;
|
|
2682
2819
|
}
|
|
2683
2820
|
}
|
|
2684
2821
|
);
|
|
2685
|
-
const keysLine = rokuBuild ? `Press ${
|
|
2822
|
+
const keysLine = rokuBuild ? `Press ${chalk4.bold("r")} to reload the channel, ${chalk4.bold("q")} or Ctrl-C to end the session.` : "Ctrl-C to end the session.";
|
|
2686
2823
|
console.error(`
|
|
2687
|
-
Watch and interact: ${
|
|
2824
|
+
Watch and interact: ${chalk4.cyan(handle.pageUrl)}
|
|
2688
2825
|
${keysLine}
|
|
2689
2826
|
`);
|
|
2690
2827
|
writeDevState(process.cwd(), {
|
|
@@ -2716,13 +2853,13 @@ async function runDevInProcess(opts) {
|
|
|
2716
2853
|
// Build output and loop notices are human output, so they go to
|
|
2717
2854
|
// stderr like every other line this command prints — stdout stays
|
|
2718
2855
|
// reserved for the `--json` event stream.
|
|
2719
|
-
log: (line) => console.error(
|
|
2856
|
+
log: (line) => console.error(chalk4.dim(line))
|
|
2720
2857
|
}),
|
|
2721
|
-
log: (line) => console.error(
|
|
2858
|
+
log: (line) => console.error(chalk4.yellow(line)),
|
|
2722
2859
|
// A failed reload is an error, not a notice — same red as every other
|
|
2723
2860
|
// failure this command prints, so it doesn't read like the yellow
|
|
2724
2861
|
// "already reloading" line.
|
|
2725
|
-
logError: (line) => console.error(
|
|
2862
|
+
logError: (line) => console.error(chalk4.red(`\u2717 ${line}`)),
|
|
2726
2863
|
...watchSeconds !== void 0 ? {
|
|
2727
2864
|
watchSeconds,
|
|
2728
2865
|
watch: watchProjectFiles(process.cwd(), rokuBuild.artifact, watchIgnore)
|
|
@@ -2730,7 +2867,7 @@ async function runDevInProcess(opts) {
|
|
|
2730
2867
|
});
|
|
2731
2868
|
if (watchSeconds !== void 0) {
|
|
2732
2869
|
console.error(
|
|
2733
|
-
|
|
2870
|
+
chalk4.dim(` Watching for changes \u2014 reloading after ${watchSeconds}s of quiet.`)
|
|
2734
2871
|
);
|
|
2735
2872
|
}
|
|
2736
2873
|
if (process.stdin.isTTY) {
|
|
@@ -2765,7 +2902,7 @@ async function runDevInProcess(opts) {
|
|
|
2765
2902
|
}
|
|
2766
2903
|
const verdict = sessionLivenessVerdict(info);
|
|
2767
2904
|
if (verdict.end && !stopping) {
|
|
2768
|
-
console.error(
|
|
2905
|
+
console.error(chalk4.red(verdict.message));
|
|
2769
2906
|
emit(opts.json, { event: "session_ended", session_id: sessionId, code: 1 });
|
|
2770
2907
|
endedEmitted = true;
|
|
2771
2908
|
await stopSession(1);
|
|
@@ -2774,7 +2911,7 @@ async function runDevInProcess(opts) {
|
|
|
2774
2911
|
const warningVerdict = lateWarningVerdict(info, reportedWarnings);
|
|
2775
2912
|
for (const w of warningVerdict.toReport) {
|
|
2776
2913
|
reportedWarnings.add(w);
|
|
2777
|
-
console.error(
|
|
2914
|
+
console.error(chalk4.yellow(`\u26A0 ${w}`));
|
|
2778
2915
|
emit(opts.json, { event: "warning", message: w });
|
|
2779
2916
|
}
|
|
2780
2917
|
if (warningVerdict.blocking && !stopping) {
|
|
@@ -2792,7 +2929,7 @@ async function runDevInProcess(opts) {
|
|
|
2792
2929
|
}
|
|
2793
2930
|
} catch (err) {
|
|
2794
2931
|
const message = err.message;
|
|
2795
|
-
console.error(
|
|
2932
|
+
console.error(chalk4.red(`error: ${message}`));
|
|
2796
2933
|
emit(opts.json, { event: "error", message });
|
|
2797
2934
|
await stopSession(1);
|
|
2798
2935
|
}
|
|
@@ -2822,12 +2959,12 @@ async function dev(opts) {
|
|
|
2822
2959
|
if (opts.json) {
|
|
2823
2960
|
console.log(JSON.stringify(parsed));
|
|
2824
2961
|
} else {
|
|
2825
|
-
console.error(
|
|
2826
|
-
console.error(
|
|
2962
|
+
console.error(chalk4.dim(`Using ${resolveAuth(opts).baseUrl}`));
|
|
2963
|
+
console.error(chalk4.green(`\u2713 dev session started in the background (pid ${parsed.pid})`));
|
|
2827
2964
|
console.error(`
|
|
2828
|
-
Watch and interact: ${
|
|
2829
|
-
console.error(
|
|
2830
|
-
console.error(
|
|
2965
|
+
Watch and interact: ${chalk4.cyan(parsed.url)}`);
|
|
2966
|
+
console.error(chalk4.dim(` vibeview dev-status \u2014 check on it`));
|
|
2967
|
+
console.error(chalk4.dim(` vibeview dev-stop \u2014 end it
|
|
2831
2968
|
`));
|
|
2832
2969
|
}
|
|
2833
2970
|
child.unref();
|
|
@@ -2837,13 +2974,13 @@ async function dev(opts) {
|
|
|
2837
2974
|
settled = true;
|
|
2838
2975
|
rl.close();
|
|
2839
2976
|
emit(opts.json, parsed);
|
|
2840
|
-
if (!opts.json) console.error(
|
|
2977
|
+
if (!opts.json) console.error(chalk4.red(`error: ${parsed.message}`));
|
|
2841
2978
|
reject(new Error(parsed.message));
|
|
2842
2979
|
} else if (parsed.event === "warning") {
|
|
2843
2980
|
if (opts.json) {
|
|
2844
2981
|
console.log(JSON.stringify(parsed));
|
|
2845
2982
|
} else {
|
|
2846
|
-
console.error(
|
|
2983
|
+
console.error(chalk4.yellow(`\u26A0 ${parsed.message}`));
|
|
2847
2984
|
}
|
|
2848
2985
|
}
|
|
2849
2986
|
});
|
|
@@ -2852,14 +2989,14 @@ async function dev(opts) {
|
|
|
2852
2989
|
rl.close();
|
|
2853
2990
|
const message = `dev session exited before starting (code ${code ?? "unknown"})`;
|
|
2854
2991
|
emit(opts.json, { event: "error", message });
|
|
2855
|
-
if (!opts.json) console.error(
|
|
2992
|
+
if (!opts.json) console.error(chalk4.red(`error: ${message}`));
|
|
2856
2993
|
reject(new Error(message));
|
|
2857
2994
|
});
|
|
2858
2995
|
child.on("error", (err) => {
|
|
2859
2996
|
if (settled) return;
|
|
2860
2997
|
rl.close();
|
|
2861
2998
|
emit(opts.json, { event: "error", message: err.message });
|
|
2862
|
-
if (!opts.json) console.error(
|
|
2999
|
+
if (!opts.json) console.error(chalk4.red(`error: ${err.message}`));
|
|
2863
3000
|
reject(err);
|
|
2864
3001
|
});
|
|
2865
3002
|
}).catch(() => {
|
|
@@ -2870,8 +3007,8 @@ async function dev(opts) {
|
|
|
2870
3007
|
}
|
|
2871
3008
|
|
|
2872
3009
|
// src/lib/formatters/devices.ts
|
|
2873
|
-
import
|
|
2874
|
-
var
|
|
3010
|
+
import chalk5 from "chalk";
|
|
3011
|
+
var HEADERS2 = ["DEVICE_ID", "NAME", "TYPE", "CATEGORY", "OS", "STATUS"];
|
|
2875
3012
|
function formatDevicesHuman(devices) {
|
|
2876
3013
|
if (devices.length === 0) {
|
|
2877
3014
|
return "No devices registered.\n";
|
|
@@ -2884,11 +3021,11 @@ function formatDevicesHuman(devices) {
|
|
|
2884
3021
|
d.os_version,
|
|
2885
3022
|
d.status
|
|
2886
3023
|
]);
|
|
2887
|
-
const widths =
|
|
3024
|
+
const widths = HEADERS2.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
2888
3025
|
const fmt = (cells) => cells.map((c, i) => c.padEnd(widths[i])).join(" ").trimEnd();
|
|
2889
|
-
const lines = [
|
|
3026
|
+
const lines = [chalk5.bold(fmt(HEADERS2))];
|
|
2890
3027
|
for (const row of rows.sort(rowSort)) {
|
|
2891
|
-
const statusColored = row[5] === "available" ?
|
|
3028
|
+
const statusColored = row[5] === "available" ? chalk5.green(row[5]) : row[5] === "busy" ? chalk5.yellow(row[5]) : chalk5.dim(row[5]);
|
|
2892
3029
|
lines.push(fmt([row[0], row[1], row[2], row[3], row[4], statusColored]));
|
|
2893
3030
|
}
|
|
2894
3031
|
return lines.join("\n") + "\n";
|
|
@@ -2915,7 +3052,7 @@ function formatDeviceModelsHuman(models) {
|
|
|
2915
3052
|
);
|
|
2916
3053
|
const widths = MODEL_HEADERS.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
2917
3054
|
const fmt = (cells) => cells.map((c, i) => c.padEnd(widths[i])).join(" ").trimEnd();
|
|
2918
|
-
return [
|
|
3055
|
+
return [chalk5.bold(fmt(MODEL_HEADERS)), ...rows.map(fmt)].join("\n") + "\n";
|
|
2919
3056
|
}
|
|
2920
3057
|
|
|
2921
3058
|
// src/agent/registry.ts
|
|
@@ -3241,7 +3378,7 @@ var AGENT_COMMANDS = [
|
|
|
3241
3378
|
// Foldable devices only.
|
|
3242
3379
|
verb: "set-posture",
|
|
3243
3380
|
tool: "set_posture",
|
|
3244
|
-
summary: "Fold or unfold a foldable device (iPhone Duo): a preset (closed, partial, open) or an exact hinge angle in degrees (--angle 0-180) \u2014 pass exactly one. Reports the posture it settled in, the angle, and which screen is lit. Errors on a device without a hinge.",
|
|
3381
|
+
summary: "Fold or unfold a foldable device (form factor `foldable` in `list-devices --models` \u2014 the iPhone Duo, Android foldables such as the Pixel 9 Pro Fold): a preset (closed, partial, open) or an exact hinge angle in degrees (--angle 0-180) \u2014 pass exactly one. Reports the posture it settled in, the angle, and which screen is lit. Errors on a device without a hinge.",
|
|
3245
3382
|
mutating: true,
|
|
3246
3383
|
params: [
|
|
3247
3384
|
{
|
|
@@ -3267,7 +3404,7 @@ var AGENT_COMMANDS = [
|
|
|
3267
3404
|
// portrait <-> landscape and accepts only 90. Refused on TV devices.
|
|
3268
3405
|
verb: "rotate",
|
|
3269
3406
|
tool: "rotate",
|
|
3270
|
-
summary: "Rotate the device. A phone toggles between portrait and landscape;
|
|
3407
|
+
summary: "Rotate the device. A phone (and an Android foldable) toggles between portrait and landscape; the iPhone Duo turns a quarter clockwise, or by --degrees 180/270. Reports the new orientation. Not available on TV devices.",
|
|
3271
3408
|
mutating: true,
|
|
3272
3409
|
params: [
|
|
3273
3410
|
{
|
|
@@ -3275,7 +3412,7 @@ var AGENT_COMMANDS = [
|
|
|
3275
3412
|
cli: "--degrees",
|
|
3276
3413
|
type: "number",
|
|
3277
3414
|
required: false,
|
|
3278
|
-
description: "
|
|
3415
|
+
description: "iPhone Duo only: 90 (default, a quarter turn clockwise), 180, or 270 (a quarter turn back). Other devices accept only 90."
|
|
3279
3416
|
}
|
|
3280
3417
|
]
|
|
3281
3418
|
},
|
|
@@ -3370,18 +3507,18 @@ var AGENT_COMMANDS = [
|
|
|
3370
3507
|
];
|
|
3371
3508
|
|
|
3372
3509
|
// src/agent/render.ts
|
|
3373
|
-
import
|
|
3510
|
+
import chalk6 from "chalk";
|
|
3374
3511
|
function renderEnvelope(env) {
|
|
3375
3512
|
const lines = [];
|
|
3376
3513
|
if (env.ok) {
|
|
3377
|
-
lines.push(
|
|
3514
|
+
lines.push(chalk6.green(`ok`) + chalk6.dim(` (${env.tool})`));
|
|
3378
3515
|
} else {
|
|
3379
|
-
lines.push(
|
|
3516
|
+
lines.push(chalk6.red(`error`) + chalk6.dim(` (${env.tool})`));
|
|
3380
3517
|
}
|
|
3381
3518
|
lines.push(...resultLines(env.result));
|
|
3382
3519
|
if (env.observation) {
|
|
3383
3520
|
if (env.observation.kind === "unavailable") {
|
|
3384
|
-
lines.push(
|
|
3521
|
+
lines.push(chalk6.yellow(`warning: ${env.observation.text}`));
|
|
3385
3522
|
} else {
|
|
3386
3523
|
lines.push(env.observation.text);
|
|
3387
3524
|
}
|
|
@@ -3394,17 +3531,17 @@ function resultLines(result) {
|
|
|
3394
3531
|
const lines = [];
|
|
3395
3532
|
if (Array.isArray(r.entries) && typeof r.cursor === "number") {
|
|
3396
3533
|
if (r.entries.length === 0) {
|
|
3397
|
-
lines.push(
|
|
3534
|
+
lines.push(chalk6.dim("(no log lines)"));
|
|
3398
3535
|
}
|
|
3399
3536
|
for (const e of r.entries) {
|
|
3400
3537
|
if (!e || typeof e !== "object") continue;
|
|
3401
3538
|
const entry = e;
|
|
3402
3539
|
const ts = typeof entry.timestamp === "number" ? new Date(entry.timestamp).toISOString().slice(11, 23) : "??:??:??.???";
|
|
3403
3540
|
const level = typeof entry.level === "string" ? entry.level : "info";
|
|
3404
|
-
const levelTag = level === "error" ?
|
|
3405
|
-
lines.push(`${
|
|
3541
|
+
const levelTag = level === "error" ? chalk6.red(level) : level === "warn" ? chalk6.yellow(level) : chalk6.dim(level);
|
|
3542
|
+
lines.push(`${chalk6.dim(ts)} ${levelTag} ${String(entry.message ?? "")}`);
|
|
3406
3543
|
}
|
|
3407
|
-
lines.push(
|
|
3544
|
+
lines.push(chalk6.dim(`cursor: ${r.cursor}`));
|
|
3408
3545
|
}
|
|
3409
3546
|
if (r.found === true && typeof r.ref === "string" && r.ref) {
|
|
3410
3547
|
let found = `found: @${r.ref}`;
|
|
@@ -3416,14 +3553,14 @@ function resultLines(result) {
|
|
|
3416
3553
|
const text = typeof a.text === "string" && a.text ? ` ${JSON.stringify(a.text)}` : "";
|
|
3417
3554
|
return `${ref}${text}`;
|
|
3418
3555
|
});
|
|
3419
|
-
if (alts.length > 0) lines.push(
|
|
3556
|
+
if (alts.length > 0) lines.push(chalk6.dim(`alternatives: ${alts.join(", ")}`));
|
|
3420
3557
|
}
|
|
3421
3558
|
}
|
|
3422
3559
|
if (r.action === "posture_set" || r.action === "rotated") {
|
|
3423
3560
|
const state = deviceStateLine(r);
|
|
3424
3561
|
if (state) lines.push(state);
|
|
3425
3562
|
}
|
|
3426
|
-
if (typeof r.note === "string" && r.note) lines.push(
|
|
3563
|
+
if (typeof r.note === "string" && r.note) lines.push(chalk6.dim(r.note));
|
|
3427
3564
|
if (typeof r.message === "string" && r.message) lines.push(r.message);
|
|
3428
3565
|
if (typeof r.warning === "string" && r.warning) lines.push(r.warning);
|
|
3429
3566
|
return lines;
|
|
@@ -3687,15 +3824,21 @@ async function runAgentVerb(cmd, positionals, opts) {
|
|
|
3687
3824
|
}
|
|
3688
3825
|
|
|
3689
3826
|
export {
|
|
3690
|
-
readDevState,
|
|
3691
|
-
clearDevState,
|
|
3692
3827
|
DEV_PLATFORMS,
|
|
3693
3828
|
PLATFORM_NEEDS_METRO,
|
|
3694
3829
|
isDevPlatform,
|
|
3830
|
+
platformWithArticle,
|
|
3695
3831
|
loadDevConfig,
|
|
3696
|
-
|
|
3832
|
+
parseBuildId,
|
|
3833
|
+
checkDevBuild,
|
|
3834
|
+
buildRows,
|
|
3835
|
+
formatBuildRowsHuman,
|
|
3836
|
+
listBuilds,
|
|
3837
|
+
readDevState,
|
|
3838
|
+
clearDevState,
|
|
3697
3839
|
readMetroState,
|
|
3698
3840
|
killOwnedMetro,
|
|
3841
|
+
checkMetro,
|
|
3699
3842
|
buildCommand,
|
|
3700
3843
|
formatDuration,
|
|
3701
3844
|
BUILD_TERMINAL_STATUSES,
|