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