@hot-updater/apple-helper 1.2.0-rc.7 → 1.2.0-rc.8
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 +230 -172
- package/dist/index.d.cts +13 -2
- package/dist/index.d.mts +13 -2
- package/dist/index.mjs +230 -172
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -783,11 +783,23 @@ const ASC_INTEGRATIONS_URL = "https://appstoreconnect.apple.com/access/integrati
|
|
|
783
783
|
const issueAscApiKey = (request) => createAscKeyIssuer()(request);
|
|
784
784
|
//#endregion
|
|
785
785
|
//#region src/utils/cocoapods.ts
|
|
786
|
+
/**
|
|
787
|
+
* A failed command's error: the command, then the last lines of its
|
|
788
|
+
* output, without execa's full message.
|
|
789
|
+
*/
|
|
790
|
+
const commandFailure = (command, error) => {
|
|
791
|
+
const { stderr, stdout } = error ?? {};
|
|
792
|
+
const detail = String(stderr || stdout || "").trimEnd().split("\n").slice(-12).join("\n") || (error instanceof Error ? error.message : String(error));
|
|
793
|
+
return new Error(`${command} failed.\n${detail}`, { cause: error });
|
|
794
|
+
};
|
|
786
795
|
const installPodsIfNeeded = async (sourceDir) => {
|
|
787
796
|
const podfilePath = path.default.join(sourceDir, "Podfile");
|
|
788
797
|
if (!fs.default.existsSync(podfilePath)) {
|
|
789
798
|
_hot_updater_cli_tools.p.log.info("No Podfile found, skipping CocoaPods installation");
|
|
790
|
-
return
|
|
799
|
+
return {
|
|
800
|
+
codegen: false,
|
|
801
|
+
pods: false
|
|
802
|
+
};
|
|
791
803
|
}
|
|
792
804
|
const gemfilePaths = [path.default.join(sourceDir, "Gemfile"), path.default.join((0, _hot_updater_cli_tools.getCwd)(), "Gemfile")];
|
|
793
805
|
const shouldUseBundler = (await Promise.all(gemfilePaths.map(checkShouldUseBundler))).some(Boolean);
|
|
@@ -801,36 +813,39 @@ const installPodsIfNeeded = async (sourceDir) => {
|
|
|
801
813
|
reactNativePath: reactNative.packagePath,
|
|
802
814
|
sourceDir
|
|
803
815
|
});
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
bundleSpinner.start("Installing Ruby gems");
|
|
816
|
+
if (shouldUseBundler) {
|
|
817
|
+
const bundleSpinner = _hot_updater_cli_tools.p.spinner();
|
|
818
|
+
bundleSpinner.start("Installing Ruby gems");
|
|
819
|
+
try {
|
|
809
820
|
await (0, execa.execa)("bundle", ["install"], { cwd: sourceDir });
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
await (0, execa.execa)("bundle", [
|
|
814
|
-
"exec",
|
|
815
|
-
"pod",
|
|
816
|
-
"install"
|
|
817
|
-
], {
|
|
818
|
-
cwd: sourceDir,
|
|
819
|
-
env: plan.env
|
|
820
|
-
});
|
|
821
|
-
podSpinner.stop("CocoaPods dependencies installed");
|
|
822
|
-
} else {
|
|
823
|
-
const spinner = _hot_updater_cli_tools.p.spinner();
|
|
824
|
-
spinner.start("Installing CocoaPods dependencies");
|
|
825
|
-
await (0, execa.execa)("pod", ["install"], {
|
|
826
|
-
cwd: sourceDir,
|
|
827
|
-
env: plan.env
|
|
828
|
-
});
|
|
829
|
-
spinner.stop("CocoaPods dependencies installed");
|
|
821
|
+
} catch (error) {
|
|
822
|
+
bundleSpinner.error("bundle install failed");
|
|
823
|
+
throw commandFailure("bundle install", error);
|
|
830
824
|
}
|
|
825
|
+
bundleSpinner.stop("Ruby gems installed");
|
|
826
|
+
}
|
|
827
|
+
const podCommand = shouldUseBundler ? [
|
|
828
|
+
"bundle",
|
|
829
|
+
"exec",
|
|
830
|
+
"pod",
|
|
831
|
+
"install"
|
|
832
|
+
] : ["pod", "install"];
|
|
833
|
+
const podSpinner = _hot_updater_cli_tools.p.spinner();
|
|
834
|
+
podSpinner.start("Installing CocoaPods dependencies");
|
|
835
|
+
try {
|
|
836
|
+
await (0, execa.execa)(podCommand[0], podCommand.slice(1), {
|
|
837
|
+
cwd: sourceDir,
|
|
838
|
+
env: plan.env
|
|
839
|
+
});
|
|
831
840
|
} catch (error) {
|
|
832
|
-
|
|
841
|
+
podSpinner.error(`${podCommand.join(" ")} failed`);
|
|
842
|
+
throw commandFailure(podCommand.join(" "), error);
|
|
833
843
|
}
|
|
844
|
+
podSpinner.stop("CocoaPods dependencies installed");
|
|
845
|
+
return {
|
|
846
|
+
codegen: plan.runCodegen,
|
|
847
|
+
pods: true
|
|
848
|
+
};
|
|
834
849
|
};
|
|
835
850
|
const checkShouldUseBundler = async (gemfilePath) => {
|
|
836
851
|
try {
|
|
@@ -863,8 +878,8 @@ const runReactNativeCodegen = async ({ projectRoot, reactNativePath, sourceDir }
|
|
|
863
878
|
"ios"
|
|
864
879
|
], { cwd: projectRoot });
|
|
865
880
|
} catch (error) {
|
|
866
|
-
spinner.
|
|
867
|
-
throw
|
|
881
|
+
spinner.error("React Native codegen failed");
|
|
882
|
+
throw commandFailure("React Native codegen", error);
|
|
868
883
|
}
|
|
869
884
|
spinner.stop("React Native codegen done");
|
|
870
885
|
};
|
|
@@ -994,26 +1009,6 @@ const getDefaultDestination = ({ platform, useGeneric, deviceType }) => {
|
|
|
994
1009
|
});
|
|
995
1010
|
};
|
|
996
1011
|
//#endregion
|
|
997
|
-
//#region src/utils/formatCommand.ts
|
|
998
|
-
/**
|
|
999
|
-
* A command to print, broken at spaces with ` \` once a line would pass
|
|
1000
|
-
* `width`, each continuation indented two spaces, so a terminal never wraps
|
|
1001
|
-
* it mid-word and it still pastes as one command.
|
|
1002
|
-
*/
|
|
1003
|
-
const formatCommand = (words, width = 74) => {
|
|
1004
|
-
const lines = [];
|
|
1005
|
-
let line = "";
|
|
1006
|
-
for (const word of words) {
|
|
1007
|
-
const next = line ? `${line} ${word}` : word;
|
|
1008
|
-
if (line && next.length > width - 2) {
|
|
1009
|
-
lines.push(`${line} \\`);
|
|
1010
|
-
line = ` ${word}`;
|
|
1011
|
-
} else line = next;
|
|
1012
|
-
}
|
|
1013
|
-
lines.push(line);
|
|
1014
|
-
return lines.join("\n");
|
|
1015
|
-
};
|
|
1016
|
-
//#endregion
|
|
1017
1012
|
//#region src/utils/parseXcodeProjectInfo.ts
|
|
1018
1013
|
const parseXcodeProjectInfo = async (sourceDir) => {
|
|
1019
1014
|
const files = await fs.default.promises.readdir(sourceDir);
|
|
@@ -1036,80 +1031,133 @@ const parseXcodeProjectInfo = async (sourceDir) => {
|
|
|
1036
1031
|
const prettifyXcodebuildError = (error) => {
|
|
1037
1032
|
if (error instanceof _hot_updater_cli_tools.BuildFailedError) {
|
|
1038
1033
|
const errorLines = error.summary.join("\n");
|
|
1039
|
-
const
|
|
1040
|
-
return new _hot_updater_cli_tools.BuildFailedError(message, {
|
|
1034
|
+
const hint = mentionsProgramLicenseAgreement(errorLines) ? PROGRAM_LICENSE_AGREEMENT_HINT : errorLines.includes("error: No profiles for") || errorLines.includes("error: Provisioning profile") ? "No valid provisioning profile is set in the project.\nCheck the Signing & Capabilities tab in Xcode." : null;
|
|
1035
|
+
return new _hot_updater_cli_tools.BuildFailedError(hint ?? error.message, {
|
|
1041
1036
|
logPath: error.logPath,
|
|
1042
1037
|
summary: error.summary,
|
|
1038
|
+
hint,
|
|
1043
1039
|
cause: error.cause
|
|
1044
1040
|
});
|
|
1045
1041
|
}
|
|
1046
1042
|
return /* @__PURE__ */ new Error(`Xcodebuild failed: ${error}`);
|
|
1047
1043
|
};
|
|
1048
1044
|
//#endregion
|
|
1045
|
+
//#region src/utils/xcodebuildPhase.ts
|
|
1046
|
+
/** The `(in target 'X' from project 'Y')` that ends an action's line. */
|
|
1047
|
+
const TARGET = /\(in target '([^']+)' from project '[^']+'\)\s*$/;
|
|
1048
|
+
/** A `PhaseScriptExecution` line's script phase, its spaces escaped. */
|
|
1049
|
+
const SCRIPT = /^PhaseScriptExecution ((?:\\.|[^\s\\])+)/;
|
|
1050
|
+
/** A batched `SwiftCompile`'s files: `Compiling\ A.swift,\ B.swift`. */
|
|
1051
|
+
const SWIFT_BATCH = /^SwiftCompile \S+ \S+ Compiling\\ ((?:\\.|[^\s\\])+)/;
|
|
1052
|
+
const unescape = (value) => value.replace(/\\(.)/g, "$1");
|
|
1053
|
+
/** Actions whose lines say what xcodebuild is doing, by their first word. */
|
|
1054
|
+
const ACTIONS = {
|
|
1055
|
+
CompileAssetCatalog: "Compiling assets",
|
|
1056
|
+
CompileAssetCatalogVariant: "Compiling assets",
|
|
1057
|
+
LinkAssetCatalog: "Compiling assets",
|
|
1058
|
+
CompileStoryboard: "Compiling storyboards",
|
|
1059
|
+
CompileXIB: "Compiling storyboards",
|
|
1060
|
+
LinkStoryboards: "Compiling storyboards",
|
|
1061
|
+
Ld: "Linking",
|
|
1062
|
+
Libtool: "Linking",
|
|
1063
|
+
CodeSign: "Signing",
|
|
1064
|
+
ProcessInfoPlistFile: "Processing",
|
|
1065
|
+
ProcessProductPackaging: "Processing",
|
|
1066
|
+
ProcessProductPackagingDER: "Processing",
|
|
1067
|
+
CopySwiftLibs: "Copying",
|
|
1068
|
+
CpResource: "Copying",
|
|
1069
|
+
GenerateDSYMFile: "Generating dSYM",
|
|
1070
|
+
Strip: "Stripping",
|
|
1071
|
+
Validate: "Validating"
|
|
1072
|
+
};
|
|
1073
|
+
const COMPILES = /* @__PURE__ */ new Set([
|
|
1074
|
+
"CompileC",
|
|
1075
|
+
"SwiftCompile",
|
|
1076
|
+
"CompileSwift"
|
|
1077
|
+
]);
|
|
1078
|
+
const PRECOMPILES = /* @__PURE__ */ new Set([
|
|
1079
|
+
"PrecompileModule",
|
|
1080
|
+
"ScanDependencies",
|
|
1081
|
+
"SwiftExplicitDependencyGeneratePcm",
|
|
1082
|
+
"SwiftExplicitDependencyCompileModuleFromInterface"
|
|
1083
|
+
]);
|
|
1084
|
+
const plural = (count, noun) => `${count} ${noun}${count === 1 ? "" : "s"}`;
|
|
1085
|
+
/**
|
|
1086
|
+
* Reads the phase of an `xcodebuild` build, archive, or export from its raw
|
|
1087
|
+
* output: `Compiling React-Core · 412 files`, `Linking CodexRelay`,
|
|
1088
|
+
* `Signing CodexRelay`, `Bundling JavaScript`, with the target each
|
|
1089
|
+
* action's line names.
|
|
1090
|
+
*/
|
|
1091
|
+
const createXcodebuildPhaseReader = () => {
|
|
1092
|
+
let compiled = 0;
|
|
1093
|
+
let precompiled = 0;
|
|
1094
|
+
return (line) => {
|
|
1095
|
+
const space = line.indexOf(" ");
|
|
1096
|
+
const action = space === -1 ? line : line.slice(0, space);
|
|
1097
|
+
const target = TARGET.exec(line)?.[1];
|
|
1098
|
+
const withTarget = (phase) => target ? `${phase} ${target}` : phase;
|
|
1099
|
+
if (COMPILES.has(action)) {
|
|
1100
|
+
const batch = SWIFT_BATCH.exec(line)?.[1];
|
|
1101
|
+
compiled += batch ? unescape(batch).split(",").length : 1;
|
|
1102
|
+
return `${withTarget("Compiling")} · ${plural(compiled, "file")}`;
|
|
1103
|
+
}
|
|
1104
|
+
if (PRECOMPILES.has(action)) {
|
|
1105
|
+
precompiled += 1;
|
|
1106
|
+
return `Precompiling modules · ${precompiled}`;
|
|
1107
|
+
}
|
|
1108
|
+
if (action === "PhaseScriptExecution") {
|
|
1109
|
+
const script = unescape(SCRIPT.exec(line)?.[1] ?? "");
|
|
1110
|
+
if (script === "Bundle React Native code and images") return "Bundling JavaScript";
|
|
1111
|
+
return script ? `Running ${script}` : withTarget("Running a script");
|
|
1112
|
+
}
|
|
1113
|
+
const phase = ACTIONS[action];
|
|
1114
|
+
if (phase) return withTarget(phase);
|
|
1115
|
+
if (line === "Prepare packages" || line.startsWith("Resolve Package")) return "Resolving packages";
|
|
1116
|
+
if (line === "ComputeTargetDependencyGraph") return "Planning the build";
|
|
1117
|
+
};
|
|
1118
|
+
};
|
|
1119
|
+
//#endregion
|
|
1049
1120
|
//#region src/utils/createXcodebuildLogger.ts
|
|
1050
|
-
const createXcodebuildLogger = ({ logPrefix }) => new _hot_updater_cli_tools.BuildLogger({
|
|
1121
|
+
const createXcodebuildLogger = ({ logPrefix, logFilePath }) => new _hot_updater_cli_tools.BuildLogger({
|
|
1051
1122
|
logPrefix,
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
"** BUILD FAILED **",
|
|
1055
|
-
"** ARCHIVE FAILED **",
|
|
1056
|
-
"The following build commands failed:",
|
|
1057
|
-
"note: Building targets in dependency order",
|
|
1058
|
-
"note: Target dependency graph",
|
|
1059
|
-
"Running script",
|
|
1060
|
-
"[CP-User]",
|
|
1061
|
-
"[CP] Check Pods Manifest.lock",
|
|
1062
|
-
"Build Succeeded"
|
|
1063
|
-
],
|
|
1064
|
-
progressStages: [
|
|
1065
|
-
["Building targets in dependency order", "Target dependency graph"],
|
|
1066
|
-
["Write Auxiliary File", "[CP] Check Pods Manifest.lock"],
|
|
1067
|
-
[
|
|
1068
|
-
"Running script",
|
|
1069
|
-
"[CP-User] [RN]Check rncore",
|
|
1070
|
-
"[CP-User] [Hermes] Replace Hermes",
|
|
1071
|
-
"[CP-User] [RN]Check FBReactNativeSpec"
|
|
1072
|
-
],
|
|
1073
|
-
[
|
|
1074
|
-
"Processing",
|
|
1075
|
-
"ProcessProductPackaging",
|
|
1076
|
-
"ProcessProductPackagingDER"
|
|
1077
|
-
],
|
|
1078
|
-
["Compiling"],
|
|
1079
|
-
["RegisterExecutionPolicyException"],
|
|
1080
|
-
["Touching"],
|
|
1081
|
-
["Building library", "Create Universal Binary"],
|
|
1082
|
-
["Copying"],
|
|
1083
|
-
[
|
|
1084
|
-
"Build Succeeded",
|
|
1085
|
-
"BUILD SUCCEEDED",
|
|
1086
|
-
"ARCHIVE SUCCEEDED"
|
|
1087
|
-
]
|
|
1088
|
-
],
|
|
1123
|
+
logFilePath,
|
|
1124
|
+
createPhaseReader: createXcodebuildPhaseReader,
|
|
1089
1125
|
failureSummary: {
|
|
1090
1126
|
lines: [/(^|:\s)error: /, /^\*\* [A-Z ]+ FAILED \*\*$/],
|
|
1091
1127
|
blocks: [{
|
|
1092
1128
|
start: "The following build commands failed:",
|
|
1093
1129
|
end: /^\(\d+ failures?\)$/,
|
|
1094
1130
|
includeEnd: true
|
|
1131
|
+
}, {
|
|
1132
|
+
start: /^error: exportArchive /,
|
|
1133
|
+
end: /^$/,
|
|
1134
|
+
includeEnd: false
|
|
1095
1135
|
}]
|
|
1096
1136
|
}
|
|
1097
1137
|
});
|
|
1098
1138
|
//#endregion
|
|
1099
|
-
//#region src/utils/
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1139
|
+
//#region src/utils/formatCommand.ts
|
|
1140
|
+
/**
|
|
1141
|
+
* A command to print, broken at spaces with ` \` once a line would pass
|
|
1142
|
+
* `width`, each continuation indented two spaces, so a terminal never wraps
|
|
1143
|
+
* it mid-word and it still pastes as one command.
|
|
1144
|
+
*/
|
|
1145
|
+
const formatCommand = (words, width = 74) => {
|
|
1146
|
+
const lines = [];
|
|
1147
|
+
let line = "";
|
|
1148
|
+
for (const word of words) {
|
|
1149
|
+
const next = line ? `${line} ${word}` : word;
|
|
1150
|
+
if (line && next.length > width - 2) {
|
|
1151
|
+
lines.push(`${line} \\`);
|
|
1152
|
+
line = ` ${word}`;
|
|
1153
|
+
} else line = next;
|
|
1111
1154
|
}
|
|
1155
|
+
lines.push(line);
|
|
1156
|
+
return lines.join("\n");
|
|
1112
1157
|
};
|
|
1158
|
+
//#endregion
|
|
1159
|
+
//#region src/utils/runXcodebuildWithLogging.ts
|
|
1160
|
+
const TERMINATION_SIGNALS$1 = ["SIGINT", "SIGTERM"];
|
|
1113
1161
|
const createRawXcodebuildProcess = ({ args, env, sourceDir }) => {
|
|
1114
1162
|
return (0, execa.execa)("xcodebuild", args, {
|
|
1115
1163
|
cwd: sourceDir,
|
|
@@ -1122,13 +1170,6 @@ const createRawXcodebuildProcess = ({ args, env, sourceDir }) => {
|
|
|
1122
1170
|
}
|
|
1123
1171
|
});
|
|
1124
1172
|
};
|
|
1125
|
-
const createXcbeautifyProcess = ({ cwd }) => {
|
|
1126
|
-
return (0, execa.execa)("xcbeautify", [], {
|
|
1127
|
-
cwd,
|
|
1128
|
-
all: true,
|
|
1129
|
-
buffer: false
|
|
1130
|
-
});
|
|
1131
|
-
};
|
|
1132
1173
|
/** Why xcodebuild failed, without its command line. */
|
|
1133
1174
|
const describeFailure = (error) => {
|
|
1134
1175
|
if (error instanceof execa.ExecaError) {
|
|
@@ -1137,10 +1178,24 @@ const describeFailure = (error) => {
|
|
|
1137
1178
|
}
|
|
1138
1179
|
return error instanceof Error ? error.message : String(error);
|
|
1139
1180
|
};
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1181
|
+
/**
|
|
1182
|
+
* Runs xcodebuild as a step titled `title`: its live line shows the build's
|
|
1183
|
+
* phase, the log file keeps the command and the whole output, and a failure
|
|
1184
|
+
* prints the output's errors and the log's path on stderr.
|
|
1185
|
+
*/
|
|
1186
|
+
const runXcodebuildWithLogging = async ({ args, env, sourceDir, title, successMessage, failureMessage, logPrefix, logFilePath, header = [] }) => {
|
|
1187
|
+
const logger = createXcodebuildLogger({
|
|
1188
|
+
logPrefix,
|
|
1189
|
+
logFilePath
|
|
1190
|
+
});
|
|
1191
|
+
await logger.start({
|
|
1192
|
+
title,
|
|
1193
|
+
header: [
|
|
1194
|
+
...header,
|
|
1195
|
+
`$ ${formatCommand(["xcodebuild", ...args])}`,
|
|
1196
|
+
""
|
|
1197
|
+
]
|
|
1198
|
+
});
|
|
1144
1199
|
const activeProcesses = [];
|
|
1145
1200
|
const terminateActiveProcesses = () => {
|
|
1146
1201
|
for (const activeProcess of activeProcesses) activeProcess.kill();
|
|
@@ -1154,31 +1209,17 @@ const runXcodebuildWithLogging = async ({ args, env, sourceDir, successMessage,
|
|
|
1154
1209
|
});
|
|
1155
1210
|
activeProcesses.push(xcodebuildProcess);
|
|
1156
1211
|
const xcodebuildOutput = xcodebuildProcess.all ?? xcodebuildProcess.stdout;
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
xcbeautifyProcess = createXcbeautifyProcess({ cwd: sourceDir });
|
|
1160
|
-
activeProcesses.push(xcbeautifyProcess);
|
|
1161
|
-
}
|
|
1162
|
-
const logging = xcodebuildOutput ? logger.processStream(xcodebuildOutput, { display: xcbeautifyProcess ? xcbeautifyProcess.all ?? xcbeautifyProcess.stdout ?? void 0 : void 0 }) : Promise.resolve();
|
|
1163
|
-
const xcbeautifyInput = xcbeautifyProcess?.stdin;
|
|
1164
|
-
if (xcodebuildOutput && xcbeautifyInput) {
|
|
1165
|
-
xcbeautifyInput.on("error", () => {
|
|
1166
|
-
xcodebuildOutput.unpipe(xcbeautifyInput);
|
|
1167
|
-
});
|
|
1168
|
-
xcodebuildOutput.pipe(xcbeautifyInput);
|
|
1169
|
-
}
|
|
1170
|
-
const [xcodebuildResult, loggingResult] = await Promise.allSettled([
|
|
1171
|
-
xcodebuildProcess,
|
|
1172
|
-
logging,
|
|
1173
|
-
xcbeautifyProcess
|
|
1174
|
-
]);
|
|
1212
|
+
const logging = xcodebuildOutput ? logger.processStream(xcodebuildOutput) : Promise.resolve();
|
|
1213
|
+
const [xcodebuildResult, loggingResult] = await Promise.allSettled([xcodebuildProcess, logging]);
|
|
1175
1214
|
if (xcodebuildResult.status === "rejected") throw xcodebuildResult.reason;
|
|
1176
1215
|
if (loggingResult.status === "rejected") throw loggingResult.reason;
|
|
1177
1216
|
logger.stop(successMessage);
|
|
1217
|
+
return { logFilePath: logger.logFilePath };
|
|
1178
1218
|
} catch (error) {
|
|
1179
1219
|
logger.writeError(error);
|
|
1180
|
-
|
|
1181
|
-
|
|
1220
|
+
const reason = describeFailure(error);
|
|
1221
|
+
logger.stop(`${failureMessage} · ${reason.replace(/\.$/, "")}`, false);
|
|
1222
|
+
throw new _hot_updater_cli_tools.BuildFailedError(`${failureMessage}: ${reason}`, {
|
|
1182
1223
|
logPath: logger.logPath,
|
|
1183
1224
|
summary: logger.failureSummary,
|
|
1184
1225
|
cause: error
|
|
@@ -1270,7 +1311,7 @@ const buildSettingsOverScheme = ({ minBundleId, authenticationArgs }) => ({
|
|
|
1270
1311
|
* written to a temporary directory, removed whether the archive succeeds or
|
|
1271
1312
|
* not.
|
|
1272
1313
|
*/
|
|
1273
|
-
const archiveXcodeProject = async ({ sourceDir, archiveDirectory, platform, installPods, destination, extraParams, configuration, xcodeScheme, xcconfig, logPrefix, minBundleId, authenticationArgs }) => {
|
|
1314
|
+
const archiveXcodeProject = async ({ sourceDir, archiveDirectory, platform, installPods, destination, extraParams, configuration, xcodeScheme, xcconfig, logPrefix, header = [], minBundleId, authenticationArgs }) => {
|
|
1274
1315
|
if (installPods) await installPodsIfNeeded(sourceDir);
|
|
1275
1316
|
const xcodeProject = await parseXcodeProjectInfo(sourceDir);
|
|
1276
1317
|
const archiveName = `${xcodeProject.name.replace(".xcworkspace", "").replace(".xcodeproj", "")}.xcarchive`;
|
|
@@ -1300,24 +1341,26 @@ const archiveXcodeProject = async ({ sourceDir, archiveDirectory, platform, inst
|
|
|
1300
1341
|
xcconfig: build.xcconfig,
|
|
1301
1342
|
xcodeScheme
|
|
1302
1343
|
});
|
|
1303
|
-
_hot_updater_cli_tools.p.log.info([
|
|
1304
|
-
"Xcode archive",
|
|
1305
|
-
`Project ${xcodeProject.name}`,
|
|
1306
|
-
`Scheme ${xcodeScheme}`,
|
|
1307
|
-
`Platform ${platform}`,
|
|
1308
|
-
"Command",
|
|
1309
|
-
formatCommand(["xcodebuild", ...archiveArgs])
|
|
1310
|
-
].join("\n"));
|
|
1311
1344
|
try {
|
|
1312
|
-
await runXcodebuildWithLogging({
|
|
1345
|
+
const { logFilePath } = await runXcodebuildWithLogging({
|
|
1313
1346
|
args: archiveArgs,
|
|
1314
1347
|
env: { HOT_UPDATER_MIN_BUNDLE_ID: minBundleId },
|
|
1348
|
+
title: `Archiving ${xcodeScheme}`,
|
|
1349
|
+
successMessage: `Archived ${xcodeScheme}`,
|
|
1315
1350
|
failureMessage: "Archive failed",
|
|
1316
1351
|
logPrefix,
|
|
1317
|
-
|
|
1318
|
-
|
|
1352
|
+
header: [
|
|
1353
|
+
...header,
|
|
1354
|
+
`Project ${xcodeProject.name}`,
|
|
1355
|
+
`Scheme ${xcodeScheme}`,
|
|
1356
|
+
`Platform ${platform}`
|
|
1357
|
+
],
|
|
1358
|
+
sourceDir
|
|
1319
1359
|
});
|
|
1320
|
-
return {
|
|
1360
|
+
return {
|
|
1361
|
+
archivePath,
|
|
1362
|
+
logFilePath
|
|
1363
|
+
};
|
|
1321
1364
|
} catch (error) {
|
|
1322
1365
|
throw prettifyXcodebuildError(error);
|
|
1323
1366
|
}
|
|
@@ -1360,7 +1403,7 @@ const prepareArchiveArgs = ({ xcodeScheme, archivePath, platform, sourceDir, xco
|
|
|
1360
1403
|
* `.app` to `outputDirectory`, and removes DerivedData, which takes 10 GB or
|
|
1361
1404
|
* more, whether the build succeeds or not.
|
|
1362
1405
|
*/
|
|
1363
|
-
const buildXcodeProject = async ({ sourceDir, outputDirectory, platform, xcodeScheme, configuration, deviceType, logPrefix, destination = [], useGenericDestination = false, installPods, extraParams, minBundleId, authenticationArgs }) => {
|
|
1406
|
+
const buildXcodeProject = async ({ sourceDir, outputDirectory, platform, xcodeScheme, configuration, deviceType, logPrefix, destination = [], useGenericDestination = false, installPods, extraParams, minBundleId, authenticationArgs, header = [] }) => {
|
|
1364
1407
|
if (installPods) await installPodsIfNeeded(sourceDir);
|
|
1365
1408
|
const xcodeProject = await parseXcodeProjectInfo(sourceDir);
|
|
1366
1409
|
const tmpDir = await createRandomTmpDir();
|
|
@@ -1398,26 +1441,26 @@ const buildXcodeProject = async ({ sourceDir, outputDirectory, platform, xcodeSc
|
|
|
1398
1441
|
sourceDir,
|
|
1399
1442
|
xcodeProject
|
|
1400
1443
|
});
|
|
1401
|
-
_hot_updater_cli_tools.p.log.info([
|
|
1402
|
-
"Xcode build",
|
|
1403
|
-
`Project ${xcodeProject.name}`,
|
|
1404
|
-
`Scheme ${xcodeScheme}`,
|
|
1405
|
-
`Configuration ${configuration}`,
|
|
1406
|
-
`Platform ${platform}`,
|
|
1407
|
-
`Device type ${deviceType}`,
|
|
1408
|
-
"Command",
|
|
1409
|
-
formatCommand(["xcodebuild", ...buildArgs])
|
|
1410
|
-
].join("\n"));
|
|
1411
1444
|
let builtAppPath;
|
|
1445
|
+
let logFilePath;
|
|
1412
1446
|
try {
|
|
1413
|
-
await runXcodebuildWithLogging({
|
|
1447
|
+
({logFilePath} = await runXcodebuildWithLogging({
|
|
1414
1448
|
args: buildArgs,
|
|
1415
1449
|
env: { HOT_UPDATER_MIN_BUNDLE_ID: minBundleId },
|
|
1416
1450
|
sourceDir,
|
|
1417
1451
|
logPrefix,
|
|
1418
|
-
|
|
1452
|
+
header: [
|
|
1453
|
+
...header,
|
|
1454
|
+
`Project ${xcodeProject.name}`,
|
|
1455
|
+
`Scheme ${xcodeScheme}`,
|
|
1456
|
+
`Configuration ${configuration}`,
|
|
1457
|
+
`Platform ${platform}`,
|
|
1458
|
+
`Device type ${deviceType}`
|
|
1459
|
+
],
|
|
1460
|
+
title: `Building ${xcodeScheme}`,
|
|
1461
|
+
successMessage: `Built ${xcodeScheme}`,
|
|
1419
1462
|
failureMessage: "Build failed"
|
|
1420
|
-
});
|
|
1463
|
+
}));
|
|
1421
1464
|
({appPath: builtAppPath} = await getBuildSettings({
|
|
1422
1465
|
configuration,
|
|
1423
1466
|
derivedDataPath,
|
|
@@ -1431,7 +1474,10 @@ const buildXcodeProject = async ({ sourceDir, outputDirectory, platform, xcodeSc
|
|
|
1431
1474
|
}
|
|
1432
1475
|
const appPath = path.default.join(outputDirectory, path.default.basename(builtAppPath));
|
|
1433
1476
|
await fs.default.promises.cp(builtAppPath, appPath, { recursive: true });
|
|
1434
|
-
return {
|
|
1477
|
+
return {
|
|
1478
|
+
appPath,
|
|
1479
|
+
logFilePath
|
|
1480
|
+
};
|
|
1435
1481
|
} finally {
|
|
1436
1482
|
await removeTmpDir(tmpDir);
|
|
1437
1483
|
}
|
|
@@ -1491,7 +1537,7 @@ const getBuildSettings = async ({ xcodeProject, sourceDir, xcodeScheme, configur
|
|
|
1491
1537
|
//#endregion
|
|
1492
1538
|
//#region src/builder/exportXcodeArchive.ts
|
|
1493
1539
|
/** Exports the archive into `exportPath`. */
|
|
1494
|
-
const exportXcodeArchive = async ({ archivePath, exportPath, sourceDir, exportExtraParams, exportOptionsPlist, logPrefix, authenticationArgs }) => {
|
|
1540
|
+
const exportXcodeArchive = async ({ archivePath, exportPath, sourceDir, exportExtraParams, exportOptionsPlist, logPrefix, logFilePath, title, authenticationArgs }) => {
|
|
1495
1541
|
const exportArgs = prepareExportArgs({
|
|
1496
1542
|
authenticationArgs,
|
|
1497
1543
|
archivePath,
|
|
@@ -1503,9 +1549,11 @@ const exportXcodeArchive = async ({ archivePath, exportPath, sourceDir, exportEx
|
|
|
1503
1549
|
await runXcodebuildWithLogging({
|
|
1504
1550
|
args: exportArgs,
|
|
1505
1551
|
sourceDir,
|
|
1506
|
-
|
|
1552
|
+
title: `Exporting ${title}`,
|
|
1553
|
+
successMessage: `Exported ${title}`,
|
|
1507
1554
|
failureMessage: "Export failed",
|
|
1508
|
-
logPrefix
|
|
1555
|
+
logPrefix,
|
|
1556
|
+
logFilePath
|
|
1509
1557
|
});
|
|
1510
1558
|
return { exportPath };
|
|
1511
1559
|
} catch (error) {
|
|
@@ -2587,7 +2635,7 @@ const readBuiltApp = async (appPath) => {
|
|
|
2587
2635
|
* once the caller has copied it out. A failed build removes it, and every
|
|
2588
2636
|
* other directory the build used, itself.
|
|
2589
2637
|
*/
|
|
2590
|
-
const buildIos = async ({ schemeConfig, minBundleId = (0, _hot_updater_cli_tools.createMinBundleId)(), credentials }) => {
|
|
2638
|
+
const buildIos = async ({ schemeConfig, minBundleId = (0, _hot_updater_cli_tools.createMinBundleId)(), credentials, logHeader = [] }) => {
|
|
2591
2639
|
await assertXcodebuildExist();
|
|
2592
2640
|
if (schemeConfig.simulator && schemeConfig.destination.length) {
|
|
2593
2641
|
_hot_updater_cli_tools.p.log.error("The scheme option 'simulator' is not compatible with the 'destination' option.");
|
|
@@ -2600,7 +2648,8 @@ const buildIos = async ({ schemeConfig, minBundleId = (0, _hot_updater_cli_tools
|
|
|
2600
2648
|
schemeConfig,
|
|
2601
2649
|
minBundleId,
|
|
2602
2650
|
credentials,
|
|
2603
|
-
buildDirectory
|
|
2651
|
+
buildDirectory,
|
|
2652
|
+
logHeader
|
|
2604
2653
|
}),
|
|
2605
2654
|
dispose: () => removeTmpDir(buildDirectory)
|
|
2606
2655
|
};
|
|
@@ -2609,15 +2658,16 @@ const buildIos = async ({ schemeConfig, minBundleId = (0, _hot_updater_cli_tools
|
|
|
2609
2658
|
throw error;
|
|
2610
2659
|
}
|
|
2611
2660
|
};
|
|
2612
|
-
const buildInto = async ({ schemeConfig, minBundleId, credentials, buildDirectory }) => {
|
|
2661
|
+
const buildInto = async ({ schemeConfig, minBundleId, credentials, buildDirectory, logHeader }) => {
|
|
2613
2662
|
const iosProjectRoot = path.default.join((0, _hot_updater_cli_tools.getCwd)(), "ios");
|
|
2614
2663
|
if (schemeConfig.simulator) {
|
|
2615
2664
|
const appDir = path.default.join(buildDirectory, "app");
|
|
2616
2665
|
await fs.default.promises.mkdir(appDir, { recursive: true });
|
|
2617
|
-
const { appPath } = await buildXcodeProject({
|
|
2666
|
+
const { appPath, logFilePath } = await buildXcodeProject({
|
|
2618
2667
|
sourceDir: iosProjectRoot,
|
|
2619
2668
|
outputDirectory: appDir,
|
|
2620
2669
|
logPrefix: `ios-${schemeConfig.hotUpdaterSchemeName}-build`,
|
|
2670
|
+
header: logHeader,
|
|
2621
2671
|
platform: schemeConfig.platform,
|
|
2622
2672
|
xcodeScheme: schemeConfig.scheme,
|
|
2623
2673
|
configuration: schemeConfig.configuration,
|
|
@@ -2630,6 +2680,7 @@ const buildInto = async ({ schemeConfig, minBundleId, credentials, buildDirector
|
|
|
2630
2680
|
return {
|
|
2631
2681
|
buildDirectory,
|
|
2632
2682
|
buildArtifactPath: appPath,
|
|
2683
|
+
logFilePath,
|
|
2633
2684
|
builtApp: await readBuiltApp(appPath)
|
|
2634
2685
|
};
|
|
2635
2686
|
} else {
|
|
@@ -2640,7 +2691,8 @@ const buildInto = async ({ schemeConfig, minBundleId, credentials, buildDirector
|
|
|
2640
2691
|
if (!signing) return archiveAndExport({
|
|
2641
2692
|
schemeConfig,
|
|
2642
2693
|
minBundleId,
|
|
2643
|
-
buildDirectory
|
|
2694
|
+
buildDirectory,
|
|
2695
|
+
logHeader
|
|
2644
2696
|
});
|
|
2645
2697
|
const appStoreConnect = await exportsForAppStoreConnect(schemeConfig);
|
|
2646
2698
|
if ((0, _hot_updater_cli_tools.isCI)() && !appStoreConnect) _hot_updater_cli_tools.p.log.info("Signing in the runner's own keychains (not an App Store Connect export).");
|
|
@@ -2651,6 +2703,7 @@ const buildInto = async ({ schemeConfig, minBundleId, credentials, buildDirector
|
|
|
2651
2703
|
schemeConfig,
|
|
2652
2704
|
minBundleId,
|
|
2653
2705
|
buildDirectory,
|
|
2706
|
+
logHeader,
|
|
2654
2707
|
authenticationArgs: signing.authenticationArgs
|
|
2655
2708
|
})
|
|
2656
2709
|
});
|
|
@@ -2660,12 +2713,12 @@ const buildInto = async ({ schemeConfig, minBundleId, credentials, buildDirector
|
|
|
2660
2713
|
* Archives a device build and exports its `.ipa`, or returns the
|
|
2661
2714
|
* `.xcarchive` of a scheme it cannot export.
|
|
2662
2715
|
*/
|
|
2663
|
-
const archiveAndExport = async ({ schemeConfig, minBundleId, buildDirectory, authenticationArgs }) => {
|
|
2716
|
+
const archiveAndExport = async ({ schemeConfig, minBundleId, buildDirectory, logHeader, authenticationArgs }) => {
|
|
2664
2717
|
const iosProjectRoot = path.default.join((0, _hot_updater_cli_tools.getCwd)(), "ios");
|
|
2665
2718
|
const archiveDir = path.default.join(buildDirectory, "archive");
|
|
2666
2719
|
const exportDir = path.default.join(buildDirectory, "export");
|
|
2667
2720
|
await fs.default.promises.mkdir(archiveDir, { recursive: true });
|
|
2668
|
-
const { archivePath } = await archiveXcodeProject({
|
|
2721
|
+
const { archivePath, logFilePath } = await archiveXcodeProject({
|
|
2669
2722
|
archiveDirectory: archiveDir,
|
|
2670
2723
|
authenticationArgs,
|
|
2671
2724
|
minBundleId,
|
|
@@ -2677,7 +2730,8 @@ const archiveAndExport = async ({ schemeConfig, minBundleId, buildDirectory, aut
|
|
|
2677
2730
|
extraParams: schemeConfig.extraParams,
|
|
2678
2731
|
configuration: schemeConfig.configuration,
|
|
2679
2732
|
installPods: schemeConfig.installPods,
|
|
2680
|
-
logPrefix: `ios-${schemeConfig.hotUpdaterSchemeName}-build
|
|
2733
|
+
logPrefix: `ios-${schemeConfig.hotUpdaterSchemeName}-build`,
|
|
2734
|
+
header: logHeader
|
|
2681
2735
|
});
|
|
2682
2736
|
const appFromArchive = extractAppFromXcarchive(archivePath);
|
|
2683
2737
|
const archivedAppPath = appFromArchive ? path.default.join(archiveDir, path.default.basename(appFromArchive)) : void 0;
|
|
@@ -2685,6 +2739,7 @@ const archiveAndExport = async ({ schemeConfig, minBundleId, buildDirectory, aut
|
|
|
2685
2739
|
if (!schemeConfig.exportOptionsPlist && !authenticationArgs) return {
|
|
2686
2740
|
buildDirectory,
|
|
2687
2741
|
buildArtifactPath: archivePath,
|
|
2742
|
+
logFilePath,
|
|
2688
2743
|
builtApp: archivedAppPath ? await readBuiltApp(archivedAppPath) : void 0
|
|
2689
2744
|
};
|
|
2690
2745
|
await fs.default.promises.mkdir(exportDir, { recursive: true });
|
|
@@ -2695,7 +2750,9 @@ const archiveAndExport = async ({ schemeConfig, minBundleId, buildDirectory, aut
|
|
|
2695
2750
|
exportExtraParams: schemeConfig.exportExtraParams,
|
|
2696
2751
|
exportOptionsPlist,
|
|
2697
2752
|
sourceDir: iosProjectRoot,
|
|
2698
|
-
logPrefix: `ios-${schemeConfig.hotUpdaterSchemeName}-build
|
|
2753
|
+
logPrefix: `ios-${schemeConfig.hotUpdaterSchemeName}-build`,
|
|
2754
|
+
logFilePath: logFilePath ?? void 0,
|
|
2755
|
+
title: schemeConfig.scheme
|
|
2699
2756
|
});
|
|
2700
2757
|
if (schemeConfig.exportOptionsPlist) await exportArchive(schemeConfig.exportOptionsPlist);
|
|
2701
2758
|
else {
|
|
@@ -2711,6 +2768,7 @@ const archiveAndExport = async ({ schemeConfig, minBundleId, buildDirectory, aut
|
|
|
2711
2768
|
return {
|
|
2712
2769
|
buildDirectory,
|
|
2713
2770
|
buildArtifactPath: path.default.join(exportDir, ipaFile),
|
|
2771
|
+
logFilePath,
|
|
2714
2772
|
builtApp: archivedAppPath ? await readBuiltApp(archivedAppPath) : void 0
|
|
2715
2773
|
};
|
|
2716
2774
|
};
|
package/dist/index.d.cts
CHANGED
|
@@ -81,15 +81,19 @@ declare const enrichNativeBuildIosScheme: ({ scheme, hotUpdaterSchemeName }: {
|
|
|
81
81
|
* once the caller has copied it out. A failed build removes it, and every
|
|
82
82
|
* other directory the build used, itself.
|
|
83
83
|
*/
|
|
84
|
-
declare const buildIos: ({ schemeConfig, minBundleId, credentials }: {
|
|
84
|
+
declare const buildIos: ({ schemeConfig, minBundleId, credentials, logHeader }: {
|
|
85
85
|
schemeConfig: EnrichedNativeBuildIosScheme;
|
|
86
86
|
/** The MIN_BUNDLE_ID the build compiles in. */
|
|
87
87
|
minBundleId?: string;
|
|
88
88
|
/** Resolves the scheme's `signing` key, when a device build signs with one. */
|
|
89
89
|
credentials?: Pick<CredentialResolver, "resolve">;
|
|
90
|
+
/** Lines the build log starts with, such as the native build key. */
|
|
91
|
+
logHeader?: string[];
|
|
90
92
|
}) => Promise<{
|
|
91
93
|
buildDirectory: string;
|
|
92
94
|
buildArtifactPath: string;
|
|
95
|
+
/** The build log, which an archive's export appends to, or null. */
|
|
96
|
+
logFilePath: string | null;
|
|
93
97
|
/** What the built app reports, from its Info.plist. */
|
|
94
98
|
builtApp?: {
|
|
95
99
|
appVersion: string | null;
|
|
@@ -191,7 +195,14 @@ declare const uploadToTestFlight: ({ ipaPath, apiKey, fetch, pollIntervalMs, pro
|
|
|
191
195
|
type AppleDeviceType = "device" | "simulator";
|
|
192
196
|
//#endregion
|
|
193
197
|
//#region src/utils/cocoapods.d.ts
|
|
194
|
-
|
|
198
|
+
/** What `installPodsIfNeeded` ran. */
|
|
199
|
+
interface PodInstallSteps {
|
|
200
|
+
/** React Native codegen, before `pod install`. */
|
|
201
|
+
readonly codegen: boolean;
|
|
202
|
+
/** `pod install`, which a project without a Podfile skips. */
|
|
203
|
+
readonly pods: boolean;
|
|
204
|
+
}
|
|
205
|
+
declare const installPodsIfNeeded: (sourceDir: string) => Promise<PodInstallSteps>;
|
|
195
206
|
//#endregion
|
|
196
207
|
//#region src/utils/extractBuiltInBundle.d.ts
|
|
197
208
|
/**
|