galbe 0.13.1 → 0.14.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/README.md +18 -1
- package/bin/commands/build.ts +10 -4
- package/bin/commands/generate/cli/index.ts +156 -0
- package/bin/commands/generate/cli/targets/cac.ts +535 -0
- package/bin/commands/generate/client.ts +32 -109
- package/bin/commands/generate/code/openapi.parser.ts +428 -133
- package/bin/commands/generate/code/route-merge.ts +248 -0
- package/bin/commands/generate/code.ts +110 -23
- package/bin/commands/generate/index.ts +2 -0
- package/package.json +4 -1
- package/src/cookies.ts +87 -0
- package/src/extras/spec/openapi.serializer.ts +236 -101
- package/src/extras.ts +1 -0
- package/src/index.ts +4 -7
- package/src/parser.ts +88 -65
- package/src/router.ts +34 -20
- package/src/routes.ts +14 -10
- package/src/schema.ts +82 -95
- package/src/server.ts +35 -21
- package/src/types.ts +179 -61
- package/src/util.ts +10 -18
- package/src/validator.ts +62 -32
- package/bin/res/cli.template.js +0 -122
package/README.md
CHANGED
|
@@ -12,8 +12,21 @@ Galbe is a fast, lightweight and highly customizable JavaScript web framework ba
|
|
|
12
12
|
> [!IMPORTANT]
|
|
13
13
|
> Galbe is currently under active development and not guaranteed to be stable. Future releases may potentially introduce breaking changes.
|
|
14
14
|
|
|
15
|
+
## Features
|
|
16
|
+
|
|
17
|
+
- **Fast**: Built on top of Bun, Galbe is designed for speed.
|
|
18
|
+
- **Lightweight**: Minimal core with a plugin system to add only what you need.
|
|
19
|
+
- **Type-safe**: Built with TypeScript for a great developer experience, with runtime validation using Galbe Schemas.
|
|
20
|
+
- **Plugins**: Extensible architecture to easily add functionality.
|
|
21
|
+
|
|
22
|
+
## Philosophy
|
|
23
|
+
|
|
24
|
+
Galbe aims to provide a developer-friendly experience without compromising on performance. It embraces the "use the platform" mentality, leveraging modern web standards and the capabilities of the Bun runtime.
|
|
25
|
+
|
|
15
26
|
## Getting started
|
|
16
27
|
|
|
28
|
+
Create a new Galbe app:
|
|
29
|
+
|
|
17
30
|
```bash
|
|
18
31
|
bun create galbe app
|
|
19
32
|
cd app
|
|
@@ -27,6 +40,10 @@ install it globally using `bun install -g galbe`.
|
|
|
27
40
|
|
|
28
41
|
The detailed documentation is available at [galbe.dev](https://galbe.dev).
|
|
29
42
|
|
|
43
|
+
## Community
|
|
44
|
+
|
|
45
|
+
- [GitHub Discussions](https://github.com/pierre-cm/galbe/discussions)
|
|
46
|
+
|
|
30
47
|
## Contributing
|
|
31
48
|
|
|
32
|
-
Please refer to the [Contributing Guide](https://github.com/pierre-cm/galbe/blob/main/docs/CONTRIBUTING.md) to start contributing to Galbe.
|
|
49
|
+
Please refer to the [Contributing Guide](https://github.com/pierre-cm/galbe/blob/main/docs/community/CONTRIBUTING.md) to start contributing to Galbe.
|
package/bin/commands/build.ts
CHANGED
|
@@ -9,10 +9,10 @@ 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
|
+
import { cpSync, existsSync } from 'fs'
|
|
13
13
|
import { softMerge } from '../../src/util'
|
|
14
14
|
|
|
15
|
-
const createBuildIndex = async (indexPath: string, g: Galbe, buildId: string) => {
|
|
15
|
+
const createBuildIndex = async (indexPath: string, g: Galbe, buildId: string, outPath: string) => {
|
|
16
16
|
const buildPath = resolve(tmpdir(), buildId)
|
|
17
17
|
const indexDir = dirname(indexPath)
|
|
18
18
|
|
|
@@ -34,6 +34,13 @@ const createBuildIndex = async (indexPath: string, g: Galbe, buildId: string) =>
|
|
|
34
34
|
await proxy.init()
|
|
35
35
|
|
|
36
36
|
if (errors.length) throw errors
|
|
37
|
+
|
|
38
|
+
// Copy static assets next to the bundle so the runtime can resolve them
|
|
39
|
+
// via static-${BUILD_ID}/<target> at request time.
|
|
40
|
+
for (const { target } of proxy._staticTargets) {
|
|
41
|
+
cpSync(target, `${outPath}/static-${buildId}/${target}`, { recursive: true, dereference: true })
|
|
42
|
+
}
|
|
43
|
+
|
|
37
44
|
await mkdir(buildPath, { recursive: true })
|
|
38
45
|
|
|
39
46
|
let buildIndex =
|
|
@@ -76,7 +83,6 @@ export default (cmd: Command) => {
|
|
|
76
83
|
}
|
|
77
84
|
|
|
78
85
|
Bun.env.GALBE_BUILD = buildID
|
|
79
|
-
Bun.env.GALBE_BUILD_OUT = outPath
|
|
80
86
|
|
|
81
87
|
const bunfig = config ? (await import(resolve(CWD, config)))?.default || {} : {}
|
|
82
88
|
|
|
@@ -101,7 +107,7 @@ export default (cmd: Command) => {
|
|
|
101
107
|
}
|
|
102
108
|
let buildIndex: string = ''
|
|
103
109
|
try {
|
|
104
|
-
buildIndex = await createBuildIndex(index, g, buildID)
|
|
110
|
+
buildIndex = await createBuildIndex(index, g, buildID, outPath)
|
|
105
111
|
} catch (errors) {
|
|
106
112
|
console.log(`\nerror: build errors`)
|
|
107
113
|
for (let error of errors) console.log(error)
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { Command, Option } from 'commander'
|
|
2
|
+
import { resolve } from 'path'
|
|
3
|
+
import { CWD, fmtList, instanciateRoutes, silentExec, abbreviateVar } from '../../../util'
|
|
4
|
+
import { $T, Galbe, GalbeCLICommand, GalbeCLIOptions } from '../../../../src'
|
|
5
|
+
import { walkRoutes } from '../../../../src/util'
|
|
6
|
+
import { schemaToTypeStr, Optional, STSchema } from '../../../../src/schema'
|
|
7
|
+
|
|
8
|
+
const cliTargets = ['cac']
|
|
9
|
+
const cliModes = ['standalone', 'module']
|
|
10
|
+
|
|
11
|
+
export default (cmd: Command) => {
|
|
12
|
+
cmd
|
|
13
|
+
.description('generate a \x1b[1;30m\x1b[36mGalbe\x1b[0m CLI')
|
|
14
|
+
.argument('<index>', 'index file (.ts or .js)')
|
|
15
|
+
.addOption(new Option('-o, --out <file>', 'output file'))
|
|
16
|
+
.addOption(
|
|
17
|
+
new Option('-t, --target <target>', `CLI target ${fmtList(cliTargets)}`).default('cac').argParser(v => {
|
|
18
|
+
if (cliTargets.includes(v)) return v
|
|
19
|
+
console.log(`error: target must be one of ${fmtList(cliTargets)}`)
|
|
20
|
+
process.exit(1)
|
|
21
|
+
})
|
|
22
|
+
)
|
|
23
|
+
.addOption(
|
|
24
|
+
new Option('-m, --mode <mode>', `output mode ${fmtList(cliModes)}`).default('standalone').argParser(v => {
|
|
25
|
+
if (cliModes.includes(v)) return v
|
|
26
|
+
console.log(`error: mode must be one of ${fmtList(cliModes)}`)
|
|
27
|
+
process.exit(1)
|
|
28
|
+
})
|
|
29
|
+
)
|
|
30
|
+
.addOption(new Option('-c, --config <file>', 'config file (.ts or .js)'))
|
|
31
|
+
.action(async (index, props) => {
|
|
32
|
+
let { target, mode, out, config } = props
|
|
33
|
+
|
|
34
|
+
if (!out) out = mode === 'module' ? 'dist/cli.ts' : 'dist/cli'
|
|
35
|
+
|
|
36
|
+
let pckg: any = {}
|
|
37
|
+
try {
|
|
38
|
+
pckg = await Bun.file(resolve(CWD, 'package.json')).json()
|
|
39
|
+
} catch (e) {}
|
|
40
|
+
|
|
41
|
+
let error = null
|
|
42
|
+
Bun.write(Bun.stdout, '💻 \x1b[1;30mBuilding \x1b[36mGalbe\x1b[0m\x1b[1;30m CLI\x1b[0m')
|
|
43
|
+
let g: Galbe = await silentExec(async () => {
|
|
44
|
+
try {
|
|
45
|
+
const g = (await import(resolve(CWD, index))).default
|
|
46
|
+
await instanciateRoutes(g)
|
|
47
|
+
await g.init()
|
|
48
|
+
return g
|
|
49
|
+
} catch (err) {
|
|
50
|
+
error = err
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
if (error) {
|
|
54
|
+
console.log(`\nerror: galbe instance import failed`)
|
|
55
|
+
console.log(error)
|
|
56
|
+
return process.exit(1)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const metaRoutes = g.meta?.reduce(
|
|
60
|
+
(routes, c) => ({ ...routes, ...c.routes }),
|
|
61
|
+
{} as Record<string, Record<string, Record<string, any>>>
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
let commands: GalbeCLICommand[] = []
|
|
65
|
+
|
|
66
|
+
walkRoutes(g.router.routes, r => {
|
|
67
|
+
let meta = metaRoutes?.[r.path]?.[r.method]
|
|
68
|
+
let [_, summary, description] = meta?.head?.match(/^([^\n]*)\n\n(.*)/) || []
|
|
69
|
+
if (!summary) description = meta?.head
|
|
70
|
+
|
|
71
|
+
const name =
|
|
72
|
+
meta?.operationId ||
|
|
73
|
+
`${r.method}-${r.path
|
|
74
|
+
.replace(/\//g, '-')
|
|
75
|
+
.replace(/:/g, '')
|
|
76
|
+
.replace(/^-/, '')
|
|
77
|
+
.replace(/-+/g, '-')
|
|
78
|
+
.replace(/-$/, '')}`
|
|
79
|
+
|
|
80
|
+
const pathT = r.path.replaceAll(/:([^\/]+)/g, '${$1}')
|
|
81
|
+
|
|
82
|
+
const params = Object.fromEntries(
|
|
83
|
+
[...r.path.matchAll(/:([^\/]+)/g)]?.map(m => [
|
|
84
|
+
m?.[1],
|
|
85
|
+
r.schema?.params?.[m?.[1]]
|
|
86
|
+
? {
|
|
87
|
+
type: schemaToTypeStr(r.schema.params[m[1]]),
|
|
88
|
+
...(r.schema.params[m[1]]?.description
|
|
89
|
+
? { description: r.schema.params[m[1]].description as string }
|
|
90
|
+
: {}),
|
|
91
|
+
}
|
|
92
|
+
: { type: 'string' },
|
|
93
|
+
])
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
commands.push({
|
|
97
|
+
name,
|
|
98
|
+
tags: meta?.tags ? (Array.isArray(meta.tags) ? meta.tags : [meta.tags]) : [],
|
|
99
|
+
description: summary || description,
|
|
100
|
+
route: r,
|
|
101
|
+
pathT,
|
|
102
|
+
arguments: Object.entries(
|
|
103
|
+
(params || {}) as Record<string, { type: string; description?: string }>
|
|
104
|
+
).map(([k, p]) => ({
|
|
105
|
+
name: k,
|
|
106
|
+
type: p.type === 'boolean' ? '' : `<${p.type}>`,
|
|
107
|
+
description: p?.description || '',
|
|
108
|
+
})),
|
|
109
|
+
options: Object.entries((r.schema?.query || {}) as Record<string, STSchema>).map(([k, o]) => {
|
|
110
|
+
const type = schemaToTypeStr({ ...o, [Optional]: false })
|
|
111
|
+
return {
|
|
112
|
+
name: k,
|
|
113
|
+
short: abbreviateVar(k),
|
|
114
|
+
type: type === 'boolean' ? '' : `<${type}>`,
|
|
115
|
+
description: (o?.description as string) || '',
|
|
116
|
+
default: o.default,
|
|
117
|
+
}
|
|
118
|
+
}),
|
|
119
|
+
})
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
// Plugin CLI hooks run first
|
|
123
|
+
for (let p of g.plugins) {
|
|
124
|
+
if (p.cli) {
|
|
125
|
+
const result = await p.cli(commands)
|
|
126
|
+
if (Array.isArray(result)) commands = result
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Load user config
|
|
131
|
+
let userTransform: ((commands: GalbeCLICommand[]) => GalbeCLICommand[]) | undefined
|
|
132
|
+
let userOptions: GalbeCLIOptions | undefined
|
|
133
|
+
if (config) {
|
|
134
|
+
try {
|
|
135
|
+
const configModule = await import(resolve(CWD, config))
|
|
136
|
+
userTransform = configModule.transform
|
|
137
|
+
userOptions = configModule.options
|
|
138
|
+
} catch (err) {
|
|
139
|
+
console.log(`\nerror: config file import failed`)
|
|
140
|
+
console.log(err)
|
|
141
|
+
return process.exit(1)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// User transform runs last (user wins)
|
|
146
|
+
if (userTransform) commands = userTransform(commands)
|
|
147
|
+
|
|
148
|
+
if (target === 'cac') {
|
|
149
|
+
const { generate } = await import('./targets/cac')
|
|
150
|
+
await generate({ commands, mode, out, pckg, options: userOptions })
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
Bun.write(Bun.stdout, ' : \x1b[1;30m\x1b[32mdone\x1b[0m\n')
|
|
154
|
+
process.exit(0)
|
|
155
|
+
})
|
|
156
|
+
}
|