dogsbay 0.2.0-beta.114 → 0.2.0-beta.116
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/audit/deliverable-vars.d.ts +1 -1
- package/dist/audit/rules/structure/index.d.ts +2 -11
- package/dist/audit/rules/structure/index.js +6 -36
- package/dist/audit/rules/structure/nav-target-exists.d.ts +1 -1
- package/dist/audit/rules/structure/topicmap-valid.d.ts +1 -1
- package/dist/child-links.d.ts +19 -0
- package/dist/child-links.js +48 -0
- package/dist/commands/convert.js +26 -0
- package/dist/commands/migrate-mkdocs.js +5 -5
- package/dist/commands/migrate-myst.d.ts +19 -0
- package/dist/commands/migrate-myst.js +77 -60
- package/dist/commands/migrate-rst.d.ts +7 -0
- package/dist/commands/migrate-rst.js +86 -35
- package/dist/commands/site-build.js +76 -6
- package/dist/commands/site-check.d.ts +1 -0
- package/dist/commands/site-check.js +27 -12
- package/dist/commands/site-dev.d.ts +27 -0
- package/dist/commands/site-dev.js +60 -2
- package/dist/commands/site-release-diff.d.ts +17 -0
- package/dist/commands/site-release-diff.js +85 -0
- package/dist/config/defaults.js +1 -0
- package/dist/config/load.js +118 -1
- package/dist/config/schema.d.ts +91 -2
- package/dist/config/to-astro-options.js +4 -0
- package/dist/import-content.d.ts +59 -0
- package/dist/import-content.js +83 -4
- package/dist/index.js +47 -5
- package/dist/lib.d.ts +2 -0
- package/dist/lib.js +4 -0
- package/dist/registry.js +11 -3
- package/dist/release/compare.d.ts +43 -0
- package/dist/release/compare.js +419 -0
- package/dist/resolve-openapi.d.ts +31 -0
- package/dist/resolve-openapi.js +181 -0
- package/dist/resolve-relative-links.d.ts +32 -0
- package/dist/resolve-relative-links.js +80 -0
- package/dist/site-build/preprocess.d.ts +8 -0
- package/dist/site-build/preprocess.js +111 -9
- package/package.json +39 -27
- package/skills/platform/{agent-readiness → dogsbay-agent-readiness}/SKILL.md +2 -2
- package/skills/platform/{cli-commands → dogsbay-cli-commands}/SKILL.md +2 -2
- package/skills/platform/{config-yml → dogsbay-config-yml}/SKILL.md +7 -7
- package/skills/platform/dogsbay-documents/SKILL.md +222 -0
- package/skills/platform/{frontmatter-fields → dogsbay-frontmatter-fields}/SKILL.md +7 -7
- package/skills/platform/{markdown-directives → dogsbay-markdown-directives}/SKILL.md +8 -4
- package/skills/platform/{migration-shape → dogsbay-migration-shape}/SKILL.md +2 -2
- package/skills/platform/{multi-source → dogsbay-multi-source}/SKILL.md +1 -1
- package/skills/platform/{nav-file → dogsbay-nav-file}/SKILL.md +1 -1
- package/skills/platform/{openapi-source → dogsbay-openapi-source}/SKILL.md +1 -1
- package/skills/platform/{plugin-api → dogsbay-plugin-api}/SKILL.md +1 -1
- package/skills/platform/{project-anatomy → dogsbay-project-anatomy}/SKILL.md +1 -1
- package/skills/platform/{taxonomy-config → dogsbay-taxonomy-config}/SKILL.md +1 -1
- package/skills/platform/{theme-tokens → dogsbay-theme-tokens}/SKILL.md +1 -1
- package/dist/audit/dist/crawler.d.ts +0 -23
- package/dist/audit/dist/crawler.js +0 -71
- package/dist/audit/lib/collect-assets.d.ts +0 -7
- package/dist/audit/lib/collect-assets.js +0 -64
- package/dist/audit/lib/validate-refs.d.ts +0 -37
- package/dist/audit/lib/validate-refs.js +0 -132
- package/dist/audit/output/json.d.ts +0 -7
- package/dist/audit/output/json.js +0 -3
- package/dist/audit/output/text.d.ts +0 -6
- package/dist/audit/output/text.js +0 -84
- package/dist/audit/registry.d.ts +0 -55
- package/dist/audit/registry.js +0 -62
- package/dist/audit/rules/seo/description-length.d.ts +0 -16
- package/dist/audit/rules/seo/description-length.js +0 -26
- package/dist/audit/rules/seo/description-missing.d.ts +0 -15
- package/dist/audit/rules/seo/description-missing.js +0 -24
- package/dist/audit/rules/seo/dist-meta.d.ts +0 -22
- package/dist/audit/rules/seo/dist-meta.js +0 -65
- package/dist/audit/rules/seo/h1-brand-keyword.d.ts +0 -2
- package/dist/audit/rules/seo/h1-brand-keyword.js +0 -99
- package/dist/audit/rules/seo/index.d.ts +0 -11
- package/dist/audit/rules/seo/index.js +0 -63
- package/dist/audit/rules/seo/json-ld-parse.d.ts +0 -13
- package/dist/audit/rules/seo/json-ld-parse.js +0 -32
- package/dist/audit/rules/seo/json-ld-required-fields.d.ts +0 -21
- package/dist/audit/rules/seo/json-ld-required-fields.js +0 -116
- package/dist/audit/rules/seo/keyword-placement.d.ts +0 -2
- package/dist/audit/rules/seo/keyword-placement.js +0 -122
- package/dist/audit/rules/seo/sitemap.d.ts +0 -5
- package/dist/audit/rules/seo/sitemap.js +0 -357
- package/dist/audit/rules/seo/title-missing.d.ts +0 -20
- package/dist/audit/rules/seo/title-missing.js +0 -33
- package/dist/audit/rules/structure/asset-refs.d.ts +0 -23
- package/dist/audit/rules/structure/asset-refs.js +0 -41
- package/dist/audit/rules/structure/internal-links.d.ts +0 -22
- package/dist/audit/rules/structure/internal-links.js +0 -28
- package/dist/audit/rules/structure/locale-coherence.d.ts +0 -17
- package/dist/audit/rules/structure/locale-coherence.js +0 -32
- package/dist/audit/rules/structure/namespace-coherence.d.ts +0 -17
- package/dist/audit/rules/structure/namespace-coherence.js +0 -39
- package/dist/audit/rules/structure/unresolved-directives.d.ts +0 -2
- package/dist/audit/rules/structure/unresolved-directives.js +0 -108
- package/dist/audit/rules/structure/version-coherence.d.ts +0 -25
- package/dist/audit/rules/structure/version-coherence.js +0 -42
- package/dist/audit/run.d.ts +0 -85
- package/dist/audit/run.js +0 -93
- package/dist/audit/types.d.ts +0 -203
- package/dist/audit/types.js +0 -1
- package/dist/commands/agent-run.js +0 -48
- package/dist/commands/agent.d.ts +0 -6
- package/dist/commands/agent.js +0 -305
- package/dist/commands/chunk.js +0 -121
- package/dist/commands/corpus.js +0 -63
- package/dist/commands/embed.js +0 -256
- package/dist/commands/enrich.js +0 -192
- package/dist/commands/index-build.js +0 -59
- package/dist/commands/pull.d.ts +0 -69
- package/dist/commands/pull.js +0 -838
- package/dist/commands/search.js +0 -144
- package/dist/commands/source.js +0 -777
- package/dist/corpus/agent-config.js +0 -212
- package/dist/corpus/bench.js +0 -694
- package/dist/corpus/classify-structured.js +0 -1132
- package/dist/corpus/classify.js +0 -331
- package/dist/corpus/comparison.js +0 -999
- package/dist/corpus/compile-check.js +0 -307
- package/dist/corpus/curate.js +0 -808
- package/dist/corpus/delta.js +0 -314
- package/dist/corpus/endpoints.js +0 -102
- package/dist/corpus/exchange.js +0 -189
- package/dist/corpus/feed.js +0 -176
- package/dist/corpus/git.js +0 -34
- package/dist/corpus/init.js +0 -135
- package/dist/corpus/judgment-cache.js +0 -109
- package/dist/corpus/near-duplicates.js +0 -192
- package/dist/corpus/ordering.js +0 -182
- package/dist/corpus/pack.js +0 -83
- package/dist/corpus/partition.js +0 -0
- package/dist/corpus/propose.js +0 -365
- package/dist/corpus/pull.js +0 -96
- package/dist/corpus/record-validation.js +0 -167
- package/dist/corpus/record.js +0 -194
- package/dist/corpus/review.js +0 -116
- package/dist/corpus/skill.js +0 -444
- package/dist/corpus/sources.js +0 -199
- package/dist/corpus/store.js +0 -203
- package/dist/corpus/update.js +0 -577
- package/dist/corpus/versions.js +0 -54
|
@@ -1,11 +1,2 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
* is a no-op.
|
|
4
|
-
*/
|
|
5
|
-
export declare function registerStructureRules(): void;
|
|
6
|
-
/**
|
|
7
|
-
* Test-only: reset the "registered" flag so unit tests can
|
|
8
|
-
* register fresh after `_clearRegistry()`. Production code never
|
|
9
|
-
* calls this.
|
|
10
|
-
*/
|
|
11
|
-
export declare function _resetStructureRegistration(): void;
|
|
1
|
+
/** Idempotent, like the shared register* functions. */
|
|
2
|
+
export declare function registerCliStructureRules(): void;
|
|
@@ -1,47 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* Rules landed in this category:
|
|
7
|
-
* - structure/namespace-coherence (corpus, multi-source axis)
|
|
8
|
-
*
|
|
9
|
-
* Future additions:
|
|
10
|
-
* - structure/orphan-pages (corpus; pages not reachable from nav)
|
|
11
|
-
* - structure/duplicate-slugs (corpus; two sources produced the same URL)
|
|
2
|
+
* The Astro CLI's own structure rules, registered on top of the shared
|
|
3
|
+
* ones from `@dogsbay/site-check`. Both read nav files and topic maps
|
|
4
|
+
* from disk, which dogsbay-ssg resolves differently.
|
|
12
5
|
*/
|
|
13
|
-
import { registerRule } from "
|
|
14
|
-
import { assetRefs } from "./asset-refs.js";
|
|
15
|
-
import { internalLinks } from "./internal-links.js";
|
|
16
|
-
import { localeCoherence } from "./locale-coherence.js";
|
|
17
|
-
import { namespaceCoherence } from "./namespace-coherence.js";
|
|
6
|
+
import { registerRule } from "@dogsbay/site-check";
|
|
18
7
|
import { navTargetExists } from "./nav-target-exists.js";
|
|
19
|
-
import { unresolvedDirectives } from "./unresolved-directives.js";
|
|
20
8
|
import { topicmapValid } from "./topicmap-valid.js";
|
|
21
|
-
import { versionCoherence } from "./version-coherence.js";
|
|
22
9
|
let registered = false;
|
|
23
|
-
/**
|
|
24
|
-
|
|
25
|
-
* is a no-op.
|
|
26
|
-
*/
|
|
27
|
-
export function registerStructureRules() {
|
|
10
|
+
/** Idempotent, like the shared register* functions. */
|
|
11
|
+
export function registerCliStructureRules() {
|
|
28
12
|
if (registered)
|
|
29
13
|
return;
|
|
30
14
|
registered = true;
|
|
31
|
-
registerRule(namespaceCoherence);
|
|
32
|
-
registerRule(versionCoherence);
|
|
33
|
-
registerRule(localeCoherence);
|
|
34
15
|
registerRule(navTargetExists);
|
|
35
|
-
registerRule(internalLinks);
|
|
36
|
-
registerRule(assetRefs);
|
|
37
|
-
registerRule(unresolvedDirectives);
|
|
38
16
|
registerRule(topicmapValid);
|
|
39
17
|
}
|
|
40
|
-
/**
|
|
41
|
-
* Test-only: reset the "registered" flag so unit tests can
|
|
42
|
-
* register fresh after `_clearRegistry()`. Production code never
|
|
43
|
-
* calls this.
|
|
44
|
-
*/
|
|
45
|
-
export function _resetStructureRegistration() {
|
|
46
|
-
registered = false;
|
|
47
|
-
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import type { Rule } from "
|
|
1
|
+
import type { Rule } from "@dogsbay/site-check";
|
|
2
2
|
export declare const navTargetExists: Rule;
|
|
@@ -8,5 +8,5 @@
|
|
|
8
8
|
* the errors to `AuditSourceInfo.topicmapErrors`; this rule just reports
|
|
9
9
|
* them with the standard exit-code/severity plumbing.
|
|
10
10
|
*/
|
|
11
|
-
import type { Rule } from "
|
|
11
|
+
import type { Rule } from "@dogsbay/site-check";
|
|
12
12
|
export declare const topicmapValid: Rule;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Realize `::child-links` on every page against the site's nav
|
|
3
|
+
* (plans/child-links.md). Shared by `site build` (after the nav is final) and
|
|
4
|
+
* `convert --to <anything but dogsbay-md>`; Dogsbay MD keeps the directive.
|
|
5
|
+
*/
|
|
6
|
+
import type { ExportPage, NavItem } from "@dogsbay/types";
|
|
7
|
+
export interface SiteChildLinksResult {
|
|
8
|
+
realized: number;
|
|
9
|
+
/** `page: folder` for every listing that found no child pages. */
|
|
10
|
+
empty: string[];
|
|
11
|
+
/** `page: folder` for listings answered from the page directory, not the nav. */
|
|
12
|
+
fromDirectory: string[];
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* @param prefix the href prefix the nav was built with (`""` for `convert`,
|
|
16
|
+
* urlBase + basePath for `site build`): a page's nav href is
|
|
17
|
+
* `prefix/slug`, and a `folder=` path is site-relative, so it is prefixed too.
|
|
18
|
+
*/
|
|
19
|
+
export declare function realizeSiteChildLinks(pages: ExportPage[], nav: NavItem[], prefix: string): SiteChildLinksResult;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { directoryChildren, hasChildLinks, navKey, realizeChildLinks } from "@dogsbay/serialize-core";
|
|
2
|
+
/**
|
|
3
|
+
* @param prefix the href prefix the nav was built with (`""` for `convert`,
|
|
4
|
+
* urlBase + basePath for `site build`): a page's nav href is
|
|
5
|
+
* `prefix/slug`, and a `folder=` path is site-relative, so it is prefixed too.
|
|
6
|
+
*/
|
|
7
|
+
export function realizeSiteChildLinks(pages, nav, prefix) {
|
|
8
|
+
const out = { realized: 0, empty: [], fromDirectory: [] };
|
|
9
|
+
if (!pages.some((p) => hasChildLinks(p.tree)))
|
|
10
|
+
return out;
|
|
11
|
+
const base = prefix.replace(/\/+$/, "");
|
|
12
|
+
const hrefOf = (slug) => `${base}/${slug.replace(/(^|\/)index$/, "")}`;
|
|
13
|
+
const info = new Map();
|
|
14
|
+
for (const page of pages) {
|
|
15
|
+
const { description, tags } = page.frontmatter ?? {};
|
|
16
|
+
info.set(navKey(hrefOf(page.slug)), {
|
|
17
|
+
title: page.title || undefined,
|
|
18
|
+
description: typeof description === "string" ? description : undefined,
|
|
19
|
+
tags: Array.isArray(tags) ? tags.map(String) : undefined,
|
|
20
|
+
// For `match=` (front-matter filters).
|
|
21
|
+
data: page.frontmatter,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
// Trees carry SITE-relative links: Astro adds the base path itself, and an
|
|
25
|
+
// export target (a vault, MkDocs) has none. The nav is built with it on.
|
|
26
|
+
const outputHref = (href) => base && (href === base || href.startsWith(`${base}/`)) ? href.slice(base.length) || "/" : href;
|
|
27
|
+
let fallbackChildren;
|
|
28
|
+
for (const page of pages) {
|
|
29
|
+
if (!hasChildLinks(page.tree))
|
|
30
|
+
continue;
|
|
31
|
+
// Fragments, partials (`_`-prefixed segments) and redirect stubs are not pages to list.
|
|
32
|
+
fallbackChildren ??= directoryChildren(pages.filter((p) => !p.fragment && !p.redirect && !p.slug.split("/").some((seg) => seg.startsWith("_"))), hrefOf);
|
|
33
|
+
const r = realizeChildLinks(page.tree, {
|
|
34
|
+
nav,
|
|
35
|
+
fallbackChildren,
|
|
36
|
+
outputHref,
|
|
37
|
+
pageHref: hrefOf(page.slug),
|
|
38
|
+
folderHref: (folder) => `${base}/${folder.replace(/^\/+/, "")}`,
|
|
39
|
+
lookup: (href) => info.get(navKey(href)),
|
|
40
|
+
});
|
|
41
|
+
out.realized += r.realized;
|
|
42
|
+
for (const e of r.empty)
|
|
43
|
+
out.empty.push(`${page.slug}: ${e}`);
|
|
44
|
+
for (const e of r.fromFallback)
|
|
45
|
+
out.fromDirectory.push(`${page.slug}: ${e}`);
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
}
|
package/dist/commands/convert.js
CHANGED
|
@@ -4,6 +4,7 @@ import YAML from "yaml";
|
|
|
4
4
|
import pc from "picocolors";
|
|
5
5
|
import { existsSync, realpathSync } from "node:fs";
|
|
6
6
|
import { realizeIncludes, hasIncludes } from "@dogsbay/serialize-core";
|
|
7
|
+
import { realizeSiteChildLinks } from "../child-links.js";
|
|
7
8
|
import { dogsbayMdToTree } from "@dogsbay/format-dogsbay-md";
|
|
8
9
|
import { FORMATS as formats, DOCUSAURUS_ADAPTERS as docusaurusAdapters, loadAdapter, MDX_ADAPTERS as mdxAdapters, GATSBY_ADAPTERS as gatsbyAdapters, STARLIGHT_ADAPTERS as starlightAdapters, detectFormat, importerNames, exporterNames, } from "../formats.js";
|
|
9
10
|
// Re-exported so tests and programmatic callers keep a stable import site.
|
|
@@ -606,6 +607,7 @@ export async function runConvert(source, options, log = console.log) {
|
|
|
606
607
|
["--distro", options.distro],
|
|
607
608
|
["--topic-map", options.topicMap],
|
|
608
609
|
["--loader", options.loader],
|
|
610
|
+
["--api-schema", options.apiSchema],
|
|
609
611
|
]
|
|
610
612
|
.filter(([flag, value]) => typeof value === "string" && !declared.has(flag) && !CONSUMED_BY_CONVERT.has(flag))
|
|
611
613
|
.map(([flag]) => flag);
|
|
@@ -632,6 +634,18 @@ export async function runConvert(source, options, log = console.log) {
|
|
|
632
634
|
// where the pages actually land.
|
|
633
635
|
if (options.hrefPrefix === undefined)
|
|
634
636
|
options.hrefPrefix = "";
|
|
637
|
+
// Template partials travel as `{% with %}{% include %}` TEXT that only a
|
|
638
|
+
// Dogsbay-MD build renders (as template pages). Any other target would show
|
|
639
|
+
// the directives as literal text, so refuse before importing anything.
|
|
640
|
+
if (options.partialMode !== undefined && !["inline", "include", "template"].includes(String(options.partialMode))) {
|
|
641
|
+
throw new ConvertError(`--partial-mode must be inline, include or template (got ${String(options.partialMode)})`, "");
|
|
642
|
+
}
|
|
643
|
+
if (options.partialMode === "template" && importer.name !== "starlight") {
|
|
644
|
+
throw new ConvertError(`--partial-mode template is supported for --from starlight (got --from ${importer.name})`, "Other importers keep their partials inline, or use --partial-mode include (Docusaurus).");
|
|
645
|
+
}
|
|
646
|
+
if (options.partialMode === "template" && exporter.name !== "dogsbay-md") {
|
|
647
|
+
throw new ConvertError(`--partial-mode template needs --to dogsbay-md (got --to ${exporter.name})`, "Template partials are rendered by a Dogsbay-MD build. For other targets use --partial-mode inline.");
|
|
648
|
+
}
|
|
635
649
|
const importResult = await importer.import(sourceDir, options);
|
|
636
650
|
const { pages, nav, contentRoot } = importResult;
|
|
637
651
|
// Mount the importer's asset directories so the `/_assets/…` refs it
|
|
@@ -660,6 +674,18 @@ export async function runConvert(source, options, log = console.log) {
|
|
|
660
674
|
// legitimately keep shared fragments above the content dir.
|
|
661
675
|
const roots = contentRoot ? [resolve(contentRoot), sourceDir] : [sourceDir];
|
|
662
676
|
includeLoss = realizePageIncludes(pages, roots, log, options.partialMode === "include");
|
|
677
|
+
// `::child-links` → the listing, from the importer's nav. Dogsbay MD keeps
|
|
678
|
+
// the directive (the next build lists pages added since).
|
|
679
|
+
const links = realizeSiteChildLinks(pages, nav, String(options.hrefPrefix ?? ""));
|
|
680
|
+
if (links.realized) {
|
|
681
|
+
log(` Child links: ${links.realized} listing(s) realized from the nav` +
|
|
682
|
+
(links.fromDirectory.length ? ` (${links.fromDirectory.length} from the page directory: not in the nav)` : ""));
|
|
683
|
+
}
|
|
684
|
+
if (links.empty.length) {
|
|
685
|
+
log(pc.yellow(` Warning: ${links.empty.length} child-links listing(s) found no child pages:`));
|
|
686
|
+
for (const e of links.empty.slice(0, 10))
|
|
687
|
+
log(pc.yellow(` ${e}`));
|
|
688
|
+
}
|
|
663
689
|
}
|
|
664
690
|
const output = resolve(options.output || `${source}-${exporter.name}`);
|
|
665
691
|
await exporter.export(pages, nav, output, options);
|
|
@@ -95,7 +95,7 @@ export async function migrateMkdocs(source, options) {
|
|
|
95
95
|
console.log(`Output: ${outputDir}`);
|
|
96
96
|
console.log();
|
|
97
97
|
// 1. Output skeleton. The canonical Dogsbay layout (see
|
|
98
|
-
// packages/cli/skills/platform/migration-shape/SKILL.md) puts
|
|
98
|
+
// packages/cli/skills/platform/dogsbay-migration-shape/SKILL.md) puts
|
|
99
99
|
// `content/` and `dogsbay.config.yml` at the root; the
|
|
100
100
|
// generated Astro project lives under `./astro/` (created by
|
|
101
101
|
// emitSiteScaffold below).
|
|
@@ -122,7 +122,7 @@ export async function migrateMkdocs(source, options) {
|
|
|
122
122
|
console.log(pc.green(`Wrote`) + ` ${pageCount} pages to ./content/ as Dogsbay-MD`);
|
|
123
123
|
// 3. Convert MkDocs nav: → ./content/nav.yml in canonical
|
|
124
124
|
// single-key-map shape (- Label: file.md / nested children).
|
|
125
|
-
// See packages/cli/skills/platform/nav-file/SKILL.md.
|
|
125
|
+
// See packages/cli/skills/platform/dogsbay-nav-file/SKILL.md.
|
|
126
126
|
// Do NOT emit nav.json — runtime loader prefers it and would
|
|
127
127
|
// silently override the human-edited yml. The loader auto-
|
|
128
128
|
// detects nav.yml at the content root.
|
|
@@ -305,7 +305,7 @@ async function collectAndWriteContent(sourceDir, fullDocsDir, outputDir, mkdocsC
|
|
|
305
305
|
// Convert `.md` links to root-relative slugs at parse time.
|
|
306
306
|
// baseUrl: "" gives `/foo` / `/tutorial/quickstart` form (no
|
|
307
307
|
// basePath prefix — site build adds /docs at render time).
|
|
308
|
-
// See packages/cli/skills/platform/migration-shape/SKILL.md
|
|
308
|
+
// See packages/cli/skills/platform/dogsbay-migration-shape/SKILL.md
|
|
309
309
|
// → "Internal links — root-relative absolute slugs".
|
|
310
310
|
linkRewrite: { baseUrl: "" },
|
|
311
311
|
// fileInclude: `{* ../../docs_src/foo.py *}` in MkDocs sources
|
|
@@ -388,7 +388,7 @@ async function collectAndWriteContent(sourceDir, fullDocsDir, outputDir, mkdocsC
|
|
|
388
388
|
// resolved relative paths to root-relative `/img/...` form;
|
|
389
389
|
// this walker just adds the `_assets/` prefix so the refs line
|
|
390
390
|
// up with where copyAssets deposits files. See
|
|
391
|
-
// packages/cli/skills/platform/migration-shape/SKILL.md
|
|
391
|
+
// packages/cli/skills/platform/dogsbay-migration-shape/SKILL.md
|
|
392
392
|
// → "Asset folder".
|
|
393
393
|
rewriteImageRefsToAssets(tree);
|
|
394
394
|
// No frontmatter title lift in Phase 3: format-mkdocs's
|
|
@@ -889,7 +889,7 @@ function copyAssets(srcDocsDir, destPublicDir) {
|
|
|
889
889
|
*
|
|
890
890
|
* NOT the `{ label: "...", file: "..." }` shape — the runtime
|
|
891
891
|
* validator throws on multi-key entries. See
|
|
892
|
-
* packages/cli/skills/platform/nav-file/SKILL.md.
|
|
892
|
+
* packages/cli/skills/platform/dogsbay-nav-file/SKILL.md.
|
|
893
893
|
*/
|
|
894
894
|
function convertNav(nav) {
|
|
895
895
|
if (!Array.isArray(nav))
|
|
@@ -5,5 +5,24 @@ export interface MigrateMystOptions {
|
|
|
5
5
|
siteUrl?: string;
|
|
6
6
|
local?: boolean;
|
|
7
7
|
quiet?: boolean;
|
|
8
|
+
/** "sphinx" | "translate"; auto when omitted (see chooseMystLoader). */
|
|
9
|
+
loader?: string;
|
|
8
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* Which way to import a MyST project. A Sphinx project (conf.py) built by
|
|
13
|
+
* Sphinx resolves what the translator cannot see from one page at a time:
|
|
14
|
+
* `{ref}` to a label on another page, `{term}` to the glossary, extlinks
|
|
15
|
+
* roles, toctrees across documents. Measured on Ubuntu Server (255 MyST
|
|
16
|
+
* pages): 4 link errors through Sphinx against 657 translated. The
|
|
17
|
+
* translator stays the path for mystmd / Jupyter Book (no conf.py), for a
|
|
18
|
+
* machine without Sphinx, and for a repository whose code should not run.
|
|
19
|
+
*/
|
|
20
|
+
export declare function chooseMystLoader(sourceDir: string, requested: string | undefined, has: {
|
|
21
|
+
sphinxSrc: boolean;
|
|
22
|
+
sphinx: boolean;
|
|
23
|
+
}): {
|
|
24
|
+
loader: "sphinx" | "translate";
|
|
25
|
+
note?: string;
|
|
26
|
+
error?: string;
|
|
27
|
+
};
|
|
9
28
|
export declare function migrateMyst(source: string, options: MigrateMystOptions): Promise<void>;
|
|
@@ -15,12 +15,48 @@
|
|
|
15
15
|
* Pure Markdown — no external runtime needed. Mirrors migrate-rst /
|
|
16
16
|
* migrate-gitbook in shape.
|
|
17
17
|
*/
|
|
18
|
-
import {
|
|
18
|
+
import { existsSync, mkdirSync, rmSync, statSync, writeFileSync, } from "node:fs";
|
|
19
19
|
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
20
20
|
import pc from "picocolors";
|
|
21
21
|
import { stringify as stringifyYaml } from "yaml";
|
|
22
22
|
import { emitSiteScaffold } from "@dogsbay/format-astro";
|
|
23
23
|
import { importMyst } from "@dogsbay/format-myst/cli";
|
|
24
|
+
import { findSphinxSrc, pythonHasModule, sphinxAvailable } from "@dogsbay/format-rst";
|
|
25
|
+
import { migrateRst, mirrorAssets } from "./migrate-rst.js";
|
|
26
|
+
import { copyOpenApiSpecs } from "../resolve-openapi.js";
|
|
27
|
+
/**
|
|
28
|
+
* Which way to import a MyST project. A Sphinx project (conf.py) built by
|
|
29
|
+
* Sphinx resolves what the translator cannot see from one page at a time:
|
|
30
|
+
* `{ref}` to a label on another page, `{term}` to the glossary, extlinks
|
|
31
|
+
* roles, toctrees across documents. Measured on Ubuntu Server (255 MyST
|
|
32
|
+
* pages): 4 link errors through Sphinx against 657 translated. The
|
|
33
|
+
* translator stays the path for mystmd / Jupyter Book (no conf.py), for a
|
|
34
|
+
* machine without Sphinx, and for a repository whose code should not run.
|
|
35
|
+
*/
|
|
36
|
+
export function chooseMystLoader(sourceDir, requested, has) {
|
|
37
|
+
if (requested && requested !== "sphinx" && requested !== "translate") {
|
|
38
|
+
return { loader: "translate", error: `--loader must be 'sphinx' or 'translate', not '${requested}'` };
|
|
39
|
+
}
|
|
40
|
+
if (requested === "translate")
|
|
41
|
+
return { loader: "translate" };
|
|
42
|
+
if (requested === "sphinx") {
|
|
43
|
+
if (!has.sphinxSrc)
|
|
44
|
+
return { loader: "translate", error: `--loader sphinx needs a conf.py under ${sourceDir}` };
|
|
45
|
+
if (!has.sphinx)
|
|
46
|
+
return { loader: "translate", error: "--loader sphinx needs Sphinx (and the project's extensions) for the Python Dogsbay uses (DOGSBAY_PYTHON)" };
|
|
47
|
+
return { loader: "sphinx" };
|
|
48
|
+
}
|
|
49
|
+
if (has.sphinxSrc && has.sphinx) {
|
|
50
|
+
return { loader: "sphinx", note: "a Sphinx project (conf.py): building it with Sphinx. Use --loader translate to translate the MyST without running project code." };
|
|
51
|
+
}
|
|
52
|
+
if (has.sphinxSrc) {
|
|
53
|
+
return {
|
|
54
|
+
loader: "translate",
|
|
55
|
+
note: "a Sphinx project (conf.py), but Sphinx with myst-parser is not installed for DOGSBAY_PYTHON: translating the MyST directly. Cross-page {ref} labels, {term} and extlinks roles will not resolve; install the project's docs requirements for a Sphinx build.",
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return { loader: "translate" };
|
|
59
|
+
}
|
|
24
60
|
function yamlQuote(s) {
|
|
25
61
|
return /[:#&*!|>%@`,'"[\]{}]|^\s|\s$/.test(s) ? JSON.stringify(s) : s;
|
|
26
62
|
}
|
|
@@ -65,68 +101,43 @@ function buildConfig(siteName, siteUrl) {
|
|
|
65
101
|
function frontmatterBlock(title) {
|
|
66
102
|
return `---\n${stringifyYaml({ title }).replace(/\n$/, "")}\n---\n\n`;
|
|
67
103
|
}
|
|
68
|
-
/**
|
|
69
|
-
* Real page/doc assets — an allowlist (not a blocklist) so a MyST project
|
|
70
|
-
* tree's `.py` config helpers, `conf.py`, `requirements.txt`, etc. don't get
|
|
71
|
-
* swept into the migrated content/.
|
|
72
|
-
*/
|
|
73
|
-
const ASSET_EXTS = new Set([
|
|
74
|
-
".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg", ".ico", ".bmp",
|
|
75
|
-
".pdf", ".css", ".js", ".mjs", ".json", ".woff", ".woff2", ".ttf", ".otf",
|
|
76
|
-
".mp4", ".webm", ".mov", ".txt", ".csv", ".zip", ".tar", ".gz",
|
|
77
|
-
]);
|
|
78
|
-
const SKIP_DIRS = new Set(["node_modules", ".git", "_build", "_templates", "__pycache__"]);
|
|
79
|
-
/** Mirror asset files (by extension allowlist) from the docs root into content/. */
|
|
80
|
-
function mirrorAssets(root, contentDir) {
|
|
81
|
-
let count = 0;
|
|
82
|
-
const walk = (dir) => {
|
|
83
|
-
let entries;
|
|
84
|
-
try {
|
|
85
|
-
entries = readdirSync(dir);
|
|
86
|
-
}
|
|
87
|
-
catch {
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
for (const name of entries) {
|
|
91
|
-
if (SKIP_DIRS.has(name))
|
|
92
|
-
continue;
|
|
93
|
-
const full = join(dir, name);
|
|
94
|
-
let st;
|
|
95
|
-
try {
|
|
96
|
-
st = statSync(full);
|
|
97
|
-
}
|
|
98
|
-
catch {
|
|
99
|
-
continue;
|
|
100
|
-
}
|
|
101
|
-
if (st.isDirectory()) {
|
|
102
|
-
if (name.startsWith("."))
|
|
103
|
-
continue;
|
|
104
|
-
walk(full);
|
|
105
|
-
continue;
|
|
106
|
-
}
|
|
107
|
-
const ext = name.slice(name.lastIndexOf(".")).toLowerCase();
|
|
108
|
-
if (!ASSET_EXTS.has(ext))
|
|
109
|
-
continue; // skip .md, .py, conf.py, etc.
|
|
110
|
-
const dest = join(contentDir, relative(root, full));
|
|
111
|
-
try {
|
|
112
|
-
mkdirSync(dirname(dest), { recursive: true });
|
|
113
|
-
copyFileSync(full, dest);
|
|
114
|
-
count += 1;
|
|
115
|
-
}
|
|
116
|
-
catch {
|
|
117
|
-
/* ignore individual asset failures */
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
};
|
|
121
|
-
walk(root);
|
|
122
|
-
return count;
|
|
123
|
-
}
|
|
124
104
|
export async function migrateMyst(source, options) {
|
|
125
105
|
const sourceDir = resolve(source);
|
|
126
106
|
if (!existsSync(sourceDir) || !statSync(sourceDir).isDirectory()) {
|
|
127
107
|
console.log(pc.red(`Error: source must be a directory: ${sourceDir}`));
|
|
128
108
|
process.exit(1);
|
|
129
109
|
}
|
|
110
|
+
const choice = chooseMystLoader(sourceDir, options.loader, {
|
|
111
|
+
sphinxSrc: findSphinxSrc(sourceDir) !== null,
|
|
112
|
+
// Sphinx alone cannot read MyST: myst-parser must be there too, or the
|
|
113
|
+
// build fails on a machine that has Sphinx for some other project.
|
|
114
|
+
sphinx: options.loader === "translate" ? false : sphinxAvailable() && pythonHasModule("myst_parser"),
|
|
115
|
+
});
|
|
116
|
+
if (choice.error) {
|
|
117
|
+
console.log(pc.red(`Error: ${choice.error}`));
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
if (choice.note)
|
|
121
|
+
console.log(pc.yellow(`myst: ${choice.note}`));
|
|
122
|
+
if (choice.loader === "sphinx") {
|
|
123
|
+
// The same pipeline as migrate-rst: Sphinx builds the MyST pages through
|
|
124
|
+
// myst-parser, and its XML is converted (toctree nav, labels, glossary,
|
|
125
|
+
// redirects). It prints the trust warning about running conf.py.
|
|
126
|
+
try {
|
|
127
|
+
return await migrateRst(source, { ...options, loader: "sphinx", corpusLabel: "MyST (Sphinx)", throwOnImportError: true });
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
131
|
+
// Asked for explicitly: a failure is the answer.
|
|
132
|
+
if (options.loader === "sphinx") {
|
|
133
|
+
console.log(pc.red(`Error: ${msg}`));
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
// Chosen automatically: the project's extensions may simply not be
|
|
137
|
+
// installed. Translate instead, and say so.
|
|
138
|
+
console.log(pc.yellow(`myst: the Sphinx build failed; translating the MyST directly instead.\n ${msg.split("\n")[1] ?? msg}`));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
130
141
|
const siteName = options.siteName?.trim() || prettify(basename(sourceDir));
|
|
131
142
|
const outputDir = resolve(options.output || `${basename(sourceDir)}-dogsbay`);
|
|
132
143
|
const existingConfig = ["yml", "yaml", "json"]
|
|
@@ -146,9 +157,9 @@ export async function migrateMyst(source, options) {
|
|
|
146
157
|
console.log(`Source: ${sourceDir}`);
|
|
147
158
|
console.log(`Output: ${outputDir}`);
|
|
148
159
|
console.log();
|
|
149
|
-
let pages, nav, docsDir, navSource;
|
|
160
|
+
let pages, nav, docsDir, navSource, excludeAsset;
|
|
150
161
|
try {
|
|
151
|
-
({ pages, nav, docsDir, navSource } = await importMyst(sourceDir, {}));
|
|
162
|
+
({ pages, nav, docsDir, navSource, excludeAsset } = await importMyst(sourceDir, {}));
|
|
152
163
|
}
|
|
153
164
|
catch (err) {
|
|
154
165
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -184,9 +195,15 @@ export async function migrateMyst(source, options) {
|
|
|
184
195
|
console.log(pc.green(`Converted`) +
|
|
185
196
|
` ${pages.length} page(s)` +
|
|
186
197
|
(collisions ? pc.yellow(` (${collisions} collision(s) skipped)`) : ""));
|
|
187
|
-
|
|
198
|
+
// migrate-rst's mirror: links are not followed (one in someone else's
|
|
199
|
+
// repo can name any file on this machine), and what conf.py excludes stays
|
|
200
|
+
// out.
|
|
201
|
+
const assetCount = mirrorAssets(docsDir, contentDir, excludeAsset);
|
|
188
202
|
if (assetCount > 0)
|
|
189
203
|
console.log(pc.green(`Mirrored`) + ` ${assetCount} asset(s)`);
|
|
204
|
+
const specCount = copyOpenApiSpecs(pages, docsDir, contentDir);
|
|
205
|
+
if (specCount > 0)
|
|
206
|
+
console.log(pc.green(`Copied`) + ` ${specCount} OpenAPI spec(s) the API reference pages embed`);
|
|
190
207
|
writeFileSync(join(contentDir, "nav.yml"), `# Migrated by dogsbay migrate-myst.\n# Edit freely.\n\n${navToYaml(nav)}\n`);
|
|
191
208
|
console.log(pc.green(`Wrote`) + ` content/nav.yml`);
|
|
192
209
|
writeFileSync(join(outputDir, "dogsbay.config.yml"), buildConfig(siteName, options.siteUrl));
|
|
@@ -7,5 +7,12 @@ export interface MigrateRstOptions {
|
|
|
7
7
|
loader?: string;
|
|
8
8
|
local?: boolean;
|
|
9
9
|
quiet?: boolean;
|
|
10
|
+
/** What the source is called in output; migrate-myst passes "MyST (Sphinx)". */
|
|
11
|
+
corpusLabel?: string;
|
|
12
|
+
/** Throw an import failure instead of exiting, so the caller can fall back. */
|
|
13
|
+
throwOnImportError?: boolean;
|
|
10
14
|
}
|
|
15
|
+
/** Mirror asset files (by extension allowlist) from the docs root into
|
|
16
|
+
* content/, skipping what `exclude` names (a root-relative path). */
|
|
17
|
+
export declare function mirrorAssets(root: string, contentDir: string, exclude?: (rel: string) => boolean): number;
|
|
11
18
|
export declare function migrateRst(source: string, options: MigrateRstOptions): Promise<void>;
|