@nuasite/cms 0.2.2 → 0.3.1

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 (55) hide show
  1. package/README.md +81 -73
  2. package/dist/src/build-processor.d.ts.map +1 -1
  3. package/dist/src/component-registry.d.ts +6 -2
  4. package/dist/src/component-registry.d.ts.map +1 -1
  5. package/dist/src/dev-middleware.d.ts.map +1 -1
  6. package/dist/src/editor/api.d.ts +14 -0
  7. package/dist/src/editor/api.d.ts.map +1 -1
  8. package/dist/src/editor/components/ai-chat.d.ts.map +1 -1
  9. package/dist/src/editor/components/block-editor.d.ts.map +1 -1
  10. package/dist/src/editor/components/color-toolbar.d.ts.map +1 -1
  11. package/dist/src/editor/components/editable-highlights.d.ts.map +1 -1
  12. package/dist/src/editor/components/outline.d.ts.map +1 -1
  13. package/dist/src/editor/constants.d.ts +1 -0
  14. package/dist/src/editor/constants.d.ts.map +1 -1
  15. package/dist/src/editor/dom.d.ts +9 -0
  16. package/dist/src/editor/dom.d.ts.map +1 -1
  17. package/dist/src/editor/editor.d.ts.map +1 -1
  18. package/dist/src/editor/history.d.ts.map +1 -1
  19. package/dist/src/editor/hooks/useBlockEditorHandlers.d.ts.map +1 -1
  20. package/dist/src/editor/index.d.ts.map +1 -1
  21. package/dist/src/editor/storage.d.ts +2 -0
  22. package/dist/src/editor/storage.d.ts.map +1 -1
  23. package/dist/src/handlers/array-ops.d.ts +59 -0
  24. package/dist/src/handlers/array-ops.d.ts.map +1 -0
  25. package/dist/src/handlers/component-ops.d.ts +26 -0
  26. package/dist/src/handlers/component-ops.d.ts.map +1 -1
  27. package/dist/src/index.d.ts.map +1 -1
  28. package/dist/src/source-finder/cross-file-tracker.d.ts.map +1 -1
  29. package/dist/src/tsconfig.tsbuildinfo +1 -1
  30. package/package.json +1 -1
  31. package/src/build-processor.ts +27 -0
  32. package/src/component-registry.ts +125 -76
  33. package/src/dev-middleware.ts +85 -16
  34. package/src/editor/api.ts +72 -0
  35. package/src/editor/components/ai-chat.tsx +0 -1
  36. package/src/editor/components/block-editor.tsx +92 -17
  37. package/src/editor/components/color-toolbar.tsx +7 -1
  38. package/src/editor/components/editable-highlights.tsx +4 -1
  39. package/src/editor/components/outline.tsx +11 -6
  40. package/src/editor/constants.ts +1 -0
  41. package/src/editor/dom.ts +46 -1
  42. package/src/editor/editor.ts +5 -2
  43. package/src/editor/history.ts +1 -6
  44. package/src/editor/hooks/useBlockEditorHandlers.ts +86 -29
  45. package/src/editor/index.tsx +24 -8
  46. package/src/editor/storage.ts +24 -0
  47. package/src/handlers/array-ops.ts +452 -0
  48. package/src/handlers/component-ops.ts +269 -18
  49. package/src/handlers/markdown-ops.ts +7 -4
  50. package/src/handlers/request-utils.ts +1 -1
  51. package/src/handlers/source-writer.ts +4 -5
  52. package/src/index.ts +15 -10
  53. package/src/manifest-writer.ts +1 -1
  54. package/src/source-finder/cross-file-tracker.ts +1 -1
  55. package/src/source-finder/search-index.ts +1 -1
@@ -100,13 +100,10 @@ export async function handleInsertComponent(
100
100
  } else {
101
101
  const occurrenceIndex = getComponentOccurrenceIndex(manifest, referenceComponent)
102
102
  refLineIndex = findComponentInvocationLine(lines, referenceComponent.componentName, occurrenceIndex)
103
- if (refLineIndex < 0) {
104
- refLineIndex = referenceComponent.sourceLine - 1
105
- }
106
103
  }
107
104
 
108
105
  if (refLineIndex < 0 || refLineIndex >= lines.length) {
109
- return { success: false, error: `Invalid source line for reference component: ${refLineIndex + 1}` }
106
+ return { success: false, error: `Could not find <${referenceComponent.componentName}> invocation in ${filePath}` }
110
107
  }
111
108
 
112
109
  const newComponentJsx = generateComponentJsx(componentName, props, componentDef)
@@ -126,6 +123,10 @@ export async function handleInsertComponent(
126
123
  .join('\n')
127
124
 
128
125
  lines.splice(insertIndex, 0, indentedJsx)
126
+
127
+ // Ensure the component is imported in the frontmatter
128
+ ensureComponentImport(lines, componentName, componentDef.file, filePath)
129
+
129
130
  await fs.writeFile(fullPath, lines.join('\n'), 'utf-8')
130
131
 
131
132
  return {
@@ -200,13 +201,10 @@ export async function handleRemoveComponent(
200
201
  } else {
201
202
  const occurrenceIndex = getComponentOccurrenceIndex(manifest, component)
202
203
  refLineIndex = findComponentInvocationLine(lines, component.componentName, occurrenceIndex)
203
- if (refLineIndex < 0) {
204
- refLineIndex = component.sourceLine - 1
205
- }
206
204
  }
207
205
 
208
206
  if (refLineIndex < 0 || refLineIndex >= lines.length) {
209
- return { success: false, error: `Invalid source line for component: ${refLineIndex + 1}` }
207
+ return { success: false, error: `Could not find <${component.componentName}> invocation in ${filePath}` }
210
208
  }
211
209
 
212
210
  const { startLine, endLine } = findComponentBounds(
@@ -304,7 +302,7 @@ function findComponentBounds(
304
302
  return { startLine, endLine }
305
303
  }
306
304
 
307
- function getPageFileCandidates(pageUrl: string): string[] {
305
+ export function getPageFileCandidates(pageUrl: string): string[] {
308
306
  let pathname: string
309
307
  try {
310
308
  const url = new URL(pageUrl)
@@ -330,7 +328,7 @@ function getPageFileCandidates(pageUrl: string): string[] {
330
328
  ]
331
329
  }
332
330
 
333
- function getComponentOccurrenceIndex(
331
+ export function getComponentOccurrenceIndex(
334
332
  manifest: CmsManifest,
335
333
  referenceComponent: ComponentInstance,
336
334
  ): number {
@@ -343,14 +341,14 @@ function getComponentOccurrenceIndex(
343
341
  const sameNameComponents = Object.values(manifest.components)
344
342
  .filter(c =>
345
343
  c.componentName === componentName
346
- && (!invocationSource || c.invocationSourcePath === invocationSource),
344
+ && (!invocationSource || c.invocationSourcePath === invocationSource)
347
345
  )
348
346
 
349
347
  const index = sameNameComponents.findIndex(c => c.id === referenceComponent.id)
350
348
  return index >= 0 ? index : 0
351
349
  }
352
350
 
353
- async function findComponentInvocationFile(
351
+ export async function findComponentInvocationFile(
354
352
  projectRoot: string,
355
353
  pageUrl: string,
356
354
  manifest: CmsManifest,
@@ -401,14 +399,16 @@ async function findComponentInvocationFile(
401
399
  return null
402
400
  }
403
401
 
404
- function findComponentInvocationLine(
402
+ export function findComponentInvocationLine(
405
403
  lines: string[],
406
404
  componentName: string,
407
405
  occurrenceIndex: number,
408
406
  ): number {
409
- const pattern = new RegExp(`<${escapeRegex(componentName)}(?:\\s|>|/>)`)
407
+ const pattern = new RegExp(`<${escapeRegex(componentName)}(?:\\s|>|/>|$)`)
408
+ // Skip frontmatter section in .astro/.mdx files (code between --- delimiters)
409
+ const startLine = findFrontmatterEnd(lines)
410
410
  let found = 0
411
- for (let i = 0; i < lines.length; i++) {
411
+ for (let i = startLine; i < lines.length; i++) {
412
412
  if (pattern.test(lines[i]!)) {
413
413
  if (found === occurrenceIndex) return i
414
414
  found++
@@ -417,6 +417,18 @@ function findComponentInvocationLine(
417
417
  return found > 0 ? findComponentInvocationLine(lines, componentName, 0) : -1
418
418
  }
419
419
 
420
+ /**
421
+ * Find the line index after the frontmatter block (--- ... ---).
422
+ * Returns 0 if no frontmatter is found.
423
+ */
424
+ export function findFrontmatterEnd(lines: string[]): number {
425
+ if (lines.length === 0 || lines[0]!.trim() !== '---') return 0
426
+ for (let i = 1; i < lines.length; i++) {
427
+ if (lines[i]!.trim() === '---') return i + 1
428
+ }
429
+ return 0
430
+ }
431
+
420
432
  function generateComponentJsx(
421
433
  componentName: string,
422
434
  props: Record<string, unknown>,
@@ -452,12 +464,251 @@ function escapeHtml(str: string): string {
452
464
  .replace(/>/g, '&gt;')
453
465
  }
454
466
 
455
- function getIndentation(line: string): string {
467
+ export function getIndentation(line: string): string {
456
468
  const match = line.match(/^(\s*)/)
457
469
  return match ? match[1]! : ''
458
470
  }
459
471
 
460
- function normalizeFilePath(p: string): string {
461
- return p.startsWith('/') ? p.slice(1) : p
472
+ export function normalizeFilePath(p: string): string {
473
+ if (!p.startsWith('/')) return p
474
+ // Absolute filesystem paths (e.g. /Users/.../src/pages/index.astro) must stay intact
475
+ const projectRoot = path.resolve(getProjectRoot())
476
+ if (p.startsWith(projectRoot)) return p
477
+ // Project-relative paths with a leading slash (e.g. /src/pages/...) → strip it
478
+ return p.slice(1)
462
479
  }
463
480
 
481
+ /**
482
+ * Extract prop values from a component invocation in source code.
483
+ * Parses the opening JSX tag starting at `lineIndex` and returns a map of prop names to values.
484
+ */
485
+ export function extractPropsFromSource(
486
+ lines: string[],
487
+ lineIndex: number,
488
+ componentName: string,
489
+ ): Record<string, any> {
490
+ // Accumulate lines until we have the complete opening tag
491
+ let text = ''
492
+ for (let i = lineIndex; i < lines.length; i++) {
493
+ text += (i > lineIndex ? '\n' : '') + lines[i]!
494
+ if (findOpeningTagEnd(text) >= 0) break
495
+ }
496
+ return parseOpeningTagProps(text, componentName)
497
+ }
498
+
499
+ /**
500
+ * Find the position of `>` or `/>` that closes the opening tag,
501
+ * correctly skipping over braces and string literals.
502
+ * Returns -1 if the tag end is not found.
503
+ */
504
+ function findOpeningTagEnd(text: string): number {
505
+ let braceDepth = 0
506
+ let inStr: string | null = null
507
+ let pastTagName = false
508
+
509
+ for (let i = 0; i < text.length; i++) {
510
+ const ch = text[i]!
511
+
512
+ if (!pastTagName) {
513
+ if (ch === '<') {
514
+ // Skip past `<ComponentName`
515
+ while (i < text.length && !/\s|\/|>/.test(text[i + 1] ?? '')) i++
516
+ pastTagName = true
517
+ }
518
+ continue
519
+ }
520
+
521
+ if (inStr) {
522
+ if (ch === '\\') {
523
+ i++
524
+ continue
525
+ }
526
+ if (ch === inStr) inStr = null
527
+ continue
528
+ }
529
+
530
+ if (ch === '"' || ch === "'" || ch === '`') {
531
+ inStr = ch
532
+ continue
533
+ }
534
+ if (ch === '{') {
535
+ braceDepth++
536
+ continue
537
+ }
538
+ if (ch === '}') {
539
+ braceDepth--
540
+ continue
541
+ }
542
+
543
+ if (braceDepth === 0) {
544
+ if (ch === '/' && text[i + 1] === '>') return i + 1
545
+ if (ch === '>') return i
546
+ }
547
+ }
548
+
549
+ return -1
550
+ }
551
+
552
+ /**
553
+ * Parse JSX props from an opening tag string like `<Comp foo="bar" count={3} active />`.
554
+ */
555
+ function parseOpeningTagProps(text: string, componentName: string): Record<string, any> {
556
+ const props: Record<string, any> = {}
557
+
558
+ // Find <ComponentName and skip past it
559
+ const tagIdx = text.indexOf('<' + componentName)
560
+ if (tagIdx < 0) return props
561
+
562
+ let pos = tagIdx + 1 + componentName.length
563
+
564
+ while (pos < text.length) {
565
+ // Skip whitespace
566
+ while (pos < text.length && /\s/.test(text[pos]!)) pos++
567
+
568
+ // End of tag?
569
+ if (pos >= text.length || text[pos] === '>' || text[pos] === '/') break
570
+
571
+ // Spread: {...expr} — skip it
572
+ if (text[pos] === '{') {
573
+ pos = skipBracedBlock(text, pos)
574
+ continue
575
+ }
576
+
577
+ // Parse attribute name
578
+ const nameStart = pos
579
+ while (pos < text.length && /[\w\-:.]/.test(text[pos]!)) pos++
580
+ const name = text.slice(nameStart, pos)
581
+ if (!name) {
582
+ pos++
583
+ continue
584
+ }
585
+
586
+ // Skip whitespace
587
+ while (pos < text.length && /\s/.test(text[pos]!)) pos++
588
+
589
+ // No `=` means boolean shorthand
590
+ if (text[pos] !== '=') {
591
+ props[name] = true
592
+ continue
593
+ }
594
+ pos++ // skip =
595
+
596
+ // Skip whitespace
597
+ while (pos < text.length && /\s/.test(text[pos]!)) pos++
598
+
599
+ const ch = text[pos]
600
+ if (ch === '"' || ch === "'") {
601
+ // Quoted string: prop="value" or prop='value'
602
+ pos++
603
+ const start = pos
604
+ while (pos < text.length && text[pos] !== ch) {
605
+ if (text[pos] === '\\') pos++
606
+ pos++
607
+ }
608
+ props[name] = text.slice(start, pos)
609
+ pos++ // skip closing quote
610
+ } else if (ch === '{') {
611
+ // JSX expression: prop={...}
612
+ const inner = extractBracedContent(text, pos)
613
+ pos = skipBracedBlock(text, pos)
614
+
615
+ const trimmed = inner.trim()
616
+ if (trimmed === 'true') props[name] = true
617
+ else if (trimmed === 'false') props[name] = false
618
+ else if (/^-?\d+(\.\d+)?$/.test(trimmed)) props[name] = Number(trimmed)
619
+ else if (
620
+ (trimmed.startsWith('"') && trimmed.endsWith('"'))
621
+ || (trimmed.startsWith("'") && trimmed.endsWith("'"))
622
+ ) {
623
+ props[name] = trimmed.slice(1, -1)
624
+ } else if (trimmed.startsWith('`') && trimmed.endsWith('`')) {
625
+ props[name] = trimmed.slice(1, -1)
626
+ } else {
627
+ props[name] = trimmed // raw expression
628
+ }
629
+ }
630
+ }
631
+
632
+ return props
633
+ }
634
+
635
+ /** Skip past a balanced `{ ... }` block, handling nested braces and string literals. */
636
+ function skipBracedBlock(text: string, pos: number): number {
637
+ let depth = 0
638
+ let inStr: string | null = null
639
+ while (pos < text.length) {
640
+ const ch = text[pos]!
641
+ if (inStr) {
642
+ if (ch === '\\') {
643
+ pos += 2
644
+ continue
645
+ }
646
+ if (ch === inStr) inStr = null
647
+ } else {
648
+ if (ch === '{') depth++
649
+ else if (ch === '}') {
650
+ depth--
651
+ if (depth === 0) return pos + 1
652
+ } else if (ch === '"' || ch === "'" || ch === '`') inStr = ch
653
+ }
654
+ pos++
655
+ }
656
+ return pos
657
+ }
658
+
659
+ /** Extract the text content between `{` and its matching `}`. */
660
+ function extractBracedContent(text: string, pos: number): string {
661
+ const start = pos + 1
662
+ const end = skipBracedBlock(text, pos) - 1
663
+ return text.slice(start, end)
664
+ }
665
+
666
+ /**
667
+ * Ensure the component has an import statement in the file's frontmatter.
668
+ * If the component is already imported, this is a no-op.
669
+ * Mutates the `lines` array in place.
670
+ */
671
+ export function ensureComponentImport(
672
+ lines: string[],
673
+ componentName: string,
674
+ componentFile: string,
675
+ targetFile: string,
676
+ ): void {
677
+ // Check if the component is already imported anywhere in the frontmatter
678
+ const importPattern = new RegExp(
679
+ `import\\s+${escapeRegex(componentName)}\\s+from\\s+['"]`,
680
+ )
681
+
682
+ const frontmatterEnd = findFrontmatterEnd(lines)
683
+
684
+ // Scan frontmatter for existing import
685
+ for (let i = 0; i < frontmatterEnd; i++) {
686
+ if (importPattern.test(lines[i]!)) {
687
+ return // Already imported
688
+ }
689
+ }
690
+
691
+ // Compute relative import path from target file to component file
692
+ const targetDir = path.dirname(targetFile)
693
+ let relativePath = path.relative(targetDir, componentFile)
694
+ if (!relativePath.startsWith('.')) {
695
+ relativePath = './' + relativePath
696
+ }
697
+
698
+ const importStatement = `import ${componentName} from '${relativePath}'`
699
+
700
+ if (frontmatterEnd > 0) {
701
+ // Has frontmatter — insert import before the closing ---
702
+ // Find the last import line or insert right after opening ---
703
+ let insertAt = 1 // After opening ---
704
+ for (let i = 1; i < frontmatterEnd - 1; i++) {
705
+ if (/^\s*import\s/.test(lines[i]!)) {
706
+ insertAt = i + 1
707
+ }
708
+ }
709
+ lines.splice(insertAt, 0, importStatement)
710
+ } else {
711
+ // No frontmatter — create one at the top
712
+ lines.splice(0, 0, '---', importStatement, '---')
713
+ }
714
+ }
@@ -2,7 +2,7 @@ import fs from 'node:fs/promises'
2
2
  import path from 'node:path'
3
3
  import yaml from 'yaml'
4
4
  import { getProjectRoot } from '../config'
5
- import { acquireFileLock, resolveAndValidatePath as sharedResolveAndValidatePath } from '../utils'
5
+ import { acquireFileLock } from '../utils'
6
6
 
7
7
  export interface BlogFrontmatter {
8
8
  title: string
@@ -143,11 +143,14 @@ export async function handleCreateMarkdown(
143
143
  */
144
144
  function resolveAndValidatePath(filePath: string): string {
145
145
  const projectRoot = getProjectRoot()
146
- const normalizedPath = filePath.startsWith('/') ? filePath.slice(1) : filePath
147
- const fullPath = path.resolve(projectRoot, normalizedPath)
146
+ const resolvedRoot = path.resolve(projectRoot)
147
+ // Absolute filesystem paths (e.g. /Users/...) stay intact;
148
+ // project-relative paths with a leading slash (e.g. /src/content/...) get it stripped
149
+ const isAbsoluteFs = filePath.startsWith(resolvedRoot)
150
+ const normalizedPath = (!isAbsoluteFs && filePath.startsWith('/')) ? filePath.slice(1) : filePath
151
+ const fullPath = path.isAbsolute(normalizedPath) ? path.resolve(normalizedPath) : path.resolve(projectRoot, normalizedPath)
148
152
 
149
153
  // Ensure the resolved path is within the project root
150
- const resolvedRoot = path.resolve(projectRoot)
151
154
  if (!fullPath.startsWith(resolvedRoot + path.sep) && fullPath !== resolvedRoot) {
152
155
  throw new Error(`Path traversal detected: ${filePath}`)
153
156
  }
@@ -99,7 +99,7 @@ export function parseMultipartFile(body: Buffer, contentTypeHeader: string): Par
99
99
 
100
100
  // Sanitize filename: strip path separators and dots prefix to prevent traversal
101
101
  const rawFilename = dispositionMatch[1] ?? 'upload'
102
- const filename = rawFilename.replace(/[/\\]/g, '_').replace(/^\.+/, '') || 'upload'
102
+ const filename = rawFilename.replace(/[/\\]/g, '_').replace(/^\.+/, '') || 'upload'
103
103
  const ctMatch = headerSection.match(/Content-Type:\s*(\S+)/)
104
104
  const contentType = ctMatch?.[1] ?? 'application/octet-stream'
105
105
 
@@ -66,10 +66,10 @@ export async function handleUpdate(
66
66
  const pageData = manifestWriter.getPageManifest(pagePath)
67
67
  const manifest: CmsManifest = pageData
68
68
  ? {
69
- entries: pageData.entries,
70
- components: pageData.components,
71
- componentDefinitions: manifestWriter.getComponentDefinitions(),
72
- }
69
+ entries: pageData.entries,
70
+ components: pageData.components,
71
+ componentDefinitions: manifestWriter.getComponentDefinitions(),
72
+ }
73
73
  : manifestWriter.getGlobalManifest()
74
74
 
75
75
  // Group changes by source file
@@ -646,4 +646,3 @@ function findExpressionSrcAttribute(text: string): { index: number; length: numb
646
646
  function escapeRegExp(string: string): string {
647
647
  return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
648
648
  }
649
-
package/src/index.ts CHANGED
@@ -1,7 +1,7 @@
1
+ import tailwindcss from '@tailwindcss/vite'
1
2
  import type { AstroIntegration } from 'astro'
2
3
  import { dirname, join } from 'node:path'
3
4
  import { fileURLToPath } from 'node:url'
4
- import tailwindcss from '@tailwindcss/vite'
5
5
 
6
6
  import { processBuildOutput } from './build-processor'
7
7
  import { scanCollections } from './collection-scanner'
@@ -142,7 +142,9 @@ export default function nuaCms(options: NuaCmsOptions = {}): AstroIntegration {
142
142
  ? `window.NuaCmsConfig = ${JSON.stringify(resolvedCmsConfig)};`
143
143
  : ''
144
144
 
145
- injectScript('page', `
145
+ injectScript(
146
+ 'page',
147
+ `
146
148
  ${configScript}
147
149
  if (!document.querySelector('script[data-nuasite-cms]')) {
148
150
  const s = document.createElement('script');
@@ -151,7 +153,8 @@ export default function nuaCms(options: NuaCmsOptions = {}): AstroIntegration {
151
153
  s.dataset.nuasiteCms = '';
152
154
  document.head.appendChild(s);
153
155
  }
154
- `)
156
+ `,
157
+ )
155
158
 
156
159
  // Resolve the virtual CMS editor path to the actual source file.
157
160
  // Vite's dev server transforms the TSX, resolves imports, and serves it.
@@ -195,13 +198,15 @@ export default function nuaCms(options: NuaCmsOptions = {}): AstroIntegration {
195
198
  updateConfig({
196
199
  vite: {
197
200
  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,
201
+ resolve: !src
202
+ ? {
203
+ alias: {
204
+ 'react': 'preact/compat',
205
+ 'react-dom': 'preact/compat',
206
+ 'react/jsx-runtime': 'preact/jsx-runtime',
207
+ },
208
+ }
209
+ : undefined,
205
210
  server: {
206
211
  proxy: proxyConfig,
207
212
  },
@@ -161,7 +161,7 @@ export class ManifestWriter {
161
161
  this.writePageManifest(pagePath, entries, components, collection, seo)
162
162
  .catch((err) => {
163
163
  console.error(`[astro-cms] Failed to write manifest for ${pagePath}:`, err)
164
- }),
164
+ })
165
165
  )
166
166
  }
167
167
  }
@@ -2,12 +2,12 @@ import fs from 'node:fs/promises'
2
2
  import path from 'node:path'
3
3
 
4
4
  import { getProjectRoot } from '../config'
5
+ import { escapeRegex } from '../utils'
5
6
  import { buildDefinitionPath, parseExpressionPath } from './ast-extractors'
6
7
  import { getCachedParsedFile } from './ast-parser'
7
8
  import { findComponentProp, findExpressionProp, findSpreadProp } from './element-finder'
8
9
  import { normalizeText } from './snippet-utils'
9
10
  import type { ImportInfo, SourceLocation, VariableDefinition } from './types'
10
- import { escapeRegex } from '../utils'
11
11
  import { getExportedDefinitions, resolveImportPath } from './variable-extraction'
12
12
 
13
13
  // ============================================================================
@@ -3,6 +3,7 @@ import fs from 'node:fs/promises'
3
3
  import path from 'node:path'
4
4
 
5
5
  import { getProjectRoot } from '../config'
6
+ import { escapeRegex } from '../utils'
6
7
  import { buildDefinitionPath, parseExpressionPath } from './ast-extractors'
7
8
  import { getCachedParsedFile } from './ast-parser'
8
9
  import {
@@ -14,7 +15,6 @@ import {
14
15
  isSearchIndexInitialized,
15
16
  setSearchIndexInitialized,
16
17
  } from './cache'
17
- import { escapeRegex } from '../utils'
18
18
  import { extractImageSnippet, extractInnerHtmlFromSnippet, normalizeText } from './snippet-utils'
19
19
  import type { CachedParsedFile, SourceLocation } from './types'
20
20