@glw907/cairn-cms 0.26.0 → 0.29.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 +49 -0
- package/dist/auth/crypto.d.ts +0 -1
- package/dist/auth/store.d.ts +0 -1
- package/dist/auth/types.d.ts +0 -1
- package/dist/components/AdminLayout.svelte.d.ts +0 -1
- package/dist/components/ComponentForm.svelte.d.ts +0 -1
- package/dist/components/ComponentInsertDialog.svelte.d.ts +0 -1
- package/dist/components/ConceptList.svelte.d.ts +0 -1
- package/dist/components/ConfirmPage.svelte.d.ts +0 -1
- package/dist/components/DeleteDialog.svelte.d.ts +0 -1
- package/dist/components/EditPage.svelte.d.ts +0 -1
- package/dist/components/EditorToolbar.svelte.d.ts +0 -1
- package/dist/components/IconPicker.svelte.d.ts +0 -1
- package/dist/components/LinkPicker.svelte.d.ts +0 -1
- package/dist/components/LoginPage.svelte.d.ts +0 -1
- package/dist/components/ManageEditors.svelte.d.ts +0 -1
- package/dist/components/MarkdownEditor.svelte.d.ts +0 -1
- package/dist/components/NavTree.svelte.d.ts +0 -1
- package/dist/components/RenameDialog.svelte.d.ts +0 -1
- package/dist/components/index.d.ts +0 -1
- package/dist/components/link-completion.d.ts +0 -1
- package/dist/components/markdown-format.d.ts +0 -1
- package/dist/content/adapter.d.ts +0 -1
- package/dist/content/compose.d.ts +1 -2
- package/dist/content/compose.js +2 -3
- package/dist/content/concepts.d.ts +7 -1
- package/dist/content/concepts.js +49 -1
- package/dist/content/frontmatter.d.ts +0 -1
- package/dist/content/identity.d.ts +23 -0
- package/dist/content/identity.js +43 -0
- package/dist/content/ids.d.ts +0 -1
- package/dist/content/links.d.ts +0 -1
- package/dist/content/manifest.d.ts +3 -2
- package/dist/content/manifest.js +6 -26
- package/dist/content/permalink.d.ts +0 -1
- package/dist/content/schema.d.ts +0 -1
- package/dist/content/types.d.ts +0 -1
- package/dist/content/validate.d.ts +0 -1
- package/dist/delivery/CairnHead.svelte.d.ts +0 -1
- package/dist/delivery/content-index.d.ts +0 -1
- package/dist/delivery/content-index.js +8 -25
- package/dist/delivery/data.d.ts +0 -1
- package/dist/delivery/excerpt.d.ts +0 -1
- package/dist/delivery/feeds.d.ts +0 -1
- package/dist/delivery/head.d.ts +0 -1
- package/dist/delivery/index.d.ts +0 -1
- package/dist/delivery/json-ld.d.ts +0 -1
- package/dist/delivery/manifest.d.ts +0 -1
- package/dist/delivery/paginate.d.ts +0 -1
- package/dist/delivery/responses.d.ts +0 -1
- package/dist/delivery/robots.d.ts +0 -1
- package/dist/delivery/seo-fields.d.ts +0 -1
- package/dist/delivery/seo.d.ts +0 -1
- package/dist/delivery/site-descriptors.d.ts +0 -1
- package/dist/delivery/site-descriptors.js +5 -6
- package/dist/delivery/site-index.d.ts +0 -1
- package/dist/delivery/site-indexes.d.ts +0 -1
- package/dist/delivery/sitemap.d.ts +0 -1
- package/dist/email.d.ts +0 -1
- package/dist/env.d.ts +0 -1
- package/dist/github/credentials.d.ts +0 -1
- package/dist/github/repo.d.ts +0 -1
- package/dist/github/signing.d.ts +0 -1
- package/dist/github/types.d.ts +0 -1
- package/dist/index.d.ts +1 -28
- package/dist/index.js +1 -23
- package/dist/nav/site-config.d.ts +0 -1
- package/dist/render/component-grammar.d.ts +0 -1
- package/dist/render/component-insert.d.ts +0 -1
- package/dist/render/component-reference.d.ts +0 -1
- package/dist/render/component-validate.d.ts +0 -1
- package/dist/render/glyph.d.ts +0 -1
- package/dist/render/index.d.ts +0 -1
- package/dist/render/pipeline.d.ts +0 -1
- package/dist/render/pipeline.js +5 -1
- package/dist/render/registry.d.ts +0 -1
- package/dist/render/rehype-dispatch.d.ts +0 -1
- package/dist/render/remark-directives.d.ts +0 -1
- package/dist/render/resolve-links.d.ts +0 -1
- package/dist/render/sanitize-schema.d.ts +14 -1
- package/dist/render/sanitize-schema.js +96 -0
- package/dist/sveltekit/auth-routes.d.ts +0 -1
- package/dist/sveltekit/content-routes.d.ts +0 -1
- package/dist/sveltekit/editors-routes.d.ts +0 -1
- package/dist/sveltekit/guard.d.ts +0 -1
- package/dist/sveltekit/health.d.ts +0 -1
- package/dist/sveltekit/index.d.ts +1 -3
- package/dist/sveltekit/index.js +0 -1
- package/dist/sveltekit/nav-routes.d.ts +0 -1
- package/dist/sveltekit/public-routes.d.ts +0 -1
- package/dist/sveltekit/types.d.ts +0 -1
- package/dist/vite/bin.d.ts +0 -1
- package/dist/vite/index.d.ts +0 -1
- package/package.json +2 -1
- package/src/lib/content/compose.ts +3 -3
- package/src/lib/content/concepts.ts +61 -1
- package/src/lib/content/identity.ts +60 -0
- package/src/lib/content/manifest.ts +6 -27
- package/src/lib/delivery/content-index.ts +8 -27
- package/src/lib/delivery/site-descriptors.ts +5 -6
- package/src/lib/index.ts +1 -56
- package/src/lib/render/pipeline.ts +4 -1
- package/src/lib/render/sanitize-schema.ts +97 -0
- package/src/lib/sveltekit/index.ts +2 -8
- package/dist/auth/crypto.d.ts.map +0 -1
- package/dist/auth/store.d.ts.map +0 -1
- package/dist/auth/types.d.ts.map +0 -1
- package/dist/components/AdminLayout.svelte.d.ts.map +0 -1
- package/dist/components/ComponentForm.svelte.d.ts.map +0 -1
- package/dist/components/ComponentInsertDialog.svelte.d.ts.map +0 -1
- package/dist/components/ConceptList.svelte.d.ts.map +0 -1
- package/dist/components/ConfirmPage.svelte.d.ts.map +0 -1
- package/dist/components/DeleteDialog.svelte.d.ts.map +0 -1
- package/dist/components/EditPage.svelte.d.ts.map +0 -1
- package/dist/components/EditorToolbar.svelte.d.ts.map +0 -1
- package/dist/components/IconPicker.svelte.d.ts.map +0 -1
- package/dist/components/LinkPicker.svelte.d.ts.map +0 -1
- package/dist/components/LoginPage.svelte.d.ts.map +0 -1
- package/dist/components/ManageEditors.svelte.d.ts.map +0 -1
- package/dist/components/MarkdownEditor.svelte.d.ts.map +0 -1
- package/dist/components/NavTree.svelte.d.ts.map +0 -1
- package/dist/components/RenameDialog.svelte.d.ts.map +0 -1
- package/dist/components/index.d.ts.map +0 -1
- package/dist/components/link-completion.d.ts.map +0 -1
- package/dist/components/markdown-format.d.ts.map +0 -1
- package/dist/content/adapter.d.ts.map +0 -1
- package/dist/content/compose.d.ts.map +0 -1
- package/dist/content/concepts.d.ts.map +0 -1
- package/dist/content/frontmatter.d.ts.map +0 -1
- package/dist/content/ids.d.ts.map +0 -1
- package/dist/content/links.d.ts.map +0 -1
- package/dist/content/manifest.d.ts.map +0 -1
- package/dist/content/permalink.d.ts.map +0 -1
- package/dist/content/schema.d.ts.map +0 -1
- package/dist/content/types.d.ts.map +0 -1
- package/dist/content/validate.d.ts.map +0 -1
- package/dist/delivery/CairnHead.svelte.d.ts.map +0 -1
- package/dist/delivery/content-index.d.ts.map +0 -1
- package/dist/delivery/data.d.ts.map +0 -1
- package/dist/delivery/excerpt.d.ts.map +0 -1
- package/dist/delivery/feeds.d.ts.map +0 -1
- package/dist/delivery/head.d.ts.map +0 -1
- package/dist/delivery/index.d.ts.map +0 -1
- package/dist/delivery/json-ld.d.ts.map +0 -1
- package/dist/delivery/manifest.d.ts.map +0 -1
- package/dist/delivery/paginate.d.ts.map +0 -1
- package/dist/delivery/responses.d.ts.map +0 -1
- package/dist/delivery/robots.d.ts.map +0 -1
- package/dist/delivery/seo-fields.d.ts.map +0 -1
- package/dist/delivery/seo.d.ts.map +0 -1
- package/dist/delivery/site-descriptors.d.ts.map +0 -1
- package/dist/delivery/site-index.d.ts.map +0 -1
- package/dist/delivery/site-indexes.d.ts.map +0 -1
- package/dist/delivery/sitemap.d.ts.map +0 -1
- package/dist/email.d.ts.map +0 -1
- package/dist/env.d.ts.map +0 -1
- package/dist/github/credentials.d.ts.map +0 -1
- package/dist/github/repo.d.ts.map +0 -1
- package/dist/github/signing.d.ts.map +0 -1
- package/dist/github/types.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/nav/site-config.d.ts.map +0 -1
- package/dist/render/component-grammar.d.ts.map +0 -1
- package/dist/render/component-insert.d.ts.map +0 -1
- package/dist/render/component-reference.d.ts.map +0 -1
- package/dist/render/component-validate.d.ts.map +0 -1
- package/dist/render/glyph.d.ts.map +0 -1
- package/dist/render/index.d.ts.map +0 -1
- package/dist/render/pipeline.d.ts.map +0 -1
- package/dist/render/registry.d.ts.map +0 -1
- package/dist/render/rehype-dispatch.d.ts.map +0 -1
- package/dist/render/remark-directives.d.ts.map +0 -1
- package/dist/render/resolve-links.d.ts.map +0 -1
- package/dist/render/sanitize-schema.d.ts.map +0 -1
- package/dist/sveltekit/auth-routes.d.ts.map +0 -1
- package/dist/sveltekit/content-routes.d.ts.map +0 -1
- package/dist/sveltekit/editors-routes.d.ts.map +0 -1
- package/dist/sveltekit/guard.d.ts.map +0 -1
- package/dist/sveltekit/health.d.ts.map +0 -1
- package/dist/sveltekit/index.d.ts.map +0 -1
- package/dist/sveltekit/nav-routes.d.ts.map +0 -1
- package/dist/sveltekit/public-routes.d.ts.map +0 -1
- package/dist/sveltekit/types.d.ts.map +0 -1
- package/dist/vite/bin.d.ts.map +0 -1
- package/dist/vite/index.d.ts.map +0 -1
|
@@ -19,4 +19,3 @@ export declare function readSeoFields(frontmatter: Record<string, unknown>): Seo
|
|
|
19
19
|
* bare path also anchors to the origin root; against a sub-path origin it would resolve relative to
|
|
20
20
|
* that path, per the WHATWG URL rules. */
|
|
21
21
|
export declare function resolveImageUrl(image: string, origin: string): string | undefined;
|
|
22
|
-
//# sourceMappingURL=seo-fields.d.ts.map
|
package/dist/delivery/seo.d.ts
CHANGED
|
@@ -2,4 +2,3 @@ import type { CairnAdapter, ConceptDescriptor } from '../content/types.js';
|
|
|
2
2
|
import type { SiteConfig } from '../nav/site-config.js';
|
|
3
3
|
/** Per-concept descriptors for a site, from its adapter content and its parsed site config. */
|
|
4
4
|
export declare function siteDescriptors(adapter: CairnAdapter, siteConfig: SiteConfig): ConceptDescriptor[];
|
|
5
|
-
//# sourceMappingURL=site-descriptors.d.ts.map
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
// cairn-cms: the one-call descriptor helper. A delivery site needs the same per-concept
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
import {
|
|
5
|
-
import { urlPolicyFrom } from '../nav/site-config.js';
|
|
1
|
+
// cairn-cms: the one-call descriptor helper. A delivery site needs the same per-concept descriptors
|
|
2
|
+
// the admin runtime uses; this delegates to the shared resolveConcepts so the pairing is one path, not
|
|
3
|
+
// tribal knowledge. The YAML URL policy stays the single source of truth.
|
|
4
|
+
import { resolveConcepts } from '../content/concepts.js';
|
|
6
5
|
/** Per-concept descriptors for a site, from its adapter content and its parsed site config. */
|
|
7
6
|
export function siteDescriptors(adapter, siteConfig) {
|
|
8
|
-
return
|
|
7
|
+
return resolveConcepts(adapter.content, siteConfig);
|
|
9
8
|
}
|
|
@@ -23,4 +23,3 @@ export type SiteIndexes<A extends CairnAdapter> = {
|
|
|
23
23
|
export declare function createSiteIndexes<const A extends CairnAdapter>(adapter: A, config: SiteConfig, globs: SiteGlobs<A>, opts?: {
|
|
24
24
|
validate?: boolean;
|
|
25
25
|
}): SiteIndexes<A>;
|
|
26
|
-
//# sourceMappingURL=site-indexes.d.ts.map
|
package/dist/email.d.ts
CHANGED
package/dist/env.d.ts
CHANGED
|
@@ -9,4 +9,3 @@ export interface GithubKeyEnv {
|
|
|
9
9
|
* installation) and the Worker's private-key secret. Throws when the secret is unset.
|
|
10
10
|
*/
|
|
11
11
|
export declare function appCredentials(backend: Pick<BackendConfig, 'appId' | 'installationId'>, env: GithubKeyEnv): AppCredentials;
|
|
12
|
-
//# sourceMappingURL=credentials.d.ts.map
|
package/dist/github/repo.d.ts
CHANGED
package/dist/github/signing.d.ts
CHANGED
package/dist/github/types.d.ts
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -27,38 +27,11 @@ export type { ReferenceOptions } from './render/component-reference.js';
|
|
|
27
27
|
export { glyph } from './render/glyph.js';
|
|
28
28
|
export type { IconSet } from './render/glyph.js';
|
|
29
29
|
export { remarkDirectiveStamp } from './render/remark-directives.js';
|
|
30
|
-
export { rehypeDispatch,
|
|
30
|
+
export { rehypeDispatch, iconSpan, cardShell, headRow } from './render/rehype-dispatch.js';
|
|
31
31
|
export type { MakeIcon } from './render/rehype-dispatch.js';
|
|
32
32
|
export { createRenderer } from './render/pipeline.js';
|
|
33
33
|
export type { RendererOptions } from './render/pipeline.js';
|
|
34
34
|
export type { RepoRef, RepoFile, CommitAuthor, AppCredentials } from './github/types.js';
|
|
35
35
|
export { CommitConflictError } from './github/types.js';
|
|
36
|
-
export { appJwt, installationToken, signingSelfTest } from './github/signing.js';
|
|
37
|
-
export { treeUrl, markdownFilesIn, listMarkdown, contentsUrl, readRaw, fileSha, commitFile, } from './github/repo.js';
|
|
38
|
-
export { appCredentials } from './github/credentials.js';
|
|
39
|
-
export type { GithubKeyEnv } from './github/credentials.js';
|
|
40
36
|
export { parseSiteConfig, urlPolicyFrom, extractMenu, setMenu, validateNavTree, MAX_NAV_NODES, NavValidationError, SiteConfigError, } from './nav/site-config.js';
|
|
41
37
|
export type { NavNode, SiteConfig } from './nav/site-config.js';
|
|
42
|
-
export { permalink } from './content/permalink.js';
|
|
43
|
-
export { createContentIndex, fromGlob } from './delivery/content-index.js';
|
|
44
|
-
export type { RawFile, ContentSummary, ContentEntry, ContentIndex, ContentProblem, } from './delivery/content-index.js';
|
|
45
|
-
export { createSiteIndex } from './delivery/site-index.js';
|
|
46
|
-
export type { SiteIndex, ConceptIndex } from './delivery/site-index.js';
|
|
47
|
-
export { createSiteIndexes } from './delivery/site-indexes.js';
|
|
48
|
-
export type { SiteIndexes, SiteGlobs } from './delivery/site-indexes.js';
|
|
49
|
-
export { deriveExcerpt, wordCount } from './delivery/excerpt.js';
|
|
50
|
-
export { buildRssFeed, buildJsonFeed } from './delivery/feeds.js';
|
|
51
|
-
export type { FeedChannel, FeedItem } from './delivery/feeds.js';
|
|
52
|
-
export { buildSitemap } from './delivery/sitemap.js';
|
|
53
|
-
export type { SitemapUrl } from './delivery/sitemap.js';
|
|
54
|
-
export { buildRobots } from './delivery/robots.js';
|
|
55
|
-
export { buildSeoMeta } from './delivery/seo.js';
|
|
56
|
-
export type { SeoInput, SeoMeta } from './delivery/seo.js';
|
|
57
|
-
export { readSeoFields, resolveImageUrl } from './delivery/seo-fields.js';
|
|
58
|
-
export type { SeoFields } from './delivery/seo-fields.js';
|
|
59
|
-
export { paginate } from './delivery/paginate.js';
|
|
60
|
-
export type { Page } from './delivery/paginate.js';
|
|
61
|
-
export { rssResponse, jsonFeedResponse, sitemapResponse, robotsResponse } from './delivery/responses.js';
|
|
62
|
-
export { createPublicRoutes } from './sveltekit/public-routes.js';
|
|
63
|
-
export type { PublicRoutesDeps, ListData, TagData, TagIndexData, EntryData } from './sveltekit/public-routes.js';
|
|
64
|
-
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -21,30 +21,8 @@ export { buildComponentInsert } from './render/component-insert.js';
|
|
|
21
21
|
export { generateComponentReference } from './render/component-reference.js';
|
|
22
22
|
export { glyph } from './render/glyph.js';
|
|
23
23
|
export { remarkDirectiveStamp } from './render/remark-directives.js';
|
|
24
|
-
export { rehypeDispatch,
|
|
24
|
+
export { rehypeDispatch, iconSpan, cardShell, headRow } from './render/rehype-dispatch.js';
|
|
25
25
|
export { createRenderer } from './render/pipeline.js';
|
|
26
26
|
export { CommitConflictError } from './github/types.js';
|
|
27
|
-
export { appJwt, installationToken, signingSelfTest } from './github/signing.js';
|
|
28
|
-
export { treeUrl, markdownFilesIn, listMarkdown, contentsUrl, readRaw, fileSha, commitFile, } from './github/repo.js';
|
|
29
|
-
export { appCredentials } from './github/credentials.js';
|
|
30
27
|
// Nav tree and site-config helpers (Plan 06).
|
|
31
28
|
export { parseSiteConfig, urlPolicyFrom, extractMenu, setMenu, validateNavTree, MAX_NAV_NODES, NavValidationError, SiteConfigError, } from './nav/site-config.js';
|
|
32
|
-
// Public content delivery (public-delivery design): the query index, syndication, and
|
|
33
|
-
// discovery surface that sites read. Pure builders plus the one permalink resolver; the
|
|
34
|
-
// SvelteKit loaders live under the /sveltekit subpath.
|
|
35
|
-
export { permalink } from './content/permalink.js';
|
|
36
|
-
export { createContentIndex, fromGlob } from './delivery/content-index.js';
|
|
37
|
-
export { createSiteIndex } from './delivery/site-index.js';
|
|
38
|
-
export { createSiteIndexes } from './delivery/site-indexes.js';
|
|
39
|
-
export { deriveExcerpt, wordCount } from './delivery/excerpt.js';
|
|
40
|
-
export { buildRssFeed, buildJsonFeed } from './delivery/feeds.js';
|
|
41
|
-
export { buildSitemap } from './delivery/sitemap.js';
|
|
42
|
-
export { buildRobots } from './delivery/robots.js';
|
|
43
|
-
export { buildSeoMeta } from './delivery/seo.js';
|
|
44
|
-
export { readSeoFields, resolveImageUrl } from './delivery/seo-fields.js';
|
|
45
|
-
export { paginate } from './delivery/paginate.js';
|
|
46
|
-
// Root superset of the delivery route surface: a wrong guess from root for a route loader or a
|
|
47
|
-
// response helper now resolves. The CairnHead component stays out of root so the root barrel stays
|
|
48
|
-
// node-importable for the unit suite; it resolves from @glw907/cairn-cms/delivery/head.
|
|
49
|
-
export { rssResponse, jsonFeedResponse, sitemapResponse, robotsResponse } from './delivery/responses.js';
|
|
50
|
-
export { createPublicRoutes } from './sveltekit/public-routes.js';
|
|
@@ -11,4 +11,3 @@ export type ComponentInsert = {
|
|
|
11
11
|
/** Serialize a component's form values, then validate the result against its schema. Returns the
|
|
12
12
|
* markdown to insert at the cursor, or the field errors keyed by attribute key or slot name. */
|
|
13
13
|
export declare function buildComponentInsert(def: ComponentDef, values: ComponentValues): Promise<ComponentInsert>;
|
|
14
|
-
//# sourceMappingURL=component-insert.d.ts.map
|
|
@@ -8,4 +8,3 @@ export interface ReferenceOptions {
|
|
|
8
8
|
/** Build a self-contained markdown reference (the llms-full.txt shape) for a component registry, for
|
|
9
9
|
* authors and for pointing an LLM at one curated file. */
|
|
10
10
|
export declare function generateComponentReference(registry: ComponentRegistry, opts: ReferenceOptions): string;
|
|
11
|
-
//# sourceMappingURL=component-reference.d.ts.map
|
package/dist/render/glyph.d.ts
CHANGED
package/dist/render/index.d.ts
CHANGED
package/dist/render/pipeline.js
CHANGED
|
@@ -8,7 +8,7 @@ import rehypeSlug from 'rehype-slug';
|
|
|
8
8
|
import rehypeStringify from 'rehype-stringify';
|
|
9
9
|
import rehypeSanitize from 'rehype-sanitize';
|
|
10
10
|
import { VFile } from 'vfile';
|
|
11
|
-
import { buildSanitizeSchema, rehypeAnchorRel } from './sanitize-schema.js';
|
|
11
|
+
import { buildSanitizeSchema, rehypeAnchorRel, rehypeSinkGuard } from './sanitize-schema.js';
|
|
12
12
|
import { remarkDirectiveStamp } from './remark-directives.js';
|
|
13
13
|
import { remarkResolveCairnLinks, CAIRN_RESOLVE } from './resolve-links.js';
|
|
14
14
|
import { rehypeDispatch } from './rehype-dispatch.js';
|
|
@@ -33,6 +33,10 @@ export function createRenderer(registry = defineRegistry({ components: [] }), op
|
|
|
33
33
|
];
|
|
34
34
|
if (rel !== false)
|
|
35
35
|
rehypePlugins.push([rehypeAnchorRel, rel]);
|
|
36
|
+
// The sink guard runs last, over the fully-built tree, so it neutralizes a sink a component
|
|
37
|
+
// build() emitted after the floor. Gated by the same switch as the floor.
|
|
38
|
+
if (!options.unsafeDisableSanitize)
|
|
39
|
+
rehypePlugins.push(rehypeSinkGuard);
|
|
36
40
|
const processor = unified()
|
|
37
41
|
.use(remarkParse)
|
|
38
42
|
.use(remarkGfm)
|
|
@@ -91,4 +91,3 @@ export interface ComponentValues {
|
|
|
91
91
|
/** Seed an empty {@link ComponentValues} from a component's schema: attribute defaults (or '' / false)
|
|
92
92
|
* and empty slot values ([] for repeatable, '' otherwise). */
|
|
93
93
|
export declare function emptyValues(def: ComponentDef): ComponentValues;
|
|
94
|
-
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -23,4 +23,3 @@ export declare function markFirstList(children: ElementContent[]): Element | und
|
|
|
23
23
|
* `data-rise` while dropping `style`. Nested primitives never get it. Non-primitive
|
|
24
24
|
* content (lede, intro paragraphs, the page-toc nav) passes through untouched. */
|
|
25
25
|
export declare function rehypeDispatch(registry: ComponentRegistry, stagger?: boolean): (tree: Root) => void;
|
|
26
|
-
//# sourceMappingURL=rehype-dispatch.d.ts.map
|
|
@@ -5,4 +5,3 @@ export declare const CAIRN_RESOLVE = "cairnResolve";
|
|
|
5
5
|
* pass through. A missing target is marked with the cairn-broken-link class (the resolver returns
|
|
6
6
|
* undefined) or, when the resolver throws, the error propagates and fails the build. */
|
|
7
7
|
export declare function remarkResolveCairnLinks(): (tree: unknown, file: VFile) => void;
|
|
8
|
-
//# sourceMappingURL=resolve-links.d.ts.map
|
|
@@ -18,4 +18,17 @@ export declare function buildSanitizeSchema(registry: ComponentRegistry, extend?
|
|
|
18
18
|
* `anchorRel` option (default `noopener noreferrer`); a site can override it or disable it entirely.
|
|
19
19
|
*/
|
|
20
20
|
export declare function rehypeAnchorRel(rel: string): (tree: Root) => void;
|
|
21
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Post-dispatch safety floor over the fully-built tree. The pre-dispatch rehype-sanitize floor
|
|
23
|
+
* cleans author content, but a component build() runs after it and can route a raw author
|
|
24
|
+
* attribute value into a sink. This guard runs last and neutralizes those sinks on every element
|
|
25
|
+
* no matter which plugin or which build() produced it: an unsafe URL scheme in a URL-bearing
|
|
26
|
+
* attribute, an inline on* event handler, or an inline style (stripped wholesale, matching the
|
|
27
|
+
* floor and cairn's class-driven styling). It is gated by the same unsafeDisableSanitize switch as
|
|
28
|
+
* the floor.
|
|
29
|
+
*
|
|
30
|
+
* The guard's boundary is the URL scheme check plus the on* and style strip. It does not remove a
|
|
31
|
+
* build()-emitted raw script, style, or iframe srcdoc element node. A build() that emits those is
|
|
32
|
+
* running site-developer code, and author markdown is cleaned by the pre-dispatch floor.
|
|
33
|
+
*/
|
|
34
|
+
export declare function rehypeSinkGuard(): (tree: Root) => void;
|
|
@@ -56,3 +56,99 @@ export function rehypeAnchorRel(rel) {
|
|
|
56
56
|
});
|
|
57
57
|
};
|
|
58
58
|
}
|
|
59
|
+
// URL-bearing hast properties the post-dispatch guard scheme-checks. hast camelCases attribute
|
|
60
|
+
// names through property-information (srcset -> srcSet, xlink:href -> xLinkHref with a capital L,
|
|
61
|
+
// formaction -> formAction). data is the <object data> URL attribute; data-* attributes camelCase
|
|
62
|
+
// to dataFoo and are not matched here.
|
|
63
|
+
const URL_PROPS = new Set([
|
|
64
|
+
'href',
|
|
65
|
+
'src',
|
|
66
|
+
'srcSet',
|
|
67
|
+
'xLinkHref',
|
|
68
|
+
'poster',
|
|
69
|
+
'formAction',
|
|
70
|
+
'action',
|
|
71
|
+
'data',
|
|
72
|
+
'background',
|
|
73
|
+
]);
|
|
74
|
+
// The safe URL schemes: the union of every protocol list in defaultSchema, plus cairn. The
|
|
75
|
+
// floor admits these and strips the rest, so deriving from the same source keeps the floor and
|
|
76
|
+
// this guard from drifting on what a safe scheme is. javascript:/data:/vbscript: are never in
|
|
77
|
+
// defaultSchema, so they are never safe.
|
|
78
|
+
const SAFE_SCHEMES = (() => {
|
|
79
|
+
const protocols = defaultSchema.protocols ?? {};
|
|
80
|
+
const schemes = new Set(['cairn']);
|
|
81
|
+
for (const list of Object.values(protocols)) {
|
|
82
|
+
for (const scheme of list ?? [])
|
|
83
|
+
schemes.add(String(scheme).toLowerCase());
|
|
84
|
+
}
|
|
85
|
+
return schemes;
|
|
86
|
+
})();
|
|
87
|
+
// Read a URL value's scheme for the safety check, defeating the whitespace and control-character
|
|
88
|
+
// tricks a browser ignores inside a scheme (java\tscript:, a leading space). A value with no
|
|
89
|
+
// scheme (relative, anchor, query) returns undefined and is always safe.
|
|
90
|
+
function urlScheme(value) {
|
|
91
|
+
const cleaned = value.replace(/[\x00-\x20]+/g, '');
|
|
92
|
+
const match = /^([a-z][a-z0-9+.-]*):/i.exec(cleaned);
|
|
93
|
+
return match ? match[1].toLowerCase() : undefined;
|
|
94
|
+
}
|
|
95
|
+
function isSafeUrl(value) {
|
|
96
|
+
const scheme = urlScheme(value);
|
|
97
|
+
return scheme === undefined || SAFE_SCHEMES.has(scheme);
|
|
98
|
+
}
|
|
99
|
+
// srcset is "url descriptor, url descriptor, …". hast may store it as a string or, because
|
|
100
|
+
// property-information marks it comma-separated, as a string array. One unsafe candidate makes
|
|
101
|
+
// the whole attribute unsafe.
|
|
102
|
+
function isSafeSrcset(value) {
|
|
103
|
+
const candidates = Array.isArray(value)
|
|
104
|
+
? value.map(String)
|
|
105
|
+
: typeof value === 'string'
|
|
106
|
+
? value.split(',')
|
|
107
|
+
: [];
|
|
108
|
+
return candidates.every((candidate) => {
|
|
109
|
+
const url = candidate.trim().split(/\s+/)[0];
|
|
110
|
+
return url === '' || isSafeUrl(url);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
// Decide whether one URL-bearing property value is safe to keep. srcset has its own
|
|
114
|
+
// multi-candidate grammar. A non-string value carries no scheme to abuse, so the floor's own
|
|
115
|
+
// handling stands and the guard leaves it alone.
|
|
116
|
+
function isSafeUrlProp(key, value) {
|
|
117
|
+
if (key === 'srcSet')
|
|
118
|
+
return isSafeSrcset(value);
|
|
119
|
+
if (typeof value !== 'string')
|
|
120
|
+
return true;
|
|
121
|
+
return isSafeUrl(value);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Post-dispatch safety floor over the fully-built tree. The pre-dispatch rehype-sanitize floor
|
|
125
|
+
* cleans author content, but a component build() runs after it and can route a raw author
|
|
126
|
+
* attribute value into a sink. This guard runs last and neutralizes those sinks on every element
|
|
127
|
+
* no matter which plugin or which build() produced it: an unsafe URL scheme in a URL-bearing
|
|
128
|
+
* attribute, an inline on* event handler, or an inline style (stripped wholesale, matching the
|
|
129
|
+
* floor and cairn's class-driven styling). It is gated by the same unsafeDisableSanitize switch as
|
|
130
|
+
* the floor.
|
|
131
|
+
*
|
|
132
|
+
* The guard's boundary is the URL scheme check plus the on* and style strip. It does not remove a
|
|
133
|
+
* build()-emitted raw script, style, or iframe srcdoc element node. A build() that emits those is
|
|
134
|
+
* running site-developer code, and author markdown is cleaned by the pre-dispatch floor.
|
|
135
|
+
*/
|
|
136
|
+
export function rehypeSinkGuard() {
|
|
137
|
+
return (tree) => {
|
|
138
|
+
visit(tree, 'element', (node) => {
|
|
139
|
+
const props = node.properties;
|
|
140
|
+
if (!props)
|
|
141
|
+
return;
|
|
142
|
+
for (const key of Object.keys(props)) {
|
|
143
|
+
if (/^on/i.test(key) || key === 'style') {
|
|
144
|
+
delete props[key];
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (!URL_PROPS.has(key))
|
|
148
|
+
continue;
|
|
149
|
+
if (!isSafeUrlProp(key, props[key]))
|
|
150
|
+
delete props[key];
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
};
|
|
154
|
+
}
|
|
@@ -89,4 +89,3 @@ export declare function createContentRoutes(runtime: CairnRuntime, deps?: Conten
|
|
|
89
89
|
renameAction: (event: ContentEvent) => Promise<ReturnType<typeof fail> | never>;
|
|
90
90
|
mintToken: (env: GithubKeyEnv) => Promise<string>;
|
|
91
91
|
};
|
|
92
|
-
//# sourceMappingURL=content-routes.d.ts.map
|
|
@@ -6,4 +6,3 @@ export declare function createAuthGuard(): ({ event, resolve }: HandleInput) =>
|
|
|
6
6
|
export declare function requireSession(event: RequestContext): Editor;
|
|
7
7
|
/** For the management surface: a signed-in owner, or 403 for an editor. */
|
|
8
8
|
export declare function requireOwner(event: RequestContext): Editor;
|
|
9
|
-
//# sourceMappingURL=guard.d.ts.map
|
|
@@ -7,6 +7,4 @@ export { createNavRoutes } from './nav-routes.js';
|
|
|
7
7
|
export type { NavLoadData, NavPageOption, NavRoutesDeps } from './nav-routes.js';
|
|
8
8
|
export { healthLoad, type HealthData } from './health.js';
|
|
9
9
|
export type { RequestContext, CookieJar, HandleInput } from './types.js';
|
|
10
|
-
export {
|
|
11
|
-
export type { PublicRoutesDeps, ListData as PublicListData, TagData, TagIndexData, EntryData, } from './public-routes.js';
|
|
12
|
-
//# sourceMappingURL=index.d.ts.map
|
|
10
|
+
export type { GithubKeyEnv } from '../github/credentials.js';
|
package/dist/sveltekit/index.js
CHANGED
package/dist/vite/bin.d.ts
CHANGED
package/dist/vite/index.d.ts
CHANGED
|
@@ -30,4 +30,3 @@ export declare function cairnManifest(opts: CairnManifestOptions): Plugin;
|
|
|
30
30
|
* resolution, and writes the serialized manifest. The cairn-manifest bin calls this; it is exported
|
|
31
31
|
* so the write logic is testable apart from the CLI shell. */
|
|
32
32
|
export declare function writeManifest(cwd?: string): Promise<void>;
|
|
33
|
-
//# sourceMappingURL=index.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glw907/cairn-cms",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.0",
|
|
4
4
|
"description": "Embedded, magic-link, GitHub-committing CMS for SvelteKit/Cloudflare sites.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": [
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"scripts": {
|
|
29
29
|
"package": "svelte-package && chmod +x dist/vite/bin.js",
|
|
30
30
|
"check:package": "npm run package && publint --strict && attw --pack . --ignore-rules no-resolution cjs-resolves-to-esm internal-resolution-error",
|
|
31
|
+
"check:reference": "npm run package && node scripts/reference-coverage.mjs",
|
|
31
32
|
"prepare": "npm run package",
|
|
32
33
|
"check": "svelte-check --tsconfig ./tsconfig.json",
|
|
33
34
|
"test": "vitest run",
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
// concepts, components, field types, and save hooks. Shaped now so the extension contract
|
|
5
5
|
// is additive later.
|
|
6
6
|
import type { AdminPanel, CairnAdapter, CairnExtension, CairnRuntime, ConceptConfig, FieldTypeDef } from './types.js';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import { resolveConcepts } from './concepts.js';
|
|
8
|
+
import type { SiteConfig } from '../nav/site-config.js';
|
|
9
9
|
|
|
10
10
|
/** The input to {@link composeRuntime}. `siteConfig` is required so the per-concept URL policy is
|
|
11
11
|
* always derived from one source and can never be silently dropped. `extensions` fold in after the
|
|
@@ -36,7 +36,7 @@ export function composeRuntime({ adapter, siteConfig, extensions = [] }: Compose
|
|
|
36
36
|
}
|
|
37
37
|
return {
|
|
38
38
|
siteName: adapter.siteName,
|
|
39
|
-
concepts:
|
|
39
|
+
concepts: resolveConcepts(content, siteConfig),
|
|
40
40
|
backend: adapter.backend,
|
|
41
41
|
sender: adapter.sender,
|
|
42
42
|
render: adapter.render,
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// future Fragments concept attaches by adding one key under `content` and one routing
|
|
5
5
|
// entry, with no reshape here.
|
|
6
6
|
import type { ConceptConfig, ConceptDescriptor, ConceptUrlPolicy, RoutingRule } from './types.js';
|
|
7
|
+
import { urlPolicyFrom, type SiteConfig } from '../nav/site-config.js';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Concept-fixed routing, keyed by concept id (spec §7.2). Posts are dated feed entries;
|
|
@@ -28,6 +29,43 @@ function defaultPermalink(id: string): string {
|
|
|
28
29
|
return id === 'pages' ? '/:slug' : `/${id}/:slug`;
|
|
29
30
|
}
|
|
30
31
|
|
|
32
|
+
/** Permalink tokens the resolver understands. */
|
|
33
|
+
const KNOWN_TOKENS = new Set(['slug', 'year', 'month', 'day']);
|
|
34
|
+
/** The date-bearing tokens; valid only for a dated concept. */
|
|
35
|
+
const DATE_TOKENS = new Set(['year', 'month', 'day']);
|
|
36
|
+
/** The valid date-prefix granularities. A runtime check, since the YAML is untyped. */
|
|
37
|
+
const DATE_PREFIXES = new Set<string>(['year', 'month', 'day']);
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Validate one concept's URL policy at build, so a misconfigured permalink or datePrefix fails loudly
|
|
41
|
+
* here rather than emitting a wrong or defaulted URL at render. The permalink must be root-relative and
|
|
42
|
+
* use only known tokens, a date token requires a dated concept, and the datePrefix must be in range.
|
|
43
|
+
*/
|
|
44
|
+
function validateUrlPolicy(id: string, policy: ConceptUrlPolicy, dated: boolean): void {
|
|
45
|
+
if (policy.permalink !== undefined) {
|
|
46
|
+
const pattern = policy.permalink;
|
|
47
|
+
if (!pattern.startsWith('/')) {
|
|
48
|
+
throw new Error(`cairn: concept "${id}" permalink "${pattern}" must start with "/"`);
|
|
49
|
+
}
|
|
50
|
+
for (const match of pattern.matchAll(/:(\w+)/g)) {
|
|
51
|
+
const token = match[1];
|
|
52
|
+
if (!KNOWN_TOKENS.has(token)) {
|
|
53
|
+
throw new Error(`cairn: concept "${id}" permalink "${pattern}" uses unknown token ":${token}"`);
|
|
54
|
+
}
|
|
55
|
+
if (DATE_TOKENS.has(token) && !dated) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
`cairn: concept "${id}" is not dated, so permalink "${pattern}" cannot use the date token ":${token}"`,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (policy.datePrefix !== undefined && !DATE_PREFIXES.has(policy.datePrefix)) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
`cairn: concept "${id}" datePrefix "${policy.datePrefix}" must be one of year, month, day`,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
31
69
|
/**
|
|
32
70
|
* Normalize an adapter's declared concepts into uniform descriptors (seam 1). URL policy
|
|
33
71
|
* (`permalink`, `datePrefix`) comes from the YAML site-config, passed here as `urlPolicy` keyed by
|
|
@@ -41,6 +79,14 @@ export function normalizeConcepts(
|
|
|
41
79
|
routing: Readonly<Record<string, RoutingRule>> = CONCEPT_ROUTING,
|
|
42
80
|
): ConceptDescriptor[] {
|
|
43
81
|
const descriptors: ConceptDescriptor[] = [];
|
|
82
|
+
const declaredConcepts = new Set(
|
|
83
|
+
Object.keys(content).filter((key) => content[key] !== undefined),
|
|
84
|
+
);
|
|
85
|
+
for (const key of Object.keys(urlPolicy)) {
|
|
86
|
+
if (!declaredConcepts.has(key)) {
|
|
87
|
+
throw new Error(`cairn: URL policy names concept "${key}", which is not declared under content`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
44
90
|
for (const [id, config] of Object.entries(content)) {
|
|
45
91
|
if (!config) continue;
|
|
46
92
|
const summaryFields = config.summaryFields ?? [];
|
|
@@ -51,12 +97,14 @@ export function normalizeConcepts(
|
|
|
51
97
|
`cairn: concept "${id}" summaryFields key "${undeclared}" is not a declared field`,
|
|
52
98
|
);
|
|
53
99
|
}
|
|
100
|
+
const conceptRouting = routing[id] ?? DEFAULT_ROUTING;
|
|
54
101
|
const policy = urlPolicy[id] ?? {};
|
|
102
|
+
validateUrlPolicy(id, policy, conceptRouting.dated);
|
|
55
103
|
descriptors.push({
|
|
56
104
|
id,
|
|
57
105
|
label: config.label ?? defaultLabel(id),
|
|
58
106
|
dir: config.dir,
|
|
59
|
-
routing:
|
|
107
|
+
routing: conceptRouting,
|
|
60
108
|
permalink: policy.permalink ?? defaultPermalink(id),
|
|
61
109
|
datePrefix: policy.datePrefix ?? 'day',
|
|
62
110
|
fields: config.schema.fields,
|
|
@@ -67,6 +115,18 @@ export function normalizeConcepts(
|
|
|
67
115
|
return descriptors;
|
|
68
116
|
}
|
|
69
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Resolve a site's concept descriptors from its content map and parsed site config. The admin runtime
|
|
120
|
+
* (composeRuntime) and the delivery layer (siteDescriptors) both call this, so the per-concept URL
|
|
121
|
+
* policy is derived once from the YAML and the runtime and delivery permalinks cannot diverge.
|
|
122
|
+
*/
|
|
123
|
+
export function resolveConcepts(
|
|
124
|
+
content: Record<string, ConceptConfig | undefined>,
|
|
125
|
+
siteConfig: SiteConfig,
|
|
126
|
+
): ConceptDescriptor[] {
|
|
127
|
+
return normalizeConcepts(content, urlPolicyFrom(siteConfig));
|
|
128
|
+
}
|
|
129
|
+
|
|
70
130
|
/** Look up a normalized concept by id, or undefined when the site does not enable it. */
|
|
71
131
|
export function findConcept(
|
|
72
132
|
concepts: ConceptDescriptor[],
|