@sprintup-cms/sdk 1.0.0 → 1.1.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/CHANGELOG.md CHANGED
@@ -2,6 +2,27 @@
2
2
 
3
3
  All notable changes to `@sprintup-cms/sdk` will be documented here.
4
4
 
5
+ ## [1.1.0] — 2026-03-05
6
+
7
+ ### Added
8
+ - `getSitemap()` — all published page URLs with priority, changefreq, lastmod for `app/sitemap.ts`
9
+ - `getStatus()` — connectivity check returning page counts; always fresh (`cache: 'no-store'`)
10
+ - `CMSBlocksProps.custom` — override or extend any built-in block type with a custom React component
11
+ - `CMSSitemapResponse`, `CMSSitemapUrl`, `CMSStatusResponse` types exported
12
+ - `rendererKey` and `variant` fields on `CMSPageType`
13
+ - `createRevalidateHandler` `onRevalidate` callback for post-revalidation hooks
14
+ - Root `"."` export now resolves the `src/index.ts` barrel; new `"./client"` subpath added
15
+
16
+ ### Changed
17
+ - `CMSListResponse` corrected to flat `{ data, count }` shape (was `{ data, meta: { total } }`)
18
+ - `block.data` is now canonical; `block.content` kept as legacy fallback in all renderers
19
+ - Config resolved lazily at request time — no more build-time crashes when env vars are absent
20
+
21
+ ### Fixed
22
+ - Removed `require('./page-type-renderers')` CommonJS call that broke ES module consumers
23
+ - Removed non-existent `getPageTypes()` list method (no list endpoint exists in the v1 API)
24
+ - Internal link anchor regex now handles multi-segment slugs and all quote styles
25
+
5
26
  ## [1.0.0] — 2026-03-03
6
27
 
7
28
  ### Added
package/README.md CHANGED
@@ -4,155 +4,445 @@ Official SDK for **SprintUp Forge CMS** — typed API client, Next.js App Router
4
4
 
5
5
  ---
6
6
 
7
+ ## Table of contents
8
+
9
+ 1. [Install](#install)
10
+ 2. [Environment variables](#environment-variables)
11
+ 3. [Initial setup](#initial-setup)
12
+ - [Connect to the CMS](#connect-to-the-cms)
13
+ - [Catch-all page route](#catch-all-page-route)
14
+ - [On-demand revalidation webhook](#on-demand-revalidation-webhook)
15
+ - [Preview mode exit](#preview-mode-exit)
16
+ - [Sitemap](#sitemap)
17
+ 4. [Verify your connection](#verify-your-connection)
18
+ 5. [Adding new pages](#adding-new-pages)
19
+ 6. [Custom page layouts](#custom-page-layouts)
20
+ 7. [Navigation and footer](#navigation-and-footer)
21
+ 8. [Extending with custom blocks](#extending-with-custom-blocks)
22
+ 9. [ISR and caching strategy](#isr-and-caching-strategy)
23
+ 10. [API reference](#api-reference)
24
+ 11. [Block types reference](#block-types-reference)
25
+ 12. [Troubleshooting](#troubleshooting)
26
+
27
+ ---
28
+
7
29
  ## Install
8
30
 
9
31
  ```bash
10
32
  npm install @sprintup-cms/sdk
33
+ # or
34
+ pnpm add @sprintup-cms/sdk
35
+ # or
36
+ yarn add @sprintup-cms/sdk
11
37
  ```
12
38
 
13
39
  ---
14
40
 
15
- ## Quick start
16
-
17
- ### 1. Environment variables
41
+ ## Environment variables
18
42
 
19
- Add to your school website's `.env.local`:
43
+ Add the following to your website's `.env.local`. **Never commit these to git.**
20
44
 
21
45
  ```env
46
+ # ── Required ──────────────────────────────────────────────────────────────────
47
+ # Base URL of your SprintUp Forge CMS deployment
22
48
  NEXT_PUBLIC_CMS_URL=https://your-cms.vercel.app
49
+
50
+ # API key from CMS Admin → Settings → API Keys → Create key
23
51
  CMS_API_KEY=cmsk_xxxxxxxxxxxxxxxxxxxx
52
+
53
+ # App ID from CMS Admin → Settings → Apps
54
+ # (shown in the URL when you select a site: /admin/dashboard?app=school-website)
24
55
  CMS_APP_ID=school-website
56
+
57
+ # ── Required for webhooks ──────────────────────────────────────────────────────
58
+ # Secret that the CMS will send with every webhook request — validate on your end
25
59
  CMS_WEBHOOK_SECRET=your-random-secret
60
+
61
+ # ── Optional ──────────────────────────────────────────────────────────────────
62
+ # Public URL of this website — used by sitemap generation
63
+ NEXT_PUBLIC_SITE_URL=https://www.yourschool.edu
26
64
  ```
27
65
 
28
- Generate a webhook secret with:
66
+ Generate a secure webhook secret:
29
67
 
30
68
  ```bash
31
69
  openssl rand -hex 32
32
70
  ```
33
71
 
72
+ **Where to find your values in the CMS:**
73
+
74
+ | Variable | Location in CMS Admin |
75
+ |---|---|
76
+ | `NEXT_PUBLIC_CMS_URL` | The domain your CMS is deployed to |
77
+ | `CMS_API_KEY` | Settings → API Keys → Create new key |
78
+ | `CMS_APP_ID` | Settings → Apps → click your site → copy App ID |
79
+ | `CMS_WEBHOOK_SECRET` | Settings → Webhooks → add secret when registering the URL |
80
+
81
+ ---
82
+
83
+ ## Initial setup
84
+
85
+ ### Connect to the CMS
86
+
87
+ The SDK reads your environment variables automatically. No extra configuration is required for the default singleton client.
88
+
89
+ ```ts
90
+ // lib/cms.ts
91
+ import { cmsClient } from '@sprintup-cms/sdk'
92
+ export default cmsClient
93
+ ```
94
+
95
+ For multiple sites or custom configuration:
96
+
97
+ ```ts
98
+ import { createCMSClient } from '@sprintup-cms/sdk'
99
+
100
+ export const schoolCms = createCMSClient({
101
+ baseUrl: process.env.NEXT_PUBLIC_CMS_URL,
102
+ apiKey: process.env.CMS_API_KEY,
103
+ appId: process.env.CMS_APP_ID,
104
+ })
105
+ ```
106
+
34
107
  ---
35
108
 
36
- ### 2. Catch-all CMS page
109
+ ### Catch-all page route
37
110
 
38
- Create `app/[...slug]/page.tsx`:
111
+ This single file automatically renders **every page** published in your CMS — no manual routing needed.
39
112
 
40
113
  ```ts
114
+ // app/[...slug]/page.tsx
41
115
  export { CMSCatchAllPage as default, generateMetadata } from '@sprintup-cms/sdk/next'
42
116
  ```
43
117
 
44
- That's it. Every page published in the CMS will be automatically rendered.
118
+ How it works:
119
+ - Fetches the page by `slug` from the CMS on each request (ISR, 60s revalidation).
120
+ - Falls through to `notFound()` when the slug does not exist in the CMS.
121
+ - Activates draft/preview mode automatically when `draftMode()` is enabled.
122
+ - Renders `<CMSBlocks>` for standard pages and structured content for page-type pages.
45
123
 
46
124
  ---
47
125
 
48
- ### 3. On-demand revalidation webhook
126
+ ### On-demand revalidation webhook
49
127
 
50
- Create `app/api/cms-revalidate/route.ts`:
128
+ When an editor publishes or updates a page in the CMS, this webhook fires to instantly clear the ISR cache for that page.
51
129
 
52
130
  ```ts
131
+ // app/api/cms-revalidate/route.ts
53
132
  export { POST } from '@sprintup-cms/sdk/next'
54
133
  ```
55
134
 
56
- Then set the webhook URL in your CMS App Settings to:
135
+ Then register the webhook in your **CMS Admin Settings → Webhooks**:
57
136
 
58
137
  ```
59
- https://your-school-site.vercel.app/api/cms-revalidate
138
+ URL: https://www.yourschool.edu/api/cms-revalidate
139
+ Secret: (same value as CMS_WEBHOOK_SECRET)
140
+ Events: published, deleted, archived
60
141
  ```
61
142
 
62
- ---
143
+ Custom hook with extra logic:
63
144
 
64
- ### 4. Preview exit
145
+ ```ts
146
+ // app/api/cms-revalidate/route.ts
147
+ import { createRevalidateHandler } from '@sprintup-cms/sdk/next'
148
+
149
+ export const POST = createRevalidateHandler({
150
+ secret: process.env.CMS_WEBHOOK_SECRET,
151
+ onRevalidate: async ({ slug, event }) => {
152
+ console.log(`CMS revalidated /${slug} — event: ${event}`)
153
+ // e.g. update a search index, notify Slack, etc.
154
+ },
155
+ })
156
+ ```
65
157
 
66
- Create `app/api/cms-preview/exit/route.ts`:
158
+ ---
159
+
160
+ ### Preview mode exit
67
161
 
68
162
  ```ts
163
+ // app/api/cms-preview/exit/route.ts
69
164
  export { previewExitGET as GET } from '@sprintup-cms/sdk/next'
70
165
  ```
71
166
 
72
- ---
167
+ The CMS editor links to `/api/cms-preview/exit?redirect=/your-slug` to leave draft mode. This handler disables `draftMode()` and redirects the editor back to the live page.
73
168
 
74
- ## Manual usage
169
+ ---
75
170
 
76
- ### Core client
171
+ ### Sitemap
77
172
 
78
173
  ```ts
174
+ // app/sitemap.ts
175
+ import type { MetadataRoute } from 'next'
79
176
  import { cmsClient } from '@sprintup-cms/sdk'
80
177
 
81
- // Get a single page by slug
82
- const page = await cmsClient.getPage('about')
178
+ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
179
+ const data = await cmsClient.getSitemap()
180
+ if (!data?.enabled) return []
83
181
 
84
- // Get all blog posts
85
- const posts = await cmsClient.getBlogPosts()
86
-
87
- // Get site structure (nav, footer)
88
- const structure = await cmsClient.getSiteStructure()
182
+ return data.urls.map(url => ({
183
+ url: `${process.env.NEXT_PUBLIC_SITE_URL}${url.loc}`,
184
+ lastModified: url.lastmod,
185
+ changeFrequency: url.changefreq as MetadataRoute.Sitemap[0]['changeFrequency'],
186
+ priority: url.priority,
187
+ }))
188
+ }
89
189
  ```
90
190
 
91
- ### Custom client instance
191
+ ---
192
+
193
+ ## Verify your connection
194
+
195
+ Add a temporary status check page to confirm your API key and App ID are working:
92
196
 
93
197
  ```ts
94
- import { createCMSClient } from '@sprintup-cms/sdk'
198
+ // app/cms-status/page.tsx (remove before going to production)
199
+ import { cmsClient } from '@sprintup-cms/sdk'
95
200
 
96
- const cms = createCMSClient({
97
- baseUrl: 'https://your-cms.vercel.app',
98
- apiKey: 'cmsk_xxxx',
99
- appId: 'school-website',
100
- })
201
+ export default async function CmsStatusPage() {
202
+ const status = await cmsClient.getStatus()
203
+ if (!status) return <p>CMS connection failed — check your environment variables.</p>
204
+
205
+ return (
206
+ <div style={{ padding: 32, fontFamily: 'monospace' }}>
207
+ <h1>CMS Status</h1>
208
+ <p>App ID: {status.appId}</p>
209
+ <p>Total pages: {status.totalPages}</p>
210
+ <p>Published: {status.publishedPages}</p>
211
+ <pre>{JSON.stringify(status.pages.slice(0, 5), null, 2)}</pre>
212
+ </div>
213
+ )
214
+ }
215
+ ```
216
+
217
+ ---
218
+
219
+ ## Adding new pages
220
+
221
+ You **never write code** to add a new page. All content management happens in the CMS:
222
+
223
+ 1. Go to **CMS Admin → Content** (scoped to your site).
224
+ 2. Click **New Page**.
225
+ 3. Choose a page type (e.g. Standard Page, Blog Post, Event).
226
+ 4. Fill in the title, slug, and content blocks.
227
+ 5. Click **Publish**.
228
+ 6. The webhook fires, your Next.js site revalidates within seconds, and the page is live at `https://yoursite.com/<slug>`.
229
+
230
+ To add a **custom page type** (e.g. Programme, Staff Profile):
231
+
232
+ 1. Go to **CMS Admin → Page Types**.
233
+ 2. Click **New Page Type**.
234
+ 3. Define sections and fields.
235
+ 4. Create content using that type.
236
+ 5. In your website, customize the rendering — see [Custom page layouts](#custom-page-layouts).
237
+
238
+ ---
239
+
240
+ ## Custom page layouts
241
+
242
+ Override the default rendering for a specific page type by wrapping `CMSCatchAllPage`:
243
+
244
+ ```tsx
245
+ // app/[...slug]/page.tsx
246
+ import { notFound } from 'next/navigation'
247
+ import { cmsClient } from '@sprintup-cms/sdk'
248
+ import { CMSBlocks } from '@sprintup-cms/sdk/react'
249
+ import type { Metadata } from 'next'
250
+
251
+ export async function generateMetadata({ params }: { params: Promise<{ slug: string[] }> }): Promise<Metadata> {
252
+ const { slug } = await params
253
+ const page = await cmsClient.getPage(slug.join('/'))
254
+ if (!page) return { title: 'Not Found' }
255
+ return {
256
+ title: page.seo?.title || page.title,
257
+ description: page.seo?.description || '',
258
+ }
259
+ }
260
+
261
+ export default async function Page({ params }: { params: Promise<{ slug: string[] }> }) {
262
+ const { slug } = await params
263
+ const page = await cmsClient.getPage(slug.join('/'))
264
+ if (!page) notFound()
265
+
266
+ // Render a blog post with a custom layout
267
+ if (page.pageType === 'blog-post') {
268
+ return (
269
+ <article className="max-w-2xl mx-auto py-16 px-6">
270
+ <h1 className="text-4xl font-bold">{page.title}</h1>
271
+ <time className="text-sm text-gray-500">{page.publishedAt}</time>
272
+ <CMSBlocks blocks={page.blocks} />
273
+ </article>
274
+ )
275
+ }
276
+
277
+ // Fall back to default layout for all other page types
278
+ return (
279
+ <main className="max-w-5xl mx-auto py-12 px-6">
280
+ <h1 className="text-4xl font-bold mb-8">{page.title}</h1>
281
+ <CMSBlocks blocks={page.blocks} />
282
+ </main>
283
+ )
284
+ }
101
285
  ```
102
286
 
103
- ### React block renderer
287
+ ---
288
+
289
+ ## Navigation and footer
290
+
291
+ Pull live nav and footer data from the CMS site structure:
104
292
 
105
293
  ```tsx
106
- import { CMSBlocks, CMSPreviewBanner } from '@sprintup-cms/sdk/react'
294
+ // components/layout/header.tsx
295
+ import { cmsClient } from '@sprintup-cms/sdk'
296
+ import type { CMSMenuItem } from '@sprintup-cms/sdk'
297
+ import Link from 'next/link'
298
+
299
+ export async function Header() {
300
+ const structure = await cmsClient.getSiteStructure()
301
+ const navItems: CMSMenuItem[] = structure?.menus.header ?? []
107
302
 
108
- export default function Page({ page, pageType, isPreview }) {
109
303
  return (
110
- <>
111
- <CMSPreviewBanner isPreview={isPreview} status={page.status} slug={page.slug} />
112
- <CMSBlocks
113
- blocks={page.blocks}
114
- pageType={pageType}
115
- // Override any block type with your own component:
116
- custom={{
117
- 'my-hero': (block) => <MyHeroComponent {...block.data} />,
118
- }}
119
- />
120
- </>
304
+ <header className="border-b">
305
+ <nav className="max-w-5xl mx-auto px-6 py-4 flex gap-6">
306
+ {navItems.map(item => (
307
+ <Link key={item.id} href={item.href} target={item.openInNewTab ? '_blank' : undefined}>
308
+ {item.label}
309
+ </Link>
310
+ ))}
311
+ </nav>
312
+ </header>
313
+ )
314
+ }
315
+ ```
316
+
317
+ ```tsx
318
+ // app/layout.tsx
319
+ import { Header } from '@/components/layout/header'
320
+
321
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
322
+ return (
323
+ <html lang="en">
324
+ <body>
325
+ <Header />
326
+ {children}
327
+ </body>
328
+ </html>
121
329
  )
122
330
  }
123
331
  ```
124
332
 
125
333
  ---
126
334
 
127
- ## Supported block types
335
+ ## Extending with custom blocks
128
336
 
129
- | Type | Description |
337
+ Override any built-in block or add a completely new block type using the `custom` prop:
338
+
339
+ ```tsx
340
+ import { CMSBlocks } from '@sprintup-cms/sdk/react'
341
+ import { MyVideoPlayer } from '@/components/video-player'
342
+ import { ProgrammeCard } from '@/components/programme-card'
343
+
344
+ <CMSBlocks
345
+ blocks={page.blocks}
346
+ custom={{
347
+ // Override built-in video block with your own player
348
+ 'video': (block) => <MyVideoPlayer src={block.data?.url} />,
349
+
350
+ // Add a completely custom block type created in the CMS
351
+ 'programme-card': (block) => (
352
+ <ProgrammeCard
353
+ title={block.data?.title}
354
+ duration={block.data?.duration}
355
+ applyUrl={block.data?.applyUrl}
356
+ />
357
+ ),
358
+ }}
359
+ />
360
+ ```
361
+
362
+ The `custom` prop is a `Record<blockType, (block: CMSBlock) => React.ReactNode>`. It takes priority over all built-in renderers.
363
+
364
+ ---
365
+
366
+ ## ISR and caching strategy
367
+
368
+ | Data | Revalidation | Cache tag |
369
+ |---|---|---|
370
+ | Individual page | 60 seconds | `cms-page-{slug}` |
371
+ | All pages list | 60 seconds | `cms-pages-{appId}` |
372
+ | Page type schema | 3600 seconds | `cms-page-type-{id}` |
373
+ | Site structure (nav/footer) | 300 seconds | `site-structure-{appId}` |
374
+ | Sitemap | 3600 seconds | — |
375
+ | Status check | No cache | — |
376
+
377
+ The revalidation webhook calls `revalidateTag('cms-page-{slug}')` and `revalidatePath('/{slug}')` when the CMS publishes a page, giving you **instant updates** without a full rebuild.
378
+
379
+ ---
380
+
381
+ ## API reference
382
+
383
+ ### `cmsClient`
384
+
385
+ | Method | Description |
130
386
  |---|---|
131
- | `heading` | H1–H4 heading |
132
- | `text` | Plain paragraph |
133
- | `richtext` | HTML rich text (rendered with `prose`) |
134
- | `image` | Image with optional caption |
135
- | `hero` / `hero-section` | Hero with title, subtitle, CTA buttons |
136
- | `cta` | Call-to-action section |
137
- | `faq` | Accordion FAQ list |
138
- | `stats` | Stats grid |
139
- | `testimonial` | Quote card with author |
140
- | `quote` | Blockquote |
141
- | `alert` | Info / success / warning / error banner |
142
- | `divider` | Horizontal rule |
143
- | `spacer` | Vertical spacing (sm/md/lg/xl) |
144
- | `video` | YouTube / Vimeo embed |
145
- | *(any page type section)* | Rendered as labelled fields from schema |
146
-
147
- ---
148
-
149
- ## Entry points
150
-
151
- | Import | Contents |
387
+ | `getPage(slug)` | Fetch a single published page by slug |
388
+ | `getPages(options?)` | Fetch multiple pages — filterable by `type`, `group`, `page`, `perPage` |
389
+ | `getBlogPosts()` | Shorthand for `getPages({ type: 'blog-post' })` |
390
+ | `getEvents()` | Shorthand for `getPages({ type: 'event-page' })` |
391
+ | `getAnnouncements()` | Shorthand for `getPages({ type: 'announcement-page' })` |
392
+ | `getPageType(id)` | Fetch a page type schema by ID |
393
+ | `getSiteStructure()` | Fetch nav, footer, and page tree |
394
+ | `getPreviewPage(token)` | Fetch a draft page for preview mode |
395
+ | `getPageWithPreview(slug, token?)` | Fetch page preview if token present, live otherwise |
396
+ | `getSitemap()` | Fetch all published slugs with sitemap metadata |
397
+ | `getStatus()` | Connectivity check returns counts and page list |
398
+
399
+ ---
400
+
401
+ ## Block types reference
402
+
403
+ | Block type | Key fields in `block.data` |
152
404
  |---|---|
153
- | `@sprintup-cms/sdk` | Typed client, all interfaces |
154
- | `@sprintup-cms/sdk/next` | `CMSCatchAllPage`, `POST` revalidate, `previewExitGET` |
155
- | `@sprintup-cms/sdk/react` | `CMSBlocks`, `CMSPreviewBanner` |
405
+ | `heading` | `text`, `level` (1–4) |
406
+ | `text` | `text` |
407
+ | `richtext` | `content` (HTML string) |
408
+ | `image` | `src`, `alt`, `caption` |
409
+ | `hero` | `title`, `subtitle`, `primaryButton`, `primaryUrl`, `secondaryButton`, `secondaryUrl`, `badge` |
410
+ | `cta` | `title`, `subtitle`, `primaryButton`, `primaryUrl`, `secondaryButton`, `secondaryUrl` |
411
+ | `faq` | `title`, `items[]` — each `{ question, answer }` |
412
+ | `stats` | `items[]` — each `{ value, label, description }` |
413
+ | `testimonial` | `quote`, `author`, `role`, `avatar` |
414
+ | `quote` | `quote`, `author`, `source` |
415
+ | `alert` | `message`, `type` (info/success/warning/error) |
416
+ | `divider` | — |
417
+ | `spacer` | `size` (sm/md/lg/xl) |
418
+ | `video` | `url` (YouTube or Vimeo), `title`, `autoplay` |
419
+
420
+ ---
421
+
422
+ ## Troubleshooting
423
+
424
+ **Pages return empty (`getPage` returns `null`)**
425
+
426
+ - Check `CMS_APP_ID` matches exactly what is shown in CMS Admin → Apps.
427
+ - Verify the page status is **Published** (not Draft).
428
+ - Confirm the API key has `read` permission for the app.
429
+
430
+ **`getStatus()` returns `null`**
431
+
432
+ - All three env vars (`NEXT_PUBLIC_CMS_URL`, `CMS_API_KEY`, `CMS_APP_ID`) must be set.
433
+ - The CMS URL must not have a trailing slash.
434
+ - Test the API key directly: `curl -H "X-CMS-API-Key: cmsk_xxx" https://your-cms.vercel.app/api/v1/school-website/status`
435
+
436
+ **Webhook not firing / pages stale**
437
+
438
+ - Confirm the webhook URL is registered in CMS Admin → Settings → Webhooks.
439
+ - `CMS_WEBHOOK_SECRET` on both sides must match exactly.
440
+ - Check your deployment logs for `[sprintup-cms] revalidate error:` messages.
441
+
442
+ **Preview mode not working**
443
+
444
+ - The `/api/cms-preview/exit` route must exist.
445
+ - `draftMode()` requires a Next.js App Router project (not Pages Router).
156
446
 
157
447
  ---
158
448
 
package/dist/client.cjs CHANGED
@@ -110,31 +110,31 @@ function createCMSClient(options) {
110
110
  return null;
111
111
  }
112
112
  }
113
- async function getPageTypes() {
113
+ async function getSiteStructure() {
114
114
  const { baseUrl, apiKey, appId } = cfg();
115
- if (!baseUrl || !apiKey || !appId) return [];
115
+ if (!baseUrl || !apiKey || !appId) return null;
116
116
  try {
117
- const res = await fetch(`${baseUrl}/api/v1/${appId}/page-types`, {
117
+ const res = await fetch(`${baseUrl}/api/v1/${appId}/site-structure`, {
118
118
  headers: headers(),
119
- next: { revalidate: 3600, tags: [`cms-page-types-${appId}`] }
119
+ next: {
120
+ revalidate: 300,
121
+ tags: [`site-structure-${appId}`]
122
+ }
120
123
  });
121
- if (!res.ok) return [];
124
+ if (!res.ok) return null;
122
125
  const json = await res.json();
123
- return json.data ?? [];
126
+ return json.data ?? null;
124
127
  } catch {
125
- return [];
128
+ return null;
126
129
  }
127
130
  }
128
- async function getSiteStructure() {
131
+ async function getSitemap() {
129
132
  const { baseUrl, apiKey, appId } = cfg();
130
133
  if (!baseUrl || !apiKey || !appId) return null;
131
134
  try {
132
- const res = await fetch(`${baseUrl}/api/v1/${appId}/site-structure`, {
135
+ const res = await fetch(`${baseUrl}/api/v1/${appId}/sitemap`, {
133
136
  headers: headers(),
134
- next: {
135
- revalidate: 300,
136
- tags: [`site-structure-${appId}`]
137
- }
137
+ next: { revalidate: 3600 }
138
138
  });
139
139
  if (!res.ok) return null;
140
140
  const json = await res.json();
@@ -143,6 +143,20 @@ function createCMSClient(options) {
143
143
  return null;
144
144
  }
145
145
  }
146
+ async function getStatus() {
147
+ const { baseUrl, apiKey, appId } = cfg();
148
+ if (!baseUrl || !apiKey || !appId) return null;
149
+ try {
150
+ const res = await fetch(`${baseUrl}/api/v1/${appId}/status`, {
151
+ headers: headers(),
152
+ cache: "no-store"
153
+ });
154
+ if (!res.ok) return null;
155
+ return await res.json();
156
+ } catch {
157
+ return null;
158
+ }
159
+ }
146
160
  return {
147
161
  getPages,
148
162
  getPage,
@@ -152,8 +166,9 @@ function createCMSClient(options) {
152
166
  getPreviewPage,
153
167
  getPageWithPreview,
154
168
  getPageType,
155
- getPageTypes,
156
- getSiteStructure
169
+ getSiteStructure,
170
+ getSitemap,
171
+ getStatus
157
172
  };
158
173
  }
159
174
  var cmsClient = createCMSClient();