@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
@@ -0,0 +1,25 @@
1
+ {
2
+ "extends": "../tsconfig.settings.json",
3
+ "compilerOptions": {
4
+ "outDir": "../dist/src",
5
+ "composite": true,
6
+ "baseUrl": ".",
7
+ "jsx": "react-jsx",
8
+ "jsxImportSource": "preact",
9
+ "lib": ["es2022", "DOM", "DOM.Iterable"],
10
+ "target": "es2022",
11
+ "esModuleInterop": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "moduleResolution": "bundler",
14
+ "resolveJsonModule": true,
15
+ "verbatimModuleSyntax": true,
16
+ "types": ["@types/bun"],
17
+ "paths": {
18
+ "~/*": ["*"],
19
+ "react": ["../../../node_modules/preact/compat/"],
20
+ "react/jsx-runtime": ["../../../node_modules/preact/jsx-runtime"],
21
+ "react-dom": ["../../../node_modules/preact/compat/"],
22
+ "react-dom/*": ["../../../node_modules/preact/compat/*"]
23
+ }
24
+ }
25
+ }
package/src/types.ts ADDED
@@ -0,0 +1,406 @@
1
+ /** SEO tracking options */
2
+ export interface SeoOptions {
3
+ /** Whether to track SEO elements (default: true) */
4
+ trackSeo?: boolean
5
+ /** Whether to mark the page title with a CMS ID (default: true) */
6
+ markTitle?: boolean
7
+ /** Whether to parse JSON-LD structured data (default: true) */
8
+ parseJsonLd?: boolean
9
+ }
10
+
11
+ export interface CmsMarkerOptions {
12
+ attributeName?: string
13
+ includeTags?: string[] | null
14
+ excludeTags?: string[]
15
+ includeEmptyText?: boolean
16
+ generateManifest?: boolean
17
+ manifestFile?: string
18
+ markComponents?: boolean
19
+ componentDirs?: string[]
20
+ /** Directory containing content collections (default: 'src/content') */
21
+ contentDir?: string
22
+ /** SEO tracking options */
23
+ seo?: SeoOptions
24
+ }
25
+
26
+ export interface ComponentProp {
27
+ name: string
28
+ type: string
29
+ required: boolean
30
+ defaultValue?: string
31
+ description?: string
32
+ }
33
+
34
+ export interface ComponentDefinition {
35
+ name: string
36
+ file: string
37
+ props: ComponentProp[]
38
+ description?: string
39
+ slots?: string[]
40
+ previewUrl?: string
41
+ /** Viewport width (in px) used to render the preview iframe (default: 1280) */
42
+ previewWidth?: number
43
+ }
44
+
45
+ /** Image metadata for better tracking and integrity */
46
+ export interface ImageMetadata {
47
+ /** Image source URL */
48
+ src: string
49
+ /** Alt text */
50
+ alt: string
51
+ /** SHA256 hash of image content (for integrity checking) */
52
+ hash?: string
53
+ /** Image dimensions */
54
+ dimensions?: { width: number; height: number }
55
+ /** Responsive image srcset */
56
+ srcSet?: string
57
+ /** Image sizes attribute */
58
+ sizes?: string
59
+ }
60
+
61
+ /** Content constraints for validation */
62
+ export interface ContentConstraints {
63
+ /** Maximum content length */
64
+ maxLength?: number
65
+ /** Minimum content length */
66
+ minLength?: number
67
+ /** Regex pattern for validation */
68
+ pattern?: string
69
+ /** Allowed HTML tags for rich text content */
70
+ allowedTags?: string[]
71
+ }
72
+
73
+ /** Represents a single Tailwind color with its shades and values */
74
+ export interface TailwindColor {
75
+ /** Color name (e.g., 'red', 'blue', 'primary') */
76
+ name: string
77
+ /** Map of shade to CSS color value (e.g., { '500': '#ef4444', '600': '#dc2626' }) */
78
+ values: Record<string, string>
79
+ /** Whether this is a custom/theme color vs default Tailwind */
80
+ isCustom?: boolean
81
+ }
82
+
83
+ /** Attribute with source information for git diff tracking */
84
+ export interface Attribute {
85
+ /** The resolved attribute value (from rendered HTML) */
86
+ value: string
87
+ /** The expression text if dynamic (e.g., "component.githubUrl") */
88
+ sourceExpression?: string
89
+ /** Path to the source file where the value is defined */
90
+ sourcePath?: string
91
+ /** Line number where the value is defined in source (1-indexed) */
92
+ sourceLine?: number
93
+ /** The exact source snippet that can be replaced for git diff */
94
+ sourceSnippet?: string
95
+ }
96
+
97
+ /** Available colors palette from Tailwind config */
98
+ export interface AvailableColors {
99
+ /** All available colors with their shades */
100
+ colors: TailwindColor[]
101
+ /** Default Tailwind color names */
102
+ defaultColors: string[]
103
+ /** Custom/theme color names */
104
+ customColors: string[]
105
+ }
106
+
107
+ /** Text style value with class name and CSS properties */
108
+ export interface TextStyleValue {
109
+ /** Tailwind class name (e.g., 'font-bold', 'text-xl') */
110
+ class: string
111
+ /** Display label for UI */
112
+ label: string
113
+ /** CSS properties to apply (e.g., { fontWeight: '700' }) */
114
+ css: Record<string, string>
115
+ }
116
+
117
+ /** Available text styles from Tailwind config */
118
+ export interface AvailableTextStyles {
119
+ /** Font weight options (font-normal, font-bold, etc.) */
120
+ fontWeight: TextStyleValue[]
121
+ /** Font size options (text-xs, text-sm, text-base, etc.) */
122
+ fontSize: TextStyleValue[]
123
+ /** Text decoration options (underline, line-through, etc.) */
124
+ textDecoration: TextStyleValue[]
125
+ /** Font style options (italic, not-italic) */
126
+ fontStyle: TextStyleValue[]
127
+ }
128
+
129
+ export interface ManifestEntry {
130
+ id: string
131
+ tag: string
132
+ /** Plain text content (for display/search) */
133
+ text: string
134
+ /** HTML content when element contains inline styling (strong, em, etc.) */
135
+ html?: string
136
+ sourcePath?: string
137
+ sourceLine?: number
138
+ /** Full element snippet from opening to closing tag (for text content updates) */
139
+ sourceSnippet?: string
140
+ variableName?: string
141
+ childCmsIds?: string[]
142
+ parentComponentId?: string
143
+ /** Collection name for collection entries (e.g., 'services', 'blog') */
144
+ collectionName?: string
145
+ /** Entry slug for collection entries (e.g., '3d-tisk') */
146
+ collectionSlug?: string
147
+ /** Path to the markdown content file (e.g., 'src/content/blog/my-post.md') */
148
+ contentPath?: string
149
+
150
+ // === Robustness fields ===
151
+
152
+ /** Stable ID derived from content + context hash, survives rebuilds */
153
+ stableId?: string
154
+ /** SHA256 hash of sourceSnippet at generation time for conflict detection */
155
+ sourceHash?: string
156
+ /** Image metadata for img elements (replaces imageSrc/imageAlt) */
157
+ imageMetadata?: ImageMetadata
158
+ /** Content validation constraints */
159
+ constraints?: ContentConstraints
160
+ /** Color classes applied to this element (for buttons, etc.) */
161
+ colorClasses?: Record<string, Attribute>
162
+ /** All HTML attributes with source information */
163
+ attributes?: Record<string, Attribute>
164
+ }
165
+
166
+ export interface ComponentInstance {
167
+ id: string
168
+ componentName: string
169
+ file: string
170
+ sourcePath: string
171
+ sourceLine: number
172
+ props: Record<string, any>
173
+ slots?: Record<string, string>
174
+ parentId?: string
175
+ /** File where this component is invoked (parent page/layout) */
176
+ invocationSourcePath?: string
177
+ /** 0-based index among same-name component invocations in the parent file */
178
+ invocationIndex?: number
179
+ }
180
+
181
+ /** Represents a content collection entry (markdown file) */
182
+ export interface CollectionEntry {
183
+ /** Collection name (e.g., 'services', 'blog') */
184
+ collectionName: string
185
+ /** Entry slug (e.g., '3d-tisk') */
186
+ collectionSlug: string
187
+ /** Path to the markdown file relative to project root */
188
+ sourcePath: string
189
+ /** Frontmatter fields with their values and line numbers */
190
+ frontmatter: Record<string, { value: string; line: number }>
191
+ /** Full markdown body content */
192
+ body: string
193
+ /** Line number where body starts (1-indexed) */
194
+ bodyStartLine: number
195
+ /** ID of the wrapper element containing the rendered markdown */
196
+ wrapperId?: string
197
+ }
198
+
199
+ /** Field types for collection schema inference */
200
+ export type FieldType =
201
+ | 'text'
202
+ | 'textarea'
203
+ | 'date'
204
+ | 'boolean'
205
+ | 'number'
206
+ | 'image'
207
+ | 'url'
208
+ | 'select'
209
+ | 'array'
210
+ | 'object'
211
+ | 'reference'
212
+
213
+ /** Definition of a single field in a collection's schema */
214
+ export interface FieldDefinition {
215
+ /** Field name as it appears in frontmatter */
216
+ name: string
217
+ /** Inferred or specified field type */
218
+ type: FieldType
219
+ /** Whether the field is required (present in all entries) */
220
+ required: boolean
221
+ /** Default value for the field */
222
+ defaultValue?: unknown
223
+ /** Options for 'select' type fields */
224
+ options?: string[]
225
+ /** Item type for 'array' fields */
226
+ itemType?: FieldType
227
+ /** Nested fields for 'object' type */
228
+ fields?: FieldDefinition[]
229
+ /** Sample values seen across entries */
230
+ examples?: unknown[]
231
+ }
232
+
233
+ /** Per-entry metadata for collection browsing */
234
+ export interface CollectionEntryInfo {
235
+ slug: string
236
+ title?: string
237
+ sourcePath: string
238
+ draft?: boolean
239
+ /** URL pathname of the rendered page for this entry */
240
+ pathname?: string
241
+ }
242
+
243
+ /** Definition of a content collection with inferred schema */
244
+ export interface CollectionDefinition {
245
+ /** Collection identifier (directory name) */
246
+ name: string
247
+ /** Human-readable label for the collection */
248
+ label: string
249
+ /** Path to the collection directory */
250
+ path: string
251
+ /** Number of entries in the collection */
252
+ entryCount: number
253
+ /** Inferred field definitions */
254
+ fields: FieldDefinition[]
255
+ /** Whether the collection has draft support */
256
+ supportsDraft?: boolean
257
+ /** File extension used by entries */
258
+ fileExtension: 'md' | 'mdx'
259
+ /** Per-entry metadata for browsing */
260
+ entries?: CollectionEntryInfo[]
261
+ }
262
+
263
+ /** Manifest metadata for versioning and conflict detection */
264
+ export interface ManifestMetadata {
265
+ /** Manifest schema version */
266
+ version: string
267
+ /** ISO timestamp when manifest was generated */
268
+ generatedAt: string
269
+ /** Build system that generated the manifest (e.g., 'astro', 'vite') */
270
+ generatedBy?: string
271
+ /** Build ID for correlation */
272
+ buildId?: string
273
+ /** SHA256 hash of all entry content for quick drift detection */
274
+ contentHash?: string
275
+ /** Per-source-file hashes for granular conflict detection */
276
+ sourceFileHashes?: Record<string, string>
277
+ }
278
+
279
+ /** Page entry for the global manifest */
280
+ export interface PageEntry {
281
+ /** Page URL pathname (e.g., '/', '/about') */
282
+ pathname: string
283
+ /** Page title from SEO data */
284
+ title?: string
285
+ }
286
+
287
+ export interface CmsManifest {
288
+ /** Manifest metadata for versioning and conflict detection */
289
+ metadata?: ManifestMetadata
290
+ entries: Record<string, ManifestEntry>
291
+ components: Record<string, ComponentInstance>
292
+ componentDefinitions: Record<string, ComponentDefinition>
293
+ /** Content collection entries indexed by "collectionName/slug" */
294
+ collections?: Record<string, CollectionEntry>
295
+ /** Collection definitions with inferred schemas */
296
+ collectionDefinitions?: Record<string, CollectionDefinition>
297
+ /** Available Tailwind colors from the project's config */
298
+ availableColors?: AvailableColors
299
+ /** Available text styles from the project's Tailwind config */
300
+ availableTextStyles?: AvailableTextStyles
301
+ /** All pages in the site with pathname and title */
302
+ pages?: PageEntry[]
303
+ }
304
+
305
+ // === SEO Types ===
306
+
307
+ /** Source tracking information for SEO elements */
308
+ export interface SeoSourceInfo {
309
+ /** CMS ID if element was marked for editing */
310
+ id?: string
311
+ /** Path to source file */
312
+ sourcePath: string
313
+ /** Line number in source file (1-indexed) */
314
+ sourceLine: number
315
+ /** Exact source code snippet for matching/replacement */
316
+ sourceSnippet: string
317
+ }
318
+
319
+ /** SEO meta tag with source tracking */
320
+ export interface SeoMetaTag extends SeoSourceInfo {
321
+ /** Meta tag name attribute (for name-based meta tags) */
322
+ name?: string
323
+ /** Meta tag property attribute (for Open Graph/Twitter tags) */
324
+ property?: string
325
+ /** Meta tag content value */
326
+ content: string
327
+ }
328
+
329
+ /** Open Graph metadata */
330
+ export interface OpenGraphData {
331
+ title?: SeoMetaTag
332
+ description?: SeoMetaTag
333
+ image?: SeoMetaTag
334
+ url?: SeoMetaTag
335
+ type?: SeoMetaTag
336
+ siteName?: SeoMetaTag
337
+ }
338
+
339
+ /** Twitter Card metadata */
340
+ export interface TwitterCardData {
341
+ card?: SeoMetaTag
342
+ title?: SeoMetaTag
343
+ description?: SeoMetaTag
344
+ image?: SeoMetaTag
345
+ site?: SeoMetaTag
346
+ }
347
+
348
+ /** JSON-LD structured data entry */
349
+ export interface JsonLdEntry extends SeoSourceInfo {
350
+ /** Schema.org @type value */
351
+ type: string
352
+ /** Parsed JSON-LD data */
353
+ data: Record<string, unknown>
354
+ }
355
+
356
+ /** Canonical URL link element */
357
+ export interface CanonicalUrl extends SeoSourceInfo {
358
+ /** The canonical URL href value */
359
+ href: string
360
+ }
361
+
362
+ /** Favicon link element */
363
+ export interface SeoFavicon extends SeoSourceInfo {
364
+ /** The favicon href value */
365
+ href: string
366
+ /** The type attribute (e.g. "image/png", "image/svg+xml") */
367
+ type?: string
368
+ /** The sizes attribute (e.g. "32x32", "16x16") */
369
+ sizes?: string
370
+ /** The rel value (e.g. "icon", "apple-touch-icon") */
371
+ rel: string
372
+ }
373
+
374
+ /** Page title element with optional CMS ID */
375
+ export interface SeoTitle extends SeoSourceInfo {
376
+ /** Title text content */
377
+ content: string
378
+ }
379
+
380
+ /** Meta keywords with parsed array */
381
+ export interface SeoKeywords extends SeoSourceInfo {
382
+ /** Raw keywords string */
383
+ content: string
384
+ /** Parsed array of individual keywords */
385
+ keywords: string[]
386
+ }
387
+
388
+ /** Complete SEO data for a page */
389
+ export interface PageSeoData {
390
+ /** Page title */
391
+ title?: SeoTitle
392
+ /** Meta description */
393
+ description?: SeoMetaTag
394
+ /** Meta keywords */
395
+ keywords?: SeoKeywords
396
+ /** Canonical URL */
397
+ canonical?: CanonicalUrl
398
+ /** Favicons */
399
+ favicons?: SeoFavicon[]
400
+ /** Open Graph metadata */
401
+ openGraph?: OpenGraphData
402
+ /** Twitter Card metadata */
403
+ twitterCard?: TwitterCardData
404
+ /** JSON-LD structured data blocks */
405
+ jsonLd?: JsonLdEntry[]
406
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,186 @@
1
+ import { createHash } from 'node:crypto'
2
+ import path from 'node:path'
3
+ import { getProjectRoot } from './config'
4
+ import type { ManifestEntry } from './types'
5
+
6
+ /**
7
+ * Normalize a page path by removing query strings, hashes, and trailing slashes.
8
+ * Handles both full URLs and plain pathnames.
9
+ * Examples: '/about/' -> '/about', 'http://localhost/about?x=1' -> '/about'
10
+ */
11
+ export function normalizePagePath(url: string): string {
12
+ let pathname: string
13
+ try {
14
+ const parsed = new URL(url)
15
+ pathname = parsed.pathname
16
+ } catch {
17
+ pathname = url.split('?')[0]?.split('#')[0] ?? ''
18
+ }
19
+ if (pathname.length > 1 && pathname.endsWith('/')) {
20
+ pathname = pathname.slice(0, -1)
21
+ }
22
+ return pathname || '/'
23
+ }
24
+
25
+ /**
26
+ * Generate a SHA256 hash of the given content
27
+ */
28
+ export function sha256(content: string): string {
29
+ return createHash('sha256').update(content, 'utf8').digest('hex')
30
+ }
31
+
32
+ /**
33
+ * Generate a short hash (first 12 characters of SHA256)
34
+ * Used for stableId to keep it reasonably short but still unique
35
+ */
36
+ export function shortHash(content: string): string {
37
+ return sha256(content).substring(0, 12)
38
+ }
39
+
40
+ /**
41
+ * Generate a stable ID for an element based on its content and context.
42
+ * This ID survives rebuilds as long as the content and structure remain similar.
43
+ *
44
+ * Components:
45
+ * - tag name
46
+ * - first 50 chars of text content
47
+ * - source path (if available)
48
+ */
49
+ export function generateStableId(
50
+ tag: string,
51
+ text: string,
52
+ sourcePath?: string,
53
+ ): string {
54
+ const components = [
55
+ tag,
56
+ text.substring(0, 50).trim(),
57
+ sourcePath || '',
58
+ ]
59
+
60
+ return shortHash(components.join('|'))
61
+ }
62
+
63
+ /**
64
+ * Generate a hash of the source snippet for conflict detection.
65
+ * If the source file changes, this hash will differ from what's stored in manifest.
66
+ */
67
+ export function generateSourceHash(sourceSnippet: string): string {
68
+ return sha256(sourceSnippet)
69
+ }
70
+
71
+ /**
72
+ * Generate a content hash for the entire manifest (all entries).
73
+ * Used for quick drift detection without comparing individual entries.
74
+ */
75
+ export function generateManifestContentHash(entries: Record<string, ManifestEntry>): string {
76
+ // Sort keys for deterministic hashing
77
+ const sortedKeys = Object.keys(entries).sort()
78
+ const content = sortedKeys.map(key => {
79
+ const entry = entries[key]!
80
+ // Hash only content-relevant fields, not generated IDs
81
+ return `${entry.tag}|${entry.text}|${entry.html || ''}|${entry.sourcePath || ''}`
82
+ }).join('\n')
83
+
84
+ return sha256(content)
85
+ }
86
+
87
+ /**
88
+ * Generate per-source-file hashes for granular conflict detection.
89
+ * Maps source file path -> hash of all entries from that file.
90
+ */
91
+ export function generateSourceFileHashes(entries: Record<string, ManifestEntry>): Record<string, string> {
92
+ // Group entries by source file
93
+ const entriesByFile: Record<string, ManifestEntry[]> = {}
94
+
95
+ for (const entry of Object.values(entries)) {
96
+ const sourcePath = entry.sourcePath
97
+ if (sourcePath) {
98
+ if (!entriesByFile[sourcePath]) {
99
+ entriesByFile[sourcePath] = []
100
+ }
101
+ entriesByFile[sourcePath].push(entry)
102
+ }
103
+ }
104
+
105
+ // Generate hash for each file
106
+ const hashes: Record<string, string> = {}
107
+ for (const [filePath, fileEntries] of Object.entries(entriesByFile)) {
108
+ // Sort entries by line number for determinism
109
+ const sorted = fileEntries.sort((a, b) => (a.sourceLine || 0) - (b.sourceLine || 0))
110
+ const content = sorted.map(e => `${e.sourceLine || 0}|${e.text}|${e.sourceSnippet || ''}`).join('\n')
111
+ hashes[filePath] = sha256(content)
112
+ }
113
+
114
+ return hashes
115
+ }
116
+
117
+ // ============================================================================
118
+ // Regex Utility
119
+ // ============================================================================
120
+
121
+ /**
122
+ * Escape special regex characters in a string for use in `new RegExp()`.
123
+ */
124
+ export function escapeRegex(str: string): string {
125
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
126
+ }
127
+
128
+ /**
129
+ * Escape a string for safe use as a replacement in `String.prototype.replace()`.
130
+ * Prevents `$1`, `$$`, `$&` etc. from being interpreted as backreferences.
131
+ */
132
+ export function escapeReplacement(str: string): string {
133
+ return str.replace(/\$/g, '$$$$')
134
+ }
135
+
136
+ // ============================================================================
137
+ // Path Validation
138
+ // ============================================================================
139
+
140
+ /**
141
+ * Resolve a user-provided file path and ensure it stays within the project root.
142
+ * Throws if the resolved path escapes the project boundary.
143
+ */
144
+ export function resolveAndValidatePath(filePath: string): string {
145
+ const projectRoot = getProjectRoot()
146
+ const fullPath = path.isAbsolute(filePath)
147
+ ? path.resolve(filePath)
148
+ : path.resolve(projectRoot, filePath)
149
+
150
+ const resolvedRoot = path.resolve(projectRoot)
151
+ if (!fullPath.startsWith(resolvedRoot + path.sep) && fullPath !== resolvedRoot) {
152
+ throw new Error(`Path traversal detected: ${filePath}`)
153
+ }
154
+
155
+ return fullPath
156
+ }
157
+
158
+ // ============================================================================
159
+ // File Lock
160
+ // ============================================================================
161
+
162
+ const fileLocks = new Map<string, Promise<void>>()
163
+
164
+ /**
165
+ * Acquire a per-file lock for safe read-modify-write operations.
166
+ * Returns a release function to call when done.
167
+ */
168
+ export async function acquireFileLock(filePath: string): Promise<() => void> {
169
+ const key = path.resolve(filePath)
170
+
171
+ // Wait for any existing lock on this file
172
+ while (fileLocks.has(key)) {
173
+ await fileLocks.get(key)
174
+ }
175
+
176
+ let release!: () => void
177
+ const lockPromise = new Promise<void>((resolve) => {
178
+ release = resolve
179
+ })
180
+ fileLocks.set(key, lockPromise)
181
+
182
+ return () => {
183
+ fileLocks.delete(key)
184
+ release()
185
+ }
186
+ }
@@ -0,0 +1,42 @@
1
+ import type { Plugin } from 'vite'
2
+ import type { ManifestWriter } from './manifest-writer'
3
+ import type { CmsMarkerOptions, ComponentDefinition } from './types'
4
+
5
+ export interface VitePluginContext {
6
+ manifestWriter: ManifestWriter
7
+ componentDefinitions: Record<string, ComponentDefinition>
8
+ config: Required<CmsMarkerOptions>
9
+ idCounter: { value: number }
10
+ command: 'dev' | 'build' | 'preview' | 'sync'
11
+ }
12
+
13
+ export function createVitePlugin(context: VitePluginContext): Plugin[] {
14
+ const { manifestWriter, componentDefinitions } = context
15
+
16
+ const virtualManifestPlugin: Plugin = {
17
+ name: 'cms-marker-virtual-manifest',
18
+ resolveId(id) {
19
+ if (id === '/@cms/manifest' || id === 'virtual:cms-manifest') {
20
+ return '\0virtual:cms-manifest'
21
+ }
22
+ if (id === '/@cms/components' || id === 'virtual:cms-components') {
23
+ return '\0virtual:cms-components'
24
+ }
25
+ },
26
+ load(id) {
27
+ if (id === '\0virtual:cms-manifest') {
28
+ return `export default ${JSON.stringify(manifestWriter.getGlobalManifest())};`
29
+ }
30
+ if (id === '\0virtual:cms-components') {
31
+ return `export default ${JSON.stringify(componentDefinitions)};`
32
+ }
33
+ },
34
+ }
35
+
36
+ // Note: We cannot use transformIndexHtml for static Astro builds because
37
+ // Astro generates HTML files directly without going through Vite's HTML pipeline.
38
+ // HTML processing is done in build-processor.ts after pages are generated.
39
+ // Source location attributes are provided natively by Astro's compiler
40
+ // (data-astro-source-file, data-astro-source-loc) in dev mode.
41
+ return [virtualManifestPlugin]
42
+ }