From 3aa394408be2ac5565fe526ff364ba640aa55bdb Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Tue, 24 Sep 2024 12:00:54 +0900 Subject: [PATCH 01/10] feat(solidstart): Add `withSentry` config wrapper to enable building instrumentation files --- packages/solidstart/.eslintrc.js | 2 +- .../src/config/addInstrumentation.ts | 82 +++++++++++++ packages/solidstart/src/config/index.ts | 0 packages/solidstart/src/config/types.ts | 35 ++++++ packages/solidstart/src/config/withSentry.ts | 47 ++++++++ .../src/vite/buildInstrumentationFile.ts | 55 +++++++++ .../src/vite/sentrySolidStartVite.ts | 3 + packages/solidstart/src/vite/sourceMaps.ts | 2 +- packages/solidstart/src/vite/types.ts | 8 ++ .../test/vite/buildInstrumentation.test.ts | 114 ++++++++++++++++++ .../test/vite/sentrySolidStartVite.test.ts | 11 +- 11 files changed, 353 insertions(+), 6 deletions(-) create mode 100644 packages/solidstart/src/config/addInstrumentation.ts create mode 100644 packages/solidstart/src/config/index.ts create mode 100644 packages/solidstart/src/config/types.ts create mode 100644 packages/solidstart/src/config/withSentry.ts create mode 100644 packages/solidstart/src/vite/buildInstrumentationFile.ts create mode 100644 packages/solidstart/test/vite/buildInstrumentation.test.ts diff --git a/packages/solidstart/.eslintrc.js b/packages/solidstart/.eslintrc.js index d567b12530d0..0fe78630b548 100644 --- a/packages/solidstart/.eslintrc.js +++ b/packages/solidstart/.eslintrc.js @@ -11,7 +11,7 @@ module.exports = { }, }, { - files: ['src/vite/**', 'src/server/**'], + files: ['src/vite/**', 'src/server/**', 'src/config/**'], rules: { '@sentry-internal/sdk/no-optional-chaining': 'off', '@sentry-internal/sdk/no-nullish-coalescing': 'off', diff --git a/packages/solidstart/src/config/addInstrumentation.ts b/packages/solidstart/src/config/addInstrumentation.ts new file mode 100644 index 000000000000..05e5477ae6c4 --- /dev/null +++ b/packages/solidstart/src/config/addInstrumentation.ts @@ -0,0 +1,82 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import { consoleSandbox } from '@sentry/utils'; +import type { Nitro } from './types'; + +/** + * Adds the built `instrument.server.js` file to the output directory. + * + * This will no-op if no `instrument.server.js` file was found in the + * build directory. Make sure the `sentrySolidStartVite` plugin was + * added to `app.config.ts` to enable building the instrumentation file. + */ +export async function addInstrumentationFileToBuild(nitro: Nitro): Promise { + const { buildDir, serverDir } = nitro.options.output; + const source = path.join(buildDir, 'build', 'ssr', 'instrument.server.js'); + const destination = path.join(serverDir, 'instrument.server.mjs'); + + try { + await fs.promises.access(source, fs.constants.F_OK); + await fs.promises.copyFile(source, destination); + + consoleSandbox(() => { + // eslint-disable-next-line no-console + console.log(`[Sentry SolidStart withSentry] Successfully created ${destination}.`); + }); + } catch (error) { + consoleSandbox(() => { + // eslint-disable-next-line no-console + console.warn(`[Sentry SolidStart withSentry] Failed to create ${destination}.`, error); + }); + } +} + +/** + * Adds an `instrument.server.mjs` import to the top of the server entry file. + * + * This is meant as an escape hatch and should only be used in environments where + * it's not possible to `--import` the file instead as it comes with a limited + * tracing experience, only collecting http traces. + */ +export async function experimental_addInstrumentationFileTopLevelImportToServerEntry( + serverDir: string, + preset: string, +): Promise { + // other presets ('node-server' or 'vercel') have an index.mjs + const presetsWithServerFile = ['netlify']; + const instrumentationFile = path.join(serverDir, 'instrument.server.mjs'); + const serverEntryFileName = presetsWithServerFile.includes(preset) ? 'server.mjs' : 'index.mjs'; + const serverEntryFile = path.join(serverDir, serverEntryFileName); + + try { + await fs.promises.access(instrumentationFile, fs.constants.F_OK); + } catch (error) { + consoleSandbox(() => { + // eslint-disable-next-line no-console + console.warn( + `[Sentry SolidStart withSentry] Tried to add \`${instrumentationFile}\` as top level import to \`${serverEntryFile}\`.`, + error, + ); + }); + return; + } + + try { + const content = await fs.promises.readFile(serverEntryFile, 'utf-8'); + const updatedContent = `import './instrument.server.mjs';\n${content}`; + await fs.promises.writeFile(serverEntryFile, updatedContent); + + consoleSandbox(() => { + // eslint-disable-next-line no-console + console.log( + `[Sentry SolidStart withSentry] Added \`${instrumentationFile}\` as top level import to \`${serverEntryFile}\`.`, + ); + }); + } catch (error) { + // eslint-disable-next-line no-console + console.warn( + `[Sentry SolidStart withSentry] An error occurred when trying to add \`${instrumentationFile}\` as top level import to \`${serverEntryFile}\`.`, + error, + ); + } +} diff --git a/packages/solidstart/src/config/index.ts b/packages/solidstart/src/config/index.ts new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/solidstart/src/config/types.ts b/packages/solidstart/src/config/types.ts new file mode 100644 index 000000000000..3ffa25aeaf0b --- /dev/null +++ b/packages/solidstart/src/config/types.ts @@ -0,0 +1,35 @@ +// Types to avoid pulling in extra dependencies +// These are non-exhaustive +export type Nitro = { + options: { + buildDir: string; + output: { + buildDir: string; + serverDir: string; + }; + preset: string; + }; +}; + +export type SolidStartInlineConfig = { + server?: { + hooks?: { + close?: () => unknown; + 'rollup:before'?: (nitro: Nitro) => unknown; + }; + }; +}; + +export type SentrySolidStartConfigOptions = { + /** + * Enabling basic server tracing can be used for environments where modifying the node option `--import` is not possible. + * However, enabling this option only supports limited tracing instrumentation. Only http traces will be collected (but no database-specific traces etc.). + * + * If this option is `true`, the Sentry SDK will import the instrumentation.server.ts|js file at the top of the server entry file to load the SDK on the server. + * + * **DO NOT** enable this option if you've already added the node option `--import` in your node start script. This would initialize Sentry twice on the server-side and leads to unexpected issues. + * + * @default false + */ + experimental_basicServerTracing?: boolean; +}; diff --git a/packages/solidstart/src/config/withSentry.ts b/packages/solidstart/src/config/withSentry.ts new file mode 100644 index 000000000000..921eca950889 --- /dev/null +++ b/packages/solidstart/src/config/withSentry.ts @@ -0,0 +1,47 @@ +import { + addInstrumentationFileToBuild, + experimental_addInstrumentationFileTopLevelImportToServerEntry, +} from './addInstrumentation'; +import type { SentrySolidStartConfigOptions, SolidStartInlineConfig } from './types'; + +export const withSentry = ( + solidStartConfig: SolidStartInlineConfig = {}, + sentrySolidStartConfigOptions: SentrySolidStartConfigOptions = {}, +): SolidStartInlineConfig => { + const server = solidStartConfig.server || {}; + const hooks = server.hooks || {}; + + let serverDir: string; + let buildPreset: string; + + return { + ...solidStartConfig, + server: { + ...server, + hooks: { + ...hooks, + async close() { + if (sentrySolidStartConfigOptions.experimental_basicServerTracing) { + await experimental_addInstrumentationFileTopLevelImportToServerEntry(serverDir, buildPreset); + } + + // Run user provided hook + if (hooks.close) { + hooks.close(); + } + }, + async 'rollup:before'(nitro) { + serverDir = nitro.options.output.serverDir; + buildPreset = nitro.options.preset; + + await addInstrumentationFileToBuild(nitro); + + // Run user provided hook + if (hooks['rollup:before']) { + hooks['rollup:before'](nitro); + } + }, + }, + }, + }; +}; diff --git a/packages/solidstart/src/vite/buildInstrumentationFile.ts b/packages/solidstart/src/vite/buildInstrumentationFile.ts new file mode 100644 index 000000000000..a25546e15c94 --- /dev/null +++ b/packages/solidstart/src/vite/buildInstrumentationFile.ts @@ -0,0 +1,55 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import { consoleSandbox } from '@sentry/utils'; +import type { Plugin, UserConfig } from 'vite'; + +/** + * A Sentry plugin for SolidStart to build the server + * `instrument.server.ts` file. + */ +export function makeBuildInstrumentationFilePlugin( + instrumentationFilePath: string = './src/instrument.server.ts', +): Plugin { + return { + name: 'sentry-solidstart-build-instrumentation-file', + apply: 'build', + enforce: 'post', + async config(config: UserConfig, { command }) { + const router = (config as UserConfig & { router: { target: string; name: string; root: string } }).router; + const build = config.build || {}; + const rollupOptions = build.rollupOptions || {}; + const input = [...((rollupOptions.input || []) as string[])]; + + // plugin runs for client, server and sever-fns, we only want to run it for the server once. + if (command !== 'build' || router.target !== 'server' || router.name === 'server-fns') { + return config; + } + + try { + await fs.promises.access(instrumentationFilePath, fs.constants.F_OK); + } catch (error) { + consoleSandbox(() => { + // eslint-disable-next-line no-console + console.warn( + `[Sentry SolidStart Plugin] Could not access \`${instrumentationFilePath}\`, please make sure it exists.`, + error, + ); + }); + return config; + } + + input.push(path.join(router.root, instrumentationFilePath)); + + return { + ...config, + build: { + ...build, + rollupOptions: { + ...rollupOptions, + input, + }, + }, + }; + }, + }; +} diff --git a/packages/solidstart/src/vite/sentrySolidStartVite.ts b/packages/solidstart/src/vite/sentrySolidStartVite.ts index 59435f919071..c8332373bc58 100644 --- a/packages/solidstart/src/vite/sentrySolidStartVite.ts +++ b/packages/solidstart/src/vite/sentrySolidStartVite.ts @@ -1,4 +1,5 @@ import type { Plugin } from 'vite'; +import { makeBuildInstrumentationFilePlugin } from './buildInstrumentationFile'; import { makeSourceMapsVitePlugin } from './sourceMaps'; import type { SentrySolidStartPluginOptions } from './types'; @@ -8,6 +9,8 @@ import type { SentrySolidStartPluginOptions } from './types'; export const sentrySolidStartVite = (options: SentrySolidStartPluginOptions = {}): Plugin[] => { const sentryPlugins: Plugin[] = []; + sentryPlugins.push(makeBuildInstrumentationFilePlugin(options.instrumentation)); + if (process.env.NODE_ENV !== 'development') { if (options.sourceMapsUploadOptions?.enabled ?? true) { sentryPlugins.push(...makeSourceMapsVitePlugin(options)); diff --git a/packages/solidstart/src/vite/sourceMaps.ts b/packages/solidstart/src/vite/sourceMaps.ts index 548038515e79..21dce8070c73 100644 --- a/packages/solidstart/src/vite/sourceMaps.ts +++ b/packages/solidstart/src/vite/sourceMaps.ts @@ -22,7 +22,7 @@ export function makeSourceMapsVitePlugin(options: SentrySolidStartPluginOptions) if (!sourceMapsUploadOptions?.filesToDeleteAfterUpload) { // eslint-disable-next-line no-console console.warn( - `[Sentry SolidStart PLugin] We recommend setting the \`sourceMapsUploadOptions.filesToDeleteAfterUpload\` option to clean up source maps after uploading. + `[Sentry SolidStart Plugin] We recommend setting the \`sourceMapsUploadOptions.filesToDeleteAfterUpload\` option to clean up source maps after uploading. [Sentry SolidStart Plugin] Otherwise, source maps might be deployed to production, depending on your configuration`, ); } diff --git a/packages/solidstart/src/vite/types.ts b/packages/solidstart/src/vite/types.ts index 4a64e4856b5d..a725478aae7b 100644 --- a/packages/solidstart/src/vite/types.ts +++ b/packages/solidstart/src/vite/types.ts @@ -125,4 +125,12 @@ export type SentrySolidStartPluginOptions = { * Enabling this will give you, for example logs about source maps. */ debug?: boolean; + + /** + * The path to your `instrumentation.server.ts|js` file. + * e.g. './src/instrumentation.server.ts` + * + * Defaults to: `./src/instrumentation.server.ts` + */ + instrumentation?: string; }; diff --git a/packages/solidstart/test/vite/buildInstrumentation.test.ts b/packages/solidstart/test/vite/buildInstrumentation.test.ts new file mode 100644 index 000000000000..c05409de6e7f --- /dev/null +++ b/packages/solidstart/test/vite/buildInstrumentation.test.ts @@ -0,0 +1,114 @@ +import type { UserConfig } from 'vite'; +import { describe, expect, it, vi } from 'vitest'; +import { makeBuildInstrumentationFilePlugin } from '../../src/vite/buildInstrumentationFile'; + +const fsAccessMock = vi.fn(); + +vi.mock('fs', async () => { + const actual = await vi.importActual('fs'); + return { + ...actual, + promises: { + // @ts-expect-error this exists + ...actual.promises, + access: () => fsAccessMock(), + }, + }; +}); + +const consoleWarnSpy = vi.spyOn(console, 'warn'); + +beforeEach(() => { + vi.clearAllMocks(); +}); + +describe('makeBuildInstrumentationFilePlugin()', () => { + const viteConfig: UserConfig & { router: { target: string; name: string; root: string } } = { + router: { + target: 'server', + name: 'ssr', + root: '/some/project/path', + }, + build: { + rollupOptions: { + input: ['/path/to/entry1.js', '/path/to/entry2.js'], + }, + }, + }; + + it('returns a plugin to set `sourcemaps` to `true`', () => { + const buildInstrumentationFilePlugin = makeBuildInstrumentationFilePlugin(); + + expect(buildInstrumentationFilePlugin.name).toEqual('sentry-solidstart-build-instrumentation-file'); + expect(buildInstrumentationFilePlugin.apply).toEqual('build'); + expect(buildInstrumentationFilePlugin.enforce).toEqual('post'); + expect(buildInstrumentationFilePlugin.config).toEqual(expect.any(Function)); + }); + + it('adds the instrumentation file for server builds', async () => { + const buildInstrumentationFilePlugin = makeBuildInstrumentationFilePlugin(); + const config = await buildInstrumentationFilePlugin.config(viteConfig, { command: 'build' }); + expect(config.build.rollupOptions.input).toContain('/some/project/path/src/instrument.server.ts'); + }); + + it('adds the correct instrumentation file', async () => { + const buildInstrumentationFilePlugin = makeBuildInstrumentationFilePlugin('./src/myapp/instrument.server.ts'); + const config = await buildInstrumentationFilePlugin.config(viteConfig, { command: 'build' }); + expect(config.build.rollupOptions.input).toContain('/some/project/path/src/myapp/instrument.server.ts'); + }); + + it("doesn't add the instrumentation file for server function builds", async () => { + const buildInstrumentationFilePlugin = makeBuildInstrumentationFilePlugin(); + const config = await buildInstrumentationFilePlugin.config( + { + ...viteConfig, + router: { + ...viteConfig.router, + name: 'server-fns', + }, + }, + { command: 'build' }, + ); + expect(config.build.rollupOptions.input).not.toContain('/some/project/path/src/instrument.server.ts'); + }); + + it("doesn't add the instrumentation file for client builds", async () => { + const buildInstrumentationFilePlugin = makeBuildInstrumentationFilePlugin(); + const config = await buildInstrumentationFilePlugin.config( + { + ...viteConfig, + router: { + ...viteConfig.router, + target: 'client', + }, + }, + { command: 'build' }, + ); + expect(config.build.rollupOptions.input).not.toContain('/some/project/path/src/instrument.server.ts'); + }); + + it("doesn't add the instrumentation file when serving", async () => { + const buildInstrumentationFilePlugin = makeBuildInstrumentationFilePlugin(); + const config = await buildInstrumentationFilePlugin.config(viteConfig, { command: 'serve' }); + expect(config.build.rollupOptions.input).not.toContain('/some/project/path/src/instrument.server.ts'); + }); + + it("doesn't modify the config if the instrumentation file doesn't exist", async () => { + fsAccessMock.mockRejectedValueOnce(undefined); + const buildInstrumentationFilePlugin = makeBuildInstrumentationFilePlugin(); + const config = await buildInstrumentationFilePlugin.config(viteConfig, { command: 'build' }); + expect(config).toEqual(viteConfig); + }); + + it("logs a warning if the instrumentation file doesn't exist", async () => { + const error = new Error("File doesn't exist."); + fsAccessMock.mockRejectedValueOnce(error); + const buildInstrumentationFilePlugin = makeBuildInstrumentationFilePlugin(); + const config = await buildInstrumentationFilePlugin.config(viteConfig, { command: 'build' }); + expect(config).toEqual(viteConfig); + expect(consoleWarnSpy).toHaveBeenCalledWith( + '[Sentry SolidStart Plugin] Could not access `./src/instrument.server.ts`, please make sure it exists.', + error, + ); + }); +}); diff --git a/packages/solidstart/test/vite/sentrySolidStartVite.test.ts b/packages/solidstart/test/vite/sentrySolidStartVite.test.ts index d3f905313859..8915c5a70671 100644 --- a/packages/solidstart/test/vite/sentrySolidStartVite.test.ts +++ b/packages/solidstart/test/vite/sentrySolidStartVite.test.ts @@ -23,6 +23,7 @@ describe('sentrySolidStartVite()', () => { const plugins = getSentrySolidStartVitePlugins(); const names = plugins.map(plugin => plugin.name); expect(names).toEqual([ + 'sentry-solidstart-build-instrumentation-file', 'sentry-solidstart-source-maps', 'sentry-telemetry-plugin', 'sentry-vite-release-injection-plugin', @@ -33,17 +34,19 @@ describe('sentrySolidStartVite()', () => { ]); }); - it("returns an empty array if source maps upload isn't enabled", () => { + it("returns only build-instrumentation-file plugin if source maps upload isn't enabled", () => { const plugins = getSentrySolidStartVitePlugins({ sourceMapsUploadOptions: { enabled: false } }); - expect(plugins).toHaveLength(0); + const names = plugins.map(plugin => plugin.name); + expect(names).toEqual(['sentry-solidstart-build-instrumentation-file']); }); - it('returns an empty array if `NODE_ENV` is development', async () => { + it('returns only build-instrumentation-file plugin if `NODE_ENV` is development', async () => { const previousEnv = process.env.NODE_ENV; process.env.NODE_ENV = 'development'; const plugins = getSentrySolidStartVitePlugins({ sourceMapsUploadOptions: { enabled: true } }); - expect(plugins).toHaveLength(0); + const names = plugins.map(plugin => plugin.name); + expect(names).toEqual(['sentry-solidstart-build-instrumentation-file']); process.env.NODE_ENV = previousEnv; }); From 5546d1138727c976aae6fc9616d9541d34b38e62 Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Tue, 24 Sep 2024 15:14:23 +0900 Subject: [PATCH 02/10] Fix typing, unify vite plugin options --- .../src/config/addInstrumentation.ts | 11 ++++++----- packages/solidstart/src/config/index.ts | 1 + packages/solidstart/src/config/types.ts | 13 +++++++------ packages/solidstart/src/config/withSentry.ts | 11 ++++++++--- packages/solidstart/src/index.server.ts | 1 + packages/solidstart/src/index.types.ts | 1 + .../src/vite/buildInstrumentationFile.ts | 8 ++++---- .../src/vite/sentrySolidStartVite.ts | 2 +- .../test/vite/buildInstrumentation.test.ts | 18 +++++++++++++++++- 9 files changed, 46 insertions(+), 20 deletions(-) diff --git a/packages/solidstart/src/config/addInstrumentation.ts b/packages/solidstart/src/config/addInstrumentation.ts index 05e5477ae6c4..2e065e67ef19 100644 --- a/packages/solidstart/src/config/addInstrumentation.ts +++ b/packages/solidstart/src/config/addInstrumentation.ts @@ -11,9 +11,10 @@ import type { Nitro } from './types'; * added to `app.config.ts` to enable building the instrumentation file. */ export async function addInstrumentationFileToBuild(nitro: Nitro): Promise { - const { buildDir, serverDir } = nitro.options.output; - const source = path.join(buildDir, 'build', 'ssr', 'instrument.server.js'); - const destination = path.join(serverDir, 'instrument.server.mjs'); + const buildDir = nitro.options.buildDir; + const serverDir = nitro.options.output.serverDir; + const source = path.resolve(buildDir, 'build', 'ssr', 'instrument.server.js'); + const destination = path.resolve(serverDir, 'instrument.server.mjs'); try { await fs.promises.access(source, fs.constants.F_OK); @@ -44,9 +45,9 @@ export async function experimental_addInstrumentationFileTopLevelImportToServerE ): Promise { // other presets ('node-server' or 'vercel') have an index.mjs const presetsWithServerFile = ['netlify']; - const instrumentationFile = path.join(serverDir, 'instrument.server.mjs'); + const instrumentationFile = path.resolve(serverDir, 'instrument.server.mjs'); const serverEntryFileName = presetsWithServerFile.includes(preset) ? 'server.mjs' : 'index.mjs'; - const serverEntryFile = path.join(serverDir, serverEntryFileName); + const serverEntryFile = path.resolve(serverDir, serverEntryFileName); try { await fs.promises.access(instrumentationFile, fs.constants.F_OK); diff --git a/packages/solidstart/src/config/index.ts b/packages/solidstart/src/config/index.ts index e69de29bb2d1..4949f4bdf523 100644 --- a/packages/solidstart/src/config/index.ts +++ b/packages/solidstart/src/config/index.ts @@ -0,0 +1 @@ +export * from './withSentry'; diff --git a/packages/solidstart/src/config/types.ts b/packages/solidstart/src/config/types.ts index 3ffa25aeaf0b..eb7845fc3b6c 100644 --- a/packages/solidstart/src/config/types.ts +++ b/packages/solidstart/src/config/types.ts @@ -1,3 +1,4 @@ +import type { defineConfig } from '@solidjs/start/config'; // Types to avoid pulling in extra dependencies // These are non-exhaustive export type Nitro = { @@ -11,12 +12,12 @@ export type Nitro = { }; }; -export type SolidStartInlineConfig = { - server?: { - hooks?: { - close?: () => unknown; - 'rollup:before'?: (nitro: Nitro) => unknown; - }; +export type SolidStartInlineConfig = Parameters[0]; + +export type SolidStartInlineConfigNitroHooks = { + hooks?: { + close?: () => unknown; + 'rollup:before'?: (nitro: Nitro) => unknown; }; }; diff --git a/packages/solidstart/src/config/withSentry.ts b/packages/solidstart/src/config/withSentry.ts index 921eca950889..d1ecb05bd7af 100644 --- a/packages/solidstart/src/config/withSentry.ts +++ b/packages/solidstart/src/config/withSentry.ts @@ -2,13 +2,18 @@ import { addInstrumentationFileToBuild, experimental_addInstrumentationFileTopLevelImportToServerEntry, } from './addInstrumentation'; -import type { SentrySolidStartConfigOptions, SolidStartInlineConfig } from './types'; +import type { + Nitro, + SentrySolidStartConfigOptions, + SolidStartInlineConfig, + SolidStartInlineConfigNitroHooks, +} from './types'; export const withSentry = ( solidStartConfig: SolidStartInlineConfig = {}, sentrySolidStartConfigOptions: SentrySolidStartConfigOptions = {}, ): SolidStartInlineConfig => { - const server = solidStartConfig.server || {}; + const server = (solidStartConfig.server || {}) as SolidStartInlineConfigNitroHooks; const hooks = server.hooks || {}; let serverDir: string; @@ -30,7 +35,7 @@ export const withSentry = ( hooks.close(); } }, - async 'rollup:before'(nitro) { + async 'rollup:before'(nitro: Nitro) { serverDir = nitro.options.output.serverDir; buildPreset = nitro.options.preset; diff --git a/packages/solidstart/src/index.server.ts b/packages/solidstart/src/index.server.ts index d675a1c72820..a20a0367f557 100644 --- a/packages/solidstart/src/index.server.ts +++ b/packages/solidstart/src/index.server.ts @@ -1,2 +1,3 @@ export * from './server'; export * from './vite'; +export * from './config'; diff --git a/packages/solidstart/src/index.types.ts b/packages/solidstart/src/index.types.ts index 51adf848775a..13b9a6dd7432 100644 --- a/packages/solidstart/src/index.types.ts +++ b/packages/solidstart/src/index.types.ts @@ -4,6 +4,7 @@ export * from './client'; export * from './server'; export * from './vite'; +export * from './config'; import type { Integration, Options, StackParser } from '@sentry/types'; diff --git a/packages/solidstart/src/vite/buildInstrumentationFile.ts b/packages/solidstart/src/vite/buildInstrumentationFile.ts index a25546e15c94..abb02e8d03ce 100644 --- a/packages/solidstart/src/vite/buildInstrumentationFile.ts +++ b/packages/solidstart/src/vite/buildInstrumentationFile.ts @@ -2,19 +2,19 @@ import * as fs from 'fs'; import * as path from 'path'; import { consoleSandbox } from '@sentry/utils'; import type { Plugin, UserConfig } from 'vite'; +import type { SentrySolidStartPluginOptions } from './types'; /** * A Sentry plugin for SolidStart to build the server * `instrument.server.ts` file. */ -export function makeBuildInstrumentationFilePlugin( - instrumentationFilePath: string = './src/instrument.server.ts', -): Plugin { +export function makeBuildInstrumentationFilePlugin(options: SentrySolidStartPluginOptions = {}): Plugin { return { name: 'sentry-solidstart-build-instrumentation-file', apply: 'build', enforce: 'post', async config(config: UserConfig, { command }) { + const instrumentationFilePath = options.instrumentation || './src/instrument.server.ts'; const router = (config as UserConfig & { router: { target: string; name: string; root: string } }).router; const build = config.build || {}; const rollupOptions = build.rollupOptions || {}; @@ -38,7 +38,7 @@ export function makeBuildInstrumentationFilePlugin( return config; } - input.push(path.join(router.root, instrumentationFilePath)); + input.push(path.resolve(router.root, instrumentationFilePath)); return { ...config, diff --git a/packages/solidstart/src/vite/sentrySolidStartVite.ts b/packages/solidstart/src/vite/sentrySolidStartVite.ts index c8332373bc58..70df00c1fdaf 100644 --- a/packages/solidstart/src/vite/sentrySolidStartVite.ts +++ b/packages/solidstart/src/vite/sentrySolidStartVite.ts @@ -9,7 +9,7 @@ import type { SentrySolidStartPluginOptions } from './types'; export const sentrySolidStartVite = (options: SentrySolidStartPluginOptions = {}): Plugin[] => { const sentryPlugins: Plugin[] = []; - sentryPlugins.push(makeBuildInstrumentationFilePlugin(options.instrumentation)); + sentryPlugins.push(makeBuildInstrumentationFilePlugin(options)); if (process.env.NODE_ENV !== 'development') { if (options.sourceMapsUploadOptions?.enabled ?? true) { diff --git a/packages/solidstart/test/vite/buildInstrumentation.test.ts b/packages/solidstart/test/vite/buildInstrumentation.test.ts index c05409de6e7f..52378a668870 100644 --- a/packages/solidstart/test/vite/buildInstrumentation.test.ts +++ b/packages/solidstart/test/vite/buildInstrumentation.test.ts @@ -47,18 +47,26 @@ describe('makeBuildInstrumentationFilePlugin()', () => { it('adds the instrumentation file for server builds', async () => { const buildInstrumentationFilePlugin = makeBuildInstrumentationFilePlugin(); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore - this is always defined and always a function const config = await buildInstrumentationFilePlugin.config(viteConfig, { command: 'build' }); expect(config.build.rollupOptions.input).toContain('/some/project/path/src/instrument.server.ts'); }); it('adds the correct instrumentation file', async () => { - const buildInstrumentationFilePlugin = makeBuildInstrumentationFilePlugin('./src/myapp/instrument.server.ts'); + const buildInstrumentationFilePlugin = makeBuildInstrumentationFilePlugin({ + instrumentation: './src/myapp/instrument.server.ts', + }); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore - this is always defined and always a function const config = await buildInstrumentationFilePlugin.config(viteConfig, { command: 'build' }); expect(config.build.rollupOptions.input).toContain('/some/project/path/src/myapp/instrument.server.ts'); }); it("doesn't add the instrumentation file for server function builds", async () => { const buildInstrumentationFilePlugin = makeBuildInstrumentationFilePlugin(); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore - this is always defined and always a function const config = await buildInstrumentationFilePlugin.config( { ...viteConfig, @@ -74,6 +82,8 @@ describe('makeBuildInstrumentationFilePlugin()', () => { it("doesn't add the instrumentation file for client builds", async () => { const buildInstrumentationFilePlugin = makeBuildInstrumentationFilePlugin(); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore - this is always defined and always a function const config = await buildInstrumentationFilePlugin.config( { ...viteConfig, @@ -89,6 +99,8 @@ describe('makeBuildInstrumentationFilePlugin()', () => { it("doesn't add the instrumentation file when serving", async () => { const buildInstrumentationFilePlugin = makeBuildInstrumentationFilePlugin(); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore - this is always defined and always a function const config = await buildInstrumentationFilePlugin.config(viteConfig, { command: 'serve' }); expect(config.build.rollupOptions.input).not.toContain('/some/project/path/src/instrument.server.ts'); }); @@ -96,6 +108,8 @@ describe('makeBuildInstrumentationFilePlugin()', () => { it("doesn't modify the config if the instrumentation file doesn't exist", async () => { fsAccessMock.mockRejectedValueOnce(undefined); const buildInstrumentationFilePlugin = makeBuildInstrumentationFilePlugin(); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore - this is always defined and always a function const config = await buildInstrumentationFilePlugin.config(viteConfig, { command: 'build' }); expect(config).toEqual(viteConfig); }); @@ -104,6 +118,8 @@ describe('makeBuildInstrumentationFilePlugin()', () => { const error = new Error("File doesn't exist."); fsAccessMock.mockRejectedValueOnce(error); const buildInstrumentationFilePlugin = makeBuildInstrumentationFilePlugin(); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore - this is always defined and always a function const config = await buildInstrumentationFilePlugin.config(viteConfig, { command: 'build' }); expect(config).toEqual(viteConfig); expect(consoleWarnSpy).toHaveBeenCalledWith( From 4bd25075cb6f91b9a43aef9c0bf96428bbb6f049 Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Tue, 24 Sep 2024 15:19:49 +0900 Subject: [PATCH 03/10] Add JSDoc to `withSentry` --- packages/solidstart/src/config/withSentry.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/solidstart/src/config/withSentry.ts b/packages/solidstart/src/config/withSentry.ts index d1ecb05bd7af..d27d3028fc50 100644 --- a/packages/solidstart/src/config/withSentry.ts +++ b/packages/solidstart/src/config/withSentry.ts @@ -9,6 +9,15 @@ import type { SolidStartInlineConfigNitroHooks, } from './types'; +/** + * Modifies the passed in Solid Start configuration with build-time enhancements such as + * building the `instrument.server.ts` file into the appropriate build folder based on + * build preset. + * + * @param solidStartConfig A Solid Start configuration object, as usually passed to `defineConfig` in `app.config.ts|js` + * @param sentrySolidStartConfigOptions Options to configure the plugin + * @returns The modified config to be exported and passed back into `defineConfig` + */ export const withSentry = ( solidStartConfig: SolidStartInlineConfig = {}, sentrySolidStartConfigOptions: SentrySolidStartConfigOptions = {}, From 39d917365536f57d3ff65ccf4ef047748495c028 Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Wed, 25 Sep 2024 12:48:17 +0900 Subject: [PATCH 04/10] Add unit tests for config helpers --- .../src/config/addInstrumentation.ts | 22 ++- packages/solidstart/src/config/types.ts | 1 - .../test/config/addInstrumentation.test.ts | 135 ++++++++++++++++++ .../solidstart/test/config/withSentry.test.ts | 65 +++++++++ 4 files changed, 217 insertions(+), 6 deletions(-) create mode 100644 packages/solidstart/test/config/addInstrumentation.test.ts create mode 100644 packages/solidstart/test/config/withSentry.test.ts diff --git a/packages/solidstart/src/config/addInstrumentation.ts b/packages/solidstart/src/config/addInstrumentation.ts index 2e065e67ef19..1aab37cfc157 100644 --- a/packages/solidstart/src/config/addInstrumentation.ts +++ b/packages/solidstart/src/config/addInstrumentation.ts @@ -3,6 +3,11 @@ import * as path from 'path'; import { consoleSandbox } from '@sentry/utils'; import type { Nitro } from './types'; +// Nitro presets for hosts that only host static files +export const staticHostPresets = ['github_pages']; +// Nitro presets for hosts that use `server.mjs` as opposed to `index.mjs` +export const serverFilePresets = ['netlify']; + /** * Adds the built `instrument.server.js` file to the output directory. * @@ -11,13 +16,17 @@ import type { Nitro } from './types'; * added to `app.config.ts` to enable building the instrumentation file. */ export async function addInstrumentationFileToBuild(nitro: Nitro): Promise { + // Static file hosts have no server component so there's nothing to do + if (staticHostPresets.includes(nitro.options.preset)) { + return; + } + const buildDir = nitro.options.buildDir; const serverDir = nitro.options.output.serverDir; const source = path.resolve(buildDir, 'build', 'ssr', 'instrument.server.js'); const destination = path.resolve(serverDir, 'instrument.server.mjs'); try { - await fs.promises.access(source, fs.constants.F_OK); await fs.promises.copyFile(source, destination); consoleSandbox(() => { @@ -43,10 +52,13 @@ export async function experimental_addInstrumentationFileTopLevelImportToServerE serverDir: string, preset: string, ): Promise { - // other presets ('node-server' or 'vercel') have an index.mjs - const presetsWithServerFile = ['netlify']; + // Static file hosts have no server component so there's nothing to do + if (staticHostPresets.includes(preset)) { + return; + } + const instrumentationFile = path.resolve(serverDir, 'instrument.server.mjs'); - const serverEntryFileName = presetsWithServerFile.includes(preset) ? 'server.mjs' : 'index.mjs'; + const serverEntryFileName = serverFilePresets.includes(preset) ? 'server.mjs' : 'index.mjs'; const serverEntryFile = path.resolve(serverDir, serverEntryFileName); try { @@ -55,7 +67,7 @@ export async function experimental_addInstrumentationFileTopLevelImportToServerE consoleSandbox(() => { // eslint-disable-next-line no-console console.warn( - `[Sentry SolidStart withSentry] Tried to add \`${instrumentationFile}\` as top level import to \`${serverEntryFile}\`.`, + `[Sentry SolidStart withSentry] Failed to add \`${instrumentationFile}\` as top level import to \`${serverEntryFile}\`.`, error, ); }); diff --git a/packages/solidstart/src/config/types.ts b/packages/solidstart/src/config/types.ts index eb7845fc3b6c..e9623313f438 100644 --- a/packages/solidstart/src/config/types.ts +++ b/packages/solidstart/src/config/types.ts @@ -5,7 +5,6 @@ export type Nitro = { options: { buildDir: string; output: { - buildDir: string; serverDir: string; }; preset: string; diff --git a/packages/solidstart/test/config/addInstrumentation.test.ts b/packages/solidstart/test/config/addInstrumentation.test.ts new file mode 100644 index 000000000000..7f20911a70b3 --- /dev/null +++ b/packages/solidstart/test/config/addInstrumentation.test.ts @@ -0,0 +1,135 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import type { Nitro } from '../../build/types/config/types'; +import { + addInstrumentationFileToBuild, + experimental_addInstrumentationFileTopLevelImportToServerEntry, + serverFilePresets, + staticHostPresets, +} from '../../src/config/addInstrumentation'; + +const consoleLogSpy = vi.spyOn(console, 'log'); +const consoleWarnSpy = vi.spyOn(console, 'warn'); +const fsAccessMock = vi.fn(); +const fsCopyFileMock = vi.fn(); +const fsReadFile = vi.fn(); +const fsWriteFileMock = vi.fn(); + +vi.mock('fs', async () => { + const actual = await vi.importActual('fs'); + return { + ...actual, + promises: { + // @ts-expect-error this exists + ...actual.promises, + access: (...args: unknown[]) => fsAccessMock(...args), + copyFile: (...args: unknown[]) => fsCopyFileMock(...args), + readFile: (...args: unknown[]) => fsReadFile(...args), + writeFile: (...args: unknown[]) => fsWriteFileMock(...args), + }, + }; +}); + +beforeEach(() => { + vi.clearAllMocks(); +}); + +describe('addInstrumentationFileToBuild()', () => { + const nitroOptions: Nitro = { + options: { + buildDir: '/path/to/buildDir', + output: { + serverDir: '/path/to/serverDir', + }, + preset: 'vercel', + }, + }; + + it('adds `instrument.server.mjs` to the server output directory', async () => { + fsCopyFileMock.mockResolvedValueOnce(true); + await addInstrumentationFileToBuild(nitroOptions); + expect(fsCopyFileMock).toHaveBeenCalledWith( + '/path/to/buildDir/build/ssr/instrument.server.js', + '/path/to/serverDir/instrument.server.mjs', + ); + expect(consoleLogSpy).toHaveBeenCalledWith( + '[Sentry SolidStart withSentry] Successfully created /path/to/serverDir/instrument.server.mjs.', + ); + }); + + it('warns when `instrument.server.js` can not be copied to the server output directory', async () => { + const error = new Error('Failed to copy file.'); + fsCopyFileMock.mockRejectedValueOnce(error); + await addInstrumentationFileToBuild(nitroOptions); + expect(fsCopyFileMock).toHaveBeenCalledWith( + '/path/to/buildDir/build/ssr/instrument.server.js', + '/path/to/serverDir/instrument.server.mjs', + ); + expect(consoleWarnSpy).toHaveBeenCalledWith( + '[Sentry SolidStart withSentry] Failed to create /path/to/serverDir/instrument.server.mjs.', + error, + ); + }); + + it.each([staticHostPresets])("doesn't add `instrument.server.mjs` for static host `%s`", async preset => { + await addInstrumentationFileToBuild({ + ...nitroOptions, + options: { + ...nitroOptions.options, + preset, + }, + }); + expect(fsCopyFileMock).not.toHaveBeenCalled(); + }); +}); + +describe('experimental_addInstrumentationFileTopLevelImportToServerEntry()', () => { + it('adds a top level import of `instrument.server.mjs` to the index.mjs entry file', async () => { + fsAccessMock.mockResolvedValueOnce(true); + fsReadFile.mockResolvedValueOnce("import process from 'node:process';"); + fsWriteFileMock.mockResolvedValueOnce(true); + await experimental_addInstrumentationFileTopLevelImportToServerEntry('/path/to/serverDir', 'node_server'); + expect(fsWriteFileMock).toHaveBeenCalledWith( + '/path/to/serverDir/index.mjs', + "import './instrument.server.mjs';\nimport process from 'node:process';", + ); + expect(consoleLogSpy).toHaveBeenCalledWith( + '[Sentry SolidStart withSentry] Added `/path/to/serverDir/instrument.server.mjs` as top level import to `/path/to/serverDir/index.mjs`.', + ); + }); + + it.each([serverFilePresets])( + 'adds a top level import of `instrument.server.mjs` to the server.mjs entry file for preset `%s`', + async preset => { + fsAccessMock.mockResolvedValueOnce(true); + fsReadFile.mockResolvedValueOnce("import process from 'node:process';"); + fsWriteFileMock.mockResolvedValueOnce(true); + await experimental_addInstrumentationFileTopLevelImportToServerEntry('/path/to/serverDir', preset); + expect(fsWriteFileMock).toHaveBeenCalledWith( + '/path/to/serverDir/server.mjs', + "import './instrument.server.mjs';\nimport process from 'node:process';", + ); + expect(consoleLogSpy).toHaveBeenCalledWith( + '[Sentry SolidStart withSentry] Added `/path/to/serverDir/instrument.server.mjs` as top level import to `/path/to/serverDir/server.mjs`.', + ); + }, + ); + + it("doesn't modify the sever entry file if `instrumentation.server.mjs` is not found", async () => { + const error = new Error('File not found.'); + fsAccessMock.mockRejectedValueOnce(error); + await experimental_addInstrumentationFileTopLevelImportToServerEntry('/path/to/serverDir', 'node_server'); + expect(consoleWarnSpy).toHaveBeenCalledWith( + '[Sentry SolidStart withSentry] Failed to add `/path/to/serverDir/instrument.server.mjs` as top level import to `/path/to/serverDir/index.mjs`.', + error, + ); + }); + + it.each([staticHostPresets])( + "doesn't import `instrument.server.mjs` as top level import for host `%s`", + async preset => { + fsAccessMock.mockResolvedValueOnce(true); + await experimental_addInstrumentationFileTopLevelImportToServerEntry('/path/to/serverDir', preset); + expect(fsWriteFileMock).not.toHaveBeenCalled(); + }, + ); +}); diff --git a/packages/solidstart/test/config/withSentry.test.ts b/packages/solidstart/test/config/withSentry.test.ts new file mode 100644 index 000000000000..faf54e4e4679 --- /dev/null +++ b/packages/solidstart/test/config/withSentry.test.ts @@ -0,0 +1,65 @@ +import { beforeEach, describe, it, vi } from 'vitest'; +import type { Nitro } from '../../build/types/config/types'; +import { withSentry } from '../../src/config'; + +const userDefinedNitroRollupBeforeHookMock = vi.fn(); +const userDefinedNitroCloseHookMock = vi.fn(); +const addInstrumentationFileToBuildMock = vi.fn(); +const experimental_addInstrumentationFileTopLevelImportToServerEntryMock = vi.fn(); + +vi.mock('../../src/config/addInstrumentation', () => ({ + addInstrumentationFileToBuild: (...args: unknown[]) => addInstrumentationFileToBuildMock(...args), + experimental_addInstrumentationFileTopLevelImportToServerEntry: (...args: unknown[]) => + experimental_addInstrumentationFileTopLevelImportToServerEntryMock(...args), +})); + +beforeEach(() => { + vi.clearAllMocks(); +}); + +describe('withSentry()', () => { + const solidStartConfig = { + middleware: './src/middleware.ts', + server: { + hooks: { + close: userDefinedNitroCloseHookMock, + 'rollup:before': userDefinedNitroRollupBeforeHookMock, + }, + }, + }; + const nitroOptions: Nitro = { + options: { + buildDir: '/path/to/buildDir', + output: { + serverDir: '/path/to/serverDir', + }, + preset: 'vercel', + }, + }; + + it('adds a nitro hook to add the instrumentation file to the build', async () => { + const config = withSentry(solidStartConfig); + await config?.server.hooks['rollup:before'](nitroOptions); + expect(addInstrumentationFileToBuildMock).toHaveBeenCalledWith(nitroOptions); + expect(userDefinedNitroRollupBeforeHookMock).toHaveBeenCalledWith(nitroOptions); + }); + + it('adds a nitro hook to add the instrumentation file as top level import to the server entry file', async () => { + const config = withSentry(solidStartConfig, { experimental_basicServerTracing: true }); + await config?.server.hooks['rollup:before'](nitroOptions); + await config?.server.hooks['close'](nitroOptions); + expect(experimental_addInstrumentationFileTopLevelImportToServerEntryMock).toHaveBeenCalledWith( + '/path/to/serverDir', + 'vercel', + ); + expect(userDefinedNitroCloseHookMock).toHaveBeenCalled(); + }); + + it('does not add the instrumentation file as top level import if experimental flag was not true', async () => { + const config = withSentry(solidStartConfig, { experimental_basicServerTracing: false }); + await config?.server.hooks['rollup:before'](nitroOptions); + await config?.server.hooks['close'](nitroOptions); + expect(experimental_addInstrumentationFileTopLevelImportToServerEntryMock).not.toHaveBeenCalled(); + expect(userDefinedNitroCloseHookMock).toHaveBeenCalled(); + }); +}); From d5b247aa4dbb7015f9c480c7a5a34f7be8c32bf6 Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Wed, 25 Sep 2024 13:45:25 +0900 Subject: [PATCH 05/10] Change ordering of plugins --- packages/solidstart/src/vite/sentrySolidStartVite.ts | 10 ++++++++-- .../solidstart/test/vite/sentrySolidStartVite.test.ts | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/solidstart/src/vite/sentrySolidStartVite.ts b/packages/solidstart/src/vite/sentrySolidStartVite.ts index 70df00c1fdaf..35a7da456b06 100644 --- a/packages/solidstart/src/vite/sentrySolidStartVite.ts +++ b/packages/solidstart/src/vite/sentrySolidStartVite.ts @@ -9,13 +9,19 @@ import type { SentrySolidStartPluginOptions } from './types'; export const sentrySolidStartVite = (options: SentrySolidStartPluginOptions = {}): Plugin[] => { const sentryPlugins: Plugin[] = []; - sentryPlugins.push(makeBuildInstrumentationFilePlugin(options)); - if (process.env.NODE_ENV !== 'development') { if (options.sourceMapsUploadOptions?.enabled ?? true) { sentryPlugins.push(...makeSourceMapsVitePlugin(options)); } } + // TODO: Ensure this file is source mapped too. + // Placing this after the sentry vite plugin means this + // file won't get a sourcemap and won't have a debug id injected. + // Because the file is just copied over to the output server + // directory the release injection file from sentry vite plugin + // wouldn't resolve correctly otherwise. + sentryPlugins.push(makeBuildInstrumentationFilePlugin(options)); + return sentryPlugins; }; diff --git a/packages/solidstart/test/vite/sentrySolidStartVite.test.ts b/packages/solidstart/test/vite/sentrySolidStartVite.test.ts index 8915c5a70671..45faa8b797f9 100644 --- a/packages/solidstart/test/vite/sentrySolidStartVite.test.ts +++ b/packages/solidstart/test/vite/sentrySolidStartVite.test.ts @@ -23,7 +23,6 @@ describe('sentrySolidStartVite()', () => { const plugins = getSentrySolidStartVitePlugins(); const names = plugins.map(plugin => plugin.name); expect(names).toEqual([ - 'sentry-solidstart-build-instrumentation-file', 'sentry-solidstart-source-maps', 'sentry-telemetry-plugin', 'sentry-vite-release-injection-plugin', @@ -31,6 +30,7 @@ describe('sentrySolidStartVite()', () => { 'sentry-vite-debug-id-injection-plugin', 'sentry-vite-debug-id-upload-plugin', 'sentry-file-deletion-plugin', + 'sentry-solidstart-build-instrumentation-file', ]); }); From 46f4f85e30eee1ccc626138c09224d0a95428e16 Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Wed, 25 Sep 2024 14:32:56 +0900 Subject: [PATCH 06/10] Update README --- packages/solidstart/README.md | 133 +++++++++++++++++++++++++--------- 1 file changed, 97 insertions(+), 36 deletions(-) diff --git a/packages/solidstart/README.md b/packages/solidstart/README.md index ceda55838e8d..3bf1663e6c74 100644 --- a/packages/solidstart/README.md +++ b/packages/solidstart/README.md @@ -101,16 +101,109 @@ export default defineConfig({ The Sentry middleware enhances the data collected by Sentry on the server side by enabling distributed tracing between the client and server. -### 5. Run your application +### 5. Configure your application + +For Sentry to work properly, SolidStart's `app.config.ts` has to be modified. + +#### 5.1 Wrapping the config with `withSentry` + +Add `withSentry` from `@sentry/solidstart` and wrap SolidStart's config inside `app.config.ts`. + +```typescript +import { defineConfig } from '@solidjs/start/config' +import { withSentry } from "@sentry/solidstart"; + +export default defineConfig(withSentry({ + // ... + middleware: './src/middleware.ts', +})) + +``` + +#### 5.2 Generate source maps and build `instrument.server.ts` + +Sentry relies on running `instrument.server.ts` as early as possible. Add the `sentrySolidStartVite` plugin +from `@sentry/solidstart` to your `app.config.ts`. This takes care of building `instrument.server.ts` and placing it alongside the server entry file. + +To upload source maps, configure an auth token. Auth tokens can be passed to the plugin explicitly with the `authToken` option, with a +`SENTRY_AUTH_TOKEN` environment variable, or with an `.env.sentry-build-plugin` file in the working directory when +building your project. We recommend you add the auth token to your CI/CD environment as an environment variable. + +Learn more about configuring the plugin in our +[Sentry Vite Plugin documentation](https://www.npmjs.com/package/@sentry/vite-plugin). + +```typescript +// app.config.ts +import { defineConfig } from '@solidjs/start/config'; +import { sentrySolidStartVite, withSentry } from '@sentry/solidstart'; + +export default defineConfig(withSentry({ + // ... + middleware: './src/middleware.ts', + vite: { + plugins: [ + sentrySolidStartVite({ + org: process.env.SENTRY_ORG, + project: process.env.SENTRY_PROJECT, + authToken: process.env.SENTRY_AUTH_TOKEN, + debug: true, + }), + ], + }, + // ... +})); +``` + +### 6. Run your application Then run your app ```bash -NODE_OPTIONS='--import=./instrument.server.mjs' yarn start -# or -NODE_OPTIONS='--require=./instrument.server.js' yarn start +NODE_OPTIONS='--import=./.output/server/instrument.server.mjs' yarn start +``` + +⚠️ **Note build presets** ⚠️ +Depending on [build preset](https://nitro.unjs.io/deploy), the location of `instrument.server.mjs` differs. +To find out where `instrument.server.mjs` is located, monitor the build log output for + +```bash +[Sentry SolidStart withSentry] Successfully created /my/project/path/.output/server/instrument.server.mjs. +``` + + +⚠️ **Note for platforms without the ability to modify `NODE_OPTIONS` or use `--import`** ⚠️ +Depending on where the application is deployed to, it might not be possible to modify or use `NODE_OPTIONS` to +import `instrument.server.mjs`. + +For such platforms, we offer the `experimental_basicServerTracing` flag to add a top +level import of `instrument.server.mjs` to the server entry file. + +```typescript +// app.config.ts +import { defineConfig } from '@solidjs/start/config'; +import { sentrySolidStartVite, withSentry } from '@sentry/solidstart'; + +export default defineConfig(withSentry({ + // ... + middleware: './src/middleware.ts', + vite: { + plugins: [ + sentrySolidStartVite({ + org: process.env.SENTRY_ORG, + project: process.env.SENTRY_PROJECT, + authToken: process.env.SENTRY_AUTH_TOKEN, + debug: true, + }), + ], + }, + // ... +}, { experimental_basicServerTracing: true })); ``` +This has a **fundamental restriction**: It only supports limited performance instrumentation. +**Only basic http instrumentation** will work, and no DB or framework-specific instrumentation will be available. + + # Solid Router The Solid Router instrumentation uses the Solid Router library to create navigation spans to ensure you collect @@ -156,35 +249,3 @@ render( document.getElementById('root'), ); ``` - -## Uploading Source Maps - -To upload source maps, add the `sentrySolidStartVite` plugin from `@sentry/solidstart` to your `app.config.ts` and -configure an auth token. Auth tokens can be passed to the plugin explicitly with the `authToken` option, with a -`SENTRY_AUTH_TOKEN` environment variable, or with an `.env.sentry-build-plugin` file in the working directory when -building your project. We recommend you add the auth token to your CI/CD environment as an environment variable. - -Learn more about configuring the plugin in our -[Sentry Vite Plugin documentation](https://www.npmjs.com/package/@sentry/vite-plugin). - -```typescript -// app.config.ts -import { defineConfig } from '@solidjs/start/config'; -import { sentrySolidStartVite } from '@sentry/solidstart'; - -export default defineConfig({ - // ... - - vite: { - plugins: [ - sentrySolidStartVite({ - org: process.env.SENTRY_ORG, - project: process.env.SENTRY_PROJECT, - authToken: process.env.SENTRY_AUTH_TOKEN, - debug: true, - }), - ], - }, - // ... -}); -``` From a57a1980e3fbd0ad69bb4e5059d9d8d84919a72d Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Wed, 25 Sep 2024 14:42:36 +0900 Subject: [PATCH 07/10] Document optional instrumentation file path option --- packages/solidstart/README.md | 119 ++++++++++--------- packages/solidstart/src/config/types.ts | 2 +- packages/solidstart/src/config/withSentry.ts | 4 +- packages/solidstart/src/vite/types.ts | 6 +- 4 files changed, 72 insertions(+), 59 deletions(-) diff --git a/packages/solidstart/README.md b/packages/solidstart/README.md index 3bf1663e6c74..882ebd111963 100644 --- a/packages/solidstart/README.md +++ b/packages/solidstart/README.md @@ -60,7 +60,7 @@ mount(() => , document.getElementById('app')); ### 3. Server-side Setup -Create an instrument file named `instrument.server.mjs` and add your initialization code for the server-side SDK. +Create an instrument file named `src/instrument.server.ts` and add your initialization code for the server-side SDK. ```javascript import * as Sentry from '@sentry/solidstart'; @@ -110,24 +110,30 @@ For Sentry to work properly, SolidStart's `app.config.ts` has to be modified. Add `withSentry` from `@sentry/solidstart` and wrap SolidStart's config inside `app.config.ts`. ```typescript -import { defineConfig } from '@solidjs/start/config' -import { withSentry } from "@sentry/solidstart"; - -export default defineConfig(withSentry({ - // ... - middleware: './src/middleware.ts', -})) +import { defineConfig } from '@solidjs/start/config'; +import { withSentry } from '@sentry/solidstart'; +export default defineConfig( + withSentry({ + // ... + middleware: './src/middleware.ts', + }), +); ``` #### 5.2 Generate source maps and build `instrument.server.ts` -Sentry relies on running `instrument.server.ts` as early as possible. Add the `sentrySolidStartVite` plugin -from `@sentry/solidstart` to your `app.config.ts`. This takes care of building `instrument.server.ts` and placing it alongside the server entry file. +Sentry relies on running `instrument.server.ts` as early as possible. Add the `sentrySolidStartVite` plugin from +`@sentry/solidstart` to your `app.config.ts`. This takes care of building `instrument.server.ts` and placing it +alongside the server entry file. -To upload source maps, configure an auth token. Auth tokens can be passed to the plugin explicitly with the `authToken` option, with a -`SENTRY_AUTH_TOKEN` environment variable, or with an `.env.sentry-build-plugin` file in the working directory when -building your project. We recommend you add the auth token to your CI/CD environment as an environment variable. +If your `instrument.server.ts` file is not located in the `src` folder, you can specify the path via the +`sentrySolidStartVite` plugin. + +To upload source maps, configure an auth token. Auth tokens can be passed to the plugin explicitly with the `authToken` +option, with a `SENTRY_AUTH_TOKEN` environment variable, or with an `.env.sentry-build-plugin` file in the working +directory when building your project. We recommend you add the auth token to your CI/CD environment as an environment +variable. Learn more about configuring the plugin in our [Sentry Vite Plugin documentation](https://www.npmjs.com/package/@sentry/vite-plugin). @@ -137,21 +143,25 @@ Learn more about configuring the plugin in our import { defineConfig } from '@solidjs/start/config'; import { sentrySolidStartVite, withSentry } from '@sentry/solidstart'; -export default defineConfig(withSentry({ - // ... - middleware: './src/middleware.ts', - vite: { - plugins: [ - sentrySolidStartVite({ - org: process.env.SENTRY_ORG, - project: process.env.SENTRY_PROJECT, - authToken: process.env.SENTRY_AUTH_TOKEN, - debug: true, - }), - ], - }, - // ... -})); +export default defineConfig( + withSentry({ + // ... + middleware: './src/middleware.ts', + vite: { + plugins: [ + sentrySolidStartVite({ + org: process.env.SENTRY_ORG, + project: process.env.SENTRY_PROJECT, + authToken: process.env.SENTRY_AUTH_TOKEN, + debug: true, + // optional: if your `instrument.server.ts` file is not located inside `src` + instrumentation: './mypath/instrument.server.ts', + }), + ], + }, + // ... + }), +); ``` ### 6. Run your application @@ -163,46 +173,49 @@ NODE_OPTIONS='--import=./.output/server/instrument.server.mjs' yarn start ``` ⚠️ **Note build presets** ⚠️ -Depending on [build preset](https://nitro.unjs.io/deploy), the location of `instrument.server.mjs` differs. -To find out where `instrument.server.mjs` is located, monitor the build log output for +Depending on [build preset](https://nitro.unjs.io/deploy), the location of `instrument.server.mjs` differs. To find out +where `instrument.server.mjs` is located, monitor the build log output for ```bash [Sentry SolidStart withSentry] Successfully created /my/project/path/.output/server/instrument.server.mjs. ``` - ⚠️ **Note for platforms without the ability to modify `NODE_OPTIONS` or use `--import`** ⚠️ -Depending on where the application is deployed to, it might not be possible to modify or use `NODE_OPTIONS` to -import `instrument.server.mjs`. +Depending on where the application is deployed to, it might not be possible to modify or use `NODE_OPTIONS` to import +`instrument.server.mjs`. -For such platforms, we offer the `experimental_basicServerTracing` flag to add a top -level import of `instrument.server.mjs` to the server entry file. +For such platforms, we offer the `experimental_basicServerTracing` flag to add a top level import of +`instrument.server.mjs` to the server entry file. ```typescript // app.config.ts import { defineConfig } from '@solidjs/start/config'; import { sentrySolidStartVite, withSentry } from '@sentry/solidstart'; -export default defineConfig(withSentry({ - // ... - middleware: './src/middleware.ts', - vite: { - plugins: [ - sentrySolidStartVite({ - org: process.env.SENTRY_ORG, - project: process.env.SENTRY_PROJECT, - authToken: process.env.SENTRY_AUTH_TOKEN, - debug: true, - }), - ], - }, - // ... -}, { experimental_basicServerTracing: true })); +export default defineConfig( + withSentry( + { + // ... + middleware: './src/middleware.ts', + vite: { + plugins: [ + sentrySolidStartVite({ + org: process.env.SENTRY_ORG, + project: process.env.SENTRY_PROJECT, + authToken: process.env.SENTRY_AUTH_TOKEN, + debug: true, + }), + ], + }, + // ... + }, + { experimental_basicServerTracing: true }, + ), +); ``` -This has a **fundamental restriction**: It only supports limited performance instrumentation. -**Only basic http instrumentation** will work, and no DB or framework-specific instrumentation will be available. - +This has a **fundamental restriction**: It only supports limited performance instrumentation. **Only basic http +instrumentation** will work, and no DB or framework-specific instrumentation will be available. # Solid Router diff --git a/packages/solidstart/src/config/types.ts b/packages/solidstart/src/config/types.ts index e9623313f438..2c67942c8a4d 100644 --- a/packages/solidstart/src/config/types.ts +++ b/packages/solidstart/src/config/types.ts @@ -13,7 +13,7 @@ export type Nitro = { export type SolidStartInlineConfig = Parameters[0]; -export type SolidStartInlineConfigNitroHooks = { +export type SolidStartInlineServerConfig = { hooks?: { close?: () => unknown; 'rollup:before'?: (nitro: Nitro) => unknown; diff --git a/packages/solidstart/src/config/withSentry.ts b/packages/solidstart/src/config/withSentry.ts index d27d3028fc50..3e2e631af8be 100644 --- a/packages/solidstart/src/config/withSentry.ts +++ b/packages/solidstart/src/config/withSentry.ts @@ -6,7 +6,7 @@ import type { Nitro, SentrySolidStartConfigOptions, SolidStartInlineConfig, - SolidStartInlineConfigNitroHooks, + SolidStartInlineServerConfig, } from './types'; /** @@ -22,7 +22,7 @@ export const withSentry = ( solidStartConfig: SolidStartInlineConfig = {}, sentrySolidStartConfigOptions: SentrySolidStartConfigOptions = {}, ): SolidStartInlineConfig => { - const server = (solidStartConfig.server || {}) as SolidStartInlineConfigNitroHooks; + const server = (solidStartConfig.server || {}) as SolidStartInlineServerConfig; const hooks = server.hooks || {}; let serverDir: string; diff --git a/packages/solidstart/src/vite/types.ts b/packages/solidstart/src/vite/types.ts index a725478aae7b..fdf252471de1 100644 --- a/packages/solidstart/src/vite/types.ts +++ b/packages/solidstart/src/vite/types.ts @@ -127,10 +127,10 @@ export type SentrySolidStartPluginOptions = { debug?: boolean; /** - * The path to your `instrumentation.server.ts|js` file. - * e.g. './src/instrumentation.server.ts` + * The path to your `instrument.server.ts|js` file. + * e.g. `./src/instrument.server.ts` * - * Defaults to: `./src/instrumentation.server.ts` + * Defaults to: `./src/instrument.server.ts` */ instrumentation?: string; }; From cef94cba8da4f368506a8323d1037a168f7aabde Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Fri, 27 Sep 2024 12:30:48 +0900 Subject: [PATCH 08/10] Add `sentrySolidStartVite` plugin when using `withSentry` --- packages/solidstart/src/config/index.ts | 1 + packages/solidstart/src/config/withSentry.ts | 20 +++-- .../src/vite/sentrySolidStartVite.ts | 15 +++- packages/solidstart/src/vite/types.ts | 14 ++- .../solidstart/test/config/withSentry.test.ts | 85 ++++++++++++++++++- 5 files changed, 122 insertions(+), 13 deletions(-) diff --git a/packages/solidstart/src/config/index.ts b/packages/solidstart/src/config/index.ts index 4949f4bdf523..4cf4b985c18a 100644 --- a/packages/solidstart/src/config/index.ts +++ b/packages/solidstart/src/config/index.ts @@ -1 +1,2 @@ export * from './withSentry'; +export type { Nitro, SentrySolidStartConfigOptions } from './types'; diff --git a/packages/solidstart/src/config/withSentry.ts b/packages/solidstart/src/config/withSentry.ts index 3e2e631af8be..a91d7e4458ad 100644 --- a/packages/solidstart/src/config/withSentry.ts +++ b/packages/solidstart/src/config/withSentry.ts @@ -1,13 +1,10 @@ +import { addSentryPluginToVite } from '../vite'; +import type { SentrySolidStartPluginOptions } from '../vite/types'; import { addInstrumentationFileToBuild, experimental_addInstrumentationFileTopLevelImportToServerEntry, } from './addInstrumentation'; -import type { - Nitro, - SentrySolidStartConfigOptions, - SolidStartInlineConfig, - SolidStartInlineServerConfig, -} from './types'; +import type { Nitro, SolidStartInlineConfig, SolidStartInlineServerConfig } from './types'; /** * Modifies the passed in Solid Start configuration with build-time enhancements such as @@ -15,27 +12,32 @@ import type { * build preset. * * @param solidStartConfig A Solid Start configuration object, as usually passed to `defineConfig` in `app.config.ts|js` - * @param sentrySolidStartConfigOptions Options to configure the plugin + * @param sentrySolidStartPluginOptions Options to configure the plugin * @returns The modified config to be exported and passed back into `defineConfig` */ export const withSentry = ( solidStartConfig: SolidStartInlineConfig = {}, - sentrySolidStartConfigOptions: SentrySolidStartConfigOptions = {}, + sentrySolidStartPluginOptions: SentrySolidStartPluginOptions = {}, ): SolidStartInlineConfig => { const server = (solidStartConfig.server || {}) as SolidStartInlineServerConfig; const hooks = server.hooks || {}; + const vite = + typeof solidStartConfig.vite === 'function' + ? (...args: unknown[]) => addSentryPluginToVite(solidStartConfig.vite(...args), sentrySolidStartPluginOptions) + : addSentryPluginToVite(solidStartConfig.vite, sentrySolidStartPluginOptions); let serverDir: string; let buildPreset: string; return { ...solidStartConfig, + vite, server: { ...server, hooks: { ...hooks, async close() { - if (sentrySolidStartConfigOptions.experimental_basicServerTracing) { + if (sentrySolidStartPluginOptions.experimental_basicServerTracing) { await experimental_addInstrumentationFileTopLevelImportToServerEntry(serverDir, buildPreset); } diff --git a/packages/solidstart/src/vite/sentrySolidStartVite.ts b/packages/solidstart/src/vite/sentrySolidStartVite.ts index 35a7da456b06..227a303b0ad4 100644 --- a/packages/solidstart/src/vite/sentrySolidStartVite.ts +++ b/packages/solidstart/src/vite/sentrySolidStartVite.ts @@ -1,4 +1,4 @@ -import type { Plugin } from 'vite'; +import type { Plugin, UserConfig } from 'vite'; import { makeBuildInstrumentationFilePlugin } from './buildInstrumentationFile'; import { makeSourceMapsVitePlugin } from './sourceMaps'; import type { SentrySolidStartPluginOptions } from './types'; @@ -25,3 +25,16 @@ export const sentrySolidStartVite = (options: SentrySolidStartPluginOptions = {} return sentryPlugins; }; + +/** + * Helper to add the Sentry SolidStart vite plugin to a vite config. + */ +export const addSentryPluginToVite = (config: UserConfig = {}, options: SentrySolidStartPluginOptions): UserConfig => { + const plugins = Array.isArray(config.plugins) ? [...config.plugins] : []; + plugins.unshift(sentrySolidStartVite(options)); + + return { + ...config, + plugins, + }; +}; diff --git a/packages/solidstart/src/vite/types.ts b/packages/solidstart/src/vite/types.ts index fdf252471de1..c31e901efc2e 100644 --- a/packages/solidstart/src/vite/types.ts +++ b/packages/solidstart/src/vite/types.ts @@ -85,7 +85,7 @@ type BundleSizeOptimizationOptions = { }; /** - * Build options for the Sentry module. These options are used during build-time by the Sentry SDK. + * Build options for the Sentry plugin. These options are used during build-time by the Sentry SDK. */ export type SentrySolidStartPluginOptions = { /** @@ -133,4 +133,16 @@ export type SentrySolidStartPluginOptions = { * Defaults to: `./src/instrument.server.ts` */ instrumentation?: string; + + /** + * Enabling basic server tracing can be used for environments where modifying the node option `--import` is not possible. + * However, enabling this option only supports limited tracing instrumentation. Only http traces will be collected (but no database-specific traces etc.). + * + * If this option is `true`, the Sentry SDK will import the instrumentation.server.ts|js file at the top of the server entry file to load the SDK on the server. + * + * **DO NOT** enable this option if you've already added the node option `--import` in your node start script. This would initialize Sentry twice on the server-side and leads to unexpected issues. + * + * @default false + */ + experimental_basicServerTracing?: boolean; }; diff --git a/packages/solidstart/test/config/withSentry.test.ts b/packages/solidstart/test/config/withSentry.test.ts index faf54e4e4679..52ebb2449c25 100644 --- a/packages/solidstart/test/config/withSentry.test.ts +++ b/packages/solidstart/test/config/withSentry.test.ts @@ -1,5 +1,6 @@ -import { beforeEach, describe, it, vi } from 'vitest'; -import type { Nitro } from '../../build/types/config/types'; +import type { Plugin } from 'vite'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import type { Nitro } from '../../src/config'; import { withSentry } from '../../src/config'; const userDefinedNitroRollupBeforeHookMock = vi.fn(); @@ -62,4 +63,84 @@ describe('withSentry()', () => { expect(experimental_addInstrumentationFileTopLevelImportToServerEntryMock).not.toHaveBeenCalled(); expect(userDefinedNitroCloseHookMock).toHaveBeenCalled(); }); + + it('adds the sentry solidstart vite plugin', () => { + const config = withSentry(solidStartConfig, { + project: 'project', + org: 'org', + authToken: 'token', + }); + const names = config?.vite.plugins.flat().map((plugin: Plugin) => plugin.name); + expect(names).toEqual([ + 'sentry-solidstart-source-maps', + 'sentry-telemetry-plugin', + 'sentry-vite-release-injection-plugin', + 'sentry-debug-id-upload-plugin', + 'sentry-vite-debug-id-injection-plugin', + 'sentry-vite-debug-id-upload-plugin', + 'sentry-file-deletion-plugin', + 'sentry-solidstart-build-instrumentation-file', + ]); + }); + + it('extends the passed in vite config object', () => { + const config = withSentry( + { + ...solidStartConfig, + vite: { + plugins: [{ name: 'my-test-plugin' }], + }, + }, + { + project: 'project', + org: 'org', + authToken: 'token', + }, + ); + + const names = config?.vite.plugins.flat().map((plugin: Plugin) => plugin.name); + expect(names).toEqual([ + 'sentry-solidstart-source-maps', + 'sentry-telemetry-plugin', + 'sentry-vite-release-injection-plugin', + 'sentry-debug-id-upload-plugin', + 'sentry-vite-debug-id-injection-plugin', + 'sentry-vite-debug-id-upload-plugin', + 'sentry-file-deletion-plugin', + 'sentry-solidstart-build-instrumentation-file', + 'my-test-plugin', + ]); + }); + + it('extends the passed in vite function config', () => { + const config = withSentry( + { + ...solidStartConfig, + vite() { + return { plugins: [{ name: 'my-test-plugin' }] }; + }, + }, + { + project: 'project', + org: 'org', + authToken: 'token', + }, + ); + + const names = config + ?.vite() + .plugins.flat() + .map((plugin: Plugin) => plugin.name); + expect(names).toEqual([ + 'sentry-solidstart-source-maps', + 'sentry-telemetry-plugin', + 'sentry-vite-release-injection-plugin', + 'sentry-debug-id-upload-plugin', + 'sentry-vite-debug-id-injection-plugin', + 'sentry-vite-debug-id-upload-plugin', + 'sentry-file-deletion-plugin', + 'sentry-solidstart-build-instrumentation-file', + 'my-test-plugin', + ]); + }); }); From 82e4c12eb62061e602ef7f115d28ac2039e3524d Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Fri, 27 Sep 2024 12:47:19 +0900 Subject: [PATCH 09/10] Update README --- packages/solidstart/README.md | 92 ++++++++++++----------------------- 1 file changed, 32 insertions(+), 60 deletions(-) diff --git a/packages/solidstart/README.md b/packages/solidstart/README.md index 882ebd111963..2ec876b35c8c 100644 --- a/packages/solidstart/README.md +++ b/packages/solidstart/README.md @@ -103,64 +103,39 @@ the client and server. ### 5. Configure your application -For Sentry to work properly, SolidStart's `app.config.ts` has to be modified. - -#### 5.1 Wrapping the config with `withSentry` - -Add `withSentry` from `@sentry/solidstart` and wrap SolidStart's config inside `app.config.ts`. - -```typescript -import { defineConfig } from '@solidjs/start/config'; -import { withSentry } from '@sentry/solidstart'; - -export default defineConfig( - withSentry({ - // ... - middleware: './src/middleware.ts', - }), -); -``` - -#### 5.2 Generate source maps and build `instrument.server.ts` - -Sentry relies on running `instrument.server.ts` as early as possible. Add the `sentrySolidStartVite` plugin from -`@sentry/solidstart` to your `app.config.ts`. This takes care of building `instrument.server.ts` and placing it -alongside the server entry file. +For Sentry to work properly, SolidStart's `app.config.ts` has to be modified. Wrap your config with `withSentry` and +configure it to upload source maps. If your `instrument.server.ts` file is not located in the `src` folder, you can specify the path via the -`sentrySolidStartVite` plugin. +`instrumentation` option to `withSentry`. -To upload source maps, configure an auth token. Auth tokens can be passed to the plugin explicitly with the `authToken` -option, with a `SENTRY_AUTH_TOKEN` environment variable, or with an `.env.sentry-build-plugin` file in the working -directory when building your project. We recommend you add the auth token to your CI/CD environment as an environment -variable. +To upload source maps, configure an auth token. Auth tokens can be passed explicitly with the `authToken` option, with a +`SENTRY_AUTH_TOKEN` environment variable, or with an `.env.sentry-build-plugin` file in the working directory when +building your project. We recommend adding the auth token to your CI/CD environment as an environment variable. Learn more about configuring the plugin in our [Sentry Vite Plugin documentation](https://www.npmjs.com/package/@sentry/vite-plugin). ```typescript -// app.config.ts import { defineConfig } from '@solidjs/start/config'; -import { sentrySolidStartVite, withSentry } from '@sentry/solidstart'; +import { withSentry } from '@sentry/solidstart'; export default defineConfig( - withSentry({ - // ... - middleware: './src/middleware.ts', - vite: { - plugins: [ - sentrySolidStartVite({ - org: process.env.SENTRY_ORG, - project: process.env.SENTRY_PROJECT, - authToken: process.env.SENTRY_AUTH_TOKEN, - debug: true, - // optional: if your `instrument.server.ts` file is not located inside `src` - instrumentation: './mypath/instrument.server.ts', - }), - ], + withSentry( + { + // SolidStart config + middleware: './src/middleware.ts', + }, + { + // Sentry `withSentry` options + org: process.env.SENTRY_ORG, + project: process.env.SENTRY_PROJECT, + authToken: process.env.SENTRY_AUTH_TOKEN, + debug: true, + // optional: if your `instrument.server.ts` file is not located inside `src` + instrumentation: './mypath/instrument.server.ts', }, - // ... - }), + ), ); ``` @@ -188,28 +163,25 @@ For such platforms, we offer the `experimental_basicServerTracing` flag to add a `instrument.server.mjs` to the server entry file. ```typescript -// app.config.ts import { defineConfig } from '@solidjs/start/config'; -import { sentrySolidStartVite, withSentry } from '@sentry/solidstart'; +import { withSentry } from '@sentry/solidstart'; export default defineConfig( withSentry( { // ... middleware: './src/middleware.ts', - vite: { - plugins: [ - sentrySolidStartVite({ - org: process.env.SENTRY_ORG, - project: process.env.SENTRY_PROJECT, - authToken: process.env.SENTRY_AUTH_TOKEN, - debug: true, - }), - ], - }, - // ... }, - { experimental_basicServerTracing: true }, + { + org: process.env.SENTRY_ORG, + project: process.env.SENTRY_PROJECT, + authToken: process.env.SENTRY_AUTH_TOKEN, + debug: true, + // optional: if your `instrument.server.ts` file is not located inside `src` + instrumentation: './mypath/instrument.server.ts', + // optional: if NODE_OPTIONS or --import is not avaiable + experimental_basicServerTracing: true, + }, ), ); ``` From 718d561b887e36ab92140bd8bd4da4e7230025f1 Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Fri, 27 Sep 2024 15:53:12 +0900 Subject: [PATCH 10/10] Add reload workoarund for dev server hydration error :( --- .../solidstart/app.config.ts | 27 ++++++++++++++----- .../solidstart/tests/errorboundary.test.ts | 2 ++ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/solidstart/app.config.ts b/dev-packages/e2e-tests/test-applications/solidstart/app.config.ts index 0b9a5553fb0a..337068467558 100644 --- a/dev-packages/e2e-tests/test-applications/solidstart/app.config.ts +++ b/dev-packages/e2e-tests/test-applications/solidstart/app.config.ts @@ -1,8 +1,23 @@ -import { sentrySolidStartVite } from '@sentry/solidstart'; +import { withSentry } from '@sentry/solidstart'; import { defineConfig } from '@solidjs/start/config'; -export default defineConfig({ - vite: { - plugins: [sentrySolidStartVite()], - }, -}); +export default defineConfig( + withSentry( + {}, + { + // Typically we want to default to ./src/instrument.sever.ts + // `withSentry` would then build and copy the file over to + // the .output folder, but since we can't use the production + // server for our e2e tests, we have to delete the build folders + // prior to using the dev server for our tests. Which also gets + // rid of the instrument.server.mjs file that we need to --import. + // Therefore, we specify the .mjs file here and to ensure + // `withSentry` gets its file to build and we continue to reference + // the file from the `src` folder for --import without needing to + // transpile before. + // This can be removed once we get the production server to work + // with our e2e tests. + instrumentation: './src/instrument.server.mjs', + }, + ), +); diff --git a/dev-packages/e2e-tests/test-applications/solidstart/tests/errorboundary.test.ts b/dev-packages/e2e-tests/test-applications/solidstart/tests/errorboundary.test.ts index b709760aab94..088f69df6380 100644 --- a/dev-packages/e2e-tests/test-applications/solidstart/tests/errorboundary.test.ts +++ b/dev-packages/e2e-tests/test-applications/solidstart/tests/errorboundary.test.ts @@ -11,6 +11,8 @@ test('captures an exception', async ({ page }) => { }); await page.goto('/error-boundary'); + // The first page load causes a hydration error on the dev server sometimes - a reload works around this + await page.reload(); await page.locator('#caughtErrorBtn').click(); const errorEvent = await errorEventPromise;