vibeview 0.36.1 → 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.
package/README.md
CHANGED
|
@@ -838,10 +838,24 @@ function iosWorkspaceName(cwd) {
|
|
|
838
838
|
var IOS_ARTIFACT = "ios/build/Build/Products/Debug-iphonesimulator/*.app";
|
|
839
839
|
var ANDROID_ARTIFACT = "android/app/build/outputs/apk/debug/*.apk";
|
|
840
840
|
function installCommand(cwd) {
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
841
|
+
const hit = JS_LOCKFILE_INSTALLS.find(([lockfile]) => existsSync3(join5(cwd, lockfile)));
|
|
842
|
+
return hit ? hit[1] : "npm install";
|
|
843
|
+
}
|
|
844
|
+
var JS_LOCKFILE_INSTALLS = [
|
|
845
|
+
["yarn.lock", "yarn install --immutable"],
|
|
846
|
+
["pnpm-lock.yaml", "pnpm install --frozen-lockfile"],
|
|
847
|
+
["package-lock.json", "npm ci"],
|
|
848
|
+
["npm-shrinkwrap.json", "npm ci"],
|
|
849
|
+
["bun.lock", "npm install"],
|
|
850
|
+
["bun.lockb", "npm install"]
|
|
851
|
+
];
|
|
852
|
+
function bareInstallPrefix(cwd, platform) {
|
|
853
|
+
const legs = [];
|
|
854
|
+
if (isFile(join5(cwd, "package.json"))) legs.push(installCommand(cwd));
|
|
855
|
+
if (platform === "ios" && isFile(join5(cwd, "ios", "Podfile"))) {
|
|
856
|
+
legs.push("cd ios && pod install && cd ..");
|
|
857
|
+
}
|
|
858
|
+
return legs.map((l) => `${l} && `).join("");
|
|
845
859
|
}
|
|
846
860
|
function managedExpoAndroidCommand(cwd) {
|
|
847
861
|
return `${installCommand(cwd)} && npx expo prebuild --platform android --no-install && cd android && ./gradlew assembleDebug`;
|
|
@@ -871,7 +885,7 @@ function rokuDefaults(cwd) {
|
|
|
871
885
|
}
|
|
872
886
|
return { buildCommand: ROKU_ZIP_COMMAND, artifact };
|
|
873
887
|
}
|
|
874
|
-
function defaultsFor(cwd, platform) {
|
|
888
|
+
function defaultsFor(cwd, platform, evaluatedIosName) {
|
|
875
889
|
const flavor = detectFlavor(cwd);
|
|
876
890
|
if (platform === "roku") return flavor === "roku" ? rokuDefaults(cwd) : {};
|
|
877
891
|
if (flavor === "unknown" || flavor === "roku") return {};
|
|
@@ -880,13 +894,13 @@ function defaultsFor(cwd, platform) {
|
|
|
880
894
|
if (platform === "android") {
|
|
881
895
|
const managed = isManagedExpo(cwd, "android");
|
|
882
896
|
return {
|
|
883
|
-
buildCommand: managed ? managedExpoAndroidCommand(cwd) : "cd android && ./gradlew assembleDebug
|
|
897
|
+
buildCommand: managed ? managedExpoAndroidCommand(cwd) : `${bareInstallPrefix(cwd, "android")}cd android && ./gradlew assembleDebug`,
|
|
884
898
|
artifact: ANDROID_ARTIFACT,
|
|
885
899
|
metroCommand
|
|
886
900
|
};
|
|
887
901
|
}
|
|
888
902
|
if (isManagedExpo(cwd, "ios")) {
|
|
889
|
-
const name = managedExpoIosName(cwd);
|
|
903
|
+
const name = managedExpoIosName(cwd, evaluatedIosName);
|
|
890
904
|
return {
|
|
891
905
|
buildCommand: name ? managedExpoIosCommand(cwd, name) : void 0,
|
|
892
906
|
artifact: IOS_ARTIFACT,
|
|
@@ -895,14 +909,14 @@ function defaultsFor(cwd, platform) {
|
|
|
895
909
|
}
|
|
896
910
|
const ws = iosWorkspaceName(cwd);
|
|
897
911
|
return {
|
|
898
|
-
buildCommand: ws ?
|
|
912
|
+
buildCommand: ws ? `${bareInstallPrefix(cwd, "ios")}xcodebuild -workspace ios/${ws}.xcworkspace -scheme ${ws} -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build CODE_SIGNING_ALLOWED=NO` : void 0,
|
|
899
913
|
artifact: IOS_ARTIFACT,
|
|
900
914
|
metroCommand
|
|
901
915
|
};
|
|
902
916
|
}
|
|
903
917
|
var ANDROID_PRODUCTION_ARTIFACT = "android/app/build/outputs/bundle/release/*.aab";
|
|
904
918
|
function androidProductionCommand(cwd) {
|
|
905
|
-
return `${
|
|
919
|
+
return `${bareInstallPrefix(cwd, "android")}cd android && ./gradlew bundleRelease`;
|
|
906
920
|
}
|
|
907
921
|
var IOS_PRODUCTION_ARTIFACT = "ios/build/*.ipa";
|
|
908
922
|
function managedExpoAndroidProductionCommand(cwd) {
|
|
@@ -911,10 +925,13 @@ function managedExpoAndroidProductionCommand(cwd) {
|
|
|
911
925
|
function expoSanitizedName(name) {
|
|
912
926
|
return name.replace(/[\W_]+/g, "").normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
913
927
|
}
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
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;
|
|
918
935
|
const raw = readJson(cwd, "app.config.json") ?? readJson(cwd, "app.json");
|
|
919
936
|
if (!raw) return null;
|
|
920
937
|
const config = "expo" in raw ? raw.expo : raw;
|
|
@@ -929,7 +946,10 @@ function iosArchiveExportCommand(ws) {
|
|
|
929
946
|
const exportCmd = `xcodebuild -exportArchive -archivePath ${archivePath} -exportPath ios/build -exportOptionsPlist "$VIBEVIEW_EXPORT_OPTIONS_PLIST"`;
|
|
930
947
|
return `${archiveCmd} && ${exportCmd}`;
|
|
931
948
|
}
|
|
932
|
-
function
|
|
949
|
+
function managedExpoIosProductionCommand(cwd, name) {
|
|
950
|
+
return `${installCommand(cwd)} && npx expo prebuild --platform ios && ` + iosArchiveExportCommand(name);
|
|
951
|
+
}
|
|
952
|
+
function productionDefaultsFor(cwd, platform, evaluatedIosName) {
|
|
933
953
|
if (platform === "android" || platform === "androidtv") {
|
|
934
954
|
return {
|
|
935
955
|
buildCommand: isManagedExpo(cwd, "android") ? managedExpoAndroidProductionCommand(cwd) : androidProductionCommand(cwd),
|
|
@@ -941,17 +961,17 @@ function productionDefaultsFor(cwd, platform) {
|
|
|
941
961
|
}
|
|
942
962
|
if (platform === "ios") {
|
|
943
963
|
if (isManagedExpo(cwd, "ios")) {
|
|
944
|
-
const name = managedExpoIosName(cwd);
|
|
964
|
+
const name = managedExpoIosName(cwd, evaluatedIosName);
|
|
945
965
|
if (!name) return { artifact: IOS_PRODUCTION_ARTIFACT };
|
|
946
966
|
return {
|
|
947
|
-
buildCommand:
|
|
967
|
+
buildCommand: managedExpoIosProductionCommand(cwd, name),
|
|
948
968
|
artifact: IOS_PRODUCTION_ARTIFACT
|
|
949
969
|
};
|
|
950
970
|
}
|
|
951
971
|
const ws = iosWorkspaceName(cwd);
|
|
952
972
|
if (!ws) return { artifact: IOS_PRODUCTION_ARTIFACT };
|
|
953
973
|
return {
|
|
954
|
-
buildCommand: `${
|
|
974
|
+
buildCommand: `${bareInstallPrefix(cwd, "ios")}${iosArchiveExportCommand(ws)}`,
|
|
955
975
|
artifact: IOS_PRODUCTION_ARTIFACT
|
|
956
976
|
};
|
|
957
977
|
}
|
|
@@ -980,36 +1000,65 @@ function pbxprojText(cwd) {
|
|
|
980
1000
|
return null;
|
|
981
1001
|
}
|
|
982
1002
|
}
|
|
983
|
-
|
|
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) => {
|
|
984
1018
|
const cli = join5(cwd, "node_modules", "expo", "bin", "cli");
|
|
985
1019
|
if (!existsSync3(cli)) {
|
|
986
|
-
return Promise.reject(
|
|
1020
|
+
return Promise.reject(
|
|
1021
|
+
new Error("Expo isn't installed in this project yet (run your install command first)")
|
|
1022
|
+
);
|
|
987
1023
|
}
|
|
988
1024
|
return new Promise((resolve, reject) => {
|
|
989
|
-
|
|
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(
|
|
990
1033
|
process.execPath,
|
|
991
1034
|
[cli, "config", "--json", "--type", "public"],
|
|
992
1035
|
{
|
|
993
1036
|
cwd,
|
|
994
|
-
timeout: 3e4,
|
|
995
1037
|
maxBuffer: 8 * 1024 * 1024,
|
|
996
1038
|
env: { ...process.env, EXPO_NO_TELEMETRY: "1", CI: "1" }
|
|
997
1039
|
},
|
|
998
1040
|
(err, stdout, stderr) => {
|
|
999
1041
|
if (err) {
|
|
1000
1042
|
const why = String(stderr || err.message).split("\n").map((l) => l.trim()).find((l) => l.length > 0);
|
|
1001
|
-
reject(new Error(`expo config failed${why ? `: ${why}` : ""}`));
|
|
1043
|
+
settle(() => reject(new Error(`expo config failed${why ? `: ${why}` : ""}`)));
|
|
1002
1044
|
return;
|
|
1003
1045
|
}
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
} catch {
|
|
1009
|
-
reject(new Error("expo config printed something that is not JSON"));
|
|
1010
|
-
}
|
|
1046
|
+
const parsed = parseExpoConfigJson(stdout);
|
|
1047
|
+
settle(
|
|
1048
|
+
() => parsed ? resolve(parsed) : reject(new Error("expo config printed something that is not JSON"))
|
|
1049
|
+
);
|
|
1011
1050
|
}
|
|
1012
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();
|
|
1013
1062
|
});
|
|
1014
1063
|
};
|
|
1015
1064
|
async function localAppIdentity(cwd, platform, readConfig = readExpoConfig) {
|
|
@@ -1020,9 +1069,7 @@ async function localAppIdentity(cwd, platform, readConfig = readExpoConfig) {
|
|
|
1020
1069
|
let version = null;
|
|
1021
1070
|
let bundleId = null;
|
|
1022
1071
|
let versionUnreadable = null;
|
|
1023
|
-
const dynamicName =
|
|
1024
|
-
(f) => existsSync3(join5(cwd, f))
|
|
1025
|
-
);
|
|
1072
|
+
const dynamicName = dynamicExpoConfig(cwd);
|
|
1026
1073
|
const staticRaw = readJson(cwd, "app.config.json") ?? readJson(cwd, "app.json");
|
|
1027
1074
|
const isExpo = Boolean(dynamicName) || staticRaw !== null && "expo" in staticRaw || existsSync3(join5(cwd, "node_modules", "expo", "package.json"));
|
|
1028
1075
|
let expo = null;
|
|
@@ -1061,6 +1108,41 @@ async function localAppIdentity(cwd, platform, readConfig = readExpoConfig) {
|
|
|
1061
1108
|
}
|
|
1062
1109
|
return { reactNative, version, bundleId, versionUnreadable: version ? null : versionUnreadable };
|
|
1063
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
|
+
}
|
|
1064
1146
|
|
|
1065
1147
|
// src/lib/dev-setup.ts
|
|
1066
1148
|
function platformChoices(cwd) {
|
|
@@ -1095,6 +1177,37 @@ function fillLaterHint(skipped, platform) {
|
|
|
1095
1177
|
}
|
|
1096
1178
|
return lines.join("\n");
|
|
1097
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();
|
|
1098
1211
|
async function commandSelect(message, detected) {
|
|
1099
1212
|
const choices = [];
|
|
1100
1213
|
if (detected) choices.push({ value: "default", name: `Use detected default: ${detected}` });
|
|
@@ -1103,7 +1216,8 @@ async function commandSelect(message, detected) {
|
|
|
1103
1216
|
return select({ message, choices });
|
|
1104
1217
|
}
|
|
1105
1218
|
async function promptBuildConfig(cwd, platform) {
|
|
1106
|
-
const defaults =
|
|
1219
|
+
const defaults = await promptDefaults(cwd, platform);
|
|
1220
|
+
evaluatedConfigNote(cwd, defaults);
|
|
1107
1221
|
const choice = await commandSelect(`Build command for ${platform}`, defaults.buildCommand);
|
|
1108
1222
|
if (choice === "skip") return null;
|
|
1109
1223
|
if (choice === "default") {
|
|
@@ -1123,7 +1237,8 @@ async function promptBuildConfig(cwd, platform) {
|
|
|
1123
1237
|
return { command, artifact };
|
|
1124
1238
|
}
|
|
1125
1239
|
async function promptProductionBuildConfig(cwd, platform) {
|
|
1126
|
-
const defaults =
|
|
1240
|
+
const defaults = await promptDefaults(cwd, platform, { production: true });
|
|
1241
|
+
evaluatedConfigNote(cwd, defaults);
|
|
1127
1242
|
const choice = await commandSelect(
|
|
1128
1243
|
`Production build command for ${platform}`,
|
|
1129
1244
|
defaults.buildCommand
|
|
@@ -1329,6 +1444,203 @@ function runTar(args) {
|
|
|
1329
1444
|
});
|
|
1330
1445
|
}
|
|
1331
1446
|
|
|
1447
|
+
// src/lib/build-preflight.ts
|
|
1448
|
+
import { execFile as execFile3 } from "child_process";
|
|
1449
|
+
import { realpathSync, statSync as statSync4 } from "fs";
|
|
1450
|
+
import { dirname as dirname2, isAbsolute, join as join7, relative } from "path";
|
|
1451
|
+
import { parse } from "shell-quote";
|
|
1452
|
+
var DIRECTORY_CHANGERS = /* @__PURE__ */ new Set(["cd", "pushd"]);
|
|
1453
|
+
var DIRECTORY_FLAGS = /^(--cwd|--prefix|--dir|-C)(=|$)/;
|
|
1454
|
+
var VALUE_FLAGS = /^(--cwd|--prefix|--dir|-C|--loglevel|--registry|--workspace|--filter)$/;
|
|
1455
|
+
var GLOBAL_FLAGS = /* @__PURE__ */ new Set(["-g", "--global", "--location=global"]);
|
|
1456
|
+
var SEPARATORS = /* @__PURE__ */ new Set(["&&", "||", ";", ";;", "|", "|&", "&", "(", ")"]);
|
|
1457
|
+
var REDIRECTIONS = /* @__PURE__ */ new Set([">", ">>", "<", ">&", "<&", "&>", "&>>", ">|", "<>"]);
|
|
1458
|
+
var WRAPPERS = /* @__PURE__ */ new Set(["time", "env", "corepack", "command", "exec", "nohup"]);
|
|
1459
|
+
var JS_PACKAGE_MANAGERS = /* @__PURE__ */ new Set(["npm", "npx", "yarn", "pnpm", "pnpx", "bun", "bunx"]);
|
|
1460
|
+
var ASSIGNMENT = /^[A-Za-z_][A-Za-z0-9_]*=/;
|
|
1461
|
+
function simpleCommands(command) {
|
|
1462
|
+
const out = [];
|
|
1463
|
+
const flush = (words) => {
|
|
1464
|
+
while (words.length > 0 && ASSIGNMENT.test(words[0])) words.shift();
|
|
1465
|
+
while (words.length > 0 && WRAPPERS.has(words[0])) {
|
|
1466
|
+
words.shift();
|
|
1467
|
+
while (words.length > 0 && (words[0].startsWith("-") || ASSIGNMENT.test(words[0]))) {
|
|
1468
|
+
words.shift();
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
if (words.length === 0) return;
|
|
1472
|
+
const base = words[0].replace(/@[^/]*$/, "");
|
|
1473
|
+
const program = JS_PACKAGE_MANAGERS.has(base) ? base : words[0];
|
|
1474
|
+
out.push({ program, args: words.slice(1) });
|
|
1475
|
+
};
|
|
1476
|
+
for (const line of command.replace(/\\\r?\n/g, " ").split(/\r?\n/)) {
|
|
1477
|
+
let tokens;
|
|
1478
|
+
try {
|
|
1479
|
+
tokens = parse(line, (name) => `$${name}`);
|
|
1480
|
+
} catch {
|
|
1481
|
+
continue;
|
|
1482
|
+
}
|
|
1483
|
+
let words = [];
|
|
1484
|
+
let redirectTarget = false;
|
|
1485
|
+
for (const t of tokens) {
|
|
1486
|
+
if (typeof t === "string") {
|
|
1487
|
+
if (redirectTarget) {
|
|
1488
|
+
redirectTarget = false;
|
|
1489
|
+
continue;
|
|
1490
|
+
}
|
|
1491
|
+
if (t === "{" || t === "}") flush(words.splice(0));
|
|
1492
|
+
else if (t.length > 0) words.push(t);
|
|
1493
|
+
} else if ("comment" in t) {
|
|
1494
|
+
break;
|
|
1495
|
+
} else if ("op" in t && t.op === "glob") {
|
|
1496
|
+
words.push(t.pattern);
|
|
1497
|
+
} else if ("op" in t && SEPARATORS.has(t.op)) {
|
|
1498
|
+
flush(words);
|
|
1499
|
+
words = [];
|
|
1500
|
+
} else if ("op" in t && REDIRECTIONS.has(t.op)) {
|
|
1501
|
+
if (words.length > 1 && /^\d+$/.test(words[words.length - 1])) words.pop();
|
|
1502
|
+
redirectTarget = true;
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
flush(words);
|
|
1506
|
+
}
|
|
1507
|
+
return out;
|
|
1508
|
+
}
|
|
1509
|
+
function positionals(args) {
|
|
1510
|
+
const out = [];
|
|
1511
|
+
for (let i = 0; i < args.length; i++) {
|
|
1512
|
+
const a = args[i];
|
|
1513
|
+
if (a.startsWith("-")) {
|
|
1514
|
+
if (VALUE_FLAGS.test(a)) i++;
|
|
1515
|
+
continue;
|
|
1516
|
+
}
|
|
1517
|
+
out.push(a);
|
|
1518
|
+
}
|
|
1519
|
+
return out;
|
|
1520
|
+
}
|
|
1521
|
+
var isGlobal = (args) => args.some((a) => GLOBAL_FLAGS.has(a));
|
|
1522
|
+
var VERSION_OR_HELP = /* @__PURE__ */ new Set(["-v", "--version", "-h", "--help"]);
|
|
1523
|
+
var NPM_INSTALL_VERBS = /* @__PURE__ */ new Set([
|
|
1524
|
+
"ci",
|
|
1525
|
+
"clean-install",
|
|
1526
|
+
"install-clean",
|
|
1527
|
+
"install",
|
|
1528
|
+
"i",
|
|
1529
|
+
"add",
|
|
1530
|
+
"install-test",
|
|
1531
|
+
"it"
|
|
1532
|
+
]);
|
|
1533
|
+
var NEEDS_MANIFEST_VERBS = /* @__PURE__ */ new Set([
|
|
1534
|
+
"ci",
|
|
1535
|
+
"clean-install",
|
|
1536
|
+
"install-clean",
|
|
1537
|
+
"install-test",
|
|
1538
|
+
"it",
|
|
1539
|
+
"run",
|
|
1540
|
+
"run-script"
|
|
1541
|
+
]);
|
|
1542
|
+
function installsInStep({ program, args }) {
|
|
1543
|
+
if (isGlobal(args)) return false;
|
|
1544
|
+
const [verb, ...rest] = positionals(args);
|
|
1545
|
+
if (program === "yarn") {
|
|
1546
|
+
if (verb === void 0) return !args.some((a) => VERSION_OR_HELP.has(a));
|
|
1547
|
+
if (verb === "install" || verb === "add") return true;
|
|
1548
|
+
return verb === "workspaces" && rest[0] === "focus";
|
|
1549
|
+
}
|
|
1550
|
+
if (program === "npm" || program === "pnpm" || program === "bun") {
|
|
1551
|
+
return verb !== void 0 && NPM_INSTALL_VERBS.has(verb);
|
|
1552
|
+
}
|
|
1553
|
+
return false;
|
|
1554
|
+
}
|
|
1555
|
+
function needsManifest({ program, args }) {
|
|
1556
|
+
if (isGlobal(args) || args.some((a) => DIRECTORY_FLAGS.test(a))) return false;
|
|
1557
|
+
const [verb, ...rest] = positionals(args);
|
|
1558
|
+
if (program === "yarn") {
|
|
1559
|
+
if (verb === void 0) return !args.some((a) => VERSION_OR_HELP.has(a));
|
|
1560
|
+
if (verb === "install" || verb === "run") return true;
|
|
1561
|
+
return verb === "workspaces" && rest[0] === "focus";
|
|
1562
|
+
}
|
|
1563
|
+
if (program === "npm" || program === "pnpm" || program === "bun") {
|
|
1564
|
+
if (verb === void 0) return false;
|
|
1565
|
+
if (NEEDS_MANIFEST_VERBS.has(verb)) return true;
|
|
1566
|
+
return (verb === "install" || verb === "i") && rest.length === 0;
|
|
1567
|
+
}
|
|
1568
|
+
return false;
|
|
1569
|
+
}
|
|
1570
|
+
function packageManagerAtRoot(command) {
|
|
1571
|
+
for (const step of simpleCommands(command)) {
|
|
1572
|
+
if (DIRECTORY_CHANGERS.has(step.program)) return null;
|
|
1573
|
+
if (JS_PACKAGE_MANAGERS.has(step.program) && positionals(step.args)[0] === "init") return null;
|
|
1574
|
+
if (needsManifest(step)) return step.program;
|
|
1575
|
+
}
|
|
1576
|
+
return null;
|
|
1577
|
+
}
|
|
1578
|
+
function installsDependencies(command) {
|
|
1579
|
+
return simpleCommands(command).some(installsInStep);
|
|
1580
|
+
}
|
|
1581
|
+
function missingPackageJsonError(command, files, dir) {
|
|
1582
|
+
const pm = packageManagerAtRoot(command);
|
|
1583
|
+
if (!pm || files.has("package.json")) return null;
|
|
1584
|
+
return `No package.json in ${dir}. Run vibeview build from your project root. (Your build command runs \`${pm}\` there; nothing was uploaded or charged.)`;
|
|
1585
|
+
}
|
|
1586
|
+
function missingInstallWarning(command, files) {
|
|
1587
|
+
const hit = JS_LOCKFILE_INSTALLS.find(([lockfile]) => files.has(lockfile));
|
|
1588
|
+
if (!hit || files.has(".pnp.cjs") || installsDependencies(command)) return null;
|
|
1589
|
+
return `Your build command doesn't install dependencies; node_modules is never uploaded, so the build will likely fail. Add \`${hit[1]} &&\` at the start.`;
|
|
1590
|
+
}
|
|
1591
|
+
function missingLockfileWarning(command, files) {
|
|
1592
|
+
if (!files.has("package.json")) return null;
|
|
1593
|
+
if (JS_LOCKFILE_INSTALLS.some(([lockfile]) => files.has(lockfile))) return null;
|
|
1594
|
+
if (!installsDependencies(command)) return null;
|
|
1595
|
+
return "No lockfile in the upload (yarn.lock, package-lock.json, pnpm-lock.yaml): a frozen install (`npm ci`, `--immutable`, `--frozen-lockfile`) will fail, and any other install resolves fresh versions. Commit your lockfile, or run vibeview build from the directory that has it.";
|
|
1596
|
+
}
|
|
1597
|
+
function bunWarning(command) {
|
|
1598
|
+
const step = simpleCommands(command).find((s) => s.program === "bun" || s.program === "bunx");
|
|
1599
|
+
if (!step) return null;
|
|
1600
|
+
return `Your build command runs \`${step.program}\`, but cloud builders don't have Bun, so that step will fail unless your command installs Bun first. Use npm, yarn or pnpm instead.`;
|
|
1601
|
+
}
|
|
1602
|
+
function buildCommandWarnings(command, files) {
|
|
1603
|
+
return [
|
|
1604
|
+
bunWarning(command),
|
|
1605
|
+
missingInstallWarning(command, files),
|
|
1606
|
+
missingLockfileWarning(command, files)
|
|
1607
|
+
].filter((w) => w !== null);
|
|
1608
|
+
}
|
|
1609
|
+
function projectRootHint(cwd, gitRoot, files, hasPackageJson = (dir) => isFile2(join7(dir, "package.json"))) {
|
|
1610
|
+
if (!gitRoot || files.has("package.json")) return null;
|
|
1611
|
+
cwd = realPathOr(cwd);
|
|
1612
|
+
gitRoot = realPathOr(gitRoot);
|
|
1613
|
+
const rel = relative(gitRoot, cwd);
|
|
1614
|
+
if (rel === "" || rel.startsWith("..") || isAbsolute(rel)) return null;
|
|
1615
|
+
for (let dir = dirname2(cwd); ; dir = dirname2(dir)) {
|
|
1616
|
+
if (hasPackageJson(dir)) {
|
|
1617
|
+
return `You're running from ${cwd}; the project root looks like ${dir}. Only ${cwd} will be uploaded.`;
|
|
1618
|
+
}
|
|
1619
|
+
if (dir === gitRoot || dir === dirname2(dir)) return null;
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
function realPathOr(path) {
|
|
1623
|
+
try {
|
|
1624
|
+
return realpathSync(path);
|
|
1625
|
+
} catch {
|
|
1626
|
+
return path;
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
function isFile2(path) {
|
|
1630
|
+
try {
|
|
1631
|
+
return statSync4(path).isFile();
|
|
1632
|
+
} catch {
|
|
1633
|
+
return false;
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
async function gitToplevel(cwd) {
|
|
1637
|
+
return new Promise((resolve) => {
|
|
1638
|
+
execFile3("git", ["rev-parse", "--show-toplevel"], { cwd }, (err, stdout) => {
|
|
1639
|
+
resolve(err ? null : stdout.trim() || null);
|
|
1640
|
+
});
|
|
1641
|
+
});
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1332
1644
|
// src/commands/build.ts
|
|
1333
1645
|
var CLOUD_PLATFORMS = ["ios", "android", "tvos", "androidtv"];
|
|
1334
1646
|
var PRODUCTION_SIGNING_PLATFORMS = ["ios", "android", "androidtv", "tvos"];
|
|
@@ -1795,6 +2107,25 @@ async function runCloudBuildCommand(cwd, cfgRef, opts, deps) {
|
|
|
1795
2107
|
}
|
|
1796
2108
|
throw err;
|
|
1797
2109
|
}
|
|
2110
|
+
const uploaded = new Set(files);
|
|
2111
|
+
const warnings = /* @__PURE__ */ new Map();
|
|
2112
|
+
for (const p of platforms) {
|
|
2113
|
+
const command = productionBuilds.get(p)?.command ?? cfg.platforms[p].build.command;
|
|
2114
|
+
const missingPkg = missingPackageJsonError(command, uploaded, cwd);
|
|
2115
|
+
if (missingPkg) fail(opts, missingPkg, "no_package_json");
|
|
2116
|
+
for (const w of buildCommandWarnings(command, uploaded)) {
|
|
2117
|
+
warnings.set(w, [...warnings.get(w) ?? [], p]);
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
const rootHint = projectRootHint(cwd, await (d.gitToplevel ?? gitToplevel)(cwd), uploaded);
|
|
2121
|
+
if (rootHint) warnings.set(rootHint, [...platforms]);
|
|
2122
|
+
for (const [message, ps] of warnings) {
|
|
2123
|
+
for (const p of ps) emitCloud(opts.json, { event: "warning", platform: p, message });
|
|
2124
|
+
if (!opts.json) {
|
|
2125
|
+
const prefix = platforms.length > 1 && ps.length < platforms.length ? `${ps.join(", ")}: ` : "";
|
|
2126
|
+
console.error(chalk3.yellow(`\u26A0 ${prefix}${message}`));
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
1798
2129
|
const packSpinner = !opts.json ? createSpinner(`packing source (${files.length} files)`) : null;
|
|
1799
2130
|
packSpinner?.start();
|
|
1800
2131
|
let tarPath;
|
|
@@ -2173,7 +2504,7 @@ your build command must produce a zip \u2014 use the package/zip step, not deplo
|
|
|
2173
2504
|
// src/commands/dev.ts
|
|
2174
2505
|
import { spawn as spawn4 } from "child_process";
|
|
2175
2506
|
import { createWriteStream, mkdirSync as mkdirSync5 } from "fs";
|
|
2176
|
-
import { dirname as
|
|
2507
|
+
import { dirname as dirname5 } from "path";
|
|
2177
2508
|
import { createInterface as createInterface3 } from "readline";
|
|
2178
2509
|
import { select as select3 } from "@inquirer/prompts";
|
|
2179
2510
|
import chalk4 from "chalk";
|
|
@@ -2376,14 +2707,14 @@ import { createInterface as createInterface2 } from "readline";
|
|
|
2376
2707
|
|
|
2377
2708
|
// src/lib/metro-state.ts
|
|
2378
2709
|
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync7, rmSync as rmSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
2379
|
-
import { dirname as
|
|
2710
|
+
import { dirname as dirname3, join as join8 } from "path";
|
|
2380
2711
|
var STATE_DIR2 = ".vibeview";
|
|
2381
2712
|
var STATE_FILE2 = "metro-state.json";
|
|
2382
2713
|
function statePath2(cwd) {
|
|
2383
|
-
return
|
|
2714
|
+
return join8(cwd, STATE_DIR2, STATE_FILE2);
|
|
2384
2715
|
}
|
|
2385
2716
|
function metroLogPath(cwd) {
|
|
2386
|
-
return
|
|
2717
|
+
return join8(cwd, STATE_DIR2, "metro.log");
|
|
2387
2718
|
}
|
|
2388
2719
|
function isPidAlive2(pid) {
|
|
2389
2720
|
try {
|
|
@@ -2395,7 +2726,7 @@ function isPidAlive2(pid) {
|
|
|
2395
2726
|
}
|
|
2396
2727
|
function writeMetroState(cwd, state) {
|
|
2397
2728
|
const path = statePath2(cwd);
|
|
2398
|
-
mkdirSync4(
|
|
2729
|
+
mkdirSync4(dirname3(path), { recursive: true });
|
|
2399
2730
|
writeFileSync6(path, JSON.stringify(state, null, 2) + "\n");
|
|
2400
2731
|
}
|
|
2401
2732
|
function readMetroState(cwd) {
|
|
@@ -2534,7 +2865,7 @@ async function waitFor(cond, timeoutMs, everyMs) {
|
|
|
2534
2865
|
|
|
2535
2866
|
// src/lib/reload-loop.ts
|
|
2536
2867
|
import { watch } from "fs";
|
|
2537
|
-
import { basename, dirname as
|
|
2868
|
+
import { basename, dirname as dirname4 } from "path";
|
|
2538
2869
|
function createReloadLoop(deps) {
|
|
2539
2870
|
let inFlight = null;
|
|
2540
2871
|
let pendingFollowUp = false;
|
|
@@ -2615,11 +2946,11 @@ function isIgnoredWatchPath(relPath, artifact, userIgnore = []) {
|
|
|
2615
2946
|
if (IGNORED_FILES.includes(rel)) return true;
|
|
2616
2947
|
if (userIgnore.some((entry) => isUnderPath(rel, entry))) return true;
|
|
2617
2948
|
const artifactPattern = artifact.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
2618
|
-
const artifactDir =
|
|
2949
|
+
const artifactDir = dirname4(artifactPattern);
|
|
2619
2950
|
if (artifactDir !== "." && (rel === artifactDir || rel.startsWith(`${artifactDir}/`))) {
|
|
2620
2951
|
return true;
|
|
2621
2952
|
}
|
|
2622
|
-
if (
|
|
2953
|
+
if (dirname4(rel) !== artifactDir) return false;
|
|
2623
2954
|
const name = basename(artifactPattern);
|
|
2624
2955
|
const re = new RegExp(`^${name.split("*").map(escapeRegExp).join(".*")}$`);
|
|
2625
2956
|
return re.test(basename(rel));
|
|
@@ -3497,7 +3828,7 @@ async function runDevInProcess(opts) {
|
|
|
3497
3828
|
const captureMetroLine = (line) => {
|
|
3498
3829
|
try {
|
|
3499
3830
|
if (!metroLogStream) {
|
|
3500
|
-
mkdirSync5(
|
|
3831
|
+
mkdirSync5(dirname5(metroLogPath(process.cwd())), { recursive: true });
|
|
3501
3832
|
metroLogStream = createWriteStream(metroLogPath(process.cwd()), { flags: "w" });
|
|
3502
3833
|
}
|
|
3503
3834
|
metroLogStream.write(line.replace(/^\[metro\] /, "") + "\n");
|
|
@@ -4618,9 +4949,9 @@ function flagKey(cli) {
|
|
|
4618
4949
|
const name = cli.replace(/^--/, "");
|
|
4619
4950
|
return name.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
4620
4951
|
}
|
|
4621
|
-
function buildToolCall(cmd,
|
|
4952
|
+
function buildToolCall(cmd, positionals2, opts) {
|
|
4622
4953
|
if (cmd.verb === "tap") {
|
|
4623
|
-
const target =
|
|
4954
|
+
const target = positionals2[0];
|
|
4624
4955
|
if (!target) throw new Error('tap requires a target: an element ref (@e5) or "x,y"');
|
|
4625
4956
|
const coords = parseCoords(target);
|
|
4626
4957
|
if (coords) {
|
|
@@ -4635,7 +4966,7 @@ function buildToolCall(cmd, positionals, opts) {
|
|
|
4635
4966
|
return { tool: "tap", args: { ref: stripRef(target) } };
|
|
4636
4967
|
}
|
|
4637
4968
|
if (cmd.verb === "long-press") {
|
|
4638
|
-
const target =
|
|
4969
|
+
const target = positionals2[0];
|
|
4639
4970
|
if (!target) throw new Error("long-press requires a ref, e.g. @e5");
|
|
4640
4971
|
if (parseCoords(target) || target.includes(",") && !REF_RE.test(target)) {
|
|
4641
4972
|
throw new Error(
|
|
@@ -4649,8 +4980,8 @@ function buildToolCall(cmd, positionals, opts) {
|
|
|
4649
4980
|
return { tool: cmd.tool, args: args2 };
|
|
4650
4981
|
}
|
|
4651
4982
|
if (cmd.verb === "drag") {
|
|
4652
|
-
const from =
|
|
4653
|
-
const to =
|
|
4983
|
+
const from = positionals2[0];
|
|
4984
|
+
const to = positionals2[1];
|
|
4654
4985
|
if (!from || !to) {
|
|
4655
4986
|
throw new Error('drag requires two targets: <from> <to>, each an element ref (@e5) or "x,y"');
|
|
4656
4987
|
}
|
|
@@ -4691,7 +5022,7 @@ function buildToolCall(cmd, positionals, opts) {
|
|
|
4691
5022
|
return { tool: cmd.tool, args: args2 };
|
|
4692
5023
|
}
|
|
4693
5024
|
if (cmd.verb === "scroll") {
|
|
4694
|
-
const direction =
|
|
5025
|
+
const direction = positionals2[0];
|
|
4695
5026
|
const valid = ["up", "down", "left", "right"];
|
|
4696
5027
|
if (!direction || !valid.includes(direction)) {
|
|
4697
5028
|
throw new Error(`scroll: direction must be one of ${valid.join(", ")}, got "${direction}"`);
|
|
@@ -4699,7 +5030,7 @@ function buildToolCall(cmd, positionals, opts) {
|
|
|
4699
5030
|
return { tool: cmd.tool, args: { name: `scroll_${direction}` } };
|
|
4700
5031
|
}
|
|
4701
5032
|
if (cmd.verb === "swipe") {
|
|
4702
|
-
const direction =
|
|
5033
|
+
const direction = positionals2[0];
|
|
4703
5034
|
const valid = ["up", "down", "left", "right"];
|
|
4704
5035
|
if (!direction || !valid.includes(direction)) {
|
|
4705
5036
|
throw new Error(`swipe: direction must be one of ${valid.join(", ")}, got "${direction}"`);
|
|
@@ -4711,7 +5042,7 @@ function buildToolCall(cmd, positionals, opts) {
|
|
|
4711
5042
|
return { tool: cmd.tool, args: args2 };
|
|
4712
5043
|
}
|
|
4713
5044
|
if (cmd.verb === "set-posture") {
|
|
4714
|
-
const hasPosture =
|
|
5045
|
+
const hasPosture = positionals2[0] !== void 0 && positionals2[0] !== "";
|
|
4715
5046
|
const hasAngle = opts.angle !== void 0 && opts.angle !== "";
|
|
4716
5047
|
if (hasPosture === hasAngle) {
|
|
4717
5048
|
throw new FlagError(
|
|
@@ -4724,7 +5055,7 @@ function buildToolCall(cmd, positionals, opts) {
|
|
|
4724
5055
|
for (const param of cmd.params) {
|
|
4725
5056
|
let raw;
|
|
4726
5057
|
if (param.cli === "positional") {
|
|
4727
|
-
raw =
|
|
5058
|
+
raw = positionals2[positionalIndex];
|
|
4728
5059
|
positionalIndex += 1;
|
|
4729
5060
|
} else {
|
|
4730
5061
|
const key = flagKey(param.cli);
|
|
@@ -4774,7 +5105,7 @@ var NON_USE_TOOLS = /* @__PURE__ */ new Set([
|
|
|
4774
5105
|
function countsAsUse(tool) {
|
|
4775
5106
|
return !NON_USE_TOOLS.has(tool);
|
|
4776
5107
|
}
|
|
4777
|
-
async function runAgentVerb(cmd,
|
|
5108
|
+
async function runAgentVerb(cmd, positionals2, opts) {
|
|
4778
5109
|
const { token, baseUrl } = resolveAuth(opts);
|
|
4779
5110
|
const sessionId = opts.session || readDevState(process.cwd())?.session_id;
|
|
4780
5111
|
if (!sessionId) {
|
|
@@ -4783,7 +5114,7 @@ async function runAgentVerb(cmd, positionals, opts) {
|
|
|
4783
5114
|
}
|
|
4784
5115
|
let call;
|
|
4785
5116
|
try {
|
|
4786
|
-
call = buildToolCall(cmd,
|
|
5117
|
+
call = buildToolCall(cmd, positionals2, opts);
|
|
4787
5118
|
} catch (err) {
|
|
4788
5119
|
process.stderr.write(`error: ${err instanceof Error ? err.message : String(err)}
|
|
4789
5120
|
`);
|
package/dist/cli.js
CHANGED
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
readMetroState,
|
|
26
26
|
recordDevUse,
|
|
27
27
|
runAgentVerb
|
|
28
|
-
} from "./chunk-
|
|
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-
|
|
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(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vibeview",
|
|
3
|
-
"version": "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",
|
|
@@ -33,11 +33,13 @@
|
|
|
33
33
|
"ignore": "^7.0.0",
|
|
34
34
|
"open": "^11.0.0",
|
|
35
35
|
"ora": "^9.0.0",
|
|
36
|
+
"shell-quote": "^1.8.3",
|
|
36
37
|
"ws": "^8.18.0",
|
|
37
38
|
"zod": "^4.0.0"
|
|
38
39
|
},
|
|
39
40
|
"devDependencies": {
|
|
40
41
|
"@types/node": "^20.0.0",
|
|
42
|
+
"@types/shell-quote": "^1.7.5",
|
|
41
43
|
"@types/ws": "^8.5.0",
|
|
42
44
|
"dotenv": "^16.4.5",
|
|
43
45
|
"tsup": "^8.0.0",
|