@decocms/blocks-cli 7.20.10 → 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.
@@ -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
- const parts = cls.split(":");
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) {
@@ -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: [],
@@ -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((cls) => {
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;