eoas 3.0.5 → 3.1.0
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 +5 -5
- package/bin/dev.js +3 -0
- package/bin/run.js +3 -0
- package/dist/commands/generate-certs.js +22 -2
- package/dist/commands/init.js +2 -2
- package/dist/commands/publish.js +55 -31
- package/dist/commands/republish.js +87 -27
- package/dist/commands/server/init.d.ts +17 -0
- package/dist/commands/server/init.js +654 -0
- package/dist/commands/server/validate.d.ts +10 -0
- package/dist/commands/server/validate.js +115 -0
- package/dist/lib/assets.d.ts +30 -8
- package/dist/lib/assets.js +158 -4
- package/dist/lib/auth.js +2 -1
- package/dist/lib/log.d.ts +8 -2
- package/dist/lib/log.js +47 -31
- package/dist/lib/ora.d.ts +10 -9
- package/dist/lib/ora.js +49 -88
- package/dist/lib/packageRunner.js +2 -1
- package/dist/lib/prompts.d.ts +32 -0
- package/dist/lib/prompts.js +86 -1
- package/dist/lib/serverConfig/choices.d.ts +62 -0
- package/dist/lib/serverConfig/choices.js +65 -0
- package/dist/lib/serverConfig/envCatalog.d.ts +41 -0
- package/dist/lib/serverConfig/envCatalog.js +582 -0
- package/dist/lib/serverConfig/helmValues.d.ts +22 -0
- package/dist/lib/serverConfig/helmValues.js +281 -0
- package/dist/lib/serverConfig/passwordPolicy.d.ts +3 -0
- package/dist/lib/serverConfig/passwordPolicy.js +36 -0
- package/dist/lib/serverUpdates.d.ts +45 -0
- package/dist/lib/serverUpdates.js +108 -0
- package/dist/lib/utils.d.ts +1 -0
- package/dist/lib/utils.js +18 -14
- package/package.json +8 -6
package/README.md
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
# EOAS
|
|
1
|
+
# EOAS
|
|
2
2
|
|
|
3
|
-
EOAS
|
|
3
|
+
EOAS is a powerful helper package designed to simplify the setup and update publication process for the [xprem](https://github.com/mercuretechnologies/xprem) 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://mercure-technologies.gitbook.io/
|
|
8
|
+
[EOAS Official Documentation](https://mercure-technologies.gitbook.io/xprem/eoas/overview)
|
|
9
9
|
|
|
10
10
|
## Learn More
|
|
11
|
-
For detailed information and to explore the core functionalities of
|
|
12
|
-
[
|
|
11
|
+
For detailed information and to explore the core functionalities of xprem, visit the main repository:
|
|
12
|
+
[xprem on GitHub](https://github.com/mercuretechnologies/xprem)
|
|
13
13
|
|
|
14
14
|
---
|
|
15
15
|
|
package/bin/dev.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
// Silence DEP0040 (punycode) emitted by node-fetch@2 -> whatwg-url@5 on Node 21+.
|
|
3
|
+
process.noDeprecation = true;
|
|
4
|
+
|
|
2
5
|
// eslint-disable-next-line node/shebang, unicorn/prefer-top-level-await
|
|
3
6
|
(async () => {
|
|
4
7
|
const oclif = await import('@oclif/core');
|
package/bin/run.js
CHANGED
|
@@ -8,6 +8,9 @@ const path_1 = tslib_1.__importDefault(require("path"));
|
|
|
8
8
|
const log_1 = tslib_1.__importDefault(require("../lib/log"));
|
|
9
9
|
const prompts_1 = require("../lib/prompts");
|
|
10
10
|
const utils_1 = require("../lib/utils");
|
|
11
|
+
// Owner read/write only: the private key signs every update served by the OTA
|
|
12
|
+
// server.
|
|
13
|
+
const PRIVATE_KEY_MODE = 0o600;
|
|
11
14
|
class GenerateCerts extends core_1.Command {
|
|
12
15
|
static args = {};
|
|
13
16
|
static description = 'Generate private & public certificates for code signing';
|
|
@@ -46,6 +49,20 @@ class GenerateCerts extends core_1.Command {
|
|
|
46
49
|
}
|
|
47
50
|
},
|
|
48
51
|
});
|
|
52
|
+
const keyOutput = path_1.default.resolve(process.cwd(), keyOutputDir);
|
|
53
|
+
const privateKeyPath = path_1.default.join(keyOutput, 'private-key.pem');
|
|
54
|
+
if ((0, fs_extra_1.existsSync)(privateKeyPath)) {
|
|
55
|
+
const overwrite = await (0, prompts_1.confirmAsync)({
|
|
56
|
+
message: `${privateKeyPath} already exists. Overwrite it? Updates signed with the current key will no longer be accepted by apps embedding the matching certificate.`,
|
|
57
|
+
name: 'overwritePrivateKey',
|
|
58
|
+
type: 'confirm',
|
|
59
|
+
initial: false,
|
|
60
|
+
});
|
|
61
|
+
if (!overwrite) {
|
|
62
|
+
log_1.default.warn('Aborted: no certificate or key was written.');
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
49
66
|
const { certificateCommonName } = await (0, prompts_1.promptAsync)({
|
|
50
67
|
message: 'Please enter your Organization name',
|
|
51
68
|
name: 'certificateCommonName',
|
|
@@ -66,7 +83,6 @@ class GenerateCerts extends core_1.Command {
|
|
|
66
83
|
});
|
|
67
84
|
const validityDurationYears = Math.floor(Number(certificateValidityDurationYears));
|
|
68
85
|
const certificateOutput = path_1.default.resolve(process.cwd(), certificateOutputDir);
|
|
69
|
-
const keyOutput = path_1.default.resolve(process.cwd(), keyOutputDir);
|
|
70
86
|
const validityNotBefore = new Date();
|
|
71
87
|
const validityNotAfter = new Date();
|
|
72
88
|
validityNotAfter.setFullYear(validityNotAfter.getFullYear() + validityDurationYears);
|
|
@@ -82,9 +98,13 @@ class GenerateCerts extends core_1.Command {
|
|
|
82
98
|
// Before the key touches the disk, so there is no window where it exists
|
|
83
99
|
// uncovered by the ignore rule.
|
|
84
100
|
(0, utils_1.ensurePrivateKeyIgnored)(process.cwd());
|
|
101
|
+
// Removed first: writeFile only applies the mode when it creates the file,
|
|
102
|
+
// so overwriting an existing key would keep its (possibly world readable)
|
|
103
|
+
// permissions.
|
|
104
|
+
await (0, fs_extra_1.remove)(privateKeyPath);
|
|
85
105
|
await Promise.all([
|
|
86
106
|
(0, fs_extra_1.writeFile)(path_1.default.join(keyOutput, 'public-key.pem'), keyPairPEM.publicKeyPEM),
|
|
87
|
-
(0, fs_extra_1.writeFile)(
|
|
107
|
+
(0, fs_extra_1.writeFile)(privateKeyPath, keyPairPEM.privateKeyPEM, { mode: PRIVATE_KEY_MODE }),
|
|
88
108
|
(0, fs_extra_1.writeFile)(path_1.default.join(certificateOutput, 'certificate.pem'), certificatePEM),
|
|
89
109
|
]);
|
|
90
110
|
log_1.default.succeed(`Generated public and private keys output in ${keyOutputDir}. Please follow the documentation to securely store them and do not commit them to your repository.`);
|
package/dist/commands/init.js
CHANGED
|
@@ -12,7 +12,7 @@ const prompts_1 = require("../lib/prompts");
|
|
|
12
12
|
const utils_1 = require("../lib/utils");
|
|
13
13
|
class Init extends core_1.Command {
|
|
14
14
|
static args = {};
|
|
15
|
-
static description = 'Configure your existing expo project with
|
|
15
|
+
static description = 'Configure your existing expo project with xprem';
|
|
16
16
|
static examples = ['<%= config.bin %> <%= command.id %>'];
|
|
17
17
|
static flags = {};
|
|
18
18
|
async run() {
|
|
@@ -31,7 +31,7 @@ class Init extends core_1.Command {
|
|
|
31
31
|
?.projectId;
|
|
32
32
|
const { appId } = await (0, prompts_1.promptAsync)({
|
|
33
33
|
message: 'Enter the project id for this project (sent as the expo-app-id header).\n' +
|
|
34
|
-
' See https://mercure-technologies.gitbook.io/
|
|
34
|
+
' See https://mercure-technologies.gitbook.io/xprem/stateless-mode/getting-started for details.',
|
|
35
35
|
name: 'appId',
|
|
36
36
|
type: 'text',
|
|
37
37
|
initial: detectedAppId,
|
package/dist/commands/publish.js
CHANGED
|
@@ -4,6 +4,7 @@ const tslib_1 = require("tslib");
|
|
|
4
4
|
const eas_build_job_1 = require("@expo/eas-build-job");
|
|
5
5
|
const spawn_async_1 = tslib_1.__importDefault(require("@expo/spawn-async"));
|
|
6
6
|
const core_1 = require("@oclif/core");
|
|
7
|
+
const crypto_1 = require("crypto");
|
|
7
8
|
const form_data_1 = tslib_1.__importDefault(require("form-data"));
|
|
8
9
|
const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
|
|
9
10
|
const mime_1 = tslib_1.__importDefault(require("mime"));
|
|
@@ -243,6 +244,9 @@ class Publish extends core_1.Command {
|
|
|
243
244
|
process.exit(1);
|
|
244
245
|
}
|
|
245
246
|
let uploadUrls = [];
|
|
247
|
+
// One group id for the whole run: every platform update of this publish
|
|
248
|
+
// shares it, so control plane servers list them as a single publish.
|
|
249
|
+
const publishGroupId = (0, crypto_1.randomUUID)();
|
|
246
250
|
try {
|
|
247
251
|
uploadUrls = await Promise.all(runtimeVersions.map(async ({ runtimeVersion, platform }) => {
|
|
248
252
|
if (!runtimeVersion) {
|
|
@@ -260,50 +264,56 @@ class Publish extends core_1.Command {
|
|
|
260
264
|
commitHash,
|
|
261
265
|
message: resolvedMessage,
|
|
262
266
|
rolloutPercentage,
|
|
267
|
+
publishGroup: publishGroupId,
|
|
263
268
|
branch,
|
|
264
269
|
})),
|
|
265
270
|
runtimeVersion,
|
|
266
271
|
platform,
|
|
267
272
|
};
|
|
268
273
|
}));
|
|
269
|
-
|
|
270
|
-
|
|
274
|
+
// Every path and URL the server handed back is checked here, before a
|
|
275
|
+
// single file is opened. A server that forges a filePath would otherwise
|
|
276
|
+
// make the CLI read arbitrary files off this machine and PUT them wherever
|
|
277
|
+
// it likes. Validated per response: each platform gets its own upload URLs
|
|
278
|
+
// for the same files.
|
|
279
|
+
const resolvedUploads = (await Promise.all(uploadUrls.map(({ uploadRequests }) => (0, assets_1.resolveUploadRequests)({
|
|
280
|
+
uploadRequests,
|
|
281
|
+
exportDir: path_1.default.join(projectDir, outputDir),
|
|
282
|
+
manifest: files,
|
|
283
|
+
})))).flat();
|
|
284
|
+
await Promise.all(resolvedUploads.map(async ({ item: itm, absolutePath, manifestEntry }) => {
|
|
271
285
|
const isLocalBucketFileUpload = itm.requestUploadUrl.startsWith(`${serverUrl}/${appId}/uploadLocalFile`);
|
|
272
|
-
const formData = new form_data_1.default();
|
|
273
|
-
let file;
|
|
274
|
-
try {
|
|
275
|
-
file = fs_extra_1.default.createReadStream(path_1.default.join(projectDir, outputDir, itm.filePath));
|
|
276
|
-
}
|
|
277
|
-
catch {
|
|
278
|
-
throw new Error(`Failed to read file ${itm.filePath}`);
|
|
279
|
-
}
|
|
280
|
-
formData.append(itm.fileName, file);
|
|
281
286
|
if (isLocalBucketFileUpload) {
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
287
|
+
const formData = new form_data_1.default();
|
|
288
|
+
const file = fs_extra_1.default.createReadStream(absolutePath);
|
|
289
|
+
formData.append(itm.fileName, file);
|
|
290
|
+
try {
|
|
291
|
+
const response = await (0, fetch_1.fetchWithRetries)(itm.requestUploadUrl, {
|
|
292
|
+
method: 'PUT',
|
|
293
|
+
headers: {
|
|
294
|
+
...formData.getHeaders(),
|
|
295
|
+
...(0, auth_1.getAuthHeaders)(credentials),
|
|
296
|
+
},
|
|
297
|
+
body: formData,
|
|
298
|
+
// The URL was validated as a string; following a redirect would
|
|
299
|
+
// send these bytes to an origin nothing ever checked.
|
|
300
|
+
redirect: 'error',
|
|
301
|
+
});
|
|
302
|
+
if (!response.ok) {
|
|
303
|
+
log_1.default.error('Failed to upload file', await response.text());
|
|
304
|
+
throw new Error('Failed to upload file');
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
finally {
|
|
308
|
+
file.close();
|
|
293
309
|
}
|
|
294
|
-
file.close();
|
|
295
310
|
return;
|
|
296
311
|
}
|
|
297
|
-
|
|
298
|
-
if (!findFile) {
|
|
299
|
-
log_1.default.error(`File ${itm.filePath} not found`);
|
|
300
|
-
throw new Error(`File ${itm.filePath} not found`);
|
|
301
|
-
}
|
|
302
|
-
let contentType = mime_1.default.getType(findFile.ext);
|
|
312
|
+
let contentType = mime_1.default.getType(manifestEntry.ext);
|
|
303
313
|
if (!contentType) {
|
|
304
314
|
contentType = 'application/octet-stream';
|
|
305
315
|
}
|
|
306
|
-
const buffer = await fs_extra_1.default.readFile(
|
|
316
|
+
const buffer = await fs_extra_1.default.readFile(absolutePath);
|
|
307
317
|
const response = await (0, fetch_1.fetchWithRetries)(itm.requestUploadUrl, {
|
|
308
318
|
method: 'PUT',
|
|
309
319
|
headers: {
|
|
@@ -312,12 +322,17 @@ class Publish extends core_1.Command {
|
|
|
312
322
|
...(itm.headers ?? {}),
|
|
313
323
|
},
|
|
314
324
|
body: buffer,
|
|
325
|
+
// Only the URL string was validated. node-fetch follows up to 20
|
|
326
|
+
// redirects by default with no protocol or host check, so a server
|
|
327
|
+
// handing back a valid https URL that 302s to http://internal-host
|
|
328
|
+
// would exfiltrate the bundle in cleartext to an origin of its
|
|
329
|
+
// choosing. Nothing legitimate redirects an upload PUT.
|
|
330
|
+
redirect: 'error',
|
|
315
331
|
});
|
|
316
332
|
if (!response.ok) {
|
|
317
333
|
log_1.default.error('❌ File upload failed', await response.text());
|
|
318
334
|
process.exit(1);
|
|
319
335
|
}
|
|
320
|
-
file.close();
|
|
321
336
|
}));
|
|
322
337
|
uploadFilesSpinner.succeed('✅ Files uploaded successfully');
|
|
323
338
|
}
|
|
@@ -385,6 +400,15 @@ class Publish extends core_1.Command {
|
|
|
385
400
|
if (hasSuccess) {
|
|
386
401
|
log_1.default.withInfo(`🌿 Branch: \`${branch}\``);
|
|
387
402
|
log_1.default.withInfo(`⏳ Deployed at: \`${new Date().toUTCString()}\`\n`);
|
|
403
|
+
const groupAcknowledged = uploadUrls.every(u => u.publishGroup === publishGroupId);
|
|
404
|
+
if (groupAcknowledged) {
|
|
405
|
+
log_1.default.withInfo(`📦 Publish group: \`${publishGroupId}\``);
|
|
406
|
+
}
|
|
407
|
+
else if (uploadUrls.length > 1) {
|
|
408
|
+
// Only worth a note when several platforms were published: a single
|
|
409
|
+
// update has nothing to group anyway.
|
|
410
|
+
log_1.default.withInfo('ℹ️ Platform updates were published without grouping (publish groups require a server in control plane mode).');
|
|
411
|
+
}
|
|
388
412
|
log_1.default.withInfo('🔥 Your users will receive the latest update automatically!');
|
|
389
413
|
}
|
|
390
414
|
}
|
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const tslib_1 = require("tslib");
|
|
4
4
|
const core_1 = require("@oclif/core");
|
|
5
|
-
const ora_1 = tslib_1.__importDefault(require("ora"));
|
|
6
5
|
const auth_1 = require("../lib/auth");
|
|
7
6
|
const expoConfig_1 = require("../lib/expoConfig");
|
|
8
7
|
const fetch_1 = require("../lib/fetch");
|
|
9
8
|
const log_1 = tslib_1.__importDefault(require("../lib/log"));
|
|
9
|
+
const ora_1 = require("../lib/ora");
|
|
10
10
|
const package_1 = require("../lib/package");
|
|
11
11
|
const prompts_1 = require("../lib/prompts");
|
|
12
|
+
const serverUpdates_1 = require("../lib/serverUpdates");
|
|
12
13
|
const vcs_1 = require("../lib/vcs");
|
|
13
14
|
class Publish extends core_1.Command {
|
|
14
15
|
static args = {};
|
|
@@ -50,7 +51,6 @@ class Publish extends core_1.Command {
|
|
|
50
51
|
}
|
|
51
52
|
const vcsClient = (0, vcs_1.resolveVcsClient)(true);
|
|
52
53
|
await vcsClient.ensureRepoExistsAsync();
|
|
53
|
-
// const commitHash = await vcsClient.getCommitHashAsync();
|
|
54
54
|
const projectDir = process.cwd();
|
|
55
55
|
const hasExpo = (0, package_1.isExpoInstalled)(projectDir);
|
|
56
56
|
if (!hasExpo) {
|
|
@@ -75,24 +75,19 @@ class Publish extends core_1.Command {
|
|
|
75
75
|
log_1.default.error('Invalid URL', e);
|
|
76
76
|
process.exit(1);
|
|
77
77
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
});
|
|
85
|
-
if (!response.ok) {
|
|
86
|
-
log_1.default.error(`Failed to fetch runtime versions: ${await response.text()}`);
|
|
78
|
+
let runtimeVersions;
|
|
79
|
+
try {
|
|
80
|
+
runtimeVersions = await (0, serverUpdates_1.fetchRuntimeVersions)({ baseUrl, appId, branch, credentials });
|
|
81
|
+
}
|
|
82
|
+
catch (e) {
|
|
83
|
+
log_1.default.error(e instanceof Error ? e.message : e);
|
|
87
84
|
process.exit(1);
|
|
88
85
|
}
|
|
89
|
-
const runtimeVersions = (await response.json());
|
|
90
86
|
const filteredRuntimeVersions = runtimeVersions.filter(runtimeVersion => runtimeVersion.numberOfUpdates > 1);
|
|
91
87
|
if (filteredRuntimeVersions.length === 0) {
|
|
92
88
|
log_1.default.error('No runtime versions found');
|
|
93
89
|
process.exit(1);
|
|
94
90
|
}
|
|
95
|
-
// Ask the user to select a runtime version
|
|
96
91
|
const selectedRuntimeVersion = await (0, prompts_1.promptAsync)({
|
|
97
92
|
type: 'select',
|
|
98
93
|
name: 'runtimeVersion',
|
|
@@ -103,21 +98,86 @@ class Publish extends core_1.Command {
|
|
|
103
98
|
})),
|
|
104
99
|
});
|
|
105
100
|
log_1.default.log(`Selected runtime version: ${selectedRuntimeVersion.runtimeVersion}`);
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
101
|
+
let allUpdates;
|
|
102
|
+
try {
|
|
103
|
+
allUpdates = await (0, serverUpdates_1.fetchUpdates)({
|
|
104
|
+
baseUrl,
|
|
105
|
+
appId,
|
|
106
|
+
branch,
|
|
107
|
+
runtimeVersion: selectedRuntimeVersion.runtimeVersion,
|
|
108
|
+
credentials,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
catch (e) {
|
|
112
|
+
log_1.default.error(e instanceof Error ? e.message : e);
|
|
115
113
|
process.exit(1);
|
|
116
114
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
});
|
|
115
|
+
// Rollback markers have no files to republish.
|
|
116
|
+
const updates = allUpdates.filter(u => u.updateUUID !== 'Rollback to embedded');
|
|
120
117
|
if (updates.length === 0) {
|
|
118
|
+
log_1.default.error(`No republishable updates found for runtime version ${selectedRuntimeVersion.runtimeVersion}.`);
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
const { groups } = (0, serverUpdates_1.groupPublishedUpdates)(updates);
|
|
122
|
+
// Offer the group mode only when there is something to group and the user
|
|
123
|
+
// did not already narrow the run to one platform.
|
|
124
|
+
let mode = 'single';
|
|
125
|
+
if (platform === 'all' && groups.length > 0) {
|
|
126
|
+
const selectedMode = await (0, prompts_1.promptAsync)({
|
|
127
|
+
type: 'select',
|
|
128
|
+
name: 'mode',
|
|
129
|
+
message: 'What do you want to republish?',
|
|
130
|
+
choices: [
|
|
131
|
+
{
|
|
132
|
+
title: 'A full publish (all its platforms together)',
|
|
133
|
+
description: 'Only for servers in control plane mode',
|
|
134
|
+
value: 'group',
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
title: 'A single platform update',
|
|
138
|
+
description: 'Pick one iOS or Android update',
|
|
139
|
+
value: 'single',
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
});
|
|
143
|
+
mode = selectedMode.mode;
|
|
144
|
+
}
|
|
145
|
+
if (mode === 'group') {
|
|
146
|
+
const selectedGroup = await (0, prompts_1.promptAsync)({
|
|
147
|
+
type: 'select',
|
|
148
|
+
name: 'group',
|
|
149
|
+
message: 'Select a publish to republish',
|
|
150
|
+
choices: groups.map(group => ({
|
|
151
|
+
...(0, serverUpdates_1.describePublishGroup)(group),
|
|
152
|
+
value: group,
|
|
153
|
+
})),
|
|
154
|
+
});
|
|
155
|
+
const group = selectedGroup.group;
|
|
156
|
+
const republishUrl = new URL(`${baseUrl}/${appId}/republish/${branch}`);
|
|
157
|
+
republishUrl.searchParams.set('runtimeVersion', selectedRuntimeVersion.runtimeVersion);
|
|
158
|
+
republishUrl.searchParams.set('publishGroup', group.publishGroup);
|
|
159
|
+
const republishSpinner = (0, ora_1.ora)(`🔄 Republishing ${group.platforms.join(' + ')} updates...`).start();
|
|
160
|
+
const response = await (0, fetch_1.fetchWithRetries)(republishUrl.toString(), {
|
|
161
|
+
method: 'POST',
|
|
162
|
+
headers: {
|
|
163
|
+
...(0, auth_1.getAuthHeaders)(credentials),
|
|
164
|
+
'use-cli-auth': 'true',
|
|
165
|
+
'Content-Type': 'application/json',
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
if (!response.ok) {
|
|
169
|
+
republishSpinner.fail('❌ Republish failed');
|
|
170
|
+
log_1.default.error(`Failed to republish publish group: ${await response.text()}`);
|
|
171
|
+
process.exit(1);
|
|
172
|
+
}
|
|
173
|
+
const result = (await response.json());
|
|
174
|
+
republishSpinner.succeed(result.publishGroup
|
|
175
|
+
? `✅ Republished ${group.platforms.join(' + ')} as publish group ${result.publishGroup}`
|
|
176
|
+
: `✅ Republished ${group.platforms.join(' + ')}`);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const platformUpdates = updates.filter(u => platform === 'all' || u.platform === platform);
|
|
180
|
+
if (platformUpdates.length === 0) {
|
|
121
181
|
log_1.default.error(`No republishable updates found for runtime version ${selectedRuntimeVersion.runtimeVersion} on platform ${platform}.`);
|
|
122
182
|
process.exit(1);
|
|
123
183
|
}
|
|
@@ -125,7 +185,7 @@ class Publish extends core_1.Command {
|
|
|
125
185
|
type: 'select',
|
|
126
186
|
name: 'update',
|
|
127
187
|
message: 'Select an update to republish',
|
|
128
|
-
choices:
|
|
188
|
+
choices: platformUpdates.map(update => ({
|
|
129
189
|
title: update.updateUUID,
|
|
130
190
|
value: update,
|
|
131
191
|
description: `Created at: ${update.createdAt}, Platform: ${update.platform}, Commit hash: ${update.commitHash}`,
|
|
@@ -137,7 +197,7 @@ class Publish extends core_1.Command {
|
|
|
137
197
|
republishUrl.searchParams.set('runtimeVersion', selectedRuntimeVersion.runtimeVersion);
|
|
138
198
|
republishUrl.searchParams.set('updateId', selectedUpdated.update.updateId);
|
|
139
199
|
republishUrl.searchParams.set('commitHash', selectedUpdated.update.commitHash);
|
|
140
|
-
const republishSpinner = (0, ora_1.
|
|
200
|
+
const republishSpinner = (0, ora_1.ora)('🔄 Republishing update...').start();
|
|
141
201
|
const republishResponse = await (0, fetch_1.fetchWithRetries)(republishUrl.toString(), {
|
|
142
202
|
method: 'POST',
|
|
143
203
|
headers: {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import { type Deployment } from '../../lib/serverConfig/choices';
|
|
3
|
+
/** A master key found in the file the wizard is about to replace. `unreadable`
|
|
4
|
+
* distinguishes "the file has no key" from "the file could not be parsed". */
|
|
5
|
+
type ExistingMasterKey = {
|
|
6
|
+
key?: string;
|
|
7
|
+
unreadable: boolean;
|
|
8
|
+
};
|
|
9
|
+
export declare function readExistingMasterKey(deployment: Deployment): Promise<ExistingMasterKey>;
|
|
10
|
+
export default class ServerInit extends Command {
|
|
11
|
+
static args: {};
|
|
12
|
+
static description: string;
|
|
13
|
+
static examples: string[];
|
|
14
|
+
static flags: {};
|
|
15
|
+
run(): Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
export {};
|