@capawesome/cli 3.6.0 → 3.7.0-dev.f4e106a.1764835462
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/dist/commands/apps/builds/cancel.js +1 -1
- package/dist/commands/apps/builds/create.js +56 -18
- package/dist/commands/apps/builds/download.js +1 -1
- package/dist/commands/apps/builds/logs.js +1 -1
- package/dist/commands/apps/bundles/create.js +139 -40
- package/dist/commands/apps/bundles/create.test.js +1 -1
- package/dist/commands/apps/bundles/delete.js +1 -1
- package/dist/commands/apps/bundles/update.js +1 -1
- package/dist/commands/apps/bundles/update.test.js +1 -1
- package/dist/commands/apps/channels/create.js +1 -1
- package/dist/commands/apps/channels/delete.js +1 -1
- package/dist/commands/apps/channels/get.js +1 -1
- package/dist/commands/apps/channels/list.js +1 -1
- package/dist/commands/apps/channels/list.test.js +1 -1
- package/dist/commands/apps/channels/update.js +1 -1
- package/dist/commands/apps/channels/update.test.js +1 -1
- package/dist/commands/apps/create.js +1 -1
- package/dist/commands/apps/delete.js +1 -1
- package/dist/commands/apps/deployments/cancel.js +1 -1
- package/dist/commands/apps/deployments/create.js +1 -1
- package/dist/commands/apps/deployments/logs.js +2 -2
- package/dist/commands/apps/devices/delete.js +1 -1
- package/dist/commands/organizations/create.js +1 -1
- package/dist/commands/organizations/create.test.js +1 -1
- package/dist/services/apps.js +8 -0
- package/dist/utils/capacitor-config.js +84 -0
- package/dist/utils/package-json.js +58 -0
- package/package.json +2 -2
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
|
+
## [3.7.0](https://github.com/capawesome-team/cli/compare/v3.6.0...v3.7.0) (2025-12-02)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **apps:builds:create:** add JSON output option ([#101](https://github.com/capawesome-team/cli/issues/101)) ([32797f9](https://github.com/capawesome-team/cli/commit/32797f93229848fc23565e89f245772e3e509500))
|
|
11
|
+
|
|
5
12
|
## [3.6.0](https://github.com/capawesome-team/cli/compare/v3.5.0...v3.6.0) (2025-11-29)
|
|
6
13
|
|
|
7
14
|
|
|
@@ -28,7 +28,7 @@ export default defineCommand({
|
|
|
28
28
|
let { appId, buildId } = options;
|
|
29
29
|
// Check if the user is logged in
|
|
30
30
|
if (!authorizationService.hasAuthorizationToken()) {
|
|
31
|
-
consola.error('You must be logged in to run this command.');
|
|
31
|
+
consola.error('You must be logged in to run this command. Please run the `login` command first.');
|
|
32
32
|
process.exit(1);
|
|
33
33
|
}
|
|
34
34
|
// Prompt for app ID if not provided
|
|
@@ -44,6 +44,7 @@ export default defineCommand({
|
|
|
44
44
|
.union([z.boolean(), z.string()])
|
|
45
45
|
.optional()
|
|
46
46
|
.describe('Download the generated IPA file (iOS only). Optionally provide a file path.'),
|
|
47
|
+
json: z.boolean().optional().describe('Output in JSON format.'),
|
|
47
48
|
platform: z
|
|
48
49
|
.enum(['ios', 'android'], {
|
|
49
50
|
message: 'Platform must be either `ios` or `android`.',
|
|
@@ -56,10 +57,10 @@ export default defineCommand({
|
|
|
56
57
|
.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`.'),
|
|
57
58
|
})),
|
|
58
59
|
action: async (options) => {
|
|
59
|
-
let { appId, platform, type, gitRef, environment, certificate } = options;
|
|
60
|
+
let { appId, platform, type, gitRef, environment, certificate, json } = options;
|
|
60
61
|
// Check if the user is logged in
|
|
61
62
|
if (!authorizationService.hasAuthorizationToken()) {
|
|
62
|
-
consola.error('You must be logged in to run this command.');
|
|
63
|
+
consola.error('You must be logged in to run this command. Please run the `login` command first.');
|
|
63
64
|
process.exit(1);
|
|
64
65
|
}
|
|
65
66
|
// Validate that detached flag cannot be used with artifact flags
|
|
@@ -193,7 +194,9 @@ export default defineCommand({
|
|
|
193
194
|
}
|
|
194
195
|
}
|
|
195
196
|
// Create the app build
|
|
196
|
-
|
|
197
|
+
if (!json) {
|
|
198
|
+
consola.start('Creating build...');
|
|
199
|
+
}
|
|
197
200
|
const response = await appBuildsService.create({
|
|
198
201
|
appCertificateName: certificate,
|
|
199
202
|
appEnvironmentName: environment,
|
|
@@ -202,10 +205,12 @@ export default defineCommand({
|
|
|
202
205
|
platform,
|
|
203
206
|
type,
|
|
204
207
|
});
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
208
|
+
if (!json) {
|
|
209
|
+
consola.success(`Build created successfully.`);
|
|
210
|
+
consola.info(`Build Number: ${response.numberAsString}`);
|
|
211
|
+
consola.info(`Build ID: ${response.id}`);
|
|
212
|
+
consola.info(`Build URL: ${DEFAULT_CONSOLE_BASE_URL}/apps/${appId}/builds/${response.id}`);
|
|
213
|
+
}
|
|
209
214
|
// Wait for build job to complete by default, unless --detached flag is set
|
|
210
215
|
const shouldWait = !options.detached;
|
|
211
216
|
if (shouldWait) {
|
|
@@ -226,7 +231,7 @@ export default defineCommand({
|
|
|
226
231
|
const jobStatus = build.job.status;
|
|
227
232
|
// Show spinner while queued or pending
|
|
228
233
|
if (jobStatus === 'queued' || jobStatus === 'pending') {
|
|
229
|
-
if (isWaitingForStart) {
|
|
234
|
+
if (isWaitingForStart && !json) {
|
|
230
235
|
consola.start(`Waiting for build to start (status: ${jobStatus})...`);
|
|
231
236
|
}
|
|
232
237
|
await wait(3000);
|
|
@@ -235,10 +240,12 @@ export default defineCommand({
|
|
|
235
240
|
// Stop spinner when job moves to in_progress
|
|
236
241
|
if (isWaitingForStart && jobStatus === 'in_progress') {
|
|
237
242
|
isWaitingForStart = false;
|
|
238
|
-
|
|
243
|
+
if (!json) {
|
|
244
|
+
consola.success('Build started...');
|
|
245
|
+
}
|
|
239
246
|
}
|
|
240
247
|
// Print new logs
|
|
241
|
-
if (build.job.jobLogs && build.job.jobLogs.length > 0) {
|
|
248
|
+
if (!json && build.job.jobLogs && build.job.jobLogs.length > 0) {
|
|
242
249
|
const newLogs = build.job.jobLogs
|
|
243
250
|
.filter((log) => log.number > lastPrintedLogNumber)
|
|
244
251
|
.sort((a, b) => a.number - b.number);
|
|
@@ -253,10 +260,14 @@ export default defineCommand({
|
|
|
253
260
|
jobStatus === 'canceled' ||
|
|
254
261
|
jobStatus === 'rejected' ||
|
|
255
262
|
jobStatus === 'timed_out') {
|
|
256
|
-
|
|
257
|
-
if (jobStatus === 'succeeded') {
|
|
258
|
-
consola.success('Build completed successfully.');
|
|
263
|
+
if (!json) {
|
|
259
264
|
console.log(); // New line for better readability
|
|
265
|
+
}
|
|
266
|
+
if (jobStatus === 'succeeded') {
|
|
267
|
+
if (!json) {
|
|
268
|
+
consola.success('Build completed successfully.');
|
|
269
|
+
console.log(); // New line for better readability
|
|
270
|
+
}
|
|
260
271
|
// Download artifacts if flags are set
|
|
261
272
|
if (options.apk && platform === 'android') {
|
|
262
273
|
await handleArtifactDownload({
|
|
@@ -265,6 +276,7 @@ export default defineCommand({
|
|
|
265
276
|
buildArtifacts: build.appBuildArtifacts,
|
|
266
277
|
artifactType: 'apk',
|
|
267
278
|
filePath: typeof options.apk === 'string' ? options.apk : undefined,
|
|
279
|
+
json,
|
|
268
280
|
});
|
|
269
281
|
}
|
|
270
282
|
if (options.aab && platform === 'android') {
|
|
@@ -274,6 +286,7 @@ export default defineCommand({
|
|
|
274
286
|
buildArtifacts: build.appBuildArtifacts,
|
|
275
287
|
artifactType: 'aab',
|
|
276
288
|
filePath: typeof options.aab === 'string' ? options.aab : undefined,
|
|
289
|
+
json,
|
|
277
290
|
});
|
|
278
291
|
}
|
|
279
292
|
if (options.ipa && platform === 'ios') {
|
|
@@ -283,8 +296,17 @@ export default defineCommand({
|
|
|
283
296
|
buildArtifacts: build.appBuildArtifacts,
|
|
284
297
|
artifactType: 'ipa',
|
|
285
298
|
filePath: typeof options.ipa === 'string' ? options.ipa : undefined,
|
|
299
|
+
json,
|
|
286
300
|
});
|
|
287
301
|
}
|
|
302
|
+
// Output JSON if json flag is set
|
|
303
|
+
if (json) {
|
|
304
|
+
console.log(JSON.stringify({
|
|
305
|
+
id: response.id,
|
|
306
|
+
numberAsString: response.numberAsString,
|
|
307
|
+
}, null, 2));
|
|
308
|
+
}
|
|
309
|
+
// Exit successfully
|
|
288
310
|
process.exit(0);
|
|
289
311
|
}
|
|
290
312
|
else if (jobStatus === 'failed') {
|
|
@@ -313,24 +335,38 @@ export default defineCommand({
|
|
|
313
335
|
}
|
|
314
336
|
}
|
|
315
337
|
}
|
|
338
|
+
else {
|
|
339
|
+
if (json) {
|
|
340
|
+
console.log(JSON.stringify({
|
|
341
|
+
id: response.id,
|
|
342
|
+
numberAsString: response.numberAsString,
|
|
343
|
+
}, null, 2));
|
|
344
|
+
}
|
|
345
|
+
}
|
|
316
346
|
},
|
|
317
347
|
});
|
|
318
348
|
/**
|
|
319
349
|
* Download a build artifact (APK, AAB, or IPA).
|
|
320
350
|
*/
|
|
321
351
|
const handleArtifactDownload = async (options) => {
|
|
322
|
-
const { appId, buildId, buildArtifacts, artifactType, filePath } = options;
|
|
352
|
+
const { appId, buildId, buildArtifacts, artifactType, filePath, json } = options;
|
|
323
353
|
try {
|
|
324
354
|
const artifactTypeUpper = artifactType.toUpperCase();
|
|
325
|
-
|
|
355
|
+
if (!json) {
|
|
356
|
+
consola.start(`Downloading ${artifactTypeUpper}...`);
|
|
357
|
+
}
|
|
326
358
|
// Find the artifact
|
|
327
359
|
const artifact = buildArtifacts?.find((artifact) => artifact.type === artifactType);
|
|
328
360
|
if (!artifact) {
|
|
329
|
-
|
|
361
|
+
if (!json) {
|
|
362
|
+
consola.warn(`No ${artifactTypeUpper} artifact found for this build.`);
|
|
363
|
+
}
|
|
330
364
|
return;
|
|
331
365
|
}
|
|
332
366
|
if (artifact.status !== 'ready') {
|
|
333
|
-
|
|
367
|
+
if (!json) {
|
|
368
|
+
consola.warn(`${artifactTypeUpper} artifact is not ready (status: ${artifact.status}).`);
|
|
369
|
+
}
|
|
334
370
|
return;
|
|
335
371
|
}
|
|
336
372
|
// Download the artifact
|
|
@@ -351,7 +387,9 @@ const handleArtifactDownload = async (options) => {
|
|
|
351
387
|
}
|
|
352
388
|
// Save the file
|
|
353
389
|
await fs.writeFile(outputPath, Buffer.from(artifactData));
|
|
354
|
-
|
|
390
|
+
if (!json) {
|
|
391
|
+
consola.success(`${artifactTypeUpper} downloaded successfully: ${outputPath}`);
|
|
392
|
+
}
|
|
355
393
|
}
|
|
356
394
|
catch (error) {
|
|
357
395
|
consola.error(`Failed to download ${artifactType.toUpperCase()}:`, error);
|
|
@@ -41,7 +41,7 @@ export default defineCommand({
|
|
|
41
41
|
let { appId, buildId } = options;
|
|
42
42
|
// Check if the user is logged in
|
|
43
43
|
if (!authorizationService.hasAuthorizationToken()) {
|
|
44
|
-
consola.error('You must be logged in to run this command.');
|
|
44
|
+
consola.error('You must be logged in to run this command. Please run the `login` command first.');
|
|
45
45
|
process.exit(1);
|
|
46
46
|
}
|
|
47
47
|
// Prompt for app ID if not provided
|
|
@@ -29,7 +29,7 @@ export default defineCommand({
|
|
|
29
29
|
let { appId, buildId } = options;
|
|
30
30
|
// Check if the user is logged in
|
|
31
31
|
if (!authorizationService.hasAuthorizationToken()) {
|
|
32
|
-
consola.error('You must be logged in to run this command.');
|
|
32
|
+
consola.error('You must be logged in to run this command. Please run the `login` command first.');
|
|
33
33
|
process.exit(1);
|
|
34
34
|
}
|
|
35
35
|
// Prompt for app ID if not provided
|
|
@@ -5,18 +5,24 @@ import appsService from '../../../services/apps.js';
|
|
|
5
5
|
import authorizationService from '../../../services/authorization-service.js';
|
|
6
6
|
import organizationsService from '../../../services/organizations.js';
|
|
7
7
|
import { createBufferFromPath, createBufferFromReadStream, createBufferFromString, isPrivateKeyContent, } from '../../../utils/buffer.js';
|
|
8
|
+
import { findCapacitorConfigPath, getCapawesomeCloudAppIdFromConfig, getWebDirFromConfig, } from '../../../utils/capacitor-config.js';
|
|
8
9
|
import { fileExistsAtPath, getFilesInDirectoryAndSubdirectories, isDirectory } from '../../../utils/file.js';
|
|
9
10
|
import { createHash } from '../../../utils/hash.js';
|
|
10
11
|
import { generateManifestJson } from '../../../utils/manifest.js';
|
|
12
|
+
import { findPackageJsonPath, getBuildScript } from '../../../utils/package-json.js';
|
|
11
13
|
import { formatPrivateKey } from '../../../utils/private-key.js';
|
|
12
14
|
import { prompt } from '../../../utils/prompt.js';
|
|
13
15
|
import { createSignature } from '../../../utils/signature.js';
|
|
14
16
|
import zip from '../../../utils/zip.js';
|
|
15
17
|
import { defineCommand, defineOptions } from '@robingenz/zli';
|
|
18
|
+
import { exec } from 'child_process';
|
|
16
19
|
import consola from 'consola';
|
|
17
20
|
import { createReadStream } from 'fs';
|
|
21
|
+
import pathModule from 'path';
|
|
18
22
|
import { hasTTY } from 'std-env';
|
|
23
|
+
import { promisify } from 'util';
|
|
19
24
|
import { z } from 'zod';
|
|
25
|
+
const execAsync = promisify(exec);
|
|
20
26
|
export default defineCommand({
|
|
21
27
|
description: 'Create a new app bundle.',
|
|
22
28
|
options: defineOptions(z.object({
|
|
@@ -100,7 +106,7 @@ export default defineCommand({
|
|
|
100
106
|
let { androidEq, androidMax, androidMin, appId, artifactType, channel, commitMessage, commitRef, commitSha, customProperty, expiresInDays, iosEq, iosMax, iosMin, path, privateKey, rollout, url, } = options;
|
|
101
107
|
// Check if the user is logged in
|
|
102
108
|
if (!authorizationService.hasAuthorizationToken()) {
|
|
103
|
-
consola.error('You must be logged in to run this command.');
|
|
109
|
+
consola.error('You must be logged in to run this command. Please run the `login` command first.');
|
|
104
110
|
process.exit(1);
|
|
105
111
|
}
|
|
106
112
|
// Calculate the expiration date
|
|
@@ -112,20 +118,81 @@ export default defineCommand({
|
|
|
112
118
|
}
|
|
113
119
|
// Check that either a path or a url is provided
|
|
114
120
|
if (!path && !url) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
121
|
+
// Try to auto-detect webDir from Capacitor config
|
|
122
|
+
const capacitorConfigPath = await findCapacitorConfigPath();
|
|
123
|
+
if (capacitorConfigPath) {
|
|
124
|
+
const webDir = await getWebDirFromConfig(capacitorConfigPath);
|
|
125
|
+
if (webDir) {
|
|
126
|
+
consola.info(`Auto-detected web asset directory "${webDir}" from Capacitor config.`);
|
|
127
|
+
path = webDir;
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
consola.warn('No web asset directory found in Capacitor config (`webDir`).');
|
|
131
|
+
}
|
|
118
132
|
}
|
|
119
133
|
else {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
134
|
+
consola.warn('No Capacitor config found to auto-detect web asset directory.');
|
|
135
|
+
}
|
|
136
|
+
// If still no path, prompt the user
|
|
137
|
+
if (!path) {
|
|
138
|
+
if (!hasTTY) {
|
|
139
|
+
consola.error('You must provide either a path or a url when running in non-interactive environment.');
|
|
125
140
|
process.exit(1);
|
|
126
141
|
}
|
|
142
|
+
else {
|
|
143
|
+
path = await prompt('Enter the path to the app bundle:', {
|
|
144
|
+
type: 'text',
|
|
145
|
+
});
|
|
146
|
+
if (!path) {
|
|
147
|
+
consola.error('You must provide a path to the app bundle.');
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// Check for build scripts if a path is provided or detected
|
|
154
|
+
if (path && !url) {
|
|
155
|
+
const packageJsonPath = await findPackageJsonPath();
|
|
156
|
+
if (!packageJsonPath) {
|
|
157
|
+
consola.warn('No package.json file found.');
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
const buildScript = await getBuildScript(packageJsonPath);
|
|
161
|
+
if (!buildScript) {
|
|
162
|
+
consola.warn('No build script found in package.json.');
|
|
163
|
+
}
|
|
164
|
+
else if (hasTTY) {
|
|
165
|
+
const shouldBuild = await prompt('Do you want to rebuild your web assets before proceeding?', {
|
|
166
|
+
type: 'select',
|
|
167
|
+
options: ['Yes', 'No'],
|
|
168
|
+
});
|
|
169
|
+
if (shouldBuild === 'Yes') {
|
|
170
|
+
try {
|
|
171
|
+
consola.start(`Running ${buildScript.name} script...`);
|
|
172
|
+
const { stdout, stderr } = await execAsync(`npm run ${buildScript.name}`);
|
|
173
|
+
if (stdout) {
|
|
174
|
+
console.log(stdout);
|
|
175
|
+
}
|
|
176
|
+
if (stderr) {
|
|
177
|
+
console.error(stderr);
|
|
178
|
+
}
|
|
179
|
+
consola.success('Build completed successfully.');
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
consola.error('Build failed.');
|
|
183
|
+
if (error.stdout) {
|
|
184
|
+
console.log(error.stdout);
|
|
185
|
+
}
|
|
186
|
+
if (error.stderr) {
|
|
187
|
+
console.error(error.stderr);
|
|
188
|
+
}
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
127
193
|
}
|
|
128
194
|
}
|
|
195
|
+
// Validate the provided path
|
|
129
196
|
if (path) {
|
|
130
197
|
// Check if the path exists when a path is provided
|
|
131
198
|
const pathExists = await fileExistsAtPath(path);
|
|
@@ -165,39 +232,57 @@ export default defineCommand({
|
|
|
165
232
|
process.exit(1);
|
|
166
233
|
}
|
|
167
234
|
if (!appId) {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
type: 'select',
|
|
180
|
-
options: organizations.map((organization) => ({ label: organization.name, value: organization.id })),
|
|
181
|
-
});
|
|
182
|
-
if (!organizationId) {
|
|
183
|
-
consola.error('You must select the organization of an app for which you want to create a bundle.');
|
|
184
|
-
process.exit(1);
|
|
235
|
+
// Try to auto-detect appId from Capacitor config
|
|
236
|
+
const capacitorConfigPath = await findCapacitorConfigPath();
|
|
237
|
+
if (capacitorConfigPath) {
|
|
238
|
+
const configAppId = await getCapawesomeCloudAppIdFromConfig(capacitorConfigPath);
|
|
239
|
+
if (configAppId) {
|
|
240
|
+
console.info(`Auto-detected Capawesome Cloud app ID "${configAppId}" from Capacitor config.`);
|
|
241
|
+
appId = configAppId;
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
consola.warn('No Capawesome Cloud app ID found in Capacitor config (`plugins.LiveUpdate.appId`).');
|
|
245
|
+
}
|
|
185
246
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
});
|
|
189
|
-
if (apps.length === 0) {
|
|
190
|
-
consola.error('You must create an app before creating a bundle.');
|
|
191
|
-
process.exit(1);
|
|
247
|
+
else {
|
|
248
|
+
consola.warn('No Capacitor config found to auto-detect Capawesome Cloud app ID.');
|
|
192
249
|
}
|
|
193
|
-
//
|
|
194
|
-
appId = await prompt('Which app do you want to deploy to:', {
|
|
195
|
-
type: 'select',
|
|
196
|
-
options: apps.map((app) => ({ label: app.name, value: app.id })),
|
|
197
|
-
});
|
|
250
|
+
// If still no appId, prompt the user
|
|
198
251
|
if (!appId) {
|
|
199
|
-
|
|
200
|
-
|
|
252
|
+
if (!hasTTY) {
|
|
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
|
+
}
|
|
201
286
|
}
|
|
202
287
|
}
|
|
203
288
|
if (!channel && hasTTY) {
|
|
@@ -242,9 +327,23 @@ export default defineCommand({
|
|
|
242
327
|
process.exit(1);
|
|
243
328
|
}
|
|
244
329
|
}
|
|
330
|
+
// Get app details for confirmation
|
|
331
|
+
const app = await appsService.findOne({ appId });
|
|
332
|
+
const appName = app.name;
|
|
333
|
+
// Final confirmation before creating bundle
|
|
334
|
+
if (path && hasTTY) {
|
|
335
|
+
const relativePath = pathModule.relative(process.cwd(), path);
|
|
336
|
+
const confirmed = await prompt(`Are you sure you want to create a bundle from path ${relativePath} for app "${appName}"?`, {
|
|
337
|
+
type: 'confirm',
|
|
338
|
+
});
|
|
339
|
+
if (confirmed) {
|
|
340
|
+
consola.info('Bundle creation cancelled.');
|
|
341
|
+
process.exit(0);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
// Create the app bundle
|
|
245
345
|
let appBundleId;
|
|
246
346
|
try {
|
|
247
|
-
// Create the app bundle
|
|
248
347
|
consola.start('Creating bundle...');
|
|
249
348
|
let checksum;
|
|
250
349
|
let signature;
|
|
@@ -41,7 +41,7 @@ describe('apps-bundles-create', () => {
|
|
|
41
41
|
const options = { appId, path: './dist', artifactType: 'zip', rollout: 1 };
|
|
42
42
|
mockAuthorizationService.hasAuthorizationToken.mockReturnValue(false);
|
|
43
43
|
await expect(createBundleCommand.action(options, undefined)).rejects.toThrow('Process exited with code 1');
|
|
44
|
-
expect(mockConsola.error).toHaveBeenCalledWith('You must be logged in to run this command.');
|
|
44
|
+
expect(mockConsola.error).toHaveBeenCalledWith('You must be logged in to run this command. Please run the `login` command first.');
|
|
45
45
|
});
|
|
46
46
|
it('should create bundle with self-hosted URL', async () => {
|
|
47
47
|
const appId = 'app-123';
|
|
@@ -16,7 +16,7 @@ export default defineCommand({
|
|
|
16
16
|
action: async (options, args) => {
|
|
17
17
|
let { appId, bundleId } = options;
|
|
18
18
|
if (!authorizationService.hasAuthorizationToken()) {
|
|
19
|
-
consola.error('You must be logged in to run this command.');
|
|
19
|
+
consola.error('You must be logged in to run this command. Please run the `login` command first.');
|
|
20
20
|
process.exit(1);
|
|
21
21
|
}
|
|
22
22
|
// Prompt for missing arguments
|
|
@@ -48,7 +48,7 @@ export default defineCommand({
|
|
|
48
48
|
action: async (options, args) => {
|
|
49
49
|
let { androidMax, androidMin, androidEq, appId, bundleId, rollout, iosMax, iosMin, iosEq } = options;
|
|
50
50
|
if (!authorizationService.hasAuthorizationToken()) {
|
|
51
|
-
consola.error('You must be logged in to run this command.');
|
|
51
|
+
consola.error('You must be logged in to run this command. Please run the `login` command first.');
|
|
52
52
|
process.exit(1);
|
|
53
53
|
}
|
|
54
54
|
// Prompt for missing arguments
|
|
@@ -38,7 +38,7 @@ describe('apps-bundles-update', () => {
|
|
|
38
38
|
const options = { appId, bundleId };
|
|
39
39
|
mockAuthorizationService.hasAuthorizationToken.mockReturnValue(false);
|
|
40
40
|
await expect(updateBundleCommand.action(options, undefined)).rejects.toThrow('Process exited with code 1');
|
|
41
|
-
expect(mockConsola.error).toHaveBeenCalledWith('You must be logged in to run this command.');
|
|
41
|
+
expect(mockConsola.error).toHaveBeenCalledWith('You must be logged in to run this command. Please run the `login` command first.');
|
|
42
42
|
});
|
|
43
43
|
it('should update bundle with provided options', async () => {
|
|
44
44
|
const appId = 'app-123';
|
|
@@ -31,7 +31,7 @@ export default defineCommand({
|
|
|
31
31
|
action: async (options, args) => {
|
|
32
32
|
let { appId, bundleLimit, expiresInDays, ignoreErrors, name } = options;
|
|
33
33
|
if (!authorizationService.hasAuthorizationToken()) {
|
|
34
|
-
consola.error('You must be logged in to run this command.');
|
|
34
|
+
consola.error('You must be logged in to run this command. Please run the `login` command first.');
|
|
35
35
|
process.exit(1);
|
|
36
36
|
}
|
|
37
37
|
// Calculate the expiration date
|
|
@@ -23,7 +23,7 @@ export default defineCommand({
|
|
|
23
23
|
action: async (options, args) => {
|
|
24
24
|
let { appId, channelId, name } = options;
|
|
25
25
|
if (!authorizationService.hasAuthorizationToken()) {
|
|
26
|
-
consola.error('You must be logged in to run this command.');
|
|
26
|
+
consola.error('You must be logged in to run this command. Please run the `login` command first.');
|
|
27
27
|
process.exit(1);
|
|
28
28
|
}
|
|
29
29
|
if (!appId) {
|
|
@@ -14,7 +14,7 @@ export default defineCommand({
|
|
|
14
14
|
action: async (options, args) => {
|
|
15
15
|
let { appId, channelId, json, name } = options;
|
|
16
16
|
if (!authorizationService.hasAuthorizationToken()) {
|
|
17
|
-
consola.error('You must be logged in to run this command.');
|
|
17
|
+
consola.error('You must be logged in to run this command. Please run the `login` command first.');
|
|
18
18
|
process.exit(1);
|
|
19
19
|
}
|
|
20
20
|
if (!appId) {
|
|
@@ -14,7 +14,7 @@ export default defineCommand({
|
|
|
14
14
|
action: async (options, args) => {
|
|
15
15
|
let { appId, json, limit, offset } = options;
|
|
16
16
|
if (!authorizationService.hasAuthorizationToken()) {
|
|
17
|
-
consola.error('You must be logged in to run this command.');
|
|
17
|
+
consola.error('You must be logged in to run this command. Please run the `login` command first.');
|
|
18
18
|
process.exit(1);
|
|
19
19
|
}
|
|
20
20
|
if (!appId) {
|
|
@@ -33,7 +33,7 @@ describe('apps-channels-list', () => {
|
|
|
33
33
|
const options = { appId };
|
|
34
34
|
mockAuthorizationService.hasAuthorizationToken.mockReturnValue(false);
|
|
35
35
|
await expect(listChannelsCommand.action(options, undefined)).rejects.toThrow('Process exited with code 1');
|
|
36
|
-
expect(mockConsola.error).toHaveBeenCalledWith('You must be logged in to run this command.');
|
|
36
|
+
expect(mockConsola.error).toHaveBeenCalledWith('You must be logged in to run this command. Please run the `login` command first.');
|
|
37
37
|
});
|
|
38
38
|
it('should require appId', async () => {
|
|
39
39
|
const options = { appId: undefined };
|
|
@@ -21,7 +21,7 @@ export default defineCommand({
|
|
|
21
21
|
action: async (options, args) => {
|
|
22
22
|
let { appId, channelId, bundleLimit, name } = options;
|
|
23
23
|
if (!authorizationService.hasAuthorizationToken()) {
|
|
24
|
-
consola.error('You must be logged in to run this command.');
|
|
24
|
+
consola.error('You must be logged in to run this command. Please run the `login` command first.');
|
|
25
25
|
process.exit(1);
|
|
26
26
|
}
|
|
27
27
|
// Prompt app ID if not provided
|
|
@@ -38,7 +38,7 @@ describe('apps-channels-update', () => {
|
|
|
38
38
|
const options = { appId, channelId };
|
|
39
39
|
mockAuthorizationService.hasAuthorizationToken.mockReturnValue(false);
|
|
40
40
|
await expect(updateChannelCommand.action(options, undefined)).rejects.toThrow('Process exited with code 1');
|
|
41
|
-
expect(mockConsola.error).toHaveBeenCalledWith('You must be logged in to run this command.');
|
|
41
|
+
expect(mockConsola.error).toHaveBeenCalledWith('You must be logged in to run this command. Please run the `login` command first.');
|
|
42
42
|
});
|
|
43
43
|
it('should update channel with provided options', async () => {
|
|
44
44
|
const appId = 'app-123';
|
|
@@ -15,7 +15,7 @@ export default defineCommand({
|
|
|
15
15
|
action: async (options, args) => {
|
|
16
16
|
let { name, organizationId } = options;
|
|
17
17
|
if (!authorizationService.hasAuthorizationToken()) {
|
|
18
|
-
consola.error('You must be logged in to run this command.');
|
|
18
|
+
consola.error('You must be logged in to run this command. Please run the `login` command first.');
|
|
19
19
|
process.exit(1);
|
|
20
20
|
}
|
|
21
21
|
if (!organizationId) {
|
|
@@ -14,7 +14,7 @@ export default defineCommand({
|
|
|
14
14
|
action: async (options, args) => {
|
|
15
15
|
let { appId } = options;
|
|
16
16
|
if (!authorizationService.hasAuthorizationToken()) {
|
|
17
|
-
consola.error('You must be logged in to run this command.');
|
|
17
|
+
consola.error('You must be logged in to run this command. Please run the `login` command first.');
|
|
18
18
|
process.exit(1);
|
|
19
19
|
}
|
|
20
20
|
if (!appId) {
|
|
@@ -28,7 +28,7 @@ export default defineCommand({
|
|
|
28
28
|
let { appId, deploymentId } = options;
|
|
29
29
|
// Check if the user is logged in
|
|
30
30
|
if (!authorizationService.hasAuthorizationToken()) {
|
|
31
|
-
consola.error('You must be logged in to run this command.');
|
|
31
|
+
consola.error('You must be logged in to run this command. Please run the `login` command first.');
|
|
32
32
|
process.exit(1);
|
|
33
33
|
}
|
|
34
34
|
// Prompt for app ID if not provided
|
|
@@ -37,7 +37,7 @@ export default defineCommand({
|
|
|
37
37
|
let { appId, buildId, destination } = options;
|
|
38
38
|
// Check if the user is logged in
|
|
39
39
|
if (!authorizationService.hasAuthorizationToken()) {
|
|
40
|
-
consola.error('You must be logged in to run this command.');
|
|
40
|
+
consola.error('You must be logged in to run this command. Please run the `login` command first.');
|
|
41
41
|
process.exit(1);
|
|
42
42
|
}
|
|
43
43
|
// Prompt for app ID if not provided
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import appDeploymentsService from '../../../services/app-deployments.js';
|
|
1
2
|
import appsService from '../../../services/apps.js';
|
|
2
3
|
import authorizationService from '../../../services/authorization-service.js';
|
|
3
4
|
import organizationsService from '../../../services/organizations.js';
|
|
@@ -8,7 +9,6 @@ import { defineCommand, defineOptions } from '@robingenz/zli';
|
|
|
8
9
|
import consola from 'consola';
|
|
9
10
|
import { hasTTY } from 'std-env';
|
|
10
11
|
import { z } from 'zod';
|
|
11
|
-
import appDeploymentsService from '../../../services/app-deployments.js';
|
|
12
12
|
export default defineCommand({
|
|
13
13
|
description: 'View the deployment logs of an app.',
|
|
14
14
|
options: defineOptions(z.object({
|
|
@@ -29,7 +29,7 @@ export default defineCommand({
|
|
|
29
29
|
let { appId, deploymentId } = options;
|
|
30
30
|
// Check if the user is logged in
|
|
31
31
|
if (!authorizationService.hasAuthorizationToken()) {
|
|
32
|
-
consola.error('You must be logged in to run this command.');
|
|
32
|
+
consola.error('You must be logged in to run this command. Please run the `login` command first.');
|
|
33
33
|
process.exit(1);
|
|
34
34
|
}
|
|
35
35
|
// Prompt for app ID if not provided
|
|
@@ -16,7 +16,7 @@ export default defineCommand({
|
|
|
16
16
|
action: async (options, args) => {
|
|
17
17
|
let { appId, deviceId } = options;
|
|
18
18
|
if (!authorizationService.hasAuthorizationToken()) {
|
|
19
|
-
consola.error('You must be logged in to run this command.');
|
|
19
|
+
consola.error('You must be logged in to run this command. Please run the `login` command first.');
|
|
20
20
|
process.exit(1);
|
|
21
21
|
}
|
|
22
22
|
// Prompt for app ID if not provided
|
|
@@ -13,7 +13,7 @@ export default defineCommand({
|
|
|
13
13
|
action: async (options, args) => {
|
|
14
14
|
let { name } = options;
|
|
15
15
|
if (!authorizationService.hasAuthorizationToken()) {
|
|
16
|
-
consola.error('You must be logged in to run this command.');
|
|
16
|
+
consola.error('You must be logged in to run this command. Please run the `login` command first.');
|
|
17
17
|
process.exit(1);
|
|
18
18
|
}
|
|
19
19
|
if (!name) {
|
|
@@ -67,7 +67,7 @@ describe('organizations-create', () => {
|
|
|
67
67
|
const options = { name: organizationName };
|
|
68
68
|
mockAuthorizationService.hasAuthorizationToken.mockReturnValue(false);
|
|
69
69
|
await expect(createOrganizationCommand.action(options, undefined)).rejects.toThrow('Process exited with code 1');
|
|
70
|
-
expect(mockConsola.error).toHaveBeenCalledWith('You must be logged in to run this command.');
|
|
70
|
+
expect(mockConsola.error).toHaveBeenCalledWith('You must be logged in to run this command. Please run the `login` command first.');
|
|
71
71
|
});
|
|
72
72
|
it('should handle API error during creation', async () => {
|
|
73
73
|
const organizationName = 'Test Organization';
|
package/dist/services/apps.js
CHANGED
|
@@ -31,6 +31,14 @@ class AppsServiceImpl {
|
|
|
31
31
|
});
|
|
32
32
|
return response.data;
|
|
33
33
|
}
|
|
34
|
+
async findOne(dto) {
|
|
35
|
+
const response = await this.httpClient.get(`/v1/apps/${dto.appId}`, {
|
|
36
|
+
headers: {
|
|
37
|
+
Authorization: `Bearer ${authorizationService.getCurrentAuthorizationToken()}`,
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
return response.data;
|
|
41
|
+
}
|
|
34
42
|
}
|
|
35
43
|
const appsService = new AppsServiceImpl(httpClient);
|
|
36
44
|
export default appsService;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import pathModule from 'path';
|
|
3
|
+
import { fileExistsAtPath } from './file.js';
|
|
4
|
+
/**
|
|
5
|
+
* Find the Capacitor config file in the current working directory.
|
|
6
|
+
* Looks for capacitor.config.json or capacitor.config.ts.
|
|
7
|
+
*
|
|
8
|
+
* @returns The path to the config file, or undefined if not found.
|
|
9
|
+
*/
|
|
10
|
+
export const findCapacitorConfigPath = async () => {
|
|
11
|
+
const cwd = process.cwd();
|
|
12
|
+
const jsonPath = pathModule.join(cwd, 'capacitor.config.json');
|
|
13
|
+
const tsPath = pathModule.join(cwd, 'capacitor.config.ts');
|
|
14
|
+
if (await fileExistsAtPath(jsonPath)) {
|
|
15
|
+
return jsonPath;
|
|
16
|
+
}
|
|
17
|
+
if (await fileExistsAtPath(tsPath)) {
|
|
18
|
+
return tsPath;
|
|
19
|
+
}
|
|
20
|
+
return undefined;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Read and parse the Capacitor config file.
|
|
24
|
+
*
|
|
25
|
+
* @param configPath The path to the config file.
|
|
26
|
+
* @returns The parsed config object, or undefined if parsing fails.
|
|
27
|
+
*/
|
|
28
|
+
export const readCapacitorConfig = async (configPath) => {
|
|
29
|
+
try {
|
|
30
|
+
if (configPath.endsWith('.json')) {
|
|
31
|
+
const content = await fs.promises.readFile(configPath, 'utf-8');
|
|
32
|
+
return JSON.parse(content);
|
|
33
|
+
}
|
|
34
|
+
else if (configPath.endsWith('.ts')) {
|
|
35
|
+
// For TypeScript config, parse as text and extract values
|
|
36
|
+
const content = await fs.promises.readFile(configPath, 'utf-8');
|
|
37
|
+
// Extract webDir using regex
|
|
38
|
+
const webDirMatch = content.match(/webDir:\s*['"]([^'"]+)['"]/);
|
|
39
|
+
const appIdMatch = content.match(/LiveUpdate:\s*{[^}]*appId:\s*['"]([^'"]+)['"]/s);
|
|
40
|
+
const config = {};
|
|
41
|
+
if (webDirMatch) {
|
|
42
|
+
config.webDir = webDirMatch[1];
|
|
43
|
+
}
|
|
44
|
+
if (appIdMatch) {
|
|
45
|
+
config.plugins = {
|
|
46
|
+
LiveUpdate: {
|
|
47
|
+
appId: appIdMatch[1],
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return Object.keys(config).length > 0 ? config : undefined;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
// Return undefined if parsing fails
|
|
56
|
+
}
|
|
57
|
+
return undefined;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Get the webDir from the Capacitor config.
|
|
61
|
+
* Returns the absolute path to the webDir.
|
|
62
|
+
*
|
|
63
|
+
* @param configPath The path to the config file.
|
|
64
|
+
* @returns The absolute path to the webDir, or undefined if not found.
|
|
65
|
+
*/
|
|
66
|
+
export const getWebDirFromConfig = async (configPath) => {
|
|
67
|
+
const config = await readCapacitorConfig(configPath);
|
|
68
|
+
if (config?.webDir) {
|
|
69
|
+
// Resolve the webDir relative to the config file location
|
|
70
|
+
const configDir = pathModule.dirname(configPath);
|
|
71
|
+
return pathModule.resolve(configDir, config.webDir);
|
|
72
|
+
}
|
|
73
|
+
return undefined;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Get the LiveUpdate appId from the Capacitor config.
|
|
77
|
+
*
|
|
78
|
+
* @param configPath The path to the config file.
|
|
79
|
+
* @returns The appId, or undefined if not found.
|
|
80
|
+
*/
|
|
81
|
+
export const getCapawesomeCloudAppIdFromConfig = async (configPath) => {
|
|
82
|
+
const config = await readCapacitorConfig(configPath);
|
|
83
|
+
return config?.plugins?.LiveUpdate?.appId;
|
|
84
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { fileExistsAtPath } from './file.js';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import pathModule from 'path';
|
|
4
|
+
/**
|
|
5
|
+
* Find the package.json file in the current working directory.
|
|
6
|
+
*
|
|
7
|
+
* @returns The path to the package.json file, or undefined if not found.
|
|
8
|
+
*/
|
|
9
|
+
export const findPackageJsonPath = async () => {
|
|
10
|
+
const cwd = process.cwd();
|
|
11
|
+
const packageJsonPath = pathModule.join(cwd, 'package.json');
|
|
12
|
+
if (await fileExistsAtPath(packageJsonPath)) {
|
|
13
|
+
return packageJsonPath;
|
|
14
|
+
}
|
|
15
|
+
return undefined;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Read and parse the package.json file.
|
|
19
|
+
*
|
|
20
|
+
* @param packageJsonPath The path to the package.json file.
|
|
21
|
+
* @returns The parsed package.json object, or undefined if parsing fails.
|
|
22
|
+
*/
|
|
23
|
+
export const readPackageJson = async (packageJsonPath) => {
|
|
24
|
+
try {
|
|
25
|
+
const content = await fs.promises.readFile(packageJsonPath, 'utf-8');
|
|
26
|
+
return JSON.parse(content);
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Get the build script from package.json.
|
|
34
|
+
* Prefers 'capawesome:build' over 'build' if both exist.
|
|
35
|
+
*
|
|
36
|
+
* @param packageJsonPath The path to the package.json file.
|
|
37
|
+
* @returns An object with the script name and command, or undefined if not found.
|
|
38
|
+
*/
|
|
39
|
+
export const getBuildScript = async (packageJsonPath) => {
|
|
40
|
+
const packageJson = await readPackageJson(packageJsonPath);
|
|
41
|
+
if (!packageJson?.scripts) {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
// Prefer 'capawesome:build' over 'build'
|
|
45
|
+
if (packageJson.scripts['capawesome:build']) {
|
|
46
|
+
return {
|
|
47
|
+
name: 'capawesome:build',
|
|
48
|
+
command: packageJson.scripts['capawesome:build'],
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
if (packageJson.scripts['build']) {
|
|
52
|
+
return {
|
|
53
|
+
name: 'build',
|
|
54
|
+
command: packageJson.scripts['build'],
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
return undefined;
|
|
58
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capawesome/cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.7.0-dev.f4e106a.1764835462",
|
|
4
4
|
"description": "The Capawesome Cloud Command Line Interface (CLI) to manage Live Updates and more.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"fmt": "npm run prettier -- --write",
|
|
14
14
|
"prettier": "prettier \"**/*.{css,html,ts,js}\"",
|
|
15
15
|
"sentry:releases:new": "sentry-cli releases new capawesome-team-cli@$npm_package_version --org genz-it-solutions-gmbh --project capawesome-team-cli",
|
|
16
|
-
"sentry:releases:set-commits": "sentry-cli releases set-commits capawesome-team-cli@$npm_package_version --auto --org genz-it-solutions-gmbh --project capawesome-team-cli
|
|
16
|
+
"sentry:releases:set-commits": "sentry-cli releases set-commits capawesome-team-cli@$npm_package_version --auto --org genz-it-solutions-gmbh --project capawesome-team-cli",
|
|
17
17
|
"sentry:releases:finalize": "sentry-cli releases finalize capawesome-team-cli@$npm_package_version --org genz-it-solutions-gmbh --project capawesome-team-cli",
|
|
18
18
|
"release": "commit-and-tag-version",
|
|
19
19
|
"prepublishOnly": "npm run build && npm run sentry:releases:new && npm run sentry:releases:set-commits",
|