@expo/build-tools 21.0.1 → 21.0.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/builders/android.js +12 -1
- package/dist/builders/ios.js +13 -1
- package/dist/common/easBuildInternal.js +3 -0
- package/dist/common/hookableBuildPhase.d.ts +18 -0
- package/dist/common/hookableBuildPhase.js +72 -0
- package/dist/common/jobHooks.d.ts +16 -0
- package/dist/common/jobHooks.js +95 -0
- package/dist/common/setup.d.ts +14 -2
- package/dist/common/setup.js +40 -21
- package/dist/context.js +1 -31
- package/dist/index.d.ts +4 -3
- package/dist/index.js +5 -5
- package/dist/{gcs/LoggerStream.d.ts → logging/RemoteLoggerStream.d.ts} +6 -10
- package/dist/{gcs/LoggerStream.js → logging/RemoteLoggerStream.js} +13 -27
- package/dist/steps/functions/maestroResultParser.d.ts +1 -0
- package/dist/steps/functions/maestroResultParser.js +12 -0
- package/dist/steps/functions/maestroScreenshots.d.ts +1 -0
- package/dist/steps/functions/maestroScreenshots.js +167 -34
- package/dist/steps/functions/maestroTests.js +4 -0
- package/dist/steps/functions/startServeSimRemoteSession.js +2 -3
- package/dist/steps/functions/uploadDeviceRunSessionScreenRecordings.js +20 -1
- package/dist/steps/utils/IosSimulatorRecordingUtils.js +6 -2
- package/dist/steps/utils/remoteDeviceRunSession.d.ts +0 -1
- package/dist/steps/utils/remoteDeviceRunSession.js +7 -8
- package/dist/{gcs → storage}/retry.d.ts +5 -4
- package/dist/{gcs → storage}/retry.js +13 -9
- package/dist/storage/uploadWithSignedUrl.d.ts +12 -0
- package/dist/storage/uploadWithSignedUrl.js +68 -0
- package/package.json +7 -8
- package/resources/record-sim/Sources/RecordSim/RecordingModels.swift +7 -0
- package/resources/record-sim/Sources/RecordSim/RecordingOutputWriter.swift +5 -1
- package/resources/record-sim/Sources/RecordSim/SimulatorRecorder.swift +13 -1
- package/resources/record-sim/Sources/record-sim/main.swift +20 -4
- package/dist/gcs/__unit__/gcs.test.d.ts +0 -1
- package/dist/gcs/__unit__/gcs.test.js +0 -335
- package/dist/gcs/client.d.ts +0 -58
- package/dist/gcs/client.js +0 -173
package/dist/builders/android.js
CHANGED
|
@@ -41,7 +41,18 @@ async function androidBuilder(ctx) {
|
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
async function buildAsync(ctx) {
|
|
44
|
-
|
|
44
|
+
const jobHooksRef = { current: null };
|
|
45
|
+
try {
|
|
46
|
+
await buildInnerAsync(ctx, jobHooksRef);
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
// Native builds have no other drain path for the hook context's queued
|
|
50
|
+
// step-metric uploads.
|
|
51
|
+
await jobHooksRef.current?.customBuildContext.drainPendingMetricUploads();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async function buildInnerAsync(ctx, jobHooksRef) {
|
|
55
|
+
await (0, setup_1.setupAsync)(ctx, { wrappedAnchors: ['install_node_modules'], jobHooksRef });
|
|
45
56
|
const evictUsedBefore = new Date();
|
|
46
57
|
const workingDirectory = ctx.getReactNativeProjectDirectory();
|
|
47
58
|
const hasNativeCode = ctx.job.type === eas_build_job_1.Workflow.GENERIC;
|
package/dist/builders/ios.js
CHANGED
|
@@ -50,7 +50,19 @@ async function iosBuilder(ctx) {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
async function buildAsync(ctx) {
|
|
53
|
-
|
|
53
|
+
const jobHooksRef = { current: null };
|
|
54
|
+
try {
|
|
55
|
+
await buildInnerAsync(ctx, jobHooksRef);
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
// Native builds have no other drain path for the hook context's queued
|
|
59
|
+
// step-metric uploads. Must wrap the WHOLE body — iOS's existing inner
|
|
60
|
+
// try/finally only starts after setupAsync returns.
|
|
61
|
+
await jobHooksRef.current?.customBuildContext.drainPendingMetricUploads();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async function buildInnerAsync(ctx, jobHooksRef) {
|
|
65
|
+
await (0, setup_1.setupAsync)(ctx, { wrappedAnchors: ['install_node_modules'], jobHooksRef });
|
|
54
66
|
const hasNativeCode = ctx.job.type === eas_build_job_1.Workflow.GENERIC;
|
|
55
67
|
const evictUsedBefore = new Date();
|
|
56
68
|
const credentialsManager = new manager_1.default(ctx);
|
|
@@ -111,6 +111,9 @@ function validateEasBuildInternalResult({ oldJob, result, }) {
|
|
|
111
111
|
// We want to retain values that we have set on the job.
|
|
112
112
|
appId: oldJob.appId,
|
|
113
113
|
initiatingUserId: oldJob.initiatingUserId,
|
|
114
|
+
// Hooks are resolved server-side at submit; the mid-build eas.json
|
|
115
|
+
// regeneration must not add, drop, or change them (the original wins).
|
|
116
|
+
hooks: oldJob.hooks,
|
|
114
117
|
...(oldJob.platform === eas_build_job_1.Platform.IOS && oldJob.refreshAdHocProvisioningProfile === true
|
|
115
118
|
? { refreshAdHocProvisioningProfile: true }
|
|
116
119
|
: null),
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { BuildJob, BuildPhase, HookAnchorId } from '@expo/eas-build-job';
|
|
2
|
+
import { BuildContext } from '../context';
|
|
3
|
+
import { ParsedJobHooks } from './jobHooks';
|
|
4
|
+
/**
|
|
5
|
+
* Runs a native build phase with its `before_<anchor>` / `after_<anchor>` hooks
|
|
6
|
+
* wrapped around it: before-entries -> phase (in a try) -> after-entries, with
|
|
7
|
+
* after-on-failure guaranteed. A failing before sequence skips the phase AND the
|
|
8
|
+
* after-hooks (steps-engine parity). The phase error always wins over an
|
|
9
|
+
* after-hook error. Passthrough (no hooks or no keys for this anchor) runs the
|
|
10
|
+
* phase exactly once with zero overhead.
|
|
11
|
+
*/
|
|
12
|
+
export declare function runHookableBuildPhaseAsync<TJob extends BuildJob, T>({ ctx, hooks, buildPhase, anchor, fn, }: {
|
|
13
|
+
ctx: BuildContext<TJob>;
|
|
14
|
+
hooks: ParsedJobHooks | null;
|
|
15
|
+
buildPhase: BuildPhase;
|
|
16
|
+
anchor: HookAnchorId;
|
|
17
|
+
fn: () => Promise<T>;
|
|
18
|
+
}): Promise<T>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runHookableBuildPhaseAsync = runHookableBuildPhaseAsync;
|
|
4
|
+
const eas_build_job_1 = require("@expo/eas-build-job");
|
|
5
|
+
const steps_1 = require("@expo/steps");
|
|
6
|
+
/**
|
|
7
|
+
* Runs a native build phase with its `before_<anchor>` / `after_<anchor>` hooks
|
|
8
|
+
* wrapped around it: before-entries -> phase (in a try) -> after-entries, with
|
|
9
|
+
* after-on-failure guaranteed. A failing before sequence skips the phase AND the
|
|
10
|
+
* after-hooks (steps-engine parity). The phase error always wins over an
|
|
11
|
+
* after-hook error. Passthrough (no hooks or no keys for this anchor) runs the
|
|
12
|
+
* phase exactly once with zero overhead.
|
|
13
|
+
*/
|
|
14
|
+
async function runHookableBuildPhaseAsync({ ctx, hooks, buildPhase, anchor, fn, }) {
|
|
15
|
+
// runHookEntriesAsync no-ops when the key has no entries.
|
|
16
|
+
if (hooks) {
|
|
17
|
+
await runHookEntriesAsync({ ctx, hooks, timing: 'before', anchor });
|
|
18
|
+
}
|
|
19
|
+
let result;
|
|
20
|
+
try {
|
|
21
|
+
result = await ctx.runBuildPhase(buildPhase, fn);
|
|
22
|
+
}
|
|
23
|
+
catch (phaseError) {
|
|
24
|
+
if (hooks) {
|
|
25
|
+
try {
|
|
26
|
+
await runHookEntriesAsync({ ctx, hooks, timing: 'after', anchor, anchorFailed: true });
|
|
27
|
+
}
|
|
28
|
+
catch (hookError) {
|
|
29
|
+
// The phase error wins; still surface the after-hook failure in the log.
|
|
30
|
+
ctx.logger.error({ err: hookError }, `after_${anchor} hook failed`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
throw phaseError;
|
|
34
|
+
}
|
|
35
|
+
if (hooks) {
|
|
36
|
+
await runHookEntriesAsync({ ctx, hooks, timing: 'after', anchor, anchorFailed: false });
|
|
37
|
+
}
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
async function runHookEntriesAsync({ ctx, hooks, timing, anchor, anchorFailed = false, }) {
|
|
41
|
+
const hookKey = `${timing}_${anchor}`;
|
|
42
|
+
const entries = hooks.hookEntriesByKey[hookKey];
|
|
43
|
+
if (!entries?.length) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
// Refresh the hook context from the live BuildContext before running: env
|
|
47
|
+
// moves as the build runs (set-env written back below; profile env), and
|
|
48
|
+
// job/metadata are replaced by eas build:internal.
|
|
49
|
+
hooks.customBuildContext.updateEnv(ctx.env);
|
|
50
|
+
// Overwrite the job/metadata snapshot; NOT updateJobInformation, whose
|
|
51
|
+
// delta-merge would duplicate environmentSecrets on each refresh.
|
|
52
|
+
hooks.customBuildContext.job = ctx.job;
|
|
53
|
+
hooks.customBuildContext.metadata = ctx.metadata;
|
|
54
|
+
if (timing === 'after' && anchorFailed) {
|
|
55
|
+
// Mirror the engine: the anchor's failure is marked on the shared context so
|
|
56
|
+
// `failure()` / `success()` on after-hook steps read the phase outcome.
|
|
57
|
+
hooks.globalContext.markAsFailed();
|
|
58
|
+
}
|
|
59
|
+
const { failedLocally, firstError } = await (0, steps_1.executeHookStepsAsync)(hooks.globalContext, entries, {
|
|
60
|
+
anchor,
|
|
61
|
+
timing,
|
|
62
|
+
...(timing === 'after' ? { anchorResult: anchorFailed ? 'failed' : 'success' } : null),
|
|
63
|
+
});
|
|
64
|
+
// Reverse write-back BEFORE propagating a failure: env set by the entries that
|
|
65
|
+
// did run must reach the build context even when a later entry failed, so the
|
|
66
|
+
// native phases (and the outer lifecycle hooks) see it. Hook-carrying builds
|
|
67
|
+
// are git-based, so updateEnv's gate does not fire here.
|
|
68
|
+
ctx.updateEnv(hooks.globalContext.env);
|
|
69
|
+
if (failedLocally) {
|
|
70
|
+
throw new eas_build_job_1.UserError(eas_build_job_1.ErrorCode.HOOKS_ERROR, `Hook "${hookKey}" failed: ${firstError instanceof Error ? firstError.message : String(firstError)}. Check the failing hook step's logs above.`, { cause: firstError });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { BuildJob, HookAnchorId, HookKey } from '@expo/eas-build-job';
|
|
2
|
+
import { BuildStepGlobalContext, HookEntry } from '@expo/steps';
|
|
3
|
+
import { BuildContext } from '../context';
|
|
4
|
+
import { CustomBuildContext } from '../customBuildContext';
|
|
5
|
+
export interface ParsedJobHooks {
|
|
6
|
+
globalContext: BuildStepGlobalContext;
|
|
7
|
+
customBuildContext: CustomBuildContext;
|
|
8
|
+
hookEntriesByKey: Partial<Record<HookKey, HookEntry[]>>;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Parses the native job's `hooks` into engine-ready entries, once, at setup
|
|
12
|
+
* time. Shape-validates every registered key (reachable or not); constructs
|
|
13
|
+
* entries only for the anchors this build actually wraps. Returns null when the
|
|
14
|
+
* job declares no hooks.
|
|
15
|
+
*/
|
|
16
|
+
export declare function parseJobHooksAsync<TJob extends BuildJob>(ctx: BuildContext<TJob>, wrappedAnchors: readonly HookAnchorId[]): Promise<ParsedJobHooks | null>;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseJobHooksAsync = parseJobHooksAsync;
|
|
4
|
+
const eas_build_job_1 = require("@expo/eas-build-job");
|
|
5
|
+
const steps_1 = require("@expo/steps");
|
|
6
|
+
const customBuildContext_1 = require("../customBuildContext");
|
|
7
|
+
const easFunctionGroups_1 = require("../steps/easFunctionGroups");
|
|
8
|
+
const easFunctions_1 = require("../steps/easFunctions");
|
|
9
|
+
/**
|
|
10
|
+
* Parses the native job's `hooks` into engine-ready entries, once, at setup
|
|
11
|
+
* time. Shape-validates every registered key (reachable or not); constructs
|
|
12
|
+
* entries only for the anchors this build actually wraps. Returns null when the
|
|
13
|
+
* job declares no hooks.
|
|
14
|
+
*/
|
|
15
|
+
async function parseJobHooksAsync(ctx, wrappedAnchors) {
|
|
16
|
+
const hooks = ctx.job.hooks;
|
|
17
|
+
if (!hooks || Object.keys(hooks).length === 0) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
const customBuildContext = new customBuildContext_1.CustomBuildContext(ctx);
|
|
21
|
+
const globalContext = new steps_1.BuildStepGlobalContext(customBuildContext, false);
|
|
22
|
+
// Hook steps run in the checked-out project directory, not the pre-checkout
|
|
23
|
+
// target directory the global context defaults to.
|
|
24
|
+
globalContext.markAsCheckedOut(ctx.logger);
|
|
25
|
+
const externalFunctions = (0, easFunctions_1.getEasFunctions)(customBuildContext);
|
|
26
|
+
const externalFunctionGroups = (0, easFunctionGroups_1.getEasFunctionGroups)(customBuildContext);
|
|
27
|
+
// Shape-validate every REGISTERED key, reachable or not (steps-world parity:
|
|
28
|
+
// a malformed `before_submit` fails a steps build, so it fails here too), and
|
|
29
|
+
// warn on keys this build won't run. An unknown key is inert — a key newer
|
|
30
|
+
// than this worker must be ignored, never fail the job.
|
|
31
|
+
for (const [key, steps] of Object.entries(hooks)) {
|
|
32
|
+
const parsed = (0, eas_build_job_1.parseHookKey)(key);
|
|
33
|
+
if (parsed === null) {
|
|
34
|
+
ctx.logger.warn(`Ignoring unknown hook key "${key}".`);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (Array.isArray(steps) && steps.length === 0) {
|
|
38
|
+
// `[]` is an explicit opt-out; validateSteps requires >= 1 step.
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
(0, eas_build_job_1.validateSteps)(steps);
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
throw new eas_build_job_1.UserError(eas_build_job_1.ErrorCode.HOOKS_ERROR, `Invalid steps in "hooks.${key}": ${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
46
|
+
}
|
|
47
|
+
// Registered but not wrapped by this build: shape-checked above, then
|
|
48
|
+
// skipped. Function existence is deliberately NOT checked (that happens only
|
|
49
|
+
// during construction) — an unknown `uses:` under an unwrapped key warns
|
|
50
|
+
// rather than failing the build.
|
|
51
|
+
if (!wrappedAnchors.includes(parsed.anchorId)) {
|
|
52
|
+
ctx.logger.warn(`Ignoring "hooks.${key}": this build does not run the "${parsed.anchorId}" step.`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// Construct entries for the wrapped anchors in EXECUTION order (per anchor,
|
|
56
|
+
// before_ then after_) so generated step ids and output references follow the
|
|
57
|
+
// order steps actually run. All entries share one globalContext, so env and
|
|
58
|
+
// outputs accumulate across keys.
|
|
59
|
+
const hookEntriesByKey = {};
|
|
60
|
+
const orderedSteps = [];
|
|
61
|
+
for (const anchor of wrappedAnchors) {
|
|
62
|
+
for (const side of ['before', 'after']) {
|
|
63
|
+
const key = `${side}_${anchor}`;
|
|
64
|
+
const steps = hooks[key];
|
|
65
|
+
if (!Array.isArray(steps) || steps.length === 0) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
let entries;
|
|
69
|
+
try {
|
|
70
|
+
entries = await (0, steps_1.constructHookEntriesAsync)(globalContext, steps, {
|
|
71
|
+
externalFunctions,
|
|
72
|
+
externalFunctionGroups,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
throw new eas_build_job_1.UserError(eas_build_job_1.ErrorCode.HOOKS_ERROR, `Failed to parse hooks.${key}: ${err instanceof Error ? err.message : String(err)}. Hooks use the same syntax as workflow steps.`, { cause: err });
|
|
77
|
+
}
|
|
78
|
+
hookEntriesByKey[key] = entries;
|
|
79
|
+
for (const entry of entries) {
|
|
80
|
+
orderedSteps.push(...entry.steps);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// One aggregate pass over every constructed step in execution order: unique
|
|
85
|
+
// ids, output references, and platform allowance. Per-key validation misses
|
|
86
|
+
// cross-key id collisions and would reject a legal after -> before output
|
|
87
|
+
// reference as a future reference.
|
|
88
|
+
try {
|
|
89
|
+
await (0, steps_1.validateHookStepsAsync)(globalContext, orderedSteps);
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
throw new eas_build_job_1.UserError(eas_build_job_1.ErrorCode.HOOKS_ERROR, `The job's hooks are invalid: ${err instanceof Error ? err.message : String(err)}. Fix the reported step ids or output references.`, { cause: err });
|
|
93
|
+
}
|
|
94
|
+
return { globalContext, customBuildContext, hookEntriesByKey };
|
|
95
|
+
}
|
package/dist/common/setup.d.ts
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
-
import { BuildJob } from '@expo/eas-build-job';
|
|
1
|
+
import { BuildJob, HookAnchorId } from '@expo/eas-build-job';
|
|
2
|
+
import { ParsedJobHooks } from './jobHooks';
|
|
2
3
|
import { BuildContext } from '../context';
|
|
3
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Mutable holder the builder passes into setupAsync. setupAsync assigns
|
|
6
|
+
* `current` the moment hooks are parsed (before any hook runs), so the builder's
|
|
7
|
+
* finally can drain queued metric uploads even if setup fails partway through.
|
|
8
|
+
*/
|
|
9
|
+
export interface JobHooksRef {
|
|
10
|
+
current: ParsedJobHooks | null;
|
|
11
|
+
}
|
|
12
|
+
export declare function setupAsync<TJob extends BuildJob>(ctx: BuildContext<TJob>, { wrappedAnchors, jobHooksRef, }: {
|
|
13
|
+
wrappedAnchors: readonly HookAnchorId[];
|
|
14
|
+
jobHooksRef: JobHooksRef;
|
|
15
|
+
}): Promise<void>;
|
package/dist/common/setup.js
CHANGED
|
@@ -10,7 +10,9 @@ const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
|
10
10
|
const nullthrows_1 = __importDefault(require("nullthrows"));
|
|
11
11
|
const path_1 = __importDefault(require("path"));
|
|
12
12
|
const easBuildInternal_1 = require("./easBuildInternal");
|
|
13
|
+
const hookableBuildPhase_1 = require("./hookableBuildPhase");
|
|
13
14
|
const installDependencies_1 = require("./installDependencies");
|
|
15
|
+
const jobHooks_1 = require("./jobHooks");
|
|
14
16
|
const projectSources_1 = require("./projectSources");
|
|
15
17
|
const xcodeEnv_1 = require("../ios/xcodeEnv");
|
|
16
18
|
const hooks_1 = require("../utils/hooks");
|
|
@@ -26,7 +28,7 @@ class DoctorTimeoutError extends Error {
|
|
|
26
28
|
}
|
|
27
29
|
class InstallDependenciesTimeoutError extends Error {
|
|
28
30
|
}
|
|
29
|
-
async function setupAsync(ctx) {
|
|
31
|
+
async function setupAsync(ctx, { wrappedAnchors, jobHooksRef, }) {
|
|
30
32
|
await ctx.runBuildPhase(eas_build_job_1.BuildPhase.PREPARE_PROJECT, async () => {
|
|
31
33
|
await (0, retry_1.retryAsync)(async () => {
|
|
32
34
|
await fs_extra_1.default.rm(ctx.buildDirectory, { recursive: true, force: true });
|
|
@@ -64,27 +66,42 @@ async function setupAsync(ctx) {
|
|
|
64
66
|
ctx.logger.error({ err }, `Failed to read eas.json.`);
|
|
65
67
|
}
|
|
66
68
|
});
|
|
67
|
-
|
|
68
|
-
|
|
69
|
+
await ctx.runBuildPhase(eas_build_job_1.BuildPhase.READ_PACKAGE_JSON, async () => {
|
|
70
|
+
(0, project_1.readAndLogPackageJson)(ctx.logger, ctx.getReactNativeProjectDirectory());
|
|
69
71
|
});
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
// Parse hooks unconditionally, after every pre-install env mutation
|
|
73
|
+
// (PREPARE_PROJECT, the git-based profile-env block, PRE_INSTALL_HOOK) and
|
|
74
|
+
// before the first wrapped phase.
|
|
75
|
+
jobHooksRef.current = await (0, jobHooks_1.parseJobHooksAsync)(ctx, wrappedAnchors);
|
|
76
|
+
await (0, hookableBuildPhase_1.runHookableBuildPhaseAsync)({
|
|
77
|
+
ctx,
|
|
78
|
+
hooks: jobHooksRef.current,
|
|
79
|
+
buildPhase: eas_build_job_1.BuildPhase.INSTALL_DEPENDENCIES,
|
|
80
|
+
anchor: 'install_node_modules',
|
|
81
|
+
fn: async () => {
|
|
82
|
+
// Read package.json fresh here rather than reusing the READ_PACKAGE_JSON
|
|
83
|
+
// capture: a before_install_node_modules hook may have patched it, and the
|
|
84
|
+
// steps-world install already reads it fresh. Silent read — READ_PACKAGE_JSON
|
|
85
|
+
// stays the only logging site.
|
|
86
|
+
const freshPackageJson = (0, project_1.readPackageJson)(ctx.getReactNativeProjectDirectory());
|
|
87
|
+
const expoVersion = ctx.metadata?.sdkVersion ??
|
|
88
|
+
(0, packageManager_1.getPackageVersionFromPackageJson)({
|
|
89
|
+
packageJson: freshPackageJson,
|
|
90
|
+
packageName: 'expo',
|
|
91
|
+
});
|
|
92
|
+
const reactNativeVersion = ctx.metadata?.reactNativeVersion ??
|
|
93
|
+
(0, packageManager_1.getPackageVersionFromPackageJson)({
|
|
94
|
+
packageJson: freshPackageJson,
|
|
95
|
+
packageName: 'react-native',
|
|
96
|
+
});
|
|
97
|
+
await runInstallDependenciesAsync(ctx, {
|
|
98
|
+
useFrozenLockfile: (0, packageManager_1.shouldUseFrozenLockfile)({
|
|
99
|
+
env: ctx.env,
|
|
100
|
+
sdkVersion: expoVersion,
|
|
101
|
+
reactNativeVersion,
|
|
102
|
+
}),
|
|
75
103
|
});
|
|
76
|
-
|
|
77
|
-
(0, packageManager_1.getPackageVersionFromPackageJson)({
|
|
78
|
-
packageJson,
|
|
79
|
-
packageName: 'react-native',
|
|
80
|
-
});
|
|
81
|
-
await runInstallDependenciesAsync(ctx, {
|
|
82
|
-
useFrozenLockfile: (0, packageManager_1.shouldUseFrozenLockfile)({
|
|
83
|
-
env: ctx.env,
|
|
84
|
-
sdkVersion: expoVersion,
|
|
85
|
-
reactNativeVersion,
|
|
86
|
-
}),
|
|
87
|
-
});
|
|
104
|
+
},
|
|
88
105
|
});
|
|
89
106
|
await ctx.runBuildPhase(eas_build_job_1.BuildPhase.READ_APP_CONFIG, async () => {
|
|
90
107
|
const appConfig = await ctx.appConfig;
|
|
@@ -111,7 +128,9 @@ async function setupAsync(ctx) {
|
|
|
111
128
|
ctx.updateJobInformation(newJob, newMetadata);
|
|
112
129
|
});
|
|
113
130
|
}
|
|
114
|
-
|
|
131
|
+
// Read fresh: a before_install_node_modules hook may have added or removed
|
|
132
|
+
// the expo dependency.
|
|
133
|
+
const hasExpoPackage = !!(0, project_1.readPackageJson)(ctx.getReactNativeProjectDirectory()).dependencies?.expo;
|
|
115
134
|
if (!ctx.env.EAS_BUILD_DISABLE_EXPO_DOCTOR_STEP && hasExpoPackage) {
|
|
116
135
|
await ctx.runBuildPhase(eas_build_job_1.BuildPhase.RUN_EXPO_DOCTOR, async () => {
|
|
117
136
|
try {
|
package/dist/context.js
CHANGED
|
@@ -17,26 +17,6 @@ const packageManager_1 = require("./utils/packageManager");
|
|
|
17
17
|
class SkipNativeBuildError extends Error {
|
|
18
18
|
}
|
|
19
19
|
exports.SkipNativeBuildError = SkipNativeBuildError;
|
|
20
|
-
function logEnvComparison({ oldMergeOrderEnv, newMergeOrderEnv, }) {
|
|
21
|
-
const envNames = new Set([...Object.keys(oldMergeOrderEnv), ...Object.keys(newMergeOrderEnv)]);
|
|
22
|
-
const sameEnvNames = [];
|
|
23
|
-
const differentEnvNames = [];
|
|
24
|
-
for (const key of envNames) {
|
|
25
|
-
const hasOldValue = key in oldMergeOrderEnv;
|
|
26
|
-
const hasNewValue = key in newMergeOrderEnv;
|
|
27
|
-
if (hasOldValue === hasNewValue && oldMergeOrderEnv[key] === newMergeOrderEnv[key]) {
|
|
28
|
-
sameEnvNames.push(key);
|
|
29
|
-
}
|
|
30
|
-
else {
|
|
31
|
-
differentEnvNames.push(key);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
if (differentEnvNames.length === 0) {
|
|
35
|
-
datadog_1.Datadog.log('BuildContext.updateEnv merge order produced same env');
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
datadog_1.Datadog.log(`BuildContext.updateEnv merge order produced different env: compared_env_count=${envNames.size} different_env_count=${differentEnvNames.length} different_env_names=${differentEnvNames.join(',')} same_env_count=${sameEnvNames.length} same_env_names=${sameEnvNames.join(',')}`);
|
|
39
|
-
}
|
|
40
20
|
class BuildContext {
|
|
41
21
|
workingdir;
|
|
42
22
|
logger;
|
|
@@ -177,21 +157,11 @@ class BuildContext {
|
|
|
177
157
|
if (this._job.triggeredBy !== common_1.BuildTrigger.GIT_BASED_INTEGRATION) {
|
|
178
158
|
throw new Error('Updating environment variables is only allowed when build was triggered by a git-based integration.');
|
|
179
159
|
}
|
|
180
|
-
|
|
181
|
-
...env,
|
|
182
|
-
...this._env,
|
|
183
|
-
__EAS_BUILD_ENVS_DIR: this.buildEnvsDirectory,
|
|
184
|
-
};
|
|
185
|
-
const newMergeOrderEnv = {
|
|
160
|
+
this._env = {
|
|
186
161
|
...this._env,
|
|
187
162
|
...env,
|
|
188
163
|
__EAS_BUILD_ENVS_DIR: this.buildEnvsDirectory,
|
|
189
164
|
};
|
|
190
|
-
logEnvComparison({
|
|
191
|
-
oldMergeOrderEnv,
|
|
192
|
-
newMergeOrderEnv,
|
|
193
|
-
});
|
|
194
|
-
this._env = newMergeOrderEnv;
|
|
195
165
|
this._env.PATH = this._env.PATH
|
|
196
166
|
? [this.buildExecutablesDirectory, this._env.PATH].join(':')
|
|
197
167
|
: this.buildExecutablesDirectory;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as Builders from './builders';
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
export {
|
|
2
|
+
import RemoteLoggerStream from './logging/RemoteLoggerStream';
|
|
3
|
+
export { Builders, RemoteLoggerStream };
|
|
4
|
+
export { uploadWithSignedUrl } from './storage/uploadWithSignedUrl';
|
|
5
|
+
export type { SignedUrl, UploadWithSignedUrlParams } from './storage/uploadWithSignedUrl';
|
|
5
6
|
export { ArtifactToUpload, Artifacts, BuildContext, BuildContextOptions, CacheManager, LogBuffer, SkipNativeBuildError, } from './context';
|
|
6
7
|
export { PackageManager } from './utils/packageManager';
|
|
7
8
|
export { findAndUploadXcodeBuildLogsAsync } from './ios/xcodeBuildLogs';
|
package/dist/index.js
CHANGED
|
@@ -39,13 +39,13 @@ 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.Sentry = exports.Datadog = exports.formatGradleProfileReport = exports.parseGradleProfile = exports.runHookIfPresent = exports.Hook = exports.findAndUploadXcodeBuildLogsAsync = exports.PackageManager = exports.SkipNativeBuildError = exports.BuildContext = exports.
|
|
42
|
+
exports.Sentry = exports.Datadog = exports.formatGradleProfileReport = exports.parseGradleProfile = exports.runHookIfPresent = exports.Hook = exports.findAndUploadXcodeBuildLogsAsync = exports.PackageManager = exports.SkipNativeBuildError = exports.BuildContext = exports.uploadWithSignedUrl = exports.RemoteLoggerStream = exports.Builders = void 0;
|
|
43
43
|
const Builders = __importStar(require("./builders"));
|
|
44
44
|
exports.Builders = Builders;
|
|
45
|
-
const
|
|
46
|
-
exports.
|
|
47
|
-
|
|
48
|
-
exports
|
|
45
|
+
const RemoteLoggerStream_1 = __importDefault(require("./logging/RemoteLoggerStream"));
|
|
46
|
+
exports.RemoteLoggerStream = RemoteLoggerStream_1.default;
|
|
47
|
+
var uploadWithSignedUrl_1 = require("./storage/uploadWithSignedUrl");
|
|
48
|
+
Object.defineProperty(exports, "uploadWithSignedUrl", { enumerable: true, get: function () { return uploadWithSignedUrl_1.uploadWithSignedUrl; } });
|
|
49
49
|
var context_1 = require("./context");
|
|
50
50
|
Object.defineProperty(exports, "BuildContext", { enumerable: true, get: function () { return context_1.BuildContext; } });
|
|
51
51
|
Object.defineProperty(exports, "SkipNativeBuildError", { enumerable: true, get: function () { return context_1.SkipNativeBuildError; } });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { bunyan } from '@expo/logger';
|
|
2
2
|
import { Writable } from 'stream';
|
|
3
|
-
import
|
|
4
|
-
declare class
|
|
3
|
+
import { type SignedUrl } from '../storage/uploadWithSignedUrl';
|
|
4
|
+
declare class RemoteLoggerStream extends Writable {
|
|
5
5
|
writable: boolean;
|
|
6
6
|
private readonly logger;
|
|
7
7
|
private readonly uploadMethod?;
|
|
@@ -16,7 +16,7 @@ declare class GCSLoggerStream extends Writable {
|
|
|
16
16
|
private buffer;
|
|
17
17
|
private writePromise?;
|
|
18
18
|
private cleanUpCalled;
|
|
19
|
-
constructor({ logger, uploadMethod, options }:
|
|
19
|
+
constructor({ logger, uploadMethod, options }: RemoteLoggerStream.Config);
|
|
20
20
|
private findNormalizedHeader;
|
|
21
21
|
init(): Promise<string>;
|
|
22
22
|
cleanUp(): Promise<void>;
|
|
@@ -27,7 +27,7 @@ declare class GCSLoggerStream extends Writable {
|
|
|
27
27
|
private upload;
|
|
28
28
|
private createCompressedStream;
|
|
29
29
|
}
|
|
30
|
-
declare namespace
|
|
30
|
+
declare namespace RemoteLoggerStream {
|
|
31
31
|
enum CompressionMethod {
|
|
32
32
|
GZIP = "gzip",
|
|
33
33
|
BR = "br"
|
|
@@ -37,11 +37,7 @@ declare namespace GCSLoggerStream {
|
|
|
37
37
|
compress?: CompressionMethod;
|
|
38
38
|
}
|
|
39
39
|
type UploadMethod = {
|
|
40
|
-
|
|
41
|
-
key: string;
|
|
42
|
-
customTime: Date | null;
|
|
43
|
-
} | {
|
|
44
|
-
signedUrl: GCS.SignedUrl;
|
|
40
|
+
signedUrl: SignedUrl;
|
|
45
41
|
};
|
|
46
42
|
interface Config {
|
|
47
43
|
logger: bunyan;
|
|
@@ -49,4 +45,4 @@ declare namespace GCSLoggerStream {
|
|
|
49
45
|
options: Options;
|
|
50
46
|
}
|
|
51
47
|
}
|
|
52
|
-
export default
|
|
48
|
+
export default RemoteLoggerStream;
|
|
@@ -11,9 +11,9 @@ const path_1 = __importDefault(require("path"));
|
|
|
11
11
|
const stream_1 = require("stream");
|
|
12
12
|
const util_1 = require("util");
|
|
13
13
|
const zlib_1 = __importDefault(require("zlib"));
|
|
14
|
-
const
|
|
14
|
+
const uploadWithSignedUrl_1 = require("../storage/uploadWithSignedUrl");
|
|
15
15
|
const pipe = (0, util_1.promisify)(stream_1.pipeline);
|
|
16
|
-
class
|
|
16
|
+
class RemoteLoggerStream extends stream_1.Writable {
|
|
17
17
|
writable = true;
|
|
18
18
|
logger;
|
|
19
19
|
uploadMethod;
|
|
@@ -38,7 +38,7 @@ class GCSLoggerStream extends stream_1.Writable {
|
|
|
38
38
|
this.temporaryCompressedLogsPath = `${this.temporaryLogsPath}.compressed`;
|
|
39
39
|
}
|
|
40
40
|
findNormalizedHeader(name) {
|
|
41
|
-
if (!this.uploadMethod
|
|
41
|
+
if (!this.uploadMethod) {
|
|
42
42
|
return null;
|
|
43
43
|
}
|
|
44
44
|
const normalizedName = name.toLowerCase().replace('-', '');
|
|
@@ -74,7 +74,7 @@ class GCSLoggerStream extends stream_1.Writable {
|
|
|
74
74
|
await this.flush(true);
|
|
75
75
|
await fs_extra_1.default.remove(this.temporaryLogsPath);
|
|
76
76
|
await fs_extra_1.default.remove(this.temporaryCompressedLogsPath);
|
|
77
|
-
this.logger.info('Cleaning up
|
|
77
|
+
this.logger.info('Cleaning up log stream');
|
|
78
78
|
}
|
|
79
79
|
write(rec) {
|
|
80
80
|
if (!this.fileHandle) {
|
|
@@ -102,7 +102,7 @@ class GCSLoggerStream extends stream_1.Writable {
|
|
|
102
102
|
this.hasChangesToUpload = true;
|
|
103
103
|
}
|
|
104
104
|
catch (err) {
|
|
105
|
-
this.logger.error({ err, origin: '
|
|
105
|
+
this.logger.error({ err, origin: 'remote-logger' }, 'Failed to write logs to file');
|
|
106
106
|
}
|
|
107
107
|
finally {
|
|
108
108
|
this.writePromise = undefined;
|
|
@@ -129,7 +129,7 @@ class GCSLoggerStream extends stream_1.Writable {
|
|
|
129
129
|
return result;
|
|
130
130
|
})
|
|
131
131
|
.catch(err => {
|
|
132
|
-
this.logger.error({ err }, 'Failed to upload logs file
|
|
132
|
+
this.logger.error({ err }, 'Failed to upload logs file');
|
|
133
133
|
})
|
|
134
134
|
.then(result => {
|
|
135
135
|
this.uploadingPromise = undefined;
|
|
@@ -145,24 +145,10 @@ class GCSLoggerStream extends stream_1.Writable {
|
|
|
145
145
|
const srcGeneratorAsync = async () => {
|
|
146
146
|
return await this.createCompressedStream(fs_extra_1.default.createReadStream(this.temporaryLogsPath, { end: size }));
|
|
147
147
|
};
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
srcGeneratorAsync,
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
const { Location } = await this.uploadMethod.client.uploadFile({
|
|
155
|
-
key: this.uploadMethod.key,
|
|
156
|
-
src: await srcGeneratorAsync(),
|
|
157
|
-
streamOptions: {
|
|
158
|
-
metadata: {
|
|
159
|
-
contentType: 'text/plain;charset=utf-8',
|
|
160
|
-
...(this.compress !== null ? { contentEncoding: this.compress } : {}),
|
|
161
|
-
customTime: this.uploadMethod.customTime,
|
|
162
|
-
},
|
|
163
|
-
},
|
|
148
|
+
return await (0, uploadWithSignedUrl_1.uploadWithSignedUrl)({
|
|
149
|
+
signedUrl: this.uploadMethod.signedUrl,
|
|
150
|
+
srcGeneratorAsync,
|
|
164
151
|
});
|
|
165
|
-
return Location;
|
|
166
152
|
}
|
|
167
153
|
async createCompressedStream(src) {
|
|
168
154
|
if (!this.compress) {
|
|
@@ -184,11 +170,11 @@ class GCSLoggerStream extends stream_1.Writable {
|
|
|
184
170
|
return fs_extra_1.default.createReadStream(this.temporaryCompressedLogsPath, { end: size });
|
|
185
171
|
}
|
|
186
172
|
}
|
|
187
|
-
(function (
|
|
173
|
+
(function (RemoteLoggerStream) {
|
|
188
174
|
let CompressionMethod;
|
|
189
175
|
(function (CompressionMethod) {
|
|
190
176
|
CompressionMethod["GZIP"] = "gzip";
|
|
191
177
|
CompressionMethod["BR"] = "br";
|
|
192
|
-
})(CompressionMethod =
|
|
193
|
-
})(
|
|
194
|
-
exports.default =
|
|
178
|
+
})(CompressionMethod = RemoteLoggerStream.CompressionMethod || (RemoteLoggerStream.CompressionMethod = {}));
|
|
179
|
+
})(RemoteLoggerStream || (RemoteLoggerStream = {}));
|
|
180
|
+
exports.default = RemoteLoggerStream;
|
|
@@ -17,6 +17,7 @@ export interface JUnitTestCaseResult {
|
|
|
17
17
|
tags: string[];
|
|
18
18
|
properties: Record<string, string>;
|
|
19
19
|
}
|
|
20
|
+
export declare function parseFailedFlowNamesFromJUnitFile(junitFile: string): Promise<Set<string>>;
|
|
20
21
|
export declare function parseJUnitTestCases(junitDirectory: string): Promise<JUnitTestCaseResult[]>;
|
|
21
22
|
export declare function isFileAttrRun(testcases: JUnitTestCaseResult[]): testcases is (JUnitTestCaseResult & {
|
|
22
23
|
file: string;
|
|
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.parseFailedFlowNamesFromJUnitFile = parseFailedFlowNamesFromJUnitFile;
|
|
6
7
|
exports.parseJUnitTestCases = parseJUnitTestCases;
|
|
7
8
|
exports.isFileAttrRun = isFileAttrRun;
|
|
8
9
|
exports.junitFileHasFileAttrs = junitFileHasFileAttrs;
|
|
@@ -99,6 +100,17 @@ async function parseJUnitFile(filePath) {
|
|
|
99
100
|
return [];
|
|
100
101
|
}
|
|
101
102
|
}
|
|
103
|
+
// Raw names (slashes kept) of the testcases a single JUnit file marks failed. Best-effort: an
|
|
104
|
+
// unreadable or malformed file yields an empty set rather than throwing.
|
|
105
|
+
async function parseFailedFlowNamesFromJUnitFile(junitFile) {
|
|
106
|
+
const failed = new Set();
|
|
107
|
+
for (const testcase of await parseJUnitFile(junitFile)) {
|
|
108
|
+
if (testcase.status === 'failed') {
|
|
109
|
+
failed.add(testcase.name);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return failed;
|
|
113
|
+
}
|
|
102
114
|
async function parseJUnitTestCases(junitDirectory) {
|
|
103
115
|
let entries;
|
|
104
116
|
try {
|
|
@@ -18,6 +18,7 @@ export declare function harvestFailureScreenshotsAsync(args: {
|
|
|
18
18
|
testsDirectory: string;
|
|
19
19
|
capturedSinceMs: number;
|
|
20
20
|
attemptIndex: number;
|
|
21
|
+
failedFlowNames: ReadonlySet<string>;
|
|
21
22
|
logger: bunyan;
|
|
22
23
|
}): Promise<HarvestedScreenshot[]>;
|
|
23
24
|
export declare function computePureFailureFlowNames(testCases: readonly {
|