tw-migrate 0.4.0 → 0.5.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 +58 -48
- package/dist/bin.js +7 -2
- package/dist/index.d.ts +6 -0
- package/dist/index.js +1513 -505
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -3,8 +3,8 @@ 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
5
|
import { execFile } from "node:child_process";
|
|
6
|
-
import { parse } from "parse5";
|
|
7
6
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
7
|
+
import { parse } from "parse5";
|
|
8
8
|
//#region src/util/diff.ts
|
|
9
9
|
function unifiedDiff(path, before, after) {
|
|
10
10
|
const oldLines = before.split("\n");
|
|
@@ -18,186 +18,10 @@ function unifiedDiff(path, before, after) {
|
|
|
18
18
|
].join("");
|
|
19
19
|
}
|
|
20
20
|
//#endregion
|
|
21
|
-
//#region src/parser/html.ts
|
|
22
|
-
const TEMPLATE_MARKERS = /\{\{|\}\}|\$\{|<%|%>|<#|#>|\[%|%\]/;
|
|
23
|
-
function parseHtmlSource(path, source) {
|
|
24
|
-
const errors = [];
|
|
25
|
-
const document = parse(source, {
|
|
26
|
-
sourceCodeLocationInfo: true,
|
|
27
|
-
onParseError(error) {
|
|
28
|
-
if (error.code !== "missing-doctype") errors.push(error);
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
if (errors.length > 0) throw new Error(`Failed to parse ${path}: ${errors.map((error) => error.code).join(", ")}`);
|
|
32
|
-
const links = [];
|
|
33
|
-
const bases = [];
|
|
34
|
-
const elements = [];
|
|
35
|
-
const dynamicAttributes = [];
|
|
36
|
-
const scriptTexts = [];
|
|
37
|
-
const unquoted = (attribute) => source[attribute.start - 1] !== "\"" && source[attribute.start - 1] !== "'";
|
|
38
|
-
function visit(node) {
|
|
39
|
-
if (node.tagName === "script") {
|
|
40
|
-
for (const child of node.childNodes ?? []) if (child.nodeName === "#text" && child.value) scriptTexts.push(child.value);
|
|
41
|
-
}
|
|
42
|
-
if (node.tagName) {
|
|
43
|
-
const attributes = new Map((node.attrs ?? []).map((attribute) => [attribute.name, attribute.value]));
|
|
44
|
-
const locations = node.sourceCodeLocation?.attrs;
|
|
45
|
-
if (locations) {
|
|
46
|
-
if (node.tagName === "link" && stylesheetRel(attributes.get("rel")) && !attributes.has("disabled")) {
|
|
47
|
-
const href = locatedAttribute(source, locations.href, attributes.get("href"));
|
|
48
|
-
const media = locatedAttribute(source, locations.media, attributes.get("media"));
|
|
49
|
-
const tag = node.sourceCodeLocation?.startTag ?? node.sourceCodeLocation;
|
|
50
|
-
if (href && tag) links.push({
|
|
51
|
-
href: href.value,
|
|
52
|
-
media: media?.value ?? "",
|
|
53
|
-
start: href.start,
|
|
54
|
-
end: href.end,
|
|
55
|
-
tagStart: tag.startOffset,
|
|
56
|
-
tagEnd: tag.endOffset
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
if (node.tagName === "base" && bases.length === 0) {
|
|
60
|
-
const href = locatedAttribute(source, locations.href, attributes.get("href"));
|
|
61
|
-
if (href) bases.push({
|
|
62
|
-
href: href.value,
|
|
63
|
-
writable: href.writable,
|
|
64
|
-
start: href.start,
|
|
65
|
-
end: href.end
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
let classAttribute = locatedAttribute(source, locations.class, attributes.get("class"));
|
|
69
|
-
const idAttribute = locatedAttribute(source, locations.id, attributes.get("id"));
|
|
70
|
-
const dynamic = (!classAttribute && idAttribute && locations.class ? {
|
|
71
|
-
start: locations.class.startOffset,
|
|
72
|
-
end: locations.class.endOffset
|
|
73
|
-
} : void 0) ?? [classAttribute, idAttribute].find((attribute) => attribute && (!attribute.writable || TEMPLATE_MARKERS.test(attribute.value))) ?? (classAttribute && unquoted(classAttribute) ? classAttribute : void 0);
|
|
74
|
-
if (dynamic) dynamicAttributes.push({
|
|
75
|
-
start: dynamic.start,
|
|
76
|
-
end: dynamic.end
|
|
77
|
-
});
|
|
78
|
-
else if (classAttribute || idAttribute) {
|
|
79
|
-
if (!classAttribute && idAttribute) {
|
|
80
|
-
const startTag = node.sourceCodeLocation?.startTag;
|
|
81
|
-
if (startTag && source[startTag.endOffset - 1] === ">") {
|
|
82
|
-
const insertion = classInsertionOffset(source, startTag.startOffset, startTag.endOffset - 1);
|
|
83
|
-
classAttribute = {
|
|
84
|
-
value: "",
|
|
85
|
-
start: insertion,
|
|
86
|
-
end: insertion,
|
|
87
|
-
synthetic: true
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
elements.push({
|
|
92
|
-
classAttribute,
|
|
93
|
-
idAttribute
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
for (const child of node.childNodes ?? []) visit(child);
|
|
99
|
-
if (node.content) visit(node.content);
|
|
100
|
-
}
|
|
101
|
-
visit(document);
|
|
102
|
-
return {
|
|
103
|
-
...toByteOffsets(source, {
|
|
104
|
-
links,
|
|
105
|
-
bases,
|
|
106
|
-
elements,
|
|
107
|
-
dynamicAttributes
|
|
108
|
-
}),
|
|
109
|
-
scriptText: scriptTexts.join("\n")
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
function utf8OffsetMap(source, indices) {
|
|
113
|
-
const sorted = [...new Set(indices)].sort((left, right) => left - right);
|
|
114
|
-
const map = /* @__PURE__ */ new Map();
|
|
115
|
-
let lastIndex = 0;
|
|
116
|
-
let bytes = 0;
|
|
117
|
-
for (const index of sorted) {
|
|
118
|
-
bytes += Buffer.byteLength(source.slice(lastIndex, index));
|
|
119
|
-
lastIndex = index;
|
|
120
|
-
map.set(index, bytes);
|
|
121
|
-
}
|
|
122
|
-
return map;
|
|
123
|
-
}
|
|
124
|
-
function offsetLookup(offsets) {
|
|
125
|
-
return (index) => {
|
|
126
|
-
const byte = offsets.get(index);
|
|
127
|
-
if (byte === void 0) throw new Error(`No byte offset was mapped for index ${index}`);
|
|
128
|
-
return byte;
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
function toByteOffsets(source, parsed) {
|
|
132
|
-
const offset = offsetLookup(utf8OffsetMap(source, [
|
|
133
|
-
...parsed.links.flatMap((link) => [
|
|
134
|
-
link.start,
|
|
135
|
-
link.end,
|
|
136
|
-
link.tagStart,
|
|
137
|
-
link.tagEnd
|
|
138
|
-
]),
|
|
139
|
-
...parsed.bases.flatMap((base) => [base.start, base.end]),
|
|
140
|
-
...parsed.elements.flatMap((element) => [element.classAttribute, element.idAttribute].flatMap((value) => value ? [value.start, value.end] : [])),
|
|
141
|
-
...parsed.dynamicAttributes.flatMap((value) => [value.start, value.end])
|
|
142
|
-
]));
|
|
143
|
-
const attribute = (value) => ({
|
|
144
|
-
...value,
|
|
145
|
-
start: offset(value.start),
|
|
146
|
-
end: offset(value.end)
|
|
147
|
-
});
|
|
148
|
-
return {
|
|
149
|
-
links: parsed.links.map((link) => ({
|
|
150
|
-
...link,
|
|
151
|
-
start: offset(link.start),
|
|
152
|
-
end: offset(link.end),
|
|
153
|
-
tagStart: offset(link.tagStart),
|
|
154
|
-
tagEnd: offset(link.tagEnd)
|
|
155
|
-
})),
|
|
156
|
-
bases: parsed.bases.map((base) => attribute(base)),
|
|
157
|
-
elements: parsed.elements.map((element) => ({
|
|
158
|
-
classAttribute: element.classAttribute && attribute(element.classAttribute),
|
|
159
|
-
idAttribute: element.idAttribute && attribute(element.idAttribute)
|
|
160
|
-
})),
|
|
161
|
-
dynamicAttributes: parsed.dynamicAttributes.map((value) => attribute(value))
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
function classInsertionOffset(source, floor, tagEnd) {
|
|
165
|
-
let offset = tagEnd;
|
|
166
|
-
if (source[offset - 1] === "/") offset -= 1;
|
|
167
|
-
while (offset > floor && /\s/.test(source[offset - 1] ?? "")) offset -= 1;
|
|
168
|
-
return offset;
|
|
169
|
-
}
|
|
170
|
-
function stylesheetRel(value = "") {
|
|
171
|
-
const tokens = value.toLowerCase().split(/\s+/);
|
|
172
|
-
return tokens.includes("stylesheet") && !tokens.includes("alternate");
|
|
173
|
-
}
|
|
174
|
-
function locatedAttribute(source, location, parsedValue) {
|
|
175
|
-
if (!location || parsedValue === void 0) return void 0;
|
|
176
|
-
const raw = source.slice(location.startOffset, location.endOffset);
|
|
177
|
-
const equals = raw.indexOf("=");
|
|
178
|
-
if (equals < 0) return void 0;
|
|
179
|
-
let start = equals + 1;
|
|
180
|
-
while (/\s/.test(raw[start] ?? "")) start += 1;
|
|
181
|
-
const quote = raw[start];
|
|
182
|
-
let end;
|
|
183
|
-
if (quote === "\"" || quote === "'") {
|
|
184
|
-
start += 1;
|
|
185
|
-
end = raw.lastIndexOf(quote);
|
|
186
|
-
if (end < start) return void 0;
|
|
187
|
-
} else {
|
|
188
|
-
end = start;
|
|
189
|
-
while (end < raw.length && !/\s/.test(raw[end] ?? "")) end += 1;
|
|
190
|
-
}
|
|
191
|
-
const value = raw.slice(start, end);
|
|
192
|
-
return {
|
|
193
|
-
value: value.includes("&") ? parsedValue : value,
|
|
194
|
-
writable: !value.includes("&"),
|
|
195
|
-
start: location.startOffset + start,
|
|
196
|
-
end: location.startOffset + end
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
//#endregion
|
|
200
21
|
//#region src/native.ts
|
|
22
|
+
function errorCode(error) {
|
|
23
|
+
return error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : void 0;
|
|
24
|
+
}
|
|
201
25
|
const require = createRequire(import.meta.url);
|
|
202
26
|
const target = {
|
|
203
27
|
"darwin-arm64": "darwin-arm64",
|
|
@@ -219,7 +43,75 @@ try {
|
|
|
219
43
|
throw new Error(`No tw-migrate native addon was found for ${target}. Reinstall the package or build it locally.`);
|
|
220
44
|
}
|
|
221
45
|
}
|
|
222
|
-
const {
|
|
46
|
+
const { collectMediaConditions, mediaProbeKey, decodeSourceMap, planBatchMigration, validateCss } = binding;
|
|
47
|
+
function object(value) {
|
|
48
|
+
return value !== null && typeof value === "object";
|
|
49
|
+
}
|
|
50
|
+
function decode(label, json, valid) {
|
|
51
|
+
const value = JSON.parse(json);
|
|
52
|
+
if (!valid(value)) throw new Error(`Invalid native ${label} result`);
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
function cssDirectives$1(source) {
|
|
56
|
+
try {
|
|
57
|
+
const value = JSON.parse(binding.collectCssDirectives(source));
|
|
58
|
+
return Array.isArray(value) ? value : null;
|
|
59
|
+
} catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function sourceImport(value) {
|
|
64
|
+
return object(value) && typeof value.specifier === "string" && typeof value.typeOnly === "boolean" && typeof value.dynamic === "boolean";
|
|
65
|
+
}
|
|
66
|
+
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") && [
|
|
68
|
+
value.vueGlobUnverifiable,
|
|
69
|
+
value.hasDynamicImport,
|
|
70
|
+
value.hasVueFallthroughMacro,
|
|
71
|
+
value.usesCssModule,
|
|
72
|
+
value.hasUnboundUseCssModule,
|
|
73
|
+
value.definesRootUseCssModule
|
|
74
|
+
].every((item) => typeof item === "boolean");
|
|
75
|
+
}
|
|
76
|
+
const ANALYSIS_CACHE_LIMIT = 4096;
|
|
77
|
+
const sourceAnalysisCache = /* @__PURE__ */ new Map();
|
|
78
|
+
const stylesheetAnalysisCache = /* @__PURE__ */ new Map();
|
|
79
|
+
function bound(cache) {
|
|
80
|
+
while (cache.size > ANALYSIS_CACHE_LIMIT) {
|
|
81
|
+
const oldest = cache.keys().next().value;
|
|
82
|
+
if (oldest === void 0) break;
|
|
83
|
+
cache.delete(oldest);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
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"));
|
|
88
|
+
}
|
|
89
|
+
function sourceAnalysis(path, source) {
|
|
90
|
+
const cached = sourceAnalysisCache.get(path);
|
|
91
|
+
if (cached?.source === source) return cached.analysis;
|
|
92
|
+
const analysis = decode("source analysis", binding.sourceAnalysis(path, source), sourceAnalysisResult);
|
|
93
|
+
sourceAnalysisCache.set(path, {
|
|
94
|
+
source,
|
|
95
|
+
analysis
|
|
96
|
+
});
|
|
97
|
+
bound(sourceAnalysisCache);
|
|
98
|
+
return analysis;
|
|
99
|
+
}
|
|
100
|
+
function stylesheetAnalysis(path, source) {
|
|
101
|
+
const cached = stylesheetAnalysisCache.get(path);
|
|
102
|
+
if (cached?.source === source) return cached.analysis;
|
|
103
|
+
const analysis = decode("stylesheet analysis", binding.stylesheetAnalysis(path, source), (value) => object(value) && Array.isArray(value.references) && value.references.every((item) => typeof item === "string") && Array.isArray(value.imports) && value.imports.every((item) => object(item) && typeof item.href === "string" && typeof item.media === "string" && typeof item.start === "number" && typeof item.end === "number") && [
|
|
104
|
+
value.unverifiable,
|
|
105
|
+
value.scopeEscapesUnverifiable,
|
|
106
|
+
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");
|
|
108
|
+
stylesheetAnalysisCache.set(path, {
|
|
109
|
+
source,
|
|
110
|
+
analysis
|
|
111
|
+
});
|
|
112
|
+
bound(stylesheetAnalysisCache);
|
|
113
|
+
return analysis;
|
|
114
|
+
}
|
|
223
115
|
//#endregion
|
|
224
116
|
//#region src/parser/style-compiler.ts
|
|
225
117
|
const SASS_EXTENSIONS = /* @__PURE__ */ new Set([".scss", ".sass"]);
|
|
@@ -355,9 +247,6 @@ const IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
|
355
247
|
"node_modules"
|
|
356
248
|
]);
|
|
357
249
|
const RECOVERABLE_INPUT_ERROR = "TW_MIGRATE_RECOVERABLE_INPUT:";
|
|
358
|
-
function errorCode(error) {
|
|
359
|
-
return error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : void 0;
|
|
360
|
-
}
|
|
361
250
|
function errorMessage(error) {
|
|
362
251
|
return error instanceof Error ? error.message : String(error);
|
|
363
252
|
}
|
|
@@ -374,15 +263,6 @@ function isStylesheetModule(path) {
|
|
|
374
263
|
const syntax = stylesheetSyntax(path);
|
|
375
264
|
return syntax !== void 0 && path.endsWith(`.module.${syntax}`);
|
|
376
265
|
}
|
|
377
|
-
function sourceReferencesStyle(file, stylePath) {
|
|
378
|
-
let importPath = normalizedRelativePath(dirname(file.path), stylePath);
|
|
379
|
-
if (!importPath.startsWith(".")) importPath = `./${importPath}`;
|
|
380
|
-
return [
|
|
381
|
-
`'${importPath}'`,
|
|
382
|
-
`"${importPath}"`,
|
|
383
|
-
`\`${importPath}\``
|
|
384
|
-
].some((literal) => file.source.includes(literal));
|
|
385
|
-
}
|
|
386
266
|
function isProjectInput(workspaceRoot, path) {
|
|
387
267
|
return isWithin(workspaceRoot, path) && !relative(workspaceRoot, path).split(/[\\/]/).includes("node_modules");
|
|
388
268
|
}
|
|
@@ -427,24 +307,26 @@ function isIntegrityError(error) {
|
|
|
427
307
|
function normalizedRelativePath(root, path) {
|
|
428
308
|
return relative(root, path).split(sep).join("/");
|
|
429
309
|
}
|
|
430
|
-
function maskCssComments(source) {
|
|
431
|
-
return source.replace(/\/\*[\s\S]*?\*\//g, (comment) => comment.replace(/[^\r\n]/g, " "));
|
|
432
|
-
}
|
|
433
310
|
function indexStylesheetDependents(styleSources) {
|
|
434
311
|
const dependents = /* @__PURE__ */ new Map();
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
312
|
+
const allPossibleTargets = [...styleSources.keys()].filter((path) => isStylesheetModule(path) || isPreprocessorPath(path));
|
|
313
|
+
for (const [path, source] of styleSources) {
|
|
314
|
+
let references;
|
|
315
|
+
try {
|
|
316
|
+
const analysis = stylesheetAnalysis(path, source);
|
|
317
|
+
references = analysis.references;
|
|
318
|
+
if (analysis.unverifiable) references = [...references, ...allPossibleTargets];
|
|
319
|
+
} catch {
|
|
320
|
+
references = allPossibleTargets;
|
|
321
|
+
}
|
|
322
|
+
for (const reference of new Set(references)) {
|
|
323
|
+
const targets = styleSources.has(reference) ? [reference] : stylesheetReferenceTargets(path, reference, styleSources);
|
|
324
|
+
for (const target of targets) {
|
|
325
|
+
if (target === path || !isStylesheetModule(target) && !isPreprocessorPath(target)) continue;
|
|
326
|
+
const paths = dependents.get(target) ?? [];
|
|
327
|
+
paths.push(path);
|
|
328
|
+
dependents.set(target, paths);
|
|
329
|
+
}
|
|
448
330
|
}
|
|
449
331
|
}
|
|
450
332
|
for (const [target, paths] of dependents) dependents.set(target, [...new Set(paths)].sort());
|
|
@@ -466,63 +348,8 @@ function stylesheetReferenceTargets(importer, reference, styleSources) {
|
|
|
466
348
|
join(target, `index${syntax}`)
|
|
467
349
|
])).filter((path) => styleSources.has(path));
|
|
468
350
|
}
|
|
469
|
-
function cssImports(source) {
|
|
470
|
-
|
|
471
|
-
const masked = maskCssComments(source);
|
|
472
|
-
let depth = 0;
|
|
473
|
-
let quote;
|
|
474
|
-
let importsAllowed = true;
|
|
475
|
-
for (let index = 0; index < masked.length; index += 1) {
|
|
476
|
-
const character = masked[index];
|
|
477
|
-
if (quote) {
|
|
478
|
-
if (character === "\\") index += 1;
|
|
479
|
-
else if (character === quote) quote = void 0;
|
|
480
|
-
continue;
|
|
481
|
-
}
|
|
482
|
-
if (character === "\"" || character === "'") {
|
|
483
|
-
quote = character;
|
|
484
|
-
continue;
|
|
485
|
-
}
|
|
486
|
-
if (character === "{") {
|
|
487
|
-
depth += 1;
|
|
488
|
-
importsAllowed = false;
|
|
489
|
-
continue;
|
|
490
|
-
}
|
|
491
|
-
if (character === "}") {
|
|
492
|
-
depth = Math.max(0, depth - 1);
|
|
493
|
-
continue;
|
|
494
|
-
}
|
|
495
|
-
if (!importsAllowed || depth !== 0 || masked.slice(index, index + 7).toLowerCase() !== "@import" || /[-\w]/.test(masked[index + 7] ?? "")) continue;
|
|
496
|
-
let end = index + 7;
|
|
497
|
-
let importQuote;
|
|
498
|
-
let parentheses = 0;
|
|
499
|
-
for (; end < masked.length; end += 1) {
|
|
500
|
-
const next = masked[end];
|
|
501
|
-
if (importQuote) {
|
|
502
|
-
if (next === "\\") end += 1;
|
|
503
|
-
else if (next === importQuote) importQuote = void 0;
|
|
504
|
-
} else if (next === "\"" || next === "'") importQuote = next;
|
|
505
|
-
else if (next === "(") parentheses += 1;
|
|
506
|
-
else if (next === ")") parentheses = Math.max(0, parentheses - 1);
|
|
507
|
-
else if (next === ";" && parentheses === 0) break;
|
|
508
|
-
}
|
|
509
|
-
if (end >= masked.length) continue;
|
|
510
|
-
const statement = masked.slice(index, end + 1);
|
|
511
|
-
const match = /^@import\s+(?:url\(\s*)?(?:["']([^"']+)["']|([^"'()\s;]+))\s*\)?\s*([^;]*);$/i.exec(statement);
|
|
512
|
-
if (match) imports.push({
|
|
513
|
-
href: match[1] ?? match[2],
|
|
514
|
-
media: match[3].trim(),
|
|
515
|
-
start: index,
|
|
516
|
-
end: end + 1
|
|
517
|
-
});
|
|
518
|
-
index = end;
|
|
519
|
-
}
|
|
520
|
-
const offsets = utf8OffsetMap(source, imports.flatMap((imported) => [imported.start, imported.end]));
|
|
521
|
-
return imports.map((imported) => ({
|
|
522
|
-
...imported,
|
|
523
|
-
start: offsets.get(imported.start) ?? imported.start,
|
|
524
|
-
end: offsets.get(imported.end) ?? imported.end
|
|
525
|
-
}));
|
|
351
|
+
function cssImports(path, source) {
|
|
352
|
+
return stylesheetAnalysis(path, source).imports;
|
|
526
353
|
}
|
|
527
354
|
//#endregion
|
|
528
355
|
//#region src/discovery.ts
|
|
@@ -593,6 +420,28 @@ async function findGitRoot(cwd) {
|
|
|
593
420
|
return;
|
|
594
421
|
}
|
|
595
422
|
}
|
|
423
|
+
async function scannerIgnoredPaths(workspaceRoot, paths) {
|
|
424
|
+
if (paths.length === 0 || await findGitRoot(workspaceRoot) !== workspaceRoot) return /* @__PURE__ */ new Set();
|
|
425
|
+
return new Promise((resolvePromise) => {
|
|
426
|
+
execFile("git", [
|
|
427
|
+
"check-ignore",
|
|
428
|
+
"--no-index",
|
|
429
|
+
"-z",
|
|
430
|
+
"--stdin"
|
|
431
|
+
], {
|
|
432
|
+
cwd: workspaceRoot,
|
|
433
|
+
maxBuffer: 67108864
|
|
434
|
+
}, (error, stdout) => {
|
|
435
|
+
if (error !== null && error.code !== 1) {
|
|
436
|
+
resolvePromise(new Set(paths));
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
const ignored = /* @__PURE__ */ new Set();
|
|
440
|
+
for (const entry of stdout.split("\0")) if (entry) ignored.add(resolve(workspaceRoot, entry));
|
|
441
|
+
resolvePromise(ignored);
|
|
442
|
+
}).stdin?.end(paths.map((path) => `${relative(workspaceRoot, path)}\0`).join(""));
|
|
443
|
+
});
|
|
444
|
+
}
|
|
596
445
|
async function isIgnoredByGit(gitRoot, path) {
|
|
597
446
|
if (gitRoot === path) return false;
|
|
598
447
|
try {
|
|
@@ -617,7 +466,7 @@ async function discoverGitFiles(root) {
|
|
|
617
466
|
"."
|
|
618
467
|
], {
|
|
619
468
|
cwd: root,
|
|
620
|
-
maxBuffer:
|
|
469
|
+
maxBuffer: 67108864
|
|
621
470
|
});
|
|
622
471
|
const paths = stdout.split("\0").filter(Boolean).map((path) => resolve(root, path)).filter((path) => !hasIgnoredDirectory(root, path) && isRelevantDiscoveredFile(path));
|
|
623
472
|
return (await Promise.all(paths.map(async (path) => {
|
|
@@ -626,37 +475,509 @@ async function discoverGitFiles(root) {
|
|
|
626
475
|
} catch {
|
|
627
476
|
return;
|
|
628
477
|
}
|
|
629
|
-
}))).flatMap((path) => path ? [path] : []).sort();
|
|
478
|
+
}))).flatMap((path) => path ? [path] : []).sort();
|
|
479
|
+
}
|
|
480
|
+
async function discoverPackageRoots(workspaceRoot, paths) {
|
|
481
|
+
const roots = paths.filter((path) => basename(path) === "package.json").map(dirname);
|
|
482
|
+
try {
|
|
483
|
+
await readFile(join(workspaceRoot, "package.json"), "utf8");
|
|
484
|
+
roots.push(workspaceRoot);
|
|
485
|
+
} catch (error) {
|
|
486
|
+
if (errorCode(error) !== "ENOENT") throw error;
|
|
487
|
+
}
|
|
488
|
+
return [...new Set(roots)].sort();
|
|
489
|
+
}
|
|
490
|
+
function hasIgnoredDirectory(root, path) {
|
|
491
|
+
return relative(root, path).split(/[\\/]/).some((part) => IGNORED_DIRECTORIES.has(part));
|
|
492
|
+
}
|
|
493
|
+
function isRelevantDiscoveredFile(path) {
|
|
494
|
+
return basename(path) === "package.json" || isStylesheetPath(path) || SOURCE_EXTENSIONS.has(extname(path));
|
|
495
|
+
}
|
|
496
|
+
async function collectFiles(root, include) {
|
|
497
|
+
const files = [];
|
|
498
|
+
async function visit(directory) {
|
|
499
|
+
const entries = await readdir(directory, { withFileTypes: true });
|
|
500
|
+
for (const entry of entries) {
|
|
501
|
+
const path = join(directory, entry.name);
|
|
502
|
+
if (entry.isDirectory()) {
|
|
503
|
+
if (!IGNORED_DIRECTORIES.has(entry.name)) await visit(path);
|
|
504
|
+
} else if (entry.isFile() && include(path)) files.push(path);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
await visit(root);
|
|
508
|
+
return files;
|
|
509
|
+
}
|
|
510
|
+
//#endregion
|
|
511
|
+
//#region src/parser/html.ts
|
|
512
|
+
const TEMPLATE_MARKERS = /\{\{|\}\}|\$\{|<%|%>|<#|#>|\[%|%\]/;
|
|
513
|
+
function parseHtmlSource(path, source) {
|
|
514
|
+
const errors = [];
|
|
515
|
+
const document = parse(source, {
|
|
516
|
+
sourceCodeLocationInfo: true,
|
|
517
|
+
scriptingEnabled: false,
|
|
518
|
+
onParseError(error) {
|
|
519
|
+
if (error.code !== "missing-doctype") errors.push(error);
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
if (errors.length > 0) throw new Error(`Failed to parse ${path}: ${errors.map((error) => error.code).join(", ")}`);
|
|
523
|
+
const links = [];
|
|
524
|
+
const bases = [];
|
|
525
|
+
const elements = [];
|
|
526
|
+
const dynamicAttributes = [];
|
|
527
|
+
const scriptTexts = [];
|
|
528
|
+
let hasStyle = false;
|
|
529
|
+
const unquoted = (attribute) => source[attribute.start - 1] !== "\"" && source[attribute.start - 1] !== "'";
|
|
530
|
+
function visit(node) {
|
|
531
|
+
if (node.tagName === "style") hasStyle = true;
|
|
532
|
+
if (node.tagName === "script") {
|
|
533
|
+
for (const child of node.childNodes ?? []) if (child.nodeName === "#text" && child.value) scriptTexts.push(child.value);
|
|
534
|
+
}
|
|
535
|
+
if (node.tagName) {
|
|
536
|
+
const attributes = new Map((node.attrs ?? []).map((attribute) => [attribute.name, attribute.value]));
|
|
537
|
+
const locations = node.sourceCodeLocation?.attrs;
|
|
538
|
+
if (locations) {
|
|
539
|
+
if (node.tagName === "link" && stylesheetRel(attributes.get("rel")) && !attributes.has("disabled")) {
|
|
540
|
+
const href = locatedAttribute(source, locations.href, attributes.get("href"));
|
|
541
|
+
const media = locatedAttribute(source, locations.media, attributes.get("media"));
|
|
542
|
+
const tag = node.sourceCodeLocation?.startTag ?? node.sourceCodeLocation;
|
|
543
|
+
if (href && tag) links.push({
|
|
544
|
+
href: href.value,
|
|
545
|
+
media: media?.value ?? "",
|
|
546
|
+
start: href.start,
|
|
547
|
+
end: href.end,
|
|
548
|
+
tagStart: tag.startOffset,
|
|
549
|
+
tagEnd: tag.endOffset
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
if (node.tagName === "base" && bases.length === 0) {
|
|
553
|
+
const href = locatedAttribute(source, locations.href, attributes.get("href"));
|
|
554
|
+
if (href) bases.push({
|
|
555
|
+
href: href.value,
|
|
556
|
+
writable: href.writable,
|
|
557
|
+
start: href.start,
|
|
558
|
+
end: href.end
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
let classAttribute = locatedAttribute(source, locations.class, attributes.get("class"));
|
|
562
|
+
const idAttribute = locatedAttribute(source, locations.id, attributes.get("id"));
|
|
563
|
+
const dynamic = (!classAttribute && idAttribute && locations.class ? {
|
|
564
|
+
start: locations.class.startOffset,
|
|
565
|
+
end: locations.class.endOffset
|
|
566
|
+
} : void 0) ?? [classAttribute, idAttribute].find((attribute) => attribute && (!attribute.writable || TEMPLATE_MARKERS.test(attribute.value))) ?? (classAttribute && unquoted(classAttribute) ? classAttribute : void 0);
|
|
567
|
+
if (dynamic) dynamicAttributes.push({
|
|
568
|
+
start: dynamic.start,
|
|
569
|
+
end: dynamic.end
|
|
570
|
+
});
|
|
571
|
+
else if (classAttribute || idAttribute) {
|
|
572
|
+
if (!classAttribute && idAttribute) {
|
|
573
|
+
const startTag = node.sourceCodeLocation?.startTag;
|
|
574
|
+
if (startTag && source[startTag.endOffset - 1] === ">") {
|
|
575
|
+
const insertion = classInsertionOffset(source, startTag.startOffset, startTag.endOffset - 1);
|
|
576
|
+
classAttribute = {
|
|
577
|
+
value: "",
|
|
578
|
+
start: insertion,
|
|
579
|
+
end: insertion,
|
|
580
|
+
synthetic: true
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
elements.push({
|
|
585
|
+
classAttribute,
|
|
586
|
+
idAttribute
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
for (const child of node.childNodes ?? []) visit(child);
|
|
592
|
+
if (node.content) visit(node.content);
|
|
593
|
+
}
|
|
594
|
+
visit(document);
|
|
595
|
+
return {
|
|
596
|
+
...toByteOffsets(source, {
|
|
597
|
+
links,
|
|
598
|
+
bases,
|
|
599
|
+
elements,
|
|
600
|
+
dynamicAttributes
|
|
601
|
+
}),
|
|
602
|
+
scriptText: scriptTexts.join("\n"),
|
|
603
|
+
hasStyle
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
function utf8OffsetMap(source, indices) {
|
|
607
|
+
const sorted = [...new Set(indices)].sort((left, right) => left - right);
|
|
608
|
+
const map = /* @__PURE__ */ new Map();
|
|
609
|
+
let lastIndex = 0;
|
|
610
|
+
let bytes = 0;
|
|
611
|
+
for (const index of sorted) {
|
|
612
|
+
bytes += Buffer.byteLength(source.slice(lastIndex, index));
|
|
613
|
+
lastIndex = index;
|
|
614
|
+
map.set(index, bytes);
|
|
615
|
+
}
|
|
616
|
+
return map;
|
|
617
|
+
}
|
|
618
|
+
function offsetLookup(offsets) {
|
|
619
|
+
return (index) => {
|
|
620
|
+
const byte = offsets.get(index);
|
|
621
|
+
if (byte === void 0) throw new Error(`No byte offset was mapped for index ${index}`);
|
|
622
|
+
return byte;
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
function toByteOffsets(source, parsed) {
|
|
626
|
+
const offset = offsetLookup(utf8OffsetMap(source, [
|
|
627
|
+
...parsed.links.flatMap((link) => [
|
|
628
|
+
link.start,
|
|
629
|
+
link.end,
|
|
630
|
+
link.tagStart,
|
|
631
|
+
link.tagEnd
|
|
632
|
+
]),
|
|
633
|
+
...parsed.bases.flatMap((base) => [base.start, base.end]),
|
|
634
|
+
...parsed.elements.flatMap((element) => [element.classAttribute, element.idAttribute].flatMap((value) => value ? [value.start, value.end] : [])),
|
|
635
|
+
...parsed.dynamicAttributes.flatMap((value) => [value.start, value.end])
|
|
636
|
+
]));
|
|
637
|
+
const attribute = (value) => ({
|
|
638
|
+
...value,
|
|
639
|
+
start: offset(value.start),
|
|
640
|
+
end: offset(value.end)
|
|
641
|
+
});
|
|
642
|
+
return {
|
|
643
|
+
links: parsed.links.map((link) => ({
|
|
644
|
+
...link,
|
|
645
|
+
start: offset(link.start),
|
|
646
|
+
end: offset(link.end),
|
|
647
|
+
tagStart: offset(link.tagStart),
|
|
648
|
+
tagEnd: offset(link.tagEnd)
|
|
649
|
+
})),
|
|
650
|
+
bases: parsed.bases.map((base) => attribute(base)),
|
|
651
|
+
elements: parsed.elements.map((element) => ({
|
|
652
|
+
classAttribute: element.classAttribute && attribute(element.classAttribute),
|
|
653
|
+
idAttribute: element.idAttribute && attribute(element.idAttribute)
|
|
654
|
+
})),
|
|
655
|
+
dynamicAttributes: parsed.dynamicAttributes.map((value) => attribute(value))
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
function classInsertionOffset(source, floor, tagEnd) {
|
|
659
|
+
let offset = tagEnd;
|
|
660
|
+
if (source[offset - 1] === "/") offset -= 1;
|
|
661
|
+
while (offset > floor && /\s/.test(source[offset - 1] ?? "")) offset -= 1;
|
|
662
|
+
return offset;
|
|
663
|
+
}
|
|
664
|
+
function stylesheetRel(value = "") {
|
|
665
|
+
const tokens = value.toLowerCase().split(/\s+/);
|
|
666
|
+
return tokens.includes("stylesheet") && !tokens.includes("alternate");
|
|
667
|
+
}
|
|
668
|
+
function locatedAttribute(source, location, parsedValue) {
|
|
669
|
+
if (!location || parsedValue === void 0) return void 0;
|
|
670
|
+
const raw = source.slice(location.startOffset, location.endOffset);
|
|
671
|
+
const equals = raw.indexOf("=");
|
|
672
|
+
if (equals < 0) return void 0;
|
|
673
|
+
let start = equals + 1;
|
|
674
|
+
while (/\s/.test(raw[start] ?? "")) start += 1;
|
|
675
|
+
const quote = raw[start];
|
|
676
|
+
let end;
|
|
677
|
+
if (quote === "\"" || quote === "'") {
|
|
678
|
+
start += 1;
|
|
679
|
+
end = raw.lastIndexOf(quote);
|
|
680
|
+
if (end < start) return void 0;
|
|
681
|
+
} else {
|
|
682
|
+
end = start;
|
|
683
|
+
while (end < raw.length && !/\s/.test(raw[end] ?? "")) end += 1;
|
|
684
|
+
}
|
|
685
|
+
const value = raw.slice(start, end);
|
|
686
|
+
return {
|
|
687
|
+
value: value.includes("&") ? parsedValue : value,
|
|
688
|
+
writable: !value.includes("&"),
|
|
689
|
+
start: location.startOffset + start,
|
|
690
|
+
end: location.startOffset + end
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
//#endregion
|
|
694
|
+
//#region src/plan/entry.ts
|
|
695
|
+
function emitsUtilities(specifier) {
|
|
696
|
+
return specifier === "tailwindcss" || specifier === "tailwindcss/utilities" || specifier === "tailwindcss/utilities.css";
|
|
697
|
+
}
|
|
698
|
+
function hasTailwindImport(source) {
|
|
699
|
+
return cssDirectives(source)?.some((directive) => directive.kind === "import" && directive.specifier !== null && emitsUtilities(directive.specifier)) ?? false;
|
|
700
|
+
}
|
|
701
|
+
function tailwindEntryCatalog(styleSources, pathOwners) {
|
|
702
|
+
const catalog = /* @__PURE__ */ new Map();
|
|
703
|
+
for (const [path, source] of styleSources) {
|
|
704
|
+
if (extname(path) !== ".css") continue;
|
|
705
|
+
if (!hasTailwindImport(source)) continue;
|
|
706
|
+
const owner = pathOwners.get(path);
|
|
707
|
+
if (owner === void 0) continue;
|
|
708
|
+
const entries = catalog.get(owner) ?? [];
|
|
709
|
+
entries.push(path);
|
|
710
|
+
catalog.set(owner, entries);
|
|
711
|
+
}
|
|
712
|
+
for (const entries of catalog.values()) entries.sort();
|
|
713
|
+
return catalog;
|
|
714
|
+
}
|
|
715
|
+
function cssDirectives(source) {
|
|
716
|
+
return cssDirectives$1(source)?.filter((directive) => directive !== null && typeof directive === "object" && "kind" in directive && typeof directive.kind === "string") ?? null;
|
|
717
|
+
}
|
|
718
|
+
function entryGraphSheets(entry, styleSources) {
|
|
719
|
+
const sheets = [];
|
|
720
|
+
const seen = /* @__PURE__ */ new Set();
|
|
721
|
+
const pending = [entry];
|
|
722
|
+
while (pending.length > 0) {
|
|
723
|
+
const path = pending.pop();
|
|
724
|
+
if (path === void 0 || seen.has(path)) continue;
|
|
725
|
+
seen.add(path);
|
|
726
|
+
const raw = styleSources.get(path);
|
|
727
|
+
if (raw === void 0) return null;
|
|
728
|
+
const directives = cssDirectives(raw);
|
|
729
|
+
if (directives === null) return null;
|
|
730
|
+
sheets.push({
|
|
731
|
+
path,
|
|
732
|
+
directives
|
|
733
|
+
});
|
|
734
|
+
for (const directive of directives) {
|
|
735
|
+
if (directive.kind !== "import") continue;
|
|
736
|
+
if (directive.specifier === null) return null;
|
|
737
|
+
const spec = directive.specifier;
|
|
738
|
+
if (directive.tailwind) continue;
|
|
739
|
+
if (!spec.startsWith(".") && !spec.startsWith("/")) {
|
|
740
|
+
let resolved;
|
|
741
|
+
try {
|
|
742
|
+
resolved = createRequire(join(dirname(path), "package.json")).resolve(spec);
|
|
743
|
+
} catch {
|
|
744
|
+
return null;
|
|
745
|
+
}
|
|
746
|
+
if (/[\\/]node_modules[\\/]/.test(resolved)) continue;
|
|
747
|
+
pending.push(resolved);
|
|
748
|
+
continue;
|
|
749
|
+
}
|
|
750
|
+
const resolved = resolve(dirname(path), spec);
|
|
751
|
+
pending.push(styleSources.has(resolved) ? resolved : `${resolved}.css`);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
return sheets;
|
|
755
|
+
}
|
|
756
|
+
function scanProof(options) {
|
|
757
|
+
const sheets = entryGraphSheets(options.entry, new Map([...options.styleSources ?? [], [options.entry, options.entrySource]]));
|
|
758
|
+
if (sheets === null) return null;
|
|
759
|
+
let literal = false;
|
|
760
|
+
let automaticDisabled = false;
|
|
761
|
+
for (const { path, directives } of sheets) {
|
|
762
|
+
const base = dirname(path);
|
|
763
|
+
const coversChild = (scope) => {
|
|
764
|
+
if (scope.includes("*")) return false;
|
|
765
|
+
return isWithin(resolve(base, scope), options.packageRoot);
|
|
766
|
+
};
|
|
767
|
+
const excludesChild = (scope) => {
|
|
768
|
+
const stripped = resolve(base, scope.replace(/\*.*$/, ""));
|
|
769
|
+
if (scope.includes("*")) return options.packageRoot.startsWith(stripped) || stripped.startsWith(options.packageRoot);
|
|
770
|
+
return isWithin(stripped, options.packageRoot) || isWithin(options.packageRoot, stripped);
|
|
771
|
+
};
|
|
772
|
+
for (const directive of directives) if (directive.kind === "source") {
|
|
773
|
+
if (directive.inline) continue;
|
|
774
|
+
if (directive.unreadable || directive.scope === null) return null;
|
|
775
|
+
if (directive.not) {
|
|
776
|
+
if (excludesChild(directive.scope)) return null;
|
|
777
|
+
} else if (coversChild(directive.scope)) literal = true;
|
|
778
|
+
} else if (directive.kind === "import" && directive.tailwind) {
|
|
779
|
+
if (directive.sourceUnreadable) return null;
|
|
780
|
+
if (directive.source === "none") automaticDisabled = true;
|
|
781
|
+
else if (directive.source !== null) if (coversChild(directive.source)) literal = true;
|
|
782
|
+
else automaticDisabled = true;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
if (literal) return "literal";
|
|
786
|
+
if (automaticDisabled) return null;
|
|
787
|
+
return isWithin(dirname(options.entry), options.packageRoot) ? "automatic" : null;
|
|
788
|
+
}
|
|
789
|
+
const OPAQUE_SOURCE_IMPORT = {
|
|
790
|
+
specifier: "#unverifiable-source",
|
|
791
|
+
typeOnly: false,
|
|
792
|
+
dynamic: true
|
|
793
|
+
};
|
|
794
|
+
function sourceImports(file) {
|
|
795
|
+
const extension = extname(file.path);
|
|
796
|
+
if (extension === ".html") return [];
|
|
797
|
+
if (extension === ".vue") return file.sourceImports && !file.sourceImportsUnverifiable ? file.sourceImports : [OPAQUE_SOURCE_IMPORT];
|
|
798
|
+
try {
|
|
799
|
+
return sourceAnalysis(file.path, file.source).imports;
|
|
800
|
+
} catch {
|
|
801
|
+
return [OPAQUE_SOURCE_IMPORT];
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
const RESOLVABLE_EXTENSIONS = [
|
|
805
|
+
".ts",
|
|
806
|
+
".tsx",
|
|
807
|
+
".mts",
|
|
808
|
+
".cts",
|
|
809
|
+
".js",
|
|
810
|
+
".jsx",
|
|
811
|
+
".mjs",
|
|
812
|
+
".cjs",
|
|
813
|
+
".vue"
|
|
814
|
+
];
|
|
815
|
+
function resolveImport(fromDir, spec, known) {
|
|
816
|
+
if (!spec.startsWith(".")) return void 0;
|
|
817
|
+
const base = resolve(fromDir, spec);
|
|
818
|
+
const emitted = base.match(/^(.*)\.(?:mjs|cjs|js|jsx)$/);
|
|
819
|
+
return [
|
|
820
|
+
base,
|
|
821
|
+
...emitted ? [
|
|
822
|
+
".ts",
|
|
823
|
+
".tsx",
|
|
824
|
+
".mts",
|
|
825
|
+
".cts"
|
|
826
|
+
].map((extension) => `${emitted[1]}${extension}`) : [],
|
|
827
|
+
...RESOLVABLE_EXTENSIONS.map((extension) => `${base}${extension}`),
|
|
828
|
+
...RESOLVABLE_EXTENSIONS.map((extension) => join(base, `index${extension}`))
|
|
829
|
+
].find((candidate) => known.has(candidate));
|
|
830
|
+
}
|
|
831
|
+
function scriptLikeSpecifier(spec) {
|
|
832
|
+
if (!spec.startsWith(".")) return false;
|
|
833
|
+
const trimmed = spec.split(/[?#]/, 1)[0];
|
|
834
|
+
return /\.(?:m?[jt]sx?|c[jt]s|vue)$/.test(trimmed) || !/\.[^/.]+$/.test(trimmed);
|
|
835
|
+
}
|
|
836
|
+
function externalPackageSpecifier(spec) {
|
|
837
|
+
return /^(?:node:[\w/.-]+|(?:@[a-z0-9~][\w.~-]*\/)?[a-z0-9~][\w.~-]*(?:\/.*)?)$/.test(spec);
|
|
838
|
+
}
|
|
839
|
+
function importEdges(files) {
|
|
840
|
+
const known = new Set(files.map((file) => file.path));
|
|
841
|
+
const edges = /* @__PURE__ */ new Map();
|
|
842
|
+
for (const file of files) {
|
|
843
|
+
if (extname(file.path) === ".html") continue;
|
|
844
|
+
const targets = [];
|
|
845
|
+
for (const record of sourceImports(file)) {
|
|
846
|
+
if (record.typeOnly) continue;
|
|
847
|
+
const resolved = resolveImport(dirname(file.path), record.specifier, known);
|
|
848
|
+
if (resolved !== void 0) targets.push(resolved);
|
|
849
|
+
}
|
|
850
|
+
edges.set(file.path, targets);
|
|
851
|
+
}
|
|
852
|
+
return edges;
|
|
853
|
+
}
|
|
854
|
+
function reachableFrom(starts, edges) {
|
|
855
|
+
const reached = new Set(starts);
|
|
856
|
+
const pending = [...reached];
|
|
857
|
+
while (pending.length > 0) {
|
|
858
|
+
const current = pending.pop();
|
|
859
|
+
if (current === void 0) break;
|
|
860
|
+
for (const target of edges.get(current) ?? []) if (!reached.has(target)) {
|
|
861
|
+
reached.add(target);
|
|
862
|
+
pending.push(target);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
return reached;
|
|
866
|
+
}
|
|
867
|
+
function exposedFiles(packageRoot, packageJson, files, inboundSeeds) {
|
|
868
|
+
const known = new Set(files.map((file) => file.path));
|
|
869
|
+
const specs = [];
|
|
870
|
+
const collect = (value) => {
|
|
871
|
+
if (typeof value === "string") {
|
|
872
|
+
if (value.includes("*")) return "all";
|
|
873
|
+
specs.push(value);
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
if (value !== null && typeof value === "object") for (const [key, child] of Object.entries(value)) {
|
|
877
|
+
if (key.includes("*")) return "all";
|
|
878
|
+
if (collect(child) === "all") return "all";
|
|
879
|
+
}
|
|
880
|
+
};
|
|
881
|
+
for (const field of [
|
|
882
|
+
"main",
|
|
883
|
+
"module",
|
|
884
|
+
"exports",
|
|
885
|
+
"browser"
|
|
886
|
+
]) if (collect(packageJson[field]) === "all") return "all";
|
|
887
|
+
if (packageJson.exports === void 0 && packageJson.private !== true) return "all";
|
|
888
|
+
const declared = specs.length > 0;
|
|
889
|
+
const entryFileSeeds = [...inboundSeeds];
|
|
890
|
+
if (!declared) specs.push("./index");
|
|
891
|
+
const entryFiles = [...entryFileSeeds];
|
|
892
|
+
for (const spec of specs) {
|
|
893
|
+
const resolved = resolveImport(packageRoot, spec.startsWith(".") ? spec : `./${spec}`, known);
|
|
894
|
+
if (resolved === void 0) {
|
|
895
|
+
if (declared) return "all";
|
|
896
|
+
continue;
|
|
897
|
+
}
|
|
898
|
+
entryFiles.push(resolved);
|
|
899
|
+
}
|
|
900
|
+
const byPath = new Map(files.map((file) => [file.path, file]));
|
|
901
|
+
const exposed = new Set(entryFiles);
|
|
902
|
+
const pending = [...entryFiles];
|
|
903
|
+
while (pending.length > 0) {
|
|
904
|
+
const current = pending.pop();
|
|
905
|
+
if (current === void 0) break;
|
|
906
|
+
const file = byPath.get(current);
|
|
907
|
+
if (file === void 0 || extname(file.path) === ".html") continue;
|
|
908
|
+
for (const record of sourceImports(file)) {
|
|
909
|
+
if (record.typeOnly) continue;
|
|
910
|
+
const resolved = resolveImport(dirname(file.path), record.specifier, known);
|
|
911
|
+
if (resolved === void 0) {
|
|
912
|
+
if (scriptLikeSpecifier(record.specifier) || !record.specifier.startsWith(".") && !externalPackageSpecifier(record.specifier)) return "all";
|
|
913
|
+
continue;
|
|
914
|
+
}
|
|
915
|
+
if (!exposed.has(resolved)) {
|
|
916
|
+
exposed.add(resolved);
|
|
917
|
+
pending.push(resolved);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
return exposed;
|
|
922
|
+
}
|
|
923
|
+
function proveSharedEntry(options) {
|
|
924
|
+
const scan = scanProof(options);
|
|
925
|
+
if (scan === null) return null;
|
|
926
|
+
const ownedSources = options.packageSources.filter((file) => options.owned(file.path));
|
|
927
|
+
const loaders = ownedSources.filter((file) => options.writable(file.path)).filter((file) => (file.htmlStylesheets ?? []).some((context) => context.cssPath === options.entry) || htmlLinksEntry(file, options.entry) || extname(file.path) !== ".html" && importsEntry(file, options.entry)).map((file) => file.path);
|
|
928
|
+
if (loaders.length === 0) return null;
|
|
929
|
+
const reachable = reachableFrom(loaders, importEdges(ownedSources));
|
|
930
|
+
const ownedPaths = new Set(ownedSources.map((file) => file.path));
|
|
931
|
+
const packageName = typeof options.packageJson.name === "string" ? options.packageJson.name : void 0;
|
|
932
|
+
const resolveInbound = (fromDir, spec) => {
|
|
933
|
+
const relative = resolveImport(fromDir, spec, ownedPaths);
|
|
934
|
+
if (relative !== void 0) return relative;
|
|
935
|
+
if (packageName !== void 0 && spec.startsWith(`${packageName}/`)) return resolveImport(options.packageRoot, `./${spec.slice(packageName.length + 1)}`, ownedPaths);
|
|
936
|
+
};
|
|
937
|
+
const inboundSeeds = [];
|
|
938
|
+
let foreignUnverifiable = false;
|
|
939
|
+
for (const file of options.packageSources) {
|
|
940
|
+
if (options.owned(file.path) || extname(file.path) === ".html") continue;
|
|
941
|
+
if (importsEntry(file, options.entry)) continue;
|
|
942
|
+
const records = sourceImports(file);
|
|
943
|
+
if (records.includes(OPAQUE_SOURCE_IMPORT)) {
|
|
944
|
+
foreignUnverifiable = true;
|
|
945
|
+
continue;
|
|
946
|
+
}
|
|
947
|
+
for (const record of records) {
|
|
948
|
+
if (record.typeOnly) continue;
|
|
949
|
+
const resolved = resolveInbound(dirname(file.path), record.specifier);
|
|
950
|
+
if (resolved !== void 0) inboundSeeds.push(resolved);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
const exposed = foreignUnverifiable ? "all" : exposedFiles(options.packageRoot, options.packageJson, ownedSources, inboundSeeds);
|
|
954
|
+
const provenConsumer = (consumer) => options.owned(consumer.path) && !sourceImports(consumer).includes(OPAQUE_SOURCE_IMPORT) && reachable.has(consumer.path) && (exposed === "all" ? false : !exposed.has(consumer.path)) && (scan === "literal" || !options.ignoredPaths.has(consumer.path));
|
|
955
|
+
return {
|
|
956
|
+
provenConsumer,
|
|
957
|
+
provenStyle: (consumers) => consumers.length > 0 && consumers.every(provenConsumer)
|
|
958
|
+
};
|
|
630
959
|
}
|
|
631
|
-
|
|
632
|
-
|
|
960
|
+
function htmlLinksEntry(file, entry) {
|
|
961
|
+
if (extname(file.path) !== ".html") return false;
|
|
633
962
|
try {
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
963
|
+
const parsed = parseHtmlSource(file.path, file.source);
|
|
964
|
+
if (parsed.bases.length > 0) return false;
|
|
965
|
+
return parsed.links.some((link) => {
|
|
966
|
+
const media = link.media.trim().toLowerCase();
|
|
967
|
+
if (media !== "" && media !== "all") return false;
|
|
968
|
+
const href = link.href.split(/[?#]/, 1)[0];
|
|
969
|
+
if (!href || /^[a-z][a-z0-9+.-]*:|^\/\//i.test(href)) return false;
|
|
970
|
+
return resolve(dirname(file.path), href) === entry;
|
|
971
|
+
});
|
|
972
|
+
} catch {
|
|
973
|
+
return false;
|
|
638
974
|
}
|
|
639
|
-
return [...new Set(roots)].sort();
|
|
640
|
-
}
|
|
641
|
-
function hasIgnoredDirectory(root, path) {
|
|
642
|
-
return relative(root, path).split(/[\\/]/).some((part) => IGNORED_DIRECTORIES.has(part));
|
|
643
975
|
}
|
|
644
|
-
function
|
|
645
|
-
return
|
|
976
|
+
function importsStylesheet(file, stylePath) {
|
|
977
|
+
return sourceImports(file).some((record) => record === OPAQUE_SOURCE_IMPORT && file.source.includes(basename(stylePath)) || !record.typeOnly && record.specifier.startsWith(".") && resolve(dirname(file.path), record.specifier) === stylePath);
|
|
646
978
|
}
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
async function visit(directory) {
|
|
650
|
-
const entries = await readdir(directory, { withFileTypes: true });
|
|
651
|
-
for (const entry of entries) {
|
|
652
|
-
const path = join(directory, entry.name);
|
|
653
|
-
if (entry.isDirectory()) {
|
|
654
|
-
if (!IGNORED_DIRECTORIES.has(entry.name)) await visit(path);
|
|
655
|
-
} else if (entry.isFile() && include(path)) files.push(path);
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
await visit(root);
|
|
659
|
-
return files;
|
|
979
|
+
function importsEntry(file, entry) {
|
|
980
|
+
return sourceImports(file).some((record) => !record.typeOnly && !record.dynamic && record.specifier.startsWith(".") && resolve(dirname(file.path), record.specifier) === entry);
|
|
660
981
|
}
|
|
661
982
|
//#endregion
|
|
662
983
|
//#region src/plan/html.ts
|
|
@@ -783,7 +1104,7 @@ async function collectHtmlStyleContexts(state) {
|
|
|
783
1104
|
analyzable: true
|
|
784
1105
|
});
|
|
785
1106
|
if (extname(state.path) !== ".css") return;
|
|
786
|
-
for (const imported of cssImports(source)) {
|
|
1107
|
+
for (const imported of cssImports(state.path, source)) {
|
|
787
1108
|
const variants = mediaVariants(imported.media);
|
|
788
1109
|
if (variants === void 0) {
|
|
789
1110
|
state.warnings.push(htmlWarning("unsupported-link-media", state.path, imported.start, imported.end, `The stylesheet import media condition ${JSON.stringify(imported.media)} cannot be represented safely.`));
|
|
@@ -806,7 +1127,7 @@ function inferredPreprocessorPath(state) {
|
|
|
806
1127
|
return matches.length === 1 ? matches[0] : void 0;
|
|
807
1128
|
}
|
|
808
1129
|
function addInferredPreprocessorContext(state) {
|
|
809
|
-
if (state.styleSources.has(state.path) && state.sourceFiles.some((file) => extname(file.path) !== ".html" &&
|
|
1130
|
+
if (state.styleSources.has(state.path) && state.sourceFiles.some((file) => extname(file.path) !== ".html" && importsStylesheet(file, state.path))) return false;
|
|
810
1131
|
const path = inferredPreprocessorPath(state);
|
|
811
1132
|
if (!path) return false;
|
|
812
1133
|
state.generatedPaths.add(state.path);
|
|
@@ -857,22 +1178,14 @@ function htmlWarning(code, file, start, end, message) {
|
|
|
857
1178
|
}
|
|
858
1179
|
//#endregion
|
|
859
1180
|
//#region src/tailwind.ts
|
|
860
|
-
function
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
});
|
|
866
|
-
if (configuredPath) return {
|
|
867
|
-
path: configuredPath,
|
|
868
|
-
entries
|
|
869
|
-
};
|
|
1181
|
+
function findTailwindEntries(stylePaths, styleSources) {
|
|
1182
|
+
return stylePaths.filter((path) => extname(path) === ".css" && importSpecifiers(styleSources.get(path) ?? "").some((specifier) => specifier === "tailwindcss" || specifier.startsWith("tailwindcss/")));
|
|
1183
|
+
}
|
|
1184
|
+
function selectTailwindEntry(entries, configuredPath) {
|
|
1185
|
+
if (configuredPath) return configuredPath;
|
|
870
1186
|
if (entries.length === 0) throw new Error("No Tailwind v4 CSS entry was found. Pass --tailwind-css.");
|
|
871
1187
|
if (entries.length > 1) throw new Error("Multiple Tailwind CSS entries were found. Pass --tailwind-css.");
|
|
872
|
-
return
|
|
873
|
-
path: entries[0],
|
|
874
|
-
entries
|
|
875
|
-
};
|
|
1188
|
+
return entries[0];
|
|
876
1189
|
}
|
|
877
1190
|
async function loadTailwind(packageRoot, tailwindCss, snapshots, workspaceRoot) {
|
|
878
1191
|
const projectRequire = createRequire(join(packageRoot, "package.json"));
|
|
@@ -911,45 +1224,265 @@ async function loadTailwind(packageRoot, tailwindCss, snapshots, workspaceRoot)
|
|
|
911
1224
|
base: dirname(path)
|
|
912
1225
|
};
|
|
913
1226
|
};
|
|
1227
|
+
const defaultTheme = await readFile(join(dirname(packagePath), "theme.css"), "utf8");
|
|
1228
|
+
const graphSources = [];
|
|
914
1229
|
const themeTokens = {
|
|
915
|
-
...extractThemeTokens(
|
|
916
|
-
...await extractThemeTokensFromGraph(css, base, loadStylesheet)
|
|
1230
|
+
...extractThemeTokens("tailwind-default.theme.css", defaultTheme),
|
|
1231
|
+
...await extractThemeTokensFromGraph(tailwindCss, css, base, loadStylesheet, graphSources)
|
|
917
1232
|
};
|
|
1233
|
+
graphSources.push({
|
|
1234
|
+
path: tailwindCss,
|
|
1235
|
+
source: css
|
|
1236
|
+
});
|
|
1237
|
+
const designSystem = await loadDesignSystem(css, {
|
|
1238
|
+
base,
|
|
1239
|
+
loadModule,
|
|
1240
|
+
loadStylesheet
|
|
1241
|
+
});
|
|
1242
|
+
const loadWith = (replacement) => loadDesignSystem(replacement, {
|
|
1243
|
+
base,
|
|
1244
|
+
loadModule,
|
|
1245
|
+
loadStylesheet
|
|
1246
|
+
});
|
|
918
1247
|
return {
|
|
919
|
-
designSystem
|
|
920
|
-
base,
|
|
921
|
-
loadModule,
|
|
922
|
-
loadStylesheet
|
|
923
|
-
}),
|
|
1248
|
+
designSystem,
|
|
924
1249
|
css,
|
|
925
1250
|
path: tailwindCss,
|
|
926
|
-
themeTokens
|
|
1251
|
+
themeTokens,
|
|
1252
|
+
graphSources,
|
|
1253
|
+
loadWith
|
|
927
1254
|
};
|
|
928
1255
|
}
|
|
929
|
-
function extractThemeTokens(css) {
|
|
930
|
-
|
|
931
|
-
for (const block of css.matchAll(/@theme[^{]*\{([^}]*)\}/gs)) for (const match of block[1].matchAll(/--([\w-]+):\s*([^;{}]+);/g)) tokens[match[1]] = match[2].trim();
|
|
932
|
-
return tokens;
|
|
1256
|
+
function extractThemeTokens(key, css) {
|
|
1257
|
+
return stylesheetAnalysis(key, css).themeTokens;
|
|
933
1258
|
}
|
|
934
|
-
|
|
1259
|
+
function importSpecifiers(css) {
|
|
1260
|
+
return (cssDirectives$1(css) ?? []).flatMap((directive) => directive !== null && typeof directive === "object" && "kind" in directive && directive.kind === "import" && "specifier" in directive && typeof directive.specifier === "string" ? [directive.specifier] : []);
|
|
1261
|
+
}
|
|
1262
|
+
async function extractThemeTokensFromGraph(identity, css, base, loadStylesheet, graphSources, seen = /* @__PURE__ */ new Set()) {
|
|
935
1263
|
const tokens = {};
|
|
936
|
-
for (const
|
|
937
|
-
const key = `${base}\0${
|
|
1264
|
+
for (const spec of importSpecifiers(css)) {
|
|
1265
|
+
const key = `${base}\0${spec}`;
|
|
938
1266
|
if (seen.has(key)) continue;
|
|
939
1267
|
seen.add(key);
|
|
940
|
-
const loaded = await loadStylesheet(
|
|
941
|
-
|
|
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));
|
|
942
1274
|
}
|
|
943
|
-
return Object.assign(tokens, extractThemeTokens(css));
|
|
1275
|
+
return Object.assign(tokens, extractThemeTokens(`${identity}.theme.css`, css));
|
|
944
1276
|
}
|
|
945
|
-
function invalidCandidates(
|
|
946
|
-
const generated =
|
|
1277
|
+
function invalidCandidates(system, candidates) {
|
|
1278
|
+
const generated = system.candidatesToCss(candidates);
|
|
947
1279
|
return candidates.filter((_, index) => generated[index] === null);
|
|
948
1280
|
}
|
|
949
1281
|
//#endregion
|
|
1282
|
+
//#region src/plan/media.ts
|
|
1283
|
+
const GENERATED_NAMESPACE = "twm-media";
|
|
1284
|
+
/**
|
|
1285
|
+
* Resolve one fixed name per key across a complete entry group.
|
|
1286
|
+
*
|
|
1287
|
+
* Resolution follows the RFC order: a built-in candidate with a verified
|
|
1288
|
+
* effective expansion, an existing breakpoint matched by exact value and
|
|
1289
|
+
* verified the same way, an adopted definition whose name, normalized
|
|
1290
|
+
* meaning, and effective expansion equal what the migration would emit,
|
|
1291
|
+
* the readable generated name, the digest name, and finally the
|
|
1292
|
+
* arbitrary-variant fallback. Reservation is ownership-blind:
|
|
1293
|
+
* authored names with any different meaning, active theme names, explicit
|
|
1294
|
+
* reserved names such as inert scanned candidates, and any name the design
|
|
1295
|
+
* system resolves all block a generated name.
|
|
1296
|
+
*/
|
|
1297
|
+
function resolveMediaNames(collections, themeTokens, probes, reservedNames = []) {
|
|
1298
|
+
const components = mergeComponents(collections);
|
|
1299
|
+
const authoredKeysByName = mergeAuthoredVariants(collections);
|
|
1300
|
+
const reserved = new Set(reservedNames);
|
|
1301
|
+
for (const token of Object.keys(themeTokens)) if (token.startsWith("breakpoint-")) reserved.add(token.slice(11));
|
|
1302
|
+
const claimed = /* @__PURE__ */ new Set();
|
|
1303
|
+
const adoptableKey = (name) => {
|
|
1304
|
+
const keys = authoredKeysByName.get(name);
|
|
1305
|
+
if (keys === void 0 || keys.size !== 1) return null;
|
|
1306
|
+
const [key] = keys;
|
|
1307
|
+
return key ?? null;
|
|
1308
|
+
};
|
|
1309
|
+
const taken = (name) => claimed.has(name) || reserved.has(name) || authoredKeysByName.has(name) || probes.resolves(name);
|
|
1310
|
+
const names = /* @__PURE__ */ new Map();
|
|
1311
|
+
const fallbacks = /* @__PURE__ */ new Set();
|
|
1312
|
+
for (const component of components) {
|
|
1313
|
+
const resolved = (name, kind) => {
|
|
1314
|
+
claimed.add(name);
|
|
1315
|
+
names.set(component.key, {
|
|
1316
|
+
name,
|
|
1317
|
+
kind
|
|
1318
|
+
});
|
|
1319
|
+
};
|
|
1320
|
+
if (component.builtin !== null && probes.expansionMatches(component.builtin, component.key)) {
|
|
1321
|
+
resolved(component.builtin, "builtin");
|
|
1322
|
+
continue;
|
|
1323
|
+
}
|
|
1324
|
+
if (component.breakpoint !== null && probes.expansionMatches(component.breakpoint, component.key)) {
|
|
1325
|
+
resolved(component.breakpoint, "breakpoint");
|
|
1326
|
+
continue;
|
|
1327
|
+
}
|
|
1328
|
+
const digestName = `${GENERATED_NAMESPACE}-${component.digest}`;
|
|
1329
|
+
const candidates = component.readableName === null ? [digestName] : [component.readableName, digestName];
|
|
1330
|
+
let done = false;
|
|
1331
|
+
for (const candidate of candidates) {
|
|
1332
|
+
if (adoptableKey(candidate) === component.key && !claimed.has(candidate) && probes.expansionMatches(candidate, component.key)) {
|
|
1333
|
+
resolved(candidate, "adopted");
|
|
1334
|
+
done = true;
|
|
1335
|
+
break;
|
|
1336
|
+
}
|
|
1337
|
+
if (!taken(candidate)) {
|
|
1338
|
+
resolved(candidate, "generated");
|
|
1339
|
+
done = true;
|
|
1340
|
+
break;
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
if (!done) fallbacks.add(component.key);
|
|
1344
|
+
}
|
|
1345
|
+
return {
|
|
1346
|
+
names,
|
|
1347
|
+
fallbacks
|
|
1348
|
+
};
|
|
1349
|
+
}
|
|
1350
|
+
function mergeComponents(collections) {
|
|
1351
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
1352
|
+
for (const collection of collections) for (const component of collection.components) if (!byKey.has(component.key)) byKey.set(component.key, component);
|
|
1353
|
+
return [...byKey.values()].sort((left, right) => left.key < right.key ? -1 : left.key > right.key ? 1 : 0);
|
|
1354
|
+
}
|
|
1355
|
+
function mergeAuthoredVariants(collections) {
|
|
1356
|
+
const byName = /* @__PURE__ */ new Map();
|
|
1357
|
+
for (const collection of collections) for (const variant of collection.authoredVariants) {
|
|
1358
|
+
const keys = byName.get(variant.name) ?? /* @__PURE__ */ new Set();
|
|
1359
|
+
keys.add(variant.mediaQueryKey);
|
|
1360
|
+
byName.set(variant.name, keys);
|
|
1361
|
+
}
|
|
1362
|
+
return byName;
|
|
1363
|
+
}
|
|
1364
|
+
const PROBE_UTILITY = "[--tw-probe:1]";
|
|
1365
|
+
function probeCandidate(prefix, name) {
|
|
1366
|
+
return `${prefix === null || prefix === "" ? "" : `${prefix}:`}${name}:${PROBE_UTILITY}`;
|
|
1367
|
+
}
|
|
1368
|
+
/**
|
|
1369
|
+
* Build the effective-expansion probes against the unaugmented loaded
|
|
1370
|
+
* design system. `resolves` proves only existence; `expansionMatches`
|
|
1371
|
+
* proves that compiling the name yields exactly one `@media` wrapper whose
|
|
1372
|
+
* normalized query equals the key.
|
|
1373
|
+
*/
|
|
1374
|
+
function buildMediaProbes(tailwind) {
|
|
1375
|
+
const compiled = /* @__PURE__ */ new Map();
|
|
1376
|
+
const prefix = tailwind.designSystem.theme.prefix;
|
|
1377
|
+
const compile = (name) => {
|
|
1378
|
+
let css = compiled.get(name);
|
|
1379
|
+
if (css === void 0) {
|
|
1380
|
+
[css = null] = tailwind.designSystem.candidatesToCss([probeCandidate(prefix, name)]);
|
|
1381
|
+
compiled.set(name, css);
|
|
1382
|
+
}
|
|
1383
|
+
return css;
|
|
1384
|
+
};
|
|
1385
|
+
return {
|
|
1386
|
+
resolves: (name) => compile(name) !== null,
|
|
1387
|
+
expansionMatches: (name, key) => {
|
|
1388
|
+
const css = compile(name);
|
|
1389
|
+
if (css === null) return false;
|
|
1390
|
+
return JSON.parse(mediaProbeKey(css)) === key;
|
|
1391
|
+
}
|
|
1392
|
+
};
|
|
1393
|
+
}
|
|
1394
|
+
/**
|
|
1395
|
+
* Conservative scanned-corpus reservation: every colon-chained token in the
|
|
1396
|
+
* package's scanned sources reserves its variant segments, so an inert
|
|
1397
|
+
* candidate such as `width-lte-768px:hidden` is never activated with a new
|
|
1398
|
+
* meaning. Over-reservation only pushes a generated name to its digest
|
|
1399
|
+
* form, and content-identity adoption bypasses these reservations, so
|
|
1400
|
+
* reruns stay idempotent.
|
|
1401
|
+
*/
|
|
1402
|
+
function scannedVariantReservations(sources) {
|
|
1403
|
+
const reserved = /* @__PURE__ */ new Set();
|
|
1404
|
+
for (const source of sources) for (const match of source.matchAll(/[A-Za-z][\w-]*(?::[^\s"'`{}]+)+/g)) for (const segment of match[0].split(":").slice(0, -1)) reserved.add(segment);
|
|
1405
|
+
return reserved;
|
|
1406
|
+
}
|
|
1407
|
+
/**
|
|
1408
|
+
* Run native condition collection for one member package of an entry
|
|
1409
|
+
* group. Kept separate from resolution so a recoverable collection
|
|
1410
|
+
* failure is attributable to its member.
|
|
1411
|
+
*/
|
|
1412
|
+
function collectMemberMediaConditions(stylesheets, tailwind) {
|
|
1413
|
+
return JSON.parse(collectMediaConditions(JSON.stringify({
|
|
1414
|
+
stylesheets,
|
|
1415
|
+
themeTokens: tailwind.themeTokens,
|
|
1416
|
+
tailwindSources: tailwind.graphSources
|
|
1417
|
+
})));
|
|
1418
|
+
}
|
|
1419
|
+
/**
|
|
1420
|
+
* Resolve the fixed map the planner consumes plus the definitions
|
|
1421
|
+
* extraction may emit from the group's member collections. Name
|
|
1422
|
+
* allocation runs once for the complete group, so different keys from
|
|
1423
|
+
* different packages can never independently claim one name.
|
|
1424
|
+
*/
|
|
1425
|
+
function planMediaExtraction(options) {
|
|
1426
|
+
const resolution = resolveMediaNames(options.collections, options.tailwind.themeTokens, buildMediaProbes(options.tailwind), scannedVariantReservations([...options.scannedSources, ...options.tailwind.graphSources.map((graphSource) => graphSource.source)]));
|
|
1427
|
+
const names = {};
|
|
1428
|
+
const generated = [];
|
|
1429
|
+
for (const [key, resolved] of resolution.names) {
|
|
1430
|
+
names[key] = resolved.name;
|
|
1431
|
+
if (resolved.kind === "generated") generated.push({
|
|
1432
|
+
key,
|
|
1433
|
+
name: resolved.name
|
|
1434
|
+
});
|
|
1435
|
+
}
|
|
1436
|
+
return {
|
|
1437
|
+
names,
|
|
1438
|
+
generated
|
|
1439
|
+
};
|
|
1440
|
+
}
|
|
1441
|
+
/**
|
|
1442
|
+
* The generated definitions the final plan actually uses, ordered by the
|
|
1443
|
+
* first applied rule that uses each name. A rule counts as applied only
|
|
1444
|
+
* when every one of its candidates survived into `plan.candidates`: a
|
|
1445
|
+
* retained global rule still applies its utilities to proven consumers,
|
|
1446
|
+
* while a blocked rule always contains a dropped candidate, so its
|
|
1447
|
+
* occurrences must not fix registration order for a candidate another rule
|
|
1448
|
+
* still applies. Registration order controls conflicting utility
|
|
1449
|
+
* precedence, so the order follows the applied rules rather than every
|
|
1450
|
+
* encountered condition.
|
|
1451
|
+
*/
|
|
1452
|
+
function usedGeneratedDefinitions(extraction, plan) {
|
|
1453
|
+
const applied = new Set(plan.candidates);
|
|
1454
|
+
const byName = new Map(extraction.generated.map((definition) => [definition.name, definition]));
|
|
1455
|
+
const ordered = [];
|
|
1456
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1457
|
+
for (const rule of plan.rules) {
|
|
1458
|
+
if (!rule.candidates.every((candidate) => applied.has(candidate))) continue;
|
|
1459
|
+
for (const candidate of rule.candidates) for (const segment of candidate.split(":").slice(0, -1)) {
|
|
1460
|
+
const definition = byName.get(segment);
|
|
1461
|
+
if (definition !== void 0 && !seen.has(segment)) {
|
|
1462
|
+
seen.add(segment);
|
|
1463
|
+
ordered.push(definition);
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
return ordered;
|
|
1468
|
+
}
|
|
1469
|
+
/**
|
|
1470
|
+
* Append the used definitions to the entry source, each as its own block,
|
|
1471
|
+
* matching the planner's moved-block formatting. Definitions already
|
|
1472
|
+
* present are recognized through adoption before this runs, so appending
|
|
1473
|
+
* never duplicates one.
|
|
1474
|
+
*/
|
|
1475
|
+
function appendMediaDefinitions(source, definitions) {
|
|
1476
|
+
let output = source;
|
|
1477
|
+
for (const { key, name } of definitions) {
|
|
1478
|
+
if (!output.endsWith("\n")) output += "\n";
|
|
1479
|
+
if (!output.endsWith("\n\n")) output += "\n";
|
|
1480
|
+
output += `@custom-variant ${name} {\n @media ${key} {\n @slot;\n }\n}\n`;
|
|
1481
|
+
}
|
|
1482
|
+
return output;
|
|
1483
|
+
}
|
|
1484
|
+
//#endregion
|
|
950
1485
|
//#region src/parser/vue.ts
|
|
951
|
-
const ESCAPE_SELECTOR = /(?:::v-|:)(?:deep|global|slotted)\(([^)]*)\)/g;
|
|
952
|
-
const ESCAPE_RESIDUE = /(?:>>>|\/deep\/|::v-deep|:deep|::v-slotted|:slotted|::v-global|:global)/;
|
|
953
1486
|
const SUPPORTED_STYLE_ATTRIBUTES = /* @__PURE__ */ new Set([
|
|
954
1487
|
"lang",
|
|
955
1488
|
"module",
|
|
@@ -1053,6 +1586,7 @@ function analyzeVueSource(compiler, path, source) {
|
|
|
1053
1586
|
const shadowPreprocessorTexts = [];
|
|
1054
1587
|
const unscopedShadowPreprocessorTexts = [];
|
|
1055
1588
|
let escapeUnverifiable = false;
|
|
1589
|
+
let opaqueStyleBlocks = false;
|
|
1056
1590
|
let moduleSiblingUnsupported = false;
|
|
1057
1591
|
for (const style of descriptor.styles) {
|
|
1058
1592
|
const start = style.loc.start.offset;
|
|
@@ -1063,6 +1597,7 @@ function analyzeVueSource(compiler, path, source) {
|
|
|
1063
1597
|
warn("unsupported-sfc-block", start, end, `Unsupported <style> attributes: ${unsupportedAttributes.join(", ")}.`);
|
|
1064
1598
|
if (usesDefaultModuleBinding) moduleSiblingUnsupported = true;
|
|
1065
1599
|
escapeUnverifiable = true;
|
|
1600
|
+
opaqueStyleBlocks = true;
|
|
1066
1601
|
continue;
|
|
1067
1602
|
}
|
|
1068
1603
|
if (style.src !== void 0) {
|
|
@@ -1070,6 +1605,7 @@ function analyzeVueSource(compiler, path, source) {
|
|
|
1070
1605
|
warn("unsupported-sfc-block", start, end, "A <style module src> block is not supported.");
|
|
1071
1606
|
if (usesDefaultModuleBinding) moduleSiblingUnsupported = true;
|
|
1072
1607
|
escapeUnverifiable = true;
|
|
1608
|
+
opaqueStyleBlocks = true;
|
|
1073
1609
|
} else styleBlockImports.push({
|
|
1074
1610
|
reference: style.src,
|
|
1075
1611
|
start,
|
|
@@ -1081,12 +1617,19 @@ function analyzeVueSource(compiler, path, source) {
|
|
|
1081
1617
|
if (usesDefaultModuleBinding) moduleSiblingUnsupported = true;
|
|
1082
1618
|
warn("unsupported-sfc-block", start, end, "Named <style module> blocks are not supported.");
|
|
1083
1619
|
shadowModuleCssTexts.push(style.content);
|
|
1620
|
+
opaqueStyleBlocks = true;
|
|
1084
1621
|
continue;
|
|
1085
1622
|
}
|
|
1086
1623
|
if (!SUPPORTED_STYLE_LANGUAGES.has(style.lang)) {
|
|
1087
1624
|
if (usesDefaultModuleBinding) moduleSiblingUnsupported = true;
|
|
1088
1625
|
warn("preprocessor-style-block", start, end, `The <style lang="${style.lang}"> language is not supported.`);
|
|
1089
1626
|
shadowPreprocessorTexts.push(style.content);
|
|
1627
|
+
opaqueStyleBlocks = true;
|
|
1628
|
+
try {
|
|
1629
|
+
if (stylesheetAnalysis(`${path}.css`, style.content).selectorsUnverifiable) escapeUnverifiable = true;
|
|
1630
|
+
} catch {
|
|
1631
|
+
escapeUnverifiable = true;
|
|
1632
|
+
}
|
|
1090
1633
|
continue;
|
|
1091
1634
|
}
|
|
1092
1635
|
const outerStart = source.lastIndexOf("<style", start);
|
|
@@ -1094,6 +1637,7 @@ function analyzeVueSource(compiler, path, source) {
|
|
|
1094
1637
|
if (outerStart < 0 || !closing) {
|
|
1095
1638
|
warn("unsupported-sfc-block", start, end, "The style block tags could not be located.");
|
|
1096
1639
|
escapeUnverifiable = true;
|
|
1640
|
+
opaqueStyleBlocks = true;
|
|
1097
1641
|
continue;
|
|
1098
1642
|
}
|
|
1099
1643
|
const block = {
|
|
@@ -1110,24 +1654,37 @@ function analyzeVueSource(compiler, path, source) {
|
|
|
1110
1654
|
}
|
|
1111
1655
|
if (!style.scoped) {
|
|
1112
1656
|
unscopedBlocks.push(block);
|
|
1113
|
-
if (style.lang && style.lang !== "css")
|
|
1114
|
-
|
|
1657
|
+
if (style.lang && style.lang !== "css") {
|
|
1658
|
+
unscopedShadowPreprocessorTexts.push(style.content);
|
|
1659
|
+
try {
|
|
1660
|
+
if (stylesheetAnalysis(`${path}.${style.lang}`, style.content).selectorsUnverifiable) escapeUnverifiable = true;
|
|
1661
|
+
} catch {
|
|
1662
|
+
escapeUnverifiable = true;
|
|
1663
|
+
}
|
|
1664
|
+
} else unscopedShadowCssTexts.push(style.content);
|
|
1115
1665
|
continue;
|
|
1116
1666
|
}
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1667
|
+
try {
|
|
1668
|
+
const escapes = stylesheetAnalysis(`${path}.${style.lang ?? "css"}`, style.content);
|
|
1669
|
+
shadowCssTexts.push(...escapes.scopeEscapes);
|
|
1670
|
+
if (escapes.scopeEscapes.length > 0) block.shadowSource = escapes.scopeShadowCss.join("\n") || "/* scope escapes extracted */";
|
|
1671
|
+
if (escapes.scopeEscapesUnverifiable || escapes.selectorsUnverifiable) escapeUnverifiable = true;
|
|
1672
|
+
} catch {
|
|
1673
|
+
escapeUnverifiable = true;
|
|
1120
1674
|
}
|
|
1121
|
-
if (ESCAPE_RESIDUE.test(style.content.replace(ESCAPE_SELECTOR, " "))) escapeUnverifiable = true;
|
|
1122
1675
|
blocks.push(block);
|
|
1123
1676
|
}
|
|
1677
|
+
const scriptLang = descriptor.scriptSetup?.lang ?? descriptor.script?.lang;
|
|
1124
1678
|
const state = {
|
|
1125
1679
|
elements: [],
|
|
1126
1680
|
components: [],
|
|
1127
1681
|
dynamic: false,
|
|
1128
1682
|
vHtml: false,
|
|
1129
1683
|
hasSlot: false,
|
|
1130
|
-
|
|
1684
|
+
moduleClosureBroken: false,
|
|
1685
|
+
referencesUseCssModule: false,
|
|
1686
|
+
expressionReferences: /* @__PURE__ */ new Set(),
|
|
1687
|
+
expressionPath: scriptLang === "ts" || scriptLang === "tsx" ? "Component.ts" : "Component.js"
|
|
1131
1688
|
};
|
|
1132
1689
|
visitTemplateNode(source, template.ast, state);
|
|
1133
1690
|
const alwaysRenderedRoots = template.ast.children.filter((node) => node.type === NODE_ELEMENT && (node.tagType === TAG_ELEMENT || node.tagType === TAG_COMPONENT) && !node.props?.some((prop) => prop.type === PROP_DIRECTIVE && [
|
|
@@ -1137,8 +1694,14 @@ function analyzeVueSource(compiler, path, source) {
|
|
|
1137
1694
|
"for"
|
|
1138
1695
|
].includes(prop.name))).length;
|
|
1139
1696
|
const scriptText = [descriptor.script?.content, descriptor.scriptSetup?.content].filter(Boolean).join("\n");
|
|
1140
|
-
const fallthroughUnverifiable = Boolean(descriptor.script) || descriptor.scriptSetup?.src !== void 0 || /\b(?:defineOptions|defineProps|inheritAttrs)\b/.test(descriptor.scriptSetup?.content ?? "");
|
|
1141
1697
|
let scriptImportsUnverifiable = false;
|
|
1698
|
+
const scriptImports = [];
|
|
1699
|
+
let setupAnalysis;
|
|
1700
|
+
let scriptUsesCssModule = false;
|
|
1701
|
+
let scriptHasDynamicImport = false;
|
|
1702
|
+
const scriptVueReferences = [];
|
|
1703
|
+
const scriptVueGlobPatterns = [];
|
|
1704
|
+
let scriptVueGlobUnverifiable = false;
|
|
1142
1705
|
const styleImports = [descriptor.script, descriptor.scriptSetup].flatMap((script) => {
|
|
1143
1706
|
if (!script) return [];
|
|
1144
1707
|
if (script.src || !SUPPORTED_SCRIPT_LANGUAGES.has(script.lang)) {
|
|
@@ -1146,18 +1709,22 @@ function analyzeVueSource(compiler, path, source) {
|
|
|
1146
1709
|
return [];
|
|
1147
1710
|
}
|
|
1148
1711
|
try {
|
|
1149
|
-
|
|
1712
|
+
const analysis = sourceAnalysis(`${path}.${script.lang ?? "js"}`, script.content);
|
|
1713
|
+
if (script === descriptor.scriptSetup) setupAnalysis = analysis;
|
|
1714
|
+
if (analysis.usesCssModule || analysis.hasUnboundUseCssModule) scriptUsesCssModule = true;
|
|
1715
|
+
if (analysis.hasDynamicImport) scriptHasDynamicImport = true;
|
|
1716
|
+
scriptImports.push(...analysis.imports);
|
|
1717
|
+
scriptVueReferences.push(...analysis.imports.filter((record) => !record.typeOnly).map((record) => record.specifier));
|
|
1718
|
+
scriptVueGlobPatterns.push(...analysis.vueGlobPatterns);
|
|
1719
|
+
if (analysis.vueGlobUnverifiable) scriptVueGlobUnverifiable = true;
|
|
1720
|
+
return analysis.staticImports;
|
|
1150
1721
|
} catch {
|
|
1151
1722
|
scriptImportsUnverifiable = true;
|
|
1152
1723
|
return [];
|
|
1153
1724
|
}
|
|
1154
1725
|
});
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
componentImports = staticImportBindings(`${path}.${descriptor.scriptSetup.lang ?? "js"}`, descriptor.scriptSetup.content);
|
|
1158
|
-
} catch {
|
|
1159
|
-
componentImports = [];
|
|
1160
|
-
}
|
|
1726
|
+
const fallthroughUnverifiable = Boolean(descriptor.script) || descriptor.scriptSetup?.src !== void 0 || Boolean(descriptor.scriptSetup) && !setupAnalysis || setupAnalysis?.hasVueFallthroughMacro === true;
|
|
1727
|
+
const componentImports = setupAnalysis?.defaultImports ?? [];
|
|
1161
1728
|
const offsets = utf8OffsetMap(source, [
|
|
1162
1729
|
...warnings.flatMap((warning) => [warning.start, warning.end]),
|
|
1163
1730
|
...styleRanges.flatMap((range) => [range.start, range.end]),
|
|
@@ -1209,7 +1776,7 @@ function analyzeVueSource(compiler, path, source) {
|
|
|
1209
1776
|
blocks: blocks.map(toByteBlock),
|
|
1210
1777
|
unscopedBlocks: unscopedBlocks.map(toByteBlock),
|
|
1211
1778
|
moduleBlocks: moduleBlocks.map(toByteBlock),
|
|
1212
|
-
moduleClosureBroken: moduleSiblingUnsupported || scriptImportsUnverifiable ||
|
|
1779
|
+
moduleClosureBroken: moduleSiblingUnsupported || scriptImportsUnverifiable || scriptUsesCssModule || state.moduleClosureBroken || state.referencesUseCssModule && setupAnalysis?.definesRootUseCssModule !== true || (setupAnalysis?.useCssModuleLocals ?? []).some((name) => state.expressionReferences.has(name)),
|
|
1213
1780
|
htmlElements: state.elements.map(toByteSite),
|
|
1214
1781
|
componentSites: state.components.map(toByteSite),
|
|
1215
1782
|
rootStarts: template.ast.children.filter((node) => node.type === NODE_ELEMENT).map((node) => offsets.get(node.loc.start.offset)),
|
|
@@ -1217,7 +1784,13 @@ function analyzeVueSource(compiler, path, source) {
|
|
|
1217
1784
|
rootFragment: template.ast.children.filter((node) => node.type === NODE_ELEMENT || node.type === NODE_INTERPOLATION || node.type === NODE_TEXT && node.content?.trim?.() !== "").length !== 1,
|
|
1218
1785
|
scriptText,
|
|
1219
1786
|
scriptStyleImports: [...new Set(styleImports)],
|
|
1787
|
+
scriptImports,
|
|
1220
1788
|
scriptImportsUnverifiable,
|
|
1789
|
+
scriptHasDynamicImport,
|
|
1790
|
+
scriptVueReferences,
|
|
1791
|
+
scriptVueGlobPatterns,
|
|
1792
|
+
scriptVueGlobUnverifiable,
|
|
1793
|
+
hasOpaqueStyleBlocks: opaqueStyleBlocks,
|
|
1221
1794
|
styleBlockImports: styleBlockImports.map((entry) => ({
|
|
1222
1795
|
...entry,
|
|
1223
1796
|
start: offset(entry.start),
|
|
@@ -1254,27 +1827,36 @@ function visitTemplateNode(source, node, state) {
|
|
|
1254
1827
|
let moduleBinding;
|
|
1255
1828
|
for (const prop of node.props ?? []) {
|
|
1256
1829
|
if (prop.type !== PROP_DIRECTIVE) continue;
|
|
1830
|
+
const expression = prop.exp?.content ? prop.name === "on" ? templateHandler(prop.exp.content) : templateExpression(state.expressionPath, prop.exp.content) : void 0;
|
|
1831
|
+
if (prop.exp?.content && !expression) state.moduleClosureBroken = true;
|
|
1257
1832
|
let provenModuleExpression = false;
|
|
1258
1833
|
if (prop.name === "bind") {
|
|
1259
1834
|
if (!prop.arg || !prop.arg.isStatic) classOpaque = true;
|
|
1260
1835
|
else if (prop.arg.content === "class") {
|
|
1261
|
-
const member = node.tagType === TAG_ELEMENT
|
|
1836
|
+
const member = node.tagType === TAG_ELEMENT ? expression?.vueModuleMember : void 0;
|
|
1262
1837
|
if (member && !moduleBinding) {
|
|
1263
1838
|
moduleBinding = {
|
|
1264
|
-
name: member
|
|
1839
|
+
name: member,
|
|
1265
1840
|
start: attributeRemovalStart(source, node, prop),
|
|
1266
1841
|
end: prop.loc.end.offset
|
|
1267
1842
|
};
|
|
1268
1843
|
provenModuleExpression = true;
|
|
1269
1844
|
} else {
|
|
1270
|
-
const value =
|
|
1845
|
+
const value = expression?.staticString ?? void 0;
|
|
1271
1846
|
if (value === void 0) classOpaque = true;
|
|
1272
1847
|
else bindingClasses.push(...value.split(/[\t\n\f\r ]+/).filter(Boolean));
|
|
1273
1848
|
}
|
|
1274
1849
|
} else if (prop.arg.content === "id") classOpaque = true;
|
|
1275
1850
|
}
|
|
1276
|
-
if (!provenModuleExpression &&
|
|
1277
|
-
if (
|
|
1851
|
+
if (!provenModuleExpression && expression?.usesCssModule) state.moduleClosureBroken = true;
|
|
1852
|
+
if (expression?.referencesUseCssModule) state.referencesUseCssModule = true;
|
|
1853
|
+
for (const name of expression?.references ?? []) state.expressionReferences.add(name);
|
|
1854
|
+
if (prop.arg && !prop.arg.isStatic && prop.arg.content) {
|
|
1855
|
+
const argExpression = templateExpression(state.expressionPath, prop.arg.content);
|
|
1856
|
+
state.moduleClosureBroken ||= argExpression?.usesCssModule ?? true;
|
|
1857
|
+
state.referencesUseCssModule ||= argExpression?.referencesUseCssModule ?? false;
|
|
1858
|
+
for (const name of argExpression?.references ?? []) state.expressionReferences.add(name);
|
|
1859
|
+
}
|
|
1278
1860
|
if (prop.name === "html") state.vHtml = true;
|
|
1279
1861
|
}
|
|
1280
1862
|
if (classOpaque) state.dynamic = true;
|
|
@@ -1288,7 +1870,12 @@ function visitTemplateNode(source, node, state) {
|
|
|
1288
1870
|
if (node.tagType === TAG_COMPONENT) state.components.push(element);
|
|
1289
1871
|
else if (node.tagType === TAG_ELEMENT) state.elements.push(element);
|
|
1290
1872
|
}
|
|
1291
|
-
if (node.type === NODE_INTERPOLATION && node.content?.content)
|
|
1873
|
+
if (node.type === NODE_INTERPOLATION && node.content?.content) {
|
|
1874
|
+
const interpolation = templateExpression(state.expressionPath, node.content.content);
|
|
1875
|
+
state.moduleClosureBroken ||= interpolation?.usesCssModule ?? true;
|
|
1876
|
+
state.referencesUseCssModule ||= interpolation?.referencesUseCssModule ?? false;
|
|
1877
|
+
for (const name of interpolation?.references ?? []) state.expressionReferences.add(name);
|
|
1878
|
+
}
|
|
1292
1879
|
for (const child of node.children ?? []) visitTemplateNode(source, child, state);
|
|
1293
1880
|
}
|
|
1294
1881
|
function attributeRemovalStart(source, node, prop) {
|
|
@@ -1297,10 +1884,23 @@ function attributeRemovalStart(source, node, prop) {
|
|
|
1297
1884
|
while (start > node.loc.start.offset + 1 && /\s/.test(source[start - 1])) start -= 1;
|
|
1298
1885
|
return /[\r\n]/.test(source.slice(start, attributeStart)) ? attributeStart : start;
|
|
1299
1886
|
}
|
|
1300
|
-
function
|
|
1301
|
-
if (!prop.exp) return void 0;
|
|
1887
|
+
function templateExpression(path, source) {
|
|
1302
1888
|
try {
|
|
1303
|
-
return
|
|
1889
|
+
return expressionAnalysis(path, source);
|
|
1890
|
+
} catch {
|
|
1891
|
+
return;
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
function templateHandler(source) {
|
|
1895
|
+
try {
|
|
1896
|
+
const analysis = sourceAnalysis("Component.handler.ts", `async function handler($event) { ${source} }`);
|
|
1897
|
+
return {
|
|
1898
|
+
staticString: null,
|
|
1899
|
+
vueModuleMember: null,
|
|
1900
|
+
usesCssModule: analysis.usesCssModule,
|
|
1901
|
+
referencesUseCssModule: analysis.hasUnboundUseCssModule,
|
|
1902
|
+
references: analysis.unboundReferences
|
|
1903
|
+
};
|
|
1304
1904
|
} catch {
|
|
1305
1905
|
return;
|
|
1306
1906
|
}
|
|
@@ -1371,12 +1971,12 @@ function vueReferenceTarget(importer, reference, vuePaths) {
|
|
|
1371
1971
|
join(target, "index.vue")
|
|
1372
1972
|
].find((path) => vuePaths.has(path));
|
|
1373
1973
|
}
|
|
1374
|
-
function
|
|
1375
|
-
return new Set(
|
|
1376
|
-
if (/[?*[\]{}]/.test(
|
|
1377
|
-
const target = vueReferenceTarget(
|
|
1974
|
+
function vueGlobTargets(importer, patterns, vuePaths) {
|
|
1975
|
+
return new Set(patterns.flatMap((pattern) => {
|
|
1976
|
+
if (/[?*[\]{}]/.test(pattern)) return pattern.includes("vue") || isLocalVueReference(pattern) ? [...vuePaths] : [];
|
|
1977
|
+
const target = vueReferenceTarget(importer, pattern, vuePaths);
|
|
1378
1978
|
if (target) return [target];
|
|
1379
|
-
return
|
|
1979
|
+
return pattern.includes(".vue") ? [...vuePaths] : [];
|
|
1380
1980
|
}));
|
|
1381
1981
|
}
|
|
1382
1982
|
function normalizedVueTag(value) {
|
|
@@ -1423,28 +2023,48 @@ function buildVueComponentGraph(ownedVue, sourceFiles, analyses, { styleSources,
|
|
|
1423
2023
|
if (analysis.componentsOpen) for (const target of vuePaths) callerOpen.add(target);
|
|
1424
2024
|
}
|
|
1425
2025
|
for (const file of sourceFiles) {
|
|
2026
|
+
if (extname(file.path) === ".html") continue;
|
|
1426
2027
|
let references;
|
|
2028
|
+
let vueReferences;
|
|
1427
2029
|
let blockReferences = [];
|
|
2030
|
+
let hasDynamicImport = false;
|
|
2031
|
+
let vueGlobPatterns = [];
|
|
2032
|
+
let vueGlobUnverifiable = false;
|
|
1428
2033
|
if (extname(file.path) === ".vue") {
|
|
1429
2034
|
const fileAnalysis = analyses.get(file.path);
|
|
1430
2035
|
if (fileAnalysis && !fileAnalysis.retained) {
|
|
1431
2036
|
references = fileAnalysis.scriptStyleImports;
|
|
2037
|
+
vueReferences = fileAnalysis.scriptVueReferences;
|
|
1432
2038
|
blockReferences = fileAnalysis.styleBlockImports.map((entry) => entry.reference);
|
|
1433
|
-
|
|
2039
|
+
hasDynamicImport = fileAnalysis.scriptHasDynamicImport || fileAnalysis.scriptImportsUnverifiable;
|
|
2040
|
+
vueGlobPatterns = fileAnalysis.scriptVueGlobPatterns;
|
|
2041
|
+
vueGlobUnverifiable = fileAnalysis.scriptVueGlobUnverifiable;
|
|
2042
|
+
} else {
|
|
2043
|
+
references = [];
|
|
2044
|
+
vueReferences = [];
|
|
2045
|
+
hasDynamicImport = true;
|
|
2046
|
+
}
|
|
1434
2047
|
} else try {
|
|
1435
|
-
|
|
2048
|
+
const analysis = sourceAnalysis(file.path, file.source);
|
|
2049
|
+
references = analysis.staticImports;
|
|
2050
|
+
vueReferences = analysis.imports.filter((record) => !record.typeOnly).map((record) => record.specifier);
|
|
2051
|
+
hasDynamicImport = analysis.hasDynamicImport;
|
|
2052
|
+
vueGlobPatterns = analysis.vueGlobPatterns;
|
|
2053
|
+
vueGlobUnverifiable = analysis.vueGlobUnverifiable;
|
|
1436
2054
|
} catch {
|
|
1437
2055
|
references = [];
|
|
2056
|
+
vueReferences = [];
|
|
2057
|
+
hasDynamicImport = true;
|
|
1438
2058
|
}
|
|
1439
|
-
if (
|
|
2059
|
+
if (hasDynamicImport || vueGlobUnverifiable || vueReferences.some((reference) => !stylesheetReference.test(reference) && isLocalVueReference(reference) && couldResolveToVue(reference) && !vueReferenceTarget(file.path, reference, vuePaths))) for (const target of vuePaths) callerOpen.add(target);
|
|
1440
2060
|
const resolvesOwned = (reference) => stylesheetReferenceTargets(file.path, reference, styleSources).some((path) => pathOwners.get(path) === packageRoot);
|
|
1441
2061
|
if (pathOwners.get(file.path) === packageRoot && (references.some((reference) => stylesheetReference.test(reference) && (!reference.startsWith(".") || !resolvesOwned(reference))) || blockReferences.some((reference) => !resolvesOwned(reference)))) unresolvedStyleImport = true;
|
|
1442
|
-
const imported = new Set(
|
|
2062
|
+
const imported = new Set(vueReferences.flatMap((reference) => {
|
|
1443
2063
|
const target = vueReferenceTarget(file.path, reference, vuePaths);
|
|
1444
2064
|
return target ? [target] : [];
|
|
1445
2065
|
}));
|
|
1446
2066
|
for (const target of imported) if (extname(file.path) !== ".vue" || !analyses.get(file.path)?.setupImports?.has(target)) callerOpen.add(target);
|
|
1447
|
-
for (const target of
|
|
2067
|
+
for (const target of vueGlobTargets(file.path, vueGlobPatterns, vuePaths)) if (!imported.has(target)) callerOpen.add(target);
|
|
1448
2068
|
}
|
|
1449
2069
|
return {
|
|
1450
2070
|
callers,
|
|
@@ -1452,15 +2072,16 @@ function buildVueComponentGraph(ownedVue, sourceFiles, analyses, { styleSources,
|
|
|
1452
2072
|
unresolvedStyleImport
|
|
1453
2073
|
};
|
|
1454
2074
|
}
|
|
1455
|
-
function vueWarningsOnlyResult(preparedVue) {
|
|
1456
|
-
|
|
2075
|
+
function vueWarningsOnlyResult(preparedVue, extraWarnings = []) {
|
|
2076
|
+
const warnings = [...preparedVue.warnings, ...extraWarnings];
|
|
2077
|
+
if (warnings.length === 0) return {};
|
|
1457
2078
|
return { plan: {
|
|
1458
2079
|
files: [],
|
|
1459
2080
|
deletedFiles: [],
|
|
1460
2081
|
unlinkedFiles: [],
|
|
1461
2082
|
candidates: [],
|
|
1462
2083
|
rules: [],
|
|
1463
|
-
warnings
|
|
2084
|
+
warnings,
|
|
1464
2085
|
convertedRules: 0,
|
|
1465
2086
|
retainedRules: 0
|
|
1466
2087
|
} };
|
|
@@ -1542,13 +2163,17 @@ async function preparePackageVue({ packageRoot, styleCompilers, sourceFiles, sty
|
|
|
1542
2163
|
pathOwners,
|
|
1543
2164
|
packageRoot
|
|
1544
2165
|
});
|
|
1545
|
-
const generatesSelectors = (text) => /#\{|@\{|&[\w-]/.test(text);
|
|
1546
2166
|
const vueShadowCss = [];
|
|
1547
2167
|
const vueShadowModuleCss = [];
|
|
1548
2168
|
let vueShadowUnverifiable = vueGraph.unresolvedStyleImport;
|
|
1549
2169
|
for (const [path, source] of styleSources) {
|
|
1550
2170
|
if (pathOwners.get(path) !== packageRoot) continue;
|
|
1551
|
-
|
|
2171
|
+
try {
|
|
2172
|
+
if (stylesheetAnalysis(path, source).selectorsUnverifiable) {
|
|
2173
|
+
vueShadowUnverifiable = true;
|
|
2174
|
+
continue;
|
|
2175
|
+
}
|
|
2176
|
+
} catch {
|
|
1552
2177
|
vueShadowUnverifiable = true;
|
|
1553
2178
|
continue;
|
|
1554
2179
|
}
|
|
@@ -1558,21 +2183,23 @@ async function preparePackageVue({ packageRoot, styleCompilers, sourceFiles, sty
|
|
|
1558
2183
|
source
|
|
1559
2184
|
});
|
|
1560
2185
|
}
|
|
1561
|
-
for (const file of sourceFiles) if (extname(file.path) === ".html" && pathOwners.get(file.path) === packageRoot
|
|
2186
|
+
for (const file of sourceFiles) if (extname(file.path) === ".html" && pathOwners.get(file.path) === packageRoot) try {
|
|
2187
|
+
if (parseHtmlSource(file.path, file.source).hasStyle) vueShadowUnverifiable = true;
|
|
2188
|
+
} catch {
|
|
2189
|
+
vueShadowUnverifiable = true;
|
|
2190
|
+
}
|
|
1562
2191
|
for (const file of ownedVue) {
|
|
1563
2192
|
const analysis = analysisOf(file.path);
|
|
1564
2193
|
if (analysis.retained) {
|
|
1565
|
-
if (
|
|
2194
|
+
if (analysis.styleRanges.length > 0) vueShadowUnverifiable = true;
|
|
1566
2195
|
continue;
|
|
1567
2196
|
}
|
|
1568
2197
|
if (analysis.escapeUnverifiable || analysis.scriptImportsUnverifiable) vueShadowUnverifiable = true;
|
|
1569
|
-
for (const text of analysis.shadowPreprocessorTexts)
|
|
1570
|
-
else vueShadowCss.push({
|
|
2198
|
+
for (const text of analysis.shadowPreprocessorTexts) vueShadowCss.push({
|
|
1571
2199
|
path: file.path,
|
|
1572
2200
|
source: text
|
|
1573
2201
|
});
|
|
1574
|
-
for (const text of analysis.unscopedShadowPreprocessorTexts)
|
|
1575
|
-
else vueShadowCss.push({
|
|
2202
|
+
for (const text of analysis.unscopedShadowPreprocessorTexts) vueShadowCss.push({
|
|
1576
2203
|
path: file.path,
|
|
1577
2204
|
source: text,
|
|
1578
2205
|
migratingUnscoped: true
|
|
@@ -1586,7 +2213,7 @@ async function preparePackageVue({ packageRoot, styleCompilers, sourceFiles, sty
|
|
|
1586
2213
|
migratingUnscoped: true
|
|
1587
2214
|
})), ...analysis.blocks.map((block) => ({
|
|
1588
2215
|
path: file.path,
|
|
1589
|
-
source: block.analysisSource ?? block.content,
|
|
2216
|
+
source: block.shadowSource ?? block.analysisSource ?? block.content,
|
|
1590
2217
|
scoped: true
|
|
1591
2218
|
})));
|
|
1592
2219
|
vueShadowModuleCss.push(...analysis.shadowModuleCssTexts, ...analysis.moduleBlocks.map((block) => block.analysisSource ?? block.content));
|
|
@@ -1627,7 +2254,7 @@ async function preparePackageVue({ packageRoot, styleCompilers, sourceFiles, sty
|
|
|
1627
2254
|
for (const path of importedStyles) {
|
|
1628
2255
|
stylePaths.add(path);
|
|
1629
2256
|
if (extname(path) !== ".css") continue;
|
|
1630
|
-
for (const imported of cssImports(styleSources.get(path) ?? "")) {
|
|
2257
|
+
for (const imported of cssImports(path, styleSources.get(path) ?? "")) {
|
|
1631
2258
|
if (imported.media) continue;
|
|
1632
2259
|
for (const target of stylesheetReferenceTargets(path, imported.href, styleSources)) if (pathOwners.get(target) === packageRoot) importedStyles.add(target);
|
|
1633
2260
|
}
|
|
@@ -1712,6 +2339,19 @@ async function preparePackageVue({ packageRoot, styleCompilers, sourceFiles, sty
|
|
|
1712
2339
|
message
|
|
1713
2340
|
});
|
|
1714
2341
|
}
|
|
2342
|
+
const allStyleBlocks = [
|
|
2343
|
+
...analysis.blocks,
|
|
2344
|
+
...analysis.moduleBlocks,
|
|
2345
|
+
...analysis.unscopedBlocks
|
|
2346
|
+
];
|
|
2347
|
+
let vueAtRuleIdentities;
|
|
2348
|
+
if (analysis.hasOpaqueStyleBlocks) vueAtRuleIdentities = null;
|
|
2349
|
+
else try {
|
|
2350
|
+
const registrations = allStyleBlocks.map((block) => stylesheetAnalysis(`${file.path}.${block.analysisSource ? "css" : block.syntax}`, block.analysisSource ?? block.content));
|
|
2351
|
+
vueAtRuleIdentities = registrations.some((entry) => entry.globalAtRulesUnverifiable) ? null : registrations.flatMap((entry) => entry.globalAtRuleIdentities);
|
|
2352
|
+
} catch {
|
|
2353
|
+
vueAtRuleIdentities = null;
|
|
2354
|
+
}
|
|
1715
2355
|
const vueBlocks = migrateUnscoped ? analysis.unscopedBlocks : analysis.blocks;
|
|
1716
2356
|
if (vueBlocks.length === 0 && !migrateModule) continue;
|
|
1717
2357
|
const vueRetention = migrateUnscoped ? void 0 : retention;
|
|
@@ -1719,19 +2359,20 @@ async function preparePackageVue({ packageRoot, styleCompilers, sourceFiles, sty
|
|
|
1719
2359
|
if (migrateModule) stylesheets.push({
|
|
1720
2360
|
cssPath: file.path,
|
|
1721
2361
|
cssSource: file.source,
|
|
1722
|
-
cssModuleId: normalizedRelativePath(
|
|
2362
|
+
cssModuleId: normalizedRelativePath(workspaceRoot, file.path),
|
|
1723
2363
|
syntax: "css",
|
|
1724
2364
|
isModule: true,
|
|
1725
2365
|
vueBlocks: analysis.moduleBlocks,
|
|
1726
2366
|
vueModule: true,
|
|
1727
2367
|
vueShadowCss: shadowCss,
|
|
1728
2368
|
vueShadowModuleCss,
|
|
1729
|
-
vueShadowUnverifiable
|
|
2369
|
+
vueShadowUnverifiable,
|
|
2370
|
+
vueAtRuleIdentities
|
|
1730
2371
|
});
|
|
1731
2372
|
if (vueBlocks.length > 0) stylesheets.push({
|
|
1732
2373
|
cssPath: file.path,
|
|
1733
2374
|
cssSource: file.source,
|
|
1734
|
-
cssModuleId: normalizedRelativePath(
|
|
2375
|
+
cssModuleId: normalizedRelativePath(workspaceRoot, file.path),
|
|
1735
2376
|
syntax: "css",
|
|
1736
2377
|
isModule: !vueRetention,
|
|
1737
2378
|
vueBlocks,
|
|
@@ -1739,7 +2380,8 @@ async function preparePackageVue({ packageRoot, styleCompilers, sourceFiles, sty
|
|
|
1739
2380
|
vueUnscoped: migrateUnscoped,
|
|
1740
2381
|
vueShadowCss: vueRetention ? void 0 : shadowCss,
|
|
1741
2382
|
vueShadowModuleCss: vueRetention ? void 0 : vueShadowModuleCss,
|
|
1742
|
-
vueShadowUnverifiable: vueRetention ? void 0 : vueShadowUnverifiable
|
|
2383
|
+
vueShadowUnverifiable: vueRetention ? void 0 : vueShadowUnverifiable,
|
|
2384
|
+
vueAtRuleIdentities
|
|
1743
2385
|
});
|
|
1744
2386
|
}
|
|
1745
2387
|
for (const file of ownedVue) {
|
|
@@ -1747,19 +2389,22 @@ async function preparePackageVue({ packageRoot, styleCompilers, sourceFiles, sty
|
|
|
1747
2389
|
if (analysis.retained) continue;
|
|
1748
2390
|
const elements = elementsByFile.get(file.path) ?? [];
|
|
1749
2391
|
const contextPaths = [...new Set(elements.flatMap((element) => element.cssPaths))];
|
|
1750
|
-
if (contextPaths.length
|
|
1751
|
-
await rejectSymlinkTarget(file.path, packageRoot);
|
|
2392
|
+
if (contextPaths.length > 0) await rejectSymlinkTarget(file.path, packageRoot);
|
|
1752
2393
|
files.set(file.path, {
|
|
1753
2394
|
...file,
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
cssPath
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
2395
|
+
...contextPaths.length > 0 ? {
|
|
2396
|
+
htmlElements: elements,
|
|
2397
|
+
htmlStylesheets: contextPaths.map((cssPath) => ({
|
|
2398
|
+
cssPath,
|
|
2399
|
+
variants: [],
|
|
2400
|
+
direct: cssPath === file.path,
|
|
2401
|
+
analyzable: true
|
|
2402
|
+
})),
|
|
2403
|
+
htmlReferencesSafe: !analysis.dynamic,
|
|
2404
|
+
htmlScriptText: analysis.scriptText
|
|
2405
|
+
} : {},
|
|
2406
|
+
sourceImports: analysis.scriptImports,
|
|
2407
|
+
sourceImportsUnverifiable: analysis.scriptImportsUnverifiable
|
|
1763
2408
|
});
|
|
1764
2409
|
}
|
|
1765
2410
|
return {
|
|
@@ -1865,7 +2510,13 @@ async function migrate(options = {}) {
|
|
|
1865
2510
|
Promise.all(stylePaths.map(async (path) => [path, await snapshotFile(snapshots, path)])).then((entries) => new Map(entries)),
|
|
1866
2511
|
Promise.all(sourcePaths.map(async (path) => {
|
|
1867
2512
|
const source = await readFile(path, "utf8");
|
|
1868
|
-
const mayReferenceModule = extname(path) !== ".html" ||
|
|
2513
|
+
const mayReferenceModule = extname(path) !== ".html" || (() => {
|
|
2514
|
+
try {
|
|
2515
|
+
return parseHtmlSource(path, source).links.length > 0;
|
|
2516
|
+
} catch {
|
|
2517
|
+
return /<link\b/i.test(source);
|
|
2518
|
+
}
|
|
2519
|
+
})();
|
|
1869
2520
|
if (!scope.targetable.has(path) && !mayReferenceModule) return void 0;
|
|
1870
2521
|
return {
|
|
1871
2522
|
path,
|
|
@@ -1885,15 +2536,27 @@ async function migrate(options = {}) {
|
|
|
1885
2536
|
styleSources,
|
|
1886
2537
|
sourceFiles,
|
|
1887
2538
|
styleDependents: indexStylesheetDependents(styleSources),
|
|
1888
|
-
vueStyleRanges
|
|
2539
|
+
vueStyleRanges,
|
|
2540
|
+
entryCatalog: tailwindEntryCatalog(styleSources, scope.pathOwners),
|
|
2541
|
+
ignoredPaths: options.workspaces ? await scannerIgnoredPaths(workspaceRoot, sourceFiles.map((file) => file.path)) : /* @__PURE__ */ new Set()
|
|
1889
2542
|
};
|
|
1890
2543
|
const failures = [];
|
|
1891
2544
|
const plans = [];
|
|
2545
|
+
const prepared = [];
|
|
1892
2546
|
for (const packageRoot of selectedPackages) {
|
|
1893
|
-
const
|
|
1894
|
-
if (
|
|
1895
|
-
else if (
|
|
1896
|
-
|
|
2547
|
+
const preparation = await preparePackage(context, packageRoot);
|
|
2548
|
+
if (preparation.failure) failures.push(preparation.failure);
|
|
2549
|
+
else if (preparation.plan) plans.push(preparation.plan);
|
|
2550
|
+
if (preparation.prepared) prepared.push(preparation.prepared);
|
|
2551
|
+
}
|
|
2552
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2553
|
+
for (const preparation of prepared) {
|
|
2554
|
+
const members = groups.get(preparation.tailwind.path) ?? [];
|
|
2555
|
+
members.push(preparation);
|
|
2556
|
+
groups.set(preparation.tailwind.path, members);
|
|
2557
|
+
}
|
|
2558
|
+
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
|
+
else if (result.plan) plans.push(result.plan);
|
|
1897
2560
|
const originals = new Map([...styleSources, ...sourceFiles.map((file) => [file.path, file.source])]);
|
|
1898
2561
|
const { filesByPath, deletedPaths, candidates, rules, warnings, convertedRules, retainedRules } = mergePlans(plans, originals);
|
|
1899
2562
|
const changed = [...filesByPath.values()].map((file) => ({
|
|
@@ -1980,7 +2643,7 @@ async function migrate(options = {}) {
|
|
|
1980
2643
|
failures
|
|
1981
2644
|
};
|
|
1982
2645
|
}
|
|
1983
|
-
async function
|
|
2646
|
+
async function preparePackage(context, packageRoot) {
|
|
1984
2647
|
const { options, snapshots, workspaceRoot, explicitStyle, configuredEntry, styleSources, sourceFiles, styleDependents, pathOwners, targetable, selectedPackages, scannedPaths, vueStyleRanges } = context;
|
|
1985
2648
|
const recover = (error, fatal = false) => {
|
|
1986
2649
|
if (!options.force || fatal) throw error;
|
|
@@ -2030,19 +2693,84 @@ async function planPackage(context, packageRoot) {
|
|
|
2030
2693
|
if (ownedStyles.length === 0 && preparedVue.stylesheets.length === 0) return vueWarningsOnlyResult(preparedVue);
|
|
2031
2694
|
let tailwindPath;
|
|
2032
2695
|
let tailwindEntries;
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2696
|
+
let entryOwner = packageRoot;
|
|
2697
|
+
let sharedProofs;
|
|
2698
|
+
const ownEntries = findTailwindEntries(ownedStyles, styleSources);
|
|
2699
|
+
if (ownEntries.length === 0 && configuredEntry === void 0 && options.workspaces && options.extractMediaQueries !== false) {
|
|
2700
|
+
let packageJson = {};
|
|
2701
|
+
try {
|
|
2702
|
+
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
|
+
}
|
|
2707
|
+
} catch (error) {
|
|
2708
|
+
return recover(error);
|
|
2709
|
+
}
|
|
2710
|
+
const ancestors = selectedPackages.filter((root) => root !== packageRoot && isWithin(root, packageRoot)).sort((left, right) => right.length - left.length);
|
|
2711
|
+
for (const ancestor of ancestors) {
|
|
2712
|
+
const proven = [];
|
|
2713
|
+
for (const candidate of context.entryCatalog.get(ancestor) ?? []) {
|
|
2714
|
+
const proofs = proveSharedEntry({
|
|
2715
|
+
packageRoot,
|
|
2716
|
+
entry: candidate,
|
|
2717
|
+
entrySource: styleSources.get(candidate) ?? "",
|
|
2718
|
+
styleSources,
|
|
2719
|
+
packageSources,
|
|
2720
|
+
owned: (path) => pathOwners.get(path) === packageRoot,
|
|
2721
|
+
writable: (path) => targetable.has(path) && pathOwners.get(path) === packageRoot,
|
|
2722
|
+
packageJson,
|
|
2723
|
+
ignoredPaths: context.ignoredPaths
|
|
2724
|
+
});
|
|
2725
|
+
if (proofs) proven.push({
|
|
2726
|
+
entry: candidate,
|
|
2727
|
+
proofs
|
|
2728
|
+
});
|
|
2729
|
+
}
|
|
2730
|
+
if (proven.length > 1) return recover(/* @__PURE__ */ new Error("Multiple proven ancestor Tailwind entries were found. Pass --tailwind-css."));
|
|
2731
|
+
if (proven.length === 1) {
|
|
2732
|
+
[{entry: tailwindPath, proofs: sharedProofs}] = proven;
|
|
2733
|
+
entryOwner = ancestor;
|
|
2734
|
+
break;
|
|
2735
|
+
}
|
|
2736
|
+
}
|
|
2737
|
+
}
|
|
2738
|
+
if (sharedProofs && tailwindPath !== void 0) tailwindEntries = [];
|
|
2739
|
+
else {
|
|
2740
|
+
tailwindEntries = ownEntries;
|
|
2741
|
+
try {
|
|
2742
|
+
tailwindPath = selectTailwindEntry(ownEntries, configuredEntry);
|
|
2743
|
+
} catch (error) {
|
|
2744
|
+
return recover(error);
|
|
2745
|
+
}
|
|
2037
2746
|
}
|
|
2038
2747
|
const excludedEntries = /* @__PURE__ */ new Set([...tailwindEntries, tailwindPath]);
|
|
2039
2748
|
const explicitCss = explicitStyle && extname(explicitStyle) !== ".vue" ? explicitStyle : void 0;
|
|
2040
|
-
const targets = explicitCss ? [explicitCss] : explicitStyle ? [...preparedVue.stylePaths].filter((path) => !isStylesheetModule(path) && !excludedEntries.has(path)) : ownedStyles.filter((path) => !excludedEntries.has(path) && !preparedHtml.generatedPaths.has(path) && (!isPreprocessorPath(path) || preparedHtml.stylePaths.has(path) || packageSources.some((file) =>
|
|
2749
|
+
const targets = explicitCss ? [explicitCss] : explicitStyle ? [...preparedVue.stylePaths].filter((path) => !isStylesheetModule(path) && !excludedEntries.has(path)) : ownedStyles.filter((path) => !excludedEntries.has(path) && !preparedHtml.generatedPaths.has(path) && (!isPreprocessorPath(path) || preparedHtml.stylePaths.has(path) || preparedVue.stylePaths.has(path) || packageSources.some((file) => importsStylesheet(file, path))));
|
|
2041
2750
|
if (targets.length === 0 && preparedVue.stylesheets.length === 0) return vueWarningsOnlyResult(preparedVue);
|
|
2042
2751
|
if (targets.some((path) => excludedEntries.has(path))) throw new Error("The Tailwind CSS entry cannot be migrated.");
|
|
2752
|
+
const proofWarnings = [];
|
|
2753
|
+
const unprovenFlow = (path) => ({
|
|
2754
|
+
code: "unproven-shared-entry-flow",
|
|
2755
|
+
file: path,
|
|
2756
|
+
start: 0,
|
|
2757
|
+
end: 0,
|
|
2758
|
+
message: "A consumer flow could not be proven to load the shared Tailwind entry, so the stylesheet is retained."
|
|
2759
|
+
});
|
|
2760
|
+
const provenTargets = sharedProofs ? targets.filter((path) => {
|
|
2761
|
+
const proven = sharedProofs.provenStyle(packageSources.filter((file) => importsStylesheet(file, path) || (file.htmlStylesheets ?? []).some((linked) => linked.cssPath === path)));
|
|
2762
|
+
if (!proven) proofWarnings.push(unprovenFlow(path));
|
|
2763
|
+
return proven;
|
|
2764
|
+
}) : targets;
|
|
2765
|
+
const provenVueStylesheets = sharedProofs ? preparedVue.stylesheets.filter((stylesheet) => {
|
|
2766
|
+
const proven = sharedProofs.provenStyle(packageSources.filter((file) => file.path === stylesheet.cssPath));
|
|
2767
|
+
if (!proven && !proofWarnings.some((warning) => warning.file === stylesheet.cssPath)) proofWarnings.push(unprovenFlow(stylesheet.cssPath));
|
|
2768
|
+
return proven;
|
|
2769
|
+
}) : preparedVue.stylesheets;
|
|
2770
|
+
if (provenTargets.length === 0 && provenVueStylesheets.length === 0) return vueWarningsOnlyResult(preparedVue, proofWarnings);
|
|
2043
2771
|
let tailwind;
|
|
2044
2772
|
try {
|
|
2045
|
-
tailwind = await loadTailwind(
|
|
2773
|
+
tailwind = await loadTailwind(entryOwner, tailwindPath, snapshots, workspaceRoot);
|
|
2046
2774
|
} catch (error) {
|
|
2047
2775
|
return recover(error, isIntegrityError(error));
|
|
2048
2776
|
}
|
|
@@ -2057,13 +2785,13 @@ async function planPackage(context, packageRoot) {
|
|
|
2057
2785
|
try {
|
|
2058
2786
|
stylesheets = [];
|
|
2059
2787
|
const compilerDependents = /* @__PURE__ */ new Map();
|
|
2060
|
-
for (const stylePath of
|
|
2788
|
+
for (const stylePath of provenTargets.sort()) {
|
|
2061
2789
|
await rejectSymlinkTarget(stylePath, packageRoot);
|
|
2062
2790
|
const isPartial = isSassPath(stylePath) && basename(stylePath).startsWith("_");
|
|
2063
2791
|
const stylesheet = {
|
|
2064
2792
|
cssPath: stylePath,
|
|
2065
2793
|
cssSource: styleSources.get(stylePath) ?? "",
|
|
2066
|
-
cssModuleId: normalizedRelativePath(
|
|
2794
|
+
cssModuleId: normalizedRelativePath(workspaceRoot, stylePath),
|
|
2067
2795
|
cssDependents: styleDependents.get(stylePath) ?? [],
|
|
2068
2796
|
syntax: stylesheetSyntax(stylePath),
|
|
2069
2797
|
isModule: isStylesheetModule(stylePath),
|
|
@@ -2097,125 +2825,405 @@ async function planPackage(context, packageRoot) {
|
|
|
2097
2825
|
} catch (error) {
|
|
2098
2826
|
return recover(error, isIntegrityError(error));
|
|
2099
2827
|
}
|
|
2100
|
-
stylesheets.push(...
|
|
2101
|
-
|
|
2828
|
+
stylesheets.push(...provenVueStylesheets);
|
|
2829
|
+
return { prepared: {
|
|
2830
|
+
packageRoot,
|
|
2831
|
+
preparedHtml,
|
|
2832
|
+
preparedVue,
|
|
2833
|
+
styleCompilers,
|
|
2834
|
+
files,
|
|
2102
2835
|
stylesheets,
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2836
|
+
tailwind,
|
|
2837
|
+
sharedProofs,
|
|
2838
|
+
warnings: proofWarnings
|
|
2839
|
+
} };
|
|
2840
|
+
}
|
|
2841
|
+
async function planPreparedGroup(context, members) {
|
|
2842
|
+
const { options, workspaceRoot, targetable } = context;
|
|
2843
|
+
const results = [];
|
|
2844
|
+
let active = [...members].sort((left, right) => {
|
|
2845
|
+
const leftPath = normalizedRelativePath(workspaceRoot, left.packageRoot);
|
|
2846
|
+
const rightPath = normalizedRelativePath(workspaceRoot, right.packageRoot);
|
|
2847
|
+
return leftPath < rightPath ? -1 : leftPath > rightPath ? 1 : 0;
|
|
2848
|
+
});
|
|
2849
|
+
const entry = active[0].tailwind;
|
|
2850
|
+
const recoverGroup = (error, fatal = false) => {
|
|
2851
|
+
if (!options.force || fatal) throw error;
|
|
2852
|
+
for (const member of active) results.push({ failure: packageFailure(workspaceRoot, member.packageRoot, error) });
|
|
2853
|
+
return results;
|
|
2108
2854
|
};
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2855
|
+
const membersByRoot = new Map(active.map((member) => [member.packageRoot, member]));
|
|
2856
|
+
for (const member of options.extractMediaQueries !== false ? active : []) member.stylesheets = member.stylesheets.filter((stylesheet) => {
|
|
2857
|
+
const proven = member.files.filter((file) => context.pathOwners.get(file.path) !== member.packageRoot && (importsStylesheet(file, stylesheet.cssPath) || (file.htmlStylesheets ?? []).some((linked) => linked.cssPath === stylesheet.cssPath))).every((consumer) => {
|
|
2858
|
+
const consumerRoot = context.pathOwners.get(consumer.path);
|
|
2859
|
+
const consumerMember = consumerRoot === void 0 ? void 0 : membersByRoot.get(consumerRoot);
|
|
2860
|
+
if (consumerMember === void 0) return false;
|
|
2861
|
+
if (consumerMember.sharedProofs === void 0) return true;
|
|
2862
|
+
return consumerMember.sharedProofs.provenConsumer(consumer);
|
|
2863
|
+
});
|
|
2864
|
+
if (!proven) member.warnings.push({
|
|
2865
|
+
code: "unproven-shared-entry-flow",
|
|
2866
|
+
file: stylesheet.cssPath,
|
|
2867
|
+
start: 0,
|
|
2868
|
+
end: 0,
|
|
2869
|
+
message: "A consumer flow could not be proven to load the shared Tailwind entry, so the stylesheet is retained."
|
|
2870
|
+
});
|
|
2871
|
+
return proven;
|
|
2872
|
+
});
|
|
2873
|
+
for (const member of active.filter((remaining) => remaining.stylesheets.length === 0)) if (member.warnings.length > 0) results.push({ plan: {
|
|
2874
|
+
files: [],
|
|
2875
|
+
deletedFiles: [],
|
|
2876
|
+
unlinkedFiles: [],
|
|
2877
|
+
candidates: [],
|
|
2878
|
+
rules: [],
|
|
2879
|
+
warnings: [
|
|
2880
|
+
...member.warnings,
|
|
2881
|
+
...member.preparedHtml.warnings,
|
|
2882
|
+
...member.preparedVue.warnings
|
|
2883
|
+
],
|
|
2884
|
+
convertedRules: 0,
|
|
2885
|
+
retainedRules: 0
|
|
2886
|
+
} });
|
|
2887
|
+
active = active.filter((remaining) => remaining.stylesheets.length > 0);
|
|
2888
|
+
if (active.length === 0) return results;
|
|
2889
|
+
const groupWritable = !await entryUnsafe(entry.path, workspaceRoot, targetable);
|
|
2890
|
+
const mediaWarnings = [];
|
|
2891
|
+
let extraction;
|
|
2892
|
+
const collections = [];
|
|
2893
|
+
for (const member of active) try {
|
|
2894
|
+
collections.push(collectMemberMediaConditions(member.stylesheets, entry));
|
|
2112
2895
|
} catch (error) {
|
|
2113
|
-
|
|
2896
|
+
if (!options.force || !isRecoverablePlanningError(error)) throw error;
|
|
2897
|
+
results.push({ failure: packageFailure(workspaceRoot, member.packageRoot, error) });
|
|
2898
|
+
active = active.filter((remaining) => remaining !== member);
|
|
2899
|
+
}
|
|
2900
|
+
if (active.length === 0) return results;
|
|
2901
|
+
let names = extraction?.names;
|
|
2902
|
+
let generated = extraction?.generated ?? [];
|
|
2903
|
+
if (options.extractMediaQueries !== false) if (!groupWritable) {
|
|
2904
|
+
names = {};
|
|
2905
|
+
generated = [];
|
|
2906
|
+
if (collections.some((collection) => collection.components.length > 0)) mediaWarnings.push({
|
|
2907
|
+
code: "media-query-definition-fallback",
|
|
2908
|
+
file: entry.path,
|
|
2909
|
+
start: 0,
|
|
2910
|
+
end: 0,
|
|
2911
|
+
message: "The Tailwind entry could not be edited safely, so media behavior was preserved with arbitrary variants."
|
|
2912
|
+
});
|
|
2913
|
+
} else {
|
|
2914
|
+
try {
|
|
2915
|
+
extraction = planMediaExtraction({
|
|
2916
|
+
collections,
|
|
2917
|
+
tailwind: entry,
|
|
2918
|
+
scannedSources: context.sourceFiles.map((file) => file.source)
|
|
2919
|
+
});
|
|
2920
|
+
} catch (error) {
|
|
2921
|
+
return recoverGroup(error, !isRecoverablePlanningError(error));
|
|
2922
|
+
}
|
|
2923
|
+
names = extraction.names;
|
|
2924
|
+
generated = extraction.generated;
|
|
2114
2925
|
}
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2926
|
+
let plan = {
|
|
2927
|
+
files: [],
|
|
2928
|
+
deletedFiles: [],
|
|
2929
|
+
unlinkedFiles: [],
|
|
2930
|
+
candidates: [],
|
|
2931
|
+
rules: [],
|
|
2932
|
+
warnings: [],
|
|
2933
|
+
convertedRules: 0,
|
|
2934
|
+
retainedRules: 0
|
|
2935
|
+
};
|
|
2936
|
+
let augmented = entry.css;
|
|
2937
|
+
const blocked = /* @__PURE__ */ new Map();
|
|
2938
|
+
finalization: for (;;) {
|
|
2939
|
+
const baseIdentities = new Set(atRuleIdentities(`${entry.path}.group-base.css`, [entry.css, ...entry.graphSources.map((graphSource) => graphSource.source)].join("\n")));
|
|
2940
|
+
const sheets = active.flatMap((member) => member.stylesheets.map((stylesheet) => ({
|
|
2941
|
+
member,
|
|
2942
|
+
stylesheet
|
|
2943
|
+
})));
|
|
2944
|
+
const movable = ({ member, stylesheet }) => member.sharedProofs === void 0 && stylesheet.isModule === true && (stylesheet.syntax === void 0 || stylesheet.syntax === "css") && (stylesheet.vueBlocks === void 0 || stylesheet.vueBlocks.length === 0);
|
|
2945
|
+
for (const sheet of sheets) {
|
|
2946
|
+
if (movable(sheet)) continue;
|
|
2947
|
+
for (const identity of stylesheetAtRuleIdentities(sheet.stylesheet)) baseIdentities.add(identity);
|
|
2948
|
+
}
|
|
2949
|
+
const sheetIdentities = sheets.map((sheet) => movable(sheet) ? new Set(stylesheetAtRuleIdentities(sheet.stylesheet)) : /* @__PURE__ */ new Set());
|
|
2950
|
+
const opaqueVueRegistrations = sheets.some(({ stylesheet }) => stylesheet.vueAtRuleIdentities === null);
|
|
2951
|
+
const movesDisabled = new Set(opaqueVueRegistrations ? sheets.flatMap((sheet, index) => movable(sheet) ? [index] : []) : []);
|
|
2952
|
+
for (const [index, identities] of sheetIdentities.entries()) for (const identity of identities) {
|
|
2953
|
+
if (baseIdentities.has(identity)) movesDisabled.add(index);
|
|
2954
|
+
for (const [other, otherIdentities] of sheetIdentities.entries()) if (other !== index && otherIdentities.has(identity)) {
|
|
2955
|
+
movesDisabled.add(index);
|
|
2956
|
+
movesDisabled.add(other);
|
|
2957
|
+
}
|
|
2958
|
+
}
|
|
2959
|
+
const filesByPath = /* @__PURE__ */ new Map();
|
|
2960
|
+
const contextsByPath = /* @__PURE__ */ new Map();
|
|
2961
|
+
for (const member of active) for (const file of member.files) {
|
|
2962
|
+
if (file.htmlStylesheets !== void 0 && file.htmlStylesheets.length > 0) {
|
|
2963
|
+
const contexts = contextsByPath.get(file.path) ?? [];
|
|
2964
|
+
contexts.push(...file.htmlStylesheets);
|
|
2965
|
+
contextsByPath.set(file.path, contexts);
|
|
2966
|
+
}
|
|
2967
|
+
if (!filesByPath.has(file.path) || context.pathOwners.get(file.path) === member.packageRoot) filesByPath.set(file.path, file);
|
|
2968
|
+
}
|
|
2969
|
+
const files = [...filesByPath.values()].map((file) => {
|
|
2970
|
+
const contexts = contextsByPath.get(file.path);
|
|
2971
|
+
if (contexts === void 0) return file;
|
|
2972
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2973
|
+
const merged = contexts.filter((linked) => {
|
|
2974
|
+
const key = `${linked.cssPath}\0${linked.variants.join(",")}\0${linked.direct}\0${linked.analyzable}`;
|
|
2975
|
+
if (seen.has(key)) return false;
|
|
2976
|
+
seen.add(key);
|
|
2977
|
+
return true;
|
|
2978
|
+
});
|
|
2979
|
+
return {
|
|
2980
|
+
...file,
|
|
2981
|
+
htmlStylesheets: merged
|
|
2982
|
+
};
|
|
2983
|
+
}).sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0);
|
|
2984
|
+
planning: for (;;) {
|
|
2985
|
+
const request = {
|
|
2986
|
+
stylesheets: sheets.map(({ member, stylesheet }, index) => {
|
|
2987
|
+
const rules = blocked.get(index);
|
|
2988
|
+
const movesOff = member.sharedProofs !== void 0 || movesDisabled.has(index);
|
|
2989
|
+
return {
|
|
2990
|
+
...stylesheet,
|
|
2991
|
+
...rules ? { blockedRules: [...rules.values()].map((blockedRule) => blockedRule.ruleId) } : {},
|
|
2992
|
+
...movesOff ? { globalAtRuleMoves: false } : {}
|
|
2993
|
+
};
|
|
2994
|
+
}),
|
|
2995
|
+
tailwindPath: entry.path,
|
|
2996
|
+
tailwindSource: entry.css,
|
|
2997
|
+
utilityPrefix: entry.designSystem.theme.prefix,
|
|
2998
|
+
themeTokens: entry.themeTokens,
|
|
2999
|
+
...names ? { mediaNames: names } : {},
|
|
3000
|
+
...groupWritable ? {} : { entryWritable: false },
|
|
3001
|
+
files
|
|
3002
|
+
};
|
|
3003
|
+
try {
|
|
3004
|
+
plan = JSON.parse(planBatchMigration(JSON.stringify(request)));
|
|
3005
|
+
} catch (error) {
|
|
3006
|
+
return recoverGroup(error, !isRecoverablePlanningError(error));
|
|
3007
|
+
}
|
|
3008
|
+
let composed = entry.css;
|
|
3009
|
+
const plannedEntry = plan.files.findIndex((file) => file.path === entry.path);
|
|
3010
|
+
if (plannedEntry >= 0) {
|
|
3011
|
+
if (!groupWritable) throw new Error(`The planner claimed an unwritable Tailwind entry: ${entry.path}`);
|
|
3012
|
+
composed = plan.files[plannedEntry].source;
|
|
3013
|
+
plan.files.splice(plannedEntry, 1);
|
|
3014
|
+
if (!composed.startsWith(entry.css)) throw new Error(`The planner rewrote the Tailwind entry instead of appending: ${entry.path}`);
|
|
3015
|
+
}
|
|
3016
|
+
const currentExtraction = names !== void 0 ? {
|
|
3017
|
+
names,
|
|
3018
|
+
generated
|
|
3019
|
+
} : void 0;
|
|
3020
|
+
const used = currentExtraction ? usedGeneratedDefinitions(currentExtraction, plan) : [];
|
|
3021
|
+
augmented = currentExtraction ? appendMediaDefinitions(composed, used) : composed;
|
|
3022
|
+
let system;
|
|
3023
|
+
try {
|
|
3024
|
+
system = augmented === entry.css ? entry.designSystem : await entry.loadWith(augmented);
|
|
3025
|
+
} catch (error) {
|
|
3026
|
+
if (isIntegrityError(error)) throw error;
|
|
3027
|
+
if (names !== void 0 && generated.length > 0) {
|
|
3028
|
+
const generatedKeys = new Set(generated.map((definition) => definition.key));
|
|
3029
|
+
names = Object.fromEntries(Object.entries(names).filter(([key]) => !generatedKeys.has(key)));
|
|
3030
|
+
generated = [];
|
|
3031
|
+
continue planning;
|
|
3032
|
+
}
|
|
3033
|
+
return recoverGroup(error);
|
|
3034
|
+
}
|
|
3035
|
+
if (currentExtraction && names !== void 0) {
|
|
3036
|
+
const prefix = entry.designSystem.theme.prefix;
|
|
3037
|
+
const rejected = new Set(used.filter(({ name }) => system.candidatesToCss([probeCandidate(prefix, name)])[0] === null).map(({ key }) => key));
|
|
3038
|
+
if (rejected.size > 0) {
|
|
3039
|
+
names = Object.fromEntries(Object.entries(names).filter(([key]) => !rejected.has(key)));
|
|
3040
|
+
generated = generated.filter(({ key }) => !rejected.has(key));
|
|
3041
|
+
continue planning;
|
|
3042
|
+
}
|
|
3043
|
+
}
|
|
3044
|
+
const failing = invalidCandidates(currentExtraction ? system : entry.designSystem, plan.candidates);
|
|
3045
|
+
if (failing.length > 0) {
|
|
3046
|
+
if (!accumulateBlockedRules(blocked, plan, failing)) throw new Error(`Tailwind did not generate CSS for candidate: ${failing[0]}`);
|
|
3047
|
+
continue planning;
|
|
3048
|
+
}
|
|
3049
|
+
break;
|
|
3050
|
+
}
|
|
3051
|
+
for (const [index, rules] of blocked) {
|
|
3052
|
+
const cssPath = sheets[index].stylesheet.cssPath;
|
|
3053
|
+
for (const { authoredSpan, candidates } of rules.values()) {
|
|
3054
|
+
const failed = [...candidates].sort().map((candidate) => `\`${candidate}\``).join(", ");
|
|
3055
|
+
plan.warnings.push({
|
|
3056
|
+
code: "candidate-compilation-failure",
|
|
3057
|
+
file: cssPath,
|
|
3058
|
+
start: authoredSpan.start,
|
|
3059
|
+
end: authoredSpan.end,
|
|
3060
|
+
message: `Tailwind did not generate CSS for ${failed}, so the rule is retained.`
|
|
3061
|
+
});
|
|
3062
|
+
}
|
|
3063
|
+
}
|
|
3064
|
+
plan.warnings.push(...mediaWarnings);
|
|
3065
|
+
for (const member of active) plan.warnings.push(...member.warnings);
|
|
3066
|
+
removeGroupHtmlLinks(plan, active);
|
|
3067
|
+
const failure = await finishGroupPlan(context, active, plan);
|
|
3068
|
+
if (failure === void 0) break;
|
|
3069
|
+
if (!options.force || failure.fatal) throw failure.error;
|
|
3070
|
+
const failed = failure.member ?? active[0];
|
|
3071
|
+
results.push({ failure: packageFailure(workspaceRoot, failed.packageRoot, failure.error) });
|
|
3072
|
+
active = active.filter((member) => member !== failed);
|
|
3073
|
+
if (active.length === 0) return results;
|
|
3074
|
+
blocked.clear();
|
|
3075
|
+
plan.warnings = [];
|
|
3076
|
+
continue finalization;
|
|
3077
|
+
}
|
|
3078
|
+
const seenWarnings = /* @__PURE__ */ new Set();
|
|
3079
|
+
plan.warnings = plan.warnings.filter((warning) => {
|
|
3080
|
+
const key = `${warning.code}\0${warning.file}\0${warning.start}\0${warning.end}\0${warning.message}`;
|
|
3081
|
+
if (seenWarnings.has(key)) return false;
|
|
3082
|
+
seenWarnings.add(key);
|
|
3083
|
+
return true;
|
|
3084
|
+
});
|
|
3085
|
+
results.push({ plan });
|
|
3086
|
+
if (augmented !== entry.css) results.push({ plan: {
|
|
3087
|
+
files: [{
|
|
3088
|
+
path: entry.path,
|
|
3089
|
+
source: augmented
|
|
3090
|
+
}],
|
|
3091
|
+
deletedFiles: [],
|
|
3092
|
+
unlinkedFiles: [],
|
|
3093
|
+
candidates: [],
|
|
3094
|
+
rules: [],
|
|
3095
|
+
warnings: [],
|
|
3096
|
+
convertedRules: 0,
|
|
3097
|
+
retainedRules: 0
|
|
3098
|
+
} });
|
|
3099
|
+
return results;
|
|
3100
|
+
}
|
|
3101
|
+
function accumulateBlockedRules(blocked, plan, failing) {
|
|
3102
|
+
let progressed = false;
|
|
3103
|
+
for (const rule of plan.rules) {
|
|
3104
|
+
const failed = rule.candidates.filter((candidate) => failing.includes(candidate));
|
|
3105
|
+
if (failed.length === 0) continue;
|
|
3106
|
+
let rules = blocked.get(rule.stylesheet);
|
|
3107
|
+
if (!rules) blocked.set(rule.stylesheet, rules = /* @__PURE__ */ new Map());
|
|
3108
|
+
const key = `${rule.ruleId.start}-${rule.ruleId.end}`;
|
|
3109
|
+
let blockedRule = rules.get(key);
|
|
3110
|
+
if (!blockedRule) {
|
|
3111
|
+
rules.set(key, blockedRule = {
|
|
3112
|
+
ruleId: rule.ruleId,
|
|
3113
|
+
authoredSpan: rule.authoredSpan,
|
|
3114
|
+
candidates: /* @__PURE__ */ new Set()
|
|
3115
|
+
});
|
|
3116
|
+
progressed = true;
|
|
3117
|
+
}
|
|
3118
|
+
for (const candidate of failed) blockedRule.candidates.add(candidate);
|
|
3119
|
+
}
|
|
3120
|
+
return progressed;
|
|
3121
|
+
}
|
|
3122
|
+
async function entryUnsafe(entryPath, workspaceRoot, targetable) {
|
|
3123
|
+
if (!targetable.has(entryPath) || !isProjectInput(workspaceRoot, entryPath)) return true;
|
|
3124
|
+
return (await lstat(entryPath)).isSymbolicLink();
|
|
3125
|
+
}
|
|
3126
|
+
function atRuleIdentities(key, css) {
|
|
3127
|
+
return css.trim() === "" ? [] : stylesheetAnalysis(key, css).globalAtRuleIdentities;
|
|
3128
|
+
}
|
|
3129
|
+
function stylesheetAtRuleIdentities(stylesheet) {
|
|
3130
|
+
if (stylesheet.vueAtRuleIdentities) return stylesheet.vueAtRuleIdentities;
|
|
3131
|
+
if (stylesheet.vueBlocks) return stylesheet.vueBlocks.flatMap((block, index) => atRuleIdentities(`${stylesheet.cssPath}.${index}.at-rules.${block.analysisSource ? "css" : block.syntax}`, block.analysisSource ?? block.content));
|
|
3132
|
+
return stylesheet.analysisSource === void 0 ? atRuleIdentities(`${stylesheet.cssPath}.at-rules.${stylesheet.syntax ?? "css"}`, stylesheet.cssSource) : atRuleIdentities(`${stylesheet.cssPath}.at-rules.css`, stylesheet.analysisSource);
|
|
3133
|
+
}
|
|
3134
|
+
async function finishGroupPlan(context, members, plan) {
|
|
3135
|
+
const { snapshots } = context;
|
|
3136
|
+
const membersByRoot = new Map(members.map((member) => [member.packageRoot, member]));
|
|
3137
|
+
const ownerOf = (path) => {
|
|
3138
|
+
const root = context.pathOwners.get(path);
|
|
3139
|
+
return root === void 0 ? void 0 : membersByRoot.get(root);
|
|
3140
|
+
};
|
|
3141
|
+
for (const member of members) {
|
|
3142
|
+
plan.warnings.push(...member.preparedHtml.warnings);
|
|
3143
|
+
plan.warnings.push(...member.preparedVue.warnings);
|
|
2119
3144
|
}
|
|
2120
|
-
removeMigratedHtmlLinks(plan, preparedHtml);
|
|
2121
|
-
plan.warnings.push(...preparedHtml.warnings);
|
|
2122
|
-
plan.warnings.push(...preparedVue.warnings);
|
|
2123
3145
|
try {
|
|
2124
|
-
for (const file of plan.files.filter((file) => extname(file.path) === ".html"))
|
|
3146
|
+
for (const file of plan.files.filter((file) => extname(file.path) === ".html")) try {
|
|
3147
|
+
parseHtmlSource(file.path, file.source);
|
|
3148
|
+
} catch (error) {
|
|
3149
|
+
return {
|
|
3150
|
+
error,
|
|
3151
|
+
member: ownerOf(file.path),
|
|
3152
|
+
fatal: true
|
|
3153
|
+
};
|
|
3154
|
+
}
|
|
2125
3155
|
for (const file of plan.files.filter((file) => extname(file.path) === ".vue")) {
|
|
2126
|
-
const
|
|
3156
|
+
const owner = ownerOf(file.path);
|
|
3157
|
+
const preparedVue = owner?.preparedVue ?? members[0].preparedVue;
|
|
3158
|
+
const vueCompiler = preparedVue.compiler ?? members[0].preparedVue.compiler;
|
|
2127
3159
|
if (!vueCompiler) throw new Error(`No Vue compiler is available to verify ${file.path}`);
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
const
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
for (const [index, block] of blocks.entries()) {
|
|
2136
|
-
const key = `${block.syntax}\0${block.content}`;
|
|
2137
|
-
const remaining = untouched.get(key) ?? 0;
|
|
2138
|
-
if (remaining > 0) {
|
|
2139
|
-
untouched.set(key, remaining - 1);
|
|
2140
|
-
continue;
|
|
3160
|
+
try {
|
|
3161
|
+
const includeUnscoped = preparedVue.unscopedPaths.has(file.path);
|
|
3162
|
+
const blocks = verifyVueSource(vueCompiler, file.path, file.source, includeUnscoped);
|
|
3163
|
+
const untouched = /* @__PURE__ */ new Map();
|
|
3164
|
+
for (const block of verifyVueSource(vueCompiler, file.path, snapshots.get(file.path) ?? "", includeUnscoped)) {
|
|
3165
|
+
const key = `${block.syntax}\0${block.content}`;
|
|
3166
|
+
untouched.set(key, (untouched.get(key) ?? 0) + 1);
|
|
2141
3167
|
}
|
|
2142
|
-
|
|
2143
|
-
|
|
3168
|
+
for (const [index, block] of blocks.entries()) {
|
|
3169
|
+
const key = `${block.syntax}\0${block.content}`;
|
|
3170
|
+
const remaining = untouched.get(key) ?? 0;
|
|
3171
|
+
if (remaining > 0) {
|
|
3172
|
+
untouched.set(key, remaining - 1);
|
|
3173
|
+
continue;
|
|
3174
|
+
}
|
|
3175
|
+
if (block.syntax === "css") validateCss(block.content);
|
|
3176
|
+
else {
|
|
3177
|
+
const compilerOwner = owner ?? members[0];
|
|
3178
|
+
validateCss((await compileStyleEntry(compilerOwner.styleCompilers, compilerOwner.packageRoot, `${file.path}.${index}.${block.syntax}`, block.content, { virtualEntry: true })).css);
|
|
3179
|
+
}
|
|
3180
|
+
}
|
|
3181
|
+
} catch (error) {
|
|
3182
|
+
return {
|
|
3183
|
+
error,
|
|
3184
|
+
member: owner,
|
|
3185
|
+
fatal: !isMissingStyleCompilerError(error)
|
|
3186
|
+
};
|
|
2144
3187
|
}
|
|
2145
3188
|
}
|
|
2146
|
-
for (const stylesheet of stylesheets.filter((stylesheet) => isPreprocessorPath(stylesheet.cssPath))) {
|
|
3189
|
+
for (const member of members) for (const stylesheet of member.stylesheets.filter((stylesheet) => isPreprocessorPath(stylesheet.cssPath))) {
|
|
2147
3190
|
const changed = plan.files.find((file) => file.path === stylesheet.cssPath);
|
|
2148
3191
|
if (!changed && !plan.deletedFiles.includes(stylesheet.cssPath)) continue;
|
|
2149
3192
|
const source = changed?.source ?? "";
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
let plan = initialPlan;
|
|
2159
|
-
const blockedByStylesheet = /* @__PURE__ */ new Map();
|
|
2160
|
-
while (true) {
|
|
2161
|
-
const failing = invalidCandidates(tailwind, plan.candidates);
|
|
2162
|
-
if (failing.length === 0) break;
|
|
2163
|
-
let progressed = false;
|
|
2164
|
-
for (const rule of plan.rules) {
|
|
2165
|
-
const failed = rule.candidates.filter((candidate) => failing.includes(candidate));
|
|
2166
|
-
if (failed.length === 0) continue;
|
|
2167
|
-
let blocked = blockedByStylesheet.get(rule.stylesheet);
|
|
2168
|
-
if (!blocked) blockedByStylesheet.set(rule.stylesheet, blocked = /* @__PURE__ */ new Map());
|
|
2169
|
-
const key = `${rule.ruleId.start}-${rule.ruleId.end}`;
|
|
2170
|
-
let entry = blocked.get(key);
|
|
2171
|
-
if (!entry) {
|
|
2172
|
-
blocked.set(key, entry = {
|
|
2173
|
-
ruleId: rule.ruleId,
|
|
2174
|
-
authoredSpan: rule.authoredSpan,
|
|
2175
|
-
candidates: /* @__PURE__ */ new Set()
|
|
2176
|
-
});
|
|
2177
|
-
progressed = true;
|
|
3193
|
+
try {
|
|
3194
|
+
validateCss((await compileStyleEntry(member.styleCompilers, member.packageRoot, stylesheet.cssPath, source)).css);
|
|
3195
|
+
} catch (error) {
|
|
3196
|
+
return {
|
|
3197
|
+
error,
|
|
3198
|
+
member,
|
|
3199
|
+
fatal: !isMissingStyleCompilerError(error)
|
|
3200
|
+
};
|
|
2178
3201
|
}
|
|
2179
|
-
for (const candidate of failed) entry.candidates.add(candidate);
|
|
2180
|
-
}
|
|
2181
|
-
if (!progressed) throw new Error(`Tailwind did not generate CSS for candidate: ${failing[0]}`);
|
|
2182
|
-
plan = JSON.parse(planBatchMigration(JSON.stringify({
|
|
2183
|
-
...request,
|
|
2184
|
-
stylesheets: request.stylesheets.map((stylesheet, index) => ({
|
|
2185
|
-
...stylesheet,
|
|
2186
|
-
blockedRules: [...blockedByStylesheet.get(index)?.values() ?? []].map((entry) => entry.ruleId)
|
|
2187
|
-
}))
|
|
2188
|
-
})));
|
|
2189
|
-
}
|
|
2190
|
-
for (const [index, blocked] of blockedByStylesheet) {
|
|
2191
|
-
const cssPath = request.stylesheets[index].cssPath;
|
|
2192
|
-
for (const { authoredSpan, candidates } of blocked.values()) {
|
|
2193
|
-
const failed = [...candidates].sort().map((candidate) => `\`${candidate}\``).join(", ");
|
|
2194
|
-
plan.warnings.push({
|
|
2195
|
-
code: "candidate-compilation-failure",
|
|
2196
|
-
file: cssPath,
|
|
2197
|
-
start: authoredSpan.start,
|
|
2198
|
-
end: authoredSpan.end,
|
|
2199
|
-
message: `Tailwind did not generate CSS for ${failed}, so the rule is retained.`
|
|
2200
|
-
});
|
|
2201
3202
|
}
|
|
3203
|
+
} catch (error) {
|
|
3204
|
+
return {
|
|
3205
|
+
error,
|
|
3206
|
+
fatal: !isMissingStyleCompilerError(error)
|
|
3207
|
+
};
|
|
2202
3208
|
}
|
|
2203
|
-
return plan;
|
|
2204
3209
|
}
|
|
2205
|
-
function
|
|
3210
|
+
function removeGroupHtmlLinks(plan, members) {
|
|
2206
3211
|
const unlinked = new Set(plan.unlinkedFiles);
|
|
2207
3212
|
const linksByFile = /* @__PURE__ */ new Map();
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
const
|
|
2211
|
-
|
|
2212
|
-
|
|
3213
|
+
const originals = /* @__PURE__ */ new Map();
|
|
3214
|
+
for (const member of members) {
|
|
3215
|
+
for (const link of member.preparedHtml.removableLinks) {
|
|
3216
|
+
if (!unlinked.has(link.cssPath)) continue;
|
|
3217
|
+
const links = linksByFile.get(link.filePath) ?? /* @__PURE__ */ new Set();
|
|
3218
|
+
links.add(`${link.href}\0${link.media}`);
|
|
3219
|
+
linksByFile.set(link.filePath, links);
|
|
3220
|
+
}
|
|
3221
|
+
for (const file of member.preparedHtml.files) if (!originals.has(file.path)) originals.set(file.path, file.source);
|
|
2213
3222
|
}
|
|
2214
3223
|
for (const [filePath, removable] of linksByFile) {
|
|
2215
3224
|
const planned = plan.files.find((file) => file.path === filePath);
|
|
2216
|
-
const
|
|
2217
|
-
if (
|
|
2218
|
-
const source = planned?.source ?? original.source;
|
|
3225
|
+
const source = planned?.source ?? originals.get(filePath);
|
|
3226
|
+
if (source === void 0) continue;
|
|
2219
3227
|
const links = parseHtmlSource(filePath, source).links.filter((link) => removable.has(`${link.href}\0${link.media}`)).sort((left, right) => right.tagStart - left.tagStart);
|
|
2220
3228
|
let bytes = Buffer.from(source);
|
|
2221
3229
|
for (const link of links) bytes = Buffer.concat([bytes.subarray(0, link.tagStart), bytes.subarray(link.tagEnd)]);
|