@nuasite/cms-marker 0.0.67 → 0.0.70

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.
@@ -184,6 +184,44 @@ export interface CollectionEntry {
184
184
  /** ID of the wrapper element containing the rendered markdown */
185
185
  wrapperId?: string;
186
186
  }
187
+ /** Field types for collection schema inference */
188
+ export type FieldType = 'text' | 'textarea' | 'date' | 'boolean' | 'number' | 'image' | 'url' | 'select' | 'array' | 'object' | 'reference';
189
+ /** Definition of a single field in a collection's schema */
190
+ export interface FieldDefinition {
191
+ /** Field name as it appears in frontmatter */
192
+ name: string;
193
+ /** Inferred or specified field type */
194
+ type: FieldType;
195
+ /** Whether the field is required (present in all entries) */
196
+ required: boolean;
197
+ /** Default value for the field */
198
+ defaultValue?: unknown;
199
+ /** Options for 'select' type fields */
200
+ options?: string[];
201
+ /** Item type for 'array' fields */
202
+ itemType?: FieldType;
203
+ /** Nested fields for 'object' type */
204
+ fields?: FieldDefinition[];
205
+ /** Sample values seen across entries */
206
+ examples?: unknown[];
207
+ }
208
+ /** Definition of a content collection with inferred schema */
209
+ export interface CollectionDefinition {
210
+ /** Collection identifier (directory name) */
211
+ name: string;
212
+ /** Human-readable label for the collection */
213
+ label: string;
214
+ /** Path to the collection directory */
215
+ path: string;
216
+ /** Number of entries in the collection */
217
+ entryCount: number;
218
+ /** Inferred field definitions */
219
+ fields: FieldDefinition[];
220
+ /** Whether the collection has draft support */
221
+ supportsDraft?: boolean;
222
+ /** File extension used by entries */
223
+ fileExtension: 'md' | 'mdx';
224
+ }
187
225
  /** Manifest metadata for versioning and conflict detection */
188
226
  export interface ManifestMetadata {
189
227
  /** Manifest schema version */
@@ -207,6 +245,8 @@ export interface CmsManifest {
207
245
  componentDefinitions: Record<string, ComponentDefinition>;
208
246
  /** Content collection entries indexed by "collectionName/slug" */
209
247
  collections?: Record<string, CollectionEntry>;
248
+ /** Collection definitions with inferred schemas */
249
+ collectionDefinitions?: Record<string, CollectionDefinition>;
210
250
  /** Available Tailwind colors from the project's config */
211
251
  availableColors?: AvailableColors;
212
252
  /** Available text styles from the project's Tailwind config */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -14,7 +14,7 @@
14
14
  "directory": "packages/cms-marker"
15
15
  },
16
16
  "license": "Apache-2.0",
17
- "version": "0.0.67",
17
+ "version": "0.0.70",
18
18
  "module": "src/index.ts",
19
19
  "types": "src/index.ts",
20
20
  "type": "module",
@@ -0,0 +1,328 @@
1
+ import fs from 'node:fs/promises'
2
+ import path from 'node:path'
3
+ import { getProjectRoot } from './config'
4
+ import type { CollectionDefinition, FieldDefinition, FieldType } from './types'
5
+
6
+ /** Regex patterns for type inference */
7
+ const DATE_PATTERN = /^\d{4}-\d{2}-\d{2}/
8
+ const URL_PATTERN = /^(https?:\/\/|\/)/
9
+ const IMAGE_EXTENSIONS = /\.(jpg|jpeg|png|gif|webp|svg|avif)$/i
10
+
11
+ /** Maximum unique values before treating as free-form text instead of select */
12
+ const MAX_SELECT_OPTIONS = 10
13
+
14
+ /** Minimum length for textarea detection */
15
+ const TEXTAREA_MIN_LENGTH = 200
16
+
17
+ /**
18
+ * Observed values for a single field across multiple files
19
+ */
20
+ interface FieldObservation {
21
+ name: string
22
+ values: unknown[]
23
+ presentCount: number
24
+ totalEntries: number
25
+ }
26
+
27
+ /**
28
+ * Parse YAML frontmatter from markdown content
29
+ */
30
+ function parseFrontmatter(content: string): Record<string, unknown> | null {
31
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/)
32
+ if (!match?.[1]) return null
33
+
34
+ const frontmatter: Record<string, unknown> = {}
35
+ const lines = match[1].split('\n')
36
+
37
+ let currentKey: string | null = null
38
+ let currentArrayItems: unknown[] = []
39
+ let isInArray = false
40
+
41
+ for (const line of lines) {
42
+ // Check for array item (starts with "- ")
43
+ const arrayMatch = line.match(/^(\s*)- (.*)$/)
44
+ if (arrayMatch && isInArray && arrayMatch[2] !== undefined) {
45
+ const value = parseYamlValue(arrayMatch[2].trim())
46
+ currentArrayItems.push(value)
47
+ continue
48
+ }
49
+
50
+ // Check for key-value pair
51
+ const kvMatch = line.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\s*:\s*(.*)$/)
52
+ if (kvMatch?.[1] && kvMatch[2] !== undefined) {
53
+ // Save previous array if any
54
+ if (isInArray && currentKey) {
55
+ frontmatter[currentKey] = currentArrayItems
56
+ }
57
+
58
+ currentKey = kvMatch[1]
59
+ const rawValue = kvMatch[2].trim()
60
+
61
+ // Check if starting an array
62
+ if (rawValue === '' || rawValue === '[]') {
63
+ isInArray = true
64
+ currentArrayItems = []
65
+ } else if (rawValue.startsWith('[') && rawValue.endsWith(']')) {
66
+ // Inline array like [a, b, c]
67
+ const items = rawValue.slice(1, -1).split(',').map(s => parseYamlValue(s.trim()))
68
+ frontmatter[currentKey] = items
69
+ isInArray = false
70
+ } else {
71
+ frontmatter[currentKey] = parseYamlValue(rawValue)
72
+ isInArray = false
73
+ }
74
+ }
75
+ }
76
+
77
+ // Save final array if any
78
+ if (isInArray && currentKey) {
79
+ frontmatter[currentKey] = currentArrayItems
80
+ }
81
+
82
+ return frontmatter
83
+ }
84
+
85
+ /**
86
+ * Parse a YAML value into appropriate JS type
87
+ */
88
+ function parseYamlValue(value: string): unknown {
89
+ // Handle quoted strings
90
+ if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
91
+ return value.slice(1, -1)
92
+ }
93
+
94
+ // Handle booleans
95
+ if (value === 'true') return true
96
+ if (value === 'false') return false
97
+
98
+ // Handle null/empty
99
+ if (value === '' || value === 'null' || value === '~') return null
100
+
101
+ // Handle numbers
102
+ const num = Number(value)
103
+ if (!Number.isNaN(num) && value !== '') return num
104
+
105
+ return value
106
+ }
107
+
108
+ /**
109
+ * Infer the field type from a value
110
+ */
111
+ function inferFieldType(value: unknown, key: string): FieldType {
112
+ if (value === null || value === undefined) {
113
+ return 'text'
114
+ }
115
+
116
+ if (typeof value === 'boolean') {
117
+ return 'boolean'
118
+ }
119
+
120
+ if (typeof value === 'number') {
121
+ return 'number'
122
+ }
123
+
124
+ if (Array.isArray(value)) {
125
+ return 'array'
126
+ }
127
+
128
+ if (typeof value === 'object') {
129
+ return 'object'
130
+ }
131
+
132
+ if (typeof value === 'string') {
133
+ // Check for date pattern
134
+ if (DATE_PATTERN.test(value)) {
135
+ return 'date'
136
+ }
137
+
138
+ // Check for image paths
139
+ if (
140
+ IMAGE_EXTENSIONS.test(value) || key.toLowerCase().includes('image') || key.toLowerCase().includes('thumbnail')
141
+ || key.toLowerCase().includes('cover')
142
+ ) {
143
+ return 'image'
144
+ }
145
+
146
+ // Check for URLs
147
+ if (URL_PATTERN.test(value)) {
148
+ return 'url'
149
+ }
150
+
151
+ // Check for textarea (long text or contains newlines)
152
+ if (value.includes('\n') || value.length > TEXTAREA_MIN_LENGTH) {
153
+ return 'textarea'
154
+ }
155
+
156
+ return 'text'
157
+ }
158
+
159
+ return 'text'
160
+ }
161
+
162
+ /**
163
+ * Merge field observations from multiple files to determine final field definition
164
+ */
165
+ function mergeFieldObservations(observations: FieldObservation[]): FieldDefinition[] {
166
+ const fields: FieldDefinition[] = []
167
+
168
+ for (const obs of observations) {
169
+ const nonNullValues = obs.values.filter(v => v !== null && v !== undefined)
170
+ if (nonNullValues.length === 0) continue
171
+
172
+ // Determine type by consensus (most common inferred type)
173
+ const typeCounts = new Map<FieldType, number>()
174
+ for (const value of nonNullValues) {
175
+ const type = inferFieldType(value, obs.name)
176
+ typeCounts.set(type, (typeCounts.get(type) || 0) + 1)
177
+ }
178
+
179
+ // Get most common type
180
+ let fieldType: FieldType = 'text'
181
+ let maxCount = 0
182
+ for (const [type, count] of typeCounts) {
183
+ if (count > maxCount) {
184
+ maxCount = count
185
+ fieldType = type
186
+ }
187
+ }
188
+
189
+ const field: FieldDefinition = {
190
+ name: obs.name,
191
+ type: fieldType,
192
+ required: obs.presentCount === obs.totalEntries,
193
+ examples: nonNullValues.slice(0, 3),
194
+ }
195
+
196
+ // For text fields, check if we should treat as select (limited unique values)
197
+ if (fieldType === 'text') {
198
+ const uniqueValues = [...new Set(nonNullValues.map(v => String(v)))]
199
+ if (uniqueValues.length > 0 && uniqueValues.length <= MAX_SELECT_OPTIONS && nonNullValues.length >= 2) {
200
+ field.type = 'select'
201
+ field.options = uniqueValues.sort()
202
+ }
203
+ }
204
+
205
+ // For arrays, try to infer item type
206
+ if (fieldType === 'array') {
207
+ const allItems = nonNullValues.flatMap(v => (Array.isArray(v) ? v : []))
208
+ if (allItems.length > 0) {
209
+ const itemType = inferFieldType(allItems[0], obs.name)
210
+ field.itemType = itemType
211
+
212
+ // Check if array items should be select
213
+ if (itemType === 'text') {
214
+ const uniqueItems = [...new Set(allItems.map(v => String(v)))]
215
+ if (uniqueItems.length <= MAX_SELECT_OPTIONS * 2) {
216
+ field.options = uniqueItems.sort()
217
+ }
218
+ }
219
+ }
220
+ }
221
+
222
+ fields.push(field)
223
+ }
224
+
225
+ return fields
226
+ }
227
+
228
+ /**
229
+ * Scan a single collection directory and infer its schema
230
+ */
231
+ async function scanCollection(collectionPath: string, collectionName: string, contentDir: string): Promise<CollectionDefinition | null> {
232
+ try {
233
+ const entries = await fs.readdir(collectionPath, { withFileTypes: true })
234
+ const markdownFiles = entries.filter(e => e.isFile() && (e.name.endsWith('.md') || e.name.endsWith('.mdx')))
235
+
236
+ if (markdownFiles.length === 0) return null
237
+
238
+ // Determine file extension (prefer md, use mdx if that's all we have)
239
+ const hasMd = markdownFiles.some(f => f.name.endsWith('.md'))
240
+ const fileExtension: 'md' | 'mdx' = hasMd ? 'md' : 'mdx'
241
+
242
+ // Collect field observations across all files
243
+ const fieldMap = new Map<string, FieldObservation>()
244
+ let hasDraft = false
245
+
246
+ for (const file of markdownFiles) {
247
+ const filePath = path.join(collectionPath, file.name)
248
+ const content = await fs.readFile(filePath, 'utf-8')
249
+ const frontmatter = parseFrontmatter(content)
250
+
251
+ if (!frontmatter) continue
252
+
253
+ for (const [key, value] of Object.entries(frontmatter)) {
254
+ if (key === 'draft' && typeof value === 'boolean') {
255
+ hasDraft = true
256
+ }
257
+
258
+ let obs = fieldMap.get(key)
259
+ if (!obs) {
260
+ obs = {
261
+ name: key,
262
+ values: [],
263
+ presentCount: 0,
264
+ totalEntries: markdownFiles.length,
265
+ }
266
+ fieldMap.set(key, obs)
267
+ }
268
+
269
+ obs.values.push(value)
270
+ obs.presentCount++
271
+ }
272
+ }
273
+
274
+ // Update totalEntries for all observations
275
+ for (const obs of fieldMap.values()) {
276
+ obs.totalEntries = markdownFiles.length
277
+ }
278
+
279
+ const fields = mergeFieldObservations(Array.from(fieldMap.values()))
280
+
281
+ // Generate a human-readable label
282
+ const label = collectionName
283
+ .replace(/[-_]/g, ' ')
284
+ .replace(/\b\w/g, c => c.toUpperCase())
285
+
286
+ return {
287
+ name: collectionName,
288
+ label,
289
+ path: path.join(contentDir, collectionName),
290
+ entryCount: markdownFiles.length,
291
+ fields,
292
+ supportsDraft: hasDraft,
293
+ fileExtension,
294
+ }
295
+ } catch {
296
+ return null
297
+ }
298
+ }
299
+
300
+ /**
301
+ * Scan all collections in the content directory
302
+ */
303
+ export async function scanCollections(contentDir: string = 'src/content'): Promise<Record<string, CollectionDefinition>> {
304
+ const projectRoot = getProjectRoot()
305
+ const fullContentDir = path.isAbsolute(contentDir) ? contentDir : path.join(projectRoot, contentDir)
306
+
307
+ const collections: Record<string, CollectionDefinition> = {}
308
+
309
+ try {
310
+ const entries = await fs.readdir(fullContentDir, { withFileTypes: true })
311
+
312
+ const scanPromises = entries
313
+ .filter(entry => entry.isDirectory() && !entry.name.startsWith('_') && !entry.name.startsWith('.'))
314
+ .map(async entry => {
315
+ const collectionPath = path.join(fullContentDir, entry.name)
316
+ const definition = await scanCollection(collectionPath, entry.name, contentDir)
317
+ if (definition) {
318
+ collections[entry.name] = definition
319
+ }
320
+ })
321
+
322
+ await Promise.all(scanPromises)
323
+ } catch {
324
+ // Content directory doesn't exist or isn't readable
325
+ }
326
+
327
+ return collections
328
+ }
@@ -35,19 +35,21 @@ export function createDevMiddleware(
35
35
  componentDefinitions: Record<string, ComponentDefinition>,
36
36
  idCounter: { value: number },
37
37
  ) {
38
- // Serve global CMS manifest (component definitions, available colors, and settings)
38
+ // Serve global CMS manifest (component definitions, available colors, collection definitions, and settings)
39
39
  server.middlewares.use((req, res, next) => {
40
40
  if (req.url === '/cms-manifest.json') {
41
41
  res.setHeader('Content-Type', 'application/json')
42
42
  res.setHeader('Access-Control-Allow-Origin', '*')
43
- res.end(JSON.stringify(
44
- {
45
- componentDefinitions,
46
- availableColors: manifestWriter.getAvailableColors(),
47
- },
48
- null,
49
- 2,
50
- ))
43
+ const manifest: Record<string, unknown> = {
44
+ componentDefinitions,
45
+ availableColors: manifestWriter.getAvailableColors(),
46
+ availableTextStyles: manifestWriter.getAvailableTextStyles(),
47
+ }
48
+ const collectionDefs = manifestWriter.getCollectionDefinitions()
49
+ if (Object.keys(collectionDefs).length > 0) {
50
+ manifest.collectionDefinitions = collectionDefs
51
+ }
52
+ res.end(JSON.stringify(manifest, null, 2))
51
53
  return
52
54
  }
53
55
  next()
@@ -280,61 +280,10 @@ export async function processHtml(
280
280
  })
281
281
  }
282
282
 
283
- // Image detection pass: mark img elements for CMS image replacement
284
- // Store image entries separately to add to manifest later
285
- interface ImageEntry {
286
- metadata: ImageMetadata
287
- sourceFile?: string
288
- sourceLine?: number
289
- }
290
- const imageEntries = new Map<string, ImageEntry>()
291
- root.querySelectorAll('img').forEach((node) => {
292
- // Skip if already marked
293
- if (node.getAttribute(attributeName)) return
294
-
295
- const src = node.getAttribute('src')
296
- if (!src) return // Skip images without src
297
-
298
- const id = getNextId()
299
- node.setAttribute(attributeName, id)
300
- node.setAttribute('data-cms-img', 'true')
301
-
302
- // Try to get source location from the image itself or ancestors
303
- let sourceFile: string | undefined
304
- let sourceLine: number | undefined
305
- let current: HTMLNode | null = node
306
- while (current && !sourceFile) {
307
- const file = current.getAttribute?.('data-astro-source-file')
308
- const line = current.getAttribute?.('data-astro-source-loc') || current.getAttribute?.('data-astro-source-line')
309
- if (file) {
310
- sourceFile = file
311
- if (line) {
312
- const lineNum = parseInt(line.split(':')[0] ?? '1', 10)
313
- if (!Number.isNaN(lineNum)) {
314
- sourceLine = lineNum
315
- }
316
- }
317
- }
318
- current = current.parentNode as HTMLNode | null
319
- }
320
-
321
- // Build image metadata
322
- const metadata: ImageMetadata = {
323
- src,
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,
332
- sourceFile,
333
- sourceLine,
334
- })
335
- })
336
-
337
283
  // Collection wrapper detection pass: find the element that wraps markdown content
284
+ // This needs to run BEFORE image marking so we can skip images inside markdown
285
+ let markdownWrapperNode: HTMLNode | null = null
286
+
338
287
  // Two strategies:
339
288
  // 1. Dev mode: look for elements with data-astro-source-file containing children without it
340
289
  // 2. Build mode: find element whose first child content matches the start of markdown body
@@ -375,6 +324,7 @@ export async function processHtml(
375
324
  node.setAttribute(attributeName, id)
376
325
  node.setAttribute('data-cms-markdown', 'true')
377
326
  collectionWrapperId = id
327
+ markdownWrapperNode = node
378
328
  foundWrapper = true
379
329
  // Don't break - we want the deepest wrapper, so we'll overwrite
380
330
  }
@@ -436,6 +386,7 @@ export async function processHtml(
436
386
  best.node.setAttribute(attributeName, id)
437
387
  best.node.setAttribute('data-cms-markdown', 'true')
438
388
  collectionWrapperId = id
389
+ markdownWrapperNode = best.node
439
390
  foundWrapper = true
440
391
  }
441
392
  }
@@ -443,6 +394,75 @@ export async function processHtml(
443
394
  }
444
395
  }
445
396
 
397
+ // Helper function to check if a node is inside the markdown wrapper
398
+ const isInsideMarkdownWrapper = (node: HTMLNode): boolean => {
399
+ if (!markdownWrapperNode) return false
400
+ let current = node.parentNode as HTMLNode | null
401
+ while (current) {
402
+ if (current === markdownWrapperNode) return true
403
+ current = current.parentNode as HTMLNode | null
404
+ }
405
+ return false
406
+ }
407
+
408
+ // Image detection pass: mark img elements for CMS image replacement
409
+ // Store image entries separately to add to manifest later
410
+ // NOTE: Skip images inside markdown wrapper - they are edited via the markdown editor
411
+ interface ImageEntry {
412
+ metadata: ImageMetadata
413
+ sourceFile?: string
414
+ sourceLine?: number
415
+ }
416
+ const imageEntries = new Map<string, ImageEntry>()
417
+ root.querySelectorAll('img').forEach((node) => {
418
+ // Skip if already marked
419
+ if (node.getAttribute(attributeName)) return
420
+
421
+ // Skip images inside markdown wrapper - they are edited via the markdown editor
422
+ if (isInsideMarkdownWrapper(node)) return
423
+
424
+ const src = node.getAttribute('src')
425
+ if (!src) return // Skip images without src
426
+
427
+ const id = getNextId()
428
+ node.setAttribute(attributeName, id)
429
+ node.setAttribute('data-cms-img', 'true')
430
+
431
+ // Try to get source location from the image itself or ancestors
432
+ let sourceFile: string | undefined
433
+ let sourceLine: number | undefined
434
+ let current: HTMLNode | null = node
435
+ while (current && !sourceFile) {
436
+ const file = current.getAttribute?.('data-astro-source-file')
437
+ const line = current.getAttribute?.('data-astro-source-loc') || current.getAttribute?.('data-astro-source-line')
438
+ if (file) {
439
+ sourceFile = file
440
+ if (line) {
441
+ const lineNum = parseInt(line.split(':')[0] ?? '1', 10)
442
+ if (!Number.isNaN(lineNum)) {
443
+ sourceLine = lineNum
444
+ }
445
+ }
446
+ }
447
+ current = current.parentNode as HTMLNode | null
448
+ }
449
+
450
+ // Build image metadata
451
+ const metadata: ImageMetadata = {
452
+ src,
453
+ alt: node.getAttribute('alt') || '',
454
+ srcSet: node.getAttribute('srcset') || undefined,
455
+ sizes: node.getAttribute('sizes') || undefined,
456
+ }
457
+
458
+ // Store image info for manifest
459
+ imageEntries.set(id, {
460
+ metadata,
461
+ sourceFile,
462
+ sourceLine,
463
+ })
464
+ })
465
+
446
466
  // Third pass: assign IDs to all qualifying text elements and extract source locations
447
467
  root.querySelectorAll('*').forEach((node) => {
448
468
  const tag = node.tagName?.toLowerCase?.() ?? ''
@@ -451,6 +471,9 @@ export async function processHtml(
451
471
  if (includeTags && !includeTags.includes(tag)) return
452
472
  if (node.getAttribute(attributeName)) return // Already marked
453
473
 
474
+ // Skip elements inside markdown wrapper - they are edited via the markdown editor
475
+ if (isInsideMarkdownWrapper(node)) return
476
+
454
477
  // Skip inline text styling elements (strong, b, em, i, etc.)
455
478
  // These should be part of their parent's text content, not separately editable
456
479
  // Only apply when includeTags is null (all tags) - if specific tags are listed, respect them
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { AstroIntegration } from 'astro'
2
2
  import { processBuildOutput } from './build-processor'
3
+ import { scanCollections } from './collection-scanner'
3
4
  import { ComponentRegistry } from './component-registry'
4
5
  import { resetProjectRoot } from './config'
5
6
  import { createDevMiddleware } from './dev-middleware'
@@ -67,6 +68,15 @@ export default function cmsMarker(options: CmsMarkerOptions = {}): AstroIntegrat
67
68
  }
68
69
  }
69
70
 
71
+ // Scan content collections for schema inference
72
+ const collectionDefinitions = await scanCollections(contentDir)
73
+ manifestWriter.setCollectionDefinitions(collectionDefinitions)
74
+
75
+ const collectionCount = Object.keys(collectionDefinitions).length
76
+ if (collectionCount > 0) {
77
+ logger.info(`Found ${collectionCount} content collection(s)`)
78
+ }
79
+
70
80
  // Create Vite plugin context
71
81
  const pluginContext = {
72
82
  manifestWriter,
@@ -110,6 +120,8 @@ export default function cmsMarker(options: CmsMarkerOptions = {}): AstroIntegrat
110
120
 
111
121
  // Re-export config functions for testing
112
122
  export { getProjectRoot, resetProjectRoot, setProjectRoot } from './config'
123
+ // Re-export collection scanner
124
+ export { scanCollections } from './collection-scanner'
113
125
  // Re-export types for consumers
114
126
  export type { CollectionInfo, MarkdownContent, SourceLocation, VariableReference } from './source-finder'
115
127
  export { findCollectionSource, parseMarkdownContent } from './source-finder'