@refrakt-md/content 0.33.0 → 0.35.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.
Files changed (53) hide show
  1. package/dist/analyze.d.ts.map +1 -1
  2. package/dist/analyze.js.map +1 -1
  3. package/dist/content-tree.d.ts.map +1 -1
  4. package/dist/content-tree.js.map +1 -1
  5. package/dist/entity-routes.d.ts.map +1 -1
  6. package/dist/entity-routes.js +4 -1
  7. package/dist/entity-routes.js.map +1 -1
  8. package/dist/file-roots.d.ts.map +1 -1
  9. package/dist/file-roots.js.map +1 -1
  10. package/dist/format.d.ts.map +1 -1
  11. package/dist/format.js +3 -3
  12. package/dist/format.js.map +1 -1
  13. package/dist/frontmatter.d.ts.map +1 -1
  14. package/dist/frontmatter.js.map +1 -1
  15. package/dist/index.d.ts +6 -5
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +6 -5
  18. package/dist/index.js.map +1 -1
  19. package/dist/layout.d.ts.map +1 -1
  20. package/dist/layout.js +10 -4
  21. package/dist/layout.js.map +1 -1
  22. package/dist/navigation.d.ts.map +1 -1
  23. package/dist/navigation.js.map +1 -1
  24. package/dist/page-entities.d.ts.map +1 -1
  25. package/dist/page-entities.js.map +1 -1
  26. package/dist/pipeline.d.ts.map +1 -1
  27. package/dist/pipeline.js +8 -3
  28. package/dist/pipeline.js.map +1 -1
  29. package/dist/refract-loader.d.ts.map +1 -1
  30. package/dist/refract-loader.js +7 -6
  31. package/dist/refract-loader.js.map +1 -1
  32. package/dist/registry.d.ts.map +1 -1
  33. package/dist/registry.js +1 -1
  34. package/dist/registry.js.map +1 -1
  35. package/dist/router.d.ts.map +1 -1
  36. package/dist/router.js +4 -2
  37. package/dist/router.js.map +1 -1
  38. package/dist/site.d.ts.map +1 -1
  39. package/dist/site.js +134 -22
  40. package/dist/site.js.map +1 -1
  41. package/dist/sitemap.js +2 -2
  42. package/dist/sitemap.js.map +1 -1
  43. package/dist/timestamps.d.ts.map +1 -1
  44. package/dist/timestamps.js +16 -3
  45. package/dist/timestamps.js.map +1 -1
  46. package/dist/tint-cascade.js +1 -1
  47. package/dist/tint-cascade.js.map +1 -1
  48. package/dist/validate.d.ts +103 -0
  49. package/dist/validate.d.ts.map +1 -0
  50. package/dist/validate.js +154 -0
  51. package/dist/validate.js.map +1 -0
  52. package/frontmatter.schema.json +81 -81
  53. package/package.json +5 -5
@@ -0,0 +1,154 @@
1
+ import Markdoc from '@markdoc/markdoc';
2
+ /**
3
+ * Error ids reported out of the box — SPEC-132 phases 1 and 2.
4
+ *
5
+ * An explicit allow-list rather than a deny-list, so enabling an id is a
6
+ * deliberate act rather than a side effect of Markdoc adding one.
7
+ *
8
+ * `variable-undefined` is absent and must stay that way. Markdoc validates the
9
+ * *full path*, has no scope model, and most variable use in real content is
10
+ * scope-local — `$item` and `$row` bound per-iteration inside rune body
11
+ * templates, with valid paths depending on author-defined frontmatter. Enabling
12
+ * it flags every collection template. See SPEC-132 D4: it is out of scope
13
+ * entirely rather than deferred, and needs its own spec.
14
+ */
15
+ export const DEFAULT_VALIDATION_IDS = [
16
+ // Phase 1 (WORK-556). `tag-undefined` is the one the milestone is sold on:
17
+ // a mistyped rune drops its tag and renders its children as prose, so the
18
+ // block silently vanishes from the page.
19
+ 'tag-undefined',
20
+ 'attribute-undefined',
21
+ // Phase 2 (WORK-558), gated on WORK-557's blast-radius measurement. These
22
+ // are what make `required` and `matches` mean something across ~175 tags,
23
+ // and they are the ids that wake the custom attribute validators in
24
+ // `packages/runes/src/attributes.ts` and `plugins/media/src/attributes.ts`
25
+ // — which had never executed in a build, because `transform()` does not
26
+ // invoke a custom type's `validate()`.
27
+ 'attribute-value-invalid',
28
+ 'attribute-missing-required',
29
+ 'attribute-type-invalid',
30
+ ];
31
+ /** How a finding's Markdoc level maps onto `PipelineWarning.severity`.
32
+ *
33
+ * `critical` and `error` both land on `'error'` because `PipelineWarning` has
34
+ * no fifth level; the distinction that matters (D11) is not severity but
35
+ * suppressibility, which {@link isSuppressible} keeps separate. */
36
+ function severityFor(level) {
37
+ switch (level) {
38
+ case 'critical':
39
+ case 'error':
40
+ return 'error';
41
+ case 'warning':
42
+ return 'warning';
43
+ default:
44
+ return 'info';
45
+ }
46
+ }
47
+ /**
48
+ * Whether a finding may be silenced by configuration (SPEC-132 D5 / D11).
49
+ *
50
+ * `critical` means the document could not be *understood* — a parse error, an
51
+ * unclosed tag, a tag in an impossible position. That is not a matter of
52
+ * preference, so no config switch turns it off. Everything else is the
53
+ * configurable band.
54
+ */
55
+ export function isSuppressible(level) {
56
+ return level !== 'critical';
57
+ }
58
+ export const DEFAULT_VALIDATION_SETTINGS = { enabled: true };
59
+ /** Resolve the effective id allow-list for a site. */
60
+ export function resolveValidationIds(settings) {
61
+ const base = settings?.ids ?? DEFAULT_VALIDATION_IDS;
62
+ const disabled = new Set(settings?.disableIds ?? []);
63
+ return new Set(base.filter((id) => !disabled.has(id)));
64
+ }
65
+ /** Ids the site asked to stop treating as problems, as distinct from ids that
66
+ * were never enabled. The two are handled differently: a disabled id is
67
+ * demoted, an id outside the allow-list is dropped. */
68
+ function demotedIds(settings) {
69
+ return new Set(settings?.disableIds ?? []);
70
+ }
71
+ /**
72
+ * Validate one page's AST against the config its transform will use.
73
+ *
74
+ * Returns `PipelineWarning`s and nothing else — no node is injected into the
75
+ * page and no content is replaced (D9). A validation finding annotates content
76
+ * that rendered perfectly well, so it belongs beside the page, not in it;
77
+ * display is WORK-395's editor validation rail.
78
+ *
79
+ * Never throws: a validator that breaks the build it was added to protect is
80
+ * worse than no validator. A failure inside `Markdoc.validate` is itself
81
+ * reported as a finding.
82
+ *
83
+ * Three dispositions, and the difference between them is the point:
84
+ *
85
+ * | case | outcome |
86
+ * |---|---|
87
+ * | `critical` level | always reported; no configuration silences it (D11) |
88
+ * | id in `disableIds` | **demoted to `info`** |
89
+ * | id outside the allow-list, or `enabled: false` | dropped |
90
+ *
91
+ * Demotion rather than deletion is WORK-559's recorded decision. An id someone
92
+ * has actively judged unimportant should stop adding noise to the build
93
+ * summary, but it should not become invisible: a reader who goes looking — or
94
+ * the editor's validation rail — can still see what was set aside, and turning
95
+ * the id back on is then an informed choice rather than a leap. Ids that were
96
+ * never enabled are a different thing and are simply not reported; there is no
97
+ * value in an `info` stream of every check the project has decided against.
98
+ */
99
+ export function validatePage(ast, config, opts) {
100
+ const settings = opts.settings ?? DEFAULT_VALIDATION_SETTINGS;
101
+ const allowed = resolveValidationIds(settings);
102
+ const demoted = demotedIds(settings);
103
+ const pluginName = opts.contributedBy ?? 'core';
104
+ let findings;
105
+ try {
106
+ findings = Markdoc.validate(ast, config);
107
+ }
108
+ catch (err) {
109
+ return [
110
+ {
111
+ severity: 'warning',
112
+ phase: 'validate',
113
+ pluginName,
114
+ url: opts.url,
115
+ message: `content validation could not run: ${err.message}`,
116
+ },
117
+ ];
118
+ }
119
+ const out = [];
120
+ for (const finding of findings) {
121
+ const level = finding.error.level;
122
+ const id = finding.error.id;
123
+ let severity = severityFor(level);
124
+ // `critical` is reported unconditionally — neither the master switch, the
125
+ // allow-list, nor `disableIds` can touch it.
126
+ if (isSuppressible(level)) {
127
+ if (!settings.enabled)
128
+ continue;
129
+ if (demoted.has(id)) {
130
+ severity = 'info';
131
+ }
132
+ else if (!allowed.has(id)) {
133
+ continue;
134
+ }
135
+ }
136
+ out.push({
137
+ severity,
138
+ phase: 'validate',
139
+ pluginName,
140
+ url: opts.url,
141
+ message: formatFinding(finding, opts.contributedBy),
142
+ });
143
+ }
144
+ return out;
145
+ }
146
+ /** Render one finding as a single line: what is wrong, where, and — for a
147
+ * generated page — who generated it. */
148
+ function formatFinding(finding, contributedBy) {
149
+ const line = finding.lines?.[0];
150
+ const at = typeof line === 'number' ? ` (line ${line + 1})` : '';
151
+ const origin = contributedBy ? ` [generated by ${contributedBy}]` : '';
152
+ return `${finding.error.id}: ${finding.error.message}${at}${origin}`;
153
+ }
154
+ //# sourceMappingURL=validate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.js","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"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"}
@@ -1,83 +1,83 @@
1
1
  {
2
- "$schema": "http://json-schema.org/draft-07/schema#",
3
- "$id": "https://refrakt.md/frontmatter.schema.json",
4
- "title": "Frontmatter",
5
- "description": "YAML frontmatter for a refrakt content page. Describes the fields refrakt itself consumes; the set is deliberately open, since runes and pipeline hooks read arbitrary author fields. The unversioned URL https://refrakt.md/frontmatter.schema.json is a 'latest' alias that always serves the most recently published schema.",
6
- "type": "object",
7
- "properties": {
8
- "title": {
9
- "description": "Page title. Used for the document title, navigation labels and social cards; falls back to the page's first H1 when omitted.",
10
- "type": "string"
11
- },
12
- "description": {
13
- "description": "Short summary, used for the meta description, social cards and the blurb on link cards.",
14
- "type": "string"
15
- },
16
- "slug": {
17
- "description": "Override the URL segment derived from the filename.",
18
- "type": "string"
19
- },
20
- "draft": {
21
- "description": "Exclude the page from the built site and from navigation.",
22
- "type": "boolean"
23
- },
24
- "redirect": {
25
- "description": "Redirect this URL elsewhere instead of rendering a page.",
26
- "type": "string"
27
- },
28
- "order": {
29
- "description": "Sort position among sibling pages in generated navigation. Lower sorts first.",
30
- "type": "number"
31
- },
32
- "date": {
33
- "description": "Publication date (ISO 8601). Used for ordering and display by blog-style listings.",
34
- "type": "string"
35
- },
36
- "author": {
37
- "description": "Author name, used in article metadata and structured data.",
38
- "type": "string"
39
- },
40
- "tags": {
41
- "description": "Free-form labels, filterable with the `field:value` grammar `collection` and `aggregate` share.",
42
- "type": "array",
43
- "items": { "type": "string" }
44
- },
45
- "image": {
46
- "description": "Page image, used for social cards and as the media for link cards.",
47
- "type": "string"
48
- },
49
- "icon": {
50
- "description": "Icon name resolvable by the `icon` rune. `nav layout=\"cards\"` renders it on each child page's card.",
51
- "type": "string"
52
- },
53
- "tint": {
54
- "description": "Named tint preset for this page, or for the whole subtree when set in a `_layout.md`. Cascades down the layout chain; an explicit null resets to inherit from the layer above.",
55
- "type": ["string", "null"]
56
- },
57
- "tint-mode": {
58
- "description": "Initial colour-scheme behaviour. `auto` follows the user's preference; `light` / `dark` lock the server-rendered mode. Cascades down the layout chain.",
59
- "type": "string",
60
- "enum": ["auto", "light", "dark"]
61
- },
62
- "tint-lock": {
63
- "description": "Ignore the user's theme preference and hide the theme toggle — for surfaces that should look the same to every visitor. Cascades down the layout chain.",
64
- "type": "boolean"
65
- },
66
- "type": {
67
- "description": "Register this page as an entity of the named type, so `collection`, `aggregate` and `xref` can find it. This is what drives the entity registry. A `routeRules` entry can set it for a whole subtree instead of per page.",
68
- "type": "string"
69
- },
70
- "id": {
71
- "description": "Explicit entity ID for this page. Defaults to the page's slug.",
72
- "type": "string"
73
- },
74
- "created": {
75
- "description": "Creation date (ISO 8601), overriding what git history or the filesystem reports.",
76
- "type": "string"
77
- },
78
- "modified": {
79
- "description": "Last-modified date (ISO 8601), overriding what git history or the filesystem reports.",
80
- "type": "string"
81
- }
82
- }
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://refrakt.md/frontmatter.schema.json",
4
+ "title": "Frontmatter",
5
+ "description": "YAML frontmatter for a refrakt content page. Describes the fields refrakt itself consumes; the set is deliberately open, since runes and pipeline hooks read arbitrary author fields. The unversioned URL https://refrakt.md/frontmatter.schema.json is a 'latest' alias that always serves the most recently published schema.",
6
+ "type": "object",
7
+ "properties": {
8
+ "title": {
9
+ "description": "Page title. Used for the document title, navigation labels and social cards; falls back to the page's first H1 when omitted.",
10
+ "type": "string"
11
+ },
12
+ "description": {
13
+ "description": "Short summary, used for the meta description, social cards and the blurb on link cards.",
14
+ "type": "string"
15
+ },
16
+ "slug": {
17
+ "description": "Override the URL segment derived from the filename.",
18
+ "type": "string"
19
+ },
20
+ "draft": {
21
+ "description": "Exclude the page from the built site and from navigation.",
22
+ "type": "boolean"
23
+ },
24
+ "redirect": {
25
+ "description": "Redirect this URL elsewhere instead of rendering a page.",
26
+ "type": "string"
27
+ },
28
+ "order": {
29
+ "description": "Sort position among sibling pages in generated navigation. Lower sorts first.",
30
+ "type": "number"
31
+ },
32
+ "date": {
33
+ "description": "Publication date (ISO 8601). Used for ordering and display by blog-style listings.",
34
+ "type": "string"
35
+ },
36
+ "author": {
37
+ "description": "Author name, used in article metadata and structured data.",
38
+ "type": "string"
39
+ },
40
+ "tags": {
41
+ "description": "Free-form labels, filterable with the `field:value` grammar `collection` and `aggregate` share.",
42
+ "type": "array",
43
+ "items": { "type": "string" }
44
+ },
45
+ "image": {
46
+ "description": "Page image, used for social cards and as the media for link cards.",
47
+ "type": "string"
48
+ },
49
+ "icon": {
50
+ "description": "Icon name resolvable by the `icon` rune. `nav layout=\"cards\"` renders it on each child page's card.",
51
+ "type": "string"
52
+ },
53
+ "tint": {
54
+ "description": "Named tint preset for this page, or for the whole subtree when set in a `_layout.md`. Cascades down the layout chain; an explicit null resets to inherit from the layer above.",
55
+ "type": ["string", "null"]
56
+ },
57
+ "tint-mode": {
58
+ "description": "Initial colour-scheme behaviour. `auto` follows the user's preference; `light` / `dark` lock the server-rendered mode. Cascades down the layout chain.",
59
+ "type": "string",
60
+ "enum": ["auto", "light", "dark"]
61
+ },
62
+ "tint-lock": {
63
+ "description": "Ignore the user's theme preference and hide the theme toggle — for surfaces that should look the same to every visitor. Cascades down the layout chain.",
64
+ "type": "boolean"
65
+ },
66
+ "type": {
67
+ "description": "Register this page as an entity of the named type, so `collection`, `aggregate` and `xref` can find it. This is what drives the entity registry. A `routeRules` entry can set it for a whole subtree instead of per page.",
68
+ "type": "string"
69
+ },
70
+ "id": {
71
+ "description": "Explicit entity ID for this page. Defaults to the page's slug.",
72
+ "type": "string"
73
+ },
74
+ "created": {
75
+ "description": "Creation date (ISO 8601), overriding what git history or the filesystem reports.",
76
+ "type": "string"
77
+ },
78
+ "modified": {
79
+ "description": "Last-modified date (ISO 8601), overriding what git history or the filesystem reports.",
80
+ "type": "string"
81
+ }
82
+ }
83
83
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@refrakt-md/content",
3
3
  "description": "Content loading and transformation pipeline",
4
- "version": "0.33.0",
4
+ "version": "0.35.0",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -30,10 +30,10 @@
30
30
  "build": "tsc"
31
31
  },
32
32
  "dependencies": {
33
- "@refrakt-md/highlight": "0.33.0",
34
- "@refrakt-md/runes": "0.33.0",
35
- "@refrakt-md/transform": "0.33.0",
36
- "@refrakt-md/types": "0.33.0",
33
+ "@refrakt-md/highlight": "0.35.0",
34
+ "@refrakt-md/runes": "0.35.0",
35
+ "@refrakt-md/transform": "0.35.0",
36
+ "@refrakt-md/types": "0.35.0",
37
37
  "@markdoc/markdoc": "0.4.0",
38
38
  "yaml": "^2.4.0"
39
39
  }