@capawesome/cli 1.13.2 → 1.14.0-dev.8c382fa.1755584275
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/README.md +3 -3
- package/dist/commands/apps/bundles/create.js +206 -232
- package/dist/commands/apps/bundles/delete.js +42 -47
- package/dist/commands/apps/bundles/update.js +69 -72
- package/dist/commands/apps/channels/create.js +47 -70
- package/dist/commands/apps/channels/delete.js +53 -56
- package/dist/commands/apps/channels/get.js +27 -61
- package/dist/commands/apps/channels/list.js +26 -63
- package/dist/commands/apps/channels/update.js +48 -67
- package/dist/commands/apps/create.js +38 -38
- package/dist/commands/apps/delete.js +39 -40
- package/dist/commands/apps/devices/delete.js +42 -47
- package/dist/commands/doctor.js +12 -29
- package/dist/commands/login.js +48 -69
- package/dist/commands/logout.js +13 -31
- package/dist/commands/manifests/generate.js +20 -38
- package/dist/commands/whoami.js +13 -30
- package/dist/config/consts.js +2 -5
- package/dist/config/index.js +1 -17
- package/dist/index.js +50 -80
- package/dist/services/app-bundle-files.js +117 -136
- package/dist/services/app-bundles.js +22 -41
- package/dist/services/app-channels.js +54 -77
- package/dist/services/app-devices.js +10 -25
- package/dist/services/apps.js +24 -41
- package/dist/services/authorization-service.js +4 -8
- package/dist/services/config.js +14 -28
- package/dist/services/organizations.js +18 -0
- package/dist/services/session-code.js +7 -22
- package/dist/services/sessions.js +13 -30
- package/dist/services/update.js +17 -55
- package/dist/services/users.js +11 -26
- package/dist/types/app-bundle-file.js +1 -2
- package/dist/types/app-bundle.js +1 -2
- package/dist/types/app-channel.js +1 -2
- package/dist/types/app-device.js +1 -2
- package/dist/types/app.js +1 -2
- package/dist/types/index.js +8 -23
- package/dist/types/npm-package.js +1 -2
- package/dist/types/organization.js +1 -0
- package/dist/types/session-code.js +1 -2
- package/dist/types/session.js +1 -2
- package/dist/types/user.js +1 -2
- package/dist/utils/buffer.js +6 -43
- package/dist/utils/error.js +24 -14
- package/dist/utils/file.js +22 -41
- package/dist/utils/hash.js +3 -39
- package/dist/utils/http-client.js +27 -53
- package/dist/utils/manifest.js +11 -24
- package/dist/utils/prompt.js +9 -26
- package/dist/utils/signature.js +3 -39
- package/dist/utils/userConfig.js +5 -9
- package/dist/utils/zip.js +11 -27
- package/package.json +15 -8
- package/dist/utils/ci.js +0 -7
|
@@ -1,216 +1,187 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
privateKey
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
rollout: {
|
|
93
|
-
type: 'string',
|
|
94
|
-
description: 'The percentage of devices to deploy the bundle to. Must be a number between 0 and 1 (e.g. 0.5).',
|
|
95
|
-
},
|
|
96
|
-
url: {
|
|
97
|
-
type: 'string',
|
|
98
|
-
description: 'The url to the self-hosted bundle file.',
|
|
99
|
-
},
|
|
100
|
-
},
|
|
101
|
-
run: (ctx) => __awaiter(void 0, void 0, void 0, function* () {
|
|
102
|
-
if (!authorization_service_1.default.hasAuthorizationToken()) {
|
|
103
|
-
consola_1.default.error('You must be logged in to run this command.');
|
|
1
|
+
import { defineCommand, defineOptions } from '@robingenz/zli';
|
|
2
|
+
import consola from 'consola';
|
|
3
|
+
import { createReadStream } from 'fs';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { MAX_CONCURRENT_UPLOADS } from '../../../config/index.js';
|
|
6
|
+
import appBundleFilesService from '../../../services/app-bundle-files.js';
|
|
7
|
+
import appBundlesService from '../../../services/app-bundles.js';
|
|
8
|
+
import appsService from '../../../services/apps.js';
|
|
9
|
+
import authorizationService from '../../../services/authorization-service.js';
|
|
10
|
+
import organizationsService from '../../../services/organizations.js';
|
|
11
|
+
import { createBufferFromPath, createBufferFromReadStream } from '../../../utils/buffer.js';
|
|
12
|
+
import { getMessageFromUnknownError } from '../../../utils/error.js';
|
|
13
|
+
import { fileExistsAtPath, getFilesInDirectoryAndSubdirectories, isDirectory } from '../../../utils/file.js';
|
|
14
|
+
import { createHash } from '../../../utils/hash.js';
|
|
15
|
+
import { generateManifestJson } from '../../../utils/manifest.js';
|
|
16
|
+
import { prompt } from '../../../utils/prompt.js';
|
|
17
|
+
import { createSignature } from '../../../utils/signature.js';
|
|
18
|
+
import zip from '../../../utils/zip.js';
|
|
19
|
+
export default defineCommand({
|
|
20
|
+
description: 'Create a new app bundle.',
|
|
21
|
+
options: defineOptions(z.object({
|
|
22
|
+
androidMax: z.coerce
|
|
23
|
+
.string()
|
|
24
|
+
.optional()
|
|
25
|
+
.describe('The maximum Android version code (`versionCode`) that the bundle supports.'),
|
|
26
|
+
androidMin: z.coerce
|
|
27
|
+
.string()
|
|
28
|
+
.optional()
|
|
29
|
+
.describe('The minimum Android version code (`versionCode`) that the bundle supports.'),
|
|
30
|
+
appId: z
|
|
31
|
+
.string({
|
|
32
|
+
message: 'App ID must be a UUID.',
|
|
33
|
+
})
|
|
34
|
+
.uuid({
|
|
35
|
+
message: 'App ID must be a UUID.',
|
|
36
|
+
})
|
|
37
|
+
.optional()
|
|
38
|
+
.describe('App ID to deploy to.'),
|
|
39
|
+
artifactType: z
|
|
40
|
+
.enum(['manifest', 'zip'], {
|
|
41
|
+
message: 'Invalid artifact type. Must be either `manifest` or `zip`.',
|
|
42
|
+
})
|
|
43
|
+
.optional()
|
|
44
|
+
.describe('The type of artifact to deploy. Must be either `manifest` or `zip`. The default is `zip`.')
|
|
45
|
+
.default('zip'),
|
|
46
|
+
channel: z.string().optional().describe('Channel to associate the bundle with.'),
|
|
47
|
+
commitMessage: z.string().optional().describe('The commit message related to the bundle.'),
|
|
48
|
+
commitRef: z.string().optional().describe('The commit ref related to the bundle.'),
|
|
49
|
+
commitSha: z.string().optional().describe('The commit sha related to the bundle.'),
|
|
50
|
+
customProperty: z
|
|
51
|
+
.array(z.string().min(1).max(100))
|
|
52
|
+
.optional()
|
|
53
|
+
.describe('A custom property to assign to the bundle. Must be in the format `key=value`. Can be specified multiple times.'),
|
|
54
|
+
expiresInDays: z.coerce
|
|
55
|
+
.number({
|
|
56
|
+
message: 'Expiration days must be an integer.',
|
|
57
|
+
})
|
|
58
|
+
.int({
|
|
59
|
+
message: 'Expiration days must be an integer.',
|
|
60
|
+
})
|
|
61
|
+
.optional()
|
|
62
|
+
.describe('The number of days until the bundle is automatically deleted.'),
|
|
63
|
+
iosMax: z
|
|
64
|
+
.string()
|
|
65
|
+
.optional()
|
|
66
|
+
.describe('The maximum iOS bundle version (`CFBundleVersion`) that the bundle supports.'),
|
|
67
|
+
iosMin: z
|
|
68
|
+
.string()
|
|
69
|
+
.optional()
|
|
70
|
+
.describe('The minimum iOS bundle version (`CFBundleVersion`) that the bundle supports.'),
|
|
71
|
+
path: z
|
|
72
|
+
.string()
|
|
73
|
+
.optional()
|
|
74
|
+
.describe('Path to the bundle to upload. Must be a folder (e.g. `www` or `dist`) or a zip file.'),
|
|
75
|
+
privateKey: z.string().optional().describe('The path to the private key file to sign the bundle with.'),
|
|
76
|
+
rollout: z.coerce
|
|
77
|
+
.number()
|
|
78
|
+
.min(0)
|
|
79
|
+
.max(1, {
|
|
80
|
+
message: 'Rollout percentage must be a number between 0 and 1 (e.g. 0.5).',
|
|
81
|
+
})
|
|
82
|
+
.optional()
|
|
83
|
+
.default(1)
|
|
84
|
+
.describe('The percentage of devices to deploy the bundle to. Must be a number between 0 and 1 (e.g. 0.5).'),
|
|
85
|
+
url: z.string().optional().describe('The url to the self-hosted bundle file.'),
|
|
86
|
+
})),
|
|
87
|
+
action: async (options, args) => {
|
|
88
|
+
let { androidMax, androidMin, appId, artifactType, channel, commitMessage, commitRef, commitSha, customProperty, expiresInDays, iosMax, iosMin, path, privateKey, rollout, url, } = options;
|
|
89
|
+
// Check if the user is logged in
|
|
90
|
+
if (!authorizationService.hasAuthorizationToken()) {
|
|
91
|
+
consola.error('You must be logged in to run this command.');
|
|
104
92
|
process.exit(1);
|
|
105
93
|
}
|
|
106
|
-
let androidMax = ctx.args.androidMax === undefined ? undefined : ctx.args.androidMax + ''; // Convert to string
|
|
107
|
-
let androidMin = ctx.args.androidMin === undefined ? undefined : ctx.args.androidMin + ''; // Convert to string
|
|
108
|
-
let appId = ctx.args.appId;
|
|
109
|
-
let artifactType = ctx.args.artifactType === 'manifest' || ctx.args.artifactType === 'zip'
|
|
110
|
-
? ctx.args.artifactType
|
|
111
|
-
: 'zip';
|
|
112
|
-
let channelName = ctx.args.channel;
|
|
113
|
-
let customProperty = ctx.args.customProperty;
|
|
114
|
-
let expiresInDays = ctx.args.expiresInDays === undefined ? undefined : ctx.args.expiresInDays + ''; // Convert to string
|
|
115
|
-
let iosMax = ctx.args.iosMax === undefined ? undefined : ctx.args.iosMax + ''; // Convert to string
|
|
116
|
-
let iosMin = ctx.args.iosMin === undefined ? undefined : ctx.args.iosMin + ''; // Convert to string
|
|
117
|
-
let path = ctx.args.path;
|
|
118
|
-
let privateKey = ctx.args.privateKey;
|
|
119
|
-
let rolloutAsString = ctx.args.rollout === undefined ? undefined : ctx.args.rollout + ''; // Convert to string
|
|
120
|
-
let url = ctx.args.url;
|
|
121
|
-
let commitMessage = ctx.args.commitMessage;
|
|
122
|
-
let commitRef = ctx.args.commitRef;
|
|
123
|
-
let commitSha = ctx.args.commitSha;
|
|
124
94
|
// Validate the expiration days
|
|
125
95
|
let expiresAt;
|
|
126
96
|
if (expiresInDays) {
|
|
127
|
-
const expiresInDaysAsNumber = parseInt(expiresInDays, 10);
|
|
128
|
-
if (isNaN(expiresInDaysAsNumber) || expiresInDaysAsNumber < 1) {
|
|
129
|
-
consola_1.default.error('Expires in days must be a number greater than 0.');
|
|
130
|
-
process.exit(1);
|
|
131
|
-
}
|
|
132
97
|
const expiresAtDate = new Date();
|
|
133
|
-
expiresAtDate.setDate(expiresAtDate.getDate() +
|
|
98
|
+
expiresAtDate.setDate(expiresAtDate.getDate() + expiresInDays);
|
|
134
99
|
expiresAt = expiresAtDate.toISOString();
|
|
135
100
|
}
|
|
136
|
-
// Validate the rollout percentage
|
|
137
|
-
let rolloutPercentage = 1;
|
|
138
|
-
if (rolloutAsString) {
|
|
139
|
-
const rolloutAsNumber = parseFloat(rolloutAsString);
|
|
140
|
-
if (isNaN(rolloutAsNumber) || rolloutAsNumber < 0 || rolloutAsNumber > 1) {
|
|
141
|
-
consola_1.default.error('Rollout percentage must be a number between 0 and 1.');
|
|
142
|
-
process.exit(1);
|
|
143
|
-
}
|
|
144
|
-
rolloutPercentage = rolloutAsNumber;
|
|
145
|
-
}
|
|
146
101
|
// Check that either a path or a url is provided
|
|
147
102
|
if (!path && !url) {
|
|
148
|
-
path =
|
|
103
|
+
path = await prompt('Enter the path to the app bundle:', {
|
|
149
104
|
type: 'text',
|
|
150
105
|
});
|
|
151
106
|
if (!path) {
|
|
152
|
-
|
|
107
|
+
consola.error('You must provide a path to the app bundle.');
|
|
153
108
|
process.exit(1);
|
|
154
109
|
}
|
|
155
110
|
}
|
|
156
111
|
if (path) {
|
|
157
112
|
// Check if the path exists when a path is provided
|
|
158
|
-
const pathExists =
|
|
113
|
+
const pathExists = await fileExistsAtPath(path);
|
|
159
114
|
if (!pathExists) {
|
|
160
|
-
|
|
115
|
+
consola.error(`The path does not exist.`);
|
|
161
116
|
process.exit(1);
|
|
162
117
|
}
|
|
163
118
|
// Check if the directory contains an index.html file
|
|
164
|
-
const pathIsDirectory =
|
|
119
|
+
const pathIsDirectory = await isDirectory(path);
|
|
165
120
|
if (pathIsDirectory) {
|
|
166
|
-
const files =
|
|
121
|
+
const files = await getFilesInDirectoryAndSubdirectories(path);
|
|
167
122
|
const indexHtml = files.find((file) => file.href === 'index.html');
|
|
168
123
|
if (!indexHtml) {
|
|
169
|
-
|
|
124
|
+
consola.error('The directory must contain an `index.html` file.');
|
|
170
125
|
process.exit(1);
|
|
171
126
|
}
|
|
172
127
|
}
|
|
173
128
|
}
|
|
174
129
|
// Check that the path is a directory when creating a bundle with an artifact type
|
|
175
130
|
if (artifactType === 'manifest' && path) {
|
|
176
|
-
const pathIsDirectory =
|
|
131
|
+
const pathIsDirectory = await isDirectory(path);
|
|
177
132
|
if (!pathIsDirectory) {
|
|
178
|
-
|
|
133
|
+
consola.error('The path must be a folder when creating a bundle with an artifact type of `manifest`.');
|
|
179
134
|
process.exit(1);
|
|
180
135
|
}
|
|
181
136
|
}
|
|
182
137
|
// Check that a URL is not provided when creating a bundle with an artifact type of manifest
|
|
183
138
|
if (artifactType === 'manifest' && url) {
|
|
184
|
-
|
|
139
|
+
consola.error('It is not yet possible to provide a URL when creating a bundle with an artifact type of `manifest`.');
|
|
185
140
|
process.exit(1);
|
|
186
141
|
}
|
|
187
142
|
if (!appId) {
|
|
188
|
-
const
|
|
143
|
+
const organizations = await organizationsService.findAll();
|
|
144
|
+
if (organizations.length === 0) {
|
|
145
|
+
consola.error('You must create an organization before creating a bundle.');
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
149
|
+
const organizationId = await prompt('Select the organization of the app for which you want to create a bundle.', {
|
|
150
|
+
type: 'select',
|
|
151
|
+
options: organizations.map((organization) => ({ label: organization.name, value: organization.id })),
|
|
152
|
+
});
|
|
153
|
+
if (!organizationId) {
|
|
154
|
+
consola.error('You must select the organization of an app for which you want to create a bundle.');
|
|
155
|
+
process.exit(1);
|
|
156
|
+
}
|
|
157
|
+
const apps = await appsService.findAll({
|
|
158
|
+
organizationId,
|
|
159
|
+
});
|
|
189
160
|
if (apps.length === 0) {
|
|
190
|
-
|
|
161
|
+
consola.error('You must create an app before creating a bundle.');
|
|
191
162
|
process.exit(1);
|
|
192
163
|
}
|
|
193
164
|
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
194
|
-
appId =
|
|
165
|
+
appId = await prompt('Which app do you want to deploy to:', {
|
|
195
166
|
type: 'select',
|
|
196
167
|
options: apps.map((app) => ({ label: app.name, value: app.id })),
|
|
197
168
|
});
|
|
198
169
|
if (!appId) {
|
|
199
|
-
|
|
170
|
+
consola.error('You must select an app to deploy to.');
|
|
200
171
|
process.exit(1);
|
|
201
172
|
}
|
|
202
173
|
}
|
|
203
|
-
if (!
|
|
204
|
-
const promptChannel =
|
|
174
|
+
if (!channel) {
|
|
175
|
+
const promptChannel = await prompt('Do you want to deploy to a specific channel?', {
|
|
205
176
|
type: 'select',
|
|
206
177
|
options: ['Yes', 'No'],
|
|
207
178
|
});
|
|
208
179
|
if (promptChannel === 'Yes') {
|
|
209
|
-
|
|
180
|
+
channel = await prompt('Enter the channel name:', {
|
|
210
181
|
type: 'text',
|
|
211
182
|
});
|
|
212
|
-
if (!
|
|
213
|
-
|
|
183
|
+
if (!channel) {
|
|
184
|
+
consola.error('The channel name must be at least one character long.');
|
|
214
185
|
process.exit(1);
|
|
215
186
|
}
|
|
216
187
|
}
|
|
@@ -219,56 +190,56 @@ exports.default = (0, citty_1.defineCommand)({
|
|
|
219
190
|
let privateKeyBuffer;
|
|
220
191
|
if (privateKey) {
|
|
221
192
|
if (privateKey.endsWith('.pem')) {
|
|
222
|
-
const fileExists =
|
|
193
|
+
const fileExists = await fileExistsAtPath(privateKey);
|
|
223
194
|
if (fileExists) {
|
|
224
|
-
privateKeyBuffer =
|
|
195
|
+
privateKeyBuffer = await createBufferFromPath(privateKey);
|
|
225
196
|
}
|
|
226
197
|
else {
|
|
227
|
-
|
|
198
|
+
consola.error('Private key file not found.');
|
|
228
199
|
process.exit(1);
|
|
229
200
|
}
|
|
230
201
|
}
|
|
231
202
|
else {
|
|
232
|
-
|
|
203
|
+
consola.error('Private key must be a path to a .pem file.');
|
|
233
204
|
process.exit(1);
|
|
234
205
|
}
|
|
235
206
|
}
|
|
236
207
|
let appBundleId;
|
|
237
208
|
try {
|
|
238
209
|
// Create the app bundle
|
|
239
|
-
|
|
210
|
+
consola.start('Creating bundle...');
|
|
240
211
|
let checksum;
|
|
241
212
|
let signature;
|
|
242
213
|
if (path && url) {
|
|
243
214
|
// Create the file buffer
|
|
244
|
-
if (!
|
|
245
|
-
|
|
215
|
+
if (!zip.isZipped(path)) {
|
|
216
|
+
consola.error('The path must be a zip file when providing a URL.');
|
|
246
217
|
process.exit(1);
|
|
247
218
|
}
|
|
248
|
-
const fileBuffer =
|
|
219
|
+
const fileBuffer = await createBufferFromPath(path);
|
|
249
220
|
// Generate checksum
|
|
250
|
-
checksum =
|
|
221
|
+
checksum = await createHash(fileBuffer);
|
|
251
222
|
// Sign the bundle
|
|
252
223
|
if (privateKeyBuffer) {
|
|
253
|
-
signature =
|
|
224
|
+
signature = await createSignature(privateKeyBuffer, fileBuffer);
|
|
254
225
|
}
|
|
255
226
|
}
|
|
256
|
-
const response =
|
|
227
|
+
const response = await appBundlesService.create({
|
|
257
228
|
appId,
|
|
258
229
|
artifactType,
|
|
259
|
-
channelName,
|
|
230
|
+
channelName: channel,
|
|
260
231
|
checksum,
|
|
261
232
|
gitCommitMessage: commitMessage,
|
|
262
233
|
gitCommitRef: commitRef,
|
|
263
234
|
gitCommitSha: commitSha,
|
|
264
235
|
customProperties: parseCustomProperties(customProperty),
|
|
265
|
-
expiresAt
|
|
236
|
+
expiresAt,
|
|
266
237
|
url,
|
|
267
238
|
maxAndroidAppVersionCode: androidMax,
|
|
268
239
|
maxIosAppVersionCode: iosMax,
|
|
269
240
|
minAndroidAppVersionCode: androidMin,
|
|
270
241
|
minIosAppVersionCode: iosMin,
|
|
271
|
-
rolloutPercentage,
|
|
242
|
+
rolloutPercentage: rollout,
|
|
272
243
|
signature,
|
|
273
244
|
});
|
|
274
245
|
appBundleId = response.id;
|
|
@@ -281,132 +252,135 @@ exports.default = (0, citty_1.defineCommand)({
|
|
|
281
252
|
let appBundleFileId;
|
|
282
253
|
// Upload the app bundle files
|
|
283
254
|
if (artifactType === 'manifest') {
|
|
284
|
-
|
|
255
|
+
await uploadFiles({ appId, appBundleId: response.id, path, privateKeyBuffer });
|
|
285
256
|
}
|
|
286
257
|
else {
|
|
287
|
-
const result =
|
|
258
|
+
const result = await uploadZip({ appId, appBundleId: response.id, path, privateKeyBuffer });
|
|
288
259
|
appBundleFileId = result.appBundleFileId;
|
|
289
260
|
}
|
|
290
261
|
// Update the app bundle
|
|
291
|
-
|
|
292
|
-
|
|
262
|
+
consola.start('Updating bundle...');
|
|
263
|
+
await appBundlesService.update({
|
|
264
|
+
appBundleFileId,
|
|
265
|
+
appId,
|
|
266
|
+
artifactStatus: 'ready',
|
|
267
|
+
appBundleId: response.id,
|
|
268
|
+
});
|
|
293
269
|
}
|
|
294
270
|
}
|
|
295
|
-
|
|
296
|
-
|
|
271
|
+
consola.success('Bundle successfully created.');
|
|
272
|
+
consola.info(`Bundle ID: ${response.id}`);
|
|
297
273
|
}
|
|
298
274
|
catch (error) {
|
|
299
275
|
if (appBundleId) {
|
|
300
|
-
|
|
276
|
+
await appBundlesService.delete({ appId, appBundleId }).catch(() => {
|
|
301
277
|
// No-op
|
|
302
278
|
});
|
|
303
279
|
}
|
|
304
|
-
const message =
|
|
305
|
-
|
|
280
|
+
const message = getMessageFromUnknownError(error);
|
|
281
|
+
consola.error(message);
|
|
306
282
|
process.exit(1);
|
|
307
283
|
}
|
|
308
|
-
}
|
|
284
|
+
},
|
|
309
285
|
});
|
|
310
|
-
const uploadFile = (options) =>
|
|
286
|
+
const uploadFile = async (options) => {
|
|
287
|
+
let { appId, appBundleId, buffer, href, mimeType, name, privateKeyBuffer, retryOnFailure } = options;
|
|
311
288
|
try {
|
|
312
289
|
// Generate checksum
|
|
313
|
-
const hash =
|
|
290
|
+
const hash = await createHash(buffer);
|
|
314
291
|
// Sign the bundle
|
|
315
292
|
let signature;
|
|
316
|
-
if (
|
|
317
|
-
signature =
|
|
293
|
+
if (privateKeyBuffer) {
|
|
294
|
+
signature = await createSignature(privateKeyBuffer, buffer);
|
|
318
295
|
}
|
|
319
296
|
// Create the multipart upload
|
|
320
|
-
return
|
|
321
|
-
appId
|
|
322
|
-
appBundleId
|
|
323
|
-
buffer
|
|
297
|
+
return await appBundleFilesService.create({
|
|
298
|
+
appId,
|
|
299
|
+
appBundleId,
|
|
300
|
+
buffer,
|
|
324
301
|
checksum: hash,
|
|
325
|
-
href
|
|
326
|
-
mimeType
|
|
327
|
-
name
|
|
302
|
+
href,
|
|
303
|
+
mimeType,
|
|
304
|
+
name,
|
|
328
305
|
signature,
|
|
329
306
|
});
|
|
330
307
|
}
|
|
331
308
|
catch (error) {
|
|
332
|
-
if (
|
|
333
|
-
return uploadFile(
|
|
309
|
+
if (retryOnFailure) {
|
|
310
|
+
return uploadFile({
|
|
311
|
+
...options,
|
|
312
|
+
retryOnFailure: false,
|
|
313
|
+
});
|
|
334
314
|
}
|
|
335
315
|
throw error;
|
|
336
316
|
}
|
|
337
|
-
}
|
|
338
|
-
const uploadFiles = (options) =>
|
|
317
|
+
};
|
|
318
|
+
const uploadFiles = async (options) => {
|
|
319
|
+
let { appId, appBundleId, path, privateKeyBuffer } = options;
|
|
339
320
|
// Generate the manifest file
|
|
340
|
-
|
|
321
|
+
await generateManifestJson(path);
|
|
341
322
|
// Get all files in the directory
|
|
342
|
-
const files =
|
|
323
|
+
const files = await getFilesInDirectoryAndSubdirectories(path);
|
|
343
324
|
// Iterate over each file
|
|
344
325
|
let fileIndex = 0;
|
|
345
|
-
const uploadNextFile = () =>
|
|
326
|
+
const uploadNextFile = async () => {
|
|
346
327
|
if (fileIndex >= files.length) {
|
|
347
328
|
return;
|
|
348
329
|
}
|
|
349
330
|
const file = files[fileIndex];
|
|
350
331
|
fileIndex++;
|
|
351
|
-
|
|
352
|
-
const buffer =
|
|
353
|
-
|
|
354
|
-
appId
|
|
355
|
-
appBundleId:
|
|
332
|
+
consola.start(`Uploading file (${fileIndex}/${files.length})...`);
|
|
333
|
+
const buffer = await createBufferFromPath(file.path);
|
|
334
|
+
await uploadFile({
|
|
335
|
+
appId,
|
|
336
|
+
appBundleId: appBundleId,
|
|
356
337
|
buffer,
|
|
357
338
|
href: file.href,
|
|
358
339
|
mimeType: file.mimeType,
|
|
359
340
|
name: file.name,
|
|
360
|
-
privateKeyBuffer:
|
|
341
|
+
privateKeyBuffer: privateKeyBuffer,
|
|
361
342
|
retryOnFailure: true,
|
|
362
343
|
});
|
|
363
|
-
|
|
364
|
-
}
|
|
365
|
-
const uploadPromises = Array.from({ length:
|
|
366
|
-
for (let i = 0; i <
|
|
344
|
+
await uploadNextFile();
|
|
345
|
+
};
|
|
346
|
+
const uploadPromises = Array.from({ length: MAX_CONCURRENT_UPLOADS });
|
|
347
|
+
for (let i = 0; i < MAX_CONCURRENT_UPLOADS; i++) {
|
|
367
348
|
uploadPromises[i] = uploadNextFile();
|
|
368
349
|
}
|
|
369
|
-
|
|
370
|
-
}
|
|
371
|
-
const uploadZip = (options) =>
|
|
350
|
+
await Promise.all(uploadPromises);
|
|
351
|
+
};
|
|
352
|
+
const uploadZip = async (options) => {
|
|
353
|
+
let { appId, appBundleId, path, privateKeyBuffer } = options;
|
|
372
354
|
// Read the zip file
|
|
373
355
|
let fileBuffer;
|
|
374
|
-
if (
|
|
375
|
-
const readStream =
|
|
376
|
-
fileBuffer =
|
|
356
|
+
if (zip.isZipped(path)) {
|
|
357
|
+
const readStream = createReadStream(path);
|
|
358
|
+
fileBuffer = await createBufferFromReadStream(readStream);
|
|
377
359
|
}
|
|
378
360
|
else {
|
|
379
|
-
|
|
380
|
-
fileBuffer =
|
|
361
|
+
consola.start('Zipping folder...');
|
|
362
|
+
fileBuffer = await zip.zipFolder(path);
|
|
381
363
|
}
|
|
382
364
|
// Upload the zip file
|
|
383
|
-
|
|
384
|
-
const result =
|
|
385
|
-
appId
|
|
386
|
-
appBundleId:
|
|
365
|
+
consola.start('Uploading file...');
|
|
366
|
+
const result = await uploadFile({
|
|
367
|
+
appId,
|
|
368
|
+
appBundleId: appBundleId,
|
|
387
369
|
buffer: fileBuffer,
|
|
388
370
|
mimeType: 'application/zip',
|
|
389
371
|
name: 'bundle.zip',
|
|
390
|
-
privateKeyBuffer:
|
|
372
|
+
privateKeyBuffer: privateKeyBuffer,
|
|
391
373
|
});
|
|
392
374
|
return {
|
|
393
375
|
appBundleFileId: result.id,
|
|
394
376
|
};
|
|
395
|
-
}
|
|
377
|
+
};
|
|
396
378
|
const parseCustomProperties = (customProperty) => {
|
|
397
379
|
let customProperties;
|
|
398
380
|
if (customProperty) {
|
|
399
381
|
customProperties = {};
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
const [key, value] = property.split('=');
|
|
403
|
-
if (key && value) {
|
|
404
|
-
customProperties[key] = value;
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
else {
|
|
409
|
-
const [key, value] = customProperty.split('=');
|
|
382
|
+
for (const property of customProperty) {
|
|
383
|
+
const [key, value] = property.split('=');
|
|
410
384
|
if (key && value) {
|
|
411
385
|
customProperties[key] = value;
|
|
412
386
|
}
|