dogsbay 0.2.0-beta.2 → 0.2.0-beta.21
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/agent.js +305 -0
- package/dist/commands/site-build.js +66 -15
- package/dist/commands/site-dev.js +181 -23
- package/dist/commands/site-init.js +193 -32
- package/dist/config/defaults.js +8 -1
- package/dist/config/load.js +6 -32
- package/dist/config/to-astro-options.js +1 -0
- package/dist/import-content.js +13 -12
- package/dist/index.js +19 -4
- package/dist/passthrough-astro.js +152 -0
- package/dist/registry.js +8 -0
- package/package.json +11 -9
- package/skills/platform/agent-readiness/SKILL.md +262 -0
- package/skills/platform/cli-commands/SKILL.md +205 -0
- package/skills/platform/config-yml/SKILL.md +219 -0
- package/skills/platform/frontmatter-fields/SKILL.md +310 -0
- package/skills/platform/markdown-directives/SKILL.md +329 -0
- package/skills/platform/multi-source/SKILL.md +294 -0
- package/skills/platform/nav-file/SKILL.md +107 -0
- package/skills/platform/openapi-source/SKILL.md +237 -0
- package/skills/platform/plugin-api/SKILL.md +280 -0
- package/skills/platform/project-anatomy/SKILL.md +156 -0
- package/skills/platform/taxonomy-config/SKILL.md +392 -0
- package/skills/platform/theme-tokens/SKILL.md +276 -0
|
@@ -1,24 +1,34 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `dogsbay site dev` and `dogsbay site preview` — thin wrappers that
|
|
3
|
-
* run `dogsbay site build
|
|
4
|
-
* CLI inside the site directory.
|
|
3
|
+
* run `dogsbay site build`, watch content for changes, and hand
|
|
4
|
+
* control over to the Astro CLI inside the site directory.
|
|
5
5
|
*
|
|
6
|
-
* dev → astro dev
|
|
7
|
-
* preview →
|
|
6
|
+
* dev → astro dev (live HMR for already-built pages)
|
|
7
|
+
* preview → <pm> run build → astro preview
|
|
8
8
|
*
|
|
9
9
|
* The site dir is found by locating `dogsbay.config.{yml,yaml,json}`
|
|
10
|
-
* at or above cwd (or via --config).
|
|
11
|
-
* existing script) is *not* what runs here — we shell out to `astro`
|
|
12
|
-
* directly so this works whether the user uses pnpm / npm / yarn.
|
|
10
|
+
* at or above cwd (or via --config).
|
|
13
11
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
12
|
+
* site dev shells `npx astro dev` directly — fast, no script
|
|
13
|
+
* indirection. site preview needs the production build to match
|
|
14
|
+
* what's actually deployed, which means running the scaffolded
|
|
15
|
+
* package.json `build` script (chains `astro build && pagefind
|
|
16
|
+
* --site dist` so Cmd+K search works in the previewed dist/). The
|
|
17
|
+
* package manager is auto-detected (pnpm if on PATH, otherwise npm).
|
|
18
|
+
*
|
|
19
|
+
* `site dev` also installs a content watcher that re-runs
|
|
20
|
+
* `dogsbay site build` whenever a markdown / yaml / json file under
|
|
21
|
+
* any configured source path changes (or `dogsbay.config.yml`
|
|
22
|
+
* itself). Astro's own dev server then hot-reloads the regenerated
|
|
23
|
+
* `.astro` pages. Without this, NEW files in `content/` weren't
|
|
24
|
+
* picked up by site dev — only edits to existing files surfaced
|
|
25
|
+
* (because Astro's watcher only sees `astro/src/`).
|
|
17
26
|
*/
|
|
18
27
|
import { spawn } from "node:child_process";
|
|
19
28
|
import { existsSync } from "node:fs";
|
|
20
29
|
import { dirname, join, resolve } from "node:path";
|
|
21
30
|
import pc from "picocolors";
|
|
31
|
+
import chokidar from "chokidar";
|
|
22
32
|
import { findConfig, loadConfig, resolveOutputDir } from "../config/index.js";
|
|
23
33
|
import { siteBuild } from "./site-build.js";
|
|
24
34
|
const defaultRunner = (siteRoot, args) => new Promise((resolve) => {
|
|
@@ -32,6 +42,18 @@ const defaultRunner = (siteRoot, args) => new Promise((resolve) => {
|
|
|
32
42
|
resolve(1);
|
|
33
43
|
});
|
|
34
44
|
});
|
|
45
|
+
const defaultBuildRunner = (siteRoot) => new Promise((resolve) => {
|
|
46
|
+
const pm = pickPackageManager();
|
|
47
|
+
const child = spawn(pm, ["run", "build"], {
|
|
48
|
+
cwd: siteRoot,
|
|
49
|
+
stdio: "inherit",
|
|
50
|
+
});
|
|
51
|
+
child.on("exit", (code) => resolve(code ?? 0));
|
|
52
|
+
child.on("error", (err) => {
|
|
53
|
+
console.error(pc.red(`Error: failed to spawn ${pm} run build: ${err.message}`));
|
|
54
|
+
resolve(1);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
35
57
|
/**
|
|
36
58
|
* Pick the first available package manager from a preference list.
|
|
37
59
|
* Defaults to pnpm (matches the dogsbay tooling chain); falls back
|
|
@@ -63,18 +85,28 @@ function runPackageManagerInstall(pm, cwd) {
|
|
|
63
85
|
});
|
|
64
86
|
}
|
|
65
87
|
export async function siteDev(cwd, options, runner = defaultRunner) {
|
|
66
|
-
const siteRoot = await prepareForAstro(cwd, options);
|
|
67
|
-
const
|
|
68
|
-
|
|
88
|
+
const { siteRoot, outputDir } = await prepareForAstro(cwd, options);
|
|
89
|
+
const stopWatcher = startContentWatcher(siteRoot, outputDir, options);
|
|
90
|
+
try {
|
|
91
|
+
const code = await runner(outputDir, ["dev"]);
|
|
92
|
+
stopWatcher();
|
|
93
|
+
process.exit(code);
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
stopWatcher();
|
|
97
|
+
throw err;
|
|
98
|
+
}
|
|
69
99
|
}
|
|
70
|
-
export async function sitePreview(cwd, options, runner = defaultRunner) {
|
|
71
|
-
const
|
|
72
|
-
// Two-step: produce dist/
|
|
73
|
-
//
|
|
74
|
-
|
|
100
|
+
export async function sitePreview(cwd, options, runner = defaultRunner, buildRunner = defaultBuildRunner) {
|
|
101
|
+
const { outputDir } = await prepareForAstro(cwd, options);
|
|
102
|
+
// Two-step: produce dist/ via the scaffolded `build` script
|
|
103
|
+
// (astro build + pagefind), then serve it via astro preview. The
|
|
104
|
+
// build script is the source of truth — `astro build` alone would
|
|
105
|
+
// skip pagefind and Cmd+K search in the previewed dist/ would 404.
|
|
106
|
+
const buildCode = await buildRunner(outputDir);
|
|
75
107
|
if (buildCode !== 0)
|
|
76
108
|
process.exit(buildCode);
|
|
77
|
-
const previewCode = await runner(
|
|
109
|
+
const previewCode = await runner(outputDir, ["preview"]);
|
|
78
110
|
process.exit(previewCode);
|
|
79
111
|
}
|
|
80
112
|
async function prepareForAstro(cwd, options) {
|
|
@@ -110,14 +142,140 @@ async function prepareForAstro(cwd, options) {
|
|
|
110
142
|
if (!options.noBuild) {
|
|
111
143
|
// Drafts visible during local preview — site dev is the writer's
|
|
112
144
|
// iteration loop. Production `dogsbay site build` filters drafts.
|
|
113
|
-
//
|
|
114
|
-
// matrix for previewing switcher chrome.
|
|
145
|
+
// site dev defaults to primary-only for fast iteration; --full
|
|
146
|
+
// opts into the publish matrix for previewing switcher chrome.
|
|
147
|
+
// (Production `dogsbay site build` defaults to full matrix; only
|
|
148
|
+
// site dev / preview default to primary-only.)
|
|
115
149
|
await siteBuild(siteRoot, {
|
|
116
150
|
includeDrafts: true,
|
|
117
|
-
|
|
151
|
+
primaryOnly: options.full !== true,
|
|
118
152
|
});
|
|
119
153
|
}
|
|
120
|
-
return outputDir;
|
|
154
|
+
return { siteRoot, outputDir };
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Watch the project's content paths + config file, and re-run
|
|
158
|
+
* `dogsbay site build` whenever something changes. Astro's own
|
|
159
|
+
* watcher then picks up the regenerated `astro/src/pages/*.astro`
|
|
160
|
+
* files and hot-reloads.
|
|
161
|
+
*
|
|
162
|
+
* Returns a cleanup fn that closes all the watchers.
|
|
163
|
+
*
|
|
164
|
+
* Debounces aggressively — many editors fire 3-5 fs events per save
|
|
165
|
+
* (write + rename + close-write etc.), and a single Vim save bursts
|
|
166
|
+
* across multiple files. 300ms is the sweet spot: low enough that
|
|
167
|
+
* the user feels the rebuild as immediate, high enough to coalesce
|
|
168
|
+
* a save burst into one rebuild.
|
|
169
|
+
*/
|
|
170
|
+
function startContentWatcher(siteRoot, outputDir, options) {
|
|
171
|
+
// Re-load the config to know which paths to watch. We load it
|
|
172
|
+
// from disk on first event too (so config edits update the watch
|
|
173
|
+
// set on the fly).
|
|
174
|
+
let config = loadConfig(findOrFail(siteRoot, options.config));
|
|
175
|
+
// chokidar (vs Node's fs.watch) — fs.watch with `recursive: true`
|
|
176
|
+
// on Linux drops events from atomic-replace editor saves (Vim,
|
|
177
|
+
// VS Code, Helix all write tmp + rename), so nav.yml edits never
|
|
178
|
+
// surfaced as [dogsbay] rebuilds. chokidar handles atomic-replace
|
|
179
|
+
// correctly because it watches paths by name and re-arms on
|
|
180
|
+
// rename. `awaitWriteFinish` further coalesces multi-event saves
|
|
181
|
+
// into a single emit.
|
|
182
|
+
//
|
|
183
|
+
// Watch siteRoot recursively + each declared source path. The
|
|
184
|
+
// top-level `ignored` patterns keep us out of the output dir
|
|
185
|
+
// (otherwise rebuilds would self-trigger), VCS metadata, OS
|
|
186
|
+
// junk, and editor swap files.
|
|
187
|
+
const watchPaths = collectWatchPaths(siteRoot, config);
|
|
188
|
+
const watcher = chokidar.watch(watchPaths, {
|
|
189
|
+
ignoreInitial: true,
|
|
190
|
+
ignored: [
|
|
191
|
+
/(^|[\\/])\.git([\\/]|$)/,
|
|
192
|
+
/(^|[\\/])node_modules([\\/]|$)/,
|
|
193
|
+
/(^|[\\/])astro([\\/]|$)/,
|
|
194
|
+
/(^|[\\/])dist([\\/]|$)/,
|
|
195
|
+
/(^|[\\/])\.dogsbay([\\/]|$)/,
|
|
196
|
+
/\.swp$/,
|
|
197
|
+
/\.swo$/,
|
|
198
|
+
/~$/,
|
|
199
|
+
/\.DS_Store$/,
|
|
200
|
+
],
|
|
201
|
+
awaitWriteFinish: {
|
|
202
|
+
stabilityThreshold: 50,
|
|
203
|
+
pollInterval: 10,
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
// Debounced rebuild loop. If a build is in flight when a new
|
|
207
|
+
// event arrives, mark dirty + rebuild after the current one
|
|
208
|
+
// finishes. Avoids overlapping builds racing on the same files.
|
|
209
|
+
let timer = null;
|
|
210
|
+
let building = false;
|
|
211
|
+
let dirty = false;
|
|
212
|
+
const scheduleBuild = () => {
|
|
213
|
+
dirty = true;
|
|
214
|
+
if (timer)
|
|
215
|
+
clearTimeout(timer);
|
|
216
|
+
timer = setTimeout(runBuild, 300);
|
|
217
|
+
};
|
|
218
|
+
const runBuild = async () => {
|
|
219
|
+
if (building)
|
|
220
|
+
return; // a build is in flight; the dirty flag handles re-run
|
|
221
|
+
if (!dirty)
|
|
222
|
+
return;
|
|
223
|
+
dirty = false;
|
|
224
|
+
building = true;
|
|
225
|
+
try {
|
|
226
|
+
console.log(pc.cyan("[dogsbay] content changed — rebuilding…"));
|
|
227
|
+
// Reload config to pick up dogsbay.config.yml edits.
|
|
228
|
+
config = loadConfig(findOrFail(siteRoot, options.config));
|
|
229
|
+
// Re-arm any newly-added source paths (chokidar dedupes
|
|
230
|
+
// already-watched roots internally).
|
|
231
|
+
const newPaths = collectWatchPaths(siteRoot, config);
|
|
232
|
+
watcher.add(newPaths);
|
|
233
|
+
await siteBuild(siteRoot, {
|
|
234
|
+
includeDrafts: true,
|
|
235
|
+
primaryOnly: options.full !== true,
|
|
236
|
+
});
|
|
237
|
+
console.log(pc.green("[dogsbay] rebuild complete"));
|
|
238
|
+
}
|
|
239
|
+
catch (err) {
|
|
240
|
+
console.error(pc.red(`[dogsbay] rebuild failed: ${err.message}`));
|
|
241
|
+
}
|
|
242
|
+
finally {
|
|
243
|
+
building = false;
|
|
244
|
+
// Coalesced changes during the build → run again.
|
|
245
|
+
if (dirty)
|
|
246
|
+
setImmediate(runBuild);
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
watcher.on("all", (_event, _path) => scheduleBuild());
|
|
250
|
+
// Diagnostic: show what's being watched on startup.
|
|
251
|
+
console.log(pc.gray(`[dogsbay] watching ${watchPaths.length} path${watchPaths.length === 1 ? "" : "s"} for content changes`));
|
|
252
|
+
return () => {
|
|
253
|
+
if (timer)
|
|
254
|
+
clearTimeout(timer);
|
|
255
|
+
void watcher.close();
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Collect the absolute paths chokidar should watch: the site root
|
|
260
|
+
* (so `dogsbay.config.yml` edits trigger a reload) plus every
|
|
261
|
+
* declared `content.sources[].path`. Skips entries that don't exist
|
|
262
|
+
* on disk yet — chokidar would otherwise log a noisy ENOENT.
|
|
263
|
+
*/
|
|
264
|
+
function collectWatchPaths(siteRoot, config) {
|
|
265
|
+
const out = new Set();
|
|
266
|
+
if (existsSync(siteRoot))
|
|
267
|
+
out.add(siteRoot);
|
|
268
|
+
for (const source of config.content?.sources ?? []) {
|
|
269
|
+
if (typeof source.path === "string") {
|
|
270
|
+
const abs = resolve(siteRoot, source.path);
|
|
271
|
+
if (existsSync(abs))
|
|
272
|
+
out.add(abs);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return [...out];
|
|
276
|
+
}
|
|
277
|
+
function findOrFail(siteRoot, explicit) {
|
|
278
|
+
return resolveConfigPath(siteRoot, explicit);
|
|
121
279
|
}
|
|
122
280
|
function resolveConfigPath(startDir, explicit) {
|
|
123
281
|
if (explicit) {
|
|
@@ -101,61 +101,214 @@ export async function siteInit(targetDir, options) {
|
|
|
101
101
|
}
|
|
102
102
|
// Emit the static scaffold into the configured output dir
|
|
103
103
|
// (default ./astro). The project root keeps only the config and
|
|
104
|
-
// human-edited files (content/, theme/, public/).
|
|
104
|
+
// human-edited files (content/, theme/, public/). projectDir
|
|
105
|
+
// (= absTarget) lets deploy emitters write artifacts that GitHub
|
|
106
|
+
// Actions reads from the repo root rather than the Astro subdir.
|
|
105
107
|
const outputDir = resolveOutputDir(config, configPath);
|
|
106
108
|
const astroOpts = configToAstroOptions(config);
|
|
109
|
+
astroOpts.projectDir = absTarget;
|
|
107
110
|
if (options.local)
|
|
108
111
|
astroOpts.local = true;
|
|
109
112
|
emitSiteScaffold(outputDir, config.site.name, astroOpts, true);
|
|
110
113
|
// Seed starter content so the first `dogsbay site build` succeeds
|
|
111
|
-
// without manual intervention.
|
|
112
|
-
// init (writeConfig=true;
|
|
113
|
-
//
|
|
114
|
-
// overwrites user files. See
|
|
115
|
-
|
|
114
|
+
// without manual intervention. Writes index.md, getting-started.md,
|
|
115
|
+
// and nav.yml when (a) this is a fresh init (writeConfig=true;
|
|
116
|
+
// scaffold-only consumers already have content) and (b) the
|
|
117
|
+
// content dir doesn't exist yet. Never overwrites user files. See
|
|
118
|
+
// plans/beta-launch-followups.md.
|
|
119
|
+
let starterContentPaths = [];
|
|
116
120
|
if (writeConfig) {
|
|
117
|
-
|
|
121
|
+
starterContentPaths = seedStarterContent(absTarget, config);
|
|
118
122
|
}
|
|
119
|
-
printNextSteps(absTarget, outputDir, config,
|
|
123
|
+
printNextSteps(absTarget, outputDir, config, starterContentPaths);
|
|
120
124
|
}
|
|
121
125
|
/**
|
|
122
|
-
* Create a starter
|
|
123
|
-
*
|
|
124
|
-
*
|
|
126
|
+
* Create a starter content set if the content dir doesn't already
|
|
127
|
+
* exist. Writes `index.md`, `getting-started.md`, and `nav.yml`
|
|
128
|
+
* so the user has a working multi-page site with a navigation
|
|
129
|
+
* structure to learn from. Returns the list of paths written, or
|
|
130
|
+
* an empty array when nothing was created. Never overwrites.
|
|
125
131
|
*/
|
|
126
132
|
function seedStarterContent(absTarget, config) {
|
|
127
133
|
const sources = config.content.sources ?? [];
|
|
128
134
|
const first = sources[0];
|
|
129
135
|
if (!first?.path)
|
|
130
|
-
return
|
|
136
|
+
return [];
|
|
131
137
|
const contentDir = isAbsolute(first.path)
|
|
132
138
|
? first.path
|
|
133
139
|
: join(absTarget, first.path);
|
|
134
140
|
if (existsSync(contentDir))
|
|
135
|
-
return
|
|
141
|
+
return []; // user already has content
|
|
136
142
|
mkdirSync(contentDir, { recursive: true });
|
|
143
|
+
const written = [];
|
|
144
|
+
const siteName = config.site.name?.trim() || "your documentation site";
|
|
145
|
+
const basePath = config.site.basePath ?? "/docs";
|
|
137
146
|
const indexPath = join(contentDir, "index.md");
|
|
138
147
|
writeFileSync(indexPath, `---
|
|
139
|
-
title:
|
|
148
|
+
title: Welcome
|
|
140
149
|
description: Edit content/index.md to get started.
|
|
141
150
|
---
|
|
142
151
|
|
|
143
|
-
#
|
|
152
|
+
# Welcome
|
|
144
153
|
|
|
145
|
-
This is
|
|
146
|
-
|
|
154
|
+
This is the starting point of ${siteName}, built with
|
|
155
|
+
[Dogsbay](https://github.com/dogsbay/dogsbay). Replace this
|
|
156
|
+
content with your own — every \`.md\` file under \`content/\` becomes
|
|
157
|
+
a page.
|
|
147
158
|
|
|
148
|
-
|
|
159
|
+
> [!TIP]
|
|
160
|
+
> Edit this file (\`content/index.md\`) and save. The dev server
|
|
161
|
+
> reloads automatically.
|
|
149
162
|
|
|
150
|
-
|
|
151
|
-
- Add more \`.md\` files alongside it for additional pages.
|
|
152
|
-
- Run \`dogsbay site dev\` to preview live as you edit.
|
|
153
|
-
- See \`dogsbay.config.yml\` for site-wide settings (theme, agent
|
|
154
|
-
readiness, deploy target, etc.).
|
|
163
|
+
## Get started in 60 seconds
|
|
155
164
|
|
|
156
|
-
|
|
165
|
+
:::steps
|
|
166
|
+
1. **Edit a page**
|
|
167
|
+
Open \`content/index.md\` and change something. Save — the
|
|
168
|
+
preview updates live.
|
|
169
|
+
|
|
170
|
+
2. **Add a page**
|
|
171
|
+
Create \`content/about.md\` with frontmatter and a heading.
|
|
172
|
+
|
|
173
|
+
3. **Wire it in**
|
|
174
|
+
Add the new page to \`content/nav.yml\` so it shows up in the
|
|
175
|
+
sidebar.
|
|
176
|
+
:::
|
|
177
|
+
|
|
178
|
+
## Where to go next
|
|
179
|
+
|
|
180
|
+
:::cards
|
|
181
|
+
- **[Getting started](${basePath}/getting-started)** {icon="rocket"}
|
|
182
|
+
Three-minute orientation to editing, adding, and grouping pages.
|
|
183
|
+
|
|
184
|
+
- **[Configuration](${basePath}/getting-started)** {icon="settings"}
|
|
185
|
+
Site name, theme, base path, and per-source settings live in
|
|
186
|
+
\`dogsbay.config.yml\`.
|
|
187
|
+
|
|
188
|
+
- **[Source on GitHub](https://github.com/dogsbay/dogsbay)** {icon="github"}
|
|
189
|
+
Star, browse, file an issue, or follow the roadmap.
|
|
190
|
+
|
|
191
|
+
- **[Plugins](https://github.com/dogsbay/dogsbay/tree/main/docs)** {icon="puzzle"}
|
|
192
|
+
Image zoom, TypeDoc, and your own — the plugin API is small,
|
|
193
|
+
typed, and explicit.
|
|
194
|
+
:::
|
|
195
|
+
|
|
196
|
+
## Markdown that does more
|
|
197
|
+
|
|
198
|
+
Cards, steps, tabs, callouts, fenced code — Dogsbay markdown is
|
|
199
|
+
a small superset of CommonMark with directives for the components
|
|
200
|
+
docs sites need most. Here's the same config in two formats:
|
|
201
|
+
|
|
202
|
+
:::tabs
|
|
203
|
+
YAML
|
|
204
|
+
: \`\`\`yaml
|
|
205
|
+
site:
|
|
206
|
+
name: ${siteName.includes("documentation") ? "Acme Docs" : siteName}
|
|
207
|
+
url: https://example.com
|
|
208
|
+
content:
|
|
209
|
+
sources:
|
|
210
|
+
- path: ./content
|
|
211
|
+
from: dogsbay-md
|
|
212
|
+
\`\`\`
|
|
213
|
+
|
|
214
|
+
JSON
|
|
215
|
+
: \`\`\`json
|
|
216
|
+
{
|
|
217
|
+
"site": { "name": "${siteName.includes("documentation") ? "Acme Docs" : siteName}" },
|
|
218
|
+
"content": {
|
|
219
|
+
"sources": [{ "path": "./content", "from": "dogsbay-md" }]
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
\`\`\`
|
|
223
|
+
:::
|
|
224
|
+
|
|
225
|
+
For the full markdown reference, see
|
|
226
|
+
[github.com/dogsbay/dogsbay](https://github.com/dogsbay/dogsbay).
|
|
227
|
+
`);
|
|
228
|
+
written.push(indexPath);
|
|
229
|
+
const gettingStartedPath = join(contentDir, "getting-started.md");
|
|
230
|
+
writeFileSync(gettingStartedPath, `---
|
|
231
|
+
title: Getting started
|
|
232
|
+
description: Quick orientation for new contributors.
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
# Getting started
|
|
236
|
+
|
|
237
|
+
Three minutes to your first edit. Powered by
|
|
238
|
+
[Dogsbay](https://github.com/dogsbay/dogsbay).
|
|
239
|
+
|
|
240
|
+
## 1. Edit a page
|
|
241
|
+
|
|
242
|
+
Open \`content/index.md\` in your editor and change something.
|
|
243
|
+
Save — the dev server reloads automatically.
|
|
244
|
+
|
|
245
|
+
## 2. Add a page
|
|
246
|
+
|
|
247
|
+
Create a new file like \`content/about.md\`:
|
|
248
|
+
|
|
249
|
+
\`\`\`md
|
|
250
|
+
---
|
|
251
|
+
title: About
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
# About
|
|
255
|
+
|
|
256
|
+
Whatever you want to say here.
|
|
257
|
+
\`\`\`
|
|
258
|
+
|
|
259
|
+
Then add it to \`content/nav.yml\` so it appears in the sidebar.
|
|
260
|
+
Each entry is a single-key map — key = label, value = file path:
|
|
261
|
+
|
|
262
|
+
\`\`\`yaml
|
|
263
|
+
- About: about.md
|
|
264
|
+
\`\`\`
|
|
265
|
+
|
|
266
|
+
External URLs work the same way:
|
|
267
|
+
|
|
268
|
+
\`\`\`yaml
|
|
269
|
+
- GitHub: https://github.com/your-org/your-repo
|
|
270
|
+
\`\`\`
|
|
271
|
+
|
|
272
|
+
## 3. Group pages
|
|
273
|
+
|
|
274
|
+
To create a section in the sidebar, give the entry a list of
|
|
275
|
+
children instead of a single file:
|
|
276
|
+
|
|
277
|
+
\`\`\`yaml
|
|
278
|
+
- Guides:
|
|
279
|
+
- Configuration: guides/configuration.md
|
|
280
|
+
- Deployment: guides/deployment.md
|
|
281
|
+
\`\`\`
|
|
282
|
+
|
|
283
|
+
The folder structure under \`content/\` doesn't have to match the
|
|
284
|
+
nav — but it usually does, because it makes URLs predictable.
|
|
285
|
+
`);
|
|
286
|
+
written.push(gettingStartedPath);
|
|
287
|
+
const navPath = join(contentDir, "nav.yml");
|
|
288
|
+
writeFileSync(navPath, `# Sidebar navigation. Loaded by Dogsbay automatically — name this
|
|
289
|
+
# file nav.yml, nav.yaml, or nav.json (in that order of precedence).
|
|
290
|
+
#
|
|
291
|
+
# Each entry is a single-key map. The key is the sidebar label.
|
|
292
|
+
# The value is one of:
|
|
293
|
+
# - a file path (relative to content/, e.g. "guide.md")
|
|
294
|
+
# - an absolute URL (e.g. "https://...")
|
|
295
|
+
# - a list of child entries (creates a group)
|
|
296
|
+
#
|
|
297
|
+
# Examples:
|
|
298
|
+
# - Home: index.md # leaf — file path
|
|
299
|
+
# - GitHub: https://github.com/… # leaf — external URL
|
|
300
|
+
# - Guides: # group — children below
|
|
301
|
+
# - Configuration: guides/config.md
|
|
302
|
+
# - Deployment: guides/deploy.md
|
|
303
|
+
#
|
|
304
|
+
# Edit freely as you add or rearrange pages. See
|
|
305
|
+
# https://github.com/dogsbay/dogsbay for the full nav-file reference.
|
|
306
|
+
|
|
307
|
+
- Home: index.md
|
|
308
|
+
- Getting started: getting-started.md
|
|
157
309
|
`);
|
|
158
|
-
|
|
310
|
+
written.push(navPath);
|
|
311
|
+
return written;
|
|
159
312
|
}
|
|
160
313
|
// ─── Resolution: flags + prompts → DogsbayConfig ─────────────────────────
|
|
161
314
|
async function resolveConfig(opts, interactive) {
|
|
@@ -168,10 +321,15 @@ function resolveNonInteractive(opts) {
|
|
|
168
321
|
if (!opts.siteName?.trim()) {
|
|
169
322
|
throw new Error("--site-name is required when running non-interactively (no TTY or --yes).");
|
|
170
323
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
324
|
+
// Default --content to ./content (matches the interactive default
|
|
325
|
+
// and the convention every other Dogsbay command assumes). This
|
|
326
|
+
// makes `dogsbay site init <dir> --yes --site-name X` Just Work
|
|
327
|
+
// without forcing the writer to remember a redundant flag.
|
|
328
|
+
const resolved = {
|
|
329
|
+
...opts,
|
|
330
|
+
content: opts.content?.trim() || "./content",
|
|
331
|
+
};
|
|
332
|
+
return applyDefaults(buildConfig(resolved));
|
|
175
333
|
}
|
|
176
334
|
async function resolveInteractive(opts) {
|
|
177
335
|
const answers = await prompts([
|
|
@@ -378,6 +536,9 @@ function buildConfig(opts) {
|
|
|
378
536
|
if (opts.deploy === "cloudflare-workers") {
|
|
379
537
|
config.deploy = { target: "cloudflare-workers" };
|
|
380
538
|
}
|
|
539
|
+
else if (opts.deploy === "github-pages") {
|
|
540
|
+
config.deploy = { target: "github-pages" };
|
|
541
|
+
}
|
|
381
542
|
if (opts.plausibleDomain?.trim()) {
|
|
382
543
|
config.analytics = {
|
|
383
544
|
plausible: {
|
|
@@ -401,14 +562,14 @@ function findExistingConfig(dir) {
|
|
|
401
562
|
}
|
|
402
563
|
return null;
|
|
403
564
|
}
|
|
404
|
-
function printNextSteps(absTarget, outputDir, config,
|
|
565
|
+
function printNextSteps(absTarget, outputDir, config, starterContentPaths) {
|
|
405
566
|
void config;
|
|
406
567
|
const sameDir = absTarget === outputDir;
|
|
407
568
|
console.log("");
|
|
408
569
|
console.log(pc.green("Wrote:"));
|
|
409
570
|
console.log(` ${absTarget}/dogsbay.config.yml`);
|
|
410
|
-
|
|
411
|
-
console.log(` ${
|
|
571
|
+
for (const p of starterContentPaths) {
|
|
572
|
+
console.log(` ${p}`);
|
|
412
573
|
}
|
|
413
574
|
console.log(` ${outputDir}/package.json`);
|
|
414
575
|
console.log(` ${outputDir}/astro.config.mjs`);
|
package/dist/config/defaults.js
CHANGED
|
@@ -73,10 +73,17 @@ export function applyDefaults(config) {
|
|
|
73
73
|
function fillTaxonomyDefaults(raw) {
|
|
74
74
|
const out = {};
|
|
75
75
|
for (const [name, entry] of Object.entries(raw)) {
|
|
76
|
+
// Declaring `prefixes:` (with their own labels / colors) is a strong
|
|
77
|
+
// signal the writer wants `/tags/<prefix>/` to be a real browsable
|
|
78
|
+
// index — not just a styling axis. Default `hierarchical` to true
|
|
79
|
+
// in that case so prefix-index routes get emitted and the
|
|
80
|
+
// breadcrumb / sub-tag links don't 404. Writers can opt out with
|
|
81
|
+
// `hierarchical: false` explicitly.
|
|
82
|
+
const hasPrefixes = entry.prefixes !== undefined && Object.keys(entry.prefixes).length > 0;
|
|
76
83
|
out[name] = {
|
|
77
84
|
indexPath: entry.indexPath ?? `/${name}`,
|
|
78
85
|
values: entry.values,
|
|
79
|
-
hierarchical: entry.hierarchical ??
|
|
86
|
+
hierarchical: entry.hierarchical ?? hasPrefixes,
|
|
80
87
|
prefixes: entry.prefixes,
|
|
81
88
|
labels: entry.labels,
|
|
82
89
|
};
|
package/dist/config/load.js
CHANGED
|
@@ -260,38 +260,12 @@ function validateSite(site, sourcePath) {
|
|
|
260
260
|
`or repeated slashes. Got ${JSON.stringify(s.basePath)}`);
|
|
261
261
|
}
|
|
262
262
|
}
|
|
263
|
-
//
|
|
264
|
-
//
|
|
265
|
-
// `
|
|
266
|
-
//
|
|
267
|
-
//
|
|
268
|
-
//
|
|
269
|
-
if (typeof s.url === "string" && s.url.length > 0) {
|
|
270
|
-
let parsedUrl;
|
|
271
|
-
try {
|
|
272
|
-
parsedUrl = new URL(s.url);
|
|
273
|
-
}
|
|
274
|
-
catch {
|
|
275
|
-
// Invalid URL — leave further checks for downstream Astro;
|
|
276
|
-
// we only want to catch the host+path overlap here.
|
|
277
|
-
}
|
|
278
|
-
const basePath = typeof s.basePath === "string" ? s.basePath : undefined;
|
|
279
|
-
const basePathIsSet = basePath !== undefined && basePath !== "" && basePath !== "/";
|
|
280
|
-
if (parsedUrl && basePathIsSet) {
|
|
281
|
-
const urlPath = parsedUrl.pathname.replace(/\/+$/, ""); // strip trailing /
|
|
282
|
-
if (urlPath !== "" && urlPath !== "/") {
|
|
283
|
-
const origin = `${parsedUrl.protocol}//${parsedUrl.host}`;
|
|
284
|
-
throw new Error(`site.url must be host-only when site.basePath is set in ${sourcePath}; ` +
|
|
285
|
-
`got ${JSON.stringify(s.url)}, expected ${JSON.stringify(origin)} ` +
|
|
286
|
-
`with basePath ${JSON.stringify(basePath)}.\n\n` +
|
|
287
|
-
`Astro splits these into:\n` +
|
|
288
|
-
` site: ${JSON.stringify(origin)}\n` +
|
|
289
|
-
` base: ${JSON.stringify(basePath)}\n` +
|
|
290
|
-
`Without this fix, canonical URLs double-count the prefix ` +
|
|
291
|
-
`(e.g. ${origin}${urlPath}${basePath}/).`);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
}
|
|
263
|
+
// site.url and site.basePath are now independent prefixes that
|
|
264
|
+
// compose at emit time. The path component of site.url drives
|
|
265
|
+
// Astro's `base` (where the served space sits in the host);
|
|
266
|
+
// basePath stays as content's filesystem position within that
|
|
267
|
+
// served space. See plans/astro-base-from-site-url.md and
|
|
268
|
+
// packages/format-astro/src/base-path.ts:resolvePrefixes.
|
|
295
269
|
return s;
|
|
296
270
|
}
|
|
297
271
|
const VALID_FROM = [
|
|
@@ -22,6 +22,7 @@ export function configToAstroOptions(config) {
|
|
|
22
22
|
plausibleDomain: config.analytics?.plausible?.domain,
|
|
23
23
|
plausibleScriptUrl: config.analytics?.plausible?.scriptUrl,
|
|
24
24
|
deploy: config.deploy?.target,
|
|
25
|
+
inlineStylesheets: config.build?.inlineStylesheets,
|
|
25
26
|
llmsTxt: config.agent?.llmsTxt,
|
|
26
27
|
mdMirror: config.agent?.mdMirror,
|
|
27
28
|
aiTrain: config.agent?.contentSignal?.aiTrain,
|
package/dist/import-content.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { normalizeBasePath } from "@dogsbay/format-astro";
|
|
1
|
+
import { normalizeBasePath, resolvePrefixes } from "@dogsbay/format-astro";
|
|
2
2
|
import { plugin as mkdocsPlugin } from "@dogsbay/format-mkdocs/cli";
|
|
3
3
|
import { plugin as astroPlugin } from "@dogsbay/format-astro/cli";
|
|
4
4
|
import { plugin as obsidianPlugin } from "@dogsbay/format-obsidian/cli";
|
|
@@ -396,16 +396,17 @@ function buildImportOptions(config, source) {
|
|
|
396
396
|
if (config.taxonomies) {
|
|
397
397
|
opts.taxonomyNames = Object.keys(config.taxonomies);
|
|
398
398
|
}
|
|
399
|
-
// Pass
|
|
400
|
-
//
|
|
401
|
-
//
|
|
402
|
-
//
|
|
403
|
-
//
|
|
404
|
-
//
|
|
405
|
-
//
|
|
406
|
-
//
|
|
407
|
-
//
|
|
408
|
-
//
|
|
409
|
-
|
|
399
|
+
// Pass the COMBINED URL prefix (urlBase from site.url's path +
|
|
400
|
+
// site.basePath) as `hrefPrefix` so nav-builders produce hrefs
|
|
401
|
+
// matching the served URL of each page. Two prefix layers
|
|
402
|
+
// because subpath-mounted deploys (GH Pages project pages,
|
|
403
|
+
// multi-mount Cloudflare under one Worker) add a host-level
|
|
404
|
+
// prefix on top of dogsbay's basePath. See
|
|
405
|
+
// plans/astro-base-from-site-url.md.
|
|
406
|
+
//
|
|
407
|
+
// Back-compat: when site.url is origin-only (no path), urlBase
|
|
408
|
+
// is empty and combined === basePath, so existing sites see no
|
|
409
|
+
// change.
|
|
410
|
+
opts.hrefPrefix = resolvePrefixes(config.site.url, config.site.basePath).combined;
|
|
410
411
|
return opts;
|
|
411
412
|
}
|