@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.cjs +371 -191
- package/dist/index.d.cts +21 -5
- package/dist/index.d.ts +21 -5
- package/dist/index.js +370 -192
- package/package.json +3 -3
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,
|
|
309
|
+
const archiveXcodeProject = async ({ sourceDir, platform, installPods, destination, extraParams, configuration, xcodeScheme, xcconfig, logPrefix }) => {
|
|
177
310
|
const xcodeProject = await parseXcodeProjectInfo(sourceDir);
|
|
178
|
-
if (
|
|
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 ${
|
|
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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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,
|
|
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
|
-
|
|
350
|
+
xcodeScheme,
|
|
213
351
|
"-configuration",
|
|
214
|
-
|
|
352
|
+
configuration,
|
|
215
353
|
"archive",
|
|
216
354
|
"-archivePath",
|
|
217
355
|
archivePath,
|
|
218
356
|
`HOT_UPDATER_MIN_BUNDLE_ID=${generateMinBundleId()}`
|
|
219
357
|
];
|
|
220
|
-
if (
|
|
221
|
-
if (
|
|
358
|
+
if (xcconfig) args.push("-xcconfig", xcconfig);
|
|
359
|
+
if (extraParams) args.push(...extraParams);
|
|
222
360
|
const resolvedDestinations = resolveDestinations({
|
|
223
|
-
destinations:
|
|
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,
|
|
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
|
|
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
|
-
|
|
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 ${
|
|
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
|
-
|
|
381
|
-
|
|
382
|
-
|
|
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
|
-
|
|
387
|
-
|
|
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,
|
|
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
|
-
|
|
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,
|
|
421
|
-
const
|
|
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
|
-
|
|
453
|
+
xcodeScheme,
|
|
428
454
|
"-configuration",
|
|
429
455
|
configuration,
|
|
430
|
-
|
|
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
|
|
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
|
-
|
|
914
|
+
xcodeScheme: schemeConfig.scheme,
|
|
762
915
|
configuration: schemeConfig.configuration,
|
|
763
916
|
deviceType: selectedDevice.type,
|
|
764
|
-
|
|
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
|
-
|
|
940
|
+
xcodeScheme: schemeConfig.scheme,
|
|
787
941
|
configuration: schemeConfig.configuration,
|
|
788
942
|
deviceType: device.type,
|
|
789
|
-
|
|
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
|
-
|
|
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 };
|