duto 0.260915.0 → 0.261001.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 (49) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/src/commands/new.ts +55 -30
  4. package/src/commands/templates/pages/README.md +21 -0
  5. package/src/commands/templates/pages/_gitignore +24 -0
  6. package/src/commands/templates/pages/components/locale-select.tsx +59 -0
  7. package/src/commands/templates/pages/components/select.tsx +95 -0
  8. package/src/commands/templates/pages/i18n/en.ts +17 -0
  9. package/src/commands/templates/pages/i18n/index.ts +19 -0
  10. package/src/commands/templates/pages/i18n/pt-br.ts +17 -0
  11. package/src/commands/templates/pages/package.json +22 -0
  12. package/src/commands/templates/pages/pages/404.tsx +12 -0
  13. package/src/commands/templates/pages/pages/index.tsx +16 -0
  14. package/src/commands/templates/pages/pages/layout.tsx +24 -0
  15. package/src/commands/templates/pages/style.css +24 -0
  16. package/src/commands/templates/pages/tsconfig.json +54 -0
  17. package/src/commands/templates/pages/types.ts +8 -0
  18. package/src/commands/templates/server/base/.editorconfig +21 -0
  19. package/src/commands/templates/{base → server/base}/README.md +1 -1
  20. package/src/pages/cli/commands/dev.ts +10 -4
  21. package/src/pages/cli/utils.ts +3 -22
  22. package/src/pages/client/app.tsx +26 -73
  23. package/src/pages/client/router.ts +14 -280
  24. package/src/pages/compiler/aliases.ts +2 -8
  25. package/src/pages/compiler/ast.ts +1 -538
  26. package/src/pages/compiler/constants.ts +95 -13
  27. package/src/pages/compiler/index.ts +20 -126
  28. package/src/pages/compiler/meta.ts +38 -227
  29. package/src/pages/compiler/routes.ts +4 -5
  30. package/src/pages/compiler/types.ts +1 -101
  31. package/src/pages/compiler/vite-plugin.ts +73 -27
  32. package/src/pages/i18n/client.ts +18 -6
  33. package/src/pages/i18n/server.ts +21 -6
  34. package/src/pages/index.ts +1 -2
  35. package/src/server/routes.ts +4 -26
  36. package/src/utils.ts +34 -3
  37. package/src/pages/compiler/island.ts +0 -108
  38. /package/src/commands/templates/{base → pages}/.editorconfig +0 -0
  39. /package/src/commands/templates/{base → server/base}/.env.dev.example +0 -0
  40. /package/src/commands/templates/{base → server/base}/.env.prod.example +0 -0
  41. /package/src/commands/templates/{base → server/base}/_gitignore +0 -0
  42. /package/src/commands/templates/{base → server/base}/actions/blog/index.get.ts +0 -0
  43. /package/src/commands/templates/{base → server/base}/actions/blog/post/[id].get.ts +0 -0
  44. /package/src/commands/templates/{base → server/base}/actions/index.get.ts +0 -0
  45. /package/src/commands/templates/{base → server/base}/configs/duto.ts +0 -0
  46. /package/src/commands/templates/{base → server/base}/configs/roles.ts +0 -0
  47. /package/src/commands/templates/{base → server/base}/package.json +0 -0
  48. /package/src/commands/templates/{base → server/base}/tsconfig.json +0 -0
  49. /package/src/commands/templates/{workerd → server/workerd}/wrangler.toml +0 -0
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <h1 align="left">
2
- 🛢 Duto &nbsp;
2
+ ⛳️ Duto &nbsp;
3
3
  <a href="https://npmjs.com/package/duto"><img src="https://img.shields.io/npm/v/duto.svg" alt="npm package"></a>
4
4
  <br/>
5
5
  <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>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "duto",
3
3
  "description": "All-in-one serverless framework for web artisans",
4
- "version": "0.260915.0",
4
+ "version": "0.261001.0",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
7
7
  "types": "./src/index.d.ts",
@@ -1,7 +1,7 @@
1
- import { basename, join, relative, resolve } from 'pathe'
1
+ import { basename, join, resolve } from 'pathe'
2
2
  import { command } from 't0n/cli'
3
- import { bold, dim, green, red } from 't0n/color'
4
- import { event, error, step, log, rn } from 't0n/log'
3
+ import { bold, dim, red } from 't0n/color'
4
+ import { event, step, rn } from 't0n/log'
5
5
  import { _root } from '@/utils'
6
6
 
7
7
  import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs'
@@ -11,6 +11,7 @@ import select from '@inquirer/select'
11
11
  import { createSpinner } from 'nanospinner'
12
12
  import { exit } from 'node:process'
13
13
 
14
+ const types = ['pages', 'server']
14
15
  const templates = [
15
16
  'aws',
16
17
  'workerd',
@@ -63,6 +64,11 @@ export default command({
63
64
  type: 'boolean',
64
65
  default: false,
65
66
  },
67
+ type: {
68
+ description: 'Project type',
69
+ type: 'enum',
70
+ options: types,
71
+ },
66
72
  template: {
67
73
  alias: 't',
68
74
  description: 'Template to use',
@@ -71,61 +77,80 @@ export default command({
71
77
  },
72
78
  },
73
79
  async run({ args }) { // @ts-ignore
74
- const root = process.cwd() || process.env?.PWD || process.stdin?.path
75
80
  let dir = args._[0]
76
81
  if (dir) {
77
82
  step(bold('Using target directory: '+ dim('/'+ dir.replace(/^\//, ''))))
78
83
  } else {
79
- dir = await input({ message: 'Target directory', default: root })
80
- if (!dir.startsWith(root))
81
- dir = join(root, dir)
84
+ dir = await input({ message: 'Target directory', default: _root })
85
+ if (!dir.startsWith(_root))
86
+ dir = join(_root, dir)
82
87
  }
83
88
 
84
- const name = basename(/^(\.\/|\.\\|\.)$/.test(dir) ? root : dir)
85
- const template = args.template || (await select({
89
+ const type = args.type || (await select({
86
90
  loop: true,
87
- message: 'Which template do you want to use?',
88
- choices: templates.map(v => ({ title: v, value: v})),
89
- default: 'workerd',
91
+ message: 'Which type of project is it?',
92
+ choices: types.map(v => ({ title: v, value: v})),
90
93
  }))
91
94
 
92
- if (!template)
93
- throw new Error('No template selected')
95
+ if (!type)
96
+ throw new Error('No type selected')
97
+ if (!types.includes(type))
98
+ throw new Error(`Invalid type selected: ${type}`)
99
+
100
+ const dirs = [type === 'server' ? 'base' : '']
101
+ const name = basename(/^(\.\/|\.\\|\.)$/.test(dir) ? _root : dir)
102
+
103
+ if (type === 'server') {
104
+ const template = args.template || (await select({
105
+ loop: true,
106
+ message: 'Which template do you want to use?',
107
+ choices: templates.map(v => ({ title: v, value: v})),
108
+ default: 'workerd',
109
+ }))
94
110
 
95
- if (!templates.includes(template))
96
- throw new Error(`Invalid template selected: ${template}`)
111
+ if (!template)
112
+ throw new Error('No template selected')
113
+ if (!templates.includes(template))
114
+ throw new Error(`Invalid template selected: ${template}`)
97
115
 
98
- if (existsSync(dir) && readdirSync(dir).length > 0) {
99
- const response = await confirm({ message: 'Directory not empty. Continue?', default: false })
100
- if (!response) process.exit(1)
116
+ dirs.push(template)
117
+ }
118
+
119
+ if (
120
+ existsSync(dir) && readdirSync(dir).length > 0
121
+ && !(await confirm({ message: 'Directory not empty. Continue?', default: false }))
122
+ ) {
123
+ process.exit(1)
101
124
  } else {
102
125
  mkdirp(dir)
103
126
  }
104
127
 
105
- const spinner = createSpinner('Cloning the template').start()
106
- for (const t of ['base', template]) {
107
- const templateDir = resolve(import.meta.dir, './templates/'+ t)
128
+ const spinner = createSpinner('Cloning project..').start()
129
+ for (const d of dirs) {
130
+ const templateDir = resolve(import.meta.dir, `./templates/${type}/`+ d)
108
131
  const files = readdirSync(templateDir)
109
- for (const file of files.filter(f => f !== 'package.json'))
132
+ for (const file of files)
110
133
  copy(join(templateDir, file), join(dir, renameFiles[file] ?? file))
111
134
  }
112
135
 
113
- spinner.success()
136
+ spinner.success(bold('Cloned project'))
114
137
 
115
- const pkgPath = resolve(__dirname, `./templates/base/package.json`)
138
+ const pkgPath = join(dir, 'package.json')
116
139
  if (existsSync(pkgPath)) {
117
140
  const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))
118
141
  pkg.name = name
119
142
  writeFileSync(join(dir, 'package.json'), JSON.stringify(pkg, null, 2))
120
143
  }
121
144
 
122
- if (args?.install) {
145
+
146
+ const install = args.install || (await confirm({ message: 'Install dependencies?', default: false }))
147
+ if (install) {
123
148
  const spinner2 = createSpinner('Installing project dependencies').start()
124
149
  const proc = Bun.spawn(['bun', 'i'], { stdout: 'ignore', stderr: 'inherit' })
125
150
  const procExit = await proc.exited
126
151
 
127
152
  if (procExit === 0) {
128
- spinner2.success()
153
+ spinner2.success(bold('Installed dependencies'))
129
154
  } else {
130
155
  spinner2.stop({
131
156
  mark: red('×'),
@@ -135,12 +160,12 @@ export default command({
135
160
  }
136
161
  }
137
162
 
138
- event('New project successfully created')
139
- if (dir != root) {
163
+ event(bold('Project successfully created'))
164
+ if (dir != _root) {
140
165
  rn()
141
166
  console.log(
142
167
  dim('Get started with:'),
143
- bold(`cd ${name}`),
168
+ bold(`cd ${name} && duto dev`),
144
169
  )
145
170
  }
146
171
  },
@@ -0,0 +1,21 @@
1
+ # Duto Pages - Starter Template
2
+
3
+ This template should help get you started developing.
4
+
5
+ ## Install dependencies
6
+
7
+ To install, run:
8
+
9
+ ```bash
10
+ bun i
11
+ ```
12
+
13
+ ## Development environment
14
+
15
+ Test the application locally in an environment identical to production using the command:
16
+
17
+ ```bash
18
+ duto dev
19
+ ```
20
+
21
+ [Read the Full Docs to Learn More](https://hub.bi/docs/duto)
@@ -0,0 +1,24 @@
1
+ # Logs
2
+ logs
3
+ *.log
4
+ npm-debug.log*
5
+ yarn-debug.log*
6
+ yarn-error.log*
7
+ pnpm-debug.log*
8
+ lerna-debug.log*
9
+
10
+ node_modules
11
+ dist
12
+ dist-ssr
13
+ *.local
14
+
15
+ # Editor directories and files
16
+ .vscode/*
17
+ !.vscode/extensions.json
18
+ .idea
19
+ .DS_Store
20
+ *.suo
21
+ *.ntvs*
22
+ *.njsproj
23
+ *.sln
24
+ *.sw?
@@ -0,0 +1,59 @@
1
+ import { useSignal } from '@preact/signals'
2
+ import { options } from '@/i18n'
3
+ import { local, getLocale, setLocale, isPrefixed, r } from 'duto'
4
+ import { useLayoutEffect } from 'preact/hooks'
5
+
6
+ import Select from './select'
7
+
8
+ function Languages(props: any) {
9
+ return (
10
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class={[props.class || '', props.className || ''].filter(Boolean).join(' ')}><path d="m5 8 6 6" /><path d="m4 14 6-6 2-3" /><path d="M2 5h12" /><path d="M7 2h1" /><path d="m22 22-5-10-5 10" /><path d="M14 18h6" /></svg>
11
+ )
12
+ }
13
+ export default function LocaleSelect({ class: className = '' }) {
14
+ const localeOptions = Object.entries(options).map(([value, label]) => ({ label, value }))
15
+ const selectedValue = useSignal(getLocale())
16
+ let unsubscribe: (() => void) | null = null
17
+ const handle = (e: any, lang?: string) => {
18
+ lang ??= getLocale()
19
+ selectedValue.value = lang
20
+ }
21
+
22
+ useLayoutEffect(() => {
23
+ unsubscribe = local.subscribe('lang', e => !isPrefixed() && handle(null, e.val as string))
24
+ const lang = getLocale()
25
+ selectedValue.value = lang
26
+
27
+ window.addEventListener('popstate', handle)
28
+ return () => {
29
+ window.removeEventListener('popstate', handle)
30
+ unsubscribe?.()
31
+ }
32
+ }, [])
33
+
34
+ const handleChange = async (e: Event) => {
35
+ const target = e.target as HTMLSelectElement
36
+ if (target) {
37
+ await setLocale(target.value)
38
+ selectedValue.value = target.value
39
+ isPrefixed() && r((window?.location?.pathname || '').split('/').filter(Boolean).slice(1).join('/'), target.value)
40
+ }
41
+ }
42
+
43
+ const containerClasses = [
44
+ 'locale-select overflow-hidden text-ellipsis p-1 rounded-lg hover:bg-neutral-200 hover:text-zinc-900',
45
+ className
46
+ ].filter(Boolean).join(' ')
47
+
48
+ return (
49
+ <Select
50
+ Icon={Languages}
51
+ options={localeOptions}
52
+ value={selectedValue.value}
53
+ class={'outline-none text-ellipsis absolute inset-0 opacity-0 cursor-pointer' + className}
54
+ containerClass={containerClasses}
55
+ id="locale-select"
56
+ onchange={handleChange}
57
+ />
58
+ )
59
+ }
@@ -0,0 +1,95 @@
1
+ import { forwardRef } from 'preact/compat'
2
+
3
+ type OptionObject = {
4
+ label: string
5
+ value: string
6
+ }
7
+
8
+ type Option =
9
+ | OptionObject
10
+ | [value: string, label: string]
11
+
12
+ type Props = {
13
+ placeholder?: string,
14
+ value?: string,
15
+ Icon?: any,
16
+ // icon?: string,
17
+ id?: string,
18
+ class?: string,
19
+ containerClass?: string,
20
+ options: Record<string, Option[]> | Option[],
21
+ onchange?: (e: Event) => void,
22
+ onclick?: (e: Event) => void,
23
+ required?: boolean,
24
+ ref?: (el: HTMLSelectElement | null) => void,
25
+ }
26
+
27
+ function renderOption(op: Option, value: string) {
28
+ if (Array.isArray(op)) {
29
+ const [val, label] = op
30
+ return <option key={val} value={val}>{label}</option>
31
+ }
32
+
33
+ return <option key={op.value} value={op.value}>{op.label}</option>
34
+ }
35
+
36
+ export default forwardRef<HTMLSelectElement, Props>(function Select({
37
+ placeholder,
38
+ value = '',
39
+ Icon,
40
+ id,
41
+ class: className,
42
+ containerClass,
43
+ options,
44
+ ...props
45
+ }, ref) {
46
+
47
+ return (
48
+ <label
49
+ htmlFor={id}
50
+ class={[
51
+ 'relative inline-flex items-center cursor-pointer select',
52
+ containerClass,
53
+ ].filter(Boolean).join(' ')}
54
+ >
55
+ {Icon && <Icon class="size-6 shrink-0" />}
56
+ {/* {icon && (
57
+ <Icon
58
+ name={icon}
59
+ class="icon label-icon pointer-events-none"
60
+ />
61
+ )} */}
62
+
63
+ <select
64
+ ref={ref}
65
+ id={id}
66
+ value={value}
67
+ autocomplete="off"
68
+ class={className}
69
+ {...props}
70
+ >
71
+ {placeholder && (
72
+ <option disabled value="">{placeholder}</option>
73
+ )}
74
+
75
+ {Array.isArray(options)
76
+ ? options.map(op => renderOption(op, value))
77
+ : Object.entries(options).map(([label, opts]) => (
78
+ <optgroup
79
+ key={label}
80
+ label={label}
81
+ >
82
+ {opts.map(op => renderOption(op, value))}
83
+ </optgroup>
84
+ ))}
85
+ </select>
86
+
87
+ {/* <Icon
88
+ name="lucide:chevron-down"
89
+ class="icon caret"
90
+ style="scale:115% 115%"
91
+ /> */}
92
+ </label>
93
+ )
94
+ })
95
+
@@ -0,0 +1,17 @@
1
+ export default {
2
+ routes: {
3
+ about: 'about',
4
+ shop: 'shop',
5
+ notfound: 'not-found',
6
+ },
7
+ messages: {
8
+ title: 'Welcome to Duto',
9
+ description: 'A fast, lightweight framework that keeps things simple!',
10
+ gotodocs: 'Go to docs',
11
+ notfound: 'Page not found',
12
+ sorry404: 'Sorry, the page you are looking for does not exist.',
13
+ backhome: 'Back to homepage',
14
+ about: 'About',
15
+ shop: 'Shop',
16
+ },
17
+ }
@@ -0,0 +1,19 @@
1
+ export const fallbackLocale = 'pt-br'
2
+ export const locales = ['en', 'pt-br']
3
+
4
+ export const options = {
5
+ en: 'English',
6
+ 'pt-br': 'Português (Brasil)',
7
+ // de: 'Deutsch',
8
+ // es: 'Español',
9
+ // 'zh-cn': '简体中文',
10
+ // 'zh-tw': '正體中文',
11
+ // fr: 'Français',
12
+ // hi: 'हिन्दी',
13
+ // ar: 'العربية',
14
+ // ja: '日本語',
15
+ // ko: '한국어',
16
+ // pl: 'Polski',
17
+ // ru: 'Русский',
18
+ // it: 'Italiano',
19
+ }
@@ -0,0 +1,17 @@
1
+ export default {
2
+ routes: {
3
+ about: 'sobre',
4
+ shop: 'loja',
5
+ notfound: 'pagina-desconhecida',
6
+ },
7
+ messages: {
8
+ title: 'Bem-vindo ao Duto',
9
+ description: 'Um framework rápido e leve que mantém as coisas simples!',
10
+ gotodocs: 'Documentação',
11
+ notfound: 'Página não encontrada',
12
+ sorry404: 'Esta página pode ter sido excluída ou movida...',
13
+ backhome: 'Ir para página inicial',
14
+ about: 'Sobre',
15
+ shop: 'Loja',
16
+ },
17
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "duto-docs",
3
+ "private": true,
4
+ "type": "module",
5
+ "dependencies": {
6
+ "duto": "workspace:*",
7
+ "@preact/signals": "catalog:",
8
+ "preact": "catalog:"
9
+ },
10
+ "devDependencies": {
11
+ "bun-types": "catalog:",
12
+ "tailwindcss": "catalog:",
13
+ "typescript": "catalog:"
14
+ },
15
+ "duto": {
16
+ "type": "pages",
17
+ "platform": "workerd",
18
+ "dev": {
19
+ "port": 3000
20
+ }
21
+ }
22
+ }
@@ -0,0 +1,12 @@
1
+ import { t, Link } from 'duto'
2
+
3
+ export default function NotFound() {
4
+ return (
5
+ <div class="flex flex-col items-center justify-center text-center w-full text-lg py-16">
6
+ <h1 class="text-sky-gratient text-mono text-7xl lg:text-8xl">404</h1>
7
+ <p class="body-large mb-2 mt-4 text-3xl lg:text-4xl">{t`notfound`}</p>
8
+ <p class="body lg:body-large">{t`sorry404`}</p>
9
+ <Link to="/" class="border-b border-current mt-8">{t`backhome`}</Link>
10
+ </div>
11
+ )
12
+ }
@@ -0,0 +1,16 @@
1
+ import { t, Link } from 'duto'
2
+
3
+ export default function Index() {
4
+ return (
5
+ <div class="relative">
6
+ <h1 class="title text-sky-gratient mt-10 lg:mt-2">{t`title`}</h1>
7
+ <p class="sub-title">{t`description`}</p>
8
+ <div class="flex flex-row gap-8 mt-10">
9
+
10
+ <Link href="https://hub.bi/docs/duto" class="group inline-flex shrink-0 items-center justify-center leading-6 font-medium select-none focus:outline-0 gap-2 px-4 py-2.5 text-base rounded-lg bg-white hover:bg-neutral-200 border border-gray-700 text-zinc-900">
11
+ {t`gotodocs`}<span class="ml-0.5 inline-block transition-transform group-hover:translate-x-1">→</span>
12
+ </Link>
13
+ </div>
14
+ </div>
15
+ )
16
+ }
@@ -0,0 +1,24 @@
1
+ import type { ComponentChildren } from 'preact'
2
+
3
+ import LocaleSelect from '@/components/locale-select'
4
+
5
+ export default function Layout({ children }: { children: ComponentChildren }) {
6
+ return (
7
+ <div class="relative w-full max-w-full min-h-svh overflow-hidden flex flex-col flex-1 justify-center items-center bg-gradient-to-br from-zinc-800 to-zinc-950 text-gray-300 select-none">
8
+ <div class='absolute top-10 right-10'>
9
+ <LocaleSelect />
10
+ </div>
11
+
12
+ <main class="flex justify-center items-center px-6 py-16 min-h-[calc(100vh-3.75rem)]" children={children} />
13
+
14
+ <footer class="py-[1.1rem] relative text-base lex flex-row items-center mx-auto">
15
+ <a href="https://hub.bi?ref=duto" class="no-underline group flex items-center justify-center" target="_blank" rel="noopener noreferrer">
16
+ Powered by
17
+ <span class="group-hover:underline flex items-center">
18
+ <svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 20 20" aria-hidden="true" class="mx-1 text-xl" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M16.555 5.412a8.028 8.028 0 0 0-3.503-2.81 14.899 14.899 0 0 1 1.663 4.472 8.547 8.547 0 0 0 1.84-1.662ZM13.326 7.825a13.43 13.43 0 0 0-2.413-5.773 8.087 8.087 0 0 0-1.826 0 13.43 13.43 0 0 0-2.413 5.773A8.473 8.473 0 0 0 10 8.5c1.18 0 2.304-.24 3.326-.675ZM6.514 9.376A9.98 9.98 0 0 0 10 10c1.226 0 2.4-.22 3.486-.624a13.54 13.54 0 0 1-.351 3.759A13.54 13.54 0 0 1 10 13.5c-1.079 0-2.128-.127-3.134-.366a13.538 13.538 0 0 1-.352-3.758ZM5.285 7.074a14.9 14.9 0 0 1 1.663-4.471 8.028 8.028 0 0 0-3.503 2.81c.529.638 1.149 1.199 1.84 1.66ZM17.334 6.798a7.973 7.973 0 0 1 .614 4.115 13.47 13.47 0 0 1-3.178 1.72 15.093 15.093 0 0 0 .174-3.939 10.043 10.043 0 0 0 2.39-1.896ZM2.666 6.798a10.042 10.042 0 0 0 2.39 1.896 15.196 15.196 0 0 0 .174 3.94 13.472 13.472 0 0 1-3.178-1.72 7.973 7.973 0 0 1 .615-4.115ZM10 15c.898 0 1.778-.079 2.633-.23a13.473 13.473 0 0 1-1.72 3.178 8.099 8.099 0 0 1-1.826 0 13.47 13.47 0 0 1-1.72-3.178c.855.151 1.735.23 2.633.23ZM14.357 14.357a14.912 14.912 0 0 1-1.305 3.04 8.027 8.027 0 0 0 4.345-4.345c-.953.542-1.971.981-3.04 1.305ZM6.948 17.397a8.027 8.027 0 0 1-4.345-4.345c.953.542 1.971.981 3.04 1.305a14.912 14.912 0 0 0 1.305 3.04Z"></path></svg>
19
+ HUB</span>
20
+ </a>
21
+ </footer>
22
+ </div>
23
+ )
24
+ }
@@ -0,0 +1,24 @@
1
+ @import "tailwindcss";
2
+
3
+ html, body {
4
+ @apply overscroll-none
5
+ }
6
+
7
+ .text-mono {
8
+ font-family: ui-monospace,SFMono-Regular,monospace;
9
+ --tw-font-weight: 300;
10
+ font-weight: 300
11
+ }
12
+
13
+ .text-sky-gratient {
14
+ @apply bg-clip-text text-transparent;
15
+ background-image: radial-gradient(138.06% 1036.51% at 95.25% -2.54%, rgb(112, 209, 255) 14.06%, rgb(173, 228, 255) 51.02%, rgb(219, 243, 255) 79.09%);
16
+ }
17
+
18
+ .title {
19
+ @apply text-4xl lg:text-[3.5rem] leading-[1.1] font-semibold tracking-tight text-balance
20
+ }
21
+
22
+ .sub-title {
23
+ @apply mt-4 max-w-[60ch] text-lg text-balance md:mt-6 md:text-xl
24
+ }
@@ -0,0 +1,54 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "lib": ["ES2023", "DOM", "DOM.Iterable"],
6
+ "types": ["vite/client", "bun-types", "node"],
7
+
8
+ /* Bundler mode */
9
+ "moduleResolution": "bundler",
10
+ "allowImportingTsExtensions": true,
11
+ "verbatimModuleSyntax": true,
12
+ "moduleDetection": "force",
13
+ "noEmit": true,
14
+ "jsx": "react-jsx",
15
+ "jsxImportSource": "preact",
16
+ "useDefineForClassFields": true,
17
+
18
+ /* Linting */
19
+ "strict": true,
20
+ "skipLibCheck": true,
21
+ "noUnusedLocals": true,
22
+ "noUnusedParameters": true,
23
+ "erasableSyntaxOnly": true,
24
+ "noFallthroughCasesInSwitch": true,
25
+ "noUncheckedSideEffectImports": true,
26
+
27
+ "paths": {
28
+ "react": ["./node_modules/preact/compat/"],
29
+ "react-dom": ["./node_modules/preact/compat/"],
30
+ // "@duto/*": ["./@duto/*"],
31
+ // "duto/*": ["./node_modules/duto/*"],
32
+ // "~icons/*": ["./@iconify-json/*"],
33
+ "@/*": ["./*"]
34
+ }
35
+ },
36
+ "include": [
37
+ "components",
38
+ "configs",
39
+ "enums",
40
+ "libs",
41
+ "i18n",
42
+ "pages",
43
+ "tests/**/*",
44
+ "utils",
45
+ "i18n.ts",
46
+ "i18n.tsx",
47
+ "config.ts",
48
+ "types.ts"
49
+ ],
50
+ "exclude": [
51
+ ".duto",
52
+ "node_modules"
53
+ ]
54
+ }
@@ -0,0 +1,8 @@
1
+
2
+ export type Posts = Post[]
3
+ export type Post = {
4
+ id: number | string,
5
+ userid?: string,
6
+ title: string,
7
+ body: string,
8
+ }
@@ -0,0 +1,21 @@
1
+ root = true
2
+
3
+ [*]
4
+ charset = utf-8
5
+ end_of_line = lf
6
+ insert_final_newline = true
7
+ trim_trailing_whitespace = true
8
+ indent_style = space
9
+ indent_size = 2
10
+
11
+ # [*.{js,jsx,ts,tsx,json,yaml,yml}]
12
+ # Inherits all settings from [*] and applies to these extensions
13
+
14
+ [*.{json,jsonc}]
15
+ insert_final_newline = false
16
+
17
+ [*.md]
18
+ trim_trailing_whitespace = false
19
+
20
+ [Dockerfile]
21
+ indent_size = 4
@@ -1,4 +1,4 @@
1
- # Duto - Starter Template
1
+ # Duto Server - Starter Template
2
2
 
3
3
  This template should help get you started developing.
4
4
 
@@ -26,20 +26,26 @@ export default command({
26
26
  name: 'dev',
27
27
  description: '🧪 Start dev server\n',
28
28
  }, // @ts-ignore
29
- args,
29
+ args: {
30
+ ...args,
31
+ minify: {
32
+ description: 'Minify the result',
33
+ type: 'boolean',
34
+ default: true,
35
+ },
36
+ },
30
37
  async run({ args }) {
31
38
  const config = await getConfig()
32
39
  const duto = getEnv('duto', {})
33
40
  const desiredPort = Number(args.port || duto?.dev?.port || 3000)
34
41
  const host = String(args.host || duto?.dev?.host || 'localhost')
35
- // const desiredPort = args.port ? Number(args.port) : (config?.preview?.port ? Number(config.preview.port) : 3000)
36
- // const host = args.host ? String(args.host) : (config?.preview?.host ? String(config.preview.host) : 'localhost')
37
42
 
43
+ const minify = args.minify ?? true
38
44
  let buildProcess: Bun.Subprocess | null = null
39
45
 
40
46
  const _build = async () => {
41
47
  if (buildProcess) await killProcess(buildProcess)
42
- buildProcess = Bun.spawn(['duto', 'build', '--hmr'], {stdin: 'inherit', stdout: 'ignore', stderr: 'inherit'})
48
+ buildProcess = Bun.spawn(['duto', 'build', '--hmr', minify ? '' : '--no-minify'], {stdin: 'inherit', stdout: 'ignore', stderr: 'inherit'})
43
49
 
44
50
  try {
45
51
  const code = await buildProcess.exited