@capawesome/cli 4.6.0-dev.bda9e9d.1774345600 → 4.7.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 +24 -0
- package/dist/commands/apps/builds/create.js +98 -12
- package/dist/commands/apps/bundles/create.js +4 -2
- package/dist/commands/apps/bundles/delete.js +2 -3
- package/dist/commands/apps/bundles/update.js +2 -3
- package/dist/commands/apps/certificates/delete.js +28 -5
- package/dist/commands/apps/certificates/get.js +28 -5
- package/dist/commands/apps/deployments/create.js +5 -77
- package/dist/commands/apps/devices/forcechannel.js +9 -7
- package/dist/commands/apps/devices/unforcechannel.js +9 -7
- package/dist/commands/apps/link.js +34 -0
- package/dist/commands/apps/link.test.js +94 -0
- package/dist/commands/apps/liveupdates/bundle.js +7 -2
- package/dist/commands/apps/liveupdates/create.js +148 -44
- package/dist/commands/apps/liveupdates/create.test.js +300 -0
- package/dist/commands/apps/liveupdates/generate-manifest.js +12 -1
- package/dist/commands/apps/liveupdates/generate-manifest.test.js +21 -1
- package/dist/commands/apps/liveupdates/register.js +10 -15
- package/dist/commands/apps/liveupdates/upload.js +18 -16
- package/dist/commands/apps/transfer.js +47 -0
- package/dist/commands/apps/transfer.test.js +123 -0
- package/dist/commands/apps/unlink.js +35 -0
- package/dist/commands/apps/unlink.test.js +99 -0
- package/dist/commands/manifests/generate.js +1 -1
- package/dist/index.js +3 -0
- package/dist/services/app-build-sources.js +120 -0
- package/dist/services/app-devices.js +8 -0
- package/dist/services/apps.js +25 -0
- package/dist/services/authorization-service.js +5 -1
- package/dist/services/jobs.js +13 -0
- package/dist/types/app-build-source.js +1 -0
- package/dist/types/index.js +1 -0
- package/dist/utils/custom-properties.js +22 -0
- package/dist/utils/file.js +8 -1
- package/dist/utils/git.js +91 -0
- package/dist/utils/git.test.js +130 -0
- package/dist/utils/{build.js → job.js} +26 -23
- package/dist/utils/prompt.js +1 -1
- package/dist/utils/zip.js +19 -2
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,30 @@
|
|
|
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.7.0](https://github.com/capawesome-team/cli/compare/v4.6.0...v4.7.0) (2026-04-04)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **apps:builds:create:** support ad hoc environment variables ([#134](https://github.com/capawesome-team/cli/issues/134)) ([80c962a](https://github.com/capawesome-team/cli/commit/80c962abbc7548d32a7c0782860632117817d62b))
|
|
11
|
+
* **apps:certificates:** add `--type` option to `get` and `delete` commands ([#135](https://github.com/capawesome-team/cli/issues/135)) ([b9bdc51](https://github.com/capawesome-team/cli/commit/b9bdc5190a7a19b82589876cc798a30a83a4c5f2))
|
|
12
|
+
* **apps:certificates:** deprecate `--type` option ([#130](https://github.com/capawesome-team/cli/issues/130)) ([c53b766](https://github.com/capawesome-team/cli/commit/c53b76697189e40c9b96f8ad679e058775c23cd9))
|
|
13
|
+
* **apps:devices:** support multiple `--device-id` flags for `forcechannel` and `unforcechannel` ([#132](https://github.com/capawesome-team/cli/issues/132)) ([f61b812](https://github.com/capawesome-team/cli/commit/f61b8126d862eb44c1c1989692e823fdb95b0635))
|
|
14
|
+
* **apps:liveupdates:** add `apps:liveupdates:create` command ([#133](https://github.com/capawesome-team/cli/issues/133)) ([244c0b0](https://github.com/capawesome-team/cli/commit/244c0b0f785548083c555453c63941584135bc17))
|
|
15
|
+
* **apps:liveupdates:** add source map detection and warning ([f90427c](https://github.com/capawesome-team/cli/commit/f90427c0f1d9a691f609f481c4b098c51f6adb1f))
|
|
16
|
+
* **apps:liveupdates:** support multiple `--channel` options in `create` command ([#140](https://github.com/capawesome-team/cli/issues/140)) ([12df3ca](https://github.com/capawesome-team/cli/commit/12df3ca100cb53196064ca18c3f5970b7326b1a2))
|
|
17
|
+
* **apps:** add `--path` and `--url` options ([#131](https://github.com/capawesome-team/cli/issues/131)) ([8ae803e](https://github.com/capawesome-team/cli/commit/8ae803e38b74634be3eb32333aca072f722c1bf4))
|
|
18
|
+
* **apps:** add `apps:link` and `apps:unlink` commands ([#141](https://github.com/capawesome-team/cli/issues/141)) ([e37e2fe](https://github.com/capawesome-team/cli/commit/e37e2fe8c8d80845e92379a7f859564f78725052))
|
|
19
|
+
* **apps:** add `apps:transfer` command ([#142](https://github.com/capawesome-team/cli/issues/142)) ([b6f6b57](https://github.com/capawesome-team/cli/commit/b6f6b57943075ac3ab04d52a9a2d62deda85482e))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Bug Fixes
|
|
23
|
+
|
|
24
|
+
* **apps:liveupdates:** improve error message for invalid private keys ([#138](https://github.com/capawesome-team/cli/issues/138)) ([7752df3](https://github.com/capawesome-team/cli/commit/7752df3fc94f61555d07f7e2e9488d51fca0ae9b))
|
|
25
|
+
* **apps:liveupdates:** skip symlinks and validate directory path in live update commands ([#139](https://github.com/capawesome-team/cli/issues/139)) ([e1ad835](https://github.com/capawesome-team/cli/commit/e1ad83592ea0e6fdf21ea888f39b5dce66a63e29))
|
|
26
|
+
* **login:** trim authorization token to prevent invalid header characters ([#137](https://github.com/capawesome-team/cli/issues/137)) ([d49d410](https://github.com/capawesome-team/cli/commit/d49d4104c4392d32bfc0fb351e5837a74e02b6b3))
|
|
27
|
+
* **utils:** use error instead of warn for canceled build status ([ef3ab54](https://github.com/capawesome-team/cli/commit/ef3ab540416553f71047a0400a6af4e28da32568))
|
|
28
|
+
|
|
5
29
|
## [4.6.0](https://github.com/capawesome-team/cli/compare/v4.5.0...v4.6.0) (2026-03-18)
|
|
6
30
|
|
|
7
31
|
|
|
@@ -1,11 +1,14 @@
|
|
|
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';
|
|
5
|
-
import {
|
|
6
|
+
import { parseKeyValuePairs } from '../../../utils/app-environments.js';
|
|
6
7
|
import { withAuth } from '../../../utils/auth.js';
|
|
7
8
|
import { isInteractive } from '../../../utils/environment.js';
|
|
9
|
+
import { waitForJobCompletion } from '../../../utils/job.js';
|
|
8
10
|
import { prompt, promptAppSelection, promptOrganizationSelection } from '../../../utils/prompt.js';
|
|
11
|
+
import zip from '../../../utils/zip.js';
|
|
9
12
|
import { defineCommand, defineOptions } from '@robingenz/zli';
|
|
10
13
|
import consola from 'consola';
|
|
11
14
|
import fs from 'fs/promises';
|
|
@@ -14,7 +17,7 @@ import { z } from 'zod';
|
|
|
14
17
|
const IOS_BUILD_TYPES = ['simulator', 'development', 'ad-hoc', 'app-store', 'enterprise'];
|
|
15
18
|
const ANDROID_BUILD_TYPES = ['debug', 'release'];
|
|
16
19
|
export default defineCommand({
|
|
17
|
-
description: 'Create a new app build.',
|
|
20
|
+
description: 'Create a new app build on Capawesome Cloud Runners.',
|
|
18
21
|
options: defineOptions(z.object({
|
|
19
22
|
aab: z
|
|
20
23
|
.union([z.boolean(), z.string()])
|
|
@@ -44,6 +47,7 @@ export default defineCommand({
|
|
|
44
47
|
.optional()
|
|
45
48
|
.describe('Download the generated IPA file (iOS only). Optionally provide a file path.'),
|
|
46
49
|
json: z.boolean().optional().describe('Output in JSON format.'),
|
|
50
|
+
path: z.string().optional().describe('Path to local source files to upload.'),
|
|
47
51
|
platform: z
|
|
48
52
|
.enum(['ios', 'android', 'web'], {
|
|
49
53
|
message: 'Platform must be either `ios`, `android`, or `web`.',
|
|
@@ -56,10 +60,19 @@ export default defineCommand({
|
|
|
56
60
|
})
|
|
57
61
|
.optional()
|
|
58
62
|
.describe('The build stack to use for the build process.'),
|
|
63
|
+
url: z.string().optional().describe('URL to a zip file to use as build source.'),
|
|
59
64
|
type: z
|
|
60
65
|
.string()
|
|
61
66
|
.optional()
|
|
62
67
|
.describe('The type of build. For iOS, supported values are `simulator`, `development`, `ad-hoc`, `app-store`, and `enterprise`. For Android, supported values are `debug` and `release`. For Web, no type is required.'),
|
|
68
|
+
variable: z
|
|
69
|
+
.array(z.string())
|
|
70
|
+
.optional()
|
|
71
|
+
.describe('Ad hoc environment variable in key=value format. Can be specified multiple times.'),
|
|
72
|
+
variableFile: z
|
|
73
|
+
.string()
|
|
74
|
+
.optional()
|
|
75
|
+
.describe('Path to a file containing ad hoc environment variables in .env format.'),
|
|
63
76
|
zip: z
|
|
64
77
|
.union([z.boolean(), z.string()])
|
|
65
78
|
.optional()
|
|
@@ -67,7 +80,7 @@ export default defineCommand({
|
|
|
67
80
|
yes: z.boolean().optional().describe('Skip confirmation prompts.'),
|
|
68
81
|
}), { y: 'yes' }),
|
|
69
82
|
action: withAuth(async (options) => {
|
|
70
|
-
let { appId, platform, type, gitRef, environment, certificate, json, stack } = options;
|
|
83
|
+
let { appId, platform, type, gitRef, environment, certificate, json, stack, path: sourcePath, url } = options;
|
|
71
84
|
// Validate that detached flag cannot be used with artifact flags
|
|
72
85
|
if (options.detached && (options.apk || options.aab || options.ipa || options.zip)) {
|
|
73
86
|
consola.error('The --detached flag cannot be used with --apk, --aab, --ipa, or --zip flags.');
|
|
@@ -83,6 +96,39 @@ export default defineCommand({
|
|
|
83
96
|
consola.error('The --channel and --destination flags cannot be used together.');
|
|
84
97
|
process.exit(1);
|
|
85
98
|
}
|
|
99
|
+
// Validate that path, url, and gitRef cannot be used together
|
|
100
|
+
if (sourcePath && gitRef) {
|
|
101
|
+
consola.error('The --path and --git-ref flags cannot be used together.');
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
if (url && gitRef) {
|
|
105
|
+
consola.error('The --url and --git-ref flags cannot be used together.');
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
if (url && sourcePath) {
|
|
109
|
+
consola.error('The --url and --path flags cannot be used together.');
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
// Validate url if provided
|
|
113
|
+
if (url) {
|
|
114
|
+
consola.warn('The --url option is experimental and may change in the future.');
|
|
115
|
+
}
|
|
116
|
+
// Validate path if provided
|
|
117
|
+
if (sourcePath) {
|
|
118
|
+
consola.warn('The --path option is experimental and may change in the future.');
|
|
119
|
+
const resolvedPath = path.resolve(sourcePath);
|
|
120
|
+
const stat = await fs.stat(resolvedPath).catch(() => null);
|
|
121
|
+
if (!stat || !stat.isDirectory()) {
|
|
122
|
+
consola.error('The --path must point to an existing directory.');
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
const packageJsonPath = path.join(resolvedPath, 'package.json');
|
|
126
|
+
const packageJsonStat = await fs.stat(packageJsonPath).catch(() => null);
|
|
127
|
+
if (!packageJsonStat || !packageJsonStat.isFile()) {
|
|
128
|
+
consola.error('The directory specified by --path must contain a package.json file.');
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
86
132
|
// Prompt for app ID if not provided
|
|
87
133
|
if (!appId) {
|
|
88
134
|
if (!isInteractive()) {
|
|
@@ -112,10 +158,10 @@ export default defineCommand({
|
|
|
112
158
|
process.exit(1);
|
|
113
159
|
}
|
|
114
160
|
}
|
|
115
|
-
// Prompt for git ref if not provided
|
|
116
|
-
if (!gitRef) {
|
|
161
|
+
// Prompt for git ref if not provided and no path or url specified
|
|
162
|
+
if (!sourcePath && !url && !gitRef) {
|
|
117
163
|
if (!isInteractive()) {
|
|
118
|
-
consola.error('You must provide a git ref when running in non-interactive environment.');
|
|
164
|
+
consola.error('You must provide a git ref, path, or url when running in non-interactive environment.');
|
|
119
165
|
process.exit(1);
|
|
120
166
|
}
|
|
121
167
|
gitRef = await prompt('Enter the Git reference (branch, tag, or commit SHA):', {
|
|
@@ -196,9 +242,48 @@ export default defineCommand({
|
|
|
196
242
|
}
|
|
197
243
|
}
|
|
198
244
|
}
|
|
245
|
+
// Parse ad hoc environment variables from inline and file
|
|
246
|
+
const variablesMap = new Map();
|
|
247
|
+
if (options.variableFile) {
|
|
248
|
+
const fileContent = await fs.readFile(options.variableFile, 'utf-8');
|
|
249
|
+
const fileVariables = parseKeyValuePairs(fileContent);
|
|
250
|
+
fileVariables.forEach((v) => variablesMap.set(v.key, v.value));
|
|
251
|
+
}
|
|
252
|
+
if (options.variable) {
|
|
253
|
+
const inlineVariables = parseKeyValuePairs(options.variable.join('\n'));
|
|
254
|
+
inlineVariables.forEach((v) => variablesMap.set(v.key, v.value));
|
|
255
|
+
}
|
|
256
|
+
const adHocEnvironmentVariables = variablesMap.size > 0 ? Object.fromEntries(variablesMap) : undefined;
|
|
257
|
+
// Create build source from URL if provided
|
|
258
|
+
let appBuildSourceId;
|
|
259
|
+
if (url) {
|
|
260
|
+
consola.start('Creating build source from URL...');
|
|
261
|
+
const appBuildSource = await appBuildSourcesService.createFromUrl({ appId, fileUrl: url });
|
|
262
|
+
appBuildSourceId = appBuildSource.id;
|
|
263
|
+
consola.success('Build source created successfully.');
|
|
264
|
+
}
|
|
265
|
+
// Upload source files if path is provided
|
|
266
|
+
if (sourcePath) {
|
|
267
|
+
const resolvedPath = path.resolve(sourcePath);
|
|
268
|
+
consola.start('Zipping source files...');
|
|
269
|
+
const buffer = await zip.zipFolderWithGitignore(resolvedPath);
|
|
270
|
+
consola.start('Uploading source files...');
|
|
271
|
+
const appBuildSource = await appBuildSourcesService.createFromFile({
|
|
272
|
+
appId,
|
|
273
|
+
fileSizeInBytes: buffer.byteLength,
|
|
274
|
+
buffer,
|
|
275
|
+
name: 'source.zip',
|
|
276
|
+
}, (currentPart, totalParts) => {
|
|
277
|
+
consola.start(`Uploading source files (${currentPart}/${totalParts})...`);
|
|
278
|
+
});
|
|
279
|
+
appBuildSourceId = appBuildSource.id;
|
|
280
|
+
consola.success('Source files uploaded successfully.');
|
|
281
|
+
}
|
|
199
282
|
// Create the app build
|
|
200
283
|
consola.start('Creating build...');
|
|
201
284
|
const response = await appBuildsService.create({
|
|
285
|
+
adHocEnvironmentVariables,
|
|
286
|
+
appBuildSourceId,
|
|
202
287
|
appCertificateName: certificate,
|
|
203
288
|
appEnvironmentName: environment,
|
|
204
289
|
appId,
|
|
@@ -214,10 +299,11 @@ export default defineCommand({
|
|
|
214
299
|
// Wait for build job to complete by default, unless --detached flag is set
|
|
215
300
|
const shouldWait = !options.detached;
|
|
216
301
|
if (shouldWait) {
|
|
217
|
-
|
|
302
|
+
await waitForJobCompletion({ jobId: response.jobId });
|
|
303
|
+
const appBuild = await appBuildsService.findOne({
|
|
218
304
|
appId,
|
|
219
305
|
appBuildId: response.id,
|
|
220
|
-
relations: 'appBuildArtifacts
|
|
306
|
+
relations: 'appBuildArtifacts',
|
|
221
307
|
});
|
|
222
308
|
consola.info(`Build ID: ${response.id}`);
|
|
223
309
|
consola.info(`Build Number: ${response.numberAsString}`);
|
|
@@ -229,7 +315,7 @@ export default defineCommand({
|
|
|
229
315
|
await handleArtifactDownload({
|
|
230
316
|
appId,
|
|
231
317
|
buildId: response.id,
|
|
232
|
-
buildArtifacts:
|
|
318
|
+
buildArtifacts: appBuild.appBuildArtifacts,
|
|
233
319
|
artifactType: 'apk',
|
|
234
320
|
filePath: typeof options.apk === 'string' ? options.apk : undefined,
|
|
235
321
|
});
|
|
@@ -238,7 +324,7 @@ export default defineCommand({
|
|
|
238
324
|
await handleArtifactDownload({
|
|
239
325
|
appId,
|
|
240
326
|
buildId: response.id,
|
|
241
|
-
buildArtifacts:
|
|
327
|
+
buildArtifacts: appBuild.appBuildArtifacts,
|
|
242
328
|
artifactType: 'aab',
|
|
243
329
|
filePath: typeof options.aab === 'string' ? options.aab : undefined,
|
|
244
330
|
});
|
|
@@ -247,7 +333,7 @@ export default defineCommand({
|
|
|
247
333
|
await handleArtifactDownload({
|
|
248
334
|
appId,
|
|
249
335
|
buildId: response.id,
|
|
250
|
-
buildArtifacts:
|
|
336
|
+
buildArtifacts: appBuild.appBuildArtifacts,
|
|
251
337
|
artifactType: 'ipa',
|
|
252
338
|
filePath: typeof options.ipa === 'string' ? options.ipa : undefined,
|
|
253
339
|
});
|
|
@@ -256,7 +342,7 @@ export default defineCommand({
|
|
|
256
342
|
await handleArtifactDownload({
|
|
257
343
|
appId,
|
|
258
344
|
buildId: response.id,
|
|
259
|
-
buildArtifacts:
|
|
345
|
+
buildArtifacts: appBuild.appBuildArtifacts,
|
|
260
346
|
artifactType: 'zip',
|
|
261
347
|
filePath: typeof options.zip === 'string' ? options.zip : undefined,
|
|
262
348
|
});
|
|
@@ -2,7 +2,7 @@ import { defineCommand, defineOptions } from '@robingenz/zli';
|
|
|
2
2
|
import consola from 'consola';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
export default defineCommand({
|
|
5
|
-
description: 'Create a new app bundle.',
|
|
5
|
+
description: 'Create a new app bundle. Deprecated, use the `apps:liveupdates` commands instead.',
|
|
6
6
|
options: defineOptions(z.object({
|
|
7
7
|
androidMax: z.coerce
|
|
8
8
|
.string()
|
|
@@ -38,6 +38,7 @@ export default defineCommand({
|
|
|
38
38
|
commitSha: z.string().optional().describe('The commit sha related to the bundle.'),
|
|
39
39
|
customProperty: z
|
|
40
40
|
.array(z.string().min(1).max(100))
|
|
41
|
+
.max(10)
|
|
41
42
|
.optional()
|
|
42
43
|
.describe('A custom property to assign to the bundle. Must be in the format `key=value`. Can be specified multiple times.'),
|
|
43
44
|
expiresInDays: z.coerce
|
|
@@ -83,7 +84,8 @@ export default defineCommand({
|
|
|
83
84
|
action: async (options, args) => {
|
|
84
85
|
consola.warn('The `apps:bundles:create` command has been deprecated.');
|
|
85
86
|
consola.info('Please use one of the following commands instead:');
|
|
86
|
-
consola.info(' - `apps:liveupdates:
|
|
87
|
+
consola.info(' - `apps:liveupdates:create` to build and deploy using Capawesome Cloud Runners');
|
|
88
|
+
consola.info(' - `apps:liveupdates:upload` to upload a locally built bundle to Capawesome Cloud');
|
|
87
89
|
consola.info(' - `apps:liveupdates:register` to register a self-hosted bundle URL');
|
|
88
90
|
process.exit(1);
|
|
89
91
|
},
|
|
@@ -2,14 +2,13 @@ import { defineCommand, defineOptions } from '@robingenz/zli';
|
|
|
2
2
|
import consola from 'consola';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
export default defineCommand({
|
|
5
|
-
description: 'Delete an app bundle.',
|
|
5
|
+
description: 'Delete an app bundle. Deprecated.',
|
|
6
6
|
options: defineOptions(z.object({
|
|
7
7
|
appId: z.string().optional().describe('ID of the app.'),
|
|
8
8
|
bundleId: z.string().optional().describe('ID of the bundle.'),
|
|
9
9
|
})),
|
|
10
10
|
action: async (options, args) => {
|
|
11
|
-
consola.warn('The `apps:bundles:delete` command has been deprecated
|
|
12
|
-
consola.info('Please refer to the official documentation for alternative approaches.');
|
|
11
|
+
consola.warn('The `apps:bundles:delete` command has been deprecated. Please use the `apps:liveupdates` commands instead.');
|
|
13
12
|
process.exit(1);
|
|
14
13
|
},
|
|
15
14
|
});
|
|
@@ -2,7 +2,7 @@ import { defineCommand, defineOptions } from '@robingenz/zli';
|
|
|
2
2
|
import consola from 'consola';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
export default defineCommand({
|
|
5
|
-
description: 'Update an app bundle.',
|
|
5
|
+
description: 'Update an app bundle. Deprecated.',
|
|
6
6
|
options: defineOptions(z.object({
|
|
7
7
|
androidMax: z
|
|
8
8
|
.string()
|
|
@@ -40,8 +40,7 @@ export default defineCommand({
|
|
|
40
40
|
.describe('The exact iOS bundle version (`CFBundleVersion`) that the bundle should not support.'),
|
|
41
41
|
})),
|
|
42
42
|
action: async (options, args) => {
|
|
43
|
-
consola.warn('The `apps:bundles:update` command has been deprecated
|
|
44
|
-
consola.info('Please refer to the official documentation for alternative approaches.');
|
|
43
|
+
consola.warn('The `apps:bundles:update` command has been deprecated. Please use the `apps:liveupdates` commands instead.');
|
|
45
44
|
process.exit(1);
|
|
46
45
|
},
|
|
47
46
|
});
|
|
@@ -15,10 +15,14 @@ export default defineCommand({
|
|
|
15
15
|
.enum(['android', 'ios', 'web'])
|
|
16
16
|
.optional()
|
|
17
17
|
.describe('Platform of the certificate (android, ios, web).'),
|
|
18
|
+
type: z
|
|
19
|
+
.enum(['development', 'production'])
|
|
20
|
+
.optional()
|
|
21
|
+
.describe('Type of the certificate (development, production).'),
|
|
18
22
|
yes: z.boolean().optional().describe('Skip confirmation prompt.'),
|
|
19
23
|
}), { y: 'yes' }),
|
|
20
24
|
action: withAuth(async (options, args) => {
|
|
21
|
-
let { appId, certificateId, name, platform } = options;
|
|
25
|
+
let { appId, certificateId, name, platform, type } = options;
|
|
22
26
|
if (!appId) {
|
|
23
27
|
if (!isInteractive()) {
|
|
24
28
|
consola.error('You must provide an app ID when running in non-interactive environment.');
|
|
@@ -29,10 +33,19 @@ export default defineCommand({
|
|
|
29
33
|
}
|
|
30
34
|
if (!certificateId) {
|
|
31
35
|
if (name && platform) {
|
|
32
|
-
const certificates = await appCertificatesService.findAll({ appId, name, platform });
|
|
36
|
+
const certificates = await appCertificatesService.findAll({ appId, name, platform, type });
|
|
33
37
|
const firstCertificate = certificates[0];
|
|
34
38
|
if (!firstCertificate) {
|
|
35
|
-
|
|
39
|
+
if (type) {
|
|
40
|
+
consola.error(`No certificate found with name '${name}', platform '${platform}', and type '${type}'.`);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
consola.error(`No certificate found with name '${name}' and platform '${platform}'.`);
|
|
44
|
+
}
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
if (certificates.length > 1 && !type) {
|
|
48
|
+
consola.error(`Multiple certificates found with name '${name}' and platform '${platform}'. Please specify --type or --certificate-id to disambiguate.`);
|
|
36
49
|
process.exit(1);
|
|
37
50
|
}
|
|
38
51
|
certificateId = firstCertificate.id;
|
|
@@ -49,9 +62,19 @@ export default defineCommand({
|
|
|
49
62
|
],
|
|
50
63
|
});
|
|
51
64
|
}
|
|
52
|
-
|
|
65
|
+
if (!type) {
|
|
66
|
+
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
67
|
+
type = await prompt('Select the type:', {
|
|
68
|
+
type: 'select',
|
|
69
|
+
options: [
|
|
70
|
+
{ label: 'Development', value: 'development' },
|
|
71
|
+
{ label: 'Production', value: 'production' },
|
|
72
|
+
],
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
const certificates = await appCertificatesService.findAll({ appId, name, platform, type });
|
|
53
76
|
if (!certificates.length) {
|
|
54
|
-
consola.error(
|
|
77
|
+
consola.error(`No certificates found with platform '${platform}' and type '${type}'. Create one first.`);
|
|
55
78
|
process.exit(1);
|
|
56
79
|
}
|
|
57
80
|
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
@@ -16,9 +16,13 @@ export default defineCommand({
|
|
|
16
16
|
.enum(['android', 'ios', 'web'])
|
|
17
17
|
.optional()
|
|
18
18
|
.describe('Platform of the certificate (android, ios, web).'),
|
|
19
|
+
type: z
|
|
20
|
+
.enum(['development', 'production'])
|
|
21
|
+
.optional()
|
|
22
|
+
.describe('Type of the certificate (development, production).'),
|
|
19
23
|
})),
|
|
20
24
|
action: withAuth(async (options, args) => {
|
|
21
|
-
let { appId, certificateId, name, platform } = options;
|
|
25
|
+
let { appId, certificateId, name, platform, type } = options;
|
|
22
26
|
if (!appId) {
|
|
23
27
|
if (!isInteractive()) {
|
|
24
28
|
consola.error('You must provide an app ID when running in non-interactive environment.');
|
|
@@ -29,10 +33,19 @@ export default defineCommand({
|
|
|
29
33
|
}
|
|
30
34
|
if (!certificateId) {
|
|
31
35
|
if (name && platform) {
|
|
32
|
-
const certificates = await appCertificatesService.findAll({ appId, name, platform });
|
|
36
|
+
const certificates = await appCertificatesService.findAll({ appId, name, platform, type });
|
|
33
37
|
const firstCertificate = certificates[0];
|
|
34
38
|
if (!firstCertificate) {
|
|
35
|
-
|
|
39
|
+
if (type) {
|
|
40
|
+
consola.error(`No certificate found with name '${name}', platform '${platform}', and type '${type}'.`);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
consola.error(`No certificate found with name '${name}' and platform '${platform}'.`);
|
|
44
|
+
}
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
if (certificates.length > 1 && !type) {
|
|
48
|
+
consola.error(`Multiple certificates found with name '${name}' and platform '${platform}'. Please specify --type or --certificate-id to disambiguate.`);
|
|
36
49
|
process.exit(1);
|
|
37
50
|
}
|
|
38
51
|
certificateId = firstCertificate.id;
|
|
@@ -49,9 +62,19 @@ export default defineCommand({
|
|
|
49
62
|
],
|
|
50
63
|
});
|
|
51
64
|
}
|
|
52
|
-
|
|
65
|
+
if (!type) {
|
|
66
|
+
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
67
|
+
type = await prompt('Select the type:', {
|
|
68
|
+
type: 'select',
|
|
69
|
+
options: [
|
|
70
|
+
{ label: 'Development', value: 'development' },
|
|
71
|
+
{ label: 'Production', value: 'production' },
|
|
72
|
+
],
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
const certificates = await appCertificatesService.findAll({ appId, name, platform, type });
|
|
53
76
|
if (!certificates.length) {
|
|
54
|
-
consola.error(
|
|
77
|
+
consola.error(`No certificates found with platform '${platform}' and type '${type}'. Create one first.`);
|
|
55
78
|
process.exit(1);
|
|
56
79
|
}
|
|
57
80
|
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
@@ -2,16 +2,15 @@ import { DEFAULT_CONSOLE_BASE_URL } from '../../../config/consts.js';
|
|
|
2
2
|
import appBuildsService from '../../../services/app-builds.js';
|
|
3
3
|
import appDeploymentsService from '../../../services/app-deployments.js';
|
|
4
4
|
import appDestinationsService from '../../../services/app-destinations.js';
|
|
5
|
-
import { unescapeAnsi } from '../../../utils/ansi.js';
|
|
6
5
|
import { withAuth } from '../../../utils/auth.js';
|
|
7
6
|
import { isInteractive } from '../../../utils/environment.js';
|
|
7
|
+
import { waitForJobCompletion } from '../../../utils/job.js';
|
|
8
8
|
import { prompt, promptAppSelection, promptOrganizationSelection } from '../../../utils/prompt.js';
|
|
9
|
-
import { wait } from '../../../utils/wait.js';
|
|
10
9
|
import { defineCommand, defineOptions } from '@robingenz/zli';
|
|
11
10
|
import consola from 'consola';
|
|
12
11
|
import { z } from 'zod';
|
|
13
12
|
export default defineCommand({
|
|
14
|
-
description: 'Create a new app deployment.',
|
|
13
|
+
description: 'Create a new app deployment on Capawesome Cloud.',
|
|
15
14
|
options: defineOptions(z.object({
|
|
16
15
|
appId: z
|
|
17
16
|
.uuid({
|
|
@@ -159,80 +158,9 @@ export default defineCommand({
|
|
|
159
158
|
// Wait for deployment job to complete by default, unless --detached flag is set
|
|
160
159
|
const shouldWait = !options.detached && build.platform !== 'web';
|
|
161
160
|
if (shouldWait) {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
while (true) {
|
|
166
|
-
try {
|
|
167
|
-
const deployment = await appDeploymentsService.findOne({
|
|
168
|
-
appId,
|
|
169
|
-
appDeploymentId: response.id,
|
|
170
|
-
relations: 'job,job.jobLogs',
|
|
171
|
-
});
|
|
172
|
-
if (!deployment.job) {
|
|
173
|
-
await wait(3000);
|
|
174
|
-
continue;
|
|
175
|
-
}
|
|
176
|
-
const jobStatus = deployment.job.status;
|
|
177
|
-
// Show spinner while queued or pending
|
|
178
|
-
if (jobStatus === 'queued' || jobStatus === 'pending') {
|
|
179
|
-
if (isWaitingForStart) {
|
|
180
|
-
consola.start(`Waiting for deployment to start (status: ${jobStatus})...`);
|
|
181
|
-
}
|
|
182
|
-
await wait(3000);
|
|
183
|
-
continue;
|
|
184
|
-
}
|
|
185
|
-
// Stop spinner when job moves to in_progress
|
|
186
|
-
if (isWaitingForStart && jobStatus === 'in_progress') {
|
|
187
|
-
isWaitingForStart = false;
|
|
188
|
-
consola.success('Deployment started...');
|
|
189
|
-
}
|
|
190
|
-
// Print new logs
|
|
191
|
-
if (deployment.job.jobLogs && deployment.job.jobLogs.length > 0) {
|
|
192
|
-
const newLogs = deployment.job.jobLogs
|
|
193
|
-
.filter((log) => log.number > lastPrintedLogNumber)
|
|
194
|
-
.sort((a, b) => a.number - b.number);
|
|
195
|
-
for (const log of newLogs) {
|
|
196
|
-
console.log(unescapeAnsi(log.payload));
|
|
197
|
-
lastPrintedLogNumber = log.number;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
// Handle terminal states
|
|
201
|
-
if (jobStatus === 'succeeded' ||
|
|
202
|
-
jobStatus === 'failed' ||
|
|
203
|
-
jobStatus === 'canceled' ||
|
|
204
|
-
jobStatus === 'rejected' ||
|
|
205
|
-
jobStatus === 'timed_out') {
|
|
206
|
-
console.log(); // New line for better readability
|
|
207
|
-
if (jobStatus === 'succeeded') {
|
|
208
|
-
consola.success('Deployment completed successfully.');
|
|
209
|
-
process.exit(0);
|
|
210
|
-
}
|
|
211
|
-
else if (jobStatus === 'failed') {
|
|
212
|
-
consola.error('Deployment failed.');
|
|
213
|
-
process.exit(1);
|
|
214
|
-
}
|
|
215
|
-
else if (jobStatus === 'canceled') {
|
|
216
|
-
consola.warn('Deployment was canceled.');
|
|
217
|
-
process.exit(1);
|
|
218
|
-
}
|
|
219
|
-
else if (jobStatus === 'rejected') {
|
|
220
|
-
consola.error('Deployment was rejected.');
|
|
221
|
-
process.exit(1);
|
|
222
|
-
}
|
|
223
|
-
else if (jobStatus === 'timed_out') {
|
|
224
|
-
consola.error('Deployment timed out.');
|
|
225
|
-
process.exit(1);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
// Wait before next poll (3 seconds)
|
|
229
|
-
await wait(3000);
|
|
230
|
-
}
|
|
231
|
-
catch (error) {
|
|
232
|
-
consola.error('Error polling deployment status:', error);
|
|
233
|
-
process.exit(1);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
161
|
+
await waitForJobCompletion({ jobId: response.jobId });
|
|
162
|
+
consola.success('Deployment completed successfully.');
|
|
163
|
+
process.exit(0);
|
|
236
164
|
}
|
|
237
165
|
}),
|
|
238
166
|
});
|
|
@@ -10,11 +10,11 @@ export default defineCommand({
|
|
|
10
10
|
description: 'Force a device to use a specific channel.',
|
|
11
11
|
options: defineOptions(z.object({
|
|
12
12
|
appId: z.string().uuid({ message: 'App ID must be a UUID.' }).optional().describe('ID of the app.'),
|
|
13
|
-
deviceId: z.string().optional().describe('ID of the device.'),
|
|
13
|
+
deviceId: z.array(z.string()).optional().describe('ID of the device. Can be specified multiple times.'),
|
|
14
14
|
channel: z.string().optional().describe('Name of the channel to force.'),
|
|
15
15
|
})),
|
|
16
16
|
action: withAuth(async (options, args) => {
|
|
17
|
-
let { appId, deviceId, channel } = options;
|
|
17
|
+
let { appId, deviceId: deviceIds, channel } = options;
|
|
18
18
|
if (!appId) {
|
|
19
19
|
if (!isInteractive()) {
|
|
20
20
|
consola.error('You must provide an app ID when running in non-interactive environment.');
|
|
@@ -23,14 +23,15 @@ export default defineCommand({
|
|
|
23
23
|
const organizationId = await promptOrganizationSelection();
|
|
24
24
|
appId = await promptAppSelection(organizationId);
|
|
25
25
|
}
|
|
26
|
-
if (!
|
|
26
|
+
if (!deviceIds || deviceIds.length === 0) {
|
|
27
27
|
if (!isInteractive()) {
|
|
28
28
|
consola.error('You must provide the device ID when running in non-interactive environment.');
|
|
29
29
|
process.exit(1);
|
|
30
30
|
}
|
|
31
|
-
deviceId = await prompt('Enter the device ID:', {
|
|
31
|
+
const deviceId = await prompt('Enter the device ID:', {
|
|
32
32
|
type: 'text',
|
|
33
33
|
});
|
|
34
|
+
deviceIds = [deviceId];
|
|
34
35
|
}
|
|
35
36
|
if (!channel) {
|
|
36
37
|
if (!isInteractive()) {
|
|
@@ -56,11 +57,12 @@ export default defineCommand({
|
|
|
56
57
|
consola.error('Channel ID not found.');
|
|
57
58
|
process.exit(1);
|
|
58
59
|
}
|
|
59
|
-
await appDevicesService.
|
|
60
|
+
await appDevicesService.updateMany({
|
|
60
61
|
appId,
|
|
61
|
-
|
|
62
|
+
deviceIds,
|
|
62
63
|
forcedAppChannelId: channelId,
|
|
63
64
|
});
|
|
64
|
-
|
|
65
|
+
const deviceCount = deviceIds.length;
|
|
66
|
+
consola.success(`${deviceCount === 1 ? 'Device' : `${deviceCount} devices`} forced to channel successfully.`);
|
|
65
67
|
}),
|
|
66
68
|
});
|
|
@@ -9,10 +9,10 @@ export default defineCommand({
|
|
|
9
9
|
description: 'Remove the forced channel from a device.',
|
|
10
10
|
options: defineOptions(z.object({
|
|
11
11
|
appId: z.string().uuid({ message: 'App ID must be a UUID.' }).optional().describe('ID of the app.'),
|
|
12
|
-
deviceId: z.string().optional().describe('ID of the device.'),
|
|
12
|
+
deviceId: z.array(z.string()).optional().describe('ID of the device. Can be specified multiple times.'),
|
|
13
13
|
})),
|
|
14
14
|
action: withAuth(async (options, args) => {
|
|
15
|
-
let { appId, deviceId } = options;
|
|
15
|
+
let { appId, deviceId: deviceIds } = options;
|
|
16
16
|
if (!appId) {
|
|
17
17
|
if (!isInteractive()) {
|
|
18
18
|
consola.error('You must provide an app ID when running in non-interactive environment.');
|
|
@@ -21,20 +21,22 @@ export default defineCommand({
|
|
|
21
21
|
const organizationId = await promptOrganizationSelection();
|
|
22
22
|
appId = await promptAppSelection(organizationId);
|
|
23
23
|
}
|
|
24
|
-
if (!
|
|
24
|
+
if (!deviceIds || deviceIds.length === 0) {
|
|
25
25
|
if (!isInteractive()) {
|
|
26
26
|
consola.error('You must provide the device ID when running in non-interactive environment.');
|
|
27
27
|
process.exit(1);
|
|
28
28
|
}
|
|
29
|
-
deviceId = await prompt('Enter the device ID:', {
|
|
29
|
+
const deviceId = await prompt('Enter the device ID:', {
|
|
30
30
|
type: 'text',
|
|
31
31
|
});
|
|
32
|
+
deviceIds = [deviceId];
|
|
32
33
|
}
|
|
33
|
-
await appDevicesService.
|
|
34
|
+
await appDevicesService.updateMany({
|
|
34
35
|
appId,
|
|
35
|
-
|
|
36
|
+
deviceIds,
|
|
36
37
|
forcedAppChannelId: null,
|
|
37
38
|
});
|
|
38
|
-
|
|
39
|
+
const deviceCount = deviceIds.length;
|
|
40
|
+
consola.success(`Forced channel removed from ${deviceCount === 1 ? 'device' : `${deviceCount} devices`} successfully.`);
|
|
39
41
|
}),
|
|
40
42
|
});
|