Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

typescript issues #474

Closed
selfagency opened this issue Sep 14, 2024 · 13 comments
Closed

typescript issues #474

selfagency opened this issue Sep 14, 2024 · 13 comments
Assignees

Comments

@selfagency
Copy link

sorry to say this has come up again

tsconfig

{
  "include": ["api/**/*"],
  "exclude": ["node_modules", "**/*.spec.ts"],
  "compilerOptions": {
    "target": "ES2020",
    "module": "Node16",
    "rootDir": "./api",
    "moduleResolution": "Node16",
    "types": [
      "node",
      "polka"
    ],
    "resolveJsonModule": true,
    "allowJs": true,
    "sourceMap": true,
    "outDir": "./dist",
    "importHelpers": true,
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "noImplicitAny": false,
    "skipLibCheck": true
  }
}

result

❯ ts-node-esm api/index.ts
/Users/daniel/Library/pnpm/global/5/.pnpm/[email protected]_@[email protected][email protected]/node_modules/ts-node/src/index.ts:859
    return new TSError(diagnosticText, diagnosticCodes, diagnostics);
           ^
TSError: ⨯ Unable to compile TypeScript:
api/index.ts:18:5 - error TS2349: This expression is not callable.
  Type 'typeof import("/Users/daniel/Developer/places-api/node_modules/.pnpm/[email protected]/node_modules/helmet/index")' has no call signatures.

18     helmet({
       ~~~~~~

    at createTSError (/Users/daniel/Library/pnpm/global/5/.pnpm/[email protected]_@[email protected][email protected]/node_modules/ts-node/src/index.ts:859:12)
    at reportTSError (/Users/daniel/Library/pnpm/global/5/.pnpm/[email protected]_@[email protected][email protected]/node_modules/ts-node/src/index.ts:863:19)
    at getOutput (/Users/daniel/Library/pnpm/global/5/.pnpm/[email protected]_@[email protected][email protected]/node_modules/ts-node/src/index.ts:1077:36)
    at Object.compile (/Users/daniel/Library/pnpm/global/5/.pnpm/[email protected]_@[email protected][email protected]/node_modules/ts-node/src/index.ts:1433:41)
    at transformSource (/Users/daniel/Library/pnpm/global/5/.pnpm/[email protected]_@[email protected][email protected]/node_modules/ts-node/src/esm.ts:400:37)
    at /Users/daniel/Library/pnpm/global/5/.pnpm/[email protected]_@[email protected][email protected]/node_modules/ts-node/src/esm.ts:278:53
    at async addShortCircuitFlag (/Users/daniel/Library/pnpm/global/5/.pnpm/[email protected]_@[email protected][email protected]/node_modules/ts-node/src/esm.ts:409:15)
    at async nextLoad (node:internal/modules/esm/loader:163:22)
    at async ESMLoader.load (node:internal/modules/esm/loader:603:20)
    at async ESMLoader.moduleProvider (node:internal/modules/esm/loader:457:11) {
  diagnosticCodes: [ 2349 ]
}
@EvanHahn
Copy link
Member

How are you importing Helmet? Could you show me a code snippet that can reproduce this problem?

@selfagency
Copy link
Author

selfagency commented Sep 16, 2024

import { City, Country, State } from 'country-state-city';
import helmet from 'helmet';
import polka from 'polka';
import bearerToken from 'polka-bearer-token';

const app = polka();
const port = process.env.PORT || 3000;

app
  .use((req, res, next) => {
    res.setHeader('Content-Type', 'application/json');
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
    next();
  })
  .use(
    helmet({
      crossOriginResourcePolicy: false,
    })
  )
  .use(bearerToken())
  .use((req, res, next) => {
    const token = (req as typeof req & { token: string }).token.replace('Bearer ', '');
    if (token !== process.env.TOKEN) {
      res.statusCode = 401;
      res.end(JSON.stringify({ message: 'Unauthorized', status: 401 }));
    }
    next();
  })
  .get('/countries', (req, res) => {
    res.end(JSON.stringify(Country.getAllCountries()));
  })
  .get('/countries/:id', (req, res) => {
    res.end(JSON.stringify(Country.getCountryByCode(req.params.id)));
  })
  .get('/states', (req, res) => {
    res.end(JSON.stringify(State.getStatesOfCountry(req.query.country as string)));
  })
  .get('/states/:id', (req, res) => {
    res.end(JSON.stringify(State.getStateByCode(req.params.id)));
  })
  .get('/cities', (req, res) => {
    res.end(JSON.stringify(City.getCitiesOfState(req.query.country as string, req.query.state as string)));
  })
  .get('*', (req, res) => {
    res.statusCode = 404;
    res.end(JSON.stringify({ message: 'Not Found', status: 404 }));
  });

app.listen(port, () => {
  console.log(`Started on ${port}`);
});

@EvanHahn
Copy link
Member

I couldn't reproduce this in a reduced sample app. Here's what I have:

package.json:

{
  "private": true,
  "scripts": {
    "typecheck": "tsc --noEmit"
  },
  "dependencies": {
    "helmet": "^7.1.0"
  },
  "devDependencies": {
    "typescript": "^5.6.2"
  }
}

tsconfig.json (modified from yours):

{
  "include": ["app.ts"],
  "compilerOptions": {
    "target": "ES2020",
    "module": "Node16",
    "moduleResolution": "Node16",
    "types": [],
    "resolveJsonModule": true,
    "allowJs": true,
    "sourceMap": true,
    "importHelpers": true,
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "noImplicitAny": false,
    "skipLibCheck": true
  }
}

app.ts (simplified from yours):

import helmet from "helmet";

console.log(
  helmet({
    crossOriginResourcePolicy: false,
  }),
);

What versions are you using? Are any of your dependencies outdated? What about your version of Node.js?

@marc-wittwer
Copy link

marc-wittwer commented Nov 3, 2024

@EvanHahn I made a reproducible example: https://codesandbox.io/p/devbox/r8t9gx
Run yarn start in the terminal at the bottom to see the error.

The sandbox is using node v20.9.

src/index.ts(7,9): error TS2349: This expression is not callable.
  Type 'typeof import("/node_modules/helmet/index")' has no call signatures.

A potential fix could be to export helmet additionally as a named export.

// index.ts of helmet package
- const helmet: Helmet = Object.assign(
+ export const helmet: Helmet = Object.assign(
  function helmet(options: Readonly<HelmetOptions> = {}) {

More context on how another package has solved a similar issue with module resolution "node16". postcss/postcss#1815

@EvanHahn
Copy link
Member

EvanHahn commented Nov 3, 2024

@marc-wittwer Thanks. I'm able to reproduce the issue you're seeing and I'll try to fix it soon.

@fdiazcobos
Copy link

Same issue here

@EvanHahn
Copy link
Member

EvanHahn commented Nov 9, 2024

I don't know why this is happening, but it seems to be a ts-node issue and not a Helmet issue. (Might be wrong, though!)

On @marc-wittwer's repo, when I run npx tsc --noEmit, it exits with no problems. But when I run ts-node, it gives the error you're seeing.

I don't know why that is. Does anybody have any ideas? Does the problem persist if you switch to another tool like tsx?

@mdmower-csnw
Copy link

mdmower-csnw commented Jan 1, 2025

@EvanHahn - Best I can tell, your suggestion that this is a ts-node problem seems correct (and I think it's specifically a ts-node+ESM problem). I don't run into this issue when compiling with tsc or running with tsx. I also don't run into this issue when using ts-node in transpile-only mode.

I believe I've narrowed the issue down to ts-node's support for conditional exports in package.json. Right now, helmet has the following in the packaged package.json:

"exports": {
	"import": "./index.mjs",
	"require": "./index.cjs"
},

and I get the same error as mentioned above when attempting to call helmet(). If I update exports to the following (which is not correct according to docs because default must come last):

// don't do this
"exports": {
	"default": "./index.mjs",
	"import": "./index.mjs",
	"require": "./index.cjs"
},

suddenly the error is resolved. So, it seems like ts-node has trouble with exports: {import, require}. Given this, I know it'd be easy to chalk this issue up as "not your problem" and that would be understandable, but I have an idea if you'd be willing to support us sad folks who for one reason or another are stuck with ts-node+ESM.

I got the following idea from Node's documentation about different approaches to dual CJS and MJS support:

"exports": {
	".": {
		"import": "./index.mjs",
		"require": "./index.cjs"
	},
	"./module": "./index.mjs"
},

This keeps existing conditional exports for the vast majority of users that import/require helmet. For those of us who need to hold ts-node's hand through an ESM import, we can use the following (which also works fine for regular compilation with tsc):

import helmet from "helmet/module";

What do you think?

mdmower-csnw added a commit to mdmower-csnw/helmet that referenced this issue Jan 1, 2025
Make it possible to import the ESM module from 'helmet/module' to
support older tools that don't well support conditional exports (e.g.
ts-node).

Duplicate ESM tests to ensure importing from 'helmet/module' works
as expected.

Fixes helmetjs#474
@EvanHahn
Copy link
Member

EvanHahn commented Jan 1, 2025

@mdmower-csnw Thanks for this investigation and for your PR. I haven't read either in detail, but will look when I get a chance.

@EvanHahn
Copy link
Member

EvanHahn commented Jan 2, 2025

I haven't investigated this deeply, but I think I'd rather fix the bug in ts-node than work around the bug in Helmet. What's to stop this workaround from making its way into other npm modules?

As a workaround, could you import from the file directly? Something like this:

import helmet from "helmet/index.mjs";

@mdmower-csnw Your thorough investigation makes me think we could file a bug on ts-node, and possibly offer a fix. Is that something you'd be willing to do?

@mdmower-csnw
Copy link

@EvanHahn - there's a problem with that approach:

  1. Seeking sponsorship to restart development TypeStrong/ts-node#2140
  2. Last commit date for a change to ts-node was 2 years ago: https://github.com/TypeStrong/ts-node/commits/main/

This is the background for my comment above:

if you'd be willing to support us sad folks who for one reason or another are stuck with ts-node+ESM.

@mdmower-csnw
Copy link

I neglected to respond to your first idea: import helmet from "helmet/index.mjs".

I don't think this will work because you've defined exports for this package, and ./index.mjs is not one of those defined. When I test locally, I see the following error in VSCode:

Cannot find module 'helmet/index.mjs' or its corresponding type declarations.ts(2307)

and attempting to run the source with ts-node produces:

index.ts(1,20): error TS2307: Cannot find module 'helmet/index.mjs' or its corresponding type declarations.

@EvanHahn
Copy link
Member

EvanHahn commented Jan 6, 2025

I'm sympathetic to an unmaintained dependency causing issues—this is happening to my in a personal project right now!—but I don't think Helmet should add workarounds for ts-node's bugs.

Instead, you could manually patch Helmet to fix the bug. You could install patch-package and then add the following patch in patches/helmet+8.0.0.patch:

diff --git a/node_modules/helmet/package.json b/node_modules/helmet/package.json
index 4088f52..be02b75 100644
--- a/node_modules/helmet/package.json
+++ b/node_modules/helmet/package.json
@@ -39,10 +39,7 @@
 	"engines": {
 		"node": ">=18.0.0"
 	},
-	"exports": {
-		"import": "./index.mjs",
-		"require": "./index.cjs"
-	},
-	"main": "./index.cjs",
-	"types": "./index.d.cts"
+	"exports": "./index.mjs",
+	"main": "./index.mjs",
+	"types": "./index.d.mts"
 }

I've forked the CodeSandbox to show a working solution.

I'm going to close this issue because I think this is a reasonable workaround for ts-node's bug, but let me know if that's wrong and I'll reopen.

@EvanHahn EvanHahn closed this as not planned Won't fix, can't repro, duplicate, stale Jan 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging a pull request may close this issue.

5 participants