explico 0.11.1 → 0.13.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/CHANGELOG.md +1292 -1
- package/README.md +91 -13
- package/dist/compiler/astro.d.ts +11 -4
- package/dist/compiler/astro.js +48 -15
- package/dist/compiler/cli.d.ts +27 -12
- package/dist/compiler/cli.js +96 -25
- package/dist/compiler/collections.d.ts +33 -2
- package/dist/compiler/collections.js +91 -19
- package/dist/compiler/content-schema.d.ts +31 -0
- package/dist/compiler/content-schema.js +82 -0
- package/dist/compiler/content-schemas.d.ts +422 -0
- package/dist/compiler/content-schemas.js +229 -0
- package/dist/compiler/course-config.d.ts +1 -1
- package/dist/compiler/course-config.js +30 -2
- package/dist/compiler/exports/combined-html.d.ts +97 -0
- package/dist/compiler/exports/combined-html.js +492 -0
- package/dist/compiler/exports/download-index.d.ts +9 -0
- package/dist/compiler/exports/download-index.js +33 -0
- package/dist/compiler/exports/export-chapters.d.ts +98 -0
- package/dist/compiler/exports/export-chapters.js +164 -0
- package/dist/compiler/exports/export-pages.d.ts +48 -0
- package/dist/compiler/exports/export-pages.js +22 -0
- package/dist/compiler/exports/html-tree.d.ts +22 -0
- package/dist/compiler/exports/html-tree.js +47 -0
- package/dist/compiler/exports/latex/capture.d.ts +106 -0
- package/dist/compiler/exports/latex/capture.js +435 -0
- package/dist/compiler/exports/latex/compile.d.ts +73 -0
- package/dist/compiler/exports/latex/compile.js +115 -0
- package/dist/compiler/exports/latex/document.d.ts +102 -0
- package/dist/compiler/exports/latex/document.js +349 -0
- package/dist/compiler/exports/latex/explico.cls +415 -0
- package/dist/compiler/exports/latex/numbering.d.ts +18 -0
- package/dist/compiler/exports/latex/numbering.js +57 -0
- package/dist/compiler/exports/latex/serialize.d.ts +112 -0
- package/dist/compiler/exports/latex/serialize.js +606 -0
- package/dist/compiler/exports/latex-export.d.ts +44 -0
- package/dist/compiler/exports/latex-export.js +169 -0
- package/dist/compiler/exports/markdown/serialize.d.ts +51 -0
- package/dist/compiler/exports/markdown/serialize.js +527 -0
- package/dist/compiler/exports/markdown-export.d.ts +39 -0
- package/dist/compiler/exports/markdown-export.js +240 -0
- package/dist/compiler/exports/standalone-html.d.ts +15 -5
- package/dist/compiler/exports/standalone-html.js +23 -51
- package/dist/compiler/exports/static-tree.d.ts +253 -0
- package/dist/compiler/exports/static-tree.js +625 -0
- package/dist/compiler/exports/zip.d.ts +16 -0
- package/dist/compiler/exports/zip.js +72 -0
- package/dist/compiler/lesson-derivation.d.ts +8 -2
- package/dist/compiler/lesson-derivation.js +6 -7
- package/dist/compiler/manifest.d.ts +115 -8
- package/dist/compiler/manifest.js +221 -39
- package/dist/compiler/render-diagnostics.d.ts +65 -0
- package/dist/compiler/render-diagnostics.js +56 -0
- package/dist/compiler/resolution-report.d.ts +3 -1
- package/dist/{course-config-Dsnri9Tk.d.ts → course-config-rRtME5UV.d.ts} +56 -13
- package/dist/curriculum/validation.d.ts +9 -3
- package/dist/curriculum/validation.js +35 -17
- package/dist/reference/base-abbreviations.d.mts +2 -2
- package/dist/reference/base-abbreviations.mjs +2 -2
- package/dist/reference/base-operators.d.mts +8 -2
- package/dist/reference/base-operators.mjs +8 -10
- package/dist/reference/base-roman-labels.d.mts +4 -3
- package/dist/reference/base-roman-labels.mjs +4 -3
- package/dist/reference/citation-format.d.mts +35 -0
- package/dist/reference/citation-format.mjs +74 -0
- package/dist/reference/consistency.d.ts +5 -9
- package/dist/reference/consistency.js +34 -38
- package/dist/reference/curriculum-alignment.js +3 -24
- package/dist/reference/derived-entries.d.ts +45 -0
- package/dist/reference/derived-entries.js +50 -0
- package/dist/reference/derived-notation.d.mts +125 -0
- package/dist/reference/derived-notation.mjs +121 -0
- package/dist/reference/diagram-fit.d.ts +45 -0
- package/dist/reference/diagram-fit.js +54 -0
- package/dist/reference/diagram-layout.d.ts +59 -10
- package/dist/reference/diagram-layout.js +85 -24
- package/dist/reference/diagrams-validate.d.ts +1 -1
- package/dist/reference/equations-validate.d.ts +1 -1
- package/dist/reference/equations-validate.js +26 -1
- package/dist/reference/equations.d.mts +27 -0
- package/dist/reference/equations.mjs +120 -22
- package/dist/reference/examples-validate.d.ts +1 -1
- package/dist/reference/figure-fit.d.ts +70 -0
- package/dist/reference/figure-fit.js +60 -0
- package/dist/reference/figure-pictures.d.ts +27 -0
- package/dist/reference/figure-pictures.js +49 -0
- package/dist/reference/figure-symbols.d.ts +32 -0
- package/dist/reference/figure-symbols.js +55 -0
- package/dist/reference/figures-validate.d.ts +1 -1
- package/dist/reference/gate-abbreviations.d.ts +1 -1
- package/dist/reference/gate-abbreviations.js +1 -1
- package/dist/reference/gate-course-rules.d.ts +4 -3
- package/dist/reference/gate-course-rules.js +9 -4
- package/dist/reference/gate-heading-length.d.ts +2 -0
- package/dist/reference/gate-heading-length.js +7 -5
- package/dist/reference/gate-heading-structure.js +26 -0
- package/dist/reference/gate-indices.d.ts +35 -0
- package/dist/reference/gate-indices.js +299 -0
- package/dist/reference/gate-math.d.ts +32 -2
- package/dist/reference/gate-math.js +68 -12
- package/dist/reference/gate-mdx-expression.d.ts +17 -0
- package/dist/reference/gate-mdx-expression.js +29 -0
- package/dist/reference/gate-non-chapter-pages.d.ts +39 -0
- package/dist/reference/gate-non-chapter-pages.js +156 -0
- package/dist/reference/gate-part-pages.d.ts +4 -2
- package/dist/reference/gate-part-pages.js +2 -1
- package/dist/reference/gate-prop-text.d.ts +28 -0
- package/dist/reference/gate-prop-text.js +106 -0
- package/dist/reference/gate-prose-notation.d.ts +3 -1
- package/dist/reference/gate-prose-notation.js +4 -2
- package/dist/reference/gate-retired-keys.d.ts +35 -0
- package/dist/reference/gate-retired-keys.js +58 -0
- package/dist/reference/gloss.d.ts +2 -0
- package/dist/reference/katex-measure.d.ts +17 -0
- package/dist/reference/katex-measure.js +223 -0
- package/dist/reference/keyed-float-validate.js +12 -0
- package/dist/reference/keyed-jsx-floats.d.mts +2 -2
- package/dist/reference/keyed-jsx-floats.mjs +11 -14
- package/dist/reference/lab-katex-validate.d.ts +43 -0
- package/dist/reference/lab-katex-validate.js +96 -0
- package/dist/reference/lab-math-scope.d.ts +1 -0
- package/dist/reference/lab-math-scope.js +13 -2
- package/dist/reference/lint-ignore.js +1 -1
- package/dist/reference/math-glyphs.d.mts +94 -2
- package/dist/reference/math-glyphs.mjs +851 -46
- package/dist/reference/mixed-content.d.ts +8 -3
- package/dist/reference/mixed-content.js +4 -2
- package/dist/reference/page-notation.d.ts +39 -7
- package/dist/reference/page-notation.js +62 -10
- package/dist/reference/raw-floats.mjs +37 -13
- package/dist/reference/references.d.ts +11 -1
- package/dist/reference/references.js +6 -0
- package/dist/reference/registry.js +115 -11
- package/dist/reference/rehype-fail-katex-errors.d.mts +9 -0
- package/dist/reference/rehype-fail-katex-errors.mjs +19 -0
- package/dist/reference/rehype-heading-anchors.mjs +11 -1
- package/dist/reference/remark-citation.mjs +0 -0
- package/dist/reference/remark-notation.mjs +275 -23
- package/dist/reference/render-lab-math.d.ts +13 -2
- package/dist/reference/render-lab-math.js +18 -6
- package/dist/reference/sections-validate.d.ts +28 -3
- package/dist/reference/sections-validate.js +31 -4
- package/dist/reference/sections.d.mts +24 -6
- package/dist/reference/sections.mjs +92 -17
- package/dist/reference/tables-validate.d.ts +1 -1
- package/dist/reference/tables-validate.js +24 -1
- package/dist/reference/text-measure.d.ts +10 -0
- package/dist/reference/text-measure.js +41 -0
- package/dist/reference/text-widths.d.ts +10 -0
- package/dist/reference/text-widths.js +1238 -0
- package/dist/reference/types.d.ts +64 -8
- package/dist/rules/catalog.js +223 -32
- package/dist/rules/in-effect.d.ts +1 -1
- package/dist/rules/rules.schema.json +100 -27
- package/dist/rules/settings.d.ts +1 -1
- package/dist/rules/settings.js +7 -0
- package/dist/session/PreferenceStore.d.ts +18 -1
- package/dist/session/PreferenceStore.js +29 -1
- package/docs/authoring/README.md +33 -21
- package/docs/authoring/agents.md +38 -7
- package/docs/authoring/components.md +115 -10
- package/docs/authoring/migrating.md +878 -0
- package/docs/authoring/notation.md +285 -30
- package/docs/authoring/retired.md +1 -0
- package/docs/authoring/structure-rules.md +84 -45
- package/docs/authoring/structure.md +67 -2
- package/docs/authoring/visualizations.md +167 -25
- package/package.json +12 -7
- package/src/compiler/astro.ts +72 -21
- package/src/compiler/cli.ts +143 -35
- package/src/compiler/collections.ts +158 -27
- package/src/compiler/content-schema.ts +108 -0
- package/src/compiler/content-schemas.ts +391 -0
- package/src/compiler/course-config.ts +86 -15
- package/src/compiler/exports/combined-html.ts +669 -0
- package/src/compiler/exports/download-index.ts +36 -0
- package/src/compiler/exports/export-chapters.ts +233 -0
- package/src/compiler/exports/export-pages.ts +44 -0
- package/src/compiler/exports/html-tree.ts +62 -0
- package/src/compiler/exports/latex/capture.ts +651 -0
- package/src/compiler/exports/latex/compile.ts +168 -0
- package/src/compiler/exports/latex/document.ts +479 -0
- package/src/compiler/exports/latex/explico.cls +415 -0
- package/src/compiler/exports/latex/numbering.ts +74 -0
- package/src/compiler/exports/latex/serialize.ts +787 -0
- package/src/compiler/exports/latex/texlive-packages.txt +76 -0
- package/src/compiler/exports/latex-export.ts +255 -0
- package/src/compiler/exports/markdown/serialize.ts +632 -0
- package/src/compiler/exports/markdown-export.ts +329 -0
- package/src/compiler/exports/standalone-html.ts +39 -70
- package/src/compiler/exports/static-tree.ts +944 -0
- package/src/compiler/exports/zip.ts +88 -0
- package/src/compiler/lesson-derivation.ts +13 -8
- package/src/compiler/manifest.ts +387 -40
- package/src/compiler/render-diagnostics.ts +111 -0
- package/src/components/CourseContents.astro +6 -0
- package/src/components/HoverPanel.astro +204 -6
- package/src/components/PartOverview.astro +18 -9
- package/src/components/abbreviation/AbbreviationGlossary.astro +6 -2
- package/src/components/abbreviation/AbbreviationLayer.astro +21 -12
- package/src/components/assessments/AssessmentRunner.tsx +7 -1
- package/src/components/assessments/AssessmentSet.astro +14 -1
- package/src/components/block-heading.ts +38 -0
- package/src/components/card-arrow.ts +18 -4
- package/src/components/card-carry.ts +406 -0
- package/src/components/card-drag.ts +1 -30
- package/src/components/card-fetch.ts +87 -0
- package/src/components/card-manager.ts +1222 -38
- package/src/components/card-memory.ts +5 -1
- package/src/components/card-placement.ts +211 -13
- package/src/components/card-side.ts +281 -31
- package/src/components/citation/CitationLayer.astro +45 -40
- package/src/components/diagram/Diagram.astro +123 -44
- package/src/components/disclosure/print-as-shown.ts +42 -6
- package/src/components/figure/Figure.astro +17 -1
- package/src/components/figure/FloatCaption.astro +4 -17
- package/src/components/figure/figure-state.ts +26 -17
- package/src/components/float-toolbar.ts +274 -0
- package/src/components/memo/Memo.astro +21 -10
- package/src/components/memo/ShortNote.astro +4 -0
- package/src/components/notation/NotationGlossary.astro +22 -17
- package/src/components/notation/NotationLayer.astro +300 -79
- package/src/components/notation/NotationMath.astro +8 -1
- package/src/components/notation/render-notation-math.ts +51 -5
- package/src/components/readable-text.ts +25 -0
- package/src/components/reference/FloatRefLayer.astro +162 -89
- package/src/components/starlight/HeadingAnchorEnhancer.astro +13 -8
- package/src/components/starlight/KeyedFloatEnhancer.astro +28 -9
- package/src/components/starlight/LayoutPageSidebar.astro +17 -3
- package/src/components/starlight/LayoutSocialIcons.astro +79 -29
- package/src/components/starlight/LessonExportActions.astro +384 -310
- package/src/components/starlight/LessonFooter.astro +66 -8
- package/src/components/starlight/PrintController.astro +28 -0
- package/src/components/starlight/TextSizeControl.astro +137 -0
- package/src/components/starlight/print-controller.ts +380 -0
- package/src/components/starlight/section-summary.ts +13 -5
- package/src/components/table/Table.astro +14 -22
- package/src/components/table/table-truncation.ts +15 -0
- package/src/content-config.ts +20 -312
- package/src/curriculum/validation.ts +72 -24
- package/src/reference/base-abbreviations.mjs +2 -2
- package/src/reference/base-operators.mjs +8 -10
- package/src/reference/base-roman-labels.mjs +4 -3
- package/src/reference/citation-format.mjs +74 -0
- package/src/reference/consistency.ts +47 -61
- package/src/reference/curriculum-alignment.ts +4 -41
- package/src/reference/derived-entries.ts +68 -0
- package/src/reference/derived-notation.mjs +121 -0
- package/src/reference/diagram-fit.ts +104 -0
- package/src/reference/diagram-layout.ts +182 -51
- package/src/reference/diagrams-validate.ts +2 -1
- package/src/reference/equations-validate.ts +41 -2
- package/src/reference/equations.mjs +120 -22
- package/src/reference/examples-validate.ts +1 -0
- package/src/reference/figure-fit.ts +139 -0
- package/src/reference/figure-pictures.ts +96 -0
- package/src/reference/figure-symbols.ts +117 -0
- package/src/reference/figures-validate.ts +2 -1
- package/src/reference/gate-abbreviations.ts +1 -1
- package/src/reference/gate-course-rules.ts +22 -7
- package/src/reference/gate-heading-length.ts +25 -7
- package/src/reference/gate-heading-structure.ts +40 -1
- package/src/reference/gate-indices.ts +470 -0
- package/src/reference/gate-math.ts +102 -13
- package/src/reference/gate-mdx-expression.ts +45 -0
- package/src/reference/gate-non-chapter-pages.ts +241 -0
- package/src/reference/gate-part-pages.ts +1 -1
- package/src/reference/gate-prop-text.ts +145 -0
- package/src/reference/gate-prose-notation.ts +19 -5
- package/src/reference/gate-retired-keys.ts +101 -0
- package/src/reference/gloss.ts +2 -0
- package/src/reference/katex-measure.ts +384 -0
- package/src/reference/keyed-float-validate.ts +22 -0
- package/src/reference/keyed-jsx-floats.mjs +11 -14
- package/src/reference/lab-katex-validate.ts +156 -0
- package/src/reference/lab-math-scope.ts +15 -1
- package/src/reference/lint-ignore.ts +4 -1
- package/src/reference/math-glyphs.mjs +851 -46
- package/src/reference/mixed-content.ts +11 -2
- package/src/reference/page-notation.ts +114 -14
- package/src/reference/raw-floats.mjs +37 -13
- package/src/reference/references.ts +14 -0
- package/src/reference/registry.ts +170 -9
- package/src/reference/rehype-fail-katex-errors.mjs +19 -0
- package/src/reference/rehype-heading-anchors.mjs +11 -1
- package/src/reference/remark-citation.mjs +0 -0
- package/src/reference/remark-notation.mjs +275 -23
- package/src/reference/render-lab-math.ts +40 -5
- package/src/reference/sections-validate.ts +67 -3
- package/src/reference/sections.mjs +92 -17
- package/src/reference/tables-validate.ts +40 -2
- package/src/reference/text-measure.ts +63 -0
- package/src/reference/text-widths.ts +133 -0
- package/src/reference/types.ts +75 -8
- package/src/rules/catalog.ts +241 -31
- package/src/rules/settings.ts +10 -0
- package/src/session/PreferenceStore.ts +53 -0
- package/src/styles/global.css +322 -83
package/dist/compiler/cli.js
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
parseFrontmatter
|
|
8
8
|
} from "@astrojs/markdown-remark";
|
|
9
9
|
import rehypeKatex from "rehype-katex";
|
|
10
|
+
import remarkDirective from "remark-directive";
|
|
10
11
|
import remarkMath from "remark-math";
|
|
11
12
|
import remarkMdx from "remark-mdx";
|
|
12
13
|
import { createNotationKatexOptions } from "../reference/katex-options.mjs";
|
|
@@ -17,12 +18,14 @@ import remarkNotation, {
|
|
|
17
18
|
createRemarkNotationOptions
|
|
18
19
|
} from "../reference/remark-notation.mjs";
|
|
19
20
|
import remarkShortNote from "../reference/remark-short-note.mjs";
|
|
21
|
+
import { readRenderDiagnostics } from "./render-diagnostics.js";
|
|
20
22
|
import { isLessonSlug, lessonIdFromSlug } from "./lesson-derivation.js";
|
|
21
23
|
import { isCourseRuleId, ownerOf, ELEMENTS } from "../rules/catalog.js";
|
|
22
24
|
import {
|
|
23
25
|
renderRulesInEffect,
|
|
24
26
|
rulesInEffect
|
|
25
27
|
} from "../rules/in-effect.js";
|
|
28
|
+
import { RESERVED_KEY_PREFIXES, reservedKeyPrefix } from "../reference/references.js";
|
|
26
29
|
const REPOSITORY_ROOT = process.cwd();
|
|
27
30
|
const CONTENT_ROOT = join(REPOSITORY_ROOT, "content");
|
|
28
31
|
const DOCS_ROOT = join(CONTENT_ROOT, "docs");
|
|
@@ -47,13 +50,26 @@ function ownerFields(code) {
|
|
|
47
50
|
function withOwner(diagnostic) {
|
|
48
51
|
return { ...diagnostic, ...ownerFields(diagnostic.code) };
|
|
49
52
|
}
|
|
53
|
+
function renderedCliDiagnostic(diagnostic) {
|
|
54
|
+
return {
|
|
55
|
+
code: `render:${diagnostic.code}`,
|
|
56
|
+
...ownerFields(diagnostic.code),
|
|
57
|
+
severity: diagnosticSeverity(diagnostic.severity),
|
|
58
|
+
message: diagnostic.message,
|
|
59
|
+
file: diagnostic.file
|
|
60
|
+
};
|
|
61
|
+
}
|
|
50
62
|
function mentions(message, lessonId, file) {
|
|
51
63
|
return message.includes(lessonId) || message.includes(file) || message.includes(file.replace(/^content\/docs\//, ""));
|
|
52
64
|
}
|
|
53
|
-
function lessonDiagnostics(manifest, lesson) {
|
|
65
|
+
function lessonDiagnostics(manifest, lesson, rendered = readRenderDiagnostics()) {
|
|
54
66
|
const out = [];
|
|
55
67
|
const { file, id } = lesson;
|
|
56
68
|
const assessments = new Set(lesson.assessments);
|
|
69
|
+
const bundle = new Set(lesson.notation.definitionIds);
|
|
70
|
+
const entryFiles = new Set(
|
|
71
|
+
manifest.notation.definitions.filter((definition) => definition.scope.kind === "shared" && bundle.has(definition.id)).map((definition) => definition.source.file)
|
|
72
|
+
);
|
|
57
73
|
const withFile = (value) => value === void 0 ? {} : { file: value };
|
|
58
74
|
for (const issue of manifest.diagnostics.curriculum) {
|
|
59
75
|
if (!mentions(issue.message, id, file)) continue;
|
|
@@ -69,7 +85,7 @@ function lessonDiagnostics(manifest, lesson) {
|
|
|
69
85
|
]) {
|
|
70
86
|
const [group, entry] = diagnostic;
|
|
71
87
|
const entryFile = entry.source?.file;
|
|
72
|
-
if (entry.lessonId !== id && entryFile !== file && !mentions(entry.message, id, file)) {
|
|
88
|
+
if (entry.lessonId !== id && entryFile !== file && !(entryFile !== void 0 && entryFiles.has(entryFile)) && !mentions(entry.message, id, file)) {
|
|
73
89
|
continue;
|
|
74
90
|
}
|
|
75
91
|
out.push({
|
|
@@ -80,7 +96,7 @@ function lessonDiagnostics(manifest, lesson) {
|
|
|
80
96
|
});
|
|
81
97
|
}
|
|
82
98
|
for (const diagnostic of manifest.diagnostics.math) {
|
|
83
|
-
const forLesson = diagnostic.file === file || diagnostic.context === "assessment" && [...assessments].some(
|
|
99
|
+
const forLesson = diagnostic.file === file || entryFiles.has(diagnostic.file) || diagnostic.context === "assessment" && [...assessments].some(
|
|
84
100
|
(assessmentId) => diagnostic.file.startsWith(`${assessmentId} `)
|
|
85
101
|
);
|
|
86
102
|
if (!forLesson) continue;
|
|
@@ -133,6 +149,10 @@ function lessonDiagnostics(manifest, lesson) {
|
|
|
133
149
|
...withFile(diagnostic.file)
|
|
134
150
|
});
|
|
135
151
|
}
|
|
152
|
+
for (const diagnostic of rendered) {
|
|
153
|
+
if (diagnostic.file !== file && diagnostic.lessonId !== id) continue;
|
|
154
|
+
out.push(renderedCliDiagnostic(diagnostic));
|
|
155
|
+
}
|
|
136
156
|
return out.map(withOwner).sort(
|
|
137
157
|
(a, b) => a.code.localeCompare(b.code) || a.message.localeCompare(b.message)
|
|
138
158
|
);
|
|
@@ -148,6 +168,11 @@ function processorFor(manifest) {
|
|
|
148
168
|
// as the real Astro/MDX build, so `remarkNotation` sees the same tree
|
|
149
169
|
// shape here that it does there (D7/F-tables-figures parity).
|
|
150
170
|
remarkMdx,
|
|
171
|
+
// Starlight parses `:::note` asides as directives in the real build
|
|
172
|
+
// (on every page, not only its own `src/content/docs/`); without this
|
|
173
|
+
// an aside's body read as top-level lesson flow here, so its equation
|
|
174
|
+
// took a number the build does not give it.
|
|
175
|
+
remarkDirective,
|
|
151
176
|
remarkMath,
|
|
152
177
|
remarkShortNote,
|
|
153
178
|
remarkMemo,
|
|
@@ -171,7 +196,7 @@ function processorFor(manifest) {
|
|
|
171
196
|
return processorPromise;
|
|
172
197
|
}
|
|
173
198
|
function stripEsm(body) {
|
|
174
|
-
return body.replace(/^(?:import|export)\s[^\n]
|
|
199
|
+
return body.replace(/^(?:import|export)\s[^\n]*/gm, "");
|
|
175
200
|
}
|
|
176
201
|
function renderErrorCode(message) {
|
|
177
202
|
if (/Unresolved notation/i.test(message)) return "render:notation";
|
|
@@ -195,21 +220,50 @@ async function renderLesson(manifest, input) {
|
|
|
195
220
|
relative(DOCS_ROOT, input.path).replace(/\\/g, "/")
|
|
196
221
|
);
|
|
197
222
|
try {
|
|
198
|
-
await
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
223
|
+
await withoutParseLogs(
|
|
224
|
+
() => processor.render(stripEsm(input.body), {
|
|
225
|
+
frontmatter: { ...input.frontmatter, lessonId },
|
|
226
|
+
fileURL: pathToFileURL(input.path)
|
|
227
|
+
})
|
|
228
|
+
);
|
|
202
229
|
return { rendered: true, diagnostics: [] };
|
|
203
230
|
} catch (error) {
|
|
204
231
|
const message = error instanceof Error ? error.message : String(error);
|
|
232
|
+
const { line, column } = error ?? {};
|
|
205
233
|
return {
|
|
206
234
|
rendered: false,
|
|
207
235
|
diagnostics: [
|
|
208
|
-
{
|
|
236
|
+
{
|
|
237
|
+
code: renderErrorCode(message),
|
|
238
|
+
severity: "error",
|
|
239
|
+
message,
|
|
240
|
+
...typeof line === "number" ? { line } : {},
|
|
241
|
+
...typeof column === "number" ? { column } : {}
|
|
242
|
+
}
|
|
209
243
|
]
|
|
210
244
|
};
|
|
211
245
|
}
|
|
212
246
|
}
|
|
247
|
+
let quietRenders = 0;
|
|
248
|
+
let consoleError;
|
|
249
|
+
async function withoutParseLogs(work) {
|
|
250
|
+
if (quietRenders++ === 0) {
|
|
251
|
+
const original = console.error;
|
|
252
|
+
consoleError = original;
|
|
253
|
+
console.error = (...args) => {
|
|
254
|
+
if (args[0] instanceof Error && args[0].message.startsWith("Failed to parse Markdown file")) return;
|
|
255
|
+
original(...args);
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
try {
|
|
259
|
+
return await work();
|
|
260
|
+
} finally {
|
|
261
|
+
if (--quietRenders === 0 && consoleError !== void 0) {
|
|
262
|
+
console.error = consoleError;
|
|
263
|
+
consoleError = void 0;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
213
267
|
function estimateTokens(text) {
|
|
214
268
|
return Math.ceil(text.length / 4);
|
|
215
269
|
}
|
|
@@ -272,6 +326,13 @@ function lessonRaw(lesson) {
|
|
|
272
326
|
body: parsed.content.replace(/^\n/, "")
|
|
273
327
|
};
|
|
274
328
|
}
|
|
329
|
+
function lessonSource(text) {
|
|
330
|
+
const parsed = parseFrontmatter(text, { frontmatter: "empty-with-spaces" });
|
|
331
|
+
return {
|
|
332
|
+
frontmatter: parsed.frontmatter ?? {},
|
|
333
|
+
body: parsed.content
|
|
334
|
+
};
|
|
335
|
+
}
|
|
275
336
|
const DOMAIN_DIRS_BY_SECTION = {
|
|
276
337
|
foundations: [".", "rates", "valuation"],
|
|
277
338
|
bonds: ["bonds", "rates"],
|
|
@@ -317,9 +378,7 @@ function domainSignaturesForSection(section) {
|
|
|
317
378
|
signatures: signaturesInFile(path)
|
|
318
379
|
})).filter((entry) => entry.signatures.length > 0);
|
|
319
380
|
}
|
|
320
|
-
function contentStatus(manifest) {
|
|
321
|
-
const draftLessons = manifest.lessons.filter((lesson) => lesson.status === "draft").map((lesson) => lesson.id).sort();
|
|
322
|
-
const draftNotation = manifest.notation.definitions.filter((definition) => definition.status === "draft").map((definition) => definition.id).sort();
|
|
381
|
+
function contentStatus(manifest, rendered = readRenderDiagnostics()) {
|
|
323
382
|
const orphans = manifest.notation.diagnostics.filter((diagnostic) => diagnostic.code === "unused-definition").map((diagnostic) => ({
|
|
324
383
|
code: `notation:${diagnostic.code}`,
|
|
325
384
|
...ownerFields(diagnostic.code),
|
|
@@ -343,7 +402,8 @@ function contentStatus(manifest) {
|
|
|
343
402
|
...manifest.diagnostics.consistency.map((d) => d.severity),
|
|
344
403
|
...manifest.diagnostics.knowledge.map((d) => d.severity),
|
|
345
404
|
...manifest.diagnostics.config.map((d) => d.severity),
|
|
346
|
-
...manifest.diagnostics.waivers.map((d) => d.severity)
|
|
405
|
+
...manifest.diagnostics.waivers.map((d) => d.severity),
|
|
406
|
+
...rendered.map((d) => diagnosticSeverity(d.severity))
|
|
347
407
|
];
|
|
348
408
|
const keyedEquations = manifest.lessons.reduce(
|
|
349
409
|
(count, lesson) => count + lesson.equations.length,
|
|
@@ -383,7 +443,6 @@ function contentStatus(manifest) {
|
|
|
383
443
|
keyedDiagrams,
|
|
384
444
|
keyedExamples
|
|
385
445
|
},
|
|
386
|
-
draftDebt: { lessons: draftLessons, notation: draftNotation },
|
|
387
446
|
orphans,
|
|
388
447
|
diagnostics: {
|
|
389
448
|
errors: allDiagnostics.filter((severity) => severity === "error").length,
|
|
@@ -405,7 +464,7 @@ const SEVERITY_RANK = {
|
|
|
405
464
|
warning: 1,
|
|
406
465
|
error: 2
|
|
407
466
|
};
|
|
408
|
-
function contentDiagnostics(manifest, minimumSeverity = "info") {
|
|
467
|
+
function contentDiagnostics(manifest, minimumSeverity = "info", rendered = readRenderDiagnostics()) {
|
|
409
468
|
const lessonFileByHint = /* @__PURE__ */ new Map();
|
|
410
469
|
for (const lesson of manifest.lessons) {
|
|
411
470
|
lessonFileByHint.set(lesson.id, lesson.file);
|
|
@@ -439,6 +498,10 @@ function contentDiagnostics(manifest, minimumSeverity = "info") {
|
|
|
439
498
|
});
|
|
440
499
|
}
|
|
441
500
|
}
|
|
501
|
+
for (const diagnostic of rendered) {
|
|
502
|
+
const cli = renderedCliDiagnostic(diagnostic);
|
|
503
|
+
if (SEVERITY_RANK[cli.severity] >= SEVERITY_RANK[minimumSeverity]) diagnostics.push(cli);
|
|
504
|
+
}
|
|
442
505
|
diagnostics.sort(
|
|
443
506
|
(left, right) => (left.file ?? "").localeCompare(right.file ?? "") || (left.line ?? 0) - (right.line ?? 0) || (left.column ?? 0) - (right.column ?? 0) || left.code.localeCompare(right.code) || left.message.localeCompare(right.message)
|
|
444
507
|
);
|
|
@@ -448,8 +511,8 @@ function contentDiagnostics(manifest, minimumSeverity = "info") {
|
|
|
448
511
|
diagnostics
|
|
449
512
|
};
|
|
450
513
|
}
|
|
451
|
-
function contentWarnings(manifest) {
|
|
452
|
-
const warnings = contentDiagnostics(manifest, "warning").diagnostics.filter(
|
|
514
|
+
function contentWarnings(manifest, rendered = readRenderDiagnostics()) {
|
|
515
|
+
const warnings = contentDiagnostics(manifest, "warning", rendered).diagnostics.filter(
|
|
453
516
|
(diagnostic) => diagnostic.severity === "warning"
|
|
454
517
|
);
|
|
455
518
|
return { count: warnings.length, warnings };
|
|
@@ -515,7 +578,6 @@ function contentContext(manifest, rawId) {
|
|
|
515
578
|
file: lesson.file,
|
|
516
579
|
title: lesson.title,
|
|
517
580
|
description: lesson.description,
|
|
518
|
-
status: lesson.status,
|
|
519
581
|
teaches: lesson.teaches,
|
|
520
582
|
requires: lesson.requires,
|
|
521
583
|
assumptions: lesson.assumptions,
|
|
@@ -561,7 +623,9 @@ function contentContext(manifest, rawId) {
|
|
|
561
623
|
}
|
|
562
624
|
async function checkOneLesson(manifest, lesson) {
|
|
563
625
|
const manifestDiagnostics = lessonDiagnostics(manifest, lesson);
|
|
564
|
-
const { frontmatter, body } =
|
|
626
|
+
const { frontmatter, body } = lessonSource(
|
|
627
|
+
readFileSync(lessonAbsolutePath(lesson), "utf8")
|
|
628
|
+
);
|
|
565
629
|
const render = await renderLesson(manifest, {
|
|
566
630
|
body,
|
|
567
631
|
frontmatter,
|
|
@@ -611,17 +675,18 @@ ${untracked}`;
|
|
|
611
675
|
}
|
|
612
676
|
async function contentCheck(manifest, options) {
|
|
613
677
|
let targets;
|
|
678
|
+
const named = [...options.lesson ? [options.lesson] : [], ...options.lessons ?? []];
|
|
614
679
|
if (options.changed) {
|
|
615
680
|
const ids = changedLessonIds();
|
|
616
681
|
targets = manifest.lessons.filter((lesson) => ids.includes(lesson.id));
|
|
617
682
|
} else {
|
|
618
|
-
if (
|
|
683
|
+
if (named.length === 0) {
|
|
619
684
|
throw new ContentCliError(
|
|
620
685
|
"usage",
|
|
621
686
|
"content check needs a <lesson> id or --changed."
|
|
622
687
|
);
|
|
623
688
|
}
|
|
624
|
-
targets =
|
|
689
|
+
targets = named.map((id) => findLesson(manifest, id));
|
|
625
690
|
}
|
|
626
691
|
const lessons = await Promise.all(
|
|
627
692
|
targets.map((lesson) => checkOneLesson(manifest, lesson))
|
|
@@ -655,7 +720,6 @@ function scaffoldLesson(rawId) {
|
|
|
655
720
|
const contents = `---
|
|
656
721
|
title: ${title}
|
|
657
722
|
description: One sentence on what the reader can do after this lesson.
|
|
658
|
-
editorialStatus: draft
|
|
659
723
|
teaches: []
|
|
660
724
|
assumptions: []
|
|
661
725
|
---
|
|
@@ -698,6 +762,13 @@ function scaffoldTerm(rawKey) {
|
|
|
698
762
|
`A notation key is lower-case, dot/hyphen separated, got ${JSON.stringify(rawKey)}.`
|
|
699
763
|
);
|
|
700
764
|
}
|
|
765
|
+
const reserved = reservedKeyPrefix(key);
|
|
766
|
+
if (reserved !== void 0) {
|
|
767
|
+
throw new ContentCliError(
|
|
768
|
+
"invalid-id",
|
|
769
|
+
`A notation key may not start with ${RESERVED_KEY_PREFIXES.join(", ")}: [[${key}]] would be read as a reference to that kind of float. Got ${JSON.stringify(rawKey)}.`
|
|
770
|
+
);
|
|
771
|
+
}
|
|
701
772
|
const relPath = `content/notation/${key}.md`;
|
|
702
773
|
if (existsSync(join(REPOSITORY_ROOT, relPath))) {
|
|
703
774
|
throw new ContentCliError(
|
|
@@ -715,13 +786,12 @@ sources: []
|
|
|
715
786
|
seeAlso: []
|
|
716
787
|
alignment:
|
|
717
788
|
kind: general
|
|
718
|
-
rationale: Draft scaffold \u2014 set a real alignment
|
|
719
|
-
editorialStatus: draft
|
|
789
|
+
rationale: Draft scaffold \u2014 set a real alignment.
|
|
720
790
|
aiAssisted: true
|
|
721
791
|
---
|
|
722
792
|
|
|
723
793
|
Draft definition for [[${key}]]. Replace this body and the placeholder
|
|
724
|
-
frontmatter
|
|
794
|
+
frontmatter.
|
|
725
795
|
`;
|
|
726
796
|
return { path: relPath, contents, companions: [] };
|
|
727
797
|
}
|
|
@@ -737,6 +807,7 @@ export {
|
|
|
737
807
|
estimateTokens,
|
|
738
808
|
findLesson,
|
|
739
809
|
lessonDiagnostics,
|
|
810
|
+
lessonSource,
|
|
740
811
|
normalizeLessonId,
|
|
741
812
|
parseRulesFilter,
|
|
742
813
|
renderLesson,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { CurriculumCatalog } from '../curriculum/validation.js';
|
|
2
|
-
import { NotationReferenceInput, AbbreviationDefinitionInput, NotationRegistryInput, LocalNotationDefinitionInput, NotationSuspension } from '../reference/types.js';
|
|
2
|
+
import { NotationLessonInput, NotationReferenceInput, AbbreviationDefinitionInput, NotationRegistryInput, LocalNotationDefinitionInput, NotationSuspension } from '../reference/types.js';
|
|
3
|
+
import { ContentRecord } from '../reference/gate-retired-keys.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* The single path from content files to typed data.
|
|
@@ -54,12 +55,42 @@ declare function loadAbbreviationRegistryInput(): Promise<readonly AbbreviationD
|
|
|
54
55
|
* decoration (the page would contradict itself) each fail the load.
|
|
55
56
|
*/
|
|
56
57
|
declare function notationSuspensions(raw: unknown, localDefinitions: readonly LocalNotationDefinitionInput[], file: string): NotationSuspension[];
|
|
58
|
+
/**
|
|
59
|
+
* A docs page that is **not a chapter** (rule S10): the landing page, a part's
|
|
60
|
+
* overview page, and any other page outside a part directory — every slug
|
|
61
|
+
* `isLessonSlug` rejects. Such a page may explain its symbols like a chapter,
|
|
62
|
+
* but it recalls them only: it takes no `notation.local` and no float numbers.
|
|
63
|
+
*/
|
|
64
|
+
interface NonChapterPage {
|
|
65
|
+
/** `lessonIdFromSlug(slug)` — `index`, `motion.index`, `glossary`. Never a lesson id. */
|
|
66
|
+
readonly id: string;
|
|
67
|
+
/** Docs slug without extension, e.g. `motion/index`. */
|
|
68
|
+
readonly slug: string;
|
|
69
|
+
readonly file: string;
|
|
70
|
+
readonly data: Record<string, unknown>;
|
|
71
|
+
readonly body: string;
|
|
72
|
+
readonly bodyLine: number;
|
|
73
|
+
/**
|
|
74
|
+
* The page as notation input — no local entries, no suspensions — so the
|
|
75
|
+
* registry resolves its `[[key]]` references and the text and math gates
|
|
76
|
+
* scan it exactly as they scan a chapter.
|
|
77
|
+
*/
|
|
78
|
+
readonly notation: NotationLessonInput;
|
|
79
|
+
}
|
|
80
|
+
declare function loadNonChapterPages(): NonChapterPage[];
|
|
57
81
|
declare function loadNotationRegistryInput(): Promise<NotationRegistryInput>;
|
|
58
82
|
/**
|
|
59
83
|
* Notation frontmatter objects for `remark-notation`'s `definitions` option.
|
|
60
84
|
* Returned synchronously because the remark transform reads the loader inline.
|
|
61
85
|
*/
|
|
62
86
|
declare function loadNotationDefinitions(): Record<string, unknown>[];
|
|
87
|
+
/**
|
|
88
|
+
* Every content file's top-level fields and text: the frontmatter of a page,
|
|
89
|
+
* a notation entry or an abbreviation, and the object of a competency,
|
|
90
|
+
* assessment, source or track. The typed loaders above drop keys they do not
|
|
91
|
+
* know; the retired-key gate (`reference/gate-retired-keys.ts`) needs them.
|
|
92
|
+
*/
|
|
93
|
+
declare function loadContentRecords(): ContentRecord[];
|
|
63
94
|
/** Parsed `src/content/sources/*.json` records for `remark-citation`. */
|
|
64
95
|
declare function loadSourceRecords(): Record<string, unknown>[];
|
|
65
96
|
/**
|
|
@@ -101,4 +132,4 @@ declare function notationSourceLocatorGaps(): {
|
|
|
101
132
|
total: number;
|
|
102
133
|
};
|
|
103
134
|
|
|
104
|
-
export { type NotationSpec, type PartOverviewPage, extractNotationReferences, loadAbbreviationRegistryInput, loadCurriculumCatalog, loadLessonChecks, loadNotationDefinitions, loadNotationRegistryInput, loadNotationSpecs, loadPartOverviews, loadSourceRecords, notationSourceLocatorGaps, notationSuspensions };
|
|
135
|
+
export { type NonChapterPage, type NotationSpec, type PartOverviewPage, extractNotationReferences, loadAbbreviationRegistryInput, loadContentRecords, loadCurriculumCatalog, loadLessonChecks, loadNonChapterPages, loadNotationDefinitions, loadNotationRegistryInput, loadNotationSpecs, loadPartOverviews, loadSourceRecords, notationSourceLocatorGaps, notationSuspensions };
|
|
@@ -13,6 +13,8 @@ import { parseMemoRef } from "../reference/remark-memo.mjs";
|
|
|
13
13
|
import { parseSectionRef } from "../reference/sections.mjs";
|
|
14
14
|
import { resolveLabel } from "../reference/label.js";
|
|
15
15
|
import { resolveGlosses } from "../reference/gloss.js";
|
|
16
|
+
import { indexEntryFormula } from "../reference/derived-notation.mjs";
|
|
17
|
+
import { isIndexEntryLatex } from "./content-schemas.js";
|
|
16
18
|
import {
|
|
17
19
|
deriveRequires,
|
|
18
20
|
deriveSources,
|
|
@@ -100,12 +102,6 @@ function boolean(value, label) {
|
|
|
100
102
|
}
|
|
101
103
|
return value;
|
|
102
104
|
}
|
|
103
|
-
function status(value, label) {
|
|
104
|
-
if (value !== "draft" && value !== "in-review" && value !== "reviewed") {
|
|
105
|
-
throw new TypeError(`${label} must be draft, in-review, or reviewed`);
|
|
106
|
-
}
|
|
107
|
-
return value;
|
|
108
|
-
}
|
|
109
105
|
function alignment(value, label) {
|
|
110
106
|
const data = record(value, label);
|
|
111
107
|
if (data.kind === "general") {
|
|
@@ -139,16 +135,20 @@ function sourceSpan(file, text, index) {
|
|
|
139
135
|
column: (lines.at(-1)?.length ?? 0) + 1
|
|
140
136
|
};
|
|
141
137
|
}
|
|
142
|
-
function maskMarkdownLiterals(text) {
|
|
138
|
+
function maskMarkdownLiterals(text, mdx) {
|
|
143
139
|
const mask = (value) => value.replace(/[^\n]/g, " ");
|
|
144
|
-
|
|
140
|
+
const fenced = text.replace(
|
|
145
141
|
/(^|\n)[ \t]{0,3}(`{3,}|~{3,})[^\n]*\n[\s\S]*?(?:\n[ \t]{0,3}\2[^\n]*(?=\n|$)|$)/g,
|
|
146
142
|
mask
|
|
147
|
-
)
|
|
143
|
+
);
|
|
144
|
+
return (mdx ? fenced : fenced.replace(/^(?: {4}|\t).*$/gm, mask)).replace(
|
|
145
|
+
/(`+)(?!`)([^\n]*?)\1/g,
|
|
146
|
+
mask
|
|
147
|
+
);
|
|
148
148
|
}
|
|
149
149
|
function extractNotationReferences(text, file, kind) {
|
|
150
150
|
const references = [];
|
|
151
|
-
const searchable = maskMarkdownLiterals(text);
|
|
151
|
+
const searchable = maskMarkdownLiterals(text, file.endsWith(".mdx"));
|
|
152
152
|
const patterns = [
|
|
153
153
|
[/\[\[\s*([^\][]+?)\s*\]\]/g, kind ?? "prose"],
|
|
154
154
|
[/\\explain\{([^{}]+)\}/g, kind ?? "math"]
|
|
@@ -221,7 +221,6 @@ function lessonEntries(prerequisitesById) {
|
|
|
221
221
|
const teaches = data.teaches ?? [];
|
|
222
222
|
lessons.push({
|
|
223
223
|
id: lessonIdFromSlug(slug),
|
|
224
|
-
status: data.editorialStatus ?? "draft",
|
|
225
224
|
requires: deriveRequires(teaches, prerequisitesById),
|
|
226
225
|
teaches,
|
|
227
226
|
assessments: lessonCheckIds(path),
|
|
@@ -276,6 +275,18 @@ function glossEntries(raw, ownerKey, at) {
|
|
|
276
275
|
}
|
|
277
276
|
return glosses;
|
|
278
277
|
}
|
|
278
|
+
function indexFields(data, at, key, latex, formula) {
|
|
279
|
+
const reads = optionalString(data.reads, `${at}.reads`);
|
|
280
|
+
const range = optionalString(data.range, `${at}.range`);
|
|
281
|
+
const fixedIndex = data.fixedIndex === void 0 ? false : boolean(data.fixedIndex, `${at}.fixedIndex`);
|
|
282
|
+
const template = formula !== void 0 && isIndexEntryLatex(latex) ? formula : void 0;
|
|
283
|
+
return {
|
|
284
|
+
...reads === void 0 ? {} : { reads },
|
|
285
|
+
...range === void 0 ? {} : { range },
|
|
286
|
+
...fixedIndex ? { fixedIndex: true } : {},
|
|
287
|
+
...template === void 0 ? {} : { formula: indexEntryFormula(template, key), formulaTemplate: template }
|
|
288
|
+
};
|
|
289
|
+
}
|
|
279
290
|
function localDefinition(raw, file, index) {
|
|
280
291
|
const at = `${file} notation.local[${index}]`;
|
|
281
292
|
const data = record(raw, at);
|
|
@@ -284,7 +295,6 @@ function localDefinition(raw, file, index) {
|
|
|
284
295
|
const meaning = meaningText(data.meaning, `${at}.meaning`);
|
|
285
296
|
const formula = optionalString(data.formula, `${at}.formula`);
|
|
286
297
|
const units = optionalString(data.units, `${at}.units`);
|
|
287
|
-
const definitionText = [meaning, formula].filter((value) => value !== void 0).join("\n");
|
|
288
298
|
return {
|
|
289
299
|
key,
|
|
290
300
|
notation: latex,
|
|
@@ -293,11 +303,15 @@ function localDefinition(raw, file, index) {
|
|
|
293
303
|
sources: sourceIds(data.sources, `${at}.sources`),
|
|
294
304
|
seeAlso: strings(data.seeAlso, `${at}.seeAlso`),
|
|
295
305
|
alignment: data.alignment === void 0 ? { kind: "general", rationale: "Lesson-local symbol." } : alignment(data.alignment, `${at}.alignment`),
|
|
296
|
-
references:
|
|
306
|
+
references: [
|
|
307
|
+
...extractNotationReferences(meaning, file, "definition"),
|
|
308
|
+
...formula === void 0 ? [] : extractNotationReferences(formula, file, "formula")
|
|
309
|
+
],
|
|
297
310
|
glosses: glossEntries(data.glosses, key, `${at}.glosses`),
|
|
298
311
|
source: { file },
|
|
299
312
|
...formula === void 0 ? {} : { formula },
|
|
300
|
-
...units === void 0 ? {} : { units }
|
|
313
|
+
...units === void 0 ? {} : { units },
|
|
314
|
+
...indexFields(data, at, key, latex, formula)
|
|
301
315
|
};
|
|
302
316
|
}
|
|
303
317
|
function sharedNotationEntries() {
|
|
@@ -321,15 +335,16 @@ function sharedNotationEntries() {
|
|
|
321
335
|
domain: string(data.domain, `${file} domain`),
|
|
322
336
|
sources: sourceIds(data.sources, `${file} sources`),
|
|
323
337
|
seeAlso: strings(data.seeAlso, `${file} seeAlso`),
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
aiAssisted: boolean(data.aiAssisted, `${file} aiAssisted`),
|
|
338
|
+
// The collection schema's defaults (`compiler/content-schemas.ts`).
|
|
339
|
+
alignment: data.alignment === void 0 ? { kind: "general", rationale: "General notation." } : alignment(data.alignment, `${file} alignment`),
|
|
340
|
+
aiAssisted: data.aiAssisted === void 0 ? false : boolean(data.aiAssisted, `${file} aiAssisted`),
|
|
327
341
|
body,
|
|
328
342
|
references: extractNotationReferences(body, file, void 0),
|
|
329
343
|
glosses: glossEntries(data.glosses, key, `${file} glosses`),
|
|
330
344
|
source: { file },
|
|
331
345
|
...formula === void 0 ? {} : { formula },
|
|
332
|
-
...units === void 0 ? {} : { units }
|
|
346
|
+
...units === void 0 ? {} : { units },
|
|
347
|
+
...indexFields(data, file, key, string(data.latex, `${file} latex`), formula)
|
|
333
348
|
};
|
|
334
349
|
});
|
|
335
350
|
}
|
|
@@ -401,7 +416,6 @@ function notationLessonEntries() {
|
|
|
401
416
|
);
|
|
402
417
|
lessons.push({
|
|
403
418
|
lessonId: lessonIdFromSlug(slug),
|
|
404
|
-
status: status(data.editorialStatus, `${file} editorialStatus`),
|
|
405
419
|
localDefinitions,
|
|
406
420
|
suspended: notationSuspensions(notation.suspend, localDefinitions, file),
|
|
407
421
|
references: extractNotationReferences(body, file, void 0),
|
|
@@ -412,6 +426,33 @@ function notationLessonEntries() {
|
|
|
412
426
|
}
|
|
413
427
|
return lessons;
|
|
414
428
|
}
|
|
429
|
+
function loadNonChapterPages() {
|
|
430
|
+
const pages = [];
|
|
431
|
+
for (const path of filesBelow(join(CONTENT_ROOT, "docs"), [".md", ".mdx"])) {
|
|
432
|
+
const slug = docSlug(path);
|
|
433
|
+
if (isLessonSlug(slug)) continue;
|
|
434
|
+
const { file, data, body, bodyLine } = parseDocument(path);
|
|
435
|
+
const id = lessonIdFromSlug(slug);
|
|
436
|
+
pages.push({
|
|
437
|
+
id,
|
|
438
|
+
slug: slug.replace(/\.(md|mdx)$/, ""),
|
|
439
|
+
file,
|
|
440
|
+
data,
|
|
441
|
+
body,
|
|
442
|
+
bodyLine,
|
|
443
|
+
notation: {
|
|
444
|
+
lessonId: id,
|
|
445
|
+
localDefinitions: [],
|
|
446
|
+
suspended: [],
|
|
447
|
+
references: extractNotationReferences(body, file, void 0),
|
|
448
|
+
body,
|
|
449
|
+
bodyLine,
|
|
450
|
+
source: { file }
|
|
451
|
+
}
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
return pages;
|
|
455
|
+
}
|
|
415
456
|
async function loadNotationRegistryInput() {
|
|
416
457
|
return {
|
|
417
458
|
sharedDefinitions: sharedNotationEntries(),
|
|
@@ -423,6 +464,35 @@ function loadNotationDefinitions() {
|
|
|
423
464
|
(path) => parseDocument(path).data
|
|
424
465
|
);
|
|
425
466
|
}
|
|
467
|
+
function loadContentRecords() {
|
|
468
|
+
const below = (directory, extensions) => {
|
|
469
|
+
const path = join(CONTENT_ROOT, directory);
|
|
470
|
+
return existsSync(path) ? filesBelow(path, extensions) : [];
|
|
471
|
+
};
|
|
472
|
+
const records = [];
|
|
473
|
+
for (const directory of ["docs", "notation", "abbreviations"]) {
|
|
474
|
+
for (const path of below(directory, [".md", ".mdx"])) {
|
|
475
|
+
const text = readFileSync(path, "utf8");
|
|
476
|
+
records.push({
|
|
477
|
+
file: relative(REPOSITORY_ROOT, path),
|
|
478
|
+
data: parseFrontmatter(text).frontmatter ?? {},
|
|
479
|
+
text
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
for (const directory of ["competencies", "assessments", "sources", "tracks"]) {
|
|
484
|
+
for (const path of below(directory, [".json"])) {
|
|
485
|
+
const text = readFileSync(path, "utf8");
|
|
486
|
+
const data = JSON.parse(text);
|
|
487
|
+
records.push({
|
|
488
|
+
file: relative(REPOSITORY_ROOT, path),
|
|
489
|
+
data: data !== null && typeof data === "object" && !Array.isArray(data) ? data : {},
|
|
490
|
+
text
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
return records;
|
|
495
|
+
}
|
|
426
496
|
function loadSourceRecords() {
|
|
427
497
|
return jsonEntries(
|
|
428
498
|
join(CONTENT_ROOT, "sources")
|
|
@@ -504,8 +574,10 @@ function notationSourceLocatorGaps() {
|
|
|
504
574
|
export {
|
|
505
575
|
extractNotationReferences,
|
|
506
576
|
loadAbbreviationRegistryInput,
|
|
577
|
+
loadContentRecords,
|
|
507
578
|
loadCurriculumCatalog,
|
|
508
579
|
loadLessonChecks,
|
|
580
|
+
loadNonChapterPages,
|
|
509
581
|
loadNotationDefinitions,
|
|
510
582
|
loadNotationRegistryInput,
|
|
511
583
|
loadNotationSpecs,
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { CurriculumIssue } from '../curriculum/validation.js';
|
|
2
|
+
import { ContentRecord } from '../reference/gate-retired-keys.js';
|
|
3
|
+
import { CONTENT_SCHEMAS } from './content-schemas.js';
|
|
4
|
+
import 'astro/zod';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The content-schema check (invariant `content-schema`). Every content file
|
|
8
|
+
* must satisfy its collection's schema — the same zod schema `astro build`
|
|
9
|
+
* loads it with (`content-schemas.ts`) — so `pnpm validate:content` and
|
|
10
|
+
* `pnpm content check` reject what the build would, at the key's line,
|
|
11
|
+
* instead of passing it and leaving `astro build` to fail with
|
|
12
|
+
* `InvalidContentEntryDataError`.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
type ContentCollection = keyof typeof CONTENT_SCHEMAS;
|
|
16
|
+
/**
|
|
17
|
+
* The collection a repository-relative content file belongs to, or `null`
|
|
18
|
+
* for one Astro's loaders skip (`src/content-config.ts`): a docs page whose
|
|
19
|
+
* name starts with `_`, a notation or abbreviation file that is not `.md`.
|
|
20
|
+
*/
|
|
21
|
+
declare function collectionOf(file: string): ContentCollection | null;
|
|
22
|
+
/**
|
|
23
|
+
* The 1-based line of the value at `path` in a YAML or JSON `text`: each name
|
|
24
|
+
* is searched for after the previous one, and an array index skips that many
|
|
25
|
+
* `- ` items of a YAML list. Falls back to the deepest line found.
|
|
26
|
+
*/
|
|
27
|
+
declare function lineOfPath(text: string, path: readonly (string | number)[]): number | undefined;
|
|
28
|
+
/** One `content-schema` issue per schema problem in each record, in record order. */
|
|
29
|
+
declare function validateContentSchemas(records: readonly ContentRecord[]): CurriculumIssue[];
|
|
30
|
+
|
|
31
|
+
export { type ContentCollection, collectionOf, lineOfPath, validateContentSchemas };
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { keyLine } from "../reference/gate-retired-keys.js";
|
|
2
|
+
import { CONTENT_SCHEMAS } from "./content-schemas.js";
|
|
3
|
+
function collectionOf(file) {
|
|
4
|
+
const parts = file.replace(/\\/g, "/").split("/");
|
|
5
|
+
if (parts[0] !== "content" || parts.length < 3) return null;
|
|
6
|
+
const directory = parts[1] ?? "";
|
|
7
|
+
const name = parts[parts.length - 1] ?? "";
|
|
8
|
+
if (!Object.hasOwn(CONTENT_SCHEMAS, directory)) return null;
|
|
9
|
+
const collection = directory;
|
|
10
|
+
if (collection === "docs") {
|
|
11
|
+
return /\.mdx?$/.test(name) && !name.startsWith("_") ? collection : null;
|
|
12
|
+
}
|
|
13
|
+
if (collection === "notation" || collection === "abbreviations") {
|
|
14
|
+
return name.endsWith(".md") ? collection : null;
|
|
15
|
+
}
|
|
16
|
+
return name.endsWith(".json") ? collection : null;
|
|
17
|
+
}
|
|
18
|
+
function lineOfPath(text, path) {
|
|
19
|
+
const first = path[0];
|
|
20
|
+
if (typeof first !== "string") return void 0;
|
|
21
|
+
const start = keyLine(text, first);
|
|
22
|
+
if (start === void 0) return void 0;
|
|
23
|
+
const lines = text.split("\n");
|
|
24
|
+
let at = start - 1;
|
|
25
|
+
for (const segment of path.slice(1)) {
|
|
26
|
+
if (typeof segment === "number") {
|
|
27
|
+
let seen = -1;
|
|
28
|
+
let found2 = -1;
|
|
29
|
+
for (let index = at + 1; index < lines.length; index += 1) {
|
|
30
|
+
if (/^\s*-\s/.test(lines[index] ?? "")) seen += 1;
|
|
31
|
+
if (seen === segment) {
|
|
32
|
+
found2 = index;
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (found2 === -1) break;
|
|
37
|
+
at = found2;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const name = segment.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
41
|
+
const pattern = new RegExp(`^[\\s-]*(?:"${name}"|${name})[ \\t]*:`);
|
|
42
|
+
let found = -1;
|
|
43
|
+
for (let index = at; index < lines.length; index += 1) {
|
|
44
|
+
if (pattern.test(lines[index] ?? "")) {
|
|
45
|
+
found = index;
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (found === -1) break;
|
|
50
|
+
at = found;
|
|
51
|
+
}
|
|
52
|
+
return at + 1;
|
|
53
|
+
}
|
|
54
|
+
function validateContentSchemas(records) {
|
|
55
|
+
const issues = [];
|
|
56
|
+
for (const { file, data, text } of records) {
|
|
57
|
+
const collection = collectionOf(file);
|
|
58
|
+
if (!collection) continue;
|
|
59
|
+
const result = CONTENT_SCHEMAS[collection].safeParse(data);
|
|
60
|
+
if (result.success) continue;
|
|
61
|
+
for (const problem of result.error.issues) {
|
|
62
|
+
const path = problem.path.filter(
|
|
63
|
+
(segment) => typeof segment === "string" || typeof segment === "number"
|
|
64
|
+
);
|
|
65
|
+
const line = lineOfPath(text, path);
|
|
66
|
+
const at = line === void 0 ? file : `${file}:${line}`;
|
|
67
|
+
const field = path.length > 0 ? `${path.join(".")}: ` : "";
|
|
68
|
+
issues.push({
|
|
69
|
+
kind: "content-schema",
|
|
70
|
+
message: `${at}: ${field}${problem.message} (the ${collection} collection's schema, which astro build applies too)`,
|
|
71
|
+
file,
|
|
72
|
+
...line === void 0 ? {} : { line }
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return issues;
|
|
77
|
+
}
|
|
78
|
+
export {
|
|
79
|
+
collectionOf,
|
|
80
|
+
lineOfPath,
|
|
81
|
+
validateContentSchemas
|
|
82
|
+
};
|