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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-headroom-site",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Scaffold a new Headroom CMS frontend site (Astro or Next.js)",
5
5
  "type": "module",
6
6
  "license": "PolyForm-Noncommercial-1.0.0",
@@ -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.4"
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.4"
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 { getClient } from "@/lib/client";
5
- import { unstable_cache } from "next/cache";
4
+ import { getSettings } from "@/lib/settings";
6
5
  import "./globals.css";
7
6
 
8
- const getSettings = unstable_cache(
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
- <Header />
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 client.getSingleton("site-settings").catch(() => null);
11
- const { items: posts } = await client.listContent("posts", {
12
- limit: 3,
13
- sort: "published_desc",
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
- import { getClient } from "@/lib/client";
2
-
3
- export async function Footer() {
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
- export async function Header() {
5
- const client = getClient();
6
- const settings = await client.getSingleton("site-settings").catch(() => null);
7
- const siteName = (settings?.body?.siteName as string) || "My Site";
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
+ });