@cms-lab/core 1.0.10 → 1.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/README.md +22 -1
- package/dist/index.d.ts +20 -3
- package/dist/index.js +83 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Core config, types, diagnostics, route resolution, and scan orchestration for cms-lab.
|
|
4
4
|
|
|
5
5
|
```ts
|
|
6
|
-
import { defineConfig, scanDocuments } from "@cms-lab/core";
|
|
6
|
+
import { defineConfig, scanDocuments, strapiRelationSlug } from "@cms-lab/core";
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
Most users should install `cms-lab` and use the CLI. This package is public for
|
|
@@ -19,6 +19,27 @@ Sanity image `alt` fields.
|
|
|
19
19
|
`readCmsDataPath` is exported for adapters that need to read dotted paths from
|
|
20
20
|
normalized CMS payloads, for example custom `uidField` and `urlField` mapping.
|
|
21
21
|
|
|
22
|
+
`strapiRelationSlug` and `strapiRelationValue` help Strapi route mappings read
|
|
23
|
+
relation values from both Strapi v4 `data.attributes` payloads and newer flat
|
|
24
|
+
REST payloads:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
routes: [
|
|
28
|
+
{
|
|
29
|
+
type: "article",
|
|
30
|
+
pattern: "/blog/:topic/:slug",
|
|
31
|
+
getPath: (doc) => {
|
|
32
|
+
const topic = strapiRelationSlug(doc.data, "topic") ?? "uncategorized";
|
|
33
|
+
return `/blog/${topic}/${doc.uid}`;
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
];
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`site.healthPath` and `site.healthUrl` let `doctor` and the initial scan health
|
|
40
|
+
probe hit a known-good page or endpoint while normal route probes still use
|
|
41
|
+
`site.url`.
|
|
42
|
+
|
|
22
43
|
## Release History
|
|
23
44
|
|
|
24
45
|
See the repository [changelog](https://github.com/i-afaqrashid/cms-lab/blob/main/CHANGELOG.md)
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ type CMSDocument = {
|
|
|
4
4
|
type: string;
|
|
5
5
|
uid?: string;
|
|
6
6
|
url?: string;
|
|
7
|
+
routable?: boolean;
|
|
8
|
+
entryKind?: "collection" | "single";
|
|
7
9
|
status: CMSDocumentStatus;
|
|
8
10
|
data: unknown;
|
|
9
11
|
};
|
|
@@ -49,15 +51,24 @@ type CmsFieldMappingConfig = {
|
|
|
49
51
|
uidField?: string;
|
|
50
52
|
urlField?: string;
|
|
51
53
|
};
|
|
54
|
+
type StrapiLocaleConfig = {
|
|
55
|
+
locale?: string;
|
|
56
|
+
};
|
|
52
57
|
type StrapiCollectionConfig = {
|
|
53
58
|
type: string;
|
|
54
59
|
endpoint: string;
|
|
55
|
-
} & CmsFieldMappingConfig;
|
|
60
|
+
} & CmsFieldMappingConfig & StrapiLocaleConfig;
|
|
61
|
+
type StrapiSingleTypeConfig = {
|
|
62
|
+
type: string;
|
|
63
|
+
endpoint: string;
|
|
64
|
+
} & CmsFieldMappingConfig & StrapiLocaleConfig;
|
|
56
65
|
type StrapiCmsProviderConfig = {
|
|
57
66
|
provider: "strapi";
|
|
58
67
|
url: string;
|
|
59
68
|
token?: string;
|
|
60
|
-
|
|
69
|
+
locale?: string;
|
|
70
|
+
collections?: StrapiCollectionConfig[];
|
|
71
|
+
singleTypes?: StrapiSingleTypeConfig[];
|
|
61
72
|
};
|
|
62
73
|
type DirectusCollectionConfig = {
|
|
63
74
|
type: string;
|
|
@@ -108,6 +119,8 @@ type CmsProviderConfig = PrismicCmsProviderConfig | StrapiCmsProviderConfig | Di
|
|
|
108
119
|
type CmsLabConfig = {
|
|
109
120
|
site: {
|
|
110
121
|
url: string;
|
|
122
|
+
healthPath?: string;
|
|
123
|
+
healthUrl?: string;
|
|
111
124
|
};
|
|
112
125
|
framework: {
|
|
113
126
|
type: "next";
|
|
@@ -181,6 +194,9 @@ declare class SiteUnreachableError extends CmsLabError {
|
|
|
181
194
|
constructor(message: string);
|
|
182
195
|
}
|
|
183
196
|
|
|
197
|
+
declare function strapiRelationSlug(data: unknown, path: string): string | undefined;
|
|
198
|
+
declare function strapiRelationValue(data: unknown, path: string, field: string): string | undefined;
|
|
199
|
+
|
|
184
200
|
type ScanDocumentsOptions = {
|
|
185
201
|
config: CmsLabConfig;
|
|
186
202
|
project: ProjectInfo;
|
|
@@ -192,5 +208,6 @@ type ScanDocumentsOptions = {
|
|
|
192
208
|
filters?: ScanFilters;
|
|
193
209
|
};
|
|
194
210
|
declare function scanDocuments(options: ScanDocumentsOptions): Promise<ScanResult>;
|
|
211
|
+
declare function resolveSiteHealthUrl(site: CmsLabConfig["site"]): URL;
|
|
195
212
|
|
|
196
|
-
export { type CMSDocument, type CMSDocumentStatus, type CheckGroup, CmsFetchError, type CmsFieldMappingConfig, type CmsLabConfig, CmsLabError, type CmsProviderConfig, ConfigLoadError, type ContentfulCmsProviderConfig, type ContentfulContentTypeConfig, type Diagnostic, type DiagnosticExplanation, type DiagnosticSeverity, type DirectusCmsProviderConfig, type DirectusCollectionConfig, type FetchLike, type LoadedCmsLabConfig, type PrismicCmsProviderConfig, type ProjectInfo, type RequiredFieldRule, type RouteDefinition, type SanityCmsProviderConfig, type SanityContentTypeConfig, type ScanDocumentsOptions, type ScanFilters, type ScanResult, type ScanSummary, SiteUnreachableError, type StrapiCmsProviderConfig, type StrapiCollectionConfig, type WordPressCmsProviderConfig, type WordPressContentTypeConfig, createDiagnostic, defineConfig, explainDiagnostic, listDiagnosticExplanations, loadCmsLabConfig, readCmsDataPath, scanDocuments, summarizeDiagnostics, validateConfig };
|
|
213
|
+
export { type CMSDocument, type CMSDocumentStatus, type CheckGroup, CmsFetchError, type CmsFieldMappingConfig, type CmsLabConfig, CmsLabError, type CmsProviderConfig, ConfigLoadError, type ContentfulCmsProviderConfig, type ContentfulContentTypeConfig, type Diagnostic, type DiagnosticExplanation, type DiagnosticSeverity, type DirectusCmsProviderConfig, type DirectusCollectionConfig, type FetchLike, type LoadedCmsLabConfig, type PrismicCmsProviderConfig, type ProjectInfo, type RequiredFieldRule, type RouteDefinition, type SanityCmsProviderConfig, type SanityContentTypeConfig, type ScanDocumentsOptions, type ScanFilters, type ScanResult, type ScanSummary, SiteUnreachableError, type StrapiCmsProviderConfig, type StrapiCollectionConfig, type StrapiLocaleConfig, type StrapiSingleTypeConfig, type WordPressCmsProviderConfig, type WordPressContentTypeConfig, createDiagnostic, defineConfig, explainDiagnostic, listDiagnosticExplanations, loadCmsLabConfig, readCmsDataPath, resolveSiteHealthUrl, scanDocuments, strapiRelationSlug, strapiRelationValue, summarizeDiagnostics, validateConfig };
|
package/dist/index.js
CHANGED
|
@@ -53,18 +53,29 @@ var cmsFieldMappingShape = {
|
|
|
53
53
|
uidField: z.string().min(1).optional(),
|
|
54
54
|
urlField: z.string().min(1).optional()
|
|
55
55
|
};
|
|
56
|
+
var strapiLocaleShape = {
|
|
57
|
+
locale: z.string().min(1).optional()
|
|
58
|
+
};
|
|
59
|
+
var strapiContentShape = z.object({
|
|
60
|
+
type: z.string().min(1),
|
|
61
|
+
endpoint: z.string().min(1),
|
|
62
|
+
...cmsFieldMappingShape,
|
|
63
|
+
...strapiLocaleShape
|
|
64
|
+
}).strict();
|
|
56
65
|
var strapiConfigSchema = z.object({
|
|
57
66
|
provider: z.literal("strapi"),
|
|
58
67
|
url: z.string().url(),
|
|
59
68
|
token: z.string().optional(),
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
69
|
+
...strapiLocaleShape,
|
|
70
|
+
collections: z.array(strapiContentShape).min(1).optional(),
|
|
71
|
+
singleTypes: z.array(strapiContentShape).min(1).optional()
|
|
72
|
+
}).refine(
|
|
73
|
+
(config) => (config.collections?.length ?? 0) > 0 || (config.singleTypes?.length ?? 0) > 0,
|
|
74
|
+
{
|
|
75
|
+
message: "Strapi config must include collections or singleTypes",
|
|
76
|
+
path: ["collections"]
|
|
77
|
+
}
|
|
78
|
+
).strict();
|
|
68
79
|
var directusConfigSchema = z.object({
|
|
69
80
|
provider: z.literal("directus"),
|
|
70
81
|
url: z.string().url(),
|
|
@@ -154,7 +165,12 @@ var checksSchema = z.object({
|
|
|
154
165
|
}).strict().optional();
|
|
155
166
|
var configSchema = z.object({
|
|
156
167
|
site: z.object({
|
|
157
|
-
url: z.string().url()
|
|
168
|
+
url: z.string().url(),
|
|
169
|
+
healthPath: z.string().min(1).refine(
|
|
170
|
+
(path) => path.startsWith("/") && !path.startsWith("//"),
|
|
171
|
+
"healthPath must be a same-origin path starting with a single /"
|
|
172
|
+
).optional(),
|
|
173
|
+
healthUrl: z.string().url().optional()
|
|
158
174
|
}).strict(),
|
|
159
175
|
framework: z.object({
|
|
160
176
|
type: z.literal("next"),
|
|
@@ -324,6 +340,42 @@ function listDiagnosticExplanations() {
|
|
|
324
340
|
return [...explanations];
|
|
325
341
|
}
|
|
326
342
|
|
|
343
|
+
// src/route-helpers.ts
|
|
344
|
+
function strapiRelationSlug(data, path) {
|
|
345
|
+
return strapiRelationValue(data, path, "slug");
|
|
346
|
+
}
|
|
347
|
+
function strapiRelationValue(data, path, field) {
|
|
348
|
+
const relation = unwrapStrapiRelation(readCmsDataPath(data, path));
|
|
349
|
+
const value = readCmsDataPath(relation, field);
|
|
350
|
+
if (typeof value === "string" && value.length > 0) {
|
|
351
|
+
return value;
|
|
352
|
+
}
|
|
353
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
354
|
+
return String(value);
|
|
355
|
+
}
|
|
356
|
+
return void 0;
|
|
357
|
+
}
|
|
358
|
+
function unwrapStrapiRelation(value) {
|
|
359
|
+
const record = asRecord2(value);
|
|
360
|
+
if (record && "data" in record) {
|
|
361
|
+
return unwrapStrapiRelation(record.data);
|
|
362
|
+
}
|
|
363
|
+
if (Array.isArray(value)) {
|
|
364
|
+
return unwrapStrapiRelation(value[0]);
|
|
365
|
+
}
|
|
366
|
+
if (record && "attributes" in record) {
|
|
367
|
+
const attributes = asRecord2(record.attributes);
|
|
368
|
+
return attributes ? { id: record.id, ...attributes } : record;
|
|
369
|
+
}
|
|
370
|
+
return value;
|
|
371
|
+
}
|
|
372
|
+
function asRecord2(value) {
|
|
373
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
374
|
+
return value;
|
|
375
|
+
}
|
|
376
|
+
return void 0;
|
|
377
|
+
}
|
|
378
|
+
|
|
327
379
|
// src/scan.ts
|
|
328
380
|
async function scanDocuments(options) {
|
|
329
381
|
const fetchImpl = options.fetch ?? fetch;
|
|
@@ -333,7 +385,7 @@ async function scanDocuments(options) {
|
|
|
333
385
|
const documents = filterDocuments(options.documents, options.filters);
|
|
334
386
|
const diagnostics = [];
|
|
335
387
|
await assertSiteReachable(
|
|
336
|
-
options.config.site.
|
|
388
|
+
resolveSiteHealthUrl(options.config.site).toString(),
|
|
337
389
|
fetchImpl,
|
|
338
390
|
timeoutMs,
|
|
339
391
|
retries
|
|
@@ -379,6 +431,9 @@ function resolveRouteCandidates(config, documents, diagnostics) {
|
|
|
379
431
|
(candidate) => candidate.type === document.type
|
|
380
432
|
);
|
|
381
433
|
if (!route) {
|
|
434
|
+
if (document.routable === false) {
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
382
437
|
diagnostics.push(
|
|
383
438
|
createDiagnostic({
|
|
384
439
|
severity: "info",
|
|
@@ -502,6 +557,16 @@ async function checkRouteReachability(config, candidates, fetchImpl, timeoutMs,
|
|
|
502
557
|
});
|
|
503
558
|
return results.flat();
|
|
504
559
|
}
|
|
560
|
+
function resolveSiteHealthUrl(site) {
|
|
561
|
+
if (site.healthUrl) {
|
|
562
|
+
return new URL(site.healthUrl);
|
|
563
|
+
}
|
|
564
|
+
const siteUrl = new URL(site.url);
|
|
565
|
+
if (site.healthPath) {
|
|
566
|
+
return resolveSiteRouteUrl(siteUrl, site.healthPath);
|
|
567
|
+
}
|
|
568
|
+
return siteUrl;
|
|
569
|
+
}
|
|
505
570
|
function resolveSiteRouteUrl(siteUrl, path) {
|
|
506
571
|
const url = new URL(path, siteUrl);
|
|
507
572
|
if (!url.search && siteUrl.search) {
|
|
@@ -564,7 +629,7 @@ function checkSeoFields(config, documents) {
|
|
|
564
629
|
const checkMetaTitle = typeof seo === "object" ? seo.metaTitle !== false : true;
|
|
565
630
|
const checkMetaDescription = typeof seo === "object" ? seo.metaDescription !== false : true;
|
|
566
631
|
for (const document of documents) {
|
|
567
|
-
const data =
|
|
632
|
+
const data = asRecord3(document.data);
|
|
568
633
|
if (!data) {
|
|
569
634
|
continue;
|
|
570
635
|
}
|
|
@@ -692,7 +757,7 @@ function collectImagesMissingAlt(value, provider, path = "data") {
|
|
|
692
757
|
(item, index) => collectImagesMissingAlt(item, provider, `${path}[${index}]`)
|
|
693
758
|
);
|
|
694
759
|
}
|
|
695
|
-
const record =
|
|
760
|
+
const record = asRecord3(value);
|
|
696
761
|
if (!record) {
|
|
697
762
|
return [];
|
|
698
763
|
}
|
|
@@ -720,7 +785,7 @@ function imageAltCandidate(provider, record) {
|
|
|
720
785
|
if (provider === "contentful" && isContentfulImageRecord(record)) {
|
|
721
786
|
return { isImage: true, value: record.description ?? record.title };
|
|
722
787
|
}
|
|
723
|
-
if (provider === "sanity" && "asset" in record && (
|
|
788
|
+
if (provider === "sanity" && "asset" in record && (asRecord3(record.asset)?._ref || asRecord3(record.asset)?._id)) {
|
|
724
789
|
return { isImage: true, value: record.alt };
|
|
725
790
|
}
|
|
726
791
|
return { isImage: false, value: void 0 };
|
|
@@ -732,7 +797,7 @@ function hasImageExtension(value) {
|
|
|
732
797
|
return typeof value === "string" && /\.(?:avif|gif|jpe?g|png|svg|webp)$/i.test(value);
|
|
733
798
|
}
|
|
734
799
|
function isContentfulImageRecord(record) {
|
|
735
|
-
const file =
|
|
800
|
+
const file = asRecord3(record.file);
|
|
736
801
|
return typeof file?.url === "string" && (isContentfulImageHost(file.url) || hasImageExtension(file.url)) || hasImageExtension(file?.fileName);
|
|
737
802
|
}
|
|
738
803
|
function isContentfulImageHost(value) {
|
|
@@ -883,7 +948,7 @@ function isMissingFieldValue(value) {
|
|
|
883
948
|
function isBlankOrPlaceholderAlt(value) {
|
|
884
949
|
return isBlank(value) || typeof value === "string" && ["image", "photo", "picture"].includes(value.trim().toLowerCase());
|
|
885
950
|
}
|
|
886
|
-
function
|
|
951
|
+
function asRecord3(value) {
|
|
887
952
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
888
953
|
return value;
|
|
889
954
|
}
|
|
@@ -911,7 +976,10 @@ export {
|
|
|
911
976
|
listDiagnosticExplanations,
|
|
912
977
|
loadCmsLabConfig,
|
|
913
978
|
readCmsDataPath,
|
|
979
|
+
resolveSiteHealthUrl,
|
|
914
980
|
scanDocuments,
|
|
981
|
+
strapiRelationSlug,
|
|
982
|
+
strapiRelationValue,
|
|
915
983
|
summarizeDiagnostics,
|
|
916
984
|
validateConfig
|
|
917
985
|
};
|