weifuwu 0.25.2 → 0.27.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/README.md +291 -2489
- package/ai/provider.ts +129 -0
- package/ai/stream.ts +63 -0
- package/cli.ts +55 -257
- package/core/cookie.ts +114 -0
- package/core/env.ts +142 -0
- package/core/logger.ts +72 -0
- package/core/router.ts +795 -0
- package/core/serve.ts +294 -0
- package/core/sse.ts +85 -0
- package/core/trace.ts +146 -0
- package/graphql.ts +267 -0
- package/hub.ts +133 -0
- package/index.ts +71 -0
- package/mailer.ts +81 -0
- package/middleware/compress.ts +103 -0
- package/middleware/cors.ts +81 -0
- package/middleware/csrf.ts +112 -0
- package/middleware/flash.ts +144 -0
- package/middleware/health.ts +44 -0
- package/middleware/helmet.ts +98 -0
- package/middleware/i18n.ts +175 -0
- package/middleware/rate-limit.ts +167 -0
- package/middleware/request-id.ts +60 -0
- package/middleware/static.ts +149 -0
- package/middleware/theme.ts +84 -0
- package/middleware/upload.ts +168 -0
- package/middleware/validate.ts +186 -0
- package/package.json +14 -36
- package/postgres/client.ts +132 -0
- package/postgres/index.ts +4 -0
- package/postgres/module.ts +37 -0
- package/postgres/schema/columns.ts +186 -0
- package/postgres/schema/index.ts +36 -0
- package/postgres/schema/sql.ts +39 -0
- package/postgres/schema/table.ts +548 -0
- package/postgres/schema/where.ts +99 -0
- package/postgres/types.ts +48 -0
- package/queue/cron.ts +90 -0
- package/queue/index.ts +654 -0
- package/queue/types.ts +60 -0
- package/redis/client.ts +24 -0
- package/{dist/redis/index.d.ts → redis/index.ts} +2 -2
- package/redis/types.ts +28 -0
- package/types.ts +78 -0
- package/cli/template/app.ts +0 -22
- package/cli/template/index.ts +0 -10
- package/cli/template/locales/en.json +0 -13
- package/cli/template/locales/zh-CN.json +0 -13
- package/cli/template/locales/zh-TW.json +0 -13
- package/cli/template/locales/zh.json +0 -13
- package/cli/template/ui/app/globals.css +0 -2
- package/cli/template/ui/app/layout.tsx +0 -15
- package/cli/template/ui/app/page.tsx +0 -124
- package/cli/template/ui/components/Greeting.tsx +0 -3
- package/dist/agent/client.d.ts +0 -2
- package/dist/agent/index.d.ts +0 -2
- package/dist/agent/rest.d.ts +0 -14
- package/dist/agent/run.d.ts +0 -19
- package/dist/agent/types.d.ts +0 -55
- package/dist/ai/provider.d.ts +0 -45
- package/dist/ai/utils.d.ts +0 -5
- package/dist/ai/workflow.d.ts +0 -17
- package/dist/ai-sdk.d.ts +0 -2
- package/dist/ai.d.ts +0 -13
- package/dist/analytics.d.ts +0 -45
- package/dist/auth.d.ts +0 -22
- package/dist/cache.d.ts +0 -74
- package/dist/cli.d.ts +0 -2
- package/dist/cli.js +0 -302
- package/dist/client-locale.d.ts +0 -25
- package/dist/client-pref.d.ts +0 -3
- package/dist/client-router.d.ts +0 -300
- package/dist/client-state.d.ts +0 -22
- package/dist/client-theme.d.ts +0 -36
- package/dist/compile.d.ts +0 -15
- package/dist/compress.d.ts +0 -20
- package/dist/cookie.d.ts +0 -36
- package/dist/cors.d.ts +0 -25
- package/dist/cron-utils.d.ts +0 -73
- package/dist/csrf.d.ts +0 -47
- package/dist/deploy/config.d.ts +0 -2
- package/dist/deploy/gateway.d.ts +0 -2
- package/dist/deploy/index.d.ts +0 -4
- package/dist/deploy/manager.d.ts +0 -16
- package/dist/deploy/process.d.ts +0 -14
- package/dist/deploy/types.d.ts +0 -53
- package/dist/env.d.ts +0 -69
- package/dist/error-boundary.d.ts +0 -2
- package/dist/flash.d.ts +0 -90
- package/dist/fts.d.ts +0 -36
- package/dist/graphql.d.ts +0 -16
- package/dist/head.d.ts +0 -6
- package/dist/health.d.ts +0 -24
- package/dist/helmet.d.ts +0 -33
- package/dist/html-shell.d.ts +0 -1
- package/dist/hub.d.ts +0 -37
- package/dist/i18n.d.ts +0 -39
- package/dist/iii/client.d.ts +0 -2
- package/dist/iii/index.d.ts +0 -4
- package/dist/iii/register-worker.d.ts +0 -9
- package/dist/iii/rest.d.ts +0 -3
- package/dist/iii/stream.d.ts +0 -82
- package/dist/iii/types.d.ts +0 -121
- package/dist/iii/worker.d.ts +0 -2
- package/dist/iii/ws.d.ts +0 -22
- package/dist/index.d.ts +0 -101
- package/dist/index.js +0 -12752
- package/dist/kb/index.d.ts +0 -3
- package/dist/kb/types.d.ts +0 -72
- package/dist/layout.d.ts +0 -2
- package/dist/live.d.ts +0 -7
- package/dist/logdb/client.d.ts +0 -2
- package/dist/logdb/index.d.ts +0 -2
- package/dist/logdb/rest.d.ts +0 -5
- package/dist/logdb/types.d.ts +0 -27
- package/dist/logger.d.ts +0 -16
- package/dist/mailer.d.ts +0 -51
- package/dist/mcp.d.ts +0 -34
- package/dist/messager/agent.d.ts +0 -11
- package/dist/messager/client.d.ts +0 -2
- package/dist/messager/index.d.ts +0 -2
- package/dist/messager/rest.d.ts +0 -15
- package/dist/messager/types.d.ts +0 -57
- package/dist/messager/ws.d.ts +0 -14
- package/dist/module-server.d.ts +0 -9
- package/dist/not-found.d.ts +0 -2
- package/dist/notifier/client.d.ts +0 -2
- package/dist/notifier/index.d.ts +0 -2
- package/dist/notifier/types.d.ts +0 -105
- package/dist/opencode/client.d.ts +0 -2
- package/dist/opencode/index.d.ts +0 -2
- package/dist/opencode/permissions.d.ts +0 -5
- package/dist/opencode/prompt.d.ts +0 -8
- package/dist/opencode/rest.d.ts +0 -16
- package/dist/opencode/run.d.ts +0 -13
- package/dist/opencode/session.d.ts +0 -26
- package/dist/opencode/skills.d.ts +0 -4
- package/dist/opencode/tools/bash.d.ts +0 -6
- package/dist/opencode/tools/edit.d.ts +0 -19
- package/dist/opencode/tools/glob.d.ts +0 -9
- package/dist/opencode/tools/grep.d.ts +0 -17
- package/dist/opencode/tools/index.d.ts +0 -12
- package/dist/opencode/tools/question.d.ts +0 -5
- package/dist/opencode/tools/read.d.ts +0 -16
- package/dist/opencode/tools/skill.d.ts +0 -18
- package/dist/opencode/tools/web.d.ts +0 -18
- package/dist/opencode/tools/write.d.ts +0 -13
- package/dist/opencode/types.d.ts +0 -90
- package/dist/opencode/ws.d.ts +0 -21
- package/dist/permissions.d.ts +0 -51
- package/dist/postgres/client.d.ts +0 -4
- package/dist/postgres/index.d.ts +0 -4
- package/dist/postgres/module.d.ts +0 -17
- package/dist/postgres/schema/columns.d.ts +0 -99
- package/dist/postgres/schema/index.d.ts +0 -6
- package/dist/postgres/schema/sql.d.ts +0 -22
- package/dist/postgres/schema/table.d.ts +0 -141
- package/dist/postgres/schema/where.d.ts +0 -29
- package/dist/postgres/types.d.ts +0 -50
- package/dist/queue/index.d.ts +0 -2
- package/dist/queue/types.d.ts +0 -62
- package/dist/rate-limit.d.ts +0 -45
- package/dist/react.d.ts +0 -14
- package/dist/react.js +0 -751
- package/dist/redis/client.d.ts +0 -2
- package/dist/redis/types.d.ts +0 -18
- package/dist/request-id.d.ts +0 -40
- package/dist/router.d.ts +0 -73
- package/dist/s3.d.ts +0 -68
- package/dist/seo.d.ts +0 -104
- package/dist/serve.d.ts +0 -38
- package/dist/server-registry.d.ts +0 -10
- package/dist/session.d.ts +0 -117
- package/dist/sse.d.ts +0 -47
- package/dist/ssr-entries.d.ts +0 -4
- package/dist/ssr.d.ts +0 -11
- package/dist/static.d.ts +0 -23
- package/dist/stream.d.ts +0 -24
- package/dist/tailwind.d.ts +0 -15
- package/dist/tenant/client.d.ts +0 -2
- package/dist/tenant/graphql.d.ts +0 -3
- package/dist/tenant/index.d.ts +0 -2
- package/dist/tenant/rest.d.ts +0 -3
- package/dist/tenant/schema.d.ts +0 -5
- package/dist/tenant/types.d.ts +0 -48
- package/dist/tenant/utils.d.ts +0 -9
- package/dist/test-utils.d.ts +0 -194
- package/dist/theme.d.ts +0 -31
- package/dist/trace.d.ts +0 -95
- package/dist/tsx-context.d.ts +0 -32
- package/dist/types.d.ts +0 -47
- package/dist/upload.d.ts +0 -55
- package/dist/use-action.d.ts +0 -42
- package/dist/use-agent-stream.d.ts +0 -49
- package/dist/use-flash-message.d.ts +0 -17
- package/dist/use-websocket.d.ts +0 -42
- package/dist/user/client.d.ts +0 -30
- package/dist/user/index.d.ts +0 -2
- package/dist/user/oauth-login.d.ts +0 -21
- package/dist/user/oauth2.d.ts +0 -31
- package/dist/user/types.d.ts +0 -178
- package/dist/validate.d.ts +0 -32
- package/dist/vendor.d.ts +0 -7
- package/dist/webhook.d.ts +0 -79
- package/opencode/ui/app/globals.css +0 -1
- package/opencode/ui/app/layout.tsx +0 -13
- 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
|
+
}
|
package/cli.ts
CHANGED
|
@@ -1,29 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/* eslint-disable no-console */
|
|
3
|
-
|
|
4
|
-
import {
|
|
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()
|
|
17
|
-
return JSON.parse(
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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: {
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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: '
|
|
73
|
+
dev: 'node --watch index.ts',
|
|
128
74
|
start: 'node index.ts',
|
|
129
75
|
},
|
|
130
|
-
dependencies:
|
|
131
|
-
|
|
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\
|
|
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
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
|
122
|
+
weifuwu — Web-standard HTTP microframework for Node.js
|
|
299
123
|
|
|
300
124
|
Usage:
|
|
301
|
-
npx weifuwu init <name> Create a new project
|
|
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
|
|
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> [--
|
|
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
|
-
|
|
144
|
+
cmdInit(name, { skipInstall: !!values['skip-install'] }).catch(console.error)
|
|
347
145
|
} else {
|
|
348
146
|
console.log(HELP)
|
|
349
147
|
}
|