dogsbay 0.2.0-beta.117 → 0.2.0-beta.119
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 +29 -2
- package/dist/resolve-autodoc.js +7 -4
- package/dist/resolve-openapi.d.ts +4 -1
- package/dist/resolve-openapi.js +33 -23
- package/package.json +29 -29
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
* Source-of-truth becomes `./content/*.md`; the GitBook source can be
|
|
21
21
|
* deleted. Mirrors migrate-asciidoc / migrate-mkdocs in shape.
|
|
22
22
|
*/
|
|
23
|
-
import { copyFileSync, existsSync, mkdirSync, readdirSync, rmSync, statSync, writeFileSync, } from "node:fs";
|
|
24
|
-
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
23
|
+
import { copyFileSync, existsSync, mkdirSync, readdirSync, rmSync, realpathSync, statSync, writeFileSync, } from "node:fs";
|
|
24
|
+
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
25
25
|
import pc from "picocolors";
|
|
26
26
|
import { stringify as stringifyYaml } from "yaml";
|
|
27
27
|
import { emitSiteScaffold } from "@dogsbay/format-astro";
|
|
@@ -104,6 +104,27 @@ function mirrorAssets(roots, contentDir) {
|
|
|
104
104
|
let count = 0;
|
|
105
105
|
for (const root of roots) {
|
|
106
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]);
|
|
107
128
|
const walk = (dir) => {
|
|
108
129
|
let entries;
|
|
109
130
|
try {
|
|
@@ -118,6 +139,8 @@ function mirrorAssets(roots, contentDir) {
|
|
|
118
139
|
const full = join(dir, name);
|
|
119
140
|
let st;
|
|
120
141
|
try {
|
|
142
|
+
if (!inside(full))
|
|
143
|
+
continue;
|
|
121
144
|
st = statSync(full);
|
|
122
145
|
}
|
|
123
146
|
catch {
|
|
@@ -127,6 +150,10 @@ function mirrorAssets(roots, contentDir) {
|
|
|
127
150
|
// Skip hidden dirs EXCEPT `.gitbook` (holds assets + includes).
|
|
128
151
|
if (name.startsWith(".") && name !== ".gitbook")
|
|
129
152
|
continue;
|
|
153
|
+
const real = realpathSync(full);
|
|
154
|
+
if (seen.has(real))
|
|
155
|
+
continue;
|
|
156
|
+
seen.add(real);
|
|
130
157
|
walk(full);
|
|
131
158
|
continue;
|
|
132
159
|
}
|
package/dist/resolve-autodoc.js
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
import { resolve as resolvePath } from "node:path";
|
|
23
23
|
import MarkdownIt from "markdown-it";
|
|
24
24
|
import pc from "picocolors";
|
|
25
|
+
import { apiSymbolLevels } from "@dogsbay/serialize-core";
|
|
25
26
|
import { resolveIdentifier, symbolToTreeNode, } from "@dogsbay/autodoc-python";
|
|
26
27
|
/**
|
|
27
28
|
* Default render options for autodoc directives. Mirrors
|
|
@@ -127,9 +128,9 @@ function treeHasApiHeading(node) {
|
|
|
127
128
|
}
|
|
128
129
|
/**
|
|
129
130
|
* Walk the page's TreeNode tree for api-class / api-member nodes
|
|
130
|
-
* and append a Heading entry for each
|
|
131
|
-
*
|
|
132
|
-
*
|
|
131
|
+
* and append a Heading entry for each, at the level the page renders it
|
|
132
|
+
* (`apiSymbolLevels` in @dogsbay/serialize-core: below the last heading
|
|
133
|
+
* before it, nested symbols below their parent). The
|
|
133
134
|
* `kind` prop is preserved on the heading so the TOC component
|
|
134
135
|
* can render type-colored badges (class blue, method green, etc.).
|
|
135
136
|
*
|
|
@@ -142,12 +143,14 @@ function appendApiHeadings(page) {
|
|
|
142
143
|
seen.set(h.slug, (seen.get(h.slug) ?? 0) + 1);
|
|
143
144
|
}
|
|
144
145
|
const headings = [];
|
|
146
|
+
const levels = apiSymbolLevels(page.tree);
|
|
145
147
|
function walk(nodes) {
|
|
146
148
|
for (const node of nodes) {
|
|
147
149
|
if (node.type === "api-class" || node.type === "api-member") {
|
|
148
150
|
const name = node.props?.name || "";
|
|
149
151
|
const kind = node.props?.kind || "";
|
|
150
|
-
|
|
152
|
+
// The level the page renders the symbol at (apiSymbolLevels), so the TOC nests as the page does.
|
|
153
|
+
const depth = levels.get(node) ?? (node.type === "api-class" ? 2 : 3);
|
|
151
154
|
const baseSlug = slugify(name);
|
|
152
155
|
const count = seen.get(baseSlug) ?? 0;
|
|
153
156
|
const slug = count === 0 ? baseSlug : `${baseSlug}-${count}`;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import type { ExportPage } from "@dogsbay/types";
|
|
2
2
|
/**
|
|
3
|
-
* The spec file a directive names, or an error.
|
|
3
|
+
* The spec file a directive names, or an error. The rules are format-openapi's
|
|
4
|
+
* `specCandidates` (shared with dogsbay-ssg); this tries each candidate in each
|
|
5
|
+
* root, the page's own source first, and holds the file inside that root once
|
|
6
|
+
* symlinks are resolved. Exported for tests.
|
|
4
7
|
*/
|
|
5
8
|
export declare function findOpenApiSpec(spec: string, slug: string, roots: readonly string[]): {
|
|
6
9
|
path: string;
|
package/dist/resolve-openapi.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Build-time resolver for the `:::openapi` directive
|
|
2
|
+
* Build-time resolver for the `:::openapi` directive (the Node wrapper over
|
|
3
|
+
* `@dogsbay/format-openapi/core`, which dogsbay-ssg runs in a Worker).
|
|
3
4
|
*
|
|
4
5
|
* `:::openapi{spec="/reference/api/ams-api.yaml"}` is an OpenAPI (or Swagger
|
|
5
6
|
* 2.0) spec embedded in a page: what a Swagger UI or Redoc `<script>` embed
|
|
@@ -13,6 +14,10 @@
|
|
|
13
14
|
* - inside a content root once symlinks are resolved;
|
|
14
15
|
* - `.yaml`, `.yml` or `.json`.
|
|
15
16
|
*
|
|
17
|
+
* `path` and `method` together select one operation instead of the whole
|
|
18
|
+
* spec: `:::openapi{spec="api.yaml" path="/pets" method="get"}`, what a GitBook
|
|
19
|
+
* `{% openapi %}` block shows (migrate-gitbook writes it).
|
|
20
|
+
*
|
|
16
21
|
* A leading `/` is the content root. A relative path is tried against the
|
|
17
22
|
* page's folder, then the page's own folder for an index page (slug
|
|
18
23
|
* `reference` may be `reference/index.md`). Importers write root-relative
|
|
@@ -21,29 +26,26 @@
|
|
|
21
26
|
* A spec that cannot be used logs a warning and leaves the placeholder, whose
|
|
22
27
|
* fallback body (if any) renders, mirroring resolve-autodoc.ts.
|
|
23
28
|
*
|
|
24
|
-
*
|
|
29
|
+
* A spec is parsed once per build (`createSpecCache`); each embed builds its
|
|
30
|
+
* nodes from it, with ids that avoid the ones its page already uses.
|
|
25
31
|
*/
|
|
26
32
|
import { copyFileSync, existsSync, mkdirSync, realpathSync, statSync } from "node:fs";
|
|
27
|
-
import {
|
|
33
|
+
import { readFile } from "node:fs/promises";
|
|
34
|
+
import { dirname, join, sep } from "node:path";
|
|
28
35
|
import pc from "picocolors";
|
|
36
|
+
import { specCandidates } from "@dogsbay/format-openapi/spec-path";
|
|
29
37
|
const SPEC_EXT = /\.(ya?ml|json)$/i;
|
|
30
38
|
/**
|
|
31
|
-
* The spec file a directive names, or an error.
|
|
39
|
+
* The spec file a directive names, or an error. The rules are format-openapi's
|
|
40
|
+
* `specCandidates` (shared with dogsbay-ssg); this tries each candidate in each
|
|
41
|
+
* root, the page's own source first, and holds the file inside that root once
|
|
42
|
+
* symlinks are resolved. Exported for tests.
|
|
32
43
|
*/
|
|
33
44
|
export function findOpenApiSpec(spec, slug, roots) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
if (/[?#]/.test(spec) || !SPEC_EXT.test(spec)) {
|
|
40
|
-
return { error: `not a .yaml, .yml or .json file (${spec})` };
|
|
41
|
-
}
|
|
42
|
-
const clean = spec.replace(/\\/g, "/");
|
|
43
|
-
const rels = clean.startsWith("/")
|
|
44
|
-
? [clean.replace(/^\/+/, "")]
|
|
45
|
-
: [posix.join(posix.dirname(slug), clean), posix.join(slug, clean)];
|
|
46
|
-
let escaped = false;
|
|
45
|
+
const c = specCandidates(spec, slug);
|
|
46
|
+
if ("error" in c)
|
|
47
|
+
return c;
|
|
48
|
+
let escaped = c.paths.length === 0;
|
|
47
49
|
for (const root of roots) {
|
|
48
50
|
let realRoot;
|
|
49
51
|
try {
|
|
@@ -52,7 +54,7 @@ export function findOpenApiSpec(spec, slug, roots) {
|
|
|
52
54
|
catch {
|
|
53
55
|
continue;
|
|
54
56
|
}
|
|
55
|
-
for (const rel of
|
|
57
|
+
for (const rel of c.paths) {
|
|
56
58
|
const candidate = join(root, rel);
|
|
57
59
|
if (!existsSync(candidate))
|
|
58
60
|
continue;
|
|
@@ -81,7 +83,10 @@ export function findOpenApiSpec(spec, slug, roots) {
|
|
|
81
83
|
* mutated in place; tag sections are added to the page's headings.
|
|
82
84
|
*/
|
|
83
85
|
export async function resolveOpenApiRefs(pages, options) {
|
|
84
|
-
const {
|
|
86
|
+
const { embedSpec, createSpecCache, directiveOperation } = await import("@dogsbay/format-openapi/core");
|
|
87
|
+
const { parse: parseYaml } = await import("yaml");
|
|
88
|
+
// Each spec is parsed and dereferenced once, however many pages embed it.
|
|
89
|
+
const specs = createSpecCache((path) => readFile(path, "utf-8"), parseYaml);
|
|
85
90
|
let resolved = 0;
|
|
86
91
|
let failed = 0;
|
|
87
92
|
let operations = 0;
|
|
@@ -106,16 +111,21 @@ export async function resolveOpenApiRefs(pages, options) {
|
|
|
106
111
|
// A relative spec resolves against the page as authored, before any
|
|
107
112
|
// version/namespace prefix was put on its slug.
|
|
108
113
|
const slug = page.multiSource?.originalSlug ?? page.slug;
|
|
109
|
-
|
|
114
|
+
// `path` + `method` select one operation (GitBook's `{% openapi %}` block).
|
|
115
|
+
const selected = directiveOperation(node.props);
|
|
116
|
+
const found = "error" in selected ? selected : findOpenApiSpec(spec, slug, options.rootsFor(page));
|
|
110
117
|
if ("error" in found) {
|
|
111
118
|
failed++;
|
|
112
119
|
console.warn(pc.yellow(` Warning: :::openapi on ${page.slug}: ${found.error}`));
|
|
113
120
|
continue;
|
|
114
121
|
}
|
|
115
122
|
try {
|
|
116
|
-
// Parsed per embed
|
|
123
|
+
// Parsed once per build; the nodes are built per embed, with ids that avoid this page's.
|
|
117
124
|
const headings = page.headings ?? [];
|
|
118
|
-
const embed = await
|
|
125
|
+
const embed = embedSpec(await specs.get(found.path), {
|
|
126
|
+
usedIds: headings.map((h) => h.slug),
|
|
127
|
+
operation: "operation" in selected ? selected.operation : undefined,
|
|
128
|
+
});
|
|
119
129
|
nodes.splice(i, 1, ...embed.tree);
|
|
120
130
|
i += embed.tree.length - 1;
|
|
121
131
|
const at = Math.min(headingsBefore + inserted, headings.length);
|
|
@@ -126,7 +136,7 @@ export async function resolveOpenApiRefs(pages, options) {
|
|
|
126
136
|
}
|
|
127
137
|
catch (err) {
|
|
128
138
|
failed++;
|
|
129
|
-
console.warn(pc.yellow(` Warning: :::openapi on ${page.slug}: cannot
|
|
139
|
+
console.warn(pc.yellow(` Warning: :::openapi on ${page.slug}: cannot use ${spec}: ${err.message}`));
|
|
130
140
|
}
|
|
131
141
|
}
|
|
132
142
|
};
|
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.119",
|
|
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,34 +43,34 @@
|
|
|
43
43
|
"typescript": "^5.9.3",
|
|
44
44
|
"valibot": "1.1.0",
|
|
45
45
|
"yaml": "^2.9.0",
|
|
46
|
-
"@dogsbay/
|
|
47
|
-
"@dogsbay/
|
|
48
|
-
"@dogsbay/
|
|
49
|
-
"@dogsbay/
|
|
50
|
-
"@dogsbay/format-
|
|
51
|
-
"@dogsbay/format-dita": "0.2.0-beta.
|
|
52
|
-
"@dogsbay/format-docusaurus": "0.2.0-beta.
|
|
53
|
-
"@dogsbay/format-dogsbay-md": "0.2.0-beta.
|
|
54
|
-
"@dogsbay/format-fluidtopics-markdown": "0.2.0-beta.
|
|
55
|
-
"@dogsbay/format-fluidtopics-rest": "0.2.0-beta.
|
|
56
|
-
"@dogsbay/format-gatsby": "0.2.0-beta.
|
|
57
|
-
"@dogsbay/format-gitbook": "0.2.0-beta.
|
|
58
|
-
"@dogsbay/format-hugo": "0.2.0-beta.
|
|
59
|
-
"@dogsbay/format-mdx": "0.2.0-beta.
|
|
60
|
-
"@dogsbay/format-mkdocs": "0.2.0-beta.
|
|
61
|
-
"@dogsbay/format-myst": "0.2.0-beta.
|
|
62
|
-
"@dogsbay/format-obsidian": "0.2.0-beta.
|
|
63
|
-
"@dogsbay/format-openapi": "0.2.0-beta.
|
|
64
|
-
"@dogsbay/format-openknowledge": "0.2.0-beta.
|
|
65
|
-
"@dogsbay/format-rst": "0.2.0-beta.
|
|
66
|
-
"@dogsbay/format-starlight": "0.2.0-beta.
|
|
67
|
-
"@dogsbay/format-topicmap": "0.2.0-beta.
|
|
68
|
-
"@dogsbay/format-vitepress": "0.2.0-beta.
|
|
69
|
-
"@dogsbay/minja": "0.2.0-beta.
|
|
70
|
-
"@dogsbay/render": "0.2.0-beta.
|
|
71
|
-
"@dogsbay/serialize-core": "0.2.0-beta.
|
|
72
|
-
"@dogsbay/site-check": "0.2.0-beta.
|
|
73
|
-
"@dogsbay/types": "0.2.0-beta.
|
|
46
|
+
"@dogsbay/adoc2md-modular": "0.2.0-beta.119",
|
|
47
|
+
"@dogsbay/commands": "0.2.0-beta.119",
|
|
48
|
+
"@dogsbay/format-asciidoc": "0.2.0-beta.119",
|
|
49
|
+
"@dogsbay/autodoc-python": "0.2.0-beta.119",
|
|
50
|
+
"@dogsbay/format-astro": "0.2.0-beta.119",
|
|
51
|
+
"@dogsbay/format-dita": "0.2.0-beta.119",
|
|
52
|
+
"@dogsbay/format-docusaurus": "0.2.0-beta.119",
|
|
53
|
+
"@dogsbay/format-dogsbay-md": "0.2.0-beta.119",
|
|
54
|
+
"@dogsbay/format-fluidtopics-markdown": "0.2.0-beta.119",
|
|
55
|
+
"@dogsbay/format-fluidtopics-rest": "0.2.0-beta.119",
|
|
56
|
+
"@dogsbay/format-gatsby": "0.2.0-beta.119",
|
|
57
|
+
"@dogsbay/format-gitbook": "0.2.0-beta.119",
|
|
58
|
+
"@dogsbay/format-hugo": "0.2.0-beta.119",
|
|
59
|
+
"@dogsbay/format-mdx": "0.2.0-beta.119",
|
|
60
|
+
"@dogsbay/format-mkdocs": "0.2.0-beta.119",
|
|
61
|
+
"@dogsbay/format-myst": "0.2.0-beta.119",
|
|
62
|
+
"@dogsbay/format-obsidian": "0.2.0-beta.119",
|
|
63
|
+
"@dogsbay/format-openapi": "0.2.0-beta.119",
|
|
64
|
+
"@dogsbay/format-openknowledge": "0.2.0-beta.119",
|
|
65
|
+
"@dogsbay/format-rst": "0.2.0-beta.119",
|
|
66
|
+
"@dogsbay/format-starlight": "0.2.0-beta.119",
|
|
67
|
+
"@dogsbay/format-topicmap": "0.2.0-beta.119",
|
|
68
|
+
"@dogsbay/format-vitepress": "0.2.0-beta.119",
|
|
69
|
+
"@dogsbay/minja": "0.2.0-beta.119",
|
|
70
|
+
"@dogsbay/render": "0.2.0-beta.119",
|
|
71
|
+
"@dogsbay/serialize-core": "0.2.0-beta.119",
|
|
72
|
+
"@dogsbay/site-check": "0.2.0-beta.119",
|
|
73
|
+
"@dogsbay/types": "0.2.0-beta.119"
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
76
|
"@mdx-js/mdx": "^3.1.1",
|