nebula-cms 0.1.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 (241) hide show
  1. package/.claude/settings.local.json +42 -0
  2. package/.github/workflows/ci.yml +31 -0
  3. package/.mcp.json +12 -0
  4. package/.prettierignore +5 -0
  5. package/.prettierrc.cjs +22 -0
  6. package/AGENTS.md +183 -0
  7. package/LICENSE +201 -0
  8. package/README.md +128 -0
  9. package/package.json +74 -0
  10. package/playground/.claude/settings.local.json +5 -0
  11. package/playground/astro.config.mjs +7 -0
  12. package/playground/node_modules/.bin/astro +21 -0
  13. package/playground/node_modules/.bin/rollup +21 -0
  14. package/playground/node_modules/.bin/tsc +21 -0
  15. package/playground/node_modules/.bin/tsserver +21 -0
  16. package/playground/node_modules/.bin/vite +21 -0
  17. package/playground/node_modules/.vite/_svelte_metadata.json +1 -0
  18. package/playground/node_modules/.vite/deps/@astrojs_svelte_client__js.js +80 -0
  19. package/playground/node_modules/.vite/deps/@astrojs_svelte_client__js.js.map +7 -0
  20. package/playground/node_modules/.vite/deps/_metadata.json +184 -0
  21. package/playground/node_modules/.vite/deps/astro___aria-query.js +6776 -0
  22. package/playground/node_modules/.vite/deps/astro___aria-query.js.map +7 -0
  23. package/playground/node_modules/.vite/deps/astro___axobject-query.js +3754 -0
  24. package/playground/node_modules/.vite/deps/astro___axobject-query.js.map +7 -0
  25. package/playground/node_modules/.vite/deps/astro___html-escaper.js +34 -0
  26. package/playground/node_modules/.vite/deps/astro___html-escaper.js.map +7 -0
  27. package/playground/node_modules/.vite/deps/chunk-AJXJMYAF.js +0 -0
  28. package/playground/node_modules/.vite/deps/chunk-AJXJMYAF.js.map +7 -0
  29. package/playground/node_modules/.vite/deps/chunk-ALJIOON6.js +1005 -0
  30. package/playground/node_modules/.vite/deps/chunk-ALJIOON6.js.map +7 -0
  31. package/playground/node_modules/.vite/deps/chunk-BUSYA2B4.js +8 -0
  32. package/playground/node_modules/.vite/deps/chunk-BUSYA2B4.js.map +7 -0
  33. package/playground/node_modules/.vite/deps/chunk-CNYJBM5F.js +21 -0
  34. package/playground/node_modules/.vite/deps/chunk-CNYJBM5F.js.map +7 -0
  35. package/playground/node_modules/.vite/deps/chunk-DBPNBGEI.js +223 -0
  36. package/playground/node_modules/.vite/deps/chunk-DBPNBGEI.js.map +7 -0
  37. package/playground/node_modules/.vite/deps/chunk-G3C2FXJT.js +204 -0
  38. package/playground/node_modules/.vite/deps/chunk-G3C2FXJT.js.map +7 -0
  39. package/playground/node_modules/.vite/deps/chunk-GKDKFWC5.js +27 -0
  40. package/playground/node_modules/.vite/deps/chunk-GKDKFWC5.js.map +7 -0
  41. package/playground/node_modules/.vite/deps/chunk-HNCLEOC5.js +4376 -0
  42. package/playground/node_modules/.vite/deps/chunk-HNCLEOC5.js.map +7 -0
  43. package/playground/node_modules/.vite/deps/chunk-JICYXBFU.js +688 -0
  44. package/playground/node_modules/.vite/deps/chunk-JICYXBFU.js.map +7 -0
  45. package/playground/node_modules/.vite/deps/chunk-KCUTL6DD.js +5099 -0
  46. package/playground/node_modules/.vite/deps/chunk-KCUTL6DD.js.map +7 -0
  47. package/playground/node_modules/.vite/deps/chunk-ZP4UNCSN.js +23 -0
  48. package/playground/node_modules/.vite/deps/chunk-ZP4UNCSN.js.map +7 -0
  49. package/playground/node_modules/.vite/deps/chunk-ZREFNRZZ.js +148 -0
  50. package/playground/node_modules/.vite/deps/chunk-ZREFNRZZ.js.map +7 -0
  51. package/playground/node_modules/.vite/deps/package.json +3 -0
  52. package/playground/node_modules/.vite/deps/smol-toml.js +843 -0
  53. package/playground/node_modules/.vite/deps/smol-toml.js.map +7 -0
  54. package/playground/node_modules/.vite/deps/svelte.js +55 -0
  55. package/playground/node_modules/.vite/deps/svelte.js.map +7 -0
  56. package/playground/node_modules/.vite/deps/svelte___clsx.js +9 -0
  57. package/playground/node_modules/.vite/deps/svelte___clsx.js.map +7 -0
  58. package/playground/node_modules/.vite/deps/svelte_animate.js +57 -0
  59. package/playground/node_modules/.vite/deps/svelte_animate.js.map +7 -0
  60. package/playground/node_modules/.vite/deps/svelte_attachments.js +15 -0
  61. package/playground/node_modules/.vite/deps/svelte_attachments.js.map +7 -0
  62. package/playground/node_modules/.vite/deps/svelte_easing.js +67 -0
  63. package/playground/node_modules/.vite/deps/svelte_easing.js.map +7 -0
  64. package/playground/node_modules/.vite/deps/svelte_events.js +11 -0
  65. package/playground/node_modules/.vite/deps/svelte_events.js.map +7 -0
  66. package/playground/node_modules/.vite/deps/svelte_internal.js +5 -0
  67. package/playground/node_modules/.vite/deps/svelte_internal.js.map +7 -0
  68. package/playground/node_modules/.vite/deps/svelte_internal_client.js +402 -0
  69. package/playground/node_modules/.vite/deps/svelte_internal_client.js.map +7 -0
  70. package/playground/node_modules/.vite/deps/svelte_internal_disclose-version.js +10 -0
  71. package/playground/node_modules/.vite/deps/svelte_internal_disclose-version.js.map +7 -0
  72. package/playground/node_modules/.vite/deps/svelte_internal_flags_async.js +8 -0
  73. package/playground/node_modules/.vite/deps/svelte_internal_flags_async.js.map +7 -0
  74. package/playground/node_modules/.vite/deps/svelte_internal_flags_legacy.js +8 -0
  75. package/playground/node_modules/.vite/deps/svelte_internal_flags_legacy.js.map +7 -0
  76. package/playground/node_modules/.vite/deps/svelte_internal_flags_tracing.js +8 -0
  77. package/playground/node_modules/.vite/deps/svelte_internal_flags_tracing.js.map +7 -0
  78. package/playground/node_modules/.vite/deps/svelte_legacy.js +35 -0
  79. package/playground/node_modules/.vite/deps/svelte_legacy.js.map +7 -0
  80. package/playground/node_modules/.vite/deps/svelte_motion.js +545 -0
  81. package/playground/node_modules/.vite/deps/svelte_motion.js.map +7 -0
  82. package/playground/node_modules/.vite/deps/svelte_reactivity.js +29 -0
  83. package/playground/node_modules/.vite/deps/svelte_reactivity.js.map +7 -0
  84. package/playground/node_modules/.vite/deps/svelte_reactivity_window.js +127 -0
  85. package/playground/node_modules/.vite/deps/svelte_reactivity_window.js.map +7 -0
  86. package/playground/node_modules/.vite/deps/svelte_store.js +103 -0
  87. package/playground/node_modules/.vite/deps/svelte_store.js.map +7 -0
  88. package/playground/node_modules/.vite/deps/svelte_transition.js +208 -0
  89. package/playground/node_modules/.vite/deps/svelte_transition.js.map +7 -0
  90. package/playground/package.json +16 -0
  91. package/playground/pnpm-lock.yaml +3167 -0
  92. package/playground/src/content/authors/jane-doe.json +8 -0
  93. package/playground/src/content/config/build.toml +2 -0
  94. package/playground/src/content/courses/web-fundamentals.json +29 -0
  95. package/playground/src/content/docs/advanced.mdx +6 -0
  96. package/playground/src/content/docs/intro.md +6 -0
  97. package/playground/src/content/guides/getting-started.mdx +6 -0
  98. package/playground/src/content/posts/hello-world.md +7 -0
  99. package/playground/src/content/products/t-shirt.json +16 -0
  100. package/playground/src/content/recipes/pancakes.mdoc +8 -0
  101. package/playground/src/content/settings/site.yml +2 -0
  102. package/playground/src/content.config.ts +198 -0
  103. package/playground/src/env.d.ts +1 -0
  104. package/playground/src/pages/index.astro +11 -0
  105. package/playground/src/pages/nebula.astro +14 -0
  106. package/pnpm-workspace.yaml +2 -0
  107. package/scripts/subset-icons.mjs +178 -0
  108. package/src/astro/index.ts +295 -0
  109. package/src/client/Admin.svelte +283 -0
  110. package/src/client/components/BackendPicker.svelte +291 -0
  111. package/src/client/components/DraftChip.svelte +46 -0
  112. package/src/client/components/MetadataForm.svelte +56 -0
  113. package/src/client/components/ThemeToggle.svelte +18 -0
  114. package/src/client/components/dialogs/DeleteDraftDialog.svelte +51 -0
  115. package/src/client/components/dialogs/FilenameDialog.svelte +129 -0
  116. package/src/client/components/editor/EditorPane.svelte +227 -0
  117. package/src/client/components/editor/EditorTabs.svelte +81 -0
  118. package/src/client/components/editor/EditorToolbar.svelte +131 -0
  119. package/src/client/components/editor/FormatSelector.svelte +66 -0
  120. package/src/client/components/editor/Toolbar.svelte +17 -0
  121. package/src/client/components/fields/ArrayField.svelte +339 -0
  122. package/src/client/components/fields/ArrayItem.svelte +325 -0
  123. package/src/client/components/fields/BooleanField.svelte +114 -0
  124. package/src/client/components/fields/DateField.svelte +82 -0
  125. package/src/client/components/fields/EnumField.svelte +74 -0
  126. package/src/client/components/fields/FieldWrapper.svelte +96 -0
  127. package/src/client/components/fields/NumberField.svelte +99 -0
  128. package/src/client/components/fields/ObjectField.svelte +121 -0
  129. package/src/client/components/fields/SchemaField.svelte +107 -0
  130. package/src/client/components/fields/StringField.svelte +104 -0
  131. package/src/client/components/sidebar/AdminSidebar.svelte +339 -0
  132. package/src/client/components/sidebar/AdminSidebarSort.svelte +123 -0
  133. package/src/client/css/a11y.css +14 -0
  134. package/src/client/css/btn.css +113 -0
  135. package/src/client/css/dialog.css +29 -0
  136. package/src/client/css/field-input.css +39 -0
  137. package/src/client/css/reset.css +59 -0
  138. package/src/client/css/theme.css +77 -0
  139. package/src/client/index.ts +1 -0
  140. package/src/client/js/drafts/merge.svelte.ts +121 -0
  141. package/src/client/js/drafts/ops.svelte.ts +227 -0
  142. package/src/client/js/drafts/storage.ts +108 -0
  143. package/src/client/js/drafts/workers/diff.ts +40 -0
  144. package/src/client/js/editor/editor.svelte.ts +343 -0
  145. package/src/client/js/editor/languages.ts +98 -0
  146. package/src/client/js/editor/link-wrap.ts +45 -0
  147. package/src/client/js/editor/markdown-shortcuts.ts +261 -0
  148. package/src/client/js/handlers/admin.ts +246 -0
  149. package/src/client/js/state/dialogs.svelte.ts +35 -0
  150. package/src/client/js/state/router.svelte.ts +156 -0
  151. package/src/client/js/state/schema.svelte.ts +140 -0
  152. package/src/client/js/state/state.svelte.ts +334 -0
  153. package/src/client/js/state/theme.svelte.ts +173 -0
  154. package/src/client/js/storage/adapter.ts +102 -0
  155. package/src/client/js/storage/client.ts +150 -0
  156. package/src/client/js/storage/db.ts +36 -0
  157. package/src/client/js/storage/fsa.ts +110 -0
  158. package/src/client/js/storage/github.ts +297 -0
  159. package/src/client/js/storage/storage.ts +83 -0
  160. package/src/client/js/storage/workers/frontmatter.ts +320 -0
  161. package/src/client/js/storage/workers/storage.ts +177 -0
  162. package/src/client/js/storage/workers/toml-parser.ts +106 -0
  163. package/src/client/js/storage/workers/yaml-parser.ts +132 -0
  164. package/src/client/js/utils/file-types.ts +192 -0
  165. package/src/client/js/utils/format.ts +16 -0
  166. package/src/client/js/utils/frontmatter.ts +38 -0
  167. package/src/client/js/utils/schema-utils.ts +295 -0
  168. package/src/client/js/utils/slug.ts +18 -0
  169. package/src/client/js/utils/sort.ts +84 -0
  170. package/src/client/js/utils/stable-stringify.ts +27 -0
  171. package/src/client/js/utils/url-utils.ts +38 -0
  172. package/src/types.ts +25 -0
  173. package/src/virtual.d.ts +22 -0
  174. package/svelte.config.js +4 -0
  175. package/tests/astro/build.test.ts +63 -0
  176. package/tests/astro/index.test.ts +689 -0
  177. package/tests/client/components/Admin.test.ts +446 -0
  178. package/tests/client/components/BackendPicker.test.ts +239 -0
  179. package/tests/client/components/DraftChip.test.ts +53 -0
  180. package/tests/client/components/MetadataForm.test.ts +164 -0
  181. package/tests/client/components/dialogs/DeleteDraftDialog.test.ts +91 -0
  182. package/tests/client/components/dialogs/FilenameDialog.test.ts +209 -0
  183. package/tests/client/components/dialogs/dialog-stubs.ts +19 -0
  184. package/tests/client/components/editor/EditorPane.test.ts +100 -0
  185. package/tests/client/components/editor/EditorTabs.test.ts +253 -0
  186. package/tests/client/components/editor/EditorToolbar.test.ts +252 -0
  187. package/tests/client/components/editor/fixtures.ts +31 -0
  188. package/tests/client/components/fields/ArrayField.test.ts +197 -0
  189. package/tests/client/components/fields/BooleanField.test.ts +206 -0
  190. package/tests/client/components/fields/DateField.test.ts +210 -0
  191. package/tests/client/components/fields/EnumField.test.ts +246 -0
  192. package/tests/client/components/fields/NumberField.test.ts +240 -0
  193. package/tests/client/components/fields/ObjectField.test.ts +157 -0
  194. package/tests/client/components/fields/SchemaField.test.ts +190 -0
  195. package/tests/client/components/fields/StringField.test.ts +223 -0
  196. package/tests/client/components/sidebar/AdminSidebar.test.ts +285 -0
  197. package/tests/client/components/sidebar/AdminSidebarSort.test.ts +135 -0
  198. package/tests/client/components/sidebar/sort-mock.ts +23 -0
  199. package/tests/client/js/drafts/fixtures.ts +22 -0
  200. package/tests/client/js/drafts/merge.test.ts +282 -0
  201. package/tests/client/js/drafts/ops.test.ts +658 -0
  202. package/tests/client/js/drafts/storage.test.ts +200 -0
  203. package/tests/client/js/drafts/workers/diff.test.ts +165 -0
  204. package/tests/client/js/editor/editor.test.ts +616 -0
  205. package/tests/client/js/editor/link-wrap.test.ts +225 -0
  206. package/tests/client/js/editor/markdown-shortcuts.test.ts +370 -0
  207. package/tests/client/js/handlers/admin.test.ts +467 -0
  208. package/tests/client/js/state/router.test.ts +619 -0
  209. package/tests/client/js/state/schema.test.ts +266 -0
  210. package/tests/client/js/state/state.test.ts +328 -0
  211. package/tests/client/js/storage/adapter.test.ts +115 -0
  212. package/tests/client/js/storage/client.test.ts +250 -0
  213. package/tests/client/js/storage/db.test.ts +59 -0
  214. package/tests/client/js/storage/fsa.test.ts +284 -0
  215. package/tests/client/js/storage/github.test.ts +349 -0
  216. package/tests/client/js/storage/mock-port.ts +95 -0
  217. package/tests/client/js/storage/storage.test.ts +77 -0
  218. package/tests/client/js/storage/workers/frontmatter.test.ts +479 -0
  219. package/tests/client/js/storage/workers/storage.test.ts +299 -0
  220. package/tests/client/js/storage/workers/toml-parser.test.ts +169 -0
  221. package/tests/client/js/storage/workers/yaml-parser.test.ts +168 -0
  222. package/tests/client/js/utils/file-types.test.ts +268 -0
  223. package/tests/client/js/utils/frontmatter.test.ts +87 -0
  224. package/tests/client/js/utils/schema-utils.test.ts +318 -0
  225. package/tests/client/js/utils/slug.test.ts +58 -0
  226. package/tests/client/js/utils/sort.test.ts +276 -0
  227. package/tests/client/js/utils/stable-stringify.test.ts +68 -0
  228. package/tests/client/js/utils/url-utils.test.ts +70 -0
  229. package/tests/e2e/backend-connection.test.ts +301 -0
  230. package/tests/e2e/draft-lifecycle.test.ts +388 -0
  231. package/tests/e2e/editing.test.ts +355 -0
  232. package/tests/e2e/github-adapter.test.ts +330 -0
  233. package/tests/e2e/helpers/mock-adapter.ts +166 -0
  234. package/tests/e2e/helpers/test-app.ts +155 -0
  235. package/tests/e2e/navigation.test.ts +358 -0
  236. package/tests/e2e/publishing.test.ts +345 -0
  237. package/tests/e2e/unsaved-changes.test.ts +317 -0
  238. package/tests/setup.ts +2 -0
  239. package/tests/stubs/codemirror.ts +197 -0
  240. package/tsconfig.json +19 -0
  241. package/vitest.config.ts +178 -0
@@ -0,0 +1,84 @@
1
+ /*
2
+ * Sorting utilities and types for admin sidebar content lists.
3
+ * Handles sort mode persistence, date coercion, and comparator generation.
4
+ */
5
+
6
+ // Sidebar item displayed as a link in the admin navigation. Used by AdminSidebar for both collections and content lists.
7
+ export type SidebarItem = {
8
+ label: string;
9
+ href: string;
10
+ subtitle?: string;
11
+ date?: Date;
12
+ // Draft-specific fields
13
+ draftId?: string;
14
+ isDraft?: boolean;
15
+ isOutdated?: boolean;
16
+ };
17
+
18
+ // Sort mode identifiers.
19
+ export type SortMode = 'alpha' | 'date-asc' | 'date-desc';
20
+
21
+ // Sort mode configuration: icon name and display label
22
+ export const SORT_MODES: Record<SortMode, { icon: string; label: string }> = {
23
+ alpha: { icon: 'sort_by_alpha', label: 'Alphabetical' },
24
+ 'date-asc': { icon: 'hourglass_arrow_down', label: 'Oldest first' },
25
+ 'date-desc': { icon: 'hourglass_arrow_up', label: 'Newest first' },
26
+ };
27
+
28
+ // Fixed display order for sort options in the popover
29
+ export const SORT_ORDER: SortMode[] = ['alpha', 'date-asc', 'date-desc'];
30
+
31
+ /**
32
+ * Reads the persisted sort mode from localStorage, defaulting to 'alpha' if absent or invalid.
33
+ * @param {string} key - Collection name used to construct the storage key
34
+ * @return {SortMode} The persisted sort mode, or 'alpha' if not set
35
+ */
36
+ export function readSortMode(key: string): SortMode {
37
+ const stored = localStorage.getItem(`cms-sort-${key}`);
38
+ if (stored === 'alpha' || stored === 'date-asc' || stored === 'date-desc') {
39
+ return stored;
40
+ }
41
+ return 'alpha';
42
+ }
43
+
44
+ /**
45
+ * Persists the sort mode to localStorage.
46
+ * @param {string} key - Collection name used to construct the storage key
47
+ * @param {SortMode} mode - The sort mode to persist
48
+ * @return {void}
49
+ */
50
+ export function writeSortMode(key: string, mode: SortMode): void {
51
+ localStorage.setItem(`cms-sort-${key}`, mode);
52
+ }
53
+
54
+ /**
55
+ * Coerces a frontmatter published value (Date object or ISO string) into a Date for sorting. Returns undefined if the value is not a recognized date type.
56
+ * @param {unknown} val - The published value from frontmatter
57
+ * @return {Date | undefined} The resolved Date, or undefined
58
+ */
59
+ export function toSortDate(val: unknown): Date | undefined {
60
+ if (val instanceof Date) return val;
61
+ return typeof val === 'string' ? new Date(val) : undefined;
62
+ }
63
+
64
+ /**
65
+ * Returns a comparator function for sorting SidebarItems by the given mode.
66
+ * @param {SortMode} mode - The sort mode to use
67
+ * @return {(a: SidebarItem, b: SidebarItem) => number} A comparator function suitable for Array.sort
68
+ */
69
+ export function createComparator(
70
+ mode: SortMode,
71
+ ): (a: SidebarItem, b: SidebarItem) => number {
72
+ return (a, b) => {
73
+ if (mode === 'date-asc' || mode === 'date-desc') {
74
+ // Items without a date sort to the top (e.g. new drafts without a published date)
75
+ if (!a.date && !b.date) return 0;
76
+ if (!a.date) return -1;
77
+ if (!b.date) return 1;
78
+ return mode === 'date-asc'
79
+ ? a.date.getTime() - b.date.getTime()
80
+ : b.date.getTime() - a.date.getTime();
81
+ }
82
+ return a.label.toLowerCase().localeCompare(b.label.toLowerCase());
83
+ };
84
+ }
@@ -0,0 +1,27 @@
1
+ /*
2
+ * Deterministic JSON serialization with sorted keys.
3
+ * Prevents false-positive outdated detection when comparing snapshots.
4
+ */
5
+
6
+ /**
7
+ * Recursively sorts object keys and serializes to JSON.
8
+ * Produces deterministic output regardless of key insertion order.
9
+ * @param {unknown} value - The value to serialize
10
+ * @return {string} A JSON string with sorted keys at every nesting level
11
+ */
12
+ export function stableStringify(value: unknown): string {
13
+ return JSON.stringify(value, (_key, val) => {
14
+ if (val !== null && typeof val === 'object' && !Array.isArray(val)) {
15
+ return Object.keys(val as Record<string, unknown>)
16
+ .sort()
17
+ .reduce(
18
+ (sorted, k) => {
19
+ sorted[k] = (val as Record<string, unknown>)[k];
20
+ return sorted;
21
+ },
22
+ {} as Record<string, unknown>,
23
+ );
24
+ }
25
+ return val;
26
+ });
27
+ }
@@ -0,0 +1,38 @@
1
+ /*
2
+ * URL validation utilities for the editor's smart paste handler.
3
+ */
4
+
5
+ /**
6
+ * Checks whether a string is a valid URL (absolute with scheme, or absolute path).
7
+ * Used by the smart paste handler to determine whether clipboard content should
8
+ * be auto-wrapped as a markdown link rather than inserted as plain text.
9
+ * Trims whitespace before validating. Rejects plain words and relative paths
10
+ * without a leading slash to avoid false positives on normal text.
11
+ * @param {string} text - The string to validate
12
+ * @return {boolean} True if the text is a valid URL or absolute path
13
+ */
14
+ export function isURL(text: string): boolean {
15
+ const trimmed = text.trim();
16
+ if (!trimmed) return false;
17
+
18
+ // Absolute URL with scheme (https://, mailto:, etc.)
19
+ try {
20
+ new URL(trimmed);
21
+ return true;
22
+ } catch {
23
+ // Not an absolute URL — fall through
24
+ }
25
+
26
+ // Absolute path starting with /
27
+ if (trimmed.startsWith('/')) {
28
+ try {
29
+ // Dummy base required by URL constructor to resolve absolute paths
30
+ new URL(trimmed, 'https://a.com');
31
+ return true;
32
+ } catch {
33
+ return false;
34
+ }
35
+ }
36
+
37
+ return false;
38
+ }
package/src/types.ts ADDED
@@ -0,0 +1,25 @@
1
+ /*
2
+ * Configuration options for the nebula-cms Astro integration.
3
+ */
4
+ export interface NebulaCMSConfig {
5
+ /**
6
+ * URL prefix for the admin SPA.
7
+ * Accepts a relative or absolute path. Normalized automatically: leading `/`
8
+ * prepended if missing, consecutive slashes collapsed, trailing `/` stripped.
9
+ * After normalization, must contain only segments of letters, digits, hyphens,
10
+ * and underscores — or be `/` for root mount.
11
+ * @default '/admin'
12
+ */
13
+ basePath?: string;
14
+
15
+ /**
16
+ * URL prefix under which collection schema files are served.
17
+ * Accepts a relative or absolute path. Normalized automatically: leading `/`
18
+ * prepended if missing, consecutive slashes collapsed, trailing `/` stripped.
19
+ * After normalization, must contain only segments of letters, digits, hyphens,
20
+ * and underscores. Cannot be `/` (root) — collections require at least one
21
+ * path segment.
22
+ * @default '/collections'
23
+ */
24
+ collectionsPath?: string;
25
+ }
@@ -0,0 +1,22 @@
1
+ /*
2
+ * Ambient type declarations for virtual modules injected by the nebula-cms
3
+ * Vite plugin. Consumers reference these via `/// <reference types="nebula-cms/virtual" />`
4
+ * in their env.d.ts.
5
+ */
6
+ declare module 'virtual:nebula/config' {
7
+ /*
8
+ * CMS configuration: basePath is the URL prefix for the admin SPA,
9
+ * collectionsPath is the URL prefix for schema files.
10
+ */
11
+ const config: {
12
+ basePath: string;
13
+ collectionsPath: string;
14
+ };
15
+ export default config;
16
+ }
17
+
18
+ declare module 'virtual:nebula/collections' {
19
+ // Map of collection names to their schema.json public URLs
20
+ const collections: Record<string, string>;
21
+ export default collections;
22
+ }
@@ -0,0 +1,4 @@
1
+ /** @type {import('@sveltejs/package').Config} */
2
+ const config = {};
3
+
4
+ export default config;
@@ -0,0 +1,63 @@
1
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
2
+ import { execFileSync } from 'node:child_process';
3
+ import { resolve } from 'node:path';
4
+ import { existsSync, readdirSync, readFileSync, rmSync } from 'node:fs';
5
+
6
+ // Resolve playground path relative to this test file
7
+ const playgroundDir = resolve(import.meta.dirname, '../../playground');
8
+ const rootDir = resolve(playgroundDir, '..');
9
+
10
+ describe('playground build integration', () => {
11
+ beforeAll(() => {
12
+ // Build root package so playground can resolve the nebula-cms integration.
13
+ // The prebuild script generates icons.css automatically before svelte-package runs.
14
+ execFileSync('pnpm', ['build'], { cwd: rootDir, stdio: 'pipe' });
15
+ // Generate collection schemas, then build the Astro site
16
+ execFileSync('pnpm', ['sync'], { cwd: playgroundDir, stdio: 'pipe' });
17
+ execFileSync('pnpm', ['build'], { cwd: playgroundDir, stdio: 'pipe' });
18
+ }, 60_000);
19
+
20
+ afterAll(() => {
21
+ const dist = resolve(playgroundDir, 'dist');
22
+ if (existsSync(dist)) rmSync(dist, { recursive: true });
23
+ });
24
+
25
+ it('copies schema files into dist/collections/ via astro:build:done', () => {
26
+ const distCollections = resolve(playgroundDir, 'dist/collections');
27
+ expect(existsSync(distCollections)).toBe(true);
28
+
29
+ const files = readdirSync(distCollections).filter((f) =>
30
+ f.endsWith('.schema.json'),
31
+ );
32
+ const names = files.map((f) => f.replace('.schema.json', ''));
33
+ expect(names).toContain('posts');
34
+ expect(names).toContain('authors');
35
+ });
36
+
37
+ it('generates .schema.json for all four collections', () => {
38
+ const collectionsDir = resolve(playgroundDir, '.astro/collections');
39
+ const files = readdirSync(collectionsDir).filter((f) =>
40
+ f.endsWith('.schema.json'),
41
+ );
42
+ const names = files.map((f) => f.replace('.schema.json', ''));
43
+
44
+ expect(names).toContain('posts');
45
+ expect(names).toContain('authors');
46
+ expect(names).toContain('products');
47
+ expect(names).toContain('courses');
48
+ });
49
+
50
+ it('bundles collection schema paths into the client JS', () => {
51
+ // Admin uses client:only="svelte", so collection data lives in the JS
52
+ // bundle via the virtual:collections module, not in the static HTML.
53
+ const assetDir = resolve(playgroundDir, 'dist/_astro');
54
+ const jsFiles = readdirSync(assetDir).filter((f) => f.endsWith('.js'));
55
+ const allJs = jsFiles
56
+ .map((f) => readFileSync(resolve(assetDir, f), 'utf-8'))
57
+ .join('\n');
58
+
59
+ for (const name of ['posts', 'authors', 'products', 'courses']) {
60
+ expect(allJs).toContain(`/collections/${name}.schema.json`);
61
+ }
62
+ });
63
+ });