payload-mcp-toolkit 0.2.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/LICENSE +21 -0
- package/README.md +133 -0
- package/dist/__tests__/introspection.test.js +364 -0
- package/dist/__tests__/introspection.test.js.map +1 -0
- package/dist/__tests__/url-validator.test.js +326 -0
- package/dist/__tests__/url-validator.test.js.map +1 -0
- package/dist/draft-workflow.d.ts +60 -0
- package/dist/draft-workflow.js +93 -0
- package/dist/draft-workflow.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +142 -0
- package/dist/index.js.map +1 -0
- package/dist/introspection.d.ts +23 -0
- package/dist/introspection.js +238 -0
- package/dist/introspection.js.map +1 -0
- package/dist/prompts.d.ts +21 -0
- package/dist/prompts.js +215 -0
- package/dist/prompts.js.map +1 -0
- package/dist/rate-limiter.d.ts +25 -0
- package/dist/rate-limiter.js +51 -0
- package/dist/rate-limiter.js.map +1 -0
- package/dist/resources.d.ts +18 -0
- package/dist/resources.js +77 -0
- package/dist/resources.js.map +1 -0
- package/dist/tools/compose-helpers.d.ts +117 -0
- package/dist/tools/compose-helpers.js +236 -0
- package/dist/tools/compose-helpers.js.map +1 -0
- package/dist/tools/compose-layout.d.ts +139 -0
- package/dist/tools/compose-layout.js +61 -0
- package/dist/tools/compose-layout.js.map +1 -0
- package/dist/tools/patch-layout.d.ts +107 -0
- package/dist/tools/patch-layout.js +123 -0
- package/dist/tools/patch-layout.js.map +1 -0
- package/dist/tools/publish-draft.d.ts +24 -0
- package/dist/tools/publish-draft.js +69 -0
- package/dist/tools/publish-draft.js.map +1 -0
- package/dist/tools/resolve-reference.d.ts +31 -0
- package/dist/tools/resolve-reference.js +169 -0
- package/dist/tools/resolve-reference.js.map +1 -0
- package/dist/tools/safe-delete.d.ts +37 -0
- package/dist/tools/safe-delete.js +161 -0
- package/dist/tools/safe-delete.js.map +1 -0
- package/dist/tools/schedule-publish.d.ts +49 -0
- package/dist/tools/schedule-publish.js +120 -0
- package/dist/tools/schedule-publish.js.map +1 -0
- package/dist/tools/search-content.d.ts +43 -0
- package/dist/tools/search-content.js +210 -0
- package/dist/tools/search-content.js.map +1 -0
- package/dist/tools/update-document.d.ts +32 -0
- package/dist/tools/update-document.js +114 -0
- package/dist/tools/update-document.js.map +1 -0
- package/dist/tools/upload-media.d.ts +26 -0
- package/dist/tools/upload-media.js +115 -0
- package/dist/tools/upload-media.js.map +1 -0
- package/dist/tools/versions.d.ts +50 -0
- package/dist/tools/versions.js +159 -0
- package/dist/tools/versions.js.map +1 -0
- package/dist/types.d.ts +118 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/url-validator.d.ts +36 -0
- package/dist/url-validator.js +222 -0
- package/dist/url-validator.js.map +1 -0
- package/package.json +85 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 payload-mcp-toolkit contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# payload-mcp-toolkit
|
|
2
|
+
|
|
3
|
+
> Schema-aware MCP toolkit for Payload CMS — wraps the official [`@payloadcms/plugin-mcp`](https://github.com/payloadcms/payload/tree/main/packages/plugin-mcp) with introspected prompts, resources, draft workflow, and AI-friendly tools so non-technical editors can manage content via AI chat.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
The official Payload MCP plugin gives every collection a generic CRUD surface. That works, but an LLM driving it has no idea:
|
|
8
|
+
|
|
9
|
+
- which collections support drafts vs publish-immediately,
|
|
10
|
+
- which block types are valid inside which sections,
|
|
11
|
+
- which fields are searchable for resolving relationships,
|
|
12
|
+
- how to compose a page layout without trial and error.
|
|
13
|
+
|
|
14
|
+
`payload-mcp-toolkit` introspects the Payload config at boot, then layers schema-aware **prompts**, **resources**, and **tools** on top of the official plugin so an AI client (Claude Desktop, Claude API, any MCP-compatible chat) can drive your CMS confidently.
|
|
15
|
+
|
|
16
|
+
## What it adds
|
|
17
|
+
|
|
18
|
+
**Auto-generated prompts** (no setup required):
|
|
19
|
+
- `contentModelOverview` — every collection, fields, and relationships.
|
|
20
|
+
- `blockCompositionGuide` — section/leaf hierarchy and nesting rules.
|
|
21
|
+
- `draftWorkflowGuide` — which collections need `publishDraft` to go live.
|
|
22
|
+
|
|
23
|
+
**Auto-generated resources** (machine-readable JSON for the LLM):
|
|
24
|
+
- `blocks://catalog`, `collections://schema`, `collections://relationships`.
|
|
25
|
+
|
|
26
|
+
**Custom tools** (11 total):
|
|
27
|
+
|
|
28
|
+
*Authoring*
|
|
29
|
+
- `composePageLayout` — build a validated page layout from sections + leaves.
|
|
30
|
+
- `patchLayout` — surgically append/prepend/insertAt/replaceAt sections on a doc's block-array field without round-tripping the whole array. Safer than `updateDocument` for incremental layout edits.
|
|
31
|
+
- `updateDocument` — Local-API based update that survives the upload-field bug in the official plugin.
|
|
32
|
+
- `uploadMedia` — fetch a public HTTPS image, validate (SSRF-safe), create a Media doc.
|
|
33
|
+
|
|
34
|
+
*Discovery*
|
|
35
|
+
- `resolveReference` — search collections by name/title/slug for relationship IDs.
|
|
36
|
+
- `searchContent` — natural-language editor triage. Filter by `status`, `olderThanDays` / `newerThanDays`, `missingFields`, free-text `query`, scoped to one collection or all.
|
|
37
|
+
|
|
38
|
+
*Lifecycle / safety*
|
|
39
|
+
- `publishDraft` — flip `_status` from draft to published.
|
|
40
|
+
- `schedulePublish` — **bring your own scheduler.** Stamps a future `publishedAt` on a draft and leaves `_status: 'draft'`; it does **not** itself flip status at the appointed time. Auto-registered only for collections that have both drafts AND a `publishedAt` date field. To actually publish on schedule, you must wire up one of: a [Payload Jobs Queue scheduled task](https://payloadcms.com/docs/jobs-queue/scheduled-jobs), an external cron worker, or a `beforeRead` hook that resolves status on the fly. Without one of those, scheduled drafts stay drafts forever — the tool says so in its response, but it's still a footgun if you skim past it.
|
|
41
|
+
- `listVersions` — recent saved versions of a draft document with id/status/timestamp/displayName.
|
|
42
|
+
- `restoreVersion` — roll a document back to a saved version (creates a new version on top, so itself reversible).
|
|
43
|
+
- `safeDelete` — relationship-aware delete. Walks the introspected relationship graph, refuses with a structured impact summary if other documents reference the target. Override with `confirm: true` after reviewing.
|
|
44
|
+
|
|
45
|
+
**Draft workflow** wired into the official plugin's `mcpCollections`:
|
|
46
|
+
- Disables raw `update` for `always-draft` collections so clients go through `publishDraft` (or `patchLayout` / `updateDocument`, both of which preserve draft semantics).
|
|
47
|
+
- Appends preview URLs to draft responses (path prefixes are configurable via `previewPaths`).
|
|
48
|
+
|
|
49
|
+
## Install
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pnpm add payload-mcp-toolkit @payloadcms/plugin-mcp
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Peer dependencies: `payload` ^3, `@payloadcms/plugin-mcp` ^3, `zod` ^3.
|
|
56
|
+
|
|
57
|
+
## Use
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
// payload.config.ts
|
|
61
|
+
import { contentToolkitPlugin } from 'payload-mcp-toolkit'
|
|
62
|
+
|
|
63
|
+
export default buildConfig({
|
|
64
|
+
// ...your collections, blocks, globals
|
|
65
|
+
plugins: [
|
|
66
|
+
contentToolkitPlugin({
|
|
67
|
+
siteUrl: process.env.SITE_URL!,
|
|
68
|
+
previewSecret: process.env.PREVIEW_SECRET!,
|
|
69
|
+
previewPaths: {
|
|
70
|
+
pages: '', // pages live at /
|
|
71
|
+
posts: '/blog', // posts live at /blog/:slug
|
|
72
|
+
},
|
|
73
|
+
draftBehavior: {
|
|
74
|
+
pages: 'always-draft',
|
|
75
|
+
posts: 'always-draft',
|
|
76
|
+
},
|
|
77
|
+
domainPrompts: [
|
|
78
|
+
// Optional site-specific vocabulary — see examples/
|
|
79
|
+
],
|
|
80
|
+
}),
|
|
81
|
+
],
|
|
82
|
+
})
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
That's it. The toolkit reads your Payload config and registers everything against the official MCP plugin. Connect any MCP client to your Payload server and the LLM will see the prompts, resources, and tools.
|
|
86
|
+
|
|
87
|
+
See `examples/angels-config.example.ts` for a fully-worked domain-prompt setup from a real-world site.
|
|
88
|
+
|
|
89
|
+
## Configuration reference
|
|
90
|
+
|
|
91
|
+
| Option | Type | Description |
|
|
92
|
+
|---|---|---|
|
|
93
|
+
| `siteUrl` | `string` | Base URL used to construct preview URLs. |
|
|
94
|
+
| `previewSecret` | `string` | Secret embedded in the preview URL query. |
|
|
95
|
+
| `previewPaths` | `Record<string, string>` | Per-collection URL path prefix. Defaults to `/{slug}` if omitted. Use `''` for collections at the site root. |
|
|
96
|
+
| `draftBehavior` | `Record<string, 'always-draft' \| 'always-publish'>` | Override the default draft behavior per collection. |
|
|
97
|
+
| `domainPrompts` | `DomainPrompt[]` | Custom prompts that teach the AI site-specific vocabulary. |
|
|
98
|
+
| `mediaUpload.maxFileSize` | `number` | Max bytes for `uploadMedia` (default 10MB). |
|
|
99
|
+
| `mediaUpload.collectionSlug` | `string` | Media collection slug (default `media`). |
|
|
100
|
+
| `excludeCollections` | `string[]` | Collection slugs to hide from MCP. |
|
|
101
|
+
| `excludeGlobals` | `string[]` | Global slugs to hide from MCP. |
|
|
102
|
+
|
|
103
|
+
## Development
|
|
104
|
+
|
|
105
|
+
This package follows the [official Payload 3 plugin template](https://github.com/payloadcms/payload/tree/main/templates/plugin) layout: source in `src/`, a fully-working Payload + Next.js app in `dev/`, source-export `package.json` so the dev harness consumes the plugin directly without a build step.
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
pnpm install
|
|
109
|
+
cp dev/.env.example dev/.env
|
|
110
|
+
pnpm dev # boot dev/ Next.js + Payload at http://localhost:3000
|
|
111
|
+
pnpm test # vitest — runs introspection unit tests
|
|
112
|
+
pnpm build # produce dist/ for npm publish
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
The dev harness ships with a realistic CMS schema:
|
|
116
|
+
- `Pages` — block-based layout (FullWidth, TwoColumn, CtaBanner, HeadingOnly), drafts enabled.
|
|
117
|
+
- `Posts` — title/slug/excerpt/content/cover/category/authors/tags/SEO, drafts enabled.
|
|
118
|
+
- `Authors`, `Categories`, `Media`, `Users` — taxonomy + auth.
|
|
119
|
+
- `SiteSettings` — global with site name, logo, social, footer.
|
|
120
|
+
- 5 leaf blocks (Heading, RichText, Image, ButtonGroup, Quote) and 4 section blocks.
|
|
121
|
+
|
|
122
|
+
Seed sample content:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# Generate the admin import map first time:
|
|
126
|
+
pnpm dev:generate-importmap
|
|
127
|
+
|
|
128
|
+
# Then visit http://localhost:3000/admin and create your first user.
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## License
|
|
132
|
+
|
|
133
|
+
MIT
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { introspectCollection, introspectCollections, introspectBlocks, buildRelationshipGraph } from '../introspection';
|
|
3
|
+
// ─── Sample schema (kept inline so the test is self-contained) ─────
|
|
4
|
+
const Media = {
|
|
5
|
+
slug: 'media',
|
|
6
|
+
upload: true,
|
|
7
|
+
fields: [
|
|
8
|
+
{
|
|
9
|
+
name: 'alt',
|
|
10
|
+
type: 'text',
|
|
11
|
+
required: true
|
|
12
|
+
}
|
|
13
|
+
]
|
|
14
|
+
};
|
|
15
|
+
const Categories = {
|
|
16
|
+
slug: 'categories',
|
|
17
|
+
fields: [
|
|
18
|
+
{
|
|
19
|
+
name: 'name',
|
|
20
|
+
type: 'text',
|
|
21
|
+
required: true
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: 'slug',
|
|
25
|
+
type: 'text',
|
|
26
|
+
required: true
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
};
|
|
30
|
+
const Authors = {
|
|
31
|
+
slug: 'authors',
|
|
32
|
+
fields: [
|
|
33
|
+
{
|
|
34
|
+
name: 'name',
|
|
35
|
+
type: 'text',
|
|
36
|
+
required: true
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'slug',
|
|
40
|
+
type: 'text',
|
|
41
|
+
required: true
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: 'avatar',
|
|
45
|
+
type: 'upload',
|
|
46
|
+
relationTo: 'media'
|
|
47
|
+
}
|
|
48
|
+
]
|
|
49
|
+
};
|
|
50
|
+
// Leaf blocks
|
|
51
|
+
const Heading = {
|
|
52
|
+
slug: 'heading',
|
|
53
|
+
fields: [
|
|
54
|
+
{
|
|
55
|
+
name: 'text',
|
|
56
|
+
type: 'text',
|
|
57
|
+
required: true
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: 'level',
|
|
61
|
+
type: 'select',
|
|
62
|
+
options: [
|
|
63
|
+
'h1',
|
|
64
|
+
'h2',
|
|
65
|
+
'h3'
|
|
66
|
+
],
|
|
67
|
+
defaultValue: 'h2'
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: 'align',
|
|
71
|
+
type: 'select',
|
|
72
|
+
options: [
|
|
73
|
+
'left',
|
|
74
|
+
'center',
|
|
75
|
+
'right'
|
|
76
|
+
],
|
|
77
|
+
defaultValue: 'left'
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
};
|
|
81
|
+
const RichText = {
|
|
82
|
+
slug: 'richText',
|
|
83
|
+
fields: [
|
|
84
|
+
{
|
|
85
|
+
name: 'content',
|
|
86
|
+
type: 'richText'
|
|
87
|
+
}
|
|
88
|
+
]
|
|
89
|
+
};
|
|
90
|
+
const ImageBlock = {
|
|
91
|
+
slug: 'image',
|
|
92
|
+
fields: [
|
|
93
|
+
{
|
|
94
|
+
name: 'image',
|
|
95
|
+
type: 'upload',
|
|
96
|
+
relationTo: 'media',
|
|
97
|
+
required: true
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
name: 'caption',
|
|
101
|
+
type: 'text'
|
|
102
|
+
}
|
|
103
|
+
]
|
|
104
|
+
};
|
|
105
|
+
const allLeafBlocks = [
|
|
106
|
+
Heading,
|
|
107
|
+
RichText,
|
|
108
|
+
ImageBlock
|
|
109
|
+
];
|
|
110
|
+
// Section blocks
|
|
111
|
+
const FullWidth = {
|
|
112
|
+
slug: 'fullWidth',
|
|
113
|
+
fields: [
|
|
114
|
+
{
|
|
115
|
+
name: 'content',
|
|
116
|
+
type: 'blocks',
|
|
117
|
+
blocks: allLeafBlocks
|
|
118
|
+
}
|
|
119
|
+
]
|
|
120
|
+
};
|
|
121
|
+
const HeadingOnly = {
|
|
122
|
+
slug: 'headingOnly',
|
|
123
|
+
fields: [
|
|
124
|
+
{
|
|
125
|
+
name: 'content',
|
|
126
|
+
type: 'blocks',
|
|
127
|
+
maxRows: 1,
|
|
128
|
+
blocks: [
|
|
129
|
+
Heading
|
|
130
|
+
]
|
|
131
|
+
}
|
|
132
|
+
]
|
|
133
|
+
};
|
|
134
|
+
const CtaBanner = {
|
|
135
|
+
slug: 'ctaBanner',
|
|
136
|
+
fields: [
|
|
137
|
+
{
|
|
138
|
+
name: 'headline',
|
|
139
|
+
type: 'text',
|
|
140
|
+
required: true
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
name: 'buttonLabel',
|
|
144
|
+
type: 'text'
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
name: 'buttonHref',
|
|
148
|
+
type: 'text'
|
|
149
|
+
}
|
|
150
|
+
]
|
|
151
|
+
};
|
|
152
|
+
const allSectionBlocks = [
|
|
153
|
+
FullWidth,
|
|
154
|
+
HeadingOnly,
|
|
155
|
+
CtaBanner
|
|
156
|
+
];
|
|
157
|
+
const Posts = {
|
|
158
|
+
slug: 'posts',
|
|
159
|
+
versions: {
|
|
160
|
+
drafts: true
|
|
161
|
+
},
|
|
162
|
+
fields: [
|
|
163
|
+
{
|
|
164
|
+
name: 'title',
|
|
165
|
+
type: 'text',
|
|
166
|
+
required: true
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
name: 'slug',
|
|
170
|
+
type: 'text',
|
|
171
|
+
required: true
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
name: 'featured',
|
|
175
|
+
type: 'checkbox'
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
name: 'category',
|
|
179
|
+
type: 'relationship',
|
|
180
|
+
relationTo: 'categories'
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
name: 'authors',
|
|
184
|
+
type: 'relationship',
|
|
185
|
+
relationTo: 'authors',
|
|
186
|
+
hasMany: true
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
name: 'coverImage',
|
|
190
|
+
type: 'upload',
|
|
191
|
+
relationTo: 'media'
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
name: 'tags',
|
|
195
|
+
type: 'array',
|
|
196
|
+
fields: [
|
|
197
|
+
{
|
|
198
|
+
name: 'tag',
|
|
199
|
+
type: 'text'
|
|
200
|
+
}
|
|
201
|
+
]
|
|
202
|
+
}
|
|
203
|
+
]
|
|
204
|
+
};
|
|
205
|
+
const Pages = {
|
|
206
|
+
slug: 'pages',
|
|
207
|
+
versions: {
|
|
208
|
+
drafts: true
|
|
209
|
+
},
|
|
210
|
+
fields: [
|
|
211
|
+
{
|
|
212
|
+
type: 'tabs',
|
|
213
|
+
tabs: [
|
|
214
|
+
{
|
|
215
|
+
name: 'hero',
|
|
216
|
+
label: 'Hero',
|
|
217
|
+
fields: [
|
|
218
|
+
{
|
|
219
|
+
name: 'heroTitle',
|
|
220
|
+
type: 'text'
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
name: 'heroSize',
|
|
224
|
+
type: 'select',
|
|
225
|
+
options: [
|
|
226
|
+
'small',
|
|
227
|
+
'medium',
|
|
228
|
+
'large'
|
|
229
|
+
],
|
|
230
|
+
defaultValue: 'medium'
|
|
231
|
+
}
|
|
232
|
+
]
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
label: 'Content',
|
|
236
|
+
fields: [
|
|
237
|
+
{
|
|
238
|
+
name: 'slug',
|
|
239
|
+
type: 'text',
|
|
240
|
+
required: true
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
name: 'layout',
|
|
244
|
+
type: 'blocks',
|
|
245
|
+
blocks: allSectionBlocks
|
|
246
|
+
}
|
|
247
|
+
]
|
|
248
|
+
}
|
|
249
|
+
]
|
|
250
|
+
}
|
|
251
|
+
]
|
|
252
|
+
};
|
|
253
|
+
// ─── Tests ─────────────────────────────────────────────────────────
|
|
254
|
+
describe('introspectCollection', ()=>{
|
|
255
|
+
it('extracts Posts collection fields, relationships, and draft status', ()=>{
|
|
256
|
+
const schema = introspectCollection(Posts);
|
|
257
|
+
expect(schema.slug).toBe('posts');
|
|
258
|
+
expect(schema.hasDrafts).toBe(true);
|
|
259
|
+
const fieldNames = schema.fields.map((f)=>f.name);
|
|
260
|
+
expect(fieldNames).toContain('title');
|
|
261
|
+
expect(fieldNames).toContain('slug');
|
|
262
|
+
expect(fieldNames).toContain('featured');
|
|
263
|
+
expect(fieldNames).toContain('tags');
|
|
264
|
+
const relFieldNames = schema.relationships.map((r)=>r.fieldName);
|
|
265
|
+
expect(relFieldNames).toContain('category');
|
|
266
|
+
expect(relFieldNames).toContain('authors');
|
|
267
|
+
const cover = schema.relationships.find((r)=>r.fieldName === 'coverImage');
|
|
268
|
+
expect(cover).toBeDefined();
|
|
269
|
+
expect(cover.relationTo).toBe('media');
|
|
270
|
+
expect(schema.searchableFields).toContain('title');
|
|
271
|
+
expect(schema.searchableFields).toContain('slug');
|
|
272
|
+
});
|
|
273
|
+
it('extracts Pages collection with tab-nested fields', ()=>{
|
|
274
|
+
const schema = introspectCollection(Pages);
|
|
275
|
+
expect(schema.slug).toBe('pages');
|
|
276
|
+
expect(schema.hasDrafts).toBe(true);
|
|
277
|
+
const fieldNames = schema.fields.map((f)=>f.name);
|
|
278
|
+
expect(fieldNames).toContain('heroTitle');
|
|
279
|
+
expect(fieldNames).toContain('slug');
|
|
280
|
+
expect(fieldNames).toContain('layout');
|
|
281
|
+
});
|
|
282
|
+
it('detects collections without draft support', ()=>{
|
|
283
|
+
const schema = introspectCollection(Categories);
|
|
284
|
+
expect(schema.hasDrafts).toBe(false);
|
|
285
|
+
});
|
|
286
|
+
it('extracts select field options from Pages heroSize', ()=>{
|
|
287
|
+
const schema = introspectCollection(Pages);
|
|
288
|
+
const heroSize = schema.fields.find((f)=>f.name === 'heroSize');
|
|
289
|
+
expect(heroSize).toBeDefined();
|
|
290
|
+
expect(heroSize.type).toBe('select');
|
|
291
|
+
expect(heroSize.options).toBeDefined();
|
|
292
|
+
expect(heroSize.options.length).toBe(3);
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
describe('introspectBlocks', ()=>{
|
|
296
|
+
it('discovers fullWidth accepts all leaf blocks (composable)', ()=>{
|
|
297
|
+
const catalog = introspectBlocks(allSectionBlocks, allLeafBlocks);
|
|
298
|
+
const fullWidth = catalog.sections.find((s)=>s.slug === 'fullWidth');
|
|
299
|
+
expect(fullWidth).toBeDefined();
|
|
300
|
+
expect(fullWidth.nestingType).toBe('composable');
|
|
301
|
+
expect(fullWidth.acceptedLeafSlugs.length).toBe(allLeafBlocks.length);
|
|
302
|
+
});
|
|
303
|
+
it('marks ctaBanner as fixed (no nested blocks)', ()=>{
|
|
304
|
+
const catalog = introspectBlocks(allSectionBlocks, allLeafBlocks);
|
|
305
|
+
const ctaBanner = catalog.sections.find((s)=>s.slug === 'ctaBanner');
|
|
306
|
+
expect(ctaBanner).toBeDefined();
|
|
307
|
+
expect(ctaBanner.nestingType).toBe('fixed');
|
|
308
|
+
expect(ctaBanner.acceptedLeafSlugs).toHaveLength(0);
|
|
309
|
+
});
|
|
310
|
+
it('marks headingOnly as constrained (single leaf type, maxRows: 1)', ()=>{
|
|
311
|
+
const catalog = introspectBlocks(allSectionBlocks, allLeafBlocks);
|
|
312
|
+
const headingOnly = catalog.sections.find((s)=>s.slug === 'headingOnly');
|
|
313
|
+
expect(headingOnly).toBeDefined();
|
|
314
|
+
expect(headingOnly.nestingType).toBe('constrained');
|
|
315
|
+
expect(headingOnly.acceptedLeafSlugs).toEqual([
|
|
316
|
+
'heading'
|
|
317
|
+
]);
|
|
318
|
+
expect(headingOnly.maxRows).toBe(1);
|
|
319
|
+
});
|
|
320
|
+
it('extracts all leaf blocks', ()=>{
|
|
321
|
+
const catalog = introspectBlocks(allSectionBlocks, allLeafBlocks);
|
|
322
|
+
expect(catalog.leaves).toHaveLength(3);
|
|
323
|
+
const leafSlugs = catalog.leaves.map((l)=>l.slug);
|
|
324
|
+
expect(leafSlugs).toEqual([
|
|
325
|
+
'heading',
|
|
326
|
+
'richText',
|
|
327
|
+
'image'
|
|
328
|
+
]);
|
|
329
|
+
});
|
|
330
|
+
it('extracts leaf block fields including select options', ()=>{
|
|
331
|
+
const catalog = introspectBlocks(allSectionBlocks, allLeafBlocks);
|
|
332
|
+
const heading = catalog.leaves.find((l)=>l.slug === 'heading');
|
|
333
|
+
expect(heading).toBeDefined();
|
|
334
|
+
const headingFieldNames = heading.fields.map((f)=>f.name);
|
|
335
|
+
expect(headingFieldNames).toEqual([
|
|
336
|
+
'text',
|
|
337
|
+
'level',
|
|
338
|
+
'align'
|
|
339
|
+
]);
|
|
340
|
+
const level = heading.fields.find((f)=>f.name === 'level');
|
|
341
|
+
expect(level.options).toBeDefined();
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
describe('buildRelationshipGraph', ()=>{
|
|
345
|
+
it('builds correct graph from sample collections', ()=>{
|
|
346
|
+
const schemas = introspectCollections([
|
|
347
|
+
Posts,
|
|
348
|
+
Pages,
|
|
349
|
+
Categories,
|
|
350
|
+
Authors,
|
|
351
|
+
Media
|
|
352
|
+
]);
|
|
353
|
+
const edges = buildRelationshipGraph(schemas);
|
|
354
|
+
const postEdges = edges.filter((e)=>e.fromCollection === 'posts');
|
|
355
|
+
const postTargets = postEdges.map((e)=>e.toCollection);
|
|
356
|
+
expect(postTargets).toContain('categories');
|
|
357
|
+
expect(postTargets).toContain('authors');
|
|
358
|
+
expect(postTargets).toContain('media');
|
|
359
|
+
const authorEdges = edges.filter((e)=>e.fromCollection === 'authors');
|
|
360
|
+
expect(authorEdges.map((e)=>e.toCollection)).toContain('media');
|
|
361
|
+
});
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
//# sourceMappingURL=introspection.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/__tests__/introspection.test.ts"],"sourcesContent":["import { describe, it, expect } from 'vitest'\nimport type { Block, CollectionConfig } from 'payload'\nimport {\n introspectCollection,\n introspectCollections,\n introspectBlocks,\n buildRelationshipGraph,\n} from '../introspection'\n\n// ─── Sample schema (kept inline so the test is self-contained) ─────\n\nconst Media: CollectionConfig = {\n slug: 'media',\n upload: true,\n fields: [{ name: 'alt', type: 'text', required: true }],\n}\n\nconst Categories: CollectionConfig = {\n slug: 'categories',\n fields: [\n { name: 'name', type: 'text', required: true },\n { name: 'slug', type: 'text', required: true },\n ],\n}\n\nconst Authors: CollectionConfig = {\n slug: 'authors',\n fields: [\n { name: 'name', type: 'text', required: true },\n { name: 'slug', type: 'text', required: true },\n { name: 'avatar', type: 'upload', relationTo: 'media' },\n ],\n}\n\n// Leaf blocks\nconst Heading: Block = {\n slug: 'heading',\n fields: [\n { name: 'text', type: 'text', required: true },\n {\n name: 'level',\n type: 'select',\n options: ['h1', 'h2', 'h3'],\n defaultValue: 'h2',\n },\n {\n name: 'align',\n type: 'select',\n options: ['left', 'center', 'right'],\n defaultValue: 'left',\n },\n ],\n}\n\nconst RichText: Block = {\n slug: 'richText',\n fields: [{ name: 'content', type: 'richText' }],\n}\n\nconst ImageBlock: Block = {\n slug: 'image',\n fields: [\n { name: 'image', type: 'upload', relationTo: 'media', required: true },\n { name: 'caption', type: 'text' },\n ],\n}\n\nconst allLeafBlocks: Block[] = [Heading, RichText, ImageBlock]\n\n// Section blocks\nconst FullWidth: Block = {\n slug: 'fullWidth',\n fields: [\n {\n name: 'content',\n type: 'blocks',\n blocks: allLeafBlocks,\n },\n ],\n}\n\nconst HeadingOnly: Block = {\n slug: 'headingOnly',\n fields: [\n {\n name: 'content',\n type: 'blocks',\n maxRows: 1,\n blocks: [Heading],\n },\n ],\n}\n\nconst CtaBanner: Block = {\n slug: 'ctaBanner',\n fields: [\n { name: 'headline', type: 'text', required: true },\n { name: 'buttonLabel', type: 'text' },\n { name: 'buttonHref', type: 'text' },\n ],\n}\n\nconst allSectionBlocks: Block[] = [FullWidth, HeadingOnly, CtaBanner]\n\nconst Posts: CollectionConfig = {\n slug: 'posts',\n versions: { drafts: true },\n fields: [\n { name: 'title', type: 'text', required: true },\n { name: 'slug', type: 'text', required: true },\n { name: 'featured', type: 'checkbox' },\n { name: 'category', type: 'relationship', relationTo: 'categories' },\n {\n name: 'authors',\n type: 'relationship',\n relationTo: 'authors',\n hasMany: true,\n },\n { name: 'coverImage', type: 'upload', relationTo: 'media' },\n {\n name: 'tags',\n type: 'array',\n fields: [{ name: 'tag', type: 'text' }],\n },\n ],\n}\n\nconst Pages: CollectionConfig = {\n slug: 'pages',\n versions: { drafts: true },\n fields: [\n {\n type: 'tabs',\n tabs: [\n {\n name: 'hero',\n label: 'Hero',\n fields: [\n { name: 'heroTitle', type: 'text' },\n {\n name: 'heroSize',\n type: 'select',\n options: ['small', 'medium', 'large'],\n defaultValue: 'medium',\n },\n ],\n },\n {\n label: 'Content',\n fields: [\n { name: 'slug', type: 'text', required: true },\n { name: 'layout', type: 'blocks', blocks: allSectionBlocks },\n ],\n },\n ],\n },\n ],\n}\n\n// ─── Tests ─────────────────────────────────────────────────────────\n\ndescribe('introspectCollection', () => {\n it('extracts Posts collection fields, relationships, and draft status', () => {\n const schema = introspectCollection(Posts)\n\n expect(schema.slug).toBe('posts')\n expect(schema.hasDrafts).toBe(true)\n\n const fieldNames = schema.fields.map((f) => f.name)\n expect(fieldNames).toContain('title')\n expect(fieldNames).toContain('slug')\n expect(fieldNames).toContain('featured')\n expect(fieldNames).toContain('tags')\n\n const relFieldNames = schema.relationships.map((r) => r.fieldName)\n expect(relFieldNames).toContain('category')\n expect(relFieldNames).toContain('authors')\n\n const cover = schema.relationships.find((r) => r.fieldName === 'coverImage')\n expect(cover).toBeDefined()\n expect(cover!.relationTo).toBe('media')\n\n expect(schema.searchableFields).toContain('title')\n expect(schema.searchableFields).toContain('slug')\n })\n\n it('extracts Pages collection with tab-nested fields', () => {\n const schema = introspectCollection(Pages)\n\n expect(schema.slug).toBe('pages')\n expect(schema.hasDrafts).toBe(true)\n\n const fieldNames = schema.fields.map((f) => f.name)\n expect(fieldNames).toContain('heroTitle')\n expect(fieldNames).toContain('slug')\n expect(fieldNames).toContain('layout')\n })\n\n it('detects collections without draft support', () => {\n const schema = introspectCollection(Categories)\n expect(schema.hasDrafts).toBe(false)\n })\n\n it('extracts select field options from Pages heroSize', () => {\n const schema = introspectCollection(Pages)\n const heroSize = schema.fields.find((f) => f.name === 'heroSize')\n expect(heroSize).toBeDefined()\n expect(heroSize!.type).toBe('select')\n expect(heroSize!.options).toBeDefined()\n expect(heroSize!.options!.length).toBe(3)\n })\n})\n\ndescribe('introspectBlocks', () => {\n it('discovers fullWidth accepts all leaf blocks (composable)', () => {\n const catalog = introspectBlocks(allSectionBlocks, allLeafBlocks)\n\n const fullWidth = catalog.sections.find((s) => s.slug === 'fullWidth')\n expect(fullWidth).toBeDefined()\n expect(fullWidth!.nestingType).toBe('composable')\n expect(fullWidth!.acceptedLeafSlugs.length).toBe(allLeafBlocks.length)\n })\n\n it('marks ctaBanner as fixed (no nested blocks)', () => {\n const catalog = introspectBlocks(allSectionBlocks, allLeafBlocks)\n\n const ctaBanner = catalog.sections.find((s) => s.slug === 'ctaBanner')\n expect(ctaBanner).toBeDefined()\n expect(ctaBanner!.nestingType).toBe('fixed')\n expect(ctaBanner!.acceptedLeafSlugs).toHaveLength(0)\n })\n\n it('marks headingOnly as constrained (single leaf type, maxRows: 1)', () => {\n const catalog = introspectBlocks(allSectionBlocks, allLeafBlocks)\n\n const headingOnly = catalog.sections.find((s) => s.slug === 'headingOnly')\n expect(headingOnly).toBeDefined()\n expect(headingOnly!.nestingType).toBe('constrained')\n expect(headingOnly!.acceptedLeafSlugs).toEqual(['heading'])\n expect(headingOnly!.maxRows).toBe(1)\n })\n\n it('extracts all leaf blocks', () => {\n const catalog = introspectBlocks(allSectionBlocks, allLeafBlocks)\n expect(catalog.leaves).toHaveLength(3)\n const leafSlugs = catalog.leaves.map((l) => l.slug)\n expect(leafSlugs).toEqual(['heading', 'richText', 'image'])\n })\n\n it('extracts leaf block fields including select options', () => {\n const catalog = introspectBlocks(allSectionBlocks, allLeafBlocks)\n const heading = catalog.leaves.find((l) => l.slug === 'heading')\n expect(heading).toBeDefined()\n const headingFieldNames = heading!.fields.map((f) => f.name)\n expect(headingFieldNames).toEqual(['text', 'level', 'align'])\n const level = heading!.fields.find((f) => f.name === 'level')\n expect(level!.options).toBeDefined()\n })\n})\n\ndescribe('buildRelationshipGraph', () => {\n it('builds correct graph from sample collections', () => {\n const schemas = introspectCollections([Posts, Pages, Categories, Authors, Media])\n const edges = buildRelationshipGraph(schemas)\n\n const postEdges = edges.filter((e) => e.fromCollection === 'posts')\n const postTargets = postEdges.map((e) => e.toCollection)\n expect(postTargets).toContain('categories')\n expect(postTargets).toContain('authors')\n expect(postTargets).toContain('media')\n\n const authorEdges = edges.filter((e) => e.fromCollection === 'authors')\n expect(authorEdges.map((e) => e.toCollection)).toContain('media')\n })\n})\n"],"names":["describe","it","expect","introspectCollection","introspectCollections","introspectBlocks","buildRelationshipGraph","Media","slug","upload","fields","name","type","required","Categories","Authors","relationTo","Heading","options","defaultValue","RichText","ImageBlock","allLeafBlocks","FullWidth","blocks","HeadingOnly","maxRows","CtaBanner","allSectionBlocks","Posts","versions","drafts","hasMany","Pages","tabs","label","schema","toBe","hasDrafts","fieldNames","map","f","toContain","relFieldNames","relationships","r","fieldName","cover","find","toBeDefined","searchableFields","heroSize","length","catalog","fullWidth","sections","s","nestingType","acceptedLeafSlugs","ctaBanner","toHaveLength","headingOnly","toEqual","leaves","leafSlugs","l","heading","headingFieldNames","level","schemas","edges","postEdges","filter","e","fromCollection","postTargets","toCollection","authorEdges"],"mappings":"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"}
|