tw-migrate 0.5.0 → 0.7.0
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 +13 -129
- package/dist/bin.js +9 -3
- package/dist/index.js +816 -171
- package/package.json +12 -8
package/dist/index.js
CHANGED
|
@@ -2,20 +2,18 @@ import { createRequire } from "node:module";
|
|
|
2
2
|
import { promisify } from "node:util";
|
|
3
3
|
import { chmod, lstat, readFile, readdir, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
4
4
|
import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
5
|
+
import * as z from "zod/mini";
|
|
6
|
+
import { FILE_HEADERS_ONLY, createTwoFilesPatch } from "diff";
|
|
5
7
|
import { execFile } from "node:child_process";
|
|
6
8
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
7
9
|
import { parse } from "parse5";
|
|
8
10
|
//#region src/util/diff.ts
|
|
9
11
|
function unifiedDiff(path, before, after) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
`@@ -1,${oldLines.length} +1,${newLines.length} @@\n`,
|
|
16
|
-
...oldLines.map((line, index) => `${index === oldLines.length - 1 && line === "" ? " " : "-"}${line}\n`),
|
|
17
|
-
...newLines.map((line, index) => `${index === newLines.length - 1 && line === "" ? " " : "+"}${line}\n`)
|
|
18
|
-
].join("");
|
|
12
|
+
if (before === after) return "";
|
|
13
|
+
return createTwoFilesPatch(`a/${path}`, `b/${path}`, before, after, void 0, void 0, {
|
|
14
|
+
context: 3,
|
|
15
|
+
headerOptions: FILE_HEADERS_ONLY
|
|
16
|
+
});
|
|
19
17
|
}
|
|
20
18
|
//#endregion
|
|
21
19
|
//#region src/native.ts
|
|
@@ -64,13 +62,17 @@ function sourceImport(value) {
|
|
|
64
62
|
return object(value) && typeof value.specifier === "string" && typeof value.typeOnly === "boolean" && typeof value.dynamic === "boolean";
|
|
65
63
|
}
|
|
66
64
|
function sourceAnalysisResult(value) {
|
|
67
|
-
return object(value) && Array.isArray(value.imports) && value.imports.every(sourceImport) && Array.isArray(value.staticImports) && value.staticImports.every((item) => typeof item === "string") && Array.isArray(value.defaultImports) && value.defaultImports.every((item) => object(item) && typeof item.source === "string" && typeof item.local === "string") && Array.isArray(value.vueGlobPatterns) && value.vueGlobPatterns.every((item) => typeof item === "string") && Array.isArray(value.useCssModuleLocals) && value.useCssModuleLocals.every((item) => typeof item === "string") && Array.isArray(value.unboundReferences) && value.unboundReferences.every((item) => typeof item === "string") && [
|
|
65
|
+
return object(value) && Array.isArray(value.imports) && value.imports.every(sourceImport) && Array.isArray(value.staticImports) && value.staticImports.every((item) => typeof item === "string") && Array.isArray(value.defaultImports) && value.defaultImports.every((item) => object(item) && typeof item.source === "string" && typeof item.local === "string") && Array.isArray(value.vueGlobPatterns) && value.vueGlobPatterns.every((item) => typeof item === "string") && Array.isArray(value.templatePrefixes) && value.templatePrefixes.every((item) => typeof item === "string") && Array.isArray(value.useCssModuleLocals) && value.useCssModuleLocals.every((item) => typeof item === "string") && Array.isArray(value.unboundReferences) && value.unboundReferences.every((item) => typeof item === "string") && Array.isArray(value.stylesheetLinks) && value.stylesheetLinks.every((item) => typeof item === "string") && Array.isArray(value.customProperties) && value.customProperties.every((item) => typeof item === "string") && [
|
|
68
66
|
value.vueGlobUnverifiable,
|
|
69
67
|
value.hasDynamicImport,
|
|
70
68
|
value.hasVueFallthroughMacro,
|
|
71
69
|
value.usesCssModule,
|
|
72
70
|
value.hasUnboundUseCssModule,
|
|
73
|
-
value.definesRootUseCssModule
|
|
71
|
+
value.definesRootUseCssModule,
|
|
72
|
+
value.stylesheetLinksUnverifiable,
|
|
73
|
+
value.rendersStyleElement,
|
|
74
|
+
value.injectsMarkup,
|
|
75
|
+
value.customPropertiesUnbounded
|
|
74
76
|
].every((item) => typeof item === "boolean");
|
|
75
77
|
}
|
|
76
78
|
const ANALYSIS_CACHE_LIMIT = 4096;
|
|
@@ -84,7 +86,7 @@ function bound(cache) {
|
|
|
84
86
|
}
|
|
85
87
|
}
|
|
86
88
|
function expressionAnalysis(path, source) {
|
|
87
|
-
return decode("expression analysis", binding.expressionAnalysis(path, source), (value) => object(value) && (typeof value.staticString === "string" || value.staticString === null) && (typeof value.vueModuleMember === "string" || value.vueModuleMember === null) && typeof value.usesCssModule === "boolean" && typeof value.referencesUseCssModule === "boolean" && Array.isArray(value.references) && value.references.every((item) => typeof item === "string"));
|
|
89
|
+
return decode("expression analysis", binding.expressionAnalysis(path, source), (value) => object(value) && (typeof value.staticString === "string" || value.staticString === null) && (typeof value.vueModuleMember === "string" || value.vueModuleMember === null) && typeof value.usesCssModule === "boolean" && typeof value.referencesUseCssModule === "boolean" && Array.isArray(value.references) && value.references.every((item) => typeof item === "string") && Array.isArray(value.templatePrefixes) && value.templatePrefixes.every((item) => typeof item === "string"));
|
|
88
90
|
}
|
|
89
91
|
function sourceAnalysis(path, source) {
|
|
90
92
|
const cached = sourceAnalysisCache.get(path);
|
|
@@ -97,6 +99,12 @@ function sourceAnalysis(path, source) {
|
|
|
97
99
|
bound(sourceAnalysisCache);
|
|
98
100
|
return analysis;
|
|
99
101
|
}
|
|
102
|
+
function fontFamilyStack(value) {
|
|
103
|
+
return decode("font family stack", binding.fontFamilyStack(value), (parsed) => parsed === null || object(parsed) && typeof parsed.value === "string" && object(parsed.firstFamily) && typeof parsed.firstFamily.name === "string" && (parsed.firstFamily.kind === "name" || parsed.firstFamily.kind === "generic" || parsed.firstFamily.kind === "css-wide"));
|
|
104
|
+
}
|
|
105
|
+
function compiledShape(css) {
|
|
106
|
+
return decode("compiled shape", binding.compiledShape(css), (value) => object(value) && Array.isArray(value.declarations) && value.declarations.every((item) => object(item) && typeof item.property === "string" && typeof item.important === "boolean" && typeof item.value === "string") && Array.isArray(value.referencedProperties) && value.referencedProperties.every((item) => typeof item === "string"));
|
|
107
|
+
}
|
|
100
108
|
function stylesheetAnalysis(path, source) {
|
|
101
109
|
const cached = stylesheetAnalysisCache.get(path);
|
|
102
110
|
if (cached?.source === source) return cached.analysis;
|
|
@@ -104,7 +112,7 @@ function stylesheetAnalysis(path, source) {
|
|
|
104
112
|
value.unverifiable,
|
|
105
113
|
value.scopeEscapesUnverifiable,
|
|
106
114
|
value.selectorsUnverifiable
|
|
107
|
-
].every((item) => typeof item === "boolean") && Array.isArray(value.scopeEscapes) && value.scopeEscapes.every((item) => typeof item === "string") && Array.isArray(value.scopeShadowCss) && value.scopeShadowCss.every((item) => typeof item === "string") && object(value.themeTokens) && Object.values(value.themeTokens).every((item) => typeof item === "string") && Array.isArray(value.globalAtRuleIdentities) && value.globalAtRuleIdentities.every((item) => typeof item === "string") && typeof value.globalAtRulesUnverifiable === "boolean");
|
|
115
|
+
].every((item) => typeof item === "boolean") && Array.isArray(value.scopeEscapes) && value.scopeEscapes.every((item) => typeof item === "string") && Array.isArray(value.scopeShadowCss) && value.scopeShadowCss.every((item) => typeof item === "string") && object(value.themeTokens) && Object.values(value.themeTokens).every((item) => typeof item === "string") && Array.isArray(value.globalAtRuleIdentities) && value.globalAtRuleIdentities.every((item) => typeof item === "string") && typeof value.globalAtRulesUnverifiable === "boolean" && Array.isArray(value.classNames) && value.classNames.every((item) => typeof item === "string") && typeof value.classReservationsUnbounded === "boolean" && Array.isArray(value.customProperties) && value.customProperties.every((item) => typeof item === "string") && typeof value.customPropertiesUnbounded === "boolean");
|
|
108
116
|
stylesheetAnalysisCache.set(path, {
|
|
109
117
|
source,
|
|
110
118
|
analysis
|
|
@@ -221,6 +229,10 @@ function sourcePathFromMap(source, sourceBase) {
|
|
|
221
229
|
}
|
|
222
230
|
//#endregion
|
|
223
231
|
//#region src/util/shared.ts
|
|
232
|
+
const projectPackageSchema = z.looseObject({
|
|
233
|
+
name: z.optional(z.string()),
|
|
234
|
+
private: z.optional(z.boolean())
|
|
235
|
+
});
|
|
224
236
|
const SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
225
237
|
".html",
|
|
226
238
|
".vue",
|
|
@@ -348,6 +360,11 @@ function stylesheetReferenceTargets(importer, reference, styleSources) {
|
|
|
348
360
|
join(target, `index${syntax}`)
|
|
349
361
|
])).filter((path) => styleSources.has(path));
|
|
350
362
|
}
|
|
363
|
+
function localHrefTarget(href) {
|
|
364
|
+
const target = href.split(/[?#]/, 1)[0];
|
|
365
|
+
if (!target || /^[a-z][a-z0-9+.-]*:|^\/\//i.test(target)) return void 0;
|
|
366
|
+
return target;
|
|
367
|
+
}
|
|
351
368
|
function cssImports(path, source) {
|
|
352
369
|
return stylesheetAnalysis(path, source).imports;
|
|
353
370
|
}
|
|
@@ -510,6 +527,9 @@ async function collectFiles(root, include) {
|
|
|
510
527
|
//#endregion
|
|
511
528
|
//#region src/parser/html.ts
|
|
512
529
|
const TEMPLATE_MARKERS = /\{\{|\}\}|\$\{|<%|%>|<#|#>|\[%|%\]/;
|
|
530
|
+
/** Balanced template expressions, for masking a templated attribute value
|
|
531
|
+
* before extracting its static class-prefix tokens. */
|
|
532
|
+
const TEMPLATE_EXPRESSIONS = /\{\{[\s\S]*?\}\}|\$\{[\s\S]*?\}|<%[\s\S]*?%>|<#[\s\S]*?#>|\[%[\s\S]*?%\]/g;
|
|
513
533
|
function parseHtmlSource(path, source) {
|
|
514
534
|
const errors = [];
|
|
515
535
|
const document = parse(source, {
|
|
@@ -524,6 +544,9 @@ function parseHtmlSource(path, source) {
|
|
|
524
544
|
const bases = [];
|
|
525
545
|
const elements = [];
|
|
526
546
|
const dynamicAttributes = [];
|
|
547
|
+
const dynamicClassValues = [];
|
|
548
|
+
const styleAttributeValues = [];
|
|
549
|
+
const handlerTexts = [];
|
|
527
550
|
const scriptTexts = [];
|
|
528
551
|
let hasStyle = false;
|
|
529
552
|
const unquoted = (attribute) => source[attribute.start - 1] !== "\"" && source[attribute.start - 1] !== "'";
|
|
@@ -534,6 +557,9 @@ function parseHtmlSource(path, source) {
|
|
|
534
557
|
}
|
|
535
558
|
if (node.tagName) {
|
|
536
559
|
const attributes = new Map((node.attrs ?? []).map((attribute) => [attribute.name, attribute.value]));
|
|
560
|
+
const style = attributes.get("style");
|
|
561
|
+
if (style !== void 0 && style !== "") styleAttributeValues.push(style);
|
|
562
|
+
for (const [name, value] of attributes) if (name.startsWith("on") && value !== "") handlerTexts.push(value);
|
|
537
563
|
const locations = node.sourceCodeLocation?.attrs;
|
|
538
564
|
if (locations) {
|
|
539
565
|
if (node.tagName === "link" && stylesheetRel(attributes.get("rel")) && !attributes.has("disabled")) {
|
|
@@ -560,15 +586,20 @@ function parseHtmlSource(path, source) {
|
|
|
560
586
|
}
|
|
561
587
|
let classAttribute = locatedAttribute(source, locations.class, attributes.get("class"));
|
|
562
588
|
const idAttribute = locatedAttribute(source, locations.id, attributes.get("id"));
|
|
563
|
-
const
|
|
589
|
+
const unparsedClass = !classAttribute && idAttribute && locations.class ? {
|
|
564
590
|
start: locations.class.startOffset,
|
|
565
591
|
end: locations.class.endOffset
|
|
566
|
-
} : void 0
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
592
|
+
} : void 0;
|
|
593
|
+
const dynamic = unparsedClass ?? [classAttribute, idAttribute].find((attribute) => attribute && (!attribute.writable || TEMPLATE_MARKERS.test(attribute.value))) ?? (classAttribute && unquoted(classAttribute) ? classAttribute : void 0);
|
|
594
|
+
if (dynamic) {
|
|
595
|
+
dynamicAttributes.push({
|
|
596
|
+
start: dynamic.start,
|
|
597
|
+
end: dynamic.end
|
|
598
|
+
});
|
|
599
|
+
const decoded = attributes.get("class");
|
|
600
|
+
const entityBearing = classAttribute !== void 0 && dynamic === classAttribute && classAttribute.writable === false;
|
|
601
|
+
if (dynamic !== idAttribute && decoded !== void 0 && (TEMPLATE_MARKERS.test(decoded) || entityBearing || dynamic === unparsedClass)) dynamicClassValues.push(decoded);
|
|
602
|
+
} else if (classAttribute || idAttribute) {
|
|
572
603
|
if (!classAttribute && idAttribute) {
|
|
573
604
|
const startTag = node.sourceCodeLocation?.startTag;
|
|
574
605
|
if (startTag && source[startTag.endOffset - 1] === ">") {
|
|
@@ -599,6 +630,9 @@ function parseHtmlSource(path, source) {
|
|
|
599
630
|
elements,
|
|
600
631
|
dynamicAttributes
|
|
601
632
|
}),
|
|
633
|
+
dynamicClassValues,
|
|
634
|
+
styleAttributeValues,
|
|
635
|
+
handlerText: handlerTexts.join(";\n"),
|
|
602
636
|
scriptText: scriptTexts.join("\n"),
|
|
603
637
|
hasStyle
|
|
604
638
|
};
|
|
@@ -691,18 +725,129 @@ function locatedAttribute(source, location, parsedValue) {
|
|
|
691
725
|
};
|
|
692
726
|
}
|
|
693
727
|
//#endregion
|
|
694
|
-
//#region src/
|
|
695
|
-
function
|
|
696
|
-
return specifier === "tailwindcss" || specifier === "tailwindcss/utilities" || specifier === "tailwindcss/utilities.css";
|
|
728
|
+
//#region src/tailwind.ts
|
|
729
|
+
function findTailwindEntries(stylePaths, styleSources) {
|
|
730
|
+
return stylePaths.filter((path) => extname(path) === ".css" && importEntries(styleSources.get(path) ?? "").some(({ specifier }) => specifier === "tailwindcss" || specifier === "tailwindcss/index" || specifier === "tailwindcss/index.css" || specifier === "tailwindcss/utilities" || specifier === "tailwindcss/utilities.css"));
|
|
731
|
+
}
|
|
732
|
+
const externalStylesheetUrl = z.url({ protocol: /^(https?|data)$/ });
|
|
733
|
+
const tailwindPackageSchema = z.object({ version: z.string() });
|
|
734
|
+
function isExternalStylesheet(specifier) {
|
|
735
|
+
return specifier.startsWith("//") || /^(?:https?:\/\/|data:)/i.test(specifier) && externalStylesheetUrl.safeParse(specifier).success;
|
|
736
|
+
}
|
|
737
|
+
function selectTailwindEntry(entries, configuredPath) {
|
|
738
|
+
if (configuredPath) return configuredPath;
|
|
739
|
+
if (entries.length === 0) throw new Error("No Tailwind v4 CSS entry was found. Pass --tailwind-css.");
|
|
740
|
+
if (entries.length > 1) throw new Error("Multiple Tailwind CSS entries were found. Pass --tailwind-css.");
|
|
741
|
+
return entries[0];
|
|
742
|
+
}
|
|
743
|
+
async function loadTailwind(packageRoot, tailwindCss, snapshots, workspaceRoot) {
|
|
744
|
+
const projectRequire = createRequire(join(packageRoot, "package.json"));
|
|
745
|
+
let packagePath;
|
|
746
|
+
try {
|
|
747
|
+
packagePath = projectRequire.resolve("tailwindcss/package.json");
|
|
748
|
+
} catch {
|
|
749
|
+
throw new Error("Tailwind v4 must be installed in the target project.");
|
|
750
|
+
}
|
|
751
|
+
const packageJson = tailwindPackageSchema.parse(JSON.parse(await readFile(packagePath, "utf8")));
|
|
752
|
+
if (!packageJson.version.startsWith("4.")) throw new Error(`Tailwind v4 is required; found ${packageJson.version}.`);
|
|
753
|
+
const { __unstable__loadDesignSystem: loadDesignSystem } = await loadProjectModule(packageRoot, "tailwindcss", "Tailwind v4 must be installed in the target project.");
|
|
754
|
+
const css = await snapshotFile(snapshots, tailwindCss);
|
|
755
|
+
const base = dirname(tailwindCss);
|
|
756
|
+
const tailwindRoot = dirname(packagePath);
|
|
757
|
+
const loadModule = async (id, moduleBase) => {
|
|
758
|
+
const path = createRequire(join(moduleBase, "package.json")).resolve(id);
|
|
759
|
+
if (isProjectInput(workspaceRoot, path)) await snapshotFile(snapshots, path);
|
|
760
|
+
const imported = await import(pathToFileURL(path).href);
|
|
761
|
+
return {
|
|
762
|
+
path,
|
|
763
|
+
base: dirname(path),
|
|
764
|
+
module: imported.default ?? imported
|
|
765
|
+
};
|
|
766
|
+
};
|
|
767
|
+
const loadStylesheet = async (id, sheetBase) => {
|
|
768
|
+
if (isExternalStylesheet(id)) return {
|
|
769
|
+
content: "",
|
|
770
|
+
base: sheetBase
|
|
771
|
+
};
|
|
772
|
+
let path;
|
|
773
|
+
if (id === "tailwindcss") path = join(tailwindRoot, "index.css");
|
|
774
|
+
else if (id.startsWith("tailwindcss/")) {
|
|
775
|
+
const subpath = id.slice(12);
|
|
776
|
+
path = join(tailwindRoot, subpath.endsWith(".css") ? subpath : `${subpath}.css`);
|
|
777
|
+
} else if (id.startsWith(".") || isAbsolute(id)) path = resolve(sheetBase, id);
|
|
778
|
+
else path = projectRequire.resolve(id);
|
|
779
|
+
return {
|
|
780
|
+
content: isProjectInput(workspaceRoot, path) ? await snapshotFile(snapshots, path) : await readFile(path, "utf8"),
|
|
781
|
+
base: dirname(path)
|
|
782
|
+
};
|
|
783
|
+
};
|
|
784
|
+
const defaultTheme = await readFile(join(dirname(packagePath), "theme.css"), "utf8");
|
|
785
|
+
const graphSources = [];
|
|
786
|
+
const referenceTokens = /* @__PURE__ */ new Set();
|
|
787
|
+
const themeTokens = {
|
|
788
|
+
...extractThemeTokens("tailwind-default.theme.css", defaultTheme),
|
|
789
|
+
...await extractThemeTokensFromGraph(tailwindCss, css, base, loadStylesheet, graphSources, referenceTokens)
|
|
790
|
+
};
|
|
791
|
+
graphSources.push({
|
|
792
|
+
path: tailwindCss,
|
|
793
|
+
source: css
|
|
794
|
+
});
|
|
795
|
+
const designSystem = await loadDesignSystem(css, {
|
|
796
|
+
base,
|
|
797
|
+
loadModule,
|
|
798
|
+
loadStylesheet
|
|
799
|
+
});
|
|
800
|
+
const loadWith = (replacement) => loadDesignSystem(replacement, {
|
|
801
|
+
base,
|
|
802
|
+
loadModule,
|
|
803
|
+
loadStylesheet
|
|
804
|
+
});
|
|
805
|
+
return {
|
|
806
|
+
designSystem,
|
|
807
|
+
css,
|
|
808
|
+
path: tailwindCss,
|
|
809
|
+
themeTokens,
|
|
810
|
+
referenceTokens,
|
|
811
|
+
graphSources,
|
|
812
|
+
loadWith
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
function extractThemeTokens(key, css) {
|
|
816
|
+
return stylesheetAnalysis(key, css).themeTokens;
|
|
817
|
+
}
|
|
818
|
+
function importEntries(css) {
|
|
819
|
+
return (cssDirectives$1(css) ?? []).flatMap((directive) => directive !== null && typeof directive === "object" && "kind" in directive && directive.kind === "import" && "specifier" in directive && typeof directive.specifier === "string" ? [{
|
|
820
|
+
specifier: directive.specifier,
|
|
821
|
+
reference: "reference" in directive && directive.reference === true
|
|
822
|
+
}] : []);
|
|
697
823
|
}
|
|
698
|
-
function
|
|
699
|
-
|
|
824
|
+
async function extractThemeTokensFromGraph(identity, css, base, loadStylesheet, graphSources, referenceTokens, inReference = false, seen = /* @__PURE__ */ new Set()) {
|
|
825
|
+
const tokens = {};
|
|
826
|
+
for (const { specifier: spec, reference } of importEntries(css)) {
|
|
827
|
+
if (isExternalStylesheet(spec)) continue;
|
|
828
|
+
const key = `${base}\0${spec}`;
|
|
829
|
+
if (seen.has(key)) continue;
|
|
830
|
+
seen.add(key);
|
|
831
|
+
const loaded = await loadStylesheet(spec, base);
|
|
832
|
+
if (spec !== "tailwindcss" && !spec.startsWith("tailwindcss/")) graphSources.push({
|
|
833
|
+
path: key,
|
|
834
|
+
source: loaded.content
|
|
835
|
+
});
|
|
836
|
+
Object.assign(tokens, await extractThemeTokensFromGraph(key, loaded.content, loaded.base, loadStylesheet, graphSources, referenceTokens, inReference || reference, seen));
|
|
837
|
+
}
|
|
838
|
+
const own = extractThemeTokens(`${identity}.theme.css`, css);
|
|
839
|
+
if (inReference) for (const token of Object.keys(own)) referenceTokens.add(token);
|
|
840
|
+
return Object.assign(tokens, own);
|
|
700
841
|
}
|
|
842
|
+
function invalidCandidates(system, candidates) {
|
|
843
|
+
const generated = system.candidatesToCss(candidates);
|
|
844
|
+
return candidates.filter((_, index) => generated[index] === null);
|
|
845
|
+
}
|
|
846
|
+
//#endregion
|
|
847
|
+
//#region src/plan/entry.ts
|
|
701
848
|
function tailwindEntryCatalog(styleSources, pathOwners) {
|
|
702
849
|
const catalog = /* @__PURE__ */ new Map();
|
|
703
|
-
for (const
|
|
704
|
-
if (extname(path) !== ".css") continue;
|
|
705
|
-
if (!hasTailwindImport(source)) continue;
|
|
850
|
+
for (const path of findTailwindEntries([...styleSources.keys()], styleSources)) {
|
|
706
851
|
const owner = pathOwners.get(path);
|
|
707
852
|
if (owner === void 0) continue;
|
|
708
853
|
const entries = catalog.get(owner) ?? [];
|
|
@@ -735,7 +880,7 @@ function entryGraphSheets(entry, styleSources) {
|
|
|
735
880
|
if (directive.kind !== "import") continue;
|
|
736
881
|
if (directive.specifier === null) return null;
|
|
737
882
|
const spec = directive.specifier;
|
|
738
|
-
if (directive.tailwind) continue;
|
|
883
|
+
if (directive.tailwind || isExternalStylesheet(spec)) continue;
|
|
739
884
|
if (!spec.startsWith(".") && !spec.startsWith("/")) {
|
|
740
885
|
let resolved;
|
|
741
886
|
try {
|
|
@@ -778,8 +923,10 @@ function scanProof(options) {
|
|
|
778
923
|
} else if (directive.kind === "import" && directive.tailwind) {
|
|
779
924
|
if (directive.sourceUnreadable) return null;
|
|
780
925
|
if (directive.source === "none") automaticDisabled = true;
|
|
781
|
-
else if (directive.source !== null)
|
|
782
|
-
|
|
926
|
+
else if (directive.source !== null) {
|
|
927
|
+
if (coversChild(directive.source)) literal = true;
|
|
928
|
+
else automaticDisabled = true;
|
|
929
|
+
}
|
|
783
930
|
}
|
|
784
931
|
}
|
|
785
932
|
if (literal) return "literal";
|
|
@@ -965,8 +1112,8 @@ function htmlLinksEntry(file, entry) {
|
|
|
965
1112
|
return parsed.links.some((link) => {
|
|
966
1113
|
const media = link.media.trim().toLowerCase();
|
|
967
1114
|
if (media !== "" && media !== "all") return false;
|
|
968
|
-
const href = link.href
|
|
969
|
-
if (
|
|
1115
|
+
const href = localHrefTarget(link.href);
|
|
1116
|
+
if (href === void 0) return false;
|
|
970
1117
|
return resolve(dirname(file.path), href) === entry;
|
|
971
1118
|
});
|
|
972
1119
|
} catch {
|
|
@@ -1142,8 +1289,8 @@ function addInferredPreprocessorContext(state) {
|
|
|
1142
1289
|
return true;
|
|
1143
1290
|
}
|
|
1144
1291
|
function localHtmlReference(packageRoot, base, reference) {
|
|
1145
|
-
const path = reference
|
|
1146
|
-
if (
|
|
1292
|
+
const path = localHrefTarget(reference);
|
|
1293
|
+
if (path === void 0) return void 0;
|
|
1147
1294
|
let decoded;
|
|
1148
1295
|
try {
|
|
1149
1296
|
decoded = decodeURIComponent(path);
|
|
@@ -1177,112 +1324,297 @@ function htmlWarning(code, file, start, end, message) {
|
|
|
1177
1324
|
};
|
|
1178
1325
|
}
|
|
1179
1326
|
//#endregion
|
|
1180
|
-
//#region src/
|
|
1181
|
-
|
|
1182
|
-
|
|
1327
|
+
//#region src/plan/canonicalize.ts
|
|
1328
|
+
const caches = /* @__PURE__ */ new WeakMap();
|
|
1329
|
+
function canonicalCandidate(system, candidate) {
|
|
1330
|
+
if (typeof system.canonicalizeCandidates !== "function") return null;
|
|
1331
|
+
let cache = caches.get(system);
|
|
1332
|
+
if (cache === void 0) {
|
|
1333
|
+
cache = /* @__PURE__ */ new Map();
|
|
1334
|
+
caches.set(system, cache);
|
|
1335
|
+
}
|
|
1336
|
+
const cached = cache.get(candidate);
|
|
1337
|
+
if (cached !== void 0) return cached;
|
|
1338
|
+
let canonical;
|
|
1339
|
+
try {
|
|
1340
|
+
const results = system.canonicalizeCandidates([candidate]);
|
|
1341
|
+
canonical = results.length === 1 && typeof results[0] === "string" && results[0] !== candidate ? results[0] : null;
|
|
1342
|
+
} catch {
|
|
1343
|
+
canonical = null;
|
|
1344
|
+
}
|
|
1345
|
+
cache.set(candidate, canonical);
|
|
1346
|
+
return canonical;
|
|
1347
|
+
}
|
|
1348
|
+
function spellingReservations(styleSources, resolvedExtras, resolvedImports = /* @__PURE__ */ new Set()) {
|
|
1349
|
+
const reservations = {
|
|
1350
|
+
names: /* @__PURE__ */ new Set(),
|
|
1351
|
+
prefixes: /* @__PURE__ */ new Set(),
|
|
1352
|
+
unbounded: false,
|
|
1353
|
+
properties: /* @__PURE__ */ new Set(),
|
|
1354
|
+
propertiesUnbounded: false
|
|
1355
|
+
};
|
|
1356
|
+
for (const [path, source] of styleSources) scanStylesheetReservations(path, source, styleSources, reservations, resolvedImports);
|
|
1357
|
+
for (const [path, source] of resolvedExtras) try {
|
|
1358
|
+
const analysis = stylesheetAnalysis(path, source);
|
|
1359
|
+
for (const name of analysis.classNames) reservations.names.add(name);
|
|
1360
|
+
if (analysis.classReservationsUnbounded) reservations.unbounded = true;
|
|
1361
|
+
for (const property of analysis.customProperties) reservations.properties.add(property);
|
|
1362
|
+
if (analysis.customPropertiesUnbounded) reservations.propertiesUnbounded = true;
|
|
1363
|
+
if (entryDirectiveHazard(source)) reservations.unbounded = true;
|
|
1364
|
+
} catch {
|
|
1365
|
+
reservations.unbounded = true;
|
|
1366
|
+
}
|
|
1367
|
+
return reservations;
|
|
1183
1368
|
}
|
|
1184
|
-
function
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1369
|
+
function scanStylesheetReservations(path, source, styleSources, reservations, resolvedImports = /* @__PURE__ */ new Set()) {
|
|
1370
|
+
try {
|
|
1371
|
+
const analysis = stylesheetAnalysis(path, source);
|
|
1372
|
+
for (const name of analysis.classNames) reservations.names.add(name);
|
|
1373
|
+
if (analysis.classReservationsUnbounded) reservations.unbounded = true;
|
|
1374
|
+
for (const property of analysis.customProperties) reservations.properties.add(property);
|
|
1375
|
+
if (analysis.customPropertiesUnbounded) reservations.propertiesUnbounded = true;
|
|
1376
|
+
if (analysis.unverifiable) reservations.unbounded = true;
|
|
1377
|
+
for (const reference of analysis.references) {
|
|
1378
|
+
if (reference === "tailwindcss" || reference.startsWith("tailwindcss/")) continue;
|
|
1379
|
+
if (reference.startsWith("sass:")) continue;
|
|
1380
|
+
if (resolvedImports.has(`${dirname(path)}\0${reference}`) || styleSources.has(reference)) continue;
|
|
1381
|
+
if (stylesheetReferenceTargets(path, reference, styleSources).length === 0) reservations.unbounded = true;
|
|
1382
|
+
}
|
|
1383
|
+
} catch {
|
|
1384
|
+
reservations.unbounded = true;
|
|
1385
|
+
}
|
|
1189
1386
|
}
|
|
1190
|
-
|
|
1191
|
-
const
|
|
1192
|
-
|
|
1387
|
+
function entryDirectiveHazard(source) {
|
|
1388
|
+
const directives = cssDirectives$1(source);
|
|
1389
|
+
if (directives === null) return true;
|
|
1390
|
+
return directives.some((directive) => {
|
|
1391
|
+
if (directive === null || typeof directive !== "object" || !("kind" in directive)) return false;
|
|
1392
|
+
if (directive.kind === "import") return "source" in directive && directive.source !== null || "sourceUnreadable" in directive && directive.sourceUnreadable === true;
|
|
1393
|
+
if (directive.kind === "source") return "not" in directive && directive.not === true || "unreadable" in directive && directive.unreadable === true;
|
|
1394
|
+
return directive.kind === "other" && "name" in directive && (directive.name === "plugin" || directive.name === "config");
|
|
1395
|
+
});
|
|
1396
|
+
}
|
|
1397
|
+
function scanInlineStyleReservations(key, value, reservations) {
|
|
1193
1398
|
try {
|
|
1194
|
-
|
|
1399
|
+
const analysis = stylesheetAnalysis(key, `.x{${value}}`);
|
|
1400
|
+
for (const property of analysis.customProperties) reservations.properties.add(property);
|
|
1401
|
+
if (analysis.customPropertiesUnbounded) reservations.propertiesUnbounded = true;
|
|
1195
1402
|
} catch {
|
|
1196
|
-
|
|
1403
|
+
reservations.propertiesUnbounded = true;
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
function reservedSpelling(reservations, spelling) {
|
|
1407
|
+
if (reservations.names.has(spelling)) return true;
|
|
1408
|
+
for (const prefix of reservations.prefixes) if (spelling.startsWith(prefix)) return true;
|
|
1409
|
+
return false;
|
|
1410
|
+
}
|
|
1411
|
+
function utilitySegment(candidate) {
|
|
1412
|
+
let depth = 0;
|
|
1413
|
+
let start = 0;
|
|
1414
|
+
for (let index = 0; index < candidate.length; index += 1) {
|
|
1415
|
+
const character = candidate[index];
|
|
1416
|
+
if (character === "[" || character === "(") depth += 1;
|
|
1417
|
+
else if (character === "]" || character === ")") depth -= 1;
|
|
1418
|
+
else if (character === ":" && depth === 0) start = index + 1;
|
|
1419
|
+
}
|
|
1420
|
+
return candidate.slice(start);
|
|
1421
|
+
}
|
|
1422
|
+
function sameShape(left, right) {
|
|
1423
|
+
return left.length === right.length && left.every((declaration, index) => declaration.property === right[index].property && declaration.important === right[index].important);
|
|
1424
|
+
}
|
|
1425
|
+
function acceptedCandidateAliases(system, probes, reservations) {
|
|
1426
|
+
const aliases = {};
|
|
1427
|
+
if (reservations.unbounded) return aliases;
|
|
1428
|
+
const prefix = system.theme.prefix;
|
|
1429
|
+
for (const probe of new Set(probes)) {
|
|
1430
|
+
const canonical = canonicalCandidate(system, probe);
|
|
1431
|
+
if (canonical === null) continue;
|
|
1432
|
+
const unprefixed = prefix !== null && canonical.startsWith(`${prefix}:`) ? canonical.slice(prefix.length + 1) : null;
|
|
1433
|
+
const segment = utilitySegment(canonical).replace(/!$/, "");
|
|
1434
|
+
if (segment.includes("[") || reservedSpelling(reservations, canonical) || unprefixed !== null && reservedSpelling(reservations, unprefixed) || reservedSpelling(reservations, segment)) continue;
|
|
1435
|
+
const [compiledProbe, compiledCanonical] = system.candidatesToCss([probe, canonical]);
|
|
1436
|
+
if (compiledProbe == null || compiledCanonical == null) continue;
|
|
1437
|
+
let probeShape;
|
|
1438
|
+
let canonicalShape;
|
|
1439
|
+
try {
|
|
1440
|
+
probeShape = compiledShape(compiledProbe);
|
|
1441
|
+
canonicalShape = compiledShape(compiledCanonical);
|
|
1442
|
+
} catch {
|
|
1443
|
+
continue;
|
|
1444
|
+
}
|
|
1445
|
+
if (canonicalShape.referencedProperties.length > 0 || probeShape.referencedProperties.length > 0 || !sameShape(probeShape.declarations, canonicalShape.declarations)) continue;
|
|
1446
|
+
aliases[probe] = canonical;
|
|
1197
1447
|
}
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
const
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1448
|
+
return aliases;
|
|
1449
|
+
}
|
|
1450
|
+
//#endregion
|
|
1451
|
+
//#region src/plan/fonts.ts
|
|
1452
|
+
function fontTokenName(family) {
|
|
1453
|
+
const collapsed = family.normalize("NFC").toLowerCase().replace(/[^\p{L}\p{N}]+/gu, "-").replace(/^-+|-+$/g, "");
|
|
1454
|
+
if (collapsed === "") return "family";
|
|
1455
|
+
return /^\p{N}/u.test(collapsed) ? `family-${collapsed}` : collapsed;
|
|
1456
|
+
}
|
|
1457
|
+
function aliasedCandidate(candidate, utility) {
|
|
1458
|
+
const segment = utilitySegment(candidate);
|
|
1459
|
+
const important = segment.endsWith("!") ? "!" : "";
|
|
1460
|
+
return candidate.slice(0, candidate.length - segment.length) + utility + important;
|
|
1461
|
+
}
|
|
1462
|
+
function appendFontTheme(css, tokens) {
|
|
1463
|
+
const names = Object.keys(tokens).sort();
|
|
1464
|
+
if (names.length === 0) return css;
|
|
1465
|
+
let output = css;
|
|
1466
|
+
if (!output.endsWith("\n")) output += "\n";
|
|
1467
|
+
if (!output.endsWith("\n\n")) output += "\n";
|
|
1468
|
+
output += `@theme {\n${names.map((name) => ` --font-${name}: ${tokens[name]};\n`).join("")}}\n`;
|
|
1469
|
+
return output;
|
|
1470
|
+
}
|
|
1471
|
+
function fontShapesAccept(system, probeCandidate, aliased, reservations, backingToken) {
|
|
1472
|
+
const [compiledProbe, compiledAliased] = system.candidatesToCss([probeCandidate, aliased]);
|
|
1473
|
+
if (compiledProbe == null || compiledAliased == null) return false;
|
|
1474
|
+
let probeShape;
|
|
1475
|
+
let aliasedShape;
|
|
1476
|
+
try {
|
|
1477
|
+
probeShape = compiledShape(compiledProbe);
|
|
1478
|
+
aliasedShape = compiledShape(compiledAliased);
|
|
1479
|
+
} catch {
|
|
1480
|
+
return false;
|
|
1481
|
+
}
|
|
1482
|
+
if (probeShape.referencedProperties.length > 0) return false;
|
|
1483
|
+
if (aliasedShape.referencedProperties.some((property) => reservations.properties.has(property))) return false;
|
|
1484
|
+
if (backingToken !== void 0) {
|
|
1485
|
+
if (!aliasedShape.declarations.some((declaration) => declaration.property === "font-family" && /^var\(--[\w-]+\)$/.test(declaration.value) && (declaration.value === `var(--${backingToken})` || declaration.value.endsWith(`-${backingToken})`)))) return false;
|
|
1486
|
+
}
|
|
1487
|
+
return sameShape(probeShape.declarations, aliasedShape.declarations);
|
|
1488
|
+
}
|
|
1489
|
+
async function registerFontTokens(options) {
|
|
1490
|
+
const { system, reservations } = options;
|
|
1491
|
+
if (reservations.unbounded || reservations.propertiesUnbounded) return {
|
|
1492
|
+
aliases: {},
|
|
1493
|
+
tokens: {},
|
|
1494
|
+
failures: /* @__PURE__ */ new Set()
|
|
1213
1495
|
};
|
|
1214
|
-
const
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1496
|
+
const prefix = system.theme.prefix;
|
|
1497
|
+
const aliases = {};
|
|
1498
|
+
const failures = /* @__PURE__ */ new Set();
|
|
1499
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1500
|
+
const allocatedByStack = /* @__PURE__ */ new Map();
|
|
1501
|
+
const proposals = [];
|
|
1502
|
+
const claimed = /* @__PURE__ */ new Set();
|
|
1503
|
+
const tokenStacks = fontTokenStacks(options.themeTokens, options.referenceTokens);
|
|
1504
|
+
for (const probe of options.probes) {
|
|
1505
|
+
if (seen.has(probe.candidate)) continue;
|
|
1506
|
+
seen.add(probe.candidate);
|
|
1507
|
+
if (probe.firstFamily.kind !== "name") continue;
|
|
1508
|
+
if (probe.candidate in options.existingAliases) continue;
|
|
1509
|
+
let reused = false;
|
|
1510
|
+
for (const token of matchingFontTokens(tokenStacks, probe.value)) {
|
|
1511
|
+
const aliased = aliasedCandidate(probe.candidate, token);
|
|
1512
|
+
if (reservedSpelling(reservations, token) || reservedSpelling(reservations, aliased)) continue;
|
|
1513
|
+
const reuseName = token.replace(/^font-/, "");
|
|
1514
|
+
const owner = options.globalAllocations.get(reuseName);
|
|
1515
|
+
if (owner !== void 0 && (owner === null || owner !== probe.value)) continue;
|
|
1516
|
+
if (fontShapesAccept(system, probe.candidate, aliased, reservations, token)) {
|
|
1517
|
+
aliases[probe.candidate] = aliased;
|
|
1518
|
+
options.globalAllocations.set(reuseName, probe.value);
|
|
1519
|
+
reused = true;
|
|
1520
|
+
break;
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
if (reused) continue;
|
|
1524
|
+
if (!options.generate) continue;
|
|
1525
|
+
const stackKey = probe.value;
|
|
1526
|
+
const shared = allocatedByStack.get(stackKey);
|
|
1527
|
+
if (shared !== void 0) {
|
|
1528
|
+
proposals.push({
|
|
1529
|
+
probe,
|
|
1530
|
+
name: shared
|
|
1531
|
+
});
|
|
1532
|
+
continue;
|
|
1533
|
+
}
|
|
1534
|
+
const base = fontTokenName(probe.firstFamily.name);
|
|
1535
|
+
let allocated = false;
|
|
1536
|
+
for (let attempt = 1; attempt <= 100; attempt += 1) {
|
|
1537
|
+
const name = attempt === 1 ? base : `${base}-${attempt}`;
|
|
1538
|
+
if (claimed.has(name)) continue;
|
|
1539
|
+
if (reservations.properties.has(`--font-${name}`)) continue;
|
|
1540
|
+
if (prefix !== null && reservations.properties.has(`--${prefix}-font-${name}`)) continue;
|
|
1541
|
+
if (reservedSpelling(reservations, `font-${name}`)) continue;
|
|
1542
|
+
if (options.mentionedSegments.has(`font-${name}`)) continue;
|
|
1543
|
+
const global = options.globalAllocations.get(name);
|
|
1544
|
+
if (global !== void 0 && (global === null || global !== probe.value)) continue;
|
|
1545
|
+
const compiled = prefix !== null ? `${prefix}:font-${name}` : `font-${name}`;
|
|
1546
|
+
if (system.candidatesToCss([compiled])[0] !== null) continue;
|
|
1547
|
+
claimed.add(name);
|
|
1548
|
+
allocatedByStack.set(stackKey, name);
|
|
1549
|
+
proposals.push({
|
|
1550
|
+
probe,
|
|
1551
|
+
name
|
|
1552
|
+
});
|
|
1553
|
+
allocated = true;
|
|
1554
|
+
break;
|
|
1555
|
+
}
|
|
1556
|
+
if (!allocated) failures.add(probe.candidate);
|
|
1557
|
+
}
|
|
1558
|
+
if (proposals.length === 0) return {
|
|
1559
|
+
aliases,
|
|
1560
|
+
tokens: {},
|
|
1561
|
+
failures
|
|
1562
|
+
};
|
|
1563
|
+
const tokens = {};
|
|
1564
|
+
for (const { probe, name } of proposals) tokens[name] = probe.value;
|
|
1565
|
+
let fontSystem;
|
|
1566
|
+
try {
|
|
1567
|
+
fontSystem = await options.loadWith(appendFontTheme(options.entryCss, tokens));
|
|
1568
|
+
} catch (error) {
|
|
1569
|
+
if (isIntegrityError(error)) throw error;
|
|
1570
|
+
for (const { probe } of proposals) failures.add(probe.candidate);
|
|
1222
1571
|
return {
|
|
1223
|
-
|
|
1224
|
-
|
|
1572
|
+
aliases,
|
|
1573
|
+
tokens: {},
|
|
1574
|
+
failures
|
|
1225
1575
|
};
|
|
1226
|
-
}
|
|
1227
|
-
const
|
|
1228
|
-
const
|
|
1229
|
-
const
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
});
|
|
1242
|
-
const loadWith = (replacement) => loadDesignSystem(replacement, {
|
|
1243
|
-
base,
|
|
1244
|
-
loadModule,
|
|
1245
|
-
loadStylesheet
|
|
1246
|
-
});
|
|
1576
|
+
}
|
|
1577
|
+
const before = system.candidatesToCss(options.corpus);
|
|
1578
|
+
const after = fontSystem.candidatesToCss(options.corpus);
|
|
1579
|
+
const corpusChanged = options.corpus.some((candidate, index) => before[index] !== after[index]);
|
|
1580
|
+
const accepted = {};
|
|
1581
|
+
for (const { probe, name } of proposals) {
|
|
1582
|
+
const canonical = corpusChanged ? null : canonicalCandidate(fontSystem, probe.candidate);
|
|
1583
|
+
const expected = aliasedCandidate(probe.candidate, `font-${name}`);
|
|
1584
|
+
if (canonical !== expected || !fontShapesAccept(fontSystem, probe.candidate, expected, reservations)) {
|
|
1585
|
+
failures.add(probe.candidate);
|
|
1586
|
+
continue;
|
|
1587
|
+
}
|
|
1588
|
+
aliases[probe.candidate] = expected;
|
|
1589
|
+
accepted[name] = probe.value;
|
|
1590
|
+
}
|
|
1247
1591
|
return {
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
themeTokens,
|
|
1252
|
-
graphSources,
|
|
1253
|
-
loadWith
|
|
1592
|
+
aliases,
|
|
1593
|
+
tokens: accepted,
|
|
1594
|
+
failures
|
|
1254
1595
|
};
|
|
1255
1596
|
}
|
|
1256
|
-
function
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
const tokens = {};
|
|
1264
|
-
for (const spec of importSpecifiers(css)) {
|
|
1265
|
-
const key = `${base}\0${spec}`;
|
|
1266
|
-
if (seen.has(key)) continue;
|
|
1267
|
-
seen.add(key);
|
|
1268
|
-
const loaded = await loadStylesheet(spec, base);
|
|
1269
|
-
if (spec !== "tailwindcss" && !spec.startsWith("tailwindcss/")) graphSources.push({
|
|
1270
|
-
path: key,
|
|
1271
|
-
source: loaded.content
|
|
1272
|
-
});
|
|
1273
|
-
Object.assign(tokens, await extractThemeTokensFromGraph(key, loaded.content, loaded.base, loadStylesheet, graphSources, seen));
|
|
1597
|
+
function fontTokenStacks(themeTokens, referenceTokens = /* @__PURE__ */ new Set()) {
|
|
1598
|
+
const stacks = /* @__PURE__ */ new Map();
|
|
1599
|
+
for (const [token, value] of Object.entries(themeTokens)) {
|
|
1600
|
+
if (!token.startsWith("font-")) continue;
|
|
1601
|
+
if (referenceTokens.has(token)) continue;
|
|
1602
|
+
const parsed = fontFamilyStack(value);
|
|
1603
|
+
if (parsed !== null) stacks.set(token, parsed.value);
|
|
1274
1604
|
}
|
|
1275
|
-
return
|
|
1605
|
+
return stacks;
|
|
1276
1606
|
}
|
|
1277
|
-
function
|
|
1278
|
-
|
|
1279
|
-
return candidates.filter((_, index) => generated[index] === null);
|
|
1607
|
+
function matchingFontTokens(stacks, normalizedStack) {
|
|
1608
|
+
return [...stacks].filter(([, stack]) => stack === normalizedStack).map(([token]) => token).sort();
|
|
1280
1609
|
}
|
|
1281
1610
|
//#endregion
|
|
1282
1611
|
//#region src/plan/media.ts
|
|
1283
1612
|
const GENERATED_NAMESPACE = "twm-media";
|
|
1284
1613
|
/**
|
|
1285
|
-
* Resolve one fixed name per key across a complete entry group
|
|
1614
|
+
* Resolve one fixed name per key across a complete entry group: key to
|
|
1615
|
+
* resolved name, in sorted key order. Only `generated` entries emit a
|
|
1616
|
+
* definition; a key absent from the map could not escape existing owners
|
|
1617
|
+
* and keeps the arbitrary-variant fallback.
|
|
1286
1618
|
*
|
|
1287
1619
|
* Resolution follows the RFC order: a built-in candidate with a verified
|
|
1288
1620
|
* effective expansion, an existing breakpoint matched by exact value and
|
|
@@ -1308,7 +1640,6 @@ function resolveMediaNames(collections, themeTokens, probes, reservedNames = [])
|
|
|
1308
1640
|
};
|
|
1309
1641
|
const taken = (name) => claimed.has(name) || reserved.has(name) || authoredKeysByName.has(name) || probes.resolves(name);
|
|
1310
1642
|
const names = /* @__PURE__ */ new Map();
|
|
1311
|
-
const fallbacks = /* @__PURE__ */ new Set();
|
|
1312
1643
|
for (const component of components) {
|
|
1313
1644
|
const resolved = (name, kind) => {
|
|
1314
1645
|
claimed.add(name);
|
|
@@ -1327,25 +1658,18 @@ function resolveMediaNames(collections, themeTokens, probes, reservedNames = [])
|
|
|
1327
1658
|
}
|
|
1328
1659
|
const digestName = `${GENERATED_NAMESPACE}-${component.digest}`;
|
|
1329
1660
|
const candidates = component.readableName === null ? [digestName] : [component.readableName, digestName];
|
|
1330
|
-
let done = false;
|
|
1331
1661
|
for (const candidate of candidates) {
|
|
1332
1662
|
if (adoptableKey(candidate) === component.key && !claimed.has(candidate) && probes.expansionMatches(candidate, component.key)) {
|
|
1333
1663
|
resolved(candidate, "adopted");
|
|
1334
|
-
done = true;
|
|
1335
1664
|
break;
|
|
1336
1665
|
}
|
|
1337
1666
|
if (!taken(candidate)) {
|
|
1338
1667
|
resolved(candidate, "generated");
|
|
1339
|
-
done = true;
|
|
1340
1668
|
break;
|
|
1341
1669
|
}
|
|
1342
1670
|
}
|
|
1343
|
-
if (!done) fallbacks.add(component.key);
|
|
1344
1671
|
}
|
|
1345
|
-
return
|
|
1346
|
-
names,
|
|
1347
|
-
fallbacks
|
|
1348
|
-
};
|
|
1672
|
+
return names;
|
|
1349
1673
|
}
|
|
1350
1674
|
function mergeComponents(collections) {
|
|
1351
1675
|
const byKey = /* @__PURE__ */ new Map();
|
|
@@ -1426,7 +1750,7 @@ function planMediaExtraction(options) {
|
|
|
1426
1750
|
const resolution = resolveMediaNames(options.collections, options.tailwind.themeTokens, buildMediaProbes(options.tailwind), scannedVariantReservations([...options.scannedSources, ...options.tailwind.graphSources.map((graphSource) => graphSource.source)]));
|
|
1427
1751
|
const names = {};
|
|
1428
1752
|
const generated = [];
|
|
1429
|
-
for (const [key, resolved] of resolution
|
|
1753
|
+
for (const [key, resolved] of resolution) {
|
|
1430
1754
|
names[key] = resolved.name;
|
|
1431
1755
|
if (resolved.kind === "generated") generated.push({
|
|
1432
1756
|
key,
|
|
@@ -1535,7 +1859,9 @@ function analyzeVueSource(compiler, path, source) {
|
|
|
1535
1859
|
const { descriptor, errors } = compiler.parse(source, { filename: path });
|
|
1536
1860
|
const styleRanges = descriptor.styles.map((style) => ({
|
|
1537
1861
|
start: style.loc.start.offset,
|
|
1538
|
-
end: style.loc.end.offset
|
|
1862
|
+
end: style.loc.end.offset,
|
|
1863
|
+
lang: style.lang ?? "css",
|
|
1864
|
+
...style.src !== void 0 ? { src: style.src } : {}
|
|
1539
1865
|
}));
|
|
1540
1866
|
const byteRanges = (offset) => ({
|
|
1541
1867
|
warnings: warnings.map((warning) => ({
|
|
@@ -1544,6 +1870,7 @@ function analyzeVueSource(compiler, path, source) {
|
|
|
1544
1870
|
end: offset(warning.end)
|
|
1545
1871
|
})),
|
|
1546
1872
|
styleRanges: styleRanges.map((range) => ({
|
|
1873
|
+
...range,
|
|
1547
1874
|
start: offset(range.start),
|
|
1548
1875
|
end: offset(range.end)
|
|
1549
1876
|
}))
|
|
@@ -1684,6 +2011,14 @@ function analyzeVueSource(compiler, path, source) {
|
|
|
1684
2011
|
moduleClosureBroken: false,
|
|
1685
2012
|
referencesUseCssModule: false,
|
|
1686
2013
|
expressionReferences: /* @__PURE__ */ new Set(),
|
|
2014
|
+
templatePrefixes: /* @__PURE__ */ new Set(),
|
|
2015
|
+
entityClassTokens: [],
|
|
2016
|
+
styleAttributeValues: [],
|
|
2017
|
+
styleAttributesUnverifiable: false,
|
|
2018
|
+
customProperties: /* @__PURE__ */ new Set(),
|
|
2019
|
+
customPropertiesUnbounded: false,
|
|
2020
|
+
stylesheetLinks: [],
|
|
2021
|
+
stylesheetLinksUnverifiable: false,
|
|
1687
2022
|
expressionPath: scriptLang === "ts" || scriptLang === "tsx" ? "Component.ts" : "Component.js"
|
|
1688
2023
|
};
|
|
1689
2024
|
visitTemplateNode(source, template.ast, state);
|
|
@@ -1699,8 +2034,11 @@ function analyzeVueSource(compiler, path, source) {
|
|
|
1699
2034
|
let setupAnalysis;
|
|
1700
2035
|
let scriptUsesCssModule = false;
|
|
1701
2036
|
let scriptHasDynamicImport = false;
|
|
2037
|
+
const scriptCustomProperties = [];
|
|
2038
|
+
let scriptCustomPropertiesUnbounded = false;
|
|
1702
2039
|
const scriptVueReferences = [];
|
|
1703
2040
|
const scriptVueGlobPatterns = [];
|
|
2041
|
+
const scriptTemplatePrefixes = [];
|
|
1704
2042
|
let scriptVueGlobUnverifiable = false;
|
|
1705
2043
|
const styleImports = [descriptor.script, descriptor.scriptSetup].flatMap((script) => {
|
|
1706
2044
|
if (!script) return [];
|
|
@@ -1716,6 +2054,9 @@ function analyzeVueSource(compiler, path, source) {
|
|
|
1716
2054
|
scriptImports.push(...analysis.imports);
|
|
1717
2055
|
scriptVueReferences.push(...analysis.imports.filter((record) => !record.typeOnly).map((record) => record.specifier));
|
|
1718
2056
|
scriptVueGlobPatterns.push(...analysis.vueGlobPatterns);
|
|
2057
|
+
scriptTemplatePrefixes.push(...analysis.templatePrefixes);
|
|
2058
|
+
scriptCustomProperties.push(...analysis.customProperties);
|
|
2059
|
+
if (analysis.customPropertiesUnbounded) scriptCustomPropertiesUnbounded = true;
|
|
1719
2060
|
if (analysis.vueGlobUnverifiable) scriptVueGlobUnverifiable = true;
|
|
1720
2061
|
return analysis.staticImports;
|
|
1721
2062
|
} catch {
|
|
@@ -1790,6 +2131,14 @@ function analyzeVueSource(compiler, path, source) {
|
|
|
1790
2131
|
scriptVueReferences,
|
|
1791
2132
|
scriptVueGlobPatterns,
|
|
1792
2133
|
scriptVueGlobUnverifiable,
|
|
2134
|
+
templatePrefixes: [.../* @__PURE__ */ new Set([...state.templatePrefixes, ...scriptTemplatePrefixes])],
|
|
2135
|
+
templateClassTokens: [...new Set(state.entityClassTokens)],
|
|
2136
|
+
templateStyleValues: state.styleAttributeValues,
|
|
2137
|
+
templateStylesUnverifiable: state.styleAttributesUnverifiable,
|
|
2138
|
+
scriptCustomProperties: [...scriptCustomProperties, ...state.customProperties],
|
|
2139
|
+
scriptCustomPropertiesUnbounded: scriptCustomPropertiesUnbounded || state.customPropertiesUnbounded,
|
|
2140
|
+
templateStylesheetLinks: state.stylesheetLinks,
|
|
2141
|
+
templateStylesheetLinksUnverifiable: state.stylesheetLinksUnverifiable,
|
|
1793
2142
|
hasOpaqueStyleBlocks: opaqueStyleBlocks,
|
|
1794
2143
|
styleBlockImports: styleBlockImports.map((entry) => ({
|
|
1795
2144
|
...entry,
|
|
@@ -1822,6 +2171,29 @@ function verifyVueSource(compiler, path, source, includeUnscoped = false) {
|
|
|
1822
2171
|
function visitTemplateNode(source, node, state) {
|
|
1823
2172
|
if (node.type === NODE_ELEMENT) {
|
|
1824
2173
|
if (node.tagType === TAG_SLOT) state.hasSlot = true;
|
|
2174
|
+
for (const prop of node.props ?? []) {
|
|
2175
|
+
if (prop.type === PROP_ATTRIBUTE && prop.name === "style" && prop.value?.content) state.styleAttributeValues.push(prop.value.content);
|
|
2176
|
+
if (prop.type === PROP_DIRECTIVE && prop.name === "bind") {
|
|
2177
|
+
const argument = prop.arg?.isStatic === true ? prop.arg.content : void 0;
|
|
2178
|
+
if (prop.arg === void 0 || argument === void 0 || argument === "style") state.styleAttributesUnverifiable = true;
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
if (node.tag === "link" && node.tagType === TAG_ELEMENT) {
|
|
2182
|
+
let rel;
|
|
2183
|
+
let href;
|
|
2184
|
+
let dynamic = false;
|
|
2185
|
+
for (const prop of node.props ?? []) if (prop.type === PROP_ATTRIBUTE) {
|
|
2186
|
+
if (prop.name === "rel") rel = prop.value?.content ?? "";
|
|
2187
|
+
if (prop.name === "href") href = prop.value?.content ?? "";
|
|
2188
|
+
} else if (prop.type === PROP_DIRECTIVE && prop.name === "bind") {
|
|
2189
|
+
const argument = prop.arg?.isStatic ? prop.arg.content : void 0;
|
|
2190
|
+
if (argument === void 0 || argument === "rel" || argument === "href") dynamic = true;
|
|
2191
|
+
}
|
|
2192
|
+
if ((rel?.toLowerCase().split(/\s+/) ?? []).includes("stylesheet") || rel === void 0 && dynamic) {
|
|
2193
|
+
if (href !== void 0 && !dynamic) state.stylesheetLinks.push(href);
|
|
2194
|
+
else state.stylesheetLinksUnverifiable = true;
|
|
2195
|
+
} else if (dynamic) state.stylesheetLinksUnverifiable = true;
|
|
2196
|
+
}
|
|
1825
2197
|
const bindingClasses = [];
|
|
1826
2198
|
let classOpaque = false;
|
|
1827
2199
|
let moduleBinding;
|
|
@@ -1851,11 +2223,15 @@ function visitTemplateNode(source, node, state) {
|
|
|
1851
2223
|
if (!provenModuleExpression && expression?.usesCssModule) state.moduleClosureBroken = true;
|
|
1852
2224
|
if (expression?.referencesUseCssModule) state.referencesUseCssModule = true;
|
|
1853
2225
|
for (const name of expression?.references ?? []) state.expressionReferences.add(name);
|
|
2226
|
+
for (const prefix of expression?.templatePrefixes ?? []) state.templatePrefixes.add(prefix);
|
|
2227
|
+
for (const name of expression?.customProperties ?? []) state.customProperties.add(name);
|
|
2228
|
+
if (expression?.customPropertiesUnbounded) state.customPropertiesUnbounded = true;
|
|
1854
2229
|
if (prop.arg && !prop.arg.isStatic && prop.arg.content) {
|
|
1855
2230
|
const argExpression = templateExpression(state.expressionPath, prop.arg.content);
|
|
1856
2231
|
state.moduleClosureBroken ||= argExpression?.usesCssModule ?? true;
|
|
1857
2232
|
state.referencesUseCssModule ||= argExpression?.referencesUseCssModule ?? false;
|
|
1858
2233
|
for (const name of argExpression?.references ?? []) state.expressionReferences.add(name);
|
|
2234
|
+
for (const prefix of argExpression?.templatePrefixes ?? []) state.templatePrefixes.add(prefix);
|
|
1859
2235
|
}
|
|
1860
2236
|
if (prop.name === "html") state.vHtml = true;
|
|
1861
2237
|
}
|
|
@@ -1875,6 +2251,7 @@ function visitTemplateNode(source, node, state) {
|
|
|
1875
2251
|
state.moduleClosureBroken ||= interpolation?.usesCssModule ?? true;
|
|
1876
2252
|
state.referencesUseCssModule ||= interpolation?.referencesUseCssModule ?? false;
|
|
1877
2253
|
for (const name of interpolation?.references ?? []) state.expressionReferences.add(name);
|
|
2254
|
+
for (const prefix of interpolation?.templatePrefixes ?? []) state.templatePrefixes.add(prefix);
|
|
1878
2255
|
}
|
|
1879
2256
|
for (const child of node.children ?? []) visitTemplateNode(source, child, state);
|
|
1880
2257
|
}
|
|
@@ -1899,18 +2276,24 @@ function templateHandler(source) {
|
|
|
1899
2276
|
vueModuleMember: null,
|
|
1900
2277
|
usesCssModule: analysis.usesCssModule,
|
|
1901
2278
|
referencesUseCssModule: analysis.hasUnboundUseCssModule,
|
|
1902
|
-
references: analysis.unboundReferences
|
|
2279
|
+
references: analysis.unboundReferences,
|
|
2280
|
+
templatePrefixes: analysis.templatePrefixes,
|
|
2281
|
+
customProperties: analysis.customProperties,
|
|
2282
|
+
customPropertiesUnbounded: analysis.customPropertiesUnbounded
|
|
1903
2283
|
};
|
|
1904
2284
|
} catch {
|
|
1905
2285
|
return;
|
|
1906
2286
|
}
|
|
1907
2287
|
}
|
|
1908
2288
|
function templateSite(source, node, bindingClasses, classOpaque, state) {
|
|
1909
|
-
|
|
1910
|
-
const
|
|
1911
|
-
|
|
1912
|
-
const
|
|
1913
|
-
if (
|
|
2289
|
+
const classProp = staticAttribute(node, "class");
|
|
2290
|
+
const idProp = staticAttribute(node, "id");
|
|
2291
|
+
let classAttribute = literalAttribute(source, classProp);
|
|
2292
|
+
const idAttribute = literalAttribute(source, idProp);
|
|
2293
|
+
if (classProp && !classAttribute || idProp && !idAttribute) {
|
|
2294
|
+
if (classProp && !classAttribute) {
|
|
2295
|
+
for (const token of classProp.value?.content.split(/[\t\n\f\r ]+/) ?? []) if (token !== "") state.entityClassTokens.push(token);
|
|
2296
|
+
}
|
|
1914
2297
|
state.dynamic = true;
|
|
1915
2298
|
return { idAttribute };
|
|
1916
2299
|
}
|
|
@@ -1931,8 +2314,10 @@ function templateSite(source, node, bindingClasses, classOpaque, state) {
|
|
|
1931
2314
|
matchClasses
|
|
1932
2315
|
};
|
|
1933
2316
|
}
|
|
1934
|
-
function
|
|
1935
|
-
|
|
2317
|
+
function staticAttribute(node, name) {
|
|
2318
|
+
return node.props?.find((prop) => prop.type === PROP_ATTRIBUTE && prop.name === name);
|
|
2319
|
+
}
|
|
2320
|
+
function literalAttribute(source, prop) {
|
|
1936
2321
|
if (!prop?.value) return void 0;
|
|
1937
2322
|
const start = prop.value.loc.start.offset;
|
|
1938
2323
|
const end = prop.value.loc.end.offset;
|
|
@@ -2218,7 +2603,7 @@ async function preparePackageVue({ packageRoot, styleCompilers, sourceFiles, sty
|
|
|
2218
2603
|
})));
|
|
2219
2604
|
vueShadowModuleCss.push(...analysis.shadowModuleCssTexts, ...analysis.moduleBlocks.map((block) => block.analysisSource ?? block.content));
|
|
2220
2605
|
}
|
|
2221
|
-
const packageIsPrivate = JSON.parse(await snapshotFile(snapshots, join(packageRoot, "package.json"))).private === true;
|
|
2606
|
+
const packageIsPrivate = projectPackageSchema.parse(JSON.parse(await snapshotFile(snapshots, join(packageRoot, "package.json")))).private === true;
|
|
2222
2607
|
const files = /* @__PURE__ */ new Map();
|
|
2223
2608
|
const stylesheets = [];
|
|
2224
2609
|
const stylePaths = /* @__PURE__ */ new Set();
|
|
@@ -2404,7 +2789,16 @@ async function preparePackageVue({ packageRoot, styleCompilers, sourceFiles, sty
|
|
|
2404
2789
|
htmlScriptText: analysis.scriptText
|
|
2405
2790
|
} : {},
|
|
2406
2791
|
sourceImports: analysis.scriptImports,
|
|
2407
|
-
sourceImportsUnverifiable: analysis.scriptImportsUnverifiable
|
|
2792
|
+
sourceImportsUnverifiable: analysis.scriptImportsUnverifiable,
|
|
2793
|
+
templatePrefixes: analysis.templatePrefixes,
|
|
2794
|
+
templateInjectsMarkup: analysis.vHtml,
|
|
2795
|
+
templateClassTokens: analysis.templateClassTokens,
|
|
2796
|
+
templateStyleValues: analysis.templateStyleValues,
|
|
2797
|
+
templateStylesUnverifiable: analysis.templateStylesUnverifiable,
|
|
2798
|
+
sourceCustomProperties: analysis.scriptCustomProperties,
|
|
2799
|
+
sourceCustomPropertiesUnbounded: analysis.scriptCustomPropertiesUnbounded,
|
|
2800
|
+
templateStylesheetLinks: analysis.templateStylesheetLinks,
|
|
2801
|
+
templateStylesheetLinksUnverifiable: analysis.templateStylesheetLinksUnverifiable
|
|
2408
2802
|
});
|
|
2409
2803
|
}
|
|
2410
2804
|
return {
|
|
@@ -2493,7 +2887,20 @@ function indexWarningPositions(source, offsets, unicodeSeparatorsAreNewlines, fo
|
|
|
2493
2887
|
}
|
|
2494
2888
|
return positions;
|
|
2495
2889
|
}
|
|
2890
|
+
const optionPath = z.string().check(z.minLength(1), z.refine((path) => !path.includes("\0")));
|
|
2891
|
+
const migrateOptionsSchema = z.strictObject({
|
|
2892
|
+
styleFile: z.optional(optionPath),
|
|
2893
|
+
cwd: z.optional(optionPath),
|
|
2894
|
+
write: z.optional(z.boolean()),
|
|
2895
|
+
tailwindCss: z.optional(optionPath),
|
|
2896
|
+
workspaces: z.optional(z.boolean()),
|
|
2897
|
+
force: z.optional(z.boolean()),
|
|
2898
|
+
extractMediaQueries: z.optional(z.boolean())
|
|
2899
|
+
});
|
|
2496
2900
|
async function migrate(options = {}) {
|
|
2901
|
+
const parsedOptions = migrateOptionsSchema.safeParse(options);
|
|
2902
|
+
if (!parsedOptions.success) throw new TypeError(`Invalid migrate options: ${parsedOptions.error.message}`);
|
|
2903
|
+
options = parsedOptions.data;
|
|
2497
2904
|
if (options.styleFile && options.workspaces) throw new TypeError("styleFile cannot be combined with workspaces");
|
|
2498
2905
|
if (options.tailwindCss && options.workspaces) throw new TypeError("tailwindCss cannot be combined with workspaces");
|
|
2499
2906
|
const scope = await resolveScope(options);
|
|
@@ -2538,7 +2945,8 @@ async function migrate(options = {}) {
|
|
|
2538
2945
|
styleDependents: indexStylesheetDependents(styleSources),
|
|
2539
2946
|
vueStyleRanges,
|
|
2540
2947
|
entryCatalog: tailwindEntryCatalog(styleSources, scope.pathOwners),
|
|
2541
|
-
ignoredPaths:
|
|
2948
|
+
ignoredPaths: await scannerIgnoredPaths(workspaceRoot, sourceFiles.map((file) => file.path)),
|
|
2949
|
+
fontAllocations: /* @__PURE__ */ new Map()
|
|
2542
2950
|
};
|
|
2543
2951
|
const failures = [];
|
|
2544
2952
|
const plans = [];
|
|
@@ -2555,6 +2963,14 @@ async function migrate(options = {}) {
|
|
|
2555
2963
|
members.push(preparation);
|
|
2556
2964
|
groups.set(preparation.tailwind.path, members);
|
|
2557
2965
|
}
|
|
2966
|
+
for (const [, members] of groups) for (const [token, value] of Object.entries(members[0].tailwind.themeTokens)) {
|
|
2967
|
+
if (!token.startsWith("font-")) continue;
|
|
2968
|
+
const parsed = fontFamilyStack(value);
|
|
2969
|
+
const name = token.replace(/^font-/, "");
|
|
2970
|
+
const stack = parsed && parsed.value;
|
|
2971
|
+
const existing = context.fontAllocations.get(name);
|
|
2972
|
+
context.fontAllocations.set(name, existing === void 0 || existing === stack ? stack : null);
|
|
2973
|
+
}
|
|
2558
2974
|
for (const [, members] of [...groups].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)) for (const result of await planPreparedGroup(context, members)) if (result.failure) failures.push(result.failure);
|
|
2559
2975
|
else if (result.plan) plans.push(result.plan);
|
|
2560
2976
|
const originals = new Map([...styleSources, ...sourceFiles.map((file) => [file.path, file.source])]);
|
|
@@ -2700,10 +3116,7 @@ async function preparePackage(context, packageRoot) {
|
|
|
2700
3116
|
let packageJson = {};
|
|
2701
3117
|
try {
|
|
2702
3118
|
const rawPackageJson = snapshots.get(join(packageRoot, "package.json"));
|
|
2703
|
-
if (rawPackageJson !== void 0)
|
|
2704
|
-
const parsed = JSON.parse(rawPackageJson);
|
|
2705
|
-
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) packageJson = { ...parsed };
|
|
2706
|
-
}
|
|
3119
|
+
if (rawPackageJson !== void 0) packageJson = projectPackageSchema.parse(JSON.parse(rawPackageJson));
|
|
2707
3120
|
} catch (error) {
|
|
2708
3121
|
return recover(error);
|
|
2709
3122
|
}
|
|
@@ -2847,6 +3260,174 @@ async function planPreparedGroup(context, members) {
|
|
|
2847
3260
|
return leftPath < rightPath ? -1 : leftPath > rightPath ? 1 : 0;
|
|
2848
3261
|
});
|
|
2849
3262
|
const entry = active[0].tailwind;
|
|
3263
|
+
const reservations = spellingReservations(context.styleSources, entry.graphSources.map((graphSource) => [`${graphSource.path}.graph.css`, graphSource.source]), new Set(entry.graphSources.map((graphSource) => graphSource.path).filter((path) => path.includes("\0"))));
|
|
3264
|
+
const groupFiles = new Map(active.flatMap((member) => member.files.map((file) => [file.path, file])));
|
|
3265
|
+
const mentionedSegments = /* @__PURE__ */ new Set();
|
|
3266
|
+
const mentionedSources = [...[...groupFiles.values()].map((file) => file.source), ...entry.graphSources.map((graphSource) => graphSource.source)];
|
|
3267
|
+
const addSegment = (token) => {
|
|
3268
|
+
if (token === "") return;
|
|
3269
|
+
const segment = utilitySegment(token).replace(/!$/, "");
|
|
3270
|
+
if (segment) mentionedSegments.add(segment);
|
|
3271
|
+
};
|
|
3272
|
+
for (const source of mentionedSources) {
|
|
3273
|
+
for (const token of source.split(/[\s"'`<>=,;{}()\\]+/)) addSegment(token);
|
|
3274
|
+
for (const inline of source.matchAll(/inline\(([^)]*)\)/g)) for (const candidate of inline[1].matchAll(/([^\s"',{}]*)\{([^{}]*)\}([^\s"',{}]*)/g)) for (const option of candidate[2].split(",")) {
|
|
3275
|
+
const range = /^(\d+)\.\.(\d+)(?:\.\.(\d+))?$/.exec(option);
|
|
3276
|
+
const stride = range === null ? 0 : Number(range[3] ?? "1");
|
|
3277
|
+
if (range !== null && stride > 0) {
|
|
3278
|
+
const from = Number(range[1]);
|
|
3279
|
+
const to = Number(range[2]);
|
|
3280
|
+
let steps = 0;
|
|
3281
|
+
for (let value = Math.min(from, to); value <= Math.max(from, to); value += stride) {
|
|
3282
|
+
addSegment(`${candidate[1]}${value}${candidate[3]}`);
|
|
3283
|
+
if ((steps += 1) >= 1e3) break;
|
|
3284
|
+
}
|
|
3285
|
+
continue;
|
|
3286
|
+
}
|
|
3287
|
+
addSegment(`${candidate[1]}${option}${candidate[3]}`);
|
|
3288
|
+
}
|
|
3289
|
+
}
|
|
3290
|
+
const fileRoots = new Map(active.flatMap((member) => member.files.map((file) => [file.path, member.packageRoot])));
|
|
3291
|
+
const workspaceEntries = new Set([...context.entryCatalog.values()].flat());
|
|
3292
|
+
for (const file of groupFiles.values()) {
|
|
3293
|
+
if (reservations.unbounded) break;
|
|
3294
|
+
if (context.ignoredPaths.has(file.path)) {
|
|
3295
|
+
reservations.unbounded = true;
|
|
3296
|
+
break;
|
|
3297
|
+
}
|
|
3298
|
+
const opaqueStylesheetLink = (link) => {
|
|
3299
|
+
const resolved = localHtmlReference(fileRoots.get(file.path) ?? context.workspaceRoot, dirname(file.path), link);
|
|
3300
|
+
return resolved === void 0 || !context.styleSources.has(resolved) || workspaceEntries.has(resolved) && resolved !== entry.path;
|
|
3301
|
+
};
|
|
3302
|
+
const opaqueStylesheetImports = (records) => {
|
|
3303
|
+
for (const record of records) {
|
|
3304
|
+
if (record.typeOnly) continue;
|
|
3305
|
+
const specifier = record.specifier.split(/[?#]/, 1)[0];
|
|
3306
|
+
if (!isStylesheetPath(specifier)) {
|
|
3307
|
+
if (extname(specifier) !== "" || specifier.startsWith(".")) continue;
|
|
3308
|
+
try {
|
|
3309
|
+
const resolved = createRequire(file.path).resolve(specifier);
|
|
3310
|
+
if (isStylesheetPath(resolved) && !context.styleSources.has(resolved)) reservations.unbounded = true;
|
|
3311
|
+
} catch {}
|
|
3312
|
+
continue;
|
|
3313
|
+
}
|
|
3314
|
+
const resolved = specifier.startsWith(".") ? resolve(dirname(file.path), specifier) : void 0;
|
|
3315
|
+
if (resolved === void 0 || !context.styleSources.has(resolved) || workspaceEntries.has(resolved) && resolved !== entry.path) reservations.unbounded = true;
|
|
3316
|
+
}
|
|
3317
|
+
};
|
|
3318
|
+
if (extname(file.path) === ".html") {
|
|
3319
|
+
try {
|
|
3320
|
+
const parsed = parseHtmlSource(file.path, file.source);
|
|
3321
|
+
if (parsed.hasStyle) reservations.unbounded = true;
|
|
3322
|
+
if (parsed.bases.length > 0) reservations.unbounded = true;
|
|
3323
|
+
for (const link of parsed.links) if (opaqueStylesheetLink(link.href)) reservations.unbounded = true;
|
|
3324
|
+
for (const [index, value] of parsed.styleAttributeValues.entries()) {
|
|
3325
|
+
if (TEMPLATE_MARKERS.test(value)) {
|
|
3326
|
+
reservations.propertiesUnbounded = true;
|
|
3327
|
+
continue;
|
|
3328
|
+
}
|
|
3329
|
+
scanInlineStyleReservations(`${file.path}.style.${index}.css`, value, reservations);
|
|
3330
|
+
}
|
|
3331
|
+
for (const value of parsed.dynamicClassValues) {
|
|
3332
|
+
const masked = value.replace(TEMPLATE_EXPRESSIONS, "\0");
|
|
3333
|
+
if (TEMPLATE_MARKERS.test(masked)) {
|
|
3334
|
+
reservations.unbounded = true;
|
|
3335
|
+
continue;
|
|
3336
|
+
}
|
|
3337
|
+
for (const token of masked.split(/\s+/)) {
|
|
3338
|
+
if (token === "") continue;
|
|
3339
|
+
const index = token.indexOf("\0");
|
|
3340
|
+
if (index < 0) reservations.names.add(token);
|
|
3341
|
+
else if (index === 0) reservations.unbounded = true;
|
|
3342
|
+
else reservations.prefixes.add(token.slice(0, index));
|
|
3343
|
+
}
|
|
3344
|
+
}
|
|
3345
|
+
if (parsed.handlerText.trim() !== "") try {
|
|
3346
|
+
const handlers = sourceAnalysis(`${file.path}.handlers.js`, parsed.handlerText);
|
|
3347
|
+
for (const prefix of handlers.templatePrefixes) reservations.prefixes.add(prefix);
|
|
3348
|
+
for (const property of handlers.customProperties) reservations.properties.add(property);
|
|
3349
|
+
if (handlers.customPropertiesUnbounded) reservations.propertiesUnbounded = true;
|
|
3350
|
+
} catch {
|
|
3351
|
+
reservations.unbounded = true;
|
|
3352
|
+
}
|
|
3353
|
+
if (parsed.scriptText.trim() !== "") try {
|
|
3354
|
+
const script = sourceAnalysis(`${file.path}.inline.js`, parsed.scriptText);
|
|
3355
|
+
for (const prefix of script.templatePrefixes) reservations.prefixes.add(prefix);
|
|
3356
|
+
for (const property of script.customProperties) reservations.properties.add(property);
|
|
3357
|
+
if (script.customPropertiesUnbounded) reservations.propertiesUnbounded = true;
|
|
3358
|
+
opaqueStylesheetImports(script.imports);
|
|
3359
|
+
} catch {
|
|
3360
|
+
reservations.unbounded = true;
|
|
3361
|
+
}
|
|
3362
|
+
} catch {
|
|
3363
|
+
reservations.unbounded = true;
|
|
3364
|
+
}
|
|
3365
|
+
continue;
|
|
3366
|
+
}
|
|
3367
|
+
if (extname(file.path) === ".vue") {
|
|
3368
|
+
if (file.templatePrefixes === void 0 || file.sourceImportsUnverifiable === true || file.templateInjectsMarkup === true) {
|
|
3369
|
+
reservations.unbounded = true;
|
|
3370
|
+
continue;
|
|
3371
|
+
}
|
|
3372
|
+
const styleRanges = context.vueStyleRanges.get(file.path);
|
|
3373
|
+
if (styleRanges === void 0) {
|
|
3374
|
+
reservations.unbounded = true;
|
|
3375
|
+
continue;
|
|
3376
|
+
}
|
|
3377
|
+
const fileBytes = Buffer.from(file.source);
|
|
3378
|
+
for (const [index, range] of styleRanges.entries()) {
|
|
3379
|
+
if (range.src !== void 0) {
|
|
3380
|
+
const resolved = range.src.startsWith(".") ? resolve(dirname(file.path), range.src) : void 0;
|
|
3381
|
+
if (resolved === void 0 || !context.styleSources.has(resolved)) reservations.unbounded = true;
|
|
3382
|
+
continue;
|
|
3383
|
+
}
|
|
3384
|
+
const block = fileBytes.subarray(range.start, range.end).toString();
|
|
3385
|
+
if (![
|
|
3386
|
+
"css",
|
|
3387
|
+
"scss",
|
|
3388
|
+
"sass",
|
|
3389
|
+
"less"
|
|
3390
|
+
].includes(range.lang)) {
|
|
3391
|
+
reservations.unbounded = true;
|
|
3392
|
+
continue;
|
|
3393
|
+
}
|
|
3394
|
+
scanStylesheetReservations(`${file.path}.block.${index}.${range.lang}`, block, context.styleSources, reservations);
|
|
3395
|
+
}
|
|
3396
|
+
for (const prefix of file.templatePrefixes) reservations.prefixes.add(prefix);
|
|
3397
|
+
for (const token of file.templateClassTokens ?? []) reservations.names.add(token);
|
|
3398
|
+
for (const property of file.sourceCustomProperties ?? []) reservations.properties.add(property);
|
|
3399
|
+
if (file.sourceCustomPropertiesUnbounded === true) reservations.propertiesUnbounded = true;
|
|
3400
|
+
if (file.templateStylesUnverifiable === true) reservations.propertiesUnbounded = true;
|
|
3401
|
+
for (const [index, value] of (file.templateStyleValues ?? []).entries()) scanInlineStyleReservations(`${file.path}.style.${index}.css`, value, reservations);
|
|
3402
|
+
if (file.templateStylesheetLinksUnverifiable === true) reservations.unbounded = true;
|
|
3403
|
+
for (const link of file.templateStylesheetLinks ?? []) if (opaqueStylesheetLink(link)) reservations.unbounded = true;
|
|
3404
|
+
opaqueStylesheetImports(file.sourceImports ?? []);
|
|
3405
|
+
continue;
|
|
3406
|
+
}
|
|
3407
|
+
if (![
|
|
3408
|
+
".js",
|
|
3409
|
+
".jsx",
|
|
3410
|
+
".ts",
|
|
3411
|
+
".tsx",
|
|
3412
|
+
".mjs",
|
|
3413
|
+
".cjs",
|
|
3414
|
+
".mts",
|
|
3415
|
+
".cts"
|
|
3416
|
+
].includes(extname(file.path))) continue;
|
|
3417
|
+
try {
|
|
3418
|
+
const analysis = sourceAnalysis(file.path, file.source);
|
|
3419
|
+
for (const prefix of analysis.templatePrefixes) reservations.prefixes.add(prefix);
|
|
3420
|
+
for (const property of analysis.customProperties) reservations.properties.add(property);
|
|
3421
|
+
if (analysis.customPropertiesUnbounded) reservations.propertiesUnbounded = true;
|
|
3422
|
+
opaqueStylesheetImports(analysis.imports);
|
|
3423
|
+
if (analysis.rendersStyleElement) reservations.unbounded = true;
|
|
3424
|
+
if (analysis.injectsMarkup) reservations.unbounded = true;
|
|
3425
|
+
if (analysis.stylesheetLinksUnverifiable) reservations.unbounded = true;
|
|
3426
|
+
for (const link of analysis.stylesheetLinks) if (opaqueStylesheetLink(link)) reservations.unbounded = true;
|
|
3427
|
+
} catch {
|
|
3428
|
+
reservations.unbounded = true;
|
|
3429
|
+
}
|
|
3430
|
+
}
|
|
2850
3431
|
const recoverGroup = (error, fatal = false) => {
|
|
2851
3432
|
if (!options.force || fatal) throw error;
|
|
2852
3433
|
for (const member of active) results.push({ failure: packageFailure(workspaceRoot, member.packageRoot, error) });
|
|
@@ -2900,28 +3481,30 @@ async function planPreparedGroup(context, members) {
|
|
|
2900
3481
|
if (active.length === 0) return results;
|
|
2901
3482
|
let names = extraction?.names;
|
|
2902
3483
|
let generated = extraction?.generated ?? [];
|
|
2903
|
-
if (options.extractMediaQueries !== false)
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
} else {
|
|
2914
|
-
try {
|
|
2915
|
-
extraction = planMediaExtraction({
|
|
2916
|
-
collections,
|
|
2917
|
-
tailwind: entry,
|
|
2918
|
-
scannedSources: context.sourceFiles.map((file) => file.source)
|
|
3484
|
+
if (options.extractMediaQueries !== false) {
|
|
3485
|
+
if (!groupWritable) {
|
|
3486
|
+
names = {};
|
|
3487
|
+
generated = [];
|
|
3488
|
+
if (collections.some((collection) => collection.components.length > 0)) mediaWarnings.push({
|
|
3489
|
+
code: "media-query-definition-fallback",
|
|
3490
|
+
file: entry.path,
|
|
3491
|
+
start: 0,
|
|
3492
|
+
end: 0,
|
|
3493
|
+
message: "The Tailwind entry could not be edited safely, so media behavior was preserved with arbitrary variants."
|
|
2919
3494
|
});
|
|
2920
|
-
}
|
|
2921
|
-
|
|
3495
|
+
} else {
|
|
3496
|
+
try {
|
|
3497
|
+
extraction = planMediaExtraction({
|
|
3498
|
+
collections,
|
|
3499
|
+
tailwind: entry,
|
|
3500
|
+
scannedSources: context.sourceFiles.map((file) => file.source)
|
|
3501
|
+
});
|
|
3502
|
+
} catch (error) {
|
|
3503
|
+
return recoverGroup(error, !isRecoverablePlanningError(error));
|
|
3504
|
+
}
|
|
3505
|
+
names = extraction.names;
|
|
3506
|
+
generated = extraction.generated;
|
|
2922
3507
|
}
|
|
2923
|
-
names = extraction.names;
|
|
2924
|
-
generated = extraction.generated;
|
|
2925
3508
|
}
|
|
2926
3509
|
let plan = {
|
|
2927
3510
|
files: [],
|
|
@@ -2935,6 +3518,11 @@ async function planPreparedGroup(context, members) {
|
|
|
2935
3518
|
};
|
|
2936
3519
|
let augmented = entry.css;
|
|
2937
3520
|
const blocked = /* @__PURE__ */ new Map();
|
|
3521
|
+
let candidateAliases;
|
|
3522
|
+
let canonicalized = false;
|
|
3523
|
+
let fontTokens = {};
|
|
3524
|
+
let fontFailures = /* @__PURE__ */ new Set();
|
|
3525
|
+
let composeEntry = () => entry.css;
|
|
2938
3526
|
finalization: for (;;) {
|
|
2939
3527
|
const baseIdentities = new Set(atRuleIdentities(`${entry.path}.group-base.css`, [entry.css, ...entry.graphSources.map((graphSource) => graphSource.source)].join("\n")));
|
|
2940
3528
|
const sheets = active.flatMap((member) => member.stylesheets.map((stylesheet) => ({
|
|
@@ -2997,6 +3585,7 @@ async function planPreparedGroup(context, members) {
|
|
|
2997
3585
|
utilityPrefix: entry.designSystem.theme.prefix,
|
|
2998
3586
|
themeTokens: entry.themeTokens,
|
|
2999
3587
|
...names ? { mediaNames: names } : {},
|
|
3588
|
+
...candidateAliases ? { candidateAliases } : {},
|
|
3000
3589
|
...groupWritable ? {} : { entryWritable: false },
|
|
3001
3590
|
files
|
|
3002
3591
|
};
|
|
@@ -3018,7 +3607,11 @@ async function planPreparedGroup(context, members) {
|
|
|
3018
3607
|
generated
|
|
3019
3608
|
} : void 0;
|
|
3020
3609
|
const used = currentExtraction ? usedGeneratedDefinitions(currentExtraction, plan) : [];
|
|
3021
|
-
|
|
3610
|
+
composeEntry = (tokens) => {
|
|
3611
|
+
const withFonts = appendFontTheme(composed, tokens);
|
|
3612
|
+
return currentExtraction ? appendMediaDefinitions(withFonts, used) : withFonts;
|
|
3613
|
+
};
|
|
3614
|
+
augmented = composeEntry(fontTokens);
|
|
3022
3615
|
let system;
|
|
3023
3616
|
try {
|
|
3024
3617
|
system = augmented === entry.css ? entry.designSystem : await entry.loadWith(augmented);
|
|
@@ -3041,13 +3634,65 @@ async function planPreparedGroup(context, members) {
|
|
|
3041
3634
|
continue planning;
|
|
3042
3635
|
}
|
|
3043
3636
|
}
|
|
3044
|
-
const failing = invalidCandidates(
|
|
3637
|
+
const failing = invalidCandidates(system, plan.candidates);
|
|
3045
3638
|
if (failing.length > 0) {
|
|
3046
3639
|
if (!accumulateBlockedRules(blocked, plan, failing)) throw new Error(`Tailwind did not generate CSS for candidate: ${failing[0]}`);
|
|
3047
3640
|
continue planning;
|
|
3048
3641
|
}
|
|
3642
|
+
if (!canonicalized) {
|
|
3643
|
+
canonicalized = true;
|
|
3644
|
+
const aliases = acceptedCandidateAliases(system, plan.candidateProbes ?? [], reservations);
|
|
3645
|
+
const fonts = await registerFontTokens({
|
|
3646
|
+
system,
|
|
3647
|
+
entryCss: augmented,
|
|
3648
|
+
loadWith: entry.loadWith,
|
|
3649
|
+
themeTokens: entry.themeTokens,
|
|
3650
|
+
probes: plan.fontFamilyProbes ?? [],
|
|
3651
|
+
corpus: [.../* @__PURE__ */ new Set([...plan.candidates, ...plan.candidateProbes ?? []])],
|
|
3652
|
+
reservations,
|
|
3653
|
+
existingAliases: aliases,
|
|
3654
|
+
generate: groupWritable,
|
|
3655
|
+
mentionedSegments,
|
|
3656
|
+
referenceTokens: entry.referenceTokens,
|
|
3657
|
+
globalAllocations: context.fontAllocations
|
|
3658
|
+
});
|
|
3659
|
+
Object.assign(aliases, fonts.aliases);
|
|
3660
|
+
fontTokens = fonts.tokens;
|
|
3661
|
+
fontFailures = fonts.failures;
|
|
3662
|
+
for (const [name, value] of Object.entries(fonts.tokens)) context.fontAllocations.set(name, value);
|
|
3663
|
+
if (Object.keys(aliases).length > 0) {
|
|
3664
|
+
candidateAliases = aliases;
|
|
3665
|
+
continue planning;
|
|
3666
|
+
}
|
|
3667
|
+
}
|
|
3049
3668
|
break;
|
|
3050
3669
|
}
|
|
3670
|
+
const ruleStatus = new Map(plan.rules.map((rule) => [`${rule.stylesheet}:${rule.ruleId.start}-${rule.ruleId.end}`, rule.status]));
|
|
3671
|
+
const warned = /* @__PURE__ */ new Set();
|
|
3672
|
+
for (const probe of plan.fontFamilyProbes ?? []) {
|
|
3673
|
+
if (probe.firstFamily.kind !== "name") continue;
|
|
3674
|
+
if (candidateAliases?.[probe.candidate] !== void 0) continue;
|
|
3675
|
+
const ruleKey = `${probe.stylesheet}:${probe.ruleId.start}-${probe.ruleId.end}`;
|
|
3676
|
+
if (ruleStatus.get(ruleKey) !== "retained" || warned.has(ruleKey)) continue;
|
|
3677
|
+
const failed = fontFailures.has(probe.candidate);
|
|
3678
|
+
if (groupWritable && !failed) continue;
|
|
3679
|
+
warned.add(ruleKey);
|
|
3680
|
+
plan.warnings.push({
|
|
3681
|
+
code: groupWritable ? "font-theme-registration-failed" : "font-theme-registration-required",
|
|
3682
|
+
file: sheets[probe.stylesheet].stylesheet.cssPath,
|
|
3683
|
+
start: probe.authoredSpan.start,
|
|
3684
|
+
end: probe.authoredSpan.end,
|
|
3685
|
+
message: groupWritable ? "A safe Tailwind font theme token could not be registered, so the rule is retained." : "The font family requires a Tailwind theme token, but the Tailwind entry cannot be edited, so the rule is retained."
|
|
3686
|
+
});
|
|
3687
|
+
}
|
|
3688
|
+
if (Object.keys(fontTokens).length > 0) {
|
|
3689
|
+
const applied = new Set(plan.candidates.map((candidate) => utilitySegment(candidate).replace(/!$/, "")));
|
|
3690
|
+
const pruned = Object.fromEntries(Object.entries(fontTokens).filter(([name]) => applied.has(`font-${name}`)));
|
|
3691
|
+
if (Object.keys(pruned).length !== Object.keys(fontTokens).length) {
|
|
3692
|
+
fontTokens = pruned;
|
|
3693
|
+
augmented = composeEntry(pruned);
|
|
3694
|
+
}
|
|
3695
|
+
}
|
|
3051
3696
|
for (const [index, rules] of blocked) {
|
|
3052
3697
|
const cssPath = sheets[index].stylesheet.cssPath;
|
|
3053
3698
|
for (const { authoredSpan, candidates } of rules.values()) {
|