@expo/build-tools 18.2.0 → 18.5.0
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/android/gradle.js +1 -1
- package/dist/buildErrors/detectError.d.ts +1 -1
- package/dist/buildErrors/detectError.js +8 -12
- package/dist/buildErrors/userErrorHandlers.d.ts +2 -2
- package/dist/buildErrors/userErrorHandlers.js +20 -20
- package/dist/builders/android.js +25 -3
- package/dist/builders/custom.js +1 -0
- package/dist/builders/ios.js +1 -1
- package/dist/common/setup.js +8 -9
- package/dist/context.d.ts +3 -1
- package/dist/context.js +3 -1
- package/dist/customBuildContext.d.ts +5 -1
- package/dist/customBuildContext.js +22 -0
- package/dist/generic.d.ts +1 -3
- package/dist/generic.js +13 -15
- package/dist/ios/credentials/provisioningProfile.d.ts +2 -1
- package/dist/ios/credentials/provisioningProfile.js +10 -11
- package/dist/steps/easFunctions.js +0 -2
- package/dist/steps/functionGroups/maestroTest.js +0 -3
- package/dist/steps/functions/calculateEASUpdateRuntimeVersion.js +2 -2
- package/dist/steps/functions/configureEASUpdateIfInstalled.js +2 -2
- package/dist/steps/functions/downloadArtifact.js +3 -3
- package/dist/steps/functions/downloadBuild.js +2 -2
- package/dist/steps/functions/maestroResultParser.js +134 -80
- package/dist/steps/functions/readIpaInfo.js +9 -9
- package/dist/steps/functions/repack.js +20 -3
- package/dist/steps/functions/reportMaestroTestResults.js +14 -7
- package/dist/steps/functions/restoreBuildCache.d.ts +8 -0
- package/dist/steps/functions/restoreBuildCache.js +85 -0
- package/dist/steps/functions/saveBuildCache.d.ts +8 -0
- package/dist/steps/functions/saveBuildCache.js +57 -0
- package/dist/steps/functions/startAndroidEmulator.js +19 -0
- package/dist/steps/functions/uploadToAsc.d.ts +3 -0
- package/dist/steps/functions/uploadToAsc.js +28 -5
- package/dist/steps/utils/android/gradle.js +1 -1
- package/dist/steps/utils/android/gradleConfig.d.ts +3 -0
- package/dist/steps/utils/android/gradleConfig.js +15 -1
- package/dist/steps/utils/ios/AscApiClient.d.ts +1 -1
- package/dist/steps/utils/ios/AscApiUtils.d.ts +5 -0
- package/dist/steps/utils/ios/AscApiUtils.js +19 -16
- package/dist/steps/utils/ios/credentials/provisioningProfile.d.ts +2 -1
- package/dist/steps/utils/ios/credentials/provisioningProfile.js +10 -11
- package/dist/utils/AndroidEmulatorUtils.d.ts +5 -0
- package/dist/utils/AndroidEmulatorUtils.js +17 -0
- package/dist/utils/appConfig.d.ts +4 -2
- package/dist/utils/appConfig.js +36 -5
- package/dist/utils/expoCli.d.ts +4 -0
- package/dist/utils/expoCli.js +37 -0
- package/dist/utils/expoUpdates.d.ts +1 -1
- package/dist/utils/expoUpdates.js +4 -4
- package/dist/utils/gradleCacheKey.d.ts +2 -0
- package/dist/utils/gradleCacheKey.js +56 -0
- package/dist/utils/packageManager.d.ts +10 -0
- package/dist/utils/packageManager.js +21 -0
- package/dist/utils/stepMetrics.d.ts +2 -2
- package/dist/utils/stepMetrics.js +4 -10
- package/package.json +14 -13
- package/dist/android/gradleConfig.d.ts +0 -3
- package/dist/android/gradleConfig.js +0 -47
- package/dist/steps/functions/internalMaestroTest.d.ts +0 -9
- package/dist/steps/functions/internalMaestroTest.js +0 -538
- package/dist/templates/EasBuildGradle.d.ts +0 -1
- package/dist/templates/EasBuildGradle.js +0 -57
- package/dist/utils/findMaestroPathsFlowsToExecuteAsync.d.ts +0 -8
- package/dist/utils/findMaestroPathsFlowsToExecuteAsync.js +0 -184
|
@@ -6,14 +6,19 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.createRestoreBuildCacheFunction = createRestoreBuildCacheFunction;
|
|
7
7
|
exports.createCacheStatsBuildFunction = createCacheStatsBuildFunction;
|
|
8
8
|
exports.restoreCcacheAsync = restoreCcacheAsync;
|
|
9
|
+
exports.restoreGradleCacheAsync = restoreGradleCacheAsync;
|
|
9
10
|
exports.cacheStatsAsync = cacheStatsAsync;
|
|
10
11
|
const eas_build_job_1 = require("@expo/eas-build-job");
|
|
11
12
|
const results_1 = require("@expo/results");
|
|
12
13
|
const steps_1 = require("@expo/steps");
|
|
14
|
+
const fs_1 = __importDefault(require("fs"));
|
|
13
15
|
const nullthrows_1 = __importDefault(require("nullthrows"));
|
|
16
|
+
const os_1 = __importDefault(require("os"));
|
|
17
|
+
const path_1 = __importDefault(require("path"));
|
|
14
18
|
const ccacheStats_1 = require("./ccacheStats");
|
|
15
19
|
const restoreCache_1 = require("./restoreCache");
|
|
16
20
|
const cacheKey_1 = require("../../utils/cacheKey");
|
|
21
|
+
const gradleCacheKey_1 = require("../../utils/gradleCacheKey");
|
|
17
22
|
const turtleFetch_1 = require("../../utils/turtleFetch");
|
|
18
23
|
function createRestoreBuildCacheFunction() {
|
|
19
24
|
return new steps_1.BuildFunction({
|
|
@@ -43,6 +48,14 @@ function createRestoreBuildCacheFunction() {
|
|
|
43
48
|
env,
|
|
44
49
|
secrets: stepCtx.global.staticContext.job.secrets,
|
|
45
50
|
});
|
|
51
|
+
if (platform === eas_build_job_1.Platform.ANDROID) {
|
|
52
|
+
await restoreGradleCacheAsync({
|
|
53
|
+
logger,
|
|
54
|
+
workingDirectory,
|
|
55
|
+
env,
|
|
56
|
+
secrets: stepCtx.global.staticContext.job.secrets,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
46
59
|
},
|
|
47
60
|
});
|
|
48
61
|
}
|
|
@@ -140,6 +153,78 @@ async function restoreCcacheAsync({ logger, workingDirectory, platform, env, sec
|
|
|
140
153
|
}
|
|
141
154
|
}
|
|
142
155
|
}
|
|
156
|
+
async function restoreGradleCacheAsync({ logger, workingDirectory, env, secrets, }) {
|
|
157
|
+
if (env.EXPERIMENTAL_EAS_GRADLE_CACHE !== '1') {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
try {
|
|
161
|
+
const gradlePropertiesPath = path_1.default.join(workingDirectory, 'android', 'gradle.properties');
|
|
162
|
+
const gradlePropertiesContent = await fs_1.default.promises.readFile(gradlePropertiesPath, 'utf-8');
|
|
163
|
+
await fs_1.default.promises.writeFile(gradlePropertiesPath, `${gradlePropertiesContent}\n\norg.gradle.caching=true\n`);
|
|
164
|
+
// Configure cache cleanup via init script (works with both Gradle 8 and 9,
|
|
165
|
+
// org.gradle.cache.cleanup property was removed in Gradle 9)
|
|
166
|
+
const initScriptDir = path_1.default.join(os_1.default.homedir(), '.gradle', 'init.d');
|
|
167
|
+
await fs_1.default.promises.mkdir(initScriptDir, { recursive: true });
|
|
168
|
+
await fs_1.default.promises.writeFile(path_1.default.join(initScriptDir, 'eas-cache-cleanup.gradle'), [
|
|
169
|
+
'def cacheDir = new File(System.getProperty("user.home"), ".gradle/caches/build-cache-1")',
|
|
170
|
+
'def countBefore = cacheDir.exists() ? cacheDir.listFiles()?.length ?: 0 : 0',
|
|
171
|
+
'println "[EAS] Gradle build cache entries before cleanup: ${countBefore}"',
|
|
172
|
+
'',
|
|
173
|
+
'beforeSettings { settings ->',
|
|
174
|
+
' try {',
|
|
175
|
+
' settings.caches {',
|
|
176
|
+
' cleanup = Cleanup.ALWAYS',
|
|
177
|
+
' buildCache {',
|
|
178
|
+
' setRemoveUnusedEntriesAfterDays(3)',
|
|
179
|
+
' }',
|
|
180
|
+
' }',
|
|
181
|
+
' println "[EAS] Configured Gradle cache cleanup via init script"',
|
|
182
|
+
' } catch (Exception e) {',
|
|
183
|
+
' println "[EAS] Failed to configure cache cleanup: ${e.message}"',
|
|
184
|
+
' }',
|
|
185
|
+
'}',
|
|
186
|
+
'',
|
|
187
|
+
'gradle.buildFinished {',
|
|
188
|
+
' def countAfter = cacheDir.exists() ? cacheDir.listFiles()?.length ?: 0 : 0',
|
|
189
|
+
' println "[EAS] Gradle build cache entries after build: ${countAfter} (was ${countBefore})"',
|
|
190
|
+
'}',
|
|
191
|
+
'',
|
|
192
|
+
].join('\n'));
|
|
193
|
+
const robotAccessToken = (0, nullthrows_1.default)(secrets?.robotAccessToken, 'Robot access token is required for cache operations');
|
|
194
|
+
const expoApiServerURL = (0, nullthrows_1.default)(env.__API_SERVER_URL, '__API_SERVER_URL is not set');
|
|
195
|
+
const jobId = (0, nullthrows_1.default)(env.EAS_BUILD_ID, 'EAS_BUILD_ID is not set');
|
|
196
|
+
const cacheKey = await (0, gradleCacheKey_1.generateGradleCacheKeyAsync)(workingDirectory);
|
|
197
|
+
logger.info(`Restoring Gradle cache key: ${cacheKey}`);
|
|
198
|
+
const gradleCachesPath = path_1.default.join(os_1.default.homedir(), '.gradle', 'caches');
|
|
199
|
+
const buildCachePath = path_1.default.join(gradleCachesPath, 'build-cache-1');
|
|
200
|
+
const { archivePath, matchedKey } = await (0, restoreCache_1.downloadCacheAsync)({
|
|
201
|
+
logger,
|
|
202
|
+
jobId,
|
|
203
|
+
expoApiServerURL,
|
|
204
|
+
robotAccessToken,
|
|
205
|
+
paths: [buildCachePath],
|
|
206
|
+
key: cacheKey,
|
|
207
|
+
keyPrefixes: [gradleCacheKey_1.GRADLE_CACHE_KEY_PREFIX],
|
|
208
|
+
platform: eas_build_job_1.Platform.ANDROID,
|
|
209
|
+
});
|
|
210
|
+
await fs_1.default.promises.mkdir(gradleCachesPath, { recursive: true });
|
|
211
|
+
await (0, restoreCache_1.decompressCacheAsync)({
|
|
212
|
+
archivePath,
|
|
213
|
+
workingDirectory: gradleCachesPath,
|
|
214
|
+
verbose: env.EXPO_DEBUG === '1',
|
|
215
|
+
logger,
|
|
216
|
+
});
|
|
217
|
+
logger.info(`Gradle cache restored to ${gradleCachesPath} ${matchedKey === cacheKey ? '(direct hit)' : '(prefix match)'}`);
|
|
218
|
+
}
|
|
219
|
+
catch (err) {
|
|
220
|
+
if (err instanceof turtleFetch_1.TurtleFetchError && err.response?.status === 404) {
|
|
221
|
+
logger.info('No Gradle cache found for this key');
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
logger.warn('Failed to restore Gradle cache: ', err);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
143
228
|
async function cacheStatsAsync({ logger, env, secrets, }) {
|
|
144
229
|
const enabled = env.EAS_RESTORE_CACHE === '1' || (env.EAS_USE_CACHE === '1' && env.EAS_RESTORE_CACHE !== '0');
|
|
145
230
|
if (!enabled) {
|
|
@@ -12,3 +12,11 @@ export declare function saveCcacheAsync({ logger, workingDirectory, platform, ev
|
|
|
12
12
|
robotAccessToken?: string;
|
|
13
13
|
};
|
|
14
14
|
}): Promise<void>;
|
|
15
|
+
export declare function saveGradleCacheAsync({ logger, workingDirectory, env, secrets, }: {
|
|
16
|
+
logger: bunyan;
|
|
17
|
+
workingDirectory: string;
|
|
18
|
+
env: Record<string, string | undefined>;
|
|
19
|
+
secrets?: {
|
|
20
|
+
robotAccessToken?: string;
|
|
21
|
+
};
|
|
22
|
+
}): Promise<void>;
|
|
@@ -5,13 +5,18 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.createSaveBuildCacheFunction = createSaveBuildCacheFunction;
|
|
7
7
|
exports.saveCcacheAsync = saveCcacheAsync;
|
|
8
|
+
exports.saveGradleCacheAsync = saveGradleCacheAsync;
|
|
8
9
|
const eas_build_job_1 = require("@expo/eas-build-job");
|
|
9
10
|
const results_1 = require("@expo/results");
|
|
10
11
|
const steps_1 = require("@expo/steps");
|
|
11
12
|
const fs_1 = __importDefault(require("fs"));
|
|
12
13
|
const nullthrows_1 = __importDefault(require("nullthrows"));
|
|
14
|
+
const os_1 = __importDefault(require("os"));
|
|
15
|
+
const path_1 = __importDefault(require("path"));
|
|
13
16
|
const saveCache_1 = require("./saveCache");
|
|
17
|
+
const artifacts_1 = require("../../utils/artifacts");
|
|
14
18
|
const cacheKey_1 = require("../../utils/cacheKey");
|
|
19
|
+
const gradleCacheKey_1 = require("../../utils/gradleCacheKey");
|
|
15
20
|
function createSaveBuildCacheFunction(evictUsedBefore) {
|
|
16
21
|
return new steps_1.BuildFunction({
|
|
17
22
|
namespace: 'eas',
|
|
@@ -41,6 +46,14 @@ function createSaveBuildCacheFunction(evictUsedBefore) {
|
|
|
41
46
|
env,
|
|
42
47
|
secrets: stepCtx.global.staticContext.job.secrets,
|
|
43
48
|
});
|
|
49
|
+
if (platform === eas_build_job_1.Platform.ANDROID) {
|
|
50
|
+
await saveGradleCacheAsync({
|
|
51
|
+
logger,
|
|
52
|
+
workingDirectory,
|
|
53
|
+
env,
|
|
54
|
+
secrets: stepCtx.global.staticContext.job.secrets,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
44
57
|
},
|
|
45
58
|
});
|
|
46
59
|
}
|
|
@@ -98,3 +111,47 @@ async function saveCcacheAsync({ logger, workingDirectory, platform, evictUsedBe
|
|
|
98
111
|
logger.error({ err }, 'Failed to save cache');
|
|
99
112
|
}
|
|
100
113
|
}
|
|
114
|
+
async function saveGradleCacheAsync({ logger, workingDirectory, env, secrets, }) {
|
|
115
|
+
if (env.EXPERIMENTAL_EAS_GRADLE_CACHE !== '1') {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const gradleCachesPath = path_1.default.join(os_1.default.homedir(), '.gradle', 'caches');
|
|
119
|
+
const buildCachePath = path_1.default.join(gradleCachesPath, 'build-cache-1');
|
|
120
|
+
try {
|
|
121
|
+
await fs_1.default.promises.access(buildCachePath);
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
logger.warn('No Gradle build cache found, skipping save');
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
const cacheKey = await (0, gradleCacheKey_1.generateGradleCacheKeyAsync)(workingDirectory);
|
|
129
|
+
logger.info(`Saving Gradle cache key: ${cacheKey}`);
|
|
130
|
+
const jobId = (0, nullthrows_1.default)(env.EAS_BUILD_ID, 'EAS_BUILD_ID is not set');
|
|
131
|
+
const robotAccessToken = (0, nullthrows_1.default)(secrets?.robotAccessToken, 'Robot access token is required for cache operations');
|
|
132
|
+
const expoApiServerURL = (0, nullthrows_1.default)(env.__API_SERVER_URL, '__API_SERVER_URL is not set');
|
|
133
|
+
logger.info('Compressing Gradle build cache...');
|
|
134
|
+
const { archivePath } = await (0, saveCache_1.compressCacheAsync)({
|
|
135
|
+
paths: [buildCachePath],
|
|
136
|
+
workingDirectory: gradleCachesPath,
|
|
137
|
+
verbose: env.EXPO_DEBUG === '1',
|
|
138
|
+
logger,
|
|
139
|
+
});
|
|
140
|
+
const { size } = await fs_1.default.promises.stat(archivePath);
|
|
141
|
+
logger.info(`Gradle cache archive size: ${(0, artifacts_1.formatBytes)(size)}`);
|
|
142
|
+
await (0, saveCache_1.uploadCacheAsync)({
|
|
143
|
+
logger,
|
|
144
|
+
jobId,
|
|
145
|
+
expoApiServerURL,
|
|
146
|
+
robotAccessToken,
|
|
147
|
+
archivePath,
|
|
148
|
+
key: cacheKey,
|
|
149
|
+
paths: [buildCachePath],
|
|
150
|
+
size,
|
|
151
|
+
platform: eas_build_job_1.Platform.ANDROID,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
catch (err) {
|
|
155
|
+
logger.error({ err }, 'Failed to save Gradle cache');
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -57,6 +57,11 @@ function createStartAndroidEmulatorBuildFunction() {
|
|
|
57
57
|
const systemImagePackage = `${inputs.system_image_package.value}`;
|
|
58
58
|
// We can cast because allowedValueTypeName validated this is a string.
|
|
59
59
|
const deviceIdentifier = inputs.device_identifier.value;
|
|
60
|
+
const shouldAdjustAnimationScale = env.ANDROID_EMULATOR_ADJUST_ANIMATION_SCALE !== 'false' &&
|
|
61
|
+
env.ANDROID_EMULATOR_ADJUST_ANIMATION_SCALE !== '0';
|
|
62
|
+
if (!shouldAdjustAnimationScale) {
|
|
63
|
+
logger.info('Skipping Android emulator animation scale adjustments because $ANDROID_EMULATOR_ADJUST_ANIMATION_SCALE is disabled.');
|
|
64
|
+
}
|
|
60
65
|
logger.info('Making sure system image is installed');
|
|
61
66
|
await (0, retry_1.retryAsync)(async () => {
|
|
62
67
|
await (0, turtle_spawn_1.default)('sdkmanager', [systemImagePackage], {
|
|
@@ -99,6 +104,13 @@ function createStartAndroidEmulatorBuildFunction() {
|
|
|
99
104
|
timeoutMs,
|
|
100
105
|
logger,
|
|
101
106
|
});
|
|
107
|
+
if (shouldAdjustAnimationScale) {
|
|
108
|
+
await AndroidEmulatorUtils_1.AndroidEmulatorUtils.disableWindowAndTransitionAnimationsAsync({
|
|
109
|
+
env,
|
|
110
|
+
logger,
|
|
111
|
+
serialId: attemptSerialId,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
102
114
|
logger.info(`${deviceName} is ready.`);
|
|
103
115
|
serialId = attemptSerialId;
|
|
104
116
|
emulatorPromise = startResult.emulatorPromise;
|
|
@@ -174,6 +186,13 @@ function createStartAndroidEmulatorBuildFunction() {
|
|
|
174
186
|
timeoutMs,
|
|
175
187
|
logger,
|
|
176
188
|
});
|
|
189
|
+
if (shouldAdjustAnimationScale) {
|
|
190
|
+
await AndroidEmulatorUtils_1.AndroidEmulatorUtils.disableWindowAndTransitionAnimationsAsync({
|
|
191
|
+
env,
|
|
192
|
+
logger,
|
|
193
|
+
serialId: cloneSerialId,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
177
196
|
logger.info(`${cloneIdentifier} is ready.`);
|
|
178
197
|
}
|
|
179
198
|
catch (err) {
|
|
@@ -3,6 +3,9 @@ export declare function createUploadToAscBuildFunction(): BuildFunction;
|
|
|
3
3
|
export declare function isClosedVersionTrainError(messages: {
|
|
4
4
|
code: string;
|
|
5
5
|
}[]): boolean;
|
|
6
|
+
export declare function isSdkVersionIssueError(messages: {
|
|
7
|
+
code: string;
|
|
8
|
+
}[]): boolean;
|
|
6
9
|
export declare function isInvalidBundleIdentifierError(messages: {
|
|
7
10
|
code: string;
|
|
8
11
|
}[]): boolean;
|
|
@@ -38,10 +38,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
exports.createUploadToAscBuildFunction = createUploadToAscBuildFunction;
|
|
40
40
|
exports.isClosedVersionTrainError = isClosedVersionTrainError;
|
|
41
|
+
exports.isSdkVersionIssueError = isSdkVersionIssueError;
|
|
41
42
|
exports.isInvalidBundleIdentifierError = isInvalidBundleIdentifierError;
|
|
42
43
|
exports.isMissingPurposeStringError = isMissingPurposeStringError;
|
|
43
44
|
exports.parseMissingUsageDescriptionKeys = parseMissingUsageDescriptionKeys;
|
|
44
|
-
const
|
|
45
|
+
const eas_build_job_1 = require("@expo/eas-build-job");
|
|
45
46
|
const results_1 = require("@expo/results");
|
|
46
47
|
const steps_1 = require("@expo/steps");
|
|
47
48
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
@@ -247,21 +248,40 @@ function createUploadToAscBuildFunction() {
|
|
|
247
248
|
stepsCtx.logger.error(`Errors:\n${itemizeMessages(errors)}\n`);
|
|
248
249
|
}
|
|
249
250
|
if (state.state === 'FAILED') {
|
|
251
|
+
if (isSdkVersionIssueError(errors)) {
|
|
252
|
+
throw new eas_build_job_1.UserError('EAS_UPLOAD_TO_ASC_SDK_VERSION_ISSUE', 'Build upload was rejected by App Store Connect because the IPA was built with an iOS SDK that is too old for current App Store Connect requirements. ' +
|
|
253
|
+
'In an Expo project, this usually means the build used an outdated EAS iOS image or the project still depends on an Expo SDK / native setup that does not support the required Xcode toolchain. ' +
|
|
254
|
+
'Upgrade to a supported Expo SDK if needed and/or select a newer iOS image in `eas.json`, rebuild, and submit again.', {
|
|
255
|
+
docsUrl: 'https://docs.expo.dev/workflow/upgrading-expo-sdk-walkthrough/',
|
|
256
|
+
});
|
|
257
|
+
}
|
|
250
258
|
if (isInvalidBundleIdentifierError(errors)) {
|
|
251
259
|
const ipaInfoResult = await (0, results_1.asyncResult)((0, readIpaInfo_1.readIpaInfoAsync)(ipaPath));
|
|
252
260
|
const ipaBundleIdentifier = ipaInfoResult.ok
|
|
253
261
|
? ipaInfoResult.value.bundleIdentifier
|
|
254
262
|
: null;
|
|
255
|
-
|
|
263
|
+
let visibleAppsSummary = null;
|
|
264
|
+
try {
|
|
265
|
+
const apps = await AscApiUtils_1.AscApiUtils.getAppsAsync({ client, limit: 5 });
|
|
266
|
+
visibleAppsSummary = AscApiUtils_1.AscApiUtils.formatAppsList(apps);
|
|
267
|
+
}
|
|
268
|
+
catch {
|
|
269
|
+
// Ok to fail, this is just trying to be helpful.
|
|
270
|
+
}
|
|
271
|
+
throw new eas_build_job_1.UserError('EAS_UPLOAD_TO_ASC_INVALID_BUNDLE_ID', `Build upload was rejected by App Store Connect because the app bundle identifier in the IPA does not match the selected App Store Connect app.\n\n` +
|
|
256
272
|
`IPA bundle identifier: ${ipaBundleIdentifier ?? '(unavailable)'}\n` +
|
|
257
273
|
`App Store Connect app bundle identifier: ${ascAppBundleIdentifier}\n\n` +
|
|
258
274
|
'Bundle identifier cannot be changed for an existing App Store Connect app. ' +
|
|
259
275
|
'If you selected the wrong app, change the Apple app identifier in the submit profile. ' +
|
|
260
|
-
'If you selected the right app, you may want to select a different build to upload (or rebuild with a different profile).'
|
|
276
|
+
'If you selected the right app, you may want to select a different build to upload (or rebuild with a different profile).' +
|
|
277
|
+
'\n\nOther App Store Connect apps visible to this API key:\n' +
|
|
278
|
+
(visibleAppsSummary ?? ' (unable to retrieve)'), {
|
|
279
|
+
docsUrl: 'https://expo.fyi/asc-app-id',
|
|
280
|
+
});
|
|
261
281
|
}
|
|
262
282
|
if (isMissingPurposeStringError(errors)) {
|
|
263
283
|
const missingUsageDescriptionKeys = parseMissingUsageDescriptionKeys(errors);
|
|
264
|
-
throw new
|
|
284
|
+
throw new eas_build_job_1.UserError('EAS_UPLOAD_TO_ASC_MISSING_PURPOSE_STRING', `Build upload was rejected by App Store Connect because Info.plist is missing one or more privacy purpose strings.\n\n` +
|
|
265
285
|
`${missingUsageDescriptionKeys.length > 0
|
|
266
286
|
? `Missing keys reported by App Store Connect:\n- ${missingUsageDescriptionKeys.join('\n- ')}\n\n`
|
|
267
287
|
: ''}` +
|
|
@@ -272,7 +292,7 @@ function createUploadToAscBuildFunction() {
|
|
|
272
292
|
});
|
|
273
293
|
}
|
|
274
294
|
if (isClosedVersionTrainError(errors)) {
|
|
275
|
-
throw new
|
|
295
|
+
throw new eas_build_job_1.UserError('EAS_UPLOAD_TO_ASC_CLOSED_VERSION_TRAIN', `Build upload was rejected by App Store Connect because the ${bundleShortVersion} app version is not accepted for new build submissions. ` +
|
|
276
296
|
'This usually means the version train is closed or lower than a previously approved version. ' +
|
|
277
297
|
'Bump the iOS app version (CFBundleShortVersionString, e.g. expo.version) to a higher version, then rebuild and submit again.');
|
|
278
298
|
}
|
|
@@ -293,6 +313,9 @@ function isClosedVersionTrainError(messages) {
|
|
|
293
313
|
return (messages.length > 0 &&
|
|
294
314
|
messages.every(message => ['90062', '90186', '90478'].includes(message.code)));
|
|
295
315
|
}
|
|
316
|
+
function isSdkVersionIssueError(messages) {
|
|
317
|
+
return messages.length > 0 && messages.every(message => message.code === '90725');
|
|
318
|
+
}
|
|
296
319
|
function isInvalidBundleIdentifierError(messages) {
|
|
297
320
|
return (messages.length > 0 && messages.every(message => ['90054', '90055'].includes(message.code)));
|
|
298
321
|
}
|
|
@@ -25,7 +25,7 @@ async function runGradleCommand({ logger, gradleCommand, androidDir, env, extraE
|
|
|
25
25
|
return line;
|
|
26
26
|
}
|
|
27
27
|
},
|
|
28
|
-
env: { ...env, ...extraEnv },
|
|
28
|
+
env: { ...env, ...extraEnv, LC_ALL: 'C.UTF-8' },
|
|
29
29
|
});
|
|
30
30
|
if (env.EAS_BUILD_RUNNER === 'eas-build' && process.platform === 'linux') {
|
|
31
31
|
adjustOOMScore(spawnPromise, logger);
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { Android } from '@expo/eas-build-job';
|
|
1
2
|
import { bunyan } from '@expo/logger';
|
|
3
|
+
import { BuildContext } from '../../../context';
|
|
2
4
|
export declare function injectCredentialsGradleConfig(logger: bunyan, workingDir: string): Promise<void>;
|
|
3
5
|
export declare function injectConfigureVersionGradleConfig(logger: bunyan, workingDir: string, { versionCode, versionName }: {
|
|
4
6
|
versionCode?: string;
|
|
5
7
|
versionName?: string;
|
|
6
8
|
}): Promise<void>;
|
|
9
|
+
export declare function warnIfLegacyEasBuildGradleExists(ctx: BuildContext<Android.Job>): Promise<void>;
|
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.injectCredentialsGradleConfig = injectCredentialsGradleConfig;
|
|
7
7
|
exports.injectConfigureVersionGradleConfig = injectConfigureVersionGradleConfig;
|
|
8
|
+
exports.warnIfLegacyEasBuildGradleExists = warnIfLegacyEasBuildGradleExists;
|
|
8
9
|
const config_plugins_1 = require("@expo/config-plugins");
|
|
9
10
|
const template_file_1 = require("@expo/template-file");
|
|
10
11
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
@@ -41,6 +42,19 @@ async function deleteEasBuildConfigureVersionGradle(workingDir) {
|
|
|
41
42
|
const targetPath = getEasBuildConfigureVersionGradlePath(workingDir);
|
|
42
43
|
await fs_extra_1.default.remove(targetPath);
|
|
43
44
|
}
|
|
45
|
+
let legacyEasBuildGradleWarningEmitted = false;
|
|
46
|
+
async function warnIfLegacyEasBuildGradleExists(ctx) {
|
|
47
|
+
const legacyGradlePath = getLegacyEasBuildGradlePath(ctx.getReactNativeProjectDirectory());
|
|
48
|
+
if ((await fs_extra_1.default.pathExists(legacyGradlePath)) &&
|
|
49
|
+
(process.env.NODE_ENV === 'test' || !legacyEasBuildGradleWarningEmitted)) {
|
|
50
|
+
legacyEasBuildGradleWarningEmitted = true;
|
|
51
|
+
ctx.logger.warn('eas-build.gradle script is deprecated, please remove it from your project.');
|
|
52
|
+
ctx.markBuildPhaseHasWarnings();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function getLegacyEasBuildGradlePath(projectRoot) {
|
|
56
|
+
return path_1.default.join(projectRoot, 'android/app/eas-build.gradle');
|
|
57
|
+
}
|
|
44
58
|
function getEasBuildInjectCredentialsGradlePath(workingDir) {
|
|
45
59
|
return path_1.default.join(workingDir, 'android/app/eas-build-inject-android-credentials.gradle');
|
|
46
60
|
}
|
|
@@ -83,6 +97,6 @@ function hasLine(haystack, needle) {
|
|
|
83
97
|
return (haystack
|
|
84
98
|
.replace(/\r\n/g, '\n')
|
|
85
99
|
.split('\n')
|
|
86
|
-
// Check for both single and double quotes
|
|
100
|
+
// Check for both single and double quotes.
|
|
87
101
|
.some(line => line === needle || line === needle.replace(/"/g, "'")));
|
|
88
102
|
}
|
|
@@ -10,4 +10,9 @@ export declare namespace AscApiUtils {
|
|
|
10
10
|
bundleShortVersion: string;
|
|
11
11
|
bundleVersion: string;
|
|
12
12
|
}): Promise<AscApiClientPostApi['/v1/buildUploads']['response']>;
|
|
13
|
+
function getAppsAsync({ client, limit, }: {
|
|
14
|
+
client: Pick<AscApiClient, 'getAsync'>;
|
|
15
|
+
limit?: number;
|
|
16
|
+
}): Promise<AscApiClientGetApi['/v1/apps']['response']['data']>;
|
|
17
|
+
function formatAppsList(apps: AscApiClientGetApi['/v1/apps']['response']['data']): string;
|
|
13
18
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.AscApiUtils = void 0;
|
|
4
|
-
const
|
|
4
|
+
const eas_build_job_1 = require("@expo/eas-build-job");
|
|
5
5
|
const AscApiClient_1 = require("./AscApiClient");
|
|
6
6
|
var AscApiUtils;
|
|
7
7
|
(function (AscApiUtils) {
|
|
@@ -19,13 +19,14 @@ var AscApiUtils;
|
|
|
19
19
|
}
|
|
20
20
|
let visibleAppsSummary = null;
|
|
21
21
|
try {
|
|
22
|
-
|
|
22
|
+
const apps = await AscApiUtils.getAppsAsync({ client, limit: 10 });
|
|
23
|
+
visibleAppsSummary = AscApiUtils.formatAppsList(apps);
|
|
23
24
|
}
|
|
24
25
|
catch {
|
|
25
26
|
// Don't hide the original NOT_FOUND error with a secondary lookup failure.
|
|
26
27
|
throw error;
|
|
27
28
|
}
|
|
28
|
-
throw new
|
|
29
|
+
throw new eas_build_job_1.UserError('EAS_UPLOAD_TO_ASC_APP_NOT_FOUND', `App Store Connect app for application identifier ${appleAppIdentifier} was not found. ` +
|
|
29
30
|
'Verify the configured application identifier and that the App Store Connect API key has access to the application in the correct App Store Connect account.' +
|
|
30
31
|
(visibleAppsSummary
|
|
31
32
|
? `\n\nExample applications visible to this API key:\n${visibleAppsSummary}`
|
|
@@ -64,7 +65,7 @@ var AscApiUtils;
|
|
|
64
65
|
const isDuplicateVersionError = errors.length > 0 &&
|
|
65
66
|
errors.every(item => item.code === 'ENTITY_ERROR.ATTRIBUTE.INVALID.DUPLICATE');
|
|
66
67
|
if (isDuplicateVersionError) {
|
|
67
|
-
throw new
|
|
68
|
+
throw new eas_build_job_1.UserError('EAS_UPLOAD_TO_ASC_VERSION_DUPLICATE', `Increment Build Number: Build number ${bundleVersion} for app version ${bundleShortVersion} has already been used. ` +
|
|
68
69
|
'App Store Connect requires unique build numbers within each app version (version train). ' +
|
|
69
70
|
'Increment it by setting ios.buildNumber in app.json, or set "autoIncrement": true in eas.json (recommended). Then rebuild and resubmit.', {
|
|
70
71
|
docsUrl: 'https://docs.expo.dev/build-reference/app-versions/',
|
|
@@ -75,16 +76,18 @@ var AscApiUtils;
|
|
|
75
76
|
}
|
|
76
77
|
}
|
|
77
78
|
AscApiUtils.createBuildUploadAsync = createBuildUploadAsync;
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
79
|
+
async function getAppsAsync({ client, limit, }) {
|
|
80
|
+
const appsResponse = await client.getAsync('/v1/apps', {
|
|
81
|
+
'fields[apps]': ['bundleId', 'name'],
|
|
82
|
+
limit: limit ?? 10,
|
|
83
|
+
});
|
|
84
|
+
return appsResponse.data;
|
|
85
|
+
}
|
|
86
|
+
AscApiUtils.getAppsAsync = getAppsAsync;
|
|
87
|
+
function formatAppsList(apps) {
|
|
88
|
+
return (apps
|
|
89
|
+
.map(app => `- ${app.attributes.name} (${app.attributes.bundleId}) (ID: ${app.id})`)
|
|
90
|
+
.join('\n') || ' (none)');
|
|
86
91
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
.join('\n');
|
|
90
|
-
}
|
|
92
|
+
AscApiUtils.formatAppsList = formatAppsList;
|
|
93
|
+
})(AscApiUtils || (exports.AscApiUtils = AscApiUtils = {}));
|
|
@@ -23,11 +23,12 @@ export default class ProvisioningProfile {
|
|
|
23
23
|
get data(): ProvisioningProfileData;
|
|
24
24
|
private readonly profilePath;
|
|
25
25
|
private profileData?;
|
|
26
|
+
private developerCertificates;
|
|
26
27
|
constructor(profile: Buffer, keychainPath: string, target: string, certificateCommonName: string);
|
|
27
28
|
init(logger: bunyan): Promise<void>;
|
|
28
29
|
destroy(logger: bunyan): Promise<void>;
|
|
29
30
|
verifyCertificate(fingerprint: string): void;
|
|
30
31
|
private load;
|
|
31
32
|
private resolveDistributionType;
|
|
32
|
-
private
|
|
33
|
+
private getAllDerCertFingerprints;
|
|
33
34
|
}
|
|
@@ -34,6 +34,7 @@ class ProvisioningProfile {
|
|
|
34
34
|
}
|
|
35
35
|
profilePath;
|
|
36
36
|
profileData;
|
|
37
|
+
developerCertificates = [];
|
|
37
38
|
constructor(profile, keychainPath, target, certificateCommonName) {
|
|
38
39
|
this.profile = profile;
|
|
39
40
|
this.keychainPath = keychainPath;
|
|
@@ -58,10 +59,11 @@ class ProvisioningProfile {
|
|
|
58
59
|
await fs_extra_1.default.remove(this.profilePath);
|
|
59
60
|
}
|
|
60
61
|
verifyCertificate(fingerprint) {
|
|
61
|
-
const
|
|
62
|
-
if (
|
|
63
|
-
throw new eas_build_job_1.errors.CredentialsDistCertMismatchError(`Provisioning profile and distribution certificate don't match
|
|
64
|
-
Profile's certificate
|
|
62
|
+
const devCertFingerprints = this.getAllDerCertFingerprints();
|
|
63
|
+
if (!devCertFingerprints.includes(fingerprint)) {
|
|
64
|
+
throw new eas_build_job_1.errors.CredentialsDistCertMismatchError(`Provisioning profile and distribution certificate don't match.\n` +
|
|
65
|
+
`Profile's certificate fingerprints = [${devCertFingerprints.join(', ')}], ` +
|
|
66
|
+
`distribution certificate fingerprint = ${fingerprint}`);
|
|
65
67
|
}
|
|
66
68
|
}
|
|
67
69
|
async load() {
|
|
@@ -85,6 +87,7 @@ Profile's certificate fingerprint = ${devCertFingerprint}, distribution certific
|
|
|
85
87
|
}
|
|
86
88
|
const applicationIdentifier = plistData.Entitlements['application-identifier'];
|
|
87
89
|
const bundleIdentifier = applicationIdentifier.replace(/^.+?\./, '');
|
|
90
|
+
this.developerCertificates = plistData.DeveloperCertificates.map((cert) => Buffer.from(cert, 'base64'));
|
|
88
91
|
this.profileData = {
|
|
89
92
|
path: this.profilePath,
|
|
90
93
|
target: this.target,
|
|
@@ -92,7 +95,7 @@ Profile's certificate fingerprint = ${devCertFingerprint}, distribution certific
|
|
|
92
95
|
teamId: plistData.TeamIdentifier[0],
|
|
93
96
|
uuid: plistData.UUID,
|
|
94
97
|
name: plistData.Name,
|
|
95
|
-
developerCertificate:
|
|
98
|
+
developerCertificate: this.developerCertificates[0],
|
|
96
99
|
certificateCommonName: this.certificateCommonName,
|
|
97
100
|
distributionType: this.resolveDistributionType(plistData),
|
|
98
101
|
};
|
|
@@ -108,12 +111,8 @@ Profile's certificate fingerprint = ${devCertFingerprint}, distribution certific
|
|
|
108
111
|
return DistributionType.APP_STORE;
|
|
109
112
|
}
|
|
110
113
|
}
|
|
111
|
-
|
|
112
|
-
return crypto_1.default
|
|
113
|
-
.createHash('sha1')
|
|
114
|
-
.update(new Uint8Array(this.data.developerCertificate))
|
|
115
|
-
.digest('hex')
|
|
116
|
-
.toUpperCase();
|
|
114
|
+
getAllDerCertFingerprints() {
|
|
115
|
+
return this.developerCertificates.map(cert => crypto_1.default.createHash('sha1').update(new Uint8Array(cert)).digest('hex').toUpperCase());
|
|
117
116
|
}
|
|
118
117
|
}
|
|
119
118
|
exports.default = ProvisioningProfile;
|
|
@@ -47,6 +47,11 @@ export declare namespace AndroidEmulatorUtils {
|
|
|
47
47
|
timeoutMs?: number;
|
|
48
48
|
logger?: bunyan;
|
|
49
49
|
}): Promise<void>;
|
|
50
|
+
function disableWindowAndTransitionAnimationsAsync({ serialId, env, logger, }: {
|
|
51
|
+
serialId: AndroidDeviceSerialId;
|
|
52
|
+
env: NodeJS.ProcessEnv;
|
|
53
|
+
logger: bunyan;
|
|
54
|
+
}): Promise<void>;
|
|
50
55
|
function collectLogsAsync({ serialId, env, }: {
|
|
51
56
|
serialId: AndroidDeviceSerialId;
|
|
52
57
|
env: NodeJS.ProcessEnv;
|
|
@@ -275,6 +275,23 @@ var AndroidEmulatorUtils;
|
|
|
275
275
|
});
|
|
276
276
|
}
|
|
277
277
|
AndroidEmulatorUtils.waitForReadyAsync = waitForReadyAsync;
|
|
278
|
+
async function disableWindowAndTransitionAnimationsAsync({ serialId, env, logger, }) {
|
|
279
|
+
logger.info('Disabling Android emulator window animations.');
|
|
280
|
+
try {
|
|
281
|
+
await (0, turtle_spawn_1.default)('adb', ['-s', serialId, 'shell', 'settings', 'put', 'global', 'window_animation_scale', '0'], { env });
|
|
282
|
+
}
|
|
283
|
+
catch (err) {
|
|
284
|
+
logger.warn({ err }, 'Failed to disable Android emulator window animations.');
|
|
285
|
+
}
|
|
286
|
+
logger.info('Disabling Android emulator transition animations.');
|
|
287
|
+
try {
|
|
288
|
+
await (0, turtle_spawn_1.default)('adb', ['-s', serialId, 'shell', 'settings', 'put', 'global', 'transition_animation_scale', '0'], { env });
|
|
289
|
+
}
|
|
290
|
+
catch (err) {
|
|
291
|
+
logger.warn({ err }, 'Failed to disable Android emulator transition animations.');
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
AndroidEmulatorUtils.disableWindowAndTransitionAnimationsAsync = disableWindowAndTransitionAnimationsAsync;
|
|
278
295
|
async function hasNetworkConnectionAsync({ serialId, env, }) {
|
|
279
296
|
const networkReadyCheckCommand = env.ANDROID_EMULATOR_NETWORK_READY_COMMAND?.trim();
|
|
280
297
|
if (networkReadyCheckCommand) {
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { ProjectConfig } from '@expo/config';
|
|
2
2
|
import { Env } from '@expo/eas-build-job';
|
|
3
3
|
import { bunyan } from '@expo/logger';
|
|
4
|
-
|
|
4
|
+
interface ReadAppConfigParams {
|
|
5
5
|
projectDir: string;
|
|
6
6
|
env: Env;
|
|
7
7
|
logger: bunyan;
|
|
8
8
|
sdkVersion?: string;
|
|
9
|
-
}
|
|
9
|
+
}
|
|
10
|
+
export declare function readAppConfig(params: ReadAppConfigParams): Promise<ProjectConfig>;
|
|
11
|
+
export {};
|