@glw907/cairn-cms 0.4.0 → 0.5.1

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 (114) hide show
  1. package/README.md +4 -4
  2. package/dist/adapter.d.ts +34 -1
  3. package/dist/adapter.d.ts.map +1 -1
  4. package/dist/auth/capabilities.d.ts +7 -0
  5. package/dist/auth/capabilities.d.ts.map +1 -0
  6. package/dist/auth/capabilities.js +26 -0
  7. package/dist/auth/config.d.ts +9 -9
  8. package/dist/auth/config.d.ts.map +1 -1
  9. package/dist/auth/config.js +5 -5
  10. package/dist/auth/guard.d.ts +1 -1
  11. package/dist/auth/guard.d.ts.map +1 -1
  12. package/dist/auth/guard.js +2 -2
  13. package/dist/auth/index.d.ts +1 -0
  14. package/dist/auth/index.d.ts.map +1 -1
  15. package/dist/auth/index.js +1 -0
  16. package/dist/carta.d.ts +1 -1
  17. package/dist/carta.d.ts.map +1 -1
  18. package/dist/components/AdminLayout.svelte +74 -18
  19. package/dist/components/AdminLayout.svelte.d.ts +9 -0
  20. package/dist/components/AdminLayout.svelte.d.ts.map +1 -1
  21. package/dist/components/CollectionList.svelte +96 -0
  22. package/dist/components/CollectionList.svelte.d.ts +8 -0
  23. package/dist/components/CollectionList.svelte.d.ts.map +1 -0
  24. package/dist/components/ComponentPalette.svelte +34 -0
  25. package/dist/components/ComponentPalette.svelte.d.ts +9 -0
  26. package/dist/components/ComponentPalette.svelte.d.ts.map +1 -0
  27. package/dist/components/ConfirmPage.svelte +2 -2
  28. package/dist/components/EditPage.svelte +69 -31
  29. package/dist/components/EditPage.svelte.d.ts +2 -0
  30. package/dist/components/EditPage.svelte.d.ts.map +1 -1
  31. package/dist/components/LoginPage.svelte +5 -5
  32. package/dist/components/NavTree.svelte +128 -0
  33. package/dist/components/NavTree.svelte.d.ts +8 -0
  34. package/dist/components/NavTree.svelte.d.ts.map +1 -0
  35. package/dist/components/index.d.ts +3 -1
  36. package/dist/components/index.d.ts.map +1 -1
  37. package/dist/components/index.js +3 -1
  38. package/dist/editor.d.ts +25 -0
  39. package/dist/editor.d.ts.map +1 -0
  40. package/dist/editor.js +20 -0
  41. package/dist/email.js +4 -4
  42. package/dist/frontmatter.d.ts +3 -0
  43. package/dist/frontmatter.d.ts.map +1 -0
  44. package/dist/frontmatter.js +16 -0
  45. package/dist/github.d.ts +22 -2
  46. package/dist/github.d.ts.map +1 -1
  47. package/dist/github.js +40 -5
  48. package/dist/index.d.ts +3 -0
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +3 -0
  51. package/dist/nav.d.ts +58 -0
  52. package/dist/nav.d.ts.map +1 -0
  53. package/dist/nav.js +86 -0
  54. package/dist/render/glyph.d.ts +6 -0
  55. package/dist/render/glyph.d.ts.map +1 -0
  56. package/dist/render/glyph.js +5 -0
  57. package/dist/render/index.d.ts +6 -0
  58. package/dist/render/index.d.ts.map +1 -0
  59. package/dist/render/index.js +8 -0
  60. package/dist/render/pipeline.d.ts +16 -0
  61. package/dist/render/pipeline.d.ts.map +1 -0
  62. package/dist/render/pipeline.js +29 -0
  63. package/dist/render/registry.d.ts +28 -0
  64. package/dist/render/registry.d.ts.map +1 -0
  65. package/dist/render/registry.js +11 -0
  66. package/dist/render/rehype-dispatch.d.ts +24 -0
  67. package/dist/render/rehype-dispatch.d.ts.map +1 -0
  68. package/dist/render/rehype-dispatch.js +86 -0
  69. package/dist/render/remark-directives.d.ts +4 -0
  70. package/dist/render/remark-directives.d.ts.map +1 -0
  71. package/dist/render/remark-directives.js +74 -0
  72. package/dist/slug.d.ts +7 -0
  73. package/dist/slug.d.ts.map +1 -0
  74. package/dist/slug.js +15 -0
  75. package/dist/sveltekit/index.d.ts +118 -13
  76. package/dist/sveltekit/index.d.ts.map +1 -1
  77. package/dist/sveltekit/index.js +250 -24
  78. package/dist/utils.d.ts +1 -1
  79. package/dist/utils.d.ts.map +1 -1
  80. package/dist/utils.js +2 -2
  81. package/package.json +20 -3
  82. package/src/lib/adapter.ts +37 -3
  83. package/src/lib/auth/capabilities.ts +35 -0
  84. package/src/lib/auth/config.ts +6 -6
  85. package/src/lib/auth/guard.ts +3 -3
  86. package/src/lib/auth/index.ts +1 -0
  87. package/src/lib/carta.ts +2 -2
  88. package/src/lib/components/AdminLayout.svelte +74 -18
  89. package/src/lib/components/CollectionList.svelte +96 -0
  90. package/src/lib/components/ComponentPalette.svelte +34 -0
  91. package/src/lib/components/ConfirmPage.svelte +2 -2
  92. package/src/lib/components/EditPage.svelte +69 -31
  93. package/src/lib/components/LoginPage.svelte +5 -5
  94. package/src/lib/components/NavTree.svelte +128 -0
  95. package/src/lib/components/index.ts +3 -1
  96. package/src/lib/editor.ts +38 -0
  97. package/src/lib/email.ts +4 -4
  98. package/src/lib/frontmatter.ts +17 -0
  99. package/src/lib/github.ts +38 -6
  100. package/src/lib/index.ts +3 -0
  101. package/src/lib/nav.ts +117 -0
  102. package/src/lib/render/glyph.ts +14 -0
  103. package/src/lib/render/index.ts +8 -0
  104. package/src/lib/render/pipeline.ts +37 -0
  105. package/src/lib/render/registry.ts +36 -0
  106. package/src/lib/render/rehype-dispatch.ts +97 -0
  107. package/src/lib/render/remark-directives.ts +71 -0
  108. package/src/lib/slug.ts +16 -0
  109. package/src/lib/sveltekit/index.ts +355 -37
  110. package/src/lib/utils.ts +2 -2
  111. package/dist/components/AdminList.svelte +0 -33
  112. package/dist/components/AdminList.svelte.d.ts +0 -10
  113. package/dist/components/AdminList.svelte.d.ts.map +0 -1
  114. package/src/lib/components/AdminList.svelte +0 -33
@@ -1,7 +1,7 @@
1
1
  import type { CairnUser } from '../auth/guard';
2
- import { type RepoFile } from '../github';
3
2
  import { type CairnAdapter, type CairnField } from '../adapter';
4
- /** The `platform.env` bindings the content routes read. All optional — the handlers guard. */
3
+ import { type NavNode } from '../nav';
4
+ /** The `platform.env` bindings the content routes read. All optional; the handlers guard. */
5
5
  export interface AdminEnv {
6
6
  GITHUB_APP_ID?: string;
7
7
  GITHUB_APP_INSTALLATION_ID?: string;
@@ -12,17 +12,31 @@ interface PlatformEvent {
12
12
  env?: AdminEnv;
13
13
  };
14
14
  }
15
+ /** A collection reduced to what the sidebar nav needs (no plugin graph crosses to the client). */
16
+ export interface NavCollection {
17
+ type: string;
18
+ label: string;
19
+ }
15
20
  export interface AdminLayoutData {
16
21
  user: CairnUser | null;
17
22
  siteName: string;
18
23
  pathname: string;
24
+ collections: NavCollection[];
25
+ /** Managed menus (name+label only) so the shell can show a Navigation entry. */
26
+ navMenus: {
27
+ name: string;
28
+ label: string;
29
+ }[];
30
+ /** Whether the viewer may manage navigation (gates the Navigation nav entry). */
31
+ canManageNav: boolean;
19
32
  }
20
33
  /**
21
- * Branding + session for every admin page. `siteName` flows from the adapter without pulling
22
- * its plugin graph into client bundles the import stays server-side in the layout load.
23
- * `pathname` lets the shared shell highlight the active nav item without a `$app/*` import
24
- * (those kit virtual modules have no types outside a kit app, so they can't live in the
25
- * package); reading `event.url` here also opts the layout load into rerunning on navigation.
34
+ * Branding, session, and collection nav for every admin page. `siteName` and the collection
35
+ * list flow from the adapter without pulling its plugin graph into client bundles (the import
36
+ * stays server-side in the layout load; only `{type,label}` crosses). `pathname` lets the
37
+ * shared shell highlight the active nav item without a `$app/*` import (those kit virtual
38
+ * modules have no types outside a kit app); reading `event.url` also opts the layout load into
39
+ * rerunning on navigation, keeping the active class correct.
26
40
  */
27
41
  export declare function adminLayoutLoad(event: {
28
42
  locals: {
@@ -30,20 +44,66 @@ export declare function adminLayoutLoad(event: {
30
44
  };
31
45
  url: URL;
32
46
  }, adapter: CairnAdapter): AdminLayoutData;
33
- export interface AdminCollectionList {
47
+ /**
48
+ * The `/admin` index has no content of its own now that each collection is its own page; send
49
+ * the editor straight to the first collection's entries list (a Sveltia-style landing).
50
+ */
51
+ export declare function adminIndexRedirect(adapter: CairnAdapter): never;
52
+ /** One entry row: id (filename stem), display title, optional date, draft flag. */
53
+ export interface CollectionEntry {
54
+ id: string;
55
+ path: string;
56
+ title: string;
57
+ date: string | null;
58
+ draft: boolean;
59
+ }
60
+ export interface CollectionListData {
34
61
  type: string;
35
62
  label: string;
36
- files: RepoFile[];
63
+ kind: 'page' | 'story';
64
+ entries: CollectionEntry[];
65
+ /** Set when the directory listing itself failed (rate limit, network). */
37
66
  error?: string;
67
+ /** A create-flow error bounced back via `?error=` (an invalid or taken slug). */
68
+ formError: string | null;
69
+ /** Whether the viewer may create an entry in this collection (page-create is owner-only). */
70
+ canCreate: boolean;
38
71
  }
39
- /** List every collection's markdown files. A failed listing degrades to an inline error. */
40
- export declare function adminListLoad(event: PlatformEvent, adapter: CairnAdapter): Promise<{
41
- collections: AdminCollectionList[];
42
- }>;
72
+ /**
73
+ * List one collection's entries, reading each file's frontmatter for the display title, date,
74
+ * and draft badge. Reads run in parallel; a single failed read degrades that row to the slug
75
+ * (rather than failing the page), and a failed directory listing returns an inline `error`.
76
+ * Collections are small here; the 1,000-entry / Git-Trees sharding concern is risk #11, deferred.
77
+ */
78
+ export declare function collectionListLoad(event: PlatformEvent & {
79
+ params: {
80
+ collection: string;
81
+ };
82
+ url: URL;
83
+ locals: {
84
+ user: CairnUser | null;
85
+ };
86
+ }, adapter: CairnAdapter): Promise<CollectionListData>;
87
+ /**
88
+ * The "New entry" form action. Validates the requested slug, rejects one that already exists,
89
+ * then redirects into the editor in create mode (`?new=1`, where `editLoad` serves a blank
90
+ * document and `saveCommit`'s create path commits a new file). cairn is filename-based, so the
91
+ * slug is the filename stem the author types; a title-driven auto-slug is a later (Pass K) concern.
92
+ */
93
+ export declare function createEntry(event: PlatformEvent & {
94
+ params: {
95
+ collection: string;
96
+ };
97
+ locals: {
98
+ user: CairnUser | null;
99
+ };
100
+ request: Request;
101
+ }, adapter: CairnAdapter): Promise<never>;
43
102
  export interface EditData {
44
103
  type: string;
45
104
  id: string;
46
105
  label: string;
106
+ kind: 'page' | 'story';
47
107
  fields: CairnField[];
48
108
  path: string;
49
109
  body: string;
@@ -51,6 +111,8 @@ export interface EditData {
51
111
  title: string;
52
112
  saved: boolean;
53
113
  error: string | null;
114
+ /** True when editing a not-yet-committed new entry (reached via `?new=1`). */
115
+ isNew: boolean;
54
116
  }
55
117
  export declare function editLoad(event: PlatformEvent & {
56
118
  params: {
@@ -65,5 +127,48 @@ export declare function saveCommit(event: PlatformEvent & {
65
127
  user: CairnUser | null;
66
128
  };
67
129
  }, adapter: CairnAdapter): Promise<never>;
130
+ /** A page the picker can insert: its display label and the URL the nav item points at. */
131
+ export interface NavPageOption {
132
+ label: string;
133
+ url: string;
134
+ }
135
+ export interface NavLoadData {
136
+ menu: {
137
+ name: string;
138
+ label: string;
139
+ maxDepth: number;
140
+ };
141
+ tree: NavNode[];
142
+ pages: NavPageOption[];
143
+ saved: boolean;
144
+ error: string | null;
145
+ }
146
+ export declare function navLoad(event: PlatformEvent & {
147
+ locals: {
148
+ user: CairnUser | null;
149
+ };
150
+ url: URL;
151
+ }, adapter: CairnAdapter): Promise<NavLoadData>;
152
+ export declare function navSave(event: PlatformEvent & {
153
+ locals: {
154
+ user: CairnUser | null;
155
+ };
156
+ request: Request;
157
+ }, adapter: CairnAdapter): Promise<never>;
158
+ export interface HealthData {
159
+ ok: boolean;
160
+ checks: {
161
+ githubAppSigning: {
162
+ ok: boolean;
163
+ detail?: string;
164
+ };
165
+ };
166
+ }
167
+ /**
168
+ * Deploy-time health check (M2): signs a dummy App JWT to prove the GitHub App key loads and
169
+ * the PKCS#1→PKCS#8 conversion still works, before an editor hits it on save. Behind the
170
+ * `/admin` guard (signed-in editors only); returns ok/fail with no secret in the body.
171
+ */
172
+ export declare function healthLoad(event: PlatformEvent): Promise<HealthData>;
68
173
  export {};
69
174
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/sveltekit/index.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAwD,KAAK,QAAQ,EAAE,MAAM,WAAW,CAAC;AAEhG,OAAO,EAAuC,KAAK,YAAY,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAErG,8FAA8F;AAC9F,MAAM,WAAW,QAAQ;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,0BAA0B,CAAC,EAAE,MAAM,CAAC;IACpC,0BAA0B,CAAC,EAAE,MAAM,CAAC;CACrC;AAED,UAAU,aAAa;IACrB,QAAQ,CAAC,EAAE;QAAE,GAAG,CAAC,EAAE,QAAQ,CAAA;KAAE,CAAC;CAC/B;AA2BD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,SAAS,GAAG,IAAI,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE;IAAE,MAAM,EAAE;QAAE,IAAI,EAAE,SAAS,GAAG,IAAI,CAAA;KAAE,CAAC;IAAC,GAAG,EAAE,GAAG,CAAA;CAAE,EACvD,OAAO,EAAE,YAAY,GACpB,eAAe,CAEjB;AAID,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,4FAA4F;AAC5F,wBAAsB,aAAa,CACjC,KAAK,EAAE,aAAa,EACpB,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC;IAAE,WAAW,EAAE,mBAAmB,EAAE,CAAA;CAAE,CAAC,CAajD;AAID,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,wBAAsB,QAAQ,CAC5B,KAAK,EAAE,aAAa,GAAG;IAAE,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IAAC,GAAG,EAAE,GAAG,CAAA;CAAE,EACzE,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,QAAQ,CAAC,CAyBnB;AAID,wBAAsB,UAAU,CAC9B,KAAK,EAAE,aAAa,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE;QAAE,IAAI,EAAE,SAAS,GAAG,IAAI,CAAA;KAAE,CAAA;CAAE,EAC/E,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,KAAK,CAAC,CA0ChB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/sveltekit/index.ts"],"names":[],"mappings":"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"}
@@ -2,22 +2,24 @@
2
2
  // route files are thin shims (`export const load = (event) => editLoad(event, cairn)`).
3
3
  //
4
4
  // SvelteKit's filesystem routing requires the route *files* to live in each site's
5
- // `src/routes/`, but their bodies are identical across sites only the adapter differs.
5
+ // `src/routes/`, but their bodies are identical across sites. Only the adapter differs.
6
6
  // These functions take the SvelteKit event (typed structurally, to avoid depending on the
7
7
  // site-generated `App.*` ambient types) plus the site `CairnAdapter`, and throw
8
8
  // `redirect`/`error` from `@sveltejs/kit` (a peer dependency, so the thrown objects share
9
- // class identity with the host's runtime else the redirect 500s). Auth/session/manage-editors
9
+ // class identity with the host's runtime; otherwise the redirect 500s). Auth/session/manage-editors
10
10
  // logic lives under `@glw907/cairn-cms/auth`; this module is content-only (list/edit/save).
11
11
  import { redirect, error } from '@sveltejs/kit';
12
12
  import matter from 'gray-matter';
13
- import { listMarkdown, readRaw, commitFile, installationToken } from '../github';
13
+ import { can, requireCapability } from '../auth/capabilities';
14
+ import { listMarkdown, readRaw, commitFile, installationToken, signingSelfTest, CommitConflictError, } from '../github';
14
15
  import { serializeMarkdown } from '../content';
15
16
  import { findCollection, frontmatterFromForm } from '../adapter';
17
+ import { validateNavTree, extractMenu, parseSiteConfig, setMenu } from '../nav';
16
18
  /**
17
19
  * Mint a GitHub App installation token for *reads* when the App is configured, else undefined
18
20
  * (reads then fall back to anonymous). Authenticated reads get the 5000/hr limit; anonymous
19
21
  * reads share GitHub's 60/hr-per-IP budget across Cloudflare's egress IPs, so they 403 in prod.
20
- * A mint failure degrades gracefully to anonymous rather than 500ing unlike the commit path,
22
+ * A mint failure degrades gracefully to anonymous rather than 500ing. Unlike the commit path,
21
23
  * where a missing App is fatal, a read can still succeed unauthenticated.
22
24
  */
23
25
  async function readToken(env) {
@@ -37,28 +39,132 @@ async function readToken(env) {
37
39
  }
38
40
  }
39
41
  /**
40
- * Branding + session for every admin page. `siteName` flows from the adapter without pulling
41
- * its plugin graph into client bundles the import stays server-side in the layout load.
42
- * `pathname` lets the shared shell highlight the active nav item without a `$app/*` import
43
- * (those kit virtual modules have no types outside a kit app, so they can't live in the
44
- * package); reading `event.url` here also opts the layout load into rerunning on navigation.
42
+ * Branding, session, and collection nav for every admin page. `siteName` and the collection
43
+ * list flow from the adapter without pulling its plugin graph into client bundles (the import
44
+ * stays server-side in the layout load; only `{type,label}` crosses). `pathname` lets the
45
+ * shared shell highlight the active nav item without a `$app/*` import (those kit virtual
46
+ * modules have no types outside a kit app); reading `event.url` also opts the layout load into
47
+ * rerunning on navigation, keeping the active class correct.
45
48
  */
46
49
  export function adminLayoutLoad(event, adapter) {
47
- return { user: event.locals.user, siteName: adapter.siteName, pathname: event.url.pathname };
50
+ return {
51
+ user: event.locals.user,
52
+ siteName: adapter.siteName,
53
+ pathname: event.url.pathname,
54
+ collections: adapter.collections.map(({ type, label }) => ({ type, label })),
55
+ navMenus: adapter.navMenu ? [{ name: adapter.navMenu.menuName, label: adapter.navMenu.label }] : [],
56
+ canManageNav: can(event.locals.user, 'nav:manage'),
57
+ };
58
+ }
59
+ /**
60
+ * The `/admin` index has no content of its own now that each collection is its own page; send
61
+ * the editor straight to the first collection's entries list (a Sveltia-style landing).
62
+ */
63
+ export function adminIndexRedirect(adapter) {
64
+ const first = adapter.collections[0];
65
+ if (!first)
66
+ throw error(404, 'No collections configured');
67
+ throw redirect(307, `/admin/${first.type}`);
48
68
  }
49
- /** List every collection's markdown files. A failed listing degrades to an inline error. */
50
- export async function adminListLoad(event, adapter) {
69
+ /** Coerce a frontmatter `date` (gray-matter may parse YAML dates to `Date`) to `YYYY-MM-DD`. */
70
+ function entryDate(value) {
71
+ if (value instanceof Date)
72
+ return value.toISOString().slice(0, 10);
73
+ if (typeof value === 'string')
74
+ return value;
75
+ return null;
76
+ }
77
+ /**
78
+ * List one collection's entries, reading each file's frontmatter for the display title, date,
79
+ * and draft badge. Reads run in parallel; a single failed read degrades that row to the slug
80
+ * (rather than failing the page), and a failed directory listing returns an inline `error`.
81
+ * Collections are small here; the 1,000-entry / Git-Trees sharding concern is risk #11, deferred.
82
+ */
83
+ export async function collectionListLoad(event, adapter) {
84
+ const collection = findCollection(adapter, event.params.collection);
85
+ if (!collection)
86
+ throw error(404, 'Unknown collection');
87
+ const kind = collection.kind ?? 'story';
88
+ const canCreate = can(event.locals.user, kind === 'page' ? 'page:create' : 'story:create');
89
+ const formError = event.url.searchParams.get('error');
51
90
  const token = await readToken(event.platform?.env);
52
- const collections = await Promise.all(adapter.collections.map(async ({ type, label, dir }) => {
91
+ let files;
92
+ try {
93
+ files = await listMarkdown(adapter.backend, collection.dir, token);
94
+ }
95
+ catch (err) {
96
+ return {
97
+ type: collection.type,
98
+ label: collection.label,
99
+ kind,
100
+ entries: [],
101
+ error: err instanceof Error ? err.message : 'Failed to load',
102
+ formError,
103
+ canCreate,
104
+ };
105
+ }
106
+ const entries = await Promise.all(files.map(async (file) => {
107
+ const fallback = {
108
+ id: file.id,
109
+ path: file.path,
110
+ title: file.id,
111
+ date: null,
112
+ draft: false,
113
+ };
53
114
  try {
54
- return { type, label, files: await listMarkdown(adapter.backend, dir, token) };
115
+ const raw = await readRaw(adapter.backend, file.path, token);
116
+ if (raw === null)
117
+ return fallback;
118
+ const { data } = matter(raw);
119
+ return {
120
+ id: file.id,
121
+ path: file.path,
122
+ title: typeof data.title === 'string' ? data.title : file.id,
123
+ date: entryDate(data.date),
124
+ draft: data.draft === true,
125
+ };
55
126
  }
56
- catch (err) {
57
- // A failed listing (rate limit, network) shouldn't 500 the whole admin.
58
- return { type, label, files: [], error: err instanceof Error ? err.message : 'Failed to load' };
127
+ catch {
128
+ return fallback;
59
129
  }
60
130
  }));
61
- return { collections };
131
+ return {
132
+ type: collection.type,
133
+ label: collection.label,
134
+ kind,
135
+ entries,
136
+ formError,
137
+ canCreate,
138
+ };
139
+ }
140
+ // ── /admin/[collection]?/create (POST) ─────────────────────────────────────
141
+ /** A safe filename stem: starts and ends with a lowercase alphanumeric, hyphens allowed within. */
142
+ const SLUG_RE = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
143
+ /**
144
+ * The "New entry" form action. Validates the requested slug, rejects one that already exists,
145
+ * then redirects into the editor in create mode (`?new=1`, where `editLoad` serves a blank
146
+ * document and `saveCommit`'s create path commits a new file). cairn is filename-based, so the
147
+ * slug is the filename stem the author types; a title-driven auto-slug is a later (Pass K) concern.
148
+ */
149
+ export async function createEntry(event, adapter) {
150
+ const collection = findCollection(adapter, event.params.collection);
151
+ if (!collection)
152
+ throw error(404, 'Unknown collection');
153
+ const kind = collection.kind ?? 'story';
154
+ requireCapability(event.locals.user, kind === 'page' ? 'page:create' : 'story:create');
155
+ const form = await event.request.formData();
156
+ const id = String(form.get('id') ?? '').trim();
157
+ const back = (message) => redirect(303, `/admin/${collection.type}?error=${encodeURIComponent(message)}`);
158
+ if (!SLUG_RE.test(id)) {
159
+ throw back('Enter a slug using lowercase letters, numbers, and hyphens (for example 2026-05-my-entry).');
160
+ }
161
+ const token = await readToken(event.platform?.env);
162
+ const existing = await readRaw(adapter.backend, `${collection.dir}/${id}.md`, token);
163
+ if (existing !== null)
164
+ throw back(`An entry named "${id}" already exists.`);
165
+ const date = String(form.get('date') ?? '').trim();
166
+ const dateSuffix = kind === 'story' && date ? `&date=${encodeURIComponent(date)}` : '';
167
+ throw redirect(303, `/admin/edit/${collection.type}/${id}?new=1${dateSuffix}`);
62
168
  }
63
169
  export async function editLoad(event, adapter) {
64
170
  const collection = findCollection(adapter, event.params.type);
@@ -67,15 +173,23 @@ export async function editLoad(event, adapter) {
67
173
  const token = await readToken(event.platform?.env);
68
174
  const path = `${collection.dir}/${event.params.id}.md`;
69
175
  const raw = await readRaw(adapter.backend, path, token);
70
- if (raw === null)
176
+ const isNew = event.url.searchParams.get('new') === '1';
177
+ // A missing file is a 404 normally, but in create mode (`?new=1`) it's a blank new document.
178
+ if (raw === null && !isNew)
71
179
  throw error(404, 'Content not found');
72
- // Split frontmatter from body server-side; the editor form binds to the frontmatter and
73
- // the Carta editor binds to the body, and /admin/save reassembles them on commit.
74
- const { data: frontmatter, content: body } = matter(raw);
180
+ // Split frontmatter from body server-side; the editor form binds to the frontmatter and the
181
+ // Carta editor to the body, and /admin/save reassembles them on commit. A new document starts
182
+ // empty so the author fills the fields from scratch.
183
+ const { data: frontmatter, content: body } = raw === null ? { data: {}, content: '' } : matter(raw);
184
+ const seedDate = event.url.searchParams.get('date');
185
+ if (isNew && seedDate && frontmatter.date === undefined) {
186
+ frontmatter.date = seedDate;
187
+ }
75
188
  return {
76
189
  type: event.params.type,
77
190
  id: event.params.id,
78
191
  label: collection.label,
192
+ kind: collection.kind ?? 'story',
79
193
  fields: collection.fields,
80
194
  path,
81
195
  body,
@@ -83,6 +197,7 @@ export async function editLoad(event, adapter) {
83
197
  title: typeof frontmatter.title === 'string' ? frontmatter.title : event.params.id,
84
198
  saved: event.url.searchParams.get('saved') === '1',
85
199
  error: event.url.searchParams.get('error'),
200
+ isNew,
86
201
  };
87
202
  }
88
203
  // ── /admin/save (POST) ──────────────────────────────────────────────────────
@@ -98,6 +213,7 @@ export async function saveCommit(event, adapter) {
98
213
  const type = String(form.get('type') ?? '');
99
214
  const id = String(form.get('id') ?? '');
100
215
  const body = String(form.get('body') ?? '');
216
+ const newSuffix = form.get('new') === '1' ? '&new=1' : '';
101
217
  const collection = findCollection(adapter, type);
102
218
  if (!collection || !id)
103
219
  throw error(400, 'Bad request');
@@ -109,7 +225,7 @@ export async function saveCommit(event, adapter) {
109
225
  }
110
226
  catch (err) {
111
227
  const message = err instanceof Error ? err.message : 'Invalid frontmatter';
112
- throw redirect(303, `/admin/edit/${type}/${id}?error=${encodeURIComponent(message)}`);
228
+ throw redirect(303, `/admin/edit/${type}/${id}?error=${encodeURIComponent(message)}${newSuffix}`);
113
229
  }
114
230
  const markdown = serializeMarkdown(frontmatter, body);
115
231
  const token = await installationToken({
@@ -117,6 +233,116 @@ export async function saveCommit(event, adapter) {
117
233
  installationId: env.GITHUB_APP_INSTALLATION_ID,
118
234
  privateKeyB64: env.GITHUB_APP_PRIVATE_KEY_B64,
119
235
  });
120
- await commitFile(adapter.backend, `${collection.dir}/${id}.md`, markdown, { message: `Update ${collection.label.toLowerCase()}: ${id}`, author: { name: user.name, email: user.email } }, token);
236
+ try {
237
+ await commitFile(adapter.backend, `${collection.dir}/${id}.md`, markdown, { message: `Update ${collection.label.toLowerCase()}: ${id}`, author: { name: user.name, email: user.email } }, token);
238
+ }
239
+ catch (err) {
240
+ // Concurrent-edit 409 (C3): fail safe. Bounce back with a reload prompt; the editor reloads
241
+ // the current version and reapplies. Any other error is unexpected, so rethrow.
242
+ if (err instanceof CommitConflictError) {
243
+ const message = 'This file changed since you opened it. Reload and reapply your edits.';
244
+ throw redirect(303, `/admin/edit/${type}/${id}?error=${encodeURIComponent(message)}${newSuffix}`);
245
+ }
246
+ throw err;
247
+ }
121
248
  throw redirect(303, `/admin/edit/${type}/${id}?saved=1`);
122
249
  }
250
+ /** List page-collection entries for the picker (one directory listing per page collection). */
251
+ async function navPageOptions(adapter, env) {
252
+ const token = await readToken(env);
253
+ const pageCollections = adapter.collections.filter((c) => (c.kind ?? 'story') === 'page');
254
+ const lists = await Promise.all(pageCollections.map(async (c) => {
255
+ try {
256
+ const files = await listMarkdown(adapter.backend, c.dir, token);
257
+ return files.map((f) => ({ label: f.id, url: `/${f.id}` }));
258
+ }
259
+ catch {
260
+ return [];
261
+ }
262
+ }));
263
+ return lists.flat();
264
+ }
265
+ export async function navLoad(event, adapter) {
266
+ requireCapability(event.locals.user, 'nav:manage');
267
+ const config = adapter.navMenu;
268
+ if (!config)
269
+ throw error(404, 'No navigation menu configured');
270
+ const maxDepth = config.maxDepth ?? 2;
271
+ const menu = { name: config.menuName, label: config.label, maxDepth };
272
+ // Read the menu from the committed YAML. A missing/unparsable file degrades to an empty tree so
273
+ // the editor still loads (a first edit then creates the menu); only the read itself is best-effort.
274
+ const token = await readToken(event.platform?.env);
275
+ let tree = [];
276
+ try {
277
+ const raw = await readRaw(adapter.backend, config.configPath, token);
278
+ if (raw !== null)
279
+ tree = extractMenu(parseSiteConfig(raw), config.menuName, maxDepth);
280
+ }
281
+ catch (err) {
282
+ console.error(`cairn nav: failed to read "${config.configPath}":`, err);
283
+ }
284
+ return {
285
+ menu,
286
+ tree,
287
+ pages: await navPageOptions(adapter, event.platform?.env),
288
+ saved: event.url.searchParams.get('saved') === '1',
289
+ error: event.url.searchParams.get('error'),
290
+ };
291
+ }
292
+ export async function navSave(event, adapter) {
293
+ const user = requireCapability(event.locals.user, 'nav:manage');
294
+ const config = adapter.navMenu;
295
+ if (!config)
296
+ throw error(404, 'No navigation menu configured');
297
+ const maxDepth = config.maxDepth ?? 2;
298
+ const env = event.platform?.env;
299
+ if (!env?.GITHUB_APP_ID || !env.GITHUB_APP_INSTALLATION_ID || !env.GITHUB_APP_PRIVATE_KEY_B64) {
300
+ throw error(500, 'GitHub App is not configured');
301
+ }
302
+ const form = await event.request.formData();
303
+ let tree;
304
+ try {
305
+ tree = validateNavTree(JSON.parse(String(form.get('tree') ?? '[]')), maxDepth);
306
+ }
307
+ catch (err) {
308
+ const message = err instanceof Error ? err.message : 'Invalid navigation';
309
+ throw redirect(303, `/admin/nav?error=${encodeURIComponent(message)}`);
310
+ }
311
+ const token = await installationToken({
312
+ appId: env.GITHUB_APP_ID,
313
+ installationId: env.GITHUB_APP_INSTALLATION_ID,
314
+ privateKeyB64: env.GITHUB_APP_PRIVATE_KEY_B64,
315
+ });
316
+ // Read-modify-commit: replace only this menu in the current file, preserving the rest.
317
+ const raw = await readRaw(adapter.backend, config.configPath, token);
318
+ if (raw === null)
319
+ throw error(404, `Site config not found at ${config.configPath}`);
320
+ try {
321
+ await commitFile(adapter.backend, config.configPath, setMenu(raw, config.menuName, tree), { message: `Update ${config.label.toLowerCase()}`, author: { name: user.name, email: user.email } }, token);
322
+ }
323
+ catch (err) {
324
+ // Concurrent-edit 409 (C3): fail safe, same as the content save path.
325
+ if (err instanceof CommitConflictError) {
326
+ const message = 'The site config changed since you opened it. Reload and reapply your edits.';
327
+ throw redirect(303, `/admin/nav?error=${encodeURIComponent(message)}`);
328
+ }
329
+ throw err;
330
+ }
331
+ throw redirect(303, '/admin/nav?saved=1');
332
+ }
333
+ /**
334
+ * Deploy-time health check (M2): signs a dummy App JWT to prove the GitHub App key loads and
335
+ * the PKCS#1→PKCS#8 conversion still works, before an editor hits it on save. Behind the
336
+ * `/admin` guard (signed-in editors only); returns ok/fail with no secret in the body.
337
+ */
338
+ export async function healthLoad(event) {
339
+ const env = event.platform?.env;
340
+ let githubAppSigning;
341
+ if (env?.GITHUB_APP_ID && env.GITHUB_APP_PRIVATE_KEY_B64) {
342
+ githubAppSigning = await signingSelfTest(env.GITHUB_APP_ID, env.GITHUB_APP_PRIVATE_KEY_B64);
343
+ }
344
+ else {
345
+ githubAppSigning = { ok: false, detail: 'GitHub App not configured' };
346
+ }
347
+ return { ok: githubAppSigning.ok, checks: { githubAppSigning } };
348
+ }
package/dist/utils.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- /** Encode bytes as unpadded base64url (RFC 4648 §5) the JWT/token wire format. */
1
+ /** Encode bytes as unpadded base64url (RFC 4648 §5), the JWT/token wire format. */
2
2
  export declare function bytesToB64url(bytes: Uint8Array): string;
3
3
  //# sourceMappingURL=utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/lib/utils.ts"],"names":[],"mappings":"AAOA,oFAAoF;AACpF,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAGvD"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/lib/utils.ts"],"names":[],"mappings":"AAOA,mFAAmF;AACnF,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAGvD"}
package/dist/utils.js CHANGED
@@ -1,10 +1,10 @@
1
1
  // cairn-core: internal encoding helpers shared across modules.
2
2
  //
3
- // Deliberately NOT re-exported from index.ts these are implementation details of the
3
+ // Deliberately NOT re-exported from index.ts. These are implementation details of the
4
4
  // auth/github crypto, not part of the public API (auth.ts signs tokens, github.ts builds
5
5
  // the App JWT; both need base64url). Keeping them here stops bytesToB64url leaking through
6
6
  // the `export *` barrel.
7
- /** Encode bytes as unpadded base64url (RFC 4648 §5) the JWT/token wire format. */
7
+ /** Encode bytes as unpadded base64url (RFC 4648 §5), the JWT/token wire format. */
8
8
  export function bytesToB64url(bytes) {
9
9
  const binary = Array.from(bytes, (b) => String.fromCharCode(b)).join('');
10
10
  return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
package/package.json CHANGED
@@ -1,8 +1,12 @@
1
1
  {
2
2
  "name": "@glw907/cairn-cms",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "description": "Embedded, magic-link, GitHub-committing CMS for SvelteKit/Cloudflare sites.",
5
5
  "type": "module",
6
+ "sideEffects": [
7
+ "**/*.svelte",
8
+ "**/*.css"
9
+ ],
6
10
  "license": "MIT",
7
11
  "author": "Geoff Wright",
8
12
  "repository": {
@@ -86,7 +90,21 @@
86
90
  "svelte": "^5.0.0"
87
91
  },
88
92
  "dependencies": {
89
- "gray-matter": "^4"
93
+ "@types/hast": "^3.0.4",
94
+ "@types/mdast": "^4.0.4",
95
+ "gray-matter": "^4",
96
+ "hastscript": "^9.0.1",
97
+ "mdast-util-directive": "^3.1.0",
98
+ "rehype-raw": "^7.0.0",
99
+ "rehype-slug": "^6.0.0",
100
+ "rehype-stringify": "^10.0.1",
101
+ "remark-directive": "^4.0.0",
102
+ "remark-gfm": "^4",
103
+ "remark-parse": "^11.0.0",
104
+ "remark-rehype": "^11.1.2",
105
+ "unified": "^11.0.5",
106
+ "unist-util-visit": "^5.1.0",
107
+ "yaml": "^2"
90
108
  },
91
109
  "devDependencies": {
92
110
  "@better-auth/cli": "^1.4.21",
@@ -103,7 +121,6 @@
103
121
  "svelte": "^5",
104
122
  "svelte-check": "^4",
105
123
  "typescript": "^6.0.3",
106
- "unified": "^11.0.5",
107
124
  "vitest": "^4.1.6"
108
125
  }
109
126
  }