@capawesome/cli 4.5.0 → 4.6.0-dev.0108a83.1774286472
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/builds/create.js +46 -4
- package/dist/commands/apps/certificates/create.js +3 -19
- package/dist/commands/apps/certificates/update.js +3 -1
- package/dist/commands/apps/devices/probe.js +70 -0
- package/dist/commands/apps/liveupdates/bundle.js +6 -1
- package/dist/commands/apps/liveupdates/generate-manifest.js +6 -1
- package/dist/commands/apps/liveupdates/upload.js +8 -1
- package/dist/index.js +1 -0
- package/dist/services/app-build-sources.js +112 -0
- package/dist/services/app-certificates.js +0 -1
- package/dist/services/app-devices.js +36 -0
- package/dist/types/app-build-source.js +1 -0
- package/dist/types/index.js +1 -0
- package/dist/utils/file.js +4 -0
- package/dist/utils/zip.js +19 -2
- 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
|
+
## [4.6.0](https://github.com/capawesome-team/cli/compare/v4.5.0...v4.6.0) (2026-03-18)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* add `apps:devices:probe` command ([#129](https://github.com/capawesome-team/cli/issues/129)) ([33607cd](https://github.com/capawesome-team/cli/commit/33607cdb2e65e26c48d114d694b876db3762b8ec))
|
|
11
|
+
|
|
5
12
|
## [4.5.0](https://github.com/capawesome-team/cli/compare/v4.4.0...v4.5.0) (2026-03-15)
|
|
6
13
|
|
|
7
14
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { DEFAULT_CONSOLE_BASE_URL } from '../../../config/consts.js';
|
|
2
|
+
import appBuildSourcesService from '../../../services/app-build-sources.js';
|
|
2
3
|
import appBuildsService from '../../../services/app-builds.js';
|
|
3
4
|
import appCertificatesService from '../../../services/app-certificates.js';
|
|
4
5
|
import appEnvironmentsService from '../../../services/app-environments.js';
|
|
@@ -7,6 +8,7 @@ import { withAuth } from '../../../utils/auth.js';
|
|
|
7
8
|
import { isInteractive } from '../../../utils/environment.js';
|
|
8
9
|
import { prompt, promptAppSelection, promptOrganizationSelection } from '../../../utils/prompt.js';
|
|
9
10
|
import { wait } from '../../../utils/wait.js';
|
|
11
|
+
import zip from '../../../utils/zip.js';
|
|
10
12
|
import { defineCommand, defineOptions } from '@robingenz/zli';
|
|
11
13
|
import consola from 'consola';
|
|
12
14
|
import fs from 'fs/promises';
|
|
@@ -45,6 +47,7 @@ export default defineCommand({
|
|
|
45
47
|
.optional()
|
|
46
48
|
.describe('Download the generated IPA file (iOS only). Optionally provide a file path.'),
|
|
47
49
|
json: z.boolean().optional().describe('Output in JSON format.'),
|
|
50
|
+
path: z.string().optional().describe('Path to local source files to upload.'),
|
|
48
51
|
platform: z
|
|
49
52
|
.enum(['ios', 'android', 'web'], {
|
|
50
53
|
message: 'Platform must be either `ios`, `android`, or `web`.',
|
|
@@ -68,7 +71,7 @@ export default defineCommand({
|
|
|
68
71
|
yes: z.boolean().optional().describe('Skip confirmation prompts.'),
|
|
69
72
|
}), { y: 'yes' }),
|
|
70
73
|
action: withAuth(async (options) => {
|
|
71
|
-
let { appId, platform, type, gitRef, environment, certificate, json, stack } = options;
|
|
74
|
+
let { appId, platform, type, gitRef, environment, certificate, json, stack, path: sourcePath } = options;
|
|
72
75
|
// Validate that detached flag cannot be used with artifact flags
|
|
73
76
|
if (options.detached && (options.apk || options.aab || options.ipa || options.zip)) {
|
|
74
77
|
consola.error('The --detached flag cannot be used with --apk, --aab, --ipa, or --zip flags.');
|
|
@@ -84,6 +87,26 @@ export default defineCommand({
|
|
|
84
87
|
consola.error('The --channel and --destination flags cannot be used together.');
|
|
85
88
|
process.exit(1);
|
|
86
89
|
}
|
|
90
|
+
// Validate that path and gitRef cannot be used together
|
|
91
|
+
if (sourcePath && gitRef) {
|
|
92
|
+
consola.error('The --path and --git-ref flags cannot be used together.');
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
// Validate path if provided
|
|
96
|
+
if (sourcePath) {
|
|
97
|
+
const resolvedPath = path.resolve(sourcePath);
|
|
98
|
+
const stat = await fs.stat(resolvedPath).catch(() => null);
|
|
99
|
+
if (!stat || !stat.isDirectory()) {
|
|
100
|
+
consola.error('The --path must point to an existing directory.');
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
const packageJsonPath = path.join(resolvedPath, 'package.json');
|
|
104
|
+
const packageJsonStat = await fs.stat(packageJsonPath).catch(() => null);
|
|
105
|
+
if (!packageJsonStat || !packageJsonStat.isFile()) {
|
|
106
|
+
consola.error('The directory specified by --path must contain a package.json file.');
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
87
110
|
// Prompt for app ID if not provided
|
|
88
111
|
if (!appId) {
|
|
89
112
|
if (!isInteractive()) {
|
|
@@ -113,10 +136,10 @@ export default defineCommand({
|
|
|
113
136
|
process.exit(1);
|
|
114
137
|
}
|
|
115
138
|
}
|
|
116
|
-
// Prompt for git ref if not provided
|
|
117
|
-
if (!gitRef) {
|
|
139
|
+
// Prompt for git ref if not provided and no path specified
|
|
140
|
+
if (!sourcePath && !gitRef) {
|
|
118
141
|
if (!isInteractive()) {
|
|
119
|
-
consola.error('You must provide a git ref when running in non-interactive environment.');
|
|
142
|
+
consola.error('You must provide a git ref or path when running in non-interactive environment.');
|
|
120
143
|
process.exit(1);
|
|
121
144
|
}
|
|
122
145
|
gitRef = await prompt('Enter the Git reference (branch, tag, or commit SHA):', {
|
|
@@ -197,9 +220,28 @@ export default defineCommand({
|
|
|
197
220
|
}
|
|
198
221
|
}
|
|
199
222
|
}
|
|
223
|
+
// Upload source files if path is provided
|
|
224
|
+
let appBuildSourceId;
|
|
225
|
+
if (sourcePath) {
|
|
226
|
+
const resolvedPath = path.resolve(sourcePath);
|
|
227
|
+
consola.start('Zipping source files...');
|
|
228
|
+
const buffer = await zip.zipFolderWithGitignore(resolvedPath);
|
|
229
|
+
consola.start('Uploading source files...');
|
|
230
|
+
const appBuildSource = await appBuildSourcesService.create({
|
|
231
|
+
appId,
|
|
232
|
+
fileSizeInBytes: buffer.byteLength,
|
|
233
|
+
buffer,
|
|
234
|
+
name: 'source.zip',
|
|
235
|
+
}, (currentPart, totalParts) => {
|
|
236
|
+
consola.start(`Uploading source files (${currentPart}/${totalParts})...`);
|
|
237
|
+
});
|
|
238
|
+
appBuildSourceId = appBuildSource.id;
|
|
239
|
+
consola.success('Source files uploaded successfully.');
|
|
240
|
+
}
|
|
200
241
|
// Create the app build
|
|
201
242
|
consola.start('Creating build...');
|
|
202
243
|
const response = await appBuildsService.create({
|
|
244
|
+
appBuildSourceId,
|
|
203
245
|
appCertificateName: certificate,
|
|
204
246
|
appEnvironmentName: environment,
|
|
205
247
|
appId,
|
|
@@ -74,24 +74,9 @@ export default defineCommand({
|
|
|
74
74
|
process.exit(1);
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
|
-
// 4.
|
|
78
|
-
if (
|
|
79
|
-
|
|
80
|
-
consola.error('You must provide the certificate type when running in non-interactive environment.');
|
|
81
|
-
process.exit(1);
|
|
82
|
-
}
|
|
83
|
-
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
84
|
-
type = await prompt('Select the certificate type:', {
|
|
85
|
-
type: 'select',
|
|
86
|
-
options: [
|
|
87
|
-
{ label: 'Development', value: 'development' },
|
|
88
|
-
{ label: 'Production', value: 'production' },
|
|
89
|
-
],
|
|
90
|
-
});
|
|
91
|
-
if (!type) {
|
|
92
|
-
consola.error('You must select a certificate type.');
|
|
93
|
-
process.exit(1);
|
|
94
|
-
}
|
|
77
|
+
// 4. Warn if deprecated --type option is used
|
|
78
|
+
if (type) {
|
|
79
|
+
consola.warn('The --type option is deprecated and will be removed in a future version. The certificate type is now detected automatically.');
|
|
95
80
|
}
|
|
96
81
|
// 5. Enter certificate file path
|
|
97
82
|
if (!file) {
|
|
@@ -167,7 +152,6 @@ export default defineCommand({
|
|
|
167
152
|
fileName,
|
|
168
153
|
name,
|
|
169
154
|
platform: platform,
|
|
170
|
-
type: type,
|
|
171
155
|
password,
|
|
172
156
|
keyAlias,
|
|
173
157
|
keyPassword,
|
|
@@ -36,11 +36,13 @@ export default defineCommand({
|
|
|
36
36
|
}
|
|
37
37
|
certificateId = await prompt('Enter the certificate ID:', { type: 'text' });
|
|
38
38
|
}
|
|
39
|
+
if (type) {
|
|
40
|
+
consola.warn('The --type option is deprecated and will be removed in a future version. The certificate type is now detected automatically.');
|
|
41
|
+
}
|
|
39
42
|
await appCertificatesService.update({
|
|
40
43
|
appId,
|
|
41
44
|
certificateId,
|
|
42
45
|
name,
|
|
43
|
-
type,
|
|
44
46
|
password,
|
|
45
47
|
keyAlias,
|
|
46
48
|
keyPassword,
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import appDevicesService from '../../../services/app-devices.js';
|
|
2
|
+
import { withAuth } from '../../../utils/auth.js';
|
|
3
|
+
import { isInteractive } from '../../../utils/environment.js';
|
|
4
|
+
import { prompt, promptAppSelection, promptOrganizationSelection } from '../../../utils/prompt.js';
|
|
5
|
+
import { defineCommand, defineOptions } from '@robingenz/zli';
|
|
6
|
+
import { AxiosError } from 'axios';
|
|
7
|
+
import consola from 'consola';
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
export default defineCommand({
|
|
10
|
+
description: 'Check whether a device would receive a live update.',
|
|
11
|
+
options: defineOptions(z.object({
|
|
12
|
+
appId: z.string().optional().describe('ID of the app.'),
|
|
13
|
+
deviceId: z.string().optional().describe('ID of the device.'),
|
|
14
|
+
json: z.boolean().optional().describe('Output in JSON format.'),
|
|
15
|
+
})),
|
|
16
|
+
action: withAuth(async (options) => {
|
|
17
|
+
let { appId, deviceId, json } = options;
|
|
18
|
+
if (!appId) {
|
|
19
|
+
if (!isInteractive()) {
|
|
20
|
+
consola.error('You must provide an app ID when running in non-interactive environment.');
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
const organizationId = await promptOrganizationSelection();
|
|
24
|
+
appId = await promptAppSelection(organizationId);
|
|
25
|
+
}
|
|
26
|
+
if (!deviceId) {
|
|
27
|
+
if (!isInteractive()) {
|
|
28
|
+
consola.error('You must provide the device ID when running in non-interactive environment.');
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
deviceId = await prompt('Enter the device ID:', {
|
|
32
|
+
type: 'text',
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
const device = await appDevicesService.findOneById({ appId, deviceId });
|
|
36
|
+
try {
|
|
37
|
+
const result = await appDevicesService.probe({
|
|
38
|
+
appId,
|
|
39
|
+
appVersionCode: device.appVersionCode,
|
|
40
|
+
appVersionName: device.appVersionName,
|
|
41
|
+
channelName: device.appChannel?.name,
|
|
42
|
+
customId: device.customId ?? undefined,
|
|
43
|
+
deviceId: device.id,
|
|
44
|
+
osVersion: device.osVersion,
|
|
45
|
+
platform: device.platform,
|
|
46
|
+
pluginVersion: device.pluginVersion,
|
|
47
|
+
});
|
|
48
|
+
if (json) {
|
|
49
|
+
console.log(JSON.stringify(result, null, 2));
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
console.table(result);
|
|
53
|
+
consola.success('Update available for this device.');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
if (error instanceof AxiosError && error.response?.status === 404) {
|
|
58
|
+
if (json) {
|
|
59
|
+
console.log(JSON.stringify({ bundleId: null }, null, 2));
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
consola.info('No update available for this device.');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}),
|
|
70
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isInteractive } from '../../../utils/environment.js';
|
|
2
|
-
import { fileExistsAtPath, isDirectory } from '../../../utils/file.js';
|
|
2
|
+
import { directoryContainsSourceMaps, fileExistsAtPath, isDirectory } from '../../../utils/file.js';
|
|
3
3
|
import { generateManifestJson } from '../../../utils/manifest.js';
|
|
4
4
|
import { prompt } from '../../../utils/prompt.js';
|
|
5
5
|
import zip from '../../../utils/zip.js';
|
|
@@ -62,6 +62,11 @@ export default defineCommand({
|
|
|
62
62
|
consola.error(`Directory must contain an index.html file: ${inputPath}`);
|
|
63
63
|
process.exit(1);
|
|
64
64
|
}
|
|
65
|
+
// Check for source maps
|
|
66
|
+
const containsSourceMaps = await directoryContainsSourceMaps(inputPath);
|
|
67
|
+
if (containsSourceMaps) {
|
|
68
|
+
consola.warn('Source map files were detected in the specified path. Source maps should not be distributed to end users as they expose your original source code and increase the download size. Consider excluding source map files from your build output.');
|
|
69
|
+
}
|
|
65
70
|
// 2. Output path resolution
|
|
66
71
|
if (!outputPath) {
|
|
67
72
|
outputPath = './bundle.zip';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isInteractive } from '../../../utils/environment.js';
|
|
2
|
-
import { fileExistsAtPath } from '../../../utils/file.js';
|
|
2
|
+
import { directoryContainsSourceMaps, fileExistsAtPath } from '../../../utils/file.js';
|
|
3
3
|
import { generateManifestJson } from '../../../utils/manifest.js';
|
|
4
4
|
import { prompt } from '../../../utils/prompt.js';
|
|
5
5
|
import { defineCommand, defineOptions } from '@robingenz/zli';
|
|
@@ -32,6 +32,11 @@ export default defineCommand({
|
|
|
32
32
|
consola.error(`The path does not exist.`);
|
|
33
33
|
process.exit(1);
|
|
34
34
|
}
|
|
35
|
+
// Check for source maps
|
|
36
|
+
const containsSourceMaps = await directoryContainsSourceMaps(path);
|
|
37
|
+
if (containsSourceMaps) {
|
|
38
|
+
consola.warn('Source map files were detected in the specified path. Source maps should not be distributed to end users as they expose your original source code and increase the download size. Consider excluding source map files from your build output.');
|
|
39
|
+
}
|
|
35
40
|
// Generate the manifest file
|
|
36
41
|
await generateManifestJson(path);
|
|
37
42
|
consola.success('Manifest file generated.');
|
|
@@ -5,7 +5,7 @@ import appsService from '../../../services/apps.js';
|
|
|
5
5
|
import { withAuth } from '../../../utils/auth.js';
|
|
6
6
|
import { createBufferFromPath, createBufferFromReadStream, createBufferFromString, isPrivateKeyContent, } from '../../../utils/buffer.js';
|
|
7
7
|
import { isInteractive } from '../../../utils/environment.js';
|
|
8
|
-
import { fileExistsAtPath, getFilesInDirectoryAndSubdirectories, isDirectory } from '../../../utils/file.js';
|
|
8
|
+
import { directoryContainsSourceMaps, fileExistsAtPath, getFilesInDirectoryAndSubdirectories, isDirectory, } from '../../../utils/file.js';
|
|
9
9
|
import { createHash } from '../../../utils/hash.js';
|
|
10
10
|
import { generateManifestJson } from '../../../utils/manifest.js';
|
|
11
11
|
import { formatPrivateKey } from '../../../utils/private-key.js';
|
|
@@ -156,6 +156,13 @@ export default defineCommand({
|
|
|
156
156
|
consola.error('The path must be either a folder or a zip file.');
|
|
157
157
|
process.exit(1);
|
|
158
158
|
}
|
|
159
|
+
// Check for source maps
|
|
160
|
+
if (pathIsDirectory) {
|
|
161
|
+
const containsSourceMaps = await directoryContainsSourceMaps(path);
|
|
162
|
+
if (containsSourceMaps) {
|
|
163
|
+
consola.warn('Source map files were detected in the specified path. Source maps should not be distributed to end users as they expose your original source code and increase the download size. Consider excluding source map files from your build output.');
|
|
164
|
+
}
|
|
165
|
+
}
|
|
159
166
|
// Check that the path is a directory when creating a bundle with an artifact type of manifest
|
|
160
167
|
if (artifactType === 'manifest') {
|
|
161
168
|
const pathIsDirectory = await isDirectory(path);
|
package/dist/index.js
CHANGED
|
@@ -52,6 +52,7 @@ const config = defineConfig({
|
|
|
52
52
|
'apps:destinations:update': await import('./commands/apps/destinations/update.js').then((mod) => mod.default),
|
|
53
53
|
'apps:devices:delete': await import('./commands/apps/devices/delete.js').then((mod) => mod.default),
|
|
54
54
|
'apps:devices:forcechannel': await import('./commands/apps/devices/forcechannel.js').then((mod) => mod.default),
|
|
55
|
+
'apps:devices:probe': await import('./commands/apps/devices/probe.js').then((mod) => mod.default),
|
|
55
56
|
'apps:devices:unforcechannel': await import('./commands/apps/devices/unforcechannel.js').then((mod) => mod.default),
|
|
56
57
|
'apps:environments:create': await import('./commands/apps/environments/create.js').then((mod) => mod.default),
|
|
57
58
|
'apps:environments:delete': await import('./commands/apps/environments/delete.js').then((mod) => mod.default),
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { MAX_CONCURRENT_PART_UPLOADS } from '../config/index.js';
|
|
2
|
+
import authorizationService from '../services/authorization-service.js';
|
|
3
|
+
import httpClient from '../utils/http-client.js';
|
|
4
|
+
import FormData from 'form-data';
|
|
5
|
+
class AppBuildSourcesServiceImpl {
|
|
6
|
+
httpClient;
|
|
7
|
+
constructor(httpClient) {
|
|
8
|
+
this.httpClient = httpClient;
|
|
9
|
+
}
|
|
10
|
+
async create(dto, onProgress) {
|
|
11
|
+
const response = await this.httpClient.post(`/v1/apps/${dto.appId}/build-sources`, { fileSizeInBytes: dto.fileSizeInBytes }, {
|
|
12
|
+
headers: {
|
|
13
|
+
Authorization: `Bearer ${authorizationService.getCurrentAuthorizationToken()}`,
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
await this.upload({
|
|
17
|
+
appBuildSourceId: response.data.id,
|
|
18
|
+
appId: dto.appId,
|
|
19
|
+
buffer: dto.buffer,
|
|
20
|
+
name: dto.name,
|
|
21
|
+
}, onProgress);
|
|
22
|
+
return response.data;
|
|
23
|
+
}
|
|
24
|
+
async completeUpload(dto) {
|
|
25
|
+
return this.httpClient
|
|
26
|
+
.post(`/v1/apps/${dto.appId}/build-sources/${dto.appBuildSourceId}/upload?action=mpu-complete&uploadId=${dto.uploadId}`, {
|
|
27
|
+
parts: dto.parts,
|
|
28
|
+
}, {
|
|
29
|
+
headers: {
|
|
30
|
+
Authorization: `Bearer ${authorizationService.getCurrentAuthorizationToken()}`,
|
|
31
|
+
},
|
|
32
|
+
})
|
|
33
|
+
.then((response) => response.data);
|
|
34
|
+
}
|
|
35
|
+
async createUpload(dto) {
|
|
36
|
+
const response = await this.httpClient.post(`/v1/apps/${dto.appId}/build-sources/${dto.appBuildSourceId}/upload?action=mpu-create`, {}, {
|
|
37
|
+
headers: {
|
|
38
|
+
Authorization: `Bearer ${authorizationService.getCurrentAuthorizationToken()}`,
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
return response.data;
|
|
42
|
+
}
|
|
43
|
+
async createUploadPart(dto) {
|
|
44
|
+
const formData = new FormData();
|
|
45
|
+
formData.append('blob', dto.buffer, { filename: dto.name });
|
|
46
|
+
formData.append('partNumber', dto.partNumber.toString());
|
|
47
|
+
return this.httpClient
|
|
48
|
+
.put(`/v1/apps/${dto.appId}/build-sources/${dto.appBuildSourceId}/upload?action=mpu-uploadpart&uploadId=${dto.uploadId}`, formData, {
|
|
49
|
+
headers: {
|
|
50
|
+
Authorization: `Bearer ${authorizationService.getCurrentAuthorizationToken()}`,
|
|
51
|
+
...formData.getHeaders(),
|
|
52
|
+
},
|
|
53
|
+
})
|
|
54
|
+
.then((response) => response.data);
|
|
55
|
+
}
|
|
56
|
+
async createUploadParts(dto, onProgress) {
|
|
57
|
+
const uploadedParts = [];
|
|
58
|
+
const partSize = 10 * 1024 * 1024; // 10 MB
|
|
59
|
+
const totalParts = Math.ceil(dto.buffer.byteLength / partSize);
|
|
60
|
+
let partNumber = 0;
|
|
61
|
+
const uploadNextPart = async () => {
|
|
62
|
+
if (partNumber >= totalParts) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
partNumber++;
|
|
66
|
+
onProgress?.(partNumber, totalParts);
|
|
67
|
+
const start = (partNumber - 1) * partSize;
|
|
68
|
+
const end = Math.min(start + partSize, dto.buffer.byteLength);
|
|
69
|
+
const partBuffer = dto.buffer.subarray(start, end);
|
|
70
|
+
const uploadedPart = await this.createUploadPart({
|
|
71
|
+
appBuildSourceId: dto.appBuildSourceId,
|
|
72
|
+
appId: dto.appId,
|
|
73
|
+
buffer: partBuffer,
|
|
74
|
+
name: dto.name,
|
|
75
|
+
partNumber,
|
|
76
|
+
uploadId: dto.uploadId,
|
|
77
|
+
});
|
|
78
|
+
uploadedParts.push(uploadedPart);
|
|
79
|
+
await uploadNextPart();
|
|
80
|
+
};
|
|
81
|
+
const uploadPartPromises = Array.from({ length: MAX_CONCURRENT_PART_UPLOADS });
|
|
82
|
+
for (let i = 0; i < MAX_CONCURRENT_PART_UPLOADS; i++) {
|
|
83
|
+
uploadPartPromises[i] = uploadNextPart();
|
|
84
|
+
}
|
|
85
|
+
await Promise.all(uploadPartPromises);
|
|
86
|
+
return uploadedParts.sort((a, b) => a.partNumber - b.partNumber);
|
|
87
|
+
}
|
|
88
|
+
async upload(dto, onProgress) {
|
|
89
|
+
// 1. Create a multipart upload
|
|
90
|
+
const { uploadId } = await this.createUpload({
|
|
91
|
+
appBuildSourceId: dto.appBuildSourceId,
|
|
92
|
+
appId: dto.appId,
|
|
93
|
+
});
|
|
94
|
+
// 2. Upload the file in parts
|
|
95
|
+
const parts = await this.createUploadParts({
|
|
96
|
+
appBuildSourceId: dto.appBuildSourceId,
|
|
97
|
+
appId: dto.appId,
|
|
98
|
+
buffer: dto.buffer,
|
|
99
|
+
name: dto.name,
|
|
100
|
+
uploadId,
|
|
101
|
+
}, onProgress);
|
|
102
|
+
// 3. Complete the upload
|
|
103
|
+
await this.completeUpload({
|
|
104
|
+
appBuildSourceId: dto.appBuildSourceId,
|
|
105
|
+
appId: dto.appId,
|
|
106
|
+
parts,
|
|
107
|
+
uploadId,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const appBuildSourcesService = new AppBuildSourcesServiceImpl(httpClient);
|
|
112
|
+
export default appBuildSourcesService;
|
|
@@ -11,7 +11,6 @@ class AppCertificatesServiceImpl {
|
|
|
11
11
|
formData.append('file', dto.buffer, { filename: dto.fileName });
|
|
12
12
|
formData.append('name', dto.name);
|
|
13
13
|
formData.append('platform', dto.platform);
|
|
14
|
-
formData.append('type', dto.type);
|
|
15
14
|
if (dto.password) {
|
|
16
15
|
formData.append('password', dto.password);
|
|
17
16
|
}
|
|
@@ -12,6 +12,42 @@ class AppDevicesServiceImpl {
|
|
|
12
12
|
},
|
|
13
13
|
});
|
|
14
14
|
}
|
|
15
|
+
async findOneById(data) {
|
|
16
|
+
const response = await this.httpClient.get(`/v1/apps/${data.appId}/devices/${data.deviceId}`, {
|
|
17
|
+
headers: {
|
|
18
|
+
Authorization: `Bearer ${authorizationService.getCurrentAuthorizationToken()}`,
|
|
19
|
+
},
|
|
20
|
+
params: {
|
|
21
|
+
relations: 'appChannel',
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
return response.data;
|
|
25
|
+
}
|
|
26
|
+
async probe(data) {
|
|
27
|
+
const params = {
|
|
28
|
+
appVersionCode: data.appVersionCode,
|
|
29
|
+
appVersionName: data.appVersionName,
|
|
30
|
+
osVersion: data.osVersion,
|
|
31
|
+
platform: data.platform.toString(),
|
|
32
|
+
pluginVersion: data.pluginVersion,
|
|
33
|
+
};
|
|
34
|
+
if (data.channelName) {
|
|
35
|
+
params.channelName = data.channelName;
|
|
36
|
+
}
|
|
37
|
+
if (data.customId) {
|
|
38
|
+
params.customId = data.customId;
|
|
39
|
+
}
|
|
40
|
+
if (data.deviceId) {
|
|
41
|
+
params.deviceId = data.deviceId;
|
|
42
|
+
}
|
|
43
|
+
const response = await this.httpClient.get(`/v1/apps/${data.appId}/bundles/latest`, {
|
|
44
|
+
headers: {
|
|
45
|
+
Authorization: `Bearer ${authorizationService.getCurrentAuthorizationToken()}`,
|
|
46
|
+
},
|
|
47
|
+
params,
|
|
48
|
+
});
|
|
49
|
+
return response.data;
|
|
50
|
+
}
|
|
15
51
|
async update(data) {
|
|
16
52
|
await this.httpClient.patch(`/v1/apps/${data.appId}/devices/${data.deviceId}`, { forcedAppChannelId: data.forcedAppChannelId }, {
|
|
17
53
|
headers: {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/types/index.js
CHANGED
package/dist/utils/file.js
CHANGED
|
@@ -35,6 +35,10 @@ export const getFilesInDirectoryAndSubdirectories = async (path) => {
|
|
|
35
35
|
await walk(path);
|
|
36
36
|
return files;
|
|
37
37
|
};
|
|
38
|
+
export const directoryContainsSourceMaps = async (path) => {
|
|
39
|
+
const files = await getFilesInDirectoryAndSubdirectories(path);
|
|
40
|
+
return files.some((file) => file.name.endsWith('.js.map') || file.name.endsWith('.css.map'));
|
|
41
|
+
};
|
|
38
42
|
export const fileExistsAtPath = async (path) => {
|
|
39
43
|
return new Promise((resolve) => {
|
|
40
44
|
fs.access(path, fs.constants.F_OK, (err) => {
|
package/dist/utils/zip.js
CHANGED
|
@@ -1,12 +1,29 @@
|
|
|
1
1
|
import AdmZip from 'adm-zip';
|
|
2
|
+
import { globby } from 'globby';
|
|
3
|
+
import path from 'path';
|
|
2
4
|
class ZipImpl {
|
|
3
5
|
async zipFolder(sourceFolder) {
|
|
4
6
|
const zip = new AdmZip();
|
|
5
7
|
zip.addLocalFolder(sourceFolder);
|
|
6
8
|
return zip.toBuffer();
|
|
7
9
|
}
|
|
8
|
-
|
|
9
|
-
|
|
10
|
+
async zipFolderWithGitignore(sourceFolder) {
|
|
11
|
+
const files = await globby(['**/*'], {
|
|
12
|
+
cwd: sourceFolder,
|
|
13
|
+
gitignore: true,
|
|
14
|
+
ignore: ['.git/**'],
|
|
15
|
+
dot: true,
|
|
16
|
+
});
|
|
17
|
+
const zip = new AdmZip();
|
|
18
|
+
for (const file of files) {
|
|
19
|
+
const filePath = path.join(sourceFolder, file);
|
|
20
|
+
const dirName = path.dirname(file);
|
|
21
|
+
zip.addLocalFile(filePath, dirName === '.' ? '' : dirName);
|
|
22
|
+
}
|
|
23
|
+
return zip.toBuffer();
|
|
24
|
+
}
|
|
25
|
+
isZipped(filePath) {
|
|
26
|
+
return filePath.endsWith('.zip');
|
|
10
27
|
}
|
|
11
28
|
}
|
|
12
29
|
const zip = new ZipImpl();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capawesome/cli",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.6.0-dev.0108a83.1774286472",
|
|
4
4
|
"description": "The Capawesome Cloud Command Line Interface (CLI) to manage Live Updates and more.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -61,6 +61,7 @@
|
|
|
61
61
|
"c12": "3.3.3",
|
|
62
62
|
"consola": "3.3.0",
|
|
63
63
|
"form-data": "4.0.4",
|
|
64
|
+
"globby": "16.1.1",
|
|
64
65
|
"http-proxy-agent": "7.0.2",
|
|
65
66
|
"https-proxy-agent": "7.0.6",
|
|
66
67
|
"mime": "4.0.7",
|