tw-migrate 0.2.0 → 0.4.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 +12 -11
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +149 -0
- package/{index.d.ts → dist/index.d.ts} +22 -13
- package/dist/index.js +2275 -0
- package/package.json +24 -31
- package/bin/tw-migrate.js +0 -51
- package/html.js +0 -191
- package/index.js +0 -2225
- package/native.js +0 -41
- package/style-compiler.js +0 -140
- package/vue.js +0 -541
package/README.md
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
# tw-migrate
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Migrate static React/Next.js, Vue 3 SFC, and HTML stylesheet references to Tailwind v4 utilities.
|
|
4
4
|
|
|
5
5
|
## Usage
|
|
6
6
|
|
|
7
7
|
Run it in the package you want to migrate — no install step:
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
npx tw-migrate
|
|
11
|
-
npx tw-migrate --
|
|
12
|
-
npx tw-migrate path/to/Button.module.scss
|
|
13
|
-
npx tw-migrate path/to/Button.module.scss --
|
|
14
|
-
npx tw-migrate --workspaces
|
|
10
|
+
npx tw-migrate # Migrate the current package
|
|
11
|
+
npx tw-migrate --dry-run # Preview the current package
|
|
12
|
+
npx tw-migrate path/to/Button.module.scss # Migrate one stylesheet
|
|
13
|
+
npx tw-migrate path/to/Button.module.scss --dry-run # Preview one stylesheet
|
|
14
|
+
npx tw-migrate --workspaces # Migrate every package
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
`pnpm dlx tw-migrate` and `yarn dlx tw-migrate` take the same arguments.
|
|
18
18
|
|
|
19
|
-
The CLI
|
|
19
|
+
The CLI applies changes by default; `--dry-run` prints the diff without touching files. Pass `--tailwind-css path/to/globals.css` when the current package has multiple Tailwind entries. `--force` skips package groups that fail discovery or input parsing; plan-integrity and write failures always stop the run.
|
|
20
20
|
|
|
21
21
|
## Run from a clone
|
|
22
22
|
|
|
@@ -25,14 +25,14 @@ The migration planner is a native addon, so a clone needs one build before the C
|
|
|
25
25
|
```bash
|
|
26
26
|
vp install # or: pnpm install
|
|
27
27
|
vp run build:debug # compiles the addon for your platform
|
|
28
|
-
node bin
|
|
28
|
+
node src/bin.ts --help
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
Then use `node bin
|
|
31
|
+
Then use `node src/bin.ts` wherever the commands above use `npx tw-migrate`. The CLI always migrates the package it is run from, so to migrate a different project, run the built entrypoint by absolute path from that project's directory:
|
|
32
32
|
|
|
33
33
|
```bash
|
|
34
34
|
cd ../some-app
|
|
35
|
-
node /path/to/tw-migrate/bin
|
|
35
|
+
node /path/to/tw-migrate/src/bin.ts
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
## Current support
|
|
@@ -47,6 +47,7 @@ node /path/to/tw-migrate/bin/tw-migrate.js --write
|
|
|
47
47
|
- Vue `<style module>` blocks migrate against direct `:class="$style.x"` member bindings; any other `$style`/`useCssModule` appearance or a named module retains the block
|
|
48
48
|
- direct CSS Module members, static template literals, and static expression literals
|
|
49
49
|
- global `className` and `id` literals
|
|
50
|
+
- React/Next.js `className` logical (`&&`, `||`, `??`) and conditional (`?:`) expressions: string and direct CSS Module member result leaves migrate without evaluating their conditions, `null`/`undefined`/`false`/empty-string results are warning-free, and unsupported result leaves retain with a warning while supported siblings still migrate
|
|
50
51
|
- multi-compound CSS Module selectors whose element relationships are proven from the JSX graph
|
|
51
52
|
- common state pseudo-classes, global arbitrary descendant variants, and conditional at-rules (`@media`, `@supports`, `@container`, `@starting-style`)
|
|
52
53
|
- the tier-1 property mapping families with shorthand/longhand normalization
|
|
@@ -68,7 +69,7 @@ Everything outside this subset is retained and reported with one of the warning
|
|
|
68
69
|
| `computed-css-module-reference` | A computed CSS Module access cannot be verified, so the module is retained. |
|
|
69
70
|
| `cross-package-stylesheet-link` | A linked stylesheet is owned by another package, so it is not analyzed outside workspace mode. |
|
|
70
71
|
| `css-module-composes` | The rule uses or is targeted by `composes`, so it is retained. |
|
|
71
|
-
| `dynamic-class-name` | A `className` value is dynamic, so
|
|
72
|
+
| `dynamic-class-name` | A `className` value or expression result is dynamic, so that class site cannot be migrated. |
|
|
72
73
|
| `dynamic-html-attribute` | An HTML attribute is not a safely writable quoted literal, so the element cannot be migrated. |
|
|
73
74
|
| `dynamic-template-class` | A dynamic class binding makes a Vue template's class set unprovable, so its scoped rules are retained. |
|
|
74
75
|
| `existing-tailwind-conflict` | A generated utility may conflict with a Tailwind class already on the element. |
|
package/dist/bin.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
package/dist/bin.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { styleText } from "node:util";
|
|
3
|
+
//#endregion
|
|
4
|
+
//#region src/bin.ts
|
|
5
|
+
const { version } = {
|
|
6
|
+
name: "tw-migrate",
|
|
7
|
+
version: "0.4.0",
|
|
8
|
+
description: "Preview and migrate React, Vue, and HTML CSS to Tailwind utilities",
|
|
9
|
+
keywords: [
|
|
10
|
+
"codemod",
|
|
11
|
+
"css",
|
|
12
|
+
"css-modules",
|
|
13
|
+
"migration",
|
|
14
|
+
"napi-rs",
|
|
15
|
+
"react",
|
|
16
|
+
"tailwind",
|
|
17
|
+
"tailwindcss",
|
|
18
|
+
"vue"
|
|
19
|
+
],
|
|
20
|
+
homepage: "https://github.com/jong-kyung/tw-migrate#readme",
|
|
21
|
+
bugs: { "url": "https://github.com/jong-kyung/tw-migrate/issues" },
|
|
22
|
+
license: "MIT",
|
|
23
|
+
author: "JongKyung Lee",
|
|
24
|
+
repository: {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/jong-kyung/tw-migrate.git"
|
|
27
|
+
},
|
|
28
|
+
bin: { "tw-migrate": "dist/bin.js" },
|
|
29
|
+
files: ["dist"],
|
|
30
|
+
type: "module",
|
|
31
|
+
main: "dist/index.js",
|
|
32
|
+
types: "dist/index.d.ts",
|
|
33
|
+
scripts: {
|
|
34
|
+
"build": "vp pack && napi build --manifest-path crates/tw_migrate/Cargo.toml --package-json-path package.json --output-dir . --platform --release --no-js --dts binding.d.ts",
|
|
35
|
+
"build:debug": "napi build --manifest-path crates/tw_migrate/Cargo.toml --package-json-path package.json --output-dir . --platform --no-js --dts binding.d.ts",
|
|
36
|
+
"artifacts": "node -e \"const fs=require('node:fs'); const files=fs.readdirSync('.').filter((file)=>file.startsWith('tw-migrate.')&&file.endsWith('.node')); if(files.length===0) throw new Error('no tw-migrate.*.node artifact in the package root; run build first'); for(const file of files) fs.copyFileSync(file, 'npm/'+file.slice('tw-migrate.'.length, -'.node'.length)+'/'+file)\"",
|
|
37
|
+
"test": "cargo test && pnpm build:debug && vp test",
|
|
38
|
+
"test:ecosystem": "node ecosystem-ci/run.ts",
|
|
39
|
+
"typecheck": "tsc --noEmit",
|
|
40
|
+
"snapshots:prepare": "node -e \"const fs=require('node:fs'); for(const dir of fs.readdirSync('npm')) for(const file of fs.readdirSync('npm/'+dir)) if(file.endsWith('.node')) fs.rmSync('npm/'+dir+'/'+file)\" && pnpm build && pnpm artifacts",
|
|
41
|
+
"test:snapshots": "pnpm snapshots:prepare && cargo test -p tw-migrate-snapshots",
|
|
42
|
+
"prepare": "vp config"
|
|
43
|
+
},
|
|
44
|
+
dependencies: { "parse5": "8.0.1" },
|
|
45
|
+
devDependencies: {
|
|
46
|
+
"@napi-rs/cli": "catalog:",
|
|
47
|
+
"@types/node": "catalog:",
|
|
48
|
+
"@vitest/browser-playwright": "catalog:",
|
|
49
|
+
"less": "catalog:",
|
|
50
|
+
"playwright": "catalog:",
|
|
51
|
+
"sass": "catalog:",
|
|
52
|
+
"tailwindcss": "catalog:",
|
|
53
|
+
"typescript": "catalog:",
|
|
54
|
+
"verdaccio": "catalog:",
|
|
55
|
+
"vite": "catalog:",
|
|
56
|
+
"vite-plus": "catalog:",
|
|
57
|
+
"vitest": "catalog:",
|
|
58
|
+
"vue": "catalog:"
|
|
59
|
+
},
|
|
60
|
+
optionalDependencies: {
|
|
61
|
+
"tw-migrate-darwin-arm64": "workspace:*",
|
|
62
|
+
"tw-migrate-darwin-x64": "workspace:*",
|
|
63
|
+
"tw-migrate-linux-arm64-gnu": "workspace:*",
|
|
64
|
+
"tw-migrate-linux-x64-gnu": "workspace:*",
|
|
65
|
+
"tw-migrate-win32-x64-msvc": "workspace:*"
|
|
66
|
+
},
|
|
67
|
+
napi: {
|
|
68
|
+
"binaryName": "tw-migrate",
|
|
69
|
+
"targets": [
|
|
70
|
+
"aarch64-apple-darwin",
|
|
71
|
+
"x86_64-apple-darwin",
|
|
72
|
+
"aarch64-unknown-linux-gnu",
|
|
73
|
+
"x86_64-unknown-linux-gnu",
|
|
74
|
+
"x86_64-pc-windows-msvc"
|
|
75
|
+
]
|
|
76
|
+
},
|
|
77
|
+
engines: { "node": ">=22.23.2" },
|
|
78
|
+
packageManager: "pnpm@11.20.0"
|
|
79
|
+
};
|
|
80
|
+
const usage = "Usage: tw-migrate [style-file] [options]";
|
|
81
|
+
const help = `${usage}
|
|
82
|
+
|
|
83
|
+
Arguments:
|
|
84
|
+
[style-file] Migrate one stylesheet instead of the current package.
|
|
85
|
+
Cannot be used with --workspaces.
|
|
86
|
+
|
|
87
|
+
Options:
|
|
88
|
+
--tailwind-css <entry.css> Use this Tailwind CSS entry when the current package
|
|
89
|
+
has multiple entries. Cannot be used with --workspaces.
|
|
90
|
+
--workspaces Migrate every package in the workspace.
|
|
91
|
+
--force Skip packages with recoverable discovery or input errors.
|
|
92
|
+
--dry-run Preview changes without applying them.
|
|
93
|
+
-h, --help Show this help message.
|
|
94
|
+
-v, --version Show the version number.
|
|
95
|
+
|
|
96
|
+
Examples:
|
|
97
|
+
tw-migrate
|
|
98
|
+
tw-migrate --dry-run
|
|
99
|
+
tw-migrate path/to/Button.module.scss
|
|
100
|
+
tw-migrate --workspaces`;
|
|
101
|
+
async function main() {
|
|
102
|
+
const args = process.argv.slice(2);
|
|
103
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
104
|
+
console.log(help);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (args.includes("--version") || args.includes("-v")) {
|
|
108
|
+
console.log(`tw-migrate ${version}`);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
let styleFile;
|
|
112
|
+
let tailwindCss;
|
|
113
|
+
let write = true;
|
|
114
|
+
let force = false;
|
|
115
|
+
let workspaces = false;
|
|
116
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
117
|
+
const argument = args[index];
|
|
118
|
+
if (argument === "--dry-run") write = false;
|
|
119
|
+
else if (argument === "--force") force = true;
|
|
120
|
+
else if (argument === "--workspaces") workspaces = true;
|
|
121
|
+
else if (argument === "--tailwind-css") {
|
|
122
|
+
tailwindCss = args[++index];
|
|
123
|
+
if (!tailwindCss) throw new Error(`${usage}\n--tailwind-css requires a path.`);
|
|
124
|
+
} else if (argument.startsWith("-")) throw new Error(`Unknown option: ${argument}`);
|
|
125
|
+
else if (!styleFile) styleFile = argument;
|
|
126
|
+
else throw new Error(`Unexpected argument: ${argument}`);
|
|
127
|
+
}
|
|
128
|
+
const { migrate } = await import("./index.js");
|
|
129
|
+
const report = await migrate({
|
|
130
|
+
styleFile,
|
|
131
|
+
tailwindCss,
|
|
132
|
+
write,
|
|
133
|
+
force,
|
|
134
|
+
workspaces
|
|
135
|
+
});
|
|
136
|
+
if (report.diff) process.stdout.write(report.diff);
|
|
137
|
+
for (const warning of report.warnings) {
|
|
138
|
+
const location = warning.line ? `${warning.file}:${warning.line}:${warning.column}-${warning.endLine}:${warning.endColumn}` : warning.file;
|
|
139
|
+
console.warn(styleText("yellow", `warning[${warning.code}] ${location} ${warning.message}`, { stream: process.stderr }));
|
|
140
|
+
}
|
|
141
|
+
for (const failure of report.failures) console.warn(styleText("yellow", `skipped[${failure.package}] ${failure.message}`, { stream: process.stderr }));
|
|
142
|
+
console.log(`${write ? "Applied" : "Previewed"} ${report.changedFiles.length} file(s); ${report.convertedRules} rule(s) converted, ${report.retainedRules} retained.`);
|
|
143
|
+
}
|
|
144
|
+
main().catch((error) => {
|
|
145
|
+
console.error(styleText("red", `tw-migrate: ${error.message}`, { stream: process.stderr }));
|
|
146
|
+
process.exitCode = 1;
|
|
147
|
+
});
|
|
148
|
+
//#endregion
|
|
149
|
+
export {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
//#region src/types.d.ts
|
|
2
|
+
interface MigrateOptions {
|
|
2
3
|
styleFile?: string;
|
|
3
4
|
cwd?: string;
|
|
4
5
|
write?: boolean;
|
|
@@ -6,33 +7,34 @@ export interface MigrateOptions {
|
|
|
6
7
|
workspaces?: boolean;
|
|
7
8
|
force?: boolean;
|
|
8
9
|
}
|
|
9
|
-
|
|
10
|
-
export interface MigrationWarning {
|
|
10
|
+
interface MigrationWarning {
|
|
11
11
|
code: string;
|
|
12
12
|
file: string;
|
|
13
13
|
/** Byte offsets into the authored file, or (0, 0) when no unique mapping exists. */
|
|
14
14
|
start: number;
|
|
15
15
|
end: number;
|
|
16
|
+
/** 1-based UTF-16 positions, omitted when no unique authored mapping exists. */
|
|
17
|
+
line?: number;
|
|
18
|
+
column?: number;
|
|
19
|
+
endLine?: number;
|
|
20
|
+
endColumn?: number;
|
|
16
21
|
message: string;
|
|
17
22
|
}
|
|
18
|
-
|
|
19
|
-
export interface RuleReport {
|
|
23
|
+
interface RuleReport {
|
|
20
24
|
selector: string;
|
|
21
25
|
status: "converted" | "retained";
|
|
22
26
|
candidates: string[];
|
|
23
27
|
file: string;
|
|
24
28
|
/** Rule span in the analysis source (compiled CSS for preprocessor stylesheets). */
|
|
25
|
-
ruleId:
|
|
29
|
+
ruleId: RuleSpan;
|
|
26
30
|
/** Rule span in the authored file, or (0, 0) when no unique mapping exists. */
|
|
27
|
-
authoredSpan:
|
|
31
|
+
authoredSpan: RuleSpan;
|
|
28
32
|
}
|
|
29
|
-
|
|
30
|
-
export interface MigrationFailure {
|
|
33
|
+
interface MigrationFailure {
|
|
31
34
|
package: string;
|
|
32
35
|
message: string;
|
|
33
36
|
}
|
|
34
|
-
|
|
35
|
-
export interface MigrationReport {
|
|
37
|
+
interface MigrationReport {
|
|
36
38
|
changedFiles: string[];
|
|
37
39
|
diff: string;
|
|
38
40
|
convertedRules: number;
|
|
@@ -42,5 +44,12 @@ export interface MigrationReport {
|
|
|
42
44
|
warnings: MigrationWarning[];
|
|
43
45
|
failures: MigrationFailure[];
|
|
44
46
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
+
interface RuleSpan {
|
|
48
|
+
start: number;
|
|
49
|
+
end: number;
|
|
50
|
+
}
|
|
51
|
+
//#endregion
|
|
52
|
+
//#region src/index.d.ts
|
|
53
|
+
declare function migrate(options?: MigrateOptions): Promise<MigrationReport>;
|
|
54
|
+
//#endregion
|
|
55
|
+
export { type MigrateOptions, type MigrationFailure, type MigrationReport, type MigrationWarning, type RuleReport, migrate };
|