hd-garden 0.7.0 → 0.8.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-ui/SKILL.md +26 -19
- package/README.en.md +57 -0
- package/README.md +31 -46
- package/app/blog/[...path]/page.tsx +9 -1
- package/app/columns/[column]/[...path]/page.tsx +10 -12
- package/app/components/ArticlePager.tsx +32 -0
- package/app/components/Comments.tsx +2 -1
- package/app/components/FeedMore.tsx +70 -0
- package/app/components/GardenDocsNav.tsx +25 -20
- package/app/components/HandbookLangRedirect.tsx +30 -0
- package/app/components/HandbookLangSwitch.tsx +49 -0
- package/app/components/HandbookView.tsx +187 -0
- package/app/components/MarkdownArticle.tsx +11 -5
- package/app/components/MarkdownImage.tsx +6 -4
- package/app/components/SiteChrome.tsx +11 -2
- package/app/components/SiteFooter.tsx +5 -3
- package/app/components/SiteHeader.tsx +27 -23
- package/app/components/TableOfContents.tsx +15 -9
- package/app/components/ThoughtFeed.tsx +41 -0
- package/app/components/UiLang.tsx +18 -0
- package/app/components/Webmentions.tsx +3 -2
- package/app/components/garden/FilePreview.tsx +13 -11
- package/app/components/garden/GardenBlock.tsx +3 -1
- package/app/components/garden/GardenFile.tsx +9 -7
- package/app/components/garden/blocks.tsx +34 -24
- package/app/components/garden/chrome.tsx +20 -13
- package/app/garden/[...slug]/page.tsx +10 -130
- package/app/garden/en/[...slug]/page.tsx +28 -0
- package/app/garden/en/page.tsx +16 -0
- package/app/garden/page.tsx +7 -74
- package/app/garden-modules.d.ts +3 -0
- package/app/layout.tsx +2 -4
- package/app/lib/content.ts +12 -2
- package/app/lib/garden-docs-meta.ts +60 -5
- package/app/lib/handbook-path.ts +11 -0
- package/app/lib/i18n.ts +222 -0
- package/app/lib/page-alternates.ts +2 -2
- package/app/not-found.tsx +5 -3
- package/app/site.config.ts +1 -1
- package/app/styles/a2.css +101 -32
- package/app/styles/shell.css +6 -0
- package/app/thoughts/page/[n]/page.tsx +44 -0
- package/app/thoughts/page.tsx +2 -6
- package/bin/garden.mjs +4 -0
- package/docs/handbook/ai-writing.md +99 -0
- package/docs/handbook/components/card.md +2 -0
- package/docs/handbook/components/chart.md +2 -0
- package/docs/handbook/components/compare.md +10 -12
- package/docs/handbook/components/figure.md +2 -0
- package/docs/handbook/components/file.md +2 -0
- package/docs/handbook/components/html.md +2 -0
- package/docs/handbook/components/interactive.md +2 -0
- package/docs/handbook/components/stat.md +2 -0
- package/docs/handbook/components/timeline.md +2 -0
- package/docs/handbook/components.md +1 -1
- package/docs/handbook/config.md +2 -2
- package/docs/handbook/deploy.md +1 -1
- package/docs/handbook/design.md +3 -3
- package/docs/handbook/en/ai-writing.md +99 -0
- package/docs/handbook/en/components/card.md +46 -0
- package/docs/handbook/en/components/chart.md +54 -0
- package/docs/handbook/en/components/compare.md +38 -0
- package/docs/handbook/en/components/figure.md +72 -0
- package/docs/handbook/en/components/file/checklist.md +17 -0
- package/docs/handbook/en/components/file.md +34 -0
- package/docs/handbook/en/components/html.md +69 -0
- package/docs/handbook/en/components/interactive.md +67 -0
- package/docs/handbook/en/components/stat.md +57 -0
- package/docs/handbook/en/components/timeline.md +52 -0
- package/docs/handbook/en/components.md +30 -0
- package/docs/handbook/en/config.md +125 -0
- package/docs/handbook/en/deploy.md +273 -0
- package/docs/handbook/en/design.md +81 -0
- package/docs/handbook/en/index.md +192 -0
- package/docs/handbook/en/start.md +92 -0
- package/docs/handbook/en/workflow.md +81 -0
- package/docs/handbook/en/writing.md +156 -0
- package/docs/handbook/index.md +98 -3
- package/docs/handbook/writing.md +18 -10
- package/package.json +1 -1
- package/scripts/build-content.mjs +23 -3
- package/scripts/handbook-facts.mjs +34 -0
- package/scripts/html-lang.mjs +36 -0
- package/scripts/static-404.mjs +25 -0
- package/scripts/ui-cli.mjs +14 -8
- package/templates/site/site.config.yaml +2 -0
- package/ui/core/apply.mjs +22 -4
- package/ui/core/cli-locale.mjs +67 -0
- package/ui/core/fence.mjs +18 -3
- package/ui/core/fix-prompt.mjs +18 -15
- package/ui/core/garden-fence.mjs +64 -0
- package/ui/core/scan.mjs +19 -3
- package/ui/garden/adjacent.mjs +21 -0
- package/ui/garden/check.mjs +6 -0
- package/ui/garden/html-lang.mjs +16 -0
- package/ui/garden/paging.mjs +30 -0
- package/ui/preview/dist/garden-ui.css +9 -9
- package/ui/preview/dist/garden-ui.esm.js +169 -169
- package/ui/preview/dist/garden-ui.iife.js +169 -169
- package/ui/react/components/chart.tsx +7 -4
- package/ui/react/components/display.tsx +6 -3
- package/ui/react/components/inputs.tsx +6 -3
- package/ui/react/components/layout.tsx +6 -4
- package/ui/react/components/media.tsx +7 -4
- package/ui/react/components/sandbox.tsx +3 -1
- package/ui/react/index.tsx +11 -7
- package/ui/react/lang.tsx +85 -0
- package/ui/react/runtime.tsx +3 -1
- package/ui/react/ui.css +9 -9
- package/worker/index.ts +8 -1
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Deploy to GitHub Pages
|
|
3
|
+
navTitle: Deploy to GitHub Pages
|
|
4
|
+
description: Configure a GitHub Actions workflow that supports either a single public repository or a private source repo paired with a public Pages repo.
|
|
5
|
+
section: getting-started
|
|
6
|
+
order: 4
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
`npx hd-garden init` does not create `.github/workflows/pages.yml` automatically. Add this file to your site repository when you are ready to publish to GitHub Pages. The same workflow supports both a **single public repository** and a **private source repository + public Pages repository**, switching automatically based on whether the `PAGES_REPOSITORY` variable is set.
|
|
10
|
+
|
|
11
|
+
The workflow below matches Garden's own `.github/workflows/pages.yml` with two adjustments for a site repo: it omits `paths-ignore`, `npm run lint`, and `npm test` (which only apply to the engine repo), and its `publish-repository` job installs dependencies before running `npx garden pages:prepare static-site pages-repository` (the same `scripts/prepare-pages-output.mjs` script Garden calls directly). Commit `package-lock.json` after your first `npm install` so `npm ci` can run in CI.
|
|
12
|
+
|
|
13
|
+
The `build` job runs only when the repository is named `<owner>.github.io`, or when the `SITE_URL` or `PAGES_REPOSITORY` variable is set.
|
|
14
|
+
|
|
15
|
+
## 1. Full `.github/workflows/pages.yml` Workflow
|
|
16
|
+
|
|
17
|
+
Create `.github/workflows/pages.yml` in your site repository:
|
|
18
|
+
|
|
19
|
+
```yaml
|
|
20
|
+
name: Verify and publish blog
|
|
21
|
+
|
|
22
|
+
on:
|
|
23
|
+
push:
|
|
24
|
+
branches: [main]
|
|
25
|
+
workflow_dispatch:
|
|
26
|
+
|
|
27
|
+
permissions:
|
|
28
|
+
contents: read
|
|
29
|
+
|
|
30
|
+
concurrency:
|
|
31
|
+
group: publish-blog
|
|
32
|
+
cancel-in-progress: true
|
|
33
|
+
|
|
34
|
+
jobs:
|
|
35
|
+
build:
|
|
36
|
+
if: github.event.repository.name == format('{0}.github.io', github.repository_owner) || vars.SITE_URL != '' || vars.PAGES_REPOSITORY != ''
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
steps:
|
|
39
|
+
- uses: actions/checkout@v7
|
|
40
|
+
|
|
41
|
+
- uses: actions/setup-node@v7
|
|
42
|
+
with:
|
|
43
|
+
node-version: 22
|
|
44
|
+
cache: npm
|
|
45
|
+
|
|
46
|
+
- run: npm ci
|
|
47
|
+
|
|
48
|
+
- name: Resolve the public URL
|
|
49
|
+
env:
|
|
50
|
+
SITE_URL_OVERRIDE: ${{ vars.SITE_URL }}
|
|
51
|
+
ADMIN_URL_OVERRIDE: ${{ vars.ADMIN_URL }}
|
|
52
|
+
GISCUS_REPO: ${{ vars.GISCUS_REPO }}
|
|
53
|
+
GISCUS_REPO_ID: ${{ vars.GISCUS_REPO_ID }}
|
|
54
|
+
GISCUS_CATEGORY: ${{ vars.GISCUS_CATEGORY }}
|
|
55
|
+
GISCUS_CATEGORY_ID: ${{ vars.GISCUS_CATEGORY_ID }}
|
|
56
|
+
GISCUS_DISABLED: ${{ vars.GISCUS_DISABLED }}
|
|
57
|
+
WEBMENTION_IO_DOMAIN: ${{ vars.WEBMENTION_IO_DOMAIN }}
|
|
58
|
+
ANALYTICS_DOMAIN: ${{ vars.ANALYTICS_DOMAIN }}
|
|
59
|
+
ANALYTICS_SCRIPT: ${{ vars.ANALYTICS_SCRIPT }}
|
|
60
|
+
run: |
|
|
61
|
+
owner="$GITHUB_REPOSITORY_OWNER"
|
|
62
|
+
default_url="https://$owner.github.io"
|
|
63
|
+
giscus_ready=1
|
|
64
|
+
if [ -z "$GISCUS_REPO" ] || [ "$GISCUS_REPO" = "owner/repository" ]; then giscus_ready=0; fi
|
|
65
|
+
if [ -z "$GISCUS_REPO_ID" ] || [ "$GISCUS_REPO_ID" = "R_xxxxxxxxxx" ]; then giscus_ready=0; fi
|
|
66
|
+
if [ -z "$GISCUS_CATEGORY" ]; then giscus_ready=0; fi
|
|
67
|
+
if [ -z "$GISCUS_CATEGORY_ID" ] || [ "$GISCUS_CATEGORY_ID" = "DIC_xxxxxxxxxx" ]; then giscus_ready=0; fi
|
|
68
|
+
if [ "$giscus_ready" != 1 ]; then
|
|
69
|
+
if [ "$GISCUS_DISABLED" = "1" ]; then
|
|
70
|
+
GISCUS_REPO=
|
|
71
|
+
GISCUS_REPO_ID=
|
|
72
|
+
GISCUS_CATEGORY=
|
|
73
|
+
GISCUS_CATEGORY_ID=
|
|
74
|
+
echo "GISCUS_DISABLED=1,本次构建不开放评论。"
|
|
75
|
+
else
|
|
76
|
+
echo "评论还没有配置,构建已停止。"
|
|
77
|
+
echo "1. 准备一个公开 GitHub 仓库,并开启 Discussions。"
|
|
78
|
+
echo "2. 打开 https://giscus.app/zh-CN ,安装 Giscus App,选中该仓库和分类。"
|
|
79
|
+
echo "3. 在 Settings → Secrets and variables → Actions → Variables 写入:"
|
|
80
|
+
echo " GISCUS_REPO、GISCUS_REPO_ID、GISCUS_CATEGORY、GISCUS_CATEGORY_ID"
|
|
81
|
+
echo "确定不开放评论时,把 GISCUS_DISABLED 设为 1。留空和示例值 owner/repository 都不会通过。"
|
|
82
|
+
exit 1
|
|
83
|
+
fi
|
|
84
|
+
fi
|
|
85
|
+
{
|
|
86
|
+
echo "SITE_URL=${SITE_URL_OVERRIDE:-$default_url}"
|
|
87
|
+
echo "NEXT_PUBLIC_SITE_URL=${SITE_URL_OVERRIDE:-$default_url}"
|
|
88
|
+
echo "ADMIN_URL=$ADMIN_URL_OVERRIDE"
|
|
89
|
+
echo "GISCUS_REPO=$GISCUS_REPO"
|
|
90
|
+
echo "GISCUS_REPO_ID=$GISCUS_REPO_ID"
|
|
91
|
+
echo "GISCUS_CATEGORY=$GISCUS_CATEGORY"
|
|
92
|
+
echo "GISCUS_CATEGORY_ID=$GISCUS_CATEGORY_ID"
|
|
93
|
+
echo "NEXT_PUBLIC_GISCUS_REPO=$GISCUS_REPO"
|
|
94
|
+
echo "NEXT_PUBLIC_GISCUS_REPO_ID=$GISCUS_REPO_ID"
|
|
95
|
+
echo "NEXT_PUBLIC_GISCUS_CATEGORY=$GISCUS_CATEGORY"
|
|
96
|
+
echo "NEXT_PUBLIC_GISCUS_CATEGORY_ID=$GISCUS_CATEGORY_ID"
|
|
97
|
+
echo "WEBMENTION_IO_DOMAIN=$WEBMENTION_IO_DOMAIN"
|
|
98
|
+
echo "NEXT_PUBLIC_ANALYTICS_DOMAIN=$ANALYTICS_DOMAIN"
|
|
99
|
+
echo "NEXT_PUBLIC_ANALYTICS_SCRIPT=$ANALYTICS_SCRIPT"
|
|
100
|
+
echo "STATIC_EXPORT=1"
|
|
101
|
+
} >> "$GITHUB_ENV"
|
|
102
|
+
|
|
103
|
+
- name: Build the public site
|
|
104
|
+
env:
|
|
105
|
+
GITHUB_TOKEN: ${{ github.token }}
|
|
106
|
+
run: npm run build
|
|
107
|
+
|
|
108
|
+
- name: Validate the static output
|
|
109
|
+
run: npm run pages:validate
|
|
110
|
+
|
|
111
|
+
- name: Configure GitHub Pages
|
|
112
|
+
if: ${{ vars.PAGES_REPOSITORY == '' }}
|
|
113
|
+
uses: actions/configure-pages@v6
|
|
114
|
+
|
|
115
|
+
- name: Upload the GitHub Pages artifact
|
|
116
|
+
if: ${{ vars.PAGES_REPOSITORY == '' }}
|
|
117
|
+
uses: actions/upload-pages-artifact@v5
|
|
118
|
+
with:
|
|
119
|
+
path: dist/client
|
|
120
|
+
|
|
121
|
+
- name: Upload the public-repository artifact
|
|
122
|
+
if: ${{ vars.PAGES_REPOSITORY != '' }}
|
|
123
|
+
uses: actions/upload-artifact@v7
|
|
124
|
+
with:
|
|
125
|
+
name: static-site
|
|
126
|
+
path: dist/client
|
|
127
|
+
include-hidden-files: true
|
|
128
|
+
if-no-files-found: error
|
|
129
|
+
retention-days: 1
|
|
130
|
+
|
|
131
|
+
deploy-pages:
|
|
132
|
+
if: ${{ vars.PAGES_REPOSITORY == '' }}
|
|
133
|
+
needs: build
|
|
134
|
+
permissions:
|
|
135
|
+
pages: write
|
|
136
|
+
id-token: write
|
|
137
|
+
environment:
|
|
138
|
+
name: github-pages
|
|
139
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
140
|
+
runs-on: ubuntu-latest
|
|
141
|
+
steps:
|
|
142
|
+
- name: Deploy the GitHub Pages artifact
|
|
143
|
+
id: deployment
|
|
144
|
+
uses: actions/deploy-pages@v5
|
|
145
|
+
|
|
146
|
+
publish-repository:
|
|
147
|
+
if: ${{ vars.PAGES_REPOSITORY != '' }}
|
|
148
|
+
needs: build
|
|
149
|
+
runs-on: ubuntu-latest
|
|
150
|
+
steps:
|
|
151
|
+
- uses: actions/checkout@v7
|
|
152
|
+
|
|
153
|
+
- uses: actions/setup-node@v7
|
|
154
|
+
with:
|
|
155
|
+
node-version: 22
|
|
156
|
+
cache: npm
|
|
157
|
+
|
|
158
|
+
- run: npm ci
|
|
159
|
+
|
|
160
|
+
- name: Check the split-repository configuration
|
|
161
|
+
env:
|
|
162
|
+
PAGES_DEPLOY_KEY: ${{ secrets.PAGES_DEPLOY_KEY }}
|
|
163
|
+
TARGET_REPOSITORY: ${{ vars.PAGES_REPOSITORY }}
|
|
164
|
+
run: |
|
|
165
|
+
test -n "$PAGES_DEPLOY_KEY" || { echo "缺少 PAGES_DEPLOY_KEY Secret"; exit 1; }
|
|
166
|
+
[[ "$TARGET_REPOSITORY" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]] || {
|
|
167
|
+
echo "PAGES_REPOSITORY 格式无效"
|
|
168
|
+
exit 1
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
- uses: actions/download-artifact@v8
|
|
172
|
+
with:
|
|
173
|
+
name: static-site
|
|
174
|
+
path: static-site
|
|
175
|
+
|
|
176
|
+
- name: Check out the public output repository
|
|
177
|
+
uses: actions/checkout@v7
|
|
178
|
+
with:
|
|
179
|
+
repository: ${{ vars.PAGES_REPOSITORY }}
|
|
180
|
+
ssh-key: ${{ secrets.PAGES_DEPLOY_KEY }}
|
|
181
|
+
path: pages-repository
|
|
182
|
+
fetch-depth: 1
|
|
183
|
+
|
|
184
|
+
- name: Start a clean rolling deployment
|
|
185
|
+
working-directory: pages-repository
|
|
186
|
+
env:
|
|
187
|
+
TARGET_REPOSITORY: ${{ vars.PAGES_REPOSITORY }}
|
|
188
|
+
run: |
|
|
189
|
+
expected_remote="git@github.com:$TARGET_REPOSITORY.git"
|
|
190
|
+
actual_remote="$(git remote get-url origin)"
|
|
191
|
+
test "$actual_remote" = "$expected_remote" || {
|
|
192
|
+
echo "拒绝发布到非目标仓库:$actual_remote"
|
|
193
|
+
exit 1
|
|
194
|
+
}
|
|
195
|
+
echo "PAGES_PREVIOUS_TREE=$(git rev-parse 'HEAD^{tree}')" >> "$GITHUB_ENV"
|
|
196
|
+
git checkout --orphan pages-deploy
|
|
197
|
+
git rm -q -r -f .
|
|
198
|
+
|
|
199
|
+
- name: Prepare generated-only repository contents
|
|
200
|
+
run: npx garden pages:prepare static-site pages-repository
|
|
201
|
+
|
|
202
|
+
- name: Publish the rolling deployment commit
|
|
203
|
+
working-directory: pages-repository
|
|
204
|
+
run: |
|
|
205
|
+
git config user.name "github-actions[bot]"
|
|
206
|
+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
207
|
+
git add -A
|
|
208
|
+
new_tree="$(git write-tree)"
|
|
209
|
+
if [ "$new_tree" = "$PAGES_PREVIOUS_TREE" ]; then
|
|
210
|
+
echo "静态产物没有变化,无需发布"
|
|
211
|
+
exit 0
|
|
212
|
+
fi
|
|
213
|
+
git commit -m "Deploy ${GITHUB_SHA}"
|
|
214
|
+
git push --force origin HEAD:main
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## 2. Setting Up Each Deployment Mode
|
|
218
|
+
|
|
219
|
+
### Mode 1: Single Public Repository (`PAGES_REPOSITORY` Left Empty)
|
|
220
|
+
|
|
221
|
+
If your source repository is public, name it `<owner>.github.io`. If you use another repository name, attach a custom domain to Pages and set the `SITE_URL` variable to that domain, or the `build` job will not run (Garden does not support subpath deployments such as `/<repo>/`):
|
|
222
|
+
|
|
223
|
+
1. Open **Settings → Pages** in the repository and set **Source** to **GitHub Actions**.
|
|
224
|
+
2. In **Settings → Secrets and variables → Actions → Variables**, add `GISCUS_DISABLED=1` (or set the 4 `GISCUS_*` variables below).
|
|
225
|
+
3. Push to `main`; the workflow runs `deploy-pages` to publish directly.
|
|
226
|
+
|
|
227
|
+
### Mode 2: Private Source Repository + Public Pages Repository
|
|
228
|
+
|
|
229
|
+
To keep drafts (`draft: true`) and commit history private, store your source files in a private repository (such as `blog-source`) and push only the generated static files to a public `<owner>.github.io` repository:
|
|
230
|
+
|
|
231
|
+
1. **Generate an SSH deploy key** locally:
|
|
232
|
+
```bash
|
|
233
|
+
ssh-keygen -t ed25519 -C "garden-pages-deploy" -f ./pages_deploy_key
|
|
234
|
+
```
|
|
235
|
+
2. **Add the public key to the public repository**: Open **Settings → Deploy keys → Add deploy key** in `<owner>.github.io`, paste `pages_deploy_key.pub`, and check **Allow write access**.
|
|
236
|
+
3. **Configure Pages on the public repository**: Open **Settings → Pages** in `<owner>.github.io`, set **Source** to **Deploy from a branch**, and select `main` with `/ (root)`.
|
|
237
|
+
4. **Configure variables and secrets on the private repository**: Open **Settings → Secrets and variables → Actions** in the private source repo:
|
|
238
|
+
- Under **Variables**, add `PAGES_REPOSITORY` set to `<owner>/<owner>.github.io` (along with `GISCUS_DISABLED=1` or the 4 `GISCUS_*` variables).
|
|
239
|
+
- Under **Secrets**, add `PAGES_DEPLOY_KEY` with the full contents of the private key file `pages_deploy_key`.
|
|
240
|
+
5. On every push to the private repo, the `publish-repository` job checks out the public repository using `PAGES_DEPLOY_KEY`, verifies that `PAGES_DEPLOY_KEY` is present, `PAGES_REPOSITORY` matches `owner/repo`, and the remote URL matches the target repository, then runs `npx garden pages:prepare static-site pages-repository` to write the static output, `.nojekyll`, and a short `README.md` before force-pushing a single orphan commit to `main`.
|
|
241
|
+
|
|
242
|
+
## 3. Giscus Comments Configuration
|
|
243
|
+
|
|
244
|
+
Article comments use GitHub Discussions via [Giscus](https://giscus.app). The application code reads `GISCUS_REPO`, `GISCUS_REPO_ID`, `GISCUS_CATEGORY`, and `GISCUS_CATEGORY_ID` (and their `NEXT_PUBLIC_GISCUS_*` counterparts), while the `Resolve the public URL` step in `pages.yml` checks that all four values are present before building:
|
|
245
|
+
|
|
246
|
+
- **Disable comments**: Set `GISCUS_DISABLED=1` in GitHub Actions Variables; the workflow clears `GISCUS_*` and omits the comment section.
|
|
247
|
+
- **Enable comments**: Enable Discussions on a public repository, install the Giscus App, and copy the four values into GitHub Actions Variables.
|
|
248
|
+
|
|
249
|
+
> [!NOTE] Note
|
|
250
|
+
> `npx garden pages:validate` inspects the `dist/client` directory (verifying that `index.html` exists and that no symlinks, `.env` files, `node_modules`, `.git`, `app`, `content`, `.md`, `.map`, or `.ts` files leaked into the export, apart from `.md` attachments under `media/`). It does not check Giscus variables itself.
|
|
251
|
+
|
|
252
|
+
## 4. Environment Variables Reference
|
|
253
|
+
|
|
254
|
+
All environment variables can be placed in a local `.env.local` file (never committed to Git) or in your deployment platform's Variables and Secrets:
|
|
255
|
+
|
|
256
|
+
| Variable | Purpose |
|
|
257
|
+
| :--- | :--- |
|
|
258
|
+
| `SITE_URL` / `NEXT_PUBLIC_SITE_URL` | Public root URL of the site (such as `https://example.com`), used for canonical URLs, RSS feeds, and `sitemap.xml` |
|
|
259
|
+
| `NEXT_PUBLIC_BASE_PATH` | Optional base path prefix (empty by default) |
|
|
260
|
+
| `STATIC_EXPORT` | Set to `1` to export static HTML and enable trailing slashes on internal links |
|
|
261
|
+
| `TRAILING_SLASH` | Set to `1` in server builds to enable trailing slashes on internal links (already on when `STATIC_EXPORT=1`) |
|
|
262
|
+
| `CONTENT_INCLUDE_DRAFTS` | Set to `1` to include `draft: true` drafts and future `publishAt` posts in the build |
|
|
263
|
+
| `GITHUB_TOKEN` | Fetches public GitHub profile data and pinned repositories at build time (pass `${{ github.token }}` in GitHub Actions) |
|
|
264
|
+
| `GISCUS_DISABLED` | Read by the `pages.yml` workflow script: set to `1` to build without the 4 `GISCUS_*` variables |
|
|
265
|
+
| `GISCUS_REPO` / `GISCUS_REPO_ID` | Public GitHub repository (`owner/repo`) and Repository ID for Giscus |
|
|
266
|
+
| `GISCUS_CATEGORY` / `GISCUS_CATEGORY_ID` | Discussions category name and Category ID for Giscus |
|
|
267
|
+
| `WEBMENTION_IO_DOMAIN` / `WEBMENTION_ENDPOINT` | Domain registered with Webmention.io (such as `example.com`) and custom endpoint override |
|
|
268
|
+
| `ADMIN_URL` | Public URL of a separately deployed `/admin` instance |
|
|
269
|
+
| `CONTENT_REPOSITORY` / `CONTENT_BRANCH` | Target GitHub repository (`owner/repo`) and branch (defaults to `main`) where `/admin` commits content |
|
|
270
|
+
| `CONTENT_GITHUB_TOKEN` | Fine-grained GitHub PAT used by `/admin` to commit thoughts and assets |
|
|
271
|
+
| `GITHUB_OAUTH_CLIENT_ID` / `GITHUB_OAUTH_CLIENT_SECRET` | Client ID and Secret of the GitHub OAuth App for `/admin` sign-in |
|
|
272
|
+
| `GITHUB_SESSION_SECRET` | Random secret string used to sign `/admin` session cookies |
|
|
273
|
+
| `ADMIN_GITHUB_LOGINS` | Comma-separated allowlist of GitHub usernames permitted to sign in to `/admin` |
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: How the Reading Layout Works
|
|
3
|
+
navTitle: How the Reading Layout Works
|
|
4
|
+
description: Three-tier 740/1040/1340px adaptive widths, two-line title splitting, zero-CLS image sizing, dark-mode image backing, and warm paper tokens.
|
|
5
|
+
section: writing-and-reading
|
|
6
|
+
order: 11
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
Garden automates several layout details for long-form technical reading. Blog posts, column entries, and this handbook share the same stylesheets and measurement scripts.
|
|
10
|
+
|
|
11
|
+
## 1. Three-tier Adaptive Widths: `740px` / `1040px` / `1340px`
|
|
12
|
+
|
|
13
|
+
An article page consists of a centered reading column and a floating right-hand table of contents (`200px`). On load and window resize, the `ArticleMeasure` script measures wide figures and tables in the body and expands them symmetrically into the available horizontal margins:
|
|
14
|
+
|
|
15
|
+
| Element | Max Width | Expansion Rule and Boundaries |
|
|
16
|
+
| :--- | :--- | :--- |
|
|
17
|
+
| Paragraphs, lists, code blocks, built-in components | `740px` | Body text uses `17px` font size and `1.75` line height (or `1.6` on English handbook pages) and stays inside the reading column |
|
|
18
|
+
| Wide figures (`.fig`) | `1040px` | Retina screenshots default to half their pixel width (`naturalWidth / 2`); when wider than the column on viewports `>= 900px`, they extend up to `150px` on each side (keeping at least `24px` from the viewport edge, or staying between the sidebar and TOC on handbook pages) |
|
|
19
|
+
| Wide tables (`.table-wrap`) | `1340px` | Measured at `max-content` without wrapping; when wider than `740px` on viewports `>= 900px`, tables extend up to `300px` on each side |
|
|
20
|
+
| Floating table of contents (`.toc`) | `200px` | On standalone post pages, when a widened figure or table approaches the TOC, the TOC shifts right to clear it or hides temporarily if the viewport has no room, reappearing once scrolled past |
|
|
21
|
+
|
|
22
|
+
## 2. Two-line Title Splitting and Font Scaling
|
|
23
|
+
|
|
24
|
+
The `ArticleTitle` component applies two rules to headings:
|
|
25
|
+
|
|
26
|
+
1. **With a delimiter (` | ` with surrounding spaces)**: Splits the title into two lines at the first delimiter and computes the `--l` scaling variable from the wider line's character width (counting CJK characters as `1`, uppercase letters as `0.68`, other characters as `0.58`, and spaces as `0.3`) so the longer line fits without wrapping again. Screen readers still read the full title with the delimiter.
|
|
27
|
+
2. **Without a delimiter**: Renders a single `<h1>` with CSS `text-wrap: balance`, never splitting automatically on colons or dashes and never rescaling the font size.
|
|
28
|
+
|
|
29
|
+
> [!NOTE] Note
|
|
30
|
+
> If a title includes a split delimiter with an empty left or right side, `npx garden check` emits a `title-split` warning requiring text on both sides of the delimiter.
|
|
31
|
+
|
|
32
|
+
## 3. Build-time Image Dimensions and Lightbox Zoom
|
|
33
|
+
|
|
34
|
+
When `npx garden sync` runs, the build script reads the pixel width and height of every local image (PNG, JPEG, WebP, GIF, and SVG) and writes them into the `imageSizes` map in `.garden/generated-content.ts`:
|
|
35
|
+
|
|
36
|
+
```garden figure
|
|
37
|
+
caption: Click the buttons above to inspect the three stages from build to browser render
|
|
38
|
+
svg: |
|
|
39
|
+
<svg viewBox="0 0 680 110" xmlns="http://www.w3.org/2000/svg" font-family="var(--sans)">
|
|
40
|
+
<rect data-p="sync" x="12" y="18" width="196" height="74" rx="10" fill="var(--card)" stroke="var(--rule-strong)" stroke-width="1.5"/>
|
|
41
|
+
<text data-p="sync" x="110" y="50" text-anchor="middle" font-size="14" font-weight="600" fill="var(--ink)">1. Build-time Sizing</text>
|
|
42
|
+
<text data-p="sync" x="110" y="72" text-anchor="middle" font-size="12" fill="var(--muted)">Emit width / height & --wl</text>
|
|
43
|
+
<path d="M214 55 H236" stroke="var(--muted)" stroke-width="1.5"/>
|
|
44
|
+
<rect data-p="measure" x="242" y="18" width="196" height="74" rx="10" fill="var(--card)" stroke="var(--rule-strong)" stroke-width="1.5"/>
|
|
45
|
+
<text data-p="measure" x="340" y="50" text-anchor="middle" font-size="14" font-weight="600" fill="var(--ink)">2. Client Measurement</text>
|
|
46
|
+
<text data-p="measure" x="340" y="72" text-anchor="middle" font-size="12" fill="var(--muted)">Fit wide figures & TOC</text>
|
|
47
|
+
<path d="M444 55 H466" stroke="var(--muted)" stroke-width="1.5"/>
|
|
48
|
+
<rect data-p="dark" x="472" y="18" width="196" height="74" rx="10" fill="var(--card)" stroke="var(--rule-strong)" stroke-width="1.5"/>
|
|
49
|
+
<text data-p="dark" x="570" y="50" text-anchor="middle" font-size="14" font-weight="600" fill="var(--ink)">3. Dark Backing & Zoom</text>
|
|
50
|
+
<text data-p="dark" x="570" y="72" text-anchor="middle" font-size="12" fill="var(--muted)">Pad #faf9f5 · Click to zoom</text>
|
|
51
|
+
</svg>
|
|
52
|
+
parts:
|
|
53
|
+
- id: sync
|
|
54
|
+
label: 1. Build-time Sizing
|
|
55
|
+
text: Server-rendered HTML already carries the image's `width`, `height`, `data-nw`, `data-bw`, and initial `--wl` / `--wr`, preventing layout shift before the image finishes downloading.
|
|
56
|
+
- id: measure
|
|
57
|
+
label: 2. Client Measurement
|
|
58
|
+
text: After load, `ArticleMeasure` recomputes the expansion margins for the current viewport width and checks on scroll whether a wide figure overlaps the floating table of contents.
|
|
59
|
+
- id: dark
|
|
60
|
+
label: 3. Dark Backing & Zoom
|
|
61
|
+
text: "Clicking any article figure opens it in a full-screen lightbox (press `Esc` or click the backdrop to close); dark mode sets `--image-backing: #faf9f5` behind figures."
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## 4. Dark-mode Image Backing and Warm Paper Tokens
|
|
65
|
+
|
|
66
|
+
Technical posts frequently include transparent-background architecture diagrams or equation screenshots drawn with dark lines. On a dark page background those dark lines disappear, while a CSS `filter: invert` distorts full-color screenshots.
|
|
67
|
+
|
|
68
|
+
In dark mode, Garden sets `background: var(--image-backing)` (`#faf9f5`) on article figures and lightbox images without applying color inversion or extra padding. Transparent dark-line diagrams stay legible, and opaque screenshots look unchanged. Site favicons share the same backing token, whereas post covers, avatars, and link card thumbnails do not.
|
|
69
|
+
|
|
70
|
+
The site palette is defined in `app/styles/tokens.css` without brand blue:
|
|
71
|
+
|
|
72
|
+
| Token | Light Mode | Dark Mode | Purpose |
|
|
73
|
+
| :--- | :--- | :--- | :--- |
|
|
74
|
+
| `--paper` | `#faf9f5` | `#1f1e1d` | Primary page background (`--image-backing` in dark mode also uses `#faf9f5`) |
|
|
75
|
+
| `--paper-deep` / `--well` | `#f0eee6` | `#262624` | Code block and recessed well background |
|
|
76
|
+
| `--chip` | `#f0eee6` | `#30302e` | Tag pills and button fills |
|
|
77
|
+
| `--card` | `#ffffff` | `#262624` | Elevated cards, figure frames, and dialogs |
|
|
78
|
+
| `--ink` | `#1b1a18` | `#eeece7` | Headings, strong emphasis, and primary interactive ink |
|
|
79
|
+
| `--text` / `--soft` | `#2d2c29` / `#4a4844` | `#dcd9d2` / `#bdb9b0` | Body paragraphs and secondary copy |
|
|
80
|
+
| `--muted` | `#6e6b64` | `#a09b91` | Dates, captions, and table headers (meeting WCAG AA 4.5:1 contrast) |
|
|
81
|
+
| `--rule` / `--rule-strong` | `#e6e3dc` / `#d6d2c9` | `#3d3d3a` / `#4d4c48` | `1px` hairlines, card borders, and emphasized strokes |
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Write in Markdown | Push to Update the Site
|
|
3
|
+
description: Write posts, short notes, and columns in Markdown inside your own Git repository.
|
|
4
|
+
section: overview
|
|
5
|
+
order: 0
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Garden is a Claude-style blog framework where you write posts in Markdown and describe components in plain language.
|
|
9
|
+
|
|
10
|
+
## A Complete Example
|
|
11
|
+
|
|
12
|
+
The four steps in this section come from a real post, and every command output is taken verbatim from an actual run.
|
|
13
|
+
|
|
14
|
+
### 1. The Author Writes an Idea
|
|
15
|
+
|
|
16
|
+
Drop an unnamed `garden` fence where the interactive block should go and state what the reader adjusts and sees:
|
|
17
|
+
|
|
18
|
+
````markdown
|
|
19
|
+
## Writing Pace
|
|
20
|
+
|
|
21
|
+
```garden
|
|
22
|
+
Let the reader drag daily word count and writing days per week to see how many weeks an 8,000-word article takes.
|
|
23
|
+
```
|
|
24
|
+
````
|
|
25
|
+
|
|
26
|
+
### 2. The Agent Scans and Writes Back the Component
|
|
27
|
+
|
|
28
|
+
Tell the Agent to compile `content/posts/writing-pace.md`. Following the `garden-ui` skill in the repository, the Agent first runs `npx garden ui scan` to read the line number, original wording, and hash:
|
|
29
|
+
|
|
30
|
+
```console
|
|
31
|
+
$ npx garden ui scan content/posts/writing-pace.md
|
|
32
|
+
{
|
|
33
|
+
"file": "content/posts/writing-pace.md",
|
|
34
|
+
"draft": false,
|
|
35
|
+
"blocks": [
|
|
36
|
+
{
|
|
37
|
+
"lang": "garden-idea",
|
|
38
|
+
"status": "uncompiled",
|
|
39
|
+
"startLine": 9,
|
|
40
|
+
"intentHash": "8866427a",
|
|
41
|
+
"intent": "Let the reader drag daily word count and writing days per week to see how many weeks an 8,000-word article takes.",
|
|
42
|
+
"section": "Writing Pace"
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Next, the Agent reads the component catalog from `npx garden ui prompt`, writes a `garden interactive` block, and runs `npx garden ui apply` with `--intent-hash` to replace only that fence while leaving the rest of the file byte-for-byte untouched:
|
|
49
|
+
|
|
50
|
+
```console
|
|
51
|
+
$ npx garden ui apply content/posts/writing-pace.md --intent-hash 8866427a --file new.md
|
|
52
|
+
{"ok":true,"file":"content/posts/writing-pace.md","kind":"garden","line":9}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 3. The Pre-commit Hook Checks and Prints a Fix Prompt
|
|
56
|
+
|
|
57
|
+
On the first `garden sync` or `garden dev` run, Garden writes `exec npx --no-install garden ui check --staged --quiet` to `.git/hooks/pre-commit`. If the author commits before step 2, the hook blocks the commit and prints a ready-to-paste prompt for the AI:
|
|
58
|
+
|
|
59
|
+
```console
|
|
60
|
+
$ git commit -m "Writing pace"
|
|
61
|
+
content/posts/writing-pace.md:9 - garden-idea uncompiled idea remaining
|
|
62
|
+
|
|
63
|
+
──────────────────────── Fix prompt for AI ────────────────────────
|
|
64
|
+
Handle the Garden post blocks listed below: compile uncompiled ideas, and fix interactive blocks that have errors.
|
|
65
|
+
|
|
66
|
+
Rules:
|
|
67
|
+
- First run npx garden ui prompt and read the component catalog and rules; if the repo has the garden-ui skill, follow its flow.
|
|
68
|
+
- Change only the listed blocks. Write them back with npx garden ui apply <post> --id <id> (use --intent-hash for ideas without an id). Do not edit the post by hand.
|
|
69
|
+
- Keep the author's meaning. When done, run npx garden ui check <post> until it passes.
|
|
70
|
+
- If you cannot access this repository, reply with only the finished fence for each block.
|
|
71
|
+
|
|
72
|
+
1. content/posts/writing-pace.md, lines 9–11, intent-hash: 8866427a
|
|
73
|
+
- garden-idea: this idea is not compiled yet; compile it into garden interactive or another garden component (or put the sentence back into the prose).
|
|
74
|
+
|
|
75
|
+
Current fence:
|
|
76
|
+
|
|
77
|
+
```garden
|
|
78
|
+
Let the reader drag daily word count and writing days per week to see how many weeks an 8,000-word article takes.
|
|
79
|
+
```
|
|
80
|
+
────────────────────────────────────────────────────────────
|
|
81
|
+
Wrote .garden/ui-fix-prompt.md. To commit anyway, run git commit --no-verify.
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The same prompt is saved to `.garden/ui-fix-prompt.md` and copied to the clipboard when a clipboard command is available. Once the Agent applies the block and the author commits again, the hook exits silently with code `0`.
|
|
85
|
+
|
|
86
|
+
### 4. The Rendered Result
|
|
87
|
+
|
|
88
|
+
The `garden interactive` fence written back in step 2 renders directly on the article page as an interactive calculator:
|
|
89
|
+
|
|
90
|
+
```garden interactive #writing-pace
|
|
91
|
+
caption: Writing a few hundred extra words each day finishes sooner than adding another writing day per week.
|
|
92
|
+
intent: |
|
|
93
|
+
Let the reader drag daily word count and writing days per week to see how many weeks an 8,000-word article takes.
|
|
94
|
+
---
|
|
95
|
+
<State daily={500} days={3} />
|
|
96
|
+
<Let weekly={daily * days} />
|
|
97
|
+
<Let weeks={ceil(8000 / weekly)} />
|
|
98
|
+
|
|
99
|
+
<Slider bind="daily" label="Words per day" min={200} max={2000} step={100} unit="words" />
|
|
100
|
+
<Slider bind="days" label="Days per week" min={1} max={7} step={1} unit="days" />
|
|
101
|
+
|
|
102
|
+
<Stat label="Weeks to finish 8,000 words" value={weeks} unit="weeks" hint="Rounded up to full weeks" />
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
> [!IMPORTANT] Humans and Agents Share the Same Tools
|
|
106
|
+
> The `npx garden ui scan`, `npx garden ui apply`, and `npx garden ui check` commands used by the Agent are the exact commands run by the author's pre-commit hook. When the hook stops a commit, its output is the instruction for the Agent's next step. See [Writing with AI](/garden/en/ai-writing).
|
|
107
|
+
|
|
108
|
+
The remaining sections cover the layout and component rules that work out of the box without an Agent.
|
|
109
|
+
|
|
110
|
+
## Two-line Titles and the `740px` Reading Column
|
|
111
|
+
|
|
112
|
+
Write ` | ` (with spaces on both sides) inside a title to split it across two lines and scale the font size to fit the longer line. Body copy stays inside a `740px` column, while wide figures expand symmetrically into the side margins without overlapping the navigation or table of contents.
|
|
113
|
+
|
|
114
|
+
Transparent-background line drawings automatically get a warm paper backing in dark mode, so you do not need separate dark assets:
|
|
115
|
+
|
|
116
|
+

|
|
117
|
+
|
|
118
|
+
## Wide Tables Expand Symmetrically
|
|
119
|
+
|
|
120
|
+
When a table is wider than the reading column without wrapping, it expands into both margins up to `1340px` on desktop:
|
|
121
|
+
|
|
122
|
+
| Content Type | Source Location | How the Page Handles Long-form and Media Details | Typical Use |
|
|
123
|
+
| :--- | :--- | :--- | :--- |
|
|
124
|
+
| Long-form posts | `content/posts/<slug>.md` and a matching asset folder | Reads local image dimensions at build time to prevent layout shift, splits titles on ` \| `, widens figures and tables, and renders KaTeX math and Mermaid diagrams | System design notes, code walkthroughs, debugging postmortems, and book notes |
|
|
125
|
+
| Short thoughts | `content/thoughts/<slug>.md` | Renders multi-image grids, native `image` / `audio` / `video` players, and external `link` preview cards | Short observations, terminal screenshots, audio clips, and saved links |
|
|
126
|
+
| Ordered columns | `content/columns.yaml` | Chains multiple posts from `content/posts/` in reading order with series progress and previous/next navigation | Multi-part tutorials, topic series, and chaptered notes |
|
|
127
|
+
|
|
128
|
+
## Code Blocks and the Diff Toggle
|
|
129
|
+
|
|
130
|
+
Write a one-sentence `garden` idea right below an updated code block to describe what changed, and the Agent generates the adjacent `diff` fence; you can also write the `diff` fence by hand. When a code block is immediately followed by a `diff` block, the page combines them into a single frame with tabs for the full code and the diff against the previous version, while the copy button always copies the new code.
|
|
131
|
+
|
|
132
|
+
```yaml
|
|
133
|
+
pages:
|
|
134
|
+
garden:
|
|
135
|
+
title: "Write in Markdown | Push to Update the Site"
|
|
136
|
+
subtitle: "Write posts, short notes, and columns in Markdown inside your own Git repository."
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
```diff
|
|
140
|
+
pages:
|
|
141
|
+
garden:
|
|
142
|
+
- title: "Write in Markdown.\nPush to update the site."
|
|
143
|
+
- subtitle: ""
|
|
144
|
+
+ title: "Write in Markdown | Push to Update the Site"
|
|
145
|
+
+ subtitle: "Write posts, short notes, and columns in Markdown inside your own Git repository."
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
> [!NOTE] Note
|
|
149
|
+
> A `diff` fence must sit immediately after a complete code block; a standalone `diff` fence triggers a `diff-orphan` error in `npx garden check`.
|
|
150
|
+
|
|
151
|
+
> [!TIP] Custom Callout Titles
|
|
152
|
+
> Write text on the same line after `> [!TIP]` (or place a bold line immediately below) to replace the default callout title. Supported types include `NOTE`, `TIP`, `IMPORTANT`, `WARNING`, and `CAUTION`.
|
|
153
|
+
|
|
154
|
+
## Structured Components in Markdown
|
|
155
|
+
|
|
156
|
+
Use ```` ```garden <name> ```` fences to insert metrics, side-by-side comparisons, timelines, or charts. Here is a `garden stat` block:
|
|
157
|
+
|
|
158
|
+
```garden stat
|
|
159
|
+
items:
|
|
160
|
+
- label: Reading Column
|
|
161
|
+
value: "740"
|
|
162
|
+
unit: px
|
|
163
|
+
caption: Default width for paragraphs, code blocks, and lists
|
|
164
|
+
- label: Wide Figure Zone
|
|
165
|
+
value: "1040"
|
|
166
|
+
unit: px
|
|
167
|
+
caption: Screenshots display at half their pixel width and expand up to this limit above ~2000px
|
|
168
|
+
- label: Wide Table Zone
|
|
169
|
+
value: "1340"
|
|
170
|
+
unit: px
|
|
171
|
+
caption: Maximum width for wide multi-column tables expanding symmetrically on desktop
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
See [Components Overview](/garden/en/components) in the sidebar for all 9 built-in components and their live examples.
|
|
175
|
+
|
|
176
|
+
<h2 id="garden-admin-title">From Init to Daily Writing</h2>
|
|
177
|
+
|
|
178
|
+
Your site repository stores only configuration, Markdown posts, and static assets, while `hd-garden` provides the renderer and build scripts. Initialize and preview a site locally:
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
npx hd-garden init my-blog
|
|
182
|
+
cd my-blog
|
|
183
|
+
npm install
|
|
184
|
+
make dev
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Run `make update` when a post is ready; it runs the full check and build before committing and pushing. Read the chapters for details:
|
|
188
|
+
|
|
189
|
+
- **[Quick Start](/garden/en/start)**: Directory layout, creating posts with `make new`, previewing drafts, and daily commands.
|
|
190
|
+
- **[Site & Column Config](/garden/en/config)**: Every field in `site.config.yaml`, custom SVG icons, and ordering series in `content/columns.yaml`.
|
|
191
|
+
- **[Deploy to GitHub Pages](/garden/en/deploy)**: A single GitHub Actions workflow that supports both one public repository and a private source repo paired with a public Pages repo.
|
|
192
|
+
- **[Writing Posts & Thoughts](/garden/en/writing)** and **[How the Reading Layout Works](/garden/en/design)**: Front Matter fields, external link cards, three-tier adaptive widths, and color tokens.
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Quick Start
|
|
3
|
+
navTitle: Quick Start
|
|
4
|
+
description: Initialize a site directory with npx hd-garden init, preview locally, create posts, and push updates.
|
|
5
|
+
section: getting-started
|
|
6
|
+
order: 1
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
Garden is a personal blog tool written in Markdown and managed with Git. An initialized site repository holds only your configuration files, Markdown content, and static assets, while the rendering and build pipeline comes from the `hd-garden` package.
|
|
10
|
+
|
|
11
|
+
## 1. Requirements and Initialization
|
|
12
|
+
|
|
13
|
+
Install **Node.js `>= 22.13.0`**, **Git**, and **GNU Make** first. Run the following commands in your terminal to create a site directory:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx hd-garden init my-blog
|
|
17
|
+
cd my-blog
|
|
18
|
+
npm install
|
|
19
|
+
make dev
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`npx hd-garden init` copies the site template into `my-blog` and writes the helper skills under `.agents/skills/`. It does not run `npm install` automatically, and it does not create `.github/workflows/pages.yml` (see [Deploy to GitHub Pages](/garden/en/deploy) for the workflow file).
|
|
23
|
+
|
|
24
|
+
On the first `make dev` run, if `node_modules` is missing, `make prepare` in the `Makefile` runs `npm install` before invoking `npx garden dev` (which syncs content first) and starting the local dev server at `http://localhost:3000`.
|
|
25
|
+
|
|
26
|
+
## 2. Directory Structure
|
|
27
|
+
|
|
28
|
+
An initialized site repository has the following layout:
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
my-blog/
|
|
32
|
+
├── site.config.yaml # Site name, author info, and titles/subtitles for top-level pages
|
|
33
|
+
├── content/
|
|
34
|
+
│ ├── columns.yaml # Column definitions and reading order
|
|
35
|
+
│ ├── covers/ # Cover images for posts and columns
|
|
36
|
+
│ ├── posts/ # Long-form posts (.md) and matching sidecar folders
|
|
37
|
+
│ └── thoughts/ # Short notes (.md) and media attachments
|
|
38
|
+
├── public/ # Static files served at their root paths (such as icons)
|
|
39
|
+
├── Makefile # Daily commands for previewing, creating posts, checking, and pushing
|
|
40
|
+
└── package.json # Depends only on hd-garden
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## 3. Creating Posts and Thoughts
|
|
44
|
+
|
|
45
|
+
### Creating a Post (`make new`)
|
|
46
|
+
|
|
47
|
+
Run `make new` to create a draft in `content/posts/` pre-filled with the date, title, and a commented component cheat sheet:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
make new SLUG=tcp-bbr TITLE="TCP BBR Notes"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
To place the post inside a subdirectory (such as `content/posts/network/tcp-bbr.md`), pass `DIR`:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
make new DIR=network SLUG=tcp-bbr TITLE="TCP BBR Notes"
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
> [!NOTE] Note
|
|
60
|
+
> `SLUG` may only contain lowercase letters, digits, and hyphens (`a-z`, `0-9`, `-`), never `/`; pass subdirectories via `DIR`. `make new` reads only `SLUG`, `TITLE`, and `DIR`.
|
|
61
|
+
|
|
62
|
+
Newly created files default to `draft: true`. Set `CONTENT_INCLUDE_DRAFTS=1` to preview drafts or future-dated (`publishAt`) posts locally:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
CONTENT_INCLUDE_DRAFTS=1 make dev
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Creating a Thought
|
|
69
|
+
|
|
70
|
+
Thoughts live in `content/thoughts/`. Create a `.md` file directly in `content/thoughts/` (for example `content/thoughts/2026-10-19-note.md`) with `title` and `date` in its Front Matter, or publish from the browser after configuring `/admin`. See [Writing Posts & Thoughts](/garden/en/writing) for the supported fields.
|
|
71
|
+
|
|
72
|
+
## 4. Daily Commands and Upgrading
|
|
73
|
+
|
|
74
|
+
The site `Makefile` and `garden` CLI provide these commands:
|
|
75
|
+
|
|
76
|
+
| Command | What It Runs |
|
|
77
|
+
| :--- | :--- |
|
|
78
|
+
| `make dev` | Runs `make prepare` to check dependencies, then runs `npx garden dev` (which syncs content and starts the dev server) |
|
|
79
|
+
| `CONTENT_INCLUDE_DRAFTS=1 make dev` | Previews `draft: true` drafts and future `publishAt` posts alongside published content |
|
|
80
|
+
| `make new SLUG=xxx TITLE="Title" DIR=subdir` | Calls `npx garden new` to create a draft post under `content/posts/` (`DIR` is optional) |
|
|
81
|
+
| `make check` | Runs `npx garden sync && npx garden build` in the site repo to verify content sync and the production build |
|
|
82
|
+
| `npx garden check content/posts/xxx.md` | Checks a single Markdown post's title, component fields, code diffs, and local asset paths |
|
|
83
|
+
| `make update MESSAGE="Commit message"` | Runs `make check`, then `git add -A`, `git commit -m "$(MESSAGE)"`, and `git push origin HEAD` |
|
|
84
|
+
|
|
85
|
+
### Upgrading `hd-garden`
|
|
86
|
+
|
|
87
|
+
To upgrade the engine, edit `package.json` in your site root, update the `"hd-garden"` version string, and run `npm install`:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
npm install
|
|
91
|
+
make check
|
|
92
|
+
```
|