@conduction/docusaurus-preset 3.6.1 → 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.
@@ -89,17 +89,22 @@ check('sitemap.xml exists and has at least 1 URL', () => {
89
89
 
90
90
  /* sitemap.xml should ship <lastmod> on every URL. Google treats lastmod
91
91
  as the only sitemap-level signal that actually informs recrawl
92
- priority, and only when it's trustworthy. Sites that ship priority +
93
- changefreq without lastmod (the Docusaurus default before preset
94
- 3.6.0) get treated as having no freshness signal. */
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. */
95
96
  check('sitemap.xml emits <lastmod> on URLs', () => {
96
97
  const body = readBuild('sitemap.xml');
97
98
  const locCount = (body.match(/<loc>/g) || []).length;
98
99
  const lastmodCount = (body.match(/<lastmod>/g) || []).length;
99
100
  if (locCount === 0) return {ok: false, msg: 'no <loc> entries to compare against'};
100
- if (lastmodCount === 0) return {ok: false, msg: `0 / ${locCount} URLs have <lastmod> — enable sitemap.lastmod in docusaurus.config`};
101
+ if (lastmodCount === 0) {
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.`};
103
+ }
101
104
  const ratio = lastmodCount / locCount;
102
- if (ratio < 0.5) return {ok: false, msg: `only ${lastmodCount} / ${locCount} URLs have <lastmod>`};
105
+ if (ratio < 0.9) {
106
+ return {ok: false, msg: `only ${lastmodCount} / ${locCount} URLs have <lastmod>`};
107
+ }
103
108
  return {ok: true, msg: `${lastmodCount} / ${locCount} URLs (${Math.round(ratio * 100)}%)`};
104
109
  });
105
110
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@conduction/docusaurus-preset",
3
- "version": "3.6.1",
3
+ "version": "3.7.0",
4
4
  "scripts": {
5
5
  "prepack": "node scripts/prepack-bundle-css.js"
6
6
  },
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
- presets: opts.presets || [
480
- [
481
- 'classic',
482
- {
483
- docs: {
484
- sidebarPath: './sidebars.js',
485
- editUrl: opts.editUrl,
486
- },
487
- blog: opts.blog === false ? false : {
488
- showReadingTime: true,
489
- blogTitle: opts.title + ' blog',
490
- blogDescription: 'Updates from Conduction',
491
- },
492
- theme: {
493
- customCss,
494
- },
495
- /* AI-crawler-friendly defaults for @docusaurus/plugin-sitemap.
496
- Per-locale sitemap.xml emitted automatically; /academy/tags
497
- excluded across every locale prefix. Sites passing their own
498
- presets array must include their own sitemap config too. */
499
- sitemap: DEFAULT_SITEMAP_OPTIONS,
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 every
585
- consuming site's /img/og-conduction.png; drop your own
586
- static/img/og-conduction.png to override per-site. `metadata`
587
- seeds twitter:site + twitter:card + og:type baselines; per-
588
- page MDX frontmatter still wins via Helmet de-dupe. */
589
- image: DEFAULT_OG_IMAGE,
590
- metadata: DEFAULT_METADATA,
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