@brandon_m_behring/book-scaffold-astro 4.26.2 → 4.27.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/AGENTS.md +6 -0
- package/CLAUDE.md +35 -10
- package/LATEX_TO_MDX_MAPPING.md +1 -1
- package/LICENSE +27 -0
- package/LICENSE-CONTENT +19 -0
- package/README.md +33 -0
- package/components/AssessmentTest.astro +2 -1
- package/components/ChapterNav.astro +2 -2
- package/components/Cite.astro +2 -1
- package/components/NavContent.astro +2 -2
- package/components/PartReview.astro +2 -1
- package/components/Rationale.astro +3 -2
- package/components/Sidebar.astro +2 -1
- package/components/Term.astro +2 -1
- package/components/TipsCard.astro +2 -1
- package/components/VersionSelector.tsx +39 -36
- package/components/WeekRef.astro +2 -1
- package/components/XRef.astro +2 -1
- package/dist/components/VersionSelector.d.ts +16 -2
- package/dist/components/VersionSelector.mjs +18 -17
- package/dist/index.d.ts +24 -6
- package/dist/index.mjs +96 -8
- package/dist/schemas.d.ts +1 -1
- package/dist/schemas.mjs +8 -1
- package/dist/{types-Hue-uSeQ.d.ts → types-CZrkqzpC.d.ts} +84 -48
- package/layouts/Base.astro +23 -21
- package/package.json +7 -4
- package/pages/answers.astro +2 -1
- package/pages/chapters.astro +2 -1
- package/pages/exercises.astro +2 -1
- package/pages/index.astro +4 -3
- package/pages/search.astro +2 -1
- package/pages/tips.astro +2 -1
- package/recipes/01-add-math.md +40 -28
- package/recipes/04-component-library.md +14 -4
- package/recipes/05-deploy-cloudflare.md +44 -0
- package/recipes/06-mobile-first-layout.md +25 -2
- package/recipes/09-validation.md +18 -9
- package/recipes/15-defining-styles.md +7 -3
- package/recipes/19-prevalidate-hook.md +29 -83
- package/scripts/build-bib.mjs +7 -3
- package/scripts/build-labels.mjs +33 -16
- package/scripts/read-env.mjs +25 -0
- package/scripts/resolve-book-config.mjs +96 -0
- package/scripts/validate.mjs +125 -84
- package/src/lib/define-style.ts +25 -8
- package/src/lib/nav-href.ts +24 -2
- package/styles/layout.css +14 -8
- package/styles/tokens.css +8 -10
|
@@ -1,113 +1,59 @@
|
|
|
1
|
-
# Recipe 19 —
|
|
1
|
+
# Recipe 19 — generated data before validation (v4.27+)
|
|
2
2
|
|
|
3
|
-
`book-scaffold validate` checks
|
|
3
|
+
`book-scaffold validate` checks citations and cross-references against two derived, normally gitignored files:
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
- `src/data/references.json`, emitted by `book-scaffold build-bib`
|
|
6
|
+
- `src/data/labels.json`, emitted by `book-scaffold build-labels`
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
## TL;DR
|
|
12
|
-
|
|
13
|
-
```json
|
|
14
|
-
{
|
|
15
|
-
"scripts": {
|
|
16
|
-
"prevalidate": "npm run build:bib && npm run build:labels --if-present",
|
|
17
|
-
"validate": "book-scaffold validate"
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
`npm run validate` → automatically runs `prevalidate` first (build:bib + build:labels) → then runs `validate`. CI and local behave identically; no separate `ci:validate` wrapper script needed.
|
|
23
|
-
|
|
24
|
-
---
|
|
25
|
-
|
|
26
|
-
## Why the workaround was needed
|
|
27
|
-
|
|
28
|
-
`brandon-behring/deploy-workflows@v1` (the reusable Cloudflare Workers deploy) runs `npm run <validate-command>` between `npm ci` and `npm run build`. Without the `prevalidate` hook OR an explicit wrapper script, the validate step ran against missing artifacts.
|
|
29
|
-
|
|
30
|
-
The Phase 1c first-deploy of `ssm-foundations` (2026-05-26) hit this — validate emitted 25+ "Unknown bibkey" errors that pointed at chapter content instead of the missing `references.json` artifact. The deploy-time fix shipped as a `ci:validate` wrapper:
|
|
8
|
+
Every profile can use theorem/Figure IDs and XRefs, so every generated book now carries the same npm lifecycle:
|
|
31
9
|
|
|
32
10
|
```json
|
|
33
11
|
{
|
|
34
12
|
"scripts": {
|
|
35
|
-
"
|
|
13
|
+
"prevalidate": "npm run build:bib --if-present && npm run build:labels --if-present",
|
|
14
|
+
"validate": "book-scaffold validate",
|
|
15
|
+
"prebuild": "npm run validate --if-present"
|
|
36
16
|
}
|
|
37
17
|
}
|
|
38
18
|
```
|
|
39
19
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
The cleaner long-term fix is the `prevalidate` npm-lifecycle hook (this recipe). v4.6.0's `create-book` automatically scaffolds it for academic + research-portfolio profiles.
|
|
43
|
-
|
|
44
|
-
---
|
|
20
|
+
`npm run validate` runs `prevalidate` automatically. `npm run build` runs `prebuild`, which delegates to that same path. Academic, tools, minimal, course-notes, and research-portfolio scaffolds use this identical contract.
|
|
45
21
|
|
|
46
|
-
##
|
|
22
|
+
## Direct CLI calls self-heal too
|
|
47
23
|
|
|
48
|
-
|
|
24
|
+
An npm hook cannot protect `npx book-scaffold validate` or a direct bin invocation. Starting in v4.27, the validator itself regenerates each missing artifact before loading it:
|
|
49
25
|
|
|
50
|
-
|
|
26
|
+
1. Missing `labels.json` invokes `build-labels`.
|
|
27
|
+
2. Missing `references.json` invokes `build-bib`, even when no default `bibliography.bib` exists; that valid no-bibliography case emits `{}`.
|
|
28
|
+
3. Existing files are not rewritten.
|
|
29
|
+
4. A child failure stops validation and preserves the original child diagnostic and non-zero status.
|
|
51
30
|
|
|
52
|
-
|
|
53
|
-
{
|
|
54
|
-
"scripts": {
|
|
55
|
-
- "ci:validate": "npm run build:bib && npm run build:labels --if-present && npm run validate",
|
|
56
|
-
+ "prevalidate": "npm run build:bib && npm run build:labels --if-present",
|
|
57
|
-
"validate": "book-scaffold validate"
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
```
|
|
31
|
+
This makes fresh checkouts deterministic without hiding stale existing data. After changing theorem IDs, kinds, slugs, bibliography entries, or `numberStyle`, explicitly rerun the corresponding build command.
|
|
61
32
|
|
|
62
|
-
|
|
33
|
+
## Bibliography path precedence
|
|
63
34
|
|
|
64
|
-
|
|
35
|
+
Books with a shared or non-root bibliography can set:
|
|
65
36
|
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
deploy:
|
|
69
|
-
uses: brandon-behring/deploy-workflows/.github/workflows/deploy-astro-worker.yml@v2
|
|
70
|
-
secrets: inherit
|
|
71
|
-
with:
|
|
72
|
-
working-directory: web
|
|
73
|
-
- validate-command: ci:validate
|
|
74
|
-
+ validate-command: validate
|
|
75
|
-
enable-pr-previews: true
|
|
37
|
+
```dotenv
|
|
38
|
+
BOOK_BIB_PATH=../shared/references.bib
|
|
76
39
|
```
|
|
77
40
|
|
|
78
|
-
|
|
41
|
+
`build-bib` resolves the process environment first, then the project-root `.env`, then `./bibliography.bib`. Relative paths are resolved from the book root. This same precedence applies when validation invokes `build-bib` during self-healing.
|
|
79
42
|
|
|
80
|
-
|
|
43
|
+
## Migrating older consumers
|
|
81
44
|
|
|
82
|
-
|
|
45
|
+
Replace custom `ci:validate` wrappers and profile-conditional hooks with the uniform scripts above. A reusable deploy workflow can call the normal `validate` script; no wrapper needs to repeat the artifact chain.
|
|
83
46
|
|
|
84
47
|
```diff
|
|
85
48
|
{
|
|
86
49
|
"scripts": {
|
|
87
|
-
- "
|
|
88
|
-
+ "
|
|
89
|
-
"
|
|
50
|
+
- "ci:validate": "npm run build:bib && npm run build:labels && npm run validate",
|
|
51
|
+
+ "prevalidate": "npm run build:bib --if-present && npm run build:labels --if-present",
|
|
52
|
+
"validate": "book-scaffold validate",
|
|
53
|
+
- "prebuild": "npm run build:bib && npm run build:labels && npm run validate"
|
|
54
|
+
+ "prebuild": "npm run validate --if-present"
|
|
90
55
|
}
|
|
91
56
|
}
|
|
92
57
|
```
|
|
93
58
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
---
|
|
97
|
-
|
|
98
|
-
## When `prevalidate` is NOT needed
|
|
99
|
-
|
|
100
|
-
Profiles that don't run cite-key or XRef validation don't need `prevalidate`. Specifically:
|
|
101
|
-
|
|
102
|
-
- `tools`, `minimal`: no `<Cite>` resolution against `references.json`.
|
|
103
|
-
- `course-notes`: depends on whether the consumer uses `<Cite>` (some do for source-tier attribution).
|
|
104
|
-
|
|
105
|
-
For these profiles, `book-scaffold validate` operates on chapter structure + XRef IDs only; `prevalidate` would be a no-op. The v4.6.0 create-book template adds `prevalidate` ONLY for academic + research-portfolio.
|
|
106
|
-
|
|
107
|
-
---
|
|
108
|
-
|
|
109
|
-
## Why this matters
|
|
110
|
-
|
|
111
|
-
Recipe 19 is part of [issue #76](https://github.com/brandon-behring/book-scaffold-astro/issues/76)'s v4.6 bundle. Companion: [recipe 18 — chapter-route ownership](./18-chapter-route-ownership.md), which fixes another silent-CI surface from the same Phase 1c first-deploys.
|
|
112
|
-
|
|
113
|
-
The `prevalidate` convention also closes [issue #77](https://github.com/brandon-behring/book-scaffold-astro/issues/77) — the v4.6 validator now emits a single re-framed error pointing at this recipe when `references.json` is missing, replacing the noisy 25-symptom output.
|
|
59
|
+
The historical profile split is intentionally gone: non-academic profiles still need labels, and an empty bibliography build is a supported no-op that creates the importable empty artifact.
|
package/scripts/build-bib.mjs
CHANGED
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
40
40
|
import { dirname, resolve } from 'node:path';
|
|
41
41
|
import { fileURLToPath } from 'node:url';
|
|
42
|
+
import { readEnvFile } from './read-env.mjs';
|
|
42
43
|
|
|
43
44
|
// --help / -h: non-mutating (closes #14).
|
|
44
45
|
const USAGE = `Usage: book-scaffold build-bib
|
|
@@ -49,7 +50,8 @@ AND sources/manifest.yaml -> src/data/sources.json (tools-profile sources for
|
|
|
49
50
|
the /references page). Either input may be absent.
|
|
50
51
|
|
|
51
52
|
Env:
|
|
52
|
-
BOOK_BIB_PATH Override path to .bib file (
|
|
53
|
+
BOOK_BIB_PATH Override path to .bib file (process env wins, then root
|
|
54
|
+
.env; default: ./bibliography.bib).
|
|
53
55
|
|
|
54
56
|
Options:
|
|
55
57
|
--help, -h Print this message and exit (non-mutating).
|
|
@@ -64,11 +66,13 @@ import '@citation-js/plugin-bibtex';
|
|
|
64
66
|
|
|
65
67
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
66
68
|
const PROJECT_ROOT = process.cwd();
|
|
69
|
+
const dotenv = readEnvFile(PROJECT_ROOT);
|
|
67
70
|
|
|
68
71
|
// Default: bibliography.bib at scaffold root.
|
|
69
72
|
// Override via BOOK_BIB_PATH=path/to/your.bib (absolute or relative to cwd).
|
|
70
|
-
const
|
|
71
|
-
|
|
73
|
+
const configuredBibPath = process.env.BOOK_BIB_PATH ?? dotenv.BOOK_BIB_PATH;
|
|
74
|
+
const BIB_PATH = configuredBibPath
|
|
75
|
+
? resolve(PROJECT_ROOT, configuredBibPath)
|
|
72
76
|
: resolve(PROJECT_ROOT, 'bibliography.bib');
|
|
73
77
|
const OUT_PATH = resolve(PROJECT_ROOT, 'src/data/references.json');
|
|
74
78
|
const SOURCES_PATH = resolve(PROJECT_ROOT, 'sources/manifest.yaml');
|
package/scripts/build-labels.mjs
CHANGED
|
@@ -12,14 +12,15 @@
|
|
|
12
12
|
*
|
|
13
13
|
* Kind-aware (#126): a `<Theorem kind="proposition">` resolves its display
|
|
14
14
|
* WORD through the shared theorem-label vocabulary (`Proposition 8.1`), not a
|
|
15
|
-
* kind-blind `Theorem 8.1`.
|
|
16
|
-
*
|
|
15
|
+
* kind-blind `Theorem 8.1`. v4.27.0 (#175) lets defineBookConfig / defineStyle
|
|
16
|
+
* select shared (the historical default) or independent per-kind counters.
|
|
17
17
|
*
|
|
18
18
|
* The resulting map is consumed by XRef.astro via
|
|
19
19
|
* `import.meta.glob('/src/data/labels.json', { eager: true })`.
|
|
20
20
|
*
|
|
21
|
-
* Per-chapter
|
|
22
|
-
*
|
|
21
|
+
* Per-chapter counters: each chapter resets the sequence, so two chapters can
|
|
22
|
+
* both have `Theorem 1` without colliding. Labelable component families always
|
|
23
|
+
* have independent counters; theorem kinds share or split per numberStyle. The chapter
|
|
23
24
|
* number comes from frontmatter:
|
|
24
25
|
* - tools profile: `chapter` field (number).
|
|
25
26
|
* - academic profile: `week` field (number).
|
|
@@ -46,6 +47,7 @@
|
|
|
46
47
|
import { readFile, writeFile, mkdir, readdir } from 'node:fs/promises';
|
|
47
48
|
import { resolve, relative, join, basename, dirname } from 'node:path';
|
|
48
49
|
import { readChaptersBase } from './walk-mdx.mjs';
|
|
50
|
+
import { loadResolvedBookConfig } from './resolve-book-config.mjs';
|
|
49
51
|
// #126: reuse the ONE kind vocabulary (theorem-label.ts → its own lean tsup
|
|
50
52
|
// entry) so a <Theorem kind="proposition"> xref reads "Proposition N.M", not a
|
|
51
53
|
// kind-blind "Theorem N.M" — and so an unknown/absent kind FAILS HERE (same
|
|
@@ -66,6 +68,9 @@ Env:
|
|
|
66
68
|
|
|
67
69
|
Options:
|
|
68
70
|
--help, -h Print this message and exit (non-mutating).
|
|
71
|
+
|
|
72
|
+
Numbering is read from defineBookConfig / defineStyle numberStyle. It defaults
|
|
73
|
+
to shared when no scaffold integration can be resolved.
|
|
69
74
|
`;
|
|
70
75
|
|
|
71
76
|
if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
@@ -182,6 +187,7 @@ async function walkChapters(dir) {
|
|
|
182
187
|
|
|
183
188
|
async function main() {
|
|
184
189
|
const cwd = process.cwd();
|
|
190
|
+
const { numberStyle } = await loadResolvedBookConfig(cwd);
|
|
185
191
|
const chaptersDir = resolve(cwd, CHAPTERS_DIR);
|
|
186
192
|
const files = await walkChapters(chaptersDir);
|
|
187
193
|
|
|
@@ -207,10 +213,6 @@ async function main() {
|
|
|
207
213
|
const id = extractAttr(attrs, 'id');
|
|
208
214
|
if (!id) continue;
|
|
209
215
|
|
|
210
|
-
// One shared counter per component (keyed by the JSX name, NOT the
|
|
211
|
-
// amsthm kind) — so theorem/proposition/lemma share a sequence exactly
|
|
212
|
-
// as they do under amsthm, and existing numbers never shift (#126).
|
|
213
|
-
counters[componentName] = (counters[componentName] ?? 0) + 1;
|
|
214
216
|
foundInChapter += 1;
|
|
215
217
|
totalIds += 1;
|
|
216
218
|
|
|
@@ -224,13 +226,16 @@ async function main() {
|
|
|
224
226
|
// "no kind=" rather than the misleading kind="null".
|
|
225
227
|
const labelOverride = extractAttr(attrs, 'label');
|
|
226
228
|
let word;
|
|
229
|
+
let theoremKind;
|
|
227
230
|
if (labelOverride == null) {
|
|
228
231
|
if (componentName === 'Theorem') {
|
|
229
232
|
try {
|
|
230
|
-
|
|
233
|
+
const resolvedLabel = theoremLabel({
|
|
231
234
|
kind: extractAttr(attrs, 'kind') ?? undefined,
|
|
232
235
|
type: extractAttr(attrs, 'type') ?? undefined,
|
|
233
|
-
})
|
|
236
|
+
});
|
|
237
|
+
word = resolvedLabel.fullLabel;
|
|
238
|
+
theoremKind = resolvedLabel.kind;
|
|
234
239
|
} catch (err) {
|
|
235
240
|
throw new Error(
|
|
236
241
|
`<Theorem id="${id}"> in ${relative(cwd, file)}: ${err.message}`,
|
|
@@ -241,13 +246,25 @@ async function main() {
|
|
|
241
246
|
}
|
|
242
247
|
}
|
|
243
248
|
|
|
249
|
+
// label= is a custom, unnumbered display and therefore does not consume
|
|
250
|
+
// either the historical shared sequence or a per-kind sequence. This
|
|
251
|
+
// keeps later auto-numbered entries stable when prose-only labels move.
|
|
252
|
+
const counterKey =
|
|
253
|
+
numberStyle === 'per-kind' && componentName === 'Theorem'
|
|
254
|
+
? `Theorem/${theoremKind}`
|
|
255
|
+
: componentName;
|
|
256
|
+
if (labelOverride == null) {
|
|
257
|
+
counters[counterKey] = (counters[counterKey] ?? 0) + 1;
|
|
258
|
+
}
|
|
259
|
+
|
|
244
260
|
// The bare counter string the heading reuses: Theorem.astro reads
|
|
245
261
|
// `number` by id and renders it, so heading == xref by construction.
|
|
246
262
|
// A `label=` override opts out of auto-numbering → number is null.
|
|
247
|
-
const number =
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
263
|
+
const number = labelOverride != null
|
|
264
|
+
? null
|
|
265
|
+
: chapterNum != null
|
|
266
|
+
? `${chapterNum}.${counters[counterKey]}`
|
|
267
|
+
: String(counters[counterKey]);
|
|
251
268
|
const display = labelOverride ?? `${word} ${number}`;
|
|
252
269
|
|
|
253
270
|
if (labels[id]) {
|
|
@@ -263,7 +280,7 @@ async function main() {
|
|
|
263
280
|
// labels.json serves any deploy base (root or path-proxied series).
|
|
264
281
|
href: `chapters/${slug}#${id}`,
|
|
265
282
|
display,
|
|
266
|
-
number
|
|
283
|
+
number,
|
|
267
284
|
};
|
|
268
285
|
}
|
|
269
286
|
|
|
@@ -281,7 +298,7 @@ async function main() {
|
|
|
281
298
|
process.stdout.write(
|
|
282
299
|
`build-labels: ${totalIds} id${totalIds === 1 ? '' : 's'} across ` +
|
|
283
300
|
`${chaptersWithIds} chapter${chaptersWithIds === 1 ? '' : 's'} → ` +
|
|
284
|
-
`${OUTPUT_PATH}\n`,
|
|
301
|
+
`${OUTPUT_PATH} (number-style=${numberStyle})\n`,
|
|
285
302
|
);
|
|
286
303
|
}
|
|
287
304
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
|
|
4
|
+
/** Read a small dotenv-style file without mutating process.env. */
|
|
5
|
+
export function readEnvFile(projectRoot = process.cwd()) {
|
|
6
|
+
try {
|
|
7
|
+
const out = {};
|
|
8
|
+
const source = readFileSync(resolve(projectRoot, '.env'), 'utf8');
|
|
9
|
+
for (const line of source.split(/\r?\n/)) {
|
|
10
|
+
const match = line.match(/^\s*([A-Z_][A-Z0-9_]*)\s*=\s*(.*?)\s*$/);
|
|
11
|
+
if (!match) continue;
|
|
12
|
+
let value = match[2] ?? '';
|
|
13
|
+
if (
|
|
14
|
+
(value.startsWith('"') && value.endsWith('"')) ||
|
|
15
|
+
(value.startsWith("'") && value.endsWith("'"))
|
|
16
|
+
) {
|
|
17
|
+
value = value.slice(1, -1);
|
|
18
|
+
}
|
|
19
|
+
out[match[1]] = value;
|
|
20
|
+
}
|
|
21
|
+
return out;
|
|
22
|
+
} catch {
|
|
23
|
+
return {};
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { loadConfigFromFile } from 'vite';
|
|
4
|
+
|
|
5
|
+
export const DEFAULT_TOOLING_CONFIG = Object.freeze({
|
|
6
|
+
preset: null,
|
|
7
|
+
numberStyle: 'shared',
|
|
8
|
+
integrationFound: false,
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const ASTRO_CONFIG_NAMES = [
|
|
12
|
+
'astro.config.mjs',
|
|
13
|
+
'astro.config.ts',
|
|
14
|
+
'astro.config.js',
|
|
15
|
+
'astro.config.cjs',
|
|
16
|
+
];
|
|
17
|
+
const PRESETS = ['academic', 'tools', 'minimal', 'course-notes', 'research-portfolio'];
|
|
18
|
+
|
|
19
|
+
function findAstroConfig(projectRoot) {
|
|
20
|
+
for (const name of ASTRO_CONFIG_NAMES) {
|
|
21
|
+
const path = resolve(projectRoot, name);
|
|
22
|
+
if (existsSync(path)) return path;
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function assertNumberStyle(value, configPath) {
|
|
28
|
+
if (value !== 'shared' && value !== 'per-kind') {
|
|
29
|
+
throw new Error(
|
|
30
|
+
`book-scaffold tooling: ${configPath} resolved invalid numberStyle ` +
|
|
31
|
+
`${JSON.stringify(value)}; expected shared | per-kind.`,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function assertPreset(value, configPath) {
|
|
37
|
+
if (value != null && !PRESETS.includes(value)) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
`book-scaffold tooling: ${configPath} resolved invalid preset ` +
|
|
40
|
+
`${JSON.stringify(value)}; expected ${PRESETS.join(' | ')}.`,
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Evaluate the consumer's actual Astro config and read the scaffold
|
|
47
|
+
* integration's internal resolved metadata. Absence of a config/integration is
|
|
48
|
+
* a supported legacy shape and preserves shared numbering. Evaluation errors
|
|
49
|
+
* are deliberately not swallowed: tooling must not silently use wrong config.
|
|
50
|
+
*/
|
|
51
|
+
export async function loadResolvedBookConfig(projectRoot = process.cwd()) {
|
|
52
|
+
const configPath = findAstroConfig(projectRoot);
|
|
53
|
+
if (!configPath) return { ...DEFAULT_TOOLING_CONFIG };
|
|
54
|
+
|
|
55
|
+
let loaded;
|
|
56
|
+
try {
|
|
57
|
+
loaded = await loadConfigFromFile(
|
|
58
|
+
{ command: 'build', mode: 'production', isSsrBuild: true, isPreview: false },
|
|
59
|
+
configPath,
|
|
60
|
+
projectRoot,
|
|
61
|
+
'silent',
|
|
62
|
+
);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
65
|
+
throw new Error(`book-scaffold tooling: failed to evaluate ${configPath}: ${detail}`, {
|
|
66
|
+
cause: error,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!loaded) {
|
|
71
|
+
throw new Error(`book-scaffold tooling: Vite did not return config for ${configPath}.`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const integrations = Array.isArray(loaded.config?.integrations)
|
|
75
|
+
? loaded.config.integrations.flat(Infinity)
|
|
76
|
+
: [];
|
|
77
|
+
const integration = integrations.find((candidate) => candidate?.name === 'book-scaffold-astro');
|
|
78
|
+
if (!integration) return { ...DEFAULT_TOOLING_CONFIG };
|
|
79
|
+
|
|
80
|
+
const metadata = integration.__bookScaffoldResolvedConfig;
|
|
81
|
+
if (!metadata) {
|
|
82
|
+
// A config can contain an older scaffold integration with no metadata.
|
|
83
|
+
// Preserve the historical numbering default rather than treating upgrade
|
|
84
|
+
// sequencing as a config error.
|
|
85
|
+
return { ...DEFAULT_TOOLING_CONFIG, integrationFound: true };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const numberStyle = metadata.numberStyle ?? 'shared';
|
|
89
|
+
assertNumberStyle(numberStyle, configPath);
|
|
90
|
+
assertPreset(metadata.preset, configPath);
|
|
91
|
+
return {
|
|
92
|
+
preset: metadata.preset ?? null,
|
|
93
|
+
numberStyle,
|
|
94
|
+
integrationFound: true,
|
|
95
|
+
};
|
|
96
|
+
}
|