@hot-updater/apple-helper 0.25.9 → 0.25.10

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/dist/index.cjs CHANGED
@@ -37,6 +37,8 @@ let node_os = require("node:os");
37
37
  node_os = __toESM(node_os);
38
38
  let node_path = require("node:path");
39
39
  node_path = __toESM(node_path);
40
+ let stream_promises = require("stream/promises");
41
+ stream_promises = __toESM(stream_promises);
40
42
 
41
43
  //#region src/utils/cocoapods.ts
42
44
  const installPodsIfNeeded = async (sourceDir) => {
@@ -102,26 +104,6 @@ const createRandomTmpDir = async (prefix = "apple-helper-") => {
102
104
  return await (0, node_fs_promises.mkdtemp)((0, node_path.join)((0, node_os.tmpdir)(), prefix));
103
105
  };
104
106
 
105
- //#endregion
106
- //#region src/utils/createXcodebuildLogger.ts
107
- const createXcodebuildLogger = () => new __hot_updater_cli_tools.BuildLogger({
108
- failurePatterns: ["BUILD FAILED", "ARCHIVE FAILED"],
109
- importantLogPatterns: [
110
- "error:",
111
- "** BUILD FAILED **",
112
- "** ARCHIVE FAILED **",
113
- "The following build commands failed:"
114
- ],
115
- progressMapping: [
116
- [["[CP-User] [RN]Check rncore"], 10],
117
- [["[CP-User] [Hermes] Replace Hermes"], 35],
118
- [["[CP-User] [RN]Check FBReactNativeSpec"], 53],
119
- [["React-FabricComponents"], 66],
120
- [["[CP] Check Pods Manifest.lock"], 90],
121
- [["BUILD SUCCEEDED", "ARCHIVE SUCCEEDED"], 100]
122
- ]
123
- });
124
-
125
107
  //#endregion
126
108
  //#region src/utils/platform.ts
127
109
  /**
@@ -202,56 +184,213 @@ const prettifyXcodebuildError = (error) => {
202
184
  return /* @__PURE__ */ new Error(`Xcodebuild failed: ${error}`);
203
185
  };
204
186
 
187
+ //#endregion
188
+ //#region src/utils/createXcodebuildLogger.ts
189
+ const createXcodebuildLogger = ({ logPrefix }) => new __hot_updater_cli_tools.BuildLogger({
190
+ logPrefix,
191
+ importantLogPatterns: [
192
+ "error:",
193
+ "** BUILD FAILED **",
194
+ "** ARCHIVE FAILED **",
195
+ "The following build commands failed:",
196
+ "note: Building targets in dependency order",
197
+ "note: Target dependency graph",
198
+ "Running script",
199
+ "[CP-User]",
200
+ "[CP] Check Pods Manifest.lock",
201
+ "Build Succeeded"
202
+ ],
203
+ progressStages: [
204
+ ["Building targets in dependency order", "Target dependency graph"],
205
+ ["Write Auxiliary File", "[CP] Check Pods Manifest.lock"],
206
+ [
207
+ "Running script",
208
+ "[CP-User] [RN]Check rncore",
209
+ "[CP-User] [Hermes] Replace Hermes",
210
+ "[CP-User] [RN]Check FBReactNativeSpec"
211
+ ],
212
+ [
213
+ "Processing",
214
+ "ProcessProductPackaging",
215
+ "ProcessProductPackagingDER"
216
+ ],
217
+ ["Compiling"],
218
+ ["RegisterExecutionPolicyException"],
219
+ ["Touching"],
220
+ ["Building library", "Create Universal Binary"],
221
+ ["Copying"],
222
+ [
223
+ "Build Succeeded",
224
+ "BUILD SUCCEEDED",
225
+ "ARCHIVE SUCCEEDED"
226
+ ]
227
+ ]
228
+ });
229
+
230
+ //#endregion
231
+ //#region src/utils/runXcodebuildWithLogging.ts
232
+ let cachedXcbeautifyAvailability = null;
233
+ const TERMINATION_SIGNALS = ["SIGINT", "SIGTERM"];
234
+ const IGNORED_PIPELINE_ERROR_CODES = ["EPIPE", "ERR_STREAM_PREMATURE_CLOSE"];
235
+ const isXcbeautifyAvailable = async () => {
236
+ if (cachedXcbeautifyAvailability !== null) return cachedXcbeautifyAvailability;
237
+ try {
238
+ await (0, execa.execa)("which", ["xcbeautify"]);
239
+ cachedXcbeautifyAvailability = true;
240
+ return true;
241
+ } catch {
242
+ cachedXcbeautifyAvailability = false;
243
+ return false;
244
+ }
245
+ };
246
+ const createRawXcodebuildProcess = ({ args, sourceDir }) => {
247
+ return (0, execa.execa)("xcodebuild", args, {
248
+ cwd: sourceDir,
249
+ all: true,
250
+ env: {
251
+ ...process.env,
252
+ NSUnbufferedIO: "YES"
253
+ }
254
+ });
255
+ };
256
+ const createXcbeautifyProcess = ({ cwd }) => {
257
+ return (0, execa.execa)("xcbeautify", [], {
258
+ cwd,
259
+ all: true
260
+ });
261
+ };
262
+ const getErrorCode = (error) => {
263
+ if (typeof error !== "object" || error === null || !("code" in error)) return "";
264
+ const { code } = error;
265
+ return typeof code === "string" ? code : "";
266
+ };
267
+ const shouldIgnorePipelineError = (error) => {
268
+ const errorCode = getErrorCode(error);
269
+ return IGNORED_PIPELINE_ERROR_CODES.includes(errorCode);
270
+ };
271
+ const createXcodebuildOutputPipingPromise = async ({ xcodebuildOutput, xcbeautifyInput }) => {
272
+ try {
273
+ return await (0, stream_promises.pipeline)(xcodebuildOutput, xcbeautifyInput);
274
+ } catch (error) {
275
+ if (shouldIgnorePipelineError(error)) return;
276
+ throw error;
277
+ }
278
+ };
279
+ const processOutputStream = async ({ outputStream, logger }) => {
280
+ if (!outputStream) return;
281
+ await logger.processStream(outputStream);
282
+ };
283
+ const runXcodebuildWithLogging = async ({ args, sourceDir, successMessage, failureMessage, logPrefix }) => {
284
+ const useXcbeautify = await isXcbeautifyAvailable();
285
+ const logger = createXcodebuildLogger({ logPrefix });
286
+ await logger.start();
287
+ const activeProcesses = [];
288
+ const terminateActiveProcesses = () => {
289
+ for (const activeProcess of activeProcesses) activeProcess.kill();
290
+ };
291
+ for (const signal of TERMINATION_SIGNALS) process.once(signal, terminateActiveProcesses);
292
+ try {
293
+ if (useXcbeautify) {
294
+ const xcodebuildProcess = createRawXcodebuildProcess({
295
+ args,
296
+ sourceDir
297
+ });
298
+ const xcbeautifyProcess = createXcbeautifyProcess({ cwd: sourceDir });
299
+ activeProcesses.push(xcodebuildProcess, xcbeautifyProcess);
300
+ const xcodebuildOutput = xcodebuildProcess.all ?? xcodebuildProcess.stdout;
301
+ const xcbeautifyInput = xcbeautifyProcess.stdin;
302
+ if (!xcodebuildOutput || !xcbeautifyInput) throw new Error("Failed to pipe xcodebuild output into xcbeautify");
303
+ const pipingPromise = createXcodebuildOutputPipingPromise({
304
+ xcodebuildOutput,
305
+ xcbeautifyInput
306
+ });
307
+ await processOutputStream({
308
+ logger,
309
+ outputStream: xcbeautifyProcess.all ?? xcbeautifyProcess.stdout
310
+ });
311
+ await Promise.all([
312
+ xcbeautifyProcess,
313
+ xcodebuildProcess,
314
+ pipingPromise
315
+ ]);
316
+ } else {
317
+ const xcodebuildProcess = createRawXcodebuildProcess({
318
+ args,
319
+ sourceDir
320
+ });
321
+ activeProcesses.push(xcodebuildProcess);
322
+ await processOutputStream({
323
+ logger,
324
+ outputStream: xcodebuildProcess.all ?? xcodebuildProcess.stdout
325
+ });
326
+ await xcodebuildProcess;
327
+ }
328
+ logger.stop(successMessage);
329
+ } catch (error) {
330
+ logger.writeError(error);
331
+ logger.stop(failureMessage, false);
332
+ throw error;
333
+ } finally {
334
+ for (const signal of TERMINATION_SIGNALS) process.off(signal, terminateActiveProcesses);
335
+ await logger.close();
336
+ }
337
+ };
338
+
205
339
  //#endregion
206
340
  //#region src/builder/archiveXcodeProject.ts
207
- const archiveXcodeProject = async ({ sourceDir, platform, schemeConfig }) => {
341
+ const archiveXcodeProject = async ({ sourceDir, platform, installPods, destination, extraParams, configuration, xcodeScheme, xcconfig, logPrefix }) => {
208
342
  const xcodeProject = await parseXcodeProjectInfo(sourceDir);
209
- if (schemeConfig.installPods ?? true) await installPodsIfNeeded(sourceDir);
343
+ if (installPods) await installPodsIfNeeded(sourceDir);
210
344
  const tmpDir = await createRandomTmpDir();
211
345
  const archiveName = `${xcodeProject.name.replace(".xcworkspace", "").replace(".xcodeproj", "")}.xcarchive`;
212
346
  const archivePath = path.default.join(tmpDir, archiveName);
213
347
  const archiveArgs = prepareArchiveArgs({
214
348
  archivePath,
215
349
  platform,
216
- schemeConfig,
217
350
  sourceDir,
218
- xcodeProject
351
+ xcodeProject,
352
+ configuration,
353
+ extraParams,
354
+ destination,
355
+ xcconfig,
356
+ xcodeScheme
219
357
  });
220
358
  __hot_updater_cli_tools.p.log.info(`Xcode Archive Settings:
221
359
  Project ${xcodeProject.name}
222
- Scheme ${schemeConfig.scheme}
360
+ Scheme ${xcodeScheme}
223
361
  Platform ${platform}
224
362
  Command xcodebuild ${archiveArgs.join(" ")}
225
363
  `);
226
- const logger = createXcodebuildLogger();
227
- logger.start(`${xcodeProject.name} (Archive)`);
228
364
  try {
229
- const process$1 = (0, execa.execa)("xcodebuild", archiveArgs, { cwd: sourceDir });
230
- for await (const line of process$1) logger.processLine(line);
231
- logger.stop("Archive completed successfully");
365
+ await runXcodebuildWithLogging({
366
+ args: archiveArgs,
367
+ failureMessage: "Archive failed",
368
+ logPrefix,
369
+ sourceDir,
370
+ successMessage: "Archive completed successfully"
371
+ });
232
372
  return { archivePath };
233
373
  } catch (error) {
234
- logger.stop("Archive failed", false);
235
374
  throw prettifyXcodebuildError(error);
236
375
  }
237
376
  };
238
- const prepareArchiveArgs = ({ archivePath, platform, schemeConfig, sourceDir, xcodeProject }) => {
377
+ const prepareArchiveArgs = ({ xcodeScheme, archivePath, platform, sourceDir, xcodeProject, configuration = "Release", xcconfig, extraParams, destination = [] }) => {
239
378
  const args = [
240
379
  xcodeProject.isWorkspace ? "-workspace" : "-project",
241
380
  path.default.join(sourceDir, xcodeProject.name),
242
381
  "-scheme",
243
- schemeConfig.scheme,
382
+ xcodeScheme,
244
383
  "-configuration",
245
- schemeConfig.configuration || "Release",
384
+ configuration,
246
385
  "archive",
247
386
  "-archivePath",
248
387
  archivePath,
249
388
  `HOT_UPDATER_MIN_BUNDLE_ID=${(0, __hot_updater_plugin_core.generateMinBundleId)()}`
250
389
  ];
251
- if (schemeConfig.xcconfig) args.push("-xcconfig", schemeConfig.xcconfig);
252
- if (schemeConfig.extraParams) args.push(...schemeConfig.extraParams);
390
+ if (xcconfig) args.push("-xcconfig", xcconfig);
391
+ if (extraParams) args.push(...extraParams);
253
392
  const resolvedDestinations = resolveDestinations({
254
- destinations: schemeConfig.destination || [],
393
+ destinations: destination,
255
394
  useGeneric: true
256
395
  });
257
396
  if (resolvedDestinations.length === 0) resolvedDestinations.push(getDefaultDestination({
@@ -259,209 +398,94 @@ const prepareArchiveArgs = ({ archivePath, platform, schemeConfig, sourceDir, xc
259
398
  platform,
260
399
  useGeneric: true
261
400
  }));
262
- for (const destination of resolvedDestinations) args.push("-destination", destination);
263
- return args;
264
- };
265
-
266
- //#endregion
267
- //#region src/builder/exportXcodeArchive.ts
268
- const exportXcodeArchive = async ({ archivePath, sourceDir, exportExtraParams, exportOptionsPlist }) => {
269
- const exportPath = await createRandomTmpDir();
270
- const exportArgs = prepareExportArgs({
271
- archivePath,
272
- exportPath,
273
- exportOptionsPlist,
274
- exportExtraParams
275
- });
276
- const spinner = __hot_updater_cli_tools.p.spinner();
277
- spinner.start("Exporting archive to IPA");
278
- try {
279
- await (0, execa.execa)("xcodebuild", exportArgs, { cwd: sourceDir });
280
- spinner.stop("Archive exported successfully");
281
- return { exportPath };
282
- } catch (error) {
283
- spinner.stop("Export failed");
284
- throw prettifyXcodebuildError(error);
285
- }
286
- };
287
- const prepareExportArgs = ({ exportPath, archivePath, exportExtraParams, exportOptionsPlist }) => {
288
- const args = [
289
- "-exportArchive",
290
- "-archivePath",
291
- archivePath,
292
- "-exportPath",
293
- exportPath
294
- ];
295
- if (exportExtraParams) args.push(...exportExtraParams);
296
- args.push("-exportOptionsPlist", exportOptionsPlist);
401
+ for (const destination$1 of resolvedDestinations) args.push("-destination", destination$1);
297
402
  return args;
298
403
  };
299
404
 
300
- //#endregion
301
- //#region src/utils/assertXcodebuildExist.ts
302
- const assertXcodebuildExist = async () => {
303
- try {
304
- await (0, execa.execa)("which", ["xcodebuild"]);
305
- } catch {
306
- __hot_updater_cli_tools.p.log.error(`${__hot_updater_cli_tools.colors.blueBright("xcodebuild")} resolve failed. Ensure that xcode is installed on your machine or xcodebuild is included in your path`);
307
- process.exit(1);
308
- }
309
- };
310
-
311
- //#endregion
312
- //#region src/utils/enrichNativeBuildIosScheme.ts
313
- const resolveExportOptionsPlist = (exportOptionsPlist) => {
314
- const resolvedExportOptionsPlist = exportOptionsPlist ? path.default.isAbsolute(exportOptionsPlist) ? exportOptionsPlist : path.default.resolve((0, __hot_updater_cli_tools.getCwd)(), exportOptionsPlist) : void 0;
315
- if (resolvedExportOptionsPlist && !fs.default.existsSync(resolvedExportOptionsPlist)) {
316
- __hot_updater_cli_tools.p.log.error(`exportOptionsPlist doesn't exist in ${resolvedExportOptionsPlist}`);
317
- process.exit(1);
318
- }
319
- return resolvedExportOptionsPlist;
320
- };
321
- const enrichNativeBuildIosScheme = async (scheme) => {
322
- return {
323
- platform: "ios",
324
- installPods: false,
325
- configuration: "Release",
326
- destination: [],
327
- ...scheme,
328
- exportOptionsPlist: resolveExportOptionsPlist(scheme.exportOptionsPlist)
329
- };
330
- };
331
-
332
- //#endregion
333
- //#region src/buildIos.ts
334
- const buildIos = async ({ schemeConfig: _schemeConfig }) => {
335
- await assertXcodebuildExist();
336
- const iosProjectRoot = path.default.join((0, __hot_updater_cli_tools.getCwd)(), "ios");
337
- const schemeConfig = await enrichNativeBuildIosScheme(_schemeConfig);
338
- const buildDirectory = await createRandomTmpDir();
339
- const archiveDir = path.default.join(buildDirectory, "archive");
340
- const exportDir = path.default.join(buildDirectory, "export");
341
- await fs.default.promises.mkdir(archiveDir, { recursive: true });
342
- const { archivePath } = await archiveXcodeProject({
343
- platform: schemeConfig.platform,
344
- schemeConfig,
345
- sourceDir: iosProjectRoot
346
- });
347
- const finalArchivePath = path.default.join(archiveDir, path.default.basename(archivePath));
348
- await fs.default.promises.cp(archivePath, finalArchivePath, { recursive: true });
349
- const appFromArchive = extractAppFromXcarchive(finalArchivePath);
350
- if (appFromArchive) {
351
- await fs.default.promises.cp(appFromArchive, path.default.join(archiveDir, path.default.basename(appFromArchive)), { recursive: true });
352
- __hot_updater_cli_tools.p.log.success(".app extracted from .xcarchive");
353
- }
354
- if (schemeConfig.exportOptionsPlist) {
355
- await fs.default.promises.mkdir(exportDir, { recursive: true });
356
- const { exportPath } = await exportXcodeArchive({
357
- archivePath,
358
- exportExtraParams: schemeConfig.exportExtraParams,
359
- exportOptionsPlist: schemeConfig.exportOptionsPlist,
360
- sourceDir: iosProjectRoot
361
- });
362
- await fs.default.promises.cp(exportPath, exportDir, { recursive: true });
363
- const ipaFile = fs.default.readdirSync(exportDir).find((file) => file.endsWith(".ipa"));
364
- if (ipaFile) return {
365
- buildDirectory,
366
- buildArtifactPath: path.default.join(exportDir, ipaFile)
367
- };
368
- throw new Error("IPA file not found after export");
369
- }
370
- return {
371
- buildDirectory,
372
- buildArtifactPath: finalArchivePath
373
- };
374
- };
375
- const extractAppFromXcarchive = (archivePath) => {
376
- const productsPath = path.default.join(archivePath, "Products", "Applications");
377
- if (!fs.default.existsSync(productsPath)) return;
378
- const appFile = fs.default.readdirSync(productsPath).find((file) => file.endsWith(".app"));
379
- if (appFile) return path.default.join(productsPath, appFile);
380
- __hot_updater_cli_tools.p.log.warn("Failed to extract .app from xcarchive");
381
- };
382
-
383
405
  //#endregion
384
406
  //#region src/builder/buildXcodeProject.ts
385
- const buildXcodeProject = async ({ sourceDir, platform, scheme, configuration, deviceType, udid, installPods, extraParams }) => {
407
+ const buildXcodeProject = async ({ sourceDir, platform, xcodeScheme, configuration, deviceType, logPrefix, destination = [], useGenericDestination = false, installPods, extraParams }) => {
386
408
  const xcodeProject = await parseXcodeProjectInfo(sourceDir);
387
- if (installPods ?? true) await installPodsIfNeeded(sourceDir);
409
+ if (installPods) await installPodsIfNeeded(sourceDir);
388
410
  const derivedDataPath = await createRandomTmpDir();
411
+ const resolvedDestinations = resolveDestinations({
412
+ destinations: destination,
413
+ useGeneric: useGenericDestination
414
+ });
415
+ if (resolvedDestinations.length === 0) resolvedDestinations.push(getDefaultDestination({
416
+ deviceType,
417
+ platform,
418
+ useGeneric: useGenericDestination
419
+ }));
389
420
  const buildArgs = prepareBuildArgs({
390
421
  configuration,
391
422
  derivedDataPath,
392
423
  deviceType,
424
+ resolvedDestinations,
393
425
  extraParams,
394
426
  platform,
395
- scheme,
427
+ xcodeScheme,
396
428
  sourceDir,
397
- udid,
398
429
  xcodeProject
399
430
  });
400
431
  __hot_updater_cli_tools.p.log.info(`Xcode Build Settings:
401
432
  Project ${xcodeProject.name}
402
- Scheme ${scheme}
433
+ Scheme ${xcodeScheme}
403
434
  Configuration ${configuration}
404
435
  Platform ${platform}
405
436
  Device Type ${deviceType}
406
437
  Command xcodebuild ${buildArgs.join(" ")}
407
438
  `);
408
- const logger = createXcodebuildLogger();
409
- logger.start(`${xcodeProject.name} (Build)`);
410
439
  try {
411
- const process$1 = (0, execa.execa)("xcodebuild", buildArgs, { cwd: sourceDir });
412
- for await (const line of process$1) logger.processLine(line);
413
- logger.stop("Build completed successfully");
440
+ await runXcodebuildWithLogging({
441
+ args: buildArgs,
442
+ sourceDir,
443
+ logPrefix,
444
+ successMessage: "Build completed successfully",
445
+ failureMessage: "Build failed"
446
+ });
414
447
  return await getBuildSettings({
415
448
  configuration,
416
449
  derivedDataPath,
417
- deviceType,
418
- platform,
419
- scheme,
450
+ resolvedDestinations,
451
+ xcodeScheme,
420
452
  sourceDir,
421
- udid,
422
453
  xcodeProject
423
454
  });
424
455
  } catch (error) {
425
- logger.stop("Build failed", false);
426
456
  throw prettifyXcodebuildError(error);
427
457
  }
428
458
  };
429
- const prepareBuildArgs = ({ xcodeProject, sourceDir, platform, scheme, configuration, deviceType, udid, derivedDataPath, extraParams }) => {
459
+ const prepareBuildArgs = ({ xcodeProject, sourceDir, platform, xcodeScheme, configuration, deviceType, resolvedDestinations, derivedDataPath, extraParams }) => {
430
460
  const sdk = deviceType === "simulator" ? platformConfigs[platform].simulatorSdk : platformConfigs[platform].deviceSdk;
431
- const destination = udid ? `id=${udid}` : deviceType === "simulator" ? platformConfigs[platform].simulatorDestination : platformConfigs[platform].deviceDestination;
432
461
  const args = [
433
462
  xcodeProject.isWorkspace ? "-workspace" : "-project",
434
463
  path.default.join(sourceDir, xcodeProject.name),
435
464
  "-scheme",
436
- scheme,
465
+ xcodeScheme,
437
466
  "-configuration",
438
467
  configuration,
439
468
  "-sdk",
440
469
  sdk,
441
- "-destination",
442
- destination,
443
470
  "-derivedDataPath",
444
471
  derivedDataPath,
445
472
  `HOT_UPDATER_MIN_BUNDLE_ID=${(0, __hot_updater_plugin_core.generateMinBundleId)()}`,
446
473
  "build"
447
474
  ];
448
475
  if (extraParams) args.push(...extraParams);
476
+ for (const dest of resolvedDestinations) args.push("-destination", dest);
449
477
  return args;
450
478
  };
451
- const getBuildSettings = async ({ xcodeProject, sourceDir, platform, scheme, configuration, deviceType, udid, derivedDataPath }) => {
452
- const sdk = deviceType === "simulator" ? platformConfigs[platform].simulatorSdk : platformConfigs[platform].deviceSdk;
453
- const destination = udid ? `id=${udid}` : deviceType === "simulator" ? platformConfigs[platform].simulatorDestination : platformConfigs[platform].deviceDestination;
479
+ const getBuildSettings = async ({ xcodeProject, sourceDir, xcodeScheme, configuration, resolvedDestinations, derivedDataPath }) => {
480
+ const destinationArgs = resolvedDestinations.flatMap((dest) => ["-destination", dest]);
454
481
  const { stdout: buildSettings } = await (0, execa.execa)("xcodebuild", [
455
482
  xcodeProject.isWorkspace ? "-workspace" : "-project",
456
483
  xcodeProject.name,
457
484
  "-scheme",
458
- scheme,
485
+ xcodeScheme,
459
486
  "-configuration",
460
487
  configuration,
461
- "-sdk",
462
- sdk,
463
- "-destination",
464
- destination,
488
+ ...destinationArgs,
465
489
  "-derivedDataPath",
466
490
  derivedDataPath,
467
491
  "-showBuildSettings",
@@ -483,6 +507,137 @@ const getBuildSettings = async ({ xcodeProject, sourceDir, platform, scheme, con
483
507
  };
484
508
  };
485
509
 
510
+ //#endregion
511
+ //#region src/builder/exportXcodeArchive.ts
512
+ const exportXcodeArchive = async ({ archivePath, sourceDir, exportExtraParams, exportOptionsPlist, logPrefix }) => {
513
+ const exportPath = await createRandomTmpDir();
514
+ const exportArgs = prepareExportArgs({
515
+ archivePath,
516
+ exportPath,
517
+ exportOptionsPlist,
518
+ exportExtraParams
519
+ });
520
+ try {
521
+ await runXcodebuildWithLogging({
522
+ args: exportArgs,
523
+ sourceDir,
524
+ successMessage: "Archive exported successfully",
525
+ failureMessage: "Export failed",
526
+ logPrefix
527
+ });
528
+ return { exportPath };
529
+ } catch (error) {
530
+ throw prettifyXcodebuildError(error);
531
+ }
532
+ };
533
+ const prepareExportArgs = ({ exportPath, archivePath, exportExtraParams, exportOptionsPlist }) => {
534
+ const args = [
535
+ "-exportArchive",
536
+ "-archivePath",
537
+ archivePath,
538
+ "-exportPath",
539
+ exportPath
540
+ ];
541
+ if (exportExtraParams) args.push(...exportExtraParams);
542
+ args.push("-exportOptionsPlist", exportOptionsPlist);
543
+ return args;
544
+ };
545
+
546
+ //#endregion
547
+ //#region src/utils/assertXcodebuildExist.ts
548
+ const assertXcodebuildExist = async () => {
549
+ try {
550
+ await (0, execa.execa)("which", ["xcodebuild"]);
551
+ } catch {
552
+ __hot_updater_cli_tools.p.log.error(`${__hot_updater_cli_tools.colors.blueBright("xcodebuild")} resolve failed. Ensure that xcode is installed on your machine or xcodebuild is included in your path`);
553
+ process.exit(1);
554
+ }
555
+ };
556
+
557
+ //#endregion
558
+ //#region src/buildIos.ts
559
+ const buildIos = async ({ schemeConfig }) => {
560
+ await assertXcodebuildExist();
561
+ const iosProjectRoot = path.default.join((0, __hot_updater_cli_tools.getCwd)(), "ios");
562
+ const buildDirectory = await createRandomTmpDir();
563
+ const archiveDir = path.default.join(buildDirectory, "archive");
564
+ const exportDir = path.default.join(buildDirectory, "export");
565
+ await fs.default.promises.mkdir(archiveDir, { recursive: true });
566
+ if (schemeConfig.simulator) {
567
+ if (schemeConfig.destination.length) {
568
+ __hot_updater_cli_tools.p.log.error("The scheme option 'simulator' is not compatible with the 'destination' option.");
569
+ process.exit(1);
570
+ }
571
+ const appDir = path.default.join(buildDirectory, "app");
572
+ await fs.default.promises.mkdir(appDir, { recursive: true });
573
+ const { appPath } = await buildXcodeProject({
574
+ sourceDir: iosProjectRoot,
575
+ logPrefix: `ios-${schemeConfig.hotUpdaterSchemeName}-build`,
576
+ platform: schemeConfig.platform,
577
+ xcodeScheme: schemeConfig.scheme,
578
+ configuration: schemeConfig.configuration,
579
+ deviceType: "simulator",
580
+ installPods: schemeConfig.installPods,
581
+ extraParams: schemeConfig.extraParams,
582
+ useGenericDestination: true
583
+ });
584
+ const finalAppPath = path.default.join(appDir, path.default.basename(appPath));
585
+ await fs.default.promises.cp(appPath, finalAppPath, { recursive: true });
586
+ __hot_updater_cli_tools.p.log.success(`.app file created: ${path.default.basename(appPath)}`);
587
+ return {
588
+ buildDirectory,
589
+ buildArtifactPath: finalAppPath
590
+ };
591
+ } else {
592
+ const { archivePath } = await archiveXcodeProject({
593
+ platform: schemeConfig.platform,
594
+ sourceDir: iosProjectRoot,
595
+ xcconfig: schemeConfig.xcconfig,
596
+ xcodeScheme: schemeConfig.scheme,
597
+ destination: schemeConfig.destination,
598
+ extraParams: schemeConfig.extraParams,
599
+ configuration: schemeConfig.configuration,
600
+ installPods: schemeConfig.installPods,
601
+ logPrefix: `ios-${schemeConfig.hotUpdaterSchemeName}-build-archive`
602
+ });
603
+ const finalArchivePath = path.default.join(archiveDir, path.default.basename(archivePath));
604
+ await fs.default.promises.cp(archivePath, finalArchivePath, { recursive: true });
605
+ const appFromArchive = extractAppFromXcarchive(finalArchivePath);
606
+ if (appFromArchive) {
607
+ await fs.default.promises.cp(appFromArchive, path.default.join(archiveDir, path.default.basename(appFromArchive)), { recursive: true });
608
+ __hot_updater_cli_tools.p.log.success(".app extracted from .xcarchive");
609
+ }
610
+ if (schemeConfig.exportOptionsPlist) {
611
+ await fs.default.promises.mkdir(exportDir, { recursive: true });
612
+ const { exportPath } = await exportXcodeArchive({
613
+ archivePath,
614
+ exportExtraParams: schemeConfig.exportExtraParams,
615
+ exportOptionsPlist: schemeConfig.exportOptionsPlist,
616
+ sourceDir: iosProjectRoot,
617
+ logPrefix: `ios-${schemeConfig.hotUpdaterSchemeName}-build-export`
618
+ });
619
+ await fs.default.promises.cp(exportPath, exportDir, { recursive: true });
620
+ const ipaFile = fs.default.readdirSync(exportDir).find((file) => file.endsWith(".ipa"));
621
+ if (ipaFile) return {
622
+ buildDirectory,
623
+ buildArtifactPath: path.default.join(exportDir, ipaFile)
624
+ };
625
+ throw new Error("IPA file not found after export");
626
+ }
627
+ return {
628
+ buildDirectory,
629
+ buildArtifactPath: finalArchivePath
630
+ };
631
+ }
632
+ };
633
+ const extractAppFromXcarchive = (archivePath) => {
634
+ const productsPath = path.default.join(archivePath, "Products", "Applications");
635
+ if (!fs.default.existsSync(productsPath)) return;
636
+ const appFile = fs.default.readdirSync(productsPath).find((file) => file.endsWith(".app"));
637
+ if (appFile) return path.default.join(productsPath, appFile);
638
+ __hot_updater_cli_tools.p.log.warn("Failed to extract .app from xcarchive");
639
+ };
640
+
486
641
  //#endregion
487
642
  //#region src/runner/deviceRunner.ts
488
643
  const installAndLaunchOnDevice = async ({ device, appPath, options }) => {
@@ -768,10 +923,9 @@ const Device = {
768
923
 
769
924
  //#endregion
770
925
  //#region src/runIos.ts
771
- const runIos = async ({ schemeConfig: _schemeConfig, runOption }) => {
926
+ const runIos = async ({ schemeConfig, runOption }) => {
772
927
  const { interactive, device: deviceOption } = runOption;
773
928
  const iosProjectPath = path.default.join((0, __hot_updater_cli_tools.getCwd)(), "ios");
774
- const schemeConfig = await enrichNativeBuildIosScheme(_schemeConfig);
775
929
  const device = await Device.selectTargetDevice({
776
930
  platform: "ios",
777
931
  deviceOption,
@@ -789,12 +943,13 @@ const runIos = async ({ schemeConfig: _schemeConfig, runOption }) => {
789
943
  const result$1 = await buildXcodeProject({
790
944
  sourceDir: iosProjectPath,
791
945
  platform: schemeConfig.platform,
792
- scheme: schemeConfig.scheme,
946
+ xcodeScheme: schemeConfig.scheme,
793
947
  configuration: schemeConfig.configuration,
794
948
  deviceType: selectedDevice.type,
795
- udid: selectedDevice.udid,
949
+ destination: [{ id: selectedDevice.udid }],
796
950
  installPods: schemeConfig.installPods,
797
- extraParams: schemeConfig.extraParams
951
+ extraParams: schemeConfig.extraParams,
952
+ logPrefix: `ios-${schemeConfig.hotUpdaterSchemeName}-run`
798
953
  });
799
954
  const runnerOptions$1 = {
800
955
  bundleIdentifier: schemeConfig.bundleIdentifier,
@@ -814,12 +969,13 @@ const runIos = async ({ schemeConfig: _schemeConfig, runOption }) => {
814
969
  const result = await buildXcodeProject({
815
970
  sourceDir: iosProjectPath,
816
971
  platform: schemeConfig.platform,
817
- scheme: schemeConfig.scheme,
972
+ xcodeScheme: schemeConfig.scheme,
818
973
  configuration: schemeConfig.configuration,
819
974
  deviceType: device.type,
820
- udid: device.udid,
975
+ destination: [{ id: device.udid }],
821
976
  installPods: schemeConfig.installPods,
822
- extraParams: schemeConfig.extraParams
977
+ extraParams: schemeConfig.extraParams,
978
+ logPrefix: `ios-${schemeConfig.hotUpdaterSchemeName}-run`
823
979
  });
824
980
  const runnerOptions = {
825
981
  bundleIdentifier: schemeConfig.bundleIdentifier,
@@ -843,6 +999,30 @@ const runIos = async ({ schemeConfig: _schemeConfig, runOption }) => {
843
999
  return result;
844
1000
  };
845
1001
 
1002
+ //#endregion
1003
+ //#region src/utils/enrichNativeBuildIosScheme.ts
1004
+ const resolveExportOptionsPlist = (exportOptionsPlist) => {
1005
+ const resolvedExportOptionsPlist = exportOptionsPlist ? path.default.isAbsolute(exportOptionsPlist) ? exportOptionsPlist : path.default.resolve((0, __hot_updater_cli_tools.getCwd)(), exportOptionsPlist) : void 0;
1006
+ if (resolvedExportOptionsPlist && !fs.default.existsSync(resolvedExportOptionsPlist)) {
1007
+ __hot_updater_cli_tools.p.log.error(`exportOptionsPlist doesn't exist in ${resolvedExportOptionsPlist}`);
1008
+ process.exit(1);
1009
+ }
1010
+ return resolvedExportOptionsPlist;
1011
+ };
1012
+ const enrichNativeBuildIosScheme = async ({ scheme, hotUpdaterSchemeName }) => {
1013
+ return {
1014
+ platform: "ios",
1015
+ installPods: false,
1016
+ configuration: "Release",
1017
+ destination: [],
1018
+ simulator: false,
1019
+ ...scheme,
1020
+ exportOptionsPlist: resolveExportOptionsPlist(scheme.exportOptionsPlist),
1021
+ hotUpdaterSchemeName
1022
+ };
1023
+ };
1024
+
846
1025
  //#endregion
847
1026
  exports.buildIos = buildIos;
1027
+ exports.enrichNativeBuildIosScheme = enrichNativeBuildIosScheme;
848
1028
  exports.runIos = runIos;