@nuasite/cms-marker 0.0.53 → 0.0.54

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +0 -4
  2. package/dist/types/astro-transform.d.ts +21 -0
  3. package/dist/types/astro-transform.d.ts.map +1 -0
  4. package/dist/types/build-processor.d.ts +10 -0
  5. package/dist/types/build-processor.d.ts.map +1 -0
  6. package/dist/types/component-registry.d.ts +63 -0
  7. package/dist/types/component-registry.d.ts.map +1 -0
  8. package/dist/types/dev-middleware.d.ts +7 -0
  9. package/dist/types/dev-middleware.d.ts.map +1 -0
  10. package/dist/types/html-processor.d.ts +51 -0
  11. package/dist/types/html-processor.d.ts.map +1 -0
  12. package/dist/types/index.d.ts +7 -0
  13. package/dist/types/index.d.ts.map +1 -0
  14. package/dist/types/manifest-writer.d.ts +75 -0
  15. package/dist/types/manifest-writer.d.ts.map +1 -0
  16. package/dist/types/source-finder.d.ts +97 -0
  17. package/dist/types/source-finder.d.ts.map +1 -0
  18. package/dist/types/tailwind-colors.d.ts +66 -0
  19. package/dist/types/tailwind-colors.d.ts.map +1 -0
  20. package/dist/types/tsconfig.tsbuildinfo +1 -0
  21. package/dist/types/types.d.ts +195 -0
  22. package/dist/types/types.d.ts.map +1 -0
  23. package/dist/types/utils.d.ts +38 -0
  24. package/dist/types/utils.d.ts.map +1 -0
  25. package/dist/types/vite-plugin.d.ts +14 -0
  26. package/dist/types/vite-plugin.d.ts.map +1 -0
  27. package/package.json +1 -1
  28. package/src/astro-transform.ts +4 -4
  29. package/src/build-processor.ts +1 -1
  30. package/src/component-registry.ts +2 -2
  31. package/src/dev-middleware.ts +5 -5
  32. package/src/html-processor.ts +180 -6
  33. package/src/index.ts +0 -1
  34. package/src/manifest-writer.ts +47 -1
  35. package/src/source-finder.ts +117 -6
  36. package/src/tailwind-colors.ts +338 -0
  37. package/src/tsconfig.json +1 -1
  38. package/src/types.ts +123 -1
  39. package/src/utils.ts +99 -0
@@ -1,5 +1,7 @@
1
1
  import fs from 'node:fs/promises'
2
2
  import path from 'node:path'
3
+ import type { ManifestEntry } from './types'
4
+ import { generateSourceHash } from './utils'
3
5
 
4
6
  export interface SourceLocation {
5
7
  file: string
@@ -351,9 +353,10 @@ async function searchForPropInParents(dir: string, textContent: string): Promise
351
353
  }
352
354
 
353
355
  /**
354
- * Extract complete tag snippet including content and indentation
356
+ * Extract complete tag snippet including content and indentation.
357
+ * Exported for use in html-processor to populate sourceSnippet.
355
358
  */
356
- function extractCompleteTagSnippet(lines: string[], startLine: number, tag: string): string {
359
+ export function extractCompleteTagSnippet(lines: string[], startLine: number, tag: string): string {
357
360
  const snippetLines: string[] = []
358
361
  let depth = 0
359
362
  let foundClosing = false
@@ -390,6 +393,68 @@ function extractCompleteTagSnippet(lines: string[], startLine: number, tag: stri
390
393
  return snippetLines.join('\n')
391
394
  }
392
395
 
396
+ /**
397
+ * Extract innerHTML from a complete tag snippet.
398
+ * Given `<p class="foo">content here</p>`, returns `content here`.
399
+ *
400
+ * @param snippet - The complete tag snippet from source
401
+ * @param tag - The tag name (e.g., 'p', 'h1')
402
+ * @returns The innerHTML portion, or undefined if can't extract
403
+ */
404
+ export function extractInnerHtmlFromSnippet(snippet: string, tag: string): string | undefined {
405
+ // Match opening tag (with any attributes) and extract content until closing tag
406
+ // Handle both single-line and multi-line cases
407
+ const openTagPattern = new RegExp(`<${tag}(?:\\s[^>]*)?>`, 'i')
408
+ const closeTagPattern = new RegExp(`</${tag}>`, 'i')
409
+
410
+ const openMatch = snippet.match(openTagPattern)
411
+ if (!openMatch) return undefined
412
+
413
+ const openTagEnd = openMatch.index! + openMatch[0].length
414
+ const closeMatch = snippet.match(closeTagPattern)
415
+ if (!closeMatch) return undefined
416
+
417
+ const closeTagStart = closeMatch.index!
418
+
419
+ // Extract content between opening and closing tags
420
+ if (closeTagStart > openTagEnd) {
421
+ return snippet.substring(openTagEnd, closeTagStart)
422
+ }
423
+
424
+ return undefined
425
+ }
426
+
427
+ /**
428
+ * Read source file and extract the innerHTML at the specified line.
429
+ *
430
+ * @param sourceFile - Path to source file (relative to cwd)
431
+ * @param sourceLine - 1-indexed line number
432
+ * @param tag - The tag name
433
+ * @returns The innerHTML from source, or undefined if can't extract
434
+ */
435
+ export async function extractSourceInnerHtml(
436
+ sourceFile: string,
437
+ sourceLine: number,
438
+ tag: string,
439
+ ): Promise<string | undefined> {
440
+ try {
441
+ const filePath = path.isAbsolute(sourceFile)
442
+ ? sourceFile
443
+ : path.join(process.cwd(), sourceFile)
444
+
445
+ const content = await fs.readFile(filePath, 'utf-8')
446
+ const lines = content.split('\n')
447
+
448
+ // Extract the complete tag snippet
449
+ const snippet = extractCompleteTagSnippet(lines, sourceLine - 1, tag)
450
+
451
+ // Extract innerHTML from the snippet
452
+ return extractInnerHtmlFromSnippet(snippet, tag)
453
+ } catch {
454
+ return undefined
455
+ }
456
+ }
457
+
393
458
  /**
394
459
  * Extract variable references from frontmatter
395
460
  */
@@ -669,8 +734,10 @@ export async function findMarkdownSourceLocation(
669
734
  let value = match[2]?.trim() || ''
670
735
 
671
736
  // Handle quoted strings
672
- if ((value.startsWith('"') && value.endsWith('"')) ||
673
- (value.startsWith("'") && value.endsWith("'"))) {
737
+ if (
738
+ (value.startsWith('"') && value.endsWith('"'))
739
+ || (value.startsWith("'") && value.endsWith("'"))
740
+ ) {
674
741
  value = value.slice(1, -1)
675
742
  }
676
743
 
@@ -742,8 +809,10 @@ export async function parseMarkdownContent(
742
809
  let value = match[2]?.trim() || ''
743
810
 
744
811
  // Handle quoted strings
745
- if ((value.startsWith('"') && value.endsWith('"')) ||
746
- (value.startsWith("'") && value.endsWith("'"))) {
812
+ if (
813
+ (value.startsWith('"') && value.endsWith('"'))
814
+ || (value.startsWith("'") && value.endsWith("'"))
815
+ ) {
747
816
  value = value.slice(1, -1)
748
817
  }
749
818
 
@@ -791,3 +860,45 @@ function stripMarkdownSyntax(text: string): string {
791
860
  .trim()
792
861
  }
793
862
 
863
+ /**
864
+ * Enhance manifest entries with actual source snippets from source files.
865
+ * This reads the source files and extracts the innerHTML at the specified locations.
866
+ *
867
+ * @param entries - Manifest entries to enhance
868
+ * @returns Enhanced entries with sourceSnippet populated
869
+ */
870
+ export async function enhanceManifestWithSourceSnippets(
871
+ entries: Record<string, ManifestEntry>,
872
+ ): Promise<Record<string, ManifestEntry>> {
873
+ const enhanced: Record<string, ManifestEntry> = {}
874
+
875
+ // Process entries in parallel for better performance
876
+ const entryPromises = Object.entries(entries).map(async ([id, entry]) => {
877
+ // Skip if already has sourceSnippet or missing source info
878
+ if (entry.sourceSnippet || !entry.sourcePath || !entry.sourceLine || !entry.tag) {
879
+ return [id, entry] as const
880
+ }
881
+
882
+ // Extract the actual source innerHTML
883
+ const sourceSnippet = await extractSourceInnerHtml(
884
+ entry.sourcePath,
885
+ entry.sourceLine,
886
+ entry.tag,
887
+ )
888
+
889
+ if (sourceSnippet) {
890
+ // Generate hash of source snippet for conflict detection
891
+ const sourceHash = generateSourceHash(sourceSnippet)
892
+ return [id, { ...entry, sourceSnippet, sourceHash }] as const
893
+ }
894
+
895
+ return [id, entry] as const
896
+ })
897
+
898
+ const results = await Promise.all(entryPromises)
899
+ for (const [id, entry] of results) {
900
+ enhanced[id] = entry
901
+ }
902
+
903
+ return enhanced
904
+ }
@@ -0,0 +1,338 @@
1
+ import fs from 'node:fs/promises'
2
+ import path from 'node:path'
3
+ import type { AvailableColors, ColorClasses, TailwindColor } from './types'
4
+
5
+ /**
6
+ * Default Tailwind CSS v4 color names.
7
+ * These are available by default in Tailwind v4.
8
+ */
9
+ export const DEFAULT_TAILWIND_COLORS = [
10
+ 'slate',
11
+ 'gray',
12
+ 'zinc',
13
+ 'neutral',
14
+ 'stone',
15
+ 'red',
16
+ 'orange',
17
+ 'amber',
18
+ 'yellow',
19
+ 'lime',
20
+ 'green',
21
+ 'emerald',
22
+ 'teal',
23
+ 'cyan',
24
+ 'sky',
25
+ 'blue',
26
+ 'indigo',
27
+ 'violet',
28
+ 'purple',
29
+ 'fuchsia',
30
+ 'pink',
31
+ 'rose',
32
+ ] as const
33
+
34
+ /**
35
+ * Standard Tailwind color shades.
36
+ */
37
+ export const STANDARD_SHADES = ['50', '100', '200', '300', '400', '500', '600', '700', '800', '900', '950'] as const
38
+
39
+ /**
40
+ * Special color values that don't have shades.
41
+ */
42
+ export const SPECIAL_COLORS = ['transparent', 'current', 'inherit', 'white', 'black'] as const
43
+
44
+ /**
45
+ * Build a regex pattern for matching color classes.
46
+ * Matches either:
47
+ * - Known default/special colors (e.g., bg-red, text-white)
48
+ * - Any color name followed by a shade number (e.g., bg-primary-500)
49
+ */
50
+ function buildColorPattern(prefix: string): RegExp {
51
+ const colorNames = [...DEFAULT_TAILWIND_COLORS, ...SPECIAL_COLORS].join('|')
52
+ // Match either: known-color (with optional shade) OR any-name-with-shade (to support custom colors)
53
+ return new RegExp(`^${prefix}-((?:${colorNames})(?:-(\\d+))?|([a-z]+)-(\\d+))$`)
54
+ }
55
+
56
+ /**
57
+ * Regex patterns to match Tailwind color classes.
58
+ * These patterns are specific to color utilities and won't match other utilities
59
+ * like text-lg, text-center, bg-fixed, etc.
60
+ */
61
+ const COLOR_CLASS_PATTERNS = {
62
+ // Matches: bg-red-500, bg-primary-500, bg-white, bg-transparent
63
+ bg: buildColorPattern('bg'),
64
+ // Matches: text-red-500, text-primary-500, text-white (NOT text-lg, text-center)
65
+ text: buildColorPattern('text'),
66
+ // Matches: border-red-500, border-primary-500
67
+ border: buildColorPattern('border'),
68
+ // Matches: hover:bg-red-500
69
+ hoverBg: buildColorPattern('hover:bg'),
70
+ // Matches: hover:text-red-500
71
+ hoverText: buildColorPattern('hover:text'),
72
+ }
73
+
74
+ /**
75
+ * Parse Tailwind v4 CSS config to extract available colors.
76
+ * Tailwind v4 uses CSS-based configuration with @theme directive.
77
+ *
78
+ * Example CSS:
79
+ * ```css
80
+ * @theme {
81
+ * --color-primary-50: #eff6ff;
82
+ * --color-primary-500: #3b82f6;
83
+ * --color-accent: #f59e0b;
84
+ * }
85
+ * ```
86
+ */
87
+ export async function parseTailwindConfig(projectRoot: string = process.cwd()): Promise<AvailableColors> {
88
+ // Tailwind v4 CSS files to search
89
+ const cssFiles = [
90
+ 'src/styles/global.css',
91
+ 'src/styles/tailwind.css',
92
+ 'src/styles/app.css',
93
+ 'src/app.css',
94
+ 'src/global.css',
95
+ 'src/index.css',
96
+ 'app/globals.css',
97
+ 'styles/globals.css',
98
+ ]
99
+
100
+ let customColors: TailwindColor[] = []
101
+
102
+ for (const cssFile of cssFiles) {
103
+ const fullPath = path.join(projectRoot, cssFile)
104
+ try {
105
+ const content = await fs.readFile(fullPath, 'utf-8')
106
+ customColors = extractColorsFromCss(content)
107
+ if (customColors.length > 0) {
108
+ break
109
+ }
110
+ } catch {
111
+ // File doesn't exist, continue
112
+ }
113
+ }
114
+
115
+ // Build default colors list
116
+ const defaultColors: TailwindColor[] = DEFAULT_TAILWIND_COLORS.map(name => ({
117
+ name,
118
+ shades: [...STANDARD_SHADES],
119
+ isCustom: false,
120
+ }))
121
+
122
+ // Add special colors (no shades)
123
+ const specialColors: TailwindColor[] = SPECIAL_COLORS.map(name => ({
124
+ name,
125
+ shades: [],
126
+ isCustom: false,
127
+ }))
128
+
129
+ return {
130
+ colors: [...specialColors, ...defaultColors, ...customColors],
131
+ defaultColors: [...SPECIAL_COLORS, ...DEFAULT_TAILWIND_COLORS],
132
+ customColors: customColors.map(c => c.name),
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Extract custom colors from Tailwind v4 CSS @theme block.
138
+ *
139
+ * Looks for patterns like:
140
+ * - --color-primary-50: #value;
141
+ * - --color-primary: #value;
142
+ * - --color-accent-500: oklch(...);
143
+ */
144
+ function extractColorsFromCss(content: string): TailwindColor[] {
145
+ const colors = new Map<string, Set<string>>()
146
+
147
+ // Find @theme blocks
148
+ const themeBlockPattern = /@theme\s*\{([^}]+)\}/gs
149
+ let themeMatch: RegExpExecArray | null
150
+
151
+ while ((themeMatch = themeBlockPattern.exec(content)) !== null) {
152
+ const themeContent = themeMatch[1]
153
+ if (!themeContent) continue
154
+
155
+ // Find all --color-* definitions
156
+ // Pattern: --color-{name}-{shade}: value; or --color-{name}: value;
157
+ const colorVarPattern = /--color-([a-z]+)(?:-(\d+))?:/gi
158
+ let colorMatch: RegExpExecArray | null
159
+
160
+ while ((colorMatch = colorVarPattern.exec(themeContent)) !== null) {
161
+ const colorName = colorMatch[1]?.toLowerCase()
162
+ const shade = colorMatch[2]
163
+
164
+ if (!colorName) continue
165
+
166
+ // Skip if it's a default color
167
+ if (DEFAULT_TAILWIND_COLORS.includes(colorName as any)) {
168
+ continue
169
+ }
170
+
171
+ if (!colors.has(colorName)) {
172
+ colors.set(colorName, new Set())
173
+ }
174
+
175
+ if (shade) {
176
+ colors.get(colorName)!.add(shade)
177
+ }
178
+ }
179
+ }
180
+
181
+ // Also check for inline @theme definitions (Tailwind v4 can be inline too)
182
+ // Pattern: @theme inline { ... }
183
+ const inlineThemePattern = /@theme\s+inline\s*\{([^}]+)\}/gs
184
+ let inlineMatch: RegExpExecArray | null
185
+
186
+ while ((inlineMatch = inlineThemePattern.exec(content)) !== null) {
187
+ const themeContent = inlineMatch[1]
188
+ if (!themeContent) continue
189
+
190
+ const colorVarPattern = /--color-([a-z]+)(?:-(\d+))?:/gi
191
+ let colorMatch: RegExpExecArray | null
192
+
193
+ while ((colorMatch = colorVarPattern.exec(themeContent)) !== null) {
194
+ const colorName = colorMatch[1]?.toLowerCase()
195
+ const shade = colorMatch[2]
196
+
197
+ if (!colorName) continue
198
+
199
+ if (DEFAULT_TAILWIND_COLORS.includes(colorName as any)) {
200
+ continue
201
+ }
202
+
203
+ if (!colors.has(colorName)) {
204
+ colors.set(colorName, new Set())
205
+ }
206
+
207
+ if (shade) {
208
+ colors.get(colorName)!.add(shade)
209
+ }
210
+ }
211
+ }
212
+
213
+ // Convert to TailwindColor array
214
+ const result: TailwindColor[] = []
215
+ for (const [name, shades] of colors) {
216
+ const sortedShades = Array.from(shades).sort((a, b) => parseInt(a) - parseInt(b))
217
+ result.push({
218
+ name,
219
+ shades: sortedShades.length > 0 ? sortedShades : [],
220
+ isCustom: true,
221
+ })
222
+ }
223
+
224
+ return result
225
+ }
226
+
227
+ /**
228
+ * Extract color classes from an element's class attribute.
229
+ */
230
+ export function extractColorClasses(classAttr: string | null | undefined): ColorClasses | undefined {
231
+ if (!classAttr) return undefined
232
+
233
+ const classes = classAttr.split(/\s+/).filter(Boolean)
234
+ const colorClasses: ColorClasses = {}
235
+ const allColorClasses: string[] = []
236
+
237
+ for (const cls of classes) {
238
+ // Check each pattern
239
+ for (const [key, pattern] of Object.entries(COLOR_CLASS_PATTERNS)) {
240
+ const match = cls.match(pattern)
241
+ if (match) {
242
+ allColorClasses.push(cls)
243
+ // Assign to appropriate field
244
+ if (!(key in colorClasses)) {
245
+ ;(colorClasses as any)[key] = cls
246
+ }
247
+ break
248
+ }
249
+ }
250
+ }
251
+
252
+ if (allColorClasses.length === 0) {
253
+ return undefined
254
+ }
255
+
256
+ colorClasses.allColorClasses = allColorClasses
257
+ return colorClasses
258
+ }
259
+
260
+ /**
261
+ * Check if a class is a color class.
262
+ */
263
+ export function isColorClass(className: string): boolean {
264
+ return Object.values(COLOR_CLASS_PATTERNS).some(pattern => pattern.test(className))
265
+ }
266
+
267
+ /**
268
+ * Generate a new class string with a color class replaced.
269
+ * @param currentClasses - Current class attribute value
270
+ * @param oldColorClass - The color class to replace (e.g., 'bg-blue-500')
271
+ * @param newColorClass - The new color class (e.g., 'bg-red-500')
272
+ * @returns New class string with the replacement
273
+ */
274
+ export function replaceColorClass(
275
+ currentClasses: string,
276
+ oldColorClass: string,
277
+ newColorClass: string,
278
+ ): string {
279
+ const classes = currentClasses.split(/\s+/).filter(Boolean)
280
+ const newClasses = classes.map(cls => cls === oldColorClass ? newColorClass : cls)
281
+ return newClasses.join(' ')
282
+ }
283
+
284
+ /**
285
+ * Get the color type from a color class.
286
+ * @param colorClass - e.g., 'bg-blue-500', 'text-white', 'hover:bg-red-600'
287
+ * @returns The type: 'bg', 'text', 'border', 'hoverBg', 'hoverText', or undefined
288
+ */
289
+ export function getColorType(colorClass: string): keyof ColorClasses | undefined {
290
+ for (const [key, pattern] of Object.entries(COLOR_CLASS_PATTERNS)) {
291
+ if (pattern.test(colorClass)) {
292
+ return key as keyof ColorClasses
293
+ }
294
+ }
295
+ return undefined
296
+ }
297
+
298
+ /**
299
+ * Parse a color class into its components.
300
+ * @param colorClass - e.g., 'bg-blue-500', 'text-white', 'hover:bg-red-600'
301
+ * @returns Object with prefix, colorName, and shade (if any)
302
+ */
303
+ export function parseColorClass(colorClass: string): {
304
+ prefix: string
305
+ colorName: string
306
+ shade?: string
307
+ isHover: boolean
308
+ } | undefined {
309
+ // Handle hover prefix
310
+ const isHover = colorClass.startsWith('hover:')
311
+ const classWithoutHover = isHover ? colorClass.slice(6) : colorClass
312
+
313
+ // Match prefix-color-shade or prefix-color
314
+ const match = classWithoutHover.match(/^(bg|text|border)-([a-z]+)(?:-(\d+))?$/)
315
+
316
+ if (!match) return undefined
317
+
318
+ return {
319
+ prefix: isHover ? `hover:${match[1]}` : match[1]!,
320
+ colorName: match[2]!,
321
+ shade: match[3],
322
+ isHover,
323
+ }
324
+ }
325
+
326
+ /**
327
+ * Build a color class from components.
328
+ */
329
+ export function buildColorClass(
330
+ prefix: string,
331
+ colorName: string,
332
+ shade?: string,
333
+ ): string {
334
+ if (shade) {
335
+ return `${prefix}-${colorName}-${shade}`
336
+ }
337
+ return `${prefix}-${colorName}`
338
+ }
package/src/tsconfig.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "extends": "../tsconfig.settings.json",
3
3
  "compilerOptions": {
4
- "outDir": "../dist/types"
4
+ "outDir": "../dist/types",
5
5
  }
6
6
  }
package/src/types.ts CHANGED
@@ -27,14 +27,95 @@ export interface ComponentDefinition {
27
27
  slots?: string[]
28
28
  }
29
29
 
30
+ /** Context around an element for resilient matching when exact match fails */
31
+ export interface SourceContext {
32
+ /** Text content of the preceding sibling element */
33
+ precedingText?: string
34
+ /** Text content of the following sibling element */
35
+ followingText?: string
36
+ /** Parent element's tag name */
37
+ parentTag?: string
38
+ /** Position among siblings (0-indexed) */
39
+ siblingIndex?: number
40
+ /** Parent element's class attribute */
41
+ parentClasses?: string
42
+ }
43
+
44
+ /** Image metadata for better tracking and integrity */
45
+ export interface ImageMetadata {
46
+ /** Image source URL */
47
+ src: string
48
+ /** Alt text */
49
+ alt: string
50
+ /** SHA256 hash of image content (for integrity checking) */
51
+ hash?: string
52
+ /** Image dimensions */
53
+ dimensions?: { width: number; height: number }
54
+ /** Responsive image srcset */
55
+ srcSet?: string
56
+ /** Image sizes attribute */
57
+ sizes?: string
58
+ }
59
+
60
+ /** Content constraints for validation */
61
+ export interface ContentConstraints {
62
+ /** Maximum content length */
63
+ maxLength?: number
64
+ /** Minimum content length */
65
+ minLength?: number
66
+ /** Regex pattern for validation */
67
+ pattern?: string
68
+ /** Allowed HTML tags for rich text content */
69
+ allowedTags?: string[]
70
+ }
71
+
72
+ /** Represents a single Tailwind color with its shades */
73
+ export interface TailwindColor {
74
+ /** Color name (e.g., 'red', 'blue', 'primary') */
75
+ name: string
76
+ /** Available shades (e.g., ['50', '100', '200', ..., '900', '950']) */
77
+ shades: string[]
78
+ /** Whether this is a custom/theme color vs default Tailwind */
79
+ isCustom?: boolean
80
+ }
81
+
82
+ /** Color classes currently applied to an element */
83
+ export interface ColorClasses {
84
+ /** Background color class (e.g., 'bg-blue-500') */
85
+ bg?: string
86
+ /** Text color class (e.g., 'text-white') */
87
+ text?: string
88
+ /** Border color class (e.g., 'border-blue-600') */
89
+ border?: string
90
+ /** Hover background color class (e.g., 'hover:bg-blue-600') */
91
+ hoverBg?: string
92
+ /** Hover text color class (e.g., 'hover:text-gray-100') */
93
+ hoverText?: string
94
+ /** All color-related classes as found in the element */
95
+ allColorClasses?: string[]
96
+ }
97
+
98
+ /** Available colors palette from Tailwind config */
99
+ export interface AvailableColors {
100
+ /** All available colors with their shades */
101
+ colors: TailwindColor[]
102
+ /** Default Tailwind color names */
103
+ defaultColors: string[]
104
+ /** Custom/theme color names */
105
+ customColors: string[]
106
+ }
107
+
30
108
  export interface ManifestEntry {
31
109
  id: string
32
110
  tag: string
111
+ /** Plain text content (for display/search) */
33
112
  text: string
113
+ /** HTML content when element contains inline styling (strong, em, etc.) */
114
+ html?: string
34
115
  sourcePath?: string
35
116
  sourceLine?: number
36
117
  sourceSnippet?: string
37
- sourceType?: 'static' | 'variable' | 'prop' | 'computed' | 'collection'
118
+ sourceType?: 'static' | 'variable' | 'prop' | 'computed' | 'collection' | 'image'
38
119
  variableName?: string
39
120
  childCmsIds?: string[]
40
121
  parentComponentId?: string
@@ -42,6 +123,27 @@ export interface ManifestEntry {
42
123
  collectionName?: string
43
124
  /** Entry slug for collection entries (e.g., '3d-tisk') */
44
125
  collectionSlug?: string
126
+ /** Path to the markdown content file (e.g., 'src/content/blog/my-post.md') */
127
+ contentPath?: string
128
+ /** Image source URL (for image entries) - deprecated, use imageMetadata */
129
+ imageSrc?: string
130
+ /** Image alt text (for image entries) - deprecated, use imageMetadata */
131
+ imageAlt?: string
132
+
133
+ // === Robustness fields ===
134
+
135
+ /** Stable ID derived from content + context hash, survives rebuilds */
136
+ stableId?: string
137
+ /** SHA256 hash of sourceSnippet at generation time for conflict detection */
138
+ sourceHash?: string
139
+ /** Context around the element for resilient matching */
140
+ sourceContext?: SourceContext
141
+ /** Image metadata for img elements (replaces imageSrc/imageAlt) */
142
+ imageMetadata?: ImageMetadata
143
+ /** Content validation constraints */
144
+ constraints?: ContentConstraints
145
+ /** Color classes applied to this element (for buttons, etc.) */
146
+ colorClasses?: ColorClasses
45
147
  }
46
148
 
47
149
  export interface ComponentInstance {
@@ -73,10 +175,30 @@ export interface CollectionEntry {
73
175
  wrapperId?: string
74
176
  }
75
177
 
178
+ /** Manifest metadata for versioning and conflict detection */
179
+ export interface ManifestMetadata {
180
+ /** Manifest schema version */
181
+ version: string
182
+ /** ISO timestamp when manifest was generated */
183
+ generatedAt: string
184
+ /** Build system that generated the manifest (e.g., 'astro', 'vite') */
185
+ generatedBy?: string
186
+ /** Build ID for correlation */
187
+ buildId?: string
188
+ /** SHA256 hash of all entry content for quick drift detection */
189
+ contentHash?: string
190
+ /** Per-source-file hashes for granular conflict detection */
191
+ sourceFileHashes?: Record<string, string>
192
+ }
193
+
76
194
  export interface CmsManifest {
195
+ /** Manifest metadata for versioning and conflict detection */
196
+ metadata?: ManifestMetadata
77
197
  entries: Record<string, ManifestEntry>
78
198
  components: Record<string, ComponentInstance>
79
199
  componentDefinitions: Record<string, ComponentDefinition>
80
200
  /** Content collection entries indexed by "collectionName/slug" */
81
201
  collections?: Record<string, CollectionEntry>
202
+ /** Available Tailwind colors from the project's config */
203
+ availableColors?: AvailableColors
82
204
  }