@conduction/docusaurus-preset 3.6.2 → 3.7.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/bin/validate-ai-baseline.mjs +11 -12
- package/package.json +1 -1
- package/src/index.js +90 -32
|
@@ -87,25 +87,24 @@ check('sitemap.xml exists and has at least 1 URL', () => {
|
|
|
87
87
|
return {ok: true, msg: `${n} URLs`};
|
|
88
88
|
});
|
|
89
89
|
|
|
90
|
-
/* sitemap.xml lastmod
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
release once the preset wraps user presets to inject sitemap defaults
|
|
98
|
-
automatically. */
|
|
99
|
-
check('sitemap.xml emits <lastmod> on URLs (advisory, not fatal)', () => {
|
|
90
|
+
/* sitemap.xml should ship <lastmod> on every URL. Google treats lastmod
|
|
91
|
+
as the only sitemap-level signal that actually informs recrawl
|
|
92
|
+
priority, and only when it's trustworthy. Preset 3.7+ wraps user-
|
|
93
|
+
supplied opts.presets to inject DEFAULT_SITEMAP_OPTIONS (lastmod:
|
|
94
|
+
'date') into any classic preset entry, so every site that bumps
|
|
95
|
+
should see lastmod automatically. Hard-fail blocks regression. */
|
|
96
|
+
check('sitemap.xml emits <lastmod> on URLs', () => {
|
|
100
97
|
const body = readBuild('sitemap.xml');
|
|
101
98
|
const locCount = (body.match(/<loc>/g) || []).length;
|
|
102
99
|
const lastmodCount = (body.match(/<lastmod>/g) || []).length;
|
|
103
100
|
if (locCount === 0) return {ok: false, msg: 'no <loc> entries to compare against'};
|
|
104
101
|
if (lastmodCount === 0) {
|
|
105
|
-
|
|
106
|
-
return {ok: true, msg: `WARN 0 / ${locCount} URLs have <lastmod> (enable sitemap.lastmod in docusaurus.config)`};
|
|
102
|
+
return {ok: false, msg: `0 / ${locCount} URLs have <lastmod>. Upgrade to @conduction/docusaurus-preset ^3.7.0 or set sitemap.lastmod in docusaurus.config.`};
|
|
107
103
|
}
|
|
108
104
|
const ratio = lastmodCount / locCount;
|
|
105
|
+
if (ratio < 0.9) {
|
|
106
|
+
return {ok: false, msg: `only ${lastmodCount} / ${locCount} URLs have <lastmod>`};
|
|
107
|
+
}
|
|
109
108
|
return {ok: true, msg: `${lastmodCount} / ${locCount} URLs (${Math.round(ratio * 100)}%)`};
|
|
110
109
|
});
|
|
111
110
|
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -292,6 +292,41 @@ const I18N = {
|
|
|
292
292
|
},
|
|
293
293
|
};
|
|
294
294
|
|
|
295
|
+
/**
|
|
296
|
+
* Wrap a user-supplied `opts.presets` array so the classic preset's
|
|
297
|
+
* `sitemap` option inherits brand defaults (lastmod, ignorePatterns)
|
|
298
|
+
* when the site hasn't set them explicitly. Before this helper, sites
|
|
299
|
+
* that passed their own presets array silently lost the preset's
|
|
300
|
+
* DEFAULT_SITEMAP_OPTIONS, so the fleet sitemaps never shipped
|
|
301
|
+
* <lastmod> tags despite the preset claiming to add them. See
|
|
302
|
+
* MEMORY.md project_preset-4.0-wrap-user-presets for the back story.
|
|
303
|
+
*
|
|
304
|
+
* Recognises both 'classic' and '@docusaurus/preset-classic' entries.
|
|
305
|
+
* Leaves non-classic presets untouched. Explicit user values win:
|
|
306
|
+
* `sitemap: null` opts out, `sitemap: { lastmod: false }` opts out
|
|
307
|
+
* of lastmod specifically, and so on.
|
|
308
|
+
*/
|
|
309
|
+
function wrapClassicPresetDefaults(userPresets) {
|
|
310
|
+
return userPresets.map(entry => {
|
|
311
|
+
if (!Array.isArray(entry)) return entry;
|
|
312
|
+
const [name, config] = entry;
|
|
313
|
+
const isClassic =
|
|
314
|
+
name === 'classic' || name === '@docusaurus/preset-classic';
|
|
315
|
+
if (!isClassic || typeof config !== 'object' || config === null) return entry;
|
|
316
|
+
if (config.sitemap === null) return entry; /* explicit opt-out */
|
|
317
|
+
return [
|
|
318
|
+
name,
|
|
319
|
+
{
|
|
320
|
+
...config,
|
|
321
|
+
sitemap: {
|
|
322
|
+
...DEFAULT_SITEMAP_OPTIONS,
|
|
323
|
+
...(config.sitemap || {}),
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
];
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
|
|
295
330
|
/**
|
|
296
331
|
* Brand-default navbar. Sites pass their own items[] and logo; the chrome
|
|
297
332
|
* styling (cobalt-on-white, Plex-Mono caption) is locked.
|
|
@@ -476,30 +511,34 @@ function createConfig(opts) {
|
|
|
476
511
|
|
|
477
512
|
i18n: opts.i18n || I18N,
|
|
478
513
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
514
|
+
/* Sites can pass `opts.presets` to override docs/blog/theme. When
|
|
515
|
+
they do, the preset wraps each classic preset entry to deep-merge
|
|
516
|
+
DEFAULT_SITEMAP_OPTIONS into the entry's sitemap key (lastmod
|
|
517
|
+
becomes automatic, ignorePatterns merge in). Without this wrap
|
|
518
|
+
sites would have to copy-paste the sitemap config in every
|
|
519
|
+
docusaurus.config.js, and the fleet would drift over time. */
|
|
520
|
+
presets: opts.presets
|
|
521
|
+
? wrapClassicPresetDefaults(opts.presets)
|
|
522
|
+
: [
|
|
523
|
+
[
|
|
524
|
+
'classic',
|
|
525
|
+
{
|
|
526
|
+
docs: {
|
|
527
|
+
sidebarPath: './sidebars.js',
|
|
528
|
+
editUrl: opts.editUrl,
|
|
529
|
+
},
|
|
530
|
+
blog: opts.blog === false ? false : {
|
|
531
|
+
showReadingTime: true,
|
|
532
|
+
blogTitle: opts.title + ' blog',
|
|
533
|
+
blogDescription: 'Updates from Conduction',
|
|
534
|
+
},
|
|
535
|
+
theme: {
|
|
536
|
+
customCss,
|
|
537
|
+
},
|
|
538
|
+
sitemap: DEFAULT_SITEMAP_OPTIONS,
|
|
539
|
+
},
|
|
540
|
+
],
|
|
541
|
+
],
|
|
503
542
|
|
|
504
543
|
/* Brand theme: registers ./theme/* swizzles (Navbar, Footer, …)
|
|
505
544
|
and auto-loads brand.css. Site-specific themes can be added by
|
|
@@ -581,15 +620,34 @@ function createConfig(opts) {
|
|
|
581
620
|
opts.legalLinks || {}
|
|
582
621
|
),
|
|
583
622
|
/* AI-friendly social-card defaults. `image` ships from the
|
|
584
|
-
preset's static/img/og-conduction.png and gets served at
|
|
585
|
-
consuming site's /img/og-conduction.png; drop your
|
|
586
|
-
static/img/og-conduction.png to override per-site
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
623
|
+
preset's static/img/og-conduction.png and gets served at
|
|
624
|
+
every consuming site's /img/og-conduction.png; drop your
|
|
625
|
+
own static/img/og-conduction.png to override per-site, or
|
|
626
|
+
pass `themeConfig.image: 'img/og-my-app.png'` to use a
|
|
627
|
+
different file. `metadata` seeds twitter:site + twitter:card
|
|
628
|
+
+ og:type baselines; per-page MDX frontmatter still wins
|
|
629
|
+
via Helmet de-dupe.
|
|
630
|
+
|
|
631
|
+
These two slots are handled below via explicit overrides
|
|
632
|
+
rather than the wholesale Object.assign so that user-set
|
|
633
|
+
metadata extends (rather than replaces) the brand defaults
|
|
634
|
+
and image falls back gracefully. */
|
|
635
|
+
image: opts.themeConfig?.image || DEFAULT_OG_IMAGE,
|
|
636
|
+
metadata: [
|
|
637
|
+
...DEFAULT_METADATA,
|
|
638
|
+
...(opts.themeConfig?.metadata || []),
|
|
639
|
+
],
|
|
591
640
|
},
|
|
592
|
-
opts.themeConfig
|
|
641
|
+
/* Object.assign last so opts.themeConfig overrides primitives
|
|
642
|
+
like colorMode and navbar, but the image + metadata keys
|
|
643
|
+
above pre-merged the user's values so the spread doesn't
|
|
644
|
+
clobber the brand defaults. */
|
|
645
|
+
(() => {
|
|
646
|
+
if (!opts.themeConfig) return {};
|
|
647
|
+
/* eslint-disable-next-line no-unused-vars */
|
|
648
|
+
const {image: _image, metadata: _metadata, ...rest} = opts.themeConfig;
|
|
649
|
+
return rest;
|
|
650
|
+
})()
|
|
593
651
|
),
|
|
594
652
|
|
|
595
653
|
/* AI-crawler discovery: Organization + WebSite JSON-LD on every
|