@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openrewrite/recipes-nodejs",
|
|
3
|
-
"version": "0.51.
|
|
3
|
+
"version": "0.51.2",
|
|
4
4
|
"license": "Moderne Proprietary",
|
|
5
5
|
"description": "OpenRewrite recipes for Node.js library migrations.",
|
|
6
6
|
"homepage": "https://github.com/moderneinc/rewrite-node",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"ci:test": "jest"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@openrewrite/rewrite": "^8.
|
|
28
|
+
"@openrewrite/rewrite": "^8.92.18",
|
|
29
29
|
"mutative": "^1.1.0",
|
|
30
30
|
"semver": "^7.7.3"
|
|
31
31
|
},
|
package/src/index.ts
CHANGED
|
@@ -24,15 +24,22 @@ import {ReplaceOutgoingMessageHeaders} from "./migrate/outgoing-message-headers"
|
|
|
24
24
|
import {ReplaceTlsInternalModules} from "./migrate/tls-internal-modules";
|
|
25
25
|
import {ReplaceStreamInternalModules} from "./migrate/stream-internal-modules";
|
|
26
26
|
import {RemoveNetSetSimultaneousAccepts} from "./migrate/net-set-simultaneous-accepts";
|
|
27
|
+
import {UpgradeNode20} from "./migrate/upgrade-node-20";
|
|
27
28
|
import {UpgradeNode22} from "./migrate/upgrade-node-22";
|
|
28
29
|
import {UpgradeNode24} from "./migrate/upgrade-node-24";
|
|
30
|
+
import {UpgradeNode26} from "./migrate/upgrade-node-26";
|
|
29
31
|
import {IncreaseNodeEngineVersion} from "./migrate/increase-node-engine-version";
|
|
30
32
|
import {IncreaseNodeEngineVersionInGithubActions} from "./migrate/increase-node-engine-version-in-github-actions";
|
|
31
33
|
import {FindCryptoCreateCipher} from "./migrate/find-crypto-create-cipher";
|
|
34
|
+
import {FindCryptoDefaultEncoding} from "./migrate/find-crypto-default-encoding";
|
|
32
35
|
import {FindPunycodeUsage} from "./migrate/find-punycode-usage";
|
|
33
36
|
import {FindProcessAssert} from "./migrate/find-process-assert";
|
|
34
37
|
import {FindTlsSecurePair} from "./migrate/find-tls-secure-pair";
|
|
35
38
|
import {FindTimersActive} from "./migrate/find-timers-active";
|
|
39
|
+
import {ReplaceServerResponseWriteHeader} from "./migrate/server-response-write-header";
|
|
40
|
+
import {FindAeadDecipherivWithoutAuthTagLength} from "./migrate/find-aead-decipheriv-without-auth-tag-length";
|
|
41
|
+
import {FindDuplexToWebType} from "./migrate/find-duplex-to-web-type";
|
|
42
|
+
import {FindModuleRegister} from "./migrate/find-module-register";
|
|
36
43
|
import {MigrateFsExtraToNodeFs} from "./migrate/fs-extra/migrate-fs-extra-to-node-fs";
|
|
37
44
|
import {ReplaceFsExtraRemove} from "./migrate/fs-extra/replace-fs-extra-remove";
|
|
38
45
|
import {ReplaceFsExtraMkdirs} from "./migrate/fs-extra/replace-fs-extra-mkdirs";
|
|
@@ -53,6 +60,7 @@ import {FindHpagentManualMigrations} from "./migrate/hpagent/find-hpagent-manual
|
|
|
53
60
|
import {SwapHpagentDependency} from "./migrate/hpagent/swap-hpagent-dependency";
|
|
54
61
|
import {MigrateSourceMapSupportToNative} from "./migrate/source-map-support/migrate-source-map-support-to-native";
|
|
55
62
|
import {FindSourceMapSupportManualMigrations} from "./migrate/source-map-support/find-source-map-support-manual-migrations";
|
|
63
|
+
import {EnableSourceMapsAtLaunch} from "./migrate/source-map-support/enable-source-maps-at-launch";
|
|
56
64
|
import {ReplaceSourceMapSupportInstall} from "./migrate/source-map-support/replace-source-map-support-install";
|
|
57
65
|
import {ReplaceSourceMapSupportRegisterInPackageJson} from "./migrate/source-map-support/replace-source-map-support-register-in-package-json";
|
|
58
66
|
import {ReplaceSourceMapSupportRegisterInMochaConfig} from "./migrate/source-map-support/replace-source-map-support-register-in-mocha-config";
|
|
@@ -99,15 +107,22 @@ export async function activate(marketplace: RecipeMarketplace): Promise<void> {
|
|
|
99
107
|
await marketplace.install(ReplaceTlsInternalModules, Migrate);
|
|
100
108
|
await marketplace.install(ReplaceStreamInternalModules, Migrate);
|
|
101
109
|
await marketplace.install(RemoveNetSetSimultaneousAccepts, Migrate);
|
|
110
|
+
await marketplace.install(UpgradeNode20, Migrate);
|
|
102
111
|
await marketplace.install(UpgradeNode22, Migrate);
|
|
103
112
|
await marketplace.install(UpgradeNode24, Migrate);
|
|
113
|
+
await marketplace.install(UpgradeNode26, Migrate);
|
|
104
114
|
await marketplace.install(IncreaseNodeEngineVersion, Migrate);
|
|
105
115
|
await marketplace.install(IncreaseNodeEngineVersionInGithubActions, Migrate);
|
|
106
116
|
await marketplace.install(FindCryptoCreateCipher, Migrate);
|
|
117
|
+
await marketplace.install(FindCryptoDefaultEncoding, Migrate);
|
|
107
118
|
await marketplace.install(FindPunycodeUsage, Migrate);
|
|
108
119
|
await marketplace.install(FindProcessAssert, Migrate);
|
|
109
120
|
await marketplace.install(FindTlsSecurePair, Migrate);
|
|
110
121
|
await marketplace.install(FindTimersActive, Migrate);
|
|
122
|
+
await marketplace.install(ReplaceServerResponseWriteHeader, Migrate);
|
|
123
|
+
await marketplace.install(FindAeadDecipherivWithoutAuthTagLength, Migrate);
|
|
124
|
+
await marketplace.install(FindDuplexToWebType, Migrate);
|
|
125
|
+
await marketplace.install(FindModuleRegister, Migrate);
|
|
111
126
|
await marketplace.install(MigrateFsExtraToNodeFs, FsExtra);
|
|
112
127
|
await marketplace.install(ReplaceFsExtraRemove, FsExtra);
|
|
113
128
|
await marketplace.install(ReplaceFsExtraMkdirs, FsExtra);
|
|
@@ -128,6 +143,7 @@ export async function activate(marketplace: RecipeMarketplace): Promise<void> {
|
|
|
128
143
|
await marketplace.install(SwapHpagentDependency, Hpagent);
|
|
129
144
|
await marketplace.install(MigrateSourceMapSupportToNative, SourceMapSupport);
|
|
130
145
|
await marketplace.install(ReplaceSourceMapSupportInstall, SourceMapSupport);
|
|
146
|
+
await marketplace.install(EnableSourceMapsAtLaunch, SourceMapSupport);
|
|
131
147
|
await marketplace.install(ReplaceSourceMapSupportRegisterInPackageJson, SourceMapSupport);
|
|
132
148
|
await marketplace.install(ReplaceSourceMapSupportRegisterInMochaConfig, SourceMapSupport);
|
|
133
149
|
await marketplace.install(FindSourceMapSupportManualMigrations, SourceMapSupport);
|
|
@@ -11,7 +11,7 @@ import {JavaScriptVisitor, JS, maybeUnbind, walk} from "@openrewrite/rewrite/jav
|
|
|
11
11
|
import {PlainText} from "@openrewrite/rewrite/text";
|
|
12
12
|
import * as semver from "semver";
|
|
13
13
|
import {bindingsOf, importModule, ModuleBindings} 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 PROVIDER_MODULES = new Set(['@angular/platform-browser/animations', '@angular/platform-browser/animations/async']);
|
|
17
17
|
const ANIMATIONS = '@angular/animations';
|
|
@@ -12,13 +12,14 @@ export class MigrateExpressRequestIdToNative extends Recipe {
|
|
|
12
12
|
readonly name = "org.openrewrite.node.migrate.express-request-id.migrate-express-request-id-to-native";
|
|
13
13
|
readonly displayName = "Migrate `express-request-id` to an inline middleware";
|
|
14
14
|
readonly description = "Replaces the `express-request-id` middleware with an equivalent inline middleware built on " +
|
|
15
|
-
"`randomUUID()` from `node:crypto`, then removes the dependency from each `package.json` that
|
|
15
|
+
"`randomUUID()` from `node:crypto`, then removes the dependency from each `package.json` that declares it, " +
|
|
16
|
+
"updating the lock file to match.";
|
|
16
17
|
readonly tags = ["express-request-id"];
|
|
17
18
|
|
|
18
19
|
async recipeList(): Promise<Recipe[]> {
|
|
19
20
|
return [
|
|
20
21
|
new ReplaceExpressRequestId(),
|
|
21
|
-
new RemoveUnusedExpressRequestIdDependency(
|
|
22
|
+
new RemoveUnusedExpressRequestIdDependency(),
|
|
22
23
|
];
|
|
23
24
|
}
|
|
24
25
|
}
|
|
@@ -4,22 +4,14 @@
|
|
|
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 {RemoveUnusedDependency} from "../remove-unused-dependency";
|
|
9
|
-
import {ReplaceExpressRequestId} from "./replace-express-request-id";
|
|
7
|
+
import {RemoveDependencies} from "../remove-dependencies";
|
|
10
8
|
|
|
11
|
-
export class RemoveUnusedExpressRequestIdDependency extends
|
|
9
|
+
export class RemoveUnusedExpressRequestIdDependency extends RemoveDependencies {
|
|
12
10
|
readonly name = "org.openrewrite.node.migrate.express-request-id.remove-unused-express-request-id-dependency";
|
|
13
|
-
readonly displayName = "Remove
|
|
14
|
-
readonly description = "Removes `express-request-id` and `@types/express-request-id` from each `package.json`
|
|
15
|
-
"
|
|
16
|
-
"since the application may use it directly. Lock files are not regenerated; run the package manager's install afterwards.";
|
|
11
|
+
readonly displayName = "Remove the `express-request-id` dependency";
|
|
12
|
+
readonly description = "Removes `express-request-id` and `@types/express-request-id` from each `package.json` " +
|
|
13
|
+
"that declares them, and updates the lock file to match. `uuid` is kept, since the application may use it directly.";
|
|
17
14
|
readonly tags = ["express-request-id"];
|
|
18
15
|
|
|
19
|
-
protected readonly module = 'express-request-id';
|
|
20
16
|
protected readonly packages = ['express-request-id', '@types/express-request-id'];
|
|
21
|
-
|
|
22
|
-
protected migrationRecipes(): Recipe[] {
|
|
23
|
-
return [new ReplaceExpressRequestId()];
|
|
24
|
-
}
|
|
25
17
|
}
|
|
@@ -0,0 +1,76 @@
|
|
|
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, foundSearchResult, or, Recipe, TreeVisitor} from "@openrewrite/rewrite";
|
|
7
|
+
import {JavaScriptVisitor, JS, MethodMatcher, usesMethod} from "@openrewrite/rewrite/javascript";
|
|
8
|
+
import {J} from "@openrewrite/rewrite/java";
|
|
9
|
+
|
|
10
|
+
export class FindAeadDecipherivWithoutAuthTagLength extends Recipe {
|
|
11
|
+
readonly name = "org.openrewrite.node.migrate.crypto.find-aead-decipheriv-without-auth-tag-length"
|
|
12
|
+
readonly displayName: string = "Find `crypto.createDecipheriv()` for AEAD ciphers without `authTagLength`";
|
|
13
|
+
readonly description: string = "Accepting shorter-than-default authentication tags for AEAD ciphers " +
|
|
14
|
+
"(`aes-*-gcm`, `aes-*-ccm`, `aes-*-ocb`, `chacha20-poly1305`) in `crypto.createDecipheriv()` is end-of-life in " +
|
|
15
|
+
"Node.js 26 (DEP0182). Pass an explicit `authTagLength` in the options so the tag length is validated at decipher " +
|
|
16
|
+
"construction instead of at `setAuthTag()` time.";
|
|
17
|
+
readonly tags: string[] = ["DEP0182"];
|
|
18
|
+
|
|
19
|
+
async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
20
|
+
return check(or(
|
|
21
|
+
usesMethod("crypto createDecipheriv(..)"),
|
|
22
|
+
usesMethod("node:crypto createDecipheriv(..)"),
|
|
23
|
+
), new class extends JavaScriptVisitor<ExecutionContext> {
|
|
24
|
+
matcher = new MethodMatcher("crypto createDecipheriv(..)");
|
|
25
|
+
nodeMatcher = new MethodMatcher("node:crypto createDecipheriv(..)");
|
|
26
|
+
|
|
27
|
+
private readonly aead = /^(aes-\d+-(gcm|ccm|ocb)|chacha20-poly1305)$/i;
|
|
28
|
+
|
|
29
|
+
private isAeadAlgorithm(expr: J): boolean {
|
|
30
|
+
if (expr.kind !== J.Kind.Literal) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
const value = (expr as J.Literal).value;
|
|
34
|
+
return typeof value === "string" && this.aead.test(value);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private hasAuthTagLength(expr: J): boolean {
|
|
38
|
+
if (expr.kind !== J.Kind.NewClass || (expr as J.NewClass).class) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
const obj = expr as J.NewClass;
|
|
42
|
+
const statements = obj.body?.statements ?? [];
|
|
43
|
+
for (const stmt of statements) {
|
|
44
|
+
const element = stmt.element;
|
|
45
|
+
if (element.kind !== JS.Kind.PropertyAssignment) {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const name = (element as JS.PropertyAssignment).name.element;
|
|
49
|
+
if (name.kind === J.Kind.Identifier && (name as J.Identifier).simpleName === "authTagLength") {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
if (name.kind === J.Kind.Literal && (name as J.Literal).value === "authTagLength") {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
protected async visitMethodInvocation(m: J.MethodInvocation, p: ExecutionContext): Promise<J | undefined> {
|
|
60
|
+
const method = await super.visitMethodInvocation(m, p) as J.MethodInvocation;
|
|
61
|
+
if (!this.matcher.matches(method.methodType) && !this.nodeMatcher.matches(method.methodType)) {
|
|
62
|
+
return method;
|
|
63
|
+
}
|
|
64
|
+
const args = method.arguments.elements;
|
|
65
|
+
if (args.length === 0 || !this.isAeadAlgorithm(args[0].element)) {
|
|
66
|
+
return method;
|
|
67
|
+
}
|
|
68
|
+
if (args.length >= 4 && this.hasAuthTagLength(args[3].element)) {
|
|
69
|
+
return method;
|
|
70
|
+
}
|
|
71
|
+
return foundSearchResult(method,
|
|
72
|
+
"crypto.createDecipheriv() for AEAD ciphers without authTagLength is end-of-life (DEP0182). Pass authTagLength in options.");
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
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 {ExecutionContext, foundSearchResult, Recipe, TreeVisitor} from "@openrewrite/rewrite";
|
|
7
|
+
import {JavaScriptVisitor} from "@openrewrite/rewrite/javascript";
|
|
8
|
+
import {J, Type} from "@openrewrite/rewrite/java";
|
|
9
|
+
|
|
10
|
+
const FQNS = new Set(["crypto", "node:crypto"]);
|
|
11
|
+
|
|
12
|
+
export class FindCryptoDefaultEncoding extends Recipe {
|
|
13
|
+
readonly name = "org.openrewrite.node.migrate.find-crypto-default-encoding"
|
|
14
|
+
readonly displayName: string = "Find deprecated `crypto.DEFAULT_ENCODING` usage";
|
|
15
|
+
readonly description: string = "The `crypto.DEFAULT_ENCODING` property was deprecated in Node.js 10 (DEP0091) " +
|
|
16
|
+
"and removed in Node.js 20. Each API that accepted an encoding (hash, hmac, cipher, etc.) now requires " +
|
|
17
|
+
"the encoding to be passed explicitly. Occurrences must be reviewed and fixed by hand.";
|
|
18
|
+
readonly tags: string[] = ["DEP0091"];
|
|
19
|
+
|
|
20
|
+
async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
21
|
+
return new class extends JavaScriptVisitor<ExecutionContext> {
|
|
22
|
+
private isCryptoReceiver(target: J | undefined): boolean {
|
|
23
|
+
if (!target || target.kind !== J.Kind.Identifier) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
const type = (target as J.Identifier).type;
|
|
27
|
+
if (!type) {
|
|
28
|
+
return (target as J.Identifier).simpleName === "crypto";
|
|
29
|
+
}
|
|
30
|
+
if (type.kind === Type.Kind.Class) {
|
|
31
|
+
const fqn = (type as Type.Class).fullyQualifiedName;
|
|
32
|
+
return FQNS.has(fqn);
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
protected async visitFieldAccess(fieldAccess: J.FieldAccess, p: ExecutionContext): Promise<J | undefined> {
|
|
38
|
+
const fa = await super.visitFieldAccess(fieldAccess, p) as J.FieldAccess;
|
|
39
|
+
if (fa.name.element.simpleName === "DEFAULT_ENCODING" && this.isCryptoReceiver(fa.target)) {
|
|
40
|
+
return foundSearchResult(fa,
|
|
41
|
+
"crypto.DEFAULT_ENCODING was removed in Node.js 20 (DEP0091). Pass the desired encoding explicitly to each crypto API instead.");
|
|
42
|
+
}
|
|
43
|
+
return fa;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
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, foundSearchResult, Recipe, TreeVisitor} from "@openrewrite/rewrite";
|
|
7
|
+
import {JavaScriptVisitor, JS, usesType} from "@openrewrite/rewrite/javascript";
|
|
8
|
+
import {J, Type} from "@openrewrite/rewrite/java";
|
|
9
|
+
|
|
10
|
+
export class FindDuplexToWebType extends Recipe {
|
|
11
|
+
readonly name = "org.openrewrite.node.migrate.stream.find-duplex-to-web-type"
|
|
12
|
+
readonly displayName: string = "Find `Duplex.toWeb()` calls passing `options.type`";
|
|
13
|
+
readonly description: string = "Passing `options.type` to `stream.Duplex.toWeb()` is runtime-deprecated in Node.js 26 (DEP0201). " +
|
|
14
|
+
"The `type` field controls the readable-side ReadableStream chunk type; drop it from the options and set the type on the " +
|
|
15
|
+
"resulting `ReadableStream` instead.";
|
|
16
|
+
readonly tags: string[] = ["DEP0201"];
|
|
17
|
+
|
|
18
|
+
async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
19
|
+
return check(usesType("Stream.Duplex"), new class extends JavaScriptVisitor<ExecutionContext> {
|
|
20
|
+
|
|
21
|
+
private isDuplexReceiver(select: J | undefined): boolean {
|
|
22
|
+
if (!select || select.kind !== J.Kind.Identifier) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
const type = (select as J.Identifier).type;
|
|
26
|
+
if (type?.kind !== Type.Kind.Class) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
return (type as Type.Class).fullyQualifiedName === "Stream.Duplex";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
private hasTypeKey(expr: J): boolean {
|
|
33
|
+
if (expr.kind !== J.Kind.NewClass || (expr as J.NewClass).class) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
const obj = expr as J.NewClass;
|
|
37
|
+
const statements = obj.body?.statements ?? [];
|
|
38
|
+
for (const stmt of statements) {
|
|
39
|
+
const element = stmt.element;
|
|
40
|
+
if (element.kind !== JS.Kind.PropertyAssignment) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const name = (element as JS.PropertyAssignment).name.element;
|
|
44
|
+
if (name.kind === J.Kind.Identifier && (name as J.Identifier).simpleName === "type") {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
if (name.kind === J.Kind.Literal && (name as J.Literal).value === "type") {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
protected async visitMethodInvocation(m: J.MethodInvocation, p: ExecutionContext): Promise<J | undefined> {
|
|
55
|
+
const method = await super.visitMethodInvocation(m, p) as J.MethodInvocation;
|
|
56
|
+
if (method.name.simpleName !== "toWeb" || !this.isDuplexReceiver(method.select?.element)) {
|
|
57
|
+
return method;
|
|
58
|
+
}
|
|
59
|
+
const args = method.arguments.elements;
|
|
60
|
+
if (args.length < 2) {
|
|
61
|
+
return method;
|
|
62
|
+
}
|
|
63
|
+
if (!this.hasTypeKey(args[1].element)) {
|
|
64
|
+
return method;
|
|
65
|
+
}
|
|
66
|
+
return foundSearchResult(method,
|
|
67
|
+
"Duplex.toWeb() options.type is runtime-deprecated (DEP0201). Drop it from the options and set the type on the resulting ReadableStream instead.");
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
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, foundSearchResult, or, Recipe, TreeVisitor} from "@openrewrite/rewrite";
|
|
7
|
+
import {JavaScriptVisitor, MethodMatcher, usesMethod} from "@openrewrite/rewrite/javascript";
|
|
8
|
+
import {J} from "@openrewrite/rewrite/java";
|
|
9
|
+
|
|
10
|
+
export class FindModuleRegister extends Recipe {
|
|
11
|
+
readonly name = "org.openrewrite.node.migrate.find-module-register"
|
|
12
|
+
readonly displayName: string = "Find `module.register()` usage";
|
|
13
|
+
readonly description: string = "`module.register()` for loading ESM loader hooks is runtime-deprecated in Node.js 26 (DEP0205). " +
|
|
14
|
+
"Use the `--import` CLI flag with a loader module instead.";
|
|
15
|
+
readonly tags: string[] = ["DEP0205"];
|
|
16
|
+
|
|
17
|
+
async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
18
|
+
return check(or(
|
|
19
|
+
usesMethod("module register(..)"),
|
|
20
|
+
usesMethod("node:module register(..)"),
|
|
21
|
+
), new class extends JavaScriptVisitor<ExecutionContext> {
|
|
22
|
+
moduleRegisterMatcher = new MethodMatcher("module register(..)");
|
|
23
|
+
nodeModuleRegisterMatcher = new MethodMatcher("node:module register(..)");
|
|
24
|
+
|
|
25
|
+
protected async visitMethodInvocation(m: J.MethodInvocation, p: ExecutionContext): Promise<J | undefined> {
|
|
26
|
+
const method = await super.visitMethodInvocation(m, p) as J.MethodInvocation;
|
|
27
|
+
if (this.moduleRegisterMatcher.matches(method.methodType) ||
|
|
28
|
+
this.nodeModuleRegisterMatcher.matches(method.methodType)) {
|
|
29
|
+
return foundSearchResult(method,
|
|
30
|
+
"module.register() is runtime-deprecated (DEP0205). Use the --import CLI flag with a loader module instead.");
|
|
31
|
+
}
|
|
32
|
+
return method;
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -14,7 +14,7 @@ export class FindProcessAssert extends Recipe {
|
|
|
14
14
|
readonly tags: string[] = ["DEP0100"];
|
|
15
15
|
|
|
16
16
|
async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
17
|
-
return check(usesType("
|
|
17
|
+
return check(usesType("NodeJS.Process"), new class extends JavaScriptVisitor<ExecutionContext> {
|
|
18
18
|
protected async visitMethodInvocation(m: J.MethodInvocation, p: ExecutionContext): Promise<J | undefined> {
|
|
19
19
|
const method = await super.visitMethodInvocation(m, p) as J.MethodInvocation;
|
|
20
20
|
if (method.name.simpleName !== "assert" || !method.select) {
|
|
@@ -25,7 +25,7 @@ export class FindProcessAssert extends Recipe {
|
|
|
25
25
|
if (select.kind === J.Kind.Identifier) {
|
|
26
26
|
const id = select as J.Identifier;
|
|
27
27
|
if (id.simpleName === "process" && id.type?.kind === Type.Kind.Class &&
|
|
28
|
-
(id.type as Type.Class).fullyQualifiedName === "
|
|
28
|
+
(id.type as Type.Class).fullyQualifiedName === "NodeJS.Process") {
|
|
29
29
|
return foundSearchResult(method,
|
|
30
30
|
"process.assert() is deprecated (DEP0100). Use the assert module instead.");
|
|
31
31
|
}
|
|
@@ -20,7 +20,9 @@ export class MigrateFsExtraToNodeFs extends Recipe {
|
|
|
20
20
|
"existing destination, or work across devices. Calls without a faithful native equivalent " +
|
|
21
21
|
"(`ensureSymlink`, `ensureLink`, ...) are left in place and listed in the `FsExtraManualMigrationSteps` " +
|
|
22
22
|
"data table. " +
|
|
23
|
-
"`fs-extra` and `@types/fs-extra` are removed from each `package.json`
|
|
23
|
+
"`fs-extra` and `@types/fs-extra` are removed from each `package.json` that declares them, with the lock file " +
|
|
24
|
+
"updated to match. They are removed even where a call listed in the data table still uses fs-extra, so " +
|
|
25
|
+
"migrate those before building. " +
|
|
24
26
|
"Tests that mock or spy on fs-extra itself (`vi.mock('fs-extra')`, `vi.spyOn(fs, 'outputFile')`, ...) keep " +
|
|
25
27
|
"watching a module the migrated code no longer calls, so they fail until they mock `node:fs` / " +
|
|
26
28
|
"`node:fs/promises` instead. Every such site is listed in the data table; migrate them together with the " +
|
|
@@ -31,7 +33,7 @@ export class MigrateFsExtraToNodeFs extends Recipe {
|
|
|
31
33
|
return [
|
|
32
34
|
...fsExtraRewriteRecipes(),
|
|
33
35
|
new FindFsExtraManualMigrations(),
|
|
34
|
-
new RemoveUnusedFsExtraDependency(
|
|
36
|
+
new RemoveUnusedFsExtraDependency(),
|
|
35
37
|
];
|
|
36
38
|
}
|
|
37
39
|
}
|
|
@@ -4,24 +4,14 @@
|
|
|
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 {fsExtraRewriteRecipes} from "./fs-extra-rewrites";
|
|
9
|
-
import {RemoveUnusedDependency} from "../remove-unused-dependency";
|
|
7
|
+
import {RemoveDependencies} from "../remove-dependencies";
|
|
10
8
|
|
|
11
|
-
export class RemoveUnusedFsExtraDependency extends
|
|
9
|
+
export class RemoveUnusedFsExtraDependency extends RemoveDependencies {
|
|
12
10
|
readonly name = "org.openrewrite.node.migrate.fs-extra.remove-unused-fs-extra-dependency";
|
|
13
|
-
readonly displayName = "Remove
|
|
14
|
-
readonly description = "Removes `fs-extra` and `@types/fs-extra` from each `package.json`
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
"`package.json`'s directory keeps its dependencies, since a monorepo root hoists them for its workspaces. " +
|
|
18
|
-
"A package is left alone when any source beneath it failed to parse, or none was parsed at all. " +
|
|
19
|
-
"Lock files are not regenerated; run the package manager's install afterwards.";
|
|
11
|
+
readonly displayName = "Remove the `fs-extra` dependency";
|
|
12
|
+
readonly description = "Removes `fs-extra` and `@types/fs-extra` from each `package.json` that declares them, " +
|
|
13
|
+
"and updates the lock file to match.";
|
|
14
|
+
readonly tags = ["fs-extra"];
|
|
20
15
|
|
|
21
|
-
protected readonly module = 'fs-extra';
|
|
22
16
|
protected readonly packages = ['fs-extra', '@types/fs-extra'];
|
|
23
|
-
|
|
24
|
-
protected migrationRecipes(): Recipe[] {
|
|
25
|
-
return fsExtraRewriteRecipes();
|
|
26
|
-
}
|
|
27
17
|
}
|
|
@@ -59,7 +59,7 @@ function uniformTarget(cu: JS.CompilationUnit, local: string, member: string): N
|
|
|
59
59
|
const body = cu.statements.filter(s => s.element?.kind !== JS.Kind.Import && !declarationsOf(s.element).some(d =>
|
|
60
60
|
d.variables.some(v => v.element.initializer?.element?.kind === J.Kind.MethodInvocation &&
|
|
61
61
|
(v.element.initializer.element as J.MethodInvocation).name.simpleName === 'require')));
|
|
62
|
-
walk(body, node => {
|
|
62
|
+
walk(body, (node: any) => {
|
|
63
63
|
if (node.kind === J.Kind.Identifier && node.simpleName === local) {
|
|
64
64
|
occurrences++;
|
|
65
65
|
} else if (node.kind === J.Kind.MethodInvocation && !node.select && node.name.simpleName === local) {
|
|
@@ -17,14 +17,14 @@ export class MigrateHpagentToHttpsProxyAgent extends Recipe {
|
|
|
17
17
|
"tunnel to TLS only for an `https:` target — so `HttpProxyAgent` and `HttpsProxyAgent` both become " +
|
|
18
18
|
"`https-proxy-agent`'s `HttpsProxyAgent`. The proxy URL moves from the `proxy` option to the constructor's " +
|
|
19
19
|
"first argument, `proxyRequestOptions` members move up into the options object, and the dependency is swapped " +
|
|
20
|
-
"in each `package.json` that declares it.\n\n" +
|
|
20
|
+
"in each `package.json` that declares it, with the lock file updated to match.\n\n" +
|
|
21
21
|
"Two things do not carry over, and both are reported in the `HpagentManualMigrationSteps` data table rather " +
|
|
22
22
|
"than rewritten. A TLS option set at the top level of hpagent's `HttpsProxyAgent` (`ca`, `rejectUnauthorized`, " +
|
|
23
23
|
"`cert`, ...) configured the handshake with the **destination**, because that class extends `https.Agent`; " +
|
|
24
24
|
"`https-proxy-agent` spends its constructor options on the connection to the **proxy** and takes the " +
|
|
25
25
|
"destination's TLS options from each request, so such an option has to move to the request or the client " +
|
|
26
26
|
"library. Agent options that aren't written as an object literal can't be split into the new two-argument " +
|
|
27
|
-
"form either.";
|
|
27
|
+
"form either. The dependency is swapped regardless, so migrate every reported site before building.";
|
|
28
28
|
readonly tags = ["hpagent", "https-proxy-agent"];
|
|
29
29
|
|
|
30
30
|
@Option({
|
|
@@ -53,11 +53,7 @@ export class MigrateHpagentToHttpsProxyAgent extends Recipe {
|
|
|
53
53
|
return [
|
|
54
54
|
new ReplaceHpagent({migrateSpreadOptions: this.migrateSpreadOptions}),
|
|
55
55
|
new FindHpagentManualMigrations(),
|
|
56
|
-
new SwapHpagentDependency({
|
|
57
|
-
version: this.version,
|
|
58
|
-
migrateSpreadOptions: this.migrateSpreadOptions,
|
|
59
|
-
accountForMigration: true
|
|
60
|
-
}),
|
|
56
|
+
new SwapHpagentDependency({version: this.version}),
|
|
61
57
|
];
|
|
62
58
|
}
|
|
63
59
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Moderne Proprietary. Only for use by Moderne customers under the terms of a commercial contract.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {ExecutionContext, Option, Recipe, TreeVisitor} from "@openrewrite/rewrite";
|
|
7
|
+
import {emptyMarkers, ExecutionContext, Option, randomId, Recipe, TreeVisitor} from "@openrewrite/rewrite";
|
|
8
8
|
import {isIdentifier, J, rightPadded, space} from "@openrewrite/rewrite/java";
|
|
9
9
|
import {compilationUnitOf, declarationsOf, JavaScriptVisitor, JS, maybeRebind, maybeUnbind, namesDeclaredIn} from "@openrewrite/rewrite/javascript";
|
|
10
10
|
import {requiredModule} from "../module-bindings";
|
|
@@ -107,6 +107,76 @@ function typeKey(type: J): string | undefined {
|
|
|
107
107
|
return isIdentifier(clazz) ? `${clazz.simpleName}<>` : undefined;
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Whether `proxy` is a URL however the program got here. hpagent keeps a missing proxy until a request
|
|
112
|
+
* needs it, but `https-proxy-agent` reads it in the constructor, so anything else may now throw there.
|
|
113
|
+
*/
|
|
114
|
+
function alwaysDefined(proxy: J): boolean {
|
|
115
|
+
switch (proxy.kind) {
|
|
116
|
+
case J.Kind.Literal:
|
|
117
|
+
return typeof (proxy as J.Literal).value === 'string';
|
|
118
|
+
case JS.Kind.TemplateExpression:
|
|
119
|
+
case J.Kind.NewClass:
|
|
120
|
+
return true;
|
|
121
|
+
case J.Kind.Parentheses:
|
|
122
|
+
return alwaysDefined((proxy as J.Parentheses<J>).tree.element);
|
|
123
|
+
case J.Kind.Binary:
|
|
124
|
+
return (proxy as J.Binary).operator.element === J.Binary.Type.Or && alwaysDefined((proxy as J.Binary).right);
|
|
125
|
+
case JS.Kind.Binary:
|
|
126
|
+
return (proxy as JS.Binary).operator.element === JS.Binary.Type.QuestionQuestion && alwaysDefined((proxy as JS.Binary).right);
|
|
127
|
+
default:
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Whether evaluating `e` twice is the same as evaluating it once: names, member reads and fallbacks among them. */
|
|
133
|
+
function readsOnly(e: J): boolean {
|
|
134
|
+
switch (e.kind) {
|
|
135
|
+
case J.Kind.Identifier:
|
|
136
|
+
case J.Kind.Literal:
|
|
137
|
+
return true;
|
|
138
|
+
case J.Kind.FieldAccess:
|
|
139
|
+
return readsOnly((e as J.FieldAccess).target);
|
|
140
|
+
case J.Kind.ArrayAccess: {
|
|
141
|
+
const access = e as J.ArrayAccess;
|
|
142
|
+
return readsOnly(access.indexed) && access.dimension.index.element.kind === J.Kind.Literal;
|
|
143
|
+
}
|
|
144
|
+
case J.Kind.Parentheses:
|
|
145
|
+
return readsOnly((e as J.Parentheses<J>).tree.element);
|
|
146
|
+
case J.Kind.Binary:
|
|
147
|
+
return (e as J.Binary).operator.element === J.Binary.Type.Or &&
|
|
148
|
+
readsOnly((e as J.Binary).left) && readsOnly((e as J.Binary).right);
|
|
149
|
+
case JS.Kind.Binary:
|
|
150
|
+
return (e as JS.Binary).operator.element === JS.Binary.Type.QuestionQuestion &&
|
|
151
|
+
readsOnly((e as JS.Binary).left) && readsOnly((e as JS.Binary).right);
|
|
152
|
+
default:
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** A JavaScript file, where nothing checked that the proxy is defined; TypeScript required hpagent's `proxy` to be. */
|
|
158
|
+
function isJavaScript(sourcePath: string): boolean {
|
|
159
|
+
return /\.[cm]?jsx?$/.test(sourcePath);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** `proxy ? construction : undefined`, built directly so the construction keeps its layout. */
|
|
163
|
+
function whenDefined(proxy: J, construction: J.NewClass): J.Ternary {
|
|
164
|
+
const padded = <T extends J>(element: T): J.LeftPadded<T> =>
|
|
165
|
+
({kind: J.Kind.LeftPadded, before: space(' '), element, markers: emptyMarkers});
|
|
166
|
+
const undefinedValue: J.Identifier = {
|
|
167
|
+
kind: J.Kind.Identifier, id: randomId(), prefix: space(' '), markers: emptyMarkers, annotations: [], simpleName: 'undefined'
|
|
168
|
+
};
|
|
169
|
+
return {
|
|
170
|
+
kind: J.Kind.Ternary,
|
|
171
|
+
id: randomId(),
|
|
172
|
+
prefix: construction.prefix,
|
|
173
|
+
markers: emptyMarkers,
|
|
174
|
+
condition: {...proxy, id: randomId(), prefix: space('')} as J,
|
|
175
|
+
truePart: padded({...construction, prefix: space(' ')} as J.NewClass),
|
|
176
|
+
falsePart: padded(undefinedValue),
|
|
177
|
+
} as J.Ternary;
|
|
178
|
+
}
|
|
179
|
+
|
|
110
180
|
/** The members of an object literal, still padded, with the placeholder of an empty literal dropped. */
|
|
111
181
|
function paddedMembers(literal: J.NewClass): J.RightPadded<J>[] {
|
|
112
182
|
return (literal.body?.statements ?? []).filter(padded => padded.element.kind !== J.Kind.Empty) as J.RightPadded<J>[];
|
|
@@ -120,7 +190,10 @@ export class ReplaceHpagent extends Recipe {
|
|
|
120
190
|
"option: `new HttpsProxyAgent({proxy: url, keepAlive: true})` becomes `new HttpsProxyAgent(url, {keepAlive: true})`. " +
|
|
121
191
|
"Both hpagent classes tunnel with `CONNECT`, and `https-proxy-agent` upgrades the tunnel to TLS only for an " +
|
|
122
192
|
"`https:` target, so one class serves both. `proxyRequestOptions` members move up into the options object, " +
|
|
123
|
-
"where `https-proxy-agent` applies them to the connection to the proxy.
|
|
193
|
+
"where `https-proxy-agent` applies them to the connection to the proxy. hpagent accepted a missing proxy until " +
|
|
194
|
+
"a request needed it, but `https-proxy-agent` reads it in the constructor, so in JavaScript a proxy that may be " +
|
|
195
|
+
"missing, such as `process.env.HTTP_PROXY`, guards the construction: `proxy ? new HttpsProxyAgent(proxy) : undefined`. " +
|
|
196
|
+
"Calls whose options aren't an object " +
|
|
124
197
|
"literal, or that set a TLS option for the destination handshake (`ca`, `rejectUnauthorized`, `cert`, ...), " +
|
|
125
198
|
"are left alone: `https-proxy-agent` would apply those to the proxy hop instead. " +
|
|
126
199
|
"`FindHpagentManualMigrations` reports every usage left behind.";
|
|
@@ -275,7 +348,13 @@ export class ReplaceHpagent extends Recipe {
|
|
|
275
348
|
(rewrite ? this.migrated : this.stranded).add(site.local);
|
|
276
349
|
return n;
|
|
277
350
|
}
|
|
278
|
-
|
|
351
|
+
if (!rewrite || this.stranded.has(site.local)) return n;
|
|
352
|
+
const migrated = this.applyRewrite(n, site, rewrite);
|
|
353
|
+
// Constructing with a missing proxy used to succeed and now throws, as when a module builds its
|
|
354
|
+
// agent from an environment variable a test run doesn't set, so the construction is skipped then.
|
|
355
|
+
return isJavaScript(compilationUnitOf(this)!.sourcePath) && !alwaysDefined(rewrite.proxy) && readsOnly(rewrite.proxy)
|
|
356
|
+
? whenDefined(rewrite.proxy, migrated)
|
|
357
|
+
: migrated;
|
|
279
358
|
}
|
|
280
359
|
|
|
281
360
|
/**
|