@capawesome/cli 3.7.0-dev.24727dc.1764842591 → 3.7.0-dev.9668558.1764835928

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.
@@ -5,7 +5,7 @@ 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, getLiveUpdatePluginAppIdFromConfig, getLiveUpdatePluginPublicKeyFromConfig, getWebDirFromConfig, } from '../../../utils/capacitor-config.js';
8
+ import { findCapacitorConfigPath, getCapawesomeCloudAppIdFromConfig, getWebDirFromConfig, } from '../../../utils/capacitor-config.js';
9
9
  import { fileExistsAtPath, getFilesInDirectoryAndSubdirectories, isDirectory } from '../../../utils/file.js';
10
10
  import { createHash } from '../../../utils/hash.js';
11
11
  import { generateManifestJson } from '../../../utils/manifest.js';
@@ -22,7 +22,6 @@ import pathModule from 'path';
22
22
  import { hasTTY } from 'std-env';
23
23
  import { promisify } from 'util';
24
24
  import { z } from 'zod';
25
- // Promisified exec for running build scripts
26
25
  const execAsync = promisify(exec);
27
26
  export default defineCommand({
28
27
  description: 'Create a new app bundle.',
@@ -117,24 +116,23 @@ export default defineCommand({
117
116
  expiresAtDate.setDate(expiresAtDate.getDate() + expiresInDays);
118
117
  expiresAt = expiresAtDate.toISOString();
119
118
  }
120
- // Try to auto-detect webDir from Capacitor configuration
121
- const capacitorConfigPath = await findCapacitorConfigPath();
122
119
  // Check that either a path or a url is provided
123
120
  if (!path && !url) {
124
- // Try to auto-detect webDir from Capacitor configuration
121
+ // Try to auto-detect webDir from Capacitor config
122
+ const capacitorConfigPath = await findCapacitorConfigPath();
125
123
  if (capacitorConfigPath) {
126
124
  const webDirPath = await getWebDirFromConfig(capacitorConfigPath);
127
125
  if (webDirPath) {
128
126
  const relativeWebDirPath = pathModule.relative(process.cwd(), webDirPath);
129
- consola.success(`Auto-detected web asset directory "${relativeWebDirPath}" from Capacitor configuration.`);
127
+ consola.success(`Auto-detected web asset directory "${relativeWebDirPath}" from Capacitor config.`);
130
128
  path = webDirPath;
131
129
  }
132
130
  else {
133
- consola.warn('No web asset directory found in Capacitor configuration (`webDir`).');
131
+ consola.warn('No web asset directory found in Capacitor config (`webDir`).');
134
132
  }
135
133
  }
136
134
  else {
137
- consola.warn('No Capacitor configuration found to auto-detect web asset directory.');
135
+ consola.warn('No Capacitor config found to auto-detect web asset directory.');
138
136
  }
139
137
  // If still no path, prompt the user
140
138
  if (!path) {
@@ -162,7 +160,7 @@ export default defineCommand({
162
160
  else {
163
161
  const buildScript = await getBuildScript(packageJsonPath);
164
162
  if (!buildScript) {
165
- consola.warn('No build script (`capawesome:build` or `build`) found in package.json.');
163
+ consola.warn('No build script found in package.json.');
166
164
  }
167
165
  else if (hasTTY) {
168
166
  const shouldBuild = await prompt('Do you want to rebuild your web assets before proceeding?', {
@@ -171,7 +169,7 @@ export default defineCommand({
171
169
  });
172
170
  if (shouldBuild === 'Yes') {
173
171
  try {
174
- consola.start(`Running \`${buildScript.name}\` script...`);
172
+ consola.start(`Running ${buildScript.name} script...`);
175
173
  const { stdout, stderr } = await execAsync(`npm run ${buildScript.name}`);
176
174
  if (stdout) {
177
175
  console.log(stdout);
@@ -234,23 +232,21 @@ export default defineCommand({
234
232
  consola.error('It is not yet possible to provide a URL when creating a bundle with an artifact type of `manifest`.');
235
233
  process.exit(1);
236
234
  }
237
- // Track if we found a Capacitor configuration but no app ID (for showing setup hint later)
238
- let hasCapacitorConfigWithoutAppId = false;
239
235
  if (!appId) {
240
- // Try to auto-detect appId from Capacitor configuration
236
+ // Try to auto-detect appId from Capacitor config
237
+ const capacitorConfigPath = await findCapacitorConfigPath();
241
238
  if (capacitorConfigPath) {
242
- const configAppId = await getLiveUpdatePluginAppIdFromConfig(capacitorConfigPath);
239
+ const configAppId = await getCapawesomeCloudAppIdFromConfig(capacitorConfigPath);
243
240
  if (configAppId) {
244
- consola.success(`Auto-detected Capawesome Cloud app ID "${configAppId}" from Capacitor configuration.`);
241
+ consola.success(`Auto-detected Capawesome Cloud app ID "${configAppId}" from Capacitor config.`);
245
242
  appId = configAppId;
246
243
  }
247
244
  else {
248
- hasCapacitorConfigWithoutAppId = true;
249
- consola.warn('No Capawesome Cloud app ID found in Capacitor configuration (`plugins.LiveUpdate.appId`).');
245
+ consola.warn('No Capawesome Cloud app ID found in Capacitor config (`plugins.LiveUpdate.appId`).');
250
246
  }
251
247
  }
252
248
  else {
253
- consola.warn('No Capacitor configuration found to auto-detect Capawesome Cloud app ID.');
249
+ consola.warn('No Capacitor config found to auto-detect Capawesome Cloud app ID.');
254
250
  }
255
251
  // If still no appId, prompt the user
256
252
  if (!appId) {
@@ -305,13 +301,6 @@ export default defineCommand({
305
301
  }
306
302
  }
307
303
  }
308
- // Check if public key is configured but no private key was provided
309
- if (!privateKey && capacitorConfigPath) {
310
- const publicKey = await getLiveUpdatePluginPublicKeyFromConfig(capacitorConfigPath);
311
- if (publicKey) {
312
- consola.warn('A public key for verifying the integrity of the bundles is configured in your Capacitor configuration, but no private key has been provided for signing this bundle.');
313
- }
314
- }
315
304
  // Create the private key buffer
316
305
  let privateKeyBuffer;
317
306
  if (privateKey) {
@@ -348,7 +337,7 @@ export default defineCommand({
348
337
  const confirmed = await prompt(`Are you sure you want to create a bundle from path "${relativePath}" for app "${appName}" (${appId})?`, {
349
338
  type: 'confirm',
350
339
  });
351
- if (!confirmed) {
340
+ if (confirmed) {
352
341
  consola.info('Bundle creation cancelled.');
353
342
  process.exit(0);
354
343
  }
@@ -421,11 +410,6 @@ export default defineCommand({
421
410
  }
422
411
  consola.success('Bundle successfully created.');
423
412
  consola.info(`Bundle ID: ${response.id}`);
424
- // Show setup hint if Capacitor configuration exists but no app ID was configured
425
- if (hasCapacitorConfigWithoutAppId) {
426
- console.log(); // New line for better readability
427
- consola.info('To set up the Live Update SDK in your Capacitor app, run the `apps:liveupdates:setup` command.');
428
- }
429
413
  }
430
414
  catch (error) {
431
415
  if (appBundleId) {
@@ -1,8 +1,6 @@
1
1
  import { DEFAULT_API_BASE_URL } from '../../../config/consts.js';
2
2
  import authorizationService from '../../../services/authorization-service.js';
3
- import { findCapacitorConfigPath } from '../../../utils/capacitor-config.js';
4
3
  import { fileExistsAtPath, getFilesInDirectoryAndSubdirectories, isDirectory } from '../../../utils/file.js';
5
- import { findPackageJsonPath } from '../../../utils/package-json.js';
6
4
  import userConfig from '../../../utils/user-config.js';
7
5
  import consola from 'consola';
8
6
  import nock from 'nock';
@@ -17,8 +15,6 @@ vi.mock('@/utils/buffer.js');
17
15
  vi.mock('@/utils/private-key.js');
18
16
  vi.mock('@/utils/hash.js');
19
17
  vi.mock('@/utils/signature.js');
20
- vi.mock('@/utils/capacitor-config.js');
21
- vi.mock('@/utils/package-json.js');
22
18
  vi.mock('consola');
23
19
  describe('apps-bundles-create', () => {
24
20
  const mockUserConfig = vi.mocked(userConfig);
@@ -26,16 +22,12 @@ describe('apps-bundles-create', () => {
26
22
  const mockFileExistsAtPath = vi.mocked(fileExistsAtPath);
27
23
  const mockGetFilesInDirectoryAndSubdirectories = vi.mocked(getFilesInDirectoryAndSubdirectories);
28
24
  const mockIsDirectory = vi.mocked(isDirectory);
29
- const mockFindCapacitorConfigPath = vi.mocked(findCapacitorConfigPath);
30
- const mockFindPackageJsonPath = vi.mocked(findPackageJsonPath);
31
25
  const mockConsola = vi.mocked(consola);
32
26
  beforeEach(() => {
33
27
  vi.clearAllMocks();
34
28
  mockUserConfig.read.mockReturnValue({ token: 'test-token' });
35
29
  mockAuthorizationService.hasAuthorizationToken.mockReturnValue(true);
36
30
  mockAuthorizationService.getCurrentAuthorizationToken.mockReturnValue('test-token');
37
- mockFindCapacitorConfigPath.mockResolvedValue(undefined);
38
- mockFindPackageJsonPath.mockResolvedValue(undefined);
39
31
  vi.spyOn(process, 'exit').mockImplementation((code) => {
40
32
  throw new Error(`Process exited with code ${code}`);
41
33
  });
@@ -75,11 +67,7 @@ describe('apps-bundles-create', () => {
75
67
  vi.mocked(mockZip.default.isZipped).mockReturnValue(true);
76
68
  vi.mocked(mockBuffer.createBufferFromPath).mockResolvedValue(testBuffer);
77
69
  vi.mocked(mockHash.createHash).mockResolvedValue(testHash);
78
- const appScope = nock(DEFAULT_API_BASE_URL)
79
- .get(`/v1/apps/${appId}`)
80
- .matchHeader('Authorization', `Bearer ${testToken}`)
81
- .reply(200, { id: appId, name: 'Test App' });
82
- const bundleScope = nock(DEFAULT_API_BASE_URL)
70
+ const scope = nock(DEFAULT_API_BASE_URL)
83
71
  .post(`/v1/apps/${appId}/bundles`, {
84
72
  appId,
85
73
  url: bundleUrl,
@@ -90,8 +78,7 @@ describe('apps-bundles-create', () => {
90
78
  .matchHeader('Authorization', `Bearer ${testToken}`)
91
79
  .reply(201, { id: bundleId });
92
80
  await createBundleCommand.action(options, undefined);
93
- expect(appScope.isDone()).toBe(true);
94
- expect(bundleScope.isDone()).toBe(true);
81
+ expect(scope.isDone()).toBe(true);
95
82
  expect(mockConsola.success).toHaveBeenCalledWith('Bundle successfully created.');
96
83
  expect(mockConsola.info).toHaveBeenCalledWith(`Bundle ID: ${bundleId}`);
97
84
  });
@@ -142,17 +129,12 @@ describe('apps-bundles-create', () => {
142
129
  artifactType: 'zip',
143
130
  rollout: 1,
144
131
  };
145
- const appScope = nock(DEFAULT_API_BASE_URL)
146
- .get(`/v1/apps/${appId}`)
147
- .matchHeader('Authorization', `Bearer ${testToken}`)
148
- .reply(200, { id: appId, name: 'Test App' });
149
- const bundleScope = nock(DEFAULT_API_BASE_URL)
132
+ const scope = nock(DEFAULT_API_BASE_URL)
150
133
  .post(`/v1/apps/${appId}/bundles`)
151
134
  .matchHeader('Authorization', `Bearer ${testToken}`)
152
135
  .reply(400, { message: 'Invalid bundle data' });
153
136
  await expect(createBundleCommand.action(options, undefined)).rejects.toThrow();
154
- expect(appScope.isDone()).toBe(true);
155
- expect(bundleScope.isDone()).toBe(true);
137
+ expect(scope.isDone()).toBe(true);
156
138
  });
157
139
  it('should handle private key file path', async () => {
158
140
  const appId = 'app-123';
@@ -193,11 +175,7 @@ describe('apps-bundles-create', () => {
193
175
  vi.mocked(mockBuffer.createBufferFromString).mockReturnValue(testBuffer);
194
176
  vi.mocked(mockHash.createHash).mockResolvedValue(testHash);
195
177
  vi.mocked(mockSignature.createSignature).mockResolvedValue(testSignature);
196
- const appScope = nock(DEFAULT_API_BASE_URL)
197
- .get(`/v1/apps/${appId}`)
198
- .matchHeader('Authorization', `Bearer ${testToken}`)
199
- .reply(200, { id: appId, name: 'Test App' });
200
- const bundleScope = nock(DEFAULT_API_BASE_URL)
178
+ const scope = nock(DEFAULT_API_BASE_URL)
201
179
  .post(`/v1/apps/${appId}/bundles`, {
202
180
  appId,
203
181
  url: bundleUrl,
@@ -209,8 +187,7 @@ describe('apps-bundles-create', () => {
209
187
  .matchHeader('Authorization', `Bearer ${testToken}`)
210
188
  .reply(201, { id: bundleId });
211
189
  await createBundleCommand.action(options, undefined);
212
- expect(appScope.isDone()).toBe(true);
213
- expect(bundleScope.isDone()).toBe(true);
190
+ expect(scope.isDone()).toBe(true);
214
191
  expect(mockConsola.success).toHaveBeenCalledWith('Bundle successfully created.');
215
192
  });
216
193
  it('should handle private key plain text content', async () => {
@@ -246,11 +223,7 @@ describe('apps-bundles-create', () => {
246
223
  vi.mocked(mockPrivateKey.formatPrivateKey).mockReturnValue('formatted-private-key');
247
224
  vi.mocked(mockHash.createHash).mockResolvedValue(testHash);
248
225
  vi.mocked(mockSignature.createSignature).mockResolvedValue(testSignature);
249
- const appScope = nock(DEFAULT_API_BASE_URL)
250
- .get(`/v1/apps/${appId}`)
251
- .matchHeader('Authorization', `Bearer ${testToken}`)
252
- .reply(200, { id: appId, name: 'Test App' });
253
- const bundleScope = nock(DEFAULT_API_BASE_URL)
226
+ const scope = nock(DEFAULT_API_BASE_URL)
254
227
  .post(`/v1/apps/${appId}/bundles`, {
255
228
  appId,
256
229
  url: bundleUrl,
@@ -262,8 +235,7 @@ describe('apps-bundles-create', () => {
262
235
  .matchHeader('Authorization', `Bearer ${testToken}`)
263
236
  .reply(201, { id: bundleId });
264
237
  await createBundleCommand.action(options, undefined);
265
- expect(appScope.isDone()).toBe(true);
266
- expect(bundleScope.isDone()).toBe(true);
238
+ expect(scope.isDone()).toBe(true);
267
239
  expect(mockConsola.success).toHaveBeenCalledWith('Bundle successfully created.');
268
240
  });
269
241
  it('should handle private key file not found', async () => {
package/dist/index.js CHANGED
@@ -39,7 +39,6 @@ const config = defineConfig({
39
39
  'apps:deployments:cancel': await import('./commands/apps/deployments/cancel.js').then((mod) => mod.default),
40
40
  'apps:deployments:logs': await import('./commands/apps/deployments/logs.js').then((mod) => mod.default),
41
41
  'apps:devices:delete': await import('./commands/apps/devices/delete.js').then((mod) => mod.default),
42
- 'apps:liveupdates:setup': await import('./commands/apps/liveupdates/setup.js').then((mod) => mod.default),
43
42
  'manifests:generate': await import('./commands/manifests/generate.js').then((mod) => mod.default),
44
43
  'organizations:create': await import('./commands/organizations/create.js').then((mod) => mod.default),
45
44
  },
@@ -37,16 +37,14 @@ export const readCapacitorConfig = async (configPath) => {
37
37
  // Extract webDir using regex
38
38
  const webDirMatch = content.match(/webDir:\s*['"]([^'"]+)['"]/);
39
39
  const appIdMatch = content.match(/LiveUpdate:\s*{[^}]*appId:\s*['"]([^'"]+)['"]/s);
40
- const publicKeyMatch = content.match(/LiveUpdate:\s*{[^}]*publicKey:\s*['"]([^'"]+)['"]/s);
41
40
  const config = {};
42
41
  if (webDirMatch) {
43
42
  config.webDir = webDirMatch[1];
44
43
  }
45
- if (appIdMatch || publicKeyMatch) {
44
+ if (appIdMatch) {
46
45
  config.plugins = {
47
46
  LiveUpdate: {
48
- ...(appIdMatch && { appId: appIdMatch[1] }),
49
- ...(publicKeyMatch && { publicKey: publicKeyMatch[1] }),
47
+ appId: appIdMatch[1],
50
48
  },
51
49
  };
52
50
  }
@@ -80,17 +78,7 @@ export const getWebDirFromConfig = async (configPath) => {
80
78
  * @param configPath The path to the config file.
81
79
  * @returns The appId, or undefined if not found.
82
80
  */
83
- export const getLiveUpdatePluginAppIdFromConfig = async (configPath) => {
81
+ export const getCapawesomeCloudAppIdFromConfig = async (configPath) => {
84
82
  const config = await readCapacitorConfig(configPath);
85
83
  return config?.plugins?.LiveUpdate?.appId;
86
84
  };
87
- /**
88
- * Get the LiveUpdate publicKey from the Capacitor config.
89
- *
90
- * @param configPath The path to the config file.
91
- * @returns The publicKey, or undefined if not found.
92
- */
93
- export const getLiveUpdatePluginPublicKeyFromConfig = async (configPath) => {
94
- const config = await readCapacitorConfig(configPath);
95
- return config?.plugins?.LiveUpdate?.publicKey;
96
- };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capawesome/cli",
3
- "version": "3.7.0-dev.24727dc.1764842591",
3
+ "version": "3.7.0-dev.9668558.1764835928",
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 --ignore-missing",
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",
@@ -1,252 +0,0 @@
1
- import appsService from '../../../services/apps.js';
2
- import authorizationService from '../../../services/authorization-service.js';
3
- import organizationsService from '../../../services/organizations.js';
4
- import { updateCapacitorConfig } from '../../../utils/capacitor-config-writer.js';
5
- import { findCapacitorConfigPath, readCapacitorConfig } from '../../../utils/capacitor-config.js';
6
- import { copyToClipboard } from '../../../utils/clipboard.js';
7
- import { fileExistsAtPath } from '../../../utils/file.js';
8
- import { checkOpensslInstalled, generateKeyPair, readPublicKey } from '../../../utils/openssl.js';
9
- import { installPackage } from '../../../utils/package-manager.js';
10
- import { prompt } from '../../../utils/prompt.js';
11
- import { defineCommand, defineOptions } from '@robingenz/zli';
12
- import consola from 'consola';
13
- import { hasTTY } from 'std-env';
14
- import { z } from 'zod';
15
- export default defineCommand({
16
- description: 'Set up Live Updates in your Capacitor app.',
17
- options: defineOptions(z.object({
18
- appId: z.string().uuid().optional().describe('Capawesome Cloud app ID.'),
19
- })),
20
- action: async (options, args) => {
21
- // Check if running in interactive mode
22
- if (!hasTTY) {
23
- consola.error('This command requires an interactive terminal. Please run it in an interactive environment.');
24
- process.exit(1);
25
- }
26
- let { appId } = options;
27
- // Step 1: Check if Capacitor config exists
28
- const capacitorConfigPath = await findCapacitorConfigPath();
29
- if (!capacitorConfigPath) {
30
- consola.error('No Capacitor configuration found. Make sure you are in a Capacitor project.');
31
- process.exit(1);
32
- }
33
- // Step 2: Read current config
34
- let config = await readCapacitorConfig(capacitorConfigPath);
35
- if (!config) {
36
- config = {};
37
- }
38
- // Step 3: Select/Configure Capawesome Cloud App ID
39
- // Try to get from config first
40
- if (!appId && config?.plugins?.LiveUpdate?.appId) {
41
- appId = config.plugins.LiveUpdate.appId;
42
- consola.success(`Using existing app ID from Capacitor configuration: ${appId}`);
43
- }
44
- // Prompt if still not found
45
- if (!appId) {
46
- // Check authorization
47
- if (!authorizationService.hasAuthorizationToken()) {
48
- consola.error('You must be logged in. Please run the `login` command first.');
49
- process.exit(1);
50
- }
51
- // Prompt for organization
52
- const organizations = await organizationsService.findAll();
53
- if (organizations.length === 0) {
54
- consola.error('You must create an organization before setting up Live Updates.');
55
- process.exit(1);
56
- }
57
- // @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
58
- const organizationId = await prompt('Select the organization of your app:', {
59
- type: 'select',
60
- options: organizations.map((org) => ({
61
- label: org.name,
62
- value: org.id,
63
- })),
64
- });
65
- if (!organizationId) {
66
- consola.error('You must select an organization.');
67
- process.exit(1);
68
- }
69
- // Prompt for app
70
- const apps = await appsService.findAll({ organizationId });
71
- if (apps.length === 0) {
72
- consola.error('You must create an app before setting up Live Updates.');
73
- process.exit(1);
74
- }
75
- // @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
76
- appId = await prompt('Select your app:', {
77
- type: 'select',
78
- options: apps.map((app) => ({
79
- label: app.name,
80
- value: app.id,
81
- })),
82
- });
83
- if (!appId) {
84
- consola.error('You must select an app.');
85
- process.exit(1);
86
- }
87
- }
88
- // Update config with app ID
89
- await updateCapacitorConfig(capacitorConfigPath, {
90
- plugins: {
91
- LiveUpdate: {
92
- appId,
93
- },
94
- },
95
- });
96
- consola.success(`Updated Capacitor configuration with app ID: ${appId}`);
97
- // Step 4: Install SDK (Optional)
98
- // @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
99
- const shouldInstall = await prompt('Do you want to install the Live Update SDK?', {
100
- type: 'confirm',
101
- initial: true,
102
- });
103
- if (shouldInstall) {
104
- // @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
105
- const packageManager = await prompt('Select your package manager:', {
106
- type: 'select',
107
- options: [
108
- { label: 'npm', value: 'npm' },
109
- { label: 'yarn', value: 'yarn' },
110
- { label: 'pnpm', value: 'pnpm' },
111
- ],
112
- });
113
- await installPackage('@capawesome/capacitor-live-update', packageManager);
114
- consola.success('Live Update SDK installed successfully.');
115
- }
116
- // Step 5: Set Up Code Signing (Optional)
117
- // Reload config to get latest updates
118
- config = await readCapacitorConfig(capacitorConfigPath);
119
- const hasPublicKey = !!config?.plugins?.LiveUpdate?.publicKey;
120
- if (!hasPublicKey) {
121
- // @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
122
- const shouldSetupSigning = await prompt('Do you want to set up code signing? (Recommended for production, optional for testing)', {
123
- type: 'confirm',
124
- initial: false,
125
- });
126
- if (shouldSetupSigning) {
127
- // Check OpenSSL
128
- const hasOpenssl = await checkOpensslInstalled();
129
- if (!hasOpenssl) {
130
- consola.error('OpenSSL is not installed. Please install OpenSSL to generate key pairs.');
131
- consola.info('Visit https://www.openssl.org for installation instructions.');
132
- process.exit(1);
133
- }
134
- // Define key paths
135
- const privateKeyPath = 'keypair.pem';
136
- const publicKeyPath = 'publickey.crt';
137
- // Check if files exist
138
- const privateKeyExists = await fileExistsAtPath(privateKeyPath);
139
- const publicKeyExists = await fileExistsAtPath(publicKeyPath);
140
- let shouldGenerate = true;
141
- if (privateKeyExists || publicKeyExists) {
142
- // @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
143
- const overwrite = await prompt('Key files already exist. Overwrite them?', {
144
- type: 'confirm',
145
- initial: false,
146
- });
147
- shouldGenerate = overwrite;
148
- }
149
- if (shouldGenerate) {
150
- // Generate keys
151
- consola.start('Generating RSA key pair...');
152
- await generateKeyPair(privateKeyPath, publicKeyPath);
153
- consola.success('Key pair generated successfully.');
154
- // Read public key
155
- const publicKey = await readPublicKey(publicKeyPath);
156
- // Update config
157
- await updateCapacitorConfig(capacitorConfigPath, {
158
- plugins: {
159
- LiveUpdate: {
160
- publicKey,
161
- },
162
- },
163
- });
164
- consola.success('Updated Capacitor configuration with public key.');
165
- // Show important instructions
166
- console.log(); // Blank line
167
- consola.box(`IMPORTANT: Keep your private key safe!\n\n` +
168
- `Private key location: ${privateKeyPath}\n` +
169
- `Public key location: ${publicKeyPath}\n\n` +
170
- `When creating bundles, use:\n` +
171
- `npx @capawesome/cli apps:bundles:create --private-key ${privateKeyPath}`);
172
- }
173
- }
174
- }
175
- // Step 6: Set Up Auto Rollback (Optional)
176
- // Reload config again to get latest updates
177
- config = await readCapacitorConfig(capacitorConfigPath);
178
- const hasReadyTimeout = config?.plugins?.LiveUpdate?.readyTimeout !== undefined;
179
- if (!hasReadyTimeout) {
180
- // @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
181
- const shouldSetupRollback = await prompt('Do you want to set up automatic rollbacks? (Recommended for production, optional for testing)', {
182
- type: 'confirm',
183
- initial: false,
184
- });
185
- if (shouldSetupRollback) {
186
- const timeoutStr = await prompt('Enter ready timeout in milliseconds (recommended: 10000):', {
187
- type: 'text',
188
- initial: '10000',
189
- });
190
- const readyTimeout = parseInt(timeoutStr, 10);
191
- if (isNaN(readyTimeout) || readyTimeout <= 0) {
192
- consola.error('Invalid timeout value. Must be a positive number.');
193
- process.exit(1);
194
- }
195
- // Update config
196
- await updateCapacitorConfig(capacitorConfigPath, {
197
- plugins: {
198
- LiveUpdate: {
199
- readyTimeout,
200
- },
201
- },
202
- });
203
- consola.success(`Updated Capacitor configuration with ready timeout: ${readyTimeout}ms`);
204
- // Show code snippet
205
- console.log(); // Blank line
206
- consola.box(`IMPORTANT: Add this code to your app!\n\n` +
207
- `Call this as soon as possible during app startup:\n\n` +
208
- `import { LiveUpdate } from '@capawesome/capacitor-live-update';\n\n` +
209
- `LiveUpdate.ready();`);
210
- }
211
- }
212
- // Step 7: Final Message
213
- console.log(); // Blank line
214
- consola.success('Live Updates setup completed!');
215
- consola.info('Next steps:');
216
- consola.info('1. Sync your Capacitor project: `npx cap sync`');
217
- consola.info('2. Create your first bundle: `npx @capawesome/cli apps:bundles:create`');
218
- consola.info('3. Download and apply the latest bundle in your app by calling the `sync(...)` method:');
219
- console.log(); // Blank line
220
- const syncCode = `import { LiveUpdate } from '@capawesome/capacitor-live-update';\n\n` +
221
- `const sync = async () => {\n` +
222
- ` // Automatically download the latest update\n` +
223
- ` const { nextBundleId } = await LiveUpdate.sync();\n` +
224
- ` if (nextBundleId) {\n` +
225
- ` // Reload the app to apply the update\n` +
226
- ` await LiveUpdate.reload();\n` +
227
- ` }\n` +
228
- `};`;
229
- consola.box(syncCode);
230
- // Ask if user wants to copy to clipboard
231
- // @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
232
- const shouldCopy = await prompt('Do you want to copy this code to your clipboard?', {
233
- type: 'confirm',
234
- initial: true,
235
- });
236
- if (shouldCopy) {
237
- try {
238
- await copyToClipboard(syncCode);
239
- consola.success('Code copied to clipboard!');
240
- }
241
- catch (error) {
242
- consola.warn('Failed to copy to clipboard. Please copy the code manually.');
243
- }
244
- }
245
- // Show helpful resources
246
- console.log(); // Blank line
247
- consola.log('Learn more:');
248
- consola.log('- Plugin Documentation: https://capawesome.io/plugins/live-update/');
249
- consola.log('- Update Strategies: https://capawesome.io/cloud/live-updates/guides/update-strategies/');
250
- consola.log('- Best Practices: https://capawesome.io/cloud/live-updates/guides/best-practices/');
251
- },
252
- });
@@ -1,123 +0,0 @@
1
- import fs from 'fs';
2
- import { readCapacitorConfig } from './capacitor-config.js';
3
- import { writeFile } from './file.js';
4
- /**
5
- * Deep merge two objects.
6
- */
7
- const deepMerge = (target, source) => {
8
- const result = { ...target };
9
- for (const key in source) {
10
- if (source[key] !== undefined) {
11
- if (typeof source[key] === 'object' && !Array.isArray(source[key]) && source[key] !== null) {
12
- result[key] = deepMerge(result[key] || {}, source[key]);
13
- }
14
- else {
15
- result[key] = source[key];
16
- }
17
- }
18
- }
19
- return result;
20
- };
21
- /**
22
- * Convert a value to a JavaScript object notation string.
23
- */
24
- const toJSObjectString = (value, indent = 0) => {
25
- const indentStr = ' '.repeat(indent);
26
- const nextIndentStr = ' '.repeat(indent + 1);
27
- if (value === null) {
28
- return 'null';
29
- }
30
- if (value === undefined) {
31
- return 'undefined';
32
- }
33
- if (typeof value === 'string') {
34
- // Handle multi-line strings (like public keys)
35
- if (value.includes('\n')) {
36
- // Use template literal for multi-line strings
37
- return `\`${value}\``;
38
- }
39
- return `'${value.replace(/'/g, "\\'")}'`;
40
- }
41
- if (typeof value === 'number' || typeof value === 'boolean') {
42
- return String(value);
43
- }
44
- if (Array.isArray(value)) {
45
- if (value.length === 0) {
46
- return '[]';
47
- }
48
- const items = value.map((item) => `${nextIndentStr}${toJSObjectString(item, indent + 1)}`).join(',\n');
49
- return `[\n${items}\n${indentStr}]`;
50
- }
51
- if (typeof value === 'object') {
52
- const keys = Object.keys(value);
53
- if (keys.length === 0) {
54
- return '{}';
55
- }
56
- const items = keys
57
- .map((key) => {
58
- const val = toJSObjectString(value[key], indent + 1);
59
- return `${nextIndentStr}${key}: ${val}`;
60
- })
61
- .join(',\n');
62
- return `{\n${items}\n${indentStr}}`;
63
- }
64
- return 'undefined';
65
- };
66
- /**
67
- * Write a Capacitor config to a JSON file.
68
- */
69
- export const writeCapacitorConfigJson = async (configPath, config) => {
70
- const jsonContent = JSON.stringify(config, null, 2);
71
- await writeFile(configPath, jsonContent);
72
- };
73
- /**
74
- * Write a Capacitor config to a TypeScript file.
75
- */
76
- export const writeCapacitorConfigTs = async (configPath, config) => {
77
- // Read existing file to preserve reference comments
78
- const existingContent = await fs.promises.readFile(configPath, 'utf-8');
79
- // Extract reference comments
80
- const referenceComments = [];
81
- const lines = existingContent.split('\n');
82
- for (const line of lines) {
83
- if (line.trim().startsWith('///')) {
84
- referenceComments.push(line.trim());
85
- }
86
- }
87
- // Generate TypeScript content
88
- const configObject = toJSObjectString(config, 0);
89
- const tsContent = [
90
- ...referenceComments,
91
- ...(referenceComments.length > 0 ? [''] : []),
92
- "import type { CapacitorConfig } from '@capacitor/cli';",
93
- '',
94
- 'const config: CapacitorConfig = ' + configObject + ';',
95
- '',
96
- 'export default config;',
97
- '',
98
- ].join('\n');
99
- await writeFile(configPath, tsContent);
100
- };
101
- /**
102
- * Update a Capacitor config file with partial updates.
103
- * Performs a deep merge with existing config.
104
- */
105
- export const updateCapacitorConfig = async (configPath, updates) => {
106
- // Read current config
107
- let currentConfig = await readCapacitorConfig(configPath);
108
- if (!currentConfig) {
109
- currentConfig = {};
110
- }
111
- // Merge updates
112
- const mergedConfig = deepMerge(currentConfig, updates);
113
- // Write based on file type
114
- if (configPath.endsWith('.json')) {
115
- await writeCapacitorConfigJson(configPath, mergedConfig);
116
- }
117
- else if (configPath.endsWith('.ts')) {
118
- await writeCapacitorConfigTs(configPath, mergedConfig);
119
- }
120
- else {
121
- throw new Error('Unsupported config file format');
122
- }
123
- };
@@ -1,34 +0,0 @@
1
- import { exec } from 'child_process';
2
- import { promisify } from 'util';
3
- const execAsync = promisify(exec);
4
- /**
5
- * Copy text to the system clipboard.
6
- * Works on macOS, Linux, and Windows.
7
- *
8
- * @param text The text to copy to the clipboard.
9
- */
10
- export const copyToClipboard = async (text) => {
11
- const platform = process.platform;
12
- try {
13
- if (platform === 'darwin') {
14
- // macOS
15
- await execAsync(`echo "${text.replace(/"/g, '\\"')}" | pbcopy`);
16
- }
17
- else if (platform === 'win32') {
18
- // Windows
19
- await execAsync(`echo "${text.replace(/"/g, '\\"')}" | clip`);
20
- }
21
- else {
22
- // Linux - try xclip first, fall back to xsel
23
- try {
24
- await execAsync(`echo "${text.replace(/"/g, '\\"')}" | xclip -selection clipboard`);
25
- }
26
- catch (error) {
27
- await execAsync(`echo "${text.replace(/"/g, '\\"')}" | xsel --clipboard --input`);
28
- }
29
- }
30
- }
31
- catch (error) {
32
- throw new Error('Failed to copy to clipboard. Make sure clipboard utilities are installed.');
33
- }
34
- };
@@ -1,40 +0,0 @@
1
- import { exec } from 'child_process';
2
- import fs from 'fs';
3
- import { promisify } from 'util';
4
- const execAsync = promisify(exec);
5
- /**
6
- * Check if OpenSSL is installed on the system.
7
- *
8
- * @returns True if OpenSSL is installed, false otherwise.
9
- */
10
- export const checkOpensslInstalled = async () => {
11
- try {
12
- await execAsync('openssl version');
13
- return true;
14
- }
15
- catch (error) {
16
- return false;
17
- }
18
- };
19
- /**
20
- * Generate an RSA key pair using OpenSSL.
21
- *
22
- * @param privateKeyPath Path where the private key will be saved.
23
- * @param publicKeyPath Path where the public key will be saved.
24
- */
25
- export const generateKeyPair = async (privateKeyPath, publicKeyPath) => {
26
- // Generate private key
27
- await execAsync(`openssl genrsa -out ${privateKeyPath} 2048`);
28
- // Extract public key from private key
29
- await execAsync(`openssl rsa -in ${privateKeyPath} -pubout -out ${publicKeyPath}`);
30
- };
31
- /**
32
- * Read a public key from a file.
33
- *
34
- * @param publicKeyPath Path to the public key file.
35
- * @returns The public key content as a string.
36
- */
37
- export const readPublicKey = async (publicKeyPath) => {
38
- const content = await fs.promises.readFile(publicKeyPath, 'utf-8');
39
- return content.trim();
40
- };
@@ -1,38 +0,0 @@
1
- import { exec } from 'child_process';
2
- import consola from 'consola';
3
- import { promisify } from 'util';
4
- const execAsync = promisify(exec);
5
- /**
6
- * Install a package using the specified package manager.
7
- *
8
- * @param packageName The name of the package to install.
9
- * @param packageManager The package manager to use (npm, yarn, or pnpm).
10
- */
11
- export const installPackage = async (packageName, packageManager) => {
12
- const commands = {
13
- npm: `npm install ${packageName}`,
14
- yarn: `yarn add ${packageName}`,
15
- pnpm: `pnpm add ${packageName}`,
16
- };
17
- const command = commands[packageManager];
18
- consola.start(`Installing ${packageName} using ${packageManager}...`);
19
- try {
20
- const { stdout, stderr } = await execAsync(command);
21
- if (stdout) {
22
- console.log(stdout);
23
- }
24
- if (stderr) {
25
- console.error(stderr);
26
- }
27
- }
28
- catch (error) {
29
- consola.error(`Failed to install ${packageName}.`);
30
- if (error.stdout) {
31
- console.log(error.stdout);
32
- }
33
- if (error.stderr) {
34
- console.error(error.stderr);
35
- }
36
- process.exit(1);
37
- }
38
- };