@expo/build-tools 22.2.0 → 22.3.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/steps/easFunctions.js +4 -0
- package/dist/steps/functions/downloadBuild.d.ts +9 -2
- package/dist/steps/functions/downloadBuild.js +83 -14
- package/dist/steps/functions/installBuild.d.ts +12 -0
- package/dist/steps/functions/installBuild.js +85 -0
- package/dist/steps/functions/launchApplication.d.ts +10 -0
- package/dist/steps/functions/launchApplication.js +67 -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/remoteDeviceRunSession.d.ts +4 -2
- package/dist/steps/utils/remoteDeviceRunSession.js +9 -6
- package/dist/utils/cacheKey.d.ts +8 -2
- package/dist/utils/cacheKey.js +12 -8
- package/package.json +5 -3
package/dist/builders/android.js
CHANGED
|
@@ -81,7 +81,7 @@ async function buildInnerAsync(ctx, jobHooksRef) {
|
|
|
81
81
|
await (0, restoreBuildCache_1.restoreCcacheAsync)({
|
|
82
82
|
logger: ctx.logger,
|
|
83
83
|
workingDirectory,
|
|
84
|
-
platform: ctx.job.platform,
|
|
84
|
+
target: { platform: ctx.job.platform },
|
|
85
85
|
env: ctx.env,
|
|
86
86
|
secrets: ctx.job.secrets,
|
|
87
87
|
});
|
|
@@ -203,7 +203,7 @@ async function buildInnerAsync(ctx, jobHooksRef) {
|
|
|
203
203
|
await (0, saveBuildCache_1.saveCcacheAsync)({
|
|
204
204
|
logger: ctx.logger,
|
|
205
205
|
workingDirectory,
|
|
206
|
-
platform: ctx.job.platform,
|
|
206
|
+
target: { platform: ctx.job.platform },
|
|
207
207
|
evictUsedBefore,
|
|
208
208
|
env: ctx.env,
|
|
209
209
|
secrets: ctx.job.secrets,
|
package/dist/builders/ios.js
CHANGED
|
@@ -97,7 +97,7 @@ async function buildInnerAsync(ctx, jobHooksRef) {
|
|
|
97
97
|
await (0, restoreBuildCache_1.restoreCcacheAsync)({
|
|
98
98
|
logger: ctx.logger,
|
|
99
99
|
workingDirectory,
|
|
100
|
-
platform: ctx.job.platform,
|
|
100
|
+
target: { platform: ctx.job.platform, simulator: ctx.job.simulator === true },
|
|
101
101
|
env: ctx.env,
|
|
102
102
|
secrets: ctx.job.secrets,
|
|
103
103
|
});
|
|
@@ -224,7 +224,7 @@ async function buildInnerAsync(ctx, jobHooksRef) {
|
|
|
224
224
|
await (0, saveBuildCache_1.saveCcacheAsync)({
|
|
225
225
|
logger: ctx.logger,
|
|
226
226
|
workingDirectory,
|
|
227
|
-
platform: ctx.job.platform,
|
|
227
|
+
target: { platform: ctx.job.platform, simulator: ctx.job.simulator === true },
|
|
228
228
|
evictUsedBefore,
|
|
229
229
|
env: ctx.env,
|
|
230
230
|
secrets: ctx.job.secrets,
|
|
@@ -20,8 +20,10 @@ const generateGymfileFromTemplate_1 = require("./functions/generateGymfileFromTe
|
|
|
20
20
|
const getCredentialsForBuildTriggeredByGitHubIntegration_1 = require("./functions/getCredentialsForBuildTriggeredByGitHubIntegration");
|
|
21
21
|
const injectAndroidCredentials_1 = require("./functions/injectAndroidCredentials");
|
|
22
22
|
const installMaestro_1 = require("./functions/installMaestro");
|
|
23
|
+
const installBuild_1 = require("./functions/installBuild");
|
|
23
24
|
const installNodeModules_1 = require("./functions/installNodeModules");
|
|
24
25
|
const installPods_1 = require("./functions/installPods");
|
|
26
|
+
const launchApplication_1 = require("./functions/launchApplication");
|
|
25
27
|
const prebuild_1 = require("./functions/prebuild");
|
|
26
28
|
const readAppConfig_1 = require("./functions/readAppConfig");
|
|
27
29
|
const readIpaInfo_1 = require("./functions/readIpaInfo");
|
|
@@ -70,6 +72,8 @@ function getEasFunctions(ctx) {
|
|
|
70
72
|
(0, prebuild_1.createPrebuildBuildFunction)(),
|
|
71
73
|
(0, readIpaInfo_1.createReadIpaInfoBuildFunction)(),
|
|
72
74
|
(0, downloadBuild_1.createDownloadBuildFunction)(ctx),
|
|
75
|
+
(0, installBuild_1.createInstallBuildFunction)(),
|
|
76
|
+
(0, launchApplication_1.createLaunchApplicationFunction)(),
|
|
73
77
|
(0, export_1.createEasExportBuildFunction)(),
|
|
74
78
|
(0, deploy_1.createEasDeployBuildFunction)(),
|
|
75
79
|
(0, repack_1.createRepackBuildFunction)(),
|
|
@@ -2,13 +2,20 @@ import { bunyan } from '@expo/logger';
|
|
|
2
2
|
import { BuildFunction } from '@expo/steps';
|
|
3
3
|
import { Client } from '@urql/core';
|
|
4
4
|
import { CustomBuildContext } from '../../customBuildContext';
|
|
5
|
+
type DownloadBuildSource = {
|
|
6
|
+
buildId: string;
|
|
7
|
+
applicationArchiveUrl?: never;
|
|
8
|
+
} | {
|
|
9
|
+
buildId?: never;
|
|
10
|
+
applicationArchiveUrl: string;
|
|
11
|
+
};
|
|
5
12
|
export declare function createDownloadBuildFunction(ctx: CustomBuildContext): BuildFunction;
|
|
6
|
-
export declare function downloadBuildAsync(
|
|
13
|
+
export declare function downloadBuildAsync(params: DownloadBuildSource & {
|
|
7
14
|
logger: bunyan;
|
|
8
|
-
buildId: string;
|
|
9
15
|
graphqlClient: Client;
|
|
10
16
|
robotAccessToken: string | null;
|
|
11
17
|
extensions: string[];
|
|
12
18
|
}): Promise<{
|
|
13
19
|
artifactPath: string;
|
|
14
20
|
}>;
|
|
21
|
+
export {};
|
|
@@ -8,9 +8,11 @@ exports.downloadBuildAsync = downloadBuildAsync;
|
|
|
8
8
|
const eas_build_job_1 = require("@expo/eas-build-job");
|
|
9
9
|
const results_1 = require("@expo/results");
|
|
10
10
|
const steps_1 = require("@expo/steps");
|
|
11
|
+
const content_disposition_1 = __importDefault(require("content-disposition"));
|
|
11
12
|
const fast_glob_1 = require("fast-glob");
|
|
12
13
|
const gql_tada_1 = require("gql.tada");
|
|
13
14
|
const node_fetch_1 = __importDefault(require("node-fetch"));
|
|
15
|
+
const node_assert_1 = __importDefault(require("node:assert"));
|
|
14
16
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
15
17
|
const node_os_1 = __importDefault(require("node:os"));
|
|
16
18
|
const node_path_1 = __importDefault(require("node:path"));
|
|
@@ -44,7 +46,12 @@ function createDownloadBuildFunction(ctx) {
|
|
|
44
46
|
inputProviders: [
|
|
45
47
|
steps_1.BuildStepInput.createProvider({
|
|
46
48
|
id: 'build_id',
|
|
47
|
-
required:
|
|
49
|
+
required: false,
|
|
50
|
+
allowedValueTypeName: steps_1.BuildStepInputValueTypeName.STRING,
|
|
51
|
+
}),
|
|
52
|
+
steps_1.BuildStepInput.createProvider({
|
|
53
|
+
id: 'application_archive_url',
|
|
54
|
+
required: false,
|
|
48
55
|
allowedValueTypeName: steps_1.BuildStepInputValueTypeName.STRING,
|
|
49
56
|
}),
|
|
50
57
|
steps_1.BuildStepInput.createProvider({
|
|
@@ -64,11 +71,29 @@ function createDownloadBuildFunction(ctx) {
|
|
|
64
71
|
const { logger } = stepsCtx;
|
|
65
72
|
const extensions = zod_1.z.array(zod_1.z.string()).parse(inputs.extensions.value);
|
|
66
73
|
logger.info(`Expected extensions: [${extensions.join(', ')}]`);
|
|
67
|
-
const buildId =
|
|
68
|
-
|
|
74
|
+
const buildId = inputs.build_id.value
|
|
75
|
+
? zod_1.z.string().uuid().parse(inputs.build_id.value)
|
|
76
|
+
: undefined;
|
|
77
|
+
const applicationArchiveUrl = inputs.application_archive_url.value
|
|
78
|
+
? parseHttpApplicationArchiveUrl(inputs.application_archive_url.value)
|
|
79
|
+
: undefined;
|
|
80
|
+
let source;
|
|
81
|
+
if (buildId) {
|
|
82
|
+
if (applicationArchiveUrl) {
|
|
83
|
+
throw new eas_build_job_1.UserError('EAS_DOWNLOAD_BUILD_INVALID_SOURCE', 'Pass only one of build_id or application_archive_url.');
|
|
84
|
+
}
|
|
85
|
+
source = { buildId };
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
if (!applicationArchiveUrl) {
|
|
89
|
+
throw new eas_build_job_1.UserError('EAS_DOWNLOAD_BUILD_INVALID_SOURCE', 'Pass build_id or application_archive_url.');
|
|
90
|
+
}
|
|
91
|
+
source = { applicationArchiveUrl };
|
|
92
|
+
}
|
|
93
|
+
logger.info(buildId ? `Downloading build ${buildId}...` : `Downloading application archive...`);
|
|
69
94
|
const { artifactPath } = await downloadBuildAsync({
|
|
70
95
|
logger,
|
|
71
|
-
|
|
96
|
+
...source,
|
|
72
97
|
graphqlClient: ctx.graphqlClient,
|
|
73
98
|
robotAccessToken: stepsCtx.global.staticContext.job.secrets?.robotAccessToken ?? null,
|
|
74
99
|
extensions,
|
|
@@ -92,21 +117,32 @@ async function fetchApplicationArchiveUrlAsync({ buildId, graphqlClient, }) {
|
|
|
92
117
|
}
|
|
93
118
|
return applicationArchiveUrl;
|
|
94
119
|
}
|
|
95
|
-
async function downloadBuildAsync(
|
|
120
|
+
async function downloadBuildAsync(params) {
|
|
121
|
+
const { logger, graphqlClient, robotAccessToken, extensions } = params;
|
|
122
|
+
let downloadUrl;
|
|
123
|
+
let headers;
|
|
124
|
+
if (params.applicationArchiveUrl) {
|
|
125
|
+
if (params.buildId) {
|
|
126
|
+
throw new eas_build_job_1.UserError('EAS_DOWNLOAD_BUILD_INVALID_SOURCE', 'Pass only one of buildId or applicationArchiveUrl.');
|
|
127
|
+
}
|
|
128
|
+
downloadUrl = parseHttpApplicationArchiveUrl(params.applicationArchiveUrl);
|
|
129
|
+
headers = undefined;
|
|
130
|
+
}
|
|
131
|
+
else if (params.buildId) {
|
|
132
|
+
const buildId = zod_1.z.string().uuid().parse(params.buildId);
|
|
133
|
+
downloadUrl = await fetchApplicationArchiveUrlAsync({ buildId, graphqlClient });
|
|
134
|
+
headers = robotAccessToken ? { Authorization: `Bearer ${robotAccessToken}` } : undefined;
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
throw new eas_build_job_1.UserError('EAS_DOWNLOAD_BUILD_INVALID_SOURCE', 'Pass buildId or applicationArchiveUrl.');
|
|
138
|
+
}
|
|
96
139
|
const downloadDestinationDirectory = await node_fs_1.default.promises.mkdtemp(node_path_1.default.join(node_os_1.default.tmpdir(), 'download_build-downloaded-'));
|
|
97
|
-
const
|
|
98
|
-
const response = await (0, retryOnDNSFailure_1.retryOnDNSFailure)(node_fetch_1.default)(downloadUrl, {
|
|
99
|
-
headers: robotAccessToken ? { Authorization: `Bearer ${robotAccessToken}` } : undefined,
|
|
100
|
-
});
|
|
140
|
+
const response = await (0, retryOnDNSFailure_1.retryOnDNSFailure)(node_fetch_1.default)(downloadUrl, { headers });
|
|
101
141
|
if (!response.ok) {
|
|
102
142
|
const textResult = await (0, results_1.asyncResult)(response.text());
|
|
103
143
|
throw new Error(`Unexpected response from server (${response.status}): ${textResult.value}`);
|
|
104
144
|
}
|
|
105
|
-
|
|
106
|
-
// this replaces all non-alphanumeric characters (excluding dot) with underscore
|
|
107
|
-
const archiveFilename = node_path_1.default
|
|
108
|
-
.basename(new URL(response.url).pathname)
|
|
109
|
-
.replace(/([^a-z0-9.-]+)/gi, '_');
|
|
145
|
+
const archiveFilename = resolveArchiveFilename({ response, extensions });
|
|
110
146
|
const archivePath = node_path_1.default.join(downloadDestinationDirectory, archiveFilename);
|
|
111
147
|
await streamPipeline(response.body, node_fs_1.default.createWriteStream(archivePath));
|
|
112
148
|
const { size } = await node_fs_1.default.promises.stat(archivePath);
|
|
@@ -133,3 +169,36 @@ async function downloadBuildAsync({ logger, buildId, graphqlClient, robotAccessT
|
|
|
133
169
|
logger.info(`Found ${matchingFiles.length} matching ${(0, strings_1.pluralize)(matchingFiles.length, 'entry')}:\n${matchingFiles.map(f => `- ${node_path_1.default.relative(extractionDirectory, f)}`).join('\n')}`);
|
|
134
170
|
return { artifactPath: matchingFiles[0] };
|
|
135
171
|
}
|
|
172
|
+
function parseHttpApplicationArchiveUrl(value) {
|
|
173
|
+
try {
|
|
174
|
+
const applicationArchiveUrl = zod_1.z.string().parse(value);
|
|
175
|
+
const parsedUrl = new URL(applicationArchiveUrl);
|
|
176
|
+
(0, node_assert_1.default)(parsedUrl.protocol === 'http:' || parsedUrl.protocol === 'https:');
|
|
177
|
+
return applicationArchiveUrl;
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
throw new eas_build_job_1.UserError('EAS_DOWNLOAD_BUILD_INVALID_APPLICATION_ARCHIVE_URL', 'application_archive_url must be a valid HTTP or HTTPS URL.');
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function resolveArchiveFilename({ response, extensions, }) {
|
|
184
|
+
const contentDispositionHeader = response.headers.get('content-disposition');
|
|
185
|
+
let headerFilename;
|
|
186
|
+
if (contentDispositionHeader) {
|
|
187
|
+
try {
|
|
188
|
+
headerFilename = content_disposition_1.default.parse(contentDispositionHeader).parameters.filename;
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
// Ignore malformed Content-Disposition headers and fall back to the response URL.
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const urlFilename = node_path_1.default.basename(new URL(response.url).pathname);
|
|
195
|
+
let archiveFilename = node_path_1.default.basename(headerFilename ?? urlFilename ?? '');
|
|
196
|
+
if (!archiveFilename || archiveFilename === '.' || archiveFilename === '..') {
|
|
197
|
+
archiveFilename = 'application';
|
|
198
|
+
}
|
|
199
|
+
if (!node_path_1.default.extname(archiveFilename) && extensions.length === 1) {
|
|
200
|
+
archiveFilename = `${archiveFilename}.${extensions[0]}`;
|
|
201
|
+
}
|
|
202
|
+
// URL and header filenames may contain percent-encoded or unsafe filesystem characters.
|
|
203
|
+
return archiveFilename.replace(/([^a-z0-9.-]+)/gi, '_');
|
|
204
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { bunyan } from '@expo/logger';
|
|
2
|
+
import { BuildFunction, BuildRuntimePlatform, BuildStepEnv } from '@expo/steps';
|
|
3
|
+
export declare function createInstallBuildFunction(): BuildFunction;
|
|
4
|
+
export declare function installBuildAsync({ artifactPath, runtimePlatform, env, logger, }: {
|
|
5
|
+
artifactPath: string;
|
|
6
|
+
runtimePlatform: BuildRuntimePlatform;
|
|
7
|
+
env: BuildStepEnv;
|
|
8
|
+
logger: bunyan;
|
|
9
|
+
}): Promise<{
|
|
10
|
+
applicationIdentifier: string;
|
|
11
|
+
activityName?: string;
|
|
12
|
+
}>;
|
|
@@ -0,0 +1,85 @@
|
|
|
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.createInstallBuildFunction = createInstallBuildFunction;
|
|
7
|
+
exports.installBuildAsync = installBuildAsync;
|
|
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
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
12
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
13
|
+
const zod_1 = require("zod");
|
|
14
|
+
function createInstallBuildFunction() {
|
|
15
|
+
return new steps_1.BuildFunction({
|
|
16
|
+
namespace: 'eas',
|
|
17
|
+
id: 'install_build',
|
|
18
|
+
name: 'Install build',
|
|
19
|
+
__metricsId: 'eas/install_build',
|
|
20
|
+
inputProviders: [
|
|
21
|
+
steps_1.BuildStepInput.createProvider({
|
|
22
|
+
id: 'artifact_path',
|
|
23
|
+
required: true,
|
|
24
|
+
allowedValueTypeName: steps_1.BuildStepInputValueTypeName.STRING,
|
|
25
|
+
}),
|
|
26
|
+
],
|
|
27
|
+
outputProviders: [
|
|
28
|
+
steps_1.BuildStepOutput.createProvider({
|
|
29
|
+
id: 'application_identifier',
|
|
30
|
+
required: true,
|
|
31
|
+
}),
|
|
32
|
+
steps_1.BuildStepOutput.createProvider({
|
|
33
|
+
id: 'activity_name',
|
|
34
|
+
required: false,
|
|
35
|
+
}),
|
|
36
|
+
],
|
|
37
|
+
fn: async ({ global, logger }, { inputs, outputs, env }) => {
|
|
38
|
+
const artifactPath = zod_1.z.string().min(1).parse(inputs.artifact_path.value);
|
|
39
|
+
const { applicationIdentifier, activityName } = await installBuildAsync({
|
|
40
|
+
artifactPath,
|
|
41
|
+
runtimePlatform: global.runtimePlatform,
|
|
42
|
+
env,
|
|
43
|
+
logger,
|
|
44
|
+
});
|
|
45
|
+
outputs.application_identifier.set(applicationIdentifier);
|
|
46
|
+
if (activityName) {
|
|
47
|
+
outputs.activity_name.set(activityName);
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
async function installBuildAsync({ artifactPath, runtimePlatform, env, logger, }) {
|
|
53
|
+
const artifactStat = await node_fs_1.default.promises.stat(artifactPath).catch(err => {
|
|
54
|
+
throw new eas_build_job_1.UserError('EAS_INSTALL_BUILD_INVALID_ARTIFACT', `Build artifact does not exist at ${artifactPath}.`, { cause: err });
|
|
55
|
+
});
|
|
56
|
+
if (runtimePlatform === steps_1.BuildRuntimePlatform.DARWIN) {
|
|
57
|
+
if (node_path_1.default.extname(artifactPath) !== '.app' || !artifactStat.isDirectory()) {
|
|
58
|
+
throw new eas_build_job_1.UserError('EAS_INSTALL_BUILD_INVALID_ARTIFACT', 'iOS Simulator sessions require a .app build artifact.');
|
|
59
|
+
}
|
|
60
|
+
const infoPlistPath = node_path_1.default.join(artifactPath, 'Info.plist');
|
|
61
|
+
const { stdout } = await (0, turtle_spawn_1.default)('plutil', ['-extract', 'CFBundleIdentifier', 'raw', '-o', '-', infoPlistPath], { stdio: 'pipe', env });
|
|
62
|
+
const applicationIdentifier = stdout.trim();
|
|
63
|
+
if (!applicationIdentifier) {
|
|
64
|
+
throw new eas_build_job_1.UserError('EAS_INSTALL_BUILD_MISSING_IDENTIFIER', `Could not read CFBundleIdentifier from ${infoPlistPath}.`);
|
|
65
|
+
}
|
|
66
|
+
logger.info(`Installing ${artifactPath} on the iOS Simulator.`);
|
|
67
|
+
await (0, turtle_spawn_1.default)('xcrun', ['simctl', 'install', 'booted', artifactPath], { env, logger });
|
|
68
|
+
return { applicationIdentifier };
|
|
69
|
+
}
|
|
70
|
+
if (node_path_1.default.extname(artifactPath) !== '.apk' || !artifactStat.isFile()) {
|
|
71
|
+
throw new eas_build_job_1.UserError('EAS_INSTALL_BUILD_INVALID_ARTIFACT', 'Android Emulator sessions require an .apk build artifact.');
|
|
72
|
+
}
|
|
73
|
+
const { stdout } = await (0, turtle_spawn_1.default)('aapt2', ['dump', 'badging', artifactPath], {
|
|
74
|
+
stdio: 'pipe',
|
|
75
|
+
env,
|
|
76
|
+
});
|
|
77
|
+
const applicationIdentifier = stdout.match(/package: name='([^']+)'/)?.[1];
|
|
78
|
+
const activityName = stdout.match(/launchable-activity: name='([^']+)'/)?.[1];
|
|
79
|
+
if (!applicationIdentifier) {
|
|
80
|
+
throw new eas_build_job_1.UserError('EAS_INSTALL_BUILD_MISSING_IDENTIFIER', `Could not read an Android application identifier from ${artifactPath}.`);
|
|
81
|
+
}
|
|
82
|
+
logger.info(`Installing ${artifactPath} on the Android Emulator.`);
|
|
83
|
+
await (0, turtle_spawn_1.default)('adb', ['install', '-r', artifactPath], { env, logger });
|
|
84
|
+
return { applicationIdentifier, activityName };
|
|
85
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { bunyan } from '@expo/logger';
|
|
2
|
+
import { BuildFunction, BuildRuntimePlatform, BuildStepEnv } from '@expo/steps';
|
|
3
|
+
export declare function createLaunchApplicationFunction(): BuildFunction;
|
|
4
|
+
export declare function launchApplicationAsync({ applicationIdentifier, activityName, runtimePlatform, env, logger, }: {
|
|
5
|
+
applicationIdentifier: string;
|
|
6
|
+
activityName?: string;
|
|
7
|
+
runtimePlatform: BuildRuntimePlatform;
|
|
8
|
+
env: BuildStepEnv;
|
|
9
|
+
logger: bunyan;
|
|
10
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,67 @@
|
|
|
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
|
+
],
|
|
29
|
+
fn: async ({ global, logger }, { inputs, env }) => {
|
|
30
|
+
const applicationIdentifier = parseNonEmptyStringInput(inputs.application_identifier.value, 'application_identifier');
|
|
31
|
+
const activityName = inputs.activity_name.value === undefined
|
|
32
|
+
? undefined
|
|
33
|
+
: parseNonEmptyStringInput(inputs.activity_name.value, 'activity_name');
|
|
34
|
+
await launchApplicationAsync({
|
|
35
|
+
applicationIdentifier,
|
|
36
|
+
activityName,
|
|
37
|
+
runtimePlatform: global.runtimePlatform,
|
|
38
|
+
env,
|
|
39
|
+
logger,
|
|
40
|
+
});
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
async function launchApplicationAsync({ applicationIdentifier, activityName, runtimePlatform, env, logger, }) {
|
|
45
|
+
if (runtimePlatform === steps_1.BuildRuntimePlatform.DARWIN) {
|
|
46
|
+
logger.info(`Launching ${applicationIdentifier}.`);
|
|
47
|
+
await (0, turtle_spawn_1.default)('xcrun', ['simctl', 'launch', 'booted', applicationIdentifier], {
|
|
48
|
+
env,
|
|
49
|
+
logger,
|
|
50
|
+
});
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (!activityName) {
|
|
54
|
+
throw new eas_build_job_1.UserError('EAS_LAUNCH_APPLICATION_MISSING_ACTIVITY', 'Launching an Android application requires activity_name.');
|
|
55
|
+
}
|
|
56
|
+
logger.info(`Launching ${applicationIdentifier}.`);
|
|
57
|
+
await (0, turtle_spawn_1.default)('adb', ['shell', 'am', 'start', '-n', `${applicationIdentifier}/${activityName}`], {
|
|
58
|
+
env,
|
|
59
|
+
logger,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
function parseNonEmptyStringInput(value, inputName) {
|
|
63
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
64
|
+
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.`);
|
|
65
|
+
}
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
@@ -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
|
},
|
|
@@ -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;
|
|
@@ -69,7 +69,7 @@ const sentry_1 = require("../../sentry");
|
|
|
69
69
|
const retry_1 = require("../../utils/retry");
|
|
70
70
|
const turtleFetch_1 = require("../../utils/turtleFetch");
|
|
71
71
|
const XCODE_DEVELOPER_DIR = '/Applications/Xcode.app/Contents/Developer';
|
|
72
|
-
const
|
|
72
|
+
const SERVE_SIM_PACKAGE_NAME = '@expo/serve-sim';
|
|
73
73
|
const SERVE_SIM_HOST = '127.0.0.1';
|
|
74
74
|
const SERVE_SIM_MAX_DIMENSION = '1280';
|
|
75
75
|
const SERVE_SIM_MJPEG_QUALITY = '0.55';
|
|
@@ -473,10 +473,13 @@ function metricsCorsOriginToServeSimArgs(env) {
|
|
|
473
473
|
}
|
|
474
474
|
return args;
|
|
475
475
|
}
|
|
476
|
-
function
|
|
476
|
+
function createServeSimPackageSpec(packageVersion) {
|
|
477
|
+
return `${SERVE_SIM_PACKAGE_NAME}@${packageVersion ?? 'latest'}`;
|
|
478
|
+
}
|
|
479
|
+
function createServeSimArgs({ port, turnArgs = [], metricsCorsArgs = [], packageVersion, }) {
|
|
477
480
|
return [
|
|
478
481
|
'--yes',
|
|
479
|
-
|
|
482
|
+
createServeSimPackageSpec(packageVersion),
|
|
480
483
|
'--port',
|
|
481
484
|
String(port),
|
|
482
485
|
'--host',
|
|
@@ -540,14 +543,14 @@ async function waitForServeSimReadyAsync({ serveSim, port, timeoutMs, }) {
|
|
|
540
543
|
}
|
|
541
544
|
throw new eas_build_job_1.SystemError(`Timed out waiting for serve-sim readiness at ${readyUrl}${lastError instanceof Error ? `: ${lastError.message}` : ''}. Last output:\n${serveSim.getOutput() || '<empty>'}`);
|
|
542
545
|
}
|
|
543
|
-
async function startServeSimWithTunnelAsync(ctx, { baseDomain, env, logger, timeoutMs, }) {
|
|
546
|
+
async function startServeSimWithTunnelAsync(ctx, { baseDomain, env, logger, timeoutMs, packageVersion, }) {
|
|
544
547
|
const port = await findAvailablePortAsync();
|
|
545
|
-
logger.info(`Launching ${
|
|
548
|
+
logger.info(`Launching ${createServeSimPackageSpec(packageVersion)} on ${SERVE_SIM_HOST}:${port}.`);
|
|
546
549
|
const turnArgs = await fetchServeSimTurnArgsAsync(ctx, { env, logger });
|
|
547
550
|
const metricsCorsArgs = metricsCorsOriginToServeSimArgs(env);
|
|
548
551
|
const serveSim = spawnDetached({
|
|
549
552
|
command: 'npx',
|
|
550
|
-
args: createServeSimArgs({ port, turnArgs, metricsCorsArgs }),
|
|
553
|
+
args: createServeSimArgs({ port, turnArgs, metricsCorsArgs, packageVersion }),
|
|
551
554
|
env,
|
|
552
555
|
});
|
|
553
556
|
try {
|
package/dist/utils/cacheKey.d.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { Platform } from '@expo/eas-build-job';
|
|
2
|
-
export
|
|
2
|
+
export type CcacheBuildTarget = {
|
|
3
|
+
platform: Platform.ANDROID;
|
|
4
|
+
} | {
|
|
5
|
+
platform: Platform.IOS;
|
|
6
|
+
simulator: boolean;
|
|
7
|
+
};
|
|
8
|
+
export declare function getCcacheKeyPrefix(target: CcacheBuildTarget): string;
|
|
3
9
|
export declare const PUBLIC_CACHE_KEY_PREFIX_BY_PLATFORM: Record<Platform, string>;
|
|
4
10
|
export declare function getCcachePath(env: Record<string, string | undefined>): string;
|
|
5
|
-
export declare function generateDefaultBuildCacheKeyAsync(workingDirectory: string,
|
|
11
|
+
export declare function generateDefaultBuildCacheKeyAsync(workingDirectory: string, target: CcacheBuildTarget): Promise<string>;
|
package/dist/utils/cacheKey.js
CHANGED
|
@@ -36,7 +36,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.PUBLIC_CACHE_KEY_PREFIX_BY_PLATFORM =
|
|
39
|
+
exports.PUBLIC_CACHE_KEY_PREFIX_BY_PLATFORM = void 0;
|
|
40
|
+
exports.getCcacheKeyPrefix = getCcacheKeyPrefix;
|
|
40
41
|
exports.getCcachePath = getCcachePath;
|
|
41
42
|
exports.generateDefaultBuildCacheKeyAsync = generateDefaultBuildCacheKeyAsync;
|
|
42
43
|
const eas_build_job_1 = require("@expo/eas-build-job");
|
|
@@ -46,16 +47,19 @@ const assert_1 = __importDefault(require("assert"));
|
|
|
46
47
|
const os_1 = __importDefault(require("os"));
|
|
47
48
|
const path_1 = __importDefault(require("path"));
|
|
48
49
|
const packageManager_1 = require("./packageManager");
|
|
49
|
-
const
|
|
50
|
+
const IOS_DEVICE_CACHE_KEY_PREFIX = 'ios-device-ccache-';
|
|
51
|
+
const IOS_SIMULATOR_CACHE_KEY_PREFIX = 'ios-simulator-ccache-';
|
|
50
52
|
const ANDROID_CACHE_KEY_PREFIX = 'android-ccache-';
|
|
51
53
|
const PUBLIC_IOS_CACHE_KEY_PREFIX = 'public-ios-ccache-';
|
|
52
54
|
const PUBLIC_ANDROID_CACHE_KEY_PREFIX = 'public-android-ccache-';
|
|
53
55
|
const DARWIN_CACHE_PATH = 'Library/Caches/ccache';
|
|
54
56
|
const LINUX_CACHE_PATH = '.cache/ccache';
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
57
|
+
function getCcacheKeyPrefix(target) {
|
|
58
|
+
if (target.platform === eas_build_job_1.Platform.IOS) {
|
|
59
|
+
return target.simulator ? IOS_SIMULATOR_CACHE_KEY_PREFIX : IOS_DEVICE_CACHE_KEY_PREFIX;
|
|
60
|
+
}
|
|
61
|
+
return ANDROID_CACHE_KEY_PREFIX;
|
|
62
|
+
}
|
|
59
63
|
exports.PUBLIC_CACHE_KEY_PREFIX_BY_PLATFORM = {
|
|
60
64
|
[eas_build_job_1.Platform.ANDROID]: PUBLIC_ANDROID_CACHE_KEY_PREFIX,
|
|
61
65
|
[eas_build_job_1.Platform.IOS]: PUBLIC_IOS_CACHE_KEY_PREFIX,
|
|
@@ -68,14 +72,14 @@ function getCcachePath(env) {
|
|
|
68
72
|
(0, assert_1.default)(env.HOME, 'Failed to infer directory: $HOME environment variable is empty.');
|
|
69
73
|
return path_1.default.join(env.HOME, PATH_BY_PLATFORM[os_1.default.platform()]);
|
|
70
74
|
}
|
|
71
|
-
async function generateDefaultBuildCacheKeyAsync(workingDirectory,
|
|
75
|
+
async function generateDefaultBuildCacheKeyAsync(workingDirectory, target) {
|
|
72
76
|
// This will resolve which package manager and use the relevant lock file
|
|
73
77
|
// The lock file hash is the key and ensures cache is fresh
|
|
74
78
|
const packagerRunDir = (0, packageManager_1.findPackagerRootDir)(workingDirectory);
|
|
75
79
|
const manager = PackageManagerUtils.createForProject(packagerRunDir);
|
|
76
80
|
const lockPath = path_1.default.join(packagerRunDir, manager.lockFile);
|
|
77
81
|
try {
|
|
78
|
-
return `${
|
|
82
|
+
return `${getCcacheKeyPrefix(target)}${(0, steps_1.hashFiles)([lockPath])}`;
|
|
79
83
|
}
|
|
80
84
|
catch (err) {
|
|
81
85
|
throw new Error(`Failed to read lockfile for cache key generation: ${err.message}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@expo/build-tools",
|
|
3
|
-
"version": "22.
|
|
3
|
+
"version": "22.3.0",
|
|
4
4
|
"bugs": "https://github.com/expo/eas-cli/issues",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Expo <support@expo.io>",
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
"@sentry/node": "7.77.0",
|
|
55
55
|
"@urql/core": "^6.0.1",
|
|
56
56
|
"bplist-parser": "0.3.2",
|
|
57
|
+
"content-disposition": "1.0.1",
|
|
57
58
|
"fast-glob": "^3.3.2",
|
|
58
59
|
"fast-xml-parser": "^4.4.1",
|
|
59
60
|
"fs-extra": "^11.2.0",
|
|
@@ -77,7 +78,8 @@
|
|
|
77
78
|
"zod": "^4.3.5"
|
|
78
79
|
},
|
|
79
80
|
"devDependencies": {
|
|
80
|
-
"@expo/repack-app": "~0.
|
|
81
|
+
"@expo/repack-app": "~0.9.0",
|
|
82
|
+
"@types/content-disposition": "0.5.9",
|
|
81
83
|
"@types/fs-extra": "^11.0.4",
|
|
82
84
|
"@types/jest": "^29.5.12",
|
|
83
85
|
"@types/lodash": "^4.17.4",
|
|
@@ -100,5 +102,5 @@
|
|
|
100
102
|
"typescript": "^5.5.4",
|
|
101
103
|
"uuid": "^9.0.1"
|
|
102
104
|
},
|
|
103
|
-
"gitHead": "
|
|
105
|
+
"gitHead": "70066fc8beedabdd31081f18c18928461d400806"
|
|
104
106
|
}
|