@brandon_m_behring/book-scaffold-astro 4.1.0 → 4.2.0
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/components/PocLayout.astro +1 -6
- package/package.json +1 -1
- package/recipes/16-tikz-figures.md +102 -0
- package/scripts/build-figures.mjs +90 -1
- package/scripts/build-labels.mjs +11 -2
- package/scripts/validate.mjs +6 -2
- package/scripts/walk-mdx.mjs +68 -2
|
@@ -11,12 +11,7 @@
|
|
|
11
11
|
* Closed `kind` union: discriminated literal type. To add a 6th kind in
|
|
12
12
|
* a future release, expand the union + add a CSS block in poc-layouts.css.
|
|
13
13
|
*/
|
|
14
|
-
export type PocLayoutKind =
|
|
15
|
-
| 'tutorial'
|
|
16
|
-
| 'how-to'
|
|
17
|
-
| 'tldr'
|
|
18
|
-
| 'part-summary'
|
|
19
|
-
| 'cheat-sheet';
|
|
14
|
+
export type PocLayoutKind = 'tutorial' | 'how-to' | 'tldr' | 'part-summary' | 'cheat-sheet';
|
|
20
15
|
|
|
21
16
|
interface Props {
|
|
22
17
|
kind: PocLayoutKind;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brandon_m_behring/book-scaffold-astro",
|
|
3
3
|
"description": "Astro 6 + MDX toolkit for long-form technical books. Profile-aware (academic / tools / minimal); ships Tufte typography, KaTeX, BibTeX citations, Pagefind, Cloudflare Workers deploy. See PACKAGE_DESIGN.md for the API contract.",
|
|
4
|
-
"version": "4.
|
|
4
|
+
"version": "4.2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Brandon Behring",
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Recipe 16 — TikZ figures (v4.2.0+)
|
|
2
|
+
|
|
3
|
+
`book-scaffold build-figures` (v4.2.0+) auto-compiles TikZ standalone `.tex` sources to PDF via `pdflatex`, then converts the PDF to SVG via the existing pdf2svg pipeline. Closes [#17](https://github.com/brandon-behring/book-scaffold-astro/issues/17).
|
|
4
|
+
|
|
5
|
+
## TL;DR
|
|
6
|
+
|
|
7
|
+
Drop `figures/<topic>/diagram.tex` (standalone TikZ source). Run `npm run build:figures` (or it's wired into `prebuild`). Get `public/figures/<topic>/diagram.svg` ready to reference in MDX as `<Figure src="/figures/<topic>/diagram.svg" />`.
|
|
8
|
+
|
|
9
|
+
## The TikZ source
|
|
10
|
+
|
|
11
|
+
Use the `standalone` document class; configure for SVG via `tikz` option. Recommended:
|
|
12
|
+
|
|
13
|
+
```latex
|
|
14
|
+
\documentclass[tikz,border=2mm]{standalone}
|
|
15
|
+
\usepackage{tikz}
|
|
16
|
+
\usetikzlibrary{positioning, shapes, arrows} % whichever libraries you need
|
|
17
|
+
\begin{document}
|
|
18
|
+
\begin{tikzpicture}
|
|
19
|
+
\node (a) at (0,0) {Hello};
|
|
20
|
+
\node (b) at (3,0) {World};
|
|
21
|
+
\draw[->] (a) -- (b);
|
|
22
|
+
\end{tikzpicture}
|
|
23
|
+
\end{document}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The `border=2mm` adds a small margin around the figure so it doesn't crop right at the edge.
|
|
27
|
+
|
|
28
|
+
## Discovery rule
|
|
29
|
+
|
|
30
|
+
`build-figures` walks `figures/` (or `BOOK_FIGURES_PATH`) for both `.pdf` and `.tex` files. For each `.tex` source:
|
|
31
|
+
|
|
32
|
+
- If no sibling `.pdf` exists → compile.
|
|
33
|
+
- If `.pdf` exists but `.tex` is newer → recompile.
|
|
34
|
+
- If `.pdf` is newer than (or equal in mtime to) `.tex` → skip (use the existing PDF).
|
|
35
|
+
|
|
36
|
+
This means consumers who ship pre-compiled `.pdf` figures alongside their `.tex` sources don't pay the compilation cost on every build.
|
|
37
|
+
|
|
38
|
+
## Working directory
|
|
39
|
+
|
|
40
|
+
`pdflatex` runs in `figures/<topic>/` (the directory containing the source). This makes TikZ `\input{}` relative paths work correctly and keeps intermediate files (`.aux`, `.log`) alongside the source for easy debugging.
|
|
41
|
+
|
|
42
|
+
## Gitignore intermediate files
|
|
43
|
+
|
|
44
|
+
Add to your project's `.gitignore`:
|
|
45
|
+
|
|
46
|
+
```gitignore
|
|
47
|
+
figures/**/*.aux
|
|
48
|
+
figures/**/*.log
|
|
49
|
+
figures/**/*.fdb_latexmk
|
|
50
|
+
figures/**/*.fls
|
|
51
|
+
figures/**/*.synctex.gz
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The intermediate `.pdf` files generated from `.tex` SHOULD be committed — they let consumers without TeX Live still see your figures (the SVGs still get generated from the committed PDFs).
|
|
55
|
+
|
|
56
|
+
## Required: TeX Live install
|
|
57
|
+
|
|
58
|
+
`pdflatex` is a system dependency (not an npm package). Install:
|
|
59
|
+
|
|
60
|
+
- **macOS**: `brew install --cask mactex` (full) or `brew install --cask basictex` (minimal — add packages via `tlmgr`)
|
|
61
|
+
- **Ubuntu/Debian**: `sudo apt-get install texlive-base texlive-pictures` (minimal for TikZ)
|
|
62
|
+
- **Other**: https://www.tug.org/texlive/
|
|
63
|
+
|
|
64
|
+
If `pdflatex` is missing but `.tex` files are present, `build-figures` prints a clear ERROR with the install link and continues processing any `.pdf`-only topics. Doesn't crash the build.
|
|
65
|
+
|
|
66
|
+
## CI workflow note
|
|
67
|
+
|
|
68
|
+
If your CI builds rely on regenerating SVGs from `.tex` sources (rather than just serving committed SVGs), add TeX Live to your CI workflow:
|
|
69
|
+
|
|
70
|
+
```yaml
|
|
71
|
+
- name: Install TeX Live for TikZ figures
|
|
72
|
+
run: sudo apt-get install -y texlive-base texlive-pictures
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
This adds ~200 MB to the runner — only do it if your figures actually change between commits. The recommended pattern is to commit both `.tex` AND the generated `.pdf`/`.svg`, treating regeneration as a local-dev concern.
|
|
76
|
+
|
|
77
|
+
## Debugging compilation failures
|
|
78
|
+
|
|
79
|
+
When pdflatex fails on a `.tex` source, `build-figures` prints the stderr (and falls back to stdout) and continues with the remaining figures. Don't fail the whole build for one broken figure.
|
|
80
|
+
|
|
81
|
+
To debug interactively:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
cd figures/<topic>/
|
|
85
|
+
pdflatex diagram.tex
|
|
86
|
+
# read diagram.log for the full error trace
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Common failures:
|
|
90
|
+
- **Missing package**: `! LaTeX Error: File 'tikz-cd.sty' not found.` → install with `tlmgr install tikz-cd` (macOS/Linux Tex Live) or `sudo apt-get install texlive-tikz-cd` (Debian).
|
|
91
|
+
- **Syntax error**: `! Undefined control sequence.` → check the `.log` file for the line number.
|
|
92
|
+
- **Compilation hang**: should auto-resolve via `-halt-on-error -interaction=nonstopmode` flags the scaffold uses.
|
|
93
|
+
|
|
94
|
+
## Feedback loop
|
|
95
|
+
|
|
96
|
+
If you hit friction with the TikZ pipeline (a TikZ feature that doesn't compile, an obscure error message, a workflow pattern that doesn't fit), file an issue at https://github.com/brandon-behring/book-scaffold-astro/issues with the `consumer:<your-workspace>` label. v4.x is the iteration window.
|
|
97
|
+
|
|
98
|
+
## See also
|
|
99
|
+
|
|
100
|
+
- `recipes/06-figures.md` — overall figure pipeline + matplotlib/svg sources
|
|
101
|
+
- `PACKAGE_DESIGN.md §7` — peer dependencies (lists `pdflatex` as optional system dep)
|
|
102
|
+
- `PACKAGE_DESIGN.md §8` — `book-scaffold` CLI reference (build-figures subcommand)
|
|
@@ -6,6 +6,13 @@
|
|
|
6
6
|
* pdftocairo, emitting under public/figures/. SVG preserves zoom/quality
|
|
7
7
|
* and stays small for matplotlib-style plots.
|
|
8
8
|
*
|
|
9
|
+
* v4.2.0 (closes #17): TikZ standalone `.tex` sources are auto-compiled
|
|
10
|
+
* to `.pdf` via pdflatex before the existing PDF→SVG pass runs. Discovery
|
|
11
|
+
* rule: if `figures/<topic>/<name>.tex` exists AND no sibling `.pdf`
|
|
12
|
+
* (or `.tex` is newer than `.pdf`), pdflatex runs. Graceful skip when
|
|
13
|
+
* pdflatex is not on PATH (only emits ERROR if .tex sources exist).
|
|
14
|
+
* See recipes/16-tikz-figures.md for the workflow + install pointers.
|
|
15
|
+
*
|
|
9
16
|
* Default source: figures/ at scaffold root. Override via BOOK_FIGURES_PATH
|
|
10
17
|
* env var (absolute path or path relative to scaffold root) — useful for
|
|
11
18
|
* books that share figures with a LaTeX sibling at e.g. ../shared/figures/.
|
|
@@ -98,6 +105,55 @@ async function listPdfsRecursive(root, prefix = '') {
|
|
|
98
105
|
return out;
|
|
99
106
|
}
|
|
100
107
|
|
|
108
|
+
/**
|
|
109
|
+
* v4.2.0 (#17): recursively collect .tex sources under FIGURES_SRC.
|
|
110
|
+
* Same shape as listPdfsRecursive but for `.tex` extension. TikZ
|
|
111
|
+
* standalone files at any subdirectory depth are picked up.
|
|
112
|
+
*/
|
|
113
|
+
async function listTexRecursive(root, prefix = '') {
|
|
114
|
+
if (!existsSync(root)) return [];
|
|
115
|
+
const entries = await readdir(root, { withFileTypes: true });
|
|
116
|
+
const out = [];
|
|
117
|
+
for (const entry of entries) {
|
|
118
|
+
const relPath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
119
|
+
if (entry.isDirectory()) {
|
|
120
|
+
out.push(...(await listTexRecursive(resolve(root, entry.name), relPath)));
|
|
121
|
+
} else if (entry.isFile() && entry.name.endsWith('.tex')) {
|
|
122
|
+
out.push({ relPath });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return out;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* v4.2.0 (#17): compile a TikZ standalone .tex to .pdf via pdflatex.
|
|
130
|
+
* Run in the source directory so TikZ \input{} relative paths resolve
|
|
131
|
+
* + intermediate .aux/.log files land alongside the source.
|
|
132
|
+
* Returns true on success; throws Error with stderr on failure.
|
|
133
|
+
*/
|
|
134
|
+
function compileTikz(srcPath) {
|
|
135
|
+
const srcDir = dirname(srcPath);
|
|
136
|
+
const srcName = basename(srcPath);
|
|
137
|
+
const r = spawnSync(
|
|
138
|
+
'pdflatex',
|
|
139
|
+
[
|
|
140
|
+
'-halt-on-error',
|
|
141
|
+
'-interaction=nonstopmode',
|
|
142
|
+
'-output-directory=.',
|
|
143
|
+
srcName,
|
|
144
|
+
],
|
|
145
|
+
{ cwd: srcDir, stdio: 'pipe' },
|
|
146
|
+
);
|
|
147
|
+
if (r.status !== 0) {
|
|
148
|
+
const stderr = (r.stderr ?? Buffer.from('')).toString().trim();
|
|
149
|
+
const stdout = (r.stdout ?? Buffer.from('')).toString().trim();
|
|
150
|
+
throw new Error(
|
|
151
|
+
`pdflatex failed for ${srcPath}: ${stderr || stdout || `exit code ${r.status}`}`,
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
|
|
101
157
|
function isUpToDate(srcPath, dstPath) {
|
|
102
158
|
if (!existsSync(dstPath)) return false;
|
|
103
159
|
const srcMtime = statSync(srcPath).mtimeMs;
|
|
@@ -153,6 +209,38 @@ async function main() {
|
|
|
153
209
|
return;
|
|
154
210
|
}
|
|
155
211
|
|
|
212
|
+
// v4.2.0 (#17): stage 1 — compile any TikZ standalone .tex sources to .pdf
|
|
213
|
+
// BEFORE the PDF→SVG loop. Topic-walk discovers .tex files; compiles those
|
|
214
|
+
// where no sibling .pdf exists OR .tex is newer than .pdf.
|
|
215
|
+
const texSources = await listTexRecursive(FIGURES_SRC);
|
|
216
|
+
let tikzCompiled = 0;
|
|
217
|
+
if (texSources.length > 0) {
|
|
218
|
+
if (!check('pdflatex')) {
|
|
219
|
+
console.error(
|
|
220
|
+
`build-figures: pdflatex not on $PATH but ${texSources.length} ` +
|
|
221
|
+
`.tex source(s) detected. Install TeX Live to enable TikZ ` +
|
|
222
|
+
`compilation (see https://www.tug.org/texlive/). Skipping ` +
|
|
223
|
+
`.tex sources; pre-compiled .pdf siblings (if any) will still ` +
|
|
224
|
+
`be converted to SVG.`,
|
|
225
|
+
);
|
|
226
|
+
} else {
|
|
227
|
+
for (const { relPath } of texSources) {
|
|
228
|
+
const srcPath = resolve(FIGURES_SRC, relPath);
|
|
229
|
+
const pdfPath = srcPath.replace(/\.tex$/, '.pdf');
|
|
230
|
+
if (existsSync(pdfPath) && statSync(pdfPath).mtimeMs >= statSync(srcPath).mtimeMs) {
|
|
231
|
+
continue; // pdf is up-to-date
|
|
232
|
+
}
|
|
233
|
+
try {
|
|
234
|
+
compileTikz(srcPath);
|
|
235
|
+
tikzCompiled++;
|
|
236
|
+
} catch (err) {
|
|
237
|
+
console.error(`build-figures: ${err.message ?? err}`);
|
|
238
|
+
// Continue — don't fail the whole build on one broken .tex file.
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
156
244
|
const pdfs = await listPdfsRecursive(FIGURES_SRC);
|
|
157
245
|
if (pdfs.length === 0) {
|
|
158
246
|
console.log('build-figures: no PDFs found; nothing to do.');
|
|
@@ -185,9 +273,10 @@ async function main() {
|
|
|
185
273
|
converted++;
|
|
186
274
|
}
|
|
187
275
|
|
|
276
|
+
const tikzNote = tikzCompiled > 0 ? `, ${tikzCompiled} tikz→pdf` : '';
|
|
188
277
|
console.log(
|
|
189
278
|
`build-figures: ${total} total, ${converted} converted ` +
|
|
190
|
-
`(${pngFallback} png fallback), ${skipped} cached`,
|
|
279
|
+
`(${pngFallback} png fallback), ${skipped} cached${tikzNote}`,
|
|
191
280
|
);
|
|
192
281
|
}
|
|
193
282
|
|
package/scripts/build-labels.mjs
CHANGED
|
@@ -34,7 +34,8 @@
|
|
|
34
34
|
* Designed to run in <2 s on a medium book.
|
|
35
35
|
*/
|
|
36
36
|
import { readFile, writeFile, mkdir, readdir } from 'node:fs/promises';
|
|
37
|
-
import { resolve, join, basename, dirname } from 'node:path';
|
|
37
|
+
import { resolve, relative, join, basename, dirname } from 'node:path';
|
|
38
|
+
import { readChaptersBase } from './walk-mdx.mjs';
|
|
38
39
|
|
|
39
40
|
// --help / -h: non-mutating (closes #14).
|
|
40
41
|
const USAGE = `Usage: book-scaffold build-labels
|
|
@@ -56,7 +57,15 @@ if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
|
56
57
|
process.exit(0);
|
|
57
58
|
}
|
|
58
59
|
|
|
59
|
-
|
|
60
|
+
// v4.1.1 (closes #63): readChaptersBase honors BOOK_CHAPTERS_DIR env (when set)
|
|
61
|
+
// then parses the consumer's content.config.{ts,mjs,js} for a `chapters`
|
|
62
|
+
// collection `loader.base` override. Multi-guide consumers use
|
|
63
|
+
// `src/content/<guide-slug>/` rather than the Astro 5 default.
|
|
64
|
+
const CHAPTERS_DIR_ABS = await readChaptersBase(process.cwd());
|
|
65
|
+
// build-labels uses CHAPTERS_DIR as a path relative to cwd elsewhere in the
|
|
66
|
+
// script (joined with `walkMdx`). Convert the absolute path back to relative
|
|
67
|
+
// for compatibility with the existing call sites.
|
|
68
|
+
const CHAPTERS_DIR = relative(process.cwd(), CHAPTERS_DIR_ABS) || 'src/content/chapters';
|
|
60
69
|
const OUTPUT_PATH = process.env.BOOK_LABELS_OUT ?? 'src/data/labels.json';
|
|
61
70
|
|
|
62
71
|
/** Component names that participate in cross-referencing. */
|
package/scripts/validate.mjs
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
import { readFile, access } from 'node:fs/promises';
|
|
29
29
|
import { existsSync, readFileSync } from 'node:fs';
|
|
30
30
|
import { resolve, dirname, join } from 'node:path';
|
|
31
|
-
import { walkMdx } from './walk-mdx.mjs';
|
|
31
|
+
import { walkMdx, readChaptersBase } from './walk-mdx.mjs';
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
34
|
* Best-effort .env reader. Mirrors `readEnvFile` in src/types.ts; kept inline
|
|
@@ -95,7 +95,11 @@ const presetFromFlag = presetFlagIdx >= 0 ? argv[presetFlagIdx + 1] : undefined;
|
|
|
95
95
|
// Resolves issue #8 — three reference consumers reported "0 chapter(s) checked"
|
|
96
96
|
// because ROOT was the package directory inside node_modules.
|
|
97
97
|
const ROOT = process.cwd();
|
|
98
|
-
|
|
98
|
+
// v4.1.1 (closes #63): read the consumer's content.config.{ts,mjs,js} to
|
|
99
|
+
// honor `loader.base` overrides (multi-guide pattern uses
|
|
100
|
+
// `src/content/<guide-slug>/` instead of the Astro 5 default).
|
|
101
|
+
// Falls back to `src/content/chapters` when no override / no config file.
|
|
102
|
+
const CHAPTERS_DIR = await readChaptersBase(ROOT);
|
|
99
103
|
const PUBLIC_DIR = resolve(ROOT, 'public');
|
|
100
104
|
const DATA_DIR = resolve(ROOT, 'src/data');
|
|
101
105
|
|
package/scripts/walk-mdx.mjs
CHANGED
|
@@ -12,8 +12,9 @@
|
|
|
12
12
|
* Output: relative paths in POSIX form ("subdir/file.mdx"), matching what
|
|
13
13
|
* the previous `glob('**\/*.{md,mdx}', { cwd })` produced.
|
|
14
14
|
*/
|
|
15
|
-
import { readdir } from 'node:fs/promises';
|
|
16
|
-
import {
|
|
15
|
+
import { readFile, readdir } from 'node:fs/promises';
|
|
16
|
+
import { existsSync } from 'node:fs';
|
|
17
|
+
import { join, relative, resolve } from 'node:path';
|
|
17
18
|
|
|
18
19
|
export async function* walkMdx(dir, baseDir = dir) {
|
|
19
20
|
let entries;
|
|
@@ -32,3 +33,68 @@ export async function* walkMdx(dir, baseDir = dir) {
|
|
|
32
33
|
}
|
|
33
34
|
}
|
|
34
35
|
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Read the consumer's `content.config.ts` (or `.mjs` / `.js`) and extract
|
|
39
|
+
* the `loader.base` path for the `chapters` content collection.
|
|
40
|
+
*
|
|
41
|
+
* v4.1.1 (closes #63): consumers in the multi-guide / multi-book pattern
|
|
42
|
+
* override the chapters dir to `src/content/<guide-slug>` rather than the
|
|
43
|
+
* Astro 5 default `src/content/chapters/`. Without this helper,
|
|
44
|
+
* `book-scaffold validate` + `book-scaffold build-labels` silently report
|
|
45
|
+
* 0 chapters because they walk the default path. This helper parses the
|
|
46
|
+
* consumer's config file and returns the actual base path so both scripts
|
|
47
|
+
* discover the consumer's chapter files.
|
|
48
|
+
*
|
|
49
|
+
* Strategy: regex-parse the source file (avoid runtime import; the file
|
|
50
|
+
* imports from `astro:content` / `astro/loaders` which don't resolve in
|
|
51
|
+
* plain Node). Matches both single- and double-quoted string literals;
|
|
52
|
+
* matches paths with or without the `./` prefix.
|
|
53
|
+
*
|
|
54
|
+
* Returns the resolved absolute path. Falls back to
|
|
55
|
+
* `${projectRoot}/src/content/chapters` when:
|
|
56
|
+
* - content.config.{ts,mjs,js} doesn't exist
|
|
57
|
+
* - the file exists but no `chapters` collection or `loader.base` found
|
|
58
|
+
* - the matched base path uses dynamic forms (variables, template literals)
|
|
59
|
+
* instead of a string literal
|
|
60
|
+
*
|
|
61
|
+
* Honors env override: BOOK_CHAPTERS_DIR (when set) wins over config parse.
|
|
62
|
+
*/
|
|
63
|
+
export async function readChaptersBase(projectRoot) {
|
|
64
|
+
const envOverride = process.env.BOOK_CHAPTERS_DIR;
|
|
65
|
+
if (envOverride) {
|
|
66
|
+
return resolve(projectRoot, envOverride);
|
|
67
|
+
}
|
|
68
|
+
const DEFAULT_BASE = resolve(projectRoot, 'src/content/chapters');
|
|
69
|
+
for (const ext of ['ts', 'mjs', 'js']) {
|
|
70
|
+
const configPath = join(projectRoot, `src/content.config.${ext}`);
|
|
71
|
+
if (!existsSync(configPath)) continue;
|
|
72
|
+
let source;
|
|
73
|
+
try {
|
|
74
|
+
source = await readFile(configPath, 'utf8');
|
|
75
|
+
} catch {
|
|
76
|
+
return DEFAULT_BASE;
|
|
77
|
+
}
|
|
78
|
+
// Look for a `chapters` collection's `loader.base` string. Permissive
|
|
79
|
+
// form: match the `chapters` identifier, then within the next 500
|
|
80
|
+
// chars find `base: 'string'` or `base: "string"`. NOT template
|
|
81
|
+
// literals (which use backticks and may contain ${} interpolation —
|
|
82
|
+
// those fall back to the default since the value is dynamic).
|
|
83
|
+
//
|
|
84
|
+
// Forms matched:
|
|
85
|
+
// - `const chapters = defineCollection({ loader: glob({ base: './foo' }) })`
|
|
86
|
+
// - `export const collections = { chapters: defineCollection({ loader: glob({ base: './foo' }) }) }`
|
|
87
|
+
// - any indentation / line break style
|
|
88
|
+
const re = /\bchapters\b[\s\S]{0,500}?\bbase\s*:\s*'([^']+)'|\bchapters\b[\s\S]{0,500}?\bbase\s*:\s*"([^"]+)"/;
|
|
89
|
+
const m = source.match(re);
|
|
90
|
+
const captured = m && (m[1] || m[2]);
|
|
91
|
+
if (captured) {
|
|
92
|
+
return resolve(projectRoot, captured);
|
|
93
|
+
}
|
|
94
|
+
// File exists but no override found — assume the consumer uses the
|
|
95
|
+
// scaffold's defineBookSchemas() default.
|
|
96
|
+
return DEFAULT_BASE;
|
|
97
|
+
}
|
|
98
|
+
// No content.config.{ts,mjs,js} at all — return the Astro 5 default.
|
|
99
|
+
return DEFAULT_BASE;
|
|
100
|
+
}
|