@pointsharp/antora-llm-generator 1.0.0 → 1.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/README.md CHANGED
@@ -11,10 +11,8 @@ Both files help large-language models ingest your documentation with proper stru
11
11
 
12
12
  ## Installation
13
13
 
14
- This extension is used locally. Ensure the required dependencies are installed:
15
-
16
14
  ```bash
17
- npm install node-html-markdown minimatch
15
+ npm install @pointsharp/antora-llm-generator
18
16
  ```
19
17
 
20
18
  ---
@@ -26,7 +24,7 @@ Add the extension to your `antora-playbook.yml`:
26
24
  ```yaml
27
25
  antora:
28
26
  extensions:
29
- - require: ./extensions/antora-llm-generator/llm-generator.js
27
+ - require: "@pointsharp/antora-llm-generator"
30
28
  summary: "Brief summary about your documentation site"
31
29
  details: |
32
30
  Optional longer description or important notes.
@@ -40,6 +38,7 @@ antora:
40
38
  - **`summary`** - Optional. Appears as a blockquote at the top of both output files (following llmstxt.org spec).
41
39
  - **`details`** - Optional. Appears as regular text after the summary. Can be multi-line markdown.
42
40
  - **`skippaths`** - Optional. Array of glob patterns. Files matching these patterns are omitted from both output files.
41
+ - **`debug`** - Optional. Set to `true` to enable verbose logging during build. Default: `false`.
43
42
 
44
43
  ### Navigation-based organization (default)
45
44
 
@@ -182,6 +181,40 @@ The duplicate filenames ensure compatibility with different naming conventions.
182
181
 
183
182
  ---
184
183
 
184
+ ## Logging
185
+
186
+ The extension runs **silently** by default, showing only a success message when complete:
187
+
188
+ ```
189
+ Generated llms.txt and llms-full.txt
190
+ ```
191
+
192
+ ### Enabling verbose output
193
+
194
+ To see detailed processing information (pages collected, components processed, etc.), enable the `debug` flag in your playbook:
195
+
196
+ ```yaml
197
+ antora:
198
+ extensions:
199
+ - require: "@pointsharp/antora-llm-generator"
200
+ debug: true
201
+ summary: "Your summary"
202
+ ```
203
+
204
+ With `debug: true`, you'll see:
205
+ - Navigation data source detection
206
+ - Number of pages in the page map
207
+ - Components and sections being processed
208
+ - Item counts for each section
209
+
210
+ ### Error handling
211
+
212
+ The extension always reports errors and critical warnings, regardless of the debug setting:
213
+ - **Errors** - Processing failures or file write errors
214
+ - **Warnings** - Navigator data parsing issues
215
+
216
+ ---
217
+
185
218
  ## Example configuration
186
219
 
187
220
  Here's a complete example following the [llmstxt.org](https://llmstxt.org/) best practices:
@@ -189,7 +222,7 @@ Here's a complete example following the [llmstxt.org](https://llmstxt.org/) best
189
222
  ```yaml
190
223
  antora:
191
224
  extensions:
192
- - require: ./extensions/antora-llm-generator/llm-generator.js
225
+ - require: "@pointsharp/antora-llm-generator"
193
226
  summary: "Comprehensive documentation for the Acme API, including authentication, endpoints, and best practices."
194
227
  details: |
195
228
  Important notes:
@@ -224,3 +257,12 @@ Following [llmstxt.org guidelines](https://llmstxt.org/):
224
257
  This extension follows the official [llmstxt.org specification](https://llmstxt.org/) for maximum compatibility with LLM tools and agents.
225
258
 
226
259
  For more details about the specification, visit [https://llmstxt.org/](https://llmstxt.org/).
260
+
261
+ ---
262
+
263
+ ## Copyright and License
264
+
265
+ Copyright © 2006-present Pointsharp AB.
266
+
267
+ Use of this software is granted under the terms of the Apache License Version 2.0 (Apache-2.0).
268
+ See the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) for the full license text.
package/llm-generator.js CHANGED
@@ -34,8 +34,7 @@ module.exports.register = function (context, { config }) {
34
34
  const skipPaths = config.skippaths || [];
35
35
  const summary = config.summary || null;
36
36
  const details = config.details || null;
37
-
38
- logger.info(`Skip paths: ${JSON.stringify(skipPaths)}`);
37
+ const debug = config.debug !== undefined ? config.debug : false;
39
38
 
40
39
  // Helper: Check if a page path matches any skip pattern
41
40
  const shouldSkipPath = (path) => {
@@ -43,7 +42,7 @@ module.exports.register = function (context, { config }) {
43
42
  };
44
43
 
45
44
  context.on("beforePublish", ({ contentCatalog, siteCatalog }) => {
46
- logger.info("Assembling content for LLM text files using Antora navigation.");
45
+ if (debug) logger.info("Assembling content for LLM text files using Antora navigation.");
47
46
 
48
47
  // =============================================================================
49
48
  // STEP 1: Detect navigation source
@@ -51,13 +50,15 @@ module.exports.register = function (context, { config }) {
51
50
  // Two navigation sources are supported:
52
51
  // 1. Antora native navigation (from nav.adoc files)
53
52
  // 2. Navigator extension (if installed, creates site-navigation-data.js)
54
- const navDataFile = siteCatalog.getFiles().find(f => f.out?.path === 'site-navigation-data.js');
53
+ const navDataFile = siteCatalog.getFiles().find(f => f.out?.path?.endsWith('site-navigation-data.js'));
55
54
  const useNavigatorData = !!navDataFile;
56
55
 
57
- if (useNavigatorData) {
58
- logger.info("Found site-navigation-data.js - will use navigator extension data");
59
- } else {
60
- logger.info("Navigator extension not detected - will use Antora native navigation");
56
+ if (debug) {
57
+ if (useNavigatorData) {
58
+ logger.info("Found site-navigation-data.js - will use navigator extension data");
59
+ } else {
60
+ logger.info("Navigator extension not detected - will use Antora native navigation");
61
+ }
61
62
  }
62
63
 
63
64
  // =============================================================================
@@ -98,20 +99,20 @@ module.exports.register = function (context, { config }) {
98
99
 
99
100
  // Skip pages matching skippath patterns from config
100
101
  if (shouldSkipPath(page.out.path)) {
101
- logger.info(`Skipping page matching skip pattern: ${page.out.path}`);
102
+ if (debug) logger.info(`Skipping page matching skip pattern: ${page.out.path}`);
102
103
  continue;
103
104
  }
104
105
 
105
106
  // Skip pages with :page-llms-ignore: attribute
106
107
  if (page.asciidoc.attributes["page-llms-ignore"]) {
107
- logger.info(`Skipping page with 'page-llms-ignore' attribute: ${page.src.path}`);
108
+ if (debug) logger.info(`Skipping page with 'page-llms-ignore' attribute: ${page.src.path}`);
108
109
  continue;
109
110
  }
110
111
 
111
112
  pagesByPath.set(page.out.path, page);
112
113
  }
113
114
 
114
- logger.info(`Built pagesByPath map with ${pagesByPath.size} pages`);
115
+ if (debug) logger.info(`Built pagesByPath map with ${pagesByPath.size} pages`);
115
116
 
116
117
  // =============================================================================
117
118
  // STEP 4: Build navigation sections
@@ -120,18 +121,16 @@ module.exports.register = function (context, { config }) {
120
121
  // Each item is a markdown string: "- [Page Title](url): Optional description"
121
122
  let componentSections;
122
123
  if (useNavigatorData) {
123
- // Use navigator extension's site-navigation-data.js
124
- logger.info("Using navigator extension data");
125
- componentSections = buildSectionsFromNavigatorData(navDataFile, siteUrl, pagesByPath, logger);
124
+ componentSections = buildSectionsFromNavigatorData(navDataFile, siteUrl, pagesByPath, logger, debug);
126
125
  } else {
127
- // Use Antora's native navigation structure from component versions
128
- logger.info("Using Antora native navigation");
129
- componentSections = buildSectionsFromAntoraNavigation(contentCatalog, siteUrl, pagesByPath, logger);
126
+ componentSections = buildSectionsFromAntoraNavigation(contentCatalog, siteUrl, pagesByPath, logger, debug);
130
127
  }
131
128
 
132
- logger.info(`Component sections map has ${componentSections.size} sections`);
133
- for (const [name, items] of componentSections.entries()) {
134
- logger.info(`Section "${name}" has ${items.length} items`);
129
+ if (debug) {
130
+ logger.info(`Component sections map has ${componentSections.size} sections`);
131
+ for (const [name, items] of componentSections.entries()) {
132
+ logger.info(`Section "${name}" has ${items.length} items`);
133
+ }
135
134
  }
136
135
 
137
136
  // =============================================================================
@@ -154,7 +153,7 @@ module.exports.register = function (context, { config }) {
154
153
  for (const [path, page] of pagesByPath.entries()) {
155
154
  // Skip pages with :page-llms-full-ignore: attribute
156
155
  if (page.asciidoc.attributes["page-llms-full-ignore"]) {
157
- logger.info(`Skipping page from full content with 'page-llms-full-ignore' attribute: ${page.src.path}`);
156
+ if (debug) logger.info(`Skipping page from full content with 'page-llms-full-ignore' attribute: ${page.src.path}`);
158
157
  continue;
159
158
  }
160
159
 
@@ -209,7 +208,7 @@ module.exports.register = function (context, { config }) {
209
208
  contents: Buffer.from(UTF8_BOM + indexContent, 'utf8'),
210
209
  });
211
210
 
212
- logger.info("llms.txt and llms-full.txt files have been generated successfully.");
211
+ logger.info("Generated llms.txt and llms-full.txt");
213
212
  });
214
213
  };
215
214
 
@@ -223,9 +222,10 @@ module.exports.register = function (context, { config }) {
223
222
  * @param {string} siteUrl - Base URL of the site
224
223
  * @param {Map} pagesByPath - Map of page.out.path -> page object
225
224
  * @param {Object} logger - Antora logger instance
225
+ * @param {boolean} debug - Enable detailed logging
226
226
  * @returns {Map} Map of sectionName -> array of formatted navigation items
227
227
  */
228
- function buildSectionsFromNavigatorData(navDataFile, siteUrl, pagesByPath, logger) {
228
+ function buildSectionsFromNavigatorData(navDataFile, siteUrl, pagesByPath, logger, debug) {
229
229
  const componentSections = new Map();
230
230
 
231
231
  try {
@@ -240,7 +240,7 @@ function buildSectionsFromNavigatorData(navDataFile, siteUrl, pagesByPath, logge
240
240
  }
241
241
 
242
242
  const siteNavigationData = JSON.parse(jsonMatch[1]);
243
- logger.info(`Parsed ${siteNavigationData.length} components from navigator data`);
243
+ if (debug) logger.info(`Parsed ${siteNavigationData.length} components from navigator data`);
244
244
 
245
245
  // Process each component
246
246
  for (const component of siteNavigationData) {
@@ -259,8 +259,8 @@ function buildSectionsFromNavigatorData(navDataFile, siteUrl, pagesByPath, logge
259
259
  for (const set of version.sets || []) {
260
260
  if (set.items && set.items.length > 0) {
261
261
  // Recursively collect navigation items
262
- const counts = collectNavItems(set.items, sectionArray, siteUrl, pagesByPath, logger);
263
- logger.debug(`Component ${component.name}: collected ${counts.found} items`);
262
+ const counts = collectNavItems(set.items, sectionArray, siteUrl, pagesByPath);
263
+ if (debug) logger.info(`Component ${component.name}: collected ${counts.found} items`);
264
264
  }
265
265
  }
266
266
  }
@@ -287,9 +287,10 @@ function buildSectionsFromNavigatorData(navDataFile, siteUrl, pagesByPath, logge
287
287
  * @param {string} siteUrl - Base URL of the site
288
288
  * @param {Map} pagesByPath - Map of page.out.path -> page object
289
289
  * @param {Object} logger - Antora logger instance
290
+ * @param {boolean} debug - Enable detailed logging
290
291
  * @returns {Map} Map of sectionName -> array of formatted navigation items
291
292
  */
292
- function buildSectionsFromAntoraNavigation(contentCatalog, siteUrl, pagesByPath, logger) {
293
+ function buildSectionsFromAntoraNavigation(contentCatalog, siteUrl, pagesByPath, logger, debug) {
293
294
  const componentSections = new Map();
294
295
  const components = contentCatalog.getComponents();
295
296
 
@@ -299,12 +300,12 @@ function buildSectionsFromAntoraNavigation(contentCatalog, siteUrl, pagesByPath,
299
300
  const componentTitle = version.title || componentName;
300
301
  const versionString = version.version;
301
302
 
302
- logger.info(`Processing component: ${componentName} (${versionString || 'unversioned'})`);
303
+ if (debug) logger.info(`Processing component: ${componentName} (${versionString || 'unversioned'})`);
303
304
 
304
305
  // Check if version has parsed navigation tree
305
306
  // (version.navigation is populated by Antora after parsing nav.adoc files)
306
307
  if (!version.navigation || !Array.isArray(version.navigation) || version.navigation.length === 0) {
307
- logger.debug(`No navigation tree found for ${componentName} ${versionString || 'unversioned'}`);
308
+ if (debug) logger.info(`No navigation tree found for ${componentName} ${versionString || 'unversioned'}`);
308
309
  continue;
309
310
  }
310
311
 
@@ -323,10 +324,9 @@ function buildSectionsFromAntoraNavigation(contentCatalog, siteUrl, pagesByPath,
323
324
  navTree.items,
324
325
  sectionArray,
325
326
  siteUrl,
326
- pagesByPath,
327
- logger
327
+ pagesByPath
328
328
  );
329
- logger.info(`Section "${sectionName}": collected ${counts.found} pages from navigation`);
329
+ if (debug) logger.info(`Section "${sectionName}": collected ${counts.found} pages from navigation`);
330
330
  }
331
331
  }
332
332
  }
@@ -373,8 +373,8 @@ function convertRelativeLinksToAbsolute(markdown, baseUrl, logger) {
373
373
  const absoluteUrl = new URL(url, baseWithoutFragment).href;
374
374
  return `[${text}](${absoluteUrl})`;
375
375
  } catch (error) {
376
- logger.debug(`Failed to resolve relative URL "${url}" against base "${baseUrl}": ${error.message}`);
377
- return match; // Return original if resolution fails
376
+ // Return original if resolution fails
377
+ return match;
378
378
  }
379
379
  });
380
380
  }
@@ -400,7 +400,7 @@ function convertRelativeLinksToAbsolute(markdown, baseUrl, logger) {
400
400
  * @param {string} indent - Current indentation level (increases with nesting)
401
401
  * @returns {Object} Count object: { found: number, notFound: number }
402
402
  */
403
- function collectNavItems(items, collector, siteUrl, pagesByPath, logger, indent = '') {
403
+ function collectNavItems(items, collector, siteUrl, pagesByPath, indent = '') {
404
404
  let foundCount = 0;
405
405
  let notFoundCount = 0;
406
406
 
@@ -439,7 +439,6 @@ function collectNavItems(items, collector, siteUrl, pagesByPath, logger, indent
439
439
  } else {
440
440
  // URL in navigation doesn't match any page in pagesByPath
441
441
  notFoundCount++;
442
- logger.debug(`Navigation item URL not found in pages: ${urlPath}`);
443
442
  }
444
443
  }
445
444
 
@@ -456,7 +455,6 @@ function collectNavItems(items, collector, siteUrl, pagesByPath, logger, indent
456
455
  collector,
457
456
  siteUrl,
458
457
  pagesByPath,
459
- logger,
460
458
  indent + ' ' // Add 2 spaces for each nesting level
461
459
  );
462
460
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pointsharp/antora-llm-generator",
3
3
  "private": false,
4
- "version": "1.0.0",
4
+ "version": "1.1.1",
5
5
  "description": "An Antora extension to generate llms.txt files for LLM consumption following llmstxt.org specification.",
6
6
  "main": "llm-generator.js",
7
7
  "keywords": [