@glw907/cairn-cms 0.54.0 → 0.56.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 (39) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/dist/components/ConceptList.svelte +216 -75
  3. package/dist/components/ConceptList.svelte.d.ts +6 -4
  4. package/dist/components/MarkdownEditor.svelte +64 -45
  5. package/dist/components/cairn-admin.css +50 -0
  6. package/dist/components/editor-folding.d.ts +4 -3
  7. package/dist/components/editor-folding.js +129 -97
  8. package/dist/components/editor-highlight.js +1 -13
  9. package/dist/content/concepts.js +3 -1
  10. package/dist/content/manifest.d.ts +1 -0
  11. package/dist/content/manifest.js +6 -0
  12. package/dist/content/types.d.ts +5 -0
  13. package/dist/delivery/content-index.js +1 -1
  14. package/dist/delivery/data.d.ts +1 -1
  15. package/dist/delivery/data.js +1 -1
  16. package/dist/sveltekit/content-routes.d.ts +6 -0
  17. package/dist/sveltekit/content-routes.js +11 -6
  18. package/dist/sveltekit/index.d.ts +1 -0
  19. package/dist/vite/index.d.ts +6 -4
  20. package/dist/vite/index.js +11 -7
  21. package/dist/vite/resolve-root.d.ts +16 -0
  22. package/dist/vite/resolve-root.js +16 -0
  23. package/package.json +2 -1
  24. package/src/lib/components/ConceptList.svelte +216 -75
  25. package/src/lib/components/MarkdownEditor.svelte +64 -45
  26. package/src/lib/components/editor-folding.ts +137 -104
  27. package/src/lib/components/editor-highlight.ts +0 -12
  28. package/src/lib/content/concepts.ts +3 -1
  29. package/src/lib/content/manifest.ts +7 -0
  30. package/src/lib/content/types.ts +5 -0
  31. package/src/lib/delivery/content-index.ts +1 -1
  32. package/src/lib/delivery/data.ts +1 -1
  33. package/src/lib/sveltekit/content-routes.ts +17 -6
  34. package/src/lib/sveltekit/index.ts +2 -0
  35. package/src/lib/vite/index.ts +11 -7
  36. package/src/lib/vite/resolve-root.ts +24 -0
  37. /package/dist/{delivery → content}/excerpt.d.ts +0 -0
  38. /package/dist/{delivery → content}/excerpt.js +0 -0
  39. /package/src/lib/{delivery → content}/excerpt.ts +0 -0
@@ -6,6 +6,8 @@ import { redirect, error, fail } from '@sveltejs/kit';
6
6
  import { findConcept } from '../content/concepts.js';
7
7
  import { extractCairnLinks, formatCairnToken, rewriteCairnLink } from '../content/links.js';
8
8
  import { frontmatterFromForm, parseMarkdown, dateInputValue, serializeMarkdown } from '../content/frontmatter.js';
9
+ import { deriveExcerpt } from '../content/excerpt.js';
10
+ import { asString } from '../content/identity.js';
9
11
  import { isValidId, slugify, filenameFromId, composeDatedId, slugFromId, renameId } from '../content/ids.js';
10
12
  import { appCredentials, type GithubKeyEnv } from '../github/credentials.js';
11
13
  import { listMarkdown, readRaw, commitFiles, type FileChange } from '../github/repo.js';
@@ -56,12 +58,18 @@ export interface EntrySummary {
56
58
  draft: boolean;
57
59
  /** Publish state derived from the ref set: live as-is, live with pending edits, or branch-only. */
58
60
  status: 'published' | 'edited' | 'new';
61
+ /** The row's one-line summary: the manifest's indexed excerpt for a published row, the branch
62
+ * frontmatter/body excerpt for a pending one, and null when neither yields text. */
63
+ summary: string | null;
59
64
  }
60
65
 
61
66
  /** The concept list view's data. */
62
67
  export interface ListData {
63
68
  conceptId: string;
64
69
  label: string;
70
+ /** The singular noun for the create affordances ("New post"); from the descriptor, which defaults
71
+ * it to `label`. */
72
+ singular: string;
65
73
  /** Posts carry a date in the new-entry form; pages do not (concept routing, spec §7.2). */
66
74
  dated: boolean;
67
75
  entries: EntrySummary[];
@@ -251,13 +259,16 @@ export function createContentRoutes(runtime: CairnRuntime, deps: ContentRoutesDe
251
259
  ): Promise<EntrySummary> {
252
260
  try {
253
261
  const raw = await readRaw(repo, file.path, token);
254
- if (raw === null) return { id: file.id, title: file.id, date: null, draft: false, status };
255
- const { frontmatter } = parseMarkdown(raw);
262
+ if (raw === null) return { id: file.id, title: file.id, date: null, draft: false, status, summary: null };
263
+ const { frontmatter, body } = parseMarkdown(raw);
256
264
  const title = typeof frontmatter.title === 'string' && frontmatter.title.trim() ? frontmatter.title : file.id;
257
265
  const date = dateInputValue(frontmatter.date) || null;
258
- return { id: file.id, title, date, draft: frontmatter.draft === true, status };
266
+ // Normalize an empty excerpt to null, so a pending row matches EntrySummary's `string | null`
267
+ // contract (the published builder already coalesces with `?? null`).
268
+ const summary = deriveExcerpt(body, { description: asString(frontmatter.description) }) || null;
269
+ return { id: file.id, title, date, draft: frontmatter.draft === true, status, summary };
259
270
  } catch {
260
- return { id: file.id, title: file.id, date: null, draft: false, status };
271
+ return { id: file.id, title: file.id, date: null, draft: false, status, summary: null };
261
272
  }
262
273
  }
263
274
 
@@ -298,7 +309,7 @@ export function createContentRoutes(runtime: CairnRuntime, deps: ContentRoutesDe
298
309
  const formError = event.url.searchParams.get('error');
299
310
  const publishedAllRaw = event.url.searchParams.get('publishedAll');
300
311
  const publishedAll = publishedAllRaw !== null && /^\d+$/.test(publishedAllRaw) ? Number(publishedAllRaw) : null;
301
- const base = { conceptId: concept.id, label: concept.label, dated: concept.routing.dated, formError, publishedAll };
312
+ const base = { conceptId: concept.id, label: concept.label, singular: concept.singular, dated: concept.routing.dated, formError, publishedAll };
302
313
  let token: string;
303
314
  try {
304
315
  token = await mintToken(event.platform?.env ?? {});
@@ -329,7 +340,7 @@ export function createContentRoutes(runtime: CairnRuntime, deps: ContentRoutesDe
329
340
  rows.map((e) =>
330
341
  pendingIds.has(e.id)
331
342
  ? pendingRow(concept, e.id, 'edited', token)
332
- : { id: e.id, title: e.title, date: e.date ?? null, draft: e.draft, status: 'published' as const },
343
+ : { id: e.id, title: e.title, date: e.date ?? null, draft: e.draft, status: 'published' as const, summary: e.summary ?? null },
333
344
  ),
334
345
  );
335
346
  const listed = new Set(rows.map((e) => e.id));
@@ -25,3 +25,5 @@ export { healthLoad, type HealthData } from './health.js';
25
25
  export type { RequestContext, CookieJar, HandleInput } from './types.js';
26
26
  // Re-exported here, not from root, so the public ContentRoutesDeps consumer can name it.
27
27
  export type { GithubKeyEnv } from '../github/credentials.js';
28
+ // Re-exported here, not just from root, so the app.d.ts Platform block can name it.
29
+ export type { AuthEnv } from '../auth/types.js';
@@ -8,6 +8,7 @@
8
8
  import type { Plugin, PluginOption } from 'vite';
9
9
  import { writeFile, mkdir } from 'node:fs/promises';
10
10
  import { dirname, join } from 'node:path';
11
+ import { resolveViteRoot } from './resolve-root.js';
11
12
 
12
13
  /** The key the cairnManifest plugin stashes its options under, so the write path can read them off the
13
14
  * plugin instance in the consumer's loaded config without re-parsing the config file. */
@@ -152,10 +153,12 @@ export function cairnManifest(opts: CairnManifestOptions): Plugin {
152
153
  }
153
154
 
154
155
  /** Regenerate the committed manifest from the consumer's corpus and write it to the configured
155
- * manifestPath. It loads the consumer's Vite config from `cwd`, reads the cairnManifest plugin's
156
- * options off the instance, evaluates the write-mode virtual module through the build's own
157
- * resolution, and writes the serialized manifest. The cairn-manifest bin calls this; it is exported
158
- * so the write logic is testable apart from the CLI shell. */
156
+ * manifestPath. It searches for the consumer's Vite config from `cwd`, derives the authoritative
157
+ * Vite root from the loaded config (so a configured `root` or a non-root cwd resolves correctly),
158
+ * reads the cairnManifest plugin's options off the instance, evaluates the write-mode virtual
159
+ * module through the build's own resolution, and writes the serialized manifest under the Vite
160
+ * root. The cairn-manifest bin calls this; it is exported so the write logic is testable apart
161
+ * from the CLI shell. */
159
162
  export async function writeManifest(cwd: string = process.cwd()): Promise<void> {
160
163
  const { loadConfigFromFile } = await import('vite');
161
164
  const loaded = await loadConfigFromFile({ command: 'build', mode: 'production' }, undefined, cwd);
@@ -168,11 +171,12 @@ export async function writeManifest(cwd: string = process.cwd()): Promise<void>
168
171
  'cairn-manifest: the Vite config has no cairnManifest() plugin. Add it so the bin shares the build options.',
169
172
  );
170
173
  }
171
- const serialized = await buildManifestFromVite(opts, cwd);
174
+ const root = resolveViteRoot(loaded, cwd);
175
+ const serialized = await buildManifestFromVite(opts, root);
172
176
  const manifestPath = opts.manifestPath ?? DEFAULT_MANIFEST_PATH;
173
177
  // The manifest path is app-root-absolute (a leading slash relative to the project), so resolve it
174
- // against cwd, not the filesystem root.
175
- const outPath = join(cwd, manifestPath.replace(/^\//, ''));
178
+ // against the Vite root, not the filesystem root or the config-search cwd.
179
+ const outPath = join(root, manifestPath.replace(/^\//, ''));
176
180
  await mkdir(dirname(outPath), { recursive: true });
177
181
  await writeFile(outPath, serialized);
178
182
  }
@@ -0,0 +1,24 @@
1
+ // The manifest bin's root derivation, split out so it is unit-testable without widening the public
2
+ // /vite surface (only src/lib/vite/index.ts is the package subpath; this sibling is internal).
3
+ import { dirname, isAbsolute, resolve } from 'node:path';
4
+
5
+ /** The shape of `loadConfigFromFile`'s result that the root derivation reads: the config file's own
6
+ * path and its `root` field. Typed structurally so the helper is testable without a real load. */
7
+ export interface LoadedViteConfig {
8
+ /** The resolved path of the config file Vite loaded. */
9
+ path: string;
10
+ /** The user config, of which only `root` is read here. */
11
+ config: { root?: string };
12
+ }
13
+
14
+ /** The authoritative Vite root for the manifest bin, derived from the loaded config the way Vite
15
+ * resolves a relative `root`: against the config file's own directory, not cwd. An absolute `root`
16
+ * stands as given, and no `root` falls back to `cwd` (the directory the bin was run from). This
17
+ * separates the config-search dir (cwd) from the Vite root, so a non-root cwd or a config that
18
+ * sets `root` reads and writes the manifest under the real app root. */
19
+ export function resolveViteRoot(loaded: LoadedViteConfig, cwd: string): string {
20
+ const root = loaded.config.root;
21
+ if (!root) return cwd;
22
+ if (isAbsolute(root)) return root;
23
+ return resolve(dirname(loaded.path), root);
24
+ }
File without changes
File without changes
File without changes