@hot-updater/apple-helper 0.25.8 → 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.js CHANGED
@@ -6,6 +6,7 @@ import { ExecaError, execa } from "execa";
6
6
  import { mkdtemp } from "node:fs/promises";
7
7
  import os, { tmpdir } from "node:os";
8
8
  import { join } from "node:path";
9
+ import { pipeline } from "stream/promises";
9
10
 
10
11
  //#region src/utils/cocoapods.ts
11
12
  const installPodsIfNeeded = async (sourceDir) => {
@@ -71,26 +72,6 @@ const createRandomTmpDir = async (prefix = "apple-helper-") => {
71
72
  return await mkdtemp(join(tmpdir(), prefix));
72
73
  };
73
74
 
74
- //#endregion
75
- //#region src/utils/createXcodebuildLogger.ts
76
- const createXcodebuildLogger = () => new BuildLogger({
77
- failurePatterns: ["BUILD FAILED", "ARCHIVE FAILED"],
78
- importantLogPatterns: [
79
- "error:",
80
- "** BUILD FAILED **",
81
- "** ARCHIVE FAILED **",
82
- "The following build commands failed:"
83
- ],
84
- progressMapping: [
85
- [["[CP-User] [RN]Check rncore"], 10],
86
- [["[CP-User] [Hermes] Replace Hermes"], 35],
87
- [["[CP-User] [RN]Check FBReactNativeSpec"], 53],
88
- [["React-FabricComponents"], 66],
89
- [["[CP] Check Pods Manifest.lock"], 90],
90
- [["BUILD SUCCEEDED", "ARCHIVE SUCCEEDED"], 100]
91
- ]
92
- });
93
-
94
75
  //#endregion
95
76
  //#region src/utils/platform.ts
96
77
  /**
@@ -171,56 +152,213 @@ const prettifyXcodebuildError = (error) => {
171
152
  return /* @__PURE__ */ new Error(`Xcodebuild failed: ${error}`);
172
153
  };
173
154
 
155
+ //#endregion
156
+ //#region src/utils/createXcodebuildLogger.ts
157
+ const createXcodebuildLogger = ({ logPrefix }) => new BuildLogger({
158
+ logPrefix,
159
+ importantLogPatterns: [
160
+ "error:",
161
+ "** BUILD FAILED **",
162
+ "** ARCHIVE FAILED **",
163
+ "The following build commands failed:",
164
+ "note: Building targets in dependency order",
165
+ "note: Target dependency graph",
166
+ "Running script",
167
+ "[CP-User]",
168
+ "[CP] Check Pods Manifest.lock",
169
+ "Build Succeeded"
170
+ ],
171
+ progressStages: [
172
+ ["Building targets in dependency order", "Target dependency graph"],
173
+ ["Write Auxiliary File", "[CP] Check Pods Manifest.lock"],
174
+ [
175
+ "Running script",
176
+ "[CP-User] [RN]Check rncore",
177
+ "[CP-User] [Hermes] Replace Hermes",
178
+ "[CP-User] [RN]Check FBReactNativeSpec"
179
+ ],
180
+ [
181
+ "Processing",
182
+ "ProcessProductPackaging",
183
+ "ProcessProductPackagingDER"
184
+ ],
185
+ ["Compiling"],
186
+ ["RegisterExecutionPolicyException"],
187
+ ["Touching"],
188
+ ["Building library", "Create Universal Binary"],
189
+ ["Copying"],
190
+ [
191
+ "Build Succeeded",
192
+ "BUILD SUCCEEDED",
193
+ "ARCHIVE SUCCEEDED"
194
+ ]
195
+ ]
196
+ });
197
+
198
+ //#endregion
199
+ //#region src/utils/runXcodebuildWithLogging.ts
200
+ let cachedXcbeautifyAvailability = null;
201
+ const TERMINATION_SIGNALS = ["SIGINT", "SIGTERM"];
202
+ const IGNORED_PIPELINE_ERROR_CODES = ["EPIPE", "ERR_STREAM_PREMATURE_CLOSE"];
203
+ const isXcbeautifyAvailable = async () => {
204
+ if (cachedXcbeautifyAvailability !== null) return cachedXcbeautifyAvailability;
205
+ try {
206
+ await execa("which", ["xcbeautify"]);
207
+ cachedXcbeautifyAvailability = true;
208
+ return true;
209
+ } catch {
210
+ cachedXcbeautifyAvailability = false;
211
+ return false;
212
+ }
213
+ };
214
+ const createRawXcodebuildProcess = ({ args, sourceDir }) => {
215
+ return execa("xcodebuild", args, {
216
+ cwd: sourceDir,
217
+ all: true,
218
+ env: {
219
+ ...process.env,
220
+ NSUnbufferedIO: "YES"
221
+ }
222
+ });
223
+ };
224
+ const createXcbeautifyProcess = ({ cwd }) => {
225
+ return execa("xcbeautify", [], {
226
+ cwd,
227
+ all: true
228
+ });
229
+ };
230
+ const getErrorCode = (error) => {
231
+ if (typeof error !== "object" || error === null || !("code" in error)) return "";
232
+ const { code } = error;
233
+ return typeof code === "string" ? code : "";
234
+ };
235
+ const shouldIgnorePipelineError = (error) => {
236
+ const errorCode = getErrorCode(error);
237
+ return IGNORED_PIPELINE_ERROR_CODES.includes(errorCode);
238
+ };
239
+ const createXcodebuildOutputPipingPromise = async ({ xcodebuildOutput, xcbeautifyInput }) => {
240
+ try {
241
+ return await pipeline(xcodebuildOutput, xcbeautifyInput);
242
+ } catch (error) {
243
+ if (shouldIgnorePipelineError(error)) return;
244
+ throw error;
245
+ }
246
+ };
247
+ const processOutputStream = async ({ outputStream, logger }) => {
248
+ if (!outputStream) return;
249
+ await logger.processStream(outputStream);
250
+ };
251
+ const runXcodebuildWithLogging = async ({ args, sourceDir, successMessage, failureMessage, logPrefix }) => {
252
+ const useXcbeautify = await isXcbeautifyAvailable();
253
+ const logger = createXcodebuildLogger({ logPrefix });
254
+ await logger.start();
255
+ const activeProcesses = [];
256
+ const terminateActiveProcesses = () => {
257
+ for (const activeProcess of activeProcesses) activeProcess.kill();
258
+ };
259
+ for (const signal of TERMINATION_SIGNALS) process.once(signal, terminateActiveProcesses);
260
+ try {
261
+ if (useXcbeautify) {
262
+ const xcodebuildProcess = createRawXcodebuildProcess({
263
+ args,
264
+ sourceDir
265
+ });
266
+ const xcbeautifyProcess = createXcbeautifyProcess({ cwd: sourceDir });
267
+ activeProcesses.push(xcodebuildProcess, xcbeautifyProcess);
268
+ const xcodebuildOutput = xcodebuildProcess.all ?? xcodebuildProcess.stdout;
269
+ const xcbeautifyInput = xcbeautifyProcess.stdin;
270
+ if (!xcodebuildOutput || !xcbeautifyInput) throw new Error("Failed to pipe xcodebuild output into xcbeautify");
271
+ const pipingPromise = createXcodebuildOutputPipingPromise({
272
+ xcodebuildOutput,
273
+ xcbeautifyInput
274
+ });
275
+ await processOutputStream({
276
+ logger,
277
+ outputStream: xcbeautifyProcess.all ?? xcbeautifyProcess.stdout
278
+ });
279
+ await Promise.all([
280
+ xcbeautifyProcess,
281
+ xcodebuildProcess,
282
+ pipingPromise
283
+ ]);
284
+ } else {
285
+ const xcodebuildProcess = createRawXcodebuildProcess({
286
+ args,
287
+ sourceDir
288
+ });
289
+ activeProcesses.push(xcodebuildProcess);
290
+ await processOutputStream({
291
+ logger,
292
+ outputStream: xcodebuildProcess.all ?? xcodebuildProcess.stdout
293
+ });
294
+ await xcodebuildProcess;
295
+ }
296
+ logger.stop(successMessage);
297
+ } catch (error) {
298
+ logger.writeError(error);
299
+ logger.stop(failureMessage, false);
300
+ throw error;
301
+ } finally {
302
+ for (const signal of TERMINATION_SIGNALS) process.off(signal, terminateActiveProcesses);
303
+ await logger.close();
304
+ }
305
+ };
306
+
174
307
  //#endregion
175
308
  //#region src/builder/archiveXcodeProject.ts
176
- const archiveXcodeProject = async ({ sourceDir, platform, schemeConfig }) => {
309
+ const archiveXcodeProject = async ({ sourceDir, platform, installPods, destination, extraParams, configuration, xcodeScheme, xcconfig, logPrefix }) => {
177
310
  const xcodeProject = await parseXcodeProjectInfo(sourceDir);
178
- if (schemeConfig.installPods ?? true) await installPodsIfNeeded(sourceDir);
311
+ if (installPods) await installPodsIfNeeded(sourceDir);
179
312
  const tmpDir = await createRandomTmpDir();
180
313
  const archiveName = `${xcodeProject.name.replace(".xcworkspace", "").replace(".xcodeproj", "")}.xcarchive`;
181
314
  const archivePath = path.join(tmpDir, archiveName);
182
315
  const archiveArgs = prepareArchiveArgs({
183
316
  archivePath,
184
317
  platform,
185
- schemeConfig,
186
318
  sourceDir,
187
- xcodeProject
319
+ xcodeProject,
320
+ configuration,
321
+ extraParams,
322
+ destination,
323
+ xcconfig,
324
+ xcodeScheme
188
325
  });
189
326
  p.log.info(`Xcode Archive Settings:
190
327
  Project ${xcodeProject.name}
191
- Scheme ${schemeConfig.scheme}
328
+ Scheme ${xcodeScheme}
192
329
  Platform ${platform}
193
330
  Command xcodebuild ${archiveArgs.join(" ")}
194
331
  `);
195
- const logger = createXcodebuildLogger();
196
- logger.start(`${xcodeProject.name} (Archive)`);
197
332
  try {
198
- const process$1 = execa("xcodebuild", archiveArgs, { cwd: sourceDir });
199
- for await (const line of process$1) logger.processLine(line);
200
- logger.stop("Archive completed successfully");
333
+ await runXcodebuildWithLogging({
334
+ args: archiveArgs,
335
+ failureMessage: "Archive failed",
336
+ logPrefix,
337
+ sourceDir,
338
+ successMessage: "Archive completed successfully"
339
+ });
201
340
  return { archivePath };
202
341
  } catch (error) {
203
- logger.stop("Archive failed", false);
204
342
  throw prettifyXcodebuildError(error);
205
343
  }
206
344
  };
207
- const prepareArchiveArgs = ({ archivePath, platform, schemeConfig, sourceDir, xcodeProject }) => {
345
+ const prepareArchiveArgs = ({ xcodeScheme, archivePath, platform, sourceDir, xcodeProject, configuration = "Release", xcconfig, extraParams, destination = [] }) => {
208
346
  const args = [
209
347
  xcodeProject.isWorkspace ? "-workspace" : "-project",
210
348
  path.join(sourceDir, xcodeProject.name),
211
349
  "-scheme",
212
- schemeConfig.scheme,
350
+ xcodeScheme,
213
351
  "-configuration",
214
- schemeConfig.configuration || "Release",
352
+ configuration,
215
353
  "archive",
216
354
  "-archivePath",
217
355
  archivePath,
218
356
  `HOT_UPDATER_MIN_BUNDLE_ID=${generateMinBundleId()}`
219
357
  ];
220
- if (schemeConfig.xcconfig) args.push("-xcconfig", schemeConfig.xcconfig);
221
- if (schemeConfig.extraParams) args.push(...schemeConfig.extraParams);
358
+ if (xcconfig) args.push("-xcconfig", xcconfig);
359
+ if (extraParams) args.push(...extraParams);
222
360
  const resolvedDestinations = resolveDestinations({
223
- destinations: schemeConfig.destination || [],
361
+ destinations: destination,
224
362
  useGeneric: true
225
363
  });
226
364
  if (resolvedDestinations.length === 0) resolvedDestinations.push(getDefaultDestination({
@@ -228,209 +366,94 @@ const prepareArchiveArgs = ({ archivePath, platform, schemeConfig, sourceDir, xc
228
366
  platform,
229
367
  useGeneric: true
230
368
  }));
231
- for (const destination of resolvedDestinations) args.push("-destination", destination);
369
+ for (const destination$1 of resolvedDestinations) args.push("-destination", destination$1);
232
370
  return args;
233
371
  };
234
372
 
235
- //#endregion
236
- //#region src/builder/exportXcodeArchive.ts
237
- const exportXcodeArchive = async ({ archivePath, sourceDir, exportExtraParams, exportOptionsPlist }) => {
238
- const exportPath = await createRandomTmpDir();
239
- const exportArgs = prepareExportArgs({
240
- archivePath,
241
- exportPath,
242
- exportOptionsPlist,
243
- exportExtraParams
244
- });
245
- const spinner = p.spinner();
246
- spinner.start("Exporting archive to IPA");
247
- try {
248
- await execa("xcodebuild", exportArgs, { cwd: sourceDir });
249
- spinner.stop("Archive exported successfully");
250
- return { exportPath };
251
- } catch (error) {
252
- spinner.stop("Export failed");
253
- throw prettifyXcodebuildError(error);
254
- }
255
- };
256
- const prepareExportArgs = ({ exportPath, archivePath, exportExtraParams, exportOptionsPlist }) => {
257
- const args = [
258
- "-exportArchive",
259
- "-archivePath",
260
- archivePath,
261
- "-exportPath",
262
- exportPath
263
- ];
264
- if (exportExtraParams) args.push(...exportExtraParams);
265
- args.push("-exportOptionsPlist", exportOptionsPlist);
266
- return args;
267
- };
268
-
269
- //#endregion
270
- //#region src/utils/assertXcodebuildExist.ts
271
- const assertXcodebuildExist = async () => {
272
- try {
273
- await execa("which", ["xcodebuild"]);
274
- } catch {
275
- p.log.error(`${colors.blueBright("xcodebuild")} resolve failed. Ensure that xcode is installed on your machine or xcodebuild is included in your path`);
276
- process.exit(1);
277
- }
278
- };
279
-
280
- //#endregion
281
- //#region src/utils/enrichNativeBuildIosScheme.ts
282
- const resolveExportOptionsPlist = (exportOptionsPlist) => {
283
- const resolvedExportOptionsPlist = exportOptionsPlist ? path.isAbsolute(exportOptionsPlist) ? exportOptionsPlist : path.resolve(getCwd(), exportOptionsPlist) : void 0;
284
- if (resolvedExportOptionsPlist && !fs.existsSync(resolvedExportOptionsPlist)) {
285
- p.log.error(`exportOptionsPlist doesn't exist in ${resolvedExportOptionsPlist}`);
286
- process.exit(1);
287
- }
288
- return resolvedExportOptionsPlist;
289
- };
290
- const enrichNativeBuildIosScheme = async (scheme) => {
291
- return {
292
- platform: "ios",
293
- installPods: true,
294
- configuration: "Release",
295
- destination: [],
296
- ...scheme,
297
- exportOptionsPlist: resolveExportOptionsPlist(scheme.exportOptionsPlist)
298
- };
299
- };
300
-
301
- //#endregion
302
- //#region src/buildIos.ts
303
- const buildIos = async ({ schemeConfig: _schemeConfig }) => {
304
- await assertXcodebuildExist();
305
- const iosProjectRoot = path.join(getCwd(), "ios");
306
- const schemeConfig = await enrichNativeBuildIosScheme(_schemeConfig);
307
- const buildDirectory = await createRandomTmpDir();
308
- const archiveDir = path.join(buildDirectory, "archive");
309
- const exportDir = path.join(buildDirectory, "export");
310
- await fs.promises.mkdir(archiveDir, { recursive: true });
311
- const { archivePath } = await archiveXcodeProject({
312
- platform: schemeConfig.platform,
313
- schemeConfig,
314
- sourceDir: iosProjectRoot
315
- });
316
- const finalArchivePath = path.join(archiveDir, path.basename(archivePath));
317
- await fs.promises.cp(archivePath, finalArchivePath, { recursive: true });
318
- const appFromArchive = extractAppFromXcarchive(finalArchivePath);
319
- if (appFromArchive) {
320
- await fs.promises.cp(appFromArchive, path.join(archiveDir, path.basename(appFromArchive)), { recursive: true });
321
- p.log.success(".app extracted from .xcarchive");
322
- }
323
- if (schemeConfig.exportOptionsPlist) {
324
- await fs.promises.mkdir(exportDir, { recursive: true });
325
- const { exportPath } = await exportXcodeArchive({
326
- archivePath,
327
- exportExtraParams: schemeConfig.exportExtraParams,
328
- exportOptionsPlist: schemeConfig.exportOptionsPlist,
329
- sourceDir: iosProjectRoot
330
- });
331
- await fs.promises.cp(exportPath, exportDir, { recursive: true });
332
- const ipaFile = fs.readdirSync(exportDir).find((file) => file.endsWith(".ipa"));
333
- if (ipaFile) return {
334
- buildDirectory,
335
- buildArtifactPath: path.join(exportDir, ipaFile)
336
- };
337
- throw new Error("IPA file not found after export");
338
- }
339
- return {
340
- buildDirectory,
341
- buildArtifactPath: finalArchivePath
342
- };
343
- };
344
- const extractAppFromXcarchive = (archivePath) => {
345
- const productsPath = path.join(archivePath, "Products", "Applications");
346
- if (!fs.existsSync(productsPath)) return;
347
- const appFile = fs.readdirSync(productsPath).find((file) => file.endsWith(".app"));
348
- if (appFile) return path.join(productsPath, appFile);
349
- p.log.warn("Failed to extract .app from xcarchive");
350
- };
351
-
352
373
  //#endregion
353
374
  //#region src/builder/buildXcodeProject.ts
354
- const buildXcodeProject = async ({ sourceDir, platform, scheme, configuration, deviceType, udid, installPods, extraParams }) => {
375
+ const buildXcodeProject = async ({ sourceDir, platform, xcodeScheme, configuration, deviceType, logPrefix, destination = [], useGenericDestination = false, installPods, extraParams }) => {
355
376
  const xcodeProject = await parseXcodeProjectInfo(sourceDir);
356
- if (installPods ?? true) await installPodsIfNeeded(sourceDir);
377
+ if (installPods) await installPodsIfNeeded(sourceDir);
357
378
  const derivedDataPath = await createRandomTmpDir();
379
+ const resolvedDestinations = resolveDestinations({
380
+ destinations: destination,
381
+ useGeneric: useGenericDestination
382
+ });
383
+ if (resolvedDestinations.length === 0) resolvedDestinations.push(getDefaultDestination({
384
+ deviceType,
385
+ platform,
386
+ useGeneric: useGenericDestination
387
+ }));
358
388
  const buildArgs = prepareBuildArgs({
359
389
  configuration,
360
390
  derivedDataPath,
361
391
  deviceType,
392
+ resolvedDestinations,
362
393
  extraParams,
363
394
  platform,
364
- scheme,
395
+ xcodeScheme,
365
396
  sourceDir,
366
- udid,
367
397
  xcodeProject
368
398
  });
369
399
  p.log.info(`Xcode Build Settings:
370
400
  Project ${xcodeProject.name}
371
- Scheme ${scheme}
401
+ Scheme ${xcodeScheme}
372
402
  Configuration ${configuration}
373
403
  Platform ${platform}
374
404
  Device Type ${deviceType}
375
405
  Command xcodebuild ${buildArgs.join(" ")}
376
406
  `);
377
- const logger = createXcodebuildLogger();
378
- logger.start(`${xcodeProject.name} (Build)`);
379
407
  try {
380
- const process$1 = execa("xcodebuild", buildArgs, { cwd: sourceDir });
381
- for await (const line of process$1) logger.processLine(line);
382
- logger.stop("Build completed successfully");
408
+ await runXcodebuildWithLogging({
409
+ args: buildArgs,
410
+ sourceDir,
411
+ logPrefix,
412
+ successMessage: "Build completed successfully",
413
+ failureMessage: "Build failed"
414
+ });
383
415
  return await getBuildSettings({
384
416
  configuration,
385
417
  derivedDataPath,
386
- deviceType,
387
- platform,
388
- scheme,
418
+ resolvedDestinations,
419
+ xcodeScheme,
389
420
  sourceDir,
390
- udid,
391
421
  xcodeProject
392
422
  });
393
423
  } catch (error) {
394
- logger.stop("Build failed", false);
395
424
  throw prettifyXcodebuildError(error);
396
425
  }
397
426
  };
398
- const prepareBuildArgs = ({ xcodeProject, sourceDir, platform, scheme, configuration, deviceType, udid, derivedDataPath, extraParams }) => {
427
+ const prepareBuildArgs = ({ xcodeProject, sourceDir, platform, xcodeScheme, configuration, deviceType, resolvedDestinations, derivedDataPath, extraParams }) => {
399
428
  const sdk = deviceType === "simulator" ? platformConfigs[platform].simulatorSdk : platformConfigs[platform].deviceSdk;
400
- const destination = udid ? `id=${udid}` : deviceType === "simulator" ? platformConfigs[platform].simulatorDestination : platformConfigs[platform].deviceDestination;
401
429
  const args = [
402
430
  xcodeProject.isWorkspace ? "-workspace" : "-project",
403
431
  path.join(sourceDir, xcodeProject.name),
404
432
  "-scheme",
405
- scheme,
433
+ xcodeScheme,
406
434
  "-configuration",
407
435
  configuration,
408
436
  "-sdk",
409
437
  sdk,
410
- "-destination",
411
- destination,
412
438
  "-derivedDataPath",
413
439
  derivedDataPath,
414
440
  `HOT_UPDATER_MIN_BUNDLE_ID=${generateMinBundleId()}`,
415
441
  "build"
416
442
  ];
417
443
  if (extraParams) args.push(...extraParams);
444
+ for (const dest of resolvedDestinations) args.push("-destination", dest);
418
445
  return args;
419
446
  };
420
- const getBuildSettings = async ({ xcodeProject, sourceDir, platform, scheme, configuration, deviceType, udid, derivedDataPath }) => {
421
- const sdk = deviceType === "simulator" ? platformConfigs[platform].simulatorSdk : platformConfigs[platform].deviceSdk;
422
- const destination = udid ? `id=${udid}` : deviceType === "simulator" ? platformConfigs[platform].simulatorDestination : platformConfigs[platform].deviceDestination;
447
+ const getBuildSettings = async ({ xcodeProject, sourceDir, xcodeScheme, configuration, resolvedDestinations, derivedDataPath }) => {
448
+ const destinationArgs = resolvedDestinations.flatMap((dest) => ["-destination", dest]);
423
449
  const { stdout: buildSettings } = await execa("xcodebuild", [
424
450
  xcodeProject.isWorkspace ? "-workspace" : "-project",
425
451
  xcodeProject.name,
426
452
  "-scheme",
427
- scheme,
453
+ xcodeScheme,
428
454
  "-configuration",
429
455
  configuration,
430
- "-sdk",
431
- sdk,
432
- "-destination",
433
- destination,
456
+ ...destinationArgs,
434
457
  "-derivedDataPath",
435
458
  derivedDataPath,
436
459
  "-showBuildSettings",
@@ -452,6 +475,137 @@ const getBuildSettings = async ({ xcodeProject, sourceDir, platform, scheme, con
452
475
  };
453
476
  };
454
477
 
478
+ //#endregion
479
+ //#region src/builder/exportXcodeArchive.ts
480
+ const exportXcodeArchive = async ({ archivePath, sourceDir, exportExtraParams, exportOptionsPlist, logPrefix }) => {
481
+ const exportPath = await createRandomTmpDir();
482
+ const exportArgs = prepareExportArgs({
483
+ archivePath,
484
+ exportPath,
485
+ exportOptionsPlist,
486
+ exportExtraParams
487
+ });
488
+ try {
489
+ await runXcodebuildWithLogging({
490
+ args: exportArgs,
491
+ sourceDir,
492
+ successMessage: "Archive exported successfully",
493
+ failureMessage: "Export failed",
494
+ logPrefix
495
+ });
496
+ return { exportPath };
497
+ } catch (error) {
498
+ throw prettifyXcodebuildError(error);
499
+ }
500
+ };
501
+ const prepareExportArgs = ({ exportPath, archivePath, exportExtraParams, exportOptionsPlist }) => {
502
+ const args = [
503
+ "-exportArchive",
504
+ "-archivePath",
505
+ archivePath,
506
+ "-exportPath",
507
+ exportPath
508
+ ];
509
+ if (exportExtraParams) args.push(...exportExtraParams);
510
+ args.push("-exportOptionsPlist", exportOptionsPlist);
511
+ return args;
512
+ };
513
+
514
+ //#endregion
515
+ //#region src/utils/assertXcodebuildExist.ts
516
+ const assertXcodebuildExist = async () => {
517
+ try {
518
+ await execa("which", ["xcodebuild"]);
519
+ } catch {
520
+ p.log.error(`${colors.blueBright("xcodebuild")} resolve failed. Ensure that xcode is installed on your machine or xcodebuild is included in your path`);
521
+ process.exit(1);
522
+ }
523
+ };
524
+
525
+ //#endregion
526
+ //#region src/buildIos.ts
527
+ const buildIos = async ({ schemeConfig }) => {
528
+ await assertXcodebuildExist();
529
+ const iosProjectRoot = path.join(getCwd(), "ios");
530
+ const buildDirectory = await createRandomTmpDir();
531
+ const archiveDir = path.join(buildDirectory, "archive");
532
+ const exportDir = path.join(buildDirectory, "export");
533
+ await fs.promises.mkdir(archiveDir, { recursive: true });
534
+ if (schemeConfig.simulator) {
535
+ if (schemeConfig.destination.length) {
536
+ p.log.error("The scheme option 'simulator' is not compatible with the 'destination' option.");
537
+ process.exit(1);
538
+ }
539
+ const appDir = path.join(buildDirectory, "app");
540
+ await fs.promises.mkdir(appDir, { recursive: true });
541
+ const { appPath } = await buildXcodeProject({
542
+ sourceDir: iosProjectRoot,
543
+ logPrefix: `ios-${schemeConfig.hotUpdaterSchemeName}-build`,
544
+ platform: schemeConfig.platform,
545
+ xcodeScheme: schemeConfig.scheme,
546
+ configuration: schemeConfig.configuration,
547
+ deviceType: "simulator",
548
+ installPods: schemeConfig.installPods,
549
+ extraParams: schemeConfig.extraParams,
550
+ useGenericDestination: true
551
+ });
552
+ const finalAppPath = path.join(appDir, path.basename(appPath));
553
+ await fs.promises.cp(appPath, finalAppPath, { recursive: true });
554
+ p.log.success(`.app file created: ${path.basename(appPath)}`);
555
+ return {
556
+ buildDirectory,
557
+ buildArtifactPath: finalAppPath
558
+ };
559
+ } else {
560
+ const { archivePath } = await archiveXcodeProject({
561
+ platform: schemeConfig.platform,
562
+ sourceDir: iosProjectRoot,
563
+ xcconfig: schemeConfig.xcconfig,
564
+ xcodeScheme: schemeConfig.scheme,
565
+ destination: schemeConfig.destination,
566
+ extraParams: schemeConfig.extraParams,
567
+ configuration: schemeConfig.configuration,
568
+ installPods: schemeConfig.installPods,
569
+ logPrefix: `ios-${schemeConfig.hotUpdaterSchemeName}-build-archive`
570
+ });
571
+ const finalArchivePath = path.join(archiveDir, path.basename(archivePath));
572
+ await fs.promises.cp(archivePath, finalArchivePath, { recursive: true });
573
+ const appFromArchive = extractAppFromXcarchive(finalArchivePath);
574
+ if (appFromArchive) {
575
+ await fs.promises.cp(appFromArchive, path.join(archiveDir, path.basename(appFromArchive)), { recursive: true });
576
+ p.log.success(".app extracted from .xcarchive");
577
+ }
578
+ if (schemeConfig.exportOptionsPlist) {
579
+ await fs.promises.mkdir(exportDir, { recursive: true });
580
+ const { exportPath } = await exportXcodeArchive({
581
+ archivePath,
582
+ exportExtraParams: schemeConfig.exportExtraParams,
583
+ exportOptionsPlist: schemeConfig.exportOptionsPlist,
584
+ sourceDir: iosProjectRoot,
585
+ logPrefix: `ios-${schemeConfig.hotUpdaterSchemeName}-build-export`
586
+ });
587
+ await fs.promises.cp(exportPath, exportDir, { recursive: true });
588
+ const ipaFile = fs.readdirSync(exportDir).find((file) => file.endsWith(".ipa"));
589
+ if (ipaFile) return {
590
+ buildDirectory,
591
+ buildArtifactPath: path.join(exportDir, ipaFile)
592
+ };
593
+ throw new Error("IPA file not found after export");
594
+ }
595
+ return {
596
+ buildDirectory,
597
+ buildArtifactPath: finalArchivePath
598
+ };
599
+ }
600
+ };
601
+ const extractAppFromXcarchive = (archivePath) => {
602
+ const productsPath = path.join(archivePath, "Products", "Applications");
603
+ if (!fs.existsSync(productsPath)) return;
604
+ const appFile = fs.readdirSync(productsPath).find((file) => file.endsWith(".app"));
605
+ if (appFile) return path.join(productsPath, appFile);
606
+ p.log.warn("Failed to extract .app from xcarchive");
607
+ };
608
+
455
609
  //#endregion
456
610
  //#region src/runner/deviceRunner.ts
457
611
  const installAndLaunchOnDevice = async ({ device, appPath, options }) => {
@@ -737,10 +891,9 @@ const Device = {
737
891
 
738
892
  //#endregion
739
893
  //#region src/runIos.ts
740
- const runIos = async ({ schemeConfig: _schemeConfig, runOption }) => {
894
+ const runIos = async ({ schemeConfig, runOption }) => {
741
895
  const { interactive, device: deviceOption } = runOption;
742
896
  const iosProjectPath = path.join(getCwd(), "ios");
743
- const schemeConfig = await enrichNativeBuildIosScheme(_schemeConfig);
744
897
  const device = await Device.selectTargetDevice({
745
898
  platform: "ios",
746
899
  deviceOption,
@@ -758,12 +911,13 @@ const runIos = async ({ schemeConfig: _schemeConfig, runOption }) => {
758
911
  const result$1 = await buildXcodeProject({
759
912
  sourceDir: iosProjectPath,
760
913
  platform: schemeConfig.platform,
761
- scheme: schemeConfig.scheme,
914
+ xcodeScheme: schemeConfig.scheme,
762
915
  configuration: schemeConfig.configuration,
763
916
  deviceType: selectedDevice.type,
764
- udid: selectedDevice.udid,
917
+ destination: [{ id: selectedDevice.udid }],
765
918
  installPods: schemeConfig.installPods,
766
- extraParams: schemeConfig.extraParams
919
+ extraParams: schemeConfig.extraParams,
920
+ logPrefix: `ios-${schemeConfig.hotUpdaterSchemeName}-run`
767
921
  });
768
922
  const runnerOptions$1 = {
769
923
  bundleIdentifier: schemeConfig.bundleIdentifier,
@@ -783,12 +937,13 @@ const runIos = async ({ schemeConfig: _schemeConfig, runOption }) => {
783
937
  const result = await buildXcodeProject({
784
938
  sourceDir: iosProjectPath,
785
939
  platform: schemeConfig.platform,
786
- scheme: schemeConfig.scheme,
940
+ xcodeScheme: schemeConfig.scheme,
787
941
  configuration: schemeConfig.configuration,
788
942
  deviceType: device.type,
789
- udid: device.udid,
943
+ destination: [{ id: device.udid }],
790
944
  installPods: schemeConfig.installPods,
791
- extraParams: schemeConfig.extraParams
945
+ extraParams: schemeConfig.extraParams,
946
+ logPrefix: `ios-${schemeConfig.hotUpdaterSchemeName}-run`
792
947
  });
793
948
  const runnerOptions = {
794
949
  bundleIdentifier: schemeConfig.bundleIdentifier,
@@ -813,4 +968,27 @@ const runIos = async ({ schemeConfig: _schemeConfig, runOption }) => {
813
968
  };
814
969
 
815
970
  //#endregion
816
- export { buildIos, runIos };
971
+ //#region src/utils/enrichNativeBuildIosScheme.ts
972
+ const resolveExportOptionsPlist = (exportOptionsPlist) => {
973
+ const resolvedExportOptionsPlist = exportOptionsPlist ? path.isAbsolute(exportOptionsPlist) ? exportOptionsPlist : path.resolve(getCwd(), exportOptionsPlist) : void 0;
974
+ if (resolvedExportOptionsPlist && !fs.existsSync(resolvedExportOptionsPlist)) {
975
+ p.log.error(`exportOptionsPlist doesn't exist in ${resolvedExportOptionsPlist}`);
976
+ process.exit(1);
977
+ }
978
+ return resolvedExportOptionsPlist;
979
+ };
980
+ const enrichNativeBuildIosScheme = async ({ scheme, hotUpdaterSchemeName }) => {
981
+ return {
982
+ platform: "ios",
983
+ installPods: false,
984
+ configuration: "Release",
985
+ destination: [],
986
+ simulator: false,
987
+ ...scheme,
988
+ exportOptionsPlist: resolveExportOptionsPlist(scheme.exportOptionsPlist),
989
+ hotUpdaterSchemeName
990
+ };
991
+ };
992
+
993
+ //#endregion
994
+ export { buildIos, enrichNativeBuildIosScheme, runIos };