galbe 0.10.1 → 0.12.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/.prettierrc +1 -1
- package/README.md +3 -0
- package/bin/commands/generate/client.ts +58 -30
- package/bin/commands/generate/code/openapi.parser.ts +72 -45
- package/bin/res/cli.template.js +20 -18
- package/bin/res/client.template.ts +52 -26
- package/bin/util.ts +27 -7
- package/docs/cli.md +25 -22
- package/docs/configuration.md +80 -0
- package/docs/context.md +38 -52
- package/docs/error-handler.md +20 -31
- package/docs/getting-started.md +40 -157
- package/docs/handler.md +35 -21
- package/docs/hooks.md +16 -16
- package/docs/plugins.md +50 -57
- package/docs/router.md +2 -2
- package/docs/routes.md +56 -39
- package/docs/schemas.md +115 -69
- package/package.json +1 -1
- package/src/extras/spec/openapi.serializer.ts +83 -56
- package/src/index.ts +16 -11
- package/src/parser.ts +199 -147
- package/src/router.ts +2 -2
- package/src/schema.ts +156 -66
- package/src/server.ts +25 -17
- package/src/types.ts +54 -42
- package/src/util.ts +37 -9
- package/src/validator.ts +23 -19
- package/test/parser.test.ts +375 -272
- package/test/requests.test.ts +242 -176
- package/test/resources/static/chameleon.png +0 -0
- package/test/resources/static/index.html +13 -0
- package/test/resources/static/sub/index.html +13 -0
- package/test/resources/static/sub/other.html +13 -0
- package/test/responses.test.ts +49 -49
- package/test/router.test.ts +14 -0
- package/test/test.utils.ts +4 -2
- package/test/types.test.ts +909 -0
package/.prettierrc
CHANGED
package/README.md
CHANGED
|
@@ -20,6 +20,9 @@ cd app
|
|
|
20
20
|
bun install && bun dev
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
+
The Galbe CLI ships with the package. You can invoke it with `bunx galbe` or
|
|
24
|
+
install it globally using `bun install -g galbe`.
|
|
25
|
+
|
|
23
26
|
## Documentation
|
|
24
27
|
|
|
25
28
|
The detailed documentation is available at [galbe.dev](https://galbe.dev).
|
|
@@ -5,7 +5,7 @@ import { Command, Option } from 'commander'
|
|
|
5
5
|
import { resolve, extname } from 'path'
|
|
6
6
|
import { rm } from 'fs/promises'
|
|
7
7
|
import { transformSync } from '@swc/core'
|
|
8
|
-
import { CWD, fmtList, instanciateRoutes, silentExec } from '../../util'
|
|
8
|
+
import { CWD, fmtList, instanciateRoutes, silentExec, abbreviateVar } from '../../util'
|
|
9
9
|
import { $T, Galbe, GalbeCLICommand, Method, Route, STResponse } from '../../../src'
|
|
10
10
|
import { walkRoutes } from '../../../src/util'
|
|
11
11
|
import { schemaToTypeStr, Optional, STSchema } from '../../../src/schema'
|
|
@@ -36,7 +36,7 @@ export default (cmd: Command) => {
|
|
|
36
36
|
let pckg: any = {}
|
|
37
37
|
try {
|
|
38
38
|
pckg = await Bun.file(resolve(CWD, 'package.json')).json()
|
|
39
|
-
} catch (e) {
|
|
39
|
+
} catch (e) {}
|
|
40
40
|
|
|
41
41
|
let error = null
|
|
42
42
|
Bun.write(Bun.stdout, '💻 \x1b[1;30mBuilding \x1b[36mGalbe\x1b[0m\x1b[1;30m client\x1b[0m')
|
|
@@ -63,7 +63,7 @@ export default (cmd: Command) => {
|
|
|
63
63
|
patch: [],
|
|
64
64
|
delete: [],
|
|
65
65
|
options: [],
|
|
66
|
-
head: []
|
|
66
|
+
head: [],
|
|
67
67
|
}
|
|
68
68
|
const types: Record<string, STResponse> = {}
|
|
69
69
|
let commands: GalbeCLICommand[] = []
|
|
@@ -75,7 +75,7 @@ export default (cmd: Command) => {
|
|
|
75
75
|
walkRoutes(g.router.routes, r => {
|
|
76
76
|
let meta = metaRoutes?.[r.path]?.[r.method]
|
|
77
77
|
let [_, summary, description] = meta?.head?.match(/^([^\n]*)\n\n(.*)/) || []
|
|
78
|
-
if(!summary) description = meta?.head
|
|
78
|
+
if (!summary) description = meta?.head
|
|
79
79
|
let route = {
|
|
80
80
|
...r,
|
|
81
81
|
...(meta?.operationId ? { alias: meta?.operationId } : {}),
|
|
@@ -89,36 +89,51 @@ export default (cmd: Command) => {
|
|
|
89
89
|
{
|
|
90
90
|
...(r.schema?.params?.[m?.[1]]
|
|
91
91
|
? {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
: { type: 'string' })
|
|
98
|
-
}
|
|
92
|
+
type: schemaToTypeStr(r.schema.params[m[1]]),
|
|
93
|
+
...(r.schema.params[m[1]]?.description
|
|
94
|
+
? { description: r.schema.params[m[1]].description as string }
|
|
95
|
+
: {}),
|
|
96
|
+
}
|
|
97
|
+
: { type: 'string' }),
|
|
98
|
+
},
|
|
99
99
|
])
|
|
100
100
|
) || {},
|
|
101
|
+
contentTypes: Object.keys(r.schema.body || { default: '' })
|
|
102
|
+
.map(s => `'${s}'`)
|
|
103
|
+
.join('|'),
|
|
101
104
|
schemas: {
|
|
102
105
|
...(r.schema.headers ? { headers: schemaToTypeStr($T.object(r.schema.headers)) } : {}),
|
|
103
106
|
...(r.schema.query ? { query: schemaToTypeStr($T.object(r.schema.query)) } : {}),
|
|
104
|
-
...(r.schema.body
|
|
107
|
+
...(r.schema.body
|
|
108
|
+
? {
|
|
109
|
+
body: `{${Object.entries(r.schema.body)
|
|
110
|
+
.map(([ct, s]) => `${ct}: ${schemaToTypeStr(s)}`)
|
|
111
|
+
.join(';')}}`,
|
|
112
|
+
}
|
|
113
|
+
: {}),
|
|
105
114
|
...(r.schema.response
|
|
106
115
|
? {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
116
|
+
response: Object.fromEntries(
|
|
117
|
+
Object.entries(r.schema.response).map(([k, v]) => [
|
|
118
|
+
k === 'default' ? '"default"' : k,
|
|
119
|
+
schemaToTypeStr(v as STSchema),
|
|
120
|
+
])
|
|
121
|
+
),
|
|
122
|
+
}
|
|
123
|
+
: {}),
|
|
124
|
+
},
|
|
113
125
|
}
|
|
114
|
-
Object.values(r.schema.response || {})
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
126
|
+
Object.values(r.schema.response || {})
|
|
127
|
+
.filter(s => s?.id)
|
|
128
|
+
.forEach(s => {
|
|
129
|
+
//@ts-ignore
|
|
130
|
+
types[s.id] = schemaToTypeStr(s)
|
|
131
|
+
})
|
|
118
132
|
routes[r.method.toLocaleLowerCase()].push(route)
|
|
119
133
|
if (target === 'cli' && meta?.operationId)
|
|
120
134
|
commands.push({
|
|
121
135
|
name: meta.operationId,
|
|
136
|
+
tags: meta?.tags ? (Array.isArray(meta.tags) ? meta.tags : [meta.tags]) : [],
|
|
122
137
|
description: route.summary || route.description,
|
|
123
138
|
route,
|
|
124
139
|
arguments:
|
|
@@ -127,7 +142,7 @@ export default (cmd: Command) => {
|
|
|
127
142
|
return {
|
|
128
143
|
name: k,
|
|
129
144
|
type: p.type === 'boolean' ? '' : `<${p.type}>`,
|
|
130
|
-
description: p?.description || ''
|
|
145
|
+
description: p?.description || '',
|
|
131
146
|
}
|
|
132
147
|
}
|
|
133
148
|
) || [],
|
|
@@ -136,12 +151,12 @@ export default (cmd: Command) => {
|
|
|
136
151
|
let type = schemaToTypeStr({ ...o, [Optional]: false })
|
|
137
152
|
return {
|
|
138
153
|
name: k,
|
|
139
|
-
short: k
|
|
154
|
+
short: abbreviateVar(k),
|
|
140
155
|
type: type === 'boolean' ? '' : `<${type}>`,
|
|
141
156
|
description: o?.description || '',
|
|
142
|
-
default: o.default
|
|
157
|
+
default: o.default,
|
|
143
158
|
}
|
|
144
|
-
}) || []
|
|
159
|
+
}) || [],
|
|
145
160
|
})
|
|
146
161
|
})
|
|
147
162
|
|
|
@@ -167,11 +182,11 @@ export default (cmd: Command) => {
|
|
|
167
182
|
filled = transformSync(filled, {
|
|
168
183
|
jsc: {
|
|
169
184
|
parser: {
|
|
170
|
-
syntax: 'typescript'
|
|
185
|
+
syntax: 'typescript',
|
|
171
186
|
},
|
|
172
187
|
preserveAllComments: true,
|
|
173
|
-
target: 'esnext'
|
|
174
|
-
}
|
|
188
|
+
target: 'esnext',
|
|
189
|
+
},
|
|
175
190
|
}).code
|
|
176
191
|
}
|
|
177
192
|
|
|
@@ -182,6 +197,19 @@ export default (cmd: Command) => {
|
|
|
182
197
|
// Plugin CLI hook
|
|
183
198
|
if (commands) for (let p of g.plugins) if (p.cli) await p.cli(commands)
|
|
184
199
|
|
|
200
|
+
const tags = commands.reduce(
|
|
201
|
+
(p, c) => {
|
|
202
|
+
if (c.tags.length)
|
|
203
|
+
for (const t of c.tags) {
|
|
204
|
+
if (!(t in p)) p[t] = []
|
|
205
|
+
p[t].push(c)
|
|
206
|
+
}
|
|
207
|
+
else p[''].push(c)
|
|
208
|
+
return p
|
|
209
|
+
},
|
|
210
|
+
{ '': [] as GalbeCLICommand[] }
|
|
211
|
+
)
|
|
212
|
+
|
|
185
213
|
let filled = file.replaceAll(/\/\*\%([\s\S]*?)\%\*\//g, (_match, p) => {
|
|
186
214
|
let idt = p.match(/^\n*([ \t]*)/, p)?.[1] || ''
|
|
187
215
|
const script = new Script(p)
|
|
@@ -190,7 +218,7 @@ export default (cmd: Command) => {
|
|
|
190
218
|
name: pckg?.name || 'Galbe app CLI',
|
|
191
219
|
description: pckg?.description || '',
|
|
192
220
|
version: pckg?.version || '0.1.0',
|
|
193
|
-
|
|
221
|
+
tags,
|
|
194
222
|
}
|
|
195
223
|
createContext(sandbox)
|
|
196
224
|
let res = script.runInNewContext(sandbox)
|
|
@@ -3,6 +3,7 @@ import { transformSync } from '@swc/core'
|
|
|
3
3
|
import { resolve, relative, dirname } from 'path'
|
|
4
4
|
import { load as ymlLoad } from 'js-yaml'
|
|
5
5
|
import { OpenAPIV3 } from 'openapi-types'
|
|
6
|
+
import { inferBodyType } from '../../../../src/util'
|
|
6
7
|
|
|
7
8
|
type SchemaEntry = {
|
|
8
9
|
key: string
|
|
@@ -72,7 +73,7 @@ const serialize = (obj: any) => {
|
|
|
72
73
|
return JSON.stringify(obj, (k, value) => {
|
|
73
74
|
if (k === 'pattern' && value) return `/${value}/`
|
|
74
75
|
return value
|
|
75
|
-
}).replace(/"\/(.*)\/([gimsuy]*)"/g, '/$1/$2')
|
|
76
|
+
}).replace(/"\/(.*)\/([gimsuy]*)"/g, '/$1/$2')
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
const writeCodeFile = async (path: string, content: string, target: 'js' | 'ts') => {
|
|
@@ -80,11 +81,11 @@ const writeCodeFile = async (path: string, content: string, target: 'js' | 'ts')
|
|
|
80
81
|
content = transformSync(content, {
|
|
81
82
|
jsc: {
|
|
82
83
|
parser: {
|
|
83
|
-
syntax: 'typescript'
|
|
84
|
+
syntax: 'typescript',
|
|
84
85
|
},
|
|
85
86
|
preserveAllComments: true,
|
|
86
|
-
target: 'esnext'
|
|
87
|
-
}
|
|
87
|
+
target: 'esnext',
|
|
88
|
+
},
|
|
88
89
|
}).code
|
|
89
90
|
}
|
|
90
91
|
await Bun.write(`${path}.${target}`, content)
|
|
@@ -99,7 +100,8 @@ const parseOapiSchema = (
|
|
|
99
100
|
return `$T.any(${details && Object.keys(details).length ? JSON.stringify(details) : ''})`
|
|
100
101
|
}
|
|
101
102
|
//@ts-ignore
|
|
102
|
-
|
|
103
|
+
const ref: string | undefined = os?.$ref
|
|
104
|
+
if (ref) return `%ref:${ref}%`
|
|
103
105
|
os = os as OpenAPIV3.SchemaObject
|
|
104
106
|
let options: typeof details & {
|
|
105
107
|
min?: number
|
|
@@ -115,30 +117,51 @@ const parseOapiSchema = (
|
|
|
115
117
|
} = {
|
|
116
118
|
...details,
|
|
117
119
|
title: os.title,
|
|
118
|
-
description: details.description || os.description
|
|
120
|
+
description: details.description || os.description,
|
|
119
121
|
}
|
|
120
|
-
|
|
122
|
+
|
|
121
123
|
let resp = ''
|
|
122
124
|
let hasOptions = Object.values(options).some(v => !!v)
|
|
123
125
|
let optArg = hasOptions ? serialize(options) : ''
|
|
124
|
-
let anyOf = os.oneOf || os.anyOf
|
|
126
|
+
let anyOf = os.oneOf || os.anyOf
|
|
127
|
+
let allOf = os.allOf
|
|
125
128
|
let required = os.required
|
|
126
129
|
let nullable = os.nullable
|
|
127
130
|
|
|
128
|
-
if (
|
|
131
|
+
if (os.discriminator) {
|
|
132
|
+
const propName = os.discriminator.propertyName
|
|
133
|
+
const mapEntries = Object.entries(os.discriminator.mapping || {})
|
|
134
|
+
resp = `$T.union([${mapEntries
|
|
135
|
+
.map(
|
|
136
|
+
([k, s]) =>
|
|
137
|
+
`$T.intersection([$T.object({\"${propName}\":$T.literal(\"${k}\")}),${parseOapiSchema({
|
|
138
|
+
$ref: s,
|
|
139
|
+
})}])`
|
|
140
|
+
)
|
|
141
|
+
.join(',')}], ${serialize(options)})`
|
|
142
|
+
} else if (anyOf?.length) {
|
|
129
143
|
if (anyOf.length === 1) resp = parseOapiSchema(anyOf[0] as OpenAPIV3.SchemaObject, details, extra)
|
|
130
144
|
else {
|
|
131
145
|
resp = `$T.union([${anyOf.map(s => parseOapiSchema(s as OpenAPIV3.SchemaObject)).join(',')}], ${serialize(
|
|
132
146
|
options
|
|
133
147
|
)})`
|
|
134
148
|
}
|
|
149
|
+
} else if (allOf?.length) {
|
|
150
|
+
if (allOf.length === 1) resp = parseOapiSchema(allOf[0] as OpenAPIV3.SchemaObject, details, extra)
|
|
151
|
+
else {
|
|
152
|
+
resp = `$T.intersection([${allOf.map(s => parseOapiSchema(s as OpenAPIV3.SchemaObject)).join(',')}], ${serialize(
|
|
153
|
+
options
|
|
154
|
+
)})`
|
|
155
|
+
}
|
|
156
|
+
} else if (!os?.type) {
|
|
157
|
+
return `$T.any(${details && Object.keys(details).length ? JSON.stringify(details) : ''})`
|
|
135
158
|
} else if (os.type === 'boolean') resp = `$T.boolean(${hasOptions ? serialize(options) : ''})`
|
|
136
159
|
else if (os.type === 'number') {
|
|
137
160
|
let { max, min, exclusiveMax, exclusiveMin } = {
|
|
138
161
|
max: os.maximum !== undefined && !os.exclusiveMaximum ? os.maximum : undefined,
|
|
139
162
|
min: os.minimum !== undefined && !os.exclusiveMinimum ? os.minimum : undefined,
|
|
140
163
|
exclusiveMax: os.maximum !== undefined && os.exclusiveMaximum ? os.maximum : undefined,
|
|
141
|
-
exclusiveMin: os.minimum !== undefined && os.exclusiveMinimum ? os.minimum : undefined
|
|
164
|
+
exclusiveMin: os.minimum !== undefined && os.exclusiveMinimum ? os.minimum : undefined,
|
|
142
165
|
}
|
|
143
166
|
options = { ...options, min, max, exclusiveMax, exclusiveMin }
|
|
144
167
|
hasOptions = Object.values(options).some(v => !!v)
|
|
@@ -169,14 +192,14 @@ const parseOapiSchema = (
|
|
|
169
192
|
resp = `$T.array(${parseOapiSchema(os?.items)}, ${optArg})`
|
|
170
193
|
} else if (os.type === 'object') {
|
|
171
194
|
let props = Object.entries(os?.properties || {})
|
|
172
|
-
|
|
173
|
-
|
|
195
|
+
.map(([k, v]) => `"${k}":${parseOapiSchema(v)}`)
|
|
196
|
+
.join(',')
|
|
174
197
|
if (extra?.media === 'multipart/form-data') {
|
|
175
|
-
resp = `$T.multipartForm({${props}}${optArg ? `, ${optArg}
|
|
198
|
+
resp = `$T.multipartForm({${props}}${optArg ? `, ${optArg}` : ''})`
|
|
176
199
|
} else if (extra?.media === 'application/x-www-form-urlencoded') {
|
|
177
|
-
resp = `$T.
|
|
200
|
+
resp = `$T.object({${props}}${optArg ? `, ${optArg}` : ''})`
|
|
178
201
|
} else {
|
|
179
|
-
resp = `$T.object({${props}}${optArg ? `, ${optArg}
|
|
202
|
+
resp = `$T.object({${props}}${optArg ? `, ${optArg}` : ''})`
|
|
180
203
|
}
|
|
181
204
|
} else throw new Error(`Unknown schema type ${JSON.stringify(os)}`)
|
|
182
205
|
|
|
@@ -195,20 +218,17 @@ const buildSchemaIndex = (def: OpenAPIV3.Document) => {
|
|
|
195
218
|
let dependsOn = new Set<string>()
|
|
196
219
|
if (kind === 'schemas') schema = parseOapiSchema(s, { id: k })
|
|
197
220
|
else if (kind === 'requestBodies' || kind === 'responses') {
|
|
198
|
-
let schemas=[] as string[]
|
|
199
|
-
if(!!s.content){
|
|
200
|
-
|
|
221
|
+
let schemas = [] as string[]
|
|
222
|
+
if (!!s.content) {
|
|
201
223
|
schemas = [
|
|
202
224
|
...new Set(
|
|
203
|
-
Object.entries((s as OpenAPIV3.RequestBodyObject)?.content || {null:{}}).map(([media, v]) => {
|
|
225
|
+
Object.entries((s as OpenAPIV3.RequestBodyObject)?.content || { null: {} }).map(([media, v]) => {
|
|
204
226
|
return parseOapiSchema(v.schema, { id: k }, { media })
|
|
205
227
|
})
|
|
206
|
-
)
|
|
228
|
+
),
|
|
207
229
|
]
|
|
208
230
|
} else {
|
|
209
|
-
schemas = [
|
|
210
|
-
parseOapiSchema(undefined, {id: k, ...s})
|
|
211
|
-
]
|
|
231
|
+
schemas = [parseOapiSchema(undefined, { id: k, ...s })]
|
|
212
232
|
}
|
|
213
233
|
schema = schemas.length <= 0 ? '' : schemas.length === 1 ? schemas[0] : `$T.union([${schemas.join(',')}])`
|
|
214
234
|
}
|
|
@@ -222,7 +242,7 @@ const buildSchemaIndex = (def: OpenAPIV3.Document) => {
|
|
|
222
242
|
prefix: '',
|
|
223
243
|
schema,
|
|
224
244
|
dependsOn,
|
|
225
|
-
usedBy: new Set()
|
|
245
|
+
usedBy: new Set(),
|
|
226
246
|
}
|
|
227
247
|
}
|
|
228
248
|
for (let [k, v] of Object.entries(def.components?.schemas || {})) initSchema(k, v, 'schemas')
|
|
@@ -246,7 +266,7 @@ const parseEndpointDef = (method: string, path: string, def?: OpenAPIV3.Operatio
|
|
|
246
266
|
|
|
247
267
|
let meta = '/**\n'
|
|
248
268
|
if (def.summary) meta += ` * ${def.summary}\n *\n`
|
|
249
|
-
if (def.description) meta += ` * ${def.description.replace(
|
|
269
|
+
if (def.description) meta += ` * ${def.description.replace(/\n/g, '\n * ')}\n`
|
|
250
270
|
if (def.operationId) meta += ` * @operationId ${def.operationId}\n`
|
|
251
271
|
if (def.externalDocs) meta += ` * @externalDocs ${def.externalDocs}\n`
|
|
252
272
|
if (def.tags) meta += ` * @tags ${def.tags.join(' ')}\n`
|
|
@@ -259,9 +279,13 @@ const parseEndpointDef = (method: string, path: string, def?: OpenAPIV3.Operatio
|
|
|
259
279
|
// @ts-ignore: TODO handle refs cases
|
|
260
280
|
if (_p.$ref) continue
|
|
261
281
|
let p = _p as OpenAPIV3.ParameterObject
|
|
262
|
-
let o = (s: string) =>
|
|
282
|
+
let o = (s: string) => {
|
|
283
|
+
const [isOptional, so] = [...(s.match(/^\$T.optional\((.*)\)$/) || [])]
|
|
284
|
+
s = so ?? s
|
|
285
|
+
return p.in !== 'path' && !p.required && !isOptional ? `$T.optional(${s})` : s
|
|
286
|
+
}
|
|
263
287
|
sp[p.in][p.name] = o(
|
|
264
|
-
unref(parseOapiSchema(p.schema, {description: p.description }), m => {
|
|
288
|
+
unref(parseOapiSchema(p.schema, { description: p.description }), m => {
|
|
265
289
|
let l = m.split('/')
|
|
266
290
|
imports[l[l.length - 1]] = m
|
|
267
291
|
return l[l.length - 1]
|
|
@@ -272,7 +296,7 @@ const parseEndpointDef = (method: string, path: string, def?: OpenAPIV3.Operatio
|
|
|
272
296
|
let [schemaParams, schemaQuery, schemaHeaders] = [
|
|
273
297
|
{ g: 'params', o: 'path' },
|
|
274
298
|
{ g: 'query', o: 'query' },
|
|
275
|
-
{ g: 'headers', o: 'header' }
|
|
299
|
+
{ g: 'headers', o: 'header' },
|
|
276
300
|
].map(({ g, o }) =>
|
|
277
301
|
Object.keys(sp[o]).length
|
|
278
302
|
? ` ${g}: {${Object.entries(sp[o])
|
|
@@ -282,7 +306,7 @@ const parseEndpointDef = (method: string, path: string, def?: OpenAPIV3.Operatio
|
|
|
282
306
|
)
|
|
283
307
|
|
|
284
308
|
let body = ''
|
|
285
|
-
if(!['get', 'delete', 'options', 'head'].includes(method)){
|
|
309
|
+
if (!['get', 'delete', 'options', 'head'].includes(method)) {
|
|
286
310
|
let _rb = def?.requestBody as OpenAPIV3.ReferenceObject
|
|
287
311
|
if (_rb?.$ref) {
|
|
288
312
|
body = unref(` body: %ref:${_rb.$ref}%`, m => {
|
|
@@ -295,16 +319,17 @@ const parseEndpointDef = (method: string, path: string, def?: OpenAPIV3.Operatio
|
|
|
295
319
|
let o = (s: string) => (!rb?.required ? `$T.optional(${s})` : s)
|
|
296
320
|
let bs = [
|
|
297
321
|
...new Set(
|
|
298
|
-
Object.entries(rb?.content || {null:{}}).map(([media, v]) =>
|
|
322
|
+
Object.entries(rb?.content || { null: {} }).map(([media, v]) => [
|
|
323
|
+
media,
|
|
299
324
|
unref(parseOapiSchema(v.schema, undefined, { media }), m => {
|
|
300
325
|
let l = m.split('/')
|
|
301
326
|
imports[l[l.length - 1]] = m
|
|
302
327
|
return l[l.length - 1]
|
|
303
|
-
})
|
|
304
|
-
)
|
|
305
|
-
)
|
|
328
|
+
}),
|
|
329
|
+
])
|
|
330
|
+
),
|
|
306
331
|
]
|
|
307
|
-
body = bs.length
|
|
332
|
+
body = bs.length ? ` body: {${bs.map(([k, v]) => `"${inferBodyType(k)}":${o(v)}`).join(',')}}` : ''
|
|
308
333
|
}
|
|
309
334
|
}
|
|
310
335
|
|
|
@@ -316,11 +341,13 @@ const parseEndpointDef = (method: string, path: string, def?: OpenAPIV3.Operatio
|
|
|
316
341
|
let s: string = Number.isInteger(Number(status)) ? status : 'default'
|
|
317
342
|
|
|
318
343
|
//@ts-ignore
|
|
319
|
-
let rootRef = sv?.$ref
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
344
|
+
let rootRef = sv?.$ref
|
|
345
|
+
? unref(parseOapiSchema(sv), m => {
|
|
346
|
+
let l = m.split('/')
|
|
347
|
+
imports[l[l.length - 1]] = m
|
|
348
|
+
return l[l.length - 1]
|
|
349
|
+
})
|
|
350
|
+
: null
|
|
324
351
|
if (rootRef) {
|
|
325
352
|
return [s, [rootRef]]
|
|
326
353
|
}
|
|
@@ -351,12 +378,12 @@ const parseEndpointDef = (method: string, path: string, def?: OpenAPIV3.Operatio
|
|
|
351
378
|
schema: {
|
|
352
379
|
name: schemaName,
|
|
353
380
|
imports,
|
|
354
|
-
def: schema.length ? `{\n${schema.join(',\n')}\n}` : ''
|
|
381
|
+
def: schema.length ? `{\n${schema.join(',\n')}\n}` : '',
|
|
355
382
|
},
|
|
356
383
|
endpoint: {
|
|
357
384
|
meta,
|
|
358
|
-
def: endpoint
|
|
359
|
-
}
|
|
385
|
+
def: endpoint,
|
|
386
|
+
},
|
|
360
387
|
}
|
|
361
388
|
}
|
|
362
389
|
|
|
@@ -383,7 +410,7 @@ const parseEndpoints = (def: OpenAPIV3.Document) => {
|
|
|
383
410
|
scope,
|
|
384
411
|
path,
|
|
385
412
|
schema,
|
|
386
|
-
endpoint
|
|
413
|
+
endpoint,
|
|
387
414
|
}
|
|
388
415
|
}
|
|
389
416
|
}
|
|
@@ -399,7 +426,7 @@ const writeFiles = async (
|
|
|
399
426
|
const typeMap = {
|
|
400
427
|
schemas: 'commons',
|
|
401
428
|
requestBodies: 'requests',
|
|
402
|
-
responses: 'responses'
|
|
429
|
+
responses: 'responses',
|
|
403
430
|
}
|
|
404
431
|
const parseSchemasToFile = (
|
|
405
432
|
schemas: Record<string, SchemaEntry>,
|
|
@@ -436,7 +463,7 @@ const writeFiles = async (
|
|
|
436
463
|
const sMaps = [
|
|
437
464
|
{ g: 'commons', o: 'schemas' },
|
|
438
465
|
{ g: 'requests', o: 'requestBodies' },
|
|
439
|
-
{ g: 'responses', o: 'responses' }
|
|
466
|
+
{ g: 'responses', o: 'responses' },
|
|
440
467
|
] as const
|
|
441
468
|
for (let { g, o } of sMaps) {
|
|
442
469
|
let s = parseSchemasToFile(
|
package/bin/res/cli.template.js
CHANGED
|
@@ -4,7 +4,7 @@ import { program, Option } from 'commander'
|
|
|
4
4
|
import { resolve } from 'path'
|
|
5
5
|
|
|
6
6
|
const DEFAULT_HEADERS = {
|
|
7
|
-
'user-agent': 'Galbe//*%(()=>version)()%*//cli'
|
|
7
|
+
'user-agent': 'Galbe//*%(()=>version)()%*//cli',
|
|
8
8
|
}
|
|
9
9
|
const ansi = (p, c, str) => (p ? `\x1b[${c}m${str}\x1b[0m` : str)
|
|
10
10
|
|
|
@@ -73,9 +73,9 @@ const fetchApi = async (method, path, props) => {
|
|
|
73
73
|
headers: {
|
|
74
74
|
...DEFAULT_HEADERS,
|
|
75
75
|
...(bodyFile ? { 'content-type': 'application/octet-stream' } : {}),
|
|
76
|
-
...(headers || {})
|
|
76
|
+
...(headers || {}),
|
|
77
77
|
},
|
|
78
|
-
...(body ? { body } : {})
|
|
78
|
+
...(body ? { body } : {}),
|
|
79
79
|
})
|
|
80
80
|
let endTime = Bun.nanoseconds() - startTime
|
|
81
81
|
if (format.has('s')) fmtRes(res.status, format.has('p'))
|
|
@@ -99,21 +99,23 @@ const formatDefault = def =>
|
|
|
99
99
|
? `[${def.map(d => formatDefault(d)).join(',')}]`
|
|
100
100
|
: def ?? 'undefined';
|
|
101
101
|
|
|
102
|
-
result =
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
102
|
+
result = Object.entries(tags).map(([tag, commands])=>{
|
|
103
|
+
return (tag?`const _${tag} = program.command('${tag}');\n`:'')+commands.map(c=>{
|
|
104
|
+
let args = c.arguments.map(a=>`.argument("${a.name}", "${JSON.stringify(a.description).slice(1,-1) || a.name+' argument' || ''}")`)
|
|
105
|
+
let optionsBase = [
|
|
106
|
+
{name: '%format', short:'%f', type: '[string]', description: 'response format [\'s\',\'h\',\'b\',\'t\',\'p\']', default:["s","b","p"]},
|
|
107
|
+
{name: '%header', short:'%h', type: '<string...>', description: 'request header formated as headerName=headerValue', default:[]},
|
|
108
|
+
{name: '%query', short:'%q', type: '<string...>', description: 'query param formated as paramName=paramValue', default:[]},
|
|
109
|
+
{name: '%body', short:'%b', type: '<string>', description: 'request body', default:''},
|
|
110
|
+
{name: '%bodyFile', short:'%bf', type: '<path>', description: 'request body file', default:''}
|
|
111
|
+
]
|
|
112
|
+
let options = [...optionsBase,...(c.options||[])].map(o=>`.addOption(new Option("-${o.short}, --${o.name} ${o.type}", "${JSON.stringify(o.description).slice(1,-1)}").default(${formatDefault(o.default)}))`)
|
|
113
|
+
let action = `.action(async (${c.arguments.map(a=>`${a.name},`).join('')} props) => {
|
|
114
|
+
${c.action ? ';('+c.action.toString()+')(props)' : ''}
|
|
115
|
+
return await fetchApi("${c.route.method.toUpperCase()}",\`${c.route.pathT}\`, props)
|
|
116
|
+
})`
|
|
117
|
+
return `${tag?`_${tag}`:'program'}.command("${c.name}").description("${JSON.stringify(c.description).slice(1,-1)}")${args.join('')}${options.join('')}${action}`
|
|
118
|
+
}).join(';\n')
|
|
117
119
|
})
|
|
118
120
|
%*/
|
|
119
121
|
|