hd-garden 0.3.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/.agents/skills/garden-interactive-chart/SKILL.md +84 -0
- package/.agents/skills/garden-interactive-chart/references/shell.html +49 -0
- package/.agents/skills/garden-interactive-chart/references/style.md +61 -0
- package/.env.example +56 -0
- package/.openai/hosting.json +5 -0
- package/LICENSE +21 -0
- package/Makefile +48 -0
- package/README.md +72 -0
- package/app/about/page.tsx +60 -0
- package/app/admin/page.tsx +117 -0
- package/app/api/admin/distribution/route.ts +120 -0
- package/app/api/admin/posts/pin/route.ts +32 -0
- package/app/api/admin/thoughts/[id]/route.ts +44 -0
- package/app/api/admin/thoughts/route.ts +120 -0
- package/app/api/admin/uploads/route.ts +20 -0
- package/app/api/auth/github/callback/route.ts +136 -0
- package/app/api/auth/github/route.ts +29 -0
- package/app/api/auth/logout/route.ts +13 -0
- package/app/blog/[...path]/page.tsx +135 -0
- package/app/blog/page.tsx +139 -0
- package/app/chatgpt-auth.ts +86 -0
- package/app/columns/[column]/[...path]/page.tsx +160 -0
- package/app/columns/page.tsx +68 -0
- package/app/components/AdminDashboard.tsx +410 -0
- package/app/components/Comments.tsx +55 -0
- package/app/components/ContentEmptyState.tsx +51 -0
- package/app/components/ExternalEmbed.tsx +127 -0
- package/app/components/GardenAdminGuide.tsx +116 -0
- package/app/components/GardenContentGuide.tsx +241 -0
- package/app/components/GardenMediaGuide.tsx +81 -0
- package/app/components/GiscusComments.tsx +71 -0
- package/app/components/GithubLiveOverview.tsx +118 -0
- package/app/components/GithubProfile.tsx +326 -0
- package/app/components/MarkdownArticle.tsx +144 -0
- package/app/components/MermaidDiagram.tsx +117 -0
- package/app/components/PageIntro.tsx +32 -0
- package/app/components/SiteChrome.tsx +18 -0
- package/app/components/SiteFooter.tsx +29 -0
- package/app/components/SiteHeader.tsx +420 -0
- package/app/components/TableOfContents.tsx +433 -0
- package/app/components/ThoughtCard.tsx +242 -0
- package/app/components/Webmentions.tsx +194 -0
- package/app/components/WidgetChart.tsx +222 -0
- package/app/garden/page.tsx +284 -0
- package/app/garden-modules.d.ts +62 -0
- package/app/globals.css +4454 -0
- package/app/layout.tsx +102 -0
- package/app/lib/admin-auth.ts +134 -0
- package/app/lib/content.ts +112 -0
- package/app/lib/fallback-site-config.ts +56 -0
- package/app/lib/generated-content.ts +380 -0
- package/app/lib/github-content.ts +244 -0
- package/app/lib/markdown-plugins.ts +13 -0
- package/app/lib/oss.ts +100 -0
- package/app/lib/public-thoughts.ts +9 -0
- package/app/lib/repository-path.mjs +32 -0
- package/app/lib/site.ts +51 -0
- package/app/page.tsx +119 -0
- package/app/robots.ts +15 -0
- package/app/rss.xml/route.ts +84 -0
- package/app/site.config.ts +35 -0
- package/app/sitemap.ts +46 -0
- package/app/thoughts/[slug]/page.tsx +79 -0
- package/app/thoughts/page.tsx +33 -0
- package/app/yaml.d.ts +4 -0
- package/bin/garden.mjs +348 -0
- package/build/sites-vite-plugin.ts +39 -0
- package/eslint.config.mjs +20 -0
- package/next.config.ts +13 -0
- package/package.json +112 -0
- package/postcss.config.mjs +7 -0
- package/public/favicon.svg +4 -0
- package/public/images/garden/admin-studio.png +0 -0
- package/public/og.png +0 -0
- package/scripts/build-content.mjs +582 -0
- package/scripts/fetch-embed.mjs +189 -0
- package/scripts/garden-paths.mjs +21 -0
- package/scripts/new-post.mjs +73 -0
- package/scripts/prepare-pages-output.mjs +182 -0
- package/scripts/send-webmentions.mjs +27 -0
- package/site.config.yaml +48 -0
- package/templates/site/Makefile +46 -0
- package/templates/site/README.md +13 -0
- package/templates/site/content/columns.yaml +1 -0
- package/templates/site/content/posts/.gitkeep +0 -0
- package/templates/site/content/thoughts/.gitkeep +0 -0
- package/templates/site/package.json +19 -0
- package/templates/site/site.config.yaml +46 -0
- package/tsconfig.json +34 -0
- package/vite.config.ts +130 -0
- package/worker/index.ts +53 -0
package/bin/garden.mjs
ADDED
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { createRequire } from "node:module";
|
|
6
|
+
import { gardenPackageRoot, gardenSiteRoot } from "../scripts/garden-paths.mjs";
|
|
7
|
+
|
|
8
|
+
const siteRoot = gardenSiteRoot();
|
|
9
|
+
const args = process.argv.slice(2);
|
|
10
|
+
const command = args[0] ?? "help";
|
|
11
|
+
const rest = args.slice(1);
|
|
12
|
+
|
|
13
|
+
function siteEnv(extra = {}) {
|
|
14
|
+
return {
|
|
15
|
+
...process.env,
|
|
16
|
+
...extra,
|
|
17
|
+
GARDEN_SITE: siteRoot,
|
|
18
|
+
WRANGLER_LOG_PATH:
|
|
19
|
+
process.env.WRANGLER_LOG_PATH ?? path.join(siteRoot, ".wrangler", "wrangler.log"),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function fail(message) {
|
|
24
|
+
process.stderr.write(`${message}\n`);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function runNode(script, scriptArgs = [], { cwd = siteRoot, env } = {}) {
|
|
29
|
+
const result = spawnSync(process.execPath, [path.join(gardenPackageRoot, script), ...scriptArgs], {
|
|
30
|
+
cwd,
|
|
31
|
+
env: env ?? siteEnv(),
|
|
32
|
+
stdio: "inherit",
|
|
33
|
+
});
|
|
34
|
+
if (result.status) process.exit(result.status ?? 1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function resolveVinext() {
|
|
38
|
+
const require = createRequire(path.join(gardenPackageRoot, "package.json"));
|
|
39
|
+
try {
|
|
40
|
+
const vinextRoot = path.dirname(require.resolve("vinext/package.json"));
|
|
41
|
+
const manifest = JSON.parse(fs.readFileSync(path.join(vinextRoot, "package.json"), "utf8"));
|
|
42
|
+
const bin = typeof manifest.bin === "string" ? manifest.bin : manifest.bin.vinext;
|
|
43
|
+
return path.join(vinextRoot, bin);
|
|
44
|
+
} catch {
|
|
45
|
+
for (const candidate of [
|
|
46
|
+
path.join(siteRoot, "node_modules", "vinext", "dist", "cli.js"),
|
|
47
|
+
path.join(gardenPackageRoot, "node_modules", "vinext", "dist", "cli.js"),
|
|
48
|
+
]) {
|
|
49
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
50
|
+
}
|
|
51
|
+
fail("找不到 vinext。请在站点目录运行 npm install。");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function samePath(left, right) {
|
|
56
|
+
return path.resolve(left) === path.resolve(right);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function isPackageSite() {
|
|
60
|
+
return samePath(siteRoot, gardenPackageRoot);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function ensureLink(from, to) {
|
|
64
|
+
fs.mkdirSync(path.dirname(to), { recursive: true });
|
|
65
|
+
try {
|
|
66
|
+
const stat = fs.lstatSync(to);
|
|
67
|
+
if (stat.isSymbolicLink() && fs.readlinkSync(to) === from) return;
|
|
68
|
+
if (stat.isSymbolicLink()) fs.unlinkSync(to);
|
|
69
|
+
else return;
|
|
70
|
+
} catch (error) {
|
|
71
|
+
if (error.code !== "ENOENT") throw error;
|
|
72
|
+
}
|
|
73
|
+
fs.symlinkSync(from, to);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function ensurePublic() {
|
|
77
|
+
const dest = path.join(siteRoot, "public");
|
|
78
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
79
|
+
for (const name of ["favicon.svg", "og.png"]) {
|
|
80
|
+
const from = path.join(gardenPackageRoot, "public", name);
|
|
81
|
+
const to = path.join(dest, name);
|
|
82
|
+
if (fs.existsSync(from) && !fs.existsSync(to)) fs.copyFileSync(from, to);
|
|
83
|
+
}
|
|
84
|
+
const imagesFrom = path.join(gardenPackageRoot, "public", "images");
|
|
85
|
+
const imagesTo = path.join(dest, "images");
|
|
86
|
+
if (fs.existsSync(imagesFrom) && !fs.existsSync(imagesTo)) {
|
|
87
|
+
fs.cpSync(imagesFrom, imagesTo, { recursive: true });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function ensureEnv() {
|
|
92
|
+
const example = path.join(gardenPackageRoot, ".env.example");
|
|
93
|
+
const local = path.join(siteRoot, ".env.local");
|
|
94
|
+
if (!fs.existsSync(local) && fs.existsSync(example)) {
|
|
95
|
+
fs.copyFileSync(example, local);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function resolveNodeModules() {
|
|
100
|
+
const siteModules = path.join(siteRoot, "node_modules");
|
|
101
|
+
const packageModules = path.join(gardenPackageRoot, "node_modules");
|
|
102
|
+
if (fs.existsSync(path.join(siteModules, "vinext"))) return siteModules;
|
|
103
|
+
if (fs.existsSync(path.join(packageModules, "vinext"))) return packageModules;
|
|
104
|
+
return siteModules;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function vinextRoot() {
|
|
108
|
+
if (isPackageSite()) return gardenPackageRoot;
|
|
109
|
+
|
|
110
|
+
const runtime = path.join(siteRoot, ".garden", "runtime");
|
|
111
|
+
fs.mkdirSync(runtime, { recursive: true });
|
|
112
|
+
|
|
113
|
+
const links = [
|
|
114
|
+
["app", path.join(gardenPackageRoot, "app")],
|
|
115
|
+
["build", path.join(gardenPackageRoot, "build")],
|
|
116
|
+
["worker", path.join(gardenPackageRoot, "worker")],
|
|
117
|
+
[".openai", path.join(gardenPackageRoot, ".openai")],
|
|
118
|
+
["vite.config.ts", path.join(gardenPackageRoot, "vite.config.ts")],
|
|
119
|
+
["next.config.ts", path.join(gardenPackageRoot, "next.config.ts")],
|
|
120
|
+
["postcss.config.mjs", path.join(gardenPackageRoot, "postcss.config.mjs")],
|
|
121
|
+
["tsconfig.json", path.join(gardenPackageRoot, "tsconfig.json")],
|
|
122
|
+
["package.json", path.join(gardenPackageRoot, "package.json")],
|
|
123
|
+
["node_modules", resolveNodeModules()],
|
|
124
|
+
["public", path.join(siteRoot, "public")],
|
|
125
|
+
["content", path.join(siteRoot, "content")],
|
|
126
|
+
["site.config.yaml", path.join(siteRoot, "site.config.yaml")],
|
|
127
|
+
];
|
|
128
|
+
|
|
129
|
+
for (const name of [".env", ".env.local"]) {
|
|
130
|
+
const from = path.join(siteRoot, name);
|
|
131
|
+
if (fs.existsSync(from)) links.push([name, from]);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
for (const [name, from] of links) {
|
|
135
|
+
if (fs.existsSync(from) || name === "public" || name === "content") {
|
|
136
|
+
if ((name === "public" || name === "content") && !fs.existsSync(from)) {
|
|
137
|
+
fs.mkdirSync(from, { recursive: true });
|
|
138
|
+
}
|
|
139
|
+
ensureLink(from, path.join(runtime, name));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return runtime;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function publishDist(fromRoot) {
|
|
147
|
+
const from = path.join(fromRoot, "dist");
|
|
148
|
+
const to = path.join(siteRoot, "dist");
|
|
149
|
+
if (!fs.existsSync(from) || samePath(from, to)) return;
|
|
150
|
+
fs.rmSync(to, { recursive: true, force: true });
|
|
151
|
+
fs.cpSync(from, to, { recursive: true });
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function copyIfMissing(from, to) {
|
|
155
|
+
if (fs.existsSync(to) || !fs.existsSync(from)) return false;
|
|
156
|
+
fs.mkdirSync(path.dirname(to), { recursive: true });
|
|
157
|
+
fs.cpSync(from, to, { recursive: true });
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function ensureSiteKit(destinationRoot = siteRoot) {
|
|
162
|
+
if (samePath(destinationRoot, gardenPackageRoot)) return;
|
|
163
|
+
|
|
164
|
+
const added = [];
|
|
165
|
+
if (
|
|
166
|
+
copyIfMissing(
|
|
167
|
+
path.join(gardenPackageRoot, "templates", "site", "Makefile"),
|
|
168
|
+
path.join(destinationRoot, "Makefile"),
|
|
169
|
+
)
|
|
170
|
+
) {
|
|
171
|
+
added.push("Makefile");
|
|
172
|
+
}
|
|
173
|
+
if (
|
|
174
|
+
copyIfMissing(
|
|
175
|
+
path.join(gardenPackageRoot, "scripts", "new-post.mjs"),
|
|
176
|
+
path.join(destinationRoot, "scripts", "new-post.mjs"),
|
|
177
|
+
)
|
|
178
|
+
) {
|
|
179
|
+
added.push("scripts/new-post.mjs");
|
|
180
|
+
}
|
|
181
|
+
if (added.length) {
|
|
182
|
+
process.stdout.write(`已补齐站点文件:${added.join("、")}\n`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const skillFile = path.join(
|
|
186
|
+
destinationRoot,
|
|
187
|
+
".agents",
|
|
188
|
+
"skills",
|
|
189
|
+
"garden-interactive-chart",
|
|
190
|
+
"SKILL.md",
|
|
191
|
+
);
|
|
192
|
+
if (!fs.existsSync(skillFile)) installSkill(destinationRoot);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function prepare() {
|
|
196
|
+
ensureEnv();
|
|
197
|
+
ensurePublic();
|
|
198
|
+
fs.mkdirSync(path.join(siteRoot, "content", "posts"), { recursive: true });
|
|
199
|
+
fs.mkdirSync(path.join(siteRoot, "content", "thoughts"), { recursive: true });
|
|
200
|
+
ensureSiteKit();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function sync() {
|
|
204
|
+
if (!fs.existsSync(path.join(siteRoot, "site.config.yaml"))) {
|
|
205
|
+
fail(`当前目录不是 Garden 站点(缺少 site.config.yaml):${siteRoot}`);
|
|
206
|
+
}
|
|
207
|
+
prepare();
|
|
208
|
+
runNode("scripts/build-content.mjs");
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function runVinext(vinextArgs, { copyDist = false } = {}) {
|
|
212
|
+
sync();
|
|
213
|
+
const cwd = vinextRoot();
|
|
214
|
+
const result = spawnSync(process.execPath, [resolveVinext(), ...vinextArgs], {
|
|
215
|
+
cwd,
|
|
216
|
+
env: siteEnv(),
|
|
217
|
+
stdio: "inherit",
|
|
218
|
+
});
|
|
219
|
+
if (copyDist) publishDist(cwd);
|
|
220
|
+
if (result.status) process.exit(result.status ?? 1);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function copyTemplate(from, to) {
|
|
224
|
+
fs.mkdirSync(to, { recursive: true });
|
|
225
|
+
for (const entry of fs.readdirSync(from, { withFileTypes: true })) {
|
|
226
|
+
if (entry.name === ".git") continue;
|
|
227
|
+
const source = path.join(from, entry.name);
|
|
228
|
+
const destination = path.join(to, entry.name);
|
|
229
|
+
if (entry.isDirectory()) copyTemplate(source, destination);
|
|
230
|
+
else if (!fs.existsSync(destination)) fs.copyFileSync(source, destination);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function writeEngineDependency(destination) {
|
|
235
|
+
const engine = JSON.parse(fs.readFileSync(path.join(gardenPackageRoot, "package.json"), "utf8"));
|
|
236
|
+
const sitePkgPath = path.join(destination, "package.json");
|
|
237
|
+
const sitePkg = JSON.parse(fs.readFileSync(sitePkgPath, "utf8"));
|
|
238
|
+
const dependencies = { ...(sitePkg.dependencies ?? {}) };
|
|
239
|
+
if (engine.name !== "garden") delete dependencies.garden;
|
|
240
|
+
dependencies[engine.name] = `^${engine.version}`;
|
|
241
|
+
sitePkg.dependencies = dependencies;
|
|
242
|
+
fs.writeFileSync(sitePkgPath, `${JSON.stringify(sitePkg, null, 2)}\n`);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function initSite() {
|
|
246
|
+
const destination = path.resolve(rest[0] ?? ".");
|
|
247
|
+
const template = path.join(gardenPackageRoot, "templates", "site");
|
|
248
|
+
if (!fs.existsSync(template)) fail("Garden 包里缺少 templates/site。");
|
|
249
|
+
if (fs.existsSync(path.join(destination, "site.config.yaml"))) {
|
|
250
|
+
fail(`目录已是 Garden 站点:${destination}`);
|
|
251
|
+
}
|
|
252
|
+
copyTemplate(template, destination);
|
|
253
|
+
writeEngineDependency(destination);
|
|
254
|
+
fs.mkdirSync(path.join(destination, "scripts"), { recursive: true });
|
|
255
|
+
fs.copyFileSync(
|
|
256
|
+
path.join(gardenPackageRoot, "scripts", "new-post.mjs"),
|
|
257
|
+
path.join(destination, "scripts", "new-post.mjs"),
|
|
258
|
+
);
|
|
259
|
+
process.stdout.write(`已创建站点:${destination}\n`);
|
|
260
|
+
installSkill(destination);
|
|
261
|
+
process.stdout.write("下一步:\n");
|
|
262
|
+
process.stdout.write(` cd ${destination}\n`);
|
|
263
|
+
process.stdout.write(" npm install\n");
|
|
264
|
+
process.stdout.write(" make new TITLE=\"你好,世界\" SLUG=hello\n");
|
|
265
|
+
process.stdout.write(" make dev\n");
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function packagedSkillRoot() {
|
|
269
|
+
return path.join(
|
|
270
|
+
gardenPackageRoot,
|
|
271
|
+
".agents",
|
|
272
|
+
"skills",
|
|
273
|
+
"garden-interactive-chart",
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function installSkill(destinationRoot = siteRoot) {
|
|
278
|
+
const from = packagedSkillRoot();
|
|
279
|
+
if (!fs.existsSync(path.join(from, "SKILL.md"))) {
|
|
280
|
+
fail("Garden 包里缺少图表 skill。");
|
|
281
|
+
}
|
|
282
|
+
const to = path.join(destinationRoot, ".agents", "skills", "garden-interactive-chart");
|
|
283
|
+
fs.cpSync(from, to, { recursive: true });
|
|
284
|
+
const shown = path.relative(destinationRoot, to) || to;
|
|
285
|
+
process.stdout.write(`已安装图表 skill:${shown}\n`);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function help() {
|
|
289
|
+
process.stdout.write(`Garden 命令:
|
|
290
|
+
garden dev 同步内容并本地预览
|
|
291
|
+
garden build 同步内容并构建静态站点
|
|
292
|
+
garden start 启动已构建的服务
|
|
293
|
+
garden sync 只从 Markdown 生成索引
|
|
294
|
+
garden new 创建文章草稿
|
|
295
|
+
garden embed 拉取外链摘要
|
|
296
|
+
garden init [目录] 创建只含稿和配置的新站点
|
|
297
|
+
garden skill 把可交互图表 skill 装进当前站点
|
|
298
|
+
garden pages:validate 检查 dist/client
|
|
299
|
+
garden pages:prepare 写入公开产物仓库
|
|
300
|
+
garden webmentions 发送 Webmentions
|
|
301
|
+
`);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
switch (command) {
|
|
305
|
+
case "help":
|
|
306
|
+
case "--help":
|
|
307
|
+
case "-h":
|
|
308
|
+
help();
|
|
309
|
+
break;
|
|
310
|
+
case "sync":
|
|
311
|
+
sync();
|
|
312
|
+
break;
|
|
313
|
+
case "dev":
|
|
314
|
+
runVinext(["dev", ...rest]);
|
|
315
|
+
break;
|
|
316
|
+
case "build":
|
|
317
|
+
runVinext(["build", ...rest], { copyDist: true });
|
|
318
|
+
break;
|
|
319
|
+
case "start":
|
|
320
|
+
runVinext(["start", ...rest]);
|
|
321
|
+
break;
|
|
322
|
+
case "new":
|
|
323
|
+
runNode("scripts/new-post.mjs", rest);
|
|
324
|
+
break;
|
|
325
|
+
case "embed":
|
|
326
|
+
runNode("scripts/fetch-embed.mjs", rest);
|
|
327
|
+
break;
|
|
328
|
+
case "init":
|
|
329
|
+
initSite();
|
|
330
|
+
break;
|
|
331
|
+
case "skill":
|
|
332
|
+
case "skills":
|
|
333
|
+
installSkill();
|
|
334
|
+
break;
|
|
335
|
+
case "pages:validate":
|
|
336
|
+
runNode("scripts/prepare-pages-output.mjs", [
|
|
337
|
+
rest[0] ?? path.join(siteRoot, "dist", "client"),
|
|
338
|
+
]);
|
|
339
|
+
break;
|
|
340
|
+
case "pages:prepare":
|
|
341
|
+
runNode("scripts/prepare-pages-output.mjs", rest);
|
|
342
|
+
break;
|
|
343
|
+
case "webmentions":
|
|
344
|
+
runNode("scripts/send-webmentions.mjs", rest);
|
|
345
|
+
break;
|
|
346
|
+
default:
|
|
347
|
+
fail(`未知命令:${command}`);
|
|
348
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { access, cp, mkdir, rm } from "node:fs/promises";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import type { Plugin } from "vite";
|
|
4
|
+
|
|
5
|
+
async function exists(path: string): Promise<boolean> {
|
|
6
|
+
try {
|
|
7
|
+
await access(path);
|
|
8
|
+
return true;
|
|
9
|
+
} catch (error) {
|
|
10
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
throw error;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Packages the optional backend hosting metadata after Vite finishes compiling.
|
|
18
|
+
export function sites(): Plugin {
|
|
19
|
+
let root = process.cwd();
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
name: "sites",
|
|
23
|
+
apply: "build",
|
|
24
|
+
configResolved(config) {
|
|
25
|
+
root = config.root;
|
|
26
|
+
},
|
|
27
|
+
async closeBundle() {
|
|
28
|
+
const outputDirectory = resolve(root, "dist", ".openai");
|
|
29
|
+
const hostingConfig = resolve(root, ".openai", "hosting.json");
|
|
30
|
+
|
|
31
|
+
await rm(outputDirectory, { recursive: true, force: true });
|
|
32
|
+
await mkdir(outputDirectory, { recursive: true });
|
|
33
|
+
|
|
34
|
+
if (await exists(hostingConfig)) {
|
|
35
|
+
await cp(hostingConfig, resolve(outputDirectory, "hosting.json"));
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { defineConfig, globalIgnores } from "eslint/config";
|
|
2
|
+
import nextVitals from "eslint-config-next/core-web-vitals";
|
|
3
|
+
import nextTs from "eslint-config-next/typescript";
|
|
4
|
+
|
|
5
|
+
const eslintConfig = defineConfig([
|
|
6
|
+
...nextVitals,
|
|
7
|
+
...nextTs,
|
|
8
|
+
// Override default ignores of eslint-config-next.
|
|
9
|
+
globalIgnores([
|
|
10
|
+
// Default ignores of eslint-config-next:
|
|
11
|
+
".next/**",
|
|
12
|
+
"out/**",
|
|
13
|
+
"build/**",
|
|
14
|
+
".garden/**",
|
|
15
|
+
"templates/**",
|
|
16
|
+
"next-env.d.ts",
|
|
17
|
+
]),
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
export default eslintConfig;
|
package/next.config.ts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "hd-garden",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "用 Markdown 写作、用 GitHub Pages 发布的个人博客引擎。",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://github.com/hedon954/garden",
|
|
8
|
+
"bugs": {
|
|
9
|
+
"url": "https://github.com/hedon954/garden/issues"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"hd-garden",
|
|
13
|
+
"blog",
|
|
14
|
+
"markdown",
|
|
15
|
+
"github-pages",
|
|
16
|
+
"static-site"
|
|
17
|
+
],
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"bin": {
|
|
22
|
+
"garden": "bin/garden.mjs",
|
|
23
|
+
"hd-garden": "bin/garden.mjs"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"app",
|
|
27
|
+
"bin",
|
|
28
|
+
"build",
|
|
29
|
+
"public/favicon.svg",
|
|
30
|
+
"public/og.png",
|
|
31
|
+
"public/images",
|
|
32
|
+
"scripts",
|
|
33
|
+
"templates",
|
|
34
|
+
"worker",
|
|
35
|
+
".agents",
|
|
36
|
+
".env.example",
|
|
37
|
+
".openai",
|
|
38
|
+
"Makefile",
|
|
39
|
+
"eslint.config.mjs",
|
|
40
|
+
"next.config.ts",
|
|
41
|
+
"postcss.config.mjs",
|
|
42
|
+
"site.config.yaml",
|
|
43
|
+
"tsconfig.json",
|
|
44
|
+
"vite.config.ts"
|
|
45
|
+
],
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "git+https://github.com/hedon954/garden.git"
|
|
49
|
+
},
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=22.13.0"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"content:sync": "node bin/garden.mjs sync",
|
|
55
|
+
"pages:validate": "node bin/garden.mjs pages:validate",
|
|
56
|
+
"webmentions:send": "node bin/garden.mjs webmentions",
|
|
57
|
+
"predev": "npm run content:sync",
|
|
58
|
+
"dev": "node bin/garden.mjs dev",
|
|
59
|
+
"prebuild": "npm run content:sync",
|
|
60
|
+
"build": "node bin/garden.mjs build",
|
|
61
|
+
"start": "node bin/garden.mjs start",
|
|
62
|
+
"admin:local": "npm run start -- --host 0.0.0.0 --port 3000",
|
|
63
|
+
"admin:cloudflare": "npm run build && wrangler deploy --config dist/server/wrangler.json",
|
|
64
|
+
"test": "npm run build && node --test tests/*.test.mjs",
|
|
65
|
+
"lint": "node bin/garden.mjs sync && eslint . --ignore-pattern dist --ignore-pattern .next --ignore-pattern .garden --ignore-pattern templates"
|
|
66
|
+
},
|
|
67
|
+
"dependencies": {
|
|
68
|
+
"@cloudflare/vite-plugin": "1.37.1",
|
|
69
|
+
"@phosphor-icons/react": "^2.1.10",
|
|
70
|
+
"@tailwindcss/postcss": "4.2.1",
|
|
71
|
+
"@vitejs/plugin-react": "6.0.4",
|
|
72
|
+
"@vitejs/plugin-rsc": "0.5.30",
|
|
73
|
+
"fuse.js": "^7.5.0",
|
|
74
|
+
"github-slugger": "^2.0.0",
|
|
75
|
+
"gray-matter": "^4.0.3",
|
|
76
|
+
"katex": "^0.16.47",
|
|
77
|
+
"marked": "^16.4.2",
|
|
78
|
+
"mdast-util-to-string": "^4.0.0",
|
|
79
|
+
"mermaid": "^11.16.0",
|
|
80
|
+
"next": "16.3.0",
|
|
81
|
+
"react": "19.2.8",
|
|
82
|
+
"react-dom": "19.2.8",
|
|
83
|
+
"react-markdown": "^10.1.0",
|
|
84
|
+
"react-server-dom-webpack": "19.2.8",
|
|
85
|
+
"rehype-autolink-headings": "^7.1.0",
|
|
86
|
+
"rehype-highlight": "^7.0.2",
|
|
87
|
+
"rehype-katex": "^7.0.1",
|
|
88
|
+
"rehype-raw": "^7.0.0",
|
|
89
|
+
"rehype-slug": "^6.0.0",
|
|
90
|
+
"remark-breaks": "^4.0.0",
|
|
91
|
+
"remark-cjk-friendly": "^2.3.1",
|
|
92
|
+
"remark-gfm": "^4.0.1",
|
|
93
|
+
"remark-math": "^6.0.0",
|
|
94
|
+
"remark-parse": "^11.0.0",
|
|
95
|
+
"tailwindcss": "4.2.1",
|
|
96
|
+
"typescript": "5.9.3",
|
|
97
|
+
"unified": "^11.0.5",
|
|
98
|
+
"unist-util-visit": "^5.1.0",
|
|
99
|
+
"vinext": "0.0.50",
|
|
100
|
+
"vite": "8.0.13",
|
|
101
|
+
"wrangler": "4.116.0",
|
|
102
|
+
"yaml": "^2.9.0"
|
|
103
|
+
},
|
|
104
|
+
"devDependencies": {
|
|
105
|
+
"@types/node": "22.19.19",
|
|
106
|
+
"@types/react": "19.2.17",
|
|
107
|
+
"@types/react-dom": "19.2.3",
|
|
108
|
+
"eslint": "9.39.4",
|
|
109
|
+
"eslint-config-next": "16.2.12"
|
|
110
|
+
},
|
|
111
|
+
"type": "module"
|
|
112
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
|
|
2
|
+
<rect width="256" height="256" fill="#f8f8f6"/>
|
|
3
|
+
<path fill="#202124" d="M216,208H168V184h4a68.07,68.07,0,0,0,68-68,28,28,0,0,0-56,0,12,12,0,0,1-12,12h-4V56a40,40,0,0,0-80,0V88H84A12,12,0,0,1,72,76a28,28,0,0,0-56,0,68.07,68.07,0,0,0,68,68h4v64H40a8,8,0,0,0,0,16H216a8,8,0,0,0,0-16ZM96,128H84A52.06,52.06,0,0,1,32,76a12,12,0,0,1,24,0,28,28,0,0,0,28,28H96a8,8,0,0,0,8-8V56a24,24,0,0,1,48,0v80a8,8,0,0,0,8,8h12a28,28,0,0,0,28-28,12,12,0,0,1,24,0,52.06,52.06,0,0,1-52,52H160a8,8,0,0,0-8,8v32H104V136A8,8,0,0,0,96,128Z"/>
|
|
4
|
+
</svg>
|
|
Binary file
|
package/public/og.png
ADDED
|
Binary file
|