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.
Files changed (98) hide show
  1. package/LICENSE +2 -2
  2. package/README.md +35 -13
  3. package/package.json +54 -13
  4. package/src/cli.ts +45 -0
  5. package/src/commands/new.ts +147 -0
  6. package/src/commands/templates/base/.editorconfig +21 -0
  7. package/src/commands/templates/base/.env.dev.example +2 -0
  8. package/src/commands/templates/base/.env.prod.example +2 -0
  9. package/src/commands/templates/base/README.md +29 -0
  10. package/src/commands/templates/base/_gitignore +100 -0
  11. package/src/commands/templates/base/actions/blog/index.get.ts +9 -0
  12. package/src/commands/templates/base/actions/blog/post/[id].get.ts +9 -0
  13. package/src/commands/templates/base/actions/index.get.ts +5 -0
  14. package/src/commands/templates/base/configs/duto.ts +11 -0
  15. package/src/commands/templates/base/configs/roles.ts +5 -0
  16. package/src/commands/templates/base/package.json +24 -0
  17. package/src/commands/templates/base/tsconfig.json +42 -0
  18. package/src/commands/templates/workerd/wrangler.toml +47 -0
  19. package/src/commands/upgrade.ts +30 -0
  20. package/src/enum.ts +2 -0
  21. package/src/index.d.ts +61 -0
  22. package/src/index.ts +19 -23
  23. package/src/middleware.ts +2 -0
  24. package/src/{cli → pages/cli}/commands/build.ts +13 -7
  25. package/src/{cli → pages/cli}/commands/dev.ts +11 -4
  26. package/src/{cli → pages/cli}/commands/preview.ts +6 -4
  27. package/src/pages/cli/index.ts +10 -0
  28. package/src/{cli → pages/cli}/utils.ts +4 -5
  29. package/src/{client → pages/client}/app.tsx +1 -1
  30. package/src/{client → pages/client}/router.ts +1 -1
  31. package/src/{compiler → pages/compiler}/file.ts +1 -1
  32. package/src/{compiler → pages/compiler}/index.ts +2 -4
  33. package/src/{compiler → pages/compiler}/meta.ts +2 -2
  34. package/src/{compiler → pages/compiler}/routes.ts +1 -1
  35. package/src/{compiler → pages/compiler}/types.ts +10 -0
  36. package/src/{compiler → pages/compiler}/vite-plugin.ts +20 -45
  37. package/src/{i18n → pages/i18n}/client.ts +2 -2
  38. package/src/{i18n → pages/i18n}/element.ts +1 -1
  39. package/src/{i18n → pages/i18n}/server.ts +1 -1
  40. package/src/pages/index.ts +28 -0
  41. package/src/{preact → pages/preact}/server.ts +1 -1
  42. package/src/server/adapter/aws.ts +4 -0
  43. package/src/server/adapter/workerd.ts +4 -0
  44. package/src/server/app.ts +91 -0
  45. package/src/server/auth/ability.ts +72 -0
  46. package/src/server/auth/authnz.ts +86 -0
  47. package/src/server/auth/index.ts +5 -0
  48. package/src/server/auth/token.ts +147 -0
  49. package/src/server/auth/types.ts +2 -0
  50. package/src/server/cli/commands/build.ts +41 -0
  51. package/src/server/cli/commands/deploy.ts +81 -0
  52. package/src/server/cli/commands/dev.ts +279 -0
  53. package/src/server/cli/commands/make.ts +98 -0
  54. package/src/server/cli/commands/migrate.ts +93 -0
  55. package/src/server/cli/commands/routes.ts +78 -0
  56. package/src/server/cli/index.ts +27 -0
  57. package/src/server/cli/stubs.ts +39 -0
  58. package/src/server/cli/types.ts +3 -0
  59. package/src/server/cli/utils.ts +437 -0
  60. package/src/server/config.ts +19 -0
  61. package/src/server/db/d1.d.ts +1 -0
  62. package/src/server/db/d1.ts +1 -0
  63. package/src/server/db/dynamodb.d.ts +1 -0
  64. package/src/server/db/dynamodb.ts +1 -0
  65. package/src/server/db/index.ts +1 -0
  66. package/src/server/entry.ts +19 -0
  67. package/src/server/handle.ts +30 -0
  68. package/src/server/middlewares/auth.ts +40 -0
  69. package/src/server/middlewares/logger.ts +30 -0
  70. package/src/server/open-api/register.ts +29 -0
  71. package/src/server/open-api/spec.ts +114 -0
  72. package/src/server/request.ts +265 -0
  73. package/src/server/response.ts +142 -0
  74. package/src/server/routes.ts +473 -0
  75. package/src/server/types.ts +142 -0
  76. package/src/server/utils/environment.ts +17 -0
  77. package/src/server/utils.ts +71 -0
  78. package/src/server/validator.ts +70 -0
  79. package/src/utils.ts +15 -0
  80. package/src/cli/commands/dev-old.ts +0 -213
  81. package/src/cli/index.ts +0 -28
  82. package/src/compiler/utils.ts +0 -5
  83. /package/src/{client → pages/client}/hmr.ts +0 -0
  84. /package/src/{client → pages/client}/island.ts +0 -0
  85. /package/src/{client → pages/client}/link.ts +0 -0
  86. /package/src/{client → pages/client}/storage.ts +0 -0
  87. /package/src/{compiler → pages/compiler}/aliases.ts +0 -0
  88. /package/src/{compiler → pages/compiler}/assets.ts +0 -0
  89. /package/src/{compiler → pages/compiler}/ast.ts +0 -0
  90. /package/src/{compiler → pages/compiler}/constants.ts +0 -0
  91. /package/src/{compiler → pages/compiler}/island.ts +0 -0
  92. /package/src/{components → pages/components}/link.tsx +0 -0
  93. /package/src/{preact → pages/preact}/client-dev.ts +0 -0
  94. /package/src/{preact → pages/preact}/client.ts +0 -0
  95. /package/src/{preact → pages/preact}/context.ts +0 -0
  96. /package/src/{preact → pages/preact}/signals.ts +0 -0
  97. /package/src/{preact → pages/preact}/static-html.ts +0 -0
  98. /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()
@@ -0,0 +1,3 @@
1
+ import { platforms } from './utils'
2
+
3
+ export type Platform = typeof platforms[number]