eoas 3.0.0-alpha.1 → 3.0.0-beta.10
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/README.md +3 -3
- package/dist/commands/doctor.d.ts +12 -0
- package/dist/commands/doctor.js +127 -0
- package/dist/commands/init.js +2 -2
- package/dist/commands/publish.d.ts +2 -0
- package/dist/commands/publish.js +46 -9
- package/dist/commands/republish.js +16 -11
- package/dist/commands/rollback.js +4 -4
- package/dist/lib/assets.d.ts +7 -3
- package/dist/lib/assets.js +21 -4
- package/dist/lib/auth.d.ts +8 -3
- package/dist/lib/auth.js +25 -5
- package/dist/lib/expoConfig.d.ts +1 -0
- package/dist/lib/expoConfig.js +15 -6
- package/dist/lib/packageRunner.d.ts +7 -1
- package/dist/lib/packageRunner.js +17 -5
- package/package.json +8 -5
package/README.md
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
# EOAS (Expo Open Application Services)
|
|
2
2
|
|
|
3
|
-
EOAS ((Expo Open Application Services) is a powerful helper package designed to simplify the setup and update publication process for the [expo-open-ota](https://github.com/
|
|
3
|
+
EOAS ((Expo Open Application Services) is a powerful helper package designed to simplify the setup and update publication process for the [expo-open-ota](https://github.com/mercuretechnologies/expo-open-ota) project.
|
|
4
4
|
|
|
5
5
|
## Quick Start
|
|
6
6
|
|
|
7
7
|
To get started with EOAS, check out the official documentation:
|
|
8
|
-
[EOAS Official Documentation](https://
|
|
8
|
+
[EOAS Official Documentation](https://mercure-technologies.gitbook.io/expo-open-ota/eoas/overview)
|
|
9
9
|
|
|
10
10
|
## Learn More
|
|
11
11
|
For detailed information and to explore the core functionalities of expo-open-ota, visit the main repository:
|
|
12
|
-
[expo-open-ota on GitHub](https://github.com/
|
|
12
|
+
[expo-open-ota on GitHub](https://github.com/mercuretechnologies/expo-open-ota)
|
|
13
13
|
|
|
14
14
|
---
|
|
15
15
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class Doctor extends Command {
|
|
3
|
+
static args: {};
|
|
4
|
+
static description: string;
|
|
5
|
+
static examples: string[];
|
|
6
|
+
static flags: {
|
|
7
|
+
channel: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
8
|
+
url: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
9
|
+
appId: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
10
|
+
};
|
|
11
|
+
run(): Promise<void>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
const core_1 = require("@oclif/core");
|
|
5
|
+
const expoConfig_1 = require("../lib/expoConfig");
|
|
6
|
+
const fetch_1 = require("../lib/fetch");
|
|
7
|
+
const log_1 = tslib_1.__importDefault(require("../lib/log"));
|
|
8
|
+
const ora_1 = require("../lib/ora");
|
|
9
|
+
const package_1 = require("../lib/package");
|
|
10
|
+
// probeManifest asks the server for a manifest the way a client would, and
|
|
11
|
+
// reports what came back. `appId` is omitted to impersonate a v1 client — the
|
|
12
|
+
// point of this command is that the header may legitimately be absent.
|
|
13
|
+
//
|
|
14
|
+
// A runtime version nothing was ever published against is deliberate: the
|
|
15
|
+
// server answers "no update available" instead of streaming a bundle, which
|
|
16
|
+
// exercises app resolution without downloading anything. Resolution happens
|
|
17
|
+
// before any update lookup, so a 200 proves the app was resolved either way.
|
|
18
|
+
async function probeManifest({ baseUrl, channel, appId, }) {
|
|
19
|
+
const headers = {
|
|
20
|
+
'expo-platform': 'ios',
|
|
21
|
+
'expo-runtime-version': 'eoas-doctor-probe',
|
|
22
|
+
'expo-protocol-version': '1',
|
|
23
|
+
'expo-channel-name': channel,
|
|
24
|
+
};
|
|
25
|
+
if (appId) {
|
|
26
|
+
headers['expo-app-id'] = appId;
|
|
27
|
+
}
|
|
28
|
+
const response = await (0, fetch_1.fetchWithRetries)(`${baseUrl}/manifest`, { method: 'GET', headers });
|
|
29
|
+
return {
|
|
30
|
+
ok: response.ok,
|
|
31
|
+
status: response.status,
|
|
32
|
+
body: (await response.text()).trim(),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
class Doctor extends core_1.Command {
|
|
36
|
+
static args = {};
|
|
37
|
+
static description = "Check that this project's clients can reach the update server — including builds shipped before expo-app-id existed";
|
|
38
|
+
static examples = ['<%= config.bin %> <%= command.id %> --channel=production'];
|
|
39
|
+
static flags = {
|
|
40
|
+
channel: core_1.Flags.string({
|
|
41
|
+
description: 'Channel to probe with (must exist on the server)',
|
|
42
|
+
required: true,
|
|
43
|
+
}),
|
|
44
|
+
url: core_1.Flags.string({
|
|
45
|
+
description: 'Update server URL. Defaults to updates.url from your Expo config',
|
|
46
|
+
required: false,
|
|
47
|
+
}),
|
|
48
|
+
appId: core_1.Flags.string({
|
|
49
|
+
description: 'App id to probe with. Defaults to updates.requestHeaders["expo-app-id"] from your Expo config',
|
|
50
|
+
required: false,
|
|
51
|
+
}),
|
|
52
|
+
};
|
|
53
|
+
async run() {
|
|
54
|
+
const { flags } = await this.parse(Doctor);
|
|
55
|
+
const projectDir = process.cwd();
|
|
56
|
+
if (!(0, package_1.isExpoInstalled)(projectDir)) {
|
|
57
|
+
log_1.default.error('Expo is not installed in this project. Please install Expo first.');
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
const privateConfig = await (0, expoConfig_1.getPrivateExpoConfigAsync)(projectDir, {
|
|
61
|
+
env: process.env,
|
|
62
|
+
});
|
|
63
|
+
const updateUrl = flags.url ?? (0, expoConfig_1.getExpoConfigUpdateUrl)(privateConfig);
|
|
64
|
+
if (!updateUrl) {
|
|
65
|
+
log_1.default.error("Update url is not setup in your config. Please run 'eoas init' to setup the update url, or pass --url");
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
let baseUrl;
|
|
69
|
+
try {
|
|
70
|
+
baseUrl = new URL(updateUrl).origin;
|
|
71
|
+
}
|
|
72
|
+
catch (e) {
|
|
73
|
+
log_1.default.error('Invalid URL', e);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
const appId = flags.appId ?? (0, expoConfig_1.getExpoAppId)(privateConfig);
|
|
77
|
+
log_1.default.log(`🩺 Probing ${baseUrl} on channel '${flags.channel}'`);
|
|
78
|
+
log_1.default.newLine();
|
|
79
|
+
// Probe 1 — the fleet already in users' hands. Its binary predates
|
|
80
|
+
// expo-app-id and cannot be made to send it without a store release, so
|
|
81
|
+
// this is the probe that decides whether upgrading the server strands
|
|
82
|
+
// those installs.
|
|
83
|
+
const legacySpinner = (0, ora_1.ora)('Probing as a v1 client (no expo-app-id header)...').start();
|
|
84
|
+
const legacy = await probeManifest({ baseUrl, channel: flags.channel });
|
|
85
|
+
if (legacy.ok) {
|
|
86
|
+
legacySpinner.succeed('✅ v1 clients are served — the server falls back to its EXPO_APP_ID');
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
legacySpinner.fail(`❌ v1 clients are rejected — HTTP ${legacy.status}: ${legacy.body}`);
|
|
90
|
+
}
|
|
91
|
+
// Probe 2 — what a rebuilt binary sends. Skipped when the project has no
|
|
92
|
+
// app id configured, which is itself the v1 shape and not an error.
|
|
93
|
+
let modern;
|
|
94
|
+
if (appId) {
|
|
95
|
+
const modernSpinner = (0, ora_1.ora)(`Probing as a v2 client (expo-app-id: ${appId})...`).start();
|
|
96
|
+
modern = await probeManifest({ baseUrl, channel: flags.channel, appId });
|
|
97
|
+
if (modern.ok) {
|
|
98
|
+
modernSpinner.succeed('✅ v2 clients are served');
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
modernSpinner.fail(`❌ v2 clients are rejected — HTTP ${modern.status}: ${modern.body}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
log_1.default.warn("⚠️ Skipping the v2 probe: no 'expo-app-id' in updates.requestHeaders. That is the v1 config shape — run 'npx eoas init' to add it.");
|
|
106
|
+
}
|
|
107
|
+
log_1.default.newLine();
|
|
108
|
+
if (!legacy.ok && !appId) {
|
|
109
|
+
log_1.default.error('Your v1 clients are being rejected and this project sends no app id at all.');
|
|
110
|
+
log_1.default.error('Every install in the wild has lost OTA coverage. On the server, unset SKIP_LEGACY_APP_ID_FALLBACK to restore it immediately.');
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
if (!legacy.ok) {
|
|
114
|
+
log_1.default.warn('Clients built before expo-app-id existed are rejected by this server.');
|
|
115
|
+
log_1.default.warn('That is correct only if every build your users run already ships the header. If any predate it, they have silently stopped updating — unset SKIP_LEGACY_APP_ID_FALLBACK on the server.');
|
|
116
|
+
}
|
|
117
|
+
if (modern && !modern.ok) {
|
|
118
|
+
log_1.default.error(`The app id '${appId}' is not served by this server.`);
|
|
119
|
+
log_1.default.error('Check it matches EXPO_APP_ID (single-app) or an app in the dashboard (control plane).');
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
if (legacy.ok && modern?.ok) {
|
|
123
|
+
log_1.default.succeed('Both v1 and v2 clients are served. This server is safe to cut over to.');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
exports.default = Doctor;
|
package/dist/commands/init.js
CHANGED
|
@@ -30,8 +30,8 @@ class Init extends core_1.Command {
|
|
|
30
30
|
const detectedAppId = config.extra?.eas
|
|
31
31
|
?.projectId;
|
|
32
32
|
const { appId } = await (0, prompts_1.promptAsync)({
|
|
33
|
-
message: 'Enter the
|
|
34
|
-
' See https://
|
|
33
|
+
message: 'Enter the project id for this project (sent as the expo-app-id header).\n' +
|
|
34
|
+
' See https://mercure-technologies.gitbook.io/expo-open-ota/stateless-mode/getting-started for details.',
|
|
35
35
|
name: 'appId',
|
|
36
36
|
type: 'text',
|
|
37
37
|
initial: detectedAppId,
|
|
@@ -12,6 +12,8 @@ export default class Publish extends Command {
|
|
|
12
12
|
outputDir: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
13
13
|
packageRunner: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
14
14
|
message: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
15
|
+
dumpSourcemap: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
16
|
+
'rollout-percentage': import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
15
17
|
};
|
|
16
18
|
private sanitizeFlags;
|
|
17
19
|
run(): Promise<void>;
|
package/dist/commands/publish.js
CHANGED
|
@@ -57,7 +57,7 @@ class Publish extends core_1.Command {
|
|
|
57
57
|
default: 'dist',
|
|
58
58
|
}),
|
|
59
59
|
packageRunner: core_1.Flags.string({
|
|
60
|
-
description: 'Package runner to use for spawning Expo CLI commands (e.g. npx, bunx,
|
|
60
|
+
description: 'Package runner to use for spawning Expo CLI commands (e.g. npx, bunx, "pnpm exec"). Can also be set via EOAS_PACKAGE_RUNNER env var. Defaults to npx.',
|
|
61
61
|
required: false,
|
|
62
62
|
}),
|
|
63
63
|
message: core_1.Flags.string({
|
|
@@ -65,6 +65,15 @@ class Publish extends core_1.Command {
|
|
|
65
65
|
description: 'A short message describing the update. Defaults to the latest git commit message.',
|
|
66
66
|
required: false,
|
|
67
67
|
}),
|
|
68
|
+
dumpSourcemap: core_1.Flags.boolean({
|
|
69
|
+
description: 'Emit Hermes source maps alongside the bundle so the published artifact can be symbolicated by tools like Sentry or PostHog.',
|
|
70
|
+
default: false,
|
|
71
|
+
}),
|
|
72
|
+
'rollout-percentage': core_1.Flags.integer({
|
|
73
|
+
min: 1,
|
|
74
|
+
max: 99,
|
|
75
|
+
description: 'Publish this update as a progressive rollout served to N% of devices (1-99). The remaining devices keep receiving the previous update of each branch/runtime version. With --platform all, the rollout applies independently to each platform. Progression (increase, end, revert) is managed from the dashboard.',
|
|
76
|
+
}),
|
|
68
77
|
};
|
|
69
78
|
sanitizeFlags(flags) {
|
|
70
79
|
return {
|
|
@@ -76,16 +85,18 @@ class Publish extends core_1.Command {
|
|
|
76
85
|
packageRunner: (0, packageRunner_1.resolvePackageRunner)(flags.packageRunner, process.cwd()),
|
|
77
86
|
providedDeprecatedChannel: flags.channel,
|
|
78
87
|
message: flags.message,
|
|
88
|
+
dumpSourcemap: flags.dumpSourcemap,
|
|
89
|
+
rolloutPercentage: flags['rollout-percentage'],
|
|
79
90
|
};
|
|
80
91
|
}
|
|
81
92
|
async run() {
|
|
82
|
-
const credentials = (0, auth_1.
|
|
83
|
-
if (!
|
|
84
|
-
log_1.default.error('
|
|
93
|
+
const credentials = (0, auth_1.retrieveCredentials)();
|
|
94
|
+
if (!(0, auth_1.validateCredentials)(credentials)) {
|
|
95
|
+
log_1.default.error('Invalid credentials. Please run `eas login or set EXPO_ACCESS_TOKEN or EOO_TOKEN environment variable`');
|
|
85
96
|
process.exit(1);
|
|
86
97
|
}
|
|
87
98
|
const { flags } = await this.parse(Publish);
|
|
88
|
-
const { platform, nonInteractive, branch, outputDir, packageRunner, providedDeprecatedChannel, disableRepositoryCheck, message, } = this.sanitizeFlags(flags);
|
|
99
|
+
const { platform, nonInteractive, branch, outputDir, packageRunner, providedDeprecatedChannel, disableRepositoryCheck, message, dumpSourcemap, rolloutPercentage, } = this.sanitizeFlags(flags);
|
|
89
100
|
if (!branch) {
|
|
90
101
|
log_1.default.error('Branch name is required');
|
|
91
102
|
process.exit(1);
|
|
@@ -188,7 +199,17 @@ class Publish extends core_1.Command {
|
|
|
188
199
|
const exportSpinner = (0, ora_1.ora)('📦 Exporting project files...').start();
|
|
189
200
|
try {
|
|
190
201
|
const specifiedPlatform = platform === expoConfig_1.RequestedPlatform.All ? [] : ['--platform', platform];
|
|
191
|
-
const
|
|
202
|
+
const sourcemapArgs = dumpSourcemap ? ['--dump-sourcemap'] : [];
|
|
203
|
+
const [runnerCommand, runnerArgs] = (0, packageRunner_1.splitPackageRunner)(packageRunner);
|
|
204
|
+
const { stdout } = await (0, spawn_async_1.default)(runnerCommand, [
|
|
205
|
+
...runnerArgs,
|
|
206
|
+
'expo',
|
|
207
|
+
'export',
|
|
208
|
+
'--output-dir',
|
|
209
|
+
outputDir,
|
|
210
|
+
...sourcemapArgs,
|
|
211
|
+
...specifiedPlatform,
|
|
212
|
+
], {
|
|
192
213
|
cwd: projectDir,
|
|
193
214
|
env: {
|
|
194
215
|
...process.env,
|
|
@@ -238,6 +259,8 @@ class Publish extends core_1.Command {
|
|
|
238
259
|
platform,
|
|
239
260
|
commitHash,
|
|
240
261
|
message: resolvedMessage,
|
|
262
|
+
rolloutPercentage,
|
|
263
|
+
branch,
|
|
241
264
|
})),
|
|
242
265
|
runtimeVersion,
|
|
243
266
|
platform,
|
|
@@ -260,7 +283,7 @@ class Publish extends core_1.Command {
|
|
|
260
283
|
method: 'PUT',
|
|
261
284
|
headers: {
|
|
262
285
|
...formData.getHeaders(),
|
|
263
|
-
...(0, auth_1.
|
|
286
|
+
...(0, auth_1.getAuthHeaders)(credentials),
|
|
264
287
|
},
|
|
265
288
|
body: formData,
|
|
266
289
|
});
|
|
@@ -303,7 +326,7 @@ class Publish extends core_1.Command {
|
|
|
303
326
|
process.exit(1);
|
|
304
327
|
}
|
|
305
328
|
const markAsFinishedSpinner = (0, ora_1.ora)('🔗 Marking the updates as finished...').start();
|
|
306
|
-
const results = await Promise.all(uploadUrls.map(async ({ updateId, platform, runtimeVersion }) => {
|
|
329
|
+
const results = await Promise.all(uploadUrls.map(async ({ updateId, platform, runtimeVersion, rolloutPercentage: echoedRolloutPercentage, }) => {
|
|
307
330
|
const markAsUploadedUrl = new URL(`${serverUrl}/${appId}/markUpdateAsUploaded/${branch}`);
|
|
308
331
|
markAsUploadedUrl.searchParams.set('platform', platform);
|
|
309
332
|
markAsUploadedUrl.searchParams.set('updateId', updateId);
|
|
@@ -311,20 +334,34 @@ class Publish extends core_1.Command {
|
|
|
311
334
|
const response = await (0, fetch_1.fetchWithRetries)(markAsUploadedUrl.toString(), {
|
|
312
335
|
method: 'POST',
|
|
313
336
|
headers: {
|
|
314
|
-
...(0, auth_1.
|
|
337
|
+
...(0, auth_1.getAuthHeaders)(credentials),
|
|
315
338
|
'Content-Type': 'application/json',
|
|
316
339
|
},
|
|
317
340
|
});
|
|
318
341
|
// If success and status code = 200
|
|
319
342
|
if (response.ok) {
|
|
320
343
|
log_1.default.withInfo(`✅ Update ready for ${platform}`);
|
|
344
|
+
// Announce only when the server echoed the percentage back: an old server
|
|
345
|
+
// silently ignores the param and ships the update to 100% of devices.
|
|
346
|
+
if (rolloutPercentage !== undefined && echoedRolloutPercentage !== undefined) {
|
|
347
|
+
log_1.default.withInfo(`Progressive rollout started at ${rolloutPercentage}% for ${platform}. Manage it from the dashboard.`);
|
|
348
|
+
}
|
|
321
349
|
return 'deployed';
|
|
322
350
|
}
|
|
323
351
|
// If response.status === 406 duplicate update
|
|
324
352
|
if (response.status === 406) {
|
|
325
353
|
log_1.default.withInfo(`⚠️ There is no change in the update for ${platform}, ignored...`);
|
|
354
|
+
if (rolloutPercentage !== undefined) {
|
|
355
|
+
log_1.default.withInfo(`No changes detected for ${platform}, no rollout was started.`);
|
|
356
|
+
}
|
|
326
357
|
return 'identical';
|
|
327
358
|
}
|
|
359
|
+
// The partial unique index can activate a rollout on this (branch, rtv) between
|
|
360
|
+
// requestUploadUrl and markUpdateAsUploaded, closing the publish race with a 409.
|
|
361
|
+
if (response.status === 409) {
|
|
362
|
+
log_1.default.error((0, assets_1.activeRolloutConflictMessage)(branch));
|
|
363
|
+
return 'error';
|
|
364
|
+
}
|
|
328
365
|
log_1.default.error('❌ Failed to mark the update as finished for platform', platform);
|
|
329
366
|
log_1.default.newLine();
|
|
330
367
|
log_1.default.error(await response.text());
|
|
@@ -21,7 +21,7 @@ class Publish extends core_1.Command {
|
|
|
21
21
|
}),
|
|
22
22
|
platform: core_1.Flags.string({
|
|
23
23
|
type: 'option',
|
|
24
|
-
options: ['ios', 'android'],
|
|
24
|
+
options: ['ios', 'android', 'all'],
|
|
25
25
|
default: 'all',
|
|
26
26
|
required: true,
|
|
27
27
|
}),
|
|
@@ -33,9 +33,9 @@ class Publish extends core_1.Command {
|
|
|
33
33
|
};
|
|
34
34
|
}
|
|
35
35
|
async run() {
|
|
36
|
-
const credentials = (0, auth_1.
|
|
37
|
-
if (!
|
|
38
|
-
log_1.default.error('
|
|
36
|
+
const credentials = (0, auth_1.retrieveCredentials)();
|
|
37
|
+
if (!(0, auth_1.validateCredentials)(credentials)) {
|
|
38
|
+
log_1.default.error('Invalid credentials. Please run `eas login or set EXPO_ACCESS_TOKEN or EOO_TOKEN environment variable`');
|
|
39
39
|
process.exit(1);
|
|
40
40
|
}
|
|
41
41
|
const { flags } = await this.parse(Publish);
|
|
@@ -78,8 +78,8 @@ class Publish extends core_1.Command {
|
|
|
78
78
|
const runtimeVersionsEndpoint = `${baseUrl}/api/apps/${appId}/branch/${branch}/runtimeVersions`;
|
|
79
79
|
const response = await (0, fetch_1.fetchWithRetries)(runtimeVersionsEndpoint, {
|
|
80
80
|
headers: {
|
|
81
|
-
...(0, auth_1.
|
|
82
|
-
'use-
|
|
81
|
+
...(0, auth_1.getAuthHeaders)(credentials),
|
|
82
|
+
'use-cli-auth': 'true',
|
|
83
83
|
},
|
|
84
84
|
});
|
|
85
85
|
if (!response.ok) {
|
|
@@ -106,8 +106,8 @@ class Publish extends core_1.Command {
|
|
|
106
106
|
const updatesEndpoint = `${baseUrl}/api/apps/${appId}/branch/${branch}/runtimeVersion/${selectedRuntimeVersion.runtimeVersion}/updates`;
|
|
107
107
|
const updatesResponse = await (0, fetch_1.fetchWithRetries)(updatesEndpoint, {
|
|
108
108
|
headers: {
|
|
109
|
-
...(0, auth_1.
|
|
110
|
-
'use-
|
|
109
|
+
...(0, auth_1.getAuthHeaders)(credentials),
|
|
110
|
+
'use-cli-auth': 'true',
|
|
111
111
|
},
|
|
112
112
|
});
|
|
113
113
|
if (!updatesResponse.ok) {
|
|
@@ -115,8 +115,12 @@ class Publish extends core_1.Command {
|
|
|
115
115
|
process.exit(1);
|
|
116
116
|
}
|
|
117
117
|
const updates = (await updatesResponse.json()).filter(u => {
|
|
118
|
-
return u.updateUUID !== 'Rollback to embedded' && u.platform === platform;
|
|
118
|
+
return (u.updateUUID !== 'Rollback to embedded' && (platform === 'all' || u.platform === platform));
|
|
119
119
|
});
|
|
120
|
+
if (updates.length === 0) {
|
|
121
|
+
log_1.default.error(`No republishable updates found for runtime version ${selectedRuntimeVersion.runtimeVersion} on platform ${platform}.`);
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
120
124
|
const selectedUpdated = await (0, prompts_1.promptAsync)({
|
|
121
125
|
type: 'select',
|
|
122
126
|
name: 'update',
|
|
@@ -129,7 +133,7 @@ class Publish extends core_1.Command {
|
|
|
129
133
|
});
|
|
130
134
|
log_1.default.log(`Re-publishing update: ${selectedUpdated.update.updateUUID}`);
|
|
131
135
|
const republishUrl = new URL(`${baseUrl}/${appId}/republish/${branch}`);
|
|
132
|
-
republishUrl.searchParams.set('platform', platform);
|
|
136
|
+
republishUrl.searchParams.set('platform', selectedUpdated.update.platform);
|
|
133
137
|
republishUrl.searchParams.set('runtimeVersion', selectedRuntimeVersion.runtimeVersion);
|
|
134
138
|
republishUrl.searchParams.set('updateId', selectedUpdated.update.updateId);
|
|
135
139
|
republishUrl.searchParams.set('commitHash', selectedUpdated.update.commitHash);
|
|
@@ -137,7 +141,8 @@ class Publish extends core_1.Command {
|
|
|
137
141
|
const republishResponse = await (0, fetch_1.fetchWithRetries)(republishUrl.toString(), {
|
|
138
142
|
method: 'POST',
|
|
139
143
|
headers: {
|
|
140
|
-
...(0, auth_1.
|
|
144
|
+
...(0, auth_1.getAuthHeaders)(credentials),
|
|
145
|
+
'use-cli-auth': 'true',
|
|
141
146
|
'Content-Type': 'application/json',
|
|
142
147
|
},
|
|
143
148
|
});
|
|
@@ -41,9 +41,9 @@ class Publish extends core_1.Command {
|
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
43
|
async run() {
|
|
44
|
-
const credentials = (0, auth_1.
|
|
45
|
-
if (!
|
|
46
|
-
log_1.default.error('
|
|
44
|
+
const credentials = (0, auth_1.retrieveCredentials)();
|
|
45
|
+
if (!(0, auth_1.validateCredentials)(credentials)) {
|
|
46
|
+
log_1.default.error('Invalid credentials. Please run `eas login or set EXPO_ACCESS_TOKEN or EOO_TOKEN environment variable`');
|
|
47
47
|
process.exit(1);
|
|
48
48
|
}
|
|
49
49
|
const { flags } = await this.parse(Publish);
|
|
@@ -141,7 +141,7 @@ class Publish extends core_1.Command {
|
|
|
141
141
|
const response = await (0, fetch_1.fetchWithRetries)(rollbackUrl.toString(), {
|
|
142
142
|
method: 'POST',
|
|
143
143
|
headers: {
|
|
144
|
-
...(0, auth_1.
|
|
144
|
+
...(0, auth_1.getAuthHeaders)(credentials),
|
|
145
145
|
},
|
|
146
146
|
});
|
|
147
147
|
if (!response.ok) {
|
package/dist/lib/assets.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Joi from 'joi';
|
|
2
|
-
import {
|
|
2
|
+
import { Credentials } from './auth';
|
|
3
3
|
import { RequestedPlatform } from './expoConfig';
|
|
4
4
|
export declare const MetadataJoi: Joi.ObjectSchema<any>;
|
|
5
5
|
interface AssetToUpload {
|
|
@@ -13,18 +13,22 @@ export interface RequestUploadUrlItem {
|
|
|
13
13
|
fileName: string;
|
|
14
14
|
filePath: string;
|
|
15
15
|
}
|
|
16
|
-
export declare function
|
|
16
|
+
export declare function activeRolloutConflictMessage(branch: string): string;
|
|
17
|
+
export declare function requestUploadUrls({ body, requestUploadUrl, auth, runtimeVersion, platform, commitHash, message, rolloutPercentage, branch, }: {
|
|
17
18
|
body: {
|
|
18
19
|
fileNames: string[];
|
|
19
20
|
};
|
|
20
21
|
requestUploadUrl: string;
|
|
21
|
-
auth:
|
|
22
|
+
auth: Credentials;
|
|
22
23
|
runtimeVersion: string;
|
|
23
24
|
platform: string;
|
|
24
25
|
commitHash?: string;
|
|
25
26
|
message?: string;
|
|
27
|
+
rolloutPercentage?: number;
|
|
28
|
+
branch: string;
|
|
26
29
|
}): Promise<{
|
|
27
30
|
uploadRequests: RequestUploadUrlItem[];
|
|
28
31
|
updateId: string;
|
|
32
|
+
rolloutPercentage?: number;
|
|
29
33
|
}>;
|
|
30
34
|
export {};
|
package/dist/lib/assets.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.requestUploadUrls = exports.computeFilesRequests = exports.MetadataJoi = void 0;
|
|
3
|
+
exports.requestUploadUrls = exports.activeRolloutConflictMessage = exports.computeFilesRequests = exports.MetadataJoi = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
|
|
6
6
|
const joi_1 = tslib_1.__importDefault(require("joi"));
|
|
@@ -73,11 +73,18 @@ function computeFilesRequests(projectDir, outputDir, requestedPlatform) {
|
|
|
73
73
|
return assets;
|
|
74
74
|
}
|
|
75
75
|
exports.computeFilesRequests = computeFilesRequests;
|
|
76
|
-
|
|
76
|
+
function activeRolloutConflictMessage(branch) {
|
|
77
|
+
return `A progressive rollout is already active for branch "${branch}" on this runtime version. End or revert it from the dashboard before publishing a new update.`;
|
|
78
|
+
}
|
|
79
|
+
exports.activeRolloutConflictMessage = activeRolloutConflictMessage;
|
|
80
|
+
async function requestUploadUrls({ body, requestUploadUrl, auth, runtimeVersion, platform, commitHash, message, rolloutPercentage, branch, }) {
|
|
77
81
|
const uploadUrl = new URL(requestUploadUrl);
|
|
78
82
|
uploadUrl.searchParams.set('runtimeVersion', runtimeVersion);
|
|
79
83
|
uploadUrl.searchParams.set('platform', platform);
|
|
80
84
|
uploadUrl.searchParams.set('commitHash', commitHash ?? '');
|
|
85
|
+
if (rolloutPercentage !== undefined) {
|
|
86
|
+
uploadUrl.searchParams.set('rolloutPercentage', String(rolloutPercentage));
|
|
87
|
+
}
|
|
81
88
|
const requestBody = { ...body };
|
|
82
89
|
if (message) {
|
|
83
90
|
requestBody.message = message;
|
|
@@ -85,15 +92,25 @@ async function requestUploadUrls({ body, requestUploadUrl, auth, runtimeVersion,
|
|
|
85
92
|
const response = await (0, fetch_1.fetchWithRetries)(uploadUrl.toString(), {
|
|
86
93
|
method: 'POST',
|
|
87
94
|
headers: {
|
|
88
|
-
...(0, auth_1.
|
|
95
|
+
...(0, auth_1.getAuthHeaders)(auth),
|
|
89
96
|
'Content-Type': 'application/json',
|
|
90
97
|
},
|
|
91
98
|
body: JSON.stringify(requestBody),
|
|
92
99
|
});
|
|
100
|
+
if (response.status === 409) {
|
|
101
|
+
throw new Error(activeRolloutConflictMessage(branch));
|
|
102
|
+
}
|
|
93
103
|
if (!response.ok) {
|
|
94
104
|
const text = await response.text();
|
|
95
105
|
throw new Error(`Failed to request upload URL: ${text}`);
|
|
96
106
|
}
|
|
97
|
-
|
|
107
|
+
const json = await response.json();
|
|
108
|
+
// An old server silently ignores unknown query params, so a missing echo means
|
|
109
|
+
// the rollout was not applied even though the flag was set. Abort before any
|
|
110
|
+
// file is uploaded: continuing would finalize a full 100% publish.
|
|
111
|
+
if (rolloutPercentage !== undefined && json.rolloutPercentage === undefined) {
|
|
112
|
+
throw new Error('The server ignored --rollout-percentage and would publish to 100% of devices. Update the server to a version that supports progressive rollouts, or publish without --rollout-percentage.');
|
|
113
|
+
}
|
|
114
|
+
return json;
|
|
98
115
|
}
|
|
99
116
|
exports.requestUploadUrls = requestUploadUrls;
|
package/dist/lib/auth.d.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
type ServerImplementation = 'expo' | 'eoo';
|
|
2
|
+
export interface Credentials {
|
|
2
3
|
token?: string;
|
|
3
4
|
sessionSecret?: string;
|
|
4
5
|
}
|
|
5
|
-
export declare function
|
|
6
|
-
export declare function
|
|
6
|
+
export declare function detectServerImplementation(): ServerImplementation;
|
|
7
|
+
export declare function retrieveCredentials(): Credentials;
|
|
8
|
+
export declare function validateCredentials(credentials: Credentials): boolean;
|
|
9
|
+
export declare function retrieveExpoCredentials(): Credentials;
|
|
10
|
+
export declare function getAuthHeaders(credentials: Credentials): Record<string, string>;
|
|
11
|
+
export {};
|
package/dist/lib/auth.js
CHANGED
|
@@ -1,9 +1,29 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.getAuthHeaders = exports.retrieveExpoCredentials = exports.validateCredentials = exports.retrieveCredentials = exports.detectServerImplementation = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const os_1 = require("os");
|
|
6
6
|
const path_1 = tslib_1.__importDefault(require("path"));
|
|
7
|
+
function detectServerImplementation() {
|
|
8
|
+
return process.env.EOO_TOKEN ? 'eoo' : 'expo';
|
|
9
|
+
}
|
|
10
|
+
exports.detectServerImplementation = detectServerImplementation;
|
|
11
|
+
function retrieveCredentials() {
|
|
12
|
+
const serverImplementation = detectServerImplementation();
|
|
13
|
+
if (serverImplementation === 'eoo') {
|
|
14
|
+
return {
|
|
15
|
+
token: process.env.EOO_TOKEN,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
return retrieveExpoCredentials();
|
|
19
|
+
}
|
|
20
|
+
exports.retrieveCredentials = retrieveCredentials;
|
|
21
|
+
function validateCredentials(credentials) {
|
|
22
|
+
if (!credentials)
|
|
23
|
+
return false;
|
|
24
|
+
return !!(credentials.token || credentials.sessionSecret);
|
|
25
|
+
}
|
|
26
|
+
exports.validateCredentials = validateCredentials;
|
|
7
27
|
function dotExpoHomeDirectory() {
|
|
8
28
|
const home = (0, os_1.homedir)();
|
|
9
29
|
if (!home) {
|
|
@@ -41,17 +61,17 @@ function retrieveExpoCredentials() {
|
|
|
41
61
|
return { token, sessionSecret };
|
|
42
62
|
}
|
|
43
63
|
exports.retrieveExpoCredentials = retrieveExpoCredentials;
|
|
44
|
-
function
|
|
64
|
+
function getAuthHeaders(credentials) {
|
|
45
65
|
if (credentials.token) {
|
|
46
66
|
return {
|
|
47
67
|
Authorization: `Bearer ${credentials.token}`,
|
|
48
68
|
};
|
|
49
69
|
}
|
|
50
|
-
if (credentials
|
|
70
|
+
if (credentials?.sessionSecret) {
|
|
51
71
|
return {
|
|
52
|
-
'expo-session': credentials
|
|
72
|
+
'expo-session': credentials?.sessionSecret,
|
|
53
73
|
};
|
|
54
74
|
}
|
|
55
75
|
return {};
|
|
56
76
|
}
|
|
57
|
-
exports.
|
|
77
|
+
exports.getAuthHeaders = getAuthHeaders;
|
package/dist/lib/expoConfig.d.ts
CHANGED
|
@@ -21,6 +21,7 @@ export declare function ensureExpoConfigExists(projectDir: string): void;
|
|
|
21
21
|
export declare function isUsingStaticExpoConfig(projectDir: string): boolean;
|
|
22
22
|
export declare function getPublicExpoConfigAsync(projectDir: string, opts?: ExpoConfigOptions): Promise<PublicExpoConfig>;
|
|
23
23
|
export declare function getExpoConfigUpdateUrl(config: ExpoConfig): string | undefined;
|
|
24
|
+
export declare function getExpoAppId(config: ExpoConfig): string | undefined;
|
|
24
25
|
export declare function requireExpoAppId(config: ExpoConfig): string;
|
|
25
26
|
export declare function createOrModifyExpoConfigAsync(projectDir: string, exp: Partial<ExpoConfig>): Promise<void>;
|
|
26
27
|
export declare function resolveServerUrl(config: ExpoConfig): Promise<string>;
|
package/dist/lib/expoConfig.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.resolveServerUrl = exports.createOrModifyExpoConfigAsync = exports.requireExpoAppId = exports.getExpoConfigUpdateUrl = exports.getPublicExpoConfigAsync = exports.isUsingStaticExpoConfig = exports.ensureExpoConfigExists = exports.getPrivateExpoConfigAsync = exports.RequestedPlatform = void 0;
|
|
3
|
+
exports.resolveServerUrl = exports.createOrModifyExpoConfigAsync = exports.requireExpoAppId = exports.getExpoAppId = exports.getExpoConfigUpdateUrl = exports.getPublicExpoConfigAsync = exports.isUsingStaticExpoConfig = exports.ensureExpoConfigExists = exports.getPrivateExpoConfigAsync = exports.RequestedPlatform = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
// This file is copied from eas-cli[https://github.com/expo/eas-cli] to ensure consistent user experience across the CLI.
|
|
6
6
|
const config_1 = require("@expo/config");
|
|
@@ -29,8 +29,9 @@ async function getExpoConfigInternalAsync(projectDir, opts = {}) {
|
|
|
29
29
|
let exp;
|
|
30
30
|
if ((0, package_1.isExpoInstalled)(projectDir)) {
|
|
31
31
|
const runner = (0, packageRunner_1.resolvePackageRunner)(opts.packageRunner, projectDir);
|
|
32
|
+
const [runnerCommand, runnerArgs] = (0, packageRunner_1.splitPackageRunner)(runner);
|
|
32
33
|
try {
|
|
33
|
-
const { stdout } = await (0, spawn_async_1.default)(
|
|
34
|
+
const { stdout } = await (0, spawn_async_1.default)(runnerCommand, [...runnerArgs, 'expo', 'config', '--json', ...(opts.isPublicConfig ? ['--type', 'public'] : [])], {
|
|
34
35
|
cwd: projectDir,
|
|
35
36
|
env: {
|
|
36
37
|
...process.env,
|
|
@@ -111,13 +112,21 @@ function getExpoConfigUpdateUrl(config) {
|
|
|
111
112
|
return config.updates?.url;
|
|
112
113
|
}
|
|
113
114
|
exports.getExpoConfigUpdateUrl = getExpoConfigUpdateUrl;
|
|
114
|
-
|
|
115
|
-
|
|
115
|
+
// getExpoAppId reads the app id without treating its absence as fatal. A config
|
|
116
|
+
// with no 'expo-app-id' is the shape every v1 project has, so a caller that
|
|
117
|
+
// diagnoses or migrates such a project needs to see the absence rather than be
|
|
118
|
+
// exited on. Commands that cannot proceed without an id use requireExpoAppId.
|
|
119
|
+
function getExpoAppId(config) {
|
|
120
|
+
return config.updates
|
|
116
121
|
?.requestHeaders?.['expo-app-id'];
|
|
122
|
+
}
|
|
123
|
+
exports.getExpoAppId = getExpoAppId;
|
|
124
|
+
function requireExpoAppId(config) {
|
|
125
|
+
const appId = getExpoAppId(config);
|
|
117
126
|
if (!appId) {
|
|
118
127
|
log_1.default.error("Your Expo config is missing the 'expo-app-id' entry in updates.requestHeaders.");
|
|
119
|
-
log_1.default.error("This usually means you're running eoas
|
|
120
|
-
log_1.default.error("Fix: run 'npx eoas init' to migrate, or pin to the previous CLI via 'npx eoas@
|
|
128
|
+
log_1.default.error("This usually means you're running eoas v2+ against a v1-style single-app config or your config is missing the 'expo-app-id' entry.");
|
|
129
|
+
log_1.default.error("Fix: run 'npx eoas init' to migrate, or pin to the previous CLI via 'npx eoas@1 ...'.");
|
|
121
130
|
process.exit(1);
|
|
122
131
|
}
|
|
123
132
|
return appId;
|
|
@@ -7,6 +7,12 @@
|
|
|
7
7
|
* 3. Inferred from packageManager field in package.json
|
|
8
8
|
* 4. Falls back to 'npx'
|
|
9
9
|
*
|
|
10
|
-
* Supported values: npx, bunx,
|
|
10
|
+
* Supported values: npx, bunx, "pnpm exec", or any other package runner
|
|
11
|
+
* binary, optionally followed by subcommand words.
|
|
11
12
|
*/
|
|
12
13
|
export declare function resolvePackageRunner(explicit?: string, projectDir?: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* Splits a resolved package runner into a spawnable command and its leading
|
|
16
|
+
* arguments (e.g. "pnpm exec" -> ['pnpm', ['exec']]).
|
|
17
|
+
*/
|
|
18
|
+
export declare function splitPackageRunner(runner: string): [string, string[]];
|
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.resolvePackageRunner = void 0;
|
|
3
|
+
exports.splitPackageRunner = exports.resolvePackageRunner = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
|
|
6
6
|
const path_1 = tslib_1.__importDefault(require("path"));
|
|
7
7
|
const DEFAULT_PACKAGE_RUNNER = 'npx';
|
|
8
|
-
const VALID_RUNNER_RE = /^[a-zA-Z0-9._-]
|
|
8
|
+
const VALID_RUNNER_RE = /^[a-zA-Z0-9._-]+( [a-zA-Z0-9._-]+)*$/;
|
|
9
9
|
function assertValidRunner(value, source) {
|
|
10
10
|
if (!VALID_RUNNER_RE.test(value)) {
|
|
11
|
-
throw new Error(`Invalid package runner "${value}" (from ${source}). Expected a
|
|
11
|
+
throw new Error(`Invalid package runner "${value}" (from ${source}). Expected a binary name with optional subcommands like npx, bunx or "pnpm exec".`);
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
const PACKAGE_MANAGER_RUNNERS = {
|
|
15
15
|
bun: 'bunx',
|
|
16
|
-
pnpm
|
|
16
|
+
// pnpm removed the `pnpx` binary in v7. `pnpm exec` runs the project-local
|
|
17
|
+
// Expo CLI with the same local-first semantics as npx and bunx.
|
|
18
|
+
pnpm: 'pnpm exec',
|
|
17
19
|
yarn: 'npx',
|
|
18
20
|
npm: 'npx',
|
|
19
21
|
};
|
|
@@ -26,7 +28,8 @@ const PACKAGE_MANAGER_RUNNERS = {
|
|
|
26
28
|
* 3. Inferred from packageManager field in package.json
|
|
27
29
|
* 4. Falls back to 'npx'
|
|
28
30
|
*
|
|
29
|
-
* Supported values: npx, bunx,
|
|
31
|
+
* Supported values: npx, bunx, "pnpm exec", or any other package runner
|
|
32
|
+
* binary, optionally followed by subcommand words.
|
|
30
33
|
*/
|
|
31
34
|
function resolvePackageRunner(explicit, projectDir) {
|
|
32
35
|
if (explicit) {
|
|
@@ -70,3 +73,12 @@ function detectRunnerFromPackageJson(startDir) {
|
|
|
70
73
|
}
|
|
71
74
|
return undefined;
|
|
72
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Splits a resolved package runner into a spawnable command and its leading
|
|
78
|
+
* arguments (e.g. "pnpm exec" -> ['pnpm', ['exec']]).
|
|
79
|
+
*/
|
|
80
|
+
function splitPackageRunner(runner) {
|
|
81
|
+
const [command, ...args] = runner.split(' ');
|
|
82
|
+
return [command, args];
|
|
83
|
+
}
|
|
84
|
+
exports.splitPackageRunner = splitPackageRunner;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eoas",
|
|
3
|
-
"version": "3.0.0-
|
|
3
|
+
"version": "3.0.0-beta.10",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build": "tsc --project tsconfig.json",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"engines": {
|
|
11
11
|
"node": ">=18.0.0"
|
|
12
12
|
},
|
|
13
|
-
"homepage": "https://github.com/
|
|
13
|
+
"homepage": "https://github.com/mercuretechnologies/expo-open-ota/tree/main/eoas",
|
|
14
14
|
"keywords": [
|
|
15
15
|
"expo-open-ota",
|
|
16
16
|
"expo",
|
|
@@ -19,8 +19,11 @@
|
|
|
19
19
|
],
|
|
20
20
|
"author": "Axel Marciano",
|
|
21
21
|
"license": "MIT",
|
|
22
|
-
"description": "A CLI tool to manage publishing and OTA updates for expo-open-OTA self-hosted server. This is not an official tool from Expo but an open-source project (https://github.com/
|
|
23
|
-
"repository":
|
|
22
|
+
"description": "A CLI tool to manage publishing and OTA updates for expo-open-OTA self-hosted server. This is not an official tool from Expo but an open-source project (https://github.com/mercuretechnologies/expo-open-ota)",
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/mercuretechnologies/expo-open-ota.git"
|
|
26
|
+
},
|
|
24
27
|
"dependencies": {
|
|
25
28
|
"@expo/code-signing-certificates": "^0.0.5",
|
|
26
29
|
"@expo/config": "10.0.11",
|
|
@@ -82,7 +85,7 @@
|
|
|
82
85
|
"typescript": "5.3.3"
|
|
83
86
|
},
|
|
84
87
|
"bin": {
|
|
85
|
-
"eoas": "
|
|
88
|
+
"eoas": "bin/run.js"
|
|
86
89
|
},
|
|
87
90
|
"oclif": {
|
|
88
91
|
"bin": "eoas",
|