dogsbay 0.2.0-beta.111 → 0.2.0-beta.113
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/resolve.d.ts +27 -0
- package/dist/commands/resolve.js +79 -0
- package/dist/commands/site-build.d.ts +37 -13
- package/dist/commands/site-build.js +199 -54
- package/dist/config/load.js +59 -0
- package/dist/config/schema.d.ts +31 -0
- package/dist/config/to-astro-options.js +2 -0
- package/dist/index.js +69 -22
- package/package.json +29 -29
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface ResolveOptions {
|
|
2
|
+
out: string;
|
|
3
|
+
config?: string;
|
|
4
|
+
attribute?: string[];
|
|
5
|
+
deliverable?: string;
|
|
6
|
+
undefinedRefs?: "preserve" | "asciidoc-literal";
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* `dogsbay resolve` — the resolution STAGE as a verb. Dogsbay MD in, Dogsbay
|
|
10
|
+
* MD out: `{{ var }}` substituted, `{% if %}` branches chosen, `{% include %}`
|
|
11
|
+
* spliced, and the directive vocabulary (`:::note`, tabs, steps, …) left
|
|
12
|
+
* exactly as authored. It is not a format: the templated form stays the
|
|
13
|
+
* canonical one, this is a per-variant snapshot of it. See plans/cli-grammar.md.
|
|
14
|
+
*
|
|
15
|
+
* Two shapes of input, decided by whether the directory ITSELF carries a
|
|
16
|
+
* config (an ancestor's config is not used — running `resolve ./markdown`
|
|
17
|
+
* inside a site must resolve that directory, not whatever the site declares):
|
|
18
|
+
*
|
|
19
|
+
* - a site root → `site build --to dogsbay-md`, so sources, `attributes:`,
|
|
20
|
+
* `undefinedRefs`, deliverables and include policy all come from the config
|
|
21
|
+
* and the output is byte-identical to the build's own mirror;
|
|
22
|
+
* - a bare directory → the preprocessor over that tree with only the `-a`
|
|
23
|
+
* attributes. Partial evaluation (`--only includes`, keeping unsupplied
|
|
24
|
+
* attributes as template text) is planned, not built — today an undefined
|
|
25
|
+
* reference follows `--undefined-refs`.
|
|
26
|
+
*/
|
|
27
|
+
export declare function resolveCommand(dir: string | undefined, options: ResolveOptions): Promise<void>;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { cpSync, existsSync, mkdtempSync, rmSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import pc from "picocolors";
|
|
5
|
+
import { findConfig } from "../config/index.js";
|
|
6
|
+
import { runPreprocessor } from "../site-build/preprocess.js";
|
|
7
|
+
import { parseAttributePairs, siteBuild } from "./site-build.js";
|
|
8
|
+
/**
|
|
9
|
+
* `dogsbay resolve` — the resolution STAGE as a verb. Dogsbay MD in, Dogsbay
|
|
10
|
+
* MD out: `{{ var }}` substituted, `{% if %}` branches chosen, `{% include %}`
|
|
11
|
+
* spliced, and the directive vocabulary (`:::note`, tabs, steps, …) left
|
|
12
|
+
* exactly as authored. It is not a format: the templated form stays the
|
|
13
|
+
* canonical one, this is a per-variant snapshot of it. See plans/cli-grammar.md.
|
|
14
|
+
*
|
|
15
|
+
* Two shapes of input, decided by whether the directory ITSELF carries a
|
|
16
|
+
* config (an ancestor's config is not used — running `resolve ./markdown`
|
|
17
|
+
* inside a site must resolve that directory, not whatever the site declares):
|
|
18
|
+
*
|
|
19
|
+
* - a site root → `site build --to dogsbay-md`, so sources, `attributes:`,
|
|
20
|
+
* `undefinedRefs`, deliverables and include policy all come from the config
|
|
21
|
+
* and the output is byte-identical to the build's own mirror;
|
|
22
|
+
* - a bare directory → the preprocessor over that tree with only the `-a`
|
|
23
|
+
* attributes. Partial evaluation (`--only includes`, keeping unsupplied
|
|
24
|
+
* attributes as template text) is planned, not built — today an undefined
|
|
25
|
+
* reference follows `--undefined-refs`.
|
|
26
|
+
*/
|
|
27
|
+
export async function resolveCommand(dir, options) {
|
|
28
|
+
const start = resolve(dir ?? ".");
|
|
29
|
+
const explicit = options.config ? resolve(options.config) : undefined;
|
|
30
|
+
const found = explicit ?? findConfig(start);
|
|
31
|
+
const configHere = found !== null && found !== undefined && (explicit !== undefined || dirname(found) === start);
|
|
32
|
+
if (configHere) {
|
|
33
|
+
if (options.undefinedRefs) {
|
|
34
|
+
throw new Error("--undefined-refs applies to a bare directory; with a config, set `undefinedRefs:` there.");
|
|
35
|
+
}
|
|
36
|
+
console.log(pc.bold(`Resolving ${start} (config: ${found})`));
|
|
37
|
+
await siteBuild(start, {
|
|
38
|
+
config: options.config,
|
|
39
|
+
to: ["dogsbay-md"],
|
|
40
|
+
out: options.out,
|
|
41
|
+
attribute: options.attribute,
|
|
42
|
+
deliverable: options.deliverable,
|
|
43
|
+
});
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (!existsSync(start))
|
|
47
|
+
throw new Error(`No such directory: ${start}`);
|
|
48
|
+
if (options.deliverable) {
|
|
49
|
+
throw new Error("--deliverable needs a dogsbay.config.yml in the directory being resolved.");
|
|
50
|
+
}
|
|
51
|
+
const out = resolve(options.out);
|
|
52
|
+
const inside = (parent, child) => child === parent || child.startsWith(parent.endsWith("/") ? parent : parent + "/");
|
|
53
|
+
if (inside(out, start) || inside(start, out)) {
|
|
54
|
+
throw new Error(`refusing to write the resolved Markdown to ${out}: it is inside, or contains, the source ${start}. ` +
|
|
55
|
+
`To fix: choose an output directory outside it.`);
|
|
56
|
+
}
|
|
57
|
+
console.log(pc.bold(`Resolving ${start} (no config — attributes from -a only)`));
|
|
58
|
+
// The preprocessor writes per-source subdirectories under a root and wipes
|
|
59
|
+
// its own subdirectory; stage under a temp root and move the one tree to
|
|
60
|
+
// where the user asked, so `-o` names the tree and nothing else.
|
|
61
|
+
const staging = mkdtempSync(join(tmpdir(), "dogsbay-resolve-"));
|
|
62
|
+
try {
|
|
63
|
+
const result = await runPreprocessor([{ resolvedPath: start, attributes: parseAttributePairs(options.attribute) }], { mirrorRoot: staging, undefinedRefs: options.undefinedRefs });
|
|
64
|
+
rmSync(out, { recursive: true, force: true });
|
|
65
|
+
cpSync(result.mirrorPaths[0], out, { recursive: true });
|
|
66
|
+
const parts = [`${result.filesProcessed} resolved`, `${result.filesUnchanged} unchanged`];
|
|
67
|
+
if (result.filesOptedOut > 0)
|
|
68
|
+
parts.push(`${result.filesOptedOut} opted out`);
|
|
69
|
+
if (result.pagesFailedRender.length > 0)
|
|
70
|
+
parts.push(`${result.pagesFailedRender.length} FAILED (copied raw)`);
|
|
71
|
+
if (result.pagesWithUnresolvedIncludes.length > 0) {
|
|
72
|
+
parts.push(`${result.pagesWithUnresolvedIncludes.length} with unresolved includes`);
|
|
73
|
+
}
|
|
74
|
+
console.log(` ${parts.join(", ")} → ${out}`);
|
|
75
|
+
}
|
|
76
|
+
finally {
|
|
77
|
+
rmSync(staging, { recursive: true, force: true });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { NavItem } from "@dogsbay/types";
|
|
2
|
+
import type { DogsbayConfig } from "../config/index.js";
|
|
2
3
|
export interface SiteBuildOptions {
|
|
3
4
|
/** Explicit path to a config file; bypasses upward search. */
|
|
4
5
|
config?: string;
|
|
@@ -30,15 +31,26 @@ export interface SiteBuildOptions {
|
|
|
30
31
|
from?: string;
|
|
31
32
|
nav?: string;
|
|
32
33
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
34
|
+
* Build targets, repeatable `--to <format>`: `astro`, `dogsbay-md` (the
|
|
35
|
+
* resolved Markdown), or any format the export registry can write
|
|
36
|
+
* (`mkdocs`, `docusaurus`, `obsidian`, `openknowledge`, `starlight`).
|
|
37
|
+
* When given, ONLY these are built. Absent: every `outputs:` entry in
|
|
38
|
+
* the config, or Astro. See plans/cli-grammar.md.
|
|
35
39
|
*/
|
|
36
|
-
|
|
40
|
+
to?: string[];
|
|
41
|
+
/**
|
|
42
|
+
* Output directory for a single non-Astro `--to` target, resolved against
|
|
43
|
+
* the CURRENT DIRECTORY (it names where the user wants the artefact, not
|
|
44
|
+
* a path inside the project). Astro's output is the config's `output:`;
|
|
45
|
+
* several targets take their paths from `outputs:`.
|
|
46
|
+
*/
|
|
47
|
+
out?: string;
|
|
37
48
|
/**
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* user wants the artifact, not a path inside the project.
|
|
49
|
+
* Deprecated alias of `--to <format>` (plus `--to astro` unless
|
|
50
|
+
* `--no-astro`). Prints the replacement and runs it.
|
|
41
51
|
*/
|
|
52
|
+
export?: string;
|
|
53
|
+
/** Deprecated alias of `--out` for `--export`. */
|
|
42
54
|
exportOut?: string;
|
|
43
55
|
/**
|
|
44
56
|
* Exporter pass-throughs. Prefixed ONLY where the bare name already means
|
|
@@ -121,16 +133,28 @@ export interface SiteBuildOptions {
|
|
|
121
133
|
*/
|
|
122
134
|
deliverable?: string;
|
|
123
135
|
/**
|
|
124
|
-
* `--no-astro`
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
* Used by the Docker image's `preprocess` stage and as a debug aid for
|
|
129
|
-
* inspecting resolved `{{ var }}` / `{% if %}` / `{% include %}` output.
|
|
130
|
-
* Default (undefined / true): emit the full Astro project.
|
|
136
|
+
* Deprecated. `--no-astro` alone is `--to dogsbay-md` with the mirror
|
|
137
|
+
* (`.dogsbay/build-content/`) as its output — `dogsbay resolve` is the
|
|
138
|
+
* named form. With `--export` it drops the Astro target. Prints the
|
|
139
|
+
* replacement and runs it.
|
|
131
140
|
*/
|
|
132
141
|
astro?: boolean;
|
|
133
142
|
}
|
|
143
|
+
/** One thing `site build` writes: a format and, for non-Astro, a directory. */
|
|
144
|
+
export interface BuildTarget {
|
|
145
|
+
format: string;
|
|
146
|
+
out?: string;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Turn the flags — new (`--to`/`--out`), deprecated (`--export`/`--export-out`/
|
|
150
|
+
* `--no-astro`) — and the config's `outputs:` into one list of targets. The
|
|
151
|
+
* deprecated forms map onto `--to` and say so; they keep working because
|
|
152
|
+
* three CI repos and the Docker image use them. See plans/cli-grammar.md.
|
|
153
|
+
*/
|
|
154
|
+
export declare function resolveBuildTargets(options: Pick<SiteBuildOptions, "to" | "out" | "export" | "exportOut" | "astro">, config: DogsbayConfig, configPath: string, mirrorRoot: string): {
|
|
155
|
+
targets: BuildTarget[];
|
|
156
|
+
notes: string[];
|
|
157
|
+
};
|
|
134
158
|
export declare function siteBuild(cwd: string | undefined, options: SiteBuildOptions): Promise<void>;
|
|
135
159
|
/**
|
|
136
160
|
* Parse repeatable `--attribute name=value` CLI args into a
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* init` is the prerequisite.
|
|
16
16
|
*/
|
|
17
17
|
import { swapTopicmapNavs } from "../topicmap-nav.js";
|
|
18
|
-
import { existsSync } from "node:fs";
|
|
18
|
+
import { cpSync, existsSync, readdirSync, rmSync } from "node:fs";
|
|
19
19
|
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
20
20
|
import pc from "picocolors";
|
|
21
21
|
import { emitAstroPages, emitPassthroughAstroPages, emitSiteConfig, emitConfigDerivedFiles, emitDeployArtifacts, emitAgentReadinessFiles, emitMissingTranslationStubs, emitSwitcherMap, emitTaxonomyRoutes, makeSlugExcluder, buildBlogData, emitBlogFiles, emitPluginRuntime, normalizeBasePath, basePathSegments, resolvePrefixes, } from "@dogsbay/format-astro";
|
|
@@ -27,6 +27,80 @@ import { mergeAttributes, runPreprocessor, } from "../site-build/preprocess.js";
|
|
|
27
27
|
import { collectAssets } from "../audit/lib/collect-assets.js";
|
|
28
28
|
import { validateRefs } from "../audit/lib/validate-refs.js";
|
|
29
29
|
import { loadPlugins, runExtendConfig, runOnContentImported, runTransformNav, runEmitFiles, collectClientModules, collectStyles, collectClientConfigs, collectComponentWrappers, } from "../plugins/index.js";
|
|
30
|
+
/**
|
|
31
|
+
* The two targets `site build` writes itself rather than through an exporter:
|
|
32
|
+
* Astro is the site (config `output:`), dogsbay-md is the preprocessor's
|
|
33
|
+
* mirror (the resolved Markdown).
|
|
34
|
+
*/
|
|
35
|
+
const NATIVE_TARGETS = new Set(["astro", "dogsbay-md"]);
|
|
36
|
+
/**
|
|
37
|
+
* Turn the flags — new (`--to`/`--out`), deprecated (`--export`/`--export-out`/
|
|
38
|
+
* `--no-astro`) — and the config's `outputs:` into one list of targets. The
|
|
39
|
+
* deprecated forms map onto `--to` and say so; they keep working because
|
|
40
|
+
* three CI repos and the Docker image use them. See plans/cli-grammar.md.
|
|
41
|
+
*/
|
|
42
|
+
export function resolveBuildTargets(options, config, configPath, mirrorRoot) {
|
|
43
|
+
const notes = [];
|
|
44
|
+
const configDir = dirname(resolve(configPath));
|
|
45
|
+
const fromConfig = (config.outputs ?? []).map((o) => ({
|
|
46
|
+
format: o.format,
|
|
47
|
+
out: isAbsolute(o.path) ? o.path : resolve(configDir, o.path),
|
|
48
|
+
}));
|
|
49
|
+
let targets;
|
|
50
|
+
if (options.to && options.to.length > 0) {
|
|
51
|
+
targets = [...new Set(options.to)].map((format) => ({
|
|
52
|
+
format,
|
|
53
|
+
out: fromConfig.find((o) => o.format === format)?.out,
|
|
54
|
+
}));
|
|
55
|
+
const needOut = targets.filter((t) => t.format !== "astro");
|
|
56
|
+
if (options.out) {
|
|
57
|
+
if (needOut.length !== 1) {
|
|
58
|
+
throw new Error(`--out names one output directory, but ` +
|
|
59
|
+
(needOut.length === 0
|
|
60
|
+
? "no non-Astro target was given (Astro's output is `output:` in the config)"
|
|
61
|
+
: `${needOut.length} non-Astro targets were given`) +
|
|
62
|
+
`. To fix: declare the paths under \`outputs:\` in dogsbay.config.yml, ` +
|
|
63
|
+
`or build one target per invocation.`);
|
|
64
|
+
}
|
|
65
|
+
needOut[0].out = resolve(options.out);
|
|
66
|
+
}
|
|
67
|
+
for (const t of needOut) {
|
|
68
|
+
if (!t.out) {
|
|
69
|
+
throw new Error(`--to ${t.format} needs an output directory. To fix: add --out <dir>, ` +
|
|
70
|
+
`or declare it under \`outputs:\` in dogsbay.config.yml.`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (options.export || options.astro === false) {
|
|
74
|
+
notes.push("--to given: the deprecated --export / --no-astro flags are ignored.");
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
else if (options.export) {
|
|
78
|
+
if (!options.exportOut) {
|
|
79
|
+
throw new Error(`--export ${options.export} needs an output directory. To fix: use ` +
|
|
80
|
+
`--to ${options.export} --out <dir> (--export/--export-out are deprecated).`);
|
|
81
|
+
}
|
|
82
|
+
targets = [{ format: options.export, out: resolve(options.exportOut) }];
|
|
83
|
+
if (options.astro !== false)
|
|
84
|
+
targets.unshift({ format: "astro" });
|
|
85
|
+
notes.push(`--export/--export-out are deprecated: use ` +
|
|
86
|
+
(options.astro === false
|
|
87
|
+
? `--to ${options.export} --out ${options.exportOut}`
|
|
88
|
+
: `--to astro --to ${options.export} --out ${options.exportOut}`));
|
|
89
|
+
}
|
|
90
|
+
else if (options.astro === false) {
|
|
91
|
+
targets = [{ format: "dogsbay-md", out: mirrorRoot }];
|
|
92
|
+
notes.push("--no-astro is deprecated: use `dogsbay resolve <dir> -o <out>` " +
|
|
93
|
+
"(or `site build --to dogsbay-md --out <dir>`). The mirror stays at " +
|
|
94
|
+
".dogsbay/build-content/ for this run.");
|
|
95
|
+
}
|
|
96
|
+
else if (fromConfig.length > 0) {
|
|
97
|
+
targets = fromConfig;
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
targets = [{ format: "astro" }];
|
|
101
|
+
}
|
|
102
|
+
return { targets, notes };
|
|
103
|
+
}
|
|
30
104
|
export async function siteBuild(cwd, options) {
|
|
31
105
|
const startDir = resolve(cwd || ".");
|
|
32
106
|
// 1. Find the config file
|
|
@@ -62,30 +136,47 @@ export async function siteBuild(cwd, options) {
|
|
|
62
136
|
}
|
|
63
137
|
// 3. CLI overrides
|
|
64
138
|
config = mergeOverrides(config, options);
|
|
65
|
-
// 4.
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
139
|
+
// 4. What this build writes. Targets are settled — and every exporter
|
|
140
|
+
// named is resolved against the registry — BEFORE sources are fetched,
|
|
141
|
+
// preprocessed and imported: `convert` learned this the expensive way
|
|
142
|
+
// (convert.ts:582), a typo that fails late costs a full run over a
|
|
143
|
+
// 20k-page corpus. The mirror root is needed here because the deprecated
|
|
144
|
+
// `--no-astro` is `--to dogsbay-md` with the mirror as its output.
|
|
145
|
+
const mirrorRoot = options.deliverable
|
|
146
|
+
? join(siteRoot, ".dogsbay", "deliverables", options.deliverable, "build-content")
|
|
147
|
+
: join(siteRoot, ".dogsbay", "build-content");
|
|
148
|
+
const { targets, notes: targetNotes } = resolveBuildTargets(options, config, configPath, mirrorRoot);
|
|
149
|
+
for (const n of targetNotes)
|
|
150
|
+
console.warn(pc.yellow(` Note: ${n}`));
|
|
151
|
+
const buildsAstro = targets.some((t) => t.format === "astro");
|
|
152
|
+
const mirrorTarget = targets.find((t) => t.format === "dogsbay-md");
|
|
153
|
+
const exportTargets = targets.filter((t) => !NATIVE_TARGETS.has(t.format));
|
|
154
|
+
if (exportTargets.length > 0) {
|
|
77
155
|
const { resolveExporter } = await import("../formats.js");
|
|
78
|
-
|
|
156
|
+
for (const t of exportTargets)
|
|
157
|
+
resolveExporter(t.format);
|
|
158
|
+
}
|
|
159
|
+
if (options.preprocess === false) {
|
|
160
|
+
if (mirrorTarget) {
|
|
161
|
+
throw new Error("--to dogsbay-md is the RESOLVED Markdown, and --no-preprocess skips " +
|
|
162
|
+
"resolution. To fix: drop one of the two flags.");
|
|
163
|
+
}
|
|
79
164
|
// The export path relies on the preprocessor having spliced
|
|
80
165
|
// `{% include %}`: unresolved include nodes serialize to nothing, so the
|
|
81
166
|
// transclusions vanish from the output with a clean exit. (`convert` runs
|
|
82
167
|
// realizePageIncludes for exactly this reason.)
|
|
83
|
-
if (
|
|
84
|
-
console.warn(pc.yellow(` Warning: --no-preprocess with --
|
|
85
|
-
`content — transclusions are resolved by the
|
|
86
|
-
`the export cannot recover them.`));
|
|
168
|
+
if (exportTargets.length > 0) {
|
|
169
|
+
console.warn(pc.yellow(` Warning: --no-preprocess with --to ${exportTargets.map((t) => t.format).join("/")} ` +
|
|
170
|
+
`drops {% include %} content — transclusions are resolved by the ` +
|
|
171
|
+
`preprocessor and the export cannot recover them.`));
|
|
87
172
|
}
|
|
88
173
|
}
|
|
174
|
+
if (targets.length > 1 || !buildsAstro) {
|
|
175
|
+
console.log(` Targets: ${targets.map((t) => (t.out && t.format !== "astro" ? `${t.format} → ${t.out}` : t.format)).join(", ")}`);
|
|
176
|
+
}
|
|
177
|
+
// 4a. Where the generated Astro project lives. Default `./astro` puts the
|
|
178
|
+
// project in a subfolder; `output: "."` keeps everything flat at the
|
|
179
|
+
// project root.
|
|
89
180
|
const outputDir = resolveOutputDir(config, configPath);
|
|
90
181
|
// 4b. Load plugins (resolve npm names, factory invocations,
|
|
91
182
|
// topo-sort). Plugin loading is fail-fast: a misconfigured
|
|
@@ -115,7 +206,7 @@ export async function siteBuild(cwd, options) {
|
|
|
115
206
|
// Refuse to build without a scaffold — init is the prerequisite.
|
|
116
207
|
// Mirror-only builds (--no-astro) never touch the scaffold, so they
|
|
117
208
|
// run without one (e.g. per-deliverable mirror builds).
|
|
118
|
-
if (
|
|
209
|
+
if (buildsAstro && !existsSync(`${outputDir}/package.json`)) {
|
|
119
210
|
console.error(pc.red(`Error: no scaffold found at ${outputDir}.`));
|
|
120
211
|
console.error(` Run \`dogsbay site init ${siteRoot}\` first.`);
|
|
121
212
|
process.exit(1);
|
|
@@ -214,9 +305,6 @@ export async function siteBuild(cwd, options) {
|
|
|
214
305
|
resolvedPath: resolvedSources[i],
|
|
215
306
|
attributes: mergeAttributes(config.attributes, src.attributes, cliAttributes),
|
|
216
307
|
}));
|
|
217
|
-
const mirrorRoot = options.deliverable
|
|
218
|
-
? join(siteRoot, ".dogsbay", "deliverables", options.deliverable, "build-content")
|
|
219
|
-
: join(siteRoot, ".dogsbay", "build-content");
|
|
220
308
|
const result = await runPreprocessor(preprocessSources, {
|
|
221
309
|
mirrorRoot,
|
|
222
310
|
undefinedRefs: config.undefinedRefs,
|
|
@@ -240,16 +328,28 @@ export async function siteBuild(cwd, options) {
|
|
|
240
328
|
console.log(` Preprocessor: ${changes.join(", ")} → ${mirrorRoot}`);
|
|
241
329
|
}
|
|
242
330
|
}
|
|
243
|
-
// --
|
|
244
|
-
//
|
|
245
|
-
//
|
|
246
|
-
//
|
|
247
|
-
//
|
|
248
|
-
//
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
331
|
+
// 6b. --to dogsbay-md: the resolved Markdown IS the artefact. It is the
|
|
332
|
+
// preprocessor's mirror — every file, fragments included, with `{{ var }}`
|
|
333
|
+
// substituted, `{% if %}` chosen and `{% include %}` spliced, directives
|
|
334
|
+
// untouched. Copied to the requested directory (the deprecated --no-astro
|
|
335
|
+
// leaves it in the mirror). This is what `dogsbay resolve` produces, and
|
|
336
|
+
// what the Docker image's `preprocess` stage and RAG ingestion consume.
|
|
337
|
+
if (mirrorTarget) {
|
|
338
|
+
if (importPaths.length > 1) {
|
|
339
|
+
throw new Error(`--to dogsbay-md cannot resolve a multi-source site (${importPaths.length} ` +
|
|
340
|
+
`sources) into one directory. To fix: resolve one source at a time ` +
|
|
341
|
+
`with --deliverable <name>.`);
|
|
342
|
+
}
|
|
343
|
+
const out = mirrorTarget.out;
|
|
344
|
+
if (out !== mirrorRoot) {
|
|
345
|
+
guardOutputDir(out, siteRoot, resolvedSources);
|
|
346
|
+
rmSync(out, { recursive: true, force: true });
|
|
347
|
+
cpSync(importPaths[0], out, { recursive: true });
|
|
348
|
+
}
|
|
349
|
+
const n = countFiles(out, ".md");
|
|
350
|
+
console.log(` Resolved ${n} Markdown file(s) → ${out}`);
|
|
351
|
+
if (!buildsAstro && exportTargets.length === 0)
|
|
352
|
+
return;
|
|
253
353
|
}
|
|
254
354
|
// 7. Import content
|
|
255
355
|
//
|
|
@@ -387,13 +487,15 @@ export async function siteBuild(cwd, options) {
|
|
|
387
487
|
: ""));
|
|
388
488
|
}
|
|
389
489
|
}
|
|
390
|
-
// 7e. --
|
|
391
|
-
//
|
|
392
|
-
// variants merged, topic-map navs swapped, drafts dropped,
|
|
393
|
-
// applied. None of that is recoverable from a content
|
|
394
|
-
// why this lives here and not in `convert`. After 7d
|
|
395
|
-
// blocks
|
|
396
|
-
|
|
490
|
+
// 7e. --to <format>: hand the FINAL page set to each exporter. A vault or
|
|
491
|
+
// an MkDocs project needs exactly the pages the site gets — attributes
|
|
492
|
+
// resolved, variants merged, topic-map navs swapped, drafts dropped,
|
|
493
|
+
// routablesFrom applied. None of that is recoverable from a content
|
|
494
|
+
// directory, which is why this lives here and not in `convert`. After 7d
|
|
495
|
+
// so a --strict failure blocks every output rather than shipping one the
|
|
496
|
+
// site rejected. One pipeline pass, N serializations: that is what makes
|
|
497
|
+
// the OpenShift Astro/MkDocs/Docusaurus sites page-for-page comparable.
|
|
498
|
+
if (exportTargets.length > 0) {
|
|
397
499
|
// Multi-source builds rewrite each source's asset refs under its own
|
|
398
500
|
// version/locale prefix (import-content.ts), but importer mounts carry no
|
|
399
501
|
// matching `into`, so a vault would reference `4.22/_assets/x` while the
|
|
@@ -401,23 +503,27 @@ export async function siteBuild(cwd, options) {
|
|
|
401
503
|
// handles this with assetSourceDirs; the export path has no equivalent,
|
|
402
504
|
// so refuse rather than ship it silently.
|
|
403
505
|
if (resolvedSources.length > 1) {
|
|
404
|
-
throw new Error(`--
|
|
506
|
+
throw new Error(`--to ${exportTargets[0].format} cannot build a multi-source site (${resolvedSources.length} ` +
|
|
405
507
|
`sources): per-source asset prefixes would not resolve in the ` +
|
|
406
508
|
`output. To fix: export one source at a time with ` +
|
|
407
509
|
`--deliverable <name>, or drop the extra sources for this run.`);
|
|
408
510
|
}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
511
|
+
for (const t of exportTargets) {
|
|
512
|
+
await runSecondaryExport(pages, nav, {
|
|
513
|
+
format: t.format,
|
|
514
|
+
out: t.out,
|
|
515
|
+
siteName: config.site.name,
|
|
516
|
+
siteUrl: config.site.url,
|
|
517
|
+
copyright: config.site.copyright,
|
|
518
|
+
poweredBy: config.poweredBy,
|
|
519
|
+
stripPrefix: combinedPrefix,
|
|
520
|
+
sourceDir: resolvedSources[0],
|
|
521
|
+
assetMounts: importResult.assetMounts,
|
|
522
|
+
plugin: options.exportPlugin,
|
|
523
|
+
graphGroups: options.graphGroups,
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
if (!buildsAstro)
|
|
421
527
|
return;
|
|
422
528
|
}
|
|
423
529
|
// 7. Emit content / config-derived / agent-readiness tiers.
|
|
@@ -471,7 +577,9 @@ export async function siteBuild(cwd, options) {
|
|
|
471
577
|
const { copied } = emitPassthroughAstroPages(passthroughCopies, outputDir, generatedPaths);
|
|
472
578
|
console.log(` Copied ${copied} passthrough .astro page${copied === 1 ? "" : "s"}`);
|
|
473
579
|
}
|
|
474
|
-
|
|
580
|
+
// Pages are needed for the redirect rules: the dangling-target and
|
|
581
|
+
// live-page-at-the-source checks are only possible against the emitted set.
|
|
582
|
+
emitConfigDerivedFiles(outputDir, astroOpts, pages);
|
|
475
583
|
// Deploy artifacts — write-if-missing so adding `deploy: github-pages`
|
|
476
584
|
// to dogsbay.config.yml on an existing site materializes the workflow
|
|
477
585
|
// on the next build. Author edits survive (only fired when the file
|
|
@@ -629,7 +737,12 @@ export function parseAttributePairs(pairs) {
|
|
|
629
737
|
const value = raw.slice(eq + 1).trim();
|
|
630
738
|
if (!name)
|
|
631
739
|
continue;
|
|
632
|
-
|
|
740
|
+
// `-a enterprise=false` must mean false. Everything from the shell is a
|
|
741
|
+
// string, and the string "false" is truthy to `{% if enterprise %}` —
|
|
742
|
+
// which selected the ENTERPRISE branch for the flag that said not to.
|
|
743
|
+
// Only the two literals are coerced; a YAML-side `attributes:` block
|
|
744
|
+
// already types them.
|
|
745
|
+
out[name] = value === "true" ? true : value === "false" ? false : value;
|
|
633
746
|
}
|
|
634
747
|
return Object.keys(out).length > 0 ? out : undefined;
|
|
635
748
|
}
|
|
@@ -949,6 +1062,36 @@ export function stripNavPrefix(nav, prefix) {
|
|
|
949
1062
|
* same registry `convert` uses, so any format that can export is reachable
|
|
950
1063
|
* here without further wiring.
|
|
951
1064
|
*/
|
|
1065
|
+
/**
|
|
1066
|
+
* A directory that is about to be wiped and rewritten must not be the site,
|
|
1067
|
+
* a source, or an ancestor of either. The mirror copy is `rm -rf` + copy so
|
|
1068
|
+
* that deleted pages do not linger (the criterion is "identical to the
|
|
1069
|
+
* mirror"), and that is exactly the operation that must never point at the
|
|
1070
|
+
* wrong place.
|
|
1071
|
+
*/
|
|
1072
|
+
function guardOutputDir(out, siteRoot, sources) {
|
|
1073
|
+
const inside = (parent, child) => child === parent || child.startsWith(parent.endsWith("/") ? parent : parent + "/");
|
|
1074
|
+
for (const protectedDir of [siteRoot, ...sources]) {
|
|
1075
|
+
if (inside(out, protectedDir)) {
|
|
1076
|
+
throw new Error(`refusing to write the resolved Markdown to ${out}: it contains ${protectedDir}. ` +
|
|
1077
|
+
`To fix: choose an output directory outside the site and its sources.`);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
function countFiles(dir, ext) {
|
|
1082
|
+
let n = 0;
|
|
1083
|
+
const walk = (d) => {
|
|
1084
|
+
for (const e of readdirSync(d, { withFileTypes: true })) {
|
|
1085
|
+
if (e.isDirectory())
|
|
1086
|
+
walk(join(d, e.name));
|
|
1087
|
+
else if (e.isFile() && e.name.endsWith(ext))
|
|
1088
|
+
n++;
|
|
1089
|
+
}
|
|
1090
|
+
};
|
|
1091
|
+
if (existsSync(dir))
|
|
1092
|
+
walk(dir);
|
|
1093
|
+
return n;
|
|
1094
|
+
}
|
|
952
1095
|
async function runSecondaryExport(pages, nav, opts) {
|
|
953
1096
|
const { resolveExporter } = await import("../formats.js");
|
|
954
1097
|
const exporter = resolveExporter(opts.format);
|
|
@@ -957,6 +1100,8 @@ async function runSecondaryExport(pages, nav, opts) {
|
|
|
957
1100
|
await exporter.export(pages, exportNav, out, {
|
|
958
1101
|
siteName: opts.siteName,
|
|
959
1102
|
siteUrl: opts.siteUrl,
|
|
1103
|
+
copyright: opts.copyright,
|
|
1104
|
+
poweredBy: opts.poweredBy,
|
|
960
1105
|
sourceDir: opts.sourceDir,
|
|
961
1106
|
assetMounts: opts.assetMounts,
|
|
962
1107
|
plugin: opts.plugin,
|
package/dist/config/load.js
CHANGED
|
@@ -167,6 +167,9 @@ function validate(parsed, sourcePath) {
|
|
|
167
167
|
toc: obj.toc,
|
|
168
168
|
content,
|
|
169
169
|
output: obj.output,
|
|
170
|
+
outputs: parseOutputs(obj.outputs, sourcePath),
|
|
171
|
+
redirects: parseRedirects(obj.redirects, sourcePath),
|
|
172
|
+
poweredBy: obj.poweredBy,
|
|
170
173
|
deploy: obj.deploy,
|
|
171
174
|
analytics: obj.analytics,
|
|
172
175
|
agent,
|
|
@@ -1091,3 +1094,59 @@ function validateBlog(raw, sourcePath) {
|
|
|
1091
1094
|
: {}),
|
|
1092
1095
|
};
|
|
1093
1096
|
}
|
|
1097
|
+
/**
|
|
1098
|
+
* `outputs:` — a list of `{ format, path }`. Shape only: whether a format is
|
|
1099
|
+
* buildable is decided by `site build` against the export registry, so this
|
|
1100
|
+
* loader does not import the formats (they would pull every importer in).
|
|
1101
|
+
*/
|
|
1102
|
+
function parseOutputs(raw, sourcePath) {
|
|
1103
|
+
if (raw === undefined)
|
|
1104
|
+
return undefined;
|
|
1105
|
+
if (!Array.isArray(raw)) {
|
|
1106
|
+
throw new Error(`outputs must be a list of { format, path } in ${sourcePath}`);
|
|
1107
|
+
}
|
|
1108
|
+
const seen = new Set();
|
|
1109
|
+
return raw.map((entry, i) => {
|
|
1110
|
+
const e = entry;
|
|
1111
|
+
if (!e || typeof e !== "object" || typeof e.format !== "string" || typeof e.path !== "string") {
|
|
1112
|
+
throw new Error(`outputs[${i}] must be { format: <string>, path: <string> } in ${sourcePath}`);
|
|
1113
|
+
}
|
|
1114
|
+
if (seen.has(e.format)) {
|
|
1115
|
+
throw new Error(`outputs lists "${e.format}" twice in ${sourcePath} — one directory per format`);
|
|
1116
|
+
}
|
|
1117
|
+
seen.add(e.format);
|
|
1118
|
+
return { format: e.format, path: e.path };
|
|
1119
|
+
});
|
|
1120
|
+
}
|
|
1121
|
+
/**
|
|
1122
|
+
* `redirects:` — a map of source path → target. Shape only; the rules
|
|
1123
|
+
* themselves (loops, chains, dangling targets, platform limits) are validated
|
|
1124
|
+
* by `format-astro` at emit time, where the page set is known.
|
|
1125
|
+
*/
|
|
1126
|
+
function parseRedirects(raw, sourcePath) {
|
|
1127
|
+
if (raw === undefined)
|
|
1128
|
+
return undefined;
|
|
1129
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
1130
|
+
throw new Error(`redirects must be a map of "/from/": "/to/" in ${sourcePath}`);
|
|
1131
|
+
}
|
|
1132
|
+
const out = {};
|
|
1133
|
+
for (const [from, value] of Object.entries(raw)) {
|
|
1134
|
+
if (typeof value === "string") {
|
|
1135
|
+
out[from] = value;
|
|
1136
|
+
continue;
|
|
1137
|
+
}
|
|
1138
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
1139
|
+
const v = value;
|
|
1140
|
+
if (typeof v.to !== "string") {
|
|
1141
|
+
throw new Error(`redirects["${from}"] needs a string \`to\` in ${sourcePath}`);
|
|
1142
|
+
}
|
|
1143
|
+
if (v.status !== undefined && typeof v.status !== "number") {
|
|
1144
|
+
throw new Error(`redirects["${from}"].status must be a number in ${sourcePath}`);
|
|
1145
|
+
}
|
|
1146
|
+
out[from] = { to: v.to, ...(v.status !== undefined ? { status: v.status } : {}) };
|
|
1147
|
+
continue;
|
|
1148
|
+
}
|
|
1149
|
+
throw new Error(`redirects["${from}"] must be a path string or { to, status } in ${sourcePath}`);
|
|
1150
|
+
}
|
|
1151
|
+
return out;
|
|
1152
|
+
}
|
package/dist/config/schema.d.ts
CHANGED
|
@@ -89,6 +89,11 @@ export interface DeliverableConfig {
|
|
|
89
89
|
/** Per-deliverable agent/KB outputs, merged over the site `agent:`. */
|
|
90
90
|
agent?: AgentConfig;
|
|
91
91
|
}
|
|
92
|
+
/** One entry of `outputs:` — a target format and where it is written. */
|
|
93
|
+
export interface OutputTarget {
|
|
94
|
+
format: string;
|
|
95
|
+
path: string;
|
|
96
|
+
}
|
|
92
97
|
export interface DogsbayConfig {
|
|
93
98
|
/** Schema version. Must be 1 for now. */
|
|
94
99
|
schemaVersion: 1;
|
|
@@ -109,6 +114,32 @@ export interface DogsbayConfig {
|
|
|
109
114
|
* config dir. See docs/standard-layout.md.
|
|
110
115
|
*/
|
|
111
116
|
output?: string;
|
|
117
|
+
/**
|
|
118
|
+
* Every generator this site publishes to, from ONE pipeline pass. Each
|
|
119
|
+
* entry is a format from the export registry (`mkdocs`, `docusaurus`,
|
|
120
|
+
* `obsidian`, …), `astro`, or `dogsbay-md` (the resolved Markdown), with
|
|
121
|
+
* the directory it is written to, relative to the config file. `site
|
|
122
|
+
* build` with no `--to` builds every entry; `--to` narrows to the named
|
|
123
|
+
* ones. Absent: `[{ format: "astro", path: output }]`.
|
|
124
|
+
* See plans/cli-grammar.md.
|
|
125
|
+
*/
|
|
126
|
+
outputs?: OutputTarget[];
|
|
127
|
+
/**
|
|
128
|
+
* Redirects for paths with no surviving page — bulk moves, retired
|
|
129
|
+
* sections, external targets. `"/old/": "/new/"` is shorthand for a 301.
|
|
130
|
+
* A page that still exists should declare `aliases:` in its own
|
|
131
|
+
* frontmatter instead, so the knowledge is deleted with the page.
|
|
132
|
+
* See plans/redirects-and-aliases.md.
|
|
133
|
+
*/
|
|
134
|
+
redirects?: Record<string, string | {
|
|
135
|
+
to: string;
|
|
136
|
+
status?: number;
|
|
137
|
+
}>;
|
|
138
|
+
/**
|
|
139
|
+
* "Powered by DogsBay" footer attribution on generated sites. Default true.
|
|
140
|
+
* Set `poweredBy: false` to remove it. See docs/powered-by.md.
|
|
141
|
+
*/
|
|
142
|
+
poweredBy?: boolean;
|
|
112
143
|
deploy?: DeployConfig;
|
|
113
144
|
/**
|
|
114
145
|
* Build / asset behaviour. Knobs that affect how the site
|
|
@@ -27,6 +27,8 @@ export function configToAstroOptions(config) {
|
|
|
27
27
|
plausibleDomain: config.analytics?.plausible?.domain,
|
|
28
28
|
plausibleScriptUrl: config.analytics?.plausible?.scriptUrl,
|
|
29
29
|
deploy: config.deploy?.target,
|
|
30
|
+
redirects: config.redirects,
|
|
31
|
+
poweredBy: config.poweredBy,
|
|
30
32
|
inlineStylesheets: config.build?.inlineStylesheets,
|
|
31
33
|
navMode: config.build?.navMode,
|
|
32
34
|
llmsTxt: config.agent?.llmsTxt,
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import { readFileSync } from "node:fs";
|
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
5
|
import { Command } from "commander";
|
|
6
|
+
import pc from "picocolors";
|
|
6
7
|
import { init } from "./commands/init.js";
|
|
7
8
|
import { VALID_FROM } from "./config/load.js";
|
|
8
9
|
import { add, list } from "./commands/add.js";
|
|
@@ -131,12 +132,18 @@ site
|
|
|
131
132
|
.option("--content <path>", "Override content.source")
|
|
132
133
|
.option("--from <format>", "Override content.from")
|
|
133
134
|
.option("--nav <path>", "Override content.nav")
|
|
134
|
-
.option("--
|
|
135
|
-
"
|
|
136
|
-
"
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
.option("--
|
|
135
|
+
.option("--to <format>", "Build target (repeatable): astro (the default) | mkdocs | docusaurus | " +
|
|
136
|
+
"obsidian | openknowledge | starlight | dogsbay-md (the resolved Markdown). " +
|
|
137
|
+
"Every target is serialized from the SAME resolved page set — attributes, " +
|
|
138
|
+
"variants, drafts and routablesFrom applied. With --to, only the named " +
|
|
139
|
+
"targets are built; without it, every `outputs:` entry in the config, or astro.", (val, prev = []) => [...prev, val])
|
|
140
|
+
.option("--out <dir>", "Output directory for a single non-Astro --to target, relative to the " +
|
|
141
|
+
"current directory. Astro's output is the config's `output:`; several " +
|
|
142
|
+
"targets take their paths from `outputs:`.")
|
|
143
|
+
.option("--export <format>", "Deprecated — use --to <format> (with --to astro to keep the site)")
|
|
144
|
+
.option("--export-out <dir>", "Deprecated — use --out <dir>")
|
|
145
|
+
.option("--export-plugin <mode>", "--to obsidian: vault plugin setup, bundled | registry | none")
|
|
146
|
+
.option("--graph-groups <path>", "--to obsidian: graph-view colour groups, JSON. Ignored if the vault already has a graph.json")
|
|
140
147
|
.option("--plausible-domain <domain>", "Override analytics.plausible.domain")
|
|
141
148
|
.option("--plausible-script-url <url>", "Override analytics.plausible.scriptUrl")
|
|
142
149
|
.option("--no-llms-txt", "Disable llms.txt for this invocation only")
|
|
@@ -164,27 +171,26 @@ site
|
|
|
164
171
|
.option("--no-preprocess", "Skip the build-time Minja preprocessor pass even when " +
|
|
165
172
|
"`attributes:` are declared. Debug aid — shows the raw " +
|
|
166
173
|
"`{{ var }}` directives the importer left unresolved.")
|
|
167
|
-
.option("--no-astro", "
|
|
168
|
-
"
|
|
169
|
-
"page emission. The mirror is the 'preprocessed markdown' artifact. " +
|
|
170
|
-
"With --export this instead means 'build only the export target', " +
|
|
171
|
-
"which still runs the import.")
|
|
174
|
+
.option("--no-astro", "Deprecated — the resolved Markdown is `dogsbay resolve` (or " +
|
|
175
|
+
"--to dogsbay-md --out <dir>); a single other target is --to <format>.")
|
|
172
176
|
.option("--deliverable <name>", "Build under a named deliverable from the config's `deliverables:` " +
|
|
173
177
|
"section — its attributes/nav/out/agent apply on top of the base " +
|
|
174
178
|
"config. See plans/deliverables.md.")
|
|
175
179
|
.option("--all-deliverables", "Build every deliverable in sequence. Deliverables without their own " +
|
|
176
180
|
"`out:` share the site scaffold, so this is most useful with " +
|
|
177
|
-
"--
|
|
178
|
-
".dogsbay/deliverables/<name>/) or with
|
|
181
|
+
"`dogsbay resolve --deliverable` / --to dogsbay-md (per-deliverable " +
|
|
182
|
+
"resolved-markdown mirrors under .dogsbay/deliverables/<name>/) or with " +
|
|
183
|
+
"distinct out: dirs.")
|
|
179
184
|
.action(async (dir, options) => {
|
|
180
185
|
try {
|
|
181
186
|
if (options.allDeliverables) {
|
|
182
|
-
// Every deliverable would
|
|
183
|
-
//
|
|
184
|
-
|
|
185
|
-
|
|
187
|
+
// Every deliverable would write into the SAME --out dir and the last
|
|
188
|
+
// one would silently win. One non-Astro target per invocation.
|
|
189
|
+
const nonAstro = (options.to ?? []).filter((t) => t !== "astro");
|
|
190
|
+
if (options.export || options.out || nonAstro.length > 0) {
|
|
191
|
+
throw new Error("--to <format> builds one output directory per invocation, so " +
|
|
186
192
|
"--all-deliverables would overwrite it once per deliverable. " +
|
|
187
|
-
"To fix: run
|
|
193
|
+
"To fix: run it once per deliverable with --deliverable <name>.");
|
|
188
194
|
}
|
|
189
195
|
const { findConfig: find, loadConfig: load, listDeliverables: list } = await import("./config/index.js");
|
|
190
196
|
const { resolve: resolvePath, dirname: dirnameOf } = await import("node:path");
|
|
@@ -489,9 +495,42 @@ program
|
|
|
489
495
|
.option("--output <file>", "Output file path (default: lighthouse-results.txt)")
|
|
490
496
|
.action((url, options) => lighthouse(url, options));
|
|
491
497
|
program
|
|
492
|
-
.command("
|
|
493
|
-
.description("
|
|
494
|
-
"
|
|
498
|
+
.command("resolve")
|
|
499
|
+
.description("Resolve Dogsbay MD: substitute {{ variables }}, choose {% if %} branches and " +
|
|
500
|
+
"splice {% include %}s, writing plain Dogsbay MD with the directives intact. " +
|
|
501
|
+
"Reads dogsbay.config.yml when the directory has one (sources, attributes, " +
|
|
502
|
+
"deliverables); otherwise resolves the directory as given with the -a attributes. " +
|
|
503
|
+
"The ingestion form for an LLM wiki or a RAG corpus, and the same tree " +
|
|
504
|
+
"`site build` serializes to every target.")
|
|
505
|
+
.argument("[dir]", "Site root (with dogsbay.config.yml) or a directory of Dogsbay MD (default: cwd)")
|
|
506
|
+
.requiredOption("-o, --out <dir>", "Output directory, relative to the current directory. Replaced wholesale")
|
|
507
|
+
.option("--config <path>", "Explicit path to dogsbay.config.{yml,yaml,json}")
|
|
508
|
+
.option("-a, --attribute <name=value>", "Attribute value (repeatable). With a config, stacks onto its `attributes:`; " +
|
|
509
|
+
"without one, the whole context", (v, prev = []) => [...prev, v])
|
|
510
|
+
.option("--deliverable <name>", "Resolve under a named deliverable (config only)")
|
|
511
|
+
.option("--undefined-refs <mode>", "Directory mode only: `preserve` keeps `{{ x }}` for an undefined x (default); " +
|
|
512
|
+
"`asciidoc-literal` writes `{x}` (AsciiDoc-migrated corpora)")
|
|
513
|
+
.action(async (dir, options) => {
|
|
514
|
+
try {
|
|
515
|
+
const { resolveCommand } = await import("./commands/resolve.js");
|
|
516
|
+
await resolveCommand(dir, options);
|
|
517
|
+
}
|
|
518
|
+
catch (err) {
|
|
519
|
+
cliFail(err);
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
// Hidden from the first help screen, not removed: `convert` is the page-level
|
|
523
|
+
// primitive with no config, no attributes and no variant, which is exactly
|
|
524
|
+
// why its `--to astro` form emitted half a project with `{{ product_title }}`
|
|
525
|
+
// visible on every page and exit 0. The jobs people have are `import` (a
|
|
526
|
+
// project that builds), `site build --to <format>` (publish it) and `resolve`
|
|
527
|
+
// (a snapshot). It stays for scripts and the conformance suite, and prints
|
|
528
|
+
// where it stands. See plans/cli-grammar.md.
|
|
529
|
+
program
|
|
530
|
+
.command("convert", { hidden: true })
|
|
531
|
+
.description("Page-level format conversion with no site pipeline (no config, attributes, " +
|
|
532
|
+
"variants or nav selection). Prefer `site build --to <format>` on a Dogsbay " +
|
|
533
|
+
"project, or `dogsbay resolve` for resolved Markdown.")
|
|
495
534
|
.argument("<source>", "Path to source project")
|
|
496
535
|
.option("-f, --from <format>", "Source format (auto-detected if omitted)")
|
|
497
536
|
.option("-t, --to <format>", "Target format (required)")
|
|
@@ -536,7 +575,13 @@ program
|
|
|
536
575
|
.option("--eval-config", "VitePress: EXECUTE .vitepress/config.* instead of reading it statically. Runs code from the source repo and needs its dependencies installed — use only on a project you trust, and only when the sidebar is genuinely computed")
|
|
537
576
|
.option("--distro <name>", "AsciiBinder distro filter (--from asciidoc), e.g. openshift-enterprise")
|
|
538
577
|
.option("--topic-map <path>", "AsciiBinder topic-map path relative to the source root (--from asciidoc; default: _topic_maps/_topic_map.yml)")
|
|
539
|
-
.action((source, options) =>
|
|
578
|
+
.action((source, options) => {
|
|
579
|
+
console.warn(pc.yellow(" Note: `convert` runs no site pipeline — attributes, variants and nav " +
|
|
580
|
+
"selection do not apply. On a Dogsbay project use `site build --to " +
|
|
581
|
+
(options.to ?? "<format>") +
|
|
582
|
+
"`; for resolved Markdown use `dogsbay resolve`."));
|
|
583
|
+
return convert(source, options);
|
|
584
|
+
});
|
|
540
585
|
program
|
|
541
586
|
.command("import-dita")
|
|
542
587
|
.description("Convert DITA topic content (.dita file or directory) to dogsbay-md, with a conversion report")
|
|
@@ -605,4 +650,6 @@ program
|
|
|
605
650
|
// topicmap-resolve, reindex — commands + flags generated from the
|
|
606
651
|
// registry's zod schemas (see engine-cli.ts)
|
|
607
652
|
registerEngineCommands(program);
|
|
653
|
+
program.addHelpText("after", "\nLower-level: `dogsbay convert <src> --to <format>` converts a directory with no " +
|
|
654
|
+
"site pipeline (run `dogsbay convert --help`).\n");
|
|
608
655
|
program.parse();
|
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.113",
|
|
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": {
|
|
@@ -42,32 +42,32 @@
|
|
|
42
42
|
"typescript": "^5.9.3",
|
|
43
43
|
"valibot": "1.1.0",
|
|
44
44
|
"yaml": "^2.9.0",
|
|
45
|
-
"@dogsbay/adoc2md-modular": "0.2.0-beta.
|
|
46
|
-
"@dogsbay/autodoc-python": "0.2.0-beta.
|
|
47
|
-
"@dogsbay/commands": "0.2.0-beta.
|
|
48
|
-
"@dogsbay/format-asciidoc": "0.2.0-beta.
|
|
49
|
-
"@dogsbay/format-astro": "0.2.0-beta.
|
|
50
|
-
"@dogsbay/format-dita": "0.2.0-beta.
|
|
51
|
-
"@dogsbay/format-
|
|
52
|
-
"@dogsbay/format-
|
|
53
|
-
"@dogsbay/format-fluidtopics-markdown": "0.2.0-beta.
|
|
54
|
-
"@dogsbay/format-fluidtopics-rest": "0.2.0-beta.
|
|
55
|
-
"@dogsbay/format-gatsby": "0.2.0-beta.
|
|
56
|
-
"@dogsbay/format-gitbook": "0.2.0-beta.
|
|
57
|
-
"@dogsbay/format-
|
|
58
|
-
"@dogsbay/format-
|
|
59
|
-
"@dogsbay/format-mkdocs": "0.2.0-beta.
|
|
60
|
-
"@dogsbay/format-myst": "0.2.0-beta.
|
|
61
|
-
"@dogsbay/format-obsidian": "0.2.0-beta.
|
|
62
|
-
"@dogsbay/format-
|
|
63
|
-
"@dogsbay/format-
|
|
64
|
-
"@dogsbay/format-rst": "0.2.0-beta.
|
|
65
|
-
"@dogsbay/format-
|
|
66
|
-
"@dogsbay/format-
|
|
67
|
-
"@dogsbay/format-vitepress": "0.2.0-beta.
|
|
68
|
-
"@dogsbay/minja": "0.2.0-beta.
|
|
69
|
-
"@dogsbay/serialize-core": "0.2.0-beta.
|
|
70
|
-
"@dogsbay/types": "0.2.0-beta.
|
|
45
|
+
"@dogsbay/adoc2md-modular": "0.2.0-beta.113",
|
|
46
|
+
"@dogsbay/autodoc-python": "0.2.0-beta.113",
|
|
47
|
+
"@dogsbay/commands": "0.2.0-beta.113",
|
|
48
|
+
"@dogsbay/format-asciidoc": "0.2.0-beta.113",
|
|
49
|
+
"@dogsbay/format-astro": "0.2.0-beta.113",
|
|
50
|
+
"@dogsbay/format-dita": "0.2.0-beta.113",
|
|
51
|
+
"@dogsbay/format-docusaurus": "0.2.0-beta.113",
|
|
52
|
+
"@dogsbay/format-dogsbay-md": "0.2.0-beta.113",
|
|
53
|
+
"@dogsbay/format-fluidtopics-markdown": "0.2.0-beta.113",
|
|
54
|
+
"@dogsbay/format-fluidtopics-rest": "0.2.0-beta.113",
|
|
55
|
+
"@dogsbay/format-gatsby": "0.2.0-beta.113",
|
|
56
|
+
"@dogsbay/format-gitbook": "0.2.0-beta.113",
|
|
57
|
+
"@dogsbay/format-hugo": "0.2.0-beta.113",
|
|
58
|
+
"@dogsbay/format-mdx": "0.2.0-beta.113",
|
|
59
|
+
"@dogsbay/format-mkdocs": "0.2.0-beta.113",
|
|
60
|
+
"@dogsbay/format-myst": "0.2.0-beta.113",
|
|
61
|
+
"@dogsbay/format-obsidian": "0.2.0-beta.113",
|
|
62
|
+
"@dogsbay/format-openapi": "0.2.0-beta.113",
|
|
63
|
+
"@dogsbay/format-openknowledge": "0.2.0-beta.113",
|
|
64
|
+
"@dogsbay/format-rst": "0.2.0-beta.113",
|
|
65
|
+
"@dogsbay/format-starlight": "0.2.0-beta.113",
|
|
66
|
+
"@dogsbay/format-topicmap": "0.2.0-beta.113",
|
|
67
|
+
"@dogsbay/format-vitepress": "0.2.0-beta.113",
|
|
68
|
+
"@dogsbay/minja": "0.2.0-beta.113",
|
|
69
|
+
"@dogsbay/serialize-core": "0.2.0-beta.113",
|
|
70
|
+
"@dogsbay/types": "0.2.0-beta.113"
|
|
71
71
|
},
|
|
72
72
|
"devDependencies": {
|
|
73
73
|
"@types/markdown-it": "^14.1.2",
|
|
@@ -76,8 +76,8 @@
|
|
|
76
76
|
"domhandler": "^6.0.1",
|
|
77
77
|
"vitest": "^4.1.10",
|
|
78
78
|
"@dogsbay/adapter-camunda": "0.2.0-beta.93",
|
|
79
|
-
"@dogsbay/adapter-
|
|
80
|
-
"@dogsbay/adapter-
|
|
79
|
+
"@dogsbay/adapter-posthog": "0.2.0-beta.93",
|
|
80
|
+
"@dogsbay/adapter-tigera": "0.2.0-beta.93"
|
|
81
81
|
},
|
|
82
82
|
"license": "MIT",
|
|
83
83
|
"repository": {
|