dogsbay 0.2.0-beta.17 → 0.2.0-beta.19
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 +34 -2
- package/dist/commands/site-init.js +3 -0
- package/dist/index.js +2 -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, emitDeployArtifacts, 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,8 +171,36 @@ 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);
|
|
199
|
+
// Deploy artifacts — write-if-missing so adding `deploy: github-pages`
|
|
200
|
+
// to dogsbay.config.yml on an existing site materializes the workflow
|
|
201
|
+
// on the next build. Author edits survive (only fired when the file
|
|
202
|
+
// doesn't exist; --force regeneration goes through site init).
|
|
203
|
+
emitDeployArtifacts(outputDir, astroOpts);
|
|
175
204
|
emitAgentReadinessFiles(pages, outputNav, outputDir, config.site.name, astroOpts);
|
|
176
205
|
// switcherMap.json — per-page version-equivalent lookup. No-op
|
|
177
206
|
// when the version axis isn't active; cleans up a stale file
|
|
@@ -325,6 +354,9 @@ function mergeOverrides(config, opts) {
|
|
|
325
354
|
if (opts.deploy === "cloudflare-workers") {
|
|
326
355
|
next.deploy = { target: "cloudflare-workers" };
|
|
327
356
|
}
|
|
357
|
+
else if (opts.deploy === "github-pages") {
|
|
358
|
+
next.deploy = { target: "github-pages" };
|
|
359
|
+
}
|
|
328
360
|
// Analytics
|
|
329
361
|
if (opts.plausibleDomain) {
|
|
330
362
|
next.analytics = next.analytics ?? {};
|
|
@@ -533,6 +533,9 @@ function buildConfig(opts) {
|
|
|
533
533
|
if (opts.deploy === "cloudflare-workers") {
|
|
534
534
|
config.deploy = { target: "cloudflare-workers" };
|
|
535
535
|
}
|
|
536
|
+
else if (opts.deploy === "github-pages") {
|
|
537
|
+
config.deploy = { target: "github-pages" };
|
|
538
|
+
}
|
|
536
539
|
if (opts.plausibleDomain?.trim()) {
|
|
537
540
|
config.analytics = {
|
|
538
541
|
plausible: {
|
package/dist/index.js
CHANGED
|
@@ -63,7 +63,7 @@ site
|
|
|
63
63
|
.option("--edit-uri <path>", "Repo path prefix for edit links (default: blob/main/content/)")
|
|
64
64
|
.option("--copyright <text>", "Footer copyright text")
|
|
65
65
|
.option("--theme <name>", "Theme preset (default | material)")
|
|
66
|
-
.option("--deploy <target>", "Deploy target (cloudflare-workers)")
|
|
66
|
+
.option("--deploy <target>", "Deploy target (cloudflare-workers | github-pages)")
|
|
67
67
|
.option("--content <path>", "Path to source markdown (relative to config)")
|
|
68
68
|
.option("--from <format>", "Source format (auto | dogsbay-md | mkdocs | obsidian | starlight | mdx)")
|
|
69
69
|
.option("--nav <path>", "Path to explicit nav file (.json/.yml)")
|
|
@@ -97,7 +97,7 @@ site
|
|
|
97
97
|
.option("--edit-uri <path>", "Override site.editUri")
|
|
98
98
|
.option("--copyright <text>", "Override site.copyright")
|
|
99
99
|
.option("--theme <name>", "Override theme (default | material)")
|
|
100
|
-
.option("--deploy <target>", "Override deploy.target (cloudflare-workers)")
|
|
100
|
+
.option("--deploy <target>", "Override deploy.target (cloudflare-workers | github-pages)")
|
|
101
101
|
.option("--content <path>", "Override content.source")
|
|
102
102
|
.option("--from <format>", "Override content.from")
|
|
103
103
|
.option("--nav <path>", "Override content.nav")
|
|
@@ -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.19",
|
|
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-
|
|
36
|
-
"@dogsbay/format-
|
|
37
|
-
"@dogsbay/format-
|
|
38
|
-
"@dogsbay/format-mdx": "0.2.0-beta.
|
|
39
|
-
"@dogsbay/format-starlight": "0.2.0-beta.
|
|
40
|
-
"@dogsbay/format-dogsbay-md": "0.2.0-beta.
|
|
41
|
-
"@dogsbay/format-openapi": "0.2.0-beta.
|
|
42
|
-
"@dogsbay/types": "0.2.0-beta.
|
|
35
|
+
"@dogsbay/format-astro": "0.2.0-beta.19",
|
|
36
|
+
"@dogsbay/format-obsidian": "0.2.0-beta.19",
|
|
37
|
+
"@dogsbay/format-mkdocs": "0.2.0-beta.19",
|
|
38
|
+
"@dogsbay/format-mdx": "0.2.0-beta.19",
|
|
39
|
+
"@dogsbay/format-starlight": "0.2.0-beta.19",
|
|
40
|
+
"@dogsbay/format-dogsbay-md": "0.2.0-beta.19",
|
|
41
|
+
"@dogsbay/format-openapi": "0.2.0-beta.19",
|
|
42
|
+
"@dogsbay/types": "0.2.0-beta.19"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@types/node": "^22.0.0",
|