vibeview 0.25.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.
|
@@ -8,163 +8,9 @@ import {
|
|
|
8
8
|
uploadAppBuild
|
|
9
9
|
} from "./chunk-RFFCCPGZ.js";
|
|
10
10
|
|
|
11
|
-
// src/lib/dev-build.ts
|
|
12
|
-
function parseBuildId(raw) {
|
|
13
|
-
const text = typeof raw === "number" ? String(raw) : typeof raw === "string" ? raw.trim() : "";
|
|
14
|
-
if (!/^\d+$/.test(text) || Number(text) < 1) {
|
|
15
|
-
throw new Error(
|
|
16
|
-
`--build expects a build id (a positive number, see \`vibeview list-builds <app>\`), got "${String(raw)}"`
|
|
17
|
-
);
|
|
18
|
-
}
|
|
19
|
-
return Number(text);
|
|
20
|
-
}
|
|
21
|
-
function buildKind(isDebug) {
|
|
22
|
-
if (isDebug === true) return "debug";
|
|
23
|
-
if (isDebug === false) return "release";
|
|
24
|
-
return "unknown";
|
|
25
|
-
}
|
|
26
|
-
function checkDevBuild(app, buildId, platform) {
|
|
27
|
-
if (platform === "roku") {
|
|
28
|
-
throw new Error(
|
|
29
|
-
"a build id cannot be pinned on roku \u2014 `vibeview dev` packages and sideloads the channel itself"
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
if (app.platform !== platform) {
|
|
33
|
-
throw new Error(`app ${app.public_id} is a ${app.platform} app, not ${platform}`);
|
|
34
|
-
}
|
|
35
|
-
const build = app.builds.find((b) => b.id === buildId);
|
|
36
|
-
if (!build) {
|
|
37
|
-
throw new Error(
|
|
38
|
-
`build ${buildId} is not a build of app ${app.public_id} \u2014 see \`vibeview list-builds ${app.public_id}\``
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
|
-
if (build.is_debug !== true) {
|
|
42
|
-
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";
|
|
43
|
-
throw new Error(
|
|
44
|
-
`build ${buildId} is not a debug build \u2014 ${why}. \`vibeview dev\` needs a ${platform} debug simulator/emulator build without an embedded JS bundle (see \`vibeview list-builds ${app.public_id}\`)`
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
return build;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// src/commands/list-builds.ts
|
|
51
|
-
import chalk from "chalk";
|
|
52
|
-
function buildRows(app) {
|
|
53
|
-
return app.builds.map((b) => ({
|
|
54
|
-
id: b.id,
|
|
55
|
-
version: b.version ?? null,
|
|
56
|
-
build_number: b.build_number ?? null,
|
|
57
|
-
platform: app.platform,
|
|
58
|
-
kind: buildKind(b.is_debug),
|
|
59
|
-
is_debug: b.is_debug ?? null,
|
|
60
|
-
uploaded_at: b.uploaded_at ?? null,
|
|
61
|
-
note: b.note ?? null
|
|
62
|
-
}));
|
|
63
|
-
}
|
|
64
|
-
var HEADERS = ["ID", "VERSION", "PLATFORM", "KIND", "UPLOADED", "NOTE"];
|
|
65
|
-
function formatBuildRowsHuman(app, rows) {
|
|
66
|
-
if (rows.length === 0) {
|
|
67
|
-
return `No builds uploaded for ${app.name} (${app.public_id}) \u2014 upload one with \`vibeview upload-app\`.
|
|
68
|
-
`;
|
|
69
|
-
}
|
|
70
|
-
const cells = rows.map((r) => [
|
|
71
|
-
String(r.id),
|
|
72
|
-
r.version ? `${r.version}${r.build_number ? ` (${r.build_number})` : ""}` : r.build_number ?? "",
|
|
73
|
-
r.platform,
|
|
74
|
-
r.kind,
|
|
75
|
-
r.uploaded_at ?? "",
|
|
76
|
-
(r.note ?? "").split("\n")[0]
|
|
77
|
-
]);
|
|
78
|
-
const widths = HEADERS.map((h, i) => Math.max(h.length, ...cells.map((c) => c[i].length)));
|
|
79
|
-
const pad = (row) => row.map((c, i) => c.padEnd(widths[i])).join(" ").trimEnd();
|
|
80
|
-
const lines = [chalk.bold(pad(HEADERS)), ...cells.map(pad)];
|
|
81
|
-
return lines.join("\n") + "\n";
|
|
82
|
-
}
|
|
83
|
-
async function listBuilds(appId, opts, deps) {
|
|
84
|
-
const { token, baseUrl } = resolveAuth(opts);
|
|
85
|
-
const d = deps ?? { getApp: (id) => new ApiClient(baseUrl, token).getApp(id) };
|
|
86
|
-
try {
|
|
87
|
-
const app = await d.getApp(appId);
|
|
88
|
-
const rows = buildRows(app);
|
|
89
|
-
if (opts.json) {
|
|
90
|
-
console.log(JSON.stringify(rows, null, 2));
|
|
91
|
-
} else {
|
|
92
|
-
process.stdout.write(formatBuildRowsHuman(app, rows));
|
|
93
|
-
}
|
|
94
|
-
process.exitCode = 0;
|
|
95
|
-
} catch (err) {
|
|
96
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
97
|
-
if (opts.json) console.log(JSON.stringify({ event: "error", message }));
|
|
98
|
-
console.error(chalk.red(`error: ${message}`));
|
|
99
|
-
process.exitCode = 1;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// src/lib/dev-state.ts
|
|
104
|
-
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
105
|
-
import { dirname, join } from "path";
|
|
106
|
-
var STATE_DIR = ".vibeview";
|
|
107
|
-
var STATE_FILE = "dev-state.json";
|
|
108
|
-
function statePath(cwd) {
|
|
109
|
-
return join(cwd, STATE_DIR, STATE_FILE);
|
|
110
|
-
}
|
|
111
|
-
function isPidAlive(pid) {
|
|
112
|
-
try {
|
|
113
|
-
process.kill(pid, 0);
|
|
114
|
-
return true;
|
|
115
|
-
} catch (err) {
|
|
116
|
-
return err.code === "EPERM";
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
function ensureGitignored(cwd) {
|
|
120
|
-
try {
|
|
121
|
-
const gitignore = join(cwd, ".gitignore");
|
|
122
|
-
if (!existsSync(gitignore)) return;
|
|
123
|
-
const content = readFileSync(gitignore, "utf-8");
|
|
124
|
-
const covered = content.split("\n").map((l) => l.trim()).some((l) => l === ".vibeview" || l === ".vibeview/" || l === "/.vibeview");
|
|
125
|
-
if (covered) return;
|
|
126
|
-
const prefix = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
|
|
127
|
-
writeFileSync(gitignore, `${content}${prefix}
|
|
128
|
-
# VibeView local session state
|
|
129
|
-
.vibeview/
|
|
130
|
-
`);
|
|
131
|
-
} catch {
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
function writeDevState(cwd, state) {
|
|
135
|
-
const path = statePath(cwd);
|
|
136
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
137
|
-
writeFileSync(path, JSON.stringify(state, null, 2) + "\n");
|
|
138
|
-
ensureGitignored(cwd);
|
|
139
|
-
}
|
|
140
|
-
function readDevState(cwd) {
|
|
141
|
-
let raw;
|
|
142
|
-
try {
|
|
143
|
-
raw = readFileSync(statePath(cwd), "utf-8");
|
|
144
|
-
} catch {
|
|
145
|
-
return null;
|
|
146
|
-
}
|
|
147
|
-
let parsed;
|
|
148
|
-
try {
|
|
149
|
-
parsed = JSON.parse(raw);
|
|
150
|
-
} catch {
|
|
151
|
-
return null;
|
|
152
|
-
}
|
|
153
|
-
if (typeof parsed.pid !== "number" || !isPidAlive(parsed.pid)) {
|
|
154
|
-
return null;
|
|
155
|
-
}
|
|
156
|
-
return parsed;
|
|
157
|
-
}
|
|
158
|
-
function clearDevState(cwd) {
|
|
159
|
-
const path = statePath(cwd);
|
|
160
|
-
if (existsSync(path)) {
|
|
161
|
-
rmSync(path, { force: true });
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
11
|
// src/lib/dev-config.ts
|
|
166
|
-
import { readFileSync
|
|
167
|
-
import { join
|
|
12
|
+
import { readFileSync, writeFileSync } from "fs";
|
|
13
|
+
import { join } from "path";
|
|
168
14
|
var DEV_PLATFORMS = ["ios", "android", "tvos", "androidtv", "roku"];
|
|
169
15
|
var ENV_KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
170
16
|
var PLATFORM_NEEDS_METRO = {
|
|
@@ -178,10 +24,25 @@ var FILE = "vibeview.json";
|
|
|
178
24
|
function isDevPlatform(p) {
|
|
179
25
|
return typeof p === "string" && DEV_PLATFORMS.includes(p);
|
|
180
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
|
+
}
|
|
181
42
|
function loadDevConfig(cwd) {
|
|
182
43
|
let raw;
|
|
183
44
|
try {
|
|
184
|
-
raw =
|
|
45
|
+
raw = readFileSync(join(cwd, FILE), "utf-8");
|
|
185
46
|
} catch {
|
|
186
47
|
return null;
|
|
187
48
|
}
|
|
@@ -298,7 +159,7 @@ function validateBuildEnv(env, platformKey) {
|
|
|
298
159
|
}
|
|
299
160
|
}
|
|
300
161
|
function saveDevConfig(cwd, cfg) {
|
|
301
|
-
|
|
162
|
+
writeFileSync(join(cwd, FILE), JSON.stringify(cfg, null, 2) + "\n");
|
|
302
163
|
}
|
|
303
164
|
function resolveMetro(cfg, platform, portFlag) {
|
|
304
165
|
const entry = cfg?.platforms?.[platform];
|
|
@@ -342,6 +203,162 @@ function buildSavedConfig(input2) {
|
|
|
342
203
|
};
|
|
343
204
|
}
|
|
344
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
|
+
|
|
345
362
|
// src/lib/metro-check.ts
|
|
346
363
|
async function checkMetro(port, timeoutMs = 1500) {
|
|
347
364
|
try {
|
|
@@ -871,7 +888,7 @@ function runTar(args) {
|
|
|
871
888
|
var CLOUD_PLATFORMS = ["ios", "android", "tvos", "androidtv"];
|
|
872
889
|
var PRODUCTION_SIGNING_PLATFORMS = ["ios", "android", "androidtv", "tvos"];
|
|
873
890
|
function productionUnsupportedReason(platform) {
|
|
874
|
-
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(", ")}`;
|
|
875
892
|
}
|
|
876
893
|
function isCloudPlatform(p) {
|
|
877
894
|
return CLOUD_PLATFORMS.includes(p);
|
|
@@ -898,7 +915,7 @@ async function runBuildForPlatform(cwd, platform, build, opts, log, deps = { upl
|
|
|
898
915
|
const { result } = await deps.upload(uploadPath, opts);
|
|
899
916
|
if (result.platform !== platform) {
|
|
900
917
|
throw new Error(
|
|
901
|
-
`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`
|
|
902
919
|
);
|
|
903
920
|
}
|
|
904
921
|
return result;
|
|
@@ -1135,7 +1152,7 @@ function requireCloudCapable(opts, platform) {
|
|
|
1135
1152
|
if (!isCloudPlatform(platform)) {
|
|
1136
1153
|
fail(
|
|
1137
1154
|
opts,
|
|
1138
|
-
`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(", ")}`
|
|
1139
1156
|
);
|
|
1140
1157
|
}
|
|
1141
1158
|
}
|
|
@@ -2393,13 +2410,9 @@ function buildAppStrategy(apps, buildFlag, json, isTTY) {
|
|
|
2393
2410
|
if (json || !isTTY) return "pick";
|
|
2394
2411
|
return "ask";
|
|
2395
2412
|
}
|
|
2396
|
-
function requireNonInteractive(json) {
|
|
2413
|
+
function requireNonInteractive(json, message) {
|
|
2397
2414
|
if (!json) return;
|
|
2398
|
-
emit(json, {
|
|
2399
|
-
event: "error",
|
|
2400
|
-
code: "config_required",
|
|
2401
|
-
message: "pass --platform and run once interactively, or commit vibeview.json"
|
|
2402
|
-
});
|
|
2415
|
+
emit(json, { event: "error", code: "config_required", message });
|
|
2403
2416
|
process.exit(1);
|
|
2404
2417
|
}
|
|
2405
2418
|
async function ensureConfig(api, opts) {
|
|
@@ -2409,7 +2422,10 @@ async function ensureConfig(api, opts) {
|
|
|
2409
2422
|
process.exit(2);
|
|
2410
2423
|
}
|
|
2411
2424
|
if (!opts.platform && !existing?.defaultPlatform) {
|
|
2412
|
-
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
|
+
);
|
|
2413
2429
|
}
|
|
2414
2430
|
const platform = opts.platform || existing?.defaultPlatform || await select3({
|
|
2415
2431
|
message: "Platform to run on",
|
|
@@ -2441,9 +2457,7 @@ async function ensureConfig(api, opts) {
|
|
|
2441
2457
|
let isFirstRun = false;
|
|
2442
2458
|
if (!existing) {
|
|
2443
2459
|
isFirstRun = true;
|
|
2444
|
-
if (opts.json) {
|
|
2445
|
-
requireNonInteractive(opts.json);
|
|
2446
|
-
} else if (process.stdin.isTTY) {
|
|
2460
|
+
if (!opts.json && process.stdin.isTTY) {
|
|
2447
2461
|
firstRunBuild = await promptBuildConfig(process.cwd(), platform);
|
|
2448
2462
|
if (PLATFORM_NEEDS_METRO[platform]) {
|
|
2449
2463
|
firstRunMetro = await promptMetroCommand(process.cwd(), platform);
|
|
@@ -2524,7 +2538,10 @@ Run \`vibeview dev\` without --app to pick from the list.`
|
|
|
2524
2538
|
emit(opts.json, { event: "error", code: "app_deleted", message });
|
|
2525
2539
|
process.exit(1);
|
|
2526
2540
|
}
|
|
2527
|
-
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
|
+
);
|
|
2528
2545
|
if (!process.stdin.isTTY) {
|
|
2529
2546
|
console.error(
|
|
2530
2547
|
chalk4.red(
|
|
@@ -2559,6 +2576,7 @@ Run \`vibeview dev\` without --app to pick from the list.`
|
|
|
2559
2576
|
});
|
|
2560
2577
|
saveDevConfig(process.cwd(), cfg);
|
|
2561
2578
|
if (isFirstRun) {
|
|
2579
|
+
console.error(chalk4.dim(`Created vibeview.json (platform: ${platform}, app: ${appId}).`));
|
|
2562
2580
|
const hint = fillLaterHint(
|
|
2563
2581
|
{ build: !firstRunBuild, metro: PLATFORM_NEEDS_METRO[platform] && !firstRunMetro },
|
|
2564
2582
|
platform
|
|
@@ -2708,7 +2726,7 @@ async function runDevInProcess(opts) {
|
|
|
2708
2726
|
}
|
|
2709
2727
|
console.error(
|
|
2710
2728
|
chalk4.dim(
|
|
2711
|
-
`Starting
|
|
2729
|
+
`Starting ${platformWithArticle(cfg.platform)} dev session for ${cfg.appId}${pinnedBuildId !== void 0 ? ` (build ${pinnedBuildId})` : ""}...`
|
|
2712
2730
|
)
|
|
2713
2731
|
);
|
|
2714
2732
|
let stopping = false;
|
|
@@ -3360,7 +3378,7 @@ var AGENT_COMMANDS = [
|
|
|
3360
3378
|
// Foldable devices only.
|
|
3361
3379
|
verb: "set-posture",
|
|
3362
3380
|
tool: "set_posture",
|
|
3363
|
-
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.",
|
|
3364
3382
|
mutating: true,
|
|
3365
3383
|
params: [
|
|
3366
3384
|
{
|
|
@@ -3386,7 +3404,7 @@ var AGENT_COMMANDS = [
|
|
|
3386
3404
|
// portrait <-> landscape and accepts only 90. Refused on TV devices.
|
|
3387
3405
|
verb: "rotate",
|
|
3388
3406
|
tool: "rotate",
|
|
3389
|
-
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.",
|
|
3390
3408
|
mutating: true,
|
|
3391
3409
|
params: [
|
|
3392
3410
|
{
|
|
@@ -3394,7 +3412,7 @@ var AGENT_COMMANDS = [
|
|
|
3394
3412
|
cli: "--degrees",
|
|
3395
3413
|
type: "number",
|
|
3396
3414
|
required: false,
|
|
3397
|
-
description: "
|
|
3415
|
+
description: "iPhone Duo only: 90 (default, a quarter turn clockwise), 180, or 270 (a quarter turn back). Other devices accept only 90."
|
|
3398
3416
|
}
|
|
3399
3417
|
]
|
|
3400
3418
|
},
|
|
@@ -3806,6 +3824,11 @@ async function runAgentVerb(cmd, positionals, opts) {
|
|
|
3806
3824
|
}
|
|
3807
3825
|
|
|
3808
3826
|
export {
|
|
3827
|
+
DEV_PLATFORMS,
|
|
3828
|
+
PLATFORM_NEEDS_METRO,
|
|
3829
|
+
isDevPlatform,
|
|
3830
|
+
platformWithArticle,
|
|
3831
|
+
loadDevConfig,
|
|
3809
3832
|
parseBuildId,
|
|
3810
3833
|
checkDevBuild,
|
|
3811
3834
|
buildRows,
|
|
@@ -3815,10 +3838,6 @@ export {
|
|
|
3815
3838
|
clearDevState,
|
|
3816
3839
|
readMetroState,
|
|
3817
3840
|
killOwnedMetro,
|
|
3818
|
-
DEV_PLATFORMS,
|
|
3819
|
-
PLATFORM_NEEDS_METRO,
|
|
3820
|
-
isDevPlatform,
|
|
3821
|
-
loadDevConfig,
|
|
3822
3841
|
checkMetro,
|
|
3823
3842
|
buildCommand,
|
|
3824
3843
|
formatDuration,
|
package/dist/cli.js
CHANGED
|
@@ -15,12 +15,13 @@ import {
|
|
|
15
15
|
listBuilds,
|
|
16
16
|
loadDevConfig,
|
|
17
17
|
newTransitionState,
|
|
18
|
+
platformWithArticle,
|
|
18
19
|
printOutcomeLine,
|
|
19
20
|
printTransition,
|
|
20
21
|
readDevState,
|
|
21
22
|
readMetroState,
|
|
22
23
|
runAgentVerb
|
|
23
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-JK3T3AMW.js";
|
|
24
25
|
import {
|
|
25
26
|
ApiClient,
|
|
26
27
|
EXIT_INFRA_ERROR,
|
|
@@ -1217,7 +1218,7 @@ async function devReload(opts) {
|
|
|
1217
1218
|
fail(
|
|
1218
1219
|
opts,
|
|
1219
1220
|
"not_roku",
|
|
1220
|
-
`dev-reload is for Roku channels;
|
|
1221
|
+
`dev-reload is for Roku channels; ${platformWithArticle(state.platform)} session hot-reloads through Metro on its own.`,
|
|
1221
1222
|
2
|
|
1222
1223
|
);
|
|
1223
1224
|
}
|
|
@@ -3292,7 +3293,7 @@ program.command("dev").description(
|
|
|
3292
3293
|
parseDeviceCategory
|
|
3293
3294
|
).option(
|
|
3294
3295
|
"--model <name>",
|
|
3295
|
-
'Run on this exact device model, e.g. "iPhone Duo" (see `vibeview list-devices --models`)'
|
|
3296
|
+
'Run on this exact device model, e.g. "iPhone Duo" or "Pixel 9 Pro Fold" (see `vibeview list-devices --models`)'
|
|
3296
3297
|
).option("--no-open", "Do not open the browser automatically").option("--debug", "Log raw tunnel lifecycle events (close codes, reconnects) for diagnostics").option("--json", "Emit line-delimited JSON events on stdout instead of human output", false).option(
|
|
3297
3298
|
"--detach",
|
|
3298
3299
|
"Run the dev session in the background; prints session info and exits (see `dev-status`, `dev-stop`, and `dev-reload` for Roku)",
|
|
@@ -3444,7 +3445,7 @@ program.command("dev-reload").description(
|
|
|
3444
3445
|
program.command("mcp").description(
|
|
3445
3446
|
"Run an MCP stdio server exposing the agent verbs (tap, swipe, type, ...) plus dev_start/dev_stop/dev_reload as tools"
|
|
3446
3447
|
).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").action(async (opts) => {
|
|
3447
|
-
const { runMcpServer } = await import("./server-
|
|
3448
|
+
const { runMcpServer } = await import("./server-KYDTXPRZ.js");
|
|
3448
3449
|
await runMcpServer(opts);
|
|
3449
3450
|
});
|
|
3450
3451
|
program.command("agent-setup").description(
|
|
@@ -17,10 +17,11 @@ import {
|
|
|
17
17
|
isDevPlatform,
|
|
18
18
|
loadDevConfig,
|
|
19
19
|
parseBuildId,
|
|
20
|
+
platformWithArticle,
|
|
20
21
|
readDevState,
|
|
21
22
|
renderEnvelope,
|
|
22
23
|
startDevSession
|
|
23
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-JK3T3AMW.js";
|
|
24
25
|
import {
|
|
25
26
|
ApiClient,
|
|
26
27
|
resolveAuth
|
|
@@ -87,7 +88,7 @@ var DEV_START_DEF = {
|
|
|
87
88
|
},
|
|
88
89
|
model: {
|
|
89
90
|
type: "string",
|
|
90
|
-
description: 'Run on this exact device model, e.g. "iPhone Duo" for
|
|
91
|
+
description: 'Run on this exact device model, e.g. "iPhone Duo" or "Pixel 9 Pro Fold" for a foldable (any model list_device_models marks form_factor "foldable"). Names come from list_device_models. Omit for a default phone (or TV on TV platforms).'
|
|
91
92
|
},
|
|
92
93
|
build_id: {
|
|
93
94
|
type: "number",
|
|
@@ -116,7 +117,7 @@ var STOP_SESSION_DEF = {
|
|
|
116
117
|
};
|
|
117
118
|
var LIST_DEVICE_MODELS_DEF = {
|
|
118
119
|
name: "list_device_models",
|
|
119
|
-
description: 'List the device models you can start right now (model, OS version, platform, category, and form_factor "foldable" for
|
|
120
|
+
description: 'List the device models you can start right now (model, OS version, platform, category, and form_factor "foldable" for any device with a hinge \u2014 the iPhone Duo, Android foldables such as the Pixel 9 Pro Fold). Pass a model name to dev_start to run on it.',
|
|
120
121
|
mutating: false,
|
|
121
122
|
properties: {
|
|
122
123
|
device_type: {
|
|
@@ -535,7 +536,7 @@ function createDevSessionController(auth, webUrl) {
|
|
|
535
536
|
}
|
|
536
537
|
if (target.platform !== "roku") {
|
|
537
538
|
throw new Error(
|
|
538
|
-
`dev_reload is for Roku channels;
|
|
539
|
+
`dev_reload is for Roku channels; ${platformWithArticle(target.platform)} session hot-reloads through Metro on its own`
|
|
539
540
|
);
|
|
540
541
|
}
|
|
541
542
|
if (!target.build) {
|
package/package.json
CHANGED
|
@@ -38,10 +38,17 @@ https://vibeview.io/docs/preparing-your-build.
|
|
|
38
38
|
```
|
|
39
39
|
2. Start Metro in the React Native project (`yarn start` or equivalent) and
|
|
40
40
|
leave it running.
|
|
41
|
-
3. Start a dev session in the background and get structured output
|
|
41
|
+
3. Start a dev session in the background and get structured output. No
|
|
42
|
+
`vibeview.json` yet in this project (first run here)? Pass `--platform`
|
|
43
|
+
and `--app` explicitly — non-interactive runs never prompt, so both must
|
|
44
|
+
be given up front (`--app` is `app_id` from step 1's upload output):
|
|
42
45
|
```bash
|
|
43
|
-
vibeview dev --detach --json
|
|
46
|
+
vibeview dev --platform ios --app <app_id> --detach --json
|
|
44
47
|
```
|
|
48
|
+
Once `vibeview.json` exists (this run or an earlier one), a bare
|
|
49
|
+
`vibeview dev --detach --json` reruns the same platform/app. A
|
|
50
|
+
`{"event":"error","code":"config_required",...}` line names exactly what's
|
|
51
|
+
still missing (usually `--platform` or `--app`) — pass it and retry.
|
|
45
52
|
4. Parse the `session_ready` line from stdout:
|
|
46
53
|
```json
|
|
47
54
|
{"event":"session_ready","session_id":"...","url":"...","pid":1234}
|
|
@@ -156,20 +163,29 @@ it rather than letting it appear unexplained in their `git status`.
|
|
|
156
163
|
(`dpad_up`/`dpad_down`/`dpad_left`/`dpad_right`/`dpad_center`), or move
|
|
157
164
|
focus directly with `tap-focused` (moves focus AND activates) / `focus`
|
|
158
165
|
(moves focus only, no activation).
|
|
159
|
-
- **
|
|
160
|
-
`
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
166
|
+
- **Foldables**: any model `vibeview list-devices --models` (MCP:
|
|
167
|
+
`list_device_models`) marks form factor `foldable` — the iPhone Duo,
|
|
168
|
+
Android foldables such as the Pixel 9 Pro Fold. Start on one by its exact
|
|
169
|
+
model name from that list, e.g.
|
|
170
|
+
`vibeview dev --detach --json --model "iPhone Duo"` or
|
|
171
|
+
`vibeview dev --platform android --detach --json --model "Pixel 9 Pro Fold"`
|
|
172
|
+
(MCP: `dev_start` with `model`). It starts closed, on the cover screen. Fold with a preset
|
|
173
|
+
or an exact hinge angle, and rotate:
|
|
164
174
|
```bash
|
|
165
175
|
vibeview set-posture open --session <id> # closed | partial | open
|
|
166
176
|
vibeview set-posture --angle 75 --session <id> # 0 (shut) to 180 (flat)
|
|
167
|
-
vibeview rotate --session <id> # quarter turn clockwise
|
|
168
|
-
vibeview rotate --degrees 270 --session <id> # quarter turn back (180: half)
|
|
177
|
+
vibeview rotate --session <id> # Duo: quarter turn clockwise
|
|
178
|
+
vibeview rotate --degrees 270 --session <id> # Duo: quarter turn back (180: half)
|
|
169
179
|
```
|
|
170
180
|
Each response says where the device ended up — posture, angle and which
|
|
171
|
-
screen is lit (`cover` or `inner`; the device decides — a small
|
|
172
|
-
keep the inner screen lit on the way down), or the new
|
|
181
|
+
screen is lit (`cover` or `inner`; the device decides — on the Duo a small
|
|
182
|
+
angle can keep the inner screen lit on the way down), or the new
|
|
183
|
+
orientation. The posture names come from each device's own angle ranges,
|
|
184
|
+
so trust the reported `posture` over the angle you asked for: the Duo
|
|
185
|
+
reads 120-169 as `partial`, 170 up as `open`, and 1-119 by direction of
|
|
186
|
+
travel; an Android foldable uses its own ranges (the Pixel 9 Pro Fold: below
|
|
187
|
+
30 `closed`, 30-149 `partial`, 150 up `open`) and its inner screen whenever
|
|
188
|
+
it is not closed. An Android foldable rotates like a phone. Folding
|
|
173
189
|
switches screens of different sizes and rotating turns the picture, so run
|
|
174
190
|
`ui-tree` afterwards — old refs are stale. On other phones and tablets
|
|
175
191
|
`rotate` toggles portrait/landscape (only 90 is accepted); TV devices don't
|
|
@@ -243,8 +259,8 @@ human-readable text.
|
|
|
243
259
|
| `clear-text` | — | Clear the text in the currently focused input field. |
|
|
244
260
|
| `press` | `<button>` | Press a device button or perform a system gesture (home, back, d-pad, etc). |
|
|
245
261
|
| `open-url` | `<url>` | Open a deep link or URL in the app under test. |
|
|
246
|
-
| `set-posture` | `<closed\|partial\|open>` or `--angle <0-180>` | Fold or unfold a foldable device (iPhone Duo) to a preset or an exact hinge angle — exactly one. Reports posture, angle and lit screen. Errors on a device without a hinge. |
|
|
247
|
-
| `rotate` | `[--degrees <90\|180\|270>]` | Rotate the device and report the new orientation. Phones/tablets toggle portrait/landscape (90 only);
|
|
262
|
+
| `set-posture` | `<closed\|partial\|open>` or `--angle <0-180>` | Fold or unfold a foldable device (form factor `foldable`: the iPhone Duo, Android foldables) to a preset or an exact hinge angle — exactly one. Reports posture, angle and lit screen. Errors on a device without a hinge. |
|
|
263
|
+
| `rotate` | `[--degrees <90\|180\|270>]` | Rotate the device and report the new orientation. Phones/tablets and Android foldables toggle portrait/landscape (90 only); the iPhone Duo turns a quarter clockwise by default. Not on TV. |
|
|
248
264
|
| `wait` | `[--ms <n>]` | Wait for a specified number of seconds before continuing (default: 2s). |
|
|
249
265
|
| `find` | `<text>` `[--below <text>] [--above <text>] [--near <text>]` | Find an element by text with optional spatial constraints. |
|
|
250
266
|
| `tap-focused` | `<ref>` | TV: move focus to an element and press SELECT in one step. |
|