@dominikcz/greg 0.9.27
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 +397 -0
- package/bin/greg.js +241 -0
- package/bin/init.js +351 -0
- package/bin/templates/docs/getting-started.md +47 -0
- package/bin/templates/docs/index.md +11 -0
- package/bin/templates/greg.config.js +39 -0
- package/bin/templates/greg.config.ts +38 -0
- package/bin/templates/index.html +16 -0
- package/bin/templates/src/App.svelte +5 -0
- package/bin/templates/src/app.css +20 -0
- package/bin/templates/src/main.js +9 -0
- package/bin/templates/svelte.config.js +1 -0
- package/bin/templates/tsconfig.json +21 -0
- package/bin/templates/vite.config.js +23 -0
- package/docs/__partials/markdown/examples/basic.md +4 -0
- package/docs/__partials/markdown/examples/diff.md +10 -0
- package/docs/__partials/markdown/examples/focus.md +5 -0
- package/docs/__partials/markdown/examples/language-title.md +3 -0
- package/docs/__partials/markdown/examples/line-highlighting.md +5 -0
- package/docs/__partials/markdown/examples/line-numbers.md +5 -0
- package/docs/__partials/note.md +4 -0
- package/docs/guide/__shared-warning.md +4 -0
- package/docs/guide/asset-handling.md +88 -0
- package/docs/guide/deploying.md +162 -0
- package/docs/guide/getting-started.md +334 -0
- package/docs/guide/index.md +23 -0
- package/docs/guide/localization.md +290 -0
- package/docs/guide/markdown/code.md +95 -0
- package/docs/guide/markdown/components-and-mermaid.md +43 -0
- package/docs/guide/markdown/containers.md +110 -0
- package/docs/guide/markdown/header-anchors.md +34 -0
- package/docs/guide/markdown/includes.md +84 -0
- package/docs/guide/markdown/index.md +20 -0
- package/docs/guide/markdown/inline-attributes.md +21 -0
- package/docs/guide/markdown/links-and-toc.md +64 -0
- package/docs/guide/markdown/math.md +54 -0
- package/docs/guide/markdown/syntax-highlighting.md +75 -0
- package/docs/guide/routing.md +150 -0
- package/docs/guide/using-svelte.md +88 -0
- package/docs/guide/versioning.md +281 -0
- package/docs/incompatibilities.md +48 -0
- package/docs/index.md +43 -0
- package/docs/reference/badge.md +100 -0
- package/docs/reference/carbon-ads.md +46 -0
- package/docs/reference/code-group.md +126 -0
- package/docs/reference/home-page.md +232 -0
- package/docs/reference/index.md +18 -0
- package/docs/reference/markdowndocs.md +275 -0
- package/docs/reference/outline.md +79 -0
- package/docs/reference/search.md +263 -0
- package/docs/reference/steps.md +200 -0
- package/docs/reference/team-page.md +189 -0
- package/docs/reference/theme.md +150 -0
- package/fakeDocsGenerator/generate_docs.js +310 -0
- package/package.json +92 -0
- package/scripts/build-versions.js +609 -0
- package/scripts/generate-static.js +79 -0
- package/scripts/render-markdown.js +420 -0
- package/src/lib/MarkdownDocs/AiChat.svelte +936 -0
- package/src/lib/MarkdownDocs/BackToTop.svelte +68 -0
- package/src/lib/MarkdownDocs/Breadcrumb.svelte +68 -0
- package/src/lib/MarkdownDocs/DocsNavigation.svelte +149 -0
- package/src/lib/MarkdownDocs/DocsSiteHeader.svelte +758 -0
- package/src/lib/MarkdownDocs/DocsVersionSwitcher.svelte +103 -0
- package/src/lib/MarkdownDocs/MarkdownDocs.svelte +2115 -0
- package/src/lib/MarkdownDocs/MarkdownRenderer.svelte +487 -0
- package/src/lib/MarkdownDocs/Outline.svelte +238 -0
- package/src/lib/MarkdownDocs/PrevNext.svelte +115 -0
- package/src/lib/MarkdownDocs/SearchModal.svelte +1241 -0
- package/src/lib/MarkdownDocs/TreeView.svelte +32 -0
- package/src/lib/MarkdownDocs/TreeViewItem.svelte +219 -0
- package/src/lib/MarkdownDocs/VersionOutdatedNotice.svelte +72 -0
- package/src/lib/MarkdownDocs/__tests__/codeDirectives.test.js +54 -0
- package/src/lib/MarkdownDocs/__tests__/common.test.js +41 -0
- package/src/lib/MarkdownDocs/__tests__/docsExamplesLint.test.js +77 -0
- package/src/lib/MarkdownDocs/__tests__/fixtures/docs/markdown/__partial-basic.md +3 -0
- package/src/lib/MarkdownDocs/__tests__/fixtures/docs/markdown/snippet.js +9 -0
- package/src/lib/MarkdownDocs/__tests__/fixtures/includes/part.md +11 -0
- package/src/lib/MarkdownDocs/__tests__/fixtures/includes/wrapper.md +5 -0
- package/src/lib/MarkdownDocs/__tests__/fixtures/snippets/sample.js +8 -0
- package/src/lib/MarkdownDocs/__tests__/fixtures/snippets/sample.md +5 -0
- package/src/lib/MarkdownDocs/__tests__/helpers.js +67 -0
- package/src/lib/MarkdownDocs/__tests__/localeUtils.test.js +204 -0
- package/src/lib/MarkdownDocs/__tests__/markdown.test.js +704 -0
- package/src/lib/MarkdownDocs/__tests__/markdownRendererRuntime.test.js +65 -0
- package/src/lib/MarkdownDocs/__tests__/searchIndexBuilder.test.js +117 -0
- package/src/lib/MarkdownDocs/__tests__/sqliteStore.test.js +202 -0
- package/src/lib/MarkdownDocs/__tests__/useRouter.test.js +16 -0
- package/src/lib/MarkdownDocs/ai/adapters/customAdapter.js +14 -0
- package/src/lib/MarkdownDocs/ai/adapters/customAdapter.ts +43 -0
- package/src/lib/MarkdownDocs/ai/adapters/ollamaAdapter.js +81 -0
- package/src/lib/MarkdownDocs/ai/adapters/ollamaAdapter.ts +116 -0
- package/src/lib/MarkdownDocs/ai/adapters/openaiAdapter.js +92 -0
- package/src/lib/MarkdownDocs/ai/adapters/openaiAdapter.ts +137 -0
- package/src/lib/MarkdownDocs/ai/aiProvider.ts +31 -0
- package/src/lib/MarkdownDocs/ai/characters.js +52 -0
- package/src/lib/MarkdownDocs/ai/characters.ts +69 -0
- package/src/lib/MarkdownDocs/ai/chunkStore.ts +25 -0
- package/src/lib/MarkdownDocs/ai/chunker.js +85 -0
- package/src/lib/MarkdownDocs/ai/chunker.ts +135 -0
- package/src/lib/MarkdownDocs/ai/docLinker.js +26 -0
- package/src/lib/MarkdownDocs/ai/docLinker.ts +36 -0
- package/src/lib/MarkdownDocs/ai/promptBuilder.js +33 -0
- package/src/lib/MarkdownDocs/ai/promptBuilder.ts +53 -0
- package/src/lib/MarkdownDocs/ai/ragPipeline.js +54 -0
- package/src/lib/MarkdownDocs/ai/ragPipeline.ts +106 -0
- package/src/lib/MarkdownDocs/ai/stores/memoryStore.js +88 -0
- package/src/lib/MarkdownDocs/ai/stores/memoryStore.ts +112 -0
- package/src/lib/MarkdownDocs/ai/stores/sqliteStore.ts +372 -0
- package/src/lib/MarkdownDocs/ai/types.ts +71 -0
- package/src/lib/MarkdownDocs/aiServer.js +288 -0
- package/src/lib/MarkdownDocs/codeDirectives.js +191 -0
- package/src/lib/MarkdownDocs/codeFenceInfo.js +45 -0
- package/src/lib/MarkdownDocs/codeGroup.ts +46 -0
- package/src/lib/MarkdownDocs/common.ts +47 -0
- package/src/lib/MarkdownDocs/docsUtils.js +281 -0
- package/src/lib/MarkdownDocs/index.plugins.js +22 -0
- package/src/lib/MarkdownDocs/layouts/LayoutDoc.svelte +8 -0
- package/src/lib/MarkdownDocs/layouts/LayoutHome.svelte +58 -0
- package/src/lib/MarkdownDocs/layouts/LayoutPage.svelte +9 -0
- package/src/lib/MarkdownDocs/loadGregConfig.js +82 -0
- package/src/lib/MarkdownDocs/localeUtils.ts +682 -0
- package/src/lib/MarkdownDocs/markdownRendererRuntime.ts +314 -0
- package/src/lib/MarkdownDocs/mermaidThemes.js +319 -0
- package/src/lib/MarkdownDocs/navigationUtils.js +22 -0
- package/src/lib/MarkdownDocs/rehypeCodeGroup.js +326 -0
- package/src/lib/MarkdownDocs/rehypeCodeTitle.js +96 -0
- package/src/lib/MarkdownDocs/rehypeToc.js +170 -0
- package/src/lib/MarkdownDocs/remarkCodeMeta.js +22 -0
- package/src/lib/MarkdownDocs/remarkContainers.js +329 -0
- package/src/lib/MarkdownDocs/remarkCustomAnchors.js +42 -0
- package/src/lib/MarkdownDocs/remarkEscapeSvelte.js +33 -0
- package/src/lib/MarkdownDocs/remarkGlobalComponents.js +65 -0
- package/src/lib/MarkdownDocs/remarkImports.js +461 -0
- package/src/lib/MarkdownDocs/remarkImportsBrowser.js +349 -0
- package/src/lib/MarkdownDocs/remarkInlineAttrs.js +95 -0
- package/src/lib/MarkdownDocs/remarkMathToHtml.js +138 -0
- package/src/lib/MarkdownDocs/searchIndexBuilder.js +497 -0
- package/src/lib/MarkdownDocs/searchServer.js +263 -0
- package/src/lib/MarkdownDocs/treeViewTypes.ts +11 -0
- package/src/lib/MarkdownDocs/useRouter.svelte.ts +114 -0
- package/src/lib/MarkdownDocs/useSplitter.svelte.ts +33 -0
- package/src/lib/MarkdownDocs/versioningDefaults.js +20 -0
- package/src/lib/MarkdownDocs/vitePluginAiServer.js +204 -0
- package/src/lib/MarkdownDocs/vitePluginCopyDocs.js +153 -0
- package/src/lib/MarkdownDocs/vitePluginFrontmatter.js +109 -0
- package/src/lib/MarkdownDocs/vitePluginGregConfig.js +108 -0
- package/src/lib/MarkdownDocs/vitePluginSearchIndex.js +57 -0
- package/src/lib/MarkdownDocs/vitePluginSearchServer.js +190 -0
- package/src/lib/components/Badge.svelte +59 -0
- package/src/lib/components/Button.svelte +138 -0
- package/src/lib/components/CarbonAds.svelte +99 -0
- package/src/lib/components/CodeGroup.svelte +102 -0
- package/src/lib/components/Feature.svelte +209 -0
- package/src/lib/components/Features.svelte +123 -0
- package/src/lib/components/Hero.svelte +399 -0
- package/src/lib/components/Image.svelte +128 -0
- package/src/lib/components/Link.svelte +105 -0
- package/src/lib/components/SocialLink.svelte +84 -0
- package/src/lib/components/SocialLinks.svelte +33 -0
- package/src/lib/components/Steps.svelte +143 -0
- package/src/lib/components/TeamMember.svelte +273 -0
- package/src/lib/components/TeamMembers.svelte +81 -0
- package/src/lib/components/TeamPage.svelte +65 -0
- package/src/lib/components/TeamPageSection.svelte +108 -0
- package/src/lib/components/TeamPageTitle.svelte +89 -0
- package/src/lib/components/index.js +24 -0
- package/src/lib/portal/context.js +12 -0
- package/src/lib/portal/index.js +3 -0
- package/src/lib/portal/portal.svelte +14 -0
- package/src/lib/portal/slot.svelte +8 -0
- package/src/lib/scss/__code.scss +128 -0
- package/src/lib/scss/__containers.scss +99 -0
- package/src/lib/scss/__markdown.scss +447 -0
- package/src/lib/scss/__scrollbar.scss +60 -0
- package/src/lib/scss/__steps.scss +100 -0
- package/src/lib/scss/__theme.scss +238 -0
- package/src/lib/scss/__toc.scss +55 -0
- package/src/lib/scss/__utilities.scss +7 -0
- package/src/lib/scss/greg.scss +9 -0
- package/src/lib/spinner/spinner.svelte +42 -0
- package/svelte.config.js +146 -0
- package/types/index.d.ts +456 -0
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { unified } from 'unified';
|
|
4
|
+
import remarkParse from 'remark-parse';
|
|
5
|
+
|
|
6
|
+
const SNIPPET_RE = /^<<<\s+(.+)$/;
|
|
7
|
+
const INCLUDE_RE = /^<!--\s*@include:\s*([^\s]+)\s*-->$/;
|
|
8
|
+
const TITLE_RE = /\s+\[([^\]]+)\]\s*$/;
|
|
9
|
+
const BRACES_RE = /\{([^}]*)\}\s*$/;
|
|
10
|
+
const REGION_RE = /^(.*?)#([^#{]+)$/;
|
|
11
|
+
|
|
12
|
+
const extToLang = {
|
|
13
|
+
'.js': 'js',
|
|
14
|
+
'.mjs': 'js',
|
|
15
|
+
'.cjs': 'js',
|
|
16
|
+
'.ts': 'ts',
|
|
17
|
+
'.tsx': 'tsx',
|
|
18
|
+
'.jsx': 'jsx',
|
|
19
|
+
'.json': 'json',
|
|
20
|
+
'.css': 'css',
|
|
21
|
+
'.scss': 'scss',
|
|
22
|
+
'.html': 'html',
|
|
23
|
+
'.md': 'md',
|
|
24
|
+
'.sh': 'sh',
|
|
25
|
+
'.bash': 'bash',
|
|
26
|
+
'.yml': 'yaml',
|
|
27
|
+
'.yaml': 'yaml',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
function escapeRegExp(value) {
|
|
31
|
+
return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ── Path resolution (3 rules + guard) ──────────────────────────────
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Derive the directory of the current .md file from VFile metadata.
|
|
38
|
+
*
|
|
39
|
+
* Handles three forms that mdsvex / vite-plugin-svelte may provide:
|
|
40
|
+
* 1. Filesystem absolute C:\…\docs\md\file.md /home/…/docs/md/file.md
|
|
41
|
+
* 2. Virtual absolute /docs/md/file.md (with docsDir prefix)
|
|
42
|
+
* /md/file.md (without docsDir prefix)
|
|
43
|
+
* 3. Relative md/file.md (resolved via file.cwd)
|
|
44
|
+
*
|
|
45
|
+
* Falls back to <sourceRoot>/<docsDir> when no path info is available.
|
|
46
|
+
*/
|
|
47
|
+
function getCurrentDir(file, sourceRoot, docsDir) {
|
|
48
|
+
const docsRoot = path.resolve(sourceRoot, docsDir);
|
|
49
|
+
const raw =
|
|
50
|
+
file?.path ||
|
|
51
|
+
(Array.isArray(file?.history) && file.history.length > 0
|
|
52
|
+
? file.history[file.history.length - 1]
|
|
53
|
+
: null);
|
|
54
|
+
if (!raw) return docsRoot;
|
|
55
|
+
|
|
56
|
+
const normalized = path.normalize(raw);
|
|
57
|
+
|
|
58
|
+
// Real filesystem absolute (starts inside sourceRoot or has a drive letter)
|
|
59
|
+
if (
|
|
60
|
+
path.isAbsolute(normalized) &&
|
|
61
|
+
(normalized.startsWith(sourceRoot) || /^[a-zA-Z]:/.test(normalized))
|
|
62
|
+
) {
|
|
63
|
+
return path.dirname(normalized);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Virtual absolute /docs/… or /markdown/…
|
|
67
|
+
if (raw.startsWith('/')) {
|
|
68
|
+
const stripped = raw.replace(/^\/+/, '');
|
|
69
|
+
const firstSeg = stripped.split(/[\\/]/)[0];
|
|
70
|
+
const base = firstSeg === docsDir ? sourceRoot : docsRoot;
|
|
71
|
+
return path.dirname(path.join(base, stripped));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Relative — resolve against file.cwd or sourceRoot
|
|
75
|
+
return path.dirname(path.resolve(file?.cwd || sourceRoot, raw));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Resolve an import specifier to an absolute file path.
|
|
80
|
+
*
|
|
81
|
+
* `@/path` or `@path` → <sourceRoot>/path
|
|
82
|
+
* /path → <docsRoot>/path (leading / = relative to docsDir)
|
|
83
|
+
* ./path ../path → <currentDir>/path
|
|
84
|
+
*/
|
|
85
|
+
function resolveImportPath(specPath, currentDir, sourceRoot, docsDir) {
|
|
86
|
+
if (specPath === '@') return sourceRoot;
|
|
87
|
+
if (specPath.startsWith('@/')) return path.resolve(sourceRoot, specPath.slice(2));
|
|
88
|
+
if (specPath.startsWith('@')) return path.resolve(sourceRoot, specPath.slice(1));
|
|
89
|
+
if (specPath.startsWith('/')) return path.resolve(sourceRoot, docsDir, specPath.slice(1));
|
|
90
|
+
return path.resolve(currentDir, specPath);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Throw if resolvedPath escapes the source root. */
|
|
94
|
+
function assertInsideRoot(resolvedPath, sourceRoot) {
|
|
95
|
+
const rel = path.relative(sourceRoot, resolvedPath);
|
|
96
|
+
if (rel.startsWith('..') || path.isAbsolute(rel)) {
|
|
97
|
+
throw new Error(`Import path "${resolvedPath}" escapes the source root "${sourceRoot}"`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function parseTitle(value) {
|
|
102
|
+
const match = value.match(TITLE_RE);
|
|
103
|
+
if (!match) return { text: value.trim(), title: '' };
|
|
104
|
+
return { text: value.slice(0, match.index).trim(), title: match[1].trim() };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function parseBraces(value) {
|
|
108
|
+
const match = value.match(BRACES_RE);
|
|
109
|
+
if (!match) return { text: value.trim(), braces: '' };
|
|
110
|
+
return { text: value.slice(0, match.index).trim(), braces: match[1].trim() };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function parseRegion(value) {
|
|
114
|
+
const match = value.match(REGION_RE);
|
|
115
|
+
if (!match) return { filePart: value.trim(), regionOrAnchor: '' };
|
|
116
|
+
return { filePart: match[1].trim(), regionOrAnchor: match[2].trim() };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function parseRange(value) {
|
|
120
|
+
const text = String(value ?? '').trim();
|
|
121
|
+
if (!text) return null;
|
|
122
|
+
if (/^\d+$/.test(text)) {
|
|
123
|
+
const n = Number(text);
|
|
124
|
+
return { start: n, end: n };
|
|
125
|
+
}
|
|
126
|
+
const match = text.match(/^(\d*)\s*,\s*(\d*)$/);
|
|
127
|
+
if (!match) return null;
|
|
128
|
+
const start = match[1] ? Number(match[1]) : null;
|
|
129
|
+
const end = match[2] ? Number(match[2]) : null;
|
|
130
|
+
return { start, end };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function selectLines(content, rangeText) {
|
|
134
|
+
const range = parseRange(rangeText);
|
|
135
|
+
if (!range) return content;
|
|
136
|
+
const lines = content.split(/\r?\n/);
|
|
137
|
+
const start = range.start ?? 1;
|
|
138
|
+
const end = range.end ?? lines.length;
|
|
139
|
+
const from = Math.max(1, start);
|
|
140
|
+
const to = Math.max(from, Math.min(end, lines.length));
|
|
141
|
+
return lines.slice(from - 1, to).join('\n');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function selectRegion(content, regionName) {
|
|
145
|
+
if (!regionName) return content;
|
|
146
|
+
const lines = content.split(/\r?\n/);
|
|
147
|
+
const escapedName = escapeRegExp(regionName);
|
|
148
|
+
const startRe = new RegExp(`#region\\s+${escapedName}\\s*$`, 'i');
|
|
149
|
+
const endRe = new RegExp(`#endregion\\s+${escapedName}\\s*$`, 'i');
|
|
150
|
+
|
|
151
|
+
let start = -1;
|
|
152
|
+
let end = -1;
|
|
153
|
+
for (let i = 0; i < lines.length; i++) {
|
|
154
|
+
if (start === -1 && startRe.test(lines[i])) {
|
|
155
|
+
start = i;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (start !== -1 && endRe.test(lines[i])) {
|
|
159
|
+
end = i;
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (start === -1 || end === -1 || end <= start) return content;
|
|
164
|
+
return lines.slice(start + 1, end).join('\n');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function slugifyHeading(value) {
|
|
168
|
+
return value
|
|
169
|
+
.toLowerCase()
|
|
170
|
+
.replace(/<[^>]+>/g, '')
|
|
171
|
+
.replace(/[^\w\s-]/g, '')
|
|
172
|
+
.trim()
|
|
173
|
+
.replace(/\s+/g, '-');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function getHeadingId(rawHeadingText) {
|
|
177
|
+
const customIdMatch = rawHeadingText.match(/\s*\{#([^}]+)\}\s*$/);
|
|
178
|
+
if (customIdMatch?.[1]) return customIdMatch[1].trim().toLowerCase();
|
|
179
|
+
return slugifyHeading(rawHeadingText.replace(/\s*\{#([^}]+)\}\s*$/, ''));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function selectMarkdownSectionByAnchor(content, anchor) {
|
|
183
|
+
if (!anchor) return content;
|
|
184
|
+
const lines = content.split(/\r?\n/);
|
|
185
|
+
const headingRe = /^(#{1,6})\s+(.+)$/;
|
|
186
|
+
const wanted = anchor.toLowerCase();
|
|
187
|
+
|
|
188
|
+
let startIndex = -1;
|
|
189
|
+
let startDepth = 0;
|
|
190
|
+
for (let i = 0; i < lines.length; i++) {
|
|
191
|
+
const match = lines[i].match(headingRe);
|
|
192
|
+
if (!match) continue;
|
|
193
|
+
const depth = match[1].length;
|
|
194
|
+
const id = getHeadingId(match[2]);
|
|
195
|
+
if (id === wanted) {
|
|
196
|
+
startIndex = i;
|
|
197
|
+
startDepth = depth;
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (startIndex === -1) return content;
|
|
202
|
+
|
|
203
|
+
let endIndex = lines.length;
|
|
204
|
+
for (let i = startIndex + 1; i < lines.length; i++) {
|
|
205
|
+
const match = lines[i].match(headingRe);
|
|
206
|
+
if (!match) continue;
|
|
207
|
+
const depth = match[1].length;
|
|
208
|
+
if (depth <= startDepth) {
|
|
209
|
+
endIndex = i;
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return lines.slice(startIndex, endIndex).join('\n');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function inferLang(filePath, langOverride) {
|
|
218
|
+
if (langOverride) return langOverride;
|
|
219
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
220
|
+
return extToLang[ext] ?? ext.replace(/^\./, '') ?? '';
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** Reconstruct raw text from an inline mdast node (text, linkReference, image, etc.) */
|
|
224
|
+
function inlineNodeText(node) {
|
|
225
|
+
if (!node) return '';
|
|
226
|
+
if (node.type === 'text' || node.type === 'inlineCode') return node.value ?? '';
|
|
227
|
+
if (node.type === 'linkReference') {
|
|
228
|
+
const label = node.label ?? node.identifier ?? '';
|
|
229
|
+
return `[${label}]`;
|
|
230
|
+
}
|
|
231
|
+
if (node.type === 'link') return node.children?.map(inlineNodeText).join('') ?? '';
|
|
232
|
+
if (node.children) return node.children.map(inlineNodeText).join('');
|
|
233
|
+
return '';
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async function readText(filePath) {
|
|
237
|
+
try {
|
|
238
|
+
return await fs.readFile(filePath, 'utf8');
|
|
239
|
+
} catch (err) {
|
|
240
|
+
if (err.code === 'ENOENT') return null;
|
|
241
|
+
throw err;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function parseSnippetSpec(raw) {
|
|
246
|
+
const withTitle = parseTitle(raw);
|
|
247
|
+
const withBraces = parseBraces(withTitle.text);
|
|
248
|
+
const withRegion = parseRegion(withBraces.text);
|
|
249
|
+
|
|
250
|
+
let rangePart = '';
|
|
251
|
+
let langOverride = '';
|
|
252
|
+
let metaTail = '';
|
|
253
|
+
|
|
254
|
+
if (withBraces.braces) {
|
|
255
|
+
const tokens = withBraces.braces.split(/\s+/).filter(Boolean);
|
|
256
|
+
if (tokens.length > 0 && /^(\d+|\d*\s*,\s*\d*)$/.test(tokens[0])) {
|
|
257
|
+
rangePart = tokens.shift() ?? '';
|
|
258
|
+
}
|
|
259
|
+
if (tokens.length > 0 && !tokens[0].includes(':')) {
|
|
260
|
+
langOverride = tokens.shift() ?? '';
|
|
261
|
+
}
|
|
262
|
+
metaTail = tokens.join(' ').trim();
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return {
|
|
266
|
+
title: withTitle.title,
|
|
267
|
+
filePart: withRegion.filePart,
|
|
268
|
+
regionOrAnchor: withRegion.regionOrAnchor,
|
|
269
|
+
rangePart,
|
|
270
|
+
langOverride,
|
|
271
|
+
metaTail,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function buildCodeMeta(title, metaTail) {
|
|
276
|
+
const parts = [];
|
|
277
|
+
if (metaTail) parts.push(metaTail);
|
|
278
|
+
if (title) parts.push(`[${title}]`);
|
|
279
|
+
return parts.join(' ').trim() || null;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
async function buildSnippetNode(rawSpec, currentDir, sourceRoot, docsDir) {
|
|
283
|
+
const parsed = parseSnippetSpec(rawSpec);
|
|
284
|
+
const absolutePath = resolveImportPath(parsed.filePart, currentDir, sourceRoot, docsDir);
|
|
285
|
+
assertInsideRoot(absolutePath, sourceRoot);
|
|
286
|
+
let content = await readText(absolutePath);
|
|
287
|
+
|
|
288
|
+
if (content === null) {
|
|
289
|
+
return {
|
|
290
|
+
type: 'code',
|
|
291
|
+
lang: 'text',
|
|
292
|
+
meta: '',
|
|
293
|
+
value: `[remarkImports] File not found: ${absolutePath}`,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (parsed.regionOrAnchor) {
|
|
298
|
+
content = selectRegion(content, parsed.regionOrAnchor);
|
|
299
|
+
}
|
|
300
|
+
if (parsed.rangePart) {
|
|
301
|
+
content = selectLines(content, parsed.rangePart);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const lang = inferLang(absolutePath, parsed.langOverride);
|
|
305
|
+
const meta = buildCodeMeta(parsed.title, parsed.metaTail);
|
|
306
|
+
return {
|
|
307
|
+
type: 'code',
|
|
308
|
+
lang: lang || null,
|
|
309
|
+
meta,
|
|
310
|
+
value: content,
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function parseIncludeSpec(raw) {
|
|
315
|
+
const withBraces = parseBraces(raw);
|
|
316
|
+
const withRegion = parseRegion(withBraces.text);
|
|
317
|
+
return {
|
|
318
|
+
filePart: withRegion.filePart,
|
|
319
|
+
regionOrAnchor: withRegion.regionOrAnchor,
|
|
320
|
+
rangePart: withBraces.braces,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function looksLikeSvelteMarkup(content) {
|
|
325
|
+
const text = String(content ?? '').trim();
|
|
326
|
+
if (!text) return false;
|
|
327
|
+
const hasComponentTag = /<\/?[A-Z][A-Za-z0-9]*\b/.test(text);
|
|
328
|
+
const hasSvelteExpressionAttr = /=\s*\{/.test(text);
|
|
329
|
+
const hasSvelteBlockSyntax = /\{[#/:@][^}]+\}/.test(text);
|
|
330
|
+
return (hasComponentTag && hasSvelteExpressionAttr) || hasSvelteBlockSyntax;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
async function buildIncludeNodes(rawSpec, currentDir, sourceRoot, docsDir) {
|
|
334
|
+
const parsed = parseIncludeSpec(rawSpec);
|
|
335
|
+
const absolutePath = resolveImportPath(parsed.filePart, currentDir, sourceRoot, docsDir);
|
|
336
|
+
assertInsideRoot(absolutePath, sourceRoot);
|
|
337
|
+
let content = await readText(absolutePath);
|
|
338
|
+
|
|
339
|
+
if (content === null) {
|
|
340
|
+
return { nodes: [{ type: 'paragraph', children: [{ type: 'text', value: `[remarkImports] File not found: ${absolutePath}` }] }], includeDir: currentDir };
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (parsed.regionOrAnchor) {
|
|
344
|
+
const byRegion = selectRegion(content, parsed.regionOrAnchor);
|
|
345
|
+
content = byRegion === content ? selectMarkdownSectionByAnchor(content, parsed.regionOrAnchor) : byRegion;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (parsed.rangePart) {
|
|
349
|
+
content = selectLines(content, parsed.rangePart);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (looksLikeSvelteMarkup(content)) {
|
|
353
|
+
return {
|
|
354
|
+
nodes: [{ type: 'html', value: content }],
|
|
355
|
+
includeDir: path.dirname(absolutePath),
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const tree = unified().use(remarkParse).parse(content);
|
|
360
|
+
return { nodes: tree.children ?? [], includeDir: path.dirname(absolutePath) };
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function markNodesBaseDir(nodes, baseDir) {
|
|
364
|
+
if (!Array.isArray(nodes)) return;
|
|
365
|
+
for (const node of nodes) {
|
|
366
|
+
node.data = node.data ?? {};
|
|
367
|
+
node.data.__includeDir = baseDir;
|
|
368
|
+
if (Array.isArray(node.children)) {
|
|
369
|
+
markNodesBaseDir(node.children, baseDir);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const EXTERNAL_URL_RE = /^(?:[a-z][a-z\d+\-.]*:|\/\/)/i;
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Normalize a link URL to match VitePress routing conventions:
|
|
378
|
+
* - Strip .md extension (e.g. ./routing.md → ./routing)
|
|
379
|
+
* - Strip .html extension (e.g. ./routing.html → ./routing)
|
|
380
|
+
* - Collapse /index suffix (e.g. ./guide/index → ./guide)
|
|
381
|
+
* Only applied to internal (non-external) links, not to images.
|
|
382
|
+
*/
|
|
383
|
+
function normalizeInternalLinkUrl(url) {
|
|
384
|
+
if (EXTERNAL_URL_RE.test(url)) return url;
|
|
385
|
+
// Separate the hash fragment, if any
|
|
386
|
+
const hashIdx = url.indexOf('#');
|
|
387
|
+
const pathPart = hashIdx >= 0 ? url.slice(0, hashIdx) : url;
|
|
388
|
+
const hashPart = hashIdx >= 0 ? url.slice(hashIdx) : '';
|
|
389
|
+
|
|
390
|
+
let normalized = pathPart
|
|
391
|
+
.replace(/\.(md|html)$/i, '') // strip .md / .html
|
|
392
|
+
.replace(/\/index$/, ''); // /foo/index → /foo
|
|
393
|
+
|
|
394
|
+
return (normalized || '.') + hashPart;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/** Normalize internal link URLs (remark AST). */
|
|
398
|
+
function expandAliasInUrls(node) {
|
|
399
|
+
// Normalize internal link URLs to VitePress routing conventions (links only, not images).
|
|
400
|
+
// Skip normalization when the link has an explicit target attribute (e.g. {target="_self"}),
|
|
401
|
+
// which signals a non-VitePress page where the .html extension must be preserved.
|
|
402
|
+
if (node.type === 'link' && typeof node.url === 'string') {
|
|
403
|
+
const hasExplicitTarget = node.data?.hProperties?.target;
|
|
404
|
+
if (!hasExplicitTarget) {
|
|
405
|
+
node.url = normalizeInternalLinkUrl(node.url);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
async function transformChildren(children, currentDir, sourceRoot, docsDir, depth) {
|
|
411
|
+
if (!Array.isArray(children) || depth > 20) return;
|
|
412
|
+
|
|
413
|
+
for (let i = 0; i < children.length; i++) {
|
|
414
|
+
const node = children[i];
|
|
415
|
+
const nodeDir = node?.data?.__includeDir || currentDir;
|
|
416
|
+
|
|
417
|
+
// Normalize internal link URLs
|
|
418
|
+
expandAliasInUrls(node);
|
|
419
|
+
|
|
420
|
+
if (node?.type === 'paragraph' && Array.isArray(node.children)) {
|
|
421
|
+
// Reconstruct the raw line by joining all inline node values.
|
|
422
|
+
// This handles cases where remark-parse splits the line into multiple
|
|
423
|
+
// inline nodes, e.g. `[title]` becoming a linkReference child.
|
|
424
|
+
const line = node.children.map(inlineNodeText).join('').trim();
|
|
425
|
+
const snippetMatch = line.match(SNIPPET_RE);
|
|
426
|
+
if (snippetMatch?.[1]) {
|
|
427
|
+
const snippetNode = await buildSnippetNode(snippetMatch[1].trim(), nodeDir, sourceRoot, docsDir);
|
|
428
|
+
children.splice(i, 1, snippetNode);
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (node?.type === 'html') {
|
|
434
|
+
const line = String(node.value ?? '').trim();
|
|
435
|
+
const includeMatch = line.match(INCLUDE_RE);
|
|
436
|
+
if (includeMatch?.[1]) {
|
|
437
|
+
const included = await buildIncludeNodes(includeMatch[1].trim(), nodeDir, sourceRoot, docsDir);
|
|
438
|
+
markNodesBaseDir(included.nodes, included.includeDir);
|
|
439
|
+
children.splice(i, 1, ...included.nodes);
|
|
440
|
+
i -= 1;
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (Array.isArray(node?.children)) {
|
|
446
|
+
await transformChildren(node.children, nodeDir, sourceRoot, docsDir, depth + 1);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
export function remarkImports(options = {}) {
|
|
452
|
+
const sourceRoot = path.resolve(options.sourceRoot ?? options.rootDir ?? process.cwd());
|
|
453
|
+
const docsDir = String(options.docsDir ?? 'docs').replace(/^\/+|\/+$/g, '') || 'docs';
|
|
454
|
+
|
|
455
|
+
return async (tree, file) => {
|
|
456
|
+
const currentDir = getCurrentDir(file, sourceRoot, docsDir);
|
|
457
|
+
await transformChildren(tree.children, currentDir, sourceRoot, docsDir, 0);
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
export default remarkImports;
|