@datalackey/update-markdown-toc 1.2.1 → 1.4.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/README.md CHANGED
@@ -316,11 +316,6 @@ When such errors occur, the tool prints an error message and exits non-zero with
316
316
 
317
317
  When combined with `--verbose`, skipped files (Markdown files without start/end region markers) are reported explicitly. For example:
318
318
 
319
- ```bash
320
- update-markdown-toc --recursive docs/ --verbose
321
-
322
-
323
-
324
319
  ```bash
325
320
  update-markdown-toc --recursive docs/ --verbose
326
321
  ```
@@ -371,7 +366,8 @@ future release.
371
366
 
372
367
  ## Contributing and Releasing
373
368
 
374
- For development setup, build workflow, and release procedures (including how to
369
+ For code overview, development setup, build workflow, and release procedures (including how to
375
370
  trigger a publish via Changesets), see
376
- [CONTRIBUTING.md](../docs/CONTRIBUTING.md).
371
+ [CONTRIBUTING.md](./docs/CONTRIBUTING.md).
372
+
377
373
 
@@ -1,3 +1,2 @@
1
1
  import type { PluginDescriptor } from "@datalackey/tooling-core";
2
- import type { TocRunConfig } from "./TocRunConfig.js";
3
- export declare const descriptor: PluginDescriptor<TocRunConfig>;
2
+ export declare const descriptor: PluginDescriptor;
@@ -1,50 +1,12 @@
1
- import { parseBooleanOption, parseNumberOption, runLinkValidation, } from "@datalackey/tooling-core";
2
- const DEFAULT_LINK_TIMEOUT_MS = 3000;
1
+ import { runLinkValidation } from "@datalackey/tooling-core";
3
2
  export const descriptor = {
4
3
  name: "update-markdown-toc",
5
4
  description: "Auto-generate Table of Contents for Markdown files",
6
- options: [
7
- {
8
- flag: "--no-external-link-check",
9
- description: "Skip external link validation in check mode",
10
- },
11
- {
12
- flag: "-n",
13
- description: "Skip external link validation in check mode (short form)",
14
- },
15
- {
16
- flag: "--link-timeout-ms",
17
- description: "Timeout in milliseconds for external link requests (default: 3000)",
18
- requiresValue: true,
19
- valueName: "ms",
20
- },
21
- {
22
- flag: "-l",
23
- description: "Timeout in milliseconds for external link requests (short form)",
24
- requiresValue: true,
25
- valueName: "ms",
26
- },
27
- ],
28
- parseOptions(standard, passthrough) {
29
- const noExternalCheck = parseBooleanOption("--no-external-link-check", passthrough) ||
30
- parseBooleanOption("-n", passthrough);
31
- const timeoutMs = parseNumberOption("--link-timeout-ms", passthrough) ??
32
- parseNumberOption("-l", passthrough) ??
33
- DEFAULT_LINK_TIMEOUT_MS;
34
- return {
35
- ...standard,
36
- validateExternalLinks: noExternalCheck ? false : true,
37
- linkTimeoutMs: timeoutMs,
38
- };
39
- },
5
+ options: [],
40
6
  async afterRun(files, config) {
41
7
  if (config.runMode !== "check") {
42
8
  return;
43
9
  }
44
- await runLinkValidation(files, {
45
- ...config,
46
- validateExternalLinks: config.validateExternalLinks,
47
- linkTimeoutMs: config.linkTimeoutMs,
48
- });
10
+ await runLinkValidation(files, config);
49
11
  },
50
12
  };
@@ -1 +1,2 @@
1
+ export declare function stripInlineCode(text: string): string;
1
2
  export declare function generateTOC(content: string): string;
@@ -1,13 +1,19 @@
1
- import GithubSlugger from "github-slugger";
1
+ import { parseHeadings } from "@datalackey/tooling-core";
2
2
  const START = "<!-- TOC:START -->";
3
3
  const END = "<!-- TOC:END -->";
4
+ export function stripInlineCode(text) {
5
+ // [^`\n]* — any char except backtick or newline (inline spans can't cross lines)
6
+ // Replace with "" not "``" — substituting backticks reintroduces spans that eat surrounding text
7
+ return text.replace(/`[^`\n]*`/g, "");
8
+ }
4
9
  function detectLineEnding(text) {
5
10
  return text.includes("\r\n") ? "\r\n" : "\n";
6
11
  }
7
12
  export function generateTOC(content) {
8
13
  const lineEnding = detectLineEnding(content);
9
- const hasStart = content.includes(START);
10
- const hasEnd = content.includes(END);
14
+ const stripped = stripInlineCode(content);
15
+ const hasStart = stripped.includes(START);
16
+ const hasEnd = stripped.includes(END);
11
17
  if (!hasStart && !hasEnd)
12
18
  throw new Error("TOC delimiters not found");
13
19
  if (hasStart && !hasEnd)
@@ -18,26 +24,29 @@ export function generateTOC(content) {
18
24
  const endIndex = content.indexOf(END);
19
25
  const before = content.slice(0, startIndex);
20
26
  const after = content.slice(endIndex + END.length);
21
- const contentWithoutTOC = before.replace(/\s*$/, "") + lineEnding + after.replace(/^\s*/, "");
22
- const lines = contentWithoutTOC.split(lineEnding);
23
- const headings = [];
24
- const slugger = new GithubSlugger();
25
- for (const line of lines) {
26
- const m = /^(#{1,6})\s+(.*)$/.exec(line);
27
- if (!m)
28
- continue;
29
- const level = m[1].length;
30
- const title = m[2].trim();
31
- const anchor = slugger.slug(title);
32
- headings.push({ level: level, title: title, anchor: anchor });
33
- }
27
+ // Two line endings (a blank line) are required between before and after so
28
+ // that any open HTML block in `before` (e.g. a closing </p> tag with no
29
+ // trailing blank line) is closed before remark parses `after`. A single \n
30
+ // is not enough — CommonMark only closes an HTML block at a blank line.
31
+ const contentWithoutTOC = before.replace(/\s*$/, "") +
32
+ lineEnding +
33
+ lineEnding +
34
+ after.replace(/^\s*/, "");
35
+ // parseHeadings (remark/CommonMark) also returns setext-style headings — text
36
+ // immediately followed by `---` or `===` on the next line. This is valid
37
+ // CommonMark but users writing `paragraph\n---` as a paragraph + horizontal
38
+ // rule (a very common pattern) will unintentionally produce TOC entries.
39
+ // We restrict to ATX-style headings (lines starting with `#`) which are the
40
+ // only form a user would deliberately put in a TOC.
41
+ const sourceLines = contentWithoutTOC.split(lineEnding);
42
+ const headings = parseHeadings(contentWithoutTOC).filter((h) => h.line > 0 && /^#{1,6}\s/.test(sourceLines[h.line - 1] ?? ""));
34
43
  if (headings.length === 0) {
35
44
  throw new Error("No headings found to generate TOC");
36
45
  }
37
46
  const minLevel = Math.min(...headings.map((h) => h.level));
38
47
  const tocLines = headings.map((h) => {
39
48
  const indent = " ".repeat(h.level - minLevel);
40
- return `${indent}- [${h.title}](#${h.anchor})`;
49
+ return `${indent}- [${h.rawText}](#${h.slug})`;
41
50
  });
42
51
  const tocBlock = lineEnding + tocLines.join(lineEnding) + lineEnding;
43
52
  return before + START + tocBlock + END + after;
@@ -1,7 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { generateTOC } from "./generateToc.js";
4
- import { debugLog } from "@datalackey/tooling-core";
4
+ import { debugLog, toErrorMessage } from "@datalackey/tooling-core";
5
5
  export function processFile(filePath, config) {
6
6
  const absolutePath = path.resolve(filePath);
7
7
  debugLog(config, `processFile: entry filePath=${absolutePath} runMode=${config.runMode}`);
@@ -17,7 +17,7 @@ export function processFile(filePath, config) {
17
17
  updated = generateTOC(content);
18
18
  }
19
19
  catch (err) {
20
- const message = err instanceof Error ? err.message : String(err);
20
+ const message = toErrorMessage(err);
21
21
  if (message === "TOC delimiters not found" && config.mode === "recursive") {
22
22
  debugLog(config, `processFile: skipped (no markers) filePath=${absolutePath}`);
23
23
  return "skipped";
package/package.json CHANGED
@@ -1,13 +1,12 @@
1
1
  {
2
2
  "name": "@datalackey/update-markdown-toc",
3
- "version": "1.2.1",
3
+ "version": "1.4.0",
4
4
  "description": "Auto-generate Table of Contents for a Markdown file (or files, recursively from a top level folder)",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "private": false,
8
8
  "scripts": {
9
- "build": "tsc -p tsconfig.json",
10
- "test": "npx vitest run --config vitest.config.ts && bash scripts/run-all-tests.sh"
9
+ "prepack": "npx nx build @datalackey/update-markdown-toc 1>&2"
11
10
  },
12
11
  "bin": {
13
12
  "update-markdown-toc": "./bin/update-markdown-toc.js"