@capgo/cli 4.13.7 → 4.13.9
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/.github/FUNDING.yml +1 -0
- package/.github/workflows/build.yml +46 -0
- package/.github/workflows/bump_version.yml +56 -0
- package/.github/workflows/test.yml +30 -0
- package/.prettierignore +6 -0
- package/.vscode/launch.json +23 -0
- package/.vscode/settings.json +46 -0
- package/.vscode/tasks.json +42 -0
- package/CHANGELOG.md +2739 -0
- package/build.mjs +23 -0
- package/bun.lockb +0 -0
- package/capacitor.config.ts +33 -0
- package/crypto_explained.png +0 -0
- package/dist/index.js +114692 -205
- package/eslint.config.js +10 -0
- package/package.json +33 -32
- package/renovate.json +23 -0
- package/src/api/app.ts +55 -0
- package/src/api/channels.ts +140 -0
- package/src/api/crypto.ts +116 -0
- package/src/api/devices_override.ts +41 -0
- package/src/api/update.ts +13 -0
- package/src/api/versions.ts +101 -0
- package/src/app/add.ts +158 -0
- package/src/app/debug.ts +222 -0
- package/src/app/delete.ts +106 -0
- package/src/app/info.ts +90 -0
- package/src/app/list.ts +67 -0
- package/src/app/set.ts +94 -0
- package/src/bundle/check.ts +42 -0
- package/src/bundle/cleanup.ts +127 -0
- package/src/bundle/compatibility.ts +70 -0
- package/src/bundle/decrypt.ts +54 -0
- package/src/bundle/delete.ts +53 -0
- package/src/bundle/encrypt.ts +60 -0
- package/src/bundle/list.ts +43 -0
- package/src/bundle/unlink.ts +86 -0
- package/src/bundle/upload.ts +532 -0
- package/src/bundle/zip.ts +139 -0
- package/src/channel/add.ts +74 -0
- package/src/channel/currentBundle.ts +72 -0
- package/src/channel/delete.ts +52 -0
- package/src/channel/list.ts +49 -0
- package/src/channel/set.ts +178 -0
- package/src/index.ts +307 -0
- package/src/init.ts +342 -0
- package/src/key.ts +131 -0
- package/src/login.ts +70 -0
- package/src/types/capacitor__cli.d.ts +6 -0
- package/src/types/supabase.types.ts +2193 -0
- package/src/user/account.ts +11 -0
- package/src/utils.ts +956 -0
- package/test/chunk_convert.ts +28 -0
- package/test/data.ts +18769 -0
- package/test/test_headers_rls.ts +24 -0
- package/test/test_semver.ts +13 -0
- package/tsconfig.json +39 -0
package/eslint.config.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capgo/cli",
|
|
3
|
-
"version": "4.13.
|
|
3
|
+
"version": "4.13.9",
|
|
4
4
|
"description": "A CLI to upload to capgo servers",
|
|
5
5
|
"author": "github.com/riderx",
|
|
6
6
|
"license": "Apache 2.0",
|
|
@@ -33,57 +33,58 @@
|
|
|
33
33
|
},
|
|
34
34
|
"scripts": {
|
|
35
35
|
"build": "node build.mjs",
|
|
36
|
-
"dev": "NODE_ENV=development
|
|
36
|
+
"dev": "NODE_ENV=development node build.mjs",
|
|
37
37
|
"no-debug": "node dist/index.js",
|
|
38
38
|
"test": "npx --yes ts-node -T src/index.ts",
|
|
39
|
-
"dev-build": "SUPA_DB=development
|
|
39
|
+
"dev-build": "SUPA_DB=development node build.mjs",
|
|
40
40
|
"pack": "pkg",
|
|
41
41
|
"types": "npx --yes supabase gen types typescript --project-id=xvwzpoazmxkqosrdewyv > src/types/supabase.types.ts",
|
|
42
42
|
"test_rls": "ts-node ./test/test_headers_rls.ts",
|
|
43
|
-
"lint": "eslint \"src/**/*.ts\" --fix"
|
|
44
|
-
"check-posix-paths": "node test/check-posix-paths.js"
|
|
43
|
+
"lint": "eslint \"src/**/*.ts\" --fix"
|
|
45
44
|
},
|
|
46
|
-
"
|
|
47
|
-
"@
|
|
48
|
-
"@
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@aws-sdk/client-s3": "^3.574.0",
|
|
47
|
+
"@capacitor/cli": "6.0.0",
|
|
49
48
|
"@capgo/find-package-manager": "^0.0.17",
|
|
50
49
|
"@clack/prompts": "^0.7.0",
|
|
51
50
|
"@manypkg/find-root": "^2.2.1",
|
|
52
|
-
"@supabase/supabase-js": "^2.
|
|
51
|
+
"@supabase/supabase-js": "^2.43.1",
|
|
53
52
|
"@tomasklaen/checksum": "^1.1.0",
|
|
53
|
+
"@trufflesuite/spinnies": "^0.1.1",
|
|
54
|
+
"adm-zip": "^0.5.12",
|
|
55
|
+
"ci-info": "^4.0.0",
|
|
56
|
+
"commander": "12.0.0",
|
|
57
|
+
"console-table-printer": "^2.12.0",
|
|
58
|
+
"get-latest-version": "^5.1.0",
|
|
59
|
+
"ky": "^1.2.4",
|
|
60
|
+
"logsnag": "1.0.0",
|
|
61
|
+
"mime": "^4.0.3",
|
|
62
|
+
"node-dir": "^0.1.17",
|
|
63
|
+
"open": "^10.1.0",
|
|
64
|
+
"prettyjson": "^1.2.5",
|
|
65
|
+
"prompt-sync": "^4.2.0",
|
|
66
|
+
"semver": "^7.6.2"
|
|
67
|
+
},
|
|
68
|
+
"devDependencies": {
|
|
69
|
+
"@antfu/eslint-config": "^2.17.0",
|
|
54
70
|
"@types/adm-zip": "0.5.5",
|
|
55
71
|
"@types/mime": "^4.0.0",
|
|
56
|
-
"@types/node": "^20.
|
|
72
|
+
"@types/node": "^20.12.7",
|
|
73
|
+
"@types/node-dir": "^0.0.37",
|
|
57
74
|
"@types/npmcli__ci-detect": "^2.0.3",
|
|
58
75
|
"@types/prettyjson": "^0.0.33",
|
|
59
76
|
"@types/prompt-sync": "^4.2.3",
|
|
60
77
|
"@types/semver": "^7.5.8",
|
|
61
|
-
"@
|
|
62
|
-
"@typescript-eslint/
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
"adm-zip": "^0.5.14",
|
|
66
|
-
"ci-info": "^4.0.0",
|
|
67
|
-
"commander": "12.1.0",
|
|
68
|
-
"console-table-printer": "^2.12.1",
|
|
69
|
-
"esbuild": "^0.23.0",
|
|
70
|
-
"eslint": "9.7.0",
|
|
71
|
-
"get-latest-version": "^5.1.0",
|
|
78
|
+
"@typescript-eslint/eslint-plugin": "^7.8.0",
|
|
79
|
+
"@typescript-eslint/parser": "^7.8.0",
|
|
80
|
+
"esbuild": "^0.21.2",
|
|
81
|
+
"eslint": "9.1.1",
|
|
72
82
|
"git-format-staged": "3.1.1",
|
|
73
|
-
"husky": "^9.
|
|
74
|
-
"is-wsl": "^3.1.0",
|
|
75
|
-
"jszip": "^3.10.1",
|
|
76
|
-
"ky": "^1.4.0",
|
|
77
|
-
"logsnag": "1.0.0",
|
|
78
|
-
"mime": "^4.0.4",
|
|
79
|
-
"open": "^10.1.0",
|
|
83
|
+
"husky": "^9.0.11",
|
|
80
84
|
"pkg": "5.8.1",
|
|
81
|
-
"prettyjson": "^1.2.5",
|
|
82
|
-
"semver": "^7.6.2",
|
|
83
|
-
"tmp": "^0.2.3",
|
|
84
85
|
"ts-loader": "^9.5.1",
|
|
85
86
|
"ts-node": "^10.9.2",
|
|
86
87
|
"tsconfig-paths": "4.2.0",
|
|
87
|
-
"typescript": "5.5
|
|
88
|
+
"typescript": "5.4.5"
|
|
88
89
|
}
|
|
89
90
|
}
|
package/renovate.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
|
3
|
+
"extends": [
|
|
4
|
+
"config:base",
|
|
5
|
+
"schedule:earlyMondays"
|
|
6
|
+
],
|
|
7
|
+
"lockFileMaintenance": {
|
|
8
|
+
"enabled": true,
|
|
9
|
+
"automerge": true,
|
|
10
|
+
"automergeType": "branch",
|
|
11
|
+
"platformAutomerge": true
|
|
12
|
+
},
|
|
13
|
+
"packageRules": [
|
|
14
|
+
{
|
|
15
|
+
"matchUpdateTypes": [
|
|
16
|
+
"minor",
|
|
17
|
+
"patch"
|
|
18
|
+
],
|
|
19
|
+
"matchCurrentVersion": "!/^0/",
|
|
20
|
+
"automerge": true
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
package/src/api/app.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { SupabaseClient } from '@supabase/supabase-js'
|
|
2
|
+
import * as p from '@clack/prompts'
|
|
3
|
+
import { program } from 'commander'
|
|
4
|
+
import type { Database } from '../types/supabase.types'
|
|
5
|
+
import type { OptionsBase } from '../utils'
|
|
6
|
+
import { OrganizationPerm, getPMAndCommand, isAllowedAppOrg } from '../utils'
|
|
7
|
+
|
|
8
|
+
export async function checkAppExists(supabase: SupabaseClient<Database>, appid: string) {
|
|
9
|
+
const { data: app } = await supabase
|
|
10
|
+
.rpc('exist_app_v2', { appid })
|
|
11
|
+
.single()
|
|
12
|
+
return !!app
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function checkAppExistsAndHasPermissionOrgErr(supabase: SupabaseClient<Database>, apikey: string, appid: string, requiredPermission: OrganizationPerm) {
|
|
16
|
+
const pm = getPMAndCommand()
|
|
17
|
+
const permissions = await isAllowedAppOrg(supabase, apikey, appid)
|
|
18
|
+
if (!permissions.okay) {
|
|
19
|
+
switch (permissions.error) {
|
|
20
|
+
case 'INVALID_APIKEY': {
|
|
21
|
+
p.log.error('Invalid apikey, such apikey does not exists!')
|
|
22
|
+
program.error('')
|
|
23
|
+
break
|
|
24
|
+
}
|
|
25
|
+
case 'NO_APP': {
|
|
26
|
+
p.log.error(`App ${appid} does not exist, run first \`${pm.runner} @capgo/cli app add ${appid}\` to create it`)
|
|
27
|
+
program.error('')
|
|
28
|
+
break
|
|
29
|
+
}
|
|
30
|
+
case 'NO_ORG': {
|
|
31
|
+
p.log.error('Could not find organization, please contact support to resolve this!')
|
|
32
|
+
program.error('')
|
|
33
|
+
break
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const remotePermNumber = permissions.data as number
|
|
39
|
+
const requiredPermNumber = requiredPermission as number
|
|
40
|
+
|
|
41
|
+
if (requiredPermNumber > remotePermNumber) {
|
|
42
|
+
p.log.error(`Insuficcent permissions for app ${appid}. Current permission: ${OrganizationPerm[permissions.data]}, required for this action: ${OrganizationPerm[requiredPermission]}.`)
|
|
43
|
+
program.error('')
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return permissions.data
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface Options extends OptionsBase {
|
|
50
|
+
name?: string
|
|
51
|
+
icon?: string
|
|
52
|
+
retention?: number
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const newIconPath = 'assets/icon.png'
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import process from 'node:process'
|
|
2
|
+
import type { SupabaseClient } from '@supabase/supabase-js'
|
|
3
|
+
import { program } from 'commander'
|
|
4
|
+
import { Table } from 'console-table-printer'
|
|
5
|
+
import * as p from '@clack/prompts'
|
|
6
|
+
import type { Database } from '../types/supabase.types'
|
|
7
|
+
import { formatError } from '../utils'
|
|
8
|
+
|
|
9
|
+
export async function checkVersionNotUsedInChannel(supabase: SupabaseClient<Database>, appid: string, versionData: Database['public']['Tables']['app_versions']['Row']) {
|
|
10
|
+
const { data: channelFound, error: errorChannel } = await supabase
|
|
11
|
+
.from('channels')
|
|
12
|
+
.select()
|
|
13
|
+
.eq('app_id', appid)
|
|
14
|
+
.eq('version', versionData.id)
|
|
15
|
+
if (errorChannel) {
|
|
16
|
+
p.log.error(`Cannot check Version ${appid}@${versionData.name}`)
|
|
17
|
+
program.error('')
|
|
18
|
+
}
|
|
19
|
+
if (channelFound && channelFound.length > 0) {
|
|
20
|
+
p.intro(`❌ Version ${appid}@${versionData.name} is used in ${channelFound.length} channel`)
|
|
21
|
+
if (await p.confirm({ message: 'unlink it?' })) {
|
|
22
|
+
// loop on all channels and set version to unknown
|
|
23
|
+
for (const channel of channelFound) {
|
|
24
|
+
const s = p.spinner()
|
|
25
|
+
s.start(`Unlinking channel ${channel.name}`)
|
|
26
|
+
const { error: errorChannelUpdate } = await supabase
|
|
27
|
+
.from('channels')
|
|
28
|
+
.update({
|
|
29
|
+
version: (await findUnknownVersion(supabase, appid))?.id,
|
|
30
|
+
})
|
|
31
|
+
.eq('id', channel.id)
|
|
32
|
+
if (errorChannelUpdate) {
|
|
33
|
+
s.stop(`Cannot update channel ${channel.name} ${formatError(errorChannelUpdate)}`)
|
|
34
|
+
process.exit(1)
|
|
35
|
+
}
|
|
36
|
+
s.stop(`✅ Channel ${channel.name} unlinked`)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
p.log.error(`Unlink it first`)
|
|
41
|
+
program.error('')
|
|
42
|
+
}
|
|
43
|
+
p.outro(`Version unlinked from ${channelFound.length} channel`)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function findUnknownVersion(supabase: SupabaseClient<Database>, appId: string) {
|
|
48
|
+
return supabase
|
|
49
|
+
.from('app_versions')
|
|
50
|
+
.select('id')
|
|
51
|
+
.eq('app_id', appId)
|
|
52
|
+
.eq('name', 'unknown')
|
|
53
|
+
.throwOnError()
|
|
54
|
+
.single().then(({ data }) => data)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function createChannel(supabase: SupabaseClient<Database>, update: Database['public']['Tables']['channels']['Insert']) {
|
|
58
|
+
return supabase
|
|
59
|
+
.from('channels')
|
|
60
|
+
.insert(update)
|
|
61
|
+
.select()
|
|
62
|
+
.single()
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function delChannel(supabase: SupabaseClient<Database>, name: string, appId: string, _userId: string) {
|
|
66
|
+
return supabase
|
|
67
|
+
.from('channels')
|
|
68
|
+
.delete()
|
|
69
|
+
.eq('name', name)
|
|
70
|
+
.eq('app_id', appId)
|
|
71
|
+
.single()
|
|
72
|
+
}
|
|
73
|
+
interface version {
|
|
74
|
+
id: string
|
|
75
|
+
name: string
|
|
76
|
+
}
|
|
77
|
+
export function displayChannels(data: (Database['public']['Tables']['channels']['Row'] & { version?: version, secondVersion?: version })[]) {
|
|
78
|
+
const t = new Table({
|
|
79
|
+
title: 'Channels',
|
|
80
|
+
charLength: { '❌': 2, '✅': 2 },
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
// add rows with color
|
|
84
|
+
data.reverse().forEach((row) => {
|
|
85
|
+
t.addRow({
|
|
86
|
+
'Name': row.name,
|
|
87
|
+
...(row.version ? { Version: row.version.name } : undefined),
|
|
88
|
+
'Public': row.public ? '✅' : '❌',
|
|
89
|
+
'iOS': row.ios ? '❌' : '✅',
|
|
90
|
+
'Android': row.android ? '❌' : '✅',
|
|
91
|
+
'⬆️ limit': row.disableAutoUpdate,
|
|
92
|
+
'⬇️ under native': row.disableAutoUpdateUnderNative ? '❌' : '✅',
|
|
93
|
+
'Self assign': row.allow_device_self_set ? '✅' : '❌',
|
|
94
|
+
'Progressive': row.enable_progressive_deploy ? '✅' : '❌',
|
|
95
|
+
...(row.enable_progressive_deploy && row.secondVersion ? { 'Next version': row.secondVersion.name } : undefined),
|
|
96
|
+
...(row.enable_progressive_deploy && row.secondVersion ? { 'Next %': row.secondaryVersionPercentage } : undefined),
|
|
97
|
+
'AB Testing': row.enableAbTesting ? '✅' : '❌',
|
|
98
|
+
...(row.enableAbTesting && row.secondVersion ? { 'Version B': row.secondVersion } : undefined),
|
|
99
|
+
...(row.enableAbTesting && row.secondVersion ? { 'A/B %': row.secondaryVersionPercentage } : undefined),
|
|
100
|
+
'Emulator': row.allow_emulator ? '✅' : '❌',
|
|
101
|
+
'Dev 📱': row.allow_dev ? '✅' : '❌',
|
|
102
|
+
})
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
p.log.success(t.render())
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export async function getActiveChannels(supabase: SupabaseClient<Database>, appid: string) {
|
|
109
|
+
const { data, error: vError } = await supabase
|
|
110
|
+
.from('channels')
|
|
111
|
+
.select(`
|
|
112
|
+
id,
|
|
113
|
+
name,
|
|
114
|
+
public,
|
|
115
|
+
allow_emulator,
|
|
116
|
+
allow_dev,
|
|
117
|
+
ios,
|
|
118
|
+
android,
|
|
119
|
+
allow_device_self_set,
|
|
120
|
+
disableAutoUpdateUnderNative,
|
|
121
|
+
disableAutoUpdate,
|
|
122
|
+
enable_progressive_deploy,
|
|
123
|
+
enableAbTesting,
|
|
124
|
+
secondaryVersionPercentage,
|
|
125
|
+
secondVersion (id, name),
|
|
126
|
+
created_at,
|
|
127
|
+
created_by,
|
|
128
|
+
app_id,
|
|
129
|
+
version (id, name)
|
|
130
|
+
`)
|
|
131
|
+
.eq('app_id', appid)
|
|
132
|
+
// .eq('created_by', userId)
|
|
133
|
+
.order('created_at', { ascending: false })
|
|
134
|
+
|
|
135
|
+
if (vError) {
|
|
136
|
+
p.log.error(`App ${appid} not found in database`)
|
|
137
|
+
program.error('')
|
|
138
|
+
}
|
|
139
|
+
return data
|
|
140
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import {
|
|
2
|
+
constants,
|
|
3
|
+
createCipheriv,
|
|
4
|
+
createDecipheriv,
|
|
5
|
+
generateKeyPairSync,
|
|
6
|
+
privateDecrypt,
|
|
7
|
+
publicEncrypt,
|
|
8
|
+
randomBytes,
|
|
9
|
+
} from 'node:crypto'
|
|
10
|
+
import { Buffer } from 'node:buffer'
|
|
11
|
+
|
|
12
|
+
const algorithm = 'aes-128-cbc'
|
|
13
|
+
const oaepHash = 'sha256'
|
|
14
|
+
const formatB64 = 'base64'
|
|
15
|
+
const padding = constants.RSA_PKCS1_OAEP_PADDING
|
|
16
|
+
|
|
17
|
+
export function decryptSource(source: Buffer, ivSessionKey: string, privateKey: string): Buffer {
|
|
18
|
+
// console.log('\nivSessionKey', ivSessionKey)
|
|
19
|
+
const [ivB64, sessionb64Encrypted] = ivSessionKey.split(':')
|
|
20
|
+
// console.log('\nsessionb64Encrypted', sessionb64Encrypted)
|
|
21
|
+
// console.log('\nivB64', ivB64)
|
|
22
|
+
const sessionKey = privateDecrypt(
|
|
23
|
+
{
|
|
24
|
+
key: privateKey,
|
|
25
|
+
padding,
|
|
26
|
+
oaepHash,
|
|
27
|
+
},
|
|
28
|
+
Buffer.from(sessionb64Encrypted, formatB64),
|
|
29
|
+
)
|
|
30
|
+
// ivB64 to uft-8
|
|
31
|
+
const initVector = Buffer.from(ivB64, formatB64)
|
|
32
|
+
// console.log('\nSessionB64', sessionB64)
|
|
33
|
+
|
|
34
|
+
const decipher = createDecipheriv(algorithm, sessionKey, initVector)
|
|
35
|
+
decipher.setAutoPadding(true)
|
|
36
|
+
const decryptedData = Buffer.concat([decipher.update(source), decipher.final()])
|
|
37
|
+
|
|
38
|
+
return decryptedData
|
|
39
|
+
}
|
|
40
|
+
export interface Encoded {
|
|
41
|
+
ivSessionKey: string
|
|
42
|
+
encryptedData: Buffer
|
|
43
|
+
}
|
|
44
|
+
export function encryptSource(source: Buffer, publicKey: string): Encoded {
|
|
45
|
+
// encrypt zip with key
|
|
46
|
+
const initVector = randomBytes(16)
|
|
47
|
+
const sessionKey = randomBytes(16)
|
|
48
|
+
// encrypt session key with public key
|
|
49
|
+
// console.log('\nencrypted.key', encrypted.key.toString(CryptoJS.enc.Base64))
|
|
50
|
+
const cipher = createCipheriv(algorithm, sessionKey, initVector)
|
|
51
|
+
cipher.setAutoPadding(true)
|
|
52
|
+
// console.log('\nsessionKey', sessionKey.toString())
|
|
53
|
+
// const sessionB64 = sessionKey.toString(formatB64)
|
|
54
|
+
// console.log('\nsessionB64', sessionB64)
|
|
55
|
+
const ivB64 = initVector.toString(formatB64)
|
|
56
|
+
// console.log('\nivB64', ivB64)
|
|
57
|
+
const sessionb64Encrypted = publicEncrypt(
|
|
58
|
+
{
|
|
59
|
+
key: publicKey,
|
|
60
|
+
padding,
|
|
61
|
+
oaepHash,
|
|
62
|
+
},
|
|
63
|
+
sessionKey,
|
|
64
|
+
).toString(formatB64)
|
|
65
|
+
// console.log('\nsessionb64Encrypted', sessionb64Encrypted)
|
|
66
|
+
const ivSessionKey = `${ivB64}:${sessionb64Encrypted}`
|
|
67
|
+
// console.log('\nivSessionKey', sessionKey)
|
|
68
|
+
// encrypted to buffer
|
|
69
|
+
|
|
70
|
+
const encryptedData = Buffer.concat([cipher.update(source), cipher.final()])
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
encryptedData,
|
|
74
|
+
ivSessionKey,
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
export interface RSAKeys {
|
|
78
|
+
publicKey: string
|
|
79
|
+
privateKey: string
|
|
80
|
+
}
|
|
81
|
+
export function createRSA(): RSAKeys {
|
|
82
|
+
const { publicKey, privateKey } = generateKeyPairSync('rsa', {
|
|
83
|
+
// The standard secure default length for RSA keys is 2048 bits
|
|
84
|
+
modulusLength: 2048,
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
// Generate RSA key pair
|
|
88
|
+
return {
|
|
89
|
+
publicKey: publicKey.export({
|
|
90
|
+
type: 'pkcs1',
|
|
91
|
+
format: 'pem',
|
|
92
|
+
}) as string,
|
|
93
|
+
privateKey: privateKey.export({
|
|
94
|
+
type: 'pkcs1',
|
|
95
|
+
format: 'pem',
|
|
96
|
+
}) as string,
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// test AES
|
|
100
|
+
|
|
101
|
+
// const source = 'Hello world'
|
|
102
|
+
// console.log('\nsource', source)
|
|
103
|
+
// const { publicKey, privateKey } = createRSA()
|
|
104
|
+
|
|
105
|
+
// console.log('\nencryptSource ================================================================')
|
|
106
|
+
// // convert source to base64
|
|
107
|
+
// const sourceBuff = Buffer.from(source)
|
|
108
|
+
// const res = encryptSource(sourceBuff, publicKey)
|
|
109
|
+
// console.log('\nencryptedData', res.encryptedData.toString('base64'))
|
|
110
|
+
// // console.log('\nres', res)
|
|
111
|
+
// console.log('\ndecryptSource ================================================================')
|
|
112
|
+
// const decodedSource = decryptSource(res.encryptedData, res.ivSessionKey, privateKey)
|
|
113
|
+
// // convert decodedSource from base64 to utf-8
|
|
114
|
+
// const decodedSourceString = decodedSource.toString('utf-8')
|
|
115
|
+
// console.log('\ndecodedSourceString', decodedSourceString)
|
|
116
|
+
// console.log('\n Is match', decodedSourceString === source)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import process from 'node:process'
|
|
2
|
+
import type { SupabaseClient } from '@supabase/supabase-js'
|
|
3
|
+
import { program } from 'commander'
|
|
4
|
+
import * as p from '@clack/prompts'
|
|
5
|
+
import type { Database } from '../types/supabase.types'
|
|
6
|
+
import { formatError } from '../utils'
|
|
7
|
+
|
|
8
|
+
export async function checkVersionNotUsedInDeviceOverride(supabase: SupabaseClient<Database>, appid: string, versionData: Database['public']['Tables']['app_versions']['Row']) {
|
|
9
|
+
const { data: deviceFound, error: errorDevice } = await supabase
|
|
10
|
+
.from('devices_override')
|
|
11
|
+
.select()
|
|
12
|
+
.eq('app_id', appid)
|
|
13
|
+
.eq('version', versionData.id)
|
|
14
|
+
if (errorDevice) {
|
|
15
|
+
p.log.error(`Cannot check Device override ${appid}@${versionData.name}`)
|
|
16
|
+
program.error('')
|
|
17
|
+
}
|
|
18
|
+
if (deviceFound && deviceFound.length > 0) {
|
|
19
|
+
p.intro(`❌ Version ${appid}@${versionData.name} is used in ${deviceFound.length} device override`)
|
|
20
|
+
if (await p.confirm({ message: 'unlink it?' })) {
|
|
21
|
+
// loop on all devices and set version to unknown
|
|
22
|
+
for (const device of deviceFound) {
|
|
23
|
+
const s = p.spinner()
|
|
24
|
+
s.start(`Unlinking device ${device.device_id}`)
|
|
25
|
+
const { error: errorDeviceDel } = await supabase
|
|
26
|
+
.from('devices_override')
|
|
27
|
+
.delete()
|
|
28
|
+
.eq('device_id', device.device_id)
|
|
29
|
+
if (errorDeviceDel) {
|
|
30
|
+
s.stop(`Cannot unlink device ${device.device_id} ${formatError(errorDeviceDel)}`)
|
|
31
|
+
process.exit(1)
|
|
32
|
+
}
|
|
33
|
+
s.stop(`✅ Device ${device.device_id} unlinked`)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
p.log.error(`Unlink it first`)
|
|
38
|
+
program.error('')
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import getLatest from 'get-latest-version'
|
|
2
|
+
import * as p from '@clack/prompts'
|
|
3
|
+
import pack from '../../package.json'
|
|
4
|
+
|
|
5
|
+
export async function checkLatest() {
|
|
6
|
+
const latest = await getLatest('@capgo/cli')
|
|
7
|
+
const major = latest?.split('.')[0]
|
|
8
|
+
if (latest !== pack.version) {
|
|
9
|
+
p.log.warning(`🚨 You are using @capgo/cli@${pack.version} it's not the latest version.
|
|
10
|
+
Please use @capgo/cli@${latest}" or @capgo/cli@${major} to keep up to date with the latest features and bug fixes.`,
|
|
11
|
+
)
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { SupabaseClient } from '@supabase/supabase-js'
|
|
2
|
+
import { program } from 'commander'
|
|
3
|
+
import { Table } from 'console-table-printer'
|
|
4
|
+
import * as p from '@clack/prompts'
|
|
5
|
+
import type { Database } from '../types/supabase.types'
|
|
6
|
+
|
|
7
|
+
// import { definitions } from '../types/types_supabase';
|
|
8
|
+
import { getHumanDate } from '../utils'
|
|
9
|
+
import { checkVersionNotUsedInChannel } from './channels'
|
|
10
|
+
import { checkVersionNotUsedInDeviceOverride } from './devices_override'
|
|
11
|
+
|
|
12
|
+
export async function deleteAppVersion(supabase: SupabaseClient<Database>, appid: string, bundle: string) {
|
|
13
|
+
const { error: delAppSpecVersionError } = await supabase
|
|
14
|
+
.from('app_versions')
|
|
15
|
+
.update({
|
|
16
|
+
deleted: true,
|
|
17
|
+
})
|
|
18
|
+
.eq('app_id', appid)
|
|
19
|
+
.eq('deleted', false)
|
|
20
|
+
.eq('name', bundle)
|
|
21
|
+
if (delAppSpecVersionError) {
|
|
22
|
+
p.log.error(`App Version ${appid}@${bundle} not found in database`)
|
|
23
|
+
program.error('')
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function deleteSpecificVersion(supabase: SupabaseClient<Database>, appid: string, _userId: string, bundle: string) {
|
|
28
|
+
const versionData = await getVersionData(supabase, appid, bundle)
|
|
29
|
+
await checkVersionNotUsedInChannel(supabase, appid, versionData)
|
|
30
|
+
await checkVersionNotUsedInDeviceOverride(supabase, appid, versionData)
|
|
31
|
+
// Delete only a specific version in storage
|
|
32
|
+
await deleteAppVersion(supabase, appid, bundle)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function displayBundles(data: (Database['public']['Tables']['app_versions']['Row'] & { keep?: string })[]) {
|
|
36
|
+
if (!data.length) {
|
|
37
|
+
p.log.error('No bundle found')
|
|
38
|
+
// eslint-disable-next-line node/prefer-global/process
|
|
39
|
+
process.exit(1)
|
|
40
|
+
}
|
|
41
|
+
const t = new Table({
|
|
42
|
+
title: 'Bundles',
|
|
43
|
+
charLength: { '❌': 2, '✅': 2 },
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
// add rows with color
|
|
47
|
+
data.reverse().forEach((row) => {
|
|
48
|
+
t.addRow({
|
|
49
|
+
Version: row.name,
|
|
50
|
+
Created: getHumanDate(row.created_at),
|
|
51
|
+
...(row.keep != null ? { Keep: row.keep } : {}),
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
p.log.success(t.render())
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function getActiveAppVersions(supabase: SupabaseClient<Database>, appid: string, _userId: string) {
|
|
59
|
+
const { data, error: vError } = await supabase
|
|
60
|
+
.from('app_versions')
|
|
61
|
+
.select()
|
|
62
|
+
.eq('app_id', appid)
|
|
63
|
+
// .eq('user_id', userId)
|
|
64
|
+
.eq('deleted', false)
|
|
65
|
+
.order('created_at', { ascending: false })
|
|
66
|
+
|
|
67
|
+
if (vError) {
|
|
68
|
+
p.log.error(`App ${appid} not found in database`)
|
|
69
|
+
program.error('')
|
|
70
|
+
}
|
|
71
|
+
return data
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export async function getChannelsVersion(supabase: SupabaseClient<Database>, appid: string) {
|
|
75
|
+
// get all channels versionID
|
|
76
|
+
const { data: channels, error: channelsError } = await supabase
|
|
77
|
+
.from('channels')
|
|
78
|
+
.select('version')
|
|
79
|
+
.eq('app_id', appid)
|
|
80
|
+
|
|
81
|
+
if (channelsError) {
|
|
82
|
+
p.log.error(`App ${appid} not found in database`)
|
|
83
|
+
program.error('')
|
|
84
|
+
}
|
|
85
|
+
return channels.map(c => c.version)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export async function getVersionData(supabase: SupabaseClient<Database>, appid: string, bundle: string) {
|
|
89
|
+
const { data: versionData, error: versionIdError } = await supabase
|
|
90
|
+
.from('app_versions')
|
|
91
|
+
.select()
|
|
92
|
+
.eq('app_id', appid)
|
|
93
|
+
.eq('name', bundle)
|
|
94
|
+
.eq('deleted', false)
|
|
95
|
+
.single()
|
|
96
|
+
if (!versionData || versionIdError) {
|
|
97
|
+
p.log.error(`App Version ${appid}@${bundle} doesn't exist`)
|
|
98
|
+
program.error('')
|
|
99
|
+
}
|
|
100
|
+
return versionData
|
|
101
|
+
}
|