Skip to content

Commit 3c437d1

Browse files
committed
feat(solidstart): Add sentrySolidStartVite plugin
1 parent 5b9d3bb commit 3c437d1

12 files changed

+301
-40
lines changed

packages/solidstart/.eslintrc.js

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ module.exports = {
1414
files: ['src/vite/**', 'src/server/**'],
1515
rules: {
1616
'@sentry-internal/sdk/no-optional-chaining': 'off',
17+
'@sentry-internal/sdk/no-nullish-coalescing': 'off',
1718
},
1819
},
1920
],

packages/solidstart/README.md

+16-38
Original file line numberDiff line numberDiff line change
@@ -157,58 +157,36 @@ render(
157157
);
158158
```
159159

160-
# Sourcemaps and Releases
160+
## Uploading Source Maps
161161

162-
To generate and upload source maps of your Solid Start app use our Vite bundler plugin.
163-
164-
1. Install the Sentry Vite plugin
165-
166-
```bash
167-
# Using npm
168-
npm install @sentry/vite-plugin --save-dev
169-
170-
# Using yarn
171-
yarn add @sentry/vite-plugin --dev
172-
```
173-
174-
2. Configure the vite plugin
175-
176-
To upload source maps you have to configure an auth token. Auth tokens can be passed to the plugin explicitly with the
177-
`authToken` option, with a `SENTRY_AUTH_TOKEN` environment variable, or with an `.env.sentry-build-plugin` file in the
178-
working directory when building your project. We recommend you add the auth token to your CI/CD environment as an
179-
environment variable.
162+
To upload source maps, add the `sentrySolidStartVite` plugin from `@sentry/solidstart` to your `app.config.ts` and configure
163+
an auth token. Auth tokens can be passed to the plugin explicitly with the `authToken` option, with a
164+
`SENTRY_AUTH_TOKEN` environment variable, or with an `.env.sentry-build-plugin` file in the working directory when
165+
building your project. We recommend you add the auth token to your CI/CD environment as an environment variable.
180166

181167
Learn more about configuring the plugin in our
182168
[Sentry Vite Plugin documentation](https://www.npmjs.com/package/@sentry/vite-plugin).
183169

184-
```bash
185-
// .env.sentry-build-plugin
186-
SENTRY_AUTH_TOKEN=<your auth token>
187-
SENTRY_ORG=<your org>
188-
SENTRY_PROJECT=<your project name>
189-
```
190-
191-
3. Finally, add the plugin to your `app.config.ts` file.
192-
193-
```javascript
170+
```typescript
171+
// app.config.ts
194172
import { defineConfig } from '@solidjs/start/config';
195-
import { sentryVitePlugin } from '@sentry/vite-plugin';
173+
import { sentrySolidStartVite } from '@sentry/solidstart';
196174

197175
export default defineConfig({
198-
// rest of your config
199176
// ...
200177

201178
vite: {
202-
build: {
203-
sourcemap: true,
204-
},
205179
plugins: [
206-
sentryVitePlugin({
207-
org: process.env.SENTRY_ORG,
208-
project: process.env.SENTRY_PROJECT,
209-
authToken: process.env.SENTRY_AUTH_TOKEN,
180+
sentrySolidStartVite({
181+
sourceMapsUploadOptions: {
182+
org: process.env.SENTRY_ORG,
183+
project: process.env.SENTRY_PROJECT,
184+
authToken: process.env.SENTRY_AUTH_TOKEN,
185+
},
186+
debug: true,
210187
}),
211188
],
212189
},
190+
// ...
213191
});
214192
```

packages/solidstart/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@
8484
"@sentry/solid": "8.26.0",
8585
"@sentry/types": "8.26.0",
8686
"@sentry/utils": "8.26.0",
87-
"@sentry/vite-plugin": "2.19.0"
87+
"@sentry/vite-plugin": "2.22.2"
8888
},
8989
"devDependencies": {
9090
"@solidjs/router": "^0.13.4",
+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export * from './server';
2+
export * from './vite';

packages/solidstart/src/index.types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// exports in this file - which we do below.
44
export * from './client';
55
export * from './server';
6+
export * from './vite';
67

78
import type { Integration, Options, StackParser } from '@sentry/types';
89

packages/solidstart/src/vite/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './sentrySolidStartVite';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import type { Plugin } from 'vite';
2+
import { makeSourceMapsVitePlugin } from './sourceMaps';
3+
import type { SentrySolidStartPluginOptions } from './types';
4+
5+
/**
6+
* Various Sentry vite plugins to be used for SolidStart.
7+
*/
8+
export const sentrySolidStartVite = (options: SentrySolidStartPluginOptions): Plugin[] => {
9+
const sentryPlugins: Plugin[] = [];
10+
11+
if (process.env.NODE_ENV !== 'development') {
12+
if (options.sourceMapsUploadOptions?.enabled ?? true) {
13+
sentryPlugins.push(...makeSourceMapsVitePlugin({ ...options.sourceMapsUploadOptions, debug: options.debug }));
14+
}
15+
}
16+
17+
return sentryPlugins;
18+
};
+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { sentryVitePlugin } from '@sentry/vite-plugin';
2+
import type { Plugin } from 'vite';
3+
import type { SourceMapsOptions } from './types';
4+
5+
/**
6+
* A Sentry plugin for SolidStart to enable source maps and use
7+
* @sentry/vite-plugin to automatically upload source maps to Sentry.
8+
* @param {SourceMapsOptions} options
9+
*/
10+
export function makeSourceMapsVitePlugin(options: SourceMapsOptions): Plugin[] {
11+
return [
12+
{
13+
name: 'sentry-solidstart-source-maps',
14+
apply: 'build',
15+
enforce: 'post',
16+
config(config) {
17+
const sourceMapsPreviouslyNotEnabled = !config.build?.sourcemap;
18+
if (options.debug && sourceMapsPreviouslyNotEnabled) {
19+
// eslint-disable-next-line no-console
20+
console.log('[Sentry SolidStart Plugin] Enabling source map generation');
21+
if (!options.sourcemaps?.filesToDeleteAfterUpload) {
22+
// eslint-disable-next-line no-console
23+
console.warn(
24+
`[Sentry SolidStart PLugin] We recommend setting the \`sourceMapsUploadOptions.sourcemaps.filesToDeleteAfterUpload\` option to clean up source maps after uploading.
25+
[Sentry SolidStart Plugin] Otherwise, source maps might be deployed to production, depending on your configuration`,
26+
);
27+
}
28+
}
29+
return {
30+
...config,
31+
build: {
32+
...config.build,
33+
sourcemap: true,
34+
},
35+
};
36+
},
37+
},
38+
...sentryVitePlugin({
39+
org: options.org ?? process.env.SENTRY_ORG,
40+
project: options.project ?? process.env.SENTRY_PROJECT,
41+
authToken: options.authToken ?? process.env.SENTRY_AUTH_TOKEN,
42+
telemetry: options.telemetry ?? true,
43+
sourcemaps: {
44+
assets: options.sourcemaps?.assets ?? undefined,
45+
ignore: options.sourcemaps?.ignore ?? undefined,
46+
filesToDeleteAfterUpload: options.sourcemaps?.filesToDeleteAfterUpload ?? undefined,
47+
},
48+
_metaOptions: {
49+
telemetry: {
50+
metaFramework: 'solidstart',
51+
},
52+
},
53+
debug: options.debug ?? false,
54+
}),
55+
];
56+
}

packages/solidstart/src/vite/types.ts

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
export type SourceMapsOptions = {
2+
/**
3+
* If this flag is `true`, and an auth token is detected, the Sentry SDK will
4+
* automatically generate and upload source maps to Sentry during a production build.
5+
*
6+
* @default true
7+
*/
8+
enabled?: boolean;
9+
10+
/**
11+
* The auth token to use when uploading source maps to Sentry.
12+
*
13+
* Instead of specifying this option, you can also set the `SENTRY_AUTH_TOKEN` environment variable.
14+
*
15+
* To create an auth token, follow this guide:
16+
* @see https://docs.sentry.io/product/accounts/auth-tokens/#organization-auth-tokens
17+
*/
18+
authToken?: string;
19+
20+
/**
21+
* The organization slug of your Sentry organization.
22+
* Instead of specifying this option, you can also set the `SENTRY_ORG` environment variable.
23+
*/
24+
org?: string;
25+
26+
/**
27+
* The project slug of your Sentry project.
28+
* Instead of specifying this option, you can also set the `SENTRY_PROJECT` environment variable.
29+
*/
30+
project?: string;
31+
32+
/**
33+
* If this flag is `true`, the Sentry plugin will collect some telemetry data and send it to Sentry.
34+
* It will not collect any sensitive or user-specific data.
35+
*
36+
* @default true
37+
*/
38+
telemetry?: boolean;
39+
40+
/**
41+
* Options related to sourcemaps
42+
*/
43+
sourcemaps?: {
44+
/**
45+
* A glob or an array of globs that specify the build artifacts and source maps that will be uploaded to Sentry.
46+
*
47+
* The globbing patterns must follow the implementation of the `glob` package.
48+
* @see https://www.npmjs.com/package/glob#glob-primer
49+
*/
50+
assets?: string | Array<string>;
51+
52+
/**
53+
* A glob or an array of globs that specifies which build artifacts should not be uploaded to Sentry.
54+
*
55+
* @default [] - By default no files are ignored. Thus, all files matching the `assets` glob
56+
* or the default value for `assets` are uploaded.
57+
*
58+
* The globbing patterns follow the implementation of the glob package. (https://www.npmjs.com/package/glob)
59+
*/
60+
ignore?: string | Array<string>;
61+
62+
/**
63+
* A glob or an array of globs that specifies the build artifacts that should be deleted after the artifact
64+
* upload to Sentry has been completed.
65+
*
66+
* @default [] - By default no files are deleted.
67+
*
68+
* The globbing patterns follow the implementation of the glob package. (https://www.npmjs.com/package/glob)
69+
*/
70+
filesToDeleteAfterUpload?: string | Array<string>;
71+
};
72+
73+
/**
74+
* Enable debug functionality of the SDK during build-time.
75+
* Enabling this will give you logs about source maps.
76+
*/
77+
debug?: boolean;
78+
};
79+
80+
/**
81+
* Build options for the Sentry module. These options are used during build-time by the Sentry SDK.
82+
*/
83+
export type SentrySolidStartPluginOptions = {
84+
/**
85+
* Options for the Sentry Vite plugin to customize the source maps upload process.
86+
*/
87+
sourceMapsUploadOptions?: SourceMapsOptions;
88+
89+
/**
90+
* Enable debug functionality of the SDK during build-time.
91+
* Enabling this will give you, for example logs about source maps.
92+
*/
93+
debug?: boolean;
94+
};

packages/solidstart/test/server/withServerActionInstrumentation.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ import {
1010
spanToJSON,
1111
} from '@sentry/node';
1212
import { NodeClient } from '@sentry/node';
13-
import { solidRouterBrowserTracingIntegration } from '@sentry/solidstart/solidrouter';
1413
import { redirect } from '@solidjs/router';
1514
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
15+
import { solidRouterBrowserTracingIntegration } from '../../src/client/solidrouter';
1616

1717
const mockCaptureException = vi.spyOn(SentryNode, 'captureException').mockImplementation(() => '');
1818
const mockFlush = vi.spyOn(SentryNode, 'flush').mockImplementation(async () => true);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import type { Plugin } from 'vite';
2+
import { describe, expect, it, vi } from 'vitest';
3+
import { sentrySolidStartVite } from '../../src/vite/sentrySolidStartVite';
4+
5+
vi.spyOn(console, 'log').mockImplementation(() => {
6+
/* noop */
7+
});
8+
vi.spyOn(console, 'warn').mockImplementation(() => {
9+
/* noop */
10+
});
11+
12+
function getSentrySolidStartVitePlugins(options?: Parameters<typeof sentrySolidStartVite>[0]): Plugin[] {
13+
return sentrySolidStartVite({
14+
sourceMapsUploadOptions: {
15+
authToken: 'token',
16+
org: 'org',
17+
project: 'project',
18+
...options?.sourceMapsUploadOptions,
19+
},
20+
...options,
21+
});
22+
}
23+
24+
describe('sentrySolidStartVite()', () => {
25+
it('returns an array of vite plugins', () => {
26+
const plugins = getSentrySolidStartVitePlugins();
27+
const names = plugins.map(plugin => plugin.name);
28+
expect(names).toEqual([
29+
'sentry-solidstart-source-maps',
30+
'sentry-telemetry-plugin',
31+
'sentry-vite-release-injection-plugin',
32+
'sentry-debug-id-upload-plugin',
33+
'sentry-vite-debug-id-injection-plugin',
34+
'sentry-file-deletion-plugin',
35+
'sentry-vite-debug-id-upload-plugin',
36+
]);
37+
});
38+
39+
it("returns an empty array if source maps upload isn't enabled", () => {
40+
const plugins = getSentrySolidStartVitePlugins({ sourceMapsUploadOptions: { enabled: false } });
41+
expect(plugins).toHaveLength(0);
42+
});
43+
44+
it('returns an empty array if `NODE_ENV` is development', async () => {
45+
const previousEnv = process.env.NODE_ENV;
46+
process.env.NODE_ENV = 'development';
47+
48+
const plugins = getSentrySolidStartVitePlugins({ sourceMapsUploadOptions: { enabled: true } });
49+
expect(plugins).toHaveLength(0);
50+
51+
process.env.NODE_ENV = previousEnv;
52+
});
53+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { beforeEach, describe, expect, it, vi } from 'vitest';
2+
import { makeSourceMapsVitePlugin } from '../../src/vite/sourceMaps';
3+
import * as sourceMaps from '../../src/vite/sourceMaps';
4+
5+
const mockedSentryVitePlugin = {
6+
name: 'sentry-vite-debug-id-upload-plugin',
7+
writeBundle: vi.fn(),
8+
};
9+
10+
vi.mock('@sentry/vite-plugin', async () => {
11+
const original = (await vi.importActual('@sentry/vite-plugin')) as any;
12+
13+
return {
14+
...original,
15+
sentryVitePlugin: () => [mockedSentryVitePlugin],
16+
};
17+
});
18+
19+
beforeEach(() => {
20+
vi.clearAllMocks();
21+
});
22+
23+
describe('makeSourceMapsVitePlugin()', () => {
24+
it('returns a plugin to set `sourcemaps` to `true`', async () => {
25+
const [sourceMapsConfigPlugin, sentryVitePlugin] = makeSourceMapsVitePlugin({});
26+
27+
expect(sourceMapsConfigPlugin?.name).toEqual('sentry-solidstart-source-maps');
28+
expect(sourceMapsConfigPlugin?.apply).toEqual('build');
29+
expect(sourceMapsConfigPlugin?.enforce).toEqual('post');
30+
expect(sourceMapsConfigPlugin?.config).toEqual(expect.any(Function));
31+
32+
expect(sentryVitePlugin).toEqual(mockedSentryVitePlugin);
33+
});
34+
35+
it('passes user-specified vite plugin options to vite plugin plugin', async () => {
36+
const makePluginSpy = vi.spyOn(sourceMaps, 'makeSourceMapsVitePlugin');
37+
38+
makeSourceMapsVitePlugin({
39+
org: 'my-org',
40+
authToken: 'my-token',
41+
sourcemaps: {
42+
assets: ['foo/*.js'],
43+
ignore: ['bar/*.js'],
44+
filesToDeleteAfterUpload: ['baz/*.js'],
45+
},
46+
});
47+
48+
expect(makePluginSpy).toHaveBeenCalledWith({
49+
org: 'my-org',
50+
authToken: 'my-token',
51+
sourcemaps: {
52+
assets: ['foo/*.js'],
53+
ignore: ['bar/*.js'],
54+
filesToDeleteAfterUpload: ['baz/*.js'],
55+
},
56+
});
57+
});
58+
});

0 commit comments

Comments
 (0)