@nuasite/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 (269) hide show
  1. package/README.md +237 -0
  2. package/dist/src/build-processor.d.ts +20 -0
  3. package/dist/src/build-processor.d.ts.map +1 -0
  4. package/dist/src/collection-scanner.d.ts +6 -0
  5. package/dist/src/collection-scanner.d.ts.map +1 -0
  6. package/dist/src/component-registry.d.ts +63 -0
  7. package/dist/src/component-registry.d.ts.map +1 -0
  8. package/dist/src/config.d.ts +24 -0
  9. package/dist/src/config.d.ts.map +1 -0
  10. package/dist/src/dev-middleware.d.ts +20 -0
  11. package/dist/src/dev-middleware.d.ts.map +1 -0
  12. package/dist/src/editor/ai.d.ts +60 -0
  13. package/dist/src/editor/ai.d.ts.map +1 -0
  14. package/dist/src/editor/api.d.ts +140 -0
  15. package/dist/src/editor/api.d.ts.map +1 -0
  16. package/dist/src/editor/color-utils.d.ts +106 -0
  17. package/dist/src/editor/color-utils.d.ts.map +1 -0
  18. package/dist/src/editor/components/ai-chat.d.ts +11 -0
  19. package/dist/src/editor/components/ai-chat.d.ts.map +1 -0
  20. package/dist/src/editor/components/ai-tooltip.d.ts +12 -0
  21. package/dist/src/editor/components/ai-tooltip.d.ts.map +1 -0
  22. package/dist/src/editor/components/attribute-editor.d.ts +5 -0
  23. package/dist/src/editor/components/attribute-editor.d.ts.map +1 -0
  24. package/dist/src/editor/components/block-editor.d.ts +12 -0
  25. package/dist/src/editor/components/block-editor.d.ts.map +1 -0
  26. package/dist/src/editor/components/collections-browser.d.ts +2 -0
  27. package/dist/src/editor/components/collections-browser.d.ts.map +1 -0
  28. package/dist/src/editor/components/color-toolbar.d.ts +12 -0
  29. package/dist/src/editor/components/color-toolbar.d.ts.map +1 -0
  30. package/dist/src/editor/components/confirm-dialog.d.ts +2 -0
  31. package/dist/src/editor/components/confirm-dialog.d.ts.map +1 -0
  32. package/dist/src/editor/components/create-page-modal.d.ts +2 -0
  33. package/dist/src/editor/components/create-page-modal.d.ts.map +1 -0
  34. package/dist/src/editor/components/editable-highlights.d.ts +9 -0
  35. package/dist/src/editor/components/editable-highlights.d.ts.map +1 -0
  36. package/dist/src/editor/components/error-boundary.d.ts +32 -0
  37. package/dist/src/editor/components/error-boundary.d.ts.map +1 -0
  38. package/dist/src/editor/components/fields.d.ts +75 -0
  39. package/dist/src/editor/components/fields.d.ts.map +1 -0
  40. package/dist/src/editor/components/frontmatter-fields.d.ts +29 -0
  41. package/dist/src/editor/components/frontmatter-fields.d.ts.map +1 -0
  42. package/dist/src/editor/components/highlight-overlay.d.ts +64 -0
  43. package/dist/src/editor/components/highlight-overlay.d.ts.map +1 -0
  44. package/dist/src/editor/components/image-overlay.d.ts +12 -0
  45. package/dist/src/editor/components/image-overlay.d.ts.map +1 -0
  46. package/dist/src/editor/components/markdown-editor-overlay.d.ts +6 -0
  47. package/dist/src/editor/components/markdown-editor-overlay.d.ts.map +1 -0
  48. package/dist/src/editor/components/markdown-inline-editor.d.ts +10 -0
  49. package/dist/src/editor/components/markdown-inline-editor.d.ts.map +1 -0
  50. package/dist/src/editor/components/media-library.d.ts +2 -0
  51. package/dist/src/editor/components/media-library.d.ts.map +1 -0
  52. package/dist/src/editor/components/outline.d.ts +21 -0
  53. package/dist/src/editor/components/outline.d.ts.map +1 -0
  54. package/dist/src/editor/components/redirect-countdown.d.ts +2 -0
  55. package/dist/src/editor/components/redirect-countdown.d.ts.map +1 -0
  56. package/dist/src/editor/components/seo-editor.d.ts +2 -0
  57. package/dist/src/editor/components/seo-editor.d.ts.map +1 -0
  58. package/dist/src/editor/components/text-style-toolbar.d.ts +8 -0
  59. package/dist/src/editor/components/text-style-toolbar.d.ts.map +1 -0
  60. package/dist/src/editor/components/toast/toast-container.d.ts +7 -0
  61. package/dist/src/editor/components/toast/toast-container.d.ts.map +1 -0
  62. package/dist/src/editor/components/toast/toast.d.ts +7 -0
  63. package/dist/src/editor/components/toast/toast.d.ts.map +1 -0
  64. package/dist/src/editor/components/toast/types.d.ts +7 -0
  65. package/dist/src/editor/components/toast/types.d.ts.map +1 -0
  66. package/dist/src/editor/components/toolbar.d.ts +21 -0
  67. package/dist/src/editor/components/toolbar.d.ts.map +1 -0
  68. package/dist/src/editor/config.d.ts +4 -0
  69. package/dist/src/editor/config.d.ts.map +1 -0
  70. package/dist/src/editor/constants.d.ts +101 -0
  71. package/dist/src/editor/constants.d.ts.map +1 -0
  72. package/dist/src/editor/context.d.ts +14 -0
  73. package/dist/src/editor/context.d.ts.map +1 -0
  74. package/dist/src/editor/dom.d.ts +77 -0
  75. package/dist/src/editor/dom.d.ts.map +1 -0
  76. package/dist/src/editor/editor.d.ts +64 -0
  77. package/dist/src/editor/editor.d.ts.map +1 -0
  78. package/dist/src/editor/history.d.ts +20 -0
  79. package/dist/src/editor/history.d.ts.map +1 -0
  80. package/dist/src/editor/hooks/index.d.ts +14 -0
  81. package/dist/src/editor/hooks/index.d.ts.map +1 -0
  82. package/dist/src/editor/hooks/useAIHandlers.d.ts +22 -0
  83. package/dist/src/editor/hooks/useAIHandlers.d.ts.map +1 -0
  84. package/dist/src/editor/hooks/useBlockEditorHandlers.d.ts +18 -0
  85. package/dist/src/editor/hooks/useBlockEditorHandlers.d.ts.map +1 -0
  86. package/dist/src/editor/hooks/useElementDetection.d.ts +26 -0
  87. package/dist/src/editor/hooks/useElementDetection.d.ts.map +1 -0
  88. package/dist/src/editor/hooks/useImageHoverDetection.d.ts +12 -0
  89. package/dist/src/editor/hooks/useImageHoverDetection.d.ts.map +1 -0
  90. package/dist/src/editor/hooks/useTextSelection.d.ts +23 -0
  91. package/dist/src/editor/hooks/useTextSelection.d.ts.map +1 -0
  92. package/dist/src/editor/hooks/useTooltipState.d.ts +19 -0
  93. package/dist/src/editor/hooks/useTooltipState.d.ts.map +1 -0
  94. package/dist/src/editor/hooks/utils.d.ts +32 -0
  95. package/dist/src/editor/hooks/utils.d.ts.map +1 -0
  96. package/dist/src/editor/index.d.ts +12 -0
  97. package/dist/src/editor/index.d.ts.map +1 -0
  98. package/dist/src/editor/lib/cn.d.ts +3 -0
  99. package/dist/src/editor/lib/cn.d.ts.map +1 -0
  100. package/dist/src/editor/manifest.d.ts +19 -0
  101. package/dist/src/editor/manifest.d.ts.map +1 -0
  102. package/dist/src/editor/markdown-api.d.ts +36 -0
  103. package/dist/src/editor/markdown-api.d.ts.map +1 -0
  104. package/dist/src/editor/signals.d.ts +242 -0
  105. package/dist/src/editor/signals.d.ts.map +1 -0
  106. package/dist/src/editor/storage.d.ts +27 -0
  107. package/dist/src/editor/storage.d.ts.map +1 -0
  108. package/dist/src/editor/text-styling.d.ts +350 -0
  109. package/dist/src/editor/text-styling.d.ts.map +1 -0
  110. package/dist/src/editor/themes.d.ts +38 -0
  111. package/dist/src/editor/themes.d.ts.map +1 -0
  112. package/dist/src/editor/types.d.ts +454 -0
  113. package/dist/src/editor/types.d.ts.map +1 -0
  114. package/dist/src/error-collector.d.ts +56 -0
  115. package/dist/src/error-collector.d.ts.map +1 -0
  116. package/dist/src/handlers/component-ops.d.ts +34 -0
  117. package/dist/src/handlers/component-ops.d.ts.map +1 -0
  118. package/dist/src/handlers/markdown-ops.d.ts +41 -0
  119. package/dist/src/handlers/markdown-ops.d.ts.map +1 -0
  120. package/dist/src/handlers/request-utils.d.ts +20 -0
  121. package/dist/src/handlers/request-utils.d.ts.map +1 -0
  122. package/dist/src/handlers/source-writer.d.ts +51 -0
  123. package/dist/src/handlers/source-writer.d.ts.map +1 -0
  124. package/dist/src/html-processor.d.ts +63 -0
  125. package/dist/src/html-processor.d.ts.map +1 -0
  126. package/dist/src/index.d.ts +41 -0
  127. package/dist/src/index.d.ts.map +1 -0
  128. package/dist/src/manifest-writer.d.ts +111 -0
  129. package/dist/src/manifest-writer.d.ts.map +1 -0
  130. package/dist/src/media/contember.d.ts +15 -0
  131. package/dist/src/media/contember.d.ts.map +1 -0
  132. package/dist/src/media/local.d.ts +9 -0
  133. package/dist/src/media/local.d.ts.map +1 -0
  134. package/dist/src/media/s3.d.ts +12 -0
  135. package/dist/src/media/s3.d.ts.map +1 -0
  136. package/dist/src/media/types.d.ts +40 -0
  137. package/dist/src/media/types.d.ts.map +1 -0
  138. package/dist/src/preview-generator.d.ts +19 -0
  139. package/dist/src/preview-generator.d.ts.map +1 -0
  140. package/dist/src/seo-processor.d.ts +23 -0
  141. package/dist/src/seo-processor.d.ts.map +1 -0
  142. package/dist/src/source-finder/ast-extractors.d.ts +35 -0
  143. package/dist/src/source-finder/ast-extractors.d.ts.map +1 -0
  144. package/dist/src/source-finder/ast-parser.d.ts +16 -0
  145. package/dist/src/source-finder/ast-parser.d.ts.map +1 -0
  146. package/dist/src/source-finder/cache.d.ts +18 -0
  147. package/dist/src/source-finder/cache.d.ts.map +1 -0
  148. package/dist/src/source-finder/collection-finder.d.ts +29 -0
  149. package/dist/src/source-finder/collection-finder.d.ts.map +1 -0
  150. package/dist/src/source-finder/cross-file-tracker.d.ts +39 -0
  151. package/dist/src/source-finder/cross-file-tracker.d.ts.map +1 -0
  152. package/dist/src/source-finder/element-finder.d.ts +42 -0
  153. package/dist/src/source-finder/element-finder.d.ts.map +1 -0
  154. package/dist/src/source-finder/image-finder.d.ts +24 -0
  155. package/dist/src/source-finder/image-finder.d.ts.map +1 -0
  156. package/dist/src/source-finder/index.d.ts +9 -0
  157. package/dist/src/source-finder/index.d.ts.map +1 -0
  158. package/dist/src/source-finder/search-index.d.ts +27 -0
  159. package/dist/src/source-finder/search-index.d.ts.map +1 -0
  160. package/dist/src/source-finder/snippet-utils.d.ts +90 -0
  161. package/dist/src/source-finder/snippet-utils.d.ts.map +1 -0
  162. package/dist/src/source-finder/source-lookup.d.ts +16 -0
  163. package/dist/src/source-finder/source-lookup.d.ts.map +1 -0
  164. package/dist/src/source-finder/types.d.ts +167 -0
  165. package/dist/src/source-finder/types.d.ts.map +1 -0
  166. package/dist/src/source-finder/variable-extraction.d.ts +37 -0
  167. package/dist/src/source-finder/variable-extraction.d.ts.map +1 -0
  168. package/dist/src/tailwind-colors.d.ts +54 -0
  169. package/dist/src/tailwind-colors.d.ts.map +1 -0
  170. package/dist/src/tsconfig.tsbuildinfo +1 -0
  171. package/dist/src/types.d.ts +367 -0
  172. package/dist/src/types.d.ts.map +1 -0
  173. package/dist/src/utils.d.ts +61 -0
  174. package/dist/src/utils.d.ts.map +1 -0
  175. package/dist/src/vite-plugin.d.ts +14 -0
  176. package/dist/src/vite-plugin.d.ts.map +1 -0
  177. package/dist/types/tsconfig.tsbuildinfo +1 -0
  178. package/package.json +80 -0
  179. package/src/build-processor.ts +784 -0
  180. package/src/collection-scanner.ts +304 -0
  181. package/src/component-registry.ts +393 -0
  182. package/src/config.ts +74 -0
  183. package/src/dev-middleware.ts +525 -0
  184. package/src/dist/src/tsconfig.tsbuildinfo +1 -0
  185. package/src/editor/ai.ts +185 -0
  186. package/src/editor/api.ts +513 -0
  187. package/src/editor/color-utils.ts +556 -0
  188. package/src/editor/components/ai-chat.tsx +632 -0
  189. package/src/editor/components/ai-tooltip.tsx +179 -0
  190. package/src/editor/components/attribute-editor.tsx +596 -0
  191. package/src/editor/components/block-editor.tsx +546 -0
  192. package/src/editor/components/collections-browser.tsx +248 -0
  193. package/src/editor/components/color-toolbar.tsx +314 -0
  194. package/src/editor/components/confirm-dialog.tsx +69 -0
  195. package/src/editor/components/create-page-modal.tsx +163 -0
  196. package/src/editor/components/editable-highlights.tsx +260 -0
  197. package/src/editor/components/error-boundary.tsx +87 -0
  198. package/src/editor/components/fields.tsx +387 -0
  199. package/src/editor/components/frontmatter-fields.tsx +469 -0
  200. package/src/editor/components/highlight-overlay.ts +229 -0
  201. package/src/editor/components/image-overlay.tsx +230 -0
  202. package/src/editor/components/markdown-editor-overlay.tsx +505 -0
  203. package/src/editor/components/markdown-inline-editor.tsx +780 -0
  204. package/src/editor/components/media-library.tsx +297 -0
  205. package/src/editor/components/outline.tsx +402 -0
  206. package/src/editor/components/redirect-countdown.tsx +45 -0
  207. package/src/editor/components/seo-editor.tsx +498 -0
  208. package/src/editor/components/text-style-toolbar.tsx +362 -0
  209. package/src/editor/components/toast/toast-container.tsx +15 -0
  210. package/src/editor/components/toast/toast.tsx +49 -0
  211. package/src/editor/components/toast/types.ts +7 -0
  212. package/src/editor/components/toolbar.tsx +366 -0
  213. package/src/editor/config.ts +12 -0
  214. package/src/editor/constants.ts +106 -0
  215. package/src/editor/context.tsx +38 -0
  216. package/src/editor/dom.ts +357 -0
  217. package/src/editor/editor.ts +1510 -0
  218. package/src/editor/env.d.ts +4 -0
  219. package/src/editor/history.ts +355 -0
  220. package/src/editor/hooks/index.ts +19 -0
  221. package/src/editor/hooks/useAIHandlers.ts +345 -0
  222. package/src/editor/hooks/useBlockEditorHandlers.ts +206 -0
  223. package/src/editor/hooks/useElementDetection.ts +284 -0
  224. package/src/editor/hooks/useImageHoverDetection.ts +102 -0
  225. package/src/editor/hooks/useTextSelection.ts +187 -0
  226. package/src/editor/hooks/useTooltipState.ts +126 -0
  227. package/src/editor/hooks/utils.ts +101 -0
  228. package/src/editor/index.tsx +481 -0
  229. package/src/editor/lib/cn.ts +4 -0
  230. package/src/editor/manifest.ts +25 -0
  231. package/src/editor/markdown-api.ts +209 -0
  232. package/src/editor/signals.ts +1351 -0
  233. package/src/editor/storage.ts +266 -0
  234. package/src/editor/styles.css +465 -0
  235. package/src/editor/text-styling.ts +773 -0
  236. package/src/editor/themes.ts +210 -0
  237. package/src/editor/types.ts +591 -0
  238. package/src/error-collector.ts +106 -0
  239. package/src/handlers/component-ops.ts +463 -0
  240. package/src/handlers/markdown-ops.ts +202 -0
  241. package/src/handlers/request-utils.ts +151 -0
  242. package/src/handlers/source-writer.ts +649 -0
  243. package/src/html-processor.ts +1108 -0
  244. package/src/index.ts +284 -0
  245. package/src/manifest-writer.ts +371 -0
  246. package/src/media/contember.ts +84 -0
  247. package/src/media/local.ts +114 -0
  248. package/src/media/s3.ts +133 -0
  249. package/src/media/types.ts +33 -0
  250. package/src/preview-generator.ts +293 -0
  251. package/src/seo-processor.ts +567 -0
  252. package/src/source-finder/ast-extractors.ts +185 -0
  253. package/src/source-finder/ast-parser.ts +150 -0
  254. package/src/source-finder/cache.ts +76 -0
  255. package/src/source-finder/collection-finder.ts +335 -0
  256. package/src/source-finder/cross-file-tracker.ts +741 -0
  257. package/src/source-finder/element-finder.ts +387 -0
  258. package/src/source-finder/image-finder.ts +283 -0
  259. package/src/source-finder/index.ts +37 -0
  260. package/src/source-finder/search-index.ts +525 -0
  261. package/src/source-finder/snippet-utils.ts +668 -0
  262. package/src/source-finder/source-lookup.ts +200 -0
  263. package/src/source-finder/types.ts +210 -0
  264. package/src/source-finder/variable-extraction.ts +406 -0
  265. package/src/tailwind-colors.ts +874 -0
  266. package/src/tsconfig.json +25 -0
  267. package/src/types.ts +406 -0
  268. package/src/utils.ts +186 -0
  269. package/src/vite-plugin.ts +42 -0
package/src/index.ts ADDED
@@ -0,0 +1,284 @@
1
+ import type { AstroIntegration } from 'astro'
2
+ import { dirname, join } from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+ import tailwindcss from '@tailwindcss/vite'
5
+
6
+ import { processBuildOutput } from './build-processor'
7
+ import { scanCollections } from './collection-scanner'
8
+ import { ComponentRegistry } from './component-registry'
9
+ import { resetProjectRoot } from './config'
10
+ import { createDevMiddleware } from './dev-middleware'
11
+ import { getErrorCollector, resetErrorCollector } from './error-collector'
12
+ import { ManifestWriter } from './manifest-writer'
13
+ import { createLocalStorageAdapter } from './media/local'
14
+ import type { MediaStorageAdapter } from './media/types'
15
+ import type { CmsMarkerOptions, ComponentDefinition } from './types'
16
+ import { createVitePlugin } from './vite-plugin'
17
+
18
+ export interface NuaCmsOptions extends CmsMarkerOptions {
19
+ /**
20
+ * URL to the CMS editor script.
21
+ * If not set, the built-in editor bundle is served from the dev server.
22
+ */
23
+ src?: string
24
+ /**
25
+ * CMS configuration passed as window.NuaCmsConfig.
26
+ */
27
+ cmsConfig?: {
28
+ apiBase?: string
29
+ highlightColor?: string
30
+ debug?: boolean
31
+ theme?: Record<string, string>
32
+ themePreset?: string
33
+ }
34
+ /**
35
+ * Proxy /_nua/cms requests to this target URL during dev.
36
+ * Example: 'http://localhost:8787'
37
+ */
38
+ proxy?: string
39
+ /**
40
+ * Media storage adapter for file uploads.
41
+ * Defaults to local filesystem (public/uploads) when no proxy is configured.
42
+ */
43
+ media?: MediaStorageAdapter
44
+ }
45
+
46
+ const VIRTUAL_CMS_PATH = '/@nuasite/cms-editor.js'
47
+
48
+ export default function nuaCms(options: NuaCmsOptions = {}): AstroIntegration {
49
+ const {
50
+ // CMS editor options
51
+ src,
52
+ cmsConfig,
53
+ proxy,
54
+ media,
55
+ // CMS marker options
56
+ attributeName = 'data-cms-id',
57
+ includeTags = null,
58
+ excludeTags = ['html', 'head', 'body', 'script', 'style'],
59
+ includeEmptyText = false,
60
+ generateManifest = true,
61
+ manifestFile = 'cms-manifest.json',
62
+ markComponents = true,
63
+ componentDirs = ['src/components'],
64
+ contentDir = 'src/content',
65
+ seo = { trackSeo: true, markTitle: true, parseJsonLd: true },
66
+ } = options
67
+
68
+ // When no proxy, enable local CMS API with default media adapter
69
+ const enableCmsApi = !proxy
70
+ const mediaAdapter = media ?? (enableCmsApi ? createLocalStorageAdapter() : undefined)
71
+
72
+ // Default apiBase to local dev server when no proxy
73
+ const resolvedCmsConfig = enableCmsApi && !cmsConfig?.apiBase
74
+ ? { ...cmsConfig, apiBase: '/_nua/cms' }
75
+ : cmsConfig
76
+
77
+ let componentDefinitions: Record<string, ComponentDefinition> = {}
78
+
79
+ const idCounter = { value: 0 }
80
+ const manifestWriter = new ManifestWriter(manifestFile, componentDefinitions)
81
+
82
+ const markerConfig = {
83
+ attributeName,
84
+ includeTags,
85
+ excludeTags,
86
+ includeEmptyText,
87
+ generateManifest,
88
+ manifestFile,
89
+ markComponents,
90
+ componentDirs,
91
+ contentDir,
92
+ seo,
93
+ }
94
+
95
+ return {
96
+ name: '@nuasite/astro-cms',
97
+ hooks: {
98
+ 'astro:config:setup': async ({ updateConfig, command, injectScript, logger }) => {
99
+ // --- CMS Marker setup ---
100
+ idCounter.value = 0
101
+ manifestWriter.reset()
102
+ resetErrorCollector()
103
+ resetProjectRoot()
104
+
105
+ await manifestWriter.loadAvailableColors()
106
+
107
+ if (markComponents) {
108
+ const registry = new ComponentRegistry(componentDirs)
109
+ await registry.scan()
110
+ componentDefinitions = registry.getComponents()
111
+ manifestWriter.setComponentDefinitions(componentDefinitions)
112
+
113
+ const componentCount = Object.keys(componentDefinitions).length
114
+ if (componentCount > 0) {
115
+ logger.info(`Found ${componentCount} component definitions`)
116
+ }
117
+ }
118
+
119
+ const collectionDefinitions = await scanCollections(contentDir)
120
+ manifestWriter.setCollectionDefinitions(collectionDefinitions)
121
+
122
+ const collectionCount = Object.keys(collectionDefinitions).length
123
+ if (collectionCount > 0) {
124
+ logger.info(`Found ${collectionCount} content collection(s)`)
125
+ }
126
+
127
+ const pluginContext = {
128
+ manifestWriter,
129
+ componentDefinitions,
130
+ config: markerConfig,
131
+ idCounter,
132
+ command,
133
+ }
134
+
135
+ const vitePlugins: any[] = [...(createVitePlugin(pluginContext) as any)]
136
+
137
+ // --- CMS Editor setup (dev only) ---
138
+ if (command === 'dev') {
139
+ const editorSrc = src ?? VIRTUAL_CMS_PATH
140
+
141
+ const configScript = resolvedCmsConfig
142
+ ? `window.NuaCmsConfig = ${JSON.stringify(resolvedCmsConfig)};`
143
+ : ''
144
+
145
+ injectScript('page', `
146
+ ${configScript}
147
+ if (!document.querySelector('script[data-nuasite-cms]')) {
148
+ const s = document.createElement('script');
149
+ s.type = 'module';
150
+ s.src = ${JSON.stringify(editorSrc)};
151
+ s.dataset.nuasiteCms = '';
152
+ document.head.appendChild(s);
153
+ }
154
+ `)
155
+
156
+ // Resolve the virtual CMS editor path to the actual source file.
157
+ // Vite's dev server transforms the TSX, resolves imports, and serves it.
158
+ if (!src) {
159
+ const __dirname = dirname(fileURLToPath(import.meta.url))
160
+
161
+ vitePlugins.push({
162
+ name: 'nuasite-cms-editor',
163
+ resolveId(id: string) {
164
+ if (id === VIRTUAL_CMS_PATH) {
165
+ return join(__dirname, 'editor/index.tsx')
166
+ }
167
+ },
168
+ })
169
+
170
+ // Prepend @jsxImportSource pragma for editor .tsx files
171
+ // so Vite's esbuild uses Preact's h function
172
+ vitePlugins.push({
173
+ name: 'nuasite-cms-preact-jsx',
174
+ transform(code: string, id: string) {
175
+ if (id.includes('/src/editor/') && id.endsWith('.tsx') && !code.includes('@jsxImportSource')) {
176
+ return `/** @jsxImportSource preact */\n${code}`
177
+ }
178
+ },
179
+ })
180
+
181
+ // Add Tailwind CSS Vite plugin for editor styles
182
+ vitePlugins.push(tailwindcss())
183
+ }
184
+ }
185
+
186
+ // Proxy API requests to the backend
187
+ const proxyConfig: Record<string, any> = {}
188
+ if (proxy) {
189
+ proxyConfig['/_nua'] = {
190
+ target: proxy,
191
+ changeOrigin: true,
192
+ }
193
+ }
194
+
195
+ updateConfig({
196
+ vite: {
197
+ plugins: vitePlugins,
198
+ resolve: !src ? {
199
+ alias: {
200
+ 'react': 'preact/compat',
201
+ 'react-dom': 'preact/compat',
202
+ 'react/jsx-runtime': 'preact/jsx-runtime',
203
+ },
204
+ } : undefined,
205
+ server: {
206
+ proxy: proxyConfig,
207
+ },
208
+ },
209
+ })
210
+ },
211
+
212
+ 'astro:server:setup': ({ server, logger }) => {
213
+ createDevMiddleware(
214
+ server,
215
+ markerConfig,
216
+ manifestWriter,
217
+ componentDefinitions,
218
+ idCounter,
219
+ { enableCmsApi, mediaAdapter },
220
+ )
221
+ logger.info('CMS dev middleware initialized')
222
+ if (enableCmsApi) {
223
+ logger.info('CMS API enabled at /_nua/cms/')
224
+ }
225
+ },
226
+
227
+ 'astro:build:done': async ({ dir, logger }) => {
228
+ if (generateManifest) {
229
+ await processBuildOutput(dir, markerConfig, manifestWriter, idCounter, logger)
230
+ }
231
+
232
+ const errorCollector = getErrorCollector()
233
+ if (errorCollector.hasWarnings()) {
234
+ const warnings = errorCollector.getWarnings()
235
+ logger.warn(`${warnings.length} warning(s) during processing:`)
236
+ for (const { context, message } of warnings) {
237
+ logger.warn(` - ${context}: ${message}`)
238
+ }
239
+ }
240
+ },
241
+ },
242
+ }
243
+ }
244
+
245
+ export { createContemberStorageAdapter as contemberMedia } from './media/contember'
246
+ export { createLocalStorageAdapter as localMedia } from './media/local'
247
+ export { createS3StorageAdapter as s3Media } from './media/s3'
248
+ export type { MediaItem, MediaStorageAdapter } from './media/types'
249
+
250
+ export { scanCollections } from './collection-scanner'
251
+ export { getProjectRoot, resetProjectRoot, setProjectRoot } from './config'
252
+ export type { CollectionInfo, MarkdownContent, SourceLocation, VariableReference } from './source-finder'
253
+ export { findCollectionSource, parseMarkdownContent } from './source-finder'
254
+ export type {
255
+ Attribute,
256
+ AvailableColors,
257
+ AvailableTextStyles,
258
+ CanonicalUrl,
259
+ CmsManifest,
260
+ CmsMarkerOptions,
261
+ CollectionDefinition,
262
+ CollectionEntry,
263
+ ComponentDefinition,
264
+ ComponentInstance,
265
+ ComponentProp,
266
+ ContentConstraints,
267
+ FieldDefinition,
268
+ FieldType,
269
+ ImageMetadata,
270
+ JsonLdEntry,
271
+ ManifestEntry,
272
+ ManifestMetadata,
273
+ OpenGraphData,
274
+ PageEntry,
275
+ PageSeoData,
276
+ SeoKeywords,
277
+ SeoMetaTag,
278
+ SeoOptions,
279
+ SeoSourceInfo,
280
+ SeoTitle,
281
+ TailwindColor,
282
+ TextStyleValue,
283
+ TwitterCardData,
284
+ } from './types'
@@ -0,0 +1,371 @@
1
+ import fs from 'node:fs/promises'
2
+ import path from 'node:path'
3
+ import { getProjectRoot } from './config'
4
+ import { parseTailwindConfig, parseTextStyles } from './tailwind-colors'
5
+ import type {
6
+ AvailableColors,
7
+ AvailableTextStyles,
8
+ CmsManifest,
9
+ CollectionDefinition,
10
+ CollectionEntry,
11
+ ComponentDefinition,
12
+ ComponentInstance,
13
+ ManifestEntry,
14
+ ManifestMetadata,
15
+ PageEntry,
16
+ PageSeoData,
17
+ } from './types'
18
+ import { generateManifestContentHash, generateSourceFileHashes } from './utils'
19
+
20
+ /** Current manifest schema version */
21
+ const MANIFEST_VERSION = '1.0'
22
+
23
+ /**
24
+ * Manages streaming manifest writes during build.
25
+ * Accumulates entries and writes per-page manifests as pages are processed.
26
+ */
27
+ export class ManifestWriter {
28
+ private globalManifest: CmsManifest
29
+ private pageManifests: Map<string, {
30
+ entries: Record<string, ManifestEntry>
31
+ components: Record<string, ComponentInstance>
32
+ collection?: CollectionEntry
33
+ seo?: PageSeoData
34
+ }> = new Map()
35
+ private outDir: string = ''
36
+ private manifestFile: string
37
+ private componentDefinitions: Record<string, ComponentDefinition>
38
+ private collectionDefinitions: Record<string, CollectionDefinition> = {}
39
+ private availableColors: AvailableColors | undefined
40
+ private availableTextStyles: AvailableTextStyles | undefined
41
+ private writeQueue: Promise<void> = Promise.resolve()
42
+
43
+ constructor(manifestFile: string, componentDefinitions: Record<string, ComponentDefinition> = {}) {
44
+ this.manifestFile = manifestFile
45
+ this.componentDefinitions = componentDefinitions
46
+ this.globalManifest = {
47
+ entries: {},
48
+ components: {},
49
+ componentDefinitions,
50
+ collections: {},
51
+ collectionDefinitions: {},
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Set the output directory for manifest files
57
+ */
58
+ setOutDir(dir: string): void {
59
+ this.outDir = dir
60
+ }
61
+
62
+ /**
63
+ * Update component definitions (called after initial scan)
64
+ */
65
+ setComponentDefinitions(definitions: Record<string, ComponentDefinition>): void {
66
+ this.componentDefinitions = definitions
67
+ this.globalManifest.componentDefinitions = definitions
68
+ }
69
+
70
+ /**
71
+ * Load available Tailwind colors and text styles from the project's CSS config
72
+ */
73
+ async loadAvailableColors(projectRoot: string = getProjectRoot()): Promise<void> {
74
+ this.availableColors = await parseTailwindConfig(projectRoot)
75
+ this.availableTextStyles = await parseTextStyles(projectRoot)
76
+ this.globalManifest.availableColors = this.availableColors
77
+ this.globalManifest.availableTextStyles = this.availableTextStyles
78
+ }
79
+
80
+ /**
81
+ * Set available colors directly (for testing or custom colors)
82
+ */
83
+ setAvailableColors(colors: AvailableColors): void {
84
+ this.availableColors = colors
85
+ this.globalManifest.availableColors = colors
86
+ }
87
+
88
+ /**
89
+ * Set available text styles directly (for testing or custom styles)
90
+ */
91
+ setAvailableTextStyles(textStyles: AvailableTextStyles): void {
92
+ this.availableTextStyles = textStyles
93
+ this.globalManifest.availableTextStyles = textStyles
94
+ }
95
+
96
+ /**
97
+ * Set collection definitions (inferred schemas for content collections)
98
+ */
99
+ setCollectionDefinitions(definitions: Record<string, CollectionDefinition>): void {
100
+ this.collectionDefinitions = definitions
101
+ this.globalManifest.collectionDefinitions = definitions
102
+ }
103
+
104
+ /**
105
+ * Get collection definitions
106
+ */
107
+ getCollectionDefinitions(): Record<string, CollectionDefinition> {
108
+ return this.collectionDefinitions
109
+ }
110
+
111
+ /**
112
+ * Get the manifest path for a given page
113
+ * Places manifest next to the page: /about -> /about.json, / -> /index.json
114
+ */
115
+ private getPageManifestPath(pagePath: string): string {
116
+ if (pagePath === '/' || pagePath === '') {
117
+ return path.join(this.outDir, 'index.json')
118
+ }
119
+ const cleanPath = pagePath.replace(/^\//, '')
120
+ return path.join(this.outDir, `${cleanPath}.json`)
121
+ }
122
+
123
+ /**
124
+ * Add a page's entries to the manifest (called after each page is processed)
125
+ * This is non-blocking - writes are queued
126
+ */
127
+ addPage(
128
+ pagePath: string,
129
+ entries: Record<string, ManifestEntry>,
130
+ components: Record<string, ComponentInstance>,
131
+ collection?: CollectionEntry,
132
+ seo?: PageSeoData,
133
+ ): void {
134
+ // Store in memory
135
+ this.pageManifests.set(pagePath, { entries, components, collection, seo })
136
+
137
+ // Update global manifest
138
+ Object.assign(this.globalManifest.entries, entries)
139
+ Object.assign(this.globalManifest.components, components)
140
+
141
+ // Add collection entry to global manifest
142
+ if (collection) {
143
+ const collectionKey = `${collection.collectionName}/${collection.collectionSlug}`
144
+ this.globalManifest.collections = this.globalManifest.collections || {}
145
+ this.globalManifest.collections[collectionKey] = collection
146
+
147
+ // Update pathname on collection definition entry (needed for dev mode
148
+ // where finalize() doesn't run to populate pathnames)
149
+ const def = this.collectionDefinitions[collection.collectionName]
150
+ if (def?.entries) {
151
+ const entry = def.entries.find(e => e.slug === collection.collectionSlug)
152
+ if (entry) {
153
+ entry.pathname = pagePath
154
+ }
155
+ }
156
+ }
157
+
158
+ // Queue the write operation (non-blocking, isolated errors)
159
+ if (this.outDir) {
160
+ this.writeQueue = this.writeQueue.then(() =>
161
+ this.writePageManifest(pagePath, entries, components, collection, seo)
162
+ .catch((err) => {
163
+ console.error(`[astro-cms] Failed to write manifest for ${pagePath}:`, err)
164
+ }),
165
+ )
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Write a single page manifest to disk
171
+ */
172
+ private async writePageManifest(
173
+ pagePath: string,
174
+ entries: Record<string, ManifestEntry>,
175
+ components: Record<string, ComponentInstance>,
176
+ collection?: CollectionEntry,
177
+ seo?: PageSeoData,
178
+ ): Promise<void> {
179
+ const manifestPath = this.getPageManifestPath(pagePath)
180
+ const manifestDir = path.dirname(manifestPath)
181
+
182
+ await fs.mkdir(manifestDir, { recursive: true })
183
+
184
+ // Generate metadata for this page manifest
185
+ const metadata: ManifestMetadata = {
186
+ version: MANIFEST_VERSION,
187
+ generatedAt: new Date().toISOString(),
188
+ generatedBy: 'cms',
189
+ contentHash: generateManifestContentHash(entries),
190
+ sourceFileHashes: generateSourceFileHashes(entries),
191
+ }
192
+
193
+ const pageManifest: {
194
+ metadata: ManifestMetadata
195
+ page: string
196
+ entries: Record<string, ManifestEntry>
197
+ components: Record<string, ComponentInstance>
198
+ componentDefinitions: Record<string, ComponentDefinition>
199
+ collection?: CollectionEntry
200
+ seo?: PageSeoData
201
+ } = {
202
+ metadata,
203
+ page: pagePath,
204
+ entries,
205
+ components,
206
+ componentDefinitions: this.componentDefinitions,
207
+ }
208
+
209
+ if (collection) {
210
+ pageManifest.collection = collection
211
+ }
212
+
213
+ if (seo) {
214
+ pageManifest.seo = seo
215
+ }
216
+
217
+ await fs.writeFile(manifestPath, JSON.stringify(pageManifest, null, 2), 'utf-8')
218
+ }
219
+
220
+ /**
221
+ * Finalize manifest writes
222
+ * Call this in astro:build:done to ensure all writes complete
223
+ */
224
+ async finalize(): Promise<{ totalEntries: number; totalPages: number; totalComponents: number }> {
225
+ // Wait for all queued writes to complete
226
+ await this.writeQueue
227
+
228
+ // Build pages array with pathname and title, sorted by pathname
229
+ const pages: PageEntry[] = Array.from(this.pageManifests.entries())
230
+ .map(([pathname, data]) => {
231
+ const entry: PageEntry = { pathname }
232
+ if (data.seo?.title?.content) {
233
+ entry.title = data.seo.title.content
234
+ }
235
+ return entry
236
+ })
237
+ .sort((a, b) => a.pathname.localeCompare(b.pathname))
238
+
239
+ // Build a map from "collectionName/slug" → pagePath using page collections
240
+ const collectionPathMap = new Map<string, string>()
241
+ for (const [pagePath, data] of this.pageManifests) {
242
+ if (data.collection) {
243
+ const key = `${data.collection.collectionName}/${data.collection.collectionSlug}`
244
+ collectionPathMap.set(key, pagePath)
245
+ }
246
+ }
247
+
248
+ // Populate pathname on collection definition entries
249
+ for (const def of Object.values(this.collectionDefinitions)) {
250
+ if (def.entries) {
251
+ for (const entry of def.entries) {
252
+ const pathname = collectionPathMap.get(`${def.name}/${entry.slug}`)
253
+ if (pathname) {
254
+ entry.pathname = pathname
255
+ }
256
+ }
257
+ }
258
+ }
259
+
260
+ // Write global manifest with settings (component definitions, colors, text styles, collection definitions, and pages)
261
+ if (this.outDir) {
262
+ const globalManifestPath = path.join(this.outDir, this.manifestFile)
263
+ const globalSettings: {
264
+ componentDefinitions: Record<string, ComponentDefinition>
265
+ collectionDefinitions?: Record<string, CollectionDefinition>
266
+ availableColors?: AvailableColors
267
+ availableTextStyles?: AvailableTextStyles
268
+ pages: PageEntry[]
269
+ } = {
270
+ componentDefinitions: this.componentDefinitions,
271
+ pages,
272
+ }
273
+ if (Object.keys(this.collectionDefinitions).length > 0) {
274
+ globalSettings.collectionDefinitions = this.collectionDefinitions
275
+ }
276
+ if (this.availableColors) {
277
+ globalSettings.availableColors = this.availableColors
278
+ }
279
+ if (this.availableTextStyles) {
280
+ globalSettings.availableTextStyles = this.availableTextStyles
281
+ }
282
+ await fs.writeFile(
283
+ globalManifestPath,
284
+ JSON.stringify(globalSettings, null, 2),
285
+ 'utf-8',
286
+ )
287
+ }
288
+
289
+ return {
290
+ totalEntries: Object.keys(this.globalManifest.entries).length,
291
+ totalPages: this.pageManifests.size,
292
+ totalComponents: Object.keys(this.globalManifest.components).length,
293
+ }
294
+ }
295
+
296
+ /**
297
+ * Get the global manifest (for virtual module support)
298
+ */
299
+ getGlobalManifest(): CmsManifest {
300
+ return this.globalManifest
301
+ }
302
+
303
+ /**
304
+ * Get a page's manifest data (for dev mode)
305
+ */
306
+ getPageManifest(pagePath: string): {
307
+ entries: Record<string, ManifestEntry>
308
+ components: Record<string, ComponentInstance>
309
+ collection?: CollectionEntry
310
+ seo?: PageSeoData
311
+ } | undefined {
312
+ return this.pageManifests.get(pagePath)
313
+ }
314
+
315
+ /**
316
+ * Reset state (for dev mode reloads)
317
+ */
318
+ reset(): void {
319
+ this.pageManifests.clear()
320
+ this.globalManifest = {
321
+ entries: {},
322
+ components: {},
323
+ componentDefinitions: this.componentDefinitions,
324
+ collections: {},
325
+ collectionDefinitions: this.collectionDefinitions,
326
+ availableColors: this.availableColors,
327
+ availableTextStyles: this.availableTextStyles,
328
+ }
329
+ this.writeQueue = Promise.resolve()
330
+ }
331
+
332
+ /**
333
+ * Get page data for preview generation (pages and their components)
334
+ */
335
+ getPageDataForPreviews(): Map<string, {
336
+ entries: Record<string, ManifestEntry>
337
+ components: Record<string, ComponentInstance>
338
+ collection?: CollectionEntry
339
+ seo?: PageSeoData
340
+ }> {
341
+ return this.pageManifests
342
+ }
343
+
344
+ /**
345
+ * Get component definitions
346
+ */
347
+ getComponentDefinitions(): Record<string, ComponentDefinition> {
348
+ return this.componentDefinitions
349
+ }
350
+
351
+ /**
352
+ * Get the output directory
353
+ */
354
+ getOutDir(): string {
355
+ return this.outDir
356
+ }
357
+
358
+ /**
359
+ * Get available colors (for use in dev middleware)
360
+ */
361
+ getAvailableColors(): AvailableColors | undefined {
362
+ return this.availableColors
363
+ }
364
+
365
+ /**
366
+ * Get available text styles (for use in dev middleware)
367
+ */
368
+ getAvailableTextStyles(): AvailableTextStyles | undefined {
369
+ return this.availableTextStyles
370
+ }
371
+ }