@openrewrite/recipes-nodejs 0.47.0 → 0.47.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openrewrite/recipes-nodejs",
3
- "version": "0.47.0",
3
+ "version": "0.47.3",
4
4
  "license": "Moderne Proprietary",
5
5
  "description": "OpenRewrite recipes for Node.js library migrations.",
6
6
  "homepage": "https://github.com/moderneinc/rewrite-node",
@@ -17,15 +17,15 @@
17
17
  "access": "public"
18
18
  },
19
19
  "scripts": {
20
- "prebuild": "bun -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
20
+ "prebuild": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
21
21
  "build": "tsc --build tsconfig.build.json",
22
- "postbuild": "bun -e \"const fse=require('fs-extra'); fse.existsSync('resources') && fse.copySync('resources','dist/resources',{dereference:true})\"",
22
+ "postbuild": "node -e \"const fse=require('fs-extra'); fse.existsSync('resources') && fse.copySync('resources','dist/resources',{dereference:true})\"",
23
23
  "dev": "tsc --watch -p tsconfig.json",
24
24
  "test": "npm run build && jest",
25
25
  "ci:test": "jest"
26
26
  },
27
27
  "dependencies": {
28
- "@openrewrite/rewrite": "^8.86.0-20260701-020557",
28
+ "@openrewrite/rewrite": "^8.88.0-20260729-151827",
29
29
  "mutative": "^1.1.0",
30
30
  "semver": "^7.7.3"
31
31
  },
@@ -33,7 +33,6 @@
33
33
  "@types/jest": "^29.5.13",
34
34
  "@types/node": "^22.5.4",
35
35
  "@types/semver": "^7.7.1",
36
- "bun": "^1.3.5",
37
36
  "fs-extra": "^11.3.3",
38
37
  "jest": "^29.7.0",
39
38
  "jest-junit": "^17.0.0",
@@ -11,7 +11,10 @@ import * as semver from "semver";
11
11
  export class IncreaseNodeEngineVersion extends Recipe {
12
12
  readonly name = "org.openrewrite.node.migrate.increase-node-engine-version";
13
13
  readonly displayName = "Increase Node.js engine version";
14
- readonly description = "Increases the upper bound of the `engines.node` version range in package.json to allow the specified Node.js version.";
14
+ readonly description = "Raises the lower bound of the `engines.node` version range in package.json to the specified " +
15
+ "Node.js version, performing a hard cutover that drops support for older, end-of-life versions " +
16
+ "(`22.x` → `24.x`, `>= 22` → `>= 24`). The original constraint style is preserved where possible and the " +
17
+ "version is never lowered.";
15
18
 
16
19
  @Option({
17
20
  displayName: "Version",
@@ -48,13 +51,18 @@ export class IncreaseNodeEngineVersion extends Recipe {
48
51
  return doc;
49
52
  }
50
53
 
51
- const updatedRange = increaseUpperBound(nodeRange, targetVersion);
54
+ const updatedRange = raiseMinimumVersion(nodeRange, targetVersion);
52
55
  if (updatedRange === nodeRange) {
53
56
  return doc;
54
57
  }
55
58
 
56
- packageJson.engines.node = updatedRange;
57
- const modifiedContent = JSON.stringify(packageJson, null, 2);
59
+ // Targeted replacement preserves the file's original formatting (indentation,
60
+ // trailing newline, key order) instead of round-tripping through JSON.stringify.
61
+ const nodeMemberRegex = /("engines"\s*:\s*\{[\s\S]*?"node"\s*:\s*)"([^"]+)"/;
62
+ const modifiedContent = content.replace(nodeMemberRegex, `$1${JSON.stringify(updatedRange)}`);
63
+ if (modifiedContent === content) {
64
+ return doc;
65
+ }
58
66
 
59
67
  const parsed = await new JsonParser({}).parseOne({
60
68
  text: modifiedContent,
@@ -71,67 +79,64 @@ export class IncreaseNodeEngineVersion extends Recipe {
71
79
  }
72
80
  }
73
81
 
74
- export function increaseUpperBound(range: string, targetVersion: number): string {
82
+ export function raiseMinimumVersion(range: string, targetVersion: number): string {
83
+ const trimmed = range.trim();
84
+
85
+ let minVersion: semver.SemVer | null = null;
75
86
  try {
76
- if (semver.satisfies(`${targetVersion}.0.0`, range)) {
77
- return range;
78
- }
87
+ minVersion = semver.minVersion(trimmed);
79
88
  } catch {
80
- // fall through to string manipulation
89
+ // unparseable range: fall through to string manipulation
81
90
  }
82
-
83
- // Hyphen range: "14 - 18" or "14.0.0 - 18.0.0"
84
- const hyphenMatch = range.match(/^(.+\s+-\s+)(\d+)(\.0\.0)?$/);
85
- if (hyphenMatch) {
86
- return `${hyphenMatch[1]}${targetVersion}${hyphenMatch[3] || ''}`;
91
+ if (minVersion && minVersion.major >= targetVersion) {
92
+ return range;
87
93
  }
88
94
 
89
- // Simple OR ranges (no < or > comparators): "14 || 16 || 18", "^14 || ^16", "14.x || 16.x"
90
- if (range.includes('||') && !/[<>]/.test(range)) {
91
- const separatorMatch = range.match(/(\s*\|\|\s*)/);
92
- const separator = separatorMatch ? separatorMatch[1] : ' || ';
93
- const lastPart = range.split(/\s*\|\|\s*/).pop()!.trim();
94
-
95
- let newPart: string;
96
- if (lastPart.startsWith('^')) {
97
- newPart = `^${targetVersion}`;
98
- } else if (lastPart.startsWith('~')) {
99
- newPart = `~${targetVersion}`;
100
- } else if (lastPart.endsWith('.x')) {
101
- newPart = `${targetVersion}.x`;
102
- } else {
103
- newPart = `${targetVersion}`;
95
+ // OR union with no comparators: drop terms below the target, floor the rest.
96
+ // "20.x || 22.x" -> "24.x"; "^26 || ^22" -> "^26" (preserves out-of-order terms already ≥ target).
97
+ if (trimmed.includes('||') && !/[<>]/.test(trimmed)) {
98
+ const parts = trimmed.split(/\s*\|\|\s*/).map(p => p.trim());
99
+ const kept = parts.filter(p => {
100
+ try {
101
+ const m = semver.minVersion(p);
102
+ return m !== null && m.major >= targetVersion;
103
+ } catch {
104
+ return false;
105
+ }
106
+ });
107
+ if (kept.length > 0) {
108
+ return kept.join(' || ');
104
109
  }
105
-
106
- return `${range}${separator}${newPart}`;
110
+ return floorTerm(parts[parts.length - 1], targetVersion);
107
111
  }
108
112
 
109
- // Upper bound with < or <= (not preceded by >, to avoid matching >=)
110
- const upperBoundRegex = /(?<!>)(<=?)(\d+)(\.0\.0)?(?=[^<]*$)/;
111
- const upperMatch = range.match(upperBoundRegex);
112
- if (upperMatch) {
113
- const op = upperMatch[1];
114
- const suffix = upperMatch[3] || '';
115
- const newMajor = op === '<' ? targetVersion + 1 : targetVersion;
116
- return range.replace(upperBoundRegex, `${op}${newMajor}${suffix}`);
117
- }
118
-
119
- // Single caret: "^18" or "^18.0.0"
120
- const caretMatch = range.match(/^\^(\d+)(\.0\.0)?$/);
121
- if (caretMatch) {
122
- return `${range} || ^${targetVersion}${caretMatch[2] || ''}`;
113
+ // Hyphen range: "18 - 20" or "18.0.0 - 20.0.0".
114
+ const hyphenMatch = trimmed.match(/^(.+?)\s+-\s+(.+)$/);
115
+ if (hyphenMatch) {
116
+ const upper = hyphenMatch[2].trim();
117
+ const upperMajor = parseInt(upper, 10);
118
+ if (!Number.isNaN(upperMajor) && upperMajor < targetVersion) {
119
+ return `>=${targetVersion}`;
120
+ }
121
+ return `${targetVersion} - ${upper}`;
123
122
  }
124
123
 
125
- // Single tilde: "~18" or "~18.0.0"
126
- const tildeMatch = range.match(/^~(\d+)(\.0\.0)?$/);
127
- if (tildeMatch) {
128
- return `${range} || ~${targetVersion}${tildeMatch[2] || ''}`;
124
+ // Two-sided comparator range: raise the lower bound, keep the upper bound only if it still admits the target.
125
+ if (/</.test(trimmed)) {
126
+ const upperMatch = trimmed.match(/(<=?)\s*(\d+)(?:\.\d+)*/);
127
+ let upper = '';
128
+ if (upperMatch) {
129
+ const op = upperMatch[1];
130
+ const upperMajor = parseInt(upperMatch[2], 10);
131
+ const admitsTarget = op === '<=' ? upperMajor >= targetVersion : upperMajor > targetVersion;
132
+ upper = admitsTarget ? ` ${op}${upperMatch[2]}` : '';
133
+ }
134
+ return `>=${targetVersion}${upper}`;
129
135
  }
130
136
 
131
- // Single x-range: "18.x"
132
- if (/^\d+\.x$/.test(range)) {
133
- return `${range} || ${targetVersion}.x`;
134
- }
137
+ return floorTerm(trimmed, targetVersion);
138
+ }
135
139
 
136
- return range;
140
+ function floorTerm(part: string, targetVersion: number): string {
141
+ return part.replace(/\d+/, String(targetVersion));
137
142
  }