@capawesome/cli 4.6.0-dev.80c962a.1774597304 → 4.6.0-dev.8ae803e.1775035527

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.
@@ -1,13 +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 { unescapeAnsi } from '../../../utils/ansi.js';
6
6
  import { parseKeyValuePairs } from '../../../utils/app-environments.js';
7
7
  import { withAuth } from '../../../utils/auth.js';
8
8
  import { isInteractive } from '../../../utils/environment.js';
9
+ import { waitForJobCompletion } from '../../../utils/job.js';
9
10
  import { prompt, promptAppSelection, promptOrganizationSelection } from '../../../utils/prompt.js';
10
- import { wait } from '../../../utils/wait.js';
11
+ import zip from '../../../utils/zip.js';
11
12
  import { defineCommand, defineOptions } from '@robingenz/zli';
12
13
  import consola from 'consola';
13
14
  import fs from 'fs/promises';
@@ -16,7 +17,7 @@ import { z } from 'zod';
16
17
  const IOS_BUILD_TYPES = ['simulator', 'development', 'ad-hoc', 'app-store', 'enterprise'];
17
18
  const ANDROID_BUILD_TYPES = ['debug', 'release'];
18
19
  export default defineCommand({
19
- description: 'Create a new app build.',
20
+ description: 'Create a new app build on Capawesome Cloud Runners.',
20
21
  options: defineOptions(z.object({
21
22
  aab: z
22
23
  .union([z.boolean(), z.string()])
@@ -46,6 +47,7 @@ export default defineCommand({
46
47
  .optional()
47
48
  .describe('Download the generated IPA file (iOS only). Optionally provide a file path.'),
48
49
  json: z.boolean().optional().describe('Output in JSON format.'),
50
+ path: z.string().optional().describe('Path to local source files to upload.'),
49
51
  platform: z
50
52
  .enum(['ios', 'android', 'web'], {
51
53
  message: 'Platform must be either `ios`, `android`, or `web`.',
@@ -58,6 +60,7 @@ export default defineCommand({
58
60
  })
59
61
  .optional()
60
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.'),
61
64
  type: z
62
65
  .string()
63
66
  .optional()
@@ -77,7 +80,7 @@ export default defineCommand({
77
80
  yes: z.boolean().optional().describe('Skip confirmation prompts.'),
78
81
  }), { y: 'yes' }),
79
82
  action: withAuth(async (options) => {
80
- let { appId, platform, type, gitRef, environment, certificate, json, stack } = options;
83
+ let { appId, platform, type, gitRef, environment, certificate, json, stack, path: sourcePath, url } = options;
81
84
  // Validate that detached flag cannot be used with artifact flags
82
85
  if (options.detached && (options.apk || options.aab || options.ipa || options.zip)) {
83
86
  consola.error('The --detached flag cannot be used with --apk, --aab, --ipa, or --zip flags.');
@@ -93,6 +96,39 @@ export default defineCommand({
93
96
  consola.error('The --channel and --destination flags cannot be used together.');
94
97
  process.exit(1);
95
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
+ }
96
132
  // Prompt for app ID if not provided
97
133
  if (!appId) {
98
134
  if (!isInteractive()) {
@@ -122,10 +158,10 @@ export default defineCommand({
122
158
  process.exit(1);
123
159
  }
124
160
  }
125
- // Prompt for git ref if not provided
126
- if (!gitRef) {
161
+ // Prompt for git ref if not provided and no path or url specified
162
+ if (!sourcePath && !url && !gitRef) {
127
163
  if (!isInteractive()) {
128
- 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.');
129
165
  process.exit(1);
130
166
  }
131
167
  gitRef = await prompt('Enter the Git reference (branch, tag, or commit SHA):', {
@@ -218,10 +254,36 @@ export default defineCommand({
218
254
  inlineVariables.forEach((v) => variablesMap.set(v.key, v.value));
219
255
  }
220
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
+ }
221
282
  // Create the app build
222
283
  consola.start('Creating build...');
223
284
  const response = await appBuildsService.create({
224
285
  adHocEnvironmentVariables,
286
+ appBuildSourceId,
225
287
  appCertificateName: certificate,
226
288
  appEnvironmentName: environment,
227
289
  appId,
@@ -237,127 +299,60 @@ export default defineCommand({
237
299
  // Wait for build job to complete by default, unless --detached flag is set
238
300
  const shouldWait = !options.detached;
239
301
  if (shouldWait) {
240
- let lastPrintedLogNumber = 0;
241
- let isWaitingForStart = true;
242
- // Poll build status until completion
243
- while (true) {
244
- try {
245
- const build = await appBuildsService.findOne({
246
- appId,
247
- appBuildId: response.id,
248
- relations: 'appBuildArtifacts,job,job.jobLogs',
249
- });
250
- if (!build.job) {
251
- await wait(3000);
252
- continue;
253
- }
254
- const jobStatus = build.job.status;
255
- // Show spinner while queued or pending
256
- if (jobStatus === 'queued' || jobStatus === 'pending') {
257
- if (isWaitingForStart) {
258
- consola.start(`Waiting for build to start (status: ${jobStatus})...`);
259
- }
260
- await wait(3000);
261
- continue;
262
- }
263
- // Stop spinner when job moves to in_progress
264
- if (isWaitingForStart && jobStatus === 'in_progress') {
265
- isWaitingForStart = false;
266
- consola.success('Build started...');
267
- }
268
- // Print new logs
269
- if (build.job.jobLogs && build.job.jobLogs.length > 0) {
270
- const newLogs = build.job.jobLogs
271
- .filter((log) => log.number > lastPrintedLogNumber)
272
- .sort((a, b) => a.number - b.number);
273
- for (const log of newLogs) {
274
- console.log(unescapeAnsi(log.payload));
275
- lastPrintedLogNumber = log.number;
276
- }
277
- }
278
- // Handle terminal states
279
- if (jobStatus === 'succeeded' ||
280
- jobStatus === 'failed' ||
281
- jobStatus === 'canceled' ||
282
- jobStatus === 'rejected' ||
283
- jobStatus === 'timed_out') {
284
- console.log(); // New line for better readability
285
- if (jobStatus === 'succeeded') {
286
- consola.info(`Build ID: ${response.id}`);
287
- consola.info(`Build Number: ${response.numberAsString}`);
288
- consola.info(`Build URL: ${DEFAULT_CONSOLE_BASE_URL}/apps/${appId}/builds/${response.id}`);
289
- consola.success('Build completed successfully.');
290
- console.log(); // New line for better readability
291
- // Download artifacts if flags are set
292
- if (options.apk && platform === 'android') {
293
- await handleArtifactDownload({
294
- appId,
295
- buildId: response.id,
296
- buildArtifacts: build.appBuildArtifacts,
297
- artifactType: 'apk',
298
- filePath: typeof options.apk === 'string' ? options.apk : undefined,
299
- });
300
- }
301
- if (options.aab && platform === 'android') {
302
- await handleArtifactDownload({
303
- appId,
304
- buildId: response.id,
305
- buildArtifacts: build.appBuildArtifacts,
306
- artifactType: 'aab',
307
- filePath: typeof options.aab === 'string' ? options.aab : undefined,
308
- });
309
- }
310
- if (options.ipa && platform === 'ios') {
311
- await handleArtifactDownload({
312
- appId,
313
- buildId: response.id,
314
- buildArtifacts: build.appBuildArtifacts,
315
- artifactType: 'ipa',
316
- filePath: typeof options.ipa === 'string' ? options.ipa : undefined,
317
- });
318
- }
319
- if (options.zip && platform === 'web') {
320
- await handleArtifactDownload({
321
- appId,
322
- buildId: response.id,
323
- buildArtifacts: build.appBuildArtifacts,
324
- artifactType: 'zip',
325
- filePath: typeof options.zip === 'string' ? options.zip : undefined,
326
- });
327
- }
328
- // Output JSON if json flag is set
329
- if (json) {
330
- console.log(JSON.stringify({
331
- id: response.id,
332
- numberAsString: response.numberAsString,
333
- }, null, 2));
334
- }
335
- break;
336
- }
337
- else if (jobStatus === 'failed') {
338
- consola.error('Build failed.');
339
- process.exit(1);
340
- }
341
- else if (jobStatus === 'canceled') {
342
- consola.warn('Build was canceled.');
343
- process.exit(1);
344
- }
345
- else if (jobStatus === 'rejected') {
346
- consola.error('Build was rejected.');
347
- process.exit(1);
348
- }
349
- else if (jobStatus === 'timed_out') {
350
- consola.error('Build timed out.');
351
- process.exit(1);
352
- }
353
- }
354
- // Wait before next poll (3 seconds)
355
- await wait(3000);
356
- }
357
- catch (error) {
358
- consola.error('Error polling build status:', error);
359
- process.exit(1);
360
- }
302
+ await waitForJobCompletion({ jobId: response.jobId });
303
+ const appBuild = await appBuildsService.findOne({
304
+ appId,
305
+ appBuildId: response.id,
306
+ relations: 'appBuildArtifacts',
307
+ });
308
+ consola.info(`Build ID: ${response.id}`);
309
+ consola.info(`Build Number: ${response.numberAsString}`);
310
+ consola.info(`Build URL: ${DEFAULT_CONSOLE_BASE_URL}/apps/${appId}/builds/${response.id}`);
311
+ consola.success('Build completed successfully.');
312
+ console.log();
313
+ // Download artifacts if flags are set
314
+ if (options.apk && platform === 'android') {
315
+ await handleArtifactDownload({
316
+ appId,
317
+ buildId: response.id,
318
+ buildArtifacts: appBuild.appBuildArtifacts,
319
+ artifactType: 'apk',
320
+ filePath: typeof options.apk === 'string' ? options.apk : undefined,
321
+ });
322
+ }
323
+ if (options.aab && platform === 'android') {
324
+ await handleArtifactDownload({
325
+ appId,
326
+ buildId: response.id,
327
+ buildArtifacts: appBuild.appBuildArtifacts,
328
+ artifactType: 'aab',
329
+ filePath: typeof options.aab === 'string' ? options.aab : undefined,
330
+ });
331
+ }
332
+ if (options.ipa && platform === 'ios') {
333
+ await handleArtifactDownload({
334
+ appId,
335
+ buildId: response.id,
336
+ buildArtifacts: appBuild.appBuildArtifacts,
337
+ artifactType: 'ipa',
338
+ filePath: typeof options.ipa === 'string' ? options.ipa : undefined,
339
+ });
340
+ }
341
+ if (options.zip && platform === 'web') {
342
+ await handleArtifactDownload({
343
+ appId,
344
+ buildId: response.id,
345
+ buildArtifacts: appBuild.appBuildArtifacts,
346
+ artifactType: 'zip',
347
+ filePath: typeof options.zip === 'string' ? options.zip : undefined,
348
+ });
349
+ }
350
+ // Output JSON if json flag is set
351
+ if (json) {
352
+ console.log(JSON.stringify({
353
+ id: response.id,
354
+ numberAsString: response.numberAsString,
355
+ }, null, 2));
361
356
  }
362
357
  }
363
358
  else {
@@ -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:upload` to upload a bundle to Capawesome Cloud');
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 and will be removed in future versions.');
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 and will be removed in future versions.');
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
- consola.error(`No certificate found with name '${name}' and platform '${platform}'.`);
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
- const certificates = await appCertificatesService.findAll({ appId, platform });
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('No certificates found for this app. Create one first.');
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
- consola.error(`No certificate found with name '${name}' and platform '${platform}'.`);
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
- const certificates = await appCertificatesService.findAll({ appId, platform });
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('No certificates found for this app. Create one first.');
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
- let lastPrintedLogNumber = 0;
163
- let isWaitingForStart = true;
164
- // Poll deployment status until completion
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
  });
@@ -1,5 +1,5 @@
1
1
  import { isInteractive } from '../../../utils/environment.js';
2
- import { directoryContainsSourceMaps, fileExistsAtPath, isDirectory } from '../../../utils/file.js';
2
+ import { directoryContainsSourceMaps, directoryContainsSymlinks, 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';
@@ -9,7 +9,7 @@ import fs from 'fs';
9
9
  import pathModule from 'path';
10
10
  import { z } from 'zod';
11
11
  export default defineCommand({
12
- description: 'Generate manifest file and compress web assets into a bundle.zip file.',
12
+ description: 'Generate manifest file and compress locally built web assets into a bundle.zip file.',
13
13
  options: defineOptions(z.object({
14
14
  inputPath: z.string().optional().describe('Path to the web assets directory.'),
15
15
  outputPath: z
@@ -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 symlinks
66
+ const containsSymlinks = await directoryContainsSymlinks(inputPath);
67
+ if (containsSymlinks) {
68
+ consola.warn('Symbolic links were detected in the specified path. Symbolic links are skipped during bundling.');
69
+ }
65
70
  // Check for source maps
66
71
  const containsSourceMaps = await directoryContainsSourceMaps(inputPath);
67
72
  if (containsSourceMaps) {