@nuasite/cms-marker 0.0.65 → 0.0.67

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 (36) hide show
  1. package/dist/types/build-processor.d.ts +2 -1
  2. package/dist/types/build-processor.d.ts.map +1 -1
  3. package/dist/types/component-registry.d.ts.map +1 -1
  4. package/dist/types/config.d.ts +19 -0
  5. package/dist/types/config.d.ts.map +1 -0
  6. package/dist/types/dev-middleware.d.ts +10 -2
  7. package/dist/types/dev-middleware.d.ts.map +1 -1
  8. package/dist/types/error-collector.d.ts +56 -0
  9. package/dist/types/error-collector.d.ts.map +1 -0
  10. package/dist/types/html-processor.d.ts.map +1 -1
  11. package/dist/types/index.d.ts +2 -1
  12. package/dist/types/index.d.ts.map +1 -1
  13. package/dist/types/manifest-writer.d.ts.map +1 -1
  14. package/dist/types/source-finder.d.ts +18 -3
  15. package/dist/types/source-finder.d.ts.map +1 -1
  16. package/dist/types/tailwind-colors.d.ts.map +1 -1
  17. package/dist/types/tsconfig.tsbuildinfo +1 -1
  18. package/dist/types/types.d.ts +2 -4
  19. package/dist/types/types.d.ts.map +1 -1
  20. package/dist/types/vite-plugin.d.ts.map +1 -1
  21. package/package.json +2 -1
  22. package/src/build-processor.ts +73 -19
  23. package/src/component-registry.ts +2 -0
  24. package/src/config.ts +29 -0
  25. package/src/dev-middleware.ts +12 -4
  26. package/src/error-collector.ts +106 -0
  27. package/src/html-processor.ts +55 -37
  28. package/src/index.ts +20 -4
  29. package/src/manifest-writer.ts +12 -2
  30. package/src/source-finder.ts +1003 -295
  31. package/src/tailwind-colors.ts +249 -48
  32. package/src/types.ts +2 -4
  33. package/src/vite-plugin.ts +4 -12
  34. package/dist/types/astro-transform.d.ts +0 -21
  35. package/dist/types/astro-transform.d.ts.map +0 -1
  36. package/src/astro-transform.ts +0 -205
@@ -1,9 +1,12 @@
1
- import { parse } from 'node-html-parser'
1
+ import { type HTMLElement as ParsedHTMLElement, parse } from 'node-html-parser'
2
2
  import { enhanceManifestWithSourceSnippets } from './source-finder'
3
3
  import { extractColorClasses } from './tailwind-colors'
4
- import type { ComponentInstance, ManifestEntry, SourceContext } from './types'
4
+ import type { ComponentInstance, ImageMetadata, ManifestEntry, SourceContext } from './types'
5
5
  import { generateStableId } from './utils'
6
6
 
7
+ /** Type for parsed HTML element nodes from node-html-parser */
8
+ type HTMLNode = ParsedHTMLElement
9
+
7
10
  /**
8
11
  * Inline text styling elements that should NOT be marked with CMS IDs.
9
12
  * These elements are text formatting and should be part of their parent's content.
@@ -187,7 +190,7 @@ export async function processHtml(
187
190
  const entries: Record<string, ManifestEntry> = {}
188
191
  const components: Record<string, ComponentInstance> = {}
189
192
  const sourceLocationMap = new Map<string, { file: string; line: number }>()
190
- const markedComponentRoots = new Set<any>()
193
+ const markedComponentRoots = new Set<HTMLNode>()
191
194
  let collectionWrapperId: string | undefined
192
195
 
193
196
  // First pass: detect and mark component root elements
@@ -222,15 +225,15 @@ export async function processHtml(
222
225
 
223
226
  // Check if any ancestor is already marked as a component root from the same file
224
227
  // (we only want to mark the outermost element from each component)
225
- let parent = node.parentNode
228
+ let parent = node.parentNode as HTMLNode | null
226
229
  let ancestorFromSameComponent = false
227
230
  while (parent) {
228
- const parentSource = (parent as any).getAttribute?.('data-astro-source-file')
231
+ const parentSource = parent.getAttribute?.('data-astro-source-file')
229
232
  if (parentSource === sourceFile) {
230
233
  ancestorFromSameComponent = true
231
234
  break
232
235
  }
233
- parent = parent.parentNode
236
+ parent = parent.parentNode as HTMLNode | null
234
237
  }
235
238
 
236
239
  if (ancestorFromSameComponent) return
@@ -279,7 +282,12 @@ export async function processHtml(
279
282
 
280
283
  // Image detection pass: mark img elements for CMS image replacement
281
284
  // Store image entries separately to add to manifest later
282
- const imageEntries = new Map<string, { src: string; alt: string; sourceFile?: string; sourceLine?: number }>()
285
+ interface ImageEntry {
286
+ metadata: ImageMetadata
287
+ sourceFile?: string
288
+ sourceLine?: number
289
+ }
290
+ const imageEntries = new Map<string, ImageEntry>()
283
291
  root.querySelectorAll('img').forEach((node) => {
284
292
  // Skip if already marked
285
293
  if (node.getAttribute(attributeName)) return
@@ -294,7 +302,7 @@ export async function processHtml(
294
302
  // Try to get source location from the image itself or ancestors
295
303
  let sourceFile: string | undefined
296
304
  let sourceLine: number | undefined
297
- let current: any = node
305
+ let current: HTMLNode | null = node
298
306
  while (current && !sourceFile) {
299
307
  const file = current.getAttribute?.('data-astro-source-file')
300
308
  const line = current.getAttribute?.('data-astro-source-loc') || current.getAttribute?.('data-astro-source-line')
@@ -307,13 +315,20 @@ export async function processHtml(
307
315
  }
308
316
  }
309
317
  }
310
- current = current.parentNode
318
+ current = current.parentNode as HTMLNode | null
311
319
  }
312
320
 
313
- // Store image info for manifest
314
- imageEntries.set(id, {
321
+ // Build image metadata
322
+ const metadata: ImageMetadata = {
315
323
  src,
316
324
  alt: node.getAttribute('alt') || '',
325
+ srcSet: node.getAttribute('srcset') || undefined,
326
+ sizes: node.getAttribute('sizes') || undefined,
327
+ }
328
+
329
+ // Store image info for manifest
330
+ imageEntries.set(id, {
331
+ metadata,
317
332
  sourceFile,
318
333
  sourceLine,
319
334
  })
@@ -335,23 +350,23 @@ export async function processHtml(
335
350
  // Check if this element has any direct child elements without source file attribute
336
351
  // These would be markdown-rendered elements
337
352
  const childElements = node.childNodes.filter(
338
- (child: any) => child.nodeType === 1 && child.tagName,
353
+ (child): child is HTMLNode => child.nodeType === 1 && 'tagName' in child,
339
354
  )
340
355
  const hasMarkdownChildren = childElements.some(
341
- (child: any) => !child.getAttribute?.('data-astro-source-file'),
356
+ (child) => !child.getAttribute?.('data-astro-source-file'),
342
357
  )
343
358
 
344
359
  if (hasMarkdownChildren) {
345
360
  // Check if any ancestor already has been marked as a collection wrapper
346
361
  // We want the innermost wrapper
347
- let parent = node.parentNode
362
+ let parent = node.parentNode as HTMLNode | null
348
363
  let hasAncestorWrapper = false
349
364
  while (parent) {
350
- if ((parent as any).getAttribute?.(attributeName)?.startsWith('cms-collection-')) {
365
+ if (parent.getAttribute?.(attributeName)?.startsWith('cms-collection-')) {
351
366
  hasAncestorWrapper = true
352
367
  break
353
368
  }
354
- parent = parent.parentNode
369
+ parent = parent.parentNode as HTMLNode | null
355
370
  }
356
371
 
357
372
  if (!hasAncestorWrapper) {
@@ -382,7 +397,7 @@ export async function processHtml(
382
397
 
383
398
  if (bodyStart.length > 10) {
384
399
  // Store all candidates that match the body start
385
- const candidates: Array<{ node: any; blockChildCount: number }> = []
400
+ const candidates: Array<{ node: HTMLNode; blockChildCount: number }> = []
386
401
 
387
402
  for (const node of allElements) {
388
403
  const tag = node.tagName?.toLowerCase?.() ?? ''
@@ -391,8 +406,8 @@ export async function processHtml(
391
406
 
392
407
  // Check if this element's first text content starts with the markdown body
393
408
  const firstChild = node.childNodes.find(
394
- (child: any) => child.nodeType === 1 && child.tagName,
395
- ) as any
409
+ (child): child is HTMLNode => child.nodeType === 1 && 'tagName' in child,
410
+ )
396
411
 
397
412
  if (firstChild) {
398
413
  const firstChildText = (firstChild.innerText || '').trim().substring(0, 80)
@@ -401,7 +416,8 @@ export async function processHtml(
401
416
  // Markdown typically renders to multiple block elements (p, h2, h3, ul, ol, etc.)
402
417
  const blockTags = ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ul', 'ol', 'blockquote', 'pre', 'table', 'hr']
403
418
  const blockChildCount = node.childNodes.filter(
404
- (child: any) => child.nodeType === 1 && blockTags.includes(child.tagName?.toLowerCase?.()),
419
+ (child): child is HTMLNode =>
420
+ child.nodeType === 1 && 'tagName' in child && blockTags.includes((child as HTMLNode).tagName?.toLowerCase?.() ?? ''),
405
421
  ).length
406
422
 
407
423
  candidates.push({ node, blockChildCount })
@@ -495,11 +511,14 @@ export async function processHtml(
495
511
 
496
512
  // Get child CMS elements
497
513
  const childCmsElements = node.querySelectorAll(`[${attributeName}]`)
498
- const childCmsIds = Array.from(childCmsElements).map((child: any) => child.getAttribute(attributeName))
514
+ const childCmsIds = Array.from(childCmsElements)
515
+ .map((child) => child.getAttribute(attributeName))
516
+ .filter((id): id is string => id !== null)
499
517
 
500
518
  // Build text with placeholders for child CMS elements
501
519
  // Recursively process child nodes to handle nested CMS elements correctly
502
- const buildTextWithPlaceholders = (nodes: any[]): string => {
520
+ type ChildNode = { nodeType: number; text?: string; childNodes?: ChildNode[]; getAttribute?: (name: string) => string | null }
521
+ const buildTextWithPlaceholders = (nodes: ChildNode[]): string => {
503
522
  let text = ''
504
523
  for (const child of nodes) {
505
524
  if (child.nodeType === 3) {
@@ -507,21 +526,21 @@ export async function processHtml(
507
526
  text += child.text || ''
508
527
  } else if (child.nodeType === 1) {
509
528
  // Element node
510
- const directCmsId = (child as any).getAttribute?.(attributeName)
529
+ const directCmsId = child.getAttribute?.(attributeName)
511
530
 
512
531
  if (directCmsId) {
513
532
  // Child has a direct CMS ID - use placeholder
514
533
  text += `{{cms:${directCmsId}}}`
515
534
  } else {
516
535
  // Child doesn't have a CMS ID - recursively process its children
517
- text += buildTextWithPlaceholders(child.childNodes || [])
536
+ text += buildTextWithPlaceholders((child.childNodes || []) as ChildNode[])
518
537
  }
519
538
  }
520
539
  }
521
540
  return text
522
541
  }
523
542
 
524
- const textWithPlaceholders = buildTextWithPlaceholders(node.childNodes || [])
543
+ const textWithPlaceholders = buildTextWithPlaceholders((node.childNodes || []) as ChildNode[])
525
544
 
526
545
  // Get direct text content (without placeholders)
527
546
  const directText = textWithPlaceholders.replace(/\{\{cms:[^}]+\}\}/g, '').trim()
@@ -540,14 +559,14 @@ export async function processHtml(
540
559
 
541
560
  // Find parent component if any
542
561
  let parentComponentId: string | undefined
543
- let parent = node.parentNode
562
+ let parent = node.parentNode as HTMLNode | null
544
563
  while (parent) {
545
- const parentCompId = (parent as any).getAttribute?.('data-cms-component-id')
564
+ const parentCompId = parent.getAttribute?.('data-cms-component-id')
546
565
  if (parentCompId) {
547
566
  parentComponentId = parentCompId
548
567
  break
549
568
  }
550
- parent = parent.parentNode
569
+ parent = parent.parentNode as HTMLNode | null
551
570
  }
552
571
 
553
572
  // Extract source context for resilient matching
@@ -564,7 +583,7 @@ export async function processHtml(
564
583
  const imageInfo = imageEntries.get(id)
565
584
  const isImage = !!imageInfo
566
585
 
567
- const entryText = isImage ? (imageInfo.alt || imageInfo.src) : textWithPlaceholders.trim()
586
+ const entryText = isImage ? (imageInfo.metadata.alt || imageInfo.metadata.src) : textWithPlaceholders.trim()
568
587
  // For images, use the source file we captured from ancestors if not in sourceLocationMap
569
588
  const entrySourcePath = sourceLocation?.file || imageInfo?.sourceFile || sourcePath
570
589
 
@@ -591,12 +610,11 @@ export async function processHtml(
591
610
  collectionName: isCollectionWrapper ? collectionInfo?.name : undefined,
592
611
  collectionSlug: isCollectionWrapper ? collectionInfo?.slug : undefined,
593
612
  contentPath: isCollectionWrapper ? collectionInfo?.contentPath : undefined,
594
- // Add image info for image entries
595
- imageSrc: imageInfo?.src,
596
- imageAlt: imageInfo?.alt,
597
613
  // Robustness fields
598
614
  stableId,
599
615
  sourceContext,
616
+ // Image metadata for image entries
617
+ imageMetadata: imageInfo?.metadata,
600
618
  // Color classes for buttons/styled elements
601
619
  colorClasses,
602
620
  }
@@ -604,7 +622,7 @@ export async function processHtml(
604
622
  }
605
623
 
606
624
  // Clean up any remaining source attributes from component-marked elements
607
- markedComponentRoots.forEach((node: any) => {
625
+ markedComponentRoots.forEach((node) => {
608
626
  node.removeAttribute('data-astro-source-file')
609
627
  node.removeAttribute('data-astro-source-loc')
610
628
  node.removeAttribute('data-astro-source-line')
@@ -645,13 +663,13 @@ export function cleanText(text: string): string {
645
663
  * This captures information about the element's position in the DOM
646
664
  * that can be used as fallback when exact matching fails.
647
665
  */
648
- function extractSourceContext(node: any, attributeName: string): SourceContext | undefined {
649
- const parent = node.parentNode
666
+ function extractSourceContext(node: HTMLNode, attributeName: string): SourceContext | undefined {
667
+ const parent = node.parentNode as HTMLNode | null
650
668
  if (!parent) return undefined
651
669
 
652
- const siblings = parent.childNodes?.filter((child: any) => {
670
+ const siblings = parent.childNodes?.filter((child): child is HTMLNode => {
653
671
  // Only consider element nodes, not text nodes
654
- return child.nodeType === 1 && child.tagName
672
+ return child.nodeType === 1 && 'tagName' in child
655
673
  }) || []
656
674
 
657
675
  const siblingIndex = siblings.indexOf(node)
package/src/index.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  import type { AstroIntegration } from 'astro'
2
2
  import { processBuildOutput } from './build-processor'
3
3
  import { ComponentRegistry } from './component-registry'
4
+ import { resetProjectRoot } from './config'
4
5
  import { createDevMiddleware } from './dev-middleware'
6
+ import { getErrorCollector, resetErrorCollector } from './error-collector'
5
7
  import { ManifestWriter } from './manifest-writer'
6
8
  import type { CmsMarkerOptions, ComponentDefinition } from './types'
7
9
  import { createVitePlugin } from './vite-plugin'
@@ -46,6 +48,8 @@ export default function cmsMarker(options: CmsMarkerOptions = {}): AstroIntegrat
46
48
  // Reset state for new build/dev session
47
49
  idCounter.value = 0
48
50
  manifestWriter.reset()
51
+ resetErrorCollector()
52
+ resetProjectRoot()
49
53
 
50
54
  // Load available colors from Tailwind config
51
55
  await manifestWriter.loadAvailableColors()
@@ -72,15 +76,16 @@ export default function cmsMarker(options: CmsMarkerOptions = {}): AstroIntegrat
72
76
  command,
73
77
  }
74
78
 
79
+ // VitePluginLike is compatible with both Astro's bundled Vite and root Vite
75
80
  updateConfig({
76
81
  vite: {
77
- plugins: [createVitePlugin(pluginContext) as any],
82
+ plugins: createVitePlugin(pluginContext) as any,
78
83
  },
79
84
  })
80
85
  },
81
86
 
82
87
  'astro:server:setup': ({ server, logger }) => {
83
- createDevMiddleware(server as any, config, manifestWriter, componentDefinitions, idCounter)
88
+ createDevMiddleware(server, config, manifestWriter, componentDefinitions, idCounter)
84
89
  logger.info('Dev middleware initialized')
85
90
  },
86
91
 
@@ -88,13 +93,24 @@ export default function cmsMarker(options: CmsMarkerOptions = {}): AstroIntegrat
88
93
  if (generateManifest) {
89
94
  await processBuildOutput(dir, config, manifestWriter, idCounter, logger)
90
95
  }
96
+
97
+ // Report any warnings collected during processing
98
+ const errorCollector = getErrorCollector()
99
+ if (errorCollector.hasWarnings()) {
100
+ const warnings = errorCollector.getWarnings()
101
+ logger.warn(`${warnings.length} warning(s) during processing:`)
102
+ for (const { context, message } of warnings) {
103
+ logger.warn(` - ${context}: ${message}`)
104
+ }
105
+ }
91
106
  },
92
107
  },
93
108
  }
94
109
  }
95
110
 
96
- export { findCollectionSource, parseMarkdownContent } from './source-finder'
97
-
111
+ // Re-export config functions for testing
112
+ export { getProjectRoot, resetProjectRoot, setProjectRoot } from './config'
98
113
  // Re-export types for consumers
99
114
  export type { CollectionInfo, MarkdownContent, SourceLocation, VariableReference } from './source-finder'
115
+ export { findCollectionSource, parseMarkdownContent } from './source-finder'
100
116
  export type * from './types'
@@ -1,7 +1,17 @@
1
1
  import fs from 'node:fs/promises'
2
2
  import path from 'node:path'
3
+ import { getProjectRoot } from './config'
3
4
  import { parseTailwindConfig, parseTextStyles } from './tailwind-colors'
4
- import type { AvailableColors, AvailableTextStyles, CmsManifest, CollectionEntry, ComponentDefinition, ComponentInstance, ManifestEntry, ManifestMetadata } from './types'
5
+ import type {
6
+ AvailableColors,
7
+ AvailableTextStyles,
8
+ CmsManifest,
9
+ CollectionEntry,
10
+ ComponentDefinition,
11
+ ComponentInstance,
12
+ ManifestEntry,
13
+ ManifestMetadata,
14
+ } from './types'
5
15
  import { generateManifestContentHash, generateSourceFileHashes } from './utils'
6
16
 
7
17
  /** Current manifest schema version */
@@ -54,7 +64,7 @@ export class ManifestWriter {
54
64
  /**
55
65
  * Load available Tailwind colors and text styles from the project's CSS config
56
66
  */
57
- async loadAvailableColors(projectRoot: string = process.cwd()): Promise<void> {
67
+ async loadAvailableColors(projectRoot: string = getProjectRoot()): Promise<void> {
58
68
  this.availableColors = await parseTailwindConfig(projectRoot)
59
69
  this.availableTextStyles = await parseTextStyles(projectRoot)
60
70
  this.globalManifest.availableColors = this.availableColors