@e2e-dev/eas 0.3.2-nightly-20261015074742 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.build.json +1 -1
- package/dist/client.js +64 -25
- package/dist/credentials.js +3 -1
- package/dist/json.d.ts +2 -0
- package/dist/json.js +4 -0
- package/dist/project.js +1 -4
- package/dist/provider.d.ts +3 -2
- package/dist/provider.js +6 -0
- package/package.json +5 -5
package/dist/.build.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"0.3.2
|
|
1
|
+
{"version":"0.3.2"}
|
package/dist/client.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* EAS Simulators' sessions through Expo's GraphQL API over `fetch`, the API
|
|
3
3
|
* `eas simulator:*` uses, so the integration installs no SDK.
|
|
4
4
|
*/
|
|
5
|
+
import { isRecord } from './json.js';
|
|
5
6
|
const GRAPHQL_URL = 'https://api.expo.dev/graphql';
|
|
6
7
|
const CREATE = `mutation ($input: CreateDeviceRunSessionInput!) {
|
|
7
8
|
deviceRunSession { createDeviceRunSession(deviceRunSessionInput: $input) { id app { slug ownerAccount { name } } } }
|
|
@@ -26,7 +27,7 @@ const ENDED_JOB = new Set(['ERRORED', 'CANCELED', 'FINISHED', 'PENDING_CANCEL'])
|
|
|
26
27
|
/** EAS Simulators sessions for one Expo access token or eas-cli session. */
|
|
27
28
|
export function easSessions(credentials) {
|
|
28
29
|
const auth = 'accessToken' in credentials ? { Authorization: `Bearer ${credentials.accessToken}` } : { 'expo-session': credentials.sessionSecret };
|
|
29
|
-
/** Runs one operation
|
|
30
|
+
/** Runs one operation and returns its unchecked `data`; a GraphQL error throws with EAS's message and error code. */
|
|
30
31
|
const graphql = async (query, variables, signal) => {
|
|
31
32
|
const response = await fetch(GRAPHQL_URL, {
|
|
32
33
|
method: 'POST',
|
|
@@ -42,12 +43,17 @@ export function easSessions(credentials) {
|
|
|
42
43
|
catch {
|
|
43
44
|
throw new Error(`EAS: HTTP ${response.status}, ${text.trim().slice(0, 200)}`);
|
|
44
45
|
}
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
const errors = field(body, 'errors');
|
|
47
|
+
const error = Array.isArray(errors) ? errors[0] : undefined;
|
|
48
|
+
if (error !== undefined) {
|
|
49
|
+
const message = field(error, 'message');
|
|
50
|
+
const code = field(field(error, 'extensions'), 'errorCode');
|
|
51
|
+
throw new EasError(typeof message === 'string' ? message : `HTTP ${response.status}`, typeof code === 'string' ? code : undefined);
|
|
52
|
+
}
|
|
53
|
+
const data = field(body, 'data');
|
|
54
|
+
if (!response.ok || data == null)
|
|
49
55
|
throw new Error(`EAS: HTTP ${response.status}, no data`);
|
|
50
|
-
return
|
|
56
|
+
return data;
|
|
51
57
|
};
|
|
52
58
|
return {
|
|
53
59
|
async create({ platform, deviceIdentifier, ...fields }, signal) {
|
|
@@ -57,42 +63,75 @@ export function easSessions(credentials) {
|
|
|
57
63
|
type: 'AGENT_DEVICE',
|
|
58
64
|
...(deviceIdentifier === undefined ? {} : { [platform]: { deviceIdentifier } }),
|
|
59
65
|
};
|
|
60
|
-
const
|
|
61
|
-
const
|
|
62
|
-
|
|
66
|
+
const created = field(field(await graphql(CREATE, { input }, signal), 'deviceRunSession'), 'createDeviceRunSession');
|
|
67
|
+
const id = field(created, 'id');
|
|
68
|
+
const app = field(created, 'app');
|
|
69
|
+
const slug = field(app, 'slug');
|
|
70
|
+
const owner = field(field(app, 'ownerAccount'), 'name');
|
|
71
|
+
if (typeof id !== 'string' || typeof slug !== 'string' || typeof owner !== 'string')
|
|
72
|
+
throw unexpected('createDeviceRunSession');
|
|
73
|
+
return { id, url: `https://expo.dev/accounts/${encodeURIComponent(owner)}/projects/${encodeURIComponent(slug)}/simulator-sessions/${id}` };
|
|
63
74
|
},
|
|
64
75
|
async state(id, signal) {
|
|
65
|
-
const
|
|
66
|
-
const
|
|
67
|
-
const
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
76
|
+
const session = field(field(await graphql(STATE, { id }, signal), 'deviceRunSessions'), 'byId');
|
|
77
|
+
const status = field(session, 'status');
|
|
78
|
+
const jobRun = field(session, 'turtleJobRun');
|
|
79
|
+
const job = field(jobRun, 'status');
|
|
80
|
+
const config = field(session, 'remoteConfig');
|
|
81
|
+
const daemonUrl = field(config, 'agentDeviceRemoteSessionUrl');
|
|
82
|
+
const daemonToken = field(config, 'agentDeviceRemoteSessionToken');
|
|
83
|
+
const previewUrl = field(config, 'webPreviewUrl');
|
|
84
|
+
const previewToken = field(config, 'webPreviewToken');
|
|
85
|
+
if (typeof status !== 'string' ||
|
|
86
|
+
(jobRun != null && typeof job !== 'string') ||
|
|
87
|
+
(config != null && !isRecord(config)) ||
|
|
88
|
+
![daemonUrl, daemonToken, previewUrl, previewToken].every((value) => value == null || typeof value === 'string')) {
|
|
89
|
+
throw unexpected('deviceRunSessions.byId');
|
|
90
|
+
}
|
|
91
|
+
if (status === 'STOPPED' || status === 'ERRORED')
|
|
92
|
+
return { phase: 'ended', status: status.toLowerCase() };
|
|
71
93
|
// A job that ended keeps the daemon address it had, which no longer answers.
|
|
72
|
-
if (job
|
|
94
|
+
if (typeof job === 'string' && ENDED_JOB.has(job))
|
|
73
95
|
return { phase: 'ended', status: `job ${job.toLowerCase()}` };
|
|
74
|
-
if (
|
|
75
|
-
const openPreviewUrl =
|
|
76
|
-
return { phase: 'ready', daemonUrl
|
|
96
|
+
if (typeof daemonUrl === 'string' && typeof daemonToken === 'string') {
|
|
97
|
+
const openPreviewUrl = previewToken == null && typeof previewUrl === 'string' ? previewUrl : undefined;
|
|
98
|
+
return { phase: 'ready', daemonUrl, daemonToken, openPreviewUrl };
|
|
77
99
|
}
|
|
78
|
-
return { phase: job
|
|
100
|
+
return { phase: typeof job !== 'string' || QUEUED_JOB.has(job) ? 'queued' : 'starting' };
|
|
79
101
|
},
|
|
80
102
|
async stop(id, signal) {
|
|
81
|
-
let
|
|
103
|
+
let data;
|
|
82
104
|
try {
|
|
83
|
-
|
|
84
|
-
job = data.deviceRunSession.ensureDeviceRunSessionStopped.turtleJobRun;
|
|
105
|
+
data = await graphql(STOP, { id }, signal);
|
|
85
106
|
}
|
|
86
107
|
catch (cause) {
|
|
87
108
|
if (cause instanceof EasError && cause.code === 'NOT_FOUND_ERROR')
|
|
88
109
|
return;
|
|
89
110
|
throw cause;
|
|
90
111
|
}
|
|
91
|
-
|
|
92
|
-
|
|
112
|
+
const stopped = field(field(data, 'deviceRunSession'), 'ensureDeviceRunSessionStopped');
|
|
113
|
+
if (!isRecord(stopped))
|
|
114
|
+
throw unexpected('ensureDeviceRunSessionStopped');
|
|
115
|
+
const job = stopped['turtleJobRun'];
|
|
116
|
+
if (job === null)
|
|
117
|
+
return;
|
|
118
|
+
const jobId = field(job, 'id');
|
|
119
|
+
const jobStatus = field(job, 'status');
|
|
120
|
+
if (typeof jobId !== 'string' || typeof jobStatus !== 'string')
|
|
121
|
+
throw unexpected('ensureDeviceRunSessionStopped');
|
|
122
|
+
if (QUEUED_JOB.has(jobStatus))
|
|
123
|
+
await graphql(CANCEL_JOB, { id: jobId }, signal);
|
|
93
124
|
},
|
|
94
125
|
};
|
|
95
126
|
}
|
|
127
|
+
/** `key` of a JSON object; `undefined` for a missing key or a value that is not an object. */
|
|
128
|
+
function field(value, key) {
|
|
129
|
+
return isRecord(value) ? value[key] : undefined;
|
|
130
|
+
}
|
|
131
|
+
/** The error for an answer without the fields an operation reads; it names no value, since the answer can hold a daemon token. */
|
|
132
|
+
function unexpected(operation) {
|
|
133
|
+
return new Error(`EAS: unexpected response to ${operation}`);
|
|
134
|
+
}
|
|
96
135
|
/** An error EAS answered with, its `errorCode` kept. */
|
|
97
136
|
class EasError extends Error {
|
|
98
137
|
code;
|
package/dist/credentials.js
CHANGED
|
@@ -9,6 +9,7 @@ import { readFile } from 'node:fs/promises';
|
|
|
9
9
|
import { homedir } from 'node:os';
|
|
10
10
|
import { join } from 'node:path';
|
|
11
11
|
import { envValue } from './env.js';
|
|
12
|
+
import { isRecord } from './json.js';
|
|
12
13
|
export const EXPO_TOKEN = 'EXPO_TOKEN';
|
|
13
14
|
/**
|
|
14
15
|
* `EXPO_TOKEN` from the run's environment, else the `auth.sessionSecret` eas-cli
|
|
@@ -36,7 +37,8 @@ export async function expoCredentials(env) {
|
|
|
36
37
|
// No cause: a JSON parse error quotes the text around the fault, which can be the secret.
|
|
37
38
|
throw new Error(`the eas-cli login at ${path} is not valid JSON; run \`eas login\` again, or set ${EXPO_TOKEN}`);
|
|
38
39
|
}
|
|
39
|
-
const
|
|
40
|
+
const auth = isRecord(state) ? state['auth'] : undefined;
|
|
41
|
+
const sessionSecret = isRecord(auth) ? auth['sessionSecret'] : undefined;
|
|
40
42
|
return typeof sessionSecret === 'string' && sessionSecret.trim() !== '' ? { sessionSecret: sessionSecret.trim() } : undefined;
|
|
41
43
|
}
|
|
42
44
|
/** eas-cli's production `state.json` under the run's home: `USERPROFILE` on Windows and `HOME` elsewhere, as `os.homedir()` resolves it. */
|
package/dist/json.d.ts
ADDED
package/dist/json.js
ADDED
package/dist/project.js
CHANGED
|
@@ -8,6 +8,7 @@ import { existsSync } from 'node:fs';
|
|
|
8
8
|
import { readFile } from 'node:fs/promises';
|
|
9
9
|
import { dirname, join } from 'node:path';
|
|
10
10
|
import { promisify } from 'node:util';
|
|
11
|
+
import { isRecord } from './json.js';
|
|
11
12
|
const execFileAsync = promisify(execFile);
|
|
12
13
|
/** The dynamic app config names Expo resolves, in its order; one of them wins over `app.json`. */
|
|
13
14
|
const DYNAMIC_CONFIGS = ['app.config.ts', 'app.config.mts', 'app.config.cts', 'app.config.mjs', 'app.config.cjs', 'app.config.js'];
|
|
@@ -93,7 +94,3 @@ function projectIdOf(config, source) {
|
|
|
93
94
|
return projectId.trim();
|
|
94
95
|
throw new Error(`${source} has no \`extra.eas.projectId\`; ${PASS_IT}`);
|
|
95
96
|
}
|
|
96
|
-
/** A plain JSON object, not an array or `null`. */
|
|
97
|
-
function isRecord(value) {
|
|
98
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
99
|
-
}
|
package/dist/provider.d.ts
CHANGED
|
@@ -21,13 +21,14 @@ export interface EasSimulatorsOptions {
|
|
|
21
21
|
readonly device?: string | undefined;
|
|
22
22
|
/**
|
|
23
23
|
* Minutes without an agent-device command after which EAS stops the
|
|
24
|
-
* session.
|
|
24
|
+
* session. Must be a non-negative integer; zero omits the idle limit.
|
|
25
|
+
* Defaults to 10, or one less than `maxDurationMinutes` when that
|
|
25
26
|
* is shorter, since EAS wants it below the duration. A session still
|
|
26
27
|
* queued once another session of the run has been ready for that one's
|
|
27
28
|
* limit fails the lease: EAS is stopping that one.
|
|
28
29
|
*/
|
|
29
30
|
readonly maxIdleTimeMinutes?: number | undefined;
|
|
30
|
-
/**
|
|
31
|
+
/** Non-negative integer minutes a session may run once ready before EAS stops it. Absent, the account's cap: 40, or 115 on a high-priority plan. */
|
|
31
32
|
readonly maxDurationMinutes?: number | undefined;
|
|
32
33
|
/**
|
|
33
34
|
* The agent-device version EAS starts the session's daemon at. Defaults to
|
package/dist/provider.js
CHANGED
|
@@ -52,6 +52,12 @@ const OPTION_KEYS = Object.keys({
|
|
|
52
52
|
*/
|
|
53
53
|
export function easSimulators(options = {}) {
|
|
54
54
|
rejectUnknownKeys('easSimulators()', options, OPTION_KEYS);
|
|
55
|
+
for (const key of ['maxIdleTimeMinutes', 'maxDurationMinutes']) {
|
|
56
|
+
const value = options[key];
|
|
57
|
+
if (value !== undefined && (!Number.isInteger(value) || value < 0)) {
|
|
58
|
+
throw new ConfigurationError('INVALID_CONFIG', `easSimulators: \`${key}\` must be a non-negative integer`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
55
61
|
const { projectId, buildId, applicationArchiveUrl, device, maxDurationMinutes, agentDeviceVersion, tags = [] } = options;
|
|
56
62
|
if (buildId !== undefined && applicationArchiveUrl !== undefined) {
|
|
57
63
|
throw new ConfigurationError('INVALID_CONFIG', 'easSimulators: pass `buildId` or `applicationArchiveUrl`, not both');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@e2e-dev/eas",
|
|
3
|
-
"version": "0.3.2
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "EAS Simulators for e2e: run mobile targets on Expo's hosted iOS simulators and Android emulators",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"e2e",
|
|
@@ -45,13 +45,13 @@
|
|
|
45
45
|
"NOTICE"
|
|
46
46
|
],
|
|
47
47
|
"peerDependencies": {
|
|
48
|
-
"@e2e-dev/mobile": "0.
|
|
49
|
-
"e2e": "0.
|
|
48
|
+
"@e2e-dev/mobile": ">=0.8.0 <1",
|
|
49
|
+
"e2e": ">=0.15.0 <1"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
|
-
"@e2e-dev/mobile": "0.12.
|
|
52
|
+
"@e2e-dev/mobile": "0.12.3",
|
|
53
53
|
"@types/node": "26.6.2",
|
|
54
|
-
"e2e": "0.21.
|
|
54
|
+
"e2e": "0.21.2",
|
|
55
55
|
"typescript": "7.0.2",
|
|
56
56
|
"vitest": "5.0.1"
|
|
57
57
|
},
|