Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 120 additions & 0 deletions recipes/tls-create-secure-pair-to-tls-socket/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# `tls.createSecurePair` deprecation DEP0064

This recipe transforms the usage from the deprecated `createSecurePair()` to `TLSSocket()`.

See [DEP0064](https://nodejs.org/api/deprecations.html#dep0064-tlscreatesecurepair).

## Examples

### Case 1: Basic `createSecurePair` usage

**Before:**
```js
const { createSecurePair } = require('node:tls');

const pair = createSecurePair(credentials);
```

**After:**
```js
const { TLSSocket } = require('node:tls');

const socket = new TLSSocket(underlyingSocket, { secureContext: credentials });
```

---

### Case 2: Namespace import

**Before:**
```js
const tls = require('node:tls');

const pair = tls.createSecurePair(credentials);
```

**After:**
```js
const tls = require('node:tls');

const socket = new tls.TLSSocket(underlyingSocket, { secureContext: credentials });
```

---

### Case 3: With server context

**Before:**
```js
const { createSecurePair } = require('node:tls');

const pair = createSecurePair(credentials, true, true, false);
```

**After:**
```js
const { TLSSocket } = require('node:tls');

const socket = new TLSSocket(underlyingSocket, {
secureContext: credentials,
isServer: true,
requestCert: true,
rejectUnauthorized: false
});
```

---

### Case 4: ESM import

**Before:**
```js
import { createSecurePair } from 'node:tls';

const pair = createSecurePair(credentials);
```

**After:**
```js
import { TLSSocket } from 'node:tls';

const socket = new TLSSocket(underlyingSocket, { secureContext: credentials });
```

---

### Case 5: ESM namespace import

**Before:**
```js
import * as tls from 'node:tls';

const pair = tls.createSecurePair(credentials);
```

**After:**
```js
import * as tls from 'node:tls';

const socket = new tls.TLSSocket(underlyingSocket, { secureContext: credentials });
```

---

### Case 6: Mixed usage with other TLS functions

**Before:**
```js
const { createSecurePair, createServer } = require('node:tls');

const pair = createSecurePair(credentials);
const server = createServer(options);
```

**After:**
```js
const { TLSSocket, createServer } = require('node:tls');

const socket = new TLSSocket(underlyingSocket, { secureContext: credentials });
const server = createServer(options);
```
24 changes: 24 additions & 0 deletions recipes/tls-create-secure-pair-to-tls-socket/codemod.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
schema_version: "1.0"
name: "@nodejs/tls-create-secure-pair-to-tls-socket"
version: 1.0.0
description: Handle DEP0064 by transforming `createSecurePair` to `TLSSocket`
author: Leonardo Trevizo
license: MIT
workflow: workflow.yaml
category: migration

targets:
languages:
- javascript
- typescript

keywords:
- transformation
- migration
- tls
- createSecurePair
- TLSSocket

registry:
access: public
visibility: public
24 changes: 24 additions & 0 deletions recipes/tls-create-secure-pair-to-tls-socket/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"name": "@nodejs/tls-create-secure-pair-to-tls-socket",
"version": "1.0.0",
"description": "Handle DEP0064 replacing `tls.createSecurePair()` with `tls.TLSSocket()`",
"type": "module",
"scripts": {
"test": "npx codemod jssg test -l typescript ./src/workflow.ts ./"
},
"repository": {
"type": "git",
"url": "git+https://github.com/nodejs/userland-migrations.git",
"directory": "recipes/tls-create-secure-pair-to-tls-socket",
"bugs": "https://github.com/nodejs/userland-migrations/issues"
},
"author": "Leo Trevizo",
"license": "MIT",
"homepage": "https://github.com/nodejs/userland-migrations/blob/main/recipes/tls-create-secure-pair-to-tls-socket/README.md",
"devDependencies": {
"@codemod.com/jssg-types": "^1.0.9"
},
"dependencies": {
"@nodejs/codemod-utils": "*"
}
}
172 changes: 172 additions & 0 deletions recipes/tls-create-secure-pair-to-tls-socket/src/workflow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import type { SgRoot, SgNode, Edit } from '@codemod.com/jssg-types/main';
import type JS from '@codemod.com/jssg-types/langs/javascript';
import { getNodeImportStatements } from '@nodejs/codemod-utils/ast-grep/import-statement';
import { getNodeRequireCalls } from '@nodejs/codemod-utils/ast-grep/require-call';
import { resolveBindingPath } from '@nodejs/codemod-utils/ast-grep/resolve-binding-path';

type CallSite = { call: SgNode<JS>; binding: string };

export default function transform(root: SgRoot<JS>): string | null {
const rootNode = root.root();
const tlsStmts = [
...getNodeImportStatements(root, 'node:tls'),
...getNodeImportStatements(root, 'tls'),
...getNodeRequireCalls(root, 'node:tls'),
...getNodeRequireCalls(root, 'tls'),
];
if (tlsStmts.length === 0) return null;

const cspBindings = unique(
tlsStmts
.map(s => resolveBindingPath(s as unknown as SgNode<JS>, '$.createSecurePair'))
.filter(Boolean) as string[]
);
if (cspBindings.length === 0) return null;

const callSites = findCreateSecurePairCalls(rootNode, cspBindings);
const edits: Edit[] = [];

for (const { call, binding } of callSites) {
const a = getText(call.getMatch('A'));
const b = getText(call.getMatch('B'));
const c = getText(call.getMatch('C'));
const d = getText(call.getMatch('D'));
const options = buildOptions(a, b, c, d);
const isNamespace = binding.includes('.');
const replacement = isNamespace
? `new ${binding.replace(/\.createSecurePair$/, '.TLSSocket')}(underlyingSocket, ${options})`
: `new TLSSocket(underlyingSocket, ${options})`;
edits.push(call.replace(replacement));
}

edits.push(...renamePairAssignedVariables(rootNode, cspBindings));
edits.push(...rewriteTlsImports(rootNode));
if (edits.length === 0) return null;
return rootNode.commitEdits(edits);
}

function findCreateSecurePairCalls(rootNode: SgNode<JS>, bindings: string[]): CallSite[] {
return bindings.flatMap(binding =>
rootNode
.findAll({
rule: {
any: [
{ pattern: `${binding}($A, $B, $C, $D)` },
{ pattern: `${binding}($A, $B, $C)` },
{ pattern: `${binding}($A, $B)` },
{ pattern: `${binding}($A)` },
{ pattern: `${binding}()` },
],
},
})
.map(n => ({ call: n as SgNode<JS>, binding }))
);
}

function buildOptions(a?: string | null, b?: string | null, c?: string | null, d?: string | null): string {
const kv: string[] = [];
if (a) kv.push(`secureContext: ${a}`);
if (b) kv.push(`isServer: ${b}`);
if (c) kv.push(`requestCert: ${c}`);
if (d) kv.push(`rejectUnauthorized: ${d}`);
return `{ ${kv.join(', ')} }`;
}

function getText(node: SgNode<JS> | undefined): string | null {
const t = node?.text()?.trim();
return t || null;
}

function unique<T>(arr: T[]): T[] {
return Array.from(new Set(arr));
}

function renamePairAssignedVariables(rootNode: SgNode<JS>, bindings: string[]): Edit[] {
const edits: Edit[] = [];
for (const binding of bindings) {
const decls = rootNode.findAll({
rule: {
kind: 'variable_declarator',
has: {
field: 'value',
kind: 'call_expression',
pattern: `${binding}($$$ARGS)`,
},
},
});
for (const decl of decls) {
const name = decl.field('name');
if (name && name.kind() === 'identifier' && name.text() === 'pair') {
edits.push(name.replace('socket'));
}
}
}
return edits;
}


function rewriteTlsImports(rootNode: SgNode<JS>): Edit[] {
const edits: Edit[] = [];
const tlsStmts = rootNode.findAll({
rule: {
any: [
{ pattern: `import $$ANY from 'tls'` },
{ pattern: `import $$ANY from 'node:tls'` },
{ pattern: `const $$ANY = require('tls')` },
{ pattern: `const $$ANY = require('node:tls')` },
],
},
});

for (const stmt of tlsStmts) {
const code = stmt.text();
if (/\bimport\s+\*\s+as\s+\w+\s+from\s+['"](tls|node:tls)['"]/.test(code)) continue;
{
const m = code.match(
/import\s+(?:(?<def>[\w$]+)\s*,\s*)?\{\s*(?<names>[^}]*)\s*\}\s*from\s*['"](tls|node:tls)['"]\s*;?/,
);
if (m?.groups) {
const def = m.groups.def || '';
const namesRaw = m.groups.names || '';
const names = namesRaw
.split(',')
.map(s => s.trim())
.filter(Boolean)
.map(s => s.replace(/\s+as\s+\w+$/, ''));

if (names.includes('createSecurePair')) {
const kept = Array.from(
new Set(names.filter(n => n !== 'createSecurePair').concat('TLSSocket')),
);
const left = def ? `${def}, ` : '';
const rebuilt = `import ${left}{ ${kept.join(', ')} } from 'node:tls';`;
edits.push(stmt.replace(rebuilt));
continue;
}
}
}
{
const m = code.match(
/const\s*\{\s*(?<names>[^}]*)\s*\}\s*=\s*require\(\s*['"](tls|node:tls)['"]\s*\)\s*;?/,
);
if (m?.groups) {
const namesRaw = m.groups.names || '';
const names = namesRaw
.split(',')
.map(s => s.trim())
.filter(Boolean);

if (names.includes('createSecurePair')) {
const kept = Array.from(
new Set(names.filter(n => n !== 'createSecurePair').concat('TLSSocket')),
);
const src = /node:tls/.test(code) ? 'node:tls' : 'tls';
const rebuilt = `const { ${kept.join(', ')} } = require('${src}');`;
edits.push(stmt.replace(rebuilt));
}
}
}
}

return edits;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const { TLSSocket } = require('node:tls');
const socket = new TLSSocket(underlyingSocket, { secureContext: credentials });
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const { TLSSocket } = require('node:tls');
const socket = new TLSSocket(underlyingSocket, { secureContext: credentials, isServer: true, requestCert: true, rejectUnauthorized: false });
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const tls = require('node:tls');
const socket = new tls.TLSSocket(underlyingSocket, { secureContext: credentials });
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import { TLSSocket } from 'node:tls';
const socket = new TLSSocket(underlyingSocket, { secureContext: credentials });
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import * as tls from 'node:tls';
const socket = new tls.TLSSocket(underlyingSocket, { secureContext: credentials });
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const { createServer, TLSSocket } = require('node:tls');
const socket = new TLSSocket(underlyingSocket, { secureContext: credentials });
const server = createServer(options);
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const { createSecurePair } = require('node:tls');
const pair = createSecurePair(credentials);
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const { createSecurePair } = require('node:tls');
const pair = createSecurePair(credentials, true, true, false);
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const tls = require('node:tls');
const pair = tls.createSecurePair(credentials);
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import { createSecurePair } from 'node:tls';
const pair = createSecurePair(credentials);
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import * as tls from 'node:tls';
const pair = tls.createSecurePair(credentials);
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const { createSecurePair, createServer } = require('node:tls');
const pair = createSecurePair(credentials);
const server = createServer(options);
Loading
Loading