mirror of
https://github.com/expo/eas-cli.git
synced 2026-11-13 20:28:08 -10:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ca33ad191 | ||
|
|
14d5cb1ac5 |
No files matched your search
+2
-11
@@ -8,21 +8,12 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- [build-tools] Auto-upload embedded bundle after build when `EAS_UPDATE_EXPERIMENTAL_UPLOAD_EMBEDDED_BUNDLE` is set. ([#3767](https://github.com/expo/eas-cli/pull/3767) by [@gwdp](https://github.com/gwdp))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- [expo-cocoapods-proxy] Fix iOS worker tarball build failing on macOS Tahoe due to bundler incompatibility with RubyGems 4. ([#3824](https://github.com/expo/eas-cli/pull/3824) by [@gwdp](https://github.com/gwdp))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
## [20.1.0](https://github.com/expo/eas-cli/releases/tag/v20.1.0) - 2026-06-05
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- [eas-cli] Add `eas update:embedded:view` command. ([#3810](https://github.com/expo/eas-cli/pull/3810) by [@gwdp](https://github.com/gwdp))
|
||||
- [eas-cli] Add `eas update:embedded:list` command. ([#3811](https://github.com/expo/eas-cli/pull/3811) by [@gwdp](https://github.com/gwdp))
|
||||
- [eas-cli] Add `eas update:embedded:delete` command. ([#3809](https://github.com/expo/eas-cli/pull/3809) by [@gwdp](https://github.com/gwdp))
|
||||
- [build-tools] Auto-upload embedded bundle after build when `EAS_UPDATE_EXPERIMENTAL_UPLOAD_EMBEDDED_BUNDLE` is set. ([#3767](https://github.com/expo/eas-cli/pull/3767) by [@gwdp](https://github.com/gwdp))
|
||||
|
||||
## [20.0.0](https://github.com/expo/eas-cli/releases/tag/v20.0.0) - 2026-05-29
|
||||
|
||||
### 🛠 Breaking changes
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"npmClient": "yarn",
|
||||
"version": "20.1.0",
|
||||
"version": "20.0.0",
|
||||
"$schema": "node_modules/lerna/schemas/lerna-schema.json"
|
||||
}
|
||||
@@ -3,6 +3,4 @@ _.path = "./tools/bin"
|
||||
MISE_NODE_COREPACK = "true"
|
||||
|
||||
[tools]
|
||||
gcloud = "570.0.0"
|
||||
jq = "1.8.1"
|
||||
node = "20.19.4"
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@expo/build-tools",
|
||||
"version": "20.1.0",
|
||||
"version": "20.0.0",
|
||||
"bugs": "https://github.com/expo/eas-cli/issues",
|
||||
"license": "BUSL-1.1",
|
||||
"author": "Expo <[email protected]>",
|
||||
@@ -38,14 +38,14 @@
|
||||
"@expo/config": "55.0.10",
|
||||
"@expo/config-plugins": "55.0.7",
|
||||
"@expo/downloader": "20.0.0",
|
||||
"@expo/eas-build-job": "20.1.0",
|
||||
"@expo/eas-build-job": "20.0.0",
|
||||
"@expo/env": "^0.4.0",
|
||||
"@expo/logger": "20.0.0",
|
||||
"@expo/package-manager": "1.9.10",
|
||||
"@expo/plist": "^0.2.0",
|
||||
"@expo/results": "^1.0.0",
|
||||
"@expo/spawn-async": "1.7.2",
|
||||
"@expo/steps": "20.1.0",
|
||||
"@expo/steps": "20.0.0",
|
||||
"@expo/template-file": "20.0.0",
|
||||
"@expo/turtle-spawn": "20.0.0",
|
||||
"@expo/xcpretty": "^4.3.1",
|
||||
@@ -77,7 +77,7 @@
|
||||
"zod": "^4.3.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@expo/repack-app": "~0.6.1",
|
||||
"@expo/repack-app": "~0.4.1",
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/lodash": "^4.17.4",
|
||||
|
||||
@@ -22,13 +22,13 @@ describe('Datadog singleton', () => {
|
||||
Datadog.setup(null);
|
||||
});
|
||||
|
||||
it('POSTs distribution metrics to the turtle build metrics endpoint for build targets', () => {
|
||||
it('POSTs distribution metrics to the turtle build metrics endpoint', () => {
|
||||
turtleFetchMock.mockResolvedValueOnce({} as Response);
|
||||
|
||||
Datadog.setup({
|
||||
expoApiV2BaseUrl: 'https://api.expo.test/v2/',
|
||||
turtleBuildId: 'build-id',
|
||||
robotAccessToken: 'token-abc',
|
||||
target: { kind: 'build', turtleBuildId: 'build-id' },
|
||||
});
|
||||
|
||||
Datadog.distribution('eas.build.phase_duration', 1234, {
|
||||
@@ -51,7 +51,6 @@ describe('Datadog singleton', () => {
|
||||
build_phase: 'install_dependencies',
|
||||
platform: 'ios',
|
||||
result: 'success',
|
||||
target: 'build',
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -62,49 +61,13 @@ describe('Datadog singleton', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('POSTs distribution metrics to the turtle job run metrics endpoint for jobRun targets', () => {
|
||||
it('POSTs logs to the turtle build logs endpoint', () => {
|
||||
turtleFetchMock.mockResolvedValueOnce({} as Response);
|
||||
|
||||
Datadog.setup({
|
||||
expoApiV2BaseUrl: 'https://api.expo.test/v2/',
|
||||
turtleBuildId: 'build-id',
|
||||
robotAccessToken: 'token-abc',
|
||||
target: { kind: 'jobRun', turtleJobRunId: 'job-run-id' },
|
||||
});
|
||||
|
||||
Datadog.distribution('eas.maestro.install', 1, {
|
||||
maestro_version: '2.1.0',
|
||||
});
|
||||
|
||||
expect(turtleFetchMock).toHaveBeenCalledWith(
|
||||
'https://api.expo.test/v2/turtle-job-runs/job-run-id/metrics',
|
||||
'POST',
|
||||
{
|
||||
json: {
|
||||
metrics: [
|
||||
{
|
||||
name: 'eas.maestro.install',
|
||||
type: 'distribution',
|
||||
value: 1,
|
||||
tags: {
|
||||
maestro_version: '2.1.0',
|
||||
target: 'job_run',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
headers: { Authorization: 'Bearer token-abc' },
|
||||
retries: 2,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('POSTs logs to the turtle build logs endpoint with buildId for build targets', () => {
|
||||
turtleFetchMock.mockResolvedValueOnce({} as Response);
|
||||
|
||||
Datadog.setup({
|
||||
expoApiV2BaseUrl: 'https://api.expo.test/v2/',
|
||||
robotAccessToken: 'token-abc',
|
||||
target: { kind: 'build', turtleBuildId: 'build-id' },
|
||||
});
|
||||
|
||||
Datadog.log('artifact dry-run matched', {
|
||||
@@ -122,38 +85,6 @@ describe('Datadog singleton', () => {
|
||||
tags: {
|
||||
event: 'find_artifacts_absolute_path_dry_run',
|
||||
status: 'match',
|
||||
target: 'build',
|
||||
},
|
||||
},
|
||||
headers: { Authorization: 'Bearer token-abc' },
|
||||
shouldThrowOnNotOk: false,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('POSTs logs to the turtle job run logs endpoint with jobRunId for jobRun targets', () => {
|
||||
turtleFetchMock.mockResolvedValueOnce({} as Response);
|
||||
|
||||
Datadog.setup({
|
||||
expoApiV2BaseUrl: 'https://api.expo.test/v2/',
|
||||
robotAccessToken: 'token-abc',
|
||||
target: { kind: 'jobRun', turtleJobRunId: 'job-run-id' },
|
||||
});
|
||||
|
||||
Datadog.log('Gradle cache restored (hit)', {
|
||||
cache_type: 'gradle',
|
||||
});
|
||||
|
||||
expect(turtleFetchMock).toHaveBeenCalledWith(
|
||||
'https://api.expo.test/v2/turtle-job-runs/logs',
|
||||
'POST',
|
||||
{
|
||||
json: {
|
||||
jobRunId: 'job-run-id',
|
||||
message: 'Gradle cache restored (hit)',
|
||||
tags: {
|
||||
cache_type: 'gradle',
|
||||
target: 'job_run',
|
||||
},
|
||||
},
|
||||
headers: { Authorization: 'Bearer token-abc' },
|
||||
@@ -171,12 +102,12 @@ describe('Datadog singleton', () => {
|
||||
expect(turtleFetchMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('swallows metric upload failures for build targets', async () => {
|
||||
it('swallows upload failures', async () => {
|
||||
turtleFetchMock.mockRejectedValueOnce(new Error('network down'));
|
||||
Datadog.setup({
|
||||
expoApiV2BaseUrl: 'https://api.expo.test/v2/',
|
||||
turtleBuildId: 'build-id',
|
||||
robotAccessToken: 'token-abc',
|
||||
target: { kind: 'build', turtleBuildId: 'build-id' },
|
||||
});
|
||||
|
||||
Datadog.distribution('eas.build.phase_duration', 1);
|
||||
@@ -188,54 +119,18 @@ describe('Datadog singleton', () => {
|
||||
expect.any(Error),
|
||||
expect.objectContaining({
|
||||
extras: {
|
||||
metrics: [
|
||||
{
|
||||
name: 'eas.build.phase_duration',
|
||||
type: 'distribution',
|
||||
value: 1,
|
||||
tags: { target: 'build' },
|
||||
},
|
||||
],
|
||||
metrics: [{ name: 'eas.build.phase_duration', type: 'distribution', value: 1, tags: {} }],
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('labels metric upload failures for jobRun targets with the jobRun kind', async () => {
|
||||
it('swallows log upload failures', async () => {
|
||||
turtleFetchMock.mockRejectedValueOnce(new Error('network down'));
|
||||
Datadog.setup({
|
||||
expoApiV2BaseUrl: 'https://api.expo.test/v2/',
|
||||
turtleBuildId: 'build-id',
|
||||
robotAccessToken: 'token-abc',
|
||||
target: { kind: 'jobRun', turtleJobRunId: 'job-run-id' },
|
||||
});
|
||||
|
||||
Datadog.distribution('eas.maestro.install', 1);
|
||||
await flushPromises();
|
||||
|
||||
expect(sentryCaptureMock).toHaveBeenCalledWith(
|
||||
'Failed to report turtle jobRun metric',
|
||||
expect.any(Error),
|
||||
expect.objectContaining({
|
||||
extras: {
|
||||
metrics: [
|
||||
{
|
||||
name: 'eas.maestro.install',
|
||||
type: 'distribution',
|
||||
value: 1,
|
||||
tags: { target: 'job_run' },
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('swallows log upload failures for build targets', async () => {
|
||||
turtleFetchMock.mockRejectedValueOnce(new Error('network down'));
|
||||
Datadog.setup({
|
||||
expoApiV2BaseUrl: 'https://api.expo.test/v2/',
|
||||
robotAccessToken: 'token-abc',
|
||||
target: { kind: 'build', turtleBuildId: 'build-id' },
|
||||
});
|
||||
|
||||
Datadog.log('artifact dry-run matched');
|
||||
@@ -247,37 +142,7 @@ describe('Datadog singleton', () => {
|
||||
expect.any(Error),
|
||||
expect.objectContaining({
|
||||
extras: {
|
||||
log: {
|
||||
buildId: 'build-id',
|
||||
message: 'artifact dry-run matched',
|
||||
tags: { target: 'build' },
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('labels log upload failures for jobRun targets with the jobRun kind', async () => {
|
||||
turtleFetchMock.mockRejectedValueOnce(new Error('network down'));
|
||||
Datadog.setup({
|
||||
expoApiV2BaseUrl: 'https://api.expo.test/v2/',
|
||||
robotAccessToken: 'token-abc',
|
||||
target: { kind: 'jobRun', turtleJobRunId: 'job-run-id' },
|
||||
});
|
||||
|
||||
Datadog.log('Gradle cache restored (hit)');
|
||||
await flushPromises();
|
||||
|
||||
expect(sentryCaptureMock).toHaveBeenCalledWith(
|
||||
'Failed to report turtle jobRun log',
|
||||
expect.any(Error),
|
||||
expect.objectContaining({
|
||||
extras: {
|
||||
log: {
|
||||
jobRunId: 'job-run-id',
|
||||
message: 'Gradle cache restored (hit)',
|
||||
tags: { target: 'job_run' },
|
||||
},
|
||||
log: { buildId: 'build-id', message: 'artifact dry-run matched', tags: {} },
|
||||
},
|
||||
})
|
||||
);
|
||||
@@ -288,13 +153,13 @@ describe('Datadog singleton', () => {
|
||||
|
||||
Datadog.setup({
|
||||
expoApiV2BaseUrl: 'https://api.expo.test/v2/',
|
||||
turtleBuildId: 'first-build-id',
|
||||
robotAccessToken: 'first-token',
|
||||
target: { kind: 'build', turtleBuildId: 'first-build-id' },
|
||||
});
|
||||
Datadog.setup({
|
||||
expoApiV2BaseUrl: 'https://api.expo.test/v2/',
|
||||
turtleBuildId: 'second-build-id',
|
||||
robotAccessToken: 'second-token',
|
||||
target: { kind: 'jobRun', turtleJobRunId: 'job-run-id' },
|
||||
});
|
||||
|
||||
Datadog.distribution('eas.build.phase_duration', 1);
|
||||
@@ -302,7 +167,7 @@ describe('Datadog singleton', () => {
|
||||
|
||||
expect(turtleFetchMock).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'https://api.expo.test/v2/turtle-job-runs/job-run-id/metrics',
|
||||
'https://api.expo.test/v2/turtle-builds/second-build-id/metrics',
|
||||
'POST',
|
||||
expect.objectContaining({
|
||||
headers: { Authorization: 'Bearer second-token' },
|
||||
@@ -310,7 +175,7 @@ describe('Datadog singleton', () => {
|
||||
);
|
||||
expect(turtleFetchMock).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
'https://api.expo.test/v2/turtle-job-runs/logs',
|
||||
'https://api.expo.test/v2/turtle-builds/logs',
|
||||
'POST',
|
||||
expect.objectContaining({
|
||||
headers: { Authorization: 'Bearer second-token' },
|
||||
@@ -327,8 +192,8 @@ describe('Datadog singleton', () => {
|
||||
);
|
||||
Datadog.setup({
|
||||
expoApiV2BaseUrl: 'https://api.expo.test/v2/',
|
||||
turtleBuildId: 'build-id',
|
||||
robotAccessToken: 'token-abc',
|
||||
target: { kind: 'build', turtleBuildId: 'build-id' },
|
||||
});
|
||||
|
||||
let flushed = false;
|
||||
@@ -354,8 +219,8 @@ describe('Datadog singleton', () => {
|
||||
);
|
||||
Datadog.setup({
|
||||
expoApiV2BaseUrl: 'https://api.expo.test/v2/',
|
||||
turtleBuildId: 'build-id',
|
||||
robotAccessToken: 'token-abc',
|
||||
target: { kind: 'build', turtleBuildId: 'build-id' },
|
||||
});
|
||||
|
||||
let flushed = false;
|
||||
@@ -371,35 +236,6 @@ describe('Datadog singleton', () => {
|
||||
|
||||
expect(flushed).toBe(true);
|
||||
});
|
||||
|
||||
it('only awaits uploads enqueued since the previous flush', async () => {
|
||||
let resolveSecondUpload!: (response: Response) => void;
|
||||
turtleFetchMock.mockReturnValueOnce(new Promise<Response>(() => {})).mockReturnValueOnce(
|
||||
new Promise<Response>(resolve => {
|
||||
resolveSecondUpload = resolve;
|
||||
})
|
||||
);
|
||||
Datadog.setup({
|
||||
expoApiV2BaseUrl: 'https://api.expo.test/v2/',
|
||||
robotAccessToken: 'token-abc',
|
||||
target: { kind: 'build', turtleBuildId: 'build-id' },
|
||||
});
|
||||
|
||||
// The first upload never settles; the first flush rotates it out of the queue.
|
||||
Datadog.distribution('eas.build.phase_duration', 1);
|
||||
void Datadog.flushAsync();
|
||||
|
||||
Datadog.distribution('eas.build.phase_duration', 2);
|
||||
let flushed = false;
|
||||
const flushPromise = Datadog.flushAsync().then(() => {
|
||||
flushed = true;
|
||||
});
|
||||
|
||||
resolveSecondUpload({} as Response);
|
||||
await flushPromise;
|
||||
|
||||
expect(flushed).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
async function flushPromises(): Promise<void> {
|
||||
|
||||
@@ -1,317 +0,0 @@
|
||||
import fetch from 'node-fetch';
|
||||
|
||||
import { RuntimeSettings } from '../runtimeSettings';
|
||||
import { Sentry } from '../sentry';
|
||||
|
||||
jest.mock('node-fetch', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(),
|
||||
}));
|
||||
jest.mock('../sentry', () => ({
|
||||
Sentry: {
|
||||
capture: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('runtimeSettings', () => {
|
||||
const logger = {
|
||||
info: jest.fn(),
|
||||
warn: jest.fn(),
|
||||
};
|
||||
const originalCacheUrls = {
|
||||
EAS_NPM_CACHE_URL: process.env.EAS_NPM_CACHE_URL,
|
||||
EAS_NODEJS_CACHE_URL: process.env.EAS_NODEJS_CACHE_URL,
|
||||
EAS_MAVEN_CACHE_URL: process.env.EAS_MAVEN_CACHE_URL,
|
||||
EAS_COCOAPODS_CACHE_URL: process.env.EAS_COCOAPODS_CACHE_URL,
|
||||
};
|
||||
const originalPlatform = process.platform;
|
||||
|
||||
afterEach(() => {
|
||||
jest.mocked(fetch).mockReset();
|
||||
jest.restoreAllMocks();
|
||||
jest.mocked(Sentry.capture).mockReset();
|
||||
logger.info.mockReset();
|
||||
logger.warn.mockReset();
|
||||
restoreEnv('EAS_NPM_CACHE_URL', originalCacheUrls.EAS_NPM_CACHE_URL);
|
||||
restoreEnv('EAS_NODEJS_CACHE_URL', originalCacheUrls.EAS_NODEJS_CACHE_URL);
|
||||
restoreEnv('EAS_MAVEN_CACHE_URL', originalCacheUrls.EAS_MAVEN_CACHE_URL);
|
||||
restoreEnv('EAS_COCOAPODS_CACHE_URL', originalCacheUrls.EAS_COCOAPODS_CACHE_URL);
|
||||
mockProcessPlatform(originalPlatform);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.mocked(fetch).mockResolvedValue(response({}));
|
||||
await RuntimeSettings.loadAsync({ environment: 'staging', logger: logger as any });
|
||||
jest.mocked(fetch).mockReset();
|
||||
logger.info.mockReset();
|
||||
logger.warn.mockReset();
|
||||
});
|
||||
|
||||
it('fetches runtime settings from the staging and production buckets', async () => {
|
||||
jest.mocked(fetch).mockResolvedValue(response({}));
|
||||
|
||||
await RuntimeSettings.loadAsync({
|
||||
environment: 'staging',
|
||||
logger: logger as any,
|
||||
cacheUrlFallbacks: {
|
||||
npm: 'https://fallback-npm.example',
|
||||
},
|
||||
});
|
||||
await RuntimeSettings.loadAsync({ environment: 'production', logger: logger as any });
|
||||
await RuntimeSettings.loadAsync({ environment: 'test', logger: logger as any });
|
||||
|
||||
expect(fetch).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'https://storage.googleapis.com/eas-workflows-staging/runtime-settings.json',
|
||||
{
|
||||
signal: expect.anything(),
|
||||
}
|
||||
);
|
||||
expect(fetch).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
'https://storage.googleapis.com/eas-workflows-production/runtime-settings.json',
|
||||
{
|
||||
signal: expect.anything(),
|
||||
}
|
||||
);
|
||||
expect(fetch).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('does not use caches by default when remote settings are unavailable', async () => {
|
||||
process.env.EAS_NPM_CACHE_URL = 'https://npm.example';
|
||||
jest.mocked(fetch).mockResolvedValue(response({}, 404));
|
||||
|
||||
await RuntimeSettings.loadAsync({ environment: 'staging', logger: logger as any });
|
||||
|
||||
mockProcessPlatform('linux');
|
||||
expect(RuntimeSettings.getNpmCacheUrl()).toBeNull();
|
||||
expect(RuntimeSettings.isUsingIosPrecompiledModulesEnabled()).toBe(false);
|
||||
expect(logger.warn).toHaveBeenCalled();
|
||||
expect(Sentry.capture).toHaveBeenCalledWith('Failed to fetch worker runtime settings', {
|
||||
extras: {
|
||||
status: 404,
|
||||
url: 'https://storage.googleapis.com/eas-workflows-staging/runtime-settings.json',
|
||||
},
|
||||
level: 'warning',
|
||||
});
|
||||
});
|
||||
|
||||
it('does not override existing settings when remote settings are invalid', async () => {
|
||||
process.env.EAS_NPM_CACHE_URL = 'https://npm.example';
|
||||
jest
|
||||
.mocked(fetch)
|
||||
.mockResolvedValueOnce(response({ caches: { linux: { npm: true } } }))
|
||||
.mockResolvedValueOnce(response({ iosPrecompiledModules: 'enabled' }));
|
||||
|
||||
await RuntimeSettings.loadAsync({ environment: 'staging', logger: logger as any });
|
||||
await RuntimeSettings.loadAsync({ environment: 'staging', logger: logger as any });
|
||||
|
||||
mockProcessPlatform('linux');
|
||||
expect(RuntimeSettings.getNpmCacheUrl()).toBe('https://npm.example');
|
||||
expect(RuntimeSettings.isUsingIosPrecompiledModulesEnabled()).toBe(false);
|
||||
expect(logger.warn).toHaveBeenCalled();
|
||||
expect(Sentry.capture).toHaveBeenCalledWith(
|
||||
'Failed to load worker runtime settings',
|
||||
expect.any(Error),
|
||||
{
|
||||
extras: {
|
||||
url: 'https://storage.googleapis.com/eas-workflows-staging/runtime-settings.json',
|
||||
},
|
||||
level: 'warning',
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('accepts partial runtime settings and ignores unknown fields', async () => {
|
||||
process.env.EAS_NPM_CACHE_URL = 'https://npm.example';
|
||||
process.env.EAS_NODEJS_CACHE_URL = 'https://node.example';
|
||||
process.env.EAS_MAVEN_CACHE_URL = 'https://maven.example';
|
||||
process.env.EAS_COCOAPODS_CACHE_URL = 'https://pods.example';
|
||||
jest.mocked(fetch).mockResolvedValue(
|
||||
response({
|
||||
caches: {
|
||||
linux: { npm: false, nodejs: true, maven: true },
|
||||
darwin: { npm: true, maven: true, cocoapods: false },
|
||||
},
|
||||
iosPrecompiledModules: true,
|
||||
unknownFutureSetting: true,
|
||||
})
|
||||
);
|
||||
|
||||
await RuntimeSettings.loadAsync({ environment: 'staging', logger: logger as any });
|
||||
|
||||
mockProcessPlatform('linux');
|
||||
expect(RuntimeSettings.getNpmCacheUrl()).toBeNull();
|
||||
expect(RuntimeSettings.getNodeJsCacheUrl()).toBe('https://node.example');
|
||||
expect(RuntimeSettings.getMavenCacheUrl()).toBe('https://maven.example');
|
||||
mockProcessPlatform('darwin');
|
||||
expect(RuntimeSettings.getMavenCacheUrl()).toBe('https://maven.example');
|
||||
expect(RuntimeSettings.getCocoapodsCacheUrl()).toBeNull();
|
||||
expect(RuntimeSettings.getNpmCacheUrl()).toBe('https://npm.example');
|
||||
expect(RuntimeSettings.isUsingIosPrecompiledModulesEnabled()).toBe(true);
|
||||
});
|
||||
|
||||
it('uses Maven and CocoaPods caches when enabled for the current platform and their environment variables exist', async () => {
|
||||
process.env.EAS_MAVEN_CACHE_URL = 'https://maven.example';
|
||||
process.env.EAS_COCOAPODS_CACHE_URL = 'https://pods.example';
|
||||
jest.mocked(fetch).mockResolvedValue(
|
||||
response({
|
||||
caches: {
|
||||
linux: { maven: true },
|
||||
darwin: { maven: true, cocoapods: true },
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
await RuntimeSettings.loadAsync({ environment: 'staging', logger: logger as any });
|
||||
|
||||
mockProcessPlatform('linux');
|
||||
expect(RuntimeSettings.getMavenCacheUrl()).toBe('https://maven.example');
|
||||
mockProcessPlatform('darwin');
|
||||
expect(RuntimeSettings.getMavenCacheUrl()).toBe('https://maven.example');
|
||||
expect(RuntimeSettings.getCocoapodsCacheUrl()).toBe('https://pods.example');
|
||||
});
|
||||
|
||||
it('uses cache URL fallbacks when enabled cache environment variables are not set', async () => {
|
||||
jest.mocked(fetch).mockResolvedValue(
|
||||
response({
|
||||
caches: {
|
||||
linux: { npm: true, nodejs: true, maven: true },
|
||||
darwin: { cocoapods: true },
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
await RuntimeSettings.loadAsync({
|
||||
environment: 'staging',
|
||||
logger: logger as any,
|
||||
cacheUrlFallbacks: {
|
||||
npm: 'https://fallback-npm.example',
|
||||
nodejs: 'https://fallback-node.example',
|
||||
maven: 'https://fallback-maven.example',
|
||||
cocoapods: 'https://fallback-pods.example',
|
||||
},
|
||||
});
|
||||
|
||||
mockProcessPlatform('linux');
|
||||
expect(RuntimeSettings.getNpmCacheUrl()).toBe('https://fallback-npm.example');
|
||||
expect(RuntimeSettings.getNodeJsCacheUrl()).toBe('https://fallback-node.example');
|
||||
expect(RuntimeSettings.getMavenCacheUrl()).toBe('https://fallback-maven.example');
|
||||
mockProcessPlatform('darwin');
|
||||
expect(RuntimeSettings.getCocoapodsCacheUrl()).toBe('https://fallback-pods.example');
|
||||
});
|
||||
|
||||
it('prefers cache URL environment variables over fallbacks', async () => {
|
||||
process.env.EAS_NPM_CACHE_URL = 'https://npm.example';
|
||||
jest.mocked(fetch).mockResolvedValue(
|
||||
response({
|
||||
caches: {
|
||||
linux: { npm: true },
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
await RuntimeSettings.loadAsync({
|
||||
environment: 'staging',
|
||||
logger: logger as any,
|
||||
cacheUrlFallbacks: {
|
||||
npm: 'https://fallback-npm.example',
|
||||
},
|
||||
});
|
||||
|
||||
mockProcessPlatform('linux');
|
||||
expect(RuntimeSettings.getNpmCacheUrl()).toBe('https://npm.example');
|
||||
});
|
||||
|
||||
it('does not use cache URL fallbacks when runtime settings disable caches', async () => {
|
||||
jest.mocked(fetch).mockResolvedValue(
|
||||
response({
|
||||
caches: {
|
||||
linux: { npm: false },
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
await RuntimeSettings.loadAsync({
|
||||
environment: 'staging',
|
||||
logger: logger as any,
|
||||
cacheUrlFallbacks: {
|
||||
npm: 'https://fallback-npm.example',
|
||||
},
|
||||
});
|
||||
|
||||
mockProcessPlatform('linux');
|
||||
expect(RuntimeSettings.getNpmCacheUrl()).toBeNull();
|
||||
});
|
||||
|
||||
it('clears cache URL fallbacks when loading without them', async () => {
|
||||
jest.mocked(fetch).mockResolvedValue(response({ caches: { linux: { npm: true } } }));
|
||||
|
||||
await RuntimeSettings.loadAsync({
|
||||
environment: 'staging',
|
||||
logger: logger as any,
|
||||
cacheUrlFallbacks: {
|
||||
npm: 'https://fallback-npm.example',
|
||||
},
|
||||
});
|
||||
await RuntimeSettings.loadAsync({ environment: 'staging', logger: logger as any });
|
||||
|
||||
mockProcessPlatform('linux');
|
||||
expect(RuntimeSettings.getNpmCacheUrl()).toBeNull();
|
||||
});
|
||||
|
||||
it('does not use caches when runtime settings are not loaded', () => {
|
||||
process.env.EAS_NPM_CACHE_URL = 'https://npm.example';
|
||||
|
||||
jest.isolateModules(() => {
|
||||
const { RuntimeSettings } = require('../runtimeSettings');
|
||||
|
||||
expect(RuntimeSettings.getNpmCacheUrl()).toBeNull();
|
||||
expect(RuntimeSettings.isUsingIosPrecompiledModulesEnabled()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('reports when loaded runtime settings omit the current platform cache settings', async () => {
|
||||
jest.mocked(fetch).mockResolvedValue(response({ caches: { linux: { npm: true } } }));
|
||||
mockProcessPlatform('darwin');
|
||||
|
||||
await RuntimeSettings.loadAsync({ environment: 'staging', logger: logger as any });
|
||||
|
||||
expect(RuntimeSettings.getNpmCacheUrl()).toBeNull();
|
||||
expect(Sentry.capture).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
message: 'Runtime settings are missing cache settings for platform "darwin"',
|
||||
}),
|
||||
{
|
||||
extras: {
|
||||
configuredPlatforms: ['linux'],
|
||||
platform: 'darwin',
|
||||
},
|
||||
level: 'error',
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
function restoreEnv(key: string, value: string | undefined): void {
|
||||
if (value === undefined) {
|
||||
delete process.env[key];
|
||||
} else {
|
||||
process.env[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
function mockProcessPlatform(platform: NodeJS.Platform): void {
|
||||
Object.defineProperty(process, 'platform', {
|
||||
configurable: true,
|
||||
value: platform,
|
||||
});
|
||||
}
|
||||
|
||||
function response(body: unknown, status = 200): any {
|
||||
return {
|
||||
ok: status >= 200 && status < 300,
|
||||
status,
|
||||
json: async () => body,
|
||||
};
|
||||
}
|
||||
@@ -1,26 +1,15 @@
|
||||
import { Sentry } from './sentry';
|
||||
import { turtleFetch } from './utils/turtleFetch';
|
||||
|
||||
type MetricsTarget =
|
||||
| { kind: 'build'; turtleBuildId: string }
|
||||
| { kind: 'jobRun'; turtleJobRunId: string };
|
||||
|
||||
type DatadogSetupOptions = {
|
||||
expoApiV2BaseUrl: string;
|
||||
turtleBuildId: string;
|
||||
robotAccessToken: string;
|
||||
target: MetricsTarget;
|
||||
};
|
||||
|
||||
let setupOptions: DatadogSetupOptions | null = null;
|
||||
let pendingUploads: Promise<unknown>[] = [];
|
||||
|
||||
function withTargetTag(
|
||||
tags: Record<string, string>,
|
||||
target: MetricsTarget
|
||||
): Record<string, string> {
|
||||
return { ...tags, target: target.kind === 'build' ? 'build' : 'job_run' };
|
||||
}
|
||||
|
||||
export const Datadog = {
|
||||
setup(opts: DatadogSetupOptions | null): void {
|
||||
setupOptions = opts;
|
||||
@@ -30,29 +19,28 @@ export const Datadog = {
|
||||
if (!setupOptions) {
|
||||
return;
|
||||
}
|
||||
const { expoApiV2BaseUrl, robotAccessToken, target } = setupOptions;
|
||||
const { expoApiV2BaseUrl, turtleBuildId, robotAccessToken } = setupOptions;
|
||||
const metrics = [
|
||||
{
|
||||
name,
|
||||
type: 'distribution' as const,
|
||||
value,
|
||||
tags: withTargetTag(tags, target),
|
||||
tags,
|
||||
},
|
||||
];
|
||||
|
||||
const metricsPath =
|
||||
target.kind === 'build'
|
||||
? `turtle-builds/${target.turtleBuildId}/metrics`
|
||||
: `turtle-job-runs/${target.turtleJobRunId}/metrics`;
|
||||
|
||||
const uploadPromise = turtleFetch(new URL(metricsPath, expoApiV2BaseUrl).toString(), 'POST', {
|
||||
json: { metrics },
|
||||
headers: {
|
||||
Authorization: `Bearer ${robotAccessToken}`,
|
||||
},
|
||||
retries: 2,
|
||||
}).catch(err => {
|
||||
Sentry.capture(`Failed to report turtle ${target.kind} metric`, err, {
|
||||
const uploadPromise = turtleFetch(
|
||||
new URL(`turtle-builds/${turtleBuildId}/metrics`, expoApiV2BaseUrl).toString(),
|
||||
'POST',
|
||||
{
|
||||
json: { metrics },
|
||||
headers: {
|
||||
Authorization: `Bearer ${robotAccessToken}`,
|
||||
},
|
||||
retries: 2,
|
||||
}
|
||||
).catch(err => {
|
||||
Sentry.capture('Failed to report turtle build metric', err, {
|
||||
extras: { metrics },
|
||||
});
|
||||
});
|
||||
@@ -64,22 +52,25 @@ export const Datadog = {
|
||||
if (!setupOptions) {
|
||||
return;
|
||||
}
|
||||
const { expoApiV2BaseUrl, robotAccessToken, target } = setupOptions;
|
||||
const tagsWithTarget = withTargetTag(tags, target);
|
||||
const log =
|
||||
target.kind === 'build'
|
||||
? { buildId: target.turtleBuildId, message, tags: tagsWithTarget }
|
||||
: { jobRunId: target.turtleJobRunId, message, tags: tagsWithTarget };
|
||||
const logsPath = target.kind === 'build' ? 'turtle-builds/logs' : 'turtle-job-runs/logs';
|
||||
const { expoApiV2BaseUrl, turtleBuildId, robotAccessToken } = setupOptions;
|
||||
const log = {
|
||||
buildId: turtleBuildId,
|
||||
message,
|
||||
tags,
|
||||
};
|
||||
|
||||
const uploadPromise = turtleFetch(new URL(logsPath, expoApiV2BaseUrl).toString(), 'POST', {
|
||||
json: log,
|
||||
headers: {
|
||||
Authorization: `Bearer ${robotAccessToken}`,
|
||||
},
|
||||
shouldThrowOnNotOk: false,
|
||||
}).catch(err => {
|
||||
Sentry.capture(`Failed to report turtle ${target.kind} log`, err, {
|
||||
const uploadPromise = turtleFetch(
|
||||
new URL('turtle-builds/logs', expoApiV2BaseUrl).toString(),
|
||||
'POST',
|
||||
{
|
||||
json: log,
|
||||
headers: {
|
||||
Authorization: `Bearer ${robotAccessToken}`,
|
||||
},
|
||||
shouldThrowOnNotOk: false,
|
||||
}
|
||||
).catch(err => {
|
||||
Sentry.capture('Failed to report turtle build log', err, {
|
||||
extras: { log },
|
||||
});
|
||||
});
|
||||
@@ -88,10 +79,6 @@ export const Datadog = {
|
||||
},
|
||||
|
||||
async flushAsync(): Promise<void> {
|
||||
// Rotate so each flush only awaits its own batch; uploads enqueued during the
|
||||
// await land in the fresh array for a later flush.
|
||||
const uploads = pendingUploads;
|
||||
pendingUploads = [];
|
||||
await Promise.allSettled(uploads);
|
||||
await Promise.allSettled(pendingUploads);
|
||||
},
|
||||
};
|
||||
@@ -27,4 +27,3 @@ export * from './generic';
|
||||
|
||||
export { Datadog } from './datadog';
|
||||
export { Sentry } from './sentry';
|
||||
export * from './runtimeSettings';
|
||||
@@ -51,7 +51,7 @@ async function resolvePrecompiledModulesPodInstallEnvAsync<TJob extends Ios.Job>
|
||||
return {};
|
||||
}
|
||||
|
||||
if (ctx.env.EAS_USE_PRECOMPILED_MODULES !== '1') {
|
||||
if (ctx.job.builderEnvironment?.env?.EAS_USE_PRECOMPILED_MODULES !== '1') {
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
import { type bunyan } from '@expo/logger';
|
||||
import fetch from 'node-fetch';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { Sentry } from './sentry';
|
||||
|
||||
const ENVIRONMENT_TO_RUNTIME_SETTINGS_URL: Record<string, string | undefined> = {
|
||||
staging: 'https://storage.googleapis.com/eas-workflows-staging/runtime-settings.json',
|
||||
production: 'https://storage.googleapis.com/eas-workflows-production/runtime-settings.json',
|
||||
};
|
||||
|
||||
const RuntimeSettingsSchema = z
|
||||
.object({
|
||||
caches: z.record(
|
||||
z.string(),
|
||||
z
|
||||
.object({
|
||||
npm: z.boolean(),
|
||||
nodejs: z.boolean(),
|
||||
maven: z.boolean(),
|
||||
cocoapods: z.boolean(),
|
||||
})
|
||||
.partial()
|
||||
),
|
||||
iosPrecompiledModules: z.boolean(),
|
||||
})
|
||||
.partial();
|
||||
|
||||
type CacheUrlFallbacks = {
|
||||
npm?: string | null;
|
||||
nodejs?: string | null;
|
||||
maven?: string | null;
|
||||
cocoapods?: string | null;
|
||||
};
|
||||
|
||||
let runtimeSettings: z.infer<typeof RuntimeSettingsSchema> = {};
|
||||
let cacheUrlFallbacks: CacheUrlFallbacks = {};
|
||||
|
||||
export namespace RuntimeSettings {
|
||||
export async function loadAsync({
|
||||
environment,
|
||||
logger,
|
||||
cacheUrlFallbacks: nextCacheUrlFallbacks = {},
|
||||
}: {
|
||||
environment: string;
|
||||
logger: bunyan;
|
||||
cacheUrlFallbacks?: CacheUrlFallbacks;
|
||||
}): Promise<void> {
|
||||
cacheUrlFallbacks = nextCacheUrlFallbacks;
|
||||
const url = ENVIRONMENT_TO_RUNTIME_SETTINGS_URL[environment];
|
||||
if (!url) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
signal: AbortSignal.timeout(1000),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
logger.warn(
|
||||
{ url, status: response.status },
|
||||
'Failed to fetch worker runtime settings, using defaults'
|
||||
);
|
||||
Sentry.capture('Failed to fetch worker runtime settings', {
|
||||
extras: { url, status: response.status },
|
||||
level: 'warning',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
runtimeSettings = RuntimeSettingsSchema.parse(await response.json());
|
||||
if (runtimeSettings.caches && !runtimeSettings.caches[process.platform]) {
|
||||
Sentry.capture(
|
||||
new Error(
|
||||
`Runtime settings are missing cache settings for platform "${process.platform}"`
|
||||
),
|
||||
{
|
||||
extras: {
|
||||
configuredPlatforms: Object.keys(runtimeSettings.caches),
|
||||
platform: process.platform,
|
||||
},
|
||||
level: 'error',
|
||||
}
|
||||
);
|
||||
}
|
||||
logger.info({ url, settings: runtimeSettings }, 'Loaded worker runtime settings');
|
||||
} catch (err) {
|
||||
logger.warn({ err, url }, 'Failed to load worker runtime settings, using defaults');
|
||||
Sentry.capture(
|
||||
'Failed to load worker runtime settings',
|
||||
err instanceof Error ? err : new Error(String(err)),
|
||||
{
|
||||
extras: { url },
|
||||
level: 'warning',
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function isUsingIosPrecompiledModulesEnabled(): boolean {
|
||||
return runtimeSettings.iosPrecompiledModules ?? false;
|
||||
}
|
||||
|
||||
export function getNpmCacheUrl(): string | null {
|
||||
return runtimeSettings.caches?.[process.platform]?.npm
|
||||
? process.env.EAS_NPM_CACHE_URL || cacheUrlFallbacks.npm || null
|
||||
: null;
|
||||
}
|
||||
|
||||
export function getNodeJsCacheUrl(): string | null {
|
||||
return runtimeSettings.caches?.[process.platform]?.nodejs
|
||||
? process.env.EAS_NODEJS_CACHE_URL || cacheUrlFallbacks.nodejs || null
|
||||
: null;
|
||||
}
|
||||
|
||||
export function getMavenCacheUrl(): string | null {
|
||||
return runtimeSettings.caches?.[process.platform]?.maven
|
||||
? process.env.EAS_MAVEN_CACHE_URL || cacheUrlFallbacks.maven || null
|
||||
: null;
|
||||
}
|
||||
|
||||
export function getCocoapodsCacheUrl(): string | null {
|
||||
return runtimeSettings.caches?.[process.platform]?.cocoapods
|
||||
? process.env.EAS_COCOAPODS_CACHE_URL || cacheUrlFallbacks.cocoapods || null
|
||||
: null;
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
import spawn from '@expo/turtle-spawn';
|
||||
|
||||
import { createGlobalContextMock } from '../../../__tests__/utils/context';
|
||||
import { Datadog } from '../../../datadog';
|
||||
import { createInstallMaestroBuildFunction } from '../installMaestro';
|
||||
|
||||
jest.mock('@expo/turtle-spawn', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../../datadog', () => ({
|
||||
Datadog: {
|
||||
distribution: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
const mockedSpawn = jest.mocked(spawn);
|
||||
|
||||
describe('createInstallMaestroBuildFunction', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
// `maestro --version` reports an installed version; `java -version` succeeds.
|
||||
// With no requested version and Maestro present, the step skips installation.
|
||||
mockedSpawn.mockImplementation((async (command: string) => ({
|
||||
stdout: command === 'maestro' ? '1.41.0\n' : '',
|
||||
})) as any);
|
||||
});
|
||||
|
||||
it('reports the detected Maestro version to Datadog on EAS Build VMs', async () => {
|
||||
const installMaestro = createInstallMaestroBuildFunction();
|
||||
const globalCtx = createGlobalContextMock();
|
||||
globalCtx.updateEnv({ EAS_BUILD_RUNNER: 'eas-build' });
|
||||
const step = installMaestro.createBuildStepFromFunctionCall(globalCtx, { callInputs: {} });
|
||||
|
||||
await step.executeAsync();
|
||||
|
||||
expect(step.getOutputValueByName('maestro_version')).toBe('1.41.0');
|
||||
expect(Datadog.distribution).toHaveBeenCalledWith('eas.maestro.install', 1, {
|
||||
maestro_version: '1.41.0',
|
||||
});
|
||||
});
|
||||
|
||||
it('does not report to Datadog outside EAS Build VMs', async () => {
|
||||
const installMaestro = createInstallMaestroBuildFunction();
|
||||
const step = installMaestro.createBuildStepFromFunctionCall(createGlobalContextMock(), {
|
||||
callInputs: {},
|
||||
});
|
||||
|
||||
await step.executeAsync();
|
||||
|
||||
expect(Datadog.distribution).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('extracts the version when `maestro --version` prints an analytics notice', async () => {
|
||||
mockedSpawn.mockImplementation((async (command: string) => ({
|
||||
stdout:
|
||||
command === 'maestro'
|
||||
? 'Anonymous analytics enabled. To opt out, set MAESTRO_CLI_NO_ANALYTICS environment variable to any value before running Maestro.\n2.0.10\n'
|
||||
: '',
|
||||
})) as any);
|
||||
|
||||
const installMaestro = createInstallMaestroBuildFunction();
|
||||
const globalCtx = createGlobalContextMock();
|
||||
globalCtx.updateEnv({ EAS_BUILD_RUNNER: 'eas-build' });
|
||||
const step = installMaestro.createBuildStepFromFunctionCall(globalCtx, { callInputs: {} });
|
||||
|
||||
await step.executeAsync();
|
||||
|
||||
expect(step.getOutputValueByName('maestro_version')).toBe('2.0.10');
|
||||
expect(Datadog.distribution).toHaveBeenCalledWith('eas.maestro.install', 1, {
|
||||
maestro_version: '2.0.10',
|
||||
});
|
||||
});
|
||||
|
||||
it('uses the trailing version when the notice itself contains an earlier version-like string', async () => {
|
||||
mockedSpawn.mockImplementation((async (command: string) => ({
|
||||
stdout: command === 'maestro' ? 'Analytics schema v2.0.0 enabled.\n2.0.10\n' : '',
|
||||
})) as any);
|
||||
|
||||
const installMaestro = createInstallMaestroBuildFunction();
|
||||
const globalCtx = createGlobalContextMock();
|
||||
globalCtx.updateEnv({ EAS_BUILD_RUNNER: 'eas-build' });
|
||||
const step = installMaestro.createBuildStepFromFunctionCall(globalCtx, { callInputs: {} });
|
||||
|
||||
await step.executeAsync();
|
||||
|
||||
expect(step.getOutputValueByName('maestro_version')).toBe('2.0.10');
|
||||
});
|
||||
});
|
||||
@@ -15,8 +15,6 @@ import fs from 'fs';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
|
||||
import { Datadog } from '../../datadog';
|
||||
|
||||
export function createInstallMaestroBuildFunction(): BuildFunction {
|
||||
return new BuildFunction({
|
||||
namespace: 'eas',
|
||||
@@ -125,24 +123,13 @@ export function createInstallMaestroBuildFunction(): BuildFunction {
|
||||
|
||||
logger.info(`Maestro ${maestroVersionResult.value} is ready.`);
|
||||
outputs.maestro_version.set(maestroVersionResult.value);
|
||||
|
||||
Datadog.distribution('eas.maestro.install', 1, {
|
||||
maestro_version: maestroVersionResult.value,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function getMaestroVersion({ env }: { env: BuildStepEnv }): Promise<string> {
|
||||
const { stdout } = await spawn('maestro', ['--version'], { stdio: 'pipe', env });
|
||||
// `maestro --version` can print an analytics notice to stdout before the version,
|
||||
// e.g. "Anonymous analytics enabled. To opt out, set MAESTRO_CLI_NO_ANALYTICS...\n2.0.10".
|
||||
// Take the last version-looking token: the real version is printed after the notice, so
|
||||
// this stays correct even if the notice itself contains a version-like string. Keeps the
|
||||
// step output and the eas.maestro.install metric tag clean. Best-effort only: fall back to
|
||||
// the raw output if none is found, so we never fail the build over a version string.
|
||||
const versions = stdout.match(/\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?/g);
|
||||
return versions?.at(-1) ?? stdout.trim();
|
||||
const maestroVersion = await spawn('maestro', ['--version'], { stdio: 'pipe', env });
|
||||
return maestroVersion.stdout.trim();
|
||||
}
|
||||
|
||||
async function installMaestro({
|
||||
|
||||
@@ -57,16 +57,6 @@ export function createRepackBuildFunction(): BuildFunction {
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.BOOLEAN,
|
||||
required: false,
|
||||
}),
|
||||
BuildStepInput.createProvider({
|
||||
id: 'ios_signing_use_source_app_entitlements',
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.BOOLEAN,
|
||||
required: false,
|
||||
}),
|
||||
BuildStepInput.createProvider({
|
||||
id: 'ios_signing_app_entitlements_path',
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.STRING,
|
||||
required: false,
|
||||
}),
|
||||
BuildStepInput.createProvider({
|
||||
id: 'repack_version',
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.STRING,
|
||||
@@ -148,12 +138,6 @@ export function createRepackBuildFunction(): BuildFunction {
|
||||
iosSigningOptions: await resolveIosSigningOptionsAsync({
|
||||
job: stepsCtx.global.staticContext.job,
|
||||
logger: stepsCtx.logger,
|
||||
useAppEntitlements: inputs.ios_signing_use_source_app_entitlements.value as
|
||||
| boolean
|
||||
| undefined,
|
||||
entitlementsPath: inputs.ios_signing_app_entitlements_path.value as
|
||||
| string
|
||||
| undefined,
|
||||
}),
|
||||
logger: stepsCtx.logger,
|
||||
spawnAsync: repackSpawnAsync,
|
||||
@@ -291,13 +275,9 @@ export async function resolveAndroidSigningOptionsAsync({
|
||||
export async function resolveIosSigningOptionsAsync({
|
||||
job,
|
||||
logger,
|
||||
useAppEntitlements,
|
||||
entitlementsPath,
|
||||
}: {
|
||||
job: Job;
|
||||
logger: bunyan;
|
||||
useAppEntitlements?: boolean;
|
||||
entitlementsPath?: string;
|
||||
}): Promise<IosSigningOptions | undefined> {
|
||||
const iosJob = job as Ios.Job;
|
||||
const buildCredentials = iosJob.secrets?.buildCredentials;
|
||||
@@ -315,7 +295,5 @@ export async function resolveIosSigningOptionsAsync({
|
||||
provisioningProfile,
|
||||
keychainPath: credentials.keychainPath,
|
||||
signingIdentity: credentials.applicationTargetProvisioningProfile.data.certificateCommonName,
|
||||
useAppEntitlements,
|
||||
entitlementsPath,
|
||||
};
|
||||
}
|
||||
@@ -141,10 +141,6 @@ export async function uploadCacheAsync({
|
||||
return;
|
||||
}
|
||||
const textResult = await asyncResult(response.text());
|
||||
const errorMessage = extractValidationErrorMessage(textResult.value);
|
||||
if (errorMessage) {
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
throw new Error(`Unexpected response from server (${response.status}): ${textResult.value}`);
|
||||
}
|
||||
|
||||
@@ -235,19 +231,6 @@ export async function uploadPublicCacheAsync({
|
||||
logger.info(`Uploaded cache archive to ${archivePath} (${formatBytes(size)}).`);
|
||||
}
|
||||
|
||||
function extractValidationErrorMessage(body: string | undefined): string | undefined {
|
||||
if (!body) {
|
||||
return undefined;
|
||||
}
|
||||
try {
|
||||
const { errors } = JSON.parse(body);
|
||||
if (errors?.[0]?.code === 'VALIDATION_ERROR') {
|
||||
return errors[0].message;
|
||||
}
|
||||
} catch {}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export async function compressCacheAsync({
|
||||
paths,
|
||||
workingDirectory,
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
import { SystemError } from '@expo/eas-build-job';
|
||||
import { type bunyan } from '@expo/logger';
|
||||
import { bunyan } from '@expo/logger';
|
||||
import {
|
||||
BuildFunction,
|
||||
BuildRuntimePlatform,
|
||||
type BuildStepEnv,
|
||||
BuildStepEnv,
|
||||
BuildStepInput,
|
||||
BuildStepInputValueTypeName,
|
||||
} from '@expo/steps';
|
||||
import spawn from '@expo/turtle-spawn';
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
|
||||
import { type CustomBuildContext } from '../../customBuildContext';
|
||||
import { Sentry } from '../../sentry';
|
||||
import { CustomBuildContext } from '../../customBuildContext';
|
||||
import {
|
||||
type DetachedProcessHandle,
|
||||
getDeviceRunSessionIdOrThrow,
|
||||
getNgrokAuthtokenOrThrow,
|
||||
getNgrokTunnelDomainOrThrow,
|
||||
@@ -26,7 +23,6 @@ import {
|
||||
waitForFileAsync,
|
||||
} from '../utils/remoteDeviceRunSession';
|
||||
|
||||
const AGENT_DEVICE_PACKAGE_NAME = 'agent-device';
|
||||
const AGENT_DEVICE_REPO_URL = 'https://github.com/callstackincubator/agent-device.git';
|
||||
const SRC_DIR = '/tmp/agent-device-src';
|
||||
const DAEMON_JSON_PATH = path.join(os.homedir(), '.agent-device', 'daemon.json');
|
||||
@@ -68,12 +64,34 @@ export function createStartAgentDeviceRemoteSessionBuildFunction(
|
||||
await spawn('sudo', ['xcode-select', '-s', XCODE_DEVELOPER_DIR], { env, logger });
|
||||
}
|
||||
|
||||
logger.info(
|
||||
packageVersion
|
||||
? `Cloning agent-device @ v${packageVersion} into ${SRC_DIR}.`
|
||||
: `Cloning agent-device (latest) into ${SRC_DIR}.`
|
||||
);
|
||||
await cloneAgentDeviceAsync({ packageVersion, env, logger });
|
||||
|
||||
logger.info('Installing agent-device dependencies.');
|
||||
await spawn('bun', ['install', '--production'], {
|
||||
cwd: SRC_DIR,
|
||||
env,
|
||||
logger,
|
||||
});
|
||||
|
||||
logger.info('Launching agent-device daemon.');
|
||||
const daemonProcess = await startAgentDeviceDaemonAsync({ packageVersion, env, logger });
|
||||
spawnDetached({
|
||||
command: 'bun',
|
||||
args: ['run', 'src/daemon.ts'],
|
||||
cwd: SRC_DIR,
|
||||
env: { ...env, AGENT_DEVICE_DAEMON_SERVER_MODE: 'http' },
|
||||
});
|
||||
|
||||
logger.info(`Waiting for daemon credentials at ${DAEMON_JSON_PATH}.`);
|
||||
const { port: daemonPort, token: daemonToken } = await waitForDaemonInfoAsync({
|
||||
daemonProcess,
|
||||
const { port: daemonPort, token: daemonToken } = await waitForFileAsync({
|
||||
filePath: DAEMON_JSON_PATH,
|
||||
timeoutMs: STARTUP_TIMEOUT_MS,
|
||||
description: 'agent-device daemon credentials',
|
||||
parse: parseDaemonInfo,
|
||||
});
|
||||
logger.info(`Daemon is listening on port ${daemonPort}; loaded auth token.`);
|
||||
|
||||
@@ -119,93 +137,6 @@ export function createStartAgentDeviceRemoteSessionBuildFunction(
|
||||
});
|
||||
}
|
||||
|
||||
async function startAgentDeviceDaemonAsync({
|
||||
packageVersion,
|
||||
env,
|
||||
logger,
|
||||
}: {
|
||||
packageVersion: string | undefined;
|
||||
env: BuildStepEnv;
|
||||
logger: bunyan;
|
||||
}): Promise<DetachedProcessHandle> {
|
||||
const packageSpec = createAgentDevicePackageSpec(packageVersion);
|
||||
try {
|
||||
logger.info(`Installing ${packageSpec} globally with Bun.`);
|
||||
await spawn('bun', ['add', '--global', packageSpec], {
|
||||
env,
|
||||
logger,
|
||||
});
|
||||
|
||||
const daemonPath = getGlobalAgentDeviceDaemonPath(env);
|
||||
if (!fs.existsSync(daemonPath)) {
|
||||
throw new SystemError(`Expected agent-device daemon entry at ${daemonPath}.`);
|
||||
}
|
||||
|
||||
logger.info(`Launching daemon from ${daemonPath}.`);
|
||||
return spawnDetached({
|
||||
command: 'node',
|
||||
args: [daemonPath],
|
||||
env: { ...env, AGENT_DEVICE_DAEMON_SERVER_MODE: 'http' },
|
||||
});
|
||||
} catch (err) {
|
||||
const error = err instanceof Error ? err : new Error(String(err));
|
||||
const bunVersion = await getBunVersionForDiagnosticsAsync(env);
|
||||
Sentry.capture(
|
||||
'Failed to start agent-device daemon from global Bun package; falling back to git clone',
|
||||
error,
|
||||
{
|
||||
level: 'warning',
|
||||
tags: {
|
||||
phase: 'agent-device-daemon-start',
|
||||
fallback: 'git-clone',
|
||||
},
|
||||
extras: {
|
||||
packageSpec,
|
||||
packageVersion: packageVersion ?? 'latest',
|
||||
bunVersion,
|
||||
bunInstallConfigured: Boolean(env.BUN_INSTALL?.trim()),
|
||||
},
|
||||
}
|
||||
);
|
||||
logger.warn(
|
||||
`Failed to start daemon from global ${packageSpec}; falling back to git clone: ${error.message}`
|
||||
);
|
||||
return await startAgentDeviceDaemonFromGitAsync({ packageVersion, env, logger });
|
||||
}
|
||||
}
|
||||
|
||||
async function startAgentDeviceDaemonFromGitAsync({
|
||||
packageVersion,
|
||||
env,
|
||||
logger,
|
||||
}: {
|
||||
packageVersion: string | undefined;
|
||||
env: BuildStepEnv;
|
||||
logger: bunyan;
|
||||
}): Promise<DetachedProcessHandle> {
|
||||
logger.info(
|
||||
packageVersion
|
||||
? `Cloning agent-device @ v${packageVersion} into ${SRC_DIR}.`
|
||||
: `Cloning agent-device (latest) into ${SRC_DIR}.`
|
||||
);
|
||||
await cloneAgentDeviceAsync({ packageVersion, env, logger });
|
||||
|
||||
logger.info('Installing agent-device dependencies.');
|
||||
await spawn('bun', ['install', '--production'], {
|
||||
cwd: SRC_DIR,
|
||||
env,
|
||||
logger,
|
||||
});
|
||||
|
||||
logger.info('Launching daemon from cloned agent-device source.');
|
||||
return spawnDetached({
|
||||
command: 'bun',
|
||||
args: ['run', 'src/daemon.ts'],
|
||||
cwd: SRC_DIR,
|
||||
env: { ...env, AGENT_DEVICE_DAEMON_SERVER_MODE: 'http' },
|
||||
});
|
||||
}
|
||||
|
||||
async function cloneAgentDeviceAsync({
|
||||
packageVersion,
|
||||
env,
|
||||
@@ -222,63 +153,6 @@ async function cloneAgentDeviceAsync({
|
||||
});
|
||||
}
|
||||
|
||||
async function waitForDaemonInfoAsync({
|
||||
daemonProcess,
|
||||
}: {
|
||||
daemonProcess: DetachedProcessHandle;
|
||||
}): Promise<{ port: number; token: string }> {
|
||||
try {
|
||||
return await waitForFileAsync({
|
||||
filePath: DAEMON_JSON_PATH,
|
||||
timeoutMs: STARTUP_TIMEOUT_MS,
|
||||
description: 'agent-device daemon credentials',
|
||||
parse: parseDaemonInfo,
|
||||
});
|
||||
} catch (err) {
|
||||
const output = daemonProcess.getOutput();
|
||||
throw new SystemError(
|
||||
`${
|
||||
err instanceof Error
|
||||
? err.message
|
||||
: `Timed out waiting for agent-device daemon credentials.`
|
||||
}${output ? `\nagent-device daemon output:\n${output}` : ''}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function getBunVersionForDiagnosticsAsync(env: BuildStepEnv): Promise<string> {
|
||||
try {
|
||||
const result = await spawn('bun', ['--version'], { stdio: 'pipe', env, cwd: os.tmpdir() });
|
||||
return result.stdout.trim() || 'unknown';
|
||||
} catch {
|
||||
return 'unknown';
|
||||
}
|
||||
}
|
||||
|
||||
function createAgentDevicePackageSpec(packageVersion: string | undefined): string {
|
||||
const versionSpec = packageVersion ? packageVersion.replace(/^v(?=\d)/, '') : 'latest';
|
||||
return `${AGENT_DEVICE_PACKAGE_NAME}@${versionSpec}`;
|
||||
}
|
||||
|
||||
function getGlobalAgentDeviceDaemonPath(env: BuildStepEnv): string {
|
||||
return path.join(
|
||||
getBunInstallDirectory(env),
|
||||
'install',
|
||||
'global',
|
||||
'node_modules',
|
||||
AGENT_DEVICE_PACKAGE_NAME,
|
||||
'dist',
|
||||
'src',
|
||||
'internal',
|
||||
'daemon.js'
|
||||
);
|
||||
}
|
||||
|
||||
function getBunInstallDirectory(env: BuildStepEnv): string {
|
||||
const bunInstall = env.BUN_INSTALL?.trim();
|
||||
return bunInstall ? bunInstall : path.join(os.homedir(), '.bun');
|
||||
}
|
||||
|
||||
function parseDaemonInfo(raw: string): { port: number; token: string } {
|
||||
const parsed = JSON.parse(raw) as unknown;
|
||||
if (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-eas-build-function",
|
||||
"version": "20.1.0",
|
||||
"version": "20.0.0",
|
||||
"description": "Create functions for use in EAS Build custom builds.",
|
||||
"keywords": [
|
||||
"expo",
|
||||
@@ -35,7 +35,7 @@
|
||||
"clean": "rimraf ./build/ \"*.tsbuildinfo\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@expo/steps": "20.1.0"
|
||||
"@expo/steps": "20.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@expo/package-manager": "1.9.10",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@expo/eas-build-job",
|
||||
"version": "20.1.0",
|
||||
"version": "20.0.0",
|
||||
"bugs": "https://github.com/expo/eas-cli/issues",
|
||||
"license": "MIT",
|
||||
"author": "Expo <[email protected]>",
|
||||
|
||||
+106
-176
@@ -149,10 +149,7 @@ eas --help COMMAND
|
||||
* [`eas update:configure`](#eas-updateconfigure)
|
||||
* [`eas update:delete GROUPID`](#eas-updatedelete-groupid)
|
||||
* [`eas update:edit [GROUPID]`](#eas-updateedit-groupid)
|
||||
* [`eas update:embedded:delete ID`](#eas-updateembeddeddelete-id)
|
||||
* [`eas update:embedded:list`](#eas-updateembeddedlist)
|
||||
* [`eas update:embedded:upload`](#eas-updateembeddedupload)
|
||||
* [`eas update:embedded:view ID`](#eas-updateembeddedview-id)
|
||||
* [`eas update:insights GROUPID`](#eas-updateinsights-groupid)
|
||||
* [`eas update:list`](#eas-updatelist)
|
||||
* [`eas update:republish`](#eas-updaterepublish)
|
||||
@@ -199,7 +196,7 @@ ALIASES
|
||||
$ eas login
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v20.1.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/v20.0.0/packages/eas-cli/src/commands/account/login.ts)_
|
||||
|
||||
## `eas account:logout`
|
||||
|
||||
@@ -216,7 +213,7 @@ ALIASES
|
||||
$ eas logout
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v20.1.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/v20.0.0/packages/eas-cli/src/commands/account/logout.ts)_
|
||||
|
||||
## `eas account:usage [ACCOUNT_NAME]`
|
||||
|
||||
@@ -238,7 +235,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/v20.1.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/v20.0.0/packages/eas-cli/src/commands/account/usage.ts)_
|
||||
|
||||
## `eas account:view`
|
||||
|
||||
@@ -255,7 +252,7 @@ ALIASES
|
||||
$ eas whoami
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v20.1.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/v20.0.0/packages/eas-cli/src/commands/account/view.ts)_
|
||||
|
||||
## `eas analytics [STATUS]`
|
||||
|
||||
@@ -269,7 +266,7 @@ DESCRIPTION
|
||||
display or change analytics settings
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v20.1.0/packages/eas-cli/src/commands/analytics.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v20.0.0/packages/eas-cli/src/commands/analytics.ts)_
|
||||
|
||||
## `eas autocomplete [SHELL]`
|
||||
|
||||
@@ -321,7 +318,7 @@ DESCRIPTION
|
||||
create a branch
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v20.1.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/v20.0.0/packages/eas-cli/src/commands/branch/create.ts)_
|
||||
|
||||
## `eas branch:delete [NAME]`
|
||||
|
||||
@@ -342,7 +339,7 @@ DESCRIPTION
|
||||
delete a branch
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v20.1.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/v20.0.0/packages/eas-cli/src/commands/branch/delete.ts)_
|
||||
|
||||
## `eas branch:list`
|
||||
|
||||
@@ -362,7 +359,7 @@ DESCRIPTION
|
||||
list all branches
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v20.1.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/v20.0.0/packages/eas-cli/src/commands/branch/list.ts)_
|
||||
|
||||
## `eas branch:rename`
|
||||
|
||||
@@ -382,7 +379,7 @@ DESCRIPTION
|
||||
rename a branch
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v20.1.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/v20.0.0/packages/eas-cli/src/commands/branch/rename.ts)_
|
||||
|
||||
## `eas branch:view [NAME]`
|
||||
|
||||
@@ -405,7 +402,7 @@ DESCRIPTION
|
||||
view a branch
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v20.1.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/v20.0.0/packages/eas-cli/src/commands/branch/view.ts)_
|
||||
|
||||
## `eas build`
|
||||
|
||||
@@ -446,7 +443,7 @@ DESCRIPTION
|
||||
start a build
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v20.1.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/v20.0.0/packages/eas-cli/src/commands/build/index.ts)_
|
||||
|
||||
## `eas build:cancel [BUILD_ID]`
|
||||
|
||||
@@ -466,7 +463,7 @@ DESCRIPTION
|
||||
cancel a build
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v20.1.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/v20.0.0/packages/eas-cli/src/commands/build/cancel.ts)_
|
||||
|
||||
## `eas build:configure`
|
||||
|
||||
@@ -484,7 +481,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/v20.1.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/v20.0.0/packages/eas-cli/src/commands/build/configure.ts)_
|
||||
|
||||
## `eas build:delete [BUILD_ID]`
|
||||
|
||||
@@ -504,7 +501,7 @@ DESCRIPTION
|
||||
delete a build
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/delete.ts](https://github.com/expo/eas-cli/blob/v20.1.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/v20.0.0/packages/eas-cli/src/commands/build/delete.ts)_
|
||||
|
||||
## `eas build:dev`
|
||||
|
||||
@@ -529,7 +526,7 @@ 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/v20.1.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/v20.0.0/packages/eas-cli/src/commands/build/dev.ts)_
|
||||
|
||||
## `eas build:download`
|
||||
|
||||
@@ -556,7 +553,7 @@ DESCRIPTION
|
||||
download a simulator/emulator build by build ID or fingerprint hash
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/download.ts](https://github.com/expo/eas-cli/blob/v20.1.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/v20.0.0/packages/eas-cli/src/commands/build/download.ts)_
|
||||
|
||||
## `eas build:inspect`
|
||||
|
||||
@@ -594,7 +591,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/v20.1.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/v20.0.0/packages/eas-cli/src/commands/build/inspect.ts)_
|
||||
|
||||
## `eas build:list`
|
||||
|
||||
@@ -634,7 +631,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/v20.1.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/v20.0.0/packages/eas-cli/src/commands/build/list.ts)_
|
||||
|
||||
## `eas build:resign`
|
||||
|
||||
@@ -664,7 +661,7 @@ DESCRIPTION
|
||||
re-sign a build archive
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/build/resign.ts](https://github.com/expo/eas-cli/blob/v20.1.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/v20.0.0/packages/eas-cli/src/commands/build/resign.ts)_
|
||||
|
||||
## `eas build:run`
|
||||
|
||||
@@ -692,7 +689,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/v20.1.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/v20.0.0/packages/eas-cli/src/commands/build/run.ts)_
|
||||
|
||||
## `eas build:submit`
|
||||
|
||||
@@ -744,7 +741,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/v20.1.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/v20.0.0/packages/eas-cli/src/commands/build/version/get.ts)_
|
||||
|
||||
## `eas build:version:set`
|
||||
|
||||
@@ -762,7 +759,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/v20.1.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/v20.0.0/packages/eas-cli/src/commands/build/version/set.ts)_
|
||||
|
||||
## `eas build:version:sync`
|
||||
|
||||
@@ -780,7 +777,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/v20.1.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/v20.0.0/packages/eas-cli/src/commands/build/version/sync.ts)_
|
||||
|
||||
## `eas build:view [BUILD_ID]`
|
||||
|
||||
@@ -797,7 +794,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/v20.1.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/v20.0.0/packages/eas-cli/src/commands/build/view.ts)_
|
||||
|
||||
## `eas channel:create [NAME]`
|
||||
|
||||
@@ -818,7 +815,7 @@ DESCRIPTION
|
||||
create a channel
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v20.1.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/v20.0.0/packages/eas-cli/src/commands/channel/create.ts)_
|
||||
|
||||
## `eas channel:delete [NAME]`
|
||||
|
||||
@@ -839,7 +836,7 @@ DESCRIPTION
|
||||
Delete a channel
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/delete.ts](https://github.com/expo/eas-cli/blob/v20.1.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/v20.0.0/packages/eas-cli/src/commands/channel/delete.ts)_
|
||||
|
||||
## `eas channel:edit [NAME]`
|
||||
|
||||
@@ -861,7 +858,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/v20.1.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/v20.0.0/packages/eas-cli/src/commands/channel/edit.ts)_
|
||||
|
||||
## `eas channel:insights`
|
||||
|
||||
@@ -885,7 +882,7 @@ DESCRIPTION
|
||||
display adoption, crash, and unique-user insights for a channel + runtime version
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/insights.ts](https://github.com/expo/eas-cli/blob/v20.1.0/packages/eas-cli/src/commands/channel/insights.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/channel/insights.ts](https://github.com/expo/eas-cli/blob/v20.0.0/packages/eas-cli/src/commands/channel/insights.ts)_
|
||||
|
||||
## `eas channel:list`
|
||||
|
||||
@@ -905,7 +902,7 @@ DESCRIPTION
|
||||
list all channels
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v20.1.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/v20.0.0/packages/eas-cli/src/commands/channel/list.ts)_
|
||||
|
||||
## `eas channel:pause [NAME]`
|
||||
|
||||
@@ -927,7 +924,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/v20.1.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/v20.0.0/packages/eas-cli/src/commands/channel/pause.ts)_
|
||||
|
||||
## `eas channel:resume [NAME]`
|
||||
|
||||
@@ -949,7 +946,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/v20.1.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/v20.0.0/packages/eas-cli/src/commands/channel/resume.ts)_
|
||||
|
||||
## `eas channel:rollout [CHANNEL]`
|
||||
|
||||
@@ -984,7 +981,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/v20.1.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/v20.0.0/packages/eas-cli/src/commands/channel/rollout.ts)_
|
||||
|
||||
## `eas channel:view [NAME]`
|
||||
|
||||
@@ -1007,7 +1004,7 @@ DESCRIPTION
|
||||
view a channel
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v20.1.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/v20.0.0/packages/eas-cli/src/commands/channel/view.ts)_
|
||||
|
||||
## `eas config`
|
||||
|
||||
@@ -1028,7 +1025,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/v20.1.0/packages/eas-cli/src/commands/config.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/config.ts](https://github.com/expo/eas-cli/blob/v20.0.0/packages/eas-cli/src/commands/config.ts)_
|
||||
|
||||
## `eas credentials`
|
||||
|
||||
@@ -1045,7 +1042,7 @@ DESCRIPTION
|
||||
manage credentials
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/credentials/index.ts](https://github.com/expo/eas-cli/blob/v20.1.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/v20.0.0/packages/eas-cli/src/commands/credentials/index.ts)_
|
||||
|
||||
## `eas credentials:configure-build`
|
||||
|
||||
@@ -1063,7 +1060,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/v20.1.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/v20.0.0/packages/eas-cli/src/commands/credentials/configure-build.ts)_
|
||||
|
||||
## `eas deploy [options]`
|
||||
|
||||
@@ -1092,7 +1089,7 @@ ALIASES
|
||||
$ eas worker:deploy
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/deploy/index.ts](https://github.com/expo/eas-cli/blob/v20.1.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/v20.0.0/packages/eas-cli/src/commands/deploy/index.ts)_
|
||||
|
||||
## `eas deploy:alias`
|
||||
|
||||
@@ -1117,7 +1114,7 @@ ALIASES
|
||||
$ eas deploy:promote
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/deploy/alias/index.ts](https://github.com/expo/eas-cli/blob/v20.1.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/v20.0.0/packages/eas-cli/src/commands/deploy/alias/index.ts)_
|
||||
|
||||
## `eas deploy:alias:delete [ALIAS_NAME]`
|
||||
|
||||
@@ -1138,7 +1135,7 @@ ALIASES
|
||||
$ eas worker:alias:delete
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/deploy/alias/delete.ts](https://github.com/expo/eas-cli/blob/v20.1.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/v20.0.0/packages/eas-cli/src/commands/deploy/alias/delete.ts)_
|
||||
|
||||
## `eas deploy:delete [DEPLOYMENT_ID]`
|
||||
|
||||
@@ -1159,7 +1156,7 @@ ALIASES
|
||||
$ eas worker:delete
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/deploy/delete.ts](https://github.com/expo/eas-cli/blob/v20.1.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/v20.0.0/packages/eas-cli/src/commands/deploy/delete.ts)_
|
||||
|
||||
## `eas deploy:promote`
|
||||
|
||||
@@ -1196,7 +1193,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/v20.1.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/v20.0.0/packages/eas-cli/src/commands/device/create.ts)_
|
||||
|
||||
## `eas device:delete`
|
||||
|
||||
@@ -1216,7 +1213,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/v20.1.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/v20.0.0/packages/eas-cli/src/commands/device/delete.ts)_
|
||||
|
||||
## `eas device:list`
|
||||
|
||||
@@ -1237,7 +1234,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/v20.1.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/v20.0.0/packages/eas-cli/src/commands/device/list.ts)_
|
||||
|
||||
## `eas device:rename`
|
||||
|
||||
@@ -1258,7 +1255,7 @@ DESCRIPTION
|
||||
rename a registered device
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/device/rename.ts](https://github.com/expo/eas-cli/blob/v20.1.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/v20.0.0/packages/eas-cli/src/commands/device/rename.ts)_
|
||||
|
||||
## `eas device:view [UDID]`
|
||||
|
||||
@@ -1272,7 +1269,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/v20.1.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/v20.0.0/packages/eas-cli/src/commands/device/view.ts)_
|
||||
|
||||
## `eas diagnostics`
|
||||
|
||||
@@ -1286,7 +1283,7 @@ DESCRIPTION
|
||||
display environment info
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v20.1.0/packages/eas-cli/src/commands/diagnostics.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v20.0.0/packages/eas-cli/src/commands/diagnostics.ts)_
|
||||
|
||||
## `eas env:create [ENVIRONMENT]`
|
||||
|
||||
@@ -1318,7 +1315,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/v20.1.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/v20.0.0/packages/eas-cli/src/commands/env/create.ts)_
|
||||
|
||||
## `eas env:delete [ENVIRONMENT]`
|
||||
|
||||
@@ -1344,7 +1341,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/v20.1.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/v20.0.0/packages/eas-cli/src/commands/env/delete.ts)_
|
||||
|
||||
## `eas env:exec ENVIRONMENT BASH_COMMAND`
|
||||
|
||||
@@ -1366,7 +1363,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/v20.1.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/v20.0.0/packages/eas-cli/src/commands/env/exec.ts)_
|
||||
|
||||
## `eas env:get [ENVIRONMENT]`
|
||||
|
||||
@@ -1394,7 +1391,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/v20.1.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/v20.0.0/packages/eas-cli/src/commands/env/get.ts)_
|
||||
|
||||
## `eas env:list [ENVIRONMENT]`
|
||||
|
||||
@@ -1422,7 +1419,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/v20.1.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/v20.0.0/packages/eas-cli/src/commands/env/list.ts)_
|
||||
|
||||
## `eas env:pull [ENVIRONMENT]`
|
||||
|
||||
@@ -1445,7 +1442,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/v20.1.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/v20.0.0/packages/eas-cli/src/commands/env/pull.ts)_
|
||||
|
||||
## `eas env:push [ENVIRONMENT]`
|
||||
|
||||
@@ -1467,7 +1464,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/v20.1.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/v20.0.0/packages/eas-cli/src/commands/env/push.ts)_
|
||||
|
||||
## `eas env:update [ENVIRONMENT]`
|
||||
|
||||
@@ -1501,7 +1498,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/v20.1.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/v20.0.0/packages/eas-cli/src/commands/env/update.ts)_
|
||||
|
||||
## `eas fingerprint:compare [HASH1] [HASH2]`
|
||||
|
||||
@@ -1545,7 +1542,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/v20.1.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/v20.0.0/packages/eas-cli/src/commands/fingerprint/compare.ts)_
|
||||
|
||||
## `eas fingerprint:generate`
|
||||
|
||||
@@ -1576,7 +1573,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/v20.1.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/v20.0.0/packages/eas-cli/src/commands/fingerprint/generate.ts)_
|
||||
|
||||
## `eas help [COMMAND]`
|
||||
|
||||
@@ -1655,7 +1652,7 @@ DESCRIPTION
|
||||
connect a project to an App Store Connect app
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/integrations/asc/connect.ts](https://github.com/expo/eas-cli/blob/v20.1.0/packages/eas-cli/src/commands/integrations/asc/connect.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/integrations/asc/connect.ts](https://github.com/expo/eas-cli/blob/v20.0.0/packages/eas-cli/src/commands/integrations/asc/connect.ts)_
|
||||
|
||||
## `eas integrations:asc:disconnect`
|
||||
|
||||
@@ -1674,7 +1671,7 @@ DESCRIPTION
|
||||
disconnect the current project from its App Store Connect app
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/integrations/asc/disconnect.ts](https://github.com/expo/eas-cli/blob/v20.1.0/packages/eas-cli/src/commands/integrations/asc/disconnect.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/integrations/asc/disconnect.ts](https://github.com/expo/eas-cli/blob/v20.0.0/packages/eas-cli/src/commands/integrations/asc/disconnect.ts)_
|
||||
|
||||
## `eas integrations:asc:status`
|
||||
|
||||
@@ -1692,7 +1689,7 @@ DESCRIPTION
|
||||
show the App Store Connect app link status for the current project
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/integrations/asc/status.ts](https://github.com/expo/eas-cli/blob/v20.1.0/packages/eas-cli/src/commands/integrations/asc/status.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/integrations/asc/status.ts](https://github.com/expo/eas-cli/blob/v20.0.0/packages/eas-cli/src/commands/integrations/asc/status.ts)_
|
||||
|
||||
## `eas integrations:convex:connect`
|
||||
|
||||
@@ -1714,7 +1711,7 @@ DESCRIPTION
|
||||
connect Convex to your Expo project
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/integrations/convex/connect.ts](https://github.com/expo/eas-cli/blob/v20.1.0/packages/eas-cli/src/commands/integrations/convex/connect.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/integrations/convex/connect.ts](https://github.com/expo/eas-cli/blob/v20.0.0/packages/eas-cli/src/commands/integrations/convex/connect.ts)_
|
||||
|
||||
## `eas integrations:convex:dashboard`
|
||||
|
||||
@@ -1728,7 +1725,7 @@ DESCRIPTION
|
||||
open the Convex dashboard for the linked Convex project
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/integrations/convex/dashboard.ts](https://github.com/expo/eas-cli/blob/v20.1.0/packages/eas-cli/src/commands/integrations/convex/dashboard.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/integrations/convex/dashboard.ts](https://github.com/expo/eas-cli/blob/v20.0.0/packages/eas-cli/src/commands/integrations/convex/dashboard.ts)_
|
||||
|
||||
## `eas integrations:convex:project`
|
||||
|
||||
@@ -1742,7 +1739,7 @@ DESCRIPTION
|
||||
display the Convex project linked to the current Expo app
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/integrations/convex/project.ts](https://github.com/expo/eas-cli/blob/v20.1.0/packages/eas-cli/src/commands/integrations/convex/project.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/integrations/convex/project.ts](https://github.com/expo/eas-cli/blob/v20.0.0/packages/eas-cli/src/commands/integrations/convex/project.ts)_
|
||||
|
||||
## `eas integrations:convex:project:delete`
|
||||
|
||||
@@ -1760,7 +1757,7 @@ DESCRIPTION
|
||||
remove the Convex project link for the current Expo app from EAS servers
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/integrations/convex/project/delete.ts](https://github.com/expo/eas-cli/blob/v20.1.0/packages/eas-cli/src/commands/integrations/convex/project/delete.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/integrations/convex/project/delete.ts](https://github.com/expo/eas-cli/blob/v20.0.0/packages/eas-cli/src/commands/integrations/convex/project/delete.ts)_
|
||||
|
||||
## `eas integrations:convex:team`
|
||||
|
||||
@@ -1774,7 +1771,7 @@ DESCRIPTION
|
||||
display Convex teams linked to the current Expo app's owner account
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/integrations/convex/team.ts](https://github.com/expo/eas-cli/blob/v20.1.0/packages/eas-cli/src/commands/integrations/convex/team.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/integrations/convex/team.ts](https://github.com/expo/eas-cli/blob/v20.0.0/packages/eas-cli/src/commands/integrations/convex/team.ts)_
|
||||
|
||||
## `eas integrations:convex:team:delete [CONVEX_TEAM]`
|
||||
|
||||
@@ -1795,7 +1792,7 @@ DESCRIPTION
|
||||
remove a Convex team link from the current Expo app owner account's EAS servers
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/integrations/convex/team/delete.ts](https://github.com/expo/eas-cli/blob/v20.1.0/packages/eas-cli/src/commands/integrations/convex/team/delete.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/integrations/convex/team/delete.ts](https://github.com/expo/eas-cli/blob/v20.0.0/packages/eas-cli/src/commands/integrations/convex/team/delete.ts)_
|
||||
|
||||
## `eas integrations:convex:team:invite [CONVEX_TEAM]`
|
||||
|
||||
@@ -1815,7 +1812,7 @@ DESCRIPTION
|
||||
send a Convex team invitation to your verified email address
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/integrations/convex/team/invite.ts](https://github.com/expo/eas-cli/blob/v20.1.0/packages/eas-cli/src/commands/integrations/convex/team/invite.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/integrations/convex/team/invite.ts](https://github.com/expo/eas-cli/blob/v20.0.0/packages/eas-cli/src/commands/integrations/convex/team/invite.ts)_
|
||||
|
||||
## `eas login`
|
||||
|
||||
@@ -1867,7 +1864,7 @@ DESCRIPTION
|
||||
validate the local store configuration
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/metadata/lint.ts](https://github.com/expo/eas-cli/blob/v20.1.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/v20.0.0/packages/eas-cli/src/commands/metadata/lint.ts)_
|
||||
|
||||
## `eas metadata:pull`
|
||||
|
||||
@@ -1885,7 +1882,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/v20.1.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/v20.0.0/packages/eas-cli/src/commands/metadata/pull.ts)_
|
||||
|
||||
## `eas metadata:push`
|
||||
|
||||
@@ -1903,7 +1900,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/v20.1.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/v20.0.0/packages/eas-cli/src/commands/metadata/push.ts)_
|
||||
|
||||
## `eas new [PATH]`
|
||||
|
||||
@@ -1962,7 +1959,7 @@ DESCRIPTION
|
||||
arguments, a list of the available event names and associated event counts is returned.
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/observe/events.ts](https://github.com/expo/eas-cli/blob/v20.1.0/packages/eas-cli/src/commands/observe/events.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/observe/events.ts](https://github.com/expo/eas-cli/blob/v20.0.0/packages/eas-cli/src/commands/observe/events.ts)_
|
||||
|
||||
## `eas observe:metrics [METRIC]`
|
||||
|
||||
@@ -1997,7 +1994,7 @@ DESCRIPTION
|
||||
display individual performance metric samples ordered by value
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/observe/metrics.ts](https://github.com/expo/eas-cli/blob/v20.1.0/packages/eas-cli/src/commands/observe/metrics.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/observe/metrics.ts](https://github.com/expo/eas-cli/blob/v20.0.0/packages/eas-cli/src/commands/observe/metrics.ts)_
|
||||
|
||||
## `eas observe:metrics-summary`
|
||||
|
||||
@@ -2028,7 +2025,7 @@ DESCRIPTION
|
||||
display aggregated performance metric statistics grouped by app version
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/observe/metrics-summary.ts](https://github.com/expo/eas-cli/blob/v20.1.0/packages/eas-cli/src/commands/observe/metrics-summary.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/observe/metrics-summary.ts](https://github.com/expo/eas-cli/blob/v20.0.0/packages/eas-cli/src/commands/observe/metrics-summary.ts)_
|
||||
|
||||
## `eas observe:routes`
|
||||
|
||||
@@ -2065,7 +2062,7 @@ DESCRIPTION
|
||||
display app navigation route metrics (Cold TTR, Warm TTR, TTI) grouped by route name
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/observe/routes.ts](https://github.com/expo/eas-cli/blob/v20.1.0/packages/eas-cli/src/commands/observe/routes.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/observe/routes.ts](https://github.com/expo/eas-cli/blob/v20.0.0/packages/eas-cli/src/commands/observe/routes.ts)_
|
||||
|
||||
## `eas observe:versions`
|
||||
|
||||
@@ -2090,7 +2087,7 @@ DESCRIPTION
|
||||
display app versions with build and update details
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/observe/versions.ts](https://github.com/expo/eas-cli/blob/v20.1.0/packages/eas-cli/src/commands/observe/versions.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/observe/versions.ts](https://github.com/expo/eas-cli/blob/v20.0.0/packages/eas-cli/src/commands/observe/versions.ts)_
|
||||
|
||||
## `eas onboarding [TARGET_PROJECT_DIRECTORY]`
|
||||
|
||||
@@ -2120,7 +2117,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/v20.1.0/packages/eas-cli/src/commands/open.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/open.ts](https://github.com/expo/eas-cli/blob/v20.0.0/packages/eas-cli/src/commands/open.ts)_
|
||||
|
||||
## `eas project:info`
|
||||
|
||||
@@ -2134,7 +2131,7 @@ DESCRIPTION
|
||||
information about the current project
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v20.1.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/v20.0.0/packages/eas-cli/src/commands/project/info.ts)_
|
||||
|
||||
## `eas project:init`
|
||||
|
||||
@@ -2157,7 +2154,7 @@ ALIASES
|
||||
$ eas init
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v20.1.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/v20.0.0/packages/eas-cli/src/commands/project/init.ts)_
|
||||
|
||||
## `eas project:new [PATH]`
|
||||
|
||||
@@ -2181,7 +2178,7 @@ ALIASES
|
||||
$ eas new
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/project/new.ts](https://github.com/expo/eas-cli/blob/v20.1.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/v20.0.0/packages/eas-cli/src/commands/project/new.ts)_
|
||||
|
||||
## `eas project:onboarding [TARGET_PROJECT_DIRECTORY]`
|
||||
|
||||
@@ -2199,7 +2196,7 @@ ALIASES
|
||||
$ eas onboarding
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/project/onboarding.ts](https://github.com/expo/eas-cli/blob/v20.1.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/v20.0.0/packages/eas-cli/src/commands/project/onboarding.ts)_
|
||||
|
||||
## `eas submit`
|
||||
|
||||
@@ -2232,7 +2229,7 @@ ALIASES
|
||||
$ eas build:submit
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v20.1.0/packages/eas-cli/src/commands/submit.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v20.0.0/packages/eas-cli/src/commands/submit.ts)_
|
||||
|
||||
## `eas update`
|
||||
|
||||
@@ -2275,7 +2272,7 @@ DESCRIPTION
|
||||
publish an update group
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v20.1.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/v20.0.0/packages/eas-cli/src/commands/update/index.ts)_
|
||||
|
||||
## `eas update:configure`
|
||||
|
||||
@@ -2296,7 +2293,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/v20.1.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/v20.0.0/packages/eas-cli/src/commands/update/configure.ts)_
|
||||
|
||||
## `eas update:delete GROUPID`
|
||||
|
||||
@@ -2317,7 +2314,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/v20.1.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/v20.0.0/packages/eas-cli/src/commands/update/delete.ts)_
|
||||
|
||||
## `eas update:edit [GROUPID]`
|
||||
|
||||
@@ -2342,54 +2339,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/v20.1.0/packages/eas-cli/src/commands/update/edit.ts)_
|
||||
|
||||
## `eas update:embedded:delete ID`
|
||||
|
||||
delete an embedded update registered with EAS Update
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas update:embedded:delete ID [--json] [--non-interactive]
|
||||
|
||||
ARGUMENTS
|
||||
ID The ID of the embedded update (manifest UUID from app.manifest).
|
||||
|
||||
FLAGS
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr. Implies --non-interactive.
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
|
||||
DESCRIPTION
|
||||
delete an embedded update registered with EAS Update
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/embedded/delete.ts](https://github.com/expo/eas-cli/blob/v20.1.0/packages/eas-cli/src/commands/update/embedded/delete.ts)_
|
||||
|
||||
## `eas update:embedded:list`
|
||||
|
||||
list embedded updates registered with EAS Update for this project
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas update:embedded:list [-p ios|android] [--runtime-version <value>] [--channel <value>] [--limit <value>]
|
||||
[--after-cursor <value>] [--json] [--non-interactive]
|
||||
|
||||
FLAGS
|
||||
-p, --platform=<option> Filter by platform
|
||||
<options: ios|android>
|
||||
--after-cursor=<value> Return items after this cursor (for pagination)
|
||||
--channel=<value> Filter by channel name (pass "all" to skip the channel prompt)
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr. Implies
|
||||
--non-interactive.
|
||||
--limit=<value> The number of items to fetch each query. Defaults to 25 and is capped at 50.
|
||||
--non-interactive Run the command in non-interactive mode.
|
||||
--runtime-version=<value> Filter by runtime version
|
||||
|
||||
DESCRIPTION
|
||||
list embedded updates registered with EAS Update for this project
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/embedded/list.ts](https://github.com/expo/eas-cli/blob/v20.1.0/packages/eas-cli/src/commands/update/embedded/list.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/update/edit.ts](https://github.com/expo/eas-cli/blob/v20.0.0/packages/eas-cli/src/commands/update/edit.ts)_
|
||||
|
||||
## `eas update:embedded:upload`
|
||||
|
||||
@@ -2419,27 +2369,7 @@ EXAMPLES
|
||||
$ eas update:embedded:upload --platform android --bundle android/app/src/main/assets/index.android.bundle --manifest android/app/src/main/assets/app.manifest --channel production --build-id <BUILD-ID>
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/embedded/upload.ts](https://github.com/expo/eas-cli/blob/v20.1.0/packages/eas-cli/src/commands/update/embedded/upload.ts)_
|
||||
|
||||
## `eas update:embedded:view ID`
|
||||
|
||||
view details of an embedded update registered with EAS Update
|
||||
|
||||
```
|
||||
USAGE
|
||||
$ eas update:embedded:view ID [--json]
|
||||
|
||||
ARGUMENTS
|
||||
ID The ID of the embedded update (manifest UUID from app.manifest).
|
||||
|
||||
FLAGS
|
||||
--json Enable JSON output, non-JSON messages will be printed to stderr.
|
||||
|
||||
DESCRIPTION
|
||||
view details of an embedded update registered with EAS Update
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/embedded/view.ts](https://github.com/expo/eas-cli/blob/v20.1.0/packages/eas-cli/src/commands/update/embedded/view.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/update/embedded/upload.ts](https://github.com/expo/eas-cli/blob/v20.0.0/packages/eas-cli/src/commands/update/embedded/upload.ts)_
|
||||
|
||||
## `eas update:insights GROUPID`
|
||||
|
||||
@@ -2466,7 +2396,7 @@ DESCRIPTION
|
||||
display launch, crash, unique-user, and size insights for an update group
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/insights.ts](https://github.com/expo/eas-cli/blob/v20.1.0/packages/eas-cli/src/commands/update/insights.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/update/insights.ts](https://github.com/expo/eas-cli/blob/v20.0.0/packages/eas-cli/src/commands/update/insights.ts)_
|
||||
|
||||
## `eas update:list`
|
||||
|
||||
@@ -2493,7 +2423,7 @@ DESCRIPTION
|
||||
view the recent updates
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v20.1.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/v20.0.0/packages/eas-cli/src/commands/update/list.ts)_
|
||||
|
||||
## `eas update:republish`
|
||||
|
||||
@@ -2531,7 +2461,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/v20.1.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/v20.0.0/packages/eas-cli/src/commands/update/republish.ts)_
|
||||
|
||||
## `eas update:revert-update-rollout`
|
||||
|
||||
@@ -2559,7 +2489,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/v20.1.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/v20.0.0/packages/eas-cli/src/commands/update/revert-update-rollout.ts)_
|
||||
|
||||
## `eas update:roll-back-to-embedded`
|
||||
|
||||
@@ -2589,7 +2519,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/v20.1.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/v20.0.0/packages/eas-cli/src/commands/update/roll-back-to-embedded.ts)_
|
||||
|
||||
## `eas update:rollback`
|
||||
|
||||
@@ -2610,7 +2540,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/v20.1.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/v20.0.0/packages/eas-cli/src/commands/update/rollback.ts)_
|
||||
|
||||
## `eas update:view GROUPID`
|
||||
|
||||
@@ -2634,7 +2564,7 @@ DESCRIPTION
|
||||
update group details
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v20.1.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/v20.0.0/packages/eas-cli/src/commands/update/view.ts)_
|
||||
|
||||
## `eas upload`
|
||||
|
||||
@@ -2655,7 +2585,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/v20.1.0/packages/eas-cli/src/commands/upload.ts)_
|
||||
_See code: [packages/eas-cli/src/commands/upload.ts](https://github.com/expo/eas-cli/blob/v20.0.0/packages/eas-cli/src/commands/upload.ts)_
|
||||
|
||||
## `eas webhook:create`
|
||||
|
||||
@@ -2677,7 +2607,7 @@ DESCRIPTION
|
||||
create a webhook
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v20.1.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/v20.0.0/packages/eas-cli/src/commands/webhook/create.ts)_
|
||||
|
||||
## `eas webhook:delete [ID]`
|
||||
|
||||
@@ -2697,7 +2627,7 @@ DESCRIPTION
|
||||
delete a webhook
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v20.1.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/v20.0.0/packages/eas-cli/src/commands/webhook/delete.ts)_
|
||||
|
||||
## `eas webhook:list`
|
||||
|
||||
@@ -2716,7 +2646,7 @@ DESCRIPTION
|
||||
list webhooks
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v20.1.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/v20.0.0/packages/eas-cli/src/commands/webhook/list.ts)_
|
||||
|
||||
## `eas webhook:update`
|
||||
|
||||
@@ -2739,7 +2669,7 @@ DESCRIPTION
|
||||
update a webhook
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v20.1.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/v20.0.0/packages/eas-cli/src/commands/webhook/update.ts)_
|
||||
|
||||
## `eas webhook:view ID`
|
||||
|
||||
@@ -2756,7 +2686,7 @@ DESCRIPTION
|
||||
view a webhook
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v20.1.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/v20.0.0/packages/eas-cli/src/commands/webhook/view.ts)_
|
||||
|
||||
## `eas whoami`
|
||||
|
||||
@@ -2877,7 +2807,7 @@ DESCRIPTION
|
||||
to cancel.
|
||||
```
|
||||
|
||||
_See code: [packages/eas-cli/src/commands/workflow/cancel.ts](https://github.com/expo/eas-cli/blob/v20.1.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/v20.0.0/packages/eas-cli/src/commands/workflow/cancel.ts)_
|
||||
|
||||
## `eas workflow:create [NAME]`
|
||||
|
||||
@@ -2897,7 +2827,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/v20.1.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/v20.0.0/packages/eas-cli/src/commands/workflow/create.ts)_
|
||||
|
||||
## `eas workflow:logs [ID]`
|
||||
|
||||
@@ -2921,7 +2851,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/v20.1.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/v20.0.0/packages/eas-cli/src/commands/workflow/logs.ts)_
|
||||
|
||||
## `eas workflow:run FILE`
|
||||
|
||||
@@ -2961,7 +2891,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/v20.1.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/v20.0.0/packages/eas-cli/src/commands/workflow/run.ts)_
|
||||
|
||||
## `eas workflow:runs`
|
||||
|
||||
@@ -2983,7 +2913,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/v20.1.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/v20.0.0/packages/eas-cli/src/commands/workflow/runs.ts)_
|
||||
|
||||
## `eas workflow:status [WORKFLOW_RUN_ID]`
|
||||
|
||||
@@ -3011,7 +2941,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/v20.1.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/v20.0.0/packages/eas-cli/src/commands/workflow/status.ts)_
|
||||
|
||||
## `eas workflow:validate PATH`
|
||||
|
||||
@@ -3031,7 +2961,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/v20.1.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/v20.0.0/packages/eas-cli/src/commands/workflow/validate.ts)_
|
||||
|
||||
## `eas workflow:view [ID]`
|
||||
|
||||
@@ -3053,5 +2983,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/v20.1.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/v20.0.0/packages/eas-cli/src/commands/workflow/view.ts)_
|
||||
<!-- commandsstop -->
|
||||
Generated
+13
-2186
File diff suppressed because it is too large.
Load diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "eas-cli",
|
||||
"version": "20.1.0",
|
||||
"version": "20.0.0",
|
||||
"description": "EAS command line tool",
|
||||
"keywords": [
|
||||
"cli",
|
||||
@@ -47,8 +47,8 @@
|
||||
"@expo/code-signing-certificates": "0.0.5",
|
||||
"@expo/config": "55.0.10",
|
||||
"@expo/config-plugins": "55.0.7",
|
||||
"@expo/eas-build-job": "20.1.0",
|
||||
"@expo/eas-json": "20.1.0",
|
||||
"@expo/eas-build-job": "20.0.0",
|
||||
"@expo/eas-json": "20.0.0",
|
||||
"@expo/env": "^1.0.0",
|
||||
"@expo/json-file": "8.3.3",
|
||||
"@expo/logger": "20.0.0",
|
||||
@@ -63,7 +63,7 @@
|
||||
"@expo/results": "1.0.0",
|
||||
"@expo/rudder-sdk-node": "1.1.1",
|
||||
"@expo/spawn-async": "1.7.2",
|
||||
"@expo/steps": "20.1.0",
|
||||
"@expo/steps": "20.0.0",
|
||||
"@expo/timeago.js": "1.0.0",
|
||||
"@oclif/core": "^4.8.3",
|
||||
"@oclif/plugin-autocomplete": "^3.2.40",
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
import { getMockOclifConfig } from '../../../../__tests__/commands/utils';
|
||||
import { ExpoGraphqlClient } from '../../../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { EmbeddedUpdateMutation } from '../../../../graphql/mutations/EmbeddedUpdateMutation';
|
||||
import Log from '../../../../log';
|
||||
import * as prompts from '../../../../prompts';
|
||||
import * as json from '../../../../utils/json';
|
||||
import UpdateEmbeddedDelete from '../delete';
|
||||
|
||||
jest.mock('../../../../graphql/mutations/EmbeddedUpdateMutation', () => ({
|
||||
EmbeddedUpdateMutation: { deleteEmbeddedUpdateAsync: jest.fn() },
|
||||
}));
|
||||
jest.mock('../../../../log');
|
||||
jest.mock('../../../../utils/json');
|
||||
jest.mock('../../../../prompts');
|
||||
|
||||
const mockDelete = jest.mocked(EmbeddedUpdateMutation.deleteEmbeddedUpdateAsync);
|
||||
const mockToggleConfirm = jest.mocked(prompts.toggleConfirmAsync);
|
||||
const mockLogLog = jest.mocked(Log.log);
|
||||
const mockLogWithTick = jest.mocked(Log.withTick);
|
||||
const mockLogError = jest.mocked(Log.error);
|
||||
const mockEnableJsonOutput = jest.mocked(json.enableJsonOutput);
|
||||
const mockPrintJson = jest.mocked(json.printJsonOnlyOutput);
|
||||
|
||||
const VALID_UUID = 'a1b2c3d4-1234-4000-8000-000000000000';
|
||||
|
||||
const MOCK_CONTEXT = {
|
||||
loggedIn: { graphqlClient: {} as ExpoGraphqlClient },
|
||||
};
|
||||
|
||||
describe(UpdateEmbeddedDelete, () => {
|
||||
const mockConfig = getMockOclifConfig();
|
||||
let processExitSpy: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockDelete.mockResolvedValue({ id: VALID_UUID });
|
||||
mockToggleConfirm.mockResolvedValue(true);
|
||||
processExitSpy = jest.spyOn(process, 'exit').mockImplementation((() => {
|
||||
throw new Error('process.exit');
|
||||
}) as never);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
processExitSpy.mockRestore();
|
||||
});
|
||||
|
||||
function createCommand(argv: string[]): UpdateEmbeddedDelete {
|
||||
const command = new UpdateEmbeddedDelete(argv, mockConfig);
|
||||
// @ts-expect-error getContextAsync is protected
|
||||
jest.spyOn(command, 'getContextAsync').mockResolvedValue(MOCK_CONTEXT);
|
||||
return command;
|
||||
}
|
||||
|
||||
it('deletes when the user confirms', async () => {
|
||||
await createCommand([VALID_UUID]).run();
|
||||
|
||||
expect(mockToggleConfirm).toHaveBeenCalledTimes(1);
|
||||
expect(mockDelete).toHaveBeenCalledWith(MOCK_CONTEXT.loggedIn.graphqlClient, {
|
||||
id: VALID_UUID,
|
||||
});
|
||||
expect(mockLogWithTick).toHaveBeenCalledWith(`Deleted embedded update ${VALID_UUID}`);
|
||||
});
|
||||
|
||||
it('skips confirmation in non-interactive mode', async () => {
|
||||
await createCommand([VALID_UUID, '--non-interactive']).run();
|
||||
|
||||
expect(mockToggleConfirm).not.toHaveBeenCalled();
|
||||
expect(mockDelete).toHaveBeenCalledWith(MOCK_CONTEXT.loggedIn.graphqlClient, {
|
||||
id: VALID_UUID,
|
||||
});
|
||||
});
|
||||
|
||||
it('aborts (process.exit) without calling the mutation when the user declines', async () => {
|
||||
mockToggleConfirm.mockResolvedValue(false);
|
||||
await expect(createCommand([VALID_UUID]).run()).rejects.toThrow('process.exit');
|
||||
|
||||
expect(mockDelete).not.toHaveBeenCalled();
|
||||
expect(mockLogWithTick).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('--json prints { id } and skips the human-readable success line', async () => {
|
||||
await createCommand([VALID_UUID, '--non-interactive', '--json']).run();
|
||||
|
||||
expect(mockEnableJsonOutput).toHaveBeenCalled();
|
||||
expect(mockPrintJson).toHaveBeenCalledWith({ id: VALID_UUID });
|
||||
expect(mockLogWithTick).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('rethrows unexpected mutation errors', async () => {
|
||||
const boom = new Error('boom');
|
||||
mockDelete.mockRejectedValue(boom);
|
||||
await expect(createCommand([VALID_UUID, '--non-interactive']).run()).rejects.toThrow('boom');
|
||||
});
|
||||
|
||||
it('succeeds for an unknown id (server best-effort)', async () => {
|
||||
// Server returns { id } even when nothing was deleted, so we should still tick.
|
||||
await createCommand([VALID_UUID, '--non-interactive']).run();
|
||||
expect(mockLogWithTick).toHaveBeenCalledWith(`Deleted embedded update ${VALID_UUID}`);
|
||||
});
|
||||
|
||||
it('prints an explanatory message before prompting (interactive)', async () => {
|
||||
await createCommand([VALID_UUID]).run();
|
||||
const lines = mockLogLog.mock.calls.map(c => String(c[0])).join('\n');
|
||||
expect(lines).toContain(`permanently delete embedded update: "${VALID_UUID}"`);
|
||||
expect(lines).toContain('Diff patches already generated');
|
||||
expect(lines).toContain('re-upload');
|
||||
});
|
||||
|
||||
it('logs a cancel message when the user declines', async () => {
|
||||
mockToggleConfirm.mockResolvedValue(false);
|
||||
await expect(createCommand([VALID_UUID]).run()).rejects.toThrow('process.exit');
|
||||
expect(mockLogError).toHaveBeenCalledWith(
|
||||
`Canceled deletion of embedded update: "${VALID_UUID}".`
|
||||
);
|
||||
});
|
||||
|
||||
it('passes the id through to deleteEmbeddedUpdateAsync', async () => {
|
||||
await createCommand([VALID_UUID, '--non-interactive']).run();
|
||||
expect(mockDelete).toHaveBeenCalledWith(MOCK_CONTEXT.loggedIn.graphqlClient, {
|
||||
id: VALID_UUID,
|
||||
});
|
||||
});
|
||||
|
||||
it('--json + --non-interactive: skips prompt, skips tick, prints { id }', async () => {
|
||||
await createCommand([VALID_UUID, '--non-interactive', '--json']).run();
|
||||
expect(mockToggleConfirm).not.toHaveBeenCalled();
|
||||
expect(mockLogWithTick).not.toHaveBeenCalled();
|
||||
expect(mockPrintJson).toHaveBeenCalledWith({ id: VALID_UUID });
|
||||
});
|
||||
});
|
||||
@@ -1,280 +0,0 @@
|
||||
import { getMockOclifConfig } from '../../../../__tests__/commands/utils';
|
||||
import { ExpoGraphqlClient } from '../../../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { AppPlatform } from '../../../../graphql/generated';
|
||||
import { ChannelQuery } from '../../../../graphql/queries/ChannelQuery';
|
||||
import {
|
||||
EmbeddedUpdateFragment,
|
||||
EmbeddedUpdateQuery,
|
||||
} from '../../../../graphql/queries/EmbeddedUpdateQuery';
|
||||
import Log from '../../../../log';
|
||||
import { selectAsync } from '../../../../prompts';
|
||||
import * as json from '../../../../utils/json';
|
||||
import UpdateEmbeddedList from '../list';
|
||||
|
||||
jest.mock('../../../../graphql/queries/EmbeddedUpdateQuery', () => ({
|
||||
EmbeddedUpdateQuery: { viewPaginatedAsync: jest.fn() },
|
||||
}));
|
||||
jest.mock('../../../../graphql/queries/ChannelQuery', () => ({
|
||||
ChannelQuery: { viewUpdateChannelsOnAppAsync: jest.fn() },
|
||||
}));
|
||||
jest.mock('../../../../prompts');
|
||||
jest.mock('../../../../log');
|
||||
jest.mock('../../../../utils/json');
|
||||
|
||||
const mockPaginated = jest.mocked(EmbeddedUpdateQuery.viewPaginatedAsync);
|
||||
const mockViewChannels = jest.mocked(ChannelQuery.viewUpdateChannelsOnAppAsync);
|
||||
const mockSelectAsync = jest.mocked(selectAsync);
|
||||
const mockLogLog = jest.mocked(Log.log);
|
||||
const mockEnableJsonOutput = jest.mocked(json.enableJsonOutput);
|
||||
const mockPrintJson = jest.mocked(json.printJsonOnlyOutput);
|
||||
|
||||
const MOCK_CONTEXT = {
|
||||
projectId: 'project-123',
|
||||
loggedIn: { graphqlClient: {} as ExpoGraphqlClient },
|
||||
};
|
||||
|
||||
const ROW_A: EmbeddedUpdateFragment = {
|
||||
id: 'aaaaaaaa-1111-4000-8000-000000000001',
|
||||
platform: AppPlatform.Ios,
|
||||
runtimeVersion: '1.0.0',
|
||||
channel: 'production',
|
||||
createdAt: '2026-05-29T00:00:00Z',
|
||||
launchAsset: { id: 'asset-a', fileSize: 1024, finalFileSize: 768, fileSHA256: 'abc123' },
|
||||
};
|
||||
const ROW_B: EmbeddedUpdateFragment = {
|
||||
id: 'bbbbbbbb-2222-4000-8000-000000000002',
|
||||
platform: AppPlatform.Android,
|
||||
runtimeVersion: '1.0.1',
|
||||
channel: 'preview',
|
||||
createdAt: '2026-05-30T00:00:00Z',
|
||||
launchAsset: { id: 'asset-b', fileSize: 2048, finalFileSize: null, fileSHA256: 'def456' },
|
||||
};
|
||||
|
||||
function emptyConnection(): {
|
||||
edges: { cursor: string; node: EmbeddedUpdateFragment }[];
|
||||
pageInfo: {
|
||||
hasNextPage: boolean;
|
||||
hasPreviousPage: boolean;
|
||||
startCursor: string | null;
|
||||
endCursor: string | null;
|
||||
};
|
||||
} {
|
||||
return {
|
||||
edges: [],
|
||||
pageInfo: { hasNextPage: false, hasPreviousPage: false, startCursor: null, endCursor: null },
|
||||
};
|
||||
}
|
||||
|
||||
describe(UpdateEmbeddedList, () => {
|
||||
const mockConfig = getMockOclifConfig();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
// Default: no channels on the project (skips the prompt).
|
||||
mockViewChannels.mockResolvedValue([]);
|
||||
});
|
||||
|
||||
function createCommand(argv: string[]): UpdateEmbeddedList {
|
||||
const command = new UpdateEmbeddedList(argv, mockConfig);
|
||||
// @ts-expect-error getContextAsync is protected
|
||||
jest.spyOn(command, 'getContextAsync').mockResolvedValue(MOCK_CONTEXT);
|
||||
return command;
|
||||
}
|
||||
|
||||
it('prints each row when results exist', async () => {
|
||||
mockPaginated.mockResolvedValue({
|
||||
edges: [
|
||||
{ cursor: 'c1', node: ROW_A },
|
||||
{ cursor: 'c2', node: ROW_B },
|
||||
],
|
||||
pageInfo: { hasNextPage: false, hasPreviousPage: false, startCursor: 'c1', endCursor: 'c2' },
|
||||
});
|
||||
await createCommand(['--non-interactive']).run();
|
||||
|
||||
expect(mockPaginated).toHaveBeenCalledWith(MOCK_CONTEXT.loggedIn.graphqlClient, {
|
||||
appId: MOCK_CONTEXT.projectId,
|
||||
filter: undefined,
|
||||
first: 25,
|
||||
after: undefined,
|
||||
});
|
||||
expect(mockLogLog.mock.calls.some(c => String(c[0]).includes(ROW_A.id))).toBe(true);
|
||||
expect(mockLogLog.mock.calls.some(c => String(c[0]).includes(ROW_B.id))).toBe(true);
|
||||
});
|
||||
|
||||
it('prints empty message when no results', async () => {
|
||||
mockPaginated.mockResolvedValue(emptyConnection());
|
||||
await createCommand(['--non-interactive']).run();
|
||||
expect(mockLogLog).toHaveBeenCalledWith('No embedded updates found.');
|
||||
});
|
||||
|
||||
it('--json prints connection payload and skips formatted output', async () => {
|
||||
mockPaginated.mockResolvedValue({
|
||||
edges: [{ cursor: 'c1', node: ROW_A }],
|
||||
pageInfo: { hasNextPage: false, hasPreviousPage: false, startCursor: 'c1', endCursor: 'c1' },
|
||||
});
|
||||
await createCommand(['--json', '--non-interactive']).run();
|
||||
|
||||
expect(mockEnableJsonOutput).toHaveBeenCalled();
|
||||
expect(mockPrintJson).toHaveBeenCalledWith({
|
||||
embeddedUpdates: [ROW_A],
|
||||
pageInfo: { hasNextPage: false, hasPreviousPage: false, startCursor: 'c1', endCursor: 'c1' },
|
||||
});
|
||||
expect(mockLogLog.mock.calls.every(c => !String(c[0]).includes(ROW_A.id))).toBe(true);
|
||||
});
|
||||
|
||||
it('passes filter when flags supplied', async () => {
|
||||
mockPaginated.mockResolvedValue(emptyConnection());
|
||||
await createCommand([
|
||||
'--non-interactive',
|
||||
'--platform',
|
||||
'ios',
|
||||
'--runtime-version',
|
||||
'1.2.0',
|
||||
'--channel',
|
||||
'preview',
|
||||
]).run();
|
||||
|
||||
expect(mockPaginated).toHaveBeenCalledWith(MOCK_CONTEXT.loggedIn.graphqlClient, {
|
||||
appId: MOCK_CONTEXT.projectId,
|
||||
filter: { platform: AppPlatform.Ios, runtimeVersion: '1.2.0', channel: 'preview' },
|
||||
first: 25,
|
||||
after: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('shows next-page hint when hasNextPage', async () => {
|
||||
mockPaginated.mockResolvedValue({
|
||||
edges: [{ cursor: 'c1', node: ROW_A }],
|
||||
pageInfo: { hasNextPage: true, hasPreviousPage: false, startCursor: 'c1', endCursor: 'c1' },
|
||||
});
|
||||
await createCommand(['--non-interactive']).run();
|
||||
|
||||
expect(mockLogLog.mock.calls.some(c => String(c[0]).includes('--after-cursor c1'))).toBe(true);
|
||||
});
|
||||
|
||||
it('prompts for channel in interactive mode and applies the selected channel', async () => {
|
||||
mockViewChannels.mockResolvedValue([
|
||||
{ id: 'ch1', name: 'production' } as any,
|
||||
{ id: 'ch2', name: 'preview' } as any,
|
||||
]);
|
||||
mockSelectAsync.mockResolvedValue('preview');
|
||||
mockPaginated.mockResolvedValue(emptyConnection());
|
||||
|
||||
await createCommand([]).run();
|
||||
|
||||
expect(mockSelectAsync).toHaveBeenCalledTimes(1);
|
||||
expect(mockPaginated).toHaveBeenCalledWith(MOCK_CONTEXT.loggedIn.graphqlClient, {
|
||||
appId: MOCK_CONTEXT.projectId,
|
||||
filter: { platform: undefined, runtimeVersion: undefined, channel: 'preview' },
|
||||
first: 25,
|
||||
after: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('skips channel filter when "All channels" is selected', async () => {
|
||||
mockViewChannels.mockResolvedValue([{ id: 'ch1', name: 'production' } as any]);
|
||||
// The sentinel value returned by selectAsync for the "All channels" option.
|
||||
mockSelectAsync.mockResolvedValue('__embedded_update_list__all_channels__');
|
||||
mockPaginated.mockResolvedValue(emptyConnection());
|
||||
|
||||
await createCommand([]).run();
|
||||
|
||||
expect(mockPaginated).toHaveBeenCalledWith(MOCK_CONTEXT.loggedIn.graphqlClient, {
|
||||
appId: MOCK_CONTEXT.projectId,
|
||||
filter: undefined,
|
||||
first: 25,
|
||||
after: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('skips the channel prompt when --channel is supplied', async () => {
|
||||
mockPaginated.mockResolvedValue(emptyConnection());
|
||||
await createCommand(['--channel', 'preview']).run();
|
||||
expect(mockSelectAsync).not.toHaveBeenCalled();
|
||||
expect(mockViewChannels).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('treats --channel all as no channel filter', async () => {
|
||||
mockPaginated.mockResolvedValue(emptyConnection());
|
||||
await createCommand(['--channel', 'all']).run();
|
||||
expect(mockPaginated).toHaveBeenCalledWith(MOCK_CONTEXT.loggedIn.graphqlClient, {
|
||||
appId: MOCK_CONTEXT.projectId,
|
||||
filter: undefined,
|
||||
first: 25,
|
||||
after: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('skips the channel prompt when the project has no channels', async () => {
|
||||
mockViewChannels.mockResolvedValue([]);
|
||||
mockPaginated.mockResolvedValue(emptyConnection());
|
||||
await createCommand([]).run();
|
||||
expect(mockSelectAsync).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('passes --after-cursor through to viewPaginatedAsync', async () => {
|
||||
mockPaginated.mockResolvedValue(emptyConnection());
|
||||
await createCommand(['--non-interactive', '--after-cursor', 'cursor-123']).run();
|
||||
expect(mockPaginated).toHaveBeenCalledWith(MOCK_CONTEXT.loggedIn.graphqlClient, {
|
||||
appId: MOCK_CONTEXT.projectId,
|
||||
filter: undefined,
|
||||
first: 25,
|
||||
after: 'cursor-123',
|
||||
});
|
||||
});
|
||||
|
||||
it('honors --limit', async () => {
|
||||
mockPaginated.mockResolvedValue(emptyConnection());
|
||||
await createCommand(['--non-interactive', '--limit', '5']).run();
|
||||
expect(mockPaginated).toHaveBeenCalledWith(
|
||||
MOCK_CONTEXT.loggedIn.graphqlClient,
|
||||
expect.objectContaining({ first: 5 })
|
||||
);
|
||||
});
|
||||
|
||||
it('prints the section header with row count', async () => {
|
||||
mockPaginated.mockResolvedValue({
|
||||
edges: [{ cursor: 'c1', node: ROW_A }],
|
||||
pageInfo: { hasNextPage: false, hasPreviousPage: false, startCursor: 'c1', endCursor: 'c1' },
|
||||
});
|
||||
await createCommand(['--non-interactive']).run();
|
||||
expect(mockLogLog.mock.calls.some(c => String(c[0]).includes('Embedded updates (1)'))).toBe(
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
it('suffixes the section header count with "+" when hasNextPage', async () => {
|
||||
mockPaginated.mockResolvedValue({
|
||||
edges: [{ cursor: 'c1', node: ROW_A }],
|
||||
pageInfo: { hasNextPage: true, hasPreviousPage: false, startCursor: 'c1', endCursor: 'c1' },
|
||||
});
|
||||
await createCommand(['--non-interactive']).run();
|
||||
expect(mockLogLog.mock.calls.some(c => String(c[0]).includes('Embedded updates (1+)'))).toBe(
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
it('shows relative-time "ago" hints in formatted rows', async () => {
|
||||
mockPaginated.mockResolvedValue({
|
||||
edges: [{ cursor: 'c1', node: ROW_A }],
|
||||
pageInfo: { hasNextPage: false, hasPreviousPage: false, startCursor: 'c1', endCursor: 'c1' },
|
||||
});
|
||||
await createCommand(['--non-interactive']).run();
|
||||
expect(mockLogLog.mock.calls.some(c => /ago/.test(String(c[0])))).toBe(true);
|
||||
});
|
||||
|
||||
it('puts "All channels" first in the prompt, then channels in order', async () => {
|
||||
mockViewChannels.mockResolvedValue([
|
||||
{ id: 'ch1', name: 'production' } as any,
|
||||
{ id: 'ch2', name: 'preview' } as any,
|
||||
]);
|
||||
mockSelectAsync.mockResolvedValue('production');
|
||||
mockPaginated.mockResolvedValue(emptyConnection());
|
||||
|
||||
await createCommand([]).run();
|
||||
|
||||
const [, choices] = mockSelectAsync.mock.calls[0];
|
||||
const titles = (choices as any[]).map(c => c.title);
|
||||
expect(titles).toEqual(['All channels', 'production', 'preview']);
|
||||
});
|
||||
});
|
||||
@@ -1,144 +0,0 @@
|
||||
import { CombinedError } from '@urql/core';
|
||||
|
||||
import { getMockOclifConfig } from '../../../../__tests__/commands/utils';
|
||||
import { ExpoGraphqlClient } from '../../../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { AppPlatform } from '../../../../graphql/generated';
|
||||
import {
|
||||
EmbeddedUpdateFragment,
|
||||
EmbeddedUpdateQuery,
|
||||
isEmbeddedUpdateNotFoundError,
|
||||
} from '../../../../graphql/queries/EmbeddedUpdateQuery';
|
||||
import Log from '../../../../log';
|
||||
import * as json from '../../../../utils/json';
|
||||
import UpdateEmbeddedView from '../view';
|
||||
|
||||
jest.mock('../../../../graphql/queries/EmbeddedUpdateQuery', () => ({
|
||||
EmbeddedUpdateQuery: { viewByIdAsync: jest.fn() },
|
||||
isEmbeddedUpdateNotFoundError: jest.fn(),
|
||||
}));
|
||||
jest.mock('../../../../log');
|
||||
jest.mock('../../../../utils/json');
|
||||
|
||||
const mockView = jest.mocked(EmbeddedUpdateQuery.viewByIdAsync);
|
||||
const mockIsNotFound = jest.mocked(isEmbeddedUpdateNotFoundError);
|
||||
const mockLogLog = jest.mocked(Log.log);
|
||||
const mockEnableJsonOutput = jest.mocked(json.enableJsonOutput);
|
||||
const mockPrintJson = jest.mocked(json.printJsonOnlyOutput);
|
||||
|
||||
const VALID_UUID = 'a1b2c3d4-1234-4000-8000-000000000000';
|
||||
|
||||
const MOCK_CONTEXT = {
|
||||
projectId: 'project-123',
|
||||
loggedIn: { graphqlClient: {} as ExpoGraphqlClient },
|
||||
};
|
||||
|
||||
const MOCK_EMBEDDED_UPDATE: EmbeddedUpdateFragment = {
|
||||
id: VALID_UUID,
|
||||
platform: AppPlatform.Ios,
|
||||
runtimeVersion: '1.0.0',
|
||||
channel: 'production',
|
||||
createdAt: '2026-05-29T00:00:00Z',
|
||||
launchAsset: {
|
||||
id: 'asset-1',
|
||||
fileSize: 1024,
|
||||
finalFileSize: 768,
|
||||
fileSHA256: 'abc123',
|
||||
},
|
||||
};
|
||||
|
||||
describe(UpdateEmbeddedView, () => {
|
||||
const mockConfig = getMockOclifConfig();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockView.mockResolvedValue(MOCK_EMBEDDED_UPDATE);
|
||||
mockIsNotFound.mockReturnValue(false);
|
||||
});
|
||||
|
||||
function createCommand(argv: string[]): UpdateEmbeddedView {
|
||||
const command = new UpdateEmbeddedView(argv, mockConfig);
|
||||
// @ts-expect-error getContextAsync is protected
|
||||
jest.spyOn(command, 'getContextAsync').mockResolvedValue(MOCK_CONTEXT);
|
||||
return command;
|
||||
}
|
||||
|
||||
it('prints formatted details on success', async () => {
|
||||
await createCommand([VALID_UUID]).run();
|
||||
|
||||
expect(mockView).toHaveBeenCalledWith(MOCK_CONTEXT.loggedIn.graphqlClient, {
|
||||
embeddedUpdateId: VALID_UUID,
|
||||
appId: MOCK_CONTEXT.projectId,
|
||||
});
|
||||
// Header line + body line
|
||||
expect(mockLogLog).toHaveBeenCalledTimes(2);
|
||||
expect(mockLogLog.mock.calls[0][0]).toContain('Embedded update');
|
||||
expect(mockLogLog.mock.calls[1][0]).toContain(VALID_UUID);
|
||||
expect(mockLogLog.mock.calls[1][0]).toContain('Bundle size');
|
||||
expect(mockLogLog.mock.calls[1][0]).toContain('Bundle SHA-256');
|
||||
});
|
||||
|
||||
it('--json prints the raw embedded update and skips formatted output', async () => {
|
||||
await createCommand([VALID_UUID, '--json']).run();
|
||||
|
||||
expect(mockEnableJsonOutput).toHaveBeenCalled();
|
||||
expect(mockPrintJson).toHaveBeenCalledWith(MOCK_EMBEDDED_UPDATE);
|
||||
expect(mockLogLog).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('exits with a friendly message when the server returns NOT_FOUND', async () => {
|
||||
const notFound = new CombinedError({ graphQLErrors: [] });
|
||||
mockView.mockRejectedValue(notFound);
|
||||
mockIsNotFound.mockReturnValue(true);
|
||||
|
||||
await expect(createCommand([VALID_UUID]).run()).rejects.toThrow();
|
||||
expect(mockIsNotFound).toHaveBeenCalledWith(notFound);
|
||||
});
|
||||
|
||||
it('rethrows unexpected errors', async () => {
|
||||
const boom = new Error('boom');
|
||||
mockView.mockRejectedValue(boom);
|
||||
mockIsNotFound.mockReturnValue(false);
|
||||
|
||||
await expect(createCommand([VALID_UUID]).run()).rejects.toThrow('boom');
|
||||
});
|
||||
|
||||
it('renders bundle size from finalFileSize when present', async () => {
|
||||
mockView.mockResolvedValue({
|
||||
...MOCK_EMBEDDED_UPDATE,
|
||||
launchAsset: { id: 'asset-x', fileSize: 2048, finalFileSize: 768, fileSHA256: 'abc' },
|
||||
});
|
||||
await createCommand([VALID_UUID]).run();
|
||||
// 768 B; not 2.0 KB (which would be fileSize).
|
||||
expect(mockLogLog.mock.calls[1][0]).toContain('768 B');
|
||||
});
|
||||
|
||||
it('falls back to fileSize when finalFileSize is null', async () => {
|
||||
mockView.mockResolvedValue({
|
||||
...MOCK_EMBEDDED_UPDATE,
|
||||
launchAsset: { id: 'asset-y', fileSize: 2048, finalFileSize: null, fileSHA256: 'abc' },
|
||||
});
|
||||
await createCommand([VALID_UUID]).run();
|
||||
expect(mockLogLog.mock.calls[1][0]).toContain('2.0 KB');
|
||||
});
|
||||
|
||||
it('shows runtime version, channel, and SHA-256 in the body', async () => {
|
||||
await createCommand([VALID_UUID]).run();
|
||||
const body = String(mockLogLog.mock.calls[1][0]);
|
||||
expect(body).toContain('1.0.0');
|
||||
expect(body).toContain('production');
|
||||
expect(body).toContain('abc123');
|
||||
});
|
||||
|
||||
it('shows a relative-time hint in the Created at row', async () => {
|
||||
await createCommand([VALID_UUID]).run();
|
||||
expect(mockLogLog.mock.calls[1][0]).toMatch(/\(.+ago\)/);
|
||||
});
|
||||
|
||||
it('passes the project id and uuid through to viewByIdAsync', async () => {
|
||||
await createCommand([VALID_UUID]).run();
|
||||
expect(mockView).toHaveBeenCalledWith(MOCK_CONTEXT.loggedIn.graphqlClient, {
|
||||
embeddedUpdateId: VALID_UUID,
|
||||
appId: MOCK_CONTEXT.projectId,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,72 +0,0 @@
|
||||
import { Args } from '@oclif/core';
|
||||
|
||||
import EasCommand from '../../../commandUtils/EasCommand';
|
||||
import {
|
||||
EasNonInteractiveAndJsonFlags,
|
||||
resolveNonInteractiveAndJsonFlags,
|
||||
} from '../../../commandUtils/flags';
|
||||
import { EmbeddedUpdateMutation } from '../../../graphql/mutations/EmbeddedUpdateMutation';
|
||||
import Log from '../../../log';
|
||||
import { toggleConfirmAsync } from '../../../prompts';
|
||||
import { enableJsonOutput, printJsonOnlyOutput } from '../../../utils/json';
|
||||
|
||||
export default class UpdateEmbeddedDelete extends EasCommand {
|
||||
static override description = 'delete an embedded update registered with EAS Update';
|
||||
|
||||
static override args = {
|
||||
id: Args.string({
|
||||
required: true,
|
||||
description: 'The ID of the embedded update (manifest UUID from app.manifest).',
|
||||
}),
|
||||
};
|
||||
|
||||
static override flags = {
|
||||
...EasNonInteractiveAndJsonFlags,
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const {
|
||||
args: { id: embeddedUpdateId },
|
||||
flags,
|
||||
} = await this.parse(UpdateEmbeddedDelete);
|
||||
const { json: jsonFlag, nonInteractive } = resolveNonInteractiveAndJsonFlags(flags);
|
||||
|
||||
const {
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(UpdateEmbeddedDelete, { nonInteractive });
|
||||
|
||||
if (jsonFlag) {
|
||||
enableJsonOutput();
|
||||
}
|
||||
|
||||
if (!nonInteractive) {
|
||||
Log.log(
|
||||
`You are about to permanently delete embedded update: "${embeddedUpdateId}". ` +
|
||||
`Diff patches already generated against this bundle keep serving, but new diffs ` +
|
||||
`can't be generated until you re-upload it.`
|
||||
);
|
||||
Log.newLine();
|
||||
const confirmed = await toggleConfirmAsync({ message: 'Are you sure you wish to proceed?' });
|
||||
if (!confirmed) {
|
||||
Log.error(`Canceled deletion of embedded update: "${embeddedUpdateId}".`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Best-effort delete on the server: deleting an unknown id succeeds (idempotent),
|
||||
// so we don't need a not-found branch here.
|
||||
await EmbeddedUpdateMutation.deleteEmbeddedUpdateAsync(graphqlClient, {
|
||||
id: embeddedUpdateId,
|
||||
});
|
||||
|
||||
if (jsonFlag) {
|
||||
printJsonOnlyOutput({ id: embeddedUpdateId });
|
||||
return;
|
||||
}
|
||||
Log.withTick(`Deleted embedded update ${embeddedUpdateId}`);
|
||||
}
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
import { Platform } from '@expo/eas-build-job';
|
||||
import { Flags } from '@oclif/core';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import EasCommand from '../../../commandUtils/EasCommand';
|
||||
import { ExpoGraphqlClient } from '../../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import {
|
||||
EasNonInteractiveAndJsonFlags,
|
||||
resolveNonInteractiveAndJsonFlags,
|
||||
} from '../../../commandUtils/flags';
|
||||
import { getLimitFlagWithCustomValues } from '../../../commandUtils/pagination';
|
||||
import { AppPlatform } from '../../../graphql/generated';
|
||||
import { ChannelQuery } from '../../../graphql/queries/ChannelQuery';
|
||||
import {
|
||||
EmbeddedUpdateFragment,
|
||||
EmbeddedUpdateQuery,
|
||||
} from '../../../graphql/queries/EmbeddedUpdateQuery';
|
||||
import { toAppPlatform } from '../../../graphql/types/AppPlatform';
|
||||
import Log from '../../../log';
|
||||
import { selectAsync } from '../../../prompts';
|
||||
import { fromNow } from '../../../utils/date';
|
||||
import formatFields from '../../../utils/formatFields';
|
||||
import { enableJsonOutput, printJsonOnlyOutput } from '../../../utils/json';
|
||||
|
||||
const DEFAULT_LIMIT = 25;
|
||||
const MAX_LIMIT = 50;
|
||||
const CHANNELS_LIMIT = 50;
|
||||
|
||||
export default class UpdateEmbeddedList extends EasCommand {
|
||||
static override description = 'list embedded updates registered with EAS Update for this project';
|
||||
|
||||
static override flags = {
|
||||
platform: Flags.option({
|
||||
char: 'p',
|
||||
description: 'Filter by platform',
|
||||
options: [Platform.IOS, Platform.ANDROID] as const,
|
||||
})(),
|
||||
'runtime-version': Flags.string({
|
||||
description: 'Filter by runtime version',
|
||||
}),
|
||||
channel: Flags.string({
|
||||
description: 'Filter by channel name (pass "all" to skip the channel prompt)',
|
||||
}),
|
||||
limit: getLimitFlagWithCustomValues({ defaultTo: DEFAULT_LIMIT, limit: MAX_LIMIT }),
|
||||
'after-cursor': Flags.string({
|
||||
description: 'Return items after this cursor (for pagination)',
|
||||
}),
|
||||
...EasNonInteractiveAndJsonFlags,
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectId,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { flags } = await this.parse(UpdateEmbeddedList);
|
||||
const { json: jsonFlag, nonInteractive } = resolveNonInteractiveAndJsonFlags(flags);
|
||||
|
||||
const {
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(UpdateEmbeddedList, { nonInteractive });
|
||||
|
||||
if (jsonFlag) {
|
||||
enableJsonOutput();
|
||||
}
|
||||
|
||||
const platform: AppPlatform | undefined = flags.platform
|
||||
? toAppPlatform(flags.platform)
|
||||
: undefined;
|
||||
|
||||
// Resolve channel filter:
|
||||
// - `--channel <name>`: use it
|
||||
// - `--channel all` (or no flag in non-interactive / json): no channel filter
|
||||
// - no flag in interactive: prompt with the project's channels + "All channels"
|
||||
let channel: string | undefined;
|
||||
if (flags.channel) {
|
||||
channel = flags.channel.toLowerCase() === 'all' ? undefined : flags.channel;
|
||||
} else if (!nonInteractive && !jsonFlag) {
|
||||
channel = await promptForChannelAsync(graphqlClient, projectId);
|
||||
}
|
||||
|
||||
const filter =
|
||||
platform || flags['runtime-version'] || channel
|
||||
? {
|
||||
platform,
|
||||
runtimeVersion: flags['runtime-version'],
|
||||
channel,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const limit = flags.limit ?? DEFAULT_LIMIT;
|
||||
const connection = await EmbeddedUpdateQuery.viewPaginatedAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
filter,
|
||||
first: limit,
|
||||
after: flags['after-cursor'],
|
||||
});
|
||||
|
||||
const embeddedUpdates = connection.edges.map(e => e.node);
|
||||
|
||||
if (jsonFlag) {
|
||||
printJsonOnlyOutput({
|
||||
embeddedUpdates,
|
||||
pageInfo: connection.pageInfo,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (embeddedUpdates.length === 0) {
|
||||
Log.log('No embedded updates found.');
|
||||
return;
|
||||
}
|
||||
|
||||
Log.addNewLineIfNone();
|
||||
Log.log(
|
||||
chalk.bold(
|
||||
`Embedded updates (${embeddedUpdates.length}${connection.pageInfo.hasNextPage ? '+' : ''}):`
|
||||
)
|
||||
);
|
||||
Log.newLine();
|
||||
Log.log(embeddedUpdates.map(formatEmbeddedUpdateRow).join(`\n\n${chalk.dim('———')}\n\n`));
|
||||
|
||||
if (connection.pageInfo.hasNextPage && connection.pageInfo.endCursor) {
|
||||
Log.newLine();
|
||||
Log.log(
|
||||
chalk.dim(
|
||||
`Showing ${embeddedUpdates.length}. For the next page, run with --after-cursor ${connection.pageInfo.endCursor}`
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sentinel for the "All channels" option. We can't use `undefined` here because
|
||||
// the underlying `prompts` library substitutes a choice's index when its value
|
||||
// is undefined, which then leaks into the GraphQL filter.
|
||||
const ALL_CHANNELS = '__embedded_update_list__all_channels__';
|
||||
|
||||
async function promptForChannelAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
projectId: string
|
||||
): Promise<string | undefined> {
|
||||
const channels = await ChannelQuery.viewUpdateChannelsOnAppAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
offset: 0,
|
||||
limit: CHANNELS_LIMIT,
|
||||
});
|
||||
|
||||
if (channels.length === 0) {
|
||||
// Nothing to choose from — fall back to listing everything.
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const selected = await selectAsync<string>('Filter embedded updates by which channel?', [
|
||||
{ title: 'All channels', value: ALL_CHANNELS },
|
||||
...channels.map(c => ({ title: c.name, value: c.name })),
|
||||
]);
|
||||
return selected === ALL_CHANNELS ? undefined : selected;
|
||||
}
|
||||
|
||||
function formatEmbeddedUpdateRow(embeddedUpdate: EmbeddedUpdateFragment): string {
|
||||
const createdAt = new Date(embeddedUpdate.createdAt);
|
||||
return formatFields([
|
||||
{ label: 'ID', value: embeddedUpdate.id },
|
||||
{ label: 'Platform', value: embeddedUpdate.platform.toLowerCase() },
|
||||
{ label: 'Runtime version', value: embeddedUpdate.runtimeVersion },
|
||||
{ label: 'Channel', value: embeddedUpdate.channel },
|
||||
{ label: 'Created', value: `${fromNow(createdAt)} ago` },
|
||||
]);
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
import { Args, Errors } from '@oclif/core';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import EasCommand from '../../../commandUtils/EasCommand';
|
||||
import { EasJsonOnlyFlag } from '../../../commandUtils/flags';
|
||||
import {
|
||||
EmbeddedUpdateFragment,
|
||||
EmbeddedUpdateQuery,
|
||||
isEmbeddedUpdateNotFoundError,
|
||||
} from '../../../graphql/queries/EmbeddedUpdateQuery';
|
||||
import Log from '../../../log';
|
||||
import { fromNow } from '../../../utils/date';
|
||||
import { formatBytes } from '../../../utils/files';
|
||||
import formatFields from '../../../utils/formatFields';
|
||||
import { enableJsonOutput, printJsonOnlyOutput } from '../../../utils/json';
|
||||
|
||||
export default class UpdateEmbeddedView extends EasCommand {
|
||||
static override description = 'view details of an embedded update registered with EAS Update';
|
||||
|
||||
static override args = {
|
||||
id: Args.string({
|
||||
required: true,
|
||||
description: 'The ID of the embedded update (manifest UUID from app.manifest).',
|
||||
}),
|
||||
};
|
||||
|
||||
static override flags = {
|
||||
...EasJsonOnlyFlag,
|
||||
};
|
||||
|
||||
static override contextDefinition = {
|
||||
...this.ContextOptions.ProjectId,
|
||||
...this.ContextOptions.LoggedIn,
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const {
|
||||
args: { id: embeddedUpdateId },
|
||||
flags: { json: jsonFlag },
|
||||
} = await this.parse(UpdateEmbeddedView);
|
||||
|
||||
const {
|
||||
projectId,
|
||||
loggedIn: { graphqlClient },
|
||||
} = await this.getContextAsync(UpdateEmbeddedView, { nonInteractive: true });
|
||||
|
||||
if (jsonFlag) {
|
||||
enableJsonOutput();
|
||||
}
|
||||
|
||||
let embeddedUpdate;
|
||||
try {
|
||||
embeddedUpdate = await EmbeddedUpdateQuery.viewByIdAsync(graphqlClient, {
|
||||
embeddedUpdateId,
|
||||
appId: projectId,
|
||||
});
|
||||
} catch (e: unknown) {
|
||||
if (isEmbeddedUpdateNotFoundError(e)) {
|
||||
Errors.error(
|
||||
`No embedded update found with id "${embeddedUpdateId}" for this project. ` +
|
||||
`Run "eas update:embedded:list" to see the embedded updates registered for this app.`,
|
||||
{ exit: 1 }
|
||||
);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
if (jsonFlag) {
|
||||
printJsonOnlyOutput(embeddedUpdate);
|
||||
return;
|
||||
}
|
||||
|
||||
Log.addNewLineIfNone();
|
||||
Log.log(chalk.bold('Embedded update:'));
|
||||
Log.log(formatEmbeddedUpdate(embeddedUpdate));
|
||||
}
|
||||
}
|
||||
|
||||
export function formatEmbeddedUpdate(embeddedUpdate: EmbeddedUpdateFragment): string {
|
||||
const createdAt = new Date(embeddedUpdate.createdAt);
|
||||
const bundleSize =
|
||||
embeddedUpdate.launchAsset.finalFileSize ?? embeddedUpdate.launchAsset.fileSize;
|
||||
return formatFields([
|
||||
{ label: 'ID', value: embeddedUpdate.id },
|
||||
{ label: 'Platform', value: embeddedUpdate.platform.toLowerCase() },
|
||||
{ label: 'Runtime version', value: embeddedUpdate.runtimeVersion },
|
||||
{ label: 'Channel', value: embeddedUpdate.channel },
|
||||
{ label: 'Bundle size', value: formatBytes(bundleSize) },
|
||||
{ label: 'Bundle SHA-256', value: embeddedUpdate.launchAsset.fileSHA256 },
|
||||
{
|
||||
label: 'Created at',
|
||||
value: `${createdAt.toLocaleString()} (${fromNow(createdAt)} ago)`,
|
||||
},
|
||||
]);
|
||||
}
|
||||
+4
-354
@@ -1459,7 +1459,6 @@ export type App = Project & {
|
||||
description: Scalars['String']['output'];
|
||||
devDomainName?: Maybe<AppDevDomainName>;
|
||||
deviceRunSessionsPaginated: AppDeviceRunSessionsConnection;
|
||||
embeddedUpdatesPaginated: AppEmbeddedUpdatesConnection;
|
||||
/** Environment secrets for an app */
|
||||
environmentSecrets: Array<EnvironmentSecret>;
|
||||
environmentVariableEnvironments: Array<Scalars['EnvironmentVariableEnvironment']['output']>;
|
||||
@@ -1608,8 +1607,6 @@ export type App = Project & {
|
||||
workerDeploymentsCrashes?: Maybe<WorkerDeploymentCrashes>;
|
||||
workerDeploymentsRequest: WorkerDeploymentRequestEdge;
|
||||
workerDeploymentsRequests?: Maybe<WorkerDeploymentRequests>;
|
||||
workflowDeviceTestCaseHistory: WorkflowDeviceTestCaseHistory;
|
||||
workflowDeviceTestCaseInsights: WorkflowDeviceTestCaseInsights;
|
||||
workflowRunGitBranchesPaginated: AppWorkflowRunGitBranchesConnection;
|
||||
workflowRunsPaginated: AppWorkflowRunsConnection;
|
||||
workflows: Array<Workflow>;
|
||||
@@ -1700,16 +1697,6 @@ export type AppDeviceRunSessionsPaginatedArgs = {
|
||||
};
|
||||
|
||||
|
||||
/** Represents an Exponent App (or Experience in legacy terms) */
|
||||
export type AppEmbeddedUpdatesPaginatedArgs = {
|
||||
after?: InputMaybe<Scalars['String']['input']>;
|
||||
before?: InputMaybe<Scalars['String']['input']>;
|
||||
filter?: InputMaybe<EmbeddedUpdateFilterInput>;
|
||||
first?: InputMaybe<Scalars['Int']['input']>;
|
||||
last?: InputMaybe<Scalars['Int']['input']>;
|
||||
};
|
||||
|
||||
|
||||
/** Represents an Exponent App (or Experience in legacy terms) */
|
||||
export type AppEnvironmentSecretsArgs = {
|
||||
filterNames?: InputMaybe<Array<Scalars['String']['input']>>;
|
||||
@@ -1934,21 +1921,6 @@ export type AppWorkerDeploymentsRequestsArgs = {
|
||||
};
|
||||
|
||||
|
||||
/** Represents an Exponent App (or Experience in legacy terms) */
|
||||
export type AppWorkflowDeviceTestCaseHistoryArgs = {
|
||||
filters?: InputMaybe<WorkflowDeviceTestCaseHistoryFiltersInput>;
|
||||
path: Scalars['String']['input'];
|
||||
timespan: WorkflowDeviceTestCaseInsightsTimespanInput;
|
||||
};
|
||||
|
||||
|
||||
/** Represents an Exponent App (or Experience in legacy terms) */
|
||||
export type AppWorkflowDeviceTestCaseInsightsArgs = {
|
||||
filters?: InputMaybe<WorkflowDeviceTestCaseInsightsFiltersInput>;
|
||||
timespan: WorkflowDeviceTestCaseInsightsTimespanInput;
|
||||
};
|
||||
|
||||
|
||||
/** Represents an Exponent App (or Experience in legacy terms) */
|
||||
export type AppWorkflowRunGitBranchesPaginatedArgs = {
|
||||
after?: InputMaybe<Scalars['String']['input']>;
|
||||
@@ -2055,18 +2027,6 @@ export type AppDeviceRunSessionsConnection = {
|
||||
pageInfo: PageInfo;
|
||||
};
|
||||
|
||||
export type AppEmbeddedUpdateEdge = {
|
||||
__typename?: 'AppEmbeddedUpdateEdge';
|
||||
cursor: Scalars['String']['output'];
|
||||
node: EmbeddedUpdate;
|
||||
};
|
||||
|
||||
export type AppEmbeddedUpdatesConnection = {
|
||||
__typename?: 'AppEmbeddedUpdatesConnection';
|
||||
edges: Array<AppEmbeddedUpdateEdge>;
|
||||
pageInfo: PageInfo;
|
||||
};
|
||||
|
||||
export type AppFingerprintEdge = {
|
||||
__typename?: 'AppFingerprintEdge';
|
||||
cursor: Scalars['String']['output'];
|
||||
@@ -2406,7 +2366,6 @@ export type AppObserveCustomEvent = {
|
||||
eventName: Scalars['String']['output'];
|
||||
expoSdkVersion?: Maybe<Scalars['String']['output']>;
|
||||
id: Scalars['ID']['output'];
|
||||
ingestedAt?: Maybe<Scalars['DateTime']['output']>;
|
||||
properties: Array<AppObserveEventProperty>;
|
||||
reactNativeVersion?: Maybe<Scalars['String']['output']>;
|
||||
sessionId?: Maybe<Scalars['String']['output']>;
|
||||
@@ -2504,7 +2463,6 @@ export type AppObserveCustomEventPropertyFilter = {
|
||||
export type AppObserveEvent = {
|
||||
__typename?: 'AppObserveEvent';
|
||||
appBuildNumber: Scalars['String']['output'];
|
||||
appEasBuildId?: Maybe<Scalars['String']['output']>;
|
||||
appIdentifier: Scalars['String']['output'];
|
||||
appName: Scalars['String']['output'];
|
||||
appUpdateId?: Maybe<Scalars['String']['output']>;
|
||||
@@ -5105,11 +5063,6 @@ export type DeleteDiscordUserResult = {
|
||||
id: Scalars['ID']['output'];
|
||||
};
|
||||
|
||||
export type DeleteEmbeddedUpdateResult = {
|
||||
__typename?: 'DeleteEmbeddedUpdateResult';
|
||||
id: Scalars['ID']['output'];
|
||||
};
|
||||
|
||||
export type DeleteEnvironmentSecretResult = {
|
||||
__typename?: 'DeleteEnvironmentSecretResult';
|
||||
id: Scalars['ID']['output'];
|
||||
@@ -6223,20 +6176,8 @@ export type EmbeddedUpdateAssetUploadSpec = {
|
||||
storageKey: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export type EmbeddedUpdateFilterInput = {
|
||||
channel?: InputMaybe<Scalars['String']['input']>;
|
||||
platform?: InputMaybe<AppPlatform>;
|
||||
runtimeVersion?: InputMaybe<Scalars['String']['input']>;
|
||||
};
|
||||
|
||||
export type EmbeddedUpdateMutation = {
|
||||
__typename?: 'EmbeddedUpdateMutation';
|
||||
/**
|
||||
* Delete an embedded update by id. Best-effort: deleting an unknown id succeeds
|
||||
* (mirrors background deletion jobs). The linked asset row and underlying GCS
|
||||
* object are cleaned up via the entity's afterDelete trigger chain.
|
||||
*/
|
||||
deleteEmbeddedUpdate: DeleteEmbeddedUpdateResult;
|
||||
/**
|
||||
* Register an embedded bundle as the launch asset for a given app/platform/channel.
|
||||
* Returns EMBEDDED_UPDATE_ASSET_NOT_AVAILABLE if the asset has not been finalized yet,
|
||||
@@ -6246,31 +6187,10 @@ export type EmbeddedUpdateMutation = {
|
||||
};
|
||||
|
||||
|
||||
export type EmbeddedUpdateMutationDeleteEmbeddedUpdateArgs = {
|
||||
id: Scalars['ID']['input'];
|
||||
};
|
||||
|
||||
|
||||
export type EmbeddedUpdateMutationUploadEmbeddedUpdateArgs = {
|
||||
input: UploadEmbeddedUpdateInput;
|
||||
};
|
||||
|
||||
export type EmbeddedUpdateQuery = {
|
||||
__typename?: 'EmbeddedUpdateQuery';
|
||||
/**
|
||||
* Look up an embedded update by its id and the owning app's id.
|
||||
* Throws EMBEDDED_UPDATE_NOT_FOUND when no embedded update with this id exists on
|
||||
* the given app within the caller's account.
|
||||
*/
|
||||
byId: EmbeddedUpdate;
|
||||
};
|
||||
|
||||
|
||||
export type EmbeddedUpdateQueryByIdArgs = {
|
||||
appId: Scalars['ID']['input'];
|
||||
embeddedUpdateId: Scalars['ID']['input'];
|
||||
};
|
||||
|
||||
export enum EntityTypeName {
|
||||
AccountEntity = 'AccountEntity',
|
||||
AccountSsoConfigurationEntity = 'AccountSSOConfigurationEntity',
|
||||
@@ -7556,7 +7476,7 @@ export type JobRun = {
|
||||
isWaived: Scalars['Boolean']['output'];
|
||||
logFileUrls: Array<Scalars['String']['output']>;
|
||||
/** Max run time in seconds for this job run. */
|
||||
maxRunTimeSeconds: Scalars['Int']['output'];
|
||||
maxRunTimeSeconds?: Maybe<Scalars['Int']['output']>;
|
||||
name: Scalars['String']['output'];
|
||||
priority: JobRunPriority;
|
||||
/** String describing the worker profile used to run this job run. */
|
||||
@@ -8600,7 +8520,6 @@ export type RootQuery = {
|
||||
echoProject: EchoProjectQuery;
|
||||
/** Top-level query object for querying Echo versions. */
|
||||
echoVersion: EchoVersionQuery;
|
||||
embeddedUpdates: EmbeddedUpdateQuery;
|
||||
/** Top-level query object for querying Experimentation configuration. */
|
||||
experimentation: ExperimentationQuery;
|
||||
expoGoBuild: ExpoGoBuildQuery;
|
||||
@@ -8857,7 +8776,7 @@ export type SsoUser = Actor & UserActor & {
|
||||
preferences: UserPreferences;
|
||||
/** Associated accounts */
|
||||
primaryAccount: Account;
|
||||
primaryAccountProfileImageUrl: Scalars['String']['output'];
|
||||
primaryAccountProfileImageUrl?: Maybe<Scalars['String']['output']>;
|
||||
/** @deprecated Use primaryAccountProfileImageUrl instead */
|
||||
profilePhoto: Scalars['String']['output'];
|
||||
/** Snacks associated with this account */
|
||||
@@ -10143,7 +10062,7 @@ export type User = Actor & UserActor & {
|
||||
preferences: UserPreferences;
|
||||
/** Associated accounts */
|
||||
primaryAccount: Account;
|
||||
primaryAccountProfileImageUrl: Scalars['String']['output'];
|
||||
primaryAccountProfileImageUrl?: Maybe<Scalars['String']['output']>;
|
||||
/** @deprecated Use primaryAccountProfileImageUrl instead */
|
||||
profilePhoto: Scalars['String']['output'];
|
||||
/** Get all certified second factor authentication methods */
|
||||
@@ -10247,7 +10166,7 @@ export type UserActor = {
|
||||
preferences: UserPreferences;
|
||||
/** Associated accounts */
|
||||
primaryAccount: Account;
|
||||
primaryAccountProfileImageUrl: Scalars['String']['output'];
|
||||
primaryAccountProfileImageUrl?: Maybe<Scalars['String']['output']>;
|
||||
/** @deprecated Use primaryAccountProfileImageUrl instead */
|
||||
profilePhoto: Scalars['String']['output'];
|
||||
/** Snacks associated with this user's personal account */
|
||||
@@ -10302,8 +10221,6 @@ export type UserActorPublicData = {
|
||||
firstName?: Maybe<Scalars['String']['output']>;
|
||||
id: Scalars['ID']['output'];
|
||||
lastName?: Maybe<Scalars['String']['output']>;
|
||||
primaryAccountProfileImageUrl: Scalars['String']['output'];
|
||||
/** @deprecated Use primaryAccountProfileImageUrl instead */
|
||||
profilePhoto: Scalars['String']['output'];
|
||||
/** Snacks associated with this user's personal account */
|
||||
snacks: Array<Snack>;
|
||||
@@ -11450,187 +11367,6 @@ export enum WorkflowArtifactStorageType {
|
||||
R2 = 'R2'
|
||||
}
|
||||
|
||||
/**
|
||||
* Grouping key from the [shard N] prefix-stripped error_message. `count` is
|
||||
* computed with uniqExact(test_case_result_id) so RMT pre-merge duplicates do
|
||||
* not inflate it. Scope: scans ALL status='failed' rows (NOT just
|
||||
* is_final_attempt=1) so failures from runs that ultimately passed on retry
|
||||
* still surface.
|
||||
*/
|
||||
export type WorkflowDeviceTestCaseErrorPattern = {
|
||||
__typename?: 'WorkflowDeviceTestCaseErrorPattern';
|
||||
count: Scalars['Int']['output'];
|
||||
lastSeenAt: Scalars['DateTime']['output'];
|
||||
patternKey: Scalars['String']['output'];
|
||||
sampleMessage: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export type WorkflowDeviceTestCaseHistory = {
|
||||
__typename?: 'WorkflowDeviceTestCaseHistory';
|
||||
errorPatterns: Array<WorkflowDeviceTestCaseErrorPattern>;
|
||||
recentRuns: WorkflowDeviceTestCaseRecentRunConnection;
|
||||
timeSeries: Array<WorkflowDeviceTestCaseInsightsBucket>;
|
||||
totals: WorkflowDeviceTestCaseInsightsTotals;
|
||||
};
|
||||
|
||||
|
||||
export type WorkflowDeviceTestCaseHistoryErrorPatternsArgs = {
|
||||
first: Scalars['Int']['input'];
|
||||
};
|
||||
|
||||
|
||||
export type WorkflowDeviceTestCaseHistoryRecentRunsArgs = {
|
||||
after?: InputMaybe<Scalars['String']['input']>;
|
||||
first: Scalars['Int']['input'];
|
||||
};
|
||||
|
||||
|
||||
export type WorkflowDeviceTestCaseHistoryTimeSeriesArgs = {
|
||||
granularity: WorkflowDeviceTestCaseInsightsTimeSeriesGranularity;
|
||||
};
|
||||
|
||||
export type WorkflowDeviceTestCaseHistoryFiltersInput = {
|
||||
gitRefs?: InputMaybe<Array<Scalars['String']['input']>>;
|
||||
workflowIds?: InputMaybe<Array<Scalars['ID']['input']>>;
|
||||
};
|
||||
|
||||
export type WorkflowDeviceTestCaseInsights = {
|
||||
__typename?: 'WorkflowDeviceTestCaseInsights';
|
||||
facets: WorkflowDeviceTestCaseInsightsFacets;
|
||||
tests: WorkflowDeviceTestCaseStatConnection;
|
||||
timeSeries: Array<WorkflowDeviceTestCaseInsightsBucket>;
|
||||
totals: WorkflowDeviceTestCaseInsightsTotals;
|
||||
};
|
||||
|
||||
|
||||
export type WorkflowDeviceTestCaseInsightsTestsArgs = {
|
||||
after?: InputMaybe<Scalars['String']['input']>;
|
||||
first: Scalars['Int']['input'];
|
||||
search?: InputMaybe<Scalars['String']['input']>;
|
||||
sortDirection?: InputMaybe<WorkflowDeviceTestCaseSortDirection>;
|
||||
sortField?: InputMaybe<WorkflowDeviceTestCaseStatSortField>;
|
||||
};
|
||||
|
||||
|
||||
export type WorkflowDeviceTestCaseInsightsTimeSeriesArgs = {
|
||||
granularity: WorkflowDeviceTestCaseInsightsTimeSeriesGranularity;
|
||||
};
|
||||
|
||||
/**
|
||||
* Mutually exclusive bucket counts (passedClean + flaky + failed = totalRuns for the bucket).
|
||||
* Buckets are aligned to the UTC start of the requested granularity interval
|
||||
* (minute / hour / day).
|
||||
*/
|
||||
export type WorkflowDeviceTestCaseInsightsBucket = {
|
||||
__typename?: 'WorkflowDeviceTestCaseInsightsBucket';
|
||||
bucketStartAt: Scalars['DateTime']['output'];
|
||||
failed: Scalars['Int']['output'];
|
||||
flaky: Scalars['Int']['output'];
|
||||
passedClean: Scalars['Int']['output'];
|
||||
};
|
||||
|
||||
export type WorkflowDeviceTestCaseInsightsFacets = {
|
||||
__typename?: 'WorkflowDeviceTestCaseInsightsFacets';
|
||||
gitRefs: Array<Scalars['String']['output']>;
|
||||
tags: Array<Scalars['String']['output']>;
|
||||
workflows: Array<WorkflowDeviceTestCaseWorkflowFacet>;
|
||||
};
|
||||
|
||||
export type WorkflowDeviceTestCaseInsightsFiltersInput = {
|
||||
gitRefs?: InputMaybe<Array<Scalars['String']['input']>>;
|
||||
statuses?: InputMaybe<Array<WorkflowDeviceTestCaseStatusFilter>>;
|
||||
tags?: InputMaybe<Array<Scalars['String']['input']>>;
|
||||
workflowIds?: InputMaybe<Array<Scalars['ID']['input']>>;
|
||||
};
|
||||
|
||||
/**
|
||||
* A count metric returned for the current window AND the equivalent prior window
|
||||
* (start − duration → start). The frontend uses (current, previous) to compute
|
||||
* trend deltas. Trend ratios are NOT pre-computed server-side — see
|
||||
* WorkflowsInsightsMetric for the equivalent convention.
|
||||
*
|
||||
* Float (not Int) to match WorkflowsInsightsMetric and avoid the GraphQL Int32
|
||||
* ceiling: 90-day uniqExact counts on a high-volume project can plausibly
|
||||
* exceed 2.1B. Values are integer-valued; the frontend reads them as JS numbers.
|
||||
*/
|
||||
export type WorkflowDeviceTestCaseInsightsMetric = {
|
||||
__typename?: 'WorkflowDeviceTestCaseInsightsMetric';
|
||||
currentValue: Scalars['Float']['output'];
|
||||
previousValue: Scalars['Float']['output'];
|
||||
};
|
||||
|
||||
/**
|
||||
* Same shape as WorkflowDeviceTestCaseInsightsMetric but nullable on both sides —
|
||||
* used for metrics like avgDurationMs / p90DurationMs where "no data in the
|
||||
* window" is meaningful and must not collapse to 0.
|
||||
*/
|
||||
export type WorkflowDeviceTestCaseInsightsNullableMetric = {
|
||||
__typename?: 'WorkflowDeviceTestCaseInsightsNullableMetric';
|
||||
currentValue?: Maybe<Scalars['Float']['output']>;
|
||||
previousValue?: Maybe<Scalars['Float']['output']>;
|
||||
};
|
||||
|
||||
export enum WorkflowDeviceTestCaseInsightsTimeSeriesGranularity {
|
||||
Day = 'DAY',
|
||||
Hour = 'HOUR',
|
||||
Minute = 'MINUTE'
|
||||
}
|
||||
|
||||
export type WorkflowDeviceTestCaseInsightsTimespanInput = {
|
||||
end: Scalars['DateTime']['input'];
|
||||
start: Scalars['DateTime']['input'];
|
||||
};
|
||||
|
||||
/**
|
||||
* Raw counts only (paired with previous-window values). Pass rate, flake rate,
|
||||
* and trends are computed in the frontend from the (currentValue, previousValue)
|
||||
* pair on each metric.
|
||||
*
|
||||
* totalRuns = passedCleanCount + flakyCount + failedCount (over is_final_attempt=1 rows).
|
||||
* distinctFlakyTestCount cannot be derived in the frontend from per-test rows
|
||||
* because pagination means the frontend doesn't see all test paths.
|
||||
*/
|
||||
export type WorkflowDeviceTestCaseInsightsTotals = {
|
||||
__typename?: 'WorkflowDeviceTestCaseInsightsTotals';
|
||||
avgDurationMs: WorkflowDeviceTestCaseInsightsNullableMetric;
|
||||
distinctFlakyTestCount: WorkflowDeviceTestCaseInsightsMetric;
|
||||
failedCount: WorkflowDeviceTestCaseInsightsMetric;
|
||||
flakyCount: WorkflowDeviceTestCaseInsightsMetric;
|
||||
p90DurationMs: WorkflowDeviceTestCaseInsightsNullableMetric;
|
||||
passedCleanCount: WorkflowDeviceTestCaseInsightsMetric;
|
||||
totalRuns: WorkflowDeviceTestCaseInsightsMetric;
|
||||
};
|
||||
|
||||
/**
|
||||
* One row per execution (is_final_attempt=1). The is_flaky boolean drives the
|
||||
* FLAKY pill — no per-attempt timeline is exposed in v1.
|
||||
*/
|
||||
export type WorkflowDeviceTestCaseRecentRun = {
|
||||
__typename?: 'WorkflowDeviceTestCaseRecentRun';
|
||||
commitSha?: Maybe<Scalars['String']['output']>;
|
||||
createdAt: Scalars['DateTime']['output'];
|
||||
durationMs?: Maybe<Scalars['Int']['output']>;
|
||||
gitRef?: Maybe<Scalars['String']['output']>;
|
||||
id: Scalars['ID']['output'];
|
||||
isFlaky: Scalars['Boolean']['output'];
|
||||
status: WorkflowDeviceTestCaseStatus;
|
||||
workflowRunId: Scalars['ID']['output'];
|
||||
workflowRunName: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export type WorkflowDeviceTestCaseRecentRunConnection = {
|
||||
__typename?: 'WorkflowDeviceTestCaseRecentRunConnection';
|
||||
edges: Array<WorkflowDeviceTestCaseRecentRunEdge>;
|
||||
pageInfo: PageInfo;
|
||||
totalCount: Scalars['Int']['output'];
|
||||
};
|
||||
|
||||
export type WorkflowDeviceTestCaseRecentRunEdge = {
|
||||
__typename?: 'WorkflowDeviceTestCaseRecentRunEdge';
|
||||
cursor: Scalars['String']['output'];
|
||||
node: WorkflowDeviceTestCaseRecentRun;
|
||||
};
|
||||
|
||||
/** A device test case result from a Maestro test execution. */
|
||||
export type WorkflowDeviceTestCaseResult = {
|
||||
__typename?: 'WorkflowDeviceTestCaseResult';
|
||||
@@ -11684,73 +11420,12 @@ export type WorkflowDeviceTestCaseResultMutationCreateWorkflowDeviceTestCaseResu
|
||||
input: CreateWorkflowDeviceTestCaseResultsInput;
|
||||
};
|
||||
|
||||
export enum WorkflowDeviceTestCaseSortDirection {
|
||||
Asc = 'ASC',
|
||||
Desc = 'DESC'
|
||||
}
|
||||
|
||||
export type WorkflowDeviceTestCaseStat = {
|
||||
__typename?: 'WorkflowDeviceTestCaseStat';
|
||||
avgDurationMs?: Maybe<Scalars['Int']['output']>;
|
||||
failedCount: Scalars['Int']['output'];
|
||||
flakyCount: Scalars['Int']['output'];
|
||||
lastRunAt: Scalars['DateTime']['output'];
|
||||
lastRunIsFlaky: Scalars['Boolean']['output'];
|
||||
lastRunStatus: WorkflowDeviceTestCaseStatus;
|
||||
name: Scalars['String']['output'];
|
||||
p90DurationMs?: Maybe<Scalars['Int']['output']>;
|
||||
passedCleanCount: Scalars['Int']['output'];
|
||||
path: Scalars['String']['output'];
|
||||
totalRuns: Scalars['Int']['output'];
|
||||
};
|
||||
|
||||
export type WorkflowDeviceTestCaseStatConnection = {
|
||||
__typename?: 'WorkflowDeviceTestCaseStatConnection';
|
||||
edges: Array<WorkflowDeviceTestCaseStatEdge>;
|
||||
pageInfo: PageInfo;
|
||||
totalCount: Scalars['Int']['output'];
|
||||
};
|
||||
|
||||
export type WorkflowDeviceTestCaseStatEdge = {
|
||||
__typename?: 'WorkflowDeviceTestCaseStatEdge';
|
||||
cursor: Scalars['String']['output'];
|
||||
node: WorkflowDeviceTestCaseStat;
|
||||
};
|
||||
|
||||
export enum WorkflowDeviceTestCaseStatSortField {
|
||||
Fails = 'FAILS',
|
||||
Flakes = 'FLAKES',
|
||||
FlakeRate = 'FLAKE_RATE',
|
||||
LastRun = 'LAST_RUN',
|
||||
P90Duration = 'P90_DURATION',
|
||||
PassRate = 'PASS_RATE',
|
||||
Runs = 'RUNS'
|
||||
}
|
||||
|
||||
/** Status of a device test case execution. */
|
||||
export enum WorkflowDeviceTestCaseStatus {
|
||||
Failed = 'FAILED',
|
||||
Passed = 'PASSED'
|
||||
}
|
||||
|
||||
/**
|
||||
* Mutually exclusive — matches the chart bucket semantics (passedClean / flaky / failed).
|
||||
* PASSED_CLEAN = is_final_attempt=1 AND status=passed AND is_flaky=0
|
||||
* FLAKY = is_final_attempt=1 AND is_flaky=1 (still a pass, just with retries)
|
||||
* FAILED = is_final_attempt=1 AND status=failed
|
||||
*/
|
||||
export enum WorkflowDeviceTestCaseStatusFilter {
|
||||
Failed = 'FAILED',
|
||||
Flaky = 'FLAKY',
|
||||
PassedClean = 'PASSED_CLEAN'
|
||||
}
|
||||
|
||||
export type WorkflowDeviceTestCaseWorkflowFacet = {
|
||||
__typename?: 'WorkflowDeviceTestCaseWorkflowFacet';
|
||||
id: Scalars['ID']['output'];
|
||||
name: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export type WorkflowJob = {
|
||||
__typename?: 'WorkflowJob';
|
||||
allDeviceTestCaseResults: Array<WorkflowDeviceTestCaseResult>;
|
||||
@@ -12855,13 +12530,6 @@ export type UploadEmbeddedUpdateMutationVariables = Exact<{
|
||||
|
||||
export type UploadEmbeddedUpdateMutation = { __typename?: 'RootMutation', embeddedUpdate: { __typename?: 'EmbeddedUpdateMutation', uploadEmbeddedUpdate: { __typename?: 'EmbeddedUpdate', id: string, platform: AppPlatform, runtimeVersion: string, channel: string, createdAt: any } } };
|
||||
|
||||
export type DeleteEmbeddedUpdateMutationVariables = Exact<{
|
||||
id: Scalars['ID']['input'];
|
||||
}>;
|
||||
|
||||
|
||||
export type DeleteEmbeddedUpdateMutation = { __typename?: 'RootMutation', embeddedUpdate: { __typename?: 'EmbeddedUpdateMutation', deleteEmbeddedUpdate: { __typename?: 'DeleteEmbeddedUpdateResult', id: string } } };
|
||||
|
||||
export type CreateEnvironmentSecretForAccountMutationVariables = Exact<{
|
||||
input: CreateEnvironmentSecretInput;
|
||||
accountId: Scalars['String']['input'];
|
||||
@@ -13361,24 +13029,6 @@ export type DeviceRunSessionsByAppIdQueryVariables = Exact<{
|
||||
|
||||
export type DeviceRunSessionsByAppIdQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, deviceRunSessionsPaginated: { __typename?: 'AppDeviceRunSessionsConnection', edges: Array<{ __typename?: 'AppDeviceRunSessionEdge', cursor: string, node: { __typename?: 'DeviceRunSession', id: string, status: DeviceRunSessionStatus, type: DeviceRunSessionType, platform: AppPlatform, createdAt: any, startedAt?: any | null, finishedAt?: any | null, app: { __typename?: 'App', id: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string } }, turtleJobRun?: { __typename?: 'JobRun', id: string, status: JobRunStatus } | null } }>, pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, hasPreviousPage: boolean, startCursor?: string | null, endCursor?: string | null } } } } };
|
||||
|
||||
export type ViewEmbeddedUpdateByIdQueryVariables = Exact<{
|
||||
embeddedUpdateId: Scalars['ID']['input'];
|
||||
appId: Scalars['ID']['input'];
|
||||
}>;
|
||||
|
||||
|
||||
export type ViewEmbeddedUpdateByIdQuery = { __typename?: 'RootQuery', embeddedUpdates: { __typename?: 'EmbeddedUpdateQuery', byId: { __typename?: 'EmbeddedUpdate', id: string, platform: AppPlatform, runtimeVersion: string, channel: string, createdAt: any, launchAsset: { __typename?: 'EmbeddedUpdateAsset', id: string, fileSize: number, finalFileSize?: number | null, fileSHA256: string } } } };
|
||||
|
||||
export type ViewEmbeddedUpdatesPaginatedQueryVariables = Exact<{
|
||||
appId: Scalars['String']['input'];
|
||||
first: Scalars['Int']['input'];
|
||||
after?: InputMaybe<Scalars['String']['input']>;
|
||||
filter?: InputMaybe<EmbeddedUpdateFilterInput>;
|
||||
}>;
|
||||
|
||||
|
||||
export type ViewEmbeddedUpdatesPaginatedQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byId: { __typename?: 'App', id: string, embeddedUpdatesPaginated: { __typename?: 'AppEmbeddedUpdatesConnection', edges: Array<{ __typename?: 'AppEmbeddedUpdateEdge', cursor: string, node: { __typename?: 'EmbeddedUpdate', id: string, platform: AppPlatform, runtimeVersion: string, channel: string, createdAt: any, launchAsset: { __typename?: 'EmbeddedUpdateAsset', id: string, fileSize: number, finalFileSize?: number | null, fileSHA256: string } } }>, pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, hasPreviousPage: boolean, startCursor?: string | null, endCursor?: string | null } } } } };
|
||||
|
||||
export type EnvironmentSecretsByAppIdQueryVariables = Exact<{
|
||||
appId: Scalars['String']['input'];
|
||||
}>;
|
||||
|
||||
@@ -4,8 +4,6 @@ import gql from 'graphql-tag';
|
||||
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import {
|
||||
AppPlatform,
|
||||
DeleteEmbeddedUpdateMutation,
|
||||
DeleteEmbeddedUpdateMutationVariables,
|
||||
UploadEmbeddedUpdateInput,
|
||||
UploadEmbeddedUpdateMutation,
|
||||
UploadEmbeddedUpdateMutationVariables,
|
||||
@@ -63,27 +61,4 @@ export const EmbeddedUpdateMutation = {
|
||||
);
|
||||
return data.embeddedUpdate.uploadEmbeddedUpdate;
|
||||
},
|
||||
|
||||
async deleteEmbeddedUpdateAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{ id }: { id: string }
|
||||
): Promise<{ id: string }> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.mutation<DeleteEmbeddedUpdateMutation, DeleteEmbeddedUpdateMutationVariables>(
|
||||
gql`
|
||||
mutation DeleteEmbeddedUpdate($id: ID!) {
|
||||
embeddedUpdate {
|
||||
deleteEmbeddedUpdate(id: $id) {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ id }
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
return data.embeddedUpdate.deleteEmbeddedUpdate;
|
||||
},
|
||||
};
|
||||
@@ -97,27 +97,3 @@ describe('EmbeddedUpdateMutation.uploadEmbeddedUpdateAsync', () => {
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('EmbeddedUpdateMutation.deleteEmbeddedUpdateAsync', () => {
|
||||
it('returns the deleted id from the GraphQL response', async () => {
|
||||
const client = makeGraphqlClient({
|
||||
embeddedUpdate: { deleteEmbeddedUpdate: { id: 'update-1' } },
|
||||
});
|
||||
|
||||
const result = await EmbeddedUpdateMutation.deleteEmbeddedUpdateAsync(client, {
|
||||
id: 'update-1',
|
||||
});
|
||||
|
||||
expect(result).toEqual({ id: 'update-1' });
|
||||
});
|
||||
|
||||
it('passes the id through to the mutation variables', async () => {
|
||||
const client = makeGraphqlClient({
|
||||
embeddedUpdate: { deleteEmbeddedUpdate: { id: 'update-2' } },
|
||||
});
|
||||
|
||||
await EmbeddedUpdateMutation.deleteEmbeddedUpdateAsync(client, { id: 'update-2' });
|
||||
|
||||
expect(client.mutation).toHaveBeenCalledWith(expect.anything(), { id: 'update-2' });
|
||||
});
|
||||
});
|
||||
@@ -1,118 +0,0 @@
|
||||
import { CombinedError } from '@urql/core';
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { Connection } from '../../utils/relay';
|
||||
import {
|
||||
EmbeddedUpdateFilterInput,
|
||||
ViewEmbeddedUpdateByIdQuery,
|
||||
ViewEmbeddedUpdateByIdQueryVariables,
|
||||
ViewEmbeddedUpdatesPaginatedQuery,
|
||||
ViewEmbeddedUpdatesPaginatedQueryVariables,
|
||||
} from '../generated';
|
||||
import { withErrorHandlingAsync } from '../client';
|
||||
|
||||
export function isEmbeddedUpdateNotFoundError(error: unknown): boolean {
|
||||
return (
|
||||
error instanceof CombinedError &&
|
||||
error.graphQLErrors.some(e => e.extensions?.['errorCode'] === 'EMBEDDED_UPDATE_NOT_FOUND')
|
||||
);
|
||||
}
|
||||
|
||||
export type EmbeddedUpdateFragment = ViewEmbeddedUpdateByIdQuery['embeddedUpdates']['byId'];
|
||||
|
||||
export type EmbeddedUpdateFilter = EmbeddedUpdateFilterInput;
|
||||
|
||||
export const EmbeddedUpdateQuery = {
|
||||
async viewByIdAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{ embeddedUpdateId, appId }: { embeddedUpdateId: string; appId: string }
|
||||
): Promise<EmbeddedUpdateFragment> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.query<ViewEmbeddedUpdateByIdQuery, ViewEmbeddedUpdateByIdQueryVariables>(
|
||||
gql`
|
||||
query ViewEmbeddedUpdateById($embeddedUpdateId: ID!, $appId: ID!) {
|
||||
embeddedUpdates {
|
||||
byId(embeddedUpdateId: $embeddedUpdateId, appId: $appId) {
|
||||
id
|
||||
platform
|
||||
runtimeVersion
|
||||
channel
|
||||
createdAt
|
||||
launchAsset {
|
||||
id
|
||||
fileSize
|
||||
finalFileSize
|
||||
fileSHA256
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ embeddedUpdateId, appId },
|
||||
{ additionalTypenames: ['EmbeddedUpdate'] }
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
return data.embeddedUpdates.byId;
|
||||
},
|
||||
|
||||
async viewPaginatedAsync(
|
||||
graphqlClient: ExpoGraphqlClient,
|
||||
{
|
||||
appId,
|
||||
filter,
|
||||
first,
|
||||
after,
|
||||
}: { appId: string; filter?: EmbeddedUpdateFilter; first: number; after?: string }
|
||||
): Promise<Connection<EmbeddedUpdateFragment>> {
|
||||
const data = await withErrorHandlingAsync(
|
||||
graphqlClient
|
||||
.query<ViewEmbeddedUpdatesPaginatedQuery, ViewEmbeddedUpdatesPaginatedQueryVariables>(
|
||||
gql`
|
||||
query ViewEmbeddedUpdatesPaginated(
|
||||
$appId: String!
|
||||
$first: Int!
|
||||
$after: String
|
||||
$filter: EmbeddedUpdateFilterInput
|
||||
) {
|
||||
app {
|
||||
byId(appId: $appId) {
|
||||
id
|
||||
embeddedUpdatesPaginated(first: $first, after: $after, filter: $filter) {
|
||||
edges {
|
||||
cursor
|
||||
node {
|
||||
id
|
||||
platform
|
||||
runtimeVersion
|
||||
channel
|
||||
createdAt
|
||||
launchAsset {
|
||||
id
|
||||
fileSize
|
||||
finalFileSize
|
||||
fileSHA256
|
||||
}
|
||||
}
|
||||
}
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
startCursor
|
||||
endCursor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ appId, first, after, filter },
|
||||
{ additionalTypenames: ['EmbeddedUpdate'] }
|
||||
)
|
||||
.toPromise()
|
||||
);
|
||||
return data.app.byId.embeddedUpdatesPaginated;
|
||||
},
|
||||
};
|
||||
@@ -1,178 +0,0 @@
|
||||
import { CombinedError } from '@urql/core';
|
||||
|
||||
import { ExpoGraphqlClient } from '../../../commandUtils/context/contextUtils/createGraphqlClient';
|
||||
import { AppPlatform } from '../../generated';
|
||||
import { EmbeddedUpdateQuery, isEmbeddedUpdateNotFoundError } from '../EmbeddedUpdateQuery';
|
||||
|
||||
function makeGraphqlClient(data: unknown): ExpoGraphqlClient {
|
||||
return {
|
||||
query: jest.fn().mockReturnValue({
|
||||
toPromise: jest.fn().mockResolvedValue({ data }),
|
||||
}),
|
||||
} as unknown as ExpoGraphqlClient;
|
||||
}
|
||||
|
||||
function makeCombinedError(errorCode: string): CombinedError {
|
||||
return new CombinedError({
|
||||
graphQLErrors: [{ message: 'error', extensions: { errorCode } }],
|
||||
});
|
||||
}
|
||||
|
||||
describe('isEmbeddedUpdateNotFoundError', () => {
|
||||
it('returns true for CombinedError with EMBEDDED_UPDATE_NOT_FOUND errorCode', () => {
|
||||
expect(isEmbeddedUpdateNotFoundError(makeCombinedError('EMBEDDED_UPDATE_NOT_FOUND'))).toBe(
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
it('returns false for CombinedError with a different errorCode', () => {
|
||||
expect(isEmbeddedUpdateNotFoundError(makeCombinedError('SOME_OTHER_ERROR'))).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false for CombinedError with no extensions', () => {
|
||||
expect(
|
||||
isEmbeddedUpdateNotFoundError(new CombinedError({ graphQLErrors: [{ message: 'oops' }] }))
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false for a plain Error', () => {
|
||||
expect(isEmbeddedUpdateNotFoundError(new Error('plain error'))).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false for null and non-error values', () => {
|
||||
expect(isEmbeddedUpdateNotFoundError(null)).toBe(false);
|
||||
expect(isEmbeddedUpdateNotFoundError('string')).toBe(false);
|
||||
expect(isEmbeddedUpdateNotFoundError(42)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('EmbeddedUpdateQuery.viewByIdAsync', () => {
|
||||
it('returns the embedded update from the GraphQL response', async () => {
|
||||
const expected = {
|
||||
id: 'update-1',
|
||||
platform: AppPlatform.Ios,
|
||||
runtimeVersion: '1.0.0',
|
||||
channel: 'production',
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
launchAsset: {
|
||||
id: 'asset-1',
|
||||
fileSize: 1024,
|
||||
finalFileSize: 768,
|
||||
fileSHA256: 'abc123',
|
||||
},
|
||||
};
|
||||
const client = makeGraphqlClient({
|
||||
embeddedUpdates: { byId: expected },
|
||||
});
|
||||
|
||||
const result = await EmbeddedUpdateQuery.viewByIdAsync(client, {
|
||||
embeddedUpdateId: 'update-1',
|
||||
appId: 'app-1',
|
||||
});
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it('passes the embeddedUpdateId and appId to the query', async () => {
|
||||
const client = makeGraphqlClient({
|
||||
embeddedUpdates: {
|
||||
byId: {
|
||||
id: 'update-1',
|
||||
platform: AppPlatform.Ios,
|
||||
runtimeVersion: '1.0.0',
|
||||
channel: 'production',
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
launchAsset: {
|
||||
id: 'asset-1',
|
||||
fileSize: 1024,
|
||||
finalFileSize: null,
|
||||
fileSHA256: 'abc',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await EmbeddedUpdateQuery.viewByIdAsync(client, {
|
||||
embeddedUpdateId: 'update-1',
|
||||
appId: 'app-1',
|
||||
});
|
||||
|
||||
expect(client.query).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
{ embeddedUpdateId: 'update-1', appId: 'app-1' },
|
||||
expect.objectContaining({ additionalTypenames: ['EmbeddedUpdate'] })
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('EmbeddedUpdateQuery.viewPaginatedAsync', () => {
|
||||
function makeConnection(): {
|
||||
edges: { cursor: string; node: any }[];
|
||||
pageInfo: {
|
||||
hasNextPage: boolean;
|
||||
hasPreviousPage: boolean;
|
||||
startCursor: string | null;
|
||||
endCursor: string | null;
|
||||
};
|
||||
} {
|
||||
return {
|
||||
edges: [
|
||||
{
|
||||
cursor: 'c1',
|
||||
node: {
|
||||
id: 'update-1',
|
||||
platform: AppPlatform.Ios,
|
||||
runtimeVersion: '1.0.0',
|
||||
channel: 'production',
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
launchAsset: {
|
||||
id: 'asset-1',
|
||||
fileSize: 1024,
|
||||
finalFileSize: null,
|
||||
fileSHA256: 'abc',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
pageInfo: { hasNextPage: false, hasPreviousPage: false, startCursor: 'c1', endCursor: 'c1' },
|
||||
};
|
||||
}
|
||||
|
||||
it('returns the connection from the GraphQL response', async () => {
|
||||
const connection = makeConnection();
|
||||
const client = makeGraphqlClient({
|
||||
app: { byId: { id: 'app-1', embeddedUpdatesPaginated: connection } },
|
||||
});
|
||||
|
||||
const result = await EmbeddedUpdateQuery.viewPaginatedAsync(client, {
|
||||
appId: 'app-1',
|
||||
first: 25,
|
||||
});
|
||||
|
||||
expect(result).toEqual(connection);
|
||||
});
|
||||
|
||||
it('forwards filter / first / after to the query variables', async () => {
|
||||
const client = makeGraphqlClient({
|
||||
app: { byId: { id: 'app-1', embeddedUpdatesPaginated: makeConnection() } },
|
||||
});
|
||||
|
||||
await EmbeddedUpdateQuery.viewPaginatedAsync(client, {
|
||||
appId: 'app-1',
|
||||
first: 10,
|
||||
after: 'cursor-99',
|
||||
filter: { platform: AppPlatform.Ios, runtimeVersion: '1.0.0', channel: 'production' },
|
||||
});
|
||||
|
||||
expect(client.query).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
{
|
||||
appId: 'app-1',
|
||||
first: 10,
|
||||
after: 'cursor-99',
|
||||
filter: { platform: AppPlatform.Ios, runtimeVersion: '1.0.0', channel: 'production' },
|
||||
},
|
||||
expect.objectContaining({ additionalTypenames: ['EmbeddedUpdate'] })
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@expo/eas-json",
|
||||
"version": "20.1.0",
|
||||
"version": "20.0.0",
|
||||
"description": "A library for interacting with eas.json",
|
||||
"homepage": "https://github.com/expo/eas-cli",
|
||||
"bugs": "https://github.com/expo/eas-cli/issues",
|
||||
@@ -32,7 +32,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "7.23.5",
|
||||
"@expo/eas-build-job": "20.1.0",
|
||||
"@expo/eas-build-job": "20.0.0",
|
||||
"chalk": "4.1.2",
|
||||
"env-string": "1.0.1",
|
||||
"fs-extra": "11.2.0",
|
||||
|
||||
@@ -131,7 +131,7 @@ PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
bundler (>= 2.4)
|
||||
bundler (~> 1.17)
|
||||
cocoapods (~> 1.12)
|
||||
expo-cocoapods-proxy!
|
||||
rake (~> 10.0)
|
||||
@@ -139,4 +139,4 @@ DEPENDENCIES
|
||||
rubocop (~> 1.48)
|
||||
|
||||
BUNDLED WITH
|
||||
4.0.13
|
||||
2.1.4
|
||||
@@ -32,7 +32,7 @@ Gem::Specification.new do |spec|
|
||||
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
||||
spec.require_paths = ['lib']
|
||||
|
||||
spec.add_development_dependency 'bundler', '>= 2.4'
|
||||
spec.add_development_dependency 'bundler', '~> 1.17'
|
||||
spec.add_development_dependency 'rake', '~> 10.0'
|
||||
spec.add_development_dependency 'rspec', '~> 3.0'
|
||||
end
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "eas-cli-local-build-plugin",
|
||||
"version": "20.1.0",
|
||||
"version": "20.0.0",
|
||||
"description": "Tool for running EAS compatible builds on a local machine.",
|
||||
"bugs": "https://github.com/expo/eas-cli/issues",
|
||||
"license": "BUSL-1.1",
|
||||
@@ -26,8 +26,8 @@
|
||||
"clean": "rimraf node_modules dist coverage \"*.tsbuildinfo\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@expo/build-tools": "20.1.0",
|
||||
"@expo/eas-build-job": "20.1.0",
|
||||
"@expo/build-tools": "20.0.0",
|
||||
"@expo/eas-build-job": "20.0.0",
|
||||
"@expo/logger": "20.0.0",
|
||||
"@expo/spawn-async": "^1.7.2",
|
||||
"@expo/turtle-spawn": "20.0.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@expo/steps",
|
||||
"version": "20.1.0",
|
||||
"version": "20.0.0",
|
||||
"bugs": "https://github.com/expo/eas-cli/issues",
|
||||
"license": "BUSL-1.1",
|
||||
"author": "Expo <[email protected]>",
|
||||
@@ -27,7 +27,7 @@
|
||||
"clean": "rimraf node_modules dist coverage \"*.tsbuildinfo\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@expo/eas-build-job": "20.1.0",
|
||||
"@expo/eas-build-job": "20.0.0",
|
||||
"@expo/logger": "20.0.0",
|
||||
"@expo/spawn-async": "^1.7.2",
|
||||
"arg": "^5.0.2",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@expo/worker",
|
||||
"version": "20.1.0",
|
||||
"version": "20.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "yarn watch",
|
||||
@@ -19,16 +19,16 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/core": "7.24.7",
|
||||
"@expo/build-tools": "20.1.0",
|
||||
"@expo/build-tools": "20.0.0",
|
||||
"@expo/config-plugins": "55.0.7",
|
||||
"@expo/config-types": "55.0.5",
|
||||
"@expo/downloader": "20.0.0",
|
||||
"@expo/eas-build-job": "20.1.0",
|
||||
"@expo/eas-build-job": "20.0.0",
|
||||
"@expo/logger": "20.0.0",
|
||||
"@expo/results": "1.0.0",
|
||||
"@expo/rudder-sdk-node": "1.1.1",
|
||||
"@expo/spawn-async": "1.7.2",
|
||||
"@expo/steps": "20.1.0",
|
||||
"@expo/steps": "20.0.0",
|
||||
"@expo/template-file": "20.0.0",
|
||||
"@expo/turtle-spawn": "20.0.0",
|
||||
"@hapi/boom": "10.0.1",
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
describe('config', () => {
|
||||
const originalEnvironment = process.env.ENVIRONMENT;
|
||||
const originalWorkerRuntimeConfigBase64 = process.env.WORKER_RUNTIME_CONFIG_BASE64;
|
||||
|
||||
afterEach(() => {
|
||||
restoreEnv('ENVIRONMENT', originalEnvironment);
|
||||
restoreEnv('WORKER_RUNTIME_CONFIG_BASE64', originalWorkerRuntimeConfigBase64);
|
||||
jest.resetModules();
|
||||
});
|
||||
|
||||
it('parses cache URL fallbacks from worker runtime config', () => {
|
||||
process.env.ENVIRONMENT = 'staging';
|
||||
process.env.WORKER_RUNTIME_CONFIG_BASE64 = Buffer.from(
|
||||
JSON.stringify({
|
||||
buildId: 'build-id',
|
||||
npmCacheUrl: 'https://npm-cache.example',
|
||||
nodeJsCacheUrl: 'https://node-cache.example',
|
||||
mavenCacheUrl: 'https://maven-cache.example',
|
||||
cocoapodsCacheUrl: 'https://cocoapods-cache.example',
|
||||
})
|
||||
).toString('base64');
|
||||
|
||||
jest.isolateModules(() => {
|
||||
const config = require('../config').default;
|
||||
|
||||
expect(config.npmCacheUrl).toBe('https://npm-cache.example');
|
||||
expect(config.nodeJsCacheUrl).toBe('https://node-cache.example');
|
||||
expect(config.mavenCacheUrl).toBe('https://maven-cache.example');
|
||||
expect(config.cocoapodsCacheUrl).toBe('https://cocoapods-cache.example');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function restoreEnv(key: string, value: string | undefined): void {
|
||||
if (value === undefined) {
|
||||
delete process.env[key];
|
||||
} else {
|
||||
process.env[key] = value;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
import { RuntimeSettings } from '@expo/build-tools';
|
||||
import { Platform, Workflow } from '@expo/eas-build-job';
|
||||
import os from 'os';
|
||||
|
||||
@@ -7,44 +6,9 @@ import { getBuildEnv, getGradleMemoryOptions } from '../env';
|
||||
|
||||
describe(getBuildEnv.name, () => {
|
||||
const originalSentryDsn = config.sentry.dsn;
|
||||
const originalPlatform = process.platform;
|
||||
const originalCacheUrls = {
|
||||
EAS_NPM_CACHE_URL: process.env.EAS_NPM_CACHE_URL,
|
||||
EAS_NODEJS_CACHE_URL: process.env.EAS_NODEJS_CACHE_URL,
|
||||
EAS_MAVEN_CACHE_URL: process.env.EAS_MAVEN_CACHE_URL,
|
||||
EAS_COCOAPODS_CACHE_URL: process.env.EAS_COCOAPODS_CACHE_URL,
|
||||
EAS_BUILD_NPM_CACHE_URL: process.env.EAS_BUILD_NPM_CACHE_URL,
|
||||
NPM_CACHE_URL: process.env.NPM_CACHE_URL,
|
||||
NVM_NODEJS_ORG_MIRROR: process.env.NVM_NODEJS_ORG_MIRROR,
|
||||
EAS_BUILD_MAVEN_CACHE_URL: process.env.EAS_BUILD_MAVEN_CACHE_URL,
|
||||
EAS_BUILD_COCOAPODS_CACHE_URL: process.env.EAS_BUILD_COCOAPODS_CACHE_URL,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
delete process.env.EAS_BUILD_NPM_CACHE_URL;
|
||||
delete process.env.NPM_CACHE_URL;
|
||||
delete process.env.NVM_NODEJS_ORG_MIRROR;
|
||||
delete process.env.EAS_BUILD_MAVEN_CACHE_URL;
|
||||
delete process.env.EAS_BUILD_COCOAPODS_CACHE_URL;
|
||||
jest.spyOn(RuntimeSettings, 'getNpmCacheUrl').mockReturnValue(null);
|
||||
jest.spyOn(RuntimeSettings, 'getNodeJsCacheUrl').mockReturnValue(null);
|
||||
jest.spyOn(RuntimeSettings, 'getMavenCacheUrl').mockReturnValue(null);
|
||||
jest.spyOn(RuntimeSettings, 'getCocoapodsCacheUrl').mockReturnValue(null);
|
||||
jest.spyOn(RuntimeSettings, 'isUsingIosPrecompiledModulesEnabled').mockReturnValue(false);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
config.sentry.dsn = originalSentryDsn;
|
||||
restoreEnv('EAS_NPM_CACHE_URL', originalCacheUrls.EAS_NPM_CACHE_URL);
|
||||
restoreEnv('EAS_NODEJS_CACHE_URL', originalCacheUrls.EAS_NODEJS_CACHE_URL);
|
||||
restoreEnv('EAS_MAVEN_CACHE_URL', originalCacheUrls.EAS_MAVEN_CACHE_URL);
|
||||
restoreEnv('EAS_COCOAPODS_CACHE_URL', originalCacheUrls.EAS_COCOAPODS_CACHE_URL);
|
||||
restoreEnv('EAS_BUILD_NPM_CACHE_URL', originalCacheUrls.EAS_BUILD_NPM_CACHE_URL);
|
||||
restoreEnv('NPM_CACHE_URL', originalCacheUrls.NPM_CACHE_URL);
|
||||
restoreEnv('NVM_NODEJS_ORG_MIRROR', originalCacheUrls.NVM_NODEJS_ORG_MIRROR);
|
||||
restoreEnv('EAS_BUILD_MAVEN_CACHE_URL', originalCacheUrls.EAS_BUILD_MAVEN_CACHE_URL);
|
||||
restoreEnv('EAS_BUILD_COCOAPODS_CACHE_URL', originalCacheUrls.EAS_BUILD_COCOAPODS_CACHE_URL);
|
||||
mockProcessPlatform(originalPlatform);
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
@@ -98,180 +62,6 @@ describe(getBuildEnv.name, () => {
|
||||
expect(env.EXPO_PRECOMPILED_MODULES_PATH).toBeUndefined();
|
||||
});
|
||||
|
||||
it('adds precompiled modules env vars for iOS jobs when enabled', () => {
|
||||
jest.mocked(RuntimeSettings.isUsingIosPrecompiledModulesEnabled).mockReturnValue(true);
|
||||
|
||||
const env = getBuildEnv({
|
||||
job: {
|
||||
platform: Platform.IOS,
|
||||
type: Workflow.GENERIC,
|
||||
builderEnvironment: {
|
||||
env: {},
|
||||
},
|
||||
} as any,
|
||||
projectId: 'project-id',
|
||||
metadata: {
|
||||
buildProfile: 'production',
|
||||
gitCommitHash: 'abc123',
|
||||
username: 'expo-user',
|
||||
} as any,
|
||||
buildId: 'build-id',
|
||||
});
|
||||
|
||||
expect(env.EAS_USE_PRECOMPILED_MODULES).toBe('1');
|
||||
});
|
||||
|
||||
it('does not add precompiled modules env vars for Android jobs when enabled', () => {
|
||||
jest.mocked(RuntimeSettings.isUsingIosPrecompiledModulesEnabled).mockReturnValue(true);
|
||||
|
||||
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_USE_PRECOMPILED_MODULES).toBeUndefined();
|
||||
});
|
||||
|
||||
it('leaves job-provided precompiled modules env vars in override position', () => {
|
||||
jest.mocked(RuntimeSettings.isUsingIosPrecompiledModulesEnabled).mockReturnValue(true);
|
||||
|
||||
const job = {
|
||||
platform: Platform.IOS,
|
||||
type: Workflow.GENERIC,
|
||||
builderEnvironment: {
|
||||
env: {
|
||||
EAS_USE_PRECOMPILED_MODULES: '0',
|
||||
},
|
||||
},
|
||||
} as any;
|
||||
const baseEnv = getBuildEnv({
|
||||
job,
|
||||
projectId: 'project-id',
|
||||
metadata: {
|
||||
buildProfile: 'production',
|
||||
gitCommitHash: 'abc123',
|
||||
username: 'expo-user',
|
||||
} as any,
|
||||
buildId: 'build-id',
|
||||
});
|
||||
|
||||
expect({ ...baseEnv, ...job.builderEnvironment.env }.EAS_USE_PRECOMPILED_MODULES).toBe('0');
|
||||
});
|
||||
|
||||
it('does not expose disabled Linux cache URLs in build env', () => {
|
||||
mockProcessPlatform('linux');
|
||||
process.env.EAS_NPM_CACHE_URL = 'https://npm.example';
|
||||
process.env.EAS_NODEJS_CACHE_URL = 'https://node.example';
|
||||
process.env.EAS_MAVEN_CACHE_URL = 'https://maven.example';
|
||||
|
||||
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.NPM_CACHE_URL).toBeUndefined();
|
||||
expect(env.NVM_NODEJS_ORG_MIRROR).toBeUndefined();
|
||||
expect(env.EAS_BUILD_NPM_CACHE_URL).toBeUndefined();
|
||||
expect(env.EAS_BUILD_MAVEN_CACHE_URL).toBeUndefined();
|
||||
expect(env.EAS_NPM_CACHE_URL).toBeUndefined();
|
||||
expect(env.EAS_NODEJS_CACHE_URL).toBeUndefined();
|
||||
expect(env.EAS_MAVEN_CACHE_URL).toBeUndefined();
|
||||
});
|
||||
|
||||
it('does not expose disabled Darwin cache URLs in build env', () => {
|
||||
mockProcessPlatform('darwin');
|
||||
process.env.EAS_NPM_CACHE_URL = 'https://npm.example';
|
||||
process.env.EAS_NODEJS_CACHE_URL = 'https://node.example';
|
||||
process.env.EAS_COCOAPODS_CACHE_URL = 'https://pods.example';
|
||||
|
||||
const env = getBuildEnv({
|
||||
job: {
|
||||
platform: Platform.IOS,
|
||||
type: Workflow.GENERIC,
|
||||
builderEnvironment: {
|
||||
env: {},
|
||||
},
|
||||
} as any,
|
||||
projectId: 'project-id',
|
||||
metadata: {
|
||||
buildProfile: 'production',
|
||||
gitCommitHash: 'abc123',
|
||||
username: 'expo-user',
|
||||
} as any,
|
||||
buildId: 'build-id',
|
||||
});
|
||||
|
||||
expect(env.NPM_CACHE_URL).toBeUndefined();
|
||||
expect(env.NVM_NODEJS_ORG_MIRROR).toBeUndefined();
|
||||
expect(env.EAS_BUILD_NPM_CACHE_URL).toBeUndefined();
|
||||
expect(env.EAS_BUILD_COCOAPODS_CACHE_URL).toBeUndefined();
|
||||
expect(env.EAS_NPM_CACHE_URL).toBeUndefined();
|
||||
expect(env.EAS_NODEJS_CACHE_URL).toBeUndefined();
|
||||
expect(env.EAS_COCOAPODS_CACHE_URL).toBeUndefined();
|
||||
});
|
||||
|
||||
it('exposes enabled cache URLs inferred from private worker environment variables', () => {
|
||||
mockProcessPlatform('linux');
|
||||
process.env.EAS_NPM_CACHE_URL = 'https://npm.example';
|
||||
process.env.EAS_NODEJS_CACHE_URL = 'https://node.example';
|
||||
process.env.EAS_MAVEN_CACHE_URL = 'https://maven.example';
|
||||
jest.mocked(RuntimeSettings.getNpmCacheUrl).mockReturnValue('https://npm.example');
|
||||
jest.mocked(RuntimeSettings.getNodeJsCacheUrl).mockReturnValue('https://node.example');
|
||||
jest.mocked(RuntimeSettings.getMavenCacheUrl).mockReturnValue('https://maven.example');
|
||||
|
||||
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.NPM_CACHE_URL).toBe('https://npm.example');
|
||||
expect(env.EAS_BUILD_NPM_CACHE_URL).toBe('https://npm.example');
|
||||
expect(env.NVM_NODEJS_ORG_MIRROR).toBe('https://node.example');
|
||||
expect(env.EAS_BUILD_MAVEN_CACHE_URL).toBe('https://maven.example');
|
||||
expect(env.EAS_NPM_CACHE_URL).toBeUndefined();
|
||||
expect(env.EAS_NODEJS_CACHE_URL).toBeUndefined();
|
||||
expect(env.EAS_MAVEN_CACHE_URL).toBeUndefined();
|
||||
});
|
||||
|
||||
it('sizes Gradle memory options from total memory', () => {
|
||||
const totalMemory = jest.spyOn(os, 'totalmem');
|
||||
|
||||
@@ -294,18 +84,3 @@ describe(getBuildEnv.name, () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function mockProcessPlatform(platform: NodeJS.Platform): void {
|
||||
Object.defineProperty(process, 'platform', {
|
||||
configurable: true,
|
||||
value: platform,
|
||||
});
|
||||
}
|
||||
|
||||
function restoreEnv(key: string, value: string | undefined): void {
|
||||
if (value === undefined) {
|
||||
delete process.env[key];
|
||||
} else {
|
||||
process.env[key] = value;
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,10 @@
|
||||
// @ts-nocheck
|
||||
import { RuntimeSettings } from '@expo/build-tools';
|
||||
import { Android, Ios, Job } from '@expo/eas-build-job';
|
||||
import templateFile from '@expo/template-file';
|
||||
import spawn, { SpawnResult } from '@expo/turtle-spawn';
|
||||
import { mkdirp, pathExists } from 'fs-extra';
|
||||
|
||||
import config from '../config';
|
||||
import {
|
||||
prepareRuntimeEnvironment,
|
||||
prepareRuntimeEnvironmentConfigFiles,
|
||||
} from '../runtimeEnvironment';
|
||||
import { pathExists } from 'fs-extra';
|
||||
import { prepareRuntimeEnvironment } from '../runtimeEnvironment';
|
||||
|
||||
jest.mock('fs-extra');
|
||||
jest.mock('@expo/template-file');
|
||||
jest.mock('@expo/turtle-spawn');
|
||||
|
||||
const spawnResult: SpawnResult = {
|
||||
@@ -39,75 +31,10 @@ const ctx = {
|
||||
const builderConfig: Ios.BuilderEnvironment | Android.BuilderEnvironment = {};
|
||||
|
||||
describe('prepareRuntimeEnvironment', () => {
|
||||
const originalEnvironment = config.env;
|
||||
const originalPlatform = process.platform;
|
||||
const originalCacheUrls = {
|
||||
EAS_NPM_CACHE_URL: process.env.EAS_NPM_CACHE_URL,
|
||||
EAS_MAVEN_CACHE_URL: process.env.EAS_MAVEN_CACHE_URL,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.mocked(spawn).mockReset();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
config.env = originalEnvironment;
|
||||
restoreEnv('EAS_NPM_CACHE_URL', originalCacheUrls.EAS_NPM_CACHE_URL);
|
||||
restoreEnv('EAS_MAVEN_CACHE_URL', originalCacheUrls.EAS_MAVEN_CACHE_URL);
|
||||
mockProcessPlatform(originalPlatform);
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe(prepareRuntimeEnvironmentConfigFiles.name, () => {
|
||||
beforeEach(() => {
|
||||
config.env = 'production';
|
||||
process.env.EAS_NPM_CACHE_URL = 'https://npm.example';
|
||||
process.env.EAS_MAVEN_CACHE_URL = 'https://maven.example';
|
||||
});
|
||||
|
||||
it('does not prepare disabled Linux cache config files', async () => {
|
||||
mockProcessPlatform('linux');
|
||||
jest.spyOn(RuntimeSettings, 'getNpmCacheUrl').mockReturnValue(null);
|
||||
jest.spyOn(RuntimeSettings, 'getMavenCacheUrl').mockReturnValue(null);
|
||||
|
||||
await prepareRuntimeEnvironmentConfigFiles();
|
||||
|
||||
expect(spawn).not.toHaveBeenCalledWith('npm', [
|
||||
'config',
|
||||
'set',
|
||||
'registry',
|
||||
'https://npm.example',
|
||||
]);
|
||||
expect(templateFile).not.toHaveBeenCalled();
|
||||
expect(mkdirp).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('prepares enabled Linux cache config files', async () => {
|
||||
mockProcessPlatform('linux');
|
||||
jest.spyOn(RuntimeSettings, 'getNpmCacheUrl').mockReturnValue('https://npm.example');
|
||||
jest.spyOn(RuntimeSettings, 'getMavenCacheUrl').mockReturnValue('https://maven.example');
|
||||
|
||||
await prepareRuntimeEnvironmentConfigFiles();
|
||||
|
||||
expect(spawn).toHaveBeenCalledWith('npm', [
|
||||
'config',
|
||||
'set',
|
||||
'registry',
|
||||
'https://npm.example',
|
||||
]);
|
||||
expect(templateFile).toHaveBeenCalledWith(
|
||||
expect.stringContaining('yarnrc.yml'),
|
||||
{ URL: 'https://npm.example' },
|
||||
expect.stringContaining('.yarnrc.yml')
|
||||
);
|
||||
expect(templateFile).toHaveBeenCalledWith(
|
||||
expect.stringContaining('init.gradle'),
|
||||
{ URL: 'https://maven.example' },
|
||||
expect.stringContaining('init.gradle')
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('installNode', () => {
|
||||
describe('prepareRuntimeEnvironment', () => {
|
||||
beforeEach(() => {
|
||||
@@ -261,18 +188,3 @@ describe('prepareRuntimeEnvironment', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function mockProcessPlatform(platform: NodeJS.Platform): void {
|
||||
Object.defineProperty(process, 'platform', {
|
||||
configurable: true,
|
||||
value: platform,
|
||||
});
|
||||
}
|
||||
|
||||
function restoreEnv(key: string, value: string | undefined): void {
|
||||
if (value === undefined) {
|
||||
delete process.env[key];
|
||||
} else {
|
||||
process.env[key] = value;
|
||||
}
|
||||
}
|
||||
@@ -136,14 +136,14 @@ describe('BuildService Datadog setup', () => {
|
||||
|
||||
expect(datadogSetupMock).toHaveBeenCalledWith({
|
||||
expoApiV2BaseUrl: expect.any(String),
|
||||
turtleBuildId: 'build-id',
|
||||
robotAccessToken: 'token-abc',
|
||||
target: { kind: 'build', turtleBuildId: 'build-id' },
|
||||
});
|
||||
expect(createBuildContextMock).toHaveBeenCalled();
|
||||
expect(datadogFlushAsyncMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('configures Datadog for jobRun jobs with the turtle job run id', async () => {
|
||||
it('configures Datadog for non-platform jobs with the turtle build id', async () => {
|
||||
const service = new BuildService();
|
||||
service.checkForHangingWorker = jest.fn(async () => {});
|
||||
|
||||
@@ -158,8 +158,8 @@ describe('BuildService Datadog setup', () => {
|
||||
|
||||
expect(datadogSetupMock).toHaveBeenCalledWith({
|
||||
expoApiV2BaseUrl: expect.any(String),
|
||||
turtleBuildId: 'build-id',
|
||||
robotAccessToken: 'token-abc',
|
||||
target: { kind: 'jobRun', turtleJobRunId: 'build-id' },
|
||||
});
|
||||
expect(createBuildContextMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { RuntimeSettings } from '@expo/build-tools';
|
||||
import { Env, Job, Metadata, Platform, Workflow } from '@expo/eas-build-job';
|
||||
import { spawnSync } from 'child_process';
|
||||
import micromatch from 'micromatch';
|
||||
@@ -36,14 +35,9 @@ export function getBuildEnv({
|
||||
setEnv(env, 'EAS_BUILD_PLATFORM', job.platform);
|
||||
setEnv(env, 'EAS_CLI_SENTRY_DSN', config.sentry.dsn);
|
||||
// NPM_CACHE_URL is deprecated
|
||||
const npmCacheUrl = RuntimeSettings.getNpmCacheUrl();
|
||||
const nodeJsCacheUrl = RuntimeSettings.getNodeJsCacheUrl();
|
||||
const mavenCacheUrl = RuntimeSettings.getMavenCacheUrl();
|
||||
const cocoapodsCacheUrl = RuntimeSettings.getCocoapodsCacheUrl();
|
||||
|
||||
setEnv(env, 'NPM_CACHE_URL', npmCacheUrl);
|
||||
setEnv(env, 'NVM_NODEJS_ORG_MIRROR', nodeJsCacheUrl);
|
||||
setEnv(env, 'EAS_BUILD_NPM_CACHE_URL', npmCacheUrl);
|
||||
setEnv(env, 'NPM_CACHE_URL', config.npmCacheUrl);
|
||||
setEnv(env, 'NVM_NODEJS_ORG_MIRROR', config.nodeJsCacheUrl);
|
||||
setEnv(env, 'EAS_BUILD_NPM_CACHE_URL', config.npmCacheUrl);
|
||||
setEnv(env, 'EAS_BUILD_PROFILE', metadata.buildProfile);
|
||||
setEnv(env, 'EAS_BUILD_GIT_COMMIT_HASH', metadata.gitCommitHash);
|
||||
setEnv(env, 'EAS_BUILD_ID', buildId);
|
||||
@@ -53,11 +47,8 @@ export function getBuildEnv({
|
||||
|
||||
const runnerPlatform = job.platform;
|
||||
if (runnerPlatform === Platform.IOS) {
|
||||
setEnv(env, 'EAS_BUILD_COCOAPODS_CACHE_URL', cocoapodsCacheUrl);
|
||||
setEnv(env, 'EAS_BUILD_COCOAPODS_CACHE_URL', config.cocoapodsCacheUrl);
|
||||
setEnv(env, 'COMPILER_INDEX_STORE_ENABLE', 'NO');
|
||||
if (RuntimeSettings.isUsingIosPrecompiledModulesEnabled()) {
|
||||
setEnv(env, 'EAS_USE_PRECOMPILED_MODULES', '1');
|
||||
}
|
||||
|
||||
if (job.builderEnvironment?.env?.EAS_USE_CACHE === '1') {
|
||||
setEnv(env, 'USE_CCACHE', '1');
|
||||
@@ -78,7 +69,7 @@ export function getBuildEnv({
|
||||
setEnv(env, 'ANDROID_CCACHE', binPath);
|
||||
}
|
||||
}
|
||||
setEnv(env, 'EAS_BUILD_MAVEN_CACHE_URL', mavenCacheUrl);
|
||||
setEnv(env, 'EAS_BUILD_MAVEN_CACHE_URL', config.mavenCacheUrl);
|
||||
}
|
||||
|
||||
if (config.env !== Environment.TEST) {
|
||||
@@ -145,14 +136,7 @@ export function getBuildEnv({
|
||||
}
|
||||
|
||||
function getFilteredEnv(): Env {
|
||||
const envToFilter = [
|
||||
...getAccessedEnvs(),
|
||||
'EAS_NPM_CACHE_URL',
|
||||
'EAS_NODEJS_CACHE_URL',
|
||||
'EAS_MAVEN_CACHE_URL',
|
||||
'EAS_COCOAPODS_CACHE_URL',
|
||||
'KUBERNETES_*',
|
||||
];
|
||||
const envToFilter = [...getAccessedEnvs(), 'KUBERNETES_*'];
|
||||
const envToReturn = micromatch(
|
||||
Object.keys(process.env),
|
||||
envToFilter.map(env => `!${env}`)
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { RuntimeSettings } from '@expo/build-tools';
|
||||
|
||||
import config from './config';
|
||||
import logger from './logger';
|
||||
import { startServer } from './metricsServer';
|
||||
@@ -9,16 +7,6 @@ import { prepareWorkingdir } from './workingdir';
|
||||
import startWsServer from './ws';
|
||||
|
||||
async function main(): Promise<void> {
|
||||
await RuntimeSettings.loadAsync({
|
||||
environment: config.env,
|
||||
logger,
|
||||
cacheUrlFallbacks: {
|
||||
npm: config.npmCacheUrl,
|
||||
nodejs: config.nodeJsCacheUrl,
|
||||
maven: config.mavenCacheUrl,
|
||||
cocoapods: config.cocoapodsCacheUrl,
|
||||
},
|
||||
});
|
||||
await prepareRuntimeEnvironmentConfigFiles();
|
||||
await prepareWorkingdir();
|
||||
startWsServer();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BuildContext, RuntimeSettings } from '@expo/build-tools';
|
||||
import { BuildContext } from '@expo/build-tools';
|
||||
import { Job, errors } from '@expo/eas-build-job';
|
||||
import templateFile from '@expo/template-file';
|
||||
import spawn from '@expo/turtle-spawn';
|
||||
@@ -23,28 +23,25 @@ export async function prepareRuntimeEnvironmentConfigFiles(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
const npmCacheUrl = RuntimeSettings.getNpmCacheUrl();
|
||||
const mavenCacheUrl = RuntimeSettings.getMavenCacheUrl();
|
||||
|
||||
if (npmCacheUrl) {
|
||||
if (config.npmCacheUrl) {
|
||||
// create ~/.npmrc
|
||||
await spawn('npm', ['config', 'set', 'registry', npmCacheUrl]);
|
||||
await spawn('npm', ['config', 'set', 'registry', config.npmCacheUrl]);
|
||||
// create ~/.yarnrc.yml
|
||||
await templateFile(
|
||||
path.join(__dirname, '../src/templates/yarnrc.yml'),
|
||||
{
|
||||
URL: npmCacheUrl,
|
||||
URL: config.npmCacheUrl,
|
||||
},
|
||||
path.join(os.homedir(), '.yarnrc.yml')
|
||||
);
|
||||
}
|
||||
|
||||
if (mavenCacheUrl) {
|
||||
if (config.mavenCacheUrl) {
|
||||
await fs.mkdirp(path.join(os.homedir(), '.gradle'));
|
||||
await templateFile(
|
||||
path.join(__dirname, '../src/templates/init.gradle'),
|
||||
{
|
||||
URL: mavenCacheUrl,
|
||||
URL: config.mavenCacheUrl,
|
||||
},
|
||||
path.join(os.homedir(), '.gradle/init.gradle')
|
||||
);
|
||||
|
||||
@@ -283,12 +283,8 @@ export default class BuildService {
|
||||
if (robotAccessToken) {
|
||||
Datadog.setup({
|
||||
expoApiV2BaseUrl: config.wwwApiV2BaseUrl,
|
||||
turtleBuildId: this.buildId,
|
||||
robotAccessToken,
|
||||
// job.platform is set only for build jobs; for a jobRun, this.buildId is the
|
||||
// turtle-job-run id (verified by the jobType === 'jobRun' guard in startBuild()).
|
||||
target: job.platform
|
||||
? { kind: 'build', turtleBuildId: this.buildId }
|
||||
: { kind: 'jobRun', turtleJobRunId: this.buildId },
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [[ $# -ne 1 || ("$1" != "staging" && "$1" != "production") ]]; then
|
||||
echo "Usage: scripts/edit-runtime-settings staging|production" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v curl >/dev/null 2>&1; then
|
||||
echo "curl is required." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for tool in gcloud jq; do
|
||||
if ! command -v "$tool" >/dev/null 2>&1; then
|
||||
echo "$tool is required. Run \`mise install\` from the repository root and try again." >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
env="$1"
|
||||
object_uri="gs://eas-workflows-${env}/runtime-settings.json"
|
||||
object_url="https://storage.googleapis.com/eas-workflows-${env}/runtime-settings.json"
|
||||
cache_control="no-cache, no-store, must-revalidate"
|
||||
editor="${EDITOR:-vi}"
|
||||
|
||||
tmpfile="$(mktemp)"
|
||||
minified_tmpfile="$(mktemp)"
|
||||
downloaded_tmpfile="$(mktemp)"
|
||||
trap 'rm -f "$tmpfile" "$minified_tmpfile" "$downloaded_tmpfile"' EXIT
|
||||
|
||||
echo "Remote URL: $object_url"
|
||||
curl -fsSL "$object_url" | jq . >"$tmpfile"
|
||||
echo "Edit and verify the pretty-formatted JSON file before saving."
|
||||
|
||||
while true; do
|
||||
"$editor" "$tmpfile"
|
||||
if jq empty "$tmpfile" >/dev/null; then
|
||||
break
|
||||
fi
|
||||
|
||||
read -r -n 1 -s -p "JSON is invalid. Press any key to reopen editor." _
|
||||
echo >&2
|
||||
done
|
||||
|
||||
jq -c . "$tmpfile" >"$minified_tmpfile"
|
||||
|
||||
echo "Contents to upload:"
|
||||
cat "$minified_tmpfile"
|
||||
echo
|
||||
|
||||
if [[ "$env" == "production" ]]; then
|
||||
confirmation=""
|
||||
read -r -p 'Type "production" to upload production runtime settings: ' confirmation
|
||||
if [[ "$confirmation" != "production" ]]; then
|
||||
echo "Production upload canceled." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
gcloud storage cp \
|
||||
--cache-control="$cache_control" \
|
||||
--content-type=application/json \
|
||||
--predefined-acl=publicRead \
|
||||
"$minified_tmpfile" \
|
||||
"$object_uri"
|
||||
|
||||
curl -fsSL "$object_url" >"$downloaded_tmpfile"
|
||||
if ! cmp -s "$minified_tmpfile" "$downloaded_tmpfile"; then
|
||||
echo "Read-after-write check failed: uploaded file differs from remote object." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Confirmed remote contents:"
|
||||
cat "$downloaded_tmpfile"
|
||||
echo
|
||||
@@ -2856,22 +2856,22 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@expo/build-tools@npm:20.1.0, @expo/build-tools@workspace:packages/build-tools":
|
||||
"@expo/build-tools@npm:20.0.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:55.0.10"
|
||||
"@expo/config-plugins": "npm:55.0.7"
|
||||
"@expo/downloader": "npm:20.0.0"
|
||||
"@expo/eas-build-job": "npm:20.1.0"
|
||||
"@expo/eas-build-job": "npm:20.0.0"
|
||||
"@expo/env": "npm:^0.4.0"
|
||||
"@expo/logger": "npm:20.0.0"
|
||||
"@expo/package-manager": "npm:1.9.10"
|
||||
"@expo/plist": "npm:^0.2.0"
|
||||
"@expo/repack-app": "npm:~0.6.1"
|
||||
"@expo/repack-app": "npm:~0.4.1"
|
||||
"@expo/results": "npm:^1.0.0"
|
||||
"@expo/spawn-async": "npm:1.7.2"
|
||||
"@expo/steps": "npm:20.1.0"
|
||||
"@expo/steps": "npm:20.0.0"
|
||||
"@expo/template-file": "npm:20.0.0"
|
||||
"@expo/turtle-spawn": "npm:20.0.0"
|
||||
"@expo/xcpretty": "npm:^4.3.1"
|
||||
@@ -3113,7 +3113,7 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@expo/eas-build-job@npm:20.1.0, @expo/eas-build-job@workspace:packages/eas-build-job":
|
||||
"@expo/eas-build-job@npm:20.0.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:
|
||||
@@ -3132,12 +3132,12 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@expo/eas-json@npm:20.1.0, @expo/eas-json@workspace:packages/eas-json":
|
||||
"@expo/eas-json@npm:20.0.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:20.1.0"
|
||||
"@expo/eas-build-job": "npm:20.0.0"
|
||||
"@tsconfig/node20": "npm:20.1.8"
|
||||
"@types/babel__code-frame": "npm:7.0.3"
|
||||
"@types/fs-extra": "npm:11.0.4"
|
||||
@@ -3419,15 +3419,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@expo/repack-app@npm:~0.6.1":
|
||||
version: 0.6.1
|
||||
resolution: "@expo/repack-app@npm:0.6.1"
|
||||
"@expo/repack-app@npm:~0.4.1":
|
||||
version: 0.4.1
|
||||
resolution: "@expo/repack-app@npm:0.4.1"
|
||||
dependencies:
|
||||
commander: "npm:^14.0.2"
|
||||
picocolors: "npm:^1.1.1"
|
||||
bin:
|
||||
repack-app: bin/cli.js
|
||||
checksum: 10c0/fdf3088b4f327393056631270bf90a67ba949e7994892a62abc985bd9478119ab2f1d5be35fa1efb6bd1d2780c49a3dbb87cb669d9c65b44e3d9a0b41fbf091f
|
||||
checksum: 10c0/4bc30a7ca95221f079026633802c32894ef25d55677212e86859194bf1a042598eaa5977510a277c3ceee6af6bd0d0a3aff3e21c4510c37b7dea584dd927ed0e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -3485,11 +3485,11 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@expo/steps@npm:20.1.0, @expo/steps@workspace:packages/steps":
|
||||
"@expo/steps@npm:20.0.0, @expo/steps@workspace:packages/steps":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@expo/steps@workspace:packages/steps"
|
||||
dependencies:
|
||||
"@expo/eas-build-job": "npm:20.1.0"
|
||||
"@expo/eas-build-job": "npm:20.0.0"
|
||||
"@expo/logger": "npm:20.0.0"
|
||||
"@expo/spawn-async": "npm:^1.7.2"
|
||||
"@jest/globals": "npm:^29.7.0"
|
||||
@@ -3558,16 +3558,16 @@ __metadata:
|
||||
resolution: "@expo/worker@workspace:packages/worker"
|
||||
dependencies:
|
||||
"@babel/core": "npm:7.24.7"
|
||||
"@expo/build-tools": "npm:20.1.0"
|
||||
"@expo/build-tools": "npm:20.0.0"
|
||||
"@expo/config-plugins": "npm:55.0.7"
|
||||
"@expo/config-types": "npm:55.0.5"
|
||||
"@expo/downloader": "npm:20.0.0"
|
||||
"@expo/eas-build-job": "npm:20.1.0"
|
||||
"@expo/eas-build-job": "npm:20.0.0"
|
||||
"@expo/logger": "npm:20.0.0"
|
||||
"@expo/results": "npm:1.0.0"
|
||||
"@expo/rudder-sdk-node": "npm:1.1.1"
|
||||
"@expo/spawn-async": "npm:1.7.2"
|
||||
"@expo/steps": "npm:20.1.0"
|
||||
"@expo/steps": "npm:20.0.0"
|
||||
"@expo/template-file": "npm:20.0.0"
|
||||
"@expo/turtle-spawn": "npm:20.0.0"
|
||||
"@hapi/boom": "npm:10.0.1"
|
||||
@@ -10487,7 +10487,7 @@ __metadata:
|
||||
resolution: "create-eas-build-function@workspace:packages/create-eas-build-function"
|
||||
dependencies:
|
||||
"@expo/package-manager": "npm:1.9.10"
|
||||
"@expo/steps": "npm:20.1.0"
|
||||
"@expo/steps": "npm:20.0.0"
|
||||
"@jest/globals": "npm:^29.7.0"
|
||||
"@types/jest": "npm:^29.5.12"
|
||||
"@types/node": "npm:20.14.2"
|
||||
@@ -11100,8 +11100,8 @@ __metadata:
|
||||
version: 0.0.0-use.local
|
||||
resolution: "eas-cli-local-build-plugin@workspace:packages/local-build-plugin"
|
||||
dependencies:
|
||||
"@expo/build-tools": "npm:20.1.0"
|
||||
"@expo/eas-build-job": "npm:20.1.0"
|
||||
"@expo/build-tools": "npm:20.0.0"
|
||||
"@expo/eas-build-job": "npm:20.0.0"
|
||||
"@expo/logger": "npm:20.0.0"
|
||||
"@expo/spawn-async": "npm:^1.7.2"
|
||||
"@expo/turtle-spawn": "npm:20.0.0"
|
||||
@@ -11158,8 +11158,8 @@ __metadata:
|
||||
"@expo/code-signing-certificates": "npm:0.0.5"
|
||||
"@expo/config": "npm:55.0.10"
|
||||
"@expo/config-plugins": "npm:55.0.7"
|
||||
"@expo/eas-build-job": "npm:20.1.0"
|
||||
"@expo/eas-json": "npm:20.1.0"
|
||||
"@expo/eas-build-job": "npm:20.0.0"
|
||||
"@expo/eas-json": "npm:20.0.0"
|
||||
"@expo/env": "npm:^1.0.0"
|
||||
"@expo/json-file": "npm:8.3.3"
|
||||
"@expo/logger": "npm:20.0.0"
|
||||
@@ -11174,7 +11174,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:20.1.0"
|
||||
"@expo/steps": "npm:20.0.0"
|
||||
"@expo/timeago.js": "npm:1.0.0"
|
||||
"@graphql-codegen/cli": "npm:5.0.0"
|
||||
"@graphql-codegen/introspection": "npm:4.0.0"
|
||||
|
||||
Reference in new issue
Block a user