create-headroom-site 0.1.4 → 0.1.6
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/package.json +1 -1
- package/templates/astro/CLAUDE.md +74 -0
- package/templates/astro/package.json +2 -1
- package/templates/nextjs/CLAUDE.md +85 -2
- package/templates/nextjs/package.json +2 -1
- package/templates/nextjs/src/app/layout.tsx +23 -13
- package/templates/nextjs/src/app/page.tsx +6 -6
- package/templates/nextjs/src/components/Footer.tsx +4 -6
- package/templates/nextjs/src/components/Header.tsx +5 -6
- package/templates/nextjs/src/lib/settings.ts +8 -0
package/package.json
CHANGED
|
@@ -51,6 +51,80 @@ npm run preview # Preview built site
|
|
|
51
51
|
npm run generate:schemas # Generate Zod schemas from Headroom collections
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
+
## Headroom CLI
|
|
55
|
+
|
|
56
|
+
The `@headroom-cms/cli` package is included as a dev dependency and provides a full admin CLI for managing your Headroom CMS. Run it via `npx headroom` or add scripts to `package.json`.
|
|
57
|
+
|
|
58
|
+
### Setup
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
# Login to your Headroom instance
|
|
62
|
+
npx headroom login https://your-headroom-url.cloudfront.net your-site.com \
|
|
63
|
+
--email admin@example.com --password-stdin
|
|
64
|
+
|
|
65
|
+
# Verify login
|
|
66
|
+
npx headroom whoami
|
|
67
|
+
|
|
68
|
+
# Set the active site (if managing multiple sites)
|
|
69
|
+
npx headroom site your-site.com
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Key Commands
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
# Collections
|
|
76
|
+
npx headroom collections list --table # List all collections
|
|
77
|
+
npx headroom collections describe posts # Show field schema with constraints
|
|
78
|
+
npx headroom collections example posts # Generate JSON template for content creation
|
|
79
|
+
|
|
80
|
+
# Content
|
|
81
|
+
npx headroom content list --table # List all content
|
|
82
|
+
npx headroom content list --collection posts --table # List by collection
|
|
83
|
+
npx headroom content list --related-to "author:01ABC" --table # Filter by relationship
|
|
84
|
+
npx headroom content get <content-id> # Get single item with body
|
|
85
|
+
npx headroom content references <content-id> --table # What references this content?
|
|
86
|
+
npx headroom content create --collection posts --data '{"title":"New Post"}'
|
|
87
|
+
npx headroom content publish <content-id>
|
|
88
|
+
npx headroom content delete <content-id> --force
|
|
89
|
+
|
|
90
|
+
# Media
|
|
91
|
+
npx headroom media list --table # List media files
|
|
92
|
+
npx headroom media upload ./image.jpg # Upload a local file
|
|
93
|
+
npx headroom media upload-url https://... # Upload from URL
|
|
94
|
+
|
|
95
|
+
# API Keys
|
|
96
|
+
npx headroom api-keys list --table
|
|
97
|
+
npx headroom api-keys create --data '{"label":"My Key"}'
|
|
98
|
+
|
|
99
|
+
# Tags
|
|
100
|
+
npx headroom tags list --table
|
|
101
|
+
|
|
102
|
+
# Audit log
|
|
103
|
+
npx headroom audit list --table
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Output Formats
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
npx headroom content list --table # Human-readable table
|
|
110
|
+
npx headroom content list # JSON (pipe to jq)
|
|
111
|
+
npx headroom content list --quiet # Exit code only
|
|
112
|
+
npx headroom content list --debug # Show HTTP request/response details
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Using with CI/CD
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
# Non-interactive login
|
|
119
|
+
echo "$HEADROOM_PASSWORD" | npx headroom login "$HEADROOM_URL" "$HEADROOM_SITE" \
|
|
120
|
+
--email "$HEADROOM_EMAIL" --password-stdin
|
|
121
|
+
|
|
122
|
+
# Publish all draft content in a collection
|
|
123
|
+
npx headroom content list --collection posts --status draft | \
|
|
124
|
+
jq -r '.items[].contentId' | \
|
|
125
|
+
xargs -I{} npx headroom content publish {}
|
|
126
|
+
```
|
|
127
|
+
|
|
54
128
|
## Default Collections
|
|
55
129
|
|
|
56
130
|
New Headroom sites are scaffolded with three collections:
|
|
@@ -10,9 +10,10 @@
|
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"astro": "^5.0.0",
|
|
13
|
-
"@headroom-cms/api": "^0.1.
|
|
13
|
+
"@headroom-cms/api": "^0.1.6"
|
|
14
14
|
},
|
|
15
15
|
"devDependencies": {
|
|
16
|
+
"@headroom-cms/cli": "^0.1.5",
|
|
16
17
|
"@tailwindcss/vite": "^4.0.0",
|
|
17
18
|
"tailwindcss": "^4.0.0",
|
|
18
19
|
"tsx": "^4.0.0",
|
|
@@ -21,9 +21,10 @@ src/
|
|
|
21
21
|
│ │ ├── page.tsx # Blog listing
|
|
22
22
|
│ │ └── [slug]/page.tsx # Blog post detail
|
|
23
23
|
│ └── [slug]/page.tsx # Dynamic pages from "pages" collection
|
|
24
|
-
├── components/ # Reusable React components
|
|
24
|
+
├── components/ # Reusable React components (pure presentation, receive data via props)
|
|
25
25
|
└── lib/
|
|
26
26
|
├── client.ts # Singleton HeadroomClient instance
|
|
27
|
+
├── settings.ts # React.cache() wrapper for site-settings (deduplicates per request)
|
|
27
28
|
└── links.ts # Content link resolver (maps refs to URL paths)
|
|
28
29
|
```
|
|
29
30
|
|
|
@@ -47,6 +48,80 @@ npm run start # Start production server
|
|
|
47
48
|
npm run generate:schemas # Generate Zod schemas from Headroom collections
|
|
48
49
|
```
|
|
49
50
|
|
|
51
|
+
## Headroom CLI
|
|
52
|
+
|
|
53
|
+
The `@headroom-cms/cli` package is included as a dev dependency and provides a full admin CLI for managing your Headroom CMS. Run it via `npx headroom` or add scripts to `package.json`.
|
|
54
|
+
|
|
55
|
+
### Setup
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# Login to your Headroom instance
|
|
59
|
+
npx headroom login https://your-headroom-url.cloudfront.net your-site.com \
|
|
60
|
+
--email admin@example.com --password-stdin
|
|
61
|
+
|
|
62
|
+
# Verify login
|
|
63
|
+
npx headroom whoami
|
|
64
|
+
|
|
65
|
+
# Set the active site (if managing multiple sites)
|
|
66
|
+
npx headroom site your-site.com
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Key Commands
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# Collections
|
|
73
|
+
npx headroom collections list --table # List all collections
|
|
74
|
+
npx headroom collections describe posts # Show field schema with constraints
|
|
75
|
+
npx headroom collections example posts # Generate JSON template for content creation
|
|
76
|
+
|
|
77
|
+
# Content
|
|
78
|
+
npx headroom content list --table # List all content
|
|
79
|
+
npx headroom content list --collection posts --table # List by collection
|
|
80
|
+
npx headroom content list --related-to "author:01ABC" --table # Filter by relationship
|
|
81
|
+
npx headroom content get <content-id> # Get single item with body
|
|
82
|
+
npx headroom content references <content-id> --table # What references this content?
|
|
83
|
+
npx headroom content create --collection posts --data '{"title":"New Post"}'
|
|
84
|
+
npx headroom content publish <content-id>
|
|
85
|
+
npx headroom content delete <content-id> --force
|
|
86
|
+
|
|
87
|
+
# Media
|
|
88
|
+
npx headroom media list --table # List media files
|
|
89
|
+
npx headroom media upload ./image.jpg # Upload a local file
|
|
90
|
+
npx headroom media upload-url https://... # Upload from URL
|
|
91
|
+
|
|
92
|
+
# API Keys
|
|
93
|
+
npx headroom api-keys list --table
|
|
94
|
+
npx headroom api-keys create --data '{"label":"My Key"}'
|
|
95
|
+
|
|
96
|
+
# Tags
|
|
97
|
+
npx headroom tags list --table
|
|
98
|
+
|
|
99
|
+
# Audit log
|
|
100
|
+
npx headroom audit list --table
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Output Formats
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
npx headroom content list --table # Human-readable table
|
|
107
|
+
npx headroom content list # JSON (pipe to jq)
|
|
108
|
+
npx headroom content list --quiet # Exit code only
|
|
109
|
+
npx headroom content list --debug # Show HTTP request/response details
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Using with CI/CD
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
# Non-interactive login
|
|
116
|
+
echo "$HEADROOM_PASSWORD" | npx headroom login "$HEADROOM_URL" "$HEADROOM_SITE" \
|
|
117
|
+
--email "$HEADROOM_EMAIL" --password-stdin
|
|
118
|
+
|
|
119
|
+
# Publish all draft content in a collection
|
|
120
|
+
npx headroom content list --collection posts --status draft | \
|
|
121
|
+
jq -r '.items[].contentId' | \
|
|
122
|
+
xargs -I{} npx headroom content publish {}
|
|
123
|
+
```
|
|
124
|
+
|
|
50
125
|
## Default Collections
|
|
51
126
|
|
|
52
127
|
New Headroom sites are scaffolded with three collections:
|
|
@@ -59,7 +134,15 @@ New Headroom sites are scaffolded with three collections:
|
|
|
59
134
|
|
|
60
135
|
## Fetching Data
|
|
61
136
|
|
|
62
|
-
All data fetching uses the `HeadroomClient` from `src/lib/client.ts`. Since this is a Next.js App Router project, data fetching happens in **Server Components** (the default).
|
|
137
|
+
All data fetching uses the `HeadroomClient` from `src/lib/client.ts`. Since this is a Next.js App Router project, data fetching happens in **Server Components** (the default). The layout uses `force-dynamic` to ensure fresh data on every request.
|
|
138
|
+
|
|
139
|
+
### Dev Auto-Refresh
|
|
140
|
+
|
|
141
|
+
In development (`NODE_ENV === "development"`), the layout includes a `DevRefresh` component from `@headroom-cms/api/next` that polls the CMS `/version` endpoint and calls `router.refresh()` when content changes, so edits in the admin UI are reflected without manual reload.
|
|
142
|
+
|
|
143
|
+
### Site Settings
|
|
144
|
+
|
|
145
|
+
Site settings are fetched via `getSettings()` from `src/lib/settings.ts`, which uses `React.cache()` to deduplicate the fetch across layout and page components within the same request. The layout fetches settings once and passes them as props to `Header` and `Footer` (pure presentation components).
|
|
63
146
|
|
|
64
147
|
### Getting the Client
|
|
65
148
|
|
|
@@ -12,9 +12,10 @@
|
|
|
12
12
|
"next": "^15.0.0",
|
|
13
13
|
"react": "^19.0.0",
|
|
14
14
|
"react-dom": "^19.0.0",
|
|
15
|
-
"@headroom-cms/api": "^0.1.
|
|
15
|
+
"@headroom-cms/api": "^0.1.6"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
18
|
+
"@headroom-cms/cli": "^0.1.5",
|
|
18
19
|
"@tailwindcss/postcss": "^4.0.0",
|
|
19
20
|
"tailwindcss": "^4.0.0",
|
|
20
21
|
"tsx": "^4.0.0",
|
|
@@ -1,18 +1,10 @@
|
|
|
1
1
|
import type { Metadata } from "next";
|
|
2
2
|
import { Header } from "@/components/Header";
|
|
3
3
|
import { Footer } from "@/components/Footer";
|
|
4
|
-
import {
|
|
5
|
-
import { unstable_cache } from "next/cache";
|
|
4
|
+
import { getSettings } from "@/lib/settings";
|
|
6
5
|
import "./globals.css";
|
|
7
6
|
|
|
8
|
-
const
|
|
9
|
-
async () => {
|
|
10
|
-
const client = getClient();
|
|
11
|
-
return client.getSingleton("site-settings");
|
|
12
|
-
},
|
|
13
|
-
["site-settings"],
|
|
14
|
-
{ revalidate: 3600 }
|
|
15
|
-
);
|
|
7
|
+
export const dynamic = "force-dynamic";
|
|
16
8
|
|
|
17
9
|
export async function generateMetadata(): Promise<Metadata> {
|
|
18
10
|
try {
|
|
@@ -28,14 +20,32 @@ export async function generateMetadata(): Promise<Metadata> {
|
|
|
28
20
|
}
|
|
29
21
|
}
|
|
30
22
|
|
|
31
|
-
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
23
|
+
export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
|
24
|
+
const settings = await getSettings().catch(() => null);
|
|
25
|
+
const siteName = (settings?.body?.siteName as string) || "My Site";
|
|
26
|
+
const menuItems = (settings?.body?.menuItems as Array<{ label: string; href: string }>) || [];
|
|
27
|
+
const footerText = (settings?.body?.footerText as string) || "";
|
|
28
|
+
|
|
32
29
|
return (
|
|
33
30
|
<html lang="en">
|
|
34
31
|
<body className="min-h-screen flex flex-col">
|
|
35
|
-
<
|
|
32
|
+
{process.env.NODE_ENV === "development" && <DevRefreshLoader />}
|
|
33
|
+
<Header siteName={siteName} menuItems={menuItems} />
|
|
36
34
|
<main className="flex-1">{children}</main>
|
|
37
|
-
<Footer />
|
|
35
|
+
<Footer footerText={footerText} />
|
|
38
36
|
</body>
|
|
39
37
|
</html>
|
|
40
38
|
);
|
|
41
39
|
}
|
|
40
|
+
|
|
41
|
+
async function DevRefreshLoader() {
|
|
42
|
+
const { DevRefresh } = await import("@headroom-cms/api/next");
|
|
43
|
+
const url = process.env.HEADROOM_URL || "http://localhost:3000";
|
|
44
|
+
const site = process.env.HEADROOM_SITE || "my.local";
|
|
45
|
+
return (
|
|
46
|
+
<DevRefresh
|
|
47
|
+
versionUrl={`${url}/v1/${site}/version`}
|
|
48
|
+
apiKey={process.env.HEADROOM_API_KEY || ""}
|
|
49
|
+
/>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getClient } from "@/lib/client";
|
|
2
|
+
import { getSettings } from "@/lib/settings";
|
|
2
3
|
import { PostCard } from "@/components/PostCard";
|
|
3
4
|
import { BlockRenderer } from "@headroom-cms/api/react";
|
|
4
5
|
import "@headroom-cms/api/react/headroom-blocks.css";
|
|
@@ -7,12 +8,11 @@ import type { Block, RefsMap } from "@headroom-cms/api";
|
|
|
7
8
|
|
|
8
9
|
export default async function Home() {
|
|
9
10
|
const client = getClient();
|
|
10
|
-
const settings = await
|
|
11
|
-
|
|
12
|
-
limit: 3,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const homePage = await client.getContentBySlug("pages", "home").catch(() => null);
|
|
11
|
+
const [settings, { items: posts }, homePage] = await Promise.all([
|
|
12
|
+
getSettings().catch(() => null),
|
|
13
|
+
client.listContent("posts", { limit: 3, sort: "published_desc" }),
|
|
14
|
+
client.getContentBySlug("pages", "home").catch(() => null),
|
|
15
|
+
]);
|
|
16
16
|
const homeBlocks = (homePage?.body?.content || []) as Block[];
|
|
17
17
|
const homeRefs = (homePage?._refs || {}) as RefsMap;
|
|
18
18
|
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const client = getClient();
|
|
5
|
-
const settings = await client.getSingleton("site-settings").catch(() => null);
|
|
6
|
-
const footerText = (settings?.body?.footerText as string) || "";
|
|
1
|
+
interface FooterProps {
|
|
2
|
+
footerText: string;
|
|
3
|
+
}
|
|
7
4
|
|
|
5
|
+
export function Footer({ footerText }: FooterProps) {
|
|
8
6
|
return (
|
|
9
7
|
<footer className="border-t border-gray-100 py-8 mt-16">
|
|
10
8
|
<div className="max-w-5xl mx-auto px-6 text-center text-sm text-gray-500">
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import Link from "next/link";
|
|
2
|
-
import { getClient } from "@/lib/client";
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const menuItems = (settings?.body?.menuItems as Array<{ label: string; href: string }>) || [];
|
|
3
|
+
interface HeaderProps {
|
|
4
|
+
siteName: string;
|
|
5
|
+
menuItems: Array<{ label: string; href: string }>;
|
|
6
|
+
}
|
|
9
7
|
|
|
8
|
+
export function Header({ siteName, menuItems }: HeaderProps) {
|
|
10
9
|
return (
|
|
11
10
|
<header className="border-b border-gray-100 bg-white sticky top-0 z-50">
|
|
12
11
|
<nav className="max-w-5xl mx-auto px-6 py-4 flex items-center justify-between">
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { cache } from "react";
|
|
2
|
+
import { getClient } from "./client";
|
|
3
|
+
|
|
4
|
+
/** Per-request cached site settings fetch. Deduplicates across layout + pages in a single render. */
|
|
5
|
+
export const getSettings = cache(async () => {
|
|
6
|
+
const client = getClient();
|
|
7
|
+
return client.getSingleton("site-settings");
|
|
8
|
+
});
|