Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
21b6e0b
feat(tls.createSecureContext): introduce
0hmX Aug 3, 2025
144c006
Update recipes/crypto-createCredentials/codemod.yaml
0hmX Aug 3, 2025
f65c839
Update recipes/crypto-createCredentials/package.json
0hmX Aug 3, 2025
8f7bae7
Update recipes/crypto-createCredentials/package.json
0hmX Aug 3, 2025
0b4c417
rename to crypto-create-credentials
0hmX Aug 3, 2025
e36f7be
Update recipes/crypto-create-credentials/workflow.yaml
0hmX Aug 5, 2025
6525cfd
Update recipes/crypto-create-credentials/workflow.yaml
0hmX Aug 5, 2025
a56c4bb
Update recipes/crypto-create-credentials/README.md
0hmX Aug 5, 2025
e6bf56e
sync package-lock.json
0hmX Aug 5, 2025
9ac7b8b
refactor workflow.ts
0hmX Aug 5, 2025
74d8025
fix lint codemod.yml
0hmX Aug 5, 2025
81b8480
Update recipes/crypto-create-credentials/README.md
0hmX Aug 6, 2025
7b8633f
Update recipes/crypto-create-credentials/README.md
0hmX Aug 6, 2025
55bddce
Update recipes/crypto-create-credentials/package.json
0hmX Aug 6, 2025
c08282f
added os.EOL
0hmX Aug 7, 2025
202b99f
lint fix
0hmX Aug 7, 2025
67579ea
Update recipes/crypto-create-credentials/README.md
0hmX Aug 11, 2025
f36f0ee
Update recipes/crypto-create-credentials/README.md
0hmX Aug 11, 2025
21ce469
Update recipes/crypto-create-credentials/src/workflow.ts
0hmX Aug 11, 2025
1a8b640
Update recipes/crypto-create-credentials/src/workflow.ts
0hmX Aug 12, 2025
dfeb476
Update recipes/crypto-create-credentials/src/workflow.ts
0hmX Aug 12, 2025
b3f8bef
Update recipes/crypto-create-credentials/src/workflow.ts
0hmX Aug 12, 2025
f3dde78
rename to createCredentials-to-createSecureContext
0hmX Aug 13, 2025
884a44d
updated to use destructred import
0hmX Aug 13, 2025
015cd21
stable
0hmX Aug 14, 2025
152e90f
fix
0hmX Aug 14, 2025
c0fd8e0
update to test
0hmX Aug 14, 2025
d057380
remove
0hmX Aug 14, 2025
f721b00
resize
0hmX Aug 14, 2025
adc08e3
remove line not needed
0hmX Aug 15, 2025
7dbb6b2
Update recipes/createCredentials-to-createSecureContext/src/workflow.ts
0hmX Aug 15, 2025
c3fc053
remove wasTranformed var
0hmX Aug 15, 2025
44015c1
update package-lock.json
0hmX Aug 15, 2025
46a7069
update
AugustinMauroy Nov 2, 2025
2f40e39
Merge branch 'main' into pr/156
AugustinMauroy Nov 2, 2025
39ba835
update indentation
AugustinMauroy Nov 2, 2025
5228905
update
AugustinMauroy Nov 2, 2025
19259e0
Update workflow.ts
AugustinMauroy Nov 2, 2025
bfb02a3
Update package-lock.json
AugustinMauroy Nov 2, 2025
8b6a222
Merge branch 'main' into crypto-createCredentials
AugustinMauroy Nov 2, 2025
be1de2b
Update package-lock.json
AugustinMauroy Nov 2, 2025
42ff93b
Update package-lock.json
AugustinMauroy Nov 2, 2025
96f75b5
Update package-lock.json
AugustinMauroy Nov 2, 2025
17c887f
Update package-lock.json
AugustinMauroy Nov 2, 2025
f7e605a
chore: regenerate package-lock.json
AugustinMauroy Nov 4, 2025
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
446 changes: 226 additions & 220 deletions package-lock.json

Large diffs are not rendered by default.

35 changes: 35 additions & 0 deletions recipes/createCredentials-to-createSecureContext/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# `crypto.createCredentials` DEP0010

This recipe transforms `crypto.createCredentials` usage to use modern `node:tls` methods.

See [DEP0010](https://nodejs.org/api/deprecations.html#DEP0010).

## Examples

**Before:**
```js
// Using the deprecated createCredentials from node:crypto
const { createCredentials } = require('node:crypto');
// OR
import { createCredentials } from 'node:crypto';

const credentials = createCredentials({
key: privateKey,
cert: certificate,
ca: [caCertificate]
});
```

**After:**
```js
// Updated to use createSecureContext from node:tls
const { createSecureContext } = require('node:tls');
// OR
import { createSecureContext } from 'node:tls';

const credentials = createSecureContext({
key: privateKey,
cert: certificate,
ca: [caCertificate]
});
```
21 changes: 21 additions & 0 deletions recipes/createCredentials-to-createSecureContext/codemod.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
schema_version: "1.0"
name: "@nodejs/createCredentials-to-createSecureContext"
version: 1.0.0
description: Handle DEP0010 via transforming `crypto.createCredentials` to `tls.createSecureContext`
author: 0hmx
license: MIT
workflow: workflow.yaml
category: migration

targets:
languages:
- javascript
- typescript

keywords:
- transformation
- migration

registry:
access: public
visibility: public
24 changes: 24 additions & 0 deletions recipes/createCredentials-to-createSecureContext/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"name": "@nodejs/createcredentials-to-createsecurecontext",
"version": "1.0.0",
"description": "Handle DEP0010 via transforming `crypto.createCredentials` to `tls.createSecureContext` with the appropriate options.",
"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/crypto-create-credentials",
"bugs": "https://github.com/nodejs/userland-migrations/issues"
},
"author": "0hmx",
"license": "MIT",
"homepage": "https://github.com/nodejs/userland-migrations/blob/main/recipes/crypto-create-credentials/README.md",
"devDependencies": {
"@codemod.com/jssg-types": "^1.0.9"
},
"dependencies": {
"@nodejs/codemod-utils": "*"
}
}
270 changes: 270 additions & 0 deletions recipes/createCredentials-to-createSecureContext/src/workflow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
import { EOL } from 'node:os';
import { getNodeRequireCalls } from '@nodejs/codemod-utils/ast-grep/require-call';
import {
getNodeImportCalls,
getNodeImportStatements,
} from '@nodejs/codemod-utils/ast-grep/import-statement';
import { resolveBindingPath } from '@nodejs/codemod-utils/ast-grep/resolve-binding-path';
import type { SgRoot, Edit, SgNode } from '@codemod.com/jssg-types/main';
import type Js from '@codemod.com/jssg-types/langs/javascript';

type SourceHandler = (statement: SgNode<Js>, rootNode: SgRoot<Js>) => Edit[];

type SourceTuple = [SgNode<Js>[], SourceHandler];

const newImportFunction = 'createSecureContext';
const newImportModule = 'node:tls';
const oldFunctionName = 'createCredentials';
const oldImportModule = 'node:crypto';
const newNamespace = 'tls';

function replaceUsagesForResolvedPath(
rootNode: SgRoot<Js>,
resolvedPath: string,
): Edit[] {
// Namespace/member usage e.g., crypto.createCredentials or ns.createCredentials
if (resolvedPath.includes('.')) {
const [object, property] = resolvedPath.split('.');
const usages = rootNode.root().findAll({
rule: {
kind: 'call_expression',
has: {
field: 'function',
kind: 'member_expression',
all: [
{ has: { field: 'object', regex: `^${object}$` } },
{ has: { field: 'property', regex: `^${property}$` } },
],
},
},
});

return usages
.map((u) => u.field('function'))
.filter((f): f is SgNode<Js> => Boolean(f))
.map((f) => f.replace(`${newNamespace}.${newImportFunction}`));
}

// Destructured identifier usage
// If alias was used, resolvedPath will not equal oldFunctionName; in that case we do not touch call sites
if (resolvedPath === oldFunctionName) {
const usages = rootNode.root().findAll({
rule: {
kind: 'call_expression',
has: {
field: 'function',
kind: 'identifier',
regex: `^${oldFunctionName}$`,
},
},
});

return usages
.map((usage) => usage.field('function'))
.filter((id): id is SgNode<Js> => Boolean(id))
.map((id) => id.replace(newImportFunction));
}

// Aliased destructured identifier => keep usages intact
return [];
}

function handleRequire(statement: SgNode<Js>, rootNode: SgRoot<Js>): Edit[] {
const idNode = statement.child(0);
const declaration = statement.parent();

if (!idNode || !declaration) return [];

const resolved = resolveBindingPath(statement, `$.${oldFunctionName}`);
if (!resolved) return [];

const usageEdits = replaceUsagesForResolvedPath(rootNode, resolved);

if (idNode.kind() === 'identifier') {
// Namespace require: replace import and usages
return [
...usageEdits,
declaration.replace(
`const ${newNamespace} = require('${newImportModule}');`,
),
];
}

if (idNode.kind() === 'object_pattern') {
const isAliased = resolved !== oldFunctionName;
const relevantSpecifiers = idNode
.children()
.filter(
(child) =>
child.kind() === 'pair_pattern' ||
child.kind() === 'shorthand_property_identifier_pattern',
);

const otherSpecifiers = relevantSpecifiers.filter((spec) => {
if (spec.kind() === 'pair_pattern') {
const key = spec.field('key');
return key?.text() !== oldFunctionName;
}
// shorthand
return spec.text() !== oldFunctionName;
});

const newImportSpecifier = isAliased
? `{ ${newImportFunction}: ${resolved} }`
: `{ ${newImportFunction} }`;
const newImportStatement = `const ${newImportSpecifier} = require('${newImportModule}');`;

if (otherSpecifiers.length > 0) {
const othersText = otherSpecifiers.map((s) => s.text()).join(', ');
const modifiedOldImport = `const { ${othersText} } = require('${oldImportModule}');`;
return [
...usageEdits,
declaration.replace(`${modifiedOldImport}${EOL}${newImportStatement}`),
];
}

return [...usageEdits, declaration.replace(newImportStatement)];
}

return [];
}

function handleStaticImport(
statement: SgNode<Js>,
rootNode: SgRoot<Js>,
): Edit[] {
const importClause = statement.child(1);
if (importClause?.kind() !== 'import_clause') return [];

const content = importClause.child(0);
if (!content) return [];

const resolved = resolveBindingPath(statement, `$.${oldFunctionName}`);
if (!resolved) return [];

const usageEdits = replaceUsagesForResolvedPath(rootNode, resolved);

// Namespace imports: import * as ns from '...'
if (content.kind() === 'namespace_import') {
return [
...usageEdits,
statement.replace(
`import * as ${newNamespace} from '${newImportModule}';`,
),
];
}

// Named imports: import { x } from '...'
if (content.kind() === 'named_imports') {
const specs = content
.children()
.filter((c) => c.kind() === 'import_specifier');
const otherSpecs = specs.filter(
(s) => s.field('name')?.text() !== oldFunctionName,
);

const isAliased = resolved !== oldFunctionName;
const newSpec = isAliased
? `{ ${newImportFunction} as ${resolved} }`
: `{ ${newImportFunction} }`;
const newStmt = `import ${newSpec} from '${newImportModule}';`;

return [
...usageEdits,
otherSpecs.length
? statement.replace(
`import { ${otherSpecs.map((s) => s.text()).join(', ')} } from '${oldImportModule}';${EOL}${newStmt}`,
)
: statement.replace(newStmt),
];
}

return [];
}

function handleDynamicImport(
statement: SgNode<Js>,
rootNode: SgRoot<Js>,
): Edit[] {
const valueNode = statement.field('value');
const idNode = statement.child(0);
const declaration = statement.parent();

// must be `const ... = await import(...)` and have a parent declaration
if (valueNode?.kind() !== 'await_expression' || !declaration) return [];

const resolved = resolveBindingPath(statement, `$.${oldFunctionName}`);
if (!resolved) return [];

const usageEdits = replaceUsagesForResolvedPath(rootNode, resolved);

// Case 1: `const ns = await import(...)`
if (idNode?.kind() === 'identifier') {
return [
...usageEdits,
declaration.replace(
`const ${newNamespace} = await import('${newImportModule}');`,
),
];
}

// Case 2: `const { ... } = await import(...)`
if (idNode?.kind() === 'object_pattern') {
const isAliased = resolved !== oldFunctionName;
const specifiers = idNode
.children()
.filter(
(c) =>
c.kind() === 'pair_pattern' ||
c.kind() === 'shorthand_property_identifier_pattern',
);

const otherSpecifiers = specifiers.filter((s) => {
if (s.kind() === 'pair_pattern') {
const key = s.field('key');
return key?.text() !== oldFunctionName;
}
return s.text() !== oldFunctionName;
});

const newImportSpecifier = isAliased
? `{ ${newImportFunction}: ${resolved} }`
: `{ ${newImportFunction} }`;
const newImportStmt = `const ${newImportSpecifier} = await import('${newImportModule}');`;

return [
...usageEdits,
otherSpecifiers.length
? declaration.replace(
`const { ${otherSpecifiers.map((s) => s.text()).join(', ')} } = await import('${oldImportModule}');${EOL}${newImportStmt}`,
)
: declaration.replace(newImportStmt),
];
}

return [];
}

export default function transform(root: SgRoot<Js>): string | null {
const rootNode = root.root();
const allEdits: Edit[] = [];
const sources: SourceTuple[] = [
[getNodeRequireCalls(root, 'crypto'), handleRequire],
[getNodeImportStatements(root, 'crypto'), handleStaticImport],
[getNodeImportCalls(root, 'crypto'), handleDynamicImport],
];

for (const [nodes, handler] of sources) {
for (const node of nodes) {
const edits = handler(node, root);

if (edits.length) {
allEdits.push(...edits);
}
}
}

if (!allEdits.length) return null;

return rootNode.commitEdits(allEdits);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const { createSecureContext: customCreateCredentials } = require('node:tls');

const credentials = customCreateCredentials({
key: privateKey,
cert: certificate,
ca: [caCertificate]
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { createSecureContext as customCreateCredentials } from 'node:tls';

const credentials = customCreateCredentials({
key: privateKey,
cert: certificate,
ca: [caCertificate]
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const { createSecureContext } = require('node:tls');

const credentials = createSecureContext({
key: privateKey,
cert: certificate,
ca: [caCertificate]
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { createSecureContext } from 'node:tls';

const credentials = createSecureContext({
key: privateKey,
cert: certificate,
ca: [caCertificate]
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const tls = require('node:tls');

const credentials = tls.createSecureContext({
key: fs.readFileSync('server-key.pem'),
cert: fs.readFileSync('server-cert.pem')
});
Loading
Loading