diff --git a/package-lock.json b/package-lock.json index fdd18d3b..791afb9a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1420,6 +1420,10 @@ "resolved": "recipes/create-require-from-path", "link": true }, + "node_modules/@nodejs/createCredentials-to-createSecureContext": { + "resolved": "recipes/createCredentials-to-createSecureContext", + "link": true + }, "node_modules/@nodejs/import-assertions-to-attributes": { "resolved": "recipes/import-assertions-to-attributes", "link": true @@ -4065,6 +4069,29 @@ "@types/node": "^24.0.3" } }, + "recipes/createCredentials-to-createSecureContext": { + "name": "@nodejs/createCredentials-to-createSecureContext", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@nodejs/codemod-utils": "*" + }, + "devDependencies": { + "@codemod.com/jssg-types": "^1.0.3" + } + }, + "recipes/crypto-create-credentials": { + "name": "@nodejs/crypto-create-credentials", + "version": "1.0.0", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@nodejs/codemod-utils": "*" + }, + "devDependencies": { + "@codemod.com/jssg-types": "^1.0.3" + } + }, "recipes/import-assertions-to-attributes": { "name": "@nodejs/import-assertions-to-attributes", "version": "1.0.0", diff --git a/recipes/createCredentials-to-createSecureContext/README.md b/recipes/createCredentials-to-createSecureContext/README.md new file mode 100644 index 00000000..adacd800 --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/README.md @@ -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] +}); +``` diff --git a/recipes/createCredentials-to-createSecureContext/codemod.yaml b/recipes/createCredentials-to-createSecureContext/codemod.yaml new file mode 100644 index 00000000..be3cfefe --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/codemod.yaml @@ -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 diff --git a/recipes/createCredentials-to-createSecureContext/package.json b/recipes/createCredentials-to-createSecureContext/package.json new file mode 100644 index 00000000..f416677f --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/package.json @@ -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@next 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.3" + }, + "dependencies": { + "@nodejs/codemod-utils": "*" + } +} diff --git a/recipes/createCredentials-to-createSecureContext/src/workflow.ts b/recipes/createCredentials-to-createSecureContext/src/workflow.ts new file mode 100644 index 00000000..be93b211 --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/src/workflow.ts @@ -0,0 +1,367 @@ +import { EOL } from 'node:os'; +import type { SgRoot, Edit, SgNode, Kinds, TypesMap } from "@codemod.com/jssg-types/main"; +import { getNodeRequireCalls } from "@nodejs/codemod-utils/ast-grep/require-call"; +import { getNodeImportCalls, getNodeImportStatements } from "@nodejs/codemod-utils/ast-grep/import-statement"; + +const newImportFunction = 'createSecureContext' +const newImportModule = 'node:tls' +const oldFunctionName = 'createCredentials'; +const oldImportModule = 'node:crypto' +const newNamespace = 'tls'; + +function handleNamespaceImport( + rootNode: SgRoot, + localNamespace: string, + declaration: SgNode>, + importType: 'require' | 'static' | 'dynamic-await' +): Edit[] { + const usages = rootNode.root().findAll({ + rule: { + kind: 'call_expression', + has: { + field: 'function', + kind: 'member_expression', + all: [ + { has: { field: 'object', regex: `^${localNamespace}$` } }, + { has: { field: 'property', regex: `^${oldFunctionName}$` } } + ] + } + } + }); + + if (usages.length === 0) return []; + + const usageEdits = usages + .map(usage => usage.field('function')) + .filter((func): func is SgNode> => Boolean(func)) + .map(func => func.replace(`${newNamespace}.${newImportFunction}`)); + + switch (importType) { + case 'require': + return [...usageEdits, declaration.replace(`const ${newNamespace} = require('${newImportModule}');`)]; + case 'static': + return [...usageEdits, declaration.replace(`import * as ${newNamespace} from '${newImportModule}';`)]; + case 'dynamic-await': + return [...usageEdits, declaration.replace(`const ${newNamespace} = await import('${newImportModule}');`)]; + } +} + +function handleDestructuredImport( + rootNode: SgRoot, + idNode: SgNode>, + declaration: SgNode>, + importType: 'require' | 'static' | 'dynamic-await' +): Edit[] { + let localFunctionName: string | null = null; + let targetSpecifierNode: SgNode | null = null; + let isAliased = false; + + const relevantSpecifiers = idNode.children().filter( + child => child.kind() === 'pair_pattern' + || child.kind() === 'shorthand_property_identifier_pattern' + || child.kind() === 'import_specifier' + ); + + for (const spec of relevantSpecifiers) { + let keyNode: SgNode> | null = null; + let aliasNode: SgNode> | null = null; + + if (spec.kind() === 'import_specifier') { + keyNode = spec.field('name'); + aliasNode = spec.field('alias'); + } else if (spec.kind() === 'pair_pattern') { + keyNode = spec.field('key'); + aliasNode = spec.field('value'); + } else { + keyNode = spec; + } + + if (keyNode?.text() === oldFunctionName) { + targetSpecifierNode = spec; + isAliased = Boolean(aliasNode); + localFunctionName = isAliased ? aliasNode!.text() : keyNode!.text(); + break; + } + } + + if (localFunctionName && targetSpecifierNode) { + const allEdits: Edit[] = []; + + if (!isAliased) { + allEdits.push(...findAndReplaceUsages(rootNode, localFunctionName, newImportFunction)); + } + + const aliasSeparator = importType === 'static' ? ' as' : ':'; + const newImportSpecifier = isAliased + ? `{ ${newImportFunction}${aliasSeparator} ${localFunctionName} }` + : `{ ${newImportFunction} }`; + + let newImportStatement = ''; + switch (importType) { + case 'require': + newImportStatement = `const ${newImportSpecifier} = require('${newImportModule}');`; + break; + case 'static': + newImportStatement = `import ${newImportSpecifier} from '${newImportModule}';`; + break; + case 'dynamic-await': + newImportStatement = `const ${newImportSpecifier} = await import('${newImportModule}');`; + break; + } + + const otherSpecifiers = relevantSpecifiers.filter(s => s !== targetSpecifierNode); + if (otherSpecifiers.length > 0) { + let modifiedOldImport = ''; + const otherSpecifiersText = otherSpecifiers.map(s => s.text()).join(', '); + switch (importType) { + case 'require': + modifiedOldImport = `const { ${otherSpecifiersText} } = require('${oldImportModule}');`; + break; + case 'static': + modifiedOldImport = `import { ${otherSpecifiersText} } from '${oldImportModule}';`; + break; + case 'dynamic-await': + modifiedOldImport = `const { ${otherSpecifiersText} } = await import('${oldImportModule}');`; + break; + } + allEdits.push(declaration.replace(`${modifiedOldImport}${EOL}${newImportStatement}`)); + } else { + allEdits.push(declaration.replace(newImportStatement)); + } + + return allEdits; + } + + return []; +} +function findAndReplaceUsages( + rootNode: SgRoot, + localFunctionName: string, + newFunctionName: string, + object: string | null = null +): Edit[] { + const functionRule = object + ? { + field: 'function', + kind: 'member_expression', + all: [ + { has: { field: 'object', regex: `^${object}$` } }, + { has: { field: 'property', regex: `^${localFunctionName}$` } } + ] + } + : { + field: 'function', + kind: 'identifier', + regex: `^${localFunctionName}$` + }; + + const usages = rootNode.root().findAll({ + rule: { + kind: 'call_expression', + has: functionRule + } + }); + + return usages + .map(usage => { + const functionNode = usage.field('function'); + if (!functionNode) return null; + + const nodeToReplace = object ? functionNode.field('property') : functionNode; + return nodeToReplace ? nodeToReplace.replace(newFunctionName) : null; + }) + .filter((edit): edit is Edit => Boolean(edit)); +} + +function handleRequire( + statement: SgNode>, + rootNode: SgRoot, +): Edit[] { + const idNode = statement.child(0); + const declaration = statement.parent(); + if (!idNode || !declaration) return []; + + if (idNode.kind() === 'identifier') + return handleNamespaceImport(rootNode, idNode.text(), declaration, 'require'); + + if (idNode.kind() === 'object_pattern') + return handleDestructuredImport(rootNode, idNode, declaration, 'require'); + + return []; +} + +function handleStaticImport( + statement: SgNode>, + rootNode: SgRoot +): Edit[] { + const importClause = statement.child(1); + if (importClause?.kind() !== 'import_clause') return []; + + const content = importClause.child(0); + if (!content) return []; + + // Namespace imports: import * as ns from '...' + if (content.kind() === 'namespace_import') { + const ns = content.find({ rule: { kind: 'identifier' } })?.text(); + if (!ns) return []; + + const usages = rootNode.root().findAll({ + rule: { + kind: 'call_expression', + has: { + field: 'function', + kind: 'member_expression', + all: [ + { has: { field: 'object', regex: `^${ns}$` } }, + { has: { field: 'property', regex: `^${oldFunctionName}$` } } + ] + } + } + }); + if (!usages.length) return []; + + const edits = usages + .map(u => u.field('function')?.replace(`${newNamespace}.${newImportFunction}`)) + .filter(Boolean); + edits.push(statement.replace(`import * as ${newNamespace} from '${newImportModule}';`)); + return edits as Edit[]; + } + + // Named imports: import { x } from '...' + if (content.kind() === 'named_imports') { + const specs = content.children().filter(c => c.kind() === 'import_specifier'); + const target = specs.find(s => s.field('name')?.text() === oldFunctionName); + if (!target) return []; + + const aliasNode = target.field('alias'); + const localName = aliasNode?.text() || target.field('name')?.text() || ""; + const edits = aliasNode ? [] : findAndReplaceUsages(rootNode, localName, newImportFunction); + + const newSpec = aliasNode ? `{ ${newImportFunction} as ${localName} }` : `{ ${newImportFunction} }`; + const newStmt = `import ${newSpec} from '${newImportModule}';`; + const others = specs.filter(s => s !== target); + + return [ + ...edits, + others.length + ? statement.replace( + `import { ${others.map(s => s.text()).join(', ')} } from '${oldImportModule}';${EOL}${newStmt}` + ) + : statement.replace(newStmt) + ]; + } + + return []; +} + + +function handleDynamicImport( + statement: SgNode>, + rootNode: SgRoot, +): 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 []; + + // Case 1: `const ns = await import(...)` + if (idNode?.kind() === 'identifier') { + const localNamespace = idNode.text(); + if (!localNamespace) return []; + + const usages = rootNode.root().findAll({ + rule: { + kind: 'call_expression', + has: { + field: 'function', + kind: 'member_expression', + all: [ + { has: { field: 'object', regex: `^${localNamespace}$` } }, + { has: { field: 'property', regex: `^${oldFunctionName}$` } } + ] + } + } + }); + + if (!usages.length) return []; + + const edits = usages + .map(u => u.field('function')?.replace(`${newNamespace}.${newImportFunction}`)) + .filter(Boolean) as Edit[]; + + edits.push(declaration.replace(`const ${newNamespace} = await import('${newImportModule}');`)); + return edits; + } + + // Case 2: `const { ... } = await import(...)` + if (idNode?.kind() === 'object_pattern') { + const specifiers = idNode.children().filter( + c => c.kind() === 'pair_pattern' || c.kind() === 'shorthand_property_identifier_pattern' + ); + + let targetSpecifier: SgNode | null = null; + let localFunctionName: string | null | undefined = null; + let isAliased = false; + + for (const spec of specifiers) { + const keyNode = spec.kind() === 'pair_pattern' ? spec.field('key') : spec; + if (keyNode?.text() === oldFunctionName) { + targetSpecifier = spec; + isAliased = spec.kind() === 'pair_pattern'; + localFunctionName = isAliased ? spec.field('value')?.text() : keyNode.text(); + break; + } + } + + if (!localFunctionName || !targetSpecifier) return []; + + const edits: Edit[] = []; + if (!isAliased) edits.push(...findAndReplaceUsages(rootNode, localFunctionName, newImportFunction)); + + const newImportSpecifier = isAliased + ? `{ ${newImportFunction}: ${localFunctionName} }` + : `{ ${newImportFunction} }`; + + const newImportStmt = `const ${newImportSpecifier} = await import('${newImportModule}');`; + + const otherSpecifiers = specifiers.filter(s => s !== targetSpecifier); + if (otherSpecifiers.length) { + const remaining = `const { ${otherSpecifiers.map(s => s.text()).join(', ')} } = await import('${oldImportModule}');`; + edits.push(declaration.replace(`${remaining}${EOL}${newImportStmt}`)); + } else { + edits.push(declaration.replace(newImportStmt)); + } + + return edits; + } + + return []; +} + +export default function transform(root: SgRoot): string | null { + const rootNode = root.root(); + const allEdits: Edit[] = []; + const sources: [SgNode[] | undefined, (n: SgNode, r: SgRoot) => Edit[]][] = [ + // @ts-ignore + [getNodeRequireCalls(root, 'crypto'), handleRequire], + // @ts-ignore + [getNodeImportStatements(root, 'crypto'), handleStaticImport], + // @ts-ignore + [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); +} diff --git a/recipes/createCredentials-to-createSecureContext/tests/expected/file-1-pair.cjs b/recipes/createCredentials-to-createSecureContext/tests/expected/file-1-pair.cjs new file mode 100644 index 00000000..4d399871 --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/tests/expected/file-1-pair.cjs @@ -0,0 +1,7 @@ +const { createSecureContext: customCreateCredentials } = require('node:tls'); + +const credentials = customCreateCredentials({ + key: privateKey, + cert: certificate, + ca: [caCertificate] +}); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/expected/file-1-pair.mjs b/recipes/createCredentials-to-createSecureContext/tests/expected/file-1-pair.mjs new file mode 100644 index 00000000..efa9b601 --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/tests/expected/file-1-pair.mjs @@ -0,0 +1,7 @@ +import { createSecureContext as customCreateCredentials } from 'node:tls'; + +const credentials = customCreateCredentials({ + key: privateKey, + cert: certificate, + ca: [caCertificate] +}); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/expected/file-1.cjs b/recipes/createCredentials-to-createSecureContext/tests/expected/file-1.cjs new file mode 100644 index 00000000..5e12a058 --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/tests/expected/file-1.cjs @@ -0,0 +1,7 @@ +const { createSecureContext } = require('node:tls'); + +const credentials = createSecureContext({ + key: privateKey, + cert: certificate, + ca: [caCertificate] +}); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/expected/file-1.mjs b/recipes/createCredentials-to-createSecureContext/tests/expected/file-1.mjs new file mode 100644 index 00000000..45258e37 --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/tests/expected/file-1.mjs @@ -0,0 +1,7 @@ +import { createSecureContext } from 'node:tls'; + +const credentials = createSecureContext({ + key: privateKey, + cert: certificate, + ca: [caCertificate] +}); diff --git a/recipes/createCredentials-to-createSecureContext/tests/expected/file-2.cjs b/recipes/createCredentials-to-createSecureContext/tests/expected/file-2.cjs new file mode 100644 index 00000000..3d2fa4dd --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/tests/expected/file-2.cjs @@ -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') +}); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/expected/file-2.mjs b/recipes/createCredentials-to-createSecureContext/tests/expected/file-2.mjs new file mode 100644 index 00000000..c7d63dcc --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/tests/expected/file-2.mjs @@ -0,0 +1,6 @@ +import * as tls from 'node:tls'; + +const credentials = tls.createSecureContext({ + key: fs.readFileSync('server-key.pem'), + cert: fs.readFileSync('server-cert.pem') +}); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/expected/file-3-pair.cjs b/recipes/createCredentials-to-createSecureContext/tests/expected/file-3-pair.cjs new file mode 100644 index 00000000..88385b39 --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/tests/expected/file-3-pair.cjs @@ -0,0 +1,10 @@ +const { createHash } = require('node:crypto'); +const { createSecureContext: customCreateCredentials } = require('node:tls'); + +const credentials = customCreateCredentials({ + key: privateKey, + cert: certificate +}); + +const hash = createHash('sha256'); +hash.update('some data'); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/expected/file-3-pair.mjs b/recipes/createCredentials-to-createSecureContext/tests/expected/file-3-pair.mjs new file mode 100644 index 00000000..4bf1eb7b --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/tests/expected/file-3-pair.mjs @@ -0,0 +1,10 @@ +import { createHash } from 'node:crypto'; +import { createSecureContext as customCreateCredentials } from 'node:tls'; + +const credentials = customCreateCredentials({ + key: privateKey, + cert: certificate +}); + +const hash = createHash('sha256'); +hash.update('some data'); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/expected/file-3.cjs b/recipes/createCredentials-to-createSecureContext/tests/expected/file-3.cjs new file mode 100644 index 00000000..9b0e83cf --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/tests/expected/file-3.cjs @@ -0,0 +1,10 @@ +const { createHash } = require('node:crypto'); +const { createSecureContext } = require('node:tls'); + +const credentials = createSecureContext({ + key: privateKey, + cert: certificate +}); + +const hash = createHash('sha256'); +hash.update('some data'); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/expected/file-3.mjs b/recipes/createCredentials-to-createSecureContext/tests/expected/file-3.mjs new file mode 100644 index 00000000..ded95cba --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/tests/expected/file-3.mjs @@ -0,0 +1,10 @@ +import { createHash } from 'node:crypto'; +import { createSecureContext } from 'node:tls'; + +const credentials = createSecureContext({ + key: privateKey, + cert: certificate +}); + +const hash = createHash('sha256'); +hash.update('some data'); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/expected/file-5.cjs b/recipes/createCredentials-to-createSecureContext/tests/expected/file-5.cjs new file mode 100644 index 00000000..0914b681 --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/tests/expected/file-5.cjs @@ -0,0 +1,6 @@ +const tls = require('node:tls'); + +const credentials = tls.createSecureContext({ + key: privateKey, + cert: certificate +}); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/expected/file-5.mjs b/recipes/createCredentials-to-createSecureContext/tests/expected/file-5.mjs new file mode 100644 index 00000000..3a89f407 --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/tests/expected/file-5.mjs @@ -0,0 +1,6 @@ +import * as tls from 'node:tls'; + +const credentials = tls.createSecureContext({ + key: privateKey, + cert: certificate +}); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/expected/file-6-pair.mjs b/recipes/createCredentials-to-createSecureContext/tests/expected/file-6-pair.mjs new file mode 100644 index 00000000..14b6d68c --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/tests/expected/file-6-pair.mjs @@ -0,0 +1,6 @@ +const { createSecureContext: customCreateCredentials } = await import('node:tls'); + +const credentials = customCreateCredentials({ + key: privateKey, + cert: certificate +}); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/expected/file-6.mjs b/recipes/createCredentials-to-createSecureContext/tests/expected/file-6.mjs new file mode 100644 index 00000000..76442a1c --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/tests/expected/file-6.mjs @@ -0,0 +1,6 @@ +const { createSecureContext } = await import('node:tls'); + +const credentials = createSecureContext({ + key: privateKey, + cert: certificate +}); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/input/file-1-pair.cjs b/recipes/createCredentials-to-createSecureContext/tests/input/file-1-pair.cjs new file mode 100644 index 00000000..c28229a4 --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/tests/input/file-1-pair.cjs @@ -0,0 +1,7 @@ +const { createCredentials: customCreateCredentials } = require('node:crypto'); + +const credentials = customCreateCredentials({ + key: privateKey, + cert: certificate, + ca: [caCertificate] +}); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/input/file-1-pair.mjs b/recipes/createCredentials-to-createSecureContext/tests/input/file-1-pair.mjs new file mode 100644 index 00000000..651ed81f --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/tests/input/file-1-pair.mjs @@ -0,0 +1,7 @@ +import { createCredentials as customCreateCredentials } from 'node:crypto'; + +const credentials = customCreateCredentials({ + key: privateKey, + cert: certificate, + ca: [caCertificate] +}); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/input/file-1.cjs b/recipes/createCredentials-to-createSecureContext/tests/input/file-1.cjs new file mode 100644 index 00000000..e4f625f3 --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/tests/input/file-1.cjs @@ -0,0 +1,7 @@ +const { createCredentials } = require('node:crypto'); + +const credentials = createCredentials({ + key: privateKey, + cert: certificate, + ca: [caCertificate] +}); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/input/file-1.mjs b/recipes/createCredentials-to-createSecureContext/tests/input/file-1.mjs new file mode 100644 index 00000000..e49ca2fc --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/tests/input/file-1.mjs @@ -0,0 +1,7 @@ +import { createCredentials } from 'node:crypto'; + +const credentials = createCredentials({ + key: privateKey, + cert: certificate, + ca: [caCertificate] +}); diff --git a/recipes/createCredentials-to-createSecureContext/tests/input/file-2.cjs b/recipes/createCredentials-to-createSecureContext/tests/input/file-2.cjs new file mode 100644 index 00000000..70453796 --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/tests/input/file-2.cjs @@ -0,0 +1,6 @@ +const crypto = require('node:crypto'); + +const credentials = crypto.createCredentials({ + key: fs.readFileSync('server-key.pem'), + cert: fs.readFileSync('server-cert.pem') +}); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/input/file-2.mjs b/recipes/createCredentials-to-createSecureContext/tests/input/file-2.mjs new file mode 100644 index 00000000..9e15d052 --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/tests/input/file-2.mjs @@ -0,0 +1,6 @@ +import * as crypto from 'node:crypto'; + +const credentials = crypto.createCredentials({ + key: fs.readFileSync('server-key.pem'), + cert: fs.readFileSync('server-cert.pem') +}); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/input/file-3-pair.cjs b/recipes/createCredentials-to-createSecureContext/tests/input/file-3-pair.cjs new file mode 100644 index 00000000..059bbbce --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/tests/input/file-3-pair.cjs @@ -0,0 +1,9 @@ +const { createCredentials : customCreateCredentials, createHash } = require('node:crypto'); + +const credentials = customCreateCredentials({ + key: privateKey, + cert: certificate +}); + +const hash = createHash('sha256'); +hash.update('some data'); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/input/file-3-pair.mjs b/recipes/createCredentials-to-createSecureContext/tests/input/file-3-pair.mjs new file mode 100644 index 00000000..5919d2f2 --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/tests/input/file-3-pair.mjs @@ -0,0 +1,9 @@ +import { createCredentials as customCreateCredentials, createHash } from 'node:crypto'; + +const credentials = customCreateCredentials({ + key: privateKey, + cert: certificate +}); + +const hash = createHash('sha256'); +hash.update('some data'); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/input/file-3.cjs b/recipes/createCredentials-to-createSecureContext/tests/input/file-3.cjs new file mode 100644 index 00000000..2deb8583 --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/tests/input/file-3.cjs @@ -0,0 +1,9 @@ +const { createCredentials, createHash } = require('node:crypto'); + +const credentials = createCredentials({ + key: privateKey, + cert: certificate +}); + +const hash = createHash('sha256'); +hash.update('some data'); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/input/file-3.mjs b/recipes/createCredentials-to-createSecureContext/tests/input/file-3.mjs new file mode 100644 index 00000000..c2b34328 --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/tests/input/file-3.mjs @@ -0,0 +1,9 @@ +import { createCredentials, createHash } from 'node:crypto'; + +const credentials = createCredentials({ + key: privateKey, + cert: certificate +}); + +const hash = createHash('sha256'); +hash.update('some data'); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/input/file-5.cjs b/recipes/createCredentials-to-createSecureContext/tests/input/file-5.cjs new file mode 100644 index 00000000..d8d42793 --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/tests/input/file-5.cjs @@ -0,0 +1,6 @@ +const crypto = require('node:crypto'); + +const credentials = crypto.createCredentials({ + key: privateKey, + cert: certificate +}); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/input/file-5.mjs b/recipes/createCredentials-to-createSecureContext/tests/input/file-5.mjs new file mode 100644 index 00000000..5e7a5a97 --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/tests/input/file-5.mjs @@ -0,0 +1,6 @@ +import * as crypto from 'node:crypto'; + +const credentials = crypto.createCredentials({ + key: privateKey, + cert: certificate +}); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/input/file-6-pair.mjs b/recipes/createCredentials-to-createSecureContext/tests/input/file-6-pair.mjs new file mode 100644 index 00000000..68e03ca7 --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/tests/input/file-6-pair.mjs @@ -0,0 +1,6 @@ +const { createCredentials: customCreateCredentials } = await import('node:crypto'); + +const credentials = customCreateCredentials({ + key: privateKey, + cert: certificate +}); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/input/file-6.mjs b/recipes/createCredentials-to-createSecureContext/tests/input/file-6.mjs new file mode 100644 index 00000000..209a4c5e --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/tests/input/file-6.mjs @@ -0,0 +1,6 @@ +const { createCredentials } = await import('node:crypto'); + +const credentials = createCredentials({ + key: privateKey, + cert: certificate +}); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tsconfig.json b/recipes/createCredentials-to-createSecureContext/tsconfig.json new file mode 100644 index 00000000..92c12497 --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "allowImportingTsExtensions": true, + "allowJs": true, + "alwaysStrict": true, + "baseUrl": "./", + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "lib": ["ESNext", "DOM"], + "module": "NodeNext", + "moduleResolution": "NodeNext", + "noImplicitThis": true, + "removeComments": true, + "strict": true, + "stripInternal": true, + "target": "esnext" + }, + "include": ["./"], + "exclude": [ + "tests/**" + ] +} diff --git a/recipes/createCredentials-to-createSecureContext/workflow.yaml b/recipes/createCredentials-to-createSecureContext/workflow.yaml new file mode 100644 index 00000000..f7184bb8 --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/workflow.yaml @@ -0,0 +1,27 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/codemod-com/codemod/refs/heads/main/schemas/workflow.json + +version: "1" + +nodes: + - id: apply-transforms + name: Apply AST Transformations + type: automatic + runtime: + type: direct + steps: + - name: Handle DEP0010 via transforming `crypto.createCredentials` to `tls.createSecureContext`. + js-ast-grep: + js_file: src/workflow.ts + base_path: . + include: + - "**/*.cjs" + - "**/*.js" + - "**/*.jsx" + - "**/*.mjs" + - "**/*.cts" + - "**/*.mts" + - "**/*.ts" + - "**/*.tsx" + exclude: + - "**/node_modules/**" + language: typescript