@expo/build-tools 20.1.0 → 20.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/custom.js +23 -0
- package/dist/common/installDependencies.d.ts +10 -1
- package/dist/common/installDependencies.js +95 -1
- package/dist/common/prebuild.js +2 -3
- package/dist/{ios → common}/xcpretty.d.ts +1 -0
- package/dist/{ios → common}/xcpretty.js +18 -3
- package/dist/datadog.js +7 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/ios/fastlane.js +1 -1
- package/dist/ios/pod.js +1 -1
- package/dist/runtimeSettings.d.ts +12 -0
- package/dist/runtimeSettings.js +120 -0
- package/dist/steps/easFunctions.js +1 -1
- package/dist/steps/functions/downloadBuild.d.ts +5 -3
- package/dist/steps/functions/downloadBuild.js +34 -4
- package/dist/steps/functions/findAndUploadBuildArtifacts.js +2 -2
- package/dist/steps/functions/installMaestro.js +13 -2
- package/dist/steps/functions/maestroResultParser.d.ts +18 -0
- package/dist/steps/functions/maestroResultParser.js +132 -3
- package/dist/steps/functions/maestroTests.js +26 -13
- package/dist/steps/functions/repack.d.ts +3 -1
- package/dist/steps/functions/repack.js +15 -1
- package/dist/steps/functions/reportMaestroTestResults.js +39 -20
- package/dist/steps/functions/restoreBuildCache.js +9 -6
- package/dist/steps/functions/startAgentDeviceRemoteSession.d.ts +1 -1
- package/dist/steps/functions/startAgentDeviceRemoteSession.js +101 -22
- package/dist/steps/functions/startArgentRemoteSession.d.ts +5 -0
- package/dist/steps/functions/startArgentRemoteSession.js +60 -19
- package/dist/steps/functions/startServeSimRemoteSession.js +1 -1
- package/dist/steps/functions/uploadArtifact.js +1 -1
- package/dist/steps/utils/ios/fastlane.js +1 -1
- package/dist/steps/utils/remoteDeviceRunSession.d.ts +31 -2
- package/dist/steps/utils/remoteDeviceRunSession.js +82 -3
- package/package.json +3 -3
- package/dist/steps/utils/ios/xcpretty.d.ts +0 -15
- package/dist/steps/utils/ios/xcpretty.js +0 -92
package/dist/builders/custom.js
CHANGED
|
@@ -13,6 +13,7 @@ const path_1 = __importDefault(require("path"));
|
|
|
13
13
|
const easBuildInternal_1 = require("../common/easBuildInternal");
|
|
14
14
|
const projectSources_1 = require("../common/projectSources");
|
|
15
15
|
const customBuildContext_1 = require("../customBuildContext");
|
|
16
|
+
const datadog_1 = require("../datadog");
|
|
16
17
|
const xcodeBuildLogs_1 = require("../ios/xcodeBuildLogs");
|
|
17
18
|
const easFunctionGroups_1 = require("../steps/easFunctionGroups");
|
|
18
19
|
const easFunctions_1 = require("../steps/easFunctions");
|
|
@@ -68,6 +69,7 @@ async function runCustomBuildAsync(ctx) {
|
|
|
68
69
|
throw parseError;
|
|
69
70
|
}
|
|
70
71
|
});
|
|
72
|
+
logUserProvidedCustomFunctions(workflow);
|
|
71
73
|
try {
|
|
72
74
|
try {
|
|
73
75
|
await workflow.executeAsync();
|
|
@@ -92,3 +94,24 @@ async function runCustomBuildAsync(ctx) {
|
|
|
92
94
|
}
|
|
93
95
|
return ctx.artifacts;
|
|
94
96
|
}
|
|
97
|
+
function logUserProvidedCustomFunctions(workflow) {
|
|
98
|
+
for (const buildFunction of Object.values(workflow.buildFunctions)) {
|
|
99
|
+
if (!buildFunction.customFunctionModulePath) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
datadog_1.Datadog.log('Custom build saw user-provided function', {
|
|
103
|
+
event: 'custom_build_user_provided_function',
|
|
104
|
+
custom_function_id: buildFunction.getFullId(),
|
|
105
|
+
custom_function_module_path: buildFunction.customFunctionModulePath,
|
|
106
|
+
custom_function_input_count: String(buildFunction.inputProviders?.length ?? 0),
|
|
107
|
+
custom_function_output_count: String(buildFunction.outputProviders?.length ?? 0),
|
|
108
|
+
...(buildFunction.name ? { custom_function_name: buildFunction.name } : {}),
|
|
109
|
+
...(buildFunction.shell ? { custom_function_shell: buildFunction.shell } : {}),
|
|
110
|
+
...(buildFunction.supportedRuntimePlatforms
|
|
111
|
+
? {
|
|
112
|
+
custom_function_supported_runtime_platforms: buildFunction.supportedRuntimePlatforms.join(','),
|
|
113
|
+
}
|
|
114
|
+
: {}),
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -2,14 +2,23 @@ import { Job } from '@expo/eas-build-job';
|
|
|
2
2
|
import { SpawnOptions, SpawnPromise, SpawnResult } from '@expo/turtle-spawn';
|
|
3
3
|
import { BuildContext } from '../context';
|
|
4
4
|
import { PackageManager } from '../utils/packageManager';
|
|
5
|
-
export declare function installDependenciesAsync({ packageManager, env, logger, infoCallbackFn, cwd, useFrozenLockfile, }: {
|
|
5
|
+
export declare function installDependenciesAsync({ packageManager, env, logger, infoCallbackFn, lineTransformer, cwd, useFrozenLockfile, }: {
|
|
6
6
|
packageManager: PackageManager;
|
|
7
7
|
env: Record<string, string | undefined>;
|
|
8
8
|
cwd: string;
|
|
9
9
|
logger: Exclude<SpawnOptions['logger'], undefined>;
|
|
10
10
|
infoCallbackFn?: SpawnOptions['infoCallbackFn'];
|
|
11
|
+
lineTransformer?: SpawnOptions['lineTransformer'];
|
|
11
12
|
useFrozenLockfile: boolean;
|
|
12
13
|
}): Promise<{
|
|
13
14
|
spawnPromise: SpawnPromise<SpawnResult>;
|
|
14
15
|
}>;
|
|
16
|
+
export declare function installDependenciesWithNpmCacheFallbackAsync({ packageManager, env, logger, infoCallbackFn, cwd, useFrozenLockfile, }: {
|
|
17
|
+
packageManager: PackageManager;
|
|
18
|
+
env: Record<string, string | undefined>;
|
|
19
|
+
cwd: string;
|
|
20
|
+
logger: Exclude<SpawnOptions['logger'], undefined>;
|
|
21
|
+
infoCallbackFn?: SpawnOptions['infoCallbackFn'];
|
|
22
|
+
useFrozenLockfile: boolean;
|
|
23
|
+
}): Promise<void>;
|
|
15
24
|
export declare function resolvePackagerDir(ctx: BuildContext<Job>): string;
|
|
@@ -4,12 +4,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.installDependenciesAsync = installDependenciesAsync;
|
|
7
|
+
exports.installDependenciesWithNpmCacheFallbackAsync = installDependenciesWithNpmCacheFallbackAsync;
|
|
7
8
|
exports.resolvePackagerDir = resolvePackagerDir;
|
|
9
|
+
const eas_build_job_1 = require("@expo/eas-build-job");
|
|
8
10
|
const turtle_spawn_1 = __importDefault(require("@expo/turtle-spawn"));
|
|
9
11
|
const path_1 = __importDefault(require("path"));
|
|
12
|
+
const sentry_1 = require("../sentry");
|
|
10
13
|
const packageManager_1 = require("../utils/packageManager");
|
|
11
14
|
const project_1 = require("../utils/project");
|
|
12
|
-
async function installDependenciesAsync({ packageManager, env, logger, infoCallbackFn, cwd, useFrozenLockfile, }) {
|
|
15
|
+
async function installDependenciesAsync({ packageManager, env, logger, infoCallbackFn, lineTransformer, cwd, useFrozenLockfile, }) {
|
|
13
16
|
let args;
|
|
14
17
|
switch (packageManager) {
|
|
15
18
|
case packageManager_1.PackageManager.NPM: {
|
|
@@ -60,10 +63,67 @@ async function installDependenciesAsync({ packageManager, env, logger, infoCallb
|
|
|
60
63
|
cwd,
|
|
61
64
|
logger,
|
|
62
65
|
infoCallbackFn,
|
|
66
|
+
lineTransformer,
|
|
63
67
|
env,
|
|
64
68
|
}),
|
|
65
69
|
};
|
|
66
70
|
}
|
|
71
|
+
async function installDependenciesWithNpmCacheFallbackAsync({ packageManager, env, logger, infoCallbackFn, cwd, useFrozenLockfile, }) {
|
|
72
|
+
const npmCacheUrl = env.NPM_CONFIG_REGISTRY;
|
|
73
|
+
let firstErrorLine;
|
|
74
|
+
let errorLineCount = 0;
|
|
75
|
+
try {
|
|
76
|
+
await (await installDependenciesAsync({
|
|
77
|
+
packageManager,
|
|
78
|
+
env,
|
|
79
|
+
logger,
|
|
80
|
+
infoCallbackFn,
|
|
81
|
+
lineTransformer: (line) => {
|
|
82
|
+
if (isNpmCacheRegistryErrorLine(line, { env, npmCacheUrl })) {
|
|
83
|
+
firstErrorLine ??= line;
|
|
84
|
+
errorLineCount += 1;
|
|
85
|
+
}
|
|
86
|
+
return line;
|
|
87
|
+
},
|
|
88
|
+
cwd,
|
|
89
|
+
useFrozenLockfile,
|
|
90
|
+
})).spawnPromise;
|
|
91
|
+
if (firstErrorLine) {
|
|
92
|
+
sentry_1.Sentry.capture(new NpmCacheRegistryNonFatalError(), {
|
|
93
|
+
level: 'warning',
|
|
94
|
+
tags: { packageManager },
|
|
95
|
+
extras: { cwd, npmCacheUrl, useFrozenLockfile, firstErrorLine, errorLineCount },
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
if (!isNpmCacheInstallFailure(err, { env, npmCacheUrl })) {
|
|
101
|
+
throw err;
|
|
102
|
+
}
|
|
103
|
+
logger.warn(`Failed to install dependencies using the npm cache registry (${npmCacheUrl}). Retrying without the npm cache registry.`);
|
|
104
|
+
sentry_1.Sentry.capture(new NpmCacheRegistryInstallError(err), {
|
|
105
|
+
level: 'warning',
|
|
106
|
+
tags: { packageManager },
|
|
107
|
+
extras: {
|
|
108
|
+
cwd,
|
|
109
|
+
npmCacheUrl,
|
|
110
|
+
useFrozenLockfile,
|
|
111
|
+
originalErrorMessage: err instanceof Error ? err.message : String(err),
|
|
112
|
+
status: err instanceof Error ? err.status : undefined,
|
|
113
|
+
signal: err instanceof Error ? err.signal : undefined,
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
const { NPM_CONFIG_REGISTRY: _NPM_CONFIG_REGISTRY, ...fallbackEnv } = env;
|
|
117
|
+
await (await installDependenciesAsync({
|
|
118
|
+
packageManager,
|
|
119
|
+
env: fallbackEnv,
|
|
120
|
+
logger,
|
|
121
|
+
infoCallbackFn,
|
|
122
|
+
cwd,
|
|
123
|
+
useFrozenLockfile,
|
|
124
|
+
})).spawnPromise;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
67
127
|
function resolvePackagerDir(ctx) {
|
|
68
128
|
const packagerRunDir = (0, packageManager_1.findPackagerRootDir)(ctx.getReactNativeProjectDirectory());
|
|
69
129
|
if (packagerRunDir !== ctx.getReactNativeProjectDirectory()) {
|
|
@@ -72,3 +132,37 @@ function resolvePackagerDir(ctx) {
|
|
|
72
132
|
}
|
|
73
133
|
return packagerRunDir;
|
|
74
134
|
}
|
|
135
|
+
function isNpmCacheInstallFailure(err, { env, npmCacheUrl }) {
|
|
136
|
+
if (!isNpmCacheRegistryEnabled(env, npmCacheUrl)) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
const errorOutput = getErrorOutput(err);
|
|
140
|
+
return errorOutput.includes(npmCacheUrl);
|
|
141
|
+
}
|
|
142
|
+
function isNpmCacheRegistryErrorLine(line, { env, npmCacheUrl }) {
|
|
143
|
+
return (isNpmCacheRegistryEnabled(env, npmCacheUrl) &&
|
|
144
|
+
line.includes(npmCacheUrl) &&
|
|
145
|
+
/(?:error|failed|ENOTFOUND|ECONN|ETIMEDOUT|EAI_AGAIN|FetchError)/i.test(line));
|
|
146
|
+
}
|
|
147
|
+
function isNpmCacheRegistryEnabled(env, npmCacheUrl) {
|
|
148
|
+
return env.EAS_USE_NPM_CACHE === '1' && !!npmCacheUrl;
|
|
149
|
+
}
|
|
150
|
+
function getErrorOutput(err) {
|
|
151
|
+
if (!(err instanceof Error)) {
|
|
152
|
+
return '';
|
|
153
|
+
}
|
|
154
|
+
const { stdout, stderr } = err;
|
|
155
|
+
return [stdout, stderr].filter(Boolean).join('\n');
|
|
156
|
+
}
|
|
157
|
+
class NpmCacheRegistryNonFatalError extends eas_build_job_1.SystemError {
|
|
158
|
+
name = 'NpmCacheRegistryNonFatalError';
|
|
159
|
+
constructor() {
|
|
160
|
+
super('Non-fatal npm cache registry error during dependency install');
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
class NpmCacheRegistryInstallError extends eas_build_job_1.SystemError {
|
|
164
|
+
name = 'NpmCacheRegistryInstallError';
|
|
165
|
+
constructor(cause) {
|
|
166
|
+
super('Failed to install dependencies using npm cache registry', { cause });
|
|
167
|
+
}
|
|
168
|
+
}
|
package/dist/common/prebuild.js
CHANGED
|
@@ -19,15 +19,14 @@ async function prebuildAsync(ctx, { logger, workingDir, options }) {
|
|
|
19
19
|
options: spawnOptions,
|
|
20
20
|
packageManager: ctx.packageManager,
|
|
21
21
|
});
|
|
22
|
-
|
|
22
|
+
await (0, installDependencies_1.installDependenciesWithNpmCacheFallbackAsync)({
|
|
23
23
|
packageManager: ctx.packageManager,
|
|
24
24
|
env: ctx.env,
|
|
25
25
|
logger,
|
|
26
26
|
cwd: (0, installDependencies_1.resolvePackagerDir)(ctx),
|
|
27
27
|
// prebuild sometimes modifies package.json, so we don't want to use frozen lockfile
|
|
28
28
|
useFrozenLockfile: false,
|
|
29
|
-
})
|
|
30
|
-
await installDependenciesSpawnPromise;
|
|
29
|
+
});
|
|
31
30
|
}
|
|
32
31
|
function getPrebuildCommandArgs(ctx) {
|
|
33
32
|
let prebuildCommand = ctx.job.experimental?.prebuildCommand ?? `prebuild --no-install --platform ${ctx.job.platform}`;
|
|
@@ -10,12 +10,14 @@ const assert_1 = __importDefault(require("assert"));
|
|
|
10
10
|
const fast_glob_1 = __importDefault(require("fast-glob"));
|
|
11
11
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
12
12
|
const path_1 = __importDefault(require("path"));
|
|
13
|
+
const sentry_1 = require("../sentry");
|
|
13
14
|
const CHECK_FILE_INTERVAL_MS = 1000;
|
|
14
15
|
class XcodeBuildLogger {
|
|
15
16
|
logger;
|
|
16
17
|
projectRoot;
|
|
17
18
|
loggerError;
|
|
18
19
|
flushing = false;
|
|
20
|
+
reportedFormatterError = false;
|
|
19
21
|
logReaderPromise;
|
|
20
22
|
logsPath;
|
|
21
23
|
constructor(logger, projectRoot) {
|
|
@@ -62,9 +64,22 @@ class XcodeBuildLogger {
|
|
|
62
64
|
this.logReaderPromise = (0, spawn_async_1.default)('tail', ['-n', '+0', '-f', logsPath], { stdio: 'pipe' });
|
|
63
65
|
(0, assert_1.default)(this.logReaderPromise.child.stdout, 'stdout is not available');
|
|
64
66
|
this.logReaderPromise.child.stdout.on('data', (data) => {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
try {
|
|
68
|
+
const lines = formatter.pipe(data.toString());
|
|
69
|
+
for (const line of lines) {
|
|
70
|
+
this.logger.info(line);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
// The formatter can throw on unexpected xcodebuild output. Fall back to
|
|
75
|
+
// raw logs instead of crashing the process with an uncaught exception.
|
|
76
|
+
if (!this.reportedFormatterError) {
|
|
77
|
+
this.reportedFormatterError = true;
|
|
78
|
+
sentry_1.Sentry.capture('xcpretty formatter failed to parse xcodebuild logs', err, {
|
|
79
|
+
extras: { data: data.toString().slice(-2000) },
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
this.logger.info(data.toString());
|
|
68
83
|
}
|
|
69
84
|
});
|
|
70
85
|
await this.logReaderPromise;
|
package/dist/datadog.js
CHANGED
|
@@ -5,6 +5,9 @@ const sentry_1 = require("./sentry");
|
|
|
5
5
|
const turtleFetch_1 = require("./utils/turtleFetch");
|
|
6
6
|
let setupOptions = null;
|
|
7
7
|
let pendingUploads = [];
|
|
8
|
+
function withTargetTag(tags, target) {
|
|
9
|
+
return { ...tags, target: target.kind === 'build' ? 'build' : 'job_run' };
|
|
10
|
+
}
|
|
8
11
|
exports.Datadog = {
|
|
9
12
|
setup(opts) {
|
|
10
13
|
setupOptions = opts;
|
|
@@ -19,7 +22,7 @@ exports.Datadog = {
|
|
|
19
22
|
name,
|
|
20
23
|
type: 'distribution',
|
|
21
24
|
value,
|
|
22
|
-
tags,
|
|
25
|
+
tags: withTargetTag(tags, target),
|
|
23
26
|
},
|
|
24
27
|
];
|
|
25
28
|
const metricsPath = target.kind === 'build'
|
|
@@ -43,9 +46,10 @@ exports.Datadog = {
|
|
|
43
46
|
return;
|
|
44
47
|
}
|
|
45
48
|
const { expoApiV2BaseUrl, robotAccessToken, target } = setupOptions;
|
|
49
|
+
const tagsWithTarget = withTargetTag(tags, target);
|
|
46
50
|
const log = target.kind === 'build'
|
|
47
|
-
? { buildId: target.turtleBuildId, message, tags }
|
|
48
|
-
: { jobRunId: target.turtleJobRunId, message, tags };
|
|
51
|
+
? { buildId: target.turtleBuildId, message, tags: tagsWithTarget }
|
|
52
|
+
: { jobRunId: target.turtleJobRunId, message, tags: tagsWithTarget };
|
|
49
53
|
const logsPath = target.kind === 'build' ? 'turtle-builds/logs' : 'turtle-job-runs/logs';
|
|
50
54
|
const uploadPromise = (0, turtleFetch_1.turtleFetch)(new URL(logsPath, expoApiV2BaseUrl).toString(), 'POST', {
|
|
51
55
|
json: log,
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -64,3 +64,4 @@ var datadog_1 = require("./datadog");
|
|
|
64
64
|
Object.defineProperty(exports, "Datadog", { enumerable: true, get: function () { return datadog_1.Datadog; } });
|
|
65
65
|
var sentry_1 = require("./sentry");
|
|
66
66
|
Object.defineProperty(exports, "Sentry", { enumerable: true, get: function () { return sentry_1.Sentry; } });
|
|
67
|
+
__exportStar(require("./runtimeSettings"), exports);
|
package/dist/ios/fastlane.js
CHANGED
|
@@ -14,8 +14,8 @@ const path_1 = __importDefault(require("path"));
|
|
|
14
14
|
const fastfile_1 = require("./fastfile");
|
|
15
15
|
const gymfile_1 = require("./gymfile");
|
|
16
16
|
const tvos_1 = require("./tvos");
|
|
17
|
-
const xcpretty_1 = require("./xcpretty");
|
|
18
17
|
const fastlane_1 = require("../common/fastlane");
|
|
18
|
+
const xcpretty_1 = require("../common/xcpretty");
|
|
19
19
|
const context_1 = require("../context");
|
|
20
20
|
async function runFastlaneGym(ctx, { scheme, buildConfiguration, credentials, entitlements, extraEnv, }) {
|
|
21
21
|
const workspacePath = path_1.default.join(ctx.getReactNativeProjectDirectory(), 'ios');
|
package/dist/ios/pod.js
CHANGED
|
@@ -42,7 +42,7 @@ async function resolvePrecompiledModulesPodInstallEnvAsync(ctx) {
|
|
|
42
42
|
ctx.logger.info('EXPO_USE_PRECOMPILED_MODULES=0 is set; not enabling precompiled modules use.');
|
|
43
43
|
return {};
|
|
44
44
|
}
|
|
45
|
-
if (ctx.
|
|
45
|
+
if (ctx.env.EAS_USE_PRECOMPILED_MODULES !== '1') {
|
|
46
46
|
return {};
|
|
47
47
|
}
|
|
48
48
|
let expoPackageVersion;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Env } from '@expo/eas-build-job';
|
|
2
|
+
export declare namespace RuntimeSettings {
|
|
3
|
+
function loadAsync({ environment, env: nextRuntimeEnvironment, }: {
|
|
4
|
+
environment: string;
|
|
5
|
+
env?: Env;
|
|
6
|
+
}): Promise<void>;
|
|
7
|
+
function isUsingIosPrecompiledModulesEnabled(): boolean;
|
|
8
|
+
function getNpmCacheUrl(): string | null;
|
|
9
|
+
function getNodeJsCacheUrl(): string | null;
|
|
10
|
+
function getMavenCacheUrl(): string | null;
|
|
11
|
+
function getCocoapodsCacheUrl(): string | null;
|
|
12
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
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.RuntimeSettings = void 0;
|
|
7
|
+
const node_fetch_1 = __importDefault(require("node-fetch"));
|
|
8
|
+
const zod_1 = require("zod");
|
|
9
|
+
const sentry_1 = require("./sentry");
|
|
10
|
+
const ENVIRONMENT_TO_RUNTIME_SETTINGS_URL = {
|
|
11
|
+
staging: 'https://storage.googleapis.com/eas-workflows-staging/runtime-settings.json',
|
|
12
|
+
production: 'https://storage.googleapis.com/eas-workflows-production/runtime-settings.json',
|
|
13
|
+
};
|
|
14
|
+
const RuntimeSettingsSchema = zod_1.z
|
|
15
|
+
.object({
|
|
16
|
+
caches: zod_1.z.record(zod_1.z.string(), zod_1.z
|
|
17
|
+
.object({
|
|
18
|
+
npm: zod_1.z.boolean(),
|
|
19
|
+
nodejs: zod_1.z.boolean(),
|
|
20
|
+
maven: zod_1.z.boolean(),
|
|
21
|
+
cocoapods: zod_1.z.boolean(),
|
|
22
|
+
})
|
|
23
|
+
.partial()),
|
|
24
|
+
iosPrecompiledModules: zod_1.z.boolean(),
|
|
25
|
+
})
|
|
26
|
+
.partial();
|
|
27
|
+
let runtimeSettings = {};
|
|
28
|
+
let runtimeEnvironment = {};
|
|
29
|
+
var RuntimeSettings;
|
|
30
|
+
(function (RuntimeSettings) {
|
|
31
|
+
async function loadAsync({ environment, env: nextRuntimeEnvironment = {}, }) {
|
|
32
|
+
runtimeEnvironment = nextRuntimeEnvironment;
|
|
33
|
+
const url = ENVIRONMENT_TO_RUNTIME_SETTINGS_URL[environment];
|
|
34
|
+
if (!url) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
const response = await (0, node_fetch_1.default)(url, {
|
|
39
|
+
signal: AbortSignal.timeout(1000),
|
|
40
|
+
});
|
|
41
|
+
if (!response.ok) {
|
|
42
|
+
sentry_1.Sentry.capture('Failed to fetch worker runtime settings', {
|
|
43
|
+
extras: { url, status: response.status },
|
|
44
|
+
level: 'warning',
|
|
45
|
+
});
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
runtimeSettings = RuntimeSettingsSchema.parse(await response.json());
|
|
49
|
+
if (runtimeSettings.caches && !runtimeSettings.caches[process.platform]) {
|
|
50
|
+
sentry_1.Sentry.capture(new Error(`Runtime settings are missing cache settings for platform "${process.platform}"`), {
|
|
51
|
+
extras: {
|
|
52
|
+
configuredPlatforms: Object.keys(runtimeSettings.caches),
|
|
53
|
+
platform: process.platform,
|
|
54
|
+
},
|
|
55
|
+
level: 'error',
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
sentry_1.Sentry.capture('Failed to load worker runtime settings', err instanceof Error ? err : new Error(String(err)), {
|
|
61
|
+
extras: { url },
|
|
62
|
+
level: 'warning',
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
RuntimeSettings.loadAsync = loadAsync;
|
|
67
|
+
function isUsingIosPrecompiledModulesEnabled() {
|
|
68
|
+
return runtimeSettings.iosPrecompiledModules ?? false;
|
|
69
|
+
}
|
|
70
|
+
RuntimeSettings.isUsingIosPrecompiledModulesEnabled = isUsingIosPrecompiledModulesEnabled;
|
|
71
|
+
function getNpmCacheUrl() {
|
|
72
|
+
if (runtimeEnvironment['EAS_BUILD_DISABLE_NPM_CACHE'] === '1') {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
const runtimeEnabled = runtimeSettings.caches?.[process.platform]?.npm;
|
|
76
|
+
if (runtimeEnabled === false) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
const envOverride = runtimeEnvironment['EAS_USE_NPM_CACHE'];
|
|
80
|
+
const enabled = envOverride === '1' || (envOverride !== '0' && runtimeEnabled);
|
|
81
|
+
return enabled ? process.env.EAS_NPM_CACHE_URL || null : null;
|
|
82
|
+
}
|
|
83
|
+
RuntimeSettings.getNpmCacheUrl = getNpmCacheUrl;
|
|
84
|
+
function getNodeJsCacheUrl() {
|
|
85
|
+
const runtimeEnabled = runtimeSettings.caches?.[process.platform]?.nodejs;
|
|
86
|
+
if (runtimeEnabled === false) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
const envOverride = runtimeEnvironment['EAS_USE_NODEJS_CACHE'];
|
|
90
|
+
const enabled = envOverride === '1' || (envOverride !== '0' && runtimeEnabled);
|
|
91
|
+
return enabled ? process.env.EAS_NODEJS_CACHE_URL || null : null;
|
|
92
|
+
}
|
|
93
|
+
RuntimeSettings.getNodeJsCacheUrl = getNodeJsCacheUrl;
|
|
94
|
+
function getMavenCacheUrl() {
|
|
95
|
+
if (runtimeEnvironment['EAS_BUILD_DISABLE_MAVEN_CACHE'] === '1') {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
const runtimeEnabled = runtimeSettings.caches?.[process.platform]?.maven;
|
|
99
|
+
if (runtimeEnabled === false) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
const envOverride = runtimeEnvironment['EAS_USE_MAVEN_CACHE'];
|
|
103
|
+
const enabled = envOverride === '1' || (envOverride !== '0' && runtimeEnabled);
|
|
104
|
+
return enabled ? process.env.EAS_MAVEN_CACHE_URL || null : null;
|
|
105
|
+
}
|
|
106
|
+
RuntimeSettings.getMavenCacheUrl = getMavenCacheUrl;
|
|
107
|
+
function getCocoapodsCacheUrl() {
|
|
108
|
+
if (runtimeEnvironment['EAS_BUILD_DISABLE_COCOAPODS_CACHE'] === '1') {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
const runtimeEnabled = runtimeSettings.caches?.[process.platform]?.cocoapods;
|
|
112
|
+
if (runtimeEnabled === false) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
const envOverride = runtimeEnvironment['EAS_USE_COCOAPODS_CACHE'];
|
|
116
|
+
const enabled = envOverride === '1' || (envOverride !== '0' && runtimeEnabled);
|
|
117
|
+
return enabled ? process.env.EAS_COCOAPODS_CACHE_URL || null : null;
|
|
118
|
+
}
|
|
119
|
+
RuntimeSettings.getCocoapodsCacheUrl = getCocoapodsCacheUrl;
|
|
120
|
+
})(RuntimeSettings || (exports.RuntimeSettings = RuntimeSettings = {}));
|
|
@@ -57,7 +57,7 @@ function getEasFunctions(ctx) {
|
|
|
57
57
|
(0, installNodeModules_1.createInstallNodeModulesBuildFunction)(),
|
|
58
58
|
(0, prebuild_1.createPrebuildBuildFunction)(),
|
|
59
59
|
(0, readIpaInfo_1.createReadIpaInfoBuildFunction)(),
|
|
60
|
-
(0, downloadBuild_1.createDownloadBuildFunction)(),
|
|
60
|
+
(0, downloadBuild_1.createDownloadBuildFunction)(ctx),
|
|
61
61
|
(0, export_1.createEasExportBuildFunction)(),
|
|
62
62
|
(0, deploy_1.createEasDeployBuildFunction)(),
|
|
63
63
|
(0, repack_1.createRepackBuildFunction)(),
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { bunyan } from '@expo/logger';
|
|
2
2
|
import { BuildFunction } from '@expo/steps';
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
import { Client } from '@urql/core';
|
|
4
|
+
import { CustomBuildContext } from '../../customBuildContext';
|
|
5
|
+
export declare function createDownloadBuildFunction(ctx: CustomBuildContext): BuildFunction;
|
|
6
|
+
export declare function downloadBuildAsync({ logger, buildId, graphqlClient, robotAccessToken, extensions, }: {
|
|
5
7
|
logger: bunyan;
|
|
6
8
|
buildId: string;
|
|
7
|
-
|
|
9
|
+
graphqlClient: Client;
|
|
8
10
|
robotAccessToken: string | null;
|
|
9
11
|
extensions: string[];
|
|
10
12
|
}): Promise<{
|
|
@@ -9,6 +9,7 @@ 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
11
|
const fast_glob_1 = require("fast-glob");
|
|
12
|
+
const gql_tada_1 = require("gql.tada");
|
|
12
13
|
const node_fetch_1 = __importDefault(require("node-fetch"));
|
|
13
14
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
14
15
|
const node_os_1 = __importDefault(require("node:os"));
|
|
@@ -21,7 +22,20 @@ const files_1 = require("../../utils/files");
|
|
|
21
22
|
const retryOnDNSFailure_1 = require("../../utils/retryOnDNSFailure");
|
|
22
23
|
const strings_1 = require("../../utils/strings");
|
|
23
24
|
const streamPipeline = (0, util_1.promisify)(stream_1.default.pipeline);
|
|
24
|
-
|
|
25
|
+
const BUILD_BY_ID_QUERY = (0, gql_tada_1.graphql)(`
|
|
26
|
+
query DownloadBuildByIdQuery($buildId: ID!) {
|
|
27
|
+
builds {
|
|
28
|
+
byId(buildId: $buildId) {
|
|
29
|
+
id
|
|
30
|
+
platform
|
|
31
|
+
artifacts {
|
|
32
|
+
applicationArchiveUrl
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
`);
|
|
38
|
+
function createDownloadBuildFunction(ctx) {
|
|
25
39
|
return new steps_1.BuildFunction({
|
|
26
40
|
namespace: 'eas',
|
|
27
41
|
id: 'download_build',
|
|
@@ -55,7 +69,7 @@ function createDownloadBuildFunction() {
|
|
|
55
69
|
const { artifactPath } = await downloadBuildAsync({
|
|
56
70
|
logger,
|
|
57
71
|
buildId,
|
|
58
|
-
|
|
72
|
+
graphqlClient: ctx.graphqlClient,
|
|
59
73
|
robotAccessToken: stepsCtx.global.staticContext.job.secrets?.robotAccessToken ?? null,
|
|
60
74
|
extensions,
|
|
61
75
|
});
|
|
@@ -63,9 +77,25 @@ function createDownloadBuildFunction() {
|
|
|
63
77
|
},
|
|
64
78
|
});
|
|
65
79
|
}
|
|
66
|
-
async function
|
|
80
|
+
async function fetchApplicationArchiveUrlAsync({ buildId, graphqlClient, }) {
|
|
81
|
+
const result = await graphqlClient.query(BUILD_BY_ID_QUERY, { buildId }).toPromise();
|
|
82
|
+
if (result.error) {
|
|
83
|
+
const { error } = result;
|
|
84
|
+
const message = `Could not fetch build ${buildId}: ${error.message}`;
|
|
85
|
+
throw error.networkError || result.error.response?.status >= 500
|
|
86
|
+
? new eas_build_job_1.SystemError(message, { cause: error })
|
|
87
|
+
: new eas_build_job_1.UserError('EAS_DOWNLOAD_BUILD_FETCH_FAILED', message, { cause: error });
|
|
88
|
+
}
|
|
89
|
+
const applicationArchiveUrl = result.data?.builds.byId?.artifacts?.applicationArchiveUrl;
|
|
90
|
+
if (!applicationArchiveUrl) {
|
|
91
|
+
throw new eas_build_job_1.UserError('EAS_DOWNLOAD_BUILD_NO_APPLICATION_ARCHIVE', 'Build does not have an application archive url');
|
|
92
|
+
}
|
|
93
|
+
return applicationArchiveUrl;
|
|
94
|
+
}
|
|
95
|
+
async function downloadBuildAsync({ logger, buildId, graphqlClient, robotAccessToken, extensions, }) {
|
|
67
96
|
const downloadDestinationDirectory = await node_fs_1.default.promises.mkdtemp(node_path_1.default.join(node_os_1.default.tmpdir(), 'download_build-downloaded-'));
|
|
68
|
-
const
|
|
97
|
+
const downloadUrl = await fetchApplicationArchiveUrlAsync({ buildId, graphqlClient });
|
|
98
|
+
const response = await (0, retryOnDNSFailure_1.retryOnDNSFailure)(node_fetch_1.default)(downloadUrl, {
|
|
69
99
|
headers: robotAccessToken ? { Authorization: `Bearer ${robotAccessToken}` } : undefined,
|
|
70
100
|
});
|
|
71
101
|
if (!response.ok) {
|
|
@@ -23,14 +23,14 @@ function createFindAndUploadBuildArtifactsBuildFunction(ctx) {
|
|
|
23
23
|
await uploadApplicationArchivesAsync({ ctx, stepCtx });
|
|
24
24
|
}
|
|
25
25
|
catch (err) {
|
|
26
|
-
stepCtx.logger.error(`Failed to upload application archives
|
|
26
|
+
stepCtx.logger.error({ err }, `Failed to upload application archives.`);
|
|
27
27
|
firstError ||= err;
|
|
28
28
|
}
|
|
29
29
|
try {
|
|
30
30
|
await uploadBuildArtifacts({ ctx, stepCtx });
|
|
31
31
|
}
|
|
32
32
|
catch (err) {
|
|
33
|
-
stepCtx.logger.error(`Failed to upload build artifacts
|
|
33
|
+
stepCtx.logger.error({ err }, `Failed to upload build artifacts.`);
|
|
34
34
|
firstError ||= err;
|
|
35
35
|
}
|
|
36
36
|
if (ctx.job.platform === eas_build_job_1.Platform.IOS) {
|
|
@@ -11,6 +11,7 @@ const assert_1 = __importDefault(require("assert"));
|
|
|
11
11
|
const fs_1 = __importDefault(require("fs"));
|
|
12
12
|
const os_1 = __importDefault(require("os"));
|
|
13
13
|
const path_1 = __importDefault(require("path"));
|
|
14
|
+
const datadog_1 = require("../../datadog");
|
|
14
15
|
function createInstallMaestroBuildFunction() {
|
|
15
16
|
return new steps_1.BuildFunction({
|
|
16
17
|
namespace: 'eas',
|
|
@@ -95,12 +96,22 @@ function createInstallMaestroBuildFunction() {
|
|
|
95
96
|
}
|
|
96
97
|
logger.info(`Maestro ${maestroVersionResult.value} is ready.`);
|
|
97
98
|
outputs.maestro_version.set(maestroVersionResult.value);
|
|
99
|
+
datadog_1.Datadog.distribution('eas.maestro.install', 1, {
|
|
100
|
+
maestro_version: maestroVersionResult.value,
|
|
101
|
+
});
|
|
98
102
|
},
|
|
99
103
|
});
|
|
100
104
|
}
|
|
101
105
|
async function getMaestroVersion({ env }) {
|
|
102
|
-
const
|
|
103
|
-
|
|
106
|
+
const { stdout } = await (0, turtle_spawn_1.default)('maestro', ['--version'], { stdio: 'pipe', env });
|
|
107
|
+
// `maestro --version` can print an analytics notice to stdout before the version,
|
|
108
|
+
// e.g. "Anonymous analytics enabled. To opt out, set MAESTRO_CLI_NO_ANALYTICS...\n2.0.10".
|
|
109
|
+
// Take the last version-looking token: the real version is printed after the notice, so
|
|
110
|
+
// this stays correct even if the notice itself contains a version-like string. Keeps the
|
|
111
|
+
// step output and the eas.maestro.install metric tag clean. Best-effort only: fall back to
|
|
112
|
+
// the raw output if none is found, so we never fail the build over a version string.
|
|
113
|
+
const versions = stdout.match(/\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?/g);
|
|
114
|
+
return versions?.at(-1) ?? stdout.trim();
|
|
104
115
|
}
|
|
105
116
|
async function installMaestro({ global, version, logger, env, }) {
|
|
106
117
|
logger.info('Fetching install script');
|
|
@@ -10,6 +10,7 @@ export interface MaestroFlowResult {
|
|
|
10
10
|
}
|
|
11
11
|
export interface JUnitTestCaseResult {
|
|
12
12
|
name: string;
|
|
13
|
+
file: string | undefined;
|
|
13
14
|
status: 'passed' | 'failed';
|
|
14
15
|
duration: number;
|
|
15
16
|
errorMessage: string | null;
|
|
@@ -17,6 +18,23 @@ export interface JUnitTestCaseResult {
|
|
|
17
18
|
properties: Record<string, string>;
|
|
18
19
|
}
|
|
19
20
|
export declare function parseJUnitTestCases(junitDirectory: string): Promise<JUnitTestCaseResult[]>;
|
|
21
|
+
export declare function isFileAttrRun(testcases: JUnitTestCaseResult[]): testcases is (JUnitTestCaseResult & {
|
|
22
|
+
file: string;
|
|
23
|
+
})[];
|
|
24
|
+
export declare function junitFileHasFileAttrs(junitFile: string): Promise<boolean>;
|
|
25
|
+
export declare function parseMaestroResultsFromFileAttrs(junitDirectory: string): Promise<MaestroFlowResult[]>;
|
|
26
|
+
/**
|
|
27
|
+
* Returns the `file=` paths of the failing testcases in the given attempt's
|
|
28
|
+
* JUnit file, or `null` when the result cannot be trusted (caller then falls
|
|
29
|
+
* back to dumb retry — re-run everything; never the legacy scan).
|
|
30
|
+
*
|
|
31
|
+
* Callers verify the report carries `file=` attributes first
|
|
32
|
+
* (junitFileHasFileAttrs).
|
|
33
|
+
*/
|
|
34
|
+
export declare function parseFailedFlowsFromFileAttrs(args: {
|
|
35
|
+
junitFile: string;
|
|
36
|
+
workingDirectory: string;
|
|
37
|
+
}): Promise<string[] | null>;
|
|
20
38
|
export declare function parseMaestroResults(junitDirectory: string, nameToPath: Map<string, string> | null): Promise<MaestroFlowResult[]>;
|
|
21
39
|
/**
|
|
22
40
|
* Returns the subset of `inputFlowPaths` whose testcases failed in the given
|