@hot-updater/apple-helper 1.2.0-rc.6 → 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 +299 -191
- package/dist/index.d.cts +13 -2
- package/dist/index.d.mts +13 -2
- package/dist/index.mjs +299 -191
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -551,6 +551,23 @@ const signInToAppStoreConnect = async ({ appleId, password, prompts, fetch = glo
|
|
|
551
551
|
}
|
|
552
552
|
};
|
|
553
553
|
//#endregion
|
|
554
|
+
//#region src/asc/programLicenseAgreement.ts
|
|
555
|
+
/**
|
|
556
|
+
* What to do when Apple holds a team's requests until the Account Holder
|
|
557
|
+
* accepts the latest Apple Developer Program License Agreement: App Store
|
|
558
|
+
* Connect answers 403, and xcodebuild prints "PLA Update available".
|
|
559
|
+
*/
|
|
560
|
+
const PROGRAM_LICENSE_AGREEMENT_HINT = ["The Account Holder must accept the latest Program License Agreement:", "https://developer.apple.com/account"].join("\n");
|
|
561
|
+
/** The code App Store Connect answers a team with a pending agreement. */
|
|
562
|
+
const AGREEMENT_ERROR_CODE = "FORBIDDEN.REQUIRED_AGREEMENTS_MISSING_OR_EXPIRED";
|
|
563
|
+
/**
|
|
564
|
+
* Whether App Store Connect's JSON:API errors say the team has a Program
|
|
565
|
+
* License Agreement to accept, by its code or its detail.
|
|
566
|
+
*/
|
|
567
|
+
const isProgramLicenseAgreementError = (errors) => errors.some(({ code, detail, title }) => code === AGREEMENT_ERROR_CODE || /Program License Agreement/iu.test(`${detail ?? ""} ${title ?? ""}`));
|
|
568
|
+
/** Whether xcodebuild's output says the team has an agreement to accept. */
|
|
569
|
+
const mentionsProgramLicenseAgreement = (output) => output.includes("PLA Update available");
|
|
570
|
+
//#endregion
|
|
554
571
|
//#region src/asc/ascKeyIssuer.ts
|
|
555
572
|
const IRIS_URL = "https://appstoreconnect.apple.com/iris/v1";
|
|
556
573
|
/**
|
|
@@ -603,7 +620,7 @@ const irisDetail = (body) => {
|
|
|
603
620
|
};
|
|
604
621
|
/** What to do about a key App Store Connect would not create. */
|
|
605
622
|
const createFailureHint = (status, body) => {
|
|
606
|
-
if (/agreement/iu.test(body)) return
|
|
623
|
+
if (/agreement/iu.test(body)) return PROGRAM_LICENSE_AGREEMENT_HINT;
|
|
607
624
|
if (/limit|maximum/iu.test(body)) return `The team has the most API keys App Store Connect allows. Revoke one at:\n${ASC_INTEGRATIONS_URL}`;
|
|
608
625
|
if (status === 403 || /account holder|admin|permission/iu.test(body)) return "Creating an API key needs the Account Holder or Admin role in the team.";
|
|
609
626
|
};
|
|
@@ -766,11 +783,23 @@ const ASC_INTEGRATIONS_URL = "https://appstoreconnect.apple.com/access/integrati
|
|
|
766
783
|
const issueAscApiKey = (request) => createAscKeyIssuer()(request);
|
|
767
784
|
//#endregion
|
|
768
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
|
+
};
|
|
769
795
|
const installPodsIfNeeded = async (sourceDir) => {
|
|
770
796
|
const podfilePath = path.default.join(sourceDir, "Podfile");
|
|
771
797
|
if (!fs.default.existsSync(podfilePath)) {
|
|
772
798
|
_hot_updater_cli_tools.p.log.info("No Podfile found, skipping CocoaPods installation");
|
|
773
|
-
return
|
|
799
|
+
return {
|
|
800
|
+
codegen: false,
|
|
801
|
+
pods: false
|
|
802
|
+
};
|
|
774
803
|
}
|
|
775
804
|
const gemfilePaths = [path.default.join(sourceDir, "Gemfile"), path.default.join((0, _hot_updater_cli_tools.getCwd)(), "Gemfile")];
|
|
776
805
|
const shouldUseBundler = (await Promise.all(gemfilePaths.map(checkShouldUseBundler))).some(Boolean);
|
|
@@ -784,36 +813,39 @@ const installPodsIfNeeded = async (sourceDir) => {
|
|
|
784
813
|
reactNativePath: reactNative.packagePath,
|
|
785
814
|
sourceDir
|
|
786
815
|
});
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
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 {
|
|
792
820
|
await (0, execa.execa)("bundle", ["install"], { cwd: sourceDir });
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
await (0, execa.execa)("bundle", [
|
|
797
|
-
"exec",
|
|
798
|
-
"pod",
|
|
799
|
-
"install"
|
|
800
|
-
], {
|
|
801
|
-
cwd: sourceDir,
|
|
802
|
-
env: plan.env
|
|
803
|
-
});
|
|
804
|
-
podSpinner.stop("CocoaPods dependencies installed");
|
|
805
|
-
} else {
|
|
806
|
-
const spinner = _hot_updater_cli_tools.p.spinner();
|
|
807
|
-
spinner.start("Installing CocoaPods dependencies");
|
|
808
|
-
await (0, execa.execa)("pod", ["install"], {
|
|
809
|
-
cwd: sourceDir,
|
|
810
|
-
env: plan.env
|
|
811
|
-
});
|
|
812
|
-
spinner.stop("CocoaPods dependencies installed");
|
|
821
|
+
} catch (error) {
|
|
822
|
+
bundleSpinner.error("bundle install failed");
|
|
823
|
+
throw commandFailure("bundle install", error);
|
|
813
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
|
+
});
|
|
814
840
|
} catch (error) {
|
|
815
|
-
|
|
841
|
+
podSpinner.error(`${podCommand.join(" ")} failed`);
|
|
842
|
+
throw commandFailure(podCommand.join(" "), error);
|
|
816
843
|
}
|
|
844
|
+
podSpinner.stop("CocoaPods dependencies installed");
|
|
845
|
+
return {
|
|
846
|
+
codegen: plan.runCodegen,
|
|
847
|
+
pods: true
|
|
848
|
+
};
|
|
817
849
|
};
|
|
818
850
|
const checkShouldUseBundler = async (gemfilePath) => {
|
|
819
851
|
try {
|
|
@@ -846,8 +878,8 @@ const runReactNativeCodegen = async ({ projectRoot, reactNativePath, sourceDir }
|
|
|
846
878
|
"ios"
|
|
847
879
|
], { cwd: projectRoot });
|
|
848
880
|
} catch (error) {
|
|
849
|
-
spinner.
|
|
850
|
-
throw
|
|
881
|
+
spinner.error("React Native codegen failed");
|
|
882
|
+
throw commandFailure("React Native codegen", error);
|
|
851
883
|
}
|
|
852
884
|
spinner.stop("React Native codegen done");
|
|
853
885
|
};
|
|
@@ -977,26 +1009,6 @@ const getDefaultDestination = ({ platform, useGeneric, deviceType }) => {
|
|
|
977
1009
|
});
|
|
978
1010
|
};
|
|
979
1011
|
//#endregion
|
|
980
|
-
//#region src/utils/formatCommand.ts
|
|
981
|
-
/**
|
|
982
|
-
* A command to print, broken at spaces with ` \` once a line would pass
|
|
983
|
-
* `width`, each continuation indented two spaces, so a terminal never wraps
|
|
984
|
-
* it mid-word and it still pastes as one command.
|
|
985
|
-
*/
|
|
986
|
-
const formatCommand = (words, width = 74) => {
|
|
987
|
-
const lines = [];
|
|
988
|
-
let line = "";
|
|
989
|
-
for (const word of words) {
|
|
990
|
-
const next = line ? `${line} ${word}` : word;
|
|
991
|
-
if (line && next.length > width - 2) {
|
|
992
|
-
lines.push(`${line} \\`);
|
|
993
|
-
line = ` ${word}`;
|
|
994
|
-
} else line = next;
|
|
995
|
-
}
|
|
996
|
-
lines.push(line);
|
|
997
|
-
return lines.join("\n");
|
|
998
|
-
};
|
|
999
|
-
//#endregion
|
|
1000
1012
|
//#region src/utils/parseXcodeProjectInfo.ts
|
|
1001
1013
|
const parseXcodeProjectInfo = async (sourceDir) => {
|
|
1002
1014
|
const files = await fs.default.promises.readdir(sourceDir);
|
|
@@ -1019,80 +1031,133 @@ const parseXcodeProjectInfo = async (sourceDir) => {
|
|
|
1019
1031
|
const prettifyXcodebuildError = (error) => {
|
|
1020
1032
|
if (error instanceof _hot_updater_cli_tools.BuildFailedError) {
|
|
1021
1033
|
const errorLines = error.summary.join("\n");
|
|
1022
|
-
const
|
|
1023
|
-
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, {
|
|
1024
1036
|
logPath: error.logPath,
|
|
1025
1037
|
summary: error.summary,
|
|
1038
|
+
hint,
|
|
1026
1039
|
cause: error.cause
|
|
1027
1040
|
});
|
|
1028
1041
|
}
|
|
1029
1042
|
return /* @__PURE__ */ new Error(`Xcodebuild failed: ${error}`);
|
|
1030
1043
|
};
|
|
1031
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
|
|
1032
1120
|
//#region src/utils/createXcodebuildLogger.ts
|
|
1033
|
-
const createXcodebuildLogger = ({ logPrefix }) => new _hot_updater_cli_tools.BuildLogger({
|
|
1121
|
+
const createXcodebuildLogger = ({ logPrefix, logFilePath }) => new _hot_updater_cli_tools.BuildLogger({
|
|
1034
1122
|
logPrefix,
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
"** BUILD FAILED **",
|
|
1038
|
-
"** ARCHIVE FAILED **",
|
|
1039
|
-
"The following build commands failed:",
|
|
1040
|
-
"note: Building targets in dependency order",
|
|
1041
|
-
"note: Target dependency graph",
|
|
1042
|
-
"Running script",
|
|
1043
|
-
"[CP-User]",
|
|
1044
|
-
"[CP] Check Pods Manifest.lock",
|
|
1045
|
-
"Build Succeeded"
|
|
1046
|
-
],
|
|
1047
|
-
progressStages: [
|
|
1048
|
-
["Building targets in dependency order", "Target dependency graph"],
|
|
1049
|
-
["Write Auxiliary File", "[CP] Check Pods Manifest.lock"],
|
|
1050
|
-
[
|
|
1051
|
-
"Running script",
|
|
1052
|
-
"[CP-User] [RN]Check rncore",
|
|
1053
|
-
"[CP-User] [Hermes] Replace Hermes",
|
|
1054
|
-
"[CP-User] [RN]Check FBReactNativeSpec"
|
|
1055
|
-
],
|
|
1056
|
-
[
|
|
1057
|
-
"Processing",
|
|
1058
|
-
"ProcessProductPackaging",
|
|
1059
|
-
"ProcessProductPackagingDER"
|
|
1060
|
-
],
|
|
1061
|
-
["Compiling"],
|
|
1062
|
-
["RegisterExecutionPolicyException"],
|
|
1063
|
-
["Touching"],
|
|
1064
|
-
["Building library", "Create Universal Binary"],
|
|
1065
|
-
["Copying"],
|
|
1066
|
-
[
|
|
1067
|
-
"Build Succeeded",
|
|
1068
|
-
"BUILD SUCCEEDED",
|
|
1069
|
-
"ARCHIVE SUCCEEDED"
|
|
1070
|
-
]
|
|
1071
|
-
],
|
|
1123
|
+
logFilePath,
|
|
1124
|
+
createPhaseReader: createXcodebuildPhaseReader,
|
|
1072
1125
|
failureSummary: {
|
|
1073
1126
|
lines: [/(^|:\s)error: /, /^\*\* [A-Z ]+ FAILED \*\*$/],
|
|
1074
1127
|
blocks: [{
|
|
1075
1128
|
start: "The following build commands failed:",
|
|
1076
1129
|
end: /^\(\d+ failures?\)$/,
|
|
1077
1130
|
includeEnd: true
|
|
1131
|
+
}, {
|
|
1132
|
+
start: /^error: exportArchive /,
|
|
1133
|
+
end: /^$/,
|
|
1134
|
+
includeEnd: false
|
|
1078
1135
|
}]
|
|
1079
1136
|
}
|
|
1080
1137
|
});
|
|
1081
1138
|
//#endregion
|
|
1082
|
-
//#region src/utils/
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
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;
|
|
1094
1154
|
}
|
|
1155
|
+
lines.push(line);
|
|
1156
|
+
return lines.join("\n");
|
|
1095
1157
|
};
|
|
1158
|
+
//#endregion
|
|
1159
|
+
//#region src/utils/runXcodebuildWithLogging.ts
|
|
1160
|
+
const TERMINATION_SIGNALS$1 = ["SIGINT", "SIGTERM"];
|
|
1096
1161
|
const createRawXcodebuildProcess = ({ args, env, sourceDir }) => {
|
|
1097
1162
|
return (0, execa.execa)("xcodebuild", args, {
|
|
1098
1163
|
cwd: sourceDir,
|
|
@@ -1105,13 +1170,6 @@ const createRawXcodebuildProcess = ({ args, env, sourceDir }) => {
|
|
|
1105
1170
|
}
|
|
1106
1171
|
});
|
|
1107
1172
|
};
|
|
1108
|
-
const createXcbeautifyProcess = ({ cwd }) => {
|
|
1109
|
-
return (0, execa.execa)("xcbeautify", [], {
|
|
1110
|
-
cwd,
|
|
1111
|
-
all: true,
|
|
1112
|
-
buffer: false
|
|
1113
|
-
});
|
|
1114
|
-
};
|
|
1115
1173
|
/** Why xcodebuild failed, without its command line. */
|
|
1116
1174
|
const describeFailure = (error) => {
|
|
1117
1175
|
if (error instanceof execa.ExecaError) {
|
|
@@ -1120,10 +1178,24 @@ const describeFailure = (error) => {
|
|
|
1120
1178
|
}
|
|
1121
1179
|
return error instanceof Error ? error.message : String(error);
|
|
1122
1180
|
};
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
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
|
+
});
|
|
1127
1199
|
const activeProcesses = [];
|
|
1128
1200
|
const terminateActiveProcesses = () => {
|
|
1129
1201
|
for (const activeProcess of activeProcesses) activeProcess.kill();
|
|
@@ -1137,31 +1209,17 @@ const runXcodebuildWithLogging = async ({ args, env, sourceDir, successMessage,
|
|
|
1137
1209
|
});
|
|
1138
1210
|
activeProcesses.push(xcodebuildProcess);
|
|
1139
1211
|
const xcodebuildOutput = xcodebuildProcess.all ?? xcodebuildProcess.stdout;
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
xcbeautifyProcess = createXcbeautifyProcess({ cwd: sourceDir });
|
|
1143
|
-
activeProcesses.push(xcbeautifyProcess);
|
|
1144
|
-
}
|
|
1145
|
-
const logging = xcodebuildOutput ? logger.processStream(xcodebuildOutput, { display: xcbeautifyProcess ? xcbeautifyProcess.all ?? xcbeautifyProcess.stdout ?? void 0 : void 0 }) : Promise.resolve();
|
|
1146
|
-
const xcbeautifyInput = xcbeautifyProcess?.stdin;
|
|
1147
|
-
if (xcodebuildOutput && xcbeautifyInput) {
|
|
1148
|
-
xcbeautifyInput.on("error", () => {
|
|
1149
|
-
xcodebuildOutput.unpipe(xcbeautifyInput);
|
|
1150
|
-
});
|
|
1151
|
-
xcodebuildOutput.pipe(xcbeautifyInput);
|
|
1152
|
-
}
|
|
1153
|
-
const [xcodebuildResult, loggingResult] = await Promise.allSettled([
|
|
1154
|
-
xcodebuildProcess,
|
|
1155
|
-
logging,
|
|
1156
|
-
xcbeautifyProcess
|
|
1157
|
-
]);
|
|
1212
|
+
const logging = xcodebuildOutput ? logger.processStream(xcodebuildOutput) : Promise.resolve();
|
|
1213
|
+
const [xcodebuildResult, loggingResult] = await Promise.allSettled([xcodebuildProcess, logging]);
|
|
1158
1214
|
if (xcodebuildResult.status === "rejected") throw xcodebuildResult.reason;
|
|
1159
1215
|
if (loggingResult.status === "rejected") throw loggingResult.reason;
|
|
1160
1216
|
logger.stop(successMessage);
|
|
1217
|
+
return { logFilePath: logger.logFilePath };
|
|
1161
1218
|
} catch (error) {
|
|
1162
1219
|
logger.writeError(error);
|
|
1163
|
-
|
|
1164
|
-
|
|
1220
|
+
const reason = describeFailure(error);
|
|
1221
|
+
logger.stop(`${failureMessage} · ${reason.replace(/\.$/, "")}`, false);
|
|
1222
|
+
throw new _hot_updater_cli_tools.BuildFailedError(`${failureMessage}: ${reason}`, {
|
|
1165
1223
|
logPath: logger.logPath,
|
|
1166
1224
|
summary: logger.failureSummary,
|
|
1167
1225
|
cause: error
|
|
@@ -1253,7 +1311,7 @@ const buildSettingsOverScheme = ({ minBundleId, authenticationArgs }) => ({
|
|
|
1253
1311
|
* written to a temporary directory, removed whether the archive succeeds or
|
|
1254
1312
|
* not.
|
|
1255
1313
|
*/
|
|
1256
|
-
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 }) => {
|
|
1257
1315
|
if (installPods) await installPodsIfNeeded(sourceDir);
|
|
1258
1316
|
const xcodeProject = await parseXcodeProjectInfo(sourceDir);
|
|
1259
1317
|
const archiveName = `${xcodeProject.name.replace(".xcworkspace", "").replace(".xcodeproj", "")}.xcarchive`;
|
|
@@ -1283,24 +1341,26 @@ const archiveXcodeProject = async ({ sourceDir, archiveDirectory, platform, inst
|
|
|
1283
1341
|
xcconfig: build.xcconfig,
|
|
1284
1342
|
xcodeScheme
|
|
1285
1343
|
});
|
|
1286
|
-
_hot_updater_cli_tools.p.log.info([
|
|
1287
|
-
"Xcode archive",
|
|
1288
|
-
`Project ${xcodeProject.name}`,
|
|
1289
|
-
`Scheme ${xcodeScheme}`,
|
|
1290
|
-
`Platform ${platform}`,
|
|
1291
|
-
"Command",
|
|
1292
|
-
formatCommand(["xcodebuild", ...archiveArgs])
|
|
1293
|
-
].join("\n"));
|
|
1294
1344
|
try {
|
|
1295
|
-
await runXcodebuildWithLogging({
|
|
1345
|
+
const { logFilePath } = await runXcodebuildWithLogging({
|
|
1296
1346
|
args: archiveArgs,
|
|
1297
1347
|
env: { HOT_UPDATER_MIN_BUNDLE_ID: minBundleId },
|
|
1348
|
+
title: `Archiving ${xcodeScheme}`,
|
|
1349
|
+
successMessage: `Archived ${xcodeScheme}`,
|
|
1298
1350
|
failureMessage: "Archive failed",
|
|
1299
1351
|
logPrefix,
|
|
1300
|
-
|
|
1301
|
-
|
|
1352
|
+
header: [
|
|
1353
|
+
...header,
|
|
1354
|
+
`Project ${xcodeProject.name}`,
|
|
1355
|
+
`Scheme ${xcodeScheme}`,
|
|
1356
|
+
`Platform ${platform}`
|
|
1357
|
+
],
|
|
1358
|
+
sourceDir
|
|
1302
1359
|
});
|
|
1303
|
-
return {
|
|
1360
|
+
return {
|
|
1361
|
+
archivePath,
|
|
1362
|
+
logFilePath
|
|
1363
|
+
};
|
|
1304
1364
|
} catch (error) {
|
|
1305
1365
|
throw prettifyXcodebuildError(error);
|
|
1306
1366
|
}
|
|
@@ -1343,7 +1403,7 @@ const prepareArchiveArgs = ({ xcodeScheme, archivePath, platform, sourceDir, xco
|
|
|
1343
1403
|
* `.app` to `outputDirectory`, and removes DerivedData, which takes 10 GB or
|
|
1344
1404
|
* more, whether the build succeeds or not.
|
|
1345
1405
|
*/
|
|
1346
|
-
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 = [] }) => {
|
|
1347
1407
|
if (installPods) await installPodsIfNeeded(sourceDir);
|
|
1348
1408
|
const xcodeProject = await parseXcodeProjectInfo(sourceDir);
|
|
1349
1409
|
const tmpDir = await createRandomTmpDir();
|
|
@@ -1381,26 +1441,26 @@ const buildXcodeProject = async ({ sourceDir, outputDirectory, platform, xcodeSc
|
|
|
1381
1441
|
sourceDir,
|
|
1382
1442
|
xcodeProject
|
|
1383
1443
|
});
|
|
1384
|
-
_hot_updater_cli_tools.p.log.info([
|
|
1385
|
-
"Xcode build",
|
|
1386
|
-
`Project ${xcodeProject.name}`,
|
|
1387
|
-
`Scheme ${xcodeScheme}`,
|
|
1388
|
-
`Configuration ${configuration}`,
|
|
1389
|
-
`Platform ${platform}`,
|
|
1390
|
-
`Device type ${deviceType}`,
|
|
1391
|
-
"Command",
|
|
1392
|
-
formatCommand(["xcodebuild", ...buildArgs])
|
|
1393
|
-
].join("\n"));
|
|
1394
1444
|
let builtAppPath;
|
|
1445
|
+
let logFilePath;
|
|
1395
1446
|
try {
|
|
1396
|
-
await runXcodebuildWithLogging({
|
|
1447
|
+
({logFilePath} = await runXcodebuildWithLogging({
|
|
1397
1448
|
args: buildArgs,
|
|
1398
1449
|
env: { HOT_UPDATER_MIN_BUNDLE_ID: minBundleId },
|
|
1399
1450
|
sourceDir,
|
|
1400
1451
|
logPrefix,
|
|
1401
|
-
|
|
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}`,
|
|
1402
1462
|
failureMessage: "Build failed"
|
|
1403
|
-
});
|
|
1463
|
+
}));
|
|
1404
1464
|
({appPath: builtAppPath} = await getBuildSettings({
|
|
1405
1465
|
configuration,
|
|
1406
1466
|
derivedDataPath,
|
|
@@ -1414,7 +1474,10 @@ const buildXcodeProject = async ({ sourceDir, outputDirectory, platform, xcodeSc
|
|
|
1414
1474
|
}
|
|
1415
1475
|
const appPath = path.default.join(outputDirectory, path.default.basename(builtAppPath));
|
|
1416
1476
|
await fs.default.promises.cp(builtAppPath, appPath, { recursive: true });
|
|
1417
|
-
return {
|
|
1477
|
+
return {
|
|
1478
|
+
appPath,
|
|
1479
|
+
logFilePath
|
|
1480
|
+
};
|
|
1418
1481
|
} finally {
|
|
1419
1482
|
await removeTmpDir(tmpDir);
|
|
1420
1483
|
}
|
|
@@ -1474,7 +1537,7 @@ const getBuildSettings = async ({ xcodeProject, sourceDir, xcodeScheme, configur
|
|
|
1474
1537
|
//#endregion
|
|
1475
1538
|
//#region src/builder/exportXcodeArchive.ts
|
|
1476
1539
|
/** Exports the archive into `exportPath`. */
|
|
1477
|
-
const exportXcodeArchive = async ({ archivePath, exportPath, sourceDir, exportExtraParams, exportOptionsPlist, logPrefix, authenticationArgs }) => {
|
|
1540
|
+
const exportXcodeArchive = async ({ archivePath, exportPath, sourceDir, exportExtraParams, exportOptionsPlist, logPrefix, logFilePath, title, authenticationArgs }) => {
|
|
1478
1541
|
const exportArgs = prepareExportArgs({
|
|
1479
1542
|
authenticationArgs,
|
|
1480
1543
|
archivePath,
|
|
@@ -1486,9 +1549,11 @@ const exportXcodeArchive = async ({ archivePath, exportPath, sourceDir, exportEx
|
|
|
1486
1549
|
await runXcodebuildWithLogging({
|
|
1487
1550
|
args: exportArgs,
|
|
1488
1551
|
sourceDir,
|
|
1489
|
-
|
|
1552
|
+
title: `Exporting ${title}`,
|
|
1553
|
+
successMessage: `Exported ${title}`,
|
|
1490
1554
|
failureMessage: "Export failed",
|
|
1491
|
-
logPrefix
|
|
1555
|
+
logPrefix,
|
|
1556
|
+
logFilePath
|
|
1492
1557
|
});
|
|
1493
1558
|
return { exportPath };
|
|
1494
1559
|
} catch (error) {
|
|
@@ -1917,7 +1982,7 @@ const createAppStoreConnectRequest = ({ apiKey, privateKey, fetch }) => {
|
|
|
1917
1982
|
if (!response.ok) {
|
|
1918
1983
|
const error = new AppStoreConnectError(action, response.status, await readApiErrors(response), response.headers.get("retry-after"));
|
|
1919
1984
|
if (response.status === 401) throw new Error("App Store Connect refused the API key.\nCheck its key ID and issuer ID, and that it isn't revoked.", { cause: error });
|
|
1920
|
-
if (response.status === 403) throw new Error(`${error.message}\nUploads need a key with the Admin, App Manager, or Developer role.`, { cause: error });
|
|
1985
|
+
if (response.status === 403) throw new Error(isProgramLicenseAgreementError(error.errors) ? `${action} failed (HTTP 403).\n${PROGRAM_LICENSE_AGREEMENT_HINT}` : `${error.message}\nUploads need a key with the Admin, App Manager, or Developer role.`, { cause: error });
|
|
1921
1986
|
throw error;
|
|
1922
1987
|
}
|
|
1923
1988
|
return response.status === 204 ? void 0 : await response.json();
|
|
@@ -2356,38 +2421,62 @@ const createSigningKeychain = async ({ security = runSecurity, directory = os.de
|
|
|
2356
2421
|
return {
|
|
2357
2422
|
path: keychainPath,
|
|
2358
2423
|
certificates: async () => {
|
|
2424
|
+
const identities = new Set([...(await security([
|
|
2425
|
+
"find-identity",
|
|
2426
|
+
"-p",
|
|
2427
|
+
"codesigning",
|
|
2428
|
+
keychainPath
|
|
2429
|
+
])).matchAll(/^\s*\d+\)\s+([0-9A-F]{40})\s/gm)].map((match) => match[1]));
|
|
2430
|
+
if (identities.size === 0) return [];
|
|
2359
2431
|
return ((await security([
|
|
2360
2432
|
"find-certificate",
|
|
2361
2433
|
"-a",
|
|
2362
2434
|
"-p",
|
|
2363
2435
|
keychainPath
|
|
2364
|
-
])).match(/-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/g) ?? []).
|
|
2436
|
+
])).match(/-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/g) ?? []).flatMap((pem) => {
|
|
2365
2437
|
const certificate = new crypto.default.X509Certificate(pem);
|
|
2366
|
-
return
|
|
2438
|
+
if (!identities.has(certificate.fingerprint.replaceAll(":", ""))) return [];
|
|
2439
|
+
return [{
|
|
2367
2440
|
serialNumber: normalizeSerial(certificate.serialNumber),
|
|
2368
2441
|
commonName: certificate.subject.match(/^CN=(.*)$/m)?.[1] ?? certificate.subject
|
|
2369
|
-
};
|
|
2442
|
+
}];
|
|
2370
2443
|
});
|
|
2371
2444
|
},
|
|
2372
2445
|
dispose
|
|
2373
2446
|
};
|
|
2374
2447
|
};
|
|
2448
|
+
/** A request App Store Connect refused, in one line. */
|
|
2449
|
+
var AppStoreConnectRefusal = class extends Error {
|
|
2450
|
+
/** Whether the team has a Program License Agreement to accept. */
|
|
2451
|
+
agreement;
|
|
2452
|
+
constructor(message, agreement) {
|
|
2453
|
+
super(message);
|
|
2454
|
+
this.agreement = agreement;
|
|
2455
|
+
}
|
|
2456
|
+
};
|
|
2457
|
+
const refusal = async (action, response) => {
|
|
2458
|
+
const body = await response.json().catch(() => void 0);
|
|
2459
|
+
return new AppStoreConnectRefusal(`${action} failed (HTTP ${response.status}).`, isProgramLicenseAgreementError(Array.isArray(body?.errors) ? body.errors : []));
|
|
2460
|
+
};
|
|
2375
2461
|
/**
|
|
2376
2462
|
* Revokes the certificates Xcode created in the build's keychain, whose
|
|
2377
2463
|
* private keys the keychain's deletion destroys: on a runner that starts
|
|
2378
2464
|
* empty, Xcode creates a development certificate for every build, and App
|
|
2379
2465
|
* Store Connect refuses new ones once a team has its maximum. Each is found
|
|
2380
2466
|
* in the team's certificates by serial number. A revocation that fails is
|
|
2381
|
-
* reported with
|
|
2467
|
+
* reported with each certificate's name and serial and where to revoke it
|
|
2468
|
+
* by hand, and never fails the build.
|
|
2382
2469
|
*/
|
|
2383
2470
|
const revokeKeychainCertificates = async ({ certificates, apiKey, privateKey, fetch = globalThis.fetch }) => {
|
|
2384
2471
|
const revoked = [];
|
|
2385
2472
|
const failed = [];
|
|
2473
|
+
/** Why none could be revoked: the token or the listing failed. */
|
|
2474
|
+
let reason;
|
|
2475
|
+
let agreement = false;
|
|
2386
2476
|
if (certificates.length === 0) return {
|
|
2387
|
-
revoked,
|
|
2388
|
-
failed
|
|
2477
|
+
revoked: [],
|
|
2478
|
+
failed: []
|
|
2389
2479
|
};
|
|
2390
|
-
const describe = ({ commonName, serialNumber }) => `${commonName} (serial ${serialNumber})`;
|
|
2391
2480
|
try {
|
|
2392
2481
|
const token = createAppStoreConnectToken({
|
|
2393
2482
|
privateKey: privateKey ?? crypto.default.createPrivateKey(await fs.default.promises.readFile(apiKey.keyPath, "utf-8")),
|
|
@@ -2403,7 +2492,7 @@ const revokeKeychainCertificates = async ({ certificates, apiKey, privateKey, fe
|
|
|
2403
2492
|
})}`;
|
|
2404
2493
|
while (next) {
|
|
2405
2494
|
const response = await fetch(next, { headers });
|
|
2406
|
-
if (!response.ok) throw
|
|
2495
|
+
if (!response.ok) throw await refusal("Listing the team's certificates", response);
|
|
2407
2496
|
const body = await response.json();
|
|
2408
2497
|
for (const { id, attributes } of body.data ?? []) if (attributes?.serialNumber) idsBySerial.set(normalizeSerial(attributes.serialNumber), id);
|
|
2409
2498
|
next = body.links?.next;
|
|
@@ -2415,23 +2504,32 @@ const revokeKeychainCertificates = async ({ certificates, apiKey, privateKey, fe
|
|
|
2415
2504
|
method: "DELETE",
|
|
2416
2505
|
headers
|
|
2417
2506
|
});
|
|
2418
|
-
if (response.ok || response.status === 404) revoked.push(
|
|
2419
|
-
else
|
|
2507
|
+
if (response.ok || response.status === 404) revoked.push(certificate);
|
|
2508
|
+
else {
|
|
2509
|
+
agreement ||= (await refusal("Revoking", response)).agreement;
|
|
2510
|
+
failed.push({
|
|
2511
|
+
certificate,
|
|
2512
|
+
status: response.status
|
|
2513
|
+
});
|
|
2514
|
+
}
|
|
2420
2515
|
}
|
|
2421
2516
|
} catch (error) {
|
|
2422
|
-
|
|
2423
|
-
|
|
2517
|
+
reason = error instanceof Error ? error.message : String(error);
|
|
2518
|
+
agreement ||= error instanceof AppStoreConnectRefusal && error.agreement;
|
|
2519
|
+
for (const certificate of certificates) if (!revoked.includes(certificate)) failed.push({ certificate });
|
|
2424
2520
|
}
|
|
2425
|
-
|
|
2521
|
+
const describe = ({ commonName, serialNumber }) => `${commonName} (serial ${serialNumber})`;
|
|
2522
|
+
if (revoked.length > 0) _hot_updater_cli_tools.p.log.info(["Revoked the certificates Xcode created for this build:", ...revoked.flatMap(({ commonName, serialNumber }) => [commonName, `Serial ${serialNumber}`])].join("\n"));
|
|
2426
2523
|
if (failed.length > 0) _hot_updater_cli_tools.p.log.warn([
|
|
2427
2524
|
"Couldn't revoke the certificates Xcode created for this build.",
|
|
2428
|
-
...
|
|
2429
|
-
|
|
2525
|
+
...reason === void 0 ? [] : [reason],
|
|
2526
|
+
...agreement ? [PROGRAM_LICENSE_AGREEMENT_HINT] : [],
|
|
2527
|
+
...failed.flatMap(({ certificate, status }) => [certificate.commonName, `Serial ${certificate.serialNumber}${status === void 0 ? "" : ` (HTTP ${status})`}`]),
|
|
2430
2528
|
CERTIFICATES_URL
|
|
2431
2529
|
].join("\n"));
|
|
2432
2530
|
return {
|
|
2433
|
-
revoked,
|
|
2434
|
-
failed
|
|
2531
|
+
revoked: revoked.map(describe),
|
|
2532
|
+
failed: failed.map(({ certificate }) => describe(certificate))
|
|
2435
2533
|
};
|
|
2436
2534
|
};
|
|
2437
2535
|
const TERMINATION_SIGNALS = [
|
|
@@ -2537,7 +2635,7 @@ const readBuiltApp = async (appPath) => {
|
|
|
2537
2635
|
* once the caller has copied it out. A failed build removes it, and every
|
|
2538
2636
|
* other directory the build used, itself.
|
|
2539
2637
|
*/
|
|
2540
|
-
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 = [] }) => {
|
|
2541
2639
|
await assertXcodebuildExist();
|
|
2542
2640
|
if (schemeConfig.simulator && schemeConfig.destination.length) {
|
|
2543
2641
|
_hot_updater_cli_tools.p.log.error("The scheme option 'simulator' is not compatible with the 'destination' option.");
|
|
@@ -2550,7 +2648,8 @@ const buildIos = async ({ schemeConfig, minBundleId = (0, _hot_updater_cli_tools
|
|
|
2550
2648
|
schemeConfig,
|
|
2551
2649
|
minBundleId,
|
|
2552
2650
|
credentials,
|
|
2553
|
-
buildDirectory
|
|
2651
|
+
buildDirectory,
|
|
2652
|
+
logHeader
|
|
2554
2653
|
}),
|
|
2555
2654
|
dispose: () => removeTmpDir(buildDirectory)
|
|
2556
2655
|
};
|
|
@@ -2559,15 +2658,16 @@ const buildIos = async ({ schemeConfig, minBundleId = (0, _hot_updater_cli_tools
|
|
|
2559
2658
|
throw error;
|
|
2560
2659
|
}
|
|
2561
2660
|
};
|
|
2562
|
-
const buildInto = async ({ schemeConfig, minBundleId, credentials, buildDirectory }) => {
|
|
2661
|
+
const buildInto = async ({ schemeConfig, minBundleId, credentials, buildDirectory, logHeader }) => {
|
|
2563
2662
|
const iosProjectRoot = path.default.join((0, _hot_updater_cli_tools.getCwd)(), "ios");
|
|
2564
2663
|
if (schemeConfig.simulator) {
|
|
2565
2664
|
const appDir = path.default.join(buildDirectory, "app");
|
|
2566
2665
|
await fs.default.promises.mkdir(appDir, { recursive: true });
|
|
2567
|
-
const { appPath } = await buildXcodeProject({
|
|
2666
|
+
const { appPath, logFilePath } = await buildXcodeProject({
|
|
2568
2667
|
sourceDir: iosProjectRoot,
|
|
2569
2668
|
outputDirectory: appDir,
|
|
2570
2669
|
logPrefix: `ios-${schemeConfig.hotUpdaterSchemeName}-build`,
|
|
2670
|
+
header: logHeader,
|
|
2571
2671
|
platform: schemeConfig.platform,
|
|
2572
2672
|
xcodeScheme: schemeConfig.scheme,
|
|
2573
2673
|
configuration: schemeConfig.configuration,
|
|
@@ -2580,6 +2680,7 @@ const buildInto = async ({ schemeConfig, minBundleId, credentials, buildDirector
|
|
|
2580
2680
|
return {
|
|
2581
2681
|
buildDirectory,
|
|
2582
2682
|
buildArtifactPath: appPath,
|
|
2683
|
+
logFilePath,
|
|
2583
2684
|
builtApp: await readBuiltApp(appPath)
|
|
2584
2685
|
};
|
|
2585
2686
|
} else {
|
|
@@ -2590,7 +2691,8 @@ const buildInto = async ({ schemeConfig, minBundleId, credentials, buildDirector
|
|
|
2590
2691
|
if (!signing) return archiveAndExport({
|
|
2591
2692
|
schemeConfig,
|
|
2592
2693
|
minBundleId,
|
|
2593
|
-
buildDirectory
|
|
2694
|
+
buildDirectory,
|
|
2695
|
+
logHeader
|
|
2594
2696
|
});
|
|
2595
2697
|
const appStoreConnect = await exportsForAppStoreConnect(schemeConfig);
|
|
2596
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).");
|
|
@@ -2601,6 +2703,7 @@ const buildInto = async ({ schemeConfig, minBundleId, credentials, buildDirector
|
|
|
2601
2703
|
schemeConfig,
|
|
2602
2704
|
minBundleId,
|
|
2603
2705
|
buildDirectory,
|
|
2706
|
+
logHeader,
|
|
2604
2707
|
authenticationArgs: signing.authenticationArgs
|
|
2605
2708
|
})
|
|
2606
2709
|
});
|
|
@@ -2610,12 +2713,12 @@ const buildInto = async ({ schemeConfig, minBundleId, credentials, buildDirector
|
|
|
2610
2713
|
* Archives a device build and exports its `.ipa`, or returns the
|
|
2611
2714
|
* `.xcarchive` of a scheme it cannot export.
|
|
2612
2715
|
*/
|
|
2613
|
-
const archiveAndExport = async ({ schemeConfig, minBundleId, buildDirectory, authenticationArgs }) => {
|
|
2716
|
+
const archiveAndExport = async ({ schemeConfig, minBundleId, buildDirectory, logHeader, authenticationArgs }) => {
|
|
2614
2717
|
const iosProjectRoot = path.default.join((0, _hot_updater_cli_tools.getCwd)(), "ios");
|
|
2615
2718
|
const archiveDir = path.default.join(buildDirectory, "archive");
|
|
2616
2719
|
const exportDir = path.default.join(buildDirectory, "export");
|
|
2617
2720
|
await fs.default.promises.mkdir(archiveDir, { recursive: true });
|
|
2618
|
-
const { archivePath } = await archiveXcodeProject({
|
|
2721
|
+
const { archivePath, logFilePath } = await archiveXcodeProject({
|
|
2619
2722
|
archiveDirectory: archiveDir,
|
|
2620
2723
|
authenticationArgs,
|
|
2621
2724
|
minBundleId,
|
|
@@ -2627,7 +2730,8 @@ const archiveAndExport = async ({ schemeConfig, minBundleId, buildDirectory, aut
|
|
|
2627
2730
|
extraParams: schemeConfig.extraParams,
|
|
2628
2731
|
configuration: schemeConfig.configuration,
|
|
2629
2732
|
installPods: schemeConfig.installPods,
|
|
2630
|
-
logPrefix: `ios-${schemeConfig.hotUpdaterSchemeName}-build
|
|
2733
|
+
logPrefix: `ios-${schemeConfig.hotUpdaterSchemeName}-build`,
|
|
2734
|
+
header: logHeader
|
|
2631
2735
|
});
|
|
2632
2736
|
const appFromArchive = extractAppFromXcarchive(archivePath);
|
|
2633
2737
|
const archivedAppPath = appFromArchive ? path.default.join(archiveDir, path.default.basename(appFromArchive)) : void 0;
|
|
@@ -2635,6 +2739,7 @@ const archiveAndExport = async ({ schemeConfig, minBundleId, buildDirectory, aut
|
|
|
2635
2739
|
if (!schemeConfig.exportOptionsPlist && !authenticationArgs) return {
|
|
2636
2740
|
buildDirectory,
|
|
2637
2741
|
buildArtifactPath: archivePath,
|
|
2742
|
+
logFilePath,
|
|
2638
2743
|
builtApp: archivedAppPath ? await readBuiltApp(archivedAppPath) : void 0
|
|
2639
2744
|
};
|
|
2640
2745
|
await fs.default.promises.mkdir(exportDir, { recursive: true });
|
|
@@ -2645,7 +2750,9 @@ const archiveAndExport = async ({ schemeConfig, minBundleId, buildDirectory, aut
|
|
|
2645
2750
|
exportExtraParams: schemeConfig.exportExtraParams,
|
|
2646
2751
|
exportOptionsPlist,
|
|
2647
2752
|
sourceDir: iosProjectRoot,
|
|
2648
|
-
logPrefix: `ios-${schemeConfig.hotUpdaterSchemeName}-build
|
|
2753
|
+
logPrefix: `ios-${schemeConfig.hotUpdaterSchemeName}-build`,
|
|
2754
|
+
logFilePath: logFilePath ?? void 0,
|
|
2755
|
+
title: schemeConfig.scheme
|
|
2649
2756
|
});
|
|
2650
2757
|
if (schemeConfig.exportOptionsPlist) await exportArchive(schemeConfig.exportOptionsPlist);
|
|
2651
2758
|
else {
|
|
@@ -2661,6 +2768,7 @@ const archiveAndExport = async ({ schemeConfig, minBundleId, buildDirectory, aut
|
|
|
2661
2768
|
return {
|
|
2662
2769
|
buildDirectory,
|
|
2663
2770
|
buildArtifactPath: path.default.join(exportDir, ipaFile),
|
|
2771
|
+
logFilePath,
|
|
2664
2772
|
builtApp: archivedAppPath ? await readBuiltApp(archivedAppPath) : void 0
|
|
2665
2773
|
};
|
|
2666
2774
|
};
|