dogsbay 0.2.0-beta.17 → 0.2.0-beta.18
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/site-build.js +26 -2
- package/dist/passthrough-astro.js +152 -0
- package/package.json +9 -9
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
import { existsSync } from "node:fs";
|
|
18
18
|
import { dirname, isAbsolute, resolve } from "node:path";
|
|
19
19
|
import pc from "picocolors";
|
|
20
|
-
import { emitAstroPages, emitSiteConfig, emitConfigDerivedFiles, emitAgentReadinessFiles, emitMissingTranslationStubs, emitSwitcherMap, emitTaxonomyRoutes, emitPluginRuntime, normalizeBasePath, basePathSegments, } from "@dogsbay/format-astro";
|
|
20
|
+
import { emitAstroPages, emitPassthroughAstroPages, emitSiteConfig, emitConfigDerivedFiles, emitAgentReadinessFiles, emitMissingTranslationStubs, emitSwitcherMap, emitTaxonomyRoutes, emitPluginRuntime, normalizeBasePath, basePathSegments, } from "@dogsbay/format-astro";
|
|
21
|
+
import { collectPassthroughEntries } from "../passthrough-astro.js";
|
|
21
22
|
import { configToAstroOptions, findConfig, loadConfig, resolveOutputDir, } from "../config/index.js";
|
|
22
23
|
import { filterSourcesForMode, importContent, primaryModeFiltersAnything, } from "../import-content.js";
|
|
23
24
|
import { resolveSource } from "../source-resolver.js";
|
|
@@ -170,7 +171,30 @@ export async function siteBuild(cwd, options) {
|
|
|
170
171
|
// config-derived, so changes in dogsbay.config.yml propagate without
|
|
171
172
|
// re-running site init.
|
|
172
173
|
emitSiteConfig(outputDir, config.site.name, astroOpts);
|
|
173
|
-
const { generated, outputNav } = await emitAstroPages(pages, nav, outputDir, astroOpts);
|
|
174
|
+
const { generated, outputNav, generatedPaths } = await emitAstroPages(pages, nav, outputDir, astroOpts);
|
|
175
|
+
// Passthrough Astro pages — hand-authored .astro files that live
|
|
176
|
+
// alongside the markdown sources and are listed in nav.yml. Picked
|
|
177
|
+
// up by walking the source directories for *.astro and intersecting
|
|
178
|
+
// with the resolved nav hrefs. Collisions with generated pages are
|
|
179
|
+
// a build error (loud, not silent overwrite). See
|
|
180
|
+
// plans/passthrough-astro-pages.md.
|
|
181
|
+
const passthroughCopies = [];
|
|
182
|
+
for (const sourceDir of resolvedSources) {
|
|
183
|
+
const entries = collectPassthroughEntries(sourceDir, outputNav, {
|
|
184
|
+
basePath: normalizeBasePath(astroOpts.basePath),
|
|
185
|
+
});
|
|
186
|
+
for (const entry of entries) {
|
|
187
|
+
passthroughCopies.push({
|
|
188
|
+
source: entry.source,
|
|
189
|
+
sourceAbs: entry.sourceAbs,
|
|
190
|
+
outputRelPath: entry.outputRelPath,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (passthroughCopies.length > 0) {
|
|
195
|
+
const { copied } = emitPassthroughAstroPages(passthroughCopies, outputDir, generatedPaths);
|
|
196
|
+
console.log(` Copied ${copied} passthrough .astro page${copied === 1 ? "" : "s"}`);
|
|
197
|
+
}
|
|
174
198
|
emitConfigDerivedFiles(outputDir, astroOpts);
|
|
175
199
|
emitAgentReadinessFiles(pages, outputNav, outputDir, config.site.name, astroOpts);
|
|
176
200
|
// switcherMap.json — per-page version-equivalent lookup. No-op
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Passthrough Astro page collection.
|
|
3
|
+
*
|
|
4
|
+
* Hand-authored `.astro` files that live under a content source's
|
|
5
|
+
* directory get copied verbatim to `src/pages/<basePath>/...` at
|
|
6
|
+
* build time. Authors opt in by listing the file in `nav.yml`; the
|
|
7
|
+
* intersection of "files on disk" and "hrefs in nav" defines the
|
|
8
|
+
* passthrough set.
|
|
9
|
+
*
|
|
10
|
+
* See plans/passthrough-astro-pages.md.
|
|
11
|
+
*/
|
|
12
|
+
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
13
|
+
import { join, posix, relative, sep } from "node:path";
|
|
14
|
+
/**
|
|
15
|
+
* Walk `contentDir` for `.astro` files, compute each one's canonical
|
|
16
|
+
* href, and return only the entries whose href appears in the
|
|
17
|
+
* resolved nav. Files outside the nav are ignored — passthrough is
|
|
18
|
+
* opt-in to avoid accidentally publishing scratch components.
|
|
19
|
+
*/
|
|
20
|
+
export function collectPassthroughEntries(contentDir, nav, options) {
|
|
21
|
+
if (!existsSync(contentDir))
|
|
22
|
+
return [];
|
|
23
|
+
const navHrefs = collectNavHrefs(nav);
|
|
24
|
+
const candidates = walkAstroFiles(contentDir, contentDir);
|
|
25
|
+
const entries = [];
|
|
26
|
+
for (const sourceAbs of candidates) {
|
|
27
|
+
const source = toPosix(relative(contentDir, sourceAbs));
|
|
28
|
+
const href = sourceToHref(source, options.basePath);
|
|
29
|
+
if (!navHrefs.has(href))
|
|
30
|
+
continue;
|
|
31
|
+
entries.push({
|
|
32
|
+
source,
|
|
33
|
+
sourceAbs,
|
|
34
|
+
outputRelPath: sourceToOutputRelPath(source, options.basePath),
|
|
35
|
+
href,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return entries;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Recursively gather every `.astro` file under `dir`. Skips
|
|
42
|
+
* `node_modules`, dot-directories, and any directory named
|
|
43
|
+
* `_components` / `_partials` (a common convention for "this is
|
|
44
|
+
* shared, don't publish it" — leaves authors an obvious escape
|
|
45
|
+
* hatch when they want to ship private helpers alongside content).
|
|
46
|
+
*/
|
|
47
|
+
function walkAstroFiles(dir, root) {
|
|
48
|
+
const out = [];
|
|
49
|
+
let entries = [];
|
|
50
|
+
try {
|
|
51
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
for (const entry of entries) {
|
|
57
|
+
if (entry.name.startsWith("."))
|
|
58
|
+
continue;
|
|
59
|
+
if (entry.name === "node_modules")
|
|
60
|
+
continue;
|
|
61
|
+
if (entry.name === "_components" || entry.name === "_partials")
|
|
62
|
+
continue;
|
|
63
|
+
const full = join(dir, entry.name);
|
|
64
|
+
if (entry.isDirectory()) {
|
|
65
|
+
out.push(...walkAstroFiles(full, root));
|
|
66
|
+
}
|
|
67
|
+
else if (entry.isFile() && entry.name.endsWith(".astro")) {
|
|
68
|
+
out.push(full);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Compute the public-facing href for a source path. Mirrors the
|
|
75
|
+
* slug logic in `format-dogsbay-md/src/nav-file.ts:fileToHref` so a
|
|
76
|
+
* passthrough .astro file produces the same href as a hypothetical
|
|
77
|
+
* .md sibling at the same path.
|
|
78
|
+
*
|
|
79
|
+
* - "tutorials/playground.astro" → "/docs/tutorials/playground"
|
|
80
|
+
* - "reference/index.astro" → "/docs/reference"
|
|
81
|
+
* - "index.astro" → "/docs"
|
|
82
|
+
*/
|
|
83
|
+
function sourceToHref(source, basePath) {
|
|
84
|
+
let slug = source.replace(/\.astro$/i, "");
|
|
85
|
+
if (slug.endsWith("/index"))
|
|
86
|
+
slug = slug.slice(0, -"/index".length);
|
|
87
|
+
if (slug === "index")
|
|
88
|
+
slug = "";
|
|
89
|
+
const prefix = basePath.endsWith("/") ? basePath.slice(0, -1) : basePath;
|
|
90
|
+
return slug ? `${prefix}/${slug}` : prefix || "";
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Compute the output path (relative to outputDir) where a passthrough
|
|
94
|
+
* source should be copied. Preserves the source's directory shape
|
|
95
|
+
* under `src/pages/<basePath segments>/`. Index files survive as
|
|
96
|
+
* `index.astro` so Astro's directory-routing matches the nav href.
|
|
97
|
+
*/
|
|
98
|
+
function sourceToOutputRelPath(source, basePath) {
|
|
99
|
+
const baseSegs = basePath.split("/").filter((s) => s.length > 0);
|
|
100
|
+
const sourceSegs = source.split("/");
|
|
101
|
+
return ["src", "pages", ...baseSegs, ...sourceSegs].join(sep);
|
|
102
|
+
}
|
|
103
|
+
/** Walk a NavItem tree collecting every href into a Set. */
|
|
104
|
+
function collectNavHrefs(nav) {
|
|
105
|
+
const out = new Set();
|
|
106
|
+
const stack = [...nav];
|
|
107
|
+
while (stack.length > 0) {
|
|
108
|
+
const item = stack.pop();
|
|
109
|
+
if (item.href)
|
|
110
|
+
out.add(item.href);
|
|
111
|
+
if (item.children)
|
|
112
|
+
stack.push(...item.children);
|
|
113
|
+
}
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Normalize backslashes (Windows path separators) to forward slashes
|
|
118
|
+
* before doing any href / slug computation. The slug shape is
|
|
119
|
+
* URL-flavoured even on Windows.
|
|
120
|
+
*/
|
|
121
|
+
function toPosix(p) {
|
|
122
|
+
return p.split(sep).join(posix.sep);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Build the slug set covered by the passthrough entries. Used by
|
|
126
|
+
* site-build to assert no collision with generated slugs from
|
|
127
|
+
* `emitAstroPages`.
|
|
128
|
+
*/
|
|
129
|
+
export function passthroughSlugs(entries) {
|
|
130
|
+
return new Set(entries.map((e) => e.href));
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Convenience guard — verify every passthrough source still exists
|
|
134
|
+
* on disk. Walking already only returns existing files, but this
|
|
135
|
+
* helper is useful when entries are constructed externally (e.g. in
|
|
136
|
+
* tests).
|
|
137
|
+
*/
|
|
138
|
+
export function assertPassthroughFilesExist(entries) {
|
|
139
|
+
for (const entry of entries) {
|
|
140
|
+
if (!existsSync(entry.sourceAbs)) {
|
|
141
|
+
throw new Error(`Passthrough Astro source missing: ${entry.source} ` +
|
|
142
|
+
`(resolved: ${entry.sourceAbs})`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// Touch statSync to detect non-file entries (defensive)
|
|
146
|
+
for (const entry of entries) {
|
|
147
|
+
const st = statSync(entry.sourceAbs);
|
|
148
|
+
if (!st.isFile()) {
|
|
149
|
+
throw new Error(`Passthrough Astro source is not a file: ${entry.source}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
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.18",
|
|
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": {
|
|
@@ -32,14 +32,14 @@
|
|
|
32
32
|
"picocolors": "^1.1.0",
|
|
33
33
|
"prompts": "^2.4.2",
|
|
34
34
|
"yaml": "^2.8.3",
|
|
35
|
-
"@dogsbay/format-mkdocs": "0.2.0-beta.
|
|
36
|
-
"@dogsbay/format-
|
|
37
|
-
"@dogsbay/format-
|
|
38
|
-
"@dogsbay/format-
|
|
39
|
-
"@dogsbay/format-
|
|
40
|
-
"@dogsbay/format-
|
|
41
|
-
"@dogsbay/format-
|
|
42
|
-
"@dogsbay/types": "0.2.0-beta.
|
|
35
|
+
"@dogsbay/format-mkdocs": "0.2.0-beta.18",
|
|
36
|
+
"@dogsbay/format-obsidian": "0.2.0-beta.18",
|
|
37
|
+
"@dogsbay/format-mdx": "0.2.0-beta.18",
|
|
38
|
+
"@dogsbay/format-starlight": "0.2.0-beta.18",
|
|
39
|
+
"@dogsbay/format-dogsbay-md": "0.2.0-beta.18",
|
|
40
|
+
"@dogsbay/format-openapi": "0.2.0-beta.18",
|
|
41
|
+
"@dogsbay/format-astro": "0.2.0-beta.18",
|
|
42
|
+
"@dogsbay/types": "0.2.0-beta.18"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@types/node": "^22.0.0",
|