@expo/build-tools 24.1.0 → 24.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/common/git.d.ts +1 -0
- package/dist/common/projectSources.js +25 -19
- package/dist/steps/easFunctions.js +2 -0
- package/dist/steps/functions/startSandbox.d.ts +4 -0
- package/dist/steps/functions/startSandbox.js +93 -0
- package/dist/steps/utils/sandboxDaemon.d.ts +13 -0
- package/dist/steps/utils/sandboxDaemon.js +90 -0
- package/package.json +6 -4
package/dist/common/git.d.ts
CHANGED
|
@@ -19,28 +19,21 @@ const zod_1 = require("zod");
|
|
|
19
19
|
const git_1 = require("./git");
|
|
20
20
|
const turtleFetch_1 = require("../utils/turtleFetch");
|
|
21
21
|
async function prepareProjectSourcesAsync(ctx, destinationDirectory) {
|
|
22
|
-
let projectArchive = ctx.job.projectArchive;
|
|
23
22
|
if (ctx.isLocal) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
else {
|
|
27
|
-
const projectArchiveResult = await (0, results_1.asyncResult)(fetchProjectArchiveSourceAsync(ctx));
|
|
28
|
-
if (!projectArchiveResult.ok) {
|
|
29
|
-
throw new eas_build_job_1.SystemError('Failed to fetch project sources. Re-run the job.', {
|
|
30
|
-
cause: projectArchiveResult.reason,
|
|
31
|
-
});
|
|
23
|
+
if (ctx.job.projectArchive.type !== eas_build_job_1.ArchiveSourceType.PATH) {
|
|
24
|
+
throw new eas_build_job_1.SystemError(`Expected a PATH project source for a local build, received ${ctx.job.projectArchive.type}.`, { trackingCode: 'INVALID_LOCAL_PROJECT_SOURCE' });
|
|
32
25
|
}
|
|
33
|
-
|
|
26
|
+
await prepareProjectSourcesLocallyAsync(ctx, ctx.job.projectArchive.path, destinationDirectory);
|
|
27
|
+
return { handled: true };
|
|
28
|
+
}
|
|
29
|
+
const projectArchiveResult = await (0, results_1.asyncResult)(fetchProjectArchiveSourceAsync(ctx));
|
|
30
|
+
if (!projectArchiveResult.ok) {
|
|
31
|
+
throw new eas_build_job_1.SystemError('Failed to fetch project sources. Re-run the job.', {
|
|
32
|
+
cause: projectArchiveResult.reason,
|
|
33
|
+
});
|
|
34
34
|
}
|
|
35
|
+
const projectArchive = projectArchiveResult.value;
|
|
35
36
|
switch (projectArchive.type) {
|
|
36
|
-
case eas_build_job_1.ArchiveSourceType.R2:
|
|
37
|
-
case eas_build_job_1.ArchiveSourceType.GCS: {
|
|
38
|
-
throw new Error('Remote project sources should be resolved earlier to URL');
|
|
39
|
-
}
|
|
40
|
-
case eas_build_job_1.ArchiveSourceType.PATH: {
|
|
41
|
-
await prepareProjectSourcesLocallyAsync(ctx, projectArchive.path, destinationDirectory); // used in eas build --local
|
|
42
|
-
return { handled: true };
|
|
43
|
-
}
|
|
44
37
|
case eas_build_job_1.ArchiveSourceType.NONE: {
|
|
45
38
|
// May be used in no-sources jobs like submission jobs.
|
|
46
39
|
return { handled: true };
|
|
@@ -207,7 +200,20 @@ async function fetchProjectArchiveSourceAsync(ctx) {
|
|
|
207
200
|
if (!jsonResult.ok) {
|
|
208
201
|
throw new Error(`Expected JSON response from server (${response.status}): ${jsonResult.reason}`);
|
|
209
202
|
}
|
|
210
|
-
const dataResult = zod_1.z
|
|
203
|
+
const dataResult = zod_1.z
|
|
204
|
+
.object({
|
|
205
|
+
data: zod_1.z.discriminatedUnion('type', [
|
|
206
|
+
zod_1.z.object({
|
|
207
|
+
type: zod_1.z.literal(eas_build_job_1.ArchiveSourceType.GIT),
|
|
208
|
+
repositoryUrl: zod_1.z.string().url(),
|
|
209
|
+
gitRef: zod_1.z.string().nullable(),
|
|
210
|
+
gitCommitHash: zod_1.z.string(),
|
|
211
|
+
}),
|
|
212
|
+
zod_1.z.object({ type: zod_1.z.literal(eas_build_job_1.ArchiveSourceType.URL), url: zod_1.z.string().url() }),
|
|
213
|
+
zod_1.z.object({ type: zod_1.z.literal(eas_build_job_1.ArchiveSourceType.NONE) }),
|
|
214
|
+
]),
|
|
215
|
+
})
|
|
216
|
+
.safeParse(jsonResult.value);
|
|
211
217
|
if (!dataResult.success) {
|
|
212
218
|
throw new Error(`Unexpected data from server (${response.status}): ${zod_1.z.prettifyError(dataResult.error)}`);
|
|
213
219
|
}
|
|
@@ -52,6 +52,7 @@ const startIosSimulatorRecordings_1 = require("./functions/startIosSimulatorReco
|
|
|
52
52
|
const startLocalEgress_1 = require("./functions/startLocalEgress");
|
|
53
53
|
const startWebPreviewRemoteSession_1 = require("./functions/startWebPreviewRemoteSession");
|
|
54
54
|
const startServeSimMetrics_1 = require("./functions/startServeSimMetrics");
|
|
55
|
+
const startSandbox_1 = require("./functions/startSandbox");
|
|
55
56
|
const collectServeSimMetrics_1 = require("./functions/collectServeSimMetrics");
|
|
56
57
|
const uploadArtifact_1 = require("./functions/uploadArtifact");
|
|
57
58
|
const uploadDeviceRunSessionScreenRecordings_1 = require("./functions/uploadDeviceRunSessionScreenRecordings");
|
|
@@ -106,6 +107,7 @@ function getEasFunctions(ctx) {
|
|
|
106
107
|
(0, uploadDeviceRunSessionScreenRecordings_1.createUploadDeviceRunSessionScreenRecordingsBuildFunction)(ctx),
|
|
107
108
|
(0, startWebPreviewRemoteSession_1.createStartWebPreviewRemoteSessionBuildFunction)(ctx),
|
|
108
109
|
(0, startServeSimMetrics_1.createStartServeSimMetricsBuildFunction)(),
|
|
110
|
+
(0, startSandbox_1.createStartSandboxBuildFunction)(ctx),
|
|
109
111
|
(0, collectServeSimMetrics_1.createCollectServeSimMetricsBuildFunction)(ctx),
|
|
110
112
|
(0, installMaestro_1.createInstallMaestroBuildFunction)(),
|
|
111
113
|
(0, installPods_1.createInstallPodsBuildFunction)(),
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { BuildFunction } from '@expo/steps';
|
|
2
|
+
import { CustomBuildContext } from '../../customBuildContext';
|
|
3
|
+
export declare function createStartSandboxBuildFunction(ctx: CustomBuildContext): BuildFunction;
|
|
4
|
+
export declare function markSandboxReadyAsync(ctx: CustomBuildContext, sandboxId: string, signal?: AbortSignal): Promise<void>;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createStartSandboxBuildFunction = createStartSandboxBuildFunction;
|
|
4
|
+
exports.markSandboxReadyAsync = markSandboxReadyAsync;
|
|
5
|
+
const eas_build_job_1 = require("@expo/eas-build-job");
|
|
6
|
+
const steps_1 = require("@expo/steps");
|
|
7
|
+
const gql_tada_1 = require("gql.tada");
|
|
8
|
+
const logPhase_1 = require("../../utils/logPhase");
|
|
9
|
+
const sandboxDaemon_1 = require("../utils/sandboxDaemon");
|
|
10
|
+
const MARK_SANDBOX_READY_MUTATION = (0, gql_tada_1.graphql)(`
|
|
11
|
+
mutation MarkSandboxReady($sandboxId: ID!) {
|
|
12
|
+
sandbox {
|
|
13
|
+
markSandboxReady(sandboxId: $sandboxId) {
|
|
14
|
+
id
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
`);
|
|
19
|
+
const RECONNECT_DELAY_MS = 1_000;
|
|
20
|
+
const AWAIT_SANDBOX_STEP_NAME = 'Await sandbox completion';
|
|
21
|
+
function createStartSandboxBuildFunction(ctx) {
|
|
22
|
+
return new steps_1.BuildFunction({
|
|
23
|
+
namespace: 'eas',
|
|
24
|
+
id: 'start_sandbox',
|
|
25
|
+
name: 'Start sandbox daemon',
|
|
26
|
+
__metricsId: 'eas/start_sandbox',
|
|
27
|
+
inputProviders: [
|
|
28
|
+
steps_1.BuildStepInput.createProvider({
|
|
29
|
+
id: 'sandbox_id',
|
|
30
|
+
required: true,
|
|
31
|
+
allowedValueTypeName: steps_1.BuildStepInputValueTypeName.STRING,
|
|
32
|
+
}),
|
|
33
|
+
],
|
|
34
|
+
fn: async (stepCtx, { inputs, signal }) => {
|
|
35
|
+
const sandboxToken = ctx.env.__EAS_SANDBOX_MCP_TOKEN;
|
|
36
|
+
if (!sandboxToken) {
|
|
37
|
+
throw new eas_build_job_1.SystemError('__EAS_SANDBOX_MCP_TOKEN is required to start the sandbox daemon.');
|
|
38
|
+
}
|
|
39
|
+
const mcpServerUrl = ctx.mcpServerUrl;
|
|
40
|
+
if (!mcpServerUrl) {
|
|
41
|
+
throw new eas_build_job_1.SystemError('MCP server URL is required to start the sandbox daemon.');
|
|
42
|
+
}
|
|
43
|
+
const sandboxId = String(inputs.sandbox_id.value);
|
|
44
|
+
const daemon = await (0, sandboxDaemon_1.startSandboxDaemonAsync)({
|
|
45
|
+
credential: sandboxToken,
|
|
46
|
+
serverUrl: mcpServerUrl,
|
|
47
|
+
reconnectDelayMs: RECONNECT_DELAY_MS,
|
|
48
|
+
logger: stepCtx.logger,
|
|
49
|
+
signal,
|
|
50
|
+
});
|
|
51
|
+
try {
|
|
52
|
+
await daemon.ready;
|
|
53
|
+
signal?.throwIfAborted();
|
|
54
|
+
await markSandboxReadyAsync(ctx, sandboxId, signal);
|
|
55
|
+
signal?.throwIfAborted();
|
|
56
|
+
stepCtx.logger.info('Sandbox daemon started.');
|
|
57
|
+
await (0, logPhase_1.withLogPhaseAsync)(stepCtx.logger, AWAIT_SANDBOX_STEP_NAME, async () => {
|
|
58
|
+
await waitForCancellationAsync(signal);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
await daemon.stopAsync();
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
async function markSandboxReadyAsync(ctx, sandboxId, signal) {
|
|
68
|
+
signal?.throwIfAborted();
|
|
69
|
+
const result = await ctx.graphqlClient
|
|
70
|
+
.mutation(MARK_SANDBOX_READY_MUTATION, { sandboxId }, signal
|
|
71
|
+
? {
|
|
72
|
+
fetch: (input, init) => fetch(input, {
|
|
73
|
+
...init,
|
|
74
|
+
signal: init?.signal ? AbortSignal.any([signal, init.signal]) : signal,
|
|
75
|
+
}),
|
|
76
|
+
}
|
|
77
|
+
: undefined)
|
|
78
|
+
.toPromise();
|
|
79
|
+
signal?.throwIfAborted();
|
|
80
|
+
if (result.error) {
|
|
81
|
+
throw new eas_build_job_1.SystemError(`Failed to mark sandbox ${sandboxId} as ready.`, {
|
|
82
|
+
cause: result.error,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async function waitForCancellationAsync(signal) {
|
|
87
|
+
if (signal?.aborted) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
await new Promise(resolve => {
|
|
91
|
+
signal?.addEventListener('abort', () => resolve(), { once: true });
|
|
92
|
+
});
|
|
93
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type bunyan } from '@expo/logger';
|
|
2
|
+
export interface SandboxDaemonOptions {
|
|
3
|
+
credential: string;
|
|
4
|
+
serverUrl: string;
|
|
5
|
+
reconnectDelayMs: number;
|
|
6
|
+
logger: bunyan;
|
|
7
|
+
signal?: AbortSignal;
|
|
8
|
+
}
|
|
9
|
+
export interface SandboxDaemon {
|
|
10
|
+
ready: Promise<void>;
|
|
11
|
+
stopAsync(): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
export declare function startSandboxDaemonAsync(options: SandboxDaemonOptions): Promise<SandboxDaemon>;
|
|
@@ -0,0 +1,90 @@
|
|
|
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.startSandboxDaemonAsync = startSandboxDaemonAsync;
|
|
7
|
+
const eas_build_job_1 = require("@expo/eas-build-job");
|
|
8
|
+
const promises_1 = require("node:timers/promises");
|
|
9
|
+
const ws_1 = __importDefault(require("ws"));
|
|
10
|
+
async function startSandboxDaemonAsync(options) {
|
|
11
|
+
options.signal?.throwIfAborted();
|
|
12
|
+
let socket;
|
|
13
|
+
const abortController = new AbortController();
|
|
14
|
+
let hasConnected = false;
|
|
15
|
+
let resolveConnected;
|
|
16
|
+
let rejectConnected;
|
|
17
|
+
const connected = new Promise((resolve, reject) => {
|
|
18
|
+
resolveConnected = resolve;
|
|
19
|
+
rejectConnected = reject;
|
|
20
|
+
});
|
|
21
|
+
const stop = () => {
|
|
22
|
+
abortController.abort();
|
|
23
|
+
socket?.close(1000, 'sandbox stopped');
|
|
24
|
+
};
|
|
25
|
+
options.signal?.addEventListener('abort', stop, { once: true });
|
|
26
|
+
const connectionLoop = (async () => {
|
|
27
|
+
while (!abortController.signal.aborted) {
|
|
28
|
+
try {
|
|
29
|
+
socket = new ws_1.default(new URL('/sandbox/connect', options.serverUrl), {
|
|
30
|
+
handshakeTimeout: 10_000,
|
|
31
|
+
headers: { Authorization: `Bearer ${options.credential}` },
|
|
32
|
+
});
|
|
33
|
+
await waitForOpen(socket);
|
|
34
|
+
options.logger.info('Sandbox MCP server connected.');
|
|
35
|
+
hasConnected = true;
|
|
36
|
+
resolveConnected();
|
|
37
|
+
await waitForClose(socket);
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
if (!hasConnected) {
|
|
41
|
+
const message = `Sandbox MCP server connection failed: ${error?.message ?? 'unknown error'}`;
|
|
42
|
+
if (!abortController.signal.aborted) {
|
|
43
|
+
options.logger.error({ err: error }, message);
|
|
44
|
+
}
|
|
45
|
+
rejectConnected(new eas_build_job_1.SystemError(message, { cause: error }));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (!abortController.signal.aborted) {
|
|
49
|
+
options.logger.warn({ err: error }, `Sandbox MCP server connection failed: ${error?.message ?? 'unknown error'}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (!abortController.signal.aborted) {
|
|
53
|
+
try {
|
|
54
|
+
await (0, promises_1.setTimeout)(options.reconnectDelayMs, undefined, {
|
|
55
|
+
signal: abortController.signal,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
if (!abortController.signal.aborted) {
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
})();
|
|
66
|
+
return {
|
|
67
|
+
ready: connected,
|
|
68
|
+
async stopAsync() {
|
|
69
|
+
options.signal?.removeEventListener('abort', stop);
|
|
70
|
+
stop();
|
|
71
|
+
await connectionLoop;
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function waitForOpen(socket) {
|
|
76
|
+
return new Promise((resolve, reject) => {
|
|
77
|
+
socket.once('open', resolve);
|
|
78
|
+
socket.once('error', reject);
|
|
79
|
+
socket.once('close', () => reject(new Error('Sandbox MCP server closed before it connected.')));
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
function waitForClose(socket) {
|
|
83
|
+
if (socket.readyState === ws_1.default.CLOSING || socket.readyState === ws_1.default.CLOSED) {
|
|
84
|
+
return Promise.resolve();
|
|
85
|
+
}
|
|
86
|
+
return new Promise((resolve, reject) => {
|
|
87
|
+
socket.once('close', resolve);
|
|
88
|
+
socket.once('error', reject);
|
|
89
|
+
});
|
|
90
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@expo/build-tools",
|
|
3
|
-
"version": "24.
|
|
3
|
+
"version": "24.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>",
|
|
@@ -40,14 +40,14 @@
|
|
|
40
40
|
"@expo/config": "55.0.10",
|
|
41
41
|
"@expo/config-plugins": "55.0.7",
|
|
42
42
|
"@expo/downloader": "24.0.0",
|
|
43
|
-
"@expo/eas-build-job": "24.
|
|
43
|
+
"@expo/eas-build-job": "24.3.0",
|
|
44
44
|
"@expo/env": "^0.4.0",
|
|
45
45
|
"@expo/logger": "24.0.0",
|
|
46
46
|
"@expo/package-manager": "1.9.10",
|
|
47
47
|
"@expo/plist": "^0.3.5",
|
|
48
48
|
"@expo/results": "^1.0.0",
|
|
49
49
|
"@expo/spawn-async": "1.7.2",
|
|
50
|
-
"@expo/steps": "24.
|
|
50
|
+
"@expo/steps": "24.3.0",
|
|
51
51
|
"@expo/template-file": "24.0.0",
|
|
52
52
|
"@expo/turtle-spawn": "24.0.0",
|
|
53
53
|
"@expo/xcpretty": "^4.3.1",
|
|
@@ -75,6 +75,7 @@
|
|
|
75
75
|
"semver": "^7.6.2",
|
|
76
76
|
"tar": "7.5.19",
|
|
77
77
|
"uuid": "9.0.1",
|
|
78
|
+
"ws": "8.21.1",
|
|
78
79
|
"yaml": "^2.8.1",
|
|
79
80
|
"zod": "^4.3.5"
|
|
80
81
|
},
|
|
@@ -92,6 +93,7 @@
|
|
|
92
93
|
"@types/retry": "^0.12.5",
|
|
93
94
|
"@types/semver": "^7.5.8",
|
|
94
95
|
"@types/uuid": "^9.0.8",
|
|
96
|
+
"@types/ws": "8.5.10",
|
|
95
97
|
"jest": "^29.7.0",
|
|
96
98
|
"memfs": "^4.17.1",
|
|
97
99
|
"nock": "13.4.0",
|
|
@@ -102,5 +104,5 @@
|
|
|
102
104
|
"typescript": "^5.5.4",
|
|
103
105
|
"uuid": "^9.0.1"
|
|
104
106
|
},
|
|
105
|
-
"gitHead": "
|
|
107
|
+
"gitHead": "66cdccb0e7c3ec47054c3f093c25b9799d60ff36"
|
|
106
108
|
}
|