@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.
- package/README.md +197 -41
- package/dist/index.js +335 -95820
- package/dist/package.json +83 -0
- package/package.json +48 -62
- package/.eslintignore +0 -4
- package/.github/FUNDING.yml +0 -1
- package/.github/workflows/build.yml +0 -46
- package/.github/workflows/bump_version.yml +0 -56
- package/.github/workflows/test.yml +0 -30
- package/.prettierignore +0 -6
- package/.vscode/launch.json +0 -23
- package/.vscode/settings.json +0 -5
- package/.vscode/tasks.json +0 -42
- package/CHANGELOG.md +0 -2861
- package/build.mjs +0 -23
- package/bun.lockb +0 -0
- package/capacitor.config.ts +0 -33
- package/crypto_explained.png +0 -0
- package/eslint.config.js +0 -3
- package/renovate.json +0 -23
- package/src/api/app.ts +0 -75
- package/src/api/channels.ts +0 -140
- package/src/api/crypto.ts +0 -121
- package/src/api/devices_override.ts +0 -41
- package/src/api/update.ts +0 -12
- package/src/api/versions.ts +0 -101
- package/src/app/add.ts +0 -191
- package/src/app/debug.ts +0 -220
- package/src/app/delete.ts +0 -106
- package/src/app/info.ts +0 -87
- package/src/app/list.ts +0 -67
- package/src/app/set.ts +0 -94
- package/src/bundle/check.ts +0 -42
- package/src/bundle/cleanup.ts +0 -127
- package/src/bundle/compatibility.ts +0 -70
- package/src/bundle/decrypt.ts +0 -65
- package/src/bundle/delete.ts +0 -53
- package/src/bundle/encrypt.ts +0 -69
- package/src/bundle/list.ts +0 -43
- package/src/bundle/unlink.ts +0 -86
- package/src/bundle/upload.ts +0 -516
- package/src/bundle/zip.ts +0 -139
- package/src/channel/add.ts +0 -73
- package/src/channel/currentBundle.ts +0 -72
- package/src/channel/delete.ts +0 -51
- package/src/channel/list.ts +0 -49
- package/src/channel/set.ts +0 -174
- package/src/index.ts +0 -290
- package/src/init.ts +0 -301
- package/src/key.ts +0 -158
- package/src/login.ts +0 -66
- package/src/types/capacitor__cli.d.ts +0 -6
- package/src/types/supabase.types.ts +0 -2471
- package/src/utils.ts +0 -738
- package/test/chunk_convert.ts +0 -28
- package/test/data.ts +0 -18769
- package/test/test_headers_rls.ts +0 -24
- package/test/test_semver.ts +0 -13
- 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
|
-
}
|