vibeview 0.37.0 → 0.37.1

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.
@@ -885,7 +885,7 @@ function rokuDefaults(cwd) {
885
885
  }
886
886
  return { buildCommand: ROKU_ZIP_COMMAND, artifact };
887
887
  }
888
- function defaultsFor(cwd, platform) {
888
+ function defaultsFor(cwd, platform, evaluatedIosName) {
889
889
  const flavor = detectFlavor(cwd);
890
890
  if (platform === "roku") return flavor === "roku" ? rokuDefaults(cwd) : {};
891
891
  if (flavor === "unknown" || flavor === "roku") return {};
@@ -900,7 +900,7 @@ function defaultsFor(cwd, platform) {
900
900
  };
901
901
  }
902
902
  if (isManagedExpo(cwd, "ios")) {
903
- const name = managedExpoIosName(cwd);
903
+ const name = managedExpoIosName(cwd, evaluatedIosName);
904
904
  return {
905
905
  buildCommand: name ? managedExpoIosCommand(cwd, name) : void 0,
906
906
  artifact: IOS_ARTIFACT,
@@ -925,10 +925,13 @@ function managedExpoAndroidProductionCommand(cwd) {
925
925
  function expoSanitizedName(name) {
926
926
  return name.replace(/[\W_]+/g, "").normalize("NFD").replace(/[\u0300-\u036f]/g, "");
927
927
  }
928
- function managedExpoIosName(cwd) {
929
- if (existsSync3(join5(cwd, "app.config.js")) || existsSync3(join5(cwd, "app.config.ts"))) {
930
- return null;
931
- }
928
+ var DYNAMIC_EXPO_CONFIGS = ["app.config.ts", "app.config.js", "app.config.mjs", "app.config.cjs"];
929
+ function dynamicExpoConfig(cwd) {
930
+ return DYNAMIC_EXPO_CONFIGS.find((f) => existsSync3(join5(cwd, f)));
931
+ }
932
+ function managedExpoIosName(cwd, evaluatedName) {
933
+ if (evaluatedName !== void 0) return expoSanitizedName(evaluatedName) || null;
934
+ if (dynamicExpoConfig(cwd)) return null;
932
935
  const raw = readJson(cwd, "app.config.json") ?? readJson(cwd, "app.json");
933
936
  if (!raw) return null;
934
937
  const config = "expo" in raw ? raw.expo : raw;
@@ -943,7 +946,10 @@ function iosArchiveExportCommand(ws) {
943
946
  const exportCmd = `xcodebuild -exportArchive -archivePath ${archivePath} -exportPath ios/build -exportOptionsPlist "$VIBEVIEW_EXPORT_OPTIONS_PLIST"`;
944
947
  return `${archiveCmd} && ${exportCmd}`;
945
948
  }
946
- function productionDefaultsFor(cwd, platform) {
949
+ function managedExpoIosProductionCommand(cwd, name) {
950
+ return `${installCommand(cwd)} && npx expo prebuild --platform ios && ` + iosArchiveExportCommand(name);
951
+ }
952
+ function productionDefaultsFor(cwd, platform, evaluatedIosName) {
947
953
  if (platform === "android" || platform === "androidtv") {
948
954
  return {
949
955
  buildCommand: isManagedExpo(cwd, "android") ? managedExpoAndroidProductionCommand(cwd) : androidProductionCommand(cwd),
@@ -955,10 +961,10 @@ function productionDefaultsFor(cwd, platform) {
955
961
  }
956
962
  if (platform === "ios") {
957
963
  if (isManagedExpo(cwd, "ios")) {
958
- const name = managedExpoIosName(cwd);
964
+ const name = managedExpoIosName(cwd, evaluatedIosName);
959
965
  if (!name) return { artifact: IOS_PRODUCTION_ARTIFACT };
960
966
  return {
961
- buildCommand: `${installCommand(cwd)} && npx expo prebuild --platform ios && ` + iosArchiveExportCommand(name),
967
+ buildCommand: managedExpoIosProductionCommand(cwd, name),
962
968
  artifact: IOS_PRODUCTION_ARTIFACT
963
969
  };
964
970
  }
@@ -994,36 +1000,65 @@ function pbxprojText(cwd) {
994
1000
  return null;
995
1001
  }
996
1002
  }
997
- var readExpoConfig = (cwd) => {
1003
+ function parseExpoConfigJson(stdout) {
1004
+ const lines = stdout.trim().split("\n");
1005
+ for (const text of [stdout, lines[lines.length - 1]]) {
1006
+ try {
1007
+ const parsed = JSON.parse(text);
1008
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
1009
+ return parsed;
1010
+ }
1011
+ } catch {
1012
+ }
1013
+ }
1014
+ return null;
1015
+ }
1016
+ var EXPO_CONFIG_TIMEOUT_MS = 3e4;
1017
+ var readExpoConfig = (cwd, timeoutMs = EXPO_CONFIG_TIMEOUT_MS) => {
998
1018
  const cli = join5(cwd, "node_modules", "expo", "bin", "cli");
999
1019
  if (!existsSync3(cli)) {
1000
- return Promise.reject(new Error("Expo is not installed in this project (node_modules/expo)"));
1020
+ return Promise.reject(
1021
+ new Error("Expo isn't installed in this project yet (run your install command first)")
1022
+ );
1001
1023
  }
1002
1024
  return new Promise((resolve, reject) => {
1003
- execFile(
1025
+ let settled = false;
1026
+ const settle = (fn) => {
1027
+ if (settled) return;
1028
+ settled = true;
1029
+ clearTimeout(timer);
1030
+ fn();
1031
+ };
1032
+ const child = execFile(
1004
1033
  process.execPath,
1005
1034
  [cli, "config", "--json", "--type", "public"],
1006
1035
  {
1007
1036
  cwd,
1008
- timeout: 3e4,
1009
1037
  maxBuffer: 8 * 1024 * 1024,
1010
1038
  env: { ...process.env, EXPO_NO_TELEMETRY: "1", CI: "1" }
1011
1039
  },
1012
1040
  (err, stdout, stderr) => {
1013
1041
  if (err) {
1014
1042
  const why = String(stderr || err.message).split("\n").map((l) => l.trim()).find((l) => l.length > 0);
1015
- reject(new Error(`expo config failed${why ? `: ${why}` : ""}`));
1043
+ settle(() => reject(new Error(`expo config failed${why ? `: ${why}` : ""}`)));
1016
1044
  return;
1017
1045
  }
1018
- try {
1019
- const parsed = JSON.parse(stdout);
1020
- if (parsed && typeof parsed === "object") resolve(parsed);
1021
- else reject(new Error("expo config printed no config"));
1022
- } catch {
1023
- reject(new Error("expo config printed something that is not JSON"));
1024
- }
1046
+ const parsed = parseExpoConfigJson(stdout);
1047
+ settle(
1048
+ () => parsed ? resolve(parsed) : reject(new Error("expo config printed something that is not JSON"))
1049
+ );
1025
1050
  }
1026
1051
  );
1052
+ child.stdin?.end();
1053
+ const timer = setTimeout(() => {
1054
+ child.kill("SIGKILL");
1055
+ child.stdout?.destroy();
1056
+ child.stderr?.destroy();
1057
+ settle(
1058
+ () => reject(new Error(`expo config timed out after ${Math.round(timeoutMs / 1e3)} s`))
1059
+ );
1060
+ }, timeoutMs);
1061
+ timer.unref();
1027
1062
  });
1028
1063
  };
1029
1064
  async function localAppIdentity(cwd, platform, readConfig = readExpoConfig) {
@@ -1034,9 +1069,7 @@ async function localAppIdentity(cwd, platform, readConfig = readExpoConfig) {
1034
1069
  let version = null;
1035
1070
  let bundleId = null;
1036
1071
  let versionUnreadable = null;
1037
- const dynamicName = ["app.config.ts", "app.config.js", "app.config.mjs", "app.config.cjs"].find(
1038
- (f) => existsSync3(join5(cwd, f))
1039
- );
1072
+ const dynamicName = dynamicExpoConfig(cwd);
1040
1073
  const staticRaw = readJson(cwd, "app.config.json") ?? readJson(cwd, "app.json");
1041
1074
  const isExpo = Boolean(dynamicName) || staticRaw !== null && "expo" in staticRaw || existsSync3(join5(cwd, "node_modules", "expo", "package.json"));
1042
1075
  let expo = null;
@@ -1075,6 +1108,41 @@ async function localAppIdentity(cwd, platform, readConfig = readExpoConfig) {
1075
1108
  }
1076
1109
  return { reactNative, version, bundleId, versionUnreadable: version ? null : versionUnreadable };
1077
1110
  }
1111
+ var evaluatedNameCache = /* @__PURE__ */ new Map();
1112
+ function evaluatedExpoName(cwd, readConfig, onEvaluate) {
1113
+ const cached = evaluatedNameCache.get(cwd);
1114
+ if (cached) return cached;
1115
+ onEvaluate?.();
1116
+ const pending = Promise.resolve().then(() => readConfig(cwd)).then(
1117
+ (config) => typeof config.name === "string" ? { name: config.name } : { error: "the resolved config has no name" },
1118
+ (err) => ({
1119
+ error: err instanceof Error ? err.message : String(err)
1120
+ })
1121
+ );
1122
+ evaluatedNameCache.set(cwd, pending);
1123
+ return pending;
1124
+ }
1125
+ async function resolvedDefaultsFor(cwd, platform, opts = {}) {
1126
+ const { production = false, readConfig = (c) => readExpoConfig(c), onEvaluate } = opts;
1127
+ const base = production ? productionDefaultsFor : defaultsFor;
1128
+ const dynamic = dynamicExpoConfig(cwd);
1129
+ if (platform !== "ios" || !dynamic || !isManagedExpo(cwd, "ios")) {
1130
+ return base(cwd, platform);
1131
+ }
1132
+ const evaluated = await evaluatedExpoName(cwd, readConfig, onEvaluate);
1133
+ if ("error" in evaluated) {
1134
+ return { ...base(cwd, platform), evaluatedConfig: dynamic, evaluationError: evaluated.error };
1135
+ }
1136
+ const named = base(cwd, platform, evaluated.name);
1137
+ if (!named.buildCommand) {
1138
+ return {
1139
+ ...named,
1140
+ evaluatedConfig: dynamic,
1141
+ evaluationError: `the name "${evaluated.name}" has no letters or digits`
1142
+ };
1143
+ }
1144
+ return { ...named, evaluatedConfig: dynamic };
1145
+ }
1078
1146
 
1079
1147
  // src/lib/dev-setup.ts
1080
1148
  function platformChoices(cwd) {
@@ -1109,6 +1177,37 @@ function fillLaterHint(skipped, platform) {
1109
1177
  }
1110
1178
  return lines.join("\n");
1111
1179
  }
1180
+ async function promptDefaults(cwd, platform, opts = {}) {
1181
+ const progress = {};
1182
+ try {
1183
+ return await resolvedDefaultsFor(cwd, platform, {
1184
+ ...opts,
1185
+ onEvaluate: () => {
1186
+ progress.spinner = createSpinner("Reading Expo config\u2026").start();
1187
+ }
1188
+ });
1189
+ } finally {
1190
+ progress.spinner?.stop();
1191
+ }
1192
+ }
1193
+ function evaluatedConfigNote(cwd, defaults) {
1194
+ const file = defaults.evaluatedConfig;
1195
+ if (!file) return;
1196
+ if (defaults.evaluationError) {
1197
+ console.log(chalk2.dim(` Couldn't read the name from ${file}: ${defaults.evaluationError}`));
1198
+ return;
1199
+ }
1200
+ if (notedConfigDirs.has(cwd)) return;
1201
+ notedConfigDirs.add(cwd);
1202
+ console.log(
1203
+ chalk2.dim(
1204
+ ` The iOS name was read from ${file} using your local environment.
1205
+ If it depends on environment variables, set the same ones for the cloud build
1206
+ (build.env, or build variables).`
1207
+ )
1208
+ );
1209
+ }
1210
+ var notedConfigDirs = /* @__PURE__ */ new Set();
1112
1211
  async function commandSelect(message, detected) {
1113
1212
  const choices = [];
1114
1213
  if (detected) choices.push({ value: "default", name: `Use detected default: ${detected}` });
@@ -1117,7 +1216,8 @@ async function commandSelect(message, detected) {
1117
1216
  return select({ message, choices });
1118
1217
  }
1119
1218
  async function promptBuildConfig(cwd, platform) {
1120
- const defaults = defaultsFor(cwd, platform);
1219
+ const defaults = await promptDefaults(cwd, platform);
1220
+ evaluatedConfigNote(cwd, defaults);
1121
1221
  const choice = await commandSelect(`Build command for ${platform}`, defaults.buildCommand);
1122
1222
  if (choice === "skip") return null;
1123
1223
  if (choice === "default") {
@@ -1137,7 +1237,8 @@ async function promptBuildConfig(cwd, platform) {
1137
1237
  return { command, artifact };
1138
1238
  }
1139
1239
  async function promptProductionBuildConfig(cwd, platform) {
1140
- const defaults = productionDefaultsFor(cwd, platform);
1240
+ const defaults = await promptDefaults(cwd, platform, { production: true });
1241
+ evaluatedConfigNote(cwd, defaults);
1141
1242
  const choice = await commandSelect(
1142
1243
  `Production build command for ${platform}`,
1143
1244
  defaults.buildCommand
package/dist/cli.js CHANGED
@@ -25,7 +25,7 @@ import {
25
25
  readMetroState,
26
26
  recordDevUse,
27
27
  runAgentVerb
28
- } from "./chunk-6LNOXEPC.js";
28
+ } from "./chunk-NJ2IXB5H.js";
29
29
  import {
30
30
  ApiClient,
31
31
  ApiStatusError,
@@ -4147,7 +4147,7 @@ program.command("dev-reload").description(
4147
4147
  program.command("mcp").description(
4148
4148
  "Run an MCP stdio server exposing the agent verbs (tap, swipe, type, ...) plus dev_start/dev_stop/dev_reload as tools"
4149
4149
  ).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").action(async (opts) => {
4150
- const { runMcpServer } = await import("./server-QRIH34XN.js");
4150
+ const { runMcpServer } = await import("./server-DF3CDWL4.js");
4151
4151
  await runMcpServer({ ...opts, version: pkg.version });
4152
4152
  });
4153
4153
  program.command("agent-setup").description(
@@ -28,7 +28,7 @@ import {
28
28
  renderEnvelope,
29
29
  resolveMetro,
30
30
  startDevSession
31
- } from "./chunk-6LNOXEPC.js";
31
+ } from "./chunk-NJ2IXB5H.js";
32
32
  import {
33
33
  ApiClient,
34
34
  NotFoundError,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibeview",
3
- "version": "0.37.0",
3
+ "version": "0.37.1",
4
4
  "description": "VibeView CLI -- run your app on cloud iOS simulators, Android emulators, Apple TV, Android TV and Roku 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",