@the-smithy/create-hearth-site 0.13.0 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -12
- package/bin/create-hearth-site.js +18 -11
- package/package.json +2 -2
- package/template/.env.example +3 -3
- package/template/AGENTS.md +4 -0
- package/template/CLAUDE.md +17 -0
- package/template/README.md +112 -53
- package/template/_gitignore +3 -0
- package/template/package.json +3 -1
- package/template/scripts/hearth.mjs +100 -0
package/README.md
CHANGED
|
@@ -7,26 +7,39 @@ site with every content block rendered, SEO, forms, live preview and the publish
|
|
|
7
7
|
key, so it shows nothing without a Hearth account. Developer access is by invitation: request it at
|
|
8
8
|
[hearth.thesmithy.io/developers](https://hearth.thesmithy.io/developers).
|
|
9
9
|
|
|
10
|
-
##
|
|
10
|
+
## Before you start
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
- Node 20.9 or later, and pnpm (run `corepack enable` once if `pnpm` isn't found).
|
|
13
|
+
- The site's **address** in Hearth: open the site in Hearth and copy the part after `/admin/sites/` in the
|
|
14
|
+
address bar. For `hearth.thesmithy.io/admin/sites/acme/pages` it's `acme`.
|
|
15
|
+
|
|
16
|
+
## Create the site
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
From the folder where you keep your projects, run:
|
|
18
19
|
|
|
19
20
|
```sh
|
|
20
|
-
pnpm
|
|
21
|
+
pnpm create @the-smithy/hearth-site@latest acme-website --site acme
|
|
21
22
|
```
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
Replace `acme-website` with the folder to create and `acme` with your site's address. Each part:
|
|
25
|
+
|
|
26
|
+
| Part | What it is |
|
|
24
27
|
| --- | --- |
|
|
25
|
-
|
|
|
26
|
-
|
|
|
27
|
-
| `--
|
|
28
|
+
| `pnpm create @the-smithy/hearth-site@latest` | Runs this package. `pnpm create @the-smithy/hearth-site` is pnpm's short form of `@the-smithy/create-hearth-site`. Keep `@latest`: without it, pnpm skips releases less than a day old and quietly uses an older one |
|
|
29
|
+
| `acme-website` | The folder to create, which also names the project. It must not exist yet, or must be empty |
|
|
30
|
+
| `--site acme` | Your site's address in Hearth (lowercase letters, numbers and dashes). Written to `.env.local` as `HEARTH_SITE` |
|
|
31
|
+
|
|
32
|
+
Leave out the folder or `--site` and you're asked for them. Add `--skip-install` to skip `pnpm install`.
|
|
33
|
+
|
|
34
|
+
The command copies the starter into the new folder, writes `.env.local` with the site's address, runs
|
|
35
|
+
`git init` and installs the packages. Then:
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
cd acme-website
|
|
39
|
+
```
|
|
28
40
|
|
|
29
|
-
|
|
41
|
+
Add an API key to `.env.local` (Hearth → the site → Settings → Developers → API keys), and run `pnpm dev`. The
|
|
42
|
+
new site's README walks through the first run, previews, deploying and troubleshooting.
|
|
30
43
|
|
|
31
44
|
## Documentation
|
|
32
45
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/*
|
|
3
|
-
* pnpm create @the-smithy/hearth-site <
|
|
3
|
+
* pnpm create @the-smithy/hearth-site@latest <folder> --site <address> [--skip-install]
|
|
4
4
|
*
|
|
5
5
|
* Copies the template next to this file into a new directory, names it, points it at
|
|
6
6
|
* the matching SDK version, and installs. No network calls besides `pnpm install`.
|
|
@@ -23,11 +23,15 @@ const SKIP = new Set(["node_modules", ".next", ".netlify", ".env.local", "next-e
|
|
|
23
23
|
/** Dotfiles npm won't publish (or that would confuse the monorepo) are stored with a leading underscore. */
|
|
24
24
|
const RENAMES = { _gitignore: ".gitignore", "_pnpm-workspace.yaml": "pnpm-workspace.yaml" };
|
|
25
25
|
|
|
26
|
-
const usage = `Usage: pnpm create @the-smithy/hearth-site <
|
|
26
|
+
const usage = `Usage: pnpm create @the-smithy/hearth-site@latest <folder> --site <address> [--skip-install]
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
Example (replace both names with your own; no < > around them):
|
|
29
|
+
pnpm create @the-smithy/hearth-site@latest acme-website --site acme
|
|
30
|
+
|
|
31
|
+
<folder> The folder to create, which also names the project (must be new or empty)
|
|
32
|
+
--site <address> The site's address in Hearth: the part after /admin/sites/ in Hearth's
|
|
33
|
+
address bar when you open the site, e.g. acme
|
|
34
|
+
--skip-install Don't run pnpm install`;
|
|
31
35
|
|
|
32
36
|
export function packageName(dir) {
|
|
33
37
|
return (
|
|
@@ -52,8 +56,8 @@ async function main() {
|
|
|
52
56
|
let site = values.site;
|
|
53
57
|
if ((!dir || !site) && process.stdin.isTTY) {
|
|
54
58
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
55
|
-
dir ||= (await rl.question("Folder
|
|
56
|
-
site ||= (await rl.question("Site address in Hearth (e.g. acme): ")).trim();
|
|
59
|
+
dir ||= (await rl.question("Folder to create (e.g. acme-website): ")).trim();
|
|
60
|
+
site ||= (await rl.question("Site address in Hearth, after /admin/sites/ in its address bar (e.g. acme): ")).trim();
|
|
57
61
|
rl.close();
|
|
58
62
|
}
|
|
59
63
|
if (!dir) {
|
|
@@ -61,7 +65,7 @@ async function main() {
|
|
|
61
65
|
process.exit(1);
|
|
62
66
|
}
|
|
63
67
|
if (site && !validSlug(site)) {
|
|
64
|
-
console.error(`"${site}" isn't a Hearth site address: use lowercase letters, numbers and dashes.`);
|
|
68
|
+
console.error(`"${site}" isn't a Hearth site address: use lowercase letters, numbers and dashes, as in Hearth's address bar after /admin/sites/ (e.g. acme).`);
|
|
65
69
|
process.exit(1);
|
|
66
70
|
}
|
|
67
71
|
|
|
@@ -107,10 +111,13 @@ Created ${pkg.name} in ${rel}.
|
|
|
107
111
|
|
|
108
112
|
Next:
|
|
109
113
|
cd ${rel}
|
|
110
|
-
${installed ? "" : " pnpm install\n"} Add
|
|
111
|
-
|
|
114
|
+
${installed ? "" : " pnpm install\n"} Add an API key to .env.local as HEARTH_API_KEY (Hearth → the site → Settings → Developers → API keys)${
|
|
115
|
+
site ? "" : "\n Set HEARTH_SITE in .env.local to the site's address in Hearth (after /admin/sites/ in its address bar)"
|
|
116
|
+
}
|
|
117
|
+
pnpm dev, then open http://localhost:3000
|
|
112
118
|
|
|
113
|
-
|
|
119
|
+
Only published pages show. If every page says "Page not found", publish the homepage (address "home") in Hearth.
|
|
120
|
+
README.md covers the first run, previews, deploying and troubleshooting.`);
|
|
114
121
|
}
|
|
115
122
|
|
|
116
123
|
// Run when executed (pnpm create runs it through a symlink), not when imported by tests.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@the-smithy/create-hearth-site",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.1",
|
|
4
4
|
"description": "Start a new client website powered by Hearth: pnpm create @the-smithy/hearth-site",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
],
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"vitest": "^5.0.3",
|
|
32
|
-
"@the-smithy/hearth-schema": "^0.13.
|
|
32
|
+
"@the-smithy/hearth-schema": "^0.13.1"
|
|
33
33
|
},
|
|
34
34
|
"scripts": {
|
|
35
35
|
"test": "node --test \"test/**/*.test.js\" && vitest run test/blocks.test.ts"
|
package/template/.env.example
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
# The site's address in Hearth
|
|
1
|
+
# The site's address in Hearth: the part after /admin/sites/ in Hearth's address bar, e.g. grand-eastonian.
|
|
2
2
|
HEARTH_SITE=
|
|
3
|
-
# A Delivery API key
|
|
3
|
+
# A Delivery API key: Hearth → the site → Settings → Developers → API keys. Secret: server-side only.
|
|
4
4
|
HEARTH_API_KEY=
|
|
5
|
-
# Signing secret
|
|
5
|
+
# Only once deployed: the Signing secret shown after saving the Revalidation webhook (Settings → Developers).
|
|
6
6
|
HEARTH_WEBHOOK_SECRET=
|
|
7
7
|
# Optional: point at a different Hearth (e.g. a staging deploy).
|
|
8
8
|
# HEARTH_BASE_URL=https://hearth.thesmithy.io
|
package/template/CLAUDE.md
CHANGED
|
@@ -3,6 +3,22 @@
|
|
|
3
3
|
This is a client website whose content comes from Hearth, The Smithy's headless CMS. Read
|
|
4
4
|
`node_modules/@the-smithy/hearth/llms.md` for the SDK reference before changing data fetching.
|
|
5
5
|
|
|
6
|
+
## First steps in a new checkout
|
|
7
|
+
|
|
8
|
+
Do these before writing code, and tell the developer what you found:
|
|
9
|
+
|
|
10
|
+
1. **Check the setup:** `pnpm hearth:check`. It reads `.env.local`, connects to Hearth, lists the published
|
|
11
|
+
pages and the block types they use, and flags types with no component. It never prints the API key; you
|
|
12
|
+
shouldn't either, so don't open `.env.local` to read the key or echo it. If it fails, its message says what to
|
|
13
|
+
fix (missing key, wrong `HEARTH_SITE`, revoked key). Ask the developer to fix `.env.local`; don't guess values.
|
|
14
|
+
2. **Get the full reference:** `pnpm hearth:docs` saves Hearth's API guide (every block and its fields, events,
|
|
15
|
+
forms, previews, SEO) to `hearth-api.md`. Read the parts you need from it, and from
|
|
16
|
+
`node_modules/@the-smithy/hearth/llms.md` for the SDK.
|
|
17
|
+
3. **"Nothing is published"** means every page is a 404 until editors publish in Hearth. That's not a code
|
|
18
|
+
problem: say so, and don't add placeholder content to work around it.
|
|
19
|
+
4. **Then build the design** in `src/app/globals.css` (tokens) and `src/components/blocks/` (one component per
|
|
20
|
+
block type), checking with `pnpm dev` against the published pages.
|
|
21
|
+
|
|
6
22
|
## Rules
|
|
7
23
|
|
|
8
24
|
- **Content comes from Hearth.** Don't hard-code page text, menus, contact details or SEO tags. They're edited
|
|
@@ -25,6 +41,7 @@ This is a client website whose content comes from Hearth, The Smithy's headless
|
|
|
25
41
|
## Checks
|
|
26
42
|
|
|
27
43
|
`pnpm typecheck` and `pnpm build`. The build needs the `.env.local` keys, because it reads content from Hearth.
|
|
44
|
+
`pnpm hearth:check` after changing blocks shows any block type the published content uses without a component.
|
|
28
45
|
|
|
29
46
|
<!-- BEGIN:nextjs-agent-rules -->
|
|
30
47
|
|
package/template/README.md
CHANGED
|
@@ -1,68 +1,127 @@
|
|
|
1
1
|
# Website powered by Hearth
|
|
2
2
|
|
|
3
|
-
A Next.js site whose content (pages, menu, footer, events and forms)
|
|
4
|
-
[Hearth](https://hearth.thesmithy.io)
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
3
|
+
A Next.js site whose content (pages, menu, footer, events and forms) comes from
|
|
4
|
+
[Hearth](https://hearth.thesmithy.io), The Smithy's content management system. **The code decides how things
|
|
5
|
+
look; Hearth decides what's on the page.** Editors change content in Hearth, and you never hard-code it here.
|
|
6
|
+
|
|
7
|
+
## Before you start
|
|
8
|
+
|
|
9
|
+
- **Node 20.9 or later**, and **pnpm**: run `corepack enable` once if `pnpm` isn't found.
|
|
10
|
+
- **A Hearth login with access to the site**, as an Admin or Developer. The Smithy or the site's admin adds you.
|
|
11
|
+
- **The site's address in Hearth.** It's the part after `/admin/sites/` in Hearth's address bar when you open
|
|
12
|
+
the site, e.g. `grand-eastonian`. The setup command already put it in `.env.local` as `HEARTH_SITE`.
|
|
13
|
+
|
|
14
|
+
## First run
|
|
15
|
+
|
|
16
|
+
1. **Create an API key.** In Hearth, open the site, then **Settings → Developers → API keys**, and create a key
|
|
17
|
+
(name it after where it's used, e.g. "Local"). Copy it straight away: it's only shown once.
|
|
18
|
+
2. **Add it to `.env.local`** in this folder:
|
|
19
|
+
|
|
20
|
+
```ini
|
|
21
|
+
HEARTH_SITE=grand-eastonian # already filled in
|
|
22
|
+
HEARTH_API_KEY=hk_… # the key you just created
|
|
23
|
+
HEARTH_WEBHOOK_SECRET= # leave empty until you deploy
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`.env.local` is never committed. Keep the key out of the code and out of chat.
|
|
27
|
+
3. **Check the connection:** `pnpm hearth:check`. It confirms the key works and lists what's published.
|
|
28
|
+
**Start the site:** `pnpm dev`, then open http://localhost:3000.
|
|
29
|
+
4. **Publish something in Hearth.** The site only shows **published** content, so a site whose pages are all
|
|
30
|
+
drafts shows "Page not found" everywhere. That's expected, not a bug. Ask the site's editors to publish the
|
|
31
|
+
homepage (its address is `home`), or publish it yourself if you have edit access, then reload.
|
|
32
|
+
5. **Optional: preview drafts on your machine.** In **Settings → Developers → Webhook and preview**, set
|
|
33
|
+
**Preview address** to `http://localhost:3000/api/preview`. "Preview on the website" in Hearth's editor then
|
|
34
|
+
opens your local site with drafts showing, refreshing as you edit. This applies to everyone previewing that
|
|
35
|
+
site, so clear it when you're done.
|
|
36
|
+
|
|
37
|
+
## Working with an AI coding assistant
|
|
38
|
+
|
|
39
|
+
The project comes with instructions for coding agents (`CLAUDE.md`, which Claude Code reads on its own, and
|
|
40
|
+
`AGENTS.md` for other assistants). Once your API key is in `.env.local`:
|
|
41
|
+
|
|
42
|
+
1. **Check the connection yourself, or have the agent do it:** `pnpm hearth:check` confirms the key works and
|
|
43
|
+
lists the published pages and the block types they use. It never prints the key.
|
|
44
|
+
2. **Give the agent the full reference:** `pnpm hearth:docs` saves Hearth's API guide to `hearth-api.md` (not
|
|
45
|
+
committed). The agent's instructions already point it there.
|
|
46
|
+
3. **Then tell it what to build.** Prompts that work well:
|
|
47
|
+
|
|
48
|
+
- *"Run the first steps in CLAUDE.md and tell me what's published and which blocks the site uses."*
|
|
49
|
+
- *"Restyle the site to match the brand: colors `#1F3A5F` and `#C8A15A`, font Lora for headings. Start with
|
|
50
|
+
the tokens in `globals.css`, then the hero, card grid and media-text blocks."*
|
|
51
|
+
- *"Match the current website at https://example.org: compare each published page with the same page there
|
|
52
|
+
and restyle the blocks until they look alike. Content stays in Hearth."*
|
|
53
|
+
- *"The gallery block looks plain. Make it a masonry grid with a lightbox, keeping it accessible."*
|
|
54
|
+
- *"Run `pnpm hearth:check` and add a component for every block type it flags."*
|
|
55
|
+
|
|
56
|
+
Keep the API key out of chat: the agent never needs to see it, only `.env.local` does. If it asks for content
|
|
57
|
+
(page text, menus, contact details), the answer is to add or publish it in Hearth, not to write it into the code.
|
|
58
|
+
|
|
59
|
+
## While you work
|
|
60
|
+
|
|
61
|
+
- **Seeing published changes locally.** Content is cached, and Hearth's publish webhook can't reach your
|
|
62
|
+
computer. After publishing, do a hard refresh (Ctrl+Shift+R, or Cmd+Shift+R on a Mac).
|
|
63
|
+
- **Making it the client's own.** Start with the brand tokens in `src/app/globals.css` (colors, font, content
|
|
64
|
+
width), then restyle the block components in `src/components/blocks/`.
|
|
65
|
+
- Every block uses semantic tones (`default`, `muted`, `inverse`, `accent`). Map them to the brand rather than
|
|
66
|
+
hard-coding colors in a block.
|
|
67
|
+
- Headings can mark words with `*asterisks*` to highlight them (see `Emphasis` in `src/components/ui.tsx`).
|
|
68
|
+
- To leave a block type out, delete its line in `src/components/blocks/index.tsx` and ask for it to be turned
|
|
69
|
+
off for the site in Hearth.
|
|
70
|
+
- **SEO is already handled.** Page titles, descriptions, canonical URLs, share images and structured data all
|
|
71
|
+
come from Hearth's SEO settings. There's nothing to wire up per page.
|
|
72
|
+
|
|
73
|
+
### Where things live
|
|
29
74
|
|
|
30
75
|
| Where | What |
|
|
31
76
|
| --- | --- |
|
|
32
|
-
| `src/lib/hearth.ts` | The Hearth client. Server-side only, because the key is secret |
|
|
33
|
-
| `src/app/[[...slug]]/page.tsx` | Every page in Hearth at its
|
|
34
|
-
| `src/
|
|
35
|
-
| `src/components/blocks/` | One component per Hearth block type. This is where the design lives |
|
|
77
|
+
| `src/lib/hearth.ts` | The Hearth client. Server-side only, because the API key is secret |
|
|
78
|
+
| `src/app/[[...slug]]/page.tsx` | Every page in Hearth at its address (`home` is `/`) |
|
|
79
|
+
| `src/components/blocks/` | One component per Hearth block type. **This is where the design lives** |
|
|
36
80
|
| `src/app/globals.css` | Brand tokens: colors, font, content width |
|
|
37
|
-
| `src/app/
|
|
38
|
-
| `src/
|
|
39
|
-
| `src/
|
|
40
|
-
| `src/app/api/
|
|
41
|
-
|
|
|
42
|
-
| `src/app/
|
|
81
|
+
| `src/app/events/` | Event list and detail pages (sites with Events) |
|
|
82
|
+
| `src/app/fundraisers/`, `src/app/donate/` | Fundraisers and donations (sites with Giving) |
|
|
83
|
+
| `src/components/RegistrationForm.tsx`, `src/app/registered/` | On-site event registration (sites with Tickets) |
|
|
84
|
+
| `src/app/api/revalidate/route.ts` | Hearth calls this when something is published, so changes go live in seconds |
|
|
85
|
+
| `src/app/api/preview/route.ts` | Opens drafts from Hearth's "Preview on the website"; `/api/preview/exit` ends it |
|
|
86
|
+
| `<HearthPreview />` in `src/app/layout.tsx` | Refreshes the page while an editor previews and edits in Hearth |
|
|
87
|
+
| `src/app/sitemap.ts`, `robots.ts` | Generated from Hearth. Search engines stay out until the site's web address is set |
|
|
43
88
|
|
|
44
|
-
|
|
45
|
-
settings. Nothing to wire up per page.
|
|
89
|
+
## Deploying to Netlify
|
|
46
90
|
|
|
47
|
-
|
|
91
|
+
Do these in order: the webhook's signing secret only exists once the webhook URL is saved.
|
|
48
92
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
93
|
+
1. **Create the Netlify site** from this repo. `netlify.toml` already sets the build command.
|
|
94
|
+
2. **Add environment variables** in Netlify: `HEARTH_SITE`, and `HEARTH_API_KEY` as a secret. Use a separate key
|
|
95
|
+
named "Netlify", so you can revoke one without breaking the other. Set them **before the first deploy**: the
|
|
96
|
+
build reads content from Hearth and fails without them. Nothing else is needed; the packages are public on npm.
|
|
97
|
+
3. **Deploy**, and note the site's domain.
|
|
98
|
+
4. **Set the web address in Hearth.** The site's admin sets it under **Settings → Your site → Web address**.
|
|
99
|
+
Until then, search engines are kept out.
|
|
100
|
+
5. **Connect the publish webhook.** In **Settings → Developers → Webhook and preview**, set **Revalidation
|
|
101
|
+
webhook** to `https://<domain>/api/revalidate` and save. A **Signing secret** appears: add it in Netlify as
|
|
102
|
+
`HEARTH_WEBHOOK_SECRET`, then **redeploy**, since Netlify only applies new variables on the next deploy.
|
|
103
|
+
6. **Clear the Preview address** if you set it to localhost. Empty means the live site's `/api/preview`.
|
|
104
|
+
7. **Check it:** publish a small change in Hearth. It should show on the live site within a few seconds.
|
|
55
105
|
|
|
56
|
-
|
|
106
|
+
Without the webhook, published changes still appear, but can take up to an hour.
|
|
107
|
+
|
|
108
|
+
## When something's wrong
|
|
109
|
+
|
|
110
|
+
| What you see | Why, and what to do |
|
|
111
|
+
| --- | --- |
|
|
112
|
+
| "Page not found" on every page | Nothing is published yet, or `HEARTH_SITE` is wrong. Publish the homepage in Hearth; check the address against Hearth's address bar |
|
|
113
|
+
| Not sure what's wrong | Run `pnpm hearth:check`: it checks `.env.local` and the connection and says what to fix |
|
|
114
|
+
| ``Hearth: `apiKey` is required`` (dev or build) | `HEARTH_API_KEY` is missing from `.env.local`, or from Netlify's variables |
|
|
115
|
+
| An `unauthorized` (401) error from Hearth | The key is mistyped, revoked, or belongs to another site. Create a new one for this site |
|
|
116
|
+
| Published changes don't show locally | Hard refresh (Ctrl+Shift+R). The webhook can't reach your computer |
|
|
117
|
+
| Published changes don't show live | The webhook isn't set, or `HEARTH_WEBHOOK_SECRET` doesn't match Hearth's signing secret (redeploy after changing it) |
|
|
118
|
+
| "Preview on the website" opens the wrong place | Check **Preview address** in Settings → Developers |
|
|
57
119
|
|
|
58
|
-
|
|
59
|
-
2. Add the environment variables `HEARTH_SITE`, `HEARTH_API_KEY` and `HEARTH_WEBHOOK_SECRET`. The packages
|
|
60
|
-
come from npm, so the build needs no other credentials.
|
|
61
|
-
3. In Hearth, set the site's domain, then set the webhook URL to `https://<domain>/api/revalidate`.
|
|
120
|
+
## Documentation
|
|
62
121
|
|
|
63
|
-
|
|
122
|
+
The full reference (API, SDK, every block and its fields, previews, forms) is in Hearth under **API docs**,
|
|
123
|
+
with a Markdown version to give an AI coding assistant. The SDK also ships `node_modules/@the-smithy/hearth/llms.md`.
|
|
64
124
|
|
|
65
125
|
## Updating the SDK
|
|
66
126
|
|
|
67
|
-
`pnpm up @the-smithy/hearth`. New block types
|
|
68
|
-
site needs them.
|
|
127
|
+
`pnpm up @the-smithy/hearth`. New block types arrive as typed additions: add a component when the site needs one.
|
package/template/_gitignore
CHANGED
package/template/package.json
CHANGED
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
"dev": "next dev",
|
|
8
8
|
"build": "next build",
|
|
9
9
|
"start": "next start",
|
|
10
|
-
"typecheck": "tsc --noEmit"
|
|
10
|
+
"typecheck": "tsc --noEmit",
|
|
11
|
+
"hearth:check": "node scripts/hearth.mjs check",
|
|
12
|
+
"hearth:docs": "node scripts/hearth.mjs docs"
|
|
11
13
|
},
|
|
12
14
|
"dependencies": {
|
|
13
15
|
"@the-smithy/ember": "^0.3.1",
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// Setup helpers for this site, for you or your coding agent. Both read .env.local and never print the API key.
|
|
2
|
+
// pnpm hearth:check checks .env.local and the connection to Hearth, then lists what's published
|
|
3
|
+
// pnpm hearth:docs saves Hearth's full API guide to hearth-api.md (not committed) for an AI assistant to read
|
|
4
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { createHearthClient, DEFAULT_BASE_URL, isHearthError } from "@the-smithy/hearth";
|
|
6
|
+
|
|
7
|
+
const command = process.argv[2];
|
|
8
|
+
/** Stops with a message. Thrown rather than process.exit(), which can crash Node on Windows mid-request. */
|
|
9
|
+
class Stop extends Error {}
|
|
10
|
+
const fail = (message) => {
|
|
11
|
+
throw new Stop(message);
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
function readEnv() {
|
|
15
|
+
if (!existsSync(".env.local")) fail("There's no .env.local. Copy .env.example to .env.local and fill it in (see README.md → First run).");
|
|
16
|
+
const env = Object.fromEntries(
|
|
17
|
+
readFileSync(".env.local", "utf8")
|
|
18
|
+
.split(/\r?\n/)
|
|
19
|
+
.map((line) => line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.*?)\s*$/))
|
|
20
|
+
.filter(Boolean)
|
|
21
|
+
.map(([, key, value]) => [key, value.replace(/^["']|["']$/g, "").replace(/\s+#.*$/, "")]),
|
|
22
|
+
);
|
|
23
|
+
if (!env.HEARTH_SITE) fail("HEARTH_SITE is empty in .env.local. It's the site's address: the part after /admin/sites/ in Hearth's address bar.");
|
|
24
|
+
if (!env.HEARTH_API_KEY) fail("HEARTH_API_KEY is empty in .env.local. Create a key in Hearth → the site → Settings → Developers → API keys.");
|
|
25
|
+
if (!env.HEARTH_API_KEY.startsWith("hk_")) fail("HEARTH_API_KEY doesn't look like a Hearth key (they start with hk_). Copy it again from Settings → Developers.");
|
|
26
|
+
return { site: env.HEARTH_SITE, apiKey: env.HEARTH_API_KEY, baseUrl: (env.HEARTH_BASE_URL || DEFAULT_BASE_URL).replace(/\/+$/, "") };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Block types the design has a component for (the keys of `blocks` in src/components/blocks/index.tsx). */
|
|
30
|
+
function designedBlocks() {
|
|
31
|
+
const file = "src/components/blocks/index.tsx";
|
|
32
|
+
if (!existsSync(file)) return null;
|
|
33
|
+
const map = readFileSync(file, "utf8").match(/export const blocks[^{]*\{([\s\S]*?)\n\};/);
|
|
34
|
+
return map ? new Set([...map[1].matchAll(/^\s*([a-zA-Z]+):/gm)].map((m) => m[1])) : null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function check() {
|
|
38
|
+
const env = readEnv();
|
|
39
|
+
const hearth = createHearthClient(env);
|
|
40
|
+
let site;
|
|
41
|
+
try {
|
|
42
|
+
site = await hearth.site.get();
|
|
43
|
+
} catch (err) {
|
|
44
|
+
if (isHearthError(err) && err.code === "unauthorized") {
|
|
45
|
+
fail(`Hearth didn't accept the API key for "${env.site}". Check that HEARTH_SITE matches the site the key was made in, and that the key wasn't revoked.`);
|
|
46
|
+
}
|
|
47
|
+
if (isHearthError(err) && err.code === "not_found") fail(`Hearth has no site "${env.site}". Check HEARTH_SITE against Hearth's address bar.`);
|
|
48
|
+
fail(`Couldn't reach Hearth at ${env.baseUrl}: ${err instanceof Error ? err.message : err}`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
console.log(`✓ Connected to "${site.name}" (${site.slug})${site.domain ? ` · ${site.domain}` : " · no web address set yet"}`);
|
|
52
|
+
console.log(` Features: ${site.features.join(", ") || "none"}`);
|
|
53
|
+
|
|
54
|
+
const pages = await hearth.pages.list();
|
|
55
|
+
if (!pages.length) {
|
|
56
|
+
console.log(`\n! Nothing is published yet, so every page shows "Page not found". That's expected for a new site:`);
|
|
57
|
+
console.log(` publish the homepage (address "home") in Hearth, then run this again.`);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
console.log(`\n✓ ${pages.length} published page${pages.length === 1 ? "" : "s"}:`);
|
|
61
|
+
for (const p of pages.slice(0, 40)) console.log(` ${p.path.padEnd(36)} ${p.title}`);
|
|
62
|
+
if (pages.length > 40) console.log(` … and ${pages.length - 40} more`);
|
|
63
|
+
if (!pages.some((p) => p.slug === "home")) console.log(`\n! No published homepage (address "home"), so / shows "Page not found".`);
|
|
64
|
+
|
|
65
|
+
// Which block types the published content uses, and whether the design has a component for each.
|
|
66
|
+
const used = new Map();
|
|
67
|
+
for (const p of pages.slice(0, 60)) {
|
|
68
|
+
const page = await hearth.pages.get(p.slug);
|
|
69
|
+
for (const b of page?.blocks ?? []) used.set(b.type, (used.get(b.type) ?? 0) + 1);
|
|
70
|
+
}
|
|
71
|
+
const designed = designedBlocks();
|
|
72
|
+
console.log(`\nBlock types in published pages (how often):`);
|
|
73
|
+
for (const [type, count] of [...used].sort((a, b) => b[1] - a[1])) {
|
|
74
|
+
const missing = designed && !designed.has(type);
|
|
75
|
+
console.log(` ${type.padEnd(22)} ${String(count).padStart(3)}${missing ? " ✗ no component in src/components/blocks/index.tsx" : ""}`);
|
|
76
|
+
}
|
|
77
|
+
if (site.features.includes("events")) {
|
|
78
|
+
const events = await hearth.events.list({ when: "upcoming" });
|
|
79
|
+
console.log(`\n✓ ${events.length} upcoming event${events.length === 1 ? "" : "s"}`);
|
|
80
|
+
}
|
|
81
|
+
console.log(`\nReady. Run pnpm dev and open http://localhost:3000.`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function docs() {
|
|
85
|
+
const env = readEnv();
|
|
86
|
+
const res = await fetch(`${env.baseUrl}/admin/docs/api.md`, { headers: { Authorization: `Bearer ${env.apiKey}` } });
|
|
87
|
+
if (!res.ok) fail(`Hearth didn't return the API guide (${res.status}). Check the API key with pnpm hearth:check.`);
|
|
88
|
+
writeFileSync("hearth-api.md", await res.text());
|
|
89
|
+
console.log("✓ Saved Hearth's full API guide to hearth-api.md (not committed). Point your coding assistant at it.");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
if (command === "check") await check();
|
|
94
|
+
else if (command === "docs") await docs();
|
|
95
|
+
else fail("Usage: node scripts/hearth.mjs check | docs");
|
|
96
|
+
} catch (err) {
|
|
97
|
+
if (!(err instanceof Stop)) throw err;
|
|
98
|
+
console.error(`\n✗ ${err.message}\n`);
|
|
99
|
+
process.exitCode = 1;
|
|
100
|
+
}
|