@consilioweb/payload-seo-analyzer 1.8.0 → 1.8.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/index.cjs +51 -23
- package/dist/index.d.cts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +51 -23
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1632,7 +1632,10 @@ function analyzeDoc(doc, collection, seoConfig) {
|
|
|
1632
1632
|
if (seoInput.isGlobal) {
|
|
1633
1633
|
seoInput.slug = "";
|
|
1634
1634
|
}
|
|
1635
|
-
const analysis = analyzeSeo(seoInput,
|
|
1635
|
+
const analysis = analyzeSeo(seoInput, {
|
|
1636
|
+
...seoConfig,
|
|
1637
|
+
disabledRules: [...seoConfig?.disabledRules ?? [], "geo", "eeat", "hreflang"]
|
|
1638
|
+
});
|
|
1636
1639
|
const extracted = extractDocContent(doc);
|
|
1637
1640
|
const fullText = extracted.text;
|
|
1638
1641
|
const allLinks = extracted.links;
|
|
@@ -1693,28 +1696,48 @@ function createAuditHandler(collections, seoConfig, globals = []) {
|
|
|
1693
1696
|
let cached = noCache ? null : seoCache.get(CACHE_KEY);
|
|
1694
1697
|
if (!cached) {
|
|
1695
1698
|
const { config: mergedConfig, ignoredSlugs } = await loadMergedConfig(req.payload, seoConfig);
|
|
1699
|
+
const BATCH_SIZE = Math.min(100, Math.max(1, parseInt(process.env.SEO_AUDIT_BATCH_SIZE || "25", 10) || 25));
|
|
1700
|
+
const MAX_DOCS2 = Math.max(1, parseInt(process.env.SEO_AUDIT_MAX_DOCS || "1500", 10) || 1500);
|
|
1696
1701
|
const allResults = [];
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1702
|
+
let capped2 = false;
|
|
1703
|
+
collectionsLoop:
|
|
1704
|
+
for (const collectionSlug of collections) {
|
|
1705
|
+
try {
|
|
1706
|
+
let page2 = 1;
|
|
1707
|
+
let hasMore = true;
|
|
1708
|
+
while (hasMore) {
|
|
1709
|
+
const result = await req.payload.find({
|
|
1710
|
+
collection: collectionSlug,
|
|
1711
|
+
limit: BATCH_SIZE,
|
|
1712
|
+
page: page2,
|
|
1713
|
+
depth: 1,
|
|
1714
|
+
overrideAccess: true
|
|
1715
|
+
});
|
|
1716
|
+
for (const doc of result.docs) {
|
|
1717
|
+
if (ignoredSlugs.includes(doc.slug)) continue;
|
|
1718
|
+
if (allResults.length >= MAX_DOCS2) {
|
|
1719
|
+
capped2 = true;
|
|
1720
|
+
break collectionsLoop;
|
|
1721
|
+
}
|
|
1722
|
+
try {
|
|
1723
|
+
allResults.push(analyzeDoc(doc, collectionSlug, mergedConfig));
|
|
1724
|
+
} catch (e) {
|
|
1725
|
+
req.payload.logger.warn(
|
|
1726
|
+
`[seo] audit: skipped ${collectionSlug}/${doc.id}: ${e instanceof Error ? e.message : "error"}`
|
|
1727
|
+
);
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
hasMore = result.hasNextPage;
|
|
1731
|
+
page2++;
|
|
1732
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
1712
1733
|
}
|
|
1713
|
-
|
|
1714
|
-
page2++;
|
|
1734
|
+
} catch {
|
|
1715
1735
|
}
|
|
1716
|
-
} catch {
|
|
1717
1736
|
}
|
|
1737
|
+
if (capped2) {
|
|
1738
|
+
req.payload.logger.warn(
|
|
1739
|
+
`[seo] audit: capped at ${MAX_DOCS2} docs (SEO_AUDIT_MAX_DOCS). Lower SEO_AUDIT_BATCH_SIZE on low-memory hosts, or raise the cap.`
|
|
1740
|
+
);
|
|
1718
1741
|
}
|
|
1719
1742
|
for (const globalSlug of globals) {
|
|
1720
1743
|
try {
|
|
@@ -1777,10 +1800,10 @@ function createAuditHandler(collections, seoConfig, globals = []) {
|
|
|
1777
1800
|
avgWordCount: totalDocs2 > 0 ? Math.round(enrichedResults2.reduce((s, r) => s + r.wordCount, 0) / totalDocs2) : 0,
|
|
1778
1801
|
avgReadability: totalDocs2 > 0 ? Math.round(enrichedResults2.reduce((s, r) => s + r.readabilityScore, 0) / totalDocs2) : 0
|
|
1779
1802
|
};
|
|
1780
|
-
cached = { enrichedResults: enrichedResults2, stats: stats2 };
|
|
1803
|
+
cached = { enrichedResults: enrichedResults2, stats: stats2, capped: capped2 };
|
|
1781
1804
|
seoCache.set(CACHE_KEY, cached);
|
|
1782
1805
|
}
|
|
1783
|
-
const { enrichedResults, stats } = cached;
|
|
1806
|
+
const { enrichedResults, stats, capped } = cached;
|
|
1784
1807
|
const totalDocs = enrichedResults.length;
|
|
1785
1808
|
const totalPages = Math.ceil(totalDocs / limit);
|
|
1786
1809
|
const startIdx = (page - 1) * limit;
|
|
@@ -1796,7 +1819,8 @@ function createAuditHandler(collections, seoConfig, globals = []) {
|
|
|
1796
1819
|
hasNextPage: page < totalPages,
|
|
1797
1820
|
hasPrevPage: page > 1
|
|
1798
1821
|
},
|
|
1799
|
-
cached: !noCache && seoCache.get(CACHE_KEY) !== null
|
|
1822
|
+
cached: !noCache && seoCache.get(CACHE_KEY) !== null,
|
|
1823
|
+
capped
|
|
1800
1824
|
}, { headers: { "Cache-Control": "no-store" } });
|
|
1801
1825
|
} catch (error) {
|
|
1802
1826
|
const message = error instanceof Error ? error.message : "Internal server error";
|
|
@@ -8657,6 +8681,8 @@ var seoAnalyzerPlugin = (pluginConfig = {}) => (incomingConfig) => {
|
|
|
8657
8681
|
settings: true,
|
|
8658
8682
|
gscApi: false,
|
|
8659
8683
|
// opt-in — requires Google Cloud OAuth setup + secrets
|
|
8684
|
+
warmCache: true,
|
|
8685
|
+
// disable on low-memory hosts to skip startup pre-loading
|
|
8660
8686
|
...pluginConfig.features
|
|
8661
8687
|
};
|
|
8662
8688
|
function hasExistingSeoMeta(fields) {
|
|
@@ -9083,7 +9109,9 @@ var seoAnalyzerPlugin = (pluginConfig = {}) => (incomingConfig) => {
|
|
|
9083
9109
|
const existingOnInit = config.onInit;
|
|
9084
9110
|
config.onInit = async (payload) => {
|
|
9085
9111
|
if (existingOnInit) await existingOnInit(payload);
|
|
9086
|
-
|
|
9112
|
+
if (features.warmCache) {
|
|
9113
|
+
startCacheWarmUp(payload, basePath, targetGlobals, targetCollections);
|
|
9114
|
+
}
|
|
9087
9115
|
};
|
|
9088
9116
|
return config;
|
|
9089
9117
|
};
|
package/dist/index.d.cts
CHANGED
|
@@ -168,6 +168,11 @@ interface SeoFeatures {
|
|
|
168
168
|
* enabled, the redirect URI registered, and ideally SEO_GSC_ENCRYPTION_KEY for token-at-rest.
|
|
169
169
|
*/
|
|
170
170
|
gscApi?: boolean;
|
|
171
|
+
/**
|
|
172
|
+
* Background cache warm-up on init + hourly (pre-loads collection data). Enabled by
|
|
173
|
+
* default. Set to `false` on low-memory hosting to avoid loading all documents at startup.
|
|
174
|
+
*/
|
|
175
|
+
warmCache?: boolean;
|
|
171
176
|
/** Duplicate content detection (endpoint) */
|
|
172
177
|
duplicateContent?: boolean;
|
|
173
178
|
/** Settings view (/admin/seo-config) */
|
package/dist/index.d.ts
CHANGED
|
@@ -168,6 +168,11 @@ interface SeoFeatures {
|
|
|
168
168
|
* enabled, the redirect URI registered, and ideally SEO_GSC_ENCRYPTION_KEY for token-at-rest.
|
|
169
169
|
*/
|
|
170
170
|
gscApi?: boolean;
|
|
171
|
+
/**
|
|
172
|
+
* Background cache warm-up on init + hourly (pre-loads collection data). Enabled by
|
|
173
|
+
* default. Set to `false` on low-memory hosting to avoid loading all documents at startup.
|
|
174
|
+
*/
|
|
175
|
+
warmCache?: boolean;
|
|
171
176
|
/** Duplicate content detection (endpoint) */
|
|
172
177
|
duplicateContent?: boolean;
|
|
173
178
|
/** Settings view (/admin/seo-config) */
|
package/dist/index.js
CHANGED
|
@@ -1630,7 +1630,10 @@ function analyzeDoc(doc, collection, seoConfig) {
|
|
|
1630
1630
|
if (seoInput.isGlobal) {
|
|
1631
1631
|
seoInput.slug = "";
|
|
1632
1632
|
}
|
|
1633
|
-
const analysis = analyzeSeo(seoInput,
|
|
1633
|
+
const analysis = analyzeSeo(seoInput, {
|
|
1634
|
+
...seoConfig,
|
|
1635
|
+
disabledRules: [...seoConfig?.disabledRules ?? [], "geo", "eeat", "hreflang"]
|
|
1636
|
+
});
|
|
1634
1637
|
const extracted = extractDocContent(doc);
|
|
1635
1638
|
const fullText = extracted.text;
|
|
1636
1639
|
const allLinks = extracted.links;
|
|
@@ -1691,28 +1694,48 @@ function createAuditHandler(collections, seoConfig, globals = []) {
|
|
|
1691
1694
|
let cached = noCache ? null : seoCache.get(CACHE_KEY);
|
|
1692
1695
|
if (!cached) {
|
|
1693
1696
|
const { config: mergedConfig, ignoredSlugs } = await loadMergedConfig(req.payload, seoConfig);
|
|
1697
|
+
const BATCH_SIZE = Math.min(100, Math.max(1, parseInt(process.env.SEO_AUDIT_BATCH_SIZE || "25", 10) || 25));
|
|
1698
|
+
const MAX_DOCS2 = Math.max(1, parseInt(process.env.SEO_AUDIT_MAX_DOCS || "1500", 10) || 1500);
|
|
1694
1699
|
const allResults = [];
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1700
|
+
let capped2 = false;
|
|
1701
|
+
collectionsLoop:
|
|
1702
|
+
for (const collectionSlug of collections) {
|
|
1703
|
+
try {
|
|
1704
|
+
let page2 = 1;
|
|
1705
|
+
let hasMore = true;
|
|
1706
|
+
while (hasMore) {
|
|
1707
|
+
const result = await req.payload.find({
|
|
1708
|
+
collection: collectionSlug,
|
|
1709
|
+
limit: BATCH_SIZE,
|
|
1710
|
+
page: page2,
|
|
1711
|
+
depth: 1,
|
|
1712
|
+
overrideAccess: true
|
|
1713
|
+
});
|
|
1714
|
+
for (const doc of result.docs) {
|
|
1715
|
+
if (ignoredSlugs.includes(doc.slug)) continue;
|
|
1716
|
+
if (allResults.length >= MAX_DOCS2) {
|
|
1717
|
+
capped2 = true;
|
|
1718
|
+
break collectionsLoop;
|
|
1719
|
+
}
|
|
1720
|
+
try {
|
|
1721
|
+
allResults.push(analyzeDoc(doc, collectionSlug, mergedConfig));
|
|
1722
|
+
} catch (e) {
|
|
1723
|
+
req.payload.logger.warn(
|
|
1724
|
+
`[seo] audit: skipped ${collectionSlug}/${doc.id}: ${e instanceof Error ? e.message : "error"}`
|
|
1725
|
+
);
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
hasMore = result.hasNextPage;
|
|
1729
|
+
page2++;
|
|
1730
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
1710
1731
|
}
|
|
1711
|
-
|
|
1712
|
-
page2++;
|
|
1732
|
+
} catch {
|
|
1713
1733
|
}
|
|
1714
|
-
} catch {
|
|
1715
1734
|
}
|
|
1735
|
+
if (capped2) {
|
|
1736
|
+
req.payload.logger.warn(
|
|
1737
|
+
`[seo] audit: capped at ${MAX_DOCS2} docs (SEO_AUDIT_MAX_DOCS). Lower SEO_AUDIT_BATCH_SIZE on low-memory hosts, or raise the cap.`
|
|
1738
|
+
);
|
|
1716
1739
|
}
|
|
1717
1740
|
for (const globalSlug of globals) {
|
|
1718
1741
|
try {
|
|
@@ -1775,10 +1798,10 @@ function createAuditHandler(collections, seoConfig, globals = []) {
|
|
|
1775
1798
|
avgWordCount: totalDocs2 > 0 ? Math.round(enrichedResults2.reduce((s, r) => s + r.wordCount, 0) / totalDocs2) : 0,
|
|
1776
1799
|
avgReadability: totalDocs2 > 0 ? Math.round(enrichedResults2.reduce((s, r) => s + r.readabilityScore, 0) / totalDocs2) : 0
|
|
1777
1800
|
};
|
|
1778
|
-
cached = { enrichedResults: enrichedResults2, stats: stats2 };
|
|
1801
|
+
cached = { enrichedResults: enrichedResults2, stats: stats2, capped: capped2 };
|
|
1779
1802
|
seoCache.set(CACHE_KEY, cached);
|
|
1780
1803
|
}
|
|
1781
|
-
const { enrichedResults, stats } = cached;
|
|
1804
|
+
const { enrichedResults, stats, capped } = cached;
|
|
1782
1805
|
const totalDocs = enrichedResults.length;
|
|
1783
1806
|
const totalPages = Math.ceil(totalDocs / limit);
|
|
1784
1807
|
const startIdx = (page - 1) * limit;
|
|
@@ -1794,7 +1817,8 @@ function createAuditHandler(collections, seoConfig, globals = []) {
|
|
|
1794
1817
|
hasNextPage: page < totalPages,
|
|
1795
1818
|
hasPrevPage: page > 1
|
|
1796
1819
|
},
|
|
1797
|
-
cached: !noCache && seoCache.get(CACHE_KEY) !== null
|
|
1820
|
+
cached: !noCache && seoCache.get(CACHE_KEY) !== null,
|
|
1821
|
+
capped
|
|
1798
1822
|
}, { headers: { "Cache-Control": "no-store" } });
|
|
1799
1823
|
} catch (error) {
|
|
1800
1824
|
const message = error instanceof Error ? error.message : "Internal server error";
|
|
@@ -8655,6 +8679,8 @@ var seoAnalyzerPlugin = (pluginConfig = {}) => (incomingConfig) => {
|
|
|
8655
8679
|
settings: true,
|
|
8656
8680
|
gscApi: false,
|
|
8657
8681
|
// opt-in — requires Google Cloud OAuth setup + secrets
|
|
8682
|
+
warmCache: true,
|
|
8683
|
+
// disable on low-memory hosts to skip startup pre-loading
|
|
8658
8684
|
...pluginConfig.features
|
|
8659
8685
|
};
|
|
8660
8686
|
function hasExistingSeoMeta(fields) {
|
|
@@ -9081,7 +9107,9 @@ var seoAnalyzerPlugin = (pluginConfig = {}) => (incomingConfig) => {
|
|
|
9081
9107
|
const existingOnInit = config.onInit;
|
|
9082
9108
|
config.onInit = async (payload) => {
|
|
9083
9109
|
if (existingOnInit) await existingOnInit(payload);
|
|
9084
|
-
|
|
9110
|
+
if (features.warmCache) {
|
|
9111
|
+
startCacheWarmUp(payload, basePath, targetGlobals, targetCollections);
|
|
9112
|
+
}
|
|
9085
9113
|
};
|
|
9086
9114
|
return config;
|
|
9087
9115
|
};
|
package/package.json
CHANGED