Skip to content

Commit 5b3d4e1

Browse files
feat: enhance markdown URL handling for README files
1 parent 74afcdf commit 5b3d4e1

File tree

3 files changed

+27
-23
lines changed

3 files changed

+27
-23
lines changed

src/plugins/llms/markdown.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ test('toMarkdownUrl: converts internal doc-like paths and preserves query/hash',
3434
expect(toMarkdownUrl('/build/rofl/index.mdx?x=1#sec')).toBe(
3535
'/build/rofl.md?x=1#sec',
3636
);
37+
expect(toMarkdownUrl('/build/rofl/README.mdx?x=1#sec')).toBe(
38+
'/build/rofl.md?x=1#sec',
39+
);
3740
});
3841

3942
test('toMarkdownUrl: converts same-host absolute URLs when siteBase is provided', () => {
@@ -140,6 +143,8 @@ test('renderMarkdownFromCapturedTree: rewrites .mdx and index links to permalink
140143
link('/build/rofl/quickstart.mdx#prereq', 'Quickstart'),
141144
t(' '),
142145
link('/build/rofl/index.mdx#top', 'ROFL'),
146+
t(' '),
147+
link('/build/rofl/README.mdx#top', 'ROFL README'),
143148
],
144149
});
145150
const docsByPermalink = new Map([
@@ -152,6 +157,7 @@ test('renderMarkdownFromCapturedTree: rewrites .mdx and index links to permalink
152157

153158
expect(output).toContain('](/build/rofl/quickstart#prereq)');
154159
expect(output).toContain('](/build/rofl/#top)');
160+
expect(output).not.toContain('/build/rofl/README.mdx');
155161
});
156162

157163
test('renderMarkdownFromCapturedTree: linkTarget md rewrites internal links to .md mirrors', () => {
@@ -162,6 +168,8 @@ test('renderMarkdownFromCapturedTree: linkTarget md rewrites internal links to .
162168
t(' '),
163169
link('/build/rofl/index.mdx#top', 'ROFL'),
164170
t(' '),
171+
link('/build/rofl/README.mdx#top', 'ROFL README'),
172+
t(' '),
165173
link('../features/?x=1#sec', 'Features'),
166174
],
167175
});
@@ -180,6 +188,7 @@ test('renderMarkdownFromCapturedTree: linkTarget md rewrites internal links to .
180188

181189
expect(output).toContain('](/build/rofl/quickstart.md#prereq)');
182190
expect(output).toContain('](/build/rofl.md#top)');
191+
expect(output).not.toContain('/build/rofl/README.mdx');
183192
expect(output).toContain('](/build/rofl/features.md?x=1#sec)');
184193
});
185194

src/plugins/llms/markdown.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,12 @@ function toMarkdownPathname(pathname: string): string {
7878
return parent === '/' ? '/index.md' : `${parent}.md`;
7979
}
8080

81+
// Handle /foo/README -> /foo.md (common Docusaurus pattern)
82+
if (/\/readme$/i.test(withoutExtension)) {
83+
const parent = withoutExtension.slice(0, -'/README'.length) || '/';
84+
return parent === '/' ? '/index.md' : `${parent}.md`;
85+
}
86+
8187
return `${withoutExtension}.md`;
8288
}
8389

@@ -383,6 +389,13 @@ function findCanonicalPermalink(
383389
const indexed = checkPermalink(parent) ?? checkPermalink(parent + '/');
384390
if (indexed) return indexed;
385391
}
392+
393+
// Handle /foo/README.md -> /foo (common Docusaurus pattern)
394+
if (/\/readme$/i.test(withoutExtension)) {
395+
const parent = withoutExtension.slice(0, -'/README'.length) || '/';
396+
const readmed = checkPermalink(parent) ?? checkPermalink(parent + '/');
397+
if (readmed) return readmed;
398+
}
386399
}
387400

388401
return null;

src/remark/llms-capture.ts

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import * as fs from 'fs';
22
import * as path from 'path';
33

44
import type {Transformer} from 'unified';
5-
import type {VFile} from 'vfile';
65

76
import {
87
captureDirForSite,
@@ -16,27 +15,6 @@ interface Options {
1615

1716
let hasLoggedCaptureDir = false;
1817

19-
function resolveSiteDir(vfile: VFile, options?: Options): string {
20-
// Priority 1: Explicit option
21-
if (typeof options?.siteDir === 'string' && options.siteDir.trim()) {
22-
return options.siteDir;
23-
}
24-
25-
// Priority 2: VFile data
26-
const data = vfile?.data as Record<string, unknown> | undefined;
27-
if (data && typeof data.siteDir === 'string' && data.siteDir.trim()) {
28-
return data.siteDir;
29-
}
30-
31-
// Priority 3: VFile cwd
32-
if (typeof vfile?.cwd === 'string' && vfile.cwd.trim()) {
33-
return vfile.cwd;
34-
}
35-
36-
// Fallback: process cwd
37-
return process.cwd();
38-
}
39-
4018
function cleanupTempFile(tempFile: string): void {
4119
try {
4220
fs.rmSync(tempFile, {force: true});
@@ -67,12 +45,16 @@ function atomicWriteJson(filename: string, payload: unknown): void {
6745
}
6846

6947
export default function remarkLlmsCapture(options: Options = {}): Transformer {
48+
const siteDir =
49+
typeof options.siteDir === 'string' && options.siteDir.trim()
50+
? options.siteDir
51+
: process.cwd();
52+
7053
return (tree, vfile) => {
7154
const sourcePath = vfile.history?.[0] ?? vfile.path;
7255
if (!sourcePath) return;
7356

7457
try {
75-
const siteDir = resolveSiteDir(vfile, options);
7658
const resolvedSource = path.isAbsolute(sourcePath)
7759
? sourcePath
7860
: path.join(siteDir, sourcePath);

0 commit comments

Comments
 (0)