@actuate-media/cms-core 0.26.0 → 0.27.1
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/__tests__/api/collections-discovery.test.js +47 -0
- package/dist/__tests__/api/collections-discovery.test.js.map +1 -1
- package/dist/__tests__/api/page-sections-routes.test.d.ts +2 -0
- package/dist/__tests__/api/page-sections-routes.test.d.ts.map +1 -0
- package/dist/__tests__/api/page-sections-routes.test.js +271 -0
- package/dist/__tests__/api/page-sections-routes.test.js.map +1 -0
- package/dist/__tests__/api/stats-collection-filter.test.d.ts +2 -0
- package/dist/__tests__/api/stats-collection-filter.test.d.ts.map +1 -0
- package/dist/__tests__/api/stats-collection-filter.test.js +190 -0
- package/dist/__tests__/api/stats-collection-filter.test.js.map +1 -0
- package/dist/__tests__/api/stats-seo-cache.test.d.ts +2 -0
- package/dist/__tests__/api/stats-seo-cache.test.d.ts.map +1 -0
- package/dist/__tests__/api/stats-seo-cache.test.js +96 -0
- package/dist/__tests__/api/stats-seo-cache.test.js.map +1 -0
- package/dist/api/handlers.d.ts +2 -0
- package/dist/api/handlers.d.ts.map +1 -1
- package/dist/api/handlers.js +465 -16
- package/dist/api/handlers.js.map +1 -1
- package/dist/config/types.d.ts +8 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/sections/__tests__/sections.test.d.ts +2 -0
- package/dist/sections/__tests__/sections.test.d.ts.map +1 -0
- package/dist/sections/__tests__/sections.test.js +215 -0
- package/dist/sections/__tests__/sections.test.js.map +1 -0
- package/dist/sections/helpers.d.ts +62 -0
- package/dist/sections/helpers.d.ts.map +1 -0
- package/dist/sections/helpers.js +202 -0
- package/dist/sections/helpers.js.map +1 -0
- package/dist/sections/index.d.ts +13 -0
- package/dist/sections/index.d.ts.map +1 -0
- package/dist/sections/index.js +12 -0
- package/dist/sections/index.js.map +1 -0
- package/dist/sections/registry.d.ts +27 -0
- package/dist/sections/registry.d.ts.map +1 -0
- package/dist/sections/registry.js +349 -0
- package/dist/sections/registry.js.map +1 -0
- package/dist/sections/types.d.ts +127 -0
- package/dist/sections/types.d.ts.map +1 -0
- package/dist/sections/types.js +21 -0
- package/dist/sections/types.js.map +1 -0
- package/dist/sections/validate.d.ts +10 -0
- package/dist/sections/validate.d.ts.map +1 -0
- package/dist/sections/validate.js +92 -0
- package/dist/sections/validate.js.map +1 -0
- package/package.json +6 -1
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Section content validation — shared by the admin editor's `validatePage`,
|
|
3
|
+
* the REST `/page-sections/validate` endpoint, and the MCP
|
|
4
|
+
* `validate_section_content` tool.
|
|
5
|
+
*
|
|
6
|
+
* Deterministic (no LLM, no I/O). Validates a SINGLE section's content
|
|
7
|
+
* against its registry field schema. Page-level rules (title/path, "at
|
|
8
|
+
* least one visible section", SEO warnings) live in the admin's
|
|
9
|
+
* `validatePage` because they operate on the whole `EditorPage`.
|
|
10
|
+
*
|
|
11
|
+
* Messages are intentionally label-prefix-free (e.g. `"Heading" is
|
|
12
|
+
* required.`) so callers can prepend their own context (`${sectionName}:
|
|
13
|
+
* ...`) without double-labelling.
|
|
14
|
+
*/
|
|
15
|
+
import { getSectionType } from './registry.js';
|
|
16
|
+
const URL_OK = /^(https?:\/\/|\/|#|mailto:|tel:)/i;
|
|
17
|
+
function isBlank(v) {
|
|
18
|
+
return v === undefined || v === null || (typeof v === 'string' && v.trim() === '');
|
|
19
|
+
}
|
|
20
|
+
/** Label/URL field pairs: the URL must be valid when its label is filled in. */
|
|
21
|
+
const BUTTON_PAIRS = [
|
|
22
|
+
['primaryButtonLabel', 'primaryButtonUrl'],
|
|
23
|
+
['secondaryButtonLabel', 'secondaryButtonUrl'],
|
|
24
|
+
['buttonLabel', 'buttonUrl'],
|
|
25
|
+
];
|
|
26
|
+
/**
|
|
27
|
+
* Validate one section's content + settings against its type schema.
|
|
28
|
+
*
|
|
29
|
+
* An unknown `sectionType` is a hard error (the agent asked for a type with
|
|
30
|
+
* no renderer). Required fields, button URL validity, and image alt text
|
|
31
|
+
* are enforced; everything else is advisory.
|
|
32
|
+
*/
|
|
33
|
+
export function validateSectionContent(sectionType, content, _settings) {
|
|
34
|
+
const errors = [];
|
|
35
|
+
const warnings = [];
|
|
36
|
+
const def = getSectionType(sectionType);
|
|
37
|
+
if (!def) {
|
|
38
|
+
errors.push({
|
|
39
|
+
sectionType,
|
|
40
|
+
message: `Unknown section type "${sectionType}". It has no registered renderer.`,
|
|
41
|
+
});
|
|
42
|
+
return { valid: false, errors, warnings };
|
|
43
|
+
}
|
|
44
|
+
const data = content ?? {};
|
|
45
|
+
for (const field of def.fields) {
|
|
46
|
+
if (field.required && isBlank(data[field.key])) {
|
|
47
|
+
errors.push({ sectionType, field: field.key, message: `"${field.label}" is required.` });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
for (const [labelKey, urlKey] of BUTTON_PAIRS) {
|
|
51
|
+
const hasLabel = !isBlank(data[labelKey]);
|
|
52
|
+
const url = data[urlKey];
|
|
53
|
+
if (hasLabel && !isBlank(url) && typeof url === 'string' && !URL_OK.test(url.trim())) {
|
|
54
|
+
errors.push({ sectionType, field: urlKey, message: `"${url}" is not a valid URL.` });
|
|
55
|
+
}
|
|
56
|
+
// A configured URL with no label is a soft issue — the button won't render.
|
|
57
|
+
if (!hasLabel && !isBlank(data[urlKey])) {
|
|
58
|
+
warnings.push({
|
|
59
|
+
sectionType,
|
|
60
|
+
field: labelKey,
|
|
61
|
+
message: `A URL is set for "${urlKey}" but its button label is empty, so the button will not render.`,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// Image alt text required when an image is set (accessibility).
|
|
66
|
+
if (!isBlank(data.imageUrl) && isBlank(data.imageAlt)) {
|
|
67
|
+
errors.push({
|
|
68
|
+
sectionType,
|
|
69
|
+
field: 'imageAlt',
|
|
70
|
+
message: 'image needs alt text for accessibility.',
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
// Gallery images should each carry alt text (accessibility) — advisory so
|
|
74
|
+
// it never blocks publish, but surfaced so it can be fixed.
|
|
75
|
+
if (Array.isArray(data.images)) {
|
|
76
|
+
const missingAlt = data.images.some((img) => {
|
|
77
|
+
if (!img || typeof img !== 'object')
|
|
78
|
+
return false;
|
|
79
|
+
const row = img;
|
|
80
|
+
return !isBlank(row.url) && isBlank(row.alt);
|
|
81
|
+
});
|
|
82
|
+
if (missingAlt) {
|
|
83
|
+
warnings.push({
|
|
84
|
+
sectionType,
|
|
85
|
+
field: 'images',
|
|
86
|
+
message: 'One or more gallery images are missing alt text for accessibility.',
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/sections/validate.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@actuate-media/cms-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.1",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/actuate-media/actuatecms.git",
|
|
@@ -59,6 +59,11 @@
|
|
|
59
59
|
"import": "./dist/page-builder/index.js",
|
|
60
60
|
"default": "./dist/page-builder/index.js"
|
|
61
61
|
},
|
|
62
|
+
"./page-sections": {
|
|
63
|
+
"types": "./dist/sections/index.d.ts",
|
|
64
|
+
"import": "./dist/sections/index.js",
|
|
65
|
+
"default": "./dist/sections/index.js"
|
|
66
|
+
},
|
|
62
67
|
"./realtime": {
|
|
63
68
|
"types": "./dist/realtime/index.d.ts",
|
|
64
69
|
"import": "./dist/realtime/index.js",
|