@stackql/docusaurus-plugin-aeo 0.4.2 → 0.5.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 +55 -0
- package/LICENSE +0 -0
- package/README.md +179 -45
- package/package.json +21 -5
- package/src/askAiPrompt.js +22 -0
- package/src/companionPath.js +89 -0
- package/src/features/aiRoutes.js +15 -6
- package/src/features/alternateLinks.js +133 -0
- package/src/features/companions.js +93 -104
- package/src/features/llmsTxt.js +126 -84
- package/src/features/plainMarkdown.js +623 -0
- package/src/helpers.js +34 -10
- package/src/index.js +70 -10
- package/src/routeGlob.js +60 -0
- package/src/theme/AskAiButton/brand-icons/ChatGpt.jsx +0 -0
- package/src/theme/AskAiButton/brand-icons/Claude.jsx +0 -0
- package/src/theme/AskAiButton/brand-icons/Perplexity.jsx +0 -0
- package/src/theme/AskAiButton/icons.js +0 -0
- package/src/theme/AskAiButton/index.jsx +64 -12
- package/src/theme/AskAiButton/styles.module.css +0 -0
- package/src/theme/BlogPostItem/Header/Title/index.jsx +0 -0
- package/src/theme/BlogPostItem/Header/Title/styles.module.css +0 -0
- package/src/theme/DocBreadcrumbs/index.jsx +0 -0
- package/src/theme/DocBreadcrumbs/styles.module.css +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,60 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.1
|
|
4
|
+
|
|
5
|
+
Localized sites (Docusaurus i18n) and sites with a non-root `baseUrl`. Raised against stackql.io when it gained a `de` locale.
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- **Companions and alternate links on a site whose `baseUrl` is not `/`.** Docusaurus permalinks carry `siteConfig.baseUrl` but the build output tree is rooted at it, and in a localized build the plugin runs once per locale with `baseUrl: '/<locale>/'` and `outDir: build/<locale>`. Feature 1 joined `outDir` with the full permalink, so a `de` site wrote its companions to `build/de/de/foo.md`, and feature 1b looked for the page's HTML at the same wrong path and injected nothing. Both now strip the base URL from the route before touching the file system (`outputRoute` in `src/companionPath.js`); the companion URLs, the `Source:` lines, `llms.txt` and the `_headers` routes keep the full route as before. A localized build therefore emits `/<locale>/foo.md`, `/<locale>/llms.txt` and `/<locale>/llms-full.txt` from the translated sources with no configuration.
|
|
10
|
+
- **Route patterns in a localized build or under a non-root `baseUrl`.** `companions.exclude`, `llmsTxt.exclude` and `llmsTxt.include` were matched against the full permalink, which carries `siteConfig.baseUrl`: `exclude: ['/docs/internal/**']` skipped the page in the default locale and kept `/de/docs/internal/...` in the `de` build, and the default `llmsTxt.exclude` list had the same gap. A pattern now matches a route with or without the base URL in front of it (`src/routeGlob.js`), so one configuration covers every locale. A pattern that spells the base out (`/de/blog/**`, or `/docs/guide/**` on a site built at `/docs/`) matches as before and can single out one locale. The Ask AI button follows, since it shows on the routes that have a companion.
|
|
11
|
+
- **`aiRoutes.validate` under a base URL.** The `/ai/*` convention was looked for at the start of the permalink, so pages at `/de/ai/...` or `/docs/ai/...` were never validated. It is now relative to `siteConfig.baseUrl`.
|
|
12
|
+
- **`_headers` for a non-default locale** (`companions.linkHeader`). Each build wrote its rules to `<outDir>/_headers`, which for a non-default locale is `build/<locale>/_headers`. Netlify and Cloudflare Pages document the file at the root of the published directory, so rules in a sub-directory cannot be relied on. In a build of several locales, the rules of every locale now go in the one `_headers` at the build root (Docusaurus builds the default locale first, so the file is there). A locale built on its own (`docusaurus build --locale de`) or given its own `baseUrl` keeps the file in its `outDir`. A rule already in the file is not appended a second time.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- The Ask AI button's visible strings ("Ask AI about this page", "Ask Claude", "Ask ChatGPT", "Ask Perplexity") go through `translate()` with the ids `aeo.askAi.button` and `aeo.askAi.provider.<key>`, so `docusaurus write-translations` extracts them into `i18n/<locale>/code.json` and a localized site can translate them.
|
|
17
|
+
- `{pageUrl}` in the Ask AI prompt ends with a slash on a root: the site root (`https://site/`, previously `https://site`) and a base URL such as `https://site/de/`, as the `Source:` lines already did. Other pages are unchanged (no trailing slash), and so is the default prompt, which uses `{companionUrl}`.
|
|
18
|
+
- The Ask AI button's hover background is a 4% tint of the site's `--ifm-color-primary` instead of a fixed colour.
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- `aiRoutePatterns(baseUrl)` in `@stackql/docusaurus-plugin-aeo/helpers`, and an optional `baseUrl` argument on `isAiRoute` and `buildStructuredDataAiExcludes`. `AI_ROUTE_PATTERNS` and `sitemapExclude` are unchanged and describe a site at the root with one locale; a site with a non-root `baseUrl` or several locales builds its list with `['/', '/de/'].flatMap(aiRoutePatterns)`.
|
|
23
|
+
- README: hosting notes. Which hosts `companions.linkHeader` applies to (those that read `_headers`: Netlify, Cloudflare Pages, with Cloudflare's 100-rule limit), where the file is written, `.nojekyll` on GitHub Pages, and absolute URLs on preview deployments.
|
|
24
|
+
- Tests for all of the above and for the rest of this release: companion files and `llms.txt` under a base URL, the plugin run once per locale, and the Ask AI button rendered in Node (`test/companions.test.js`, `test/localizedBuild.test.js`, `test/askAiButton.test.js`, `test/routeGlob.test.js`, `test/aiHelpers.test.js`).
|
|
25
|
+
|
|
26
|
+
## 0.5.0
|
|
27
|
+
|
|
28
|
+
Plain-markdown companions, one rule for where a companion lives, and the companion advertised from its page. Raised against stackql.io, where the Ask AI prompt on the homepage read `https://stackql.io.md` and every `.md` URL returned raw MDX (imports, MUI components, tab definitions and inline SVG path data before the first sentence), and from an independent agent-readiness audit of the built site (alternate link, `llms-full.txt` reference and size, `Source:` URLs, the `how-tos` alias).
|
|
29
|
+
|
|
30
|
+
### Changed (breaking)
|
|
31
|
+
|
|
32
|
+
- **`companions.format` now defaults to `'plain'`.** The companion is plain markdown rendered from the MDX source rather than the source itself. The source is parsed with the remark toolchain Docusaurus compiles it with (`remark-mdx`, `remark-gfm`, `remark-directive`, `@slorber/remark-comment`) to locate every MDX construct, and the original text is edited by source position: imports, exports and `{expressions}` are removed; presentational elements (`svg`, `video`, `iframe`, icons) are dropped; `<Tabs>`/`<TabItem>` become a bold label per tab with the tab's content under it (the label is read from the `label` attribute or from the `<Tabs values={[...]}>` expression); `<a>`, `<img>`, `<b>`, `<i>`, `<code>`, `<h1>`-`<h6>`, `<br>`, `<details>`/`<summary>` become their markdown equivalent; a self-closing component that carries a link (`to`/`href` plus `text`/`label`/`title`) becomes a markdown link; every other element is unwrapped and the indentation its author added inside it is removed. HTML comments (including `<!-- truncate -->`) and `{#custom-id}` heading ids are removed; fenced code is never touched. Nothing outside MDX syntax is re-stringified, so `snake_case` stays unescaped and tables, admonitions and code fences are byte for byte the author's. A `# Title` / `> description` block from the page metadata is prepended (the title only when the body does not already open with an H1). A source that fails to parse falls back to the v0.4 regex stripper with a build warning. `format: md` files (front matter or `siteConfig.markdown.format`) are parsed as CommonMark and their raw HTML stripped. Set `companions: { format: 'raw' }` to keep emitting the source.
|
|
33
|
+
- **Default `askAi.promptTemplate` is `'Read {companionUrl} and help me understand it. ...'`.** `{companionUrl}` is the page's `.md` file by the same rule feature 1 writes it. `{pageUrl}` is unchanged (the page, no trailing slash). A custom template still using the v0.4 idiom `{pageUrl}.md` keeps working: that sequence now resolves to `{companionUrl}` instead of being computed from the pathname, which produced `https://site.md` on the homepage and `/foo.md` on sites that build `/foo/index.html`.
|
|
34
|
+
- **The Ask AI button is only rendered on pages that have a companion.** Generated category index pages, React pages and blog list pages have no source markdown and so no `.md` file; the button used to point readers at a 404 there. The route list is set as plugin global data from `allContentLoaded` (Docusaurus 3 merges it with the `contentLoaded` data); if it is absent the button shows everywhere as before. With `companions.enabled: false` the button shows everywhere and the prompt uses the page URL.
|
|
35
|
+
- **`llms.txt` links are absolute** (`https://site/foo.md`), as the llms.txt spec's examples are. The file is read away from the site, where a relative link has no base.
|
|
36
|
+
|
|
37
|
+
### Added
|
|
38
|
+
|
|
39
|
+
- **`<link rel="alternate" type="text/markdown" href="...">` in every page that has a companion** (`companions.alternateLink`, default `true`), inserted into the built HTML in `postBuild`, with the absolute companion URL from the same rule the file is written by. `companions.linkHeader` (default `false`) also writes the relation as a `Link:` header per route in a Netlify-style `_headers` file, appended to one the site already ships.
|
|
40
|
+
- **`## Optional` section in `llms.txt`** linking `llms-full.txt` (`llmsTxt.linkFullTxt`, default `true`), the llmstxt.org convention for secondary resources.
|
|
41
|
+
- **`llmsTxt.fullTxt` object form** `{ include: ['<plugin>@<id>', ...], maxBytes }` to limit `llms-full.txt` to chosen content instances and/or a byte budget; the file stops before the page that would cross the cap. The boolean form is unchanged (`true` = everything).
|
|
42
|
+
- Plain companions carry a `Source: <page URL>` line under the title and description, and `llms-full.txt` reuses it rather than adding a second one.
|
|
43
|
+
- `companions.format: 'clean'` is accepted as a synonym for `'plain'`.
|
|
44
|
+
- `src/features/aiRoutes.js`: `how-tos` and `how-to` directory names map to the `howTo` payload (only `howto` / `howtos` did; stackql.io uses `how-tos`).
|
|
45
|
+
|
|
46
|
+
### Fixed
|
|
47
|
+
|
|
48
|
+
- Feature 1, feature 2 and the Ask AI button each computed the companion location on their own; the button's version was wrong for the site root and for `trailingSlash: true`/undefined sites. All three now use `src/companionPath.js`.
|
|
49
|
+
- `llms-full.txt` no longer doubles the page title (the plain companion already opens with it) and now puts a blank line after the `Source:` line, which used to run into the first paragraph.
|
|
50
|
+
- Companions are written with LF line endings regardless of the source file's.
|
|
51
|
+
|
|
52
|
+
### Added
|
|
53
|
+
|
|
54
|
+
- `src/companionPath.js` (`companionPath`, `companionUrl`, `normalizeRoute`) and `src/askAiPrompt.js` (`fillPrompt`), plain CommonJS shared by the Node side and the theme component.
|
|
55
|
+
- Runtime dependencies on `unified`, `remark-parse`, `remark-mdx`, `remark-gfm`, `remark-directive` and `@slorber/remark-comment` at the versions `@docusaurus/mdx-loader` 3.x already ships, so npm dedupes them.
|
|
56
|
+
- A test suite (`npm test`, `node --test`): the companion path and HTML path rules, the prompt template, MDX fixtures for the converter (tabs, nested containers and dedent, link recovery, inline elements, details/summary, comments and heading ids, string-literal expressions, `md` format, the parse-failure fallback, CRLF sources), `llms.txt` rendering (sections, absolute links, the Optional block, the `fullTxt` forms), the alternate-link insertion (both `trailingSlash` layouts, idempotence, `_headers`), and the `/ai/*` directory classifier.
|
|
57
|
+
|
|
3
58
|
## 0.4.2
|
|
4
59
|
|
|
5
60
|
### Fixed
|
package/LICENSE
CHANGED
|
File without changes
|
package/README.md
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
[](https://www.npmjs.com/package/@stackql/docusaurus-plugin-aeo)
|
|
2
|
+
[](https://www.npmjs.com/package/@stackql/docusaurus-plugin-aeo)
|
|
3
|
+
|
|
1
4
|
# @stackql/docusaurus-plugin-aeo
|
|
2
5
|
|
|
3
|
-
AEO (Answer Engine Optimization) helpers for Docusaurus 3.x sites: emit
|
|
6
|
+
AEO (Answer Engine Optimization) helpers for Docusaurus 3.x sites: emit plain-markdown `.md` companions for docs and blog posts, generate `llms.txt` and `llms-full.txt` at the build root, add an "Ask AI" dropdown above doc and blog content, and document a `/ai/*` routing convention for machine-readable companion content. This is a sibling to `@stackql/docusaurus-plugin-structured-data` (which emits JSON-LD); the two plugins compose without overlap.
|
|
4
7
|
|
|
5
8
|
## Installation
|
|
6
9
|
|
|
@@ -25,7 +28,7 @@ module.exports = {
|
|
|
25
28
|
};
|
|
26
29
|
```
|
|
27
30
|
|
|
28
|
-
|
|
31
|
+
Companions, `llms.txt` / `llms-full.txt`, and the Ask AI button are enabled by default. The `/ai/*` docs instance requires site configuration, and its optional validator is off by default. To configure, pass options:
|
|
29
32
|
|
|
30
33
|
```js
|
|
31
34
|
module.exports = {
|
|
@@ -45,12 +48,23 @@ module.exports = {
|
|
|
45
48
|
|
|
46
49
|
## Feature 1: `.md` companion files
|
|
47
50
|
|
|
48
|
-
For
|
|
51
|
+
For each doc or blog post with a readable source file, this plugin writes a sibling `.md` file unless its route matches `companions.exclude` (see [Route patterns](#route-patterns)). The companion mirrors the HTML layout: a page at `/docs/intro` gets `/docs/intro/index.md` (or `/docs/intro.md` if `siteConfig.trailingSlash` is `false`). A doc or blog post at the site root gets `/index.md`. The same URL rule (`src/companionPath.js`) is used by `llms.txt` and the Ask AI button.
|
|
49
52
|
|
|
50
53
|
Two modes:
|
|
51
54
|
|
|
52
|
-
- `companions.format: '
|
|
53
|
-
- `
|
|
55
|
+
- `companions.format: 'plain'` (default since 0.5.0; `'clean'` is accepted as a synonym): plain markdown rendered from the MDX source, with a `# Title`, `> description` and `Source: <page URL>` block from the page metadata at the top, so an LLM can cite the page without parsing YAML. The converter uses remark-mdx, GFM, directives and comments to locate MDX constructs, then edits the original text by source position. It does not load the site's custom remark/rehype plugins:
|
|
56
|
+
- `import` / `export` statements and `{expressions}` are removed (a string literal expression keeps its value)
|
|
57
|
+
- presentational elements (`<svg>`, `<video>`, `<iframe>`, icons, ...) are dropped with their subtree
|
|
58
|
+
- `<Tabs>` / `<TabItem>` become a bold label per tab followed by the tab's content; the label comes from the `label` attribute or from the `<Tabs values={[...]}>` expression
|
|
59
|
+
- `<a>`, `<img>`, `<b>`, `<i>`, `<code>`, `<h2>`, `<br>`, `<details>` / `<summary>` become their markdown equivalent
|
|
60
|
+
- a self-closing component that plainly carries a link (`to` or `href` plus `text`, `label` or `title`) becomes a markdown link, so download buttons survive
|
|
61
|
+
- everything else (`<div>`, `<Box>`, `<span>`, your own layout components) is unwrapped: the tags go, the children stay, and indentation the author added inside the container is removed
|
|
62
|
+
- HTML comments (including `<!-- truncate -->`) and `{#custom-id}` heading ids are removed outside ordinary code fences; `mdx-code-block` fences are unwrapped and their contents are processed as markup
|
|
63
|
+
|
|
64
|
+
Markdown is not re-stringified, so `snake_case` is not escaped and Markdown syntax is retained. Output is not byte-for-byte identical: line endings become LF, surplus blank lines and outer whitespace are trimmed, leading/trailing horizontal rules are removed, and content inside unwrapped containers is dedented. An existing opening H1 is retained instead of adding another title. A Markdown/MDX parse or conversion failure uses a regex fallback and logs a warning; front matter parsing errors are not covered by that fallback.
|
|
65
|
+
|
|
66
|
+
Source format follows front matter `format`, then `siteConfig.markdown.format`; `detect` selects by extension (`.md` as Markdown, `.mdx` as MDX). Files resolved as `md` use a CommonMark parser with GFM and directives, with HTML tags stripped or converted.
|
|
67
|
+
- `companions.format: 'raw'`: the MDX source is emitted as-is, front matter included. MDX `<Component />` tags, imports and inline SVG data all pass through.
|
|
54
68
|
|
|
55
69
|
Fetch example:
|
|
56
70
|
|
|
@@ -58,15 +72,53 @@ Fetch example:
|
|
|
58
72
|
curl https://your-site.example/docs/intro/index.md
|
|
59
73
|
```
|
|
60
74
|
|
|
61
|
-
**MIME type note.**
|
|
75
|
+
**MIME type note.** The plugin writes `.md` files; the host determines their `Content-Type`. Check a deployed companion with `curl -I` and configure the host if needed. The alternate link below advertises the companion as `text/markdown` but does not set its HTTP response type.
|
|
76
|
+
|
|
77
|
+
### Advertising the companion from the page
|
|
78
|
+
|
|
79
|
+
Every page that has a companion gets, in its `<head>`:
|
|
80
|
+
|
|
81
|
+
```html
|
|
82
|
+
<link rel="alternate" type="text/markdown" href="https://your-site.example/docs/intro/index.md">
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
inserted into the built HTML in `postBuild` (the same pattern `@stackql/docusaurus-plugin-structured-data` uses). An agent that lands on the HTML can find the markdown without guessing the URL, and agent-readiness checkers score it. The href is absolute and follows the same rule as the file on disk. Options:
|
|
86
|
+
|
|
87
|
+
| Option | Default | Description |
|
|
88
|
+
| --- | --- | --- |
|
|
89
|
+
| `companions.alternateLink` | `true` | Insert the `<link rel="alternate" type="text/markdown">` tag into each page that has a companion. |
|
|
90
|
+
| `companions.linkHeader` | `false` | Also send the relation as an HTTP response header, on hosts that read a `_headers` file (Netlify, Cloudflare Pages): one `Link: <url>; rel="alternate"; type="text/markdown"` rule per page, written to `_headers` in the build root (appended to a `_headers` your `static/` already ships). It has no effect on other hosts; see [Hosting notes](#hosting-notes). Requires `companions.alternateLink: true`. |
|
|
91
|
+
|
|
92
|
+
Generated category indexes, custom React pages and blog listing pages do not get companions. `verbose: true` logs unreadable source files and missing HTML during alternate-link insertion.
|
|
93
|
+
|
|
94
|
+
### Localized sites and non-root `baseUrl`
|
|
95
|
+
|
|
96
|
+
The plugin works from `siteConfig.url` and `siteConfig.baseUrl` as Docusaurus reports them for the build, so a site at the root, a site built at `/docs/` and a site with several locales are configured the same way.
|
|
62
97
|
|
|
63
|
-
|
|
98
|
+
On a site with Docusaurus i18n the plugin runs once per locale, like every plugin, and needs no extra configuration: each locale gets its own companions (`/de/docs/intro.md` from the translated source, or from the default-locale source when a page has no translation), its own `/de/llms.txt` and `/de/llms-full.txt`, and alternate links and `Source:` lines that carry the locale prefix. The localized home page `/de/` is a root, so its companion is `/de/index.md`. The same holds for a site built at a non-root `baseUrl`: files are written relative to the build directory, URLs carry the base.
|
|
99
|
+
|
|
100
|
+
### Route patterns
|
|
101
|
+
|
|
102
|
+
`companions.exclude`, `llmsTxt.exclude` and `llmsTxt.include` take glob patterns for routes. `**` matches across path segments; `*` and `?` match within a single segment.
|
|
103
|
+
|
|
104
|
+
Write a pattern for the route as it is on the site, without the base URL: `/docs/internal/**`. Docusaurus permalinks carry the base URL (`/de/docs/internal/notes` in a `de` build, `/docs/guide/setup` on a site built at `/docs/`), so a pattern is matched against the route both with and without it. One list therefore applies to every locale, and a pattern that includes the base still works and can target a single locale:
|
|
105
|
+
|
|
106
|
+
```js
|
|
107
|
+
companions: {
|
|
108
|
+
exclude: [
|
|
109
|
+
'/docs/internal/**', // every locale: /docs/internal/..., /de/docs/internal/...
|
|
110
|
+
'/de/blog/**', // the de locale only
|
|
111
|
+
],
|
|
112
|
+
},
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
A page excluded from companions has no `.md` file, no alternate link, no `llms.txt` entry and no Ask AI button.
|
|
64
116
|
|
|
65
117
|
## Feature 2: `llms.txt` and `llms-full.txt`
|
|
66
118
|
|
|
67
|
-
|
|
119
|
+
With the defaults enabled, the plugin writes two files to the build root after emitting companions. Both use the emitted companion list, filtered by `llmsTxt.include` and `llmsTxt.exclude`. If companions are disabled, `llms.txt` contains only the site title, tagline and optional header; no page entries or `llms-full.txt` are emitted.
|
|
68
120
|
|
|
69
|
-
- `llms.txt` - sectioned index of
|
|
121
|
+
- `llms.txt` - sectioned index of included doc/blog companions, in the format described at <https://llmstxt.org>. Links are absolute (`https://site/foo.md`) since 0.5.0, as the spec's examples are; the file is read away from the site, where a relative link has no base:
|
|
70
122
|
|
|
71
123
|
```text
|
|
72
124
|
# StackQL
|
|
@@ -75,15 +127,21 @@ After feature 1 emits all companions, the plugin writes two files to the build r
|
|
|
75
127
|
|
|
76
128
|
## Documentation
|
|
77
129
|
|
|
78
|
-
- [Getting started](/docs/intro/index.md): Install StackQL and run your first query.
|
|
79
|
-
- [Providers](/docs/providers/index.md): Catalog of supported cloud APIs.
|
|
130
|
+
- [Getting started](https://stackql.io/docs/intro/index.md): Install StackQL and run your first query.
|
|
131
|
+
- [Providers](https://stackql.io/docs/providers/index.md): Catalog of supported cloud APIs.
|
|
80
132
|
|
|
81
133
|
## Blog
|
|
82
134
|
|
|
83
|
-
- [Querying Snowflake with StackQL](/blog/snowflake/index.md): ...
|
|
135
|
+
- [Querying Snowflake with StackQL](https://stackql.io/blog/snowflake/index.md): ...
|
|
136
|
+
|
|
137
|
+
## Optional
|
|
138
|
+
|
|
139
|
+
- [Full text of every page](https://stackql.io/llms-full.txt): one file with the content of every page listed above
|
|
84
140
|
```
|
|
85
141
|
|
|
86
|
-
|
|
142
|
+
The `## Optional` section is the llmstxt.org convention for secondary resources a reader can skip; it points at `llms-full.txt` whenever that file is emitted (`llmsTxt.linkFullTxt`, default `true`).
|
|
143
|
+
|
|
144
|
+
- `llms-full.txt` - the included companions concatenated with a `\n---\n\n` separator, each block carrying a `Source: <page URL>` line and the page's title (a plain companion already opens with both; a raw one has them added) so an LLM can cite individual sections. Limit it to chosen content instances or a byte budget with the object form of `llmsTxt.fullTxt` (below).
|
|
87
145
|
|
|
88
146
|
Options:
|
|
89
147
|
|
|
@@ -92,16 +150,17 @@ Options:
|
|
|
92
150
|
| `llmsTxt.enabled` | `true` | Master switch. |
|
|
93
151
|
| `llmsTxt.exclude` | `["/search", "/404", "/blog/tags/**", "/blog/page/**", "/blog/archive", "/blog/authors/**"]` | Route glob patterns to skip. |
|
|
94
152
|
| `llmsTxt.include` | `null` | If set, only routes matching at least one pattern are included. Use for opt-in mode. |
|
|
95
|
-
| `llmsTxt.header` | `null` | String
|
|
153
|
+
| `llmsTxt.header` | `null` | String inserted after the site title/tagline and before the section list. Good for a project intro paragraph or links to key external resources. |
|
|
96
154
|
| `llmsTxt.sections` | `{ docs: 'Documentation', blog: 'Blog', pages: 'Pages' }` | Section title overrides for the default per-type grouping. |
|
|
97
155
|
| `llmsTxt.instanceSections` | `null` | Per-content-plugin-instance section map. When set, supersedes `sections` (see below). |
|
|
98
|
-
| `llmsTxt.fullTxt` | `true` | Emit `llms-full.txt`. |
|
|
156
|
+
| `llmsTxt.fullTxt` | `true` | Emit `llms-full.txt`. `true` is every section, uncapped. The object form `{ include?: string[], maxBytes?: number }` limits it: `include` names the content instances to concatenate (`'<pluginName>@<pluginId>'` keys, e.g. `'docusaurus-plugin-content-docs@ai'`; with per-type grouping the keys are `docs`, `blog`, `pages`), in `llms.txt` section order; `maxBytes` stops appending once the next page would cross the cap, so the file always ends on a whole page. The budget includes section headings and separators. If the first page exceeds it, the file is empty. An omitted or empty `include` selects all sections. |
|
|
157
|
+
| `llmsTxt.linkFullTxt` | `true` | Add the `## Optional` section to `llms.txt` with a link to `llms-full.txt`. |
|
|
99
158
|
|
|
100
|
-
|
|
159
|
+
`exclude` and `include` follow the rules in [Route patterns](#route-patterns): a pattern matches a route with or without the base URL, so the defaults apply to every locale.
|
|
101
160
|
|
|
102
161
|
### Per-instance sectioning
|
|
103
162
|
|
|
104
|
-
By default, `llms.txt` groups by content-plugin TYPE: every `@docusaurus/plugin-content-docs` instance lands under one `## Documentation` heading, every blog instance under `## Blog`, and so on. This is fine for sites with a single docs instance, but it breaks down for sites that run multiple docs instances with different audiences
|
|
163
|
+
By default, `llms.txt` groups by content-plugin TYPE: every `@docusaurus/plugin-content-docs` instance lands under one `## Documentation` heading, every blog instance under `## Blog`, and so on. This is fine for sites with a single docs instance, but it breaks down for sites that run multiple docs instances with different audiences - for example, one for human-facing docs at `/docs/*` and one for AI-targeted reference content at `/ai/*`. In that case, `llms.txt` and `llms-full.txt` interleave both surfaces under one heading, hiding the corpus shape from the crawlers and agents the file exists to serve.
|
|
105
164
|
|
|
106
165
|
Set `llmsTxt.instanceSections` to switch to per-instance grouping. Keys are `"${pluginName}@${pluginId}"`; each value is `{ title, order? }`.
|
|
107
166
|
|
|
@@ -146,31 +205,35 @@ Resulting `llms.txt`:
|
|
|
146
205
|
|
|
147
206
|
## Documentation
|
|
148
207
|
|
|
149
|
-
- [Getting started](/docs/intro/index.md): Install and run your first query.
|
|
150
|
-
- [Providers](/docs/providers/index.md): Catalog of supported cloud APIs.
|
|
208
|
+
- [Getting started](https://your-site.example/docs/intro/index.md): Install and run your first query.
|
|
209
|
+
- [Providers](https://your-site.example/docs/providers/index.md): Catalog of supported cloud APIs.
|
|
151
210
|
|
|
152
211
|
## AI Reference
|
|
153
212
|
|
|
154
|
-
- [What is StackQL](/ai/faqs/what-is-stackql/index.md): Canonical one-paragraph definition.
|
|
155
|
-
- [Connecting to AWS](/ai/howto/connect-to-aws/index.md): Step-by-step authentication.
|
|
213
|
+
- [What is StackQL](https://your-site.example/ai/faqs/what-is-stackql/index.md): Canonical one-paragraph definition.
|
|
214
|
+
- [Connecting to AWS](https://your-site.example/ai/howto/connect-to-aws/index.md): Step-by-step authentication.
|
|
156
215
|
|
|
157
216
|
## Blog
|
|
158
217
|
|
|
159
|
-
- [Querying Snowflake](/blog/snowflake/index.md): ...
|
|
218
|
+
- [Querying Snowflake](https://your-site.example/blog/snowflake/index.md): ...
|
|
219
|
+
|
|
220
|
+
## Optional
|
|
221
|
+
|
|
222
|
+
- [Full text of every page](https://your-site.example/llms-full.txt): one file with the content of every page listed above
|
|
160
223
|
```
|
|
161
224
|
|
|
162
225
|
`llms-full.txt` mirrors the same section structure: each section's title is emitted as an `## H2` heading above its concatenated companion blocks, in the same order.
|
|
163
226
|
|
|
164
227
|
Behavior:
|
|
165
228
|
|
|
166
|
-
-
|
|
229
|
+
- Non-empty sections appear in ascending `order`. Ties break alphabetically by title. Sections with no `order` go last. Pages within each mapped section are sorted by permalink.
|
|
167
230
|
- Instances not present in `instanceSections` are collected into a single appended `## Other` section, sorted alphabetically by page title. When `verbose: true`, each unmapped instance name is logged once so you notice and can map it.
|
|
168
231
|
- `llmsTxt.sections` (the per-type titles) is ignored when `instanceSections` is set. When `verbose: true`, the plugin logs a one-line notice if both are configured.
|
|
169
|
-
- When `instanceSections` is `null` (default),
|
|
232
|
+
- When `instanceSections` is `null` (default), grouping is by content type using `llmsTxt.sections` titles.
|
|
170
233
|
|
|
171
234
|
## Feature 3: "Ask AI" button
|
|
172
235
|
|
|
173
|
-
An outlined pill button with a caret reading "Ask AI about this page"
|
|
236
|
+
An outlined pill button with a caret reading "Ask AI about this page" appears above eligible doc and blog-post content. Each item in the dropdown opens the corresponding AI surface in a new tab with a prefilled prompt that references the current page's `.md` companion. The button is hidden on viewports under 997px to keep the breadcrumb row uncluttered on mobile.
|
|
174
237
|
|
|
175
238
|
Placement specifics:
|
|
176
239
|
|
|
@@ -188,11 +251,21 @@ Providers and URL patterns:
|
|
|
188
251
|
Default prompt:
|
|
189
252
|
|
|
190
253
|
```text
|
|
191
|
-
Read https://your-site.example/path/to/page.md and help me understand it. Summarize the key points, then ask me one clarifying question to dig deeper.
|
|
254
|
+
Read https://your-site.example/path/to/page/index.md and help me understand it. Summarize the key points, then ask me one clarifying question to dig deeper.
|
|
192
255
|
```
|
|
193
256
|
|
|
194
257
|
The default is self-contained - submitting it as-is yields a useful summary plus a follow-up question. The prompt is prefilled in each provider's input box, so users can still edit or replace it before sending.
|
|
195
258
|
|
|
259
|
+
Template placeholders:
|
|
260
|
+
|
|
261
|
+
| Placeholder | Value |
|
|
262
|
+
| --- | --- |
|
|
263
|
+
| `{companionUrl}` | The page's `.md` companion, by the same rule feature 1 writes it: `https://site/index.md` for the homepage, `https://site/foo.md` or `https://site/foo/index.md` depending on `trailingSlash`. With feature 1 disabled, the page URL. |
|
|
264
|
+
| `{pageUrl}` | The page itself: canonical URL, no trailing slash, except on a root (`https://site/`, or a base URL such as `https://site/de/`). |
|
|
265
|
+
| `{pageUrl}.md` | Still honoured for templates written against v0.4, and resolves to `{companionUrl}`. (On its own it produced `https://site.md` for the homepage and the wrong path on sites that build `/foo/index.html`.) |
|
|
266
|
+
|
|
267
|
+
The button only renders on pages that have a companion. Generated category index pages, React pages and blog list pages have no `.md` file, so on those the button is not shown rather than sending the reader to a 404. (The list of routes comes from `allContentLoaded`; with feature 1 disabled, the companion-route filter is disabled and the prompt uses the page URL wherever the button is rendered.)
|
|
268
|
+
|
|
196
269
|
Provider icons are hand-rolled inline SVGs in each provider's brand color (Claude `#D97757`, ChatGPT `#000000`, Perplexity `#21808D`). The Claude and Perplexity SVG paths come from [simple-icons](https://simpleicons.org) (CC0-1.0); the OpenAI mark is sourced from the @lobehub/icons project. Bundle cost is ~3KB across the three components combined.
|
|
197
270
|
|
|
198
271
|
> **Gemini was previously supported but was removed in 0.4.2** because `gemini.google.com` silently ignores URL-encoded prompts, leaving users on an empty prompt box. There is no documented Gemini URL prefill API and Google has not indicated one is coming.
|
|
@@ -204,15 +277,28 @@ Options:
|
|
|
204
277
|
| Option | Default | Description |
|
|
205
278
|
| --- | --- | --- |
|
|
206
279
|
| `askAi.enabled` | `true` | When `false`, the theme components are not registered. |
|
|
207
|
-
| `askAi.providerOrder` | `['claude', 'chatgpt', 'perplexity']` | Order of items in the dropdown.
|
|
208
|
-
| `askAi.promptTemplate` | `'Read {
|
|
209
|
-
| `askAi.placement` | `'breadcrumb-row'` | `'breadcrumb-row'` puts the button at the top of every doc/blog page (docs breadcrumb row, or above the blog title). `'none'`
|
|
280
|
+
| `askAi.providerOrder` | `['claude', 'chatgpt', 'perplexity']` | Order of items in the dropdown. Omit providers to hide them; an empty array falls back to the default order. Valid values: `'claude'`, `'chatgpt'`, `'perplexity'`. |
|
|
281
|
+
| `askAi.promptTemplate` | `'Read {companionUrl} and help me understand it. Summarize the key points, then ask me one clarifying question to dig deeper.'` | Prompt sent to each provider. `{companionUrl}` is the page's `.md` companion (the page URL when feature 1 is disabled); `{pageUrl}` is the page's canonical URL (no trailing slash except on a root). See the placeholder table above. |
|
|
282
|
+
| `askAi.placement` | `'breadcrumb-row'` | `'breadcrumb-row'` puts the button at the top of every doc/blog page (docs breadcrumb row, or above the blog title). `'none'` disables all bundled theme components, including the `@theme/AskAiButton` alias. See custom placement below. |
|
|
283
|
+
|
|
284
|
+
The button is built from [MUI](https://mui.com) primitives - outlined `Button` with a `KeyboardArrowDownIcon` caret as the trigger, and a `Menu` of `MenuItem` rows for the providers. Theming reads `--ifm-color-primary` and `--ifm-font-family-base` via MUI's `sx` prop, so the button takes the site's own colours and dark/light mode work automatically. The MUI `Menu` handles click-outside-to-close and Esc-to-close natively.
|
|
285
|
+
|
|
286
|
+
### Translating the button
|
|
287
|
+
|
|
288
|
+
The visible strings go through Docusaurus `translate()`, so `docusaurus write-translations --locale <locale>` extracts them into `i18n/<locale>/code.json` with the rest of the theme's strings:
|
|
289
|
+
|
|
290
|
+
| Id | Default |
|
|
291
|
+
| --- | --- |
|
|
292
|
+
| `aeo.askAi.button` | Ask AI about this page |
|
|
293
|
+
| `aeo.askAi.provider.claude` | Ask Claude |
|
|
294
|
+
| `aeo.askAi.provider.chatgpt` | Ask ChatGPT |
|
|
295
|
+
| `aeo.askAi.provider.perplexity` | Ask Perplexity |
|
|
210
296
|
|
|
211
|
-
|
|
297
|
+
A locale with no translation for an id shows the default. The prompt itself is `askAi.promptTemplate`; in a localized build its URLs point at that locale's page and companion.
|
|
212
298
|
|
|
213
299
|
### Peer dependencies
|
|
214
300
|
|
|
215
|
-
|
|
301
|
+
The package declares these MUI/Emotion peers as required, in addition to Docusaurus `^3.0.0` and React/React DOM `^18.0.0 || ^19.0.0`:
|
|
216
302
|
|
|
217
303
|
| Package | Range |
|
|
218
304
|
| --- | --- |
|
|
@@ -221,19 +307,21 @@ When `askAi.enabled` is `true` (the default), the following peer dependencies mu
|
|
|
221
307
|
| `@emotion/react` | `^11.0.0` |
|
|
222
308
|
| `@emotion/styled` | `^11.0.0` |
|
|
223
309
|
|
|
224
|
-
These are
|
|
310
|
+
These are peer dependencies so the plugin can share the site's MUI installation. npm can install missing peers automatically; other package managers may require you to add them explicitly.
|
|
225
311
|
|
|
226
|
-
|
|
312
|
+
Disabling Ask AI (`askAi.enabled: false`) prevents the plugin from registering its theme components, so they do not import MUI at runtime. It does not change the package's required peer declarations or the package manager's installation requirements.
|
|
227
313
|
|
|
228
314
|
### Customizing placement
|
|
229
315
|
|
|
230
|
-
|
|
316
|
+
`askAi.placement: 'none'` disables the entire theme path, so `@theme/AskAiButton` is unavailable with that setting.
|
|
317
|
+
|
|
318
|
+
To reuse the bundled button in a custom location, keep Ask AI enabled with `placement: 'breadcrumb-row'`. Add site-level theme overrides for `DocBreadcrumbs` and `BlogPostItem/Header/Title` that omit the default button, then import it in your chosen site component:
|
|
231
319
|
|
|
232
320
|
```jsx
|
|
233
321
|
import AskAiButton from '@theme/AskAiButton';
|
|
234
322
|
```
|
|
235
323
|
|
|
236
|
-
|
|
324
|
+
The button retains its companion-route filter and desktop-only styling in a custom location.
|
|
237
325
|
|
|
238
326
|
## Feature 4: `/ai/*` routing convention
|
|
239
327
|
|
|
@@ -241,7 +329,7 @@ This plugin documents but does not auto-configure a second docs instance for mac
|
|
|
241
329
|
|
|
242
330
|
```js
|
|
243
331
|
// docusaurus.config.js
|
|
244
|
-
const { sitemapExclude
|
|
332
|
+
const { sitemapExclude } = require('@stackql/docusaurus-plugin-aeo/helpers');
|
|
245
333
|
|
|
246
334
|
module.exports = {
|
|
247
335
|
plugins: [
|
|
@@ -292,23 +380,38 @@ ai-content/
|
|
|
292
380
|
└── stackql-cli.md # frontmatter: softwareApplication: { ... }
|
|
293
381
|
```
|
|
294
382
|
|
|
295
|
-
|
|
383
|
+
With default filters, these docs appear in `llms.txt` and `llms-full.txt`. Their companions follow the same URL rule: `/ai/faqs/<slug>/index.md`, or `/ai/faqs/<slug>.md` when `trailingSlash: false`.
|
|
384
|
+
|
|
385
|
+
The convention is relative to the site's base URL: on a site built at `/docs/` the routes are `/docs/ai/*`, and in a `de` build they are `/de/ai/*`.
|
|
296
386
|
|
|
297
387
|
### Optional validation
|
|
298
388
|
|
|
299
|
-
Set `aiRoutes.validate: true` to
|
|
389
|
+
Set `aiRoutes.validate: true` to warn when docs at `/ai/faqs/*`, `/ai/howto/*`, or `/ai/apps/*` lack the corresponding frontmatter payload (`faq`, `howTo`, `softwareApplication`). Validation uses route permalinks (relative to the base URL, so every locale is checked), not source directories, and does not fail the build. Set `verbose: true` for per-page details. Off by default.
|
|
300
390
|
|
|
301
391
|
### Helpers
|
|
302
392
|
|
|
303
393
|
```js
|
|
304
394
|
const {
|
|
305
395
|
AI_ROUTE_PATTERNS, // ['/ai', '/ai/**'] - glob patterns
|
|
396
|
+
aiRoutePatterns, // (baseUrl) => the same patterns under a base URL
|
|
306
397
|
sitemapExclude, // { ignorePatterns: AI_ROUTE_PATTERNS }
|
|
307
|
-
isAiRoute, // (
|
|
308
|
-
buildStructuredDataAiExcludes, // (routePaths[]) => string[]
|
|
398
|
+
isAiRoute, // (route, baseUrl?) true for /ai/*; false for /ai itself
|
|
399
|
+
buildStructuredDataAiExcludes, // (routePaths[], baseUrl?) => string[]
|
|
309
400
|
} = require('@stackql/docusaurus-plugin-aeo/helpers');
|
|
310
401
|
```
|
|
311
402
|
|
|
403
|
+
`AI_ROUTE_PATTERNS` and `sitemapExclude` describe a site at the root with one locale. These helpers run in your config, where other plugins match the full route path, base URL included (the Docusaurus docs say as much of the sitemap plugin's `ignorePatterns`). With a non-root `baseUrl` or several locales, build the list from `aiRoutePatterns`, one call per base URL:
|
|
404
|
+
|
|
405
|
+
```js
|
|
406
|
+
const { aiRoutePatterns } = require('@stackql/docusaurus-plugin-aeo/helpers');
|
|
407
|
+
|
|
408
|
+
// a site with en at / and de at /de/
|
|
409
|
+
const ignorePatterns = ['/', '/de/'].flatMap(aiRoutePatterns);
|
|
410
|
+
// ['/ai', '/ai/**', '/de/ai', '/de/ai/**']
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
`isAiRoute` and `buildStructuredDataAiExcludes` take the base URL as an optional second argument.
|
|
414
|
+
|
|
312
415
|
**Heads-up on the structured-data plugin.** `@stackql/docusaurus-plugin-structured-data`'s `themeConfig.structuredData.excludedRoutes` compares routes with strict equality, so glob patterns like `/ai/**` do not work there. Use one of these approaches:
|
|
313
416
|
|
|
314
417
|
- List the exact `/ai/*` permalinks you want skipped by hand (works if your `/ai/*` tree is small and stable).
|
|
@@ -328,6 +431,16 @@ const {
|
|
|
328
431
|
|
|
329
432
|
The `sitemapExclude` helper, by contrast, does work with globs because `@docusaurus/plugin-sitemap` uses `ignorePatterns` (micromatch under the hood).
|
|
330
433
|
|
|
434
|
+
## Hosting notes
|
|
435
|
+
|
|
436
|
+
Everything the plugin produces is a static file or an edit to the built HTML, so companions, `llms.txt`, `llms-full.txt`, the alternate link tag and the Ask AI button work on any static host with no host configuration. A few things do depend on where the site is deployed:
|
|
437
|
+
|
|
438
|
+
- **`Content-Type` of `.md` files.** Set by the host; see the MIME type note under Feature 1.
|
|
439
|
+
- **GitHub Pages.** GitHub Pages runs published files through Jekyll by default. Jekyll builds sites from Markdown, so `.md` companions may be turned into pages instead of being published as they are. Add an empty `.nojekyll` file to `static/`, as the [Docusaurus deployment guide](https://docusaurus.io/docs/deployment) recommends. A project site is served under `/<repo>/`: set `baseUrl` as usual and the plugin follows it.
|
|
440
|
+
- **`companions.linkHeader` is for hosts that read `_headers`.** That is Netlify and Cloudflare Pages. Vercel documents response headers through `vercel.json`, and GitHub Pages has no server-side configuration for them, so the option does nothing there. The alternate link tag, on by default, needs nothing from the host and is the portable way to advertise a companion. The file gets one rule per page that has a companion; Cloudflare Pages documents a limit of 100 header rules, so leave the option off there on a larger site.
|
|
441
|
+
- **Where `_headers` is written.** At the root of the build directory, which is where both hosts document the file. A build of several locales (`docusaurus build`, one deployment serving `/` and `/de/`) puts every locale's rules in that one file. A locale built on its own (`docusaurus build --locale de`) or given its own `baseUrl` is a separate deployment and gets the file in its own output directory.
|
|
442
|
+
- **Preview deployments.** Companion URLs, `Source:` lines, `llms.txt` links and the Ask AI prompt are absolute and come from `siteConfig.url`, so on a deploy preview they point at the production site.
|
|
443
|
+
|
|
331
444
|
## Full options reference
|
|
332
445
|
|
|
333
446
|
```js
|
|
@@ -335,8 +448,10 @@ The `sitemapExclude` helper, by contrast, does work with globs because `@docusau
|
|
|
335
448
|
// Feature 1
|
|
336
449
|
companions: {
|
|
337
450
|
enabled: true, // emit .md siblings
|
|
338
|
-
format: '
|
|
451
|
+
format: 'plain', // 'plain' ('clean' is a synonym) | 'raw'
|
|
339
452
|
exclude: [], // route glob patterns to skip
|
|
453
|
+
alternateLink: true, // <link rel="alternate" type="text/markdown"> in each page
|
|
454
|
+
linkHeader: false, // also a Link: header via a _headers file (Netlify, Cloudflare Pages)
|
|
340
455
|
},
|
|
341
456
|
|
|
342
457
|
// Feature 2
|
|
@@ -351,7 +466,7 @@ The `sitemapExclude` helper, by contrast, does work with globs because `@docusau
|
|
|
351
466
|
'/blog/authors/**',
|
|
352
467
|
],
|
|
353
468
|
include: null, // null = all not-excluded
|
|
354
|
-
header: null, // optional
|
|
469
|
+
header: null, // optional text after the site title/tagline
|
|
355
470
|
sections: { // per-type section titles (used when
|
|
356
471
|
docs: 'Documentation', // instanceSections is null)
|
|
357
472
|
blog: 'Blog',
|
|
@@ -361,14 +476,16 @@ The `sitemapExclude` helper, by contrast, does work with globs because `@docusau
|
|
|
361
476
|
// sections when set. Shape:
|
|
362
477
|
// { '${pluginName}@${pluginId}':
|
|
363
478
|
// { title: string, order?: number } }
|
|
364
|
-
fullTxt: true, // emit llms-full.txt
|
|
479
|
+
fullTxt: true, // emit llms-full.txt: true | false |
|
|
480
|
+
// { include: ['<plugin>@<id>'], maxBytes: 65536 }
|
|
481
|
+
linkFullTxt: true, // "## Optional" section in llms.txt -> llms-full.txt
|
|
365
482
|
},
|
|
366
483
|
|
|
367
484
|
// Feature 3
|
|
368
485
|
askAi: {
|
|
369
486
|
enabled: true,
|
|
370
487
|
providerOrder: ['claude', 'chatgpt', 'perplexity'],
|
|
371
|
-
promptTemplate: 'Read {
|
|
488
|
+
promptTemplate: 'Read {companionUrl} and help me understand it. Summarize the key points, then ask me one clarifying question to dig deeper.',
|
|
372
489
|
placement: 'breadcrumb-row', // 'breadcrumb-row' | 'none'
|
|
373
490
|
},
|
|
374
491
|
|
|
@@ -384,12 +501,29 @@ The `sitemapExclude` helper, by contrast, does work with globs because `@docusau
|
|
|
384
501
|
|
|
385
502
|
Options are validated by a small handwritten validator at plugin construction. Invalid options throw a clear error before the build starts.
|
|
386
503
|
|
|
504
|
+
## Development
|
|
505
|
+
|
|
506
|
+
Use Node.js 24.x to match CI. From the repository root:
|
|
507
|
+
|
|
508
|
+
```bash
|
|
509
|
+
npm ci
|
|
510
|
+
npm test
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
`npm test` runs every `test/*.test.js` file with Node's test runner: companion paths and files, route patterns, Ask AI prompts and the rendered button, plain Markdown, alternate links/headers, `llms.txt`, AI route classification and helpers, and the plugin run once per locale as Docusaurus runs it (`test/localizedBuild.test.js`). Shared fixtures and the loader that renders theme components in Node are in `test/helpers/`. There are no dev dependencies: the tests use the peer dependencies and the Babel setup that comes with `@docusaurus/core`.
|
|
514
|
+
|
|
515
|
+
The [Tests workflow](.github/workflows/tests.yml) runs on every pull request targeting `main` and on pushes to `main`, using the committed lockfile and Node 24.x. To make it a merge gate, require the **Tests (Node 24)** status check in the branch protection rule or ruleset for `main`. Until that rule is enabled, a failed check does not block merging.
|
|
516
|
+
|
|
517
|
+
When `npm audit` reports advisories, they are almost always in the Docusaurus toolchain, which is installed here only because it is a peer dependency. Do not run `npm audit fix`: it clears the report by downgrading the `@docusaurus/core` peer to an older release with a different set of advisories. Update the named packages instead (`npm update <package> ...`), which moves them within the ranges Docusaurus already allows, then re-run `npm audit` and `npm test`.
|
|
518
|
+
|
|
519
|
+
The plain-markdown converter lives in `src/features/plainMarkdown.js` and is exercised by `test/plainMarkdown.test.js` with small MDX fixtures; add a fixture there when a new construct needs handling.
|
|
520
|
+
|
|
387
521
|
## Composing with `@stackql/docusaurus-plugin-structured-data`
|
|
388
522
|
|
|
389
523
|
The two plugins are complementary and intended to be used together:
|
|
390
524
|
|
|
391
525
|
- `@stackql/docusaurus-plugin-structured-data` emits JSON-LD into page `<head>`. <https://github.com/stackql/docusaurus-plugin-structured-data>
|
|
392
|
-
- `@stackql/docusaurus-plugin-aeo`
|
|
526
|
+
- `@stackql/docusaurus-plugin-aeo` provides plain-markdown `.md` companions by default (raw mode is optional), `llms.txt` / `llms-full.txt`, the Ask AI button, and the `/ai/*` convention.
|
|
393
527
|
|
|
394
528
|
There is no overlap and no shared state. Add both:
|
|
395
529
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stackql/docusaurus-plugin-aeo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.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": {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"access": "public"
|
|
18
18
|
},
|
|
19
19
|
"scripts": {
|
|
20
|
-
"test": "
|
|
20
|
+
"test": "node --test test/*.test.js"
|
|
21
21
|
},
|
|
22
22
|
"peerDependencies": {
|
|
23
23
|
"@docusaurus/core": "^3.0.0",
|
|
@@ -43,11 +43,21 @@
|
|
|
43
43
|
}
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"
|
|
46
|
+
"@slorber/remark-comment": "^1.0.0",
|
|
47
|
+
"gray-matter": "npm:@11ty/gray-matter@1.0.0",
|
|
48
|
+
"remark-directive": "^3.0.0",
|
|
49
|
+
"remark-gfm": "^4.0.0",
|
|
50
|
+
"remark-mdx": "^3.0.0",
|
|
51
|
+
"remark-parse": "^11.0.0",
|
|
52
|
+
"unified": "^11.0.3"
|
|
47
53
|
},
|
|
48
54
|
"overrides": {
|
|
49
55
|
"serialize-javascript": "^7.0.5",
|
|
50
|
-
"uuid": "^11.1.1"
|
|
56
|
+
"uuid": "^11.1.1",
|
|
57
|
+
"@docusaurus/core": {
|
|
58
|
+
"tinypool": "2.1.2"
|
|
59
|
+
},
|
|
60
|
+
"postcss-selector-parser": "7.1.6"
|
|
51
61
|
},
|
|
52
62
|
"repository": {
|
|
53
63
|
"type": "git",
|
|
@@ -67,5 +77,11 @@
|
|
|
67
77
|
"bugs": {
|
|
68
78
|
"url": "https://github.com/stackql-labs/docusaurus-plugin-aeo/issues"
|
|
69
79
|
},
|
|
70
|
-
"homepage": "https://github.com/stackql-labs/docusaurus-plugin-aeo#readme"
|
|
80
|
+
"homepage": "https://github.com/stackql-labs/docusaurus-plugin-aeo#readme",
|
|
81
|
+
"engines": {
|
|
82
|
+
"node": ">=18.0"
|
|
83
|
+
},
|
|
84
|
+
"allowScripts": {
|
|
85
|
+
"core-js": false
|
|
86
|
+
}
|
|
71
87
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Fills the Ask AI prompt template. Plain CommonJS so the theme component
|
|
2
|
+
// can bundle it and the tests can require it.
|
|
3
|
+
//
|
|
4
|
+
// {companionUrl} the page's .md companion (absolute URL)
|
|
5
|
+
// {pageUrl} the page itself (canonical: no trailing slash, except
|
|
6
|
+
// on a root - the site root or a base URL such as '/de/')
|
|
7
|
+
//
|
|
8
|
+
// The v0.4 idiom `{pageUrl}.md` is still honoured so a custom template
|
|
9
|
+
// keeps working. It was wrong for the homepage (`https://site.md`) and for
|
|
10
|
+
// sites that build /foo/index.html; it now resolves to the real companion
|
|
11
|
+
// URL.
|
|
12
|
+
function fillPrompt(template, { pageUrl, companionUrl }) {
|
|
13
|
+
return String(template || '')
|
|
14
|
+
.split('{pageUrl}.md')
|
|
15
|
+
.join(companionUrl)
|
|
16
|
+
.split('{companionUrl}')
|
|
17
|
+
.join(companionUrl)
|
|
18
|
+
.split('{pageUrl}')
|
|
19
|
+
.join(pageUrl);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
module.exports = { fillPrompt };
|