@capgo/cli 5.0.0-alpha.7 → 7.0.1

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