explico 0.12.0 → 0.13.1
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 +1137 -1
- package/README.md +91 -13
- package/dist/compiler/astro.d.ts +4 -3
- package/dist/compiler/astro.js +45 -15
- package/dist/compiler/cli.d.ts +26 -12
- package/dist/compiler/cli.js +96 -25
- package/dist/compiler/collections.d.ts +9 -1
- package/dist/compiler/collections.js +55 -16
- 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 +14 -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 +87 -6
- package/dist/compiler/manifest.js +174 -31
- package/dist/compiler/render-diagnostics.d.ts +65 -0
- package/dist/compiler/render-diagnostics.js +56 -0
- package/dist/compiler/resolution-report.d.ts +2 -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 +2 -2
- package/dist/reference/base-operators.mjs +2 -2
- 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/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-part-pages.d.ts +1 -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-math-scope.d.ts +1 -0
- package/dist/reference/lab-math-scope.js +13 -2
- 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 +34 -4
- package/dist/reference/page-notation.js +54 -2
- package/dist/reference/references.d.ts +11 -1
- package/dist/reference/references.js +6 -0
- package/dist/reference/registry.js +104 -10
- 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 +84 -20
- package/dist/reference/render-lab-math.d.ts +2 -0
- package/dist/reference/render-lab-math.js +17 -5
- 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 +191 -30
- package/dist/rules/in-effect.d.ts +1 -1
- package/dist/rules/rules.schema.json +79 -24
- 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 +32 -21
- package/docs/authoring/agents.md +38 -7
- package/docs/authoring/components.md +111 -6
- package/docs/authoring/migrating.md +707 -0
- package/docs/authoring/notation.md +281 -29
- package/docs/authoring/retired.md +1 -0
- package/docs/authoring/structure-rules.md +10 -41
- package/docs/authoring/structure.md +53 -2
- package/docs/authoring/visualizations.md +144 -18
- package/package.json +12 -7
- package/src/compiler/astro.ts +57 -17
- package/src/compiler/cli.ts +143 -35
- package/src/compiler/collections.ts +92 -19
- 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 +299 -28
- package/src/compiler/render-diagnostics.ts +111 -0
- package/src/components/HoverPanel.astro +204 -6
- package/src/components/PartOverview.astro +12 -9
- 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/figure/Figure.astro +11 -0
- package/src/components/figure/FloatCaption.astro +4 -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 +321 -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 +47 -9
- 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 +13 -21
- 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 +2 -2
- 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/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-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-math-scope.ts +15 -1
- package/src/reference/math-glyphs.mjs +851 -46
- package/src/reference/mixed-content.ts +11 -2
- package/src/reference/page-notation.ts +100 -3
- package/src/reference/references.ts +14 -0
- package/src/reference/registry.ts +154 -8
- 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 +84 -20
- package/src/reference/render-lab-math.ts +29 -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 +205 -29
- package/src/rules/settings.ts +10 -0
- package/src/session/PreferenceStore.ts +53 -0
- package/src/styles/global.css +291 -75
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
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.
|
|
@@ -83,6 +84,13 @@ declare function loadNotationRegistryInput(): Promise<NotationRegistryInput>;
|
|
|
83
84
|
* Returned synchronously because the remark transform reads the loader inline.
|
|
84
85
|
*/
|
|
85
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[];
|
|
86
94
|
/** Parsed `src/content/sources/*.json` records for `remark-citation`. */
|
|
87
95
|
declare function loadSourceRecords(): Record<string, unknown>[];
|
|
88
96
|
/**
|
|
@@ -124,4 +132,4 @@ declare function notationSourceLocatorGaps(): {
|
|
|
124
132
|
total: number;
|
|
125
133
|
};
|
|
126
134
|
|
|
127
|
-
export { type NonChapterPage, type NotationSpec, type PartOverviewPage, extractNotationReferences, loadAbbreviationRegistryInput, loadCurriculumCatalog, loadLessonChecks, loadNonChapterPages, 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") {
|
|
@@ -225,7 +221,6 @@ function lessonEntries(prerequisitesById) {
|
|
|
225
221
|
const teaches = data.teaches ?? [];
|
|
226
222
|
lessons.push({
|
|
227
223
|
id: lessonIdFromSlug(slug),
|
|
228
|
-
status: data.editorialStatus ?? "draft",
|
|
229
224
|
requires: deriveRequires(teaches, prerequisitesById),
|
|
230
225
|
teaches,
|
|
231
226
|
assessments: lessonCheckIds(path),
|
|
@@ -280,6 +275,18 @@ function glossEntries(raw, ownerKey, at) {
|
|
|
280
275
|
}
|
|
281
276
|
return glosses;
|
|
282
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
|
+
}
|
|
283
290
|
function localDefinition(raw, file, index) {
|
|
284
291
|
const at = `${file} notation.local[${index}]`;
|
|
285
292
|
const data = record(raw, at);
|
|
@@ -288,7 +295,6 @@ function localDefinition(raw, file, index) {
|
|
|
288
295
|
const meaning = meaningText(data.meaning, `${at}.meaning`);
|
|
289
296
|
const formula = optionalString(data.formula, `${at}.formula`);
|
|
290
297
|
const units = optionalString(data.units, `${at}.units`);
|
|
291
|
-
const definitionText = [meaning, formula].filter((value) => value !== void 0).join("\n");
|
|
292
298
|
return {
|
|
293
299
|
key,
|
|
294
300
|
notation: latex,
|
|
@@ -297,11 +303,15 @@ function localDefinition(raw, file, index) {
|
|
|
297
303
|
sources: sourceIds(data.sources, `${at}.sources`),
|
|
298
304
|
seeAlso: strings(data.seeAlso, `${at}.seeAlso`),
|
|
299
305
|
alignment: data.alignment === void 0 ? { kind: "general", rationale: "Lesson-local symbol." } : alignment(data.alignment, `${at}.alignment`),
|
|
300
|
-
references:
|
|
306
|
+
references: [
|
|
307
|
+
...extractNotationReferences(meaning, file, "definition"),
|
|
308
|
+
...formula === void 0 ? [] : extractNotationReferences(formula, file, "formula")
|
|
309
|
+
],
|
|
301
310
|
glosses: glossEntries(data.glosses, key, `${at}.glosses`),
|
|
302
311
|
source: { file },
|
|
303
312
|
...formula === void 0 ? {} : { formula },
|
|
304
|
-
...units === void 0 ? {} : { units }
|
|
313
|
+
...units === void 0 ? {} : { units },
|
|
314
|
+
...indexFields(data, at, key, latex, formula)
|
|
305
315
|
};
|
|
306
316
|
}
|
|
307
317
|
function sharedNotationEntries() {
|
|
@@ -325,15 +335,16 @@ function sharedNotationEntries() {
|
|
|
325
335
|
domain: string(data.domain, `${file} domain`),
|
|
326
336
|
sources: sourceIds(data.sources, `${file} sources`),
|
|
327
337
|
seeAlso: strings(data.seeAlso, `${file} seeAlso`),
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
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`),
|
|
331
341
|
body,
|
|
332
342
|
references: extractNotationReferences(body, file, void 0),
|
|
333
343
|
glosses: glossEntries(data.glosses, key, `${file} glosses`),
|
|
334
344
|
source: { file },
|
|
335
345
|
...formula === void 0 ? {} : { formula },
|
|
336
|
-
...units === void 0 ? {} : { units }
|
|
346
|
+
...units === void 0 ? {} : { units },
|
|
347
|
+
...indexFields(data, file, key, string(data.latex, `${file} latex`), formula)
|
|
337
348
|
};
|
|
338
349
|
});
|
|
339
350
|
}
|
|
@@ -405,7 +416,6 @@ function notationLessonEntries() {
|
|
|
405
416
|
);
|
|
406
417
|
lessons.push({
|
|
407
418
|
lessonId: lessonIdFromSlug(slug),
|
|
408
|
-
status: status(data.editorialStatus, `${file} editorialStatus`),
|
|
409
419
|
localDefinitions,
|
|
410
420
|
suspended: notationSuspensions(notation.suspend, localDefinitions, file),
|
|
411
421
|
references: extractNotationReferences(body, file, void 0),
|
|
@@ -432,7 +442,6 @@ function loadNonChapterPages() {
|
|
|
432
442
|
bodyLine,
|
|
433
443
|
notation: {
|
|
434
444
|
lessonId: id,
|
|
435
|
-
status: status(data.editorialStatus ?? "draft", `${file} editorialStatus`),
|
|
436
445
|
localDefinitions: [],
|
|
437
446
|
suspended: [],
|
|
438
447
|
references: extractNotationReferences(body, file, void 0),
|
|
@@ -455,6 +464,35 @@ function loadNotationDefinitions() {
|
|
|
455
464
|
(path) => parseDocument(path).data
|
|
456
465
|
);
|
|
457
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
|
+
}
|
|
458
496
|
function loadSourceRecords() {
|
|
459
497
|
return jsonEntries(
|
|
460
498
|
join(CONTENT_ROOT, "sources")
|
|
@@ -536,6 +574,7 @@ function notationSourceLocatorGaps() {
|
|
|
536
574
|
export {
|
|
537
575
|
extractNotationReferences,
|
|
538
576
|
loadAbbreviationRegistryInput,
|
|
577
|
+
loadContentRecords,
|
|
539
578
|
loadCurriculumCatalog,
|
|
540
579
|
loadLessonChecks,
|
|
541
580
|
loadNonChapterPages,
|
|
@@ -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
|
+
};
|