@expo/build-tools 21.8.0 → 22.0.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.
@@ -5,6 +5,7 @@ const eas_build_job_1 = require("@expo/eas-build-job");
5
5
  const xcodeBuildLogs_1 = require("../ios/xcodeBuildLogs");
6
6
  const artifacts_1 = require("../utils/artifacts");
7
7
  const hooks_1 = require("../utils/hooks");
8
+ const sourceMaps_1 = require("../utils/sourceMaps");
8
9
  async function runBuilderWithHooksAsync(ctx, builderAsync) {
9
10
  try {
10
11
  let buildSuccess = true;
@@ -35,6 +36,9 @@ async function runBuilderWithHooksAsync(ctx, builderAsync) {
35
36
  });
36
37
  }
37
38
  await ctx.runBuildPhase(eas_build_job_1.BuildPhase.UPLOAD_BUILD_ARTIFACTS, async () => {
39
+ if (buildSuccess) {
40
+ await (0, sourceMaps_1.maybeUploadSourceMapAsync)(ctx);
41
+ }
38
42
  await (0, artifacts_1.maybeFindAndUploadBuildArtifacts)(ctx, {
39
43
  logger: ctx.logger,
40
44
  });
@@ -30,6 +30,7 @@ const expoUpdatesEmbedded_1 = require("../utils/expoUpdatesEmbedded");
30
30
  const hooks_1 = require("../utils/hooks");
31
31
  const prepareBuildExecutable_1 = require("../utils/prepareBuildExecutable");
32
32
  const processes_1 = require("../utils/processes");
33
+ const sourceMaps_1 = require("../utils/sourceMaps");
33
34
  const INSTALL_PODS_WARN_TIMEOUT_MS = 15 * 60 * 1000; // 15 minutes
34
35
  const INSTALL_PODS_KILL_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
35
36
  class InstallPodsTimeoutError extends Error {
@@ -151,19 +152,23 @@ async function buildInnerAsync(ctx, jobHooksRef) {
151
152
  fastlaneResult = await ctx.runBuildPhase(eas_build_job_1.BuildPhase.RUN_FASTLANE, async () => {
152
153
  const scheme = (0, resolve_1.resolveScheme)(ctx);
153
154
  const entitlements = await readEntitlementsAsync(ctx, { scheme, buildConfiguration });
155
+ const sourceMapPath = (0, sourceMaps_1.isSourceMapUploadEnabled)(ctx)
156
+ ? await (0, sourceMaps_1.resolveIosSourceMapPathAsync)(ctx)
157
+ : null;
154
158
  return await (0, fastlane_1.runFastlaneGym)(ctx, {
155
159
  credentials,
156
160
  scheme,
157
161
  buildConfiguration,
158
162
  entitlements,
159
- ...(resolvedExpoUpdatesRuntimeVersion?.runtimeVersion
160
- ? {
161
- extraEnv: {
163
+ extraEnv: {
164
+ ...(resolvedExpoUpdatesRuntimeVersion?.runtimeVersion
165
+ ? {
162
166
  EXPO_UPDATES_FINGERPRINT_OVERRIDE: resolvedExpoUpdatesRuntimeVersion?.runtimeVersion,
163
167
  EXPO_UPDATES_WORKFLOW_OVERRIDE: ctx.job.type,
164
- },
165
- }
166
- : null),
168
+ }
169
+ : null),
170
+ ...(sourceMapPath ? { SOURCEMAP_FILE: sourceMapPath } : null),
171
+ },
167
172
  });
168
173
  });
169
174
  }
@@ -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 maestroBackend_1 = require("./maestroBackend");
14
15
  const datadog_1 = require("../../datadog");
15
16
  function createInstallMaestroBuildFunction() {
16
17
  return new steps_1.BuildFunction({
@@ -24,6 +25,11 @@ function createInstallMaestroBuildFunction() {
24
25
  required: false,
25
26
  allowedValueTypeName: steps_1.BuildStepInputValueTypeName.STRING,
26
27
  }),
28
+ steps_1.BuildStepInput.createProvider({
29
+ id: 'backend',
30
+ required: false,
31
+ allowedValueTypeName: steps_1.BuildStepInputValueTypeName.STRING,
32
+ }),
27
33
  ],
28
34
  outputProviders: [
29
35
  steps_1.BuildStepOutput.createProvider({
@@ -32,35 +38,43 @@ function createInstallMaestroBuildFunction() {
32
38
  }),
33
39
  ],
34
40
  fn: async ({ logger, global }, { inputs, env, outputs }) => {
35
- const requestedMaestroVersion = inputs.maestro_version.value;
36
- const { value: currentMaestroVersion } = await (0, results_1.asyncResult)(getMaestroVersion({ env }));
41
+ const backend = (0, maestroBackend_1.resolveMaestroBackend)({
42
+ input: inputs.backend.value,
43
+ env,
44
+ });
45
+ const requestedVersion = inputs.maestro_version.value;
46
+ const { value: currentMaestroVersion } = await (0, results_1.asyncResult)(getMaestroVersion({ env, backend }));
37
47
  // When not running in EAS Build VM, do not modify local environment.
38
48
  if (env.EAS_BUILD_RUNNER !== 'eas-build') {
39
- const currentIsJavaInstalled = await isJavaInstalled({ env });
40
- const currentIsIdbInstalled = await isIdbInstalled({ env });
41
- if (!currentIsJavaInstalled) {
49
+ const needsToInstallJava = backend === 'maestro' && !(await isJavaInstalled({ env }));
50
+ const needsToInstallIdb = backend === 'maestro' && !(await isIdbInstalled({ env }));
51
+ if (needsToInstallJava) {
42
52
  logger.warn('It seems Java is not installed. It is required to run Maestro. If the job fails, this may be the reason.');
43
53
  logger.info('');
44
54
  }
45
- if (!currentIsIdbInstalled) {
55
+ if (needsToInstallIdb) {
46
56
  logger.warn('It seems IDB is not installed. Maestro requires it to run flows on iOS Simulator. If the job fails, this may be the reason.');
47
57
  logger.info('');
48
58
  }
49
59
  if (!currentMaestroVersion) {
50
- logger.warn('It seems Maestro is not installed. Please install Maestro manually and rerun the job.');
60
+ logger.warn(`It seems ${backend} is not installed. Please install it manually and rerun the job.`);
51
61
  logger.info('');
52
62
  }
53
63
  // Guide is helpful in these two cases, it doesn't mention Java.
54
- if (!currentIsIdbInstalled || !currentMaestroVersion) {
64
+ if (backend === 'maestro' && (needsToInstallIdb || !currentMaestroVersion)) {
55
65
  logger.warn('For more info, check out Maestro installation guide: https://maestro.mobile.dev/getting-started/installing-maestro');
56
66
  }
67
+ else if (backend === 'maestro-runner' && !currentMaestroVersion) {
68
+ logger.warn('For more info, check out maestro-runner installation guide: https://github.com/devicelab-dev/maestro-runner#install');
69
+ }
57
70
  if (currentMaestroVersion) {
58
71
  outputs.maestro_version.set(currentMaestroVersion);
59
- logger.info(`Maestro ${currentMaestroVersion} is ready.`);
72
+ logger.info(`${backend} ${currentMaestroVersion} is ready.`);
60
73
  }
61
74
  return;
62
75
  }
63
- if (!(await isJavaInstalled({ env }))) {
76
+ const needsToInstallJava = backend === 'maestro' && !(await isJavaInstalled({ env }));
77
+ if (needsToInstallJava) {
64
78
  if (global.runtimePlatform === steps_1.BuildRuntimePlatform.DARWIN) {
65
79
  logger.info('Installing Java');
66
80
  await installJavaFromGcs({ logger, env });
@@ -73,45 +87,83 @@ function createInstallMaestroBuildFunction() {
73
87
  }
74
88
  }
75
89
  // IDB is only a requirement on macOS.
76
- if (global.runtimePlatform === steps_1.BuildRuntimePlatform.DARWIN &&
77
- !(await isIdbInstalled({ env }))) {
90
+ const needsToInstallIdb = backend === 'maestro' &&
91
+ global.runtimePlatform === steps_1.BuildRuntimePlatform.DARWIN &&
92
+ !(await isIdbInstalled({ env }));
93
+ if (needsToInstallIdb) {
78
94
  logger.info('Installing IDB');
79
95
  await installIdbFromBrew({ logger, env });
80
96
  }
81
97
  // Skip installing if the input sets a specific Maestro version to install
82
98
  // and it is already installed which happens when developing on a local computer.
83
99
  if (!currentMaestroVersion ||
84
- (requestedMaestroVersion && requestedMaestroVersion !== currentMaestroVersion)) {
85
- await installMaestro({
86
- version: requestedMaestroVersion,
87
- global,
88
- logger,
89
- env,
90
- });
100
+ (requestedVersion && requestedVersion !== currentMaestroVersion)) {
101
+ switch (backend) {
102
+ case 'maestro':
103
+ await installMaestro({ version: requestedVersion, global, logger, env });
104
+ break;
105
+ case 'maestro-runner':
106
+ await installMaestroRunner({ version: requestedVersion, global, logger, env });
107
+ break;
108
+ }
91
109
  }
92
- const maestroVersionResult = await (0, results_1.asyncResult)(getMaestroVersion({ env }));
110
+ const maestroVersionResult = await (0, results_1.asyncResult)(getMaestroVersion({ env, backend }));
93
111
  if (!maestroVersionResult.ok) {
94
112
  logger.error(maestroVersionResult.reason, 'Failed to get Maestro version.');
95
- throw new Error('Failed to ensure Maestro is installed.');
113
+ throw new Error(`Failed to ensure ${backend} is installed.`);
96
114
  }
97
- logger.info(`Maestro ${maestroVersionResult.value} is ready.`);
115
+ logger.info(`${backend} ${maestroVersionResult.value} is ready.`);
98
116
  outputs.maestro_version.set(maestroVersionResult.value);
99
117
  datadog_1.Datadog.distribution('eas.maestro.install', 1, {
100
118
  maestro_version: maestroVersionResult.value,
119
+ maestro_backend: backend,
101
120
  });
102
121
  },
103
122
  });
104
123
  }
105
- async function getMaestroVersion({ env }) {
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();
124
+ async function getMaestroVersion({ env, backend, }) {
125
+ switch (backend) {
126
+ case 'maestro': {
127
+ const { stdout } = await (0, turtle_spawn_1.default)('maestro', ['--version'], { stdio: 'pipe', env });
128
+ // `maestro --version` can print an analytics notice to stdout before the version,
129
+ // e.g. "Anonymous analytics enabled. To opt out, set MAESTRO_CLI_NO_ANALYTICS...\n2.0.10".
130
+ // Take the last version-looking token: the real version is printed after the notice.
131
+ const versions = stdout.match(/\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?/g);
132
+ return versions?.at(-1) ?? stdout.trim();
133
+ }
134
+ case 'maestro-runner': {
135
+ const { stdout } = await (0, turtle_spawn_1.default)('maestro-runner', ['--version'], { stdio: 'pipe', env });
136
+ // maestro-runner prints build information after its version. The Go runtime version in
137
+ // that output is also semver-shaped, so read only the prefixed runner version.
138
+ return /^maestro-runner\s+(\S+)/m.exec(stdout)?.[1] ?? stdout.trim();
139
+ }
140
+ }
141
+ }
142
+ async function installMaestroRunner({ global, version, logger, env, }) {
143
+ logger.info('Fetching maestro-runner install script');
144
+ const tempDirectory = await fs_1.default.promises.mkdtemp(path_1.default.join(os_1.default.tmpdir(), 'install_maestro_runner'));
145
+ try {
146
+ const installMaestroRunnerScriptResponse = await fetch('https://open.devicelab.dev/install/maestro-runner');
147
+ const installMaestroRunnerScript = await installMaestroRunnerScriptResponse.text();
148
+ const scriptPath = path_1.default.join(tempDirectory, 'install_maestro_runner.sh');
149
+ await fs_1.default.promises.writeFile(scriptPath, installMaestroRunnerScript, { mode: 0o777 });
150
+ logger.info('Installing maestro-runner');
151
+ (0, assert_1.default)(env.HOME, 'Failed to infer directory to install maestro-runner in: $HOME environment variable is empty.');
152
+ await (0, turtle_spawn_1.default)(scriptPath, version && version !== 'latest' ? ['--version', version] : [], {
153
+ logger,
154
+ env,
155
+ });
156
+ const binDir = path_1.default.join(env.HOME, '.maestro-runner', 'bin');
157
+ global.updateEnv({
158
+ ...global.env,
159
+ PATH: `${global.env.PATH}:${binDir}`,
160
+ });
161
+ env.PATH = `${env.PATH}:${binDir}`;
162
+ process.env.PATH = `${process.env.PATH}:${binDir}`;
163
+ }
164
+ finally {
165
+ await fs_1.default.promises.rm(tempDirectory, { force: true, recursive: true });
166
+ }
115
167
  }
116
168
  async function installMaestro({ global, version, logger, env, }) {
117
169
  logger.info('Fetching install script');
@@ -0,0 +1,11 @@
1
+ import { BuildStepEnv } from '@expo/steps';
2
+ import { z } from 'zod';
3
+ export declare const MaestroBackendSchema: z.ZodDefault<z.ZodEnum<{
4
+ maestro: "maestro";
5
+ "maestro-runner": "maestro-runner";
6
+ }>>;
7
+ export type MaestroBackend = z.output<typeof MaestroBackendSchema>;
8
+ export declare function resolveMaestroBackend({ input, env, }: {
9
+ input: unknown;
10
+ env: BuildStepEnv;
11
+ }): MaestroBackend;
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MaestroBackendSchema = void 0;
4
+ exports.resolveMaestroBackend = resolveMaestroBackend;
5
+ const eas_build_job_1 = require("@expo/eas-build-job");
6
+ const zod_1 = require("zod");
7
+ exports.MaestroBackendSchema = zod_1.z.enum(['maestro', 'maestro-runner']).default('maestro');
8
+ function resolveMaestroBackend({ input, env, }) {
9
+ const result = exports.MaestroBackendSchema.safeParse(input || env.EAS_MAESTRO_BACKEND || undefined);
10
+ if (!result.success) {
11
+ throw new eas_build_job_1.UserError('ERR_MAESTRO_INVALID_INPUT', 'backend and EAS_MAESTRO_BACKEND must be either "maestro" or "maestro-runner".', { cause: result.error });
12
+ }
13
+ return result.data;
14
+ }
@@ -0,0 +1,5 @@
1
+ import { BuildJob } from '@expo/eas-build-job';
2
+ import { BuildContext } from '../context';
3
+ export declare function isSourceMapUploadEnabled(ctx: BuildContext<BuildJob>): boolean;
4
+ export declare function resolveIosSourceMapPathAsync(ctx: BuildContext<BuildJob>): Promise<string>;
5
+ export declare function maybeUploadSourceMapAsync(ctx: BuildContext<BuildJob>): Promise<void>;
@@ -0,0 +1,105 @@
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.isSourceMapUploadEnabled = isSourceMapUploadEnabled;
7
+ exports.resolveIosSourceMapPathAsync = resolveIosSourceMapPathAsync;
8
+ exports.maybeUploadSourceMapAsync = maybeUploadSourceMapAsync;
9
+ const eas_build_job_1 = require("@expo/eas-build-job");
10
+ const fast_glob_1 = __importDefault(require("fast-glob"));
11
+ const fs_extra_1 = __importDefault(require("fs-extra"));
12
+ const path_1 = __importDefault(require("path"));
13
+ const ANDROID_SOURCE_MAP_PATTERN = 'android/**/build/generated/sourcemaps/react/**/*.map';
14
+ const SOURCE_MAP_UPLOAD_DIRECTORY = 'observe-source-maps';
15
+ function isSourceMapUploadEnabled(ctx) {
16
+ return !ctx.isLocal && ctx.job.experimental?.uploadSourceMaps === true;
17
+ }
18
+ async function resolveIosSourceMapPathAsync(ctx) {
19
+ const configuredPath = ctx.env.SOURCEMAP_FILE;
20
+ const sourceMapPath = configuredPath
21
+ ? path_1.default.resolve(ctx.getReactNativeProjectDirectory(), 'ios', configuredPath)
22
+ : path_1.default.join(ctx.workingdir, SOURCE_MAP_UPLOAD_DIRECTORY, 'main.jsbundle.map');
23
+ await fs_extra_1.default.ensureDir(path_1.default.dirname(sourceMapPath));
24
+ return sourceMapPath;
25
+ }
26
+ async function maybeUploadSourceMapAsync(ctx) {
27
+ if (!isSourceMapUploadEnabled(ctx)) {
28
+ return;
29
+ }
30
+ try {
31
+ const sourceMapPath = await resolveSourceMapPathAsync(ctx);
32
+ const strippedSourceMapPath = await stripSourcesContentAsync(ctx, sourceMapPath);
33
+ ctx.logger.info(`Uploading source map: ${sourceMapPath}`);
34
+ await ctx.uploadArtifact({
35
+ artifact: {
36
+ type: eas_build_job_1.ManagedArtifactType.SOURCE_MAP,
37
+ paths: [strippedSourceMapPath],
38
+ },
39
+ logger: ctx.logger,
40
+ });
41
+ }
42
+ catch (err) {
43
+ ctx.logger.warn({ err }, 'Failed to upload source map.');
44
+ ctx.markBuildPhaseHasWarnings();
45
+ }
46
+ }
47
+ async function resolveSourceMapPathAsync(ctx) {
48
+ if (ctx.job.platform === eas_build_job_1.Platform.IOS) {
49
+ const sourceMapPath = await resolveIosSourceMapPathAsync(ctx);
50
+ if (!(await fs_extra_1.default.pathExists(sourceMapPath))) {
51
+ throw new eas_build_job_1.SystemError(`The iOS source map was not generated at ${sourceMapPath}.`);
52
+ }
53
+ return sourceMapPath;
54
+ }
55
+ if (ctx.job.platform === eas_build_job_1.Platform.ANDROID) {
56
+ const projectDir = ctx.getReactNativeProjectDirectory();
57
+ const sourceMapPaths = (await (0, fast_glob_1.default)(ANDROID_SOURCE_MAP_PATTERN, {
58
+ absolute: true,
59
+ cwd: projectDir,
60
+ onlyFiles: true,
61
+ })).filter(sourceMapPath => !isIntermediateAndroidSourceMap(sourceMapPath));
62
+ if (sourceMapPaths.length === 0) {
63
+ throw new eas_build_job_1.SystemError('The Android build did not generate a final composed source map.');
64
+ }
65
+ if (sourceMapPaths.length > 1) {
66
+ throw new eas_build_job_1.SystemError(`Found multiple final Android source maps: ${sourceMapPaths.join(', ')}. ` +
67
+ 'Refusing to upload a source map that may not match the application archive.');
68
+ }
69
+ return sourceMapPaths[0];
70
+ }
71
+ throw new eas_build_job_1.SystemError('Source-map upload is not supported for this build platform.');
72
+ }
73
+ function isIntermediateAndroidSourceMap(sourceMapPath) {
74
+ return sourceMapPath.endsWith('.packager.map') || sourceMapPath.endsWith('.compiler.map');
75
+ }
76
+ async function stripSourcesContentAsync(ctx, sourceMapPath) {
77
+ const sourceMap = JSON.parse(await fs_extra_1.default.readFile(sourceMapPath, 'utf8'));
78
+ if (!isRecord(sourceMap) || sourceMap.version !== 3) {
79
+ throw new eas_build_job_1.SystemError(`Invalid source map at ${sourceMapPath}.`);
80
+ }
81
+ removeSourcesContent(sourceMap);
82
+ const uploadDirectory = path_1.default.join(ctx.workingdir, SOURCE_MAP_UPLOAD_DIRECTORY);
83
+ const uploadPath = path_1.default.join(uploadDirectory, `${ctx.job.platform}.map`);
84
+ await fs_extra_1.default.ensureDir(uploadDirectory);
85
+ await fs_extra_1.default.writeFile(uploadPath, JSON.stringify(sourceMap), 'utf8');
86
+ return uploadPath;
87
+ }
88
+ function removeSourcesContent(value) {
89
+ if (Array.isArray(value)) {
90
+ for (const child of value) {
91
+ removeSourcesContent(child);
92
+ }
93
+ return;
94
+ }
95
+ if (!isRecord(value)) {
96
+ return;
97
+ }
98
+ delete value.sourcesContent;
99
+ for (const child of Object.values(value)) {
100
+ removeSourcesContent(child);
101
+ }
102
+ }
103
+ function isRecord(value) {
104
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
105
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@expo/build-tools",
3
- "version": "21.8.0",
3
+ "version": "22.0.0",
4
4
  "bugs": "https://github.com/expo/eas-cli/issues",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Expo <support@expo.io>",
@@ -38,17 +38,17 @@
38
38
  "dependencies": {
39
39
  "@expo/config": "55.0.10",
40
40
  "@expo/config-plugins": "55.0.7",
41
- "@expo/downloader": "21.0.0",
42
- "@expo/eas-build-job": "21.6.0",
41
+ "@expo/downloader": "22.0.0",
42
+ "@expo/eas-build-job": "22.0.0",
43
43
  "@expo/env": "^0.4.0",
44
- "@expo/logger": "21.0.0",
44
+ "@expo/logger": "22.0.0",
45
45
  "@expo/package-manager": "1.9.10",
46
46
  "@expo/plist": "^0.3.5",
47
47
  "@expo/results": "^1.0.0",
48
48
  "@expo/spawn-async": "1.7.2",
49
- "@expo/steps": "21.7.1",
50
- "@expo/template-file": "21.0.2",
51
- "@expo/turtle-spawn": "21.0.0",
49
+ "@expo/steps": "22.0.0",
50
+ "@expo/template-file": "22.0.0",
51
+ "@expo/turtle-spawn": "22.0.0",
52
52
  "@expo/xcpretty": "^4.3.1",
53
53
  "@ngrok/ngrok": "1.7.0",
54
54
  "@sentry/node": "7.77.0",
@@ -100,5 +100,5 @@
100
100
  "typescript": "^5.5.4",
101
101
  "uuid": "^9.0.1"
102
102
  },
103
- "gitHead": "b8cff2008966a6c7202736577cd231025ee47f21"
103
+ "gitHead": "b92a9cc5a718f133966e27b9fedcedb569f9cd17"
104
104
  }