@glw907/cairn-cms 0.21.0 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +87 -0
- package/README.md +50 -37
- package/dist/content/compose.d.ts +15 -4
- package/dist/content/compose.d.ts.map +1 -1
- package/dist/content/compose.js +9 -4
- package/dist/content/concepts.d.ts.map +1 -1
- package/dist/content/concepts.js +7 -0
- package/dist/content/frontmatter.d.ts +8 -0
- package/dist/content/frontmatter.d.ts.map +1 -1
- package/dist/content/frontmatter.js +19 -0
- package/dist/content/manifest.d.ts +20 -3
- package/dist/content/manifest.d.ts.map +1 -1
- package/dist/content/manifest.js +49 -6
- package/dist/content/types.d.ts +6 -0
- package/dist/content/types.d.ts.map +1 -1
- package/dist/content/validate.d.ts +4 -1
- package/dist/content/validate.d.ts.map +1 -1
- package/dist/content/validate.js +12 -2
- package/dist/delivery/content-index.d.ts +11 -0
- package/dist/delivery/content-index.d.ts.map +1 -1
- package/dist/delivery/content-index.js +7 -0
- package/dist/delivery/data.d.ts +24 -0
- package/dist/delivery/data.d.ts.map +1 -0
- package/dist/delivery/data.js +18 -0
- package/dist/delivery/head.d.ts +2 -0
- package/dist/delivery/head.d.ts.map +1 -0
- package/dist/delivery/head.js +4 -0
- package/dist/delivery/index.d.ts +1 -24
- package/dist/delivery/index.d.ts.map +1 -1
- package/dist/delivery/index.js +5 -21
- package/dist/index.d.ts +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -2
- package/dist/render/pipeline.d.ts +6 -2
- package/dist/render/pipeline.d.ts.map +1 -1
- package/dist/render/pipeline.js +5 -2
- package/dist/render/registry.d.ts +1 -1
- package/dist/render/registry.d.ts.map +1 -1
- package/dist/render/rehype-dispatch.d.ts +5 -0
- package/dist/render/rehype-dispatch.d.ts.map +1 -1
- package/dist/render/rehype-dispatch.js +12 -1
- package/dist/render/remark-directives.d.ts.map +1 -1
- package/dist/render/remark-directives.js +15 -6
- package/dist/render/sanitize-schema.d.ts +4 -3
- package/dist/render/sanitize-schema.d.ts.map +1 -1
- package/dist/render/sanitize-schema.js +6 -5
- package/dist/sveltekit/public-routes.d.ts +1 -0
- package/dist/sveltekit/public-routes.d.ts.map +1 -1
- package/dist/sveltekit/public-routes.js +1 -1
- package/dist/vite/bin.d.ts +3 -0
- package/dist/vite/bin.d.ts.map +1 -0
- package/dist/vite/bin.js +9 -0
- package/dist/vite/index.d.ts +33 -0
- package/dist/vite/index.d.ts.map +1 -0
- package/dist/vite/index.js +178 -0
- package/package.json +26 -4
- package/src/lib/content/compose.ts +18 -9
- package/src/lib/content/concepts.ts +9 -0
- package/src/lib/content/frontmatter.ts +21 -0
- package/src/lib/content/manifest.ts +63 -7
- package/src/lib/content/types.ts +6 -0
- package/src/lib/content/validate.ts +10 -2
- package/src/lib/delivery/content-index.ts +17 -0
- package/src/lib/delivery/data.ts +26 -0
- package/src/lib/delivery/head.ts +4 -0
- package/src/lib/delivery/index.ts +5 -29
- package/src/lib/index.ts +10 -1
- package/src/lib/render/pipeline.ts +11 -3
- package/src/lib/render/registry.ts +1 -1
- package/src/lib/render/rehype-dispatch.ts +12 -1
- package/src/lib/render/remark-directives.ts +16 -5
- package/src/lib/render/sanitize-schema.ts +6 -5
- package/src/lib/sveltekit/public-routes.ts +2 -1
- package/src/lib/vite/bin.ts +10 -0
- package/src/lib/vite/index.ts +213 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are recorded here, most recent first.
|
|
4
|
+
|
|
5
|
+
## 0.26.0
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- A `cairnManifest()` Vite plugin (`@glw907/cairn-cms/vite`) verifies the committed content manifest on
|
|
9
|
+
every build and fails the build with a diff naming what drifted. The check runs outside the prerender
|
|
10
|
+
lifecycle, so `handleHttpError` cannot mask it. Consumers must: add `cairnManifest({ configModule,
|
|
11
|
+
content, manifestPath })` to the Vite config.
|
|
12
|
+
- A `cairn-manifest` bin regenerates the committed manifest from a Vite context. Consumers must: set the
|
|
13
|
+
regenerate script to `"cairn:manifest": "cairn-manifest"` and delete the hand-written
|
|
14
|
+
`scripts/build-manifest.mjs`.
|
|
15
|
+
- A node-safe `@glw907/cairn-cms/delivery/data` entry exposes the pure delivery projections with no
|
|
16
|
+
`@sveltejs/kit` in the graph. Consumers must: move any plain-Node import of a delivery data helper
|
|
17
|
+
(such as `buildSiteManifest`) from `@glw907/cairn-cms/delivery` to `@glw907/cairn-cms/delivery/data`.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- `verifyManifest` now throws an error that names the added, removed, and changed entries. Consumers
|
|
21
|
+
must: nothing. The message is strictly more informative.
|
|
22
|
+
|
|
23
|
+
## 0.25.0
|
|
24
|
+
|
|
25
|
+
### Changed (breaking)
|
|
26
|
+
- `composeRuntime` now takes a single object, `composeRuntime({ adapter, siteConfig, extensions? })`,
|
|
27
|
+
and derives the per-concept URL policy from `siteConfig`. The loose third `urlPolicy` argument is
|
|
28
|
+
gone, and a missing `siteConfig` throws. Consumers must: pass the parsed site config to every
|
|
29
|
+
`composeRuntime` call and drop any hand-passed URL policy.
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
- `createRenderer()` now defaults its registry to the empty registry, so a plain-prose site calls
|
|
33
|
+
`createRenderer()` with no argument. Consumers must: nothing; passing a built registry is unchanged.
|
|
34
|
+
|
|
35
|
+
### Docs
|
|
36
|
+
- A render sanitize-floor reference (`docs/render-sanitize-floor.md`) states what the floor keeps,
|
|
37
|
+
strips, and rewrites, including the `target="_blank"` rel policy.
|
|
38
|
+
- An upgrade guide (`docs/upgrading.md`) collects the `0.x` renames with a consumer action each.
|
|
39
|
+
|
|
40
|
+
## 0.24.0
|
|
41
|
+
|
|
42
|
+
### Added
|
|
43
|
+
- `headRow(title, icon?)` builds the icon-plus-heading component head, exported beside `cardShell` and
|
|
44
|
+
`iconSpan`.
|
|
45
|
+
- A `createRenderer` `anchorRel` option sets the `rel` value forced on `target="_blank"` anchors
|
|
46
|
+
(default `'noopener noreferrer'`), or disables the injection when set to `false`.
|
|
47
|
+
|
|
48
|
+
### Changed
|
|
49
|
+
- A component's `defaultIconByRole` default now reaches the build through the declared `type: 'icon'`
|
|
50
|
+
attribute (`ctx.attributes`), so a role default no longer needs a hardcoded fallback in the build. A
|
|
51
|
+
component using `defaultIconByRole` must declare a `type: 'icon'` attribute.
|
|
52
|
+
- The engine drops an unclaimed directive `[label]` when a component has no `title` slot, so a stray
|
|
53
|
+
`[]` no longer renders an empty paragraph.
|
|
54
|
+
|
|
55
|
+
### Removed
|
|
56
|
+
- The internal `data-icon` marker, which no build read. The resolved icon now travels on the declared
|
|
57
|
+
attribute path.
|
|
58
|
+
|
|
59
|
+
## 0.23.0
|
|
60
|
+
|
|
61
|
+
### Changed (breaking)
|
|
62
|
+
- A `date` field now validates a real `YYYY-MM-DD` calendar date. A site adopting this version whose
|
|
63
|
+
committed content holds a malformed or impossible date will see it fail validation, which is the loud
|
|
64
|
+
failure this restores.
|
|
65
|
+
- A `tags` field now enforces its declared `options` as a closed vocabulary. A committed value outside
|
|
66
|
+
the list fails validation. Use a `freetags` field for free-form tags.
|
|
67
|
+
- `normalizeConcepts` now throws when a `summaryFields` key names no declared field, so a typo fails at
|
|
68
|
+
config load instead of silently producing an empty list card.
|
|
69
|
+
|
|
70
|
+
### Changed
|
|
71
|
+
- `AttributeField.options` is now `readonly string[]`, so a site can share one frozen `as const`
|
|
72
|
+
vocabulary across components. Read-only by use, so no call site changes.
|
|
73
|
+
|
|
74
|
+
## 0.22.0
|
|
75
|
+
|
|
76
|
+
### Added
|
|
77
|
+
- `ContentSummary.concept` and `EntryData.concept`: the read model carries its resolved concept id, so a
|
|
78
|
+
list or page branches per concept without re-deriving it from `entry.date`.
|
|
79
|
+
- A `summaryFields` knob on a concept config surfaces named frontmatter keys on `ContentSummary.fields`,
|
|
80
|
+
so a list card reads an authored field with no per-entry detail read.
|
|
81
|
+
- The package root re-exports the delivery route loaders (`createPublicRoutes`) and the response helpers
|
|
82
|
+
(`rssResponse`, `jsonFeedResponse`, `sitemapResponse`, `robotsResponse`).
|
|
83
|
+
|
|
84
|
+
### Changed (breaking)
|
|
85
|
+
- `CairnHead` moved off the `@glw907/cairn-cms/delivery` barrel to its own `@glw907/cairn-cms/delivery/head`
|
|
86
|
+
entry, so a node-environment data import from `/delivery` stays component-free. Update the import:
|
|
87
|
+
`import { CairnHead } from '@glw907/cairn-cms/delivery/head'`.
|
package/README.md
CHANGED
|
@@ -1,32 +1,33 @@
|
|
|
1
1
|
# cairn-cms
|
|
2
2
|
|
|
3
3
|
An embedded, **magic-link**, GitHub-committing CMS for SvelteKit + Cloudflare sites.
|
|
4
|
-
Non-technical authors log in by email (no GitHub account, no password), edit **raw
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
and auto-deploys.
|
|
4
|
+
Non-technical authors log in by email (no GitHub account, no password), edit **raw markdown**
|
|
5
|
+
in a client-only CodeMirror editor with a live preview, and save. Each save commits to `main`
|
|
6
|
+
via a **GitHub App** (committer = `cairn-cms[bot]`, author = the editor) and auto-deploys.
|
|
8
7
|
|
|
9
|
-
It is **design-agnostic
|
|
10
|
-
|
|
11
|
-
sites with completely different markdown pipelines
|
|
12
|
-
|
|
8
|
+
It is **design-agnostic**. Each consumer site supplies an adapter (the content contract through
|
|
9
|
+
`defineAdapter`/`defineFields`, the slug and permalink rules, and its render configuration), so the
|
|
10
|
+
same engine drives sites with completely different markdown pipelines. Two run in production today:
|
|
11
|
+
[ecnordic.ski](https://ecnordic.ski) (a remark-to-rehype directive pipeline) and
|
|
12
|
+
[907.life](https://907.life) (the engine's own `createRenderer`). Content is a fixed set of
|
|
13
|
+
first-class concepts (Posts and Pages), not open-ended collections.
|
|
13
14
|
|
|
14
15
|
## Status
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
17
|
+
cairn-cms runs two production sites today, [ecnordic.ski](https://ecnordic.ski) and
|
|
18
|
+
[907.life](https://907.life). It is `0.x` and breaks between minor versions. The author is
|
|
19
|
+
still working through the core-feature roadmap, and the project stays closely held until that
|
|
20
|
+
core lands. See the [ROADMAP](./ROADMAP.md) for what is planned and the
|
|
21
|
+
[CHANGELOG](./CHANGELOG.md) for what changed.
|
|
22
|
+
|
|
23
|
+
Editor auth is self-owned: an atomic single-use magic-link token, a POST-confirm flow, opaque
|
|
24
|
+
D1-backed session rows, and two-tier `owner`/`editor` roles. There is no better-auth, Drizzle,
|
|
25
|
+
or ORM. Pin a caret range and read the CHANGELOG before bumping; every breaking entry carries a
|
|
26
|
+
"Consumers must" line.
|
|
27
|
+
|
|
28
|
+
A contributor who feels inspired is welcome to open an issue or a discussion to start a
|
|
29
|
+
conversation. There is no formal contribution process yet, so this is not an open call for
|
|
30
|
+
pull requests.
|
|
30
31
|
|
|
31
32
|
## Install
|
|
32
33
|
|
|
@@ -34,23 +35,35 @@ range and expect 0.x churn.
|
|
|
34
35
|
npm install @glw907/cairn-cms
|
|
35
36
|
```
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
`@glw907/cairn-cms/sveltekit` (server logic) and `@glw907/cairn-cms/components` (the admin UI).
|
|
38
|
+
Peer dependencies: `svelte@^5` and `@sveltejs/kit@^2`. A consumer site implements a `CairnAdapter`
|
|
39
|
+
and mounts thin `/admin` route shims around the package subpaths:
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
- `@glw907/cairn-cms`: the core engine and adapter contract.
|
|
42
|
+
- `@glw907/cairn-cms/sveltekit`: the server load and action logic.
|
|
43
|
+
- `@glw907/cairn-cms/components`: the admin Svelte UI.
|
|
44
|
+
- `@glw907/cairn-cms/delivery` and `/delivery/data`: the public read model (indexes, feeds,
|
|
45
|
+
sitemap, SEO head). The `/delivery/data` barrel is node-safe, with no `@sveltejs/kit` in its graph.
|
|
46
|
+
- `@glw907/cairn-cms/vite`: the `cairnManifest()` Vite plugin, paired with the `cairn-manifest` bin,
|
|
47
|
+
that builds and verifies the committed content manifest at build time.
|
|
42
48
|
|
|
43
|
-
|
|
49
|
+
Each site binds a Cloudflare D1 database as `AUTH_DB` (the editor allowlist, sessions, and single-use
|
|
50
|
+
magic tokens) and a `[[send_email]]` binding named `EMAIL`. The worked reference for every shape is
|
|
51
|
+
`examples/showcase`.
|
|
44
52
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
53
|
+
## Documentation
|
|
54
|
+
|
|
55
|
+
The [`docs/`](./docs/README.md) tree is organized in four arms: a tutorial that builds a first
|
|
56
|
+
site end to end, how-to guides for each setup task, a reference for every package export, and
|
|
57
|
+
explanation pages for the architecture and design rules. Start at the
|
|
58
|
+
[documentation index](./docs/README.md). The [security policy](./SECURITY.md) covers reporting
|
|
59
|
+
and the security posture.
|
|
60
|
+
|
|
61
|
+
## How it's developed
|
|
51
62
|
|
|
52
|
-
|
|
53
|
-
|
|
63
|
+
This is a standalone repo. Consumer sites install the published package from the npm registry by
|
|
64
|
+
version range. The library's own development proves changes against `examples/showcase`, a
|
|
65
|
+
self-contained SvelteKit site that consumes the package through the relative `file:../..` path, so a
|
|
66
|
+
change is exercised end to end before it publishes.
|
|
54
67
|
|
|
55
|
-
|
|
56
|
-
|
|
68
|
+
The historical rebuild plan and the early architecture writeups live under `docs/internal/`.
|
|
69
|
+
They are kept for history and are not current.
|
|
@@ -1,7 +1,18 @@
|
|
|
1
|
-
import type { CairnAdapter, CairnExtension, CairnRuntime
|
|
1
|
+
import type { CairnAdapter, CairnExtension, CairnRuntime } from './types.js';
|
|
2
|
+
import { type SiteConfig } from '../nav/site-config.js';
|
|
3
|
+
/** The input to {@link composeRuntime}. `siteConfig` is required so the per-concept URL policy is
|
|
4
|
+
* always derived from one source and can never be silently dropped. `extensions` fold in after the
|
|
5
|
+
* adapter's concepts. */
|
|
6
|
+
export interface ComposeInput {
|
|
7
|
+
adapter: CairnAdapter;
|
|
8
|
+
siteConfig: SiteConfig;
|
|
9
|
+
extensions?: CairnExtension[];
|
|
10
|
+
}
|
|
2
11
|
/**
|
|
3
|
-
* Fold an adapter and any extensions into the composed runtime (seam 2).
|
|
4
|
-
*
|
|
12
|
+
* Fold an adapter and any extensions into the composed runtime (seam 2). The per-concept URL policy
|
|
13
|
+
* is derived from the site config, the same source the delivery path uses, so the runtime and
|
|
14
|
+
* delivery permalinks cannot diverge. Extension concepts merge after the adapter's. The asset slot
|
|
15
|
+
* (seam 4) passes through untouched.
|
|
5
16
|
*/
|
|
6
|
-
export declare function composeRuntime(adapter
|
|
17
|
+
export declare function composeRuntime({ adapter, siteConfig, extensions }: ComposeInput): CairnRuntime;
|
|
7
18
|
//# sourceMappingURL=compose.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../../src/lib/content/compose.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAc,YAAY,EAAE,cAAc,EAAE,YAAY,EAAiB,
|
|
1
|
+
{"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../../src/lib/content/compose.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAc,YAAY,EAAE,cAAc,EAAE,YAAY,EAA+B,MAAM,YAAY,CAAC;AAEtH,OAAO,EAAiB,KAAK,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAEvE;;0BAE0B;AAC1B,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,YAAY,CAAC;IACtB,UAAU,EAAE,UAAU,CAAC;IACvB,UAAU,CAAC,EAAE,cAAc,EAAE,CAAC;CAC/B;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,UAAe,EAAE,EAAE,YAAY,GAAG,YAAY,CA0BnG"}
|
package/dist/content/compose.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { normalizeConcepts } from './concepts.js';
|
|
2
|
+
import { urlPolicyFrom } from '../nav/site-config.js';
|
|
2
3
|
/**
|
|
3
|
-
* Fold an adapter and any extensions into the composed runtime (seam 2).
|
|
4
|
-
*
|
|
4
|
+
* Fold an adapter and any extensions into the composed runtime (seam 2). The per-concept URL policy
|
|
5
|
+
* is derived from the site config, the same source the delivery path uses, so the runtime and
|
|
6
|
+
* delivery permalinks cannot diverge. Extension concepts merge after the adapter's. The asset slot
|
|
7
|
+
* (seam 4) passes through untouched.
|
|
5
8
|
*/
|
|
6
|
-
export function composeRuntime(adapter, extensions = []
|
|
9
|
+
export function composeRuntime({ adapter, siteConfig, extensions = [] }) {
|
|
10
|
+
if (!siteConfig)
|
|
11
|
+
throw new Error('composeRuntime needs a site config to derive the URL policy');
|
|
7
12
|
const content = { ...adapter.content };
|
|
8
13
|
const adminPanels = [];
|
|
9
14
|
const fieldTypes = [];
|
|
@@ -19,7 +24,7 @@ export function composeRuntime(adapter, extensions = [], urlPolicy = {}) {
|
|
|
19
24
|
}
|
|
20
25
|
return {
|
|
21
26
|
siteName: adapter.siteName,
|
|
22
|
-
concepts: normalizeConcepts(content,
|
|
27
|
+
concepts: normalizeConcepts(content, urlPolicyFrom(siteConfig)),
|
|
23
28
|
backend: adapter.backend,
|
|
24
29
|
sender: adapter.sender,
|
|
25
30
|
render: adapter.render,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"concepts.d.ts","sourceRoot":"","sources":["../../src/lib/content/concepts.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAElG;;;;GAIG;AACH,eAAO,MAAM,eAAe,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAGjE,CAAC;AAeF;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,GAAG,SAAS,CAAC,EAClD,SAAS,GAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,GAAG,SAAS,CAAM,EAC5D,OAAO,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAmB,GAC/D,iBAAiB,EAAE,
|
|
1
|
+
{"version":3,"file":"concepts.d.ts","sourceRoot":"","sources":["../../src/lib/content/concepts.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAElG;;;;GAIG;AACH,eAAO,MAAM,eAAe,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAGjE,CAAC;AAeF;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,GAAG,SAAS,CAAC,EAClD,SAAS,GAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,GAAG,SAAS,CAAM,EAC5D,OAAO,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAmB,GAC/D,iBAAiB,EAAE,CA0BrB;AAED,yFAAyF;AACzF,wBAAgB,WAAW,CACzB,QAAQ,EAAE,iBAAiB,EAAE,EAC7B,EAAE,EAAE,MAAM,GACT,iBAAiB,GAAG,SAAS,CAE/B"}
|
package/dist/content/concepts.js
CHANGED
|
@@ -29,6 +29,12 @@ export function normalizeConcepts(content, urlPolicy = {}, routing = CONCEPT_ROU
|
|
|
29
29
|
for (const [id, config] of Object.entries(content)) {
|
|
30
30
|
if (!config)
|
|
31
31
|
continue;
|
|
32
|
+
const summaryFields = config.summaryFields ?? [];
|
|
33
|
+
const declared = new Set(config.schema.fields.map((field) => field.name));
|
|
34
|
+
const undeclared = summaryFields.find((key) => !declared.has(key));
|
|
35
|
+
if (undeclared !== undefined) {
|
|
36
|
+
throw new Error(`cairn: concept "${id}" summaryFields key "${undeclared}" is not a declared field`);
|
|
37
|
+
}
|
|
32
38
|
const policy = urlPolicy[id] ?? {};
|
|
33
39
|
descriptors.push({
|
|
34
40
|
id,
|
|
@@ -38,6 +44,7 @@ export function normalizeConcepts(content, urlPolicy = {}, routing = CONCEPT_ROU
|
|
|
38
44
|
permalink: policy.permalink ?? defaultPermalink(id),
|
|
39
45
|
datePrefix: policy.datePrefix ?? 'day',
|
|
40
46
|
fields: config.schema.fields,
|
|
47
|
+
summaryFields,
|
|
41
48
|
validate: config.schema.validate,
|
|
42
49
|
});
|
|
43
50
|
}
|
|
@@ -8,6 +8,14 @@ export declare function frontmatterFromForm(fields: FrontmatterField[], form: Fo
|
|
|
8
8
|
* slicing the ISO string avoids a local-timezone shift.
|
|
9
9
|
*/
|
|
10
10
|
export declare function dateInputValue(value: unknown): string;
|
|
11
|
+
/**
|
|
12
|
+
* True when `s` is a canonical zero-padded `YYYY-MM-DD` string naming a real calendar date.
|
|
13
|
+
* Rejects a wrong format, an impossible month or day, and a JS date-rollover such as
|
|
14
|
+
* `2026-02-30` (which `Date` would silently roll forward to March 2). The committed form a
|
|
15
|
+
* date field carries is exactly this canonical shape, which is what the form and
|
|
16
|
+
* `dateInputValue` emit, so a value outside it is a hand-edit or odd-YAML error.
|
|
17
|
+
*/
|
|
18
|
+
export declare function isCalendarDate(s: string): boolean;
|
|
11
19
|
/** Reassemble a markdown file from frontmatter and body for committing. */
|
|
12
20
|
export declare function serializeMarkdown(frontmatter: object, body: string): string;
|
|
13
21
|
/** Parse a markdown file into its frontmatter and body: the read-side inverse of serialize. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"frontmatter.d.ts","sourceRoot":"","sources":["../../src/lib/content/frontmatter.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,gFAAgF;AAChF,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,gBAAgB,EAAE,EAC1B,IAAI,EAAE,QAAQ,GACb,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA4BzB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CASrD;AAED,2EAA2E;AAC3E,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAE3E;AAED,+FAA+F;AAC/F,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG;IAC7C,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,IAAI,EAAE,MAAM,CAAC;CACd,CAGA"}
|
|
1
|
+
{"version":3,"file":"frontmatter.d.ts","sourceRoot":"","sources":["../../src/lib/content/frontmatter.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,gFAAgF;AAChF,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,gBAAgB,EAAE,EAC1B,IAAI,EAAE,QAAQ,GACb,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA4BzB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CASrD;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAYjD;AAED,2EAA2E;AAC3E,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAE3E;AAED,+FAA+F;AAC/F,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG;IAC7C,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,IAAI,EAAE,MAAM,CAAC;CACd,CAGA"}
|
|
@@ -47,6 +47,25 @@ export function dateInputValue(value) {
|
|
|
47
47
|
}
|
|
48
48
|
return '';
|
|
49
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* True when `s` is a canonical zero-padded `YYYY-MM-DD` string naming a real calendar date.
|
|
52
|
+
* Rejects a wrong format, an impossible month or day, and a JS date-rollover such as
|
|
53
|
+
* `2026-02-30` (which `Date` would silently roll forward to March 2). The committed form a
|
|
54
|
+
* date field carries is exactly this canonical shape, which is what the form and
|
|
55
|
+
* `dateInputValue` emit, so a value outside it is a hand-edit or odd-YAML error.
|
|
56
|
+
*/
|
|
57
|
+
export function isCalendarDate(s) {
|
|
58
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(s);
|
|
59
|
+
if (!match)
|
|
60
|
+
return false;
|
|
61
|
+
const year = Number(match[1]);
|
|
62
|
+
const month = Number(match[2]);
|
|
63
|
+
const day = Number(match[3]);
|
|
64
|
+
const date = new Date(Date.UTC(year, month - 1, day));
|
|
65
|
+
return (date.getUTCFullYear() === year &&
|
|
66
|
+
date.getUTCMonth() === month - 1 &&
|
|
67
|
+
date.getUTCDate() === day);
|
|
68
|
+
}
|
|
50
69
|
/** Reassemble a markdown file from frontmatter and body for committing. */
|
|
51
70
|
export function serializeMarkdown(frontmatter, body) {
|
|
52
71
|
return matter.stringify(body, frontmatter);
|
|
@@ -39,9 +39,26 @@ export declare function serializeManifest(manifest: Manifest): string;
|
|
|
39
39
|
* error. The build regenerates the manifest, so a real file is always canonical; this guards a
|
|
40
40
|
* hand-edited or truncated one. */
|
|
41
41
|
export declare function parseManifest(raw: string): Manifest;
|
|
42
|
-
/**
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
/** A changed entry and the fields that differ between the built and committed manifests. */
|
|
43
|
+
export interface ManifestEntryDiff {
|
|
44
|
+
concept: string;
|
|
45
|
+
id: string;
|
|
46
|
+
fields: string[];
|
|
47
|
+
}
|
|
48
|
+
/** The drift between a freshly built manifest and the committed one, keyed by concept+id. */
|
|
49
|
+
export interface ManifestDiff {
|
|
50
|
+
added: ManifestEntry[];
|
|
51
|
+
removed: ManifestEntry[];
|
|
52
|
+
changed: ManifestEntryDiff[];
|
|
53
|
+
}
|
|
54
|
+
/** Compare a built manifest against a committed one, keyed by concept+id (the same identity
|
|
55
|
+
* upsertEntry and removeEntry use). A changed entry names the fields that differ. Pure, so it is
|
|
56
|
+
* unit-tested apart from any build. */
|
|
57
|
+
export declare function diffManifests(built: Manifest, committed: Manifest): ManifestDiff;
|
|
58
|
+
/** Throw if the committed manifest drifts from what the corpus says. The canonical serialized form
|
|
59
|
+
* is the fast-path equality guard, so semantic equality never spuriously fails. On a mismatch the
|
|
60
|
+
* error names the added, removed, and changed entries, so a raw-git content edit that leaves the
|
|
61
|
+
* committed manifest stale fails the build loudly with what drifted. */
|
|
45
62
|
export declare function verifyManifest(built: Manifest, committedRaw: string): void;
|
|
46
63
|
/** Replace the entry with the same concept and id, or add it. Order does not matter, since
|
|
47
64
|
* serializeManifest sorts. This is the save path's incremental patch. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/lib/content/manifest.ts"],"names":[],"mappings":"AAQA,OAAO,EAAqB,KAAK,QAAQ,EAAE,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAChF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEpD,4FAA4F;AAC5F,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,QAAQ,EAAE,CAAC;CACnB;AAED,yFAAyF;AACzF,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,CAAC,CAAC;IACX,OAAO,EAAE,aAAa,EAAE,CAAC;CAC1B;AAED,+EAA+E;AAC/E,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;CAChB;AAmBD,qFAAqF;AACrF,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,iBAAiB,EAAE,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,aAAa,CAiBvH;AAED,+EAA+E;AAC/E,wBAAgB,aAAa,IAAI,QAAQ,CAExC;AAMD;oGACoG;AACpG,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAW5D;AAED;;;oCAGoC;AACpC,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ,CAqCnD;AAED
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/lib/content/manifest.ts"],"names":[],"mappings":"AAQA,OAAO,EAAqB,KAAK,QAAQ,EAAE,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAChF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEpD,4FAA4F;AAC5F,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,QAAQ,EAAE,CAAC;CACnB;AAED,yFAAyF;AACzF,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,CAAC,CAAC;IACX,OAAO,EAAE,aAAa,EAAE,CAAC;CAC1B;AAED,+EAA+E;AAC/E,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;CAChB;AAmBD,qFAAqF;AACrF,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,iBAAiB,EAAE,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,aAAa,CAiBvH;AAED,+EAA+E;AAC/E,wBAAgB,aAAa,IAAI,QAAQ,CAExC;AAMD;oGACoG;AACpG,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAW5D;AAED;;;oCAGoC;AACpC,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ,CAqCnD;AAED,4FAA4F;AAC5F,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,6FAA6F;AAC7F,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,OAAO,EAAE,iBAAiB,EAAE,CAAC;CAC9B;AAID;;wCAEwC;AACxC,wBAAgB,aAAa,CAAC,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,GAAG,YAAY,CAkBhF;AAWD;;;yEAGyE;AACzE,wBAAgB,cAAc,CAAC,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAa1E;AAED;0EAC0E;AAC1E,wBAAgB,WAAW,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,aAAa,GAAG,QAAQ,CAI9E;AAED,yFAAyF;AACzF,wBAAgB,WAAW,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,QAAQ,CAErF;AAED,2FAA2F;AAC3F,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;sFAEsF;AACtF,wBAAgB,YAAY,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,WAAW,EAAE,CAK3F;AAED;iGACiG;AACjG,wBAAgB,oBAAoB,CAAC,OAAO,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,EAAE,GAAG,WAAW,CAG/G"}
|
package/dist/content/manifest.js
CHANGED
|
@@ -104,13 +104,56 @@ export function parseManifest(raw) {
|
|
|
104
104
|
}
|
|
105
105
|
return { version: 1, entries: obj.entries };
|
|
106
106
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
*
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
107
|
+
const keyOf = (e) => `${e.concept}/${e.id}`;
|
|
108
|
+
/** Compare a built manifest against a committed one, keyed by concept+id (the same identity
|
|
109
|
+
* upsertEntry and removeEntry use). A changed entry names the fields that differ. Pure, so it is
|
|
110
|
+
* unit-tested apart from any build. */
|
|
111
|
+
export function diffManifests(built, committed) {
|
|
112
|
+
const builtByKey = new Map(built.entries.map((e) => [keyOf(e), e]));
|
|
113
|
+
const committedByKey = new Map(committed.entries.map((e) => [keyOf(e), e]));
|
|
114
|
+
const added = built.entries.filter((e) => !committedByKey.has(keyOf(e)));
|
|
115
|
+
const removed = committed.entries.filter((e) => !builtByKey.has(keyOf(e)));
|
|
116
|
+
const changed = [];
|
|
117
|
+
for (const b of built.entries) {
|
|
118
|
+
const c = committedByKey.get(keyOf(b));
|
|
119
|
+
if (!c)
|
|
120
|
+
continue;
|
|
121
|
+
// ManifestEntry has no index signature, so read its keys through an unknown-cast record.
|
|
122
|
+
const br = b;
|
|
123
|
+
const cr = c;
|
|
124
|
+
const fields = [...new Set([...Object.keys(b), ...Object.keys(c)])].filter((k) => JSON.stringify(br[k]) !== JSON.stringify(cr[k]));
|
|
125
|
+
if (fields.length > 0)
|
|
126
|
+
changed.push({ concept: b.concept, id: b.id, fields });
|
|
113
127
|
}
|
|
128
|
+
return { added, removed, changed };
|
|
129
|
+
}
|
|
130
|
+
/** Format a diff into a short human-readable block for a build error. */
|
|
131
|
+
function formatDiff(d) {
|
|
132
|
+
const lines = [];
|
|
133
|
+
for (const e of d.added)
|
|
134
|
+
lines.push(` + ${keyOf(e)}`);
|
|
135
|
+
for (const e of d.removed)
|
|
136
|
+
lines.push(` - ${keyOf(e)}`);
|
|
137
|
+
for (const e of d.changed)
|
|
138
|
+
lines.push(` ~ ${e.concept}/${e.id} (${e.fields.join(', ')})`);
|
|
139
|
+
return lines.join('\n');
|
|
140
|
+
}
|
|
141
|
+
/** Throw if the committed manifest drifts from what the corpus says. The canonical serialized form
|
|
142
|
+
* is the fast-path equality guard, so semantic equality never spuriously fails. On a mismatch the
|
|
143
|
+
* error names the added, removed, and changed entries, so a raw-git content edit that leaves the
|
|
144
|
+
* committed manifest stale fails the build loudly with what drifted. */
|
|
145
|
+
export function verifyManifest(built, committedRaw) {
|
|
146
|
+
const builtRaw = serializeManifest(built);
|
|
147
|
+
if (committedRaw === builtRaw)
|
|
148
|
+
return;
|
|
149
|
+
// Diff the canonical built form, not the raw one. serializeManifest sorts each entry's links, so a
|
|
150
|
+
// build whose links are in extraction order would otherwise report a false (links) drift for an
|
|
151
|
+
// entry whose link set is identical and only the order differs. Reuse the serialized form so both
|
|
152
|
+
// sides are canonical.
|
|
153
|
+
const diff = diffManifests(parseManifest(builtRaw), parseManifest(committedRaw));
|
|
154
|
+
throw new Error('content manifest is stale: the committed file does not match the corpus.\n' +
|
|
155
|
+
formatDiff(diff) +
|
|
156
|
+
'\nRegenerate it (npm run cairn:manifest) and commit the result.');
|
|
114
157
|
}
|
|
115
158
|
/** Replace the entry with the same concept and id, or add it. Order does not matter, since
|
|
116
159
|
* serializeManifest sorts. This is the save path's incremental patch. */
|
package/dist/content/types.d.ts
CHANGED
|
@@ -94,6 +94,9 @@ export interface ConceptConfig<S extends ConceptSchema = ConceptSchema> {
|
|
|
94
94
|
label?: string;
|
|
95
95
|
/** The concept's schema: the form projection, the generated validator, and the inferred type. */
|
|
96
96
|
schema: S;
|
|
97
|
+
/** Frontmatter keys to surface on each `ContentSummary.fields`, so a list card reads an authored
|
|
98
|
+
* field without a per-entry detail read. Each key should also be declared in `schema`. */
|
|
99
|
+
summaryFields?: string[];
|
|
97
100
|
}
|
|
98
101
|
/**
|
|
99
102
|
* A concept's URL policy, set per concept in the YAML site-config (not the adapter). `permalink` is
|
|
@@ -194,6 +197,9 @@ export interface ConceptDescriptor {
|
|
|
194
197
|
/** Filename date-prefix granularity for a dated concept; resolved by `normalizeConcepts`. */
|
|
195
198
|
datePrefix: DatePrefix;
|
|
196
199
|
fields: FrontmatterField[];
|
|
200
|
+
/** Frontmatter keys the index copies onto each summary's `fields` record. `normalizeConcepts`
|
|
201
|
+
* resolves it to `[]` when a concept omits `summaryFields`. */
|
|
202
|
+
summaryFields: string[];
|
|
197
203
|
validate(frontmatter: Record<string, unknown>, body: string): ValidationResult;
|
|
198
204
|
}
|
|
199
205
|
/**
|
|
@@ -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;AAC3C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,0GAA0G;AAC1G,UAAU,SAAS;IACjB,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,kBAAkB;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,gEAAgE;IAChE,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,gCAAgC;AAChC,MAAM,WAAW,SAAU,SAAQ,SAAS;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,qDAAqD;IACrD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,gCAAgC;IAChC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,uCAAuC;IACvC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;yEACqE;IACrE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AACD,+BAA+B;AAC/B,MAAM,WAAW,aAAc,SAAQ,SAAS;IAC9C,IAAI,EAAE,UAAU,CAAC;IACjB,6DAA6D;IAC7D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qDAAqD;IACrD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,gCAAgC;IAChC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,uCAAuC;IACvC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+DAA+D;IAC/D,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AACD,iCAAiC;AACjC,MAAM,WAAW,SAAU,SAAQ,SAAS;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,8CAA8C;IAC9C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,4CAA4C;IAC5C,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AACD,sCAAsC;AACtC,MAAM,WAAW,YAAa,SAAQ,SAAS;IAC7C,IAAI,EAAE,SAAS,CAAC;CACjB;AACD,sEAAsE;AACtE,MAAM,WAAW,SAAU,SAAQ,SAAS;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,iCAAiC;IACjC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5B;AACD,iEAAiE;AACjE,MAAM,WAAW,aAAc,SAAQ,SAAS;IAC9C,IAAI,EAAE,UAAU,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GACxB,SAAS,GACT,aAAa,GACb,SAAS,GACT,YAAY,GACZ,SAAS,GACT,aAAa,CAAC;AAElB;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GACxB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GAC3C;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CAAC;AAElD;;;;;;GAMG;AACH,MAAM,WAAW,aAAa,CAAC,CAAC,SAAS,aAAa,GAAG,aAAa;IACpE,iEAAiE;IACjE,GAAG,EAAE,MAAM,CAAC;IACZ,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iGAAiG;IACjG,MAAM,EAAE,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/lib/content/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,7 +3,10 @@ import type { FrontmatterField, ValidationResult } from './types.js';
|
|
|
3
3
|
* Validate raw frontmatter against a field list. Required text and date fields must be
|
|
4
4
|
* non-empty; required tag fields must be non-empty lists. A present boolean coerces to `true`
|
|
5
5
|
* and an unchecked one is omitted; a present tag field coerces to a string array and an empty
|
|
6
|
-
* one is omitted
|
|
6
|
+
* one is omitted, so validated data carries no key for an absent tag field (`tags` or `freetags`).
|
|
7
|
+
* The delivery read model
|
|
8
|
+
* (`ContentSummary.tags`) fills that case with an empty array; the two layers differ on purpose.
|
|
9
|
+
* An empty optional text or date field is omitted, so the normalized data
|
|
7
10
|
* carries only meaningful values and committed frontmatter stays minimal. Returns the
|
|
8
11
|
* normalized data, or field-keyed errors when any required field is empty.
|
|
9
12
|
*
|
|
@@ -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;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,gBAAgB,EAAE,EAC1B,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACnC,gBAAgB,CAoClB"}
|
package/dist/content/validate.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import { dateInputValue } from './frontmatter.js';
|
|
1
|
+
import { dateInputValue, isCalendarDate } from './frontmatter.js';
|
|
2
2
|
/**
|
|
3
3
|
* Validate raw frontmatter against a field list. Required text and date fields must be
|
|
4
4
|
* non-empty; required tag fields must be non-empty lists. A present boolean coerces to `true`
|
|
5
5
|
* and an unchecked one is omitted; a present tag field coerces to a string array and an empty
|
|
6
|
-
* one is omitted
|
|
6
|
+
* one is omitted, so validated data carries no key for an absent tag field (`tags` or `freetags`).
|
|
7
|
+
* The delivery read model
|
|
8
|
+
* (`ContentSummary.tags`) fills that case with an empty array; the two layers differ on purpose.
|
|
9
|
+
* An empty optional text or date field is omitted, so the normalized data
|
|
7
10
|
* carries only meaningful values and committed frontmatter stays minimal. Returns the
|
|
8
11
|
* normalized data, or field-keyed errors when any required field is empty.
|
|
9
12
|
*
|
|
@@ -27,6 +30,11 @@ export function validateFields(fields, frontmatter) {
|
|
|
27
30
|
const list = Array.isArray(value) ? value.map(String) : [];
|
|
28
31
|
if (field.required && list.length === 0)
|
|
29
32
|
errors[field.name] = `${field.label} is required`;
|
|
33
|
+
else if (field.type === 'tags') {
|
|
34
|
+
const unknown = list.find((tag) => !field.options.includes(tag));
|
|
35
|
+
if (unknown !== undefined)
|
|
36
|
+
errors[field.name] = `${field.label} contains an unknown value: ${unknown}`;
|
|
37
|
+
}
|
|
30
38
|
if (list.length > 0)
|
|
31
39
|
data[field.name] = list;
|
|
32
40
|
break;
|
|
@@ -35,6 +43,8 @@ export function validateFields(fields, frontmatter) {
|
|
|
35
43
|
const text = value instanceof Date ? dateInputValue(value) : typeof value === 'string' ? value.trim() : '';
|
|
36
44
|
if (field.required && text === '')
|
|
37
45
|
errors[field.name] = `${field.label} is required`;
|
|
46
|
+
else if (text !== '' && !isCalendarDate(text))
|
|
47
|
+
errors[field.name] = `${field.label} must be a valid date (YYYY-MM-DD)`;
|
|
38
48
|
if (text !== '')
|
|
39
49
|
data[field.name] = text;
|
|
40
50
|
break;
|
|
@@ -6,16 +6,27 @@ export interface RawFile {
|
|
|
6
6
|
}
|
|
7
7
|
/** The cheap, plain-data view of one entry, for lists, feeds, and the sitemap. */
|
|
8
8
|
export interface ContentSummary {
|
|
9
|
+
/** The descriptor id this entry belongs to, e.g. "posts". Lets a list or page branch per
|
|
10
|
+
* concept without re-deriving it from a proxy like `entry.date`. */
|
|
11
|
+
concept: string;
|
|
9
12
|
id: string;
|
|
10
13
|
slug: string;
|
|
11
14
|
permalink: string;
|
|
12
15
|
title: string;
|
|
13
16
|
date?: string;
|
|
14
17
|
updated?: string;
|
|
18
|
+
/** The entry's tags, always present as an array and empty when the file declares none. This is the
|
|
19
|
+
* read-model normalization. It differs on purpose from the validated `frontmatter.tags`, which the
|
|
20
|
+
* validator omits when empty, so a published file carries no `tags: []` noise. Read `tags` here for
|
|
21
|
+
* a list; read `frontmatter.tags` only when you need the validated, possibly-absent value. */
|
|
15
22
|
tags: string[];
|
|
16
23
|
excerpt: string;
|
|
17
24
|
wordCount: number;
|
|
18
25
|
draft: boolean;
|
|
26
|
+
/** The frontmatter keys the descriptor nominated via `summaryFields`, read off the validated,
|
|
27
|
+
* normalized frontmatter. Held in a separate record so a nominated key cannot collide with a
|
|
28
|
+
* typed summary field. Empty when the concept declares no `summaryFields`. */
|
|
29
|
+
fields: Record<string, unknown>;
|
|
19
30
|
}
|
|
20
31
|
/** The detail view: a summary plus the frontmatter and the body to render. The frontmatter
|
|
21
32
|
* type defaults to `Record<string, unknown>`; the typed-reads pass infers it from the concept
|
|
@@ -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;
|
|
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;yEACqE;IACrE,OAAO,EAAE,MAAM,CAAC;IAChB,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;;;mGAG+F;IAC/F,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;IACf;;mFAE+E;IAC/E,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;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,CA4EjB"}
|
|
@@ -44,7 +44,13 @@ export function createContentIndex(files, descriptor) {
|
|
|
44
44
|
problems.push({ id, draft, errors: result.errors });
|
|
45
45
|
continue;
|
|
46
46
|
}
|
|
47
|
+
const summaryFieldValues = {};
|
|
48
|
+
for (const key of descriptor.summaryFields) {
|
|
49
|
+
if (key in result.data)
|
|
50
|
+
summaryFieldValues[key] = result.data[key];
|
|
51
|
+
}
|
|
47
52
|
entries.push({
|
|
53
|
+
concept: descriptor.id,
|
|
48
54
|
id,
|
|
49
55
|
slug,
|
|
50
56
|
permalink: permalink(descriptor, { id, slug, date }),
|
|
@@ -55,6 +61,7 @@ export function createContentIndex(files, descriptor) {
|
|
|
55
61
|
excerpt: deriveExcerpt(body, { description: asString(raw.description) }),
|
|
56
62
|
wordCount: wordCount(body),
|
|
57
63
|
draft,
|
|
64
|
+
fields: summaryFieldValues,
|
|
58
65
|
frontmatter: result.data,
|
|
59
66
|
body,
|
|
60
67
|
});
|