Skip to content

Commit 0256cab

Browse files
committed
feat: add package.json Express v5 codemod
1 parent b41f6e6 commit 0256cab

15 files changed

Lines changed: 277 additions & 2 deletions

codemods/v5-migration-recipe/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ This codemod migration recipe helps you update your Express.js v4 applications t
44

55
Included transformations:
66

7+
- **Package JSON Dependencies**: Updates existing package entries in `package.json` that match Express.js v5 direct dependencies and related Express type packages.
78
- **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).
89
- **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).
910
- **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).

codemods/v5-migration-recipe/codemod.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ category: migration
1010

1111
targets:
1212
languages:
13+
- json
1314
- javascript
1415
- typescript
1516

@@ -23,4 +24,4 @@ keywords:
2324

2425
registry:
2526
access: public
26-
visibility: public
27+
visibility: public

codemods/v5-migration-recipe/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
"version": "1.0.0",
55
"description": "This codemod migration recipe helps you update your Express.js v4 applications to be compatible with Express.js v5 by addressing deprecated APIs.",
66
"type": "module",
7+
"scripts": {
8+
"test": "npx codemod jssg test -l json ./src/package-json.ts ./"
9+
},
710
"repository": {
811
"type": "git",
912
"url": "git+https://github.com/expressjs/codemod.git",
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import type Json from '@codemod.com/jssg-types/src/langs/json'
2+
import type { Edit, SgRoot } from '@codemod.com/jssg-types/src/main'
3+
4+
const PACKAGE_UPDATES = {
5+
'@types/express': '^5.0.0',
6+
'@types/express-serve-static-core': '^5.0.0',
7+
'@types/serve-static': '^2.2.0',
8+
accepts: '^2.0.0',
9+
'body-parser': '^2.2.1',
10+
'content-disposition': '^1.0.0',
11+
'content-type': '^1.0.5',
12+
cookie: '^0.7.1',
13+
'cookie-signature': '^1.2.1',
14+
debug: '^4.4.0',
15+
depd: '^2.0.0',
16+
encodeurl: '^2.0.0',
17+
'escape-html': '^1.0.3',
18+
etag: '^1.8.1',
19+
express: '^5.0.0',
20+
finalhandler: '^2.1.0',
21+
fresh: '^2.0.0',
22+
'http-errors': '^2.0.0',
23+
'merge-descriptors': '^2.0.0',
24+
'mime-types': '^3.0.0',
25+
'on-finished': '^2.4.1',
26+
once: '^1.4.0',
27+
parseurl: '^1.3.3',
28+
'proxy-addr': '^2.0.7',
29+
qs: '^6.14.0',
30+
'range-parser': '^1.2.1',
31+
router: '^2.2.0',
32+
send: '^1.1.0',
33+
'serve-static': '^2.2.0',
34+
statuses: '^2.0.1',
35+
'type-is': '^2.0.1',
36+
vary: '^1.1.2',
37+
} as const
38+
const DEPENDENCY_SECTIONS = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'] as const
39+
40+
type PackageJson = {
41+
[key: string]: unknown
42+
}
43+
44+
function isRecord(value: unknown): value is Record<string, unknown> {
45+
return typeof value === 'object' && value !== null && !Array.isArray(value)
46+
}
47+
48+
function updateDependency(dependencies: unknown, packageName: string, version: string): boolean {
49+
if (!isRecord(dependencies)) {
50+
return false
51+
}
52+
53+
if (Object.hasOwn(dependencies, packageName) && dependencies[packageName] !== version) {
54+
dependencies[packageName] = version
55+
return true
56+
}
57+
58+
return false
59+
}
60+
61+
function detectIndent(source: string): string | number {
62+
const match = source.match(/\n([ \t]+)"/)
63+
64+
return match?.[1] ?? 2
65+
}
66+
67+
async function transform(root: SgRoot<Json>): Promise<string | null> {
68+
const rootNode = root.root()
69+
const source = rootNode.text()
70+
let packageJson: PackageJson
71+
72+
try {
73+
packageJson = JSON.parse(source) as PackageJson
74+
} catch {
75+
return null
76+
}
77+
78+
let changed = false
79+
80+
for (const section of DEPENDENCY_SECTIONS) {
81+
const dependencies = packageJson[section]
82+
83+
for (const [packageName, version] of Object.entries(PACKAGE_UPDATES)) {
84+
changed = updateDependency(dependencies, packageName, version) || changed
85+
}
86+
}
87+
88+
if (!changed) {
89+
return null
90+
}
91+
92+
const nextSource = `${JSON.stringify(packageJson, null, detectIndent(source))}${source.endsWith('\n') ? '\n' : ''}`
93+
const edits: Edit[] = [rootNode.replace(nextSource)]
94+
95+
return rootNode.commitEdits(edits)
96+
}
97+
98+
export default transform
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"name": "express-app",
3+
"dependencies": {
4+
"body-parser": "^2.2.1",
5+
"express": "^5.0.0",
6+
"serve-static": "^2.2.0"
7+
},
8+
"devDependencies": {
9+
"@types/express": "^5.0.0",
10+
"typescript": "^5.7.2"
11+
}
12+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"name": "dev-only",
3+
"devDependencies": {
4+
"express": "^5.0.0"
5+
}
6+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"name": "no-express",
3+
"dependencies": {
4+
"koa": "^2.15.3"
5+
},
6+
"devDependencies": {
7+
"typescript": "^5.7.2"
8+
}
9+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"name": "express-plugin",
3+
"peerDependencies": {
4+
"body-parser": "^2.2.1",
5+
"express": "^5.0.0"
6+
},
7+
"optionalDependencies": {
8+
"@types/express": "^5.0.0",
9+
"serve-static": "^2.2.0"
10+
}
11+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{
2+
"name": "express-sub-dependencies",
3+
"dependencies": {
4+
"accepts": "^2.0.0",
5+
"array-flatten": "1.1.1",
6+
"body-parser": "^2.2.1",
7+
"content-disposition": "^1.0.0",
8+
"content-type": "^1.0.5",
9+
"cookie": "^0.7.1",
10+
"cookie-signature": "^1.2.1",
11+
"debug": "^4.4.0",
12+
"depd": "^2.0.0",
13+
"encodeurl": "^2.0.0",
14+
"escape-html": "^1.0.3",
15+
"etag": "^1.8.1",
16+
"express": "^5.0.0",
17+
"finalhandler": "^2.1.0",
18+
"fresh": "^2.0.0",
19+
"http-errors": "^2.0.0",
20+
"merge-descriptors": "^2.0.0",
21+
"mime-types": "^3.0.0",
22+
"on-finished": "^2.4.1",
23+
"once": "^1.4.0",
24+
"parseurl": "^1.3.3",
25+
"path-to-regexp": "~0.1.12",
26+
"proxy-addr": "^2.0.7",
27+
"qs": "^6.14.0",
28+
"range-parser": "^1.2.1",
29+
"router": "^2.2.0",
30+
"send": "^1.1.0",
31+
"serve-static": "^2.2.0",
32+
"statuses": "^2.0.1",
33+
"type-is": "^2.0.1",
34+
"utils-merge": "1.0.1",
35+
"vary": "^1.1.2"
36+
},
37+
"devDependencies": {
38+
"@types/express": "^5.0.0",
39+
"@types/express-serve-static-core": "^5.0.0",
40+
"@types/serve-static": "^2.2.0",
41+
"typescript": "^5.7.2"
42+
}
43+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"name": "express-app",
3+
"dependencies": {
4+
"body-parser": "^1.20.3",
5+
"express": "^4.18.2",
6+
"serve-static": "^1.16.2"
7+
},
8+
"devDependencies": {
9+
"@types/express": "^4.17.21",
10+
"typescript": "^5.7.2"
11+
}
12+
}

0 commit comments

Comments
 (0)