@nuasite/cms-marker 0.0.66 → 0.0.69
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.
- package/dist/types/collection-scanner.d.ts +6 -0
- package/dist/types/collection-scanner.d.ts.map +1 -0
- package/dist/types/dev-middleware.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/manifest-writer.d.ts +10 -1
- package/dist/types/manifest-writer.d.ts.map +1 -1
- package/dist/types/tailwind-colors.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/dist/types/types.d.ts +42 -0
- package/dist/types/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/collection-scanner.ts +325 -0
- package/src/dev-middleware.ts +11 -9
- package/src/index.ts +12 -0
- package/src/manifest-writer.ts +24 -1
- package/src/tailwind-colors.ts +1 -0
- package/src/types.ts +56 -0
package/dist/types/types.d.ts
CHANGED
|
@@ -87,6 +87,8 @@ export interface ColorClasses {
|
|
|
87
87
|
hoverBg?: string;
|
|
88
88
|
/** Hover text color class (e.g., 'hover:text-gray-100') */
|
|
89
89
|
hoverText?: string;
|
|
90
|
+
/** Hover border color class (e.g., 'hover:border-blue-700') */
|
|
91
|
+
hoverBorder?: string;
|
|
90
92
|
/** All color-related classes as found in the element */
|
|
91
93
|
allColorClasses?: string[];
|
|
92
94
|
}
|
|
@@ -182,6 +184,44 @@ export interface CollectionEntry {
|
|
|
182
184
|
/** ID of the wrapper element containing the rendered markdown */
|
|
183
185
|
wrapperId?: string;
|
|
184
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
|
+
}
|
|
185
225
|
/** Manifest metadata for versioning and conflict detection */
|
|
186
226
|
export interface ManifestMetadata {
|
|
187
227
|
/** Manifest schema version */
|
|
@@ -205,6 +245,8 @@ export interface CmsManifest {
|
|
|
205
245
|
componentDefinitions: Record<string, ComponentDefinition>;
|
|
206
246
|
/** Content collection entries indexed by "collectionName/slug" */
|
|
207
247
|
collections?: Record<string, CollectionEntry>;
|
|
248
|
+
/** Collection definitions with inferred schemas */
|
|
249
|
+
collectionDefinitions?: Record<string, CollectionDefinition>;
|
|
208
250
|
/** Available Tailwind colors from the project's config */
|
|
209
251
|
availableColors?: AvailableColors;
|
|
210
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
|
@@ -0,0 +1,325 @@
|
|
|
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 && 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 (!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 (IMAGE_EXTENSIONS.test(value) || key.toLowerCase().includes('image') || key.toLowerCase().includes('thumbnail') || key.toLowerCase().includes('cover')) {
|
|
140
|
+
return 'image'
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Check for URLs
|
|
144
|
+
if (URL_PATTERN.test(value)) {
|
|
145
|
+
return 'url'
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Check for textarea (long text or contains newlines)
|
|
149
|
+
if (value.includes('\n') || value.length > TEXTAREA_MIN_LENGTH) {
|
|
150
|
+
return 'textarea'
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return 'text'
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return 'text'
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Merge field observations from multiple files to determine final field definition
|
|
161
|
+
*/
|
|
162
|
+
function mergeFieldObservations(observations: FieldObservation[]): FieldDefinition[] {
|
|
163
|
+
const fields: FieldDefinition[] = []
|
|
164
|
+
|
|
165
|
+
for (const obs of observations) {
|
|
166
|
+
const nonNullValues = obs.values.filter(v => v !== null && v !== undefined)
|
|
167
|
+
if (nonNullValues.length === 0) continue
|
|
168
|
+
|
|
169
|
+
// Determine type by consensus (most common inferred type)
|
|
170
|
+
const typeCounts = new Map<FieldType, number>()
|
|
171
|
+
for (const value of nonNullValues) {
|
|
172
|
+
const type = inferFieldType(value, obs.name)
|
|
173
|
+
typeCounts.set(type, (typeCounts.get(type) || 0) + 1)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Get most common type
|
|
177
|
+
let fieldType: FieldType = 'text'
|
|
178
|
+
let maxCount = 0
|
|
179
|
+
for (const [type, count] of typeCounts) {
|
|
180
|
+
if (count > maxCount) {
|
|
181
|
+
maxCount = count
|
|
182
|
+
fieldType = type
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const field: FieldDefinition = {
|
|
187
|
+
name: obs.name,
|
|
188
|
+
type: fieldType,
|
|
189
|
+
required: obs.presentCount === obs.totalEntries,
|
|
190
|
+
examples: nonNullValues.slice(0, 3),
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// For text fields, check if we should treat as select (limited unique values)
|
|
194
|
+
if (fieldType === 'text') {
|
|
195
|
+
const uniqueValues = [...new Set(nonNullValues.map(v => String(v)))]
|
|
196
|
+
if (uniqueValues.length > 0 && uniqueValues.length <= MAX_SELECT_OPTIONS && nonNullValues.length >= 2) {
|
|
197
|
+
field.type = 'select'
|
|
198
|
+
field.options = uniqueValues.sort()
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// For arrays, try to infer item type
|
|
203
|
+
if (fieldType === 'array') {
|
|
204
|
+
const allItems = nonNullValues.flatMap(v => (Array.isArray(v) ? v : []))
|
|
205
|
+
if (allItems.length > 0) {
|
|
206
|
+
const itemType = inferFieldType(allItems[0], obs.name)
|
|
207
|
+
field.itemType = itemType
|
|
208
|
+
|
|
209
|
+
// Check if array items should be select
|
|
210
|
+
if (itemType === 'text') {
|
|
211
|
+
const uniqueItems = [...new Set(allItems.map(v => String(v)))]
|
|
212
|
+
if (uniqueItems.length <= MAX_SELECT_OPTIONS * 2) {
|
|
213
|
+
field.options = uniqueItems.sort()
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
fields.push(field)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return fields
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Scan a single collection directory and infer its schema
|
|
227
|
+
*/
|
|
228
|
+
async function scanCollection(collectionPath: string, collectionName: string): Promise<CollectionDefinition | null> {
|
|
229
|
+
try {
|
|
230
|
+
const entries = await fs.readdir(collectionPath, { withFileTypes: true })
|
|
231
|
+
const markdownFiles = entries.filter(e => e.isFile() && (e.name.endsWith('.md') || e.name.endsWith('.mdx')))
|
|
232
|
+
|
|
233
|
+
if (markdownFiles.length === 0) return null
|
|
234
|
+
|
|
235
|
+
// Determine file extension (prefer md, use mdx if that's all we have)
|
|
236
|
+
const hasMd = markdownFiles.some(f => f.name.endsWith('.md'))
|
|
237
|
+
const fileExtension: 'md' | 'mdx' = hasMd ? 'md' : 'mdx'
|
|
238
|
+
|
|
239
|
+
// Collect field observations across all files
|
|
240
|
+
const fieldMap = new Map<string, FieldObservation>()
|
|
241
|
+
let hasDraft = false
|
|
242
|
+
|
|
243
|
+
for (const file of markdownFiles) {
|
|
244
|
+
const filePath = path.join(collectionPath, file.name)
|
|
245
|
+
const content = await fs.readFile(filePath, 'utf-8')
|
|
246
|
+
const frontmatter = parseFrontmatter(content)
|
|
247
|
+
|
|
248
|
+
if (!frontmatter) continue
|
|
249
|
+
|
|
250
|
+
for (const [key, value] of Object.entries(frontmatter)) {
|
|
251
|
+
if (key === 'draft' && typeof value === 'boolean') {
|
|
252
|
+
hasDraft = true
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
let obs = fieldMap.get(key)
|
|
256
|
+
if (!obs) {
|
|
257
|
+
obs = {
|
|
258
|
+
name: key,
|
|
259
|
+
values: [],
|
|
260
|
+
presentCount: 0,
|
|
261
|
+
totalEntries: markdownFiles.length,
|
|
262
|
+
}
|
|
263
|
+
fieldMap.set(key, obs)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
obs.values.push(value)
|
|
267
|
+
obs.presentCount++
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Update totalEntries for all observations
|
|
272
|
+
for (const obs of fieldMap.values()) {
|
|
273
|
+
obs.totalEntries = markdownFiles.length
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const fields = mergeFieldObservations(Array.from(fieldMap.values()))
|
|
277
|
+
|
|
278
|
+
// Generate a human-readable label
|
|
279
|
+
const label = collectionName
|
|
280
|
+
.replace(/[-_]/g, ' ')
|
|
281
|
+
.replace(/\b\w/g, c => c.toUpperCase())
|
|
282
|
+
|
|
283
|
+
return {
|
|
284
|
+
name: collectionName,
|
|
285
|
+
label,
|
|
286
|
+
path: collectionPath,
|
|
287
|
+
entryCount: markdownFiles.length,
|
|
288
|
+
fields,
|
|
289
|
+
supportsDraft: hasDraft,
|
|
290
|
+
fileExtension,
|
|
291
|
+
}
|
|
292
|
+
} catch {
|
|
293
|
+
return null
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Scan all collections in the content directory
|
|
299
|
+
*/
|
|
300
|
+
export async function scanCollections(contentDir: string = 'src/content'): Promise<Record<string, CollectionDefinition>> {
|
|
301
|
+
const projectRoot = getProjectRoot()
|
|
302
|
+
const fullContentDir = path.isAbsolute(contentDir) ? contentDir : path.join(projectRoot, contentDir)
|
|
303
|
+
|
|
304
|
+
const collections: Record<string, CollectionDefinition> = {}
|
|
305
|
+
|
|
306
|
+
try {
|
|
307
|
+
const entries = await fs.readdir(fullContentDir, { withFileTypes: true })
|
|
308
|
+
|
|
309
|
+
const scanPromises = entries
|
|
310
|
+
.filter(entry => entry.isDirectory() && !entry.name.startsWith('_') && !entry.name.startsWith('.'))
|
|
311
|
+
.map(async entry => {
|
|
312
|
+
const collectionPath = path.join(fullContentDir, entry.name)
|
|
313
|
+
const definition = await scanCollection(collectionPath, entry.name)
|
|
314
|
+
if (definition) {
|
|
315
|
+
collections[entry.name] = definition
|
|
316
|
+
}
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
await Promise.all(scanPromises)
|
|
320
|
+
} catch {
|
|
321
|
+
// Content directory doesn't exist or isn't readable
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return collections
|
|
325
|
+
}
|
package/src/dev-middleware.ts
CHANGED
|
@@ -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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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()
|
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'
|
package/src/manifest-writer.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
AvailableColors,
|
|
7
7
|
AvailableTextStyles,
|
|
8
8
|
CmsManifest,
|
|
9
|
+
CollectionDefinition,
|
|
9
10
|
CollectionEntry,
|
|
10
11
|
ComponentDefinition,
|
|
11
12
|
ComponentInstance,
|
|
@@ -31,6 +32,7 @@ export class ManifestWriter {
|
|
|
31
32
|
private outDir: string = ''
|
|
32
33
|
private manifestFile: string
|
|
33
34
|
private componentDefinitions: Record<string, ComponentDefinition>
|
|
35
|
+
private collectionDefinitions: Record<string, CollectionDefinition> = {}
|
|
34
36
|
private availableColors: AvailableColors | undefined
|
|
35
37
|
private availableTextStyles: AvailableTextStyles | undefined
|
|
36
38
|
private writeQueue: Promise<void> = Promise.resolve()
|
|
@@ -43,6 +45,7 @@ export class ManifestWriter {
|
|
|
43
45
|
components: {},
|
|
44
46
|
componentDefinitions,
|
|
45
47
|
collections: {},
|
|
48
|
+
collectionDefinitions: {},
|
|
46
49
|
}
|
|
47
50
|
}
|
|
48
51
|
|
|
@@ -87,6 +90,21 @@ export class ManifestWriter {
|
|
|
87
90
|
this.globalManifest.availableTextStyles = textStyles
|
|
88
91
|
}
|
|
89
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Set collection definitions (inferred schemas for content collections)
|
|
95
|
+
*/
|
|
96
|
+
setCollectionDefinitions(definitions: Record<string, CollectionDefinition>): void {
|
|
97
|
+
this.collectionDefinitions = definitions
|
|
98
|
+
this.globalManifest.collectionDefinitions = definitions
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Get collection definitions
|
|
103
|
+
*/
|
|
104
|
+
getCollectionDefinitions(): Record<string, CollectionDefinition> {
|
|
105
|
+
return this.collectionDefinitions
|
|
106
|
+
}
|
|
107
|
+
|
|
90
108
|
/**
|
|
91
109
|
* Get the manifest path for a given page
|
|
92
110
|
* Places manifest next to the page: /about -> /about.json, / -> /index.json
|
|
@@ -182,16 +200,20 @@ export class ManifestWriter {
|
|
|
182
200
|
// Wait for all queued writes to complete
|
|
183
201
|
await this.writeQueue
|
|
184
202
|
|
|
185
|
-
// Write global manifest with settings (component definitions, colors, and
|
|
203
|
+
// Write global manifest with settings (component definitions, colors, text styles, and collection definitions)
|
|
186
204
|
if (this.outDir) {
|
|
187
205
|
const globalManifestPath = path.join(this.outDir, this.manifestFile)
|
|
188
206
|
const globalSettings: {
|
|
189
207
|
componentDefinitions: Record<string, ComponentDefinition>
|
|
208
|
+
collectionDefinitions?: Record<string, CollectionDefinition>
|
|
190
209
|
availableColors?: AvailableColors
|
|
191
210
|
availableTextStyles?: AvailableTextStyles
|
|
192
211
|
} = {
|
|
193
212
|
componentDefinitions: this.componentDefinitions,
|
|
194
213
|
}
|
|
214
|
+
if (Object.keys(this.collectionDefinitions).length > 0) {
|
|
215
|
+
globalSettings.collectionDefinitions = this.collectionDefinitions
|
|
216
|
+
}
|
|
195
217
|
if (this.availableColors) {
|
|
196
218
|
globalSettings.availableColors = this.availableColors
|
|
197
219
|
}
|
|
@@ -240,6 +262,7 @@ export class ManifestWriter {
|
|
|
240
262
|
components: {},
|
|
241
263
|
componentDefinitions: this.componentDefinitions,
|
|
242
264
|
collections: {},
|
|
265
|
+
collectionDefinitions: this.collectionDefinitions,
|
|
243
266
|
availableColors: this.availableColors,
|
|
244
267
|
availableTextStyles: this.availableTextStyles,
|
|
245
268
|
}
|
package/src/tailwind-colors.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -91,6 +91,8 @@ export interface ColorClasses {
|
|
|
91
91
|
hoverBg?: string
|
|
92
92
|
/** Hover text color class (e.g., 'hover:text-gray-100') */
|
|
93
93
|
hoverText?: string
|
|
94
|
+
/** Hover border color class (e.g., 'hover:border-blue-700') */
|
|
95
|
+
hoverBorder?: string
|
|
94
96
|
/** All color-related classes as found in the element */
|
|
95
97
|
allColorClasses?: string[]
|
|
96
98
|
}
|
|
@@ -193,6 +195,58 @@ export interface CollectionEntry {
|
|
|
193
195
|
wrapperId?: string
|
|
194
196
|
}
|
|
195
197
|
|
|
198
|
+
/** Field types for collection schema inference */
|
|
199
|
+
export type FieldType =
|
|
200
|
+
| 'text'
|
|
201
|
+
| 'textarea'
|
|
202
|
+
| 'date'
|
|
203
|
+
| 'boolean'
|
|
204
|
+
| 'number'
|
|
205
|
+
| 'image'
|
|
206
|
+
| 'url'
|
|
207
|
+
| 'select'
|
|
208
|
+
| 'array'
|
|
209
|
+
| 'object'
|
|
210
|
+
| 'reference'
|
|
211
|
+
|
|
212
|
+
/** Definition of a single field in a collection's schema */
|
|
213
|
+
export interface FieldDefinition {
|
|
214
|
+
/** Field name as it appears in frontmatter */
|
|
215
|
+
name: string
|
|
216
|
+
/** Inferred or specified field type */
|
|
217
|
+
type: FieldType
|
|
218
|
+
/** Whether the field is required (present in all entries) */
|
|
219
|
+
required: boolean
|
|
220
|
+
/** Default value for the field */
|
|
221
|
+
defaultValue?: unknown
|
|
222
|
+
/** Options for 'select' type fields */
|
|
223
|
+
options?: string[]
|
|
224
|
+
/** Item type for 'array' fields */
|
|
225
|
+
itemType?: FieldType
|
|
226
|
+
/** Nested fields for 'object' type */
|
|
227
|
+
fields?: FieldDefinition[]
|
|
228
|
+
/** Sample values seen across entries */
|
|
229
|
+
examples?: unknown[]
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** Definition of a content collection with inferred schema */
|
|
233
|
+
export interface CollectionDefinition {
|
|
234
|
+
/** Collection identifier (directory name) */
|
|
235
|
+
name: string
|
|
236
|
+
/** Human-readable label for the collection */
|
|
237
|
+
label: string
|
|
238
|
+
/** Path to the collection directory */
|
|
239
|
+
path: string
|
|
240
|
+
/** Number of entries in the collection */
|
|
241
|
+
entryCount: number
|
|
242
|
+
/** Inferred field definitions */
|
|
243
|
+
fields: FieldDefinition[]
|
|
244
|
+
/** Whether the collection has draft support */
|
|
245
|
+
supportsDraft?: boolean
|
|
246
|
+
/** File extension used by entries */
|
|
247
|
+
fileExtension: 'md' | 'mdx'
|
|
248
|
+
}
|
|
249
|
+
|
|
196
250
|
/** Manifest metadata for versioning and conflict detection */
|
|
197
251
|
export interface ManifestMetadata {
|
|
198
252
|
/** Manifest schema version */
|
|
@@ -217,6 +271,8 @@ export interface CmsManifest {
|
|
|
217
271
|
componentDefinitions: Record<string, ComponentDefinition>
|
|
218
272
|
/** Content collection entries indexed by "collectionName/slug" */
|
|
219
273
|
collections?: Record<string, CollectionEntry>
|
|
274
|
+
/** Collection definitions with inferred schemas */
|
|
275
|
+
collectionDefinitions?: Record<string, CollectionDefinition>
|
|
220
276
|
/** Available Tailwind colors from the project's config */
|
|
221
277
|
availableColors?: AvailableColors
|
|
222
278
|
/** Available text styles from the project's Tailwind config */
|