@maz-ui/upgrade 5.0.0-canary.ef64a02.0 → 5.1.0-beta.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 CHANGED
@@ -5,10 +5,10 @@ Automated source rewrites for migrating a project from **Maz-UI v4** to **v5**.
5
5
  Run it once at the start of your migration to clear the mechanical changes
6
6
  from your todo list, then walk through the rest of the
7
7
  [migration guide](https://maz-ui.com/guide/migration-v5) for the items that
8
- need a human eye (theme `foundation.radius` reshape, `MazIcon` API,
9
- `MazBadge` numeric sizes, `MazChart` `update-mode`).
8
+ need a human eye (theme `foundation.radius` reshape, bound `MazIcon` values,
9
+ `MazBadge` `px` / `em` sizes, `MazChart` `update-mode`).
10
10
 
11
- The companion [`@maz-ui/mcp`](https://maz-ui.com/guide/mcp) server lets your
11
+ The companion [`@maz-ui/mcp`](https://maz-ui.com/ecosystem/mcp) server lets your
12
12
  AI assistant read the migration guide and apply the manual steps with full
13
13
  context.
14
14
 
@@ -96,7 +96,7 @@ The CLI walks every file matching one of these extensions inside the
96
96
  provided paths:
97
97
 
98
98
  - **Vue:** `.vue`
99
- - **CSS:** `.css`
99
+ - **Stylesheets:** `.css`, `.scss`, `.sass`, `.less`, `.styl`, `.stylus`, `.pcss`, `.postcss`
100
100
  - **TypeScript:** `.ts`, `.tsx`, `.cts`, `.mts`
101
101
  - **JavaScript:** `.js`, `.jsx`, `.cjs`, `.mjs`
102
102
  - **Manifest:** every `package.json` (root + nested in monorepo workspaces)
@@ -124,50 +124,106 @@ outside both lists, add them to a local `.gitignore` (or to a sibling
124
124
  ## Transform groups
125
125
 
126
126
  By default all five groups run. Use `--only=<a,b,...>` to scope the run.
127
- Each group is independent and idempotent: re-running the tool on
128
- already-migrated code is a no-op.
127
+ Each group is independent. Re-running the tool on already-migrated code is a
128
+ no-op, except for the `MazAvatar` size rewrite (a unit value can not be told
129
+ apart from an already multiplied one): run it once, on v4 code.
129
130
 
130
131
  ### `imports`
131
132
 
132
- CSS subpath rename, applied to imports in `.vue` and JS/TS files:
133
+ CSS subpath renames, applied to imports in `.vue`, JS/TS files and
134
+ stylesheets (`@import`), and the root import move, applied to `.vue` and JS/TS
135
+ files:
133
136
 
134
- | v4 | v5 |
135
- | ---------------------------- | --------------------------- |
136
- | `import 'maz-ui/styles'` | `import 'maz-ui/style.css'` |
137
- | `import 'maz-ui/aos-styles'` | `import 'maz-ui/aos.css'` |
137
+ | v4 | v5 |
138
+ | ----------------------------------- | --------------------------------------- |
139
+ | `import 'maz-ui/styles'` | `import 'maz-ui/style.css'` |
140
+ | `import 'maz-ui/aos-styles'` | `import 'maz-ui/aos.css'` |
141
+ | `import 'maz-ui/dist/css/main.css'` | `import 'maz-ui/style.css'` |
142
+ | `import 'maz-ui/dist/css/aos.css'` | `import 'maz-ui/aos.css'` |
143
+ | `import { sleep } from 'maz-ui'` | `import { sleep } from '@maz-ui/utils'` |
144
+
145
+ The `maz-ui` root entry still exists in v5 as a type-only surface: imports made
146
+ only of its types (`MazColor`, `MazSize`, `MazSizeUnit`, `MazRoundedSize`,
147
+ `GlobalizableProps`, `MazComponentName`, `MazUiDefaultsOptions`,
148
+ `MazUiOptions`) are kept on `maz-ui`. Other deep `maz-ui/dist/...` imports (the
149
+ `./*` wildcard export was removed) are reported as a warning at the end of the
150
+ run.
138
151
 
139
152
  ### `props`
140
153
 
141
- Component prop / slot / class renames, applied to `.vue` files only.
142
- Patterns specific enough to rewrite globally (e.g. `left-icon` /
143
- `right-icon`) are handled file-wide. Patterns that would otherwise be
144
- ambiguous (`variant=`, `color="background"`) are rewritten **only inside
145
- `<Maz...>` opening tags**, never on your own components.
146
-
147
- | v4 | v5 |
148
- | ----------------------------------------------------------------- | ----------------------------------------------------------- |
149
- | `left-icon="x"` / `right-icon="x"` | `start-icon="x"` / `end-icon="x"` |
150
- | `:left-icon="x"` / `:right-icon="x"` | `:start-icon="x"` / `:end-icon="x"` |
151
- | `<template #left-icon>` / `<template #right-icon>` | `<template #start-icon>` / `<template #end-icon>` |
152
- | `<template #icon-left>` / `<template #icon-right>` (MazContainer) | `<template #icon-start>` / `<template #icon-end>` |
153
- | `footer-align="left"` / `footer-align="right"` | `footer-align="start"` / `footer-align="end"` |
154
- | `<MazDrawer variant="left">` / `<MazDrawer variant="right">` | `<MazDrawer variant="start">` / `<MazDrawer variant="end">` |
155
- | `<Maz... color="background">` | `<Maz... color="surface">` |
156
- | `active-color="background"` | `active-color="surface"` |
157
- | `rounded-size="base"` | `rounded-size="md"` |
158
- | `.--has-left-icon` / `.--has-right-icon` (CSS selectors) | `.--has-start-icon` / `.--has-end-icon` |
154
+ Component prop / slot / class renames, applied to `.vue` files only. The
155
+ props (`left-icon`, `variant=`, `color="background"`, icon names…) are
156
+ rewritten **only inside `<Maz...>` / `<maz-...>` opening tags**, never on your
157
+ own components or other libraries (Vant `left-icon`…). The slots,
158
+ `footer-align`, `rounded-size`, `active-color` and the maz-ui class names are
159
+ specific enough to be rewritten file-wide. Kebab-case, camelCase and
160
+ `v-bind:` / `v-slot:` forms are all handled.
161
+
162
+ | v4 | v5 |
163
+ | --------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- |
164
+ | `left-icon="x"` / `right-icon="x"` | `start-icon="x"` / `end-icon="x"` |
165
+ | `:left-icon="x"` / `:right-icon="x"` | `:start-icon="x"` / `:end-icon="x"` |
166
+ | `:leftIcon="x"` / `v-bind:right-icon="x"` | `:startIcon="x"` / `v-bind:end-icon="x"` |
167
+ | `start-icon="user"` (bare icon name, MazBtn / MazInput / MazLink / MazContainer / MazDropdown / MazAlert) | `start-icon="/user.svg"` |
168
+ | `<MazIcon src="/a.svg">` / `<MazIcon :src="url">` | `<MazIcon icon="/a.svg">` / `<MazIcon :icon="url">` |
169
+ | `<MazIcon name="home">` / `<MazIcon name="home" path="/icons">` | `<MazIcon icon="/home.svg">` / `<MazIcon icon="/icons/home.svg">` |
170
+ | `<template #left-icon>` / `<template #right-icon>` | `<template #start-icon>` / `<template #end-icon>` |
171
+ | `<template #icon-left>` / `<template #icon-right>` (MazContainer) | `<template #icon-start>` / `<template #icon-end>` |
172
+ | `footer-align="left"` / `footer-align="right"` | `footer-align="start"` / `footer-align="end"` |
173
+ | `<MazDrawer variant="left">` / `<MazDrawer variant="right">` | `<MazDrawer variant="start">` / `<MazDrawer variant="end">` |
174
+ | `<Maz... color="background">` | `<Maz... color="surface">` |
175
+ | `active-color="background"` | `active-color="surface"` |
176
+ | `rounded-size="base"` | `rounded-size="md"` |
177
+ | `.--has-left-icon` / `.--has-right-icon` (CSS selectors) | `.--has-start-icon` / `.--has-end-icon` |
178
+ | `.m-input-wrapper-left` / `.m-input-wrapper-right` | `.m-input-wrapper-start` / `.m-input-wrapper-end` |
179
+ | `.drawer-anim-left-*` / `.drawer-anim-right-*` | `.drawer-anim-start-*` / `.drawer-anim-end-*` |
180
+ | `<MazAvatar size="2rem">` (static CSS unit) | `<MazAvatar size="6rem">` (value ×3, render preserved) |
181
+ | `<MazBadge size="0.8rem">` (static `rem` size) | `<MazBadge size="sm">` (closest `MazSize` keyword) |
182
+ | `<MazPullToRefresh :on-click="x">` | `<MazPullToRefresh :on-refresh="x">` |
183
+
184
+ The `MazAvatar` rewrite targets only `<MazAvatar>` / `<maz-avatar>` opening
185
+ tags. v5 fixed the avatar size scale (a CSS-unit `size` no longer renders 3x
186
+ too large), so the numeric value is multiplied by 3 to keep the exact same
187
+ render. Every CSS unit is supported. `MazSize` keywords (`mini`..`xl`) and
188
+ bound `:size="…"` expressions are left untouched - the latter is reported as
189
+ a warning at the end of the run.
190
+
191
+ v4 rendered a string icon prop as `<MazIcon :name>`, resolved to
192
+ `${mazIconPath}/<name>.svg`. v5 only accepts a URL (or a raw SVG) for a string,
193
+ so a bare name renders nothing: it is rewritten to the relative URL of the same
194
+ file (`mazIconPath` is still prepended to relative URLs). A bare name is never
195
+ a valid v5 value, so this rewrite is idempotent. Bound values, a bound
196
+ `:name` / `:path` on `MazIcon` and the removed `@loaded` / `@unloaded` events
197
+ are reported as warnings at the end of the run. If you provide a `mazIconPath`
198
+ (Nuxt `general.defaultMazIconPath`), note that v5 also prepends it to the
199
+ relative URLs of a former `src` or `path`.
200
+
201
+ The `MazBadge` rewrite targets only `<MazBadge>` / `<maz-badge>` opening tags.
202
+ v5 `size` only accepts a `MazSize` keyword (the v4 value was the badge
203
+ font-size). The keywords are `rem` font-sizes (`mini` 0.625, `xs` 0.6875, `sm`
204
+ 0.75, `md` 0.875, `lg` 1, `xl` 1.25), so a static `rem` value is mapped to the
205
+ closest one (a tie goes to the larger keyword), which gives the table of the
206
+ migration guide whatever the root font size. `px` / `em` values depend on the
207
+ root or parent font size and are reported as warnings at the end of the run.
208
+
209
+ The `MazPullToRefresh` rewrite targets only `<MazPullToRefresh>` /
210
+ `<maz-pull-to-refresh>` opening tags. v5 renamed the callback prop `on-click`
211
+ to `on-refresh` (both kebab-case and camelCase, bound or static), so native
212
+ click handlers and the `onClick` prop of other components are never touched.
159
213
 
160
214
  ### `css`
161
215
 
162
- CSS variable renames + `hsl(var(...))` collapse, applied to `.vue` style
163
- blocks, `.css` files, and any inline strings in `.ts`/`.js`:
216
+ CSS variable renames + `hsl(var(...))` collapse, applied to `.vue` files,
217
+ stylesheets, and any inline strings in `.ts`/`.js` (the maz-ui class names of
218
+ the `props` table are renamed in stylesheets too):
164
219
 
165
220
  | v4 | v5 |
166
221
  | ------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------- |
167
222
  | `var(--maz-background)` / `var(--maz-background-700)` / `var(--maz-background-foreground)` | `var(--maz-surface)` / `var(--maz-surface-700)` / `var(--maz-surface-foreground)` |
168
223
  | `var(--maz-border)` / `var(--maz-border-700)` | `var(--maz-divider)` / `var(--maz-divider-700)` |
169
224
  | `hsl(var(--maz-primary))` | `var(--maz-primary)` |
170
- | `hsl(var(--maz-primary) / 0.5)` | `color-mix(in srgb, var(--maz-primary) 0.5, transparent)` |
225
+ | `hsl(var(--maz-primary) / 0.5)` / `hsl(var(--maz-primary) / 50%)` | `color-mix(in srgb, var(--maz-primary) 50%, transparent)` |
226
+ | `hsl(var(--maz-primary) / var(--alpha))` | `color-mix(in srgb, var(--maz-primary) calc(var(--alpha) * 100%), transparent)` |
171
227
 
172
228
  Foundation tokens that happen to start with the same prefix (e.g.
173
229
  `--maz-border-width`, `--maz-border-radius`) are explicitly **not**
@@ -178,18 +234,23 @@ renamed.
178
234
  Plugin / Nuxt module / preset config keys, applied to `.vue` and JS/TS
179
235
  files:
180
236
 
181
- | v4 | v5 |
182
- | ----------------------------------------------------------------------- | ------------------------------------- |
183
- | Nuxt `mazUi.css.injectMainCss` | `mazUi.css.injectCss` |
184
- | Theme `strategy: 'hybrid'` | `strategy: 'runtime'` |
185
- | `MazUiTheme` plugin options `injectCriticalCSS: …` / `injectFullCSS: …` | line removed (silently ignored in v5) |
186
- | Nuxt theme `injectAllCSSOnServer: …` | line removed (silently ignored in v5) |
187
- | Custom preset `colors.{light,dark}.background` | `colors.{light,dark}.surface` |
188
- | Custom preset `colors.{light,dark}.border` | `colors.{light,dark}.divider` |
189
-
190
- The preset color rename is scoped to `light: { … }` / `dark: { … }`
191
- blocks, so unrelated CSS-in-JS / JSX style props elsewhere are left
192
- alone.
237
+ | v4 | v5 |
238
+ | ---------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- |
239
+ | Nuxt `mazUi.css.injectMainCss` | `mazUi.css.injectCss` |
240
+ | Theme `strategy: 'hybrid'` | `strategy: 'runtime'` |
241
+ | `MazUiTheme` plugin options `injectCriticalCSS: …` / `injectFullCSS: …` | option removed (silently ignored in v5) |
242
+ | Nuxt theme `injectAllCSSOnServer: …` | option removed (silently ignored in v5) |
243
+ | `generateCSS` / `buildThemeCSS` / `buildSeparateThemeFiles` `onlyCritical`, `criticalColors`, `criticalFoundation`, `includeColorScales` | option removed |
244
+ | Custom preset `colors.{light,dark}.background` | `colors.{light,dark}.surface` |
245
+ | Custom preset `colors.{light,dark}.border` | `colors.{light,dark}.divider` |
246
+
247
+ The removed options are cleaned on their own line or inline
248
+ (`{ preset, onlyCritical: true }`). The preset color rename is scoped to
249
+ `light: { … }` / `dark: { … }` blocks, and it runs with the
250
+ `strategy: 'hybrid'` rewrite only in files referencing maz-ui (a `maz-ui` /
251
+ `@maz-ui/*` import, the Nuxt `mazUi` key, `MazUi*` plugins, `definePreset`,
252
+ `setupTheme`, `useTheme`, `darkModeStrategy`), so generic objects elsewhere are
253
+ left alone.
193
254
 
194
255
  ### `deps`
195
256
 
@@ -204,8 +265,15 @@ preserved; non-maz-ui dependencies are never touched.
204
265
  | `maz-ui`, `@maz-ui/*` semver ranges (`^4.x`, `~4.x`, `4.x`, `4.0.0`, …) | Rewritten to `^5.0.0` |
205
266
  | `workspace:*`, `link:…`, `file:…`, `portal:…`, `npm:…`, `http(s):…`, `git+…` | Left untouched |
206
267
  | Dist tags (`latest`, `next`, `beta`, `alpha`, `canary`) | Left untouched |
268
+ | `@maz-ui/cli` (not published in v5) | Left untouched |
207
269
  | Anything else (`vue`, `chart.js`, your own packages, …) | Left untouched |
208
270
 
271
+ When the `imports` group runs, two dependencies are added next to `maz-ui`
272
+ if a source needs them and no `package.json` declares them: `@maz-ui/utils`
273
+ (when a `from 'maz-ui'` import is moved to it) and `valibot` (`^1.3.1`, when a
274
+ source imports it: it was a dependency of maz-ui v4, it is an optional peer
275
+ dependency in v5).
276
+
209
277
  `vue-chartjs` is **not** removed automatically, even though `MazChart`
210
278
  no longer depends on it — keep it if you use it directly elsewhere,
211
279
  remove it manually otherwise.
@@ -255,7 +323,7 @@ Detected package manager: pnpm. Running `pnpm install`…
255
323
  [ … pnpm install output … ]
256
324
 
257
325
  Next: see https://maz-ui.com/guide/migration-v5 for the manual steps
258
- (foundation.radius → scales.rounded.md, MazIcon API, MazBadge sizes, MazChart update-mode).
326
+ (foundation.radius → scales.rounded.md, MazIcon API, MazBadge px / em sizes, MazChart update-mode).
259
327
  ```
260
328
 
261
329
  ## Exit codes
@@ -270,15 +338,18 @@ Next: see https://maz-ui.com/guide/migration-v5 for the manual steps
270
338
  ## What it does NOT do
271
339
 
272
340
  Some changes need a human (or your AI assistant via the
273
- [`@maz-ui/mcp`](https://maz-ui.com/guide/mcp) server) — see the
341
+ [`@maz-ui/mcp`](https://maz-ui.com/ecosystem/mcp) server) — see the
274
342
  [migration guide](https://maz-ui.com/guide/migration-v5) for the full
275
343
  context:
276
344
 
277
- - **`MazIcon` API simplification** — `name`/`path`/`src` props collapsing
278
- into a single `icon` prop needs the original asset path to rewrite
279
- correctly.
280
- - **`MazBadge` numeric `size`** — picking the right keyword (`mini`/`xs`/…)
281
- is a judgment call.
345
+ - **`MazIcon` bound values** - a bound `:name` / `:path` and the removed
346
+ `@loaded` / `@unloaded` events (reported as warnings). Static `name`,
347
+ `path` and `src` are rewritten by the `props` group.
348
+ - **Icon values in scripts** - `h(MazBtn, { leftIcon })`, `v-bind` objects,
349
+ `MazStepper` `steps[].icon`: keys too generic to rewrite safely in JS.
350
+ - **`MazBadge` `px` / `em` `size`** - the keyword depends on the root or
351
+ parent font size (reported as warnings). Static `rem` sizes are rewritten by
352
+ the `props` group.
282
353
  - **Theme preset radius reshape** — `foundation.radius` →
283
354
  `scales.rounded.md` needs to move the value to a new sibling block,
284
355
  which is too structural for a safe regex. The simpler color key renames
package/dist/cli.mjs CHANGED
@@ -5,7 +5,7 @@ import { resolve, dirname } from 'node:path';
5
5
  import process from 'node:process';
6
6
  import { logger } from '@maz-ui/node';
7
7
  import { globby } from 'globby';
8
- import { transformFile, ALL_GROUPS } from './index.mjs';
8
+ import { ALL_GROUPS, transformFile, hasMazUiDistImport, hasMazIconLegacyApi, hasMazBadgeLengthSize, hasMazAvatarBoundSize, hasFoundationRadius, hasValibotImport, hasMazUiRootImport } from './index.mjs';
9
9
 
10
10
  const DEFAULT_IGNORES = [
11
11
  "**/node_modules/**",
@@ -88,22 +88,36 @@ Options:
88
88
  -v, --version Print the upgrade tool version.
89
89
 
90
90
  Transform groups:
91
- imports maz-ui/styles \u2192 maz-ui/style.css, maz-ui/aos-styles \u2192 maz-ui/aos.css
92
- props left-icon/right-icon \u2192 start-icon/end-icon (props, slots,
93
- #icon-left/#icon-right, --has-*-icon classes), footer-align,
94
- variant, color="background", active-color, rounded-size="base"
91
+ imports maz-ui/styles \u2192 maz-ui/style.css, maz-ui/aos-styles \u2192 maz-ui/aos.css,
92
+ maz-ui/dist/css/{main,aos}.css \u2192 maz-ui/{style,aos}.css (also in
93
+ stylesheets), from 'maz-ui' \u2192 from '@maz-ui/utils' (root re-export
94
+ removed in v5, imports of the v5 root types are kept). When this
95
+ rewrite fires and the deps group runs, @maz-ui/utils is also added
96
+ to package.json next to maz-ui (and valibot when imported).
97
+ props left-icon/right-icon \u2192 start-icon/end-icon (kebab, camelCase,
98
+ v-bind:, slots #left-icon / v-slot:, #icon-left/#icon-right),
99
+ bare icon names \u2192 relative SVG URLs (start-icon="user" \u2192
100
+ "/user.svg"), MazIcon src/name/path \u2192 icon, footer-align, variant,
101
+ color="background", active-color, rounded-size="base", internal
102
+ class names (--has-*-icon, m-input-wrapper-*, drawer-anim-*),
103
+ MazAvatar static size unit x3 (scale fix, render preserved),
104
+ MazBadge rem size \u2192 closest MazSize keyword,
105
+ MazPullToRefresh on-click \u2192 on-refresh
95
106
  css --maz-background \u2192 --maz-surface, --maz-border \u2192 --maz-divider,
96
- hsl(var(--maz-X)) collapse (incl. alpha \u2192 color-mix)
107
+ hsl(var(--maz-X)) collapse (incl. alpha \u2192 color-mix with a
108
+ percentage), internal class names in stylesheets
97
109
  config Nuxt injectMainCss \u2192 injectCss, theme strategy 'hybrid' \u2192 'runtime',
98
110
  removes dropped theme options (injectCriticalCSS, injectFullCSS,
99
- injectAllCSSOnServer), preset colors.{light,dark}.background \u2192 surface
100
- and .border \u2192 divider
111
+ injectAllCSSOnServer, onlyCritical, criticalColors,
112
+ criticalFoundation, includeColorScales), preset
113
+ colors.{light,dark}.background \u2192 surface and .border \u2192 divider
101
114
  deps Bumps every maz-ui / @maz-ui/* entry in package.json
102
- (dependencies, devDependencies, peerDependencies) to ^5.0.0.
103
- Workspace, link, file and url specs are left untouched.
115
+ (dependencies, devDependencies, peerDependencies) to ^5.0.0
116
+ (except @maz-ui/cli, not published in v5). Workspace, link, file
117
+ and url specs are left untouched.
104
118
 
105
- Scans the given paths for .vue, .css, .ts/.tsx/.cts/.mts,
106
- .js/.jsx/.cjs/.mjs and package.json files. By default it respects
119
+ Scans the given paths for .vue, stylesheets (.css, .scss, .sass, .less,
120
+ .styl, .pcss), .ts/.tsx/.cts/.mts, .js/.jsx/.cjs/.mjs and package.json files. By default it respects
107
121
  your .gitignore (plus a built-in safe list of node_modules, dist,
108
122
  build, .nuxt, .output, .next, .svelte-kit, .turbo, .cache, coverage,
109
123
  .vercel, .netlify). Pass --no-gitignore to skip the .gitignore step.
@@ -115,8 +129,10 @@ npm \u2014 based on the lockfile in cwd) and runs <pm> install. Pass
115
129
 
116
130
  What it does NOT do (handle by hand or with the @maz-ui/mcp server):
117
131
  - Reshape foundation.radius into scales.rounded.md (move + key restructure).
118
- - Map MazIcon name=/path=/src= props to the new icon= API.
119
- - Map numeric MazBadge size to the keyword scale.
132
+ - Map bound MazIcon :name / :path and the removed @loaded / @unloaded events
133
+ (reported as warnings).
134
+ - Map px / em MazBadge sizes to the keyword scale (reported as warnings,
135
+ rem values are rewritten).
120
136
  - MazChart updateMode default change (judgment call).
121
137
 
122
138
  Full migration guide: https://maz-ui.com/guide/migration-v5
@@ -166,18 +182,46 @@ Detected package manager: ${pm}. Running \`${pm} install\`\u2026
166
182
  process.exit(code);
167
183
  }
168
184
  }
169
- async function run() {
170
- const opts = parseArgs(process.argv);
171
- const cwd = process.cwd();
185
+ const PACKAGE_JSON_RE = /(?:^|[\\/])package\.json$/;
186
+ function applyTransform(file, opts, deps = {}) {
187
+ const before = readFileSync(file, "utf8");
188
+ const after = transformFile(file, before, { groups: opts.groups, ...deps });
189
+ return { before, after };
190
+ }
191
+ function reportAndWrite(file, after, opts, cwd) {
192
+ const shown = file.replace(`${cwd}/`, "");
193
+ logger.log(`${opts.dryRun ? "[dry-run] would update" : "updated"}: ${shown}`);
194
+ if (!opts.dryRun)
195
+ writeFileSync(file, after, "utf8");
196
+ }
197
+ function detectManualSteps(content, importsEnabled) {
198
+ return {
199
+ needsUtilsDep: importsEnabled && hasMazUiRootImport(content),
200
+ needsValibotDep: importsEnabled && hasValibotImport(content),
201
+ radius: hasFoundationRadius(content),
202
+ avatarSize: hasMazAvatarBoundSize(content),
203
+ badgeSize: hasMazBadgeLengthSize(content),
204
+ iconLegacy: hasMazIconLegacyApi(content),
205
+ distImport: hasMazUiDistImport(content)
206
+ };
207
+ }
208
+ async function processSourceFiles(opts, cwd, packageJsonFiles) {
209
+ const importsEnabled = opts.groups.includes("imports");
172
210
  let scanned = 0;
173
211
  let changed = 0;
174
- let depsChanged = false;
212
+ let needsUtilsDep = false;
213
+ let needsValibotDep = false;
214
+ const radiusFiles = [];
215
+ const avatarSizeFiles = [];
216
+ const badgeSizeFiles = [];
217
+ const iconLegacyFiles = [];
218
+ const distImportFiles = [];
175
219
  for (const root of opts.roots) {
176
220
  const absoluteRoot = resolve(cwd, root);
177
221
  const files = await globby(
178
222
  [
179
223
  "**/*.vue",
180
- "**/*.css",
224
+ "**/*.{css,scss,sass,less,styl,stylus,pcss,postcss}",
181
225
  "**/*.{js,mjs,cjs,jsx}",
182
226
  "**/*.{ts,mts,cts,tsx}",
183
227
  "**/package.json"
@@ -190,29 +234,105 @@ async function run() {
190
234
  }
191
235
  );
192
236
  for (const file of files) {
237
+ if (PACKAGE_JSON_RE.test(file)) {
238
+ packageJsonFiles.push(file);
239
+ continue;
240
+ }
193
241
  scanned += 1;
194
- const before = readFileSync(file, "utf8");
195
- const after = transformFile(file, before, { groups: opts.groups });
242
+ const { before, after } = applyTransform(file, opts);
243
+ const detection = detectManualSteps(before, importsEnabled);
244
+ needsUtilsDep ||= detection.needsUtilsDep;
245
+ needsValibotDep ||= detection.needsValibotDep;
246
+ for (const [warning, files2] of [
247
+ [detection.radius, radiusFiles],
248
+ [detection.avatarSize, avatarSizeFiles],
249
+ [detection.badgeSize, badgeSizeFiles],
250
+ [detection.iconLegacy, iconLegacyFiles],
251
+ [detection.distImport, distImportFiles]
252
+ ]) {
253
+ if (warning)
254
+ files2.push(file);
255
+ }
196
256
  if (after === before)
197
257
  continue;
198
258
  changed += 1;
199
- if (file.endsWith("package.json"))
200
- depsChanged = true;
201
- const shown = file.replace(`${cwd}/`, "");
202
- logger.log(`${opts.dryRun ? "[dry-run] would update" : "updated"}: ${shown}`);
203
- if (!opts.dryRun) {
204
- writeFileSync(file, after, "utf8");
205
- }
259
+ reportAndWrite(file, after, opts, cwd);
206
260
  }
207
261
  }
262
+ return { scanned, changed, needsUtilsDep, needsValibotDep, radiusFiles, avatarSizeFiles, badgeSizeFiles, iconLegacyFiles, distImportFiles };
263
+ }
264
+ function processPackageJsonFiles(files, opts, cwd, deps) {
265
+ let scanned = 0;
266
+ let changed = 0;
267
+ let depsChanged = false;
268
+ for (const file of files) {
269
+ scanned += 1;
270
+ const { before, after } = applyTransform(file, opts, deps);
271
+ if (after === before)
272
+ continue;
273
+ changed += 1;
274
+ depsChanged = true;
275
+ reportAndWrite(file, after, opts, cwd);
276
+ }
277
+ return { scanned, changed, depsChanged };
278
+ }
279
+ async function run() {
280
+ const opts = parseArgs(process.argv);
281
+ const cwd = process.cwd();
282
+ const packageJsonFiles = [];
283
+ const sources = await processSourceFiles(opts, cwd, packageJsonFiles);
284
+ const pkgs = processPackageJsonFiles(packageJsonFiles, opts, cwd, { addUtilsDep: sources.needsUtilsDep, addValibotDep: sources.needsValibotDep });
285
+ const scanned = sources.scanned + pkgs.scanned;
286
+ const changed = sources.changed + pkgs.changed;
287
+ const depsChanged = pkgs.depsChanged;
208
288
  const prefix = opts.dryRun ? "would update" : "updated";
209
289
  logger.log(`
210
290
  Scanned ${scanned} files, ${prefix} ${changed}.`);
211
291
  logger.log(`Groups applied: ${opts.groups.join(", ")}`);
212
292
  maybeInstallDeps(opts, depsChanged, cwd);
293
+ if (sources.radiusFiles.length > 0) {
294
+ const cwdPrefix = `${cwd}/`;
295
+ logger.log(`
296
+ \u26A0 foundation.radius detected in ${sources.radiusFiles.length} file(s) \u2014 manual migration required:`);
297
+ for (const file of sources.radiusFiles)
298
+ logger.log(` - ${file.replace(cwdPrefix, "")}`);
299
+ logger.log(` \u2192 Move the value to scales.rounded.md (other rounded keys are now derived from it via calc).`);
300
+ }
301
+ if (sources.avatarSizeFiles.length > 0) {
302
+ const cwdPrefix = `${cwd}/`;
303
+ logger.log(`
304
+ \u26A0 MazAvatar bound :size detected in ${sources.avatarSizeFiles.length} file(s) - manual check required:`);
305
+ for (const file of sources.avatarSizeFiles)
306
+ logger.log(` - ${file.replace(cwdPrefix, "")}`);
307
+ logger.log(` \u2192 The size scale was fixed (no more x3). Multiply the CSS unit value by 3 to keep the same render, or use a MazSize keyword. Static size="<unit>" values were already rewritten.`);
308
+ }
309
+ if (sources.badgeSizeFiles.length > 0) {
310
+ const cwdPrefix = `${cwd}/`;
311
+ logger.log(`
312
+ \u26A0 MazBadge px / em size detected in ${sources.badgeSizeFiles.length} file(s) - manual migration required:`);
313
+ for (const file of sources.badgeSizeFiles)
314
+ logger.log(` - ${file.replace(cwdPrefix, "")}`);
315
+ logger.log(` \u2192 size now only accepts a MazSize keyword (mini, xs, sm, md, lg, xl): pick the closest one (rem values were already rewritten).`);
316
+ }
317
+ if (sources.iconLegacyFiles.length > 0) {
318
+ const cwdPrefix = `${cwd}/`;
319
+ logger.log(`
320
+ \u26A0 MazIcon v4 API detected in ${sources.iconLegacyFiles.length} file(s) - manual migration required:`);
321
+ for (const file of sources.iconLegacyFiles)
322
+ logger.log(` - ${file.replace(cwdPrefix, "")}`);
323
+ logger.log(` \u2192 Replace the bound :name / :path by :icon (a component, a URL or a raw SVG), and the removed @loaded / @unloaded events.`);
324
+ }
325
+ if (sources.distImportFiles.length > 0) {
326
+ const cwdPrefix = `${cwd}/`;
327
+ logger.log(`
328
+ \u26A0 Deep maz-ui/dist imports detected in ${sources.distImportFiles.length} file(s) - manual migration required:`);
329
+ for (const file of sources.distImportFiles)
330
+ logger.log(` - ${file.replace(cwdPrefix, "")}`);
331
+ logger.log(` \u2192 The maz-ui/* wildcard export was removed: import from the public entries (maz-ui/components, maz-ui/composables, \u2026).`);
332
+ }
213
333
  logger.log(`
214
334
  Next: see https://maz-ui.com/guide/migration-v5 for the manual steps`);
215
- logger.log(`(foundation.radius \u2192 scales.rounded.md, MazIcon API, MazBadge sizes, MazChart update-mode).`);
335
+ logger.log(`(foundation.radius \u2192 scales.rounded.md, MazIcon API, MazBadge px / em sizes, MazChart update-mode).`);
216
336
  }
217
337
  run().catch((err) => {
218
338
  logger.error(err);
package/dist/index.d.mts CHANGED
@@ -1,16 +1,39 @@
1
1
  declare function transformImports(content: string): string;
2
+ declare function transformStyleImports(content: string): string;
3
+ declare function hasMazUiRootImport(content: string): boolean;
4
+ declare function hasMazUiDistImport(content: string): boolean;
5
+ declare function hasValibotImport(content: string): boolean;
6
+ declare function transformClassNames(content: string): string;
7
+ declare function transformBareIconNames(content: string): string;
8
+ declare function transformMazIcon(content: string): string;
9
+ declare function hasMazIconLegacyApi(content: string): boolean;
2
10
  declare function transformProps(content: string): string;
11
+ declare function transformPullToRefresh(content: string): string;
12
+ declare function transformAvatarSize(content: string): string;
13
+ declare function transformBadgeSize(content: string): string;
14
+ declare function hasMazBadgeLengthSize(content: string): boolean;
15
+ declare function hasMazAvatarBoundSize(content: string): boolean;
3
16
  declare function transformCssVars(content: string): string;
4
17
  declare function transformHslVar(content: string): string;
5
18
  declare function transformConfig(content: string): string;
6
19
  declare function transformPresetColors(content: string): string;
7
- declare function transformDeps(content: string): string;
20
+ declare function hasFoundationRadius(content: string): boolean;
21
+ interface TransformDepsOptions {
22
+ addUtils?: boolean;
23
+ /**
24
+ * Add `valibot` next to maz-ui: a dependency of maz-ui v4, an optional peer in v5
25
+ */
26
+ addValibot?: boolean;
27
+ }
28
+ declare function transformDeps(content: string, options?: TransformDepsOptions): string;
8
29
  type TransformGroup = 'imports' | 'props' | 'css' | 'config' | 'deps';
9
30
  declare const ALL_GROUPS: readonly TransformGroup[];
10
31
  interface TransformOptions {
11
32
  groups?: readonly TransformGroup[];
33
+ addUtilsDep?: boolean;
34
+ addValibotDep?: boolean;
12
35
  }
13
36
  declare function transformFile(filename: string, content: string, options?: TransformOptions): string;
14
37
 
15
- export { ALL_GROUPS, transformConfig, transformCssVars, transformDeps, transformFile, transformHslVar, transformImports, transformPresetColors, transformProps };
38
+ export { ALL_GROUPS, hasFoundationRadius, hasMazAvatarBoundSize, hasMazBadgeLengthSize, hasMazIconLegacyApi, hasMazUiDistImport, hasMazUiRootImport, hasValibotImport, transformAvatarSize, transformBadgeSize, transformBareIconNames, transformClassNames, transformConfig, transformCssVars, transformDeps, transformFile, transformHslVar, transformImports, transformMazIcon, transformPresetColors, transformProps, transformPullToRefresh, transformStyleImports };
16
39
  export type { TransformGroup, TransformOptions };
package/dist/index.d.ts CHANGED
@@ -1,16 +1,39 @@
1
1
  declare function transformImports(content: string): string;
2
+ declare function transformStyleImports(content: string): string;
3
+ declare function hasMazUiRootImport(content: string): boolean;
4
+ declare function hasMazUiDistImport(content: string): boolean;
5
+ declare function hasValibotImport(content: string): boolean;
6
+ declare function transformClassNames(content: string): string;
7
+ declare function transformBareIconNames(content: string): string;
8
+ declare function transformMazIcon(content: string): string;
9
+ declare function hasMazIconLegacyApi(content: string): boolean;
2
10
  declare function transformProps(content: string): string;
11
+ declare function transformPullToRefresh(content: string): string;
12
+ declare function transformAvatarSize(content: string): string;
13
+ declare function transformBadgeSize(content: string): string;
14
+ declare function hasMazBadgeLengthSize(content: string): boolean;
15
+ declare function hasMazAvatarBoundSize(content: string): boolean;
3
16
  declare function transformCssVars(content: string): string;
4
17
  declare function transformHslVar(content: string): string;
5
18
  declare function transformConfig(content: string): string;
6
19
  declare function transformPresetColors(content: string): string;
7
- declare function transformDeps(content: string): string;
20
+ declare function hasFoundationRadius(content: string): boolean;
21
+ interface TransformDepsOptions {
22
+ addUtils?: boolean;
23
+ /**
24
+ * Add `valibot` next to maz-ui: a dependency of maz-ui v4, an optional peer in v5
25
+ */
26
+ addValibot?: boolean;
27
+ }
28
+ declare function transformDeps(content: string, options?: TransformDepsOptions): string;
8
29
  type TransformGroup = 'imports' | 'props' | 'css' | 'config' | 'deps';
9
30
  declare const ALL_GROUPS: readonly TransformGroup[];
10
31
  interface TransformOptions {
11
32
  groups?: readonly TransformGroup[];
33
+ addUtilsDep?: boolean;
34
+ addValibotDep?: boolean;
12
35
  }
13
36
  declare function transformFile(filename: string, content: string, options?: TransformOptions): string;
14
37
 
15
- export { ALL_GROUPS, transformConfig, transformCssVars, transformDeps, transformFile, transformHslVar, transformImports, transformPresetColors, transformProps };
38
+ export { ALL_GROUPS, hasFoundationRadius, hasMazAvatarBoundSize, hasMazBadgeLengthSize, hasMazIconLegacyApi, hasMazUiDistImport, hasMazUiRootImport, hasValibotImport, transformAvatarSize, transformBadgeSize, transformBareIconNames, transformClassNames, transformConfig, transformCssVars, transformDeps, transformFile, transformHslVar, transformImports, transformMazIcon, transformPresetColors, transformProps, transformPullToRefresh, transformStyleImports };
16
39
  export type { TransformGroup, TransformOptions };
package/dist/index.mjs CHANGED
@@ -1,61 +1,277 @@
1
- const IMPORT_PATH = /(['"])maz-ui\/(styles|aos-styles)\1/g;
1
+ const IMPORT_PATH = /(['"])maz-ui\/(styles|aos-styles|dist\/css\/main\.css|dist\/css\/aos\.css)\1/g;
2
+ const ROOT_IMPORT_SPECIFIER = /(\bfrom\s+|\bimport\s*\(\s*|\brequire\s*\(\s*)(['"])maz-ui\2/g;
3
+ const STYLE_IMPORT_TARGET = {
4
+ "styles": "style.css",
5
+ "aos-styles": "aos.css",
6
+ "dist/css/main.css": "style.css",
7
+ "dist/css/aos.css": "aos.css"
8
+ };
9
+ const MAZ_UI_ROOT_TYPES = /* @__PURE__ */ new Set([
10
+ "MazColor",
11
+ "MazRoundedSize",
12
+ "MazSize",
13
+ "MazSizeUnit",
14
+ "GlobalizableProps",
15
+ "MazComponentName",
16
+ "MazUiDefaultsOptions",
17
+ "MazUiOptions"
18
+ ]);
19
+ const ROOT_NAMED_STATEMENT = /\b(import|export)(\s+type)?\s*\{([^}]*)\}\s*from\s*(['"])maz-ui\4/g;
20
+ const ROOT_TYPES_PLACEHOLDER = "\0maz-ui-root-types\0";
21
+ function isRootTypesOnly(specifiers) {
22
+ const names = specifiers.split(",").map((specifier) => specifier.trim().replace(/^type\s/, "").trim().split(/\sas\s/)[0]?.trim()).filter(Boolean);
23
+ return names.length > 0 && names.every((name) => MAZ_UI_ROOT_TYPES.has(name));
24
+ }
25
+ function maskRootTypeImports(content) {
26
+ return content.replace(ROOT_NAMED_STATEMENT, (statement, _kind, _type, specifiers, quote) => isRootTypesOnly(specifiers) ? statement.replace(`${quote}maz-ui${quote}`, `${quote}${ROOT_TYPES_PLACEHOLDER}${quote}`) : statement);
27
+ }
2
28
  function transformImports(content) {
3
- return content.replace(IMPORT_PATH, (_, q, sub) => `${q}maz-ui/${sub === "styles" ? "style.css" : "aos.css"}${q}`);
4
- }
5
- const ICON_ATTR = /(\s:?)(left|right)-icon\b/g;
6
- const ICON_SLOT = /#(left|right)-icon\b/g;
7
- const ICON_LEFT_RIGHT_SLOT = /#icon-(left|right)\b/g;
8
- const FOOTER_ALIGN = /(\sfooter-align\s*=\s*['"])(left|right)(['"])/g;
9
- const ROUNDED_SIZE_BASE = /(\srounded-size\s*=\s*['"])base(['"])/g;
10
- const ACTIVE_COLOR_BG = /(\sactive-color\s*=\s*['"])background(['"])/g;
11
- const HAS_ICON_CLASS = /\.--has-(left|right)-icon\b/g;
12
- const MAZ_OPEN_TAG = /<Maz[A-Z]\w*\b[^>]*>/g;
29
+ return maskRootTypeImports(content).replace(IMPORT_PATH, (_, q, sub) => `${q}maz-ui/${STYLE_IMPORT_TARGET[sub]}${q}`).replace(ROOT_IMPORT_SPECIFIER, (_, prefix, quote) => `${prefix}${quote}@maz-ui/utils${quote}`).replaceAll(ROOT_TYPES_PLACEHOLDER, "maz-ui");
30
+ }
31
+ function transformStyleImports(content) {
32
+ return content.replace(IMPORT_PATH, (_, q, sub) => `${q}maz-ui/${STYLE_IMPORT_TARGET[sub]}${q}`);
33
+ }
34
+ function hasMazUiRootImport(content) {
35
+ return /(?:\bfrom\s+|\bimport\s*\(\s*|\brequire\s*\(\s*)(['"])maz-ui\1/.test(maskRootTypeImports(content));
36
+ }
37
+ const DIST_IMPORT = /(['"])maz-ui\/dist\/(?!css\/(?:main|aos)\.css\1)[^'"]+\1/;
38
+ function hasMazUiDistImport(content) {
39
+ return DIST_IMPORT.test(content);
40
+ }
41
+ const VALIBOT_IMPORT = /(?:\bfrom\s+|\bimport\s*\(\s*|\brequire\s*\(\s*)(['"])valibot\1/;
42
+ function hasValibotImport(content) {
43
+ return VALIBOT_IMPORT.test(content);
44
+ }
45
+ function openTags(names) {
46
+ return new RegExp(String.raw`<(?:${names})\b(?:[^>"']|"[^"]*"|'[^']*')*>`, "g");
47
+ }
48
+ const MAZ_OPEN_TAG = openTags(String.raw`Maz[A-Z]\w*|maz-[a-z][\w-]*`);
49
+ const ICON_ATTR = /(\s(?::|v-bind:)?)(left|right)-icon\b/g;
50
+ const ICON_ATTR_CAMEL = /(\s(?::|v-bind:)?)(left|right)Icon\b/g;
51
+ const ICON_SLOT = /(#|\bv-slot:)(left|right)-icon\b/g;
52
+ const ICON_LEFT_RIGHT_SLOT = /(#|\bv-slot:)icon-(left|right)\b/g;
53
+ const FOOTER_ALIGN = /(\s(?:footer-align|footerAlign)\s*=\s*['"])(left|right)(['"])/g;
54
+ const ROUNDED_SIZE_BASE = /(\s(?:rounded-size|roundedSize)\s*=\s*['"])base(['"])/g;
55
+ const ACTIVE_COLOR_BG = /(\s(?:active-color|activeColor)\s*=\s*['"])background(['"])/g;
13
56
  const VARIANT_LR = /(\svariant\s*=\s*['"])(left|right)(['"])/g;
14
57
  const COLOR_BG = /(\scolor\s*=\s*['"])background(['"])/g;
15
58
  function flipDirection(value) {
16
59
  return value === "left" ? "start" : "end";
17
60
  }
61
+ const HAS_ICON_CLASS = /\.--has-(left|right)-icon\b/g;
62
+ const INPUT_WRAPPER_CLASS = /\bm-input-wrapper-(left|right)\b/g;
63
+ const DRAWER_ANIM_CLASS = /\bdrawer-anim-(left|right)\b/g;
64
+ function transformClassNames(content) {
65
+ return content.replace(HAS_ICON_CLASS, (_, dir) => `.--has-${flipDirection(dir)}-icon`).replace(INPUT_WRAPPER_CLASS, (_, dir) => `m-input-wrapper-${flipDirection(dir)}`).replace(DRAWER_ANIM_CLASS, (_, dir) => `drawer-anim-${flipDirection(dir)}`);
66
+ }
67
+ const ICON_STRING_TAG = openTags("MazBtn|MazInput|MazLink|MazContainer|MazDropdown|MazAlert|maz-btn|maz-input|maz-link|maz-container|maz-dropdown|maz-alert");
68
+ const BARE_ICON_NAME = /(\s(?:start-icon|end-icon|startIcon|endIcon|icon|dropdown-icon|dropdownIcon)\s*=\s*)(["'])([\w-]+)\2/g;
69
+ function transformBareIconNames(content) {
70
+ return content.replace(ICON_STRING_TAG, (tag) => tag.replace(BARE_ICON_NAME, (_, prefix, quote, name) => `${prefix}${quote}/${name}.svg${quote}`));
71
+ }
72
+ const MAZ_ICON_OPEN_TAG = openTags("MazIcon|maz-icon");
73
+ const ICON_SRC_ATTR = /(\s(?::|v-bind:)?)src(\s*=)/g;
74
+ const ICON_STATIC_NAME = /\sname\s*=\s*(["'])([^"']+)\1/;
75
+ const ICON_STATIC_PATH = /\spath\s*=\s*(["'])([^"']*)\1/;
76
+ const ICON_BOUND_NAME_OR_PATH = /\s(?::|v-bind:)(?:name|path)\s*=/;
77
+ function transformIconName(tag) {
78
+ const nameMatch = tag.match(ICON_STATIC_NAME);
79
+ if (!nameMatch || ICON_BOUND_NAME_OR_PATH.test(tag))
80
+ return tag;
81
+ const pathMatch = tag.match(ICON_STATIC_PATH);
82
+ let base = pathMatch?.[2] ?? "";
83
+ while (base.endsWith("/"))
84
+ base = base.slice(0, -1);
85
+ const quote = nameMatch[1];
86
+ const url = `${base}/${nameMatch[2]}.svg`;
87
+ return tag.replace(ICON_STATIC_PATH, "").replace(ICON_STATIC_NAME, ` icon=${quote}${url}${quote}`);
88
+ }
89
+ function transformMazIcon(content) {
90
+ return content.replace(MAZ_ICON_OPEN_TAG, (tag) => transformIconName(tag.replace(ICON_SRC_ATTR, (_, prefix, suffix) => `${prefix}icon${suffix}`)));
91
+ }
92
+ const ICON_LEGACY_EVENTS = /\s(?:@|v-on:)(?:loaded|unloaded)\b/;
93
+ function hasMazIconLegacyApi(content) {
94
+ for (const match of content.matchAll(MAZ_ICON_OPEN_TAG)) {
95
+ if (ICON_BOUND_NAME_OR_PATH.test(match[0]) || ICON_LEGACY_EVENTS.test(match[0]))
96
+ return true;
97
+ }
98
+ return false;
99
+ }
18
100
  function transformProps(content) {
19
101
  let out = content;
20
- out = out.replace(ICON_ATTR, (_, prefix, dir) => `${prefix}${flipDirection(dir)}-icon`);
21
- out = out.replace(ICON_SLOT, (_, dir) => `#${flipDirection(dir)}-icon`);
22
- out = out.replace(ICON_LEFT_RIGHT_SLOT, (_, dir) => `#icon-${flipDirection(dir)}`);
102
+ out = out.replace(ICON_SLOT, (_, prefix, dir) => `${prefix}${flipDirection(dir)}-icon`);
103
+ out = out.replace(ICON_LEFT_RIGHT_SLOT, (_, prefix, dir) => `${prefix}icon-${flipDirection(dir)}`);
23
104
  out = out.replace(FOOTER_ALIGN, (_, p, dir, s) => `${p}${flipDirection(dir)}${s}`);
24
105
  out = out.replace(ROUNDED_SIZE_BASE, (_, p, s) => `${p}md${s}`);
25
106
  out = out.replace(ACTIVE_COLOR_BG, (_, p, s) => `${p}surface${s}`);
26
- out = out.replace(HAS_ICON_CLASS, (_, dir) => `.--has-${flipDirection(dir)}-icon`);
27
- out = out.replace(MAZ_OPEN_TAG, (tag) => tag.replace(VARIANT_LR, (_, p, dir, s) => `${p}${flipDirection(dir)}${s}`).replace(COLOR_BG, (_, p, s) => `${p}surface${s}`));
107
+ out = transformClassNames(out);
108
+ out = out.replace(MAZ_OPEN_TAG, (tag) => tag.replace(ICON_ATTR, (_, prefix, dir) => `${prefix}${flipDirection(dir)}-icon`).replace(ICON_ATTR_CAMEL, (_, prefix, dir) => `${prefix}${dir === "left" ? "start" : "end"}Icon`).replace(VARIANT_LR, (_, p, dir, s) => `${p}${flipDirection(dir)}${s}`).replace(COLOR_BG, (_, p, s) => `${p}surface${s}`));
109
+ out = transformBareIconNames(out);
110
+ out = transformMazIcon(out);
111
+ out = transformAvatarSize(out);
112
+ out = transformBadgeSize(out);
113
+ out = transformPullToRefresh(out);
28
114
  return out;
29
115
  }
116
+ const MAZ_PULL_TO_REFRESH_OPEN_TAG = openTags("MazPullToRefresh|maz-pull-to-refresh");
117
+ const PULL_TO_REFRESH_ON_CLICK_KEBAB = /(\s:?)on-click\b/g;
118
+ const PULL_TO_REFRESH_ON_CLICK_CAMEL = /(\s:?)onClick\b/g;
119
+ function transformPullToRefresh(content) {
120
+ return content.replace(MAZ_PULL_TO_REFRESH_OPEN_TAG, (tag) => tag.replace(PULL_TO_REFRESH_ON_CLICK_KEBAB, (_, prefix) => `${prefix}on-refresh`).replace(PULL_TO_REFRESH_ON_CLICK_CAMEL, (_, prefix) => `${prefix}onRefresh`));
121
+ }
122
+ const MAZ_AVATAR_OPEN_TAG = openTags("MazAvatar|maz-avatar");
123
+ const AVATAR_SIZE_UNIT = /(?<![:\w-])(size\s*=\s*(["']))(\d+(?:\.\d+)?|\.\d+)([a-z%]+)\2/gi;
124
+ function tripleLength(value) {
125
+ return String(Number.parseFloat((Number.parseFloat(value) * 3).toFixed(4)));
126
+ }
127
+ function transformAvatarSize(content) {
128
+ return content.replace(MAZ_AVATAR_OPEN_TAG, (tag) => tag.replace(AVATAR_SIZE_UNIT, (_, prefix, quote, num, unit) => `${prefix}${tripleLength(num)}${unit}${quote}`));
129
+ }
130
+ const MAZ_BADGE_OPEN_TAG = openTags("MazBadge|maz-badge");
131
+ const BADGE_STATIC_SIZE = /(?<![:\w-])(size\s*=\s*(["']))([^"']*)\2/;
132
+ const BADGE_SIZE_KEYWORDS = /* @__PURE__ */ new Set(["mini", "xs", "sm", "md", "lg", "xl"]);
133
+ const BADGE_KEYWORD_REM = [
134
+ ["mini", 0.625],
135
+ ["xs", 0.6875],
136
+ ["sm", 0.75],
137
+ ["md", 0.875],
138
+ ["lg", 1],
139
+ ["xl", 1.25]
140
+ ];
141
+ function getClosestBadgeSize(rem) {
142
+ let closest = BADGE_KEYWORD_REM[0];
143
+ for (const candidate of BADGE_KEYWORD_REM) {
144
+ if (Math.abs(candidate[1] - rem) <= Math.abs(closest[1] - rem) + 1e-9)
145
+ closest = candidate;
146
+ }
147
+ return closest[0];
148
+ }
149
+ function parseRem(value) {
150
+ if (!value.endsWith("rem"))
151
+ return void 0;
152
+ const number = value.slice(0, -"rem".length);
153
+ return /^[\d.]+$/.test(number) && Number.isFinite(Number(number)) ? Number(number) : void 0;
154
+ }
155
+ function transformBadgeSize(content) {
156
+ return content.replace(MAZ_BADGE_OPEN_TAG, (tag) => tag.replace(BADGE_STATIC_SIZE, (match, prefix, quote, value) => {
157
+ const rem = parseRem(value.trim());
158
+ return rem === void 0 ? match : `${prefix}${getClosestBadgeSize(rem)}${quote}`;
159
+ }));
160
+ }
161
+ function hasMazBadgeLengthSize(content) {
162
+ for (const match of content.matchAll(MAZ_BADGE_OPEN_TAG)) {
163
+ const value = match[0].match(BADGE_STATIC_SIZE)?.[3].trim();
164
+ if (value && !BADGE_SIZE_KEYWORDS.has(value) && parseRem(value) === void 0)
165
+ return true;
166
+ }
167
+ return false;
168
+ }
169
+ const AVATAR_BOUND_SIZE = /(?:^|\s)(?::|v-bind:)size\s*=/;
170
+ function hasMazAvatarBoundSize(content) {
171
+ for (const match of content.matchAll(MAZ_AVATAR_OPEN_TAG)) {
172
+ if (AVATAR_BOUND_SIZE.test(match[0]))
173
+ return true;
174
+ }
175
+ return false;
176
+ }
30
177
  const MAZ_BACKGROUND = /--maz-background(?:(?![-\w])|(?=-(?:foreground|\d)))/g;
31
178
  const MAZ_BORDER_COLOR = /--maz-border(?:(?![-\w])|(?=-(?:foreground|\d)))/g;
32
- const MAZ_VAR = String.raw`var\(--(?:maz|m)-[\w-]+\)`;
33
- const HSL_WITH_ALPHA = new RegExp(String.raw`hsl\(\s*(${MAZ_VAR})\s*\/\s*([^)]+)\)`, "g");
34
- const HSL_NO_ALPHA = new RegExp(String.raw`hsl\(\s*(${MAZ_VAR})\s*\)`, "g");
179
+ const MAZ_VAR = /^var\(--(?:maz|m)-[\w-]+\)$/;
180
+ const HSL_OPEN = /hsl\(/g;
35
181
  function transformCssVars(content) {
36
182
  return content.replace(MAZ_BACKGROUND, "--maz-surface").replace(MAZ_BORDER_COLOR, "--maz-divider");
37
183
  }
184
+ function findClosingParen(content, start) {
185
+ let depth = 1;
186
+ for (let index = start; index < content.length; index++) {
187
+ if (content[index] === "(")
188
+ depth++;
189
+ else if (content[index] === ")")
190
+ depth--;
191
+ if (depth === 0)
192
+ return index;
193
+ }
194
+ return -1;
195
+ }
196
+ function isPlainNumber(value) {
197
+ return /^[\d.]+$/.test(value) && Number.isFinite(Number(value));
198
+ }
199
+ function toPercentage(alpha) {
200
+ if (alpha.endsWith("%") && isPlainNumber(alpha.slice(0, -1)))
201
+ return alpha;
202
+ if (isPlainNumber(alpha))
203
+ return `${Number.parseFloat((Number.parseFloat(alpha) * 100).toFixed(4))}%`;
204
+ return `calc(${alpha} * 100%)`;
205
+ }
206
+ function collapseHsl(inner) {
207
+ const slashIndex = inner.indexOf("/");
208
+ const color = (slashIndex === -1 ? inner : inner.slice(0, slashIndex)).trim();
209
+ if (!MAZ_VAR.test(color))
210
+ return void 0;
211
+ if (slashIndex === -1)
212
+ return color;
213
+ return `color-mix(in srgb, ${color} ${toPercentage(inner.slice(slashIndex + 1).trim())}, transparent)`;
214
+ }
38
215
  function transformHslVar(content) {
39
- return content.replace(HSL_WITH_ALPHA, (_, v, alpha) => `color-mix(in srgb, ${v} ${alpha.trim()}, transparent)`).replace(HSL_NO_ALPHA, (_, v) => v);
216
+ let out = "";
217
+ let cursor = 0;
218
+ for (const match of content.matchAll(HSL_OPEN)) {
219
+ const start = match.index;
220
+ if (start < cursor)
221
+ continue;
222
+ const innerStart = start + match[0].length;
223
+ const end = findClosingParen(content, innerStart);
224
+ const collapsed = end === -1 ? void 0 : collapseHsl(content.slice(innerStart, end));
225
+ if (collapsed === void 0)
226
+ continue;
227
+ out += content.slice(cursor, start) + collapsed;
228
+ cursor = end + 1;
229
+ }
230
+ return out + content.slice(cursor);
40
231
  }
41
232
  const NUXT_INJECT_MAIN_CSS = /\binjectMainCss\b/g;
42
233
  const STRATEGY_HYBRID = /(\bstrategy\s*:\s*['"])hybrid(['"])/g;
43
- const DEPRECATED_THEME_OPTIONS = /^[^\S\n]*(?:injectCriticalCSS|injectFullCSS|injectAllCSSOnServer)\s*:[^,\n]*,?[^\S\n]*\n?/gm;
234
+ const DEPRECATED_OPTION_KEYS = "injectCriticalCSS|injectFullCSS|injectAllCSSOnServer|onlyCritical|criticalColors|criticalFoundation|includeColorScales";
235
+ const DEPRECATED_OPTION_VALUE = String.raw`(?:\[[^\]\n]*\]|\{[^}\n]*\}|[^,}\n]*)`;
236
+ const DEPRECATED_OPTION_INLINE_VALUE = String.raw`(?:\[[^\]\n]*\]|\{[^}\n]*\}|[^,}\n]*?)`;
237
+ const DEPRECATED_OPTION_LINE = new RegExp(String.raw`^[ \t]*(?:${DEPRECATED_OPTION_KEYS})\s*:\s*${DEPRECATED_OPTION_VALUE},?[^\S\n]*\n?`, "gm");
238
+ const DEPRECATED_OPTION_INLINE_AFTER = new RegExp(String.raw`,[^\S\n]*(?:${DEPRECATED_OPTION_KEYS})\s*:\s*${DEPRECATED_OPTION_INLINE_VALUE}(?=[^\S\n]*[,}])`, "g");
239
+ const DEPRECATED_OPTION_INLINE_FIRST = new RegExp(String.raw`(\{[^\S\n]*)(?:${DEPRECATED_OPTION_KEYS})\s*:\s*${DEPRECATED_OPTION_INLINE_VALUE}(?:,[^\S\n]*|(?=[^\S\n]*\}))`, "g");
240
+ const MAZ_UI_REFERENCE = /['"](?:maz-ui|@maz-ui\/[\w-]+)(?:\/[^'"]*)?['"]|\bmazUi\s*:|\bMazUi\w*\b|\b(?:definePreset|setupTheme|useTheme)\s*\(|\bdarkModeStrategy\s*:/;
241
+ function referencesMazUi(content) {
242
+ return MAZ_UI_REFERENCE.test(content);
243
+ }
44
244
  function transformConfig(content) {
45
- return content.replace(NUXT_INJECT_MAIN_CSS, "injectCss").replace(STRATEGY_HYBRID, (_, p, s) => `${p}runtime${s}`).replace(DEPRECATED_THEME_OPTIONS, "");
245
+ const out = content.replace(NUXT_INJECT_MAIN_CSS, "injectCss");
246
+ return (referencesMazUi(out) ? out.replace(STRATEGY_HYBRID, (_, p, s) => `${p}runtime${s}`) : out).replace(DEPRECATED_OPTION_LINE, "").replace(DEPRECATED_OPTION_INLINE_AFTER, "").replace(DEPRECATED_OPTION_INLINE_FIRST, (_, open) => open);
46
247
  }
47
248
  const LIGHT_DARK_BLOCK = /(\b(?:light|dark)\s*:\s*\{)([^{}]*)(\})/g;
48
249
  function transformPresetColors(content) {
250
+ if (!referencesMazUi(content))
251
+ return content;
49
252
  return content.replace(LIGHT_DARK_BLOCK, (_, open, body, close) => {
50
253
  const renamed = body.replace(/(['"]?)background\1(\s*:)/g, (_match, q, suffix) => `${q}surface${q}${suffix}`).replace(/(['"]?)border\1(\s*:)/g, (_match, q, suffix) => `${q}divider${q}${suffix}`);
51
254
  return `${open}${renamed}${close}`;
52
255
  });
53
256
  }
257
+ const FOUNDATION_BLOCK = /\bfoundation\s*:\s*\{([^{}]*)\}/g;
258
+ const RADIUS_KEY_INSIDE = /(?:^|[\s,{])(['"]?)radius\1\s*:/;
259
+ function hasFoundationRadius(content) {
260
+ for (const match of content.matchAll(FOUNDATION_BLOCK)) {
261
+ if (RADIUS_KEY_INSIDE.test(match[1]))
262
+ return true;
263
+ }
264
+ return false;
265
+ }
54
266
  const MAZ_UI_PKG = /^(?:maz-ui|@maz-ui\/.+)$/;
267
+ const UNPUBLISHED_PKGS = /* @__PURE__ */ new Set(["@maz-ui/cli"]);
55
268
  const TARGET_VERSION = "^5.0.0";
269
+ const UTILS_PKG = "@maz-ui/utils";
270
+ const VALIBOT_PKG = "valibot";
271
+ const VALIBOT_VERSION = "^1.3.1";
56
272
  const PROTECTED_PREFIX = /^(?:workspace|link|file|portal|npm|http|https|git\+|github):/;
57
273
  function shouldBump(name, range) {
58
- if (!MAZ_UI_PKG.test(name))
274
+ if (!MAZ_UI_PKG.test(name) || UNPUBLISHED_PKGS.has(name))
59
275
  return false;
60
276
  if (PROTECTED_PREFIX.test(range))
61
277
  return false;
@@ -68,62 +284,110 @@ function detectIndent(content) {
68
284
  return match ? match[1] : 2;
69
285
  }
70
286
  const DEP_FIELDS = ["dependencies", "devDependencies", "peerDependencies"];
71
- function transformDeps(content) {
72
- let pkg;
73
- try {
74
- pkg = JSON.parse(content);
75
- } catch {
76
- return content;
287
+ function getDepMap(pkg, field) {
288
+ const deps = pkg[field];
289
+ if (!deps || typeof deps !== "object" || Array.isArray(deps))
290
+ return void 0;
291
+ return deps;
292
+ }
293
+ function packageHasDep(pkg, name) {
294
+ return DEP_FIELDS.some((field) => {
295
+ const map = getDepMap(pkg, field);
296
+ return !!map && name in map;
297
+ });
298
+ }
299
+ function findMazUiHostField(pkg) {
300
+ for (const field of DEP_FIELDS) {
301
+ const map = getDepMap(pkg, field);
302
+ if (!map)
303
+ continue;
304
+ if (Object.keys(map).some((k) => k === "maz-ui" || k.startsWith("@maz-ui/")))
305
+ return field;
77
306
  }
78
- if (!pkg || typeof pkg !== "object" || Array.isArray(pkg))
79
- return content;
307
+ return void 0;
308
+ }
309
+ function bumpMazUiVersions(pkg) {
80
310
  let changed = false;
81
311
  for (const field of DEP_FIELDS) {
82
- const deps = pkg[field];
83
- if (!deps || typeof deps !== "object" || Array.isArray(deps))
312
+ const map = getDepMap(pkg, field);
313
+ if (!map)
84
314
  continue;
85
- const map = deps;
86
315
  for (const [name, range] of Object.entries(map)) {
87
- if (typeof range !== "string")
88
- continue;
89
- if (shouldBump(name, range)) {
316
+ if (typeof range === "string" && shouldBump(name, range)) {
90
317
  map[name] = TARGET_VERSION;
91
318
  changed = true;
92
319
  }
93
320
  }
94
321
  }
95
- if (!changed)
322
+ return changed;
323
+ }
324
+ function addMissingDeps(pkg, deps) {
325
+ let changed = false;
326
+ for (const [name, version] of deps) {
327
+ const host = findMazUiHostField(pkg);
328
+ if (!host || packageHasDep(pkg, name))
329
+ continue;
330
+ getDepMap(pkg, host)[name] = version;
331
+ changed = true;
332
+ }
333
+ return changed;
334
+ }
335
+ function transformDeps(content, options = {}) {
336
+ let pkg;
337
+ try {
338
+ pkg = JSON.parse(content);
339
+ } catch {
340
+ return content;
341
+ }
342
+ if (!pkg || typeof pkg !== "object" || Array.isArray(pkg))
343
+ return content;
344
+ const addedDeps = [];
345
+ if (options.addUtils)
346
+ addedDeps.push([UTILS_PKG, TARGET_VERSION]);
347
+ if (options.addValibot)
348
+ addedDeps.push([VALIBOT_PKG, VALIBOT_VERSION]);
349
+ const bumped = bumpMazUiVersions(pkg);
350
+ const added = addMissingDeps(pkg, addedDeps);
351
+ if (!bumped && !added)
96
352
  return content;
97
353
  const indent = detectIndent(content);
98
354
  const trailingNewline = content.endsWith("\n") ? "\n" : "";
99
355
  return JSON.stringify(pkg, null, indent) + trailingNewline;
100
356
  }
101
357
  const ALL_GROUPS = ["imports", "props", "css", "config", "deps"];
358
+ const STYLESHEET_EXTENSION = /\.(?:css|scss|sass|less|styl|stylus|pcss|postcss)$/;
359
+ function getFileKind(filename) {
360
+ if (filename.endsWith(".vue"))
361
+ return "vue";
362
+ if (STYLESHEET_EXTENSION.test(filename))
363
+ return "stylesheet";
364
+ if (/\.[cm]?[jt]sx?$/.test(filename))
365
+ return "script";
366
+ if (/(?:^|[\\/])package\.json$/.test(filename))
367
+ return "packageJson";
368
+ return void 0;
369
+ }
370
+ const PIPELINE = [
371
+ { group: "imports", kinds: ["vue", "script"], transform: transformImports },
372
+ { group: "imports", kinds: ["stylesheet"], transform: transformStyleImports },
373
+ { group: "props", kinds: ["vue"], transform: transformProps },
374
+ { group: "css", kinds: ["stylesheet"], transform: transformClassNames },
375
+ { group: "css", kinds: ["vue", "stylesheet", "script"], transform: transformCssVars },
376
+ { group: "css", kinds: ["vue", "stylesheet", "script"], transform: transformHslVar },
377
+ { group: "config", kinds: ["vue", "script"], transform: transformConfig },
378
+ { group: "config", kinds: ["vue", "script"], transform: transformPresetColors },
379
+ {
380
+ group: "deps",
381
+ kinds: ["packageJson"],
382
+ transform: (content, options) => transformDeps(content, { addUtils: options.addUtilsDep, addValibot: options.addValibotDep })
383
+ }
384
+ ];
102
385
  function transformFile(filename, content, options = {}) {
103
386
  const groups = options.groups ?? ALL_GROUPS;
104
- const enabled = (g) => groups.includes(g);
105
- const isVue = filename.endsWith(".vue");
106
- const isCss = filename.endsWith(".css");
107
- const isJs = /\.[cm]?[jt]sx?$/.test(filename);
108
- const isPackageJson = /(?:^|[\\/])package\.json$/.test(filename);
109
- let out = content;
110
- if (enabled("imports") && (isVue || isJs))
111
- out = transformImports(out);
112
- if (enabled("props") && isVue)
113
- out = transformProps(out);
114
- if (enabled("css")) {
115
- if (isVue || isCss)
116
- out = transformCssVars(out);
117
- if (isVue || isCss || isJs)
118
- out = transformHslVar(out);
119
- }
120
- if (enabled("config") && (isVue || isJs)) {
121
- out = transformConfig(out);
122
- out = transformPresetColors(out);
123
- }
124
- if (enabled("deps") && isPackageJson)
125
- out = transformDeps(out);
126
- return out;
387
+ const kind = getFileKind(filename);
388
+ if (!kind)
389
+ return content;
390
+ return PIPELINE.filter((step) => groups.includes(step.group) && step.kinds.includes(kind)).reduce((out, step) => step.transform(out, options), content);
127
391
  }
128
392
 
129
- export { ALL_GROUPS, transformConfig, transformCssVars, transformDeps, transformFile, transformHslVar, transformImports, transformPresetColors, transformProps };
393
+ export { ALL_GROUPS, hasFoundationRadius, hasMazAvatarBoundSize, hasMazBadgeLengthSize, hasMazIconLegacyApi, hasMazUiDistImport, hasMazUiRootImport, hasValibotImport, transformAvatarSize, transformBadgeSize, transformBareIconNames, transformClassNames, transformConfig, transformCssVars, transformDeps, transformFile, transformHslVar, transformImports, transformMazIcon, transformPresetColors, transformProps, transformPullToRefresh, transformStyleImports };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@maz-ui/upgrade",
3
3
  "type": "module",
4
- "version": "5.0.0-canary.ef64a02.0",
4
+ "version": "5.1.0-beta.0",
5
5
  "description": "Automated source rewrites for migrating a project from Maz-UI v4 to v5.",
6
6
  "author": "Louis Mazel <me@loicmazuel.com>",
7
7
  "license": "MIT",
@@ -23,7 +23,8 @@
23
23
  "exports": {
24
24
  ".": {
25
25
  "types": "./dist/index.d.ts",
26
- "import": "./dist/index.mjs"
26
+ "import": "./dist/index.mjs",
27
+ "default": "./dist/index.mjs"
27
28
  }
28
29
  },
29
30
  "main": "./dist/index.mjs",
@@ -37,24 +38,20 @@
37
38
  "dist"
38
39
  ],
39
40
  "engines": {
40
- "node": ">=20.19.0"
41
+ "node": ">=22.12.0"
41
42
  },
42
43
  "dependencies": {
43
- "globby": "^16.2.0",
44
- "@maz-ui/node": "5.0.0-canary.ef64a02.0"
44
+ "@maz-ui/node": "5.1.0-beta.0",
45
+ "globby": "^16.2.4"
45
46
  },
46
47
  "devDependencies": {
47
- "@types/node": "^25.6.0",
48
- "typescript": "^5.9.3",
49
- "unbuild": "^3.6.1",
50
- "vitest": "4.1.5",
51
- "@maz-ui/eslint-config": "5.0.0-canary.ef64a02.0"
48
+ "@maz-ui/eslint-config": "5.0.0",
49
+ "unbuild": "^3.6.1"
52
50
  },
53
51
  "lint-staged": {
54
52
  "*.{js,ts,mjs,mts,cjs,md,yml,json}": "cross-env NODE_ENV=production eslint --fix"
55
53
  },
56
54
  "scripts": {
57
- "postinstall": "nx build",
58
55
  "build": "unbuild",
59
56
  "dev": "unbuild --stub",
60
57
  "typecheck": "tsc --noEmit --skipLibCheck",