Skip to content

feat: add express.static dotfiles codemod#142

Merged
bjohansebas merged 8 commits into
expressjs:mainfrom
singhvishalkr:feat/static-dotfiles-codemod
Jun 17, 2026
Merged

feat: add express.static dotfiles codemod#142
bjohansebas merged 8 commits into
expressjs:mainfrom
singhvishalkr:feat/static-dotfiles-codemod

Conversation

@singhvishalkr

Copy link
Copy Markdown
Contributor

In Express 5, the express.static middleware's dotfiles option defaults to "ignore" instead of serving them. This breaks functionality that depends on serving dot-directories like .well-known (used for Android App Links, Apple Universal Links, Let's Encrypt verification, etc.).

This PR adds a new codemod that explicitly sets dotfiles: 'allow' on express.static() calls that don't already specify a dotfiles option. A comment is included to flag the change for manual review.

The codemod:

  • Transforms express.static('public') to express.static('public', { dotfiles: 'allow' /* Express 5: preserve v4 behavior */ })
  • Preserves existing options when present
  • Skips calls that already have an explicit dotfiles setting

The README includes a security note encouraging developers to review each call and consider whether serving dotfiles is actually necessary.

Also added the new codemod to the v5-migration-recipe workflow.

Closes #116

Add a codemod that explicitly sets dotfiles: 'allow' on express.static()
calls to preserve Express 4 behavior where dotfiles were served by default.

In Express 5, the dotfiles option defaults to 'ignore', which can break
functionality that depends on serving dot-directories like .well-known
(used by Android App Links and Apple Universal Links).

Closes expressjs#116

@AugustinMauroy AugustinMauroy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good starting point

Comment thread codemods/v5-migration-recipe/workflow.yaml
Comment thread codemods/static-dotfiles/README.md
Comment on lines +10 to +11
{ pattern: 'express.static($PATH)' },
{ pattern: 'express.static($PATH, $OPTS)' },

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this ins't resolving import alias ...

before doing creazy thing wait @bjohansebas response.
Do you have any function to resolve express import in different cases

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there's any existing function to resolve that. I think the Node Userland Migration project has something we could take inspiration from, right?

But we should identify Express imports, because it's not always express.static; people can import it under different names.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added binding resolution for default, namespace, and CommonJS express imports before rewriting static() calls, and the multiline fixture is covered in tests.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there's any existing function to resolve that. I think the Node Userland Migration project has something we could take inspiration from, right?

But we should identify Express imports, because it's not always express.static; people can import it under different names.

yeah we have this one https://github.com/nodejs/userland-migrations/blob/ec550adefc5993a67493303cac2a82f12e9919f6/utils/src/ast-grep/resolve-binding-path.ts#L40


if (!nodes.length) return null

const edits: Edit[] = []

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const edits: Edit[] = []

Comment thread codemods/static-dotfiles/src/workflow.ts
Comment on lines +28 to +38
if (optsText.includes('dotfiles')) {
continue
}

if (optsText.startsWith('{') && optsText.endsWith('}')) {
const inner = optsText.slice(1, -1).trim()
const newOpts = inner
? `{ ${inner}, dotfiles: 'allow' /* Express 5: preserve v4 behavior */ }`
: `{ dotfiles: 'allow' /* Express 5: preserve v4 behavior */ }`
edits.push(call.replace(`express.static(${pathArg.text()}, ${newOpts})`))
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seem weak. add more test case with strange indentation + multi line

Detect default, namespace, and CommonJS express bindings before rewriting static() calls. Add a multiline fixture to cover indentation-sensitive options objects.
Match the reviewer request to bump the codemod minor version alongside the alias-handling fix.
@singhvishalkr

Copy link
Copy Markdown
Contributor Author

Pushed 559f3d9. I now resolve default, namespace, and CommonJS bindings for express before rewriting static() calls, added a multiline fixture to cover indentation-sensitive options, and bumped the codemod version to 1.1.0.

Comment on lines +51 to +54
const defaultImportPattern = /import\s+([A-Za-z_$][\w$]*)(?:\s*,[\s\S]*?)?\s+from\s+['"]express['"]/g
const namespaceImportPattern = /import\s+\*\s+as\s+([A-Za-z_$][\w$]*)\s+from\s+['"]express['"]/g
const defaultAsImportPattern = /import\s+\{\s*default\s+as\s+([A-Za-z_$][\w$]*)[\s\S]*?\}\s+from\s+['"]express['"]/g
const requirePattern = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*require\s*\(\s*['"]express['"]\s*\)/g

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't use regex.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pushed a1a6b47. I replaced the alias scan with AST/definition-based resolution, removed the regex path, and added a regression for require(express).static(...) so the commonjs case stays covered.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pushed a1a6b47. The alias lookup now uses AST/definition resolution only, the regex path is gone, and the commonjs case has a direct regression test.

Force text checkouts to LF so ast-grep fixture comparisons stay stable on Windows runners. This fixes the static-dotfiles multiline fixture mismatch without changing the transform logic.

Signed-off-by: Vishal Kumar Singh <vishal.kr.singh2021@gmail.com>
@singhvishalkr

Copy link
Copy Markdown
Contributor Author

Updated the branch.

The alias-resolution thread is already covered by the AST/definition-based lookup in workflow.ts. The remaining blocker was Windows CI: tests_aliases.ts was failing due to CRLF checkout of the multiline fixture. I added .gitattributes with LF text checkout so the ast-grep fixture comparison is stable on Windows.

Verification:

  • npm install
  • npm run test

@AugustinMauroy AugustinMauroy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@singhvishalkr

Copy link
Copy Markdown
Contributor Author

Applied the remaining review cleanup in 06857c1:

  • switched the static-dotfiles README examples to diff blocks
  • kept edits next to rootNode in the transform setup

Verification:

  • npm run test
  • npm run lint
  • git diff --check

…pdating dotfiles behavior and renaming removed options
@bjohansebas bjohansebas merged commit cab8746 into expressjs:main Jun 17, 2026
29 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

new codemod: express.static dotfiles

3 participants