From cc26081a7aaa1592563d73a51a9841cb15385f33 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Thu, 9 Mar 2023 17:52:13 +0100 Subject: [PATCH 01/16] feat(sveltekit): Inject `Sentry.init` calls into server and client bundles (#7391) Add an initial version of `Sentry.init` call injection to our new SvelteKit SDK: Specifically, we add a `withSentryViteConfig` wrapper function, which users will need to wrap around their Vite config. This will: * Inject a Vite plugin which takes care of injecting `Sentry.init` calls from `sentry.(client|server).config.(ts|js)` files, providing a DX identical to the NextJS SDK. * The server-side init is injected into the server `index.js` file * The client-side init is injected into the `app.js` file * The injection works both for production builds (with the Node adapter for now) as well as for a local dev server * Add the root directory of the project to the allowed directories for the Vite dev server. We need this so that the client config is correctly picked up by the Vite dev server. --- packages/sveltekit/package.json | 8 +- packages/sveltekit/src/config/index.ts | 1 + packages/sveltekit/src/config/vitePlugins.ts | 73 +++++++++++ .../src/config/withSentryViteConfig.ts | 52 ++++++++ packages/sveltekit/src/index.server.ts | 1 + packages/sveltekit/src/index.types.ts | 1 + .../sveltekit/test/config/vitePlugins.test.ts | 58 +++++++++ .../test/config/withSentryViteConfig.test.ts | 118 ++++++++++++++++++ yarn.lock | 30 ++--- 9 files changed, 324 insertions(+), 18 deletions(-) create mode 100644 packages/sveltekit/src/config/index.ts create mode 100644 packages/sveltekit/src/config/vitePlugins.ts create mode 100644 packages/sveltekit/src/config/withSentryViteConfig.ts create mode 100644 packages/sveltekit/test/config/vitePlugins.test.ts create mode 100644 packages/sveltekit/test/config/withSentryViteConfig.test.ts diff --git a/packages/sveltekit/package.json b/packages/sveltekit/package.json index a911a89fdd09..f65603565285 100644 --- a/packages/sveltekit/package.json +++ b/packages/sveltekit/package.json @@ -24,11 +24,13 @@ "@sentry/node": "7.42.0", "@sentry/svelte": "7.42.0", "@sentry/types": "7.42.0", - "@sentry/utils": "7.42.0" + "@sentry/utils": "7.42.0", + "magic-string": "^0.30.0" }, "devDependencies": { - "@sveltejs/kit": "^1.10.0", - "vite": "^4.0.0" + "@sveltejs/kit": "^1.5.0", + "vite": "4.0.0", + "typescript": "^4.9.3" }, "scripts": { "build": "run-p build:transpile build:types", diff --git a/packages/sveltekit/src/config/index.ts b/packages/sveltekit/src/config/index.ts new file mode 100644 index 000000000000..5648cfb6bffc --- /dev/null +++ b/packages/sveltekit/src/config/index.ts @@ -0,0 +1 @@ +export { withSentryViteConfig } from './withSentryViteConfig'; diff --git a/packages/sveltekit/src/config/vitePlugins.ts b/packages/sveltekit/src/config/vitePlugins.ts new file mode 100644 index 000000000000..49df160b718a --- /dev/null +++ b/packages/sveltekit/src/config/vitePlugins.ts @@ -0,0 +1,73 @@ +import { logger } from '@sentry/utils'; +import * as fs from 'fs'; +import MagicString from 'magic-string'; +import * as path from 'path'; +import type { Plugin, TransformResult } from 'vite'; + +const serverIndexFilePath = path.join('@sveltejs', 'kit', 'src', 'runtime', 'server', 'index.js'); +const devClientAppFilePath = path.join('generated', 'client', 'app.js'); +const prodClientAppFilePath = path.join('generated', 'client-optimized', 'app.js'); + +/** + * This plugin injects the `Sentry.init` calls from `sentry.(client|server).config.(ts|js)` + * into SvelteKit runtime files. + */ +export const injectSentryInitPlugin: Plugin = { + name: 'sentry-init-injection-plugin', + + // In this hook, we inject the `Sentry.init` calls from `sentry.(client|server).config.(ts|js)` + // into SvelteKit runtime files: For the server, we inject it into the server's `index.js` + // file. For the client, we use the `_app.js` file. + transform(code, id) { + if (id.endsWith(serverIndexFilePath)) { + logger.debug('Injecting Server Sentry.init into', id); + return addSentryConfigFileImport('server', code, id) || code; + } + + if (id.endsWith(devClientAppFilePath) || id.endsWith(prodClientAppFilePath)) { + logger.debug('Injecting Client Sentry.init into', id); + return addSentryConfigFileImport('client', code, id) || code; + } + + return code; + }, + + // This plugin should run as early as possible, + // setting `enforce: 'pre'` ensures that it runs before the built-in vite plugins. + // see: https://vitejs.dev/guide/api-plugin.html#plugin-ordering + enforce: 'pre', +}; + +function addSentryConfigFileImport( + platform: 'server' | 'client', + originalCode: string, + entryFileId: string, +): TransformResult | undefined { + const projectRoot = process.cwd(); + const sentryConfigFilename = getUserConfigFile(projectRoot, platform); + + if (!sentryConfigFilename) { + logger.error(`Could not find sentry.${platform}.config.(ts|js) file.`); + return undefined; + } + + const filePath = path.join(path.relative(path.dirname(entryFileId), projectRoot), sentryConfigFilename); + const importStmt = `\nimport "${filePath}";`; + + const ms = new MagicString(originalCode); + ms.append(importStmt); + + return { code: ms.toString(), map: ms.generateMap() }; +} + +function getUserConfigFile(projectDir: string, platform: 'server' | 'client'): string | undefined { + const possibilities = [`sentry.${platform}.config.ts`, `sentry.${platform}.config.js`]; + + for (const filename of possibilities) { + if (fs.existsSync(path.resolve(projectDir, filename))) { + return filename; + } + } + + throw new Error(`Cannot find '${possibilities[0]}' or '${possibilities[1]}' in '${projectDir}'.`); +} diff --git a/packages/sveltekit/src/config/withSentryViteConfig.ts b/packages/sveltekit/src/config/withSentryViteConfig.ts new file mode 100644 index 000000000000..a5fabf3241ee --- /dev/null +++ b/packages/sveltekit/src/config/withSentryViteConfig.ts @@ -0,0 +1,52 @@ +import type { UserConfig, UserConfigExport } from 'vite'; + +import { injectSentryInitPlugin } from './vitePlugins'; + +/** + * This function adds Sentry-specific configuration to your Vite config. + * Pass your config to this function and make sure the return value is exported + * from your `vite.config.js` file. + * + * Note: If you're already wrapping your config with another wrapper, + * for instance with `defineConfig` from vitest, make sure + * that the Sentry wrapper is the outermost one. + * + * @param originalConfig your original vite config + * + * @returns a vite config with Sentry-specific configuration added to it. + */ +export function withSentryViteConfig(originalConfig: UserConfigExport): UserConfigExport { + if (typeof originalConfig === 'function') { + return function (this: unknown, ...viteConfigFunctionArgs: unknown[]): UserConfig | Promise { + const userViteConfigObject = originalConfig.apply(this, viteConfigFunctionArgs); + if (userViteConfigObject instanceof Promise) { + return userViteConfigObject.then(userConfig => addSentryConfig(userConfig)); + } + return addSentryConfig(userViteConfigObject); + }; + } else if (originalConfig instanceof Promise) { + return originalConfig.then(userConfig => addSentryConfig(userConfig)); + } + return addSentryConfig(originalConfig); +} + +function addSentryConfig(originalConfig: UserConfig): UserConfig { + const config = { + ...originalConfig, + plugins: originalConfig.plugins ? [injectSentryInitPlugin, ...originalConfig.plugins] : [injectSentryInitPlugin], + }; + + const mergedDevServerFileSystemConfig: UserConfig['server'] = { + fs: { + ...(config.server && config.server.fs), + allow: [...((config.server && config.server.fs && config.server.fs.allow) || []), '.'], + }, + }; + + config.server = { + ...config.server, + ...mergedDevServerFileSystemConfig, + }; + + return config; +} diff --git a/packages/sveltekit/src/index.server.ts b/packages/sveltekit/src/index.server.ts index 9bdd72ae4f02..acedc021218b 100644 --- a/packages/sveltekit/src/index.server.ts +++ b/packages/sveltekit/src/index.server.ts @@ -1,4 +1,5 @@ export * from './server'; +export * from './config'; // This file is the main entrypoint on the server and/or when the package is `require`d diff --git a/packages/sveltekit/src/index.types.ts b/packages/sveltekit/src/index.types.ts index f2eccf6ffb92..d4c7a9e52c25 100644 --- a/packages/sveltekit/src/index.types.ts +++ b/packages/sveltekit/src/index.types.ts @@ -4,6 +4,7 @@ // Some of the exports collide, which is not allowed, unless we redifine the colliding // exports in this file - which we do below. export * from './client'; +export * from './config'; export * from './server'; import type { Integration, Options, StackParser } from '@sentry/types'; diff --git a/packages/sveltekit/test/config/vitePlugins.test.ts b/packages/sveltekit/test/config/vitePlugins.test.ts new file mode 100644 index 000000000000..9e7c3ec2d788 --- /dev/null +++ b/packages/sveltekit/test/config/vitePlugins.test.ts @@ -0,0 +1,58 @@ +import * as fs from 'fs'; + +import { injectSentryInitPlugin } from '../../src/config/vitePlugins'; + +describe('injectSentryInitPlugin', () => { + it('has its basic properties set', () => { + expect(injectSentryInitPlugin.name).toBe('sentry-init-injection-plugin'); + expect(injectSentryInitPlugin.enforce).toBe('pre'); + expect(typeof injectSentryInitPlugin.transform).toBe('function'); + }); + + describe('tansform', () => { + jest.spyOn(fs, 'existsSync').mockReturnValue(true); + + it('transforms the server index file', () => { + const code = 'foo();'; + const id = '/node_modules/@sveltejs/kit/src/runtime/server/index.js'; + + // @ts-ignore -- transform is definitely defined and callable. Seems like TS doesn't know that. + const result = injectSentryInitPlugin.transform(code, id); + + expect(result.code).toMatch(/foo\(\);\n.*import ".*sentry\.server\.config\.ts";/gm); + expect(result.map).toBeDefined(); + }); + + it('transforms the client index file (dev server)', () => { + const code = 'foo();'; + const id = '.svelte-kit/generated/client/app.js'; + + // @ts-ignore -- transform is definitely defined and callable. Seems like TS doesn't know that. + const result = injectSentryInitPlugin.transform(code, id); + + expect(result.code).toMatch(/foo\(\);\n.*import ".*sentry\.client\.config\.ts";/gm); + expect(result.map).toBeDefined(); + }); + + it('transforms the client index file (prod build)', () => { + const code = 'foo();'; + const id = '.svelte-kit/generated/client-optimized/app.js'; + + // @ts-ignore -- transform is definitely defined and callable. Seems like TS doesn't know that. + const result = injectSentryInitPlugin.transform(code, id); + + expect(result.code).toMatch(/foo\(\);\n.*import ".*sentry\.client\.config\.ts";/gm); + expect(result.map).toBeDefined(); + }); + + it("doesn't transform other files", () => { + const code = 'foo();'; + const id = './src/routes/+page.ts'; + + // @ts-ignore -- transform is definitely defined and callable. Seems like TS doesn't know that. + const result = injectSentryInitPlugin.transform(code, id); + + expect(result).toBe(code); + }); + }); +}); diff --git a/packages/sveltekit/test/config/withSentryViteConfig.test.ts b/packages/sveltekit/test/config/withSentryViteConfig.test.ts new file mode 100644 index 000000000000..1ed7cf5d3d51 --- /dev/null +++ b/packages/sveltekit/test/config/withSentryViteConfig.test.ts @@ -0,0 +1,118 @@ +import type { Plugin, UserConfig } from 'vite'; + +import { withSentryViteConfig } from '../../src/config/withSentryViteConfig'; + +describe('withSentryViteConfig', () => { + const originalConfig = { + plugins: [{ name: 'foo' }], + server: { + fs: { + allow: ['./bar'], + }, + }, + test: { + include: ['src/**/*.{test,spec}.{js,ts}'], + }, + }; + + it('takes a POJO Vite config and returns the sentrified version', () => { + const sentrifiedConfig = withSentryViteConfig(originalConfig); + + expect(typeof sentrifiedConfig).toBe('object'); + + const plugins = (sentrifiedConfig as UserConfig).plugins as Plugin[]; + + expect(plugins).toHaveLength(2); + expect(plugins[0].name).toBe('sentry-init-injection-plugin'); + expect(plugins[1].name).toBe('foo'); + + expect((sentrifiedConfig as UserConfig).server?.fs?.allow).toStrictEqual(['./bar', '.']); + + expect((sentrifiedConfig as any).test).toEqual(originalConfig.test); + }); + + it('takes a Vite config Promise and returns the sentrified version', async () => { + const sentrifiedConfig = await withSentryViteConfig(Promise.resolve(originalConfig)); + + expect(typeof sentrifiedConfig).toBe('object'); + + const plugins = (sentrifiedConfig as UserConfig).plugins as Plugin[]; + + expect(plugins).toHaveLength(2); + expect(plugins[0].name).toBe('sentry-init-injection-plugin'); + expect(plugins[1].name).toBe('foo'); + + expect((sentrifiedConfig as UserConfig).server?.fs?.allow).toStrictEqual(['./bar', '.']); + + expect((sentrifiedConfig as any).test).toEqual(originalConfig.test); + }); + + it('takes a function returning a Vite config and returns the sentrified version', () => { + const sentrifiedConfigFunction = withSentryViteConfig(_env => { + return originalConfig; + }); + const sentrifiedConfig = + typeof sentrifiedConfigFunction === 'function' && sentrifiedConfigFunction({ command: 'build', mode: 'test' }); + + expect(typeof sentrifiedConfig).toBe('object'); + + const plugins = (sentrifiedConfig as UserConfig).plugins as Plugin[]; + + expect(plugins).toHaveLength(2); + expect(plugins[0].name).toBe('sentry-init-injection-plugin'); + expect(plugins[1].name).toBe('foo'); + + expect((sentrifiedConfig as UserConfig).server?.fs?.allow).toStrictEqual(['./bar', '.']); + + expect((sentrifiedConfig as any).test).toEqual(originalConfig.test); + }); + + it('takes a function returning a Vite config promise and returns the sentrified version', async () => { + const sentrifiedConfigFunction = withSentryViteConfig(_env => { + return Promise.resolve(originalConfig); + }); + const sentrifiedConfig = + typeof sentrifiedConfigFunction === 'function' && + (await sentrifiedConfigFunction({ command: 'build', mode: 'test' })); + + expect(typeof sentrifiedConfig).toBe('object'); + + const plugins = (sentrifiedConfig as UserConfig).plugins as Plugin[]; + + expect(plugins).toHaveLength(2); + expect(plugins[0].name).toBe('sentry-init-injection-plugin'); + expect(plugins[1].name).toBe('foo'); + + expect((sentrifiedConfig as UserConfig).server?.fs?.allow).toStrictEqual(['./bar', '.']); + + expect((sentrifiedConfig as any).test).toEqual(originalConfig.test); + }); + + it('adds the vite plugin if no plugins are present', () => { + const sentrifiedConfig = withSentryViteConfig({ + test: { + include: ['src/**/*.{test,spec}.{js,ts}'], + }, + } as UserConfig); + + expect(typeof sentrifiedConfig).toBe('object'); + + const plugins = (sentrifiedConfig as UserConfig).plugins as Plugin[]; + + expect(plugins).toHaveLength(1); + expect(plugins[0].name).toBe('sentry-init-injection-plugin'); + }); + + it('adds the vite plugin and server config to an empty vite config', () => { + const sentrifiedConfig = withSentryViteConfig({}); + + expect(typeof sentrifiedConfig).toBe('object'); + + const plugins = (sentrifiedConfig as UserConfig).plugins as Plugin[]; + + expect(plugins).toHaveLength(1); + expect(plugins[0].name).toBe('sentry-init-injection-plugin'); + + expect((sentrifiedConfig as UserConfig).server?.fs?.allow).toStrictEqual(['.']); + }); +}); diff --git a/yarn.lock b/yarn.lock index 77998803e637..803d62922290 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4205,10 +4205,10 @@ dependencies: highlight.js "^9.15.6" -"@sveltejs/kit@^1.10.0": - version "1.10.0" - resolved "https://registry.yarnpkg.com/@sveltejs/kit/-/kit-1.10.0.tgz#17d3565e5903f6d2c0730197fd875c2cf921ad01" - integrity sha512-0P35zHrByfbF3Ym3RdQL+RvzgsCDSyO3imSwuZ67XAD5HoCQFF3a8Mhh0V3sObz3rc5aJd4Qn82UpAihJqZ6gQ== +"@sveltejs/kit@^1.5.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@sveltejs/kit/-/kit-1.11.0.tgz#23f233c351e5956356ba6f3206e40637c5f5dbda" + integrity sha512-PwViZcMoLgEU/jhLoSyjf5hSrHS67wvSm0ifBo4prP9irpGa5HuPOZeVDTL5tPDSBoKxtdYi1zlGdoiJfO86jA== dependencies: "@sveltejs/vite-plugin-svelte" "^2.0.0" "@types/cookie" "^0.5.1" @@ -12057,7 +12057,7 @@ esbuild@0.13.8: esbuild-windows-64 "0.13.8" esbuild-windows-arm64 "0.13.8" -esbuild@^0.16.14: +esbuild@^0.16.3: version "0.16.17" resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.16.17.tgz#fc2c3914c57ee750635fee71b89f615f25065259" integrity sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg== @@ -21687,7 +21687,7 @@ postcss@^8.1.10, postcss@^8.1.7, postcss@^8.2.15: picocolors "^1.0.0" source-map-js "^1.0.2" -postcss@^8.2.4, postcss@^8.3.5, postcss@^8.3.7, postcss@^8.4.21: +postcss@^8.2.4, postcss@^8.3.5, postcss@^8.3.7, postcss@^8.4.19: version "8.4.21" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.21.tgz#c639b719a57efc3187b13a1d765675485f4134f4" integrity sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg== @@ -23284,7 +23284,7 @@ rollup@^2.45.1: optionalDependencies: fsevents "~2.3.2" -rollup@^3.10.0: +rollup@^3.7.0: version "3.18.0" resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.18.0.tgz#2354ba63ba66d6a09c652c3ea0dbcd9dad72bbde" integrity sha512-J8C6VfEBjkvYPESMQYxKHxNOh4A5a3FlP+0BETGo34HEcE4eTlgCrO2+eWzlu2a/sHs2QUkZco+wscH7jhhgWg== @@ -25983,7 +25983,7 @@ typescript@^3.9.5, typescript@^3.9.7: resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.9.tgz#e69905c54bc0681d0518bd4d587cc6f2d0b1a674" integrity sha512-kdMjTiekY+z/ubJCATUPlRDl39vXYiMV9iyeMuEuXZh2we6zz80uovNN2WlAxmmdE/Z/YQe+EbOEXB5RHEED3w== -typescript@^4.9.4: +typescript@^4.9.3, typescript@^4.9.4: version "4.9.5" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== @@ -26518,15 +26518,15 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" -vite@^4.0.0: - version "4.1.4" - resolved "https://registry.yarnpkg.com/vite/-/vite-4.1.4.tgz#170d93bcff97e0ebc09764c053eebe130bfe6ca0" - integrity sha512-3knk/HsbSTKEin43zHu7jTwYWv81f8kgAL99G5NWBcA1LKvtvcVAC4JjBH1arBunO9kQka+1oGbrMKOjk4ZrBg== +vite@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/vite/-/vite-4.0.0.tgz#b81b88349a06b2faaa53ae14cf96c942548e3454" + integrity sha512-ynad+4kYs8Jcnn8J7SacS9vAbk7eMy0xWg6E7bAhS1s79TK+D7tVFGXVZ55S7RNLRROU1rxoKlvZ/qjaB41DGA== dependencies: - esbuild "^0.16.14" - postcss "^8.4.21" + esbuild "^0.16.3" + postcss "^8.4.19" resolve "^1.22.1" - rollup "^3.10.0" + rollup "^3.7.0" optionalDependencies: fsevents "~2.3.2" From 8b0d536608ce104c739843f16adaf9a80a6b96cb Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Fri, 10 Mar 2023 10:15:36 +0100 Subject: [PATCH 02/16] fix(core): Avoid using `Array.findIndex()` as it is ES5 incompatible (#7400) --- packages/core/src/integration.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/core/src/integration.ts b/packages/core/src/integration.ts index 58b63dacc199..d60bb857bbd5 100644 --- a/packages/core/src/integration.ts +++ b/packages/core/src/integration.ts @@ -69,7 +69,7 @@ export function getIntegrationsToSetup(options: Options): Integration[] { // `beforeSendTransaction`. It therefore has to run after all other integrations, so that the changes of all event // processors will be reflected in the printed values. For lack of a more elegant way to guarantee that, we therefore // locate it and, assuming it exists, pop it out of its current spot and shove it onto the end of the array. - const debugIndex = finalIntegrations.findIndex(integration => integration.name === 'Debug'); + const debugIndex = findIndex(finalIntegrations, integration => integration.name === 'Debug'); if (debugIndex !== -1) { const [debugInstance] = finalIntegrations.splice(debugIndex, 1); finalIntegrations.push(debugInstance); @@ -107,3 +107,14 @@ export function setupIntegration(integration: Integration, integrationIndex: Int __DEBUG_BUILD__ && logger.log(`Integration installed: ${integration.name}`); } } + +// Polyfill for Array.findIndex(), which is not supported in ES5 +function findIndex(arr: T[], callback: (item: T) => boolean): number { + for (let i = 0; i < arr.length; i++) { + if (callback(arr[i]) === true) { + return i; + } + } + + return -1; +} From 2a22f24f7457228bd6d372d8d6846c5ba47f6a27 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Fri, 10 Mar 2023 13:03:05 +0100 Subject: [PATCH 03/16] feat(sveltekit): Add SvelteKit client and server `init` functions (#7408) AddsSDK `init` functions for the SvelteKit server- and client-side SDK. Currently these functions * set SDK metadata * call the respective base SDK's init * set a `runtime` tag ('browser' / 'node' resp.) Also fix a bug in the Svelte SDK where passed in `options._metadata.sdk` was previously overwritten with the Svelte SDKs data. --- packages/svelte/src/sdk.ts | 2 +- packages/svelte/test/sdk.test.ts | 58 ++++++++++++++++------ packages/sveltekit/src/client/index.ts | 3 +- packages/sveltekit/src/client/sdk.ts | 18 +++++++ packages/sveltekit/src/common/metadata.ts | 28 +++++++++++ packages/sveltekit/src/server/index.ts | 3 +- packages/sveltekit/src/server/sdk.ts | 19 +++++++ packages/sveltekit/test/client/sdk.test.ts | 49 ++++++++++++++++++ packages/sveltekit/test/server/sdk.test.ts | 51 +++++++++++++++++++ 9 files changed, 212 insertions(+), 19 deletions(-) create mode 100644 packages/sveltekit/src/client/sdk.ts create mode 100644 packages/sveltekit/src/common/metadata.ts create mode 100644 packages/sveltekit/src/server/sdk.ts create mode 100644 packages/sveltekit/test/client/sdk.test.ts create mode 100644 packages/sveltekit/test/server/sdk.test.ts diff --git a/packages/svelte/src/sdk.ts b/packages/svelte/src/sdk.ts index 448ccacbf046..3a7c671a7d1d 100644 --- a/packages/svelte/src/sdk.ts +++ b/packages/svelte/src/sdk.ts @@ -7,7 +7,7 @@ import { getDomElement } from '@sentry/utils'; */ export function init(options: BrowserOptions): void { options._metadata = options._metadata || {}; - options._metadata.sdk = { + options._metadata.sdk = options._metadata.sdk || { name: 'sentry.javascript.svelte', packages: [ { diff --git a/packages/svelte/test/sdk.test.ts b/packages/svelte/test/sdk.test.ts index 8203b025a437..ea3e3e383eba 100644 --- a/packages/svelte/test/sdk.test.ts +++ b/packages/svelte/test/sdk.test.ts @@ -1,23 +1,21 @@ -import { addGlobalEventProcessor, init as browserInit, SDK_VERSION } from '@sentry/browser'; +import { SDK_VERSION } from '@sentry/browser'; +import * as SentryBrowser from '@sentry/browser'; import type { EventProcessor } from '@sentry/types'; import { detectAndReportSvelteKit, init as svelteInit, isSvelteKitApp } from '../src/sdk'; let passedEventProcessor: EventProcessor | undefined; -jest.mock('@sentry/browser', () => { - const actual = jest.requireActual('@sentry/browser'); - return { - ...actual, - init: jest.fn().mockImplementation(actual.init), - addGlobalEventProcessor: jest.fn().mockImplementation(proc => { - passedEventProcessor = proc; - }), - }; -}); +const browserInit = jest.spyOn(SentryBrowser, 'init'); +const addGlobalEventProcessor = jest + .spyOn(SentryBrowser, 'addGlobalEventProcessor') + .mockImplementation((eventProcessor: EventProcessor) => { + passedEventProcessor = eventProcessor; + return () => {}; + }); describe('Initialize Svelte SDk', () => { - afterAll(() => { + beforeEach(() => { jest.clearAllMocks(); }); @@ -37,13 +35,45 @@ describe('Initialize Svelte SDk', () => { }; expect(browserInit).toHaveBeenCalledTimes(1); - expect(browserInit).toHaveBeenCalledWith(expect.objectContaining(expectedMetadata)); + expect(browserInit).toHaveBeenLastCalledWith(expect.objectContaining(expectedMetadata)); + }); + + it("doesn't add the default svelte metadata, if metadata is already passed", () => { + svelteInit({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + _metadata: { + sdk: { + name: 'sentry.javascript.sveltekit', + version: SDK_VERSION, + packages: [ + { name: 'npm:@sentry/sveltekit', version: SDK_VERSION }, + { name: 'npm:@sentry/svelte', version: SDK_VERSION }, + ], + }, + }, + }); + + expect(browserInit).toHaveBeenCalledTimes(1); + expect(browserInit).toHaveBeenLastCalledWith( + expect.objectContaining({ + _metadata: { + sdk: { + name: 'sentry.javascript.sveltekit', + version: SDK_VERSION, + packages: [ + { name: 'npm:@sentry/sveltekit', version: SDK_VERSION }, + { name: 'npm:@sentry/svelte', version: SDK_VERSION }, + ], + }, + }, + }), + ); }); }); describe('detectAndReportSvelteKit()', () => { const originalHtmlBody = document.body.innerHTML; - afterEach(() => { + beforeEach(() => { jest.clearAllMocks(); document.body.innerHTML = originalHtmlBody; passedEventProcessor = undefined; diff --git a/packages/sveltekit/src/client/index.ts b/packages/sveltekit/src/client/index.ts index cdd2d7ba8a6e..92f3f38bcd88 100644 --- a/packages/sveltekit/src/client/index.ts +++ b/packages/sveltekit/src/client/index.ts @@ -1,4 +1,3 @@ export * from '@sentry/svelte'; -// Just here so that eslint is happy until we export more stuff here -export const PLACEHOLDER_CLIENT = 'PLACEHOLDER'; +export { init } from './sdk'; diff --git a/packages/sveltekit/src/client/sdk.ts b/packages/sveltekit/src/client/sdk.ts new file mode 100644 index 000000000000..d1f0f9ac597f --- /dev/null +++ b/packages/sveltekit/src/client/sdk.ts @@ -0,0 +1,18 @@ +import type { BrowserOptions } from '@sentry/svelte'; +import { configureScope, init as initSvelteSdk } from '@sentry/svelte'; + +import { applySdkMetadata } from '../common/metadata'; + +/** + * + * @param options + */ +export function init(options: BrowserOptions): void { + applySdkMetadata(options, ['sveltekit', 'svelte']); + + initSvelteSdk(options); + + configureScope(scope => { + scope.setTag('runtime', 'browser'); + }); +} diff --git a/packages/sveltekit/src/common/metadata.ts b/packages/sveltekit/src/common/metadata.ts new file mode 100644 index 000000000000..76a9642ee36b --- /dev/null +++ b/packages/sveltekit/src/common/metadata.ts @@ -0,0 +1,28 @@ +import { SDK_VERSION } from '@sentry/core'; +import type { Options, SdkInfo } from '@sentry/types'; + +const PACKAGE_NAME_PREFIX = 'npm:@sentry/'; + +/** + * A builder for the SDK metadata in the options for the SDK initialization. + * + * Note: This function is identical to `buildMetadata` in Remix and NextJS. + * We don't extract it for bundle size reasons. + * If you make changes to this function consider updating the othera as well. + * + * @param options SDK options object that gets mutated + * @param names list of package names + */ +export function applySdkMetadata(options: Options, names: string[]): void { + options._metadata = options._metadata || {}; + options._metadata.sdk = + options._metadata.sdk || + ({ + name: 'sentry.javascript.sveltekit', + packages: names.map(name => ({ + name: `${PACKAGE_NAME_PREFIX}${name}`, + version: SDK_VERSION, + })), + version: SDK_VERSION, + } as SdkInfo); +} diff --git a/packages/sveltekit/src/server/index.ts b/packages/sveltekit/src/server/index.ts index 04654fecd19a..6ac8d97b4241 100644 --- a/packages/sveltekit/src/server/index.ts +++ b/packages/sveltekit/src/server/index.ts @@ -1,4 +1,3 @@ export * from '@sentry/node'; -// Just here so that eslint is happy until we export more stuff here -export const PLACEHOLDER_SERVER = 'PLACEHOLDER'; +export { init } from './sdk'; diff --git a/packages/sveltekit/src/server/sdk.ts b/packages/sveltekit/src/server/sdk.ts new file mode 100644 index 000000000000..1d1bbdbf2674 --- /dev/null +++ b/packages/sveltekit/src/server/sdk.ts @@ -0,0 +1,19 @@ +import { configureScope } from '@sentry/core'; +import type { NodeOptions } from '@sentry/node'; +import { init as initNodeSdk } from '@sentry/node'; + +import { applySdkMetadata } from '../common/metadata'; + +/** + * + * @param options + */ +export function init(options: NodeOptions): void { + applySdkMetadata(options, ['sveltekit', 'node']); + + initNodeSdk(options); + + configureScope(scope => { + scope.setTag('runtime', 'node'); + }); +} diff --git a/packages/sveltekit/test/client/sdk.test.ts b/packages/sveltekit/test/client/sdk.test.ts new file mode 100644 index 000000000000..5ed924a60ccc --- /dev/null +++ b/packages/sveltekit/test/client/sdk.test.ts @@ -0,0 +1,49 @@ +import { getCurrentHub } from '@sentry/core'; +import * as SentrySvelte from '@sentry/svelte'; +import { SDK_VERSION, WINDOW } from '@sentry/svelte'; + +import { init } from '../../src/client/sdk'; +const svelteInit = jest.spyOn(SentrySvelte, 'init'); + +describe('Sentry client SDK', () => { + describe('init', () => { + afterEach(() => { + jest.clearAllMocks(); + WINDOW.__SENTRY__.hub = undefined; + }); + + it('adds SvelteKit metadata to the SDK options', () => { + expect(svelteInit).not.toHaveBeenCalled(); + + init({}); + + expect(svelteInit).toHaveBeenCalledTimes(1); + expect(svelteInit).toHaveBeenCalledWith( + expect.objectContaining({ + _metadata: { + sdk: { + name: 'sentry.javascript.sveltekit', + version: SDK_VERSION, + packages: [ + { name: 'npm:@sentry/sveltekit', version: SDK_VERSION }, + { name: 'npm:@sentry/svelte', version: SDK_VERSION }, + ], + }, + }, + }), + ); + }); + + it('sets the runtime tag on the scope', () => { + const currentScope = getCurrentHub().getScope(); + + // @ts-ignore need access to protected _tags attribute + expect(currentScope._tags).toEqual({}); + + init({ dsn: 'https://public@dsn.ingest.sentry.io/1337' }); + + // @ts-ignore need access to protected _tags attribute + expect(currentScope._tags).toEqual({ runtime: 'browser' }); + }); + }); +}); diff --git a/packages/sveltekit/test/server/sdk.test.ts b/packages/sveltekit/test/server/sdk.test.ts new file mode 100644 index 000000000000..785cf158e5c8 --- /dev/null +++ b/packages/sveltekit/test/server/sdk.test.ts @@ -0,0 +1,51 @@ +import { getCurrentHub } from '@sentry/core'; +import * as SentryNode from '@sentry/node'; +import { SDK_VERSION } from '@sentry/node'; +import { GLOBAL_OBJ } from '@sentry/utils'; + +import { init } from '../../src/server/sdk'; + +const nodeInit = jest.spyOn(SentryNode, 'init'); + +describe('Sentry server SDK', () => { + describe('init', () => { + afterEach(() => { + jest.clearAllMocks(); + GLOBAL_OBJ.__SENTRY__.hub = undefined; + }); + + it('adds SvelteKit metadata to the SDK options', () => { + expect(nodeInit).not.toHaveBeenCalled(); + + init({}); + + expect(nodeInit).toHaveBeenCalledTimes(1); + expect(nodeInit).toHaveBeenCalledWith( + expect.objectContaining({ + _metadata: { + sdk: { + name: 'sentry.javascript.sveltekit', + version: SDK_VERSION, + packages: [ + { name: 'npm:@sentry/sveltekit', version: SDK_VERSION }, + { name: 'npm:@sentry/node', version: SDK_VERSION }, + ], + }, + }, + }), + ); + }); + + it('sets the runtime tag on the scope', () => { + const currentScope = getCurrentHub().getScope(); + + // @ts-ignore need access to protected _tags attribute + expect(currentScope._tags).toEqual({}); + + init({ dsn: 'https://public@dsn.ingest.sentry.io/1337' }); + + // @ts-ignore need access to protected _tags attribute + expect(currentScope._tags).toEqual({ runtime: 'node' }); + }); + }); +}); From bd45dfc5a186217c5fbca54213282b27062b3a5e Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Fri, 10 Mar 2023 14:33:31 +0100 Subject: [PATCH 04/16] feat(sveltekit): Introduce client-side `handleError` wrapper (#7406) --- packages/sveltekit/package.json | 14 +++- packages/sveltekit/src/client/handleError.ts | 28 +++++++ packages/sveltekit/src/client/index.ts | 4 + .../sveltekit/test/client/handleError.test.ts | 82 +++++++++++++++++++ yarn.lock | 2 +- 5 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 packages/sveltekit/src/client/handleError.ts create mode 100644 packages/sveltekit/test/client/handleError.test.ts diff --git a/packages/sveltekit/package.json b/packages/sveltekit/package.json index f65603565285..a75e605fdd58 100644 --- a/packages/sveltekit/package.json +++ b/packages/sveltekit/package.json @@ -13,6 +13,18 @@ "module": "build/esm/index.server.js", "browser": "build/esm/index.client.js", "types": "build/types/index.types.d.ts", + "exports": { + "browser": { + "import": "./build/esm/index.client.js", + "require": "./build/cjs/index.client.js", + "default": "./build/esm/index.client.js" + }, + "node": { + "import": "./build/esm/index.server.js", + "require": "./build/cjs/index.server.js", + "default": "./build/esm/index.server.js" + } + }, "publishConfig": { "access": "public" }, @@ -28,7 +40,7 @@ "magic-string": "^0.30.0" }, "devDependencies": { - "@sveltejs/kit": "^1.5.0", + "@sveltejs/kit": "^1.11.0", "vite": "4.0.0", "typescript": "^4.9.3" }, diff --git a/packages/sveltekit/src/client/handleError.ts b/packages/sveltekit/src/client/handleError.ts new file mode 100644 index 000000000000..a4c20b895537 --- /dev/null +++ b/packages/sveltekit/src/client/handleError.ts @@ -0,0 +1,28 @@ +import { captureException } from '@sentry/svelte'; +import { addExceptionMechanism } from '@sentry/utils'; +// For now disable the import/no-unresolved rule, because we don't have a way to +// tell eslint that we are only importing types from the @sveltejs/kit package without +// adding a custom resolver, which will take too much time. +// eslint-disable-next-line import/no-unresolved +import type { HandleClientError, NavigationEvent } from '@sveltejs/kit'; + +/** + * Wrapper for the SvelteKit error handler that sends the error to Sentry. + * + * @param handleError The original SvelteKit error handler. + */ +export function wrapHandleError(handleError: HandleClientError): HandleClientError { + return (input: { error: unknown; event: NavigationEvent }): ReturnType => { + captureException(input.error, scope => { + scope.addEventProcessor(event => { + addExceptionMechanism(event, { + type: 'sveltekit', + handled: false, + }); + return event; + }); + return scope; + }); + return handleError(input); + }; +} diff --git a/packages/sveltekit/src/client/index.ts b/packages/sveltekit/src/client/index.ts index 92f3f38bcd88..e49e83c9ce96 100644 --- a/packages/sveltekit/src/client/index.ts +++ b/packages/sveltekit/src/client/index.ts @@ -1,3 +1,7 @@ export * from '@sentry/svelte'; export { init } from './sdk'; +export { wrapHandleError } from './handleError'; + +// Just here so that eslint is happy until we export more stuff here +export const PLACEHOLDER_CLIENT = 'PLACEHOLDER'; diff --git a/packages/sveltekit/test/client/handleError.test.ts b/packages/sveltekit/test/client/handleError.test.ts new file mode 100644 index 000000000000..a075ac611c1e --- /dev/null +++ b/packages/sveltekit/test/client/handleError.test.ts @@ -0,0 +1,82 @@ +import { Scope } from '@sentry/svelte'; +// For now disable the import/no-unresolved rule, because we don't have a way to +// tell eslint that we are only importing types from the @sveltejs/kit package without +// adding a custom resolver, which will take too much time. +// eslint-disable-next-line import/no-unresolved +import type { HandleClientError, NavigationEvent } from '@sveltejs/kit'; + +import { wrapHandleError } from '../../src/client/handleError'; + +const mockCaptureException = jest.fn(); +let mockScope = new Scope(); + +jest.mock('@sentry/svelte', () => { + const original = jest.requireActual('@sentry/core'); + return { + ...original, + captureException: (err: unknown, cb: (arg0: unknown) => unknown) => { + cb(mockScope); + mockCaptureException(err, cb); + return original.captureException(err, cb); + }, + }; +}); + +const mockAddExceptionMechanism = jest.fn(); + +jest.mock('@sentry/utils', () => { + const original = jest.requireActual('@sentry/utils'); + return { + ...original, + addExceptionMechanism: (...args: unknown[]) => mockAddExceptionMechanism(...args), + }; +}); + +function handleError(_input: { error: unknown; event: NavigationEvent }): ReturnType { + return { + message: 'Whoops!', + }; +} + +const navigationEvent: NavigationEvent = { + params: { + id: '123', + }, + route: { + id: 'users/[id]', + }, + url: new URL('http://example.org/users/123'), +}; + +describe('handleError', () => { + beforeEach(() => { + mockCaptureException.mockClear(); + mockAddExceptionMechanism.mockClear(); + mockScope = new Scope(); + }); + + it('calls captureException', async () => { + const wrappedHandleError = wrapHandleError(handleError); + const mockError = new Error('test'); + const returnVal = await wrappedHandleError({ error: mockError, event: navigationEvent }); + + expect(returnVal!.message).toEqual('Whoops!'); + expect(mockCaptureException).toHaveBeenCalledTimes(1); + expect(mockCaptureException).toHaveBeenCalledWith(mockError, expect.any(Function)); + }); + + it('adds an exception mechanism', async () => { + const addEventProcessorSpy = jest.spyOn(mockScope, 'addEventProcessor').mockImplementationOnce(callback => { + void callback({}, { event_id: 'fake-event-id' }); + return mockScope; + }); + + const wrappedHandleError = wrapHandleError(handleError); + const mockError = new Error('test'); + await wrappedHandleError({ error: mockError, event: navigationEvent }); + + expect(addEventProcessorSpy).toBeCalledTimes(1); + expect(mockAddExceptionMechanism).toBeCalledTimes(1); + expect(mockAddExceptionMechanism).toBeCalledWith({}, { handled: false, type: 'sveltekit' }); + }); +}); diff --git a/yarn.lock b/yarn.lock index 803d62922290..3502d7f6c2ac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4205,7 +4205,7 @@ dependencies: highlight.js "^9.15.6" -"@sveltejs/kit@^1.5.0": +"@sveltejs/kit@^1.11.0": version "1.11.0" resolved "https://registry.yarnpkg.com/@sveltejs/kit/-/kit-1.11.0.tgz#23f233c351e5956356ba6f3206e40637c5f5dbda" integrity sha512-PwViZcMoLgEU/jhLoSyjf5hSrHS67wvSm0ifBo4prP9irpGa5HuPOZeVDTL5tPDSBoKxtdYi1zlGdoiJfO86jA== From ed1347e7b1fc07e5e4eebd6c353dd50c6ab9297f Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Fri, 10 Mar 2023 15:42:55 +0100 Subject: [PATCH 05/16] feat(sveltekit): Add server-side `handleError` wrapper (#7411) --- packages/sveltekit/src/client/handleError.ts | 6 +- packages/sveltekit/src/client/index.ts | 2 +- packages/sveltekit/src/index.types.ts | 6 ++ packages/sveltekit/src/server/handleError.ts | 30 +++++++ packages/sveltekit/src/server/index.ts | 1 + .../sveltekit/test/client/handleError.test.ts | 16 +++- .../sveltekit/test/server/handleError.test.ts | 84 +++++++++++++++++++ 7 files changed, 139 insertions(+), 6 deletions(-) create mode 100644 packages/sveltekit/src/server/handleError.ts create mode 100644 packages/sveltekit/test/server/handleError.test.ts diff --git a/packages/sveltekit/src/client/handleError.ts b/packages/sveltekit/src/client/handleError.ts index a4c20b895537..5490a3e20980 100644 --- a/packages/sveltekit/src/client/handleError.ts +++ b/packages/sveltekit/src/client/handleError.ts @@ -11,7 +11,7 @@ import type { HandleClientError, NavigationEvent } from '@sveltejs/kit'; * * @param handleError The original SvelteKit error handler. */ -export function wrapHandleError(handleError: HandleClientError): HandleClientError { +export function handleErrorWithSentry(handleError?: HandleClientError): HandleClientError { return (input: { error: unknown; event: NavigationEvent }): ReturnType => { captureException(input.error, scope => { scope.addEventProcessor(event => { @@ -23,6 +23,8 @@ export function wrapHandleError(handleError: HandleClientError): HandleClientErr }); return scope; }); - return handleError(input); + if (handleError) { + return handleError(input); + } }; } diff --git a/packages/sveltekit/src/client/index.ts b/packages/sveltekit/src/client/index.ts index e49e83c9ce96..dc6ad3407264 100644 --- a/packages/sveltekit/src/client/index.ts +++ b/packages/sveltekit/src/client/index.ts @@ -1,7 +1,7 @@ export * from '@sentry/svelte'; export { init } from './sdk'; -export { wrapHandleError } from './handleError'; +export { handleErrorWithSentry } from './handleError'; // Just here so that eslint is happy until we export more stuff here export const PLACEHOLDER_CLIENT = 'PLACEHOLDER'; diff --git a/packages/sveltekit/src/index.types.ts b/packages/sveltekit/src/index.types.ts index d4c7a9e52c25..06ff806a8377 100644 --- a/packages/sveltekit/src/index.types.ts +++ b/packages/sveltekit/src/index.types.ts @@ -8,6 +8,8 @@ export * from './config'; export * from './server'; import type { Integration, Options, StackParser } from '@sentry/types'; +// eslint-disable-next-line import/no-unresolved +import type { HandleClientError, HandleServerError } from '@sveltejs/kit'; import type * as clientSdk from './client'; import type * as serverSdk from './server'; @@ -15,6 +17,10 @@ import type * as serverSdk from './server'; /** Initializes Sentry SvelteKit SDK */ export declare function init(options: Options | clientSdk.BrowserOptions | serverSdk.NodeOptions): void; +export declare function handleErrorWithSentry( + handleError: T, +): ReturnType; + // We export a merged Integrations object so that users can (at least typing-wise) use all integrations everywhere. export declare const Integrations: typeof clientSdk.Integrations & typeof serverSdk.Integrations; diff --git a/packages/sveltekit/src/server/handleError.ts b/packages/sveltekit/src/server/handleError.ts new file mode 100644 index 000000000000..449ca65e19ce --- /dev/null +++ b/packages/sveltekit/src/server/handleError.ts @@ -0,0 +1,30 @@ +import { captureException } from '@sentry/node'; +import { addExceptionMechanism } from '@sentry/utils'; +// For now disable the import/no-unresolved rule, because we don't have a way to +// tell eslint that we are only importing types from the @sveltejs/kit package without +// adding a custom resolver, which will take too much time. +// eslint-disable-next-line import/no-unresolved +import type { HandleServerError, RequestEvent } from '@sveltejs/kit'; + +/** + * Wrapper for the SvelteKit error handler that sends the error to Sentry. + * + * @param handleError The original SvelteKit error handler. + */ +export function handleErrorWithSentry(handleError?: HandleServerError): HandleServerError { + return (input: { error: unknown; event: RequestEvent }): ReturnType => { + captureException(input.error, scope => { + scope.addEventProcessor(event => { + addExceptionMechanism(event, { + type: 'sveltekit', + handled: false, + }); + return event; + }); + return scope; + }); + if (handleError) { + return handleError(input); + } + }; +} diff --git a/packages/sveltekit/src/server/index.ts b/packages/sveltekit/src/server/index.ts index 6ac8d97b4241..1e3aed034f1e 100644 --- a/packages/sveltekit/src/server/index.ts +++ b/packages/sveltekit/src/server/index.ts @@ -1,3 +1,4 @@ export * from '@sentry/node'; export { init } from './sdk'; +export { handleErrorWithSentry } from './handleError'; diff --git a/packages/sveltekit/test/client/handleError.test.ts b/packages/sveltekit/test/client/handleError.test.ts index a075ac611c1e..49bcb6a0c57f 100644 --- a/packages/sveltekit/test/client/handleError.test.ts +++ b/packages/sveltekit/test/client/handleError.test.ts @@ -5,7 +5,7 @@ import { Scope } from '@sentry/svelte'; // eslint-disable-next-line import/no-unresolved import type { HandleClientError, NavigationEvent } from '@sveltejs/kit'; -import { wrapHandleError } from '../../src/client/handleError'; +import { handleErrorWithSentry } from '../../src/client/handleError'; const mockCaptureException = jest.fn(); let mockScope = new Scope(); @@ -55,8 +55,18 @@ describe('handleError', () => { mockScope = new Scope(); }); + it('works when a handleError func is not provided', async () => { + const wrappedHandleError = handleErrorWithSentry(); + const mockError = new Error('test'); + const returnVal = await wrappedHandleError({ error: mockError, event: navigationEvent }); + + expect(returnVal).not.toBeDefined(); + expect(mockCaptureException).toHaveBeenCalledTimes(1); + expect(mockCaptureException).toHaveBeenCalledWith(mockError, expect.any(Function)); + }); + it('calls captureException', async () => { - const wrappedHandleError = wrapHandleError(handleError); + const wrappedHandleError = handleErrorWithSentry(handleError); const mockError = new Error('test'); const returnVal = await wrappedHandleError({ error: mockError, event: navigationEvent }); @@ -71,7 +81,7 @@ describe('handleError', () => { return mockScope; }); - const wrappedHandleError = wrapHandleError(handleError); + const wrappedHandleError = handleErrorWithSentry(handleError); const mockError = new Error('test'); await wrappedHandleError({ error: mockError, event: navigationEvent }); diff --git a/packages/sveltekit/test/server/handleError.test.ts b/packages/sveltekit/test/server/handleError.test.ts new file mode 100644 index 000000000000..41e587dfa94c --- /dev/null +++ b/packages/sveltekit/test/server/handleError.test.ts @@ -0,0 +1,84 @@ +import { Scope } from '@sentry/node'; +// For now disable the import/no-unresolved rule, because we don't have a way to +// tell eslint that we are only importing types from the @sveltejs/kit package without +// adding a custom resolver, which will take too much time. +// eslint-disable-next-line import/no-unresolved +import type { HandleServerError, RequestEvent } from '@sveltejs/kit'; + +import { handleErrorWithSentry } from '../../src/server/handleError'; + +const mockCaptureException = jest.fn(); +let mockScope = new Scope(); + +jest.mock('@sentry/node', () => { + const original = jest.requireActual('@sentry/core'); + return { + ...original, + captureException: (err: unknown, cb: (arg0: unknown) => unknown) => { + cb(mockScope); + mockCaptureException(err, cb); + return original.captureException(err, cb); + }, + }; +}); + +const mockAddExceptionMechanism = jest.fn(); + +jest.mock('@sentry/utils', () => { + const original = jest.requireActual('@sentry/utils'); + return { + ...original, + addExceptionMechanism: (...args: unknown[]) => mockAddExceptionMechanism(...args), + }; +}); + +function handleError(_input: { error: unknown; event: RequestEvent }): ReturnType { + return { + message: 'Whoops!', + }; +} + +const requestEvent = {} as RequestEvent; + +describe('handleError', () => { + beforeEach(() => { + mockCaptureException.mockClear(); + mockAddExceptionMechanism.mockClear(); + mockScope = new Scope(); + }); + + it('works when a handleError func is not provided', async () => { + const wrappedHandleError = handleErrorWithSentry(); + const mockError = new Error('test'); + const returnVal = await wrappedHandleError({ error: mockError, event: requestEvent }); + + expect(returnVal).not.toBeDefined(); + expect(mockCaptureException).toHaveBeenCalledTimes(1); + expect(mockCaptureException).toHaveBeenCalledWith(mockError, expect.any(Function)); + }); + + it('calls captureException', async () => { + const wrappedHandleError = handleErrorWithSentry(handleError); + const mockError = new Error('test'); + const returnVal = await wrappedHandleError({ error: mockError, event: requestEvent }); + + expect(returnVal!.message).toEqual('Whoops!'); + expect(mockCaptureException).toHaveBeenCalledTimes(1); + expect(mockCaptureException).toHaveBeenCalledWith(mockError, expect.any(Function)); + }); + + it('adds an exception mechanism', async () => { + const addEventProcessorSpy = jest.spyOn(mockScope, 'addEventProcessor').mockImplementationOnce(callback => { + void callback({}, { event_id: 'fake-event-id' }); + return mockScope; + }); + + const wrappedHandleError = handleErrorWithSentry(handleError); + const mockError = new Error('test'); + await wrappedHandleError({ error: mockError, event: requestEvent }); + + expect(addEventProcessorSpy).toBeCalledTimes(1); + expect(mockAddExceptionMechanism).toBeCalledTimes(1); + expect(mockAddExceptionMechanism).toBeCalledWith({}, { handled: false, type: 'sveltekit' }); + }); +}); From 63ef40e5f856e29b344bb577036c88e00d0264d5 Mon Sep 17 00:00:00 2001 From: Jonas Date: Fri, 10 Mar 2023 10:59:20 -0500 Subject: [PATCH 06/16] feat(types): add profilesSampler option to node client type (#7385) Thanks for the review @lforst! --- packages/node/src/types.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/node/src/types.ts b/packages/node/src/types.ts index 4d384bc54cf3..dd4713e97952 100644 --- a/packages/node/src/types.ts +++ b/packages/node/src/types.ts @@ -1,4 +1,4 @@ -import type { ClientOptions, Options, TracePropagationTargets } from '@sentry/types'; +import type { ClientOptions, Options, SamplingContext, TracePropagationTargets } from '@sentry/types'; import type { NodeTransportOptions } from './transports'; @@ -8,6 +8,20 @@ export interface BaseNodeOptions { */ profilesSampleRate?: number; + /** + * Function to compute profiling sample rate dynamically and filter unwanted profiles. + * + * Profiling is enabled if either this or `profilesSampleRate` is defined. If both are defined, `profilesSampleRate` is + * ignored. + * + * Will automatically be passed a context object of default and optional custom data. See + * {@link Transaction.samplingContext} and {@link Hub.startTransaction}. + * + * @returns A sample rate between 0 and 1 (0 drops the profile, 1 guarantees it will be sent). Returning `true` is + * equivalent to returning 1 and returning `false` is equivalent to returning 0. + */ + profilesSampler?: (samplingContext: SamplingContext) => number | boolean; + /** Sets an optional server name (device name) */ serverName?: string; From ebcbddab5ffced99ca8b4106a1b7e20ed92f4be0 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Mon, 13 Mar 2023 08:23:29 +0000 Subject: [PATCH 07/16] fix(nextjs): Don't crash build when auth token is missing --- packages/nextjs/src/config/webpack.ts | 33 ++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/packages/nextjs/src/config/webpack.ts b/packages/nextjs/src/config/webpack.ts index 7fd142fc8aa8..f98c5f00e9b7 100644 --- a/packages/nextjs/src/config/webpack.ts +++ b/packages/nextjs/src/config/webpack.ts @@ -626,7 +626,38 @@ export function getWebpackPluginOptions( checkWebpackPluginOverrides(defaultPluginOptions, userPluginOptions); - return { ...defaultPluginOptions, ...userPluginOptions }; + return { + ...defaultPluginOptions, + ...userPluginOptions, + errorHandler(err, invokeErr, compilation) { + // Hardcoded way to check for missing auth token until we have a better way of doing this. + if (err && err.message.includes('Authentication credentials were not provided.')) { + const warningPrefix = `${chalk.yellow('warn')} -`; + // eslint-disable-next-line no-console + console.warn( + `${warningPrefix} ${ + `${chalk.bold('No Sentry auth token configured.')} Source maps will not be uploaded.\n` + + 'You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/\n' + + `After generating a Sentry auth token, set it via the ${chalk.bold.cyan( + 'SENTRY_AUTH_TOKEN', + )} environment variable during the build.\n` + }${ + process.env.VERCEL + ? "If you're deploying to Vercel, use the Vercel integration: https://vercel.com/integrations/sentry\n" + : '' + }`, + ); + + return; + } + + if (userPluginOptions.errorHandler) { + return userPluginOptions.errorHandler(err, invokeErr, compilation); + } + + return invokeErr(); + }, + }; } /** Check various conditions to decide if we should run the plugin */ From c86e63c6c3b2f7eedad4abd7a668bc840ecfee65 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Mon, 13 Mar 2023 09:48:10 +0000 Subject: [PATCH 08/16] Don't show full warning on vercel --- packages/nextjs/src/config/webpack.ts | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/packages/nextjs/src/config/webpack.ts b/packages/nextjs/src/config/webpack.ts index f98c5f00e9b7..8c06feea6eed 100644 --- a/packages/nextjs/src/config/webpack.ts +++ b/packages/nextjs/src/config/webpack.ts @@ -633,19 +633,26 @@ export function getWebpackPluginOptions( // Hardcoded way to check for missing auth token until we have a better way of doing this. if (err && err.message.includes('Authentication credentials were not provided.')) { const warningPrefix = `${chalk.yellow('warn')} -`; - // eslint-disable-next-line no-console - console.warn( - `${warningPrefix} ${ - `${chalk.bold('No Sentry auth token configured.')} Source maps will not be uploaded.\n` + + + let msg; + + if (process.env.VERCEL) { + msg = `To fix this, use Sentry's Vercel integration to automatically set the ${chalk.bold.cyan( + 'SENTRY_AUTH_TOKEN', + )} environment variable: https://vercel.com/integrations/sentry`; + } else { + msg = 'You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/\n' + `After generating a Sentry auth token, set it via the ${chalk.bold.cyan( 'SENTRY_AUTH_TOKEN', - )} environment variable during the build.\n` - }${ - process.env.VERCEL - ? "If you're deploying to Vercel, use the Vercel integration: https://vercel.com/integrations/sentry\n" - : '' - }`, + )} environment variable during the build.`; + } + + // eslint-disable-next-line no-console + console.warn( + `${warningPrefix} ${chalk.bold( + 'No Sentry auth token configured.', + )} Source maps will not be uploaded.\n${msg}\n`, ); return; From 9c2971b3a17d2a96b732743b3d81f0746d35a172 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Mon, 13 Mar 2023 10:49:47 +0100 Subject: [PATCH 09/16] chore(vscode): Add eslint working directories (#7428) --- .vscode/settings.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index 30ea95c5e635..d3c8a08448c6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -23,4 +23,10 @@ "yaml.schemas": { "https://json.schemastore.org/github-workflow.json": ".github/workflows/**.yml" }, + "eslint.packageManager": "yarn", + "eslint.workingDirectories": [ + { + "mode": "auto" + } + ] } From 045d11b73f422763fd533c14b694fcff12eccccb Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Mon, 13 Mar 2023 10:33:26 +0000 Subject: [PATCH 10/16] make error --- packages/nextjs/src/config/webpack.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nextjs/src/config/webpack.ts b/packages/nextjs/src/config/webpack.ts index 8c06feea6eed..a606af5f339c 100644 --- a/packages/nextjs/src/config/webpack.ts +++ b/packages/nextjs/src/config/webpack.ts @@ -649,7 +649,7 @@ export function getWebpackPluginOptions( } // eslint-disable-next-line no-console - console.warn( + console.error( `${warningPrefix} ${chalk.bold( 'No Sentry auth token configured.', )} Source maps will not be uploaded.\n${msg}\n`, From fe1231a02dda7c1469a245be967535b195b76080 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Mon, 13 Mar 2023 11:48:10 +0100 Subject: [PATCH 11/16] fix(node): Revert to dynamic `require` call to fix monkey patching (#7430) Partially revert #7377 which caused monkey patching errors when patching the native `http` and `https` modules in the Node SDK (#7425). Similarly, also our Serverless SDK was subjected to the same problem (#7421). The problem is that `import` doesn't permit monkey patching of the imported (`http(s)`) module, producing this error: ```bash TypeError: Cannot assign to read only property 'get' of object '[object Module]' ``` I tried using a dynamic import instead but got the same result. So it seems like we can only use `require` here :( --- packages/node/src/integrations/http.ts | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/packages/node/src/integrations/http.ts b/packages/node/src/integrations/http.ts index 017929fbe0eb..ea7a9eae173d 100644 --- a/packages/node/src/integrations/http.ts +++ b/packages/node/src/integrations/http.ts @@ -8,8 +8,8 @@ import { parseSemver, stringMatchesSomePattern, } from '@sentry/utils'; -import * as http from 'http'; -import * as https from 'https'; +import type * as http from 'http'; +import type * as https from 'https'; import { LRUMap } from 'lru_map'; import type { NodeClient } from '../client'; @@ -101,17 +101,25 @@ export class Http implements Integration { // and we will no longer have to do this optional merge, we can just pass `this._tracing` directly. const tracingOptions = this._tracing ? { ...clientOptions, ...this._tracing } : undefined; - const wrappedHttpHandlerMaker = _createWrappedRequestMethodFactory(this._breadcrumbs, tracingOptions, http); - fill(http, 'get', wrappedHttpHandlerMaker); - fill(http, 'request', wrappedHttpHandlerMaker); + // eslint-disable-next-line @typescript-eslint/no-var-requires + const httpModule = require('http'); + const wrappedHttpHandlerMaker = _createWrappedRequestMethodFactory(this._breadcrumbs, tracingOptions, httpModule); + fill(httpModule, 'get', wrappedHttpHandlerMaker); + fill(httpModule, 'request', wrappedHttpHandlerMaker); // NOTE: Prior to Node 9, `https` used internals of `http` module, thus we don't patch it. // If we do, we'd get double breadcrumbs and double spans for `https` calls. // It has been changed in Node 9, so for all versions equal and above, we patch `https` separately. if (NODE_VERSION.major && NODE_VERSION.major > 8) { - const wrappedHttpsHandlerMaker = _createWrappedRequestMethodFactory(this._breadcrumbs, tracingOptions, https); - fill(https, 'get', wrappedHttpsHandlerMaker); - fill(https, 'request', wrappedHttpsHandlerMaker); + // eslint-disable-next-line @typescript-eslint/no-var-requires + const httpsModule = require('https'); + const wrappedHttpsHandlerMaker = _createWrappedRequestMethodFactory( + this._breadcrumbs, + tracingOptions, + httpsModule, + ); + fill(httpsModule, 'get', wrappedHttpsHandlerMaker); + fill(httpsModule, 'request', wrappedHttpsHandlerMaker); } } } From 161b8f0084574d941491dfd48ef655d607b3b1e5 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Mon, 13 Mar 2023 13:25:36 +0100 Subject: [PATCH 12/16] fix(types): Fix node types & add E2E test (#7429) --- .../browser/src/integrations/breadcrumbs.ts | 4 +- packages/e2e-tests/README.md | 4 + .../node-express-app/.gitignore | 1 + .../test-applications/node-express-app/.npmrc | 2 + .../node-express-app/package.json | 23 + .../node-express-app/playwright.config.ts | 67 +++ .../node-express-app/src/app.ts | 73 +++ .../node-express-app/test-recipe.json | 12 + .../node-express-app/tests/server.test.ts | 77 +++ .../node-express-app/tsconfig.json | 10 + .../node-express-app/yarn.lock | 553 ++++++++++++++++++ packages/types/.eslintrc.js | 6 + packages/types/src/instrument.ts | 10 +- packages/types/tsconfig.json | 2 +- packages/utils/src/instrument.ts | 4 +- 15 files changed, 840 insertions(+), 8 deletions(-) create mode 100644 packages/e2e-tests/test-applications/node-express-app/.gitignore create mode 100644 packages/e2e-tests/test-applications/node-express-app/.npmrc create mode 100644 packages/e2e-tests/test-applications/node-express-app/package.json create mode 100644 packages/e2e-tests/test-applications/node-express-app/playwright.config.ts create mode 100644 packages/e2e-tests/test-applications/node-express-app/src/app.ts create mode 100644 packages/e2e-tests/test-applications/node-express-app/test-recipe.json create mode 100644 packages/e2e-tests/test-applications/node-express-app/tests/server.test.ts create mode 100644 packages/e2e-tests/test-applications/node-express-app/tsconfig.json create mode 100644 packages/e2e-tests/test-applications/node-express-app/yarn.lock diff --git a/packages/browser/src/integrations/breadcrumbs.ts b/packages/browser/src/integrations/breadcrumbs.ts index d48c2d1efbd9..dd381f1f3549 100644 --- a/packages/browser/src/integrations/breadcrumbs.ts +++ b/packages/browser/src/integrations/breadcrumbs.ts @@ -216,7 +216,7 @@ function _consoleBreadcrumb(handlerData: HandlerData & { args: unknown[]; level: /** * Creates breadcrumbs from XHR API calls */ -function _xhrBreadcrumb(handlerData: HandlerData & { xhr: SentryWrappedXMLHttpRequest }): void { +function _xhrBreadcrumb(handlerData: HandlerData & { xhr: XMLHttpRequest & SentryWrappedXMLHttpRequest }): void { if (handlerData.endTimestamp) { // We only capture complete, non-sentry requests if (handlerData.xhr.__sentry_own_request__) { @@ -248,7 +248,7 @@ function _xhrBreadcrumb(handlerData: HandlerData & { xhr: SentryWrappedXMLHttpRe /** * Creates breadcrumbs from fetch API calls */ -function _fetchBreadcrumb(handlerData: HandlerData & HandlerDataFetch): void { +function _fetchBreadcrumb(handlerData: HandlerData & HandlerDataFetch & { response?: Response }): void { // We only capture complete fetch requests if (!handlerData.endTimestamp) { return; diff --git a/packages/e2e-tests/README.md b/packages/e2e-tests/README.md index 51b1f118f658..2a30943f52f6 100644 --- a/packages/e2e-tests/README.md +++ b/packages/e2e-tests/README.md @@ -7,6 +7,10 @@ current state. Prerequisites: Docker +- Copy `.env.example` to `.env` +- Fill in auth information in `.env` for an example Sentry project + - The `E2E_TEST_AUTH_TOKEN` must have all the default permissions + ```bash yarn test:e2e ``` diff --git a/packages/e2e-tests/test-applications/node-express-app/.gitignore b/packages/e2e-tests/test-applications/node-express-app/.gitignore new file mode 100644 index 000000000000..1521c8b7652b --- /dev/null +++ b/packages/e2e-tests/test-applications/node-express-app/.gitignore @@ -0,0 +1 @@ +dist diff --git a/packages/e2e-tests/test-applications/node-express-app/.npmrc b/packages/e2e-tests/test-applications/node-express-app/.npmrc new file mode 100644 index 000000000000..c6b3ef9b3eaa --- /dev/null +++ b/packages/e2e-tests/test-applications/node-express-app/.npmrc @@ -0,0 +1,2 @@ +@sentry:registry=http://localhost:4873 +@sentry-internal:registry=http://localhost:4873 diff --git a/packages/e2e-tests/test-applications/node-express-app/package.json b/packages/e2e-tests/test-applications/node-express-app/package.json new file mode 100644 index 000000000000..4c07eb8a1028 --- /dev/null +++ b/packages/e2e-tests/test-applications/node-express-app/package.json @@ -0,0 +1,23 @@ +{ + "name": "node-express-app", + "version": "1.0.0", + "private": true, + "scripts": { + "build": "yarn tsc", + "start": "node dist/app.js", + "test": "yarn playwright test" + }, + "dependencies": { + "@sentry/integrations": "*", + "@sentry/node": "*", + "@sentry/tracing": "*", + "@sentry/types": "*", + "express": "4.18.2", + "@types/express": "4.17.17", + "@types/node": "18.15.1", + "typescript": "4.9.5" + }, + "devDependencies": { + "@playwright/test": "^1.27.1" + } +} diff --git a/packages/e2e-tests/test-applications/node-express-app/playwright.config.ts b/packages/e2e-tests/test-applications/node-express-app/playwright.config.ts new file mode 100644 index 000000000000..d665bba2d978 --- /dev/null +++ b/packages/e2e-tests/test-applications/node-express-app/playwright.config.ts @@ -0,0 +1,67 @@ +import type { PlaywrightTestConfig } from '@playwright/test'; +import { devices } from '@playwright/test'; + +/** + * See https://playwright.dev/docs/test-configuration. + */ +const config: PlaywrightTestConfig = { + testDir: './tests', + /* Maximum time one test can run for. */ + timeout: 60 * 1000, + expect: { + /** + * Maximum time expect() should wait for the condition to be met. + * For example in `await expect(locator).toHaveText();` + */ + timeout: 5000, + }, + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: 0, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'list', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ + actionTimeout: 0, + /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://localhost:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { + ...devices['Desktop Chrome'], + }, + }, + // For now we only test Chrome! + // { + // name: 'firefox', + // use: { + // ...devices['Desktop Firefox'], + // }, + // }, + // { + // name: 'webkit', + // use: { + // ...devices['Desktop Safari'], + // }, + // }, + ], + + /* Run your local dev server before starting the tests */ + webServer: { + command: 'yarn start', + port: 3000, + }, +}; + +export default config; diff --git a/packages/e2e-tests/test-applications/node-express-app/src/app.ts b/packages/e2e-tests/test-applications/node-express-app/src/app.ts new file mode 100644 index 000000000000..e128e6c33882 --- /dev/null +++ b/packages/e2e-tests/test-applications/node-express-app/src/app.ts @@ -0,0 +1,73 @@ +import * as Sentry from '@sentry/node'; +import '@sentry/tracing'; +import * as Integrations from '@sentry/integrations'; +import express from 'express'; + +declare global { + namespace globalThis { + var transactionIds: string[]; + } +} + +Sentry.init({ + dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN, + integrations: [new Integrations.HttpClient()], + debug: true, + tracesSampleRate: 1, +}); + +const app = express(); +const port = 3000; + +app.use(Sentry.Handlers.requestHandler()); +app.use(Sentry.Handlers.tracingHandler()); + +app.get('/test-success', function (req, res) { + res.send({ version: 'v1' }); +}); + +app.get('/test-param/:param', function (req, res) { + res.send({ paramWas: req.params.param }); +}); + +app.get('/test-transaction', async function (req, res) { + const transaction = Sentry.startTransaction({ name: 'test-transaction', op: 'e2e-test' }); + Sentry.getCurrentHub().configureScope(scope => scope.setSpan(transaction)); + + const span = transaction.startChild(); + + span.finish(); + transaction.finish(); + + await Sentry.flush(); + + res.send({ + transactionIds: global.transactionIds || [], + }); +}); + +app.get('/test-error', async function (req, res) { + const exceptionId = Sentry.captureException(new Error('This is an error')); + + await Sentry.flush(2000); + + res.send({ exceptionId }); +}); + +app.listen(port, () => { + console.log(`Example app listening on port ${port}`); +}); + +Sentry.addGlobalEventProcessor(event => { + global.transactionIds = global.transactionIds || []; + + if (event.type === 'transaction') { + const eventId = event.event_id; + + if (eventId) { + global.transactionIds.push(eventId); + } + } + + return event; +}); diff --git a/packages/e2e-tests/test-applications/node-express-app/test-recipe.json b/packages/e2e-tests/test-applications/node-express-app/test-recipe.json new file mode 100644 index 000000000000..e3038c8d0459 --- /dev/null +++ b/packages/e2e-tests/test-applications/node-express-app/test-recipe.json @@ -0,0 +1,12 @@ +{ + "$schema": "../../test-recipe-schema.json", + "testApplicationName": "Node Express App", + "buildCommand": "yarn install && yarn build", + "tests": [ + { + "testName": "Test express server", + "testCommand": "yarn test", + "timeoutSeconds": 60 + } + ] +} diff --git a/packages/e2e-tests/test-applications/node-express-app/tests/server.test.ts b/packages/e2e-tests/test-applications/node-express-app/tests/server.test.ts new file mode 100644 index 000000000000..654827cb8e03 --- /dev/null +++ b/packages/e2e-tests/test-applications/node-express-app/tests/server.test.ts @@ -0,0 +1,77 @@ +import { test, expect } from '@playwright/test'; +import axios, { AxiosError } from 'axios'; + +const authToken = process.env.E2E_TEST_AUTH_TOKEN; +const sentryTestOrgSlug = process.env.E2E_TEST_SENTRY_ORG_SLUG; +const sentryTestProject = process.env.E2E_TEST_SENTRY_TEST_PROJECT; +const EVENT_POLLING_TIMEOUT = 30_000; + +test('Sends exception to Sentry', async ({ baseURL }) => { + const { data } = await axios.get(`${baseURL}/test-error`); + const { exceptionId } = data; + + const url = `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${exceptionId}/`; + + console.log(`Polling for error eventId: ${exceptionId}`); + + await expect + .poll( + async () => { + try { + const response = await axios.get(url, { headers: { Authorization: `Bearer ${authToken}` } }); + + return response.status; + } catch (e) { + if (e instanceof AxiosError && e.response) { + if (e.response.status !== 404) { + throw e; + } else { + return e.response.status; + } + } else { + throw e; + } + } + }, + { timeout: EVENT_POLLING_TIMEOUT }, + ) + .toBe(200); +}); + +test('Sends transactions to Sentry', async ({ baseURL }) => { + const { data } = await axios.get(`${baseURL}/test-transaction`); + const { transactionIds } = data; + + console.log(`Polling for transaction eventIds: ${JSON.stringify(transactionIds)}`); + + expect(transactionIds.length).toBeGreaterThan(0); + + await Promise.all( + transactionIds.map(async (transactionId: string) => { + const url = `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionId}/`; + + await expect + .poll( + async () => { + try { + const response = await axios.get(url, { headers: { Authorization: `Bearer ${authToken}` } }); + + return response.status; + } catch (e) { + if (e instanceof AxiosError && e.response) { + if (e.response.status !== 404) { + throw e; + } else { + return e.response.status; + } + } else { + throw e; + } + } + }, + { timeout: EVENT_POLLING_TIMEOUT }, + ) + .toBe(200); + }), + ); +}); diff --git a/packages/e2e-tests/test-applications/node-express-app/tsconfig.json b/packages/e2e-tests/test-applications/node-express-app/tsconfig.json new file mode 100644 index 000000000000..decfcf7a0879 --- /dev/null +++ b/packages/e2e-tests/test-applications/node-express-app/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "types": ["node"], + "esModuleInterop": true, + "lib": ["ES6"], + "strict": true, + "outDir": "dist" + }, + "include": ["src/**/*.ts"] +} diff --git a/packages/e2e-tests/test-applications/node-express-app/yarn.lock b/packages/e2e-tests/test-applications/node-express-app/yarn.lock new file mode 100644 index 000000000000..45187aacd4e2 --- /dev/null +++ b/packages/e2e-tests/test-applications/node-express-app/yarn.lock @@ -0,0 +1,553 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@playwright/test@^1.27.1": + version "1.31.2" + resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.31.2.tgz#426d8545143a97a6fed250a2a27aa1c8e5e2548e" + integrity sha512-BYVutxDI4JeZKV1+ups6dt5WiqKhjBtIYowyZIJ3kBDmJgsuPKsqqKNIMFbUePLSCmp2cZu+BDL427RcNKTRYw== + dependencies: + "@types/node" "*" + playwright-core "1.31.2" + optionalDependencies: + fsevents "2.3.2" + +"@types/body-parser@*": + version "1.19.2" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" + integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.35" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" + integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== + dependencies: + "@types/node" "*" + +"@types/express-serve-static-core@^4.17.33": + version "4.17.33" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.33.tgz#de35d30a9d637dc1450ad18dd583d75d5733d543" + integrity sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + +"@types/express@4.17.17": + version "4.17.17" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.17.tgz#01d5437f6ef9cfa8668e616e13c2f2ac9a491ae4" + integrity sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.33" + "@types/qs" "*" + "@types/serve-static" "*" + +"@types/mime@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" + integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA== + +"@types/node@*", "@types/node@18.15.1": + version "18.15.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.1.tgz#41dc2bf78e8085a250d4670d95edb7fba621dd29" + integrity sha512-U2TWca8AeHSmbpi314QBESRk7oPjSZjDsR+c+H4ECC1l+kFgpZf8Ydhv3SJpPy51VyZHHqxlb6mTTqYNNRVAIw== + +"@types/qs@*": + version "6.9.7" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" + integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== + +"@types/range-parser@*": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" + integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== + +"@types/serve-static@*": + version "1.15.1" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.1.tgz#86b1753f0be4f9a1bee68d459fcda5be4ea52b5d" + integrity sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ== + dependencies: + "@types/mime" "*" + "@types/node" "*" + +accepts@~1.3.8: + version "1.3.8" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== + +body-parser@1.20.1: + version "1.20.1" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" + integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw== + dependencies: + bytes "3.1.2" + content-type "~1.0.4" + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + http-errors "2.0.0" + iconv-lite "0.4.24" + on-finished "2.4.1" + qs "6.11.0" + raw-body "2.5.1" + type-is "~1.6.18" + unpipe "1.0.0" + +bytes@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + +call-bind@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + +content-disposition@0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== + dependencies: + safe-buffer "5.2.1" + +content-type@~1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" + integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== + +cookie@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" + integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== + +cookie@^0.4.1: + version "0.4.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" + integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== + +debug@2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +depd@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +destroy@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" + integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== + +express@4.18.2: + version "4.18.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" + integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ== + dependencies: + accepts "~1.3.8" + array-flatten "1.1.1" + body-parser "1.20.1" + content-disposition "0.5.4" + content-type "~1.0.4" + cookie "0.5.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "2.0.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "1.2.0" + fresh "0.5.2" + http-errors "2.0.0" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "2.4.1" + parseurl "~1.3.3" + path-to-regexp "0.1.7" + proxy-addr "~2.0.7" + qs "6.11.0" + range-parser "~1.2.1" + safe-buffer "5.2.1" + send "0.18.0" + serve-static "1.15.0" + setprototypeof "1.2.0" + statuses "2.0.1" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + +finalhandler@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" + integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "2.4.1" + parseurl "~1.3.3" + statuses "2.0.1" + unpipe "~1.0.0" + +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== + +fsevents@2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +get-intrinsic@^1.0.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.0.tgz#7ad1dc0535f3a2904bba075772763e5051f6d05f" + integrity sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.3" + +has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +http-errors@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" + integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== + dependencies: + depd "2.0.0" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses "2.0.1" + toidentifier "1.0.1" + +https-proxy-agent@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +immediate@~3.0.5: + version "3.0.6" + resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" + integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ== + +inherits@2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +lie@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e" + integrity sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw== + dependencies: + immediate "~3.0.5" + +localforage@^1.8.1: + version "1.10.0" + resolved "https://registry.yarnpkg.com/localforage/-/localforage-1.10.0.tgz#5c465dc5f62b2807c3a84c0c6a1b1b3212781dd4" + integrity sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg== + dependencies: + lie "3.1.1" + +lru_map@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/lru_map/-/lru_map-0.3.3.tgz#b5c8351b9464cbd750335a79650a0ec0e56118dd" + integrity sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ== + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== + +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== + +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@~2.1.24, mime-types@~2.1.34: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +ms@2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +negotiator@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + +object-inspect@^1.9.0: + version "1.12.3" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" + integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== + +on-finished@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + +parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== + +playwright-core@1.31.2: + version "1.31.2" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.31.2.tgz#debf4b215d14cb619adb7e511c164d068075b2ed" + integrity sha512-a1dFgCNQw4vCsG7bnojZjDnPewZcw7tZUNFN0ZkcLYKj+mPmXvg4MpaaKZ5SgqPsOmqIf2YsVRkgqiRDxD+fDQ== + +proxy-addr@~2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + dependencies: + forwarded "0.2.0" + ipaddr.js "1.9.1" + +qs@6.11.0: + version "6.11.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" + integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== + dependencies: + side-channel "^1.0.4" + +range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857" + integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== + dependencies: + bytes "3.1.2" + http-errors "2.0.0" + iconv-lite "0.4.24" + unpipe "1.0.0" + +safe-buffer@5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +send@0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" + integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== + dependencies: + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "2.0.0" + mime "1.6.0" + ms "2.1.3" + on-finished "2.4.1" + range-parser "~1.2.1" + statuses "2.0.1" + +serve-static@1.15.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" + integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.18.0" + +setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + +statuses@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" + integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== + +toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + +tslib@^1.9.3: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +typescript@4.9.5: + version "4.9.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" + integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== + +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== diff --git a/packages/types/.eslintrc.js b/packages/types/.eslintrc.js index da4b0f29f027..8249a539d7be 100644 --- a/packages/types/.eslintrc.js +++ b/packages/types/.eslintrc.js @@ -1,5 +1,11 @@ +/* eslint-env node */ + module.exports = { extends: ['../../.eslintrc.js'], + env: { + node: false, + browser: false, + }, rules: { '@typescript-eslint/no-explicit-any': 'off', }, diff --git a/packages/types/src/instrument.ts b/packages/types/src/instrument.ts index 85045217c45b..102376aa0a8b 100644 --- a/packages/types/src/instrument.ts +++ b/packages/types/src/instrument.ts @@ -1,6 +1,9 @@ -type XHRSendInput = null | Blob | BufferSource | FormData | URLSearchParams | string; +// This should be: null | Blob | BufferSource | FormData | URLSearchParams | string +// But since not all of those are available in node, we just export `unknown` here for now +// Make sure to cast it where needed! +type XHRSendInput = unknown; -export interface SentryWrappedXMLHttpRequest extends XMLHttpRequest { +export interface SentryWrappedXMLHttpRequest { [key: string]: any; __sentry_xhr__?: { method?: string; @@ -19,5 +22,6 @@ export interface HandlerDataFetch { args: any[]; fetchData: SentryFetchData; startTimestamp: number; - response?: Response; + // This is actually `Response`, make sure to cast this where needed (not available in Node) + response?: unknown; } diff --git a/packages/types/tsconfig.json b/packages/types/tsconfig.json index bf45a09f2d71..9cbd605aef9a 100644 --- a/packages/types/tsconfig.json +++ b/packages/types/tsconfig.json @@ -4,6 +4,6 @@ "include": ["src/**/*"], "compilerOptions": { - // package-specific options + "lib": ["ES6"] } } diff --git a/packages/utils/src/instrument.ts b/packages/utils/src/instrument.ts index 68ee52789de9..6adeb72d64c0 100644 --- a/packages/utils/src/instrument.ts +++ b/packages/utils/src/instrument.ts @@ -208,7 +208,7 @@ function instrumentXHR(): void { const xhrproto = XMLHttpRequest.prototype; fill(xhrproto, 'open', function (originalOpen: () => void): () => void { - return function (this: SentryWrappedXMLHttpRequest, ...args: any[]): void { + return function (this: XMLHttpRequest & SentryWrappedXMLHttpRequest, ...args: any[]): void { // eslint-disable-next-line @typescript-eslint/no-this-alias const xhr = this; const url = args[1]; @@ -259,7 +259,7 @@ function instrumentXHR(): void { }); fill(xhrproto, 'send', function (originalSend: () => void): () => void { - return function (this: SentryWrappedXMLHttpRequest, ...args: any[]): void { + return function (this: XMLHttpRequest & SentryWrappedXMLHttpRequest, ...args: any[]): void { if (this.__sentry_xhr__ && args[0] !== undefined) { this.__sentry_xhr__.body = args[0]; } From 3376a159ede1466d5ea660268f9f0901ae277c9a Mon Sep 17 00:00:00 2001 From: Jonas Date: Mon, 13 Mar 2023 08:57:48 -0400 Subject: [PATCH 13/16] ref(utils): test for webpack error before running replace (#7412) --- packages/utils/src/stacktrace.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/utils/src/stacktrace.ts b/packages/utils/src/stacktrace.ts index ccf18c64e897..a5975f7a23b3 100644 --- a/packages/utils/src/stacktrace.ts +++ b/packages/utils/src/stacktrace.ts @@ -1,6 +1,8 @@ import type { StackFrame, StackLineParser, StackLineParserFn, StackParser } from '@sentry/types'; const STACKTRACE_LIMIT = 50; +// Used to sanitize webpack (error: *) wrapped stack errors +const WEBPACK_ERROR_REGEXP = /\(error: (.*)\)/; /** * Creates a stack parser with the supplied line parsers @@ -25,7 +27,7 @@ export function createStackParser(...parsers: StackLineParser[]): StackParser { // https://github.com/getsentry/sentry-javascript/issues/5459 // Remove webpack (error: *) wrappers - const cleanedLine = line.replace(/\(error: (.*)\)/, '$1'); + const cleanedLine = WEBPACK_ERROR_REGEXP.test(line) ? line.replace(WEBPACK_ERROR_REGEXP, '$1') : line; for (const parser of sortedParsers) { const frame = parser(cleanedLine); From 5e27e8fb33bed24cdd90511743402e49e5529fd4 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Mon, 13 Mar 2023 14:02:07 +0100 Subject: [PATCH 14/16] fix(nextjs): Add better error messages for missing params during next build (#7434) --- packages/nextjs/src/config/webpack.ts | 96 +++++++++++++++++++++------ 1 file changed, 74 insertions(+), 22 deletions(-) diff --git a/packages/nextjs/src/config/webpack.ts b/packages/nextjs/src/config/webpack.ts index a606af5f339c..6092ee2ba3a2 100644 --- a/packages/nextjs/src/config/webpack.ts +++ b/packages/nextjs/src/config/webpack.ts @@ -630,32 +630,80 @@ export function getWebpackPluginOptions( ...defaultPluginOptions, ...userPluginOptions, errorHandler(err, invokeErr, compilation) { - // Hardcoded way to check for missing auth token until we have a better way of doing this. - if (err && err.message.includes('Authentication credentials were not provided.')) { - const warningPrefix = `${chalk.yellow('warn')} -`; - - let msg; - - if (process.env.VERCEL) { - msg = `To fix this, use Sentry's Vercel integration to automatically set the ${chalk.bold.cyan( - 'SENTRY_AUTH_TOKEN', - )} environment variable: https://vercel.com/integrations/sentry`; - } else { - msg = - 'You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/\n' + - `After generating a Sentry auth token, set it via the ${chalk.bold.cyan( + if (err) { + const errorMessagePrefix = `${chalk.red('error')} -`; + + // Hardcoded way to check for missing auth token until we have a better way of doing this. + if (err.message.includes('Authentication credentials were not provided.')) { + let msg; + + if (process.env.VERCEL) { + msg = `To fix this, use Sentry's Vercel integration to automatically set the ${chalk.bold.cyan( 'SENTRY_AUTH_TOKEN', - )} environment variable during the build.`; + )} environment variable: https://vercel.com/integrations/sentry`; + } else { + msg = + 'You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/\n' + + `After generating a Sentry auth token, set it via the ${chalk.bold.cyan( + 'SENTRY_AUTH_TOKEN', + )} environment variable during the build.`; + } + + // eslint-disable-next-line no-console + console.error( + `${errorMessagePrefix} ${chalk.bold( + 'No Sentry auth token configured.', + )} Source maps will not be uploaded.\n${msg}\n`, + ); + + return; } - // eslint-disable-next-line no-console - console.error( - `${warningPrefix} ${chalk.bold( - 'No Sentry auth token configured.', - )} Source maps will not be uploaded.\n${msg}\n`, - ); + // Hardcoded way to check for missing org slug until we have a better way of doing this. + if (err.message.includes('An organization slug is required')) { + let msg; + if (process.env.VERCEL) { + msg = `To fix this, use Sentry's Vercel integration to automatically set the ${chalk.bold.cyan( + 'SENTRY_ORG', + )} environment variable: https://vercel.com/integrations/sentry`; + } else { + msg = `To fix this, set the ${chalk.bold.cyan( + 'SENTRY_ORG', + )} environment variable to the to your organization slug during the build.`; + } + + // eslint-disable-next-line no-console + console.error( + `${errorMessagePrefix} ${chalk.bold( + 'No Sentry organization slug configured.', + )} Source maps will not be uploaded.\n${msg}\n`, + ); + + return; + } - return; + // Hardcoded way to check for missing project slug until we have a better way of doing this. + if (err.message.includes('A project slug is required')) { + let msg; + if (process.env.VERCEL) { + msg = `To fix this, use Sentry's Vercel integration to automatically set the ${chalk.bold.cyan( + 'SENTRY_PROJECT', + )} environment variable: https://vercel.com/integrations/sentry`; + } else { + msg = `To fix this, set the ${chalk.bold.cyan( + 'SENTRY_PROJECT', + )} environment variable to the name of your Sentry project during the build.`; + } + + // eslint-disable-next-line no-console + console.error( + `${errorMessagePrefix} ${chalk.bold( + 'No Sentry project slug configured.', + )} Source maps will not be uploaded.\n${msg}\n`, + ); + + return; + } } if (userPluginOptions.errorHandler) { @@ -679,6 +727,10 @@ function shouldEnableWebpackPlugin(buildContext: BuildContext, userSentryOptions // with the `--ignore-scripts` option, this will be blocked and the missing binary will cause an error when users // try to build their apps. if (!SentryWebpackPlugin.cliBinaryExists()) { + // eslint-disable-next-line no-console + console.error( + `${chalk.red('error')} - ${chalk.bold('Sentry CLI binary not found.')} Source maps will not be uploaded.\n`, + ); return false; } From e358e16af3dbb74fae3132106073de3b78eda0b7 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Mon, 13 Mar 2023 14:18:50 +0100 Subject: [PATCH 15/16] feat(nextjs): Run source map upload in Vercel develop and preview environments (#7436) --- packages/nextjs/src/config/webpack.ts | 4 --- .../webpack/sentryWebpackPlugin.test.ts | 34 ------------------- 2 files changed, 38 deletions(-) diff --git a/packages/nextjs/src/config/webpack.ts b/packages/nextjs/src/config/webpack.ts index 6092ee2ba3a2..f4e2ce71de29 100644 --- a/packages/nextjs/src/config/webpack.ts +++ b/packages/nextjs/src/config/webpack.ts @@ -753,10 +753,6 @@ function shouldEnableWebpackPlugin(buildContext: BuildContext, userSentryOptions // return false } - if (process.env.VERCEL_ENV === 'preview' || process.env.VERCEL_ENV === 'development') { - return false; - } - // We've passed all of the tests! return true; } diff --git a/packages/nextjs/test/config/webpack/sentryWebpackPlugin.test.ts b/packages/nextjs/test/config/webpack/sentryWebpackPlugin.test.ts index fce1b852395e..bc6b8dd458a1 100644 --- a/packages/nextjs/test/config/webpack/sentryWebpackPlugin.test.ts +++ b/packages/nextjs/test/config/webpack/sentryWebpackPlugin.test.ts @@ -240,40 +240,6 @@ describe('Sentry webpack plugin config', () => { false, true, ], - [ - 'disables the plugin in Vercel `preview` environment', - exportedNextConfig, - { VERCEL_ENV: 'preview' }, - false, - false, - ], - [ - 'disables the plugin in Vercel `development` environment', - exportedNextConfig, - { VERCEL_ENV: 'development' }, - false, - false, - ], - [ - 'allows `disableClientWebpackPlugin = false` to override env vars`', - { - ...exportedNextConfig, - sentry: { disableClientWebpackPlugin: false }, - }, - { VERCEL_ENV: 'preview' }, - false, - true, - ], - [ - 'allows `disableServerWebpackPlugin = false` to override env vars`', - { - ...exportedNextConfig, - sentry: { disableServerWebpackPlugin: false }, - }, - { VERCEL_ENV: 'preview' }, - true, - false, - ], ])( '%s', async ( From 2c256108eeeed390428a25d27caee91396777e9f Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Mon, 13 Mar 2023 14:31:51 +0100 Subject: [PATCH 16/16] meta(changelog): Update changelog for 7.43.0 --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6eddb94ba9eb..49a101e7a76f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,17 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott +## 7.43.0 + +- feat(nextjs): Run source map upload in Vercel develop and preview environments (#7436) +- feat(types): Add `profilesSampler` option to node client type (#7385) +- fix(core): Avoid using `Array.findIndex()` as it is ES5 incompatible (#7400) +- fix(nextjs): Add better error messages for missing params during next build (#7434) +- fix(nextjs): Don't crash build when auth token is missing +- fix(node): Revert to dynamic `require` call to fix monkey patching (#7430) +- fix(types): Fix node types & add E2E test (#7429) + + ## 7.42.0 - feat(core): Add lifecycle hooks (#7370)