@expo/build-tools 18.12.1 → 18.12.3

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/index.d.ts CHANGED
@@ -7,3 +7,4 @@ export { PackageManager } from './utils/packageManager';
7
7
  export { findAndUploadXcodeBuildLogsAsync } from './ios/xcodeBuildLogs';
8
8
  export { Hook, runHookIfPresent } from './utils/hooks';
9
9
  export * from './generic';
10
+ export { Sentry } from './sentry';
package/dist/index.js CHANGED
@@ -39,7 +39,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
39
39
  return (mod && mod.__esModule) ? mod : { "default": mod };
40
40
  };
41
41
  Object.defineProperty(exports, "__esModule", { value: true });
42
- exports.runHookIfPresent = exports.Hook = exports.findAndUploadXcodeBuildLogsAsync = exports.PackageManager = exports.SkipNativeBuildError = exports.BuildContext = exports.GCSLoggerStream = exports.GCS = exports.Builders = void 0;
42
+ exports.Sentry = exports.runHookIfPresent = exports.Hook = exports.findAndUploadXcodeBuildLogsAsync = exports.PackageManager = exports.SkipNativeBuildError = exports.BuildContext = exports.GCSLoggerStream = exports.GCS = exports.Builders = void 0;
43
43
  const Builders = __importStar(require("./builders"));
44
44
  exports.Builders = Builders;
45
45
  const LoggerStream_1 = __importDefault(require("./gcs/LoggerStream"));
@@ -57,3 +57,5 @@ var hooks_1 = require("./utils/hooks");
57
57
  Object.defineProperty(exports, "Hook", { enumerable: true, get: function () { return hooks_1.Hook; } });
58
58
  Object.defineProperty(exports, "runHookIfPresent", { enumerable: true, get: function () { return hooks_1.runHookIfPresent; } });
59
59
  __exportStar(require("./generic"), exports);
60
+ var sentry_1 = require("./sentry");
61
+ Object.defineProperty(exports, "Sentry", { enumerable: true, get: function () { return sentry_1.Sentry; } });
@@ -0,0 +1,19 @@
1
+ import * as sentryNode from '@sentry/node';
2
+ type CaptureOptions = {
3
+ tags?: Record<string, string>;
4
+ extras?: Record<string, unknown>;
5
+ level?: sentryNode.SeverityLevel;
6
+ };
7
+ type SentryAPI = {
8
+ setup(opts: {
9
+ dsn: string | null;
10
+ environment: string;
11
+ tags?: Record<string, string>;
12
+ }): void;
13
+ capture(msg: string, options?: CaptureOptions): void;
14
+ capture(msg: string, err: Error | undefined, options?: CaptureOptions): void;
15
+ capture(err: Error, options?: CaptureOptions): void;
16
+ flush(timeoutMs?: number): Promise<boolean>;
17
+ };
18
+ export declare const Sentry: SentryAPI;
19
+ export {};
package/dist/sentry.js ADDED
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.Sentry = void 0;
37
+ const sentryNode = __importStar(require("@sentry/node"));
38
+ exports.Sentry = {
39
+ setup({ dsn, environment, tags }) {
40
+ if (dsn) {
41
+ sentryNode.init({
42
+ dsn,
43
+ environment,
44
+ ...(tags ? { initialScope: { tags } } : {}),
45
+ });
46
+ }
47
+ },
48
+ capture(arg1, arg2, arg3) {
49
+ let msg;
50
+ let err;
51
+ let options = {};
52
+ if (arg1 instanceof Error) {
53
+ err = arg1;
54
+ options = arg2 ?? {};
55
+ }
56
+ else {
57
+ msg = arg1;
58
+ if (arg3 !== undefined) {
59
+ // 3-arg form: arg2 unambiguously means err (null/undefined → no err)
60
+ options = arg3;
61
+ if (arg2 !== undefined && arg2 !== null) {
62
+ err = arg2 instanceof Error ? arg2 : new Error(String(arg2));
63
+ }
64
+ }
65
+ else if (arg2 instanceof Error) {
66
+ err = arg2;
67
+ }
68
+ else if (arg2 !== undefined && arg2 !== null) {
69
+ // 2-arg form: non-Error object → options; primitive → coerced err
70
+ if (typeof arg2 === 'object') {
71
+ options = arg2;
72
+ }
73
+ else {
74
+ err = new Error(String(arg2));
75
+ }
76
+ }
77
+ }
78
+ sentryNode.withScope(scope => {
79
+ if (options.tags) {
80
+ scope.setTags(options.tags);
81
+ }
82
+ if (options.extras) {
83
+ scope.setExtras(options.extras);
84
+ }
85
+ if (options.level) {
86
+ scope.setLevel(options.level);
87
+ }
88
+ if (err) {
89
+ if (msg && err.message !== msg) {
90
+ scope.setExtra('message', msg);
91
+ }
92
+ sentryNode.captureException(err);
93
+ }
94
+ else if (msg) {
95
+ sentryNode.captureMessage(msg);
96
+ }
97
+ });
98
+ },
99
+ flush(timeoutMs = 2000) {
100
+ return sentryNode.flush(timeoutMs);
101
+ },
102
+ };
@@ -81,17 +81,33 @@ function createStartAgentDeviceRemoteSessionBuildFunction(ctx) {
81
81
  env,
82
82
  });
83
83
  logger.info('Waiting for a public tunnel URL.');
84
- const tunnelUrl = await waitForMatchInOutputAsync({
84
+ const agentDeviceRemoteSessionUrl = await waitForMatchInOutputAsync({
85
85
  process: cloudflared,
86
86
  pattern: /https:\/\/[a-z0-9-]+\.trycloudflare\.com/,
87
87
  timeoutMs: STARTUP_TIMEOUT_MS,
88
88
  description: 'cloudflared tunnel',
89
89
  });
90
- logger.info(`Tunnel is ready at ${tunnelUrl}.`);
90
+ logger.info(`Tunnel is ready at ${agentDeviceRemoteSessionUrl}.`);
91
+ // serve-sim is iOS-only — only launch it (and report a webPreviewUrl)
92
+ // on Darwin. Android sessions go without a preview URL.
93
+ let webPreviewUrl;
94
+ if (runtimePlatform === steps_1.BuildRuntimePlatform.DARWIN) {
95
+ const { previewUrl } = await (0, remoteDeviceRunSession_1.startServeSimWithTunnelAsync)({
96
+ env,
97
+ logger,
98
+ timeoutMs: STARTUP_TIMEOUT_MS,
99
+ });
100
+ webPreviewUrl = previewUrl;
101
+ logger.info(`Web preview URL: ${webPreviewUrl}`);
102
+ }
91
103
  await (0, remoteDeviceRunSession_1.uploadRemoteSessionConfigAsync)({
92
104
  ctx,
93
105
  deviceRunSessionId,
94
- remoteConfig: { url: tunnelUrl, token: daemonToken },
106
+ remoteConfig: {
107
+ agentDeviceRemoteSessionUrl,
108
+ agentDeviceRemoteSessionToken: daemonToken,
109
+ ...(webPreviewUrl ? { webPreviewUrl } : {}),
110
+ },
95
111
  logger,
96
112
  });
97
113
  logger.info('Remote session is live. Keeping the job alive until the session is stopped.');
@@ -4,14 +4,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.createStartServeSimRemoteSessionBuildFunction = createStartServeSimRemoteSessionBuildFunction;
7
- const eas_build_job_1 = require("@expo/eas-build-job");
8
7
  const steps_1 = require("@expo/steps");
9
8
  const turtle_spawn_1 = __importDefault(require("@expo/turtle-spawn"));
10
- const retry_1 = require("../../utils/retry");
11
9
  const remoteDeviceRunSession_1 = require("../utils/remoteDeviceRunSession");
12
10
  const XCODE_DEVELOPER_DIR = '/Applications/Xcode.app/Contents/Developer';
13
11
  const STARTUP_TIMEOUT_MS = 60_000;
14
- const TRYCLOUDFLARE_URL_PATTERN = /https:\/\/[a-z0-9-]+\.trycloudflare\.com/;
15
12
  function createStartServeSimRemoteSessionBuildFunction(ctx) {
16
13
  return new steps_1.BuildFunction({
17
14
  namespace: 'eas',
@@ -26,15 +23,9 @@ function createStartServeSimRemoteSessionBuildFunction(ctx) {
26
23
  await (0, turtle_spawn_1.default)('sudo', ['xcode-select', '-s', XCODE_DEVELOPER_DIR], { env, logger });
27
24
  logger.info('Ensuring cloudflared is installed.');
28
25
  await (0, remoteDeviceRunSession_1.ensureBrewPackageInstalledAsync)({ name: 'cloudflared', env, logger });
29
- logger.info('Launching serve-sim with tunnel.');
30
- const serveSim = (0, remoteDeviceRunSession_1.spawnDetached)({
31
- command: 'npx',
32
- args: ['serve-sim-szdziedzic@latest', '--tunnel'],
26
+ const { previewUrl, streamUrl } = await (0, remoteDeviceRunSession_1.startServeSimWithTunnelAsync)({
33
27
  env,
34
- });
35
- logger.info('Waiting for serve-sim to report tunnel and stream URLs.');
36
- const { previewUrl, streamUrl } = await waitForServeSimUrlsAsync({
37
- serveSim,
28
+ logger,
38
29
  timeoutMs: STARTUP_TIMEOUT_MS,
39
30
  });
40
31
  logger.info(`Preview URL: ${previewUrl}`);
@@ -52,21 +43,3 @@ function createStartServeSimRemoteSessionBuildFunction(ctx) {
52
43
  },
53
44
  });
54
45
  }
55
- async function waitForServeSimUrlsAsync({ serveSim, timeoutMs, }) {
56
- const deadline = Date.now() + timeoutMs;
57
- while (Date.now() < deadline) {
58
- const output = serveSim.getOutput();
59
- const previewUrl = matchLabeledUrl(output, 'Tunnel');
60
- const streamUrl = matchLabeledUrl(output, 'Stream');
61
- if (previewUrl && streamUrl) {
62
- return { previewUrl, streamUrl };
63
- }
64
- await (0, retry_1.sleepAsync)(1_000);
65
- }
66
- throw new eas_build_job_1.SystemError(`Timed out waiting for serve-sim to report Tunnel and Stream URLs. Last output:\n${serveSim.getOutput() || '<empty>'}`);
67
- }
68
- function matchLabeledUrl(content, label) {
69
- const labelPattern = new RegExp(`${label}:\\s*(${TRYCLOUDFLARE_URL_PATTERN.source})`);
70
- const match = labelPattern.exec(content);
71
- return match ? match[1] : null;
72
- }
@@ -1,11 +1,9 @@
1
1
  import { bunyan } from '@expo/logger';
2
2
  import { z } from 'zod';
3
3
  /**
4
- * Download xclogparser, parse xcactivitylog from derived data, and log a
5
- * compile metrics report. Never throws all errors are logged at debug level.
6
- *
7
- * Can be called from both the step-based flow (BuildFunction) and the
8
- * traditional builder flow (runBuildPhase).
4
+ * Never throws best-effort observability that does not affect build status.
5
+ * Failures route to Sentry via `Sentry.capture` for engineering triage;
6
+ * users see only a generic skip message.
9
7
  */
10
8
  export declare function parseAndReportXcactivitylog({ derivedDataPath, workspacePath, xclogparserVersion, logger, proxyBaseUrl, }: {
11
9
  derivedDataPath: string;
@@ -15,33 +15,38 @@ const fs_extra_1 = __importDefault(require("fs-extra"));
15
15
  const os_1 = __importDefault(require("os"));
16
16
  const path_1 = __importDefault(require("path"));
17
17
  const zod_1 = require("zod");
18
+ const sentry_1 = require("../../../sentry");
18
19
  const DEFAULT_XCLOGPARSER_VERSION = 'v0.2.47';
19
20
  const XCLOGPARSER_DOWNLOAD_URL = 'https://storage.googleapis.com/turtle-v2/xclogparser';
20
21
  const XCLOGPARSER_DOWNLOAD_TIMEOUT_MS = 20_000;
21
22
  const XCLOGPARSER_OUTPUT_FILENAME = 'xcactivitylog.json';
22
23
  /**
23
- * Download xclogparser, parse xcactivitylog from derived data, and log a
24
- * compile metrics report. Never throws all errors are logged at debug level.
25
- *
26
- * Can be called from both the step-based flow (BuildFunction) and the
27
- * traditional builder flow (runBuildPhase).
24
+ * Never throws best-effort observability that does not affect build status.
25
+ * Failures route to Sentry via `Sentry.capture` for engineering triage;
26
+ * users see only a generic skip message.
28
27
  */
29
28
  async function parseAndReportXcactivitylog({ derivedDataPath, workspacePath, xclogparserVersion = DEFAULT_XCLOGPARSER_VERSION, logger, proxyBaseUrl, }) {
30
29
  let tempDir;
30
+ let phase = 'creating_temp_directory';
31
31
  try {
32
32
  tempDir = await fs_extra_1.default.mkdtemp(path_1.default.join(os_1.default.tmpdir(), 'xclogparser-'));
33
+ phase = 'downloading_xclogparser';
33
34
  const xclogparserPath = await downloadXclogparser(tempDir, xclogparserVersion, logger, proxyBaseUrl);
35
+ phase = 'running_xclogparser';
34
36
  const jsonOutputPath = await runXclogparser({
35
37
  binaryPath: xclogparserPath,
36
38
  derivedDataPath,
37
39
  workspacePath,
38
40
  outputDir: tempDir,
39
41
  });
42
+ phase = 'parsing_xclogparser_output';
40
43
  const data = XcactivitylogDataSchemaZ.parse(JSON.parse(await fs_extra_1.default.readFile(jsonOutputPath, 'utf8')));
41
44
  logger.info(formatReport(data));
42
45
  }
43
46
  catch (err) {
44
- logger.debug({ err }, 'Failed to analyze build performance; continuing without a report');
47
+ logger.info('Build performance analysis skipped.');
48
+ const msg = `Build performance analysis failed during "${phase}"`;
49
+ sentry_1.Sentry.capture(msg, err, { tags: { phase } });
45
50
  }
46
51
  finally {
47
52
  if (tempDir) {
@@ -22,3 +22,11 @@ export declare function spawnDetached({ command, args, cwd, env, }: {
22
22
  cwd?: string;
23
23
  env: BuildStepEnv;
24
24
  }): DetachedProcessHandle;
25
+ export declare function startServeSimWithTunnelAsync({ env, logger, timeoutMs, }: {
26
+ env: BuildStepEnv;
27
+ logger: bunyan;
28
+ timeoutMs: number;
29
+ }): Promise<{
30
+ previewUrl: string;
31
+ streamUrl: string;
32
+ }>;
@@ -7,9 +7,12 @@ exports.getDeviceRunSessionIdOrThrow = getDeviceRunSessionIdOrThrow;
7
7
  exports.uploadRemoteSessionConfigAsync = uploadRemoteSessionConfigAsync;
8
8
  exports.ensureBrewPackageInstalledAsync = ensureBrewPackageInstalledAsync;
9
9
  exports.spawnDetached = spawnDetached;
10
+ exports.startServeSimWithTunnelAsync = startServeSimWithTunnelAsync;
10
11
  const eas_build_job_1 = require("@expo/eas-build-job");
11
12
  const turtle_spawn_1 = __importDefault(require("@expo/turtle-spawn"));
12
13
  const gql_tada_1 = require("gql.tada");
14
+ const retry_1 = require("../../utils/retry");
15
+ const TRYCLOUDFLARE_URL_PATTERN = /https:\/\/[a-z0-9-]+\.trycloudflare\.com/;
13
16
  const START_DEVICE_RUN_SESSION_MUTATION = (0, gql_tada_1.graphql)(`
14
17
  mutation StartDeviceRunSession($deviceRunSessionId: ID!, $remoteConfig: JSONObject!) {
15
18
  deviceRunSession {
@@ -63,3 +66,28 @@ function spawnDetached({ command, args, cwd, env, }) {
63
66
  promise.child.stderr?.on('data', appendChunk);
64
67
  return { getOutput: () => output };
65
68
  }
69
+ async function startServeSimWithTunnelAsync({ env, logger, timeoutMs, }) {
70
+ logger.info('Launching serve-sim with tunnel.');
71
+ const serveSim = spawnDetached({
72
+ command: 'npx',
73
+ args: ['serve-sim-szdziedzic@latest', '--tunnel'],
74
+ env,
75
+ });
76
+ logger.info('Waiting for serve-sim to report tunnel and stream URLs.');
77
+ const deadline = Date.now() + timeoutMs;
78
+ while (Date.now() < deadline) {
79
+ const output = serveSim.getOutput();
80
+ const previewUrl = matchLabeledUrl(output, 'Tunnel');
81
+ const streamUrl = matchLabeledUrl(output, 'Stream');
82
+ if (previewUrl && streamUrl) {
83
+ return { previewUrl, streamUrl };
84
+ }
85
+ await (0, retry_1.sleepAsync)(1_000);
86
+ }
87
+ throw new eas_build_job_1.SystemError(`Timed out waiting for serve-sim to report Tunnel and Stream URLs. Last output:\n${serveSim.getOutput() || '<empty>'}`);
88
+ }
89
+ function matchLabeledUrl(content, label) {
90
+ const labelPattern = new RegExp(`${label}:\\s*(${TRYCLOUDFLARE_URL_PATTERN.source})`);
91
+ const match = labelPattern.exec(content);
92
+ return match ? match[1] : null;
93
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@expo/build-tools",
3
- "version": "18.12.1",
3
+ "version": "18.12.3",
4
4
  "bugs": "https://github.com/expo/eas-cli/issues",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Expo <support@expo.io>",
@@ -50,6 +50,7 @@
50
50
  "@expo/turtle-spawn": "18.5.0",
51
51
  "@expo/xcpretty": "^4.3.1",
52
52
  "@google-cloud/storage": "^7.11.2",
53
+ "@sentry/node": "7.77.0",
53
54
  "@urql/core": "^6.0.1",
54
55
  "bplist-parser": "0.3.2",
55
56
  "fast-glob": "^3.3.2",
@@ -98,5 +99,5 @@
98
99
  "typescript": "^5.5.4",
99
100
  "uuid": "^9.0.1"
100
101
  },
101
- "gitHead": "f46b469267f4f87be6a6d1acff22464c8cf89b26"
102
+ "gitHead": "985cd9d52f4de333cf563cc7fe92f4e2d1e1c8e5"
102
103
  }