@capgo/cli 4.12.14 → 4.13.3

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