vibeview 0.28.1 → 0.29.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.
@@ -16,7 +16,7 @@ import {
16
16
  } from "./chunk-PBMB6TG4.js";
17
17
 
18
18
  // src/lib/dev-config.ts
19
- import { readFileSync, writeFileSync } from "fs";
19
+ import { readFileSync, renameSync, rmSync, writeFileSync } from "fs";
20
20
  import { join } from "path";
21
21
  var DEV_PLATFORMS = ["ios", "android", "tvos", "androidtv", "roku"];
22
22
  var ENV_KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
@@ -166,7 +166,58 @@ function validateBuildEnv(env, platformKey) {
166
166
  }
167
167
  }
168
168
  function saveDevConfig(cwd, cfg) {
169
- writeFileSync(join(cwd, FILE), JSON.stringify(cfg, null, 2) + "\n");
169
+ const target = join(cwd, FILE);
170
+ const tmp = join(cwd, `.${FILE}.${process.pid}.${Date.now()}.tmp`);
171
+ try {
172
+ writeFileSync(tmp, JSON.stringify(cfg, null, 2) + "\n");
173
+ renameSync(tmp, target);
174
+ } catch (err) {
175
+ rmSync(tmp, { force: true });
176
+ throw err;
177
+ }
178
+ }
179
+ function updateDevConfig(cwd, platform, apply) {
180
+ const fresh = loadDevConfig(cwd) ?? { defaultPlatform: platform, platforms: {} };
181
+ if (apply(fresh) === false) return fresh;
182
+ saveDevConfig(cwd, fresh);
183
+ return fresh;
184
+ }
185
+ function configLeaves(value, path = [], out = []) {
186
+ if (value && typeof value === "object" && !Array.isArray(value)) {
187
+ for (const [k, v] of Object.entries(value)) {
188
+ configLeaves(v, [...path, k], out);
189
+ }
190
+ } else if (value !== void 0) {
191
+ out.push({ path, value });
192
+ }
193
+ return out;
194
+ }
195
+ function applyConfigChanges(target, before, after) {
196
+ const key = (path) => JSON.stringify(path);
197
+ const prev = new Map(configLeaves(before ?? {}).map((l) => [key(l.path), l.value]));
198
+ const root = target;
199
+ for (const { path, value } of configLeaves(after)) {
200
+ const old = prev.get(key(path));
201
+ prev.delete(key(path));
202
+ if (old !== void 0 && JSON.stringify(old) === JSON.stringify(value)) continue;
203
+ let node = root;
204
+ for (const segment of path.slice(0, -1)) {
205
+ const child = node[segment];
206
+ if (!child || typeof child !== "object" || Array.isArray(child)) node[segment] = {};
207
+ node = node[segment];
208
+ }
209
+ node[path[path.length - 1]] = value;
210
+ }
211
+ for (const k of prev.keys()) {
212
+ const path = JSON.parse(k);
213
+ let node = root;
214
+ for (const segment of path.slice(0, -1)) {
215
+ node = node && typeof node === "object" ? node[segment] : void 0;
216
+ }
217
+ if (node && typeof node === "object") {
218
+ delete node[path[path.length - 1]];
219
+ }
220
+ }
170
221
  }
171
222
  function describeConfigChanges(before, after) {
172
223
  const flat = (value, prefix, out) => {
@@ -220,6 +271,10 @@ function buildSavedConfig(input2) {
220
271
  platforms: {
221
272
  ...existing?.platforms,
222
273
  [platform]: {
274
+ // Everything else on the entry (`submit` defaults, keys a newer CLI
275
+ // wrote) is carried forward untouched; the fields below are the ones
276
+ // `vibeview dev` owns.
277
+ ...existing?.platforms?.[platform],
223
278
  appId,
224
279
  deviceCategory: isTv ? void 0 : deviceCategory,
225
280
  ...existing?.platforms?.[platform]?.build ? { build: { ...existing.platforms[platform].build } } : {},
@@ -235,6 +290,11 @@ function buildSavedConfig(input2) {
235
290
  }
236
291
 
237
292
  // src/lib/dev-build.ts
293
+ function canUseMetro(build) {
294
+ if (build.can_use_metro === true || build.can_use_metro === false) return build.can_use_metro;
295
+ if (build.is_debug === true) return true;
296
+ return build.is_debug === false ? false : null;
297
+ }
238
298
  function parseBuildId(raw) {
239
299
  const text = typeof raw === "number" ? String(raw) : typeof raw === "string" ? raw.trim() : "";
240
300
  if (!/^\d+$/.test(text) || Number(text) < 1) {
@@ -244,9 +304,10 @@ function parseBuildId(raw) {
244
304
  }
245
305
  return Number(text);
246
306
  }
247
- function buildKind(isDebug) {
248
- if (isDebug === true) return "debug";
249
- if (isDebug === false) return "release";
307
+ function buildKind(build) {
308
+ const usable = build !== null && typeof build === "object" ? canUseMetro(build) : canUseMetro({ is_debug: build });
309
+ if (usable === true) return "debug";
310
+ if (usable === false) return "release";
250
311
  return "unknown";
251
312
  }
252
313
  function checkDevBuild(app, buildId, platform, opts = {}) {
@@ -266,10 +327,11 @@ function checkDevBuild(app, buildId, platform, opts = {}) {
266
327
  `build ${buildId} is not a build of app ${app.public_id} \u2014 see \`vibeview list-builds ${app.public_id}\``
267
328
  );
268
329
  }
269
- if (!opts.standalone && build.is_debug !== true) {
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";
330
+ const usable = canUseMetro(build);
331
+ if (!opts.standalone && usable !== true) {
332
+ const why = usable === false ? "it is a release build (or not a React Native build), so it cannot load JavaScript from Metro" : "it could not be identified as a React Native debug build";
271
333
  throw new Error(
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}\`)`
334
+ `build ${buildId} is not a debug build \u2014 ${why}. \`vibeview dev\` needs ${platformWithArticle(platform)} React Native debug simulator/emulator build (see \`vibeview list-builds ${app.public_id}\`); to run this build as it is, with no Metro, use \`vibeview dev --standalone --build ${buildId}\``
273
335
  );
274
336
  }
275
337
  return build;
@@ -283,8 +345,9 @@ function buildRows(app) {
283
345
  version: b.version ?? null,
284
346
  build_number: b.build_number ?? null,
285
347
  platform: app.platform,
286
- kind: buildKind(b.is_debug),
348
+ kind: buildKind(b),
287
349
  is_debug: b.is_debug ?? null,
350
+ can_use_metro: canUseMetro(b),
288
351
  uploaded_at: b.uploaded_at ?? null,
289
352
  note: b.note ?? null
290
353
  }));
@@ -329,7 +392,7 @@ async function listBuilds(appId, opts, deps) {
329
392
  }
330
393
 
331
394
  // src/lib/dev-state.ts
332
- import { existsSync, mkdirSync, readFileSync as readFileSync2, rmSync, writeFileSync as writeFileSync2 } from "fs";
395
+ import { existsSync, mkdirSync, readFileSync as readFileSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
333
396
  import { dirname, join as join2 } from "path";
334
397
  var STATE_DIR = ".vibeview";
335
398
  var STATE_FILE = "dev-state.json";
@@ -399,7 +462,7 @@ function readDevState(cwd) {
399
462
  function clearDevState(cwd) {
400
463
  const path = statePath(cwd);
401
464
  if (existsSync(path)) {
402
- rmSync(path, { force: true });
465
+ rmSync2(path, { force: true });
403
466
  }
404
467
  }
405
468
 
@@ -703,7 +766,7 @@ async function promptMetroCommand(cwd, platform) {
703
766
 
704
767
  // src/lib/source-pack.ts
705
768
  import { execFile, spawn } from "child_process";
706
- import { lstatSync, mkdtempSync, readFileSync as readFileSync4, rmSync as rmSync2, statSync as statSync2, writeFileSync as writeFileSync3 } from "fs";
769
+ import { lstatSync, mkdtempSync, readFileSync as readFileSync4, rmSync as rmSync3, statSync as statSync2, writeFileSync as writeFileSync3 } from "fs";
707
770
  import { tmpdir } from "os";
708
771
  import { join as join4 } from "path";
709
772
  import ignore2 from "ignore";
@@ -801,7 +864,7 @@ async function packSource(projectRoot, files, opts = {}) {
801
864
  );
802
865
  }
803
866
  const tmpDir = mkdtempSync(join4(tmpdir(), "vibeview-source-pack-"));
804
- const cleanup = () => rmSync2(tmpDir, { recursive: true, force: true });
867
+ const cleanup = () => rmSync3(tmpDir, { recursive: true, force: true });
805
868
  try {
806
869
  const supportsNull = await probeTarNullSupport();
807
870
  const tarPath = join4(tmpDir, "source.tgz");
@@ -842,7 +905,7 @@ function probeTarNullSupport() {
842
905
  writeFileSync3(listPath, "");
843
906
  const out = join4(tmp, "probe.tgz");
844
907
  execFile("tar", ["-czf", out, "-C", tmp, "--null", "-T", listPath], (err) => {
845
- rmSync2(tmp, { recursive: true, force: true });
908
+ rmSync3(tmp, { recursive: true, force: true });
846
909
  resolve(!err);
847
910
  });
848
911
  });
@@ -946,30 +1009,28 @@ async function resolveBarePlatform(cwd, cfg, opts) {
946
1009
  }
947
1010
  return platform;
948
1011
  }
949
- function persistPromptedBuild(cwd, cfg, platform, build) {
950
- const next = cfg ?? { defaultPlatform: platform, platforms: {} };
951
- const entry = next.platforms[platform];
952
- if (entry) {
953
- entry.build = build;
954
- } else {
955
- next.platforms[platform] = { build };
956
- }
957
- saveDevConfig(cwd, next);
958
- return next;
1012
+ function persistPromptedBuild(cwd, platform, build) {
1013
+ return updateDevConfig(cwd, platform, (next) => {
1014
+ const entry = next.platforms[platform];
1015
+ if (entry) {
1016
+ entry.build = build;
1017
+ } else {
1018
+ next.platforms[platform] = { build };
1019
+ }
1020
+ });
959
1021
  }
960
- function persistUploadedAppId(cwd, cfg, platform, build, appId) {
961
- const next = cfg ?? { defaultPlatform: platform, platforms: {} };
962
- const entry = next.platforms[platform];
963
- if (!entry) {
964
- next.platforms[platform] = { appId, build };
965
- saveDevConfig(cwd, next);
966
- return;
967
- }
968
- if (entry.appId !== appId || !entry.build) {
1022
+ function persistUploadedAppId(cwd, platform, build, appId) {
1023
+ updateDevConfig(cwd, platform, (next) => {
1024
+ const entry = next.platforms[platform];
1025
+ if (!entry) {
1026
+ next.platforms[platform] = { appId, build };
1027
+ return true;
1028
+ }
1029
+ if (entry.appId === appId && entry.build) return false;
969
1030
  entry.appId = appId;
970
- entry.build = build;
971
- saveDevConfig(cwd, next);
972
- }
1031
+ if (!entry.build) entry.build = build;
1032
+ return true;
1033
+ });
973
1034
  }
974
1035
  async function resolveBuildConfig(cwd, cfgRef, platform, opts) {
975
1036
  const existing = cfgRef.current?.platforms?.[platform]?.build;
@@ -985,11 +1046,15 @@ async function resolveBuildConfig(cwd, cfgRef, platform, opts) {
985
1046
  if (!answered) {
986
1047
  fail(opts, "build config is required to run vibeview build", "config_required");
987
1048
  }
988
- cfgRef.current = persistPromptedBuild(cwd, cfgRef.current, platform, answered);
1049
+ cfgRef.current = persistPromptedBuild(cwd, platform, answered);
989
1050
  return answered;
990
1051
  }
991
- function persistPromptedProductionBuild(cwd, cfg, platform, production) {
992
- const next = cfg ?? { defaultPlatform: platform, platforms: {} };
1052
+ function persistPromptedProductionBuild(cwd, platform, production) {
1053
+ return updateDevConfig(cwd, platform, (next) => {
1054
+ mergeProductionPair(next, platform, production);
1055
+ });
1056
+ }
1057
+ function mergeProductionPair(next, platform, production) {
993
1058
  const existingBuild = next.platforms[platform]?.build;
994
1059
  if (!existingBuild?.command || !existingBuild?.artifact) {
995
1060
  throw new Error(
@@ -997,8 +1062,6 @@ function persistPromptedProductionBuild(cwd, cfg, platform, production) {
997
1062
  );
998
1063
  }
999
1064
  next.platforms[platform].build = { ...existingBuild, ...production };
1000
- saveDevConfig(cwd, next);
1001
- return next;
1002
1065
  }
1003
1066
  async function resolveProductionBuildConfig(cwd, cfgRef, platform, opts) {
1004
1067
  const entry = cfgRef.current?.platforms?.[platform]?.build;
@@ -1016,7 +1079,7 @@ async function resolveProductionBuildConfig(cwd, cfgRef, platform, opts) {
1016
1079
  if (!answered) {
1017
1080
  fail(opts, "a production build config is required for --production", "config_required");
1018
1081
  }
1019
- cfgRef.current = persistPromptedProductionBuild(cwd, cfgRef.current, platform, answered);
1082
+ cfgRef.current = persistPromptedProductionBuild(cwd, platform, answered);
1020
1083
  return { command: answered.productionCommand, artifact: answered.productionArtifact };
1021
1084
  }
1022
1085
  async function buildCommand(opts, cloudDeps) {
@@ -1036,7 +1099,6 @@ async function buildCommand(opts, cloudDeps) {
1036
1099
  }
1037
1100
  const platform = await resolveBarePlatform(cwd, cfgRef.current, opts);
1038
1101
  const build = await resolveBuildConfig(cwd, cfgRef, platform, opts);
1039
- const cfg = cfgRef.current;
1040
1102
  const result = await runBuildForPlatform(
1041
1103
  cwd,
1042
1104
  platform,
@@ -1045,7 +1107,7 @@ async function buildCommand(opts, cloudDeps) {
1045
1107
  (l) => console.error(chalk3.dim(l))
1046
1108
  );
1047
1109
  for (const w of result.warnings ?? []) console.error(chalk3.yellow(`\u26A0 ${w}`));
1048
- persistUploadedAppId(cwd, cfg, platform, build, result.public_id);
1110
+ persistUploadedAppId(cwd, platform, build, result.public_id);
1049
1111
  if (opts.json) {
1050
1112
  console.log(
1051
1113
  JSON.stringify({ app_id: result.public_id, name: result.name, platform: result.platform })
@@ -1148,16 +1210,17 @@ function requireProductionCapable(opts, platform) {
1148
1210
  fail(opts, productionUnsupportedReason(platform));
1149
1211
  }
1150
1212
  }
1151
- function persistPickedAppId(cwd, cfg, platform, appId) {
1152
- const next = cfg ?? { defaultPlatform: platform, platforms: {} };
1153
- const entry = next.platforms[platform];
1154
- if (entry) {
1155
- entry.appId = appId;
1156
- } else {
1157
- next.platforms[platform] = { appId };
1158
- }
1159
- saveDevConfig(cwd, next);
1160
- return next;
1213
+ function persistPickedAppId(cwd, platform, appId, opts = {}) {
1214
+ return updateDevConfig(cwd, platform, (next) => {
1215
+ const entry = next.platforms[platform];
1216
+ if (opts.onlyIfUnset && entry?.appId) return false;
1217
+ if (entry) {
1218
+ entry.appId = appId;
1219
+ } else {
1220
+ next.platforms[platform] = { appId };
1221
+ }
1222
+ return true;
1223
+ });
1161
1224
  }
1162
1225
  var APP_PICKER_SENTINEL = {
1163
1226
  CREATE_NEW_APP: "__vibeview_create_new_app__",
@@ -1225,7 +1288,7 @@ async function resolveCloudAppIds(cwd, cfgRef, platforms, opts, listApps) {
1225
1288
  createFromArtifact.add(platform);
1226
1289
  continue;
1227
1290
  }
1228
- cfgRef.current = persistPickedAppId(cwd, cfgRef.current, platform, appId);
1291
+ cfgRef.current = persistPickedAppId(cwd, platform, appId);
1229
1292
  }
1230
1293
  return createFromArtifact;
1231
1294
  }
@@ -1235,7 +1298,7 @@ function persistResolvedAppIds(cwd, cfgRef, createFromArtifact, finished) {
1235
1298
  const platform = b.platform;
1236
1299
  if (!createFromArtifact.has(platform)) continue;
1237
1300
  if (cfgRef.current?.platforms?.[platform]?.appId) continue;
1238
- cfgRef.current = persistPickedAppId(cwd, cfgRef.current, platform, b.app_public_id);
1301
+ cfgRef.current = persistPickedAppId(cwd, platform, b.app_public_id, { onlyIfUnset: true });
1239
1302
  }
1240
1303
  }
1241
1304
  function describeAllExclusions(configured) {
@@ -1910,7 +1973,7 @@ import { spawn as spawn3 } from "child_process";
1910
1973
  import { createInterface as createInterface2 } from "readline";
1911
1974
 
1912
1975
  // src/lib/metro-state.ts
1913
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync5, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "fs";
1976
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync5, rmSync as rmSync4, writeFileSync as writeFileSync4 } from "fs";
1914
1977
  import { dirname as dirname2, join as join5 } from "path";
1915
1978
  var STATE_DIR2 = ".vibeview";
1916
1979
  var STATE_FILE2 = "metro-state.json";
@@ -1948,7 +2011,7 @@ function readMetroState(cwd) {
1948
2011
  }
1949
2012
  function clearMetroState(cwd) {
1950
2013
  const path = statePath2(cwd);
1951
- if (existsSync3(path)) rmSync3(path, { force: true });
2014
+ if (existsSync3(path)) rmSync4(path, { force: true });
1952
2015
  }
1953
2016
  function killOwnedMetro(cwd) {
1954
2017
  const state = readMetroState(cwd);
@@ -2617,7 +2680,9 @@ ${hint}`);
2617
2680
  firstRunBuild
2618
2681
  });
2619
2682
  const commitConfig = () => {
2620
- saveDevConfig(process.cwd(), cfg);
2683
+ updateDevConfig(process.cwd(), platform, (fresh) => {
2684
+ applyConfigChanges(fresh, existing, cfg);
2685
+ });
2621
2686
  if (existing) {
2622
2687
  const changes = describeConfigChanges(existing, cfg);
2623
2688
  if (changes.length) {
@@ -2657,6 +2722,13 @@ async function runDevInProcess(opts) {
2657
2722
  emit(opts.json, { event: "error", code: "invalid_flag", message });
2658
2723
  process.exit(2);
2659
2724
  }
2725
+ const standalone = opts.standalone === true;
2726
+ if (standalone && opts.platform === "roku") {
2727
+ const message = "--standalone is not for roku \u2014 a Roku dev session has no Metro; it re-packages the channel";
2728
+ console.error(chalk4.red(`error: ${message}`));
2729
+ emit(opts.json, { event: "error", code: "invalid_flag", message });
2730
+ process.exit(2);
2731
+ }
2660
2732
  let pinnedBuildId;
2661
2733
  if (typeof opts.build === "string") {
2662
2734
  try {
@@ -2685,7 +2757,7 @@ async function runDevInProcess(opts) {
2685
2757
  const cfg = await ensureConfig(api, opts);
2686
2758
  if (pinnedBuildId !== void 0) {
2687
2759
  try {
2688
- checkDevBuild(await api.getApp(cfg.appId), pinnedBuildId, cfg.platform);
2760
+ checkDevBuild(await api.getApp(cfg.appId), pinnedBuildId, cfg.platform, { standalone });
2689
2761
  } catch (err) {
2690
2762
  const message = err.message;
2691
2763
  console.error(chalk4.red(`error: ${message}`));
@@ -2721,10 +2793,12 @@ async function runDevInProcess(opts) {
2721
2793
  (l) => console.error(chalk4.dim(l))
2722
2794
  );
2723
2795
  cfg.appId = result.public_id;
2724
- if (reloaded && reloaded.platforms[cfg.platform] && reloaded.platforms[cfg.platform].appId !== result.public_id) {
2725
- reloaded.platforms[cfg.platform].appId = result.public_id;
2726
- saveDevConfig(process.cwd(), reloaded);
2727
- }
2796
+ updateDevConfig(process.cwd(), cfg.platform, (fresh) => {
2797
+ const entry = fresh.platforms[cfg.platform];
2798
+ if (!entry || entry.appId === result.public_id) return false;
2799
+ entry.appId = result.public_id;
2800
+ return true;
2801
+ });
2728
2802
  } catch (err) {
2729
2803
  const message = err.message;
2730
2804
  console.error(chalk4.red(`error: ${message}`));
@@ -2735,7 +2809,14 @@ async function runDevInProcess(opts) {
2735
2809
  let metroOwnedThisRun = false;
2736
2810
  let rokuBuild;
2737
2811
  let watchIgnore = [];
2738
- if (PLATFORM_NEEDS_METRO[cfg.platform]) {
2812
+ if (standalone && cfg.platform === "roku") {
2813
+ const message = "--standalone is not for roku \u2014 a Roku dev session has no Metro";
2814
+ console.error(chalk4.red(`error: ${message}`));
2815
+ emit(opts.json, { event: "error", code: "invalid_flag", message });
2816
+ process.exit(2);
2817
+ }
2818
+ if (standalone) {
2819
+ } else if (PLATFORM_NEEDS_METRO[cfg.platform]) {
2739
2820
  const metroPort = cfg.metroPort ?? 8081;
2740
2821
  let metroResult;
2741
2822
  try {
@@ -2787,7 +2868,7 @@ async function runDevInProcess(opts) {
2787
2868
  }
2788
2869
  console.error(
2789
2870
  chalk4.dim(
2790
- `Starting ${platformWithArticle(cfg.platform)} dev session for ${cfg.appId}${pinnedBuildId !== void 0 ? ` (build ${pinnedBuildId})` : ""}...`
2871
+ `Starting ${platformWithArticle(cfg.platform)} ${standalone ? "standalone session (no Metro)" : "dev session"} for ${cfg.appId}${pinnedBuildId !== void 0 ? ` (build ${pinnedBuildId})` : ""}...`
2791
2872
  )
2792
2873
  );
2793
2874
  let stopping = false;
@@ -2820,6 +2901,7 @@ async function runDevInProcess(opts) {
2820
2901
  deviceCategory: cfg.deviceCategory,
2821
2902
  model: cfg.model,
2822
2903
  buildId: pinnedBuildId,
2904
+ ...standalone ? { standalone } : {},
2823
2905
  metroPort: cfg.metroPort ?? 8081,
2824
2906
  // startDevSession's own browser-open happens before we've written the
2825
2907
  // state file / emitted session_ready — open it ourselves afterward
package/dist/cli.js CHANGED
@@ -22,7 +22,7 @@ import {
22
22
  readDevState,
23
23
  readMetroState,
24
24
  runAgentVerb
25
- } from "./chunk-3DKY443B.js";
25
+ } from "./chunk-T6EQQV6E.js";
26
26
  import {
27
27
  ApiClient,
28
28
  ApiStatusError,
@@ -1785,7 +1785,8 @@ function defaultDeps2(client) {
1785
1785
  getSubmission: client.getSubmission.bind(client),
1786
1786
  getSubmissionLog: client.getSubmissionLog.bind(client),
1787
1787
  listCloudBuilds: client.listCloudBuilds.bind(client),
1788
- checkStorePreflight: client.checkStorePreflight.bind(client)
1788
+ checkStorePreflight: client.checkStorePreflight.bind(client),
1789
+ getApp: client.getApp.bind(client)
1789
1790
  };
1790
1791
  }
1791
1792
  var POLL_INTERVAL_MS2 = 2e3;
@@ -1910,7 +1911,7 @@ async function submitCommand(opts, deps) {
1910
1911
  }
1911
1912
  if (rollout !== void 0 && releaseStatus !== "inProgress") {
1912
1913
  throw new UsageError(
1913
- `a rollout fraction requires release status "inProgress" (a staged rollout) \u2014 got "${releaseStatus ?? "completed"}"`
1914
+ `a rollout fraction requires release status "inProgress" (a staged rollout) \u2014 got "${releaseStatus ?? (track === "production" ? "draft" : "completed")}"`
1914
1915
  );
1915
1916
  }
1916
1917
  if (releaseStatus === "inProgress" && rollout === void 0) {
@@ -1964,12 +1965,19 @@ async function submitCommand(opts, deps) {
1964
1965
  }
1965
1966
  }
1966
1967
  async function checkStore(platform, androidFamily, ascKey2, opts, deps) {
1967
- const identifier = androidFamily ? opts.package : opts.bundleId;
1968
+ const flagged = androidFamily ? opts.package : opts.bundleId;
1969
+ const usage = androidFamily ? `--check needs the package name to look up: --package <name> (e.g. --package io.example.app)` : `--check needs the bundle identifier to look up: --bundle-id <id> (e.g. --bundle-id io.example.app)`;
1970
+ let identifier = flagged;
1968
1971
  if (!identifier) {
1969
- throw new UsageError(
1970
- androidFamily ? `--check needs the package name to look up: --package <name> (e.g. --package io.example.app)` : `--check needs the bundle identifier to look up: --bundle-id <id> (e.g. --bundle-id io.example.app)`
1971
- );
1972
+ try {
1973
+ identifier = await linkedIdentifier(platform, opts, deps);
1974
+ } catch (err) {
1975
+ printError2(opts, err);
1976
+ process.exitCode = errorExitCode(err);
1977
+ return;
1978
+ }
1972
1979
  }
1980
+ if (!identifier) throw new UsageError(usage);
1973
1981
  try {
1974
1982
  const result = await deps.checkStorePreflight({
1975
1983
  platform,
@@ -1994,6 +2002,32 @@ async function checkStore(platform, androidFamily, ascKey2, opts, deps) {
1994
2002
  process.exitCode = errorExitCode(err);
1995
2003
  }
1996
2004
  }
2005
+ async function linkedIdentifier(platform, opts, deps) {
2006
+ if (!deps.getApp) return void 0;
2007
+ let appId;
2008
+ try {
2009
+ appId = loadDevConfig(process.cwd())?.platforms?.[platform]?.appId;
2010
+ } catch {
2011
+ return void 0;
2012
+ }
2013
+ if (!appId) return void 0;
2014
+ let app;
2015
+ try {
2016
+ app = await deps.getApp(appId);
2017
+ } catch (err) {
2018
+ if (err instanceof NotFoundError) {
2019
+ throw new Error(
2020
+ `the app linked in vibeview.json for ${platform} (${appId}) no longer exists \u2014 ` + (platform === "android" || platform === "androidtv" ? "pass --package <name>" : "pass --bundle-id <id>")
2021
+ );
2022
+ }
2023
+ throw err;
2024
+ }
2025
+ if (!app.package_name) return void 0;
2026
+ if (!opts.json) {
2027
+ console.error(chalk14.dim(`Checking ${app.package_name} (${app.name}, linked in vibeview.json)`));
2028
+ }
2029
+ return app.package_name;
2030
+ }
1997
2031
  async function resolveLatestBuild(platform, deps) {
1998
2032
  const { builds: builds2 } = await deps.listCloudBuilds({
1999
2033
  platform,
@@ -3648,11 +3682,15 @@ program.command("dev").description(
3648
3682
  false
3649
3683
  ).option(
3650
3684
  "--build [id]",
3651
- "Bare: build and upload first (uses platforms.<platform>.build from vibeview.json). With an id: run that uploaded debug build instead of the newest one (see `vibeview list-builds <app>`)",
3685
+ "Bare: build and upload first (uses platforms.<platform>.build from vibeview.json). With an id: run that uploaded debug build instead of the newest one (any build with --standalone) (see `vibeview list-builds <app>`)",
3652
3686
  false
3653
3687
  ).option(
3654
3688
  "--watch [seconds]",
3655
3689
  "Roku only: auto-reload after file changes settle (default: 2 seconds of quiet)"
3690
+ ).option(
3691
+ "--standalone",
3692
+ "Run the build as it is, with no Metro and no tunnel (a release, CI or cloud build); without --build <id>, the newest release build. Code changes do not hot-reload",
3693
+ false
3656
3694
  ).action((opts) => dev(opts));
3657
3695
  program.command("build").description("Run your configured build command and upload the result (see vibeview.json)").option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option(
3658
3696
  "--platform <platform>",
@@ -3687,13 +3725,13 @@ program.command("submit").description(
3687
3725
  "ios/tvos only: which stored App Store Connect key to use (default when exactly one \u2014 see `vibeview credentials asc-key add`)"
3688
3726
  ).option(
3689
3727
  "--release-status <status>",
3690
- "android/androidtv only: Play release status \u2014 completed (default), draft, or inProgress"
3728
+ "android/androidtv only: Play release status \u2014 completed, draft, or inProgress (default: draft on the production track, completed on the others)"
3691
3729
  ).option(
3692
3730
  "--rollout <fraction>",
3693
3731
  "android/androidtv only: staged-rollout fraction, 0 < f < 1 (requires --release-status inProgress)"
3694
3732
  ).option(
3695
3733
  "--check",
3696
- "Only check store readiness: does the store know this app with your stored key? Takes --platform plus --bundle-id (ios/tvos) or --package (android/androidtv); no build needed. Exit 0 when ready",
3734
+ "Only check store readiness: does the store know this app with your stored key? Takes --platform plus --bundle-id (ios/tvos) or --package (android/androidtv), defaulting to the app linked in vibeview.json; no build needed. Exit 0 when ready",
3697
3735
  false
3698
3736
  ).option("--bundle-id <id>", "ios/tvos, with --check: the bundle identifier to look up").option("--no-wait", "Print the submission id and exit without waiting for completion").option("--json", "Emit line-delimited JSON events on stdout instead of human output", false).action((opts) => submitCommand(opts));
3699
3737
  var builds = program.command("builds").description("Manage cloud builds (see `vibeview build --cloud`)");
@@ -3793,7 +3831,7 @@ program.command("dev-reload").description(
3793
3831
  program.command("mcp").description(
3794
3832
  "Run an MCP stdio server exposing the agent verbs (tap, swipe, type, ...) plus dev_start/dev_stop/dev_reload as tools"
3795
3833
  ).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").action(async (opts) => {
3796
- const { runMcpServer } = await import("./server-BGQJOVSN.js");
3834
+ const { runMcpServer } = await import("./server-IGY2VPYT.js");
3797
3835
  await runMcpServer({ ...opts, version: pkg.version });
3798
3836
  });
3799
3837
  program.command("agent-setup").description(
@@ -21,7 +21,7 @@ import {
21
21
  readDevState,
22
22
  renderEnvelope,
23
23
  startDevSession
24
- } from "./chunk-3DKY443B.js";
24
+ } from "./chunk-T6EQQV6E.js";
25
25
  import {
26
26
  ApiClient,
27
27
  NotFoundError,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibeview",
3
- "version": "0.28.1",
3
+ "version": "0.29.0",
4
4
  "description": "VibeView CLI -- run your app on cloud iOS simulators, Android emulators, Apple TV, Android TV and Roku (beta) devices: a React Native dev loop, device control for you or your coding agent (CLI or MCP), and tests from CI",
5
5
  "mcpName": "io.github.vibeview/vibeview",
6
6
  "type": "module",