@rancher/create-extension 2.0.0 → 3.0.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.
@@ -0,0 +1,242 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const glob = require('glob');
4
+ const semver = require('semver');
5
+ const stats = require('../stats');
6
+ const { nodeRequirement, removePlaceholder } = require('../config');
7
+ const { writeContent, printContent } = require('../utils/content');
8
+
9
+ function packageUpdates(params) {
10
+ const files = glob.sync(params.paths || '**/package.json', { ignore: params.ignore });
11
+
12
+ files.forEach((file) => {
13
+ const originalContent = fs.readFileSync(file, 'utf8');
14
+ let content = originalContent;
15
+
16
+ const [librariesContent, replaceLibraries] = packageUpdatesLibraries(file, content);
17
+
18
+ if (replaceLibraries.length) {
19
+ content = librariesContent;
20
+ printContent(file, `Updating libraries`, replaceLibraries);
21
+ stats.libraries.push(file);
22
+ }
23
+
24
+ const [nodeContent, replaceNode] = packageUpdatesEngine(file, content);
25
+
26
+ if (replaceNode.length) {
27
+ content = nodeContent;
28
+ printContent(file, `Updating node engine`, replaceNode);
29
+ stats.node.push(file);
30
+ }
31
+
32
+ const [resolutionContent, replaceResolution] = packageUpdatesResolution(file, content);
33
+
34
+ if (replaceResolution.length) {
35
+ content = resolutionContent;
36
+ printContent(file, `Updating resolutions`, replaceResolution);
37
+ stats.libraries.push(file);
38
+ }
39
+
40
+ const [annotationsContent, annotationsChanges] = packageUpdatesAnnotations(file, content);
41
+
42
+ if (annotationsChanges.length) {
43
+ content = annotationsContent;
44
+ printContent(file, `Updating annotations`, annotationsChanges);
45
+ stats.annotations = stats.annotations || [];
46
+ stats.annotations.push(file);
47
+ }
48
+
49
+ if (
50
+ replaceLibraries.length ||
51
+ replaceNode.length ||
52
+ replaceResolution.length ||
53
+ annotationsChanges.length
54
+ ) {
55
+ writeContent(file, content, originalContent);
56
+ stats.total.push(file);
57
+ }
58
+ });
59
+ }
60
+
61
+ function packageUpdatesLibraries(file, oldContent) {
62
+ let content = oldContent;
63
+ let parsedJson = JSON.parse(content);
64
+ const replaceLibraries = [];
65
+ const types = ['dependencies', 'devDependencies', 'peerDependencies'];
66
+
67
+ // [Library name, new version or new library, new library version]
68
+ const librariesUpdates = [
69
+ ['@rancher/shell', '^3.0.0'],
70
+ ['@rancher/components', '^0.3.0-alpha.1'],
71
+ ['@nuxt/babel-preset-app', removePlaceholder],
72
+ ['@types/jest', '^29.5.2'],
73
+ ['@typescript-eslint/eslint-plugin', '~5.4.0'],
74
+ ['@typescript-eslint/parser', '~5.4.0'],
75
+ ['@vue/cli-plugin-babel', '~5.0.0'],
76
+ ['@vue/cli-plugin-e2e-cypress', '~5.0.0'],
77
+ ['@vue/cli-plugin-eslint', '~5.0.0'],
78
+ ['@vue/cli-plugin-router', '~5.0.0'],
79
+ ['@vue/cli-plugin-typescript', '~5.0.0'],
80
+ ['@vue/cli-plugin-unit-jest', '~5.0.0'],
81
+ ['@vue/cli-plugin-vuex', '~5.0.0'],
82
+ ['@vue/cli-service', '~5.0.0'],
83
+ ['@vue/eslint-config-typescript', '~9.1.0'],
84
+ ['@vue/vue2-jest', '@vue/vue3-jest', '^27.0.0-alpha.1'],
85
+ ['@vue/test-utils', '~2.0.0-0'],
86
+ ['core-js', '3.25.3'],
87
+ ['cache-loader', '^4.1.0'],
88
+ ['node-polyfill-webpack-plugin', '^3.0.0'],
89
+ ['portal-vue', '~3.0.0'],
90
+ ['require-extension-hooks-babel', '1.0.0'],
91
+ ['require-extension-hooks-vue', '3.0.0'],
92
+ ['require-extension-hooks', '0.3.3'],
93
+ ['sass-loader', '~12.0.0'],
94
+ ['typescript', '~4.5.5'],
95
+ ['vue-router', '~4.0.3'],
96
+ ['vue-virtual-scroll-list', 'vue3-virtual-scroll-list', '0.2.1'],
97
+ ['vue', '~3.2.13'],
98
+ ['vuex', '~4.0.0'],
99
+ ['xterm', '5.2.1'],
100
+ ];
101
+
102
+ types.forEach((type) => {
103
+ if (parsedJson[type]) {
104
+ librariesUpdates.forEach(([library, newVersion, newLibraryVersion]) => {
105
+ if (parsedJson[type][library]) {
106
+ const version = semver.coerce(parsedJson[type][library]);
107
+
108
+ if (newVersion === removePlaceholder) {
109
+ // Remove library
110
+ replaceLibraries.push([library, [parsedJson[type][library], removePlaceholder]]);
111
+ delete parsedJson[type][library];
112
+ content = JSON.stringify(parsedJson, null, 2);
113
+ } else if (newLibraryVersion) {
114
+ // Replace with a new library
115
+ replaceLibraries.push([library, [parsedJson[type][library], newVersion, newLibraryVersion]]);
116
+ content = content.replace(
117
+ `"${ library }": "${ parsedJson[type][library] }"`,
118
+ `"${ newVersion }": "${ newLibraryVersion }"`
119
+ );
120
+ parsedJson = JSON.parse(content);
121
+ } else if (version && semver.lt(version, semver.coerce(newVersion))) {
122
+ // Update library version if outdated
123
+ replaceLibraries.push([library, [parsedJson[type][library], newVersion]]);
124
+ content = content.replace(
125
+ `"${ library }": "${ parsedJson[type][library] }"`,
126
+ `"${ library }": "${ newVersion }"`
127
+ );
128
+ parsedJson = JSON.parse(content);
129
+ }
130
+ } else if (newLibraryVersion && library === newVersion) {
131
+ // Add new library if it doesn't exist
132
+ parsedJson[type][library] = newLibraryVersion;
133
+ replaceLibraries.push([library, [null, newLibraryVersion]]);
134
+ content = JSON.stringify(parsedJson, null, 2);
135
+ }
136
+ });
137
+ }
138
+ });
139
+
140
+ return [content, replaceLibraries];
141
+ }
142
+
143
+ function packageUpdatesEngine(file, oldContent) {
144
+ let content = oldContent;
145
+ let parsedJson = JSON.parse(content);
146
+ const replaceNode = [];
147
+
148
+ if (parsedJson.engines) {
149
+ const outdated = semver.lt(semver.coerce(parsedJson.engines.node), semver.coerce(nodeRequirement));
150
+
151
+ if (outdated) {
152
+ replaceNode.push([parsedJson.engines.node, nodeRequirement]);
153
+ content = content.replace(
154
+ `"node": "${ parsedJson.engines.node }"`,
155
+ `"node": ">=${ nodeRequirement }"`
156
+ );
157
+ parsedJson = JSON.parse(content);
158
+ }
159
+ }
160
+
161
+ return [content, replaceNode];
162
+ }
163
+
164
+ function packageUpdatesResolution(file, oldContent) {
165
+ let content = oldContent;
166
+ let parsedJson = JSON.parse(content);
167
+ const replaceResolution = [];
168
+ const resolutions = [
169
+ ['@vue/cli-service/html-webpack-plugin', '^5.0.0'],
170
+ ['**/webpack', removePlaceholder],
171
+ ];
172
+
173
+ if (parsedJson.resolutions) {
174
+ resolutions.forEach(([library, newVersion]) => {
175
+ if (newVersion === removePlaceholder) {
176
+ delete parsedJson.resolutions[library];
177
+ content = JSON.stringify(parsedJson, null, 2);
178
+ parsedJson = JSON.parse(content);
179
+ } else if (!parsedJson.resolutions[library]) {
180
+ parsedJson.resolutions[library] = newVersion;
181
+ content = JSON.stringify(parsedJson, null, 2);
182
+ parsedJson = JSON.parse(content);
183
+ } else {
184
+ const outdated = semver.lt(
185
+ semver.coerce(parsedJson.resolutions[library]),
186
+ semver.coerce(newVersion)
187
+ );
188
+
189
+ if (outdated) {
190
+ replaceResolution.push([parsedJson.engines.node, nodeRequirement]);
191
+ content = content.replace(
192
+ `"${ library }": "${ parsedJson.resolutions[library] }"`,
193
+ `"${ library }": "${ newVersion }"`
194
+ );
195
+ parsedJson = JSON.parse(content);
196
+ }
197
+ }
198
+ });
199
+ }
200
+
201
+ return [content, replaceResolution];
202
+ }
203
+
204
+ function packageUpdatesAnnotations(file, oldContent) {
205
+ let content = oldContent;
206
+ const parsedJson = JSON.parse(content);
207
+ const changesMade = [];
208
+
209
+ // Check if the file is in pkg/*/package.json
210
+ const dirName = path.dirname(file); // e.g., 'pkg/extension-name'
211
+ const parentDirName = path.basename(path.dirname(dirName)); // Should be 'pkg'
212
+
213
+ if (parentDirName === 'pkg') {
214
+ // The file is in pkg/<extension-name>/package.json
215
+ const annotations = {
216
+ 'catalog.cattle.io/rancher-version': '>= 2.10.0-0',
217
+ 'catalog.cattle.io/ui-extensions-version': '>= 3.0.0',
218
+ };
219
+
220
+ if (!parsedJson.rancher) {
221
+ parsedJson.rancher = { annotations };
222
+ changesMade.push('Added rancher annotations');
223
+ } else if (!parsedJson.rancher.annotations) {
224
+ parsedJson.rancher.annotations = annotations;
225
+ changesMade.push('Added rancher annotations');
226
+ } else {
227
+ // Merge existing annotations with the new ones
228
+ parsedJson.rancher.annotations = {
229
+ ...parsedJson.rancher.annotations,
230
+ ...annotations,
231
+ };
232
+ changesMade.push('Updated rancher annotations');
233
+ }
234
+
235
+ // Update content
236
+ content = JSON.stringify(parsedJson, null, 2);
237
+ }
238
+
239
+ return [content, changesMade];
240
+ }
241
+
242
+ module.exports = packageUpdates;
@@ -0,0 +1,23 @@
1
+ const glob = require('glob');
2
+ const { replaceCases } = require('../utils/content');
3
+
4
+ /**
5
+ * Vue Router
6
+ * Files: .vue, .js, .ts
7
+ */
8
+ const routerUpdates = (params) => {
9
+ const files = glob.sync(params.paths || '**/*.{vue,js,ts}', { ignore: params.ignore });
10
+ const replacementCases = [
11
+ [`import Router from 'vue-router'`, `import { createRouter } from 'vue-router'`, 'Ensure correct import of createRouter'],
12
+ [`vueApp.use(Router)`, `const router = createRouter({})`, 'Update router initialization'],
13
+ [`Vue.use(Router)`, `const router = createRouter({})`, 'Update router initialization'],
14
+
15
+ [/import\s*\{([^}]*)\s* RouteConfig\s*([^}]*)\}\s*from\s*'vue-router'/g, (match, before, after) => `import {${ before.trim() } RouteRecordRaw ${ after.trim() }} from 'vue-router'`, 'Update RouteConfig to RouteRecordRaw'],
16
+ [/import\s*\{([^}]*)\s* Location\s*([^}]*)\}\s*from\s*'vue-router'/g, (match, before, after) => `import {${ before.trim() } RouteLocation ${ after.trim() }} from 'vue-router'`, 'Update Location to RouteLocation'],
17
+ ['mode: \'history\'', 'history: createWebHistory()', 'Update router mode to history'],
18
+ ];
19
+
20
+ replaceCases('router', files, replacementCases, `Updating Vue Router`);
21
+ };
22
+
23
+ module.exports = routerUpdates;
@@ -0,0 +1,19 @@
1
+ const glob = require('glob');
2
+ const { replaceCases } = require('../utils/content');
3
+
4
+ /**
5
+ * Update styles (e.g., replace ::v-deep with :deep)
6
+ */
7
+ const stylesUpdates = (params) => {
8
+ const files = glob.sync(params.paths || '**/*.{vue,scss,css}', { ignore: params.ignore });
9
+ const cases = [
10
+ // Replace '::v-deep' without parentheses with ':deep()'
11
+ [/::v-deep(?!\()/g, ':deep()', 'Replace ::v-deep with :deep()'],
12
+ // Replace '::v-deep(' with ':deep('
13
+ [/::v-deep\(/g, ':deep(', 'Replace ::v-deep( with :deep('],
14
+ ];
15
+
16
+ replaceCases('style', files, cases, `Updating styles`);
17
+ };
18
+
19
+ module.exports = stylesUpdates;
@@ -0,0 +1,19 @@
1
+ const { isSuggest } = require('../config');
2
+
3
+ /* eslint-disable no-console */
4
+ /**
5
+ * TS Updates
6
+ * Files: tsconfig*.json
7
+ *
8
+ * Add information about TS issues, recommend @ts-nocheck as temporary solution
9
+ */
10
+ const tsUpdates = (params) => {
11
+ if (!isSuggest) {
12
+ console.warn('TS checks are stricter and may require to be fixed manually.',
13
+ 'Use @ts-nocheck to give you time to fix them.',
14
+ 'Add exception to your ESLint config to avoid linting errors.');
15
+ }
16
+ // TODO: Add case
17
+ };
18
+
19
+ module.exports = tsUpdates;
@@ -0,0 +1,30 @@
1
+ const fs = require('fs');
2
+ const glob = require('glob');
3
+ const stats = require('../stats');
4
+ const { writeContent } = require('../utils/content');
5
+
6
+ /**
7
+ * Vue config update
8
+ * Files: vue.config.js
9
+ *
10
+ * Verify vue.config presence of deprecated Webpack5 options
11
+ * - devServer.public: 'path' -> client: { webSocketURL: 'path' }
12
+ */
13
+ const vueConfigUpdates = (params) => {
14
+ const files = glob.sync(params.paths || 'vue.config**.js', { ignore: params.ignore });
15
+
16
+ files.forEach((file) => {
17
+ const originalContent = fs.readFileSync(file, 'utf8');
18
+ const content = originalContent;
19
+
20
+ // Verify vue.config presence of deprecated Webpack5 options
21
+ if (content.includes('devServer.public: \'path\'')) {
22
+ writeContent(file, content, originalContent);
23
+ stats.webpack.push(file);
24
+ stats.total.push(file);
25
+ // TODO: Add replacement
26
+ }
27
+ });
28
+ };
29
+
30
+ module.exports = vueConfigUpdates;
@@ -0,0 +1,148 @@
1
+ const glob = require('glob');
2
+ const { replaceCases } = require('../utils/content');
3
+ const {
4
+ vueSetReplacement, vueDeleteReplacement, vueKeyReplacement, vueTemplateKeyReplacement, vueTemplateKeyRemoval
5
+ } = require('../utils/vueSyntax');
6
+
7
+ function vueSyntaxUpdates(params) {
8
+ const files = glob.sync(
9
+ params.paths || '**/*.{vue,js,ts}',
10
+ {
11
+ ignore: [
12
+ ...params.ignore,
13
+ '**/*.spec.ts',
14
+ '**/*.spec.js',
15
+ '**/__tests__/**',
16
+ '**/*.test.ts',
17
+ 'jest.setup.js',
18
+ '**/*.d.ts',
19
+ '**/vue-shim.ts',
20
+ ],
21
+ }
22
+ );
23
+
24
+ const replacementCases = [
25
+ // Handle Vue.set and this.$set
26
+ [/\bVue\.set\(([^,]+),\s*([^,]+),\s*([^)]+)\)/g, (match, obj, prop, val) => vueSetReplacement(match, obj, prop, val), 'Replace Vue.set with direct assignment - https://vuejs.org/guide/extras/reactivity-in-depth.html'],
27
+ [/\bthis\.\$set\(([^,]+),\s*([^,]+),\s*([^)]+)\)/g, (match, obj, prop, val) => vueSetReplacement(match, obj, prop, val), 'Replace this.$set with direct assignment - https://vuejs.org/guide/extras/reactivity-in-depth.html'],
28
+
29
+ // Handle Vue.delete and this.$delete
30
+ [/\bVue\.delete\(([^,]+),\s*([^)]+)\)/g, (match, obj, prop) => vueDeleteReplacement(match, obj, prop), 'Replace Vue.delete with delete operator - https://vuejs.org/guide/extras/reactivity-in-depth.html'],
31
+ [/\bthis\.\$delete\(([^,]+),\s*([^)]+)\)/g, (match, obj, prop) => vueDeleteReplacement(match, obj, prop), 'Replace this.$delete with delete operator - https://vuejs.org/guide/extras/reactivity-in-depth.html'],
32
+
33
+ // Replace Vue import with createApp and initialize vueApp
34
+ [/import Vue from 'vue';?/g, `import { createApp } from 'vue';\nconst vueApp = createApp({});`, 'Replace Vue import with createApp - https://v3-migration.vuejs.org/breaking-changes/global-api.html#a-new-global-api-createapp'],
35
+
36
+ // Replace new Vue({}) with createApp({})
37
+ [/new Vue\(/g, 'createApp(', 'Replace new Vue with createApp - https://v3-migration.vuejs.org/breaking-changes/global-api.html#a-new-global-api-createapp'],
38
+
39
+ // Replace Vue global methods with vueApp methods
40
+ [/\bVue\.(config|directive|filter|mixin|component|use|prototype)\b/g, (match, method) => `vueApp.${ method }`, 'Replace Vue global methods with vueApp methods - https://v3-migration.vuejs.org/breaking-changes/global-api.html#a-new-global-api-createapp'],
41
+
42
+ // Update Vue.prototype to vueApp.config.globalProperties
43
+ [/Vue\.prototype/g, 'vueApp.config.globalProperties', 'Update Vue.prototype to vueApp.config.globalProperties - https://v3-migration.vuejs.org/breaking-changes/global-api.html#a-new-global-api-createapp'],
44
+
45
+ // Remove Vue.util as it's no longer available
46
+ [/Vue\.util/g, '', 'Vue.util is private and no longer available - https://v3-migration.vuejs.org/migration-build.html#partially-compatible-with-caveats'],
47
+
48
+ // Replace vue-virtual-scroll-list with vue3-virtual-scroll-list
49
+ [`vue-virtual-scroll-list`, `vue3-virtual-scroll-list`, 'library update'],
50
+
51
+ // Update Vue.nextTick
52
+ [/\bVue\.nextTick\b/g, 'nextTick', 'Update Vue.nextTick to nextTick - https://v3-migration.vuejs.org/breaking-changes/global-api-treeshaking.html#global-api-treeshaking'],
53
+ [/\bthis\.nextTick\b/g, 'nextTick', 'Update this.nextTick to nextTick - https://v3-migration.vuejs.org/breaking-changes/global-api-treeshaking.html#global-api-treeshaking'],
54
+ // Note: You may need to import nextTick from 'vue' where used.
55
+
56
+ // Update props default function context
57
+ [/(default)\(\)\s*\{([\s\S]*?)return\s+([\s\S]*?)\}/g, (match, def, middle, retVal) => `${ def }(props) {${ middle }return ${ retVal }}`, 'Update props default function context - https://v3-migration.vuejs.org/breaking-changes/props-default-this.html'],
58
+
59
+ // Replace @input with @update:value (excluding plainInputEvent)
60
+ [/@input="((?!.*plainInputEvent).+?)"/g, (_, handler) => `@update:value="${ handler }"`, 'Update @input to @update:value'],
61
+
62
+ // Update v-model syntax
63
+ [/v-model=/g, 'v-model:value=', 'Update v-model to v-model:value'],
64
+
65
+ // Replace .sync modifier with v-model
66
+ [/:([a-zA-Z0-9_-]+)\.sync=/g, 'v-model:$1=', 'Update .sync modifier to v-model'],
67
+
68
+ // Replace click.native with click
69
+ [/(\b@click)\.native/g, '$1', 'Remove .native modifier from @click'],
70
+
71
+ // Remove v-on="$listeners"
72
+ [/v-on="\$listeners"/g, '', 'Remove v-on="$listeners" as it is no longer needed - https://v3-migration.vuejs.org/breaking-changes/listeners-removed.html'],
73
+
74
+ // Update :listeners="$listeners" to v-bind="$attrs"
75
+ [/:listeners="\$listeners"/g, 'v-bind="$attrs"', 'Update :listeners="$listeners" to v-bind="$attrs" - https://v3-migration.vuejs.org/breaking-changes/listeners-removed.html'],
76
+
77
+ // Update $scopedSlots to $slots
78
+ [/\$scopedSlots/g, '$slots', 'Update $scopedSlots to $slots - https://v3-migration.vuejs.org/breaking-changes/slots-unification.html'],
79
+
80
+ // Update slot-scope to v-slot
81
+ [/slot-scope="([^"]+)"/g, 'v-slot="$1"', 'Update slot-scope to v-slot - https://vuejs.org/guide/components/slots.html#scoped-slots'],
82
+
83
+ // Update this.$slots['name'] to this.$slots.name()
84
+ [/this\.\$slots\['([^']+)'\]/g, `this.$slots[\'$1\']()`, `Update this.$slots['name'] to this.$slots.name() - https://vuejs.org/guide/components/slots.html#scoped-slots`],
85
+
86
+ // Remove portal-vue components (now use Teleport)
87
+ [/<\/?portal(-target)?\b[^>]*>/g, '', 'Remove portal components (use Teleport instead) - https://v3.vuejs.org/guide/teleport.html'],
88
+
89
+ // Add :key to <template v-for> elements if missing
90
+ [
91
+ /(<template\b[^>]*v-for="([^"]*)"[^>]*)(>)/g,
92
+ (match, beforeTagEnd, vForContent, tagClose) => vueKeyReplacement(match, beforeTagEnd, vForContent, tagClose),
93
+ 'Add :key to <template v-for> elements if missing, using existing variables',
94
+ ],
95
+
96
+ // Move :key from child elements to <template v-for>
97
+ [
98
+ /(<template\b[^>]*v-for="[^"]*"[^>]*>)([\s\S]*?)(<\/template>)/g,
99
+ (match, templateStart, templateContent, templateEnd) => vueTemplateKeyReplacement(match, templateStart, templateContent, templateEnd),
100
+ 'Move :key from child elements to <template v-for>',
101
+ ],
102
+
103
+ // Remove any remaining :key from child elements within <template v-for>
104
+ [
105
+ /(<template\b[^>]*v-for="[^"]*"[^>]*>)([\s\S]*?)(<\/template>)/g,
106
+ (match, templateStart, templateContent, templateEnd) => vueTemplateKeyRemoval(match, templateStart, templateContent, templateEnd),
107
+ 'Remove any remaining :key from child elements within <template v-for>',
108
+ ],
109
+
110
+ // For other elements with v-for (excluding <template>), ensure :key is present
111
+ [
112
+ /(<(?!template\b)\w+[^>]*v-for="([^"]*)"[^>]*)(>)/g,
113
+ (match, beforeTagEnd, vForContent, tagClose) => vueKeyReplacement(match, beforeTagEnd, vForContent, tagClose),
114
+ 'Add :key to elements with v-for that lack it, using existing variables (excluding <template>)',
115
+ ],
116
+
117
+ // Update custom directives hooks
118
+ [/\binserted\s*\(/g, 'mounted(', 'Update inserted hook to mounted - https://v3-migration.vuejs.org/breaking-changes/custom-directives.html'],
119
+ [/\bcomponentUpdated\s*\(/g, 'updated(', 'Update componentUpdated hook to updated - https://v3-migration.vuejs.org/breaking-changes/custom-directives.html'],
120
+ [/\bunbind\b/g, 'unmounted', 'Update unbind hook to unmounted - https://v3-migration.vuejs.org/breaking-changes/custom-directives.html'],
121
+
122
+ // Update hook events
123
+ [/@hook:/g, '@vue:', 'Update @hook: events to @vue: - https://v3-migration.vuejs.org/breaking-changes/vnode-lifecycle-events.html'],
124
+
125
+ // Remove events API ($on, $off, $once)
126
+ [/\$on\(/g, '', 'Remove $on as the events API has been removed - https://v3-migration.vuejs.org/breaking-changes/events-api.html'],
127
+ [/\$off\(/g, '', 'Remove $off as the events API has been removed - https://v3-migration.vuejs.org/breaking-changes/events-api.html'],
128
+ [/\$once\(/g, '', 'Remove $once as the events API has been removed - https://v3-migration.vuejs.org/breaking-changes/events-api.html'],
129
+
130
+ // Update Vuex store creation
131
+ [/new Vuex\.Store\(/g, 'createStore(', 'Update Vuex store creation - https://vuex.vuejs.org/guide/migrating-to-4-0-from-3-x.html#installation-process'],
132
+ [/import Vuex from 'vuex'/g, `import { createStore } from 'vuex'`, 'Update Vuex import - https://vuex.vuejs.org/guide/migrating-to-4-0-from-3-x.html#installation-process'],
133
+
134
+ // Replace n-link with router-link
135
+ [/<\/?n-link(\s|>)/g, (match) => match.replace('n-link', 'router-link'), 'Replace n-link with router-link'],
136
+
137
+ // Replace v-popover with VDropdown
138
+ [/\bv-popover\b/g, 'VDropdown', 'Replace v-popover with VDropdown'],
139
+ [/<template\s+#popover>/g, '<template #popper>', 'Update slot name from #popover to #popper'],
140
+
141
+ // Extra cases TBD (it seems like we already use the suggested way for arrays)
142
+ // watch option used on arrays not triggered by mutations - https://v3-migration.vuejs.org/breaking-changes/watch.html
143
+ ];
144
+
145
+ replaceCases('vueSyntax', files, replacementCases, 'Updating Vue syntax', params);
146
+ }
147
+
148
+ module.exports = vueSyntaxUpdates;
@@ -0,0 +1,165 @@
1
+ /* eslint-disable no-console */
2
+ const fs = require('fs');
3
+ const { createPatch } = require('diff');
4
+ const stats = require('../stats');
5
+ const {
6
+ isDry, isSuggest, isVerbose, removePlaceholder
7
+ } = require('../config');
8
+
9
+ const diffOutput = [];
10
+
11
+ function printUsage() {
12
+ console.log(`
13
+ Usage: node index.js [options]
14
+
15
+ Options:
16
+
17
+ --dry Dry Run Mode: Run the script without making any changes to your files.
18
+ --verbose Verbose Output: Enable detailed logging.
19
+ --suggest Suggest Mode: Generate a 'suggested_changes.diff' file with proposed changes.
20
+ --paths=<path> Specify Paths: Limit migration to specific paths or files (accepts glob patterns).
21
+ --ignore=<patterns> Ignore Patterns: Exclude specific files or directories (accepts comma-separated glob patterns).
22
+ --files Output Modified Files: List all files modified during the migration.
23
+ --log Generate Log File: Write detailed migration statistics to 'stats.json'.
24
+ --help, -h Display this help message and exit.
25
+ `);
26
+ }
27
+
28
+ function writeContent(file, content, originalContent) {
29
+ if (!isDry && !isSuggest) {
30
+ fs.writeFileSync(file, content);
31
+ } else if (isSuggest) {
32
+ if (typeof originalContent === 'undefined') {
33
+ originalContent = fs.readFileSync(file, 'utf8');
34
+ }
35
+ const diff = createPatch(file, originalContent, content, '', '', { context: 3 });
36
+
37
+ if (diff.trim()) {
38
+ diffOutput.push({ file, diff });
39
+ }
40
+ }
41
+ }
42
+
43
+ function printContent(...args) {
44
+ if (isVerbose) {
45
+ console.log(...args);
46
+ }
47
+ }
48
+
49
+ function escapeRegExp(string) {
50
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
51
+ }
52
+
53
+ function setParams(params) {
54
+ const args = process.argv.slice(2);
55
+ const paramKeys = ['paths', 'ignore'];
56
+
57
+ args.forEach((val) => {
58
+ paramKeys.forEach((key) => {
59
+ if (val.startsWith(`--${ key }=`)) {
60
+ const value = val.split('=')[1];
61
+
62
+ if (key === 'ignore') {
63
+ params.ignorePatterns = value.split(',').map((pattern) => pattern.trim());
64
+ } else {
65
+ params[key] = value;
66
+ }
67
+ }
68
+ });
69
+ });
70
+
71
+ // Add user-specified ignore patterns
72
+ if (params.ignorePatterns.length > 0) {
73
+ params.ignore = params.ignore.concat(params.ignorePatterns);
74
+ }
75
+ }
76
+
77
+ function printLog() {
78
+ if (process.argv.includes('--files')) {
79
+ console.dir(stats, { compact: true });
80
+ }
81
+
82
+ const statsCount = Object.entries(stats).reduce(
83
+ (acc, [key, value]) => ({
84
+ ...acc,
85
+ [key]: value.length,
86
+ }),
87
+ {}
88
+ );
89
+
90
+ console.table(statsCount);
91
+
92
+ if (isSuggest && diffOutput.length > 0) {
93
+ const diffFile = 'suggested_changes.diff';
94
+ let diffContent = '';
95
+
96
+ diffOutput.forEach(({ file, diff }) => {
97
+ diffContent += `--- ${ file }\n+++ ${ file }\n${ diff }\n`;
98
+ });
99
+
100
+ fs.writeFileSync(diffFile, diffContent);
101
+ console.log(`\nSuggested changes have been written to ${ diffFile }`);
102
+ }
103
+
104
+ if (process.argv.includes('--log')) {
105
+ fs.writeFileSync('stats.json', JSON.stringify(stats, null, 2));
106
+ }
107
+ }
108
+
109
+ function replaceCases(fileType, files, replacementCases, printText, params) {
110
+ files.forEach((file) => {
111
+ const originalContent = fs.readFileSync(file, 'utf8');
112
+ let content = originalContent;
113
+ const matchedCases = [];
114
+
115
+ replacementCases.forEach(([pattern, replacement, notes]) => {
116
+ let matches = false;
117
+
118
+ if (typeof pattern === 'string') {
119
+ const regex = new RegExp(escapeRegExp(pattern), 'g');
120
+
121
+ if (regex.test(content)) {
122
+ matches = true;
123
+
124
+ // Exclude cases without replacement
125
+ if (replacement) {
126
+ // Remove discontinued functionalities which do not break
127
+ content = content.replace(regex, replacement === removePlaceholder ? '' : replacement);
128
+ }
129
+ }
130
+ } else if (pattern.test(content)) {
131
+ matches = true;
132
+
133
+ // Exclude cases without replacement
134
+ if (replacement) {
135
+ content = content.replace(pattern, replacement === removePlaceholder ? '' : replacement);
136
+ }
137
+ }
138
+
139
+ if (matches) {
140
+ matchedCases.push({
141
+ pattern: pattern.toString(),
142
+ replacement,
143
+ notes,
144
+ });
145
+ }
146
+ });
147
+
148
+ if (matchedCases.length) {
149
+ writeContent(file, content, originalContent);
150
+ printContent(file, printText, matchedCases);
151
+ stats[fileType].push(file);
152
+ stats.total.push(file);
153
+ }
154
+ });
155
+ }
156
+
157
+ module.exports = {
158
+ printUsage,
159
+ writeContent,
160
+ printContent,
161
+ escapeRegExp,
162
+ setParams,
163
+ printLog,
164
+ replaceCases
165
+ };
@@ -0,0 +1,4 @@
1
+ const vueSyntax = require('./vueSyntax');
2
+ const utils = require('./content');
3
+
4
+ module.exports = { vueSyntax, utils };