@brandon_m_behring/book-scaffold-astro 3.3.0 → 3.5.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/AICollaborationDisclosure.astro +81 -0
- package/components/BlockedByCallout.astro +82 -0
- package/components/PolicyRef.astro +46 -0
- package/components/PreReleaseBanner.astro +73 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +120 -16
- package/dist/schemas.d.ts +35 -2
- package/dist/schemas.mjs +111 -16
- package/examples/chapter-template-research-portfolio.mdx +79 -0
- package/package.json +7 -2
- package/pages/frontmatter/[...slug].astro +48 -0
- package/recipes/12-where-to-file-issues.md +58 -0
- package/recipes/13-research-portfolio-getting-started.md +179 -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
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* AICollaborationDisclosure — render a structured AI-collaboration disclosure
|
|
4
|
+
* paragraph from props.
|
|
5
|
+
*
|
|
6
|
+
* v3.5.0 (research-portfolio profile, closes #6). Generic primitive.
|
|
7
|
+
* Research portfolios universally need to disclose AI involvement (per
|
|
8
|
+
* various norms); this component centralizes the rendering so consumers
|
|
9
|
+
* don't reinvent the disclosure block per book.
|
|
10
|
+
*
|
|
11
|
+
* Usage (props form — recommended):
|
|
12
|
+
* <AICollaborationDisclosure
|
|
13
|
+
* model="Claude Opus 4.7 + Sonnet 4.6 (Anthropic)"
|
|
14
|
+
* role="research collaborator + writing collaborator"
|
|
15
|
+
* commit_attribution="Co-Authored-By: Claude <noreply@anthropic.com>"
|
|
16
|
+
* />
|
|
17
|
+
*
|
|
18
|
+
* Usage (YAML-driven — consumer loads via astro:content data collection):
|
|
19
|
+
* ---
|
|
20
|
+
* import disclosure from '../data/ai-collaboration.yaml';
|
|
21
|
+
* ---
|
|
22
|
+
* <AICollaborationDisclosure {...disclosure} />
|
|
23
|
+
*
|
|
24
|
+
* The YAML loader is consumer-side (not shipped) to avoid pulling a YAML
|
|
25
|
+
* dep into the scaffold; Astro consumers commonly use the `astro:content`
|
|
26
|
+
* file loader with `yaml()` or similar.
|
|
27
|
+
*
|
|
28
|
+
* Slots:
|
|
29
|
+
* `default` — optional extra prose to append after the model/role line
|
|
30
|
+
* (e.g. "All factual claims independently verified by …").
|
|
31
|
+
*/
|
|
32
|
+
interface Props {
|
|
33
|
+
model: string;
|
|
34
|
+
role: string;
|
|
35
|
+
commit_attribution?: string;
|
|
36
|
+
}
|
|
37
|
+
const { model, role, commit_attribution } = Astro.props;
|
|
38
|
+
---
|
|
39
|
+
<aside class="ai-collab-disclosure" role="note" aria-labelledby="ai-collab-h">
|
|
40
|
+
<h3 id="ai-collab-h">AI collaboration disclosure</h3>
|
|
41
|
+
<p>
|
|
42
|
+
This book was authored in collaboration with <strong>{model}</strong>, acting as <em>{role}</em>.
|
|
43
|
+
</p>
|
|
44
|
+
{commit_attribution && (
|
|
45
|
+
<p>
|
|
46
|
+
Commits attributed to the model carry the trailer
|
|
47
|
+
<code>{commit_attribution}</code>.
|
|
48
|
+
</p>
|
|
49
|
+
)}
|
|
50
|
+
<slot />
|
|
51
|
+
</aside>
|
|
52
|
+
|
|
53
|
+
<style>
|
|
54
|
+
.ai-collab-disclosure {
|
|
55
|
+
display: block;
|
|
56
|
+
margin: var(--space-6) 0;
|
|
57
|
+
padding: var(--space-3) var(--space-4);
|
|
58
|
+
border-left: var(--border-bar) solid var(--warm-plum);
|
|
59
|
+
background: var(--warm-plum-tint);
|
|
60
|
+
border-radius: 0 var(--radius-md) var(--radius-md) 0;
|
|
61
|
+
font-size: var(--text-sm);
|
|
62
|
+
line-height: var(--leading-normal);
|
|
63
|
+
}
|
|
64
|
+
.ai-collab-disclosure h3 {
|
|
65
|
+
margin: 0 0 var(--space-2);
|
|
66
|
+
font-size: var(--text-base);
|
|
67
|
+
color: var(--warm-plum);
|
|
68
|
+
letter-spacing: 0.02em;
|
|
69
|
+
}
|
|
70
|
+
.ai-collab-disclosure p {
|
|
71
|
+
margin: var(--space-2) 0;
|
|
72
|
+
text-indent: 0;
|
|
73
|
+
}
|
|
74
|
+
.ai-collab-disclosure code {
|
|
75
|
+
font-family: var(--font-code);
|
|
76
|
+
font-size: 0.95em;
|
|
77
|
+
background: var(--color-code-bg);
|
|
78
|
+
padding: 0.1em 0.4em;
|
|
79
|
+
border-radius: var(--radius-sm);
|
|
80
|
+
}
|
|
81
|
+
</style>
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* BlockedByCallout — declare that this chapter / section / experiment is
|
|
4
|
+
* blocked on an upstream dependency (e.g., a tool release, a paper
|
|
5
|
+
* publication, a hardware acquisition).
|
|
6
|
+
*
|
|
7
|
+
* v3.5.0 (research-portfolio profile, closes #6 + the cross-consumer
|
|
8
|
+
* dogfooding pattern). Research books often have "blocked by X" sections
|
|
9
|
+
* — explicit, dated, with optional unblock target.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* <BlockedByCallout
|
|
13
|
+
* upstream="book-scaffold-astro v3.5.0"
|
|
14
|
+
* url="https://github.com/brandon-behring/book-scaffold-astro/issues/6"
|
|
15
|
+
* reason="research-portfolio preset + 3 new components"
|
|
16
|
+
* unblockedAt="2026-05-19"
|
|
17
|
+
* >
|
|
18
|
+
* Once the preset ships, this chapter's frontmatter migrates from the
|
|
19
|
+
* hand-rolled schema to the upstream `research-portfolio` shape.
|
|
20
|
+
* </BlockedByCallout>
|
|
21
|
+
*
|
|
22
|
+
* Slot content (optional) appears under the structured fields and is the
|
|
23
|
+
* place to put migration notes / workaround prose.
|
|
24
|
+
*/
|
|
25
|
+
interface Props {
|
|
26
|
+
upstream: string;
|
|
27
|
+
reason: string;
|
|
28
|
+
url?: string;
|
|
29
|
+
unblockedAt?: string;
|
|
30
|
+
}
|
|
31
|
+
const { upstream, reason, url, unblockedAt } = Astro.props;
|
|
32
|
+
const upstreamLabel = url ? null : upstream;
|
|
33
|
+
---
|
|
34
|
+
<aside class="blocked-by-callout" role="note" aria-labelledby="blocked-by-h">
|
|
35
|
+
<h3 id="blocked-by-h">Blocked by upstream</h3>
|
|
36
|
+
<p>
|
|
37
|
+
<strong>
|
|
38
|
+
{url ? <a href={url} rel="external noopener">{upstream}</a> : upstreamLabel}
|
|
39
|
+
</strong>
|
|
40
|
+
{' — '}{reason}{unblockedAt && (<span class="unblocked-at"> (expected {unblockedAt})</span>)}.
|
|
41
|
+
</p>
|
|
42
|
+
<div class="blocked-by-body">
|
|
43
|
+
<slot />
|
|
44
|
+
</div>
|
|
45
|
+
</aside>
|
|
46
|
+
|
|
47
|
+
<style>
|
|
48
|
+
.blocked-by-callout {
|
|
49
|
+
display: block;
|
|
50
|
+
margin: var(--space-4) 0;
|
|
51
|
+
padding: var(--space-3) var(--space-4);
|
|
52
|
+
border-left: var(--border-bar) solid var(--warm-gold);
|
|
53
|
+
background: var(--warm-gold-tint);
|
|
54
|
+
border-radius: 0 var(--radius-md) var(--radius-md) 0;
|
|
55
|
+
font-size: var(--text-sm);
|
|
56
|
+
line-height: var(--leading-normal);
|
|
57
|
+
}
|
|
58
|
+
.blocked-by-callout h3 {
|
|
59
|
+
margin: 0 0 var(--space-2);
|
|
60
|
+
font-size: var(--text-base);
|
|
61
|
+
color: var(--warm-gold);
|
|
62
|
+
letter-spacing: 0.02em;
|
|
63
|
+
}
|
|
64
|
+
.blocked-by-callout p {
|
|
65
|
+
margin: var(--space-2) 0;
|
|
66
|
+
text-indent: 0;
|
|
67
|
+
}
|
|
68
|
+
.blocked-by-callout a {
|
|
69
|
+
color: var(--warm-gold);
|
|
70
|
+
text-decoration: underline;
|
|
71
|
+
}
|
|
72
|
+
.unblocked-at {
|
|
73
|
+
color: var(--color-text-muted);
|
|
74
|
+
font-style: italic;
|
|
75
|
+
}
|
|
76
|
+
.blocked-by-body :global(p:first-child) {
|
|
77
|
+
margin-top: 0;
|
|
78
|
+
}
|
|
79
|
+
.blocked-by-body :global(p:last-child) {
|
|
80
|
+
margin-bottom: 0;
|
|
81
|
+
}
|
|
82
|
+
</style>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* PolicyRef — inline link to a repo-root policy document.
|
|
4
|
+
*
|
|
5
|
+
* v3.5.0 (research-portfolio profile, closes #6). Generic: any consumer can
|
|
6
|
+
* use it to cite ETHICS.md / SECURITY.md / CODE_OF_CONDUCT.md / LICENSE /
|
|
7
|
+
* GOVERNANCE.md without rebuilding the same "link + label" pattern per book.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* <PolicyRef file="ETHICS.md" section="§1 Dual-use disclosure" label="ethics policy" />
|
|
11
|
+
* <PolicyRef file="SECURITY.md" /> // default label = filename
|
|
12
|
+
* <PolicyRef file="GOVERNANCE.md" section="Quorum" /> // auto-anchor from section
|
|
13
|
+
*
|
|
14
|
+
* The href resolves to `/<file>` by default (assuming the consumer ships the
|
|
15
|
+
* markdown at site root via public/ or an Astro page). When `section` is set,
|
|
16
|
+
* a slugified anchor `#<slug>` is appended. The label defaults to the section
|
|
17
|
+
* name (if present) else the filename.
|
|
18
|
+
*/
|
|
19
|
+
interface Props {
|
|
20
|
+
file: string;
|
|
21
|
+
section?: string;
|
|
22
|
+
label?: string;
|
|
23
|
+
href?: string; // explicit override (otherwise computed)
|
|
24
|
+
}
|
|
25
|
+
const { file, section, label, href } = Astro.props;
|
|
26
|
+
|
|
27
|
+
const slugify = (s: string) =>
|
|
28
|
+
s.toLowerCase().replace(/[§\W]+/g, '-').replace(/^-+|-+$/g, '');
|
|
29
|
+
|
|
30
|
+
const resolvedHref = href ?? `/${file}${section ? `#${slugify(section)}` : ''}`;
|
|
31
|
+
const display = label ?? (section ? `${file} ${section}` : file);
|
|
32
|
+
---
|
|
33
|
+
<a class="policy-ref" href={resolvedHref}>{display}</a>
|
|
34
|
+
|
|
35
|
+
<style>
|
|
36
|
+
.policy-ref {
|
|
37
|
+
font-family: var(--font-code);
|
|
38
|
+
font-size: 0.95em;
|
|
39
|
+
color: var(--color-link);
|
|
40
|
+
text-decoration: none;
|
|
41
|
+
border-bottom: 1px dotted var(--color-link);
|
|
42
|
+
}
|
|
43
|
+
.policy-ref:hover {
|
|
44
|
+
border-bottom-style: solid;
|
|
45
|
+
}
|
|
46
|
+
</style>
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* PreReleaseBanner — site-wide banner declaring the book's release state.
|
|
4
|
+
*
|
|
5
|
+
* v3.5.0 (research-portfolio profile, closes #6). Generic primitive; any
|
|
6
|
+
* profile can use it but research portfolios are the primary use case
|
|
7
|
+
* (long-running research books pass through alpha → beta → rc states).
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* <PreReleaseBanner state="alpha" />
|
|
11
|
+
* <PreReleaseBanner state="beta" dismissAt="v0.7.0" />
|
|
12
|
+
* <PreReleaseBanner state="rc" message="Final review pass; please open issues." />
|
|
13
|
+
* <PreReleaseBanner state="locked" />
|
|
14
|
+
*
|
|
15
|
+
* Drop inside a layout or chapter front-matter file. Common placement is the
|
|
16
|
+
* top of every page via a layout slot — see recipes/13.
|
|
17
|
+
*/
|
|
18
|
+
interface Props {
|
|
19
|
+
state: 'alpha' | 'beta' | 'rc' | 'locked';
|
|
20
|
+
dismissAt?: string;
|
|
21
|
+
message?: string;
|
|
22
|
+
}
|
|
23
|
+
const { state, dismissAt, message } = Astro.props;
|
|
24
|
+
|
|
25
|
+
const DEFAULTS: Record<Props['state'], string> = {
|
|
26
|
+
alpha: 'This book is in alpha — expect breaking changes and partial coverage.',
|
|
27
|
+
beta: 'This book is in beta — most sections stable; minor changes possible.',
|
|
28
|
+
rc: 'Release candidate — finalizing content; substantive feedback welcome.',
|
|
29
|
+
locked: 'This release is frozen. See CHANGELOG for the next iteration.',
|
|
30
|
+
};
|
|
31
|
+
const text = message ?? DEFAULTS[state];
|
|
32
|
+
const label = `${state.toUpperCase()}${dismissAt ? ` (until ${dismissAt})` : ''}`;
|
|
33
|
+
---
|
|
34
|
+
<aside class={`pre-release-banner pre-release-banner-${state}`} role="status" aria-live="polite">
|
|
35
|
+
<strong>{label}:</strong> {text}
|
|
36
|
+
</aside>
|
|
37
|
+
|
|
38
|
+
<style>
|
|
39
|
+
.pre-release-banner {
|
|
40
|
+
display: block;
|
|
41
|
+
margin: var(--space-2) 0 var(--space-4);
|
|
42
|
+
padding: var(--space-2) var(--space-3);
|
|
43
|
+
border-radius: var(--radius-md);
|
|
44
|
+
border-left: var(--border-bar) solid;
|
|
45
|
+
font-size: var(--text-sm);
|
|
46
|
+
line-height: var(--leading-normal);
|
|
47
|
+
}
|
|
48
|
+
.pre-release-banner strong {
|
|
49
|
+
margin-right: var(--space-1);
|
|
50
|
+
font-weight: 600;
|
|
51
|
+
letter-spacing: 0.04em;
|
|
52
|
+
}
|
|
53
|
+
.pre-release-banner-alpha {
|
|
54
|
+
background: var(--warm-rose-tint);
|
|
55
|
+
border-left-color: var(--warm-rose);
|
|
56
|
+
color: var(--warm-rose);
|
|
57
|
+
}
|
|
58
|
+
.pre-release-banner-beta {
|
|
59
|
+
background: var(--warm-gold-tint);
|
|
60
|
+
border-left-color: var(--warm-gold);
|
|
61
|
+
color: var(--warm-gold);
|
|
62
|
+
}
|
|
63
|
+
.pre-release-banner-rc {
|
|
64
|
+
background: var(--warm-blue-tint);
|
|
65
|
+
border-left-color: var(--warm-blue);
|
|
66
|
+
color: var(--warm-blue);
|
|
67
|
+
}
|
|
68
|
+
.pre-release-banner-locked {
|
|
69
|
+
background: var(--warm-green-tint);
|
|
70
|
+
border-left-color: var(--warm-green);
|
|
71
|
+
color: var(--warm-green);
|
|
72
|
+
}
|
|
73
|
+
</style>
|
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, E as volatilityLevels } from './types-CzXybcNA.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 ResearchPortfolioChapter, i as RouteToggles, T as ToolsChapter, j as academicChapterSchema, k as academicParts, l as changeKinds, m as changelogSchema, n as chapterStatus, o as courseNotesChapterSchema, p as defineProfile, q as minimalChapterSchema, r as patternCategories, s as patternsSchema, t as researchPortfolioChapterSchema, u as resolvePreset, v as resolveProfile, w as sourceTiers, x as sourceTiersResearch, y as sourcesSchema, z as toolSlugs, D as toolsChapterSchema } from './types-CzXybcNA.js';
|
|
4
4
|
import 'astro/zod';
|
|
5
5
|
|
|
6
6
|
declare function defineBookConfig(opts: BookConfigOptions): Promise<AstroUserConfig>;
|
package/dist/index.mjs
CHANGED
|
@@ -192,6 +192,7 @@ var toolsChapterSchema = z.object({
|
|
|
192
192
|
updated: z.date().optional()
|
|
193
193
|
});
|
|
194
194
|
var minimalChapterSchema = toolsChapterSchema;
|
|
195
|
+
var sourceTiersResearch = ["T1", "T2", "T3", "T4"];
|
|
195
196
|
var courseNotesChapterSchema = z.object({
|
|
196
197
|
// Identity
|
|
197
198
|
title: z.string().min(1),
|
|
@@ -217,6 +218,59 @@ var courseNotesChapterSchema = z.object({
|
|
|
217
218
|
sources: z.array(z.string()).default([]),
|
|
218
219
|
draft: z.boolean().default(false)
|
|
219
220
|
});
|
|
221
|
+
var researchPortfolioChapterSchema = z.object({
|
|
222
|
+
// Identity
|
|
223
|
+
title: z.string().min(1),
|
|
224
|
+
slug: z.string().optional(),
|
|
225
|
+
// explicit slug override (otherwise filename)
|
|
226
|
+
description: z.string().optional(),
|
|
227
|
+
// Hierarchy — accept either academic-style or tools-style; all optional.
|
|
228
|
+
// The academic 'part' field is a string enum; tools 'part' is a number.
|
|
229
|
+
// Use z.union to permit either type.
|
|
230
|
+
part: z.union([z.number().int().min(0).max(20), z.string()]).optional(),
|
|
231
|
+
week: z.number().int().min(0).max(99).optional(),
|
|
232
|
+
chapter: z.number().int().min(0).max(99).optional(),
|
|
233
|
+
// Academic-style status (optional for research-portfolio — books may track
|
|
234
|
+
// chapters as 'prose_only' / 'experimental-result' / etc.).
|
|
235
|
+
status: z.enum([
|
|
236
|
+
"implemented",
|
|
237
|
+
"chapter_only",
|
|
238
|
+
"reading_only",
|
|
239
|
+
"prose_only",
|
|
240
|
+
"code_only",
|
|
241
|
+
"scaffolded",
|
|
242
|
+
"planned"
|
|
243
|
+
]).optional(),
|
|
244
|
+
// Research-portfolio specific: nature of the chapter's content.
|
|
245
|
+
// Distinct from academic's 'status' (which tracks authoring state) — this
|
|
246
|
+
// describes the EVIDENCE TYPE the chapter rests on.
|
|
247
|
+
freshness: z.enum([
|
|
248
|
+
"experimental-result",
|
|
249
|
+
// primary data the author produced
|
|
250
|
+
"literature-survey",
|
|
251
|
+
// synthesis of others' work
|
|
252
|
+
"theoretical",
|
|
253
|
+
// analytical / mathematical argument
|
|
254
|
+
"reference"
|
|
255
|
+
// canonical material (definitions, taxonomy)
|
|
256
|
+
]).optional(),
|
|
257
|
+
// Provenance (tools-style — overlap with tools/course-notes profiles).
|
|
258
|
+
volatility: z.enum(volatilityLevels).optional(),
|
|
259
|
+
tags: z.array(z.string()).default([]),
|
|
260
|
+
// freeform; replaces tools_compared
|
|
261
|
+
// Structured inline sources with T1-T4 tiers.
|
|
262
|
+
sources: z.array(
|
|
263
|
+
z.object({
|
|
264
|
+
tier: z.enum(sourceTiersResearch),
|
|
265
|
+
url: z.string().url(),
|
|
266
|
+
label: z.string().min(1)
|
|
267
|
+
})
|
|
268
|
+
).default([]),
|
|
269
|
+
// Status + dates.
|
|
270
|
+
last_verified: z.date(),
|
|
271
|
+
updated: z.date().optional(),
|
|
272
|
+
draft: z.boolean().default(false)
|
|
273
|
+
});
|
|
220
274
|
var sourcesSchema = z.object({
|
|
221
275
|
url: z.string().url(),
|
|
222
276
|
title: z.string().min(1),
|
|
@@ -263,8 +317,10 @@ var academicProfile = defineProfile({
|
|
|
263
317
|
print: true,
|
|
264
318
|
chapters: false,
|
|
265
319
|
// academic consumers ship their own week-based /chapters listing
|
|
266
|
-
convergence: false
|
|
320
|
+
convergence: false,
|
|
267
321
|
// tools-profile-specific
|
|
322
|
+
frontmatter: false
|
|
323
|
+
// opt-in per book; see #7
|
|
268
324
|
},
|
|
269
325
|
styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css"],
|
|
270
326
|
katex: true
|
|
@@ -280,8 +336,10 @@ var toolsProfile = defineProfile({
|
|
|
280
336
|
print: true,
|
|
281
337
|
chapters: true,
|
|
282
338
|
// tools profile ships a flat chapter index
|
|
283
|
-
convergence: true
|
|
339
|
+
convergence: true,
|
|
284
340
|
// tools profile ships convergence dashboard
|
|
341
|
+
frontmatter: false
|
|
342
|
+
// opt-in per book; see #7
|
|
285
343
|
},
|
|
286
344
|
styles: [
|
|
287
345
|
"tokens.css",
|
|
@@ -304,7 +362,9 @@ var minimalProfile = defineProfile({
|
|
|
304
362
|
search: true,
|
|
305
363
|
print: true,
|
|
306
364
|
chapters: false,
|
|
307
|
-
convergence: false
|
|
365
|
+
convergence: false,
|
|
366
|
+
frontmatter: false
|
|
367
|
+
// opt-in per book; see #7
|
|
308
368
|
},
|
|
309
369
|
styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css"]
|
|
310
370
|
});
|
|
@@ -319,22 +379,46 @@ var courseNotesProfile = defineProfile({
|
|
|
319
379
|
print: true,
|
|
320
380
|
chapters: false,
|
|
321
381
|
// multi-book consumers route via [book]/[slug] themselves
|
|
322
|
-
convergence: false
|
|
382
|
+
convergence: false,
|
|
383
|
+
frontmatter: false
|
|
384
|
+
// opt-in per book; see #7
|
|
323
385
|
},
|
|
324
386
|
styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css"]
|
|
325
387
|
});
|
|
326
388
|
|
|
389
|
+
// src/profiles/research-portfolio.ts
|
|
390
|
+
var researchPortfolioProfile = defineProfile({
|
|
391
|
+
name: "research-portfolio",
|
|
392
|
+
schema: researchPortfolioChapterSchema,
|
|
393
|
+
routes: {
|
|
394
|
+
references: true,
|
|
395
|
+
search: true,
|
|
396
|
+
print: true,
|
|
397
|
+
chapters: false,
|
|
398
|
+
// portfolio books ship their own landing/index
|
|
399
|
+
convergence: false,
|
|
400
|
+
// tools-profile-specific
|
|
401
|
+
frontmatter: true
|
|
402
|
+
// portfolios universally need title/disclosure/banner pages
|
|
403
|
+
},
|
|
404
|
+
styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css"],
|
|
405
|
+
katex: true
|
|
406
|
+
// math is common in research content
|
|
407
|
+
});
|
|
408
|
+
|
|
327
409
|
// src/profiles/index.ts
|
|
328
410
|
var PROFILES = {
|
|
329
411
|
academic: academicProfile,
|
|
330
412
|
tools: toolsProfile,
|
|
331
413
|
minimal: minimalProfile,
|
|
332
|
-
"course-notes": courseNotesProfile
|
|
414
|
+
"course-notes": courseNotesProfile,
|
|
415
|
+
"research-portfolio": researchPortfolioProfile
|
|
333
416
|
};
|
|
334
417
|
var BOOK_PROFILES = Object.keys(PROFILES);
|
|
335
418
|
|
|
336
419
|
// src/types.ts
|
|
337
420
|
import { existsSync, readFileSync } from "fs";
|
|
421
|
+
var BOOK_PRESETS = BOOK_PROFILES;
|
|
338
422
|
var BookConfigError = class extends Error {
|
|
339
423
|
constructor(message) {
|
|
340
424
|
super(message);
|
|
@@ -359,29 +443,33 @@ function readEnvFile(path = ".env") {
|
|
|
359
443
|
return {};
|
|
360
444
|
}
|
|
361
445
|
}
|
|
362
|
-
function
|
|
363
|
-
let candidate =
|
|
446
|
+
function resolvePreset(explicitPreset, explicitProfile) {
|
|
447
|
+
let candidate = explicitPreset ?? explicitProfile ?? process.env.BOOK_PRESET ?? process.env.BOOK_PROFILE;
|
|
364
448
|
let source = "default";
|
|
365
|
-
if (
|
|
366
|
-
else if (process.env.BOOK_PROFILE) source = "env";
|
|
449
|
+
if (explicitPreset || explicitProfile) source = "param";
|
|
450
|
+
else if (process.env.BOOK_PRESET || process.env.BOOK_PROFILE) source = "env";
|
|
367
451
|
if (!candidate) {
|
|
368
|
-
const
|
|
452
|
+
const env = readEnvFile();
|
|
453
|
+
const fromFile = env.BOOK_PRESET ?? env.BOOK_PROFILE;
|
|
369
454
|
if (fromFile) {
|
|
370
455
|
candidate = fromFile;
|
|
371
456
|
source = "dotenv";
|
|
372
457
|
}
|
|
373
458
|
}
|
|
374
459
|
candidate = candidate ?? "minimal";
|
|
375
|
-
if (!
|
|
460
|
+
if (!BOOK_PRESETS.includes(candidate)) {
|
|
376
461
|
throw new BookConfigError(
|
|
377
|
-
`
|
|
462
|
+
`preset must be one of ${BOOK_PRESETS.join(" | ")} (got ${JSON.stringify(candidate)})`
|
|
378
463
|
);
|
|
379
464
|
}
|
|
380
465
|
if (source === "default") {
|
|
381
|
-
console.warn("book-scaffold-astro:
|
|
466
|
+
console.warn("book-scaffold-astro: BOOK_PRESET not set; falling back to 'minimal'.");
|
|
382
467
|
}
|
|
383
468
|
return candidate;
|
|
384
469
|
}
|
|
470
|
+
function resolveProfile(explicit) {
|
|
471
|
+
return resolvePreset(void 0, explicit);
|
|
472
|
+
}
|
|
385
473
|
|
|
386
474
|
// src/integration.ts
|
|
387
475
|
import { fileURLToPath } from "url";
|
|
@@ -435,7 +523,11 @@ var ROUTE_REGISTRY = {
|
|
|
435
523
|
search: { pattern: "/search", file: "search.astro" },
|
|
436
524
|
print: { pattern: "/print", file: "print.astro" },
|
|
437
525
|
chapters: { pattern: "/chapters", file: "chapters.astro" },
|
|
438
|
-
convergence: { pattern: "/convergence", file: "convergence.astro" }
|
|
526
|
+
convergence: { pattern: "/convergence", file: "convergence.astro" },
|
|
527
|
+
// v3.4.0 (#7): consumer-collection-backed frontmatter route. Opt-in via
|
|
528
|
+
// routes: { frontmatter: true } AND content.config.ts defining the
|
|
529
|
+
// collection (use frontmatterCollection() helper from /schemas subpath).
|
|
530
|
+
frontmatter: { pattern: "/frontmatter/[slug]", file: "frontmatter/[...slug].astro" }
|
|
439
531
|
};
|
|
440
532
|
function resolvePage(file) {
|
|
441
533
|
return fileURLToPath(new URL(`../pages/${file}`, import.meta.url));
|
|
@@ -466,9 +558,14 @@ function bookScaffoldIntegration(opts) {
|
|
|
466
558
|
}
|
|
467
559
|
const consumerRoot = fileURLToPath(config.root);
|
|
468
560
|
const resolvedMdxPath = resolveMdxComponentsPath(consumerRoot, mdxComponentsModule);
|
|
561
|
+
const presetLiteral = JSON.stringify(profile);
|
|
469
562
|
updateConfig({
|
|
470
563
|
vite: {
|
|
471
|
-
plugins: [makeMdxComponentsVitePlugin(resolvedMdxPath)]
|
|
564
|
+
plugins: [makeMdxComponentsVitePlugin(resolvedMdxPath)],
|
|
565
|
+
define: {
|
|
566
|
+
"import.meta.env.BOOK_PRESET": presetLiteral,
|
|
567
|
+
"import.meta.env.BOOK_PROFILE": presetLiteral
|
|
568
|
+
}
|
|
472
569
|
}
|
|
473
570
|
});
|
|
474
571
|
}
|
|
@@ -478,7 +575,7 @@ function bookScaffoldIntegration(opts) {
|
|
|
478
575
|
|
|
479
576
|
// src/config.ts
|
|
480
577
|
async function defineBookConfig(opts) {
|
|
481
|
-
const profile =
|
|
578
|
+
const profile = resolvePreset(opts.preset, opts.profile);
|
|
482
579
|
const remarkPlugins = [];
|
|
483
580
|
const rehypePlugins = [];
|
|
484
581
|
if (profile === "academic") {
|
|
@@ -531,6 +628,8 @@ async function defineBookConfig(opts) {
|
|
|
531
628
|
...userMarkdown
|
|
532
629
|
};
|
|
533
630
|
const {
|
|
631
|
+
preset: _preset,
|
|
632
|
+
// v3.4.0
|
|
534
633
|
profile: _profile,
|
|
535
634
|
routes: _routes,
|
|
536
635
|
// v3.3.0
|
|
@@ -541,6 +640,7 @@ async function defineBookConfig(opts) {
|
|
|
541
640
|
markdown: _markdown,
|
|
542
641
|
...rest
|
|
543
642
|
} = opts;
|
|
643
|
+
void _preset;
|
|
544
644
|
void _profile;
|
|
545
645
|
void _routes;
|
|
546
646
|
void _mdxComponentsModule;
|
|
@@ -598,6 +698,7 @@ function freshnessLabel(f) {
|
|
|
598
698
|
}
|
|
599
699
|
}
|
|
600
700
|
export {
|
|
701
|
+
BOOK_PRESETS,
|
|
601
702
|
BOOK_PROFILES,
|
|
602
703
|
BookConfigError,
|
|
603
704
|
academicChapterSchema,
|
|
@@ -615,8 +716,11 @@ export {
|
|
|
615
716
|
minimalChapterSchema,
|
|
616
717
|
patternCategories,
|
|
617
718
|
patternsSchema,
|
|
719
|
+
researchPortfolioChapterSchema,
|
|
720
|
+
resolvePreset,
|
|
618
721
|
resolveProfile,
|
|
619
722
|
sourceTiers,
|
|
723
|
+
sourceTiersResearch,
|
|
620
724
|
sourcesSchema,
|
|
621
725
|
toolSlugs,
|
|
622
726
|
toolsChapterSchema,
|
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-CzXybcNA.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 };
|