@capgo/cli 0.8.6 → 0.11.0
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/.cz.toml +1 -1
- package/.eslintignore +1 -0
- package/.eslintrc +0 -1
- package/CHANGELOG.md +39 -0
- package/dist/index.js +1 -1
- package/package.json +6 -3
- package/src/bin/add.ts +89 -35
- package/src/bin/delete.ts +132 -31
- package/src/bin/index.ts +9 -1
- package/src/bin/login.ts +23 -0
- package/src/bin/set.ts +58 -31
- package/src/bin/types.d.ts +6 -0
- package/src/bin/types_supabase.ts +2867 -0
- package/src/bin/upload.ts +112 -146
- package/src/bin/utils.ts +100 -5
- package/test/test_headers_rls.ts +26 -0
- package/tsconfig.json +4 -1
package/src/bin/upload.ts
CHANGED
|
@@ -1,75 +1,29 @@
|
|
|
1
|
-
import { loadConfig } from '@capacitor/cli/dist/config';
|
|
2
1
|
import AdmZip from 'adm-zip';
|
|
3
|
-
import axios from 'axios';
|
|
4
|
-
import prettyjson from 'prettyjson';
|
|
5
2
|
import { program } from 'commander';
|
|
3
|
+
import { randomUUID } from 'crypto';
|
|
6
4
|
import cliProgress from 'cli-progress';
|
|
7
|
-
import
|
|
8
|
-
|
|
5
|
+
import {
|
|
6
|
+
host, hostWeb, getConfig, createSupabaseClient,
|
|
7
|
+
updateOrCreateChannel, updateOrCreateVersion, formatError, findSavedKey
|
|
8
|
+
} from './utils';
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
const oneMb = 1048576; // size of one mb in bytes
|
|
12
|
-
const maxMb = 30;
|
|
13
|
-
const alertMb = 25;
|
|
14
|
-
// enum string format
|
|
15
|
-
enum UploadMode {
|
|
16
|
-
uft8 = 'utf8',
|
|
17
|
-
base64 = 'base64',
|
|
18
|
-
hex = 'hex',
|
|
19
|
-
binary = 'binary'
|
|
20
|
-
}
|
|
21
|
-
interface uploadPayload {
|
|
10
|
+
interface Options {
|
|
22
11
|
version: string
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
channel
|
|
26
|
-
|
|
27
|
-
app: string,
|
|
28
|
-
isMultipart: boolean,
|
|
29
|
-
chunk: number,
|
|
30
|
-
totalChunks: number,
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const formatDefault = UploadMode.binary;
|
|
34
|
-
const chuckNumber = (l: number, divider: number) => l < divider ? 1 : Math.floor(l / divider)
|
|
35
|
-
const chuckSize = (l: number, divider: number) => Math.floor(l / chuckNumber(l, divider))
|
|
36
|
-
|
|
37
|
-
const mbConvert = {
|
|
38
|
-
'base64': (l: number) => Math.floor((l * 4) / 3),
|
|
39
|
-
'hex': (l: number) => Math.floor(l * 2),
|
|
40
|
-
'binary': (l: number) => l,
|
|
41
|
-
'utf8': (l: number) => l,
|
|
12
|
+
path: string
|
|
13
|
+
apikey: string
|
|
14
|
+
channel?: string
|
|
15
|
+
external?: string
|
|
42
16
|
}
|
|
43
17
|
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
method: 'POST',
|
|
47
|
-
url: hostUpload,
|
|
48
|
-
data,
|
|
49
|
-
validateStatus: () => true,
|
|
50
|
-
headers: {
|
|
51
|
-
'Content-Type': 'application/json',
|
|
52
|
-
'apikey': apikey,
|
|
53
|
-
authorization: `Bearer ${supaAnon}`
|
|
54
|
-
}
|
|
55
|
-
})
|
|
56
|
-
return res
|
|
57
|
-
}
|
|
18
|
+
const maxMb = 30;
|
|
19
|
+
const alertMb = 25;
|
|
58
20
|
|
|
59
|
-
export const uploadVersion = async (appid, options) => {
|
|
21
|
+
export const uploadVersion = async (appid: string, options: Options) => {
|
|
60
22
|
let { version, path, channel } = options;
|
|
61
|
-
const {
|
|
23
|
+
const { external } = options;
|
|
24
|
+
const apikey = options.apikey || findSavedKey()
|
|
62
25
|
channel = channel || 'dev';
|
|
63
|
-
|
|
64
|
-
let formatType = formatDefault;
|
|
65
|
-
if (format in UploadMode) {
|
|
66
|
-
formatType = format as UploadMode;
|
|
67
|
-
}
|
|
68
|
-
try {
|
|
69
|
-
config = await loadConfig();
|
|
70
|
-
} catch (err) {
|
|
71
|
-
program.error("No capacitor config file found, run `cap init` first");
|
|
72
|
-
}
|
|
26
|
+
const config = await getConfig();
|
|
73
27
|
appid = appid || config?.app?.appId
|
|
74
28
|
version = version || config?.app?.package?.version
|
|
75
29
|
path = path || config?.app?.webDir
|
|
@@ -80,94 +34,106 @@ export const uploadVersion = async (appid, options) => {
|
|
|
80
34
|
program.error("Missing argument, you need to provide a appid and a version and a path, or be in a capacitor project");
|
|
81
35
|
}
|
|
82
36
|
console.log(`Upload ${appid}@${version} started from path "${path}" to Capgo cloud`);
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
37
|
+
|
|
38
|
+
const supabase = createSupabaseClient(apikey)
|
|
39
|
+
const multibar = new cliProgress.MultiBar({
|
|
40
|
+
clearOnComplete: false,
|
|
41
|
+
hideCursor: true
|
|
42
|
+
}, cliProgress.Presets.shades_grey);
|
|
43
|
+
|
|
44
|
+
// add bars
|
|
45
|
+
const b1 = multibar.create(7, 0, {
|
|
46
|
+
format: 'Uploading: [{bar}] {percentage}% | ETA: {eta}s | {value}/{total} Part'
|
|
47
|
+
}, cliProgress.Presets.shades_grey);
|
|
48
|
+
b1.start(7, 0, {
|
|
49
|
+
speed: "N/A"
|
|
50
|
+
});
|
|
51
|
+
// checking if user has access rights before uploading
|
|
52
|
+
const { data: apiAccess, error: apiAccessError } = await supabase
|
|
53
|
+
.rpc('is_allowed_capgkey', { apikey, keymode: ['upload', 'write', 'all'], app_id: appid })
|
|
54
|
+
|
|
55
|
+
if (!apiAccess || apiAccessError) {
|
|
56
|
+
multibar.stop()
|
|
57
|
+
program.error(`Invalid API key or insufisant rights ${formatError(apiAccessError)}`);
|
|
58
|
+
}
|
|
59
|
+
b1.increment();
|
|
60
|
+
|
|
61
|
+
const { data, error: userIdError } = await supabase
|
|
62
|
+
.rpc<string>('get_user_id', { apikey })
|
|
63
|
+
|
|
64
|
+
const userId = data ? data.toString() : '';
|
|
65
|
+
if (!userId || userIdError) {
|
|
66
|
+
multibar.stop()
|
|
67
|
+
program.error(`Cannot verify user ${formatError(userIdError)}`);
|
|
68
|
+
}
|
|
69
|
+
b1.increment();
|
|
70
|
+
|
|
71
|
+
const { data: app, error: dbError0 } = await supabase
|
|
72
|
+
.rpc<string>('exist_app', { appid, apikey })
|
|
73
|
+
if (!app || dbError0) {
|
|
74
|
+
multibar.stop()
|
|
75
|
+
program.error(`Cannot find app ${appid} in your account \n${formatError(dbError0)}`)
|
|
76
|
+
}
|
|
77
|
+
b1.increment();
|
|
78
|
+
|
|
79
|
+
if (!external) {
|
|
80
|
+
const zip = new AdmZip();
|
|
81
|
+
zip.addLocalFolder(path);
|
|
82
|
+
const zipped = zip.toBuffer();
|
|
83
|
+
const mbSize = Math.floor(zipped.byteLength / 1024 / 1024);
|
|
84
|
+
const filePath = `apps/${userId}/${appid}/versions`
|
|
85
|
+
const fileName = randomUUID()
|
|
86
|
+
b1.increment();
|
|
87
|
+
if (mbSize > maxMb) {
|
|
88
|
+
multibar.stop()
|
|
89
|
+
program.error(`The app is too big, the limit is ${maxMb} Mb, your is ${mbSize} Mb`);
|
|
90
|
+
}
|
|
91
|
+
if (mbSize > alertMb) {
|
|
92
|
+
multibar.log(`WARNING !!\nThe app size is ${mbSize} Mb, the limit is ${maxMb} Mb\n`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const { error: upError } = await supabase.storage
|
|
96
|
+
.from(filePath)
|
|
97
|
+
.upload(fileName, zipped, {
|
|
98
|
+
contentType: 'application/zip',
|
|
100
99
|
})
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
} catch (err) {
|
|
105
|
-
if (err.response) {
|
|
106
|
-
program.error(`Network Error \n${prettyjson.render(err.response?.data || "")}`)
|
|
107
|
-
} else {
|
|
108
|
-
program.error(`Network Error \n${prettyjson.render(err || "")}`)
|
|
109
|
-
}
|
|
100
|
+
if (upError) {
|
|
101
|
+
multibar.stop()
|
|
102
|
+
program.error(`Cannot upload ${formatError(upError)}`)
|
|
110
103
|
}
|
|
111
|
-
} else {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
speed: "N/A"
|
|
139
|
-
});
|
|
140
|
-
let fileName
|
|
141
|
-
for (let i = 0; i < chunks.length; i += 1) {
|
|
142
|
-
const res = await sendToBack({
|
|
143
|
-
version,
|
|
144
|
-
appid,
|
|
145
|
-
fileName,
|
|
146
|
-
channel,
|
|
147
|
-
format: formatType,
|
|
148
|
-
app: chunks[i],
|
|
149
|
-
isMultipart: chunks.length > 1,
|
|
150
|
-
chunk: i + 1,
|
|
151
|
-
totalChunks: chunks.length,
|
|
152
|
-
}, apikey)
|
|
153
|
-
if (res.status !== 200) {
|
|
154
|
-
b1.stop();
|
|
155
|
-
program.error(`Server Error \n${prettyjson.render(res?.data || "")}`);
|
|
156
|
-
}
|
|
157
|
-
b1.update(i + 1)
|
|
158
|
-
fileName = res.data.fileName
|
|
159
|
-
}
|
|
160
|
-
b1.stop();
|
|
161
|
-
} catch (err) {
|
|
162
|
-
b1.stop();
|
|
163
|
-
if (err.response) {
|
|
164
|
-
program.error(`Network Error \n${prettyjson.render(err.response?.data || "")}`)
|
|
165
|
-
} else {
|
|
166
|
-
program.error(`Network Error \n${prettyjson.render(err || "")}`)
|
|
167
|
-
}
|
|
104
|
+
} else if (external && !external.startsWith('https://')) {
|
|
105
|
+
multibar.stop()
|
|
106
|
+
program.error(`External link should should start with "https://" current is "${external}"`)
|
|
107
|
+
}
|
|
108
|
+
b1.increment();
|
|
109
|
+
const fileName = randomUUID()
|
|
110
|
+
const { data: versionData, error: dbError } = await updateOrCreateVersion(supabase, {
|
|
111
|
+
bucket_id: external ? undefined : fileName,
|
|
112
|
+
user_id: userId,
|
|
113
|
+
name: version,
|
|
114
|
+
app_id: appid,
|
|
115
|
+
external_url: external,
|
|
116
|
+
}, apikey)
|
|
117
|
+
if (dbError) {
|
|
118
|
+
multibar.stop()
|
|
119
|
+
program.error(`Cannot add version ${formatError(dbError)}`)
|
|
120
|
+
}
|
|
121
|
+
b1.increment();
|
|
122
|
+
if (versionData && versionData.length) {
|
|
123
|
+
const { error: dbError3 } = await updateOrCreateChannel(supabase, {
|
|
124
|
+
name: channel,
|
|
125
|
+
app_id: appid,
|
|
126
|
+
created_by: userId,
|
|
127
|
+
version: versionData[0].id,
|
|
128
|
+
}, apikey)
|
|
129
|
+
if (dbError3) {
|
|
130
|
+
multibar.log('Cannot set version with upload key, use key with more rights for that\n');
|
|
168
131
|
}
|
|
132
|
+
} else {
|
|
133
|
+
multibar.log('Cannot set version with upload key, use key with more rights for that\n');
|
|
169
134
|
}
|
|
170
|
-
|
|
135
|
+
b1.increment();
|
|
136
|
+
multibar.stop()
|
|
171
137
|
console.log("App uploaded to server")
|
|
172
138
|
console.log(`Try it in mobile app: ${host}/app_mobile`)
|
|
173
139
|
console.log(`Or set the channel ${channel} as public here: ${hostWeb}/app/package/${appid}`)
|
package/src/bin/utils.ts
CHANGED
|
@@ -1,12 +1,107 @@
|
|
|
1
|
-
|
|
1
|
+
import { loadConfig } from '@capacitor/cli/dist/config';
|
|
2
|
+
import { program } from 'commander';
|
|
3
|
+
import { createClient, SupabaseClient } from '@supabase/supabase-js'
|
|
4
|
+
import prettyjson from 'prettyjson';
|
|
5
|
+
import fs from 'fs'
|
|
6
|
+
import os from 'os'
|
|
7
|
+
import { definitions } from './types_supabase';
|
|
8
|
+
|
|
2
9
|
export const host = 'https://capgo.app';
|
|
3
10
|
export const hostWeb = 'https://web.capgo.app';
|
|
4
|
-
export const
|
|
11
|
+
// export const hostSupa = 'https://xvwzpoazmxkqosrdewyv.functions.supabase.co';
|
|
12
|
+
export const hostSupa = 'https://aucsybvnhavogdmzwtcw.supabase.co';
|
|
13
|
+
|
|
5
14
|
// For local test purposes
|
|
6
15
|
// export const host = 'http://localhost:3334';
|
|
7
|
-
// export const hostUpload = 'http://localhost:54321/functions/v1/upload';
|
|
8
|
-
/* eslint-enable */
|
|
9
16
|
|
|
10
17
|
/* eslint-disable */
|
|
11
|
-
export const supaAnon = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlhdCI6MTYzNzgwNTAwOSwiZXhwIjoxOTUzMzgxMDA5fQ.8tgID1d4jodPwuo_fz4KHN4o1XKB9fnqyt0_GaJSj-w'
|
|
18
|
+
// export const supaAnon = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlhdCI6MTYzNzgwNTAwOSwiZXhwIjoxOTUzMzgxMDA5fQ.8tgID1d4jodPwuo_fz4KHN4o1XKB9fnqyt0_GaJSj-w'
|
|
19
|
+
export const supaAnon = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImF1Y3N5YnZuaGF2b2dkbXp3dGN3Iiwicm9sZSI6ImFub24iLCJpYXQiOjE2NTM1ODcxNjgsImV4cCI6MTk2OTE2MzE2OH0.8FKKJqiGgoVA3p9GH5wvnbWkWywIxVLqQyZFhupZ7C4'
|
|
12
20
|
/* eslint-enable */
|
|
21
|
+
|
|
22
|
+
export const createSupabaseClient = (apikey: string) => createClient(hostSupa, supaAnon, {
|
|
23
|
+
headers: {
|
|
24
|
+
capgkey: apikey,
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
export const findSavedKey = () => {
|
|
29
|
+
// search for key in home dir
|
|
30
|
+
const userHomeDir = os.homedir();
|
|
31
|
+
let keyPath = `${userHomeDir}/.capgo`;
|
|
32
|
+
if (fs.existsSync(keyPath)) {
|
|
33
|
+
const key = fs.readFileSync(keyPath, 'utf8');
|
|
34
|
+
return key.trim();
|
|
35
|
+
}
|
|
36
|
+
keyPath = `.capgo`;
|
|
37
|
+
if (fs.existsSync(keyPath)) {
|
|
38
|
+
const key = fs.readFileSync(keyPath, 'utf8');
|
|
39
|
+
return key.trim();
|
|
40
|
+
}
|
|
41
|
+
return null
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const formatError = (error: any) => error ? `\n${prettyjson.render(error)}` : ''
|
|
45
|
+
|
|
46
|
+
interface Config {
|
|
47
|
+
app: {
|
|
48
|
+
appId: string;
|
|
49
|
+
appName: string;
|
|
50
|
+
webDir: string;
|
|
51
|
+
package: {
|
|
52
|
+
version: string;
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export const getConfig = async () => {
|
|
57
|
+
let config: Config;
|
|
58
|
+
try {
|
|
59
|
+
config = await loadConfig();
|
|
60
|
+
} catch (err) {
|
|
61
|
+
program.error("No capacitor config file found, run `cap init` first");
|
|
62
|
+
}
|
|
63
|
+
return config;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const updateOrCreateVersion = async (supabase: SupabaseClient, update: Partial<definitions['app_versions']>, apikey: string) => {
|
|
67
|
+
// console.log('updateOrCreateVersion', update, apikey)
|
|
68
|
+
const { data, error } = await supabase
|
|
69
|
+
.rpc<string>('exist_app_versions', { appid: update.app_id, name_version: update.name, apikey })
|
|
70
|
+
if (data && !error) {
|
|
71
|
+
update.deleted = false
|
|
72
|
+
return supabase
|
|
73
|
+
.from<definitions['app_versions']>('app_versions')
|
|
74
|
+
.update(update, { returning: "minimal" })
|
|
75
|
+
.eq('app_id', update.app_id)
|
|
76
|
+
.eq('name', update.name)
|
|
77
|
+
}
|
|
78
|
+
// console.log('create Version', data, error)
|
|
79
|
+
|
|
80
|
+
return supabase
|
|
81
|
+
.from<definitions['app_versions']>('app_versions')
|
|
82
|
+
.insert(update, { returning: "minimal" })
|
|
83
|
+
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export const updateOrCreateChannel = async (supabase: SupabaseClient, update: Partial<definitions['channels']>, apikey: string) => {
|
|
87
|
+
// console.log('updateOrCreateChannel', update)
|
|
88
|
+
if (!update.app_id || !update.name || !update.created_by) {
|
|
89
|
+
console.error('missing app_id, name, or created_by')
|
|
90
|
+
return Promise.reject(new Error('missing app_id, name, or created_by'))
|
|
91
|
+
}
|
|
92
|
+
const { data, error } = await supabase
|
|
93
|
+
.rpc<string>('exist_channel', { appid: update.app_id, name_channel: update.name, apikey })
|
|
94
|
+
if (data && !error) {
|
|
95
|
+
return supabase
|
|
96
|
+
.from<definitions['channels']>('channels')
|
|
97
|
+
.update(update, { returning: "minimal" })
|
|
98
|
+
.eq('app_id', update.app_id)
|
|
99
|
+
.eq('name', update.name)
|
|
100
|
+
.eq('created_by', update.created_by)
|
|
101
|
+
}
|
|
102
|
+
// console.log('create Channel', data, error)
|
|
103
|
+
|
|
104
|
+
return supabase
|
|
105
|
+
.from<definitions['channels']>('channels')
|
|
106
|
+
.insert(update, { returning: "minimal" })
|
|
107
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createClient } from '@supabase/supabase-js'
|
|
2
|
+
|
|
3
|
+
const supaUrl = 'https://aucsybvnhavogdmzwtcw.supabase.co'
|
|
4
|
+
const apikey = '***'
|
|
5
|
+
const anonKey = '***'
|
|
6
|
+
const init = async () => {
|
|
7
|
+
const supabase = createClient(supaUrl, anonKey, {
|
|
8
|
+
headers: {
|
|
9
|
+
capgkey: '***',
|
|
10
|
+
}
|
|
11
|
+
})
|
|
12
|
+
const { data: userId } = await supabase
|
|
13
|
+
.rpc('get_user_id', { apikey })
|
|
14
|
+
console.log('userId', userId)
|
|
15
|
+
const apps = await supabase.from('apps')
|
|
16
|
+
.select()
|
|
17
|
+
.eq('app_id', 'ee.forgr.captime')
|
|
18
|
+
console.log('apps', apps.data)
|
|
19
|
+
// try to find one app
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
init()
|
|
23
|
+
// (is_app_shared(uid(), app_id) OR is_allowed_apikey((((current_setting('request.headers'::text, true))::json ->> 'capgkey'::text))::character varying, '{read}'::key_mode[]))
|
|
24
|
+
// is_allowed_apikey(((current_setting('request.headers'::text, true))::json ->> 'capgkey'::text), '{read}'::key_mode[])
|
|
25
|
+
|
|
26
|
+
// (((current_setting('request.headers'::text, true))::json ->> 'capgkey'::text) = 'dKC9teP4zHh7Lr5ak7hErgYn385C'::text)
|
package/tsconfig.json
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"compilerOptions": {
|
|
3
|
+
"strict": true,
|
|
4
|
+
"forceConsistentCasingInFileNames": true,
|
|
3
5
|
"module": "commonjs",
|
|
4
6
|
"esModuleInterop": true,
|
|
7
|
+
"skipLibCheck": true,
|
|
5
8
|
"resolveJsonModule": true,
|
|
6
9
|
"target": "es6",
|
|
7
10
|
"moduleResolution": "node",
|
|
@@ -11,5 +14,5 @@
|
|
|
11
14
|
"lib": ["es2016", "dom"]
|
|
12
15
|
},
|
|
13
16
|
"include": ["src/**/*"],
|
|
14
|
-
"exclude": ["node_modules", "__tests__", "**/*.spec.ts"]
|
|
17
|
+
"exclude": ["node_modules", "dist", "__tests__", "**/*.spec.ts"]
|
|
15
18
|
}
|