vueleap 0.0.0-stage → 0.1.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.
Files changed (46) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/LICENSE +21 -0
  3. package/README.md +351 -2
  4. package/dist/analyze.d.ts +18 -0
  5. package/dist/analyze.js +167 -0
  6. package/dist/bin.d.ts +2 -0
  7. package/dist/bin.js +14 -0
  8. package/dist/cli.d.ts +8 -0
  9. package/dist/cli.js +172 -0
  10. package/dist/compat.d.ts +9 -0
  11. package/dist/compat.js +64 -0
  12. package/dist/context.d.ts +26 -0
  13. package/dist/context.js +75 -0
  14. package/dist/files.d.ts +14 -0
  15. package/dist/files.js +64 -0
  16. package/dist/filters.d.ts +12 -0
  17. package/dist/filters.js +95 -0
  18. package/dist/fix.d.ts +30 -0
  19. package/dist/fix.js +75 -0
  20. package/dist/index.d.ts +14 -0
  21. package/dist/index.js +10 -0
  22. package/dist/report/html.d.ts +9 -0
  23. package/dist/report/html.js +212 -0
  24. package/dist/report/json.d.ts +5 -0
  25. package/dist/report/json.js +18 -0
  26. package/dist/report/mascot.d.ts +2 -0
  27. package/dist/report/mascot.js +3 -0
  28. package/dist/report/pretty.d.ts +11 -0
  29. package/dist/report/pretty.js +85 -0
  30. package/dist/rules.d.ts +8 -0
  31. package/dist/rules.js +465 -0
  32. package/dist/scan/package.d.ts +4 -0
  33. package/dist/scan/package.js +145 -0
  34. package/dist/scan/script.d.ts +14 -0
  35. package/dist/scan/script.js +698 -0
  36. package/dist/scan/sfc.d.ts +3 -0
  37. package/dist/scan/sfc.js +54 -0
  38. package/dist/scan/style.d.ts +9 -0
  39. package/dist/scan/style.js +33 -0
  40. package/dist/scan/template.d.ts +5 -0
  41. package/dist/scan/template.js +242 -0
  42. package/dist/types.d.ts +106 -0
  43. package/dist/types.js +1 -0
  44. package/dist/version.d.ts +3 -0
  45. package/dist/version.js +5 -0
  46. package/package.json +96 -4
package/CHANGELOG.md ADDED
@@ -0,0 +1,34 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project uses
5
+ [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [0.1.1] - 2026-10-12
8
+
9
+ ### Fixed
10
+
11
+ - The npm page now shows the English README. The Portuguese translation moved to `i18n/README.pt-BR.md`.
12
+
13
+ ## [0.1.0] - 2026-10-12
14
+
15
+ ### Added
16
+
17
+ - `npx vueleap` command (with `vue2to3` as an alias) that scans a Vue 2 project
18
+ and reports every Vue 3 breaking change with file, line, column, explanation
19
+ and a link to the official migration guide.
20
+ - 41 rules covering templates, scripts, styles and `package.json`, including
21
+ filters, `$listeners`, `$scopedSlots`, the event bus API, `$set` and
22
+ `$delete`, the global API, lifecycle and directive hook renames, `v-model`
23
+ changes, `.sync`, `.native`, keyCode modifiers, functional components,
24
+ transition classes, render functions and Vue 2 only libraries.
25
+ - Minimal `@vue/compat` configuration (runtime `configureCompat` flags and
26
+ compiler `compatConfig` flags) generated from the findings.
27
+ - `--fix` dry run with unified diffs and `--fix --write` for 8 safe codemods.
28
+ - Pretty terminal report, `--json`, and a standalone `--html` report with light
29
+ and dark themes and filters.
30
+ - `--fail-on` exit codes for CI.
31
+ - Programmatic API: `analyze`, `fix`, `formatPretty`, `formatJson`,
32
+ `formatHtml` and the rule registry.
33
+
34
+ [0.1.0]: https://github.com/kwy404/vueleap/releases/tag/v0.1.0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alexandre Kohler
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,352 @@
1
- # Temporary Holding Version
1
+ <p align="center">
2
+ <picture>
3
+ <source media="(prefers-color-scheme: dark)" srcset="https://cdn.jsdelivr.net/gh/kwy404/vueleap@main/assets/banner-dark.svg">
4
+ <img src="https://cdn.jsdelivr.net/gh/kwy404/vueleap@main/assets/banner.svg" width="820" alt="vueleap, the Vue 2 to Vue 3 migration tool: Hop the frog leaps from a lily pad marked 2 to a lily pad marked 3">
5
+ </picture>
6
+ </p>
2
7
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
8
+ <h1 align="center">vueleap: migrate Vue 2 to Vue 3 with one command</h1>
9
+
10
+ <p align="center">
11
+ <a href="https://www.npmjs.com/package/vueleap"><img src="https://img.shields.io/npm/v/vueleap?color=42b883&label=npm" width="80" alt="vueleap npm version"></a>
12
+ <a href="https://github.com/kwy404/vueleap/actions/workflows/ci.yml"><img src="https://github.com/kwy404/vueleap/actions/workflows/ci.yml/badge.svg" width="96" alt="CI status of the Vue 3 migration tool"></a>
13
+ <a href="https://github.com/kwy404/vueleap/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/vueleap?color=42b883" width="78" alt="MIT license"></a>
14
+ <a href="https://github.com/kwy404/vueleap/blob/main/package.json"><img src="https://img.shields.io/node/v/vueleap?color=42b883" width="90" alt="Node.js 18 or newer"></a>
15
+ </p>
16
+
17
+ <p align="center">
18
+ <a href="https://github.com/kwy404/vueleap/blob/main/i18n/README.pt-BR.md">Leia em Português (pt-BR)</a> &middot;
19
+ <a href="https://kwy404.github.io/vueleap/">Documentation</a> &middot;
20
+ <a href="https://kwy404.github.io/vueleap/report-example.html">Sample report</a> &middot;
21
+ <a href="https://kwy404.github.io/vueleap/vue-3-breaking-changes-checklist.html">Breaking changes checklist</a>
22
+ </p>
23
+
24
+ **Find everything that breaks when you migrate from Vue 2 to Vue 3, in one command.** vueleap is a free, open source Vue 3 migration tool: it scans a Vue 2 project and lists every Vue 3 breaking change file by file and line by line, explains each one with a link to the official migration guide, prints the exact `@vue/compat` flags your project needs for the migration build, and applies safe automatic fixes.
25
+
26
+ ```bash
27
+ npx vueleap
28
+ ```
29
+
30
+ <table>
31
+ <tr>
32
+ <td width="33%" valign="top"><img src="https://cdn.jsdelivr.net/gh/kwy404/vueleap@main/assets/icons/scan.svg" width="40" alt="Scan icon: Vue 2 to Vue 3 breaking changes checker"><br><b>Scan the whole project</b><br>41 checks over templates, scripts, styles and package.json, with file, line and column.</td>
33
+ <td width="33%" valign="top"><img src="https://cdn.jsdelivr.net/gh/kwy404/vueleap@main/assets/icons/fix.svg" width="40" alt="Wrench icon: safe automatic Vue 3 codemods"><br><b>Apply safe fixes</b><br>8 codemods that are provably safe, shown as a diff before anything is written.</td>
34
+ <td width="33%" valign="top"><img src="https://cdn.jsdelivr.net/gh/kwy404/vueleap@main/assets/icons/compat.svg" width="40" alt="Toggle icon: @vue/compat build flags"><br><b>Get exact compat flags</b><br>The minimal <code>configureCompat</code> and <code>compatConfig</code> for the migration build.</td>
35
+ </tr>
36
+ <tr>
37
+ <td width="33%" valign="top"><img src="https://cdn.jsdelivr.net/gh/kwy404/vueleap@main/assets/icons/report.svg" width="40" alt="Chart icon: HTML Vue 3 migration report"><br><b>Share an HTML report</b><br>One standalone file with filters, light and dark themes and guide links.</td>
38
+ <td width="33%" valign="top"><img src="https://cdn.jsdelivr.net/gh/kwy404/vueleap@main/assets/icons/ci.svg" width="40" alt="Pipeline icon: Vue 3 migration checks in CI"><br><b>Gate it in CI</b><br><code>--fail-on error</code> and <code>--json</code> keep Vue 2 only code from coming back.</td>
39
+ <td width="33%" valign="top"><img src="https://cdn.jsdelivr.net/gh/kwy404/vueleap@main/assets/icons/api.svg" width="40" alt="Braces icon: programmatic Node.js API"><br><b>Use the API</b><br><code>analyze()</code> and <code>fix()</code> from Node.js for your own tooling.</td>
40
+ </tr>
41
+ </table>
42
+
43
+ <p align="center">
44
+ <img src="https://cdn.jsdelivr.net/gh/kwy404/vueleap@main/assets/demo.svg" width="100%" alt="Animated terminal demo: npx vueleap scans a Vue 2 app and prints Vue 3 breaking changes such as filters, beforeDestroy renames and keyCode modifiers, then a summary with an effort score">
45
+ </p>
46
+
47
+ ## Table of contents
48
+
49
+ - [How do I check what breaks when migrating from Vue 2 to Vue 3?](#how-do-i-check-what-breaks-when-migrating-from-vue-2-to-vue-3)
50
+ - [What does vueleap check?](#what-does-vueleap-check)
51
+ - [Which fixes can vueleap apply automatically?](#which-fixes-can-vueleap-apply-automatically)
52
+ - [Which @vue/compat flags does my project need?](#which-vuecompat-flags-does-my-project-need)
53
+ - [What does the HTML report look like?](#what-does-the-html-report-look-like)
54
+ - [How do I run vueleap in CI?](#how-do-i-run-vueleap-in-ci)
55
+ - [Can I use vueleap from Node.js?](#can-i-use-vueleap-from-nodejs)
56
+ - [FAQ](#faq)
57
+ - [How does vueleap compare to other Vue 3 migration tools?](#how-does-vueleap-compare-to-other-vue-3-migration-tools)
58
+ - [Documentation and guides](#documentation-and-guides)
59
+ - [Contributing](#contributing)
60
+ - [License](#license)
61
+
62
+ ## How do I check what breaks when migrating from Vue 2 to Vue 3?
63
+
64
+ Run vueleap at the root of your Vue 2 project. No install, no config:
65
+
66
+ ```bash
67
+ npx vueleap # report for the current directory
68
+ npx vueleap ./apps/web # report for another directory
69
+ npx vueleap --html report.html # also write a standalone HTML report
70
+ npx vueleap --fix # preview safe fixes as a diff
71
+ npx vueleap --fix --write # apply them
72
+ ```
73
+
74
+ `vue2to3` is installed as an alias, so `npx -p vueleap vue2to3` works too.
75
+
76
+ vueleap reads `.vue` single file components (template, `<script>`, `<script setup>`, `<style>`), JavaScript, TypeScript, JSX, TSX, CSS, SCSS, Less and Stylus files, plus `package.json`. It respects `.gitignore` (including nested ones), always skips `node_modules` and `dist`, and never follows symbolic links. Pug templates are skipped with a warning, the rest of the file is still analyzed.
77
+
78
+ This is the real output for the sample app in [`test/fixtures/vue2-app`](https://github.com/kwy404/vueleap/tree/main/test/fixtures/vue2-app) (excerpt):
79
+
80
+ <p align="center">
81
+ <img src="https://cdn.jsdelivr.net/gh/kwy404/vueleap@main/assets/terminal.svg" width="100%" alt="vueleap terminal report for a Vue 2 app: Vue 3 breaking changes grouped by rule, each with file, line, column, migration guide link and @vue/compat flag">
82
+ </p>
83
+
84
+ | Option | Description |
85
+ | --- | --- |
86
+ | `--json` | Print the report as JSON (stable `schemaVersion: 1`) |
87
+ | `--html <file>` | Also write a standalone HTML report |
88
+ | `--fix` | Preview the safe automatic fixes as a unified diff (dry run) |
89
+ | `--write` | With `--fix`, write the fixes to disk |
90
+ | `--fail-on <level>` | Exit with code 1 when a finding at or above `error`, `warning` or `info` exists (default `none`) |
91
+ | `--compat` | Print only the `@vue/compat` configuration |
92
+ | `--rule <id>` / `--skip-rule <id>` | Run only, or skip, some rules (repeatable) |
93
+ | `--ignore <pattern>` | Extra ignore pattern in `.gitignore` syntax (repeatable) |
94
+ | `--no-gitignore` | Do not read `.gitignore` files |
95
+ | `--list-rules` | List every rule |
96
+ | `--verbose` | Print every finding (the default caps each rule at 15 lines) |
97
+ | `--no-color` | Disable colors |
98
+
99
+ Exit codes: `0` success, `1` findings at or above `--fail-on`, `2` usage or runtime error.
100
+
101
+ ## What does vueleap check?
102
+
103
+ 41 rules based on the official [Vue 3 migration guide](https://v3-migration.vuejs.org/breaking-changes/). Severity tells you what to expect: **error** breaks at compile time or at runtime, **warning** changes behavior, **info** deserves a review. Each rule links to its guide entry.
104
+
105
+ | Rule | Severity | Where | What it finds | Auto fix | @vue/compat flag |
106
+ | --- | --- | --- | --- | --- | --- |
107
+ | [`filters`](https://v3-migration.vuejs.org/breaking-changes/filters) | error | Template | Filters are removed | | `COMPILER_FILTERS` `FILTERS` |
108
+ | [`listeners`](https://v3-migration.vuejs.org/breaking-changes/listeners-removed) | error | Script | $listeners is removed | | `INSTANCE_LISTENERS` |
109
+ | [`scoped-slots`](https://v3-migration.vuejs.org/breaking-changes/slots-unification) | error | Script | $scopedSlots is removed | yes | `INSTANCE_SCOPED_SLOTS` |
110
+ | [`event-emitter`](https://v3-migration.vuejs.org/breaking-changes/events-api) | error | Script | $on, $off and $once are removed | | `INSTANCE_EVENT_EMITTER` |
111
+ | [`hook-events`](https://v3-migration.vuejs.org/breaking-changes/vnode-lifecycle-events) | warning | Template | hook: lifecycle events are renamed | yes | `INSTANCE_EVENT_HOOKS` |
112
+ | [`set-delete`](https://v3-migration.vuejs.org/breaking-changes/#removed-apis) | error | Script | $set, $delete, Vue.set and Vue.delete are removed | | `GLOBAL_SET` `GLOBAL_DELETE` `INSTANCE_SET` `INSTANCE_DELETE` |
113
+ | [`children`](https://v3-migration.vuejs.org/breaking-changes/children) | error | Script | $children is removed | | `INSTANCE_CHILDREN` |
114
+ | [`destroy`](https://v3-migration.vuejs.org/breaking-changes/#removed-apis) | error | Script | $destroy is removed | | `INSTANCE_DESTROY` |
115
+ | [`global-mount`](https://v3-migration.vuejs.org/breaking-changes/global-api#mounting-app-instance) | error | Script | new Vue() is replaced by createApp() | | `GLOBAL_MOUNT` |
116
+ | [`global-api`](https://v3-migration.vuejs.org/breaking-changes/global-api) | error | Script | Global Vue API moved to the app instance | | `GLOBAL_OBSERVABLE` `GLOBAL_PRIVATE_UTIL` |
117
+ | [`global-prototype`](https://v3-migration.vuejs.org/breaking-changes/global-api#vue-prototype-replaced-by-config-globalproperties) | error | Script | Vue.prototype is replaced by app.config.globalProperties | | `GLOBAL_PROTOTYPE` |
118
+ | [`global-extend`](https://v3-migration.vuejs.org/breaking-changes/global-api#vue-extend-removed) | error | Script | Vue.extend is removed | | `GLOBAL_EXTEND` |
119
+ | [`config-production-tip`](https://v3-migration.vuejs.org/breaking-changes/global-api#config-productiontip-removed) | warning | Script | Vue.config.productionTip is removed | yes | `CONFIG_PRODUCTION_TIP` |
120
+ | [`config-ignored-elements`](https://v3-migration.vuejs.org/breaking-changes/global-api#config-ignoredelements-is-now-config-compileroptions-iscustomelement) | warning | Script | Vue.config.ignoredElements is replaced by isCustomElement | | `CONFIG_IGNORED_ELEMENTS` |
121
+ | [`config-key-codes`](https://v3-migration.vuejs.org/breaking-changes/keycode-modifiers) | error | Script | Vue.config.keyCodes is removed | | `CONFIG_KEY_CODES` |
122
+ | [`global-config`](https://v3-migration.vuejs.org/breaking-changes/global-api#a-new-global-api-createapp) | warning | Script | Vue.config moved to app.config | | `CONFIG_SILENT` `CONFIG_DEVTOOLS` `CONFIG_OPTION_MERGE_STRATS` |
123
+ | [`destroy-hooks`](https://v3-migration.vuejs.org/breaking-changes/#other-minor-changes) | error | Script | beforeDestroy and destroyed are renamed | yes | `OPTIONS_BEFORE_DESTROY` `OPTIONS_DESTROYED` |
124
+ | [`directive-hooks`](https://v3-migration.vuejs.org/breaking-changes/custom-directives) | error | Script | Custom directive hooks are renamed | yes | `CUSTOM_DIR` |
125
+ | [`component-v-model`](https://v3-migration.vuejs.org/breaking-changes/v-model) | error | Script | v-model on components uses modelValue | | `COMPONENT_V_MODEL` |
126
+ | [`sync-modifier`](https://v3-migration.vuejs.org/breaking-changes/v-model) | error | Template | .sync is replaced by v-model:prop | yes | `COMPILER_V_BIND_SYNC` |
127
+ | [`native-modifier`](https://v3-migration.vuejs.org/breaking-changes/v-on-native-modifier-removed) | warning | Template | The .native modifier is removed | | `COMPILER_V_ON_NATIVE` |
128
+ | [`keycode-modifiers`](https://v3-migration.vuejs.org/breaking-changes/keycode-modifiers) | error | Template | KeyCode modifiers are removed | yes | `V_ON_KEYCODE_MODIFIER` |
129
+ | [`functional-components`](https://v3-migration.vuejs.org/breaking-changes/functional-components) | error | Template | Functional components changed | | `COMPONENT_FUNCTIONAL` |
130
+ | [`inline-template`](https://v3-migration.vuejs.org/breaking-changes/inline-template-attribute) | error | Template | inline-template is removed | | `COMPILER_INLINE_TEMPLATE` |
131
+ | [`template-v-for-key`](https://v3-migration.vuejs.org/breaking-changes/key-attribute) | error | Template | Key must be on &lt;template v-for&gt; | | |
132
+ | [`v-if-v-for`](https://v3-migration.vuejs.org/breaking-changes/v-if-v-for) | error | Template | v-if now has higher priority than v-for | | `COMPILER_V_IF_V_FOR_PRECEDENCE` |
133
+ | [`v-bind-object-order`](https://v3-migration.vuejs.org/breaking-changes/v-bind) | warning | Template | v-bind="object" is order sensitive | | `COMPILER_V_BIND_OBJECT_ORDER` |
134
+ | [`transition-classes`](https://v3-migration.vuejs.org/breaking-changes/transition) | warning | Style | Transition classes renamed | yes | `TRANSITION_CLASSES` |
135
+ | [`transition-group-root`](https://v3-migration.vuejs.org/breaking-changes/transition-group) | info | Template | &lt;transition-group&gt; renders no wrapper | | `TRANSITION_GROUP_ROOT` |
136
+ | [`render-function-h`](https://v3-migration.vuejs.org/breaking-changes/render-function-api) | error | Script | Render functions no longer receive h | | `RENDER_FUNCTION` |
137
+ | [`attrs-class-style`](https://v3-migration.vuejs.org/breaking-changes/attrs-includes-class-style) | info | Script | $attrs now includes class and style | | `INSTANCE_ATTRS_CLASS_STYLE` |
138
+ | [`data-object`](https://v3-migration.vuejs.org/breaking-changes/data-option) | error | Script | data must be a function | | `OPTIONS_DATA_FN` |
139
+ | [`watch-array`](https://v3-migration.vuejs.org/breaking-changes/watch) | info | Script | Array watchers need deep for mutations | | `WATCH_ARRAY` |
140
+ | [`deprecated-slot-syntax`](https://v2.vuejs.org/v2/guide/components-slots.html#Deprecated-Syntax) | error | Template | slot and slot-scope attributes are removed | | |
141
+ | [`is-on-element`](https://v3-migration.vuejs.org/breaking-changes/custom-elements-interop#customized-built-in-elements) | warning | Template | is on native elements needs the vue: prefix | | `COMPILER_IS_ON_ELEMENT` |
142
+ | [`composition-api-plugin`](https://github.com/vuejs/composition-api#readme) | warning | Script | @vue/composition-api is built in | | |
143
+ | [`vue-version`](https://v3-migration.vuejs.org/migration-build) | warning | package.json | Vue 2 dependency | | |
144
+ | [`vue2-tooling`](https://v3-migration.vuejs.org/migration-build#installation) | warning | package.json | Vue 2 build or test tooling | | |
145
+ | [`vue-router-v3`](https://router.vuejs.org/guide/migration/) | error | package.json | vue-router 3 does not support Vue 3 | | |
146
+ | [`vuex-v3`](https://vuex.vuejs.org/guide/migrating-to-4-0-from-3-x.html) | error | package.json | Vuex 3 does not support Vue 3 | | |
147
+ | [`vue2-libraries`](https://v3-migration.vuejs.org/) | error | package.json | Vue 2 only library | | |
148
+
149
+ The same list, with explanations, is on the [Vue 3 breaking changes checklist](https://kwy404.github.io/vueleap/vue-3-breaking-changes-checklist.html).
150
+
151
+ ## Which fixes can vueleap apply automatically?
152
+
153
+ Only rewrites that keep exactly the same meaning in Vue 3. Formatting is preserved because vueleap edits the exact character ranges it reported, nothing is reprinted.
154
+
155
+ | Rule | Before | After |
156
+ | --- | --- | --- |
157
+ | `destroy-hooks` | `beforeDestroy()`, `destroyed()` | `beforeUnmount()`, `unmounted()` |
158
+ | `directive-hooks` | `bind`, `inserted`, `componentUpdated`, `unbind` | `beforeMount`, `mounted`, `updated`, `unmounted` |
159
+ | `scoped-slots` | `this.$scopedSlots.default` | `this.$slots.default` |
160
+ | `transition-classes` | `.fade-enter`, `.v-leave`, `enter-class` | `.fade-enter-from`, `.v-leave-from`, `enter-from-class` |
161
+ | `keycode-modifiers` | `@keyup.13`, `@keyup.27` | `@keyup.enter`, `@keyup.esc` |
162
+ | `sync-modifier` | `:title.sync="doc.title"` | `v-model:title="doc.title"` |
163
+ | `hook-events` | `@hook:mounted` | `@vue:mounted` |
164
+ | `config-production-tip` | `Vue.config.productionTip = false` | (line removed) |
165
+
166
+ Safety rules:
167
+
168
+ - Dry run by default: `--fix` prints a diff, `--fix --write` writes.
169
+ - A rename is skipped when the new name already exists in the same object (for example a half migrated mixin with both `destroyed` and `unmounted`).
170
+ - Transition classes are renamed only for names used by a `<transition name>` or that have `-enter-active` style classes, so an unrelated `.btn-enter` is never touched.
171
+ - KeyCode modifiers are only rewritten when one key name matches (`.112` is reported, not guessed).
172
+ - Files are resolved inside the project root and checked again with their real path, and a file that changed since the analysis aborts the write.
173
+
174
+ ## Which @vue/compat flags does my project need?
175
+
176
+ The [migration build](https://v3-migration.vuejs.org/migration-build) (`@vue/compat`) runs Vue 2 code on Vue 3 and lets you switch each Vue 2 behavior on or off. vueleap maps every finding to its flag and prints the smallest configuration: Vue 3 behavior by default (`MODE: 3`) with only the flags the code still needs. Runtime flags go to `configureCompat()`, template compiler flags (`COMPILER_*`) go to your build tool.
177
+
178
+ ```bash
179
+ npx vueleap --compat
180
+ ```
181
+
182
+ Real output for [`test/fixtures/edge-cases`](https://github.com/kwy404/vueleap/tree/main/test/fixtures/edge-cases):
183
+
184
+ ```js
185
+ // main.js (with 'vue' aliased to '@vue/compat')
186
+ import { configureCompat } from 'vue'
187
+
188
+ configureCompat({
189
+ MODE: 3,
190
+ CUSTOM_DIR: true,
191
+ GLOBAL_DELETE: true,
192
+ GLOBAL_EXTEND: true,
193
+ GLOBAL_SET: true,
194
+ INSTANCE_EVENT_EMITTER: true,
195
+ INSTANCE_LISTENERS: true,
196
+ OPTIONS_BEFORE_DESTROY: true,
197
+ OPTIONS_DESTROYED: true,
198
+ RENDER_FUNCTION: true,
199
+ TRANSITION_CLASSES: true,
200
+ WATCH_ARRAY: true,
201
+ })
202
+
203
+ // vite.config.js
204
+ import vue from '@vitejs/plugin-vue'
205
+
206
+ export default {
207
+ resolve: { alias: { vue: '@vue/compat' } },
208
+ plugins: [
209
+ vue({
210
+ template: {
211
+ compilerOptions: {
212
+ compatConfig: {
213
+ MODE: 3,
214
+ COMPILER_FILTERS: true,
215
+ },
216
+ },
217
+ },
218
+ }),
219
+ ],
220
+ }
221
+ ```
222
+
223
+ Every flag name is checked by the test suite against the `DeprecationTypes` and `CompilerDeprecationTypes` enums shipped with Vue. Remove flags as you fix findings, and drop `@vue/compat` when the list is empty. More in [@vue/compat build flags explained](https://kwy404.github.io/vueleap/vue-compat-build-flags.html).
224
+
225
+ ## What does the HTML report look like?
226
+
227
+ `npx vueleap --html report.html` writes one standalone file (no external scripts or styles) that you can attach to a ticket or share with your team. It has summary cards, the compat configuration with a copy button, filters by severity, rule, text and fixability, and light and dark themes. [Open the live sample report](https://kwy404.github.io/vueleap/report-example.html).
228
+
229
+ <p align="center">
230
+ <img src="https://cdn.jsdelivr.net/gh/kwy404/vueleap@main/assets/report.png" width="100%" alt="vueleap HTML report for a Vue 2 app: summary cards with errors, warnings and auto fixable count, minimal @vue/compat configuration and Vue 3 breaking changes grouped by rule">
231
+ </p>
232
+
233
+ ## How do I run vueleap in CI?
234
+
235
+ Fail the build when Vue 2 only code is introduced, or track migration progress over time:
236
+
237
+ ```yaml
238
+ # .github/workflows/vue3-migration.yml
239
+ name: Vue 3 migration
240
+ on: [push, pull_request]
241
+ jobs:
242
+ vueleap:
243
+ runs-on: ubuntu-latest
244
+ steps:
245
+ - uses: actions/checkout@v4
246
+ - uses: actions/setup-node@v4
247
+ with:
248
+ node-version: 20
249
+ - run: npx vueleap --fail-on error --html vueleap-report.html
250
+ - uses: actions/upload-artifact@v4
251
+ if: always()
252
+ with:
253
+ name: vueleap-report
254
+ path: vueleap-report.html
255
+ ```
256
+
257
+ `--json` prints a machine readable report (`summary`, `effort`, `compat`, `findings` with `ruleId`, `severity`, `file`, `line`, `column`, `docs`, `compatFlags`, `fixable`) for dashboards and custom checks.
258
+
259
+ ## Can I use vueleap from Node.js?
260
+
261
+ Yes. The package is ESM and ships TypeScript types.
262
+
263
+ ```ts
264
+ import { promises as fs } from 'node:fs'
265
+ import { analyze, fix, formatHtml, compatSnippet } from 'vueleap'
266
+
267
+ const result = await analyze('./my-vue2-app', { skipRules: ['attrs-class-style'] })
268
+
269
+ console.log(result.summary.error, 'errors in', result.summary.filesWithFindings, 'files')
270
+ console.log('effort score:', result.effort.score, result.effort.level)
271
+ console.log(compatSnippet(result.compat))
272
+
273
+ for (const finding of result.findings) {
274
+ console.log(`${finding.file}:${finding.line}:${finding.column}`, finding.ruleId, finding.message)
275
+ }
276
+
277
+ // Preview fixes (nothing is written without write: true)
278
+ const { files } = await fix('./my-vue2-app', { write: false })
279
+ for (const file of files) console.log(file.diff)
280
+
281
+ await fs.writeFile('report.html', formatHtml(result))
282
+ ```
283
+
284
+ Main exports: `analyze`, `fix`, `planFixes`, `applyEdits`, `formatPretty`, `formatJson`, `formatHtml`, `compatSnippet`, `collectCompatFlags`, `RULES`, plus the `AnalysisResult`, `Finding` and `RuleMeta` types.
285
+
286
+ ## FAQ
287
+
288
+ ### How long does a Vue 2 to Vue 3 migration take?
289
+
290
+ It depends on how much the code relies on removed APIs and on Vue 2 only libraries, so measure instead of guessing: `npx vueleap` ends with an **effort score**. Each finding adds its rule weight (an event bus call weighs more than a lifecycle rename, a UI library such as Vuetify 2 or BootstrapVue weighs a lot), and auto fixable findings count for a tenth. As a rough guide one point is about an hour of focused work, but the score is best used to compare projects and track progress. The sample app scores 59, mostly because of BootstrapVue, vue-router 3 and Vuex 3.
291
+
292
+ ### Should I use @vue/compat?
293
+
294
+ For medium and large apps, usually yes: the migration build lets the app run on Vue 3 while you convert code step by step, and vueleap tells you exactly which flags to enable. It is a migration aid, not a long term runtime, and libraries that rely on Vue 2 internals (the official docs mention Vuetify, Quasar and Element UI) need their Vue 3 versions anyway. Small apps are often faster to migrate directly, using `npx vueleap --fix --write` and the report.
295
+
296
+ ### Is Vue 2 still supported?
297
+
298
+ No. Vue 2 reached end of life on **December 31, 2023**. It no longer receives updates from the Vue team, including security fixes, and most of the ecosystem now targets Vue 3. Existing apps keep working, but the risk grows over time. See [Vue 2 end of life](https://kwy404.github.io/vueleap/vue-2-end-of-life.html).
299
+
300
+ ### Does vueleap change my code?
301
+
302
+ Only with `--fix --write`. Without `--write`, `--fix` prints a diff. A plain `npx vueleap` only reads files.
303
+
304
+ ### Does it work with TypeScript, `<script setup>` and class components?
305
+
306
+ Yes: `lang="ts"` and `lang="tsx"` blocks, `.ts` and `.tsx` files, Vue 2.7 `<script setup>` (including the 2.7 only `set` and `del` imports), and `vue-property-decorator` or `vue-class-component` classes are analyzed.
307
+
308
+ ### Does it support Nuxt 2?
309
+
310
+ It analyzes the Vue code of a Nuxt 2 app and reports `nuxt` 2 in `package.json` with a pointer to the Nuxt migration guide, but it does not check Nuxt specific APIs.
311
+
312
+ ### Can it produce false positives?
313
+
314
+ Static analysis can only see code, not runtime values, so a few findings may need judgment (for example `$attrs` info findings or a `v-bind="obj"` order warning where the keys never overlap). Component checks only run on objects that really look like Vue options, and the test suite includes look alike code that must not be reported. Use `--skip-rule` for checks that do not apply to you.
315
+
316
+ ## How does vueleap compare to other Vue 3 migration tools?
317
+
318
+ | | vueleap | [vue-codemod](https://github.com/vuejs/vue-codemod) | [GoGoCode](https://github.com/thx/gogocode) (`gogocode-plugin-vue`) | [eslint-plugin-vue](https://eslint.vuejs.org/) |
319
+ | --- | --- | --- | --- | --- |
320
+ | Main purpose | Project wide report and plan | Code transformations | Code transformations | Linting in the editor and CI |
321
+ | Reports breaking changes with file and line | Yes | No | No | Yes, with its `no-deprecated-*` rules |
322
+ | `@vue/compat` flags for your project | Yes | No | No | No |
323
+ | Vue 2 only dependencies and effort estimate | Yes | No | No | No |
324
+ | Automatic rewrites | 8 safe codemods | Several transformations (jscodeshift) | Broad transformation of a whole project | Autofix for some rules |
325
+ | Status | Active | README says "experimental" | Plugin for Vue 2 to 3 published by the GoGoCode team | Maintained by the Vue team |
326
+
327
+ They work well together: run vueleap to see the size of the migration and to configure the compat build, use a transformation tool if you want large automated rewrites, and keep eslint-plugin-vue in your editor afterwards.
328
+
329
+ ## Documentation and guides
330
+
331
+ - [vueleap documentation home](https://kwy404.github.io/vueleap/)
332
+ - [How to migrate Vue 2 to Vue 3, step by step](https://kwy404.github.io/vueleap/migrate-vue-2-to-vue-3.html)
333
+ - [Vue 3 breaking changes checklist](https://kwy404.github.io/vueleap/vue-3-breaking-changes-checklist.html)
334
+ - [@vue/compat build flags explained](https://kwy404.github.io/vueleap/vue-compat-build-flags.html)
335
+ - [Vue filters removed in Vue 3](https://kwy404.github.io/vueleap/vue-filters-removed-vue-3.html)
336
+ - [Event bus in Vue 3 ($on, $off, $once)](https://kwy404.github.io/vueleap/vue-event-bus-vue-3.html)
337
+ - [v-model changes in Vue 3](https://kwy404.github.io/vueleap/v-model-changes-vue-3.html)
338
+ - [Vue 2 end of life](https://kwy404.github.io/vueleap/vue-2-end-of-life.html)
339
+ - [Sample HTML report](https://kwy404.github.io/vueleap/report-example.html)
340
+
341
+ ## Contributing
342
+
343
+ Issues and pull requests are welcome. [CONTRIBUTING.md](https://github.com/kwy404/vueleap/blob/main/CONTRIBUTING.md) explains the architecture and how to add a rule in a few steps (register it, detect it, add a fixture, update the snapshot).
344
+
345
+ ```bash
346
+ npm install
347
+ npm run build && npm run typecheck && npm run lint && npm test
348
+ ```
349
+
350
+ ## License
351
+
352
+ [MIT](https://github.com/kwy404/vueleap/blob/main/LICENSE) &copy; 2026 [Alexandre Kohler](https://github.com/kwy404)
@@ -0,0 +1,18 @@
1
+ import { FileContext } from './context.js';
2
+ import type { AnalysisResult, AnalyzeOptions, EffortEstimate, Finding, Summary } from './types.js';
3
+ export declare function analyzeFile(file: string, text: string, transitionNames: ReadonlySet<string>, enabled: (id: string) => boolean): FileContext;
4
+ export declare function summarize(findings: readonly Finding[], filesScanned: number): Summary;
5
+ /**
6
+ * Relative effort score. Each finding adds its rule weight (fixable findings
7
+ * count for a tenth, since `--fix` handles them). As a rough guide one point
8
+ * is about an hour of focused work, but treat it as a comparison tool.
9
+ */
10
+ export declare function estimateEffort(findings: readonly Finding[]): EffortEstimate;
11
+ /**
12
+ * Scans a Vue 2 project and returns every finding that matters for Vue 3.
13
+ *
14
+ * @example
15
+ * const result = await analyze('./my-app')
16
+ * console.log(result.summary.error, result.compat.runtime)
17
+ */
18
+ export declare function analyze(projectDir: string, options?: AnalyzeOptions): Promise<AnalysisResult>;
@@ -0,0 +1,167 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { parse as parseTemplate } from '@vue/compiler-dom';
4
+ import { collectCompatFlags } from './compat.js';
5
+ import { FileContext } from './context.js';
6
+ import { listFiles, SCRIPT_EXTENSIONS, STYLE_EXTENSIONS } from './files.js';
7
+ import { RULES, RULES_BY_ID } from './rules.js';
8
+ import { scanPackageJson } from './scan/package.js';
9
+ import { scanScript } from './scan/script.js';
10
+ import { EMITS_INPUT, scanSfc } from './scan/sfc.js';
11
+ import { collectTransitionNames, scanStyle } from './scan/style.js';
12
+ import { scanTemplate } from './scan/template.js';
13
+ import { NAME, VERSION } from './version.js';
14
+ const SEVERITY_RANK = { error: 0, warning: 1, info: 2 };
15
+ function ruleFilter(options) {
16
+ for (const id of [...(options.rules ?? []), ...(options.skipRules ?? [])]) {
17
+ if (!RULES_BY_ID.has(id))
18
+ throw new Error(`Unknown rule "${id}". Run "vueleap --list-rules" to see all rules.`);
19
+ }
20
+ const only = options.rules && options.rules.length > 0 ? new Set(options.rules) : undefined;
21
+ const skip = new Set(options.skipRules ?? []);
22
+ return (id) => (!only || only.has(id)) && !skip.has(id);
23
+ }
24
+ function scriptLangFor(ext) {
25
+ if (ext === '.ts' || ext === '.mts' || ext === '.cts')
26
+ return 'ts';
27
+ if (ext === '.tsx')
28
+ return 'tsx';
29
+ return 'jsx';
30
+ }
31
+ /**
32
+ * Scans a template given as a string (for example `template: '...'` in a
33
+ * component). The string is parsed behind a blank prefix of the same length
34
+ * as the code before it, so every location is already a file offset.
35
+ */
36
+ function templateStringScanner(ctx) {
37
+ return (content, offset) => {
38
+ if (!content.includes('<'))
39
+ return;
40
+ try {
41
+ const padding = ctx.text.slice(0, offset).replace(/[^\r\n]/g, ' ');
42
+ const ast = parseTemplate(padding + content, { onError: () => undefined });
43
+ scanTemplate(ctx, ast);
44
+ }
45
+ catch {
46
+ ctx.warn('Could not parse an inline template string, skipped.');
47
+ }
48
+ };
49
+ }
50
+ export function analyzeFile(file, text, transitionNames, enabled) {
51
+ const ctx = new FileContext(file, text, enabled);
52
+ const ext = path.extname(file).toLowerCase();
53
+ try {
54
+ if (file === 'package.json') {
55
+ scanPackageJson(ctx);
56
+ }
57
+ else if (ext === '.vue') {
58
+ scanSfc(ctx, transitionNames, templateStringScanner(ctx));
59
+ }
60
+ else if (SCRIPT_EXTENSIONS.has(ext)) {
61
+ scanScript(ctx, text, {
62
+ offset: 0,
63
+ lang: scriptLangFor(ext),
64
+ isSfc: false,
65
+ emitsInput: EMITS_INPUT.test(text),
66
+ onTemplateString: templateStringScanner(ctx),
67
+ });
68
+ }
69
+ else if (STYLE_EXTENSIONS.has(ext)) {
70
+ scanStyle(ctx, text, 0, transitionNames);
71
+ }
72
+ }
73
+ catch (error) {
74
+ ctx.warn(`Analysis failed for this file: ${error.message}`);
75
+ }
76
+ return ctx;
77
+ }
78
+ export function summarize(findings, filesScanned) {
79
+ const byRule = new Map();
80
+ for (const finding of findings) {
81
+ const rule = RULES_BY_ID.get(finding.ruleId);
82
+ let entry = byRule.get(finding.ruleId);
83
+ if (!entry) {
84
+ entry = {
85
+ ruleId: finding.ruleId,
86
+ title: rule?.title ?? finding.ruleId,
87
+ severity: finding.severity,
88
+ docs: rule?.docs ?? finding.docs,
89
+ count: 0,
90
+ fixable: 0,
91
+ };
92
+ byRule.set(finding.ruleId, entry);
93
+ }
94
+ entry.count++;
95
+ if (finding.fixable)
96
+ entry.fixable++;
97
+ }
98
+ const order = new Map(RULES.map((rule, index) => [rule.id, index]));
99
+ const rules = [...byRule.values()].sort((a, b) => SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity] ||
100
+ b.count - a.count ||
101
+ (order.get(a.ruleId) ?? 0) - (order.get(b.ruleId) ?? 0));
102
+ return {
103
+ filesScanned,
104
+ filesWithFindings: new Set(findings.map((f) => f.file)).size,
105
+ total: findings.length,
106
+ error: findings.filter((f) => f.severity === 'error').length,
107
+ warning: findings.filter((f) => f.severity === 'warning').length,
108
+ info: findings.filter((f) => f.severity === 'info').length,
109
+ fixable: findings.filter((f) => f.fixable).length,
110
+ byRule: rules,
111
+ };
112
+ }
113
+ /**
114
+ * Relative effort score. Each finding adds its rule weight (fixable findings
115
+ * count for a tenth, since `--fix` handles them). As a rough guide one point
116
+ * is about an hour of focused work, but treat it as a comparison tool.
117
+ */
118
+ export function estimateEffort(findings) {
119
+ let total = 0;
120
+ for (const finding of findings) {
121
+ const weight = finding.effort ?? RULES_BY_ID.get(finding.ruleId)?.effort ?? 0.5;
122
+ total += finding.fixable ? weight / 10 : weight;
123
+ }
124
+ const score = total === 0 ? 0 : Math.max(1, Math.round(total));
125
+ const level = score === 0 ? 'none' : score < 8 ? 'small' : score < 40 ? 'moderate' : score < 120 ? 'large' : 'very large';
126
+ return { score, level };
127
+ }
128
+ /**
129
+ * Scans a Vue 2 project and returns every finding that matters for Vue 3.
130
+ *
131
+ * @example
132
+ * const result = await analyze('./my-app')
133
+ * console.log(result.summary.error, result.compat.runtime)
134
+ */
135
+ export async function analyze(projectDir, options = {}) {
136
+ const root = path.resolve(projectDir);
137
+ const stat = await fs.stat(root).catch(() => undefined);
138
+ if (!stat?.isDirectory())
139
+ throw new Error(`Not a directory: ${root}`);
140
+ const enabled = ruleFilter(options);
141
+ const files = await listFiles(root, { ignore: options.ignore, gitignore: options.gitignore });
142
+ const texts = new Map();
143
+ for (const file of files)
144
+ texts.set(file, await fs.readFile(path.join(root, file), 'utf8'));
145
+ const transitionNames = new Set(['v']);
146
+ for (const [file, text] of texts) {
147
+ if (file.endsWith('.vue') || STYLE_EXTENSIONS.has(path.extname(file).toLowerCase()))
148
+ collectTransitionNames(text, transitionNames);
149
+ }
150
+ const findings = [];
151
+ const warnings = [];
152
+ for (const [file, text] of texts) {
153
+ const ctx = analyzeFile(file, text, transitionNames, enabled);
154
+ findings.push(...ctx.findings);
155
+ warnings.push(...ctx.warnings);
156
+ }
157
+ findings.sort((a, b) => (a.file < b.file ? -1 : a.file > b.file ? 1 : a.line - b.line || a.column - b.column));
158
+ return {
159
+ tool: { name: NAME, version: VERSION },
160
+ projectDir: root,
161
+ findings,
162
+ warnings,
163
+ summary: summarize(findings, files.length),
164
+ effort: estimateEffort(findings),
165
+ compat: collectCompatFlags(findings),
166
+ };
167
+ }
package/dist/bin.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/bin.js ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ import pc from 'picocolors';
3
+ import { main } from './cli.js';
4
+ main(process.argv.slice(2), {
5
+ stdout: (text) => process.stdout.write(text),
6
+ stderr: (text) => process.stderr.write(text),
7
+ cwd: process.cwd(),
8
+ color: pc.isColorSupported && Boolean(process.stdout.isTTY),
9
+ }).then((code) => {
10
+ process.exitCode = code;
11
+ }, (error) => {
12
+ process.stderr.write(`${String(error)}\n`);
13
+ process.exitCode = 2;
14
+ });
package/dist/cli.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ export interface CliIO {
2
+ stdout: (text: string) => void;
3
+ stderr: (text: string) => void;
4
+ cwd: string;
5
+ color: boolean;
6
+ }
7
+ /** Runs the command line interface and returns the exit code. */
8
+ export declare function main(argv: string[], io: CliIO): Promise<number>;