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