@capgo/cli 5.0.0-alpha.3 → 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 -37
  2. package/dist/index.js +327 -65172
  3. package/dist/package.json +83 -0
  4. package/package.json +48 -61
  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 -2727
  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 -142
  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 -98
  27. package/src/app/add.ts +0 -154
  28. package/src/app/debug.ts +0 -214
  29. package/src/app/delete.ts +0 -68
  30. package/src/app/info.ts +0 -87
  31. package/src/app/list.ts +0 -63
  32. package/src/app/set.ts +0 -94
  33. package/src/bundle/check.ts +0 -42
  34. package/src/bundle/cleanup.ts +0 -128
  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 -80
  41. package/src/bundle/upload.ts +0 -434
  42. package/src/bundle/zip.ts +0 -137
  43. package/src/channel/add.ts +0 -73
  44. package/src/channel/currentBundle.ts +0 -73
  45. package/src/channel/delete.ts +0 -51
  46. package/src/channel/list.ts +0 -49
  47. package/src/channel/set.ts +0 -171
  48. package/src/index.ts +0 -285
  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 -2065
  54. package/src/utils.ts +0 -719
  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
package/src/utils.ts DELETED
@@ -1,719 +0,0 @@
1
- import { existsSync, readFileSync, readdirSync } from 'node:fs'
2
- import { homedir } from 'node:os'
3
- import { resolve } from 'node:path'
4
- import process from 'node:process'
5
- import { loadConfig } from '@capacitor/cli/dist/config'
6
- import { program } from 'commander'
7
- import type { SupabaseClient } from '@supabase/supabase-js'
8
- import { createClient } from '@supabase/supabase-js'
9
- import prettyjson from 'prettyjson'
10
- import { LogSnag } from 'logsnag'
11
- import * as p from '@clack/prompts'
12
- import ky from 'ky'
13
- import { promiseFiles } from 'node-dir'
14
- import type { Database } from './types/supabase.types'
15
-
16
- export const baseKey = '.capgo_key'
17
- export const baseKeyPub = `${baseKey}.pub`
18
- export const defaultHost = 'https://capgo.app'
19
- export const defaultApiHost = 'https://api.capgo.app'
20
- export const defaultHostWeb = 'https://web.capgo.app'
21
-
22
- export const regexSemver = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/
23
- export const formatError = (error: any) => error ? `\n${prettyjson.render(error)}` : ''
24
-
25
- export interface OptionsBase {
26
- apikey: string
27
- }
28
-
29
- export function wait(ms: number) {
30
- return new Promise((resolve) => {
31
- setTimeout(resolve, ms)
32
- })
33
- }
34
-
35
- export async function getConfig() {
36
- let config: Config
37
- try {
38
- config = await loadConfig()
39
- }
40
- catch (err) {
41
- p.log.error('No capacitor config file found, run `cap init` first')
42
- program.error('')
43
- }
44
- return config
45
- }
46
-
47
- export async function getLocalConfig() {
48
- try {
49
- const config: Config = await getConfig()
50
- const capConfig: Partial<CapgoConfig> = {
51
- host: (config?.app?.extConfig?.plugins?.CapacitorUpdater?.localHost || defaultHost) as string,
52
- hostWeb: (config?.app?.extConfig?.plugins?.CapacitorUpdater?.localWebHost || defaultHostWeb) as string,
53
- }
54
-
55
- if (config?.app?.extConfig?.plugins?.CapacitorUpdater?.localSupa && config?.app?.extConfig?.plugins?.CapacitorUpdater?.localSupaAnon) {
56
- p.log.info('Using custom supabase instance from capacitor.config.json')
57
- capConfig.supaKey = config?.app?.extConfig?.plugins?.CapacitorUpdater?.localSupaAnon
58
- capConfig.supaHost = config?.app?.extConfig?.plugins?.CapacitorUpdater?.localSupa
59
- }
60
- return capConfig
61
- }
62
- catch (error) {
63
- return {
64
- host: defaultHost,
65
- hostWeb: defaultHostWeb,
66
- }
67
- }
68
- }
69
-
70
- const nativeFileRegex = /([A-Za-z0-9]+)\.(java|swift|kt|scala)$/
71
-
72
- interface CapgoConfig {
73
- supaHost: string
74
- supaKey: string
75
- host: string
76
- hostWeb: string
77
- signKey: string
78
- }
79
- export async function getRemoteConfig() {
80
- // call host + /api/get_config and parse the result as json using axios
81
- const localConfig = await getLocalConfig()
82
- return ky
83
- .get(`${defaultApiHost}/private/config`)
84
- .then(res => res.json<CapgoConfig>())
85
- .then(data => ({ ...data, ...localConfig } as CapgoConfig))
86
- .catch(() => {
87
- p.log.info(`Local config ${formatError(localConfig)}`)
88
- return localConfig
89
- })
90
- }
91
-
92
- export async function createSupabaseClient(apikey: string) {
93
- const config = await getRemoteConfig()
94
- if (!config.supaHost || !config.supaKey) {
95
- p.log.error('Cannot connect to server please try again later')
96
- program.error('')
97
- }
98
- return createClient<Database>(config.supaHost, config.supaKey, {
99
- auth: {
100
- persistSession: false,
101
- },
102
- global: {
103
- headers: {
104
- capgkey: apikey,
105
- },
106
- },
107
- })
108
- }
109
-
110
- export async function checkKey(supabase: SupabaseClient<Database>, apikey: string, keymode: Database['public']['Enums']['key_mode'][]) {
111
- const { data: apiAccess } = await supabase
112
- .rpc('is_allowed_capgkey', { apikey, keymode })
113
- .single()
114
-
115
- if (!apiAccess) {
116
- p.log.error(`Invalid API key or insufficient permissions.`)
117
- // create a string from keymode array with comma and space and "or" for the last one
118
- const keymodeStr = keymode.map((k, i) => {
119
- if (i === keymode.length - 1)
120
- return `or ${k}`
121
-
122
- return `${k}, `
123
- }).join('')
124
- p.log.error(`Your key should be: ${keymodeStr} mode.`)
125
- program.error('')
126
- }
127
- }
128
-
129
- export async function isGoodPlan(supabase: SupabaseClient<Database>, userId: string): Promise<boolean> {
130
- const { data } = await supabase
131
- .rpc('is_good_plan_v5', { userid: userId })
132
- .single()
133
- return data || false
134
- }
135
-
136
- export async function isPaying(supabase: SupabaseClient<Database>, userId: string): Promise<boolean> {
137
- const { data } = await supabase
138
- .rpc('is_paying', { userid: userId })
139
- .single()
140
- return data || false
141
- }
142
-
143
- export async function isTrial(supabase: SupabaseClient<Database>, userId: string): Promise<number> {
144
- const { data } = await supabase
145
- .rpc('is_trial', { userid: userId })
146
- .single()
147
- return data || 0
148
- }
149
-
150
- export async function isAllowedAction(supabase: SupabaseClient<Database>, userId: string): Promise<boolean> {
151
- const { data } = await supabase
152
- .rpc('is_allowed_action_user', { userid: userId })
153
- .single()
154
- return !!data
155
- }
156
-
157
- export async function isAllowedActionAppIdApiKey(supabase: SupabaseClient<Database>, appId: string, apikey: string): Promise<boolean> {
158
- const { data } = await supabase
159
- .rpc('is_allowed_action', { apikey, appid: appId })
160
- .single()
161
-
162
- return !!data
163
- }
164
-
165
- export async function getAppOwner(supabase: SupabaseClient<Database>, appId: string): Promise<string> {
166
- const { data, error } = await supabase
167
- .from('apps')
168
- .select('user_id')
169
- .eq('app_id', appId)
170
- .single()
171
-
172
- if (error) {
173
- p.log.error('Cannot get app owner, exiting')
174
- p.log.error('Please report the following error to capgo\'s staff')
175
- console.error(error)
176
- process.exit(1)
177
- }
178
-
179
- return data.user_id
180
- }
181
-
182
- export async function isAllowedApp(supabase: SupabaseClient<Database>, apikey: string, appId: string): Promise<boolean> {
183
- const { data } = await supabase
184
- .rpc('is_app_owner', { apikey, appid: appId })
185
- .single()
186
- return !!data
187
- }
188
-
189
- export enum OrganizationPerm {
190
- none = 0,
191
- read = 1,
192
- upload = 2,
193
- write = 3,
194
- admin = 4,
195
- owner = 5,
196
- }
197
-
198
- export const hasOrganizationPerm = (perm: OrganizationPerm, required: OrganizationPerm): boolean => (perm as number) >= (required as number)
199
-
200
- export async function isAllowedAppOrg(supabase: SupabaseClient<Database>, apikey: string, appId: string): Promise<{ okay: true, data: OrganizationPerm } | { okay: false, error: 'INVALID_APIKEY' | 'NO_APP' | 'NO_ORG' }> {
201
- const { data, error } = await supabase
202
- .rpc('get_org_perm_for_apikey', { apikey, app_id: appId })
203
- .single()
204
-
205
- if (error) {
206
- p.log.error('Cannot get permissions for organization!')
207
- console.error(error)
208
- process.exit(1)
209
- }
210
-
211
- const ok = (data as string).includes('perm')
212
- if (ok) {
213
- let perm = null as (OrganizationPerm | null)
214
-
215
- switch (data as string) {
216
- case 'perm_none': {
217
- perm = OrganizationPerm.none
218
- break
219
- }
220
- case 'perm_read': {
221
- perm = OrganizationPerm.read
222
- break
223
- }
224
- case 'perm_upload': {
225
- perm = OrganizationPerm.upload
226
- break
227
- }
228
- case 'perm_write': {
229
- perm = OrganizationPerm.write
230
- break
231
- }
232
- case 'perm_admin': {
233
- perm = OrganizationPerm.admin
234
- break
235
- }
236
- case 'perm_owner': {
237
- perm = OrganizationPerm.owner
238
- break
239
- }
240
- default: {
241
- if ((data as string).includes('invite')) {
242
- p.log.info('Please accept/deny the organization invitation before trying to access the app')
243
- process.exit(1)
244
- }
245
-
246
- p.log.error(`Invalid output when fetching organization permission. Response: ${data}`)
247
- process.exit(1)
248
- }
249
- }
250
-
251
- return {
252
- okay: true,
253
- data: perm,
254
- }
255
- }
256
-
257
- // This means that something went wrong here
258
- let functionError = null as 'INVALID_APIKEY' | 'NO_APP' | 'NO_ORG' | null
259
-
260
- switch (data as string) {
261
- case 'INVALID_APIKEY': {
262
- functionError = 'INVALID_APIKEY'
263
- break
264
- }
265
- case 'NO_APP': {
266
- functionError = 'NO_APP'
267
- break
268
- }
269
- case 'NO_ORG': {
270
- functionError = 'NO_ORG'
271
- break
272
- }
273
- default: {
274
- p.log.error(`Invalid error when fetching organization permission. Response: ${data}`)
275
- process.exit(1)
276
- }
277
- }
278
-
279
- return {
280
- okay: false,
281
- error: functionError,
282
- }
283
- }
284
-
285
- export async function checkPlanValid(supabase: SupabaseClient<Database>, userId: string, apikey: string, appId?: string, warning = true) {
286
- const config = await getRemoteConfig()
287
-
288
- const validPlan = await (appId ? isAllowedActionAppIdApiKey(supabase, appId, apikey) : isAllowedAction(supabase, userId))
289
- if (!validPlan) {
290
- p.log.error(`You need to upgrade your plan to continue to use capgo.\n Upgrade here: ${config.hostWeb}/dashboard/settings/plans\n`)
291
- wait(100)
292
- import('open')
293
- .then((module) => {
294
- module.default(`${config.hostWeb}/dashboard/settings/plans`)
295
- })
296
- wait(500)
297
- program.error('')
298
- }
299
- const trialDays = await isTrial(supabase, userId)
300
- const ispaying = await isPaying(supabase, userId)
301
- if (trialDays > 0 && warning && !ispaying)
302
- p.log.warn(`WARNING !!\nTrial expires in ${trialDays} days, upgrade here: ${config.hostWeb}/dashboard/settings/plans\n`)
303
- }
304
-
305
- export function findSavedKey(quiet = false) {
306
- // search for key in home dir
307
- const userHomeDir = homedir()
308
- let key
309
- let keyPath = `${userHomeDir}/.capgo`
310
- if (existsSync(keyPath)) {
311
- if (!quiet)
312
- p.log.info(`Use global apy key ${keyPath}`)
313
- key = readFileSync(keyPath, 'utf8').trim()
314
- }
315
- keyPath = `.capgo`
316
- if (!key && existsSync(keyPath)) {
317
- if (!quiet)
318
- p.log.info(`Use local apy key ${keyPath}`)
319
- key = readFileSync(keyPath, 'utf8').trim()
320
- }
321
- if (!key) {
322
- p.log.error(`Cannot find API key in local folder or global, please login first with npx @capacitor/cli login`)
323
- program.error('')
324
- }
325
- return key
326
- }
327
-
328
- async function* getFiles(dir: string): AsyncGenerator<string> {
329
- const dirents = await readdirSync(dir, { withFileTypes: true })
330
- for (const dirent of dirents) {
331
- const res = resolve(dir, dirent.name)
332
- if (dirent.isDirectory()
333
- && !dirent.name.startsWith('.')
334
- && !dirent.name.startsWith('node_modules')
335
- && !dirent.name.startsWith('dist'))
336
- yield * getFiles(res)
337
- else
338
- yield res
339
- }
340
- }
341
- export async function findMainFile() {
342
- const mainRegex = /(main|index)\.(ts|tsx|js|jsx)$/
343
- // search for main.ts or main.js in local dir and subdirs
344
- let mainFile = ''
345
- const pwd = process.cwd()
346
- const pwdL = pwd.split('/').length
347
- for await (const f of getFiles(pwd)) {
348
- // find number of folder in path after pwd
349
- const folders = f.split('/').length - pwdL
350
- if (folders <= 2 && mainRegex.test(f)) {
351
- mainFile = f
352
- p.log.info(`Found main file here ${f}`)
353
- break
354
- }
355
- }
356
- return mainFile
357
- }
358
-
359
- interface Config {
360
- app: {
361
- appId: string
362
- appName: string
363
- webDir: string
364
- package: {
365
- version: string
366
- }
367
- extConfigFilePath: string
368
- extConfig: {
369
- extConfig: object
370
- plugins: {
371
- extConfig: object
372
- CapacitorUpdater: {
373
- appReadyTimeout?: number
374
- responseTimeout?: number
375
- autoDeleteFailed?: boolean
376
- autoDeletePrevious?: boolean
377
- autoUpdate?: boolean
378
- resetWhenUpdate?: boolean
379
- updateUrl?: string
380
- statsUrl?: string
381
- version?: string
382
- directUpdate?: boolean
383
- periodCheckDelay?: number
384
- localS3?: boolean
385
- localHost?: string
386
- localWebHost?: string
387
- localSupa?: string
388
- localSupaAnon?: string
389
- allowModifyUrl?: boolean
390
- defaultChannel?: string
391
- channelUrl?: string
392
- publicKey?: string
393
- privateKey?: string
394
- }
395
- }
396
- server: {
397
- cleartext: boolean
398
- url: string
399
- }
400
- }
401
- }
402
- }
403
-
404
- export async function checKOldEncryption() {
405
- const config = await getConfig()
406
- const { extConfig } = config.app
407
- // console.log('localConfig - ', localConfig)
408
- // console.log('config - ', config)
409
-
410
- const hasPrivateKeyInConfig = !!extConfig?.plugins?.CapacitorUpdater?.privateKey
411
- const hasPublicKeyInConfig = !!extConfig?.plugins?.CapacitorUpdater?.publicKey
412
-
413
- if (hasPrivateKeyInConfig)
414
- p.log.warning(`You still have privateKey in the capacitor config, this is deprecated, please remove it`)
415
- p.log.warning(`Encryption with private will be ignored`)
416
-
417
- if (!hasPublicKeyInConfig) {
418
- p.log.warning(`publicKey not found in capacitor config, please run npx @capgo/cli key save`)
419
- program.error('')
420
- }
421
- }
422
-
423
- export async function updateOrCreateVersion(supabase: SupabaseClient<Database>, update: Database['public']['Tables']['app_versions']['Insert']) {
424
- return supabase.from('app_versions')
425
- .upsert(update, { onConflict: 'name,app_id' })
426
- .eq('app_id', update.app_id)
427
- .eq('name', update.name)
428
- }
429
-
430
- export async function uploadUrl(supabase: SupabaseClient<Database>, appId: string, bucketId: string): Promise<string> {
431
- const data = {
432
- app_id: appId,
433
- bucket_id: bucketId,
434
- }
435
- try {
436
- const pathUploadLink = 'private/upload_link'
437
- const res = await supabase.functions.invoke(pathUploadLink, { body: JSON.stringify(data) })
438
- return res.data.url
439
- }
440
- catch (error) {
441
- p.log.error(`Cannot get upload url ${formatError(error)}`)
442
- }
443
- return ''
444
- }
445
-
446
- export async function updateOrCreateChannel(supabase: SupabaseClient<Database>, update: Database['public']['Tables']['channels']['Insert']) {
447
- // console.log('updateOrCreateChannel', update)
448
- if (!update.app_id || !update.name || !update.created_by) {
449
- p.log.error('missing app_id, name, or created_by')
450
- return Promise.reject(new Error('missing app_id, name, or created_by'))
451
- }
452
- const { data, error } = await supabase
453
- .from('channels')
454
- .select('enable_progressive_deploy, secondaryVersionPercentage, secondVersion')
455
- .eq('app_id', update.app_id)
456
- .eq('name', update.name)
457
- // .eq('created_by', update.created_by)
458
- .single()
459
-
460
- if (data && !error) {
461
- if (data.enable_progressive_deploy) {
462
- p.log.info('Progressive deploy is enabled')
463
-
464
- if (data.secondaryVersionPercentage !== 1)
465
- p.log.warn('Latest progressive deploy has not finished')
466
-
467
- update.secondVersion = update.version
468
- if (!data.secondVersion) {
469
- p.log.error('missing secondVersion')
470
- return Promise.reject(new Error('missing secondVersion'))
471
- }
472
- update.version = data.secondVersion
473
- update.secondaryVersionPercentage = 0.1
474
- p.log.info('Started new progressive upload!')
475
-
476
- // update.version = undefined
477
- }
478
- return supabase
479
- .from('channels')
480
- .update(update)
481
- .eq('app_id', update.app_id)
482
- .eq('name', update.name)
483
- // .eq('created_by', update.created_by)
484
- .select()
485
- .single()
486
- }
487
-
488
- return supabase
489
- .from('channels')
490
- .insert(update)
491
- .select()
492
- .single()
493
- }
494
-
495
- export function useLogSnag(): LogSnag {
496
- const logsnag = new LogSnag({
497
- token: 'c124f5e9d0ce5bdd14bbb48f815d5583',
498
- project: 'capgo',
499
- })
500
- return logsnag
501
- }
502
-
503
- export const convertAppName = (appName: string) => appName.replace(/\./g, '--')
504
-
505
- export async function verifyUser(supabase: SupabaseClient<Database>, apikey: string, keymod: Database['public']['Enums']['key_mode'][] = ['all']) {
506
- await checkKey(supabase, apikey, keymod)
507
-
508
- const { data: dataUser, error: userIdError } = await supabase
509
- .rpc('get_user_id', { apikey })
510
- .single()
511
-
512
- const userId = (dataUser || '').toString()
513
-
514
- if (!userId || userIdError) {
515
- p.log.error(`Cannot auth user with apikey`)
516
- program.error('')
517
- }
518
- return userId
519
- }
520
-
521
- export async function requireUpdateMetadata(supabase: SupabaseClient<Database>, channel: string): Promise<boolean> {
522
- const { data, error } = await supabase
523
- .from('channels')
524
- .select('disableAutoUpdate')
525
- .eq('name', channel)
526
- .limit(1)
527
-
528
- if (error) {
529
- p.log.error(`Cannot check if disableAutoUpdate is required ${formatError(error)}`)
530
- program.error('')
531
- }
532
-
533
- // Channel does not exist and the default is never 'version_number'
534
- if (data.length === 0)
535
- return false
536
-
537
- const { disableAutoUpdate } = (data[0])
538
- return disableAutoUpdate === 'version_number'
539
- }
540
-
541
- export function getHumanDate(createdA: string | null) {
542
- const date = new Date(createdA || '')
543
- return date.toLocaleString()
544
- }
545
-
546
- export async function getLocalDepenencies() {
547
- if (!existsSync('./package.json')) {
548
- p.log.error('Missing package.json, you need to be in a capacitor project')
549
- program.error('')
550
- }
551
-
552
- let packageJson
553
- try {
554
- packageJson = JSON.parse(readFileSync('./package.json', 'utf8'))
555
- }
556
- catch (err) {
557
- p.log.error('Invalid package.json, JSON parsing failed')
558
- console.error('json parse error: ', err)
559
- program.error('')
560
- }
561
-
562
- const { dependencies } = packageJson
563
- if (!dependencies) {
564
- p.log.error('Missing dependencies section in package.json')
565
- program.error('')
566
- }
567
-
568
- for (const [key, value] of Object.entries(dependencies)) {
569
- if (typeof value !== 'string') {
570
- p.log.error(`Invalid dependency ${key}: ${value}, expected string, got ${typeof value}`)
571
- program.error('')
572
- }
573
- }
574
-
575
- if (!existsSync('./node_modules/')) {
576
- p.log.error('Missing node_modules folder, please run npm install')
577
- program.error('')
578
- }
579
-
580
- let anyInvalid = false
581
-
582
- const dependenciesObject = await Promise.all(Object.entries(dependencies as Record<string, string>)
583
-
584
- .map(async ([key, value]) => {
585
- const dependencyFolderExists = existsSync(`./node_modules/${key}`)
586
-
587
- if (!dependencyFolderExists) {
588
- anyInvalid = true
589
- p.log.error(`Missing dependency ${key}, please run npm install`)
590
- return { name: key, version: value }
591
- }
592
-
593
- let hasNativeFiles = false
594
- await promiseFiles(`./node_modules/${key}`)
595
- .then((files) => {
596
- if (files.find(fileName => nativeFileRegex.test(fileName)))
597
- hasNativeFiles = true
598
- })
599
- .catch((error) => {
600
- p.log.error(`Error reading node_modulses files for ${key} package`)
601
- console.error(error)
602
- program.error('')
603
- })
604
-
605
- return {
606
- name: key,
607
- version: value,
608
- native: hasNativeFiles,
609
- }
610
- })).catch(() => [])
611
-
612
- if (anyInvalid || dependenciesObject.find(a => a.native === undefined))
613
- program.error('')
614
-
615
- return dependenciesObject as { name: string, version: string, native: boolean }[]
616
- }
617
-
618
- export async function getRemoteDepenencies(supabase: SupabaseClient<Database>, appId: string, channel: string) {
619
- const { data: remoteNativePackages, error } = await supabase
620
- .from('channels')
621
- .select(`version (
622
- native_packages
623
- )`)
624
- .eq('name', channel)
625
- .eq('app_id', appId)
626
- .single()
627
-
628
- if (error) {
629
- p.log.error(`Error fetching native packages: ${error.message}`)
630
- program.error('')
631
- }
632
-
633
- let castedRemoteNativePackages
634
- try {
635
- castedRemoteNativePackages = (remoteNativePackages as any).version.native_packages
636
- }
637
- catch (err) {
638
- // If we do not do this we will get an unreadable
639
- p.log.error(`Error parsing native packages`)
640
- program.error('')
641
- }
642
-
643
- if (!castedRemoteNativePackages) {
644
- p.log.error(`Error parsing native packages, perhaps the metadata does not exist?`)
645
- program.error('')
646
- }
647
-
648
- // Check types
649
- castedRemoteNativePackages.forEach((data: any) => {
650
- if (typeof data !== 'object') {
651
- p.log.error(`Invalid remote native package data: ${data}, expected object, got ${typeof data}`)
652
- program.error('')
653
- }
654
-
655
- const { name, version } = data
656
- if (!name || typeof name !== 'string') {
657
- p.log.error(`Invalid remote native package name: ${name}, expected string, got ${typeof name}`)
658
- program.error('')
659
- }
660
-
661
- if (!version || typeof version !== 'string') {
662
- p.log.error(`Invalid remote native package version: ${version}, expected string, got ${typeof version}`)
663
- program.error('')
664
- }
665
- })
666
-
667
- const mappedRemoteNativePackages = new Map((castedRemoteNativePackages as { name: string, version: string }[])
668
- .map(a => [a.name, a]))
669
-
670
- return mappedRemoteNativePackages
671
- }
672
-
673
- export async function checkCompatibility(supabase: SupabaseClient<Database>, appId: string, channel: string) {
674
- const dependenciesObject = await getLocalDepenencies()
675
- const mappedRemoteNativePackages = await getRemoteDepenencies(supabase, appId, channel)
676
-
677
- const finalDepenencies:
678
- ({
679
- name: string
680
- localVersion: string
681
- remoteVersion: string
682
- } | {
683
- name: string
684
- localVersion: string
685
- remoteVersion: undefined
686
- } | {
687
- name: string
688
- localVersion: undefined
689
- remoteVersion: string
690
- })[] = dependenciesObject
691
- .filter(a => !!a.native)
692
- .map((local) => {
693
- const remotePackage = mappedRemoteNativePackages.get(local.name)
694
- if (remotePackage) {
695
- return {
696
- name: local.name,
697
- localVersion: local.version,
698
- remoteVersion: remotePackage.version,
699
- }
700
- }
701
-
702
- return {
703
- name: local.name,
704
- localVersion: local.version,
705
- remoteVersion: undefined,
706
- }
707
- })
708
-
709
- const removeNotInLocal = [...mappedRemoteNativePackages]
710
- .filter(([remoteName]) => dependenciesObject.find(a => a.name === remoteName) === undefined)
711
- .map(([name, version]) => ({ name, localVersion: undefined, remoteVersion: version.version }))
712
-
713
- finalDepenencies.push(...removeNotInLocal)
714
-
715
- return {
716
- finalCompatibility: finalDepenencies,
717
- localDependencies: dependenciesObject,
718
- }
719
- }