@capawesome/cli 1.13.2 → 1.14.0-dev.3b0fc7e.1755934102
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 +7 -3
- package/dist/commands/apps/bundles/create.js +218 -234
- package/dist/commands/apps/bundles/create.test.js +276 -0
- package/dist/commands/apps/bundles/delete.js +47 -55
- package/dist/commands/apps/bundles/delete.test.js +139 -0
- package/dist/commands/apps/bundles/update.js +73 -84
- package/dist/commands/apps/bundles/update.test.js +141 -0
- package/dist/commands/apps/channels/create.js +57 -70
- package/dist/commands/apps/channels/create.test.js +119 -0
- package/dist/commands/apps/channels/delete.js +58 -64
- 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 +50 -77
- package/dist/commands/apps/channels/update.test.js +138 -0
- package/dist/commands/apps/create.js +39 -42
- package/dist/commands/apps/create.test.js +117 -0
- package/dist/commands/apps/delete.js +41 -45
- package/dist/commands/apps/delete.test.js +120 -0
- package/dist/commands/apps/devices/delete.js +47 -55
- 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 -41
- package/dist/services/authorization-service.js +4 -8
- package/dist/services/config.js +15 -28
- package/dist/services/organizations.js +26 -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 +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,216 +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
|
-
},
|
|
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 { 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.');
|
|
104
95
|
process.exit(1);
|
|
105
96
|
}
|
|
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
97
|
// Validate the expiration days
|
|
125
98
|
let expiresAt;
|
|
126
99
|
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
100
|
const expiresAtDate = new Date();
|
|
133
|
-
expiresAtDate.setDate(expiresAtDate.getDate() +
|
|
101
|
+
expiresAtDate.setDate(expiresAtDate.getDate() + expiresInDays);
|
|
134
102
|
expiresAt = expiresAtDate.toISOString();
|
|
135
103
|
}
|
|
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
104
|
// Check that either a path or a url is provided
|
|
147
105
|
if (!path && !url) {
|
|
148
|
-
path =
|
|
106
|
+
path = await prompt('Enter the path to the app bundle:', {
|
|
149
107
|
type: 'text',
|
|
150
108
|
});
|
|
151
109
|
if (!path) {
|
|
152
|
-
|
|
110
|
+
consola.error('You must provide a path to the app bundle.');
|
|
153
111
|
process.exit(1);
|
|
154
112
|
}
|
|
155
113
|
}
|
|
156
114
|
if (path) {
|
|
157
115
|
// Check if the path exists when a path is provided
|
|
158
|
-
const pathExists =
|
|
116
|
+
const pathExists = await fileExistsAtPath(path);
|
|
159
117
|
if (!pathExists) {
|
|
160
|
-
|
|
118
|
+
consola.error(`The path does not exist.`);
|
|
161
119
|
process.exit(1);
|
|
162
120
|
}
|
|
163
121
|
// Check if the directory contains an index.html file
|
|
164
|
-
const pathIsDirectory =
|
|
122
|
+
const pathIsDirectory = await isDirectory(path);
|
|
165
123
|
if (pathIsDirectory) {
|
|
166
|
-
const files =
|
|
124
|
+
const files = await getFilesInDirectoryAndSubdirectories(path);
|
|
167
125
|
const indexHtml = files.find((file) => file.href === 'index.html');
|
|
168
126
|
if (!indexHtml) {
|
|
169
|
-
|
|
127
|
+
consola.error('The directory must contain an `index.html` file.');
|
|
170
128
|
process.exit(1);
|
|
171
129
|
}
|
|
172
130
|
}
|
|
173
131
|
}
|
|
174
132
|
// Check that the path is a directory when creating a bundle with an artifact type
|
|
175
133
|
if (artifactType === 'manifest' && path) {
|
|
176
|
-
const pathIsDirectory =
|
|
134
|
+
const pathIsDirectory = await isDirectory(path);
|
|
177
135
|
if (!pathIsDirectory) {
|
|
178
|
-
|
|
136
|
+
consola.error('The path must be a folder when creating a bundle with an artifact type of `manifest`.');
|
|
179
137
|
process.exit(1);
|
|
180
138
|
}
|
|
181
139
|
}
|
|
182
140
|
// Check that a URL is not provided when creating a bundle with an artifact type of manifest
|
|
183
141
|
if (artifactType === 'manifest' && url) {
|
|
184
|
-
|
|
142
|
+
consola.error('It is not yet possible to provide a URL when creating a bundle with an artifact type of `manifest`.');
|
|
185
143
|
process.exit(1);
|
|
186
144
|
}
|
|
187
145
|
if (!appId) {
|
|
188
|
-
const
|
|
146
|
+
const organizations = await organizationsService.findAll();
|
|
147
|
+
if (organizations.length === 0) {
|
|
148
|
+
consola.error('You must create an organization before creating a bundle.');
|
|
149
|
+
process.exit(1);
|
|
150
|
+
}
|
|
151
|
+
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
152
|
+
const organizationId = await prompt('Select the organization of the app for which you want to create a bundle.', {
|
|
153
|
+
type: 'select',
|
|
154
|
+
options: organizations.map((organization) => ({ label: organization.name, value: organization.id })),
|
|
155
|
+
});
|
|
156
|
+
if (!organizationId) {
|
|
157
|
+
consola.error('You must select the organization of an app for which you want to create a bundle.');
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
const apps = await appsService.findAll({
|
|
161
|
+
organizationId,
|
|
162
|
+
});
|
|
189
163
|
if (apps.length === 0) {
|
|
190
|
-
|
|
164
|
+
consola.error('You must create an app before creating a bundle.');
|
|
191
165
|
process.exit(1);
|
|
192
166
|
}
|
|
193
167
|
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
194
|
-
appId =
|
|
168
|
+
appId = await prompt('Which app do you want to deploy to:', {
|
|
195
169
|
type: 'select',
|
|
196
170
|
options: apps.map((app) => ({ label: app.name, value: app.id })),
|
|
197
171
|
});
|
|
198
172
|
if (!appId) {
|
|
199
|
-
|
|
173
|
+
consola.error('You must select an app to deploy to.');
|
|
200
174
|
process.exit(1);
|
|
201
175
|
}
|
|
202
176
|
}
|
|
203
|
-
if (!
|
|
204
|
-
const promptChannel =
|
|
177
|
+
if (!channel) {
|
|
178
|
+
const promptChannel = await prompt('Do you want to deploy to a specific channel?', {
|
|
205
179
|
type: 'select',
|
|
206
180
|
options: ['Yes', 'No'],
|
|
207
181
|
});
|
|
208
182
|
if (promptChannel === 'Yes') {
|
|
209
|
-
|
|
183
|
+
channel = await prompt('Enter the channel name:', {
|
|
210
184
|
type: 'text',
|
|
211
185
|
});
|
|
212
|
-
if (!
|
|
213
|
-
|
|
186
|
+
if (!channel) {
|
|
187
|
+
consola.error('The channel name must be at least one character long.');
|
|
214
188
|
process.exit(1);
|
|
215
189
|
}
|
|
216
190
|
}
|
|
@@ -218,57 +192,66 @@ exports.default = (0, citty_1.defineCommand)({
|
|
|
218
192
|
// Create the private key buffer
|
|
219
193
|
let privateKeyBuffer;
|
|
220
194
|
if (privateKey) {
|
|
221
|
-
if (privateKey
|
|
222
|
-
|
|
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);
|
|
223
203
|
if (fileExists) {
|
|
224
|
-
|
|
204
|
+
const keyBuffer = await createBufferFromPath(privateKey);
|
|
205
|
+
const keyContent = keyBuffer.toString('utf8');
|
|
206
|
+
const formattedPrivateKey = formatPrivateKey(keyContent);
|
|
207
|
+
privateKeyBuffer = createBufferFromString(formattedPrivateKey);
|
|
225
208
|
}
|
|
226
209
|
else {
|
|
227
|
-
|
|
210
|
+
consola.error('Private key file not found.');
|
|
228
211
|
process.exit(1);
|
|
229
212
|
}
|
|
230
213
|
}
|
|
231
214
|
else {
|
|
232
|
-
|
|
215
|
+
consola.error('Private key must be either a path to a .pem file or the private key content as plain text.');
|
|
233
216
|
process.exit(1);
|
|
234
217
|
}
|
|
235
218
|
}
|
|
236
219
|
let appBundleId;
|
|
237
220
|
try {
|
|
238
221
|
// Create the app bundle
|
|
239
|
-
|
|
222
|
+
consola.start('Creating bundle...');
|
|
240
223
|
let checksum;
|
|
241
224
|
let signature;
|
|
242
225
|
if (path && url) {
|
|
243
226
|
// Create the file buffer
|
|
244
|
-
if (!
|
|
245
|
-
|
|
227
|
+
if (!zip.isZipped(path)) {
|
|
228
|
+
consola.error('The path must be a zip file when providing a URL.');
|
|
246
229
|
process.exit(1);
|
|
247
230
|
}
|
|
248
|
-
const fileBuffer =
|
|
231
|
+
const fileBuffer = await createBufferFromPath(path);
|
|
249
232
|
// Generate checksum
|
|
250
|
-
checksum =
|
|
233
|
+
checksum = await createHash(fileBuffer);
|
|
251
234
|
// Sign the bundle
|
|
252
235
|
if (privateKeyBuffer) {
|
|
253
|
-
signature =
|
|
236
|
+
signature = await createSignature(privateKeyBuffer, fileBuffer);
|
|
254
237
|
}
|
|
255
238
|
}
|
|
256
|
-
const response =
|
|
239
|
+
const response = await appBundlesService.create({
|
|
257
240
|
appId,
|
|
258
241
|
artifactType,
|
|
259
|
-
channelName,
|
|
242
|
+
channelName: channel,
|
|
260
243
|
checksum,
|
|
261
244
|
gitCommitMessage: commitMessage,
|
|
262
245
|
gitCommitRef: commitRef,
|
|
263
246
|
gitCommitSha: commitSha,
|
|
264
247
|
customProperties: parseCustomProperties(customProperty),
|
|
265
|
-
expiresAt
|
|
248
|
+
expiresAt,
|
|
266
249
|
url,
|
|
267
250
|
maxAndroidAppVersionCode: androidMax,
|
|
268
251
|
maxIosAppVersionCode: iosMax,
|
|
269
252
|
minAndroidAppVersionCode: androidMin,
|
|
270
253
|
minIosAppVersionCode: iosMin,
|
|
271
|
-
rolloutPercentage,
|
|
254
|
+
rolloutPercentage: rollout,
|
|
272
255
|
signature,
|
|
273
256
|
});
|
|
274
257
|
appBundleId = response.id;
|
|
@@ -281,132 +264,133 @@ exports.default = (0, citty_1.defineCommand)({
|
|
|
281
264
|
let appBundleFileId;
|
|
282
265
|
// Upload the app bundle files
|
|
283
266
|
if (artifactType === 'manifest') {
|
|
284
|
-
|
|
267
|
+
await uploadFiles({ appId, appBundleId: response.id, path, privateKeyBuffer });
|
|
285
268
|
}
|
|
286
269
|
else {
|
|
287
|
-
const result =
|
|
270
|
+
const result = await uploadZip({ appId, appBundleId: response.id, path, privateKeyBuffer });
|
|
288
271
|
appBundleFileId = result.appBundleFileId;
|
|
289
272
|
}
|
|
290
273
|
// Update the app bundle
|
|
291
|
-
|
|
292
|
-
|
|
274
|
+
consola.start('Updating bundle...');
|
|
275
|
+
await appBundlesService.update({
|
|
276
|
+
appBundleFileId,
|
|
277
|
+
appId,
|
|
278
|
+
artifactStatus: 'ready',
|
|
279
|
+
appBundleId: response.id,
|
|
280
|
+
});
|
|
293
281
|
}
|
|
294
282
|
}
|
|
295
|
-
|
|
296
|
-
|
|
283
|
+
consola.success('Bundle successfully created.');
|
|
284
|
+
consola.info(`Bundle ID: ${response.id}`);
|
|
297
285
|
}
|
|
298
286
|
catch (error) {
|
|
299
287
|
if (appBundleId) {
|
|
300
|
-
|
|
288
|
+
await appBundlesService.delete({ appId, appBundleId }).catch(() => {
|
|
301
289
|
// No-op
|
|
302
290
|
});
|
|
303
291
|
}
|
|
304
|
-
|
|
305
|
-
consola_1.default.error(message);
|
|
306
|
-
process.exit(1);
|
|
292
|
+
throw error;
|
|
307
293
|
}
|
|
308
|
-
}
|
|
294
|
+
},
|
|
309
295
|
});
|
|
310
|
-
const uploadFile = (options) =>
|
|
296
|
+
const uploadFile = async (options) => {
|
|
297
|
+
let { appId, appBundleId, buffer, href, mimeType, name, privateKeyBuffer, retryOnFailure } = options;
|
|
311
298
|
try {
|
|
312
299
|
// Generate checksum
|
|
313
|
-
const hash =
|
|
300
|
+
const hash = await createHash(buffer);
|
|
314
301
|
// Sign the bundle
|
|
315
302
|
let signature;
|
|
316
|
-
if (
|
|
317
|
-
signature =
|
|
303
|
+
if (privateKeyBuffer) {
|
|
304
|
+
signature = await createSignature(privateKeyBuffer, buffer);
|
|
318
305
|
}
|
|
319
306
|
// Create the multipart upload
|
|
320
|
-
return
|
|
321
|
-
appId
|
|
322
|
-
appBundleId
|
|
323
|
-
buffer
|
|
307
|
+
return await appBundleFilesService.create({
|
|
308
|
+
appId,
|
|
309
|
+
appBundleId,
|
|
310
|
+
buffer,
|
|
324
311
|
checksum: hash,
|
|
325
|
-
href
|
|
326
|
-
mimeType
|
|
327
|
-
name
|
|
312
|
+
href,
|
|
313
|
+
mimeType,
|
|
314
|
+
name,
|
|
328
315
|
signature,
|
|
329
316
|
});
|
|
330
317
|
}
|
|
331
318
|
catch (error) {
|
|
332
|
-
if (
|
|
333
|
-
return uploadFile(
|
|
319
|
+
if (retryOnFailure) {
|
|
320
|
+
return uploadFile({
|
|
321
|
+
...options,
|
|
322
|
+
retryOnFailure: false,
|
|
323
|
+
});
|
|
334
324
|
}
|
|
335
325
|
throw error;
|
|
336
326
|
}
|
|
337
|
-
}
|
|
338
|
-
const uploadFiles = (options) =>
|
|
327
|
+
};
|
|
328
|
+
const uploadFiles = async (options) => {
|
|
329
|
+
let { appId, appBundleId, path, privateKeyBuffer } = options;
|
|
339
330
|
// Generate the manifest file
|
|
340
|
-
|
|
331
|
+
await generateManifestJson(path);
|
|
341
332
|
// Get all files in the directory
|
|
342
|
-
const files =
|
|
333
|
+
const files = await getFilesInDirectoryAndSubdirectories(path);
|
|
343
334
|
// Iterate over each file
|
|
344
335
|
let fileIndex = 0;
|
|
345
|
-
const uploadNextFile = () =>
|
|
336
|
+
const uploadNextFile = async () => {
|
|
346
337
|
if (fileIndex >= files.length) {
|
|
347
338
|
return;
|
|
348
339
|
}
|
|
349
340
|
const file = files[fileIndex];
|
|
350
341
|
fileIndex++;
|
|
351
|
-
|
|
352
|
-
const buffer =
|
|
353
|
-
|
|
354
|
-
appId
|
|
355
|
-
appBundleId:
|
|
342
|
+
consola.start(`Uploading file (${fileIndex}/${files.length})...`);
|
|
343
|
+
const buffer = await createBufferFromPath(file.path);
|
|
344
|
+
await uploadFile({
|
|
345
|
+
appId,
|
|
346
|
+
appBundleId: appBundleId,
|
|
356
347
|
buffer,
|
|
357
348
|
href: file.href,
|
|
358
349
|
mimeType: file.mimeType,
|
|
359
350
|
name: file.name,
|
|
360
|
-
privateKeyBuffer:
|
|
351
|
+
privateKeyBuffer: privateKeyBuffer,
|
|
361
352
|
retryOnFailure: true,
|
|
362
353
|
});
|
|
363
|
-
|
|
364
|
-
}
|
|
365
|
-
const uploadPromises = Array.from({ length:
|
|
366
|
-
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++) {
|
|
367
358
|
uploadPromises[i] = uploadNextFile();
|
|
368
359
|
}
|
|
369
|
-
|
|
370
|
-
}
|
|
371
|
-
const uploadZip = (options) =>
|
|
360
|
+
await Promise.all(uploadPromises);
|
|
361
|
+
};
|
|
362
|
+
const uploadZip = async (options) => {
|
|
363
|
+
let { appId, appBundleId, path, privateKeyBuffer } = options;
|
|
372
364
|
// Read the zip file
|
|
373
365
|
let fileBuffer;
|
|
374
|
-
if (
|
|
375
|
-
const readStream =
|
|
376
|
-
fileBuffer =
|
|
366
|
+
if (zip.isZipped(path)) {
|
|
367
|
+
const readStream = createReadStream(path);
|
|
368
|
+
fileBuffer = await createBufferFromReadStream(readStream);
|
|
377
369
|
}
|
|
378
370
|
else {
|
|
379
|
-
|
|
380
|
-
fileBuffer =
|
|
371
|
+
consola.start('Zipping folder...');
|
|
372
|
+
fileBuffer = await zip.zipFolder(path);
|
|
381
373
|
}
|
|
382
374
|
// Upload the zip file
|
|
383
|
-
|
|
384
|
-
const result =
|
|
385
|
-
appId
|
|
386
|
-
appBundleId:
|
|
375
|
+
consola.start('Uploading file...');
|
|
376
|
+
const result = await uploadFile({
|
|
377
|
+
appId,
|
|
378
|
+
appBundleId: appBundleId,
|
|
387
379
|
buffer: fileBuffer,
|
|
388
380
|
mimeType: 'application/zip',
|
|
389
381
|
name: 'bundle.zip',
|
|
390
|
-
privateKeyBuffer:
|
|
382
|
+
privateKeyBuffer: privateKeyBuffer,
|
|
391
383
|
});
|
|
392
384
|
return {
|
|
393
385
|
appBundleFileId: result.id,
|
|
394
386
|
};
|
|
395
|
-
}
|
|
387
|
+
};
|
|
396
388
|
const parseCustomProperties = (customProperty) => {
|
|
397
389
|
let customProperties;
|
|
398
390
|
if (customProperty) {
|
|
399
391
|
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('=');
|
|
392
|
+
for (const property of customProperty) {
|
|
393
|
+
const [key, value] = property.split('=');
|
|
410
394
|
if (key && value) {
|
|
411
395
|
customProperties[key] = value;
|
|
412
396
|
}
|