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