@rancher/create-extension 1.0.1 → 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.
@@ -3,28 +3,11 @@
3
3
  "version": "0.1.0",
4
4
  "private": false,
5
5
  "engines": {
6
- "node": ">=16"
7
- },
8
- "dependencies": {
9
- "cache-loader": "^4.1.0",
10
- "color": "4.2.3",
11
- "ip": "2.0.1",
12
- "node-polyfill-webpack-plugin": "^3.0.0"
6
+ "node": ">=20"
13
7
  },
8
+ "dependencies": {},
14
9
  "resolutions": {
15
- "d3-color": "3.1.0",
16
- "ejs": "3.1.9",
17
- "follow-redirects": "1.15.2",
18
- "glob": "7.2.3",
19
- "glob-parent": "6.0.2",
20
- "json5": "2.2.3",
21
- "@types/lodash": "4.17.5",
22
- "merge": "2.1.1",
23
- "node-forge": "1.3.1",
24
- "nth-check": "2.1.1",
25
- "qs": "6.11.1",
26
- "roarr": "7.0.4",
27
- "semver": "7.5.4",
28
- "@vue/cli-service/html-webpack-plugin": "^5.0.0"
10
+ "@types/node": "~20.10.0",
11
+ "@types/lodash": "4.17.5"
29
12
  }
30
13
  }
@@ -7,12 +7,10 @@ node_modules/
7
7
  .env
8
8
  .cache
9
9
  .next
10
- .nuxt
11
10
  dist
12
11
  dist-pkg
13
12
  .DS_Store
14
13
  dynamic-importer.js
15
14
  ksconfig.json
16
- nuxt.config.js
17
15
  shell/utils/dynamic-importer.js
18
16
  shell/assets/fonts
@@ -2,12 +2,8 @@ name: Build and Release Extension Catalog
2
2
 
3
3
  on:
4
4
  workflow_dispatch:
5
- push:
6
- branches:
7
- - main
8
- pull_request:
9
- branches:
10
- - main
5
+ release:
6
+ types: [released]
11
7
 
12
8
  defaults:
13
9
  run:
@@ -12,7 +12,7 @@ defaults:
12
12
 
13
13
  jobs:
14
14
  build-extension-charts:
15
- uses: rancher/dashboard/.github/workflows/build-extension-charts.yml@release-2.8
15
+ uses: rancher/dashboard/.github/workflows/build-extension-charts.yml@master
16
16
  permissions:
17
17
  actions: write
18
18
  contents: write
@@ -8,7 +8,6 @@
8
8
  ".eslintignore": true,
9
9
  ".eslintrc.js": true,
10
10
  ".gitignore": true,
11
- ".nuxt*": true,
12
11
  ".nyc_output": true,
13
12
  ".vscode": true,
14
13
  "LICENSE": true,
@@ -10,7 +10,7 @@
10
10
  ],
11
11
  "esModuleInterop": true,
12
12
  "allowJs": true,
13
- "sourceMap": true,
13
+ "sourceMap": false,
14
14
  "strict": true,
15
15
  "noEmit": true,
16
16
  "baseUrl": ".",
@@ -28,7 +28,7 @@
28
28
  "typeRoots": [
29
29
  "./node_modules",
30
30
  "./node_modules/@rancher/shell/types"
31
- ],
31
+ ],
32
32
  "types": [
33
33
  "@types/node",
34
34
  "cypress",
package/app/init CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ /* eslint-disable no-console */
2
3
 
3
4
  const path = require('path');
4
5
  const fs = require('fs-extra');
@@ -27,7 +28,7 @@ const args = process.argv;
27
28
  let appFolder = path.resolve('.');
28
29
  let shellVersion = '';
29
30
 
30
- if (args.length === 3) {
31
+ if ( args.length > 2 ) {
31
32
  const name = args[2];
32
33
  const folder = path.resolve('.');
33
34
 
@@ -64,6 +65,7 @@ if ( addGitlabWorkflow ) {
64
65
 
65
66
  let setName = false;
66
67
 
68
+ // Check that there is a package file
67
69
  if (!fs.existsSync(path.join(appFolder, './package.json'))) {
68
70
  console.log(' Adding package.json');
69
71
  fs.copySync(path.join(__dirname, 'app.package.json'), path.join(appFolder, 'package.json'));
@@ -108,6 +110,32 @@ if (creatorPkg._pkgs) {
108
110
 
109
111
  fs.writeFileSync(path.join(appFolder, 'package.json'), JSON.stringify(pkg, null, 2));
110
112
 
113
+ // Add workflow folder if needed
114
+ if ( addWorkflowFolder ) {
115
+ // Point to the workflow directory inside the skeleton application
116
+ const workflowDir = path.join(appFolder, '.github/workflows');
117
+
118
+ if ( !fs.existsSync(workflowDir) ) {
119
+ console.log(' Creating folder: .github/workflows');
120
+ fs.mkdirSync(workflowDir, { recursive: true });
121
+ }
122
+
123
+ const workflowFiles = [
124
+ 'build-extension-catalog.yml',
125
+ 'build-extension-charts.yml'
126
+ ];
127
+
128
+ workflowFiles.forEach((fileName) => {
129
+ const dest = path.join(workflowDir, fileName); // Destination in the skeleton application
130
+ const src = path.join(__dirname, 'files/.github/workflows', fileName); // Source in the package
131
+
132
+ if ( !fs.existsSync(dest) ) {
133
+ console.log(` Adding file ${ fileName } to root workflows`);
134
+ fs.copySync(src, dest);
135
+ }
136
+ });
137
+ }
138
+
111
139
  // Copy base files
112
140
  files.forEach((file) => {
113
141
  const destinationFile = file === 'gitignore' ? '.gitignore' : file;
@@ -117,7 +145,7 @@ files.forEach((file) => {
117
145
  if (!fs.existsSync(dest)) {
118
146
  console.log(` Adding file: ${ file }`);
119
147
 
120
- const folder = path.dirname(file);
148
+ const folder = path.dirname(dest);
121
149
 
122
150
  fs.ensureDirSync(folder);
123
151
 
@@ -126,4 +154,6 @@ files.forEach((file) => {
126
154
  }
127
155
  });
128
156
 
129
- console.log('');
157
+ console.log('Skeleton Application creation complete.\n');
158
+
159
+ /* eslint-enable no-console */
package/app/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "init"
12
12
  ],
13
13
  "engines": {
14
- "node": ">=16.0.0"
14
+ "node": ">=20.0.0"
15
15
  },
16
16
  "_requires": [
17
17
  "core-js",
package/init CHANGED
@@ -9,20 +9,34 @@ const creatorPkg = require('./package.json');
9
9
  const args = process.argv.slice(2);
10
10
  let extensionName = '';
11
11
  let appName = '';
12
+ let migrate = false;
12
13
  let updateOnly = false;
13
14
  let skeletonOnly = false;
14
- let ignoreShellDepCheck = false;
15
+ let ignoreShellDepCheck = false; // Ignore the check for @rancher/shell dependency (Only used for testing)
15
16
  let tagUsed = ''; // To store the inferred tag
16
17
 
17
18
  args.forEach((arg, index) => {
18
19
  switch (arg) {
20
+ case '--help':
21
+ case '-h':
22
+ console.log(`Usage: ${ creatorPkg.name } [options] [extension-name]`);
23
+ console.log('\nOptions:');
24
+ console.log(' --help, -h Show help');
25
+ console.log(' --migrate Migrate an existing extension to Vue 3');
26
+ console.log(' --update, -u Update applications');
27
+ console.log(' --app-name, -a Specify the name of the application');
28
+ console.log(' --skeleton-only, -s Create only the skeleton application');
29
+ process.exit(0);
30
+ case '--migrate':
31
+ migrate = true;
32
+ break;
19
33
  case '--update':
20
34
  case '-u':
21
35
  updateOnly = true;
22
36
  break;
23
37
  case '--app-name':
24
38
  case '-a':
25
- if ( args[index + 1] && !args[index + 1].startsWith('-') ) {
39
+ if (args[index + 1] && !args[index + 1].startsWith('-')) {
26
40
  appName = args[index + 1];
27
41
  } else {
28
42
  console.error('Error: Missing value for --app-name or -a option.');
@@ -37,14 +51,14 @@ args.forEach((arg, index) => {
37
51
  ignoreShellDepCheck = true;
38
52
  break;
39
53
  default:
40
- if ( !arg.startsWith('-') && extensionName === '' ) {
54
+ if (!arg.startsWith('-') && extensionName === '') {
41
55
  extensionName = arg;
42
56
  appName = appName || extensionName;
43
57
  }
44
58
  }
45
59
  });
46
60
 
47
- if ( !extensionName && !updateOnly && !skeletonOnly ) {
61
+ if (!extensionName && !updateOnly && !skeletonOnly && !migrate) {
48
62
  console.error('Please provide an extension name.');
49
63
  process.exit(1);
50
64
  }
@@ -55,7 +69,7 @@ try {
55
69
  const currentVersion = creatorPkg.version;
56
70
 
57
71
  // Fetch the dist-tags from npm
58
- const distTags = JSON.parse(execSync(`npm view ${ packageName } dist-tags --json`).toString());
72
+ const distTags = JSON.parse(execSync(`npm view ${ packageName } dist-tags --json`, { stdio: 'pipe' }).toString());
59
73
 
60
74
  // Find the tag matching the current version
61
75
  tagUsed = Object.keys(distTags).find((tag) => distTags[tag] === currentVersion) || 'latest';
@@ -72,12 +86,12 @@ const shellPackageName = '@rancher/shell';
72
86
 
73
87
  try {
74
88
  // Fetch the version of the `@rancher/shell` package that corresponds to the inferred tag
75
- const tagVersion = execSync(`npm view ${ shellPackageName } dist-tags.${ tagUsed }`).toString().trim();
89
+ const tagVersion = execSync(`npm view ${ shellPackageName } dist-tags.${ tagUsed }`, { stdio: 'pipe' }).toString().trim();
76
90
 
77
91
  if (tagVersion) {
78
92
  shellVersion = tagVersion;
79
93
  } else {
80
- const latestVersion = execSync(`npm view ${ shellPackageName } version`).toString().trim();
94
+ const latestVersion = execSync(`npm view ${ shellPackageName } version`, { stdio: 'pipe' }).toString().trim();
81
95
 
82
96
  shellVersion = latestVersion;
83
97
  }
@@ -86,7 +100,9 @@ try {
86
100
  process.exit(1);
87
101
  }
88
102
 
89
- console.log(` Using version ${ shellVersion } for ${ shellPackageName }`);
103
+ if (!migrate) {
104
+ console.log(` Using version ${ shellVersion } for ${ shellPackageName }`);
105
+ }
90
106
 
91
107
  const basePath = process.cwd();
92
108
  let skeletonPath;
@@ -94,12 +110,12 @@ let isInsideSkeleton = false;
94
110
  let directoryExists = false;
95
111
 
96
112
  // Check if we are inside a skeleton application by looking for package.json
97
- if ( fs.existsSync(path.join(basePath, 'package.json')) ) {
113
+ if (fs.existsSync(path.join(basePath, 'package.json'))) {
98
114
  // Check for @rancher/shell dependency
99
115
  const packageJsonPath = path.join(basePath, 'package.json');
100
116
  const packageJson = require(packageJsonPath);
101
117
 
102
- if ( !ignoreShellDepCheck && (!packageJson.dependencies || !packageJson.dependencies['@rancher/shell']) ) {
118
+ if (!ignoreShellDepCheck && (!packageJson.dependencies || !packageJson.dependencies['@rancher/shell'])) {
103
119
  throw new Error('@rancher/shell dependency is missing in package.json.');
104
120
  } else {
105
121
  isInsideSkeleton = true;
@@ -109,7 +125,7 @@ if ( fs.existsSync(path.join(basePath, 'package.json')) ) {
109
125
  // If not inside a skeleton, check if a directory with the appName already exists
110
126
  skeletonPath = path.join(basePath, appName);
111
127
 
112
- if ( fs.existsSync(skeletonPath) ) {
128
+ if (fs.existsSync(skeletonPath)) {
113
129
  directoryExists = true;
114
130
  }
115
131
  }
@@ -121,7 +137,19 @@ const skeletonExists = fs.existsSync(skeletonPath);
121
137
  const pkgExists = fs.existsSync(pkgPath);
122
138
 
123
139
  try {
124
- if ( updateOnly ) {
140
+ if (migrate) {
141
+ if (!isInsideSkeleton) {
142
+ throw new Error('Migrate option can only be used inside a skeleton application.');
143
+ }
144
+
145
+ console.log(`Migrating extension to Vue 3...`);
146
+ execSync(`node ${ path.join(__dirname, 'migrate', 'init') } ${ args.join(' ') }`, { stdio: 'inherit' });
147
+
148
+ console.log('Migration completed successfully.');
149
+ process.exit(0);
150
+ }
151
+
152
+ if (updateOnly) {
125
153
  // Run the update script directly
126
154
  console.log('Updating applications...');
127
155
  execSync(`node ${ path.join(updatePath, 'init') }`, { stdio: 'inherit' });
@@ -131,44 +159,44 @@ try {
131
159
  }
132
160
 
133
161
  // If the directory exists but we're not inside a skeleton, we should exit to prevent overwriting
134
- if ( directoryExists && !isInsideSkeleton ) {
162
+ if (directoryExists && !isInsideSkeleton) {
135
163
  throw new Error(`A directory named "${ appName }" already exists. Aborting.`);
136
164
  }
137
165
 
138
166
  // Create skeleton application if it doesn't exist
139
- if ( !isInsideSkeleton && !skeletonExists ) {
167
+ if (!isInsideSkeleton && !skeletonExists) {
140
168
  console.log(`Creating skeleton application: ${ appName }...`);
141
169
  // Pass all arguments to the app/init script
142
170
  execSync(`node ${ path.join(__dirname, 'app', 'init') } ${ appName } ${ shellVersion } ${ args.join(' ') }`, { stdio: 'inherit' });
143
171
 
144
172
  // Ensure the skeleton path directory is created before attempting to change directory
145
- if ( !fs.existsSync(skeletonPath) ) {
173
+ if (!fs.existsSync(skeletonPath)) {
146
174
  throw new Error(`Failed to create skeleton application directory: ${ skeletonPath }`);
147
175
  }
148
176
 
149
177
  // Change working directory to the newly created skeleton app
150
178
  process.chdir(skeletonPath);
151
- } else if ( isInsideSkeleton ) {
179
+ } else if (isInsideSkeleton) {
152
180
  // If skeleton exists, ensure the working directory is set correctly
153
181
  process.chdir(skeletonPath);
154
182
  }
155
183
 
156
- if ( skeletonOnly ) {
184
+ if (skeletonOnly) {
157
185
  console.log('Skeleton application created successfully. No additional packages will be installed.');
158
186
  process.exit(0);
159
187
  }
160
188
 
161
- if ( pkgExists ) {
189
+ if (pkgExists) {
162
190
  throw new Error(`A package directory for "${ extensionName }" already exists.`);
163
191
  }
164
192
 
165
193
  // Check for package existence and create it if necessary
166
- if ( !pkgExists ) {
194
+ if (!pkgExists) {
167
195
  console.log(`Creating package: ${ extensionName }...`);
168
196
  execSync(`node ${ path.join(__dirname, 'pkg', 'init') } ${ extensionName } ${ shellVersion } ${ args.join(' ') }`, { stdio: 'inherit' });
169
197
  }
170
198
 
171
- if ( args.includes('--update') || args.includes('-u') ) {
199
+ if (args.includes('--update') || args.includes('-u')) {
172
200
  // Run the update script
173
201
  console.log('Updating applications...');
174
202
  execSync(`node ${ path.join(updatePath, 'init') } ${ extensionName }`, { stdio: 'inherit' });
@@ -176,7 +204,7 @@ try {
176
204
 
177
205
  console.log('Extension created successfully.');
178
206
 
179
- if ( skeletonOnly || !isInsideSkeleton ) {
207
+ if (skeletonOnly || !isInsideSkeleton) {
180
208
  console.log(`To begin, run: \n\n\tcd ${ appName } && yarn install\n`);
181
209
  }
182
210
  } catch (error) {
@@ -0,0 +1,7 @@
1
+ module.exports = {
2
+ nodeRequirement: '20.0.0',
3
+ isDry: process.argv.includes('--dry'),
4
+ isVerbose: process.argv.includes('--verbose'),
5
+ isSuggest: process.argv.includes('--suggest'),
6
+ removePlaceholder: 'REMOVE',
7
+ };
@@ -0,0 +1,14 @@
1
+ module.exports = [
2
+ '**/node_modules/**',
3
+ '**/dist/**',
4
+ '**/dist-pkg/**',
5
+ '**/.vscode/**',
6
+ '**/assets/**',
7
+ '**/charts/**',
8
+ '**/extensions/**',
9
+ '**/scripts/vue-migrate.js',
10
+ 'docusaurus/**',
11
+ 'storybook-static/**',
12
+ 'storybook/**',
13
+ 'migration/**',
14
+ ];
package/migrate/init ADDED
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { setParams, printLog, printUsage } = require('./utils/content');
4
+ const {
5
+ packageUpdates,
6
+ nvmUpdates,
7
+ vueConfigUpdates,
8
+ vueSyntaxUpdates,
9
+ routerUpdates,
10
+ eslintUpdates,
11
+ tsUpdates,
12
+ stylesUpdates,
13
+ } = require('./tasks');
14
+ const params = require('./params');
15
+
16
+ (function main() {
17
+ if (process.argv.includes('--help') || process.argv.includes('-h')) {
18
+ printUsage();
19
+
20
+ return;
21
+ }
22
+
23
+ setParams(params);
24
+
25
+ packageUpdates(params);
26
+ nvmUpdates(params);
27
+ vueConfigUpdates(params);
28
+ vueSyntaxUpdates(params);
29
+ routerUpdates(params);
30
+ eslintUpdates(params);
31
+ tsUpdates(params);
32
+ stylesUpdates(params);
33
+
34
+ printLog();
35
+ })();
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@rancher/create-migration",
3
+ "description": "Rancher UI Vue3 migration helper",
4
+ "version": "0.0.0",
5
+ "license": "Apache-2.0",
6
+ "author": "SUSE",
7
+ "private": false,
8
+ "bin": "./init",
9
+ "files": [
10
+ "**/*.*",
11
+ "init"
12
+ ],
13
+ "engines": {
14
+ "node": ">=20.0.0"
15
+ },
16
+ "dependencies": {
17
+ "diff": "^7.0.0",
18
+ "fs-extra": "^10.0.0",
19
+ "glob": "^11.0.0",
20
+ "path": "^0.12.7",
21
+ "semver": "^7.6.3"
22
+ }
23
+ }
@@ -0,0 +1,7 @@
1
+ const ignore = require('./ignore');
2
+
3
+ module.exports = {
4
+ paths: null,
5
+ ignorePatterns: [],
6
+ ignore,
7
+ };
@@ -0,0 +1,13 @@
1
+ // stats.js
2
+ module.exports = {
3
+ libraries: [],
4
+ node: [],
5
+ nvmrc: [],
6
+ webpack: [],
7
+ router: [],
8
+ resolution: [],
9
+ eslint: [],
10
+ vueSyntax: [],
11
+ style: [],
12
+ total: [],
13
+ };
@@ -0,0 +1,70 @@
1
+ const glob = require('glob');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const stats = require('../stats');
5
+ const { writeContent, printContent } = require('../utils/content');
6
+
7
+ /**
8
+ * ESLint Updates
9
+ * Files: .eslintrc.js, .eslintrc.json, .eslintrc.yml
10
+ */
11
+ const eslintUpdates = (params) => {
12
+ const files = glob.sync(params.paths || '**/.eslintrc.*{js,json,yml}', { ignore: params.ignore });
13
+ const replacePlugins = [
14
+ ['plugin:vue/essential', 'plugin:vue/vue3-essential'],
15
+ ['plugin:vue/strongly-recommended', 'plugin:vue/vue3-strongly-recommended'],
16
+ ['plugin:vue/recommended', 'plugin:vue/vue3-recommended']
17
+ ];
18
+ const newRules = {
19
+ 'vue/one-component-per-file': 'off',
20
+ 'vue/no-deprecated-slot-attribute': 'off',
21
+ 'vue/require-explicit-emits': 'off',
22
+ 'vue/v-on-event-hyphenation': 'off',
23
+ };
24
+
25
+ files.forEach((file) => {
26
+ const originalContent = fs.readFileSync(file, 'utf8');
27
+ let content = originalContent;
28
+ const matchedCases = [];
29
+
30
+ replacePlugins.forEach(([text, replacement]) => {
31
+ if (content.includes(text)) {
32
+ content = content.replaceAll(text, replacement);
33
+ matchedCases.push([text, replacement]);
34
+
35
+ writeContent(file, content, originalContent);
36
+ }
37
+ });
38
+
39
+ const eslintConfigPath = path.join(process.cwd(), `${ file }`);
40
+ let eslintConfig;
41
+
42
+ try {
43
+ eslintConfig = require(eslintConfigPath);
44
+ } catch (error) {
45
+ eslintConfig = {};
46
+ }
47
+
48
+ if (!eslintConfig.rules) {
49
+ eslintConfig.rules = {};
50
+ }
51
+
52
+ Object.keys(newRules).forEach((rule) => {
53
+ if (!eslintConfig.rules[rule]) {
54
+ eslintConfig.rules[rule] = newRules[rule];
55
+ matchedCases.push(rule);
56
+ }
57
+ });
58
+
59
+ if (matchedCases.length) {
60
+ const updatedConfig = `module.exports = ${ JSON.stringify(eslintConfig, null, 2) }`;
61
+
62
+ writeContent(file, updatedConfig, originalContent);
63
+ printContent(file, `Updating ESLint`, matchedCases);
64
+ stats.eslint.push(file);
65
+ stats.total.push(file);
66
+ }
67
+ });
68
+ };
69
+
70
+ module.exports = eslintUpdates;
@@ -0,0 +1,19 @@
1
+ const packageUpdates = require('./packageUpdates');
2
+ const nvmUpdates = require('./nvmUpdates');
3
+ const vueConfigUpdates = require('./vueConfigUpdates');
4
+ const vueSyntaxUpdates = require('./vueSyntaxUpdates');
5
+ const routerUpdates = require('./routerUpdates');
6
+ const eslintUpdates = require('./eslintUpdates');
7
+ const tsUpdates = require('./tsUpdates');
8
+ const stylesUpdates = require('./stylesUpdates');
9
+
10
+ module.exports = {
11
+ packageUpdates,
12
+ nvmUpdates,
13
+ vueConfigUpdates,
14
+ vueSyntaxUpdates,
15
+ routerUpdates,
16
+ eslintUpdates,
17
+ tsUpdates,
18
+ stylesUpdates,
19
+ };
@@ -0,0 +1,51 @@
1
+ const fs = require('fs');
2
+ const glob = require('glob');
3
+ const semver = require('semver');
4
+ const stats = require('../stats');
5
+ const { printContent, writeContent } = require('../utils/content');
6
+ const { nodeRequirement, isDry, isSuggest } = require('../config');
7
+
8
+ /**
9
+ * NVM updates
10
+ * Files: .nvmrc
11
+ *
12
+ * Verify presence of .nvmrc, create one if none, update if any
13
+ */
14
+ const nvmUpdates = (params) => {
15
+ const files = glob.sync(params.paths || '**/.nvmrc', { ignore: params.ignore });
16
+ const nvmRequirement = 20;
17
+
18
+ if (files.length === 0) {
19
+ // If no .nvmrc files found, create one
20
+ const newFilePath = '.nvmrc';
21
+
22
+ if (!isDry && !isSuggest) {
23
+ fs.writeFileSync(newFilePath, nvmRequirement.toString());
24
+ printContent(newFilePath, `Created ${ newFilePath } with Node version ${ nvmRequirement }`, '');
25
+ }
26
+
27
+ writeContent(newFilePath, nvmRequirement.toString(), '');
28
+ stats.nvmrc.push(newFilePath);
29
+ stats.total.push(newFilePath);
30
+ }
31
+
32
+ files.forEach((file) => {
33
+ if (file) {
34
+ const originalContent = fs.readFileSync(file, 'utf8');
35
+ let content = originalContent;
36
+ const nodeVersionMatch = content.match(/([0-9.x]+)/g);
37
+ const nodeVersion = semver.coerce(nodeVersionMatch[0]);
38
+
39
+ // Ensure node version is up to date
40
+ if (nodeVersion && semver.lt(nodeVersion, semver.coerce(nodeRequirement))) {
41
+ printContent(file, `Updating node ${ [nodeVersionMatch[0], nvmRequirement] }`);
42
+ content = content.replaceAll(nodeVersionMatch[0], nvmRequirement);
43
+ writeContent(file, content, originalContent);
44
+ stats.nvmrc.push(file);
45
+ stats.total.push(file);
46
+ }
47
+ }
48
+ });
49
+ };
50
+
51
+ module.exports = nvmUpdates;