flora is a fast and secure runtime that lets you write discord bots for your servers, with a rich TypeScript SDK, without worrying about running infrastructure. [mirror]
1#!/usr/bin/env node
2
3import process from 'node:process'
4
5import { defineCommand, runMain } from 'citty'
6
7import { description, name, version } from '../package.json'
8import { deploy, get, health, list } from './commands/deployments'
9import {
10 createStore,
11 deleteStore,
12 deleteValue,
13 getValue,
14 listKeys,
15 listStores,
16 setValue
17} from './commands/kv'
18import { login } from './commands/login'
19import { logs, streamLogs } from './commands/logs'
20import { loadConfig } from './lib/config'
21import { logger } from './lib/logger'
22import type { CliConfig } from './lib/types'
23
24function positional(args: Record<string, unknown>, index: number): string | undefined {
25 const values = Array.isArray(args._) ? args._ : []
26 const value = values[index]
27 return typeof value === 'string' ? value : undefined
28}
29
30function resolveConfig(args: Record<string, unknown>): CliConfig {
31 const config = loadConfig()
32 const argApiUrl = args['api']
33 const apiUrl =
34 (typeof argApiUrl === 'string' ? argApiUrl : undefined) ?? process.env.FLORA_API_URL
35
36 if (apiUrl) {
37 config.apiUrl = apiUrl
38 }
39
40 return config
41}
42
43const kvCommand = defineCommand({
44 meta: {
45 name: 'kv',
46 description: 'KV store management'
47 },
48 subCommands: {
49 'create-store': defineCommand({
50 args: {
51 api: { type: 'string', required: false, alias: 'a' },
52 guild: { type: 'string', required: false },
53 name: { type: 'string', required: false }
54 },
55 async run({ args }) {
56 const config = resolveConfig(args)
57 await createStore(config, args.guild, args.name)
58 }
59 }),
60 'list-stores': defineCommand({
61 args: {
62 api: { type: 'string', required: false, alias: 'a' },
63 guild: { type: 'string', required: false }
64 },
65 async run({ args }) {
66 const config = resolveConfig(args)
67 await listStores(config, args.guild)
68 }
69 }),
70 'delete-store': defineCommand({
71 args: {
72 api: { type: 'string', required: false, alias: 'a' },
73 guild: { type: 'string', required: false },
74 name: { type: 'string', required: false }
75 },
76 async run({ args }) {
77 const config = resolveConfig(args)
78 await deleteStore(config, args.guild, args.name)
79 }
80 }),
81 set: defineCommand({
82 args: {
83 api: { type: 'string', required: false, alias: 'a' },
84 guild: { type: 'string', required: false },
85 store: { type: 'string', required: false },
86 key: { type: 'string', required: false },
87 expiration: { type: 'string', required: false },
88 metadata: { type: 'string', required: false }
89 },
90 async run({ args }) {
91 const config = resolveConfig(args)
92 const value = positional(args, 0)
93 const expiration = args.expiration ? Number(args.expiration) : undefined
94 await setValue(config, args.guild, args.store, args.key, value, expiration, args.metadata)
95 }
96 }),
97 get: defineCommand({
98 args: {
99 api: { type: 'string', required: false, alias: 'a' },
100 guild: { type: 'string', required: false },
101 store: { type: 'string', required: false }
102 },
103 async run({ args }) {
104 const config = resolveConfig(args)
105 const key = positional(args, 0)
106 await getValue(config, args.guild, args.store, key)
107 }
108 }),
109 delete: defineCommand({
110 args: {
111 api: { type: 'string', required: false, alias: 'a' },
112 guild: { type: 'string', required: false },
113 store: { type: 'string', required: false }
114 },
115 async run({ args }) {
116 const config = resolveConfig(args)
117 const key = positional(args, 0)
118 await deleteValue(config, args.guild, args.store, key)
119 }
120 }),
121 'list-keys': defineCommand({
122 args: {
123 api: { type: 'string', required: false, alias: 'a' },
124 guild: { type: 'string', required: false },
125 store: { type: 'string', required: false },
126 prefix: { type: 'string', required: false },
127 limit: { type: 'string', required: false },
128 cursor: { type: 'string', required: false }
129 },
130 async run({ args }) {
131 const config = resolveConfig(args)
132 await listKeys(
133 config,
134 args.guild,
135 args.store,
136 args.prefix,
137 args.limit ? Number(args.limit) : undefined,
138 args.cursor
139 )
140 }
141 })
142 }
143})
144
145const main = defineCommand({
146 meta: {
147 name,
148 description,
149 version
150 },
151 args: {
152 api: {
153 type: 'string',
154 required: false,
155 alias: 'a'
156 }
157 },
158 subCommands: {
159 deploy: defineCommand({
160 args: {
161 api: { type: 'string', required: false, alias: 'a' },
162 guild: { type: 'string', required: false },
163 root: { type: 'string', required: false }
164 },
165 async run({ args }) {
166 const config = resolveConfig(args)
167 const entry = positional(args, 0)
168 await deploy(config, args.guild, entry, args.root)
169 }
170 }),
171 get: defineCommand({
172 args: {
173 api: { type: 'string', required: false, alias: 'a' },
174 guild: { type: 'string', required: false }
175 },
176 async run({ args }) {
177 const config = resolveConfig(args)
178 await get(config, args.guild)
179 }
180 }),
181 list: defineCommand({
182 args: {
183 api: { type: 'string', required: false, alias: 'a' }
184 },
185 async run({ args }) {
186 const config = resolveConfig(args)
187 await list(config)
188 }
189 }),
190 health: defineCommand({
191 args: {
192 api: { type: 'string', required: false, alias: 'a' }
193 },
194 async run({ args }) {
195 const config = resolveConfig(args)
196 await health(config)
197 }
198 }),
199 login: defineCommand({
200 args: {
201 token: { type: 'string', required: false }
202 },
203 async run({ args }) {
204 const positionalToken = positional(args, 0)
205 await login(args.token ?? positionalToken)
206 }
207 }),
208 logs: defineCommand({
209 args: {
210 api: { type: 'string', required: false, alias: 'a' },
211 guild: { type: 'string', required: false },
212 follow: { type: 'boolean', required: false, alias: 'f' },
213 limit: { type: 'string', required: false, alias: 'n' }
214 },
215 async run({ args }) {
216 const config = resolveConfig(args)
217 const follow = Boolean(args.follow)
218 const limit = args.limit ? Number(args.limit) : 100
219
220 if (follow) {
221 await streamLogs(config, args.guild)
222 return
223 }
224
225 await logs(config, args.guild, limit)
226 }
227 }),
228 kv: kvCommand
229 }
230})
231
232runMain(main).catch((error) => {
233 const message = error instanceof Error ? error.message : String(error)
234 logger.error(message)
235 process.exit(1)
236})