@glw907/cairn-cms 0.11.0 → 0.17.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/dist/auth/crypto.d.ts +8 -2
- package/dist/auth/crypto.d.ts.map +1 -1
- package/dist/auth/crypto.js +12 -2
- package/dist/auth/store.d.ts +2 -0
- package/dist/auth/store.d.ts.map +1 -1
- package/dist/auth/store.js +17 -5
- package/dist/components/ComponentForm.svelte +33 -10
- package/dist/components/ComponentForm.svelte.d.ts.map +1 -1
- package/dist/components/EditPage.svelte +4 -6
- package/dist/components/EditPage.svelte.d.ts +1 -1
- package/dist/components/EditPage.svelte.d.ts.map +1 -1
- package/dist/components/IconPicker.svelte +53 -7
- package/dist/components/IconPicker.svelte.d.ts +7 -3
- package/dist/components/IconPicker.svelte.d.ts.map +1 -1
- package/dist/content/adapter.d.ts +4 -0
- package/dist/content/adapter.d.ts.map +1 -0
- package/dist/content/adapter.js +4 -0
- package/dist/content/concepts.js +2 -2
- package/dist/content/schema.d.ts +75 -0
- package/dist/content/schema.d.ts.map +1 -0
- package/dist/content/schema.js +72 -0
- package/dist/content/types.d.ts +30 -7
- package/dist/content/types.d.ts.map +1 -1
- package/dist/content/validate.d.ts +5 -3
- package/dist/content/validate.d.ts.map +1 -1
- package/dist/content/validate.js +14 -7
- package/dist/delivery/content-index.d.ts +8 -0
- package/dist/delivery/content-index.d.ts.map +1 -1
- package/dist/delivery/content-index.js +23 -12
- package/dist/delivery/feeds.d.ts +1 -1
- package/dist/delivery/feeds.d.ts.map +1 -1
- package/dist/delivery/feeds.js +31 -16
- package/dist/delivery/index.d.ts +5 -1
- package/dist/delivery/index.d.ts.map +1 -1
- package/dist/delivery/index.js +2 -0
- package/dist/delivery/seo-fields.d.ts +22 -0
- package/dist/delivery/seo-fields.d.ts.map +1 -0
- package/dist/delivery/seo-fields.js +32 -0
- package/dist/delivery/site-index.d.ts +2 -2
- package/dist/delivery/site-index.d.ts.map +1 -1
- package/dist/delivery/site-index.js +16 -18
- package/dist/delivery/site-indexes.d.ts +26 -0
- package/dist/delivery/site-indexes.d.ts.map +1 -0
- package/dist/delivery/site-indexes.js +30 -0
- package/dist/env.d.ts.map +1 -1
- package/dist/env.js +14 -0
- package/dist/github/signing.d.ts +12 -0
- package/dist/github/signing.d.ts.map +1 -1
- package/dist/github/signing.js +22 -0
- package/dist/index.d.ts +9 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/render/component-grammar.d.ts +7 -0
- package/dist/render/component-grammar.d.ts.map +1 -1
- package/dist/render/component-grammar.js +27 -8
- package/dist/render/component-validate.js +3 -3
- package/dist/render/glyph.d.ts +4 -1
- package/dist/render/glyph.d.ts.map +1 -1
- package/dist/render/glyph.js +6 -2
- package/dist/render/pipeline.d.ts +10 -0
- package/dist/render/pipeline.d.ts.map +1 -1
- package/dist/render/pipeline.js +15 -1
- package/dist/render/registry.d.ts +23 -5
- package/dist/render/registry.d.ts.map +1 -1
- package/dist/render/registry.js +6 -0
- package/dist/render/rehype-dispatch.d.ts +1 -5
- package/dist/render/rehype-dispatch.d.ts.map +1 -1
- package/dist/render/rehype-dispatch.js +71 -19
- package/dist/render/remark-directives.d.ts +1 -1
- package/dist/render/remark-directives.d.ts.map +1 -1
- package/dist/render/remark-directives.js +37 -0
- package/dist/render/sanitize-schema.d.ts +20 -0
- package/dist/render/sanitize-schema.d.ts.map +1 -0
- package/dist/render/sanitize-schema.js +48 -0
- package/dist/sveltekit/auth-routes.d.ts.map +1 -1
- package/dist/sveltekit/auth-routes.js +29 -11
- package/dist/sveltekit/content-routes.js +2 -2
- package/dist/sveltekit/guard.d.ts +1 -1
- package/dist/sveltekit/guard.d.ts.map +1 -1
- package/dist/sveltekit/guard.js +25 -10
- package/dist/sveltekit/nav-routes.js +2 -2
- package/dist/sveltekit/public-routes.d.ts +3 -0
- package/dist/sveltekit/public-routes.d.ts.map +1 -1
- package/dist/sveltekit/public-routes.js +10 -3
- package/dist/sveltekit/types.d.ts +6 -0
- package/dist/sveltekit/types.d.ts.map +1 -1
- package/package.json +3 -2
- package/src/lib/auth/crypto.ts +14 -2
- package/src/lib/auth/store.ts +18 -5
- package/src/lib/components/ComponentForm.svelte +33 -10
- package/src/lib/components/EditPage.svelte +4 -6
- package/src/lib/components/IconPicker.svelte +53 -7
- package/src/lib/content/adapter.ts +10 -0
- package/src/lib/content/concepts.ts +2 -2
- package/src/lib/content/schema.ts +133 -0
- package/src/lib/content/types.ts +30 -7
- package/src/lib/content/validate.ts +10 -7
- package/src/lib/delivery/content-index.ts +32 -12
- package/src/lib/delivery/feeds.ts +34 -19
- package/src/lib/delivery/index.ts +5 -1
- package/src/lib/delivery/seo-fields.ts +43 -0
- package/src/lib/delivery/site-index.ts +15 -16
- package/src/lib/delivery/site-indexes.ts +64 -0
- package/src/lib/env.ts +13 -0
- package/src/lib/github/signing.ts +32 -0
- package/src/lib/index.ts +8 -2
- package/src/lib/render/component-grammar.ts +34 -10
- package/src/lib/render/component-validate.ts +3 -3
- package/src/lib/render/glyph.ts +6 -2
- package/src/lib/render/pipeline.ts +25 -1
- package/src/lib/render/registry.ts +27 -5
- package/src/lib/render/rehype-dispatch.ts +67 -20
- package/src/lib/render/remark-directives.ts +39 -1
- package/src/lib/render/sanitize-schema.ts +57 -0
- package/src/lib/sveltekit/auth-routes.ts +30 -11
- package/src/lib/sveltekit/content-routes.ts +2 -2
- package/src/lib/sveltekit/guard.ts +25 -10
- package/src/lib/sveltekit/nav-routes.ts +2 -2
- package/src/lib/sveltekit/public-routes.ts +13 -3
- package/src/lib/sveltekit/types.ts +5 -1
- package/dist/render/sanitize.d.ts +0 -8
- package/dist/render/sanitize.d.ts.map +0 -1
- package/dist/render/sanitize.js +0 -26
- package/src/lib/render/sanitize.ts +0 -27
package/dist/content/types.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ComponentRegistry } from '../render/registry.js';
|
|
2
2
|
import type { IconSet } from '../render/glyph.js';
|
|
3
3
|
import type { DatePrefix } from './ids.js';
|
|
4
|
+
import type { ConceptSchema } from './schema.js';
|
|
4
5
|
/** Common to every frontmatter field: the frontmatter key, the form label, and whether it is required. */
|
|
5
6
|
interface FieldBase {
|
|
6
7
|
/** Frontmatter key and form input name. */
|
|
@@ -13,16 +14,37 @@ interface FieldBase {
|
|
|
13
14
|
/** A single-line text input. */
|
|
14
15
|
export interface TextField extends FieldBase {
|
|
15
16
|
type: 'text';
|
|
17
|
+
/** Minimum character length of a non-empty value. */
|
|
18
|
+
min?: number;
|
|
19
|
+
/** Maximum character length. */
|
|
20
|
+
max?: number;
|
|
21
|
+
/** Exact required character length. */
|
|
22
|
+
length?: number;
|
|
23
|
+
/** A regular-expression source string the value must match. Stored as a string so the field
|
|
24
|
+
* list stays plain serializable data; the validator compiles it. */
|
|
25
|
+
pattern?: string;
|
|
16
26
|
}
|
|
17
27
|
/** A multi-line text input. */
|
|
18
28
|
export interface TextareaField extends FieldBase {
|
|
19
29
|
type: 'textarea';
|
|
20
30
|
/** Visible rows; the editor picks a default when omitted. */
|
|
21
31
|
rows?: number;
|
|
32
|
+
/** Minimum character length of a non-empty value. */
|
|
33
|
+
min?: number;
|
|
34
|
+
/** Maximum character length. */
|
|
35
|
+
max?: number;
|
|
36
|
+
/** Exact required character length. */
|
|
37
|
+
length?: number;
|
|
38
|
+
/** A regular-expression source string the value must match. */
|
|
39
|
+
pattern?: string;
|
|
22
40
|
}
|
|
23
41
|
/** A `YYYY-MM-DD` date input. */
|
|
24
42
|
export interface DateField extends FieldBase {
|
|
25
43
|
type: 'date';
|
|
44
|
+
/** Earliest allowed date, as `YYYY-MM-DD`. */
|
|
45
|
+
min?: string;
|
|
46
|
+
/** Latest allowed date, as `YYYY-MM-DD`. */
|
|
47
|
+
max?: string;
|
|
26
48
|
}
|
|
27
49
|
/** A checkbox; absent means false. */
|
|
28
50
|
export interface BooleanField extends FieldBase {
|
|
@@ -58,18 +80,19 @@ export type ValidationResult = {
|
|
|
58
80
|
errors: Record<string, string>;
|
|
59
81
|
};
|
|
60
82
|
/**
|
|
61
|
-
* Per-site configuration for one content concept (spec §8).
|
|
62
|
-
*
|
|
83
|
+
* Per-site configuration for one content concept (spec §8). One `schema`, built with
|
|
84
|
+
* `defineFields`, is the single source of truth for the editor form, the validator, and the
|
|
85
|
+
* inferred frontmatter type. Generic over the schema so a concept's concrete type survives for
|
|
86
|
+
* typed reads. Concept-fixed behavior such as routability is not here; it lives in the engine's
|
|
87
|
+
* routing table (`CONCEPT_ROUTING`).
|
|
63
88
|
*/
|
|
64
|
-
export interface ConceptConfig {
|
|
89
|
+
export interface ConceptConfig<S extends ConceptSchema = ConceptSchema> {
|
|
65
90
|
/** Repo-relative content directory, e.g. "src/content/posts". */
|
|
66
91
|
dir: string;
|
|
67
92
|
/** Sidebar label; defaults from the concept id when omitted. */
|
|
68
93
|
label?: string;
|
|
69
|
-
/**
|
|
70
|
-
|
|
71
|
-
/** Validate submitted frontmatter before any commit. */
|
|
72
|
-
validate(frontmatter: Record<string, unknown>, body: string): ValidationResult;
|
|
94
|
+
/** The concept's schema: the form projection, the generated validator, and the inferred type. */
|
|
95
|
+
schema: S;
|
|
73
96
|
}
|
|
74
97
|
/**
|
|
75
98
|
* A concept's URL policy, set per concept in the YAML site-config (not the adapter). `permalink` is
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/lib/content/types.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/lib/content/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { FrontmatterField, ValidationResult } from './types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Validate raw frontmatter against a field list. Required text and date fields must be
|
|
4
|
-
* non-empty; required tag fields must be non-empty lists.
|
|
5
|
-
* and
|
|
6
|
-
*
|
|
4
|
+
* non-empty; required tag fields must be non-empty lists. A present boolean coerces to `true`
|
|
5
|
+
* and an unchecked one is omitted; a present tag field coerces to a string array and an empty
|
|
6
|
+
* one is omitted; an empty optional text or date field is omitted, so the normalized data
|
|
7
|
+
* carries only meaningful values and committed frontmatter stays minimal. Returns the
|
|
8
|
+
* normalized data, or field-keyed errors when any required field is empty.
|
|
7
9
|
*
|
|
8
10
|
* Frontmatter may arrive from the edit form (all string values) or from `parseMarkdown`,
|
|
9
11
|
* where gray-matter turns an unquoted YAML date into a JS `Date`. The `date` case coerces a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/lib/content/validate.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAGrE
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/lib/content/validate.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAGrE;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,gBAAgB,EAAE,EAC1B,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACnC,gBAAgB,CA+BlB"}
|
package/dist/content/validate.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { dateInputValue } from './frontmatter.js';
|
|
2
2
|
/**
|
|
3
3
|
* Validate raw frontmatter against a field list. Required text and date fields must be
|
|
4
|
-
* non-empty; required tag fields must be non-empty lists.
|
|
5
|
-
* and
|
|
6
|
-
*
|
|
4
|
+
* non-empty; required tag fields must be non-empty lists. A present boolean coerces to `true`
|
|
5
|
+
* and an unchecked one is omitted; a present tag field coerces to a string array and an empty
|
|
6
|
+
* one is omitted; an empty optional text or date field is omitted, so the normalized data
|
|
7
|
+
* carries only meaningful values and committed frontmatter stays minimal. Returns the
|
|
8
|
+
* normalized data, or field-keyed errors when any required field is empty.
|
|
7
9
|
*
|
|
8
10
|
* Frontmatter may arrive from the edit form (all string values) or from `parseMarkdown`,
|
|
9
11
|
* where gray-matter turns an unquoted YAML date into a JS `Date`. The `date` case coerces a
|
|
@@ -16,28 +18,33 @@ export function validateFields(fields, frontmatter) {
|
|
|
16
18
|
const value = frontmatter[field.name];
|
|
17
19
|
switch (field.type) {
|
|
18
20
|
case 'boolean':
|
|
19
|
-
|
|
21
|
+
// Absent or unchecked means false; omit it so a published file carries no draft: false noise.
|
|
22
|
+
if (value === true)
|
|
23
|
+
data[field.name] = true;
|
|
20
24
|
break;
|
|
21
25
|
case 'tags':
|
|
22
26
|
case 'freetags': {
|
|
23
27
|
const list = Array.isArray(value) ? value.map(String) : [];
|
|
24
28
|
if (field.required && list.length === 0)
|
|
25
29
|
errors[field.name] = `${field.label} is required`;
|
|
26
|
-
|
|
30
|
+
if (list.length > 0)
|
|
31
|
+
data[field.name] = list;
|
|
27
32
|
break;
|
|
28
33
|
}
|
|
29
34
|
case 'date': {
|
|
30
35
|
const text = value instanceof Date ? dateInputValue(value) : typeof value === 'string' ? value.trim() : '';
|
|
31
36
|
if (field.required && text === '')
|
|
32
37
|
errors[field.name] = `${field.label} is required`;
|
|
33
|
-
|
|
38
|
+
if (text !== '')
|
|
39
|
+
data[field.name] = text;
|
|
34
40
|
break;
|
|
35
41
|
}
|
|
36
42
|
default: {
|
|
37
43
|
const text = typeof value === 'string' ? value.trim() : '';
|
|
38
44
|
if (field.required && text === '')
|
|
39
45
|
errors[field.name] = `${field.label} is required`;
|
|
40
|
-
|
|
46
|
+
if (text !== '')
|
|
47
|
+
data[field.name] = text;
|
|
41
48
|
}
|
|
42
49
|
}
|
|
43
50
|
}
|
|
@@ -24,6 +24,12 @@ export interface ContentEntry<F = Record<string, unknown>> extends ContentSummar
|
|
|
24
24
|
frontmatter: F;
|
|
25
25
|
body: string;
|
|
26
26
|
}
|
|
27
|
+
/** One entry's validation failure, recorded at build for the site aggregator's gate. */
|
|
28
|
+
export interface ContentProblem {
|
|
29
|
+
id: string;
|
|
30
|
+
draft: boolean;
|
|
31
|
+
errors: Record<string, string>;
|
|
32
|
+
}
|
|
27
33
|
/** The per-concept query surface. */
|
|
28
34
|
export interface ContentIndex<F = Record<string, unknown>> {
|
|
29
35
|
all(opts?: {
|
|
@@ -41,6 +47,8 @@ export interface ContentIndex<F = Record<string, unknown>> {
|
|
|
41
47
|
newer?: ContentSummary;
|
|
42
48
|
older?: ContentSummary;
|
|
43
49
|
};
|
|
50
|
+
/** Per-entry validation failures recorded at build, for the site-level build gate. */
|
|
51
|
+
problems(): ContentProblem[];
|
|
44
52
|
}
|
|
45
53
|
/** Map a Vite eager `?raw` glob record (`{ path: raw }`) to `RawFile[]`. */
|
|
46
54
|
export declare function fromGlob(record: Record<string, string>): RawFile[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"content-index.d.ts","sourceRoot":"","sources":["../../src/lib/delivery/content-index.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAE7D,yFAAyF;AACzF,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;CACb;AAED,kFAAkF;AAClF,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;wEAEwE;AACxE,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAE,SAAQ,cAAc;IAC/E,WAAW,EAAE,CAAC,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAED,qCAAqC;AACrC,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACvD,GAAG,CAAC,IAAI,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,cAAc,EAAE,CAAC;IAC1D,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,YAAY,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;IAC9C,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,cAAc,EAAE,CAAC;IACzE,OAAO,IAAI;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC5C,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG;QAAE,KAAK,CAAC,EAAE,cAAc,CAAC;QAAC,KAAK,CAAC,EAAE,cAAc,CAAA;KAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"content-index.d.ts","sourceRoot":"","sources":["../../src/lib/delivery/content-index.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAE7D,yFAAyF;AACzF,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;CACb;AAED,kFAAkF;AAClF,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;wEAEwE;AACxE,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAE,SAAQ,cAAc;IAC/E,WAAW,EAAE,CAAC,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wFAAwF;AACxF,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAED,qCAAqC;AACrC,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACvD,GAAG,CAAC,IAAI,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,cAAc,EAAE,CAAC;IAC1D,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,YAAY,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;IAC9C,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,cAAc,EAAE,CAAC;IACzE,OAAO,IAAI;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC5C,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG;QAAE,KAAK,CAAC,EAAE,cAAc,CAAC;QAAC,KAAK,CAAC,EAAE,cAAc,CAAA;KAAE,CAAC;IACzE,sFAAsF;IACtF,QAAQ,IAAI,cAAc,EAAE,CAAC;CAC9B;AAED,4EAA4E;AAC5E,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,EAAE,CAElE;AAqBD,4EAA4E;AAC5E,wBAAgB,kBAAkB,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5D,KAAK,EAAE,OAAO,EAAE,EAChB,UAAU,EAAE,iBAAiB,GAC5B,YAAY,CAAC,CAAC,CAAC,CAsEjB"}
|
|
@@ -29,26 +29,36 @@ function asTags(value) {
|
|
|
29
29
|
}
|
|
30
30
|
/** Build a concept's index from its raw files and normalized descriptor. */
|
|
31
31
|
export function createContentIndex(files, descriptor) {
|
|
32
|
-
const
|
|
32
|
+
const problems = [];
|
|
33
|
+
const entries = [];
|
|
34
|
+
for (const file of files) {
|
|
33
35
|
const id = idFromFilename(basename(file.path));
|
|
34
36
|
const slug = slugFromId(id, descriptor.routing.dated ? descriptor.datePrefix : null);
|
|
35
|
-
const { frontmatter, body } = parseMarkdown(file.raw);
|
|
36
|
-
const date = asDate(
|
|
37
|
-
|
|
37
|
+
const { frontmatter: raw, body } = parseMarkdown(file.raw);
|
|
38
|
+
const date = asDate(raw.date);
|
|
39
|
+
const draft = raw.draft === true;
|
|
40
|
+
// Validate once at build. A failure is recorded for the site gate and excluded from the typed
|
|
41
|
+
// read, so every readable entry's frontmatter is the validator's normalized output, never raw.
|
|
42
|
+
const result = descriptor.validate(raw, body);
|
|
43
|
+
if (!result.ok) {
|
|
44
|
+
problems.push({ id, draft, errors: result.errors });
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
entries.push({
|
|
38
48
|
id,
|
|
39
49
|
slug,
|
|
40
50
|
permalink: permalink(descriptor, { id, slug, date }),
|
|
41
|
-
title: asString(
|
|
51
|
+
title: asString(raw.title) ?? id,
|
|
42
52
|
date,
|
|
43
|
-
updated: asDate(
|
|
44
|
-
tags: asTags(
|
|
45
|
-
excerpt: deriveExcerpt(body, { description: asString(
|
|
53
|
+
updated: asDate(raw.updated),
|
|
54
|
+
tags: asTags(raw.tags),
|
|
55
|
+
excerpt: deriveExcerpt(body, { description: asString(raw.description) }),
|
|
46
56
|
wordCount: wordCount(body),
|
|
47
|
-
draft
|
|
48
|
-
frontmatter:
|
|
57
|
+
draft,
|
|
58
|
+
frontmatter: result.data,
|
|
49
59
|
body,
|
|
50
|
-
};
|
|
51
|
-
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
52
62
|
// Dated concepts sort newest-first; undated concepts (Pages) sort by title.
|
|
53
63
|
const sorted = [...entries].sort((a, b) => descriptor.routing.dated ? (b.date ?? '').localeCompare(a.date ?? '') : a.title.localeCompare(b.title));
|
|
54
64
|
const summarize = (entry) => {
|
|
@@ -82,5 +92,6 @@ export function createContentIndex(files, descriptor) {
|
|
|
82
92
|
older: i < list.length - 1 ? summarize(list[i + 1]) : undefined,
|
|
83
93
|
};
|
|
84
94
|
},
|
|
95
|
+
problems: () => problems,
|
|
85
96
|
};
|
|
86
97
|
}
|
package/dist/delivery/feeds.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feeds.d.ts","sourceRoot":"","sources":["../../src/lib/delivery/feeds.ts"],"names":[],"mappings":"AAKA,gDAAgD;AAChD,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC3C;AAED,uFAAuF;AACvF,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"feeds.d.ts","sourceRoot":"","sources":["../../src/lib/delivery/feeds.ts"],"names":[],"mappings":"AAKA,gDAAgD;AAChD,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC3C;AAED,uFAAuF;AACvF,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB;AAiCD,iCAAiC;AACjC,wBAAgB,YAAY,CAAC,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,MAAM,CAqC5E;AAED,sCAAsC;AACtC,wBAAgB,aAAa,CAAC,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,MAAM,CA4B7E"}
|
package/dist/delivery/feeds.js
CHANGED
|
@@ -13,30 +13,41 @@ function escapeXml(value) {
|
|
|
13
13
|
function cdataSafe(value) {
|
|
14
14
|
return value.replace(/]]>/g, ']]]]><![CDATA[>');
|
|
15
15
|
}
|
|
16
|
-
/**
|
|
16
|
+
/** Parse a YYYY-MM-DD (or ISO) string as a UTC instant. Returns undefined for an absent or
|
|
17
|
+
* unparseable date, so a feed omits the date field rather than emit Invalid Date or throw. */
|
|
18
|
+
function parseFeedDate(date) {
|
|
19
|
+
if (!date)
|
|
20
|
+
return undefined;
|
|
21
|
+
const at = new Date(`${date.slice(0, 10)}T00:00:00.000Z`);
|
|
22
|
+
return Number.isNaN(at.getTime()) ? undefined : at;
|
|
23
|
+
}
|
|
24
|
+
/** Format a date as an RFC-822 string in UTC, as RSS wants, or undefined when it cannot parse. */
|
|
17
25
|
function rfc822(date) {
|
|
18
|
-
return
|
|
26
|
+
return parseFeedDate(date)?.toUTCString();
|
|
19
27
|
}
|
|
20
|
-
/** Format a
|
|
28
|
+
/** Format a date as an ISO-8601 instant in UTC, or undefined when it cannot parse. */
|
|
21
29
|
function iso(date) {
|
|
22
|
-
return
|
|
30
|
+
return parseFeedDate(date)?.toISOString();
|
|
23
31
|
}
|
|
24
32
|
/** Build an RSS 2.0 document. */
|
|
25
33
|
export function buildRssFeed(channel, items) {
|
|
26
34
|
const entries = items
|
|
27
35
|
.map((item) => {
|
|
28
36
|
const content = item.contentHtml ?? item.summary;
|
|
37
|
+
const pubDate = rfc822(item.date);
|
|
29
38
|
return [
|
|
30
39
|
' <item>',
|
|
31
40
|
` <title>${escapeXml(item.title)}</title>`,
|
|
32
41
|
` <link>${escapeXml(item.url)}</link>`,
|
|
33
42
|
` <guid isPermaLink="true">${escapeXml(item.url)}</guid>`,
|
|
34
|
-
` <pubDate>${
|
|
43
|
+
pubDate ? ` <pubDate>${pubDate}</pubDate>` : '',
|
|
35
44
|
` <description>${escapeXml(item.summary)}</description>`,
|
|
36
45
|
// CDATA cannot contain `]]>`, so split that one sequence rather than escape the body.
|
|
37
46
|
` <content:encoded><![CDATA[${cdataSafe(content)}]]></content:encoded>`,
|
|
38
47
|
' </item>',
|
|
39
|
-
]
|
|
48
|
+
]
|
|
49
|
+
.filter((line) => line !== '')
|
|
50
|
+
.join('\n');
|
|
40
51
|
})
|
|
41
52
|
.join('\n');
|
|
42
53
|
return [
|
|
@@ -66,15 +77,19 @@ export function buildJsonFeed(channel, items) {
|
|
|
66
77
|
feed_url: channel.feedUrl,
|
|
67
78
|
...(channel.language ? { language: channel.language } : {}),
|
|
68
79
|
...(channel.author ? { authors: [channel.author] } : {}),
|
|
69
|
-
items: items.map((item) =>
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
80
|
+
items: items.map((item) => {
|
|
81
|
+
const datePublished = iso(item.date);
|
|
82
|
+
const dateModified = iso(item.updated);
|
|
83
|
+
return {
|
|
84
|
+
id: item.url,
|
|
85
|
+
url: item.url,
|
|
86
|
+
title: item.title,
|
|
87
|
+
summary: item.summary,
|
|
88
|
+
...(datePublished ? { date_published: datePublished } : {}),
|
|
89
|
+
...(dateModified ? { date_modified: dateModified } : {}),
|
|
90
|
+
...(item.contentHtml ? { content_html: item.contentHtml } : { content_text: item.summary }),
|
|
91
|
+
...(item.tags && item.tags.length ? { tags: item.tags } : {}),
|
|
92
|
+
};
|
|
93
|
+
}),
|
|
79
94
|
}, null, 2);
|
|
80
95
|
}
|
package/dist/delivery/index.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
export { createContentIndex, fromGlob } from './content-index.js';
|
|
2
|
-
export type { RawFile, ContentSummary, ContentEntry, ContentIndex } from './content-index.js';
|
|
2
|
+
export type { RawFile, ContentSummary, ContentEntry, ContentIndex, ContentProblem } from './content-index.js';
|
|
3
3
|
export { createSiteIndex } from './site-index.js';
|
|
4
4
|
export type { SiteIndex, ConceptIndex } from './site-index.js';
|
|
5
|
+
export { createSiteIndexes } from './site-indexes.js';
|
|
6
|
+
export type { SiteIndexes, SiteGlobs } from './site-indexes.js';
|
|
5
7
|
export { siteDescriptors } from './site-descriptors.js';
|
|
6
8
|
export { deriveExcerpt, wordCount } from './excerpt.js';
|
|
7
9
|
export { buildRssFeed, buildJsonFeed } from './feeds.js';
|
|
@@ -11,6 +13,8 @@ export type { SitemapUrl } from './sitemap.js';
|
|
|
11
13
|
export { buildRobots } from './robots.js';
|
|
12
14
|
export { buildSeoMeta } from './seo.js';
|
|
13
15
|
export type { SeoInput, SeoMeta } from './seo.js';
|
|
16
|
+
export { readSeoFields, resolveImageUrl } from './seo-fields.js';
|
|
17
|
+
export type { SeoFields } from './seo-fields.js';
|
|
14
18
|
export { paginate } from './paginate.js';
|
|
15
19
|
export type { Page } from './paginate.js';
|
|
16
20
|
export { rssResponse, jsonFeedResponse, sitemapResponse, robotsResponse } from './responses.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/delivery/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAClE,YAAY,EAAE,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/delivery/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAClE,YAAY,EAAE,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAC9G,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,YAAY,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACzD,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,YAAY,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,YAAY,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACjE,YAAY,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,YAAY,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChG,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACnE,YAAY,EACV,gBAAgB,EAChB,QAAQ,EACR,OAAO,EACP,YAAY,EACZ,SAAS,GACV,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,oBAAoB,CAAC"}
|
package/dist/delivery/index.js
CHANGED
|
@@ -5,12 +5,14 @@
|
|
|
5
5
|
// github, or email, so importing it does not pull the server backend into a public bundle.
|
|
6
6
|
export { createContentIndex, fromGlob } from './content-index.js';
|
|
7
7
|
export { createSiteIndex } from './site-index.js';
|
|
8
|
+
export { createSiteIndexes } from './site-indexes.js';
|
|
8
9
|
export { siteDescriptors } from './site-descriptors.js';
|
|
9
10
|
export { deriveExcerpt, wordCount } from './excerpt.js';
|
|
10
11
|
export { buildRssFeed, buildJsonFeed } from './feeds.js';
|
|
11
12
|
export { buildSitemap } from './sitemap.js';
|
|
12
13
|
export { buildRobots } from './robots.js';
|
|
13
14
|
export { buildSeoMeta } from './seo.js';
|
|
15
|
+
export { readSeoFields, resolveImageUrl } from './seo-fields.js';
|
|
14
16
|
export { paginate } from './paginate.js';
|
|
15
17
|
export { rssResponse, jsonFeedResponse, sitemapResponse, robotsResponse } from './responses.js';
|
|
16
18
|
export { jsonLdScript } from './json-ld.js';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** The head fields a concept can carry in frontmatter. Each is optional and omitted when absent.
|
|
2
|
+
* `author` is article-scoped downstream: the head builder emits `article:author` only for a dated
|
|
3
|
+
* entry, so an `author` on an undated Page is read here but not rendered. */
|
|
4
|
+
export interface SeoFields {
|
|
5
|
+
description?: string;
|
|
6
|
+
image?: string;
|
|
7
|
+
robots?: string;
|
|
8
|
+
author?: string;
|
|
9
|
+
}
|
|
10
|
+
/** Read the known SEO head fields off an entry's normalized frontmatter. Keeps a present string,
|
|
11
|
+
* trimmed, and omits an absent, empty, or non-string value. Trimming the stored value keeps a stray
|
|
12
|
+
* `robots: " noindex "` from reaching the head tag with surrounding whitespace. The field must be
|
|
13
|
+
* declared in the concept's schema to survive the validate-once read; an undeclared key is not on the
|
|
14
|
+
* normalized frontmatter. */
|
|
15
|
+
export declare function readSeoFields(frontmatter: Record<string, unknown>): SeoFields;
|
|
16
|
+
/** Resolve an author-supplied image path to an absolute URL against the site origin. An absolute or
|
|
17
|
+
* protocol-relative URL passes through; a root-relative path anchors to the origin; a malformed
|
|
18
|
+
* string returns undefined rather than throwing at build. The sites use a bare-domain origin, so a
|
|
19
|
+
* bare path also anchors to the origin root; against a sub-path origin it would resolve relative to
|
|
20
|
+
* that path, per the WHATWG URL rules. */
|
|
21
|
+
export declare function resolveImageUrl(image: string, origin: string): string | undefined;
|
|
22
|
+
//# sourceMappingURL=seo-fields.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seo-fields.d.ts","sourceRoot":"","sources":["../../src/lib/delivery/seo-fields.ts"],"names":[],"mappings":"AAKA;;8EAE8E;AAC9E,MAAM,WAAW,SAAS;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAID;;;;8BAI8B;AAC9B,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAO7E;AAED;;;;2CAI2C;AAC3C,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAMjF"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// cairn-cms: the SEO head fields read at the cross-concept boundary (schema-source-of-truth design,
|
|
2
|
+
// Plan 3). The catch-all route resolves any concept by request path, so the entry's frontmatter is
|
|
3
|
+
// typed Record<string, unknown>; this reads the known head fields by name and coerces. Kept apart
|
|
4
|
+
// from seo.ts (the head builder) so reading frontmatter and building the head stay distinct concerns.
|
|
5
|
+
const KEYS = ['description', 'image', 'robots', 'author'];
|
|
6
|
+
/** Read the known SEO head fields off an entry's normalized frontmatter. Keeps a present string,
|
|
7
|
+
* trimmed, and omits an absent, empty, or non-string value. Trimming the stored value keeps a stray
|
|
8
|
+
* `robots: " noindex "` from reaching the head tag with surrounding whitespace. The field must be
|
|
9
|
+
* declared in the concept's schema to survive the validate-once read; an undeclared key is not on the
|
|
10
|
+
* normalized frontmatter. */
|
|
11
|
+
export function readSeoFields(frontmatter) {
|
|
12
|
+
const fields = {};
|
|
13
|
+
for (const key of KEYS) {
|
|
14
|
+
const value = frontmatter[key];
|
|
15
|
+
if (typeof value === 'string' && value.trim() !== '')
|
|
16
|
+
fields[key] = value.trim();
|
|
17
|
+
}
|
|
18
|
+
return fields;
|
|
19
|
+
}
|
|
20
|
+
/** Resolve an author-supplied image path to an absolute URL against the site origin. An absolute or
|
|
21
|
+
* protocol-relative URL passes through; a root-relative path anchors to the origin; a malformed
|
|
22
|
+
* string returns undefined rather than throwing at build. The sites use a bare-domain origin, so a
|
|
23
|
+
* bare path also anchors to the origin root; against a sub-path origin it would resolve relative to
|
|
24
|
+
* that path, per the WHATWG URL rules. */
|
|
25
|
+
export function resolveImageUrl(image, origin) {
|
|
26
|
+
try {
|
|
27
|
+
return new URL(image, origin).href;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -25,8 +25,8 @@ export interface SiteIndex {
|
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
27
|
* Union per-concept indexes into a site-level resolver. Throws on a duplicate permalink and,
|
|
28
|
-
* unless `validate` is `false`, on any entry whose frontmatter fails its concept's
|
|
29
|
-
* so malformed content fails the build instead of shipping.
|
|
28
|
+
* unless `validate` is `false`, on any non-draft entry whose frontmatter fails its concept's
|
|
29
|
+
* validator, so malformed content fails the build instead of shipping.
|
|
30
30
|
*/
|
|
31
31
|
export declare function createSiteIndex(concepts: ConceptIndex[], opts?: {
|
|
32
32
|
validate?: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"site-index.d.ts","sourceRoot":"","sources":["../../src/lib/delivery/site-index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAErF,4DAA4D;AAC5D,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,iBAAiB,CAAC;IAC9B,KAAK,EAAE,YAAY,CAAC;CACrB;AAED,8EAA8E;AAC9E,MAAM,WAAW,SAAS;IACxB,4FAA4F;IAC5F,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAAC;IACpD,iFAAiF;IACjF,QAAQ,CAAC,KAAK,EAAE,cAAc,GAAG;QAAE,KAAK,CAAC,EAAE,cAAc,CAAC;QAAC,KAAK,CAAC,EAAE,cAAc,CAAA;KAAE,CAAC;IACpF,uGAAuG;IACvG,OAAO,IAAI;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC9B,mEAAmE;IACnE,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAAC;IAC9C,0EAA0E;IAC1E,GAAG,IAAI,cAAc,EAAE,CAAC;CACzB;
|
|
1
|
+
{"version":3,"file":"site-index.d.ts","sourceRoot":"","sources":["../../src/lib/delivery/site-index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAErF,4DAA4D;AAC5D,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,iBAAiB,CAAC;IAC9B,KAAK,EAAE,YAAY,CAAC;CACrB;AAED,8EAA8E;AAC9E,MAAM,WAAW,SAAS;IACxB,4FAA4F;IAC5F,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAAC;IACpD,iFAAiF;IACjF,QAAQ,CAAC,KAAK,EAAE,cAAc,GAAG;QAAE,KAAK,CAAC,EAAE,cAAc,CAAC;QAAC,KAAK,CAAC,EAAE,cAAc,CAAA;KAAE,CAAC;IACpF,uGAAuG;IACvG,OAAO,IAAI;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC9B,mEAAmE;IACnE,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAAC;IAC9C,0EAA0E;IAC1E,GAAG,IAAI,cAAc,EAAE,CAAC;CACzB;AAqBD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,YAAY,EAAE,EAAE,IAAI,GAAE;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,SAAS,CAwCtG"}
|
|
@@ -2,34 +2,32 @@
|
|
|
2
2
|
function normalizePath(path) {
|
|
3
3
|
return path.length > 1 ? path.replace(/\/+$/, '') : path;
|
|
4
4
|
}
|
|
5
|
-
/**
|
|
6
|
-
function
|
|
5
|
+
/** Collect non-draft validation failures across concepts from each index's recorded verdicts. */
|
|
6
|
+
function siteProblems(concepts) {
|
|
7
7
|
const problems = [];
|
|
8
8
|
for (const { descriptor, index } of concepts) {
|
|
9
|
-
for (const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
if (!result.ok) {
|
|
15
|
-
for (const [field, message] of Object.entries(result.errors)) {
|
|
16
|
-
problems.push(`${descriptor.dir}/${summary.id}: ${field}: ${message}`);
|
|
17
|
-
}
|
|
9
|
+
for (const problem of index.problems()) {
|
|
10
|
+
if (problem.draft)
|
|
11
|
+
continue; // a half-finished draft never ships, so it does not fail the build
|
|
12
|
+
for (const [field, message] of Object.entries(problem.errors)) {
|
|
13
|
+
problems.push(`${descriptor.dir}/${problem.id}: ${field}: ${message}`);
|
|
18
14
|
}
|
|
19
15
|
}
|
|
20
16
|
}
|
|
21
|
-
|
|
22
|
-
throw new Error(`site index: ${problems.length} invalid frontmatter field(s):\n ${problems.join('\n ')}`);
|
|
23
|
-
}
|
|
17
|
+
return problems;
|
|
24
18
|
}
|
|
25
19
|
/**
|
|
26
20
|
* Union per-concept indexes into a site-level resolver. Throws on a duplicate permalink and,
|
|
27
|
-
* unless `validate` is `false`, on any entry whose frontmatter fails its concept's
|
|
28
|
-
* so malformed content fails the build instead of shipping.
|
|
21
|
+
* unless `validate` is `false`, on any non-draft entry whose frontmatter fails its concept's
|
|
22
|
+
* validator, so malformed content fails the build instead of shipping.
|
|
29
23
|
*/
|
|
30
24
|
export function createSiteIndex(concepts, opts = {}) {
|
|
31
|
-
if (opts.validate !== false)
|
|
32
|
-
|
|
25
|
+
if (opts.validate !== false) {
|
|
26
|
+
const problems = siteProblems(concepts);
|
|
27
|
+
if (problems.length > 0) {
|
|
28
|
+
throw new Error(`site index: ${problems.length} invalid frontmatter field(s):\n ${problems.join('\n ')}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
33
31
|
const byPath = new Map();
|
|
34
32
|
const byId = new Map();
|
|
35
33
|
for (const { descriptor, index } of concepts) {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { CairnAdapter, ConceptConfig } from '../content/types.js';
|
|
2
|
+
import type { Infer } from '../content/schema.js';
|
|
3
|
+
import type { SiteConfig } from '../nav/site-config.js';
|
|
4
|
+
import type { ContentIndex } from './content-index.js';
|
|
5
|
+
import type { SiteIndex } from './site-index.js';
|
|
6
|
+
/** A per-concept raw glob record (`{ path: raw }`) keyed by concept id, from `import.meta.glob`. */
|
|
7
|
+
export type SiteGlobs<A extends CairnAdapter> = {
|
|
8
|
+
[K in keyof A['content']]?: Record<string, string>;
|
|
9
|
+
};
|
|
10
|
+
/** The typed per-concept indexes plus the cross-concept `site` resolver. A concept literally named
|
|
11
|
+
* `site` is not supported, since `site` is the reserved resolver key. */
|
|
12
|
+
export type SiteIndexes<A extends CairnAdapter> = {
|
|
13
|
+
[K in keyof A['content']]: ContentIndex<NonNullable<A['content'][K]> extends ConceptConfig<infer S> ? Infer<S> : Record<string, unknown>>;
|
|
14
|
+
} & {
|
|
15
|
+
readonly site: SiteIndex;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Build typed per-concept indexes and a site resolver from one adapter. Pass the per-concept raw
|
|
19
|
+
* globs as `{ posts: import.meta.glob('...?raw', { eager: true }), ... }`; Vite needs the literal
|
|
20
|
+
* glob at the call site, so the engine cannot glob on the site's behalf. `validate: false` opts out
|
|
21
|
+
* of the build gate, exactly as on `createSiteIndex`.
|
|
22
|
+
*/
|
|
23
|
+
export declare function createSiteIndexes<const A extends CairnAdapter>(adapter: A, config: SiteConfig, globs: SiteGlobs<A>, opts?: {
|
|
24
|
+
validate?: boolean;
|
|
25
|
+
}): SiteIndexes<A>;
|
|
26
|
+
//# sourceMappingURL=site-indexes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"site-indexes.d.ts","sourceRoot":"","sources":["../../src/lib/delivery/site-indexes.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACvE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAIxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAgB,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE/D,oGAAoG;AACpG,MAAM,MAAM,SAAS,CAAC,CAAC,SAAS,YAAY,IAAI;KAC7C,CAAC,IAAI,MAAM,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;CACnD,CAAC;AAEF;0EAC0E;AAC1E,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,YAAY,IAAI;KAC/C,CAAC,IAAI,MAAM,CAAC,CAAC,SAAS,CAAC,GAAG,YAAY,CACrC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,aAAa,CAAC,MAAM,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CACjG;CACF,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC;AAEjC;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,CAAC,CAAC,SAAS,YAAY,EAC5D,OAAO,EAAE,CAAC,EACV,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,EACnB,IAAI,GAAE;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAO,GAChC,WAAW,CAAC,CAAC,CAAC,CAwBhB"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { siteDescriptors } from './site-descriptors.js';
|
|
2
|
+
import { createContentIndex, fromGlob } from './content-index.js';
|
|
3
|
+
import { createSiteIndex } from './site-index.js';
|
|
4
|
+
/**
|
|
5
|
+
* Build typed per-concept indexes and a site resolver from one adapter. Pass the per-concept raw
|
|
6
|
+
* globs as `{ posts: import.meta.glob('...?raw', { eager: true }), ... }`; Vite needs the literal
|
|
7
|
+
* glob at the call site, so the engine cannot glob on the site's behalf. `validate: false` opts out
|
|
8
|
+
* of the build gate, exactly as on `createSiteIndex`.
|
|
9
|
+
*/
|
|
10
|
+
export function createSiteIndexes(adapter, config, globs, opts = {}) {
|
|
11
|
+
const descriptors = siteDescriptors(adapter, config);
|
|
12
|
+
const globRecord = globs;
|
|
13
|
+
const byConcept = {};
|
|
14
|
+
const conceptIndexes = [];
|
|
15
|
+
for (const descriptor of descriptors) {
|
|
16
|
+
if (descriptor.id === 'site') {
|
|
17
|
+
throw new Error('createSiteIndexes: a concept cannot be named "site", which is the reserved cross-concept resolver key');
|
|
18
|
+
}
|
|
19
|
+
if (!Object.prototype.hasOwnProperty.call(globRecord, descriptor.id)) {
|
|
20
|
+
const passed = Object.keys(globRecord);
|
|
21
|
+
throw new Error(`createSiteIndexes: no glob passed for concept "${descriptor.id}"; pass its import.meta.glob (an empty {} for an intentionally empty concept). Globs passed: ${passed.length ? passed.join(', ') : '(none)'}`);
|
|
22
|
+
}
|
|
23
|
+
const record = globRecord[descriptor.id] ?? {};
|
|
24
|
+
const index = createContentIndex(fromGlob(record), descriptor);
|
|
25
|
+
byConcept[descriptor.id] = index;
|
|
26
|
+
conceptIndexes.push({ descriptor, index });
|
|
27
|
+
}
|
|
28
|
+
const site = createSiteIndex(conceptIndexes, opts);
|
|
29
|
+
return { ...byConcept, site };
|
|
30
|
+
}
|
package/dist/env.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../src/lib/env.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAE5D;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE;IAAE,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../src/lib/env.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAE5D;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE;IAAE,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAmBrE;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE;IAAE,OAAO,CAAC,EAAE,UAAU,CAAA;CAAE,GAAG,UAAU,CAKnE"}
|