Skip to content

Commit c556127

Browse files
committed
Merge branch 'feat/chalk-styletext-migration' of github.com:richiemccoll/userland-migrations into feat/chalk-styletext-migration
2 parents 4681188 + 0a88554 commit c556127

34 files changed

+770
-186
lines changed

package-lock.json

Lines changed: 189 additions & 186 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# `crypto.createCredentials` DEP0010
2+
3+
This recipe transforms `crypto.createCredentials` usage to use modern `node:tls` methods.
4+
5+
See [DEP0010](https://nodejs.org/api/deprecations.html#DEP0010).
6+
7+
## Examples
8+
9+
**Before:**
10+
```js
11+
// Using the deprecated createCredentials from node:crypto
12+
const { createCredentials } = require('node:crypto');
13+
// OR
14+
import { createCredentials } from 'node:crypto';
15+
16+
const credentials = createCredentials({
17+
key: privateKey,
18+
cert: certificate,
19+
ca: [caCertificate]
20+
});
21+
```
22+
23+
**After:**
24+
```js
25+
// Updated to use createSecureContext from node:tls
26+
const { createSecureContext } = require('node:tls');
27+
// OR
28+
import { createSecureContext } from 'node:tls';
29+
30+
const credentials = createSecureContext({
31+
key: privateKey,
32+
cert: certificate,
33+
ca: [caCertificate]
34+
});
35+
```
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
schema_version: "1.0"
2+
name: "@nodejs/createCredentials-to-createSecureContext"
3+
version: 1.0.0
4+
description: Handle DEP0010 via transforming `crypto.createCredentials` to `tls.createSecureContext`
5+
author: 0hmx
6+
license: MIT
7+
workflow: workflow.yaml
8+
category: migration
9+
10+
targets:
11+
languages:
12+
- javascript
13+
- typescript
14+
15+
keywords:
16+
- transformation
17+
- migration
18+
19+
registry:
20+
access: public
21+
visibility: public
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"name": "@nodejs/createcredentials-to-createsecurecontext",
3+
"version": "1.0.0",
4+
"description": "Handle DEP0010 via transforming `crypto.createCredentials` to `tls.createSecureContext` with the appropriate options.",
5+
"type": "module",
6+
"scripts": {
7+
"test": "npx codemod jssg test -l typescript ./src/workflow.ts ./"
8+
},
9+
"repository": {
10+
"type": "git",
11+
"url": "git+https://github.com/nodejs/userland-migrations.git",
12+
"directory": "recipes/crypto-create-credentials",
13+
"bugs": "https://github.com/nodejs/userland-migrations/issues"
14+
},
15+
"author": "0hmx",
16+
"license": "MIT",
17+
"homepage": "https://github.com/nodejs/userland-migrations/blob/main/recipes/crypto-create-credentials/README.md",
18+
"devDependencies": {
19+
"@codemod.com/jssg-types": "^1.0.9"
20+
},
21+
"dependencies": {
22+
"@nodejs/codemod-utils": "*"
23+
}
24+
}
Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
import { EOL } from 'node:os';
2+
import { getNodeRequireCalls } from '@nodejs/codemod-utils/ast-grep/require-call';
3+
import {
4+
getNodeImportCalls,
5+
getNodeImportStatements,
6+
} from '@nodejs/codemod-utils/ast-grep/import-statement';
7+
import { resolveBindingPath } from '@nodejs/codemod-utils/ast-grep/resolve-binding-path';
8+
import type { SgRoot, Edit, SgNode } from '@codemod.com/jssg-types/main';
9+
import type Js from '@codemod.com/jssg-types/langs/javascript';
10+
11+
type SourceHandler = (statement: SgNode<Js>, rootNode: SgRoot<Js>) => Edit[];
12+
13+
type SourceTuple = [SgNode<Js>[], SourceHandler];
14+
15+
const newImportFunction = 'createSecureContext';
16+
const newImportModule = 'node:tls';
17+
const oldFunctionName = 'createCredentials';
18+
const oldImportModule = 'node:crypto';
19+
const newNamespace = 'tls';
20+
21+
function replaceUsagesForResolvedPath(
22+
rootNode: SgRoot<Js>,
23+
resolvedPath: string,
24+
): Edit[] {
25+
// Namespace/member usage e.g., crypto.createCredentials or ns.createCredentials
26+
if (resolvedPath.includes('.')) {
27+
const [object, property] = resolvedPath.split('.');
28+
const usages = rootNode.root().findAll({
29+
rule: {
30+
kind: 'call_expression',
31+
has: {
32+
field: 'function',
33+
kind: 'member_expression',
34+
all: [
35+
{ has: { field: 'object', regex: `^${object}$` } },
36+
{ has: { field: 'property', regex: `^${property}$` } },
37+
],
38+
},
39+
},
40+
});
41+
42+
return usages
43+
.map((u) => u.field('function'))
44+
.filter((f): f is SgNode<Js> => Boolean(f))
45+
.map((f) => f.replace(`${newNamespace}.${newImportFunction}`));
46+
}
47+
48+
// Destructured identifier usage
49+
// If alias was used, resolvedPath will not equal oldFunctionName; in that case we do not touch call sites
50+
if (resolvedPath === oldFunctionName) {
51+
const usages = rootNode.root().findAll({
52+
rule: {
53+
kind: 'call_expression',
54+
has: {
55+
field: 'function',
56+
kind: 'identifier',
57+
regex: `^${oldFunctionName}$`,
58+
},
59+
},
60+
});
61+
62+
return usages
63+
.map((usage) => usage.field('function'))
64+
.filter((id): id is SgNode<Js> => Boolean(id))
65+
.map((id) => id.replace(newImportFunction));
66+
}
67+
68+
// Aliased destructured identifier => keep usages intact
69+
return [];
70+
}
71+
72+
function handleRequire(statement: SgNode<Js>, rootNode: SgRoot<Js>): Edit[] {
73+
const idNode = statement.child(0);
74+
const declaration = statement.parent();
75+
76+
if (!idNode || !declaration) return [];
77+
78+
const resolved = resolveBindingPath(statement, `$.${oldFunctionName}`);
79+
if (!resolved) return [];
80+
81+
const usageEdits = replaceUsagesForResolvedPath(rootNode, resolved);
82+
83+
if (idNode.kind() === 'identifier') {
84+
// Namespace require: replace import and usages
85+
return [
86+
...usageEdits,
87+
declaration.replace(
88+
`const ${newNamespace} = require('${newImportModule}');`,
89+
),
90+
];
91+
}
92+
93+
if (idNode.kind() === 'object_pattern') {
94+
const isAliased = resolved !== oldFunctionName;
95+
const relevantSpecifiers = idNode
96+
.children()
97+
.filter(
98+
(child) =>
99+
child.kind() === 'pair_pattern' ||
100+
child.kind() === 'shorthand_property_identifier_pattern',
101+
);
102+
103+
const otherSpecifiers = relevantSpecifiers.filter((spec) => {
104+
if (spec.kind() === 'pair_pattern') {
105+
const key = spec.field('key');
106+
return key?.text() !== oldFunctionName;
107+
}
108+
// shorthand
109+
return spec.text() !== oldFunctionName;
110+
});
111+
112+
const newImportSpecifier = isAliased
113+
? `{ ${newImportFunction}: ${resolved} }`
114+
: `{ ${newImportFunction} }`;
115+
const newImportStatement = `const ${newImportSpecifier} = require('${newImportModule}');`;
116+
117+
if (otherSpecifiers.length > 0) {
118+
const othersText = otherSpecifiers.map((s) => s.text()).join(', ');
119+
const modifiedOldImport = `const { ${othersText} } = require('${oldImportModule}');`;
120+
return [
121+
...usageEdits,
122+
declaration.replace(`${modifiedOldImport}${EOL}${newImportStatement}`),
123+
];
124+
}
125+
126+
return [...usageEdits, declaration.replace(newImportStatement)];
127+
}
128+
129+
return [];
130+
}
131+
132+
function handleStaticImport(
133+
statement: SgNode<Js>,
134+
rootNode: SgRoot<Js>,
135+
): Edit[] {
136+
const importClause = statement.child(1);
137+
if (importClause?.kind() !== 'import_clause') return [];
138+
139+
const content = importClause.child(0);
140+
if (!content) return [];
141+
142+
const resolved = resolveBindingPath(statement, `$.${oldFunctionName}`);
143+
if (!resolved) return [];
144+
145+
const usageEdits = replaceUsagesForResolvedPath(rootNode, resolved);
146+
147+
// Namespace imports: import * as ns from '...'
148+
if (content.kind() === 'namespace_import') {
149+
return [
150+
...usageEdits,
151+
statement.replace(
152+
`import * as ${newNamespace} from '${newImportModule}';`,
153+
),
154+
];
155+
}
156+
157+
// Named imports: import { x } from '...'
158+
if (content.kind() === 'named_imports') {
159+
const specs = content
160+
.children()
161+
.filter((c) => c.kind() === 'import_specifier');
162+
const otherSpecs = specs.filter(
163+
(s) => s.field('name')?.text() !== oldFunctionName,
164+
);
165+
166+
const isAliased = resolved !== oldFunctionName;
167+
const newSpec = isAliased
168+
? `{ ${newImportFunction} as ${resolved} }`
169+
: `{ ${newImportFunction} }`;
170+
const newStmt = `import ${newSpec} from '${newImportModule}';`;
171+
172+
return [
173+
...usageEdits,
174+
otherSpecs.length
175+
? statement.replace(
176+
`import { ${otherSpecs.map((s) => s.text()).join(', ')} } from '${oldImportModule}';${EOL}${newStmt}`,
177+
)
178+
: statement.replace(newStmt),
179+
];
180+
}
181+
182+
return [];
183+
}
184+
185+
function handleDynamicImport(
186+
statement: SgNode<Js>,
187+
rootNode: SgRoot<Js>,
188+
): Edit[] {
189+
const valueNode = statement.field('value');
190+
const idNode = statement.child(0);
191+
const declaration = statement.parent();
192+
193+
// must be `const ... = await import(...)` and have a parent declaration
194+
if (valueNode?.kind() !== 'await_expression' || !declaration) return [];
195+
196+
const resolved = resolveBindingPath(statement, `$.${oldFunctionName}`);
197+
if (!resolved) return [];
198+
199+
const usageEdits = replaceUsagesForResolvedPath(rootNode, resolved);
200+
201+
// Case 1: `const ns = await import(...)`
202+
if (idNode?.kind() === 'identifier') {
203+
return [
204+
...usageEdits,
205+
declaration.replace(
206+
`const ${newNamespace} = await import('${newImportModule}');`,
207+
),
208+
];
209+
}
210+
211+
// Case 2: `const { ... } = await import(...)`
212+
if (idNode?.kind() === 'object_pattern') {
213+
const isAliased = resolved !== oldFunctionName;
214+
const specifiers = idNode
215+
.children()
216+
.filter(
217+
(c) =>
218+
c.kind() === 'pair_pattern' ||
219+
c.kind() === 'shorthand_property_identifier_pattern',
220+
);
221+
222+
const otherSpecifiers = specifiers.filter((s) => {
223+
if (s.kind() === 'pair_pattern') {
224+
const key = s.field('key');
225+
return key?.text() !== oldFunctionName;
226+
}
227+
return s.text() !== oldFunctionName;
228+
});
229+
230+
const newImportSpecifier = isAliased
231+
? `{ ${newImportFunction}: ${resolved} }`
232+
: `{ ${newImportFunction} }`;
233+
const newImportStmt = `const ${newImportSpecifier} = await import('${newImportModule}');`;
234+
235+
return [
236+
...usageEdits,
237+
otherSpecifiers.length
238+
? declaration.replace(
239+
`const { ${otherSpecifiers.map((s) => s.text()).join(', ')} } = await import('${oldImportModule}');${EOL}${newImportStmt}`,
240+
)
241+
: declaration.replace(newImportStmt),
242+
];
243+
}
244+
245+
return [];
246+
}
247+
248+
export default function transform(root: SgRoot<Js>): string | null {
249+
const rootNode = root.root();
250+
const allEdits: Edit[] = [];
251+
const sources: SourceTuple[] = [
252+
[getNodeRequireCalls(root, 'crypto'), handleRequire],
253+
[getNodeImportStatements(root, 'crypto'), handleStaticImport],
254+
[getNodeImportCalls(root, 'crypto'), handleDynamicImport],
255+
];
256+
257+
for (const [nodes, handler] of sources) {
258+
for (const node of nodes) {
259+
const edits = handler(node, root);
260+
261+
if (edits.length) {
262+
allEdits.push(...edits);
263+
}
264+
}
265+
}
266+
267+
if (!allEdits.length) return null;
268+
269+
return rootNode.commitEdits(allEdits);
270+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const { createSecureContext: customCreateCredentials } = require('node:tls');
2+
3+
const credentials = customCreateCredentials({
4+
key: privateKey,
5+
cert: certificate,
6+
ca: [caCertificate]
7+
});
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { createSecureContext as customCreateCredentials } from 'node:tls';
2+
3+
const credentials = customCreateCredentials({
4+
key: privateKey,
5+
cert: certificate,
6+
ca: [caCertificate]
7+
});
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const { createSecureContext } = require('node:tls');
2+
3+
const credentials = createSecureContext({
4+
key: privateKey,
5+
cert: certificate,
6+
ca: [caCertificate]
7+
});
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { createSecureContext } from 'node:tls';
2+
3+
const credentials = createSecureContext({
4+
key: privateKey,
5+
cert: certificate,
6+
ca: [caCertificate]
7+
});
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
const tls = require('node:tls');
2+
3+
const credentials = tls.createSecureContext({
4+
key: fs.readFileSync('server-key.pem'),
5+
cert: fs.readFileSync('server-cert.pem')
6+
});

0 commit comments

Comments
 (0)