duto 0.260808.0 → 0.260915.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/LICENSE +2 -2
- package/README.md +35 -13
- package/package.json +54 -13
- package/src/cli.ts +45 -0
- package/src/commands/new.ts +147 -0
- package/src/commands/templates/base/.editorconfig +21 -0
- package/src/commands/templates/base/.env.dev.example +2 -0
- package/src/commands/templates/base/.env.prod.example +2 -0
- package/src/commands/templates/base/README.md +29 -0
- package/src/commands/templates/base/_gitignore +100 -0
- package/src/commands/templates/base/actions/blog/index.get.ts +9 -0
- package/src/commands/templates/base/actions/blog/post/[id].get.ts +9 -0
- package/src/commands/templates/base/actions/index.get.ts +5 -0
- package/src/commands/templates/base/configs/duto.ts +11 -0
- package/src/commands/templates/base/configs/roles.ts +5 -0
- package/src/commands/templates/base/package.json +24 -0
- package/src/commands/templates/base/tsconfig.json +42 -0
- package/src/commands/templates/workerd/wrangler.toml +47 -0
- package/src/commands/upgrade.ts +30 -0
- package/src/enum.ts +2 -0
- package/src/index.d.ts +61 -0
- package/src/index.ts +19 -23
- package/src/middleware.ts +2 -0
- package/src/{cli → pages/cli}/commands/build.ts +13 -7
- package/src/{cli → pages/cli}/commands/dev.ts +11 -4
- package/src/{cli → pages/cli}/commands/preview.ts +6 -4
- package/src/pages/cli/index.ts +10 -0
- package/src/{cli → pages/cli}/utils.ts +4 -5
- package/src/{client → pages/client}/app.tsx +1 -1
- package/src/{client → pages/client}/router.ts +1 -1
- package/src/{compiler → pages/compiler}/file.ts +1 -1
- package/src/{compiler → pages/compiler}/index.ts +2 -4
- package/src/{compiler → pages/compiler}/meta.ts +2 -2
- package/src/{compiler → pages/compiler}/routes.ts +1 -1
- package/src/{compiler → pages/compiler}/types.ts +10 -0
- package/src/{compiler → pages/compiler}/vite-plugin.ts +20 -45
- package/src/{i18n → pages/i18n}/client.ts +2 -2
- package/src/{i18n → pages/i18n}/element.ts +1 -1
- package/src/{i18n → pages/i18n}/server.ts +1 -1
- package/src/pages/index.ts +28 -0
- package/src/{preact → pages/preact}/server.ts +1 -1
- package/src/server/adapter/aws.ts +4 -0
- package/src/server/adapter/workerd.ts +4 -0
- package/src/server/app.ts +91 -0
- package/src/server/auth/ability.ts +72 -0
- package/src/server/auth/authnz.ts +86 -0
- package/src/server/auth/index.ts +5 -0
- package/src/server/auth/token.ts +147 -0
- package/src/server/auth/types.ts +2 -0
- package/src/server/cli/commands/build.ts +41 -0
- package/src/server/cli/commands/deploy.ts +81 -0
- package/src/server/cli/commands/dev.ts +279 -0
- package/src/server/cli/commands/make.ts +98 -0
- package/src/server/cli/commands/migrate.ts +93 -0
- package/src/server/cli/commands/routes.ts +78 -0
- package/src/server/cli/index.ts +27 -0
- package/src/server/cli/stubs.ts +39 -0
- package/src/server/cli/types.ts +3 -0
- package/src/server/cli/utils.ts +437 -0
- package/src/server/config.ts +19 -0
- package/src/server/db/d1.d.ts +1 -0
- package/src/server/db/d1.ts +1 -0
- package/src/server/db/dynamodb.d.ts +1 -0
- package/src/server/db/dynamodb.ts +1 -0
- package/src/server/db/index.ts +1 -0
- package/src/server/entry.ts +19 -0
- package/src/server/handle.ts +30 -0
- package/src/server/middlewares/auth.ts +40 -0
- package/src/server/middlewares/logger.ts +30 -0
- package/src/server/open-api/register.ts +29 -0
- package/src/server/open-api/spec.ts +114 -0
- package/src/server/request.ts +265 -0
- package/src/server/response.ts +142 -0
- package/src/server/routes.ts +473 -0
- package/src/server/types.ts +142 -0
- package/src/server/utils/environment.ts +17 -0
- package/src/server/utils.ts +71 -0
- package/src/server/validator.ts +70 -0
- package/src/utils.ts +15 -0
- package/src/cli/commands/dev-old.ts +0 -213
- package/src/cli/index.ts +0 -28
- package/src/compiler/utils.ts +0 -5
- /package/src/{client → pages/client}/hmr.ts +0 -0
- /package/src/{client → pages/client}/island.ts +0 -0
- /package/src/{client → pages/client}/link.ts +0 -0
- /package/src/{client → pages/client}/storage.ts +0 -0
- /package/src/{compiler → pages/compiler}/aliases.ts +0 -0
- /package/src/{compiler → pages/compiler}/assets.ts +0 -0
- /package/src/{compiler → pages/compiler}/ast.ts +0 -0
- /package/src/{compiler → pages/compiler}/constants.ts +0 -0
- /package/src/{compiler → pages/compiler}/island.ts +0 -0
- /package/src/{components → pages/components}/link.tsx +0 -0
- /package/src/{preact → pages/preact}/client-dev.ts +0 -0
- /package/src/{preact → pages/preact}/client.ts +0 -0
- /package/src/{preact → pages/preact}/context.ts +0 -0
- /package/src/{preact → pages/preact}/signals.ts +0 -0
- /package/src/{preact → pages/preact}/static-html.ts +0 -0
- /package/src/{preact → pages/preact}/types.ts +0 -0
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
import ts from 'typescript'
|
|
2
|
+
import { Miniflare } from 'miniflare'
|
|
3
|
+
import { mkdirSync, existsSync, statSync, readdirSync, rmSync, unlinkSync, copyFileSync, writeFileSync } from 'node:fs'
|
|
4
|
+
import { stat } from 'node:fs/promises'
|
|
5
|
+
import { basename, dirname, join } from 'pathe'
|
|
6
|
+
import { createHash, createHmac } from 'node:crypto'
|
|
7
|
+
|
|
8
|
+
import { findWranglerConfig, parseWranglerConfig, WRANGLER_CONFIG_FILES } from 'localflare-core'
|
|
9
|
+
import type { WranglerConfig, LocalflareManifest } from 'localflare-core'
|
|
10
|
+
|
|
11
|
+
import { gray, bold, italic, purple, yellow, red } from 't0n/color'
|
|
12
|
+
import { substep, event, error, warn } from 't0n/log'
|
|
13
|
+
|
|
14
|
+
import { cacheRoutes } from '$/routes'
|
|
15
|
+
import { _root, _server, filesize } from '@/utils'
|
|
16
|
+
|
|
17
|
+
import type { Platform } from './types'
|
|
18
|
+
|
|
19
|
+
export const platforms = [
|
|
20
|
+
'aws', 'lambda',
|
|
21
|
+
'cf', 'cloudflare', 'worker', 'workers', 'workerd',
|
|
22
|
+
'node', 'bun',
|
|
23
|
+
'vercel',
|
|
24
|
+
] as const
|
|
25
|
+
|
|
26
|
+
export function normalizePlatform(platform: Platform) {
|
|
27
|
+
platform = platform?.toLowerCase() as Platform
|
|
28
|
+
if (!platforms?.includes(platform)) return null
|
|
29
|
+
|
|
30
|
+
switch (platform) {
|
|
31
|
+
case 'lambda':
|
|
32
|
+
return 'aws'
|
|
33
|
+
|
|
34
|
+
case 'cf':
|
|
35
|
+
case 'cloudflare':
|
|
36
|
+
case 'worker':
|
|
37
|
+
case 'workers':
|
|
38
|
+
return 'workerd'
|
|
39
|
+
|
|
40
|
+
case 'bun':
|
|
41
|
+
return 'node'
|
|
42
|
+
|
|
43
|
+
default:
|
|
44
|
+
return platform
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const formatArgs = (...args: any[]) => args.flat().map(a => gray(italic(bold(a)))).join(', ')
|
|
49
|
+
export const platformError = () => error(`Provide a valid platform: ${formatArgs(platforms)}.\n`)
|
|
50
|
+
|
|
51
|
+
const nodeModules = [
|
|
52
|
+
'crypto', 'buffer', 'http', 'fs', 'path', 'events', 'stream', 'util',
|
|
53
|
+
'url', 'querystring', 'os', 'child_process', 'cluster', 'dns', 'net',
|
|
54
|
+
'tls', 'https', 'zlib', 'readline', 'repl', 'vm', 'module', 'assert',
|
|
55
|
+
'timers', 'string_decoder', 'punycode', 'perf_hooks', 'dgram', 'tty',
|
|
56
|
+
'worker_threads', 'wasi', 'process', 'diagnostics_channel', 'sqlite',
|
|
57
|
+
'async_hooks', 'console', 'fsevents',
|
|
58
|
+
].flatMap(lib => ['node:'+ lib, lib])
|
|
59
|
+
|
|
60
|
+
const printer = ts.createPrinter()
|
|
61
|
+
function stripDecorators(source: string) {
|
|
62
|
+
const sourceFile = ts.createSourceFile(
|
|
63
|
+
'tmp.ts', source, ts.ScriptTarget.ESNext, false
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
const transformer: ts.TransformerFactory<ts.SourceFile> = (context) => {
|
|
67
|
+
const visit: ts.Visitor = (node) => {
|
|
68
|
+
if (ts.isClassDeclaration(node) && node.modifiers?.length) {
|
|
69
|
+
let hasDecorator = false
|
|
70
|
+
const modifiers = []
|
|
71
|
+
|
|
72
|
+
for (const m of node.modifiers) {
|
|
73
|
+
if (m.kind === ts.SyntaxKind.Decorator) {
|
|
74
|
+
hasDecorator = true
|
|
75
|
+
continue
|
|
76
|
+
}
|
|
77
|
+
modifiers.push(m)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (hasDecorator) {
|
|
81
|
+
return ts.factory.updateClassDeclaration(
|
|
82
|
+
node,
|
|
83
|
+
modifiers.length ? modifiers : undefined,
|
|
84
|
+
node.name,
|
|
85
|
+
node.typeParameters,
|
|
86
|
+
node.heritageClauses,
|
|
87
|
+
node.members
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return ts.visitEachChild(node, visit, context)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return (node) => ts.visitNode(node, visit) as ts.SourceFile
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const result = ts.transform(sourceFile, [transformer])
|
|
99
|
+
const code = printer.printFile(result.transformed[0])
|
|
100
|
+
|
|
101
|
+
result.dispose()
|
|
102
|
+
return code
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const dist = '.duto'
|
|
106
|
+
export const build = async ({
|
|
107
|
+
env = 'prd',
|
|
108
|
+
platform,
|
|
109
|
+
silent = false,
|
|
110
|
+
minify = true,
|
|
111
|
+
}: {
|
|
112
|
+
env: string,
|
|
113
|
+
platform: Platform,
|
|
114
|
+
silent: boolean,
|
|
115
|
+
minify: boolean,
|
|
116
|
+
}) => {
|
|
117
|
+
const startTime = performance.now()
|
|
118
|
+
const isWorkerd = platform === 'workerd'
|
|
119
|
+
cleanDir(join(_root, dist))
|
|
120
|
+
|
|
121
|
+
if (isWorkerd) {
|
|
122
|
+
for (let file of WRANGLER_CONFIG_FILES) {
|
|
123
|
+
file = join(_root, file)
|
|
124
|
+
if (existsSync(file))
|
|
125
|
+
copyFileSync(file, join(_root, dist, basename(file)))
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (['bun', 'vercel'].includes(platform)) platform = 'workerd'
|
|
130
|
+
|
|
131
|
+
const outdir = join(_root, dist)
|
|
132
|
+
|
|
133
|
+
const USE_STRICT_RE = /(["'`])\s*use strict\s*\1;?/g
|
|
134
|
+
|
|
135
|
+
await cacheRoutes()
|
|
136
|
+
if (!silent) event('Routes cached')
|
|
137
|
+
|
|
138
|
+
const result = await Bun.build({
|
|
139
|
+
entrypoints: [join(_server, 'adapter', `${platform}.ts`)],
|
|
140
|
+
outdir,
|
|
141
|
+
format: 'esm',
|
|
142
|
+
target: 'bun',
|
|
143
|
+
conditions: isWorkerd ? ['worker', 'browser'] : [],
|
|
144
|
+
minify: !!minify,
|
|
145
|
+
|
|
146
|
+
treeShaking: true,
|
|
147
|
+
legalComments: 'none',
|
|
148
|
+
metafile: false,
|
|
149
|
+
external: [
|
|
150
|
+
'@aws-sdk', '@smithy',
|
|
151
|
+
...(isWorkerd ? [
|
|
152
|
+
'cloudflare:workers',
|
|
153
|
+
...nodeModules,
|
|
154
|
+
] : []),
|
|
155
|
+
],
|
|
156
|
+
|
|
157
|
+
define: {
|
|
158
|
+
'process.env.DUTO_ENV': JSON.stringify(env),
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
plugins: [
|
|
162
|
+
{
|
|
163
|
+
name: 'duto',
|
|
164
|
+
setup(build) {
|
|
165
|
+
build.onResolve({ filter: /\.duto[\/\\]/ }, args => ({ path: join(_root, args.path) }))
|
|
166
|
+
build.onLoad({ filter: /\.ts$/ }, async (args) => {
|
|
167
|
+
return { contents: stripDecorators(await Bun.file(args.path).text()), loader: 'ts' }
|
|
168
|
+
})
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
],
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
if (!result.success || !result?.outputs?.length)
|
|
175
|
+
throw new Error('build fail')
|
|
176
|
+
|
|
177
|
+
const output = result.outputs.find(file => file.kind === 'entry-point' && file.path.endsWith('.js'))
|
|
178
|
+
if (!output)
|
|
179
|
+
throw new Error('Build output not found')
|
|
180
|
+
|
|
181
|
+
let code = (await output.text()).replace(/\/\/\s*@bun\s*\n/g, '')
|
|
182
|
+
|
|
183
|
+
if (USE_STRICT_RE.test(code)) {
|
|
184
|
+
code = code.replace(USE_STRICT_RE, '')
|
|
185
|
+
await Bun.write(output.path, code)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const outfile = join(outdir, 'index.js')
|
|
189
|
+
if (output.path !== outfile) {
|
|
190
|
+
await Bun.write(outfile, code)
|
|
191
|
+
|
|
192
|
+
if (existsSync(output.path))
|
|
193
|
+
await Bun.file(output.path).delete()
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (!silent) {
|
|
197
|
+
event(`Build done in ${Math.ceil(performance.now() - startTime)}ms`)
|
|
198
|
+
substep(
|
|
199
|
+
// `${relative(join(_root, 'node_modules/duto/src'), opts.entryPoints[0])} → ${relative(_root, opts.outfile)}`,
|
|
200
|
+
`Size: ${filesize((await stat(outfile)).size)}`,
|
|
201
|
+
`Files: ${Object.keys(result.outputs).length}`
|
|
202
|
+
)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function cleanDir(path: string) {
|
|
207
|
+
try {
|
|
208
|
+
if (!existsSync(path))
|
|
209
|
+
return mkdirSync(path, {recursive: true})
|
|
210
|
+
|
|
211
|
+
const files = readdirSync(path)
|
|
212
|
+
|
|
213
|
+
for (const file of files) {
|
|
214
|
+
const filePath = join(path, file)
|
|
215
|
+
const stat = statSync(filePath)
|
|
216
|
+
|
|
217
|
+
stat.isDirectory()
|
|
218
|
+
? rmSync(filePath, {recursive: true, force: true})
|
|
219
|
+
: unlinkSync(filePath)
|
|
220
|
+
}
|
|
221
|
+
} catch {}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function durableObjectNamespace(name: string, uniqueKey: string) {
|
|
225
|
+
const key = createHash('sha256').update(uniqueKey).digest()
|
|
226
|
+
const nameHmac = createHmac('sha256', key).update(name).digest().subarray(0, 16)
|
|
227
|
+
return Buffer.concat([
|
|
228
|
+
nameHmac,
|
|
229
|
+
createHmac('sha256', key).update(nameHmac).digest().subarray(0, 16)
|
|
230
|
+
]).toString('hex')
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function cleanDB(key: string, config?: WranglerConfig) {
|
|
234
|
+
config ??= wranglerConfig()
|
|
235
|
+
if (!config?.d1_databases) return
|
|
236
|
+
for (const db of config.d1_databases) {
|
|
237
|
+
if (key === db.database_name || key === db.binding) {
|
|
238
|
+
const ns = 'miniflare-D1DatabaseObject'
|
|
239
|
+
const fileName = join(ns, durableObjectNamespace(db.preview_database_id || db.database_id || db.binding, ns) +'.sqlite')
|
|
240
|
+
|
|
241
|
+
for (const file of [
|
|
242
|
+
fileName,
|
|
243
|
+
fileName +'-shm',
|
|
244
|
+
fileName +'-wal',
|
|
245
|
+
]) {
|
|
246
|
+
const filePath = join(d1Path, file)
|
|
247
|
+
if (existsSync(filePath))
|
|
248
|
+
unlinkSync(filePath)
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export function wranglerConfig(file: string | null = null) {
|
|
255
|
+
file ??= findWranglerConfig(_root)
|
|
256
|
+
if (!file) {
|
|
257
|
+
error('Could not find wrangler config file')
|
|
258
|
+
error('Looking for: '+ formatArgs(WRANGLER_CONFIG_FILES))
|
|
259
|
+
process.exit(1)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
try {
|
|
263
|
+
return parseWranglerConfig(file)
|
|
264
|
+
} catch (e) {
|
|
265
|
+
warn(`Could not parse '${file}', using defaults`)
|
|
266
|
+
return {}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export function localflareManifest(opts: WranglerConfig): LocalflareManifest {
|
|
271
|
+
return {
|
|
272
|
+
name: opts.name || 'worker',
|
|
273
|
+
d1: (opts.d1_databases || []).map((db) => ({
|
|
274
|
+
binding: db.binding,
|
|
275
|
+
database_name: db.database_name,
|
|
276
|
+
})),
|
|
277
|
+
kv: (opts.kv_namespaces || []).map((kv) => ({
|
|
278
|
+
binding: kv.binding,
|
|
279
|
+
})),
|
|
280
|
+
r2: (opts.r2_buckets || []).map((r2) => ({
|
|
281
|
+
binding: r2.binding,
|
|
282
|
+
bucket_name: r2.bucket_name,
|
|
283
|
+
})),
|
|
284
|
+
queues: {
|
|
285
|
+
producers: (opts.queues?.producers || []).map((p) => ({
|
|
286
|
+
binding: p.binding,
|
|
287
|
+
queue: p.queue,
|
|
288
|
+
})),
|
|
289
|
+
consumers: (opts.queues?.consumers || []).map((c) => ({
|
|
290
|
+
queue: c.queue,
|
|
291
|
+
max_batch_size: c.max_batch_size,
|
|
292
|
+
max_batch_timeout: c.max_batch_timeout,
|
|
293
|
+
max_retries: c.max_retries,
|
|
294
|
+
dead_letter_queue: c.dead_letter_queue,
|
|
295
|
+
})),
|
|
296
|
+
},
|
|
297
|
+
do: (opts.durable_objects?.bindings || []).map((d) => ({
|
|
298
|
+
binding: d.name,
|
|
299
|
+
className: d.class_name,
|
|
300
|
+
})),
|
|
301
|
+
vars: Object.entries(opts.vars || {}).map(([key, value]) => ({
|
|
302
|
+
key,
|
|
303
|
+
value,
|
|
304
|
+
isSecret: false,
|
|
305
|
+
// isSecret: looksLikeSecret(key, value),
|
|
306
|
+
})),
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const wranglerPath = '.wrangler/state/v3'
|
|
311
|
+
export const kvPath = join(_root, wranglerPath, 'kv')
|
|
312
|
+
export const d1Path = join(_root, wranglerPath, 'd1')
|
|
313
|
+
export const r2Path = join(_root, wranglerPath, 'r2')
|
|
314
|
+
export const doPath = join(_root, wranglerPath, 'durable_objects')
|
|
315
|
+
|
|
316
|
+
export function createMiniflare(opts = {}) {
|
|
317
|
+
const entry = join(_root, opts?.main || opts?.script)
|
|
318
|
+
return new Miniflare({
|
|
319
|
+
host: opts.host || 'localhost',
|
|
320
|
+
port: opts.port || 8787,
|
|
321
|
+
https: opts.https || false,
|
|
322
|
+
httpsKey: opts.httpsKey,
|
|
323
|
+
httpsCert: opts.httpsCert,
|
|
324
|
+
liveReload: opts.liveReload !== false,
|
|
325
|
+
updateCheck: false,
|
|
326
|
+
scriptPath: entry,
|
|
327
|
+
modules: [
|
|
328
|
+
{type: 'ESModule', path: entry},
|
|
329
|
+
],
|
|
330
|
+
compatibilityDate: opts.compatibility_date || '2024-11-01',
|
|
331
|
+
compatibilityFlags: opts.compatibility_flags || [
|
|
332
|
+
'nodejs_compat',
|
|
333
|
+
],
|
|
334
|
+
bindings: {
|
|
335
|
+
...opts.vars,
|
|
336
|
+
},
|
|
337
|
+
|
|
338
|
+
d1Databases: Array.isArray(opts.d1_databases) ? Object.fromEntries(opts.d1_databases.map(db => [db.binding, db.database_id])) : {},
|
|
339
|
+
kvNamespaces: Object.fromEntries((opts.kv_namespaces || []).map(ns => [ns.binding, ns.id])),
|
|
340
|
+
// r2Buckets: Object.fromEntries((opts.r2_buckets || []).map(r2 => [r2.binding, r2.bucket_name])),
|
|
341
|
+
// durableObjects: Object.fromEntries((opts.durable_objects?.bindings || []).map(do => [do.name, do.class_name])),
|
|
342
|
+
|
|
343
|
+
kvPersist: kvPath,
|
|
344
|
+
cachePersist: join(_root, wranglerPath, 'cache'),
|
|
345
|
+
d1Persist: d1Path,
|
|
346
|
+
r2Persist: r2Path,
|
|
347
|
+
durablesPersist: doPath,
|
|
348
|
+
|
|
349
|
+
verbose: false,
|
|
350
|
+
|
|
351
|
+
// Logging
|
|
352
|
+
// log: new console.Console({
|
|
353
|
+
// stdout: process.stdout,
|
|
354
|
+
// stderr: process.stderr,
|
|
355
|
+
// inspectOptions: { depth: 3 }
|
|
356
|
+
// }),
|
|
357
|
+
|
|
358
|
+
cfFetch: false, // disable cf requests
|
|
359
|
+
upstream: opts.upstream || 'https://example.com',
|
|
360
|
+
|
|
361
|
+
sitePath: opts.site?.bucket ?
|
|
362
|
+
join(_root, opts.site.bucket) : undefined,
|
|
363
|
+
siteInclude: opts.site?.include || ['**/*'],
|
|
364
|
+
siteExclude: opts.site?.exclude || [],
|
|
365
|
+
|
|
366
|
+
globalAsyncIO: true,
|
|
367
|
+
globalTimers: true,
|
|
368
|
+
globalRandom: true,
|
|
369
|
+
|
|
370
|
+
inspectorPort: opts.inspectorPort || 9229,
|
|
371
|
+
|
|
372
|
+
cache: true,
|
|
373
|
+
cacheWarnUsage: true,
|
|
374
|
+
})
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
export async function wait(ms: number) {
|
|
378
|
+
return new Promise(r => setTimeout(r, ms))
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
export function getDockerHost() {
|
|
382
|
+
const platform = process.platform
|
|
383
|
+
|
|
384
|
+
if (platform === 'darwin') {
|
|
385
|
+
for (const socket of [
|
|
386
|
+
'/Users/'+ process.env.USER +'/.docker/run/docker.sock',
|
|
387
|
+
'/var/run/docker.sock',
|
|
388
|
+
process.env.DOCKER_HOST
|
|
389
|
+
]) {
|
|
390
|
+
if (socket && existsSync(socket.replace(/^unix:\/\//, '')))
|
|
391
|
+
return socket.includes('://') ? socket : `unix://${socket}`
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return 'tcp://localhost:2375'
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return process.env.DOCKER_HOST || (platform === 'win32' ? 'tcp://localhost:2375' : 'unix:///var/run/docker.sock')
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
export function makeFile(path: string, content: string) {
|
|
401
|
+
const dir = dirname(path)
|
|
402
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true })
|
|
403
|
+
|
|
404
|
+
writeFileSync(path, content)
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
export function hasExt(path: string) {
|
|
408
|
+
const index = path.lastIndexOf('.')
|
|
409
|
+
return index > 0 && index < path.length - 1
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
export const highlightedMethod = (method: string, str?: string | null, siblings: boolean = false): string => {
|
|
413
|
+
const val = str || method
|
|
414
|
+
|
|
415
|
+
switch (method) {
|
|
416
|
+
case 'HEAD':
|
|
417
|
+
case 'OPTIONS':
|
|
418
|
+
case 'CONNECT':
|
|
419
|
+
case 'TRACE':
|
|
420
|
+
return gray(val)
|
|
421
|
+
case 'GET':
|
|
422
|
+
return purple(val) + (siblings ? gray('|') + highlightedMethod('HEAD') : '')
|
|
423
|
+
case 'POST':
|
|
424
|
+
case 'PUT':
|
|
425
|
+
case 'PATCH':
|
|
426
|
+
return yellow(val)
|
|
427
|
+
case 'DELETE':
|
|
428
|
+
return red(val)
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return val
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
export const highlightedURI = (uri: string, method: string) => uri.replace(
|
|
435
|
+
/(?::([a-zA-Z_][a-zA-Z0-9_]*)(\{[^}]+\})?|\*)/g,
|
|
436
|
+
_ => highlightedMethod(method, _)
|
|
437
|
+
)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
|
|
2
|
+
import { get, set } from 't0n'
|
|
3
|
+
|
|
4
|
+
const obj: Record<string, unknown> = {}
|
|
5
|
+
|
|
6
|
+
export function getConfig<T = unknown>(key: string, defaultValue: T): T
|
|
7
|
+
export function getConfig<T = unknown>(key: string, defaultValue?: T): T | undefined
|
|
8
|
+
export function getConfig<T = unknown>(key: string, defaultValue?: T) {
|
|
9
|
+
return get<T>(obj, key, defaultValue)
|
|
10
|
+
}
|
|
11
|
+
export const setConfig = (key: string, val: unknown) => set(obj, key, val)
|
|
12
|
+
export function addConfig<T = unknown>(data: Record<string, T>) {
|
|
13
|
+
Object.assign(obj, data)
|
|
14
|
+
}
|
|
15
|
+
// export const getConfig = <T = unknown>(key: string, defaultValue?: T) => get<T>(obj, key, defaultValue)
|
|
16
|
+
// import { DataBag } from 't0n'
|
|
17
|
+
|
|
18
|
+
// const Config = new DataBag()
|
|
19
|
+
// export default Config
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from 'forj/d1/types'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from 'forj/d1'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from 'forj/dynamodb/types'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from 'forj/dynamodb'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Migration, Schema, Blueprint } from 'forj'
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { getConfig } from '$/config'
|
|
2
|
+
import { registerOpenAPI } from '$/open-api/register'
|
|
3
|
+
import { Ability } from '$/auth'
|
|
4
|
+
import $ from '$/app'
|
|
5
|
+
|
|
6
|
+
// @ts-ignore
|
|
7
|
+
import '.duto/imports.mjs'
|
|
8
|
+
// @ts-ignore
|
|
9
|
+
import { routes, middlewares } from '.duto/imports.mjs'
|
|
10
|
+
|
|
11
|
+
// @ts-ignore
|
|
12
|
+
Ability.fromRoutes(routes)
|
|
13
|
+
Ability.roles = getConfig('roles', {})
|
|
14
|
+
|
|
15
|
+
// @ts-ignore
|
|
16
|
+
const app = $({ routes, middlewares })
|
|
17
|
+
registerOpenAPI(app, getConfig('duto.docs', {}))
|
|
18
|
+
|
|
19
|
+
export { app }
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import res from './response'
|
|
2
|
+
import $v from './validator'
|
|
3
|
+
import { GET_REQUEST } from './request'
|
|
4
|
+
import type { Context, Next, RuleFactory } from './types'
|
|
5
|
+
|
|
6
|
+
export const _mw: Record<string, Function> = {}
|
|
7
|
+
export const getMiddlewares = () => _mw
|
|
8
|
+
export function getMiddleware(id: string | Function): Function {
|
|
9
|
+
if (typeof id === 'function') return id
|
|
10
|
+
const mw = _mw[id] || null
|
|
11
|
+
if (!mw) throw new Error(`Handler ${id} not registered`)
|
|
12
|
+
return mw
|
|
13
|
+
}
|
|
14
|
+
export function registerMiddleware(id: string, handler: Function) {
|
|
15
|
+
_mw[id] = handler
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const resolveMw = (mw: Function) => {
|
|
19
|
+
return mw.length < 3 ? mw : async (c: Context, next: Next) => await getMiddleware(mw)(c.get(GET_REQUEST as unknown as string), res, next, c)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const resolveHandle = (mw: (string | Function)[], handle: Function, ruleFn?: RuleFactory) => {
|
|
23
|
+
const h = async (c: Context) => await handle(c.get(GET_REQUEST as unknown as string), res)
|
|
24
|
+
mw = mw?.length ? mw.map(getMiddleware) : []
|
|
25
|
+
|
|
26
|
+
const rules = typeof ruleFn === 'function' ? ruleFn($v) : null
|
|
27
|
+
if (!rules) return [...mw, h]
|
|
28
|
+
|
|
29
|
+
return [...mw, ...$v.parse(rules), h]
|
|
30
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Ability } from '$/auth'
|
|
2
|
+
import type { Req, Res, Next } from '@/.'
|
|
3
|
+
|
|
4
|
+
import { RegExpRouter } from 'hono/router/reg-exp-router'
|
|
5
|
+
import { SmartRouter } from 'hono/router/smart-router'
|
|
6
|
+
import { TrieRouter } from 'hono/router/trie-router'
|
|
7
|
+
|
|
8
|
+
let used = false
|
|
9
|
+
export const useAuth = () => used
|
|
10
|
+
|
|
11
|
+
const whiteList = new SmartRouter({
|
|
12
|
+
routers: [new RegExpRouter(), new TrieRouter()],
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
export function autorizedPath(path: string, method = 'ALL'){
|
|
16
|
+
return used ? whiteList.match(method, path)[0][0]?.[0] !== undefined : true
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function auth(publicPaths: (string | [string, string])[]) {
|
|
20
|
+
if (!used) {
|
|
21
|
+
used = true
|
|
22
|
+
for (const route of publicPaths) {
|
|
23
|
+
const str = typeof route === 'string'
|
|
24
|
+
const path = str ? route : route[1]
|
|
25
|
+
|
|
26
|
+
whiteList.add(str ? 'ALL' : route[0], path, path)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return async (req: Req, res: Res, next: Next) => {
|
|
31
|
+
if (!autorizedPath(req.path, req.method)) {
|
|
32
|
+
const ability = Ability.fromRequest(req)
|
|
33
|
+
|
|
34
|
+
if (!req?.user || !ability || req.cant(ability))
|
|
35
|
+
return res.unauthorized()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
await next()
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { logger } from 'hono/logger'
|
|
2
|
+
import { matchedRoutes } from 'hono/route'
|
|
3
|
+
import { Datte } from 't0n'
|
|
4
|
+
import { gray } from 't0n/color'
|
|
5
|
+
import type { Req, Res, Context, Next } from '@/.'
|
|
6
|
+
|
|
7
|
+
export const path = '*'
|
|
8
|
+
export async function handle(req: Req, res: Res, next: Next, c: Context) {
|
|
9
|
+
const method = c.req.method
|
|
10
|
+
const route = matchedRoutes(c).find(route => route.method === method)?.path
|
|
11
|
+
const logWithRoute = (args: string[]) => {
|
|
12
|
+
if (!route || !args.length) return args
|
|
13
|
+
return args.map(arg => {
|
|
14
|
+
if (!arg) return arg
|
|
15
|
+
const split = arg?.split(' ')
|
|
16
|
+
if (split.length < 3 || split[2] === route)
|
|
17
|
+
return arg
|
|
18
|
+
|
|
19
|
+
split.splice(Math.min(3, split.length), 0, gray(route))
|
|
20
|
+
return split.join(' ')
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const devLogger = logger((...args: any[]) => {
|
|
25
|
+
const timestamp = gray(Datte.dateTime())
|
|
26
|
+
console.log(timestamp, ...logWithRoute(args))
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
await devLogger(c, next)
|
|
30
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { basicAuth } from 'hono/basic-auth'
|
|
2
|
+
import response from '$/response'
|
|
3
|
+
import { getConfig } from '$/config'
|
|
4
|
+
import type { Hono } from '$/types'
|
|
5
|
+
|
|
6
|
+
export function registerOpenAPI(app: Hono, opts: any) {
|
|
7
|
+
if (!opts.path) return
|
|
8
|
+
|
|
9
|
+
if (opts?.auth?.username && opts?.auth?.password) {
|
|
10
|
+
app.use(opts.path +'/*', async (c, next) => {
|
|
11
|
+
const realm = opts.auth?.realm || 'Docs'
|
|
12
|
+
const unauthorized = response.unauthorized(
|
|
13
|
+
null,
|
|
14
|
+
{'WWW-Authenticate': `Basic realm="${realm.replace(/"/g, '\\"')}", charset="UTF-8"`}
|
|
15
|
+
)
|
|
16
|
+
if (!c.req.raw.headers.get('Authorization')) return unauthorized
|
|
17
|
+
const auth = basicAuth({ username: opts.auth.username, password: opts.auth.password, realm })
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
await auth(c, next)
|
|
21
|
+
} catch {
|
|
22
|
+
return unauthorized
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
app.get(opts.path, async () => response.html(getConfig('DUTO_OAC')))
|
|
28
|
+
app.get(opts.path +'/openapi', async () => response.raw(200, getConfig('DUTO_OAS'), 'application/json'))
|
|
29
|
+
}
|