@tansuasici/docsync 0.0.1 → 0.0.2
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
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](./LICENSE)
|
|
4
4
|
[](https://www.typescriptlang.org/)
|
|
5
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/@tansuasici/docsync)
|
|
6
6
|
|
|
7
7
|
**Write docs once in GitHub Markdown, publish everywhere.**
|
|
8
8
|
|
|
@@ -90,7 +90,7 @@ export default defineConfig({
|
|
|
90
90
|
{ path: 'docs/**/*.md' },
|
|
91
91
|
{ path: 'CHANGELOG.md', slug: 'changelog', order: 99 },
|
|
92
92
|
],
|
|
93
|
-
target: 'fumadocs',
|
|
93
|
+
target: 'fumadocs', // or 'docusaurus', 'nextra', 'starlight'
|
|
94
94
|
|
|
95
95
|
// Optional
|
|
96
96
|
outDir: '.docsync', // default: '.docsync'
|
|
@@ -127,12 +127,12 @@ export default defineConfig({
|
|
|
127
127
|
|
|
128
128
|
## Supported Targets
|
|
129
129
|
|
|
130
|
-
| Target | Status |
|
|
131
|
-
|
|
132
|
-
| Fumadocs | Full support |
|
|
133
|
-
| Docusaurus |
|
|
134
|
-
| Nextra |
|
|
135
|
-
| Starlight |
|
|
130
|
+
| Target | Status | Alert Syntax | Nav Config |
|
|
131
|
+
|--------|--------|-------------|------------|
|
|
132
|
+
| Fumadocs | Full support | `<Callout>` | `meta.json` |
|
|
133
|
+
| Docusaurus | Full support | `:::note` directive | `_category_.json` |
|
|
134
|
+
| Nextra | Full support | `<Callout>` | `_meta.json` |
|
|
135
|
+
| Starlight | Full support | `:::note` directive | frontmatter `sidebar` |
|
|
136
136
|
|
|
137
137
|
## How It Works
|
|
138
138
|
|
|
@@ -16,7 +16,7 @@ var configSchema = z.object({
|
|
|
16
16
|
/** Source files to include */
|
|
17
17
|
sources: z.array(sourceEntrySchema).min(1),
|
|
18
18
|
/** Target docs framework */
|
|
19
|
-
target: z.enum(["fumadocs"]),
|
|
19
|
+
target: z.enum(["fumadocs", "docusaurus", "nextra", "starlight"]),
|
|
20
20
|
/** Output directory for generated files */
|
|
21
21
|
outDir: z.string().default(".docsync"),
|
|
22
22
|
/** GitHub repository info for external link rewriting */
|
|
@@ -376,9 +376,148 @@ ${content}
|
|
|
376
376
|
}
|
|
377
377
|
};
|
|
378
378
|
|
|
379
|
+
// src/adapters/docusaurus.ts
|
|
380
|
+
import { toString as toString2 } from "mdast-util-to-string";
|
|
381
|
+
var ALERT_TYPE_MAP2 = {
|
|
382
|
+
note: "note",
|
|
383
|
+
tip: "tip",
|
|
384
|
+
important: "info",
|
|
385
|
+
warning: "warning",
|
|
386
|
+
caution: "danger"
|
|
387
|
+
};
|
|
388
|
+
var docusaurusAdapter = {
|
|
389
|
+
name: "docusaurus",
|
|
390
|
+
transformAlert(type, node) {
|
|
391
|
+
const admonitionType = ALERT_TYPE_MAP2[type];
|
|
392
|
+
const content = toString2(node);
|
|
393
|
+
const htmlNode = {
|
|
394
|
+
type: "html",
|
|
395
|
+
value: `:::${admonitionType}
|
|
396
|
+
|
|
397
|
+
${content}
|
|
398
|
+
|
|
399
|
+
:::`
|
|
400
|
+
};
|
|
401
|
+
return htmlNode;
|
|
402
|
+
},
|
|
403
|
+
generateNavConfig(_pages) {
|
|
404
|
+
const category = {
|
|
405
|
+
label: "Documentation",
|
|
406
|
+
position: 1,
|
|
407
|
+
link: {
|
|
408
|
+
type: "generated-index"
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
return {
|
|
412
|
+
filename: "_category_.json",
|
|
413
|
+
content: JSON.stringify(category, null, 2) + "\n"
|
|
414
|
+
};
|
|
415
|
+
},
|
|
416
|
+
generateFrontmatter(page) {
|
|
417
|
+
const fm = {
|
|
418
|
+
title: page.title ?? "Untitled",
|
|
419
|
+
sidebar_position: page.order
|
|
420
|
+
};
|
|
421
|
+
if (page.description) {
|
|
422
|
+
fm.description = page.description;
|
|
423
|
+
}
|
|
424
|
+
if (page.slug === "index") {
|
|
425
|
+
fm.slug = "/";
|
|
426
|
+
}
|
|
427
|
+
return fm;
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
// src/adapters/nextra.ts
|
|
432
|
+
import { toString as toString3 } from "mdast-util-to-string";
|
|
433
|
+
var ALERT_TYPE_MAP3 = {
|
|
434
|
+
note: "info",
|
|
435
|
+
tip: "default",
|
|
436
|
+
important: "info",
|
|
437
|
+
warning: "warning",
|
|
438
|
+
caution: "error"
|
|
439
|
+
};
|
|
440
|
+
var nextraAdapter = {
|
|
441
|
+
name: "nextra",
|
|
442
|
+
transformAlert(type, node) {
|
|
443
|
+
const calloutType = ALERT_TYPE_MAP3[type];
|
|
444
|
+
const content = toString3(node);
|
|
445
|
+
const htmlNode = {
|
|
446
|
+
type: "html",
|
|
447
|
+
value: `<Callout type="${calloutType}">
|
|
448
|
+
${content}
|
|
449
|
+
</Callout>`
|
|
450
|
+
};
|
|
451
|
+
return htmlNode;
|
|
452
|
+
},
|
|
453
|
+
generateNavConfig(pages) {
|
|
454
|
+
const meta = {};
|
|
455
|
+
for (const page of pages) {
|
|
456
|
+
const name = page.slug === "index" ? "index" : page.slug.split("/").pop();
|
|
457
|
+
meta[name] = page.title ?? name;
|
|
458
|
+
}
|
|
459
|
+
return {
|
|
460
|
+
filename: "_meta.json",
|
|
461
|
+
content: JSON.stringify(meta, null, 2) + "\n"
|
|
462
|
+
};
|
|
463
|
+
},
|
|
464
|
+
generateFrontmatter(page) {
|
|
465
|
+
const fm = {
|
|
466
|
+
title: page.title ?? "Untitled"
|
|
467
|
+
};
|
|
468
|
+
if (page.description) {
|
|
469
|
+
fm.description = page.description;
|
|
470
|
+
}
|
|
471
|
+
return fm;
|
|
472
|
+
},
|
|
473
|
+
getImports() {
|
|
474
|
+
return ["import { Callout } from 'nextra/components'"];
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
// src/adapters/starlight.ts
|
|
479
|
+
import { toString as toString4 } from "mdast-util-to-string";
|
|
480
|
+
var ALERT_TYPE_MAP4 = {
|
|
481
|
+
note: "note",
|
|
482
|
+
tip: "tip",
|
|
483
|
+
important: "note",
|
|
484
|
+
warning: "caution",
|
|
485
|
+
caution: "danger"
|
|
486
|
+
};
|
|
487
|
+
var starlightAdapter = {
|
|
488
|
+
name: "starlight",
|
|
489
|
+
transformAlert(type, node) {
|
|
490
|
+
const asideType = ALERT_TYPE_MAP4[type];
|
|
491
|
+
const content = toString4(node);
|
|
492
|
+
const htmlNode = {
|
|
493
|
+
type: "html",
|
|
494
|
+
value: `:::${asideType}
|
|
495
|
+
${content}
|
|
496
|
+
:::`
|
|
497
|
+
};
|
|
498
|
+
return htmlNode;
|
|
499
|
+
},
|
|
500
|
+
generateNavConfig() {
|
|
501
|
+
return null;
|
|
502
|
+
},
|
|
503
|
+
generateFrontmatter(page) {
|
|
504
|
+
const fm = {
|
|
505
|
+
title: page.title ?? "Untitled"
|
|
506
|
+
};
|
|
507
|
+
if (page.description) {
|
|
508
|
+
fm.description = page.description;
|
|
509
|
+
}
|
|
510
|
+
fm.sidebar = { order: page.order };
|
|
511
|
+
return fm;
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
|
|
379
515
|
// src/adapters/index.ts
|
|
380
516
|
var adapters = {
|
|
381
|
-
fumadocs: fumadocsAdapter
|
|
517
|
+
fumadocs: fumadocsAdapter,
|
|
518
|
+
docusaurus: docusaurusAdapter,
|
|
519
|
+
nextra: nextraAdapter,
|
|
520
|
+
starlight: starlightAdapter
|
|
382
521
|
};
|
|
383
522
|
function getAdapter(target) {
|
|
384
523
|
const adapter = adapters[target];
|
|
@@ -443,4 +582,4 @@ export {
|
|
|
443
582
|
resolveSourceFiles,
|
|
444
583
|
buildPipeline
|
|
445
584
|
};
|
|
446
|
-
//# sourceMappingURL=chunk-
|
|
585
|
+
//# sourceMappingURL=chunk-VMKHKCT5.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/config/schema.ts","../src/config/loader.ts","../src/core/source-resolver.ts","../src/core/pipeline.ts","../src/transform/index.ts","../src/transform/gfm-alerts.ts","../src/transform/escape-mdx.ts","../src/transform/rewrite-links.ts","../src/transform/rewrite-images.ts","../src/transform/frontmatter.ts","../src/adapters/fumadocs.ts","../src/adapters/index.ts"],"sourcesContent":["import { z } from 'zod'\n\nconst sourceEntrySchema = z.object({\n /** File path or glob pattern (repo-relative) */\n path: z.string(),\n /** Override slug (for explicit entries only) */\n slug: z.string().optional(),\n /** Override page title */\n title: z.string().optional(),\n /** Override page description */\n description: z.string().optional(),\n /** Sidebar position (lower = higher) */\n order: z.number().optional(),\n})\n\nexport const configSchema = z.object({\n /** Source files to include */\n sources: z.array(sourceEntrySchema).min(1),\n /** Target docs framework */\n target: z.enum(['fumadocs']),\n /** Output directory for generated files */\n outDir: z.string().default('.docsync'),\n /** GitHub repository info for external link rewriting */\n github: z\n .object({\n repo: z.string(),\n branch: z.string().default('main'),\n })\n .optional(),\n /** Base URL path for docs */\n baseUrl: z.string().default('/docs'),\n /** Clean output directory before build */\n clean: z.boolean().default(true),\n})\n\nexport type DocSyncConfig = z.infer<typeof configSchema>\nexport type SourceEntry = z.infer<typeof sourceEntrySchema>\n","import { loadConfig } from 'c12'\nimport { configSchema, type DocSyncConfig } from './schema.js'\n\nexport async function loadDocSyncConfig(\n cwd: string = process.cwd(),\n): Promise<DocSyncConfig> {\n const { config } = await loadConfig<DocSyncConfig>({\n name: 'docsync',\n cwd,\n })\n\n const result = configSchema.safeParse(config)\n\n if (!result.success) {\n const errors = result.error.issues\n .map((i) => ` - ${i.path.join('.')}: ${i.message}`)\n .join('\\n')\n throw new Error(`Invalid docsync config:\\n${errors}`)\n }\n\n return result.data\n}\n","import path from 'node:path'\nimport fs from 'node:fs/promises'\nimport fg from 'fast-glob'\nimport type { SourceEntry } from '../config/schema.js'\n\nexport interface ResolvedPage {\n /** Absolute file path */\n filePath: string\n /** Repo-relative file path */\n relativePath: string\n /** URL slug (no leading slash, no extension) */\n slug: string\n /** Page title (from config or extracted later from content) */\n title?: string\n /** Page description (from config or extracted later from content) */\n description?: string\n /** Sidebar order */\n order: number\n}\n\nexport async function resolveSourceFiles(\n sources: SourceEntry[],\n cwd: string,\n): Promise<ResolvedPage[]> {\n const pages: ResolvedPage[] = []\n let autoOrder = 0\n\n for (const source of sources) {\n const matches = await fg(source.path, {\n cwd,\n onlyFiles: true,\n absolute: false,\n })\n\n if (matches.length === 0) {\n console.warn(`[docsync] No files matched: ${source.path}`)\n continue\n }\n\n // Sort for deterministic ordering\n matches.sort()\n\n for (const match of matches) {\n const filePath = path.resolve(cwd, match)\n\n // Verify file exists and is readable\n await fs.access(filePath, fs.constants.R_OK)\n\n const slug = source.slug ?? deriveSlug(match)\n const order = source.order ?? autoOrder++\n\n pages.push({\n filePath,\n relativePath: match,\n slug,\n title: source.title,\n description: source.description,\n order,\n })\n }\n }\n\n // Deduplicate by slug (first entry wins)\n const seen = new Set<string>()\n const deduped: ResolvedPage[] = []\n for (const page of pages) {\n if (!seen.has(page.slug)) {\n seen.add(page.slug)\n deduped.push(page)\n } else {\n console.warn(\n `[docsync] Duplicate slug \"${page.slug}\" from ${page.relativePath} — skipped`,\n )\n }\n }\n\n // Sort by order\n deduped.sort((a, b) => a.order - b.order)\n\n return deduped\n}\n\n/**\n * Derive a URL slug from a file path.\n *\n * Examples:\n * README.md → index\n * docs/getting-started.md → getting-started\n * docs/guides/setup.md → guides/setup\n */\nfunction deriveSlug(filePath: string): string {\n const parsed = path.parse(filePath)\n const name = parsed.name.toLowerCase()\n\n // README → index\n if (name === 'readme') {\n // If it's in a subdirectory, use the directory path\n if (parsed.dir && parsed.dir !== '.') {\n return stripDocsPrefix(parsed.dir) + '/index'\n }\n return 'index'\n }\n\n const dir = parsed.dir && parsed.dir !== '.' ? stripDocsPrefix(parsed.dir) : ''\n const slug = dir ? `${dir}/${name}` : name\n\n return slug\n}\n\n/**\n * Strip common docs directory prefixes.\n * docs/guides/foo → guides/foo\n */\nfunction stripDocsPrefix(dir: string): string {\n return dir.replace(/^docs\\/?/, '')\n}\n","import fs from 'node:fs/promises'\nimport path from 'node:path'\nimport type { DocSyncConfig } from '../config/schema.js'\nimport { resolveSourceFiles, type ResolvedPage } from './source-resolver.js'\nimport { transformMarkdown } from '../transform/index.js'\nimport { getAdapter } from '../adapters/index.js'\n\nexport interface BuildResult {\n pages: { slug: string; outputPath: string }[]\n errors: { file: string; error: string }[]\n}\n\nexport async function buildPipeline(\n config: DocSyncConfig,\n cwd: string,\n): Promise<BuildResult> {\n const adapter = getAdapter(config.target)\n const pages = await resolveSourceFiles(config.sources, cwd)\n const outDir = path.resolve(cwd, config.outDir)\n\n // Clean output directory if configured\n if (config.clean) {\n await fs.rm(outDir, { recursive: true, force: true })\n }\n await fs.mkdir(outDir, { recursive: true })\n\n const result: BuildResult = { pages: [], errors: [] }\n\n // Build a slug map for link rewriting\n const slugMap = buildSlugMap(pages)\n\n // Transform each page\n for (const page of pages) {\n try {\n const source = await fs.readFile(page.filePath, 'utf-8')\n\n const mdx = await transformMarkdown(source, {\n page,\n slugMap,\n adapter,\n config,\n })\n\n const outputPath = path.join(outDir, `${page.slug}.mdx`)\n await fs.mkdir(path.dirname(outputPath), { recursive: true })\n await fs.writeFile(outputPath, mdx, 'utf-8')\n\n result.pages.push({ slug: page.slug, outputPath })\n } catch (err) {\n result.errors.push({\n file: page.relativePath,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }\n\n // Generate navigation config\n const navConfig = adapter.generateNavConfig(pages)\n if (navConfig) {\n const navPath = path.join(outDir, navConfig.filename)\n await fs.writeFile(navPath, navConfig.content, 'utf-8')\n }\n\n return result\n}\n\n/**\n * Build a map from relative file paths to slugs for link rewriting.\n * Key: repo-relative path (e.g., \"docs/guide.md\")\n * Value: slug (e.g., \"guide\")\n */\nfunction buildSlugMap(pages: ResolvedPage[]): Map<string, string> {\n const map = new Map<string, string>()\n for (const page of pages) {\n map.set(page.relativePath, page.slug)\n // Also map with ./ prefix\n map.set(`./${page.relativePath}`, page.slug)\n }\n return map\n}\n","import { unified } from 'unified'\nimport remarkParse from 'remark-parse'\nimport remarkGfm from 'remark-gfm'\nimport remarkFrontmatter from 'remark-frontmatter'\nimport remarkStringify from 'remark-stringify'\nimport { remarkGfmAlerts } from './gfm-alerts.js'\nimport { remarkEscapeMdx } from './escape-mdx.js'\nimport { remarkRewriteLinks } from './rewrite-links.js'\nimport { remarkRewriteImages } from './rewrite-images.js'\nimport { extractFrontmatter } from './frontmatter.js'\nimport type { ResolvedPage } from '../core/source-resolver.js'\nimport type { DocSyncConfig } from '../config/schema.js'\nimport type { TargetAdapter } from '../adapters/types.js'\n\nexport interface TransformContext {\n page: ResolvedPage\n slugMap: Map<string, string>\n adapter: TargetAdapter\n config: DocSyncConfig\n}\n\nexport async function transformMarkdown(\n source: string,\n ctx: TransformContext,\n): Promise<string> {\n // Extract title and description from content if not set in config\n const { title, description, contentWithoutH1 } = extractFrontmatter(source, ctx.page)\n\n // Generate frontmatter via adapter\n const frontmatter = ctx.adapter.generateFrontmatter({\n ...ctx.page,\n title,\n description,\n })\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const processor = (unified() as any)\n .use(remarkParse)\n .use(remarkGfm)\n .use(remarkFrontmatter)\n .use(remarkGfmAlerts, { adapter: ctx.adapter })\n .use(remarkEscapeMdx)\n .use(remarkRewriteLinks, {\n slugMap: ctx.slugMap,\n baseUrl: ctx.config.baseUrl,\n page: ctx.page,\n })\n .use(remarkRewriteImages, {\n github: ctx.config.github,\n page: ctx.page,\n })\n .use(remarkStringify, {\n bullet: '-',\n emphasis: '*',\n strong: '**',\n rule: '-',\n })\n\n const file = await processor.process(contentWithoutH1)\n const body = String(file)\n\n // Build final MDX output\n const lines: string[] = []\n\n // Frontmatter block\n lines.push('---')\n for (const [key, value] of Object.entries(frontmatter)) {\n if (value !== undefined && value !== null) {\n lines.push(`${key}: ${formatYamlValue(value)}`)\n }\n }\n lines.push('---')\n lines.push('')\n\n // Imports (from adapter)\n const imports = ctx.adapter.getImports?.()\n if (imports && imports.length > 0) {\n lines.push(...imports)\n lines.push('')\n }\n\n // Body\n lines.push(body.trim())\n lines.push('')\n\n return lines.join('\\n')\n}\n\nfunction formatYamlValue(value: unknown): string {\n if (typeof value === 'string') {\n // Quote strings that contain special YAML characters\n if (/[:#{}[\\],&*?|>!%@`]/.test(value) || value.includes('\\n')) {\n return `\"${value.replace(/\"/g, '\\\\\"')}\"`\n }\n return `\"${value}\"`\n }\n if (typeof value === 'number' || typeof value === 'boolean') {\n return String(value)\n }\n return JSON.stringify(value)\n}\n","import { visit } from 'unist-util-visit'\nimport type { Plugin } from 'unified'\nimport type { Blockquote, Root } from 'mdast'\nimport type { TargetAdapter } from '../adapters/types.js'\n\nexport type AlertType = 'note' | 'tip' | 'important' | 'warning' | 'caution'\n\ninterface GfmAlertsOptions {\n adapter: TargetAdapter\n}\n\nconst ALERT_PATTERN = /^\\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\\]\\s*/i\n\n/**\n * Remark plugin that transforms GitHub-style alerts in blockquotes:\n * > [!NOTE]\n * > Content here\n *\n * Into framework-specific callout components via the adapter.\n */\nexport const remarkGfmAlerts: Plugin<[GfmAlertsOptions], Root> = (options) => {\n return (tree) => {\n visit(tree, 'blockquote', (node: Blockquote, index, parent) => {\n if (!parent || index === undefined) return\n\n // Check first child for alert marker\n const firstChild = node.children[0]\n if (!firstChild || firstChild.type !== 'paragraph') return\n\n const firstInline = firstChild.children[0]\n if (!firstInline || firstInline.type !== 'text') return\n\n const match = firstInline.value.match(ALERT_PATTERN)\n if (!match) return\n\n const alertType = match[1].toLowerCase() as AlertType\n\n // Remove the alert marker from the text\n firstInline.value = firstInline.value.replace(ALERT_PATTERN, '')\n\n // If the first text node is now empty, remove it\n if (firstInline.value.trim() === '') {\n firstChild.children.shift()\n }\n\n // If the first paragraph is now empty, remove it\n if (firstChild.children.length === 0) {\n node.children.shift()\n }\n\n // Let the adapter transform the alert\n const replacement = options.adapter.transformAlert(alertType, node)\n if (replacement) {\n parent.children[index] = replacement\n }\n })\n }\n}\n","import { visit } from 'unist-util-visit'\nimport type { Plugin } from 'unified'\nimport type { Root, Text, Html } from 'mdast'\n\n/**\n * Remark plugin that escapes MDX-breaking syntax in text nodes:\n * - { } → \\{ \\}\n * - < > in text (not HTML tags) → \\< \\>\n * - <!-- --> HTML comments → {/* * /} JSX comments\n */\nexport const remarkEscapeMdx: Plugin<[], Root> = () => {\n return (tree) => {\n // Escape curly braces and angle brackets in text nodes\n visit(tree, 'text', (node: Text) => {\n // Escape curly braces\n node.value = node.value.replace(/([{}])/g, '\\\\$1')\n\n // Escape standalone angle brackets (not part of HTML tags)\n // Only escape < that isn't followed by a valid tag name or /\n node.value = node.value.replace(/<(?![a-zA-Z/!])/g, '\\\\<')\n node.value = node.value.replace(/(?<![a-zA-Z\"'/])>/g, '\\\\>')\n })\n\n // Convert HTML comments to JSX comments\n visit(tree, 'html', (node: Html, index, parent) => {\n if (!parent || index === undefined) return\n\n const commentMatch = node.value.match(/^<!--\\s*([\\s\\S]*?)\\s*-->$/)\n if (commentMatch) {\n node.value = `{/* ${commentMatch[1]} */}`\n }\n\n // Self-close void elements\n node.value = node.value.replace(\n /<(br|hr|img|input|meta|link)(\\s[^>]*)?\\s*>/gi,\n '<$1$2 />',\n )\n })\n }\n}\n","import path from 'node:path'\nimport { visit } from 'unist-util-visit'\nimport type { Plugin } from 'unified'\nimport type { Root, Link } from 'mdast'\nimport type { ResolvedPage } from '../core/source-resolver.js'\n\ninterface RewriteLinksOptions {\n slugMap: Map<string, string>\n baseUrl: string\n page: ResolvedPage\n}\n\n/**\n * Remark plugin that rewrites relative markdown links to docs-site URLs.\n *\n * Examples:\n * ./docs/guide.md → /docs/guide (if in source map)\n * ../README.md → /docs/index (if in source map)\n * ./src/index.ts → https://github.com/user/repo/blob/main/src/index.ts (if not in source map)\n * #section → #section (pass-through)\n * https://example.com → https://example.com (pass-through)\n */\nexport const remarkRewriteLinks: Plugin<[RewriteLinksOptions], Root> = (options) => {\n return (tree) => {\n visit(tree, 'link', (node: Link) => {\n const url = node.url\n\n // Skip external links and anchors\n if (url.startsWith('http://') || url.startsWith('https://') || url.startsWith('#')) {\n return\n }\n\n // Split URL and anchor\n const [urlPath, anchor] = url.split('#')\n\n // Resolve the relative path against the current file's directory\n const currentDir = path.dirname(options.page.relativePath)\n const resolvedPath = path.normalize(path.join(currentDir, urlPath))\n\n // Check if this file is in our source map\n const slug = options.slugMap.get(resolvedPath) ?? options.slugMap.get(`./${resolvedPath}`)\n\n if (slug !== undefined) {\n // Rewrite to docs-site URL\n const base = options.baseUrl.replace(/\\/$/, '')\n node.url = slug === 'index' ? base : `${base}/${slug}`\n if (anchor) {\n node.url += `#${anchor}`\n }\n }\n // If not in source map, leave as-is (or could rewrite to GitHub URL)\n })\n }\n}\n","import path from 'node:path'\nimport { visit } from 'unist-util-visit'\nimport type { Plugin } from 'unified'\nimport type { Root, Image } from 'mdast'\nimport type { ResolvedPage } from '../core/source-resolver.js'\n\ninterface RewriteImagesOptions {\n github?: { repo: string; branch: string }\n page: ResolvedPage\n}\n\n/**\n * Remark plugin that rewrites relative image paths to GitHub raw URLs.\n *\n * Examples:\n * ./assets/logo.png → https://raw.githubusercontent.com/user/repo/main/assets/logo.png\n * https://example.com/img.png → https://example.com/img.png (pass-through)\n */\nexport const remarkRewriteImages: Plugin<[RewriteImagesOptions], Root> = (options) => {\n return (tree) => {\n visit(tree, 'image', (node: Image) => {\n const url = node.url\n\n // Skip absolute URLs\n if (url.startsWith('http://') || url.startsWith('https://') || url.startsWith('data:')) {\n return\n }\n\n // If no GitHub config, leave relative paths as-is\n if (!options.github) return\n\n // Resolve relative path against current file's directory\n const currentDir = path.dirname(options.page.relativePath)\n const resolvedPath = path.normalize(path.join(currentDir, url))\n\n const { repo, branch } = options.github\n node.url = `https://raw.githubusercontent.com/${repo}/${branch}/${resolvedPath}`\n })\n }\n}\n","import type { ResolvedPage } from '../core/source-resolver.js'\n\ninterface ExtractedFrontmatter {\n title: string\n description?: string\n /** Content with the first H1 heading removed (since it becomes the title) */\n contentWithoutH1: string\n}\n\n/**\n * Extract title and description from markdown content.\n *\n * - Title: first # heading, or config override, or filename\n * - Description: first paragraph after the heading\n * - Strips the first H1 from content (it becomes frontmatter title)\n */\nexport function extractFrontmatter(\n source: string,\n page: ResolvedPage,\n): ExtractedFrontmatter {\n const lines = source.split('\\n')\n\n let title = page.title\n let description = page.description\n let h1LineIndex = -1\n\n // Find first H1\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i].trim()\n\n // Skip empty lines and frontmatter\n if (line === '' || line === '---') continue\n // Stop if we hit frontmatter block\n if (i === 0 && line === '---') {\n const endIndex = lines.indexOf('---', 1)\n if (endIndex !== -1) {\n // Skip existing frontmatter — we'll generate our own\n // TODO: merge existing frontmatter in future version\n continue\n }\n }\n\n const h1Match = line.match(/^#\\s+(.+)$/)\n if (h1Match) {\n if (!title) {\n title = h1Match[1].trim()\n }\n h1LineIndex = i\n break\n }\n }\n\n // Fallback title from filename\n if (!title) {\n title = page.slug === 'index' ? 'Introduction' : slugToTitle(page.slug)\n }\n\n // Extract description from first paragraph after H1\n if (!description && h1LineIndex !== -1) {\n for (let i = h1LineIndex + 1; i < lines.length; i++) {\n const line = lines[i].trim()\n if (line === '') continue\n // Stop at headings, code blocks, lists, etc.\n if (line.startsWith('#') || line.startsWith('```') || line.startsWith('-') || line.startsWith('>')) {\n break\n }\n description = line\n break\n }\n }\n\n // Remove the first H1 line from content\n let contentWithoutH1 = source\n if (h1LineIndex !== -1) {\n const newLines = [...lines]\n newLines.splice(h1LineIndex, 1)\n // Also remove trailing empty line after H1 if present\n if (newLines[h1LineIndex]?.trim() === '') {\n newLines.splice(h1LineIndex, 1)\n }\n contentWithoutH1 = newLines.join('\\n')\n }\n\n return { title, description, contentWithoutH1 }\n}\n\nfunction slugToTitle(slug: string): string {\n const name = slug.split('/').pop() ?? slug\n return name\n .replace(/[-_]/g, ' ')\n .replace(/\\b\\w/g, (c) => c.toUpperCase())\n}\n","import type { Blockquote, RootContent, Html } from 'mdast'\nimport type { AlertType } from '../transform/gfm-alerts.js'\nimport type { ResolvedPage } from '../core/source-resolver.js'\nimport type { TargetAdapter, NavConfigOutput } from './types.js'\nimport { toString } from 'mdast-util-to-string'\n\n/**\n * Alert type mapping: GFM → Fumadocs Callout type\n *\n * Fumadocs Callout types: info, warn, error\n * GFM alert types: note, tip, important, warning, caution\n */\nconst ALERT_TYPE_MAP: Record<AlertType, string> = {\n note: 'info',\n tip: 'info',\n important: 'info',\n warning: 'warn',\n caution: 'error',\n}\n\nexport const fumadocsAdapter: TargetAdapter = {\n name: 'fumadocs',\n\n transformAlert(type: AlertType, node: Blockquote): RootContent {\n const calloutType = ALERT_TYPE_MAP[type]\n const content = toString(node)\n\n // Generate an HTML node with Callout JSX\n const htmlNode: Html = {\n type: 'html',\n value: `<Callout type=\"${calloutType}\">\\n${content}\\n</Callout>`,\n }\n\n return htmlNode\n },\n\n generateNavConfig(pages: ResolvedPage[]): NavConfigOutput {\n const pageNames = pages.map((p) => {\n // meta.json uses filenames without extension\n const parts = p.slug.split('/')\n return parts[parts.length - 1]\n })\n\n const meta = {\n title: 'Documentation',\n pages: pageNames,\n }\n\n return {\n filename: 'meta.json',\n content: JSON.stringify(meta, null, 2) + '\\n',\n }\n },\n\n generateFrontmatter(page: ResolvedPage): Record<string, unknown> {\n const fm: Record<string, unknown> = {\n title: page.title ?? 'Untitled',\n }\n\n if (page.description) {\n fm.description = page.description\n }\n\n return fm\n },\n\n getImports(): string[] {\n return [\"import { Callout } from 'fumadocs-ui/components/callout'\"]\n },\n}\n","import type { TargetAdapter } from './types.js'\nimport { fumadocsAdapter } from './fumadocs.js'\n\nconst adapters: Record<string, TargetAdapter> = {\n fumadocs: fumadocsAdapter,\n}\n\nexport function getAdapter(target: string): TargetAdapter {\n const adapter = adapters[target]\n if (!adapter) {\n throw new Error(\n `Unknown target \"${target}\". Available targets: ${Object.keys(adapters).join(', ')}`,\n )\n }\n return adapter\n}\n\nexport type { TargetAdapter, NavConfigOutput } from './types.js'\n"],"mappings":"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","names":["fs","path","visit","path","visit","path","visit","path","fs"]}
|
|
1
|
+
{"version":3,"sources":["../src/config/schema.ts","../src/config/loader.ts","../src/core/source-resolver.ts","../src/core/pipeline.ts","../src/transform/index.ts","../src/transform/gfm-alerts.ts","../src/transform/escape-mdx.ts","../src/transform/rewrite-links.ts","../src/transform/rewrite-images.ts","../src/transform/frontmatter.ts","../src/adapters/fumadocs.ts","../src/adapters/docusaurus.ts","../src/adapters/nextra.ts","../src/adapters/starlight.ts","../src/adapters/index.ts"],"sourcesContent":["import { z } from 'zod'\n\nconst sourceEntrySchema = z.object({\n /** File path or glob pattern (repo-relative) */\n path: z.string(),\n /** Override slug (for explicit entries only) */\n slug: z.string().optional(),\n /** Override page title */\n title: z.string().optional(),\n /** Override page description */\n description: z.string().optional(),\n /** Sidebar position (lower = higher) */\n order: z.number().optional(),\n})\n\nexport const configSchema = z.object({\n /** Source files to include */\n sources: z.array(sourceEntrySchema).min(1),\n /** Target docs framework */\n target: z.enum(['fumadocs', 'docusaurus', 'nextra', 'starlight']),\n /** Output directory for generated files */\n outDir: z.string().default('.docsync'),\n /** GitHub repository info for external link rewriting */\n github: z\n .object({\n repo: z.string(),\n branch: z.string().default('main'),\n })\n .optional(),\n /** Base URL path for docs */\n baseUrl: z.string().default('/docs'),\n /** Clean output directory before build */\n clean: z.boolean().default(true),\n})\n\nexport type DocSyncConfig = z.infer<typeof configSchema>\nexport type SourceEntry = z.infer<typeof sourceEntrySchema>\n","import { loadConfig } from 'c12'\nimport { configSchema, type DocSyncConfig } from './schema.js'\n\nexport async function loadDocSyncConfig(\n cwd: string = process.cwd(),\n): Promise<DocSyncConfig> {\n const { config } = await loadConfig<DocSyncConfig>({\n name: 'docsync',\n cwd,\n })\n\n const result = configSchema.safeParse(config)\n\n if (!result.success) {\n const errors = result.error.issues\n .map((i) => ` - ${i.path.join('.')}: ${i.message}`)\n .join('\\n')\n throw new Error(`Invalid docsync config:\\n${errors}`)\n }\n\n return result.data\n}\n","import path from 'node:path'\nimport fs from 'node:fs/promises'\nimport fg from 'fast-glob'\nimport type { SourceEntry } from '../config/schema.js'\n\nexport interface ResolvedPage {\n /** Absolute file path */\n filePath: string\n /** Repo-relative file path */\n relativePath: string\n /** URL slug (no leading slash, no extension) */\n slug: string\n /** Page title (from config or extracted later from content) */\n title?: string\n /** Page description (from config or extracted later from content) */\n description?: string\n /** Sidebar order */\n order: number\n}\n\nexport async function resolveSourceFiles(\n sources: SourceEntry[],\n cwd: string,\n): Promise<ResolvedPage[]> {\n const pages: ResolvedPage[] = []\n let autoOrder = 0\n\n for (const source of sources) {\n const matches = await fg(source.path, {\n cwd,\n onlyFiles: true,\n absolute: false,\n })\n\n if (matches.length === 0) {\n console.warn(`[docsync] No files matched: ${source.path}`)\n continue\n }\n\n // Sort for deterministic ordering\n matches.sort()\n\n for (const match of matches) {\n const filePath = path.resolve(cwd, match)\n\n // Verify file exists and is readable\n await fs.access(filePath, fs.constants.R_OK)\n\n const slug = source.slug ?? deriveSlug(match)\n const order = source.order ?? autoOrder++\n\n pages.push({\n filePath,\n relativePath: match,\n slug,\n title: source.title,\n description: source.description,\n order,\n })\n }\n }\n\n // Deduplicate by slug (first entry wins)\n const seen = new Set<string>()\n const deduped: ResolvedPage[] = []\n for (const page of pages) {\n if (!seen.has(page.slug)) {\n seen.add(page.slug)\n deduped.push(page)\n } else {\n console.warn(\n `[docsync] Duplicate slug \"${page.slug}\" from ${page.relativePath} — skipped`,\n )\n }\n }\n\n // Sort by order\n deduped.sort((a, b) => a.order - b.order)\n\n return deduped\n}\n\n/**\n * Derive a URL slug from a file path.\n *\n * Examples:\n * README.md → index\n * docs/getting-started.md → getting-started\n * docs/guides/setup.md → guides/setup\n */\nfunction deriveSlug(filePath: string): string {\n const parsed = path.parse(filePath)\n const name = parsed.name.toLowerCase()\n\n // README → index\n if (name === 'readme') {\n // If it's in a subdirectory, use the directory path\n if (parsed.dir && parsed.dir !== '.') {\n return stripDocsPrefix(parsed.dir) + '/index'\n }\n return 'index'\n }\n\n const dir = parsed.dir && parsed.dir !== '.' ? stripDocsPrefix(parsed.dir) : ''\n const slug = dir ? `${dir}/${name}` : name\n\n return slug\n}\n\n/**\n * Strip common docs directory prefixes.\n * docs/guides/foo → guides/foo\n */\nfunction stripDocsPrefix(dir: string): string {\n return dir.replace(/^docs\\/?/, '')\n}\n","import fs from 'node:fs/promises'\nimport path from 'node:path'\nimport type { DocSyncConfig } from '../config/schema.js'\nimport { resolveSourceFiles, type ResolvedPage } from './source-resolver.js'\nimport { transformMarkdown } from '../transform/index.js'\nimport { getAdapter } from '../adapters/index.js'\n\nexport interface BuildResult {\n pages: { slug: string; outputPath: string }[]\n errors: { file: string; error: string }[]\n}\n\nexport async function buildPipeline(\n config: DocSyncConfig,\n cwd: string,\n): Promise<BuildResult> {\n const adapter = getAdapter(config.target)\n const pages = await resolveSourceFiles(config.sources, cwd)\n const outDir = path.resolve(cwd, config.outDir)\n\n // Clean output directory if configured\n if (config.clean) {\n await fs.rm(outDir, { recursive: true, force: true })\n }\n await fs.mkdir(outDir, { recursive: true })\n\n const result: BuildResult = { pages: [], errors: [] }\n\n // Build a slug map for link rewriting\n const slugMap = buildSlugMap(pages)\n\n // Transform each page\n for (const page of pages) {\n try {\n const source = await fs.readFile(page.filePath, 'utf-8')\n\n const mdx = await transformMarkdown(source, {\n page,\n slugMap,\n adapter,\n config,\n })\n\n const outputPath = path.join(outDir, `${page.slug}.mdx`)\n await fs.mkdir(path.dirname(outputPath), { recursive: true })\n await fs.writeFile(outputPath, mdx, 'utf-8')\n\n result.pages.push({ slug: page.slug, outputPath })\n } catch (err) {\n result.errors.push({\n file: page.relativePath,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }\n\n // Generate navigation config\n const navConfig = adapter.generateNavConfig(pages)\n if (navConfig) {\n const navPath = path.join(outDir, navConfig.filename)\n await fs.writeFile(navPath, navConfig.content, 'utf-8')\n }\n\n return result\n}\n\n/**\n * Build a map from relative file paths to slugs for link rewriting.\n * Key: repo-relative path (e.g., \"docs/guide.md\")\n * Value: slug (e.g., \"guide\")\n */\nfunction buildSlugMap(pages: ResolvedPage[]): Map<string, string> {\n const map = new Map<string, string>()\n for (const page of pages) {\n map.set(page.relativePath, page.slug)\n // Also map with ./ prefix\n map.set(`./${page.relativePath}`, page.slug)\n }\n return map\n}\n","import { unified } from 'unified'\nimport remarkParse from 'remark-parse'\nimport remarkGfm from 'remark-gfm'\nimport remarkFrontmatter from 'remark-frontmatter'\nimport remarkStringify from 'remark-stringify'\nimport { remarkGfmAlerts } from './gfm-alerts.js'\nimport { remarkEscapeMdx } from './escape-mdx.js'\nimport { remarkRewriteLinks } from './rewrite-links.js'\nimport { remarkRewriteImages } from './rewrite-images.js'\nimport { extractFrontmatter } from './frontmatter.js'\nimport type { ResolvedPage } from '../core/source-resolver.js'\nimport type { DocSyncConfig } from '../config/schema.js'\nimport type { TargetAdapter } from '../adapters/types.js'\n\nexport interface TransformContext {\n page: ResolvedPage\n slugMap: Map<string, string>\n adapter: TargetAdapter\n config: DocSyncConfig\n}\n\nexport async function transformMarkdown(\n source: string,\n ctx: TransformContext,\n): Promise<string> {\n // Extract title and description from content if not set in config\n const { title, description, contentWithoutH1 } = extractFrontmatter(source, ctx.page)\n\n // Generate frontmatter via adapter\n const frontmatter = ctx.adapter.generateFrontmatter({\n ...ctx.page,\n title,\n description,\n })\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const processor = (unified() as any)\n .use(remarkParse)\n .use(remarkGfm)\n .use(remarkFrontmatter)\n .use(remarkGfmAlerts, { adapter: ctx.adapter })\n .use(remarkEscapeMdx)\n .use(remarkRewriteLinks, {\n slugMap: ctx.slugMap,\n baseUrl: ctx.config.baseUrl,\n page: ctx.page,\n })\n .use(remarkRewriteImages, {\n github: ctx.config.github,\n page: ctx.page,\n })\n .use(remarkStringify, {\n bullet: '-',\n emphasis: '*',\n strong: '**',\n rule: '-',\n })\n\n const file = await processor.process(contentWithoutH1)\n const body = String(file)\n\n // Build final MDX output\n const lines: string[] = []\n\n // Frontmatter block\n lines.push('---')\n for (const [key, value] of Object.entries(frontmatter)) {\n if (value !== undefined && value !== null) {\n lines.push(`${key}: ${formatYamlValue(value)}`)\n }\n }\n lines.push('---')\n lines.push('')\n\n // Imports (from adapter)\n const imports = ctx.adapter.getImports?.()\n if (imports && imports.length > 0) {\n lines.push(...imports)\n lines.push('')\n }\n\n // Body\n lines.push(body.trim())\n lines.push('')\n\n return lines.join('\\n')\n}\n\nfunction formatYamlValue(value: unknown): string {\n if (typeof value === 'string') {\n // Quote strings that contain special YAML characters\n if (/[:#{}[\\],&*?|>!%@`]/.test(value) || value.includes('\\n')) {\n return `\"${value.replace(/\"/g, '\\\\\"')}\"`\n }\n return `\"${value}\"`\n }\n if (typeof value === 'number' || typeof value === 'boolean') {\n return String(value)\n }\n return JSON.stringify(value)\n}\n","import { visit } from 'unist-util-visit'\nimport type { Plugin } from 'unified'\nimport type { Blockquote, Root } from 'mdast'\nimport type { TargetAdapter } from '../adapters/types.js'\n\nexport type AlertType = 'note' | 'tip' | 'important' | 'warning' | 'caution'\n\ninterface GfmAlertsOptions {\n adapter: TargetAdapter\n}\n\nconst ALERT_PATTERN = /^\\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\\]\\s*/i\n\n/**\n * Remark plugin that transforms GitHub-style alerts in blockquotes:\n * > [!NOTE]\n * > Content here\n *\n * Into framework-specific callout components via the adapter.\n */\nexport const remarkGfmAlerts: Plugin<[GfmAlertsOptions], Root> = (options) => {\n return (tree) => {\n visit(tree, 'blockquote', (node: Blockquote, index, parent) => {\n if (!parent || index === undefined) return\n\n // Check first child for alert marker\n const firstChild = node.children[0]\n if (!firstChild || firstChild.type !== 'paragraph') return\n\n const firstInline = firstChild.children[0]\n if (!firstInline || firstInline.type !== 'text') return\n\n const match = firstInline.value.match(ALERT_PATTERN)\n if (!match) return\n\n const alertType = match[1].toLowerCase() as AlertType\n\n // Remove the alert marker from the text\n firstInline.value = firstInline.value.replace(ALERT_PATTERN, '')\n\n // If the first text node is now empty, remove it\n if (firstInline.value.trim() === '') {\n firstChild.children.shift()\n }\n\n // If the first paragraph is now empty, remove it\n if (firstChild.children.length === 0) {\n node.children.shift()\n }\n\n // Let the adapter transform the alert\n const replacement = options.adapter.transformAlert(alertType, node)\n if (replacement) {\n parent.children[index] = replacement\n }\n })\n }\n}\n","import { visit } from 'unist-util-visit'\nimport type { Plugin } from 'unified'\nimport type { Root, Text, Html } from 'mdast'\n\n/**\n * Remark plugin that escapes MDX-breaking syntax in text nodes:\n * - { } → \\{ \\}\n * - < > in text (not HTML tags) → \\< \\>\n * - <!-- --> HTML comments → {/* * /} JSX comments\n */\nexport const remarkEscapeMdx: Plugin<[], Root> = () => {\n return (tree) => {\n // Escape curly braces and angle brackets in text nodes\n visit(tree, 'text', (node: Text) => {\n // Escape curly braces\n node.value = node.value.replace(/([{}])/g, '\\\\$1')\n\n // Escape standalone angle brackets (not part of HTML tags)\n // Only escape < that isn't followed by a valid tag name or /\n node.value = node.value.replace(/<(?![a-zA-Z/!])/g, '\\\\<')\n node.value = node.value.replace(/(?<![a-zA-Z\"'/])>/g, '\\\\>')\n })\n\n // Convert HTML comments to JSX comments\n visit(tree, 'html', (node: Html, index, parent) => {\n if (!parent || index === undefined) return\n\n const commentMatch = node.value.match(/^<!--\\s*([\\s\\S]*?)\\s*-->$/)\n if (commentMatch) {\n node.value = `{/* ${commentMatch[1]} */}`\n }\n\n // Self-close void elements\n node.value = node.value.replace(\n /<(br|hr|img|input|meta|link)(\\s[^>]*)?\\s*>/gi,\n '<$1$2 />',\n )\n })\n }\n}\n","import path from 'node:path'\nimport { visit } from 'unist-util-visit'\nimport type { Plugin } from 'unified'\nimport type { Root, Link } from 'mdast'\nimport type { ResolvedPage } from '../core/source-resolver.js'\n\ninterface RewriteLinksOptions {\n slugMap: Map<string, string>\n baseUrl: string\n page: ResolvedPage\n}\n\n/**\n * Remark plugin that rewrites relative markdown links to docs-site URLs.\n *\n * Examples:\n * ./docs/guide.md → /docs/guide (if in source map)\n * ../README.md → /docs/index (if in source map)\n * ./src/index.ts → https://github.com/user/repo/blob/main/src/index.ts (if not in source map)\n * #section → #section (pass-through)\n * https://example.com → https://example.com (pass-through)\n */\nexport const remarkRewriteLinks: Plugin<[RewriteLinksOptions], Root> = (options) => {\n return (tree) => {\n visit(tree, 'link', (node: Link) => {\n const url = node.url\n\n // Skip external links and anchors\n if (url.startsWith('http://') || url.startsWith('https://') || url.startsWith('#')) {\n return\n }\n\n // Split URL and anchor\n const [urlPath, anchor] = url.split('#')\n\n // Resolve the relative path against the current file's directory\n const currentDir = path.dirname(options.page.relativePath)\n const resolvedPath = path.normalize(path.join(currentDir, urlPath))\n\n // Check if this file is in our source map\n const slug = options.slugMap.get(resolvedPath) ?? options.slugMap.get(`./${resolvedPath}`)\n\n if (slug !== undefined) {\n // Rewrite to docs-site URL\n const base = options.baseUrl.replace(/\\/$/, '')\n node.url = slug === 'index' ? base : `${base}/${slug}`\n if (anchor) {\n node.url += `#${anchor}`\n }\n }\n // If not in source map, leave as-is (or could rewrite to GitHub URL)\n })\n }\n}\n","import path from 'node:path'\nimport { visit } from 'unist-util-visit'\nimport type { Plugin } from 'unified'\nimport type { Root, Image } from 'mdast'\nimport type { ResolvedPage } from '../core/source-resolver.js'\n\ninterface RewriteImagesOptions {\n github?: { repo: string; branch: string }\n page: ResolvedPage\n}\n\n/**\n * Remark plugin that rewrites relative image paths to GitHub raw URLs.\n *\n * Examples:\n * ./assets/logo.png → https://raw.githubusercontent.com/user/repo/main/assets/logo.png\n * https://example.com/img.png → https://example.com/img.png (pass-through)\n */\nexport const remarkRewriteImages: Plugin<[RewriteImagesOptions], Root> = (options) => {\n return (tree) => {\n visit(tree, 'image', (node: Image) => {\n const url = node.url\n\n // Skip absolute URLs\n if (url.startsWith('http://') || url.startsWith('https://') || url.startsWith('data:')) {\n return\n }\n\n // If no GitHub config, leave relative paths as-is\n if (!options.github) return\n\n // Resolve relative path against current file's directory\n const currentDir = path.dirname(options.page.relativePath)\n const resolvedPath = path.normalize(path.join(currentDir, url))\n\n const { repo, branch } = options.github\n node.url = `https://raw.githubusercontent.com/${repo}/${branch}/${resolvedPath}`\n })\n }\n}\n","import type { ResolvedPage } from '../core/source-resolver.js'\n\ninterface ExtractedFrontmatter {\n title: string\n description?: string\n /** Content with the first H1 heading removed (since it becomes the title) */\n contentWithoutH1: string\n}\n\n/**\n * Extract title and description from markdown content.\n *\n * - Title: first # heading, or config override, or filename\n * - Description: first paragraph after the heading\n * - Strips the first H1 from content (it becomes frontmatter title)\n */\nexport function extractFrontmatter(\n source: string,\n page: ResolvedPage,\n): ExtractedFrontmatter {\n const lines = source.split('\\n')\n\n let title = page.title\n let description = page.description\n let h1LineIndex = -1\n\n // Find first H1\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i].trim()\n\n // Skip empty lines and frontmatter\n if (line === '' || line === '---') continue\n // Stop if we hit frontmatter block\n if (i === 0 && line === '---') {\n const endIndex = lines.indexOf('---', 1)\n if (endIndex !== -1) {\n // Skip existing frontmatter — we'll generate our own\n // TODO: merge existing frontmatter in future version\n continue\n }\n }\n\n const h1Match = line.match(/^#\\s+(.+)$/)\n if (h1Match) {\n if (!title) {\n title = h1Match[1].trim()\n }\n h1LineIndex = i\n break\n }\n }\n\n // Fallback title from filename\n if (!title) {\n title = page.slug === 'index' ? 'Introduction' : slugToTitle(page.slug)\n }\n\n // Extract description from first paragraph after H1\n if (!description && h1LineIndex !== -1) {\n for (let i = h1LineIndex + 1; i < lines.length; i++) {\n const line = lines[i].trim()\n if (line === '') continue\n // Stop at headings, code blocks, lists, etc.\n if (line.startsWith('#') || line.startsWith('```') || line.startsWith('-') || line.startsWith('>')) {\n break\n }\n description = line\n break\n }\n }\n\n // Remove the first H1 line from content\n let contentWithoutH1 = source\n if (h1LineIndex !== -1) {\n const newLines = [...lines]\n newLines.splice(h1LineIndex, 1)\n // Also remove trailing empty line after H1 if present\n if (newLines[h1LineIndex]?.trim() === '') {\n newLines.splice(h1LineIndex, 1)\n }\n contentWithoutH1 = newLines.join('\\n')\n }\n\n return { title, description, contentWithoutH1 }\n}\n\nfunction slugToTitle(slug: string): string {\n const name = slug.split('/').pop() ?? slug\n return name\n .replace(/[-_]/g, ' ')\n .replace(/\\b\\w/g, (c) => c.toUpperCase())\n}\n","import type { Blockquote, RootContent, Html } from 'mdast'\nimport type { AlertType } from '../transform/gfm-alerts.js'\nimport type { ResolvedPage } from '../core/source-resolver.js'\nimport type { TargetAdapter, NavConfigOutput } from './types.js'\nimport { toString } from 'mdast-util-to-string'\n\n/**\n * Alert type mapping: GFM → Fumadocs Callout type\n *\n * Fumadocs Callout types: info, warn, error\n * GFM alert types: note, tip, important, warning, caution\n */\nconst ALERT_TYPE_MAP: Record<AlertType, string> = {\n note: 'info',\n tip: 'info',\n important: 'info',\n warning: 'warn',\n caution: 'error',\n}\n\nexport const fumadocsAdapter: TargetAdapter = {\n name: 'fumadocs',\n\n transformAlert(type: AlertType, node: Blockquote): RootContent {\n const calloutType = ALERT_TYPE_MAP[type]\n const content = toString(node)\n\n // Generate an HTML node with Callout JSX\n const htmlNode: Html = {\n type: 'html',\n value: `<Callout type=\"${calloutType}\">\\n${content}\\n</Callout>`,\n }\n\n return htmlNode\n },\n\n generateNavConfig(pages: ResolvedPage[]): NavConfigOutput {\n const pageNames = pages.map((p) => {\n // meta.json uses filenames without extension\n const parts = p.slug.split('/')\n return parts[parts.length - 1]\n })\n\n const meta = {\n title: 'Documentation',\n pages: pageNames,\n }\n\n return {\n filename: 'meta.json',\n content: JSON.stringify(meta, null, 2) + '\\n',\n }\n },\n\n generateFrontmatter(page: ResolvedPage): Record<string, unknown> {\n const fm: Record<string, unknown> = {\n title: page.title ?? 'Untitled',\n }\n\n if (page.description) {\n fm.description = page.description\n }\n\n return fm\n },\n\n getImports(): string[] {\n return [\"import { Callout } from 'fumadocs-ui/components/callout'\"]\n },\n}\n","import type { Blockquote, RootContent, Html } from 'mdast'\nimport type { AlertType } from '../transform/gfm-alerts.js'\nimport type { ResolvedPage } from '../core/source-resolver.js'\nimport type { TargetAdapter, NavConfigOutput } from './types.js'\nimport { toString } from 'mdast-util-to-string'\n\n/**\n * Alert type mapping: GFM → Docusaurus admonition type\n *\n * Docusaurus types: note, tip, info, warning, danger\n */\nconst ALERT_TYPE_MAP: Record<AlertType, string> = {\n note: 'note',\n tip: 'tip',\n important: 'info',\n warning: 'warning',\n caution: 'danger',\n}\n\nexport const docusaurusAdapter: TargetAdapter = {\n name: 'docusaurus',\n\n transformAlert(type: AlertType, node: Blockquote): RootContent {\n const admonitionType = ALERT_TYPE_MAP[type]\n const content = toString(node)\n\n // Docusaurus uses ::: directive syntax\n const htmlNode: Html = {\n type: 'html',\n value: `:::${admonitionType}\\n\\n${content}\\n\\n:::`,\n }\n\n return htmlNode\n },\n\n generateNavConfig(_pages: ResolvedPage[]): NavConfigOutput {\n const category = {\n label: 'Documentation',\n position: 1,\n link: {\n type: 'generated-index',\n },\n }\n\n return {\n filename: '_category_.json',\n content: JSON.stringify(category, null, 2) + '\\n',\n }\n },\n\n generateFrontmatter(page: ResolvedPage): Record<string, unknown> {\n const fm: Record<string, unknown> = {\n title: page.title ?? 'Untitled',\n sidebar_position: page.order,\n }\n\n if (page.description) {\n fm.description = page.description\n }\n\n if (page.slug === 'index') {\n fm.slug = '/'\n }\n\n return fm\n },\n}\n","import type { Blockquote, RootContent, Html } from 'mdast'\nimport type { AlertType } from '../transform/gfm-alerts.js'\nimport type { ResolvedPage } from '../core/source-resolver.js'\nimport type { TargetAdapter, NavConfigOutput } from './types.js'\nimport { toString } from 'mdast-util-to-string'\n\n/**\n * Alert type mapping: GFM → Nextra Callout type\n *\n * Nextra Callout types: info, warning, error, default\n */\nconst ALERT_TYPE_MAP: Record<AlertType, string> = {\n note: 'info',\n tip: 'default',\n important: 'info',\n warning: 'warning',\n caution: 'error',\n}\n\nexport const nextraAdapter: TargetAdapter = {\n name: 'nextra',\n\n transformAlert(type: AlertType, node: Blockquote): RootContent {\n const calloutType = ALERT_TYPE_MAP[type]\n const content = toString(node)\n\n const htmlNode: Html = {\n type: 'html',\n value: `<Callout type=\"${calloutType}\">\\n${content}\\n</Callout>`,\n }\n\n return htmlNode\n },\n\n generateNavConfig(pages: ResolvedPage[]): NavConfigOutput {\n // Nextra uses _meta.json with page-name: title/config pairs\n const meta: Record<string, string | { title: string }> = {}\n\n for (const page of pages) {\n const name = page.slug === 'index' ? 'index' : page.slug.split('/').pop()!\n meta[name] = page.title ?? name\n }\n\n return {\n filename: '_meta.json',\n content: JSON.stringify(meta, null, 2) + '\\n',\n }\n },\n\n generateFrontmatter(page: ResolvedPage): Record<string, unknown> {\n const fm: Record<string, unknown> = {\n title: page.title ?? 'Untitled',\n }\n\n if (page.description) {\n fm.description = page.description\n }\n\n return fm\n },\n\n getImports(): string[] {\n return [\"import { Callout } from 'nextra/components'\"]\n },\n}\n","import type { Blockquote, RootContent, Html } from 'mdast'\nimport type { AlertType } from '../transform/gfm-alerts.js'\nimport type { ResolvedPage } from '../core/source-resolver.js'\nimport type { TargetAdapter, NavConfigOutput } from './types.js'\nimport { toString } from 'mdast-util-to-string'\n\n/**\n * Alert type mapping: GFM → Starlight aside type\n *\n * Starlight aside types: note, tip, caution, danger\n */\nconst ALERT_TYPE_MAP: Record<AlertType, string> = {\n note: 'note',\n tip: 'tip',\n important: 'note',\n warning: 'caution',\n caution: 'danger',\n}\n\nexport const starlightAdapter: TargetAdapter = {\n name: 'starlight',\n\n transformAlert(type: AlertType, node: Blockquote): RootContent {\n const asideType = ALERT_TYPE_MAP[type]\n const content = toString(node)\n\n // Starlight uses ::: directive syntax (Astro-flavored)\n const htmlNode: Html = {\n type: 'html',\n value: `:::${asideType}\\n${content}\\n:::`,\n }\n\n return htmlNode\n },\n\n generateNavConfig(): NavConfigOutput | null {\n // Starlight uses frontmatter sidebar config + astro.config.mjs\n // No separate nav config file needed\n return null\n },\n\n generateFrontmatter(page: ResolvedPage): Record<string, unknown> {\n const fm: Record<string, unknown> = {\n title: page.title ?? 'Untitled',\n }\n\n if (page.description) {\n fm.description = page.description\n }\n\n // Starlight uses nested sidebar config in frontmatter\n fm.sidebar = { order: page.order }\n\n return fm\n },\n}\n","import type { TargetAdapter } from './types.js'\nimport { fumadocsAdapter } from './fumadocs.js'\nimport { docusaurusAdapter } from './docusaurus.js'\nimport { nextraAdapter } from './nextra.js'\nimport { starlightAdapter } from './starlight.js'\n\nconst adapters: Record<string, TargetAdapter> = {\n fumadocs: fumadocsAdapter,\n docusaurus: docusaurusAdapter,\n nextra: nextraAdapter,\n starlight: starlightAdapter,\n}\n\nexport function getAdapter(target: string): TargetAdapter {\n const adapter = adapters[target]\n if (!adapter) {\n throw new Error(\n `Unknown target \"${target}\". Available targets: ${Object.keys(adapters).join(', ')}`,\n )\n }\n return adapter\n}\n\nexport type { TargetAdapter, NavConfigOutput } from './types.js'\n"],"mappings":"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","names":["fs","path","visit","path","visit","path","visit","toString","ALERT_TYPE_MAP","toString","ALERT_TYPE_MAP","toString","ALERT_TYPE_MAP","path","fs"]}
|
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
buildPipeline,
|
|
4
4
|
loadDocSyncConfig
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-VMKHKCT5.js";
|
|
6
6
|
|
|
7
7
|
// src/cli/index.ts
|
|
8
8
|
import { defineCommand as defineCommand3, runMain } from "citty";
|
|
@@ -111,7 +111,7 @@ var initCommand = defineCommand2({
|
|
|
111
111
|
var main = defineCommand3({
|
|
112
112
|
meta: {
|
|
113
113
|
name: "docsync",
|
|
114
|
-
version: "0.0.
|
|
114
|
+
version: "0.0.2",
|
|
115
115
|
description: "Transform GitHub markdown into docs-framework-compatible output."
|
|
116
116
|
},
|
|
117
117
|
subCommands: {
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli/index.ts","../src/cli/commands/build.ts","../src/cli/commands/init.ts"],"sourcesContent":["import { defineCommand, runMain } from 'citty'\nimport { buildCommand } from './commands/build.js'\nimport { initCommand } from './commands/init.js'\n\nconst main = defineCommand({\n meta: {\n name: 'docsync',\n version: '0.0.
|
|
1
|
+
{"version":3,"sources":["../src/cli/index.ts","../src/cli/commands/build.ts","../src/cli/commands/init.ts"],"sourcesContent":["import { defineCommand, runMain } from 'citty'\nimport { buildCommand } from './commands/build.js'\nimport { initCommand } from './commands/init.js'\n\nconst main = defineCommand({\n meta: {\n name: 'docsync',\n version: '0.0.2',\n description: 'Transform GitHub markdown into docs-framework-compatible output.',\n },\n subCommands: {\n build: buildCommand,\n init: initCommand,\n },\n})\n\nrunMain(main)\n","import { defineCommand } from 'citty'\nimport { loadDocSyncConfig } from '../../config/loader.js'\nimport { buildPipeline } from '../../core/pipeline.js'\n\nexport const buildCommand = defineCommand({\n meta: {\n name: 'build',\n description: 'Transform markdown sources into docs-framework output.',\n },\n args: {\n cwd: {\n type: 'string',\n description: 'Working directory',\n default: '.',\n },\n },\n async run({ args }) {\n const cwd = args.cwd === '.' ? process.cwd() : args.cwd\n\n console.log('[docsync] Loading config...')\n\n let config\n try {\n config = await loadDocSyncConfig(cwd)\n } catch (err) {\n console.error(\n `[docsync] ${err instanceof Error ? err.message : String(err)}`,\n )\n process.exit(1)\n }\n\n console.log(`[docsync] Target: ${config.target}`)\n console.log(`[docsync] Output: ${config.outDir}`)\n\n const result = await buildPipeline(config, cwd)\n\n // Report results\n for (const page of result.pages) {\n console.log(` ✓ ${page.slug}.mdx`)\n }\n\n for (const error of result.errors) {\n console.error(` ✗ ${error.file}: ${error.error}`)\n }\n\n console.log(\n `\\n[docsync] Done! ${result.pages.length} files written to ${config.outDir}/`,\n )\n\n if (result.errors.length > 0) {\n console.error(`[docsync] ${result.errors.length} error(s) occurred.`)\n process.exit(1)\n }\n },\n})\n","import fs from 'node:fs/promises'\nimport path from 'node:path'\nimport { defineCommand } from 'citty'\n\nconst CONFIG_TEMPLATE = `import { defineConfig } from '@tansuasici/docsync'\n\nexport default defineConfig({\n sources: [\n { path: 'README.md', slug: 'index', title: 'Introduction' },\n { path: 'docs/**/*.md' },\n ],\n target: 'fumadocs',\n outDir: '.docsync',\n // github: { repo: 'user/repo' },\n})\n`\n\nexport const initCommand = defineCommand({\n meta: {\n name: 'init',\n description: 'Initialize a docsync config file.',\n },\n args: {\n cwd: {\n type: 'string',\n description: 'Working directory',\n default: '.',\n },\n },\n async run({ args }) {\n const cwd = args.cwd === '.' ? process.cwd() : args.cwd\n const configPath = path.join(cwd, 'docsync.config.ts')\n\n // Check if config already exists\n try {\n await fs.access(configPath)\n console.log('[docsync] Config file already exists: docsync.config.ts')\n return\n } catch {\n // File doesn't exist — good\n }\n\n await fs.writeFile(configPath, CONFIG_TEMPLATE, 'utf-8')\n console.log('[docsync] Created docsync.config.ts')\n\n // Add .docsync/ to .gitignore if it exists\n const gitignorePath = path.join(cwd, '.gitignore')\n try {\n const gitignore = await fs.readFile(gitignorePath, 'utf-8')\n if (!gitignore.includes('.docsync')) {\n await fs.appendFile(gitignorePath, '\\n.docsync/\\n')\n console.log('[docsync] Added .docsync/ to .gitignore')\n }\n } catch {\n // No .gitignore — skip\n }\n\n console.log('\\nNext steps:')\n console.log(' 1. Edit docsync.config.ts to configure your sources')\n console.log(' 2. Run: npx docsync build')\n },\n})\n"],"mappings":"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","names":["defineCommand","defineCommand","defineCommand"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -17,6 +17,9 @@ declare const configSchema: z.ZodObject<{
|
|
|
17
17
|
}, z.core.$strip>>;
|
|
18
18
|
target: z.ZodEnum<{
|
|
19
19
|
fumadocs: "fumadocs";
|
|
20
|
+
docusaurus: "docusaurus";
|
|
21
|
+
nextra: "nextra";
|
|
22
|
+
starlight: "starlight";
|
|
20
23
|
}>;
|
|
21
24
|
outDir: z.ZodDefault<z.ZodString>;
|
|
22
25
|
github: z.ZodOptional<z.ZodObject<{
|
package/dist/index.js
CHANGED