@brandon_m_behring/book-scaffold-astro 3.2.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.mjs CHANGED
@@ -118,9 +118,232 @@ var init_katex_macros = __esm({
118
118
  import mdx from "@astrojs/mdx";
119
119
  import preact from "@astrojs/preact";
120
120
 
121
+ // src/profile-kit.ts
122
+ function defineProfile(p) {
123
+ return p;
124
+ }
125
+
126
+ // src/schemas.ts
127
+ import { z } from "astro/zod";
128
+ var toolSlugs = [
129
+ "claude-code",
130
+ "gemini-cli",
131
+ "codex-cli",
132
+ "cross-tool"
133
+ ];
134
+ var volatilityLevels = [
135
+ "stable-principle",
136
+ "architectural-pattern",
137
+ "feature-surface"
138
+ ];
139
+ var sourceTiers = [
140
+ "T1-official",
141
+ "T2-release-notes",
142
+ "T3-practitioner",
143
+ "T4-conjecture"
144
+ ];
145
+ var changeKinds = ["added", "removed", "changed", "deprecated"];
146
+ var patternCategories = [
147
+ "safety",
148
+ "scale",
149
+ "context",
150
+ "interaction",
151
+ "extension",
152
+ "other"
153
+ ];
154
+ var academicParts = [
155
+ "foundations",
156
+ "ssm-core",
157
+ "beyond-ssm",
158
+ "integration",
159
+ "synthesis"
160
+ ];
161
+ var chapterStatus = [
162
+ "implemented",
163
+ "chapter_only",
164
+ "reading_only",
165
+ "prose_only",
166
+ "code_only",
167
+ "scaffolded",
168
+ "planned"
169
+ ];
170
+ var academicChapterSchema = z.object({
171
+ week: z.number().int().min(1).max(99),
172
+ part: z.enum(academicParts),
173
+ title: z.string().min(1),
174
+ status: z.enum(chapterStatus),
175
+ roadmap_lines: z.tuple([z.number().int(), z.number().int()]).optional(),
176
+ code_path: z.string().optional(),
177
+ tests_path: z.string().optional(),
178
+ notebook_path: z.string().optional(),
179
+ description: z.string().optional(),
180
+ draft: z.boolean().default(false)
181
+ });
182
+ var toolsChapterSchema = z.object({
183
+ title: z.string().min(1),
184
+ part: z.number().int().min(0).max(10),
185
+ chapter: z.number().int().min(0).max(99),
186
+ volatility: z.enum(volatilityLevels),
187
+ tools_compared: z.array(z.enum(toolSlugs)).min(1),
188
+ last_verified: z.date(),
189
+ sources: z.array(z.string()).default([]),
190
+ description: z.string().optional(),
191
+ draft: z.boolean().default(false),
192
+ updated: z.date().optional()
193
+ });
194
+ var minimalChapterSchema = toolsChapterSchema;
195
+ var courseNotesChapterSchema = z.object({
196
+ // Identity
197
+ title: z.string().min(1),
198
+ chapter: z.number().int().min(0).max(99),
199
+ part: z.number().int().min(0).max(20).default(1),
200
+ description: z.string().optional(),
201
+ // Source attribution
202
+ course: z.string().optional(),
203
+ instructor: z.string().optional(),
204
+ source_url: z.string().url().optional(),
205
+ // Pedagogy
206
+ learning_outcomes: z.array(
207
+ z.object({
208
+ id: z.string(),
209
+ verb: z.string(),
210
+ text: z.string()
211
+ })
212
+ ).default([]),
213
+ tags: z.array(z.string()).default([]),
214
+ // Provenance + status (shared shape with tools profile)
215
+ last_verified: z.date(),
216
+ volatility: z.enum(volatilityLevels).default("architectural-pattern"),
217
+ sources: z.array(z.string()).default([]),
218
+ draft: z.boolean().default(false)
219
+ });
220
+ var sourcesSchema = z.object({
221
+ url: z.string().url(),
222
+ title: z.string().min(1),
223
+ author: z.string().optional(),
224
+ publish_date: z.date().optional(),
225
+ captured_at: z.date(),
226
+ content_hash: z.string().regex(/^sha256:[a-f0-9]+$/).optional(),
227
+ tier: z.enum(sourceTiers),
228
+ tool: z.enum(toolSlugs),
229
+ perma_cc: z.string().url().nullable().optional(),
230
+ local_cache: z.string().nullable().optional()
231
+ });
232
+ var changelogSchema = z.object({
233
+ tool: z.enum(toolSlugs),
234
+ versions: z.array(
235
+ z.object({
236
+ version: z.string().min(1),
237
+ date: z.date(),
238
+ changes: z.array(
239
+ z.object({
240
+ pattern: z.string(),
241
+ kind: z.enum(changeKinds),
242
+ note: z.string().min(1),
243
+ source_key: z.string().optional()
244
+ })
245
+ ).default([])
246
+ })
247
+ ).default([])
248
+ });
249
+ var patternsSchema = z.object({
250
+ name: z.string().min(1),
251
+ description: z.string().optional(),
252
+ category: z.enum(patternCategories).optional(),
253
+ convergence_date: z.date().nullable().optional()
254
+ });
255
+
256
+ // src/profiles/academic.ts
257
+ var academicProfile = defineProfile({
258
+ name: "academic",
259
+ schema: academicChapterSchema,
260
+ routes: {
261
+ references: true,
262
+ search: true,
263
+ print: true,
264
+ chapters: false,
265
+ // academic consumers ship their own week-based /chapters listing
266
+ convergence: false,
267
+ // tools-profile-specific
268
+ frontmatter: false
269
+ // opt-in per book; see #7
270
+ },
271
+ styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css"],
272
+ katex: true
273
+ });
274
+
275
+ // src/profiles/tools.ts
276
+ var toolsProfile = defineProfile({
277
+ name: "tools",
278
+ schema: toolsChapterSchema,
279
+ routes: {
280
+ references: true,
281
+ search: true,
282
+ print: true,
283
+ chapters: true,
284
+ // tools profile ships a flat chapter index
285
+ convergence: true,
286
+ // tools profile ships convergence dashboard
287
+ frontmatter: false
288
+ // opt-in per book; see #7
289
+ },
290
+ styles: [
291
+ "tokens.css",
292
+ "layout.css",
293
+ "callouts.css",
294
+ "chapter.css",
295
+ "typography.css",
296
+ "print.css",
297
+ "convergence.css",
298
+ "tool-filter.css"
299
+ ]
300
+ });
301
+
302
+ // src/profiles/minimal.ts
303
+ var minimalProfile = defineProfile({
304
+ name: "minimal",
305
+ schema: minimalChapterSchema,
306
+ routes: {
307
+ references: true,
308
+ search: true,
309
+ print: true,
310
+ chapters: false,
311
+ convergence: false,
312
+ frontmatter: false
313
+ // opt-in per book; see #7
314
+ },
315
+ styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css"]
316
+ });
317
+
318
+ // src/profiles/course-notes.ts
319
+ var courseNotesProfile = defineProfile({
320
+ name: "course-notes",
321
+ schema: courseNotesChapterSchema,
322
+ routes: {
323
+ references: true,
324
+ search: true,
325
+ print: true,
326
+ chapters: false,
327
+ // multi-book consumers route via [book]/[slug] themselves
328
+ convergence: false,
329
+ frontmatter: false
330
+ // opt-in per book; see #7
331
+ },
332
+ styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css"]
333
+ });
334
+
335
+ // src/profiles/index.ts
336
+ var PROFILES = {
337
+ academic: academicProfile,
338
+ tools: toolsProfile,
339
+ minimal: minimalProfile,
340
+ "course-notes": courseNotesProfile
341
+ };
342
+ var BOOK_PROFILES = Object.keys(PROFILES);
343
+
121
344
  // src/types.ts
122
345
  import { existsSync, readFileSync } from "fs";
123
- var BOOK_PROFILES = ["academic", "tools", "minimal"];
346
+ var BOOK_PRESETS = BOOK_PROFILES;
124
347
  var BookConfigError = class extends Error {
125
348
  constructor(message) {
126
349
  super(message);
@@ -145,74 +368,131 @@ function readEnvFile(path = ".env") {
145
368
  return {};
146
369
  }
147
370
  }
148
- function resolveProfile(explicit) {
149
- let candidate = explicit ?? process.env.BOOK_PROFILE;
371
+ function resolvePreset(explicitPreset, explicitProfile) {
372
+ let candidate = explicitPreset ?? explicitProfile ?? process.env.BOOK_PRESET ?? process.env.BOOK_PROFILE;
150
373
  let source = "default";
151
- if (explicit) source = "param";
152
- 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";
153
376
  if (!candidate) {
154
- const fromFile = readEnvFile().BOOK_PROFILE;
377
+ const env = readEnvFile();
378
+ const fromFile = env.BOOK_PRESET ?? env.BOOK_PROFILE;
155
379
  if (fromFile) {
156
380
  candidate = fromFile;
157
381
  source = "dotenv";
158
382
  }
159
383
  }
160
384
  candidate = candidate ?? "minimal";
161
- if (!BOOK_PROFILES.includes(candidate)) {
385
+ if (!BOOK_PRESETS.includes(candidate)) {
162
386
  throw new BookConfigError(
163
- `profile must be one of ${BOOK_PROFILES.join(" | ")} (got ${JSON.stringify(candidate)})`
387
+ `preset must be one of ${BOOK_PRESETS.join(" | ")} (got ${JSON.stringify(candidate)})`
164
388
  );
165
389
  }
166
390
  if (source === "default") {
167
- console.warn("book-scaffold-astro: BOOK_PROFILE not set; falling back to 'minimal'.");
391
+ console.warn("book-scaffold-astro: BOOK_PRESET not set; falling back to 'minimal'.");
168
392
  }
169
393
  return candidate;
170
394
  }
395
+ function resolveProfile(explicit) {
396
+ return resolvePreset(void 0, explicit);
397
+ }
171
398
 
172
399
  // src/integration.ts
173
400
  import { fileURLToPath } from "url";
174
- var PACKAGE_NAME = "@brandon_m_behring/book-scaffold-astro";
175
- var ALWAYS_ON_STYLES = [
176
- "tokens.css",
177
- "layout.css",
178
- "callouts.css",
179
- "chapter.css",
180
- "typography.css",
181
- "print.css"
182
- ];
183
- var TOOLS_ONLY_STYLES = ["convergence.css", "tool-filter.css"];
184
- var DEFAULT_ROUTES_ALL = [
185
- { pattern: "/references", file: "references.astro" },
186
- { pattern: "/search", file: "search.astro" },
187
- { pattern: "/print", file: "print.astro" }
188
- ];
189
- var DEFAULT_ROUTES_TOOLS = [
190
- { pattern: "/chapters", file: "chapters.astro" },
191
- { pattern: "/convergence", file: "convergence.astro" }
401
+
402
+ // src/mdx-components-resolver.ts
403
+ import { existsSync as existsSync2 } from "fs";
404
+ import { resolve } from "path";
405
+ var CANDIDATE_FILES = [
406
+ "src/mdx-components.ts",
407
+ "src/mdx-components.js",
408
+ "src/mdx-components.mjs"
192
409
  ];
410
+ var VIRTUAL_ID = "virtual:book-scaffold/mdx-components";
411
+ var RESOLVED_ID = "\0" + VIRTUAL_ID;
412
+ function resolveMdxComponentsPath(consumerRoot, explicit) {
413
+ if (explicit) {
414
+ const p = resolve(consumerRoot, explicit);
415
+ return existsSync2(p) ? p : null;
416
+ }
417
+ for (const candidate of CANDIDATE_FILES) {
418
+ const p = resolve(consumerRoot, candidate);
419
+ if (existsSync2(p)) return p;
420
+ }
421
+ return null;
422
+ }
423
+ function makeMdxComponentsVitePlugin(consumerPath) {
424
+ return {
425
+ name: "book-scaffold:mdx-components",
426
+ enforce: "pre",
427
+ resolveId(id) {
428
+ if (id === VIRTUAL_ID) return RESOLVED_ID;
429
+ return null;
430
+ },
431
+ load(id) {
432
+ if (id !== RESOLVED_ID) return null;
433
+ if (consumerPath === null) {
434
+ return "export default {};";
435
+ }
436
+ return `export { default } from ${JSON.stringify(consumerPath)};`;
437
+ }
438
+ };
439
+ }
440
+ function defineMdxComponents(components) {
441
+ return components;
442
+ }
443
+
444
+ // src/integration.ts
445
+ var PACKAGE_NAME = "@brandon_m_behring/book-scaffold-astro";
446
+ var ROUTE_REGISTRY = {
447
+ references: { pattern: "/references", file: "references.astro" },
448
+ search: { pattern: "/search", file: "search.astro" },
449
+ print: { pattern: "/print", file: "print.astro" },
450
+ chapters: { pattern: "/chapters", file: "chapters.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" }
456
+ };
193
457
  function resolvePage(file) {
194
458
  return fileURLToPath(new URL(`../pages/${file}`, import.meta.url));
195
459
  }
196
460
  function bookScaffoldIntegration(opts) {
197
- const { profile, extraStyles = [] } = opts;
461
+ const { profile, routes: userOverrides = {}, extraStyles = [], mdxComponentsModule } = opts;
462
+ const def = PROFILES[profile];
463
+ const enabledRoutes = { ...def.routes, ...userOverrides };
198
464
  return {
199
465
  name: "book-scaffold-astro",
200
466
  hooks: {
201
- "astro:config:setup": ({ injectScript, injectRoute }) => {
202
- const styles = profile === "tools" ? [...ALWAYS_ON_STYLES, ...TOOLS_ONLY_STYLES, ...extraStyles] : [...ALWAYS_ON_STYLES, ...extraStyles];
467
+ "astro:config:setup": ({ injectScript, injectRoute, updateConfig, config }) => {
468
+ const styles = [...def.styles, ...extraStyles];
203
469
  for (const sheet of styles) {
204
470
  injectScript("page-ssr", `import '${PACKAGE_NAME}/styles/${sheet}';`);
205
471
  }
206
- if (profile === "academic") {
472
+ if (def.katex) {
207
473
  injectScript("page-ssr", "import 'katex/dist/katex.min.css';");
208
474
  }
209
- const routes = profile === "tools" ? [...DEFAULT_ROUTES_ALL, ...DEFAULT_ROUTES_TOOLS] : [...DEFAULT_ROUTES_ALL];
210
- for (const route of routes) {
475
+ for (const [name, on] of Object.entries(enabledRoutes)) {
476
+ if (!on) continue;
477
+ const route = ROUTE_REGISTRY[name];
478
+ if (!route) continue;
211
479
  injectRoute({
212
480
  pattern: route.pattern,
213
481
  entrypoint: resolvePage(route.file)
214
482
  });
215
483
  }
484
+ const consumerRoot = fileURLToPath(config.root);
485
+ const resolvedMdxPath = resolveMdxComponentsPath(consumerRoot, mdxComponentsModule);
486
+ const presetLiteral = JSON.stringify(profile);
487
+ updateConfig({
488
+ vite: {
489
+ plugins: [makeMdxComponentsVitePlugin(resolvedMdxPath)],
490
+ define: {
491
+ "import.meta.env.BOOK_PRESET": presetLiteral,
492
+ "import.meta.env.BOOK_PROFILE": presetLiteral
493
+ }
494
+ }
495
+ });
216
496
  }
217
497
  }
218
498
  };
@@ -220,7 +500,7 @@ function bookScaffoldIntegration(opts) {
220
500
 
221
501
  // src/config.ts
222
502
  async function defineBookConfig(opts) {
223
- const profile = resolveProfile(opts.profile);
503
+ const profile = resolvePreset(opts.preset, opts.profile);
224
504
  const remarkPlugins = [];
225
505
  const rehypePlugins = [];
226
506
  if (profile === "academic") {
@@ -249,7 +529,14 @@ async function defineBookConfig(opts) {
249
529
  const integrations = [
250
530
  mdx(),
251
531
  preact(),
252
- bookScaffoldIntegration({ profile, extraStyles: opts.extraStyles }),
532
+ bookScaffoldIntegration({
533
+ profile,
534
+ routes: opts.routes,
535
+ // v3.3.0 — per-route override (issue #3)
536
+ mdxComponentsModule: opts.mdxComponentsModule,
537
+ // v3.3.0 — explicit mdx-components path (issue #2)
538
+ extraStyles: opts.extraStyles
539
+ }),
253
540
  ...opts.extraIntegrations ?? []
254
541
  ];
255
542
  const userMarkdown = opts.markdown ?? {};
@@ -266,13 +553,22 @@ async function defineBookConfig(opts) {
266
553
  ...userMarkdown
267
554
  };
268
555
  const {
556
+ preset: _preset,
557
+ // v3.4.0
269
558
  profile: _profile,
559
+ routes: _routes,
560
+ // v3.3.0
561
+ mdxComponentsModule: _mdxComponentsModule,
562
+ // v3.3.0
270
563
  extraIntegrations: _extraIntegrations,
271
564
  extraStyles: _extraStyles,
272
565
  markdown: _markdown,
273
566
  ...rest
274
567
  } = opts;
568
+ void _preset;
275
569
  void _profile;
570
+ void _routes;
571
+ void _mdxComponentsModule;
276
572
  void _extraIntegrations;
277
573
  void _extraStyles;
278
574
  void _markdown;
@@ -293,110 +589,41 @@ async function defineBookConfig(opts) {
293
589
  return config;
294
590
  }
295
591
 
296
- // src/schemas.ts
297
- import { z } from "astro/zod";
298
- var toolSlugs = [
299
- "claude-code",
300
- "gemini-cli",
301
- "codex-cli",
302
- "cross-tool"
303
- ];
304
- var volatilityLevels = [
305
- "stable-principle",
306
- "architectural-pattern",
307
- "feature-surface"
308
- ];
309
- var sourceTiers = [
310
- "T1-official",
311
- "T2-release-notes",
312
- "T3-practitioner",
313
- "T4-conjecture"
314
- ];
315
- var changeKinds = ["added", "removed", "changed", "deprecated"];
316
- var patternCategories = [
317
- "safety",
318
- "scale",
319
- "context",
320
- "interaction",
321
- "extension",
322
- "other"
323
- ];
324
- var academicParts = [
325
- "foundations",
326
- "ssm-core",
327
- "beyond-ssm",
328
- "integration",
329
- "synthesis"
330
- ];
331
- var chapterStatus = [
332
- "implemented",
333
- "chapter_only",
334
- "reading_only",
335
- "prose_only",
336
- "code_only",
337
- "scaffolded",
338
- "planned"
339
- ];
340
- var academicChapterSchema = z.object({
341
- week: z.number().int().min(1).max(99),
342
- part: z.enum(academicParts),
343
- title: z.string().min(1),
344
- status: z.enum(chapterStatus),
345
- roadmap_lines: z.tuple([z.number().int(), z.number().int()]).optional(),
346
- code_path: z.string().optional(),
347
- tests_path: z.string().optional(),
348
- notebook_path: z.string().optional(),
349
- description: z.string().optional(),
350
- draft: z.boolean().default(false)
351
- });
352
- var toolsChapterSchema = z.object({
353
- title: z.string().min(1),
354
- part: z.number().int().min(0).max(10),
355
- chapter: z.number().int().min(0).max(99),
356
- volatility: z.enum(volatilityLevels),
357
- tools_compared: z.array(z.enum(toolSlugs)).min(1),
358
- last_verified: z.date(),
359
- sources: z.array(z.string()).default([]),
360
- description: z.string().optional(),
361
- draft: z.boolean().default(false),
362
- updated: z.date().optional()
363
- });
364
- var sourcesSchema = z.object({
365
- url: z.string().url(),
366
- title: z.string().min(1),
367
- author: z.string().optional(),
368
- publish_date: z.date().optional(),
369
- captured_at: z.date(),
370
- content_hash: z.string().regex(/^sha256:[a-f0-9]+$/).optional(),
371
- tier: z.enum(sourceTiers),
372
- tool: z.enum(toolSlugs),
373
- perma_cc: z.string().url().nullable().optional(),
374
- local_cache: z.string().nullable().optional()
375
- });
376
- var changelogSchema = z.object({
377
- tool: z.enum(toolSlugs),
378
- versions: z.array(
379
- z.object({
380
- version: z.string().min(1),
381
- date: z.date(),
382
- changes: z.array(
383
- z.object({
384
- pattern: z.string(),
385
- kind: z.enum(changeKinds),
386
- note: z.string().min(1),
387
- source_key: z.string().optional()
388
- })
389
- ).default([])
390
- })
391
- ).default([])
392
- });
393
- var patternsSchema = z.object({
394
- name: z.string().min(1),
395
- description: z.string().optional(),
396
- category: z.enum(patternCategories).optional(),
397
- convergence_date: z.date().nullable().optional()
398
- });
592
+ // src/lib/freshness.ts
593
+ var THRESHOLDS = {
594
+ "stable-principle": 365,
595
+ "architectural-pattern": 180,
596
+ "feature-surface": 90
597
+ };
598
+ var MS_PER_DAY = 1e3 * 60 * 60 * 24;
599
+ function getFreshness(lastVerified, volatility, now = /* @__PURE__ */ new Date()) {
600
+ if (!(lastVerified instanceof Date)) return null;
601
+ const thresholdDays = THRESHOLDS[volatility];
602
+ const daysOld = Math.floor((now.getTime() - lastVerified.getTime()) / MS_PER_DAY);
603
+ const daysUntil = thresholdDays - daysOld;
604
+ let status;
605
+ if (daysOld < thresholdDays * 0.75) {
606
+ status = "fresh";
607
+ } else if (daysOld < thresholdDays) {
608
+ status = "verify-soon";
609
+ } else {
610
+ status = "stale";
611
+ }
612
+ return { status, daysOld, thresholdDays, daysUntil };
613
+ }
614
+ function freshnessLabel(f) {
615
+ if (f === null) return "Verification status unknown";
616
+ switch (f.status) {
617
+ case "fresh":
618
+ return `Fresh (${f.daysOld}d old; verify within ${f.daysUntil}d)`;
619
+ case "verify-soon":
620
+ return `Verify soon (${f.daysOld}d old; ${f.daysUntil}d until stale)`;
621
+ case "stale":
622
+ return `Stale (${f.daysOld}d old; ${Math.abs(f.daysUntil)}d past threshold)`;
623
+ }
624
+ }
399
625
  export {
626
+ BOOK_PRESETS,
400
627
  BOOK_PROFILES,
401
628
  BookConfigError,
402
629
  academicChapterSchema,
@@ -405,9 +632,16 @@ export {
405
632
  changeKinds,
406
633
  changelogSchema,
407
634
  chapterStatus,
635
+ courseNotesChapterSchema,
408
636
  defineBookConfig,
637
+ defineMdxComponents,
638
+ defineProfile,
639
+ freshnessLabel,
640
+ getFreshness,
641
+ minimalChapterSchema,
409
642
  patternCategories,
410
643
  patternsSchema,
644
+ resolvePreset,
411
645
  resolveProfile,
412
646
  sourceTiers,
413
647
  sourcesSchema,
package/dist/schemas.d.ts CHANGED
@@ -1,6 +1,40 @@
1
- import { e as BookSchemasOptions } from './types-BoCXCvBy.js';
1
+ import { defineCollection } from 'astro:content';
2
+ import { g as BookSchemasOptions } from './types-Bb97Na9S.js';
2
3
  import 'astro';
4
+ import 'astro/zod';
3
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;
4
38
  /**
5
39
  * Returns the package's default content collections. Closed shape per Q5;
6
40
  * consumer extends via object spread and Zod `.extend()` (see PACKAGE_DESIGN.md §5).
@@ -9,4 +43,4 @@ declare function defineBookSchemas(opts?: BookSchemasOptions): {
9
43
  collections: Record<string, unknown>;
10
44
  };
11
45
 
12
- export { defineBookSchemas };
46
+ export { defineBookSchemas, frontmatterCollection };