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