@openrewrite/recipes-nodejs 0.52.0-20261009-102010 → 0.52.1
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 +2 -0
- package/dist/index.js.map +1 -1
- package/dist/migrate/code-style.d.ts +14 -0
- package/dist/migrate/code-style.d.ts.map +1 -0
- package/dist/migrate/code-style.js +70 -0
- package/dist/migrate/code-style.js.map +1 -0
- package/dist/migrate/hpagent/find-hpagent-manual-migrations.d.ts.map +1 -1
- package/dist/migrate/hpagent/find-hpagent-manual-migrations.js +36 -133
- package/dist/migrate/hpagent/find-hpagent-manual-migrations.js.map +1 -1
- package/dist/migrate/hpagent/hpagent-support.d.ts +36 -15
- package/dist/migrate/hpagent/hpagent-support.d.ts.map +1 -1
- package/dist/migrate/hpagent/hpagent-support.js +294 -55
- package/dist/migrate/hpagent/hpagent-support.js.map +1 -1
- package/dist/migrate/hpagent/replace-hpagent.d.ts.map +1 -1
- package/dist/migrate/hpagent/replace-hpagent.js +510 -153
- package/dist/migrate/hpagent/replace-hpagent.js.map +1 -1
- package/dist/migrate/source-map-support/enable-source-maps-at-launch.d.ts.map +1 -1
- package/dist/migrate/source-map-support/enable-source-maps-at-launch.js +8 -16
- package/dist/migrate/source-map-support/enable-source-maps-at-launch.js.map +1 -1
- package/dist/migrate/source-map-support/find-source-map-support-manual-migrations.d.ts.map +1 -1
- package/dist/migrate/source-map-support/find-source-map-support-manual-migrations.js +26 -9
- package/dist/migrate/source-map-support/find-source-map-support-manual-migrations.js.map +1 -1
- 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 +7 -4
- package/dist/migrate/source-map-support/migrate-source-map-support-to-native.js.map +1 -1
- package/dist/migrate/source-map-support/replace-source-map-support-api.d.ts +9 -0
- package/dist/migrate/source-map-support/replace-source-map-support-api.d.ts.map +1 -0
- package/dist/migrate/source-map-support/replace-source-map-support-api.js +275 -0
- package/dist/migrate/source-map-support/replace-source-map-support-api.js.map +1 -0
- package/dist/migrate/source-map-support/replace-source-map-support-install.d.ts.map +1 -1
- package/dist/migrate/source-map-support/replace-source-map-support-install.js +2 -1
- package/dist/migrate/source-map-support/replace-source-map-support-install.js.map +1 -1
- package/dist/migrate/source-map-support/source-map-support-api-source.d.ts +14 -0
- package/dist/migrate/source-map-support/source-map-support-api-source.d.ts.map +1 -0
- package/dist/migrate/source-map-support/source-map-support-api-source.js +426 -0
- package/dist/migrate/source-map-support/source-map-support-api-source.js.map +1 -0
- 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/source-map-support/source-map-support-support.d.ts +11 -3
- package/dist/migrate/source-map-support/source-map-support-support.d.ts.map +1 -1
- package/dist/migrate/source-map-support/source-map-support-support.js +69 -10
- package/dist/migrate/source-map-support/source-map-support-support.js.map +1 -1
- package/dist/resources/advisories-npm.csv +342 -6
- package/package.json +2 -2
- package/src/index.ts +2 -0
- package/src/migrate/code-style.ts +92 -0
- package/src/migrate/fs-extra/replace-fs-extra-native-methods.ts +2 -2
- package/src/migrate/hpagent/find-hpagent-manual-migrations.ts +45 -142
- package/src/migrate/hpagent/hpagent-support.ts +358 -60
- package/src/migrate/hpagent/replace-hpagent.ts +580 -189
- package/src/migrate/source-map-support/enable-source-maps-at-launch.ts +17 -26
- package/src/migrate/source-map-support/find-source-map-support-manual-migrations.ts +25 -9
- package/src/migrate/source-map-support/migrate-source-map-support-to-native.ts +7 -4
- package/src/migrate/source-map-support/replace-source-map-support-api.ts +281 -0
- package/src/migrate/source-map-support/replace-source-map-support-install.ts +2 -1
- package/src/migrate/source-map-support/source-map-support-api-source.ts +470 -0
- package/src/migrate/source-map-support/source-map-support-rewrites.ts +3 -0
- package/src/migrate/source-map-support/source-map-support-support.ts +79 -10
|
@@ -4,16 +4,222 @@
|
|
|
4
4
|
* Moderne Proprietary. Only for use by Moderne customers under the terms of a commercial contract.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {Column, DataTable, emptyMarkers, randomId} from "@openrewrite/rewrite";
|
|
7
|
+
import {Column, Cursor, DataTable, emptyMarkers, randomId} from "@openrewrite/rewrite";
|
|
8
8
|
import {emptySpace, isIdentifier, J, rightPadded, space} from "@openrewrite/rewrite/java";
|
|
9
|
-
import {JS, walk} from "@openrewrite/rewrite/javascript";
|
|
10
|
-
import {
|
|
9
|
+
import {bindingNames, declarationsOf, JS, scopeOf, walk} from "@openrewrite/rewrite/javascript";
|
|
10
|
+
import {CodeStyle, DEFAULT_CODE_STYLE, indented} from "../code-style";
|
|
11
|
+
import {bindingsOf, importModule, ModuleBindings, requiredModule, stringLiteral} from "../module-bindings";
|
|
11
12
|
|
|
12
|
-
export {
|
|
13
|
+
export {sourcePathOf, stringLiteral} from "../module-bindings";
|
|
13
14
|
export type {ModuleBindings} from "../module-bindings";
|
|
14
15
|
|
|
15
16
|
export const HPAGENT_MODULES = new Set(['hpagent']);
|
|
16
17
|
|
|
18
|
+
/** `require('hpagent')`, also when cast as in `require('hpagent') as typeof import('hpagent')`. */
|
|
19
|
+
export function isHpagentRequire(e: J | undefined): boolean {
|
|
20
|
+
const call = e?.kind === JS.Kind.As ? (e as JS.As).left.element : e;
|
|
21
|
+
return HPAGENT_MODULES.has(requiredModule(call) ?? '');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* What a file's hpagent imports and requires bind, and the id of the scope each binds in: the
|
|
26
|
+
* compilation unit for a top-level binding, or the block a `const {...} = require('hpagent')` sits in,
|
|
27
|
+
* as when a CLI requires the proxy agent only once it knows a proxy is configured.
|
|
28
|
+
*/
|
|
29
|
+
export interface HpagentBindings {
|
|
30
|
+
bindings: ModuleBindings;
|
|
31
|
+
scopes: Map<string, string>;
|
|
32
|
+
/** The `require` calls these bindings come from, so a report can tell a shape it doesn't follow. */
|
|
33
|
+
requires: Set<string>;
|
|
34
|
+
/** Names a `require` binds, which the rewrite moves itself since `maybeRebind` moves only imports. */
|
|
35
|
+
requireBound: Set<string>;
|
|
36
|
+
/** Names a default import binds, which has to become a namespace import: `https-proxy-agent` has no default export. */
|
|
37
|
+
defaults: Set<string>;
|
|
38
|
+
/** Names bound by `({...} = require('hpagent'))` into a `let` declared without a value, which keep their names. */
|
|
39
|
+
assigned: Set<string>;
|
|
40
|
+
/** The identifiers such an assignment writes, which are no use of the class as a value. */
|
|
41
|
+
assignmentTargets: Set<string>;
|
|
42
|
+
/** Names a type-only import binds, which can only ever name a type. */
|
|
43
|
+
typeOnly: Set<string>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The local and the export each member of `({X, Y: local} = ...)` binds, with the identifiers it writes. */
|
|
47
|
+
function assignmentPattern(pattern: J): {name: string; member: string; ids: string[]}[] | undefined {
|
|
48
|
+
if (!isObjectLiteral(pattern)) return undefined;
|
|
49
|
+
const bound = [];
|
|
50
|
+
for (const member of objectMembers(pattern)) {
|
|
51
|
+
if (member.kind !== JS.Kind.PropertyAssignment) return undefined;
|
|
52
|
+
const prop = member as JS.PropertyAssignment;
|
|
53
|
+
const property = propertyName(prop);
|
|
54
|
+
const local = prop.initializer ?? prop.name.element;
|
|
55
|
+
if (property === undefined || !isIdentifier(local)) return undefined;
|
|
56
|
+
bound.push({name: local.simpleName, member: property, ids: [prop.name.element.id, local.id]});
|
|
57
|
+
}
|
|
58
|
+
return bound;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function emptyHpagentBindings(): HpagentBindings {
|
|
62
|
+
return {
|
|
63
|
+
bindings: new Map(), scopes: new Map(), requires: new Set(), requireBound: new Set(), defaults: new Set(),
|
|
64
|
+
assigned: new Set(), assignmentTargets: new Set(), typeOnly: new Set()
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function hpagentBindings(cu: JS.CompilationUnit): HpagentBindings {
|
|
69
|
+
const bindings = bindingsOf(cu, HPAGENT_MODULES);
|
|
70
|
+
const defaults = new Set<string>();
|
|
71
|
+
const typeOnly = new Set<string>();
|
|
72
|
+
for (const padded of cu.statements) {
|
|
73
|
+
const imp = padded.element as JS.Import;
|
|
74
|
+
if (imp?.kind !== JS.Kind.Import || !HPAGENT_MODULES.has(importModule(imp) ?? '') || !imp.importClause) continue;
|
|
75
|
+
const clause = imp.importClause;
|
|
76
|
+
const name = clause.name?.element;
|
|
77
|
+
if (name && !clause.typeOnly) defaults.add(name.simpleName);
|
|
78
|
+
// Type-only imports name nothing at runtime, but their names still have to move off hpagent.
|
|
79
|
+
if (clause.typeOnly && clause.namedBindings?.kind === JS.Kind.Alias) {
|
|
80
|
+
const alias = (clause.namedBindings as JS.Alias).alias;
|
|
81
|
+
if (isIdentifier(alias)) {
|
|
82
|
+
bindings.set(alias.simpleName, undefined);
|
|
83
|
+
typeOnly.add(alias.simpleName);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (clause.namedBindings?.kind !== JS.Kind.NamedImports) continue;
|
|
87
|
+
for (const el of (clause.namedBindings as JS.NamedImports).elements.elements) {
|
|
88
|
+
const spec = el.element as JS.ImportSpecifier;
|
|
89
|
+
if (spec.kind !== JS.Kind.ImportSpecifier || !(clause.typeOnly || spec.importType?.element)) continue;
|
|
90
|
+
if (isIdentifier(spec.specifier)) {
|
|
91
|
+
bindings.set(spec.specifier.simpleName, spec.specifier.simpleName);
|
|
92
|
+
typeOnly.add(spec.specifier.simpleName);
|
|
93
|
+
} else if (spec.specifier.kind === JS.Kind.Alias && isIdentifier((spec.specifier as JS.Alias).alias)) {
|
|
94
|
+
const alias = spec.specifier as JS.Alias;
|
|
95
|
+
bindings.set((alias.alias as J.Identifier).simpleName, alias.propertyName.element.simpleName);
|
|
96
|
+
typeOnly.add((alias.alias as J.Identifier).simpleName);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const scopes = new Map([...bindings.keys()].map(local => [local, cu.id]));
|
|
101
|
+
const requires = new Set<string>();
|
|
102
|
+
const requireBound = new Set<string>();
|
|
103
|
+
const assigned = new Set<string>();
|
|
104
|
+
const assignmentTargets = new Set<string>();
|
|
105
|
+
// Each name a `let` or `var` declares without a value, with the scopes declaring it so.
|
|
106
|
+
const unassigned = new Map<string, string[]>();
|
|
107
|
+
const assignments: J.Assignment[] = [];
|
|
108
|
+
const declare = (statement: J, scope: J) => {
|
|
109
|
+
for (const decl of declarationsOf(statement)) {
|
|
110
|
+
for (const v of decl.variables) {
|
|
111
|
+
const initializer = v.element.initializer?.element;
|
|
112
|
+
const declared = v.element.name as J;
|
|
113
|
+
if (!initializer && isIdentifier(declared)) {
|
|
114
|
+
unassigned.set(declared.simpleName, [...(unassigned.get(declared.simpleName) ?? []), scope.id]);
|
|
115
|
+
}
|
|
116
|
+
if (!isHpagentRequire(initializer)) continue;
|
|
117
|
+
requires.add((initializer!.kind === JS.Kind.As ? (initializer as JS.As).left.element : initializer)!.id);
|
|
118
|
+
const pattern = v.element.name as J;
|
|
119
|
+
const names = isIdentifier(pattern) ? [{name: pattern.simpleName, member: undefined}] : bindingNames(pattern);
|
|
120
|
+
for (const {name, member} of names) {
|
|
121
|
+
// A name bound twice is left to the report rather than guessed between.
|
|
122
|
+
if (scope !== cu && scopes.has(name)) continue;
|
|
123
|
+
bindings.set(name, member);
|
|
124
|
+
scopes.set(name, scope.id);
|
|
125
|
+
requireBound.add(name);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
for (const padded of cu.statements) declare(padded.element, cu);
|
|
131
|
+
walk(cu, (n: any) => {
|
|
132
|
+
if (n?.kind === J.Kind.Block) {
|
|
133
|
+
for (const padded of (n as J.Block).statements) declare(padded.element, n);
|
|
134
|
+
} else if (n?.kind === J.Kind.Assignment && isHpagentRequire((n as J.Assignment).assignment.element)) {
|
|
135
|
+
assignments.push(n);
|
|
136
|
+
}
|
|
137
|
+
return true;
|
|
138
|
+
});
|
|
139
|
+
// `let HttpsProxyAgent; ... ({HttpsProxyAgent} = require('hpagent'))`, as code does that requires hpagent
|
|
140
|
+
// only once a proxy is configured. The binding lives where the `let` is, if one declaration settles it.
|
|
141
|
+
for (const assignment of assignments) {
|
|
142
|
+
const pattern = assignmentPattern(assignment.variable);
|
|
143
|
+
if (!pattern || pattern.some(({name}) => unassigned.get(name)?.length !== 1 || bindings.has(name))) continue;
|
|
144
|
+
const call = assignment.assignment.element;
|
|
145
|
+
requires.add((call.kind === JS.Kind.As ? (call as JS.As).left.element : call).id);
|
|
146
|
+
for (const {name, member, ids} of pattern) {
|
|
147
|
+
bindings.set(name, member);
|
|
148
|
+
scopes.set(name, unassigned.get(name)![0]);
|
|
149
|
+
requireBound.add(name);
|
|
150
|
+
assigned.add(name);
|
|
151
|
+
ids.forEach(id => assignmentTargets.add(id));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return {bindings, scopes, requires, requireBound, defaults, assigned, assignmentTargets, typeOnly};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Whether `name` at `cursor` resolves to the hpagent binding rather than a nearer declaration. */
|
|
158
|
+
export function resolvesToBinding(cursor: Cursor, name: string, found: HpagentBindings): boolean {
|
|
159
|
+
const scope = found.scopes.get(name);
|
|
160
|
+
return scope !== undefined && scopeOf(cursor).declaringScope(name)?.id === scope;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Trees that hold an identifier as a declaration or import rather than a use of it. */
|
|
164
|
+
const DECLARING_KINDS = new Set<string>([
|
|
165
|
+
JS.Kind.Import, JS.Kind.ImportClause, JS.Kind.ImportSpecifier, JS.Kind.Alias, JS.Kind.NamedImports,
|
|
166
|
+
JS.Kind.ExportSpecifier, JS.Kind.BindingElement, JS.Kind.ObjectBindingPattern,
|
|
167
|
+
]);
|
|
168
|
+
|
|
169
|
+
export function inTypePosition(cursor: Cursor): boolean {
|
|
170
|
+
// The type an `as` casts to, however deep, as `ConstructorParameters<typeof HttpProxyAgent>[0]` is.
|
|
171
|
+
for (let at: Cursor | undefined = cursor; at; at = at.parentTree()) {
|
|
172
|
+
const parent = at.parentTree()?.value as J | undefined;
|
|
173
|
+
if (parent?.kind === JS.Kind.As && (parent as JS.As).right === at.value) return true;
|
|
174
|
+
}
|
|
175
|
+
return !!cursor.firstEnclosing((t: any): t is J =>
|
|
176
|
+
t?.kind === JS.Kind.TypeInfo || t?.kind === JS.Kind.TypeQuery || t?.kind === JS.Kind.TypeDeclaration);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Whether the identifier or member access at `cursor` stands for the class it names as a value: not
|
|
181
|
+
* constructed, not tested with `instanceof`, not a type, not declared or imported, and not the
|
|
182
|
+
* module a member is read from.
|
|
183
|
+
*/
|
|
184
|
+
export function isValueUse(cursor: Cursor): boolean {
|
|
185
|
+
const tree = cursor.value as J;
|
|
186
|
+
const parent = cursor.parentTree()?.value as J | undefined;
|
|
187
|
+
if (!parent || DECLARING_KINDS.has(parent.kind) || inTypePosition(cursor)) return false;
|
|
188
|
+
switch (parent.kind) {
|
|
189
|
+
case J.Kind.NewClass:
|
|
190
|
+
return (parent as J.NewClass).class !== tree;
|
|
191
|
+
case J.Kind.InstanceOf:
|
|
192
|
+
return (parent as J.InstanceOf).class !== tree;
|
|
193
|
+
case J.Kind.FieldAccess:
|
|
194
|
+
return (parent as J.FieldAccess).target !== tree;
|
|
195
|
+
case J.Kind.MethodInvocation: {
|
|
196
|
+
const m = parent as J.MethodInvocation;
|
|
197
|
+
return m.select?.element !== tree && m.name !== tree;
|
|
198
|
+
}
|
|
199
|
+
case J.Kind.NamedVariable:
|
|
200
|
+
return (parent as J.VariableDeclarations.NamedVariable).name !== tree;
|
|
201
|
+
default:
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const MOCK_OWNERS = new Set(['vi', 'jest']);
|
|
207
|
+
const MOCK_METHODS = new Set(['mock', 'doMock', 'unmock', 'unstable_mockModule']);
|
|
208
|
+
|
|
209
|
+
/** `jest.mock(...)`, `vi.mock(...)` and their kin, which name the module they replace. */
|
|
210
|
+
export function isMockCall(m: J.MethodInvocation): boolean {
|
|
211
|
+
const select = m.select?.element;
|
|
212
|
+
return !!select && isIdentifier(select) && MOCK_OWNERS.has(select.simpleName) && MOCK_METHODS.has(m.name.simpleName);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** `jest.spyOn(hpagent, 'HttpsProxyAgent')`, which reads the class off the module by name; the class it spies on. */
|
|
216
|
+
export function spiedClass(m: J.MethodInvocation): string | undefined {
|
|
217
|
+
const select = m.select?.element;
|
|
218
|
+
if (!select || !isIdentifier(select) || !MOCK_OWNERS.has(select.simpleName) || m.name.simpleName !== 'spyOn') return undefined;
|
|
219
|
+
const member = stringLiteral(m.arguments.elements[1]?.element);
|
|
220
|
+
return member !== undefined && HPAGENT_CLASSES.has(member) ? member : undefined;
|
|
221
|
+
}
|
|
222
|
+
|
|
17
223
|
/** Both hpagent classes tunnel with `CONNECT`, which is what `https-proxy-agent` does for either target scheme. */
|
|
18
224
|
export const HPAGENT_CLASSES = new Set(['HttpProxyAgent', 'HttpsProxyAgent']);
|
|
19
225
|
|
|
@@ -50,44 +256,28 @@ const DESTINATION_TLS_OPTIONS = new Set([
|
|
|
50
256
|
/** The `proxyRequestOptions` members, which configure the `CONNECT` request and so the proxy hop. */
|
|
51
257
|
const PROXY_REQUEST_OPTIONS = new Set(['ca', 'headers', 'rejectUnauthorized']);
|
|
52
258
|
|
|
259
|
+
/** hpagent's option interfaces, which the adapter's own options type stands in for. */
|
|
260
|
+
export const ADAPTED_TYPES = new Set(['HttpProxyAgentOptions', 'HttpsProxyAgentOptions']);
|
|
261
|
+
|
|
262
|
+
export const ADAPTER_FUNCTION = 'createProxyAgent';
|
|
263
|
+
export const ADAPTER_TYPE = 'ProxyAgentOptions';
|
|
264
|
+
/** What hpagent called `ProxyAgentRequestOptions`, declared beside the adapter's type where a file names it. */
|
|
265
|
+
export const REQUEST_TYPE = 'ProxyRequestOptions';
|
|
266
|
+
|
|
53
267
|
export const REASONS = {
|
|
54
|
-
|
|
55
|
-
"
|
|
56
|
-
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"either migrate by hand or re-run with `migrateSpreadOptions` enabled.",
|
|
60
|
-
spreadNoProxy: "The agent options are spread from another object and no `proxy` is given here, so the proxy URL " +
|
|
61
|
-
"can only come from the spread and can't be read out statically. Pass it as `https-proxy-agent`'s first " +
|
|
62
|
-
"argument explicitly.",
|
|
63
|
-
notLiteral: "The agent options aren't an object literal, so `proxy` can't be separated from the rest. " +
|
|
64
|
-
"Call `new HttpsProxyAgent(options.proxy, options)` explicitly, folding any `proxyRequestOptions` " +
|
|
65
|
-
"members into the second argument.",
|
|
66
|
-
destinationTls: (option: string) => `\`${option}\` configured the TLS handshake with the destination, since ` +
|
|
67
|
-
"hpagent's `HttpsProxyAgent` extends `https.Agent`. `https-proxy-agent` spends its constructor options on " +
|
|
68
|
-
"the connection to the proxy and takes the destination's TLS options from each request, so pass this " +
|
|
69
|
-
"option to `https.request`/`fetch` instead, or to the client library making the request.",
|
|
70
|
-
unknownOption: (option: string) => `\`${option}\` isn't a recognized hpagent option, so it isn't known which ` +
|
|
71
|
-
"hop it configures. Check it against `https-proxy-agent`'s options and move it by hand.",
|
|
72
|
-
computedOption: "An agent option has a computed name, so it can't be classified. Name the option literally, " +
|
|
73
|
-
"or build the two argument lists by hand.",
|
|
74
|
-
nameTaken: "Both `HttpProxyAgent` and `HttpsProxyAgent` are bound here and both migrate to " +
|
|
75
|
-
"`https-proxy-agent`'s `HttpsProxyAgent`, which would collide. Migrate the two bindings by hand; both " +
|
|
76
|
-
"tunnel with `CONNECT`, so one agent serves http and https targets alike.",
|
|
77
|
-
value: "hpagent's agent class is used as a value rather than instantiated, so its constructor call can't be " +
|
|
78
|
-
"rewritten here. `https-proxy-agent` takes the proxy URL as its first argument, not as an option.",
|
|
79
|
-
type: "hpagent's option types don't match `https-proxy-agent`'s, whose options are generic in the proxy URL's " +
|
|
80
|
-
"scheme. Use `HttpsProxyAgentOptions<string>` from `https-proxy-agent` or declare a local type.",
|
|
268
|
+
arguments: "hpagent's constructor takes one options object, and this call doesn't pass exactly one, so it " +
|
|
269
|
+
"can't be handed to the adapter. Pass the options as a single object.",
|
|
270
|
+
value: "hpagent's agent class is used as a value rather than instantiated, as in `const Agent = HttpProxyAgent`, " +
|
|
271
|
+
"so whatever constructs it later still passes hpagent's options object. `https-proxy-agent` takes the " +
|
|
272
|
+
"proxy URL as its first argument; construct it directly, or through the adapter this migration writes.",
|
|
81
273
|
reExport: "Re-exporting hpagent makes it part of this module's API. Re-export from `https-proxy-agent`, whose " +
|
|
82
274
|
"constructor takes the proxy URL positionally.",
|
|
83
275
|
dynamicImport: "Dynamic import of hpagent, which the automated migration doesn't follow. Import " +
|
|
84
276
|
"`https-proxy-agent` instead and migrate its uses.",
|
|
85
|
-
nestedRequire: "hpagent required
|
|
86
|
-
"require
|
|
277
|
+
nestedRequire: "hpagent required in a shape the automated migration doesn't follow; it moves a block's " +
|
|
278
|
+
"`const {...} = require('hpagent')` but not this. Bind it that way or migrate its uses by hand.",
|
|
87
279
|
mock: "Test mocks hpagent. Once the code under test constructs `https-proxy-agent`'s `HttpsProxyAgent`, mock " +
|
|
88
280
|
"that module instead; its constructor takes the proxy URL as its first argument.",
|
|
89
|
-
defaultImport: "hpagent has no default export, and `https-proxy-agent` none either. Import `HttpsProxyAgent` " +
|
|
90
|
-
"as a named binding.",
|
|
91
281
|
};
|
|
92
282
|
|
|
93
283
|
/** What one member of an hpagent options object literal is, and so where it goes. */
|
|
@@ -96,7 +286,7 @@ export type OptionKind =
|
|
|
96
286
|
| {kind: 'proxyRequestOptions'; value: J}
|
|
97
287
|
| {kind: 'agent'; member: JS.PropertyAssignment}
|
|
98
288
|
| {kind: 'spread'; value: J | undefined}
|
|
99
|
-
| {kind: 'unmappable'
|
|
289
|
+
| {kind: 'unmappable'};
|
|
100
290
|
|
|
101
291
|
export function propertyName(prop: JS.PropertyAssignment): string | undefined {
|
|
102
292
|
const name = prop.name.element;
|
|
@@ -115,22 +305,120 @@ export function objectMembers(literal: J.NewClass): J[] {
|
|
|
115
305
|
/** Classifies one member of an hpagent options object literal. */
|
|
116
306
|
export function classifyOption(statement: J): OptionKind {
|
|
117
307
|
if (statement.kind === JS.Kind.Spread) return {kind: 'spread', value: (statement as JS.Spread).expression};
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
return {kind: 'unmappable', option: '(not a property)', reason: REASONS.notLiteral};
|
|
121
|
-
}
|
|
308
|
+
// A method or a getter is no name/value pair this can read.
|
|
309
|
+
if (statement.kind !== JS.Kind.PropertyAssignment) return {kind: 'unmappable'};
|
|
122
310
|
const prop = statement as JS.PropertyAssignment;
|
|
123
311
|
const name = propertyName(prop);
|
|
124
|
-
if (name === undefined) return {kind: 'unmappable'
|
|
312
|
+
if (name === undefined) return {kind: 'unmappable'};
|
|
125
313
|
// A shorthand `{proxy}` carries the value in its name, which the caller reads back from the property.
|
|
126
314
|
const value = prop.initializer ?? prop.name.element;
|
|
127
315
|
if (name === 'proxy') return {kind: 'proxy', value};
|
|
128
316
|
if (name === 'proxyRequestOptions') return {kind: 'proxyRequestOptions', value};
|
|
129
|
-
|
|
130
|
-
|
|
317
|
+
// A destination TLS option would land on the proxy hop in the two-argument form, so it takes the adapter.
|
|
318
|
+
if (DESTINATION_TLS_OPTIONS.has(name)) return {kind: 'unmappable'};
|
|
319
|
+
return AGENT_OPTIONS.has(name) ? {kind: 'agent', member: prop} : {kind: 'unmappable'};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/** The one options argument a construction passes, which is what the adapter takes, or undefined. */
|
|
323
|
+
export function constructionArgument(n: J.NewClass): J | undefined {
|
|
324
|
+
const args = n.arguments.elements.map(a => a.element).filter(a => a.kind !== J.Kind.Empty);
|
|
325
|
+
return args.length === 1 && args[0].kind !== JS.Kind.Spread ? args[0] : undefined;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/** Source for a declaration the migration writes, with the text of the doc comment written above it. */
|
|
329
|
+
export interface GeneratedDeclaration {
|
|
330
|
+
doc: string;
|
|
331
|
+
code: string;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/** The pool options hpagent gave `http.Agent`; it sent no other agent option to the proxy. */
|
|
335
|
+
const POOL_OPTIONS = ['keepAlive', 'keepAliveMsecs', 'maxSockets', 'maxTotalSockets', 'maxFreeSockets', 'scheduling', 'timeout'];
|
|
336
|
+
|
|
337
|
+
/** How a braced list separates its members: on one line, and on lines of their own. */
|
|
338
|
+
interface Separators {
|
|
339
|
+
inline: string;
|
|
340
|
+
each: string;
|
|
341
|
+
/** After the last member on a line of its own. */
|
|
342
|
+
last: string;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* `head{members}tail` on one line at `level`, or with a member to a line where that runs past the file's
|
|
347
|
+
* print width. In a `pattern`, a rest element takes no trailing comma, which the language forbids there.
|
|
348
|
+
*/
|
|
349
|
+
function braced(style: CodeStyle, level: number, head: string, members: string[], tail: string, pattern = false,
|
|
350
|
+
separators: Separators = {inline: ', ', each: ',', last: style.trailingCommas ? ',' : ''}): string {
|
|
351
|
+
const margin = ' '.repeat(level);
|
|
352
|
+
const inner = members.join(separators.inline);
|
|
353
|
+
const line = `${margin}${head}${style.braceSpacing ? `{ ${inner} }` : `{${inner}}`}${tail}`;
|
|
354
|
+
const indentWidth = style.indent === '\t' ? 4 : style.indent.length;
|
|
355
|
+
if (line.length - margin.length + indentWidth * level <= style.printWidth) return line;
|
|
356
|
+
const lines = members.map((member, i) => `${margin} ${member}${i < members.length - 1
|
|
357
|
+
? separators.each
|
|
358
|
+
: pattern && member.startsWith('...') ? '' : separators.last}`);
|
|
359
|
+
return [`${margin}${head}{`, ...lines, `${margin}}${tail}`].join('\n');
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* The adapter a file gets when one of its constructions can't be rewritten to the two-argument form.
|
|
364
|
+
* `agentClass` is how the file now names `https-proxy-agent`'s class.
|
|
365
|
+
*/
|
|
366
|
+
export function adapterSource(name: string, typeName: string, agentClass: string, typeScript: boolean,
|
|
367
|
+
style: CodeStyle = DEFAULT_CODE_STYLE): GeneratedDeclaration {
|
|
368
|
+
const end = style.semicolons ? ';' : '';
|
|
369
|
+
const head = `function ${name}${style.functionParenSpace ? ' ' : ''}(`;
|
|
370
|
+
const parameters = ['proxy', 'proxyRequestOptions', '...options'];
|
|
371
|
+
return {
|
|
372
|
+
doc: `*
|
|
373
|
+
* hpagent's options-object constructor on \`https-proxy-agent\`, whose constructor options configure the
|
|
374
|
+
* connection to the proxy. hpagent sent the proxy only \`proxyRequestOptions\`, and handed every other
|
|
375
|
+
* option to \`https.Agent\`, which merges an agent's options into each request and so into the
|
|
376
|
+
* destination's TLS handshake. \`agent.options\` is that same per-request object here.
|
|
377
|
+
`,
|
|
378
|
+
code: indented(style, [
|
|
379
|
+
braced(style, 0, head, parameters, typeScript ? `: ${typeName}): ${agentClass}<string> {` : ') {', true),
|
|
380
|
+
braced(style, 1, 'const ', POOL_OPTIONS, ` = options${end}`, true),
|
|
381
|
+
braced(style, 1, `const agent = new ${agentClass}(proxy, `, [...POOL_OPTIONS, '...proxyRequestOptions'], `)${end}`),
|
|
382
|
+
` Object.assign(agent.options || {}, options)${end}`,
|
|
383
|
+
` return agent${end}`,
|
|
384
|
+
'}',
|
|
385
|
+
].join('\n'))
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* The options hpagent's agents took, which the adapter takes in their place, and hpagent's
|
|
391
|
+
* `ProxyAgentRequestOptions` as `requestTypeName` where the file names that. `specifier` spells a builtin the file's way.
|
|
392
|
+
*/
|
|
393
|
+
export function adapterTypeSource(typeName: string | undefined, requestTypeName: string | undefined,
|
|
394
|
+
specifier: (module: string) => string,
|
|
395
|
+
style: CodeStyle = DEFAULT_CODE_STYLE): GeneratedDeclaration[] {
|
|
396
|
+
const module = (name: string) => `import(${style.quote}${specifier(name)}${style.quote})`;
|
|
397
|
+
const end = style.semicolons ? ';' : '';
|
|
398
|
+
const request = ['ca?: string[]', `headers?: ${module('http')}.OutgoingHttpHeaders`, 'rejectUnauthorized?: boolean'];
|
|
399
|
+
// A type literal written on one line keeps its delimiters either way; one written a member to a line needs them only with semicolons.
|
|
400
|
+
const separators = {inline: style.semicolons ? '; ' : ', ', each: end, last: end};
|
|
401
|
+
const declarations: GeneratedDeclaration[] = [];
|
|
402
|
+
if (typeName) {
|
|
403
|
+
declarations.push({
|
|
404
|
+
doc: "* The options hpagent's agents took: an `https.Agent`'s, plus the proxy to tunnel through. ",
|
|
405
|
+
code: indented(style, [
|
|
406
|
+
`type ${typeName} = ${module('https')}.AgentOptions & {`,
|
|
407
|
+
` proxy: string | ${module('url')}.URL${end}`,
|
|
408
|
+
requestTypeName
|
|
409
|
+
? ` proxyRequestOptions?: ${requestTypeName}${end}`
|
|
410
|
+
: braced(style, 1, 'proxyRequestOptions?: ', request, end, false, separators),
|
|
411
|
+
`}${end}`,
|
|
412
|
+
].join('\n'))
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
if (requestTypeName) {
|
|
416
|
+
declarations.push({
|
|
417
|
+
doc: "* What hpagent sent the proxy with its `CONNECT` request, its `ProxyAgentRequestOptions`. ",
|
|
418
|
+
code: indented(style, braced(style, 0, `type ${requestTypeName} = `, request, end, false, separators))
|
|
419
|
+
});
|
|
131
420
|
}
|
|
132
|
-
|
|
133
|
-
return {kind: 'unmappable', option: name, reason: REASONS.unknownOption(name)};
|
|
421
|
+
return declarations;
|
|
134
422
|
}
|
|
135
423
|
|
|
136
424
|
/** Whether every member of a `proxyRequestOptions` literal is one hpagent forwards to the `CONNECT` request. */
|
|
@@ -187,24 +475,34 @@ export function spreadIsPlainAgentOptions(cu: JS.CompilationUnit, spread: J | un
|
|
|
187
475
|
return literal !== undefined && objectMembers(literal).every(m => classifyOption(m).kind === 'agent');
|
|
188
476
|
}
|
|
189
477
|
|
|
478
|
+
/** The builtin module whose agent an hpagent class extends. */
|
|
479
|
+
export function builtinAgentModule(member: string): 'http' | 'https' {
|
|
480
|
+
return member === 'HttpProxyAgent' ? 'http' : 'https';
|
|
481
|
+
}
|
|
482
|
+
|
|
190
483
|
/**
|
|
191
|
-
*
|
|
192
|
-
*
|
|
484
|
+
* The builtin agent an hpagent class extends, which is its type exactly: hpagent declares nothing beyond
|
|
485
|
+
* it. `https-proxy-agent`'s class extends `http.Agent` too, and is assignable to `https.Agent`, so anything
|
|
486
|
+
* the type accepted it still does. Through `local` where the file binds the builtin, else `import('…')`.
|
|
193
487
|
*/
|
|
194
|
-
export function
|
|
195
|
-
const name: J.
|
|
196
|
-
kind: J.Kind.Identifier, id: randomId(), prefix:
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
488
|
+
export function builtinAgentType(local: string | undefined, specifier: string, prefix: J.Space): J {
|
|
489
|
+
const identifier = (name: string, at: J.Space = emptySpace): J.Identifier => ({
|
|
490
|
+
kind: J.Kind.Identifier, id: randomId(), prefix: at, markers: emptyMarkers, annotations: [], simpleName: name
|
|
491
|
+
} as unknown as J.Identifier);
|
|
492
|
+
const agent = {kind: J.Kind.LeftPadded, before: emptySpace, element: identifier('Agent'), markers: emptyMarkers};
|
|
493
|
+
if (local) {
|
|
494
|
+
return {kind: J.Kind.FieldAccess, id: randomId(), prefix, markers: emptyMarkers, target: identifier(local), name: agent} as unknown as J;
|
|
495
|
+
}
|
|
496
|
+
const module = {
|
|
497
|
+
kind: JS.Kind.LiteralType, id: randomId(), prefix: emptySpace, markers: emptyMarkers,
|
|
498
|
+
literal: {kind: J.Kind.Literal, id: randomId(), prefix: emptySpace, markers: emptyMarkers, value: specifier, valueSource: `'${specifier}'`}
|
|
499
|
+
};
|
|
200
500
|
return {
|
|
201
|
-
kind:
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
207
|
-
} as unknown as J.ParameterizedType;
|
|
501
|
+
kind: JS.Kind.ImportType, id: randomId(), prefix, markers: emptyMarkers,
|
|
502
|
+
hasTypeof: rightPadded(false, emptySpace),
|
|
503
|
+
argumentAndAttributes: {kind: J.Kind.Container, before: emptySpace, elements: [rightPadded(module, emptySpace)], markers: emptyMarkers},
|
|
504
|
+
qualifier: agent
|
|
505
|
+
} as unknown as J;
|
|
208
506
|
}
|
|
209
507
|
|
|
210
508
|
export class HpagentManualMigrationStep {
|