multisite-cms-mcp 1.0.21 → 1.0.23
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/tools/get-conversion-guide.d.ts.map +1 -1
- package/dist/tools/get-conversion-guide.js +120 -6
- 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 +132 -0
- package/dist/tools/get-schema.d.ts.map +1 -1
- package/dist/tools/get-schema.js +80 -0
- package/dist/tools/validate-manifest.d.ts.map +1 -1
- package/dist/tools/validate-manifest.js +90 -3
- package/package.json +1 -1
|
@@ -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,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,UAAU,GAAG,WAAW,GAAG,UAAU,GAAG,WAAW,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,WAAW,CAAC;AA80B1H;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAkD1E"}
|
|
@@ -230,6 +230,43 @@ This allows same or different paths for list vs detail pages (e.g., /videos for
|
|
|
230
230
|
2. **Detail Templates** - Single item pages (blog_post.html)
|
|
231
231
|
3. **Static Pages** - Fixed content with data-edit-key
|
|
232
232
|
|
|
233
|
+
## Static UI vs Dynamic Content
|
|
234
|
+
|
|
235
|
+
When converting templates, distinguish between:
|
|
236
|
+
|
|
237
|
+
### 1. Static UI Elements (Keep as-is)
|
|
238
|
+
Logos, icons, decorative backgrounds - these stay as \`/public/\` paths:
|
|
239
|
+
\`\`\`html
|
|
240
|
+
<!-- KEEP these static -->
|
|
241
|
+
<img src="/public/images/logo.png" alt="Company Logo">
|
|
242
|
+
<img src="/public/images/icons/arrow.svg" alt="">
|
|
243
|
+
\`\`\`
|
|
244
|
+
|
|
245
|
+
### 2. Dynamic Content (Replace with CMS tokens)
|
|
246
|
+
Blog posts, team members, products - replace example content with \`{{#each}}\` loops:
|
|
247
|
+
|
|
248
|
+
\`\`\`html
|
|
249
|
+
<!-- WRONG: Hardcoded example content -->
|
|
250
|
+
<article class="video-card">
|
|
251
|
+
<img src="/images/example-video.jpg" alt="Example Video">
|
|
252
|
+
<h2>My Example Video Title</h2>
|
|
253
|
+
<p>This is a sample description...</p>
|
|
254
|
+
</article>
|
|
255
|
+
|
|
256
|
+
<!-- CORRECT: CMS tokens with loop -->
|
|
257
|
+
{{#each video sort="publishedAt" order="desc"}}
|
|
258
|
+
<article class="video-card">
|
|
259
|
+
{{#if videothumbnail}}
|
|
260
|
+
<img src="{{videothumbnail}}" alt="{{name}}">
|
|
261
|
+
{{/if}}
|
|
262
|
+
<h2><a href="{{url}}">{{name}}</a></h2>
|
|
263
|
+
<p>{{shortdescription}}</p>
|
|
264
|
+
</article>
|
|
265
|
+
{{/each}}
|
|
266
|
+
\`\`\`
|
|
267
|
+
|
|
268
|
+
**Rule of thumb:** If it's site branding/design → keep static. If it's content that changes per item → use CMS tokens.
|
|
269
|
+
|
|
233
270
|
## Index Template Pattern (Blog List)
|
|
234
271
|
|
|
235
272
|
\`\`\`html
|
|
@@ -616,11 +653,11 @@ document.querySelectorAll('form[data-form-name]').forEach(form => {
|
|
|
616
653
|
**Tenant context is automatic** - the system handles associating forms with the correct site.`,
|
|
617
654
|
assets: `# Asset Path Rules
|
|
618
655
|
|
|
619
|
-
##
|
|
656
|
+
## Static Assets (CSS, JS, Fonts, Site Images)
|
|
620
657
|
|
|
621
|
-
**ALL asset paths must use /public/ prefix**
|
|
658
|
+
**ALL static asset paths must use /public/ prefix**
|
|
622
659
|
|
|
623
|
-
|
|
660
|
+
### HTML Examples
|
|
624
661
|
|
|
625
662
|
\`\`\`html
|
|
626
663
|
<!-- CSS -->
|
|
@@ -629,14 +666,14 @@ document.querySelectorAll('form[data-form-name]').forEach(form => {
|
|
|
629
666
|
<!-- JavaScript -->
|
|
630
667
|
<script src="/public/js/main.js"></script>
|
|
631
668
|
|
|
632
|
-
<!-- Images -->
|
|
669
|
+
<!-- Static Images (logos, icons, decorative) -->
|
|
633
670
|
<img src="/public/images/logo.png" alt="Logo">
|
|
634
671
|
|
|
635
672
|
<!-- Favicon -->
|
|
636
673
|
<link rel="icon" href="/public/images/favicon.ico">
|
|
637
674
|
\`\`\`
|
|
638
675
|
|
|
639
|
-
|
|
676
|
+
### CSS Examples
|
|
640
677
|
|
|
641
678
|
\`\`\`css
|
|
642
679
|
/* Correct */
|
|
@@ -648,7 +685,7 @@ background-image: url('../images/hero.jpg');
|
|
|
648
685
|
background-image: url('images/hero.jpg');
|
|
649
686
|
\`\`\`
|
|
650
687
|
|
|
651
|
-
|
|
688
|
+
### Conversion Table
|
|
652
689
|
|
|
653
690
|
| Original | Converted |
|
|
654
691
|
|----------|-----------|
|
|
@@ -657,6 +694,79 @@ background-image: url('images/hero.jpg');
|
|
|
657
694
|
| ./images/logo.png | /public/images/logo.png |
|
|
658
695
|
| /images/logo.png | /public/images/logo.png |
|
|
659
696
|
|
|
697
|
+
---
|
|
698
|
+
|
|
699
|
+
## CMS Content Images (CRITICAL)
|
|
700
|
+
|
|
701
|
+
**CMS-managed images are different from static assets. They are uploaded by users through the CMS dashboard and served from a CDN.**
|
|
702
|
+
|
|
703
|
+
### Two Types of Images
|
|
704
|
+
|
|
705
|
+
1. **Static assets** (\`/public/images/\`) - Site logos, icons, decorative elements bundled in the package
|
|
706
|
+
2. **CMS images** (\`{{fieldName}}\`) - User-uploaded content images managed through the dashboard
|
|
707
|
+
|
|
708
|
+
### CMS Image Handling
|
|
709
|
+
|
|
710
|
+
**ALWAYS use CMS tokens for content images - NEVER hardcode URLs**
|
|
711
|
+
|
|
712
|
+
\`\`\`html
|
|
713
|
+
<!-- CORRECT: CMS token for dynamic content -->
|
|
714
|
+
{{#if mainImage}}
|
|
715
|
+
<img src="{{mainImage}}" alt="{{name}}">
|
|
716
|
+
{{/if}}
|
|
717
|
+
|
|
718
|
+
<!-- WRONG: Hardcoded example content -->
|
|
719
|
+
<img src="/images/example-post.jpg" alt="Example Post">
|
|
720
|
+
\`\`\`
|
|
721
|
+
|
|
722
|
+
### CMS Image URL Format
|
|
723
|
+
|
|
724
|
+
When users upload images through the CMS dashboard, they are stored and served from:
|
|
725
|
+
\`\`\`
|
|
726
|
+
https://api.fastmode.ai/media/{tenantId}/{filename}
|
|
727
|
+
\`\`\`
|
|
728
|
+
|
|
729
|
+
**Templates should NEVER reference this URL directly** - the CMS automatically provides the correct URL through the token.
|
|
730
|
+
|
|
731
|
+
### Image Field Examples
|
|
732
|
+
|
|
733
|
+
\`\`\`html
|
|
734
|
+
<!-- Blog post images -->
|
|
735
|
+
<img src="{{mainImage}}" alt="{{name}}">
|
|
736
|
+
<img src="{{thumbnailImage}}" alt="{{name}}">
|
|
737
|
+
|
|
738
|
+
<!-- Team member photos -->
|
|
739
|
+
<img src="{{photo}}" alt="{{name}}">
|
|
740
|
+
|
|
741
|
+
<!-- Author photos -->
|
|
742
|
+
<img src="{{picture}}" alt="{{name}}">
|
|
743
|
+
|
|
744
|
+
<!-- Custom collection images (field slug becomes token) -->
|
|
745
|
+
<img src="{{videothumbnail}}" alt="{{name}}">
|
|
746
|
+
<img src="{{ogimage}}" alt="{{name}}">
|
|
747
|
+
<img src="{{productImage}}" alt="{{name}}">
|
|
748
|
+
\`\`\`
|
|
749
|
+
|
|
750
|
+
### Common Mistake: Mixing Static and CMS Images
|
|
751
|
+
|
|
752
|
+
\`\`\`html
|
|
753
|
+
<!-- WRONG: Don't mix hardcoded images with CMS content -->
|
|
754
|
+
{{#each video}}
|
|
755
|
+
<img src="/images/video-placeholder.jpg" alt="Video"> <!-- BAD -->
|
|
756
|
+
<h2>{{name}}</h2>
|
|
757
|
+
{{/each}}
|
|
758
|
+
|
|
759
|
+
<!-- CORRECT: All content comes from CMS -->
|
|
760
|
+
{{#each video}}
|
|
761
|
+
{{#if videothumbnail}}
|
|
762
|
+
<img src="{{videothumbnail}}" alt="{{name}}">
|
|
763
|
+
{{/if}}
|
|
764
|
+
<h2>{{name}}</h2>
|
|
765
|
+
{{/each}}
|
|
766
|
+
\`\`\`
|
|
767
|
+
|
|
768
|
+
---
|
|
769
|
+
|
|
660
770
|
## External Assets
|
|
661
771
|
|
|
662
772
|
Keep external URLs unchanged:
|
|
@@ -694,6 +804,10 @@ Keep external URLs unchanged:
|
|
|
694
804
|
- [ ] Rich text uses {{{triple braces}}}
|
|
695
805
|
- [ ] Parent refs (../) only inside loops
|
|
696
806
|
- [ ] Correct field names used
|
|
807
|
+
- [ ] **Static UI images** (logos, icons) use \`/public/\` paths
|
|
808
|
+
- [ ] **Content images** (blog photos, team headshots) use CMS tokens
|
|
809
|
+
- [ ] **No hardcoded example content** - dynamic items use \`{{#each}}\` loops
|
|
810
|
+
- [ ] Index pages iterate over collections, not static example cards
|
|
697
811
|
|
|
698
812
|
## ✓ Field Names
|
|
699
813
|
|
|
@@ -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' | 'data_edit_keys' | 'each_loop' | 'conditional_if' | 'nested_fields' | 'featured_posts' | 'parent_context' | 'equality_comparison';
|
|
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' | 'data_edit_keys' | 'each_loop' | 'conditional_if' | 'nested_fields' | 'featured_posts' | 'parent_context' | 'equality_comparison';
|
|
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,WAAW,GACX,gBAAgB,GAChB,eAAe,GACf,gBAAgB,GAChB,gBAAgB,GAChB,qBAAqB,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,gBAAgB,GAChB,WAAW,GACX,gBAAgB,GAChB,eAAe,GACf,gBAAgB,GAChB,gBAAgB,GAChB,qBAAqB,CAAC;AA+gC1B;;GAEG;AACH,wBAAsB,UAAU,CAAC,WAAW,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAE1E"}
|
|
@@ -554,6 +554,138 @@ background-image: url('images/hero-bg.jpg');
|
|
|
554
554
|
- \`href="css/style.css"\` → \`href="/public/css/style.css"\`
|
|
555
555
|
- \`src="../images/logo.png"\` → \`src="/public/images/logo.png"\`
|
|
556
556
|
- \`url('../fonts/custom.woff')\` → \`url('/public/fonts/custom.woff')\``,
|
|
557
|
+
image_handling: `# Image Handling in Templates
|
|
558
|
+
|
|
559
|
+
## Two Types of Images
|
|
560
|
+
|
|
561
|
+
### 1. Static/UI Images (Keep as /public/ paths)
|
|
562
|
+
Logos, icons, decorative backgrounds, UI elements - these are bundled with the site:
|
|
563
|
+
\`\`\`html
|
|
564
|
+
<!-- KEEP these as static paths -->
|
|
565
|
+
<img src="/public/images/logo.png" alt="Company Logo">
|
|
566
|
+
<img src="/public/images/icons/arrow.svg" alt="">
|
|
567
|
+
<link rel="icon" href="/public/images/favicon.ico">
|
|
568
|
+
<div style="background-image: url('/public/images/hero-pattern.svg')">
|
|
569
|
+
\`\`\`
|
|
570
|
+
|
|
571
|
+
### 2. Content Images (Use CMS Tokens)
|
|
572
|
+
Blog photos, team headshots, product images - these are managed through the CMS:
|
|
573
|
+
\`\`\`html
|
|
574
|
+
<!-- USE CMS tokens for content -->
|
|
575
|
+
<img src="{{mainImage}}" alt="{{name}}">
|
|
576
|
+
<img src="{{thumbnailImage}}" alt="{{name}}">
|
|
577
|
+
<img src="{{photo}}" alt="{{name}}">
|
|
578
|
+
\`\`\`
|
|
579
|
+
|
|
580
|
+
---
|
|
581
|
+
|
|
582
|
+
## How to Decide: Static vs CMS Token
|
|
583
|
+
|
|
584
|
+
| Image Type | Use | Example |
|
|
585
|
+
|------------|-----|---------|
|
|
586
|
+
| Company logo | \`/public/images/logo.png\` | Header/footer logo |
|
|
587
|
+
| UI icons | \`/public/images/icons/...\` | Arrows, social icons |
|
|
588
|
+
| Decorative backgrounds | \`/public/images/...\` | Hero patterns, textures |
|
|
589
|
+
| Favicon | \`/public/images/favicon.ico\` | Browser tab icon |
|
|
590
|
+
| Blog post images | \`{{mainImage}}\` | Article hero image |
|
|
591
|
+
| Team member photos | \`{{photo}}\` | Staff headshots |
|
|
592
|
+
| Product images | \`{{productImage}}\` | E-commerce photos |
|
|
593
|
+
| User-uploaded content | \`{{fieldName}}\` | Any CMS-managed media |
|
|
594
|
+
|
|
595
|
+
**Rule of thumb:** If the image is part of the site's design/branding → static. If it's content that changes per item → CMS token.
|
|
596
|
+
|
|
597
|
+
---
|
|
598
|
+
|
|
599
|
+
## Content Images: Avoid Hardcoded Examples
|
|
600
|
+
|
|
601
|
+
When converting a static site with example blog posts or team members, replace the example content with CMS tokens:
|
|
602
|
+
|
|
603
|
+
**WRONG - Hardcoded example content:**
|
|
604
|
+
\`\`\`html
|
|
605
|
+
<div class="videos-grid">
|
|
606
|
+
<article class="video-card">
|
|
607
|
+
<img src="/images/example-video.jpg" alt="Example">
|
|
608
|
+
<h2>Example Video Title</h2>
|
|
609
|
+
<p>Example description text...</p>
|
|
610
|
+
</article>
|
|
611
|
+
</div>
|
|
612
|
+
\`\`\`
|
|
613
|
+
|
|
614
|
+
**CORRECT - CMS tokens with loop:**
|
|
615
|
+
\`\`\`html
|
|
616
|
+
<div class="videos-grid">
|
|
617
|
+
{{#each video sort="publishedAt" order="desc"}}
|
|
618
|
+
<article class="video-card">
|
|
619
|
+
{{#if videothumbnail}}
|
|
620
|
+
<img src="{{videothumbnail}}" alt="{{name}}">
|
|
621
|
+
{{/if}}
|
|
622
|
+
<h2><a href="{{url}}">{{name}}</a></h2>
|
|
623
|
+
<p>{{shortdescription}}</p>
|
|
624
|
+
</article>
|
|
625
|
+
{{/each}}
|
|
626
|
+
</div>
|
|
627
|
+
\`\`\`
|
|
628
|
+
|
|
629
|
+
---
|
|
630
|
+
|
|
631
|
+
## Always Wrap Content Images in Conditionals
|
|
632
|
+
|
|
633
|
+
Since CMS images may not be uploaded, always check:
|
|
634
|
+
\`\`\`html
|
|
635
|
+
{{#if mainImage}}
|
|
636
|
+
<img src="{{mainImage}}" alt="{{name}}" class="hero-image">
|
|
637
|
+
{{/if}}
|
|
638
|
+
\`\`\`
|
|
639
|
+
|
|
640
|
+
Or provide a CSS fallback (not a hardcoded placeholder image):
|
|
641
|
+
\`\`\`html
|
|
642
|
+
{{#if mainImage}}
|
|
643
|
+
<img src="{{mainImage}}" alt="{{name}}">
|
|
644
|
+
{{else}}
|
|
645
|
+
<div class="placeholder-image"></div>
|
|
646
|
+
{{/if}}
|
|
647
|
+
\`\`\`
|
|
648
|
+
|
|
649
|
+
---
|
|
650
|
+
|
|
651
|
+
## Image Field Names by Collection
|
|
652
|
+
|
|
653
|
+
**Blog Posts:**
|
|
654
|
+
- \`{{mainImage}}\` - Hero/cover image
|
|
655
|
+
- \`{{thumbnailImage}}\` - Small preview image
|
|
656
|
+
|
|
657
|
+
**Team Members:**
|
|
658
|
+
- \`{{photo}}\` - Headshot
|
|
659
|
+
|
|
660
|
+
**Authors:**
|
|
661
|
+
- \`{{picture}}\` - Author photo
|
|
662
|
+
|
|
663
|
+
**Custom Collections:**
|
|
664
|
+
- **Token name = field slug** defined when creating the collection
|
|
665
|
+
- e.g., field slug \`videothumbnail\` → \`{{videothumbnail}}\`
|
|
666
|
+
- e.g., field slug \`ogimage\` → \`{{ogimage}}\`
|
|
667
|
+
- e.g., field slug \`productImage\` → \`{{productImage}}\`
|
|
668
|
+
|
|
669
|
+
**Use \`get_tenant_schema\` tool to see exact field slugs for a tenant's custom collections.**
|
|
670
|
+
|
|
671
|
+
---
|
|
672
|
+
|
|
673
|
+
## How CMS Images Work
|
|
674
|
+
|
|
675
|
+
1. User creates a custom collection with image fields (e.g., \`videothumbnail\`)
|
|
676
|
+
2. User uploads images via CMS dashboard
|
|
677
|
+
3. Template uses the field slug as the token: \`{{videothumbnail}}\`
|
|
678
|
+
4. CMS automatically provides the correct URL at runtime
|
|
679
|
+
|
|
680
|
+
---
|
|
681
|
+
|
|
682
|
+
## Common Mistakes
|
|
683
|
+
|
|
684
|
+
1. **Replacing logos with CMS tokens** - Keep static UI images as \`/public/\` paths
|
|
685
|
+
2. **Hardcoding example blog/product images** - Use \`{{#each}}\` loops with CMS tokens
|
|
686
|
+
3. **Wrong field names** - \`{{photo}}\` for team, \`{{picture}}\` for authors
|
|
687
|
+
4. **Missing conditionals** - Always wrap optional content images in \`{{#if}}\`
|
|
688
|
+
5. **Hardcoded placeholder images** - Use CSS placeholders, not image files`,
|
|
557
689
|
data_edit_keys: `# Inline Editing with data-edit-key
|
|
558
690
|
|
|
559
691
|
Make text editable in the CMS visual editor:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-schema.d.ts","sourceRoot":"","sources":["../../src/tools/get-schema.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAsB,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"get-schema.d.ts","sourceRoot":"","sources":["../../src/tools/get-schema.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAsB,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,CA4fjD"}
|
package/dist/tools/get-schema.js
CHANGED
|
@@ -323,6 +323,86 @@ For HTML content that should NOT be escaped:
|
|
|
323
323
|
|
|
324
324
|
---
|
|
325
325
|
|
|
326
|
+
## Image Handling in Templates
|
|
327
|
+
|
|
328
|
+
### Two Types of Images
|
|
329
|
+
|
|
330
|
+
**1. Static/UI Images** - Logos, icons, decorative backgrounds, UI elements
|
|
331
|
+
- These are bundled with the site package
|
|
332
|
+
- Use \`/public/\` paths
|
|
333
|
+
- Keep these as static files
|
|
334
|
+
|
|
335
|
+
**2. Content Images** - Blog photos, team headshots, product images, user-uploaded media
|
|
336
|
+
- These are managed through the CMS dashboard
|
|
337
|
+
- Use CMS tokens like \`{{mainImage}}\`, \`{{photo}}\`
|
|
338
|
+
- Never hardcode example/placeholder content images
|
|
339
|
+
|
|
340
|
+
### Static UI Images (Keep as /public/ paths)
|
|
341
|
+
|
|
342
|
+
\`\`\`html
|
|
343
|
+
<!-- These are CORRECT - static UI elements -->
|
|
344
|
+
<img src="/public/images/logo.png" alt="Company Logo">
|
|
345
|
+
<img src="/public/images/icons/arrow.svg" alt="">
|
|
346
|
+
<div style="background-image: url('/public/images/hero-bg.jpg')">
|
|
347
|
+
<link rel="icon" href="/public/images/favicon.ico">
|
|
348
|
+
\`\`\`
|
|
349
|
+
|
|
350
|
+
### Content Images (Use CMS Tokens)
|
|
351
|
+
|
|
352
|
+
\`\`\`html
|
|
353
|
+
<!-- These are CORRECT - CMS-managed content -->
|
|
354
|
+
<img src="{{mainImage}}" alt="{{name}}">
|
|
355
|
+
<img src="{{thumbnailImage}}" alt="{{name}}">
|
|
356
|
+
<img src="{{photo}}" alt="{{name}}">
|
|
357
|
+
|
|
358
|
+
<!-- WRONG - Don't hardcode example/placeholder content -->
|
|
359
|
+
<img src="/images/sample-blog-post.jpg" alt="Sample Post">
|
|
360
|
+
<img src="/images/placeholder-team.jpg" alt="Team Member">
|
|
361
|
+
\`\`\`
|
|
362
|
+
|
|
363
|
+
### Image Field Pattern
|
|
364
|
+
|
|
365
|
+
Always wrap image fields in conditionals since they may not be set:
|
|
366
|
+
|
|
367
|
+
\`\`\`html
|
|
368
|
+
{{#if mainImage}}
|
|
369
|
+
<img src="{{mainImage}}" alt="{{name}}" class="hero-image">
|
|
370
|
+
{{/if}}
|
|
371
|
+
\`\`\`
|
|
372
|
+
|
|
373
|
+
### Custom Collection Image Fields
|
|
374
|
+
|
|
375
|
+
For custom collections, the **token name matches the field slug** defined when creating the collection.
|
|
376
|
+
|
|
377
|
+
**Example:** A "video" collection with these fields:
|
|
378
|
+
- \`videothumbnail\` (type: image)
|
|
379
|
+
- \`ogimage\` (type: image)
|
|
380
|
+
- \`shortdescription\` (type: text)
|
|
381
|
+
|
|
382
|
+
**Template uses the field slugs as tokens:**
|
|
383
|
+
\`\`\`html
|
|
384
|
+
{{#each video sort="publishedAt" order="desc"}}
|
|
385
|
+
<article class="video-card">
|
|
386
|
+
{{#if videothumbnail}}
|
|
387
|
+
<img src="{{videothumbnail}}" alt="{{name}}">
|
|
388
|
+
{{/if}}
|
|
389
|
+
<h2><a href="{{url}}">{{name}}</a></h2>
|
|
390
|
+
<p>{{shortdescription}}</p>
|
|
391
|
+
</article>
|
|
392
|
+
{{/each}}
|
|
393
|
+
\`\`\`
|
|
394
|
+
|
|
395
|
+
**To find exact field slugs for a tenant's custom collections, use the \`get_tenant_schema\` tool.**
|
|
396
|
+
|
|
397
|
+
### Common Mistakes to Avoid
|
|
398
|
+
|
|
399
|
+
1. **Replacing logos/icons with CMS tokens** - Static UI images should stay as \`/public/\` paths
|
|
400
|
+
2. **Hardcoding example blog/product content** - Use \`{{#each}}\` loops with CMS tokens
|
|
401
|
+
3. **Missing \`{{#each}}\` loops** - Dynamic content requires iteration over the collection
|
|
402
|
+
4. **Wrong field names** - Token must match the exact field slug (case-sensitive)
|
|
403
|
+
|
|
404
|
+
---
|
|
405
|
+
|
|
326
406
|
## CMS Template Configuration
|
|
327
407
|
|
|
328
408
|
Templates are configured in manifest.json using a **flat format** that is the SINGLE SOURCE OF TRUTH for URL paths.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-manifest.d.ts","sourceRoot":"","sources":["../../src/tools/validate-manifest.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"validate-manifest.d.ts","sourceRoot":"","sources":["../../src/tools/validate-manifest.ts"],"names":[],"mappings":"AAyCA;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA4N5E"}
|
|
@@ -8,7 +8,8 @@ const pageSchema = zod_1.z.object({
|
|
|
8
8
|
title: zod_1.z.string(),
|
|
9
9
|
editable: zod_1.z.boolean().optional(),
|
|
10
10
|
});
|
|
11
|
-
|
|
11
|
+
// Built-in collection templates
|
|
12
|
+
const builtInTemplatesSchema = zod_1.z.object({
|
|
12
13
|
blogIndex: zod_1.z.string().optional(),
|
|
13
14
|
blogIndexPath: zod_1.z.string().startsWith('/').optional(),
|
|
14
15
|
blogPost: zod_1.z.string().optional(),
|
|
@@ -20,8 +21,17 @@ const cmsTemplatesSchema = zod_1.z.object({
|
|
|
20
21
|
authorsIndex: zod_1.z.string().optional(),
|
|
21
22
|
authorDetail: zod_1.z.string().optional(),
|
|
22
23
|
authorsIndexPath: zod_1.z.string().startsWith('/').optional(),
|
|
24
|
+
// Legacy format - deprecated
|
|
23
25
|
collectionPaths: zod_1.z.record(zod_1.z.string().startsWith('/')).optional(),
|
|
24
|
-
|
|
26
|
+
collectionTemplates: zod_1.z.record(zod_1.z.object({
|
|
27
|
+
path: zod_1.z.string().startsWith('/').optional(),
|
|
28
|
+
indexTemplate: zod_1.z.string().optional(),
|
|
29
|
+
detailTemplate: zod_1.z.string().optional(),
|
|
30
|
+
})).optional(),
|
|
31
|
+
});
|
|
32
|
+
// Custom collection templates use flat format: {slug}Index, {slug}Detail, {slug}IndexPath, {slug}DetailPath
|
|
33
|
+
// This schema allows any additional string keys for custom collections
|
|
34
|
+
const cmsTemplatesSchema = builtInTemplatesSchema.catchall(zod_1.z.string()).optional();
|
|
25
35
|
const manifestSchema = zod_1.z.object({
|
|
26
36
|
pages: zod_1.z.array(pageSchema),
|
|
27
37
|
cmsTemplates: cmsTemplatesSchema,
|
|
@@ -85,6 +95,7 @@ The manifest.json file is not valid JSON. Check for:
|
|
|
85
95
|
}
|
|
86
96
|
// Check cmsTemplates
|
|
87
97
|
const templates = m.cmsTemplates;
|
|
98
|
+
const customCollections = new Set();
|
|
88
99
|
if (templates) {
|
|
89
100
|
// Check template file paths
|
|
90
101
|
const templateFields = ['blogIndex', 'blogPost', 'team', 'downloads', 'authorsIndex', 'authorDetail'];
|
|
@@ -120,9 +131,85 @@ The manifest.json file is not valid JSON. Check for:
|
|
|
120
131
|
if (templates.authorDetail && !templates.authorsIndex) {
|
|
121
132
|
warnings.push('- authorDetail template defined but no authorsIndex - authors need both templates');
|
|
122
133
|
}
|
|
134
|
+
// Detect and validate custom collection templates (flat format)
|
|
135
|
+
// Pattern: {slug}Index, {slug}Detail, {slug}IndexPath, {slug}DetailPath
|
|
136
|
+
const allKeys = Object.keys(templates);
|
|
137
|
+
for (const key of allKeys) {
|
|
138
|
+
// Skip built-in keys
|
|
139
|
+
if (['blogIndex', 'blogIndexPath', 'blogPost', 'blogPostPath', 'team', 'teamPath',
|
|
140
|
+
'downloads', 'downloadsPath', 'authorsIndex', 'authorDetail', 'authorsIndexPath',
|
|
141
|
+
'collectionPaths', 'collectionTemplates'].includes(key)) {
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
// Extract collection slug from key pattern
|
|
145
|
+
let slug = null;
|
|
146
|
+
if (key.endsWith('Index') && !key.endsWith('IndexPath')) {
|
|
147
|
+
slug = key.slice(0, -5); // Remove 'Index'
|
|
148
|
+
}
|
|
149
|
+
else if (key.endsWith('Detail') && !key.endsWith('DetailPath')) {
|
|
150
|
+
slug = key.slice(0, -6); // Remove 'Detail'
|
|
151
|
+
}
|
|
152
|
+
else if (key.endsWith('IndexPath')) {
|
|
153
|
+
slug = key.slice(0, -9); // Remove 'IndexPath'
|
|
154
|
+
}
|
|
155
|
+
else if (key.endsWith('DetailPath')) {
|
|
156
|
+
slug = key.slice(0, -10); // Remove 'DetailPath'
|
|
157
|
+
}
|
|
158
|
+
if (slug && slug.length > 0) {
|
|
159
|
+
customCollections.add(slug);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// Validate each custom collection
|
|
163
|
+
for (const slug of customCollections) {
|
|
164
|
+
const indexTemplate = templates[`${slug}Index`];
|
|
165
|
+
const detailTemplate = templates[`${slug}Detail`];
|
|
166
|
+
const indexPath = templates[`${slug}IndexPath`];
|
|
167
|
+
const detailPath = templates[`${slug}DetailPath`];
|
|
168
|
+
// Check template file paths
|
|
169
|
+
if (typeof indexTemplate === 'string') {
|
|
170
|
+
if (!indexTemplate.endsWith('.html')) {
|
|
171
|
+
warnings.push(`- cmsTemplates.${slug}Index: should end with .html (got: ${indexTemplate})`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (typeof detailTemplate === 'string') {
|
|
175
|
+
if (!detailTemplate.endsWith('.html')) {
|
|
176
|
+
warnings.push(`- cmsTemplates.${slug}Detail: should end with .html (got: ${detailTemplate})`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// Check path format
|
|
180
|
+
if (typeof indexPath === 'string' && !indexPath.startsWith('/')) {
|
|
181
|
+
errors.push(`- cmsTemplates.${slug}IndexPath: must start with "/" (got: ${indexPath})`);
|
|
182
|
+
}
|
|
183
|
+
if (typeof detailPath === 'string' && !detailPath.startsWith('/')) {
|
|
184
|
+
errors.push(`- cmsTemplates.${slug}DetailPath: must start with "/" (got: ${detailPath})`);
|
|
185
|
+
}
|
|
186
|
+
// Check consistency - if you have one, you should have the others
|
|
187
|
+
if (indexTemplate && !indexPath) {
|
|
188
|
+
warnings.push(`- Custom collection "${slug}": has ${slug}Index but no ${slug}IndexPath`);
|
|
189
|
+
}
|
|
190
|
+
if (detailTemplate && !detailPath) {
|
|
191
|
+
warnings.push(`- Custom collection "${slug}": has ${slug}Detail but no ${slug}DetailPath`);
|
|
192
|
+
}
|
|
193
|
+
if (indexPath && !indexTemplate) {
|
|
194
|
+
warnings.push(`- Custom collection "${slug}": has ${slug}IndexPath but no ${slug}Index template`);
|
|
195
|
+
}
|
|
196
|
+
if (detailPath && !detailTemplate) {
|
|
197
|
+
warnings.push(`- Custom collection "${slug}": has ${slug}DetailPath but no ${slug}Detail template`);
|
|
198
|
+
}
|
|
199
|
+
// Note: indexPath and detailPath can be the same (e.g., both "/videos")
|
|
200
|
+
// The system distinguishes by whether a slug segment exists after the path
|
|
201
|
+
}
|
|
202
|
+
// Warn about deprecated collectionTemplates format
|
|
203
|
+
if (templates.collectionTemplates) {
|
|
204
|
+
warnings.push('- cmsTemplates.collectionTemplates: This nested format is deprecated. Use flat format instead: {slug}Index, {slug}Detail, {slug}IndexPath, {slug}DetailPath');
|
|
205
|
+
}
|
|
123
206
|
}
|
|
124
207
|
// Build result
|
|
125
208
|
let output = '';
|
|
209
|
+
// Build custom collections summary
|
|
210
|
+
const customCollectionsSummary = customCollections.size > 0
|
|
211
|
+
? `\n- Custom collections: ${Array.from(customCollections).join(', ')} (using flat format: {slug}Index, {slug}Detail, {slug}IndexPath, {slug}DetailPath)`
|
|
212
|
+
: '';
|
|
126
213
|
if (errors.length === 0 && warnings.length === 0) {
|
|
127
214
|
output = `✅ MANIFEST VALID
|
|
128
215
|
|
|
@@ -130,7 +217,7 @@ The manifest.json structure is correct.
|
|
|
130
217
|
|
|
131
218
|
Summary:
|
|
132
219
|
- ${m.pages?.length || 0} static page(s) defined
|
|
133
|
-
- CMS templates: ${templates ? 'configured in manifest' : 'not configured (can be set via Settings → CMS Templates after upload)'}
|
|
220
|
+
- CMS templates: ${templates ? 'configured in manifest' : 'not configured (can be set via Settings → CMS Templates after upload)'}${customCollectionsSummary}
|
|
134
221
|
- Head HTML: ${m.defaultHeadHtml ? 'configured' : 'not configured'}`;
|
|
135
222
|
}
|
|
136
223
|
else if (errors.length === 0) {
|
package/package.json
CHANGED