Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions codemods/v5-migration-recipe/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ This codemod migration recipe helps you update your Express.js v4 applications t

Included transformations:

- **Package JSON Dependencies**: Updates existing package entries in `package.json` that match Express.js v5 direct dependencies and related Express type packages.
- **Back Redirect Deprecated**: This transformation updates instances of `res.redirect('back')` and `res.location('back')` to use the recommended alternatives. Registry entry: [https://app.codemod.com/registry/@expressjs/back-redirect-deprecated](https://app.codemod.com/registry/@expressjs/back-redirect-deprecated).
- **Explicit Request Params**: Migrates usage of the legacy API `req.param(name)` to the current recommended alternatives. Registry entry: [https://app.codemod.com/registry/@expressjs/explicit-request-params](https://app.codemod.com/registry/@expressjs/explicit-request-params).
- **Pluralize Method Names**: Migrates deprecated singular request methods to their pluralized counterparts where applicable. Registry entry: [https://app.codemod.com/registry/@expressjs/pluralize-method-names](https://app.codemod.com/registry/@expressjs/pluralize-method-names).
Expand Down
3 changes: 2 additions & 1 deletion codemods/v5-migration-recipe/codemod.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ category: migration

targets:
languages:
- json
- javascript
- typescript

Expand All @@ -23,4 +24,4 @@ keywords:

registry:
access: public
visibility: public
visibility: public
3 changes: 3 additions & 0 deletions codemods/v5-migration-recipe/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
"version": "1.0.0",
"description": "This codemod migration recipe helps you update your Express.js v4 applications to be compatible with Express.js v5 by addressing deprecated APIs.",
"type": "module",
"scripts": {
"test": "npx codemod jssg test -l json ./src/package-json.ts ./"
},
"repository": {
"type": "git",
"url": "git+https://github.com/expressjs/codemod.git",
Expand Down
103 changes: 103 additions & 0 deletions codemods/v5-migration-recipe/src/package-json.ts
Comment thread
Vinayak1337 marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import type Json from '@codemod.com/jssg-types/src/langs/json'
import type { Edit, SgRoot } from '@codemod.com/jssg-types/src/main'

const PACKAGE_UPDATES = {
'@types/express': '^5.0.0',
'@types/express-serve-static-core': '^5.0.0',
'@types/serve-static': '^2.2.0',
accepts: '^2.0.0',
'body-parser': '^2.2.1',
'content-disposition': '^1.0.0',
'content-type': '^1.0.5',
cookie: '^0.7.1',
'cookie-signature': '^1.2.1',
debug: '^4.4.0',
depd: '^2.0.0',
encodeurl: '^2.0.0',
'escape-html': '^1.0.3',
etag: '^1.8.1',
express: '^5.0.0',
finalhandler: '^2.1.0',
fresh: '^2.0.0',
'http-errors': '^2.0.0',
'merge-descriptors': '^2.0.0',
'mime-types': '^3.0.0',
'on-finished': '^2.4.1',
once: '^1.4.0',
parseurl: '^1.3.3',
'proxy-addr': '^2.0.7',
qs: '^6.14.0',
'range-parser': '^1.2.1',
router: '^2.2.0',
send: '^1.1.0',
'serve-static': '^2.2.0',
statuses: '^2.0.1',
'type-is': '^2.0.1',
vary: '^1.1.2',
} as const
const DEPENDENCY_SECTIONS = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'] as const

type PackageJson = {
[key: string]: unknown
}

function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value)
}

function updateDependency(dependencies: unknown, packageName: string, version: string): boolean {
if (!isRecord(dependencies)) {
return false
}

if (Object.hasOwn(dependencies, packageName) && dependencies[packageName] !== version) {
dependencies[packageName] = version
return true
}

return false
}

function detectIndent(source: string): string | number {
const match = source.match(/\n([ \t]+)"/)

return match?.[1] ?? 2
}

function detectLineEnding(source: string): string {
return source.includes('\r\n') ? '\r\n' : '\n'
}

async function transform(root: SgRoot<Json>): Promise<string | null> {
const rootNode = root.root()
const source = rootNode.text()
let packageJson: PackageJson

try {
packageJson = JSON.parse(source) as PackageJson
} catch {
return null
}

let changed = false

for (const section of DEPENDENCY_SECTIONS) {
const dependencies = packageJson[section]

for (const [packageName, version] of Object.entries(PACKAGE_UPDATES)) {
changed = updateDependency(dependencies, packageName, version) || changed
}
}

if (!changed) {
return null
}

const lineEnding = detectLineEnding(source)
const nextSource = `${JSON.stringify(packageJson, null, detectIndent(source)).replace(/\n/g, lineEnding)}${source.endsWith('\n') ? lineEnding : ''}`
const edits: Edit[] = [rootNode.replace(nextSource)]

return rootNode.commitEdits(edits)
}

export default transform
12 changes: 12 additions & 0 deletions codemods/v5-migration-recipe/tests/expected/dependencies.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "express-app",
"dependencies": {
"body-parser": "^2.2.1",
"express": "^5.0.0",
"serve-static": "^2.2.0"
},
"devDependencies": {
"@types/express": "^5.0.0",
"typescript": "^5.7.2"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "dev-only",
"devDependencies": {
"express": "^5.0.0"
}
}
9 changes: 9 additions & 0 deletions codemods/v5-migration-recipe/tests/expected/no-express.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "no-express",
"dependencies": {
"koa": "^2.15.3"
},
"devDependencies": {
"typescript": "^5.7.2"
}
}
11 changes: 11 additions & 0 deletions codemods/v5-migration-recipe/tests/expected/peer-and-optional.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "express-plugin",
"peerDependencies": {
"body-parser": "^2.2.1",
"express": "^5.0.0"
},
"optionalDependencies": {
"@types/express": "^5.0.0",
"serve-static": "^2.2.0"
}
}
43 changes: 43 additions & 0 deletions codemods/v5-migration-recipe/tests/expected/sub-dependencies.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"name": "express-sub-dependencies",
"dependencies": {
"accepts": "^2.0.0",
"array-flatten": "1.1.1",
"body-parser": "^2.2.1",
"content-disposition": "^1.0.0",
"content-type": "^1.0.5",
"cookie": "^0.7.1",
"cookie-signature": "^1.2.1",
"debug": "^4.4.0",
"depd": "^2.0.0",
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"etag": "^1.8.1",
"express": "^5.0.0",
"finalhandler": "^2.1.0",
"fresh": "^2.0.0",
"http-errors": "^2.0.0",
"merge-descriptors": "^2.0.0",
"mime-types": "^3.0.0",
"on-finished": "^2.4.1",
"once": "^1.4.0",
"parseurl": "^1.3.3",
"path-to-regexp": "~0.1.12",
"proxy-addr": "^2.0.7",
"qs": "^6.14.0",
"range-parser": "^1.2.1",
"router": "^2.2.0",
"send": "^1.1.0",
"serve-static": "^2.2.0",
"statuses": "^2.0.1",
"type-is": "^2.0.1",
"utils-merge": "1.0.1",
"vary": "^1.1.2"
},
"devDependencies": {
"@types/express": "^5.0.0",
"@types/express-serve-static-core": "^5.0.0",
"@types/serve-static": "^2.2.0",
"typescript": "^5.7.2"
}
}
12 changes: 12 additions & 0 deletions codemods/v5-migration-recipe/tests/input/dependencies.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "express-app",
"dependencies": {
"body-parser": "^1.20.3",
"express": "^4.18.2",
"serve-static": "^1.16.2"
},
"devDependencies": {
"@types/express": "^4.17.21",
"typescript": "^5.7.2"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "dev-only",
"devDependencies": {
"express": "~4.21.0"
}
}
9 changes: 9 additions & 0 deletions codemods/v5-migration-recipe/tests/input/no-express.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "no-express",
"dependencies": {
"koa": "^2.15.3"
},
"devDependencies": {
"typescript": "^5.7.2"
}
}
11 changes: 11 additions & 0 deletions codemods/v5-migration-recipe/tests/input/peer-and-optional.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "express-plugin",
"peerDependencies": {
"body-parser": "^1.20.2",
"express": ">=4"
},
"optionalDependencies": {
"@types/express": "^4.17.0",
"serve-static": "^1.15.0"
}
}
43 changes: 43 additions & 0 deletions codemods/v5-migration-recipe/tests/input/sub-dependencies.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"name": "express-sub-dependencies",
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
"body-parser": "~1.20.5",
"content-disposition": "~0.5.4",
"content-type": "~1.0.4",
"cookie": "~0.7.1",
"cookie-signature": "~1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"express": "^4.18.2",
"finalhandler": "~1.3.1",
"fresh": "~0.5.2",
"http-errors": "~2.0.0",
"merge-descriptors": "1.0.3",
"mime-types": "^2.1.35",
"on-finished": "~2.4.1",
"once": "^1.3.3",
"parseurl": "~1.3.3",
"path-to-regexp": "~0.1.12",
"proxy-addr": "~2.0.7",
"qs": "~6.15.1",
"range-parser": "~1.2.1",
"router": "^1.3.8",
"send": "~0.19.0",
"serve-static": "~1.16.2",
"statuses": "~2.0.1",
"type-is": "~1.6.18",
"utils-merge": "1.0.1",
"vary": "~1.1.2"
},
"devDependencies": {
"@types/express": "^4.17.21",
"@types/express-serve-static-core": "^4.19.6",
"@types/serve-static": "^1.15.7",
"typescript": "^5.7.2"
}
}
12 changes: 11 additions & 1 deletion codemods/v5-migration-recipe/workflow.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@ nodes:
runtime:
type: direct
steps:
- name: Updates package.json Express dependencies for Express.js v5 migrations
js-ast-grep:
js_file: src/package-json.ts
base_path: .
semantic_analysis: file
include:
- "**/package.json"
exclude:
- "**/node_modules/**"
language: json
- name: Migrates usage of the legacy APIs `res.redirect('back')` and `res.location('back')` to the current recommended approaches
codemod:
source: "@expressjs/back-redirect-deprecated"
Expand All @@ -29,4 +39,4 @@ nodes:
source: "@expressjs/camelcase-sendfile"
- name: Migrates usage of the legacy APIs `app.del()` to `app.delete()`
codemod:
source: "@expressjs/route-del-to-delete"
source: "@expressjs/route-del-to-delete"