@glw907/cairn-cms 0.5.0 → 0.6.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (216) hide show
  1. package/dist/auth/crypto.d.ts +13 -0
  2. package/dist/auth/crypto.d.ts.map +1 -0
  3. package/dist/auth/crypto.js +31 -0
  4. package/dist/auth/store.d.ts +41 -0
  5. package/dist/auth/store.d.ts.map +1 -0
  6. package/dist/auth/store.js +115 -0
  7. package/dist/auth/types.d.ts +25 -0
  8. package/dist/auth/types.d.ts.map +1 -0
  9. package/dist/auth/types.js +1 -0
  10. package/dist/components/AdminLayout.svelte +58 -108
  11. package/dist/components/AdminLayout.svelte.d.ts +14 -9
  12. package/dist/components/AdminLayout.svelte.d.ts.map +1 -1
  13. package/dist/components/ComponentPalette.svelte +50 -0
  14. package/dist/components/ComponentPalette.svelte.d.ts +16 -0
  15. package/dist/components/ComponentPalette.svelte.d.ts.map +1 -0
  16. package/dist/components/ConceptList.svelte +81 -0
  17. package/dist/components/ConceptList.svelte.d.ts +13 -0
  18. package/dist/components/ConceptList.svelte.d.ts.map +1 -0
  19. package/dist/components/ConfirmPage.svelte +23 -20
  20. package/dist/components/ConfirmPage.svelte.d.ts +6 -0
  21. package/dist/components/ConfirmPage.svelte.d.ts.map +1 -1
  22. package/dist/components/EditPage.svelte +160 -103
  23. package/dist/components/EditPage.svelte.d.ts +17 -7
  24. package/dist/components/EditPage.svelte.d.ts.map +1 -1
  25. package/dist/components/LoginPage.svelte +42 -52
  26. package/dist/components/LoginPage.svelte.d.ts +12 -0
  27. package/dist/components/LoginPage.svelte.d.ts.map +1 -1
  28. package/dist/components/ManageEditors.svelte +81 -0
  29. package/dist/components/ManageEditors.svelte.d.ts +24 -0
  30. package/dist/components/ManageEditors.svelte.d.ts.map +1 -0
  31. package/dist/components/MarkdownEditor.svelte +81 -0
  32. package/dist/components/MarkdownEditor.svelte.d.ts +20 -0
  33. package/dist/components/MarkdownEditor.svelte.d.ts.map +1 -0
  34. package/dist/components/NavTree.svelte +138 -0
  35. package/dist/components/NavTree.svelte.d.ts +17 -0
  36. package/dist/components/NavTree.svelte.d.ts.map +1 -0
  37. package/dist/components/cairn-admin.css +42 -0
  38. package/dist/components/index.d.ts +5 -2
  39. package/dist/components/index.d.ts.map +1 -1
  40. package/dist/components/index.js +7 -4
  41. package/dist/content/compose.d.ts +7 -0
  42. package/dist/content/compose.d.ts.map +1 -0
  43. package/dist/content/compose.js +32 -0
  44. package/dist/content/concepts.d.ts +17 -0
  45. package/dist/content/concepts.d.ts.map +1 -0
  46. package/dist/content/concepts.js +41 -0
  47. package/dist/content/frontmatter.d.ts +18 -0
  48. package/dist/content/frontmatter.d.ts.map +1 -0
  49. package/dist/content/frontmatter.js +58 -0
  50. package/dist/content/ids.d.ts +17 -0
  51. package/dist/content/ids.d.ts.map +1 -0
  52. package/dist/content/ids.js +33 -0
  53. package/dist/content/types.d.ts +210 -0
  54. package/dist/content/types.d.ts.map +1 -0
  55. package/dist/content/types.js +1 -0
  56. package/dist/content/validate.d.ts +13 -0
  57. package/dist/content/validate.d.ts.map +1 -0
  58. package/dist/content/validate.js +45 -0
  59. package/dist/email.d.ts +25 -12
  60. package/dist/email.d.ts.map +1 -1
  61. package/dist/email.js +24 -24
  62. package/dist/env.d.ts +24 -0
  63. package/dist/env.d.ts.map +1 -0
  64. package/dist/env.js +29 -0
  65. package/dist/github/credentials.d.ts +12 -0
  66. package/dist/github/credentials.d.ts.map +1 -0
  67. package/dist/github/credentials.js +11 -0
  68. package/dist/github/repo.d.ts +49 -0
  69. package/dist/github/repo.d.ts.map +1 -0
  70. package/dist/github/repo.js +123 -0
  71. package/dist/github/signing.d.ts +17 -0
  72. package/dist/github/signing.d.ts.map +1 -0
  73. package/dist/github/signing.js +79 -0
  74. package/dist/github/types.d.ts +35 -0
  75. package/dist/github/types.d.ts.map +1 -0
  76. package/dist/github/types.js +19 -0
  77. package/dist/index.d.ts +27 -6
  78. package/dist/index.d.ts.map +1 -1
  79. package/dist/index.js +21 -8
  80. package/dist/nav/site-config.d.ts +50 -0
  81. package/dist/nav/site-config.d.ts.map +1 -0
  82. package/dist/nav/site-config.js +100 -0
  83. package/dist/render/glyph.d.ts +1 -1
  84. package/dist/render/glyph.d.ts.map +1 -1
  85. package/dist/render/index.d.ts +5 -5
  86. package/dist/render/index.d.ts.map +1 -1
  87. package/dist/render/index.js +6 -6
  88. package/dist/render/pipeline.d.ts +3 -3
  89. package/dist/render/pipeline.d.ts.map +1 -1
  90. package/dist/render/pipeline.js +4 -4
  91. package/dist/render/registry.d.ts +6 -4
  92. package/dist/render/registry.d.ts.map +1 -1
  93. package/dist/render/registry.js +8 -6
  94. package/dist/render/rehype-dispatch.d.ts +1 -1
  95. package/dist/render/rehype-dispatch.d.ts.map +1 -1
  96. package/dist/render/remark-directives.d.ts +1 -1
  97. package/dist/render/remark-directives.d.ts.map +1 -1
  98. package/dist/render/sanitize.d.ts +8 -0
  99. package/dist/render/sanitize.d.ts.map +1 -0
  100. package/dist/render/sanitize.js +26 -0
  101. package/dist/sveltekit/auth-routes.d.ts +23 -0
  102. package/dist/sveltekit/auth-routes.d.ts.map +1 -0
  103. package/dist/sveltekit/auth-routes.js +85 -0
  104. package/dist/sveltekit/content-routes.d.ts +80 -0
  105. package/dist/sveltekit/content-routes.d.ts.map +1 -0
  106. package/dist/sveltekit/content-routes.js +183 -0
  107. package/dist/sveltekit/editors-routes.d.ts +24 -0
  108. package/dist/sveltekit/editors-routes.d.ts.map +1 -0
  109. package/dist/sveltekit/editors-routes.js +73 -0
  110. package/dist/sveltekit/guard.d.ts +9 -0
  111. package/dist/sveltekit/guard.d.ts.map +1 -0
  112. package/dist/sveltekit/guard.js +43 -0
  113. package/dist/sveltekit/health.d.ts +19 -0
  114. package/dist/sveltekit/health.d.ts.map +1 -0
  115. package/dist/sveltekit/health.js +12 -0
  116. package/dist/sveltekit/index.d.ts +9 -83
  117. package/dist/sveltekit/index.d.ts.map +1 -1
  118. package/dist/sveltekit/index.js +8 -149
  119. package/dist/sveltekit/nav-routes.d.ts +30 -0
  120. package/dist/sveltekit/nav-routes.d.ts.map +1 -0
  121. package/dist/sveltekit/nav-routes.js +103 -0
  122. package/dist/sveltekit/types.d.ts +32 -0
  123. package/dist/sveltekit/types.d.ts.map +1 -0
  124. package/dist/sveltekit/types.js +1 -0
  125. package/package.json +38 -58
  126. package/src/lib/auth/crypto.ts +37 -0
  127. package/src/lib/auth/store.ts +158 -0
  128. package/src/lib/auth/types.ts +27 -0
  129. package/src/lib/components/AdminLayout.svelte +58 -108
  130. package/src/lib/components/ComponentPalette.svelte +50 -0
  131. package/src/lib/components/ConceptList.svelte +81 -0
  132. package/src/lib/components/ConfirmPage.svelte +23 -20
  133. package/src/lib/components/EditPage.svelte +160 -103
  134. package/src/lib/components/LoginPage.svelte +42 -52
  135. package/src/lib/components/ManageEditors.svelte +81 -0
  136. package/src/lib/components/MarkdownEditor.svelte +81 -0
  137. package/src/lib/components/NavTree.svelte +138 -0
  138. package/src/lib/components/cairn-admin.css +42 -0
  139. package/src/lib/components/index.ts +7 -4
  140. package/src/lib/content/compose.ts +39 -0
  141. package/src/lib/content/concepts.ts +57 -0
  142. package/src/lib/content/frontmatter.ts +71 -0
  143. package/src/lib/content/ids.ts +38 -0
  144. package/src/lib/content/types.ts +235 -0
  145. package/src/lib/content/validate.ts +51 -0
  146. package/src/lib/email.ts +52 -38
  147. package/src/lib/env.ts +32 -0
  148. package/src/lib/github/credentials.ts +27 -0
  149. package/src/lib/github/repo.ts +138 -0
  150. package/src/lib/github/signing.ts +97 -0
  151. package/src/lib/github/types.ts +46 -0
  152. package/src/lib/index.ts +86 -8
  153. package/src/lib/nav/site-config.ts +124 -0
  154. package/src/lib/render/glyph.ts +6 -6
  155. package/src/lib/render/index.ts +6 -6
  156. package/src/lib/render/pipeline.ts +22 -22
  157. package/src/lib/render/registry.ts +33 -26
  158. package/src/lib/render/rehype-dispatch.ts +47 -47
  159. package/src/lib/render/remark-directives.ts +46 -46
  160. package/src/lib/render/sanitize.ts +27 -0
  161. package/src/lib/sveltekit/auth-routes.ts +107 -0
  162. package/src/lib/sveltekit/content-routes.ts +261 -0
  163. package/src/lib/sveltekit/editors-routes.ts +82 -0
  164. package/src/lib/sveltekit/guard.ts +47 -0
  165. package/src/lib/sveltekit/health.ts +24 -0
  166. package/src/lib/sveltekit/index.ts +19 -235
  167. package/src/lib/sveltekit/nav-routes.ts +139 -0
  168. package/src/lib/sveltekit/types.ts +33 -0
  169. package/dist/adapter.d.ts +0 -69
  170. package/dist/adapter.d.ts.map +0 -1
  171. package/dist/adapter.js +0 -30
  172. package/dist/auth/admins.d.ts +0 -33
  173. package/dist/auth/admins.d.ts.map +0 -1
  174. package/dist/auth/admins.js +0 -90
  175. package/dist/auth/config.d.ts +0 -2097
  176. package/dist/auth/config.d.ts.map +0 -1
  177. package/dist/auth/config.js +0 -78
  178. package/dist/auth/guard.d.ts +0 -34
  179. package/dist/auth/guard.d.ts.map +0 -1
  180. package/dist/auth/guard.js +0 -47
  181. package/dist/auth/index.d.ts +0 -4
  182. package/dist/auth/index.d.ts.map +0 -1
  183. package/dist/auth/index.js +0 -6
  184. package/dist/auth/schema.d.ts +0 -750
  185. package/dist/auth/schema.d.ts.map +0 -1
  186. package/dist/auth/schema.js +0 -93
  187. package/dist/carta.d.ts +0 -39
  188. package/dist/carta.d.ts.map +0 -1
  189. package/dist/carta.js +0 -30
  190. package/dist/components/AdminList.svelte +0 -33
  191. package/dist/components/AdminList.svelte.d.ts +0 -10
  192. package/dist/components/AdminList.svelte.d.ts.map +0 -1
  193. package/dist/components/ManageAdmins.svelte +0 -84
  194. package/dist/components/ManageAdmins.svelte.d.ts +0 -10
  195. package/dist/components/ManageAdmins.svelte.d.ts.map +0 -1
  196. package/dist/content.d.ts +0 -3
  197. package/dist/content.d.ts.map +0 -1
  198. package/dist/content.js +0 -10
  199. package/dist/github.d.ts +0 -72
  200. package/dist/github.d.ts.map +0 -1
  201. package/dist/github.js +0 -171
  202. package/dist/utils.d.ts +0 -3
  203. package/dist/utils.d.ts.map +0 -1
  204. package/dist/utils.js +0 -11
  205. package/src/lib/adapter.ts +0 -119
  206. package/src/lib/auth/admins.ts +0 -106
  207. package/src/lib/auth/config.ts +0 -108
  208. package/src/lib/auth/guard.ts +0 -60
  209. package/src/lib/auth/index.ts +0 -6
  210. package/src/lib/auth/schema.ts +0 -112
  211. package/src/lib/carta.ts +0 -59
  212. package/src/lib/components/AdminList.svelte +0 -33
  213. package/src/lib/components/ManageAdmins.svelte +0 -84
  214. package/src/lib/content.ts +0 -11
  215. package/src/lib/github.ts +0 -220
  216. package/src/lib/utils.ts +0 -12
@@ -0,0 +1,17 @@
1
+ import type { AppCredentials } from './types.js';
2
+ /** Mint a GitHub App JWT (RS256), valid ~9 min, with `iat` backdated for clock skew. */
3
+ export declare function appJwt(appId: string, privateKeyPem: string): Promise<string>;
4
+ /** Exchange the App JWT for a short-lived installation access token. */
5
+ export declare function installationToken(creds: AppCredentials): Promise<string>;
6
+ /**
7
+ * Deploy-time self-test for the App signer: sign a dummy JWT with the configured key. It
8
+ * exercises the brittle PKCS#1-to-PKCS#8 conversion and the Web Crypto import and sign with
9
+ * no network call and no secret in the result, so `/admin/healthz` (Plan 05) catches a bad
10
+ * or rotated key before an editor's save fails. The `detail` is a fixed classifier, never the
11
+ * raw crypto error, so the surfaced health result cannot echo key bytes. Never throws.
12
+ */
13
+ export declare function signingSelfTest(appId: string, privateKeyB64: string): Promise<{
14
+ ok: boolean;
15
+ detail?: string;
16
+ }>;
17
+ //# sourceMappingURL=signing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signing.d.ts","sourceRoot":"","sources":["../../src/lib/github/signing.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AA0CjD,wFAAwF;AACxF,wBAAsB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAclF;AAED,wEAAwE;AACxE,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAa9E;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAQrH"}
@@ -0,0 +1,79 @@
1
+ const API = 'https://api.github.com';
2
+ const encoder = new TextEncoder();
3
+ /** Encode bytes as unpadded base64url (RFC 4648 §5), the JWT wire format. */
4
+ function bytesToB64url(bytes) {
5
+ const binary = Array.from(bytes, (b) => String.fromCharCode(b)).join('');
6
+ return btoa(binary).replaceAll('+', '-').replaceAll('/', '_').replaceAll('=', '');
7
+ }
8
+ // TextEncoder/atob produce Uint8Arrays whose generic buffer type no longer satisfies Web
9
+ // Crypto's BufferSource under strict lib types; hand the underlying ArrayBuffer over.
10
+ function buf(bytes) {
11
+ return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
12
+ }
13
+ /** DER length octets for a value of `n` bytes (short form < 128, else long form). */
14
+ function derLength(n) {
15
+ if (n < 0x80)
16
+ return [n];
17
+ const out = [];
18
+ for (let v = n; v > 0; v >>= 8)
19
+ out.unshift(v & 0xff);
20
+ return [0x80 | out.length, ...out];
21
+ }
22
+ // AlgorithmIdentifier for rsaEncryption (OID 1.2.840.113549.1.1.1) with NULL parameters.
23
+ const RSA_ALG_ID = [0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00];
24
+ /** Wrap a PKCS#1 RSAPrivateKey (DER) as PKCS#8 (the only RSA form Web Crypto importKey takes). */
25
+ function pkcs1ToPkcs8(pkcs1) {
26
+ const octet = [0x04, ...derLength(pkcs1.length), ...pkcs1];
27
+ const body = [0x02, 0x01, 0x00, ...RSA_ALG_ID, ...octet];
28
+ return Uint8Array.from([0x30, ...derLength(body.length), ...body]);
29
+ }
30
+ /** Decode a PEM private key to PKCS#8 DER, converting from PKCS#1 (GitHub's format) if needed. */
31
+ function pemToPkcs8(pem) {
32
+ const b64 = pem.replace(/-----[^-]+-----/g, '').replace(/\s+/g, '');
33
+ const der = Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));
34
+ return pem.includes('RSA PRIVATE KEY') ? pkcs1ToPkcs8(der) : der;
35
+ }
36
+ /** Mint a GitHub App JWT (RS256), valid ~9 min, with `iat` backdated for clock skew. */
37
+ export async function appJwt(appId, privateKeyPem) {
38
+ const now = Math.floor(Date.now() / 1000);
39
+ const header = bytesToB64url(encoder.encode(JSON.stringify({ alg: 'RS256', typ: 'JWT' })));
40
+ const payload = bytesToB64url(encoder.encode(JSON.stringify({ iat: now - 60, exp: now + 540, iss: appId })));
41
+ const signingInput = `${header}.${payload}`;
42
+ const key = await crypto.subtle.importKey('pkcs8', buf(pemToPkcs8(privateKeyPem)), { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' }, false, ['sign']);
43
+ const sig = await crypto.subtle.sign('RSASSA-PKCS1-v1_5', key, buf(encoder.encode(signingInput)));
44
+ return `${signingInput}.${bytesToB64url(new Uint8Array(sig))}`;
45
+ }
46
+ /** Exchange the App JWT for a short-lived installation access token. */
47
+ export async function installationToken(creds) {
48
+ const jwt = await appJwt(creds.appId, atob(creds.privateKeyB64));
49
+ const res = await fetch(`${API}/app/installations/${creds.installationId}/access_tokens`, {
50
+ method: 'POST',
51
+ headers: {
52
+ Accept: 'application/vnd.github+json',
53
+ Authorization: `Bearer ${jwt}`,
54
+ 'User-Agent': 'cairn-cms',
55
+ 'X-GitHub-Api-Version': '2022-11-28',
56
+ },
57
+ });
58
+ if (!res.ok)
59
+ throw new Error(`GitHub installation token failed: ${res.status}`);
60
+ return (await res.json()).token;
61
+ }
62
+ /**
63
+ * Deploy-time self-test for the App signer: sign a dummy JWT with the configured key. It
64
+ * exercises the brittle PKCS#1-to-PKCS#8 conversion and the Web Crypto import and sign with
65
+ * no network call and no secret in the result, so `/admin/healthz` (Plan 05) catches a bad
66
+ * or rotated key before an editor's save fails. The `detail` is a fixed classifier, never the
67
+ * raw crypto error, so the surfaced health result cannot echo key bytes. Never throws.
68
+ */
69
+ export async function signingSelfTest(appId, privateKeyB64) {
70
+ try {
71
+ const jwt = await appJwt(appId, atob(privateKeyB64));
72
+ if (jwt.split('.').length !== 3)
73
+ return { ok: false, detail: 'malformed JWT' };
74
+ return { ok: true };
75
+ }
76
+ catch {
77
+ return { ok: false, detail: 'key import or sign failed' };
78
+ }
79
+ }
@@ -0,0 +1,35 @@
1
+ /** Repo coordinates pinned to a branch: the structural subset of `BackendConfig` the read and commit paths need. */
2
+ export interface RepoRef {
3
+ owner: string;
4
+ repo: string;
5
+ branch: string;
6
+ }
7
+ /** A markdown file in a concept directory. `id` is the filename without `.md`. */
8
+ export interface RepoFile {
9
+ id: string;
10
+ name: string;
11
+ path: string;
12
+ }
13
+ /** A commit author: the signed-in editor (spec §7.4). The committer is left to the App. */
14
+ export interface CommitAuthor {
15
+ name: string;
16
+ email: string;
17
+ }
18
+ /** What the App signer needs: the app id, the installation, and the base64 PEM secret. */
19
+ export interface AppCredentials {
20
+ appId: string;
21
+ installationId: string;
22
+ /** The stored `GITHUB_APP_PRIVATE_KEY_B64`: base64 of the PEM, single line. */
23
+ privateKeyB64: string;
24
+ }
25
+ /**
26
+ * A concurrent edit lost the SHA race: the file changed between the read and the PUT, from
27
+ * another editor or the site's own CI. Thrown so the save fails safe (re-fetch and ask the
28
+ * editor to reapply) instead of surfacing a raw 409. Defined and caught inside the package
29
+ * so `instanceof` is reliable, unlike kit's `redirect`/`error` across the peer boundary.
30
+ */
31
+ export declare class CommitConflictError extends Error {
32
+ readonly path: string;
33
+ constructor(path: string);
34
+ }
35
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/lib/github/types.ts"],"names":[],"mappings":"AAMA,oHAAoH;AACpH,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,kFAAkF;AAClF,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,2FAA2F;AAC3F,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,0FAA0F;AAC1F,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;IACvB,+EAA+E;IAC/E,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;;;;GAKG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;aAChB,IAAI,EAAE,MAAM;gBAAZ,IAAI,EAAE,MAAM;CAIzC"}
@@ -0,0 +1,19 @@
1
+ // src/lib/github/types.ts
2
+ // cairn-cms: the GitHub backend's plain data types and its one typed error. The backend
3
+ // reads repo coordinates from the adapter's `BackendConfig` (spec §8); `RepoRef` is the
4
+ // `{ owner, repo, branch }` subset, so `backend` is assignable wherever a `RepoRef` is
5
+ // wanted with no conversion.
6
+ /**
7
+ * A concurrent edit lost the SHA race: the file changed between the read and the PUT, from
8
+ * another editor or the site's own CI. Thrown so the save fails safe (re-fetch and ask the
9
+ * editor to reapply) instead of surfacing a raw 409. Defined and caught inside the package
10
+ * so `instanceof` is reliable, unlike kit's `redirect`/`error` across the peer boundary.
11
+ */
12
+ export class CommitConflictError extends Error {
13
+ path;
14
+ constructor(path) {
15
+ super(`Commit conflict on ${path}: it changed since it was opened`);
16
+ this.path = path;
17
+ this.name = 'CommitConflictError';
18
+ }
19
+ }
package/dist/index.d.ts CHANGED
@@ -1,7 +1,28 @@
1
- export * from './email';
2
- export * from './github';
3
- export * from './carta';
4
- export * from './content';
5
- export * from './adapter';
6
- export * from './render';
1
+ export { requireOrigin } from './env.js';
2
+ export type { Role, Editor, AuthEnv } from './auth/types.js';
3
+ export type { AuthBranding, MagicLinkMessage, SendMagicLink } from './email.js';
4
+ export { buildMagicLinkMessage, cloudflareSend } from './email.js';
5
+ export type { CairnAdapter, ConceptConfig, FrontmatterField, TextField, TextareaField, DateField, BooleanField, TagsField, FreeTagsField, ValidationResult, BackendConfig, SenderConfig, NavMenuConfig, AssetConfig, RoutingRule, ConceptDescriptor, CairnExtension, CairnRuntime, AdminPanel, FieldTypeDef, } from './content/types.js';
6
+ export { CONCEPT_ROUTING, normalizeConcepts, findConcept } from './content/concepts.js';
7
+ export { composeRuntime } from './content/compose.js';
8
+ export { frontmatterFromForm, dateInputValue, serializeMarkdown, parseMarkdown, } from './content/frontmatter.js';
9
+ export { validateFields } from './content/validate.js';
10
+ export { isValidId, idFromFilename, filenameFromId, slugify } from './content/ids.js';
11
+ export { defineRegistry } from './render/registry.js';
12
+ export type { ComponentDef, ComponentRegistry } from './render/registry.js';
13
+ export { glyph } from './render/glyph.js';
14
+ export type { IconSet } from './render/glyph.js';
15
+ export { remarkDirectiveStamp } from './render/remark-directives.js';
16
+ export { rehypeDispatch, isElement, strProp, iconSpan, splitHead, cardShell, markFirstList, } from './render/rehype-dispatch.js';
17
+ export type { MakeIcon } from './render/rehype-dispatch.js';
18
+ export { createRenderer } from './render/pipeline.js';
19
+ export type { RendererOptions } from './render/pipeline.js';
20
+ export type { RepoRef, RepoFile, CommitAuthor, AppCredentials } from './github/types.js';
21
+ export { CommitConflictError } from './github/types.js';
22
+ export { appJwt, installationToken, signingSelfTest } from './github/signing.js';
23
+ export { treeUrl, markdownFilesIn, listMarkdown, contentsUrl, readRaw, fileSha, commitFile, } from './github/repo.js';
24
+ export { appCredentials } from './github/credentials.js';
25
+ export type { GithubKeyEnv } from './github/credentials.js';
26
+ export { parseSiteConfig, extractMenu, setMenu, validateNavTree, MAX_NAV_NODES, NavValidationError, SiteConfigError, } from './nav/site-config.js';
27
+ export type { NavNode, SiteConfig } from './nav/site-config.js';
7
28
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/lib/index.ts"],"names":[],"mappings":"AAEA,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/lib/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC7D,YAAY,EAAE,YAAY,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAChF,OAAO,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAGnE,YAAY,EACV,YAAY,EACZ,aAAa,EACb,gBAAgB,EAChB,SAAS,EACT,aAAa,EACb,SAAS,EACT,YAAY,EACZ,SAAS,EACT,aAAa,EACb,gBAAgB,EAChB,aAAa,EACb,YAAY,EACZ,aAAa,EACb,WAAW,EACX,WAAW,EACX,iBAAiB,EACjB,cAAc,EACd,YAAY,EACZ,UAAU,EACV,YAAY,GACb,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACxF,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EACL,mBAAmB,EACnB,cAAc,EACd,iBAAiB,EACjB,aAAa,GACd,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAEtF,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,YAAY,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAC5E,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC1C,YAAY,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EACL,cAAc,EACd,SAAS,EACT,OAAO,EACP,QAAQ,EACR,SAAS,EACT,SAAS,EACT,aAAa,GACd,MAAM,6BAA6B,CAAC;AACrC,YAAY,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,YAAY,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAG5D,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACzF,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,MAAM,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACjF,OAAO,EACL,OAAO,EACP,eAAe,EACf,YAAY,EACZ,WAAW,EACX,OAAO,EACP,OAAO,EACP,UAAU,GACX,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACzD,YAAY,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAG5D,OAAO,EACL,eAAe,EACf,WAAW,EACX,OAAO,EACP,eAAe,EACf,aAAa,EACb,kBAAkB,EAClB,eAAe,GAChB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC"}
package/dist/index.js CHANGED
@@ -1,8 +1,21 @@
1
- // cairn-cms public API. Consumers import content/email/github/adapter from 'cairn-cms';
2
- // auth (better-auth factory, guards, manage-editors) lives at the 'cairn-cms/auth' subpath.
3
- export * from './email';
4
- export * from './github';
5
- export * from './carta';
6
- export * from './content';
7
- export * from './adapter';
8
- export * from './render';
1
+ // Engine entry. Auth landed in Plan 01, the content model and adapter in Plan 02, and the
2
+ // GitHub read-and-commit backend in Plan 03; render and nav follow.
3
+ export { requireOrigin } from './env.js';
4
+ export { buildMagicLinkMessage, cloudflareSend } from './email.js';
5
+ export { CONCEPT_ROUTING, normalizeConcepts, findConcept } from './content/concepts.js';
6
+ export { composeRuntime } from './content/compose.js';
7
+ export { frontmatterFromForm, dateInputValue, serializeMarkdown, parseMarkdown, } from './content/frontmatter.js';
8
+ export { validateFields } from './content/validate.js';
9
+ export { isValidId, idFromFilename, filenameFromId, slugify } from './content/ids.js';
10
+ // Render engine (Plan 04): generic directive pipeline; sites own the component registry.
11
+ export { defineRegistry } from './render/registry.js';
12
+ export { glyph } from './render/glyph.js';
13
+ export { remarkDirectiveStamp } from './render/remark-directives.js';
14
+ export { rehypeDispatch, isElement, strProp, iconSpan, splitHead, cardShell, markFirstList, } from './render/rehype-dispatch.js';
15
+ export { createRenderer } from './render/pipeline.js';
16
+ export { CommitConflictError } from './github/types.js';
17
+ export { appJwt, installationToken, signingSelfTest } from './github/signing.js';
18
+ export { treeUrl, markdownFilesIn, listMarkdown, contentsUrl, readRaw, fileSha, commitFile, } from './github/repo.js';
19
+ export { appCredentials } from './github/credentials.js';
20
+ // Nav tree and site-config helpers (Plan 06).
21
+ export { parseSiteConfig, extractMenu, setMenu, validateNavTree, MAX_NAV_NODES, NavValidationError, SiteConfigError, } from './nav/site-config.js';
@@ -0,0 +1,50 @@
1
+ /** One navigation node. An omitted or empty `url` is a label-only grouping header; no `children` is a leaf. */
2
+ export interface NavNode {
3
+ label: string;
4
+ url?: string;
5
+ children?: NavNode[];
6
+ }
7
+ /** Total node cap across the whole tree, a guard against a runaway payload. */
8
+ export declare const MAX_NAV_NODES = 200;
9
+ /** Maximum character length for a node label. */
10
+ export declare const MAX_LABEL_LENGTH = 500;
11
+ /** Maximum character length for a node URL. */
12
+ export declare const MAX_URL_LENGTH = 2048;
13
+ export declare class NavValidationError extends Error {
14
+ constructor(message: string);
15
+ }
16
+ /**
17
+ * Validate and normalize an untrusted value into a NavNode[]: arrays only, non-empty labels, depth
18
+ * within `maxDepth` (1 is flat), a bounded node count, and only the three known keys kept. Throws
19
+ * NavValidationError on any violation. Used by navSave before writing.
20
+ */
21
+ export declare function validateNavTree(value: unknown, maxDepth: number): NavNode[];
22
+ /**
23
+ * Shape of the YAML site-config file. Unknown keys are ignored so the file can grow without an
24
+ * engine change. Read at build time by the public site.
25
+ */
26
+ export interface SiteConfig {
27
+ siteName: string;
28
+ description?: string;
29
+ author?: string;
30
+ url?: string;
31
+ locale?: string;
32
+ /** Named navigation menus, each a NavNode[] (normalized by extractMenu). */
33
+ menus?: Record<string, unknown>;
34
+ [key: string]: unknown;
35
+ }
36
+ export declare class SiteConfigError extends Error {
37
+ constructor(message: string);
38
+ }
39
+ /** Parse the YAML site-config text into a typed object. Throws SiteConfigError on a malformed root. */
40
+ export declare function parseSiteConfig(raw: string): SiteConfig;
41
+ /** Extract one named menu from a parsed config and validate it. Returns [] when the menu is absent. */
42
+ export declare function extractMenu(config: SiteConfig, name: string, maxDepth: number): NavNode[];
43
+ /**
44
+ * Replace one named menu in the YAML site-config text and reserialize, preserving every other
45
+ * top-level key (siteName, other menus, settings). Parses into a Document so the rest of the file
46
+ * round-trips. YAML comments are not preserved (an accepted trade); data keys are. A leaf node
47
+ * serializes without `url`/`children` keys.
48
+ */
49
+ export declare function setMenu(raw: string, name: string, tree: NavNode[]): string;
50
+ //# sourceMappingURL=site-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"site-config.d.ts","sourceRoot":"","sources":["../../src/lib/nav/site-config.ts"],"names":[],"mappings":"AAMA,+GAA+G;AAC/G,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;CACtB;AAED,+EAA+E;AAC/E,eAAO,MAAM,aAAa,MAAM,CAAC;AAEjC,iDAAiD;AACjD,eAAO,MAAM,gBAAgB,MAAM,CAAC;AAEpC,+CAA+C;AAC/C,eAAO,MAAM,cAAc,OAAO,CAAC;AAKnC,qBAAa,kBAAmB,SAAQ,KAAK;gBAC/B,OAAO,EAAE,MAAM;CAI5B;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,EAAE,CA6B3E;AAED;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,qBAAa,eAAgB,SAAQ,KAAK;gBAC5B,OAAO,EAAE,MAAM;CAI5B;AAED,uGAAuG;AACvG,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAUvD;AAED,uGAAuG;AACvG,wBAAgB,WAAW,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,EAAE,CAIzF;AAED;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,MAAM,CAO1E"}
@@ -0,0 +1,100 @@
1
+ // The navigation tree and its YAML site-config. A menu lives in the site's git-committed config
2
+ // under `menus.<name>`, read at build time by the public layout and edited from /admin/nav, which
3
+ // commits the file back through the GitHub-App pipeline. This module is pure: parse, validate, and
4
+ // rewrite only. The engine returns data; each site renders the tree with its own markup.
5
+ import { parse as parseYaml, parseDocument } from 'yaml';
6
+ /** Total node cap across the whole tree, a guard against a runaway payload. */
7
+ export const MAX_NAV_NODES = 200;
8
+ /** Maximum character length for a node label. */
9
+ export const MAX_LABEL_LENGTH = 500;
10
+ /** Maximum character length for a node URL. */
11
+ export const MAX_URL_LENGTH = 2048;
12
+ /** Allowlist for safe URL schemes: site-relative, in-page anchors, http(s), mailto, and tel. */
13
+ const SAFE_URL = /^(\/|#|https?:\/\/|mailto:|tel:)/i;
14
+ export class NavValidationError extends Error {
15
+ constructor(message) {
16
+ super(message);
17
+ this.name = 'NavValidationError';
18
+ }
19
+ }
20
+ /**
21
+ * Validate and normalize an untrusted value into a NavNode[]: arrays only, non-empty labels, depth
22
+ * within `maxDepth` (1 is flat), a bounded node count, and only the three known keys kept. Throws
23
+ * NavValidationError on any violation. Used by navSave before writing.
24
+ */
25
+ export function validateNavTree(value, maxDepth) {
26
+ let count = 0;
27
+ function walk(nodes, depth) {
28
+ if (!Array.isArray(nodes))
29
+ throw new NavValidationError('Navigation must be a list of items');
30
+ if (depth > maxDepth)
31
+ throw new NavValidationError(`Navigation is nested deeper than ${maxDepth} levels`);
32
+ return nodes.map((raw) => {
33
+ if (typeof raw !== 'object' || raw === null)
34
+ throw new NavValidationError('Each item must be an object');
35
+ const item = raw;
36
+ const label = typeof item.label === 'string' ? item.label.trim() : '';
37
+ if (!label)
38
+ throw new NavValidationError('Each item needs a label');
39
+ if (label.length > MAX_LABEL_LENGTH)
40
+ throw new NavValidationError('Label is too long (max 500 characters)');
41
+ if (++count > MAX_NAV_NODES)
42
+ throw new NavValidationError('Too many navigation items');
43
+ const node = { label };
44
+ if (typeof item.url === 'string' && item.url.trim()) {
45
+ const url = item.url.trim();
46
+ if (url.length > MAX_URL_LENGTH)
47
+ throw new NavValidationError('URL is too long (max 2048 characters)');
48
+ if (!SAFE_URL.test(url))
49
+ throw new NavValidationError('URL must start with /, #, http(s)://, mailto:, or tel:');
50
+ node.url = url;
51
+ }
52
+ if (item.children !== undefined) {
53
+ const children = walk(item.children, depth + 1);
54
+ if (children.length)
55
+ node.children = children;
56
+ }
57
+ return node;
58
+ });
59
+ }
60
+ return walk(value, 1);
61
+ }
62
+ export class SiteConfigError extends Error {
63
+ constructor(message) {
64
+ super(message);
65
+ this.name = 'SiteConfigError';
66
+ }
67
+ }
68
+ /** Parse the YAML site-config text into a typed object. Throws SiteConfigError on a malformed root. */
69
+ export function parseSiteConfig(raw) {
70
+ const parsed = parseYaml(raw);
71
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
72
+ throw new SiteConfigError('Site config must be a YAML mapping');
73
+ }
74
+ const { siteName } = parsed;
75
+ if (typeof siteName !== 'string' || !siteName.trim()) {
76
+ throw new SiteConfigError('Site config needs a siteName');
77
+ }
78
+ return parsed;
79
+ }
80
+ /** Extract one named menu from a parsed config and validate it. Returns [] when the menu is absent. */
81
+ export function extractMenu(config, name, maxDepth) {
82
+ const menu = config.menus?.[name];
83
+ if (menu === undefined)
84
+ return [];
85
+ return validateNavTree(menu, maxDepth);
86
+ }
87
+ /**
88
+ * Replace one named menu in the YAML site-config text and reserialize, preserving every other
89
+ * top-level key (siteName, other menus, settings). Parses into a Document so the rest of the file
90
+ * round-trips. YAML comments are not preserved (an accepted trade); data keys are. A leaf node
91
+ * serializes without `url`/`children` keys.
92
+ */
93
+ export function setMenu(raw, name, tree) {
94
+ const doc = parseDocument(raw);
95
+ if (doc.get('siteName') === undefined) {
96
+ throw new SiteConfigError('Site config must be a mapping with a siteName');
97
+ }
98
+ doc.setIn(['menus', name], tree);
99
+ return doc.toString();
100
+ }
@@ -1,5 +1,5 @@
1
1
  import type { Element } from 'hast';
2
- /** A glyph name SVG path-data map (the site owns the icon set). */
2
+ /** A glyph name to SVG path-data map (the site owns the icon set). */
3
3
  export type IconSet = Record<string, string>;
4
4
  /** Inline SVG glyph as a real hast node: class ec-glyph, 256 viewBox, currentColor fill. */
5
5
  export declare function glyph(name: string, icons: IconSet): Element;
@@ -1 +1 @@
1
- {"version":3,"file":"glyph.d.ts","sourceRoot":"","sources":["../../src/lib/render/glyph.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAEpC,qEAAqE;AACrE,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE7C,4FAA4F;AAC5F,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAM3D"}
1
+ {"version":3,"file":"glyph.d.ts","sourceRoot":"","sources":["../../src/lib/render/glyph.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAEpC,sEAAsE;AACtE,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE7C,4FAA4F;AAC5F,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAM3D"}
@@ -1,6 +1,6 @@
1
- export * from './registry';
2
- export * from './glyph';
3
- export * from './remark-directives';
4
- export * from './rehype-dispatch';
5
- export * from './pipeline';
1
+ export * from './registry.js';
2
+ export * from './glyph.js';
3
+ export * from './remark-directives.js';
4
+ export * from './rehype-dispatch.js';
5
+ export * from './pipeline.js';
6
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/render/index.ts"],"names":[],"mappings":"AAGA,cAAc,YAAY,CAAC;AAC3B,cAAc,SAAS,CAAC;AACxB,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/render/index.ts"],"names":[],"mappings":"AAGA,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,eAAe,CAAC"}
@@ -1,8 +1,8 @@
1
- // cairn-cms render engine: a directive-driven markdown HTML pipeline whose
1
+ // cairn-cms render engine: a directive-driven markdown to HTML pipeline whose
2
2
  // component vocabulary is supplied by a site's component registry. The site owns the
3
3
  // component builders, class names, icon set, and CSS; the engine owns the machinery.
4
- export * from './registry';
5
- export * from './glyph';
6
- export * from './remark-directives';
7
- export * from './rehype-dispatch';
8
- export * from './pipeline';
4
+ export * from './registry.js';
5
+ export * from './glyph.js';
6
+ export * from './remark-directives.js';
7
+ export * from './rehype-dispatch.js';
8
+ export * from './pipeline.js';
@@ -1,12 +1,12 @@
1
1
  import { type PluggableList } from 'unified';
2
- import type { ComponentRegistry } from './registry';
2
+ import type { ComponentRegistry } from './registry.js';
3
3
  export interface RendererOptions {
4
4
  /** A site's per-index motion formula for the top-level rise stagger
5
5
  * (e.g. ecnordic's `(i) => '--rise:' + …`). Omit for no stagger. */
6
6
  rise?: (idx: number) => string;
7
7
  }
8
- /** Compose a site's render pipeline from its component registry: directive syntax
9
- * stamped markers registry-built hast. Returns `renderMarkdown` plus the remark/
8
+ /** Compose a site's render pipeline from its component registry: directive syntax to
9
+ * stamped markers to registry-built hast. Returns `renderMarkdown` plus the remark/
10
10
  * rehype plugin arrays (so the Carta editor preview can reuse the exact same set). */
11
11
  export declare function createRenderer(registry: ComponentRegistry, options?: RendererOptions): {
12
12
  remarkPlugins: PluggableList;
@@ -1 +1 @@
1
- {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/lib/render/pipeline.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,KAAK,aAAa,EAAE,MAAM,SAAS,CAAC;AAUtD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEpD,MAAM,WAAW,eAAe;IAC/B;yEACqE;IACrE,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;CAC/B;AAED;;uFAEuF;AACvF,wBAAgB,cAAc,CAAC,QAAQ,EAAE,iBAAiB,EAAE,OAAO,GAAE,eAAoB;;;8BAavD,MAAM,KAAG,OAAO,CAAC,MAAM,CAAC;EAEzD"}
1
+ {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/lib/render/pipeline.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,KAAK,aAAa,EAAE,MAAM,SAAS,CAAC;AAUtD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAEvD,MAAM,WAAW,eAAe;IAC9B;yEACqE;IACrE,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;CAChC;AAED;;uFAEuF;AACvF,wBAAgB,cAAc,CAAC,QAAQ,EAAE,iBAAiB,EAAE,OAAO,GAAE,eAAoB;;;8BAarD,MAAM,KAAG,OAAO,CAAC,MAAM,CAAC;EAE3D"}
@@ -6,10 +6,10 @@ import remarkRehype from 'remark-rehype';
6
6
  import rehypeRaw from 'rehype-raw';
7
7
  import rehypeSlug from 'rehype-slug';
8
8
  import rehypeStringify from 'rehype-stringify';
9
- import { remarkDirectiveStamp } from './remark-directives';
10
- import { rehypeDispatch } from './rehype-dispatch';
11
- /** Compose a site's render pipeline from its component registry: directive syntax
12
- * stamped markers registry-built hast. Returns `renderMarkdown` plus the remark/
9
+ import { remarkDirectiveStamp } from './remark-directives.js';
10
+ import { rehypeDispatch } from './rehype-dispatch.js';
11
+ /** Compose a site's render pipeline from its component registry: directive syntax to
12
+ * stamped markers to registry-built hast. Returns `renderMarkdown` plus the remark/
13
13
  * rehype plugin arrays (so the Carta editor preview can reuse the exact same set). */
14
14
  export function createRenderer(registry, options = {}) {
15
15
  const remarkPlugins = [remarkDirective, [remarkDirectiveStamp, registry]];
@@ -11,7 +11,7 @@ export interface ComponentDef {
11
11
  insertTemplate: string;
12
12
  /** Build the final hast element from the stamped directive element. */
13
13
  build: (node: Element, rise?: string) => Element;
14
- /** Optional roledefault-icon (e.g. `{ caution: 'warning' }`). */
14
+ /** Optional role-to-default-icon, e.g. `{ caution: 'warning' }`. */
15
15
  defaultIconByRole?: Record<string, string>;
16
16
  }
17
17
  export interface ComponentRegistry {
@@ -20,9 +20,11 @@ export interface ComponentRegistry {
20
20
  get(name: string): ComponentDef | undefined;
21
21
  defaultIcon(name: string, role?: string): string | undefined;
22
22
  }
23
- /** Build a registry from a site's component definitions. The single source the
24
- * render pipeline (directive stamp + rehype dispatch) and the editor palette read. */
25
- export declare function defineRegistry(input: {
23
+ /**
24
+ * Build a registry from a site's component definitions. The single source the render
25
+ * pipeline (directive stamp plus rehype dispatch) and the editor palette both read.
26
+ */
27
+ export declare function defineRegistry({ components }: {
26
28
  components: ComponentDef[];
27
29
  }): ComponentRegistry;
28
30
  //# sourceMappingURL=registry.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/lib/render/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAEpC,6EAA6E;AAC7E,MAAM,WAAW,YAAY;IAC5B,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAC;IACb,qBAAqB;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,2BAA2B;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,sEAAsE;IACtE,cAAc,EAAE,MAAM,CAAC;IACvB,uEAAuE;IACvE,KAAK,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC;IACjD,kEAAkE;IAClE,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC3C;AAED,MAAM,WAAW,iBAAiB;IACjC,IAAI,EAAE,YAAY,EAAE,CAAC;IACrB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAAC;IAC5C,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;CAC7D;AAED;uFACuF;AACvF,wBAAgB,cAAc,CAAC,KAAK,EAAE;IAAE,UAAU,EAAE,YAAY,EAAE,CAAA;CAAE,GAAG,iBAAiB,CAQvF"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/lib/render/registry.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAEpC,6EAA6E;AAC7E,MAAM,WAAW,YAAY;IAC3B,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAC;IACb,qBAAqB;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,2BAA2B;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,sEAAsE;IACtE,cAAc,EAAE,MAAM,CAAC;IACvB,uEAAuE;IACvE,KAAK,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC;IACjD,oEAAoE;IACpE,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC5C;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,YAAY,EAAE,CAAC;IACrB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAAC;IAC5C,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;CAC9D;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,EAAE,UAAU,EAAE,EAAE;IAAE,UAAU,EAAE,YAAY,EAAE,CAAA;CAAE,GAAG,iBAAiB,CAQhG"}
@@ -1,10 +1,12 @@
1
- /** Build a registry from a site's component definitions. The single source the
2
- * render pipeline (directive stamp + rehype dispatch) and the editor palette read. */
3
- export function defineRegistry(input) {
4
- const byName = new Map(input.components.map((c) => [c.name, c]));
1
+ /**
2
+ * Build a registry from a site's component definitions. The single source the render
3
+ * pipeline (directive stamp plus rehype dispatch) and the editor palette both read.
4
+ */
5
+ export function defineRegistry({ components }) {
6
+ const byName = new Map(components.map((c) => [c.name, c]));
5
7
  return {
6
- defs: input.components,
7
- names: input.components.map((c) => c.name),
8
+ defs: components,
9
+ names: components.map((c) => c.name),
8
10
  get: (name) => byName.get(name),
9
11
  defaultIcon: (name, role) => (role ? byName.get(name)?.defaultIconByRole?.[role] : undefined),
10
12
  };
@@ -1,5 +1,5 @@
1
1
  import type { Root, Element, ElementContent } from 'hast';
2
- import type { ComponentRegistry } from './registry';
2
+ import type { ComponentRegistry } from './registry.js';
3
3
  export declare function isElement(node: ElementContent | undefined): node is Element;
4
4
  export declare function strProp(node: Element, name: string): string | undefined;
5
5
  /** Wrap a pre-built glyph in an ec-icon span; secondary role adds the modifier. */
@@ -1 +1 @@
1
- {"version":3,"file":"rehype-dispatch.d.ts","sourceRoot":"","sources":["../../src/lib/render/rehype-dispatch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,cAAc,EAAc,MAAM,MAAM,CAAC;AAEtE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEpD,wBAAgB,SAAS,CAAC,IAAI,EAAE,cAAc,GAAG,SAAS,GAAG,IAAI,IAAI,OAAO,CAE3E;AAKD,wBAAgB,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGvE;AAED,mFAAmF;AACnF,wBAAgB,QAAQ,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAGjE;AAED,kFAAkF;AAClF,MAAM,MAAM,QAAQ,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC;AAMhE,wBAAgB,SAAS,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,QAAQ,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,cAAc,EAAE,CAAA;CAAE,CAYvG;AAED;0CAC0C;AAC1C,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,cAAc,EAAE,GAAG,OAAO,CAItG;AAED;kFACkF;AAClF,wBAAgB,aAAa,CAAC,QAAQ,EAAE,cAAc,EAAE,GAAG,OAAO,GAAG,SAAS,CAS7E;AAmBD;;;mFAGmF;AACnF,wBAAgB,cAAc,CAAC,QAAQ,EAAE,iBAAiB,EAAE,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,IACjF,MAAM,IAAI,UAUlB"}
1
+ {"version":3,"file":"rehype-dispatch.d.ts","sourceRoot":"","sources":["../../src/lib/render/rehype-dispatch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,cAAc,EAAc,MAAM,MAAM,CAAC;AAEtE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAEvD,wBAAgB,SAAS,CAAC,IAAI,EAAE,cAAc,GAAG,SAAS,GAAG,IAAI,IAAI,OAAO,CAE3E;AAKD,wBAAgB,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGvE;AAED,mFAAmF;AACnF,wBAAgB,QAAQ,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAGjE;AAED,kFAAkF;AAClF,MAAM,MAAM,QAAQ,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC;AAMhE,wBAAgB,SAAS,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,QAAQ,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,cAAc,EAAE,CAAA;CAAE,CAYvG;AAED;0CAC0C;AAC1C,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,cAAc,EAAE,GAAG,OAAO,CAItG;AAED;kFACkF;AAClF,wBAAgB,aAAa,CAAC,QAAQ,EAAE,cAAc,EAAE,GAAG,OAAO,GAAG,SAAS,CAS7E;AAmBD;;;mFAGmF;AACnF,wBAAgB,cAAc,CAAC,QAAQ,EAAE,iBAAiB,EAAE,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,IAChF,MAAM,IAAI,UAUnB"}
@@ -1,4 +1,4 @@
1
1
  import type { Root } from 'mdast';
2
- import type { ComponentRegistry } from './registry';
2
+ import type { ComponentRegistry } from './registry.js';
3
3
  export declare function remarkDirectiveStamp(registry: ComponentRegistry): (tree: Root) => void;
4
4
  //# sourceMappingURL=remark-directives.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"remark-directives.d.ts","sourceRoot":"","sources":["../../src/lib/render/remark-directives.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAA8B,IAAI,EAAQ,MAAM,OAAO,CAAC;AAGpE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAmCpD,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,iBAAiB,IAEvD,MAAM,IAAI,UA8BlB"}
1
+ {"version":3,"file":"remark-directives.d.ts","sourceRoot":"","sources":["../../src/lib/render/remark-directives.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAA8B,IAAI,EAAQ,MAAM,OAAO,CAAC;AAGpE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAmCvD,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,iBAAiB,IAEtD,MAAM,IAAI,UA8BnB"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Sanitize rendered preview HTML before it reaches `{@html}`. Strips scripts, inline event
3
+ * handlers, and dangerous URL schemes (`javascript:`, `data:`) while keeping ordinary formatting.
4
+ * Also forces `rel="noopener noreferrer"` on any anchor with `target="_blank"` to prevent
5
+ * reverse-tabnabbing. Browser-only; resolves the same string DOMPurify would return.
6
+ */
7
+ export declare function sanitizePreviewHtml(html: string): Promise<string>;
8
+ //# sourceMappingURL=sanitize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../../src/lib/render/sanitize.ts"],"names":[],"mappings":"AAOA;;;;;GAKG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAavE"}
@@ -0,0 +1,26 @@
1
+ // The live preview's sanitize floor. Carta runs with `sanitizer: false` behind the MarkdownEditor
2
+ // seam, so the admin preview pane is the one barrier between editor-authored markdown and the DOM.
3
+ // DOMPurify needs a DOM, and the preview renders only in the browser after mount, so DOMPurify
4
+ // loads through a dynamic import: the module never evaluates a DOM library on the Worker, and a
5
+ // server import of this file pulls in nothing.
6
+ let purify = null;
7
+ /**
8
+ * Sanitize rendered preview HTML before it reaches `{@html}`. Strips scripts, inline event
9
+ * handlers, and dangerous URL schemes (`javascript:`, `data:`) while keeping ordinary formatting.
10
+ * Also forces `rel="noopener noreferrer"` on any anchor with `target="_blank"` to prevent
11
+ * reverse-tabnabbing. Browser-only; resolves the same string DOMPurify would return.
12
+ */
13
+ export async function sanitizePreviewHtml(html) {
14
+ if (!purify) {
15
+ const mod = await import('dompurify');
16
+ purify = mod.default;
17
+ purify.addHook('afterSanitizeAttributes', (node) => {
18
+ if (node.tagName === 'A' && node.getAttribute('target') === '_blank') {
19
+ node.setAttribute('rel', 'noopener noreferrer');
20
+ }
21
+ });
22
+ }
23
+ // ADD_ATTR: ['target'] allows target="_blank" through so the afterSanitizeAttributes hook
24
+ // can enforce rel="noopener noreferrer" on those anchors before they reach the DOM.
25
+ return purify.sanitize(html, { ADD_ATTR: ['target'] });
26
+ }