dogsbay 0.2.0-beta.130 → 0.2.0-beta.131
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/dist/commands/migrate-gitbook.js +53 -250
- package/dist/formats.d.ts +2 -17
- package/dist/formats.js +20 -38
- package/package.json +32 -31
|
@@ -2,33 +2,24 @@
|
|
|
2
2
|
* `dogsbay migrate-gitbook <source>` — one-way migrate a GitBook Git Sync
|
|
3
3
|
* corpus to a scaffolded Dogsbay-MD project.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* 3.
|
|
14
|
-
* `content/` at matching relative paths so the translated relative
|
|
15
|
-
* image refs resolve.
|
|
16
|
-
* 4. Write `content/nav.yml` (from SUMMARY), `dogsbay.config.yml`,
|
|
17
|
-
* scaffold the Astro project, carry `.gitbook.yaml` redirects into
|
|
18
|
-
* `public/_redirects`, and write `MIGRATION.md`.
|
|
5
|
+
* The migration itself is format-gitbook's: `importGitbook` (the folder as a
|
|
6
|
+
* corpus → pages, nav, redirects, ledger) and `gitbookProject` (→ the
|
|
7
|
+
* project's files, the assets to copy, the redirects). The playground runs
|
|
8
|
+
* the same two over a map of files (`@dogsbay/format-gitbook/migrate`), so a
|
|
9
|
+
* migration there is the one this command does. What stays here is the disk:
|
|
10
|
+
* 1. write the project's files (content/*.md, content/nav.yml,
|
|
11
|
+
* dogsbay.config.yml, MIGRATION.md) and copy the assets;
|
|
12
|
+
* 2. `.gitbook.yaml` redirects → astro/public/_redirects;
|
|
13
|
+
* 3. scaffold the Astro project, and the console output.
|
|
19
14
|
*
|
|
20
15
|
* Source-of-truth becomes `./content/*.md`; the GitBook source can be
|
|
21
16
|
* deleted. Mirrors migrate-asciidoc / migrate-mkdocs in shape.
|
|
22
17
|
*/
|
|
23
|
-
import { copyFileSync, existsSync, mkdirSync,
|
|
18
|
+
import { copyFileSync, existsSync, mkdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
24
19
|
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
25
20
|
import pc from "picocolors";
|
|
26
|
-
import { stringify as stringifyYaml } from "yaml";
|
|
27
21
|
import { emitSiteScaffold } from "@dogsbay/format-astro";
|
|
28
|
-
import { importGitbook } from "@dogsbay/format-gitbook/cli";
|
|
29
|
-
function yamlQuote(s) {
|
|
30
|
-
return /[:#&*!|>%@`,'"[\]{}]|^\s|\s$/.test(s) ? JSON.stringify(s) : s;
|
|
31
|
-
}
|
|
22
|
+
import { gitbookProject, importGitbook } from "@dogsbay/format-gitbook/cli";
|
|
32
23
|
function prettify(slug) {
|
|
33
24
|
return slug
|
|
34
25
|
.split(/[-_/]/)
|
|
@@ -36,147 +27,6 @@ function prettify(slug) {
|
|
|
36
27
|
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
37
28
|
.join(" ");
|
|
38
29
|
}
|
|
39
|
-
/** Serialize NavItem[] to nav.yml (mirrors migrate-asciidoc's navToYaml). */
|
|
40
|
-
function navToYaml(items, indent = 0) {
|
|
41
|
-
const pad = " ".repeat(indent);
|
|
42
|
-
const lines = [];
|
|
43
|
-
for (const item of items) {
|
|
44
|
-
const hasChildren = item.children !== undefined && item.children.length > 0;
|
|
45
|
-
const target = item.href === undefined
|
|
46
|
-
? undefined
|
|
47
|
-
: /^(?:https?:\/\/|mailto:|tel:)/i.test(item.href) || item.href.includes("#")
|
|
48
|
-
? item.href
|
|
49
|
-
: `${item.href.replace(/^\/+/, "")}.md`;
|
|
50
|
-
if (target !== undefined && !hasChildren) {
|
|
51
|
-
lines.push(`${pad}- ${yamlQuote(item.label)}: ${target}`);
|
|
52
|
-
}
|
|
53
|
-
else {
|
|
54
|
-
lines.push(`${pad}- ${yamlQuote(item.label)}:`);
|
|
55
|
-
if (target !== undefined)
|
|
56
|
-
lines.push(`${pad} - ${yamlQuote(target)}`);
|
|
57
|
-
if (hasChildren)
|
|
58
|
-
lines.push(navToYaml(item.children, indent + 2));
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
return lines.join("\n");
|
|
62
|
-
}
|
|
63
|
-
function buildConfig(siteName, siteUrl) {
|
|
64
|
-
const lines = [
|
|
65
|
-
`schemaVersion: 1`,
|
|
66
|
-
`site:`,
|
|
67
|
-
` name: ${yamlQuote(siteName)}`,
|
|
68
|
-
` basePath: ""`,
|
|
69
|
-
];
|
|
70
|
-
if (siteUrl)
|
|
71
|
-
lines.push(` url: ${yamlQuote(siteUrl)}`);
|
|
72
|
-
lines.push(`content:`, ` sources:`, ` - path: ./content`, ` from: dogsbay-md`,
|
|
73
|
-
// `.gitbook/includes` fragments are `{% include %}` targets, not
|
|
74
|
-
// routable pages. routablesFrom: nav restricts routes to SUMMARY-
|
|
75
|
-
// named pages; the fragments stay on disk so includes resolve.
|
|
76
|
-
` excludeFromRoutes:`, ` - .gitbook`, ` routablesFrom: nav`, ``);
|
|
77
|
-
return lines.join("\n");
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* Serialize frontmatter (source keys + a title) as a YAML block.
|
|
81
|
-
*
|
|
82
|
-
* Uses YAML.stringify so array/object values (e.g. `tags: [a, b]`,
|
|
83
|
-
* GitBook `cover:` maps) round-trip instead of being silently dropped
|
|
84
|
-
* (review #4). The title is the source `title:` only when it's a
|
|
85
|
-
* non-empty string; otherwise the computed title (H1 / slug) — a blank
|
|
86
|
-
* or whitespace-only source title must NOT clobber it (review #6).
|
|
87
|
-
*/
|
|
88
|
-
function frontmatterBlock(fm, title) {
|
|
89
|
-
const sourceTitle = typeof fm.title === "string" && fm.title.trim() ? fm.title : undefined;
|
|
90
|
-
const out = { title: sourceTitle ?? title, ...fm };
|
|
91
|
-
out.title = sourceTitle ?? title;
|
|
92
|
-
const body = stringifyYaml(out).replace(/\n$/, "");
|
|
93
|
-
return `---\n${body}\n---\n\n`;
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Mirror non-`.md` assets from each docs root into `content/`, preserving
|
|
97
|
-
* the docs-root-relative layout under the space's slug prefix. Mirroring
|
|
98
|
-
* from the docs root (not the repo root) matters when `.gitbook.yaml`
|
|
99
|
-
* sets `root: ./docs/gitbook/` — otherwise unrelated repo source lands in
|
|
100
|
-
* content/ and the translated relative image refs (`../.gitbook/assets/…`)
|
|
101
|
-
* point at the wrong place.
|
|
102
|
-
*/
|
|
103
|
-
function mirrorAssets(roots, contentDir) {
|
|
104
|
-
let count = 0;
|
|
105
|
-
for (const root of roots) {
|
|
106
|
-
const destBase = root.prefix ? join(contentDir, root.prefix) : contentDir;
|
|
107
|
-
// Only what is inside the space once links resolve: a symlink in someone
|
|
108
|
-
// else's repo must not copy a file from outside it into content/, where
|
|
109
|
-
// a `:::openapi` spec, for one, is rendered into the page.
|
|
110
|
-
let realRoot;
|
|
111
|
-
try {
|
|
112
|
-
realRoot = realpathSync(root.dir);
|
|
113
|
-
}
|
|
114
|
-
catch {
|
|
115
|
-
continue;
|
|
116
|
-
}
|
|
117
|
-
const inside = (p) => {
|
|
118
|
-
try {
|
|
119
|
-
const real = realpathSync(p);
|
|
120
|
-
return real === realRoot || real.startsWith(realRoot + sep);
|
|
121
|
-
}
|
|
122
|
-
catch {
|
|
123
|
-
return false;
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
// Each directory once: two links to `.` made the walk branch twice per level until ELOOP.
|
|
127
|
-
const seen = new Set([realRoot]);
|
|
128
|
-
const walk = (dir) => {
|
|
129
|
-
let entries;
|
|
130
|
-
try {
|
|
131
|
-
entries = readdirSync(dir);
|
|
132
|
-
}
|
|
133
|
-
catch {
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
for (const name of entries) {
|
|
137
|
-
if (name === "node_modules" || name === ".git")
|
|
138
|
-
continue;
|
|
139
|
-
const full = join(dir, name);
|
|
140
|
-
let st;
|
|
141
|
-
try {
|
|
142
|
-
if (!inside(full))
|
|
143
|
-
continue;
|
|
144
|
-
st = statSync(full);
|
|
145
|
-
}
|
|
146
|
-
catch {
|
|
147
|
-
continue;
|
|
148
|
-
}
|
|
149
|
-
if (st.isDirectory()) {
|
|
150
|
-
// Skip hidden dirs EXCEPT `.gitbook` (holds assets + includes).
|
|
151
|
-
if (name.startsWith(".") && name !== ".gitbook")
|
|
152
|
-
continue;
|
|
153
|
-
const real = realpathSync(full);
|
|
154
|
-
if (seen.has(real))
|
|
155
|
-
continue;
|
|
156
|
-
seen.add(real);
|
|
157
|
-
walk(full);
|
|
158
|
-
continue;
|
|
159
|
-
}
|
|
160
|
-
if (name.toLowerCase().endsWith(".md"))
|
|
161
|
-
continue; // pages handled separately
|
|
162
|
-
if (name === ".gitbook.yaml")
|
|
163
|
-
continue;
|
|
164
|
-
const rel = relative(root.dir, full);
|
|
165
|
-
const dest = join(destBase, rel);
|
|
166
|
-
try {
|
|
167
|
-
mkdirSync(dirname(dest), { recursive: true });
|
|
168
|
-
copyFileSync(full, dest);
|
|
169
|
-
count += 1;
|
|
170
|
-
}
|
|
171
|
-
catch {
|
|
172
|
-
/* ignore individual asset copy failures */
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
};
|
|
176
|
-
walk(root.dir);
|
|
177
|
-
}
|
|
178
|
-
return count;
|
|
179
|
-
}
|
|
180
30
|
export async function migrateGitbook(source, options) {
|
|
181
31
|
const sourceDir = resolve(source);
|
|
182
32
|
if (!existsSync(sourceDir) || !statSync(sourceDir).isDirectory()) {
|
|
@@ -198,16 +48,21 @@ export async function migrateGitbook(source, options) {
|
|
|
198
48
|
console.log(`Source: ${sourceDir}`);
|
|
199
49
|
console.log(`Output: ${outputDir}`);
|
|
200
50
|
console.log();
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
});
|
|
51
|
+
const imported = await importGitbook(sourceDir, { space: options.space });
|
|
52
|
+
const { pages, corpus } = imported;
|
|
204
53
|
if (pages.length === 0) {
|
|
205
54
|
console.log(pc.red(`Error: no GitBook pages found under ${sourceDir}`));
|
|
206
55
|
console.log("Point this at a directory containing a SUMMARY.md or .gitbook.yaml.");
|
|
207
56
|
process.exit(1);
|
|
208
57
|
}
|
|
209
|
-
const
|
|
210
|
-
|
|
58
|
+
const project = gitbookProject(imported, corpus, {
|
|
59
|
+
siteName,
|
|
60
|
+
siteUrl: options.siteUrl,
|
|
61
|
+
sourceLabel: sourceDir,
|
|
62
|
+
redirectsFile: "astro/public/_redirects",
|
|
63
|
+
});
|
|
64
|
+
const { stats } = project;
|
|
65
|
+
console.log(`Pages: ${stats.pages}` + (stats.spaces.length > 1 ? ` across ${stats.spaces.length} spaces` : ""));
|
|
211
66
|
// 1. Output skeleton — clear content/ on a fresh/forced run.
|
|
212
67
|
const contentDir = join(outputDir, "content");
|
|
213
68
|
const astroDir = join(outputDir, "astro");
|
|
@@ -215,72 +70,51 @@ export async function migrateGitbook(source, options) {
|
|
|
215
70
|
rmSync(contentDir, { recursive: true, force: true });
|
|
216
71
|
mkdirSync(contentDir, { recursive: true });
|
|
217
72
|
mkdirSync(astroDir, { recursive: true });
|
|
218
|
-
// 2.
|
|
219
|
-
//
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
collisions += 1;
|
|
229
|
-
console.warn(`gitbook migrate: slug collision at "${page.slug}" — keeping the first page, skipping a later source that maps to the same file.`);
|
|
230
|
-
continue;
|
|
231
|
-
}
|
|
232
|
-
written.add(dst);
|
|
73
|
+
// 2. The project's files, then the assets (.gitbook/assets, images) so relative refs resolve.
|
|
74
|
+
// Every path is the project's, from someone else's repo: nothing lands outside the output.
|
|
75
|
+
const inside = (path) => {
|
|
76
|
+
const dst = resolve(outputDir, path);
|
|
77
|
+
if (!dst.startsWith(outputDir + sep))
|
|
78
|
+
throw new Error(`refusing to write outside ${outputDir}: ${path}`);
|
|
79
|
+
return dst;
|
|
80
|
+
};
|
|
81
|
+
for (const [path, text] of project.files) {
|
|
82
|
+
const dst = inside(path);
|
|
233
83
|
mkdirSync(dirname(dst), { recursive: true });
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
84
|
+
writeFileSync(dst, text);
|
|
85
|
+
}
|
|
86
|
+
for (const e of project.ledger.filter((e) => e.code === "slug-collision"))
|
|
87
|
+
console.warn(`gitbook migrate: ${e.message}`);
|
|
88
|
+
console.log(pc.green(`Converted`) +
|
|
89
|
+
` ${stats.pages} page(s)` +
|
|
90
|
+
(stats.fragments ? ` + ${stats.fragments} fragment(s)` : "") +
|
|
91
|
+
(stats.collisions ? pc.yellow(` (${stats.collisions} slug collision(s) skipped)`) : ""));
|
|
92
|
+
let assetCount = 0;
|
|
93
|
+
for (const { from, to } of project.assets) {
|
|
94
|
+
const dst = inside(to);
|
|
95
|
+
try {
|
|
96
|
+
mkdirSync(dirname(dst), { recursive: true });
|
|
97
|
+
copyFileSync(corpus.absolute(from), dst);
|
|
98
|
+
assetCount += 1;
|
|
239
99
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
writeFileSync(dst, fm + (body.endsWith("\n") ? body : body + "\n"));
|
|
100
|
+
catch {
|
|
101
|
+
/* ignore individual asset copy failures */
|
|
243
102
|
}
|
|
244
103
|
}
|
|
245
|
-
console.log(pc.green(`Converted`) +
|
|
246
|
-
` ${routable.length} page(s)` +
|
|
247
|
-
(fragments ? ` + ${fragments} fragment(s)` : "") +
|
|
248
|
-
(collisions ? pc.yellow(` (${collisions} slug collision(s) skipped)`) : ""));
|
|
249
|
-
// 3. Mirror assets (.gitbook/assets, images) so relative refs resolve.
|
|
250
|
-
const assetCount = mirrorAssets(assetRoots, contentDir);
|
|
251
104
|
if (assetCount > 0)
|
|
252
105
|
console.log(pc.green(`Mirrored`) + ` ${assetCount} asset(s)`);
|
|
253
|
-
// 4. nav.yml.
|
|
254
|
-
writeFileSync(join(contentDir, "nav.yml"), `# Migrated by dogsbay migrate-gitbook.\n# Edit freely.\n\n${navToYaml(nav)}\n`);
|
|
255
106
|
console.log(pc.green(`Wrote`) + ` content/nav.yml`);
|
|
256
|
-
// 5. dogsbay.config.yml.
|
|
257
|
-
writeFileSync(join(outputDir, "dogsbay.config.yml"), buildConfig(siteName, options.siteUrl));
|
|
258
107
|
console.log(pc.green(`Wrote`) + ` dogsbay.config.yml`);
|
|
259
|
-
//
|
|
260
|
-
|
|
261
|
-
if (redirectKeys.length > 0) {
|
|
108
|
+
// 3. Redirects → public/_redirects (Netlify/CF Pages style).
|
|
109
|
+
if (project.redirects.length > 0) {
|
|
262
110
|
const publicDir = join(astroDir, "public");
|
|
263
111
|
mkdirSync(publicDir, { recursive: true });
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
// README landing (bare `README` OR `<dir>/README`) to its directory
|
|
267
|
-
// — a bare `README` maps to the site root `/`, not a dead `/README`
|
|
268
|
-
// (review #5).
|
|
269
|
-
const to = redirects[from]
|
|
270
|
-
.replace(/^\.\//, "")
|
|
271
|
-
.replace(/\.md$/i, "")
|
|
272
|
-
.replace(/(^|\/)README$/i, "$1")
|
|
273
|
-
.replace(/\/$/, "");
|
|
274
|
-
return `/${from} /${to} 301`;
|
|
275
|
-
});
|
|
276
|
-
writeFileSync(join(publicDir, "_redirects"), lines.join("\n") + "\n");
|
|
277
|
-
console.log(pc.green(`Wrote`) + ` ${redirectKeys.length} redirect(s) → astro/public/_redirects`);
|
|
112
|
+
writeFileSync(join(publicDir, "_redirects"), project.redirects.map((r) => `${r.from} ${r.to} 301`).join("\n") + "\n");
|
|
113
|
+
console.log(pc.green(`Wrote`) + ` ${project.redirects.length} redirect(s) → astro/public/_redirects`);
|
|
278
114
|
}
|
|
279
|
-
//
|
|
115
|
+
// 4. Scaffold the Astro project.
|
|
280
116
|
emitSiteScaffold(astroDir, siteName, { siteName, siteUrl: options.siteUrl, basePath: "", llmsTxt: true, mdMirror: true, local: options.local, sourceDir }, true);
|
|
281
117
|
console.log(pc.green(`Scaffolded`) + ` Astro project at ./astro/`);
|
|
282
|
-
// 8. MIGRATION.md.
|
|
283
|
-
writeFileSync(join(outputDir, "MIGRATION.md"), buildMigrationNotes({ sourceDir, siteName, pageCount: routable.length, fragments, spaces, redirectCount: redirectKeys.length }));
|
|
284
118
|
console.log(pc.green(`Wrote`) + ` MIGRATION.md`);
|
|
285
119
|
if (!options.quiet) {
|
|
286
120
|
console.log();
|
|
@@ -290,34 +124,3 @@ export async function migrateGitbook(source, options) {
|
|
|
290
124
|
console.log(` npx dogsbay site check # audit rules`);
|
|
291
125
|
}
|
|
292
126
|
}
|
|
293
|
-
function buildMigrationNotes(a) {
|
|
294
|
-
return [
|
|
295
|
-
`# Migration: ${a.siteName}`,
|
|
296
|
-
``,
|
|
297
|
-
`Migrated from a GitBook Git Sync corpus by \`dogsbay migrate-gitbook\`.`,
|
|
298
|
-
``,
|
|
299
|
-
`- **Source**: \`${a.sourceDir}\``,
|
|
300
|
-
`- **Pages**: ${a.pageCount}${a.fragments ? ` (+ ${a.fragments} include fragments)` : ""}`,
|
|
301
|
-
a.spaces.length > 1 ? `- **Spaces**: ${a.spaces.join(", ")} (each a top-level nav section)` : ``,
|
|
302
|
-
a.redirectCount ? `- **Redirects**: ${a.redirectCount} carried into \`astro/public/_redirects\`` : ``,
|
|
303
|
-
``,
|
|
304
|
-
`## What was converted`,
|
|
305
|
-
``,
|
|
306
|
-
`- GitBook \`{% hint %}\` → \`:::note/:::warning/:::info/:::danger\` callouts`,
|
|
307
|
-
`- \`{% tabs %}\` / \`{% stepper %}\` / \`{% columns %}\` → \`:::tabs\` / \`:::steps\` / \`:::grid\``,
|
|
308
|
-
`- \`{% code %}\`, \`{% content-ref %}\`, \`{% embed %}\`, \`{% file %}\`, \`{% page-ref %}\` → native Dogsbay-MD`,
|
|
309
|
-
`- \`{% include %}\` reusable content preserved (resolved at build time)`,
|
|
310
|
-
`- \`<figure>\` images, GitBook emoji spans, raw HTML tables handled`,
|
|
311
|
-
`- \`SUMMARY.md\` → \`content/nav.yml\`; \`.gitbook.yaml\` redirects → \`_redirects\``,
|
|
312
|
-
``,
|
|
313
|
-
`## Known caveats`,
|
|
314
|
-
``,
|
|
315
|
-
`- \`{% openapi %}\` / \`{% swagger %}\` blocks emit \`:::api-endpoint\` placeholders — wire them to the OpenAPI spec for full rendering.`,
|
|
316
|
-
`- Review any pages flagged in the import ledger (printed during migration).`,
|
|
317
|
-
``,
|
|
318
|
-
]
|
|
319
|
-
.filter((l) => l !== ``)
|
|
320
|
-
.join("\n")
|
|
321
|
-
.replace(/\n(##)/g, "\n\n$1")
|
|
322
|
-
.concat("\n");
|
|
323
|
-
}
|
package/dist/formats.d.ts
CHANGED
|
@@ -22,24 +22,9 @@
|
|
|
22
22
|
*/
|
|
23
23
|
import type { FormatPlugin } from "@dogsbay/types";
|
|
24
24
|
import { type MdxAdapter } from "@dogsbay/format-mdx";
|
|
25
|
-
/**
|
|
26
|
-
* ORDER IS DETECTION PRECEDENCE — {@link detectFormat} returns the first
|
|
27
|
-
* IMPORTABLE plugin whose `detectSource` claims the path.
|
|
28
|
-
*
|
|
29
|
-
* Config-anchored detectors (mkdocs.yml, docusaurus.config.*, hugo.*, …) come
|
|
30
|
-
* first because they cannot false-positive. The two CATCH-ALL detectors go
|
|
31
|
-
* last-but-one and last-ish, and their relative order is load-bearing:
|
|
32
|
-
*
|
|
33
|
-
* asciidoc → a corpus whose `.adoc` files outnumber its `.md` files
|
|
34
|
-
* dogsbay-md → any `.md` anywhere under the source
|
|
35
|
-
*
|
|
36
|
-
* asciidoc goes FIRST of the two: a real AsciiDoc corpus almost always carries
|
|
37
|
-
* a `README.md` (openshift-docs does), and markdown-first would claim it and
|
|
38
|
-
* import that one file instead of 20k pages. The inverse mistake is prevented
|
|
39
|
-
* inside asciidoc's own detector, which now requires `.adoc` DOMINANCE rather
|
|
40
|
-
* than mere presence — see `format-asciidoc/src/loaders/plain.ts`.
|
|
41
|
-
*/
|
|
42
25
|
export declare const FORMATS: FormatPlugin[];
|
|
26
|
+
/** `plugins` in FORMAT_ORDER; throws unless the two name the same formats, once each. */
|
|
27
|
+
export declare function inDetectionOrder(plugins: FormatPlugin[]): FormatPlugin[];
|
|
43
28
|
/**
|
|
44
29
|
* Site adapters are OPTIONAL, and loaded only when named.
|
|
45
30
|
*
|
package/dist/formats.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { FORMAT_ORDER } from "@dogsbay/detect";
|
|
1
2
|
import { plugin as mkdocsPlugin } from "@dogsbay/format-mkdocs/cli";
|
|
2
3
|
import { plugin as astroPlugin } from "@dogsbay/format-astro/cli";
|
|
3
4
|
import { plugin as obsidianPlugin } from "@dogsbay/format-obsidian/cli";
|
|
@@ -19,20 +20,13 @@ import { fumadocsAdapter, mintlifyAdapter } from "@dogsbay/format-mdx";
|
|
|
19
20
|
* ORDER IS DETECTION PRECEDENCE — {@link detectFormat} returns the first
|
|
20
21
|
* IMPORTABLE plugin whose `detectSource` claims the path.
|
|
21
22
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* dogsbay-md → any `.md` anywhere under the source
|
|
28
|
-
*
|
|
29
|
-
* asciidoc goes FIRST of the two: a real AsciiDoc corpus almost always carries
|
|
30
|
-
* a `README.md` (openshift-docs does), and markdown-first would claim it and
|
|
31
|
-
* import that one file instead of 20k pages. The inverse mistake is prevented
|
|
32
|
-
* inside asciidoc's own detector, which now requires `.adoc` DOMINANCE rather
|
|
33
|
-
* than mere presence — see `format-asciidoc/src/loaders/plain.ts`.
|
|
23
|
+
* The order is `FORMAT_ORDER` from @dogsbay/detect, which says why each format
|
|
24
|
+
* sits where it does: the playground's browser import detects in the same
|
|
25
|
+
* order, so a repo is the same format in both. Every plugin must be named
|
|
26
|
+
* there exactly once, and nothing else — a missing or extra name throws when
|
|
27
|
+
* this module loads, rather than leaving a plugin silently undetectable.
|
|
34
28
|
*/
|
|
35
|
-
|
|
29
|
+
const PLUGINS = [
|
|
36
30
|
mkdocsPlugin,
|
|
37
31
|
astroPlugin,
|
|
38
32
|
obsidianPlugin,
|
|
@@ -40,41 +34,29 @@ export const FORMATS = [
|
|
|
40
34
|
mdxPlugin,
|
|
41
35
|
starlightPlugin,
|
|
42
36
|
docusaurusPlugin,
|
|
43
|
-
// gitbook BEFORE dogsbay-md: GitBook corpora are markdown, so the
|
|
44
|
-
// generic dogsbay-md detector would claim them first and skip the
|
|
45
|
-
// {% %} tag translation. gitbook's detector requires SUMMARY.md /
|
|
46
|
-
// .gitbook.yaml, so it can't false-positive on a real dogsbay-md site.
|
|
47
37
|
gitbookPlugin,
|
|
48
|
-
// hugo BEFORE dogsbay-md: Hugo content is markdown, so the generic
|
|
49
|
-
// dogsbay-md detector would claim it first and skip every shortcode.
|
|
50
|
-
// Hugo's detector requires a hugo.*/config.* file AND a content directory,
|
|
51
|
-
// so it cannot false-positive on a plain markdown corpus.
|
|
52
38
|
hugoPlugin,
|
|
53
|
-
// vitepress BEFORE dogsbay-md: VitePress content is markdown, so the
|
|
54
|
-
// generic dogsbay-md detector would claim it first and skip every `:::`
|
|
55
|
-
// container, code-group and `<<<` snippet. Its detector requires an actual
|
|
56
|
-
// `.vitepress/config.*`, so it cannot false-positive on a plain markdown,
|
|
57
|
-
// MkDocs or GitBook corpus.
|
|
58
39
|
vitepressPlugin,
|
|
59
40
|
rstPlugin,
|
|
60
|
-
// myst BEFORE dogsbay-md, AFTER gitbook/rst: MyST corpora are markdown,
|
|
61
|
-
// so the generic dogsbay-md detector would claim them first and skip the
|
|
62
|
-
// directive/role translation. myst's detector requires an explicit MyST
|
|
63
|
-
// config (myst.yml / _toc.yml) or MyST-specific syntax, so it can't
|
|
64
|
-
// false-positive on a plain dogsbay-md or GitBook site.
|
|
65
41
|
mystPlugin,
|
|
66
|
-
// asciidoc BEFORE dogsbay-md — see the precedence note above.
|
|
67
42
|
asciidocPlugin,
|
|
68
43
|
dogsbayMdPlugin,
|
|
69
44
|
gatsbyPlugin,
|
|
70
|
-
// openapi LAST. Its directory probe matches a top-level
|
|
71
|
-
// `openapi.{yaml,yml,json}` / `spec.*`, and plenty of docs corpora ship
|
|
72
|
-
// exactly that file alongside their pages — dub and ollama both do. In that
|
|
73
|
-
// case the DOCS format should win and the spec is imported deliberately with
|
|
74
|
-
// `--from openapi`; auto-detection only claims a bare spec file or a
|
|
75
|
-
// spec-only directory, where nothing else matches.
|
|
76
45
|
openapiPlugin,
|
|
77
46
|
];
|
|
47
|
+
export const FORMATS = inDetectionOrder(PLUGINS);
|
|
48
|
+
/** `plugins` in FORMAT_ORDER; throws unless the two name the same formats, once each. */
|
|
49
|
+
export function inDetectionOrder(plugins) {
|
|
50
|
+
const byName = new Map(plugins.map((p) => [p.name, p]));
|
|
51
|
+
const missing = FORMAT_ORDER.filter((n) => !byName.has(n));
|
|
52
|
+
const extra = plugins.filter((p) => !FORMAT_ORDER.includes(p.name)).map((p) => p.name);
|
|
53
|
+
if (missing.length || extra.length || byName.size !== plugins.length) {
|
|
54
|
+
throw new Error(`The format registry and @dogsbay/detect's FORMAT_ORDER disagree: ` +
|
|
55
|
+
`${missing.length ? `no plugin for ${missing.join(", ")}` : ""}${missing.length && extra.length ? "; " : ""}` +
|
|
56
|
+
`${extra.length ? `not in FORMAT_ORDER: ${extra.join(", ")}` : ""}${byName.size !== plugins.length ? " (a name is registered twice)" : ""}`);
|
|
57
|
+
}
|
|
58
|
+
return FORMAT_ORDER.map((n) => byName.get(n));
|
|
59
|
+
}
|
|
78
60
|
export const DOCUSAURUS_ADAPTERS = {
|
|
79
61
|
tigera: { module: "@dogsbay/adapter-tigera", export: "tigeraAdapter" },
|
|
80
62
|
camunda: { module: "@dogsbay/adapter-camunda", export: "camundaAdapter" },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dogsbay",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.131",
|
|
4
4
|
"description": "CLI for Dogsbay — scaffold, build, and serve documentation sites with markdown / MkDocs / Obsidian / OpenAPI sources",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -43,36 +43,37 @@
|
|
|
43
43
|
"typescript": "^5.9.3",
|
|
44
44
|
"valibot": "1.1.0",
|
|
45
45
|
"yaml": "^2.9.0",
|
|
46
|
-
"@dogsbay/adoc2md-modular": "0.2.0-beta.
|
|
47
|
-
"@dogsbay/autodoc-python": "0.2.0-beta.
|
|
48
|
-
"@dogsbay/commands": "0.2.0-beta.
|
|
49
|
-
"@dogsbay/
|
|
50
|
-
"@dogsbay/format-
|
|
51
|
-
"@dogsbay/format-
|
|
52
|
-
"@dogsbay/format-
|
|
53
|
-
"@dogsbay/format-
|
|
54
|
-
"@dogsbay/format-
|
|
55
|
-
"@dogsbay/format-fluidtopics-
|
|
56
|
-
"@dogsbay/format-gatsby": "0.2.0-beta.
|
|
57
|
-
"@dogsbay/format-
|
|
58
|
-
"@dogsbay/format-
|
|
59
|
-
"@dogsbay/format-hugo": "0.2.0-beta.
|
|
60
|
-
"@dogsbay/format-mkdocs": "0.2.0-beta.
|
|
61
|
-
"@dogsbay/format-
|
|
62
|
-
"@dogsbay/format-
|
|
63
|
-
"@dogsbay/format-
|
|
64
|
-
"@dogsbay/format-openapi": "0.2.0-beta.
|
|
65
|
-
"@dogsbay/format-
|
|
66
|
-
"@dogsbay/format-
|
|
67
|
-
"@dogsbay/format-
|
|
68
|
-
"@dogsbay/format-
|
|
69
|
-
"@dogsbay/
|
|
70
|
-
"@dogsbay/
|
|
71
|
-
"@dogsbay/render
|
|
72
|
-
"@dogsbay/
|
|
73
|
-
"@dogsbay/
|
|
74
|
-
"@dogsbay/site-
|
|
75
|
-
"@dogsbay/
|
|
46
|
+
"@dogsbay/adoc2md-modular": "0.2.0-beta.131",
|
|
47
|
+
"@dogsbay/autodoc-python": "0.2.0-beta.131",
|
|
48
|
+
"@dogsbay/commands": "0.2.0-beta.131",
|
|
49
|
+
"@dogsbay/detect": "0.2.0-beta.131",
|
|
50
|
+
"@dogsbay/format-asciidoc": "0.2.0-beta.131",
|
|
51
|
+
"@dogsbay/format-astro": "0.2.0-beta.131",
|
|
52
|
+
"@dogsbay/format-dita": "0.2.0-beta.131",
|
|
53
|
+
"@dogsbay/format-docusaurus": "0.2.0-beta.131",
|
|
54
|
+
"@dogsbay/format-dogsbay-md": "0.2.0-beta.131",
|
|
55
|
+
"@dogsbay/format-fluidtopics-markdown": "0.2.0-beta.131",
|
|
56
|
+
"@dogsbay/format-gatsby": "0.2.0-beta.131",
|
|
57
|
+
"@dogsbay/format-fluidtopics-rest": "0.2.0-beta.131",
|
|
58
|
+
"@dogsbay/format-gitbook": "0.2.0-beta.131",
|
|
59
|
+
"@dogsbay/format-hugo": "0.2.0-beta.131",
|
|
60
|
+
"@dogsbay/format-mkdocs": "0.2.0-beta.131",
|
|
61
|
+
"@dogsbay/format-mdx": "0.2.0-beta.131",
|
|
62
|
+
"@dogsbay/format-myst": "0.2.0-beta.131",
|
|
63
|
+
"@dogsbay/format-obsidian": "0.2.0-beta.131",
|
|
64
|
+
"@dogsbay/format-openapi": "0.2.0-beta.131",
|
|
65
|
+
"@dogsbay/format-openknowledge": "0.2.0-beta.131",
|
|
66
|
+
"@dogsbay/format-rst": "0.2.0-beta.131",
|
|
67
|
+
"@dogsbay/format-starlight": "0.2.0-beta.131",
|
|
68
|
+
"@dogsbay/format-topicmap": "0.2.0-beta.131",
|
|
69
|
+
"@dogsbay/format-vitepress": "0.2.0-beta.131",
|
|
70
|
+
"@dogsbay/minja": "0.2.0-beta.131",
|
|
71
|
+
"@dogsbay/render": "0.2.0-beta.131",
|
|
72
|
+
"@dogsbay/render-html": "0.2.0-beta.131",
|
|
73
|
+
"@dogsbay/serialize-core": "0.2.0-beta.131",
|
|
74
|
+
"@dogsbay/site-check": "0.2.0-beta.131",
|
|
75
|
+
"@dogsbay/site-files": "0.2.0-beta.131",
|
|
76
|
+
"@dogsbay/types": "0.2.0-beta.131"
|
|
76
77
|
},
|
|
77
78
|
"devDependencies": {
|
|
78
79
|
"@mdx-js/mdx": "^3.1.1",
|