@capawesome/cli 1.12.0 → 1.13.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 +7 -0
- package/dist/commands/apps/bundles/create.js +15 -12
- package/dist/config/consts.js +2 -1
- package/dist/services/app-bundle-files.js +115 -1
- package/dist/utils/file.js +13 -34
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
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.13.0](https://github.com/capawesome-team/cli/compare/v1.12.0...v1.13.0) (2025-06-01)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* support for uploading bundles with more than 100 mb ([#52](https://github.com/capawesome-team/cli/issues/52)) ([788b70b](https://github.com/capawesome-team/cli/commit/788b70b71258f57537a8284ce9c5fc93be81443b))
|
|
11
|
+
|
|
5
12
|
## [1.12.0](https://github.com/capawesome-team/cli/compare/v1.11.1...v1.12.0) (2025-05-22)
|
|
6
13
|
|
|
7
14
|
|
|
@@ -15,6 +15,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
15
15
|
const citty_1 = require("citty");
|
|
16
16
|
const consola_1 = __importDefault(require("consola"));
|
|
17
17
|
const fs_1 = require("fs");
|
|
18
|
+
const config_1 = require("../../../config");
|
|
18
19
|
const app_bundle_files_1 = __importDefault(require("../../../services/app-bundle-files"));
|
|
19
20
|
const app_bundles_1 = __importDefault(require("../../../services/app-bundles"));
|
|
20
21
|
const apps_1 = __importDefault(require("../../../services/apps"));
|
|
@@ -309,20 +310,21 @@ exports.default = (0, citty_1.defineCommand)({
|
|
|
309
310
|
const uploadFile = (options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
310
311
|
try {
|
|
311
312
|
// Generate checksum
|
|
312
|
-
const hash = yield (0, hash_1.createHash)(options.
|
|
313
|
+
const hash = yield (0, hash_1.createHash)(options.buffer);
|
|
313
314
|
// Sign the bundle
|
|
314
315
|
let signature;
|
|
315
316
|
if (options.privateKeyBuffer) {
|
|
316
|
-
signature = yield (0, signature_1.createSignature)(options.privateKeyBuffer, options.
|
|
317
|
+
signature = yield (0, signature_1.createSignature)(options.privateKeyBuffer, options.buffer);
|
|
317
318
|
}
|
|
318
319
|
// Create the multipart upload
|
|
319
320
|
return yield app_bundle_files_1.default.create({
|
|
320
321
|
appId: options.appId,
|
|
321
322
|
appBundleId: options.appBundleId,
|
|
323
|
+
buffer: options.buffer,
|
|
322
324
|
checksum: hash,
|
|
323
|
-
fileBuffer: options.fileBuffer,
|
|
324
|
-
fileName: options.fileName,
|
|
325
325
|
href: options.href,
|
|
326
|
+
mimeType: options.mimeType,
|
|
327
|
+
name: options.name,
|
|
326
328
|
signature,
|
|
327
329
|
});
|
|
328
330
|
}
|
|
@@ -339,7 +341,6 @@ const uploadFiles = (options) => __awaiter(void 0, void 0, void 0, function* ()
|
|
|
339
341
|
// Get all files in the directory
|
|
340
342
|
const files = yield (0, file_1.getFilesInDirectoryAndSubdirectories)(options.path);
|
|
341
343
|
// Iterate over each file
|
|
342
|
-
const MAX_CONCURRENT_UPLOADS = 20;
|
|
343
344
|
let fileIndex = 0;
|
|
344
345
|
const uploadNextFile = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
345
346
|
if (fileIndex >= files.length) {
|
|
@@ -348,20 +349,21 @@ const uploadFiles = (options) => __awaiter(void 0, void 0, void 0, function* ()
|
|
|
348
349
|
const file = files[fileIndex];
|
|
349
350
|
fileIndex++;
|
|
350
351
|
consola_1.default.start(`Uploading file (${fileIndex}/${files.length})...`);
|
|
351
|
-
const
|
|
352
|
+
const buffer = yield (0, buffer_1.createBufferFromPath)(file.path);
|
|
352
353
|
yield uploadFile({
|
|
353
354
|
appId: options.appId,
|
|
354
355
|
appBundleId: options.appBundleId,
|
|
355
|
-
|
|
356
|
-
fileName: file.name,
|
|
356
|
+
buffer,
|
|
357
357
|
href: file.href,
|
|
358
|
+
mimeType: file.mimeType,
|
|
359
|
+
name: file.name,
|
|
358
360
|
privateKeyBuffer: options.privateKeyBuffer,
|
|
359
361
|
retryOnFailure: true,
|
|
360
362
|
});
|
|
361
363
|
yield uploadNextFile();
|
|
362
364
|
});
|
|
363
|
-
const uploadPromises = Array(MAX_CONCURRENT_UPLOADS);
|
|
364
|
-
for (let i = 0; i < MAX_CONCURRENT_UPLOADS; i++) {
|
|
365
|
+
const uploadPromises = Array.from({ length: config_1.MAX_CONCURRENT_UPLOADS });
|
|
366
|
+
for (let i = 0; i < config_1.MAX_CONCURRENT_UPLOADS; i++) {
|
|
365
367
|
uploadPromises[i] = uploadNextFile();
|
|
366
368
|
}
|
|
367
369
|
yield Promise.all(uploadPromises);
|
|
@@ -382,8 +384,9 @@ const uploadZip = (options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
382
384
|
const result = yield uploadFile({
|
|
383
385
|
appId: options.appId,
|
|
384
386
|
appBundleId: options.appBundleId,
|
|
385
|
-
fileBuffer,
|
|
386
|
-
|
|
387
|
+
buffer: fileBuffer,
|
|
388
|
+
mimeType: 'application/zip',
|
|
389
|
+
name: 'bundle.zip',
|
|
387
390
|
privateKeyBuffer: options.privateKeyBuffer,
|
|
388
391
|
});
|
|
389
392
|
return {
|
package/dist/config/consts.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.MANIFEST_JSON_FILE_NAME = void 0;
|
|
3
|
+
exports.MAX_CONCURRENT_UPLOADS = exports.MANIFEST_JSON_FILE_NAME = void 0;
|
|
4
4
|
exports.MANIFEST_JSON_FILE_NAME = 'capawesome-live-update-manifest.json'; // Do NOT change this!
|
|
5
|
+
exports.MAX_CONCURRENT_UPLOADS = 20;
|
|
@@ -13,6 +13,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
13
13
|
};
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
15
|
const form_data_1 = __importDefault(require("form-data"));
|
|
16
|
+
const config_1 = require("../config");
|
|
16
17
|
const http_client_1 = __importDefault(require("../utils/http-client"));
|
|
17
18
|
const authorization_service_1 = __importDefault(require("./authorization-service"));
|
|
18
19
|
class AppBundleFilesServiceImpl {
|
|
@@ -21,21 +22,134 @@ class AppBundleFilesServiceImpl {
|
|
|
21
22
|
}
|
|
22
23
|
create(dto) {
|
|
23
24
|
return __awaiter(this, void 0, void 0, function* () {
|
|
25
|
+
const sizeInBytes = dto.buffer.byteLength;
|
|
26
|
+
const useMultipartUpload = sizeInBytes >= 100 * 1024 * 1024; // 100 MB
|
|
24
27
|
const formData = new form_data_1.default();
|
|
25
28
|
formData.append('checksum', dto.checksum);
|
|
26
|
-
|
|
29
|
+
if (!useMultipartUpload) {
|
|
30
|
+
formData.append('file', dto.buffer, { filename: dto.name });
|
|
31
|
+
}
|
|
27
32
|
if (dto.href) {
|
|
28
33
|
formData.append('href', dto.href);
|
|
29
34
|
}
|
|
35
|
+
formData.append('mimeType', dto.mimeType);
|
|
36
|
+
formData.append('name', dto.name);
|
|
30
37
|
if (dto.signature) {
|
|
31
38
|
formData.append('signature', dto.signature);
|
|
32
39
|
}
|
|
40
|
+
formData.append('sizeInBytes', sizeInBytes.toString());
|
|
33
41
|
const response = yield this.httpClient.post(`/v1/apps/${dto.appId}/bundles/${dto.appBundleId}/files`, formData, {
|
|
34
42
|
headers: Object.assign({ Authorization: `Bearer ${authorization_service_1.default.getCurrentAuthorizationToken()}` }, formData.getHeaders()),
|
|
35
43
|
});
|
|
44
|
+
if (useMultipartUpload) {
|
|
45
|
+
yield this.upload({
|
|
46
|
+
appBundleFileId: response.data.id,
|
|
47
|
+
appBundleId: dto.appBundleId,
|
|
48
|
+
appId: dto.appId,
|
|
49
|
+
buffer: dto.buffer,
|
|
50
|
+
name: dto.name,
|
|
51
|
+
checksum: dto.checksum,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
36
54
|
return response.data;
|
|
37
55
|
});
|
|
38
56
|
}
|
|
57
|
+
completeUpload(dto) {
|
|
58
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
59
|
+
return this.httpClient
|
|
60
|
+
.post(`/v1/apps/${dto.appId}/bundles/${dto.appBundleId}/files/${dto.appBundleFileId}/upload?action=mpu-complete&uploadId=${dto.uploadId}`, {
|
|
61
|
+
parts: dto.parts,
|
|
62
|
+
}, {
|
|
63
|
+
headers: {
|
|
64
|
+
Authorization: `Bearer ${authorization_service_1.default.getCurrentAuthorizationToken()}`,
|
|
65
|
+
},
|
|
66
|
+
})
|
|
67
|
+
.then((response) => response.data);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
createUpload(dto) {
|
|
71
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
72
|
+
const response = yield this.httpClient.post(`/v1/apps/${dto.appId}/bundles/${dto.appBundleId}/files/${dto.appBundleFileId}/upload?action=mpu-create`, {}, {
|
|
73
|
+
headers: {
|
|
74
|
+
Authorization: `Bearer ${authorization_service_1.default.getCurrentAuthorizationToken()}`,
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
return response.data;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
createUploadPart(dto) {
|
|
81
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
82
|
+
const formData = new form_data_1.default();
|
|
83
|
+
formData.append('blob', dto.buffer, { filename: dto.name });
|
|
84
|
+
formData.append('partNumber', dto.partNumber.toString());
|
|
85
|
+
return this.httpClient
|
|
86
|
+
.put(`/v1/apps/${dto.appId}/bundles/${dto.appBundleId}/files/${dto.appBundleFileId}/upload?action=mpu-uploadpart&uploadId=${dto.uploadId}`, formData, {
|
|
87
|
+
headers: Object.assign({ Authorization: `Bearer ${authorization_service_1.default.getCurrentAuthorizationToken()}` }, formData.getHeaders()),
|
|
88
|
+
})
|
|
89
|
+
.then((response) => response.data);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
createUploadParts(dto) {
|
|
93
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
94
|
+
const uploadedParts = [];
|
|
95
|
+
const partSize = 10 * 1024 * 1024; // 10 MB. 5 MB is the minimum part size except for the last part.
|
|
96
|
+
const totalParts = Math.ceil(dto.buffer.byteLength / partSize);
|
|
97
|
+
let partNumber = 0;
|
|
98
|
+
const uploadNextPart = () => __awaiter(this, void 0, void 0, function* () {
|
|
99
|
+
if (partNumber >= totalParts) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
partNumber++;
|
|
103
|
+
const start = (partNumber - 1) * partSize;
|
|
104
|
+
const end = Math.min(start + partSize, dto.buffer.byteLength);
|
|
105
|
+
const partBuffer = dto.buffer.subarray(start, end);
|
|
106
|
+
const uploadedPart = yield this.createUploadPart({
|
|
107
|
+
appBundleFileId: dto.appBundleFileId,
|
|
108
|
+
appBundleId: dto.appBundleId,
|
|
109
|
+
appId: dto.appId,
|
|
110
|
+
buffer: partBuffer,
|
|
111
|
+
name: dto.name,
|
|
112
|
+
partNumber,
|
|
113
|
+
uploadId: dto.uploadId,
|
|
114
|
+
});
|
|
115
|
+
uploadedParts.push(uploadedPart);
|
|
116
|
+
yield uploadNextPart();
|
|
117
|
+
});
|
|
118
|
+
const uploadPartPromises = Array.from({ length: config_1.MAX_CONCURRENT_UPLOADS });
|
|
119
|
+
for (let i = 0; i < config_1.MAX_CONCURRENT_UPLOADS; i++) {
|
|
120
|
+
uploadPartPromises[i] = uploadNextPart();
|
|
121
|
+
}
|
|
122
|
+
yield Promise.all(uploadPartPromises);
|
|
123
|
+
return uploadedParts;
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
upload(dto) {
|
|
127
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
128
|
+
// 1. Create a multipart upload
|
|
129
|
+
const { uploadId } = yield this.createUpload({
|
|
130
|
+
appBundleFileId: dto.appBundleFileId,
|
|
131
|
+
appBundleId: dto.appBundleId,
|
|
132
|
+
appId: dto.appId,
|
|
133
|
+
});
|
|
134
|
+
// 2. Upload the file in parts
|
|
135
|
+
const parts = yield this.createUploadParts({
|
|
136
|
+
appBundleFileId: dto.appBundleFileId,
|
|
137
|
+
appBundleId: dto.appBundleId,
|
|
138
|
+
appId: dto.appId,
|
|
139
|
+
buffer: dto.buffer,
|
|
140
|
+
name: dto.name,
|
|
141
|
+
uploadId,
|
|
142
|
+
});
|
|
143
|
+
// 3. Complete the upload
|
|
144
|
+
yield this.completeUpload({
|
|
145
|
+
appBundleFileId: dto.appBundleFileId,
|
|
146
|
+
appBundleId: dto.appBundleId,
|
|
147
|
+
appId: dto.appId,
|
|
148
|
+
parts,
|
|
149
|
+
uploadId,
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
}
|
|
39
153
|
}
|
|
40
154
|
const appBundleFilesService = new AppBundleFilesServiceImpl(http_client_1.default);
|
|
41
155
|
exports.default = appBundleFilesService;
|
package/dist/utils/file.js
CHANGED
|
@@ -1,27 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
-
if (mod && mod.__esModule) return mod;
|
|
20
|
-
var result = {};
|
|
21
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
-
__setModuleDefault(result, mod);
|
|
23
|
-
return result;
|
|
24
|
-
};
|
|
25
2
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
26
3
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
27
4
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
@@ -31,21 +8,25 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
31
8
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
32
9
|
});
|
|
33
10
|
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
34
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
35
15
|
exports.writeFile = exports.isDirectory = exports.fileExistsAtPath = exports.getFilesInDirectoryAndSubdirectories = void 0;
|
|
16
|
+
const fs_1 = __importDefault(require("fs"));
|
|
17
|
+
const mime_1 = __importDefault(require("mime"));
|
|
18
|
+
const path_1 = __importDefault(require("path"));
|
|
36
19
|
const getFilesInDirectoryAndSubdirectories = (path) => __awaiter(void 0, void 0, void 0, function* () {
|
|
37
|
-
const fs = yield Promise.resolve().then(() => __importStar(require('fs')));
|
|
38
|
-
const pathModule = yield Promise.resolve().then(() => __importStar(require('path')));
|
|
39
20
|
const files = [];
|
|
40
21
|
const walk = (directory) => __awaiter(void 0, void 0, void 0, function* () {
|
|
41
|
-
const dirEntries = yield
|
|
22
|
+
const dirEntries = yield fs_1.default.promises.readdir(directory, { withFileTypes: true }).catch(() => []);
|
|
42
23
|
for (const dirEntry of dirEntries) {
|
|
43
|
-
const fullPath =
|
|
24
|
+
const fullPath = path_1.default.join(directory, dirEntry.name);
|
|
44
25
|
if (dirEntry.isDirectory()) {
|
|
45
26
|
yield walk(fullPath);
|
|
46
27
|
}
|
|
47
28
|
else {
|
|
48
|
-
let pathToReplace =
|
|
29
|
+
let pathToReplace = path_1.default.normalize(path);
|
|
49
30
|
// Remove the leading './' from the path
|
|
50
31
|
if (pathToReplace.startsWith('./')) {
|
|
51
32
|
pathToReplace = pathToReplace.replace('./', '');
|
|
@@ -59,6 +40,7 @@ const getFilesInDirectoryAndSubdirectories = (path) => __awaiter(void 0, void 0,
|
|
|
59
40
|
}
|
|
60
41
|
files.push({
|
|
61
42
|
href,
|
|
43
|
+
mimeType: mime_1.default.getType(dirEntry.name) || 'application/octet-stream',
|
|
62
44
|
name: dirEntry.name,
|
|
63
45
|
path: fullPath,
|
|
64
46
|
});
|
|
@@ -70,27 +52,24 @@ const getFilesInDirectoryAndSubdirectories = (path) => __awaiter(void 0, void 0,
|
|
|
70
52
|
});
|
|
71
53
|
exports.getFilesInDirectoryAndSubdirectories = getFilesInDirectoryAndSubdirectories;
|
|
72
54
|
const fileExistsAtPath = (path) => __awaiter(void 0, void 0, void 0, function* () {
|
|
73
|
-
const fs = yield Promise.resolve().then(() => __importStar(require('fs')));
|
|
74
55
|
return new Promise((resolve) => {
|
|
75
|
-
|
|
56
|
+
fs_1.default.access(path, fs_1.default.constants.F_OK, (err) => {
|
|
76
57
|
resolve(!err);
|
|
77
58
|
});
|
|
78
59
|
});
|
|
79
60
|
});
|
|
80
61
|
exports.fileExistsAtPath = fileExistsAtPath;
|
|
81
62
|
const isDirectory = (path) => __awaiter(void 0, void 0, void 0, function* () {
|
|
82
|
-
const fs = yield Promise.resolve().then(() => __importStar(require('fs')));
|
|
83
63
|
return new Promise((resolve) => {
|
|
84
|
-
|
|
64
|
+
fs_1.default.lstat(path, (err, stats) => {
|
|
85
65
|
resolve(stats.isDirectory());
|
|
86
66
|
});
|
|
87
67
|
});
|
|
88
68
|
});
|
|
89
69
|
exports.isDirectory = isDirectory;
|
|
90
70
|
const writeFile = (path, data) => __awaiter(void 0, void 0, void 0, function* () {
|
|
91
|
-
const fs = yield Promise.resolve().then(() => __importStar(require('fs')));
|
|
92
71
|
return new Promise((resolve, reject) => {
|
|
93
|
-
|
|
72
|
+
fs_1.default.writeFile(path, data, (err) => {
|
|
94
73
|
if (err) {
|
|
95
74
|
reject(err);
|
|
96
75
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capawesome/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.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",
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
"citty": "0.1.6",
|
|
49
49
|
"consola": "3.3.0",
|
|
50
50
|
"form-data": "4.0.1",
|
|
51
|
+
"mime": "4.0.7",
|
|
51
52
|
"open": "8.4.2",
|
|
52
53
|
"rc9": "2.1.2",
|
|
53
54
|
"semver": "7.6.3",
|