multisite-cms-mcp 1.5.5 → 1.5.7
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/dist/index.js +3 -2
- package/dist/tools/get-conversion-guide.d.ts +1 -1
- package/dist/tools/get-conversion-guide.d.ts.map +1 -1
- package/dist/tools/get-conversion-guide.js +137 -1
- package/dist/tools/get-example.d.ts +1 -1
- package/dist/tools/get-example.d.ts.map +1 -1
- package/dist/tools/get-example.js +129 -0
- package/dist/tools/get-field-types.d.ts +1 -1
- package/dist/tools/get-field-types.js +2 -2
- package/dist/tools/validate-template.d.ts.map +1 -1
- package/dist/tools/validate-template.js +40 -5
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -118,6 +118,7 @@ const TOOLS = [
|
|
|
118
118
|
'parent_context',
|
|
119
119
|
'equality_comparison',
|
|
120
120
|
'youtube_embed',
|
|
121
|
+
'nested_collection_loop',
|
|
121
122
|
],
|
|
122
123
|
description: 'The type of example to retrieve',
|
|
123
124
|
},
|
|
@@ -133,8 +134,8 @@ const TOOLS = [
|
|
|
133
134
|
properties: {
|
|
134
135
|
section: {
|
|
135
136
|
type: 'string',
|
|
136
|
-
enum: ['full', 'analysis', 'structure', 'manifest', 'templates', 'tokens', 'forms', 'assets', 'checklist'],
|
|
137
|
-
description: 'Specific section to retrieve, or "full" for everything',
|
|
137
|
+
enum: ['full', 'first_steps', 'analysis', 'structure', 'seo', 'manifest', 'templates', 'tokens', 'forms', 'assets', 'checklist'],
|
|
138
|
+
description: 'Specific section to retrieve, or "full" for everything. Use "first_steps" to get the required initial steps for establishing the target project.',
|
|
138
139
|
},
|
|
139
140
|
},
|
|
140
141
|
required: [],
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
type Section = 'full' | 'analysis' | 'structure' | 'seo' | 'manifest' | 'templates' | 'tokens' | 'forms' | 'assets' | 'checklist';
|
|
1
|
+
type Section = 'full' | 'first_steps' | 'analysis' | 'structure' | 'seo' | 'manifest' | 'templates' | 'tokens' | 'forms' | 'assets' | 'checklist';
|
|
2
2
|
/**
|
|
3
3
|
* Returns the conversion guide, either full or a specific section
|
|
4
4
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-conversion-guide.d.ts","sourceRoot":"","sources":["../../src/tools/get-conversion-guide.ts"],"names":[],"mappings":"AAAA,KAAK,OAAO,GAAG,MAAM,GAAG,UAAU,GAAG,WAAW,GAAG,KAAK,GAAG,UAAU,GAAG,WAAW,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"get-conversion-guide.d.ts","sourceRoot":"","sources":["../../src/tools/get-conversion-guide.ts"],"names":[],"mappings":"AAAA,KAAK,OAAO,GAAG,MAAM,GAAG,aAAa,GAAG,UAAU,GAAG,WAAW,GAAG,KAAK,GAAG,UAAU,GAAG,WAAW,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,WAAW,CAAC;AA+2BlJ;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CA0D1E"}
|
|
@@ -2,6 +2,52 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getConversionGuide = getConversionGuide;
|
|
4
4
|
const SECTIONS = {
|
|
5
|
+
first_steps: `# REQUIRED FIRST STEPS (Do These Before Building the Package)
|
|
6
|
+
|
|
7
|
+
**IMPORTANT:** Before converting any website, you MUST establish the target project first. This determines the schema and ensures you're deploying to the correct location.
|
|
8
|
+
|
|
9
|
+
## Step 1: Check for Existing Projects
|
|
10
|
+
|
|
11
|
+
Call \`list_projects\` to see the user's existing Fast Mode projects.
|
|
12
|
+
|
|
13
|
+
## Step 2: Ask the User
|
|
14
|
+
|
|
15
|
+
Based on what \`list_projects\` returns:
|
|
16
|
+
|
|
17
|
+
**If NO projects exist:**
|
|
18
|
+
- This is a new user - ask: "What would you like to name your new project?"
|
|
19
|
+
- Proceed to Step 3b (New Project)
|
|
20
|
+
|
|
21
|
+
**If projects exist:**
|
|
22
|
+
- Present the list and ask: "Is this website for one of your existing projects, or should I create a new one?"
|
|
23
|
+
- Let the user choose which project, or confirm they want a new one
|
|
24
|
+
|
|
25
|
+
## Step 3a: For EXISTING Projects
|
|
26
|
+
|
|
27
|
+
1. User selects the project from the list
|
|
28
|
+
2. Call \`get_tenant_schema(projectId)\` to get the current collections and fields
|
|
29
|
+
3. **Use this schema to build templates with the correct field names**
|
|
30
|
+
4. When deploying, this will UPDATE the existing project
|
|
31
|
+
5. Proceed to Phase 1 (Website Analysis)
|
|
32
|
+
|
|
33
|
+
## Step 3b: For NEW Projects
|
|
34
|
+
|
|
35
|
+
1. Ask for the project name if you don't have it
|
|
36
|
+
2. Call \`create_site(name)\` to create the project
|
|
37
|
+
3. You'll receive a projectId to use for deployment
|
|
38
|
+
4. After building the package, call \`sync_schema\` to create collections/fields
|
|
39
|
+
5. Proceed to Phase 1 (Website Analysis)
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
**WHY THIS MATTERS:**
|
|
44
|
+
- For existing projects: The schema determines which fields to use in templates
|
|
45
|
+
- For new projects: You need the projectId before you can deploy
|
|
46
|
+
- Always: The user must confirm which project to use - never assume
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
`,
|
|
5
51
|
analysis: `# Phase 1: Website Analysis (MANDATORY)
|
|
6
52
|
|
|
7
53
|
Before writing ANY code, complete this analysis:
|
|
@@ -415,6 +461,93 @@ Use for rich text content:
|
|
|
415
461
|
- \`featured=true\` - Only featured (if collection has boolean "featured" field)
|
|
416
462
|
- \`sort="field"\` - Sort by field
|
|
417
463
|
- \`order="asc|desc"\` - Sort direction
|
|
464
|
+
- \`where="field:value"\` - Filter by field value
|
|
465
|
+
|
|
466
|
+
## Nested Loops (Hierarchical Content)
|
|
467
|
+
|
|
468
|
+
For content hierarchies like categories with items, docs sections with pages, or any parent-child relationships.
|
|
469
|
+
|
|
470
|
+
**Works on ALL page types:** static pages, index pages, AND detail pages.
|
|
471
|
+
|
|
472
|
+
\`\`\`html
|
|
473
|
+
{{#each doc_categories}}
|
|
474
|
+
<div class="category-section">
|
|
475
|
+
<h3>{{name}}</h3>
|
|
476
|
+
<ul>
|
|
477
|
+
{{#each doc_pages where="category.slug:{{slug}}"}}
|
|
478
|
+
<li><a href="{{url}}">{{name}}</a></li>
|
|
479
|
+
{{/each}}
|
|
480
|
+
</ul>
|
|
481
|
+
</div>
|
|
482
|
+
{{/each}}
|
|
483
|
+
\`\`\`
|
|
484
|
+
|
|
485
|
+
**How it works:**
|
|
486
|
+
- The outer loop iterates through categories
|
|
487
|
+
- \`{{slug}}\` in the where clause references the current category's slug
|
|
488
|
+
- The inner loop filters doc_pages to only those matching that category
|
|
489
|
+
|
|
490
|
+
---
|
|
491
|
+
|
|
492
|
+
### Using @root. Prefix
|
|
493
|
+
|
|
494
|
+
Use \`@root.\` to access collections from the root context when you need to be explicit:
|
|
495
|
+
|
|
496
|
+
\`\`\`html
|
|
497
|
+
{{#each doc_categories}}
|
|
498
|
+
<div class="category-section">
|
|
499
|
+
<h3>{{name}}</h3>
|
|
500
|
+
<ul>
|
|
501
|
+
{{#each @root.doc_pages where="category.slug:{{slug}}"}}
|
|
502
|
+
<li><a href="{{url}}">{{name}}</a></li>
|
|
503
|
+
{{/each}}
|
|
504
|
+
</ul>
|
|
505
|
+
</div>
|
|
506
|
+
{{/each}}
|
|
507
|
+
\`\`\`
|
|
508
|
+
|
|
509
|
+
**When to use @root.:**
|
|
510
|
+
- When the inner collection name might be ambiguous
|
|
511
|
+
- When you want to be explicit about accessing root-level data
|
|
512
|
+
- Both \`{{#each collection}}\` and \`{{#each @root.collection}}\` work the same
|
|
513
|
+
|
|
514
|
+
---
|
|
515
|
+
|
|
516
|
+
### Common Patterns
|
|
517
|
+
|
|
518
|
+
\`\`\`html
|
|
519
|
+
<!-- Categories with posts -->
|
|
520
|
+
{{#each categories}}
|
|
521
|
+
<section>
|
|
522
|
+
<h2>{{name}}</h2>
|
|
523
|
+
{{#each posts where="category.slug:{{slug}}"}}
|
|
524
|
+
<article>{{name}}</article>
|
|
525
|
+
{{/each}}
|
|
526
|
+
</section>
|
|
527
|
+
{{/each}}
|
|
528
|
+
|
|
529
|
+
<!-- Authors with their articles -->
|
|
530
|
+
{{#each authors}}
|
|
531
|
+
<div class="author-section">
|
|
532
|
+
<h3>{{name}}</h3>
|
|
533
|
+
{{#each posts where="author.id:{{id}}"}}
|
|
534
|
+
<a href="{{url}}">{{name}}</a>
|
|
535
|
+
{{/each}}
|
|
536
|
+
</div>
|
|
537
|
+
{{/each}}
|
|
538
|
+
|
|
539
|
+
<!-- Documentation sidebar (on any page type) -->
|
|
540
|
+
{{#each doc_categories sort="order" order="asc"}}
|
|
541
|
+
<div class="sidebar-section">
|
|
542
|
+
<h4>{{name}}</h4>
|
|
543
|
+
{{#each @root.doc_pages where="category.slug:{{slug}}" sort="order" order="asc"}}
|
|
544
|
+
<a href="{{url}}">{{name}}</a>
|
|
545
|
+
{{/each}}
|
|
546
|
+
</div>
|
|
547
|
+
{{/each}}
|
|
548
|
+
\`\`\`
|
|
549
|
+
|
|
550
|
+
**Important:** The \`{{field}}\` in the where clause is resolved from the outer loop's current item.
|
|
418
551
|
|
|
419
552
|
## Conditionals
|
|
420
553
|
\`\`\`html
|
|
@@ -741,6 +874,9 @@ async function getConversionGuide(section) {
|
|
|
741
874
|
if (section === 'full') {
|
|
742
875
|
return `# Complete Website Conversion Guide
|
|
743
876
|
|
|
877
|
+
${SECTIONS.first_steps}
|
|
878
|
+
---
|
|
879
|
+
|
|
744
880
|
${SECTIONS.analysis}
|
|
745
881
|
|
|
746
882
|
---
|
|
@@ -781,7 +917,7 @@ ${SECTIONS.checklist}
|
|
|
781
917
|
|
|
782
918
|
Use these MCP tools during conversion:
|
|
783
919
|
|
|
784
|
-
- \`
|
|
920
|
+
- \`get_field_types\` - Get available field types for sync_schema
|
|
785
921
|
- \`get_tenant_schema\` - Get exact collections and fields for a specific project
|
|
786
922
|
- \`get_example\` - Get code examples for specific patterns
|
|
787
923
|
- \`validate_manifest\` - Check your manifest.json
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
type ExampleType = 'manifest_basic' | 'manifest_custom_paths' | 'manifest_minimal_with_ui' | 'blog_index_template' | 'blog_post_template' | 'team_template' | 'downloads_template' | 'authors_template' | 'author_detail_template' | 'custom_collection_template' | 'form_handling' | 'asset_paths' | 'image_handling' | 'relation_fields' | 'data_edit_keys' | 'each_loop' | 'conditional_if' | 'nested_fields' | 'featured_posts' | 'parent_context' | 'equality_comparison' | 'youtube_embed';
|
|
1
|
+
type ExampleType = 'manifest_basic' | 'manifest_custom_paths' | 'manifest_minimal_with_ui' | 'blog_index_template' | 'blog_post_template' | 'team_template' | 'downloads_template' | 'authors_template' | 'author_detail_template' | 'custom_collection_template' | 'form_handling' | 'asset_paths' | 'image_handling' | 'relation_fields' | 'data_edit_keys' | 'each_loop' | 'conditional_if' | 'nested_fields' | 'featured_posts' | 'parent_context' | 'equality_comparison' | 'youtube_embed' | 'nested_collection_loop';
|
|
2
2
|
/**
|
|
3
3
|
* Returns example code for a specific pattern
|
|
4
4
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-example.d.ts","sourceRoot":"","sources":["../../src/tools/get-example.ts"],"names":[],"mappings":"AAAA,KAAK,WAAW,GACZ,gBAAgB,GAChB,uBAAuB,GACvB,0BAA0B,GAC1B,qBAAqB,GACrB,oBAAoB,GACpB,eAAe,GACf,oBAAoB,GACpB,kBAAkB,GAClB,wBAAwB,GACxB,4BAA4B,GAC5B,eAAe,GACf,aAAa,GACb,gBAAgB,GAChB,iBAAiB,GACjB,gBAAgB,GAChB,WAAW,GACX,gBAAgB,GAChB,eAAe,GACf,gBAAgB,GAChB,gBAAgB,GAChB,qBAAqB,GACrB,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"get-example.d.ts","sourceRoot":"","sources":["../../src/tools/get-example.ts"],"names":[],"mappings":"AAAA,KAAK,WAAW,GACZ,gBAAgB,GAChB,uBAAuB,GACvB,0BAA0B,GAC1B,qBAAqB,GACrB,oBAAoB,GACpB,eAAe,GACf,oBAAoB,GACpB,kBAAkB,GAClB,wBAAwB,GACxB,4BAA4B,GAC5B,eAAe,GACf,aAAa,GACb,gBAAgB,GAChB,iBAAiB,GACjB,gBAAgB,GAChB,WAAW,GACX,gBAAgB,GAChB,eAAe,GACf,gBAAgB,GAChB,gBAAgB,GAChB,qBAAqB,GACrB,eAAe,GACf,wBAAwB,CAAC;AAmqC7B;;GAEG;AACH,wBAAsB,UAAU,CAAC,WAAW,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAE1E"}
|
|
@@ -1033,6 +1033,135 @@ https://www.youtube.com/watch?v=VIDEO_ID
|
|
|
1033
1033
|
{{/if}}
|
|
1034
1034
|
</div>
|
|
1035
1035
|
\`\`\``,
|
|
1036
|
+
nested_collection_loop: `# Nested Collection Loops
|
|
1037
|
+
|
|
1038
|
+
For hierarchical content like documentation categories with pages, product categories with products, or authors with their posts.
|
|
1039
|
+
|
|
1040
|
+
**Works on ALL page types:** static pages, index pages, AND detail pages.
|
|
1041
|
+
|
|
1042
|
+
## The Pattern
|
|
1043
|
+
|
|
1044
|
+
\`\`\`html
|
|
1045
|
+
{{#each outer_collection}}
|
|
1046
|
+
<div class="category-section">
|
|
1047
|
+
<h3>{{name}}</h3>
|
|
1048
|
+
<ul>
|
|
1049
|
+
{{#each inner_collection where="relation_field.slug:{{slug}}"}}
|
|
1050
|
+
<li><a href="{{url}}">{{name}}</a></li>
|
|
1051
|
+
{{/each}}
|
|
1052
|
+
</ul>
|
|
1053
|
+
</div>
|
|
1054
|
+
{{/each}}
|
|
1055
|
+
\`\`\`
|
|
1056
|
+
|
|
1057
|
+
**How it works:**
|
|
1058
|
+
- The outer loop iterates through parent items (e.g., categories)
|
|
1059
|
+
- \`{{slug}}\` in the \`where\` clause references the current outer item's slug
|
|
1060
|
+
- The inner loop filters to only items matching that parent
|
|
1061
|
+
|
|
1062
|
+
---
|
|
1063
|
+
|
|
1064
|
+
## Using @root. Prefix
|
|
1065
|
+
|
|
1066
|
+
Use \`@root.\` to explicitly access collections from the root context:
|
|
1067
|
+
|
|
1068
|
+
\`\`\`html
|
|
1069
|
+
{{#each doc_categories}}
|
|
1070
|
+
<h3>{{name}}</h3>
|
|
1071
|
+
{{#each @root.doc_pages where="category.slug:{{slug}}"}}
|
|
1072
|
+
<a href="{{url}}">{{name}}</a>
|
|
1073
|
+
{{/each}}
|
|
1074
|
+
{{/each}}
|
|
1075
|
+
\`\`\`
|
|
1076
|
+
|
|
1077
|
+
Both syntaxes work identically:
|
|
1078
|
+
- \`{{#each doc_pages}}\` - Standard syntax
|
|
1079
|
+
- \`{{#each @root.doc_pages}}\` - Explicit root reference
|
|
1080
|
+
|
|
1081
|
+
---
|
|
1082
|
+
|
|
1083
|
+
## Documentation Sidebar Example
|
|
1084
|
+
|
|
1085
|
+
**Collections needed:**
|
|
1086
|
+
- \`doc_categories\` (name, slug, order)
|
|
1087
|
+
- \`doc_pages\` (name, slug, content, category → relation to doc_categories)
|
|
1088
|
+
|
|
1089
|
+
\`\`\`html
|
|
1090
|
+
<nav class="docs-sidebar">
|
|
1091
|
+
{{#each doc_categories sort="order" order="asc"}}
|
|
1092
|
+
<div class="sidebar-section">
|
|
1093
|
+
<h4 class="section-title">{{name}}</h4>
|
|
1094
|
+
<ul class="section-links">
|
|
1095
|
+
{{#each @root.doc_pages where="category.slug:{{slug}}" sort="order" order="asc"}}
|
|
1096
|
+
<li>
|
|
1097
|
+
<a href="{{url}}" class="doc-link">{{name}}</a>
|
|
1098
|
+
</li>
|
|
1099
|
+
{{/each}}
|
|
1100
|
+
</ul>
|
|
1101
|
+
</div>
|
|
1102
|
+
{{/each}}
|
|
1103
|
+
</nav>
|
|
1104
|
+
\`\`\`
|
|
1105
|
+
|
|
1106
|
+
This sidebar pattern can be included on:
|
|
1107
|
+
- **Static pages** (about, contact, etc.)
|
|
1108
|
+
- **Index pages** (docs listing page)
|
|
1109
|
+
- **Detail pages** (individual doc pages)
|
|
1110
|
+
|
|
1111
|
+
---
|
|
1112
|
+
|
|
1113
|
+
## Blog Categories with Posts Example
|
|
1114
|
+
|
|
1115
|
+
\`\`\`html
|
|
1116
|
+
<section class="categorized-posts">
|
|
1117
|
+
{{#each categories}}
|
|
1118
|
+
<div class="category-group">
|
|
1119
|
+
<h2><a href="{{url}}">{{name}}</a></h2>
|
|
1120
|
+
<div class="posts-grid">
|
|
1121
|
+
{{#each posts where="category.id:{{id}}" limit=3 sort="publishedAt" order="desc"}}
|
|
1122
|
+
<article class="post-card">
|
|
1123
|
+
{{#if image}}
|
|
1124
|
+
<img src="{{image}}" alt="{{name}}">
|
|
1125
|
+
{{/if}}
|
|
1126
|
+
<h3><a href="{{url}}">{{name}}</a></h3>
|
|
1127
|
+
<p>{{summary}}</p>
|
|
1128
|
+
</article>
|
|
1129
|
+
{{/each}}
|
|
1130
|
+
</div>
|
|
1131
|
+
<a href="{{url}}" class="view-all">View all {{name}}</a>
|
|
1132
|
+
</div>
|
|
1133
|
+
{{/each}}
|
|
1134
|
+
</section>
|
|
1135
|
+
\`\`\`
|
|
1136
|
+
|
|
1137
|
+
---
|
|
1138
|
+
|
|
1139
|
+
## Important Notes
|
|
1140
|
+
|
|
1141
|
+
1. **Works on ALL page types** - Static pages, index pages, AND detail pages
|
|
1142
|
+
2. **The \`where\` clause uses parent context** - \`{{slug}}\` or \`{{id}}\` comes from the outer loop's current item
|
|
1143
|
+
3. **@root. is optional** - Use it when you want to be explicit about root context
|
|
1144
|
+
4. **Supports all modifiers** - \`limit\`, \`sort\`, \`order\` work on inner loops
|
|
1145
|
+
5. **Relation field format** - Use \`relation_field.slug:{{slug}}\` or \`relation_field.id:{{id}}\`
|
|
1146
|
+
|
|
1147
|
+
---
|
|
1148
|
+
|
|
1149
|
+
## Manifest Configuration
|
|
1150
|
+
|
|
1151
|
+
\`\`\`json
|
|
1152
|
+
{
|
|
1153
|
+
"cmsTemplates": {
|
|
1154
|
+
"doc_categoriesIndex": "templates/docs_index.html",
|
|
1155
|
+
"doc_categoriesIndexPath": "/docs",
|
|
1156
|
+
"doc_pagesDetail": "templates/doc_page.html",
|
|
1157
|
+
"doc_pagesDetailPath": "/docs"
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
\`\`\`
|
|
1161
|
+
|
|
1162
|
+
This gives you URLs like:
|
|
1163
|
+
- \`/docs\` - Documentation index showing all categories with their pages
|
|
1164
|
+
- \`/docs/quick-start-guide\` - Individual doc page`,
|
|
1036
1165
|
};
|
|
1037
1166
|
/**
|
|
1038
1167
|
* Returns example code for a specific pattern
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Returns the list of field types that can be used when CREATING new fields
|
|
5
5
|
* in collections. This is NOT the list of fields in your schema - use
|
|
6
|
-
*
|
|
6
|
+
* get_tenant_schema for that.
|
|
7
7
|
*
|
|
8
8
|
* No authentication required.
|
|
9
9
|
*/
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Returns the list of field types that can be used when CREATING new fields
|
|
6
6
|
* in collections. This is NOT the list of fields in your schema - use
|
|
7
|
-
*
|
|
7
|
+
* get_tenant_schema for that.
|
|
8
8
|
*
|
|
9
9
|
* No authentication required.
|
|
10
10
|
*/
|
|
@@ -104,7 +104,7 @@ async function getFieldTypes() {
|
|
|
104
104
|
|
|
105
105
|
These are the field types you can use when CREATING new fields with the \`sync_schema\` tool.
|
|
106
106
|
|
|
107
|
-
**Note:** This is NOT the list of fields in your schema. Use \`
|
|
107
|
+
**Note:** This is NOT the list of fields in your schema. Use \`get_tenant_schema\` to see a specific project's schema.
|
|
108
108
|
|
|
109
109
|
## Field Types
|
|
110
110
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-template.d.ts","sourceRoot":"","sources":["../../src/tools/validate-template.ts"],"names":[],"mappings":"AAMA,KAAK,YAAY,GAAG,cAAc,GAAG,eAAe,GAAG,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"validate-template.d.ts","sourceRoot":"","sources":["../../src/tools/validate-template.ts"],"names":[],"mappings":"AAMA,KAAK,YAAY,GAAG,cAAc,GAAG,eAAe,GAAG,aAAa,CAAC;AAmRrE;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,YAAY,EAC1B,cAAc,CAAC,EAAE,MAAM,EACvB,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC,CA4djB"}
|
|
@@ -190,12 +190,15 @@ function validateForms(html) {
|
|
|
190
190
|
}
|
|
191
191
|
/**
|
|
192
192
|
* Extract collection slugs referenced in {{#each}} loops on static pages
|
|
193
|
+
* Supports both {{#each collection}} and {{#each @root.collection}} syntax
|
|
193
194
|
*/
|
|
194
195
|
function extractCollectionReferences(html) {
|
|
195
196
|
const collections = [];
|
|
196
|
-
|
|
197
|
+
// Match both {{#each collection}} and {{#each @root.collection}}
|
|
198
|
+
const eachLoops = html.match(/\{\{#each\s+(?:@root\.)?([\w]+)[^}]*\}\}/g) || [];
|
|
197
199
|
for (const loop of eachLoops) {
|
|
198
|
-
|
|
200
|
+
// Extract collection name, handling @root. prefix
|
|
201
|
+
const match = loop.match(/\{\{#each\s+(?:@root\.)?([\w]+)/);
|
|
199
202
|
if (match) {
|
|
200
203
|
collections.push(match[1]);
|
|
201
204
|
}
|
|
@@ -218,7 +221,8 @@ async function validateTemplate(html, templateType, collectionSlug, projectId) {
|
|
|
218
221
|
// Extract all tokens from the HTML
|
|
219
222
|
const doubleTokens = html.match(/\{\{([^{}#/]+)\}\}/g) || [];
|
|
220
223
|
const tripleTokens = html.match(/\{\{\{([^{}]+)\}\}\}/g) || [];
|
|
221
|
-
|
|
224
|
+
// Match both {{#each collection}} and {{#each @root.collection}}
|
|
225
|
+
const eachLoops = html.match(/\{\{#each\s+(?:@root\.)?([\w]+)[^}]*\}\}/g) || [];
|
|
222
226
|
const conditionals = html.match(/\{\{#if\s+([^}]+)\}\}/g) || [];
|
|
223
227
|
// Check for tokens with spaces (like {{ name }} instead of {{name}})
|
|
224
228
|
const tokensWithSpaces = html.match(/\{\{\s+[^{}]+\s*\}\}/g) || [];
|
|
@@ -245,9 +249,9 @@ async function validateTemplate(html, templateType, collectionSlug, projectId) {
|
|
|
245
249
|
warnings.push(`- No {{#each}} loop found - index templates usually need a loop to display items`);
|
|
246
250
|
}
|
|
247
251
|
else {
|
|
248
|
-
// Check if loop uses correct collection name
|
|
252
|
+
// Check if loop uses correct collection name (handles @root. prefix)
|
|
249
253
|
for (const loop of eachLoops) {
|
|
250
|
-
const match = loop.match(/\{\{#each\s+(\w+)/);
|
|
254
|
+
const match = loop.match(/\{\{#each\s+(?:@root\.)?([\w]+)/);
|
|
251
255
|
if (match) {
|
|
252
256
|
const usedCollection = match[1];
|
|
253
257
|
if (collectionSlug && usedCollection !== collectionSlug) {
|
|
@@ -339,6 +343,37 @@ async function validateTemplate(html, templateType, collectionSlug, projectId) {
|
|
|
339
343
|
suggestions.push(`- Found ${parentRefs.length} parent context reference(s) ({{../fieldName}}) - accesses parent scope in loops`);
|
|
340
344
|
}
|
|
341
345
|
}
|
|
346
|
+
// Validate nested loops with parent context in where clause
|
|
347
|
+
// Supports both {{#each collection}} and {{#each @root.collection}}
|
|
348
|
+
const nestedLoopPattern = /\{\{#each\s+(?:@root\.)?(\w+)\s+[^}]*where="[^:]+:\{\{(\w+)\}\}"[^}]*\}\}/g;
|
|
349
|
+
const nestedLoops = html.match(nestedLoopPattern) || [];
|
|
350
|
+
if (nestedLoops.length > 0) {
|
|
351
|
+
suggestions.push(`- Found ${nestedLoops.length} nested loop(s) with parent context filtering (e.g., where="field:{{parentField}}")`);
|
|
352
|
+
// Check if there's an outer loop to provide context
|
|
353
|
+
// Match both {{#each collection}} and {{#each @root.collection}}
|
|
354
|
+
const allLoopMatches = html.match(/\{\{#each\s+(?:@root\.)?\w+/g) || [];
|
|
355
|
+
if (allLoopMatches.length < 2 && nestedLoops.length > 0) {
|
|
356
|
+
warnings.push(`- Nested loop with where="...{{field}}" found but no outer loop detected. The {{field}} needs to come from an outer loop's item.`);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
// Detect common nested loop patterns
|
|
360
|
+
const hierarchicalPatterns = [
|
|
361
|
+
{ outer: 'categories', inner: 'posts', relation: 'category' },
|
|
362
|
+
{ outer: 'doc_categories', inner: 'doc_pages', relation: 'category' },
|
|
363
|
+
{ outer: 'authors', inner: 'posts', relation: 'author' },
|
|
364
|
+
{ outer: 'tags', inner: 'posts', relation: 'tags' },
|
|
365
|
+
];
|
|
366
|
+
for (const pattern of hierarchicalPatterns) {
|
|
367
|
+
const outerMatch = new RegExp(`\\{\\{#each\\s+${pattern.outer}`, 'g').test(html);
|
|
368
|
+
const innerMatch = new RegExp(`\\{\\{#each\\s+${pattern.inner}`, 'g').test(html);
|
|
369
|
+
if (outerMatch && innerMatch) {
|
|
370
|
+
// Check if inner loop has proper where clause
|
|
371
|
+
const innerWithWhere = new RegExp(`\\{\\{#each\\s+${pattern.inner}\\s+[^}]*where=`, 'g').test(html);
|
|
372
|
+
if (!innerWithWhere) {
|
|
373
|
+
warnings.push(`- Nested loops: ${pattern.outer} → ${pattern.inner} detected. Consider adding where="${pattern.relation}.slug:{{slug}}" to filter ${pattern.inner} by parent ${pattern.outer}.`);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
342
377
|
// Validate equality helper syntax
|
|
343
378
|
const eqHelpers = html.match(/\{\{#if\s+\(eq\s+[^)]+\)\s*\}\}/g) || [];
|
|
344
379
|
if (eqHelpers.length > 0) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "multisite-cms-mcp",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.7",
|
|
4
4
|
"description": "MCP server for Fast Mode CMS. Convert websites, validate packages, and deploy directly to Fast Mode. Includes authentication, project creation, schema sync, and one-click deployment.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|