@capawesome/cli 4.2.0 → 4.3.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 +17 -0
- package/dist/commands/apps/liveupdates/bundle.js +5 -2
- package/dist/commands/apps/liveupdates/upload.js +16 -13
- package/dist/config/consts.js +2 -1
- package/dist/services/app-bundle-files.js +10 -9
- package/dist/utils/time-format.js +1 -1
- package/dist/utils/zip.js +4 -10
- package/package.json +4 -8
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
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
|
+
## [4.3.0](https://github.com/capawesome-team/cli/compare/v4.2.1...v4.3.0) (2026-03-07)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* add part-level progress for multipart zip uploads ([#122](https://github.com/capawesome-team/cli/issues/122)) ([f74cda4](https://github.com/capawesome-team/cli/commit/f74cda48f454dfbc786052498ebecccef98e6fc5))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* **deps:** remove `glob` dependency ([#120](https://github.com/capawesome-team/cli/issues/120)) ([80709de](https://github.com/capawesome-team/cli/commit/80709deac3292b976f3633f3335004d2cb80fc22))
|
|
16
|
+
* read deployment ID from update response ([#121](https://github.com/capawesome-team/cli/issues/121)) ([9ef9979](https://github.com/capawesome-team/cli/commit/9ef99794a5bb14631fcecf41c1a9885931627dfd))
|
|
17
|
+
* use ISO string timestamps ([#119](https://github.com/capawesome-team/cli/issues/119)) ([7fe8263](https://github.com/capawesome-team/cli/commit/7fe8263956fc83ca7a93d803e6867133fea8c879))
|
|
18
|
+
* validate prompt response in bundle command ([#118](https://github.com/capawesome-team/cli/issues/118)) ([a1541a0](https://github.com/capawesome-team/cli/commit/a1541a074f614b42bf69f2c6a186eeef5d95200b))
|
|
19
|
+
|
|
20
|
+
## [4.2.1](https://github.com/capawesome-team/cli/compare/v4.2.0...v4.2.1) (2026-03-01)
|
|
21
|
+
|
|
5
22
|
## [4.2.0](https://github.com/capawesome-team/cli/compare/v4.1.0...v4.2.0) (2026-02-20)
|
|
6
23
|
|
|
7
24
|
|
|
@@ -33,10 +33,13 @@ export default defineCommand({
|
|
|
33
33
|
process.exit(1);
|
|
34
34
|
}
|
|
35
35
|
consola.warn('Make sure you have built your web assets before creating a bundle (e.g., `npm run build`).');
|
|
36
|
-
|
|
36
|
+
inputPath = await prompt('Enter the path to the web assets directory (e.g., `dist` or `www`):', {
|
|
37
37
|
type: 'text',
|
|
38
38
|
});
|
|
39
|
-
inputPath
|
|
39
|
+
if (!inputPath) {
|
|
40
|
+
consola.error('You must provide an input path.');
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
40
43
|
}
|
|
41
44
|
// Convert to absolute path
|
|
42
45
|
inputPath = pathModule.resolve(inputPath);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DEFAULT_CONSOLE_BASE_URL,
|
|
1
|
+
import { DEFAULT_CONSOLE_BASE_URL, MAX_CONCURRENT_FILE_UPLOADS } from '../../../config/index.js';
|
|
2
2
|
import appBundleFilesService from '../../../services/app-bundle-files.js';
|
|
3
3
|
import appBundlesService from '../../../services/app-bundles.js';
|
|
4
4
|
import appsService from '../../../services/apps.js';
|
|
@@ -236,7 +236,7 @@ export default defineCommand({
|
|
|
236
236
|
}
|
|
237
237
|
// Create the app bundle
|
|
238
238
|
consola.start('Creating bundle...');
|
|
239
|
-
const
|
|
239
|
+
const createBundleResponse = await appBundlesService.create({
|
|
240
240
|
appId,
|
|
241
241
|
artifactType,
|
|
242
242
|
channelName: channel,
|
|
@@ -258,29 +258,29 @@ export default defineCommand({
|
|
|
258
258
|
let appBundleFileId;
|
|
259
259
|
// Upload the app bundle files
|
|
260
260
|
if (artifactType === 'manifest') {
|
|
261
|
-
await uploadFiles({ appId, appBundleId:
|
|
261
|
+
await uploadFiles({ appId, appBundleId: createBundleResponse.id, path, privateKeyBuffer });
|
|
262
262
|
}
|
|
263
263
|
else {
|
|
264
|
-
const result = await uploadZip({ appId, appBundleId:
|
|
264
|
+
const result = await uploadZip({ appId, appBundleId: createBundleResponse.id, path, privateKeyBuffer });
|
|
265
265
|
appBundleFileId = result.appBundleFileId;
|
|
266
266
|
}
|
|
267
267
|
// Update the app bundle
|
|
268
268
|
consola.start('Updating bundle...');
|
|
269
|
-
await appBundlesService.update({
|
|
269
|
+
const updateBundleResponse = await appBundlesService.update({
|
|
270
270
|
appBundleFileId,
|
|
271
271
|
appId,
|
|
272
272
|
artifactStatus: 'ready',
|
|
273
|
-
appBundleId:
|
|
273
|
+
appBundleId: createBundleResponse.id,
|
|
274
274
|
});
|
|
275
|
-
consola.info(`Build Artifact ID: ${
|
|
276
|
-
if (
|
|
277
|
-
consola.info(`Deployment URL: ${DEFAULT_CONSOLE_BASE_URL}/apps/${appId}/deployments/${
|
|
275
|
+
consola.info(`Build Artifact ID: ${createBundleResponse.id}`);
|
|
276
|
+
if (updateBundleResponse.appDeploymentId) {
|
|
277
|
+
consola.info(`Deployment URL: ${DEFAULT_CONSOLE_BASE_URL}/apps/${appId}/deployments/${updateBundleResponse.appDeploymentId}`);
|
|
278
278
|
}
|
|
279
279
|
consola.success('Live Update successfully uploaded.');
|
|
280
280
|
}),
|
|
281
281
|
});
|
|
282
282
|
const uploadFile = async (options) => {
|
|
283
|
-
let { appId, appBundleId, buffer, href, mimeType, name, privateKeyBuffer, retryOnFailure } = options;
|
|
283
|
+
let { appId, appBundleId, buffer, href, mimeType, name, onProgress, privateKeyBuffer, retryOnFailure } = options;
|
|
284
284
|
try {
|
|
285
285
|
// Generate checksum
|
|
286
286
|
const hash = await createHash(buffer);
|
|
@@ -299,7 +299,7 @@ const uploadFile = async (options) => {
|
|
|
299
299
|
mimeType,
|
|
300
300
|
name,
|
|
301
301
|
signature,
|
|
302
|
-
});
|
|
302
|
+
}, onProgress);
|
|
303
303
|
}
|
|
304
304
|
catch (error) {
|
|
305
305
|
if (retryOnFailure) {
|
|
@@ -339,8 +339,8 @@ const uploadFiles = async (options) => {
|
|
|
339
339
|
});
|
|
340
340
|
await uploadNextFile();
|
|
341
341
|
};
|
|
342
|
-
const uploadPromises = Array.from({ length:
|
|
343
|
-
for (let i = 0; i <
|
|
342
|
+
const uploadPromises = Array.from({ length: MAX_CONCURRENT_FILE_UPLOADS });
|
|
343
|
+
for (let i = 0; i < MAX_CONCURRENT_FILE_UPLOADS; i++) {
|
|
344
344
|
uploadPromises[i] = uploadNextFile();
|
|
345
345
|
}
|
|
346
346
|
await Promise.all(uploadPromises);
|
|
@@ -365,6 +365,9 @@ const uploadZip = async (options) => {
|
|
|
365
365
|
buffer: fileBuffer,
|
|
366
366
|
mimeType: 'application/zip',
|
|
367
367
|
name: 'bundle.zip',
|
|
368
|
+
onProgress: (completed, total) => {
|
|
369
|
+
consola.start(`Uploading file (part ${completed}/${total})...`);
|
|
370
|
+
},
|
|
368
371
|
privateKeyBuffer: privateKeyBuffer,
|
|
369
372
|
});
|
|
370
373
|
return {
|
package/dist/config/consts.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export const DEFAULT_API_BASE_URL = 'https://api.cloud.capawesome.io';
|
|
2
2
|
export const DEFAULT_CONSOLE_BASE_URL = 'https://console.cloud.capawesome.io';
|
|
3
3
|
export const MANIFEST_JSON_FILE_NAME = 'capawesome-live-update-manifest.json'; // Do NOT change this!
|
|
4
|
-
export const
|
|
4
|
+
export const MAX_CONCURRENT_FILE_UPLOADS = 20;
|
|
5
|
+
export const MAX_CONCURRENT_PART_UPLOADS = 4;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { MAX_CONCURRENT_PART_UPLOADS } from '../config/index.js';
|
|
2
2
|
import authorizationService from '../services/authorization-service.js';
|
|
3
3
|
import httpClient from '../utils/http-client.js';
|
|
4
4
|
import FormData from 'form-data';
|
|
@@ -7,7 +7,7 @@ class AppBundleFilesServiceImpl {
|
|
|
7
7
|
constructor(httpClient) {
|
|
8
8
|
this.httpClient = httpClient;
|
|
9
9
|
}
|
|
10
|
-
async create(dto) {
|
|
10
|
+
async create(dto, onProgress) {
|
|
11
11
|
const sizeInBytes = dto.buffer.byteLength;
|
|
12
12
|
const useMultipartUpload = sizeInBytes >= 50 * 1024 * 1024; // 50 MB
|
|
13
13
|
const formData = new FormData();
|
|
@@ -38,7 +38,7 @@ class AppBundleFilesServiceImpl {
|
|
|
38
38
|
buffer: dto.buffer,
|
|
39
39
|
name: dto.name,
|
|
40
40
|
checksum: dto.checksum,
|
|
41
|
-
});
|
|
41
|
+
}, onProgress);
|
|
42
42
|
}
|
|
43
43
|
return response.data;
|
|
44
44
|
}
|
|
@@ -74,7 +74,7 @@ class AppBundleFilesServiceImpl {
|
|
|
74
74
|
})
|
|
75
75
|
.then((response) => response.data);
|
|
76
76
|
}
|
|
77
|
-
async createUploadParts(dto) {
|
|
77
|
+
async createUploadParts(dto, onProgress) {
|
|
78
78
|
const uploadedParts = [];
|
|
79
79
|
const partSize = 10 * 1024 * 1024; // 10 MB. 5 MB is the minimum part size except for the last part.
|
|
80
80
|
const totalParts = Math.ceil(dto.buffer.byteLength / partSize);
|
|
@@ -84,6 +84,7 @@ class AppBundleFilesServiceImpl {
|
|
|
84
84
|
return;
|
|
85
85
|
}
|
|
86
86
|
partNumber++;
|
|
87
|
+
onProgress?.(partNumber, totalParts);
|
|
87
88
|
const start = (partNumber - 1) * partSize;
|
|
88
89
|
const end = Math.min(start + partSize, dto.buffer.byteLength);
|
|
89
90
|
const partBuffer = dto.buffer.subarray(start, end);
|
|
@@ -99,14 +100,14 @@ class AppBundleFilesServiceImpl {
|
|
|
99
100
|
uploadedParts.push(uploadedPart);
|
|
100
101
|
await uploadNextPart();
|
|
101
102
|
};
|
|
102
|
-
const uploadPartPromises = Array.from({ length:
|
|
103
|
-
for (let i = 0; i <
|
|
103
|
+
const uploadPartPromises = Array.from({ length: MAX_CONCURRENT_PART_UPLOADS });
|
|
104
|
+
for (let i = 0; i < MAX_CONCURRENT_PART_UPLOADS; i++) {
|
|
104
105
|
uploadPartPromises[i] = uploadNextPart();
|
|
105
106
|
}
|
|
106
107
|
await Promise.all(uploadPartPromises);
|
|
107
|
-
return uploadedParts;
|
|
108
|
+
return uploadedParts.sort((a, b) => a.partNumber - b.partNumber);
|
|
108
109
|
}
|
|
109
|
-
async upload(dto) {
|
|
110
|
+
async upload(dto, onProgress) {
|
|
110
111
|
// 1. Create a multipart upload
|
|
111
112
|
const { uploadId } = await this.createUpload({
|
|
112
113
|
appBundleFileId: dto.appBundleFileId,
|
|
@@ -121,7 +122,7 @@ class AppBundleFilesServiceImpl {
|
|
|
121
122
|
buffer: dto.buffer,
|
|
122
123
|
name: dto.name,
|
|
123
124
|
uploadId,
|
|
124
|
-
});
|
|
125
|
+
}, onProgress);
|
|
125
126
|
// 3. Complete the upload
|
|
126
127
|
await this.completeUpload({
|
|
127
128
|
appBundleFileId: dto.appBundleFileId,
|
package/dist/utils/zip.js
CHANGED
|
@@ -1,15 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import AdmZip from 'adm-zip';
|
|
2
2
|
class ZipImpl {
|
|
3
3
|
async zipFolder(sourceFolder) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
archive.on('data', (data) => buffers.push(data));
|
|
8
|
-
archive.on('error', (err) => reject(err));
|
|
9
|
-
archive.on('end', () => resolve(Buffer.concat(buffers)));
|
|
10
|
-
archive.directory(sourceFolder, false);
|
|
11
|
-
archive.finalize();
|
|
12
|
-
});
|
|
4
|
+
const zip = new AdmZip();
|
|
5
|
+
zip.addLocalFolder(sourceFolder);
|
|
6
|
+
return zip.toBuffer();
|
|
13
7
|
}
|
|
14
8
|
isZipped(path) {
|
|
15
9
|
return path.endsWith('.zip');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capawesome/cli",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.3.0",
|
|
4
4
|
"description": "The Capawesome Cloud Command Line Interface (CLI) to manage Live Updates and more.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -51,15 +51,11 @@
|
|
|
51
51
|
"url": "https://opencollective.com/capawesome"
|
|
52
52
|
}
|
|
53
53
|
],
|
|
54
|
-
"overrides": {
|
|
55
|
-
"glob": "13.0.6",
|
|
56
|
-
"minimatch": "10.2.2"
|
|
57
|
-
},
|
|
58
54
|
"dependencies": {
|
|
59
55
|
"@clack/prompts": "0.7.0",
|
|
60
56
|
"@robingenz/zli": "0.2.0",
|
|
61
57
|
"@sentry/node": "8.55.0",
|
|
62
|
-
"
|
|
58
|
+
"adm-zip": "0.5.16",
|
|
63
59
|
"axios": "1.13.5",
|
|
64
60
|
"axios-retry": "4.5.0",
|
|
65
61
|
"c12": "3.3.3",
|
|
@@ -77,12 +73,12 @@
|
|
|
77
73
|
"devDependencies": {
|
|
78
74
|
"@ionic/prettier-config": "4.0.0",
|
|
79
75
|
"@sentry/cli": "2.52.0",
|
|
80
|
-
"@types/
|
|
76
|
+
"@types/adm-zip": "0.5.7",
|
|
81
77
|
"@types/mime": "3.0.4",
|
|
82
78
|
"@types/node": "24.2.1",
|
|
83
79
|
"@types/semver": "7.5.8",
|
|
84
80
|
"@vitest/ui": "3.2.4",
|
|
85
|
-
"commit-and-tag-version": "12.
|
|
81
|
+
"commit-and-tag-version": "12.6.1",
|
|
86
82
|
"nock": "14.0.10",
|
|
87
83
|
"prettier": "3.3.3",
|
|
88
84
|
"rimraf": "6.0.1",
|