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,147 @@
|
|
|
1
|
+
import { Envir, parseUA } from 't0n'
|
|
2
|
+
import { sha256, toString, type Config } from 'cripta'
|
|
3
|
+
import { config, create, parse } from 'cripta/token'
|
|
4
|
+
import type { IRequest } from '$/types'
|
|
5
|
+
|
|
6
|
+
const STABLE_HEADERS = [
|
|
7
|
+
'host',
|
|
8
|
+
'connection',
|
|
9
|
+
'sec-ch-ua',
|
|
10
|
+
'sec-ch-ua-mobile',
|
|
11
|
+
'sec-ch-ua-platform',
|
|
12
|
+
'upgrade-insecure-requests',
|
|
13
|
+
'user-agent',
|
|
14
|
+
'accept',
|
|
15
|
+
'sec-fetch-site',
|
|
16
|
+
'sec-fetch-mode',
|
|
17
|
+
'sec-fetch-user',
|
|
18
|
+
'sec-fetch-dest',
|
|
19
|
+
'accept-encoding',
|
|
20
|
+
'accept-language',
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
function headerOrdering(headers: Record<string, string>) {
|
|
24
|
+
const order: string[] = []
|
|
25
|
+
|
|
26
|
+
for (const name in headers) {
|
|
27
|
+
const key = name.toLowerCase()
|
|
28
|
+
|
|
29
|
+
if (STABLE_HEADERS.includes(key))
|
|
30
|
+
order.push(key)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return order
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class Token {
|
|
37
|
+
static #cookieName: string = '__auth_'
|
|
38
|
+
static #name: string = 'Authorization'
|
|
39
|
+
static #prefix: string = 'bearer'
|
|
40
|
+
static #cripta: Config
|
|
41
|
+
|
|
42
|
+
static async fromRequest(req: IRequest) {
|
|
43
|
+
const token = this.fromCookie(req) || this.fromHeader(req)
|
|
44
|
+
return token ? await this.parse(req, token) : null
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
static fromHeader(req: IRequest): string | null {
|
|
48
|
+
const header = req.header(this.#name) || req.header('HTTP_AUTHORIZATION') || req.header('REDIRECT_HTTP_AUTHORIZATION') || null
|
|
49
|
+
|
|
50
|
+
if (header) {
|
|
51
|
+
const pos = header.toLowerCase().indexOf(this.#prefix.toLowerCase())
|
|
52
|
+
if (pos < 0) return header
|
|
53
|
+
|
|
54
|
+
let token = header.slice(pos + this.#prefix.length).trim()
|
|
55
|
+
const commaPos = token.indexOf(',')
|
|
56
|
+
if (commaPos > -1) token = token.slice(0, commaPos).trim()
|
|
57
|
+
|
|
58
|
+
return token
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return null
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
static fromCookie(req: IRequest): string | null {
|
|
65
|
+
const uid = req.header('uid')
|
|
66
|
+
|
|
67
|
+
if (uid) {
|
|
68
|
+
const auth = req.cookie.get(this.#cookieName + uid)
|
|
69
|
+
return auth ? auth : null
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return null
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
static async cripta() {
|
|
76
|
+
if (this.#cripta)
|
|
77
|
+
return this.#cripta
|
|
78
|
+
|
|
79
|
+
return this.#cripta = await config({ key: Envir.get<string>('KEY') })
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
static async parse(req: IRequest, token: string) {
|
|
83
|
+
const host = this.host(req)
|
|
84
|
+
const serveHost = Envir.get('FLOW_SERVER', host) as string
|
|
85
|
+
|
|
86
|
+
return parse(await this.cripta(), token, {
|
|
87
|
+
iss: serveHost,
|
|
88
|
+
aud: [host],
|
|
89
|
+
'_': await this.fingerprint(req),
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
static async create(req: IRequest, user: any, exp: number = 7200) {
|
|
94
|
+
const time = Math.floor(Date.now() / 1000)
|
|
95
|
+
const host = this.host(req)
|
|
96
|
+
|
|
97
|
+
return create(await this.cripta(), user, {
|
|
98
|
+
iss: host,
|
|
99
|
+
aud: [host],
|
|
100
|
+
iat: time,
|
|
101
|
+
exp: time + exp,
|
|
102
|
+
'_': await this.fingerprint(req),
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
static setPrefix(prefix: string): void {
|
|
107
|
+
this.#prefix = prefix
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
static setName(name: string): void {
|
|
111
|
+
this.#name = name
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
static host(req: IRequest): string {
|
|
115
|
+
const url = req.url || req.header('host')
|
|
116
|
+
if (!url) return ''
|
|
117
|
+
|
|
118
|
+
let formattedUrl = String(url)
|
|
119
|
+
if (!formattedUrl.startsWith('http'))
|
|
120
|
+
formattedUrl = 'http://' + formattedUrl
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
const parsedUrl = new URL(formattedUrl)
|
|
124
|
+
return parsedUrl.host
|
|
125
|
+
} catch {
|
|
126
|
+
return ''
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
static async fingerprint(req: IRequest) {
|
|
131
|
+
const ua = parseUA(req.header('user-agent'))
|
|
132
|
+
|
|
133
|
+
const id = toString(await sha256(
|
|
134
|
+
// (req.header('accept-language') || '')
|
|
135
|
+
this.ip(req)
|
|
136
|
+
+ ua.browser.name + ua.browser.version.split('.')[0]
|
|
137
|
+
+ ua.os.type + ua.os.name
|
|
138
|
+
+ headerOrdering(req.header()).join('')
|
|
139
|
+
))
|
|
140
|
+
|
|
141
|
+
return id.substring(0, 8) + id.substring(56, 64)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
static ip(req: IRequest) {
|
|
145
|
+
return req?.ip || 0
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Envir } from 't0n'
|
|
2
|
+
import { command } from 't0n/cli'
|
|
3
|
+
import { gray } from 't0n/color'
|
|
4
|
+
import { wait, error, rn } from 't0n/log'
|
|
5
|
+
import { platforms, build, normalizePlatform, platformError } from '../utils'
|
|
6
|
+
|
|
7
|
+
export default command({
|
|
8
|
+
meta: {
|
|
9
|
+
name: 'build',
|
|
10
|
+
description: '🗂️ Perform the build\n',
|
|
11
|
+
},
|
|
12
|
+
args: {
|
|
13
|
+
platform: {
|
|
14
|
+
alias: 'p',
|
|
15
|
+
description: 'Environment platform',
|
|
16
|
+
type: 'enum',
|
|
17
|
+
options: platforms,
|
|
18
|
+
},
|
|
19
|
+
minify: {
|
|
20
|
+
description: 'Minify the result',
|
|
21
|
+
type: 'boolean',
|
|
22
|
+
default: true,
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
async run({ args }) {
|
|
26
|
+
const platform = normalizePlatform(args.p || args.platform || Envir.get('duto.platform') || args._[0] || 'node')
|
|
27
|
+
if (!platform)
|
|
28
|
+
return platformError()
|
|
29
|
+
|
|
30
|
+
wait('Building for platform: '+ gray(platform))
|
|
31
|
+
|
|
32
|
+
try { // @ts-ignore
|
|
33
|
+
await build({ platform, minify: args.minify })
|
|
34
|
+
} catch (e: any) {
|
|
35
|
+
error('Build failed:\n\n', e)
|
|
36
|
+
process.exit(0)
|
|
37
|
+
} finally {
|
|
38
|
+
rn()
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
})
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process'
|
|
2
|
+
import { Envir } from 't0n'
|
|
3
|
+
import { command } from 't0n/cli'
|
|
4
|
+
import { error } from 't0n/log'
|
|
5
|
+
import { getRuntime } from 't0n/cli'
|
|
6
|
+
|
|
7
|
+
import { build, platforms, normalizePlatform, platformError } from '../utils'
|
|
8
|
+
import { _root } from '@/utils'
|
|
9
|
+
|
|
10
|
+
// import build from './build'
|
|
11
|
+
|
|
12
|
+
export default command({
|
|
13
|
+
meta: {
|
|
14
|
+
name: 'deploy',
|
|
15
|
+
description: '☁️ Perform the build and execute deploy\n',
|
|
16
|
+
},
|
|
17
|
+
args: {
|
|
18
|
+
platform: {
|
|
19
|
+
alias: 'p',
|
|
20
|
+
description: 'Environment platform',
|
|
21
|
+
type: 'enum',
|
|
22
|
+
options: platforms,
|
|
23
|
+
},
|
|
24
|
+
minify: {
|
|
25
|
+
description: 'Minify the result',
|
|
26
|
+
type: 'boolean',
|
|
27
|
+
default: true,
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
async run({ args }) { // @ts-ignore
|
|
31
|
+
const platform = normalizePlatform(args.p || args.platform || args._[0] || Envir.get('duto.platform'))
|
|
32
|
+
if (!platform)
|
|
33
|
+
return platformError()
|
|
34
|
+
|
|
35
|
+
// @ts-ignore
|
|
36
|
+
await build({ platform, minify: args.minify })
|
|
37
|
+
const isBun = getRuntime() === 'bun'
|
|
38
|
+
|
|
39
|
+
switch (platform) {
|
|
40
|
+
case 'aws':
|
|
41
|
+
// TODO: perform aws deploy
|
|
42
|
+
return error('Platform not yet implemented, contact the webmaster')
|
|
43
|
+
case 'workerd':
|
|
44
|
+
const child = spawn(
|
|
45
|
+
isBun ? 'bunx' : 'npx',
|
|
46
|
+
['wrangler', 'deploy'],
|
|
47
|
+
{
|
|
48
|
+
stdio: 'inherit',
|
|
49
|
+
cwd: _root,
|
|
50
|
+
}
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
child.on('exit', code => process.exit(code ?? 0))
|
|
54
|
+
.on('message', msg => {
|
|
55
|
+
process.send && process.send(msg)
|
|
56
|
+
}).on('disconnect', () => {
|
|
57
|
+
process.disconnect && process.disconnect()
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
return
|
|
61
|
+
case 'vercel':
|
|
62
|
+
return error('Platform not yet implemented, contact the webmaster')
|
|
63
|
+
const vchild = spawn(
|
|
64
|
+
isBun ? 'bunx' : 'npx',
|
|
65
|
+
['vercel', 'deploy'],
|
|
66
|
+
{
|
|
67
|
+
stdio: 'inherit',
|
|
68
|
+
cwd: _root,
|
|
69
|
+
}
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
vchild.on('exit', code => process.exit(code ?? 0))
|
|
73
|
+
.on('message', msg => {
|
|
74
|
+
process.send && process.send(msg)
|
|
75
|
+
}).on('disconnect', () => {
|
|
76
|
+
process.disconnect && process.disconnect()
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
})
|
|
@@ -0,0 +1,277 @@
|
|
|
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 { Envir } 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 = Envir.get('duto', {})
|
|
54
|
+
const platform = normalizePlatform(args.p || args.platform || duto.platform || args._[0] || 'node')
|
|
55
|
+
if (!platform)
|
|
56
|
+
return platformError()
|
|
57
|
+
|
|
58
|
+
const desiredPort = args.port ? Number(args.port) : duto?.dev?.port || 3000
|
|
59
|
+
const host = args.host ? String(args.host) : duto?.dev?.host || 'localhost'
|
|
60
|
+
|
|
61
|
+
let isBuilding = false
|
|
62
|
+
const startApp = async (start: Function, stop: Function|undefined = undefined, building: boolean = true) => {
|
|
63
|
+
if (building) {
|
|
64
|
+
if (isBuilding) return
|
|
65
|
+
isBuilding = true
|
|
66
|
+
event('Building..')
|
|
67
|
+
}
|
|
68
|
+
const fn = async (silent: boolean = false) => { // @ts-ignore
|
|
69
|
+
building && await build({ platform, silent, minify: args.minify, env: 'dev' })
|
|
70
|
+
await start()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
await fn()
|
|
75
|
+
watch(async () => {
|
|
76
|
+
// event('Restarting..')
|
|
77
|
+
try {
|
|
78
|
+
await fn(true)
|
|
79
|
+
} catch (e) {
|
|
80
|
+
error(e)
|
|
81
|
+
}
|
|
82
|
+
// event('Restarted...')
|
|
83
|
+
}, [
|
|
84
|
+
join(_root, '{actions,features,routes,configs,enums,libs,locales,middlewares,models,utils}/**/*.ts'),
|
|
85
|
+
join(_root, 'node_modules/{duto,cripta,t0n}/**/*.ts'),
|
|
86
|
+
join(_root, '.env.dev'),
|
|
87
|
+
join(_root, '.env.prod'),
|
|
88
|
+
join(_root, 'package.json'),
|
|
89
|
+
...WRANGLER_CONFIG_FILES.map(f => join(_root, f)),
|
|
90
|
+
], _root)
|
|
91
|
+
// @ts-ignore
|
|
92
|
+
stop && shutdown(stop)
|
|
93
|
+
} catch (e: any) {
|
|
94
|
+
error(e?.stack || e?.message || e)
|
|
95
|
+
process.exit(0)
|
|
96
|
+
} finally {
|
|
97
|
+
isBuilding = false
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const applyExit = async (app: ChildProcess | null) => {
|
|
102
|
+
if (!app) return null
|
|
103
|
+
|
|
104
|
+
app //?.on('exit', code => process.exit(code ?? 0))
|
|
105
|
+
.on('message', msg => {
|
|
106
|
+
process.send && process.send(msg)
|
|
107
|
+
}).on('disconnect', () => {
|
|
108
|
+
process.disconnect && process.disconnect()
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
const killProcess = async (app: ChildProcess | null) => {
|
|
112
|
+
if (!app) return null
|
|
113
|
+
// event('Stopping..')
|
|
114
|
+
try {
|
|
115
|
+
if (!app?.killed) {
|
|
116
|
+
app.kill('SIGTERM')
|
|
117
|
+
await wait(1000)
|
|
118
|
+
|
|
119
|
+
if (!app?.killed) { // force kill
|
|
120
|
+
app.kill('SIGKILL')
|
|
121
|
+
await wait(1000)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return null
|
|
126
|
+
} catch (e) {
|
|
127
|
+
error('Error stopping:', e)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return null
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const started = (port: number) => {
|
|
134
|
+
log(`Starting API on http://${host}:${port}`)
|
|
135
|
+
if (platform === 'workerd')
|
|
136
|
+
log(`Localflare on https://studio.localflare.dev`)
|
|
137
|
+
rn()
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
withPort(desiredPort, async port => {
|
|
141
|
+
switch (platform) {
|
|
142
|
+
case 'aws':
|
|
143
|
+
started(port)
|
|
144
|
+
let lambda: ChildProcess | null = null
|
|
145
|
+
const stopLambda = async () => {
|
|
146
|
+
lambda = await killProcess(lambda)
|
|
147
|
+
}
|
|
148
|
+
const startLambda = async () => {
|
|
149
|
+
if (lambda) await stopLambda()
|
|
150
|
+
|
|
151
|
+
lambda = spawn(
|
|
152
|
+
'sam',
|
|
153
|
+
[
|
|
154
|
+
'local', 'start-api',
|
|
155
|
+
'--warm-containers', 'LAZY',
|
|
156
|
+
'--debug', '--template-file', join(_root, 'template-dev.yaml'),
|
|
157
|
+
'--port', String(port),
|
|
158
|
+
],
|
|
159
|
+
{
|
|
160
|
+
stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
|
|
161
|
+
// stdio: ['pipe', 'pipe', 'pipe', 'ipc'],
|
|
162
|
+
shell: process.platform === 'win32',
|
|
163
|
+
env: {...process.env, DOCKER_HOST: getDockerHost()},
|
|
164
|
+
}
|
|
165
|
+
)
|
|
166
|
+
//.on('exit', code => {
|
|
167
|
+
// warn(`Lambda process exited with code ${code ?? 0}`)
|
|
168
|
+
// if (code != 0 && code != null)
|
|
169
|
+
// error('Lambda process crashed, waiting for restart...')
|
|
170
|
+
|
|
171
|
+
// lambda = null
|
|
172
|
+
// }).on('message', msg => {
|
|
173
|
+
// process.send && process.send(msg)
|
|
174
|
+
// }).on('disconnect', () => {
|
|
175
|
+
// process.disconnect && process.disconnect()
|
|
176
|
+
// }).on('error', e => {
|
|
177
|
+
// error('Lambda process error:', e)
|
|
178
|
+
// lambda = null
|
|
179
|
+
// })
|
|
180
|
+
applyExit(lambda)
|
|
181
|
+
await wait(2000)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
await startApp(startLambda, stopLambda)
|
|
185
|
+
break
|
|
186
|
+
|
|
187
|
+
case 'workerd':
|
|
188
|
+
started(port)
|
|
189
|
+
|
|
190
|
+
let localflare: Miniflare | null = null
|
|
191
|
+
const startLocalflare = async (workerConfig: WranglerConfig) => {
|
|
192
|
+
if (localflare) return
|
|
193
|
+
|
|
194
|
+
localflare = createMiniflare({
|
|
195
|
+
...workerConfig,
|
|
196
|
+
vars: {
|
|
197
|
+
...workerConfig.vars,
|
|
198
|
+
LOCALFLARE_MANIFEST: JSON.stringify(localflareManifest(workerConfig)),
|
|
199
|
+
},
|
|
200
|
+
main: '.duto/localfire.js',
|
|
201
|
+
port: 8788,
|
|
202
|
+
inspectorPort: 9230,
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
await localflare.ready
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
let worker: Miniflare | null = null
|
|
209
|
+
const startWorker = async () => {
|
|
210
|
+
if (worker) await worker.dispose()
|
|
211
|
+
|
|
212
|
+
const workerConfig = await wranglerConfig() // @ts-ignore
|
|
213
|
+
workerConfig.host = host // @ts-ignore
|
|
214
|
+
workerConfig.liveReload = false
|
|
215
|
+
|
|
216
|
+
worker = createMiniflare({ ...workerConfig, port })
|
|
217
|
+
await worker.ready
|
|
218
|
+
|
|
219
|
+
if (!localflare) await startLocalflare(workerConfig)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
await startApp(startWorker)
|
|
223
|
+
break
|
|
224
|
+
default:
|
|
225
|
+
case 'node':
|
|
226
|
+
return console.error('@Deprecated')
|
|
227
|
+
const isBun = getRuntime() === 'bun'
|
|
228
|
+
const isWin32 = process.platform === 'win32'
|
|
229
|
+
|
|
230
|
+
const _arg = isBun ? 'run' : findTsx()
|
|
231
|
+
|
|
232
|
+
if (!_arg) {
|
|
233
|
+
console.error('Error: "tsx" is not available. Please install tsx:')
|
|
234
|
+
console.error(' npm i -D tsx')
|
|
235
|
+
console.error(' or')
|
|
236
|
+
console.error(' bun i -D tsx')
|
|
237
|
+
process.exit(1)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
started(port)
|
|
241
|
+
const params = isBun
|
|
242
|
+
? ['--port=' + port, '--hot', '--silent', '--no-clear-screen', '--no-summary']
|
|
243
|
+
: ['watch']
|
|
244
|
+
|
|
245
|
+
let nodeApp: ChildProcess | null = null
|
|
246
|
+
const stopNode = async () => {
|
|
247
|
+
nodeApp = await killProcess(nodeApp)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const startNode = async () => {
|
|
251
|
+
if (nodeApp) await stopNode()
|
|
252
|
+
|
|
253
|
+
nodeApp = spawn(
|
|
254
|
+
isBun && isWin32 ? 'bun' : process.execPath,
|
|
255
|
+
[_arg, ...params, join(_root, `node_modules/duto/src/${isBun ? 'app/dev' : 'adapter/node'}.ts`)],
|
|
256
|
+
{
|
|
257
|
+
stdio: ['inherit', isBun ? 'pipe' : 'inherit', 'inherit', 'ipc'],
|
|
258
|
+
env: {...process.env, PORT: port},
|
|
259
|
+
}
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
if (isBun && nodeApp?.stdout) {
|
|
263
|
+
nodeApp.stdout?.on('data', data => {
|
|
264
|
+
const output = data.toString()
|
|
265
|
+
if (!output.includes('Started development server'))
|
|
266
|
+
process.stdout.write(output)
|
|
267
|
+
})
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
applyExit(nodeApp)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
await startApp(startNode, stopNode, false)
|
|
274
|
+
}
|
|
275
|
+
})
|
|
276
|
+
},
|
|
277
|
+
})
|
|
@@ -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
|
+
})
|