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,279 @@
|
|
|
1
|
+
import { join } from 'pathe'
|
|
2
|
+
import { spawn, type ChildProcess } from 'node:child_process'
|
|
3
|
+
|
|
4
|
+
import { command } from 't0n/cli'
|
|
5
|
+
import type { Miniflare } from 'miniflare'
|
|
6
|
+
import { WRANGLER_CONFIG_FILES, type WranglerConfig } from 'localflare-core'
|
|
7
|
+
|
|
8
|
+
import { error, event, log, rn, warn } from 't0n/log'
|
|
9
|
+
import { getRuntime, shutdown, watch } from 't0n/cli'
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
build, wait, normalizePlatform, platformError,
|
|
13
|
+
wranglerConfig, createMiniflare, localflareManifest,
|
|
14
|
+
getDockerHost,
|
|
15
|
+
// findTsx
|
|
16
|
+
} from '../utils'
|
|
17
|
+
import { _root } from '@/utils'
|
|
18
|
+
import { withPort } from 't0n/port'
|
|
19
|
+
import { getEnv } from 't0n'
|
|
20
|
+
import { $dev, setEnv } from '$/utils/environment'
|
|
21
|
+
|
|
22
|
+
export default command({
|
|
23
|
+
meta: {
|
|
24
|
+
name: 'dev',
|
|
25
|
+
description: '💻 Start the localhost server\n',
|
|
26
|
+
},
|
|
27
|
+
args: {
|
|
28
|
+
port: {
|
|
29
|
+
description: 'Port to listen on',
|
|
30
|
+
type: 'number',
|
|
31
|
+
default: 3000,
|
|
32
|
+
},
|
|
33
|
+
host: {
|
|
34
|
+
description: 'Host to forward requests to, defaults to the zone of project',
|
|
35
|
+
type: 'string',
|
|
36
|
+
default: 'localhost',
|
|
37
|
+
},
|
|
38
|
+
platform: {
|
|
39
|
+
alias: 'p',
|
|
40
|
+
description: 'Environment platform',
|
|
41
|
+
type: 'enum',
|
|
42
|
+
options: ['aws', 'cf', 'node'] as const,
|
|
43
|
+
// required: true,
|
|
44
|
+
},
|
|
45
|
+
minify: {
|
|
46
|
+
description: 'Minify the result',
|
|
47
|
+
type: 'boolean',
|
|
48
|
+
default: true,
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
async run({ args }) {
|
|
52
|
+
setEnv($dev)
|
|
53
|
+
const duto = getEnv('duto', {})
|
|
54
|
+
const platform = normalizePlatform(args.p || args.platform || duto.platform || args._[0] || 'node')
|
|
55
|
+
if (!platform)
|
|
56
|
+
return platformError()
|
|
57
|
+
|
|
58
|
+
const desiredPort = Number(args.port || duto?.dev?.port || 3000)
|
|
59
|
+
const host = String(args.host || duto?.dev?.host || 'localhost')
|
|
60
|
+
// const desiredPort = args.port ? Number(args.port) : duto?.dev?.port || 3000
|
|
61
|
+
// const host = args.host ? String(args.host) : duto?.dev?.host || 'localhost'
|
|
62
|
+
|
|
63
|
+
let isBuilding = false
|
|
64
|
+
const startApp = async (start: Function, stop: Function|undefined = undefined, building: boolean = true) => {
|
|
65
|
+
if (building) {
|
|
66
|
+
if (isBuilding) return
|
|
67
|
+
isBuilding = true
|
|
68
|
+
event('Building..')
|
|
69
|
+
}
|
|
70
|
+
const fn = async (silent: boolean = false) => { // @ts-ignore
|
|
71
|
+
building && await build({ platform, silent, minify: args.minify, env: 'dev' })
|
|
72
|
+
await start()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
await fn()
|
|
77
|
+
watch(async () => {
|
|
78
|
+
// event('Restarting..')
|
|
79
|
+
try {
|
|
80
|
+
await fn(true)
|
|
81
|
+
} catch (e) {
|
|
82
|
+
error(e)
|
|
83
|
+
}
|
|
84
|
+
// event('Restarted...')
|
|
85
|
+
}, [
|
|
86
|
+
join(_root, '{actions,features,routes,configs,enums,libs,locales,middlewares,models,utils}/**/*.ts'),
|
|
87
|
+
join(_root, 'node_modules/{duto,cripta,t0n}/**/*.ts'),
|
|
88
|
+
join(_root, '.env.dev'),
|
|
89
|
+
join(_root, '.env.prod'),
|
|
90
|
+
join(_root, 'package.json'),
|
|
91
|
+
...WRANGLER_CONFIG_FILES.map(f => join(_root, f)),
|
|
92
|
+
], _root)
|
|
93
|
+
// @ts-ignore
|
|
94
|
+
stop && shutdown(stop)
|
|
95
|
+
} catch (e: any) {
|
|
96
|
+
error(e?.stack || e?.message || e)
|
|
97
|
+
process.exit(0)
|
|
98
|
+
} finally {
|
|
99
|
+
isBuilding = false
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const applyExit = async (app: ChildProcess | null) => {
|
|
104
|
+
if (!app) return null
|
|
105
|
+
|
|
106
|
+
app //?.on('exit', code => process.exit(code ?? 0))
|
|
107
|
+
.on('message', msg => {
|
|
108
|
+
process.send && process.send(msg)
|
|
109
|
+
}).on('disconnect', () => {
|
|
110
|
+
process.disconnect && process.disconnect()
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
const killProcess = async (app: ChildProcess | null) => {
|
|
114
|
+
if (!app) return null
|
|
115
|
+
// event('Stopping..')
|
|
116
|
+
try {
|
|
117
|
+
if (!app?.killed) {
|
|
118
|
+
app.kill('SIGTERM')
|
|
119
|
+
await wait(1000)
|
|
120
|
+
|
|
121
|
+
if (!app?.killed) { // force kill
|
|
122
|
+
app.kill('SIGKILL')
|
|
123
|
+
await wait(1000)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return null
|
|
128
|
+
} catch (e) {
|
|
129
|
+
error('Error stopping:', e)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return null
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const started = (port: number) => {
|
|
136
|
+
log(`Starting API on http://${host}:${port}`)
|
|
137
|
+
if (platform === 'workerd')
|
|
138
|
+
log(`Localflare on https://studio.localflare.dev`)
|
|
139
|
+
rn()
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
withPort(desiredPort, async port => {
|
|
143
|
+
switch (platform) {
|
|
144
|
+
case 'aws':
|
|
145
|
+
started(port)
|
|
146
|
+
let lambda: ChildProcess | null = null
|
|
147
|
+
const stopLambda = async () => {
|
|
148
|
+
lambda = await killProcess(lambda)
|
|
149
|
+
}
|
|
150
|
+
const startLambda = async () => {
|
|
151
|
+
if (lambda) await stopLambda()
|
|
152
|
+
|
|
153
|
+
lambda = spawn(
|
|
154
|
+
'sam',
|
|
155
|
+
[
|
|
156
|
+
'local', 'start-api',
|
|
157
|
+
'--warm-containers', 'LAZY',
|
|
158
|
+
'--debug', '--template-file', join(_root, 'template-dev.yaml'),
|
|
159
|
+
'--port', String(port),
|
|
160
|
+
],
|
|
161
|
+
{
|
|
162
|
+
stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
|
|
163
|
+
// stdio: ['pipe', 'pipe', 'pipe', 'ipc'],
|
|
164
|
+
shell: process.platform === 'win32',
|
|
165
|
+
env: {...process.env, DOCKER_HOST: getDockerHost()},
|
|
166
|
+
}
|
|
167
|
+
)
|
|
168
|
+
//.on('exit', code => {
|
|
169
|
+
// warn(`Lambda process exited with code ${code ?? 0}`)
|
|
170
|
+
// if (code != 0 && code != null)
|
|
171
|
+
// error('Lambda process crashed, waiting for restart...')
|
|
172
|
+
|
|
173
|
+
// lambda = null
|
|
174
|
+
// }).on('message', msg => {
|
|
175
|
+
// process.send && process.send(msg)
|
|
176
|
+
// }).on('disconnect', () => {
|
|
177
|
+
// process.disconnect && process.disconnect()
|
|
178
|
+
// }).on('error', e => {
|
|
179
|
+
// error('Lambda process error:', e)
|
|
180
|
+
// lambda = null
|
|
181
|
+
// })
|
|
182
|
+
applyExit(lambda)
|
|
183
|
+
await wait(2000)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
await startApp(startLambda, stopLambda)
|
|
187
|
+
break
|
|
188
|
+
|
|
189
|
+
case 'workerd':
|
|
190
|
+
started(port)
|
|
191
|
+
|
|
192
|
+
let localflare: Miniflare | null = null
|
|
193
|
+
const startLocalflare = async (workerConfig: WranglerConfig) => {
|
|
194
|
+
if (localflare) return
|
|
195
|
+
|
|
196
|
+
localflare = createMiniflare({
|
|
197
|
+
...workerConfig,
|
|
198
|
+
vars: {
|
|
199
|
+
...workerConfig.vars,
|
|
200
|
+
LOCALFLARE_MANIFEST: JSON.stringify(localflareManifest(workerConfig)),
|
|
201
|
+
},
|
|
202
|
+
main: '.duto/localfire.js',
|
|
203
|
+
port: 8788,
|
|
204
|
+
inspectorPort: 9230,
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
await localflare.ready
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
let worker: Miniflare | null = null
|
|
211
|
+
const startWorker = async () => {
|
|
212
|
+
if (worker) await worker.dispose()
|
|
213
|
+
|
|
214
|
+
const workerConfig = await wranglerConfig() // @ts-ignore
|
|
215
|
+
workerConfig.host = host // @ts-ignore
|
|
216
|
+
workerConfig.liveReload = false
|
|
217
|
+
|
|
218
|
+
worker = createMiniflare({ ...workerConfig, port })
|
|
219
|
+
await worker.ready
|
|
220
|
+
|
|
221
|
+
if (!localflare) await startLocalflare(workerConfig)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
await startApp(startWorker)
|
|
225
|
+
break
|
|
226
|
+
default:
|
|
227
|
+
case 'node':
|
|
228
|
+
return console.error('@Deprecated')
|
|
229
|
+
const isBun = getRuntime() === 'bun'
|
|
230
|
+
const isWin32 = process.platform === 'win32'
|
|
231
|
+
|
|
232
|
+
const _arg = isBun ? 'run' : findTsx()
|
|
233
|
+
|
|
234
|
+
if (!_arg) {
|
|
235
|
+
console.error('Error: "tsx" is not available. Please install tsx:')
|
|
236
|
+
console.error(' npm i -D tsx')
|
|
237
|
+
console.error(' or')
|
|
238
|
+
console.error(' bun i -D tsx')
|
|
239
|
+
process.exit(1)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
started(port)
|
|
243
|
+
const params = isBun
|
|
244
|
+
? ['--port=' + port, '--hot', '--silent', '--no-clear-screen', '--no-summary']
|
|
245
|
+
: ['watch']
|
|
246
|
+
|
|
247
|
+
let nodeApp: ChildProcess | null = null
|
|
248
|
+
const stopNode = async () => {
|
|
249
|
+
nodeApp = await killProcess(nodeApp)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const startNode = async () => {
|
|
253
|
+
if (nodeApp) await stopNode()
|
|
254
|
+
|
|
255
|
+
nodeApp = spawn(
|
|
256
|
+
isBun && isWin32 ? 'bun' : process.execPath,
|
|
257
|
+
[_arg, ...params, join(_root, `node_modules/duto/src/${isBun ? 'app/dev' : 'adapter/node'}.ts`)],
|
|
258
|
+
{
|
|
259
|
+
stdio: ['inherit', isBun ? 'pipe' : 'inherit', 'inherit', 'ipc'],
|
|
260
|
+
env: {...process.env, PORT: port},
|
|
261
|
+
}
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
if (isBun && nodeApp?.stdout) {
|
|
265
|
+
nodeApp.stdout?.on('data', data => {
|
|
266
|
+
const output = data.toString()
|
|
267
|
+
if (!output.includes('Started development server'))
|
|
268
|
+
process.stdout.write(output)
|
|
269
|
+
})
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
applyExit(nodeApp)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
await startApp(startNode, stopNode, false)
|
|
276
|
+
}
|
|
277
|
+
})
|
|
278
|
+
},
|
|
279
|
+
})
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { join, relative } from 'pathe'
|
|
2
|
+
import { command } from 't0n/cli'
|
|
3
|
+
import { dim } from 't0n/color'
|
|
4
|
+
import { event, error } from 't0n/log'
|
|
5
|
+
import { Migrator } from 'forj'
|
|
6
|
+
import { camelCase, kebabCase } from 't0n'
|
|
7
|
+
import { makeFile, hasExt } from '../utils'
|
|
8
|
+
import { _root } from '@/utils'
|
|
9
|
+
import * as stub from '../stubs'
|
|
10
|
+
|
|
11
|
+
export default command({
|
|
12
|
+
meta: {
|
|
13
|
+
name: 'make',
|
|
14
|
+
description: '📄 Create new files\n',
|
|
15
|
+
},
|
|
16
|
+
async run({ args }) {
|
|
17
|
+
const alias = process.argv[2]?.startsWith('make:')
|
|
18
|
+
const action = alias ? process.argv[2].replace('make:', '') : process.argv[3]
|
|
19
|
+
const name = alias ? args._[0] : args._[1]
|
|
20
|
+
|
|
21
|
+
const command = 'make'+ (alias ? ':': ' ') + action +' <name> '
|
|
22
|
+
|
|
23
|
+
if (!name)
|
|
24
|
+
return error('File name is required')
|
|
25
|
+
|
|
26
|
+
const makeFileName = (s: string) => {
|
|
27
|
+
const lastDotIndex = s.lastIndexOf('.')
|
|
28
|
+
const hasExtension = lastDotIndex > 0 && lastDotIndex < s.length - 1
|
|
29
|
+
|
|
30
|
+
return hasExtension
|
|
31
|
+
? `${kebabCase(s.substring(0, lastDotIndex))}.${s.substring(lastDotIndex + 1)}`
|
|
32
|
+
: s
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const path = (p: string, dir: string = action) => join(_root, dir + 's', p)
|
|
36
|
+
let fileName = ''
|
|
37
|
+
switch (action) {
|
|
38
|
+
case 'config':
|
|
39
|
+
fileName = path(makeFileName(name))
|
|
40
|
+
if (!hasExt(fileName)) fileName += '.ts'
|
|
41
|
+
makeFile(fileName, name.endsWith('.json') ? '{\n}' : 'export default {\n\n}\n')
|
|
42
|
+
break
|
|
43
|
+
case 'enum':
|
|
44
|
+
error('Action not yet implemented, contact the webmaster')
|
|
45
|
+
break
|
|
46
|
+
case 'route':
|
|
47
|
+
case 'action':
|
|
48
|
+
case 'endpoint':
|
|
49
|
+
fileName = path(makeFileName(name))
|
|
50
|
+
if (!fileName.endsWith('.ts')) fileName += '.ts'
|
|
51
|
+
makeFile(fileName, stub.replace(stub.route, { R_NAME: camelCase(name) }))
|
|
52
|
+
break
|
|
53
|
+
case 'migrate':
|
|
54
|
+
case 'migration':
|
|
55
|
+
const binding = alias ? args._[1] : args._[2]
|
|
56
|
+
if (!binding)
|
|
57
|
+
return error(`Provide a database: `+ dim(command +'<database>'))
|
|
58
|
+
|
|
59
|
+
fileName = Migrator.fileName(name)
|
|
60
|
+
const [table, create] = Migrator.guess(name)
|
|
61
|
+
fileName = path(join(binding || '', fileName), 'migration')
|
|
62
|
+
if (!fileName.endsWith('.ts')) fileName += '.ts'
|
|
63
|
+
makeFile(fileName, stub.replace(stub.migration, {
|
|
64
|
+
M_NAME: Migrator.className(name),
|
|
65
|
+
S_NAME: create ? 'create' : 'table',
|
|
66
|
+
T_NAME: table || 'TABLE_NAME',
|
|
67
|
+
M_CONTENT: create ? stub.migrationCreate : '//',
|
|
68
|
+
}))
|
|
69
|
+
break
|
|
70
|
+
case 'model':
|
|
71
|
+
error('Action not yet implemented, contact the webmaster')
|
|
72
|
+
break
|
|
73
|
+
case 'job':
|
|
74
|
+
error('Action not yet implemented, contact the webmaster')
|
|
75
|
+
break
|
|
76
|
+
case 'seed':
|
|
77
|
+
case 'seeder':
|
|
78
|
+
error('Action not yet implemented, contact the webmaster')
|
|
79
|
+
break
|
|
80
|
+
case 'test':
|
|
81
|
+
error('Action not yet implemented, contact the webmaster')
|
|
82
|
+
break
|
|
83
|
+
default:
|
|
84
|
+
return error('Invalid action: '+ dim('make <action>['+ [
|
|
85
|
+
'config',
|
|
86
|
+
'enum',
|
|
87
|
+
'route', // 'action', 'endpoint',
|
|
88
|
+
'migrate', // 'migration',
|
|
89
|
+
'model',
|
|
90
|
+
'job',
|
|
91
|
+
'seed', // 'seeder',
|
|
92
|
+
'test',
|
|
93
|
+
].join('|') +'] <name>'))
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
event(action.charAt(0).toUpperCase() + action.slice(1) +' "'+ relative(_root, fileName) +'" created successfully')
|
|
97
|
+
},
|
|
98
|
+
})
|
|
@@ -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,78 @@
|
|
|
1
|
+
import { command } from 't0n/cli'
|
|
2
|
+
import { rn } from 't0n/log'
|
|
3
|
+
import { highlightedURI, highlightedMethod } from '../utils'
|
|
4
|
+
import { getRoutes } from '$/routes'
|
|
5
|
+
|
|
6
|
+
export default command({
|
|
7
|
+
meta: {
|
|
8
|
+
name: 'routes',
|
|
9
|
+
description: '📒 Displays all registered routes\n',
|
|
10
|
+
},
|
|
11
|
+
args: {
|
|
12
|
+
path: {
|
|
13
|
+
description: 'Filter the routes by path',
|
|
14
|
+
type: 'string',
|
|
15
|
+
},
|
|
16
|
+
method: {
|
|
17
|
+
description: 'Filter the routes by method',
|
|
18
|
+
type: 'string',
|
|
19
|
+
},
|
|
20
|
+
reverse: {
|
|
21
|
+
description: 'Reverse the ordering of the routes',
|
|
22
|
+
type: 'boolean',
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
async run({ args }) {
|
|
26
|
+
const opts = {
|
|
27
|
+
path: args?.path || '',
|
|
28
|
+
method: args?.method?.toUpperCase() || '',
|
|
29
|
+
reverse: !!args?.reverse,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const keys: Set<string> = new Set()
|
|
33
|
+
let maxMethodLength = 0
|
|
34
|
+
let maxPathLength = 0
|
|
35
|
+
|
|
36
|
+
let routes = (await getRoutes())
|
|
37
|
+
.filter(({ method, path }) => {
|
|
38
|
+
method = method.toUpperCase()
|
|
39
|
+
const key = method + '-' + path
|
|
40
|
+
if (keys.has(key)) return false
|
|
41
|
+
keys.add(key)
|
|
42
|
+
|
|
43
|
+
let mLength = method.length
|
|
44
|
+
if (method === 'GET') mLength += 5
|
|
45
|
+
|
|
46
|
+
maxMethodLength = Math.max(maxMethodLength, mLength)
|
|
47
|
+
maxPathLength = Math.max(maxPathLength, path.length)
|
|
48
|
+
|
|
49
|
+
return [
|
|
50
|
+
opts.path ? path.startsWith(opts.path) : true,
|
|
51
|
+
opts.method ? method === opts.method : true,
|
|
52
|
+
].every(Boolean)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
if (!routes.length) {
|
|
56
|
+
console.log(' No route found.')
|
|
57
|
+
return rn()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (opts.reverse)
|
|
61
|
+
routes = routes.reverse()
|
|
62
|
+
|
|
63
|
+
routes.forEach(route => {
|
|
64
|
+
if (!route) return
|
|
65
|
+
const method = route.method.toUpperCase()
|
|
66
|
+
|
|
67
|
+
let mLength = method.length
|
|
68
|
+
let str = highlightedMethod(method, null, true)
|
|
69
|
+
|
|
70
|
+
if (method === 'GET')
|
|
71
|
+
mLength += 5
|
|
72
|
+
|
|
73
|
+
console.log(str + ' '.repeat(maxMethodLength - mLength) +' '+ highlightedURI(route.path, method))
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
rn()
|
|
77
|
+
},
|
|
78
|
+
})
|
|
@@ -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()
|