weifuwu 0.27.7 → 0.27.9
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/dist/cli.js +7 -219
- package/dist/index.js +26 -4
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// cli.ts
|
|
4
|
-
import { mkdir, writeFile } from "node:fs/promises";
|
|
4
|
+
import { mkdir, writeFile, readFile, cp } from "node:fs/promises";
|
|
5
5
|
import { existsSync } from "node:fs";
|
|
6
6
|
import { execSync } from "node:child_process";
|
|
7
7
|
import { join, dirname, resolve } from "node:path";
|
|
@@ -65,226 +65,14 @@ async function generateMinimal(targetDir, name, version, skipInstall) {
|
|
|
65
65
|
await finishInit(targetDir, skipInstall);
|
|
66
66
|
}
|
|
67
67
|
async function generateFull(targetDir, name, version, skipInstall) {
|
|
68
|
-
|
|
69
|
-
await
|
|
70
|
-
|
|
71
|
-
await
|
|
68
|
+
const templateDir = existsSync(join(__dirname, "cli", "template")) ? join(__dirname, "cli", "template") : join(__dirname, "template");
|
|
69
|
+
await cp(templateDir, targetDir, { recursive: true });
|
|
70
|
+
const pkgPath = join(targetDir, "package.json");
|
|
71
|
+
const pkgContent = await readFile(pkgPath, "utf-8");
|
|
72
72
|
await writeFile(
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
`import { Router, layout, view, theme, i18n, cssContext, cssRouter, assetRouter } from 'weifuwu'`,
|
|
76
|
-
``,
|
|
77
|
-
`export const app = new Router()`,
|
|
78
|
-
``,
|
|
79
|
-
`// Middleware`,
|
|
80
|
-
`app.use(theme())`,
|
|
81
|
-
`app.use(i18n({ dir: './locales' }))`,
|
|
82
|
-
`app.use(cssContext('./ui'))`,
|
|
83
|
-
``,
|
|
84
|
-
`// Layout \u2014 wraps all pages`,
|
|
85
|
-
`app.use(layout('./ui/app/layout.ts'))`,
|
|
86
|
-
``,
|
|
87
|
-
`// Static assets (HTMX, Alpine)`,
|
|
88
|
-
`app.use('/', assetRouter())`,
|
|
89
|
-
``,
|
|
90
|
-
`// CSS serving`,
|
|
91
|
-
`app.use('/', cssRouter('./ui'))`,
|
|
92
|
-
``,
|
|
93
|
-
`// Pages`,
|
|
94
|
-
`app.get('/', view('./ui/app/page.ts'))`,
|
|
95
|
-
``,
|
|
96
|
-
`// API route`,
|
|
97
|
-
`app.get('/api/ping', () => Response.json({ pong: true, time: new Date().toISOString() }))`,
|
|
98
|
-
``
|
|
99
|
-
].join("\n")
|
|
73
|
+
pkgPath,
|
|
74
|
+
pkgContent.replace("__PROJECT_NAME__", name).replace("__VERSION__", version)
|
|
100
75
|
);
|
|
101
|
-
await writeFile(
|
|
102
|
-
join(targetDir, "index.ts"),
|
|
103
|
-
[
|
|
104
|
-
`import { loadEnv, serve } from 'weifuwu'`,
|
|
105
|
-
`import { app } from './app.ts'`,
|
|
106
|
-
``,
|
|
107
|
-
`loadEnv()`,
|
|
108
|
-
`const port = Number(process.env.PORT) || 3000`,
|
|
109
|
-
`serve(app.handler(), { port })`,
|
|
110
|
-
``
|
|
111
|
-
].join("\n")
|
|
112
|
-
);
|
|
113
|
-
await writeFile(
|
|
114
|
-
join(targetDir, "ui", "app", "globals.css"),
|
|
115
|
-
[`@import "tailwindcss";`, `@custom-variant dark (&:is(.dark *));`, ``].join("\n")
|
|
116
|
-
);
|
|
117
|
-
await writeFile(
|
|
118
|
-
join(targetDir, "ui", "app", "layout.ts"),
|
|
119
|
-
[
|
|
120
|
-
`import { html, raw, assetScripts } from 'weifuwu'`,
|
|
121
|
-
``,
|
|
122
|
-
`export default function(body: string, ctx: any) {`,
|
|
123
|
-
` // Theme: resolve before paint to prevent flash`,
|
|
124
|
-
` const themeScript = raw(\`<script>`,
|
|
125
|
-
`!function(){`,
|
|
126
|
-
`var t=(document.cookie.match(/(?:^|;\\s*)theme=([^;]+)/)||[])[1]||'system';`,
|
|
127
|
-
`if(t==='system')t=window.matchMedia('(prefers-color-scheme:dark)').matches?'dark':'light';`,
|
|
128
|
-
`document.documentElement.classList.toggle('dark',t==='dark');`,
|
|
129
|
-
`}()`,
|
|
130
|
-
`</script>\`)`,
|
|
131
|
-
``,
|
|
132
|
-
` // i18n: set lang attribute`,
|
|
133
|
-
` const lang = ctx.i18n?.locale || 'en'`,
|
|
134
|
-
``,
|
|
135
|
-
` // CSS: include compiled stylesheet`,
|
|
136
|
-
` const cssLink = ctx.css?.url`,
|
|
137
|
-
` ? raw(\`<link rel="stylesheet" href="\${ctx.css.url}">\`)`,
|
|
138
|
-
` : ''`,
|
|
139
|
-
``,
|
|
140
|
-
` return html\`<!DOCTYPE html>`,
|
|
141
|
-
`<html lang="\${lang}">`,
|
|
142
|
-
`<head>`,
|
|
143
|
-
` <meta charset="utf-8" />`,
|
|
144
|
-
` <meta name="viewport" content="width=device-width, initial-scale=1" />`,
|
|
145
|
-
` \${themeScript}`,
|
|
146
|
-
` \${assetScripts()}`,
|
|
147
|
-
` \${cssLink}`,
|
|
148
|
-
`</head>`,
|
|
149
|
-
`<body class="min-h-screen bg-white text-gray-900 dark:bg-gray-950 dark:text-gray-100">`,
|
|
150
|
-
` \${raw(body)}`,
|
|
151
|
-
`</body>`,
|
|
152
|
-
`</html>\``,
|
|
153
|
-
`}`,
|
|
154
|
-
``
|
|
155
|
-
].join("\n")
|
|
156
|
-
);
|
|
157
|
-
await writeFile(
|
|
158
|
-
join(targetDir, "ui", "app", "page.ts"),
|
|
159
|
-
[
|
|
160
|
-
`import { html } from 'weifuwu'`,
|
|
161
|
-
``,
|
|
162
|
-
`export default function(ctx: any) {`,
|
|
163
|
-
` const t = ctx.i18n?.t || ((k: string) => k)`,
|
|
164
|
-
` const theme = ctx.theme?.value || 'system'`,
|
|
165
|
-
` const locale = ctx.i18n?.locale || 'en'`,
|
|
166
|
-
``,
|
|
167
|
-
` return html\`<div x-data="{ open: false }" class="min-h-screen">`,
|
|
168
|
-
` <!-- Navbar -->`,
|
|
169
|
-
` <nav class="border-b border-gray-200 dark:border-gray-800">`,
|
|
170
|
-
` <div class="max-w-5xl mx-auto flex items-center justify-between h-14 px-4">`,
|
|
171
|
-
` <span class="font-bold text-lg">weifuwu</span>`,
|
|
172
|
-
` <div class="flex items-center gap-3 text-sm">`,
|
|
173
|
-
` <!-- Locale toggle -->`,
|
|
174
|
-
` <a href="/__lang/\${locale === 'en' ? 'zh-CN' : 'en'}"`,
|
|
175
|
-
` class="px-2 py-1 rounded border border-gray-300 dark:border-gray-600`,
|
|
176
|
-
` hover:bg-gray-100 dark:hover:bg-gray-800 transition">`,
|
|
177
|
-
` \${locale === 'en' ? '\u4E2D\u6587' : 'EN'}`,
|
|
178
|
-
` </a>`,
|
|
179
|
-
` <!-- Theme toggle -->`,
|
|
180
|
-
` <a href="/__theme/\${theme === 'dark' ? 'light' : 'dark'}"`,
|
|
181
|
-
` class="px-2 py-1 rounded border border-gray-300 dark:border-gray-600`,
|
|
182
|
-
` hover:bg-gray-100 dark:hover:bg-gray-800 transition">`,
|
|
183
|
-
` \${theme === 'dark' ? '\u2600\uFE0F' : '\u{1F319}'}`,
|
|
184
|
-
` </a>`,
|
|
185
|
-
` </div>`,
|
|
186
|
-
` </div>`,
|
|
187
|
-
` </nav>`,
|
|
188
|
-
``,
|
|
189
|
-
` <!-- Hero -->`,
|
|
190
|
-
` <section class="max-w-3xl mx-auto px-4 py-16 text-center">`,
|
|
191
|
-
` <h1 class="text-4xl font-bold tracking-tight mb-3">\${t('hero.title')}</h1>`,
|
|
192
|
-
` <p class="text-gray-500 dark:text-gray-400 text-lg mb-8">`,
|
|
193
|
-
` Pure Node.js, no build step`,
|
|
194
|
-
` </p>`,
|
|
195
|
-
``,
|
|
196
|
-
` <div class="flex justify-center gap-3">`,
|
|
197
|
-
` <button @click="open = !open"`,
|
|
198
|
-
` class="rounded-md bg-gray-900 px-4 py-2 text-sm font-medium text-white`,
|
|
199
|
-
` hover:bg-gray-700 dark:bg-gray-100 dark:text-gray-900 dark:hover:bg-gray-300">`,
|
|
200
|
-
` \${t('hero.cta')}`,
|
|
201
|
-
` </button>`,
|
|
202
|
-
` <a href="/docs"`,
|
|
203
|
-
` class="rounded-md border border-gray-300 px-4 py-2 text-sm font-medium`,
|
|
204
|
-
` hover:bg-gray-100 dark:border-gray-600 dark:hover:bg-gray-800">`,
|
|
205
|
-
` \${t('hero.docs')}`,
|
|
206
|
-
` </a>`,
|
|
207
|
-
` </div>`,
|
|
208
|
-
``,
|
|
209
|
-
` <!-- Alpine demo: click to reveal -->`,
|
|
210
|
-
` <div x-show="open" x-cloak`,
|
|
211
|
-
` class="mt-6 p-4 bg-gray-100 dark:bg-gray-800 rounded-lg text-sm text-left">`,
|
|
212
|
-
` \${t('demo.alpine')}`,
|
|
213
|
-
` </div>`,
|
|
214
|
-
` </section>`,
|
|
215
|
-
` </div>\``,
|
|
216
|
-
`}`,
|
|
217
|
-
``
|
|
218
|
-
].join("\n")
|
|
219
|
-
);
|
|
220
|
-
await writeFile(
|
|
221
|
-
join(targetDir, "ui", "lib", "utils.ts"),
|
|
222
|
-
[
|
|
223
|
-
`/**`,
|
|
224
|
-
` * cn() \u2014 Merge class names, handling conditional and array inputs.`,
|
|
225
|
-
` * Lightweight alternative to clsx + tailwind-merge.`,
|
|
226
|
-
` */`,
|
|
227
|
-
`export function cn(...classes: (string | false | null | undefined)[]): string {`,
|
|
228
|
-
` return classes.filter(Boolean).join(' ')`,
|
|
229
|
-
`}`,
|
|
230
|
-
``
|
|
231
|
-
].join("\n")
|
|
232
|
-
);
|
|
233
|
-
await writeFile(
|
|
234
|
-
join(targetDir, "locales", "en.json"),
|
|
235
|
-
JSON.stringify(
|
|
236
|
-
{
|
|
237
|
-
"hero.title": "Build APIs & UI, Zero Build Step",
|
|
238
|
-
"hero.cta": "Try Alpine",
|
|
239
|
-
"hero.docs": "Documentation",
|
|
240
|
-
"demo.alpine": "This is Alpine.js in action \u2014 click-toggled content, zero JavaScript written."
|
|
241
|
-
},
|
|
242
|
-
null,
|
|
243
|
-
2
|
|
244
|
-
) + "\n"
|
|
245
|
-
);
|
|
246
|
-
await writeFile(
|
|
247
|
-
join(targetDir, "locales", "zh-CN.json"),
|
|
248
|
-
JSON.stringify(
|
|
249
|
-
{
|
|
250
|
-
"hero.title": "\u96F6\u7F16\u8BD1\u6784\u5EFA API \u548C UI",
|
|
251
|
-
"hero.cta": "\u4F53\u9A8C Alpine",
|
|
252
|
-
"hero.docs": "\u6587\u6863",
|
|
253
|
-
"demo.alpine": "\u8FD9\u662F Alpine.js \u7684\u6F14\u793A\u2014\u2014\u70B9\u51FB\u5207\u6362\u5185\u5BB9\uFF0C\u4E0D\u9700\u8981\u5199 JavaScript\u3002"
|
|
254
|
-
},
|
|
255
|
-
null,
|
|
256
|
-
2
|
|
257
|
-
) + "\n"
|
|
258
|
-
);
|
|
259
|
-
await writePackageJson(targetDir, name, version, {
|
|
260
|
-
dependencies: {
|
|
261
|
-
weifuwu: `^${version}`
|
|
262
|
-
},
|
|
263
|
-
devDependencies: {}
|
|
264
|
-
});
|
|
265
|
-
await writeFile(
|
|
266
|
-
join(targetDir, "tsconfig.json"),
|
|
267
|
-
JSON.stringify(
|
|
268
|
-
{
|
|
269
|
-
compilerOptions: {
|
|
270
|
-
target: "ESNext",
|
|
271
|
-
module: "NodeNext",
|
|
272
|
-
moduleResolution: "NodeNext",
|
|
273
|
-
strict: true,
|
|
274
|
-
skipLibCheck: true,
|
|
275
|
-
noEmit: true,
|
|
276
|
-
allowImportingTsExtensions: true,
|
|
277
|
-
paths: {
|
|
278
|
-
"@/*": ["./ui/*"]
|
|
279
|
-
}
|
|
280
|
-
},
|
|
281
|
-
include: ["*.ts", "ui/**/*.ts"]
|
|
282
|
-
},
|
|
283
|
-
null,
|
|
284
|
-
2
|
|
285
|
-
) + "\n"
|
|
286
|
-
);
|
|
287
|
-
await writeCommonFiles(targetDir);
|
|
288
76
|
await finishInit(targetDir, skipInstall);
|
|
289
77
|
}
|
|
290
78
|
async function writePackageJson(targetDir, name, version, extra) {
|
package/dist/index.js
CHANGED
|
@@ -2460,8 +2460,8 @@ function postgres(opts) {
|
|
|
2460
2460
|
const stmtTimeout = options.statementTimeout ?? 3e4;
|
|
2461
2461
|
let connStr = typeof connection === "string" ? connection : "";
|
|
2462
2462
|
if (stmtTimeout > 0 && typeof connection === "string") {
|
|
2463
|
-
const
|
|
2464
|
-
connStr = `${connStr}${
|
|
2463
|
+
const sep3 = connStr.includes("?") ? "&" : "?";
|
|
2464
|
+
connStr = `${connStr}${sep3}options=-c%20statement_timeout%3D${stmtTimeout}`;
|
|
2465
2465
|
}
|
|
2466
2466
|
const sql = postgresFactory(connStr, {
|
|
2467
2467
|
max: options.max,
|
|
@@ -3592,15 +3592,37 @@ function view(path, options) {
|
|
|
3592
3592
|
// ssr/css.ts
|
|
3593
3593
|
import { createHash } from "node:crypto";
|
|
3594
3594
|
import { existsSync, readFileSync as readFileSync2 } from "node:fs";
|
|
3595
|
-
import { join as join3, resolve as resolve6 } from "node:path";
|
|
3595
|
+
import { join as join3, resolve as resolve6, sep as sep2 } from "node:path";
|
|
3596
3596
|
import tailwindPlugin from "@tailwindcss/postcss";
|
|
3597
3597
|
import postcss from "postcss";
|
|
3598
3598
|
var cssCache = /* @__PURE__ */ new Map();
|
|
3599
|
+
function findTailwindDir() {
|
|
3600
|
+
const hoisted = resolve6(process.cwd(), "node_modules", "tailwindcss");
|
|
3601
|
+
if (existsSync(hoisted)) return hoisted;
|
|
3602
|
+
let dir = process.cwd();
|
|
3603
|
+
const root = sep2 === "/" ? "/" : "";
|
|
3604
|
+
while (dir !== root) {
|
|
3605
|
+
const wfwDir = resolve6(dir, "node_modules", "weifuwu");
|
|
3606
|
+
if (existsSync(wfwDir)) {
|
|
3607
|
+
const nested = resolve6(wfwDir, "node_modules", "tailwindcss");
|
|
3608
|
+
if (existsSync(nested)) return nested;
|
|
3609
|
+
const parent = resolve6(dir, "..", "node_modules", "tailwindcss");
|
|
3610
|
+
if (existsSync(parent)) return parent;
|
|
3611
|
+
}
|
|
3612
|
+
dir = resolve6(dir, "..");
|
|
3613
|
+
}
|
|
3614
|
+
return null;
|
|
3615
|
+
}
|
|
3599
3616
|
async function compileCSS(cssPath, sourceDir) {
|
|
3600
3617
|
if (!existsSync(cssPath)) {
|
|
3601
3618
|
return { css: "", hash: "empty", url: "" };
|
|
3602
3619
|
}
|
|
3603
|
-
|
|
3620
|
+
let raw2 = readFileSync2(cssPath, "utf-8");
|
|
3621
|
+
const tailwindDir = findTailwindDir();
|
|
3622
|
+
if (tailwindDir && raw2.includes('@import "tailwindcss"')) {
|
|
3623
|
+
const tailwindCss = readFileSync2(join3(tailwindDir, "index.css"), "utf-8");
|
|
3624
|
+
raw2 = raw2.replace('@import "tailwindcss"', tailwindCss);
|
|
3625
|
+
}
|
|
3604
3626
|
const src = `@source "${sourceDir}";
|
|
3605
3627
|
${raw2}`;
|
|
3606
3628
|
const result = await postcss([tailwindPlugin()]).process(src, { from: cssPath });
|