Skip to content

Commit 97a6d98

Browse files
Merge pull request #14 from RoboVault/feat/cli-qol-fixes
Feat/cli qol fixes
2 parents 98d9d68 + 875ccf6 commit 97a6d98

23 files changed

+639
-868
lines changed

CHANGELOG.md

+11
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
# v0.4.16
2+
- feat: enhance `arkiver list` command, see `arkiver list --help`
3+
- feat: added 3 new commands to the CLI:
4+
- `arkiver keygen` to generate a new API key
5+
- `arkiver keyrm` to remove an API key
6+
- `arkiver keyls` to list all API keys
7+
- feat: adjusted `arkiver init` command
8+
- feat: Add --gql-only option to arkiver start command to start only the graphQL server
9+
- feat: Change EventHandler and BlockHandler return type to be Promise<void> | void
10+
- fix: bug where deploying while logged out causes issues when trying to login again in the same command
11+
112
# v0.4.15
213
- feat: populate relational entities on graphql endpoint
314
- feat: add overload to `manifest.addChain` and `chain.addContract` functions to pass in a callback function that takes in a DataSourceBuilder and ContractBuilder instance respectively

cli.ts

+26-3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import {
33
Command,
44
deploy,
55
init,
6+
keygen,
7+
keyls,
8+
keyrm,
69
list,
710
login,
811
logout,
@@ -14,7 +17,7 @@ import {
1417
} from './cli/mod.ts'
1518
import 'https://deno.land/std@0.179.0/dotenv/load.ts'
1619

17-
export const version = 'v0.4.15'
20+
export const version = 'v0.4.16'
1821

1922
const command = new Command()
2023
.name('arkiver')
@@ -88,6 +91,7 @@ command
8891
.option('--log-level <logLevel:string>', 'Log level', {
8992
default: 'INFO',
9093
})
94+
.option('--gql-only', 'Only start GraphQL server')
9195
.action(async (opts, ...args) => {
9296
util.logHeader(version)
9397
await checkVersion(version)
@@ -111,11 +115,30 @@ command
111115
// list
112116
command
113117
.command('list', 'List all your arkives')
114-
.action(async () => {
118+
.option('-A, --all', 'List all arkives')
119+
.option('-s, --status <status>', 'Filter by status')
120+
.action(async (opts) => {
115121
await checkVersion(version)
116-
await list.action()
122+
await list.action(opts)
123+
})
124+
125+
// keygen
126+
command
127+
.command('keygen', 'Generate a new API key')
128+
.action(keygen.action)
129+
130+
// keyrm
131+
command
132+
.command('keyrm', 'Delete an API key')
133+
.arguments('<key:string>')
134+
.action(async (_, key) => {
135+
await keyrm.action(key)
117136
})
118137

138+
command
139+
.command('keyls', 'List all API keys')
140+
.action(keyls.action)
141+
119142
if (import.meta.main) {
120143
await command.parse(Deno.args)
121144
}

cli/deploy/cleanup.ts

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import { spinner } from '../spinner.ts'
2+
13
export const cleanup = async (tempPath: string) => {
4+
spinner().text = 'Cleaning up...'
25
await Deno.remove(tempPath, { recursive: true })
36
}

cli/deploy/mod.ts

+31-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
import { parseArkiveManifest } from '../../mod.ts'
2-
import { join, wait } from '../deps.ts'
1+
import { ArkiveManifest, parseArkiveManifest } from '../../mod.ts'
2+
import { join } from '../deps.ts'
3+
import { spinner } from '../spinner.ts'
4+
import { craftEndpoint, getSupabaseClient } from '../utils.ts'
35
import { cleanup } from './cleanup.ts'
46
import { pkg } from './pkg.ts'
57
import { upload } from './upload.ts'
@@ -12,9 +14,8 @@ export const action = async (
1214

1315
if (dev) return deployDev(options, directory)
1416

15-
const spinner = wait('Packaging...').start()
16-
1717
try {
18+
spinner('Deploying...')
1819
// package directory
1920
const { fileName, tempPath } = await pkg(directory)
2021

@@ -25,7 +26,8 @@ export const action = async (
2526
} catch (error) {
2627
throw new Error(`Error importing manifest.ts: ${error.message}`)
2728
}
28-
const manifest = manifestImport.default ?? manifestImport.manifest
29+
const manifest: ArkiveManifest = manifestImport.default ??
30+
manifestImport.manifest
2931
if (!manifest) {
3032
throw new Error(
3133
`Manifest file must export a default or manifest object.`,
@@ -41,18 +43,37 @@ export const action = async (
4143
throw new Error(`Invalid manifest: ${problems}`)
4244
}
4345

44-
spinner.text = 'Uploading package...'
4546
// upload package
4647
await upload(fileName, tempPath, manifest, options)
4748

48-
spinner.text = 'Cleaning up...'
4949
// cleanup
5050
await cleanup(tempPath)
5151

52-
spinner.succeed('Deployed successfully!')
52+
spinner().succeed(`Deployed successfully!`)
53+
54+
const client = getSupabaseClient()
55+
const { data: userData, error: userError } = await client.auth.getUser()
56+
57+
if (userError) {
58+
console.error(userError)
59+
} else {
60+
const { data, error } = await client.from('user_profile').select(
61+
'username',
62+
).eq('id', userData.user.id).single()
63+
64+
if (error) {
65+
console.error(error)
66+
} else {
67+
const endpoint = craftEndpoint({
68+
arkiveName: manifest.name,
69+
environment: options.env ?? 'staging',
70+
username: data.username,
71+
})
72+
console.log(`🚀 Arkive GraphQL endpoint ready at: ${endpoint}`)
73+
}
74+
}
5375
} catch (error) {
54-
spinner.fail('Deployment failed: ' + error.message)
55-
console.error(error)
76+
spinner().fail('Deployment failed: ' + error.message)
5677
}
5778

5879
Deno.exit()

cli/deploy/pkg.ts

+12-19
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,19 @@
11
import { join } from 'https://deno.land/std@0.175.0/path/mod.ts'
2+
import { $ } from '../deps.ts'
3+
import { spinner } from '../spinner.ts'
24

35
export const pkg = async (dir: string) => {
4-
const tempPath = await Deno.makeTempDir()
5-
const fileName = crypto.randomUUID() + '.tar.gz'
6-
const out = join(tempPath, fileName)
6+
spinner().text = 'Packaging...'
7+
try {
8+
const tempPath = await Deno.makeTempDir()
9+
const fileName = crypto.randomUUID() + '.tar.gz'
10+
const out = join(tempPath, fileName)
711

8-
const process = Deno.run({
9-
cmd: ['tar', '-zcvf', out, '-C', dir, '.'],
10-
stdout: 'piped',
11-
stderr: 'piped',
12-
})
12+
await $`tar -zcvf ${out} -C ${dir} .`
1313

14-
const [status, err] = await Promise.all([
15-
process.status(),
16-
process.stderrOutput(),
17-
])
18-
if (status.code !== 0) {
19-
const errMsg = `Failed to build package: ${new TextDecoder().decode(err)}`
20-
throw new Error(errMsg)
14+
return { fileName, tempPath }
15+
} catch (error) {
16+
spinner().fail('Packaging failed: ' + error)
17+
Deno.exit(1)
2118
}
22-
23-
process.close()
24-
25-
return { fileName, tempPath }
2619
}

cli/deploy/upload.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,20 @@ import { getSupabaseClient } from '../utils.ts'
22
import { login } from '../login/mod.ts'
33
import { SUPABASE_FUNCTIONS_URL } from '../constants.ts'
44
import { ArkiveManifest, JSONBigIntReplacer } from '../../mod.ts'
5+
import { spinner } from '../spinner.ts'
56

67
export const upload = async (
78
pkgName: string,
89
tempPath: string,
910
manifest: ArkiveManifest,
1011
options: { public?: true; major?: true; env?: string },
1112
) => {
13+
spinner().text = 'Uploading...'
1214
const supabase = getSupabaseClient()
1315
const sessionRes = await supabase.auth.getSession()
1416

1517
if (!sessionRes.data.session) {
18+
spinner().info('Not logged in, logging in now...')
1619
await login({}, supabase)
1720
}
1821

@@ -57,7 +60,7 @@ export const upload = async (
5760
},
5861
)
5962
if (!res.ok) {
60-
throw new Error(await res.text())
63+
spinner().fail(`Upload failed: ${await res.text()}`)
64+
Deno.exit(1)
6165
}
62-
console.log('Deployed successfully: ', await res.json())
6366
}

cli/deps.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
export { z } from 'https://esm.sh/zod@3.20.2'
2-
export { Command } from 'https://deno.land/x/cliffy@v0.25.7/command/mod.ts'
2+
export { Command } from 'https://deno.land/x/cliffy@v1.0.0-rc.1/command/mod.ts'
33
export {
44
Input,
55
prompt,
66
Secret,
77
Select,
88
Toggle,
9-
} from 'https://deno.land/x/cliffy@v0.25.7/prompt/mod.ts'
9+
} from 'https://deno.land/x/cliffy@v1.0.0-rc.1/prompt/mod.ts'
1010
export {
1111
createClient,
1212
SupabaseClient,
1313
} from 'https://esm.sh/@supabase/supabase-js@2.4.1'
14-
export { wait } from 'https://deno.land/x/wait@0.1.12/mod.ts'
14+
export { Spinner, wait } from 'https://deno.land/x/wait@0.1.13/mod.ts'
1515
export { default as $ } from 'https://deno.land/x/dax@0.31.0/mod.ts'
1616
export { delay } from 'https://deno.land/std@0.179.0/async/mod.ts'
1717
export { join } from 'https://deno.land/std@0.179.0/path/mod.ts'

cli/init/mod.ts

+23-26
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,11 @@ import { $, Input, join, prompt, Select, Toggle } from '../deps.ts'
33
export const action = async () => {
44
let pb = $.progress('Fetching templates...')
55

6-
let templatesRes: Response
7-
8-
await pb.with(async () => {
9-
templatesRes = await fetch(
6+
const templatesRes = await pb.with(() =>
7+
fetch(
108
'https://api.github.com/repos/RoboVault/robo-arkiver/contents/examples',
119
)
12-
})
10+
)
1311

1412
if (!templatesRes!.ok) {
1513
console.log('Error fetching templates: ', templatesRes!.statusText)
@@ -52,8 +50,8 @@ export const action = async () => {
5250
default: templateNames[0].value,
5351
},
5452
{
55-
name: 'vscode',
56-
message: 'Are you using VSCode?',
53+
name: 'git',
54+
message: 'Initialize git repo?',
5755
type: Toggle,
5856
default: true,
5957
},
@@ -74,8 +72,8 @@ export const action = async () => {
7472
)
7573

7674
await $`git remote add origin https://github.com/RoboVault/robo-arkiver && git pull origin main && rm -rf .git`
75+
.quiet('both')
7776
.cwd(newDir)
78-
.quiet('stdout')
7977

8078
// traverse the template directory and move all files to the root
8179
for await (
@@ -88,38 +86,37 @@ export const action = async () => {
8886

8987
await Deno.remove(join(newDir, 'examples'), { recursive: true })
9088

91-
if (arkive.vscode) {
92-
const dir = arkive.dir ?? defaultPath
93-
await Deno.mkdir(join(Deno.cwd(), dir, '.vscode'))
89+
const dir = arkive.dir ?? defaultPath
90+
await Deno.mkdir(join(Deno.cwd(), dir, '.vscode'))
9491

95-
const vscode = `{
92+
const vscode = `{
9693
"deno.enable": true,
9794
"deno.unstable": true
9895
}`
99-
await Deno.writeTextFile(
100-
join(Deno.cwd(), dir, '.vscode', 'settings.json'),
101-
vscode,
102-
)
96+
await Deno.writeTextFile(
97+
join(Deno.cwd(), dir, '.vscode', 'settings.json'),
98+
vscode,
99+
)
103100

104-
const gitignore = `/.vscode
101+
const gitignore = `/.vscode
105102
/.vscode/*
106103
/.vscode/**/*
107104
`
108-
await Deno.writeTextFile(
109-
join(Deno.cwd(), dir, '.gitignore'),
110-
gitignore,
111-
)
112-
}
105+
await Deno.writeTextFile(
106+
join(Deno.cwd(), dir, '.gitignore'),
107+
gitignore,
108+
)
113109

114-
await $`git init && git add . && git commit -m "Initial commit"`
115-
.cwd(newDir)
116-
.quiet('stdout')
110+
if (arkive.git) {
111+
await $`git init && git add . && git commit -m "Initial commit"`
112+
.cwd(newDir)
113+
.quiet('stdout')
114+
}
117115
} catch (e) {
118116
$.logError(`Error initializing arkive: ${e}`)
119117
return
120118
}
121119

122-
// spinner.succeed('Initialized arkive')
123120
pb.finish()
124121
$.logStep('Initialized arkive')
125122
}

cli/key/delete.ts

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { spinner } from '../spinner.ts'
2+
import { getSupabaseClient } from '../utils.ts'
3+
4+
export const action = async (key: string) => {
5+
spinner(`Deleting key ${key}`)
6+
7+
const client = getSupabaseClient()
8+
9+
const { error } = await client.functions.invoke('api-key', {
10+
method: 'DELETE',
11+
body: {
12+
apiKey: key,
13+
},
14+
})
15+
16+
if (error) {
17+
spinner().fail(`Failed to delete key: ${error.message}`)
18+
return
19+
}
20+
21+
spinner().succeed(`Successfully deleted key: ${key}`)
22+
}

cli/key/list.ts

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { spinner } from '../spinner.ts'
2+
import { getSupabaseClient } from '../utils.ts'
3+
4+
export const action = async () => {
5+
spinner('Fetching API keys')
6+
7+
const client = getSupabaseClient()
8+
9+
const { data, error } = await client.functions.invoke<{ api_key: string }[]>(
10+
'api-key',
11+
{
12+
method: 'GET',
13+
},
14+
)
15+
16+
if (error) {
17+
spinner().fail(`Failed to fetch keys: ${error.message}`)
18+
return
19+
}
20+
21+
if (!data) {
22+
spinner().fail(`Failed to fetch keys: no data returned`)
23+
return
24+
}
25+
26+
spinner().succeed(`Successfully fetched keys:`)
27+
28+
console.table(data.map((d) => ({ key: d.api_key })))
29+
}

0 commit comments

Comments
 (0)