eoas 2.0.0 → 2.0.1

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.
@@ -17,7 +17,7 @@ class GenerateCerts extends core_1.Command {
17
17
  message: 'In which directory would you like to store your code signing certificate (used by your expo app)?',
18
18
  name: 'certificateOutputDir',
19
19
  type: 'text',
20
- initial: './keysStore',
20
+ initial: './certs',
21
21
  validate: v => {
22
22
  try {
23
23
  // eslint-disable-next-line
@@ -30,10 +30,10 @@ class GenerateCerts extends core_1.Command {
30
30
  },
31
31
  });
32
32
  const { keyOutputDir } = await (0, prompts_1.promptAsync)({
33
- message: 'In which directory would you like to store your key pair (used by your OTA Server) ?. ⚠️ Those keysStore are sensitive and should be kept private.',
33
+ message: 'In which directory would you like to store your key pair (used by your OTA Server) ?. ⚠️ Those certss are sensitive and should be kept private.',
34
34
  name: 'keyOutputDir',
35
35
  type: 'text',
36
- initial: './keysStore',
36
+ initial: './certs',
37
37
  validate: v => {
38
38
  try {
39
39
  // eslint-disable-next-line
@@ -49,19 +49,19 @@ class Init extends core_1.Command {
49
49
  }
50
50
  }
51
51
  const confirmed = await (0, prompts_1.confirmAsync)({
52
- message: 'Do you have already generated your certificates and keysStore for code signing?',
52
+ message: 'Do you have already generated your certificates for code signing?',
53
53
  name: 'certificates',
54
54
  type: 'confirm',
55
55
  });
56
56
  if (!confirmed) {
57
- log_1.default.fail('You need to generate your certificates first by using npx eoas generate-keysStore');
57
+ log_1.default.fail('You need to generate your certificates first by using npx eoas generate-certs');
58
58
  return;
59
59
  }
60
60
  const { codeSigningCertificatePath } = await (0, prompts_1.promptAsync)({
61
- message: 'Enter the path to your code signing certificate (ex: ./keysStore/certificate.pem)',
61
+ message: 'Enter the path to your code signing certificate (ex: ./certs/certificate.pem)',
62
62
  name: 'codeSigningCertificatePath',
63
63
  type: 'text',
64
- initial: './keysStore/certificate.pem',
64
+ initial: './certs/certificate.pem',
65
65
  validate: v => {
66
66
  try {
67
67
  const fullPath = path_1.default.resolve(projectDir, v);
@@ -1,9 +1,5 @@
1
1
  import { Command } from '@oclif/core';
2
- import { Config } from '@oclif/core/lib/config';
3
- import { Client } from '../lib/vcs/vcs';
4
2
  export default class Publish extends Command {
5
- vcsClient: Client;
6
- constructor(argv: string[], config: Config);
7
3
  static args: {};
8
4
  static description: string;
9
5
  static examples: string[];
@@ -21,11 +21,6 @@ const runtimeVersion_1 = require("../lib/runtimeVersion");
21
21
  const vcs_1 = require("../lib/vcs");
22
22
  const workflow_1 = require("../lib/workflow");
23
23
  class Publish extends core_1.Command {
24
- vcsClient;
25
- constructor(argv, config) {
26
- super(argv, config);
27
- this.vcsClient = (0, vcs_1.resolveVcsClient)(false);
28
- }
29
24
  static args = {};
30
25
  static description = 'Publish a new update to the self-hosted update server';
31
26
  static examples = ['<%= config.bin %> <%= command.id %>'];
@@ -61,25 +56,26 @@ class Publish extends core_1.Command {
61
56
  const credentials = (0, auth_1.retrieveExpoCredentials)();
62
57
  if (!credentials.token && !credentials.sessionSecret) {
63
58
  log_1.default.error('You are not logged to eas, please run `eas login`');
64
- return;
59
+ process.exit(1);
65
60
  }
66
61
  const { flags } = await this.parse(Publish);
67
62
  const { platform, nonInteractive, branch, channel } = this.sanitizeFlags(flags);
68
63
  if (!branch) {
69
64
  log_1.default.error('Branch name is required');
70
- return;
65
+ process.exit(1);
71
66
  }
72
67
  if (!channel) {
73
68
  log_1.default.error('Channel name is required');
74
- return;
69
+ process.exit(1);
75
70
  }
76
- await this.vcsClient.ensureRepoExistsAsync();
77
- await (0, repo_1.ensureRepoIsCleanAsync)(this.vcsClient, nonInteractive);
71
+ const vcsClient = (0, vcs_1.resolveVcsClient)(true);
72
+ await vcsClient.ensureRepoExistsAsync();
73
+ await (0, repo_1.ensureRepoIsCleanAsync)(vcsClient, nonInteractive);
78
74
  const projectDir = process.cwd();
79
75
  const hasExpo = (0, package_1.isExpoInstalled)(projectDir);
80
76
  if (!hasExpo) {
81
77
  log_1.default.error('Expo is not installed in this project. Please install Expo first.');
82
- return;
78
+ process.exit(1);
83
79
  }
84
80
  const privateConfig = await (0, expoConfig_1.getPrivateExpoConfigAsync)(projectDir, {
85
81
  env: {
@@ -89,7 +85,7 @@ class Publish extends core_1.Command {
89
85
  const updateUrl = (0, expoConfig_1.getExpoConfigUpdateUrl)(privateConfig);
90
86
  if (!updateUrl) {
91
87
  log_1.default.error("Update url is not setup in your config. Please run 'eoas init' to setup the update url");
92
- return;
88
+ process.exit(1);
93
89
  }
94
90
  let baseUrl;
95
91
  try {
@@ -98,7 +94,7 @@ class Publish extends core_1.Command {
98
94
  }
99
95
  catch (e) {
100
96
  log_1.default.error('Invalid URL', e);
101
- return;
97
+ process.exit(1);
102
98
  }
103
99
  if (!nonInteractive) {
104
100
  const confirmed = await (0, prompts_1.confirmAsync)({
@@ -108,44 +104,51 @@ class Publish extends core_1.Command {
108
104
  });
109
105
  if (!confirmed) {
110
106
  log_1.default.error('Please run `eoas init` to setup the correct update url');
107
+ process.exit(1);
111
108
  }
112
109
  }
113
- const runtimeSpinner = (0, ora_1.ora)('Resolving runtime version').start();
110
+ const runtimeSpinner = (0, ora_1.ora)('🔄 Resolving runtime version...').start();
114
111
  const runtimeVersions = [
115
112
  ...(!platform || platform === expoConfig_1.RequestedPlatform.All || platform === expoConfig_1.RequestedPlatform.Ios
116
113
  ? [
117
- (await (0, runtimeVersion_1.resolveRuntimeVersionAsync)({
118
- exp: privateConfig,
114
+ {
115
+ runtimeVersion: (await (0, runtimeVersion_1.resolveRuntimeVersionAsync)({
116
+ exp: privateConfig,
117
+ platform: 'ios',
118
+ workflow: await (0, workflow_1.resolveWorkflowAsync)(projectDir, eas_build_job_1.Platform.IOS, vcsClient),
119
+ projectDir,
120
+ env: {
121
+ RELEASE_CHANNEL: channel,
122
+ },
123
+ }))?.runtimeVersion,
119
124
  platform: 'ios',
120
- workflow: await (0, workflow_1.resolveWorkflowAsync)(projectDir, eas_build_job_1.Platform.IOS, this.vcsClient),
121
- projectDir,
122
- env: {
123
- RELEASE_CHANNEL: channel,
124
- },
125
- }))?.runtimeVersion,
125
+ },
126
126
  ]
127
127
  : []),
128
128
  ...(!platform || platform === expoConfig_1.RequestedPlatform.All || platform === expoConfig_1.RequestedPlatform.Android
129
129
  ? [
130
- (await (0, runtimeVersion_1.resolveRuntimeVersionAsync)({
131
- exp: privateConfig,
130
+ {
131
+ runtimeVersion: (await (0, runtimeVersion_1.resolveRuntimeVersionAsync)({
132
+ exp: privateConfig,
133
+ platform: 'android',
134
+ workflow: await (0, workflow_1.resolveWorkflowAsync)(projectDir, eas_build_job_1.Platform.ANDROID, vcsClient),
135
+ projectDir,
136
+ env: {
137
+ RELEASE_CHANNEL: channel,
138
+ },
139
+ }))?.runtimeVersion,
132
140
  platform: 'android',
133
- workflow: await (0, workflow_1.resolveWorkflowAsync)(projectDir, eas_build_job_1.Platform.ANDROID, this.vcsClient),
134
- projectDir,
135
- env: {
136
- RELEASE_CHANNEL: channel,
137
- },
138
- }))?.runtimeVersion,
141
+ },
139
142
  ]
140
143
  : []),
141
- ].filter(Boolean);
144
+ ].filter(({ runtimeVersion }) => !!runtimeVersion);
142
145
  if (!runtimeVersions.length) {
143
146
  runtimeSpinner.fail('Could not resolve runtime versions for the requested platforms');
144
147
  log_1.default.error('Could not resolve runtime versions for the requested platforms');
145
- return;
148
+ process.exit(1);
146
149
  }
147
- runtimeSpinner.succeed('Runtime versions resolved');
148
- const exportSpinner = (0, ora_1.ora)("Exporting project's static files").start();
150
+ runtimeSpinner.succeed('Runtime versions resolved');
151
+ const exportSpinner = (0, ora_1.ora)('📦 Exporting project files...').start();
149
152
  try {
150
153
  await (0, spawn_async_1.default)('rm', ['-rf', 'dist'], { cwd: projectDir });
151
154
  const { stdout } = await (0, spawn_async_1.default)('npx', ['expo', 'export', '--output-dir', 'dist'], {
@@ -155,39 +158,46 @@ class Publish extends core_1.Command {
155
158
  EXPO_NO_DOTENV: '1',
156
159
  },
157
160
  });
158
- exportSpinner.succeed('Project exported successfully');
161
+ exportSpinner.succeed('🚀 Project exported successfully');
159
162
  log_1.default.withInfo(stdout);
160
163
  }
161
164
  catch {
162
- exportSpinner.fail('Failed to export the project');
165
+ exportSpinner.fail('Failed to export the project');
166
+ process.exit(1);
163
167
  }
164
168
  const publicConfig = await (0, expoConfig_1.getPublicExpoConfigAsync)(projectDir, {
165
169
  skipSDKVersionRequirement: true,
166
170
  });
167
171
  if (!publicConfig) {
168
172
  log_1.default.error('Could not find Expo config in this project. Please make sure you have an Expo config.');
169
- return;
173
+ process.exit(1);
170
174
  }
171
175
  // eslint-disable-next-line
172
176
  fs_extra_1.default.writeJsonSync(path_1.default.join(projectDir, 'dist', 'expoConfig.json'), publicConfig, {
173
177
  spaces: 2,
174
178
  });
175
179
  log_1.default.withInfo('expoConfig.json file created in dist directory');
176
- const uploadFilesSpinner = (0, ora_1.ora)('Uploading files to the server').start();
180
+ const uploadFilesSpinner = (0, ora_1.ora)('📤 Uploading files...').start();
177
181
  const files = (0, assets_1.computeFilesRequests)(projectDir, platform || expoConfig_1.RequestedPlatform.All);
178
182
  if (!files.length) {
179
183
  uploadFilesSpinner.fail('No files to upload');
184
+ process.exit(1);
180
185
  }
186
+ let uploadUrls = [];
181
187
  try {
182
- const uploadUrls = await Promise.all(runtimeVersions.map(runtimeVersion => {
188
+ uploadUrls = await Promise.all(runtimeVersions.map(async ({ runtimeVersion, platform }) => {
183
189
  if (!runtimeVersion) {
184
190
  throw new Error('Runtime version is not resolved');
185
191
  }
186
- return (0, assets_1.requestUploadUrls)({
187
- fileNames: files.map(file => file.path),
188
- }, `${baseUrl}/requestUploadUrl/${branch}`, credentials, runtimeVersion);
192
+ return {
193
+ ...(await (0, assets_1.requestUploadUrls)({
194
+ fileNames: files.map(file => file.path),
195
+ }, `${baseUrl}/requestUploadUrl/${branch}`, credentials, runtimeVersion)),
196
+ runtimeVersion,
197
+ platform,
198
+ };
189
199
  }));
190
- const allItems = uploadUrls.flat();
200
+ const allItems = uploadUrls.flatMap(({ uploadRequests }) => uploadRequests);
191
201
  await Promise.all(allItems.map(async (itm) => {
192
202
  const isLocalBucketFileUpload = itm.requestUploadUrl.startsWith(`${baseUrl}/uploadLocalFile`);
193
203
  const formData = new form_data_1.default();
@@ -234,15 +244,61 @@ class Publish extends core_1.Command {
234
244
  body: buffer,
235
245
  });
236
246
  if (!response.ok) {
237
- log_1.default.error('Failed to upload file', await response.text());
238
- throw new Error('Failed to upload file');
247
+ log_1.default.error(' File upload failed', await response.text());
248
+ process.exit(1);
239
249
  }
240
250
  file.close();
241
251
  }));
242
- uploadFilesSpinner.succeed('Files uploaded successfully');
252
+ uploadFilesSpinner.succeed('Files uploaded successfully');
243
253
  }
244
- catch {
245
- uploadFilesSpinner.fail('Failed to upload static files');
254
+ catch (e) {
255
+ uploadFilesSpinner.fail('Failed to upload static files');
256
+ log_1.default.error(e);
257
+ process.exit(1);
258
+ }
259
+ const markAsFinishedSpinner = (0, ora_1.ora)('🔗 Marking the updates as finished...').start();
260
+ const results = await Promise.all(uploadUrls.map(async ({ updateId, platform, runtimeVersion }) => {
261
+ const response = await (0, node_fetch_1.default)(`${baseUrl}/markUpdateAsUploaded/${branch}?platform=${platform}&updateId=${updateId}&runtimeVersion=${runtimeVersion}`, {
262
+ method: 'POST',
263
+ headers: {
264
+ ...(0, auth_1.getAuthExpoHeaders)(credentials),
265
+ 'Content-Type': 'application/json',
266
+ },
267
+ });
268
+ // If success and status code = 200
269
+ if (response.ok) {
270
+ log_1.default.withInfo(`✅ Update ready for ${platform}`);
271
+ return 'deployed';
272
+ }
273
+ // If response.status === 406 duplicate update
274
+ if (response.status === 406) {
275
+ log_1.default.withInfo(`⚠️ There is no change in the update for ${platform}, ignored...`);
276
+ return 'identical';
277
+ }
278
+ log_1.default.error('❌ Failed to mark the update as finished for platform', platform);
279
+ log_1.default.newLine();
280
+ log_1.default.error(await response.text());
281
+ return 'error';
282
+ }));
283
+ const erroredUpdates = results.filter(result => result === 'error');
284
+ const hasSuccess = results.some(result => result === 'deployed');
285
+ const allIdentical = results.every(result => result === 'identical');
286
+ if (allIdentical) {
287
+ markAsFinishedSpinner.warn('⚠️ No changes found in the update, nothing to deploy');
288
+ return;
289
+ }
290
+ if (erroredUpdates.length) {
291
+ markAsFinishedSpinner.fail('❌ Some errors occurred while marking updates as finished');
292
+ throw new Error();
293
+ }
294
+ else {
295
+ markAsFinishedSpinner.succeed(`\n✅ Your update has been successfully pushed to ${updateUrl}`);
296
+ }
297
+ if (hasSuccess) {
298
+ log_1.default.withInfo(`🔗 Channel: \`${channel}\``);
299
+ log_1.default.withInfo(`🌿 Branch: \`${branch}\``);
300
+ log_1.default.withInfo(`⏳ Deployed at: \`${new Date().toUTCString()}\`\n`);
301
+ log_1.default.withInfo('🔥 Your users will receive the latest update automatically!');
246
302
  }
247
303
  }
248
304
  }
@@ -15,5 +15,8 @@ export interface RequestUploadUrlItem {
15
15
  }
16
16
  export declare function requestUploadUrls(body: {
17
17
  fileNames: string[];
18
- }, requestUploadUrl: string, auth: ExpoCredentials, runtimeVersion: string): Promise<RequestUploadUrlItem[]>;
18
+ }, requestUploadUrl: string, auth: ExpoCredentials, runtimeVersion: string): Promise<{
19
+ uploadRequests: RequestUploadUrlItem[];
20
+ updateId: string;
21
+ }>;
19
22
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eoas",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "build": "tsc --project tsconfig.json",