galbe 0.9.1 → 0.10.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/bin/commands/build.ts +12 -2
- package/bin/commands/dev.ts +22 -18
- package/bin/commands/generate/code/openapi.parser.ts +1 -1
- package/bin/commands/generate/spec.ts +4 -2
- package/bin/util.ts +3 -13
- package/package.json +1 -1
- package/src/extras/spec/openapi.serializer.ts +6 -3
- package/src/index.ts +2 -3
- package/src/routes.ts +1 -1
- package/src/server.ts +15 -8
- package/src/types.ts +14 -2
- package/src/util.ts +9 -0
package/bin/commands/build.ts
CHANGED
|
@@ -9,9 +9,16 @@ import { CWD, fmtVal, silentExec } from '../util'
|
|
|
9
9
|
import { Galbe } from '../../src'
|
|
10
10
|
import { defineRoutes, GalbeProxy } from '../../src/routes'
|
|
11
11
|
import { BuildConfig } from 'bun'
|
|
12
|
+
import { existsSync } from 'fs'
|
|
12
13
|
|
|
13
14
|
const createBuildIndex = async (indexPath: string, g: Galbe, buildId: string) => {
|
|
14
15
|
const buildPath = resolve(tmpdir(), buildId)
|
|
16
|
+
const indexDir = dirname(indexPath)
|
|
17
|
+
|
|
18
|
+
let configPath = ''
|
|
19
|
+
if (existsSync(`${indexDir}/galbe.config.ts`)) configPath = `${indexDir}/galbe.config.ts`
|
|
20
|
+
else if (existsSync(`${indexDir}/galbe.config.js`)) configPath = `${indexDir}/galbe.config.js`
|
|
21
|
+
|
|
15
22
|
const routes = new Map<string, { filepath: string, static?: { path: string, root: string } }>()
|
|
16
23
|
let errors: any[] = []
|
|
17
24
|
// Create GalbeProxy here
|
|
@@ -30,6 +37,9 @@ const createBuildIndex = async (indexPath: string, g: Galbe, buildId: string) =>
|
|
|
30
37
|
|
|
31
38
|
let buildIndex =
|
|
32
39
|
`import galbe from '${relative(buildPath, indexPath)}';\n` +
|
|
40
|
+
(configPath ? `import config from '${relative(buildPath, configPath)}';\n` : '') +
|
|
41
|
+
(configPath ? `import {softMerge} from '${relative(buildPath, `${indexDir}/node_modules/galbe/src/util`)}';\n` : '') +
|
|
42
|
+
(configPath ? `let conf = galbe.config;\ngalbe.config = softMerge(config, conf)\n` : '') +
|
|
33
43
|
`${[...routes.values()].map((r, idx) => `import _${idx} from '${relative(buildPath, r.filepath)}'`).join(';\n')}\n` +
|
|
34
44
|
`Bun.env.BUN_ENV = 'production';\n` +
|
|
35
45
|
`Bun.env.GALBE_BUILD = '${buildId}';\n` +
|
|
@@ -92,10 +102,10 @@ export default (cmd: Command) => {
|
|
|
92
102
|
|
|
93
103
|
const buildConfig: BuildConfig = {
|
|
94
104
|
publicPath: `${outPath}/`,
|
|
95
|
-
|
|
105
|
+
sourcemap: 'external',
|
|
106
|
+
...Object.fromEntries(Object.entries(bunfig).filter(([_, v]) => v)),
|
|
96
107
|
entrypoints: [buildIndex],
|
|
97
108
|
outdir: outPath,
|
|
98
|
-
sourcemap: 'external',
|
|
99
109
|
target: 'bun',
|
|
100
110
|
}
|
|
101
111
|
|
package/bin/commands/dev.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { $ } from 'bun'
|
|
2
2
|
import { Command, Option } from 'commander'
|
|
3
|
-
import { resolve } from 'path'
|
|
3
|
+
import { resolve, dirname } from 'path'
|
|
4
4
|
|
|
5
|
-
import { CWD, fmtInterval, fmtVal, instanciateRoutes,
|
|
5
|
+
import { CWD, fmtInterval, fmtVal, instanciateRoutes, watchDir } from '../util'
|
|
6
6
|
import { Galbe } from '../../src'
|
|
7
|
+
import { softMerge } from '../../src/util'
|
|
8
|
+
import { existsSync } from 'fs'
|
|
7
9
|
|
|
8
|
-
const
|
|
10
|
+
const DEFAULT_PORT = 3000
|
|
9
11
|
|
|
10
12
|
export default (cmd: Command) => {
|
|
11
13
|
cmd
|
|
@@ -18,29 +20,29 @@ export default (cmd: Command) => {
|
|
|
18
20
|
console.log(`error: port range must be between ${fmtInterval(1, 65535)}`)
|
|
19
21
|
process.exit(1)
|
|
20
22
|
})
|
|
21
|
-
.default(null, fmtVal(
|
|
23
|
+
.default(null, fmtVal(DEFAULT_PORT))
|
|
22
24
|
)
|
|
23
|
-
.addOption(new Option('-w, --watch
|
|
25
|
+
.addOption(new Option('-w, --watch [dir]', 'watch file changes').default(false, fmtVal(false)))
|
|
24
26
|
.addOption(new Option('-wi, --watchignore <regexp>', 'ignore file changes').default(false, fmtVal(false)))
|
|
25
27
|
.addOption(new Option('-nc, --noclear', "don't clear on file changes").default(false, fmtVal(false)))
|
|
26
|
-
.addOption(
|
|
27
|
-
new Option('-f, --force', 'kills any process running on defined port before strating the server').default(
|
|
28
|
-
false,
|
|
29
|
-
fmtVal(false)
|
|
30
|
-
)
|
|
31
|
-
)
|
|
32
28
|
.action(async (index, props) => {
|
|
33
|
-
const { port, watch, watchignore, noclear
|
|
34
|
-
let watch_dir = typeof watch === 'string' ? watch : CWD
|
|
35
|
-
const clear = !noclear
|
|
29
|
+
const { port, watch, watchignore, noclear } = props
|
|
36
30
|
const indexPath = resolve(CWD, index)
|
|
31
|
+
const indexDir = dirname(indexPath)
|
|
32
|
+
let watch_dir = typeof watch === 'string' ? watch : watch ? indexDir : ''
|
|
33
|
+
const clear = !noclear
|
|
34
|
+
let galbeConfig = {}
|
|
37
35
|
let g: Galbe
|
|
38
36
|
|
|
39
|
-
if (
|
|
37
|
+
if (existsSync(`${indexDir}/galbe.config.ts`)) {
|
|
38
|
+
galbeConfig = (await import(`${indexDir}/galbe.config.ts`)).default
|
|
39
|
+
} else if (existsSync(`${indexDir}/galbe.config.js`)) {
|
|
40
|
+
galbeConfig = (await import(`${indexDir}/galbe.config.js`)).default
|
|
41
|
+
}
|
|
40
42
|
|
|
41
|
-
if (
|
|
43
|
+
if (!Bun.env.BUN_ENV) Bun.env.BUN_ENV = 'development'
|
|
42
44
|
|
|
43
|
-
if (!!
|
|
45
|
+
if (!!watch_dir) {
|
|
44
46
|
await watchDir(
|
|
45
47
|
watch_dir,
|
|
46
48
|
async () => {
|
|
@@ -55,8 +57,10 @@ export default (cmd: Command) => {
|
|
|
55
57
|
)
|
|
56
58
|
}
|
|
57
59
|
|
|
58
|
-
if (!!
|
|
60
|
+
if (!!watch_dir && clear) await $`clear`
|
|
59
61
|
g = (await import(indexPath)).default
|
|
62
|
+
let conf = g.config
|
|
63
|
+
g.config = softMerge(galbeConfig, conf)
|
|
60
64
|
await instanciateRoutes(g)
|
|
61
65
|
await g.listen(port)
|
|
62
66
|
})
|
|
@@ -282,7 +282,7 @@ const parseEndpointDef = (method: string, path: string, def?: OpenAPIV3.Operatio
|
|
|
282
282
|
)
|
|
283
283
|
|
|
284
284
|
let body = ''
|
|
285
|
-
if(!['get', 'options', 'head'].includes(method)){
|
|
285
|
+
if(!['get', 'delete', 'options', 'head'].includes(method)){
|
|
286
286
|
let _rb = def?.requestBody as OpenAPIV3.ReferenceObject
|
|
287
287
|
if (_rb?.$ref) {
|
|
288
288
|
body = unref(` body: %ref:${_rb.$ref}%`, m => {
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { Command, Option } from 'commander'
|
|
2
2
|
import { resolve, relative, extname } from 'path'
|
|
3
3
|
import { dump as ymlDump, load as ymlLoad } from 'js-yaml'
|
|
4
|
-
import { CWD, fmtList, instanciateRoutes, silentExec
|
|
4
|
+
import { CWD, fmtList, instanciateRoutes, silentExec } from '../../util'
|
|
5
5
|
import { Galbe } from '../../../src'
|
|
6
6
|
import { OpenAPISerializer } from '../../../src/extras'
|
|
7
|
+
import { softMerge } from '../../../src/util'
|
|
8
|
+
import { OpenAPIV3 } from 'openapi-types'
|
|
7
9
|
|
|
8
10
|
const specTargets = ['openapi:3.0:json', 'openapi:3.0:yaml']
|
|
9
11
|
const parsePckgAuthoRgx = /^\s*([^<(]*)(?:<([^>]+)>)?\s*(?:\(([^)]*)\))?\s*$/
|
|
@@ -85,7 +87,7 @@ export default (cmd: Command) => {
|
|
|
85
87
|
version: pckg?.version || '0.1.0'
|
|
86
88
|
}
|
|
87
89
|
}
|
|
88
|
-
openapiSpec = softMerge(openapiSpec, baseSpec)
|
|
90
|
+
openapiSpec = softMerge(openapiSpec, baseSpec) as OpenAPIV3.Document
|
|
89
91
|
Bun.write(resolve(CWD, out), tFormat === 'json' ? JSON.stringify(openapiSpec, null, 2) : ymlDump(openapiSpec))
|
|
90
92
|
}
|
|
91
93
|
|
package/bin/util.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { relative } from 'path'
|
|
2
2
|
import { watch } from 'chokidar'
|
|
3
|
-
import { Galbe, Route } from '../src'
|
|
3
|
+
import { Galbe, type Route } from '../src'
|
|
4
4
|
import { logRoute, walkRoutes } from '../src/util'
|
|
5
|
-
import { GalbeProxy, RouteMeta, defineRoutes } from '../src/routes'
|
|
5
|
+
import { GalbeProxy, type RouteMeta, defineRoutes } from '../src/routes'
|
|
6
6
|
|
|
7
7
|
export { default as pckg } from '../package.json'
|
|
8
8
|
|
|
@@ -15,7 +15,7 @@ export const fmtVal = (v: any) => {
|
|
|
15
15
|
if (typeof v === 'number') return `\x1b[36m${v}\x1b[0m`
|
|
16
16
|
return v
|
|
17
17
|
}
|
|
18
|
-
export const fmtList = (l: any) => `[${l.map(v => fmtVal(v)).join(', ')}]`
|
|
18
|
+
export const fmtList = (l: any) => `[${l.map((v:any) => fmtVal(v)).join(', ')}]`
|
|
19
19
|
export const fmtInterval = (a: any, b: any) => `[${fmtVal(a)}-${fmtVal(b)}]`
|
|
20
20
|
|
|
21
21
|
export const silentExec = async (fn: () => any) => {
|
|
@@ -95,16 +95,6 @@ export const instanciateRoutes = async (g: Galbe) => {
|
|
|
95
95
|
console.log('\x1b[1;30m\x1b[32mdone\x1b[0m\n')
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
export const softMerge = (base, override) => {
|
|
99
|
-
for (const key in override) {
|
|
100
|
-
if (override[key] instanceof Object && !(override[key] instanceof Array)) {
|
|
101
|
-
if (!base[key]) Object.assign(base, { [key]: {} })
|
|
102
|
-
softMerge(base[key], override[key])
|
|
103
|
-
} else Object.assign(base, { [key]: override[key] })
|
|
104
|
-
}
|
|
105
|
-
return base
|
|
106
|
-
}
|
|
107
|
-
|
|
108
98
|
export const killPort = async (port: number) => {
|
|
109
99
|
let getProcCmd: string[], killCmd: (port: string) => string[]
|
|
110
100
|
|
package/package.json
CHANGED
|
@@ -240,12 +240,12 @@ export const OpenAPISerializer = async (g: Galbe, version = '3.0.3'): Promise<Op
|
|
|
240
240
|
responses = Object.fromEntries(
|
|
241
241
|
Object.entries(r.schema.response).map(([status, v]) => {
|
|
242
242
|
if(!v) return []
|
|
243
|
-
let s =
|
|
243
|
+
let s = status as keyof typeof HttpStatus | 'default'
|
|
244
244
|
let { schema, isJson } = schemaToOpenapi(v)
|
|
245
245
|
let { type, format } = resolveRef(schema)
|
|
246
246
|
let media = schemaToMedia({ type, format, isJson } as SchemaType)
|
|
247
247
|
let response: OpenAPIV3.ResponseObject = {
|
|
248
|
-
description: v.description || HttpStatus[
|
|
248
|
+
description: v.description || HttpStatus[s as keyof typeof HttpStatus] || 'Response',
|
|
249
249
|
content: { [media]: { schema: schema } }
|
|
250
250
|
}
|
|
251
251
|
if (components.responses && r.schema.response?.[s]?.id) {
|
|
@@ -261,9 +261,12 @@ export const OpenAPISerializer = async (g: Galbe, version = '3.0.3'): Promise<Op
|
|
|
261
261
|
default: { description: HttpStatus[200] }
|
|
262
262
|
}
|
|
263
263
|
}
|
|
264
|
+
let summary = meta?.head.match(/^([^\n]+)/)?.[1]
|
|
265
|
+
console.log('#', r.method, r.path)
|
|
266
|
+
console.log(r.schema.body)
|
|
264
267
|
paths[path][r.method] = {
|
|
265
268
|
tags: tags.length ? tags : undefined,
|
|
266
|
-
summary:
|
|
269
|
+
summary: summary,
|
|
267
270
|
operationId: meta?.operationId,
|
|
268
271
|
parameters: parameters.length ? parameters : undefined,
|
|
269
272
|
requestBody,
|
package/src/index.ts
CHANGED
|
@@ -111,6 +111,8 @@ export const $T = new SchemaType()
|
|
|
111
111
|
|
|
112
112
|
export { RequestError } from './types'
|
|
113
113
|
|
|
114
|
+
export const config = (config: GalbeConfig) => config
|
|
115
|
+
|
|
114
116
|
/**
|
|
115
117
|
* #### Galbe Server
|
|
116
118
|
* Instanciate a Galbe web server
|
|
@@ -136,13 +138,10 @@ export class Galbe {
|
|
|
136
138
|
plugins: GalbePlugin[] = []
|
|
137
139
|
constructor(config?: GalbeConfig) {
|
|
138
140
|
this.config = config ?? {}
|
|
139
|
-
this.config.routes = this.config.routes ?? true
|
|
140
141
|
this.router = new GalbeRouter({
|
|
141
142
|
prefix: this.config?.basePath || '',
|
|
142
143
|
cacheEnabled: this.config?.router?.cacheEnabled
|
|
143
144
|
})
|
|
144
|
-
this.config.requestValidator = config?.requestValidator ?? { enabled: true }
|
|
145
|
-
this.config.responseValidator = config?.responseValidator ?? { enabled: true }
|
|
146
145
|
}
|
|
147
146
|
private add(route: any) {
|
|
148
147
|
this.router.add(route)
|
package/src/routes.ts
CHANGED
|
@@ -234,7 +234,7 @@ export const defineRoutes = async (
|
|
|
234
234
|
options: Pick<GalbeConfig, 'routes'>,
|
|
235
235
|
proxy: GalbeProxy,
|
|
236
236
|
) => {
|
|
237
|
-
const routes = options?.routes === true ? DEFAULT_ROUTE_PATTERN : options?.routes
|
|
237
|
+
const routes = options?.routes === undefined || options?.routes === true ? DEFAULT_ROUTE_PATTERN : options?.routes
|
|
238
238
|
if (!routes) return
|
|
239
239
|
const root = process.cwd()
|
|
240
240
|
if (typeof routes === 'string') {
|
package/src/server.ts
CHANGED
|
@@ -30,6 +30,7 @@ export default async (galbe: Galbe, port?: number, hostname?: string) => {
|
|
|
30
30
|
|
|
31
31
|
const server = Bun.serve({
|
|
32
32
|
port: port || galbe.config?.port || 3000,
|
|
33
|
+
reusePort: galbe?.config?.reusePort,
|
|
33
34
|
hostname: hostname || galbe.config?.hostname || 'localhost',
|
|
34
35
|
tls: galbe.config?.tls,
|
|
35
36
|
|
|
@@ -81,7 +82,7 @@ export default async (galbe: Galbe, port?: number, hostname?: string) => {
|
|
|
81
82
|
context.params = inParams
|
|
82
83
|
|
|
83
84
|
// request validation
|
|
84
|
-
if (galbe.config?.requestValidator?.enabled) {
|
|
85
|
+
if (galbe.config?.requestValidator?.enabled !== false) {
|
|
85
86
|
let errors: RequestError[] = []
|
|
86
87
|
try {
|
|
87
88
|
if (schema?.headers)
|
|
@@ -150,7 +151,7 @@ export default async (galbe: Galbe, port?: number, hostname?: string) => {
|
|
|
150
151
|
|
|
151
152
|
const parsedResponse = responseParser(response, context as Context, schema.response)
|
|
152
153
|
|
|
153
|
-
if (galbe.config?.responseValidator?.enabled && schema.response)
|
|
154
|
+
if (galbe.config?.responseValidator?.enabled !== false && schema.response)
|
|
154
155
|
validateResponse(response, schema.response, parsedResponse.status || 200)
|
|
155
156
|
|
|
156
157
|
for (const p of pluginsCb.afterHandle) {
|
|
@@ -172,14 +173,20 @@ export default async (galbe: Galbe, port?: number, hostname?: string) => {
|
|
|
172
173
|
headers: { 'Content-Type': 'application/json' }
|
|
173
174
|
})
|
|
174
175
|
} else if (error instanceof RequestError) {
|
|
175
|
-
let payload =
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
176
|
+
let payload = error.payload
|
|
177
|
+
let headers = new Headers(error?.headers || {})
|
|
178
|
+
if (!headers.has('content-type')) {
|
|
179
|
+
if (typeof error.payload === 'string') headers.set('content-type', 'text/plain')
|
|
180
|
+
else {
|
|
181
|
+
headers.set('content-type', 'application/json')
|
|
182
|
+
try {
|
|
183
|
+
payload = JSON.stringify(error.payload)
|
|
184
|
+
} catch (err) { }
|
|
185
|
+
}
|
|
186
|
+
}
|
|
180
187
|
return new Response(payload, {
|
|
181
188
|
status: error.status,
|
|
182
|
-
headers
|
|
189
|
+
headers
|
|
183
190
|
})
|
|
184
191
|
} else console.log(error)
|
|
185
192
|
return new Response('"Internal Server Error"', {
|
package/src/types.ts
CHANGED
|
@@ -99,15 +99,25 @@ export type STQuery = Record<string, STQueryValue>
|
|
|
99
99
|
* ```
|
|
100
100
|
*/
|
|
101
101
|
export type GalbeConfig = {
|
|
102
|
+
/** The port number that the server will be listening on. */
|
|
102
103
|
port?: number
|
|
104
|
+
/** Allow to share the same port across processes (Linux only). */
|
|
105
|
+
reusePort?: boolean
|
|
106
|
+
/** The hostname of the server. */
|
|
103
107
|
hostname?: string
|
|
108
|
+
/** The base path is added as a prefix to all the routes created. */
|
|
104
109
|
basePath?: string
|
|
110
|
+
/** Enable or disable TLS support. */
|
|
105
111
|
tls?: TLSOptions
|
|
106
112
|
server?: Exclude<ServeOptions, 'port'> | TLSServeOptions
|
|
113
|
+
/** A Glob Pattern or a list of Glob patterns defining the route files to be analyzed by the Automatic Route Analyzer. */
|
|
107
114
|
routes?: boolean | string | string[]
|
|
108
115
|
router?: { cacheEnabled: boolean }
|
|
116
|
+
/** A property that can be used by plugins to add plugin's specific configuration. */
|
|
109
117
|
plugin?: Record<string, any>
|
|
118
|
+
/** Enable or disable the request schema validation.*/
|
|
110
119
|
requestValidator?: { enabled: boolean }
|
|
120
|
+
/** Enable or disable the response schema validation.*/
|
|
111
121
|
responseValidator?: { enabled: boolean }
|
|
112
122
|
}
|
|
113
123
|
/**
|
|
@@ -245,10 +255,12 @@ export type StaticEndpoint<P extends string = string, T extends string = string>
|
|
|
245
255
|
|
|
246
256
|
export class RequestError {
|
|
247
257
|
status: number
|
|
248
|
-
payload
|
|
249
|
-
|
|
258
|
+
payload?: any
|
|
259
|
+
headers?: Record<string, string>
|
|
260
|
+
constructor(options: { status?: number; payload?: any, headers?: Record<string, string> }) {
|
|
250
261
|
this.status = options.status ?? 500
|
|
251
262
|
this.payload = options.payload
|
|
263
|
+
this.headers = options.headers
|
|
252
264
|
}
|
|
253
265
|
}
|
|
254
266
|
|
package/src/util.ts
CHANGED
|
@@ -12,6 +12,15 @@ const METHOD_COLOR: Record<string, string> = {
|
|
|
12
12
|
}
|
|
13
13
|
const ansiRegex = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g
|
|
14
14
|
|
|
15
|
+
export const softMerge = <T> (base: T, override: T): T => {
|
|
16
|
+
for (const key in override) {
|
|
17
|
+
if (override[key] instanceof Object && !(override[key] instanceof Array)) {
|
|
18
|
+
if (!base[key]) Object.assign(base as any, { [key]: {} })
|
|
19
|
+
softMerge(base[key], override[key])
|
|
20
|
+
} else Object.assign(base as any, { [key]: override[key] })
|
|
21
|
+
}
|
|
22
|
+
return base
|
|
23
|
+
}
|
|
15
24
|
export const logRoute = (
|
|
16
25
|
r: { method: string; path: string, static?: { path: string, root: string } },
|
|
17
26
|
meta?: RouteMeta,
|