banhaten 0.1.1 → 0.1.2
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 +20 -8
- package/package.json +8 -2
- package/registry/components/autocomplete.tsx +637 -0
- package/registry/components/avatar.tsx +258 -22
- package/registry/components/badge.tsx +97 -35
- package/registry/components/date-picker-state.ts +253 -0
- package/registry/components/date-picker.tsx +115 -158
- package/registry/components/expanded/EmptyState.tsx +155 -0
- package/registry/components/expanded/emptyState.css +111 -0
- package/registry/components/expanded/slideout.css +1 -0
- package/registry/components/expanded/table.css +1 -0
- package/registry/components/input-otp.tsx +574 -0
- package/registry/components/input.tsx +21 -11
- package/registry/components/menu.tsx +371 -8
- package/registry/components/popover.tsx +840 -0
- package/registry/components/select.tsx +4 -0
- package/registry/components/skeleton.css +57 -0
- package/registry/components/skeleton.tsx +482 -0
- package/registry/components/spinner.tsx +79 -11
- package/registry/components/textarea.tsx +1 -1
- package/registry/components/tooltip.tsx +4 -0
- package/registry/examples/autocomplete-demo.tsx +109 -0
- package/registry/examples/avatar-demo.tsx +102 -47
- package/registry/examples/badge-demo.tsx +16 -0
- package/registry/examples/expanded/command-bar-demo.tsx +236 -0
- package/registry/examples/expanded/empty-state-demo.tsx +39 -0
- package/registry/examples/input-demo.tsx +1 -1
- package/registry/examples/input-otp-demo.tsx +72 -0
- package/registry/examples/menu-demo.tsx +101 -88
- package/registry/examples/popover-demo.tsx +546 -0
- package/registry/examples/select-demo.tsx +1 -1
- package/registry/examples/skeleton-demo.tsx +56 -0
- package/registry/examples/spinner-demo.tsx +23 -1
- package/registry/examples/textarea-demo.tsx +1 -1
- package/registry/index.json +240 -8
- package/registry/styles/globals.css +88 -0
- package/src/cli/index.js +997 -62
package/src/cli/index.js
CHANGED
|
@@ -409,13 +409,16 @@ async function writeGlobalCss(cwd, config, options) {
|
|
|
409
409
|
const sourcePath = path.join(registryRoot, "styles", "globals.css")
|
|
410
410
|
const tokenCss = await fs.readFile(sourcePath, "utf8")
|
|
411
411
|
const current = await readTextIfExists(cssPath)
|
|
412
|
+
const tokenBlock = current
|
|
413
|
+
? removeExistingTokenBlock(current, relative(cwd, cssPath))
|
|
414
|
+
: { content: "", found: false }
|
|
412
415
|
|
|
413
|
-
if (
|
|
416
|
+
if (tokenBlock.found && !options.force) {
|
|
414
417
|
return [`kept ${relative(cwd, cssPath)}`]
|
|
415
418
|
}
|
|
416
419
|
|
|
417
420
|
const nextCss = current
|
|
418
|
-
? `${
|
|
421
|
+
? `${tokenBlock.content.trimEnd()}\n\n${tokenCss}\n`
|
|
419
422
|
: `${tokenCss}\n`
|
|
420
423
|
const next = ensureGlobalCssImports(nextCss)
|
|
421
424
|
|
|
@@ -489,20 +492,17 @@ async function writeViteAliasConfig(cwd, aliasRoot, options) {
|
|
|
489
492
|
|
|
490
493
|
const current = await fs.readFile(viteConfigPath, "utf8")
|
|
491
494
|
const sourceRoot = shouldUseSrcDirectory(cwd) ? "./src" : "."
|
|
492
|
-
|
|
495
|
+
let next
|
|
493
496
|
|
|
494
|
-
|
|
495
|
-
current
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
) {
|
|
499
|
-
return [`kept ${relative(cwd, viteConfigPath)}`]
|
|
497
|
+
try {
|
|
498
|
+
next = updateViteConfigSource(current, { aliasRoot, sourceRoot })
|
|
499
|
+
} catch (error) {
|
|
500
|
+
throw new Error(`${relative(cwd, viteConfigPath)}: ${error.message}`)
|
|
500
501
|
}
|
|
501
502
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
next = addVitePluginCall(next, "tailwindcss()")
|
|
503
|
+
if (next === current) {
|
|
504
|
+
return [`kept ${relative(cwd, viteConfigPath)}`]
|
|
505
|
+
}
|
|
506
506
|
|
|
507
507
|
const status = await writeFile(viteConfigPath, next, {
|
|
508
508
|
dryRun: options.dryRun,
|
|
@@ -528,50 +528,809 @@ async function findViteConfigPath(cwd) {
|
|
|
528
528
|
}
|
|
529
529
|
|
|
530
530
|
function ensureNodeUrlImport(source) {
|
|
531
|
-
|
|
531
|
+
const imports = parseImportDeclarations(source)
|
|
532
|
+
const nodeUrlImports = imports.filter((entry) => entry.moduleSpecifier === "node:url")
|
|
533
|
+
const importedLocals = new Set(nodeUrlImports.flatMap((entry) => entry.namedLocals))
|
|
534
|
+
const missing = ["fileURLToPath", "URL"].filter((name) => !importedLocals.has(name))
|
|
535
|
+
|
|
536
|
+
if (missing.length === 0) return source
|
|
537
|
+
|
|
538
|
+
return prependImportLines(
|
|
539
|
+
source,
|
|
540
|
+
[`import { ${missing.join(", ")} } from "node:url"`]
|
|
541
|
+
)
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function ensureTailwindViteImport(source, localName) {
|
|
545
|
+
const imports = parseImportDeclarations(source)
|
|
546
|
+
const tailwindImport = imports.find(
|
|
547
|
+
(entry) => entry.moduleSpecifier === "@tailwindcss/vite"
|
|
548
|
+
)
|
|
549
|
+
|
|
550
|
+
if (!tailwindImport) {
|
|
551
|
+
return prependImportLines(
|
|
552
|
+
source,
|
|
553
|
+
[`import ${localName} from "@tailwindcss/vite"`]
|
|
554
|
+
)
|
|
555
|
+
}
|
|
532
556
|
|
|
533
|
-
|
|
557
|
+
if (!tailwindImport.defaultLocal) {
|
|
558
|
+
throw new Error(
|
|
559
|
+
"unsupported Vite config: @tailwindcss/vite must be imported with a default binding."
|
|
560
|
+
)
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
return source
|
|
534
564
|
}
|
|
535
565
|
|
|
536
|
-
function
|
|
537
|
-
|
|
566
|
+
function updateViteConfigSource(source, { aliasRoot, sourceRoot }) {
|
|
567
|
+
const tailwindLocalName = getTailwindViteLocalName(source)
|
|
568
|
+
let next = addViteResolveAlias(source, aliasRoot, sourceRoot)
|
|
569
|
+
next = addVitePluginCall(next, `${tailwindLocalName}()`, tailwindLocalName)
|
|
570
|
+
next = ensureTailwindViteImport(next, tailwindLocalName)
|
|
571
|
+
next = ensureNodeUrlImport(next)
|
|
572
|
+
return next
|
|
573
|
+
}
|
|
538
574
|
|
|
539
|
-
|
|
575
|
+
function getTailwindViteLocalName(source) {
|
|
576
|
+
const tailwindImport = parseImportDeclarations(source).find(
|
|
577
|
+
(entry) => entry.moduleSpecifier === "@tailwindcss/vite"
|
|
578
|
+
)
|
|
579
|
+
|
|
580
|
+
if (!tailwindImport) return "tailwindcss"
|
|
581
|
+
if (tailwindImport.defaultLocal) return tailwindImport.defaultLocal
|
|
582
|
+
|
|
583
|
+
throw new Error(
|
|
584
|
+
"unsupported Vite config: @tailwindcss/vite must be imported with a default binding."
|
|
585
|
+
)
|
|
540
586
|
}
|
|
541
587
|
|
|
542
588
|
function addViteResolveAlias(source, aliasRoot, sourceRoot) {
|
|
543
|
-
const
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
589
|
+
const configObject = findViteConfigObject(source)
|
|
590
|
+
const resolveProperty = findObjectProperty(source, configObject, "resolve")
|
|
591
|
+
const aliasEntry = `${JSON.stringify(aliasRoot)}: fileURLToPath(new URL(${JSON.stringify(
|
|
592
|
+
sourceRoot
|
|
593
|
+
)}, import.meta.url))`
|
|
594
|
+
|
|
595
|
+
if (!resolveProperty) {
|
|
596
|
+
return insertObjectProperty(
|
|
597
|
+
source,
|
|
598
|
+
configObject,
|
|
599
|
+
[
|
|
600
|
+
"resolve: {",
|
|
601
|
+
" alias: {",
|
|
602
|
+
` ${aliasEntry},`,
|
|
603
|
+
" },",
|
|
604
|
+
"},",
|
|
605
|
+
].join("\n")
|
|
606
|
+
)
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
const resolveObject = expectObjectLiteralValue(
|
|
610
|
+
source,
|
|
611
|
+
resolveProperty,
|
|
612
|
+
"unsupported Vite config: resolve must be an object literal."
|
|
613
|
+
)
|
|
614
|
+
const aliasProperty = findObjectProperty(source, resolveObject, "alias")
|
|
615
|
+
|
|
616
|
+
if (!aliasProperty) {
|
|
617
|
+
return insertObjectProperty(
|
|
618
|
+
source,
|
|
619
|
+
resolveObject,
|
|
620
|
+
[
|
|
621
|
+
"alias: {",
|
|
622
|
+
` ${aliasEntry},`,
|
|
623
|
+
"},",
|
|
624
|
+
].join("\n")
|
|
625
|
+
)
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
const aliasObject = expectObjectLiteralValue(
|
|
629
|
+
source,
|
|
630
|
+
aliasProperty,
|
|
631
|
+
"unsupported Vite config: resolve.alias must be an object literal."
|
|
632
|
+
)
|
|
633
|
+
|
|
634
|
+
if (findObjectProperty(source, aliasObject, aliasRoot)) {
|
|
635
|
+
return source
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
return insertObjectProperty(source, aliasObject, `${aliasEntry},`)
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
function addVitePluginCall(source, pluginCall, pluginLocalName) {
|
|
642
|
+
const configObject = findViteConfigObject(source)
|
|
643
|
+
const pluginsProperty = findObjectProperty(source, configObject, "plugins")
|
|
644
|
+
|
|
645
|
+
if (!pluginsProperty) {
|
|
646
|
+
return insertObjectProperty(source, configObject, `plugins: [${pluginCall}],`)
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
const pluginsArray = expectArrayLiteralValue(
|
|
650
|
+
source,
|
|
651
|
+
pluginsProperty,
|
|
652
|
+
"unsupported Vite config: plugins must be an array literal."
|
|
653
|
+
)
|
|
654
|
+
|
|
655
|
+
if (arrayHasCallExpression(source, pluginsArray, pluginLocalName)) {
|
|
656
|
+
return source
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
return appendArrayElement(source, pluginsArray, pluginCall)
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
function findViteConfigObject(source) {
|
|
663
|
+
const expressionStart = findExportDefaultExpressionStart(source)
|
|
664
|
+
|
|
665
|
+
if (expressionStart === -1) {
|
|
666
|
+
throw new Error(
|
|
667
|
+
"unsupported Vite config: expected `export default defineConfig({ ... })` or `export default { ... }`."
|
|
668
|
+
)
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
if (source[expressionStart] === "{") {
|
|
672
|
+
return createBracketRange(source, expressionStart, "{")
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
const callee = readIdentifierAt(source, expressionStart)
|
|
676
|
+
if (callee?.name !== "defineConfig") {
|
|
677
|
+
throw new Error(
|
|
678
|
+
"unsupported Vite config: default export must be `defineConfig({ ... })` or an object literal."
|
|
679
|
+
)
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
let index = skipWhitespaceAndComments(source, callee.end)
|
|
683
|
+
if (source[index] !== "(") {
|
|
684
|
+
throw new Error("unsupported Vite config: malformed defineConfig call.")
|
|
570
685
|
}
|
|
571
686
|
|
|
572
|
-
|
|
687
|
+
index = skipWhitespaceAndComments(source, index + 1)
|
|
688
|
+
if (source[index] !== "{") {
|
|
689
|
+
throw new Error(
|
|
690
|
+
"unsupported Vite config: defineConfig must receive a config object literal."
|
|
691
|
+
)
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
return createBracketRange(source, index, "{")
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
function findExportDefaultExpressionStart(source) {
|
|
698
|
+
let index = 0
|
|
573
699
|
|
|
574
|
-
|
|
700
|
+
while (index < source.length) {
|
|
701
|
+
const next = skipIgnoredSyntax(source, index)
|
|
702
|
+
if (next !== index) {
|
|
703
|
+
index = next
|
|
704
|
+
continue
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
if (!matchIdentifier(source, index, "export")) {
|
|
708
|
+
index += 1
|
|
709
|
+
continue
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
const defaultStart = skipWhitespaceAndComments(source, index + "export".length)
|
|
713
|
+
if (!matchIdentifier(source, defaultStart, "default")) {
|
|
714
|
+
index += "export".length
|
|
715
|
+
continue
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
return skipWhitespaceAndComments(source, defaultStart + "default".length)
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
return -1
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
function createBracketRange(source, start, open) {
|
|
725
|
+
const close = open === "{" ? "}" : open === "[" ? "]" : ")"
|
|
726
|
+
const end = findMatchingBracket(source, start, open, close)
|
|
727
|
+
return { start, end: end + 1 }
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
function expectObjectLiteralValue(source, property, message) {
|
|
731
|
+
const valueStart = skipWhitespaceAndComments(source, property.valueStart)
|
|
732
|
+
if (source[valueStart] !== "{") throw new Error(message)
|
|
733
|
+
return createBracketRange(source, valueStart, "{")
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
function expectArrayLiteralValue(source, property, message) {
|
|
737
|
+
const valueStart = skipWhitespaceAndComments(source, property.valueStart)
|
|
738
|
+
if (source[valueStart] !== "[") throw new Error(message)
|
|
739
|
+
return createBracketRange(source, valueStart, "[")
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
function findObjectProperty(source, objectRange, propertyName) {
|
|
743
|
+
const bodyEnd = objectRange.end - 1
|
|
744
|
+
let index = objectRange.start + 1
|
|
745
|
+
|
|
746
|
+
while (index < bodyEnd) {
|
|
747
|
+
index = skipWhitespaceCommentsAndCommas(source, index, bodyEnd)
|
|
748
|
+
if (index >= bodyEnd) return null
|
|
749
|
+
|
|
750
|
+
const propertyStart = index
|
|
751
|
+
const name = readPropertyName(source, index)
|
|
752
|
+
const propertyEnd = findTopLevelCommaOrEnd(source, propertyStart, bodyEnd)
|
|
753
|
+
|
|
754
|
+
if (!name) {
|
|
755
|
+
index = propertyEnd + 1
|
|
756
|
+
continue
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
index = skipWhitespaceAndComments(source, name.end)
|
|
760
|
+
if (source[index] !== ":") {
|
|
761
|
+
if (name.name === propertyName) {
|
|
762
|
+
throw new Error(
|
|
763
|
+
`unsupported Vite config: ${propertyName} must be a normal object property.`
|
|
764
|
+
)
|
|
765
|
+
}
|
|
766
|
+
index = propertyEnd + 1
|
|
767
|
+
continue
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
if (name.name === propertyName) {
|
|
771
|
+
return {
|
|
772
|
+
name: name.name,
|
|
773
|
+
start: propertyStart,
|
|
774
|
+
end: propertyEnd,
|
|
775
|
+
valueStart: skipWhitespaceAndComments(source, index + 1),
|
|
776
|
+
valueEnd: propertyEnd,
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
index = propertyEnd + 1
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
return null
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
function readPropertyName(source, index) {
|
|
787
|
+
if (source[index] === "\"" || source[index] === "'") {
|
|
788
|
+
const literal = readStringLiteral(source, index)
|
|
789
|
+
return literal ? { name: literal.value, end: literal.end } : null
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
return readIdentifierAt(source, index)
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
function insertObjectProperty(source, objectRange, propertyText) {
|
|
796
|
+
const insertAt = objectRange.start + 1
|
|
797
|
+
const bodyEnd = objectRange.end - 1
|
|
798
|
+
const body = source.slice(insertAt, bodyEnd)
|
|
799
|
+
const hasContent = body.trim().length > 0
|
|
800
|
+
const propertyIndent = detectObjectChildIndent(source, objectRange)
|
|
801
|
+
const closeIndent = getLineIndent(source, bodyEnd)
|
|
802
|
+
const indentedProperty = indentMultiline(propertyText, propertyIndent)
|
|
803
|
+
|
|
804
|
+
if (!hasContent) {
|
|
805
|
+
return `${source.slice(0, insertAt)}\n${indentedProperty}\n${closeIndent}${source.slice(insertAt)}`
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
const needsTrailingNewline = source[insertAt] !== "\n" && source[insertAt] !== "\r"
|
|
809
|
+
const insertion = `\n${indentedProperty}${needsTrailingNewline ? "\n" : ""}`
|
|
810
|
+
return `${source.slice(0, insertAt)}${insertion}${source.slice(insertAt)}`
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
function detectObjectChildIndent(source, objectRange) {
|
|
814
|
+
const bodyEnd = objectRange.end - 1
|
|
815
|
+
let index = objectRange.start + 1
|
|
816
|
+
|
|
817
|
+
while (index < bodyEnd) {
|
|
818
|
+
if (source[index] === "\n") {
|
|
819
|
+
const next = skipHorizontalWhitespace(source, index + 1)
|
|
820
|
+
if (next < bodyEnd && source[next] !== "\n" && source[next] !== "\r") {
|
|
821
|
+
return source.slice(index + 1, next)
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
index += 1
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
return `${getLineIndent(source, objectRange.start)} `
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
function appendArrayElement(source, arrayRange, elementText) {
|
|
831
|
+
const bodyStart = arrayRange.start + 1
|
|
832
|
+
const bodyEnd = arrayRange.end - 1
|
|
833
|
+
const body = source.slice(bodyStart, bodyEnd)
|
|
834
|
+
|
|
835
|
+
if (body.trim().length === 0) {
|
|
836
|
+
return `${source.slice(0, bodyStart)}${elementText}${source.slice(bodyStart)}`
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
const contentEnd = trimEndIndex(source, bodyStart, bodyEnd)
|
|
840
|
+
const hasNewline = bodyIncludesNewline(source, bodyStart, bodyEnd)
|
|
841
|
+
const hasTrailingComma = previousCodeChar(source, contentEnd) === ","
|
|
842
|
+
|
|
843
|
+
if (!hasNewline) {
|
|
844
|
+
const separator = hasTrailingComma ? " " : ", "
|
|
845
|
+
return `${source.slice(0, contentEnd)}${separator}${elementText}${source.slice(contentEnd)}`
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
const closeIndent = getLineIndent(source, bodyEnd)
|
|
849
|
+
const elementIndent = `${closeIndent} `
|
|
850
|
+
const separator = hasTrailingComma ? "\n" : ",\n"
|
|
851
|
+
return `${source.slice(0, contentEnd)}${separator}${elementIndent}${elementText},${source.slice(contentEnd)}`
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
function arrayHasCallExpression(source, arrayRange, localName) {
|
|
855
|
+
const bodyEnd = arrayRange.end - 1
|
|
856
|
+
let index = arrayRange.start + 1
|
|
857
|
+
|
|
858
|
+
while (index < bodyEnd) {
|
|
859
|
+
index = skipWhitespaceCommentsAndCommas(source, index, bodyEnd)
|
|
860
|
+
if (index >= bodyEnd) return false
|
|
861
|
+
|
|
862
|
+
const elementEnd = findTopLevelCommaOrEnd(source, index, bodyEnd)
|
|
863
|
+
if (startsWithCallExpression(source, index, elementEnd, localName)) {
|
|
864
|
+
return true
|
|
865
|
+
}
|
|
866
|
+
index = elementEnd + 1
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
return false
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
function startsWithCallExpression(source, start, end, localName) {
|
|
873
|
+
const index = skipWhitespaceAndComments(source, start)
|
|
874
|
+
if (!matchIdentifier(source, index, localName)) return false
|
|
875
|
+
|
|
876
|
+
const callStart = skipWhitespaceAndComments(source, index + localName.length)
|
|
877
|
+
if (source[callStart] !== "(") return false
|
|
878
|
+
|
|
879
|
+
const callEnd = createBracketRange(source, callStart, "(").end
|
|
880
|
+
const next = skipWhitespaceAndComments(source, callEnd)
|
|
881
|
+
return next <= end
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
function parseImportDeclarations(source) {
|
|
885
|
+
const declarations = []
|
|
886
|
+
let index = 0
|
|
887
|
+
|
|
888
|
+
while (index < source.length) {
|
|
889
|
+
const next = skipIgnoredSyntax(source, index)
|
|
890
|
+
if (next !== index) {
|
|
891
|
+
index = next
|
|
892
|
+
continue
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
if (!matchIdentifier(source, index, "import")) {
|
|
896
|
+
index += 1
|
|
897
|
+
continue
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
const clauseStart = skipWhitespaceAndComments(source, index + "import".length)
|
|
901
|
+
if (source[clauseStart] === ".") {
|
|
902
|
+
index = clauseStart + 1
|
|
903
|
+
continue
|
|
904
|
+
}
|
|
905
|
+
if (source[clauseStart] === "(") {
|
|
906
|
+
index = clauseStart + 1
|
|
907
|
+
continue
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
const end = findImportDeclarationEnd(source, index)
|
|
911
|
+
const declaration = source.slice(index, end)
|
|
912
|
+
const moduleSpecifier = getImportModuleSpecifier(declaration)
|
|
913
|
+
|
|
914
|
+
if (moduleSpecifier) {
|
|
915
|
+
declarations.push({
|
|
916
|
+
start: index,
|
|
917
|
+
end,
|
|
918
|
+
moduleSpecifier,
|
|
919
|
+
defaultLocal: getDefaultImportLocal(declaration),
|
|
920
|
+
namedLocals: getNamedImportLocals(declaration),
|
|
921
|
+
})
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
index = end
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
return declarations
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
function findImportDeclarationEnd(source, start) {
|
|
931
|
+
const stack = []
|
|
932
|
+
let sawModuleSpecifier = false
|
|
933
|
+
let index = start
|
|
934
|
+
|
|
935
|
+
while (index < source.length) {
|
|
936
|
+
const skipped = skipComment(source, index)
|
|
937
|
+
if (skipped !== index) {
|
|
938
|
+
index = skipped
|
|
939
|
+
continue
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
if (source[index] === "\"" || source[index] === "'") {
|
|
943
|
+
const literal = readStringLiteral(source, index)
|
|
944
|
+
if (!literal) return source.length
|
|
945
|
+
sawModuleSpecifier = true
|
|
946
|
+
index = literal.end
|
|
947
|
+
continue
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
if (isOpeningBracket(source[index])) {
|
|
951
|
+
stack.push(expectedClosingBracket(source[index]))
|
|
952
|
+
index += 1
|
|
953
|
+
continue
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
if (isClosingBracket(source[index])) {
|
|
957
|
+
if (stack.at(-1) === source[index]) stack.pop()
|
|
958
|
+
index += 1
|
|
959
|
+
continue
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
if (stack.length === 0 && source[index] === ";") return index + 1
|
|
963
|
+
if (
|
|
964
|
+
stack.length === 0 &&
|
|
965
|
+
sawModuleSpecifier &&
|
|
966
|
+
(source[index] === "\n" || source[index] === "\r")
|
|
967
|
+
) {
|
|
968
|
+
return index + 1
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
index += 1
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
return source.length
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
function getImportModuleSpecifier(declaration) {
|
|
978
|
+
let moduleSpecifier = null
|
|
979
|
+
let index = 0
|
|
980
|
+
|
|
981
|
+
while (index < declaration.length) {
|
|
982
|
+
const skipped = skipComment(declaration, index)
|
|
983
|
+
if (skipped !== index) {
|
|
984
|
+
index = skipped
|
|
985
|
+
continue
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
if (declaration[index] === "\"" || declaration[index] === "'") {
|
|
989
|
+
const literal = readStringLiteral(declaration, index)
|
|
990
|
+
if (!literal) break
|
|
991
|
+
moduleSpecifier = literal.value
|
|
992
|
+
index = literal.end
|
|
993
|
+
continue
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
index += 1
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
return moduleSpecifier
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
function getDefaultImportLocal(declaration) {
|
|
1003
|
+
let index = skipWhitespaceAndComments(declaration, "import".length)
|
|
1004
|
+
|
|
1005
|
+
if (matchIdentifier(declaration, index, "type")) {
|
|
1006
|
+
index = skipWhitespaceAndComments(declaration, index + "type".length)
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
if (declaration[index] === "\"" || declaration[index] === "{" || declaration[index] === "*") {
|
|
1010
|
+
return null
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
const local = readIdentifierAt(declaration, index)
|
|
1014
|
+
return local?.name ?? null
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
function getNamedImportLocals(declaration) {
|
|
1018
|
+
const open = declaration.indexOf("{")
|
|
1019
|
+
if (open === -1) return []
|
|
1020
|
+
|
|
1021
|
+
const close = findMatchingBracket(declaration, open, "{", "}")
|
|
1022
|
+
const namedImportBody = declaration.slice(open + 1, close)
|
|
1023
|
+
const locals = []
|
|
1024
|
+
|
|
1025
|
+
for (const specifier of splitTopLevelList(namedImportBody)) {
|
|
1026
|
+
const trimmed = specifier.trim()
|
|
1027
|
+
if (!trimmed) continue
|
|
1028
|
+
|
|
1029
|
+
const withoutType = trimmed.startsWith("type ")
|
|
1030
|
+
? trimmed.slice("type ".length).trim()
|
|
1031
|
+
: trimmed
|
|
1032
|
+
const asIndex = withoutType.toLowerCase().lastIndexOf(" as ")
|
|
1033
|
+
const localName = asIndex === -1
|
|
1034
|
+
? withoutType
|
|
1035
|
+
: withoutType.slice(asIndex + " as ".length).trim()
|
|
1036
|
+
const local = readIdentifierAt(localName, 0)
|
|
1037
|
+
|
|
1038
|
+
if (local) locals.push(local.name)
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
return locals
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
function splitTopLevelList(source) {
|
|
1045
|
+
const items = []
|
|
1046
|
+
let start = 0
|
|
1047
|
+
let index = 0
|
|
1048
|
+
|
|
1049
|
+
while (index < source.length) {
|
|
1050
|
+
const skipped = skipIgnoredSyntax(source, index)
|
|
1051
|
+
if (skipped !== index) {
|
|
1052
|
+
index = skipped
|
|
1053
|
+
continue
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
if (source[index] === ",") {
|
|
1057
|
+
items.push(source.slice(start, index))
|
|
1058
|
+
start = index + 1
|
|
1059
|
+
}
|
|
1060
|
+
index += 1
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
items.push(source.slice(start))
|
|
1064
|
+
return items
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
function prependImportLines(source, lines) {
|
|
1068
|
+
if (lines.length === 0) return source
|
|
1069
|
+
|
|
1070
|
+
let insertAt = 0
|
|
1071
|
+
if (source.startsWith("#!")) {
|
|
1072
|
+
const lineEnd = source.indexOf("\n")
|
|
1073
|
+
insertAt = lineEnd === -1 ? source.length : lineEnd + 1
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
const prefix = `${lines.join("\n")}\n`
|
|
1077
|
+
return `${source.slice(0, insertAt)}${prefix}${source.slice(insertAt)}`
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
function findTopLevelCommaOrEnd(source, start, end) {
|
|
1081
|
+
const stack = []
|
|
1082
|
+
let index = start
|
|
1083
|
+
|
|
1084
|
+
while (index < end) {
|
|
1085
|
+
const next = skipIgnoredSyntax(source, index)
|
|
1086
|
+
if (next !== index) {
|
|
1087
|
+
index = next
|
|
1088
|
+
continue
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
if (isOpeningBracket(source[index])) {
|
|
1092
|
+
stack.push(expectedClosingBracket(source[index]))
|
|
1093
|
+
index += 1
|
|
1094
|
+
continue
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
if (isClosingBracket(source[index])) {
|
|
1098
|
+
if (stack.at(-1) === source[index]) stack.pop()
|
|
1099
|
+
index += 1
|
|
1100
|
+
continue
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
if (stack.length === 0 && source[index] === ",") return index
|
|
1104
|
+
index += 1
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
return end
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
function findMatchingBracket(source, start, open, close) {
|
|
1111
|
+
if (source[start] !== open) {
|
|
1112
|
+
throw new Error("unsupported Vite config: malformed JavaScript object.")
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
const stack = []
|
|
1116
|
+
let index = start
|
|
1117
|
+
|
|
1118
|
+
while (index < source.length) {
|
|
1119
|
+
const next = skipIgnoredSyntax(source, index)
|
|
1120
|
+
if (next !== index) {
|
|
1121
|
+
index = next
|
|
1122
|
+
continue
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
if (isOpeningBracket(source[index])) {
|
|
1126
|
+
stack.push(expectedClosingBracket(source[index]))
|
|
1127
|
+
index += 1
|
|
1128
|
+
continue
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
if (isClosingBracket(source[index])) {
|
|
1132
|
+
const expected = stack.pop()
|
|
1133
|
+
if (expected !== source[index]) {
|
|
1134
|
+
throw new Error("unsupported Vite config: malformed JavaScript brackets.")
|
|
1135
|
+
}
|
|
1136
|
+
if (stack.length === 0) return index
|
|
1137
|
+
index += 1
|
|
1138
|
+
continue
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
index += 1
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
throw new Error(`unsupported Vite config: missing closing ${close}.`)
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
function skipIgnoredSyntax(source, index) {
|
|
1148
|
+
const commentEnd = skipComment(source, index)
|
|
1149
|
+
if (commentEnd !== index) return commentEnd
|
|
1150
|
+
|
|
1151
|
+
if (source[index] === "\"" || source[index] === "'" || source[index] === "`") {
|
|
1152
|
+
return skipQuotedText(source, index)
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
return index
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
function skipComment(source, index) {
|
|
1159
|
+
if (source[index] !== "/") return index
|
|
1160
|
+
|
|
1161
|
+
if (source[index + 1] === "/") {
|
|
1162
|
+
const lineEnd = source.indexOf("\n", index + 2)
|
|
1163
|
+
return lineEnd === -1 ? source.length : lineEnd + 1
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
if (source[index + 1] === "*") {
|
|
1167
|
+
const blockEnd = source.indexOf("*/", index + 2)
|
|
1168
|
+
return blockEnd === -1 ? source.length : blockEnd + 2
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
return index
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
function skipQuotedText(source, index) {
|
|
1175
|
+
const quote = source[index]
|
|
1176
|
+
index += 1
|
|
1177
|
+
|
|
1178
|
+
while (index < source.length) {
|
|
1179
|
+
if (source[index] === "\\") {
|
|
1180
|
+
index += 2
|
|
1181
|
+
continue
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
if (source[index] === quote) return index + 1
|
|
1185
|
+
index += 1
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
return source.length
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
function readStringLiteral(source, index) {
|
|
1192
|
+
const quote = source[index]
|
|
1193
|
+
if (quote !== "\"" && quote !== "'") return null
|
|
1194
|
+
|
|
1195
|
+
let value = ""
|
|
1196
|
+
index += 1
|
|
1197
|
+
|
|
1198
|
+
while (index < source.length) {
|
|
1199
|
+
if (source[index] === "\\") {
|
|
1200
|
+
value += source[index + 1] ?? ""
|
|
1201
|
+
index += 2
|
|
1202
|
+
continue
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
if (source[index] === quote) {
|
|
1206
|
+
return { value, end: index + 1 }
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
value += source[index]
|
|
1210
|
+
index += 1
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
return null
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
function readIdentifierAt(source, index) {
|
|
1217
|
+
if (!isIdentifierStart(source[index])) return null
|
|
1218
|
+
|
|
1219
|
+
let end = index + 1
|
|
1220
|
+
while (end < source.length && isIdentifierPart(source[end])) {
|
|
1221
|
+
end += 1
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
return { name: source.slice(index, end), end }
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
function matchIdentifier(source, index, name) {
|
|
1228
|
+
if (source.slice(index, index + name.length) !== name) return false
|
|
1229
|
+
|
|
1230
|
+
const before = source[index - 1]
|
|
1231
|
+
const after = source[index + name.length]
|
|
1232
|
+
return !isIdentifierPart(before) && !isIdentifierPart(after)
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
function isIdentifierStart(char) {
|
|
1236
|
+
return Boolean(char && /[A-Za-z_$]/.test(char))
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
function isIdentifierPart(char) {
|
|
1240
|
+
return Boolean(char && /[A-Za-z0-9_$]/.test(char))
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
function isOpeningBracket(char) {
|
|
1244
|
+
return char === "{" || char === "[" || char === "("
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
function isClosingBracket(char) {
|
|
1248
|
+
return char === "}" || char === "]" || char === ")"
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
function expectedClosingBracket(char) {
|
|
1252
|
+
if (char === "{") return "}"
|
|
1253
|
+
if (char === "[") return "]"
|
|
1254
|
+
return ")"
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
function skipWhitespaceAndComments(source, index) {
|
|
1258
|
+
while (index < source.length) {
|
|
1259
|
+
const next = skipComment(source, index)
|
|
1260
|
+
if (next !== index) {
|
|
1261
|
+
index = next
|
|
1262
|
+
continue
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
if (!isWhitespace(source[index])) return index
|
|
1266
|
+
index += 1
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
return index
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
function skipWhitespaceCommentsAndCommas(source, index, end) {
|
|
1273
|
+
while (index < end) {
|
|
1274
|
+
const next = skipWhitespaceAndComments(source, index)
|
|
1275
|
+
if (next !== index) {
|
|
1276
|
+
index = next
|
|
1277
|
+
continue
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
if (source[index] !== ",") return index
|
|
1281
|
+
index += 1
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
return index
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
function skipHorizontalWhitespace(source, index) {
|
|
1288
|
+
while (source[index] === " " || source[index] === "\t") {
|
|
1289
|
+
index += 1
|
|
1290
|
+
}
|
|
1291
|
+
return index
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
function isWhitespace(char) {
|
|
1295
|
+
return char === " " || char === "\t" || char === "\n" || char === "\r"
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
function indentMultiline(text, indent) {
|
|
1299
|
+
return text
|
|
1300
|
+
.split("\n")
|
|
1301
|
+
.map((line) => `${indent}${line}`)
|
|
1302
|
+
.join("\n")
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
function getLineIndent(source, index) {
|
|
1306
|
+
let lineStart = index
|
|
1307
|
+
while (lineStart > 0 && source[lineStart - 1] !== "\n") {
|
|
1308
|
+
lineStart -= 1
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
return source.slice(lineStart, skipHorizontalWhitespace(source, lineStart))
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
function trimEndIndex(source, start, end) {
|
|
1315
|
+
while (end > start && isWhitespace(source[end - 1])) {
|
|
1316
|
+
end -= 1
|
|
1317
|
+
}
|
|
1318
|
+
return end
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
function previousCodeChar(source, index) {
|
|
1322
|
+
index -= 1
|
|
1323
|
+
while (index >= 0 && isWhitespace(source[index])) {
|
|
1324
|
+
index -= 1
|
|
1325
|
+
}
|
|
1326
|
+
return source[index]
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
function bodyIncludesNewline(source, start, end) {
|
|
1330
|
+
for (let index = start; index < end; index += 1) {
|
|
1331
|
+
if (source[index] === "\n" || source[index] === "\r") return true
|
|
1332
|
+
}
|
|
1333
|
+
return false
|
|
575
1334
|
}
|
|
576
1335
|
|
|
577
1336
|
async function getPackageChanges(cwd, dependencies) {
|
|
@@ -734,9 +1493,76 @@ async function readJsonc(filePath) {
|
|
|
734
1493
|
}
|
|
735
1494
|
|
|
736
1495
|
function stripJsonComments(content) {
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
1496
|
+
let output = ""
|
|
1497
|
+
let inString = false
|
|
1498
|
+
let escaped = false
|
|
1499
|
+
|
|
1500
|
+
for (let index = 0; index < content.length; index += 1) {
|
|
1501
|
+
const char = content[index]
|
|
1502
|
+
const next = content[index + 1]
|
|
1503
|
+
|
|
1504
|
+
if (inString) {
|
|
1505
|
+
output += char
|
|
1506
|
+
|
|
1507
|
+
if (escaped) {
|
|
1508
|
+
escaped = false
|
|
1509
|
+
} else if (char === "\\") {
|
|
1510
|
+
escaped = true
|
|
1511
|
+
} else if (char === "\"") {
|
|
1512
|
+
inString = false
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
continue
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
if (char === "\"") {
|
|
1519
|
+
inString = true
|
|
1520
|
+
output += char
|
|
1521
|
+
continue
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
if (char === "/" && next === "/") {
|
|
1525
|
+
output += " "
|
|
1526
|
+
index += 2
|
|
1527
|
+
|
|
1528
|
+
while (index < content.length && content[index] !== "\n" && content[index] !== "\r") {
|
|
1529
|
+
output += " "
|
|
1530
|
+
index += 1
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
if (index < content.length) {
|
|
1534
|
+
output += content[index]
|
|
1535
|
+
if (content[index] === "\r" && content[index + 1] === "\n") {
|
|
1536
|
+
index += 1
|
|
1537
|
+
output += content[index]
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
continue
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
if (char === "/" && next === "*") {
|
|
1545
|
+
output += " "
|
|
1546
|
+
index += 2
|
|
1547
|
+
|
|
1548
|
+
while (index < content.length) {
|
|
1549
|
+
if (content[index] === "*" && content[index + 1] === "/") {
|
|
1550
|
+
output += " "
|
|
1551
|
+
index += 1
|
|
1552
|
+
break
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
output += content[index] === "\n" || content[index] === "\r" ? content[index] : " "
|
|
1556
|
+
index += 1
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
continue
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
output += char
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
return output
|
|
740
1566
|
}
|
|
741
1567
|
|
|
742
1568
|
async function readJsonIfExists(filePath) {
|
|
@@ -766,27 +1592,136 @@ async function exists(filePath) {
|
|
|
766
1592
|
}
|
|
767
1593
|
}
|
|
768
1594
|
|
|
769
|
-
function removeExistingTokenBlock(content) {
|
|
770
|
-
const
|
|
771
|
-
|
|
1595
|
+
function removeExistingTokenBlock(content, fileLabel = "CSS file") {
|
|
1596
|
+
const lines = splitLinesPreservingEndings(content)
|
|
1597
|
+
const kept = []
|
|
1598
|
+
let found = false
|
|
1599
|
+
let inTokenBlock = false
|
|
1600
|
+
|
|
1601
|
+
for (const line of lines) {
|
|
1602
|
+
const trimmed = line.trimStart()
|
|
772
1603
|
|
|
773
|
-
|
|
774
|
-
|
|
1604
|
+
if (trimmed.startsWith(CSS_START)) {
|
|
1605
|
+
if (found || inTokenBlock) {
|
|
1606
|
+
throw new Error(`${fileLabel}: multiple Banhaten token blocks found.`)
|
|
1607
|
+
}
|
|
1608
|
+
found = true
|
|
1609
|
+
inTokenBlock = true
|
|
1610
|
+
continue
|
|
1611
|
+
}
|
|
775
1612
|
|
|
776
|
-
|
|
1613
|
+
if (trimmed.startsWith(CSS_END)) {
|
|
1614
|
+
if (!inTokenBlock) {
|
|
1615
|
+
throw new Error(`${fileLabel}: Banhaten token end marker has no start marker.`)
|
|
1616
|
+
}
|
|
1617
|
+
inTokenBlock = false
|
|
1618
|
+
continue
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
if (!inTokenBlock) {
|
|
1622
|
+
kept.push(line)
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
if (inTokenBlock) {
|
|
1627
|
+
throw new Error(`${fileLabel}: Banhaten token block is missing its end marker.`)
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
return { content: kept.join(""), found }
|
|
777
1631
|
}
|
|
778
1632
|
|
|
779
1633
|
function ensureGlobalCssImports(content) {
|
|
780
|
-
const
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
1634
|
+
const cleaned = splitLinesPreservingEndings(content)
|
|
1635
|
+
.filter((line) => {
|
|
1636
|
+
const importSpecifier = readCssImportSpecifier(line)
|
|
1637
|
+
return (
|
|
1638
|
+
importSpecifier !== "tailwindcss" &&
|
|
1639
|
+
!isManagedInterFontImport(importSpecifier)
|
|
1640
|
+
)
|
|
1641
|
+
})
|
|
1642
|
+
.join("")
|
|
786
1643
|
.trimStart()
|
|
787
1644
|
return `${TAILWIND_IMPORT}\n${FONT_IMPORTS.join("\n")}\n${cleaned}`
|
|
788
1645
|
}
|
|
789
1646
|
|
|
1647
|
+
function splitLinesPreservingEndings(content) {
|
|
1648
|
+
const lines = []
|
|
1649
|
+
let lineStart = 0
|
|
1650
|
+
let index = 0
|
|
1651
|
+
|
|
1652
|
+
while (index < content.length) {
|
|
1653
|
+
if (content[index] === "\r" && content[index + 1] === "\n") {
|
|
1654
|
+
lines.push(content.slice(lineStart, index + 2))
|
|
1655
|
+
index += 2
|
|
1656
|
+
lineStart = index
|
|
1657
|
+
continue
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
if (content[index] === "\n") {
|
|
1661
|
+
lines.push(content.slice(lineStart, index + 1))
|
|
1662
|
+
index += 1
|
|
1663
|
+
lineStart = index
|
|
1664
|
+
continue
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
index += 1
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
if (lineStart < content.length) {
|
|
1671
|
+
lines.push(content.slice(lineStart))
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
return lines
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
function readCssImportSpecifier(line) {
|
|
1678
|
+
const trimmed = line.trim()
|
|
1679
|
+
const importPrefix = "@import"
|
|
1680
|
+
if (!trimmed.startsWith(importPrefix)) return null
|
|
1681
|
+
|
|
1682
|
+
let index = importPrefix.length
|
|
1683
|
+
while (trimmed[index] === " " || trimmed[index] === "\t") {
|
|
1684
|
+
index += 1
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
const quote = trimmed[index]
|
|
1688
|
+
if (quote !== "\"" && quote !== "'") return null
|
|
1689
|
+
|
|
1690
|
+
const start = index + 1
|
|
1691
|
+
index = start
|
|
1692
|
+
while (index < trimmed.length && trimmed[index] !== quote) {
|
|
1693
|
+
index += 1
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
if (index >= trimmed.length) return null
|
|
1697
|
+
const specifier = trimmed.slice(start, index)
|
|
1698
|
+
index += 1
|
|
1699
|
+
|
|
1700
|
+
while (trimmed[index] === " " || trimmed[index] === "\t") {
|
|
1701
|
+
index += 1
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
if (trimmed[index] === ";") index += 1
|
|
1705
|
+
|
|
1706
|
+
while (trimmed[index] === " " || trimmed[index] === "\t") {
|
|
1707
|
+
index += 1
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
return index === trimmed.length ? specifier : null
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
function isManagedInterFontImport(specifier) {
|
|
1714
|
+
if (!specifier) return false
|
|
1715
|
+
if (specifier === "@fontsource/inter") return true
|
|
1716
|
+
if (specifier === "@fontsource-variable/inter") return true
|
|
1717
|
+
|
|
1718
|
+
for (const weight of ["400", "500", "600", "700"]) {
|
|
1719
|
+
if (specifier === `@fontsource/inter/${weight}.css`) return true
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
return false
|
|
1723
|
+
}
|
|
1724
|
+
|
|
790
1725
|
function appendAliasSegment(alias, segment) {
|
|
791
1726
|
if (!alias) return null
|
|
792
1727
|
return `${alias.replace(/\/$/, "")}/${segment}`
|