@capawesome/cli 1.4.0 → 1.5.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/CHANGELOG.md CHANGED
@@ -2,6 +2,25 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.
4
4
 
5
+ ## [1.5.0](https://github.com/capawesome-team/cli/compare/v1.4.1...v1.5.0) (2025-02-21)
6
+
7
+
8
+ ### Features
9
+
10
+ * **apps:bundles:create:** support code signing for self-hosted bundles ([ebc84ec](https://github.com/capawesome-team/cli/commit/ebc84ecdf3006e9b68491d3082991d0bcf8f9423))
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **apps:bundles:create:** provide a url without path to support self-hosted bundles ([1dcf020](https://github.com/capawesome-team/cli/commit/1dcf020ff006445e3ff211ed66a1cec1afdd012d))
16
+
17
+ ## [1.4.1](https://github.com/capawesome-team/cli/compare/v1.4.0...v1.4.1) (2025-02-16)
18
+
19
+
20
+ ### Bug Fixes
21
+
22
+ * **bundles:** add retry mechanism for file upload on failure ([86a62da](https://github.com/capawesome-team/cli/commit/86a62daa2ae50d691791714ef6a5812a6e87dc4b))
23
+
5
24
  ## [1.4.0](https://github.com/capawesome-team/cli/compare/v1.3.2...v1.4.0) (2025-01-29)
6
25
 
7
26
 
package/README.md CHANGED
@@ -10,6 +10,16 @@ The Capawesome Cloud CLI can be installed globally via npm:
10
10
  npm install -g @capawesome/cli
11
11
  ```
12
12
 
13
+ ## Usage
14
+
15
+ The Capawesome Cloud CLI can be invoked with the `capawesome` command.
16
+
17
+ ```bash
18
+ npx capawesome <command> [options]
19
+ ```
20
+
21
+ You can find a list of available commands in the [Command Reference](https://capawesome.io/cloud/cli/).
22
+
13
23
  ## Help
14
24
 
15
25
  The Capawesome Cloud CLI ships with command documentation that is accessible with the `--help` flag.
@@ -18,10 +28,35 @@ The Capawesome Cloud CLI ships with command documentation that is accessible wit
18
28
  npx capawesome --help
19
29
  ```
20
30
 
31
+ ## Development
32
+
33
+ Run the following commands to get started with development:
34
+
35
+ 1. Clone the repository:
36
+
37
+ ```bash
38
+ git clone https://github.com/capawesome-team/cli.git
39
+ ```
40
+
41
+ 2. Install dependencies:
42
+
43
+ ```bash
44
+ npm install
45
+ ```
46
+
47
+ 3. Copy the `.capawesomerc.example` file to `.capawesomerc`
48
+ 4. Run your first command:
49
+
50
+ ```bash
51
+ npm start -- --help
52
+ ```
53
+
54
+ **Note:** The `--` is required to pass arguments to the script.
55
+
21
56
  ## Changelog
22
57
 
23
58
  See [CHANGELOG](./CHANGELOG.md).
24
59
 
25
60
  ## License
26
61
 
27
- See [LICENSE](./LICENSE.md).
62
+ See [LICENSE](./LICENSE.md).
@@ -127,6 +127,7 @@ exports.default = (0, citty_1.defineCommand)({
127
127
  }
128
128
  rolloutPercentage = rolloutAsNumber;
129
129
  }
130
+ // Check that either a path or a url is provided
130
131
  if (!path && !url) {
131
132
  path = yield (0, prompt_1.prompt)('Enter the path to the app bundle:', {
132
133
  type: 'text',
@@ -136,6 +137,7 @@ exports.default = (0, citty_1.defineCommand)({
136
137
  process.exit(1);
137
138
  }
138
139
  }
140
+ // Check that the path is a directory when creating a bundle with an artifact type
139
141
  if (artifactType === 'manifest' && path) {
140
142
  const pathIsDirectory = (0, file_1.isDirectory)(path);
141
143
  if (!pathIsDirectory) {
@@ -143,12 +145,20 @@ exports.default = (0, citty_1.defineCommand)({
143
145
  process.exit(1);
144
146
  }
145
147
  }
146
- // Check if the path exists
147
- const pathExists = yield (0, file_1.fileExistsAtPath)(path);
148
- if (!pathExists) {
149
- consola_1.default.error(`The path does not exist.`);
148
+ // Check that a URL is not provided when creating a bundle with an artifact type of manifest
149
+ if (artifactType === 'manifest' && url) {
150
+ consola_1.default.error('It is not yet possible to provide a URL when creating a bundle with an artifact type of `manifest`.');
150
151
  process.exit(1);
151
152
  }
153
+ // Check if the path exists when a path is provided
154
+ if (path) {
155
+ const pathExists = yield (0, file_1.fileExistsAtPath)(path);
156
+ if (!pathExists) {
157
+ consola_1.default.error(`The path does not exist.`);
158
+ process.exit(1);
159
+ }
160
+ }
161
+ // Let the user select an app and channel if not provided
152
162
  if (!appId) {
153
163
  const apps = yield apps_1.default.findAll();
154
164
  if (apps.length === 0) {
@@ -180,6 +190,7 @@ exports.default = (0, citty_1.defineCommand)({
180
190
  }
181
191
  }
182
192
  }
193
+ // Create the private key buffer
183
194
  let privateKeyBuffer;
184
195
  if (privateKey) {
185
196
  if (privateKey.endsWith('.pem')) {
@@ -201,10 +212,22 @@ exports.default = (0, citty_1.defineCommand)({
201
212
  try {
202
213
  // Create the app bundle
203
214
  consola_1.default.start('Creating bundle...');
215
+ let checksum;
216
+ let signature;
217
+ if (path && url) {
218
+ const fileBuffer = yield (0, buffer_1.createBufferFromPath)(path);
219
+ // Generate checksum
220
+ checksum = yield (0, hash_1.createHash)(fileBuffer);
221
+ // Sign the bundle
222
+ if (privateKeyBuffer) {
223
+ signature = yield (0, signature_1.createSignature)(privateKeyBuffer, fileBuffer);
224
+ }
225
+ }
204
226
  const response = yield app_bundles_1.default.create({
205
227
  appId,
206
228
  artifactType,
207
229
  channelName,
230
+ checksum,
208
231
  customProperties: parseCustomProperties(customProperty),
209
232
  expiresAt: expiresAt,
210
233
  url,
@@ -213,21 +236,28 @@ exports.default = (0, citty_1.defineCommand)({
213
236
  minAndroidAppVersionCode: androidMin,
214
237
  minIosAppVersionCode: iosMin,
215
238
  rolloutPercentage,
239
+ signature,
216
240
  });
217
241
  appBundleId = response.id;
218
242
  if (path) {
219
- let appBundleFileId;
220
- // Upload the app bundle files
221
- if (artifactType === 'manifest') {
222
- yield uploadFiles({ appId, appBundleId: response.id, path, privateKeyBuffer });
243
+ if (url) {
244
+ // Important: Do NOT upload files if the URL is provided.
245
+ // The user wants to self-host the bundle. The path is only needed for code signing.
223
246
  }
224
247
  else {
225
- const result = yield uploadZip({ appId, appBundleId: response.id, path, privateKeyBuffer });
226
- appBundleFileId = result.appBundleFileId;
248
+ let appBundleFileId;
249
+ // Upload the app bundle files
250
+ if (artifactType === 'manifest') {
251
+ yield uploadFiles({ appId, appBundleId: response.id, path, privateKeyBuffer });
252
+ }
253
+ else {
254
+ const result = yield uploadZip({ appId, appBundleId: response.id, path, privateKeyBuffer });
255
+ appBundleFileId = result.appBundleFileId;
256
+ }
257
+ // Update the app bundle
258
+ consola_1.default.start('Updating bundle...');
259
+ yield app_bundles_1.default.update({ appBundleFileId, appId, artifactStatus: 'ready', appBundleId: response.id });
227
260
  }
228
- // Update the app bundle
229
- consola_1.default.start('Updating bundle...');
230
- yield app_bundles_1.default.update({ appBundleFileId, appId, artifactStatus: 'ready', appBundleId: response.id });
231
261
  }
232
262
  consola_1.default.success('Bundle successfully created.');
233
263
  consola_1.default.info(`Bundle ID: ${response.id}`);
@@ -245,23 +275,31 @@ exports.default = (0, citty_1.defineCommand)({
245
275
  }),
246
276
  });
247
277
  const uploadFile = (options) => __awaiter(void 0, void 0, void 0, function* () {
248
- // Generate checksum
249
- const hash = yield (0, hash_1.createHash)(options.fileBuffer);
250
- // Sign the bundle
251
- let signature;
252
- if (options.privateKeyBuffer) {
253
- signature = yield (0, signature_1.createSignature)(options.privateKeyBuffer, options.fileBuffer);
278
+ try {
279
+ // Generate checksum
280
+ const hash = yield (0, hash_1.createHash)(options.fileBuffer);
281
+ // Sign the bundle
282
+ let signature;
283
+ if (options.privateKeyBuffer) {
284
+ signature = yield (0, signature_1.createSignature)(options.privateKeyBuffer, options.fileBuffer);
285
+ }
286
+ // Create the multipart upload
287
+ return yield app_bundle_files_1.default.create({
288
+ appId: options.appId,
289
+ appBundleId: options.appBundleId,
290
+ checksum: hash,
291
+ fileBuffer: options.fileBuffer,
292
+ fileName: options.fileName,
293
+ href: options.href,
294
+ signature,
295
+ });
296
+ }
297
+ catch (error) {
298
+ if (options.retryOnFailure) {
299
+ return uploadFile(Object.assign(Object.assign({}, options), { retryOnFailure: false }));
300
+ }
301
+ throw error;
254
302
  }
255
- // Create the multipart upload
256
- return app_bundle_files_1.default.create({
257
- appId: options.appId,
258
- appBundleId: options.appBundleId,
259
- checksum: hash,
260
- fileBuffer: options.fileBuffer,
261
- fileName: options.fileName,
262
- href: options.href,
263
- signature,
264
- });
265
303
  });
266
304
  const uploadFiles = (options) => __awaiter(void 0, void 0, void 0, function* () {
267
305
  // Generate the manifest file
@@ -288,6 +326,7 @@ const uploadFiles = (options) => __awaiter(void 0, void 0, void 0, function* ()
288
326
  fileName,
289
327
  href,
290
328
  privateKeyBuffer: options.privateKeyBuffer,
329
+ retryOnFailure: true,
291
330
  });
292
331
  yield uploadNextFile();
293
332
  });
@@ -53,7 +53,7 @@ class UpdateServiceImpl {
53
53
  }
54
54
  }
55
55
  catch (error) {
56
- consola_1.default.error('Failed to check for updates.');
56
+ consola_1.default.warn('Failed to check for updates.');
57
57
  }
58
58
  });
59
59
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capawesome/cli",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "The Capawesome Cloud Command Line Interface (CLI) to manage Live Updates and more.",
5
5
  "scripts": {
6
6
  "build": "patch-package && rimraf ./dist && tsc",