@stackql/docusaurus-plugin-aeo 0.1.0 → 0.1.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/CHANGELOG.md CHANGED
@@ -1,5 +1,41 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.1
4
+
5
+ Bugfix release. v0.1.0 failed to build on a real Docusaurus 3.10 consumer with three distinct crashes; all three are fixed here.
6
+
7
+ ### Fixed
8
+
9
+ - **Plugin construction crash: `plugin.options.id` is `undefined`.** v0.1.0 exported a `validateOptions` function that bypassed Docusaurus's option normalization, so the standard `id: 'default'` default was never applied. `@docusaurus/core` then crashed in `lib/server/plugins/actions.js` at `createPluginActionsUtils`:
10
+
11
+ ```text
12
+ TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string.
13
+ Received undefined
14
+ at Object.join (node:path:460:7)
15
+ at createPluginActionsUtils (.../@docusaurus/core/lib/server/plugins/actions.js:27:36)
16
+ ```
17
+
18
+ Fix: removed the `validateOptions` export so Docusaurus's built-in plugin schema runs. The plugin's handwritten construction-time validator (`normalizeOptions` + the internal `validateOptions` in `src/index.js`) still validates plugin-specific options.
19
+
20
+ - **SSR stack overflow on every doc and blog page.** The footer wrappers at `src/theme/DocItem/Footer/index.jsx` and `src/theme/BlogPostItem/Footer/index.jsx` imported `@theme-original/DocItem/Footer` and `@theme-original/BlogPostItem/Footer`. When a *plugin* (not a *theme*) contributes the wrapper and is the only contributor in the wrapper layer, `@theme-original/X` resolves back to the wrapper itself, recursing during SSG:
21
+
22
+ ```text
23
+ Error: Can't render static file for pathname "/docs/intro"
24
+ [cause]: RangeError: Maximum call stack size exceeded
25
+ at RegExp.exec (<anonymous>)
26
+ at F (server.bundle.js:15836:87)
27
+ at Ka (server.bundle.js:15845:249)
28
+ at Pa (server.bundle.js:15853:68)
29
+ ```
30
+
31
+ Fix: switched both wrappers to import from `@theme-init/X`, which always resolves to the un-wrapped initial component from the theme chain.
32
+
33
+ - **`getThemePath()` returning `undefined` crashes core.** With `askAi.enabled: false` (or `askAi.placement: 'none'`), v0.1.0's `getThemePath` returned `undefined`, which `@docusaurus/core` then handed to `path.join` inside `webpack/server.js`. Fix: the plugin now constructs its plugin object conditionally and only attaches `getThemePath` when the theme is enabled. This is the idiomatic signal that a plugin contributes no theme.
34
+
35
+ ### Notes for consumers
36
+
37
+ No config changes required. Bump the version and rebuild.
38
+
3
39
  ## 0.1.0
4
40
 
5
41
  Initial release.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackql/docusaurus-plugin-aeo",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "AEO (Answer Engine Optimization) helpers for Docusaurus: .md companion files, llms.txt / llms-full.txt, an Ask AI dropdown, and /ai/* route conventions.",
5
5
  "main": "src/index.js",
6
6
  "exports": {
package/src/index.js CHANGED
@@ -113,19 +113,11 @@ module.exports = function pluginAeo(context, rawOptions) {
113
113
  // Map<pluginName, { plugin: { name, id }, content: any }>
114
114
  const loadedContentByPlugin = new Map();
115
115
 
116
- return {
117
- name: '@stackql/docusaurus-plugin-aeo',
118
-
119
- getThemePath() {
120
- if (!options.askAi.enabled || options.askAi.placement === 'none') {
121
- return undefined;
122
- }
123
- return path.resolve(__dirname, './theme');
124
- },
116
+ const themeEnabled =
117
+ options.askAi.enabled && options.askAi.placement !== 'none';
125
118
 
126
- getClientModules() {
127
- return [];
128
- },
119
+ const plugin = {
120
+ name: '@stackql/docusaurus-plugin-aeo',
129
121
 
130
122
  // Surface the askAi config to theme components via a global data
131
123
  // injection. Docusaurus client code can read this through useDocusaurusContext().
@@ -188,13 +180,18 @@ module.exports = function pluginAeo(context, rawOptions) {
188
180
  }
189
181
  },
190
182
  };
191
- };
192
183
 
193
- module.exports.validateOptions = function validateDocusaurusOptions({
194
- options,
195
- validate: _validate,
196
- }) {
197
- // Docusaurus passes a Joi validator we deliberately don't use - the plugin
198
- // entry runs its own handwritten validator at construction time.
199
- return options || {};
184
+ // Only contribute a theme path when the Ask AI button is enabled.
185
+ // Returning undefined or an invalid value from getThemePath crashes
186
+ // @docusaurus/core in webpack/server.js; omitting the method entirely
187
+ // is the idiomatic signal that this plugin contributes no theme.
188
+ if (themeEnabled) {
189
+ plugin.getThemePath = () => path.resolve(__dirname, './theme');
190
+ }
191
+
192
+ return plugin;
200
193
  };
194
+
195
+ // Intentionally no validateOptions export: Docusaurus applies its own default
196
+ // option normalization (including id: 'default') when this is absent.
197
+ // The plugin's handwritten validator runs at construction time.
@@ -1,5 +1,8 @@
1
1
  import React from 'react';
2
- import Footer from '@theme-original/BlogPostItem/Footer';
2
+ // See note in src/theme/DocItem/Footer/index.jsx - @theme-init avoids the
3
+ // SSR recursion that @theme-original triggers when a plugin (not a theme)
4
+ // contributes the wrapper.
5
+ import Footer from '@theme-init/BlogPostItem/Footer';
3
6
  import AskAiButton from '@theme/AskAiButton';
4
7
 
5
8
  export default function FooterWrapper(props) {
@@ -1,5 +1,10 @@
1
1
  import React from 'react';
2
- import Footer from '@theme-original/DocItem/Footer';
2
+ // Use @theme-init (the initial component from the theme chain, before any
3
+ // wrappers) instead of @theme-original. When a plugin contributes both the
4
+ // wrapper and is the only contributor in the wrapper layer,
5
+ // @theme-original/X resolves back to this wrapper file and renders infinitely
6
+ // on every SSR pass. @theme-init/X always resolves to the un-wrapped origin.
7
+ import Footer from '@theme-init/DocItem/Footer';
3
8
  import AskAiButton from '@theme/AskAiButton';
4
9
 
5
10
  export default function FooterWrapper(props) {