@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.
- package/README.md +81 -73
- package/dist/src/build-processor.d.ts.map +1 -1
- package/dist/src/component-registry.d.ts +6 -2
- package/dist/src/component-registry.d.ts.map +1 -1
- package/dist/src/dev-middleware.d.ts.map +1 -1
- package/dist/src/editor/api.d.ts +14 -0
- package/dist/src/editor/api.d.ts.map +1 -1
- package/dist/src/editor/components/ai-chat.d.ts.map +1 -1
- package/dist/src/editor/components/block-editor.d.ts.map +1 -1
- package/dist/src/editor/components/color-toolbar.d.ts.map +1 -1
- package/dist/src/editor/components/editable-highlights.d.ts.map +1 -1
- package/dist/src/editor/components/outline.d.ts.map +1 -1
- package/dist/src/editor/constants.d.ts +1 -0
- package/dist/src/editor/constants.d.ts.map +1 -1
- package/dist/src/editor/dom.d.ts +9 -0
- package/dist/src/editor/dom.d.ts.map +1 -1
- package/dist/src/editor/editor.d.ts.map +1 -1
- package/dist/src/editor/history.d.ts.map +1 -1
- package/dist/src/editor/hooks/useBlockEditorHandlers.d.ts.map +1 -1
- package/dist/src/editor/index.d.ts.map +1 -1
- package/dist/src/editor/storage.d.ts +2 -0
- package/dist/src/editor/storage.d.ts.map +1 -1
- package/dist/src/handlers/array-ops.d.ts +59 -0
- package/dist/src/handlers/array-ops.d.ts.map +1 -0
- package/dist/src/handlers/component-ops.d.ts +26 -0
- package/dist/src/handlers/component-ops.d.ts.map +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/source-finder/cross-file-tracker.d.ts.map +1 -1
- package/dist/src/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/build-processor.ts +27 -0
- package/src/component-registry.ts +125 -76
- package/src/dev-middleware.ts +85 -16
- package/src/editor/api.ts +72 -0
- package/src/editor/components/ai-chat.tsx +0 -1
- package/src/editor/components/block-editor.tsx +92 -17
- package/src/editor/components/color-toolbar.tsx +7 -1
- package/src/editor/components/editable-highlights.tsx +4 -1
- package/src/editor/components/outline.tsx +11 -6
- package/src/editor/constants.ts +1 -0
- package/src/editor/dom.ts +46 -1
- package/src/editor/editor.ts +5 -2
- package/src/editor/history.ts +1 -6
- package/src/editor/hooks/useBlockEditorHandlers.ts +86 -29
- package/src/editor/index.tsx +24 -8
- package/src/editor/storage.ts +24 -0
- package/src/handlers/array-ops.ts +452 -0
- package/src/handlers/component-ops.ts +269 -18
- package/src/handlers/markdown-ops.ts +7 -4
- package/src/handlers/request-utils.ts +1 -1
- package/src/handlers/source-writer.ts +4 -5
- package/src/index.ts +15 -10
- package/src/manifest-writer.ts +1 -1
- package/src/source-finder/cross-file-tracker.ts +1 -1
- 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: `
|
|
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: `
|
|
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 =
|
|
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, '>')
|
|
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
|
-
|
|
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
|
|
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
|
|
147
|
-
|
|
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(/^\.+/, '')
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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(
|
|
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
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
},
|
package/src/manifest-writer.ts
CHANGED
|
@@ -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
|
|