duto 0.0.0 → 0.260808.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 +56 -2
- package/src/cli/commands/build.ts +74 -0
- package/src/cli/commands/dev-old.ts +213 -0
- package/src/cli/commands/dev.ts +87 -0
- package/src/cli/commands/preview.ts +33 -0
- package/src/cli/index.ts +28 -0
- package/src/cli/utils.ts +146 -0
- package/src/client/app.tsx +123 -0
- package/src/client/hmr.ts +13 -0
- package/src/client/island.ts +136 -0
- package/src/client/link.ts +70 -0
- package/src/client/router.ts +329 -0
- package/src/client/storage.ts +95 -0
- package/src/compiler/aliases.ts +36 -0
- package/src/compiler/assets.ts +43 -0
- package/src/compiler/ast.ts +764 -0
- package/src/compiler/constants.ts +24 -0
- package/src/compiler/file.ts +60 -0
- package/src/compiler/index.ts +307 -0
- package/src/compiler/island.ts +108 -0
- package/src/compiler/meta.ts +601 -0
- package/src/compiler/routes.ts +29 -0
- package/src/compiler/types.ts +209 -0
- package/src/compiler/utils.ts +5 -0
- package/src/compiler/vite-plugin.ts +324 -0
- package/src/components/link.tsx +38 -0
- package/src/i18n/client.ts +403 -0
- package/src/i18n/element.ts +44 -0
- package/src/i18n/server.ts +56 -0
- package/src/index.ts +28 -0
- package/src/preact/client-dev.ts +5 -0
- package/src/preact/client.ts +87 -0
- package/src/preact/context.ts +41 -0
- package/src/preact/server.ts +186 -0
- package/src/preact/signals.ts +89 -0
- package/src/preact/static-html.ts +45 -0
- package/src/preact/types.ts +26 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-present, ZUNQ.com
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,17 +1,26 @@
|
|
|
1
|
-
|
|
1
|
+
<h1 align="left">Duto<br/><a href="https://pr.new/attla/duto"><img align="right" src="https://developer.stackblitz.com/img/start_pr_dark_small.svg" alt="Start new PR in StackBlitz Codeflow"></a><a href="https://npmjs.com/package/duto"><img align="right" src="https://img.shields.io/npm/v/duto.svg" alt="npm package"></a></h1>
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
```
|
|
3
|
+
> ⚠️ Duto is under ALFA development — expect updates, rough edges, and occasional breack changes.
|
|
4
|
+
<br/>
|
|
6
5
|
|
|
7
|
-
|
|
6
|
+
This framework is fully geared towards the serverless world, specifically for static pages.
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
- 💡 SSG
|
|
9
|
+
- ⚡️ Dynamic static routes
|
|
10
|
+
- 📦 Optimized Build
|
|
12
11
|
|
|
13
|
-
|
|
12
|
+
[Read the Docs to Learn More](https://github.com/attla/duto/blob/main/DOCS.md)
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
## Ecosystem Packages
|
|
15
|
+
|
|
16
|
+
| Package | Version | Description |
|
|
17
|
+
| -: | :- | :- |
|
|
18
|
+
| [rajt](https://github.com/attla/rajt) | [](https://npm.im/rajt) | 🧱 The core of Rajt serverless framework. |
|
|
19
|
+
| [create-rajt](https://github.com/attla/create-rajt) | [](https://npm.im/create-rajt) | 📁 CLI tool for create new Rajt projects, from starter templates. |
|
|
20
|
+
| [forj](https://github.com/attla/forj) | [](https://npm.im/forj) | 🗂️ Just another query builder in typescript |
|
|
21
|
+
| [t0n](https://github.com/attla/t0n) | [](https://npm.im/t0n) | 🧰 Collection of elegant typescript resources for web artisans. |
|
|
22
|
+
| [cripta](https://github.com/attla/cripta) | [](https://npm.im/cripta) | 🧛 A layer of encryption a little too judicious. |
|
|
23
|
+
|
|
24
|
+
## License
|
|
25
|
+
|
|
26
|
+
This package is licensed under the [MIT license](https://github.com/attla/duto/blob/main/LICENSE) © [Zunq](https://zunq.com)
|
package/package.json
CHANGED
|
@@ -1,4 +1,58 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "duto",
|
|
3
|
-
"
|
|
2
|
+
"name": "duto",
|
|
3
|
+
"description": "Static framework for web artisans",
|
|
4
|
+
"version": "0.260808.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/index.ts",
|
|
7
|
+
"files": ["src"],
|
|
8
|
+
"bin": "./src/cli/index.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": "./src/index.ts"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@preact/signals": "^2.9.2",
|
|
14
|
+
"@swc/core": "^1.15.21",
|
|
15
|
+
"chokidar": "^3.5.2",
|
|
16
|
+
"citty": "^0.2.2",
|
|
17
|
+
"consola": "^3.4.2",
|
|
18
|
+
"filesize": "^11.0",
|
|
19
|
+
"pathe": "^2.0",
|
|
20
|
+
"preact": "^10.29.7",
|
|
21
|
+
"preact-render-to-string": "^6.6.6",
|
|
22
|
+
"t0n": "0.x",
|
|
23
|
+
"vite": "^8.0.0",
|
|
24
|
+
"wouter-preact": "^3.10.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@minify-html/node": "^0.18.1",
|
|
28
|
+
"@preact/preset-vite": "^2.10.5",
|
|
29
|
+
"@tailwindcss/vite": "^4.3.1",
|
|
30
|
+
"@types/node": "^25.9.0",
|
|
31
|
+
"bun-types": "^1.3.14",
|
|
32
|
+
"typescript": "^5.9.3"
|
|
33
|
+
},
|
|
34
|
+
"packageManager": "bun@1.3.14",
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=18.0.0"
|
|
37
|
+
},
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"registry": "https://registry.npmjs.org"
|
|
40
|
+
},
|
|
41
|
+
"author": "Zunq <open-source@zunq.com>",
|
|
42
|
+
"license": "MIT",
|
|
43
|
+
"homepage": "https://zunq.dev",
|
|
44
|
+
"repository": "git://github.com/attla/duto",
|
|
45
|
+
"bugs": "https://github.com/attla/duto/issues",
|
|
46
|
+
"keywords": [
|
|
47
|
+
"SSG",
|
|
48
|
+
"application",
|
|
49
|
+
"framework",
|
|
50
|
+
"router",
|
|
51
|
+
"serverless",
|
|
52
|
+
"typescript",
|
|
53
|
+
"cloudflare",
|
|
54
|
+
"workers",
|
|
55
|
+
"vercel",
|
|
56
|
+
"bun"
|
|
57
|
+
]
|
|
4
58
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { build } from 'vite'
|
|
2
|
+
import { filesize } from 'filesize'
|
|
3
|
+
import { command } from 't0n/cli'
|
|
4
|
+
import { dim, underline } from 't0n/color'
|
|
5
|
+
import { wait, error, rn, event } from 't0n/log'
|
|
6
|
+
import { meta } from '@/vite-plugin'
|
|
7
|
+
import { getConfig } from '~/utils'
|
|
8
|
+
import type { SummaryMetadata } from '@/types'
|
|
9
|
+
|
|
10
|
+
export default command({
|
|
11
|
+
meta: {
|
|
12
|
+
name: 'build',
|
|
13
|
+
description: '🗂️ Perform the build for production\n',
|
|
14
|
+
},
|
|
15
|
+
args: {
|
|
16
|
+
},
|
|
17
|
+
async run({ args }) {
|
|
18
|
+
global.__hmr = !!args?.hmr
|
|
19
|
+
wait('Building..')
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
await build(await getConfig())
|
|
23
|
+
const st = global?.__st ? Math.ceil(performance.now() - global.__st) : 0
|
|
24
|
+
const metadata = meta()
|
|
25
|
+
|
|
26
|
+
const trunc = (str: string, length: number, width = 35) => length > width ? str.slice(0, width - 3) + '...' : str + (length < width ? ' '.repeat(width - length) : '')
|
|
27
|
+
|
|
28
|
+
const format = (row: SummaryMetadata, i: number, max: number, deep: boolean = false) => {
|
|
29
|
+
let str = (deep ? (i === max - 1 ? '└' : '├') : (i === 0 ? '┌' : (i === max - 1 ? '└' : '├'))) + ' '
|
|
30
|
+
|
|
31
|
+
if (!deep) str += (['○', '●', '⌾'][row.type] || '○') + ' '
|
|
32
|
+
const size = filesize(row.sizeBr || 0)
|
|
33
|
+
return dim(str + [row.path, size + ' '.repeat(10 - size.length) + filesize(row.size || 0)].map(col => trunc(col, col.length, deep ? 33 : 35)).join(''))
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
rn()
|
|
37
|
+
console.log(' '+ ['Route', 'Size'].map(col => trunc(dim(underline(col)), col.length)).join(''))
|
|
38
|
+
|
|
39
|
+
let i = 0
|
|
40
|
+
const max = metadata.summary.size
|
|
41
|
+
for (const [, row] of metadata.summary) {
|
|
42
|
+
console.log(format(row, i, max))
|
|
43
|
+
|
|
44
|
+
if (row.children?.size) {
|
|
45
|
+
let i2 = 0
|
|
46
|
+
const max2 = row.children.size
|
|
47
|
+
for (const [, r] of row.children) {
|
|
48
|
+
console.log(dim('├ ')+ format(r, i2, max2, true))
|
|
49
|
+
i2++
|
|
50
|
+
|
|
51
|
+
if (i2 > 3 && max2 > 4) {
|
|
52
|
+
console.log(dim(` [+${max2 - i2} more paths]`))
|
|
53
|
+
break
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
i++
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
rn(2)
|
|
61
|
+
console.log(dim('○ [Static] prerendered as static HTML'))
|
|
62
|
+
console.log(dim('● [SSG] prerendered as static HTML (uses getStaticProps)'))
|
|
63
|
+
console.log(dim('⌾ [CSR] rendered on client side (uses getProps)'))
|
|
64
|
+
rn()
|
|
65
|
+
event(`Builded successfully${st ? dim(` in ${st} ms`) : ''}`)
|
|
66
|
+
} catch (e: any) {
|
|
67
|
+
error(e)
|
|
68
|
+
// error('Build failed:', e?.message || e)
|
|
69
|
+
process.exit(0)
|
|
70
|
+
} finally {
|
|
71
|
+
rn()
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
})
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { defineCommand } from 'citty'
|
|
2
|
+
import { build, preview } from 'vite'
|
|
3
|
+
import { withPort } from 't0n/port'
|
|
4
|
+
import { error, event, log, rn, wait } from 't0n/log'
|
|
5
|
+
import { getConfig } from '~/utils'
|
|
6
|
+
import { _root } from '@/utils'
|
|
7
|
+
import { watch } from 't0n/cli'
|
|
8
|
+
import { spawn, type ChildProcess } from 'node:child_process'
|
|
9
|
+
|
|
10
|
+
export const args = {
|
|
11
|
+
port: {
|
|
12
|
+
alias: 'p',
|
|
13
|
+
description: 'Port to listen on',
|
|
14
|
+
type: 'number',
|
|
15
|
+
default: 3000,
|
|
16
|
+
},
|
|
17
|
+
host: {
|
|
18
|
+
description: 'Host to forward requests to, defaults to the zone of project',
|
|
19
|
+
type: 'string',
|
|
20
|
+
default: 'localhost',
|
|
21
|
+
},
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default defineCommand({
|
|
25
|
+
meta: {
|
|
26
|
+
name: 'dev',
|
|
27
|
+
description: '🧪 Start dev server\n',
|
|
28
|
+
}, // @ts-ignore
|
|
29
|
+
args,
|
|
30
|
+
async run({ args }) {
|
|
31
|
+
const config = await getConfig()
|
|
32
|
+
const desiredPort = args.port ? Number(args.port) : (config?.preview?.port ? Number(config.preview.port) : 3000)
|
|
33
|
+
const host = args.host ? String(args.host) : (config?.preview?.host ? config.preview.host : 'localhost')
|
|
34
|
+
|
|
35
|
+
// const _build = async () => {
|
|
36
|
+
// return await build(await getConfig())
|
|
37
|
+
// }
|
|
38
|
+
|
|
39
|
+
// let buildProcess: ChildProcess | null = null
|
|
40
|
+
// const _build = async () => {
|
|
41
|
+
// if (buildProcess) await killProcess(buildProcess)
|
|
42
|
+
|
|
43
|
+
// return new Promise<void>((resolve, reject) => {
|
|
44
|
+
// let timeoutId: NodeJS.Timeout | null = null
|
|
45
|
+
|
|
46
|
+
// buildProcess = spawn('duto', ['build'], {
|
|
47
|
+
// // stdio: 'ignore',
|
|
48
|
+
// stdio: ['inherit', 'ignore', 'inherit', 'ipc'],
|
|
49
|
+
// shell: process.platform === 'win32'
|
|
50
|
+
// })
|
|
51
|
+
|
|
52
|
+
// buildProcess.on('close', (code) => {
|
|
53
|
+
// if (timeoutId) clearTimeout(timeoutId)
|
|
54
|
+
// buildProcess = null
|
|
55
|
+
|
|
56
|
+
// if (code === 0) {
|
|
57
|
+
// resolve()
|
|
58
|
+
// } else {
|
|
59
|
+
// reject(new Error(`Build failed with code ${code}`))
|
|
60
|
+
// }
|
|
61
|
+
// })
|
|
62
|
+
|
|
63
|
+
// buildProcess.on('error', (error) => {
|
|
64
|
+
// if (timeoutId) clearTimeout(timeoutId)
|
|
65
|
+
// buildProcess = null
|
|
66
|
+
// reject(error)
|
|
67
|
+
// })
|
|
68
|
+
// })
|
|
69
|
+
// }
|
|
70
|
+
|
|
71
|
+
// const killProcess = async (app: ChildProcess | null) => {
|
|
72
|
+
// if (!app) return null
|
|
73
|
+
// // event('Stopping..')
|
|
74
|
+
// try {
|
|
75
|
+
// if (!app?.killed) {
|
|
76
|
+
// app.kill('SIGTERM')
|
|
77
|
+
// await wait(1000)
|
|
78
|
+
|
|
79
|
+
// if (!app?.killed) { // force kill
|
|
80
|
+
// app.kill('SIGKILL')
|
|
81
|
+
// await wait(1000)
|
|
82
|
+
// }
|
|
83
|
+
// }
|
|
84
|
+
|
|
85
|
+
// return null
|
|
86
|
+
// } catch (e) {
|
|
87
|
+
// error('Error stopping:', e)
|
|
88
|
+
// }
|
|
89
|
+
|
|
90
|
+
// return null
|
|
91
|
+
// }
|
|
92
|
+
|
|
93
|
+
let buildProcess: Bun.Subprocess | null = null
|
|
94
|
+
|
|
95
|
+
const _build = async () => {
|
|
96
|
+
if (buildProcess) await killProcess(buildProcess)
|
|
97
|
+
buildProcess = Bun.spawn(['duto', 'build'], {stdin: 'inherit', stdout: 'ignore', stderr: 'inherit'})
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
const code = await buildProcess.exited
|
|
101
|
+
|
|
102
|
+
if (code !== 0)
|
|
103
|
+
throw new Error(`Build failed with code ${code}`)
|
|
104
|
+
} finally {
|
|
105
|
+
buildProcess = null
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const killProcess = async (proc: Bun.Subprocess | null) => {
|
|
110
|
+
if (!proc || proc.exitCode !== null) return
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
proc.kill('SIGTERM')
|
|
114
|
+
|
|
115
|
+
const exited = await Promise.race([
|
|
116
|
+
proc.exited.then(() => true),
|
|
117
|
+
new Promise(r => setTimeout(r, 1000)).then(() => false),
|
|
118
|
+
])
|
|
119
|
+
|
|
120
|
+
if (!exited && proc.exitCode === null) {
|
|
121
|
+
proc.kill('SIGKILL')
|
|
122
|
+
await proc.exited
|
|
123
|
+
}
|
|
124
|
+
} catch (e) {
|
|
125
|
+
error('Error stopping:', e)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
withPort(desiredPort, async (port) => {
|
|
130
|
+
const ignore = [
|
|
131
|
+
/(^|[\/\\])\.(duto|git|cache|vscode|idea|next|nuxt|nyc_output)([\/\\]|$)/,
|
|
132
|
+
/(^|[\/\\])(node_modules|tmp|dist|build|out|coverage)([\/\\]|$)/,
|
|
133
|
+
|
|
134
|
+
/\.(DS_Store|localized|tmp|temp|log|pid|swp|swo)$/,
|
|
135
|
+
/(Thumbs\.db|desktop\.ini)$/,
|
|
136
|
+
/~$/,
|
|
137
|
+
|
|
138
|
+
/(^|[\/\\])\.(Spotlight-V100|Trashes)([\/\\]|$)/,
|
|
139
|
+
/(^|[\/\\])__MACOSX([\/\\]|$)/,
|
|
140
|
+
]
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
await _build()
|
|
144
|
+
|
|
145
|
+
const clients = new Set<ReadableStreamDefaultController<string>>()
|
|
146
|
+
const reload = () => {
|
|
147
|
+
for (const client of clients)
|
|
148
|
+
client.enqueue('data: reload\n\n')
|
|
149
|
+
}
|
|
150
|
+
Bun.serve({
|
|
151
|
+
port: 1322,
|
|
152
|
+
|
|
153
|
+
fetch(req) {
|
|
154
|
+
const headers = {
|
|
155
|
+
'Access-Control-Allow-Origin': '*',
|
|
156
|
+
'Access-Control-Allow-Methods': 'GET',
|
|
157
|
+
'Access-Control-Allow-Headers': '*',
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (req.method === 'OPTIONS')
|
|
161
|
+
return new Response(null, { status: 204, headers })
|
|
162
|
+
|
|
163
|
+
const { pathname } = new URL(req.url)
|
|
164
|
+
|
|
165
|
+
if (pathname !== '/events')
|
|
166
|
+
return new Response(null, { status: 404, headers })
|
|
167
|
+
|
|
168
|
+
const stream = new ReadableStream<string>({
|
|
169
|
+
start(controller) {
|
|
170
|
+
clients.add(controller)
|
|
171
|
+
|
|
172
|
+
req.signal.addEventListener('abort', () => {
|
|
173
|
+
clients.delete(controller)
|
|
174
|
+
controller.close()
|
|
175
|
+
})
|
|
176
|
+
},
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
return new Response(stream, {
|
|
180
|
+
headers: {
|
|
181
|
+
...headers,
|
|
182
|
+
'Content-Type': 'text/event-stream',
|
|
183
|
+
'Cache-Control': 'no-cache',
|
|
184
|
+
'Connection': 'keep-alive',
|
|
185
|
+
},
|
|
186
|
+
})
|
|
187
|
+
},
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
await preview({ ...config, preview: { port, host } })
|
|
191
|
+
|
|
192
|
+
log(`Running on http://${host}:${port}`)
|
|
193
|
+
|
|
194
|
+
watch(async () => {
|
|
195
|
+
await _build()
|
|
196
|
+
reload()
|
|
197
|
+
}, '.', _root, (path: string) => {
|
|
198
|
+
for (const pattern of ignore)
|
|
199
|
+
if (pattern.test(path)) return true
|
|
200
|
+
|
|
201
|
+
return false
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
} catch (e: any) {
|
|
205
|
+
error(e)
|
|
206
|
+
process.exit(0)
|
|
207
|
+
} finally {
|
|
208
|
+
rn()
|
|
209
|
+
}
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
},
|
|
213
|
+
})
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { command, watch, killProcess } from 't0n/cli'
|
|
2
|
+
import { dim } from 't0n/color'
|
|
3
|
+
import { error, log, rn } from 't0n/log'
|
|
4
|
+
import { withPort } from 't0n/port'
|
|
5
|
+
import { getMetadata } from '@/meta'
|
|
6
|
+
import { _root } from '@/utils'
|
|
7
|
+
import { getConfig, preview, reload } from '~/utils'
|
|
8
|
+
|
|
9
|
+
export const args = {
|
|
10
|
+
port: {
|
|
11
|
+
alias: 'p',
|
|
12
|
+
description: 'Port to listen on',
|
|
13
|
+
type: 'number',
|
|
14
|
+
default: 3000,
|
|
15
|
+
},
|
|
16
|
+
host: {
|
|
17
|
+
description: 'Host to forward requests to, defaults to the zone of project',
|
|
18
|
+
type: 'string',
|
|
19
|
+
default: 'localhost',
|
|
20
|
+
},
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default command({
|
|
24
|
+
meta: {
|
|
25
|
+
name: 'dev',
|
|
26
|
+
description: '🧪 Start dev server\n',
|
|
27
|
+
}, // @ts-ignore
|
|
28
|
+
args,
|
|
29
|
+
async run({ args }) {
|
|
30
|
+
const config = await getConfig()
|
|
31
|
+
const desiredPort = args.port ? Number(args.port) : (config?.preview?.port ? Number(config.preview.port) : 3000)
|
|
32
|
+
const host = args.host ? String(args.host) : (config?.preview?.host ? String(config.preview.host) : 'localhost')
|
|
33
|
+
|
|
34
|
+
let buildProcess: Bun.Subprocess | null = null
|
|
35
|
+
|
|
36
|
+
const _build = async () => {
|
|
37
|
+
if (buildProcess) await killProcess(buildProcess)
|
|
38
|
+
buildProcess = Bun.spawn(['duto', 'build', '--hmr'], {stdin: 'inherit', stdout: 'ignore', stderr: 'inherit'})
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const code = await buildProcess.exited
|
|
42
|
+
|
|
43
|
+
if (code !== 0)
|
|
44
|
+
throw new Error(`Build failed with code ${code}`)
|
|
45
|
+
} finally {
|
|
46
|
+
buildProcess = null
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
withPort(desiredPort, async (port) => {
|
|
51
|
+
const ignore = [
|
|
52
|
+
/(^|[\/\\])\.(duto|git|cache|vscode|idea|next|nuxt|nyc_output)([\/\\]|$)/,
|
|
53
|
+
/(^|[\/\\])(node_modules|tmp|dist|build|out|coverage)([\/\\]|$)/,
|
|
54
|
+
|
|
55
|
+
/\.(DS_Store|localized|tmp|temp|log|pid|swp|swo)$/,
|
|
56
|
+
/(Thumbs\.db|desktop\.ini)$/,
|
|
57
|
+
/~$/,
|
|
58
|
+
|
|
59
|
+
/(^|[\/\\])\.(Spotlight-V100|Trashes)([\/\\]|$)/,
|
|
60
|
+
/(^|[\/\\])__MACOSX([\/\\]|$)/,
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
await _build()
|
|
65
|
+
await preview(await getMetadata(config.root as string, config.pagesDir as string), host, port)
|
|
66
|
+
|
|
67
|
+
log(`Running on http://${host}:${port}${global?.__st ? dim(` in ${Math.ceil(performance.now() - global.__st)} ms`) : ''}`)
|
|
68
|
+
|
|
69
|
+
watch(async () => {
|
|
70
|
+
await _build()
|
|
71
|
+
reload()
|
|
72
|
+
}, '.', _root, (path: string) => {
|
|
73
|
+
for (const pattern of ignore)
|
|
74
|
+
if (pattern.test(path)) return true
|
|
75
|
+
|
|
76
|
+
return false
|
|
77
|
+
})
|
|
78
|
+
} catch (e: any) {
|
|
79
|
+
error(e)
|
|
80
|
+
process.exit(0)
|
|
81
|
+
} finally {
|
|
82
|
+
rn()
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
},
|
|
87
|
+
})
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { command } from 't0n/cli'
|
|
2
|
+
import { dim } from 't0n/color'
|
|
3
|
+
import { error, log, rn } from 't0n/log'
|
|
4
|
+
import { withPort } from 't0n/port'
|
|
5
|
+
import { getMetadata } from '@/meta'
|
|
6
|
+
import { _root } from '@/utils'
|
|
7
|
+
import { args } from './dev'
|
|
8
|
+
import { getConfig, preview } from '~/utils'
|
|
9
|
+
|
|
10
|
+
export default command({
|
|
11
|
+
meta: {
|
|
12
|
+
name: 'preview',
|
|
13
|
+
description: '💻 Locally preview production build\n',
|
|
14
|
+
}, // @ts-ignore
|
|
15
|
+
args,
|
|
16
|
+
async run({ args }) {
|
|
17
|
+
const config = await getConfig()
|
|
18
|
+
const desiredPort = args.port ? Number(args.port) : (config?.preview?.port ? Number(config.preview.port) : 3000)
|
|
19
|
+
const host = args.host ? String(args.host) : (config?.preview?.host ? config.preview.host as string : 'localhost')
|
|
20
|
+
|
|
21
|
+
withPort(desiredPort, async (port) => {
|
|
22
|
+
try {
|
|
23
|
+
await preview(await getMetadata(config.root as string, config.pagesDir as string), host, port)
|
|
24
|
+
log(`Running on http://${host}:${port}${global?.__st ? dim(` in ${Math.ceil(performance.now() - global.__st)} ms`) : ''}`)
|
|
25
|
+
} catch (e: any) {
|
|
26
|
+
error('Preview failed:', e?.message || e)
|
|
27
|
+
process.exit(0)
|
|
28
|
+
} finally {
|
|
29
|
+
rn()
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
},
|
|
33
|
+
})
|
package/src/cli/index.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
if (!process?.isBun || typeof Bun === 'undefined') {
|
|
4
|
+
console.error('Error: "Bun" is not available. Please install:')
|
|
5
|
+
console.log(' curl -fsSL https://bun.sh/install | bash')
|
|
6
|
+
process.exit(0)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
global.__st = performance.now()
|
|
10
|
+
|
|
11
|
+
import { join } from 'pathe'
|
|
12
|
+
import { cli } from 't0n/cli'
|
|
13
|
+
import { version } from '!/package.json'
|
|
14
|
+
|
|
15
|
+
if (typeof document === 'undefined' && import.meta.main) {
|
|
16
|
+
const cmd = (c: string) => join(import.meta.dirname, 'commands', c)
|
|
17
|
+
|
|
18
|
+
cli({
|
|
19
|
+
name: 'duto',
|
|
20
|
+
description: 'Duto CLI',
|
|
21
|
+
version,
|
|
22
|
+
commands: {
|
|
23
|
+
dev: cmd('dev'),
|
|
24
|
+
build: cmd('build'),
|
|
25
|
+
preview: cmd('preview'),
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
}
|