@brandon_m_behring/book-scaffold-astro 4.26.3 → 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 +89 -5
- package/dist/schemas.d.ts +1 -1
- package/dist/schemas.mjs +8 -1
- package/dist/{types-CWXP1S4b.d.ts → types-CZrkqzpC.d.ts} +58 -26
- package/layouts/Base.astro +20 -19
- 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 +5 -2
- 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 +15 -6
- package/src/lib/nav-href.ts +24 -2
- package/styles/layout.css +14 -8
- package/styles/tokens.css +8 -10
package/dist/index.mjs
CHANGED
|
@@ -912,6 +912,7 @@ var BOOK_PROFILES = Object.keys(PROFILES);
|
|
|
912
912
|
// src/types.ts
|
|
913
913
|
import { existsSync, readFileSync } from "fs";
|
|
914
914
|
var BOOK_PRESETS = BOOK_PROFILES;
|
|
915
|
+
var NUMBER_STYLES = ["shared", "per-kind"];
|
|
915
916
|
var BookConfigError = class extends Error {
|
|
916
917
|
constructor(message) {
|
|
917
918
|
super(message);
|
|
@@ -936,6 +937,7 @@ function readEnvFile(path = ".env") {
|
|
|
936
937
|
return {};
|
|
937
938
|
}
|
|
938
939
|
}
|
|
940
|
+
var PRESET_FALLBACK_WARNING = /* @__PURE__ */ Symbol.for("book-scaffold-astro:preset-fallback-warning");
|
|
939
941
|
function resolvePreset(explicitPreset, explicitProfile) {
|
|
940
942
|
let candidate = explicitPreset ?? explicitProfile ?? process.env.BOOK_PRESET ?? process.env.BOOK_PROFILE;
|
|
941
943
|
let source = "default";
|
|
@@ -956,7 +958,13 @@ function resolvePreset(explicitPreset, explicitProfile) {
|
|
|
956
958
|
);
|
|
957
959
|
}
|
|
958
960
|
if (source === "default") {
|
|
959
|
-
|
|
961
|
+
const warningState = globalThis;
|
|
962
|
+
if (!warningState[PRESET_FALLBACK_WARNING]) {
|
|
963
|
+
warningState[PRESET_FALLBACK_WARNING] = true;
|
|
964
|
+
console.warn(
|
|
965
|
+
"book-scaffold-astro: no preset resolved; falling back to 'minimal' for v4 compatibility. This fallback will be removed in v5. Add a built-in style to defineBookConfig and pass the same preset to defineBookSchemas, or set BOOK_PRESET."
|
|
966
|
+
);
|
|
967
|
+
}
|
|
960
968
|
}
|
|
961
969
|
return candidate;
|
|
962
970
|
}
|
|
@@ -966,7 +974,7 @@ function resolveProfile(explicit) {
|
|
|
966
974
|
|
|
967
975
|
// src/integration.ts
|
|
968
976
|
import { fileURLToPath } from "url";
|
|
969
|
-
import { readFileSync as readFileSync2 } from "fs";
|
|
977
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
970
978
|
import { join } from "path";
|
|
971
979
|
|
|
972
980
|
// src/lib/define-style.ts
|
|
@@ -981,6 +989,7 @@ function composeStyles(styles) {
|
|
|
981
989
|
for (const style of styles) {
|
|
982
990
|
if (style.name !== void 0) merged.name = style.name;
|
|
983
991
|
if (style.preset !== void 0) merged.preset = style.preset;
|
|
992
|
+
if (style.numberStyle !== void 0) merged.numberStyle = style.numberStyle;
|
|
984
993
|
if (style.site !== void 0) merged.site = style.site;
|
|
985
994
|
if (style.deploy !== void 0) merged.deploy = style.deploy;
|
|
986
995
|
if (style.releaseStatus !== void 0) {
|
|
@@ -1197,6 +1206,17 @@ var ROUTE_REGISTRY = {
|
|
|
1197
1206
|
// empty string → '/[slug]'; arbitrary string → '/<prefix>/[slug]').
|
|
1198
1207
|
frontmatter: { pattern: "/frontmatter/[slug]", file: "frontmatter/[...slug].astro" }
|
|
1199
1208
|
};
|
|
1209
|
+
var DEFAULT_CONTENT_SECURITY_POLICY = "default-src 'self'; script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval' https://static.cloudflareinsights.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self' https://cloudflareinsights.com; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; form-action 'self'";
|
|
1210
|
+
function renderSecurityHeaders(contentSecurityPolicy) {
|
|
1211
|
+
const csp = contentSecurityPolicy ?? DEFAULT_CONTENT_SECURITY_POLICY;
|
|
1212
|
+
return `/*
|
|
1213
|
+
Strict-Transport-Security: max-age=31536000; includeSubDomains
|
|
1214
|
+
X-Content-Type-Options: nosniff
|
|
1215
|
+
Referrer-Policy: strict-origin-when-cross-origin
|
|
1216
|
+
Permissions-Policy: camera=(), microphone=(), geolocation=()
|
|
1217
|
+
Content-Security-Policy: ${csp}
|
|
1218
|
+
`;
|
|
1219
|
+
}
|
|
1200
1220
|
function frontmatterPatternFromPrefix(prefix) {
|
|
1201
1221
|
if (prefix === void 0) return ROUTE_REGISTRY.frontmatter.pattern;
|
|
1202
1222
|
if (prefix === "") return "/[slug]";
|
|
@@ -1208,6 +1228,7 @@ function resolvePage(file) {
|
|
|
1208
1228
|
function bookScaffoldIntegration(opts) {
|
|
1209
1229
|
const {
|
|
1210
1230
|
profile,
|
|
1231
|
+
numberStyle = "shared",
|
|
1211
1232
|
routes: userOverrides = {},
|
|
1212
1233
|
extraStyles = [],
|
|
1213
1234
|
mdxComponentsModule,
|
|
@@ -1215,6 +1236,7 @@ function bookScaffoldIntegration(opts) {
|
|
|
1215
1236
|
title,
|
|
1216
1237
|
subtitle,
|
|
1217
1238
|
releaseStatus,
|
|
1239
|
+
securityHeaders,
|
|
1218
1240
|
description,
|
|
1219
1241
|
portfolio,
|
|
1220
1242
|
// v4.6.0: book-level author + SEO config, propagated through the
|
|
@@ -1245,7 +1267,7 @@ function bookScaffoldIntegration(opts) {
|
|
|
1245
1267
|
),
|
|
1246
1268
|
frontmatter: fmEnabled
|
|
1247
1269
|
};
|
|
1248
|
-
|
|
1270
|
+
const integration = {
|
|
1249
1271
|
name: "book-scaffold-astro",
|
|
1250
1272
|
hooks: {
|
|
1251
1273
|
"astro:config:setup": ({ injectScript, injectRoute, updateConfig, config }) => {
|
|
@@ -1315,9 +1337,38 @@ function bookScaffoldIntegration(opts) {
|
|
|
1315
1337
|
}
|
|
1316
1338
|
}
|
|
1317
1339
|
});
|
|
1340
|
+
},
|
|
1341
|
+
// v4.27.0 (#188): emit defaults for every build, not only newly
|
|
1342
|
+
// scaffolded projects, so existing consumers become protected on their
|
|
1343
|
+
// next package upgrade. Astro copies public/_headers into the output
|
|
1344
|
+
// before build:done; an existing target therefore means the consumer
|
|
1345
|
+
// owns the complete file and must win byte-for-byte.
|
|
1346
|
+
"astro:build:done": ({ dir, logger }) => {
|
|
1347
|
+
if (securityHeaders === false) {
|
|
1348
|
+
logger.info("security-header emission disabled by defineBookConfig (#188)");
|
|
1349
|
+
return;
|
|
1350
|
+
}
|
|
1351
|
+
const target = join(fileURLToPath(dir), "_headers");
|
|
1352
|
+
if (existsSync3(target)) {
|
|
1353
|
+
logger.info("consumer public/_headers present; scaffold defaults skipped (#188)");
|
|
1354
|
+
return;
|
|
1355
|
+
}
|
|
1356
|
+
writeFileSync(
|
|
1357
|
+
target,
|
|
1358
|
+
renderSecurityHeaders(securityHeaders?.contentSecurityPolicy),
|
|
1359
|
+
"utf8"
|
|
1360
|
+
);
|
|
1361
|
+
logger.info("emitted default security headers; public/_headers overrides them (#188)");
|
|
1318
1362
|
}
|
|
1319
1363
|
}
|
|
1320
1364
|
};
|
|
1365
|
+
Object.defineProperty(integration, "__bookScaffoldResolvedConfig", {
|
|
1366
|
+
value: Object.freeze({ preset: profile, numberStyle }),
|
|
1367
|
+
enumerable: false,
|
|
1368
|
+
configurable: false,
|
|
1369
|
+
writable: false
|
|
1370
|
+
});
|
|
1371
|
+
return integration;
|
|
1321
1372
|
}
|
|
1322
1373
|
|
|
1323
1374
|
// src/config.ts
|
|
@@ -1354,8 +1405,19 @@ async function defineBookConfig(opts) {
|
|
|
1354
1405
|
if ("preset" in opts || "profile" in opts) {
|
|
1355
1406
|
throw v3MigrationError(opts);
|
|
1356
1407
|
}
|
|
1408
|
+
if (Object.prototype.hasOwnProperty.call(opts, "deploy")) {
|
|
1409
|
+
console.warn(
|
|
1410
|
+
"book-scaffold-astro: defineBookConfig({ deploy }) is inert and deprecated; create-book chooses wrangler.toml from its CLI preset. Remove this field before upgrading to v5 (#180)."
|
|
1411
|
+
);
|
|
1412
|
+
}
|
|
1357
1413
|
const composed = composeStyles(opts.styles ?? []);
|
|
1358
|
-
const profile =
|
|
1414
|
+
const profile = resolvePreset(composed.preset);
|
|
1415
|
+
const numberStyle = opts.numberStyle ?? composed.numberStyle ?? "shared";
|
|
1416
|
+
if (!NUMBER_STYLES.includes(numberStyle)) {
|
|
1417
|
+
throw new BookConfigError(
|
|
1418
|
+
`numberStyle must be one of ${NUMBER_STYLES.join(" | ")} (got ${JSON.stringify(numberStyle)})`
|
|
1419
|
+
);
|
|
1420
|
+
}
|
|
1359
1421
|
const site = opts.site ?? composed.site;
|
|
1360
1422
|
if (!site) {
|
|
1361
1423
|
throw new BookConfigError(
|
|
@@ -1423,6 +1485,7 @@ async function defineBookConfig(opts) {
|
|
|
1423
1485
|
sitemap(sitemapOptions),
|
|
1424
1486
|
bookScaffoldIntegration({
|
|
1425
1487
|
profile,
|
|
1488
|
+
numberStyle,
|
|
1426
1489
|
routes: mergedRoutes,
|
|
1427
1490
|
mdxComponentsModule,
|
|
1428
1491
|
extraStyles: mergedExtraStyles,
|
|
@@ -1433,6 +1496,11 @@ async function defineBookConfig(opts) {
|
|
|
1433
1496
|
subtitle: opts.subtitle,
|
|
1434
1497
|
// v4.26.2 (#149; style inheritance + opt-out fixed in v4.26.3).
|
|
1435
1498
|
releaseStatus: resolvedReleaseStatus === false ? void 0 : resolvedReleaseStatus,
|
|
1499
|
+
// v4.27.0 (#188): undefined enables the audited defaults, false opts
|
|
1500
|
+
// out, and an object can replace only the CSP. The integration owns
|
|
1501
|
+
// the build:done emission because that is where Astro's output dir is
|
|
1502
|
+
// known and a copied consumer public/_headers can be detected.
|
|
1503
|
+
securityHeaders: opts.securityHeaders,
|
|
1436
1504
|
description: opts.description,
|
|
1437
1505
|
portfolio: resolvedPortfolio,
|
|
1438
1506
|
// v4.6.0: book-level author + SEO config (ogImage, twitterHandle),
|
|
@@ -1479,6 +1547,7 @@ async function defineBookConfig(opts) {
|
|
|
1479
1547
|
};
|
|
1480
1548
|
const {
|
|
1481
1549
|
styles: _styles,
|
|
1550
|
+
numberStyle: _numberStyle,
|
|
1482
1551
|
site: _site,
|
|
1483
1552
|
routes: _routes,
|
|
1484
1553
|
deploy: _deploy,
|
|
@@ -1492,6 +1561,8 @@ async function defineBookConfig(opts) {
|
|
|
1492
1561
|
subtitle: _subtitle,
|
|
1493
1562
|
// v4.26.2 (#149): strip the release-state banner config.
|
|
1494
1563
|
releaseStatus: _releaseStatus,
|
|
1564
|
+
// v4.27.0 (#188): consumed by the scaffold integration at build:done.
|
|
1565
|
+
securityHeaders: _securityHeaders,
|
|
1495
1566
|
description: _description,
|
|
1496
1567
|
portfolio: _portfolio,
|
|
1497
1568
|
// v4.6.0: strip new book-level SEO opts (author + seo block).
|
|
@@ -1512,6 +1583,7 @@ async function defineBookConfig(opts) {
|
|
|
1512
1583
|
...rest
|
|
1513
1584
|
} = opts;
|
|
1514
1585
|
void _styles;
|
|
1586
|
+
void _numberStyle;
|
|
1515
1587
|
void _site;
|
|
1516
1588
|
void _routes;
|
|
1517
1589
|
void _deploy;
|
|
@@ -1523,6 +1595,7 @@ async function defineBookConfig(opts) {
|
|
|
1523
1595
|
void _title;
|
|
1524
1596
|
void _subtitle;
|
|
1525
1597
|
void _releaseStatus;
|
|
1598
|
+
void _securityHeaders;
|
|
1526
1599
|
void _description;
|
|
1527
1600
|
void _portfolio;
|
|
1528
1601
|
void _author;
|
|
@@ -1628,6 +1701,9 @@ function assertEnumProp(value, allowed, ctx) {
|
|
|
1628
1701
|
);
|
|
1629
1702
|
}
|
|
1630
1703
|
|
|
1704
|
+
// src/index.ts
|
|
1705
|
+
init_katex_macros();
|
|
1706
|
+
|
|
1631
1707
|
// src/lib/book-link.ts
|
|
1632
1708
|
function resolveBookHref(siblingBooks, book, to) {
|
|
1633
1709
|
const base = siblingBooks?.[book];
|
|
@@ -1641,9 +1717,13 @@ function resolveBookHref(siblingBooks, book, to) {
|
|
|
1641
1717
|
}
|
|
1642
1718
|
|
|
1643
1719
|
// src/lib/nav-href.ts
|
|
1644
|
-
function
|
|
1720
|
+
function normalizeBase(baseUrl) {
|
|
1645
1721
|
return (baseUrl || "/").replace(/\/*$/, "/");
|
|
1646
1722
|
}
|
|
1723
|
+
function baseNoSlash(baseUrl) {
|
|
1724
|
+
return (baseUrl || "/").replace(/\/+$/, "");
|
|
1725
|
+
}
|
|
1726
|
+
var normBase = normalizeBase;
|
|
1647
1727
|
function fillTokens(pattern, tokens) {
|
|
1648
1728
|
return pattern.replace(/:(book|slug|route|id)\b/g, (_m, k) => tokens[k] ?? "");
|
|
1649
1729
|
}
|
|
@@ -1916,6 +1996,7 @@ export {
|
|
|
1916
1996
|
BookConfigError,
|
|
1917
1997
|
DEFAULT_GITHUB_BRANCH,
|
|
1918
1998
|
KIND_LABEL,
|
|
1999
|
+
NUMBER_STYLES,
|
|
1919
2000
|
THEOREM_KINDS,
|
|
1920
2001
|
UNKNOWN_PART_ORDINAL,
|
|
1921
2002
|
academicChapterSchema,
|
|
@@ -1928,6 +2009,7 @@ export {
|
|
|
1928
2009
|
apparatusHref,
|
|
1929
2010
|
assertEnumProp,
|
|
1930
2011
|
assertKnownDomain,
|
|
2012
|
+
baseNoSlash,
|
|
1931
2013
|
bloomLevels,
|
|
1932
2014
|
bookOf,
|
|
1933
2015
|
bookScaffoldIntegration,
|
|
@@ -1962,6 +2044,7 @@ export {
|
|
|
1962
2044
|
layoutModes,
|
|
1963
2045
|
minimalChapterSchema,
|
|
1964
2046
|
minimalStyle,
|
|
2047
|
+
normalizeBase,
|
|
1965
2048
|
normalizeFrontmatterConfig,
|
|
1966
2049
|
originUrlFromGitConfig,
|
|
1967
2050
|
parseRepoSlug,
|
|
@@ -1993,6 +2076,7 @@ export {
|
|
|
1993
2076
|
sourceTiersResearch,
|
|
1994
2077
|
sourcesSchema,
|
|
1995
2078
|
spreadBlueprint,
|
|
2079
|
+
ssmMacros,
|
|
1996
2080
|
theoremLabel,
|
|
1997
2081
|
tocHeadings,
|
|
1998
2082
|
toolSlugs,
|
package/dist/schemas.d.ts
CHANGED
package/dist/schemas.mjs
CHANGED
|
@@ -814,6 +814,7 @@ function readEnvFile(path = ".env") {
|
|
|
814
814
|
return {};
|
|
815
815
|
}
|
|
816
816
|
}
|
|
817
|
+
var PRESET_FALLBACK_WARNING = /* @__PURE__ */ Symbol.for("book-scaffold-astro:preset-fallback-warning");
|
|
817
818
|
function resolvePreset(explicitPreset, explicitProfile) {
|
|
818
819
|
let candidate = explicitPreset ?? explicitProfile ?? process.env.BOOK_PRESET ?? process.env.BOOK_PROFILE;
|
|
819
820
|
let source = "default";
|
|
@@ -834,7 +835,13 @@ function resolvePreset(explicitPreset, explicitProfile) {
|
|
|
834
835
|
);
|
|
835
836
|
}
|
|
836
837
|
if (source === "default") {
|
|
837
|
-
|
|
838
|
+
const warningState = globalThis;
|
|
839
|
+
if (!warningState[PRESET_FALLBACK_WARNING]) {
|
|
840
|
+
warningState[PRESET_FALLBACK_WARNING] = true;
|
|
841
|
+
console.warn(
|
|
842
|
+
"book-scaffold-astro: no preset resolved; falling back to 'minimal' for v4 compatibility. This fallback will be removed in v5. Add a built-in style to defineBookConfig and pass the same preset to defineBookSchemas, or set BOOK_PRESET."
|
|
843
|
+
);
|
|
844
|
+
}
|
|
838
845
|
}
|
|
839
846
|
return candidate;
|
|
840
847
|
}
|
|
@@ -353,6 +353,8 @@ interface Style {
|
|
|
353
353
|
readonly name?: string;
|
|
354
354
|
/** Profile that backs this style — determines schema + default routes + styles + KaTeX wiring. */
|
|
355
355
|
readonly preset?: BookPreset;
|
|
356
|
+
/** Theorem-family numbering strategy; shallow override (last wins). */
|
|
357
|
+
readonly numberStyle?: NumberStyle;
|
|
356
358
|
/** Book's deployed origin (sitemap, canonical, Pagefind). Required at composition end;
|
|
357
359
|
* optional inside a Style (so styles can omit it and consumers can provide per-book). */
|
|
358
360
|
readonly site?: string;
|
|
@@ -374,10 +376,10 @@ interface Style {
|
|
|
374
376
|
* `remarkPlugins` and `rehypePlugins` arrays concat across the chain;
|
|
375
377
|
* scalar fields override. */
|
|
376
378
|
readonly markdown?: AstroUserConfig['markdown'];
|
|
377
|
-
/**
|
|
378
|
-
* -
|
|
379
|
-
*
|
|
380
|
-
*
|
|
379
|
+
/** Reserved legacy metadata. It is composed but has no runtime or
|
|
380
|
+
* create-book effect: create-book chooses wrangler.toml from its CLI
|
|
381
|
+
* preset before a consumer Style exists (#180).
|
|
382
|
+
* @deprecated Inert in v4; scheduled for removal in v5. */
|
|
381
383
|
readonly deploy?: 'pages' | 'workers';
|
|
382
384
|
/**
|
|
383
385
|
* v4.26.2 (#149; style inheritance fixed in v4.26.3): release-state
|
|
@@ -445,7 +447,7 @@ declare function defineStyle(opts: StyleInput): Style;
|
|
|
445
447
|
* - Top-level `defineBookConfig` fields beat any style (handled in config.ts)
|
|
446
448
|
*
|
|
447
449
|
* Per-key merge strategy:
|
|
448
|
-
* - `preset`, `site`, `deploy`, `mdxComponentsModule`, `name`, `releaseStatus`
|
|
450
|
+
* - `preset`, `numberStyle`, `site`, `deploy`, `mdxComponentsModule`, `name`, `releaseStatus`
|
|
449
451
|
* → shallow override (last defined wins; `releaseStatus: false` suppresses)
|
|
450
452
|
* - `routes` → per-route spread (each route key independently overridable)
|
|
451
453
|
* - `katexMacros` → per-macro spread (each macro key independently overridable)
|
|
@@ -482,6 +484,30 @@ declare function normalizeFrontmatterConfig(v: FrontmatterRouteConfig | undefine
|
|
|
482
484
|
|
|
483
485
|
type BookPreset = BookProfile;
|
|
484
486
|
declare const BOOK_PRESETS: readonly ("academic" | "tools" | "minimal" | "course-notes" | "research-portfolio")[];
|
|
487
|
+
/** Theorem-family numbering strategy used by build-labels and validate. */
|
|
488
|
+
type NumberStyle = 'shared' | 'per-kind';
|
|
489
|
+
declare const NUMBER_STYLES: readonly ["shared", "per-kind"];
|
|
490
|
+
/**
|
|
491
|
+
* v4.27.0 (#188): build-time security-header customization.
|
|
492
|
+
*
|
|
493
|
+
* The integration emits a Cloudflare-compatible `dist/_headers` file by
|
|
494
|
+
* default. Consumers that need a different CSP can replace that one header
|
|
495
|
+
* while retaining the scaffold's other audited defaults.
|
|
496
|
+
*/
|
|
497
|
+
interface SecurityHeadersConfig {
|
|
498
|
+
contentSecurityPolicy?: string;
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* v4.26.2 (#149): book-level release state rendered by
|
|
502
|
+
* `<PreReleaseBanner>` across every page.
|
|
503
|
+
*
|
|
504
|
+
* Style inheritance and explicit suppression were fixed in v4.26.3.
|
|
505
|
+
*/
|
|
506
|
+
interface ReleaseStatusConfig {
|
|
507
|
+
state: 'alpha' | 'beta' | 'rc' | 'locked';
|
|
508
|
+
dismissAt?: string;
|
|
509
|
+
message?: string;
|
|
510
|
+
}
|
|
485
511
|
/**
|
|
486
512
|
* v4.26.2 (#149): book-level release state rendered by
|
|
487
513
|
* `<PreReleaseBanner>` across every page.
|
|
@@ -531,6 +557,13 @@ interface BookConfigOptions {
|
|
|
531
557
|
* });
|
|
532
558
|
*/
|
|
533
559
|
styles?: readonly Style[];
|
|
560
|
+
/**
|
|
561
|
+
* v4.27.0 (#175): theorem-family counter strategy. `shared` preserves the
|
|
562
|
+
* amsthm-style sequence used through v4.26; `per-kind` gives theorem,
|
|
563
|
+
* proposition, lemma, and each other theorem kind an independent sequence.
|
|
564
|
+
* May also be supplied by a Style; top-level config wins.
|
|
565
|
+
*/
|
|
566
|
+
numberStyle?: NumberStyle;
|
|
534
567
|
/**
|
|
535
568
|
* Optional per-route override of the composed profile's defaults. Use to
|
|
536
569
|
* disable an auto-injected route (e.g. multi-book consumer that ships
|
|
@@ -545,10 +578,13 @@ interface BookConfigOptions {
|
|
|
545
578
|
*/
|
|
546
579
|
routes?: PartialRouteToggles;
|
|
547
580
|
/**
|
|
548
|
-
* v4.0.0 (
|
|
549
|
-
*
|
|
550
|
-
*
|
|
551
|
-
*
|
|
581
|
+
* v4.0.0 (#50): RESERVED — accepted and style-chain-merged, but currently
|
|
582
|
+
* has NO runtime effect (#180). The wrangler.toml shape is decided once, at
|
|
583
|
+
* scaffold time, by `create-book` from the profile name (academic/tools/
|
|
584
|
+
* minimal → Workers; course-notes/research-portfolio → Pages); setting this
|
|
585
|
+
* field changes nothing afterward. Wire-or-remove is a v5 decision.
|
|
586
|
+
* @deprecated Inert in v4; remove this option before v5. Deployment shape
|
|
587
|
+
* is chosen by create-book or by the consumer's own deployment files.
|
|
552
588
|
*/
|
|
553
589
|
deploy?: 'pages' | 'workers';
|
|
554
590
|
/**
|
|
@@ -625,6 +661,14 @@ interface BookConfigOptions {
|
|
|
625
661
|
* composed style chain; set `false` to suppress an inherited banner.
|
|
626
662
|
*/
|
|
627
663
|
releaseStatus?: ReleaseStatusConfig | false;
|
|
664
|
+
/**
|
|
665
|
+
* v4.27.0 (#188): security headers emitted to `dist/_headers` after an
|
|
666
|
+
* Astro build. Omit for the scaffold defaults; set `false` to emit no
|
|
667
|
+
* scaffold-owned file; provide `contentSecurityPolicy` to replace only the
|
|
668
|
+
* default CSP while retaining HSTS, XCTO, Referrer-Policy, and
|
|
669
|
+
* Permissions-Policy. A consumer-owned `public/_headers` always wins.
|
|
670
|
+
*/
|
|
671
|
+
securityHeaders?: SecurityHeadersConfig | false;
|
|
628
672
|
/**
|
|
629
673
|
* v4.5.0: Book description. Read by the auto-injected `/` landing page (lead paragraph + <meta description>).
|
|
630
674
|
* Optional; landing renders no description paragraph if unset.
|
|
@@ -757,6 +801,8 @@ interface BookSchemasOptions {
|
|
|
757
801
|
/** Options for the internal `bookScaffoldIntegration`. See PACKAGE_DESIGN.md §6. */
|
|
758
802
|
interface BookScaffoldIntegrationOptions {
|
|
759
803
|
profile: BookProfile;
|
|
804
|
+
/** Resolved theorem-family counter strategy exposed to package CLI tooling. */
|
|
805
|
+
numberStyle?: NumberStyle;
|
|
760
806
|
/**
|
|
761
807
|
* Per-route override; merged into the profile's defaults.
|
|
762
808
|
* v4.0.0: `routes.frontmatter` widened to `boolean | { enabled, prefix? }`
|
|
@@ -780,6 +826,8 @@ interface BookScaffoldIntegrationOptions {
|
|
|
780
826
|
/** v4.26.2 (#149; style inheritance fixed in v4.26.3): resolved
|
|
781
827
|
* release-state banner propagated via the book-config virtual module. */
|
|
782
828
|
releaseStatus?: ReleaseStatusConfig;
|
|
829
|
+
/** v4.27.0 (#188): resolved security-header emission policy. */
|
|
830
|
+
securityHeaders?: SecurityHeadersConfig | false;
|
|
783
831
|
/** v4.5.0: book description, propagated to `/` landing via vite.define. */
|
|
784
832
|
description?: string;
|
|
785
833
|
/**
|
|
@@ -831,22 +879,6 @@ interface BookScaffoldIntegrationOptions {
|
|
|
831
879
|
declare class BookConfigError extends Error {
|
|
832
880
|
constructor(message: string);
|
|
833
881
|
}
|
|
834
|
-
/**
|
|
835
|
-
* Resolve preset from explicit args → env → .env → default. Throws on invalid.
|
|
836
|
-
*
|
|
837
|
-
* v3.4.0 (closes #9): canonical resolver. Accepts both `preset` and `profile`
|
|
838
|
-
* (back-compat) explicit args; reads both `BOOK_PRESET` (preferred) and
|
|
839
|
-
* `BOOK_PROFILE` (alias) env vars; same for .env file lookups.
|
|
840
|
-
*
|
|
841
|
-
* Resolution order:
|
|
842
|
-
* 1. explicitPreset (from defineBookConfig({ preset: ... }))
|
|
843
|
-
* 2. explicitProfile (from defineBookConfig({ profile: ... }))
|
|
844
|
-
* 3. process.env.BOOK_PRESET
|
|
845
|
-
* 4. process.env.BOOK_PROFILE
|
|
846
|
-
* 5. .env BOOK_PRESET
|
|
847
|
-
* 6. .env BOOK_PROFILE
|
|
848
|
-
* 7. 'minimal' (with console.warn)
|
|
849
|
-
*/
|
|
850
882
|
declare function resolvePreset(explicitPreset?: BookPreset, explicitProfile?: BookProfile): BookPreset;
|
|
851
883
|
/**
|
|
852
884
|
* Backward-compat alias. New code should use `resolvePreset()`.
|
|
@@ -855,4 +887,4 @@ declare function resolvePreset(explicitPreset?: BookPreset, explicitProfile?: Bo
|
|
|
855
887
|
*/
|
|
856
888
|
declare function resolveProfile(explicit?: BookProfile): BookProfile;
|
|
857
889
|
|
|
858
|
-
export { BOOK_PRESETS as B, type ChapterFor as C, type FreshnessAffordance as F, type PartKey as P, type ReleaseStatusConfig as R, type
|
|
890
|
+
export { BOOK_PRESETS as B, type ChapterFor as C, type FreshnessAffordance as F, NUMBER_STYLES as N, type PartKey as P, type ReleaseStatusConfig as R, type SecurityHeadersConfig as S, type VolatilityBadge as V, BOOK_PROFILES as a, BookConfigError as b, type BookConfigOptions as c, type BookPreset as d, type BookProfile as e, type BookScaffoldIntegrationOptions as f, type BookSchemasOptions as g, type ChaptersRenderer as h, type FrontmatterRouteConfig as i, type NumberStyle as j, type PartialRouteToggles as k, type ProfileDefinition as l, type RouteToggles as m, type StatusBadge as n, type Style as o, type StyleInput as p, composeStyles as q, defineProfile as r, defineStyle as s, normalizeFrontmatterConfig as t, resolvePreset as u, resolveProfile as v };
|
package/layouts/Base.astro
CHANGED
|
@@ -15,15 +15,15 @@
|
|
|
15
15
|
* lang — html lang attribute (default: en)
|
|
16
16
|
* showSidebar — render the left chapter-navigation sidebar (default: true).
|
|
17
17
|
* Set false for the landing page or any full-bleed surface.
|
|
18
|
-
* Below
|
|
19
|
-
*
|
|
18
|
+
* Below 80rem (1280px) the sidebar is auto-hidden via CSS and
|
|
19
|
+
* the mobile nav drawer is the chapter nav (v4.26.0, #80).
|
|
20
|
+
* showChrome — render the automatic tools chrome (ToolFilter)
|
|
20
21
|
* (default: true). Set false on a landing/hub page with no
|
|
21
|
-
*
|
|
22
|
+
* chapter filtering; search + theme toggle always render.
|
|
22
23
|
*
|
|
23
|
-
* Tools chrome (ToolFilter
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* (search + theme toggle only).
|
|
24
|
+
* Tools chrome (ToolFilter): only mounted when BOOK_PROFILE !== 'academic' AND
|
|
25
|
+
* showChrome !== false. VersionSelector is a prop-driven public component that
|
|
26
|
+
* consumers mount explicitly once they have real deployed-version data.
|
|
27
27
|
*
|
|
28
28
|
* Usage:
|
|
29
29
|
* ---
|
|
@@ -51,12 +51,11 @@ import '../styles/chapter.css';
|
|
|
51
51
|
import '../styles/tool-filter.css';
|
|
52
52
|
import '../styles/convergence.css';
|
|
53
53
|
import '../styles/print.css';
|
|
54
|
-
// Use package-path
|
|
54
|
+
// Use the package-path import for the .tsx island so Vite resolves it
|
|
55
55
|
// via the exports map (→ pre-compiled dist/components/*.mjs with proper
|
|
56
56
|
// preact JSX). The raw .tsx files are also shipped so consumers can
|
|
57
57
|
// import them directly, but relative imports from inside the package
|
|
58
58
|
// must route through the exports map, not the raw source.
|
|
59
|
-
import VersionSelector from '@brandon_m_behring/book-scaffold-astro/components/VersionSelector';
|
|
60
59
|
import ToolFilter from '@brandon_m_behring/book-scaffold-astro/components/ToolFilter';
|
|
61
60
|
import Sidebar from '../components/Sidebar.astro';
|
|
62
61
|
// v4.26.2 (#149; style inheritance fixed in v4.26.3): site-wide
|
|
@@ -68,6 +67,7 @@ import NavContent from '../components/NavContent.astro';
|
|
|
68
67
|
// description fallback, ogImage default, twitterHandle) from the
|
|
69
68
|
// book-config virtual module (was landing-config in v4.5.1).
|
|
70
69
|
import bookConfig from 'virtual:book-scaffold/book-config';
|
|
70
|
+
import { normalizeBase } from '../src/lib/nav-href';
|
|
71
71
|
|
|
72
72
|
const profile = import.meta.env.BOOK_PROFILE ?? 'minimal';
|
|
73
73
|
|
|
@@ -76,10 +76,10 @@ interface Props {
|
|
|
76
76
|
description?: string;
|
|
77
77
|
lang?: string;
|
|
78
78
|
showSidebar?: boolean;
|
|
79
|
-
/** #163: render the optional
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
79
|
+
/** #163: render the optional automatic tools chrome — the ToolFilter island
|
|
80
|
+
* (default true). Set false on landing/hub pages with no chapter filtering;
|
|
81
|
+
* the search + theme-toggle cluster always renders. (No-op for the academic
|
|
82
|
+
* profile, which never shows the ToolFilter.) */
|
|
83
83
|
showChrome?: boolean;
|
|
84
84
|
/** v4.6.0: Open Graph image URL (relative to site root, or absolute). */
|
|
85
85
|
ogImage?: string;
|
|
@@ -97,9 +97,9 @@ const {
|
|
|
97
97
|
ogType = 'website',
|
|
98
98
|
} = Astro.props;
|
|
99
99
|
|
|
100
|
-
//
|
|
100
|
+
// Automatic tools chrome (ToolFilter) renders only for non-academic
|
|
101
101
|
// profiles AND when the page opts in via showChrome (default true). A
|
|
102
|
-
// landing/hub page (no
|
|
102
|
+
// landing/hub page (no chapter filtering) passes showChrome={false} to
|
|
103
103
|
// get the search + theme-toggle cluster only — the chrome-free path that
|
|
104
104
|
// previously forced borrowing the academic profile (and its katex deps). #163.
|
|
105
105
|
const showToolsChrome = (profile !== 'academic') && showChrome;
|
|
@@ -120,7 +120,7 @@ const absoluteOgImage = resolvedOgImage
|
|
|
120
120
|
const twitterHandle = bookConfig.seo?.twitterHandle ?? null;
|
|
121
121
|
const ogSiteName = bookConfig.title ?? title;
|
|
122
122
|
const ogDescription = description ?? bookConfig.description ?? '';
|
|
123
|
-
const baseUrl = (import.meta.env.BASE_URL
|
|
123
|
+
const baseUrl = normalizeBase(import.meta.env.BASE_URL);
|
|
124
124
|
---
|
|
125
125
|
|
|
126
126
|
<!doctype html>
|
|
@@ -196,7 +196,6 @@ const baseUrl = (import.meta.env.BASE_URL ?? '/').replace(/\/*$/, '/');
|
|
|
196
196
|
</a>
|
|
197
197
|
)}
|
|
198
198
|
{showToolsChrome && <ToolFilter client:idle />}
|
|
199
|
-
{showToolsChrome && <VersionSelector client:idle />}
|
|
200
199
|
<a
|
|
201
200
|
href={`${baseUrl}search/`}
|
|
202
201
|
class="chrome-button"
|
|
@@ -217,7 +216,8 @@ const baseUrl = (import.meta.env.BASE_URL ?? '/').replace(/\/*$/, '/');
|
|
|
217
216
|
</button>
|
|
218
217
|
</div>
|
|
219
218
|
{/* v4.26.0 (#80): mobile/tablet nav drawer (sibling of <main>, NOT a <main>).
|
|
220
|
-
CSS-hidden ≥
|
|
219
|
+
CSS-hidden ≥80rem where the Sidebar is the nav (layout.css and the
|
|
220
|
+
controller's desktop auto-close agree on 80rem). role=dialog + the inline
|
|
221
221
|
controller below provide focus-trap/ESC; `:target` is the no-JS fallback. */}
|
|
222
222
|
{showSidebar && (
|
|
223
223
|
<div id="nav-drawer" class="nav-drawer" data-nav-drawer>
|
|
@@ -390,7 +390,8 @@ const baseUrl = (import.meta.env.BASE_URL ?? '/').replace(/\/*$/, '/');
|
|
|
390
390
|
text-decoration: none;
|
|
391
391
|
}
|
|
392
392
|
|
|
393
|
-
/*
|
|
393
|
+
/* Opt-in VersionSelector dropdown. Base provides its visual language but
|
|
394
|
+
* deliberately does not mount it without a consumer-owned version manifest. */
|
|
394
395
|
.version-selector {
|
|
395
396
|
position: relative;
|
|
396
397
|
display: inline-block;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brandon_m_behring/book-scaffold-astro",
|
|
3
|
-
"description": "Astro 6 + MDX toolkit for long-form technical books
|
|
4
|
-
"version": "4.
|
|
3
|
+
"description": "Astro 6 + MDX toolkit for long-form technical books with five typed presets, Tufte typography, citations, search, PDF, and Cloudflare deployment.",
|
|
4
|
+
"version": "4.27.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Brandon Behring",
|
|
@@ -161,9 +161,11 @@
|
|
|
161
161
|
"pedagogy",
|
|
162
162
|
"examples",
|
|
163
163
|
"CLAUDE.md",
|
|
164
|
+
"AGENTS.md",
|
|
164
165
|
"README.md",
|
|
165
|
-
"
|
|
166
|
-
"
|
|
166
|
+
"LICENSE",
|
|
167
|
+
"LICENSE-CONTENT",
|
|
168
|
+
"LATEX_TO_MDX_MAPPING.md"
|
|
167
169
|
],
|
|
168
170
|
"scripts": {
|
|
169
171
|
"build": "tsup",
|
|
@@ -195,6 +197,7 @@
|
|
|
195
197
|
"@fontsource-variable/roboto": "^5.2.10",
|
|
196
198
|
"@fontsource-variable/source-code-pro": "^5.2.7",
|
|
197
199
|
"pagefind": "^1.5.2",
|
|
200
|
+
"vite": "^7.3.2",
|
|
198
201
|
"yaml": "^2.9.0"
|
|
199
202
|
},
|
|
200
203
|
"devDependencies": {
|
package/pages/answers.astro
CHANGED
|
@@ -26,6 +26,7 @@ import { render } from 'astro:content';
|
|
|
26
26
|
import bookConfig from 'virtual:book-scaffold/book-config';
|
|
27
27
|
import { getAllQuestions, groupByChapter } from '../src/lib/questions';
|
|
28
28
|
import { assertKnownDomain } from '../src/lib/exam-domains';
|
|
29
|
+
import { normalizeBase } from '../src/lib/nav-href';
|
|
29
30
|
|
|
30
31
|
// Presence-gate: only touch the collection when the directory holds files.
|
|
31
32
|
const questionModules = import.meta.glob('/src/content/questions/**/*.{md,mdx}', {
|
|
@@ -51,7 +52,7 @@ const rendered = await Promise.all(
|
|
|
51
52
|
const byChapter = groupByChapter(rendered);
|
|
52
53
|
const total = rendered.length;
|
|
53
54
|
// #142: practice-exam + chapter links must prefix the deploy base.
|
|
54
|
-
const baseUrl = (import.meta.env.BASE_URL
|
|
55
|
+
const baseUrl = normalizeBase(import.meta.env.BASE_URL);
|
|
55
56
|
const practiceHref = (bookConfig.enabledRoutes ?? []).includes('practiceExam')
|
|
56
57
|
? `${baseUrl}practice-exam`
|
|
57
58
|
: null;
|
package/pages/chapters.astro
CHANGED
|
@@ -26,9 +26,10 @@ import { getAllChapters, type Chapter } from '../src/lib/chapters';
|
|
|
26
26
|
import { PROFILES } from '../src/profiles/index';
|
|
27
27
|
import { fallbackChaptersRenderer } from '../src/profiles/renderers/fallback-chapters';
|
|
28
28
|
import type { ChaptersRenderer, PartKey } from '../src/lib/chapters-renderer';
|
|
29
|
+
import { normalizeBase } from '../src/lib/nav-href';
|
|
29
30
|
|
|
30
31
|
// #142: prefix BASE_URL so chapter-card links stay inside a non-root deploy base.
|
|
31
|
-
const baseUrl = (import.meta.env.BASE_URL
|
|
32
|
+
const baseUrl = normalizeBase(import.meta.env.BASE_URL);
|
|
32
33
|
|
|
33
34
|
const profileName = (import.meta.env.BOOK_PROFILE ?? 'minimal') as keyof typeof PROFILES;
|
|
34
35
|
const profileDef = PROFILES[profileName];
|
package/pages/exercises.astro
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* page (doesn't fail the build).
|
|
11
11
|
*/
|
|
12
12
|
import Base from '../layouts/Base.astro';
|
|
13
|
+
import { normalizeBase } from '../src/lib/nav-href';
|
|
13
14
|
|
|
14
15
|
// v4.4.0 fix: use Vite's import.meta.glob with a project-root-relative
|
|
15
16
|
// path (same lesson as tips.astro). `/src/data/...` is consumer-project-
|
|
@@ -28,7 +29,7 @@ const chapters = Object.keys(byChapter).sort();
|
|
|
28
29
|
const total = chapters.reduce((sum, c) => sum + byChapter[c].length, 0);
|
|
29
30
|
|
|
30
31
|
// #142: chapter deep-links must prefix the deploy base.
|
|
31
|
-
const baseUrl = (import.meta.env.BASE_URL
|
|
32
|
+
const baseUrl = normalizeBase(import.meta.env.BASE_URL);
|
|
32
33
|
---
|
|
33
34
|
<Base title="Exercises" description="All exercises in this book, grouped by chapter with deep links.">
|
|
34
35
|
<article class="prose">
|
package/pages/index.astro
CHANGED
|
@@ -34,15 +34,16 @@
|
|
|
34
34
|
*/
|
|
35
35
|
import Base from '../layouts/Base.astro';
|
|
36
36
|
import bookConfig from 'virtual:book-scaffold/book-config';
|
|
37
|
+
import { normalizeBase } from '../src/lib/nav-href';
|
|
37
38
|
|
|
38
39
|
const title = bookConfig.title ?? 'book-scaffold-astro';
|
|
39
40
|
const description = bookConfig.description;
|
|
40
41
|
const portfolio = bookConfig.portfolio;
|
|
41
42
|
const enabledRoutes = bookConfig.enabledRoutes;
|
|
42
43
|
|
|
43
|
-
// Astro
|
|
44
|
-
//
|
|
45
|
-
const baseUrl = (import.meta.env.BASE_URL
|
|
44
|
+
// Astro accepts bases with or without a trailing slash. Route every value
|
|
45
|
+
// through the shared helper before composing links (#142, #182).
|
|
46
|
+
const baseUrl = normalizeBase(import.meta.env.BASE_URL);
|
|
46
47
|
|
|
47
48
|
// Map from internal route name → display label + URL. Only routes that
|
|
48
49
|
// produce a single landing-list entry are listed here (frontmatter is a
|