@openrewrite/recipes-nodejs 0.51.0 → 0.51.2
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/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -1
- package/dist/migrate/angular-animations/remove-angular-animations-providers.js +7 -7
- package/dist/migrate/angular-animations/remove-angular-animations-providers.js.map +1 -1
- package/dist/migrate/express-request-id/migrate-express-request-id-to-native.d.ts.map +1 -1
- package/dist/migrate/express-request-id/migrate-express-request-id-to-native.js +3 -2
- package/dist/migrate/express-request-id/migrate-express-request-id-to-native.js.map +1 -1
- package/dist/migrate/express-request-id/remove-unused-express-request-id-dependency.d.ts +3 -6
- package/dist/migrate/express-request-id/remove-unused-express-request-id-dependency.d.ts.map +1 -1
- package/dist/migrate/express-request-id/remove-unused-express-request-id-dependency.js +5 -11
- package/dist/migrate/express-request-id/remove-unused-express-request-id-dependency.js.map +1 -1
- package/dist/migrate/find-aead-decipheriv-without-auth-tag-length.d.ts +9 -0
- package/dist/migrate/find-aead-decipheriv-without-auth-tag-length.d.ts.map +1 -0
- package/dist/migrate/find-aead-decipheriv-without-auth-tag-length.js +89 -0
- package/dist/migrate/find-aead-decipheriv-without-auth-tag-length.js.map +1 -0
- package/dist/migrate/find-crypto-default-encoding.d.ts +9 -0
- package/dist/migrate/find-crypto-default-encoding.d.ts.map +1 -0
- package/dist/migrate/find-crypto-default-encoding.js +61 -0
- package/dist/migrate/find-crypto-default-encoding.js.map +1 -0
- package/dist/migrate/find-duplex-to-web-type.d.ts +9 -0
- package/dist/migrate/find-duplex-to-web-type.d.ts.map +1 -0
- package/dist/migrate/find-duplex-to-web-type.js +86 -0
- package/dist/migrate/find-duplex-to-web-type.js.map +1 -0
- package/dist/migrate/find-module-register.d.ts +9 -0
- package/dist/migrate/find-module-register.d.ts.map +1 -0
- package/dist/migrate/find-module-register.js +50 -0
- package/dist/migrate/find-module-register.js.map +1 -0
- package/dist/migrate/find-process-assert.js +2 -2
- package/dist/migrate/find-process-assert.js.map +1 -1
- package/dist/migrate/fs-extra/migrate-fs-extra-to-node-fs.d.ts.map +1 -1
- package/dist/migrate/fs-extra/migrate-fs-extra-to-node-fs.js +4 -2
- package/dist/migrate/fs-extra/migrate-fs-extra-to-node-fs.js.map +1 -1
- package/dist/migrate/fs-extra/remove-unused-fs-extra-dependency.d.ts +4 -6
- package/dist/migrate/fs-extra/remove-unused-fs-extra-dependency.d.ts.map +1 -1
- package/dist/migrate/fs-extra/remove-unused-fs-extra-dependency.js +6 -14
- package/dist/migrate/fs-extra/remove-unused-fs-extra-dependency.js.map +1 -1
- package/dist/migrate/fs-extra/replace-fs-extra-native-methods.js +1 -1
- package/dist/migrate/fs-extra/replace-fs-extra-native-methods.js.map +1 -1
- package/dist/migrate/hpagent/migrate-hpagent-to-https-proxy-agent.d.ts.map +1 -1
- package/dist/migrate/hpagent/migrate-hpagent-to-https-proxy-agent.js +3 -7
- package/dist/migrate/hpagent/migrate-hpagent-to-https-proxy-agent.js.map +1 -1
- package/dist/migrate/hpagent/replace-hpagent.d.ts.map +1 -1
- package/dist/migrate/hpagent/replace-hpagent.js +68 -2
- package/dist/migrate/hpagent/replace-hpagent.js.map +1 -1
- package/dist/migrate/hpagent/swap-hpagent-dependency.d.ts +3 -15
- package/dist/migrate/hpagent/swap-hpagent-dependency.d.ts.map +1 -1
- package/dist/migrate/hpagent/swap-hpagent-dependency.js +11 -62
- package/dist/migrate/hpagent/swap-hpagent-dependency.js.map +1 -1
- package/dist/migrate/package-json-support.d.ts +6 -0
- package/dist/migrate/package-json-support.d.ts.map +1 -0
- package/dist/migrate/package-json-support.js +31 -0
- package/dist/migrate/package-json-support.js.map +1 -0
- package/dist/migrate/path/remove-path-dependency.js +8 -8
- package/dist/migrate/path/remove-path-dependency.js.map +1 -1
- package/dist/migrate/process-exit-code.js +3 -3
- package/dist/migrate/process-exit-code.js.map +1 -1
- package/dist/migrate/process-features-tls.js +1 -1
- package/dist/migrate/process-features-tls.js.map +1 -1
- package/dist/migrate/prop-types/remove-prop-types.js +8 -8
- package/dist/migrate/prop-types/remove-prop-types.js.map +1 -1
- package/dist/migrate/remove-dependencies.d.ts +6 -0
- package/dist/migrate/remove-dependencies.d.ts.map +1 -0
- package/dist/migrate/remove-dependencies.js +23 -0
- package/dist/migrate/remove-dependencies.js.map +1 -0
- package/dist/migrate/server-response-write-header.d.ts +9 -0
- package/dist/migrate/server-response-write-header.d.ts.map +1 -0
- package/dist/migrate/server-response-write-header.js +62 -0
- package/dist/migrate/server-response-write-header.js.map +1 -0
- package/dist/migrate/source-map-support/enable-source-maps-at-launch.d.ts +19 -0
- package/dist/migrate/source-map-support/enable-source-maps-at-launch.d.ts.map +1 -0
- package/dist/migrate/source-map-support/enable-source-maps-at-launch.js +311 -0
- package/dist/migrate/source-map-support/enable-source-maps-at-launch.js.map +1 -0
- package/dist/migrate/source-map-support/migrate-source-map-support-to-native.d.ts.map +1 -1
- package/dist/migrate/source-map-support/migrate-source-map-support-to-native.js +9 -4
- package/dist/migrate/source-map-support/migrate-source-map-support-to-native.js.map +1 -1
- package/dist/migrate/source-map-support/register-flag.d.ts +4 -0
- package/dist/migrate/source-map-support/register-flag.d.ts.map +1 -1
- package/dist/migrate/source-map-support/register-flag.js +35 -0
- package/dist/migrate/source-map-support/register-flag.js.map +1 -1
- package/dist/migrate/source-map-support/remove-unused-source-map-support-dependency.d.ts +3 -6
- package/dist/migrate/source-map-support/remove-unused-source-map-support-dependency.d.ts.map +1 -1
- package/dist/migrate/source-map-support/remove-unused-source-map-support-dependency.js +5 -12
- package/dist/migrate/source-map-support/remove-unused-source-map-support-dependency.js.map +1 -1
- package/dist/migrate/source-map-support/source-map-support-rewrites.d.ts.map +1 -1
- package/dist/migrate/source-map-support/source-map-support-rewrites.js +2 -0
- package/dist/migrate/source-map-support/source-map-support-rewrites.js.map +1 -1
- package/dist/migrate/upgrade-node-20.d.ts +8 -0
- package/dist/migrate/upgrade-node-20.d.ts.map +1 -0
- package/dist/migrate/upgrade-node-20.js +42 -0
- package/dist/migrate/upgrade-node-20.js.map +1 -0
- package/dist/migrate/upgrade-node-22.d.ts +1 -1
- package/dist/migrate/upgrade-node-22.d.ts.map +1 -1
- package/dist/migrate/upgrade-node-22.js +7 -5
- package/dist/migrate/upgrade-node-22.js.map +1 -1
- package/dist/migrate/upgrade-node-24.d.ts.map +1 -1
- package/dist/migrate/upgrade-node-24.js +2 -0
- package/dist/migrate/upgrade-node-24.js.map +1 -1
- package/dist/migrate/upgrade-node-26.d.ts +8 -0
- package/dist/migrate/upgrade-node-26.d.ts.map +1 -0
- package/dist/migrate/upgrade-node-26.js +53 -0
- package/dist/migrate/upgrade-node-26.js.map +1 -0
- package/dist/resources/advisories-npm.csv +278 -20
- package/package.json +2 -2
- package/src/index.ts +16 -0
- package/src/migrate/angular-animations/remove-angular-animations-providers.ts +1 -1
- package/src/migrate/express-request-id/migrate-express-request-id-to-native.ts +3 -2
- package/src/migrate/express-request-id/remove-unused-express-request-id-dependency.ts +5 -13
- package/src/migrate/find-aead-decipheriv-without-auth-tag-length.ts +76 -0
- package/src/migrate/find-crypto-default-encoding.ts +47 -0
- package/src/migrate/find-duplex-to-web-type.ts +71 -0
- package/src/migrate/find-module-register.ts +36 -0
- package/src/migrate/find-process-assert.ts +2 -2
- package/src/migrate/fs-extra/migrate-fs-extra-to-node-fs.ts +4 -2
- package/src/migrate/fs-extra/remove-unused-fs-extra-dependency.ts +6 -16
- package/src/migrate/fs-extra/replace-fs-extra-native-methods.ts +1 -1
- package/src/migrate/hpagent/migrate-hpagent-to-https-proxy-agent.ts +3 -7
- package/src/migrate/hpagent/replace-hpagent.ts +82 -3
- package/src/migrate/hpagent/swap-hpagent-dependency.ts +13 -68
- package/src/migrate/package-json-support.ts +34 -0
- package/src/migrate/path/remove-path-dependency.ts +1 -1
- package/src/migrate/process-exit-code.ts +3 -3
- package/src/migrate/process-features-tls.ts +1 -1
- package/src/migrate/prop-types/remove-prop-types.ts +1 -1
- package/src/migrate/remove-dependencies.ts +21 -0
- package/src/migrate/server-response-write-header.ts +51 -0
- package/src/migrate/source-map-support/enable-source-maps-at-launch.ts +289 -0
- package/src/migrate/source-map-support/migrate-source-map-support-to-native.ts +9 -4
- package/src/migrate/source-map-support/register-flag.ts +48 -0
- package/src/migrate/source-map-support/remove-unused-source-map-support-dependency.ts +5 -14
- package/src/migrate/source-map-support/source-map-support-rewrites.ts +3 -0
- package/src/migrate/upgrade-node-20.ts +32 -0
- package/src/migrate/upgrade-node-22.ts +8 -6
- package/src/migrate/upgrade-node-24.ts +2 -0
- package/src/migrate/upgrade-node-26.ts +45 -0
- package/dist/migrate/remove-unused-dependency.d.ts +0 -43
- package/dist/migrate/remove-unused-dependency.d.ts.map +0 -1
- package/dist/migrate/remove-unused-dependency.js +0 -257
- package/dist/migrate/remove-unused-dependency.js.map +0 -1
- package/src/migrate/remove-unused-dependency.ts +0 -275
|
@@ -4,35 +4,23 @@
|
|
|
4
4
|
* Moderne Proprietary. Only for use by Moderne customers under the terms of a commercial contract.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {anyBeneath, dirOf, isPackageJson, RemoveUnusedDependency} from "../remove-unused-dependency";
|
|
10
|
-
import {insertPaddedAfter, literal, member, memberIndex} from "../json-edits";
|
|
11
|
-
import {ReplaceHpagent} from "./replace-hpagent";
|
|
7
|
+
import {Option, Recipe} from "@openrewrite/rewrite";
|
|
8
|
+
import {changeDependency} from "@openrewrite/rewrite/javascript";
|
|
12
9
|
import {TARGET_MODULE} from "./hpagent-support";
|
|
13
10
|
|
|
14
|
-
const SECTIONS = ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies'];
|
|
15
|
-
|
|
16
11
|
/**
|
|
17
12
|
* `https-proxy-agent` 7 is the last major to keep hpagent's Node floor: hpagent supports Node 14 and
|
|
18
13
|
* up, as do `https-proxy-agent` 7 and 8, while 9 requires Node 20.
|
|
19
14
|
*/
|
|
20
15
|
const DEFAULT_VERSION = "^7.0.6";
|
|
21
16
|
|
|
22
|
-
export class SwapHpagentDependency extends
|
|
17
|
+
export class SwapHpagentDependency extends Recipe {
|
|
23
18
|
readonly name = "org.openrewrite.node.migrate.hpagent.swap-hpagent-dependency";
|
|
24
19
|
readonly displayName = "Swap the `hpagent` dependency for `https-proxy-agent`";
|
|
25
|
-
readonly description = "Replaces `hpagent` with `https-proxy-agent` in each `package.json` that declares it, in
|
|
26
|
-
"
|
|
27
|
-
"`package.json` still references hpagent — a usage the migration left in place, a mock, or a bundler " +
|
|
28
|
-
"configuration naming it — `https-proxy-agent` is added alongside and hpagent kept, since both are then " +
|
|
29
|
-
"needed. A `package.json` whose sources the migration didn't reach is left alone entirely, rather than " +
|
|
30
|
-
"gaining a dependency nothing uses. Lock files are not regenerated; run the package manager's install afterwards.";
|
|
20
|
+
readonly description = "Replaces `hpagent` with `https-proxy-agent` in each `package.json` that declares it, in " +
|
|
21
|
+
"whichever dependency section declares it, and updates the lock file to match.";
|
|
31
22
|
readonly tags = ["hpagent", "https-proxy-agent"];
|
|
32
23
|
|
|
33
|
-
protected readonly module = 'hpagent';
|
|
34
|
-
protected readonly packages = ['hpagent'];
|
|
35
|
-
|
|
36
24
|
@Option({
|
|
37
25
|
displayName: "`https-proxy-agent` version",
|
|
38
26
|
description: "The version range to declare for `https-proxy-agent`. The default keeps hpagent's Node 14 " +
|
|
@@ -42,58 +30,15 @@ export class SwapHpagentDependency extends RemoveUnusedDependency {
|
|
|
42
30
|
})
|
|
43
31
|
version?: string;
|
|
44
32
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
constructor(options?: { version?: string; accountForMigration?: boolean; migrateSpreadOptions?: boolean }) {
|
|
49
|
-
super({accountForMigration: options?.accountForMigration});
|
|
50
|
-
this.version = options?.version;
|
|
51
|
-
this.migrateSpreadOptions = options?.migrateSpreadOptions;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
protected migrationRecipes(): Recipe[] {
|
|
55
|
-
return [new ReplaceHpagent({migrateSpreadOptions: this.migrateSpreadOptions})];
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
protected override replacementModules(): string[] {
|
|
59
|
-
return [TARGET_MODULE];
|
|
33
|
+
constructor(options?: { version?: string }) {
|
|
34
|
+
super(options);
|
|
60
35
|
}
|
|
61
36
|
|
|
62
|
-
async
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
// Nothing beneath was parsed, so there is no telling whether the migration reached it.
|
|
69
|
-
if (!anyBeneath(dir, acc.parsedFiles)) return doc;
|
|
70
|
-
const stillUsed = anyBeneath(dir, acc.referencingFiles);
|
|
71
|
-
// Nothing beneath came to use the replacement, so declaring it would add an unused dependency.
|
|
72
|
-
if (!anyBeneath(dir, acc.replacementFiles)) return doc;
|
|
73
|
-
|
|
74
|
-
const root = doc.value as Json.Object;
|
|
75
|
-
let changed = false;
|
|
76
|
-
const members = root.members.map(padded => {
|
|
77
|
-
if (!isMember(padded.element)) return padded;
|
|
78
|
-
const section = padded.element as Json.Member;
|
|
79
|
-
if (!SECTIONS.includes(getMemberKeyName(section) ?? '') || !isObject(section.value)) return padded;
|
|
80
|
-
const obj = section.value as Json.Object;
|
|
81
|
-
const hpagent = memberIndex(obj, 'hpagent');
|
|
82
|
-
// Only a section declaring hpagent needs the replacement, and only where it isn't already there.
|
|
83
|
-
if (hpagent < 0 || memberIndex(obj, TARGET_MODULE) >= 0) return padded;
|
|
84
|
-
changed = true;
|
|
85
|
-
const replacement = member(TARGET_MODULE, literal(version));
|
|
86
|
-
const declaration = obj.members[hpagent];
|
|
87
|
-
const updated = stillUsed
|
|
88
|
-
? insertPaddedAfter(obj.members, hpagent, replacement as Json)
|
|
89
|
-
// Replacing it in place keeps the section's formatting and its declaration order.
|
|
90
|
-
: obj.members.map((m, i) => i === hpagent
|
|
91
|
-
? {...m, element: {...replacement, prefix: declaration.element.prefix} as Json}
|
|
92
|
-
: m);
|
|
93
|
-
return {...padded, element: {...section, value: {...obj, members: updated}} as Json.Member};
|
|
94
|
-
});
|
|
95
|
-
return changed ? {...doc, value: {...root, members}} as Json.Document : doc;
|
|
96
|
-
}
|
|
97
|
-
};
|
|
37
|
+
async recipeList(): Promise<Recipe[]> {
|
|
38
|
+
return [await changeDependency({
|
|
39
|
+
oldPackageName: 'hpagent',
|
|
40
|
+
newPackageName: TARGET_MODULE,
|
|
41
|
+
newVersion: this.version ?? DEFAULT_VERSION
|
|
42
|
+
})];
|
|
98
43
|
}
|
|
99
44
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 the original author or authors.
|
|
3
|
+
*
|
|
4
|
+
* Moderne Proprietary. Only for use by Moderne customers under the terms of a commercial contract.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {emptyMarkers, randomId} from "@openrewrite/rewrite";
|
|
8
|
+
import {getMemberKeyName, isMember, Json, rightPadded, space} from "@openrewrite/rewrite/json";
|
|
9
|
+
|
|
10
|
+
export function dirOf(path: string): string {
|
|
11
|
+
const i = path.lastIndexOf('/');
|
|
12
|
+
return i < 0 ? '' : path.substring(0, i);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function isPackageJson(path: string): boolean {
|
|
16
|
+
return (path === 'package.json' || path.endsWith('/package.json')) && !path.includes('node_modules/');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function anyBeneath(dir: string, files: string[]): boolean {
|
|
20
|
+
return files.some(file => dir === '' || file.startsWith(dir + '/'));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Removes members named in `keys`, handing the removed last member's trailing space to the one before it. */
|
|
24
|
+
export function removeMembers(obj: Json.Object, keys: Set<string>): Json.Object {
|
|
25
|
+
const kept = obj.members.filter(rp => !(isMember(rp.element) && keys.has(getMemberKeyName(rp.element as Json.Member) ?? '')));
|
|
26
|
+
if (kept.length === obj.members.length) return obj;
|
|
27
|
+
if (kept.length === 0) {
|
|
28
|
+
const empty: Json.Empty = {kind: Json.Kind.Empty, id: randomId(), prefix: space(''), markers: emptyMarkers};
|
|
29
|
+
return {...obj, members: [rightPadded(empty, space(''))]};
|
|
30
|
+
}
|
|
31
|
+
const closing = obj.members[obj.members.length - 1].after;
|
|
32
|
+
kept[kept.length - 1] = {...kept[kept.length - 1], after: closing};
|
|
33
|
+
return {...obj, members: kept};
|
|
34
|
+
}
|
|
@@ -9,7 +9,7 @@ import {getMemberKeyName, isObject, Json, JsonVisitor} from "@openrewrite/rewrit
|
|
|
9
9
|
import {JS, walk} from "@openrewrite/rewrite/javascript";
|
|
10
10
|
import {J} from "@openrewrite/rewrite/java";
|
|
11
11
|
import {PlainText} from "@openrewrite/rewrite/text";
|
|
12
|
-
import {anyBeneath, dirOf, isPackageJson, removeMembers} from "../
|
|
12
|
+
import {anyBeneath, dirOf, isPackageJson, removeMembers} from "../package-json-support";
|
|
13
13
|
import {requiredModule, stringLiteral} from "../module-bindings";
|
|
14
14
|
|
|
15
15
|
const SECTIONS = new Set(['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies']);
|
|
@@ -19,8 +19,8 @@ export class AvoidImplicitCoercionOfExitCode extends Recipe {
|
|
|
19
19
|
readonly tags: string[] = ["DEP0164"];
|
|
20
20
|
|
|
21
21
|
async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
22
|
-
return check(usesType("
|
|
23
|
-
exitMatcher = new MethodMatcher("Process exit(..)");
|
|
22
|
+
return check(usesType("NodeJS.Process"), new class extends JavaScriptVisitor<ExecutionContext> {
|
|
23
|
+
exitMatcher = new MethodMatcher("NodeJS.Process exit(..)");
|
|
24
24
|
|
|
25
25
|
private isProcessExitCodeAccess(fieldAccess: J.FieldAccess): boolean {
|
|
26
26
|
if (fieldAccess.name.element.simpleName !== "exitCode") {
|
|
@@ -33,7 +33,7 @@ export class AvoidImplicitCoercionOfExitCode extends Recipe {
|
|
|
33
33
|
|
|
34
34
|
if (idType?.kind === Type.Kind.Class) {
|
|
35
35
|
const classType = idType as Type.Class;
|
|
36
|
-
return classType.fullyQualifiedName === "
|
|
36
|
+
return classType.fullyQualifiedName === "NodeJS.Process";
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
|
|
@@ -17,7 +17,7 @@ export class RemoveUsageOfProcessFeaturesTlsConstants extends Recipe {
|
|
|
17
17
|
readonly tags: string[] = ["DEP0189"];
|
|
18
18
|
|
|
19
19
|
async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
20
|
-
return check(usesType("
|
|
20
|
+
return check(usesType("NodeJS.Process"), new class extends JavaScriptVisitor<ExecutionContext> {
|
|
21
21
|
protected async visitExpression(expression: Expression, p: ExecutionContext): Promise<J | undefined> {
|
|
22
22
|
const ret = await super.visitExpression(expression, p) as Expression;
|
|
23
23
|
if (await pattern`process.features.tls_alpn`.match(ret, this.cursor)
|
|
@@ -11,7 +11,7 @@ import {declarationsOf, JavaScriptVisitor, JS, maybeUnbind, walk as walkTree} fr
|
|
|
11
11
|
import {PlainText} from "@openrewrite/rewrite/text";
|
|
12
12
|
import * as semver from "semver";
|
|
13
13
|
import {bindingsOf, importModule, requiredModule, stringLiteral} from "../module-bindings";
|
|
14
|
-
import {anyBeneath, dirOf, isPackageJson, removeMembers} from "../
|
|
14
|
+
import {anyBeneath, dirOf, isPackageJson, removeMembers} from "../package-json-support";
|
|
15
15
|
|
|
16
16
|
const MODULE = 'prop-types';
|
|
17
17
|
const JSX_RUNTIMES = new Set(['react', 'preact', 'preact/compat']);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 the original author or authors.
|
|
3
|
+
*
|
|
4
|
+
* Moderne Proprietary. Only for use by Moderne customers under the terms of a commercial contract.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {Recipe} from "@openrewrite/rewrite";
|
|
8
|
+
import {removeDependency} from "@openrewrite/rewrite/javascript";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Removes a library's packages from every `package.json` that declares them, updating the lock file
|
|
12
|
+
* to match, through the native dependency recipe.
|
|
13
|
+
*/
|
|
14
|
+
export abstract class RemoveDependencies extends Recipe {
|
|
15
|
+
/** The packages removed together, e.g. the library and its `@types`. */
|
|
16
|
+
protected abstract readonly packages: string[];
|
|
17
|
+
|
|
18
|
+
async recipeList(): Promise<Recipe[]> {
|
|
19
|
+
return Promise.all(this.packages.map(packageName => removeDependency({packageName})));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 the original author or authors.
|
|
3
|
+
*
|
|
4
|
+
* Moderne Proprietary. Only for use by Moderne customers under the terms of a commercial contract.
|
|
5
|
+
*/
|
|
6
|
+
import {check, ExecutionContext, or, Recipe, TreeVisitor} from "@openrewrite/rewrite";
|
|
7
|
+
import {JavaScriptVisitor, template, usesType} from "@openrewrite/rewrite/javascript";
|
|
8
|
+
import {J, Type} from "@openrewrite/rewrite/java";
|
|
9
|
+
|
|
10
|
+
export class ReplaceServerResponseWriteHeader extends Recipe {
|
|
11
|
+
readonly name = "org.openrewrite.node.migrate.http.replace-server-response-write-header"
|
|
12
|
+
readonly displayName: string = "Replace `ServerResponse.writeHeader()` with `writeHead()`";
|
|
13
|
+
readonly description: string = "`ServerResponse.prototype.writeHeader()` was an undocumented alias for `writeHead()` " +
|
|
14
|
+
"that was deprecated in Node.js 11 (DEP0063) and removed in Node.js 26. The signatures are identical.";
|
|
15
|
+
readonly tags: string[] = ["DEP0063"];
|
|
16
|
+
|
|
17
|
+
async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
18
|
+
return check(or(
|
|
19
|
+
usesType("http.ServerResponse"),
|
|
20
|
+
usesType("node:http.ServerResponse"),
|
|
21
|
+
), new class extends JavaScriptVisitor<ExecutionContext> {
|
|
22
|
+
|
|
23
|
+
private isServerResponseType(type: Type | undefined): boolean {
|
|
24
|
+
if (!type) return false;
|
|
25
|
+
if (type.kind === Type.Kind.Class) {
|
|
26
|
+
const fqn = (type as Type.Class).fullyQualifiedName;
|
|
27
|
+
return fqn === "http.ServerResponse" || fqn === "node:http.ServerResponse";
|
|
28
|
+
}
|
|
29
|
+
if (type.kind === Type.Kind.Parameterized) {
|
|
30
|
+
return this.isServerResponseType((type as Type.Parameterized).type);
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
protected async visitMethodInvocation(method: J.MethodInvocation, p: ExecutionContext): Promise<J | undefined> {
|
|
36
|
+
const m = await super.visitMethodInvocation(method, p) as J.MethodInvocation;
|
|
37
|
+
|
|
38
|
+
if (m.name.simpleName !== "writeHeader" || !m.select) {
|
|
39
|
+
return m;
|
|
40
|
+
}
|
|
41
|
+
if (!this.isServerResponseType(m.select.element.type)) {
|
|
42
|
+
return m;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const target = m.select.element;
|
|
46
|
+
const args = m.arguments.elements;
|
|
47
|
+
return await template`${target}.writeHead(${args})`.apply(m, this.cursor);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 the original author or authors.
|
|
3
|
+
*
|
|
4
|
+
* Moderne Proprietary. Only for use by Moderne customers under the terms of a commercial contract.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {ExecutionContext, ScanningRecipe, Tree, TreeVisitor} from "@openrewrite/rewrite";
|
|
8
|
+
import {J} from "@openrewrite/rewrite/java";
|
|
9
|
+
import {JavaScriptVisitor, JS, maybeUnbind} from "@openrewrite/rewrite/javascript";
|
|
10
|
+
import {getMemberKeyName, isLiteral, isMember, isObject, Json, JsonVisitor} from "@openrewrite/rewrite/json";
|
|
11
|
+
import {PlainText} from "@openrewrite/rewrite/text";
|
|
12
|
+
import {dirOf, isPackageJson} from "../package-json-support";
|
|
13
|
+
import {addSourceMapsFlag, launchesNode, REGISTER_MODULES, shebangWithSourceMapsFlag} from "./register-flag";
|
|
14
|
+
import {isSourceMapSupport, SourceMapSupportVisitor, sourcePathOf, stringLiteral} from "./source-map-support-support";
|
|
15
|
+
|
|
16
|
+
interface Accumulator {
|
|
17
|
+
/** Files holding an `install()` or `register` entry point the in-place rewrite declines. */
|
|
18
|
+
declined: string[];
|
|
19
|
+
/** `bin` targets each `package.json` declares, as repo-relative paths. */
|
|
20
|
+
binTargets: string[];
|
|
21
|
+
/** Files whose shebang can carry Node's flags. */
|
|
22
|
+
rewritableShebangs: string[];
|
|
23
|
+
/** Packages with a script that runs Node directly, which can take the flag. */
|
|
24
|
+
launchableScripts: string[];
|
|
25
|
+
/** Every `package.json` directory, so a source can be attributed to its package. */
|
|
26
|
+
packageDirs: string[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** The nearest enclosing directory that is a package, for a repo-relative path. */
|
|
30
|
+
function packageOf(path: string, packageDirs: string[]): string | undefined {
|
|
31
|
+
const candidates = packageDirs.filter(dir => dir === '' || path.startsWith(dir + '/'));
|
|
32
|
+
return candidates.sort((a, b) => b.length - a.length)[0];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function joinPath(dir: string, rest: string): string {
|
|
36
|
+
const normalized = rest.replace(/^\.\//, '');
|
|
37
|
+
return dir === '' ? normalized : `${dir}/${normalized}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class EnableSourceMapsAtLaunch extends ScanningRecipe<Accumulator> {
|
|
41
|
+
readonly name = "org.openrewrite.node.migrate.source-map-support.enable-source-maps-at-launch";
|
|
42
|
+
readonly displayName = "Enable native source maps at launch and drop `source-map-support`";
|
|
43
|
+
readonly description = "Where `source-map-support` is installed too late for `process.setSourceMapsEnabled(true)` " +
|
|
44
|
+
"to stand in for it — in an ES module, every `import` is evaluated before any statement runs, so the call " +
|
|
45
|
+
"would only map what loads after it — this puts `--enable-source-maps` on whatever launches the process " +
|
|
46
|
+
"instead, which Node applies before the first module loads. The flag goes into the package's `bin` shebang " +
|
|
47
|
+
"(as `#!/usr/bin/env -S node --enable-source-maps`) and into any `package.json` script that runs `node` or " +
|
|
48
|
+
"`mocha` directly. The `install()` call and `register` import are then redundant and are removed, rather " +
|
|
49
|
+
"than replaced by a call that would map less. A package with no launcher this recipe can reach is left " +
|
|
50
|
+
"alone and reported by `FindSourceMapSupportManualMigrations`.";
|
|
51
|
+
readonly tags = ["source-map-support"];
|
|
52
|
+
|
|
53
|
+
initialValue(_ctx: ExecutionContext): Accumulator {
|
|
54
|
+
return {declined: [], binTargets: [], rewritableShebangs: [], launchableScripts: [], packageDirs: []};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async scanner(acc: Accumulator): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
58
|
+
const js = new class extends SourceMapSupportVisitor<ExecutionContext> {
|
|
59
|
+
protected override async visitShebang(shebang: JS.Shebang, p: ExecutionContext): Promise<J | undefined> {
|
|
60
|
+
if (shebangWithSourceMapsFlag(shebang.text)) acc.rewritableShebangs.push(sourcePathOf(this.cursor));
|
|
61
|
+
return super.visitShebang(shebang, p);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
protected override async visitImportDeclaration(jsImport: JS.Import, p: ExecutionContext): Promise<J | undefined> {
|
|
65
|
+
if (!jsImport.importClause && REGISTER_MODULES.has(stringLiteral(jsImport.moduleSpecifier?.element) ?? '')) {
|
|
66
|
+
this.recordIfDeclined();
|
|
67
|
+
}
|
|
68
|
+
return super.visitImportDeclaration(jsImport, p);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
protected override async visitMethodInvocation(method: J.MethodInvocation, p: ExecutionContext): Promise<J | undefined> {
|
|
72
|
+
if (this.isInstall(method) || isRegisterRequire(method)) this.recordIfDeclined();
|
|
73
|
+
return super.visitMethodInvocation(method, p);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Only a site the in-place rewrite turns down is this recipe's business. */
|
|
77
|
+
private recordIfDeclined(): void {
|
|
78
|
+
if (this.mapsEverythingLoaded()) return;
|
|
79
|
+
const path = sourcePathOf(this.cursor);
|
|
80
|
+
if (!acc.declined.includes(path)) acc.declined.push(path);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const json = new class extends JsonVisitor<ExecutionContext> {
|
|
85
|
+
protected override async visitDocument(doc: Json.Document, p: ExecutionContext): Promise<Json | undefined> {
|
|
86
|
+
if (!isPackageJson(doc.sourcePath) || !isObject(doc.value)) return doc;
|
|
87
|
+
const dir = dirOf(doc.sourcePath);
|
|
88
|
+
if (!acc.packageDirs.includes(dir)) acc.packageDirs.push(dir);
|
|
89
|
+
const root = doc.value as Json.Object;
|
|
90
|
+
for (const padded of root.members) {
|
|
91
|
+
if (!isMember(padded.element)) continue;
|
|
92
|
+
const member = padded.element as Json.Member;
|
|
93
|
+
const key = getMemberKeyName(member) ?? '';
|
|
94
|
+
if (key === 'bin') {
|
|
95
|
+
for (const target of binTargetsOf(member.value)) acc.binTargets.push(joinPath(dir, target));
|
|
96
|
+
} else if (key === 'scripts' && isObject(member.value)) {
|
|
97
|
+
const launchable = (member.value as Json.Object).members.some(entry => {
|
|
98
|
+
if (!isMember(entry.element)) return false;
|
|
99
|
+
const value = (entry.element as Json.Member).value;
|
|
100
|
+
return isLiteral(value) && typeof value.value === 'string' && launchesNode(value.value);
|
|
101
|
+
});
|
|
102
|
+
if (launchable && !acc.launchableScripts.includes(dir)) acc.launchableScripts.push(dir);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return doc;
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
return new class extends TreeVisitor<Tree, ExecutionContext> {
|
|
110
|
+
protected override async accept(tree: Tree, p: ExecutionContext): Promise<Tree | undefined> {
|
|
111
|
+
if (tree.kind === JS.Kind.CompilationUnit) return js.visit(tree, p);
|
|
112
|
+
if (tree.kind === Json.Kind.Document) return json.visit(tree, p);
|
|
113
|
+
if (tree.kind === PlainText.Kind.PlainText) {
|
|
114
|
+
const text = tree as PlainText;
|
|
115
|
+
const first = text.text.split('\n', 1)[0];
|
|
116
|
+
if (shebangWithSourceMapsFlag(first)) acc.rewritableShebangs.push(text.sourcePath);
|
|
117
|
+
}
|
|
118
|
+
return tree;
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async editorWithData(acc: Accumulator): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
124
|
+
const packageOfPath = (path: string) => enclosingPackage(path, acc.packageDirs);
|
|
125
|
+
|
|
126
|
+
// A package can take the flag if a script runs Node, or if a shebang this recipe can rewrite
|
|
127
|
+
// belongs to it — either the declining file's own or one its `bin` points at.
|
|
128
|
+
const shebangsFor = (dir: string) => acc.rewritableShebangs.filter(path =>
|
|
129
|
+
packageOfPath(path) === dir &&
|
|
130
|
+
(acc.declined.includes(path) || acc.binTargets.includes(path)));
|
|
131
|
+
|
|
132
|
+
const declinedPackages = new Set(acc.declined.map(packageOfPath));
|
|
133
|
+
const flagged = new Set([...declinedPackages].filter(dir =>
|
|
134
|
+
acc.launchableScripts.includes(dir) || shebangsFor(dir).length > 0));
|
|
135
|
+
|
|
136
|
+
const removeSites = new Set(acc.declined.filter(path => flagged.has(packageOfPath(path))));
|
|
137
|
+
const shebangsToFlag = new Set([...flagged].flatMap(shebangsFor));
|
|
138
|
+
// A shebang already puts the flag on the process this package launches, so its scripts —
|
|
139
|
+
// which may run unrelated tools — are left as they are.
|
|
140
|
+
const scriptPackages = new Set([...flagged].filter(dir => shebangsFor(dir).length === 0));
|
|
141
|
+
|
|
142
|
+
const js = new class extends JavaScriptVisitor<ExecutionContext> {
|
|
143
|
+
protected override async visitJsCompilationUnit(cu: JS.CompilationUnit, p: ExecutionContext): Promise<J | undefined> {
|
|
144
|
+
const flagShebang = shebangsToFlag.has(cu.sourcePath);
|
|
145
|
+
const removeHere = removeSites.has(cu.sourcePath);
|
|
146
|
+
if (!flagShebang && !removeHere) return cu;
|
|
147
|
+
|
|
148
|
+
let statements = cu.statements;
|
|
149
|
+
if (flagShebang) {
|
|
150
|
+
statements = statements.map(padded => {
|
|
151
|
+
if (padded.element.kind !== JS.Kind.Shebang) return padded;
|
|
152
|
+
const flagged = shebangWithSourceMapsFlag((padded.element as JS.Shebang).text);
|
|
153
|
+
return flagged ? {...padded, element: {...padded.element, text: flagged} as JS.Shebang} : padded;
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
if (removeHere) {
|
|
157
|
+
statements = withoutStatements(statements, s => isRedundantEntryPoint(s));
|
|
158
|
+
}
|
|
159
|
+
const result = statements === cu.statements ? cu : {...cu, statements} as JS.CompilationUnit;
|
|
160
|
+
const visited = await super.visitJsCompilationUnit(result, p) as JS.CompilationUnit;
|
|
161
|
+
if (removeHere) {
|
|
162
|
+
// The launch flag covers the whole process, so nothing needs the module any more.
|
|
163
|
+
maybeUnbind(this, {module: 'source-map-support'});
|
|
164
|
+
maybeUnbind(this, {module: 'source-map-support', member: 'install'});
|
|
165
|
+
}
|
|
166
|
+
return visited;
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const json = new class extends JsonVisitor<ExecutionContext> {
|
|
171
|
+
protected override async visitDocument(doc: Json.Document, p: ExecutionContext): Promise<Json | undefined> {
|
|
172
|
+
if (!isPackageJson(doc.sourcePath) || !isObject(doc.value)) return doc;
|
|
173
|
+
if (!scriptPackages.has(dirOf(doc.sourcePath))) return doc;
|
|
174
|
+
const root = doc.value as Json.Object;
|
|
175
|
+
let changed = false;
|
|
176
|
+
const members = root.members.map(padded => {
|
|
177
|
+
if (!isMember(padded.element)) return padded;
|
|
178
|
+
const member = padded.element as Json.Member;
|
|
179
|
+
if (getMemberKeyName(member) !== 'scripts' || !isObject(member.value)) return padded;
|
|
180
|
+
const scripts = member.value as Json.Object;
|
|
181
|
+
let sectionChanged = false;
|
|
182
|
+
const entries = scripts.members.map(entry => {
|
|
183
|
+
if (!isMember(entry.element)) return entry;
|
|
184
|
+
const script = entry.element as Json.Member;
|
|
185
|
+
const value = script.value;
|
|
186
|
+
if (!isLiteral(value) || typeof value.value !== 'string') return entry;
|
|
187
|
+
const updated = addSourceMapsFlag(value.value);
|
|
188
|
+
if (updated === value.value) return entry;
|
|
189
|
+
sectionChanged = true;
|
|
190
|
+
return {
|
|
191
|
+
...entry,
|
|
192
|
+
element: {...script, value: {...value, value: updated, source: JSON.stringify(updated)}} as Json.Member
|
|
193
|
+
};
|
|
194
|
+
});
|
|
195
|
+
if (!sectionChanged) return padded;
|
|
196
|
+
changed = true;
|
|
197
|
+
return {...padded, element: {...member, value: {...scripts, members: entries}} as Json.Member};
|
|
198
|
+
});
|
|
199
|
+
return changed ? {...doc, value: {...root, members}} as Json.Document : doc;
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
return new class extends TreeVisitor<Tree, ExecutionContext> {
|
|
204
|
+
protected override async accept(tree: Tree, p: ExecutionContext): Promise<Tree | undefined> {
|
|
205
|
+
if (tree.kind === JS.Kind.CompilationUnit) return js.visit(tree, p);
|
|
206
|
+
if (tree.kind === Json.Kind.Document) return json.visit(tree, p);
|
|
207
|
+
if (tree.kind === PlainText.Kind.PlainText) {
|
|
208
|
+
const text = tree as PlainText;
|
|
209
|
+
if (!shebangsToFlag.has(text.sourcePath)) return tree;
|
|
210
|
+
const lines = text.text.split('\n');
|
|
211
|
+
const flagged = shebangWithSourceMapsFlag(lines[0]);
|
|
212
|
+
if (!flagged) return tree;
|
|
213
|
+
return {...text, text: [flagged, ...lines.slice(1)].join('\n')} as PlainText;
|
|
214
|
+
}
|
|
215
|
+
return tree;
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Drops the statements `remove` selects, handing the first of a removed run its successor's place
|
|
223
|
+
* in the layout, so the blank line that separated it from what came before isn't left behind. Only
|
|
224
|
+
* the whitespace moves: a comment sits in the prefix of the statement it documents, so taking the
|
|
225
|
+
* successor's prefix wholesale would delete the comment that belongs to it.
|
|
226
|
+
*/
|
|
227
|
+
function withoutStatements(statements: J.RightPadded<any>[], remove: (s: J) => boolean): J.RightPadded<any>[] {
|
|
228
|
+
const kept: J.RightPadded<any>[] = [];
|
|
229
|
+
let pendingWhitespace: string | undefined;
|
|
230
|
+
for (const padded of statements) {
|
|
231
|
+
if (remove(padded.element)) {
|
|
232
|
+
pendingWhitespace ??= (padded.element.prefix as J.Space).whitespace;
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
kept.push(pendingWhitespace === undefined ? padded : {
|
|
236
|
+
...padded,
|
|
237
|
+
element: {...padded.element, prefix: {...padded.element.prefix, whitespace: pendingWhitespace}}
|
|
238
|
+
});
|
|
239
|
+
pendingWhitespace = undefined;
|
|
240
|
+
}
|
|
241
|
+
return kept;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** The innermost package a repo-relative path belongs to, of those the scan saw. */
|
|
245
|
+
function enclosingPackage(path: string, packageDirs: string[]): string {
|
|
246
|
+
return packageOf(path, packageDirs) ?? '';
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** `require('source-map-support/register')`, which loads the module for its side effect alone. */
|
|
250
|
+
function isRegisterRequire(m: J.MethodInvocation): boolean {
|
|
251
|
+
return !m.select && m.name.simpleName === 'require' &&
|
|
252
|
+
REGISTER_MODULES.has(stringLiteral(m.arguments.elements[0]?.element) ?? '');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** Whether a top-level statement exists only to install source-map-support. */
|
|
256
|
+
function isRedundantEntryPoint(statement: J): boolean {
|
|
257
|
+
if (statement.kind === JS.Kind.Import) {
|
|
258
|
+
const imp = statement as JS.Import;
|
|
259
|
+
return !imp.importClause && isSourceMapSupport(stringLiteral(imp.moduleSpecifier?.element));
|
|
260
|
+
}
|
|
261
|
+
const expression = statement.kind === JS.Kind.ExpressionStatement
|
|
262
|
+
? (statement as JS.ExpressionStatement).expression
|
|
263
|
+
: statement;
|
|
264
|
+
const call = expression?.kind === JS.Kind.Await ? (expression as JS.Await).expression : expression;
|
|
265
|
+
if (call?.kind !== J.Kind.MethodInvocation) return false;
|
|
266
|
+
const m = call as J.MethodInvocation;
|
|
267
|
+
if (isRegisterRequire(m)) return true;
|
|
268
|
+
if (m.name.simpleName !== 'install') return false;
|
|
269
|
+
const select = m.select?.element;
|
|
270
|
+
return select?.kind === J.Kind.MethodInvocation
|
|
271
|
+
? isSourceMapSupport(requiredModuleOf(select as J.MethodInvocation))
|
|
272
|
+
: select !== undefined;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function requiredModuleOf(m: J.MethodInvocation): string | undefined {
|
|
276
|
+
return !m.select && m.name.simpleName === 'require'
|
|
277
|
+
? stringLiteral(m.arguments.elements[0]?.element)
|
|
278
|
+
: undefined;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function binTargetsOf(value: Json.Value): string[] {
|
|
282
|
+
if (isLiteral(value)) return typeof value.value === 'string' ? [value.value] : [];
|
|
283
|
+
if (!isObject(value)) return [];
|
|
284
|
+
return (value as Json.Object).members.flatMap(padded => {
|
|
285
|
+
if (!isMember(padded.element)) return [];
|
|
286
|
+
const entry = (padded.element as Json.Member).value;
|
|
287
|
+
return isLiteral(entry) && typeof entry.value === 'string' ? [entry.value] : [];
|
|
288
|
+
});
|
|
289
|
+
}
|
|
@@ -14,17 +14,22 @@ export class MigrateSourceMapSupportToNative extends Recipe {
|
|
|
14
14
|
readonly displayName = "Migrate `source-map-support` to Node.js native source maps";
|
|
15
15
|
readonly description = "Replaces the `source-map-support` package with Node.js's built-in source map support. " +
|
|
16
16
|
"Where Node is launched from `package.json` scripts or mocha/ava configuration, `source-map-support/register` becomes " +
|
|
17
|
-
"the `--enable-source-maps` flag; in code, `install()` and `register` imports become
|
|
17
|
+
"the `--enable-source-maps` flag; in code, `install()` and `register` imports become " +
|
|
18
|
+
"`process.setSourceMapsEnabled(true)` where that maps everything the module loads. Where it would not " +
|
|
19
|
+
"— in an ES module every `import` is evaluated before any statement runs — the flag goes on the " +
|
|
20
|
+
"launcher instead, in the package's `bin` shebang or a `node` script, and the entry point is removed " +
|
|
21
|
+
"as redundant. " +
|
|
18
22
|
"Uses with no native equivalent (custom `retrieveSourceMap`, browser bundles, webpack banners) are left in place, and " +
|
|
19
|
-
"the dependency is removed from each `package.json` that
|
|
20
|
-
"recorded in the `SourceMapSupportManualMigrationSteps` data table with a
|
|
23
|
+
"the dependency is removed from each `package.json` that declares it, with the lock file updated to match. " +
|
|
24
|
+
"Everything left in place is recorded in the `SourceMapSupportManualMigrationSteps` data table with a " +
|
|
25
|
+
"suggested replacement, and has to be migrated before building, since the dependency is removed regardless.";
|
|
21
26
|
readonly tags = ["source-map-support"];
|
|
22
27
|
|
|
23
28
|
async recipeList(): Promise<Recipe[]> {
|
|
24
29
|
return [
|
|
25
30
|
...sourceMapSupportRewriteRecipes(),
|
|
26
31
|
new FindSourceMapSupportManualMigrations(),
|
|
27
|
-
new RemoveUnusedSourceMapSupportDependency(
|
|
32
|
+
new RemoveUnusedSourceMapSupportDependency(),
|
|
28
33
|
];
|
|
29
34
|
}
|
|
30
35
|
}
|
|
@@ -30,6 +30,54 @@ function commandOf(segment: string): string | undefined {
|
|
|
30
30
|
return undefined;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/** A `#!/usr/bin/env node` line, which `env -S` lets carry Node's own flags. */
|
|
34
|
+
const SHEBANG = /^#!(\s*\S*\/env\s+)(-S\s+)?node(?=\s|$)(.*)$/;
|
|
35
|
+
|
|
36
|
+
const ENABLE_FLAG = '--enable-source-maps';
|
|
37
|
+
|
|
38
|
+
export function hasSourceMapsFlag(text: string): boolean {
|
|
39
|
+
return new RegExp(`(^|\\s)${ENABLE_FLAG}(\\s|$)`).test(text);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Whether any part of a shell command launches this package's own code under Node. An inline
|
|
44
|
+
* `node -e` program and a `node_modules/...` entry point are Node processes too, but neither loads
|
|
45
|
+
* the module whose entry point is being removed, so neither is a launcher worth flagging.
|
|
46
|
+
*/
|
|
47
|
+
export function launchesNode(script: string): boolean {
|
|
48
|
+
return script.split(/(\s*(?:&&|\|\||;|\|)\s*)/).some((part, i) => {
|
|
49
|
+
if (i % 2 === 1 || !NODE_FLAG_COMMANDS.has(commandOf(part) ?? '')) return false;
|
|
50
|
+
if (/(^|\s)(?:-e|--eval|-p|--print)(?=\s|$)/.test(part)) return false;
|
|
51
|
+
return !/(^|\s)node_modules\//.test(part);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Adds `--enable-source-maps` to each part of a shell command that runs Node or mocha, so the flag
|
|
57
|
+
* is in place before any module loads. Parts running anything else are left alone, since a flag
|
|
58
|
+
* they don't recognize would fail them.
|
|
59
|
+
*/
|
|
60
|
+
export function addSourceMapsFlag(script: string): string {
|
|
61
|
+
const parts = script.split(/(\s*(?:&&|\|\||;|\|)\s*)/);
|
|
62
|
+
return parts.map((part, i) => {
|
|
63
|
+
if (i % 2 === 1 || !NODE_FLAG_COMMANDS.has(commandOf(part) ?? '') || hasSourceMapsFlag(part)) return part;
|
|
64
|
+
// Straight after the command, where Node's own flags have to sit: anything later is the script's.
|
|
65
|
+
return part.replace(/(^|\s)((?:\S*[\\/])?(?:node|mocha|_mocha))(?=\s|$)/, `$1$2 ${ENABLE_FLAG}`);
|
|
66
|
+
}).join('');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The shebang with `--enable-source-maps` added, or undefined where the line isn't a plain
|
|
71
|
+
* `env node` shebang. `env -S` is what allows an argument at all; without it the kernel hands
|
|
72
|
+
* `node --enable-source-maps` to `env` as a single program name.
|
|
73
|
+
*/
|
|
74
|
+
export function shebangWithSourceMapsFlag(line: string): string | undefined {
|
|
75
|
+
const match = SHEBANG.exec(line);
|
|
76
|
+
if (!match || hasSourceMapsFlag(line)) return undefined;
|
|
77
|
+
const [, env, dashS, rest] = match;
|
|
78
|
+
return `#!${env}${dashS ?? '-S '}node ${ENABLE_FLAG}${rest}`;
|
|
79
|
+
}
|
|
80
|
+
|
|
33
81
|
/**
|
|
34
82
|
* Replaces `-r source-map-support/register` with `--enable-source-maps` in each part of a shell
|
|
35
83
|
* command that runs Node or mocha. Parts that run anything else are left alone, since a flag they
|