@brandon_m_behring/book-scaffold-astro 3.3.0 → 3.4.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/dist/index.d.ts +2 -2
- package/dist/index.mjs +42 -15
- package/dist/schemas.d.ts +35 -2
- package/dist/schemas.mjs +34 -14
- package/package.json +1 -1
- package/pages/frontmatter/[...slug].astro +48 -0
- package/recipes/12-where-to-file-issues.md +58 -0
- package/scripts/build-bib.mjs +18 -0
- package/scripts/build-figures.mjs +19 -0
- package/scripts/build-labels.mjs +20 -0
- package/scripts/render-notebooks.mjs +19 -0
- package/scripts/validate.mjs +51 -37
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AstroUserConfig, AstroIntegration } from 'astro';
|
|
2
|
-
import {
|
|
3
|
-
export { A as AcademicChapter, B as
|
|
2
|
+
import { c as BookConfigOptions, f as BookScaffoldIntegrationOptions, y as volatilityLevels } from './types-Bb97Na9S.js';
|
|
3
|
+
export { A as AcademicChapter, B as BOOK_PRESETS, a as BOOK_PROFILES, b as BookConfigError, d as BookPreset, e as BookProfile, g as BookSchemasOptions, C as ChapterFor, h as CourseNotesChapter, M as MinimalChapter, P as ProfileDefinition, R as RouteToggles, T as ToolsChapter, i as academicChapterSchema, j as academicParts, k as changeKinds, l as changelogSchema, m as chapterStatus, n as courseNotesChapterSchema, o as defineProfile, p as minimalChapterSchema, q as patternCategories, r as patternsSchema, s as resolvePreset, t as resolveProfile, u as sourceTiers, v as sourcesSchema, w as toolSlugs, x as toolsChapterSchema } from './types-Bb97Na9S.js';
|
|
4
4
|
import 'astro/zod';
|
|
5
5
|
|
|
6
6
|
declare function defineBookConfig(opts: BookConfigOptions): Promise<AstroUserConfig>;
|
package/dist/index.mjs
CHANGED
|
@@ -263,8 +263,10 @@ var academicProfile = defineProfile({
|
|
|
263
263
|
print: true,
|
|
264
264
|
chapters: false,
|
|
265
265
|
// academic consumers ship their own week-based /chapters listing
|
|
266
|
-
convergence: false
|
|
266
|
+
convergence: false,
|
|
267
267
|
// tools-profile-specific
|
|
268
|
+
frontmatter: false
|
|
269
|
+
// opt-in per book; see #7
|
|
268
270
|
},
|
|
269
271
|
styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css"],
|
|
270
272
|
katex: true
|
|
@@ -280,8 +282,10 @@ var toolsProfile = defineProfile({
|
|
|
280
282
|
print: true,
|
|
281
283
|
chapters: true,
|
|
282
284
|
// tools profile ships a flat chapter index
|
|
283
|
-
convergence: true
|
|
285
|
+
convergence: true,
|
|
284
286
|
// tools profile ships convergence dashboard
|
|
287
|
+
frontmatter: false
|
|
288
|
+
// opt-in per book; see #7
|
|
285
289
|
},
|
|
286
290
|
styles: [
|
|
287
291
|
"tokens.css",
|
|
@@ -304,7 +308,9 @@ var minimalProfile = defineProfile({
|
|
|
304
308
|
search: true,
|
|
305
309
|
print: true,
|
|
306
310
|
chapters: false,
|
|
307
|
-
convergence: false
|
|
311
|
+
convergence: false,
|
|
312
|
+
frontmatter: false
|
|
313
|
+
// opt-in per book; see #7
|
|
308
314
|
},
|
|
309
315
|
styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css"]
|
|
310
316
|
});
|
|
@@ -319,7 +325,9 @@ var courseNotesProfile = defineProfile({
|
|
|
319
325
|
print: true,
|
|
320
326
|
chapters: false,
|
|
321
327
|
// multi-book consumers route via [book]/[slug] themselves
|
|
322
|
-
convergence: false
|
|
328
|
+
convergence: false,
|
|
329
|
+
frontmatter: false
|
|
330
|
+
// opt-in per book; see #7
|
|
323
331
|
},
|
|
324
332
|
styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css"]
|
|
325
333
|
});
|
|
@@ -335,6 +343,7 @@ var BOOK_PROFILES = Object.keys(PROFILES);
|
|
|
335
343
|
|
|
336
344
|
// src/types.ts
|
|
337
345
|
import { existsSync, readFileSync } from "fs";
|
|
346
|
+
var BOOK_PRESETS = BOOK_PROFILES;
|
|
338
347
|
var BookConfigError = class extends Error {
|
|
339
348
|
constructor(message) {
|
|
340
349
|
super(message);
|
|
@@ -359,29 +368,33 @@ function readEnvFile(path = ".env") {
|
|
|
359
368
|
return {};
|
|
360
369
|
}
|
|
361
370
|
}
|
|
362
|
-
function
|
|
363
|
-
let candidate =
|
|
371
|
+
function resolvePreset(explicitPreset, explicitProfile) {
|
|
372
|
+
let candidate = explicitPreset ?? explicitProfile ?? process.env.BOOK_PRESET ?? process.env.BOOK_PROFILE;
|
|
364
373
|
let source = "default";
|
|
365
|
-
if (
|
|
366
|
-
else if (process.env.BOOK_PROFILE) source = "env";
|
|
374
|
+
if (explicitPreset || explicitProfile) source = "param";
|
|
375
|
+
else if (process.env.BOOK_PRESET || process.env.BOOK_PROFILE) source = "env";
|
|
367
376
|
if (!candidate) {
|
|
368
|
-
const
|
|
377
|
+
const env = readEnvFile();
|
|
378
|
+
const fromFile = env.BOOK_PRESET ?? env.BOOK_PROFILE;
|
|
369
379
|
if (fromFile) {
|
|
370
380
|
candidate = fromFile;
|
|
371
381
|
source = "dotenv";
|
|
372
382
|
}
|
|
373
383
|
}
|
|
374
384
|
candidate = candidate ?? "minimal";
|
|
375
|
-
if (!
|
|
385
|
+
if (!BOOK_PRESETS.includes(candidate)) {
|
|
376
386
|
throw new BookConfigError(
|
|
377
|
-
`
|
|
387
|
+
`preset must be one of ${BOOK_PRESETS.join(" | ")} (got ${JSON.stringify(candidate)})`
|
|
378
388
|
);
|
|
379
389
|
}
|
|
380
390
|
if (source === "default") {
|
|
381
|
-
console.warn("book-scaffold-astro:
|
|
391
|
+
console.warn("book-scaffold-astro: BOOK_PRESET not set; falling back to 'minimal'.");
|
|
382
392
|
}
|
|
383
393
|
return candidate;
|
|
384
394
|
}
|
|
395
|
+
function resolveProfile(explicit) {
|
|
396
|
+
return resolvePreset(void 0, explicit);
|
|
397
|
+
}
|
|
385
398
|
|
|
386
399
|
// src/integration.ts
|
|
387
400
|
import { fileURLToPath } from "url";
|
|
@@ -435,7 +448,11 @@ var ROUTE_REGISTRY = {
|
|
|
435
448
|
search: { pattern: "/search", file: "search.astro" },
|
|
436
449
|
print: { pattern: "/print", file: "print.astro" },
|
|
437
450
|
chapters: { pattern: "/chapters", file: "chapters.astro" },
|
|
438
|
-
convergence: { pattern: "/convergence", file: "convergence.astro" }
|
|
451
|
+
convergence: { pattern: "/convergence", file: "convergence.astro" },
|
|
452
|
+
// v3.4.0 (#7): consumer-collection-backed frontmatter route. Opt-in via
|
|
453
|
+
// routes: { frontmatter: true } AND content.config.ts defining the
|
|
454
|
+
// collection (use frontmatterCollection() helper from /schemas subpath).
|
|
455
|
+
frontmatter: { pattern: "/frontmatter/[slug]", file: "frontmatter/[...slug].astro" }
|
|
439
456
|
};
|
|
440
457
|
function resolvePage(file) {
|
|
441
458
|
return fileURLToPath(new URL(`../pages/${file}`, import.meta.url));
|
|
@@ -466,9 +483,14 @@ function bookScaffoldIntegration(opts) {
|
|
|
466
483
|
}
|
|
467
484
|
const consumerRoot = fileURLToPath(config.root);
|
|
468
485
|
const resolvedMdxPath = resolveMdxComponentsPath(consumerRoot, mdxComponentsModule);
|
|
486
|
+
const presetLiteral = JSON.stringify(profile);
|
|
469
487
|
updateConfig({
|
|
470
488
|
vite: {
|
|
471
|
-
plugins: [makeMdxComponentsVitePlugin(resolvedMdxPath)]
|
|
489
|
+
plugins: [makeMdxComponentsVitePlugin(resolvedMdxPath)],
|
|
490
|
+
define: {
|
|
491
|
+
"import.meta.env.BOOK_PRESET": presetLiteral,
|
|
492
|
+
"import.meta.env.BOOK_PROFILE": presetLiteral
|
|
493
|
+
}
|
|
472
494
|
}
|
|
473
495
|
});
|
|
474
496
|
}
|
|
@@ -478,7 +500,7 @@ function bookScaffoldIntegration(opts) {
|
|
|
478
500
|
|
|
479
501
|
// src/config.ts
|
|
480
502
|
async function defineBookConfig(opts) {
|
|
481
|
-
const profile =
|
|
503
|
+
const profile = resolvePreset(opts.preset, opts.profile);
|
|
482
504
|
const remarkPlugins = [];
|
|
483
505
|
const rehypePlugins = [];
|
|
484
506
|
if (profile === "academic") {
|
|
@@ -531,6 +553,8 @@ async function defineBookConfig(opts) {
|
|
|
531
553
|
...userMarkdown
|
|
532
554
|
};
|
|
533
555
|
const {
|
|
556
|
+
preset: _preset,
|
|
557
|
+
// v3.4.0
|
|
534
558
|
profile: _profile,
|
|
535
559
|
routes: _routes,
|
|
536
560
|
// v3.3.0
|
|
@@ -541,6 +565,7 @@ async function defineBookConfig(opts) {
|
|
|
541
565
|
markdown: _markdown,
|
|
542
566
|
...rest
|
|
543
567
|
} = opts;
|
|
568
|
+
void _preset;
|
|
544
569
|
void _profile;
|
|
545
570
|
void _routes;
|
|
546
571
|
void _mdxComponentsModule;
|
|
@@ -598,6 +623,7 @@ function freshnessLabel(f) {
|
|
|
598
623
|
}
|
|
599
624
|
}
|
|
600
625
|
export {
|
|
626
|
+
BOOK_PRESETS,
|
|
601
627
|
BOOK_PROFILES,
|
|
602
628
|
BookConfigError,
|
|
603
629
|
academicChapterSchema,
|
|
@@ -615,6 +641,7 @@ export {
|
|
|
615
641
|
minimalChapterSchema,
|
|
616
642
|
patternCategories,
|
|
617
643
|
patternsSchema,
|
|
644
|
+
resolvePreset,
|
|
618
645
|
resolveProfile,
|
|
619
646
|
sourceTiers,
|
|
620
647
|
sourcesSchema,
|
package/dist/schemas.d.ts
CHANGED
|
@@ -1,7 +1,40 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defineCollection } from 'astro:content';
|
|
2
|
+
import { g as BookSchemasOptions } from './types-Bb97Na9S.js';
|
|
2
3
|
import 'astro';
|
|
3
4
|
import 'astro/zod';
|
|
4
5
|
|
|
6
|
+
/**
|
|
7
|
+
* v3.4.0 (closes #7): consumer-facing helper to define a `frontmatter`
|
|
8
|
+
* content collection that the scaffold's auto-injected
|
|
9
|
+
* `/frontmatter/[slug]` route can render.
|
|
10
|
+
*
|
|
11
|
+
* Usage in consumer's content.config.ts:
|
|
12
|
+
*
|
|
13
|
+
* import { defineBookSchemas, frontmatterCollection } from
|
|
14
|
+
* '@brandon_m_behring/book-scaffold-astro/schemas';
|
|
15
|
+
* import { z } from 'astro:content';
|
|
16
|
+
*
|
|
17
|
+
* export const { collections } = {
|
|
18
|
+
* collections: {
|
|
19
|
+
* ...defineBookSchemas().collections,
|
|
20
|
+
* frontmatter: frontmatterCollection(z.object({
|
|
21
|
+
* slug: z.string(),
|
|
22
|
+
* title: z.string(),
|
|
23
|
+
* order: z.number(),
|
|
24
|
+
* description: z.string().optional(),
|
|
25
|
+
* })),
|
|
26
|
+
* },
|
|
27
|
+
* };
|
|
28
|
+
*
|
|
29
|
+
* Then enable the route via `defineBookConfig({ routes: { frontmatter: true } })`
|
|
30
|
+
* and drop MDX files under `src/content/frontmatter/`. The scaffold-injected
|
|
31
|
+
* route renders each entry with the consumer's mdx-components in scope (issue #2
|
|
32
|
+
* plumbing applies).
|
|
33
|
+
*
|
|
34
|
+
* Default loader: `**\/*.{md,mdx}` under `./src/content/frontmatter` (excluding
|
|
35
|
+
* underscore-prefixed files). Override `base` via the second arg.
|
|
36
|
+
*/
|
|
37
|
+
declare function frontmatterCollection(schema: Parameters<typeof defineCollection>[0]['schema'], base?: string): unknown;
|
|
5
38
|
/**
|
|
6
39
|
* Returns the package's default content collections. Closed shape per Q5;
|
|
7
40
|
* consumer extends via object spread and Zod `.extend()` (see PACKAGE_DESIGN.md §5).
|
|
@@ -10,4 +43,4 @@ declare function defineBookSchemas(opts?: BookSchemasOptions): {
|
|
|
10
43
|
collections: Record<string, unknown>;
|
|
11
44
|
};
|
|
12
45
|
|
|
13
|
-
export { defineBookSchemas };
|
|
46
|
+
export { defineBookSchemas, frontmatterCollection };
|
package/dist/schemas.mjs
CHANGED
|
@@ -148,8 +148,10 @@ var academicProfile = defineProfile({
|
|
|
148
148
|
print: true,
|
|
149
149
|
chapters: false,
|
|
150
150
|
// academic consumers ship their own week-based /chapters listing
|
|
151
|
-
convergence: false
|
|
151
|
+
convergence: false,
|
|
152
152
|
// tools-profile-specific
|
|
153
|
+
frontmatter: false
|
|
154
|
+
// opt-in per book; see #7
|
|
153
155
|
},
|
|
154
156
|
styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css"],
|
|
155
157
|
katex: true
|
|
@@ -165,8 +167,10 @@ var toolsProfile = defineProfile({
|
|
|
165
167
|
print: true,
|
|
166
168
|
chapters: true,
|
|
167
169
|
// tools profile ships a flat chapter index
|
|
168
|
-
convergence: true
|
|
170
|
+
convergence: true,
|
|
169
171
|
// tools profile ships convergence dashboard
|
|
172
|
+
frontmatter: false
|
|
173
|
+
// opt-in per book; see #7
|
|
170
174
|
},
|
|
171
175
|
styles: [
|
|
172
176
|
"tokens.css",
|
|
@@ -189,7 +193,9 @@ var minimalProfile = defineProfile({
|
|
|
189
193
|
search: true,
|
|
190
194
|
print: true,
|
|
191
195
|
chapters: false,
|
|
192
|
-
convergence: false
|
|
196
|
+
convergence: false,
|
|
197
|
+
frontmatter: false
|
|
198
|
+
// opt-in per book; see #7
|
|
193
199
|
},
|
|
194
200
|
styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css"]
|
|
195
201
|
});
|
|
@@ -204,7 +210,9 @@ var courseNotesProfile = defineProfile({
|
|
|
204
210
|
print: true,
|
|
205
211
|
chapters: false,
|
|
206
212
|
// multi-book consumers route via [book]/[slug] themselves
|
|
207
|
-
convergence: false
|
|
213
|
+
convergence: false,
|
|
214
|
+
frontmatter: false
|
|
215
|
+
// opt-in per book; see #7
|
|
208
216
|
},
|
|
209
217
|
styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css"]
|
|
210
218
|
});
|
|
@@ -220,6 +228,7 @@ var BOOK_PROFILES = Object.keys(PROFILES);
|
|
|
220
228
|
|
|
221
229
|
// src/types.ts
|
|
222
230
|
import { existsSync, readFileSync } from "fs";
|
|
231
|
+
var BOOK_PRESETS = BOOK_PROFILES;
|
|
223
232
|
var BookConfigError = class extends Error {
|
|
224
233
|
constructor(message) {
|
|
225
234
|
super(message);
|
|
@@ -244,33 +253,43 @@ function readEnvFile(path = ".env") {
|
|
|
244
253
|
return {};
|
|
245
254
|
}
|
|
246
255
|
}
|
|
247
|
-
function
|
|
248
|
-
let candidate =
|
|
256
|
+
function resolvePreset(explicitPreset, explicitProfile) {
|
|
257
|
+
let candidate = explicitPreset ?? explicitProfile ?? process.env.BOOK_PRESET ?? process.env.BOOK_PROFILE;
|
|
249
258
|
let source = "default";
|
|
250
|
-
if (
|
|
251
|
-
else if (process.env.BOOK_PROFILE) source = "env";
|
|
259
|
+
if (explicitPreset || explicitProfile) source = "param";
|
|
260
|
+
else if (process.env.BOOK_PRESET || process.env.BOOK_PROFILE) source = "env";
|
|
252
261
|
if (!candidate) {
|
|
253
|
-
const
|
|
262
|
+
const env = readEnvFile();
|
|
263
|
+
const fromFile = env.BOOK_PRESET ?? env.BOOK_PROFILE;
|
|
254
264
|
if (fromFile) {
|
|
255
265
|
candidate = fromFile;
|
|
256
266
|
source = "dotenv";
|
|
257
267
|
}
|
|
258
268
|
}
|
|
259
269
|
candidate = candidate ?? "minimal";
|
|
260
|
-
if (!
|
|
270
|
+
if (!BOOK_PRESETS.includes(candidate)) {
|
|
261
271
|
throw new BookConfigError(
|
|
262
|
-
`
|
|
272
|
+
`preset must be one of ${BOOK_PRESETS.join(" | ")} (got ${JSON.stringify(candidate)})`
|
|
263
273
|
);
|
|
264
274
|
}
|
|
265
275
|
if (source === "default") {
|
|
266
|
-
console.warn("book-scaffold-astro:
|
|
276
|
+
console.warn("book-scaffold-astro: BOOK_PRESET not set; falling back to 'minimal'.");
|
|
267
277
|
}
|
|
268
278
|
return candidate;
|
|
269
279
|
}
|
|
270
280
|
|
|
271
281
|
// src/schemas-entry.ts
|
|
282
|
+
function frontmatterCollection(schema, base = "./src/content/frontmatter") {
|
|
283
|
+
return defineCollection({
|
|
284
|
+
loader: glob({
|
|
285
|
+
pattern: ["**/*.{md,mdx}", "!**/_*"],
|
|
286
|
+
base
|
|
287
|
+
}),
|
|
288
|
+
schema
|
|
289
|
+
});
|
|
290
|
+
}
|
|
272
291
|
function defineBookSchemas(opts = {}) {
|
|
273
|
-
const profile =
|
|
292
|
+
const profile = resolvePreset(opts.preset, opts.profile);
|
|
274
293
|
const chaptersBase = opts.chaptersBase ?? "./src/content/chapters";
|
|
275
294
|
const schemaForProfile = profile === "academic" ? academicChapterSchema : profile === "course-notes" ? courseNotesChapterSchema : profile === "minimal" ? minimalChapterSchema : toolsChapterSchema;
|
|
276
295
|
const chapters = defineCollection({
|
|
@@ -305,5 +324,6 @@ function defineBookSchemas(opts = {}) {
|
|
|
305
324
|
return { collections };
|
|
306
325
|
}
|
|
307
326
|
export {
|
|
308
|
-
defineBookSchemas
|
|
327
|
+
defineBookSchemas,
|
|
328
|
+
frontmatterCollection
|
|
309
329
|
};
|
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": "3.
|
|
4
|
+
"version": "3.4.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Brandon Behring",
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* pages/frontmatter/[...slug].astro — auto-injected route for the
|
|
4
|
+
* consumer-defined `frontmatter` content collection. v3.4.0 closes #7.
|
|
5
|
+
*
|
|
6
|
+
* Opt-in: consumer enables via defineBookConfig({ routes: { frontmatter: true } })
|
|
7
|
+
* AND defines the collection in src/content.config.ts via the
|
|
8
|
+
* `frontmatterCollection(schema)` helper. Drops MDX files under
|
|
9
|
+
* src/content/frontmatter/; each renders at /frontmatter/<slug>/.
|
|
10
|
+
*
|
|
11
|
+
* Why a single template route (not consumer-owned): the rendering shape
|
|
12
|
+
* is uniform (Base layout + prose + Content) — every consumer would write
|
|
13
|
+
* the same file. Centralizing it keeps the consumer-side surface to just
|
|
14
|
+
* the schema definition + the routes-toggle flip.
|
|
15
|
+
*
|
|
16
|
+
* mdx-components plumbing (issue #2): the consumer's src/mdx-components.ts
|
|
17
|
+
* components are imported via the virtual module and threaded through
|
|
18
|
+
* <Content components={mdxComponents} />, so custom MDX components render
|
|
19
|
+
* here exactly as they do on /print and chapter routes.
|
|
20
|
+
*/
|
|
21
|
+
import { getCollection, render } from 'astro:content';
|
|
22
|
+
import Base from '../../layouts/Base.astro';
|
|
23
|
+
import mdxComponents from 'virtual:book-scaffold/mdx-components';
|
|
24
|
+
|
|
25
|
+
export async function getStaticPaths() {
|
|
26
|
+
const entries = await getCollection('frontmatter');
|
|
27
|
+
return entries.map((entry) => ({
|
|
28
|
+
params: { slug: entry.id },
|
|
29
|
+
props: { entry },
|
|
30
|
+
}));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const { entry } = Astro.props;
|
|
34
|
+
const { Content } = await render(entry);
|
|
35
|
+
|
|
36
|
+
// The schema is consumer-defined; we read defensively to avoid crashes
|
|
37
|
+
// if the consumer skipped title/description fields. The frontmatterCollection
|
|
38
|
+
// helper documents the recommended shape.
|
|
39
|
+
const data = entry.data as Record<string, unknown>;
|
|
40
|
+
const title = typeof data.title === 'string' ? data.title : entry.id;
|
|
41
|
+
const description = typeof data.description === 'string' ? data.description : undefined;
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
<Base title={title} description={description ?? ''}>
|
|
45
|
+
<article class="prose frontmatter-page">
|
|
46
|
+
<Content components={mdxComponents} />
|
|
47
|
+
</article>
|
|
48
|
+
</Base>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Recipe 12 — Where to file issues (consumer-driven evolution)
|
|
2
|
+
|
|
3
|
+
This toolkit grows through cross-consumer dogfooding. Each new book project you stand up — academic curriculum, AI-CLI comparison, course notes, research portfolio, or something new — is both content work *and* a structured test of the scaffold's abstraction.
|
|
4
|
+
|
|
5
|
+
## When to file an issue
|
|
6
|
+
|
|
7
|
+
File against [`brandon-behring/book-scaffold-astro/issues`](https://github.com/brandon-behring/book-scaffold-astro/issues) when:
|
|
8
|
+
|
|
9
|
+
- The scaffold's current schemas don't fit your book's content shape (e.g. course notes needing freeform `tags` instead of the `tools_compared` enum).
|
|
10
|
+
- An auto-injected route conflicts with your book's URL structure (e.g. multi-book corpus that routes via `[book]/[slug]/`).
|
|
11
|
+
- A scaffold-injected route can't render your custom MDX components (e.g. you have `<AnkiCard>` that needs to appear on `/print`).
|
|
12
|
+
- A CLI subcommand crashes or behaves unexpectedly (e.g. `validate` reports zero chapters).
|
|
13
|
+
- A scaffold component you rebuilt has an exact equivalent already shipped (waste signal — file as `docs: missing in LATEX_TO_MDX_MAPPING.md`).
|
|
14
|
+
- An API decision blocks one of your downstream projects.
|
|
15
|
+
|
|
16
|
+
## Issue shape
|
|
17
|
+
|
|
18
|
+
Mirror the pattern used by issues [#1–#14](https://github.com/brandon-behring/book-scaffold-astro/issues?q=is%3Aissue+sort%3Acreated-desc):
|
|
19
|
+
|
|
20
|
+
```markdown
|
|
21
|
+
## Problem
|
|
22
|
+
<observed behavior + repro steps + which consumer surfaced it>
|
|
23
|
+
|
|
24
|
+
## Evidence
|
|
25
|
+
<command output, file paths, version pin (`npm view @brandon_m_behring/book-scaffold-astro version`)>
|
|
26
|
+
|
|
27
|
+
## Suggested fix
|
|
28
|
+
<one or more concrete options; trade-offs noted>
|
|
29
|
+
|
|
30
|
+
## Acceptance criteria
|
|
31
|
+
<bulleted checklist a reviewer can verify>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Label with `bug` / `enhancement` / `documentation`. Reference the consumer repo + line where the friction was hit.
|
|
35
|
+
|
|
36
|
+
## Why this matters (the loop)
|
|
37
|
+
|
|
38
|
+
Each batch of cross-consumer issues drives a minor toolkit release:
|
|
39
|
+
|
|
40
|
+
- **v3.0–v3.2** absorbed Phase B/C/D feedback from `post_transformers` + `book-template-astro`.
|
|
41
|
+
- **v3.3.0** closed 5 issues surfaced from the DLAI knowledge-graphs-rag pilot (course-notes profile + defineMdxComponents + per-route override + LaTeX migration doc).
|
|
42
|
+
- **v3.4.0** closed 8 more (preset vocabulary + propagation + frontmatter helper + validate root fix + CI hygiene + docs).
|
|
43
|
+
- **v3.5.0** (future) is expected to add the `research-portfolio` preset per issue #6 once cross-repo coordination with `prompt-injection-portfolio` is ready.
|
|
44
|
+
|
|
45
|
+
Profile-by-profile growth is the explicit strategy: the toolkit gets a new profile when a real consumer needs one, not before.
|
|
46
|
+
|
|
47
|
+
## What NOT to file
|
|
48
|
+
|
|
49
|
+
- Bug reports from external users of a single book — file those against the book's repo, not the scaffold's.
|
|
50
|
+
- Style preferences that already have an escape hatch (e.g. `extraStyles` array, consumer-side `<style>` blocks).
|
|
51
|
+
- Speculative features ("we might one day want X"). Wait for the second consumer to actually need it.
|
|
52
|
+
|
|
53
|
+
## Where to find prior decisions
|
|
54
|
+
|
|
55
|
+
- [`CHANGELOG.md`](../../CHANGELOG.md) — release-by-release breakdown.
|
|
56
|
+
- [`PACKAGE_DESIGN.md`](../PACKAGE_DESIGN.md) §1 Q1–Q6 — original Phase A locked decisions.
|
|
57
|
+
- [`LATEX_TO_MDX_MAPPING.md`](../LATEX_TO_MDX_MAPPING.md) — 38-component reference.
|
|
58
|
+
- [Closed issues](https://github.com/brandon-behring/book-scaffold-astro/issues?q=is%3Aissue+is%3Aclosed) — many problems already have rejected-alternative discussion attached.
|
package/scripts/build-bib.mjs
CHANGED
|
@@ -33,6 +33,24 @@
|
|
|
33
33
|
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
34
34
|
import { dirname, resolve } from 'node:path';
|
|
35
35
|
import { fileURLToPath } from 'node:url';
|
|
36
|
+
|
|
37
|
+
// --help / -h: non-mutating (closes #14).
|
|
38
|
+
const USAGE = `Usage: book-scaffold build-bib
|
|
39
|
+
|
|
40
|
+
Bibliography pipeline (academic profile). Reads bibliography.bib (or
|
|
41
|
+
BOOK_BIB_PATH if set), parses via @citation-js, emits src/data/references.json.
|
|
42
|
+
|
|
43
|
+
Env:
|
|
44
|
+
BOOK_BIB_PATH Override path to .bib file (default: ./bibliography.bib).
|
|
45
|
+
|
|
46
|
+
Options:
|
|
47
|
+
--help, -h Print this message and exit (non-mutating).
|
|
48
|
+
`;
|
|
49
|
+
|
|
50
|
+
if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
51
|
+
process.stdout.write(USAGE);
|
|
52
|
+
process.exit(0);
|
|
53
|
+
}
|
|
36
54
|
import { Cite } from '@citation-js/core';
|
|
37
55
|
import '@citation-js/plugin-bibtex';
|
|
38
56
|
|
|
@@ -30,6 +30,25 @@ import { dirname, resolve, basename } from 'node:path';
|
|
|
30
30
|
import { fileURLToPath } from 'node:url';
|
|
31
31
|
import { spawnSync } from 'node:child_process';
|
|
32
32
|
|
|
33
|
+
// --help / -h: non-mutating (closes #14).
|
|
34
|
+
const USAGE = `Usage: book-scaffold build-figures
|
|
35
|
+
|
|
36
|
+
Figure pipeline. PDF -> SVG via pdftocairo (PNG fallback via pdftoppm at
|
|
37
|
+
200dpi). Walks figures/ (or BOOK_FIGURES_PATH), emits to public/figures/.
|
|
38
|
+
Graceful-skip if pdftocairo / pdftoppm not on PATH.
|
|
39
|
+
|
|
40
|
+
Env:
|
|
41
|
+
BOOK_FIGURES_PATH Override figures source (default: figures/).
|
|
42
|
+
|
|
43
|
+
Options:
|
|
44
|
+
--help, -h Print this message and exit (non-mutating).
|
|
45
|
+
`;
|
|
46
|
+
|
|
47
|
+
if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
48
|
+
process.stdout.write(USAGE);
|
|
49
|
+
process.exit(0);
|
|
50
|
+
}
|
|
51
|
+
|
|
33
52
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
34
53
|
const PROJECT_ROOT = process.cwd();
|
|
35
54
|
|
package/scripts/build-labels.mjs
CHANGED
|
@@ -36,6 +36,26 @@
|
|
|
36
36
|
import { readFile, writeFile, mkdir, readdir } from 'node:fs/promises';
|
|
37
37
|
import { resolve, join, basename, dirname } from 'node:path';
|
|
38
38
|
|
|
39
|
+
// --help / -h: non-mutating (closes #14).
|
|
40
|
+
const USAGE = `Usage: book-scaffold build-labels
|
|
41
|
+
|
|
42
|
+
Emit src/data/labels.json for <XRef> resolution. Walks chapter MDX files,
|
|
43
|
+
extracts labelable components (Theorem, Figure, ...), assigns display strings
|
|
44
|
+
like "Theorem 4.2" matching LaTeX \\cref.
|
|
45
|
+
|
|
46
|
+
Env:
|
|
47
|
+
BOOK_CHAPTERS_DIR Override chapters dir (default: src/content/chapters).
|
|
48
|
+
BOOK_LABELS_OUT Override output path (default: src/data/labels.json).
|
|
49
|
+
|
|
50
|
+
Options:
|
|
51
|
+
--help, -h Print this message and exit (non-mutating).
|
|
52
|
+
`;
|
|
53
|
+
|
|
54
|
+
if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
55
|
+
process.stdout.write(USAGE);
|
|
56
|
+
process.exit(0);
|
|
57
|
+
}
|
|
58
|
+
|
|
39
59
|
const CHAPTERS_DIR = process.env.BOOK_CHAPTERS_DIR ?? 'src/content/chapters';
|
|
40
60
|
const OUTPUT_PATH = process.env.BOOK_LABELS_OUT ?? 'src/data/labels.json';
|
|
41
61
|
|
|
@@ -35,6 +35,25 @@ import { dirname, resolve, basename } from 'node:path';
|
|
|
35
35
|
import { fileURLToPath } from 'node:url';
|
|
36
36
|
import { spawnSync } from 'node:child_process';
|
|
37
37
|
|
|
38
|
+
// --help / -h: non-mutating (closes #14).
|
|
39
|
+
const USAGE = `Usage: book-scaffold render-notebooks
|
|
40
|
+
|
|
41
|
+
Notebook pipeline. .ipynb -> standalone HTML via Jupyter nbconvert (--basic).
|
|
42
|
+
Walks notebooks/ (or BOOK_NOTEBOOKS_PATH), emits to public/notebooks/.
|
|
43
|
+
Graceful-skip if uv not on PATH.
|
|
44
|
+
|
|
45
|
+
Env:
|
|
46
|
+
BOOK_NOTEBOOKS_PATH Override notebooks source (default: notebooks/).
|
|
47
|
+
|
|
48
|
+
Options:
|
|
49
|
+
--help, -h Print this message and exit (non-mutating).
|
|
50
|
+
`;
|
|
51
|
+
|
|
52
|
+
if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
53
|
+
process.stdout.write(USAGE);
|
|
54
|
+
process.exit(0);
|
|
55
|
+
}
|
|
56
|
+
|
|
38
57
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
39
58
|
const PROJECT_ROOT = process.cwd();
|
|
40
59
|
|
package/scripts/validate.mjs
CHANGED
|
@@ -7,56 +7,70 @@
|
|
|
7
7
|
* book so it's pre-commit-hook friendly.
|
|
8
8
|
*
|
|
9
9
|
* Checks performed (per Q14 in the v2.0 plan):
|
|
10
|
-
*
|
|
11
10
|
* 1. <Cite key="..." /> — key exists in src/data/references.json.
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* typos ship to readers as "[?label]" placeholders.
|
|
18
|
-
*
|
|
19
|
-
* 3. <Figure src="/path/..." /> — referenced file exists under
|
|
20
|
-
* public/. Figure.astro renders a broken-image icon otherwise.
|
|
21
|
-
*
|
|
22
|
-
* 4. Internal markdown links [text](/foo) — target resolves to a
|
|
23
|
-
* known chapter slug or a known top-level route. External (http*)
|
|
24
|
-
* links are not checked (would need network IO).
|
|
11
|
+
* 2. <XRef id="..." /> — id exists in src/data/labels.json.
|
|
12
|
+
* 3. <Figure src="/path/..." /> — file exists under public/.
|
|
13
|
+
* 4. Internal markdown links [text](/foo) — target resolves.
|
|
14
|
+
* 5. <CodeRef path="..." line={N} /> — when BOOK_REPO_ROOT set,
|
|
15
|
+
* path exists + line in bounds.
|
|
25
16
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* isn't set (the scaffold default; only meaningful for academic
|
|
30
|
-
* books that paired with an experiments/ subtree).
|
|
31
|
-
*
|
|
32
|
-
* What this DOESN'T do (and why):
|
|
33
|
-
* - frontmatter Zod validation — already done by astro build's
|
|
34
|
-
* content-collection sync.
|
|
35
|
-
* - MDX renders — same; astro build will fail.
|
|
36
|
-
* - KaTeX strict-mode — covered by rehype-katex when academic
|
|
37
|
-
* profile is active; undefined macros become build errors.
|
|
17
|
+
* Run from the consumer's project root. Closes #8 (was resolving paths
|
|
18
|
+
* from the package's own directory inside node_modules — false negatives
|
|
19
|
+
* across all reference consumers).
|
|
38
20
|
*
|
|
39
21
|
* Usage:
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* Exit code = total failure count (0 = pass, ≥1 = errors).
|
|
22
|
+
* book-scaffold validate
|
|
23
|
+
* book-scaffold validate --preset academic
|
|
24
|
+
* BOOK_REPO_ROOT=/abs/path npx book-scaffold validate
|
|
44
25
|
*
|
|
45
|
-
*
|
|
46
|
-
* - package.json scripts: "validate": "node scripts/validate.mjs"
|
|
47
|
-
* - pre-commit hook: .pre-commit-config.yaml
|
|
48
|
-
* - CI build pipeline: run before `astro build`
|
|
26
|
+
* Exit code = total failure count (0 = pass, >=1 = errors).
|
|
49
27
|
*/
|
|
50
28
|
import { readFile, access } from 'node:fs/promises';
|
|
51
29
|
import { glob } from 'node:fs/promises';
|
|
52
30
|
import { resolve, dirname, join } from 'node:path';
|
|
53
|
-
import { fileURLToPath } from 'node:url';
|
|
54
31
|
|
|
55
|
-
|
|
32
|
+
// --help / -h: non-mutating (closes #14).
|
|
33
|
+
const USAGE = `Usage: book-scaffold validate [--preset <name>]
|
|
34
|
+
|
|
35
|
+
Pre-flight content validator. Checks Cite keys, XRef ids, Figure srcs,
|
|
36
|
+
internal markdown links, and (when BOOK_REPO_ROOT is set) CodeRef paths.
|
|
37
|
+
|
|
38
|
+
Options:
|
|
39
|
+
--preset <name> academic | tools | minimal | course-notes
|
|
40
|
+
(overrides BOOK_PRESET / BOOK_PROFILE env)
|
|
41
|
+
--help, -h Print this message and exit (non-mutating).
|
|
42
|
+
|
|
43
|
+
Env:
|
|
44
|
+
BOOK_PRESET Preset name (preferred over BOOK_PROFILE).
|
|
45
|
+
BOOK_PROFILE Backward-compat alias for BOOK_PRESET.
|
|
46
|
+
BOOK_REPO_ROOT Absolute path to a sibling code repo for CodeRef checks.
|
|
47
|
+
|
|
48
|
+
Exit code = total failure count.
|
|
49
|
+
`;
|
|
50
|
+
|
|
51
|
+
if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
52
|
+
process.stdout.write(USAGE);
|
|
53
|
+
process.exit(0);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// --preset <name> CLI flag (closes #9 — single source of truth across
|
|
57
|
+
// defineBookConfig + validate).
|
|
58
|
+
const argv = process.argv.slice(2);
|
|
59
|
+
const presetFlagIdx = argv.findIndex((a) => a === '--preset');
|
|
60
|
+
const presetFromFlag = presetFlagIdx >= 0 ? argv[presetFlagIdx + 1] : undefined;
|
|
61
|
+
|
|
62
|
+
// v3.4.0: ROOT is the consumer's CWD, not the package's own dir.
|
|
63
|
+
// Resolves issue #8 — three reference consumers reported "0 chapter(s) checked"
|
|
64
|
+
// because ROOT was the package directory inside node_modules.
|
|
65
|
+
const ROOT = process.cwd();
|
|
56
66
|
const CHAPTERS_DIR = resolve(ROOT, 'src/content/chapters');
|
|
57
67
|
const PUBLIC_DIR = resolve(ROOT, 'public');
|
|
58
68
|
const DATA_DIR = resolve(ROOT, 'src/data');
|
|
59
|
-
|
|
69
|
+
|
|
70
|
+
// Preset resolution: --preset flag > BOOK_PRESET env > BOOK_PROFILE env > 'minimal'.
|
|
71
|
+
const PRESET = presetFromFlag ?? process.env.BOOK_PRESET ?? process.env.BOOK_PROFILE ?? 'minimal';
|
|
72
|
+
// Alias kept for downstream message text only; the resolution above is canonical.
|
|
73
|
+
const PROFILE = PRESET;
|
|
60
74
|
const REPO_ROOT = process.env.BOOK_REPO_ROOT ?? null;
|
|
61
75
|
|
|
62
76
|
const errors = [];
|