@decocms/blocks-cli 7.21.0 → 7.21.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/package.json +2 -2
- package/scripts/migrate/analyzers/tailwind-config.test.ts +149 -0
- package/scripts/migrate/analyzers/tailwind-config.ts +283 -0
- package/scripts/migrate/css-compile-check.test.ts +115 -0
- package/scripts/migrate/css-compile-check.ts +137 -0
- package/scripts/migrate/phase-analyze.ts +13 -0
- package/scripts/migrate/phase-cleanup.test.ts +1 -0
- package/scripts/migrate/phase-compile.test.ts +48 -0
- package/scripts/migrate/phase-compile.ts +38 -0
- package/scripts/migrate/phase-report.test.ts +103 -0
- package/scripts/migrate/phase-report.ts +64 -4
- package/scripts/migrate/phase-verify.test.ts +1 -0
- package/scripts/migrate/templates/app-css.test.ts +150 -0
- package/scripts/migrate/templates/app-css.ts +142 -52
- package/scripts/migrate/templates/hooks.test.ts +1 -0
- package/scripts/migrate/templates/package-json.ts +5 -0
- package/scripts/migrate/transforms/color-oklch.test.ts +83 -0
- package/scripts/migrate/transforms/color-oklch.ts +122 -0
- package/scripts/migrate/transforms/css.test.ts +109 -0
- package/scripts/migrate/transforms/css.ts +186 -0
- package/scripts/migrate/transforms/tailwind-renames.test.ts +37 -0
- package/scripts/migrate/transforms/tailwind-renames.ts +224 -0
- package/scripts/migrate/transforms/tailwind.test.ts +74 -0
- package/scripts/migrate/transforms/tailwind.ts +27 -123
- package/scripts/migrate/types.ts +18 -0
- package/scripts/tailwind-lint.ts +9 -54
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import type { TransformResult } from "../types";
|
|
2
|
+
import {
|
|
3
|
+
PX_TO_SPACING,
|
|
4
|
+
TEXT_SIZE_MAP,
|
|
5
|
+
detectDaisyUiV5StructuralIssues,
|
|
6
|
+
detectLogicalPropertyConflict,
|
|
7
|
+
renameToken,
|
|
8
|
+
} from "./tailwind-renames";
|
|
2
9
|
|
|
3
10
|
/**
|
|
4
11
|
* Tailwind v3 → v4 class migration transform.
|
|
@@ -9,6 +16,8 @@ import type { TransformResult } from "../types";
|
|
|
9
16
|
* 3. Responsive class ordering (base → sm → md → lg → xl → 2xl)
|
|
10
17
|
* 4. Arbitrary values → native equivalents (px-[16px] → px-4)
|
|
11
18
|
* 5. Deprecated patterns
|
|
19
|
+
* 6. Flags (does not auto-fix) DaisyUI v5 structural breaks and v4
|
|
20
|
+
* logical-vs-physical spacing property conflicts
|
|
12
21
|
*/
|
|
13
22
|
|
|
14
23
|
// ── Breakpoint order (mobile-first) ─────────────────────────────
|
|
@@ -18,111 +27,6 @@ BREAKPOINT_ORDER.forEach((bp, i) => {
|
|
|
18
27
|
BP_INDEX[bp] = i + 1; // base = 0
|
|
19
28
|
});
|
|
20
29
|
|
|
21
|
-
// ── Tailwind v3 → v4 class renames ──────────────────────────────
|
|
22
|
-
// These are direct 1:1 replacements
|
|
23
|
-
const CLASS_RENAMES: Record<string, string> = {
|
|
24
|
-
// Flexbox/Grid
|
|
25
|
-
"flex-grow-0": "grow-0",
|
|
26
|
-
"flex-grow": "grow",
|
|
27
|
-
"flex-shrink-0": "shrink-0",
|
|
28
|
-
"flex-shrink": "shrink",
|
|
29
|
-
|
|
30
|
-
// Overflow
|
|
31
|
-
"overflow-ellipsis": "text-ellipsis",
|
|
32
|
-
|
|
33
|
-
// Decoration
|
|
34
|
-
"decoration-clone": "box-decoration-clone",
|
|
35
|
-
"decoration-slice": "box-decoration-slice",
|
|
36
|
-
|
|
37
|
-
// Transforms (v4 applies transforms automatically)
|
|
38
|
-
"transform": "", // remove — v4 applies automatically
|
|
39
|
-
"transform-gpu": "",
|
|
40
|
-
"transform-none": "transform-none", // this one stays
|
|
41
|
-
|
|
42
|
-
// Blur/filter (v4 applies automatically)
|
|
43
|
-
"filter": "", // remove
|
|
44
|
-
"backdrop-filter": "", // remove
|
|
45
|
-
|
|
46
|
-
// Ring width default
|
|
47
|
-
"ring": "ring-3", // v4 changed default from 3px to 1px
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
// ── DaisyUI v4 → v5 class renames ──────────────────────────────
|
|
51
|
-
const DAISYUI_RENAMES: Record<string, string> = {
|
|
52
|
-
// Button changes
|
|
53
|
-
"btn-ghost": "btn-ghost", // kept
|
|
54
|
-
"btn-outline": "btn-outline", // kept
|
|
55
|
-
"btn-active": "btn-active", // kept
|
|
56
|
-
|
|
57
|
-
// Alert/Badge
|
|
58
|
-
"badge-ghost": "badge-soft",
|
|
59
|
-
"alert-info": "alert-info",
|
|
60
|
-
"alert-success": "alert-success",
|
|
61
|
-
"alert-warning": "alert-warning",
|
|
62
|
-
"alert-error": "alert-error",
|
|
63
|
-
|
|
64
|
-
// Card
|
|
65
|
-
"card-compact": "card-sm",
|
|
66
|
-
|
|
67
|
-
// Modal
|
|
68
|
-
"modal-open": "modal-open",
|
|
69
|
-
|
|
70
|
-
// Drawer
|
|
71
|
-
"drawer-end": "drawer-end",
|
|
72
|
-
|
|
73
|
-
// Menu
|
|
74
|
-
"menu-horizontal": "menu-horizontal",
|
|
75
|
-
|
|
76
|
-
// Toast position classes (daisy v5 uses different system)
|
|
77
|
-
"toast-top": "toast-top",
|
|
78
|
-
"toast-bottom": "toast-bottom",
|
|
79
|
-
"toast-center": "toast-center",
|
|
80
|
-
"toast-end": "toast-end",
|
|
81
|
-
"toast-start": "toast-start",
|
|
82
|
-
"toast-middle": "toast-middle",
|
|
83
|
-
|
|
84
|
-
// Loading
|
|
85
|
-
"loading-spinner": "loading-spinner",
|
|
86
|
-
"loading-dots": "loading-dots",
|
|
87
|
-
"loading-ring": "loading-ring",
|
|
88
|
-
"loading-ball": "loading-ball",
|
|
89
|
-
"loading-bars": "loading-bars",
|
|
90
|
-
"loading-infinity": "loading-infinity",
|
|
91
|
-
|
|
92
|
-
// Sizes (daisy v5 naming)
|
|
93
|
-
"btn-xs": "btn-xs",
|
|
94
|
-
"btn-sm": "btn-sm",
|
|
95
|
-
"btn-md": "btn-md",
|
|
96
|
-
"btn-lg": "btn-lg",
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
// ── Spacing scale: px → Tailwind unit ───────────────────────────
|
|
100
|
-
const PX_TO_SPACING: Record<number, string> = {};
|
|
101
|
-
for (let i = 0; i <= 96; i++) {
|
|
102
|
-
PX_TO_SPACING[i * 4] = String(i);
|
|
103
|
-
}
|
|
104
|
-
PX_TO_SPACING[2] = "0.5";
|
|
105
|
-
PX_TO_SPACING[6] = "1.5";
|
|
106
|
-
PX_TO_SPACING[10] = "2.5";
|
|
107
|
-
PX_TO_SPACING[14] = "3.5";
|
|
108
|
-
|
|
109
|
-
// Text size: px → native class
|
|
110
|
-
const TEXT_SIZE_MAP: Record<string, string> = {
|
|
111
|
-
"12": "xs",
|
|
112
|
-
"14": "sm",
|
|
113
|
-
"16": "base",
|
|
114
|
-
"18": "lg",
|
|
115
|
-
"20": "xl",
|
|
116
|
-
"24": "2xl",
|
|
117
|
-
"30": "3xl",
|
|
118
|
-
"36": "4xl",
|
|
119
|
-
"48": "5xl",
|
|
120
|
-
"60": "6xl",
|
|
121
|
-
"72": "7xl",
|
|
122
|
-
"96": "8xl",
|
|
123
|
-
"128": "9xl",
|
|
124
|
-
};
|
|
125
|
-
|
|
126
30
|
// Properties that accept spacing values
|
|
127
31
|
const SPACING_PROPS = new Set([
|
|
128
32
|
"p", "px", "py", "pt", "pb", "pl", "pr",
|
|
@@ -200,24 +104,7 @@ function parseClass(cls: string): ParsedClass {
|
|
|
200
104
|
|
|
201
105
|
// ── Fix class renames ───────────────────────────────────────────
|
|
202
106
|
function fixRenames(cls: string): string {
|
|
203
|
-
|
|
204
|
-
const utility = parts.pop()!;
|
|
205
|
-
|
|
206
|
-
// Check direct rename
|
|
207
|
-
if (CLASS_RENAMES[utility] !== undefined) {
|
|
208
|
-
const renamed = CLASS_RENAMES[utility];
|
|
209
|
-
if (renamed === "") return ""; // Remove class entirely
|
|
210
|
-
parts.push(renamed);
|
|
211
|
-
return parts.join(":");
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// Check DaisyUI rename
|
|
215
|
-
if (DAISYUI_RENAMES[utility] && DAISYUI_RENAMES[utility] !== utility) {
|
|
216
|
-
parts.push(DAISYUI_RENAMES[utility]);
|
|
217
|
-
return parts.join(":");
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
return cls;
|
|
107
|
+
return renameToken(cls);
|
|
221
108
|
}
|
|
222
109
|
|
|
223
110
|
// ── Fix arbitrary values ────────────────────────────────────────
|
|
@@ -402,6 +289,11 @@ function fixClassNameString(classes: string): { fixed: string; changes: string[]
|
|
|
402
289
|
}
|
|
403
290
|
}
|
|
404
291
|
|
|
292
|
+
// 5. Flag (don't auto-fix) shorthand/longhand spacing conflicts — gotcha #42
|
|
293
|
+
for (const finding of detectLogicalPropertyConflict(classList)) {
|
|
294
|
+
changes.push(`MANUAL: ${finding.message}`);
|
|
295
|
+
}
|
|
296
|
+
|
|
405
297
|
return { fixed: classList.join(" "), changes };
|
|
406
298
|
}
|
|
407
299
|
|
|
@@ -626,6 +518,18 @@ export function transformTailwind(content: string): TransformResult {
|
|
|
626
518
|
let changed = false;
|
|
627
519
|
let result = content;
|
|
628
520
|
|
|
521
|
+
// ── Flag (don't auto-fix) DaisyUI v5 structural breaks — gotcha #37 ──
|
|
522
|
+
const daisyFindings = detectDaisyUiV5StructuralIssues(result);
|
|
523
|
+
if (daisyFindings.length > 0) {
|
|
524
|
+
// No content is rewritten here, but the caller (phase-transform) only
|
|
525
|
+
// surfaces `notes` when `changed` is true — mark it so these
|
|
526
|
+
// manual-review findings aren't silently dropped.
|
|
527
|
+
changed = true;
|
|
528
|
+
for (const finding of daisyFindings) {
|
|
529
|
+
notes.push(`MANUAL: ${finding.message}`);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
629
533
|
// ── Fix negative z-index on background images ──────────────────
|
|
630
534
|
const zFix = fixNegativeZIndex(result);
|
|
631
535
|
if (zFix.changed) {
|
package/scripts/migrate/types.ts
CHANGED
|
@@ -157,6 +157,15 @@ export interface MigrationContext {
|
|
|
157
157
|
/** Font family from CMS config */
|
|
158
158
|
fontFamily: string | null;
|
|
159
159
|
|
|
160
|
+
/**
|
|
161
|
+
* Statically-extracted content of the source site's `tailwind.config.ts`
|
|
162
|
+
* (colors, fontFamily, screens, safelist, plugins) — read during analyze,
|
|
163
|
+
* before the file is deleted in cleanup, so its content can be ported into
|
|
164
|
+
* the scaffolded `src/styles/app.css` @theme block instead of silently
|
|
165
|
+
* dropped. See analyzers/tailwind-config.ts.
|
|
166
|
+
*/
|
|
167
|
+
tailwindConfig: import("./analyzers/tailwind-config").TailwindConfigExtract;
|
|
168
|
+
|
|
160
169
|
/** All categorized source files */
|
|
161
170
|
files: FileRecord[];
|
|
162
171
|
|
|
@@ -217,6 +226,15 @@ export function createContext(
|
|
|
217
226
|
discoveredNpmDeps: {},
|
|
218
227
|
themeColors: {},
|
|
219
228
|
fontFamily: null,
|
|
229
|
+
tailwindConfig: {
|
|
230
|
+
colors: {},
|
|
231
|
+
fontFamily: {},
|
|
232
|
+
screens: {},
|
|
233
|
+
safelist: [],
|
|
234
|
+
safelistPatterns: [],
|
|
235
|
+
plugins: [],
|
|
236
|
+
reviewItems: [],
|
|
237
|
+
},
|
|
220
238
|
files: [],
|
|
221
239
|
sectionMetas: [],
|
|
222
240
|
islandClassifications: [],
|
package/scripts/tailwind-lint.ts
CHANGED
|
@@ -18,6 +18,13 @@
|
|
|
18
18
|
|
|
19
19
|
import { readFileSync, writeFileSync, readdirSync, statSync } from "node:fs";
|
|
20
20
|
import { join, relative } from "node:path";
|
|
21
|
+
import {
|
|
22
|
+
CLASS_RENAMES,
|
|
23
|
+
DAISYUI_RENAMES,
|
|
24
|
+
PX_TO_SPACING,
|
|
25
|
+
TEXT_SIZE_MAP,
|
|
26
|
+
renameToken,
|
|
27
|
+
} from "./migrate/transforms/tailwind-renames";
|
|
21
28
|
|
|
22
29
|
// ── Breakpoint order (mobile-first) ─────────────────────────────
|
|
23
30
|
const BREAKPOINT_ORDER = ["sm", "md", "lg", "xl", "2xl"] as const;
|
|
@@ -26,44 +33,6 @@ BREAKPOINT_ORDER.forEach((bp, i) => {
|
|
|
26
33
|
BP_INDEX[bp] = i + 1;
|
|
27
34
|
});
|
|
28
35
|
|
|
29
|
-
// ── Tailwind v3 → v4 class renames ──────────────────────────────
|
|
30
|
-
const CLASS_RENAMES: Record<string, string> = {
|
|
31
|
-
"flex-grow-0": "grow-0",
|
|
32
|
-
"flex-grow": "grow",
|
|
33
|
-
"flex-shrink-0": "shrink-0",
|
|
34
|
-
"flex-shrink": "shrink",
|
|
35
|
-
"overflow-ellipsis": "text-ellipsis",
|
|
36
|
-
"decoration-clone": "box-decoration-clone",
|
|
37
|
-
"decoration-slice": "box-decoration-slice",
|
|
38
|
-
"transform": "",
|
|
39
|
-
"transform-gpu": "",
|
|
40
|
-
"filter": "",
|
|
41
|
-
"backdrop-filter": "",
|
|
42
|
-
"ring": "ring-3",
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
// ── DaisyUI v4 → v5 class renames ──────────────────────────────
|
|
46
|
-
const DAISYUI_RENAMES: Record<string, string> = {
|
|
47
|
-
"badge-ghost": "badge-soft",
|
|
48
|
-
"card-compact": "card-sm",
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
// ── Spacing scale ───────────────────────────────────────────────
|
|
52
|
-
const PX_TO_SPACING: Record<number, string> = {};
|
|
53
|
-
for (let i = 0; i <= 96; i++) {
|
|
54
|
-
PX_TO_SPACING[i * 4] = String(i);
|
|
55
|
-
}
|
|
56
|
-
PX_TO_SPACING[2] = "0.5";
|
|
57
|
-
PX_TO_SPACING[6] = "1.5";
|
|
58
|
-
PX_TO_SPACING[10] = "2.5";
|
|
59
|
-
PX_TO_SPACING[14] = "3.5";
|
|
60
|
-
|
|
61
|
-
const TEXT_SIZE_MAP: Record<string, string> = {
|
|
62
|
-
"12": "xs", "14": "sm", "16": "base", "18": "lg", "20": "xl",
|
|
63
|
-
"24": "2xl", "30": "3xl", "36": "4xl", "48": "5xl", "60": "6xl",
|
|
64
|
-
"72": "7xl", "96": "8xl", "128": "9xl",
|
|
65
|
-
};
|
|
66
|
-
|
|
67
36
|
const SPACING_PROPS = new Set([
|
|
68
37
|
"p", "px", "py", "pt", "pb", "pl", "pr",
|
|
69
38
|
"m", "mx", "my", "mt", "mb", "ml", "mr",
|
|
@@ -247,7 +216,7 @@ function scanFile(filePath: string): Issue[] {
|
|
|
247
216
|
for (const cls of classList) {
|
|
248
217
|
const parts = cls.split(":");
|
|
249
218
|
const utility = parts[parts.length - 1];
|
|
250
|
-
if (CLASS_RENAMES[utility] !== undefined) {
|
|
219
|
+
if (CLASS_RENAMES[utility] !== undefined && CLASS_RENAMES[utility] !== utility) {
|
|
251
220
|
const renamed = CLASS_RENAMES[utility];
|
|
252
221
|
issues.push({
|
|
253
222
|
file: filePath, line, type: "rename",
|
|
@@ -291,21 +260,7 @@ function fixClassOrder(classes: string): string {
|
|
|
291
260
|
function fixClassName(classes: string): string {
|
|
292
261
|
let classList = classes.split(/\s+/).filter(Boolean);
|
|
293
262
|
|
|
294
|
-
classList = classList.map((
|
|
295
|
-
const parts = cls.split(":");
|
|
296
|
-
const utility = parts.pop()!;
|
|
297
|
-
if (CLASS_RENAMES[utility] !== undefined) {
|
|
298
|
-
const renamed = CLASS_RENAMES[utility];
|
|
299
|
-
if (renamed === "") return "";
|
|
300
|
-
parts.push(renamed);
|
|
301
|
-
return parts.join(":");
|
|
302
|
-
}
|
|
303
|
-
if (DAISYUI_RENAMES[utility] && DAISYUI_RENAMES[utility] !== utility) {
|
|
304
|
-
parts.push(DAISYUI_RENAMES[utility]);
|
|
305
|
-
return parts.join(":");
|
|
306
|
-
}
|
|
307
|
-
return cls;
|
|
308
|
-
}).filter(Boolean);
|
|
263
|
+
classList = classList.map(renameToken).filter(Boolean);
|
|
309
264
|
|
|
310
265
|
classList = classList.map((cls) => {
|
|
311
266
|
if (!cls.includes("[")) return cls;
|