weifuwu 0.25.2 → 0.27.1

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 (208) hide show
  1. package/README.md +291 -2489
  2. package/ai/provider.ts +129 -0
  3. package/ai/stream.ts +63 -0
  4. package/{dist/cli.d.ts → cli.js} +1 -1
  5. package/cli.ts +55 -257
  6. package/core/cookie.ts +114 -0
  7. package/core/env.ts +142 -0
  8. package/core/logger.ts +72 -0
  9. package/core/router.ts +795 -0
  10. package/core/serve.ts +294 -0
  11. package/core/sse.ts +85 -0
  12. package/core/trace.ts +146 -0
  13. package/graphql.ts +267 -0
  14. package/hub.ts +133 -0
  15. package/index.ts +71 -0
  16. package/mailer.ts +81 -0
  17. package/middleware/compress.ts +103 -0
  18. package/middleware/cors.ts +81 -0
  19. package/middleware/csrf.ts +112 -0
  20. package/middleware/flash.ts +144 -0
  21. package/middleware/health.ts +44 -0
  22. package/middleware/helmet.ts +98 -0
  23. package/middleware/i18n.ts +175 -0
  24. package/middleware/rate-limit.ts +167 -0
  25. package/middleware/request-id.ts +60 -0
  26. package/middleware/static.ts +149 -0
  27. package/middleware/theme.ts +84 -0
  28. package/middleware/upload.ts +168 -0
  29. package/middleware/validate.ts +186 -0
  30. package/package.json +15 -36
  31. package/postgres/client.ts +132 -0
  32. package/postgres/index.ts +4 -0
  33. package/postgres/module.ts +37 -0
  34. package/postgres/schema/columns.ts +186 -0
  35. package/postgres/schema/index.ts +36 -0
  36. package/postgres/schema/sql.ts +39 -0
  37. package/postgres/schema/table.ts +548 -0
  38. package/postgres/schema/where.ts +99 -0
  39. package/postgres/types.ts +48 -0
  40. package/queue/cron.ts +90 -0
  41. package/queue/index.ts +654 -0
  42. package/queue/types.ts +60 -0
  43. package/redis/client.ts +24 -0
  44. package/{dist/redis/index.d.ts → redis/index.ts} +2 -2
  45. package/redis/types.ts +28 -0
  46. package/types.ts +78 -0
  47. package/cli/template/app.ts +0 -22
  48. package/cli/template/index.ts +0 -10
  49. package/cli/template/locales/en.json +0 -13
  50. package/cli/template/locales/zh-CN.json +0 -13
  51. package/cli/template/locales/zh-TW.json +0 -13
  52. package/cli/template/locales/zh.json +0 -13
  53. package/cli/template/ui/app/globals.css +0 -2
  54. package/cli/template/ui/app/layout.tsx +0 -15
  55. package/cli/template/ui/app/page.tsx +0 -124
  56. package/cli/template/ui/components/Greeting.tsx +0 -3
  57. package/dist/agent/client.d.ts +0 -2
  58. package/dist/agent/index.d.ts +0 -2
  59. package/dist/agent/rest.d.ts +0 -14
  60. package/dist/agent/run.d.ts +0 -19
  61. package/dist/agent/types.d.ts +0 -55
  62. package/dist/ai/provider.d.ts +0 -45
  63. package/dist/ai/utils.d.ts +0 -5
  64. package/dist/ai/workflow.d.ts +0 -17
  65. package/dist/ai-sdk.d.ts +0 -2
  66. package/dist/ai.d.ts +0 -13
  67. package/dist/analytics.d.ts +0 -45
  68. package/dist/auth.d.ts +0 -22
  69. package/dist/cache.d.ts +0 -74
  70. package/dist/cli.js +0 -302
  71. package/dist/client-locale.d.ts +0 -25
  72. package/dist/client-pref.d.ts +0 -3
  73. package/dist/client-router.d.ts +0 -300
  74. package/dist/client-state.d.ts +0 -22
  75. package/dist/client-theme.d.ts +0 -36
  76. package/dist/compile.d.ts +0 -15
  77. package/dist/compress.d.ts +0 -20
  78. package/dist/cookie.d.ts +0 -36
  79. package/dist/cors.d.ts +0 -25
  80. package/dist/cron-utils.d.ts +0 -73
  81. package/dist/csrf.d.ts +0 -47
  82. package/dist/deploy/config.d.ts +0 -2
  83. package/dist/deploy/gateway.d.ts +0 -2
  84. package/dist/deploy/index.d.ts +0 -4
  85. package/dist/deploy/manager.d.ts +0 -16
  86. package/dist/deploy/process.d.ts +0 -14
  87. package/dist/deploy/types.d.ts +0 -53
  88. package/dist/env.d.ts +0 -69
  89. package/dist/error-boundary.d.ts +0 -2
  90. package/dist/flash.d.ts +0 -90
  91. package/dist/fts.d.ts +0 -36
  92. package/dist/graphql.d.ts +0 -16
  93. package/dist/head.d.ts +0 -6
  94. package/dist/health.d.ts +0 -24
  95. package/dist/helmet.d.ts +0 -33
  96. package/dist/html-shell.d.ts +0 -1
  97. package/dist/hub.d.ts +0 -37
  98. package/dist/i18n.d.ts +0 -39
  99. package/dist/iii/client.d.ts +0 -2
  100. package/dist/iii/index.d.ts +0 -4
  101. package/dist/iii/register-worker.d.ts +0 -9
  102. package/dist/iii/rest.d.ts +0 -3
  103. package/dist/iii/stream.d.ts +0 -82
  104. package/dist/iii/types.d.ts +0 -121
  105. package/dist/iii/worker.d.ts +0 -2
  106. package/dist/iii/ws.d.ts +0 -22
  107. package/dist/index.d.ts +0 -101
  108. package/dist/index.js +0 -12752
  109. package/dist/kb/index.d.ts +0 -3
  110. package/dist/kb/types.d.ts +0 -72
  111. package/dist/layout.d.ts +0 -2
  112. package/dist/live.d.ts +0 -7
  113. package/dist/logdb/client.d.ts +0 -2
  114. package/dist/logdb/index.d.ts +0 -2
  115. package/dist/logdb/rest.d.ts +0 -5
  116. package/dist/logdb/types.d.ts +0 -27
  117. package/dist/logger.d.ts +0 -16
  118. package/dist/mailer.d.ts +0 -51
  119. package/dist/mcp.d.ts +0 -34
  120. package/dist/messager/agent.d.ts +0 -11
  121. package/dist/messager/client.d.ts +0 -2
  122. package/dist/messager/index.d.ts +0 -2
  123. package/dist/messager/rest.d.ts +0 -15
  124. package/dist/messager/types.d.ts +0 -57
  125. package/dist/messager/ws.d.ts +0 -14
  126. package/dist/module-server.d.ts +0 -9
  127. package/dist/not-found.d.ts +0 -2
  128. package/dist/notifier/client.d.ts +0 -2
  129. package/dist/notifier/index.d.ts +0 -2
  130. package/dist/notifier/types.d.ts +0 -105
  131. package/dist/opencode/client.d.ts +0 -2
  132. package/dist/opencode/index.d.ts +0 -2
  133. package/dist/opencode/permissions.d.ts +0 -5
  134. package/dist/opencode/prompt.d.ts +0 -8
  135. package/dist/opencode/rest.d.ts +0 -16
  136. package/dist/opencode/run.d.ts +0 -13
  137. package/dist/opencode/session.d.ts +0 -26
  138. package/dist/opencode/skills.d.ts +0 -4
  139. package/dist/opencode/tools/bash.d.ts +0 -6
  140. package/dist/opencode/tools/edit.d.ts +0 -19
  141. package/dist/opencode/tools/glob.d.ts +0 -9
  142. package/dist/opencode/tools/grep.d.ts +0 -17
  143. package/dist/opencode/tools/index.d.ts +0 -12
  144. package/dist/opencode/tools/question.d.ts +0 -5
  145. package/dist/opencode/tools/read.d.ts +0 -16
  146. package/dist/opencode/tools/skill.d.ts +0 -18
  147. package/dist/opencode/tools/web.d.ts +0 -18
  148. package/dist/opencode/tools/write.d.ts +0 -13
  149. package/dist/opencode/types.d.ts +0 -90
  150. package/dist/opencode/ws.d.ts +0 -21
  151. package/dist/permissions.d.ts +0 -51
  152. package/dist/postgres/client.d.ts +0 -4
  153. package/dist/postgres/index.d.ts +0 -4
  154. package/dist/postgres/module.d.ts +0 -17
  155. package/dist/postgres/schema/columns.d.ts +0 -99
  156. package/dist/postgres/schema/index.d.ts +0 -6
  157. package/dist/postgres/schema/sql.d.ts +0 -22
  158. package/dist/postgres/schema/table.d.ts +0 -141
  159. package/dist/postgres/schema/where.d.ts +0 -29
  160. package/dist/postgres/types.d.ts +0 -50
  161. package/dist/queue/index.d.ts +0 -2
  162. package/dist/queue/types.d.ts +0 -62
  163. package/dist/rate-limit.d.ts +0 -45
  164. package/dist/react.d.ts +0 -14
  165. package/dist/react.js +0 -751
  166. package/dist/redis/client.d.ts +0 -2
  167. package/dist/redis/types.d.ts +0 -18
  168. package/dist/request-id.d.ts +0 -40
  169. package/dist/router.d.ts +0 -73
  170. package/dist/s3.d.ts +0 -68
  171. package/dist/seo.d.ts +0 -104
  172. package/dist/serve.d.ts +0 -38
  173. package/dist/server-registry.d.ts +0 -10
  174. package/dist/session.d.ts +0 -117
  175. package/dist/sse.d.ts +0 -47
  176. package/dist/ssr-entries.d.ts +0 -4
  177. package/dist/ssr.d.ts +0 -11
  178. package/dist/static.d.ts +0 -23
  179. package/dist/stream.d.ts +0 -24
  180. package/dist/tailwind.d.ts +0 -15
  181. package/dist/tenant/client.d.ts +0 -2
  182. package/dist/tenant/graphql.d.ts +0 -3
  183. package/dist/tenant/index.d.ts +0 -2
  184. package/dist/tenant/rest.d.ts +0 -3
  185. package/dist/tenant/schema.d.ts +0 -5
  186. package/dist/tenant/types.d.ts +0 -48
  187. package/dist/tenant/utils.d.ts +0 -9
  188. package/dist/test-utils.d.ts +0 -194
  189. package/dist/theme.d.ts +0 -31
  190. package/dist/trace.d.ts +0 -95
  191. package/dist/tsx-context.d.ts +0 -32
  192. package/dist/types.d.ts +0 -47
  193. package/dist/upload.d.ts +0 -55
  194. package/dist/use-action.d.ts +0 -42
  195. package/dist/use-agent-stream.d.ts +0 -49
  196. package/dist/use-flash-message.d.ts +0 -17
  197. package/dist/use-websocket.d.ts +0 -42
  198. package/dist/user/client.d.ts +0 -30
  199. package/dist/user/index.d.ts +0 -2
  200. package/dist/user/oauth-login.d.ts +0 -21
  201. package/dist/user/oauth2.d.ts +0 -31
  202. package/dist/user/types.d.ts +0 -178
  203. package/dist/validate.d.ts +0 -32
  204. package/dist/vendor.d.ts +0 -7
  205. package/dist/webhook.d.ts +0 -79
  206. package/opencode/ui/app/globals.css +0 -1
  207. package/opencode/ui/app/layout.tsx +0 -13
  208. package/opencode/ui/app/page.tsx +0 -523
package/ai/provider.ts ADDED
@@ -0,0 +1,129 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import type { Context, Middleware } from '../types.ts'
3
+ import { createOpenAI } from '@ai-sdk/openai'
4
+ import {
5
+ embed as aiEmbed,
6
+ embedMany as aiEmbedMany,
7
+ generateText as aiGenerateText,
8
+ streamText as aiStreamText,
9
+ type LanguageModel,
10
+ type EmbeddingModel,
11
+ } from 'ai'
12
+
13
+ // Augment Context with ai property
14
+ declare module '../types.ts' {
15
+ interface Context {
16
+ ai: AIProvider
17
+ }
18
+ }
19
+
20
+ // ── Types ───────────────────────────────────────────────────────────────────
21
+
22
+ export interface AIProviderInjected {
23
+ ai: AIProvider
24
+ }
25
+
26
+ export interface AIProviderOptions {
27
+ /** API base URL (default: OPENAI_BASE_URL env or http://localhost:11434/v1). */
28
+ baseURL?: string
29
+ /** API key (default: OPENAI_API_KEY env or 'ollama'). */
30
+ apiKey?: string
31
+ /** Chat model name (default: OPENAI_MODEL env or 'qwen3:0.6b'). */
32
+ model?: string
33
+ /** Embedding model name (default: OPENAI_EMBEDDING_MODEL env or 'qwen3-embedding:0.6b'). */
34
+ embeddingModel?: string
35
+ /** Vector dimension (default: EMBEDDING_DIMENSION env or 1024). */
36
+ embeddingDimension?: number
37
+ }
38
+
39
+ export interface AIProvider {
40
+ /** Get the language model. Caches by default; pass a name to override. */
41
+ model(name?: string): LanguageModel
42
+ /** Get the embedding model. Caches by default; pass a name to override. */
43
+ embeddingModel(name?: string): EmbeddingModel
44
+ /** Embed a single text string into a vector. */
45
+ embed(text: string): Promise<number[]>
46
+ /** Embed multiple text strings in batch. */
47
+ embedMany(texts: string[]): Promise<number[][]>
48
+ /** The configured vector dimension. */
49
+ readonly dimension: number
50
+
51
+ /**
52
+ * Generate text using the configured model.
53
+ * All options are passed through to the AI SDK's `generateText`, with `model` auto-injected.
54
+ */
55
+ generateText(
56
+ params: Omit<Parameters<typeof aiGenerateText>[0], 'model'>,
57
+ ): ReturnType<typeof aiGenerateText>
58
+ /**
59
+ * Stream text using the configured model.
60
+ * All options are passed through to the AI SDK's `streamText`, with `model` auto-injected.
61
+ */
62
+ streamText(
63
+ params: Omit<Parameters<typeof aiStreamText>[0], 'model'>,
64
+ ): ReturnType<typeof aiStreamText>
65
+ }
66
+
67
+ // ── Factory ─────────────────────────────────────────────────────────────────
68
+
69
+ export function aiProvider(
70
+ options?: AIProviderOptions,
71
+ ): Middleware<Context, Context & AIProviderInjected> & AIProvider {
72
+ const baseURL = options?.baseURL ?? process.env.OPENAI_BASE_URL ?? 'http://localhost:11434/v1'
73
+ const apiKey = options?.apiKey ?? process.env.OPENAI_API_KEY ?? 'ollama'
74
+ const modelName = options?.model ?? process.env.OPENAI_MODEL ?? 'qwen3:0.6b'
75
+ const embedModelName =
76
+ options?.embeddingModel ?? process.env.OPENAI_EMBEDDING_MODEL ?? 'qwen3-embedding:0.6b'
77
+ const dimension =
78
+ options?.embeddingDimension ?? parseInt(process.env.EMBEDDING_DIMENSION || '1024', 10)
79
+
80
+ const client = createOpenAI({ baseURL, apiKey })
81
+
82
+ let _model: LanguageModel | undefined
83
+ let _embedModel: EmbeddingModel | undefined
84
+
85
+ const provider: AIProvider = {
86
+ get dimension() {
87
+ return dimension
88
+ },
89
+
90
+ model(name?: string): LanguageModel {
91
+ const m = name ?? modelName
92
+ if (!_model) _model = client(m)
93
+ return _model
94
+ },
95
+
96
+ embeddingModel(name?: string): EmbeddingModel {
97
+ const m = name ?? embedModelName
98
+ if (!_embedModel) _embedModel = client.embedding(m)
99
+ return _embedModel
100
+ },
101
+
102
+ async embed(text: string): Promise<number[]> {
103
+ const result = await aiEmbed({ model: this.embeddingModel(), value: text })
104
+ return result.embedding
105
+ },
106
+
107
+ async embedMany(texts: string[]): Promise<number[][]> {
108
+ const result = await aiEmbedMany({ model: this.embeddingModel(), values: texts })
109
+ return result.embeddings
110
+ },
111
+
112
+ generateText(params: Omit<Parameters<typeof aiGenerateText>[0], 'model'>) {
113
+ return aiGenerateText({ ...params, model: this.model() } as any)
114
+ },
115
+
116
+ streamText(params: Omit<Parameters<typeof aiStreamText>[0], 'model'>) {
117
+ return aiStreamText({ ...params, model: this.model() } as any)
118
+ },
119
+ }
120
+
121
+ const mw: Middleware<Context, Context & AIProviderInjected> = async (req, ctx, next) => {
122
+ ;(ctx as Context & AIProviderInjected).ai = provider
123
+ return next(req, ctx as Context & AIProviderInjected)
124
+ }
125
+ mw.__meta = { injects: ['ai'], depends: [] }
126
+
127
+ return Object.assign(mw, provider) as Middleware<Context, Context & AIProviderInjected> &
128
+ AIProvider
129
+ }
package/ai/stream.ts ADDED
@@ -0,0 +1,63 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import type { Context } from '../types.ts'
3
+ import { Router } from '../core/router.ts'
4
+ import type { AIProvider } from './provider.ts'
5
+
6
+ export type AIHandler = (
7
+ req: Request,
8
+ ctx: Context,
9
+ ) => Record<string, unknown> | Promise<Record<string, unknown>>
10
+
11
+ export const _ai: Record<string, any> = {}
12
+
13
+ async function getStreamText() {
14
+ if (!_ai.streamText) _ai.streamText = (await import('ai')).streamText
15
+ return _ai.streamText
16
+ }
17
+
18
+ async function getStreamObject() {
19
+ if (!_ai.streamObject) _ai.streamObject = (await import('ai')).streamObject
20
+ return _ai.streamObject
21
+ }
22
+
23
+ /**
24
+ * Create a streaming AI endpoint.
25
+ *
26
+ * @param handler - Returns options for `streamText` or `streamObject` (if `schema` is present).
27
+ * @param provider - Optional AI provider. If provided and the handler does not return a `model`,
28
+ * `provider.model()` is used as the default.
29
+ */
30
+ export async function aiStream(handler: AIHandler, provider?: AIProvider): Promise<Router> {
31
+ const r = new Router()
32
+
33
+ r.post('/', async (req, ctx) => {
34
+ let options
35
+ try {
36
+ options = await handler(req, ctx)
37
+ } catch (err) {
38
+ const message = err instanceof Error ? err.message : String(err)
39
+ return new Response(JSON.stringify({ error: message }), {
40
+ status: 500,
41
+ headers: { 'Content-Type': 'application/json' },
42
+ })
43
+ }
44
+
45
+ // Inject default model from provider if handler didn't specify one
46
+ if (provider && !options.model) {
47
+ options.model = provider.model()
48
+ }
49
+
50
+ if (options.schema) {
51
+ const streamObject = await getStreamObject()
52
+ const { schema, ...params } = options
53
+ const result = streamObject({ ...params, schema: schema as any, output: 'object' as const })
54
+ return result.toTextStreamResponse()
55
+ }
56
+
57
+ const streamText = await getStreamText()
58
+ const result = streamText(options)
59
+ return result.toTextStreamResponse()
60
+ })
61
+
62
+ return r
63
+ }
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- export {};
2
+ import('./cli.ts')
package/cli.ts CHANGED
@@ -1,29 +1,23 @@
1
1
  #!/usr/bin/env node
2
2
  /* eslint-disable no-console */
3
- /* eslint-disable @typescript-eslint/no-explicit-any */
4
- import { mkdir, writeFile, copyFile, readFile, cp } from 'node:fs/promises'
5
- import { existsSync, readdirSync } from 'node:fs'
3
+ import { mkdir, writeFile } from 'node:fs/promises'
4
+ import { existsSync } from 'node:fs'
6
5
  import { execSync } from 'node:child_process'
7
- import { homedir } from 'node:os'
8
6
  import { join, dirname, resolve } from 'node:path'
9
7
  import { fileURLToPath } from 'node:url'
8
+ import { parseArgs } from 'node:util'
10
9
 
11
10
  const __filename = fileURLToPath(import.meta.url)
12
11
  const __dirname = dirname(__filename)
13
12
 
14
13
  const pkgRoot = existsSync(join(__dirname, 'package.json')) ? __dirname : resolve(__dirname, '..')
15
14
 
16
- async function readPkg(): Promise<any> {
17
- return JSON.parse(await readFile(join(pkgRoot, 'package.json'), 'utf-8'))
18
- }
19
-
20
- // ── Commands ────────────────────────────────────────────────────────────
21
-
22
- async function cmdSkill() {
23
- const targetDir = join(homedir(), '.agents', 'skills', 'weifuwu')
24
- await mkdir(targetDir, { recursive: true })
25
- await copyFile(join(pkgRoot, 'README.md'), join(targetDir, 'SKILL.md'))
26
- console.log('✅ Installed weifuwu skill to ~/.agents/skills/weifuwu/')
15
+ async function readPkg() {
16
+ return JSON.parse(
17
+ await import('node:fs/promises').then((fs) =>
18
+ fs.readFile(join(pkgRoot, 'package.json'), 'utf-8'),
19
+ ),
20
+ )
27
21
  }
28
22
 
29
23
  async function cmdVersion() {
@@ -31,91 +25,43 @@ async function cmdVersion() {
31
25
  console.log(pkg.version)
32
26
  }
33
27
 
34
- async function cmdInit(name: string, opts: { minimal?: boolean; skipInstall?: boolean }) {
28
+ async function cmdInit(name: string, opts: { skipInstall?: boolean }) {
35
29
  const targetDir = resolve(process.cwd(), name)
36
- const pkg = await readPkg()
37
- const v = pkg.version
38
- // Map of known type package versions that align with our runtime deps
39
- const typeVersions: Record<string, string> = {
40
- '@types/react': '^19',
41
- '@types/react-dom': '^19',
42
- '@types/node': '^22',
43
- }
44
- const depVer = (depName: string) =>
45
- typeVersions[depName] || `^${(pkg.devDependencies?.[depName] || '0.0.0').replace(/^\^/, '')}`
46
-
47
- await mkdir(targetDir, { recursive: true })
48
-
49
- // Copy templates
50
- const templateDir = join(pkgRoot, 'cli', 'template')
51
- await cp(templateDir, targetDir, { recursive: true })
52
30
 
53
- // Rewrite local imports → package imports
54
- for (const file of ['app.ts', 'index.ts']) {
55
- const fp = join(targetDir, file)
56
- let content = await readFile(fp, 'utf-8')
57
- content = content
58
- .replace(/from '\.\.\/\.\.\/index\.ts'/g, "from 'weifuwu'")
59
- .replace(/from '\.\.\/\.\.\/\.\.\/react\.ts'/g, "from 'weifuwu/react'")
60
- await writeFile(fp, content)
61
- }
62
-
63
- // Rewrite UI imports
64
- const uiPage = join(targetDir, 'ui', 'app', 'page.tsx')
65
- if (existsSync(uiPage)) {
66
- let content = await readFile(uiPage, 'utf-8')
67
- content = content.replace(/from '\.\.\/\.\.\/\.\.\/\.\.\/react\.ts'/g, "from 'weifuwu/react'")
68
- await writeFile(uiPage, content)
31
+ if (existsSync(targetDir)) {
32
+ console.error(`Directory ${name} already exists.`)
33
+ process.exit(1)
69
34
  }
70
35
 
71
- // Minimal mode: strip SSR/i18n/theme, keep only HTTP core
72
- if (opts.minimal) {
73
- try {
74
- await rmrf(join(targetDir, 'ui'))
75
- } catch {}
76
- try {
77
- await rmrf(join(targetDir, 'locales'))
78
- } catch {}
36
+ const pkg = await readPkg()
79
37
 
80
- // Write minimal app.ts
81
- await writeFile(
82
- join(targetDir, 'app.ts'),
83
- [
84
- `import { Router } from 'weifuwu'`,
85
- ``,
86
- `export const app = new Router()`,
87
- ``,
88
- `app.get('/', () => new Response('Hello from ${name}!'))`,
89
- `app.get('/api/ping', () => Response.json({ pong: true, time: new Date().toISOString() }))`,
90
- ``,
91
- ].join('\n'),
92
- )
38
+ await mkdir(targetDir, { recursive: true })
93
39
 
94
- // Write minimal index.ts
95
- await writeFile(
96
- join(targetDir, 'index.ts'),
97
- [
98
- `import { serve } from 'weifuwu'`,
99
- `import { app } from './app.ts'`,
100
- ``,
101
- `const port = Number(process.env.PORT) || 3000`,
102
- `serve(app.handler(), { port })`,
103
- ``,
104
- ].join('\n'),
105
- )
106
- }
40
+ await writeFile(
41
+ join(targetDir, 'app.ts'),
42
+ [
43
+ `import { Router } from 'weifuwu'`,
44
+ ``,
45
+ `export const app = new Router()`,
46
+ ``,
47
+ `app.get('/', () => new Response('Hello from ${name}!'))`,
48
+ `app.get('/api/ping', () => Response.json({ pong: true, time: new Date().toISOString() }))`,
49
+ ``,
50
+ ].join('\n'),
51
+ )
107
52
 
108
- // Write package.json
109
- const deps: Record<string, string> = { weifuwu: `^${v}` }
110
- const devDeps: Record<string, string> = {}
111
- if (!opts.minimal) {
112
- deps['react'] = '^19'
113
- deps['react-dom'] = '^19'
114
- deps['tailwindcss'] = '^4'
115
- devDeps['@types/react'] = depVer('@types/react')
116
- devDeps['@types/react-dom'] = depVer('@types/react-dom')
117
- }
118
- devDeps['@types/node'] = depVer('@types/node')
53
+ await writeFile(
54
+ join(targetDir, 'index.ts'),
55
+ [
56
+ `import { loadEnv, serve } from 'weifuwu'`,
57
+ `import { app } from './app.ts'`,
58
+ ``,
59
+ `loadEnv()`,
60
+ `const port = Number(process.env.PORT) || 3000`,
61
+ `serve(app.handler(), { port })`,
62
+ ``,
63
+ ].join('\n'),
64
+ )
119
65
 
120
66
  await writeFile(
121
67
  join(targetDir, 'package.json'),
@@ -124,19 +70,18 @@ async function cmdInit(name: string, opts: { minimal?: boolean; skipInstall?: bo
124
70
  name,
125
71
  type: 'module',
126
72
  scripts: {
127
- dev: 'NODE_ENV=development node --watch index.ts',
73
+ dev: 'node --watch index.ts',
128
74
  start: 'node index.ts',
129
75
  },
130
- dependencies: deps,
131
- devDependencies: devDeps,
76
+ dependencies: {
77
+ weifuwu: `^${pkg.version}`,
78
+ },
132
79
  },
133
80
  null,
134
81
  2,
135
82
  ) + '\n',
136
83
  )
137
84
 
138
- // Write tsconfig.json
139
- const include = opts.minimal ? ['*.ts'] : ['*.ts', 'ui/**/*.ts', 'ui/**/*.tsx']
140
85
  await writeFile(
141
86
  join(targetDir, 'tsconfig.json'),
142
87
  JSON.stringify(
@@ -146,204 +91,57 @@ async function cmdInit(name: string, opts: { minimal?: boolean; skipInstall?: bo
146
91
  module: 'NodeNext',
147
92
  moduleResolution: 'NodeNext',
148
93
  strict: true,
149
- jsx: 'react-jsx',
150
94
  skipLibCheck: true,
151
95
  noEmit: true,
152
96
  allowImportingTsExtensions: true,
153
97
  },
154
- include,
98
+ include: ['*.ts'],
155
99
  },
156
100
  null,
157
101
  2,
158
102
  ) + '\n',
159
103
  )
160
104
 
161
- await writeFile(join(targetDir, '.gitignore'), 'node_modules\ndist\n.env\n.sessions\n.weifuwu\n')
105
+ await writeFile(join(targetDir, '.gitignore'), 'node_modules\n.env\n')
162
106
  await writeFile(join(targetDir, '.env'), 'PORT=3000\n')
163
- await writeFile(
164
- join(targetDir, 'AGENTS.md'),
165
- [
166
- `# ${name}`,
167
- '',
168
- `This is a [weifuwu](https://weifuwu.io) application — pure Node.js, no build step.`,
169
- '',
170
- '## Commands',
171
- '',
172
- '- `npm run dev` — start dev server with hot reload',
173
- '- `npm start` — start production server',
174
- '- `npm install` — install dependencies',
175
- '- `npx tsc --noEmit` — type-check',
176
- '',
177
- '## API Reference',
178
- '',
179
- 'See `node_modules/weifuwu/README.md` for the full documentation.',
180
- '',
181
- ].join('\n'),
182
- )
183
107
 
184
108
  if (!opts.skipInstall) {
185
109
  console.log('\nInstalling dependencies...')
186
110
  execSync('npm install', { cwd: targetDir, stdio: 'inherit' })
187
111
  }
188
- console.log(
189
- `\n✅ Created ${name}/ — cd ${name} && ${opts.skipInstall ? 'npm install && ' : ''}npm run dev`,
190
- )
191
- }
192
-
193
- async function cmdDev() {
194
- const entry = existsSync('index.ts') ? 'index.ts' : existsSync('app.ts') ? 'app.ts' : null
195
112
 
196
- if (!entry) {
197
- console.error('No index.ts or app.ts found in current directory.')
198
- console.error('Run `npx weifuwu init <name>` to create a new project.')
199
- process.exit(1)
200
- }
201
-
202
- console.log(`Starting dev server (${entry})...`)
203
- execSync(`NODE_ENV=development node --watch ${entry}`, { stdio: 'inherit' })
204
- }
205
-
206
- async function cmdGenerate(type: string, name: string) {
207
- if (type !== 'module') {
208
- console.error(`Unknown generate type: ${type}. Usage: npx weifuwu generate module <name>`)
209
- process.exit(1)
210
- }
211
-
212
- const dir = join(process.cwd(), name)
213
- if (existsSync(dir)) {
214
- console.error(`Directory ${name} already exists.`)
215
- process.exit(1)
216
- }
217
-
218
- await mkdir(dir, { recursive: true })
219
- await writeFile(
220
- join(dir, 'index.ts'),
221
- [
222
- `import type { Middleware } from 'weifuwu'`,
223
- ``,
224
- `export interface ${capitalize(name)}Options {`,
225
- ` // Add your options here`,
226
- `}`,
227
- ``,
228
- `export function ${name}(opts?: ${capitalize(name)}Options): Middleware {`,
229
- ` return async (req, ctx, next) => {`,
230
- ` // Your middleware logic here`,
231
- ` return next(req, ctx)`,
232
- ` }`,
233
- `}`,
234
- ``,
235
- ].join('\n'),
236
- )
237
-
238
- await mkdir(join(dir, '..', 'test'), { recursive: true })
239
- await writeFile(
240
- join(dir, '..', 'test', `${name}.test.ts`),
241
- [
242
- `import { describe, it } from 'node:test'`,
243
- `import assert from 'node:assert/strict'`,
244
- `import { ${name} } from '../${name}/index.ts'`,
245
- `import { Router } from 'weifuwu'`,
246
- ``,
247
- `describe('${name}', () => {`,
248
- ` it('works as middleware', async () => {`,
249
- ` const app = new Router()`,
250
- ` app.use(${name}())`,
251
- ` app.get('/', () => new Response('ok'))`,
252
- ``,
253
- ` const res = await app.handler()(`,
254
- ` new Request('http://localhost/'),`,
255
- ` { params: {}, query: {} } as any,`,
256
- ` )`,
257
- ` assert.equal(res.status, 200)`,
258
- ` })`,
259
- `})`,
260
- ``,
261
- ].join('\n'),
262
- )
263
-
264
- console.log(`✅ Created module ${name}/ with index.ts and test/${name}.test.ts`)
265
- }
266
-
267
- function capitalize(s: string): string {
268
- return s.charAt(0).toUpperCase() + s.slice(1)
269
- }
270
-
271
- // Minimal rm -rf
272
- async function rmrf(dir: string) {
273
- try {
274
- const entries = readdirSync(dir, { withFileTypes: true })
275
- for (const entry of entries) {
276
- const p = join(dir, entry.name)
277
- if (entry.isDirectory()) {
278
- await rmrf(p)
279
- } else {
280
- const { unlink } = await import('node:fs/promises')
281
- await unlink(p)
282
- }
283
- }
284
- const { rmdir } = await import('node:fs/promises')
285
- await rmdir(dir)
286
- } catch {
287
- /* ignore */
288
- }
113
+ console.log(`\n✅ Created ${name}/`)
114
+ console.log(` cd ${name}`)
115
+ if (opts.skipInstall) console.log(` npm install`)
116
+ console.log(` npm run dev`)
289
117
  }
290
118
 
291
- import { parseArgs } from 'node:util'
292
-
293
- // ── CLI dispatcher ──────────────────────────────────────────────────────
294
-
295
119
  const cmd = process.argv[2]
296
120
 
297
121
  const HELP = `
298
- weifuwu — Web-standard HTTP framework for Node.js
122
+ weifuwu — Web-standard HTTP microframework for Node.js
299
123
 
300
124
  Usage:
301
- npx weifuwu init <name> Create a new project (SSR + i18n + theme)
302
- npx weifuwu init <name> --minimal Create a minimal HTTP project
125
+ npx weifuwu init <name> Create a new project
303
126
  npx weifuwu init <name> --skip-install Create project, skip npm install
304
- npx weifuwu dev Start dev server in current directory
305
- npx weifuwu generate module <name> Scaffold a new module
306
- npx weifuwu version Print version
127
+ npx weifuwu version Print version
307
128
  `
308
129
 
309
130
  if (cmd === 'version' || cmd === '-v' || cmd === '--version') {
310
131
  cmdVersion().catch(console.error)
311
- } else if (cmd === 'skill') {
312
- cmdSkill().catch(console.error)
313
132
  } else if (cmd === 'init') {
314
133
  const { values, positionals } = parseArgs({
315
134
  args: process.argv.slice(3),
316
- options: {
317
- minimal: { type: 'boolean', short: 'm' },
318
- 'skip-install': { type: 'boolean' },
319
- },
135
+ options: { 'skip-install': { type: 'boolean' } },
320
136
  strict: false,
321
137
  allowPositionals: true,
322
138
  })
323
139
  const name = positionals[0]
324
140
  if (!name) {
325
- console.error('Usage: npx weifuwu init <name> [--minimal] [--skip-install]')
326
- process.exit(1)
327
- }
328
- cmdInit(name, { minimal: !!values.minimal, skipInstall: !!values['skip-install'] }).catch(
329
- console.error,
330
- )
331
- } else if (cmd === 'dev') {
332
- cmdDev()
333
- } else if (cmd === 'generate' || cmd === 'g') {
334
- const { positionals } = parseArgs({
335
- args: process.argv.slice(3),
336
- options: {},
337
- strict: false,
338
- allowPositionals: true,
339
- })
340
- const type = positionals[0]
341
- const name = positionals[1]
342
- if (!type || !name) {
343
- console.error('Usage: npx weifuwu generate module <name>')
141
+ console.error('Usage: npx weifuwu init <name> [--skip-install]')
344
142
  process.exit(1)
345
143
  }
346
- cmdGenerate(type, name).catch(console.error)
144
+ cmdInit(name, { skipInstall: !!values['skip-install'] }).catch(console.error)
347
145
  } else {
348
146
  console.log(HELP)
349
147
  }