tw-migrate 0.8.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +1 -1
- package/dist/index.js +70 -37
- package/package.json +6 -6
package/dist/bin.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import { promisify } from "node:util";
|
|
3
|
-
import { chmod, lstat, readFile, readdir, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
4
3
|
import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
5
4
|
import * as z from "zod/mini";
|
|
6
5
|
import { FILE_HEADERS_ONLY, createTwoFilesPatch } from "diff";
|
|
7
6
|
import { execFile } from "node:child_process";
|
|
7
|
+
import { chmod, readFile, readdir, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
8
8
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
9
9
|
import { parse } from "parse5";
|
|
10
10
|
//#region src/util/diff.ts
|
|
@@ -278,12 +278,6 @@ function isStylesheetModule(path) {
|
|
|
278
278
|
function isProjectInput(workspaceRoot, path) {
|
|
279
279
|
return isWithin(workspaceRoot, path) && !relative(workspaceRoot, path).split(/[\\/]/).includes("node_modules");
|
|
280
280
|
}
|
|
281
|
-
async function rejectSymlinkTarget(path, root) {
|
|
282
|
-
for (let current = path; isWithin(root, current); current = dirname(current)) {
|
|
283
|
-
if ((await lstat(current)).isSymbolicLink()) throw new Error(`Refusing to migrate a symbolic-link target: ${path}`);
|
|
284
|
-
if (current === root) break;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
281
|
async function* readInBatches(paths) {
|
|
288
282
|
for (let offset = 0; offset < paths.length; offset += 32) {
|
|
289
283
|
const batch = paths.slice(offset, offset + 32);
|
|
@@ -393,7 +387,6 @@ async function resolveScope(options) {
|
|
|
393
387
|
if (explicitStyle && !isStylesheetPath(explicitStyle) && extname(explicitStyle) !== ".vue") throw new TypeError("Only .css, .scss, .sass, .less, and .vue files can be migrated");
|
|
394
388
|
if (configuredEntry && extname(configuredEntry) !== ".css") throw new TypeError("The Tailwind CSS entry must be a .css file");
|
|
395
389
|
if (configuredEntry && !(await stat(configuredEntry)).isFile()) throw new TypeError("The Tailwind CSS entry must be a file");
|
|
396
|
-
for (const path of [explicitStyle, configuredEntry]) if (path) await rejectSymlinkTarget(path, currentPackage);
|
|
397
390
|
if (explicitStyle && !isWithin(currentPackage, explicitStyle)) throw new TypeError("The selected stylesheet must belong to the current package");
|
|
398
391
|
for (const path of [explicitStyle, configuredEntry]) {
|
|
399
392
|
if (path && !allPaths.includes(path)) allPaths.push(path);
|
|
@@ -761,12 +754,17 @@ async function loadTailwind(packageRoot, tailwindCss, snapshots, workspaceRoot)
|
|
|
761
754
|
const packageJson = tailwindPackageSchema.parse(JSON.parse(await readFile(packagePath, "utf8")));
|
|
762
755
|
if (!packageJson.version.startsWith("4.")) throw new Error(`Tailwind v4 is required; found ${packageJson.version}.`);
|
|
763
756
|
const { __unstable__loadDesignSystem: loadDesignSystem } = await loadProjectModule(packageRoot, "tailwindcss", "Tailwind v4 must be installed in the target project.");
|
|
764
|
-
const
|
|
757
|
+
const inputPaths = /* @__PURE__ */ new Set();
|
|
758
|
+
const readInput = (path) => {
|
|
759
|
+
inputPaths.add(path);
|
|
760
|
+
return snapshotFile(snapshots, path);
|
|
761
|
+
};
|
|
762
|
+
const css = await readInput(tailwindCss);
|
|
765
763
|
const base = dirname(tailwindCss);
|
|
766
764
|
const tailwindRoot = dirname(packagePath);
|
|
767
765
|
const loadModule = async (id, moduleBase) => {
|
|
768
766
|
const path = createRequire(join(moduleBase, "package.json")).resolve(id);
|
|
769
|
-
if (isProjectInput(workspaceRoot, path)) await
|
|
767
|
+
if (isProjectInput(workspaceRoot, path)) await readInput(path);
|
|
770
768
|
const imported = await import(pathToFileURL(path).href);
|
|
771
769
|
return {
|
|
772
770
|
path,
|
|
@@ -787,7 +785,7 @@ async function loadTailwind(packageRoot, tailwindCss, snapshots, workspaceRoot)
|
|
|
787
785
|
} else if (id.startsWith(".") || isAbsolute(id)) path = resolve(sheetBase, id);
|
|
788
786
|
else path = projectRequire.resolve(id);
|
|
789
787
|
return {
|
|
790
|
-
content: isProjectInput(workspaceRoot, path) ? await
|
|
788
|
+
content: isProjectInput(workspaceRoot, path) ? await readInput(path) : await readFile(path, "utf8"),
|
|
791
789
|
base: dirname(path)
|
|
792
790
|
};
|
|
793
791
|
};
|
|
@@ -819,6 +817,7 @@ async function loadTailwind(packageRoot, tailwindCss, snapshots, workspaceRoot)
|
|
|
819
817
|
themeTokens,
|
|
820
818
|
referenceTokens,
|
|
821
819
|
graphSources,
|
|
820
|
+
inputPaths,
|
|
822
821
|
loadWith
|
|
823
822
|
};
|
|
824
823
|
}
|
|
@@ -1355,15 +1354,26 @@ function canonicalCandidate(system, candidate) {
|
|
|
1355
1354
|
cache.set(candidate, canonical);
|
|
1356
1355
|
return canonical;
|
|
1357
1356
|
}
|
|
1358
|
-
function spellingReservations(styleSources, resolvedExtras, resolvedImports = /* @__PURE__ */ new Set()) {
|
|
1357
|
+
function spellingReservations(styleSources, resolvedExtras, resolvedImports = /* @__PURE__ */ new Set(), cache) {
|
|
1358
|
+
const key = JSON.stringify([...resolvedImports].sort());
|
|
1359
|
+
let base = cache?.get(key);
|
|
1360
|
+
if (base === void 0) {
|
|
1361
|
+
base = {
|
|
1362
|
+
names: /* @__PURE__ */ new Set(),
|
|
1363
|
+
prefixes: /* @__PURE__ */ new Set(),
|
|
1364
|
+
unbounded: false,
|
|
1365
|
+
properties: /* @__PURE__ */ new Set(),
|
|
1366
|
+
propertiesUnbounded: false
|
|
1367
|
+
};
|
|
1368
|
+
for (const [path, source] of styleSources) scanStylesheetReservations(path, source, styleSources, base, resolvedImports);
|
|
1369
|
+
cache?.set(key, base);
|
|
1370
|
+
}
|
|
1359
1371
|
const reservations = {
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
properties:
|
|
1364
|
-
propertiesUnbounded: false
|
|
1372
|
+
...base,
|
|
1373
|
+
names: new Set(base.names),
|
|
1374
|
+
prefixes: new Set(base.prefixes),
|
|
1375
|
+
properties: new Set(base.properties)
|
|
1365
1376
|
};
|
|
1366
|
-
for (const [path, source] of styleSources) scanStylesheetReservations(path, source, styleSources, reservations, resolvedImports);
|
|
1367
1377
|
for (const [path, source] of resolvedExtras) try {
|
|
1368
1378
|
const analysis = stylesheetAnalysis(path, source);
|
|
1369
1379
|
for (const name of analysis.classNames) reservations.names.add(name);
|
|
@@ -1434,25 +1444,32 @@ function sameShape(left, right) {
|
|
|
1434
1444
|
}
|
|
1435
1445
|
function acceptedCandidateAliases(system, probes, reservations) {
|
|
1436
1446
|
const aliases = {};
|
|
1437
|
-
if (reservations.unbounded) return aliases;
|
|
1447
|
+
if (reservations.unbounded || typeof system.canonicalizeCandidates !== "function") return aliases;
|
|
1438
1448
|
const prefix = system.theme.prefix;
|
|
1439
1449
|
for (const probe of new Set(probes)) {
|
|
1450
|
+
const compiledProbe = system.candidatesToCss([probe])[0];
|
|
1451
|
+
if (compiledProbe == null) continue;
|
|
1452
|
+
let probeShape;
|
|
1453
|
+
try {
|
|
1454
|
+
probeShape = compiledShape(compiledProbe);
|
|
1455
|
+
} catch {
|
|
1456
|
+
continue;
|
|
1457
|
+
}
|
|
1458
|
+
if (probeShape.referencedProperties.length > 0) continue;
|
|
1440
1459
|
const canonical = canonicalCandidate(system, probe);
|
|
1441
1460
|
if (canonical === null) continue;
|
|
1442
1461
|
const unprefixed = prefix !== null && canonical.startsWith(`${prefix}:`) ? canonical.slice(prefix.length + 1) : null;
|
|
1443
1462
|
const segment = utilitySegment(canonical).replace(/!$/, "");
|
|
1444
1463
|
if (segment.includes("[") || reservedSpelling(reservations, canonical) || unprefixed !== null && reservedSpelling(reservations, unprefixed) || reservedSpelling(reservations, segment)) continue;
|
|
1445
|
-
const
|
|
1446
|
-
if (
|
|
1447
|
-
let probeShape;
|
|
1464
|
+
const compiledCanonical = system.candidatesToCss([canonical])[0];
|
|
1465
|
+
if (compiledCanonical == null) continue;
|
|
1448
1466
|
let canonicalShape;
|
|
1449
1467
|
try {
|
|
1450
|
-
probeShape = compiledShape(compiledProbe);
|
|
1451
1468
|
canonicalShape = compiledShape(compiledCanonical);
|
|
1452
1469
|
} catch {
|
|
1453
1470
|
continue;
|
|
1454
1471
|
}
|
|
1455
|
-
if (canonicalShape.referencedProperties.length > 0 ||
|
|
1472
|
+
if (canonicalShape.referencedProperties.length > 0 || !sameShape(probeShape.declarations, canonicalShape.declarations)) continue;
|
|
1456
1473
|
aliases[probe] = canonical;
|
|
1457
1474
|
}
|
|
1458
1475
|
return aliases;
|
|
@@ -1745,7 +1762,7 @@ function scannedVariantReservations(sources) {
|
|
|
1745
1762
|
*/
|
|
1746
1763
|
function collectMemberMediaConditions(stylesheets, tailwind) {
|
|
1747
1764
|
return JSON.parse(collectMediaConditions(JSON.stringify({
|
|
1748
|
-
stylesheets,
|
|
1765
|
+
stylesheets: stylesheets.map(({ vueShadowCss: _shadowCss, vueShadowModuleCss: _shadowModuleCss, ...stylesheet }) => stylesheet),
|
|
1749
1766
|
themeTokens: tailwind.themeTokens,
|
|
1750
1767
|
tailwindSources: tailwind.graphSources
|
|
1751
1768
|
})));
|
|
@@ -2691,7 +2708,6 @@ async function preparePackageVue({ packageRoot, styleCompilers, sourceFiles, sty
|
|
|
2691
2708
|
}
|
|
2692
2709
|
}
|
|
2693
2710
|
for (const file of selected) {
|
|
2694
|
-
await rejectSymlinkTarget(file.path, packageRoot);
|
|
2695
2711
|
const analysis = analysisOf(file.path);
|
|
2696
2712
|
warnings.push(...analysis.warnings);
|
|
2697
2713
|
if (analysis.retained) continue;
|
|
@@ -2784,7 +2800,6 @@ async function preparePackageVue({ packageRoot, styleCompilers, sourceFiles, sty
|
|
|
2784
2800
|
if (analysis.retained) continue;
|
|
2785
2801
|
const elements = elementsByFile.get(file.path) ?? [];
|
|
2786
2802
|
const contextPaths = [...new Set(elements.flatMap((element) => element.cssPaths))];
|
|
2787
|
-
if (contextPaths.length > 0) await rejectSymlinkTarget(file.path, packageRoot);
|
|
2788
2803
|
files.set(file.path, {
|
|
2789
2804
|
...file,
|
|
2790
2805
|
...contextPaths.length > 0 ? {
|
|
@@ -2956,6 +2971,8 @@ async function migrate(options = {}) {
|
|
|
2956
2971
|
styleDependents: indexStylesheetDependents(styleSources),
|
|
2957
2972
|
vueStyleRanges,
|
|
2958
2973
|
entryCatalog: tailwindEntryCatalog(styleSources, scope.pathOwners),
|
|
2974
|
+
loadedTailwind: /* @__PURE__ */ new Map(),
|
|
2975
|
+
stylesheetReservationCache: /* @__PURE__ */ new Map(),
|
|
2959
2976
|
ignoredPaths: await scannerIgnoredPaths(workspaceRoot, sourceFiles.map((file) => file.path)),
|
|
2960
2977
|
fontAllocations: /* @__PURE__ */ new Map()
|
|
2961
2978
|
};
|
|
@@ -3194,7 +3211,12 @@ async function preparePackage(context, packageRoot) {
|
|
|
3194
3211
|
if (provenTargets.length === 0 && provenVueStylesheets.length === 0) return vueWarningsOnlyResult(preparedVue, proofWarnings);
|
|
3195
3212
|
let tailwind;
|
|
3196
3213
|
try {
|
|
3197
|
-
|
|
3214
|
+
const key = `${entryOwner}\0${tailwindPath}`;
|
|
3215
|
+
tailwind = context.loadedTailwind.get(key);
|
|
3216
|
+
if (tailwind === void 0) {
|
|
3217
|
+
tailwind = await loadTailwind(entryOwner, tailwindPath, snapshots, workspaceRoot);
|
|
3218
|
+
context.loadedTailwind.set(key, tailwind);
|
|
3219
|
+
} else for (const path of tailwind.inputPaths) await snapshotFile(snapshots, path);
|
|
3198
3220
|
} catch (error) {
|
|
3199
3221
|
return recover(error, isIntegrityError(error));
|
|
3200
3222
|
}
|
|
@@ -3210,7 +3232,6 @@ async function preparePackage(context, packageRoot) {
|
|
|
3210
3232
|
stylesheets = [];
|
|
3211
3233
|
const compilerDependents = /* @__PURE__ */ new Map();
|
|
3212
3234
|
for (const stylePath of provenTargets.sort()) {
|
|
3213
|
-
await rejectSymlinkTarget(stylePath, packageRoot);
|
|
3214
3235
|
const isPartial = isSassPath(stylePath) && basename(stylePath).startsWith("_");
|
|
3215
3236
|
const stylesheet = {
|
|
3216
3237
|
cssPath: stylePath,
|
|
@@ -3271,7 +3292,7 @@ async function planPreparedGroup(context, members) {
|
|
|
3271
3292
|
return leftPath < rightPath ? -1 : leftPath > rightPath ? 1 : 0;
|
|
3272
3293
|
});
|
|
3273
3294
|
const entry = active[0].tailwind;
|
|
3274
|
-
const reservations = spellingReservations(context.styleSources, entry.graphSources.map((graphSource) => [`${graphSource.path}.graph.css`, graphSource.source]), new Set(entry.graphSources.map((graphSource) => graphSource.path).filter((path) => path.includes("\0"))));
|
|
3295
|
+
const reservations = spellingReservations(context.styleSources, entry.graphSources.map((graphSource) => [`${graphSource.path}.graph.css`, graphSource.source]), new Set(entry.graphSources.map((graphSource) => graphSource.path).filter((path) => path.includes("\0"))), context.stylesheetReservationCache);
|
|
3275
3296
|
const groupFiles = new Map(active.flatMap((member) => member.files.map((file) => [file.path, file])));
|
|
3276
3297
|
const mentionedSegments = /* @__PURE__ */ new Set();
|
|
3277
3298
|
const mentionedSources = [...[...groupFiles.values()].map((file) => file.source), ...entry.graphSources.map((graphSource) => graphSource.source)];
|
|
@@ -3478,7 +3499,7 @@ async function planPreparedGroup(context, members) {
|
|
|
3478
3499
|
} });
|
|
3479
3500
|
active = active.filter((remaining) => remaining.stylesheets.length > 0);
|
|
3480
3501
|
if (active.length === 0) return results;
|
|
3481
|
-
const groupWritable =
|
|
3502
|
+
const groupWritable = targetable.has(entry.path) && isProjectInput(workspaceRoot, entry.path);
|
|
3482
3503
|
const mediaWarnings = [];
|
|
3483
3504
|
let extraction;
|
|
3484
3505
|
const collections = [];
|
|
@@ -3580,13 +3601,29 @@ async function planPreparedGroup(context, members) {
|
|
|
3580
3601
|
htmlStylesheets: merged
|
|
3581
3602
|
};
|
|
3582
3603
|
}).sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0);
|
|
3604
|
+
const shadowSourceIds = /* @__PURE__ */ new Map();
|
|
3605
|
+
const shadowRefs = (sources) => sources?.map((source) => {
|
|
3606
|
+
let id = shadowSourceIds.get(source);
|
|
3607
|
+
if (id === void 0) {
|
|
3608
|
+
id = shadowSourceIds.size;
|
|
3609
|
+
shadowSourceIds.set(source, id);
|
|
3610
|
+
}
|
|
3611
|
+
return id;
|
|
3612
|
+
});
|
|
3613
|
+
const pooledSheets = sheets.map(({ stylesheet }) => ({
|
|
3614
|
+
...stylesheet,
|
|
3615
|
+
vueShadowCss: shadowRefs(stylesheet.vueShadowCss),
|
|
3616
|
+
vueShadowModuleCss: shadowRefs(stylesheet.vueShadowModuleCss)
|
|
3617
|
+
}));
|
|
3618
|
+
const vueShadowSources = [...shadowSourceIds.keys()];
|
|
3583
3619
|
planning: for (;;) {
|
|
3584
3620
|
const request = {
|
|
3585
|
-
|
|
3621
|
+
vueShadowSources,
|
|
3622
|
+
stylesheets: sheets.map(({ member }, index) => {
|
|
3586
3623
|
const rules = blocked.get(index);
|
|
3587
3624
|
const movesOff = member.sharedProofs !== void 0 || movesDisabled.has(index);
|
|
3588
3625
|
return {
|
|
3589
|
-
...
|
|
3626
|
+
...pooledSheets[index],
|
|
3590
3627
|
...rules ? { blockedRules: [...rules.values()].map((blockedRule) => blockedRule.ruleId) } : {},
|
|
3591
3628
|
...movesOff ? { globalAtRuleMoves: false } : {}
|
|
3592
3629
|
};
|
|
@@ -3775,10 +3812,6 @@ function accumulateBlockedRules(blocked, plan, failing) {
|
|
|
3775
3812
|
}
|
|
3776
3813
|
return progressed;
|
|
3777
3814
|
}
|
|
3778
|
-
async function entryUnsafe(entryPath, workspaceRoot, targetable) {
|
|
3779
|
-
if (!targetable.has(entryPath) || !isProjectInput(workspaceRoot, entryPath)) return true;
|
|
3780
|
-
return (await lstat(entryPath)).isSymbolicLink();
|
|
3781
|
-
}
|
|
3782
3815
|
function atRuleIdentities(key, css) {
|
|
3783
3816
|
return css.trim() === "" ? [] : stylesheetAnalysis(key, css).globalAtRuleIdentities;
|
|
3784
3817
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tw-migrate",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "Preview and migrate React, Vue, and HTML CSS to Tailwind utilities",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"codemod",
|
|
@@ -66,11 +66,11 @@
|
|
|
66
66
|
"vue": "catalog:"
|
|
67
67
|
},
|
|
68
68
|
"optionalDependencies": {
|
|
69
|
-
"tw-migrate-darwin-arm64": "0.8.
|
|
70
|
-
"tw-migrate-darwin-x64": "0.8.
|
|
71
|
-
"tw-migrate-linux-arm64-gnu": "0.8.
|
|
72
|
-
"tw-migrate-linux-x64-gnu": "0.8.
|
|
73
|
-
"tw-migrate-win32-x64-msvc": "0.8.
|
|
69
|
+
"tw-migrate-darwin-arm64": "0.8.1",
|
|
70
|
+
"tw-migrate-darwin-x64": "0.8.1",
|
|
71
|
+
"tw-migrate-linux-arm64-gnu": "0.8.1",
|
|
72
|
+
"tw-migrate-linux-x64-gnu": "0.8.1",
|
|
73
|
+
"tw-migrate-win32-x64-msvc": "0.8.1"
|
|
74
74
|
},
|
|
75
75
|
"napi": {
|
|
76
76
|
"binaryName": "tw-migrate",
|