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.
@@ -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
- * Pipeline:
6
- * 1. importGitbook (format-gitbook plugin): detect the corpus, read
7
- * `.gitbook.yaml` + `SUMMARY.md`, translate every page's Git Sync
8
- * markdown to Dogsbay-MD, and return pages + nav + redirects +
9
- * detected spaces.
10
- * 2. Write each page to `content/<slug>.md` (source frontmatter + a
11
- * title, then the translated body). Fragments (`.gitbook/includes/*`)
12
- * are written without frontmatter as `{% include %}` targets.
13
- * 3. Mirror non-`.md` assets (`.gitbook/assets/**`, images) into
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, readdirSync, rmSync, realpathSync, statSync, writeFileSync, } from "node:fs";
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 { pages, nav, redirects, spaces, assetRoots } = await importGitbook(sourceDir, {
202
- space: options.space,
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 routable = pages.filter((p) => !p.fragment);
210
- console.log(`Pages: ${routable.length}` + (spaces.length > 1 ? ` across ${spaces.length} spaces` : ""));
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. Write pages + fragments. Guard against slug collisions — two
219
- // source files can slugify to the same path (e.g. README.md and
220
- // index.md both → `index`). Keep the first, skip + warn on the rest
221
- // rather than silently overwriting a whole page (review #3).
222
- let fragments = 0;
223
- let collisions = 0;
224
- const written = new Set();
225
- for (const page of pages) {
226
- const dst = join(contentDir, `${page.slug}.md`);
227
- if (written.has(dst)) {
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
- const body = page.bodyMarkdown ?? "";
235
- if (page.fragment) {
236
- // Fragments: pure body, no frontmatter (they're include targets).
237
- writeFileSync(dst, body.endsWith("\n") ? body : body + "\n");
238
- fragments += 1;
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
- else {
241
- const fm = frontmatterBlock(page.frontmatter ?? {}, page.title);
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
- // 6. Redirects → public/_redirects (Netlify/CF Pages style).
260
- const redirectKeys = Object.keys(redirects);
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
- const lines = redirectKeys.map((from) => {
265
- // Normalize target to its served URL: drop ./ + .md, and collapse a
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
- // 7. Scaffold the Astro project.
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
- * Config-anchored detectors (mkdocs.yml, docusaurus.config.*, hugo.*, …) come
23
- * first because they cannot false-positive. The two CATCH-ALL detectors go
24
- * last-but-one and last-ish, and their relative order is load-bearing:
25
- *
26
- * asciidoc → a corpus whose `.adoc` files outnumber its `.md` files
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
- export const FORMATS = [
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.130",
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.130",
47
- "@dogsbay/autodoc-python": "0.2.0-beta.130",
48
- "@dogsbay/commands": "0.2.0-beta.130",
49
- "@dogsbay/format-asciidoc": "0.2.0-beta.130",
50
- "@dogsbay/format-astro": "0.2.0-beta.130",
51
- "@dogsbay/format-dita": "0.2.0-beta.130",
52
- "@dogsbay/format-docusaurus": "0.2.0-beta.130",
53
- "@dogsbay/format-dogsbay-md": "0.2.0-beta.130",
54
- "@dogsbay/format-fluidtopics-markdown": "0.2.0-beta.130",
55
- "@dogsbay/format-fluidtopics-rest": "0.2.0-beta.130",
56
- "@dogsbay/format-gatsby": "0.2.0-beta.130",
57
- "@dogsbay/format-gitbook": "0.2.0-beta.130",
58
- "@dogsbay/format-mdx": "0.2.0-beta.130",
59
- "@dogsbay/format-hugo": "0.2.0-beta.130",
60
- "@dogsbay/format-mkdocs": "0.2.0-beta.130",
61
- "@dogsbay/format-myst": "0.2.0-beta.130",
62
- "@dogsbay/format-obsidian": "0.2.0-beta.130",
63
- "@dogsbay/format-openknowledge": "0.2.0-beta.130",
64
- "@dogsbay/format-openapi": "0.2.0-beta.130",
65
- "@dogsbay/format-starlight": "0.2.0-beta.130",
66
- "@dogsbay/format-topicmap": "0.2.0-beta.130",
67
- "@dogsbay/format-rst": "0.2.0-beta.130",
68
- "@dogsbay/format-vitepress": "0.2.0-beta.130",
69
- "@dogsbay/minja": "0.2.0-beta.130",
70
- "@dogsbay/render": "0.2.0-beta.130",
71
- "@dogsbay/render-html": "0.2.0-beta.130",
72
- "@dogsbay/serialize-core": "0.2.0-beta.130",
73
- "@dogsbay/site-check": "0.2.0-beta.130",
74
- "@dogsbay/site-files": "0.2.0-beta.130",
75
- "@dogsbay/types": "0.2.0-beta.130"
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",