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.
- package/.claude/settings.local.json +42 -0
- package/.github/workflows/ci.yml +31 -0
- package/.mcp.json +12 -0
- package/.prettierignore +5 -0
- package/.prettierrc.cjs +22 -0
- package/AGENTS.md +183 -0
- package/LICENSE +201 -0
- package/README.md +128 -0
- package/package.json +74 -0
- package/playground/.claude/settings.local.json +5 -0
- package/playground/astro.config.mjs +7 -0
- package/playground/node_modules/.bin/astro +21 -0
- package/playground/node_modules/.bin/rollup +21 -0
- package/playground/node_modules/.bin/tsc +21 -0
- package/playground/node_modules/.bin/tsserver +21 -0
- package/playground/node_modules/.bin/vite +21 -0
- package/playground/node_modules/.vite/_svelte_metadata.json +1 -0
- package/playground/node_modules/.vite/deps/@astrojs_svelte_client__js.js +80 -0
- package/playground/node_modules/.vite/deps/@astrojs_svelte_client__js.js.map +7 -0
- package/playground/node_modules/.vite/deps/_metadata.json +184 -0
- package/playground/node_modules/.vite/deps/astro___aria-query.js +6776 -0
- package/playground/node_modules/.vite/deps/astro___aria-query.js.map +7 -0
- package/playground/node_modules/.vite/deps/astro___axobject-query.js +3754 -0
- package/playground/node_modules/.vite/deps/astro___axobject-query.js.map +7 -0
- package/playground/node_modules/.vite/deps/astro___html-escaper.js +34 -0
- package/playground/node_modules/.vite/deps/astro___html-escaper.js.map +7 -0
- package/playground/node_modules/.vite/deps/chunk-AJXJMYAF.js +0 -0
- package/playground/node_modules/.vite/deps/chunk-AJXJMYAF.js.map +7 -0
- package/playground/node_modules/.vite/deps/chunk-ALJIOON6.js +1005 -0
- package/playground/node_modules/.vite/deps/chunk-ALJIOON6.js.map +7 -0
- package/playground/node_modules/.vite/deps/chunk-BUSYA2B4.js +8 -0
- package/playground/node_modules/.vite/deps/chunk-BUSYA2B4.js.map +7 -0
- package/playground/node_modules/.vite/deps/chunk-CNYJBM5F.js +21 -0
- package/playground/node_modules/.vite/deps/chunk-CNYJBM5F.js.map +7 -0
- package/playground/node_modules/.vite/deps/chunk-DBPNBGEI.js +223 -0
- package/playground/node_modules/.vite/deps/chunk-DBPNBGEI.js.map +7 -0
- package/playground/node_modules/.vite/deps/chunk-G3C2FXJT.js +204 -0
- package/playground/node_modules/.vite/deps/chunk-G3C2FXJT.js.map +7 -0
- package/playground/node_modules/.vite/deps/chunk-GKDKFWC5.js +27 -0
- package/playground/node_modules/.vite/deps/chunk-GKDKFWC5.js.map +7 -0
- package/playground/node_modules/.vite/deps/chunk-HNCLEOC5.js +4376 -0
- package/playground/node_modules/.vite/deps/chunk-HNCLEOC5.js.map +7 -0
- package/playground/node_modules/.vite/deps/chunk-JICYXBFU.js +688 -0
- package/playground/node_modules/.vite/deps/chunk-JICYXBFU.js.map +7 -0
- package/playground/node_modules/.vite/deps/chunk-KCUTL6DD.js +5099 -0
- package/playground/node_modules/.vite/deps/chunk-KCUTL6DD.js.map +7 -0
- package/playground/node_modules/.vite/deps/chunk-ZP4UNCSN.js +23 -0
- package/playground/node_modules/.vite/deps/chunk-ZP4UNCSN.js.map +7 -0
- package/playground/node_modules/.vite/deps/chunk-ZREFNRZZ.js +148 -0
- package/playground/node_modules/.vite/deps/chunk-ZREFNRZZ.js.map +7 -0
- package/playground/node_modules/.vite/deps/package.json +3 -0
- package/playground/node_modules/.vite/deps/smol-toml.js +843 -0
- package/playground/node_modules/.vite/deps/smol-toml.js.map +7 -0
- package/playground/node_modules/.vite/deps/svelte.js +55 -0
- package/playground/node_modules/.vite/deps/svelte.js.map +7 -0
- package/playground/node_modules/.vite/deps/svelte___clsx.js +9 -0
- package/playground/node_modules/.vite/deps/svelte___clsx.js.map +7 -0
- package/playground/node_modules/.vite/deps/svelte_animate.js +57 -0
- package/playground/node_modules/.vite/deps/svelte_animate.js.map +7 -0
- package/playground/node_modules/.vite/deps/svelte_attachments.js +15 -0
- package/playground/node_modules/.vite/deps/svelte_attachments.js.map +7 -0
- package/playground/node_modules/.vite/deps/svelte_easing.js +67 -0
- package/playground/node_modules/.vite/deps/svelte_easing.js.map +7 -0
- package/playground/node_modules/.vite/deps/svelte_events.js +11 -0
- package/playground/node_modules/.vite/deps/svelte_events.js.map +7 -0
- package/playground/node_modules/.vite/deps/svelte_internal.js +5 -0
- package/playground/node_modules/.vite/deps/svelte_internal.js.map +7 -0
- package/playground/node_modules/.vite/deps/svelte_internal_client.js +402 -0
- package/playground/node_modules/.vite/deps/svelte_internal_client.js.map +7 -0
- package/playground/node_modules/.vite/deps/svelte_internal_disclose-version.js +10 -0
- package/playground/node_modules/.vite/deps/svelte_internal_disclose-version.js.map +7 -0
- package/playground/node_modules/.vite/deps/svelte_internal_flags_async.js +8 -0
- package/playground/node_modules/.vite/deps/svelte_internal_flags_async.js.map +7 -0
- package/playground/node_modules/.vite/deps/svelte_internal_flags_legacy.js +8 -0
- package/playground/node_modules/.vite/deps/svelte_internal_flags_legacy.js.map +7 -0
- package/playground/node_modules/.vite/deps/svelte_internal_flags_tracing.js +8 -0
- package/playground/node_modules/.vite/deps/svelte_internal_flags_tracing.js.map +7 -0
- package/playground/node_modules/.vite/deps/svelte_legacy.js +35 -0
- package/playground/node_modules/.vite/deps/svelte_legacy.js.map +7 -0
- package/playground/node_modules/.vite/deps/svelte_motion.js +545 -0
- package/playground/node_modules/.vite/deps/svelte_motion.js.map +7 -0
- package/playground/node_modules/.vite/deps/svelte_reactivity.js +29 -0
- package/playground/node_modules/.vite/deps/svelte_reactivity.js.map +7 -0
- package/playground/node_modules/.vite/deps/svelte_reactivity_window.js +127 -0
- package/playground/node_modules/.vite/deps/svelte_reactivity_window.js.map +7 -0
- package/playground/node_modules/.vite/deps/svelte_store.js +103 -0
- package/playground/node_modules/.vite/deps/svelte_store.js.map +7 -0
- package/playground/node_modules/.vite/deps/svelte_transition.js +208 -0
- package/playground/node_modules/.vite/deps/svelte_transition.js.map +7 -0
- package/playground/package.json +16 -0
- package/playground/pnpm-lock.yaml +3167 -0
- package/playground/src/content/authors/jane-doe.json +8 -0
- package/playground/src/content/config/build.toml +2 -0
- package/playground/src/content/courses/web-fundamentals.json +29 -0
- package/playground/src/content/docs/advanced.mdx +6 -0
- package/playground/src/content/docs/intro.md +6 -0
- package/playground/src/content/guides/getting-started.mdx +6 -0
- package/playground/src/content/posts/hello-world.md +7 -0
- package/playground/src/content/products/t-shirt.json +16 -0
- package/playground/src/content/recipes/pancakes.mdoc +8 -0
- package/playground/src/content/settings/site.yml +2 -0
- package/playground/src/content.config.ts +198 -0
- package/playground/src/env.d.ts +1 -0
- package/playground/src/pages/index.astro +11 -0
- package/playground/src/pages/nebula.astro +14 -0
- package/pnpm-workspace.yaml +2 -0
- package/scripts/subset-icons.mjs +178 -0
- package/src/astro/index.ts +295 -0
- package/src/client/Admin.svelte +283 -0
- package/src/client/components/BackendPicker.svelte +291 -0
- package/src/client/components/DraftChip.svelte +46 -0
- package/src/client/components/MetadataForm.svelte +56 -0
- package/src/client/components/ThemeToggle.svelte +18 -0
- package/src/client/components/dialogs/DeleteDraftDialog.svelte +51 -0
- package/src/client/components/dialogs/FilenameDialog.svelte +129 -0
- package/src/client/components/editor/EditorPane.svelte +227 -0
- package/src/client/components/editor/EditorTabs.svelte +81 -0
- package/src/client/components/editor/EditorToolbar.svelte +131 -0
- package/src/client/components/editor/FormatSelector.svelte +66 -0
- package/src/client/components/editor/Toolbar.svelte +17 -0
- package/src/client/components/fields/ArrayField.svelte +339 -0
- package/src/client/components/fields/ArrayItem.svelte +325 -0
- package/src/client/components/fields/BooleanField.svelte +114 -0
- package/src/client/components/fields/DateField.svelte +82 -0
- package/src/client/components/fields/EnumField.svelte +74 -0
- package/src/client/components/fields/FieldWrapper.svelte +96 -0
- package/src/client/components/fields/NumberField.svelte +99 -0
- package/src/client/components/fields/ObjectField.svelte +121 -0
- package/src/client/components/fields/SchemaField.svelte +107 -0
- package/src/client/components/fields/StringField.svelte +104 -0
- package/src/client/components/sidebar/AdminSidebar.svelte +339 -0
- package/src/client/components/sidebar/AdminSidebarSort.svelte +123 -0
- package/src/client/css/a11y.css +14 -0
- package/src/client/css/btn.css +113 -0
- package/src/client/css/dialog.css +29 -0
- package/src/client/css/field-input.css +39 -0
- package/src/client/css/reset.css +59 -0
- package/src/client/css/theme.css +77 -0
- package/src/client/index.ts +1 -0
- package/src/client/js/drafts/merge.svelte.ts +121 -0
- package/src/client/js/drafts/ops.svelte.ts +227 -0
- package/src/client/js/drafts/storage.ts +108 -0
- package/src/client/js/drafts/workers/diff.ts +40 -0
- package/src/client/js/editor/editor.svelte.ts +343 -0
- package/src/client/js/editor/languages.ts +98 -0
- package/src/client/js/editor/link-wrap.ts +45 -0
- package/src/client/js/editor/markdown-shortcuts.ts +261 -0
- package/src/client/js/handlers/admin.ts +246 -0
- package/src/client/js/state/dialogs.svelte.ts +35 -0
- package/src/client/js/state/router.svelte.ts +156 -0
- package/src/client/js/state/schema.svelte.ts +140 -0
- package/src/client/js/state/state.svelte.ts +334 -0
- package/src/client/js/state/theme.svelte.ts +173 -0
- package/src/client/js/storage/adapter.ts +102 -0
- package/src/client/js/storage/client.ts +150 -0
- package/src/client/js/storage/db.ts +36 -0
- package/src/client/js/storage/fsa.ts +110 -0
- package/src/client/js/storage/github.ts +297 -0
- package/src/client/js/storage/storage.ts +83 -0
- package/src/client/js/storage/workers/frontmatter.ts +320 -0
- package/src/client/js/storage/workers/storage.ts +177 -0
- package/src/client/js/storage/workers/toml-parser.ts +106 -0
- package/src/client/js/storage/workers/yaml-parser.ts +132 -0
- package/src/client/js/utils/file-types.ts +192 -0
- package/src/client/js/utils/format.ts +16 -0
- package/src/client/js/utils/frontmatter.ts +38 -0
- package/src/client/js/utils/schema-utils.ts +295 -0
- package/src/client/js/utils/slug.ts +18 -0
- package/src/client/js/utils/sort.ts +84 -0
- package/src/client/js/utils/stable-stringify.ts +27 -0
- package/src/client/js/utils/url-utils.ts +38 -0
- package/src/types.ts +25 -0
- package/src/virtual.d.ts +22 -0
- package/svelte.config.js +4 -0
- package/tests/astro/build.test.ts +63 -0
- package/tests/astro/index.test.ts +689 -0
- package/tests/client/components/Admin.test.ts +446 -0
- package/tests/client/components/BackendPicker.test.ts +239 -0
- package/tests/client/components/DraftChip.test.ts +53 -0
- package/tests/client/components/MetadataForm.test.ts +164 -0
- package/tests/client/components/dialogs/DeleteDraftDialog.test.ts +91 -0
- package/tests/client/components/dialogs/FilenameDialog.test.ts +209 -0
- package/tests/client/components/dialogs/dialog-stubs.ts +19 -0
- package/tests/client/components/editor/EditorPane.test.ts +100 -0
- package/tests/client/components/editor/EditorTabs.test.ts +253 -0
- package/tests/client/components/editor/EditorToolbar.test.ts +252 -0
- package/tests/client/components/editor/fixtures.ts +31 -0
- package/tests/client/components/fields/ArrayField.test.ts +197 -0
- package/tests/client/components/fields/BooleanField.test.ts +206 -0
- package/tests/client/components/fields/DateField.test.ts +210 -0
- package/tests/client/components/fields/EnumField.test.ts +246 -0
- package/tests/client/components/fields/NumberField.test.ts +240 -0
- package/tests/client/components/fields/ObjectField.test.ts +157 -0
- package/tests/client/components/fields/SchemaField.test.ts +190 -0
- package/tests/client/components/fields/StringField.test.ts +223 -0
- package/tests/client/components/sidebar/AdminSidebar.test.ts +285 -0
- package/tests/client/components/sidebar/AdminSidebarSort.test.ts +135 -0
- package/tests/client/components/sidebar/sort-mock.ts +23 -0
- package/tests/client/js/drafts/fixtures.ts +22 -0
- package/tests/client/js/drafts/merge.test.ts +282 -0
- package/tests/client/js/drafts/ops.test.ts +658 -0
- package/tests/client/js/drafts/storage.test.ts +200 -0
- package/tests/client/js/drafts/workers/diff.test.ts +165 -0
- package/tests/client/js/editor/editor.test.ts +616 -0
- package/tests/client/js/editor/link-wrap.test.ts +225 -0
- package/tests/client/js/editor/markdown-shortcuts.test.ts +370 -0
- package/tests/client/js/handlers/admin.test.ts +467 -0
- package/tests/client/js/state/router.test.ts +619 -0
- package/tests/client/js/state/schema.test.ts +266 -0
- package/tests/client/js/state/state.test.ts +328 -0
- package/tests/client/js/storage/adapter.test.ts +115 -0
- package/tests/client/js/storage/client.test.ts +250 -0
- package/tests/client/js/storage/db.test.ts +59 -0
- package/tests/client/js/storage/fsa.test.ts +284 -0
- package/tests/client/js/storage/github.test.ts +349 -0
- package/tests/client/js/storage/mock-port.ts +95 -0
- package/tests/client/js/storage/storage.test.ts +77 -0
- package/tests/client/js/storage/workers/frontmatter.test.ts +479 -0
- package/tests/client/js/storage/workers/storage.test.ts +299 -0
- package/tests/client/js/storage/workers/toml-parser.test.ts +169 -0
- package/tests/client/js/storage/workers/yaml-parser.test.ts +168 -0
- package/tests/client/js/utils/file-types.test.ts +268 -0
- package/tests/client/js/utils/frontmatter.test.ts +87 -0
- package/tests/client/js/utils/schema-utils.test.ts +318 -0
- package/tests/client/js/utils/slug.test.ts +58 -0
- package/tests/client/js/utils/sort.test.ts +276 -0
- package/tests/client/js/utils/stable-stringify.test.ts +68 -0
- package/tests/client/js/utils/url-utils.test.ts +70 -0
- package/tests/e2e/backend-connection.test.ts +301 -0
- package/tests/e2e/draft-lifecycle.test.ts +388 -0
- package/tests/e2e/editing.test.ts +355 -0
- package/tests/e2e/github-adapter.test.ts +330 -0
- package/tests/e2e/helpers/mock-adapter.ts +166 -0
- package/tests/e2e/helpers/test-app.ts +155 -0
- package/tests/e2e/navigation.test.ts +358 -0
- package/tests/e2e/publishing.test.ts +345 -0
- package/tests/e2e/unsaved-changes.test.ts +317 -0
- package/tests/setup.ts +2 -0
- package/tests/stubs/codemirror.ts +197 -0
- package/tsconfig.json +19 -0
- 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
|
+
}
|
package/src/virtual.d.ts
ADDED
|
@@ -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
|
+
}
|
package/svelte.config.js
ADDED
|
@@ -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
|
+
});
|