@sprintup-cms/sdk 1.0.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 +21 -0
- package/README.md +169 -0
- package/dist/client.cjs +164 -0
- package/dist/client.cjs.map +1 -0
- package/dist/client.d.cts +190 -0
- package/dist/client.d.ts +190 -0
- package/dist/client.js +161 -0
- package/dist/client.js.map +1 -0
- package/dist/next/index.cjs +557 -0
- package/dist/next/index.cjs.map +1 -0
- package/dist/next/index.d.cts +72 -0
- package/dist/next/index.d.ts +72 -0
- package/dist/next/index.js +546 -0
- package/dist/next/index.js.map +1 -0
- package/dist/react/index.cjs +299 -0
- package/dist/react/index.cjs.map +1 -0
- package/dist/react/index.d.cts +27 -0
- package/dist/react/index.d.ts +27 -0
- package/dist/react/index.js +292 -0
- package/dist/react/index.js.map +1 -0
- package/package.json +68 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@sprintup-cms/sdk` will be documented here.
|
|
4
|
+
|
|
5
|
+
## [1.0.0] — 2026-03-03
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- `@sprintup-cms/sdk` — zero-dependency typed API client (`cmsClient`, `createCMSClient`)
|
|
9
|
+
- `getPage`, `getPages`, `getBlogPosts`, `getEvents`, `getAnnouncements`
|
|
10
|
+
- `getPageType`, `getPageTypes`
|
|
11
|
+
- `getSiteStructure`
|
|
12
|
+
- `getPreviewPage`, `getPageWithPreview`
|
|
13
|
+
- `@sprintup-cms/sdk/next` — Next.js 15 App Router helpers
|
|
14
|
+
- `CMSCatchAllPage` — drop-in catch-all page with draft mode support
|
|
15
|
+
- `generateMetadata` — SEO metadata from CMS page data
|
|
16
|
+
- `POST` / `createRevalidateHandler` — on-demand ISR revalidation webhook
|
|
17
|
+
- `previewExitGET` / `createPreviewExitHandler` — draft mode exit
|
|
18
|
+
- `@sprintup-cms/sdk/react` — React block renderer
|
|
19
|
+
- `CMSBlocks` — renders all built-in block types + custom overrides
|
|
20
|
+
- `CMSPreviewBanner` — draft preview banner with exit link
|
|
21
|
+
- Full TypeScript types for all CMS entities
|
package/README.md
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# @sprintup-cms/sdk
|
|
2
|
+
|
|
3
|
+
Official SDK for **SprintUp Forge CMS** — typed API client, Next.js App Router helpers, and a React block renderer.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @sprintup-cms/sdk
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Quick start
|
|
16
|
+
|
|
17
|
+
### 1. Environment variables
|
|
18
|
+
|
|
19
|
+
Add to your school website's `.env.local`:
|
|
20
|
+
|
|
21
|
+
```env
|
|
22
|
+
NEXT_PUBLIC_CMS_URL=https://your-cms.vercel.app
|
|
23
|
+
CMS_API_KEY=cmsk_xxxxxxxxxxxxxxxxxxxx
|
|
24
|
+
CMS_APP_ID=school-website
|
|
25
|
+
CMS_WEBHOOK_SECRET=your-random-secret
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Generate a webhook secret with:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
openssl rand -hex 32
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
### 2. Catch-all CMS page
|
|
37
|
+
|
|
38
|
+
Create `app/[...slug]/page.tsx`:
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
export { CMSCatchAllPage as default, generateMetadata } from '@sprintup-cms/sdk/next'
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
That's it. Every page published in the CMS will be automatically rendered.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
### 3. On-demand revalidation webhook
|
|
49
|
+
|
|
50
|
+
Create `app/api/cms-revalidate/route.ts`:
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
export { POST } from '@sprintup-cms/sdk/next'
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Then set the webhook URL in your CMS App Settings to:
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
https://your-school-site.vercel.app/api/cms-revalidate
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
### 4. Preview exit
|
|
65
|
+
|
|
66
|
+
Create `app/api/cms-preview/exit/route.ts`:
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
export { previewExitGET as GET } from '@sprintup-cms/sdk/next'
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Manual usage
|
|
75
|
+
|
|
76
|
+
### Core client
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
import { cmsClient } from '@sprintup-cms/sdk'
|
|
80
|
+
|
|
81
|
+
// Get a single page by slug
|
|
82
|
+
const page = await cmsClient.getPage('about')
|
|
83
|
+
|
|
84
|
+
// Get all blog posts
|
|
85
|
+
const posts = await cmsClient.getBlogPosts()
|
|
86
|
+
|
|
87
|
+
// Get site structure (nav, footer)
|
|
88
|
+
const structure = await cmsClient.getSiteStructure()
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Custom client instance
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
import { createCMSClient } from '@sprintup-cms/sdk'
|
|
95
|
+
|
|
96
|
+
const cms = createCMSClient({
|
|
97
|
+
baseUrl: 'https://your-cms.vercel.app',
|
|
98
|
+
apiKey: 'cmsk_xxxx',
|
|
99
|
+
appId: 'school-website',
|
|
100
|
+
})
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### React block renderer
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
import { CMSBlocks, CMSPreviewBanner } from '@sprintup-cms/sdk/react'
|
|
107
|
+
|
|
108
|
+
export default function Page({ page, pageType, isPreview }) {
|
|
109
|
+
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
|
+
</>
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Supported block types
|
|
128
|
+
|
|
129
|
+
| Type | Description |
|
|
130
|
+
|---|---|
|
|
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 |
|
|
152
|
+
|---|---|
|
|
153
|
+
| `@sprintup-cms/sdk` | Typed client, all interfaces |
|
|
154
|
+
| `@sprintup-cms/sdk/next` | `CMSCatchAllPage`, `POST` revalidate, `previewExitGET` |
|
|
155
|
+
| `@sprintup-cms/sdk/react` | `CMSBlocks`, `CMSPreviewBanner` |
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Requirements
|
|
160
|
+
|
|
161
|
+
- Node.js 18+
|
|
162
|
+
- Next.js 14+ (for `/next` entry)
|
|
163
|
+
- React 18+ (for `/react` entry)
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## License
|
|
168
|
+
|
|
169
|
+
MIT — SprintUp IO
|
package/dist/client.cjs
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/* @sprintup-cms/sdk — https://forgecms.io */
|
|
4
|
+
|
|
5
|
+
// src/client.ts
|
|
6
|
+
function createCMSClient(options) {
|
|
7
|
+
function cfg() {
|
|
8
|
+
return {
|
|
9
|
+
baseUrl: (options?.baseUrl ?? process.env.NEXT_PUBLIC_CMS_URL ?? process.env.CMS_BASE_URL ?? "").replace(/\/$/, ""),
|
|
10
|
+
apiKey: options?.apiKey ?? process.env.CMS_API_KEY ?? "",
|
|
11
|
+
appId: options?.appId ?? process.env.CMS_APP_ID ?? ""
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function headers() {
|
|
15
|
+
return { "X-CMS-API-Key": cfg().apiKey, "Content-Type": "application/json" };
|
|
16
|
+
}
|
|
17
|
+
async function getPages(params) {
|
|
18
|
+
const { baseUrl, apiKey, appId } = cfg();
|
|
19
|
+
if (!baseUrl || !apiKey || !appId) {
|
|
20
|
+
console.warn("[sprintup-cms] Missing CMS_BASE_URL / CMS_API_KEY / CMS_APP_ID \u2014 returning []");
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
const qs = new URLSearchParams();
|
|
25
|
+
if (params?.type) qs.set("type", params.type);
|
|
26
|
+
if (params?.group) qs.set("group", params.group);
|
|
27
|
+
if (params?.page) qs.set("page", String(params.page));
|
|
28
|
+
if (params?.perPage) qs.set("perPage", String(params.perPage));
|
|
29
|
+
const url = `${baseUrl}/api/v1/${appId}/pages${qs.size ? `?${qs}` : ""}`;
|
|
30
|
+
const res = await fetch(url, {
|
|
31
|
+
headers: headers(),
|
|
32
|
+
next: { revalidate: 60, tags: [`cms-pages-${appId}`] }
|
|
33
|
+
});
|
|
34
|
+
if (!res.ok) {
|
|
35
|
+
console.error(`[sprintup-cms] getPages (${res.status})`);
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
const json = await res.json();
|
|
39
|
+
return json.data ?? [];
|
|
40
|
+
} catch (err) {
|
|
41
|
+
console.error("[sprintup-cms] getPages error:", err);
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async function getPage(slug) {
|
|
46
|
+
const { baseUrl, apiKey, appId } = cfg();
|
|
47
|
+
if (!baseUrl || !apiKey || !appId) {
|
|
48
|
+
console.warn("[sprintup-cms] Missing config \u2014 returning null");
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
const res = await fetch(`${baseUrl}/api/v1/${appId}/pages/${slug}`, {
|
|
53
|
+
headers: headers(),
|
|
54
|
+
next: { revalidate: 60, tags: [`cms-page-${slug}`, `cms-pages-${appId}`] }
|
|
55
|
+
});
|
|
56
|
+
if (res.status === 404) return null;
|
|
57
|
+
if (!res.ok) {
|
|
58
|
+
console.error(`[sprintup-cms] getPage "${slug}" (${res.status})`);
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
const json = await res.json();
|
|
62
|
+
return json.data ?? null;
|
|
63
|
+
} catch (err) {
|
|
64
|
+
console.error(`[sprintup-cms] getPage "${slug}" error:`, err);
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async function getBlogPosts() {
|
|
69
|
+
return getPages({ type: "blog-post" });
|
|
70
|
+
}
|
|
71
|
+
async function getEvents() {
|
|
72
|
+
return getPages({ type: "event-page" });
|
|
73
|
+
}
|
|
74
|
+
async function getAnnouncements() {
|
|
75
|
+
return getPages({ type: "announcement-page" });
|
|
76
|
+
}
|
|
77
|
+
async function getPreviewPage(token) {
|
|
78
|
+
const { baseUrl, appId } = cfg();
|
|
79
|
+
if (!baseUrl || !appId) return null;
|
|
80
|
+
try {
|
|
81
|
+
const res = await fetch(`${baseUrl}/api/v1/${appId}/preview?token=${encodeURIComponent(token)}`, {
|
|
82
|
+
cache: "no-store"
|
|
83
|
+
});
|
|
84
|
+
if (!res.ok) return null;
|
|
85
|
+
const json = await res.json();
|
|
86
|
+
return json.data ?? null;
|
|
87
|
+
} catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async function getPageWithPreview(slug, previewToken) {
|
|
92
|
+
if (previewToken) {
|
|
93
|
+
const preview = await getPreviewPage(previewToken);
|
|
94
|
+
if (preview?.slug === slug) return preview;
|
|
95
|
+
}
|
|
96
|
+
return getPage(slug);
|
|
97
|
+
}
|
|
98
|
+
async function getPageType(pageTypeId) {
|
|
99
|
+
const { baseUrl, apiKey, appId } = cfg();
|
|
100
|
+
if (!baseUrl || !apiKey || !appId || !pageTypeId) return null;
|
|
101
|
+
try {
|
|
102
|
+
const res = await fetch(`${baseUrl}/api/v1/${appId}/page-types/${pageTypeId}`, {
|
|
103
|
+
headers: headers(),
|
|
104
|
+
next: { revalidate: 3600, tags: [`cms-page-type-${pageTypeId}`] }
|
|
105
|
+
});
|
|
106
|
+
if (!res.ok) return null;
|
|
107
|
+
const json = await res.json();
|
|
108
|
+
return json.data ?? null;
|
|
109
|
+
} catch {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
async function getPageTypes() {
|
|
114
|
+
const { baseUrl, apiKey, appId } = cfg();
|
|
115
|
+
if (!baseUrl || !apiKey || !appId) return [];
|
|
116
|
+
try {
|
|
117
|
+
const res = await fetch(`${baseUrl}/api/v1/${appId}/page-types`, {
|
|
118
|
+
headers: headers(),
|
|
119
|
+
next: { revalidate: 3600, tags: [`cms-page-types-${appId}`] }
|
|
120
|
+
});
|
|
121
|
+
if (!res.ok) return [];
|
|
122
|
+
const json = await res.json();
|
|
123
|
+
return json.data ?? [];
|
|
124
|
+
} catch {
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
async function getSiteStructure() {
|
|
129
|
+
const { baseUrl, apiKey, appId } = cfg();
|
|
130
|
+
if (!baseUrl || !apiKey || !appId) return null;
|
|
131
|
+
try {
|
|
132
|
+
const res = await fetch(`${baseUrl}/api/v1/${appId}/site-structure`, {
|
|
133
|
+
headers: headers(),
|
|
134
|
+
next: {
|
|
135
|
+
revalidate: 300,
|
|
136
|
+
tags: [`site-structure-${appId}`]
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
if (!res.ok) return null;
|
|
140
|
+
const json = await res.json();
|
|
141
|
+
return json.data ?? null;
|
|
142
|
+
} catch {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
getPages,
|
|
148
|
+
getPage,
|
|
149
|
+
getBlogPosts,
|
|
150
|
+
getEvents,
|
|
151
|
+
getAnnouncements,
|
|
152
|
+
getPreviewPage,
|
|
153
|
+
getPageWithPreview,
|
|
154
|
+
getPageType,
|
|
155
|
+
getPageTypes,
|
|
156
|
+
getSiteStructure
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
var cmsClient = createCMSClient();
|
|
160
|
+
|
|
161
|
+
exports.cmsClient = cmsClient;
|
|
162
|
+
exports.createCMSClient = createCMSClient;
|
|
163
|
+
//# sourceMappingURL=client.cjs.map
|
|
164
|
+
//# sourceMappingURL=client.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/client.ts"],"names":[],"mappings":"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","file":"client.cjs","sourcesContent":["/**\n * @sprintup-cms/sdk — Core Client\n *\n * Zero-dependency, framework-agnostic typed API client for SprintUp Forge CMS.\n *\n * @example\n * import { cmsClient } from '@sprintup-cms/sdk'\n * const page = await cmsClient.getPage('about')\n *\n * @example Custom instance\n * import { createCMSClient } from '@sprintup-cms/sdk'\n * const cms = createCMSClient({ baseUrl: '...', apiKey: '...', appId: '...' })\n */\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\nexport interface CMSBlock {\n id: string\n type: string\n label?: string\n locked?: boolean\n data?: Record<string, any>\n /** Legacy field — blocks created before v1.1 used `content` instead of `data` */\n content?: Record<string, any>\n order?: number\n}\n\nexport interface CMSPage {\n _id?: string\n slug: string\n title: string\n description?: string\n pageType?: string\n pageTypeId?: string\n variant?: string\n status: 'draft' | 'published' | 'archived'\n visibility?: 'public' | 'private' | 'password'\n blocks: CMSBlock[]\n publishedAt?: string\n updatedAt?: string\n seo?: {\n title?: string\n description?: string\n keywords?: string[]\n ogImage?: string\n noIndex?: boolean\n }\n}\n\nexport interface CMSPageTypeField {\n id: string\n name: string\n label: string\n fieldType:\n | 'text' | 'textarea' | 'richtext' | 'image' | 'url'\n | 'number' | 'boolean' | 'date' | 'select' | 'relation'\n | 'email' | 'phone' | 'slug' | 'color' | 'embed'\n | 'multi-select' | 'repeater' | 'file' | 'code'\n required?: boolean\n options?: string[]\n description?: string\n maxLength?: number\n multiple?: boolean\n targetPageTypeKey?: string\n}\n\nexport interface CMSPageTypeSection {\n id: string\n name: string\n label: string\n order: number\n locked?: boolean\n fields: CMSPageTypeField[]\n}\n\nexport interface CMSPageTypeVariant {\n key: string\n label: string\n description?: string\n visibleSections?: string[]\n}\n\nexport interface CMSPageType {\n _id: string\n name: string\n key: string\n description?: string\n icon?: string\n category: 'singleton' | 'collection' | 'global'\n contentCategory: 'singleton' | 'collection' | 'global'\n allowedRoles?: string[]\n variants: CMSPageTypeVariant[]\n sections: CMSPageTypeSection[]\n}\n\n// ── Site Structure ─────────────────────────────────────────────────────────────\n\nexport type CMSMenuItemType = 'page' | 'url' | 'dynamic'\n\nexport interface CMSMenuItem {\n id: string\n type: CMSMenuItemType\n label: string\n contentId?: string\n url?: string\n /** Resolved href — ready to pass to <a href> or <Link href>. Never null, falls back to \"#\". */\n href: string\n locked: boolean\n openInNewTab: boolean\n children: CMSMenuItem[]\n page?: {\n title?: string\n slug?: string\n seoTitle?: string\n seoDescription?: string\n }\n}\n\nexport interface CMSPageTreeNode {\n id: string\n contentId: string\n parentId: string | null\n order: number\n locked: boolean\n visible?: boolean\n page?: {\n title?: string\n slug?: string\n seoTitle?: string\n seoDescription?: string\n }\n}\n\nexport interface CMSFooterGroup {\n id: string\n heading: string\n headingUrl?: string\n locked: boolean\n links: CMSMenuItem[]\n}\n\nexport interface CMSSiteStructure {\n appId: string\n pageTree: CMSPageTreeNode[]\n menus: {\n header: CMSMenuItem[]\n footer: CMSFooterGroup[]\n footerBottom: CMSMenuItem[]\n sidebar: CMSMenuItem[]\n [slot: string]: CMSMenuItem[] | CMSFooterGroup[]\n }\n updatedAt?: string\n}\n\n// ── Pagination & Responses ─────────────────────────────────────────────────────\n\nexport interface CMSListMeta {\n total: number\n page: number\n perPage: number\n totalPages: number\n}\n\nexport interface CMSListResponse<T = CMSPage> {\n data: T[]\n meta: CMSListMeta\n appId: string\n}\n\nexport interface CMSSingleResponse<T = CMSPage> {\n data: T\n}\n\n// ── Client Options ─────────────────────────────────────────────────────────────\n\nexport interface CMSClientOptions {\n /** Base URL of your Forge CMS instance, e.g. https://cms.yourschool.io */\n baseUrl?: string\n /** API key generated in CMS Admin → API Keys. Server-side only. */\n apiKey?: string\n /** App ID from CMS Admin → Apps, e.g. \"school-website\" */\n appId?: string\n}\n\nexport interface CMSGetPagesOptions {\n type?: string\n group?: string\n page?: number\n perPage?: number\n status?: 'published' | 'draft' | 'archived'\n}\n\n// ── Factory ───────────────────────────────────────────────────────────────────\n\nexport function createCMSClient(options?: CMSClientOptions) {\n /**\n * Resolve config lazily at request time — NOT at module/build time.\n * This prevents static prerendering crashes when env vars are absent during build.\n */\n function cfg() {\n return {\n baseUrl: (options?.baseUrl ?? process.env.NEXT_PUBLIC_CMS_URL ?? process.env.CMS_BASE_URL ?? '').replace(/\\/$/, ''),\n apiKey: options?.apiKey ?? process.env.CMS_API_KEY ?? '',\n appId: options?.appId ?? process.env.CMS_APP_ID ?? '',\n }\n }\n\n function headers() {\n return { 'X-CMS-API-Key': cfg().apiKey, 'Content-Type': 'application/json' }\n }\n\n // ── Pages ──────────────────────────────────────────────────────────────────\n\n async function getPages(params?: CMSGetPagesOptions): Promise<CMSPage[]> {\n const { baseUrl, apiKey, appId } = cfg()\n if (!baseUrl || !apiKey || !appId) {\n console.warn('[sprintup-cms] Missing CMS_BASE_URL / CMS_API_KEY / CMS_APP_ID — returning []')\n return []\n }\n try {\n const qs = new URLSearchParams()\n if (params?.type) qs.set('type', params.type)\n if (params?.group) qs.set('group', params.group)\n if (params?.page) qs.set('page', String(params.page))\n if (params?.perPage) qs.set('perPage', String(params.perPage))\n const url = `${baseUrl}/api/v1/${appId}/pages${qs.size ? `?${qs}` : ''}`\n const res = await fetch(url, {\n headers: headers(),\n next: { revalidate: 60, tags: [`cms-pages-${appId}`] },\n } as RequestInit)\n if (!res.ok) { console.error(`[sprintup-cms] getPages (${res.status})`); return [] }\n const json: CMSListResponse = await res.json()\n return json.data ?? []\n } catch (err) {\n console.error('[sprintup-cms] getPages error:', err)\n return []\n }\n }\n\n async function getPage(slug: string): Promise<CMSPage | null> {\n const { baseUrl, apiKey, appId } = cfg()\n if (!baseUrl || !apiKey || !appId) {\n console.warn('[sprintup-cms] Missing config — returning null')\n return null\n }\n try {\n const res = await fetch(`${baseUrl}/api/v1/${appId}/pages/${slug}`, {\n headers: headers(),\n next: { revalidate: 60, tags: [`cms-page-${slug}`, `cms-pages-${appId}`] },\n } as RequestInit)\n if (res.status === 404) return null\n if (!res.ok) { console.error(`[sprintup-cms] getPage \"${slug}\" (${res.status})`); return null }\n const json: CMSSingleResponse = await res.json()\n return json.data ?? null\n } catch (err) {\n console.error(`[sprintup-cms] getPage \"${slug}\" error:`, err)\n return null\n }\n }\n\n async function getBlogPosts(): Promise<CMSPage[]> {\n return getPages({ type: 'blog-post' })\n }\n\n async function getEvents(): Promise<CMSPage[]> {\n return getPages({ type: 'event-page' })\n }\n\n async function getAnnouncements(): Promise<CMSPage[]> {\n return getPages({ type: 'announcement-page' })\n }\n\n // ── Preview ────────────────────────────────────────────────────────────────\n\n async function getPreviewPage(token: string): Promise<CMSPage | null> {\n const { baseUrl, appId } = cfg()\n if (!baseUrl || !appId) return null\n try {\n const res = await fetch(`${baseUrl}/api/v1/${appId}/preview?token=${encodeURIComponent(token)}`, {\n cache: 'no-store',\n })\n if (!res.ok) return null\n const json = await res.json()\n return json.data ?? null\n } catch {\n return null\n }\n }\n\n async function getPageWithPreview(slug: string, previewToken?: string | null): Promise<CMSPage | null> {\n if (previewToken) {\n const preview = await getPreviewPage(previewToken)\n if (preview?.slug === slug) return preview\n }\n return getPage(slug)\n }\n\n // ── Page Types ─────────────────────────────────────────────────────────────\n\n async function getPageType(pageTypeId: string): Promise<CMSPageType | null> {\n const { baseUrl, apiKey, appId } = cfg()\n if (!baseUrl || !apiKey || !appId || !pageTypeId) return null\n try {\n const res = await fetch(`${baseUrl}/api/v1/${appId}/page-types/${pageTypeId}`, {\n headers: headers(),\n next: { revalidate: 3600, tags: [`cms-page-type-${pageTypeId}`] },\n } as RequestInit)\n if (!res.ok) return null\n const json = await res.json()\n return json.data ?? null\n } catch {\n return null\n }\n }\n\n async function getPageTypes(): Promise<CMSPageType[]> {\n const { baseUrl, apiKey, appId } = cfg()\n if (!baseUrl || !apiKey || !appId) return []\n try {\n const res = await fetch(`${baseUrl}/api/v1/${appId}/page-types`, {\n headers: headers(),\n next: { revalidate: 3600, tags: [`cms-page-types-${appId}`] },\n } as RequestInit)\n if (!res.ok) return []\n const json = await res.json()\n return json.data ?? []\n } catch {\n return []\n }\n }\n\n // ── Site Structure ─────────────────────────────────────────────────────────\n\n async function getSiteStructure(): Promise<CMSSiteStructure | null> {\n const { baseUrl, apiKey, appId } = cfg()\n if (!baseUrl || !apiKey || !appId) return null\n try {\n const res = await fetch(`${baseUrl}/api/v1/${appId}/site-structure`, {\n headers: headers(),\n next: {\n revalidate: 300,\n tags: [`site-structure-${appId}`],\n },\n } as RequestInit)\n if (!res.ok) return null\n const json = await res.json()\n return json.data ?? null\n } catch {\n return null\n }\n }\n\n return {\n getPages,\n getPage,\n getBlogPosts,\n getEvents,\n getAnnouncements,\n getPreviewPage,\n getPageWithPreview,\n getPageType,\n getPageTypes,\n getSiteStructure,\n }\n}\n\n// ── Default singleton ─────────────────────────────────────────────────────────\n\n/** Pre-configured singleton. Reads env vars lazily at request time. */\nexport const cmsClient = createCMSClient()\n"]}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @sprintup-cms/sdk — Core Client
|
|
3
|
+
*
|
|
4
|
+
* Zero-dependency, framework-agnostic typed API client for SprintUp Forge CMS.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* import { cmsClient } from '@sprintup-cms/sdk'
|
|
8
|
+
* const page = await cmsClient.getPage('about')
|
|
9
|
+
*
|
|
10
|
+
* @example Custom instance
|
|
11
|
+
* import { createCMSClient } from '@sprintup-cms/sdk'
|
|
12
|
+
* const cms = createCMSClient({ baseUrl: '...', apiKey: '...', appId: '...' })
|
|
13
|
+
*/
|
|
14
|
+
interface CMSBlock {
|
|
15
|
+
id: string;
|
|
16
|
+
type: string;
|
|
17
|
+
label?: string;
|
|
18
|
+
locked?: boolean;
|
|
19
|
+
data?: Record<string, any>;
|
|
20
|
+
/** Legacy field — blocks created before v1.1 used `content` instead of `data` */
|
|
21
|
+
content?: Record<string, any>;
|
|
22
|
+
order?: number;
|
|
23
|
+
}
|
|
24
|
+
interface CMSPage {
|
|
25
|
+
_id?: string;
|
|
26
|
+
slug: string;
|
|
27
|
+
title: string;
|
|
28
|
+
description?: string;
|
|
29
|
+
pageType?: string;
|
|
30
|
+
pageTypeId?: string;
|
|
31
|
+
variant?: string;
|
|
32
|
+
status: 'draft' | 'published' | 'archived';
|
|
33
|
+
visibility?: 'public' | 'private' | 'password';
|
|
34
|
+
blocks: CMSBlock[];
|
|
35
|
+
publishedAt?: string;
|
|
36
|
+
updatedAt?: string;
|
|
37
|
+
seo?: {
|
|
38
|
+
title?: string;
|
|
39
|
+
description?: string;
|
|
40
|
+
keywords?: string[];
|
|
41
|
+
ogImage?: string;
|
|
42
|
+
noIndex?: boolean;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
interface CMSPageTypeField {
|
|
46
|
+
id: string;
|
|
47
|
+
name: string;
|
|
48
|
+
label: string;
|
|
49
|
+
fieldType: 'text' | 'textarea' | 'richtext' | 'image' | 'url' | 'number' | 'boolean' | 'date' | 'select' | 'relation' | 'email' | 'phone' | 'slug' | 'color' | 'embed' | 'multi-select' | 'repeater' | 'file' | 'code';
|
|
50
|
+
required?: boolean;
|
|
51
|
+
options?: string[];
|
|
52
|
+
description?: string;
|
|
53
|
+
maxLength?: number;
|
|
54
|
+
multiple?: boolean;
|
|
55
|
+
targetPageTypeKey?: string;
|
|
56
|
+
}
|
|
57
|
+
interface CMSPageTypeSection {
|
|
58
|
+
id: string;
|
|
59
|
+
name: string;
|
|
60
|
+
label: string;
|
|
61
|
+
order: number;
|
|
62
|
+
locked?: boolean;
|
|
63
|
+
fields: CMSPageTypeField[];
|
|
64
|
+
}
|
|
65
|
+
interface CMSPageTypeVariant {
|
|
66
|
+
key: string;
|
|
67
|
+
label: string;
|
|
68
|
+
description?: string;
|
|
69
|
+
visibleSections?: string[];
|
|
70
|
+
}
|
|
71
|
+
interface CMSPageType {
|
|
72
|
+
_id: string;
|
|
73
|
+
name: string;
|
|
74
|
+
key: string;
|
|
75
|
+
description?: string;
|
|
76
|
+
icon?: string;
|
|
77
|
+
category: 'singleton' | 'collection' | 'global';
|
|
78
|
+
contentCategory: 'singleton' | 'collection' | 'global';
|
|
79
|
+
allowedRoles?: string[];
|
|
80
|
+
variants: CMSPageTypeVariant[];
|
|
81
|
+
sections: CMSPageTypeSection[];
|
|
82
|
+
}
|
|
83
|
+
type CMSMenuItemType = 'page' | 'url' | 'dynamic';
|
|
84
|
+
interface CMSMenuItem {
|
|
85
|
+
id: string;
|
|
86
|
+
type: CMSMenuItemType;
|
|
87
|
+
label: string;
|
|
88
|
+
contentId?: string;
|
|
89
|
+
url?: string;
|
|
90
|
+
/** Resolved href — ready to pass to <a href> or <Link href>. Never null, falls back to "#". */
|
|
91
|
+
href: string;
|
|
92
|
+
locked: boolean;
|
|
93
|
+
openInNewTab: boolean;
|
|
94
|
+
children: CMSMenuItem[];
|
|
95
|
+
page?: {
|
|
96
|
+
title?: string;
|
|
97
|
+
slug?: string;
|
|
98
|
+
seoTitle?: string;
|
|
99
|
+
seoDescription?: string;
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
interface CMSPageTreeNode {
|
|
103
|
+
id: string;
|
|
104
|
+
contentId: string;
|
|
105
|
+
parentId: string | null;
|
|
106
|
+
order: number;
|
|
107
|
+
locked: boolean;
|
|
108
|
+
visible?: boolean;
|
|
109
|
+
page?: {
|
|
110
|
+
title?: string;
|
|
111
|
+
slug?: string;
|
|
112
|
+
seoTitle?: string;
|
|
113
|
+
seoDescription?: string;
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
interface CMSFooterGroup {
|
|
117
|
+
id: string;
|
|
118
|
+
heading: string;
|
|
119
|
+
headingUrl?: string;
|
|
120
|
+
locked: boolean;
|
|
121
|
+
links: CMSMenuItem[];
|
|
122
|
+
}
|
|
123
|
+
interface CMSSiteStructure {
|
|
124
|
+
appId: string;
|
|
125
|
+
pageTree: CMSPageTreeNode[];
|
|
126
|
+
menus: {
|
|
127
|
+
header: CMSMenuItem[];
|
|
128
|
+
footer: CMSFooterGroup[];
|
|
129
|
+
footerBottom: CMSMenuItem[];
|
|
130
|
+
sidebar: CMSMenuItem[];
|
|
131
|
+
[slot: string]: CMSMenuItem[] | CMSFooterGroup[];
|
|
132
|
+
};
|
|
133
|
+
updatedAt?: string;
|
|
134
|
+
}
|
|
135
|
+
interface CMSListMeta {
|
|
136
|
+
total: number;
|
|
137
|
+
page: number;
|
|
138
|
+
perPage: number;
|
|
139
|
+
totalPages: number;
|
|
140
|
+
}
|
|
141
|
+
interface CMSListResponse<T = CMSPage> {
|
|
142
|
+
data: T[];
|
|
143
|
+
meta: CMSListMeta;
|
|
144
|
+
appId: string;
|
|
145
|
+
}
|
|
146
|
+
interface CMSSingleResponse<T = CMSPage> {
|
|
147
|
+
data: T;
|
|
148
|
+
}
|
|
149
|
+
interface CMSClientOptions {
|
|
150
|
+
/** Base URL of your Forge CMS instance, e.g. https://cms.yourschool.io */
|
|
151
|
+
baseUrl?: string;
|
|
152
|
+
/** API key generated in CMS Admin → API Keys. Server-side only. */
|
|
153
|
+
apiKey?: string;
|
|
154
|
+
/** App ID from CMS Admin → Apps, e.g. "school-website" */
|
|
155
|
+
appId?: string;
|
|
156
|
+
}
|
|
157
|
+
interface CMSGetPagesOptions {
|
|
158
|
+
type?: string;
|
|
159
|
+
group?: string;
|
|
160
|
+
page?: number;
|
|
161
|
+
perPage?: number;
|
|
162
|
+
status?: 'published' | 'draft' | 'archived';
|
|
163
|
+
}
|
|
164
|
+
declare function createCMSClient(options?: CMSClientOptions): {
|
|
165
|
+
getPages: (params?: CMSGetPagesOptions) => Promise<CMSPage[]>;
|
|
166
|
+
getPage: (slug: string) => Promise<CMSPage | null>;
|
|
167
|
+
getBlogPosts: () => Promise<CMSPage[]>;
|
|
168
|
+
getEvents: () => Promise<CMSPage[]>;
|
|
169
|
+
getAnnouncements: () => Promise<CMSPage[]>;
|
|
170
|
+
getPreviewPage: (token: string) => Promise<CMSPage | null>;
|
|
171
|
+
getPageWithPreview: (slug: string, previewToken?: string | null) => Promise<CMSPage | null>;
|
|
172
|
+
getPageType: (pageTypeId: string) => Promise<CMSPageType | null>;
|
|
173
|
+
getPageTypes: () => Promise<CMSPageType[]>;
|
|
174
|
+
getSiteStructure: () => Promise<CMSSiteStructure | null>;
|
|
175
|
+
};
|
|
176
|
+
/** Pre-configured singleton. Reads env vars lazily at request time. */
|
|
177
|
+
declare const cmsClient: {
|
|
178
|
+
getPages: (params?: CMSGetPagesOptions) => Promise<CMSPage[]>;
|
|
179
|
+
getPage: (slug: string) => Promise<CMSPage | null>;
|
|
180
|
+
getBlogPosts: () => Promise<CMSPage[]>;
|
|
181
|
+
getEvents: () => Promise<CMSPage[]>;
|
|
182
|
+
getAnnouncements: () => Promise<CMSPage[]>;
|
|
183
|
+
getPreviewPage: (token: string) => Promise<CMSPage | null>;
|
|
184
|
+
getPageWithPreview: (slug: string, previewToken?: string | null) => Promise<CMSPage | null>;
|
|
185
|
+
getPageType: (pageTypeId: string) => Promise<CMSPageType | null>;
|
|
186
|
+
getPageTypes: () => Promise<CMSPageType[]>;
|
|
187
|
+
getSiteStructure: () => Promise<CMSSiteStructure | null>;
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
export { type CMSBlock, type CMSClientOptions, type CMSFooterGroup, type CMSGetPagesOptions, type CMSListMeta, type CMSListResponse, type CMSMenuItem, type CMSMenuItemType, type CMSPage, type CMSPageTreeNode, type CMSPageType, type CMSPageTypeField, type CMSPageTypeSection, type CMSPageTypeVariant, type CMSSingleResponse, type CMSSiteStructure, cmsClient, createCMSClient };
|