@capawesome/cli 4.5.0 → 4.6.0-dev.0108a83.1774286472

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