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

Cannot find module '.prisma/client/default' #12325

Closed
3 tasks done
Tracked by #12485
P4sca1 opened this issue Jun 3, 2024 · 17 comments
Closed
3 tasks done
Tracked by #12485

Cannot find module '.prisma/client/default' #12325

P4sca1 opened this issue Jun 3, 2024 · 17 comments
Assignees
Labels

Comments

@P4sca1
Copy link

P4sca1 commented Jun 3, 2024

Is there an existing issue for this?

How do you use Sentry?

Sentry Saas (sentry.io)

Which SDK are you using?

@sentry/node

SDK Version

8.7.0

Framework Version

No response

Link to Sentry event

No response

SDK Setup

// Preloaded using node --import flag. Do not import code from other source files here.
// https://docs.sentry.io/platforms/javascript/guides/node/install/esm/
// @ts-check

import { dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
import * as Sentry from '@sentry/node'
import { nodeProfilingIntegration } from '@sentry/profiling-node'

/**
 * The root directory of the application.
 * @see {@link https://docs.sentry.io/platforms/javascript/guides/node/configuration/integrations/rewriteframes/}
 */
const root = dirname(fileURLToPath(import.meta.url))

const env =
	process.env['SENTRY_ENV'] || process.env['NODE_ENV'] || 'development'
const version = process.env['PACKAGE_VERSION']

Sentry.init({
	dsn: 'redacted',
	release: version ? `api@${version}` : undefined,
	environment: env,
	integrations: [
		nodeProfilingIntegration(),
		Sentry.dedupeIntegration(),
		Sentry.rewriteFramesIntegration({ root }),
		Sentry.prismaIntegration(),
	],
	beforeBreadcrumb: (breadcrumb) => {
		// Filter out writes to apollographql reporting API, because they are not helpful for error tracing
		// and spam breadcrumbs.
		if (
			breadcrumb.type === 'http' &&
			typeof breadcrumb.data?.['url'] === 'string' &&
			breadcrumb.data['url'].includes('api.apollographql.com')
		) {
			return null
		}

		return breadcrumb
	},
	tracesSampleRate: 0.1,
	profilesSampleRate: 0.1,
})

Steps to Reproduce

I migrated my code to Sentry v8 and can no longer start my application. I start my app using node --import ./instrument.js ./dist/main.js. I use pnpm as my package manager.

Expected Result

Prisma v8 should work using ESM, pnpm, and prisma.

Actual Result

Error: Cannot find module '.prisma/client/default'
Require stack:
- /Users/pascal/code/ips-hosting/api/node_modules/.pnpm/[email protected]/node_modules/import-in-the-middle/lib/get-exports.js
- /Users/pascal/code/ips-hosting/api/node_modules/.pnpm/[email protected]/node_modules/import-in-the-middle/hook.js
    at Module._resolveFilename (node:internal/modules/cjs/loader:1048:15)
    at Function.resolve (node:internal/modules/helpers:136:19)
    at /Users/pascal/code/ips-hosting/api/node_modules/.pnpm/[email protected]/node_modules/import-in-the-middle/lib/get-exports.js:26:33
    at Array.map (<anonymous>)
    at getFullCjsExports (/Users/pascal/code/ips-hosting/api/node_modules/.pnpm/[email protected]/node_modules/import-in-the-middle/lib/get-exports.js:23:40)
    at getExports (/Users/pascal/code/ips-hosting/api/node_modules/.pnpm/[email protected]/node_modules/import-in-the-middle/lib/get-exports.js:83:12)
    at async processModule (/Users/pascal/code/ips-hosting/api/node_modules/.pnpm/[email protected]/node_modules/import-in-the-middle/hook.js:134:23)
    at async getSource (/Users/pascal/code/ips-hosting/api/node_modules/.pnpm/[email protected]/node_modules/import-in-the-middle/hook.js:269:60)
    at async load (/Users/pascal/code/ips-hosting/api/node_modules/.pnpm/[email protected]/node_modules/import-in-the-middle/hook.js:335:26)
    at async nextLoad (node:internal/modules/esm/hooks:833:22) {
  code: 'MODULE_NOT_FOUND',
  requireStack: [
    '/Users/pascal/code/ips-hosting/api/node_modules/.pnpm/[email protected]/node_modules/import-in-the-middle/lib/get-exports.js',
    '/Users/pascal/code/ips-hosting/api/node_modules/.pnpm/[email protected]/node_modules/import-in-the-middle/hook.js'
  ]
}
@github-actions github-actions bot added the Package: node Issues related to the Sentry Node SDK label Jun 3, 2024
@AbhiPrasad
Copy link
Member

This should be fixed by our import-in-the-middle fixes. @P4sca1 could you try using the patch we have to validate this: #12242 (comment)

If the patch works, fix is released with open-telemetry/opentelemetry-js#4745

@P4sca1
Copy link
Author

P4sca1 commented Jun 3, 2024

I tried to apply the following patch file using ppm, but still have the same issue.

diff --git a/hook.js b/hook.js
index 79a2e5b..9539e87 100644
--- a/hook.js
+++ b/hook.js
@@ -3,6 +3,7 @@
 // This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc.
 
 const { randomBytes } = require('crypto')
+const { URL } = require('url')
 const specifiers = new Map()
 const isWin = process.platform === 'win32'
 
@@ -91,6 +92,24 @@ function isStarExportLine (line) {
   return /^\* from /.test(line)
 }
 
+function isBareSpecifier (specifier) {
+  // Relative and absolute paths are not bare specifiers.
+  if (
+    specifier.startsWith('.') ||
+    specifier.startsWith('/')) {
+    return false
+  }
+
+  // Valid URLs are not bare specifiers. (file:, http:, node:, etc.)
+  try {
+    // eslint-disable-next-line no-new
+    new URL(specifier)
+    return false
+  } catch (err) {
+    return true
+  }
+}
+
 /**
  * @typedef {object} ProcessedModule
  * @property {string[]} imports A set of ESM import lines to be added to the
@@ -128,6 +147,7 @@ async function processModule ({
   srcUrl,
   context,
   parentGetSource,
+  parentResolve,
   ns = 'namespace',
   defaultAs = 'default'
 }) {
@@ -154,13 +174,22 @@ async function processModule ({
     if (isStarExportLine(n) === true) {
       const [, modFile] = n.split('* from ')
       const normalizedModName = normalizeModName(modFile)
-      const modUrl = new URL(modFile, srcUrl).toString()
       const modName = Buffer.from(modFile, 'hex') + Date.now() + randomBytes(4).toString('hex')
 
+      let modUrl
+      if (isBareSpecifier(modFile)) {
+        // Bare specifiers need to be resolved relative to the parent module.
+        const result = await parentResolve(modFile, { parentURL: srcUrl })
+        modUrl = result.url
+      } else {
+        modUrl = new URL(modFile, srcUrl).toString()
+      }
+
       const data = await processModule({
         srcUrl: modUrl,
         context,
         parentGetSource,
+        parentResolve,
         ns: `$${modName}`,
         defaultAs: normalizedModName
       })
@@ -180,7 +209,7 @@ async function processModule ({
       // needs to utilize that new name while being initialized from the
       // corresponding origin namespace.
       const renamedExport = matches[2]
-      setters.set(`$${renamedExport}${ns}`, `
+      setters.set(`$${renamedExport}`, `
       let $${renamedExport} = ${ns}.default
       export { $${renamedExport} as ${renamedExport} }
       set.${renamedExport} = (v) => {
@@ -191,7 +220,7 @@ async function processModule ({
       continue
     }
 
-    setters.set(`$${n}` + ns, `
+    setters.set(`$${n}`, `
     let $${n} = ${ns}.${n}
     export { $${n} as ${n} }
     set.${n} = (v) => {
@@ -229,7 +258,19 @@ function addIitm (url) {
 }
 
 function createHook (meta) {
+  let cachedResolve
+  const iitmURL = new URL('lib/register.js', meta.url).toString()
+
   async function resolve (specifier, context, parentResolve) {
+    cachedResolve = parentResolve
+    // See github.com/DataDog/import-in-the-middle/pull/76.
+    if (specifier === iitmURL) {
+      return {
+        url: specifier,
+        shortCircuit: true
+      }
+    }
+
     const { parentURL = '' } = context
     const newSpecifier = deleteIitm(specifier)
     if (isWin && parentURL.indexOf('file:node') === 0) {
@@ -253,6 +294,15 @@ function createHook (meta) {
       return url
     }
 
+    // If the file is referencing itself, we need to skip adding the iitm search params
+    if (url.url === parentURL) {
+      return {
+        url: url.url,
+        shortCircuit: true,
+        format: url.format
+      }
+    }
+
     specifiers.set(url.url, specifier)
 
     return {
@@ -262,14 +312,14 @@ function createHook (meta) {
     }
   }
 
-  const iitmURL = new URL('lib/register.js', meta.url).toString()
   async function getSource (url, context, parentGetSource) {
     if (hasIitm(url)) {
       const realUrl = deleteIitm(url)
       const { imports, namespaces, setters: mapSetters } = await processModule({
         srcUrl: realUrl,
         context,
-        parentGetSource
+        parentGetSource,
+        parentResolve: cachedResolve
       })
       const setters = Array.from(mapSetters.values())
     "pnpm": {
		"patchedDependencies": {
			"[email protected]": "patches/[email protected]"
		}
	}

@P4sca1
Copy link
Author

P4sca1 commented Jun 3, 2024

Here is how I import prisma:

const require = createRequire(import.meta.url)
const client = require('@prisma/client').PrismaClient as typeof PrismaClient

@AbhiPrasad
Copy link
Member

Shoot. one last thing to try then, could you override import-in-the-middle to use 1.8.0 to see if that fixes it?

package.json

{
  "pnpm": {
    "overrides": {
      "import-in-the-middle": "^1.8.0",
    }
  }
}

if this doesn't work, it's def another bug we have to look into. We'll investigate and fix asap. Sorry for the trouble.

@timfish
Copy link
Collaborator

timfish commented Jun 4, 2024

It looks like this might be a different bug caused by the CJS evaluation of import-in-the-middle. I found something similar but different here with the util-deprecate package.

@P4sca1 is there any reason you're doing this:

const require = createRequire(import.meta.url)
const client = require('@prisma/client').PrismaClient as typeof PrismaClient

Rather than this?

import { PrismaClient } from '@prisma/client'

@timfish timfish self-assigned this Jun 4, 2024
@P4sca1
Copy link
Author

P4sca1 commented Jun 4, 2024

Prisma is a CJS package and does not support ESM yet. I think you can't just import CJS packages in an ESM environment, but I will try if it works that way.

@timfish
Copy link
Collaborator

timfish commented Jun 4, 2024

you can't just import CJS packages in an ESM environment

import { X } from 'some-cjs-lib' and import * as lib from 'some-cjs-lib' is supported by Node but it can sometimes be tricky with certain kinds of default exports, especially when a bundler is being used.

It is require(esm) that is currently behind an experimental flag.

@timfish
Copy link
Collaborator

timfish commented Jun 4, 2024

I think this is the cause:
nodejs/import-in-the-middle#95

Working on a fix!

@P4sca1
Copy link
Author

P4sca1 commented Jun 5, 2024

Importing Prisma using import { PrismaClient } from '@prisma/client' in an ESM environment works. The workaround using createRequire is not (or no longer?) required.
The issue with import-in-the-middle persists, no matter how Prisma is imported. Thank you for working on a fix! :)

@timfish
Copy link
Collaborator

timfish commented Jun 5, 2024

I've just tested import { PrismaClient } from '@prisma/client' directly with [email protected] and it handles it correctly without error.

Are you using TypeScript and transpiling to CommonJs? Is there anything else about your setup that might help me reproduce this?

@P4sca1
Copy link
Author

P4sca1 commented Jun 5, 2024

Here's my tsconfig.json:

{
	"extends": ["@tsconfig/node-lts", "@tsconfig/strictest"],
	"compilerOptions": {
		"outDir": "./dist",
		"rootDir": "./src",

		// Use ESM
		"module": "nodenext",
		"moduleResolution": "nodenext",

		// Use helpers from tslib, instead of inlining them
		"noEmitHelpers": true,
		"importHelpers": true,

		// Generate inline source maps
		"sourceMap": true,
		"inlineSources": true,
		"sourceRoot": "/",

		// Don not require exact optional property types. The generated prisma client does not work with it.
		"exactOptionalPropertyTypes": false
	},
	"include": ["src/**/*.ts", "./types/**/*.d.ts"]
}

I run pnpm run build && pnpm run start for testing. My (stripped down) package.json looks like this:

{
	"scripts": {
		"tsc:build": "tsc --project ./tsconfig.json",
		"build": "prisma generate && pnpm run tsc:build",
		"start": "node --import ./instrument.js ./dist/main.js",
	},
	"type": "module",
	"dependencies": {
		"@prisma/client": "5.14.0",
		"@sentry/node": "8.7.0",
		"@sentry/profiling-node": "8.7.0",
		"prisma": "5.14.0",
		"tslib": "2.6.2",
	},
	"devDependencies": {
		"@sentry/cli": "2.32.1",
		"@swc-node/register": "1.9.0",
		"@tsconfig/node-lts": "20.1.3",
		"@tsconfig/strictest": "2.0.5",
		"typescript": "5.4.5"
	},
	"packageManager": "[email protected]",
	"pnpm": {
		"overrides": {
			"import-in-the-middle": "1.8.0"
		}
	}
}

@P4sca1
Copy link
Author

P4sca1 commented Jun 7, 2024

@timfish I created a minimal reproduction here: https://codesandbox.io/p/devbox/sentry-issue-12325-z9jfx7
Run node --import ./instrument.js index.js to reproduce the error.

@timfish
Copy link
Collaborator

timfish commented Jun 12, 2024

Because prisma is CJS, I think this is fixed by nodejs/import-in-the-middle#96 but I need to confirm

@timfish
Copy link
Collaborator

timfish commented Jun 15, 2024

This should hopefully have been fixed by the numerous PRs recently merged at import-in-the-middle.

While we wait for this to be released, there is a patch available that combines all the fixes. If anyone can confirm this patch fixes this issue that would be super helpful!

@P4sca1
Copy link
Author

P4sca1 commented Jun 26, 2024

The issue persists with [email protected]. I updated my reproduction with the latest version.

@timfish
Copy link
Collaborator

timfish commented Jun 26, 2024

This is still waiting for this PR to be merged.

@AbhiPrasad
Copy link
Member

AbhiPrasad commented Jul 8, 2024

With the newest release of import-in-the-middle v1.9.0 this should be fixed.

If you upgrade to a fresh install of the latest version of the Node SDK it should use [email protected] by default.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
Archived in project
Development

No branches or pull requests

4 participants