@expo/build-tools 22.2.0 → 22.4.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/builders/android.js +2 -2
- package/dist/builders/ios.js +2 -2
- package/dist/ios/xcode.d.ts +3 -0
- package/dist/ios/xcode.js +22 -0
- package/dist/steps/easFunctions.js +4 -0
- package/dist/steps/functions/downloadBuild.d.ts +9 -2
- package/dist/steps/functions/downloadBuild.js +108 -15
- package/dist/steps/functions/installBuild.d.ts +12 -0
- package/dist/steps/functions/installBuild.js +85 -0
- package/dist/steps/functions/installMaestro.js +92 -11
- package/dist/steps/functions/launchApplication.d.ts +12 -0
- package/dist/steps/functions/launchApplication.js +121 -0
- package/dist/steps/functions/readIpaInfo.d.ts +1 -0
- package/dist/steps/functions/readIpaInfo.js +2 -0
- package/dist/steps/functions/repack.d.ts +5 -2
- package/dist/steps/functions/repack.js +50 -2
- package/dist/steps/functions/restoreBuildCache.d.ts +3 -3
- package/dist/steps/functions/restoreBuildCache.js +19 -6
- package/dist/steps/functions/saveBuildCache.d.ts +3 -3
- package/dist/steps/functions/saveBuildCache.js +17 -4
- package/dist/steps/functions/startServeSimRemoteSession.js +7 -0
- package/dist/steps/functions/uploadToAsc.js +11 -1
- package/dist/steps/utils/ios/AscApiClient.d.ts +1 -0
- package/dist/steps/utils/ios/AscApiUtils.d.ts +11 -2
- package/dist/steps/utils/ios/AscApiUtils.js +36 -2
- package/dist/steps/utils/ios/xcactivitylog.js +2 -8
- package/dist/steps/utils/remoteDeviceRunSession.d.ts +4 -2
- package/dist/steps/utils/remoteDeviceRunSession.js +9 -6
- package/dist/utils/IosSimulatorUtils.d.ts +8 -0
- package/dist/utils/IosSimulatorUtils.js +9 -0
- package/dist/utils/cacheKey.d.ts +8 -2
- package/dist/utils/cacheKey.js +12 -8
- package/dist/utils/download.d.ts +4 -0
- package/dist/utils/download.js +10 -0
- package/package.json +6 -4
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createLaunchApplicationFunction = createLaunchApplicationFunction;
|
|
7
|
+
exports.launchApplicationAsync = launchApplicationAsync;
|
|
8
|
+
const eas_build_job_1 = require("@expo/eas-build-job");
|
|
9
|
+
const steps_1 = require("@expo/steps");
|
|
10
|
+
const turtle_spawn_1 = __importDefault(require("@expo/turtle-spawn"));
|
|
11
|
+
function createLaunchApplicationFunction() {
|
|
12
|
+
return new steps_1.BuildFunction({
|
|
13
|
+
namespace: 'eas',
|
|
14
|
+
id: 'launch_application',
|
|
15
|
+
name: 'Launch application',
|
|
16
|
+
__metricsId: 'eas/launch_application',
|
|
17
|
+
inputProviders: [
|
|
18
|
+
steps_1.BuildStepInput.createProvider({
|
|
19
|
+
id: 'application_identifier',
|
|
20
|
+
required: true,
|
|
21
|
+
allowedValueTypeName: steps_1.BuildStepInputValueTypeName.STRING,
|
|
22
|
+
}),
|
|
23
|
+
steps_1.BuildStepInput.createProvider({
|
|
24
|
+
id: 'activity_name',
|
|
25
|
+
required: false,
|
|
26
|
+
allowedValueTypeName: steps_1.BuildStepInputValueTypeName.STRING,
|
|
27
|
+
}),
|
|
28
|
+
steps_1.BuildStepInput.createProvider({
|
|
29
|
+
id: 'launch_args',
|
|
30
|
+
required: false,
|
|
31
|
+
allowedValueTypeName: steps_1.BuildStepInputValueTypeName.JSON,
|
|
32
|
+
}),
|
|
33
|
+
steps_1.BuildStepInput.createProvider({
|
|
34
|
+
id: 'open_url',
|
|
35
|
+
required: false,
|
|
36
|
+
allowedValueTypeName: steps_1.BuildStepInputValueTypeName.STRING,
|
|
37
|
+
}),
|
|
38
|
+
],
|
|
39
|
+
fn: async ({ global, logger }, { inputs, env }) => {
|
|
40
|
+
const applicationIdentifier = parseNonEmptyStringInput(inputs.application_identifier.value, 'application_identifier');
|
|
41
|
+
const activityName = inputs.activity_name.value === undefined
|
|
42
|
+
? undefined
|
|
43
|
+
: parseNonEmptyStringInput(inputs.activity_name.value, 'activity_name');
|
|
44
|
+
const launchArgs = parseLaunchArgsInput(inputs.launch_args.value);
|
|
45
|
+
const openUrl = inputs.open_url.value === undefined ? undefined : parseOpenUrlInput(inputs.open_url.value);
|
|
46
|
+
await launchApplicationAsync({
|
|
47
|
+
applicationIdentifier,
|
|
48
|
+
activityName,
|
|
49
|
+
launchArgs,
|
|
50
|
+
openUrl,
|
|
51
|
+
runtimePlatform: global.runtimePlatform,
|
|
52
|
+
env,
|
|
53
|
+
logger,
|
|
54
|
+
});
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
async function launchApplicationAsync({ applicationIdentifier, activityName, launchArgs = [], openUrl, runtimePlatform, env, logger, }) {
|
|
59
|
+
if (runtimePlatform === steps_1.BuildRuntimePlatform.DARWIN) {
|
|
60
|
+
logApplicationLaunch(logger, applicationIdentifier, launchArgs);
|
|
61
|
+
await (0, turtle_spawn_1.default)('xcrun', ['simctl', 'launch', 'booted', applicationIdentifier, ...launchArgs], {
|
|
62
|
+
env,
|
|
63
|
+
logger,
|
|
64
|
+
});
|
|
65
|
+
if (openUrl) {
|
|
66
|
+
logger.info(`Opening ${openUrl} in ${applicationIdentifier}.`);
|
|
67
|
+
await (0, turtle_spawn_1.default)('xcrun', ['simctl', 'openurl', 'booted', openUrl], { env, logger });
|
|
68
|
+
}
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (!activityName) {
|
|
72
|
+
throw new eas_build_job_1.UserError('EAS_LAUNCH_APPLICATION_MISSING_ACTIVITY', 'Launching an Android application requires activity_name.');
|
|
73
|
+
}
|
|
74
|
+
logApplicationLaunch(logger, applicationIdentifier, launchArgs);
|
|
75
|
+
// Android does not support process arguments like iOS. Pass raw `am start` Intent
|
|
76
|
+
// arguments instead, such as `--es key value` or `--ez key true`.
|
|
77
|
+
await (0, turtle_spawn_1.default)('adb', ['shell', 'am', 'start', ...launchArgs, '-n', `${applicationIdentifier}/${activityName}`], {
|
|
78
|
+
env,
|
|
79
|
+
logger,
|
|
80
|
+
});
|
|
81
|
+
if (openUrl) {
|
|
82
|
+
logger.info(`Opening ${openUrl} in ${applicationIdentifier}.`);
|
|
83
|
+
await (0, turtle_spawn_1.default)('adb', [
|
|
84
|
+
'shell',
|
|
85
|
+
'am',
|
|
86
|
+
'start',
|
|
87
|
+
'-a',
|
|
88
|
+
'android.intent.action.VIEW',
|
|
89
|
+
'-d',
|
|
90
|
+
openUrl,
|
|
91
|
+
'-n',
|
|
92
|
+
`${applicationIdentifier}/${activityName}`,
|
|
93
|
+
], { env, logger });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function logApplicationLaunch(logger, applicationIdentifier, launchArgs) {
|
|
97
|
+
const argumentsDescription = launchArgs.length > 0 ? ` with arguments ${JSON.stringify(launchArgs)}` : '';
|
|
98
|
+
logger.info(`Launching ${applicationIdentifier}${argumentsDescription}.`);
|
|
99
|
+
}
|
|
100
|
+
function parseNonEmptyStringInput(value, inputName) {
|
|
101
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
102
|
+
throw new eas_build_job_1.UserError('EAS_LAUNCH_APPLICATION_INVALID_INPUT', `Input "${inputName}" must be a non-empty string. Pass the "${inputName}" output from eas/install_build.`);
|
|
103
|
+
}
|
|
104
|
+
return value;
|
|
105
|
+
}
|
|
106
|
+
function parseLaunchArgsInput(value) {
|
|
107
|
+
if (value === undefined) {
|
|
108
|
+
return [];
|
|
109
|
+
}
|
|
110
|
+
if (!Array.isArray(value) || !value.every(argument => typeof argument === 'string')) {
|
|
111
|
+
throw new eas_build_job_1.UserError('EAS_LAUNCH_APPLICATION_INVALID_INPUT', 'Input "launch_args" must be an array of strings.');
|
|
112
|
+
}
|
|
113
|
+
return value;
|
|
114
|
+
}
|
|
115
|
+
function parseOpenUrlInput(value) {
|
|
116
|
+
const openUrl = parseNonEmptyStringInput(value, 'open_url');
|
|
117
|
+
if (!URL.canParse(openUrl)) {
|
|
118
|
+
throw new eas_build_job_1.UserError('EAS_LAUNCH_APPLICATION_INVALID_INPUT', 'Input "open_url" must be a valid URL.');
|
|
119
|
+
}
|
|
120
|
+
return openUrl;
|
|
121
|
+
}
|
|
@@ -3,6 +3,7 @@ export type IpaInfo = {
|
|
|
3
3
|
bundleIdentifier: string;
|
|
4
4
|
bundleShortVersion: string;
|
|
5
5
|
bundleVersion: string;
|
|
6
|
+
dtPlatformName: string | null;
|
|
6
7
|
};
|
|
7
8
|
export declare function createReadIpaInfoBuildFunction(): BuildFunction;
|
|
8
9
|
export declare function readIpaInfoAsync(ipaPath: string): Promise<IpaInfo>;
|
|
@@ -70,10 +70,12 @@ async function readIpaInfoAsync(ipaPath) {
|
|
|
70
70
|
if (typeof bundleVersion !== 'string') {
|
|
71
71
|
throw new eas_build_job_1.UserError('EAS_READ_IPA_INFO_INVALID_INFO_PLIST', 'Failed to read IPA info: Missing or invalid CFBundleVersion in Info.plist');
|
|
72
72
|
}
|
|
73
|
+
const dtPlatformName = typeof infoPlist.DTPlatformName === 'string' ? infoPlist.DTPlatformName : null;
|
|
73
74
|
return {
|
|
74
75
|
bundleIdentifier,
|
|
75
76
|
bundleShortVersion,
|
|
76
77
|
bundleVersion,
|
|
78
|
+
dtPlatformName,
|
|
77
79
|
};
|
|
78
80
|
}
|
|
79
81
|
catch (error) {
|
|
@@ -11,11 +11,14 @@ export declare function resolveAndroidSigningOptionsAsync({ job, tmpDir, }: {
|
|
|
11
11
|
tmpDir: string;
|
|
12
12
|
}): Promise<AndroidSigningOptions | undefined>;
|
|
13
13
|
/**
|
|
14
|
-
* Resolves iOS signing options from the job secrets
|
|
14
|
+
* Resolves iOS signing options from the job secrets, dispatching on the
|
|
15
|
+
* requested signing backend.
|
|
15
16
|
*/
|
|
16
|
-
export declare function resolveIosSigningOptionsAsync({ job, logger, useAppEntitlements, entitlementsPath, }: {
|
|
17
|
+
export declare function resolveIosSigningOptionsAsync({ job, logger, backend, useAppEntitlements, entitlementsPath, tmpDir, }: {
|
|
17
18
|
job: Job;
|
|
18
19
|
logger: bunyan;
|
|
20
|
+
backend?: 'fastlane' | 'zsign';
|
|
19
21
|
useAppEntitlements?: boolean;
|
|
20
22
|
entitlementsPath?: string;
|
|
23
|
+
tmpDir: string;
|
|
21
24
|
}): Promise<IosSigningOptions | undefined>;
|
|
@@ -53,6 +53,12 @@ function createRepackBuildFunction() {
|
|
|
53
53
|
allowedValueTypeName: steps_1.BuildStepInputValueTypeName.STRING,
|
|
54
54
|
required: false,
|
|
55
55
|
}),
|
|
56
|
+
steps_1.BuildStepInput.createProvider({
|
|
57
|
+
id: 'ios_signing_backend',
|
|
58
|
+
allowedValueTypeName: steps_1.BuildStepInputValueTypeName.STRING,
|
|
59
|
+
required: false,
|
|
60
|
+
allowedValues: ['fastlane', 'zsign'],
|
|
61
|
+
}),
|
|
56
62
|
steps_1.BuildStepInput.createProvider({
|
|
57
63
|
id: 'repack_version',
|
|
58
64
|
allowedValueTypeName: steps_1.BuildStepInputValueTypeName.STRING,
|
|
@@ -123,8 +129,10 @@ function createRepackBuildFunction() {
|
|
|
123
129
|
iosSigningOptions: await resolveIosSigningOptionsAsync({
|
|
124
130
|
job: stepsCtx.global.staticContext.job,
|
|
125
131
|
logger: stepsCtx.logger,
|
|
132
|
+
backend: inputs.ios_signing_backend.value,
|
|
126
133
|
useAppEntitlements: inputs.ios_signing_use_source_app_entitlements.value,
|
|
127
134
|
entitlementsPath: inputs.ios_signing_app_entitlements_path.value,
|
|
135
|
+
tmpDir,
|
|
128
136
|
}),
|
|
129
137
|
logger: stepsCtx.logger,
|
|
130
138
|
spawnAsync: repackSpawnAsync,
|
|
@@ -234,14 +242,26 @@ async function resolveAndroidSigningOptionsAsync({ job, tmpDir, }) {
|
|
|
234
242
|
};
|
|
235
243
|
}
|
|
236
244
|
/**
|
|
237
|
-
* Resolves iOS signing options from the job secrets
|
|
245
|
+
* Resolves iOS signing options from the job secrets, dispatching on the
|
|
246
|
+
* requested signing backend.
|
|
238
247
|
*/
|
|
239
|
-
async function resolveIosSigningOptionsAsync({ job, logger, useAppEntitlements, entitlementsPath, }) {
|
|
248
|
+
async function resolveIosSigningOptionsAsync({ job, logger, backend, useAppEntitlements, entitlementsPath, tmpDir, }) {
|
|
240
249
|
const iosJob = job;
|
|
241
250
|
const buildCredentials = iosJob.secrets?.buildCredentials;
|
|
242
251
|
if (iosJob.simulator || buildCredentials == null) {
|
|
243
252
|
return undefined;
|
|
244
253
|
}
|
|
254
|
+
const commonOptions = { buildCredentials, logger, useAppEntitlements, entitlementsPath };
|
|
255
|
+
return backend === 'zsign'
|
|
256
|
+
? await createIosZsignOptionsAsync({ ...commonOptions, tmpDir })
|
|
257
|
+
: await createIosFastlaneOptionsAsync(commonOptions);
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Creates signing options for the fastlane backend: certificates are imported
|
|
261
|
+
* into a temporary keychain and provisioning profiles are parsed with the
|
|
262
|
+
* macOS `security` tool.
|
|
263
|
+
*/
|
|
264
|
+
async function createIosFastlaneOptionsAsync({ buildCredentials, logger, useAppEntitlements, entitlementsPath, }) {
|
|
245
265
|
const credentialsManager = new manager_1.default(buildCredentials);
|
|
246
266
|
const credentials = await credentialsManager.prepare(logger);
|
|
247
267
|
const provisioningProfile = {};
|
|
@@ -256,3 +276,31 @@ async function resolveIosSigningOptionsAsync({ job, logger, useAppEntitlements,
|
|
|
256
276
|
entitlementsPath,
|
|
257
277
|
};
|
|
258
278
|
}
|
|
279
|
+
/**
|
|
280
|
+
* Creates signing options for the zsign backend. The distribution certificate
|
|
281
|
+
* secret is already a PKCS#12 file, so it goes to disk as-is together with the
|
|
282
|
+
* provisioning profiles.
|
|
283
|
+
*/
|
|
284
|
+
async function createIosZsignOptionsAsync({ buildCredentials, logger, useAppEntitlements, entitlementsPath, tmpDir, }) {
|
|
285
|
+
const targets = Object.entries(buildCredentials);
|
|
286
|
+
const [targetName, targetCredentials] = targets[0];
|
|
287
|
+
logger.info(`Using the distribution certificate from target '${targetName}' for zsign`);
|
|
288
|
+
const certificatePath = node_path_1.default.join(tmpDir, `dist-cert-${(0, node_crypto_1.randomUUID)()}.p12`);
|
|
289
|
+
await node_fs_1.default.promises.writeFile(certificatePath, new Uint8Array(Buffer.from(targetCredentials.distributionCertificate.dataBase64, 'base64')));
|
|
290
|
+
// zsign matches profiles to bundles by the app-id suffix itself, so the
|
|
291
|
+
// record keys are informational only.
|
|
292
|
+
const provisioningProfile = {};
|
|
293
|
+
for (const [target, credentials] of targets) {
|
|
294
|
+
const profilePath = node_path_1.default.join(tmpDir, `profile-${target}-${(0, node_crypto_1.randomUUID)()}.mobileprovision`);
|
|
295
|
+
await node_fs_1.default.promises.writeFile(profilePath, new Uint8Array(Buffer.from(credentials.provisioningProfileBase64, 'base64')));
|
|
296
|
+
provisioningProfile[target] = profilePath;
|
|
297
|
+
}
|
|
298
|
+
return {
|
|
299
|
+
backend: 'zsign',
|
|
300
|
+
certificatePath,
|
|
301
|
+
keyPassword: targetCredentials.distributionCertificate.password,
|
|
302
|
+
provisioningProfile,
|
|
303
|
+
useAppEntitlements,
|
|
304
|
+
entitlementsPath,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { Platform } from '@expo/eas-build-job';
|
|
2
1
|
import { bunyan } from '@expo/logger';
|
|
3
2
|
import { BuildFunction } from '@expo/steps';
|
|
3
|
+
import { CcacheBuildTarget } from '../../utils/cacheKey';
|
|
4
4
|
export declare function createRestoreBuildCacheFunction(): BuildFunction;
|
|
5
5
|
export declare function createCacheStatsBuildFunction(): BuildFunction;
|
|
6
|
-
export declare function restoreCcacheAsync({ logger, workingDirectory,
|
|
6
|
+
export declare function restoreCcacheAsync({ logger, workingDirectory, target, env, secrets, }: {
|
|
7
7
|
logger: bunyan;
|
|
8
8
|
workingDirectory: string;
|
|
9
|
-
|
|
9
|
+
target: CcacheBuildTarget;
|
|
10
10
|
env: Record<string, string | undefined>;
|
|
11
11
|
secrets?: {
|
|
12
12
|
robotAccessToken?: string;
|
|
@@ -33,6 +33,11 @@ function createRestoreBuildCacheFunction() {
|
|
|
33
33
|
required: false,
|
|
34
34
|
allowedValueTypeName: steps_1.BuildStepInputValueTypeName.STRING,
|
|
35
35
|
}),
|
|
36
|
+
steps_1.BuildStepInput.createProvider({
|
|
37
|
+
id: 'simulator',
|
|
38
|
+
required: false,
|
|
39
|
+
allowedValueTypeName: steps_1.BuildStepInputValueTypeName.BOOLEAN,
|
|
40
|
+
}),
|
|
36
41
|
],
|
|
37
42
|
fn: async (stepCtx, { env, inputs }) => {
|
|
38
43
|
const { logger } = stepCtx;
|
|
@@ -42,10 +47,18 @@ function createRestoreBuildCacheFunction() {
|
|
|
42
47
|
if (!platform || ![eas_build_job_1.Platform.ANDROID, eas_build_job_1.Platform.IOS].includes(platform)) {
|
|
43
48
|
throw new Error(`Unsupported platform: ${platform}. Platform must be "${eas_build_job_1.Platform.ANDROID}" or "${eas_build_job_1.Platform.IOS}"`);
|
|
44
49
|
}
|
|
50
|
+
const target = platform === eas_build_job_1.Platform.IOS
|
|
51
|
+
? {
|
|
52
|
+
platform,
|
|
53
|
+
simulator: inputs.simulator.value ??
|
|
54
|
+
(stepCtx.global.staticContext.job.platform === eas_build_job_1.Platform.IOS &&
|
|
55
|
+
stepCtx.global.staticContext.job.simulator === true),
|
|
56
|
+
}
|
|
57
|
+
: { platform };
|
|
45
58
|
await restoreCcacheAsync({
|
|
46
59
|
logger,
|
|
47
60
|
workingDirectory,
|
|
48
|
-
|
|
61
|
+
target,
|
|
49
62
|
env,
|
|
50
63
|
secrets: stepCtx.global.staticContext.job.secrets,
|
|
51
64
|
});
|
|
@@ -80,7 +93,7 @@ function createCacheStatsBuildFunction() {
|
|
|
80
93
|
},
|
|
81
94
|
});
|
|
82
95
|
}
|
|
83
|
-
async function restoreCcacheAsync({ logger, workingDirectory,
|
|
96
|
+
async function restoreCcacheAsync({ logger, workingDirectory, target, env, secrets, }) {
|
|
84
97
|
const enabled = env.EAS_RESTORE_CACHE === '1' || (env.EAS_USE_CACHE === '1' && env.EAS_RESTORE_CACHE !== '0');
|
|
85
98
|
if (!enabled) {
|
|
86
99
|
return;
|
|
@@ -103,7 +116,7 @@ async function restoreCcacheAsync({ logger, workingDirectory, platform, env, sec
|
|
|
103
116
|
env,
|
|
104
117
|
stdio: 'pipe',
|
|
105
118
|
}));
|
|
106
|
-
const cacheKey = await (0, cacheKey_1.generateDefaultBuildCacheKeyAsync)(workingDirectory,
|
|
119
|
+
const cacheKey = await (0, cacheKey_1.generateDefaultBuildCacheKeyAsync)(workingDirectory, target);
|
|
107
120
|
logger.info(`Restoring cache key: ${cacheKey}`);
|
|
108
121
|
const jobId = (0, nullthrows_1.default)(env.EAS_BUILD_ID, 'EAS_BUILD_ID is not set');
|
|
109
122
|
const { archivePath, matchedKey } = await (0, restoreCache_1.downloadCacheAsync)({
|
|
@@ -113,8 +126,8 @@ async function restoreCcacheAsync({ logger, workingDirectory, platform, env, sec
|
|
|
113
126
|
robotAccessToken,
|
|
114
127
|
paths: [cachePath],
|
|
115
128
|
key: cacheKey,
|
|
116
|
-
keyPrefixes: [cacheKey_1.
|
|
117
|
-
platform,
|
|
129
|
+
keyPrefixes: [(0, cacheKey_1.getCcacheKeyPrefix)(target)],
|
|
130
|
+
platform: target.platform,
|
|
118
131
|
});
|
|
119
132
|
await (0, restoreCache_1.decompressCacheAsync)({
|
|
120
133
|
archivePath,
|
|
@@ -139,7 +152,7 @@ async function restoreCcacheAsync({ logger, workingDirectory, platform, env, sec
|
|
|
139
152
|
expoApiServerURL,
|
|
140
153
|
robotAccessToken,
|
|
141
154
|
paths: [cachePath],
|
|
142
|
-
platform,
|
|
155
|
+
platform: target.platform,
|
|
143
156
|
});
|
|
144
157
|
await (0, restoreCache_1.decompressCacheAsync)({
|
|
145
158
|
archivePath,
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { Platform } from '@expo/eas-build-job';
|
|
2
1
|
import { bunyan } from '@expo/logger';
|
|
3
2
|
import { BuildFunction } from '@expo/steps';
|
|
3
|
+
import { CcacheBuildTarget } from '../../utils/cacheKey';
|
|
4
4
|
export declare function createSaveBuildCacheFunction(evictUsedBefore: Date): BuildFunction;
|
|
5
|
-
export declare function saveCcacheAsync({ logger, workingDirectory,
|
|
5
|
+
export declare function saveCcacheAsync({ logger, workingDirectory, target, evictUsedBefore, env, secrets, }: {
|
|
6
6
|
logger: bunyan;
|
|
7
7
|
workingDirectory: string;
|
|
8
|
-
|
|
8
|
+
target: CcacheBuildTarget;
|
|
9
9
|
evictUsedBefore: Date;
|
|
10
10
|
env: Record<string, string | undefined>;
|
|
11
11
|
secrets?: {
|
|
@@ -29,6 +29,11 @@ function createSaveBuildCacheFunction(evictUsedBefore) {
|
|
|
29
29
|
required: false,
|
|
30
30
|
allowedValueTypeName: steps_1.BuildStepInputValueTypeName.STRING,
|
|
31
31
|
}),
|
|
32
|
+
steps_1.BuildStepInput.createProvider({
|
|
33
|
+
id: 'simulator',
|
|
34
|
+
required: false,
|
|
35
|
+
allowedValueTypeName: steps_1.BuildStepInputValueTypeName.BOOLEAN,
|
|
36
|
+
}),
|
|
32
37
|
],
|
|
33
38
|
fn: async (stepCtx, { env, inputs }) => {
|
|
34
39
|
const { logger } = stepCtx;
|
|
@@ -38,10 +43,18 @@ function createSaveBuildCacheFunction(evictUsedBefore) {
|
|
|
38
43
|
if (!platform || ![eas_build_job_1.Platform.ANDROID, eas_build_job_1.Platform.IOS].includes(platform)) {
|
|
39
44
|
throw new Error(`Unsupported platform: ${platform}. Platform must be "${eas_build_job_1.Platform.ANDROID}" or "${eas_build_job_1.Platform.IOS}"`);
|
|
40
45
|
}
|
|
46
|
+
const target = platform === eas_build_job_1.Platform.IOS
|
|
47
|
+
? {
|
|
48
|
+
platform,
|
|
49
|
+
simulator: inputs.simulator.value ??
|
|
50
|
+
(stepCtx.global.staticContext.job.platform === eas_build_job_1.Platform.IOS &&
|
|
51
|
+
stepCtx.global.staticContext.job.simulator === true),
|
|
52
|
+
}
|
|
53
|
+
: { platform };
|
|
41
54
|
await saveCcacheAsync({
|
|
42
55
|
logger,
|
|
43
56
|
workingDirectory,
|
|
44
|
-
|
|
57
|
+
target,
|
|
45
58
|
evictUsedBefore,
|
|
46
59
|
env,
|
|
47
60
|
secrets: stepCtx.global.staticContext.job.secrets,
|
|
@@ -57,7 +70,7 @@ function createSaveBuildCacheFunction(evictUsedBefore) {
|
|
|
57
70
|
},
|
|
58
71
|
});
|
|
59
72
|
}
|
|
60
|
-
async function saveCcacheAsync({ logger, workingDirectory,
|
|
73
|
+
async function saveCcacheAsync({ logger, workingDirectory, target, evictUsedBefore, env, secrets, }) {
|
|
61
74
|
const enabled = env.EAS_SAVE_CACHE === '1' || (env.EAS_USE_CACHE === '1' && env.EAS_SAVE_CACHE !== '0');
|
|
62
75
|
if (!enabled) {
|
|
63
76
|
return;
|
|
@@ -72,7 +85,7 @@ async function saveCcacheAsync({ logger, workingDirectory, platform, evictUsedBe
|
|
|
72
85
|
return;
|
|
73
86
|
}
|
|
74
87
|
try {
|
|
75
|
-
const cacheKey = await (0, cacheKey_1.generateDefaultBuildCacheKeyAsync)(workingDirectory,
|
|
88
|
+
const cacheKey = await (0, cacheKey_1.generateDefaultBuildCacheKeyAsync)(workingDirectory, target);
|
|
76
89
|
logger.info(`Saving cache key: ${cacheKey}`);
|
|
77
90
|
const jobId = (0, nullthrows_1.default)(env.EAS_BUILD_ID, 'EAS_BUILD_ID is not set');
|
|
78
91
|
const robotAccessToken = (0, nullthrows_1.default)(secrets?.robotAccessToken, 'Robot access token is required for cache operations');
|
|
@@ -104,7 +117,7 @@ async function saveCcacheAsync({ logger, workingDirectory, platform, evictUsedBe
|
|
|
104
117
|
key: cacheKey,
|
|
105
118
|
paths: [cachePath],
|
|
106
119
|
size,
|
|
107
|
-
platform,
|
|
120
|
+
platform: target.platform,
|
|
108
121
|
});
|
|
109
122
|
}
|
|
110
123
|
catch (err) {
|
|
@@ -12,6 +12,11 @@ function createStartServeSimRemoteSessionBuildFunction(ctx) {
|
|
|
12
12
|
__metricsId: 'eas/start_serve_sim_remote_session',
|
|
13
13
|
supportedRuntimePlatforms: [steps_1.BuildRuntimePlatform.DARWIN],
|
|
14
14
|
inputProviders: [
|
|
15
|
+
steps_1.BuildStepInput.createProvider({
|
|
16
|
+
id: 'package_version',
|
|
17
|
+
required: false,
|
|
18
|
+
allowedValueTypeName: steps_1.BuildStepInputValueTypeName.STRING,
|
|
19
|
+
}),
|
|
15
20
|
steps_1.BuildStepInput.createProvider({
|
|
16
21
|
id: 'max_duration_seconds',
|
|
17
22
|
required: false,
|
|
@@ -22,6 +27,7 @@ function createStartServeSimRemoteSessionBuildFunction(ctx) {
|
|
|
22
27
|
const deviceRunSessionId = (0, remoteDeviceRunSession_1.getDeviceRunSessionIdOrThrow)(env);
|
|
23
28
|
const ngrokTunnelDomain = (0, remoteDeviceRunSession_1.getNgrokTunnelDomainOrThrow)(env);
|
|
24
29
|
const maxDurationSeconds = inputs.max_duration_seconds?.value;
|
|
30
|
+
const packageVersion = inputs.package_version?.value;
|
|
25
31
|
logger.info('Starting serve-sim remote session.');
|
|
26
32
|
await (0, remoteDeviceRunSession_1.selectXcodeDeveloperDirectoryAsync)({ env, logger });
|
|
27
33
|
const serveSim = await (0, remoteDeviceRunSession_1.startServeSimWithTunnelAsync)(ctx, {
|
|
@@ -29,6 +35,7 @@ function createStartServeSimRemoteSessionBuildFunction(ctx) {
|
|
|
29
35
|
env,
|
|
30
36
|
logger,
|
|
31
37
|
timeoutMs: STARTUP_TIMEOUT_MS,
|
|
38
|
+
packageVersion,
|
|
32
39
|
});
|
|
33
40
|
logger.info(`Preview URL: ${serveSim.previewUrl}`);
|
|
34
41
|
try {
|
|
@@ -141,15 +141,25 @@ function createUploadToAscBuildFunction() {
|
|
|
141
141
|
const appResponse = await AscApiUtils_1.AscApiUtils.getAppInfoAsync({ client, appleAppIdentifier });
|
|
142
142
|
const ascAppBundleIdentifier = appResponse.data.attributes.bundleId;
|
|
143
143
|
stepsCtx.logger.info(`Uploading Build to "${appResponse.data.attributes.name}" (${ascAppBundleIdentifier})...`);
|
|
144
|
+
// Derive the App Store Connect platform from the IPA itself so tvOS (and
|
|
145
|
+
// other) binaries are uploaded to the correct version train instead of the
|
|
146
|
+
// iOS one. Falls back to `IOS` if the platform cannot be read.
|
|
147
|
+
const ipaInfoResult = await (0, results_1.asyncResult)((0, readIpaInfo_1.readIpaInfoAsync)(ipaPath));
|
|
148
|
+
const platform = ipaInfoResult.ok
|
|
149
|
+
? AscApiUtils_1.AscApiUtils.ascPlatformFromDtPlatformName(ipaInfoResult.value.dtPlatformName)
|
|
150
|
+
: 'IOS';
|
|
151
|
+
stepsCtx.logger.info(`Detected App Store Connect platform: ${platform}`);
|
|
144
152
|
stepsCtx.logger.info('Creating Build Upload...');
|
|
145
153
|
const buildUploadResponse = await AscApiUtils_1.AscApiUtils.createBuildUploadAsync({
|
|
146
154
|
client,
|
|
147
155
|
appleAppIdentifier,
|
|
148
156
|
bundleShortVersion,
|
|
149
157
|
bundleVersion,
|
|
158
|
+
platform,
|
|
150
159
|
});
|
|
151
160
|
const buildUploadId = buildUploadResponse.data.id;
|
|
152
|
-
const
|
|
161
|
+
const platformPathSegment = AscApiUtils_1.AscApiUtils.testFlightPlatformPathSegment(platform);
|
|
162
|
+
const buildUploadUrl = `https://appstoreconnect.apple.com/apps/${appleAppIdentifier}/testflight/${platformPathSegment}/${buildUploadId}`;
|
|
153
163
|
outputs.build_upload_id.set(buildUploadId);
|
|
154
164
|
outputs.build_upload_url.set(buildUploadUrl);
|
|
155
165
|
stepsCtx.logger.info(`Build Upload initialized (ID: ${buildUploadId}). Preparing IPA upload...`);
|
|
@@ -264,6 +264,7 @@ export type AscApiClientPostApi = {
|
|
|
264
264
|
response: z.output<(typeof PostApi)[Path]['response']>;
|
|
265
265
|
};
|
|
266
266
|
};
|
|
267
|
+
export type AscPlatform = AscApiClientPostApi['/v1/buildUploads']['request']['data']['attributes']['platform'];
|
|
267
268
|
export type AscApiClientPatchApi = {
|
|
268
269
|
[Path in keyof typeof PatchApi]: {
|
|
269
270
|
request: z.input<(typeof PatchApi)[Path]['request']>;
|
|
@@ -1,14 +1,23 @@
|
|
|
1
|
-
import { AscApiClient, AscApiClientGetApi, AscApiClientPostApi } from './AscApiClient';
|
|
1
|
+
import { AscApiClient, AscApiClientGetApi, AscApiClientPostApi, AscPlatform } from './AscApiClient';
|
|
2
2
|
export declare namespace AscApiUtils {
|
|
3
|
+
/**
|
|
4
|
+
* Maps a bundle's `DTPlatformName` (from its Info.plist) to the App Store
|
|
5
|
+
* Connect platform used for a build upload. Unknown or missing values fall
|
|
6
|
+
* back to `IOS` to preserve the previous default.
|
|
7
|
+
*/
|
|
8
|
+
function ascPlatformFromDtPlatformName(dtPlatformName: string | null): AscPlatform;
|
|
9
|
+
/** The App Store Connect TestFlight URL path segment for a platform. */
|
|
10
|
+
function testFlightPlatformPathSegment(platform: AscPlatform): string;
|
|
3
11
|
function getAppInfoAsync({ client, appleAppIdentifier, }: {
|
|
4
12
|
client: Pick<AscApiClient, 'getAsync'>;
|
|
5
13
|
appleAppIdentifier: string;
|
|
6
14
|
}): Promise<AscApiClientGetApi['/v1/apps/:id']['response']>;
|
|
7
|
-
function createBuildUploadAsync({ client, appleAppIdentifier, bundleShortVersion, bundleVersion, }: {
|
|
15
|
+
function createBuildUploadAsync({ client, appleAppIdentifier, bundleShortVersion, bundleVersion, platform, }: {
|
|
8
16
|
client: Pick<AscApiClient, 'postAsync'>;
|
|
9
17
|
appleAppIdentifier: string;
|
|
10
18
|
bundleShortVersion: string;
|
|
11
19
|
bundleVersion: string;
|
|
20
|
+
platform: AscPlatform;
|
|
12
21
|
}): Promise<AscApiClientPostApi['/v1/buildUploads']['response']>;
|
|
13
22
|
function getAppsAsync({ client, limit, }: {
|
|
14
23
|
client: Pick<AscApiClient, 'getAsync'>;
|
|
@@ -5,6 +5,40 @@ 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) {
|
|
8
|
+
/**
|
|
9
|
+
* Maps a bundle's `DTPlatformName` (from its Info.plist) to the App Store
|
|
10
|
+
* Connect platform used for a build upload. Unknown or missing values fall
|
|
11
|
+
* back to `IOS` to preserve the previous default.
|
|
12
|
+
*/
|
|
13
|
+
function ascPlatformFromDtPlatformName(dtPlatformName) {
|
|
14
|
+
switch (dtPlatformName) {
|
|
15
|
+
case 'appletvos':
|
|
16
|
+
return 'TV_OS';
|
|
17
|
+
case 'macosx':
|
|
18
|
+
return 'MAC_OS';
|
|
19
|
+
case 'xros':
|
|
20
|
+
return 'VISION_OS';
|
|
21
|
+
case 'iphoneos':
|
|
22
|
+
default:
|
|
23
|
+
return 'IOS';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
AscApiUtils.ascPlatformFromDtPlatformName = ascPlatformFromDtPlatformName;
|
|
27
|
+
/** The App Store Connect TestFlight URL path segment for a platform. */
|
|
28
|
+
function testFlightPlatformPathSegment(platform) {
|
|
29
|
+
switch (platform) {
|
|
30
|
+
case 'TV_OS':
|
|
31
|
+
return 'tvos';
|
|
32
|
+
case 'MAC_OS':
|
|
33
|
+
return 'macos';
|
|
34
|
+
case 'VISION_OS':
|
|
35
|
+
return 'visionos';
|
|
36
|
+
case 'IOS':
|
|
37
|
+
default:
|
|
38
|
+
return 'ios';
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
AscApiUtils.testFlightPlatformPathSegment = testFlightPlatformPathSegment;
|
|
8
42
|
async function getAppInfoAsync({ client, appleAppIdentifier, }) {
|
|
9
43
|
try {
|
|
10
44
|
return await client.getAsync('/v1/apps/:id', { 'fields[apps]': ['bundleId', 'name'] }, { id: appleAppIdentifier });
|
|
@@ -37,13 +71,13 @@ var AscApiUtils;
|
|
|
37
71
|
}
|
|
38
72
|
}
|
|
39
73
|
AscApiUtils.getAppInfoAsync = getAppInfoAsync;
|
|
40
|
-
async function createBuildUploadAsync({ client, appleAppIdentifier, bundleShortVersion, bundleVersion, }) {
|
|
74
|
+
async function createBuildUploadAsync({ client, appleAppIdentifier, bundleShortVersion, bundleVersion, platform, }) {
|
|
41
75
|
try {
|
|
42
76
|
return await client.postAsync('/v1/buildUploads', {
|
|
43
77
|
data: {
|
|
44
78
|
type: 'buildUploads',
|
|
45
79
|
attributes: {
|
|
46
|
-
platform
|
|
80
|
+
platform,
|
|
47
81
|
cfBundleShortVersionString: bundleShortVersion,
|
|
48
82
|
cfBundleVersion: bundleVersion,
|
|
49
83
|
},
|
|
@@ -16,6 +16,7 @@ const os_1 = __importDefault(require("os"));
|
|
|
16
16
|
const path_1 = __importDefault(require("path"));
|
|
17
17
|
const zod_1 = require("zod");
|
|
18
18
|
const sentry_1 = require("../../../sentry");
|
|
19
|
+
const download_1 = require("../../../utils/download");
|
|
19
20
|
const DEFAULT_XCLOGPARSER_VERSION = 'v0.2.47';
|
|
20
21
|
const XCLOGPARSER_DOWNLOAD_URL = 'https://storage.googleapis.com/turtle-v2/xclogparser';
|
|
21
22
|
const XCLOGPARSER_DOWNLOAD_TIMEOUT_MS = 20_000;
|
|
@@ -123,7 +124,7 @@ async function downloadXclogparser({ tempDir, version, logger, proxyBaseUrl, env
|
|
|
123
124
|
const zipName = getXclogparserZipName(version);
|
|
124
125
|
const zipPath = path_1.default.join(tempDir, zipName);
|
|
125
126
|
const directUrl = `${XCLOGPARSER_DOWNLOAD_URL}/${zipName}`;
|
|
126
|
-
const proxiedUrl = getProxiedDownloadUrl({ directUrl, proxyBaseUrl });
|
|
127
|
+
const proxiedUrl = (0, download_1.getProxiedDownloadUrl)({ directUrl, proxyBaseUrl });
|
|
127
128
|
if (proxiedUrl) {
|
|
128
129
|
const proxiedDownloadResult = await (0, results_1.asyncResult)(downloadAndUnpackXclogparser({ tempDir, zipPath, sourceUrl: proxiedUrl, env }));
|
|
129
130
|
if (!proxiedDownloadResult.ok) {
|
|
@@ -188,13 +189,6 @@ const COMPILE_SIGNATURE_PREFIXES = ['SwiftCompile ', 'SwiftGeneratePch '];
|
|
|
188
189
|
function getXclogparserZipName(version) {
|
|
189
190
|
return `XCLogParser-macOS-x86-64-arm64-${version}.zip`;
|
|
190
191
|
}
|
|
191
|
-
function getProxiedDownloadUrl({ directUrl, proxyBaseUrl, }) {
|
|
192
|
-
if (!proxyBaseUrl) {
|
|
193
|
-
return null;
|
|
194
|
-
}
|
|
195
|
-
const parsedUrl = new URL(directUrl);
|
|
196
|
-
return directUrl.replace(`${parsedUrl.protocol}//${parsedUrl.host}`, `${proxyBaseUrl}/${parsedUrl.host}`);
|
|
197
|
-
}
|
|
198
192
|
function isCompileStep(step) {
|
|
199
193
|
const detailStepType = step.detailStepType ?? '';
|
|
200
194
|
const signature = step.signature ?? '';
|
|
@@ -92,10 +92,11 @@ export declare function spawnDetached({ command, args, cwd, env, }: {
|
|
|
92
92
|
env: BuildStepEnv;
|
|
93
93
|
}): DetachedProcessHandle;
|
|
94
94
|
export declare function metricsCorsOriginToServeSimArgs(env: BuildStepEnv): string[];
|
|
95
|
-
export declare function createServeSimArgs({ port, turnArgs, metricsCorsArgs, }: {
|
|
95
|
+
export declare function createServeSimArgs({ port, turnArgs, metricsCorsArgs, packageVersion, }: {
|
|
96
96
|
port: number;
|
|
97
97
|
turnArgs?: string[];
|
|
98
98
|
metricsCorsArgs?: string[];
|
|
99
|
+
packageVersion?: string;
|
|
99
100
|
}): string[];
|
|
100
101
|
export declare function waitForServeSimReadyAsync({ serveSim, port, timeoutMs, }: {
|
|
101
102
|
serveSim: Pick<DetachedProcessHandle, 'pid' | 'getOutput'>;
|
|
@@ -106,11 +107,12 @@ export type ServeSimPreviewHandle = {
|
|
|
106
107
|
previewUrl: string;
|
|
107
108
|
stopAsync: () => Promise<void>;
|
|
108
109
|
};
|
|
109
|
-
export declare function startServeSimWithTunnelAsync(ctx: CustomBuildContext, { baseDomain, env, logger, timeoutMs, }: {
|
|
110
|
+
export declare function startServeSimWithTunnelAsync(ctx: CustomBuildContext, { baseDomain, env, logger, timeoutMs, packageVersion, }: {
|
|
110
111
|
baseDomain: string;
|
|
111
112
|
env: BuildStepEnv;
|
|
112
113
|
logger: bunyan;
|
|
113
114
|
timeoutMs: number;
|
|
115
|
+
packageVersion?: string;
|
|
114
116
|
}): Promise<ServeSimPreviewHandle>;
|
|
115
117
|
export type NgrokTunnelHandle = {
|
|
116
118
|
url: string;
|