@livx.cc/appwrap 0.25.1 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/cli.ts +40 -3
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -1181,6 +1181,7 @@ async function deploy(cwd: string, flags: Record<string, string>, positionals: s
|
|
|
1181
1181
|
const ipaPath = join(ipaDir, ipa);
|
|
1182
1182
|
|
|
1183
1183
|
console.log(`▶ installing ${ipa} → ${device.name} [${device.transport}]`);
|
|
1184
|
+
let installedViaUsbmux = false;
|
|
1184
1185
|
try {
|
|
1185
1186
|
// Capture (not inherit) so we can recognize specific failures; echo it for visibility.
|
|
1186
1187
|
// Wrapped so a LOCKED device waits-and-retries instead of hard-failing (the common annoyance).
|
|
@@ -1204,17 +1205,34 @@ async function deploy(cwd: string, flags: Record<string, string>, positionals: s
|
|
|
1204
1205
|
` xcrun devicectl device uninstall app --device ${device.id} <bundleId>\n` +
|
|
1205
1206
|
' (A paid Apple Developer account removes this limit.)'
|
|
1206
1207
|
);
|
|
1208
|
+
process.exit(1);
|
|
1209
|
+
} else if (/Command timeout|got stuck|could not be reached|Unable to connect to device/.test(log)) {
|
|
1210
|
+
// devicectl/CoreDevice is stuck (a connection hang, NOT a lock). usbmux (ideviceinstaller) is a
|
|
1211
|
+
// separate stack that usually still works — auto-fall-back instead of chasing a phantom "unlock".
|
|
1212
|
+
console.error('\n⚠ devicectl/CoreDevice is stuck (connection hang, not a lock) — falling back to ideviceinstaller (usbmux)…');
|
|
1213
|
+
if (usbmuxInstall(ipaPath)) {
|
|
1214
|
+
installedViaUsbmux = true;
|
|
1215
|
+
} else {
|
|
1216
|
+
console.error(
|
|
1217
|
+
'✖ usbmux fallback unavailable.\n' +
|
|
1218
|
+
' → Re-plug the USB cable (re-establishes the CoreDevice tunnel) and re-run, OR\n' +
|
|
1219
|
+
' `brew install ideviceinstaller` for a usbmux install path.\n' +
|
|
1220
|
+
` The built .ipa is ready: ${ipaPath}`
|
|
1221
|
+
);
|
|
1222
|
+
process.exit(1);
|
|
1223
|
+
}
|
|
1207
1224
|
} else {
|
|
1208
1225
|
console.error(
|
|
1209
1226
|
'✖ Install failed (device still locked after waiting, or only on Wi-Fi).\n' +
|
|
1210
1227
|
' → Unlock the phone (and plug in USB for a reliable connection), then re-run.\n' +
|
|
1211
1228
|
` The built .ipa is ready: ${ipaPath}`
|
|
1212
1229
|
);
|
|
1230
|
+
process.exit(1);
|
|
1213
1231
|
}
|
|
1214
|
-
process.exit(1);
|
|
1215
1232
|
}
|
|
1216
1233
|
|
|
1217
|
-
|
|
1234
|
+
// devicectl process-launch is also stuck when we fell back to usbmux — skip it; the user taps the icon.
|
|
1235
|
+
if (!('no-launch' in flags) && !installedViaUsbmux) {
|
|
1218
1236
|
console.log(`▶ launching ${cfg.id}`);
|
|
1219
1237
|
try {
|
|
1220
1238
|
withUnlockRetry('Launch', () =>
|
|
@@ -1224,7 +1242,23 @@ async function deploy(cwd: string, flags: Record<string, string>, positionals: s
|
|
|
1224
1242
|
console.error('⚠ Launch failed (still locked after waiting). The app is installed — unlock and tap it, or re-run.');
|
|
1225
1243
|
}
|
|
1226
1244
|
}
|
|
1227
|
-
console.log(
|
|
1245
|
+
console.log(installedViaUsbmux
|
|
1246
|
+
? `✓ Installed to ${device.name} via usbmux (devicectl was stuck). Tap the app icon to open it.`
|
|
1247
|
+
: `✓ Deployed to ${device.name}.`);
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
/** Install an .ipa over usbmux via ideviceinstaller — a separate stack from devicectl/CoreDevice, so it
|
|
1251
|
+
* works when the CoreDevice tunnel is stuck. Returns false if ideviceinstaller is absent or the install
|
|
1252
|
+
* fails (the caller then prints the re-plug / brew-install remedy). */
|
|
1253
|
+
function usbmuxInstall(ipaPath: string): boolean {
|
|
1254
|
+
try {
|
|
1255
|
+
const out = execFileSync('ideviceinstaller', ['install', ipaPath], { encoding: 'utf8', stdio: ['inherit', 'pipe', 'pipe'] });
|
|
1256
|
+
process.stdout.write(out);
|
|
1257
|
+
return /Complete|Installed/i.test(out);
|
|
1258
|
+
} catch (e: any) {
|
|
1259
|
+
process.stderr.write(`${e?.stdout ?? ''}${e?.stderr ?? ''}`);
|
|
1260
|
+
return false;
|
|
1261
|
+
}
|
|
1228
1262
|
}
|
|
1229
1263
|
|
|
1230
1264
|
/** Run a devicectl op; if it fails because the device is LOCKED (or transiently unavailable), prompt
|
|
@@ -1238,6 +1272,9 @@ function withUnlockRetry<T>(label: string, run: () => T, tries = 40, delayMs = 3
|
|
|
1238
1272
|
} catch (e: any) {
|
|
1239
1273
|
const log = `${e?.stdout ?? ''}${e?.stderr ?? ''}`;
|
|
1240
1274
|
if (/maximum number of installed apps|MIInstallerErrorDomain error 13|ApplicationVerificationFailed/.test(log)) throw e;
|
|
1275
|
+
// devicectl/CoreDevice tunnel STUCK (it switches to a [wired] path and hangs) is NOT a lock —
|
|
1276
|
+
// retrying as "waiting for unlock" is pointless + misleading. Re-throw so the caller can fall back.
|
|
1277
|
+
if (/Command timeout|got stuck|could not be reached|Unable to connect to device/.test(log)) throw e;
|
|
1241
1278
|
if (i >= tries) throw e;
|
|
1242
1279
|
if (i === 0) process.stdout.write(`\n🔒 ${label}: device unavailable — unlock your iPhone. Waiting (auto-retries every ${delayMs / 1000}s, up to ${Math.round((tries * delayMs) / 1000)}s)…\n`);
|
|
1243
1280
|
else process.stdout.write(` …waiting for unlock (${i}/${tries})\n`);
|