@capawesome/cli 3.11.0 → 4.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/dist/commands/apps/builds/cancel.js +1 -1
- package/dist/commands/apps/builds/create.js +58 -50
- package/dist/commands/apps/builds/download.js +27 -3
- package/dist/commands/apps/bundles/create.js +5 -449
- package/dist/commands/apps/bundles/delete.js +3 -68
- package/dist/commands/apps/bundles/update.js +3 -66
- package/dist/commands/apps/channels/create.js +5 -8
- package/dist/commands/apps/channels/create.test.js +6 -9
- package/dist/commands/apps/channels/delete.js +3 -2
- package/dist/commands/apps/channels/get.js +2 -12
- package/dist/commands/apps/channels/get.test.js +1 -2
- package/dist/commands/apps/channels/list.js +2 -10
- package/dist/commands/apps/channels/list.test.js +2 -3
- package/dist/commands/apps/channels/pause.js +85 -0
- package/dist/commands/apps/channels/resume.js +85 -0
- package/dist/commands/apps/channels/update.js +4 -7
- package/dist/commands/apps/channels/update.test.js +2 -4
- package/dist/commands/apps/create.js +1 -1
- package/dist/commands/apps/delete.js +3 -2
- package/dist/commands/apps/deployments/cancel.js +1 -1
- package/dist/commands/apps/deployments/create.js +82 -31
- package/dist/commands/apps/devices/delete.js +3 -2
- package/dist/commands/apps/environments/create.js +1 -1
- package/dist/commands/apps/environments/delete.js +3 -2
- package/dist/commands/apps/liveupdates/bundle.js +117 -0
- package/dist/commands/apps/liveupdates/generate-manifest.js +39 -0
- package/dist/commands/{manifests/generate.test.js → apps/liveupdates/generate-manifest.test.js} +6 -6
- package/dist/commands/apps/liveupdates/register.js +291 -0
- package/dist/commands/apps/{bundles/create.test.js → liveupdates/register.test.js} +123 -111
- package/dist/commands/apps/liveupdates/rollback.js +171 -0
- package/dist/commands/apps/liveupdates/rollout.js +147 -0
- package/dist/commands/apps/liveupdates/upload.js +420 -0
- package/dist/commands/apps/liveupdates/upload.test.js +325 -0
- package/dist/commands/manifests/generate.js +2 -27
- package/dist/commands/organizations/create.js +1 -1
- package/dist/index.js +8 -0
- package/dist/services/app-builds.js +9 -2
- package/dist/services/app-channels.js +19 -0
- package/dist/services/app-deployments.js +24 -14
- package/dist/services/config.js +2 -0
- package/dist/utils/app-environments.js +2 -1
- package/dist/utils/time-format.js +26 -0
- package/package.json +3 -3
- package/dist/commands/apps/bundles/delete.test.js +0 -142
- package/dist/commands/apps/bundles/update.test.js +0 -144
- package/dist/utils/capacitor-config.js +0 -96
- package/dist/utils/package-json.js +0 -58
|
@@ -1,29 +1,6 @@
|
|
|
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 { findCapacitorConfigPath, getLiveUpdatePluginAppIdFromConfig, getLiveUpdatePluginPublicKeyFromConfig, getWebDirFromConfig, } from '../../../utils/capacitor-config.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 { findPackageJsonPath, getBuildScript } from '../../../utils/package-json.js';
|
|
13
|
-
import { formatPrivateKey } from '../../../utils/private-key.js';
|
|
14
|
-
import { prompt } from '../../../utils/prompt.js';
|
|
15
|
-
import { createSignature } from '../../../utils/signature.js';
|
|
16
|
-
import zip from '../../../utils/zip.js';
|
|
17
1
|
import { defineCommand, defineOptions } from '@robingenz/zli';
|
|
18
|
-
import { exec } from 'child_process';
|
|
19
2
|
import consola from 'consola';
|
|
20
|
-
import { createReadStream } from 'fs';
|
|
21
|
-
import pathModule from 'path';
|
|
22
|
-
import { promisify } from 'util';
|
|
23
3
|
import { z } from 'zod';
|
|
24
|
-
import { isInteractive } from '../../../utils/environment.js';
|
|
25
|
-
// Promisified exec for running build scripts
|
|
26
|
-
const execAsync = promisify(exec);
|
|
27
4
|
export default defineCommand({
|
|
28
5
|
description: 'Create a new app bundle.',
|
|
29
6
|
options: defineOptions(z.object({
|
|
@@ -104,431 +81,10 @@ export default defineCommand({
|
|
|
104
81
|
url: z.string().optional().describe('The url to the self-hosted bundle file.'),
|
|
105
82
|
})),
|
|
106
83
|
action: async (options, args) => {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
// Calculate the expiration date
|
|
114
|
-
let expiresAt;
|
|
115
|
-
if (expiresInDays) {
|
|
116
|
-
const expiresAtDate = new Date();
|
|
117
|
-
expiresAtDate.setDate(expiresAtDate.getDate() + expiresInDays);
|
|
118
|
-
expiresAt = expiresAtDate.toISOString();
|
|
119
|
-
}
|
|
120
|
-
// Try to auto-detect webDir from Capacitor configuration
|
|
121
|
-
const capacitorConfigPath = await findCapacitorConfigPath();
|
|
122
|
-
if (!capacitorConfigPath) {
|
|
123
|
-
consola.warn('No Capacitor configuration found to auto-detect web asset directory or app ID.');
|
|
124
|
-
}
|
|
125
|
-
// Check that either a path or a url is provided
|
|
126
|
-
if (!path && !url) {
|
|
127
|
-
// Try to auto-detect webDir from Capacitor configuration
|
|
128
|
-
if (capacitorConfigPath) {
|
|
129
|
-
const webDirPath = await getWebDirFromConfig(capacitorConfigPath);
|
|
130
|
-
if (webDirPath) {
|
|
131
|
-
const relativeWebDirPath = pathModule.relative(process.cwd(), webDirPath);
|
|
132
|
-
consola.success(`Auto-detected web asset directory "${relativeWebDirPath}" from Capacitor configuration.`);
|
|
133
|
-
path = webDirPath;
|
|
134
|
-
}
|
|
135
|
-
else {
|
|
136
|
-
consola.warn('No web asset directory found in Capacitor configuration (`webDir`).');
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
// If still no path, prompt the user
|
|
140
|
-
if (!path) {
|
|
141
|
-
if (!isInteractive()) {
|
|
142
|
-
consola.error('You must provide either a path or a url when running in non-interactive environment.');
|
|
143
|
-
process.exit(1);
|
|
144
|
-
}
|
|
145
|
-
else {
|
|
146
|
-
path = await prompt('Enter the path to the app bundle:', {
|
|
147
|
-
type: 'text',
|
|
148
|
-
});
|
|
149
|
-
if (!path) {
|
|
150
|
-
consola.error('You must provide a path to the app bundle.');
|
|
151
|
-
process.exit(1);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
// Check for build scripts if a path is provided or detected
|
|
157
|
-
if (path && !url) {
|
|
158
|
-
const packageJsonPath = await findPackageJsonPath();
|
|
159
|
-
if (!packageJsonPath) {
|
|
160
|
-
consola.warn('No package.json file found.');
|
|
161
|
-
}
|
|
162
|
-
else {
|
|
163
|
-
const buildScript = await getBuildScript(packageJsonPath);
|
|
164
|
-
if (!buildScript) {
|
|
165
|
-
consola.warn('No build script (`capawesome:build` or `build`) found in package.json.');
|
|
166
|
-
}
|
|
167
|
-
else if (isInteractive()) {
|
|
168
|
-
const shouldBuild = await prompt('Do you want to run the build script before creating the bundle to ensure the latest assets are included?', {
|
|
169
|
-
type: 'confirm',
|
|
170
|
-
initial: true,
|
|
171
|
-
});
|
|
172
|
-
if (shouldBuild) {
|
|
173
|
-
try {
|
|
174
|
-
consola.start(`Running \`${buildScript.name}\` script...`);
|
|
175
|
-
const { stdout, stderr } = await execAsync(`npm run ${buildScript.name}`);
|
|
176
|
-
if (stdout) {
|
|
177
|
-
console.log(stdout);
|
|
178
|
-
}
|
|
179
|
-
if (stderr) {
|
|
180
|
-
console.error(stderr);
|
|
181
|
-
}
|
|
182
|
-
consola.success('Build completed successfully.');
|
|
183
|
-
}
|
|
184
|
-
catch (error) {
|
|
185
|
-
consola.error('Build failed.');
|
|
186
|
-
if (error.stdout) {
|
|
187
|
-
console.log(error.stdout);
|
|
188
|
-
}
|
|
189
|
-
if (error.stderr) {
|
|
190
|
-
console.error(error.stderr);
|
|
191
|
-
}
|
|
192
|
-
process.exit(1);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
// Validate the provided path
|
|
199
|
-
if (path) {
|
|
200
|
-
// Check if the path exists when a path is provided
|
|
201
|
-
const pathExists = await fileExistsAtPath(path);
|
|
202
|
-
if (!pathExists) {
|
|
203
|
-
consola.error(`The path does not exist.`);
|
|
204
|
-
process.exit(1);
|
|
205
|
-
}
|
|
206
|
-
// Check if the directory contains an index.html file
|
|
207
|
-
const pathIsDirectory = await isDirectory(path);
|
|
208
|
-
if (pathIsDirectory) {
|
|
209
|
-
const files = await getFilesInDirectoryAndSubdirectories(path);
|
|
210
|
-
const indexHtml = files.find((file) => file.href === 'index.html');
|
|
211
|
-
if (!indexHtml) {
|
|
212
|
-
consola.error('The directory must contain an `index.html` file.');
|
|
213
|
-
process.exit(1);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
else if (zip.isZipped(path)) {
|
|
217
|
-
// No-op
|
|
218
|
-
}
|
|
219
|
-
else {
|
|
220
|
-
consola.error('The path must be either a folder or a zip file.');
|
|
221
|
-
process.exit(1);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
// Check that the path is a directory when creating a bundle with an artifact type
|
|
225
|
-
if (artifactType === 'manifest' && path) {
|
|
226
|
-
const pathIsDirectory = await isDirectory(path);
|
|
227
|
-
if (!pathIsDirectory) {
|
|
228
|
-
consola.error('The path must be a folder when creating a bundle with an artifact type of `manifest`.');
|
|
229
|
-
process.exit(1);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
// Check that a URL is not provided when creating a bundle with an artifact type of manifest
|
|
233
|
-
if (artifactType === 'manifest' && url) {
|
|
234
|
-
consola.error('It is not yet possible to provide a URL when creating a bundle with an artifact type of `manifest`.');
|
|
235
|
-
process.exit(1);
|
|
236
|
-
}
|
|
237
|
-
// Track if we found a Capacitor configuration but no app ID (for showing setup hint later)
|
|
238
|
-
if (!appId) {
|
|
239
|
-
// Try to auto-detect appId from Capacitor configuration
|
|
240
|
-
if (capacitorConfigPath) {
|
|
241
|
-
const configAppId = await getLiveUpdatePluginAppIdFromConfig(capacitorConfigPath);
|
|
242
|
-
if (configAppId) {
|
|
243
|
-
consola.success(`Auto-detected Capawesome Cloud app ID "${configAppId}" from Capacitor configuration.`);
|
|
244
|
-
appId = configAppId;
|
|
245
|
-
}
|
|
246
|
-
else {
|
|
247
|
-
consola.warn('No Capawesome Cloud app ID found in Capacitor configuration (`plugins.LiveUpdate.appId`).');
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
// If still no appId, prompt the user
|
|
251
|
-
if (!appId) {
|
|
252
|
-
if (!isInteractive()) {
|
|
253
|
-
consola.error('You must provide an app ID when running in non-interactive environment.');
|
|
254
|
-
process.exit(1);
|
|
255
|
-
}
|
|
256
|
-
const organizations = await organizationsService.findAll();
|
|
257
|
-
if (organizations.length === 0) {
|
|
258
|
-
consola.error('You must create an organization before creating a bundle.');
|
|
259
|
-
process.exit(1);
|
|
260
|
-
}
|
|
261
|
-
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
262
|
-
const organizationId = await prompt('Select the organization of the app for which you want to create a bundle.', {
|
|
263
|
-
type: 'select',
|
|
264
|
-
options: organizations.map((organization) => ({ label: organization.name, value: organization.id })),
|
|
265
|
-
});
|
|
266
|
-
if (!organizationId) {
|
|
267
|
-
consola.error('You must select the organization of an app for which you want to create a bundle.');
|
|
268
|
-
process.exit(1);
|
|
269
|
-
}
|
|
270
|
-
const apps = await appsService.findAll({
|
|
271
|
-
organizationId,
|
|
272
|
-
});
|
|
273
|
-
if (apps.length === 0) {
|
|
274
|
-
consola.error('You must create an app before creating a bundle.');
|
|
275
|
-
process.exit(1);
|
|
276
|
-
}
|
|
277
|
-
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
278
|
-
appId = await prompt('Which app do you want to deploy to:', {
|
|
279
|
-
type: 'select',
|
|
280
|
-
options: apps.map((app) => ({ label: app.name, value: app.id })),
|
|
281
|
-
});
|
|
282
|
-
if (!appId) {
|
|
283
|
-
consola.error('You must select an app to deploy to.');
|
|
284
|
-
process.exit(1);
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
if (!channel && isInteractive()) {
|
|
289
|
-
const shouldDeployToChannel = await prompt('Do you want to deploy to a specific channel?', {
|
|
290
|
-
type: 'confirm',
|
|
291
|
-
initial: false,
|
|
292
|
-
});
|
|
293
|
-
if (shouldDeployToChannel) {
|
|
294
|
-
channel = await prompt('Enter the channel name:', {
|
|
295
|
-
type: 'text',
|
|
296
|
-
});
|
|
297
|
-
if (!channel) {
|
|
298
|
-
consola.error('The channel name must be at least one character long.');
|
|
299
|
-
process.exit(1);
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
// Check if public key is configured but no private key was provided
|
|
304
|
-
if (!privateKey && capacitorConfigPath) {
|
|
305
|
-
const publicKey = await getLiveUpdatePluginPublicKeyFromConfig(capacitorConfigPath);
|
|
306
|
-
if (publicKey) {
|
|
307
|
-
consola.warn('A public key for verifying the integrity of the bundles is configured in your Capacitor configuration, but no private key has been provided for signing this bundle.');
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
// Create the private key buffer
|
|
311
|
-
let privateKeyBuffer;
|
|
312
|
-
if (privateKey) {
|
|
313
|
-
if (isPrivateKeyContent(privateKey)) {
|
|
314
|
-
// Handle plain text private key content
|
|
315
|
-
const formattedPrivateKey = formatPrivateKey(privateKey);
|
|
316
|
-
privateKeyBuffer = createBufferFromString(formattedPrivateKey);
|
|
317
|
-
}
|
|
318
|
-
else if (privateKey.endsWith('.pem')) {
|
|
319
|
-
// Handle file path
|
|
320
|
-
const fileExists = await fileExistsAtPath(privateKey);
|
|
321
|
-
if (fileExists) {
|
|
322
|
-
const keyBuffer = await createBufferFromPath(privateKey);
|
|
323
|
-
const keyContent = keyBuffer.toString('utf8');
|
|
324
|
-
const formattedPrivateKey = formatPrivateKey(keyContent);
|
|
325
|
-
privateKeyBuffer = createBufferFromString(formattedPrivateKey);
|
|
326
|
-
}
|
|
327
|
-
else {
|
|
328
|
-
consola.error('Private key file not found.');
|
|
329
|
-
process.exit(1);
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
else {
|
|
333
|
-
consola.error('Private key must be either a path to a .pem file or the private key content as plain text.');
|
|
334
|
-
process.exit(1);
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
// Get app details for confirmation
|
|
338
|
-
const app = await appsService.findOne({ appId });
|
|
339
|
-
const appName = app.name;
|
|
340
|
-
// Final confirmation before creating bundle
|
|
341
|
-
if (path && isInteractive()) {
|
|
342
|
-
const relativePath = pathModule.relative(process.cwd(), path);
|
|
343
|
-
const confirmed = await prompt(`Are you sure you want to create a bundle from path "${relativePath}" for app "${appName}" (${appId})?`, {
|
|
344
|
-
type: 'confirm',
|
|
345
|
-
});
|
|
346
|
-
if (!confirmed) {
|
|
347
|
-
consola.info('Bundle creation cancelled.');
|
|
348
|
-
process.exit(0);
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
// Create the app bundle
|
|
352
|
-
let appBundleId;
|
|
353
|
-
try {
|
|
354
|
-
consola.start('Creating bundle...');
|
|
355
|
-
let checksum;
|
|
356
|
-
let signature;
|
|
357
|
-
if (path && url) {
|
|
358
|
-
// Create the file buffer
|
|
359
|
-
if (!zip.isZipped(path)) {
|
|
360
|
-
consola.error('The path must be a zip file when providing a URL.');
|
|
361
|
-
process.exit(1);
|
|
362
|
-
}
|
|
363
|
-
const fileBuffer = await createBufferFromPath(path);
|
|
364
|
-
// Generate checksum
|
|
365
|
-
checksum = await createHash(fileBuffer);
|
|
366
|
-
// Sign the bundle
|
|
367
|
-
if (privateKeyBuffer) {
|
|
368
|
-
signature = await createSignature(privateKeyBuffer, fileBuffer);
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
const response = await appBundlesService.create({
|
|
372
|
-
appId,
|
|
373
|
-
artifactType,
|
|
374
|
-
channelName: channel,
|
|
375
|
-
checksum,
|
|
376
|
-
eqAndroidAppVersionCode: androidEq,
|
|
377
|
-
eqIosAppVersionCode: iosEq,
|
|
378
|
-
gitCommitMessage: commitMessage,
|
|
379
|
-
gitCommitRef: commitRef,
|
|
380
|
-
gitCommitSha: commitSha,
|
|
381
|
-
customProperties: parseCustomProperties(customProperty),
|
|
382
|
-
expiresAt,
|
|
383
|
-
url,
|
|
384
|
-
maxAndroidAppVersionCode: androidMax,
|
|
385
|
-
maxIosAppVersionCode: iosMax,
|
|
386
|
-
minAndroidAppVersionCode: androidMin,
|
|
387
|
-
minIosAppVersionCode: iosMin,
|
|
388
|
-
rolloutPercentage: rollout,
|
|
389
|
-
signature,
|
|
390
|
-
});
|
|
391
|
-
appBundleId = response.id;
|
|
392
|
-
if (path) {
|
|
393
|
-
if (url) {
|
|
394
|
-
// Important: Do NOT upload files if the URL is provided.
|
|
395
|
-
// The user wants to self-host the bundle. The path is only needed for code signing.
|
|
396
|
-
}
|
|
397
|
-
else {
|
|
398
|
-
let appBundleFileId;
|
|
399
|
-
// Upload the app bundle files
|
|
400
|
-
if (artifactType === 'manifest') {
|
|
401
|
-
await uploadFiles({ appId, appBundleId: response.id, path, privateKeyBuffer });
|
|
402
|
-
}
|
|
403
|
-
else {
|
|
404
|
-
const result = await uploadZip({ appId, appBundleId: response.id, path, privateKeyBuffer });
|
|
405
|
-
appBundleFileId = result.appBundleFileId;
|
|
406
|
-
}
|
|
407
|
-
// Update the app bundle
|
|
408
|
-
consola.start('Updating bundle...');
|
|
409
|
-
await appBundlesService.update({
|
|
410
|
-
appBundleFileId,
|
|
411
|
-
appId,
|
|
412
|
-
artifactStatus: 'ready',
|
|
413
|
-
appBundleId: response.id,
|
|
414
|
-
});
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
consola.success('Bundle successfully created.');
|
|
418
|
-
consola.info(`Bundle ID: ${response.id}`);
|
|
419
|
-
}
|
|
420
|
-
catch (error) {
|
|
421
|
-
if (appBundleId) {
|
|
422
|
-
await appBundlesService.delete({ appId, appBundleId }).catch(() => {
|
|
423
|
-
// No-op
|
|
424
|
-
});
|
|
425
|
-
}
|
|
426
|
-
throw error;
|
|
427
|
-
}
|
|
84
|
+
consola.warn('The `apps:bundles:create` command has been deprecated.');
|
|
85
|
+
consola.info('Please use one of the following commands instead:');
|
|
86
|
+
consola.info(' - `apps:liveupdates:upload` to upload a bundle to Capawesome Cloud');
|
|
87
|
+
consola.info(' - `apps:liveupdates:register` to register a self-hosted bundle URL');
|
|
88
|
+
process.exit(0);
|
|
428
89
|
},
|
|
429
90
|
});
|
|
430
|
-
const uploadFile = async (options) => {
|
|
431
|
-
let { appId, appBundleId, buffer, href, mimeType, name, privateKeyBuffer, retryOnFailure } = options;
|
|
432
|
-
try {
|
|
433
|
-
// Generate checksum
|
|
434
|
-
const hash = await createHash(buffer);
|
|
435
|
-
// Sign the bundle
|
|
436
|
-
let signature;
|
|
437
|
-
if (privateKeyBuffer) {
|
|
438
|
-
signature = await createSignature(privateKeyBuffer, buffer);
|
|
439
|
-
}
|
|
440
|
-
// Create the multipart upload
|
|
441
|
-
return await appBundleFilesService.create({
|
|
442
|
-
appId,
|
|
443
|
-
appBundleId,
|
|
444
|
-
buffer,
|
|
445
|
-
checksum: hash,
|
|
446
|
-
href,
|
|
447
|
-
mimeType,
|
|
448
|
-
name,
|
|
449
|
-
signature,
|
|
450
|
-
});
|
|
451
|
-
}
|
|
452
|
-
catch (error) {
|
|
453
|
-
if (retryOnFailure) {
|
|
454
|
-
return uploadFile({
|
|
455
|
-
...options,
|
|
456
|
-
retryOnFailure: false,
|
|
457
|
-
});
|
|
458
|
-
}
|
|
459
|
-
throw error;
|
|
460
|
-
}
|
|
461
|
-
};
|
|
462
|
-
const uploadFiles = async (options) => {
|
|
463
|
-
let { appId, appBundleId, path, privateKeyBuffer } = options;
|
|
464
|
-
// Generate the manifest file
|
|
465
|
-
await generateManifestJson(path);
|
|
466
|
-
// Get all files in the directory
|
|
467
|
-
const files = await getFilesInDirectoryAndSubdirectories(path);
|
|
468
|
-
// Iterate over each file
|
|
469
|
-
let fileIndex = 0;
|
|
470
|
-
const uploadNextFile = async () => {
|
|
471
|
-
if (fileIndex >= files.length) {
|
|
472
|
-
return;
|
|
473
|
-
}
|
|
474
|
-
const file = files[fileIndex];
|
|
475
|
-
fileIndex++;
|
|
476
|
-
consola.start(`Uploading file (${fileIndex}/${files.length})...`);
|
|
477
|
-
const buffer = await createBufferFromPath(file.path);
|
|
478
|
-
await uploadFile({
|
|
479
|
-
appId,
|
|
480
|
-
appBundleId: appBundleId,
|
|
481
|
-
buffer,
|
|
482
|
-
href: file.href,
|
|
483
|
-
mimeType: file.mimeType,
|
|
484
|
-
name: file.name,
|
|
485
|
-
privateKeyBuffer: privateKeyBuffer,
|
|
486
|
-
retryOnFailure: true,
|
|
487
|
-
});
|
|
488
|
-
await uploadNextFile();
|
|
489
|
-
};
|
|
490
|
-
const uploadPromises = Array.from({ length: MAX_CONCURRENT_UPLOADS });
|
|
491
|
-
for (let i = 0; i < MAX_CONCURRENT_UPLOADS; i++) {
|
|
492
|
-
uploadPromises[i] = uploadNextFile();
|
|
493
|
-
}
|
|
494
|
-
await Promise.all(uploadPromises);
|
|
495
|
-
};
|
|
496
|
-
const uploadZip = async (options) => {
|
|
497
|
-
let { appId, appBundleId, path, privateKeyBuffer } = options;
|
|
498
|
-
// Read the zip file
|
|
499
|
-
let fileBuffer;
|
|
500
|
-
if (zip.isZipped(path)) {
|
|
501
|
-
const readStream = createReadStream(path);
|
|
502
|
-
fileBuffer = await createBufferFromReadStream(readStream);
|
|
503
|
-
}
|
|
504
|
-
else {
|
|
505
|
-
consola.start('Zipping folder...');
|
|
506
|
-
fileBuffer = await zip.zipFolder(path);
|
|
507
|
-
}
|
|
508
|
-
// Upload the zip file
|
|
509
|
-
consola.start('Uploading file...');
|
|
510
|
-
const result = await uploadFile({
|
|
511
|
-
appId,
|
|
512
|
-
appBundleId: appBundleId,
|
|
513
|
-
buffer: fileBuffer,
|
|
514
|
-
mimeType: 'application/zip',
|
|
515
|
-
name: 'bundle.zip',
|
|
516
|
-
privateKeyBuffer: privateKeyBuffer,
|
|
517
|
-
});
|
|
518
|
-
return {
|
|
519
|
-
appBundleFileId: result.id,
|
|
520
|
-
};
|
|
521
|
-
};
|
|
522
|
-
const parseCustomProperties = (customProperty) => {
|
|
523
|
-
let customProperties;
|
|
524
|
-
if (customProperty) {
|
|
525
|
-
customProperties = {};
|
|
526
|
-
for (const property of customProperty) {
|
|
527
|
-
const [key, value] = property.split('=');
|
|
528
|
-
if (key && value) {
|
|
529
|
-
customProperties[key] = value;
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
return customProperties;
|
|
534
|
-
};
|
|
@@ -1,11 +1,5 @@
|
|
|
1
|
-
import appBundlesService from '../../../services/app-bundles.js';
|
|
2
|
-
import appsService from '../../../services/apps.js';
|
|
3
|
-
import authorizationService from '../../../services/authorization-service.js';
|
|
4
|
-
import organizationsService from '../../../services/organizations.js';
|
|
5
|
-
import { prompt } from '../../../utils/prompt.js';
|
|
6
1
|
import { defineCommand, defineOptions } from '@robingenz/zli';
|
|
7
2
|
import consola from 'consola';
|
|
8
|
-
import { isInteractive } from '../../../utils/environment.js';
|
|
9
3
|
import { z } from 'zod';
|
|
10
4
|
export default defineCommand({
|
|
11
5
|
description: 'Delete an app bundle.',
|
|
@@ -14,67 +8,8 @@ export default defineCommand({
|
|
|
14
8
|
bundleId: z.string().optional().describe('ID of the bundle.'),
|
|
15
9
|
})),
|
|
16
10
|
action: async (options, args) => {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
process.exit(1);
|
|
21
|
-
}
|
|
22
|
-
// Prompt for missing arguments
|
|
23
|
-
if (!appId) {
|
|
24
|
-
if (!isInteractive()) {
|
|
25
|
-
consola.error('You must provide an app ID when running in non-interactive environment.');
|
|
26
|
-
process.exit(1);
|
|
27
|
-
}
|
|
28
|
-
const organizations = await organizationsService.findAll();
|
|
29
|
-
if (organizations.length === 0) {
|
|
30
|
-
consola.error('You must create an organization before deleting a bundle.');
|
|
31
|
-
process.exit(1);
|
|
32
|
-
}
|
|
33
|
-
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
34
|
-
const organizationId = await prompt('Select the organization of the app from which you want to delete a bundle.', {
|
|
35
|
-
type: 'select',
|
|
36
|
-
options: organizations.map((organization) => ({ label: organization.name, value: organization.id })),
|
|
37
|
-
});
|
|
38
|
-
if (!organizationId) {
|
|
39
|
-
consola.error('You must select the organization of an app from which you want to delete a bundle.');
|
|
40
|
-
process.exit(1);
|
|
41
|
-
}
|
|
42
|
-
const apps = await appsService.findAll({
|
|
43
|
-
organizationId,
|
|
44
|
-
});
|
|
45
|
-
if (!apps.length) {
|
|
46
|
-
consola.error('You must create an app before deleting a bundle.');
|
|
47
|
-
process.exit(1);
|
|
48
|
-
}
|
|
49
|
-
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
50
|
-
appId = await prompt('Which app do you want to delete the bundle from?', {
|
|
51
|
-
type: 'select',
|
|
52
|
-
options: apps.map((app) => ({ label: app.name, value: app.id })),
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
if (!bundleId) {
|
|
56
|
-
if (!isInteractive()) {
|
|
57
|
-
consola.error('You must provide the bundle ID when running in non-interactive environment.');
|
|
58
|
-
process.exit(1);
|
|
59
|
-
}
|
|
60
|
-
bundleId = await prompt('Enter the bundle ID:', {
|
|
61
|
-
type: 'text',
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
// Confirm deletion
|
|
65
|
-
if (isInteractive()) {
|
|
66
|
-
const confirmed = await prompt('Are you sure you want to delete this bundle?', {
|
|
67
|
-
type: 'confirm',
|
|
68
|
-
});
|
|
69
|
-
if (!confirmed) {
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
// Delete bundle
|
|
74
|
-
await appBundlesService.delete({
|
|
75
|
-
appId,
|
|
76
|
-
appBundleId: bundleId,
|
|
77
|
-
});
|
|
78
|
-
consola.success('Bundle deleted successfully.');
|
|
11
|
+
consola.warn('The `apps:bundles:delete` command has been deprecated and will be removed in future versions.');
|
|
12
|
+
consola.info('Please refer to the official documentation for alternative approaches.');
|
|
13
|
+
process.exit(0);
|
|
79
14
|
},
|
|
80
15
|
});
|
|
@@ -1,11 +1,5 @@
|
|
|
1
|
-
import appBundlesService from '../../../services/app-bundles.js';
|
|
2
|
-
import appsService from '../../../services/apps.js';
|
|
3
|
-
import authorizationService from '../../../services/authorization-service.js';
|
|
4
|
-
import organizationsService from '../../../services/organizations.js';
|
|
5
|
-
import { prompt } from '../../../utils/prompt.js';
|
|
6
1
|
import { defineCommand, defineOptions } from '@robingenz/zli';
|
|
7
2
|
import consola from 'consola';
|
|
8
|
-
import { isInteractive } from '../../../utils/environment.js';
|
|
9
3
|
import { z } from 'zod';
|
|
10
4
|
export default defineCommand({
|
|
11
5
|
description: 'Update an app bundle.',
|
|
@@ -46,65 +40,8 @@ export default defineCommand({
|
|
|
46
40
|
.describe('The exact iOS bundle version (`CFBundleVersion`) that the bundle should not support.'),
|
|
47
41
|
})),
|
|
48
42
|
action: async (options, args) => {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
process.exit(1);
|
|
53
|
-
}
|
|
54
|
-
// Prompt for missing arguments
|
|
55
|
-
if (!appId) {
|
|
56
|
-
if (!isInteractive()) {
|
|
57
|
-
consola.error('You must provide an app ID when running in non-interactive environment.');
|
|
58
|
-
process.exit(1);
|
|
59
|
-
}
|
|
60
|
-
const organizations = await organizationsService.findAll();
|
|
61
|
-
if (organizations.length === 0) {
|
|
62
|
-
consola.error('You must create an organization before updating a bundle.');
|
|
63
|
-
process.exit(1);
|
|
64
|
-
}
|
|
65
|
-
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
66
|
-
const organizationId = await prompt('Select the organization of the app for which you want to update a bundle.', {
|
|
67
|
-
type: 'select',
|
|
68
|
-
options: organizations.map((organization) => ({ label: organization.name, value: organization.id })),
|
|
69
|
-
});
|
|
70
|
-
if (!organizationId) {
|
|
71
|
-
consola.error('You must select the organization of an app for which you want to update a bundle.');
|
|
72
|
-
process.exit(1);
|
|
73
|
-
}
|
|
74
|
-
const apps = await appsService.findAll({
|
|
75
|
-
organizationId,
|
|
76
|
-
});
|
|
77
|
-
if (!apps.length) {
|
|
78
|
-
consola.error('You must create an app before updating a bundle.');
|
|
79
|
-
process.exit(1);
|
|
80
|
-
}
|
|
81
|
-
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
82
|
-
appId = await prompt('Which app do you want to update the bundle for?', {
|
|
83
|
-
type: 'select',
|
|
84
|
-
options: apps.map((app) => ({ label: app.name, value: app.id })),
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
if (!bundleId) {
|
|
88
|
-
if (!isInteractive()) {
|
|
89
|
-
consola.error('You must provide the bundle ID when running in non-interactive environment.');
|
|
90
|
-
process.exit(1);
|
|
91
|
-
}
|
|
92
|
-
bundleId = await prompt('Enter the bundle ID:', {
|
|
93
|
-
type: 'text',
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
// Update bundle
|
|
97
|
-
await appBundlesService.update({
|
|
98
|
-
appId,
|
|
99
|
-
appBundleId: bundleId,
|
|
100
|
-
maxAndroidAppVersionCode: androidMax,
|
|
101
|
-
maxIosAppVersionCode: iosMax,
|
|
102
|
-
minAndroidAppVersionCode: androidMin,
|
|
103
|
-
minIosAppVersionCode: iosMin,
|
|
104
|
-
eqAndroidAppVersionCode: androidEq,
|
|
105
|
-
eqIosAppVersionCode: iosEq,
|
|
106
|
-
rolloutPercentage: rollout,
|
|
107
|
-
});
|
|
108
|
-
consola.success('Bundle updated successfully.');
|
|
43
|
+
consola.warn('The `apps:bundles:update` command has been deprecated and will be removed in future versions.');
|
|
44
|
+
consola.info('Please refer to the official documentation for alternative approaches.');
|
|
45
|
+
process.exit(0);
|
|
109
46
|
},
|
|
110
47
|
});
|
|
@@ -2,20 +2,16 @@ import appChannelsService from '../../../services/app-channels.js';
|
|
|
2
2
|
import appsService from '../../../services/apps.js';
|
|
3
3
|
import authorizationService from '../../../services/authorization-service.js';
|
|
4
4
|
import organizationsService from '../../../services/organizations.js';
|
|
5
|
+
import { isInteractive } from '../../../utils/environment.js';
|
|
5
6
|
import { getMessageFromUnknownError } from '../../../utils/error.js';
|
|
6
7
|
import { prompt } from '../../../utils/prompt.js';
|
|
7
8
|
import { defineCommand, defineOptions } from '@robingenz/zli';
|
|
8
9
|
import consola from 'consola';
|
|
9
|
-
import { isInteractive } from '../../../utils/environment.js';
|
|
10
10
|
import { z } from 'zod';
|
|
11
11
|
export default defineCommand({
|
|
12
12
|
description: 'Create a new app channel.',
|
|
13
13
|
options: defineOptions(z.object({
|
|
14
14
|
appId: z.string().optional().describe('ID of the app.'),
|
|
15
|
-
bundleLimit: z.coerce
|
|
16
|
-
.number()
|
|
17
|
-
.optional()
|
|
18
|
-
.describe('Maximum number of bundles that can be assigned to the channel. If more bundles are assigned, the oldest bundles will be automatically deleted.'),
|
|
19
15
|
expiresInDays: z.coerce
|
|
20
16
|
.number({
|
|
21
17
|
message: 'Expiration days must be an integer.',
|
|
@@ -27,9 +23,10 @@ export default defineCommand({
|
|
|
27
23
|
.describe('The number of days until the channel is automatically deleted.'),
|
|
28
24
|
ignoreErrors: z.boolean().optional().describe('Whether to ignore errors or not.'),
|
|
29
25
|
name: z.string().optional().describe('Name of the channel.'),
|
|
26
|
+
protected: z.boolean().optional().describe('Whether to protect the channel or not. Default is `false`.'),
|
|
30
27
|
})),
|
|
31
28
|
action: async (options, args) => {
|
|
32
|
-
let { appId,
|
|
29
|
+
let { appId, expiresInDays, ignoreErrors, name, protected: _protected } = options;
|
|
33
30
|
if (!authorizationService.hasAuthorizationToken()) {
|
|
34
31
|
consola.error('You must be logged in to run this command. Please run the `login` command first.');
|
|
35
32
|
process.exit(1);
|
|
@@ -85,12 +82,12 @@ export default defineCommand({
|
|
|
85
82
|
try {
|
|
86
83
|
const response = await appChannelsService.create({
|
|
87
84
|
appId,
|
|
85
|
+
protected: _protected,
|
|
88
86
|
name,
|
|
89
|
-
totalAppBundleLimit: bundleLimit,
|
|
90
87
|
expiresAt,
|
|
91
88
|
});
|
|
92
|
-
consola.success('Channel created successfully.');
|
|
93
89
|
consola.info(`Channel ID: ${response.id}`);
|
|
90
|
+
consola.success('Channel created successfully.');
|
|
94
91
|
}
|
|
95
92
|
catch (error) {
|
|
96
93
|
if (ignoreErrors) {
|