@capgo/cli 5.0.0-alpha.7 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/README.md +197 -41
  2. package/dist/index.js +329 -95820
  3. package/dist/package.json +83 -0
  4. package/package.json +48 -62
  5. package/.eslintignore +0 -4
  6. package/.github/FUNDING.yml +0 -1
  7. package/.github/workflows/build.yml +0 -46
  8. package/.github/workflows/bump_version.yml +0 -56
  9. package/.github/workflows/test.yml +0 -30
  10. package/.prettierignore +0 -6
  11. package/.vscode/launch.json +0 -23
  12. package/.vscode/settings.json +0 -5
  13. package/.vscode/tasks.json +0 -42
  14. package/CHANGELOG.md +0 -2861
  15. package/build.mjs +0 -23
  16. package/bun.lockb +0 -0
  17. package/capacitor.config.ts +0 -33
  18. package/crypto_explained.png +0 -0
  19. package/eslint.config.js +0 -3
  20. package/renovate.json +0 -23
  21. package/src/api/app.ts +0 -75
  22. package/src/api/channels.ts +0 -140
  23. package/src/api/crypto.ts +0 -121
  24. package/src/api/devices_override.ts +0 -41
  25. package/src/api/update.ts +0 -12
  26. package/src/api/versions.ts +0 -101
  27. package/src/app/add.ts +0 -191
  28. package/src/app/debug.ts +0 -220
  29. package/src/app/delete.ts +0 -106
  30. package/src/app/info.ts +0 -87
  31. package/src/app/list.ts +0 -67
  32. package/src/app/set.ts +0 -94
  33. package/src/bundle/check.ts +0 -42
  34. package/src/bundle/cleanup.ts +0 -127
  35. package/src/bundle/compatibility.ts +0 -70
  36. package/src/bundle/decrypt.ts +0 -65
  37. package/src/bundle/delete.ts +0 -53
  38. package/src/bundle/encrypt.ts +0 -69
  39. package/src/bundle/list.ts +0 -43
  40. package/src/bundle/unlink.ts +0 -86
  41. package/src/bundle/upload.ts +0 -516
  42. package/src/bundle/zip.ts +0 -139
  43. package/src/channel/add.ts +0 -73
  44. package/src/channel/currentBundle.ts +0 -72
  45. package/src/channel/delete.ts +0 -51
  46. package/src/channel/list.ts +0 -49
  47. package/src/channel/set.ts +0 -174
  48. package/src/index.ts +0 -290
  49. package/src/init.ts +0 -301
  50. package/src/key.ts +0 -158
  51. package/src/login.ts +0 -66
  52. package/src/types/capacitor__cli.d.ts +0 -6
  53. package/src/types/supabase.types.ts +0 -2471
  54. package/src/utils.ts +0 -738
  55. package/test/chunk_convert.ts +0 -28
  56. package/test/data.ts +0 -18769
  57. package/test/test_headers_rls.ts +0 -24
  58. package/test/test_semver.ts +0 -13
  59. package/tsconfig.json +0 -39
@@ -1,516 +0,0 @@
1
- import { randomUUID } from 'node:crypto'
2
- import { existsSync, readFileSync } from 'node:fs'
3
- import process from 'node:process'
4
- import type { Buffer } from 'node:buffer'
5
- import AdmZip from 'adm-zip'
6
- import { program } from 'commander'
7
- import * as p from '@clack/prompts'
8
- import { checksum as getChecksum } from '@tomasklaen/checksum'
9
- import ciDetect from 'ci-info'
10
- import ky from 'ky'
11
- import {
12
- PutObjectCommand,
13
- S3Client,
14
- } from '@aws-sdk/client-s3'
15
- import { checkLatest } from '../api/update'
16
- import { checkAppExistsAndHasPermissionOrgErr } from '../api/app'
17
- import { encryptSource } from '../api/crypto'
18
- import type {
19
- OptionsBase,
20
- } from '../utils'
21
- import {
22
- EMPTY_UUID,
23
- OrganizationPerm,
24
- baseKey,
25
- checKOldEncryption,
26
- checkCompatibility,
27
- checkPlanValid,
28
- convertAppName,
29
- createSupabaseClient,
30
- findSavedKey,
31
- formatError,
32
- getAppOwner,
33
- getConfig,
34
- getLocalConfig,
35
- getLocalDepenencies,
36
- getOrganizationId,
37
- hasOrganizationPerm,
38
- regexSemver,
39
- requireUpdateMetadata,
40
- updateOrCreateChannel,
41
- updateOrCreateVersion,
42
- uploadUrl,
43
- useLogSnag,
44
- verifyUser,
45
- } from '../utils'
46
- import { checkIndexPosition, searchInDirectory } from './check'
47
-
48
- const alertMb = 20
49
-
50
- interface Options extends OptionsBase {
51
- bundle?: string
52
- path?: string
53
- channel?: string
54
- displayIvSession?: boolean
55
- external?: string
56
- key?: boolean | string
57
- keyData?: string
58
- ivSessionKey?: string
59
- s3Region?: string
60
- s3Apikey?: string
61
- s3Apisecret?: string
62
- s3BucketName?: string
63
- bundleUrl?: boolean
64
- codeCheck?: boolean
65
- minUpdateVersion?: string
66
- autoMinUpdateVersion?: boolean
67
- ignoreMetadataCheck?: boolean
68
- }
69
-
70
- export async function uploadBundle(appid: string, options: Options, shouldExit = true) {
71
- p.intro(`Uploading`)
72
- await checkLatest()
73
- let { bundle, path, channel } = options
74
- const { external, key, displayIvSession, autoMinUpdateVersion, ignoreMetadataCheck } = options
75
- let { minUpdateVersion } = options
76
- const { s3Region, s3Apikey, s3Apisecret, s3BucketName } = options
77
- let useS3 = false
78
- let s3Client
79
- if (s3Region && s3Apikey && s3Apisecret && s3BucketName) {
80
- p.log.info('Uploading to S3')
81
- useS3 = true
82
- s3Client = new S3Client({
83
- region: s3Region,
84
- credentials: {
85
- accessKeyId: s3Apikey,
86
- secretAccessKey: s3Apisecret,
87
- },
88
- })
89
- }
90
-
91
- options.apikey = options.apikey || findSavedKey()
92
- const snag = useLogSnag()
93
-
94
- channel = channel || 'dev'
95
-
96
- const config = await getConfig()
97
- const localS3: boolean = (config.app.extConfig.plugins && config.app.extConfig.plugins.CapacitorUpdater
98
- && config.app.extConfig.plugins.CapacitorUpdater.localS3) === true
99
-
100
- const checkNotifyAppReady = options.codeCheck
101
- appid = appid || config?.app?.appId
102
- // create bundle name format : 1.0.0-beta.x where x is a uuid
103
- const uuid = randomUUID().split('-')[0]
104
- bundle = bundle || config?.app?.extConfig?.plugins?.CapacitorUpdater?.version || config?.app?.package?.version || `0.0.1-beta.${uuid}`
105
- // check if bundle is valid
106
- if (!regexSemver.test(bundle)) {
107
- p.log.error(`Your bundle name ${bundle}, is not valid it should follow semver convention : https://semver.org/`)
108
- program.error('')
109
- }
110
- path = path || config?.app?.webDir
111
- if (!options.apikey) {
112
- p.log.error(`Missing API key, you need to provide a API key to upload your bundle`)
113
- program.error('')
114
- }
115
- if (!appid || !bundle || !path) {
116
- p.log.error('Missing argument, you need to provide a appid and a bundle and a path, or be in a capacitor project')
117
- program.error('')
118
- }
119
- // if one S3 variable is set, check that all are set
120
- if (s3BucketName || s3Region || s3Apikey || s3Apisecret) {
121
- if (!s3BucketName || !s3Region || !s3Apikey || !s3Apisecret) {
122
- p.log.error('Missing argument, for S3 upload you need to provide a bucket name, region, API key, and API secret')
123
- program.error('')
124
- }
125
- }
126
- // check if path exist
127
- if (!existsSync(path)) {
128
- p.log.error(`Path ${path} does not exist, build your app first, or provide a valid path`)
129
- program.error('')
130
- }
131
-
132
- if (typeof checkNotifyAppReady === 'undefined' || checkNotifyAppReady) {
133
- const isPluginConfigured = searchInDirectory(path, 'notifyAppReady')
134
- if (!isPluginConfigured) {
135
- p.log.error(`notifyAppReady() is missing in the source code. see: https://capgo.app/docs/plugin/api/#notifyappready`)
136
- program.error('')
137
- }
138
- const foundIndex = checkIndexPosition(path)
139
- if (!foundIndex) {
140
- p.log.error(`index.html is missing in the root folder or in the only folder in the root folder`)
141
- program.error('')
142
- }
143
- }
144
-
145
- p.log.info(`Upload ${appid}@${bundle} started from path "${path}" to Capgo cloud`)
146
-
147
- const localConfig = await getLocalConfig()
148
- const supabase = await createSupabaseClient(options.apikey)
149
- const userId = await verifyUser(supabase, options.apikey, ['write', 'all', 'upload'])
150
- // Check we have app access to this appId
151
- // await checkAppExistsAndHasPermissionErr(supabase, options.apikey, appid);
152
-
153
- const permissions = await checkAppExistsAndHasPermissionOrgErr(supabase, options.apikey, appid, OrganizationPerm.upload)
154
-
155
- // Now if it does exist we will fetch the org id
156
- const orgId = await getOrganizationId(supabase, appid)
157
- await checkPlanValid(supabase, orgId, options.apikey, appid, true)
158
-
159
- const updateMetadataRequired = await requireUpdateMetadata(supabase, channel, appid)
160
-
161
- // Check compatibility here
162
- const { data: channelData, error: channelError } = await supabase
163
- .from('channels')
164
- .select('version ( minUpdateVersion, native_packages )')
165
- .eq('name', channel)
166
- .eq('app_id', appid)
167
- .single()
168
-
169
- // eslint-disable-next-line no-undef-init
170
- let localDependencies: Awaited<ReturnType<typeof getLocalDepenencies>> | undefined = undefined
171
- let finalCompatibility: Awaited<ReturnType<typeof checkCompatibility>>['finalCompatibility']
172
-
173
- // We only check compatibility IF the channel exists
174
- if (!channelError && channelData && channelData.version && (channelData.version as any).native_packages && !ignoreMetadataCheck) {
175
- const spinner = p.spinner()
176
- spinner.start(`Checking bundle compatibility with channel ${channel}`)
177
- const {
178
- finalCompatibility: finalCompatibilityWithChannel,
179
- localDependencies: localDependenciesWithChannel,
180
- } = await checkCompatibility(supabase, appid, channel)
181
-
182
- finalCompatibility = finalCompatibilityWithChannel
183
- localDependencies = localDependenciesWithChannel
184
-
185
- if (finalCompatibility.find(x => x.localVersion !== x.remoteVersion)) {
186
- p.log.error(`Your bundle is not compatible with the channel ${channel}`)
187
- p.log.warn(`You can check compatibility with "npx @capgo/cli bundle compatibility"`)
188
-
189
- if (autoMinUpdateVersion) {
190
- minUpdateVersion = bundle
191
- p.log.info(`Auto set min-update-version to ${minUpdateVersion}`)
192
- }
193
- }
194
- else if (autoMinUpdateVersion) {
195
- try {
196
- const { minUpdateVersion: lastMinUpdateVersion } = channelData.version as any
197
- if (!lastMinUpdateVersion || !regexSemver.test(lastMinUpdateVersion)) {
198
- p.log.error('Invalid remote min update version, skipping auto setting compatibility')
199
- program.error('')
200
- }
201
-
202
- minUpdateVersion = lastMinUpdateVersion
203
- p.log.info(`Auto set min-update-version to ${minUpdateVersion}`)
204
- }
205
- catch (error) {
206
- p.log.error(`Cannot auto set compatibility, invalid data ${channelData}`)
207
- program.error('')
208
- }
209
- }
210
- spinner.stop(`Bundle compatible with ${channel} channel`)
211
- }
212
- else if (!ignoreMetadataCheck) {
213
- p.log.warn(`Channel ${channel} is new or it's your first upload with compatibility check, it will be ignored this time`)
214
- localDependencies = await getLocalDepenencies()
215
-
216
- if (autoMinUpdateVersion) {
217
- minUpdateVersion = bundle
218
- p.log.info(`Auto set min-update-version to ${minUpdateVersion}`)
219
- }
220
- }
221
-
222
- if (updateMetadataRequired && !minUpdateVersion && !ignoreMetadataCheck) {
223
- p.log.error(`You need to provide a min-update-version to upload a bundle to this channel`)
224
- program.error('')
225
- }
226
-
227
- if (minUpdateVersion) {
228
- if (!regexSemver.test(minUpdateVersion)) {
229
- p.log.error(`Your minimal version update ${minUpdateVersion}, is not valid it should follow semver convention : https://semver.org/`)
230
- program.error('')
231
- }
232
- }
233
-
234
- const { data: isTrial, error: isTrialsError } = await supabase
235
- .rpc('is_trial_org', { orgid: orgId })
236
- .single()
237
- if ((isTrial && isTrial > 0) || isTrialsError) {
238
- // TODO: Come back to this to fix for orgs v3
239
- p.log.warn(`WARNING !!\nTrial expires in ${isTrial} days`)
240
- p.log.warn(`Upgrade here: ${localConfig.hostWeb}/dashboard/settings/plans?oid=${orgId}`)
241
- }
242
-
243
- // check if app already exist
244
- const { data: appVersion, error: appVersionError } = await supabase
245
- .rpc('exist_app_versions', { appid, apikey: options.apikey, name_version: bundle })
246
- .single()
247
-
248
- if (appVersion || appVersionError) {
249
- p.log.error(`Version already exists ${formatError(appVersionError)}`)
250
- program.error('')
251
- }
252
-
253
- let sessionKey
254
- let checksum = ''
255
- let zipped: Buffer | null = null
256
- if (!external && useS3 === false) {
257
- const zip = new AdmZip()
258
- zip.addLocalFolder(path)
259
- zipped = zip.toBuffer()
260
- const s = p.spinner()
261
- s.start(`Calculating checksum`)
262
- checksum = await getChecksum(zipped, 'crc32')
263
- s.stop(`Checksum: ${checksum}`)
264
- if (key === false) {
265
- p.log.info(`Encryption ignored`)
266
- }
267
- else if (key || existsSync(baseKey)) {
268
- await checKOldEncryption()
269
- const privateKey = typeof key === 'string' ? key : baseKey
270
- let keyData = options.keyData || ''
271
- // check if privateKey exist
272
- if (!keyData && !existsSync(privateKey)) {
273
- p.log.error(`Cannot find private key ${privateKey}`)
274
- if (ciDetect.isCI)
275
- program.error('')
276
-
277
- const res = await p.confirm({ message: 'Do you want to use our private key ?' })
278
- if (!res) {
279
- p.log.error(`Error: Missing public key`)
280
- program.error('')
281
- }
282
- keyData = localConfig.signKey || ''
283
- }
284
- await snag.track({
285
- channel: 'app',
286
- event: 'App encryption',
287
- icon: '🔑',
288
- user_id: userId,
289
- tags: {
290
- 'app-id': appid,
291
- },
292
- notify: false,
293
- }).catch()
294
- // open with fs privateKey path
295
- if (!keyData) {
296
- const keyFile = readFileSync(privateKey)
297
- keyData = keyFile.toString()
298
- }
299
- // encrypt
300
- if (keyData && !keyData.startsWith('-----BEGIN RSA PRIVATE KEY-----')) {
301
- p.log.error(`the private key provided is not a valid RSA Private key`)
302
- program.error('')
303
- }
304
- p.log.info(`Encrypting your bundle`)
305
- const res = encryptSource(zipped, keyData)
306
- sessionKey = res.ivSessionKey
307
- if (displayIvSession) {
308
- p.log.info(`Your Iv Session key is ${sessionKey},
309
- keep it safe, you will need it to decrypt your bundle.
310
- It will be also visible in your dashboard\n`)
311
- }
312
- zipped = res.encryptedData
313
- }
314
- const mbSize = Math.floor(zipped.byteLength / 1024 / 1024)
315
- if (mbSize > alertMb) {
316
- p.log.warn(`WARNING !!\nThe app size is ${mbSize} Mb, this may take a while to download for users\n`)
317
- p.log.info(`Learn how to optimize your assets https://capgo.app/blog/optimise-your-images-for-updates/\n`)
318
- await snag.track({
319
- channel: 'app-error',
320
- event: 'App Too Large',
321
- icon: '🚛',
322
- user_id: userId,
323
- tags: {
324
- 'app-id': appid,
325
- },
326
- notify: false,
327
- }).catch()
328
- }
329
- }
330
- else if (external && !external.startsWith('https://')) {
331
- p.log.error(`External link should should start with "https://" current is "${external}"`)
332
- program.error('')
333
- }
334
- else {
335
- if (useS3) {
336
- const zip = new AdmZip()
337
- zip.addLocalFolder(path)
338
- zipped = zip.toBuffer()
339
- const s = p.spinner()
340
- s.start(`Calculating checksum`)
341
- checksum = await getChecksum(zipped, 'crc32')
342
- s.stop(`Checksum: ${checksum}`)
343
- }
344
- await snag.track({
345
- channel: 'app',
346
- event: 'App external',
347
- icon: '📤',
348
- user_id: userId,
349
- tags: {
350
- 'app-id': appid,
351
- },
352
- notify: false,
353
- }).catch()
354
- sessionKey = options.ivSessionKey
355
- }
356
-
357
- const hashedLocalDependencies = localDependencies
358
- ? new Map(localDependencies
359
- .filter(a => !!a.native && a.native !== undefined)
360
- .map(a => [a.name, a]))
361
- : new Map()
362
-
363
- const nativePackages = (hashedLocalDependencies.size > 0 || !options.ignoreMetadataCheck) ? Array.from(hashedLocalDependencies, ([name, value]) => ({ name, version: value.version })) : undefined
364
-
365
- const appOwner = await getAppOwner(supabase, appid)
366
-
367
- const versionData = {
368
- // bucket_id: external ? undefined : fileName,
369
- name: bundle,
370
- app_id: appid,
371
- session_key: sessionKey,
372
- external_url: external,
373
- storage_provider: external ? 'external' : 'r2-direct',
374
- minUpdateVersion,
375
- native_packages: nativePackages,
376
- owner_org: EMPTY_UUID,
377
- user_id: userId,
378
- checksum,
379
- }
380
- const { error: dbError } = await updateOrCreateVersion(supabase, versionData)
381
- if (dbError) {
382
- p.log.error(`Cannot add bundle ${formatError(dbError)}`)
383
- program.error('')
384
- }
385
- if (!external && zipped) {
386
- const spinner = p.spinner()
387
- spinner.start(`Uploading Bundle`)
388
-
389
- const url = await uploadUrl(supabase, appid, bundle)
390
- if (!url) {
391
- p.log.error(`Cannot get upload url`)
392
- program.error('')
393
- }
394
- try {
395
- await ky.put(url, {
396
- timeout: 60000,
397
- retry: 5,
398
- body: zipped,
399
- headers: (!localS3
400
- ? {
401
- 'Content-Type': 'application/octet-stream',
402
- 'Cache-Control': 'public, max-age=456789, immutable',
403
- 'x-amz-meta-crc32': checksum,
404
- }
405
- : undefined),
406
- })
407
- }
408
- catch (errorUpload) {
409
- p.log.error(`Cannot upload bundle ${formatError(errorUpload)}`)
410
- program.error('')
411
- }
412
- versionData.storage_provider = 'r2'
413
- const { error: dbError2 } = await updateOrCreateVersion(supabase, versionData)
414
- if (dbError2) {
415
- p.log.error(`Cannot update bundle ${formatError(dbError2)}`)
416
- program.error('')
417
- }
418
- spinner.stop('Bundle Uploaded 💪')
419
- }
420
- else if (useS3 && zipped) {
421
- const spinner = p.spinner()
422
- spinner.start(`Uploading Bundle`)
423
-
424
- const fileName = `${appid}-${bundle}`
425
- const encodeFileName = encodeURIComponent(fileName)
426
- const command: PutObjectCommand = new PutObjectCommand({
427
- Bucket: s3BucketName,
428
- Key: fileName,
429
- Body: zipped,
430
- })
431
-
432
- const response = await s3Client.send(command)
433
- if (response.$metadata.httpStatusCode !== 200) {
434
- p.log.error(`Cannot upload to S3`)
435
- program.error('')
436
- }
437
-
438
- versionData.storage_provider = 'external'
439
- versionData.external_url = `https://${s3BucketName}.s3.amazonaws.com/${encodeFileName}`
440
- const { error: dbError2 } = await updateOrCreateVersion(supabase, versionData)
441
- if (dbError2) {
442
- p.log.error(`Cannot update bundle ${formatError(dbError2)}`)
443
- program.error('')
444
- }
445
- spinner.stop('Bundle Uploaded 💪')
446
- }
447
- const { data: versionId } = await supabase
448
- .rpc('get_app_versions', { apikey: options.apikey, name_version: bundle, appid })
449
- .single()
450
-
451
- if (versionId && hasOrganizationPerm(permissions, OrganizationPerm.write)) {
452
- const { error: dbError3, data } = await updateOrCreateChannel(supabase, {
453
- name: channel,
454
- app_id: appid,
455
- created_by: appOwner,
456
- version: versionId,
457
- owner_org: EMPTY_UUID,
458
- })
459
- if (dbError3) {
460
- p.log.error(`Cannot set channel, the upload key is not allowed to do that, use the "all" for this. ${formatError(dbError3)}`)
461
- program.error('')
462
- }
463
- const appidWeb = convertAppName(appid)
464
- const bundleUrl = `${localConfig.hostWeb}/app/p/${appidWeb}/channel/${data.id}`
465
- if (data?.public)
466
- p.log.info('Your update is now available in your public channel 🎉')
467
- else if (data?.id)
468
- p.log.info(`Link device to this bundle to try it: ${bundleUrl}`)
469
-
470
- if (options.bundleUrl)
471
- p.log.info(`Bundle url: ${bundleUrl}`)
472
- }
473
- else if (!versionId) {
474
- p.log.warn('Cannot set bundle with upload key, use key with more rights for that')
475
- program.error('')
476
- }
477
- else if (!hasOrganizationPerm(permissions, OrganizationPerm.write)) {
478
- p.log.warn('Cannot set channel as a upload organization member')
479
- }
480
- await snag.track({
481
- channel: 'app',
482
- event: 'App Uploaded',
483
- icon: '⏫',
484
- user_id: userId,
485
- tags: {
486
- 'app-id': appid,
487
- },
488
- notify: false,
489
- }).catch()
490
- if (shouldExit) {
491
- p.outro('Time to share your update to the world 🌍')
492
- process.exit()
493
- }
494
- return true
495
- }
496
-
497
- export async function uploadCommand(apikey: string, options: Options) {
498
- try {
499
- await uploadBundle(apikey, options, true)
500
- }
501
- catch (error) {
502
- p.log.error(formatError(error))
503
- program.error('')
504
- }
505
- }
506
-
507
- export async function uploadDeprecatedCommand(apikey: string, options: Options) {
508
- p.log.warn('⚠️ This command is deprecated, use "npx @capgo/cli bundle upload" instead ⚠️')
509
- try {
510
- await uploadBundle(apikey, options, true)
511
- }
512
- catch (error) {
513
- p.log.error(formatError(error))
514
- program.error('')
515
- }
516
- }
package/src/bundle/zip.ts DELETED
@@ -1,139 +0,0 @@
1
- import { randomUUID } from 'node:crypto'
2
- import { writeFileSync } from 'node:fs'
3
- import process from 'node:process'
4
- import AdmZip from 'adm-zip'
5
- import { program } from 'commander'
6
- import * as p from '@clack/prompts'
7
- import { checksum as getChecksum } from '@tomasklaen/checksum'
8
- import { checkLatest } from '../api/update'
9
- import type {
10
- OptionsBase,
11
- } from '../utils'
12
- import {
13
- formatError,
14
- getConfig,
15
- regexSemver,
16
- useLogSnag,
17
- } from '../utils'
18
- import { checkIndexPosition, searchInDirectory } from './check'
19
-
20
- const alertMb = 20
21
-
22
- interface Options extends OptionsBase {
23
- bundle?: string
24
- path?: string
25
- codeCheck?: boolean
26
- name?: string
27
- json?: boolean
28
- }
29
-
30
- export async function zipBundle(appId: string, options: Options) {
31
- let { bundle, path } = options
32
- const { json } = options
33
- const snag = useLogSnag()
34
- if (!json)
35
- await checkLatest()
36
-
37
- const config = await getConfig()
38
- appId = appId || config?.app?.appId
39
- // create bundle name format : 1.0.0-beta.x where x is a uuid
40
- const uuid = randomUUID().split('-')[0]
41
- bundle = bundle || config?.app?.package?.version || `0.0.1-beta.${uuid}`
42
- if (!json)
43
- p.intro(`Zipping ${appId}@${bundle}`)
44
- // check if bundle is valid
45
- if (!regexSemver.test(bundle)) {
46
- if (!json)
47
- p.log.error(`Your bundle name ${bundle}, is not valid it should follow semver convention : https://semver.org/`)
48
- else
49
- console.error(formatError({ error: 'invalid_semver' }))
50
- program.error('')
51
- }
52
- path = path || config?.app?.webDir
53
- if (!appId || !bundle || !path) {
54
- if (!json)
55
- p.log.error('Missing argument, you need to provide a appId and a bundle and a path, or be in a capacitor project')
56
- else
57
- console.error(formatError({ error: 'missing_argument' }))
58
- program.error('')
59
- }
60
- if (!json)
61
- p.log.info(`Started from path "${path}"`)
62
- const checkNotifyAppReady = options.codeCheck
63
- if (typeof checkNotifyAppReady === 'undefined' || checkNotifyAppReady) {
64
- const isPluginConfigured = searchInDirectory(path, 'notifyAppReady')
65
- if (!isPluginConfigured) {
66
- if (!json)
67
- p.log.error(`notifyAppReady() is missing in the source code. see: https://capgo.app/docs/plugin/api/#notifyappready`)
68
- else
69
- console.error(formatError({ error: 'notifyAppReady_not_in_source_code' }))
70
- program.error('')
71
- }
72
- const foundIndex = checkIndexPosition(path)
73
- if (!foundIndex) {
74
- if (!json)
75
- p.log.error(`index.html is missing in the root folder or in the only folder in the root folder`)
76
- else
77
- console.error(formatError({ error: 'index_html_not_found' }))
78
- program.error('')
79
- }
80
- }
81
- const zip = new AdmZip()
82
- zip.addLocalFolder(path)
83
- const zipped = zip.toBuffer()
84
- if (!json)
85
- p.log.info(`Zipped ${zipped.byteLength} bytes`)
86
- const s = p.spinner()
87
- if (!json)
88
- s.start(`Calculating checksum`)
89
- const checksum = await getChecksum(zipped, 'crc32')
90
- if (!json)
91
- s.stop(`Checksum: ${checksum}`)
92
- const mbSize = Math.floor(zipped.byteLength / 1024 / 1024)
93
- // We do not issue this warning for json
94
- if (mbSize > alertMb && !json) {
95
- p.log.warn(`WARNING !!\nThe app size is ${mbSize} Mb, this may take a while to download for users\n`)
96
- p.log.warn(`Learn how to optimize your assets https://capgo.app/blog/optimise-your-images-for-updates/\n`)
97
- await snag.track({
98
- channel: 'app-error',
99
- event: 'App Too Large',
100
- icon: '🚛',
101
- tags: {
102
- 'app-id': appId,
103
- },
104
- notify: false,
105
- }).catch()
106
- }
107
- const s2 = p.spinner()
108
- const name = options.name || `${appId}_${bundle}.zip`
109
- if (!json)
110
- s2.start(`Saving to ${name}`)
111
- writeFileSync(name, zipped)
112
- if (!json)
113
- s2.stop(`Saved to ${name}`)
114
-
115
- await snag.track({
116
- channel: 'app',
117
- event: 'App zip',
118
- icon: '⏫',
119
- tags: {
120
- 'app-id': appId,
121
- },
122
- notify: false,
123
- }).catch()
124
-
125
- if (!json)
126
- p.outro(`Done ✅`)
127
-
128
- if (json) {
129
- const output = {
130
- bundle,
131
- filename: name,
132
- checksum,
133
- }
134
- // Keep the console log and stringify for user who parse the output
135
- // eslint-disable-next-line no-console
136
- console.log(JSON.stringify(output, null, 2))
137
- }
138
- process.exit()
139
- }