@expo/build-tools 20.5.1 → 21.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/android/gradle.d.ts +5 -0
- package/dist/android/gradle.js +13 -33
- package/dist/builders/custom.js +1 -0
- package/dist/common/installDependencies.js +21 -18
- package/dist/context.js +31 -1
- package/dist/customBuildContext.d.ts +2 -1
- package/dist/customBuildContext.js +4 -0
- package/dist/generic.js +1 -0
- package/dist/steps/easFunctions.js +18 -0
- package/dist/steps/functions/capturePosthogEvent.d.ts +2 -0
- package/dist/steps/functions/capturePosthogEvent.js +79 -0
- package/dist/steps/functions/checkout.js +1 -0
- package/dist/steps/functions/createPosthogAnnotation.d.ts +2 -0
- package/dist/steps/functions/createPosthogAnnotation.js +75 -0
- package/dist/steps/functions/finishIosSimulatorRecordings.d.ts +2 -0
- package/dist/steps/functions/finishIosSimulatorRecordings.js +24 -0
- package/dist/steps/functions/installNodeModules.js +1 -0
- package/dist/steps/functions/maestroTests.js +1 -0
- package/dist/steps/functions/rolloutPosthogFlag.d.ts +2 -0
- package/dist/steps/functions/rolloutPosthogFlag.js +208 -0
- package/dist/steps/functions/startAgentDeviceRemoteSession.js +21 -8
- package/dist/steps/functions/startArgentRemoteSession.d.ts +14 -0
- package/dist/steps/functions/startArgentRemoteSession.js +105 -44
- package/dist/steps/functions/startIosSimulatorRecordings.d.ts +2 -0
- package/dist/steps/functions/startIosSimulatorRecordings.js +20 -0
- package/dist/steps/functions/startServeSimRemoteSession.js +7 -5
- package/dist/steps/functions/uploadDeviceRunSessionScreenRecordings.d.ts +3 -0
- package/dist/steps/functions/uploadDeviceRunSessionScreenRecordings.js +91 -0
- package/dist/steps/functions/uploadPosthogSourcemaps.d.ts +2 -0
- package/dist/steps/functions/uploadPosthogSourcemaps.js +83 -0
- package/dist/steps/functions/uploadToAsc.js +1 -0
- package/dist/steps/functions/waitForPosthogMetric.d.ts +2 -0
- package/dist/steps/functions/waitForPosthogMetric.js +135 -0
- package/dist/steps/functions/waitForPosthogQuery.d.ts +2 -0
- package/dist/steps/functions/waitForPosthogQuery.js +95 -0
- package/dist/steps/utils/IosSimulatorRecordingUtils.d.ts +17 -0
- package/dist/steps/utils/IosSimulatorRecordingUtils.js +218 -0
- package/dist/steps/utils/PosthogClient.d.ts +46 -0
- package/dist/steps/utils/PosthogClient.js +156 -0
- package/dist/steps/utils/PosthogUtils.d.ts +41 -0
- package/dist/steps/utils/PosthogUtils.js +65 -0
- package/dist/steps/utils/agentDeviceArtifacts.d.ts +27 -0
- package/dist/steps/utils/agentDeviceArtifacts.js +133 -0
- package/dist/steps/utils/android/gradle.d.ts +5 -0
- package/dist/steps/utils/android/gradle.js +13 -62
- package/dist/steps/utils/argentArtifacts.d.ts +5 -3
- package/dist/steps/utils/argentArtifacts.js +95 -23
- package/dist/steps/utils/deviceRunSessionArtifacts.d.ts +3 -1
- package/dist/steps/utils/deviceRunSessionArtifacts.js +6 -2
- package/dist/steps/utils/remoteDeviceRunSession.d.ts +8 -0
- package/dist/steps/utils/remoteDeviceRunSession.js +62 -1
- package/dist/utils/AndroidEmulatorUtils.js +3 -0
- package/dist/utils/IosSimulatorUtils.d.ts +1 -0
- package/dist/utils/IosSimulatorUtils.js +41 -2
- package/dist/utils/expoUpdatesEmbedded.js +4 -0
- package/dist/utils/hookMetrics.d.ts +8 -0
- package/dist/utils/hookMetrics.js +18 -0
- package/dist/utils/processes.d.ts +1 -0
- package/dist/utils/processes.js +23 -0
- package/package.json +10 -9
- package/resources/record-sim/Package.swift +28 -0
- package/resources/record-sim/README.md +79 -0
- package/resources/record-sim/Sources/RecordSim/FramebufferDisplaySource.swift +192 -0
- package/resources/record-sim/Sources/RecordSim/PrivateSimulatorServices.swift +96 -0
- package/resources/record-sim/Sources/RecordSim/RecordingModels.swift +70 -0
- package/resources/record-sim/Sources/RecordSim/RecordingOutputWriter.swift +136 -0
- package/resources/record-sim/Sources/RecordSim/SimulatorRecorder.swift +632 -0
- package/resources/record-sim/Sources/RecordSim/Utilities.swift +60 -0
- package/resources/record-sim/Sources/record-sim/main.swift +143 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { bunyan } from '@expo/logger';
|
|
2
|
+
import { type BuildStepEnv } from '@expo/steps';
|
|
3
|
+
import { Response } from 'node-fetch';
|
|
4
|
+
import { PosthogUtils } from './PosthogUtils';
|
|
5
|
+
export declare class PosthogRetryableError extends Error {
|
|
6
|
+
}
|
|
7
|
+
export declare class PosthogClient {
|
|
8
|
+
private readonly host;
|
|
9
|
+
private readonly apiKey;
|
|
10
|
+
private readonly projectId;
|
|
11
|
+
private constructor();
|
|
12
|
+
static fromEnv({ apiKeyOverride, projectIdOverride, env, }: {
|
|
13
|
+
apiKeyOverride: string | undefined;
|
|
14
|
+
projectIdOverride: string | undefined;
|
|
15
|
+
env: BuildStepEnv;
|
|
16
|
+
}): {
|
|
17
|
+
client: PosthogClient;
|
|
18
|
+
} | {
|
|
19
|
+
client: undefined;
|
|
20
|
+
missing: PosthogUtils.Credential[];
|
|
21
|
+
};
|
|
22
|
+
static forIngestion({ apiKeyOverride, hostOverride, env, }: {
|
|
23
|
+
apiKeyOverride: string | undefined;
|
|
24
|
+
hostOverride: string | undefined;
|
|
25
|
+
env: BuildStepEnv;
|
|
26
|
+
}): PosthogClient | undefined;
|
|
27
|
+
captureEventAsync({ event, distinctId, properties, signal, }: {
|
|
28
|
+
event: string;
|
|
29
|
+
distinctId: string | undefined;
|
|
30
|
+
properties: Record<string, unknown> | undefined;
|
|
31
|
+
signal: AbortSignal | undefined;
|
|
32
|
+
}): Promise<void>;
|
|
33
|
+
requestAsync(method: 'GET' | 'POST' | 'PATCH', path: string, { action, forbiddenScope, body, signal, }: {
|
|
34
|
+
action: string;
|
|
35
|
+
forbiddenScope: string;
|
|
36
|
+
body?: unknown;
|
|
37
|
+
signal: AbortSignal | undefined;
|
|
38
|
+
}): Promise<Response>;
|
|
39
|
+
runQueryAsync(query: string, logger: bunyan, signal: AbortSignal | undefined): Promise<unknown>;
|
|
40
|
+
cliConfig(): {
|
|
41
|
+
host: string;
|
|
42
|
+
env: Record<string, string>;
|
|
43
|
+
};
|
|
44
|
+
private apiUrl;
|
|
45
|
+
private authHeaders;
|
|
46
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
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.PosthogClient = exports.PosthogRetryableError = void 0;
|
|
7
|
+
const eas_build_job_1 = require("@expo/eas-build-job");
|
|
8
|
+
const node_fetch_1 = __importDefault(require("node-fetch"));
|
|
9
|
+
const zod_1 = require("zod");
|
|
10
|
+
const PosthogUtils_1 = require("./PosthogUtils");
|
|
11
|
+
const POSTHOG_DEFAULT_HOST = 'https://us.posthog.com';
|
|
12
|
+
const SYSTEM_DISTINCT_ID = 'eas-workflow';
|
|
13
|
+
const QueryResponseSchema = zod_1.z.object({ results: zod_1.z.array(zod_1.z.array(zod_1.z.unknown())) });
|
|
14
|
+
class PosthogRetryableError extends Error {
|
|
15
|
+
}
|
|
16
|
+
exports.PosthogRetryableError = PosthogRetryableError;
|
|
17
|
+
class PosthogClient {
|
|
18
|
+
host;
|
|
19
|
+
apiKey;
|
|
20
|
+
projectId;
|
|
21
|
+
constructor(host, apiKey, projectId) {
|
|
22
|
+
this.host = host;
|
|
23
|
+
this.apiKey = apiKey;
|
|
24
|
+
this.projectId = projectId;
|
|
25
|
+
}
|
|
26
|
+
static fromEnv({ apiKeyOverride, projectIdOverride, env, }) {
|
|
27
|
+
const apiKey = apiKeyOverride || env.POSTHOG_CLI_API_KEY;
|
|
28
|
+
const projectId = projectIdOverride || env.POSTHOG_CLI_PROJECT_ID;
|
|
29
|
+
if (!apiKey || !projectId) {
|
|
30
|
+
return {
|
|
31
|
+
client: undefined,
|
|
32
|
+
missing: [
|
|
33
|
+
...(apiKey ? [] : [PosthogUtils_1.PosthogUtils.API_CREDENTIALS.apiKey]),
|
|
34
|
+
...(projectId ? [] : [PosthogUtils_1.PosthogUtils.API_CREDENTIALS.projectId]),
|
|
35
|
+
],
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
client: new PosthogClient((env.POSTHOG_CLI_HOST || POSTHOG_DEFAULT_HOST).replace(/\/+$/, ''), apiKey, projectId),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
static forIngestion({ apiKeyOverride, hostOverride, env, }) {
|
|
43
|
+
const apiKey = apiKeyOverride || env.EXPO_PUBLIC_POSTHOG_API_KEY || env.POSTHOG_API_KEY;
|
|
44
|
+
if (!apiKey) {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
return new PosthogClient((hostOverride || env.EXPO_PUBLIC_POSTHOG_HOST || POSTHOG_DEFAULT_HOST).replace(/\/+$/, ''), apiKey, '');
|
|
48
|
+
}
|
|
49
|
+
async captureEventAsync({ event, distinctId, properties, signal, }) {
|
|
50
|
+
let response;
|
|
51
|
+
try {
|
|
52
|
+
response = await (0, node_fetch_1.default)(`${this.host}/i/v0/e/`, {
|
|
53
|
+
method: 'POST',
|
|
54
|
+
headers: { 'Content-Type': 'application/json' },
|
|
55
|
+
body: JSON.stringify({
|
|
56
|
+
api_key: this.apiKey,
|
|
57
|
+
event,
|
|
58
|
+
distinct_id: distinctId ?? SYSTEM_DISTINCT_ID,
|
|
59
|
+
properties: distinctId !== undefined
|
|
60
|
+
? properties
|
|
61
|
+
: { ...properties, $process_person_profile: false },
|
|
62
|
+
timestamp: new Date().toISOString(),
|
|
63
|
+
}),
|
|
64
|
+
signal,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
if (signal?.aborted) {
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
throw new eas_build_job_1.UserError('EAS_POSTHOG_CAPTURE_FAILED', `Sending PostHog event "${event}" failed.`, {
|
|
72
|
+
cause: error,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
if (!response.ok) {
|
|
76
|
+
throw new eas_build_job_1.UserError('EAS_POSTHOG_CAPTURE_FAILED', `Sending PostHog event "${event}" failed with ${await PosthogUtils_1.PosthogUtils.readErrorAsync(response)}.`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async requestAsync(method, path, { action, forbiddenScope, body, signal, }) {
|
|
80
|
+
let response;
|
|
81
|
+
try {
|
|
82
|
+
response = await (0, node_fetch_1.default)(this.apiUrl(path), {
|
|
83
|
+
method,
|
|
84
|
+
headers: this.authHeaders(),
|
|
85
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
86
|
+
signal,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
if (signal?.aborted) {
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
throw new eas_build_job_1.UserError('EAS_POSTHOG_REQUEST_FAILED', `${action} failed.`, { cause: error });
|
|
94
|
+
}
|
|
95
|
+
if (response.status === 403) {
|
|
96
|
+
throw new eas_build_job_1.UserError('EAS_POSTHOG_FORBIDDEN', `${action} was forbidden (403). The personal API key likely lacks the "${forbiddenScope}" scope.`);
|
|
97
|
+
}
|
|
98
|
+
if (!response.ok) {
|
|
99
|
+
throw new eas_build_job_1.UserError('EAS_POSTHOG_REQUEST_FAILED', `${action} failed with ${await PosthogUtils_1.PosthogUtils.readErrorAsync(response)}.`);
|
|
100
|
+
}
|
|
101
|
+
return response;
|
|
102
|
+
}
|
|
103
|
+
async runQueryAsync(query, logger, signal) {
|
|
104
|
+
let response;
|
|
105
|
+
try {
|
|
106
|
+
response = await (0, node_fetch_1.default)(this.apiUrl('/query/'), {
|
|
107
|
+
method: 'POST',
|
|
108
|
+
headers: this.authHeaders(),
|
|
109
|
+
body: JSON.stringify({ query: { kind: 'HogQLQuery', query }, refresh: 'blocking' }),
|
|
110
|
+
signal,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
if (signal?.aborted) {
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
logger.debug(error);
|
|
118
|
+
logger.warn('Running the PostHog query failed with a network error; will retry on the next poll.');
|
|
119
|
+
throw new PosthogRetryableError();
|
|
120
|
+
}
|
|
121
|
+
if (response.status === 403) {
|
|
122
|
+
throw new eas_build_job_1.UserError('EAS_POSTHOG_FORBIDDEN', 'Running the PostHog query was forbidden (403). The personal API key likely lacks the "query:read" scope.');
|
|
123
|
+
}
|
|
124
|
+
if (response.status >= 500 || response.status === 429) {
|
|
125
|
+
logger.warn(`Running the PostHog query failed with status ${response.status}; will retry on the next poll.`);
|
|
126
|
+
throw new PosthogRetryableError();
|
|
127
|
+
}
|
|
128
|
+
if (!response.ok) {
|
|
129
|
+
throw new eas_build_job_1.UserError('EAS_POSTHOG_REQUEST_FAILED', `Running the PostHog query failed with ${await PosthogUtils_1.PosthogUtils.readErrorAsync(response)}.`);
|
|
130
|
+
}
|
|
131
|
+
let body;
|
|
132
|
+
try {
|
|
133
|
+
body = await response.json();
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
logger.debug(error);
|
|
137
|
+
logger.warn('The PostHog query response was not valid JSON; will retry on the next poll.');
|
|
138
|
+
throw new PosthogRetryableError();
|
|
139
|
+
}
|
|
140
|
+
const parsed = QueryResponseSchema.safeParse(body);
|
|
141
|
+
return parsed.success ? parsed.data.results[0]?.[0] : undefined;
|
|
142
|
+
}
|
|
143
|
+
cliConfig() {
|
|
144
|
+
return {
|
|
145
|
+
host: this.host,
|
|
146
|
+
env: { POSTHOG_CLI_API_KEY: this.apiKey, POSTHOG_CLI_PROJECT_ID: this.projectId },
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
apiUrl(path) {
|
|
150
|
+
return `${this.host}/api/projects/${encodeURIComponent(this.projectId)}${path}`;
|
|
151
|
+
}
|
|
152
|
+
authHeaders() {
|
|
153
|
+
return { Authorization: `Bearer ${this.apiKey}`, 'Content-Type': 'application/json' };
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
exports.PosthogClient = PosthogClient;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { UserError } from '@expo/eas-build-job';
|
|
2
|
+
import { bunyan } from '@expo/logger';
|
|
3
|
+
import { Response } from 'node-fetch';
|
|
4
|
+
export declare namespace PosthogUtils {
|
|
5
|
+
interface Credential {
|
|
6
|
+
label: string;
|
|
7
|
+
envVar: string;
|
|
8
|
+
input: string;
|
|
9
|
+
}
|
|
10
|
+
const API_CREDENTIALS: {
|
|
11
|
+
apiKey: {
|
|
12
|
+
label: string;
|
|
13
|
+
envVar: string;
|
|
14
|
+
input: string;
|
|
15
|
+
};
|
|
16
|
+
projectId: {
|
|
17
|
+
label: string;
|
|
18
|
+
envVar: string;
|
|
19
|
+
input: string;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
function missingCredentialsError(missing: Credential[]): UserError;
|
|
23
|
+
const DEFAULT_POLL_TIMEOUT_SECONDS = 600;
|
|
24
|
+
const DEFAULT_POLL_INTERVAL_SECONDS = 30;
|
|
25
|
+
function assertPollBoundsPositive({ timeoutSeconds, intervalSeconds, errorCode, }: {
|
|
26
|
+
timeoutSeconds: number;
|
|
27
|
+
intervalSeconds: number;
|
|
28
|
+
errorCode: string;
|
|
29
|
+
}): void;
|
|
30
|
+
function waitForNextPollAsync({ intervalSeconds, deadline, signal, }: {
|
|
31
|
+
intervalSeconds: number;
|
|
32
|
+
deadline: number;
|
|
33
|
+
signal: AbortSignal | undefined;
|
|
34
|
+
}): Promise<boolean>;
|
|
35
|
+
function failOrLogError({ logger, ignoreError, error, }: {
|
|
36
|
+
logger: bunyan;
|
|
37
|
+
ignoreError: boolean;
|
|
38
|
+
error: unknown;
|
|
39
|
+
}): void;
|
|
40
|
+
function readErrorAsync(response: Response): Promise<string>;
|
|
41
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PosthogUtils = void 0;
|
|
4
|
+
const promises_1 = require("node:timers/promises");
|
|
5
|
+
const eas_build_job_1 = require("@expo/eas-build-job");
|
|
6
|
+
var PosthogUtils;
|
|
7
|
+
(function (PosthogUtils) {
|
|
8
|
+
PosthogUtils.API_CREDENTIALS = {
|
|
9
|
+
apiKey: { label: 'personal API key', envVar: 'POSTHOG_CLI_API_KEY', input: 'api_key' },
|
|
10
|
+
projectId: { label: 'project id', envVar: 'POSTHOG_CLI_PROJECT_ID', input: 'project_id' },
|
|
11
|
+
};
|
|
12
|
+
function missingCredentialsError(missing) {
|
|
13
|
+
const labels = missing.map(credential => credential.label).join(', ');
|
|
14
|
+
const envVars = missing.map(credential => credential.envVar).join(', ');
|
|
15
|
+
const inputs = missing.map(credential => credential.input).join(', ');
|
|
16
|
+
return new eas_build_job_1.UserError('EAS_POSTHOG_MISSING_CREDENTIALS', `Missing PostHog credentials: ${labels}. Set the environment variables (${envVars}) or step inputs (${inputs}) on EAS, or re-run "eas integrations:posthog:connect" with error tracking enabled.`);
|
|
17
|
+
}
|
|
18
|
+
PosthogUtils.missingCredentialsError = missingCredentialsError;
|
|
19
|
+
PosthogUtils.DEFAULT_POLL_TIMEOUT_SECONDS = 600;
|
|
20
|
+
PosthogUtils.DEFAULT_POLL_INTERVAL_SECONDS = 30;
|
|
21
|
+
function assertPollBoundsPositive({ timeoutSeconds, intervalSeconds, errorCode, }) {
|
|
22
|
+
if (timeoutSeconds <= 0 || intervalSeconds <= 0) {
|
|
23
|
+
throw new eas_build_job_1.UserError(errorCode, '"timeout_seconds" and "interval_seconds" must be greater than 0.');
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
PosthogUtils.assertPollBoundsPositive = assertPollBoundsPositive;
|
|
27
|
+
async function waitForNextPollAsync({ intervalSeconds, deadline, signal, }) {
|
|
28
|
+
const remainingMs = deadline - Date.now();
|
|
29
|
+
if (remainingMs <= 0) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
await (0, promises_1.setTimeout)(Math.min(intervalSeconds * 1000, remainingMs), undefined, { signal });
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
PosthogUtils.waitForNextPollAsync = waitForNextPollAsync;
|
|
36
|
+
function failOrLogError({ logger, ignoreError, error, }) {
|
|
37
|
+
if (!ignoreError ||
|
|
38
|
+
(error instanceof eas_build_job_1.UserError && error.errorCode === 'EAS_POSTHOG_FORBIDDEN')) {
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
42
|
+
logger.warn({ err: error }, `${message} Ignoring error.`);
|
|
43
|
+
}
|
|
44
|
+
PosthogUtils.failOrLogError = failOrLogError;
|
|
45
|
+
async function readErrorAsync(response) {
|
|
46
|
+
const text = await response.text().catch(() => '');
|
|
47
|
+
if (text) {
|
|
48
|
+
try {
|
|
49
|
+
const body = JSON.parse(text);
|
|
50
|
+
if (typeof body === 'object' && body !== null) {
|
|
51
|
+
const { detail } = body;
|
|
52
|
+
if (typeof detail === 'string' && detail !== '') {
|
|
53
|
+
return `status ${response.status}: ${detail} (${text})`;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return `status ${response.status}: ${text}`;
|
|
59
|
+
}
|
|
60
|
+
return `status ${response.status}: ${text}`;
|
|
61
|
+
}
|
|
62
|
+
return `status ${response.status}`;
|
|
63
|
+
}
|
|
64
|
+
PosthogUtils.readErrorAsync = readErrorAsync;
|
|
65
|
+
})(PosthogUtils || (exports.PosthogUtils = PosthogUtils = {}));
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type bunyan } from '@expo/logger';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { CustomBuildContext } from '../../customBuildContext';
|
|
4
|
+
declare const AgentDeviceArtifactSchema: z.ZodObject<{
|
|
5
|
+
id: z.ZodString;
|
|
6
|
+
artifactType: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
7
|
+
filename: z.ZodString;
|
|
8
|
+
}, z.core.$strip>;
|
|
9
|
+
export type AgentDeviceArtifact = z.infer<typeof AgentDeviceArtifactSchema>;
|
|
10
|
+
export declare function pollAgentDeviceArtifactsForUploadAsync(ctx: CustomBuildContext, { deviceRunSessionId, daemonUrl, daemonToken, logger, }: {
|
|
11
|
+
deviceRunSessionId: string;
|
|
12
|
+
daemonUrl: string;
|
|
13
|
+
daemonToken: string;
|
|
14
|
+
logger: bunyan;
|
|
15
|
+
}): Promise<void>;
|
|
16
|
+
export declare function listAgentDeviceArtifactsAsync({ daemonUrl, daemonToken, }: {
|
|
17
|
+
daemonUrl: string;
|
|
18
|
+
daemonToken: string;
|
|
19
|
+
}): Promise<AgentDeviceArtifact[]>;
|
|
20
|
+
export declare function uploadAgentDeviceArtifactAsync(ctx: CustomBuildContext, { deviceRunSessionId, daemonUrl, daemonToken, artifact, logger, }: {
|
|
21
|
+
deviceRunSessionId: string;
|
|
22
|
+
daemonUrl: string;
|
|
23
|
+
daemonToken: string;
|
|
24
|
+
artifact: AgentDeviceArtifact;
|
|
25
|
+
logger: bunyan;
|
|
26
|
+
}): Promise<void>;
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,133 @@
|
|
|
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.pollAgentDeviceArtifactsForUploadAsync = pollAgentDeviceArtifactsForUploadAsync;
|
|
7
|
+
exports.listAgentDeviceArtifactsAsync = listAgentDeviceArtifactsAsync;
|
|
8
|
+
exports.uploadAgentDeviceArtifactAsync = uploadAgentDeviceArtifactAsync;
|
|
9
|
+
const eas_build_job_1 = require("@expo/eas-build-job");
|
|
10
|
+
const node_fs_1 = require("node:fs");
|
|
11
|
+
const promises_1 = require("node:fs/promises");
|
|
12
|
+
const node_fetch_1 = __importDefault(require("node-fetch"));
|
|
13
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
14
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
15
|
+
const promises_2 = require("node:stream/promises");
|
|
16
|
+
const zod_1 = require("zod");
|
|
17
|
+
const sentry_1 = require("../../sentry");
|
|
18
|
+
const artifacts_1 = require("../../utils/artifacts");
|
|
19
|
+
const retry_1 = require("../../utils/retry");
|
|
20
|
+
const deviceRunSessionArtifacts_1 = require("./deviceRunSessionArtifacts");
|
|
21
|
+
const AGENT_DEVICE_ARTIFACT_UPLOAD_POLL_INTERVAL_MS = 5_000;
|
|
22
|
+
const AgentDeviceArtifactSchema = zod_1.z.object({
|
|
23
|
+
id: zod_1.z.string(),
|
|
24
|
+
artifactType: zod_1.z.string().nullish(),
|
|
25
|
+
filename: zod_1.z.string(),
|
|
26
|
+
});
|
|
27
|
+
const AgentDeviceArtifactsListResponseSchema = zod_1.z.object({
|
|
28
|
+
artifacts: zod_1.z.array(AgentDeviceArtifactSchema),
|
|
29
|
+
});
|
|
30
|
+
class AgentDeviceArtifactsUnsupportedError extends eas_build_job_1.SystemError {
|
|
31
|
+
constructor() {
|
|
32
|
+
super('agent-device daemon does not expose artifact inventory.');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async function pollAgentDeviceArtifactsForUploadAsync(ctx, { deviceRunSessionId, daemonUrl, daemonToken, logger, }) {
|
|
36
|
+
logger.info('Started polling agent-device daemon for artifacts.');
|
|
37
|
+
const uploadedArtifactIds = new Set();
|
|
38
|
+
let listArtifactsErrorCount = 0;
|
|
39
|
+
for (;;) {
|
|
40
|
+
try {
|
|
41
|
+
const artifacts = await listAgentDeviceArtifactsAsync({ daemonUrl, daemonToken });
|
|
42
|
+
listArtifactsErrorCount = 0;
|
|
43
|
+
for (const artifact of artifacts) {
|
|
44
|
+
if (uploadedArtifactIds.has(artifact.id)) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
await uploadAgentDeviceArtifactAsync(ctx, {
|
|
49
|
+
deviceRunSessionId,
|
|
50
|
+
daemonUrl,
|
|
51
|
+
daemonToken,
|
|
52
|
+
artifact,
|
|
53
|
+
logger,
|
|
54
|
+
});
|
|
55
|
+
uploadedArtifactIds.add(artifact.id);
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
59
|
+
sentry_1.Sentry.capture('Could not upload agent-device remote session artifact', error);
|
|
60
|
+
logger.warn({ err: error }, 'Could not upload agent-device remote session artifact.');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
if (err instanceof AgentDeviceArtifactsUnsupportedError) {
|
|
66
|
+
logger.warn('agent-device daemon does not expose artifact inventory; remote session artifact uploads are disabled.');
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
70
|
+
listArtifactsErrorCount += 1;
|
|
71
|
+
if (listArtifactsErrorCount === 1 || listArtifactsErrorCount % 5 === 0) {
|
|
72
|
+
sentry_1.Sentry.capture('Could not list agent-device remote session artifacts', error);
|
|
73
|
+
logger.warn({ err: error, failedArtifactListCount: listArtifactsErrorCount }, 'Could not list agent-device remote session artifacts.');
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
await (0, retry_1.sleepAsync)(AGENT_DEVICE_ARTIFACT_UPLOAD_POLL_INTERVAL_MS);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async function listAgentDeviceArtifactsAsync({ daemonUrl, daemonToken, }) {
|
|
80
|
+
const response = await (0, node_fetch_1.default)(new URL('/artifacts', daemonUrl).toString(), {
|
|
81
|
+
headers: { Authorization: `Bearer ${daemonToken}` },
|
|
82
|
+
});
|
|
83
|
+
if (response.status === 404) {
|
|
84
|
+
throw new AgentDeviceArtifactsUnsupportedError();
|
|
85
|
+
}
|
|
86
|
+
if (!response.ok) {
|
|
87
|
+
throw new eas_build_job_1.SystemError(`Failed to list agent-device artifacts: ${response.status} ${response.statusText}`);
|
|
88
|
+
}
|
|
89
|
+
const result = AgentDeviceArtifactsListResponseSchema.safeParse(await response.json());
|
|
90
|
+
if (!result.success) {
|
|
91
|
+
throw new eas_build_job_1.SystemError(`Invalid agent-device artifacts response: ${result.error.message}`);
|
|
92
|
+
}
|
|
93
|
+
return result.data.artifacts;
|
|
94
|
+
}
|
|
95
|
+
async function uploadAgentDeviceArtifactAsync(ctx, { deviceRunSessionId, daemonUrl, daemonToken, artifact, logger, }) {
|
|
96
|
+
logger.info(`Downloading artifact ${artifact.filename}.`);
|
|
97
|
+
const temporaryDirectory = await (0, promises_1.mkdtemp)(node_path_1.default.join(node_os_1.default.tmpdir(), 'agent-device-artifact-'));
|
|
98
|
+
try {
|
|
99
|
+
const temporaryArtifactPath = node_path_1.default.join(temporaryDirectory, node_path_1.default.basename(artifact.filename));
|
|
100
|
+
await downloadAgentDeviceArtifactToFileAsync({
|
|
101
|
+
artifact,
|
|
102
|
+
daemonUrl,
|
|
103
|
+
daemonToken,
|
|
104
|
+
destinationPath: temporaryArtifactPath,
|
|
105
|
+
});
|
|
106
|
+
const { size } = await (0, promises_1.stat)(temporaryArtifactPath);
|
|
107
|
+
logger.info(`Uploading artifact ${artifact.filename} (${(0, artifacts_1.formatBytes)(size)}).`);
|
|
108
|
+
await (0, deviceRunSessionArtifacts_1.uploadDeviceRunSessionArtifactAsync)(ctx, {
|
|
109
|
+
deviceRunSessionId,
|
|
110
|
+
artifactId: artifact.id,
|
|
111
|
+
name: `${artifact.filename} (${artifact.id})`,
|
|
112
|
+
filename: artifact.filename,
|
|
113
|
+
kind: artifact.artifactType ?? undefined,
|
|
114
|
+
size,
|
|
115
|
+
stream: (0, node_fs_1.createReadStream)(temporaryArtifactPath),
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
await (0, promises_1.rm)(temporaryDirectory, { recursive: true, force: true });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async function downloadAgentDeviceArtifactToFileAsync({ artifact, daemonUrl, daemonToken, destinationPath, }) {
|
|
123
|
+
const response = await (0, node_fetch_1.default)(new URL(`/artifacts/${encodeURIComponent(artifact.id)}`, daemonUrl).toString(), {
|
|
124
|
+
headers: { Authorization: `Bearer ${daemonToken}` },
|
|
125
|
+
});
|
|
126
|
+
if (!response.ok) {
|
|
127
|
+
throw new eas_build_job_1.SystemError(`Failed to download agent-device artifact ${artifact.id}: ${response.status} ${response.statusText}`);
|
|
128
|
+
}
|
|
129
|
+
if (!response.body) {
|
|
130
|
+
throw new eas_build_job_1.SystemError(`Agent-device artifact ${artifact.id} response did not include a readable body.`);
|
|
131
|
+
}
|
|
132
|
+
await (0, promises_2.pipeline)(response.body, (0, node_fs_1.createWriteStream)(destinationPath));
|
|
133
|
+
}
|
|
@@ -8,4 +8,9 @@ export declare function runGradleCommand({ logger, gradleCommand, androidDir, en
|
|
|
8
8
|
env: BuildStepEnv;
|
|
9
9
|
extraEnv?: BuildStepEnv;
|
|
10
10
|
}): Promise<void>;
|
|
11
|
+
export declare function getGradleShellCommand({ gradleCommand, oomScoreAdj, verboseFlag, }: {
|
|
12
|
+
gradleCommand: string;
|
|
13
|
+
oomScoreAdj?: number;
|
|
14
|
+
verboseFlag: string;
|
|
15
|
+
}): string;
|
|
11
16
|
export declare function resolveGradleCommand(job: Android.Job, command?: string): string;
|
|
@@ -4,17 +4,25 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.runGradleCommand = runGradleCommand;
|
|
7
|
+
exports.getGradleShellCommand = getGradleShellCommand;
|
|
7
8
|
exports.resolveGradleCommand = resolveGradleCommand;
|
|
8
9
|
const eas_build_job_1 = require("@expo/eas-build-job");
|
|
9
10
|
const turtle_spawn_1 = __importDefault(require("@expo/turtle-spawn"));
|
|
10
|
-
const assert_1 = __importDefault(require("assert"));
|
|
11
11
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
12
12
|
const path_1 = __importDefault(require("path"));
|
|
13
13
|
async function runGradleCommand({ logger, gradleCommand, androidDir, env, extraEnv, }) {
|
|
14
14
|
const verboseFlag = env['EAS_VERBOSE'] === '1' ? '--info' : '';
|
|
15
15
|
logger.info(`Running 'gradlew ${gradleCommand} ${verboseFlag}' in ${androidDir}`);
|
|
16
16
|
await fs_extra_1.default.chmod(path_1.default.join(androidDir, 'gradlew'), 0o755);
|
|
17
|
-
const
|
|
17
|
+
const shouldResetOOMScore = env.EAS_BUILD_RUNNER === 'eas-build' && process.platform === 'linux';
|
|
18
|
+
await (0, turtle_spawn_1.default)('bash', [
|
|
19
|
+
'-c',
|
|
20
|
+
getGradleShellCommand({
|
|
21
|
+
gradleCommand,
|
|
22
|
+
oomScoreAdj: shouldResetOOMScore ? 0 : undefined,
|
|
23
|
+
verboseFlag,
|
|
24
|
+
}),
|
|
25
|
+
], {
|
|
18
26
|
cwd: androidDir,
|
|
19
27
|
logger,
|
|
20
28
|
lineTransformer: (line) => {
|
|
@@ -31,67 +39,10 @@ async function runGradleCommand({ logger, gradleCommand, androidDir, env, extraE
|
|
|
31
39
|
LC_ALL: 'C.UTF-8',
|
|
32
40
|
},
|
|
33
41
|
});
|
|
34
|
-
if (env.EAS_BUILD_RUNNER === 'eas-build' && process.platform === 'linux') {
|
|
35
|
-
adjustOOMScore(spawnPromise, logger);
|
|
36
|
-
}
|
|
37
|
-
await spawnPromise;
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* OOM Killer sometimes kills worker server while build is exceeding memory limits.
|
|
41
|
-
* `oom_score_adj` is a value between -1000 and 1000 and it defaults to 0.
|
|
42
|
-
* It defines which process is more likely to get killed (higher value more likely).
|
|
43
|
-
*
|
|
44
|
-
* This function sets oom_score_adj for Gradle process and all its child processes.
|
|
45
|
-
*/
|
|
46
|
-
function adjustOOMScore(spawnPromise, logger) {
|
|
47
|
-
setTimeout(async () => {
|
|
48
|
-
try {
|
|
49
|
-
(0, assert_1.default)(spawnPromise.child.pid);
|
|
50
|
-
const pids = await getParentAndDescendantProcessPidsAsync(spawnPromise.child.pid);
|
|
51
|
-
await Promise.all(pids.map(async (pid) => {
|
|
52
|
-
// Value 800 is just a guess here. It's probably higher than most other
|
|
53
|
-
// process. I didn't want to set it any higher, because I'm not sure if OOM Killer
|
|
54
|
-
// can start killing processes when there is still enough memory left.
|
|
55
|
-
const oomScoreOverride = 800;
|
|
56
|
-
await fs_extra_1.default.writeFile(`/proc/${pid}/oom_score_adj`, `${oomScoreOverride}\n`);
|
|
57
|
-
}));
|
|
58
|
-
}
|
|
59
|
-
catch (err) {
|
|
60
|
-
logger.debug({ err, stderr: err?.stderr }, 'Failed to override oom_score_adj');
|
|
61
|
-
}
|
|
62
|
-
},
|
|
63
|
-
// Wait 20 seconds to make sure all child processes are started
|
|
64
|
-
20000);
|
|
65
42
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
stdio: 'pipe',
|
|
70
|
-
});
|
|
71
|
-
return result.stdout
|
|
72
|
-
.toString()
|
|
73
|
-
.split('\n')
|
|
74
|
-
.map(i => Number(i.trim()))
|
|
75
|
-
.filter(i => i);
|
|
76
|
-
}
|
|
77
|
-
catch {
|
|
78
|
-
return [];
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
async function getParentAndDescendantProcessPidsAsync(ppid) {
|
|
82
|
-
const children = new Set([ppid]);
|
|
83
|
-
let shouldCheckAgain = true;
|
|
84
|
-
while (shouldCheckAgain) {
|
|
85
|
-
const pids = await getChildrenPidsAsync([...children]);
|
|
86
|
-
shouldCheckAgain = false;
|
|
87
|
-
for (const pid of pids) {
|
|
88
|
-
if (!children.has(pid)) {
|
|
89
|
-
shouldCheckAgain = true;
|
|
90
|
-
children.add(pid);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
return [...children];
|
|
43
|
+
function getGradleShellCommand({ gradleCommand, oomScoreAdj, verboseFlag, }) {
|
|
44
|
+
const oomScoreAdjPrefix = oomScoreAdj === undefined ? '' : `echo ${oomScoreAdj} > /proc/$$/oom_score_adj || true; `;
|
|
45
|
+
return `${oomScoreAdjPrefix}exec ./gradlew ${gradleCommand} --profile ${verboseFlag}`;
|
|
95
46
|
}
|
|
96
47
|
function resolveGradleCommand(job, command) {
|
|
97
48
|
if (command) {
|
|
@@ -8,15 +8,17 @@ declare const ArgentArtifactSchema: z.ZodObject<{
|
|
|
8
8
|
isDirectory: z.ZodOptional<z.ZodBoolean>;
|
|
9
9
|
}, z.core.$strip>;
|
|
10
10
|
type ArgentArtifact = z.infer<typeof ArgentArtifactSchema>;
|
|
11
|
-
export declare function pollArgentArtifactsForUploadAsync(ctx: CustomBuildContext, { deviceRunSessionId, toolsUrl, toolsAuthToken, logger, }: {
|
|
11
|
+
export declare function pollArgentArtifactsForUploadAsync(ctx: CustomBuildContext, { deviceRunSessionId, toolsUrl, toolsAuthToken, logger, signal, }: {
|
|
12
12
|
deviceRunSessionId: string;
|
|
13
13
|
toolsUrl: string;
|
|
14
14
|
toolsAuthToken?: string;
|
|
15
15
|
logger: bunyan;
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
signal: AbortSignal;
|
|
17
|
+
}): Promise<void>;
|
|
18
|
+
export declare function listArgentArtifactsAsync({ toolsUrl, toolsAuthToken, signal, }: {
|
|
18
19
|
toolsUrl: string;
|
|
19
20
|
toolsAuthToken?: string;
|
|
21
|
+
signal?: AbortSignal;
|
|
20
22
|
}): Promise<ArgentArtifact[]>;
|
|
21
23
|
export declare function uploadArgentArtifactAsync(ctx: CustomBuildContext, { deviceRunSessionId, toolsUrl, toolsAuthToken, artifact, logger, }: {
|
|
22
24
|
deviceRunSessionId: string;
|