@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.
Files changed (57) hide show
  1. package/.github/FUNDING.yml +1 -0
  2. package/.github/workflows/build.yml +46 -0
  3. package/.github/workflows/bump_version.yml +56 -0
  4. package/.github/workflows/test.yml +30 -0
  5. package/.prettierignore +6 -0
  6. package/.vscode/launch.json +23 -0
  7. package/.vscode/settings.json +46 -0
  8. package/.vscode/tasks.json +42 -0
  9. package/CHANGELOG.md +2739 -0
  10. package/build.mjs +23 -0
  11. package/bun.lockb +0 -0
  12. package/capacitor.config.ts +33 -0
  13. package/crypto_explained.png +0 -0
  14. package/dist/index.js +114692 -205
  15. package/eslint.config.js +10 -0
  16. package/package.json +33 -32
  17. package/renovate.json +23 -0
  18. package/src/api/app.ts +55 -0
  19. package/src/api/channels.ts +140 -0
  20. package/src/api/crypto.ts +116 -0
  21. package/src/api/devices_override.ts +41 -0
  22. package/src/api/update.ts +13 -0
  23. package/src/api/versions.ts +101 -0
  24. package/src/app/add.ts +158 -0
  25. package/src/app/debug.ts +222 -0
  26. package/src/app/delete.ts +106 -0
  27. package/src/app/info.ts +90 -0
  28. package/src/app/list.ts +67 -0
  29. package/src/app/set.ts +94 -0
  30. package/src/bundle/check.ts +42 -0
  31. package/src/bundle/cleanup.ts +127 -0
  32. package/src/bundle/compatibility.ts +70 -0
  33. package/src/bundle/decrypt.ts +54 -0
  34. package/src/bundle/delete.ts +53 -0
  35. package/src/bundle/encrypt.ts +60 -0
  36. package/src/bundle/list.ts +43 -0
  37. package/src/bundle/unlink.ts +86 -0
  38. package/src/bundle/upload.ts +532 -0
  39. package/src/bundle/zip.ts +139 -0
  40. package/src/channel/add.ts +74 -0
  41. package/src/channel/currentBundle.ts +72 -0
  42. package/src/channel/delete.ts +52 -0
  43. package/src/channel/list.ts +49 -0
  44. package/src/channel/set.ts +178 -0
  45. package/src/index.ts +307 -0
  46. package/src/init.ts +342 -0
  47. package/src/key.ts +131 -0
  48. package/src/login.ts +70 -0
  49. package/src/types/capacitor__cli.d.ts +6 -0
  50. package/src/types/supabase.types.ts +2193 -0
  51. package/src/user/account.ts +11 -0
  52. package/src/utils.ts +956 -0
  53. package/test/chunk_convert.ts +28 -0
  54. package/test/data.ts +18769 -0
  55. package/test/test_headers_rls.ts +24 -0
  56. package/test/test_semver.ts +13 -0
  57. package/tsconfig.json +39 -0
@@ -0,0 +1,10 @@
1
+ const antfu = require('@antfu/eslint-config').default
2
+
3
+ module.exports = antfu({
4
+ ignores: [
5
+ 'dist',
6
+ 'test',
7
+ 'webpack.config.js',
8
+ 'src/types/types_supabase.ts',
9
+ ],
10
+ })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/cli",
3
- "version": "4.13.7",
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 ncc build",
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 ncc build",
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
- "devDependencies": {
47
- "@antfu/eslint-config": "^2.23.0",
48
- "@bradenmacdonald/s3-lite-client": "npm:@jsr/bradenmacdonald__s3-lite-client",
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.44.4",
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.14.11",
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
- "@types/tmp": "^0.2.6",
62
- "@typescript-eslint/eslint-plugin": "^7.16.1",
63
- "@typescript-eslint/parser": "^7.16.1",
64
- "@vercel/ncc": "^0.38.1",
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.1.1",
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.3"
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
+ }