mirror of
https://github.com/expo/eas-cli.git
synced 2026-11-14 10:08:06 -10:00
Compare commits
23
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d38664a38a | ||
|
|
e699a06c07 | ||
|
|
ce819c1a14 | ||
|
|
92ad6b5ba3 | ||
|
|
d63d51f548 | ||
|
|
bfb12f0317 | ||
|
|
b162162007 | ||
|
|
6ed7e14a69 | ||
|
|
37910b17e1 | ||
|
|
0d3c56f4d6 | ||
|
|
bc8e60d385 | ||
|
|
99b8d67d3f | ||
|
|
4e202db843 | ||
|
|
ab209463ab | ||
|
|
99e9d367a1 | ||
|
|
a6f68462c9 | ||
|
|
1c58823991 | ||
|
|
6a804ab8d7 | ||
|
|
74eff9bf67 | ||
|
|
d20c465ddf | ||
|
|
0c17846e67 | ||
|
|
d5a3fc06f9 | ||
|
|
c51ac27e25 |
No files matched your search
@@ -0,0 +1,11 @@
|
||||
# THIS IS AUTOGENERATED. DO NOT EDIT MANUALLY
|
||||
version = 1
|
||||
name = "eas-cli"
|
||||
|
||||
[setup]
|
||||
script = '''
|
||||
mise trust
|
||||
mise install
|
||||
yarn
|
||||
yarn typecheck
|
||||
'''
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
with:
|
||||
script: |
|
||||
core.setFailed('Please add a changelog entry!')
|
||||
- uses: mshick/add-pr-comment@v2.1.0
|
||||
- uses: mshick/add-pr-comment@v2.8.2
|
||||
if: always()
|
||||
with:
|
||||
message-id: changelog-entry-check
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"packages/eas-cli/README.md",
|
||||
"packages/eas-cli/schema/**",
|
||||
"packages/eas-cli/graphql.schema.json",
|
||||
"packages/eas-cli/src/graphql/generated.ts",
|
||||
"packages/build-tools/schema.graphql"
|
||||
]
|
||||
}
|
||||
@@ -3,3 +3,4 @@
|
||||
When working with this repository follow instructions from CLAUDE.md.
|
||||
|
||||
- [./CLAUDE.md](./CLAUDE.md)
|
||||
- Before committing, run the formatter on modified files.
|
||||
+9
-3
@@ -8,12 +8,19 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Add `--skip-build-if-not-found` flag to `eas build:dev`. ([#3495](https://github.com/expo/eas-cli/pull/3495) by [@mmichels-brex](https://github.com/mmichels-brex))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- [eas-cli] Fix workflow:logs for builds using built-in EAS build steps. ([#3523](https://github.com/expo/eas-cli/pull/3523) by [@douglowder](https://github.com/douglowder))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
## [18.4.0](https://github.com/expo/eas-cli/releases/tag/v18.4.0) - 2026-03-16
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- Auto-detect non-interactive mode in CI and non-TTY environments. ([#3486](https://github.com/expo/eas-cli/pull/3486) by [@EvanBacon](https://github.com/EvanBacon))
|
||||
- Add `skip-build-if-not-found` flag to `eas build:dev`. ([#3495](https://github.com/expo/eas-cli/pull/3495) by [@mmichels-brex](https://github.com/mmichels-brex))
|
||||
|
||||
## [18.3.0](https://github.com/expo/eas-cli/releases/tag/v18.3.0) - 2026-03-10
|
||||
|
||||
### 🎉 New features
|
||||
@@ -1675,7 +1682,6 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
- Add rollback disambiguation command. ([#2004](https://github.com/expo/eas-cli/pull/2004) by [@wschurman](https://github.com/wschurman))
|
||||
- Detect devices that fail to be provisioned, list them to the user and show the explanation message with the link to the devices page to check actual status. ([#2011](https://github.com/expo/eas-cli/pull/2011) by [@radoslawkrzemien](https://github.com/radoslawkrzemien))
|
||||
- Add info to EAS Update asset upload process about asset counts and limits. ([#2013](https://github.com/expo/eas-cli/pull/2013) by [@wschurman](https://github.com/wschurman))
|
||||
- .nvmrc support for setting node version. ([#1954](https://github.com/expo/eas-cli/pull/1954) by [@khamilowicz](https://github.com/khamilowicz))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"npmClient": "yarn",
|
||||
"version": "18.3.0",
|
||||
"version": "18.4.0",
|
||||
"$schema": "node_modules/lerna/schemas/lerna-schema.json"
|
||||
}
|
||||
+2
-1
@@ -17,7 +17,7 @@
|
||||
"lint-changelog": "./scripts/bin/run lint-changelog",
|
||||
"release": "lerna run rebuild --concurrency 1 && ./scripts/bin/run release",
|
||||
"test": "lerna run test",
|
||||
"clean": "lerna run clean && rm -rf node_modules coverage"
|
||||
"clean": "lerna run clean && rimraf node_modules coverage"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "29.5.11",
|
||||
@@ -30,6 +30,7 @@
|
||||
"oxfmt": "0.28.0",
|
||||
"oxlint": "1.43.0",
|
||||
"oxlint-tsgolint": "0.11.1",
|
||||
"rimraf": "3.0.2",
|
||||
"ts-jest": "29.1.1",
|
||||
"typescript": "5.5.4"
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@expo/build-tools",
|
||||
"version": "18.2.0",
|
||||
"version": "18.4.0",
|
||||
"bugs": "https://github.com/expo/eas-cli/issues",
|
||||
"license": "BUSL-1.1",
|
||||
"author": "Expo <[email protected]>",
|
||||
@@ -24,12 +24,12 @@
|
||||
"prebuild": "yarn gql",
|
||||
"build": "tsc",
|
||||
"typecheck": "tsc",
|
||||
"prepack": "rm -rf dist *.tsbuildinfo && yarn gql && tsc -p tsconfig.build.json",
|
||||
"prepack": "rimraf dist \"*.tsbuildinfo\" && yarn gql && tsc -p tsconfig.build.json",
|
||||
"jest-unit": "jest --config jest/unit-config.ts",
|
||||
"jest-integration": "jest --config jest/integration-config.ts",
|
||||
"jest-unit-watch": "jest --config jest/unit-config.ts --watch",
|
||||
"jest-integration-watch": "jest --config jest/integration-config.ts --watch",
|
||||
"clean": "rm -rf node_modules dist coverage *.tsbuildinfo",
|
||||
"clean": "rimraf node_modules dist coverage \"*.tsbuildinfo\"",
|
||||
"gql": "gql.tada generate-schema ${API_SERVER_URL:-https://api.expo.dev}/graphql --output ./schema.graphql && gql.tada generate-output",
|
||||
"gql:local": "API_SERVER_URL=http://api.expo.test yarn gql",
|
||||
"test": "yarn jest-unit"
|
||||
@@ -38,14 +38,14 @@
|
||||
"@expo/config": "10.0.6",
|
||||
"@expo/config-plugins": "9.0.12",
|
||||
"@expo/downloader": "18.0.1",
|
||||
"@expo/eas-build-job": "18.2.0",
|
||||
"@expo/eas-build-job": "18.4.0",
|
||||
"@expo/env": "^0.4.0",
|
||||
"@expo/logger": "18.0.1",
|
||||
"@expo/package-manager": "1.9.10",
|
||||
"@expo/plist": "^0.2.0",
|
||||
"@expo/results": "^1.0.0",
|
||||
"@expo/spawn-async": "1.7.2",
|
||||
"@expo/steps": "18.2.0",
|
||||
"@expo/steps": "18.4.0",
|
||||
"@expo/template-file": "18.0.1",
|
||||
"@expo/turtle-spawn": "18.0.1",
|
||||
"@expo/xcpretty": "^4.3.1",
|
||||
@@ -75,7 +75,7 @@
|
||||
"zod": "^4.3.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@expo/repack-app": "~0.2.5",
|
||||
"@expo/repack-app": "~0.4.1",
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/lodash": "^4.17.4",
|
||||
@@ -91,6 +91,7 @@
|
||||
"jest": "^29.7.0",
|
||||
"memfs": "^4.17.1",
|
||||
"nock": "13.4.0",
|
||||
"rimraf": "3.0.2",
|
||||
"ts-jest": "^29.1.4",
|
||||
"ts-mockito": "^2.6.1",
|
||||
"tslib": "^2.6.3",
|
||||
|
||||
@@ -67,28 +67,26 @@ export async function resolveBuildPhaseErrorAsync(
|
||||
logLines: string[],
|
||||
errorContext: ErrorContext,
|
||||
buildLogsDirectory: string
|
||||
): Promise<errors.BuildError> {
|
||||
): Promise<errors.ExpoError> {
|
||||
const { phase } = errorContext;
|
||||
if (error instanceof errors.BuildError) {
|
||||
if (error instanceof errors.ExpoError) {
|
||||
error.buildPhase ??= phase;
|
||||
return error;
|
||||
}
|
||||
const xcodeBuildLogs = await maybeReadXcodeBuildLogs(phase, buildLogsDirectory);
|
||||
const userError =
|
||||
error instanceof errors.UserError
|
||||
? error
|
||||
: (resolveError(userErrorHandlers, logLines, errorContext, xcodeBuildLogs) ??
|
||||
new errors.UnknownError(errorContext.phase));
|
||||
resolveError(userErrorHandlers, logLines, errorContext, xcodeBuildLogs) ??
|
||||
new errors.UnknownError(errorContext.phase);
|
||||
const buildError = resolveError(buildErrorHandlers, logLines, errorContext, xcodeBuildLogs);
|
||||
|
||||
const trackingCode =
|
||||
buildError && buildError.errorCode !== userError.errorCode ? buildError.errorCode : undefined;
|
||||
|
||||
return new errors.BuildError(userError.message, {
|
||||
errorCode: userError.errorCode,
|
||||
return new errors.UserError(userError.errorCode, userError.message, {
|
||||
trackingCode,
|
||||
docsUrl: userError.docsUrl,
|
||||
cause: error,
|
||||
buildPhase: phase,
|
||||
metadata: buildError?.metadata,
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
@@ -287,7 +287,7 @@ export class BuildContext<TJob extends Job = Job> {
|
||||
private async handleBuildPhaseErrorAsync(
|
||||
err: any,
|
||||
buildPhase: BuildPhase
|
||||
): Promise<errors.BuildError> {
|
||||
): Promise<errors.ExpoError> {
|
||||
const buildError = await resolveBuildPhaseErrorAsync(
|
||||
err,
|
||||
this.logBuffer.getPhaseLogs(buildPhase),
|
||||
|
||||
@@ -51,7 +51,6 @@ export function createEasMaestroTestFunctionGroup(
|
||||
steps.push(
|
||||
new BuildStep(globalCtx, {
|
||||
id: BuildStep.getNewId(),
|
||||
name: 'install_app',
|
||||
displayName: `Install app to Simulator`,
|
||||
command: `
|
||||
# shopt -s nullglob is necessary not to try to install
|
||||
@@ -97,7 +96,6 @@ export function createEasMaestroTestFunctionGroup(
|
||||
steps.push(
|
||||
new BuildStep(globalCtx, {
|
||||
id: BuildStep.getNewId(),
|
||||
name: 'install_app',
|
||||
displayName: `Install app to Emulator`,
|
||||
command: `
|
||||
# shopt -s globstar is necessary to add /**/ support
|
||||
@@ -134,7 +132,6 @@ export function createEasMaestroTestFunctionGroup(
|
||||
steps.push(
|
||||
new BuildStep(globalCtx, {
|
||||
id: BuildStep.getNewId(),
|
||||
name: 'maestro_test',
|
||||
ifCondition: '${ always() }',
|
||||
displayName: `maestro test ${flowPath}`,
|
||||
command: `maestro test ${flowPath}`,
|
||||
|
||||
@@ -177,6 +177,34 @@ describe(createInternalEasMaestroTestFunction, () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('generates per-attempt report filenames when retrying', async () => {
|
||||
// Mock sequence:
|
||||
// 1. xcrun simctl shutdown → succeeds (beforeEach default is fine)
|
||||
// 2. maestro test attempt 0 → fails (triggers retry)
|
||||
// 3. maestro test attempt 1 → succeeds (beforeEach default)
|
||||
mockedSpawnAsync
|
||||
.mockResolvedValueOnce(undefined as any) // xcrun simctl shutdown
|
||||
.mockRejectedValueOnce(new Error('Maestro test failed')); // maestro attempt 0
|
||||
// Attempt 1 falls through to beforeEach's mockResolvedValue default
|
||||
|
||||
const step = createStep({
|
||||
callInputs: { output_format: 'junit', retries: 2 },
|
||||
});
|
||||
await step.executeAsync();
|
||||
|
||||
// Filter to just the maestro test command calls (skip xcrun simctl shutdown)
|
||||
const maestroCalls = mockedSpawnAsync.mock.calls.filter(([cmd]) => cmd === 'maestro');
|
||||
expect(maestroCalls).toHaveLength(2);
|
||||
|
||||
// Extract --output paths from maestro calls
|
||||
const outputArgs = maestroCalls.map(([, args]) => args[args.indexOf('--output') + 1]);
|
||||
|
||||
expect(outputArgs[0]).toContain('attempt-0');
|
||||
expect(outputArgs[1]).toContain('attempt-1');
|
||||
// Filenames must be unique (not overwriting)
|
||||
expect(outputArgs[0]).not.toBe(outputArgs[1]);
|
||||
});
|
||||
|
||||
it('fails Android flow when clone startup exhausts all attempts', async () => {
|
||||
mockedAndroidUtils.startAsync
|
||||
.mockResolvedValueOnce({
|
||||
|
||||
@@ -217,6 +217,217 @@ describe(parseMaestroResults, () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns per-attempt results when multiple JUnit files exist for the same flow', async () => {
|
||||
vol.fromJSON({
|
||||
// Attempt 0: login FAILED
|
||||
'/junit/junit-report-flow-1-attempt-0.xml': [
|
||||
'<?xml version="1.0"?>',
|
||||
'<testsuites>',
|
||||
' <testsuite name="Test Suite" tests="1" failures="1">',
|
||||
' <testcase id="login" name="login" classname="login" time="5.0" status="ERROR">',
|
||||
' <failure>Timeout</failure>',
|
||||
' </testcase>',
|
||||
' </testsuite>',
|
||||
'</testsuites>',
|
||||
].join('\n'),
|
||||
// Attempt 1: login PASSED
|
||||
'/junit/junit-report-flow-1-attempt-1.xml': [
|
||||
'<?xml version="1.0"?>',
|
||||
'<testsuites>',
|
||||
' <testsuite name="Test Suite" tests="1" failures="0">',
|
||||
' <testcase id="login" name="login" classname="login" time="3.0" status="SUCCESS"/>',
|
||||
' </testsuite>',
|
||||
'</testsuites>',
|
||||
].join('\n'),
|
||||
// ai-*.json metadata (2 timestamp dirs = 2 attempts)
|
||||
'/tests/2026-01-28_055409/ai-login.json': JSON.stringify({
|
||||
flow_name: 'login',
|
||||
flow_file_path: '/root/project/.maestro/login.yml',
|
||||
}),
|
||||
'/tests/2026-01-28_055420/ai-login.json': JSON.stringify({
|
||||
flow_name: 'login',
|
||||
flow_file_path: '/root/project/.maestro/login.yml',
|
||||
}),
|
||||
});
|
||||
|
||||
const results = await parseMaestroResults('/junit', '/tests', '/root/project');
|
||||
|
||||
// Should return 2 results — one per attempt
|
||||
expect(results).toHaveLength(2);
|
||||
expect(results).toEqual([
|
||||
expect.objectContaining({
|
||||
name: 'login',
|
||||
path: '.maestro/login.yml',
|
||||
status: 'failed',
|
||||
errorMessage: 'Timeout',
|
||||
duration: 5000,
|
||||
retryCount: 0,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
name: 'login',
|
||||
path: '.maestro/login.yml',
|
||||
status: 'passed',
|
||||
errorMessage: null,
|
||||
duration: 3000,
|
||||
retryCount: 1,
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns per-attempt results for reuse_devices=true (all flows in every attempt)', async () => {
|
||||
vol.fromJSON({
|
||||
// Attempt 0: home FAILED, login PASSED
|
||||
'/junit-reports/android-maestro-junit-attempt-0.xml': [
|
||||
'<?xml version="1.0"?>',
|
||||
'<testsuites>',
|
||||
' <testsuite name="Test Suite" tests="2" failures="1">',
|
||||
' <testcase id="home" name="home" classname="home" time="5.0" status="ERROR">',
|
||||
' <failure>Timeout</failure>',
|
||||
' </testcase>',
|
||||
' <testcase id="login" name="login" classname="login" time="3.0" status="SUCCESS"/>',
|
||||
' </testsuite>',
|
||||
'</testsuites>',
|
||||
].join('\n'),
|
||||
// Attempt 1: both PASSED
|
||||
'/junit-reports/android-maestro-junit-attempt-1.xml': [
|
||||
'<?xml version="1.0"?>',
|
||||
'<testsuites>',
|
||||
' <testsuite name="Test Suite" tests="2" failures="0">',
|
||||
' <testcase id="home" name="home" classname="home" time="4.0" status="SUCCESS"/>',
|
||||
' <testcase id="login" name="login" classname="login" time="2.0" status="SUCCESS"/>',
|
||||
' </testsuite>',
|
||||
'</testsuites>',
|
||||
].join('\n'),
|
||||
'/tests/2026-01-28_055409/ai-home.json': JSON.stringify({
|
||||
flow_name: 'home',
|
||||
flow_file_path: '/root/project/.maestro/home.yml',
|
||||
}),
|
||||
'/tests/2026-01-28_055409/ai-login.json': JSON.stringify({
|
||||
flow_name: 'login',
|
||||
flow_file_path: '/root/project/.maestro/login.yml',
|
||||
}),
|
||||
'/tests/2026-01-28_055420/ai-home.json': JSON.stringify({
|
||||
flow_name: 'home',
|
||||
flow_file_path: '/root/project/.maestro/home.yml',
|
||||
}),
|
||||
'/tests/2026-01-28_055420/ai-login.json': JSON.stringify({
|
||||
flow_name: 'login',
|
||||
flow_file_path: '/root/project/.maestro/login.yml',
|
||||
}),
|
||||
});
|
||||
|
||||
const results = await parseMaestroResults('/junit-reports', '/tests', '/root/project');
|
||||
|
||||
// 4 results: 2 flows × 2 attempts
|
||||
expect(results).toHaveLength(4);
|
||||
expect(results).toEqual([
|
||||
expect.objectContaining({ name: 'home', status: 'failed', retryCount: 0 }),
|
||||
expect.objectContaining({ name: 'home', status: 'passed', retryCount: 1 }),
|
||||
expect.objectContaining({ name: 'login', status: 'passed', retryCount: 0 }),
|
||||
expect.objectContaining({ name: 'login', status: 'passed', retryCount: 1 }),
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns per-attempt results with sharding (multiple testsuites per attempt file)', async () => {
|
||||
vol.fromJSON({
|
||||
// Attempt 0: shard 1 has home (FAILED), shard 2 has login (PASSED)
|
||||
'/junit-reports/android-maestro-junit-attempt-0.xml': [
|
||||
'<?xml version="1.0"?>',
|
||||
'<testsuites>',
|
||||
' <testsuite name="Test Suite" device="emulator-5554" tests="1" failures="1">',
|
||||
' <testcase id="home" name="home" classname="home" time="5.0" status="ERROR">',
|
||||
' <failure>Timeout</failure>',
|
||||
' </testcase>',
|
||||
' </testsuite>',
|
||||
' <testsuite name="Test Suite" device="emulator-5556" tests="1" failures="0">',
|
||||
' <testcase id="login" name="login" classname="login" time="3.0" status="SUCCESS"/>',
|
||||
' </testsuite>',
|
||||
'</testsuites>',
|
||||
].join('\n'),
|
||||
// Attempt 1: all passed across shards
|
||||
'/junit-reports/android-maestro-junit-attempt-1.xml': [
|
||||
'<?xml version="1.0"?>',
|
||||
'<testsuites>',
|
||||
' <testsuite name="Test Suite" device="emulator-5554" tests="1" failures="0">',
|
||||
' <testcase id="home" name="home" classname="home" time="4.0" status="SUCCESS"/>',
|
||||
' </testsuite>',
|
||||
' <testsuite name="Test Suite" device="emulator-5556" tests="1" failures="0">',
|
||||
' <testcase id="login" name="login" classname="login" time="2.0" status="SUCCESS"/>',
|
||||
' </testsuite>',
|
||||
'</testsuites>',
|
||||
].join('\n'),
|
||||
'/tests/2026-01-28_055409/ai-home.json': JSON.stringify({
|
||||
flow_name: 'home',
|
||||
flow_file_path: '/root/project/.maestro/home.yml',
|
||||
}),
|
||||
'/tests/2026-01-28_055409/ai-login.json': JSON.stringify({
|
||||
flow_name: 'login',
|
||||
flow_file_path: '/root/project/.maestro/login.yml',
|
||||
}),
|
||||
'/tests/2026-01-28_055420/ai-home.json': JSON.stringify({
|
||||
flow_name: 'home',
|
||||
flow_file_path: '/root/project/.maestro/home.yml',
|
||||
}),
|
||||
'/tests/2026-01-28_055420/ai-login.json': JSON.stringify({
|
||||
flow_name: 'login',
|
||||
flow_file_path: '/root/project/.maestro/login.yml',
|
||||
}),
|
||||
});
|
||||
|
||||
const results = await parseMaestroResults('/junit-reports', '/tests', '/root/project');
|
||||
|
||||
expect(results).toHaveLength(4);
|
||||
expect(results).toEqual([
|
||||
expect.objectContaining({ name: 'home', status: 'failed', retryCount: 0 }),
|
||||
expect.objectContaining({ name: 'home', status: 'passed', retryCount: 1 }),
|
||||
expect.objectContaining({ name: 'login', status: 'passed', retryCount: 0 }),
|
||||
expect.objectContaining({ name: 'login', status: 'passed', retryCount: 1 }),
|
||||
]);
|
||||
});
|
||||
|
||||
it('backward compat: reuse_devices=true with retries but old single JUnit file', async () => {
|
||||
vol.fromJSON({
|
||||
// Single overwritten JUnit (only has final attempt's results)
|
||||
'/maestro-tests/android-maestro-junit.xml': [
|
||||
'<?xml version="1.0"?>',
|
||||
'<testsuites>',
|
||||
' <testsuite name="Test Suite" tests="2" failures="0">',
|
||||
' <testcase id="home" name="home" classname="home" time="4.0" status="SUCCESS"/>',
|
||||
' <testcase id="login" name="login" classname="login" time="2.0" status="SUCCESS"/>',
|
||||
' </testsuite>',
|
||||
'</testsuites>',
|
||||
].join('\n'),
|
||||
// 2 timestamp dirs — both flows appear in both (entire suite retried)
|
||||
'/maestro-tests/2026-01-28_055409/ai-home.json': JSON.stringify({
|
||||
flow_name: 'home',
|
||||
flow_file_path: '/root/project/.maestro/home.yml',
|
||||
}),
|
||||
'/maestro-tests/2026-01-28_055409/ai-login.json': JSON.stringify({
|
||||
flow_name: 'login',
|
||||
flow_file_path: '/root/project/.maestro/login.yml',
|
||||
}),
|
||||
'/maestro-tests/2026-01-28_055420/ai-home.json': JSON.stringify({
|
||||
flow_name: 'home',
|
||||
flow_file_path: '/root/project/.maestro/home.yml',
|
||||
}),
|
||||
'/maestro-tests/2026-01-28_055420/ai-login.json': JSON.stringify({
|
||||
flow_name: 'login',
|
||||
flow_file_path: '/root/project/.maestro/login.yml',
|
||||
}),
|
||||
});
|
||||
|
||||
const results = await parseMaestroResults('/maestro-tests', '/maestro-tests', '/root/project');
|
||||
|
||||
// Both flows have 2 occurrences → retryCount = 1 for both
|
||||
expect(results).toHaveLength(2);
|
||||
expect(results).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ name: 'home', status: 'passed', retryCount: 1 }),
|
||||
expect.objectContaining({ name: 'login', status: 'passed', retryCount: 1 }),
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it('handles reuse_devices=false (separate junit_report_directory)', async () => {
|
||||
vol.fromJSON({
|
||||
// JUnit in temp dir (per-flow files)
|
||||
|
||||
@@ -238,6 +238,45 @@ describe(createReportMaestroTestResultsFunction, () => {
|
||||
expect(mockGraphqlClient.mutation).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('sends per-attempt results with same name but different retryCount', async () => {
|
||||
vol.fromJSON({
|
||||
// Two JUnit files for same flow (per-attempt)
|
||||
'/junit/junit-report-flow-1-attempt-0.xml': [
|
||||
'<?xml version="1.0"?>',
|
||||
'<testsuites>',
|
||||
' <testsuite name="Test Suite" tests="1" failures="1">',
|
||||
' <testcase id="home" name="home" classname="home" time="5.0" status="ERROR">',
|
||||
' <failure>Tap failed</failure>',
|
||||
' </testcase>',
|
||||
' </testsuite>',
|
||||
'</testsuites>',
|
||||
].join('\n'),
|
||||
'/junit/junit-report-flow-1-attempt-1.xml': JUNIT_PASS,
|
||||
'/tests/2026-01-28_055409/ai-home.json': FLOW_AI,
|
||||
'/tests/2026-01-28_055420/ai-home.json': FLOW_AI,
|
||||
});
|
||||
|
||||
mockMutationFn.mockResolvedValue({
|
||||
data: {
|
||||
workflowDeviceTestCaseResult: {
|
||||
createWorkflowDeviceTestCaseResults: [{ id: 'id-1' }, { id: 'id-2' }],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await createStep().executeAsync();
|
||||
|
||||
expect(mockGraphqlClient.mutation).toHaveBeenCalledTimes(1);
|
||||
const [, variables] = (mockGraphqlClient.mutation as jest.Mock).mock.calls[0];
|
||||
expect(variables.input.testCaseResults).toHaveLength(2);
|
||||
expect(variables.input.testCaseResults[0]).toEqual(
|
||||
expect.objectContaining({ name: 'home', status: 'FAILED', retryCount: 0 })
|
||||
);
|
||||
expect(variables.input.testCaseResults[1]).toEqual(
|
||||
expect.objectContaining({ name: 'home', status: 'PASSED', retryCount: 1 })
|
||||
);
|
||||
});
|
||||
|
||||
it('uses default directories when inputs are not provided', async () => {
|
||||
vol.fromJSON({
|
||||
'/home/expo/.maestro/tests/report.xml': JUNIT_PASS,
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
isClosedVersionTrainError,
|
||||
isInvalidBundleIdentifierError,
|
||||
isMissingPurposeStringError,
|
||||
isSdkVersionIssueError,
|
||||
parseMissingUsageDescriptionKeys,
|
||||
} from '../uploadToAsc';
|
||||
|
||||
@@ -58,6 +59,20 @@ describe(isMissingPurposeStringError, () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe(isSdkVersionIssueError, () => {
|
||||
it('returns true when all errors are SDK-version-issue codes', () => {
|
||||
expect(isSdkVersionIssueError([{ code: '90725' }, { code: '90725' }])).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false when any other error code is present', () => {
|
||||
expect(isSdkVersionIssueError([{ code: '90725' }, { code: '90062' }])).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false when there are no errors', () => {
|
||||
expect(isSdkVersionIssueError([])).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe(parseMissingUsageDescriptionKeys, () => {
|
||||
it('extracts missing UsageDescription keys from ASC messages', () => {
|
||||
expect(
|
||||
|
||||
@@ -214,18 +214,17 @@ export function createInternalEasMaestroTestFunction(ctx: CustomBuildContext): B
|
||||
for (const [flowIndex, flowPath] of flowPathsToExecute.entries()) {
|
||||
stepCtx.logger.info('');
|
||||
|
||||
// If output_format is empty or noop, we won't use this.
|
||||
const outputPath = path.join(
|
||||
maestroReportsDir,
|
||||
[
|
||||
`${output_format ? output_format + '-' : ''}report-flow-${flowIndex + 1}`,
|
||||
MaestroOutputFormatToExtensionMap[output_format ?? 'noop'],
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join('.')
|
||||
);
|
||||
|
||||
for (let attemptCount = 0; attemptCount < retries; attemptCount++) {
|
||||
// Generate unique report path per attempt (not overwritten on retry)
|
||||
const outputPath = path.join(
|
||||
maestroReportsDir,
|
||||
[
|
||||
`${output_format ? output_format + '-' : ''}report-flow-${flowIndex + 1}-attempt-${attemptCount}`,
|
||||
MaestroOutputFormatToExtensionMap[output_format ?? 'noop'],
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join('.')
|
||||
);
|
||||
const localDeviceName = `eas-simulator-${flowIndex}-${attemptCount}` as
|
||||
| IosSimulatorName
|
||||
| AndroidVirtualDeviceName;
|
||||
@@ -276,12 +275,11 @@ export function createInternalEasMaestroTestFunction(ctx: CustomBuildContext): B
|
||||
if (logsResult?.ok) {
|
||||
try {
|
||||
const extension = path.extname(logsResult.value.outputPath);
|
||||
const destinationPath = path.join(deviceLogsDir, `flow-${flowIndex}${extension}`);
|
||||
const destinationPath = path.join(
|
||||
deviceLogsDir,
|
||||
`flow-${flowIndex}-attempt-${attemptCount}${extension}`
|
||||
);
|
||||
|
||||
await fs.promises.rm(destinationPath, {
|
||||
force: true,
|
||||
recursive: true,
|
||||
});
|
||||
await fs.promises.rename(logsResult.value.outputPath, destinationPath);
|
||||
} catch (err) {
|
||||
stepCtx.logger.warn({ err }, 'Failed to prepare device logs for upload.');
|
||||
|
||||
@@ -38,6 +38,79 @@ const xmlParser = new XMLParser({
|
||||
isArray: name => ['testsuite', 'testcase', 'property'].includes(name),
|
||||
});
|
||||
|
||||
// Internal helper — not exported. Parses a single JUnit XML file.
|
||||
async function parseJUnitFile(filePath: string): Promise<JUnitTestCaseResult[]> {
|
||||
const results: JUnitTestCaseResult[] = [];
|
||||
try {
|
||||
const content = await fs.readFile(filePath, 'utf-8');
|
||||
const parsed = xmlParser.parse(content);
|
||||
|
||||
const testsuites = parsed?.testsuites?.testsuite;
|
||||
if (!Array.isArray(testsuites)) {
|
||||
return results;
|
||||
}
|
||||
|
||||
for (const suite of testsuites) {
|
||||
const testcases = suite?.testcase;
|
||||
if (!Array.isArray(testcases)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const tc of testcases) {
|
||||
const name = tc['@_name'];
|
||||
if (!name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const timeStr = tc['@_time'];
|
||||
const timeSeconds = timeStr ? parseFloat(timeStr) : 0;
|
||||
const duration = Number.isFinite(timeSeconds) ? Math.round(timeSeconds * 1000) : 0;
|
||||
|
||||
const status: 'passed' | 'failed' = tc['@_status'] === 'SUCCESS' ? 'passed' : 'failed';
|
||||
const failureText =
|
||||
tc.failure != null
|
||||
? typeof tc.failure === 'string'
|
||||
? tc.failure
|
||||
: (tc.failure?.['#text'] ?? null)
|
||||
: null;
|
||||
const errorText =
|
||||
tc.error != null
|
||||
? typeof tc.error === 'string'
|
||||
? tc.error
|
||||
: (tc.error?.['#text'] ?? null)
|
||||
: null;
|
||||
const errorMessage: string | null = failureText ?? errorText ?? null;
|
||||
|
||||
const rawProperties: { '@_name': string; '@_value': string }[] =
|
||||
tc.properties?.property ?? [];
|
||||
const properties: Record<string, string> = {};
|
||||
for (const prop of rawProperties) {
|
||||
const propName = prop['@_name'];
|
||||
const value = prop['@_value'];
|
||||
if (typeof propName !== 'string' || typeof value !== 'string') {
|
||||
continue;
|
||||
}
|
||||
properties[propName] = value;
|
||||
}
|
||||
|
||||
const tagsValue = properties['tags'];
|
||||
const tags: string[] = tagsValue
|
||||
? tagsValue
|
||||
.split(',')
|
||||
.map(t => t.trim())
|
||||
.filter(Boolean)
|
||||
: [];
|
||||
delete properties['tags'];
|
||||
|
||||
results.push({ name, status, duration, errorMessage, tags, properties });
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Skip malformed XML files
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
export async function parseJUnitTestCases(junitDirectory: string): Promise<JUnitTestCaseResult[]> {
|
||||
let entries: string[];
|
||||
try {
|
||||
@@ -52,83 +125,9 @@ export async function parseJUnitTestCases(junitDirectory: string): Promise<JUnit
|
||||
}
|
||||
|
||||
const results: JUnitTestCaseResult[] = [];
|
||||
|
||||
for (const xmlFile of xmlFiles) {
|
||||
try {
|
||||
const content = await fs.readFile(path.join(junitDirectory, xmlFile), 'utf-8');
|
||||
const parsed = xmlParser.parse(content);
|
||||
|
||||
const testsuites = parsed?.testsuites?.testsuite;
|
||||
if (!Array.isArray(testsuites)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const suite of testsuites) {
|
||||
const testcases = suite?.testcase;
|
||||
if (!Array.isArray(testcases)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const tc of testcases) {
|
||||
const name = tc['@_name'];
|
||||
if (!name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const timeStr = tc['@_time'];
|
||||
const timeSeconds = timeStr ? parseFloat(timeStr) : 0;
|
||||
const duration = Number.isFinite(timeSeconds) ? Math.round(timeSeconds * 1000) : 0;
|
||||
|
||||
// Use @_status as primary indicator (more robust than checking <failure> presence)
|
||||
const status: 'passed' | 'failed' = tc['@_status'] === 'SUCCESS' ? 'passed' : 'failed';
|
||||
// Extract error message from <failure> or <error> elements
|
||||
const failureText =
|
||||
tc.failure != null
|
||||
? typeof tc.failure === 'string'
|
||||
? tc.failure
|
||||
: (tc.failure?.['#text'] ?? null)
|
||||
: null;
|
||||
const errorText =
|
||||
tc.error != null
|
||||
? typeof tc.error === 'string'
|
||||
? tc.error
|
||||
: (tc.error?.['#text'] ?? null)
|
||||
: null;
|
||||
const errorMessage: string | null = failureText ?? errorText ?? null;
|
||||
|
||||
// Extract properties
|
||||
const rawProperties: { '@_name': string; '@_value': string }[] =
|
||||
tc.properties?.property ?? [];
|
||||
const properties: Record<string, string> = {};
|
||||
|
||||
for (const prop of rawProperties) {
|
||||
const propName = prop['@_name'];
|
||||
const value = prop['@_value'];
|
||||
if (typeof propName !== 'string' || typeof value !== 'string') {
|
||||
continue;
|
||||
}
|
||||
properties[propName] = value;
|
||||
}
|
||||
|
||||
// Extract tags from "tags" property (Maestro 2.2.0+, comma-separated)
|
||||
const tagsValue = properties['tags'];
|
||||
const tags: string[] = tagsValue
|
||||
? tagsValue
|
||||
.split(',')
|
||||
.map(t => t.trim())
|
||||
.filter(Boolean)
|
||||
: [];
|
||||
delete properties['tags'];
|
||||
|
||||
results.push({ name, status, duration, errorMessage, tags, properties });
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Skip malformed XML files
|
||||
continue;
|
||||
}
|
||||
results.push(...(await parseJUnitFile(path.join(junitDirectory, xmlFile))));
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
@@ -167,9 +166,32 @@ export async function parseMaestroResults(
|
||||
testsDirectory: string,
|
||||
projectRoot: string
|
||||
): Promise<MaestroFlowResult[]> {
|
||||
// 1. Parse JUnit XML files (primary source)
|
||||
const junitResults = await parseJUnitTestCases(junitDirectory);
|
||||
if (junitResults.length === 0) {
|
||||
// 1. Parse JUnit XML files, tracking which file each result came from
|
||||
let junitEntries: string[];
|
||||
try {
|
||||
junitEntries = await fs.readdir(junitDirectory);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
const xmlFiles = junitEntries.filter(f => f.endsWith('.xml')).sort();
|
||||
if (xmlFiles.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
interface JUnitResultWithSource {
|
||||
result: JUnitTestCaseResult;
|
||||
sourceFile: string;
|
||||
}
|
||||
|
||||
const junitResultsWithSource: JUnitResultWithSource[] = [];
|
||||
for (const xmlFile of xmlFiles) {
|
||||
const fileResults = await parseJUnitFile(path.join(junitDirectory, xmlFile));
|
||||
for (const result of fileResults) {
|
||||
junitResultsWithSource.push({ result, sourceFile: xmlFile });
|
||||
}
|
||||
}
|
||||
|
||||
if (junitResultsWithSource.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -217,25 +239,63 @@ export async function parseMaestroResults(
|
||||
// 3. Merge: JUnit results + ai-*.json metadata
|
||||
const results: MaestroFlowResult[] = [];
|
||||
|
||||
for (const junit of junitResults) {
|
||||
const flowFilePath = flowPathMap.get(junit.name);
|
||||
// Parse attempt index from filename pattern: *-attempt-N.*
|
||||
const ATTEMPT_PATTERN = /attempt-(\d+)/;
|
||||
|
||||
// Group results by flow name
|
||||
const resultsByName = new Map<string, JUnitResultWithSource[]>();
|
||||
for (const entry of junitResultsWithSource) {
|
||||
const group = resultsByName.get(entry.result.name) ?? [];
|
||||
group.push(entry);
|
||||
resultsByName.set(entry.result.name, group);
|
||||
}
|
||||
|
||||
for (const [flowName, flowEntries] of resultsByName) {
|
||||
const flowFilePath = flowPathMap.get(flowName);
|
||||
const relativePath = flowFilePath
|
||||
? await relativizePathAsync(flowFilePath, projectRoot)
|
||||
: junit.name; // fallback: use flow name if ai-*.json not found
|
||||
: flowName;
|
||||
|
||||
const occurrences = flowOccurrences.get(junit.name) ?? 0;
|
||||
const retryCount = Math.max(0, occurrences - 1);
|
||||
if (flowEntries.length === 1) {
|
||||
// Single result for this flow — use ai-*.json occurrence count for retryCount
|
||||
// (backward compat with old-style single JUnit file that gets overwritten)
|
||||
const { result } = flowEntries[0];
|
||||
const occurrences = flowOccurrences.get(flowName) ?? 0;
|
||||
const retryCount = Math.max(0, occurrences - 1);
|
||||
|
||||
results.push({
|
||||
name: junit.name,
|
||||
path: relativePath,
|
||||
status: junit.status,
|
||||
errorMessage: junit.errorMessage,
|
||||
duration: junit.duration,
|
||||
retryCount,
|
||||
tags: junit.tags,
|
||||
properties: junit.properties,
|
||||
});
|
||||
results.push({
|
||||
name: flowName,
|
||||
path: relativePath,
|
||||
status: result.status,
|
||||
errorMessage: result.errorMessage,
|
||||
duration: result.duration,
|
||||
retryCount,
|
||||
tags: result.tags,
|
||||
properties: result.properties,
|
||||
});
|
||||
} else {
|
||||
// Multiple results — per-attempt JUnit files. Sort by attempt index from filename.
|
||||
const sorted = flowEntries
|
||||
.map(entry => {
|
||||
const match = entry.sourceFile.match(ATTEMPT_PATTERN);
|
||||
const attemptIndex = match ? parseInt(match[1], 10) : 0;
|
||||
return { ...entry, attemptIndex };
|
||||
})
|
||||
.sort((a, b) => a.attemptIndex - b.attemptIndex);
|
||||
|
||||
for (const { result, attemptIndex } of sorted) {
|
||||
results.push({
|
||||
name: flowName,
|
||||
path: relativePath,
|
||||
status: result.status,
|
||||
errorMessage: result.errorMessage,
|
||||
duration: result.duration,
|
||||
retryCount: attemptIndex,
|
||||
tags: result.tags,
|
||||
properties: result.properties,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
|
||||
@@ -51,6 +51,11 @@ export function createRepackBuildFunction(): BuildFunction {
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.BOOLEAN,
|
||||
required: false,
|
||||
}),
|
||||
BuildStepInput.createProvider({
|
||||
id: 'js_bundle_only',
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.BOOLEAN,
|
||||
required: false,
|
||||
}),
|
||||
BuildStepInput.createProvider({
|
||||
id: 'repack_version',
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.STRING,
|
||||
@@ -82,7 +87,10 @@ export function createRepackBuildFunction(): BuildFunction {
|
||||
);
|
||||
}
|
||||
|
||||
const repackSpawnAsync = createSpawnAsyncStepAdapter({ verbose, logger: stepsCtx.logger });
|
||||
const repackSpawnAsync = createSpawnAsyncStepAdapter({
|
||||
verbose,
|
||||
logger: stepsCtx.logger,
|
||||
});
|
||||
|
||||
const tmpDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), `repack-`));
|
||||
const workingDirectory = path.join(tmpDir, 'working-directory');
|
||||
@@ -98,6 +106,7 @@ export function createRepackBuildFunction(): BuildFunction {
|
||||
sourcemapOutput: undefined,
|
||||
}
|
||||
: undefined;
|
||||
const jsBundleOnly = (inputs.js_bundle_only.value as boolean | undefined) ?? false;
|
||||
|
||||
stepsCtx.logger.info(
|
||||
`Using repack from: ${inputs.repack_package.value}@${inputs.repack_version.value}`
|
||||
@@ -118,6 +127,7 @@ export function createRepackBuildFunction(): BuildFunction {
|
||||
outputPath,
|
||||
workingDirectory,
|
||||
exportEmbedOptions,
|
||||
jsBundleOnly,
|
||||
iosSigningOptions: await resolveIosSigningOptionsAsync({
|
||||
job: stepsCtx.global.staticContext.job,
|
||||
logger: stepsCtx.logger,
|
||||
@@ -146,6 +156,7 @@ export function createRepackBuildFunction(): BuildFunction {
|
||||
outputPath,
|
||||
workingDirectory,
|
||||
exportEmbedOptions,
|
||||
jsBundleOnly,
|
||||
androidSigningOptions,
|
||||
logger: stepsCtx.logger,
|
||||
spawnAsync: repackSpawnAsync,
|
||||
|
||||
@@ -64,14 +64,21 @@ export function createReportMaestroTestResultsFunction(ctx: CustomBuildContext):
|
||||
return;
|
||||
}
|
||||
|
||||
// Maestro allows overriding flow names via config, so different flow files can share
|
||||
// the same name. JUnit XML only contains names (not file paths), making it impossible
|
||||
// to map duplicates back to their original flow files. Skip and let the user fix it.
|
||||
const names = flowResults.map(r => r.name);
|
||||
const duplicates = names.filter((n, i) => names.indexOf(n) !== i);
|
||||
if (duplicates.length > 0) {
|
||||
// Detect truly conflicting results: same (name, retryCount) pair means different flow files
|
||||
// share the same name (Maestro config override), which we can't disambiguate.
|
||||
// Same name with different retryCount is expected (per-attempt results from retries).
|
||||
const seen = new Set<string>();
|
||||
const conflicting = new Set<string>();
|
||||
for (const r of flowResults) {
|
||||
const key = `${r.name}:${r.retryCount}`;
|
||||
if (seen.has(key)) {
|
||||
conflicting.add(r.name);
|
||||
}
|
||||
seen.add(key);
|
||||
}
|
||||
if (conflicting.size > 0) {
|
||||
logger.error(
|
||||
`Duplicate test case names found in JUnit output: ${[...new Set(duplicates)].join(
|
||||
`Duplicate test case names found in JUnit output: ${[...conflicting].join(
|
||||
', '
|
||||
)}. Skipping report. Ensure each Maestro flow has a unique name.`
|
||||
);
|
||||
|
||||
@@ -266,12 +266,31 @@ export function createUploadToAscBuildFunction(): BuildFunction {
|
||||
}
|
||||
|
||||
if (state.state === 'FAILED') {
|
||||
if (isSdkVersionIssueError(errors)) {
|
||||
throw new UserError(
|
||||
'EAS_UPLOAD_TO_ASC_SDK_VERSION_ISSUE',
|
||||
'Build upload was rejected by App Store Connect because the IPA was built with an iOS SDK that is too old for current App Store Connect requirements. ' +
|
||||
'In an Expo project, this usually means the build used an outdated EAS iOS image or the project still depends on an Expo SDK / native setup that does not support the required Xcode toolchain. ' +
|
||||
'Upgrade to a supported Expo SDK if needed and/or select a newer iOS image in `eas.json`, rebuild, and submit again.',
|
||||
{
|
||||
docsUrl: 'https://docs.expo.dev/workflow/upgrading-expo-sdk-walkthrough/',
|
||||
}
|
||||
);
|
||||
}
|
||||
if (isInvalidBundleIdentifierError(errors)) {
|
||||
const ipaInfoResult = await asyncResult(readIpaInfoAsync(ipaPath));
|
||||
const ipaBundleIdentifier = ipaInfoResult.ok
|
||||
? ipaInfoResult.value.bundleIdentifier
|
||||
: null;
|
||||
|
||||
let visibleAppsSummary: string | null = null;
|
||||
try {
|
||||
const apps = await AscApiUtils.getAppsAsync({ client, limit: 5 });
|
||||
visibleAppsSummary = AscApiUtils.formatAppsList(apps);
|
||||
} catch {
|
||||
// Ok to fail, this is just trying to be helpful.
|
||||
}
|
||||
|
||||
throw new UserError(
|
||||
'EAS_UPLOAD_TO_ASC_INVALID_BUNDLE_ID',
|
||||
`Build upload was rejected by App Store Connect because the app bundle identifier in the IPA does not match the selected App Store Connect app.\n\n` +
|
||||
@@ -279,7 +298,12 @@ export function createUploadToAscBuildFunction(): BuildFunction {
|
||||
`App Store Connect app bundle identifier: ${ascAppBundleIdentifier}\n\n` +
|
||||
'Bundle identifier cannot be changed for an existing App Store Connect app. ' +
|
||||
'If you selected the wrong app, change the Apple app identifier in the submit profile. ' +
|
||||
'If you selected the right app, you may want to select a different build to upload (or rebuild with a different profile).'
|
||||
'If you selected the right app, you may want to select a different build to upload (or rebuild with a different profile).' +
|
||||
'\n\nOther App Store Connect apps visible to this API key:\n' +
|
||||
(visibleAppsSummary ?? ' (unable to retrieve)'),
|
||||
{
|
||||
docsUrl: 'https://expo.fyi/asc-app-id',
|
||||
}
|
||||
);
|
||||
}
|
||||
if (isMissingPurposeStringError(errors)) {
|
||||
@@ -331,6 +355,10 @@ export function isClosedVersionTrainError(messages: { code: string }[]): boolean
|
||||
);
|
||||
}
|
||||
|
||||
export function isSdkVersionIssueError(messages: { code: string }[]): boolean {
|
||||
return messages.length > 0 && messages.every(message => message.code === '90725');
|
||||
}
|
||||
|
||||
export function isInvalidBundleIdentifierError(messages: { code: string }[]): boolean {
|
||||
return (
|
||||
messages.length > 0 && messages.every(message => ['90054', '90055'].includes(message.code))
|
||||
|
||||
@@ -34,11 +34,13 @@ export namespace AscApiUtils {
|
||||
|
||||
let visibleAppsSummary: string | null = null;
|
||||
try {
|
||||
visibleAppsSummary = await getVisibleAppsSummaryAsync(client);
|
||||
const apps = await AscApiUtils.getAppsAsync({ client, limit: 10 });
|
||||
visibleAppsSummary = AscApiUtils.formatAppsList(apps);
|
||||
} catch {
|
||||
// Don't hide the original NOT_FOUND error with a secondary lookup failure.
|
||||
throw error;
|
||||
}
|
||||
|
||||
throw new UserError(
|
||||
'EAS_UPLOAD_TO_ASC_APP_NOT_FOUND',
|
||||
`App Store Connect app for application identifier ${appleAppIdentifier} was not found. ` +
|
||||
@@ -108,19 +110,26 @@ export namespace AscApiUtils {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function getVisibleAppsSummaryAsync(
|
||||
client: Pick<AscApiClient, 'getAsync'>
|
||||
): Promise<string | null> {
|
||||
const appsResponse = await client.getAsync('/v1/apps', {
|
||||
'fields[apps]': ['bundleId', 'name'],
|
||||
limit: 10,
|
||||
});
|
||||
if (appsResponse.data.length === 0) {
|
||||
return ' (none)';
|
||||
export async function getAppsAsync({
|
||||
client,
|
||||
limit,
|
||||
}: {
|
||||
client: Pick<AscApiClient, 'getAsync'>;
|
||||
limit?: number;
|
||||
}): Promise<AscApiClientGetApi['/v1/apps']['response']['data']> {
|
||||
const appsResponse = await client.getAsync('/v1/apps', {
|
||||
'fields[apps]': ['bundleId', 'name'],
|
||||
limit: limit ?? 10,
|
||||
});
|
||||
return appsResponse.data;
|
||||
}
|
||||
|
||||
export function formatAppsList(apps: AscApiClientGetApi['/v1/apps']['response']['data']): string {
|
||||
return (
|
||||
apps
|
||||
.map(app => `- ${app.attributes.name} (${app.attributes.bundleId}) (ID: ${app.id})`)
|
||||
.join('\n') || ' (none)'
|
||||
);
|
||||
}
|
||||
return appsResponse.data
|
||||
.map(app => `- ${app.attributes.name} (${app.attributes.bundleId}) (ID: ${app.id})`)
|
||||
.join('\n');
|
||||
}
|
||||
@@ -60,8 +60,8 @@ describe('AscApiUtils', () => {
|
||||
expect.objectContaining({
|
||||
errorCode: 'EAS_UPLOAD_TO_ASC_APP_NOT_FOUND',
|
||||
docsUrl: 'https://expo.fyi/asc-app-id',
|
||||
message: expect.stringContaining(
|
||||
'App Store Connect app for application identifier 1234567890 was not found'
|
||||
message: expect.stringMatching(
|
||||
/App Store Connect app for application identifier 1234567890 was not found[\s\S]*- Visible App \(com\.visible\.app\) \(ID: 1111111111\)/
|
||||
),
|
||||
})
|
||||
);
|
||||
@@ -96,6 +96,31 @@ describe('AscApiUtils', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatAppsList', () => {
|
||||
it('formats visible apps', () => {
|
||||
expect(
|
||||
AscApiUtils.formatAppsList([
|
||||
{
|
||||
type: 'apps',
|
||||
id: '1111111111',
|
||||
attributes: { name: 'Visible App', bundleId: 'com.visible.app' },
|
||||
},
|
||||
{
|
||||
type: 'apps',
|
||||
id: '2222222222',
|
||||
attributes: { name: 'Second App', bundleId: 'com.second.app' },
|
||||
},
|
||||
])
|
||||
).toBe(
|
||||
'- Visible App (com.visible.app) (ID: 1111111111)\n- Second App (com.second.app) (ID: 2222222222)'
|
||||
);
|
||||
});
|
||||
|
||||
it('returns a placeholder when no visible apps are found', () => {
|
||||
expect(AscApiUtils.formatAppsList([])).toBe(' (none)');
|
||||
});
|
||||
});
|
||||
|
||||
describe('createBuildUploadAsync', () => {
|
||||
it('throws UserError when ASC duplicate version error is returned', async () => {
|
||||
const payload = {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-eas-build-function",
|
||||
"version": "18.2.0",
|
||||
"version": "18.4.0",
|
||||
"description": "Create functions for use in EAS Build custom builds.",
|
||||
"keywords": [
|
||||
"expo",
|
||||
@@ -35,7 +35,7 @@
|
||||
"clean": "rimraf ./build/ \"*.tsbuildinfo\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@expo/steps": "18.2.0"
|
||||
"@expo/steps": "18.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@expo/package-manager": "1.9.10",
|
||||
|
||||
@@ -19,10 +19,10 @@
|
||||
"watch": "tsc --watch --preserveWatchOutput",
|
||||
"build": "tsc",
|
||||
"typecheck": "tsc",
|
||||
"prepack": "rm -rf dist *.tsbuildinfo && tsc -p tsconfig.build.json",
|
||||
"prepack": "rimraf dist \"*.tsbuildinfo\" && tsc -p tsconfig.build.json",
|
||||
"test": "jest --config jest.config.ts",
|
||||
"test:watch": "jest --config jest.config.ts --watch",
|
||||
"clean": "rm -rf node_modules dist coverage *.tsbuildinfo"
|
||||
"clean": "rimraf node_modules dist coverage \"*.tsbuildinfo\""
|
||||
},
|
||||
"dependencies": {
|
||||
"fs-extra": "^11.2.0",
|
||||
@@ -33,6 +33,7 @@
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/node": "20.14.2",
|
||||
"jest": "^29.7.0",
|
||||
"rimraf": "3.0.2",
|
||||
"ts-jest": "^29.1.4",
|
||||
"typescript": "^5.5.4"
|
||||
}
|
||||
|
||||
@@ -26,9 +26,9 @@
|
||||
"watch-allow-unused": "yarn build-allow-unused --watch --preserveWatchOutput",
|
||||
"typecheck": "tsc",
|
||||
"prepack": "yarn rebuild",
|
||||
"rebuild": "rimraf build *.tsbuildinfo && yarn build",
|
||||
"rebuild": "rimraf build \"*.tsbuildinfo\" && yarn build",
|
||||
"test": "jest --passWithNoTests",
|
||||
"clean": "rimraf build node_modules yarn-error.log *.tsbuildinfo"
|
||||
"clean": "rimraf build node_modules yarn-error.log \"*.tsbuildinfo\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "7.23.5",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@expo/eas-build-job",
|
||||
"version": "18.2.0",
|
||||
"version": "18.4.0",
|
||||
"bugs": "https://github.com/expo/eas-cli/issues",
|
||||
"license": "MIT",
|
||||
"author": "Expo <[email protected]>",
|
||||
@@ -19,10 +19,10 @@
|
||||
"watch": "tsc --watch --preserveWatchOutput",
|
||||
"build": "tsc",
|
||||
"typecheck": "tsc",
|
||||
"prepack": "rm -rf dist *.tsbuildinfo && tsc -p tsconfig.build.json",
|
||||
"prepack": "rimraf dist \"*.tsbuildinfo\" && tsc -p tsconfig.build.json",
|
||||
"test": "jest --config jest.config.ts",
|
||||
"test:watch": "jest --config jest.config.ts --watch",
|
||||
"clean": "rm -rf node_modules dist coverage *.tsbuildinfo"
|
||||
"clean": "rimraf node_modules dist coverage \"*.tsbuildinfo\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@expo/logger": "18.0.1",
|
||||
@@ -34,6 +34,7 @@
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/node": "20.14.2",
|
||||
"jest": "^29.7.0",
|
||||
"rimraf": "3.0.2",
|
||||
"ts-jest": "^29.1.4",
|
||||
"typescript": "^5.5.4"
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { BuildError, ExpoError, UserError } from '../errors';
|
||||
import { ErrorCode, ExpoError, SystemError, UserError } from '../errors';
|
||||
import { BuildPhase } from '../logs';
|
||||
|
||||
type ExpoErrorDetails = ConstructorParameters<typeof ExpoError>[1];
|
||||
|
||||
class TestExpoError extends ExpoError {
|
||||
constructor(message: string, details: ConstructorParameters<typeof BuildError>[1]) {
|
||||
constructor(message: string, details: ExpoErrorDetails) {
|
||||
super(message, details);
|
||||
}
|
||||
}
|
||||
@@ -27,27 +29,7 @@ describe(ExpoError, () => {
|
||||
});
|
||||
expect(error.buildPhase).toBe(BuildPhase.PREBUILD);
|
||||
expect(error.cause).toBe(cause);
|
||||
expect(error.format()).toEqual({
|
||||
errorCode: 'ERR_CODE',
|
||||
message: 'canonical message',
|
||||
docsUrl: 'https://docs.example.dev',
|
||||
buildPhase: BuildPhase.PREBUILD,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe(BuildError, () => {
|
||||
it('formats using canonical message and errorCode', () => {
|
||||
const error = new BuildError('canonical message', {
|
||||
errorCode: 'ERR_CODE',
|
||||
docsUrl: 'https://docs.example.dev',
|
||||
buildPhase: BuildPhase.PREBUILD,
|
||||
metadata: { packageName: '@expo/config' },
|
||||
});
|
||||
|
||||
expect(error).toBeInstanceOf(ExpoError);
|
||||
expect(error.metadata).toEqual({ packageName: '@expo/config' });
|
||||
expect(error.format()).toEqual({
|
||||
expect(error.toExternalExpoError()).toEqual({
|
||||
errorCode: 'ERR_CODE',
|
||||
message: 'canonical message',
|
||||
docsUrl: 'https://docs.example.dev',
|
||||
@@ -74,6 +56,16 @@ describe(UserError, () => {
|
||||
expect(error.cause).toBe(cause);
|
||||
});
|
||||
|
||||
it('supports trackingCode and buildPhase in options', () => {
|
||||
const error = new UserError('ERR_CODE', 'message', {
|
||||
trackingCode: 'TRACKING_CODE',
|
||||
buildPhase: BuildPhase.PREBUILD,
|
||||
});
|
||||
|
||||
expect(error.trackingCode).toBe('TRACKING_CODE');
|
||||
expect(error.buildPhase).toBe(BuildPhase.PREBUILD);
|
||||
});
|
||||
|
||||
it('supports docsUrl, metadata, and cause in options', () => {
|
||||
const cause = new Error('root cause');
|
||||
const error = new UserError('ERR_CODE', 'message', {
|
||||
@@ -87,3 +79,30 @@ describe(UserError, () => {
|
||||
expect(error.cause).toBe(cause);
|
||||
});
|
||||
});
|
||||
|
||||
describe(SystemError, () => {
|
||||
it('always formats with SERVER_ERROR and preserves trackingCode', () => {
|
||||
const cause = new Error('root cause');
|
||||
const error = new SystemError('system message', {
|
||||
trackingCode: 'TRACKING_CODE',
|
||||
docsUrl: 'https://docs.example.dev',
|
||||
buildPhase: BuildPhase.PREBUILD,
|
||||
metadata: { packageName: '@expo/config' },
|
||||
cause,
|
||||
});
|
||||
|
||||
expect(error).toBeInstanceOf(ExpoError);
|
||||
expect(error.errorCode).toBe(ErrorCode.SERVER_ERROR);
|
||||
expect(error.trackingCode).toBe('TRACKING_CODE');
|
||||
expect(error.docsUrl).toBe('https://docs.example.dev');
|
||||
expect(error.metadata).toEqual({ packageName: '@expo/config' });
|
||||
expect(error.buildPhase).toBe(BuildPhase.PREBUILD);
|
||||
expect(error.cause).toBe(cause);
|
||||
expect(error.toExternalExpoError()).toEqual({
|
||||
errorCode: ErrorCode.SERVER_ERROR,
|
||||
message: 'system message',
|
||||
docsUrl: 'https://docs.example.dev',
|
||||
buildPhase: BuildPhase.PREBUILD,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -54,7 +54,10 @@ export abstract class ExpoError<TMetadata extends ErrorMetadata = ErrorMetadata>
|
||||
this.buildPhase = details.buildPhase;
|
||||
}
|
||||
|
||||
public format(): ExternalBuildError {
|
||||
/**
|
||||
* Serialized error payload used by the orchestrator-worker API.
|
||||
*/
|
||||
public toExternalExpoError(): ExternalBuildError {
|
||||
return {
|
||||
errorCode: this.errorCode,
|
||||
message: this.message,
|
||||
@@ -64,29 +67,49 @@ export abstract class ExpoError<TMetadata extends ErrorMetadata = ErrorMetadata>
|
||||
}
|
||||
}
|
||||
|
||||
export class BuildError<
|
||||
TMetadata extends ErrorMetadata = ErrorMetadata,
|
||||
> extends ExpoError<TMetadata> {
|
||||
constructor(message: string, details: ExpoErrorDetails<TMetadata>) {
|
||||
super(message, details);
|
||||
}
|
||||
}
|
||||
|
||||
export class UserError<
|
||||
TMetadata extends ErrorMetadata = ErrorMetadata,
|
||||
> extends ExpoError<TMetadata> {
|
||||
constructor(
|
||||
public errorCode: string,
|
||||
public message: string,
|
||||
message: string,
|
||||
options?: {
|
||||
trackingCode?: string;
|
||||
docsUrl?: string;
|
||||
buildPhase?: BuildPhase;
|
||||
metadata?: TMetadata;
|
||||
cause?: unknown;
|
||||
}
|
||||
) {
|
||||
super(message, {
|
||||
errorCode,
|
||||
trackingCode: options?.trackingCode,
|
||||
docsUrl: options?.docsUrl,
|
||||
buildPhase: options?.buildPhase,
|
||||
metadata: options?.metadata,
|
||||
cause: options?.cause,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class SystemError<
|
||||
TMetadata extends ErrorMetadata = ErrorMetadata,
|
||||
> extends ExpoError<TMetadata> {
|
||||
constructor(
|
||||
message: string,
|
||||
options?: {
|
||||
trackingCode?: string;
|
||||
docsUrl?: string;
|
||||
buildPhase?: BuildPhase;
|
||||
metadata?: TMetadata;
|
||||
cause?: unknown;
|
||||
}
|
||||
) {
|
||||
super(message, {
|
||||
errorCode: ErrorCode.SERVER_ERROR,
|
||||
trackingCode: options?.trackingCode,
|
||||
docsUrl: options?.docsUrl,
|
||||
buildPhase: options?.buildPhase,
|
||||
metadata: options?.metadata,
|
||||
cause: options?.cause,
|
||||
});
|
||||
@@ -99,28 +122,25 @@ export class UnknownError extends UserError {
|
||||
ErrorCode.UNKNOWN_ERROR,
|
||||
buildPhase
|
||||
? `Unknown error. See logs of the ${buildPhaseDisplayName[buildPhase]} build phase for more information.`
|
||||
: 'Unknown error. See logs for more information.'
|
||||
: 'Unknown error. See logs for more information.',
|
||||
{ buildPhase }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class UnknownBuildError extends BuildError {
|
||||
export class UnknownBuildError extends UserError {
|
||||
constructor() {
|
||||
const errorCode = ErrorCode.UNKNOWN_ERROR;
|
||||
const message = 'Unknown error. See logs for more information.';
|
||||
super(message, {
|
||||
errorCode,
|
||||
});
|
||||
super(errorCode, message);
|
||||
}
|
||||
}
|
||||
|
||||
export class UnknownCustomBuildError extends BuildError {
|
||||
export class UnknownCustomBuildError extends UserError {
|
||||
constructor() {
|
||||
const errorCode = ErrorCode.UNKNOWN_CUSTOM_BUILD_ERROR;
|
||||
const message = 'Unknown custom build error. See logs for more information.';
|
||||
super(message, {
|
||||
errorCode,
|
||||
});
|
||||
super(errorCode, message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+94
-93
@@ -178,7 +178,7 @@ ALIASES
|
||||
$ eas login
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/account/login.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/account/login.ts)_
|
||||
|
||||
## `eas account:logout`
|
||||
|
||||
@@ -195,7 +195,7 @@ ALIASES
|
||||
$ eas logout
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/account/logout.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/account/logout.ts)_
|
||||
|
||||
## `eas account:usage [ACCOUNT_NAME]`
|
||||
|
||||
@@ -217,7 +217,7 @@ DESCRIPTION
|
||||
view account usage and billing for the current cycle
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/account/usage.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/account/usage.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/account/usage.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/account/usage.ts)_
|
||||
|
||||
## `eas account:view`
|
||||
|
||||
@@ -234,7 +234,7 @@ ALIASES
|
||||
$ eas whoami
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/account/view.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/account/view.ts)_
|
||||
|
||||
## `eas analytics [STATUS]`
|
||||
|
||||
@@ -248,7 +248,7 @@ DESCRIPTION
|
||||
display or change analytics settings
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/analytics.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/analytics.ts)_
|
||||
|
||||
## `eas autocomplete [SHELL]`
|
||||
|
||||
@@ -300,7 +300,7 @@ DESCRIPTION
|
||||
create a branch
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/branch/create.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/branch/create.ts)_
|
||||
|
||||
## `eas branch:delete [NAME]`
|
||||
|
||||
@@ -321,7 +321,7 @@ DESCRIPTION
|
||||
delete a branch
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/branch/delete.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/branch/delete.ts)_
|
||||
|
||||
## `eas branch:list`
|
||||
|
||||
@@ -341,7 +341,7 @@ DESCRIPTION
|
||||
list all branches
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/branch/list.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/branch/list.ts)_
|
||||
|
||||
## `eas branch:rename`
|
||||
|
||||
@@ -361,7 +361,7 @@ DESCRIPTION
|
||||
rename a branch
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/branch/rename.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/branch/rename.ts)_
|
||||
|
||||
## `eas branch:view [NAME]`
|
||||
|
||||
@@ -384,7 +384,7 @@ DESCRIPTION
|
||||
view a branch
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/branch/view.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/branch/view.ts)_
|
||||
|
||||
## `eas build`
|
||||
|
||||
@@ -422,7 +422,7 @@ DESCRIPTION
|
||||
start a build
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/build/index.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/build/index.ts)_
|
||||
|
||||
## `eas build:cancel [BUILD_ID]`
|
||||
|
||||
@@ -442,7 +442,7 @@ DESCRIPTION
|
||||
cancel a build
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/build/cancel.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/build/cancel.ts)_
|
||||
|
||||
## `eas build:configure`
|
||||
|
||||
@@ -460,7 +460,7 @@ DESCRIPTION
|
||||
configure the project to support EAS Build
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/build/configure.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/build/configure.ts)_
|
||||
|
||||
## `eas build:delete [BUILD_ID]`
|
||||
|
||||
@@ -480,7 +480,7 @@ DESCRIPTION
|
||||
delete a build
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/delete.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/build/delete.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/delete.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/build/delete.ts)_
|
||||
|
||||
## `eas build:dev`
|
||||
|
||||
@@ -488,19 +488,20 @@ run dev client simulator/emulator build with matching fingerprint or create a ne
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas build:dev [-p ios|android] [-e PROFILE_NAME]
|
||||
$ eas build:dev [-p ios|android] [-e PROFILE_NAME] [--skip-build-if-not-found]
|
||||
|
||||
FLAGS
|
||||
-e, --profile=PROFILE_NAME Name of the build profile from eas.json. It must be a profile allowing to create
|
||||
emulator/simulator internal distribution dev client builds. The "development-simulator"
|
||||
build profile will be selected by default.
|
||||
-p, --platform=<option> <options: ios|android>
|
||||
-e, --profile=PROFILE_NAME Name of the build profile from eas.json. It must be a profile allowing to create
|
||||
emulator/simulator internal distribution dev client builds. The "development-simulator"
|
||||
build profile will be selected by default.
|
||||
-p, --platform=<option> <options: ios|android>
|
||||
--skip-build-if-not-found Skip build if no successful build with matching fingerprint is found.
|
||||
|
||||
DESCRIPTION
|
||||
run dev client simulator/emulator build with matching fingerprint or create a new one
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/dev.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/build/dev.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/dev.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/build/dev.ts)_
|
||||
|
||||
## `eas build:download`
|
||||
|
||||
@@ -521,7 +522,7 @@ DESCRIPTION
|
||||
download simulator/emulator builds for a given fingerprint hash
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/download.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/build/download.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/download.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/build/download.ts)_
|
||||
|
||||
## `eas build:inspect`
|
||||
|
||||
@@ -559,7 +560,7 @@ DESCRIPTION
|
||||
inspect the state of the project at specific build stages, useful for troubleshooting
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/inspect.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/build/inspect.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/inspect.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/build/inspect.ts)_
|
||||
|
||||
## `eas build:list`
|
||||
|
||||
@@ -599,7 +600,7 @@ DESCRIPTION
|
||||
list all builds for your project
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/build/list.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/build/list.ts)_
|
||||
|
||||
## `eas build:resign`
|
||||
|
||||
@@ -629,7 +630,7 @@ DESCRIPTION
|
||||
re-sign a build archive
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/resign.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/build/resign.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/resign.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/build/resign.ts)_
|
||||
|
||||
## `eas build:run`
|
||||
|
||||
@@ -655,7 +656,7 @@ DESCRIPTION
|
||||
run simulator/emulator builds from eas-cli
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/run.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/build/run.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/run.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/build/run.ts)_
|
||||
|
||||
## `eas build:submit`
|
||||
|
||||
@@ -707,7 +708,7 @@ DESCRIPTION
|
||||
get the latest version from EAS servers
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/version/get.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/build/version/get.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/version/get.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/build/version/get.ts)_
|
||||
|
||||
## `eas build:version:set`
|
||||
|
||||
@@ -725,7 +726,7 @@ DESCRIPTION
|
||||
update version of an app
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/version/set.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/build/version/set.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/version/set.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/build/version/set.ts)_
|
||||
|
||||
## `eas build:version:sync`
|
||||
|
||||
@@ -743,7 +744,7 @@ DESCRIPTION
|
||||
update a version in native code with a value stored on EAS servers
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/version/sync.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/build/version/sync.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/version/sync.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/build/version/sync.ts)_
|
||||
|
||||
## `eas build:view [BUILD_ID]`
|
||||
|
||||
@@ -760,7 +761,7 @@ DESCRIPTION
|
||||
view a build for your project
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/build/view.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/build/view.ts)_
|
||||
|
||||
## `eas channel:create [NAME]`
|
||||
|
||||
@@ -781,7 +782,7 @@ DESCRIPTION
|
||||
create a channel
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/channel/create.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/channel/create.ts)_
|
||||
|
||||
## `eas channel:delete [NAME]`
|
||||
|
||||
@@ -802,7 +803,7 @@ DESCRIPTION
|
||||
Delete a channel
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/delete.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/channel/delete.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/channel/delete.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/channel/delete.ts)_
|
||||
|
||||
## `eas channel:edit [NAME]`
|
||||
|
||||
@@ -824,7 +825,7 @@ DESCRIPTION
|
||||
point a channel at a new branch
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/edit.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/channel/edit.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/channel/edit.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/channel/edit.ts)_
|
||||
|
||||
## `eas channel:list`
|
||||
|
||||
@@ -844,7 +845,7 @@ DESCRIPTION
|
||||
list all channels
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/channel/list.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/channel/list.ts)_
|
||||
|
||||
## `eas channel:pause [NAME]`
|
||||
|
||||
@@ -866,7 +867,7 @@ DESCRIPTION
|
||||
pause a channel to stop it from sending updates
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/pause.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/channel/pause.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/channel/pause.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/channel/pause.ts)_
|
||||
|
||||
## `eas channel:resume [NAME]`
|
||||
|
||||
@@ -888,7 +889,7 @@ DESCRIPTION
|
||||
resume a channel to start sending updates
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/resume.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/channel/resume.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/channel/resume.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/channel/resume.ts)_
|
||||
|
||||
## `eas channel:rollout [CHANNEL]`
|
||||
|
||||
@@ -923,7 +924,7 @@ DESCRIPTION
|
||||
Roll a new branch out on a channel incrementally.
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/rollout.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/channel/rollout.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/channel/rollout.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/channel/rollout.ts)_
|
||||
|
||||
## `eas channel:view [NAME]`
|
||||
|
||||
@@ -946,7 +947,7 @@ DESCRIPTION
|
||||
view a channel
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/channel/view.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/channel/view.ts)_
|
||||
|
||||
## `eas config`
|
||||
|
||||
@@ -967,7 +968,7 @@ DESCRIPTION
|
||||
display project configuration (app.json + eas.json)
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/config.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/config.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/config.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/config.ts)_
|
||||
|
||||
## `eas credentials`
|
||||
|
||||
@@ -984,7 +985,7 @@ DESCRIPTION
|
||||
manage credentials
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/credentials/index.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/credentials/index.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/credentials/index.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/credentials/index.ts)_
|
||||
|
||||
## `eas credentials:configure-build`
|
||||
|
||||
@@ -1002,7 +1003,7 @@ DESCRIPTION
|
||||
Set up credentials for building your project.
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/credentials/configure-build.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/credentials/configure-build.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/credentials/configure-build.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/credentials/configure-build.ts)_
|
||||
|
||||
## `eas deploy [options]`
|
||||
|
||||
@@ -1031,7 +1032,7 @@ ALIASES
|
||||
$ eas worker:deploy
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/deploy/index.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/deploy/index.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/deploy/index.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/deploy/index.ts)_
|
||||
|
||||
## `eas deploy:alias`
|
||||
|
||||
@@ -1056,7 +1057,7 @@ ALIASES
|
||||
$ eas deploy:promote
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/deploy/alias/index.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/deploy/alias/index.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/deploy/alias/index.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/deploy/alias/index.ts)_
|
||||
|
||||
## `eas deploy:alias:delete [ALIAS_NAME]`
|
||||
|
||||
@@ -1077,7 +1078,7 @@ ALIASES
|
||||
$ eas worker:alias:delete
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/deploy/alias/delete.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/deploy/alias/delete.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/deploy/alias/delete.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/deploy/alias/delete.ts)_
|
||||
|
||||
## `eas deploy:delete [DEPLOYMENT_ID]`
|
||||
|
||||
@@ -1098,7 +1099,7 @@ ALIASES
|
||||
$ eas worker:delete
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/deploy/delete.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/deploy/delete.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/deploy/delete.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/deploy/delete.ts)_
|
||||
|
||||
## `eas deploy:promote`
|
||||
|
||||
@@ -1135,7 +1136,7 @@ DESCRIPTION
|
||||
register new Apple Devices to use for internal distribution
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/device/create.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/device/create.ts)_
|
||||
|
||||
## `eas device:delete`
|
||||
|
||||
@@ -1155,7 +1156,7 @@ DESCRIPTION
|
||||
remove a registered device from your account
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/device/delete.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/device/delete.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/device/delete.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/device/delete.ts)_
|
||||
|
||||
## `eas device:list`
|
||||
|
||||
@@ -1176,7 +1177,7 @@ DESCRIPTION
|
||||
list all registered devices for your account
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/device/list.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/device/list.ts)_
|
||||
|
||||
## `eas device:rename`
|
||||
|
||||
@@ -1197,7 +1198,7 @@ DESCRIPTION
|
||||
rename a registered device
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/device/rename.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/device/rename.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/device/rename.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/device/rename.ts)_
|
||||
|
||||
## `eas device:view [UDID]`
|
||||
|
||||
@@ -1211,7 +1212,7 @@ DESCRIPTION
|
||||
view a device for your project
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/device/view.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/device/view.ts)_
|
||||
|
||||
## `eas diagnostics`
|
||||
|
||||
@@ -1225,7 +1226,7 @@ DESCRIPTION
|
||||
display environment info
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/diagnostics.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/diagnostics.ts)_
|
||||
|
||||
## `eas env:create [ENVIRONMENT]`
|
||||
|
||||
@@ -1257,7 +1258,7 @@ DESCRIPTION
|
||||
create an environment variable for the current project or account
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/env/create.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/env/create.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/env/create.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/env/create.ts)_
|
||||
|
||||
## `eas env:delete [ENVIRONMENT]`
|
||||
|
||||
@@ -1283,7 +1284,7 @@ DESCRIPTION
|
||||
delete an environment variable for the current project or account
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/env/delete.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/env/delete.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/env/delete.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/env/delete.ts)_
|
||||
|
||||
## `eas env:exec ENVIRONMENT BASH_COMMAND`
|
||||
|
||||
@@ -1305,7 +1306,7 @@ DESCRIPTION
|
||||
execute a command with environment variables from the selected environment
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/env/exec.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/env/exec.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/env/exec.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/env/exec.ts)_
|
||||
|
||||
## `eas env:get [ENVIRONMENT]`
|
||||
|
||||
@@ -1333,7 +1334,7 @@ DESCRIPTION
|
||||
view an environment variable for the current project or account
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/env/get.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/env/get.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/env/get.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/env/get.ts)_
|
||||
|
||||
## `eas env:list [ENVIRONMENT]`
|
||||
|
||||
@@ -1361,7 +1362,7 @@ DESCRIPTION
|
||||
list environment variables for the current project or account
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/env/list.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/env/list.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/env/list.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/env/list.ts)_
|
||||
|
||||
## `eas env:pull [ENVIRONMENT]`
|
||||
|
||||
@@ -1384,7 +1385,7 @@ DESCRIPTION
|
||||
pull environment variables for the selected environment to .env file
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/env/pull.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/env/pull.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/env/pull.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/env/pull.ts)_
|
||||
|
||||
## `eas env:push [ENVIRONMENT]`
|
||||
|
||||
@@ -1406,7 +1407,7 @@ DESCRIPTION
|
||||
push environment variables from .env file to the selected environment
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/env/push.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/env/push.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/env/push.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/env/push.ts)_
|
||||
|
||||
## `eas env:update [ENVIRONMENT]`
|
||||
|
||||
@@ -1440,7 +1441,7 @@ DESCRIPTION
|
||||
update an environment variable on the current project or account
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/env/update.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/env/update.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/env/update.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/env/update.ts)_
|
||||
|
||||
## `eas fingerprint:compare [HASH1] [HASH2]`
|
||||
|
||||
@@ -1484,7 +1485,7 @@ EXAMPLES
|
||||
$ eas fingerprint:compare <FINGERPRINT-HASH> --update-id <UPDATE-ID> # Compare fingerprint from update against provided fingerprint
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/fingerprint/compare.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/fingerprint/compare.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/fingerprint/compare.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/fingerprint/compare.ts)_
|
||||
|
||||
## `eas fingerprint:generate`
|
||||
|
||||
@@ -1515,7 +1516,7 @@ EXAMPLES
|
||||
$ eas fingerprint:generate --json --non-interactive --platform android # Output fingerprint json to stdout
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/fingerprint/generate.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/fingerprint/generate.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/fingerprint/generate.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/fingerprint/generate.ts)_
|
||||
|
||||
## `eas help [COMMAND]`
|
||||
|
||||
@@ -1624,7 +1625,7 @@ DESCRIPTION
|
||||
validate the local store configuration
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/metadata/lint.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/metadata/lint.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/metadata/lint.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/metadata/lint.ts)_
|
||||
|
||||
## `eas metadata:pull`
|
||||
|
||||
@@ -1641,7 +1642,7 @@ DESCRIPTION
|
||||
generate the local store configuration from the app stores
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/metadata/pull.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/metadata/pull.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/metadata/pull.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/metadata/pull.ts)_
|
||||
|
||||
## `eas metadata:push`
|
||||
|
||||
@@ -1658,7 +1659,7 @@ DESCRIPTION
|
||||
sync the local store configuration to the app stores
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/metadata/push.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/metadata/push.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/metadata/push.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/metadata/push.ts)_
|
||||
|
||||
## `eas new [PATH]`
|
||||
|
||||
@@ -1710,7 +1711,7 @@ DESCRIPTION
|
||||
open the project page in a web browser
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/open.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/open.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/open.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/open.ts)_
|
||||
|
||||
## `eas project:info`
|
||||
|
||||
@@ -1724,7 +1725,7 @@ DESCRIPTION
|
||||
information about the current project
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/project/info.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/project/info.ts)_
|
||||
|
||||
## `eas project:init`
|
||||
|
||||
@@ -1747,7 +1748,7 @@ ALIASES
|
||||
$ eas init
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/project/init.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/project/init.ts)_
|
||||
|
||||
## `eas project:new [PATH]`
|
||||
|
||||
@@ -1771,7 +1772,7 @@ ALIASES
|
||||
$ eas new
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/project/new.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/project/new.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/project/new.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/project/new.ts)_
|
||||
|
||||
## `eas project:onboarding [TARGET_PROJECT_DIRECTORY]`
|
||||
|
||||
@@ -1789,7 +1790,7 @@ ALIASES
|
||||
$ eas onboarding
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/project/onboarding.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/project/onboarding.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/project/onboarding.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/project/onboarding.ts)_
|
||||
|
||||
## `eas submit`
|
||||
|
||||
@@ -1822,7 +1823,7 @@ ALIASES
|
||||
$ eas build:submit
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/submit.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/submit.ts)_
|
||||
|
||||
## `eas update`
|
||||
|
||||
@@ -1865,7 +1866,7 @@ DESCRIPTION
|
||||
publish an update group
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/update/index.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/update/index.ts)_
|
||||
|
||||
## `eas update:configure`
|
||||
|
||||
@@ -1886,7 +1887,7 @@ DESCRIPTION
|
||||
configure the project to support EAS Update
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/configure.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/update/configure.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/update/configure.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/update/configure.ts)_
|
||||
|
||||
## `eas update:delete GROUPID`
|
||||
|
||||
@@ -1907,7 +1908,7 @@ DESCRIPTION
|
||||
delete all the updates in an update group
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/delete.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/update/delete.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/update/delete.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/update/delete.ts)_
|
||||
|
||||
## `eas update:edit [GROUPID]`
|
||||
|
||||
@@ -1932,7 +1933,7 @@ DESCRIPTION
|
||||
edit all the updates in an update group
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/edit.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/update/edit.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/update/edit.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/update/edit.ts)_
|
||||
|
||||
## `eas update:list`
|
||||
|
||||
@@ -1959,7 +1960,7 @@ DESCRIPTION
|
||||
view the recent updates
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/update/list.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/update/list.ts)_
|
||||
|
||||
## `eas update:republish`
|
||||
|
||||
@@ -1997,7 +1998,7 @@ DESCRIPTION
|
||||
roll back to an existing update
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/republish.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/update/republish.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/update/republish.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/update/republish.ts)_
|
||||
|
||||
## `eas update:revert-update-rollout`
|
||||
|
||||
@@ -2025,7 +2026,7 @@ DESCRIPTION
|
||||
revert a rollout update for a project
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/revert-update-rollout.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/update/revert-update-rollout.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/update/revert-update-rollout.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/update/revert-update-rollout.ts)_
|
||||
|
||||
## `eas update:roll-back-to-embedded`
|
||||
|
||||
@@ -2055,7 +2056,7 @@ DESCRIPTION
|
||||
roll back to the embedded update
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/roll-back-to-embedded.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/update/roll-back-to-embedded.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/update/roll-back-to-embedded.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/update/roll-back-to-embedded.ts)_
|
||||
|
||||
## `eas update:rollback`
|
||||
|
||||
@@ -2076,7 +2077,7 @@ DESCRIPTION
|
||||
instead execute "eas update:republish" or "eas update:roll-back-to-embedded".
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/rollback.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/update/rollback.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/update/rollback.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/update/rollback.ts)_
|
||||
|
||||
## `eas update:view GROUPID`
|
||||
|
||||
@@ -2096,7 +2097,7 @@ DESCRIPTION
|
||||
update group details
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/update/view.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/update/view.ts)_
|
||||
|
||||
## `eas upload`
|
||||
|
||||
@@ -2117,7 +2118,7 @@ DESCRIPTION
|
||||
upload a local build and generate a sharable link
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/upload.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/upload.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/upload.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/upload.ts)_
|
||||
|
||||
## `eas webhook:create`
|
||||
|
||||
@@ -2139,7 +2140,7 @@ DESCRIPTION
|
||||
create a webhook
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/webhook/create.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/webhook/create.ts)_
|
||||
|
||||
## `eas webhook:delete [ID]`
|
||||
|
||||
@@ -2159,7 +2160,7 @@ DESCRIPTION
|
||||
delete a webhook
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/webhook/delete.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/webhook/delete.ts)_
|
||||
|
||||
## `eas webhook:list`
|
||||
|
||||
@@ -2178,7 +2179,7 @@ DESCRIPTION
|
||||
list webhooks
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/webhook/list.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/webhook/list.ts)_
|
||||
|
||||
## `eas webhook:update`
|
||||
|
||||
@@ -2201,7 +2202,7 @@ DESCRIPTION
|
||||
update a webhook
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/webhook/update.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/webhook/update.ts)_
|
||||
|
||||
## `eas webhook:view ID`
|
||||
|
||||
@@ -2218,7 +2219,7 @@ DESCRIPTION
|
||||
view a webhook
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/webhook/view.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/webhook/view.ts)_
|
||||
|
||||
## `eas whoami`
|
||||
|
||||
@@ -2339,7 +2340,7 @@ DESCRIPTION
|
||||
to cancel.
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/workflow/cancel.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/workflow/cancel.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/workflow/cancel.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/workflow/cancel.ts)_
|
||||
|
||||
## `eas workflow:create [NAME]`
|
||||
|
||||
@@ -2359,7 +2360,7 @@ DESCRIPTION
|
||||
create a new workflow configuration YAML file
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/workflow/create.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/workflow/create.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/workflow/create.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/workflow/create.ts)_
|
||||
|
||||
## `eas workflow:logs [ID]`
|
||||
|
||||
@@ -2383,7 +2384,7 @@ DESCRIPTION
|
||||
If no ID is passed in, you will be prompted to select from recent workflow runs for the current project.
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/workflow/logs.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/workflow/logs.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/workflow/logs.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/workflow/logs.ts)_
|
||||
|
||||
## `eas workflow:run FILE`
|
||||
|
||||
@@ -2423,7 +2424,7 @@ FLAG DESCRIPTIONS
|
||||
Exit codes: 0 = success, 11 = failure, 12 = canceled, 13 = wait aborted.
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/workflow/run.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/workflow/run.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/workflow/run.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/workflow/run.ts)_
|
||||
|
||||
## `eas workflow:runs`
|
||||
|
||||
@@ -2445,7 +2446,7 @@ DESCRIPTION
|
||||
list recent workflow runs for this project, with their IDs, statuses, and timestamps
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/workflow/runs.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/workflow/runs.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/workflow/runs.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/workflow/runs.ts)_
|
||||
|
||||
## `eas workflow:status [WORKFLOW_RUN_ID]`
|
||||
|
||||
@@ -2473,7 +2474,7 @@ FLAG DESCRIPTIONS
|
||||
Exit codes: 0 = success, 11 = failure, 12 = canceled, 13 = wait aborted.
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/workflow/status.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/workflow/status.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/workflow/status.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/workflow/status.ts)_
|
||||
|
||||
## `eas workflow:validate PATH`
|
||||
|
||||
@@ -2493,7 +2494,7 @@ DESCRIPTION
|
||||
validate a workflow configuration yaml file
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/workflow/validate.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/workflow/validate.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/workflow/validate.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/workflow/validate.ts)_
|
||||
|
||||
## `eas workflow:view [ID]`
|
||||
|
||||
@@ -2515,5 +2516,5 @@ DESCRIPTION
|
||||
workflow runs for the current project.
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/workflow/view.ts](https://github.com/expo/eas-cli/blob/v18.3.0/packages/eas-cli/src/commands/workflow/view.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/workflow/view.ts](https://github.com/expo/eas-cli/blob/v18.4.0/packages/eas-cli/src/commands/workflow/view.ts)_
|
||||
<!-- commandsstop -->
|
||||
Generated
+3067
-359
File diff suppressed because it is too large.
Load diff
@@ -1,4 +1,12 @@
|
||||
/* eslint-disable no-console */
|
||||
|
||||
// Simulate an interactive environment so that commands default to interactive mode in tests.
|
||||
// Without this, isNonInteractiveByDefault() returns true because:
|
||||
// 1. Jest runs in a non-TTY environment (!process.stdin.isTTY)
|
||||
// 2. CI environments set CI=1 (boolish('CI', false))
|
||||
Object.defineProperty(process.stdin, 'isTTY', { value: true, writable: true });
|
||||
delete process.env.CI;
|
||||
|
||||
beforeAll(() => {
|
||||
console.log = jest.fn();
|
||||
console.warn = jest.fn();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "eas-cli",
|
||||
"version": "18.3.0",
|
||||
"version": "18.4.0",
|
||||
"description": "EAS command line tool",
|
||||
"keywords": [
|
||||
"cli",
|
||||
@@ -28,7 +28,7 @@
|
||||
"scripts": {
|
||||
"postpack": "rimraf oclif.manifest.json",
|
||||
"prepack": "yarn rebuild && node ./scripts/prepack.js",
|
||||
"rebuild": "rimraf build *.tsbuildinfo && yarn build",
|
||||
"rebuild": "rimraf build \"*.tsbuildinfo\" && yarn build",
|
||||
"build": "yarn typecheck-for-build && yarn copy-new-templates",
|
||||
"build-allow-unused": "tsc --project tsconfig.allowUnused.json",
|
||||
"watch": "yarn typecheck-for-build --watch --preserveWatchOutput",
|
||||
@@ -39,7 +39,7 @@
|
||||
"version": "yarn run -T oclif readme && node scripts/patch-readme && git add README.md",
|
||||
"generate-graphql-code": "graphql-codegen --config graphql-codegen.yml",
|
||||
"verify-graphql-code": "./scripts/verify-graphql.sh",
|
||||
"clean": "rimraf dist build tmp node_modules yarn-error.log *.tsbuildinfo",
|
||||
"clean": "rimraf dist build tmp node_modules yarn-error.log \"*.tsbuildinfo\"",
|
||||
"copy-new-templates": "rimraf build/commandUtils/new/templates && mkdir -p build/commandUtils/new && cp -r src/commandUtils/new/templates build/commandUtils/new"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -47,8 +47,8 @@
|
||||
"@expo/code-signing-certificates": "0.0.5",
|
||||
"@expo/config": "10.0.6",
|
||||
"@expo/config-plugins": "9.0.12",
|
||||
"@expo/eas-build-job": "18.2.0",
|
||||
"@expo/eas-json": "18.2.0",
|
||||
"@expo/eas-build-job": "18.4.0",
|
||||
"@expo/eas-json": "18.4.0",
|
||||
"@expo/env": "^1.0.0",
|
||||
"@expo/json-file": "8.3.3",
|
||||
"@expo/logger": "18.0.1",
|
||||
@@ -63,11 +63,12 @@
|
||||
"@expo/results": "1.0.0",
|
||||
"@expo/rudder-sdk-node": "1.1.1",
|
||||
"@expo/spawn-async": "1.7.2",
|
||||
"@expo/steps": "18.2.0",
|
||||
"@expo/steps": "18.4.0",
|
||||
"@expo/timeago.js": "1.0.0",
|
||||
"@oclif/core": "^4.8.3",
|
||||
"@oclif/plugin-autocomplete": "^3.2.40",
|
||||
"@segment/ajv-human-errors": "^2.1.2",
|
||||
"@sentry/node": "7.77.0",
|
||||
"@urql/core": "4.0.11",
|
||||
"@urql/exchange-retry": "1.2.0",
|
||||
"ajv": "8.11.0",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
getMockEmptyWorkflowRunsFragment,
|
||||
getMockWorkflowCustomJobFragment,
|
||||
getMockWorkflowRunWithBuildJobsFragment,
|
||||
getMockWorkflowRunWithJobsFragment,
|
||||
getMockWorkflowRunsFragment,
|
||||
mockCommandContext,
|
||||
@@ -124,7 +125,8 @@ describe(WorkflowLogView, () => {
|
||||
.mocked(AppQuery.byIdWorkflowRunsFilteredByStatusAsync)
|
||||
.mockResolvedValue(getMockWorkflowRunsFragment({ withJobs: 1 }));
|
||||
let promptCalls = 0;
|
||||
jest.mocked(promptAsync).mockImplementation(async () => {
|
||||
let stepPrompt: any;
|
||||
jest.mocked(promptAsync).mockImplementation(async prompt => {
|
||||
if (promptCalls === 0) {
|
||||
promptCalls++;
|
||||
return { selectedRun: 'build1' };
|
||||
@@ -132,13 +134,14 @@ describe(WorkflowLogView, () => {
|
||||
promptCalls++;
|
||||
return { selectedJob: 0 };
|
||||
} else {
|
||||
return { selectedStep: 'step1' };
|
||||
stepPrompt = prompt;
|
||||
return { selectedStep: 'step-id-1' };
|
||||
}
|
||||
});
|
||||
jest
|
||||
.mocked(fetchRawLogsForCustomJobAsync)
|
||||
.mockResolvedValue(
|
||||
'{"result":"test","marker":"test","buildStepDisplayName":"step1","buildStepInternalId":"step1","time":"2022-01-01T00:00:00.000Z","msg":"test"}'
|
||||
'{"result":"success","marker":"end-step","buildStepDisplayName":"Install dependencies","buildStepId":"step-id-1","time":"2022-01-01T00:00:00.000Z","msg":"test"}'
|
||||
);
|
||||
await cmd.run();
|
||||
expect(AppQuery.byIdWorkflowRunsFilteredByStatusAsync).toHaveBeenCalledWith(
|
||||
@@ -148,6 +151,14 @@ describe(WorkflowLogView, () => {
|
||||
20
|
||||
);
|
||||
expect(promptAsync).toHaveBeenCalledTimes(3);
|
||||
expect(stepPrompt.choices).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
title: 'Install dependencies - success',
|
||||
value: 'step-id-1',
|
||||
}),
|
||||
])
|
||||
);
|
||||
expect(Log.error).not.toHaveBeenCalled();
|
||||
expect(Log.log).toHaveBeenCalledWith(' 2022-01-01T00:00:00.000Z test');
|
||||
});
|
||||
@@ -159,6 +170,9 @@ describe(WorkflowLogView, () => {
|
||||
jest
|
||||
.mocked(AppQuery.byIdWorkflowRunsFilteredByStatusAsync)
|
||||
.mockResolvedValue(getMockWorkflowRunsFragment({ withBuildJobs: 1 }));
|
||||
jest
|
||||
.mocked(WorkflowRunQuery.withJobsByIdAsync)
|
||||
.mockResolvedValue(getMockWorkflowRunWithBuildJobsFragment());
|
||||
jest.mocked(BuildQuery.byIdAsync).mockResolvedValue({
|
||||
id: 'build1',
|
||||
status: BuildStatus.Finished,
|
||||
@@ -181,7 +195,8 @@ describe(WorkflowLogView, () => {
|
||||
logFiles: ['https://example.com/log1'],
|
||||
});
|
||||
let promptCalls = 0;
|
||||
jest.mocked(promptAsync).mockImplementation(async () => {
|
||||
let stepPrompt: any;
|
||||
jest.mocked(promptAsync).mockImplementation(async prompt => {
|
||||
if (promptCalls === 0) {
|
||||
promptCalls++;
|
||||
return { selectedRun: 'build1' };
|
||||
@@ -189,13 +204,14 @@ describe(WorkflowLogView, () => {
|
||||
promptCalls++;
|
||||
return { selectedJob: 0 };
|
||||
} else {
|
||||
return { selectedStep: 'step1' };
|
||||
stepPrompt = prompt;
|
||||
return { selectedStep: 'legacy-step-1' };
|
||||
}
|
||||
});
|
||||
jest
|
||||
.mocked(fetchRawLogsForBuildJobAsync)
|
||||
.mockResolvedValue(
|
||||
'{"result":"test","marker":"test","buildStepDisplayName":"step1","buildStepInternalId":"step1","time":"2022-01-01T00:00:00.000Z","msg":"test"}'
|
||||
'{"result":"success","marker":"end-step","buildStepDisplayName":"step1","buildStepInternalId":"legacy-step-1","time":"2022-01-01T00:00:00.000Z","msg":"test"}'
|
||||
);
|
||||
await cmd.run();
|
||||
expect(AppQuery.byIdWorkflowRunsFilteredByStatusAsync).toHaveBeenCalledWith(
|
||||
@@ -205,9 +221,65 @@ describe(WorkflowLogView, () => {
|
||||
20
|
||||
);
|
||||
expect(promptAsync).toHaveBeenCalledTimes(3);
|
||||
expect(stepPrompt.choices).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
title: 'step1 - success',
|
||||
value: 'legacy-step-1',
|
||||
}),
|
||||
])
|
||||
);
|
||||
expect(Log.error).not.toHaveBeenCalled();
|
||||
expect(Log.log).toHaveBeenCalledWith(' 2022-01-01T00:00:00.000Z test');
|
||||
});
|
||||
test('view logs keeps duplicate display names separated by step id', async () => {
|
||||
const ctx = mockCommandContext(WorkflowLogView, {
|
||||
projectId: mockProjectId,
|
||||
});
|
||||
const cmd = mockTestCommand(WorkflowLogView, [], ctx);
|
||||
jest
|
||||
.mocked(AppQuery.byIdWorkflowRunsFilteredByStatusAsync)
|
||||
.mockResolvedValue(getMockWorkflowRunsFragment({ withJobs: 1 }));
|
||||
let promptCalls = 0;
|
||||
let stepPrompt: any;
|
||||
jest.mocked(promptAsync).mockImplementation(async prompt => {
|
||||
if (promptCalls === 0) {
|
||||
promptCalls++;
|
||||
return { selectedRun: 'build1' };
|
||||
} else if (promptCalls === 1) {
|
||||
promptCalls++;
|
||||
return { selectedJob: 0 };
|
||||
} else {
|
||||
stepPrompt = prompt;
|
||||
return { selectedStep: 'step-id-2' };
|
||||
}
|
||||
});
|
||||
jest
|
||||
.mocked(fetchRawLogsForCustomJobAsync)
|
||||
.mockResolvedValue(
|
||||
[
|
||||
'{"result":"success","marker":"end-step","buildStepDisplayName":"Install","buildStepId":"step-id-1","time":"2022-01-01T00:00:00.000Z","msg":"first"}',
|
||||
'{"result":"fail","marker":"end-step","buildStepDisplayName":"Install","buildStepId":"step-id-2","time":"2022-01-01T00:00:01.000Z","msg":"second"}',
|
||||
].join('\n')
|
||||
);
|
||||
|
||||
await cmd.run();
|
||||
|
||||
expect(promptAsync).toHaveBeenCalledTimes(3);
|
||||
expect(stepPrompt.choices).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
title: 'Install - success',
|
||||
value: 'step-id-1',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
title: 'Install - fail',
|
||||
value: 'step-id-2',
|
||||
}),
|
||||
])
|
||||
);
|
||||
expect(Log.log).toHaveBeenCalledWith(' 2022-01-01T00:00:01.000Z second');
|
||||
});
|
||||
test('view logs for a workflow job, passing in a job ID, all steps', async () => {
|
||||
const ctx = mockCommandContext(WorkflowLogView, {
|
||||
projectId: mockProjectId,
|
||||
|
||||
@@ -12,8 +12,8 @@ import slash from 'slash';
|
||||
|
||||
import { AndroidCredentials } from '../../credentials/android/AndroidCredentialsProvider';
|
||||
import { getCustomBuildConfigPathForJob } from '../../project/customBuildConfig';
|
||||
import { getUsernameForBuildMetadataAndBuildJob } from '../../project/projectUtils';
|
||||
import { BuildContext } from '../context';
|
||||
import { getActorUsername } from '../../user/User';
|
||||
|
||||
interface JobData {
|
||||
projectArchive: ArchiveSource;
|
||||
@@ -29,7 +29,7 @@ export async function prepareJobAsync(
|
||||
ctx: BuildContext<Platform.ANDROID>,
|
||||
jobData: JobData
|
||||
): Promise<Android.Job> {
|
||||
const username = getUsernameForBuildMetadataAndBuildJob(ctx.user);
|
||||
const username = getActorUsername(ctx.user) ?? undefined;
|
||||
const buildProfile: BuildProfile<Platform.ANDROID> = ctx.buildProfile;
|
||||
const projectRootDirectory =
|
||||
slash(path.relative(await ctx.vcsClient.getRootPathAsync(), ctx.projectDir)) || '.';
|
||||
|
||||
@@ -455,6 +455,9 @@ const queueProgressBar = new cliProgress.SingleBar(
|
||||
cliProgress.Presets.rect
|
||||
);
|
||||
|
||||
let statusNewSetAt: number | null = null;
|
||||
const NEW_STATUS_GRACE_PERIOD_MS = 15_000;
|
||||
|
||||
async function handleSingleBuildProgressAsync(
|
||||
{
|
||||
build,
|
||||
@@ -486,11 +489,19 @@ async function handleSingleBuildProgressAsync(
|
||||
case BuildStatus.Finished:
|
||||
spinner.succeed('Build finished');
|
||||
return { refetch: false };
|
||||
case BuildStatus.New:
|
||||
spinner.text = `Build concurrency limit reached for your account. Build will enter queue once a concurrency becomes available. Add additional concurrencies at ${link(
|
||||
formatAccountBillingUrl(accountName)
|
||||
)}.`;
|
||||
case BuildStatus.New: {
|
||||
const now = Date.now();
|
||||
statusNewSetAt ??= now;
|
||||
const newStatusDurationMs = now - statusNewSetAt;
|
||||
if (newStatusDurationMs < NEW_STATUS_GRACE_PERIOD_MS) {
|
||||
spinner.text = 'Waiting for build to get enqueued…';
|
||||
} else {
|
||||
spinner.text = `Build concurrency limit reached for your account. Build will enter queue once a concurrency becomes available. Add additional concurrencies at ${link(
|
||||
formatAccountBillingUrl(accountName)
|
||||
)}.`;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case BuildStatus.InQueue: {
|
||||
spinner.text = 'Build queued...';
|
||||
const progressBarPayload =
|
||||
@@ -553,7 +564,7 @@ const priorityToQueueDisplayName: Record<BuildPriority, string> = {
|
||||
};
|
||||
|
||||
const statusToDisplayName: Record<BuildStatus, string> = {
|
||||
[BuildStatus.New]: 'waiting to enter the queue (concurrency limit reached)',
|
||||
[BuildStatus.New]: 'waiting to enter the queue',
|
||||
[BuildStatus.InQueue]: 'in queue',
|
||||
[BuildStatus.InProgress]: 'in progress',
|
||||
[BuildStatus.PendingCancel]: 'canceled',
|
||||
@@ -571,6 +582,7 @@ async function handleMultipleBuildsProgressAsync(
|
||||
const buildCount = maybeBuilds.length;
|
||||
const builds = maybeBuilds.filter<BuildFragment>(isBuildFragment);
|
||||
|
||||
const someNew = builds.some(build => build.status === BuildStatus.New);
|
||||
const allFinished =
|
||||
builds.filter(build => build.status === BuildStatus.Finished).length === buildCount;
|
||||
const allSettled =
|
||||
@@ -583,6 +595,10 @@ async function handleMultipleBuildsProgressAsync(
|
||||
].includes(build.status)
|
||||
).length === buildCount;
|
||||
|
||||
if (someNew && statusNewSetAt === null) {
|
||||
statusNewSetAt = Date.now();
|
||||
}
|
||||
|
||||
if (allSettled) {
|
||||
if (allFinished) {
|
||||
spinner.succeed(formatSettledBuildsText(builds));
|
||||
@@ -591,7 +607,11 @@ async function handleMultipleBuildsProgressAsync(
|
||||
}
|
||||
return { refetch: false };
|
||||
} else {
|
||||
spinner.text = formatPendingBuildsText(originalSpinnerText, builds);
|
||||
const showConcurrencyWarning =
|
||||
someNew &&
|
||||
statusNewSetAt !== null &&
|
||||
Date.now() - statusNewSetAt >= NEW_STATUS_GRACE_PERIOD_MS;
|
||||
spinner.text = formatPendingBuildsText(originalSpinnerText, builds, showConcurrencyWarning);
|
||||
return { refetch: true };
|
||||
}
|
||||
}
|
||||
@@ -610,12 +630,20 @@ function formatSettledBuildsText(builds: BuildFragment[]): string {
|
||||
.join('\n ');
|
||||
}
|
||||
|
||||
function formatPendingBuildsText(originalSpinnerText: string, builds: BuildFragment[]): string {
|
||||
function formatPendingBuildsText(
|
||||
originalSpinnerText: string,
|
||||
builds: BuildFragment[],
|
||||
showConcurrencyWarning: boolean
|
||||
): string {
|
||||
return [
|
||||
originalSpinnerText,
|
||||
...platforms.map(platform => {
|
||||
const build = builds.find(build => build.platform === platform);
|
||||
const status = build ? statusToDisplayName[build.status] : 'unknown';
|
||||
const concurrencyWarning =
|
||||
showConcurrencyWarning && build?.status === BuildStatus.New
|
||||
? ' (concurrency limit reached)'
|
||||
: '';
|
||||
let extraInfo = '';
|
||||
if (
|
||||
build?.status === BuildStatus.InQueue &&
|
||||
@@ -635,7 +663,7 @@ function formatPendingBuildsText(originalSpinnerText: string, builds: BuildFragm
|
||||
}
|
||||
return `${appPlatformEmojis[platform]} ${
|
||||
appPlatformDisplayNames[platform]
|
||||
} build - status: ${chalk.bold(status)}${extraInfo}`;
|
||||
} build - status: ${chalk.bold(status)}${concurrencyWarning}${extraInfo}`;
|
||||
}),
|
||||
].join('\n ');
|
||||
}
|
||||
|
||||
@@ -14,8 +14,8 @@ import slash from 'slash';
|
||||
import { IosCredentials, TargetCredentials } from '../../credentials/ios/types';
|
||||
import { IosJobSecretsInput } from '../../graphql/generated';
|
||||
import { getCustomBuildConfigPathForJob } from '../../project/customBuildConfig';
|
||||
import { getUsernameForBuildMetadataAndBuildJob } from '../../project/projectUtils';
|
||||
import { BuildContext } from '../context';
|
||||
import { getActorUsername } from '../../user/User';
|
||||
|
||||
interface JobData {
|
||||
projectArchive: ArchiveSource;
|
||||
@@ -32,7 +32,7 @@ export async function prepareJobAsync(
|
||||
ctx: BuildContext<Platform.IOS>,
|
||||
jobData: JobData
|
||||
): Promise<Ios.Job> {
|
||||
const username = getUsernameForBuildMetadataAndBuildJob(ctx.user);
|
||||
const username = getActorUsername(ctx.user) ?? undefined;
|
||||
const buildProfile: BuildProfile<Platform.IOS> = ctx.buildProfile;
|
||||
const projectRootDirectory =
|
||||
slash(path.relative(await ctx.vcsClient.getRootPathAsync(), ctx.projectDir)) || '.';
|
||||
|
||||
@@ -10,12 +10,12 @@ import { LocalBuildMode } from './local';
|
||||
import { BuildDistributionType } from './types';
|
||||
import Log from '../log';
|
||||
import {
|
||||
getUsernameForBuildMetadataAndBuildJob,
|
||||
isExpoUpdatesInstalled,
|
||||
} from '../project/projectUtils';
|
||||
import { readChannelSafelyAsync as readAndroidChannelSafelyAsync } from '../update/android/UpdatesModule';
|
||||
import { readChannelSafelyAsync as readIosChannelSafelyAsync } from '../update/ios/UpdatesModule';
|
||||
import { easCliVersion } from '../utils/easCli';
|
||||
import { getActorUsername } from '../user/User';
|
||||
|
||||
export async function collectMetadataAsync<T extends Platform>(
|
||||
ctx: BuildContext<T>,
|
||||
@@ -49,7 +49,7 @@ export async function collectMetadataAsync<T extends Platform>(
|
||||
ctx.localBuildOptions.localBuildMode === LocalBuildMode.INTERNAL
|
||||
? false
|
||||
: await ctx.vcsClient.hasUncommittedChangesAsync(),
|
||||
username: getUsernameForBuildMetadataAndBuildJob(ctx.user),
|
||||
username: getActorUsername(ctx.user) ?? undefined,
|
||||
message: ctx.message,
|
||||
...(ctx.platform === Platform.IOS && {
|
||||
iosEnterpriseProvisioning: resolveIosEnterpriseProvisioning(
|
||||
|
||||
@@ -27,7 +27,9 @@ import {
|
||||
createAnalyticsAsync,
|
||||
} from '../analytics/AnalyticsManager';
|
||||
import Log, { link } from '../log';
|
||||
import Sentry from '../sentry';
|
||||
import SessionManager from '../user/SessionManager';
|
||||
import { getActorDisplayName } from '../user/User';
|
||||
import { Client } from '../vcs/vcs';
|
||||
|
||||
export type ContextInput<
|
||||
@@ -169,7 +171,6 @@ export default abstract class EasCommand extends Command {
|
||||
* It is set up here to ensure a consistent setup.
|
||||
*/
|
||||
private analyticsInternal?: AnalyticsWithOrchestration;
|
||||
|
||||
/**
|
||||
* Execute the context in the contextDefinition to satisfy command prerequisites.
|
||||
*/
|
||||
@@ -230,7 +231,13 @@ export default abstract class EasCommand extends Command {
|
||||
|
||||
// this is needed for logEvent call below as it identifies the user in the analytics system
|
||||
// if possible
|
||||
await this.sessionManager.getUserAsync();
|
||||
const actor = await this.sessionManager.getUserAsync();
|
||||
if (actor) {
|
||||
Sentry.setUser({
|
||||
id: actor.id,
|
||||
username: getActorDisplayName(actor),
|
||||
});
|
||||
}
|
||||
|
||||
this.analytics.logEvent(CommandEvent.ACTION, {
|
||||
// id is assigned by oclif in constructor based on the filepath:
|
||||
@@ -244,11 +251,13 @@ export default abstract class EasCommand extends Command {
|
||||
// eslint-disable-next-line async-protect/async-suffix
|
||||
override async finally(err: Error): Promise<any> {
|
||||
await this.analytics.flushAsync();
|
||||
await Sentry.flush(2000);
|
||||
return await super.finally(err);
|
||||
}
|
||||
|
||||
protected override catch(err: Error): Promise<any> {
|
||||
let baseMessage = `${this.id} command failed.`;
|
||||
const commandId = this.id ?? 'unknown';
|
||||
let baseMessage = `${commandId} command failed.`;
|
||||
if (err instanceof EasCommandError) {
|
||||
Log.error(err.message);
|
||||
} else if (err instanceof CombinedError && err?.graphQLErrors) {
|
||||
@@ -293,6 +302,17 @@ export default abstract class EasCommand extends Command {
|
||||
Log.error(err.message);
|
||||
}
|
||||
Log.debug(err);
|
||||
Sentry.withScope(scope => {
|
||||
scope.setTag('command', commandId);
|
||||
scope.setTag('error_name', err.name);
|
||||
scope.setExtra('commandId', commandId);
|
||||
scope.setExtra('commandMessage', baseMessage);
|
||||
scope.setExtra('originalMessage', err.message);
|
||||
if (err instanceof CombinedError) {
|
||||
scope.setExtra('graphQLErrorCount', err.graphQLErrors.length);
|
||||
}
|
||||
Sentry.captureException(err);
|
||||
});
|
||||
const sanitizedError = new Error(baseMessage);
|
||||
sanitizedError.stack = err.stack;
|
||||
throw sanitizedError;
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
import { CombinedError } from '@urql/core';
|
||||
import { GraphQLError } from 'graphql/error';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { AnalyticsWithOrchestration, createAnalyticsAsync } from '../../analytics/AnalyticsManager';
|
||||
import Log from '../../log';
|
||||
import SessionManager from '../../user/SessionManager';
|
||||
import EasCommand from '../EasCommand';
|
||||
import { AnalyticsWithOrchestration } from '../../analytics/AnalyticsManager';
|
||||
|
||||
jest.mock('../../user/User');
|
||||
jest.mock('../../user/SessionManager');
|
||||
jest.mock('../../sentry', () => ({
|
||||
__esModule: true,
|
||||
default: {
|
||||
init: jest.fn(),
|
||||
setTag: jest.fn(),
|
||||
setUser: jest.fn(),
|
||||
withScope: jest.fn(),
|
||||
captureException: jest.fn(),
|
||||
flush: jest.fn(async (): Promise<void> => {}),
|
||||
},
|
||||
}));
|
||||
jest.mock('../../analytics/AnalyticsManager', () => {
|
||||
const { CommandEvent } = jest.requireActual('../../analytics/AnalyticsManager');
|
||||
return {
|
||||
@@ -38,20 +44,22 @@ const analytics: AnalyticsWithOrchestration = {
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
|
||||
jest.mocked(createAnalyticsAsync).mockResolvedValue(analytics);
|
||||
});
|
||||
|
||||
const createTestEasCommand = () => {
|
||||
const createTestEasCommand = (): any => {
|
||||
const { createAnalyticsAsync } = jest.requireMock('../../analytics/AnalyticsManager');
|
||||
createAnalyticsAsync.mockResolvedValue(analytics);
|
||||
const EasCommand = require('../EasCommand').default;
|
||||
|
||||
class TestEasCommand extends EasCommand {
|
||||
override async runAsync(): Promise<void> {}
|
||||
async runAsync(): Promise<void> {}
|
||||
}
|
||||
|
||||
TestEasCommand.id = 'testEasCommand'; // normally oclif will assign ids, but b/c this is located outside the commands folder it will not
|
||||
return TestEasCommand;
|
||||
};
|
||||
|
||||
describe(EasCommand.name, () => {
|
||||
describe('EasCommand', () => {
|
||||
describe('without exceptions', () => {
|
||||
// The first test in this suite should have an increased timeout
|
||||
// because of the implementation of Command from @oclif/command.
|
||||
@@ -64,6 +72,7 @@ describe(EasCommand.name, () => {
|
||||
const TestEasCommand = createTestEasCommand();
|
||||
await TestEasCommand.run();
|
||||
|
||||
const SessionManager = jest.requireMock('../../user/SessionManager').default;
|
||||
const sessionManagerSpy = jest.spyOn(SessionManager.prototype, 'getUserAsync');
|
||||
expect(sessionManagerSpy).toBeCalledTimes(1);
|
||||
}, 60_000);
|
||||
@@ -72,6 +81,7 @@ describe(EasCommand.name, () => {
|
||||
const TestEasCommand = createTestEasCommand();
|
||||
await TestEasCommand.run();
|
||||
|
||||
const { createAnalyticsAsync } = jest.requireMock('../../analytics/AnalyticsManager');
|
||||
expect(createAnalyticsAsync).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -82,6 +92,14 @@ describe(EasCommand.name, () => {
|
||||
expect(analytics.flushAsync).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('flushes Sentry', async () => {
|
||||
const TestEasCommand = createTestEasCommand();
|
||||
await TestEasCommand.run();
|
||||
|
||||
const Sentry = jest.requireMock('../../sentry').default;
|
||||
expect(Sentry.flush).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('logs events', async () => {
|
||||
const TestEasCommand = createTestEasCommand();
|
||||
await TestEasCommand.run();
|
||||
@@ -105,8 +123,59 @@ describe(EasCommand.name, () => {
|
||||
});
|
||||
|
||||
describe('catch', () => {
|
||||
it('captures unexpected errors', async () => {
|
||||
const TestEasCommand = createTestEasCommand();
|
||||
const runAsyncMock = jest.spyOn(TestEasCommand.prototype, 'runAsync');
|
||||
const error = new Error('Unexpected, internal error message');
|
||||
const sentryScope = {
|
||||
setTag: jest.fn(),
|
||||
setExtra: jest.fn(),
|
||||
};
|
||||
const Sentry = jest.requireMock('../../sentry').default;
|
||||
Sentry.withScope.mockImplementation((cb: (scope: typeof sentryScope) => void) => {
|
||||
cb(sentryScope);
|
||||
});
|
||||
runAsyncMock.mockImplementation(() => {
|
||||
throw error;
|
||||
});
|
||||
|
||||
try {
|
||||
await TestEasCommand.run();
|
||||
} catch {}
|
||||
|
||||
expect(sentryScope.setTag).toHaveBeenCalledWith('command', TestEasCommand.id);
|
||||
expect(sentryScope.setTag).toHaveBeenCalledWith('error_name', error.name);
|
||||
expect(sentryScope.setExtra).toHaveBeenCalledWith('commandId', TestEasCommand.id);
|
||||
expect(sentryScope.setExtra).toHaveBeenCalledWith(
|
||||
'commandMessage',
|
||||
`${TestEasCommand.id} command failed.`
|
||||
);
|
||||
expect(sentryScope.setExtra).toHaveBeenCalledWith('originalMessage', error.message);
|
||||
expect(Sentry.captureException).toHaveBeenCalledWith(error);
|
||||
});
|
||||
|
||||
it('captures expected command errors', async () => {
|
||||
const TestEasCommand = createTestEasCommand();
|
||||
const runAsyncMock = jest.spyOn(TestEasCommand.prototype, 'runAsync');
|
||||
const { EasCommandError } = require('../errors');
|
||||
const Sentry = jest.requireMock('../../sentry').default;
|
||||
Sentry.withScope.mockImplementation((cb: (scope: any) => void) => {
|
||||
cb({ setTag: jest.fn(), setExtra: jest.fn() });
|
||||
});
|
||||
runAsyncMock.mockImplementation(() => {
|
||||
throw new EasCommandError('Expected user-facing error');
|
||||
});
|
||||
|
||||
try {
|
||||
await TestEasCommand.run();
|
||||
} catch {}
|
||||
|
||||
expect(Sentry.captureException).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('logs the message', async () => {
|
||||
const TestEasCommand = createTestEasCommand();
|
||||
const Log = jest.requireMock('../../log').default;
|
||||
const logErrorSpy = jest.spyOn(Log, 'error');
|
||||
const logDebugSpy = jest.spyOn(Log, 'debug');
|
||||
const runAsyncMock = jest.spyOn(TestEasCommand.prototype, 'runAsync');
|
||||
@@ -124,9 +193,11 @@ describe(EasCommand.name, () => {
|
||||
|
||||
it('logs the cleaned message if needed', async () => {
|
||||
const TestEasCommand = createTestEasCommand();
|
||||
const Log = jest.requireMock('../../log').default;
|
||||
const logErrorSpy = jest.spyOn(Log, 'error');
|
||||
const logDebugSpy = jest.spyOn(Log, 'debug');
|
||||
const runAsyncMock = jest.spyOn(TestEasCommand.prototype, 'runAsync');
|
||||
const { CombinedError } = require('@urql/core');
|
||||
const graphQLErrors = ['Unexpected GraphQL error message'];
|
||||
const error = new CombinedError({ graphQLErrors });
|
||||
runAsyncMock.mockImplementation(() => {
|
||||
@@ -142,9 +213,12 @@ describe(EasCommand.name, () => {
|
||||
|
||||
it('logs the cleaned message with request ID if present', async () => {
|
||||
const TestEasCommand = createTestEasCommand();
|
||||
const Log = jest.requireMock('../../log').default;
|
||||
const logErrorSpy = jest.spyOn(Log, 'error');
|
||||
const logDebugSpy = jest.spyOn(Log, 'debug');
|
||||
const runAsyncMock = jest.spyOn(TestEasCommand.prototype, 'runAsync');
|
||||
const { CombinedError } = require('@urql/core');
|
||||
const { GraphQLError } = require('graphql/error');
|
||||
const graphQLError = new GraphQLError(
|
||||
'Unexpected GraphQL error message',
|
||||
null,
|
||||
@@ -174,9 +248,12 @@ describe(EasCommand.name, () => {
|
||||
|
||||
it('logs the cleaned messages with request IDs if multiple GraphQL errors present', async () => {
|
||||
const TestEasCommand = createTestEasCommand();
|
||||
const Log = jest.requireMock('../../log').default;
|
||||
const logErrorSpy = jest.spyOn(Log, 'error');
|
||||
const logDebugSpy = jest.spyOn(Log, 'debug');
|
||||
const runAsyncMock = jest.spyOn(TestEasCommand.prototype, 'runAsync');
|
||||
const { CombinedError } = require('@urql/core');
|
||||
const { GraphQLError } = require('graphql/error');
|
||||
const graphQLErrors = [
|
||||
new GraphQLError('Unexpected GraphQL error message', null, null, null, null, null, {
|
||||
errorCode: 'UNKNOWN_GRAPHQL_ERROR',
|
||||
@@ -227,6 +304,7 @@ describe(EasCommand.name, () => {
|
||||
it('re-throws the error with a different default base message in case of CombinedError', async () => {
|
||||
const TestEasCommand = createTestEasCommand();
|
||||
const runAsyncMock = jest.spyOn(TestEasCommand.prototype, 'runAsync');
|
||||
const { CombinedError } = require('@urql/core');
|
||||
runAsyncMock.mockImplementation(() => {
|
||||
const graphQLErrors = ['Unexpected GraphQL error message'];
|
||||
throw new CombinedError({ graphQLErrors });
|
||||
@@ -242,6 +320,8 @@ describe(EasCommand.name, () => {
|
||||
it('re-throws the error with a different default base message in case of CombinedError with multiple GraphQLErrors', async () => {
|
||||
const TestEasCommand = createTestEasCommand();
|
||||
const runAsyncMock = jest.spyOn(TestEasCommand.prototype, 'runAsync');
|
||||
const { CombinedError } = require('@urql/core');
|
||||
const { GraphQLError } = require('graphql/error');
|
||||
runAsyncMock.mockImplementation(() => {
|
||||
const graphQLErrors = [
|
||||
new GraphQLError('Unexpected GraphQL error message', null, null, null, null, null, {
|
||||
|
||||
@@ -1,4 +1,46 @@
|
||||
import { resolveNonInteractiveAndJsonFlags } from '../flags';
|
||||
import { isNonInteractiveByDefault, resolveNonInteractiveAndJsonFlags } from '../flags';
|
||||
|
||||
describe(isNonInteractiveByDefault, () => {
|
||||
const originalEnv = process.env;
|
||||
const originalIsTTY = process.stdin.isTTY;
|
||||
|
||||
afterEach(() => {
|
||||
process.env = originalEnv;
|
||||
Object.defineProperty(process.stdin, 'isTTY', { value: originalIsTTY, writable: true });
|
||||
});
|
||||
|
||||
test('returns false when stdin is a TTY and CI is not set', () => {
|
||||
process.env = { ...originalEnv };
|
||||
delete process.env.CI;
|
||||
Object.defineProperty(process.stdin, 'isTTY', { value: true, writable: true });
|
||||
expect(isNonInteractiveByDefault()).toBe(false);
|
||||
});
|
||||
|
||||
test('returns true when CI=1', () => {
|
||||
process.env = { ...originalEnv, CI: '1' };
|
||||
Object.defineProperty(process.stdin, 'isTTY', { value: true, writable: true });
|
||||
expect(isNonInteractiveByDefault()).toBe(true);
|
||||
});
|
||||
|
||||
test('returns true when CI=true', () => {
|
||||
process.env = { ...originalEnv, CI: 'true' };
|
||||
Object.defineProperty(process.stdin, 'isTTY', { value: true, writable: true });
|
||||
expect(isNonInteractiveByDefault()).toBe(true);
|
||||
});
|
||||
|
||||
test('returns true when stdin is not a TTY', () => {
|
||||
process.env = { ...originalEnv };
|
||||
delete process.env.CI;
|
||||
Object.defineProperty(process.stdin, 'isTTY', { value: undefined, writable: true });
|
||||
expect(isNonInteractiveByDefault()).toBe(true);
|
||||
});
|
||||
|
||||
test('returns true when both CI is set and stdin is not a TTY', () => {
|
||||
process.env = { ...originalEnv, CI: '1' };
|
||||
Object.defineProperty(process.stdin, 'isTTY', { value: undefined, writable: true });
|
||||
expect(isNonInteractiveByDefault()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe(resolveNonInteractiveAndJsonFlags, () => {
|
||||
test('returns both false when no flags are set', () => {
|
||||
|
||||
@@ -189,6 +189,8 @@ export async function validateOrSetProjectIdAsync({
|
||||
return user.username;
|
||||
case 'SSOUser':
|
||||
return user.username;
|
||||
case 'PartnerActor':
|
||||
return user.username;
|
||||
case 'Robot':
|
||||
throw new Error(
|
||||
'Must configure EAS project by running "eas init" before using a robot user to manage the project.'
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { Flags } from '@oclif/core';
|
||||
import { boolish } from 'getenv';
|
||||
|
||||
export function isNonInteractiveByDefault(): boolean {
|
||||
return boolish('CI', false) || !process.stdin.isTTY;
|
||||
}
|
||||
|
||||
export const EasNonInteractiveAndJsonFlags = {
|
||||
json: Flags.boolean({
|
||||
@@ -7,6 +12,8 @@ export const EasNonInteractiveAndJsonFlags = {
|
||||
}),
|
||||
'non-interactive': Flags.boolean({
|
||||
description: 'Run the command in non-interactive mode.',
|
||||
default: () => Promise.resolve(isNonInteractiveByDefault()),
|
||||
noCacheDefault: true,
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -64,6 +71,11 @@ export const EASEnvironmentVariableScopeFlag = {
|
||||
export const EASNonInteractiveFlag = {
|
||||
'non-interactive': Flags.boolean({
|
||||
description: 'Run the command in non-interactive mode.',
|
||||
// eslint-disable-next-line async-protect/async-suffix
|
||||
default: async () => {
|
||||
return isNonInteractiveByDefault();
|
||||
},
|
||||
noCacheDefault: true,
|
||||
}),
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { getMockWorkflowRunWithJobsFragment } from '../../../__tests__/commands/utils';
|
||||
import { fetchRawLogsForCustomJobAsync } from '../fetchLogs';
|
||||
import { infoForActiveWorkflowRunAsync } from '../utils';
|
||||
import { WorkflowJobStatus } from '../../../graphql/generated';
|
||||
|
||||
jest.mock('../fetchLogs');
|
||||
|
||||
describe('workflow utils', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test('shows the display name for the current step while keying logs by step id', async () => {
|
||||
const workflowRun = getMockWorkflowRunWithJobsFragment();
|
||||
workflowRun.jobs = workflowRun.jobs.map(job => ({
|
||||
...job,
|
||||
status: WorkflowJobStatus.InProgress,
|
||||
}));
|
||||
|
||||
jest
|
||||
.mocked(fetchRawLogsForCustomJobAsync)
|
||||
.mockResolvedValue(
|
||||
[
|
||||
'{"buildStepId":"step-id-1","buildStepDisplayName":"Install dependencies","time":"2022-01-01T00:00:00.000Z","msg":"npm ci"}',
|
||||
'{"buildStepId":"step-id-1","buildStepDisplayName":"Install dependencies","marker":"end-step","result":"success","time":"2022-01-01T00:00:01.000Z","msg":"done"}',
|
||||
].join('\n')
|
||||
);
|
||||
|
||||
const output = await infoForActiveWorkflowRunAsync({} as any, workflowRun);
|
||||
|
||||
expect(output).toContain('Current step');
|
||||
expect(output).toContain('Install dependencies');
|
||||
expect(output).not.toContain('step-id-1');
|
||||
});
|
||||
});
|
||||
@@ -22,6 +22,17 @@ export async function fetchRawLogsForBuildJobAsync(
|
||||
state: { graphqlClient: ExpoGraphqlClient },
|
||||
job: WorkflowJobResult
|
||||
): Promise<string | null> {
|
||||
// Prefer turtleJobRun logs, which contain JSONL-formatted step logs
|
||||
// for both built-in and custom build steps
|
||||
const turtleLogFileUrl = job.turtleJobRun?.logFileUrls?.[0];
|
||||
if (turtleLogFileUrl) {
|
||||
const response = await fetch(turtleLogFileUrl, {
|
||||
method: 'GET',
|
||||
});
|
||||
return await response.text();
|
||||
}
|
||||
|
||||
// Fall back to build logFiles
|
||||
const buildId = job.outputs?.build_id;
|
||||
if (!buildId) {
|
||||
return null;
|
||||
@@ -36,6 +47,5 @@ export async function fetchRawLogsForBuildJobAsync(
|
||||
const response = await fetch(firstLogFileUrl, {
|
||||
method: 'GET',
|
||||
});
|
||||
const rawLogs = await response.text();
|
||||
return rawLogs;
|
||||
return await response.text();
|
||||
}
|
||||
@@ -50,4 +50,10 @@ export type WorkflowLogLine = {
|
||||
err?: any;
|
||||
};
|
||||
|
||||
export type WorkflowLogs = Map<string, WorkflowLogLine[]>;
|
||||
export type WorkflowLogGroup = {
|
||||
key: string;
|
||||
label: string;
|
||||
logLines: WorkflowLogLine[];
|
||||
};
|
||||
|
||||
export type WorkflowLogs = Map<string, WorkflowLogGroup>;
|
||||
@@ -4,6 +4,7 @@ import * as fs from 'node:fs';
|
||||
import { fetchRawLogsForBuildJobAsync, fetchRawLogsForCustomJobAsync } from './fetchLogs';
|
||||
import {
|
||||
WorkflowJobResult,
|
||||
WorkflowLogGroup,
|
||||
WorkflowLogLine,
|
||||
WorkflowLogs,
|
||||
WorkflowRunResult,
|
||||
@@ -84,16 +85,17 @@ export function choiceFromWorkflowJob(job: WorkflowJobResult, index: number): Ch
|
||||
export function choicesFromWorkflowLogs(
|
||||
logs: WorkflowLogs
|
||||
): (Choice & { name: string; status: string; logLines: WorkflowLogLine[] | undefined })[] {
|
||||
return Array.from(logs.keys())
|
||||
.map(step => {
|
||||
const logLines = logs.get(step);
|
||||
return Array.from(logs.values())
|
||||
.map(({ key, label, logLines }) => {
|
||||
const stepStatus =
|
||||
logLines?.filter((line: WorkflowLogLine) => line.marker === 'end-step')[0]?.result ?? '';
|
||||
logLines?.filter(
|
||||
(line: WorkflowLogLine) => line.marker === 'end-step' || line.marker === 'END_PHASE'
|
||||
)[0]?.result ?? '';
|
||||
return {
|
||||
title: `${step} - ${stepStatus}`,
|
||||
name: step,
|
||||
title: `${label} - ${stepStatus}`,
|
||||
name: label,
|
||||
status: stepStatus,
|
||||
value: step,
|
||||
value: key,
|
||||
logLines,
|
||||
};
|
||||
})
|
||||
@@ -139,20 +141,36 @@ export async function fetchAndProcessLogsFromJobAsync(
|
||||
}
|
||||
Log.debug(`rawLogs = ${JSON.stringify(rawLogs, null, 2)}`);
|
||||
const logs: WorkflowLogs = new Map();
|
||||
const logKeys = new Set<string>();
|
||||
rawLogs.split('\n').forEach((line, index) => {
|
||||
Log.debug(`line ${index} = ${JSON.stringify(line, null, 2)}`);
|
||||
try {
|
||||
const parsedLine = JSON.parse(line);
|
||||
const { buildStepDisplayName, buildStepInternalId, time, msg, result, marker, err } =
|
||||
parsedLine;
|
||||
const stepId = buildStepDisplayName ?? buildStepInternalId;
|
||||
if (stepId) {
|
||||
if (!logKeys.has(stepId)) {
|
||||
logKeys.add(stepId);
|
||||
logs.set(stepId, []);
|
||||
const {
|
||||
buildStepDisplayName,
|
||||
buildStepId,
|
||||
buildStepInternalId,
|
||||
phase,
|
||||
time,
|
||||
msg,
|
||||
result,
|
||||
marker,
|
||||
err,
|
||||
} = parsedLine;
|
||||
const stepKey = buildStepId ?? buildStepInternalId ?? buildStepDisplayName ?? phase;
|
||||
const stepLabel = buildStepDisplayName ?? buildStepId ?? phase;
|
||||
if (stepKey && stepLabel) {
|
||||
if (!logs.has(stepKey)) {
|
||||
logs.set(stepKey, {
|
||||
key: stepKey,
|
||||
label: stepLabel,
|
||||
logLines: [],
|
||||
});
|
||||
}
|
||||
logs.get(stepId)?.push({ time, msg, result, marker, err });
|
||||
const logGroup = logs.get(stepKey) as WorkflowLogGroup;
|
||||
if (buildStepDisplayName) {
|
||||
logGroup.label = buildStepDisplayName;
|
||||
}
|
||||
logGroup.logLines.push({ time, msg, result, marker, err });
|
||||
}
|
||||
} catch {}
|
||||
});
|
||||
@@ -238,7 +256,7 @@ export async function infoForFailedWorkflowRunAsync(
|
||||
statusValues.push({ label: '', value: '' });
|
||||
statusValues.push({ label: ' Failed job', value: job.name });
|
||||
if (steps.length > 0) {
|
||||
const failedStep = steps.find(step => step.status === 'fail');
|
||||
const failedStep = steps.find(step => step.status === 'fail' || step.status === 'failed');
|
||||
if (failedStep) {
|
||||
const logs = failedStep.logLines?.map(line => line.msg).slice(-logLinesToKeep) ?? [];
|
||||
statusValues.push({ label: ' Failed step', value: failedStep.name });
|
||||
|
||||
@@ -73,6 +73,12 @@ export default class Onboarding extends EasCommand {
|
||||
);
|
||||
}
|
||||
|
||||
if (actor.__typename === 'PartnerActor') {
|
||||
throw new Error(
|
||||
'This command is not available for partner actors. Make sure you are using a user token and try again.'
|
||||
);
|
||||
}
|
||||
|
||||
if (!actor.preferences.onboarding) {
|
||||
throw new Error(
|
||||
'This command can only be run as part of the onboarding process started on the Expo website. Visit https://expo.new to start a new project.'
|
||||
|
||||
@@ -12,10 +12,9 @@ import Log from '../../log';
|
||||
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
|
||||
|
||||
function printLogsForAllSteps(logs: WorkflowLogs): void {
|
||||
[...logs.keys()].forEach(step => {
|
||||
const logLines = logs.get(step);
|
||||
if (logLines) {
|
||||
Log.log(`Step: ${step}`);
|
||||
[...logs.values()].forEach(({ label, logLines }) => {
|
||||
if (logLines.length > 0) {
|
||||
Log.log(`Step: ${label}`);
|
||||
logLines.forEach(line => {
|
||||
Log.log(` ${line.time} ${line.msg}`);
|
||||
});
|
||||
@@ -89,21 +88,25 @@ export default class WorkflowLogView extends EasCommand {
|
||||
if (allSteps) {
|
||||
if (logs) {
|
||||
if (flags.json) {
|
||||
printJsonOnlyOutput(Object.fromEntries(logs));
|
||||
printJsonOnlyOutput(
|
||||
Object.fromEntries(
|
||||
Array.from(logs.entries()).map(([key, value]) => [key, value.logLines])
|
||||
)
|
||||
);
|
||||
} else {
|
||||
printLogsForAllSteps(logs);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const selectedStep = finalSelectionState?.step as unknown as string;
|
||||
const logLines = logs?.get(selectedStep);
|
||||
if (logLines) {
|
||||
const logGroup = logs?.get(selectedStep);
|
||||
if (logGroup) {
|
||||
if (flags.json) {
|
||||
const output: { [key: string]: WorkflowLogLine[] | null } = {};
|
||||
output[selectedStep] = logLines ?? null;
|
||||
output[selectedStep] = logGroup.logLines;
|
||||
printJsonOnlyOutput(output);
|
||||
} else {
|
||||
logLines.forEach(line => {
|
||||
logGroup.logLines.forEach(line => {
|
||||
Log.log(` ${line.time} ${line.msg}`);
|
||||
});
|
||||
}
|
||||
|
||||
+1605
-8236
File diff suppressed because it is too large.
Load diff
@@ -43,6 +43,9 @@ export const UserQuery = {
|
||||
id
|
||||
...AccountFragment
|
||||
}
|
||||
... on PartnerActor {
|
||||
username
|
||||
}
|
||||
featureGates
|
||||
isExpoAdmin
|
||||
}
|
||||
|
||||
@@ -23,6 +23,9 @@ export const UpdateFragmentNode = gql`
|
||||
... on Robot {
|
||||
firstName
|
||||
}
|
||||
... on PartnerActor {
|
||||
username
|
||||
}
|
||||
}
|
||||
branch {
|
||||
id
|
||||
|
||||
@@ -10,21 +10,8 @@ import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGr
|
||||
import { AccountFragment } from '../graphql/generated';
|
||||
import { AppQuery } from '../graphql/queries/AppQuery';
|
||||
import Log, { learnMore } from '../log';
|
||||
import { Actor } from '../user/User';
|
||||
import { expoCommandAsync } from '../utils/expoCli';
|
||||
|
||||
export function getUsernameForBuildMetadataAndBuildJob(user: Actor): string | undefined {
|
||||
switch (user.__typename) {
|
||||
case 'User':
|
||||
return user.username;
|
||||
case 'SSOUser':
|
||||
return user.username;
|
||||
case 'Robot':
|
||||
// robot users don't have usernames
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a useful name describing the project config.
|
||||
* - dynamic: app.config.js
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import * as Sentry from '@sentry/node';
|
||||
|
||||
import { easCliVersion } from './utils/easCli';
|
||||
|
||||
Sentry.init({
|
||||
dsn: process.env.EAS_CLI_SENTRY_DSN,
|
||||
enabled: !!process.env.EAS_CLI_SENTRY_DSN,
|
||||
environment: getSentryEnvironment(),
|
||||
release: easCliVersion ? `eas-cli@${easCliVersion}` : undefined,
|
||||
});
|
||||
Sentry.setTag('source', 'eas-cli');
|
||||
|
||||
function getSentryEnvironment(): string {
|
||||
if (process.env.EXPO_LOCAL) {
|
||||
return 'local';
|
||||
} else if (process.env.EXPO_STAGING) {
|
||||
return 'staging';
|
||||
}
|
||||
return 'production';
|
||||
}
|
||||
|
||||
export default Sentry;
|
||||
@@ -7,6 +7,7 @@ import semver from 'semver';
|
||||
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import {
|
||||
AppPlatform,
|
||||
PartnerActor,
|
||||
Robot,
|
||||
SsoUser,
|
||||
Update,
|
||||
@@ -29,6 +30,7 @@ export type FormatUpdateParameter = Pick<Update, 'id' | 'createdAt' | 'message'>
|
||||
| Pick<Robot, '__typename' | 'firstName'>
|
||||
| Pick<User, '__typename' | 'username'>
|
||||
| Pick<SsoUser, '__typename' | 'username'>
|
||||
| Pick<PartnerActor, '__typename' | 'username'>
|
||||
| null;
|
||||
};
|
||||
|
||||
@@ -194,6 +196,7 @@ export function formatUpdateTitle(update: UpdateFragment): string {
|
||||
let actorName: string;
|
||||
switch (actor?.__typename) {
|
||||
case 'User':
|
||||
case 'PartnerActor':
|
||||
case 'SSOUser': {
|
||||
actorName = actor.username;
|
||||
break;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CurrentUserQuery, Robot, SsoUser, User } from '../graphql/generated';
|
||||
import { CurrentUserQuery, PartnerActor, Robot, SsoUser, User } from '../graphql/generated';
|
||||
|
||||
export type Actor = NonNullable<CurrentUserQuery['meActor']>;
|
||||
|
||||
@@ -12,6 +12,7 @@ export function getActorDisplayName(
|
||||
| Pick<Robot, '__typename' | 'firstName'>
|
||||
| Pick<User, '__typename' | 'username'>
|
||||
| Pick<SsoUser, '__typename' | 'username'>
|
||||
| Pick<PartnerActor, '__typename' | 'username'>
|
||||
| null
|
||||
): string {
|
||||
switch (actor?.__typename) {
|
||||
@@ -21,6 +22,8 @@ export function getActorDisplayName(
|
||||
return actor.firstName ? `${actor.firstName} (robot)` : 'robot';
|
||||
case 'SSOUser':
|
||||
return actor.username;
|
||||
case 'PartnerActor':
|
||||
return actor.username;
|
||||
case undefined:
|
||||
return 'unknown';
|
||||
}
|
||||
@@ -30,6 +33,7 @@ export function getActorUsername(actor?: Actor): string | null {
|
||||
switch (actor?.__typename) {
|
||||
case 'User':
|
||||
case 'SSOUser':
|
||||
case 'PartnerActor':
|
||||
return actor.username;
|
||||
case 'Robot':
|
||||
case undefined:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@expo/eas-json",
|
||||
"version": "18.2.0",
|
||||
"version": "18.4.0",
|
||||
"description": "A library for interacting with eas.json",
|
||||
"homepage": "https://github.com/expo/eas-cli",
|
||||
"bugs": "https://github.com/expo/eas-cli/issues",
|
||||
@@ -26,13 +26,13 @@
|
||||
"watch-allow-unused": "yarn build-allow-unused --watch --preserveWatchOutput",
|
||||
"typecheck": "tsc",
|
||||
"prepack": "yarn rebuild",
|
||||
"rebuild": "rimraf build *.tsbuildinfo && yarn build",
|
||||
"rebuild": "rimraf build \"*.tsbuildinfo\" && yarn build",
|
||||
"test": "jest",
|
||||
"clean": "rimraf build node_modules yarn-error.log *.tsbuildinfo"
|
||||
"clean": "rimraf build node_modules yarn-error.log \"*.tsbuildinfo\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "7.23.5",
|
||||
"@expo/eas-build-job": "18.2.0",
|
||||
"@expo/eas-build-job": "18.4.0",
|
||||
"chalk": "4.1.2",
|
||||
"env-string": "1.0.1",
|
||||
"fs-extra": "11.2.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "eas-cli-local-build-plugin",
|
||||
"version": "18.2.0",
|
||||
"version": "18.4.0",
|
||||
"description": "Tool for running EAS compatible builds on a local machine.",
|
||||
"bugs": "https://github.com/expo/eas-cli/issues",
|
||||
"license": "BUSL-1.1",
|
||||
@@ -21,13 +21,13 @@
|
||||
"watch": "tsc --watch --preserveWatchOutput",
|
||||
"build": "tsc",
|
||||
"typecheck": "tsc",
|
||||
"prepack": "rm -rf dist *.tsbuildinfo && tsc -p tsconfig.build.json",
|
||||
"prepack": "rimraf dist \"*.tsbuildinfo\" && tsc -p tsconfig.build.json",
|
||||
"test": "jest --passWithNoTests",
|
||||
"clean": "rm -rf node_modules dist coverage *.tsbuildinfo"
|
||||
"clean": "rimraf node_modules dist coverage \"*.tsbuildinfo\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@expo/build-tools": "18.2.0",
|
||||
"@expo/eas-build-job": "18.2.0",
|
||||
"@expo/build-tools": "18.4.0",
|
||||
"@expo/eas-build-job": "18.4.0",
|
||||
"@expo/logger": "18.0.1",
|
||||
"@expo/spawn-async": "^1.7.2",
|
||||
"@expo/turtle-spawn": "18.0.1",
|
||||
@@ -50,6 +50,7 @@
|
||||
"@types/semver": "^7.5.8",
|
||||
"@types/uuid": "^9.0.7",
|
||||
"jest": "^29.7.0",
|
||||
"rimraf": "3.0.2",
|
||||
"ts-jest": "^29.1.4",
|
||||
"typescript": "^5.5.4"
|
||||
},
|
||||
|
||||
@@ -86,7 +86,6 @@ class PrettyStream extends Writable {
|
||||
'pid',
|
||||
'hostname',
|
||||
'name',
|
||||
'buildStepInternalId',
|
||||
'buildStepId',
|
||||
'buildStepDisplayName',
|
||||
]);
|
||||
|
||||
@@ -19,10 +19,10 @@
|
||||
"watch": "tsc --watch --preserveWatchOutput",
|
||||
"build": "tsc",
|
||||
"typecheck": "tsc",
|
||||
"prepack": "rm -rf dist *.tsbuildinfo && tsc -p tsconfig.build.json",
|
||||
"prepack": "rimraf dist \"*.tsbuildinfo\" && tsc -p tsconfig.build.json",
|
||||
"test": "jest --config jest.config.ts --passWithNoTests",
|
||||
"test:watch": "jest --config jest.config.ts --watch",
|
||||
"clean": "rm -rf node_modules dist coverage *.tsbuildinfo"
|
||||
"clean": "rimraf node_modules dist coverage \"*.tsbuildinfo\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/bunyan": "^1.8.11",
|
||||
@@ -32,6 +32,7 @@
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/node": "20.14.2",
|
||||
"jest": "^29.7.0",
|
||||
"rimraf": "3.0.2",
|
||||
"ts-jest": "^29.1.4",
|
||||
"typescript": "^5.5.4"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@expo/steps",
|
||||
"version": "18.2.0",
|
||||
"version": "18.4.0",
|
||||
"bugs": "https://github.com/expo/eas-cli/issues",
|
||||
"license": "BUSL-1.1",
|
||||
"author": "Expo <[email protected]>",
|
||||
@@ -19,15 +19,15 @@
|
||||
"start": "yarn watch",
|
||||
"build": "tsc --project tsconfig.build.json",
|
||||
"typecheck": "tsc",
|
||||
"prepack": "rm -rf dist *.tsbuildinfo && tsc --project tsconfig.build.json",
|
||||
"prepack": "rimraf dist \"*.tsbuildinfo\" && tsc --project tsconfig.build.json",
|
||||
"watch": "tsc --watch --preserveWatchOutput",
|
||||
"test": "jest --config jest.config.ts",
|
||||
"test:coverage": "jest --config jest.config.ts --coverage",
|
||||
"test:watch": "yarn test --watch",
|
||||
"clean": "rm -rf node_modules dist coverage *.tsbuildinfo"
|
||||
"clean": "rimraf node_modules dist coverage \"*.tsbuildinfo\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@expo/eas-build-job": "18.2.0",
|
||||
"@expo/eas-build-job": "18.4.0",
|
||||
"@expo/logger": "18.0.1",
|
||||
"@expo/spawn-async": "^1.7.2",
|
||||
"arg": "^5.0.2",
|
||||
@@ -47,6 +47,7 @@
|
||||
"@types/node": "20.14.2",
|
||||
"eslint-plugin-async-protect": "^3.1.0",
|
||||
"jest": "^29.7.0",
|
||||
"rimraf": "3.0.2",
|
||||
"ts-jest": "^29.1.4",
|
||||
"ts-mockito": "^2.6.1",
|
||||
"ts-node": "^10.9.2",
|
||||
|
||||
@@ -127,7 +127,14 @@ export class BuildConfigParser extends AbstractConfigParser {
|
||||
__metrics_id,
|
||||
} = run;
|
||||
const id = BuildStep.getNewId(maybeId);
|
||||
const displayName = BuildStep.getDisplayName({ id, name, command });
|
||||
const displayName =
|
||||
name ??
|
||||
maybeId ??
|
||||
command
|
||||
.split('\n')
|
||||
.find(line => line.trim())
|
||||
?.trim() ??
|
||||
command;
|
||||
const inputs =
|
||||
inputsConfig && this.createBuildStepInputsFromDefinition(inputsConfig, displayName);
|
||||
const outputs =
|
||||
@@ -135,10 +142,9 @@ export class BuildConfigParser extends AbstractConfigParser {
|
||||
const timeoutMs = timeout_minutes !== undefined ? timeout_minutes * 60 * 1000 : undefined;
|
||||
return new BuildStep(this.ctx, {
|
||||
id,
|
||||
displayName,
|
||||
inputs,
|
||||
outputs,
|
||||
name,
|
||||
displayName,
|
||||
workingDirectory,
|
||||
shell,
|
||||
command,
|
||||
@@ -153,7 +159,11 @@ export class BuildConfigParser extends AbstractConfigParser {
|
||||
run: command,
|
||||
}: BuildStepBareCommandRun): BuildStep {
|
||||
const id = BuildStep.getNewId();
|
||||
const displayName = BuildStep.getDisplayName({ id, command });
|
||||
const displayName =
|
||||
command
|
||||
.split('\n')
|
||||
.find(line => line.trim())
|
||||
?.trim() ?? command;
|
||||
return new BuildStep(this.ctx, {
|
||||
id,
|
||||
displayName,
|
||||
|
||||
@@ -105,11 +105,7 @@ export class BuildFunction {
|
||||
): BuildStep {
|
||||
const buildStepId = BuildStep.getNewId(id);
|
||||
const buildStepName = name ?? this.name;
|
||||
const buildStepDisplayName = BuildStep.getDisplayName({
|
||||
id: buildStepId,
|
||||
command: this.command,
|
||||
name: buildStepName,
|
||||
});
|
||||
const buildStepDisplayName = buildStepName ?? id ?? this.getFullId();
|
||||
|
||||
const inputs = this.inputProviders?.map(inputProvider => {
|
||||
const input = inputProvider(ctx, buildStepId);
|
||||
@@ -122,7 +118,6 @@ export class BuildFunction {
|
||||
|
||||
return new BuildStep(ctx, {
|
||||
id: buildStepId,
|
||||
name: buildStepName,
|
||||
displayName: buildStepDisplayName,
|
||||
command: this.command,
|
||||
fn:
|
||||
|
||||
@@ -57,10 +57,6 @@ export type BuildStepFunction = (
|
||||
}
|
||||
) => unknown;
|
||||
|
||||
// TODO: move to a place common with tests
|
||||
const UUID_REGEX =
|
||||
/^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/;
|
||||
|
||||
export interface SerializedBuildStepOutputAccessor {
|
||||
id: string;
|
||||
executed: boolean;
|
||||
@@ -127,7 +123,6 @@ export class BuildStepOutputAccessor {
|
||||
|
||||
export class BuildStep extends BuildStepOutputAccessor {
|
||||
public readonly id: string;
|
||||
public readonly name?: string;
|
||||
public readonly displayName: string;
|
||||
public readonly supportedRuntimePlatforms?: BuildRuntimePlatform[];
|
||||
public readonly inputs?: BuildStepInput[];
|
||||
@@ -144,7 +139,6 @@ export class BuildStep extends BuildStepOutputAccessor {
|
||||
private readonly outputsDir: string;
|
||||
private readonly envsDir: string;
|
||||
|
||||
private readonly internalId: string;
|
||||
private readonly inputById: BuildStepInputById;
|
||||
protected executed = false;
|
||||
|
||||
@@ -152,38 +146,10 @@ export class BuildStep extends BuildStepOutputAccessor {
|
||||
return userDefinedId ?? uuidv4();
|
||||
}
|
||||
|
||||
public static getDisplayName({
|
||||
id,
|
||||
name,
|
||||
command,
|
||||
}: {
|
||||
id: string;
|
||||
name?: string;
|
||||
command?: string;
|
||||
}): string {
|
||||
if (name) {
|
||||
return name;
|
||||
}
|
||||
if (!id.match(UUID_REGEX)) {
|
||||
return id;
|
||||
}
|
||||
if (command) {
|
||||
const splits = command.trim().split('\n');
|
||||
for (const split of splits) {
|
||||
const trimmed = split.trim();
|
||||
if (trimmed && !trimmed.startsWith('#')) {
|
||||
return trimmed;
|
||||
}
|
||||
}
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
constructor(
|
||||
ctx: BuildStepGlobalContext,
|
||||
{
|
||||
id,
|
||||
name,
|
||||
displayName,
|
||||
inputs,
|
||||
outputs,
|
||||
@@ -198,7 +164,6 @@ export class BuildStep extends BuildStepOutputAccessor {
|
||||
__metricsId,
|
||||
}: {
|
||||
id: string;
|
||||
name?: string;
|
||||
displayName: string;
|
||||
inputs?: BuildStepInput[];
|
||||
outputs?: BuildStepOutput[];
|
||||
@@ -219,7 +184,6 @@ export class BuildStep extends BuildStepOutputAccessor {
|
||||
super(id, displayName, false, outputById);
|
||||
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.displayName = displayName;
|
||||
this.supportedRuntimePlatforms = maybeSupportedRuntimePlatforms;
|
||||
this.inputs = inputs;
|
||||
@@ -233,10 +197,7 @@ export class BuildStep extends BuildStepOutputAccessor {
|
||||
this.__metricsId = __metricsId;
|
||||
this.status = BuildStepStatus.NEW;
|
||||
|
||||
this.internalId = uuidv4();
|
||||
|
||||
const logger = ctx.baseLogger.child({
|
||||
buildStepInternalId: this.internalId,
|
||||
buildStepId: this.id,
|
||||
buildStepDisplayName: this.displayName,
|
||||
});
|
||||
|
||||
@@ -114,14 +114,20 @@ export class StepsConfigParser extends AbstractConfigParser {
|
||||
|
||||
private createBuildStepFromShellStepConfig(step: ShellStep): BuildStep {
|
||||
const id = BuildStep.getNewId(step.id);
|
||||
const displayName = BuildStep.getDisplayName({ id, name: step.name, command: step.run });
|
||||
const displayName =
|
||||
step.name ??
|
||||
step.id ??
|
||||
step.run
|
||||
.split('\n')
|
||||
.find(line => line.trim())
|
||||
?.trim() ??
|
||||
step.run;
|
||||
const outputs =
|
||||
step.outputs && this.createBuildStepOutputsFromDefinition(step.outputs, displayName);
|
||||
return new BuildStep(this.ctx, {
|
||||
id,
|
||||
outputs,
|
||||
name: step.name,
|
||||
displayName,
|
||||
outputs,
|
||||
workingDirectory: step.working_directory,
|
||||
shell: step.shell,
|
||||
command: step.run,
|
||||
|
||||
@@ -67,13 +67,7 @@ describe('Publish Update job', () => {
|
||||
function assertStepsAreMatching(step1: BuildStep, step2: BuildStep): void {
|
||||
expect(step1.id).toMatch(UUID_REGEX);
|
||||
expect(step2.id).toMatch(UUID_REGEX);
|
||||
expect(step1.name).toEqual(step2.name);
|
||||
if (step1.command) {
|
||||
expect(step1.displayName).toEqual(step2.displayName);
|
||||
} else {
|
||||
expect(step1.displayName).toMatch(UUID_REGEX);
|
||||
expect(step2.displayName).toMatch(UUID_REGEX);
|
||||
}
|
||||
expect(step1.displayName).toEqual(step2.displayName);
|
||||
expect(step1.inputs).toStrictEqual(step2.inputs);
|
||||
expect(step1.outputById).toStrictEqual(step2.outputById);
|
||||
expect(step1.command?.trim()).toEqual(step2.command?.trim());
|
||||
|
||||
@@ -113,7 +113,7 @@ describe(BuildConfigParser, () => {
|
||||
// - run: echo "Hi!"
|
||||
const step1 = buildSteps[0];
|
||||
expect(step1.id).toMatch(UUID_REGEX);
|
||||
expect(step1.name).toBeUndefined();
|
||||
expect(step1.displayName).toBe('echo "Hi!"');
|
||||
expect(step1.command).toBe('echo "Hi!"');
|
||||
expect(step1.ctx.workingDirectory).toBe(ctx.defaultWorkingDirectory);
|
||||
expect(step1.shell).toBe('/bin/bash -eo pipefail');
|
||||
@@ -129,7 +129,7 @@ describe(BuildConfigParser, () => {
|
||||
// echo "O"
|
||||
const step2 = buildSteps[1];
|
||||
expect(step2.id).toMatch(UUID_REGEX);
|
||||
expect(step2.name).toBe('Say HELLO');
|
||||
expect(step2.displayName).toBe('Say HELLO');
|
||||
expect(step2.command).toMatchSnapshot();
|
||||
expect(step2.ctx.workingDirectory).toBe(ctx.defaultWorkingDirectory);
|
||||
expect(step2.shell).toBe('/bin/bash -eo pipefail');
|
||||
@@ -143,7 +143,7 @@ describe(BuildConfigParser, () => {
|
||||
// BAR: baz
|
||||
const step3 = buildSteps[2];
|
||||
expect(step3.id).toBe('id_2137');
|
||||
expect(step3.name).toBeUndefined();
|
||||
expect(step3.displayName).toBe('id_2137');
|
||||
expect(step3.command).toBe('echo "Step with an ID"');
|
||||
expect(step3.ctx.workingDirectory).toBe(ctx.defaultWorkingDirectory);
|
||||
expect(step3.shell).toBe('/bin/bash -eo pipefail');
|
||||
@@ -158,7 +158,7 @@ describe(BuildConfigParser, () => {
|
||||
// command: ls -la
|
||||
const step4 = buildSteps[3];
|
||||
expect(step4.id).toMatch(UUID_REGEX);
|
||||
expect(step4.name).toBe('List files');
|
||||
expect(step4.displayName).toBe('List files');
|
||||
expect(step4.command).toBe('ls -la');
|
||||
expect(step4.ctx.workingDirectory).toBe(
|
||||
path.join(ctx.defaultWorkingDirectory, 'relative/path/to/files')
|
||||
@@ -172,7 +172,7 @@ describe(BuildConfigParser, () => {
|
||||
// command: ls -la
|
||||
const step5 = buildSteps[4];
|
||||
expect(step5.id).toMatch(UUID_REGEX);
|
||||
expect(step5.name).toBe('List files in another directory');
|
||||
expect(step5.displayName).toBe('List files in another directory');
|
||||
expect(step5.command).toBe('ls -la');
|
||||
expect(step5.ctx.workingDirectory).toBe(
|
||||
path.join(ctx.projectTargetDirectory, '/home/dsokal')
|
||||
@@ -187,7 +187,7 @@ describe(BuildConfigParser, () => {
|
||||
// command: echo 123
|
||||
const step6 = buildSteps[5];
|
||||
expect(step6.id).toMatch(UUID_REGEX);
|
||||
expect(step6.name).toBe('Use non-default shell');
|
||||
expect(step6.displayName).toBe('Use non-default shell');
|
||||
expect(step6.command).toBe('echo 123');
|
||||
expect(step6.ctx.workingDirectory).toBe(ctx.defaultWorkingDirectory);
|
||||
expect(step6.shell).toBe('/nib/hsab');
|
||||
@@ -220,7 +220,7 @@ describe(BuildConfigParser, () => {
|
||||
// command: echo "Hi, ${ inputs.name }, ${ inputs.boolean_value }!"
|
||||
const step1 = buildSteps[0];
|
||||
expect(step1.id).toMatch(UUID_REGEX);
|
||||
expect(step1.name).toBe('Say HI');
|
||||
expect(step1.displayName).toBe('Say HI');
|
||||
expect(step1.command).toBe('echo "Hi, ${ inputs.name }, ${ inputs.boolean_value }!"');
|
||||
expect(step1.ctx.workingDirectory).toBe(ctx.defaultWorkingDirectory);
|
||||
expect(step1.shell).toBe('/bin/bash -eo pipefail');
|
||||
@@ -271,7 +271,7 @@ describe(BuildConfigParser, () => {
|
||||
// set-output last_name Vatne
|
||||
const step1 = buildSteps[0];
|
||||
expect(step1.id).toMatch(UUID_REGEX);
|
||||
expect(step1.name).toBeUndefined();
|
||||
expect(step1.displayName).toBe('set-output first_name Brent');
|
||||
expect(step1.command).toMatchSnapshot();
|
||||
expect(step1.ctx.workingDirectory).toBe(ctx.defaultWorkingDirectory);
|
||||
expect(step1.shell).toBe('/bin/bash -eo pipefail');
|
||||
@@ -295,7 +295,7 @@ describe(BuildConfigParser, () => {
|
||||
// set-output nickname dsokal
|
||||
const step2 = buildSteps[1];
|
||||
expect(step2.id).toMatch(UUID_REGEX);
|
||||
expect(step2.name).toBeUndefined();
|
||||
expect(step2.displayName).toBe('set-output first_name Dominik');
|
||||
expect(step2.command).toMatchSnapshot();
|
||||
expect(step2.ctx.workingDirectory).toBe(ctx.defaultWorkingDirectory);
|
||||
expect(step2.shell).toBe('/bin/bash -eo pipefail');
|
||||
@@ -334,7 +334,7 @@ describe(BuildConfigParser, () => {
|
||||
// - bbb
|
||||
const step1 = buildSteps[0];
|
||||
expect(step1.id).toMatch(UUID_REGEX);
|
||||
expect(step1.name).toBe('Hi!');
|
||||
expect(step1.displayName).toBe('Hi!');
|
||||
expect(step1.command).toBe('echo "Hi, ${ inputs.name }!"');
|
||||
expect(step1.ctx.workingDirectory).toBe(ctx.defaultWorkingDirectory);
|
||||
expect(step1.shell).toBe('/bin/bash -eo pipefail');
|
||||
@@ -366,7 +366,7 @@ describe(BuildConfigParser, () => {
|
||||
// build_number: 122
|
||||
const step2 = buildSteps[1];
|
||||
expect(step2.id).toMatch(UUID_REGEX);
|
||||
expect(step2.name).toBe('Hi, Szymon!');
|
||||
expect(step2.displayName).toBe('Hi, Szymon!');
|
||||
expect(step2.command).toBe('echo "Hi, ${ inputs.name }!"');
|
||||
expect(step2.ctx.workingDirectory).toBe(ctx.defaultWorkingDirectory);
|
||||
expect(step2.shell).toBe('/bin/bash -eo pipefail');
|
||||
@@ -393,7 +393,7 @@ describe(BuildConfigParser, () => {
|
||||
// - say_hi_wojtek
|
||||
const step3 = buildSteps[2];
|
||||
expect(step3.id).toMatch(UUID_REGEX);
|
||||
expect(step3.name).toBe('Hi, Wojtek!');
|
||||
expect(step3.displayName).toBe('Hi, Wojtek!');
|
||||
expect(step3.command).toBe('echo "Hi, Wojtek!"');
|
||||
expect(step3.ctx.workingDirectory).toBe(ctx.defaultWorkingDirectory);
|
||||
expect(step3.shell).toBe('/bin/bash -eo pipefail');
|
||||
@@ -403,7 +403,7 @@ describe(BuildConfigParser, () => {
|
||||
// id: random_number
|
||||
const step4 = buildSteps[3];
|
||||
expect(step4.id).toMatch('random_number');
|
||||
expect(step4.name).toBe('Generate random number');
|
||||
expect(step4.displayName).toBe('Generate random number');
|
||||
expect(step4.command).toBe('set-output value 6');
|
||||
expect(step4.ctx.workingDirectory).toBe(ctx.defaultWorkingDirectory);
|
||||
expect(step4.shell).toBe('/bin/bash -eo pipefail');
|
||||
@@ -417,7 +417,7 @@ describe(BuildConfigParser, () => {
|
||||
// value: ${ steps.random_number.value }
|
||||
const step5 = buildSteps[4];
|
||||
expect(step5.id).toMatch(UUID_REGEX);
|
||||
expect(step5.name).toBe(undefined);
|
||||
expect(step5.displayName).toBe('print');
|
||||
expect(step5.command).toBe('echo "${ inputs.value }"');
|
||||
expect(step5.ctx.workingDirectory).toBe(ctx.defaultWorkingDirectory);
|
||||
expect(step5.shell).toBe('/bin/bash -eo pipefail');
|
||||
@@ -432,7 +432,7 @@ describe(BuildConfigParser, () => {
|
||||
// num: 123
|
||||
const step6 = buildSteps[5];
|
||||
expect(step6.id).toMatch(UUID_REGEX);
|
||||
expect(step6.name).toBe('Hi!');
|
||||
expect(step6.displayName).toBe('Hi!');
|
||||
expect(step6.command).toBe('echo "${ inputs.greeting }, ${ inputs.name }!"');
|
||||
expect(step6.ctx.workingDirectory).toBe(ctx.defaultWorkingDirectory);
|
||||
expect(step6.shell).toBe('/bin/bash -eo pipefail');
|
||||
|
||||
@@ -87,7 +87,7 @@ describe(BuildFunction, () => {
|
||||
});
|
||||
expect(step).toBeInstanceOf(BuildStep);
|
||||
expect(step.id).toMatch(UUID_REGEX);
|
||||
expect(step.name).toBe('Test function');
|
||||
expect(step.displayName).toBe('Test function');
|
||||
expect(step.command).toBe('echo 123');
|
||||
});
|
||||
it('works with build step function', () => {
|
||||
@@ -103,7 +103,7 @@ describe(BuildFunction, () => {
|
||||
});
|
||||
expect(step).toBeInstanceOf(BuildStep);
|
||||
expect(step.id).toMatch(UUID_REGEX);
|
||||
expect(step.name).toBe('Test function');
|
||||
expect(step.displayName).toBe('Test function');
|
||||
expect(step.fn).toBe(fn);
|
||||
});
|
||||
it('works with custom JS/TS function', () => {
|
||||
@@ -118,9 +118,21 @@ describe(BuildFunction, () => {
|
||||
});
|
||||
expect(step).toBeInstanceOf(BuildStep);
|
||||
expect(step.id).toMatch(UUID_REGEX);
|
||||
expect(step.name).toBe('Test function');
|
||||
expect(step.displayName).toBe('Test function');
|
||||
expect(step.fn).toEqual(expect.any(Function));
|
||||
});
|
||||
it('uses the function id as the display name when the function name is not defined', () => {
|
||||
const ctx = createGlobalContextMock();
|
||||
const func = new BuildFunction({
|
||||
namespace: 'eas',
|
||||
id: 'test1',
|
||||
command: 'echo 123',
|
||||
});
|
||||
const step = func.createBuildStepFromFunctionCall(ctx, {
|
||||
workingDirectory: ctx.defaultWorkingDirectory,
|
||||
});
|
||||
expect(step.displayName).toBe('eas/test1');
|
||||
});
|
||||
it('can override id and shell from function definition', () => {
|
||||
const ctx = createGlobalContextMock();
|
||||
const func = new BuildFunction({
|
||||
@@ -138,6 +150,7 @@ describe(BuildFunction, () => {
|
||||
expect(func.shell).toBe('/bin/bash');
|
||||
expect(step.id).toBe('test2');
|
||||
expect(step.shell).toBe('/bin/zsh');
|
||||
expect(step.displayName).toBe('Test function');
|
||||
});
|
||||
it('creates function inputs and outputs', () => {
|
||||
const ctx = createGlobalContextMock();
|
||||
|
||||
@@ -2,7 +2,6 @@ import { jest } from '@jest/globals';
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import { instance, mock, verify, when } from 'ts-mockito';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { createGlobalContextMock } from './utils/context';
|
||||
import { getError, getErrorAsync } from './utils/error';
|
||||
@@ -29,29 +28,6 @@ describe(BuildStep, () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe(BuildStep.getDisplayName, () => {
|
||||
it('returns the name if defined', () => {
|
||||
expect(BuildStep.getDisplayName({ id: 'test1', name: 'Step 1' })).toBe('Step 1');
|
||||
});
|
||||
it("returns the id if it's not a uuid", () => {
|
||||
expect(BuildStep.getDisplayName({ id: 'test1' })).toBe('test1');
|
||||
});
|
||||
it('returns the first line of the command if name is undefined and id is a uuid', () => {
|
||||
expect(BuildStep.getDisplayName({ id: uuidv4(), command: 'echo 123\necho 456' })).toBe(
|
||||
'echo 123'
|
||||
);
|
||||
});
|
||||
it('returns the first non-comment line of the command', async () => {
|
||||
expect(
|
||||
BuildStep.getDisplayName({ id: uuidv4(), command: '# list files\nls -la\necho 123' })
|
||||
).toBe('ls -la');
|
||||
});
|
||||
it('returns the uuid id if neither name nor command is defined', () => {
|
||||
const id = uuidv4();
|
||||
expect(BuildStep.getDisplayName({ id })).toBe(id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('throws when neither command nor fn is set', () => {
|
||||
const mockCtx = mock<BuildStepGlobalContext>();
|
||||
@@ -62,7 +38,7 @@ describe(BuildStep, () => {
|
||||
// eslint-disable-next-line no-new
|
||||
new BuildStep(ctx, {
|
||||
id,
|
||||
displayName: BuildStep.getDisplayName({ id }),
|
||||
displayName: id,
|
||||
workingDirectory: '/tmp',
|
||||
});
|
||||
}).toThrowError(/Either command or fn must be defined/);
|
||||
@@ -75,7 +51,7 @@ describe(BuildStep, () => {
|
||||
expect(() => {
|
||||
const id = 'test1';
|
||||
const command = 'echo 123';
|
||||
const displayName = BuildStep.getDisplayName({ id, command });
|
||||
const displayName = command;
|
||||
|
||||
// eslint-disable-next-line no-new
|
||||
new BuildStep(ctx, {
|
||||
@@ -96,7 +72,7 @@ describe(BuildStep, () => {
|
||||
|
||||
const id = 'test1';
|
||||
const command = 'ls -la';
|
||||
const displayName = BuildStep.getDisplayName({ id, command });
|
||||
const displayName = command;
|
||||
|
||||
const step = new BuildStep(ctx, {
|
||||
id,
|
||||
@@ -113,7 +89,7 @@ describe(BuildStep, () => {
|
||||
|
||||
const id = 'test1';
|
||||
const command = 'ls -la';
|
||||
const displayName = BuildStep.getDisplayName({ id, command });
|
||||
const displayName = command;
|
||||
|
||||
const step = new BuildStep(ctx, {
|
||||
id,
|
||||
@@ -129,7 +105,7 @@ describe(BuildStep, () => {
|
||||
|
||||
const id = 'test1';
|
||||
const command = 'ls -la';
|
||||
const displayName = BuildStep.getDisplayName({ id, command });
|
||||
const displayName = command;
|
||||
|
||||
const step = new BuildStep(ctx, {
|
||||
id,
|
||||
@@ -149,7 +125,7 @@ describe(BuildStep, () => {
|
||||
|
||||
const id = 'test1';
|
||||
const command = 'ls -la';
|
||||
const displayName = BuildStep.getDisplayName({ id, command });
|
||||
const displayName = command;
|
||||
|
||||
const step = new BuildStep(ctx, {
|
||||
id,
|
||||
@@ -169,7 +145,7 @@ describe(BuildStep, () => {
|
||||
|
||||
const id = 'test1';
|
||||
const command = 'ls -la';
|
||||
const displayName = BuildStep.getDisplayName({ id, command });
|
||||
const displayName = command;
|
||||
|
||||
const step = new BuildStep(ctx, {
|
||||
id,
|
||||
@@ -185,17 +161,15 @@ describe(BuildStep, () => {
|
||||
const id = 'test1';
|
||||
const name = 'Test step';
|
||||
const command = 'ls -la';
|
||||
const displayName = BuildStep.getDisplayName({ id, name, command });
|
||||
const displayName = name;
|
||||
|
||||
const step = new BuildStep(ctx, {
|
||||
id,
|
||||
name,
|
||||
displayName,
|
||||
command,
|
||||
});
|
||||
expect(ctx.baseLogger.child).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
buildStepInternalId: expect.stringMatching(UUID_REGEX),
|
||||
buildStepId: 'test1',
|
||||
buildStepDisplayName: 'Test step',
|
||||
})
|
||||
@@ -208,7 +182,7 @@ describe(BuildStep, () => {
|
||||
|
||||
const id = 'test1';
|
||||
const command = 'echo hello';
|
||||
const displayName = BuildStep.getDisplayName({ id, command });
|
||||
const displayName = command;
|
||||
|
||||
const step = new BuildStep(ctx, {
|
||||
id,
|
||||
@@ -224,7 +198,7 @@ describe(BuildStep, () => {
|
||||
|
||||
const id = 'test1';
|
||||
const command = 'echo hello';
|
||||
const displayName = BuildStep.getDisplayName({ id, command });
|
||||
const displayName = command;
|
||||
|
||||
const step = new BuildStep(ctx, {
|
||||
id,
|
||||
@@ -252,7 +226,7 @@ describe(BuildStep, () => {
|
||||
it('sets status to FAIL when step fails', async () => {
|
||||
const id = 'test1';
|
||||
const command = 'false';
|
||||
const displayName = BuildStep.getDisplayName({ id, command });
|
||||
const displayName = command;
|
||||
|
||||
const step = new BuildStep(baseStepCtx, {
|
||||
id,
|
||||
@@ -266,7 +240,7 @@ describe(BuildStep, () => {
|
||||
it('sets status to SUCCESS when step succeeds', async () => {
|
||||
const id = 'test1';
|
||||
const command = 'true';
|
||||
const displayName = BuildStep.getDisplayName({ id, command });
|
||||
const displayName = command;
|
||||
|
||||
const step = new BuildStep(baseStepCtx, {
|
||||
id,
|
||||
@@ -281,7 +255,7 @@ describe(BuildStep, () => {
|
||||
it('logs an error if the command is to be executed in non-existing working directory', async () => {
|
||||
const id = 'test1';
|
||||
const command = 'ls -la';
|
||||
const displayName = BuildStep.getDisplayName({ id, command });
|
||||
const displayName = command;
|
||||
|
||||
const step = new BuildStep(baseStepCtx, {
|
||||
id,
|
||||
@@ -312,7 +286,7 @@ describe(BuildStep, () => {
|
||||
it('does not log an error if the command is to be executed in a directory that exists', async () => {
|
||||
const id = 'test1';
|
||||
const command = 'ls -la';
|
||||
const displayName = BuildStep.getDisplayName({ id, command });
|
||||
const displayName = command;
|
||||
|
||||
await fs.mkdir(path.join(baseStepCtx.defaultWorkingDirectory, 'existing-directory'), {
|
||||
recursive: true,
|
||||
@@ -368,7 +342,7 @@ describe(BuildStep, () => {
|
||||
|
||||
const id = 'test1';
|
||||
const command = 'ls -la';
|
||||
const displayName = BuildStep.getDisplayName({ id, command });
|
||||
const displayName = command;
|
||||
|
||||
const step = new BuildStep(baseStepCtx, {
|
||||
id,
|
||||
@@ -385,7 +359,7 @@ describe(BuildStep, () => {
|
||||
it('interpolates the inputs in command template', async () => {
|
||||
const id = 'test1';
|
||||
const command = "set-output foo2 '${inputs.foo1} ${inputs.foo2} ${inputs.foo3}'";
|
||||
const displayName = BuildStep.getDisplayName({ id, command });
|
||||
const displayName = command;
|
||||
|
||||
const step = new BuildStep(baseStepCtx, {
|
||||
id,
|
||||
@@ -466,7 +440,7 @@ describe(BuildStep, () => {
|
||||
it('collects the outputs after calling the script', async () => {
|
||||
const id = 'test1';
|
||||
const command = 'set-output abc 123';
|
||||
const displayName = BuildStep.getDisplayName({ id, command });
|
||||
const displayName = command;
|
||||
|
||||
const step = new BuildStep(baseStepCtx, {
|
||||
id,
|
||||
@@ -493,7 +467,7 @@ describe(BuildStep, () => {
|
||||
env,
|
||||
});
|
||||
});
|
||||
const displayName = BuildStep.getDisplayName({ id });
|
||||
const displayName = id;
|
||||
|
||||
const step = new BuildStep(baseStepCtx, {
|
||||
id,
|
||||
@@ -512,7 +486,7 @@ describe(BuildStep, () => {
|
||||
env,
|
||||
});
|
||||
});
|
||||
const displayName = BuildStep.getDisplayName({ id });
|
||||
const displayName = id;
|
||||
|
||||
const step = new BuildStep(baseStepCtx, {
|
||||
id,
|
||||
@@ -536,7 +510,7 @@ describe(BuildStep, () => {
|
||||
it('succeeds when step completes within timeout', async () => {
|
||||
const id = 'test1';
|
||||
const command = 'sleep 0.1';
|
||||
const displayName = BuildStep.getDisplayName({ id, command });
|
||||
const displayName = command;
|
||||
|
||||
const step = new BuildStep(baseStepCtx, {
|
||||
id,
|
||||
@@ -551,7 +525,7 @@ describe(BuildStep, () => {
|
||||
it('fails when command exceeds timeout', async () => {
|
||||
const id = 'test1';
|
||||
const command = 'sleep 2';
|
||||
const displayName = BuildStep.getDisplayName({ id, command });
|
||||
const displayName = command;
|
||||
|
||||
const step = new BuildStep(baseStepCtx, {
|
||||
id,
|
||||
@@ -574,7 +548,7 @@ describe(BuildStep, () => {
|
||||
setTimeout(resolve, 2000); // 2 second delay
|
||||
})
|
||||
);
|
||||
const displayName = BuildStep.getDisplayName({ id });
|
||||
const displayName = id;
|
||||
|
||||
const step = new BuildStep(baseStepCtx, {
|
||||
id,
|
||||
@@ -592,7 +566,7 @@ describe(BuildStep, () => {
|
||||
it('works without timeout when timeoutMs is undefined', async () => {
|
||||
const id = 'test1';
|
||||
const command = 'sleep 0.1';
|
||||
const displayName = BuildStep.getDisplayName({ id, command });
|
||||
const displayName = command;
|
||||
|
||||
const step = new BuildStep(baseStepCtx, {
|
||||
id,
|
||||
@@ -609,7 +583,7 @@ describe(BuildStep, () => {
|
||||
it('works with strings with whitespaces passed as a value for an output parameter', async () => {
|
||||
const id = 'test1';
|
||||
const command = 'set-output abc "d o m i n i k"';
|
||||
const displayName = BuildStep.getDisplayName({ id, command });
|
||||
const displayName = command;
|
||||
|
||||
const step = new BuildStep(baseStepCtx, {
|
||||
id,
|
||||
@@ -631,7 +605,7 @@ describe(BuildStep, () => {
|
||||
it('throws an error if some required outputs have not been set with set-output in script', async () => {
|
||||
const id = 'test1';
|
||||
const command = 'echo 123';
|
||||
const displayName = BuildStep.getDisplayName({ id, command });
|
||||
const displayName = command;
|
||||
|
||||
const step = new BuildStep(baseStepCtx, {
|
||||
id,
|
||||
@@ -661,7 +635,7 @@ describe(BuildStep, () => {
|
||||
baseStepCtx.updateEnv(globalEnv);
|
||||
|
||||
const id = 'test1';
|
||||
const displayName = BuildStep.getDisplayName({ id });
|
||||
const displayName = id;
|
||||
|
||||
const step = new BuildStep(baseStepCtx, {
|
||||
id,
|
||||
@@ -694,7 +668,7 @@ describe(BuildStep, () => {
|
||||
baseStepCtx.updateEnv(globalEnv);
|
||||
|
||||
const id = 'test1';
|
||||
const displayName = BuildStep.getDisplayName({ id });
|
||||
const displayName = id;
|
||||
|
||||
const step = new BuildStep(baseStepCtx, {
|
||||
id,
|
||||
@@ -722,7 +696,7 @@ describe(BuildStep, () => {
|
||||
baseStepCtx.updateEnv(env);
|
||||
|
||||
const id = 'test1';
|
||||
const displayName = BuildStep.getDisplayName({ id });
|
||||
const displayName = id;
|
||||
|
||||
const inputs: BuildStepInput[] = [
|
||||
new BuildStepInput(baseStepCtx, {
|
||||
@@ -807,7 +781,7 @@ describe(BuildStep, () => {
|
||||
it('throws an error when the step has not been executed yet', async () => {
|
||||
const id = 'test1';
|
||||
const command = 'set-output abc 123';
|
||||
const displayName = BuildStep.getDisplayName({ id, command });
|
||||
const displayName = command;
|
||||
|
||||
const step = new BuildStep(baseStepCtx, {
|
||||
id,
|
||||
@@ -831,7 +805,7 @@ describe(BuildStep, () => {
|
||||
it('throws an error when trying to access a non-existent output', async () => {
|
||||
const id = 'test1';
|
||||
const command = 'set-output abc 123';
|
||||
const displayName = BuildStep.getDisplayName({ id, command });
|
||||
const displayName = command;
|
||||
|
||||
const step = new BuildStep(baseStepCtx, {
|
||||
id,
|
||||
@@ -850,13 +824,13 @@ describe(BuildStep, () => {
|
||||
step.getOutputValueByName('def');
|
||||
});
|
||||
expect(error).toBeInstanceOf(BuildStepRuntimeError);
|
||||
expect(error.message).toMatch(/Step "test1" does not have output "def"/);
|
||||
expect(error.message).toMatch(/Step "set-output abc 123" does not have output "def"/);
|
||||
});
|
||||
|
||||
it('returns the output value', async () => {
|
||||
const id = 'test1';
|
||||
const command = 'set-output abc 123';
|
||||
const displayName = BuildStep.getDisplayName({ id, command });
|
||||
const displayName = command;
|
||||
|
||||
const step = new BuildStep(baseStepCtx, {
|
||||
id,
|
||||
@@ -889,7 +863,7 @@ describe(BuildStep, () => {
|
||||
|
||||
const id = 'test1';
|
||||
const command = 'echo "$TEST_ABC $TEST_DEF"';
|
||||
const displayName = BuildStep.getDisplayName({ id, command });
|
||||
const displayName = command;
|
||||
|
||||
(baseStepCtx as any).baseLogger = logger;
|
||||
|
||||
@@ -920,7 +894,7 @@ describe(BuildStep, () => {
|
||||
|
||||
const id = 'test1';
|
||||
const command = 'echo $TEST_ABC';
|
||||
const displayName = BuildStep.getDisplayName({ id, command });
|
||||
const displayName = command;
|
||||
|
||||
(baseStepCtx as any).baseLogger = logger;
|
||||
|
||||
@@ -952,7 +926,7 @@ describe(BuildStep, () => {
|
||||
const id = 'test1';
|
||||
const command =
|
||||
'echo $__EXPO_STEPS_BUILD_ID\necho $__EXPO_STEPS_OUTPUTS_DIR\necho $__EXPO_STEPS_ENVS_DIR\necho $__EXPO_STEPS_WORKING_DIRECTORY';
|
||||
const displayName = BuildStep.getDisplayName({ id, command });
|
||||
const displayName = command;
|
||||
|
||||
(baseStepCtx as any).baseLogger = logger;
|
||||
const step = new BuildStep(baseStepCtx, {
|
||||
@@ -976,7 +950,7 @@ describe(BuildStep, () => {
|
||||
it('can update global env object with set-env', async () => {
|
||||
const id = 'test1';
|
||||
const command = 'set-env EXAMPLE value';
|
||||
const displayName = BuildStep.getDisplayName({ id, command });
|
||||
const displayName = command;
|
||||
|
||||
const step = new BuildStep(baseStepCtx, {
|
||||
id,
|
||||
@@ -989,7 +963,7 @@ describe(BuildStep, () => {
|
||||
it('can override existing envs in global env object with set-env', async () => {
|
||||
const id = 'test1';
|
||||
const command = 'set-env EXAMPLE value';
|
||||
const displayName = BuildStep.getDisplayName({ id, command });
|
||||
const displayName = command;
|
||||
|
||||
baseStepCtx.updateEnv({
|
||||
EXAMPLE: 'test1',
|
||||
@@ -1022,7 +996,7 @@ describe(BuildStep.prototype.canBeRunOnRuntimePlatform, () => {
|
||||
it('returns true when the step does not have a platform filter', async () => {
|
||||
const id = 'test1';
|
||||
const command = 'set-output abc 123';
|
||||
const displayName = BuildStep.getDisplayName({ id, command });
|
||||
const displayName = command;
|
||||
|
||||
const step = new BuildStep(baseStepCtx, {
|
||||
id,
|
||||
@@ -1035,7 +1009,7 @@ describe(BuildStep.prototype.canBeRunOnRuntimePlatform, () => {
|
||||
it('returns true when the step has a platform filter and the platform matches', async () => {
|
||||
const id = 'test1';
|
||||
const command = 'set-output abc 123';
|
||||
const displayName = BuildStep.getDisplayName({ id, command });
|
||||
const displayName = command;
|
||||
|
||||
const step = new BuildStep(baseStepCtx, {
|
||||
id,
|
||||
@@ -1049,7 +1023,7 @@ describe(BuildStep.prototype.canBeRunOnRuntimePlatform, () => {
|
||||
it('returns false when the step has a platform filter and the platform does not match', async () => {
|
||||
const id = 'test1';
|
||||
const command = 'set-output abc 123';
|
||||
const displayName = BuildStep.getDisplayName({ id, command });
|
||||
const displayName = command;
|
||||
|
||||
const step = new BuildStep(baseStepCtx, {
|
||||
id,
|
||||
@@ -1076,7 +1050,7 @@ describe(BuildStep.prototype.serialize, () => {
|
||||
it('serializes correctly', async () => {
|
||||
const id = 'test1';
|
||||
const command = 'set-output abc 123';
|
||||
const displayName = BuildStep.getDisplayName({ id, command });
|
||||
const displayName = command;
|
||||
|
||||
const outputs = [
|
||||
new BuildStepOutput(baseStepCtx, {
|
||||
|
||||
@@ -14,7 +14,7 @@ describe(BuildStepInput, () => {
|
||||
const ctx = createGlobalContextMock();
|
||||
const i = new BuildStepInput(ctx, {
|
||||
id: 'foo',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
required: true,
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.STRING,
|
||||
});
|
||||
@@ -26,7 +26,7 @@ describe(BuildStepInput, () => {
|
||||
const ctx = createGlobalContextMock();
|
||||
const i = new BuildStepInput(ctx, {
|
||||
id: 'foo',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.BOOLEAN,
|
||||
required: true,
|
||||
});
|
||||
@@ -38,7 +38,7 @@ describe(BuildStepInput, () => {
|
||||
const ctx = createGlobalContextMock();
|
||||
const i = new BuildStepInput(ctx, {
|
||||
id: 'foo',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.NUMBER,
|
||||
required: true,
|
||||
});
|
||||
@@ -50,7 +50,7 @@ describe(BuildStepInput, () => {
|
||||
const ctx = createGlobalContextMock();
|
||||
const i = new BuildStepInput(ctx, {
|
||||
id: 'foo',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.JSON,
|
||||
required: true,
|
||||
});
|
||||
@@ -64,7 +64,7 @@ describe(BuildStepInput, () => {
|
||||
const ctx = createGlobalContextMock();
|
||||
const i = new BuildStepInput(ctx, {
|
||||
id: 'foo',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
required: false,
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.STRING,
|
||||
});
|
||||
@@ -76,7 +76,7 @@ describe(BuildStepInput, () => {
|
||||
const ctx = createGlobalContextMock();
|
||||
const i = new BuildStepInput(ctx, {
|
||||
id: 'foo',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
defaultValue: 'baz',
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.STRING,
|
||||
required: true,
|
||||
@@ -88,7 +88,7 @@ describe(BuildStepInput, () => {
|
||||
const ctx = createGlobalContextMock();
|
||||
const i = new BuildStepInput(ctx, {
|
||||
id: 'foo',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
defaultValue: true,
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.BOOLEAN,
|
||||
required: true,
|
||||
@@ -100,7 +100,7 @@ describe(BuildStepInput, () => {
|
||||
const ctx = createGlobalContextMock();
|
||||
const i = new BuildStepInput(ctx, {
|
||||
id: 'foo',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
defaultValue: { foo: 'bar' },
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.JSON,
|
||||
required: true,
|
||||
@@ -114,7 +114,7 @@ describe(BuildStepInput, () => {
|
||||
const ctx = createGlobalContextMock();
|
||||
const i = new BuildStepInput(ctx, {
|
||||
id: 'foo',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
defaultValue: '${ eas.runtimePlatform }',
|
||||
required: true,
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.STRING,
|
||||
@@ -130,7 +130,7 @@ describe(BuildStepInput, () => {
|
||||
});
|
||||
const i = new BuildStepInput(ctx, {
|
||||
id: 'foo',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
defaultValue: '${{ foo }}',
|
||||
required: true,
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.STRING,
|
||||
@@ -146,7 +146,7 @@ describe(BuildStepInput, () => {
|
||||
});
|
||||
const i = new BuildStepInput(ctx, {
|
||||
id: 'foo',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
defaultValue: 'test-${{ foo }}',
|
||||
required: true,
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.STRING,
|
||||
@@ -161,7 +161,7 @@ describe(BuildStepInput, () => {
|
||||
});
|
||||
const i = new BuildStepInput(ctx, {
|
||||
id: 'foo',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
defaultValue: '${ eas.env.HOME }',
|
||||
required: true,
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.STRING,
|
||||
@@ -181,7 +181,7 @@ describe(BuildStepInput, () => {
|
||||
});
|
||||
const i = new BuildStepInput(ctx, {
|
||||
id: 'foo',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
defaultValue: '${ eas.foo.bar }',
|
||||
required: true,
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.STRING,
|
||||
@@ -201,7 +201,7 @@ describe(BuildStepInput, () => {
|
||||
});
|
||||
const i = new BuildStepInput(ctx, {
|
||||
id: 'foo',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
defaultValue: '${{ foo.bar }}',
|
||||
required: true,
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.STRING,
|
||||
@@ -221,7 +221,7 @@ describe(BuildStepInput, () => {
|
||||
});
|
||||
const i = new BuildStepInput(ctx, {
|
||||
id: 'foo',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
defaultValue: '${ eas.foo.bar }',
|
||||
required: true,
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.STRING,
|
||||
@@ -241,7 +241,7 @@ describe(BuildStepInput, () => {
|
||||
});
|
||||
const i = new BuildStepInput(ctx, {
|
||||
id: 'foo',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
defaultValue: '${{ foo.bar }}',
|
||||
required: true,
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.STRING,
|
||||
@@ -268,7 +268,7 @@ describe(BuildStepInput, () => {
|
||||
});
|
||||
const i = new BuildStepInput<BuildStepInputValueTypeName>(ctx, {
|
||||
id: 'foo',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
defaultValue: '${ eas.foo.bar[3].baz }',
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.NUMBER,
|
||||
required: true,
|
||||
@@ -293,7 +293,7 @@ describe(BuildStepInput, () => {
|
||||
});
|
||||
const i = new BuildStepInput<BuildStepInputValueTypeName>(ctx, {
|
||||
id: 'foo',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
defaultValue: '${{ foo.bar[3].baz }}',
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.NUMBER,
|
||||
required: true,
|
||||
@@ -318,7 +318,7 @@ describe(BuildStepInput, () => {
|
||||
});
|
||||
const i = new BuildStepInput<BuildStepInputValueTypeName>(ctx, {
|
||||
id: 'foo',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
defaultValue: 'test-${{ foo.bar[3].baz }}',
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.STRING,
|
||||
required: true,
|
||||
@@ -345,7 +345,7 @@ describe(BuildStepInput, () => {
|
||||
});
|
||||
const i = new BuildStepInput<BuildStepInputValueTypeName>(ctx, {
|
||||
id: 'foo',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
defaultValue: '${ eas.foo.bar[3].baz.qux }',
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.BOOLEAN,
|
||||
required: true,
|
||||
@@ -372,7 +372,7 @@ describe(BuildStepInput, () => {
|
||||
});
|
||||
const i = new BuildStepInput<BuildStepInputValueTypeName>(ctx, {
|
||||
id: 'foo',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
defaultValue: '${{ startsWith(foo.bar[3].baz.qux, "test") }}',
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.BOOLEAN,
|
||||
required: true,
|
||||
@@ -399,7 +399,7 @@ describe(BuildStepInput, () => {
|
||||
});
|
||||
const i = new BuildStepInput<BuildStepInputValueTypeName>(ctx, {
|
||||
id: 'foo',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
defaultValue: '${{ foo.bar[3].baz.qux }}',
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.BOOLEAN,
|
||||
required: true,
|
||||
@@ -426,7 +426,7 @@ describe(BuildStepInput, () => {
|
||||
});
|
||||
const i = new BuildStepInput<BuildStepInputValueTypeName>(ctx, {
|
||||
id: 'foo',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
defaultValue: '${ eas.foo }',
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.JSON,
|
||||
required: true,
|
||||
@@ -455,7 +455,7 @@ describe(BuildStepInput, () => {
|
||||
});
|
||||
const i = new BuildStepInput<BuildStepInputValueTypeName>(ctx, {
|
||||
id: 'foo',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
defaultValue: '${{ foo }}',
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.JSON,
|
||||
required: true,
|
||||
@@ -484,7 +484,7 @@ describe(BuildStepInput, () => {
|
||||
});
|
||||
const i = new BuildStepInput<BuildStepInputValueTypeName>(ctx, {
|
||||
id: 'foo',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
defaultValue: '${ eas.foo.bar[3].baz.qux }',
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.NUMBER,
|
||||
required: true,
|
||||
@@ -513,7 +513,7 @@ describe(BuildStepInput, () => {
|
||||
});
|
||||
const i = new BuildStepInput<BuildStepInputValueTypeName>(ctx, {
|
||||
id: 'foo',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
defaultValue: '${{ foo.bar[3].baz.qux }}',
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.NUMBER,
|
||||
required: true,
|
||||
@@ -542,7 +542,7 @@ describe(BuildStepInput, () => {
|
||||
});
|
||||
const i = new BuildStepInput<BuildStepInputValueTypeName>(ctx, {
|
||||
id: 'foo',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
defaultValue: '${ eas.foo.bar[3].baz.qux }',
|
||||
required: true,
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.BOOLEAN,
|
||||
@@ -571,7 +571,7 @@ describe(BuildStepInput, () => {
|
||||
});
|
||||
const i = new BuildStepInput<BuildStepInputValueTypeName>(ctx, {
|
||||
id: 'foo',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
defaultValue: '${{ foo.bar[3].baz.qux }}',
|
||||
required: true,
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.BOOLEAN,
|
||||
@@ -600,7 +600,7 @@ describe(BuildStepInput, () => {
|
||||
});
|
||||
const i = new BuildStepInput<BuildStepInputValueTypeName>(ctx, {
|
||||
id: 'foo',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
defaultValue: '${ eas.foo.bar[3].baz.qux }',
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.JSON,
|
||||
required: true,
|
||||
@@ -629,7 +629,7 @@ describe(BuildStepInput, () => {
|
||||
});
|
||||
const i = new BuildStepInput<BuildStepInputValueTypeName>(ctx, {
|
||||
id: 'foo',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
defaultValue: '${{ foo.bar[3].baz.qux }}',
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.JSON,
|
||||
required: true,
|
||||
@@ -650,7 +650,7 @@ describe(BuildStepInput, () => {
|
||||
});
|
||||
const i = new BuildStepInput(ctx, {
|
||||
id: 'foo',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
required: true,
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.JSON,
|
||||
});
|
||||
@@ -685,7 +685,7 @@ describe(BuildStepInput, () => {
|
||||
});
|
||||
const i = new BuildStepInput(ctx, {
|
||||
id: 'foo',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
required: true,
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.JSON,
|
||||
});
|
||||
@@ -717,7 +717,7 @@ describe(BuildStepInput, () => {
|
||||
});
|
||||
const i = new BuildStepInput(ctx, {
|
||||
id: 'foo',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
required: true,
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.JSON,
|
||||
});
|
||||
@@ -747,7 +747,7 @@ describe(BuildStepInput, () => {
|
||||
});
|
||||
const i = new BuildStepInput(ctx, {
|
||||
id: 'foo',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
required: true,
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.JSON,
|
||||
});
|
||||
@@ -773,7 +773,7 @@ describe(BuildStepInput, () => {
|
||||
const ctx = createGlobalContextMock();
|
||||
const i = new BuildStepInput(ctx, {
|
||||
id: 'foo',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
defaultValue: 42,
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.NUMBER,
|
||||
required: true,
|
||||
@@ -785,7 +785,7 @@ describe(BuildStepInput, () => {
|
||||
const ctx = createGlobalContextMock();
|
||||
const i = new BuildStepInput(ctx, {
|
||||
id: 'foo',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
required: true,
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.STRING,
|
||||
});
|
||||
@@ -803,7 +803,7 @@ describe(BuildStepInput, () => {
|
||||
const ctx = createGlobalContextMock();
|
||||
const i = new BuildStepInput<BuildStepInputValueTypeName>(ctx, {
|
||||
id: 'foo',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
required: true,
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.BOOLEAN,
|
||||
});
|
||||
@@ -821,7 +821,7 @@ describe(BuildStepInput, () => {
|
||||
const i = new BuildStepInput<BuildStepInputValueTypeName>(ctx, {
|
||||
id: 'foo',
|
||||
required: true,
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.JSON,
|
||||
});
|
||||
i.set('${ eas.runtimePlatform }');
|
||||
@@ -840,7 +840,7 @@ describe(BuildStepInput, () => {
|
||||
const i = new BuildStepInput<BuildStepInputValueTypeName>(ctx, {
|
||||
id: 'foo',
|
||||
required: true,
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.JSON,
|
||||
});
|
||||
i.set('${{ foo }}');
|
||||
@@ -855,7 +855,7 @@ describe(BuildStepInput, () => {
|
||||
const i = new BuildStepInput<BuildStepInputValueTypeName>(ctx, {
|
||||
id: 'foo',
|
||||
required: true,
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.NUMBER,
|
||||
});
|
||||
i.set('${ eas.runtimePlatform }');
|
||||
@@ -876,7 +876,7 @@ describe(BuildStepInput, () => {
|
||||
const i = new BuildStepInput<BuildStepInputValueTypeName>(ctx, {
|
||||
id: 'foo',
|
||||
required: true,
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.NUMBER,
|
||||
});
|
||||
i.set('${{ foo }}');
|
||||
@@ -893,7 +893,7 @@ describe(BuildStepInput, () => {
|
||||
const i = new BuildStepInput<BuildStepInputValueTypeName>(ctx, {
|
||||
id: 'foo',
|
||||
required: true,
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.BOOLEAN,
|
||||
});
|
||||
i.set('${ eas.runtimePlatform }');
|
||||
@@ -914,7 +914,7 @@ describe(BuildStepInput, () => {
|
||||
const i = new BuildStepInput<BuildStepInputValueTypeName>(ctx, {
|
||||
id: 'foo',
|
||||
required: true,
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.BOOLEAN,
|
||||
});
|
||||
i.set('${{ foo }}');
|
||||
@@ -930,7 +930,7 @@ describe(BuildStepInput, () => {
|
||||
const ctx = createGlobalContextMock();
|
||||
const i = new BuildStepInput<BuildStepInputValueTypeName>(ctx, {
|
||||
id: 'foo',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
required: true,
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.STRING,
|
||||
});
|
||||
@@ -952,21 +952,21 @@ describe(makeBuildStepInputByIdMap, () => {
|
||||
const inputs: BuildStepInput[] = [
|
||||
new BuildStepInput(ctx, {
|
||||
id: 'foo1',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
defaultValue: 'bar1',
|
||||
required: true,
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.STRING,
|
||||
}),
|
||||
new BuildStepInput(ctx, {
|
||||
id: 'foo2',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
defaultValue: 'bar2',
|
||||
required: true,
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.STRING,
|
||||
}),
|
||||
new BuildStepInput(ctx, {
|
||||
id: 'foo3',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
defaultValue: true,
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.BOOLEAN,
|
||||
required: true,
|
||||
|
||||
@@ -8,7 +8,7 @@ describe(BuildStepOutput, () => {
|
||||
const ctx = createGlobalContextMock();
|
||||
const o = new BuildStepOutput(ctx, {
|
||||
id: 'foo',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
required: true,
|
||||
});
|
||||
o.set('bar');
|
||||
@@ -19,7 +19,7 @@ describe(BuildStepOutput, () => {
|
||||
const ctx = createGlobalContextMock();
|
||||
const o = new BuildStepOutput(ctx, {
|
||||
id: 'foo',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
required: true,
|
||||
});
|
||||
expect(() => {
|
||||
@@ -36,7 +36,7 @@ describe(BuildStepOutput, () => {
|
||||
const ctx = createGlobalContextMock();
|
||||
const i = new BuildStepOutput<boolean>(ctx, {
|
||||
id: 'foo',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
required: true,
|
||||
});
|
||||
expect(() => {
|
||||
@@ -50,13 +50,13 @@ describe(BuildStepOutput, () => {
|
||||
const ctx = createGlobalContextMock();
|
||||
const o = new BuildStepOutput(ctx, {
|
||||
id: 'foo',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
required: true,
|
||||
});
|
||||
o.set('bar');
|
||||
expect(o.serialize()).toEqual({
|
||||
id: 'foo',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
required: true,
|
||||
value: 'bar',
|
||||
});
|
||||
@@ -65,12 +65,12 @@ describe(BuildStepOutput, () => {
|
||||
test('deserializes correctly', () => {
|
||||
const o = BuildStepOutput.deserialize({
|
||||
id: 'foo',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
required: true,
|
||||
value: 'bar',
|
||||
});
|
||||
expect(o.id).toBe('foo');
|
||||
expect(o.stepDisplayName).toBe(BuildStep.getDisplayName({ id: 'test1' }));
|
||||
expect(o.stepDisplayName).toBe('test1');
|
||||
expect(o.required).toBe(true);
|
||||
expect(o.value).toBe('bar');
|
||||
});
|
||||
@@ -86,12 +86,12 @@ describe(makeBuildStepOutputByIdMap, () => {
|
||||
const outputs: BuildStepOutput[] = [
|
||||
new BuildStepOutput(ctx, {
|
||||
id: 'abc1',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
required: true,
|
||||
}),
|
||||
new BuildStepOutput(ctx, {
|
||||
id: 'abc2',
|
||||
stepDisplayName: BuildStep.getDisplayName({ id: 'test1' }),
|
||||
stepDisplayName: 'test1',
|
||||
required: true,
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -18,27 +18,27 @@ describe(BuildWorkflowValidator, () => {
|
||||
buildSteps: [
|
||||
new BuildStep(ctx, {
|
||||
id: 'test1',
|
||||
displayName: BuildStep.getDisplayName({ id: 'test1', command: 'echo 123' }),
|
||||
displayName: 'echo 123',
|
||||
command: 'echo 123',
|
||||
}),
|
||||
new BuildStep(ctx, {
|
||||
id: 'test1',
|
||||
displayName: BuildStep.getDisplayName({ id: 'test1', command: 'echo 456' }),
|
||||
displayName: 'echo 456',
|
||||
command: 'echo 456',
|
||||
}),
|
||||
new BuildStep(ctx, {
|
||||
id: 'test1',
|
||||
displayName: BuildStep.getDisplayName({ id: 'test1', command: 'echo 789' }),
|
||||
displayName: 'echo 789',
|
||||
command: 'echo 789',
|
||||
}),
|
||||
new BuildStep(ctx, {
|
||||
id: 'test3',
|
||||
displayName: BuildStep.getDisplayName({ id: 'test3', command: 'echo 123' }),
|
||||
displayName: 'echo 123',
|
||||
command: 'echo 123',
|
||||
}),
|
||||
new BuildStep(ctx, {
|
||||
id: 'test3',
|
||||
displayName: BuildStep.getDisplayName({ id: 'test3', command: 'echo 456' }),
|
||||
displayName: 'echo 456',
|
||||
command: 'echo 456',
|
||||
}),
|
||||
],
|
||||
@@ -60,7 +60,7 @@ describe(BuildWorkflowValidator, () => {
|
||||
|
||||
const id1 = 'test1';
|
||||
const command1 = 'set-output output1 123';
|
||||
const displayName1 = BuildStep.getDisplayName({ id: id1, command: command1 });
|
||||
const displayName1 = id1;
|
||||
|
||||
const workflow = new BuildWorkflow(ctx, {
|
||||
buildSteps: [
|
||||
@@ -227,11 +227,11 @@ describe(BuildWorkflowValidator, () => {
|
||||
|
||||
const id1 = 'test1';
|
||||
const command1 = 'set-output output1 123';
|
||||
const displayName1 = BuildStep.getDisplayName({ id: id1, command: command1 });
|
||||
const displayName1 = id1;
|
||||
|
||||
const id2 = 'test2';
|
||||
const command2 = 'set-output output1 123';
|
||||
const displayName2 = BuildStep.getDisplayName({ id: id2, command: command2 });
|
||||
const displayName2 = id2;
|
||||
|
||||
const workflow = new BuildWorkflow(ctx, {
|
||||
buildSteps: [
|
||||
@@ -280,7 +280,7 @@ describe(BuildWorkflowValidator, () => {
|
||||
test('output from non-existent step', async () => {
|
||||
const id = 'test2';
|
||||
const command = 'echo ${ inputs.input1 }';
|
||||
const displayName = BuildStep.getDisplayName({ id, command });
|
||||
const displayName = id;
|
||||
|
||||
const ctx = createGlobalContextMock();
|
||||
const workflow = new BuildWorkflow(ctx, {
|
||||
@@ -318,15 +318,15 @@ describe(BuildWorkflowValidator, () => {
|
||||
test('undefined output', async () => {
|
||||
const id1 = 'test1';
|
||||
const command1 = 'set-output output1 123';
|
||||
const displayName1 = BuildStep.getDisplayName({ id: id1, command: command1 });
|
||||
const displayName1 = id1;
|
||||
|
||||
const id2 = 'test2';
|
||||
const command2 = 'echo ${ inputs.input1 }';
|
||||
const displayName2 = BuildStep.getDisplayName({ id: id2, command: command2 });
|
||||
const displayName2 = id2;
|
||||
|
||||
const id3 = 'test3';
|
||||
const command3 = 'echo ${ inputs.input1 }';
|
||||
const displayName3 = BuildStep.getDisplayName({ id: id3, command: command3 });
|
||||
const displayName3 = id3;
|
||||
|
||||
const ctx = createGlobalContextMock();
|
||||
const workflow = new BuildWorkflow(ctx, {
|
||||
@@ -390,15 +390,15 @@ describe(BuildWorkflowValidator, () => {
|
||||
test('multiple config errors', async () => {
|
||||
const id1 = 'test1';
|
||||
const command1 = 'set-output output1 123';
|
||||
const displayName1 = BuildStep.getDisplayName({ id: id1, command: command1 });
|
||||
const displayName1 = id1;
|
||||
|
||||
const id2 = 'test2';
|
||||
const command2 = 'echo ${ inputs.input1 }';
|
||||
const displayName2 = BuildStep.getDisplayName({ id: id2, command: command2 });
|
||||
const displayName2 = id2;
|
||||
|
||||
const id3 = 'test3';
|
||||
const command3 = 'echo ${ inputs.input1 }';
|
||||
const displayName3 = BuildStep.getDisplayName({ id: id3, command: command3 });
|
||||
const displayName3 = id3;
|
||||
|
||||
const ctx = createGlobalContextMock();
|
||||
const workflow = new BuildWorkflow(ctx, {
|
||||
@@ -465,7 +465,7 @@ describe(BuildWorkflowValidator, () => {
|
||||
});
|
||||
test('unallowed platform for build step', async () => {
|
||||
const id = 'test';
|
||||
const displayName = BuildStep.getDisplayName({ id });
|
||||
const displayName = id;
|
||||
const fn: BuildStepFunction = () => {};
|
||||
|
||||
const ctx = createGlobalContextMock({ runtimePlatform: BuildRuntimePlatform.LINUX });
|
||||
|
||||
@@ -273,7 +273,7 @@ describe(StepsConfigParser, () => {
|
||||
|
||||
const step1 = result.buildSteps[0];
|
||||
expect(step1.id).toMatch(UUID_REGEX);
|
||||
expect(step1.name).toBeUndefined();
|
||||
expect(step1.displayName).toBe('command1');
|
||||
expect(step1.command).toBe('command1');
|
||||
expect(step1.shell).toBe('/bin/bash -eo pipefail');
|
||||
expect(step1.fn).toBeUndefined();
|
||||
@@ -285,7 +285,7 @@ describe(StepsConfigParser, () => {
|
||||
|
||||
const step2 = result.buildSteps[1];
|
||||
expect(step2.id).toEqual('step2');
|
||||
expect(step2.name).toBeUndefined();
|
||||
expect(step2.displayName).toBe('step2');
|
||||
expect(step2.command).toBeUndefined();
|
||||
expect(step2.fn).toBeDefined();
|
||||
expect(step2.ctx.workingDirectory).toBe(path.join(ctx.defaultWorkingDirectory, 'test'));
|
||||
@@ -298,7 +298,7 @@ describe(StepsConfigParser, () => {
|
||||
|
||||
const step3 = result.buildSteps[2];
|
||||
expect(step3.id).toEqual('step3');
|
||||
expect(step3.name).toEqual('Step 3');
|
||||
expect(step3.displayName).toEqual('Step 3');
|
||||
expect(step3.command).toBe('command2');
|
||||
expect(step3.shell).toBe('sh');
|
||||
expect(step3.fn).toBeUndefined();
|
||||
@@ -325,7 +325,7 @@ describe(StepsConfigParser, () => {
|
||||
|
||||
const step4 = result.buildSteps[3];
|
||||
expect(step4.id).toEqual('step4');
|
||||
expect(step4.name).toEqual('Step 4');
|
||||
expect(step4.displayName).toEqual('Step 4');
|
||||
expect(step4.command).toBeUndefined();
|
||||
expect(step4.fn).toBeDefined();
|
||||
expect(step4.ctx.workingDirectory).toBe(path.join(ctx.defaultWorkingDirectory, 'dir'));
|
||||
|
||||
@@ -19,10 +19,10 @@
|
||||
"watch": "tsc --watch --preserveWatchOutput",
|
||||
"build": "tsc",
|
||||
"typecheck": "tsc",
|
||||
"prepack": "rm -rf dist *.tsbuildinfo && tsc -p tsconfig.build.json",
|
||||
"prepack": "rimraf dist \"*.tsbuildinfo\" && tsc -p tsconfig.build.json",
|
||||
"test": "jest --config jest.config.ts",
|
||||
"test:watch": "jest --config jest.config.ts --watch",
|
||||
"clean": "rm -rf node_modules dist coverage *.tsbuildinfo"
|
||||
"clean": "rimraf node_modules dist coverage \"*.tsbuildinfo\""
|
||||
},
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.21"
|
||||
@@ -33,6 +33,7 @@
|
||||
"@types/lodash.template": "^4.5.3",
|
||||
"@types/node": "20.14.2",
|
||||
"jest": "^29.7.0",
|
||||
"rimraf": "3.0.2",
|
||||
"ts-jest": "^29.1.4",
|
||||
"typescript": "^5.5.4"
|
||||
}
|
||||
|
||||
@@ -19,10 +19,10 @@
|
||||
"watch": "tsc --watch --preserveWatchOutput",
|
||||
"build": "tsc",
|
||||
"typecheck": "tsc",
|
||||
"prepack": "rm -rf dist *.tsbuildinfo && tsc -p tsconfig.build.json",
|
||||
"prepack": "rimraf dist \"*.tsbuildinfo\" && tsc -p tsconfig.build.json",
|
||||
"test": "jest --config jest.config.ts --passWithNoTests",
|
||||
"test:watch": "jest --config jest.config.ts --watch",
|
||||
"clean": "rm -rf node_modules dist coverage *.tsbuildinfo"
|
||||
"clean": "rimraf node_modules dist coverage \"*.tsbuildinfo\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@expo/logger": "18.0.1",
|
||||
@@ -32,6 +32,7 @@
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/node": "20.14.2",
|
||||
"jest": "^29.7.0",
|
||||
"rimraf": "3.0.2",
|
||||
"ts-jest": "^29.1.4",
|
||||
"typescript": "^5.5.4"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,11 @@
|
||||
"name": "EAS Worker",
|
||||
"slug": "eas-worker",
|
||||
"platforms": ["ios"],
|
||||
"ios": {
|
||||
"infoPlist": {
|
||||
"ITSAppUsesNonExemptEncryption": false
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"eas": {
|
||||
"projectId": "90798684-1384-4ce4-a73b-9f3fa3e3e368"
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "@expo/worker",
|
||||
"version": "18.2.0",
|
||||
"version": "18.4.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "yarn watch",
|
||||
"watch": "yarn compile --watch --preserveWatchOutput",
|
||||
"build": "npm run clean && npm run compile",
|
||||
"clean": "rm -rf ./dist *.tsbuildinfo",
|
||||
"clean-all": "yarn clean && rm -rf node_modules",
|
||||
"clean": "rimraf ./dist \"*.tsbuildinfo\"",
|
||||
"clean-all": "yarn clean && rimraf node_modules",
|
||||
"compile": "tsc -p tsconfig.build.json",
|
||||
"prepublishOnly": "npm run build",
|
||||
"test": "yarn test:unit",
|
||||
@@ -19,16 +19,16 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/core": "7.24.7",
|
||||
"@expo/build-tools": "18.2.0",
|
||||
"@expo/build-tools": "18.4.0",
|
||||
"@expo/config-plugins": "8.0.8",
|
||||
"@expo/config-types": "52.0.1",
|
||||
"@expo/downloader": "18.0.1",
|
||||
"@expo/eas-build-job": "18.2.0",
|
||||
"@expo/eas-build-job": "18.4.0",
|
||||
"@expo/logger": "18.0.1",
|
||||
"@expo/results": "1.0.0",
|
||||
"@expo/rudder-sdk-node": "1.1.1",
|
||||
"@expo/spawn-async": "1.7.2",
|
||||
"@expo/steps": "18.2.0",
|
||||
"@expo/steps": "18.4.0",
|
||||
"@expo/template-file": "18.0.1",
|
||||
"@expo/turtle-spawn": "18.0.1",
|
||||
"@hapi/boom": "10.0.1",
|
||||
@@ -71,6 +71,7 @@
|
||||
"@types/ws": "8.5.10",
|
||||
"jest": "29.7.0",
|
||||
"memfs": "3.4.1",
|
||||
"rimraf": "3.0.2",
|
||||
"supertest": "7.2.2",
|
||||
"ts-jest": "29.1.4",
|
||||
"typescript": "5.5.4"
|
||||
|
||||
@@ -418,8 +418,7 @@ describe('State sync mechanism', () => {
|
||||
it('should expose tracking code as internal error code from generic job', async () => {
|
||||
jest.mocked(runGenericJobAsync).mockResolvedValueOnce({
|
||||
runResult: result(
|
||||
new errors.BuildError('ASC app was not found for this account.', {
|
||||
errorCode: genericJobRunErrorCode,
|
||||
new errors.UserError(genericJobRunErrorCode, 'ASC app was not found for this account.', {
|
||||
trackingCode: 'GENERIC_TRACKING_ERROR',
|
||||
})
|
||||
),
|
||||
@@ -485,5 +484,75 @@ describe('State sync mechanism', () => {
|
||||
ws.close();
|
||||
clearTimeout(messageTimeout);
|
||||
});
|
||||
|
||||
it('should expose system errors with SERVER_ERROR and tracking code from generic job', async () => {
|
||||
jest.mocked(runGenericJobAsync).mockResolvedValueOnce({
|
||||
runResult: result(
|
||||
new errors.SystemError('Internal service is unavailable.', {
|
||||
trackingCode: 'GENERIC_SYSTEM_ERROR',
|
||||
})
|
||||
),
|
||||
buildWorkflow: {} as any,
|
||||
});
|
||||
const dispatchWS = new WebSocket(`ws://localhost:${port}?expo_vm_name=${hostname()}`);
|
||||
const dispatchHelper = new WsHelper(dispatchWS);
|
||||
await dispatchHelper.onOpen();
|
||||
dispatchWS.send(
|
||||
JSON.stringify({
|
||||
type: 'dispatch',
|
||||
buildId,
|
||||
job: createTestGenericJob(),
|
||||
initiatingUserId: '14367e1b-26fc-4c00-aedb-0629d78f8286',
|
||||
metadata: {
|
||||
trackingContext: {},
|
||||
},
|
||||
})
|
||||
);
|
||||
dispatchWS.close();
|
||||
await dispatchHelper.onClose();
|
||||
|
||||
const ws = new WebSocket(`ws://localhost:${port}?expo_vm_name=${hostname()}`);
|
||||
const helper = new WsHelper(ws);
|
||||
|
||||
let stateResponsePromiseResolve: () => void;
|
||||
const stateResponsePromise = new Promise<void>(res => {
|
||||
stateResponsePromiseResolve = res;
|
||||
});
|
||||
const onMessage = jest.fn((message: any) => {
|
||||
clearTimeout(messageTimeout);
|
||||
try {
|
||||
expect(message).toBeTruthy();
|
||||
expect(message.type).toBe('state-response');
|
||||
expect(message.status).toBe('error');
|
||||
expect(message.externalBuildError).toEqual({
|
||||
errorCode: errors.ErrorCode.SERVER_ERROR,
|
||||
message: 'Internal service is unavailable.',
|
||||
});
|
||||
expect(message.internalErrorCode).toBe('GENERIC_SYSTEM_ERROR');
|
||||
} catch (err) {
|
||||
throw err;
|
||||
} finally {
|
||||
stateResponsePromiseResolve();
|
||||
}
|
||||
});
|
||||
const openPromise = helper.onOpen();
|
||||
helper.onMessage(onMessage);
|
||||
|
||||
await openPromise;
|
||||
messageTimeout = setTimeout(() => {
|
||||
unreachableCode('state-response timeout');
|
||||
}, 3000);
|
||||
|
||||
ws.send(JSON.stringify({ type: 'state-query', buildId }));
|
||||
|
||||
await stateResponsePromise;
|
||||
|
||||
expect(helper.onErrorCb).not.toHaveBeenCalled();
|
||||
expect(helper.onOpenCb).toHaveBeenCalled();
|
||||
expect(helper.onMessageCb).toHaveBeenCalled();
|
||||
expect(helper.onCloseCb).not.toHaveBeenCalled();
|
||||
ws.close();
|
||||
clearTimeout(messageTimeout);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Platform, Workflow } from '@expo/eas-build-job';
|
||||
|
||||
import config from '../config';
|
||||
import { getBuildEnv } from '../env';
|
||||
|
||||
describe(getBuildEnv.name, () => {
|
||||
const originalSentryDsn = config.sentry.dsn;
|
||||
|
||||
afterEach(() => {
|
||||
config.sentry.dsn = originalSentryDsn;
|
||||
});
|
||||
|
||||
it('passes the CLI sentry DSN to worker child processes', () => {
|
||||
config.sentry.dsn = 'https://[email protected]/1';
|
||||
|
||||
const env = getBuildEnv({
|
||||
job: {
|
||||
platform: Platform.ANDROID,
|
||||
type: Workflow.MANAGED,
|
||||
builderEnvironment: {
|
||||
env: {},
|
||||
},
|
||||
username: 'expo-user',
|
||||
} as any,
|
||||
projectId: 'project-id',
|
||||
metadata: {
|
||||
buildProfile: 'production',
|
||||
gitCommitHash: 'abc123',
|
||||
username: 'expo-user',
|
||||
} as any,
|
||||
buildId: 'build-id',
|
||||
});
|
||||
|
||||
expect(env.EAS_CLI_SENTRY_DSN).toBe(config.sentry.dsn);
|
||||
});
|
||||
});
|
||||
@@ -4,6 +4,47 @@ import { Response } from 'node-fetch';
|
||||
import fetch from 'node-fetch';
|
||||
import z from 'zod';
|
||||
|
||||
jest.mock('@expo/build-tools', () => {
|
||||
const actual = jest.requireActual('@expo/build-tools');
|
||||
const { Writable } = jest.requireActual('node:stream');
|
||||
|
||||
class MockGCSLoggerStream extends Writable {
|
||||
public static readonly CompressionMethod = actual.GCSLoggerStream.CompressionMethod;
|
||||
public static readonly instances: MockGCSLoggerStream[] = [];
|
||||
|
||||
public readonly writes: any[] = [];
|
||||
|
||||
constructor(...args: any[]) {
|
||||
super({ objectMode: true });
|
||||
void args;
|
||||
MockGCSLoggerStream.instances.push(this);
|
||||
}
|
||||
|
||||
public async init(): Promise<string> {
|
||||
return 'https://gcs.expo.test/logs';
|
||||
}
|
||||
|
||||
public async cleanUp(): Promise<void> {}
|
||||
|
||||
public _write(
|
||||
chunk: unknown,
|
||||
_encoding: BufferEncoding,
|
||||
callback: (error?: Error | null) => void
|
||||
): void {
|
||||
this.writes.push(chunk);
|
||||
callback(null);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...actual,
|
||||
GCS: {
|
||||
uploadWithSignedUrl: jest.fn(),
|
||||
},
|
||||
GCSLoggerStream: MockGCSLoggerStream,
|
||||
};
|
||||
});
|
||||
|
||||
import config from '../config';
|
||||
import { createBuildLoggerWithSecretsFilter } from '../logger';
|
||||
|
||||
@@ -24,19 +65,29 @@ async function waitForStreamFlush(): Promise<void> {
|
||||
}
|
||||
|
||||
const fetchMock = jest.mocked(fetch);
|
||||
const { GCSLoggerStream } = jest.requireMock('@expo/build-tools') as {
|
||||
GCSLoggerStream: {
|
||||
instances: Array<{
|
||||
writes: any[];
|
||||
}>;
|
||||
};
|
||||
};
|
||||
|
||||
describe('logger', () => {
|
||||
const originalHttpBaseUrl = config.loggers.http.baseUrl;
|
||||
const originalBuildId = config.buildId;
|
||||
const originalGcsSignedUploadUrlForLogs = config.loggers.gcs.signedUploadUrlForLogs;
|
||||
|
||||
beforeEach(() => {
|
||||
fetchMock.mockReset();
|
||||
fetchMock.mockResolvedValue(new Response('', { status: 200, statusText: 'OK' }));
|
||||
GCSLoggerStream.instances.length = 0;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
config.loggers.http.baseUrl = originalHttpBaseUrl;
|
||||
config.buildId = originalBuildId;
|
||||
config.loggers.gcs.signedUploadUrlForLogs = originalGcsSignedUploadUrlForLogs;
|
||||
});
|
||||
|
||||
it('obfuscates secrets in logs', async () => {
|
||||
@@ -227,6 +278,38 @@ describe('logger', () => {
|
||||
expect(secondAttemptOriginalLog).toEqual(firstAttemptOriginalLog);
|
||||
});
|
||||
|
||||
it('does not forward HTTP upload failures to the GCS build log stream', async () => {
|
||||
config.loggers.http.baseUrl = 'https://logs.expo.test/logs/';
|
||||
config.loggers.gcs.signedUploadUrlForLogs = {
|
||||
url: 'https://gcs.expo.test/logs',
|
||||
headers: {},
|
||||
};
|
||||
config.buildId = 'build-id';
|
||||
fetchMock.mockResolvedValue(
|
||||
new Response('upload failed', { status: 500, statusText: 'Internal Server Error' })
|
||||
);
|
||||
|
||||
const { logger, cleanUp } = await createBuildLoggerWithSecretsFilter({
|
||||
robotAccessToken: 'robot-token',
|
||||
});
|
||||
|
||||
logger.info('Actual build log');
|
||||
await cleanUp();
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledTimes(2);
|
||||
expect(GCSLoggerStream.instances).toHaveLength(1);
|
||||
|
||||
const gcsLogs = GCSLoggerStream.instances[0].writes;
|
||||
expect(gcsLogs).toEqual(
|
||||
expect.arrayContaining([expect.objectContaining({ msg: 'Actual build log' })])
|
||||
);
|
||||
expect(gcsLogs).not.toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ msg: 'Failed to send logs batch over HTTP' }),
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it('drains transformed logs even without explicit output consumer', async () => {
|
||||
const { logger, outputStream } = await createBuildLoggerWithSecretsFilter({});
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ function logBuildError(logger: bunyan, analytics: Analytics, err: Error): void {
|
||||
const l = logger.child({ phase: BuildPhase.FAIL_BUILD });
|
||||
l.info({ marker: LogMarker.START_PHASE }, `Start phase: ${BuildPhase.FAIL_BUILD}`);
|
||||
|
||||
if (err instanceof errors.BuildError) {
|
||||
if (err instanceof errors.ExpoError) {
|
||||
const internalErrorCode = err.trackingCode ?? err.errorCode;
|
||||
analytics.logEvent(Event.WORKER_BUILD_FAIL, {
|
||||
reason: err?.message,
|
||||
|
||||
@@ -45,7 +45,12 @@ export default {
|
||||
}),
|
||||
},
|
||||
http: {
|
||||
baseUrl: process.env.ENVIRONMENT === 'development' ? 'http://localhost:4999/logs/' : null,
|
||||
baseUrl:
|
||||
process.env.ENVIRONMENT === 'development'
|
||||
? 'http://localhost:4999/logs/'
|
||||
: process.env.ENVIRONMENT === 'staging'
|
||||
? 'https://staging-logs.expo.dev/logs/'
|
||||
: null,
|
||||
},
|
||||
},
|
||||
buildCache: {
|
||||
|
||||
@@ -36,6 +36,7 @@ export function getBuildEnv({
|
||||
setEnv(env, 'EAS_BUILD', 'true');
|
||||
setEnv(env, 'EAS_BUILD_RUNNER', 'eas-build');
|
||||
setEnv(env, 'EAS_BUILD_PLATFORM', job.platform);
|
||||
setEnv(env, 'EAS_CLI_SENTRY_DSN', config.sentry.dsn);
|
||||
// NPM_CACHE_URL is deprecated
|
||||
setEnv(env, 'NPM_CACHE_URL', config.npmCacheUrl);
|
||||
setEnv(env, 'NVM_NODEJS_ORG_MIRROR', config.nodeJsCacheUrl);
|
||||
|
||||
+1
-9
@@ -1,7 +1,6 @@
|
||||
import { bunyan } from '@expo/logger';
|
||||
import * as sentry from '@sentry/node';
|
||||
import Flatted from 'flatted';
|
||||
import { ZodError } from 'zod';
|
||||
|
||||
import { boomify } from '../utils/boom';
|
||||
|
||||
@@ -34,20 +33,13 @@ class Sentry {
|
||||
const l = options.logger ?? this.logger;
|
||||
const tags = { ...this.options.tags, ...options.tags };
|
||||
const extras = options.extras ?? {};
|
||||
|
||||
// TurtleErrors don't need the explanatory ${msg} prefix,
|
||||
// because their message should already be user-facing.
|
||||
// ZodError#message can't be modified, so we don't reassign either.
|
||||
if (err instanceof Error && !(err instanceof ZodError)) {
|
||||
err.message = `${msg}\n${err.message}`;
|
||||
}
|
||||
|
||||
const boom = err instanceof Error ? boomify(err) : err;
|
||||
l.error({ tags, extras, err: boom }, msg);
|
||||
|
||||
sentry.withScope((scope: sentry.Scope) => {
|
||||
scope.setTags(tags);
|
||||
scope.setExtras(extras);
|
||||
scope.setExtra('message', msg);
|
||||
if (options?.level) {
|
||||
scope.setLevel(options?.level);
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ export async function createBuildLoggerWithSecretsFilter(secrets: Job['secrets']
|
||||
headers: {
|
||||
Authorization: `Bearer ${secrets.robotAccessToken}`,
|
||||
},
|
||||
logger: buildLogger,
|
||||
logger: defaultLogger,
|
||||
bufferRetentionMs: config.loggers.gcs.signedUploadUrlForLogs
|
||||
? Math.max(30_000, config.loggers.gcs.uploadIntervalMs * 2)
|
||||
: null,
|
||||
|
||||
@@ -109,13 +109,13 @@ export default class BuildService {
|
||||
this.startBuildInternal({ job, metadata, initiatingUserId, projectId });
|
||||
}
|
||||
|
||||
public async finishError(err: errors.BuildError, artifacts: Artifacts | null): Promise<void> {
|
||||
public async finishError(err: errors.ExpoError, artifacts: Artifacts | null): Promise<void> {
|
||||
logger.error({ err }, 'Job finished with error');
|
||||
|
||||
this.state.finish(Worker.Status.ERROR, {
|
||||
applicationArchiveName: artifacts?.APPLICATION_ARCHIVE ?? null,
|
||||
buildArtifactsName: artifacts?.BUILD_ARTIFACTS ?? null,
|
||||
userError: err,
|
||||
expoError: err,
|
||||
});
|
||||
const isSocketClosed: boolean = !this.ws;
|
||||
// wait 5 seconds to make sure all logs are flushed
|
||||
@@ -127,7 +127,7 @@ export default class BuildService {
|
||||
type: WorkerMessage.MessageType.ERROR,
|
||||
applicationArchiveName: artifacts?.APPLICATION_ARCHIVE ?? null,
|
||||
buildArtifactsName: artifacts?.BUILD_ARTIFACTS ?? null,
|
||||
externalBuildError: err.format(),
|
||||
externalBuildError: err.toExternalExpoError(),
|
||||
internalErrorCode: err.trackingCode ?? err.errorCode,
|
||||
});
|
||||
}
|
||||
@@ -308,31 +308,36 @@ export default class BuildService {
|
||||
await this.finishSuccess(artifacts);
|
||||
} catch (error: any) {
|
||||
const maybeArtifacts = (error.artifacts as Artifacts | undefined) ?? null;
|
||||
const err = toBuildError(error, job);
|
||||
const maybeRawError = error instanceof errors.BuildError ? (error.cause ?? error) : error;
|
||||
const internalErrorCode = err.trackingCode ?? err.errorCode;
|
||||
const err =
|
||||
error instanceof errors.ExpoError
|
||||
? error
|
||||
: 'mode' in job && [BuildMode.CUSTOM, BuildMode.REPACK].includes(job.mode)
|
||||
? new errors.UnknownCustomBuildError()
|
||||
: new errors.UnknownBuildError();
|
||||
const maybeRawError =
|
||||
error instanceof errors.ExpoError && error.cause instanceof Error ? error.cause : error;
|
||||
|
||||
sentry.handleError(err.message, maybeRawError, {
|
||||
tags: {
|
||||
...(err.buildPhase ? { buildPhase: err.buildPhase } : {}),
|
||||
errorCode: internalErrorCode,
|
||||
errorCode: err.trackingCode ?? err.errorCode,
|
||||
...('type' in job ? { workflow: job.type } : {}),
|
||||
},
|
||||
extras: {
|
||||
buildId: this.buildId,
|
||||
...(err.metadata ? { errorMetadata: err.metadata } : {}),
|
||||
...(maybeRawError.stdout ? { stdout: getLastNLines(100, maybeRawError.stdout) } : {}),
|
||||
...(maybeRawError.stderr ? { stderr: getLastNLines(100, maybeRawError.stderr) } : {}),
|
||||
...(maybeRawError?.stdout ? { stdout: getLastNLines(100, maybeRawError.stdout) } : {}),
|
||||
...(maybeRawError?.stderr ? { stderr: getLastNLines(100, maybeRawError.stderr) } : {}),
|
||||
},
|
||||
});
|
||||
|
||||
const robotAccessToken = job.secrets?.robotAccessToken;
|
||||
if (robotAccessToken && err.errorCode === errors.ErrorCode.UNKNOWN_ERROR) {
|
||||
let rawErrorMessage: string = '';
|
||||
if (maybeRawError.stderr) {
|
||||
if (maybeRawError?.stderr) {
|
||||
rawErrorMessage += '\n' + getLastNLines(100, maybeRawError.stderr);
|
||||
}
|
||||
if (maybeRawError.stdout) {
|
||||
if (maybeRawError?.stdout) {
|
||||
rawErrorMessage += '\n' + getLastNLines(100, maybeRawError.stdout);
|
||||
}
|
||||
|
||||
@@ -369,25 +374,6 @@ export default class BuildService {
|
||||
}
|
||||
}
|
||||
|
||||
function toBuildError(error: unknown, job: Job): errors.BuildError {
|
||||
if (error instanceof errors.BuildError) {
|
||||
return error;
|
||||
}
|
||||
|
||||
if (error instanceof errors.UserError) {
|
||||
return new errors.BuildError(error.message, {
|
||||
errorCode: error.errorCode,
|
||||
docsUrl: error.docsUrl,
|
||||
cause: error.cause ?? error,
|
||||
metadata: error.metadata,
|
||||
});
|
||||
}
|
||||
|
||||
return 'mode' in job && [BuildMode.CUSTOM, BuildMode.REPACK].includes(job.mode)
|
||||
? new errors.UnknownCustomBuildError()
|
||||
: new errors.UnknownBuildError();
|
||||
}
|
||||
|
||||
function getLastNLines(numberOfLines: number, stream: string): string {
|
||||
const lines = stream.split('\n');
|
||||
if (lines.length <= numberOfLines) {
|
||||
|
||||
@@ -8,7 +8,7 @@ class State {
|
||||
private status: Worker.Status = Worker.Status.NEW;
|
||||
private applicationArchiveName: string | null = null;
|
||||
private buildArtifactsName: string | null = null;
|
||||
private userError?: errors.BuildError;
|
||||
private expoError?: errors.ExpoError;
|
||||
private abortReason?: LauncherMessage.AbortReason;
|
||||
private readonly shouldCloseWorker = false;
|
||||
|
||||
@@ -25,17 +25,17 @@ class State {
|
||||
{
|
||||
applicationArchiveName,
|
||||
buildArtifactsName,
|
||||
userError,
|
||||
expoError,
|
||||
}: {
|
||||
applicationArchiveName: string | null;
|
||||
buildArtifactsName: string | null;
|
||||
userError?: errors.BuildError;
|
||||
expoError?: errors.ExpoError;
|
||||
}
|
||||
): void {
|
||||
this.status = result;
|
||||
this.applicationArchiveName = applicationArchiveName;
|
||||
this.buildArtifactsName = buildArtifactsName;
|
||||
this.userError = userError;
|
||||
this.expoError = expoError;
|
||||
}
|
||||
|
||||
public setAbortReason(reason: LauncherMessage.AbortReason): void {
|
||||
@@ -48,9 +48,9 @@ class State {
|
||||
status: this.status,
|
||||
buildArtifactsName: this.buildArtifactsName ?? null,
|
||||
applicationArchiveName: this.applicationArchiveName ?? null,
|
||||
...(this.userError && {
|
||||
externalBuildError: this.userError.format(),
|
||||
internalErrorCode: this.userError.trackingCode ?? this.userError.errorCode,
|
||||
...(this.expoError && {
|
||||
externalBuildError: this.expoError.toExternalExpoError(),
|
||||
internalErrorCode: this.expoError.trackingCode ?? this.expoError.errorCode,
|
||||
}),
|
||||
abortReason: this.abortReason,
|
||||
};
|
||||
|
||||
@@ -21,7 +21,7 @@ export default class HttpLogStream extends Writable {
|
||||
private readonly buffer: BufferedLogEntry[] = [];
|
||||
private readonly url: string;
|
||||
private readonly headers: Record<string, string>;
|
||||
private readonly logger?: bunyan;
|
||||
private readonly logger: bunyan;
|
||||
private readonly maxBatchBytes: number;
|
||||
private readonly bufferRetentionMs: number | null;
|
||||
private inFlightRequest: Promise<void> | null = null;
|
||||
@@ -98,7 +98,7 @@ export default class HttpLogStream extends Writable {
|
||||
if (!isCleanup) {
|
||||
// Keep logs in memory if upload fails so a later flush can retry.
|
||||
this.buffer.unshift(...batch);
|
||||
this.logger?.error({ err }, 'Failed to send logs batch over HTTP');
|
||||
this.logger.error({ err }, 'Failed to send logs batch over HTTP');
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
|
||||
@@ -2700,22 +2700,22 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@expo/build-tools@npm:18.2.0, @expo/build-tools@workspace:packages/build-tools":
|
||||
"@expo/build-tools@npm:18.4.0, @expo/build-tools@workspace:packages/build-tools":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@expo/build-tools@workspace:packages/build-tools"
|
||||
dependencies:
|
||||
"@expo/config": "npm:10.0.6"
|
||||
"@expo/config-plugins": "npm:9.0.12"
|
||||
"@expo/downloader": "npm:18.0.1"
|
||||
"@expo/eas-build-job": "npm:18.2.0"
|
||||
"@expo/eas-build-job": "npm:18.4.0"
|
||||
"@expo/env": "npm:^0.4.0"
|
||||
"@expo/logger": "npm:18.0.1"
|
||||
"@expo/package-manager": "npm:1.9.10"
|
||||
"@expo/plist": "npm:^0.2.0"
|
||||
"@expo/repack-app": "npm:~0.2.5"
|
||||
"@expo/repack-app": "npm:~0.4.1"
|
||||
"@expo/results": "npm:^1.0.0"
|
||||
"@expo/spawn-async": "npm:1.7.2"
|
||||
"@expo/steps": "npm:18.2.0"
|
||||
"@expo/steps": "npm:18.4.0"
|
||||
"@expo/template-file": "npm:18.0.1"
|
||||
"@expo/turtle-spawn": "npm:18.0.1"
|
||||
"@expo/xcpretty": "npm:^4.3.1"
|
||||
@@ -2753,6 +2753,7 @@ __metadata:
|
||||
promise-retry: "npm:^2.0.1"
|
||||
resolve-from: "npm:^5.0.0"
|
||||
retry: "npm:^0.13.1"
|
||||
rimraf: "npm:3.0.2"
|
||||
semver: "npm:^7.6.2"
|
||||
tar: "npm:7.5.7"
|
||||
ts-jest: "npm:^29.1.4"
|
||||
@@ -2981,12 +2982,13 @@ __metadata:
|
||||
fs-extra: "npm:^11.2.0"
|
||||
got: "npm:11.8.5"
|
||||
jest: "npm:^29.7.0"
|
||||
rimraf: "npm:3.0.2"
|
||||
ts-jest: "npm:^29.1.4"
|
||||
typescript: "npm:^5.5.4"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@expo/eas-build-job@npm:18.2.0, @expo/eas-build-job@workspace:packages/eas-build-job":
|
||||
"@expo/eas-build-job@npm:18.4.0, @expo/eas-build-job@workspace:packages/eas-build-job":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@expo/eas-build-job@workspace:packages/eas-build-job"
|
||||
dependencies:
|
||||
@@ -2995,6 +2997,7 @@ __metadata:
|
||||
"@types/node": "npm:20.14.2"
|
||||
jest: "npm:^29.7.0"
|
||||
joi: "npm:^17.13.1"
|
||||
rimraf: "npm:3.0.2"
|
||||
semver: "npm:^7.6.2"
|
||||
ts-jest: "npm:^29.1.4"
|
||||
typescript: "npm:^5.5.4"
|
||||
@@ -3002,12 +3005,12 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@expo/eas-json@npm:18.2.0, @expo/eas-json@workspace:packages/eas-json":
|
||||
"@expo/eas-json@npm:18.4.0, @expo/eas-json@workspace:packages/eas-json":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@expo/eas-json@workspace:packages/eas-json"
|
||||
dependencies:
|
||||
"@babel/code-frame": "npm:7.23.5"
|
||||
"@expo/eas-build-job": "npm:18.2.0"
|
||||
"@expo/eas-build-job": "npm:18.4.0"
|
||||
"@tsconfig/node20": "npm:20.1.8"
|
||||
"@types/babel__code-frame": "npm:7.0.3"
|
||||
"@types/fs-extra": "npm:11.0.4"
|
||||
@@ -3143,6 +3146,7 @@ __metadata:
|
||||
"@types/node": "npm:20.14.2"
|
||||
bunyan: "npm:^1.8.15"
|
||||
jest: "npm:^29.7.0"
|
||||
rimraf: "npm:3.0.2"
|
||||
ts-jest: "npm:^29.1.4"
|
||||
typescript: "npm:^5.5.4"
|
||||
languageName: unknown
|
||||
@@ -3278,15 +3282,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@expo/repack-app@npm:~0.2.5":
|
||||
version: 0.2.16
|
||||
resolution: "@expo/repack-app@npm:0.2.16"
|
||||
"@expo/repack-app@npm:~0.4.1":
|
||||
version: 0.4.1
|
||||
resolution: "@expo/repack-app@npm:0.4.1"
|
||||
dependencies:
|
||||
commander: "npm:^13.1.0"
|
||||
commander: "npm:^14.0.2"
|
||||
picocolors: "npm:^1.1.1"
|
||||
bin:
|
||||
repack-app: bin/cli.js
|
||||
checksum: 10c0/7a53dd748563d95083f2445e3aec887adf973c5dbccb3cb117dfe46e36eed8bf1fb80da77071032ef4e933e73c50104170a770e351576c2170ff409411b25e46
|
||||
checksum: 10c0/4bc30a7ca95221f079026633802c32894ef25d55677212e86859194bf1a042598eaa5977510a277c3ceee6af6bd0d0a3aff3e21c4510c37b7dea584dd927ed0e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -3328,11 +3332,11 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@expo/steps@npm:18.2.0, @expo/steps@workspace:packages/steps":
|
||||
"@expo/steps@npm:18.4.0, @expo/steps@workspace:packages/steps":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@expo/steps@workspace:packages/steps"
|
||||
dependencies:
|
||||
"@expo/eas-build-job": "npm:18.2.0"
|
||||
"@expo/eas-build-job": "npm:18.4.0"
|
||||
"@expo/logger": "npm:18.0.1"
|
||||
"@expo/spawn-async": "npm:^1.7.2"
|
||||
"@jest/globals": "npm:^29.7.0"
|
||||
@@ -3348,6 +3352,7 @@ __metadata:
|
||||
jsep: "npm:^1.3.8"
|
||||
lodash.clonedeep: "npm:^4.5.0"
|
||||
lodash.get: "npm:^4.4.2"
|
||||
rimraf: "npm:3.0.2"
|
||||
ts-jest: "npm:^29.1.4"
|
||||
ts-mockito: "npm:^2.6.1"
|
||||
ts-node: "npm:^10.9.2"
|
||||
@@ -3367,6 +3372,7 @@ __metadata:
|
||||
"@types/node": "npm:20.14.2"
|
||||
jest: "npm:^29.7.0"
|
||||
lodash: "npm:^4.17.21"
|
||||
rimraf: "npm:3.0.2"
|
||||
ts-jest: "npm:^29.1.4"
|
||||
typescript: "npm:^5.5.4"
|
||||
languageName: unknown
|
||||
@@ -3388,6 +3394,7 @@ __metadata:
|
||||
"@types/jest": "npm:^29.5.12"
|
||||
"@types/node": "npm:20.14.2"
|
||||
jest: "npm:^29.7.0"
|
||||
rimraf: "npm:3.0.2"
|
||||
ts-jest: "npm:^29.1.4"
|
||||
typescript: "npm:^5.5.4"
|
||||
languageName: unknown
|
||||
@@ -3398,16 +3405,16 @@ __metadata:
|
||||
resolution: "@expo/worker@workspace:packages/worker"
|
||||
dependencies:
|
||||
"@babel/core": "npm:7.24.7"
|
||||
"@expo/build-tools": "npm:18.2.0"
|
||||
"@expo/build-tools": "npm:18.4.0"
|
||||
"@expo/config-plugins": "npm:8.0.8"
|
||||
"@expo/config-types": "npm:52.0.1"
|
||||
"@expo/downloader": "npm:18.0.1"
|
||||
"@expo/eas-build-job": "npm:18.2.0"
|
||||
"@expo/eas-build-job": "npm:18.4.0"
|
||||
"@expo/logger": "npm:18.0.1"
|
||||
"@expo/results": "npm:1.0.0"
|
||||
"@expo/rudder-sdk-node": "npm:1.1.1"
|
||||
"@expo/spawn-async": "npm:1.7.2"
|
||||
"@expo/steps": "npm:18.2.0"
|
||||
"@expo/steps": "npm:18.4.0"
|
||||
"@expo/template-file": "npm:18.0.1"
|
||||
"@expo/turtle-spawn": "npm:18.0.1"
|
||||
"@hapi/boom": "npm:10.0.1"
|
||||
@@ -3441,6 +3448,7 @@ __metadata:
|
||||
node-fetch: "npm:2.7.0"
|
||||
node-os-utils: "npm:1.3.7"
|
||||
nullthrows: "npm:1.1.1"
|
||||
rimraf: "npm:3.0.2"
|
||||
semver: "npm:7.6.3"
|
||||
supertest: "npm:7.2.2"
|
||||
tar: "npm:7.5.7"
|
||||
@@ -9834,10 +9842,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"commander@npm:^13.1.0":
|
||||
version: 13.1.0
|
||||
resolution: "commander@npm:13.1.0"
|
||||
checksum: 10c0/7b8c5544bba704fbe84b7cab2e043df8586d5c114a4c5b607f83ae5060708940ed0b5bd5838cf8ce27539cde265c1cbd59ce3c8c6b017ed3eec8943e3a415164
|
||||
"commander@npm:^14.0.2":
|
||||
version: 14.0.3
|
||||
resolution: "commander@npm:14.0.3"
|
||||
checksum: 10c0/755652564bbf56ff2ff083313912b326450d3f8d8c85f4b71416539c9a05c3c67dbd206821ca72635bf6b160e2afdefcb458e86b317827d5cb333b69ce7f1a24
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -10164,7 +10172,7 @@ __metadata:
|
||||
resolution: "create-eas-build-function@workspace:packages/create-eas-build-function"
|
||||
dependencies:
|
||||
"@expo/package-manager": "npm:1.9.10"
|
||||
"@expo/steps": "npm:18.2.0"
|
||||
"@expo/steps": "npm:18.4.0"
|
||||
"@jest/globals": "npm:^29.7.0"
|
||||
"@types/jest": "npm:^29.5.12"
|
||||
"@types/node": "npm:20.14.2"
|
||||
@@ -10777,8 +10785,8 @@ __metadata:
|
||||
version: 0.0.0-use.local
|
||||
resolution: "eas-cli-local-build-plugin@workspace:packages/local-build-plugin"
|
||||
dependencies:
|
||||
"@expo/build-tools": "npm:18.2.0"
|
||||
"@expo/eas-build-job": "npm:18.2.0"
|
||||
"@expo/build-tools": "npm:18.4.0"
|
||||
"@expo/eas-build-job": "npm:18.4.0"
|
||||
"@expo/logger": "npm:18.0.1"
|
||||
"@expo/spawn-async": "npm:^1.7.2"
|
||||
"@expo/turtle-spawn": "npm:18.0.1"
|
||||
@@ -10796,6 +10804,7 @@ __metadata:
|
||||
joi: "npm:^17.13.1"
|
||||
lodash: "npm:^4.17.21"
|
||||
nullthrows: "npm:^1.1.1"
|
||||
rimraf: "npm:3.0.2"
|
||||
semver: "npm:^7.6.2"
|
||||
tar: "npm:7.5.7"
|
||||
ts-jest: "npm:^29.1.4"
|
||||
@@ -10820,6 +10829,7 @@ __metadata:
|
||||
oxfmt: "npm:0.28.0"
|
||||
oxlint: "npm:1.43.0"
|
||||
oxlint-tsgolint: "npm:0.11.1"
|
||||
rimraf: "npm:3.0.2"
|
||||
ts-jest: "npm:29.1.1"
|
||||
typescript: "npm:5.5.4"
|
||||
languageName: unknown
|
||||
@@ -10833,8 +10843,8 @@ __metadata:
|
||||
"@expo/code-signing-certificates": "npm:0.0.5"
|
||||
"@expo/config": "npm:10.0.6"
|
||||
"@expo/config-plugins": "npm:9.0.12"
|
||||
"@expo/eas-build-job": "npm:18.2.0"
|
||||
"@expo/eas-json": "npm:18.2.0"
|
||||
"@expo/eas-build-job": "npm:18.4.0"
|
||||
"@expo/eas-json": "npm:18.4.0"
|
||||
"@expo/env": "npm:^1.0.0"
|
||||
"@expo/json-file": "npm:8.3.3"
|
||||
"@expo/logger": "npm:18.0.1"
|
||||
@@ -10849,7 +10859,7 @@ __metadata:
|
||||
"@expo/results": "npm:1.0.0"
|
||||
"@expo/rudder-sdk-node": "npm:1.1.1"
|
||||
"@expo/spawn-async": "npm:1.7.2"
|
||||
"@expo/steps": "npm:18.2.0"
|
||||
"@expo/steps": "npm:18.4.0"
|
||||
"@expo/timeago.js": "npm:1.0.0"
|
||||
"@graphql-codegen/cli": "npm:5.0.0"
|
||||
"@graphql-codegen/introspection": "npm:4.0.0"
|
||||
@@ -10858,6 +10868,7 @@ __metadata:
|
||||
"@oclif/core": "npm:^4.8.3"
|
||||
"@oclif/plugin-autocomplete": "npm:^3.2.40"
|
||||
"@segment/ajv-human-errors": "npm:^2.1.2"
|
||||
"@sentry/node": "npm:7.77.0"
|
||||
"@tsconfig/node20": "npm:20.1.8"
|
||||
"@types/cli-progress": "npm:3.11.5"
|
||||
"@types/dateformat": "npm:3.0.1"
|
||||
|
||||
Reference in new issue
Block a user