eoas 3.1.2 → 3.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/doctor.js +3 -12
- package/dist/commands/init.js +2 -2
- package/dist/commands/publish.js +1 -1
- package/dist/commands/republish.js +1 -1
- package/dist/commands/rollback.js +1 -1
- package/dist/commands/server/init.js +56 -9
- package/dist/lib/expoConfig.d.ts +1 -0
- package/dist/lib/expoConfig.js +13 -2
- package/dist/lib/serverConfig/choices.d.ts +2 -0
- package/dist/lib/serverConfig/envCatalog.js +16 -1
- package/dist/lib/utils.js +17 -1
- package/package.json +1 -1
package/dist/commands/doctor.js
CHANGED
|
@@ -60,19 +60,10 @@ class Doctor extends core_1.Command {
|
|
|
60
60
|
const privateConfig = await (0, expoConfig_1.getPrivateExpoConfigAsync)(projectDir, {
|
|
61
61
|
env: process.env,
|
|
62
62
|
});
|
|
63
|
-
const
|
|
64
|
-
|
|
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");
|
|
63
|
+
const baseUrl = await (0, expoConfig_1.resolveServerUrl)(privateConfig, flags.url).catch(e => {
|
|
64
|
+
log_1.default.error(e.message);
|
|
66
65
|
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
|
-
}
|
|
66
|
+
});
|
|
76
67
|
const appId = flags.appId ?? (0, expoConfig_1.getExpoAppId)(privateConfig);
|
|
77
68
|
log_1.default.log(`🩺 Probing ${baseUrl} on channel '${flags.channel}'`);
|
|
78
69
|
log_1.default.newLine();
|
package/dist/commands/init.js
CHANGED
|
@@ -38,7 +38,7 @@ class Init extends core_1.Command {
|
|
|
38
38
|
validate: v => !!v,
|
|
39
39
|
});
|
|
40
40
|
const { updateUrl: promptedUrl } = await (0, prompts_1.promptAsync)({
|
|
41
|
-
message: 'Enter the URL of your update server (ex: https://customota.com)',
|
|
41
|
+
message: 'Enter the URL of your update server (ex: https://customota.com or https://api.example.com/ota)',
|
|
42
42
|
name: 'updateUrl',
|
|
43
43
|
type: 'text',
|
|
44
44
|
initial: ((0, expoConfig_1.getExpoConfigUpdateUrl)(config) || '').replace(/\/manifest$/, ''),
|
|
@@ -46,7 +46,7 @@ class Init extends core_1.Command {
|
|
|
46
46
|
return !!v && (0, utils_1.isValidUpdateUrl)(v);
|
|
47
47
|
},
|
|
48
48
|
});
|
|
49
|
-
let manifestEndpoint = `${promptedUrl}/manifest`;
|
|
49
|
+
let manifestEndpoint = `${promptedUrl.replace(/\/+$/, '')}/manifest`;
|
|
50
50
|
const updateUrl = (0, expoConfig_1.getExpoConfigUpdateUrl)(config);
|
|
51
51
|
if (updateUrl && !updateUrl.includes('expo.dev')) {
|
|
52
52
|
const confirmed = await (0, prompts_1.confirmAsync)({
|
package/dist/commands/publish.js
CHANGED
|
@@ -51,7 +51,7 @@ class Publish extends core_1.Command {
|
|
|
51
51
|
required: true,
|
|
52
52
|
}),
|
|
53
53
|
serverUrl: core_1.Flags.string({
|
|
54
|
-
description: 'URL of the self-hosted update server to publish to. Defaults to
|
|
54
|
+
description: 'URL of the self-hosted update server to publish to. Defaults to updates.url from your Expo config, minus a trailing /manifest',
|
|
55
55
|
required: false,
|
|
56
56
|
}),
|
|
57
57
|
nonInteractive: core_1.Flags.boolean({
|
|
@@ -28,7 +28,7 @@ class Publish extends core_1.Command {
|
|
|
28
28
|
required: true,
|
|
29
29
|
}),
|
|
30
30
|
serverUrl: core_1.Flags.string({
|
|
31
|
-
description: 'URL of the self-hosted update server to republish on. Defaults to
|
|
31
|
+
description: 'URL of the self-hosted update server to republish on. Defaults to updates.url from your Expo config, minus a trailing /manifest',
|
|
32
32
|
required: false,
|
|
33
33
|
}),
|
|
34
34
|
};
|
|
@@ -29,7 +29,7 @@ class Publish extends core_1.Command {
|
|
|
29
29
|
required: true,
|
|
30
30
|
}),
|
|
31
31
|
serverUrl: core_1.Flags.string({
|
|
32
|
-
description: 'URL of the self-hosted update server to roll back on. Defaults to
|
|
32
|
+
description: 'URL of the self-hosted update server to roll back on. Defaults to updates.url from your Expo config, minus a trailing /manifest',
|
|
33
33
|
required: false,
|
|
34
34
|
}),
|
|
35
35
|
nonInteractive: core_1.Flags.boolean({
|
|
@@ -64,6 +64,18 @@ function isHttpUrl(value) {
|
|
|
64
64
|
return false;
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
|
+
/** The path of the URL without trailing slash; '' when at the root or invalid. */
|
|
68
|
+
function pathPrefix(baseUrl) {
|
|
69
|
+
if (!baseUrl) {
|
|
70
|
+
return '';
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
return new URL(baseUrl).pathname.replace(/\/+$/, '');
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return '';
|
|
77
|
+
}
|
|
78
|
+
}
|
|
67
79
|
function shortPasswordHint(missing) {
|
|
68
80
|
const short = missing.map(rule => rule
|
|
69
81
|
.replace('at least 8 characters', '8+ chars')
|
|
@@ -117,8 +129,19 @@ function summaryLines(state) {
|
|
|
117
129
|
const storageChoice = STORAGE_CHOICES.find(choice => choice.value === state.storagePick);
|
|
118
130
|
const set = ACCENT;
|
|
119
131
|
const later = chalk_1.default.dim('to fill later');
|
|
132
|
+
const prefix = pathPrefix(state.baseUrl);
|
|
120
133
|
const rows = [
|
|
121
134
|
['Base URL', state.baseUrl ? set(state.baseUrl) : later],
|
|
135
|
+
...(prefix
|
|
136
|
+
? [
|
|
137
|
+
[
|
|
138
|
+
'Path prefix',
|
|
139
|
+
set(state.serveFromSubPath
|
|
140
|
+
? `${prefix} · routed by the server`
|
|
141
|
+
: `${prefix} · stripped by the proxy`),
|
|
142
|
+
],
|
|
143
|
+
]
|
|
144
|
+
: []),
|
|
122
145
|
['JWT secret', state.jwtSecret ? set('generated') : later],
|
|
123
146
|
['PostgreSQL', state.dbUrl ? set(state.dbUrl) : later],
|
|
124
147
|
['Master key', set('generated, keep a backup')],
|
|
@@ -165,16 +188,39 @@ class ServerInit extends core_1.Command {
|
|
|
165
188
|
{
|
|
166
189
|
id: 'base-url',
|
|
167
190
|
run: async () => {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
191
|
+
while (true) {
|
|
192
|
+
const value = await (0, prompts_1.textStep)('Public HTTPS URL of the server', {
|
|
193
|
+
optional: true,
|
|
194
|
+
initial: state.baseUrl,
|
|
195
|
+
validate: v => isHttpUrl(v) || 'Must be a valid http(s) URL',
|
|
196
|
+
});
|
|
197
|
+
if (value === prompts_1.BACK) {
|
|
198
|
+
return 'back';
|
|
199
|
+
}
|
|
200
|
+
state.baseUrl = value;
|
|
201
|
+
const prefix = pathPrefix(value);
|
|
202
|
+
if (!prefix) {
|
|
203
|
+
state.serveFromSubPath = undefined;
|
|
204
|
+
return 'next';
|
|
205
|
+
}
|
|
206
|
+
const passthrough = await (0, prompts_1.selectStep)(`How does your proxy forward the ${prefix} prefix?`, [
|
|
207
|
+
{
|
|
208
|
+
title: 'It strips it',
|
|
209
|
+
value: false,
|
|
210
|
+
description: `${prefix}/manifest reaches the server as /manifest`,
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
title: 'It passes it through',
|
|
214
|
+
value: true,
|
|
215
|
+
description: `the server routes under ${prefix} itself`,
|
|
216
|
+
},
|
|
217
|
+
], { allowBack: true, initial: state.serveFromSubPath ?? false });
|
|
218
|
+
if (passthrough === prompts_1.BACK) {
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
state.serveFromSubPath = passthrough;
|
|
222
|
+
return 'next';
|
|
175
223
|
}
|
|
176
|
-
state.baseUrl = value;
|
|
177
|
-
return 'next';
|
|
178
224
|
},
|
|
179
225
|
},
|
|
180
226
|
{
|
|
@@ -552,6 +598,7 @@ class ServerInit extends core_1.Command {
|
|
|
552
598
|
}
|
|
553
599
|
const choices = {
|
|
554
600
|
baseUrl: state.baseUrl,
|
|
601
|
+
serveFromSubPath: state.serveFromSubPath,
|
|
555
602
|
jwtSecret: state.jwtSecret,
|
|
556
603
|
dbUrl: state.dbUrl,
|
|
557
604
|
// The wizard always seals the master key in the env; storing it in AWS
|
package/dist/lib/expoConfig.d.ts
CHANGED
|
@@ -30,4 +30,5 @@ export type CommentedValue = {
|
|
|
30
30
|
};
|
|
31
31
|
/** Strips the comment wrappers, for the JSON path and for reading values back. */
|
|
32
32
|
export declare function unwrapValue(value: any): any;
|
|
33
|
+
export declare function publicServerUrl(raw: string): string;
|
|
33
34
|
export declare function resolveServerUrl(config: ExpoConfig, customServerUrl?: string): 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.unwrapValue = exports.createOrModifyExpoConfigAsync = exports.requireExpoAppId = exports.getExpoAppId = exports.getExpoConfigUpdateUrl = exports.getPublicExpoConfigAsync = exports.isUsingStaticExpoConfig = exports.ensureExpoConfigExists = exports.getPrivateExpoConfigAsync = exports.RequestedPlatform = void 0;
|
|
3
|
+
exports.resolveServerUrl = exports.publicServerUrl = exports.unwrapValue = 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");
|
|
@@ -314,6 +314,17 @@ function stringifyWithEnv(obj) {
|
|
|
314
314
|
: value, 2);
|
|
315
315
|
return json.replace(/"__RAW_EXPR_(\d+)__"/g, (_match, index) => rawExpressions[Number(index)]);
|
|
316
316
|
}
|
|
317
|
+
// Strips a trailing /manifest from an Expo updates.url (or --serverUrl) and
|
|
318
|
+
// keeps any path prefix the server is mounted under.
|
|
319
|
+
function publicServerUrl(raw) {
|
|
320
|
+
const parsed = new URL(raw);
|
|
321
|
+
let path = parsed.pathname.replace(/\/+$/, '');
|
|
322
|
+
if (path.endsWith('/manifest')) {
|
|
323
|
+
path = path.slice(0, -'/manifest'.length);
|
|
324
|
+
}
|
|
325
|
+
return `${parsed.origin}${path}`;
|
|
326
|
+
}
|
|
327
|
+
exports.publicServerUrl = publicServerUrl;
|
|
317
328
|
// customServerUrl wins over the Expo config, so a project serving updates and
|
|
318
329
|
// receiving publishes on two different origins can point the CLI at the second
|
|
319
330
|
// one without touching its config.
|
|
@@ -323,7 +334,7 @@ async function resolveServerUrl(config, customServerUrl) {
|
|
|
323
334
|
throw new Error("Update url is not setup in your config. Please run 'eoas init' to setup the update url, or pass --serverUrl");
|
|
324
335
|
}
|
|
325
336
|
try {
|
|
326
|
-
return
|
|
337
|
+
return publicServerUrl(updateUrl);
|
|
327
338
|
}
|
|
328
339
|
catch {
|
|
329
340
|
throw new Error(customServerUrl ? 'Invalid --serverUrl value.' : 'Invalid update URL.');
|
|
@@ -9,6 +9,8 @@ export type Deployment = 'docker' | 'binary' | 'helm';
|
|
|
9
9
|
export type GeoipStrategy = 'proxy-headers' | 'maxmind';
|
|
10
10
|
export type ServerChoices = {
|
|
11
11
|
baseUrl?: string;
|
|
12
|
+
/** The server routes under BASE_URL's path itself (SERVE_FROM_SUB_PATH). */
|
|
13
|
+
serveFromSubPath?: boolean;
|
|
12
14
|
jwtSecret?: string;
|
|
13
15
|
dbUrl?: string;
|
|
14
16
|
masterKeySource: MasterKeySource;
|
|
@@ -19,7 +19,14 @@ exports.ENV_SECTIONS = [
|
|
|
19
19
|
applies: () => true,
|
|
20
20
|
required: true,
|
|
21
21
|
value: c => or(c.baseUrl, '<https://your-ota-domain>'),
|
|
22
|
-
comment: 'Public HTTPS URL of the server
|
|
22
|
+
comment: 'Public HTTPS URL of the server, including a path prefix if the gateway mounts it under one (e.g. https://api.example.com/ota).',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: 'SERVE_FROM_SUB_PATH',
|
|
26
|
+
applies: c => c.serveFromSubPath === true,
|
|
27
|
+
required: true,
|
|
28
|
+
value: () => 'true',
|
|
29
|
+
comment: "Serve every route under the BASE_URL path prefix. Remove when the gateway strips the prefix before forwarding (it only affects routing; links always use BASE_URL's path).",
|
|
23
30
|
},
|
|
24
31
|
{
|
|
25
32
|
name: 'JWT_SECRET',
|
|
@@ -282,6 +289,13 @@ exports.ENV_SECTIONS = [
|
|
|
282
289
|
value: c => or(c.adminPassword, '<strong-password>'),
|
|
283
290
|
comment: 'Policy: 8+ characters, an uppercase, a lowercase, a digit and a special character.',
|
|
284
291
|
},
|
|
292
|
+
{
|
|
293
|
+
name: 'DASHBOARD_ROOT_REDIRECT',
|
|
294
|
+
applies: c => c.dashboard !== false,
|
|
295
|
+
required: false,
|
|
296
|
+
value: () => 'true',
|
|
297
|
+
comment: 'Sends / to the dashboard login. Off by default: the root stays a 404 rather than pointing every visitor at the admin UI.',
|
|
298
|
+
},
|
|
285
299
|
],
|
|
286
300
|
},
|
|
287
301
|
{
|
|
@@ -474,6 +488,7 @@ function inferChoicesFromEnv(env) {
|
|
|
474
488
|
const cacheMode = (env.CACHE_MODE ?? 'local');
|
|
475
489
|
return {
|
|
476
490
|
baseUrl: env.BASE_URL,
|
|
491
|
+
serveFromSubPath: env.SERVE_FROM_SUB_PATH === 'true',
|
|
477
492
|
jwtSecret: env.JWT_SECRET,
|
|
478
493
|
dbUrl: env.DB_URL,
|
|
479
494
|
masterKeySource,
|
package/dist/lib/utils.js
CHANGED
|
@@ -6,7 +6,23 @@ const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
|
|
|
6
6
|
const path_1 = tslib_1.__importDefault(require("path"));
|
|
7
7
|
const log_1 = tslib_1.__importDefault(require("./log"));
|
|
8
8
|
function isValidUpdateUrl(updateUrl) {
|
|
9
|
-
|
|
9
|
+
try {
|
|
10
|
+
const parsed = new URL(updateUrl);
|
|
11
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
if (!parsed.host) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
// A query or fragment would corrupt the `${url}/manifest` concatenation.
|
|
18
|
+
if (parsed.search || parsed.hash) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
return !parsed.pathname.replace(/\/+$/, '').endsWith('/manifest');
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
10
26
|
}
|
|
11
27
|
exports.isValidUpdateUrl = isValidUpdateUrl;
|
|
12
28
|
// Appends pattern to the project .gitignore unless an identical rule is already
|