@nuasite/cms-marker 0.0.53 → 0.0.55
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/README.md +0 -4
- package/dist/types/astro-transform.d.ts +21 -0
- package/dist/types/astro-transform.d.ts.map +1 -0
- package/dist/types/build-processor.d.ts +10 -0
- package/dist/types/build-processor.d.ts.map +1 -0
- package/dist/types/component-registry.d.ts +63 -0
- package/dist/types/component-registry.d.ts.map +1 -0
- package/dist/types/dev-middleware.d.ts +7 -0
- package/dist/types/dev-middleware.d.ts.map +1 -0
- package/dist/types/html-processor.d.ts +51 -0
- package/dist/types/html-processor.d.ts.map +1 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/manifest-writer.d.ts +75 -0
- package/dist/types/manifest-writer.d.ts.map +1 -0
- package/dist/types/source-finder.d.ts +97 -0
- package/dist/types/source-finder.d.ts.map +1 -0
- package/dist/types/tailwind-colors.d.ts +66 -0
- package/dist/types/tailwind-colors.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -0
- package/dist/types/types.d.ts +195 -0
- package/dist/types/types.d.ts.map +1 -0
- package/dist/types/utils.d.ts +38 -0
- package/dist/types/utils.d.ts.map +1 -0
- package/dist/types/vite-plugin.d.ts +14 -0
- package/dist/types/vite-plugin.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/astro-transform.ts +4 -4
- package/src/build-processor.ts +1 -1
- package/src/component-registry.ts +2 -2
- package/src/dev-middleware.ts +5 -5
- package/src/html-processor.ts +180 -6
- package/src/index.ts +0 -1
- package/src/manifest-writer.ts +47 -1
- package/src/source-finder.ts +117 -6
- package/src/tailwind-colors.ts +338 -0
- package/src/tsconfig.json +1 -1
- package/src/types.ts +123 -1
- package/src/utils.ts +99 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ManifestEntry, SourceContext } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Generate a SHA256 hash of the given content
|
|
4
|
+
*/
|
|
5
|
+
export declare function sha256(content: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* Generate a short hash (first 12 characters of SHA256)
|
|
8
|
+
* Used for stableId to keep it reasonably short but still unique
|
|
9
|
+
*/
|
|
10
|
+
export declare function shortHash(content: string): string;
|
|
11
|
+
/**
|
|
12
|
+
* Generate a stable ID for an element based on its content and context.
|
|
13
|
+
* This ID survives rebuilds as long as the content and structure remain similar.
|
|
14
|
+
*
|
|
15
|
+
* Components:
|
|
16
|
+
* - tag name
|
|
17
|
+
* - first 50 chars of text content
|
|
18
|
+
* - source path (if available)
|
|
19
|
+
* - parent tag
|
|
20
|
+
* - sibling index
|
|
21
|
+
*/
|
|
22
|
+
export declare function generateStableId(tag: string, text: string, sourcePath?: string, context?: SourceContext): string;
|
|
23
|
+
/**
|
|
24
|
+
* Generate a hash of the source snippet for conflict detection.
|
|
25
|
+
* If the source file changes, this hash will differ from what's stored in manifest.
|
|
26
|
+
*/
|
|
27
|
+
export declare function generateSourceHash(sourceSnippet: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* Generate a content hash for the entire manifest (all entries).
|
|
30
|
+
* Used for quick drift detection without comparing individual entries.
|
|
31
|
+
*/
|
|
32
|
+
export declare function generateManifestContentHash(entries: Record<string, ManifestEntry>): string;
|
|
33
|
+
/**
|
|
34
|
+
* Generate per-source-file hashes for granular conflict detection.
|
|
35
|
+
* Maps source file path -> hash of all entries from that file.
|
|
36
|
+
*/
|
|
37
|
+
export declare function generateSourceFileHashes(entries: Record<string, ManifestEntry>): Record<string, string>;
|
|
38
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAE3D;;GAEG;AACH,wBAAgB,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAC/B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,UAAU,CAAC,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,aAAa,GACrB,MAAM,CAUR;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAEhE;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,GAAG,MAAM,CAU1F;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAwBvG"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Plugin } from 'vite';
|
|
2
|
+
import type { ManifestWriter } from './manifest-writer';
|
|
3
|
+
import type { CmsMarkerOptions, ComponentDefinition } from './types';
|
|
4
|
+
export interface VitePluginContext {
|
|
5
|
+
manifestWriter: ManifestWriter;
|
|
6
|
+
componentDefinitions: Record<string, ComponentDefinition>;
|
|
7
|
+
config: Required<CmsMarkerOptions>;
|
|
8
|
+
idCounter: {
|
|
9
|
+
value: number;
|
|
10
|
+
};
|
|
11
|
+
command: 'dev' | 'build' | 'preview' | 'sync';
|
|
12
|
+
}
|
|
13
|
+
export declare function createVitePlugin(context: VitePluginContext): Plugin[];
|
|
14
|
+
//# sourceMappingURL=vite-plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vite-plugin.d.ts","sourceRoot":"","sources":["../../src/vite-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAElC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AACvD,OAAO,KAAK,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAA;AAEpE,MAAM,WAAW,iBAAiB;IACjC,cAAc,EAAE,cAAc,CAAA;IAC9B,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAA;IACzD,MAAM,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAA;IAClC,SAAS,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;IAC5B,OAAO,EAAE,KAAK,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAA;CAC7C;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,MAAM,EAAE,CAoCrE"}
|
package/package.json
CHANGED
package/src/astro-transform.ts
CHANGED
|
@@ -182,8 +182,8 @@ function injectSourceAttributes(code: string, ast: any, filePath: string): strin
|
|
|
182
182
|
// Validate line exists - if not, there's a bug in AST positions or bounds checking
|
|
183
183
|
if (line === undefined) {
|
|
184
184
|
console.error(
|
|
185
|
-
`[astro-cms-marker] Invalid modification at line ${mod.line + 1} in ${filePath}. `
|
|
186
|
-
|
|
185
|
+
`[astro-cms-marker] Invalid modification at line ${mod.line + 1} in ${filePath}. `
|
|
186
|
+
+ `This indicates a bug in @astrojs/compiler AST positions or bounds checking. Skipping modification.`,
|
|
187
187
|
)
|
|
188
188
|
continue
|
|
189
189
|
}
|
|
@@ -191,8 +191,8 @@ function injectSourceAttributes(code: string, ast: any, filePath: string): strin
|
|
|
191
191
|
// Validate column is within line bounds
|
|
192
192
|
if (mod.column < 0 || mod.column > line.length) {
|
|
193
193
|
console.error(
|
|
194
|
-
`[astro-cms-marker] Invalid column ${mod.column} at line ${mod.line + 1} in ${filePath}. `
|
|
195
|
-
|
|
194
|
+
`[astro-cms-marker] Invalid column ${mod.column} at line ${mod.line + 1} in ${filePath}. `
|
|
195
|
+
+ `Line length is ${line.length}. Skipping modification.`,
|
|
196
196
|
)
|
|
197
197
|
continue
|
|
198
198
|
}
|
package/src/build-processor.ts
CHANGED
|
@@ -86,7 +86,7 @@ async function processFile(
|
|
|
86
86
|
skipMarkdownContent: isCollectionPage,
|
|
87
87
|
// Pass collection info for wrapper element marking
|
|
88
88
|
collectionInfo: collectionInfo
|
|
89
|
-
? { name: collectionInfo.name, slug: collectionInfo.slug, bodyFirstLine }
|
|
89
|
+
? { name: collectionInfo.name, slug: collectionInfo.slug, bodyFirstLine, contentPath: collectionInfo.file }
|
|
90
90
|
: undefined,
|
|
91
91
|
},
|
|
92
92
|
idGenerator,
|
package/src/dev-middleware.ts
CHANGED
|
@@ -90,14 +90,14 @@ export function createDevMiddleware(
|
|
|
90
90
|
const requestUrl = req.url || 'unknown'
|
|
91
91
|
|
|
92
92
|
// Intercept response chunks
|
|
93
|
-
res.write =
|
|
93
|
+
res.write = ((chunk: any, ...args: any[]) => {
|
|
94
94
|
if (chunk) {
|
|
95
95
|
chunks.push(Buffer.from(chunk))
|
|
96
96
|
}
|
|
97
97
|
return true
|
|
98
|
-
} as any
|
|
98
|
+
}) as any
|
|
99
99
|
|
|
100
|
-
res.end =
|
|
100
|
+
res.end = ((chunk: any, ...args: any[]) => {
|
|
101
101
|
if (chunk) {
|
|
102
102
|
chunks.push(Buffer.from(chunk))
|
|
103
103
|
}
|
|
@@ -143,7 +143,7 @@ export function createDevMiddleware(
|
|
|
143
143
|
return res.end(Buffer.concat(chunks), ...args)
|
|
144
144
|
}
|
|
145
145
|
return res.end(...args)
|
|
146
|
-
} as any
|
|
146
|
+
}) as any
|
|
147
147
|
|
|
148
148
|
next()
|
|
149
149
|
})
|
|
@@ -191,7 +191,7 @@ async function processHtmlForDev(
|
|
|
191
191
|
skipMarkdownContent: isCollectionPage,
|
|
192
192
|
// Pass collection info for wrapper element marking
|
|
193
193
|
collectionInfo: collectionInfo
|
|
194
|
-
? { name: collectionInfo.name, slug: collectionInfo.slug, bodyFirstLine }
|
|
194
|
+
? { name: collectionInfo.name, slug: collectionInfo.slug, bodyFirstLine, contentPath: collectionInfo.file }
|
|
195
195
|
: undefined,
|
|
196
196
|
},
|
|
197
197
|
idGenerator,
|
package/src/html-processor.ts
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
import { parse } from 'node-html-parser'
|
|
2
|
-
import
|
|
2
|
+
import { enhanceManifestWithSourceSnippets } from './source-finder'
|
|
3
|
+
import { extractColorClasses } from './tailwind-colors'
|
|
4
|
+
import type { ComponentInstance, ManifestEntry, SourceContext } from './types'
|
|
5
|
+
import { generateStableId } from './utils'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Inline text styling elements that should NOT be marked with CMS IDs.
|
|
9
|
+
* These elements are text formatting and should be part of their parent's content.
|
|
10
|
+
* They will be preserved as HTML when editing the parent element.
|
|
11
|
+
*/
|
|
12
|
+
export const INLINE_STYLE_TAGS = [
|
|
13
|
+
'strong',
|
|
14
|
+
'b',
|
|
15
|
+
'em',
|
|
16
|
+
'i',
|
|
17
|
+
'u',
|
|
18
|
+
's',
|
|
19
|
+
'strike',
|
|
20
|
+
'del',
|
|
21
|
+
'ins',
|
|
22
|
+
'mark',
|
|
23
|
+
'small',
|
|
24
|
+
'sub',
|
|
25
|
+
'sup',
|
|
26
|
+
'abbr',
|
|
27
|
+
'cite',
|
|
28
|
+
'code',
|
|
29
|
+
'kbd',
|
|
30
|
+
'samp',
|
|
31
|
+
'var',
|
|
32
|
+
'time',
|
|
33
|
+
'dfn',
|
|
34
|
+
'q',
|
|
35
|
+
] as const
|
|
3
36
|
|
|
4
37
|
export interface ProcessHtmlOptions {
|
|
5
38
|
attributeName: string
|
|
@@ -13,12 +46,20 @@ export interface ProcessHtmlOptions {
|
|
|
13
46
|
markStyledSpans?: boolean
|
|
14
47
|
/** When true, only mark elements that have source file attributes (from Astro templates) */
|
|
15
48
|
skipMarkdownContent?: boolean
|
|
49
|
+
/**
|
|
50
|
+
* When true, skip marking inline text styling elements (strong, b, em, i, etc.).
|
|
51
|
+
* These elements will be preserved as part of their parent's HTML content.
|
|
52
|
+
* Defaults to true.
|
|
53
|
+
*/
|
|
54
|
+
skipInlineStyleTags?: boolean
|
|
16
55
|
/** Collection info for marking the wrapper element containing markdown content */
|
|
17
56
|
collectionInfo?: {
|
|
18
57
|
name: string
|
|
19
58
|
slug: string
|
|
20
59
|
/** First line of the markdown body (used to find wrapper element in build mode) */
|
|
21
60
|
bodyFirstLine?: string
|
|
61
|
+
/** Path to the markdown file (e.g., 'src/content/blog/my-post.md') */
|
|
62
|
+
contentPath?: string
|
|
22
63
|
}
|
|
23
64
|
}
|
|
24
65
|
|
|
@@ -128,6 +169,7 @@ export async function processHtml(
|
|
|
128
169
|
excludeComponentDirs = ['src/pages', 'src/layouts', 'src/layout'],
|
|
129
170
|
markStyledSpans = true,
|
|
130
171
|
skipMarkdownContent = false,
|
|
172
|
+
skipInlineStyleTags = true,
|
|
131
173
|
collectionInfo,
|
|
132
174
|
} = options
|
|
133
175
|
|
|
@@ -235,6 +277,27 @@ export async function processHtml(
|
|
|
235
277
|
})
|
|
236
278
|
}
|
|
237
279
|
|
|
280
|
+
// Image detection pass: mark img elements for CMS image replacement
|
|
281
|
+
// Store image entries separately to add to manifest later
|
|
282
|
+
const imageEntries = new Map<string, { src: string; alt: string }>()
|
|
283
|
+
root.querySelectorAll('img').forEach((node) => {
|
|
284
|
+
// Skip if already marked
|
|
285
|
+
if (node.getAttribute(attributeName)) return
|
|
286
|
+
|
|
287
|
+
const src = node.getAttribute('src')
|
|
288
|
+
if (!src) return // Skip images without src
|
|
289
|
+
|
|
290
|
+
const id = getNextId()
|
|
291
|
+
node.setAttribute(attributeName, id)
|
|
292
|
+
node.setAttribute('data-cms-img', 'true')
|
|
293
|
+
|
|
294
|
+
// Store image info for manifest
|
|
295
|
+
imageEntries.set(id, {
|
|
296
|
+
src,
|
|
297
|
+
alt: node.getAttribute('alt') || '',
|
|
298
|
+
})
|
|
299
|
+
})
|
|
300
|
+
|
|
238
301
|
// Collection wrapper detection pass: find the element that wraps markdown content
|
|
239
302
|
// Two strategies:
|
|
240
303
|
// 1. Dev mode: look for elements with data-astro-source-file containing children without it
|
|
@@ -274,6 +337,7 @@ export async function processHtml(
|
|
|
274
337
|
// Mark this as the collection wrapper using the standard attribute
|
|
275
338
|
const id = getNextId()
|
|
276
339
|
node.setAttribute(attributeName, id)
|
|
340
|
+
node.setAttribute('data-cms-markdown', 'true')
|
|
277
341
|
collectionWrapperId = id
|
|
278
342
|
foundWrapper = true
|
|
279
343
|
// Don't break - we want the deepest wrapper, so we'll overwrite
|
|
@@ -333,6 +397,7 @@ export async function processHtml(
|
|
|
333
397
|
// Markdown body should have at least 2 block children
|
|
334
398
|
const id = getNextId()
|
|
335
399
|
best.node.setAttribute(attributeName, id)
|
|
400
|
+
best.node.setAttribute('data-cms-markdown', 'true')
|
|
336
401
|
collectionWrapperId = id
|
|
337
402
|
foundWrapper = true
|
|
338
403
|
}
|
|
@@ -349,6 +414,23 @@ export async function processHtml(
|
|
|
349
414
|
if (includeTags && !includeTags.includes(tag)) return
|
|
350
415
|
if (node.getAttribute(attributeName)) return // Already marked
|
|
351
416
|
|
|
417
|
+
// Skip inline text styling elements (strong, b, em, i, etc.)
|
|
418
|
+
// These should be part of their parent's text content, not separately editable
|
|
419
|
+
// Only apply when includeTags is null (all tags) - if specific tags are listed, respect them
|
|
420
|
+
if (skipInlineStyleTags && includeTags === null && INLINE_STYLE_TAGS.includes(tag as typeof INLINE_STYLE_TAGS[number])) {
|
|
421
|
+
return
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Skip styled spans (spans with only text styling Tailwind classes)
|
|
425
|
+
// These are also inline text formatting and should be part of parent content
|
|
426
|
+
// Only apply when includeTags is null or doesn't include 'span'
|
|
427
|
+
if (skipInlineStyleTags && (includeTags === null || !includeTags.includes('span')) && tag === 'span') {
|
|
428
|
+
const classAttr = node.getAttribute('class')
|
|
429
|
+
if (classAttr && hasOnlyTextStyleClasses(classAttr)) {
|
|
430
|
+
return
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
352
434
|
const textContent = (node.innerText ?? '').trim()
|
|
353
435
|
if (!includeEmptyText && !textContent) return
|
|
354
436
|
|
|
@@ -369,7 +451,7 @@ export async function processHtml(
|
|
|
369
451
|
|
|
370
452
|
if (sourceFile && sourceLine) {
|
|
371
453
|
const lineNum = parseInt(sourceLine.split(':')[0] ?? '1', 10)
|
|
372
|
-
if (!isNaN(lineNum)) {
|
|
454
|
+
if (!Number.isNaN(lineNum)) {
|
|
373
455
|
sourceLocationMap.set(id, { file: sourceFile, line: lineNum })
|
|
374
456
|
}
|
|
375
457
|
// Only remove source attributes if this is NOT a component root
|
|
@@ -447,20 +529,54 @@ export async function processHtml(
|
|
|
447
529
|
parent = parent.parentNode
|
|
448
530
|
}
|
|
449
531
|
|
|
532
|
+
// Extract source context for resilient matching
|
|
533
|
+
const sourceContext = extractSourceContext(node, attributeName)
|
|
534
|
+
|
|
535
|
+
// Check if element contains inline style elements (strong, b, em, etc.) or styled spans
|
|
536
|
+
// If so, store the HTML content for source file updates
|
|
537
|
+
const inlineStyleSelector = INLINE_STYLE_TAGS.join(', ')
|
|
538
|
+
const hasInlineStyleElements = node.querySelector(inlineStyleSelector) !== null
|
|
539
|
+
const hasStyledSpans = node.querySelector('[data-cms-styled]') !== null
|
|
540
|
+
const htmlContent = (hasInlineStyleElements || hasStyledSpans) ? node.innerHTML : undefined
|
|
541
|
+
|
|
542
|
+
// Check if this is an image entry
|
|
543
|
+
const imageInfo = imageEntries.get(id)
|
|
544
|
+
const isImage = !!imageInfo
|
|
545
|
+
|
|
546
|
+
const entryText = isImage ? (imageInfo.alt || imageInfo.src) : textWithPlaceholders.trim()
|
|
547
|
+
const entrySourcePath = sourceLocation?.file || sourcePath
|
|
548
|
+
|
|
549
|
+
// Generate stable ID based on content and context
|
|
550
|
+
const stableId = generateStableId(tag, entryText, entrySourcePath, sourceContext)
|
|
551
|
+
|
|
552
|
+
// Extract color classes for buttons and other elements
|
|
553
|
+
const classAttr = node.getAttribute('class')
|
|
554
|
+
const colorClasses = extractColorClasses(classAttr)
|
|
555
|
+
|
|
450
556
|
entries[id] = {
|
|
451
557
|
id,
|
|
452
558
|
tag,
|
|
453
|
-
text:
|
|
454
|
-
|
|
559
|
+
text: entryText,
|
|
560
|
+
html: htmlContent,
|
|
561
|
+
sourcePath: entrySourcePath,
|
|
455
562
|
childCmsIds: childCmsIds.length > 0 ? childCmsIds : undefined,
|
|
456
563
|
sourceLine: sourceLocation?.line,
|
|
457
564
|
sourceSnippet: undefined,
|
|
458
|
-
sourceType: isCollectionWrapper ? 'collection' : undefined,
|
|
565
|
+
sourceType: isImage ? 'image' : (isCollectionWrapper ? 'collection' : undefined),
|
|
459
566
|
variableName: undefined,
|
|
460
567
|
parentComponentId,
|
|
461
568
|
// Add collection info for the wrapper entry
|
|
462
569
|
collectionName: isCollectionWrapper ? collectionInfo?.name : undefined,
|
|
463
570
|
collectionSlug: isCollectionWrapper ? collectionInfo?.slug : undefined,
|
|
571
|
+
contentPath: isCollectionWrapper ? collectionInfo?.contentPath : undefined,
|
|
572
|
+
// Add image info for image entries
|
|
573
|
+
imageSrc: imageInfo?.src,
|
|
574
|
+
imageAlt: imageInfo?.alt,
|
|
575
|
+
// Robustness fields
|
|
576
|
+
stableId,
|
|
577
|
+
sourceContext,
|
|
578
|
+
// Color classes for buttons/styled elements
|
|
579
|
+
colorClasses,
|
|
464
580
|
}
|
|
465
581
|
})
|
|
466
582
|
}
|
|
@@ -472,9 +588,13 @@ export async function processHtml(
|
|
|
472
588
|
node.removeAttribute('data-astro-source-line')
|
|
473
589
|
})
|
|
474
590
|
|
|
591
|
+
// Enhance manifest entries with actual source snippets from source files
|
|
592
|
+
// This allows the CMS to match and replace dynamic content in source files
|
|
593
|
+
const enhancedEntries = await enhanceManifestWithSourceSnippets(entries)
|
|
594
|
+
|
|
475
595
|
return {
|
|
476
596
|
html: root.toString(),
|
|
477
|
-
entries,
|
|
597
|
+
entries: enhancedEntries,
|
|
478
598
|
components,
|
|
479
599
|
collectionWrapperId,
|
|
480
600
|
}
|
|
@@ -497,3 +617,57 @@ function extractComponentName(sourceFile: string): string {
|
|
|
497
617
|
export function cleanText(text: string): string {
|
|
498
618
|
return text.trim().replace(/\s+/g, ' ').toLowerCase()
|
|
499
619
|
}
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* Extract source context for an element to enable resilient matching.
|
|
623
|
+
* This captures information about the element's position in the DOM
|
|
624
|
+
* that can be used as fallback when exact matching fails.
|
|
625
|
+
*/
|
|
626
|
+
function extractSourceContext(node: any, attributeName: string): SourceContext | undefined {
|
|
627
|
+
const parent = node.parentNode
|
|
628
|
+
if (!parent) return undefined
|
|
629
|
+
|
|
630
|
+
const siblings = parent.childNodes?.filter((child: any) => {
|
|
631
|
+
// Only consider element nodes, not text nodes
|
|
632
|
+
return child.nodeType === 1 && child.tagName
|
|
633
|
+
}) || []
|
|
634
|
+
|
|
635
|
+
const siblingIndex = siblings.indexOf(node)
|
|
636
|
+
|
|
637
|
+
// Get preceding sibling's text (first 30 chars)
|
|
638
|
+
let precedingText: string | undefined
|
|
639
|
+
if (siblingIndex > 0) {
|
|
640
|
+
const prevSibling = siblings[siblingIndex - 1]
|
|
641
|
+
const prevText = (prevSibling?.innerText || '').trim()
|
|
642
|
+
if (prevText) {
|
|
643
|
+
precedingText = prevText.substring(0, 30)
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// Get following sibling's text (first 30 chars)
|
|
648
|
+
let followingText: string | undefined
|
|
649
|
+
if (siblingIndex < siblings.length - 1) {
|
|
650
|
+
const nextSibling = siblings[siblingIndex + 1]
|
|
651
|
+
const nextText = (nextSibling?.innerText || '').trim()
|
|
652
|
+
if (nextText) {
|
|
653
|
+
followingText = nextText.substring(0, 30)
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// Get parent info
|
|
658
|
+
const parentTag = parent.tagName?.toLowerCase?.()
|
|
659
|
+
const parentClasses = parent.getAttribute?.('class') || undefined
|
|
660
|
+
|
|
661
|
+
// Only return context if we have meaningful data
|
|
662
|
+
if (!precedingText && !followingText && !parentTag) {
|
|
663
|
+
return undefined
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
return {
|
|
667
|
+
precedingText,
|
|
668
|
+
followingText,
|
|
669
|
+
parentTag,
|
|
670
|
+
siblingIndex: siblingIndex >= 0 ? siblingIndex : undefined,
|
|
671
|
+
parentClasses,
|
|
672
|
+
}
|
|
673
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -94,4 +94,3 @@ export default function cmsMarker(options: CmsMarkerOptions = {}): AstroIntegrat
|
|
|
94
94
|
export { findCollectionSource, parseMarkdownContent } from './source-finder'
|
|
95
95
|
export type { CollectionInfo, MarkdownContent } from './source-finder'
|
|
96
96
|
export type { CmsManifest, CmsMarkerOptions, CollectionEntry, ComponentDefinition, ComponentInstance, ManifestEntry } from './types'
|
|
97
|
-
|
package/src/manifest-writer.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import fs from 'node:fs/promises'
|
|
2
2
|
import path from 'node:path'
|
|
3
|
-
import
|
|
3
|
+
import { parseTailwindConfig } from './tailwind-colors'
|
|
4
|
+
import type { AvailableColors, CmsManifest, CollectionEntry, ComponentDefinition, ComponentInstance, ManifestEntry, ManifestMetadata } from './types'
|
|
5
|
+
import { generateManifestContentHash, generateSourceFileHashes } from './utils'
|
|
6
|
+
|
|
7
|
+
/** Current manifest schema version */
|
|
8
|
+
const MANIFEST_VERSION = '1.0'
|
|
4
9
|
|
|
5
10
|
/**
|
|
6
11
|
* Manages streaming manifest writes during build.
|
|
@@ -16,6 +21,7 @@ export class ManifestWriter {
|
|
|
16
21
|
private outDir: string = ''
|
|
17
22
|
private manifestFile: string
|
|
18
23
|
private componentDefinitions: Record<string, ComponentDefinition>
|
|
24
|
+
private availableColors: AvailableColors | undefined
|
|
19
25
|
private writeQueue: Promise<void> = Promise.resolve()
|
|
20
26
|
|
|
21
27
|
constructor(manifestFile: string, componentDefinitions: Record<string, ComponentDefinition> = {}) {
|
|
@@ -44,6 +50,22 @@ export class ManifestWriter {
|
|
|
44
50
|
this.globalManifest.componentDefinitions = definitions
|
|
45
51
|
}
|
|
46
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Load available Tailwind colors from the project's CSS config
|
|
55
|
+
*/
|
|
56
|
+
async loadAvailableColors(projectRoot: string = process.cwd()): Promise<void> {
|
|
57
|
+
this.availableColors = await parseTailwindConfig(projectRoot)
|
|
58
|
+
this.globalManifest.availableColors = this.availableColors
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Set available colors directly (for testing or custom colors)
|
|
63
|
+
*/
|
|
64
|
+
setAvailableColors(colors: AvailableColors): void {
|
|
65
|
+
this.availableColors = colors
|
|
66
|
+
this.globalManifest.availableColors = colors
|
|
67
|
+
}
|
|
68
|
+
|
|
47
69
|
/**
|
|
48
70
|
* Get the manifest path for a given page
|
|
49
71
|
* Places manifest next to the page: /about -> /about.json, / -> /index.json
|
|
@@ -100,13 +122,25 @@ export class ManifestWriter {
|
|
|
100
122
|
|
|
101
123
|
await fs.mkdir(manifestDir, { recursive: true })
|
|
102
124
|
|
|
125
|
+
// Generate metadata for this page manifest
|
|
126
|
+
const metadata: ManifestMetadata = {
|
|
127
|
+
version: MANIFEST_VERSION,
|
|
128
|
+
generatedAt: new Date().toISOString(),
|
|
129
|
+
generatedBy: 'astro-cms-marker',
|
|
130
|
+
contentHash: generateManifestContentHash(entries),
|
|
131
|
+
sourceFileHashes: generateSourceFileHashes(entries),
|
|
132
|
+
}
|
|
133
|
+
|
|
103
134
|
const pageManifest: {
|
|
135
|
+
metadata: ManifestMetadata
|
|
104
136
|
page: string
|
|
105
137
|
entries: Record<string, ManifestEntry>
|
|
106
138
|
components: Record<string, ComponentInstance>
|
|
107
139
|
componentDefinitions: Record<string, ComponentDefinition>
|
|
108
140
|
collection?: CollectionEntry
|
|
141
|
+
availableColors?: AvailableColors
|
|
109
142
|
} = {
|
|
143
|
+
metadata,
|
|
110
144
|
page: pagePath,
|
|
111
145
|
entries,
|
|
112
146
|
components,
|
|
@@ -117,6 +151,10 @@ export class ManifestWriter {
|
|
|
117
151
|
pageManifest.collection = collection
|
|
118
152
|
}
|
|
119
153
|
|
|
154
|
+
if (this.availableColors) {
|
|
155
|
+
pageManifest.availableColors = this.availableColors
|
|
156
|
+
}
|
|
157
|
+
|
|
120
158
|
await fs.writeFile(manifestPath, JSON.stringify(pageManifest, null, 2), 'utf-8')
|
|
121
159
|
}
|
|
122
160
|
|
|
@@ -176,7 +214,15 @@ export class ManifestWriter {
|
|
|
176
214
|
components: {},
|
|
177
215
|
componentDefinitions: this.componentDefinitions,
|
|
178
216
|
collections: {},
|
|
217
|
+
availableColors: this.availableColors,
|
|
179
218
|
}
|
|
180
219
|
this.writeQueue = Promise.resolve()
|
|
181
220
|
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Get available colors (for use in dev middleware)
|
|
224
|
+
*/
|
|
225
|
+
getAvailableColors(): AvailableColors | undefined {
|
|
226
|
+
return this.availableColors
|
|
227
|
+
}
|
|
182
228
|
}
|