Skip to content

Commit 1ba8d9f

Browse files
Lms24mydea
andauthored
feat(astro): Add sentryAstro integration (#9218)
Add the `sentryAstro` Astro integration which will be the core component of the Astro SDK. The integration takes care of: * injecting SDK init code into the client and server entry points. * emitting source maps and adding the Sentry Vite plugin for source maps upload The idea is: Basic SDK setup will only require `sentryAstro` to be added by users. Everything else happens automatically. We also support a more custom setup but it will require additional config files. The reason is that we can only inject serialized code. We cannot take e.g. function callbacks (e.g. `beforeSend`) in the options of `sentryAstro`. --------- Co-authored-by: Francesco Novy <[email protected]>
1 parent 8bc4d73 commit 1ba8d9f

19 files changed

+638
-22
lines changed

packages/astro/.eslintrc.js renamed to packages/astro/.eslintrc.cjs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,12 @@ module.exports = {
1111
project: ['tsconfig.test.json'],
1212
},
1313
},
14+
{
15+
files: ['src/integration/**', 'src/server/**'],
16+
rules: {
17+
'@sentry-internal/sdk/no-optional-chaining': 'off',
18+
'@sentry-internal/sdk/no-nullish-coalescing': 'off',
19+
},
20+
},
1421
],
1522
};

packages/astro/.npmignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# The paths in this file are specified so that they align with the file structure in `./build` after this file is copied
2+
# into it by the prepack script `scripts/prepack.ts`.
3+
4+
*
5+
6+
!/cjs/**/*
7+
!/esm/**/*
8+
!/types/**/*
9+
!/types-ts3.8/**/*
10+
!/integration/**/*

packages/astro/README.md

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,98 @@ This package is a wrapper around `@sentry/node` for the server and `@sentry/brow
2828

2929
## Installation and Setup
3030

31-
TODO
31+
### 1. Registering the Sentry Astro integration:
32+
33+
Add the `sentryAstro` integration to your `astro.config.mjs` file:
34+
35+
```javascript
36+
import { sentryAstro } from "@sentry/astro/integration";
37+
38+
export default defineConfig({
39+
// Rest of your Astro project config
40+
integrations: [
41+
sentryAstro({
42+
dsn: '__DSN__',
43+
}),
44+
],
45+
})
46+
```
47+
48+
This is the easiest way to configure Sentry in an Astro project.
49+
You can pass a few additional options to `sentryAstro` but the SDK comes preconfigured in an opinionated way.
50+
If you want to fully customize your SDK setup, you can do so, too:
51+
52+
### 2. [Optional] Uploading Source Maps
53+
54+
To upload source maps to Sentry, simply add the `project` and `authToken` options to `sentryAstro`:
55+
56+
```js
57+
// astro.config.mjs
58+
import { sentryAstro } from "@sentry/astro/integration";
59+
60+
export default defineConfig({
61+
// Rest of your Astro project config
62+
integrations: [
63+
sentryAstro({
64+
dsn: '__DSN__',
65+
project: 'your-project-slug',
66+
authToken: import.meta.env('SENTRY_AUTH_TOKEN'),
67+
}),
68+
],
69+
})
70+
```
71+
72+
You can also define these values as environment variables in e.g. a `.env` file
73+
or in you CI configuration:
74+
75+
```sh
76+
SENTRY_PROJECT="your-project"
77+
SENTRY_AUTH_TOKEN="your-token"
78+
```
79+
80+
Follow [this guide](https://docs.sentry.io/product/accounts/auth-tokens/#organization-auth-tokens) to create an auth token.
81+
82+
### 3. [Optional] Advanced Configuration
83+
84+
To fully customize and configure Sentry in an Astro project, follow step 1 and in addition,
85+
add a `sentry.client.config.(js|ts)` and `sentry.server.config(js|ts)` file to the root directory of your project.
86+
Inside these files, you can call `Sentry.init()` and use the full range of Sentry options.
87+
88+
Configuring the client SDK:
89+
90+
```js
91+
// sentry.client.config.ts or sentry.server.config.ts
92+
import * as Sentry from "@sentry/astro";
93+
94+
Sentry.init({
95+
dsn: "__DSN__",
96+
beforeSend(event) {
97+
console.log("Sending event on the client");
98+
return event;
99+
},
100+
tracesSampler: () => {/* ... */}
101+
});
102+
```
103+
104+
**Important**: Once you created a sentry config file, the SDK options passed to `sentryAstro` will be ignored for the respective runtime. You can also only define create of the two files.
105+
106+
#### 3.1 Custom file location
107+
108+
If you want to move the `sentry.*.config` files to another location,
109+
you can specify the file path, relative to the project root, in `sentryAstro`:
110+
111+
```js
112+
// astro.config.mjs
113+
import { sentryAstro } from "@sentry/astro/integration";
114+
115+
export default defineConfig({
116+
// Rest of your Astro project config
117+
integrations: [
118+
sentryAstro({
119+
dsn: '__DSN__',
120+
clientInitPath: '.config/sentry.client.init.js',
121+
serverInitPath: '.config/sentry.server.init.js',
122+
}),
123+
],
124+
})
125+
```

packages/astro/package.json

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,33 @@
99
"engines": {
1010
"node": ">=18.14.1"
1111
},
12-
"main": "build/cjs/index.server.js",
12+
"type": "module",
13+
"main": "build/cjs/index.client.js",
1314
"module": "build/esm/index.server.js",
1415
"browser": "build/esm/index.client.js",
1516
"types": "build/types/index.types.d.ts",
17+
"exports": {
18+
".": {
19+
"node": "./build/esm/index.server.js",
20+
"browser": "./build/esm/index.client.js",
21+
"import": "./build/esm/index.client.js",
22+
"require": "./build/cjs/index.server.js",
23+
"types": "./build/types/index.types.d.ts"
24+
}
25+
},
1626
"publishConfig": {
1727
"access": "public"
1828
},
1929
"peerDependencies": {
20-
"astro": "1.x"
30+
"astro": "3.x"
2131
},
2232
"dependencies": {
2333
"@sentry/browser": "7.73.0",
2434
"@sentry/node": "7.73.0",
2535
"@sentry/core": "7.73.0",
2636
"@sentry/utils": "7.73.0",
27-
"@sentry/types": "7.73.0"
37+
"@sentry/types": "7.73.0",
38+
"@sentry/vite-plugin": "^2.8.0"
2839
},
2940
"devDependencies": {
3041
"astro": "^3.2.3",

packages/astro/rollup.npm.config.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
import { makeBaseNPMConfig, makeNPMConfigVariants } from '../../rollup/index.js';
22

3-
export default makeNPMConfigVariants(
3+
const variants = makeNPMConfigVariants(
44
makeBaseNPMConfig({
55
entrypoints: ['src/index.server.ts', 'src/index.client.ts'],
66
packageSpecificConfig: {
77
output: {
88
dynamicImportInCjs: true,
9+
exports: 'named',
910
},
1011
},
12+
// Astro is Node 18+ no need to add polyfills
13+
addPolyfills: false,
1114
}),
1215
);
16+
17+
export default variants;
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/* eslint-disable no-console */
2+
3+
import * as fse from 'fs-extra';
4+
import * as path from 'path';
5+
6+
const buildDir = path.resolve('build');
7+
const srcIntegrationDir = path.resolve(path.join('src', 'integration'));
8+
const destIntegrationDir = path.resolve(path.join(buildDir, 'integration'));
9+
10+
try {
11+
fse.copySync(srcIntegrationDir, destIntegrationDir, {
12+
filter: (src, _) => {
13+
return !src.endsWith('.md');
14+
},
15+
});
16+
console.log('\nCopied Astro integration to ./build/integration\n');
17+
} catch (e) {
18+
console.error('\nError while copying integration to build dir:');
19+
console.error(e);
20+
}

packages/astro/src/index.server.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
// Unfortunately, we cannot `export * from '@sentry/node'` because in prod builds,
33
// Vite puts these exports into a `default` property (Sentry.default) rather than
44
// on the top - level namespace.
5+
6+
import { sentryAstro } from './integration';
7+
58
// Hence, we export everything from the Node SDK explicitly:
69
export {
710
addGlobalEventProcessor,
@@ -58,3 +61,5 @@ export {
5861
export * from '@sentry/node';
5962

6063
export { init } from './server/sdk';
64+
65+
export default sentryAstro;
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/* eslint-disable no-console */
2+
import { sentryVitePlugin } from '@sentry/vite-plugin';
3+
import type { AstroIntegration } from 'astro';
4+
import * as fs from 'fs';
5+
import * as path from 'path';
6+
7+
import { buildClientSnippet, buildSdkInitFileImportSnippet, buildServerSnippet } from './snippets';
8+
import type { SentryOptions } from './types';
9+
10+
const PKG_NAME = '@sentry/astro';
11+
12+
export const sentryAstro = (options: SentryOptions = {}): AstroIntegration => {
13+
return {
14+
name: PKG_NAME,
15+
hooks: {
16+
'astro:config:setup': async ({ updateConfig, injectScript }) => {
17+
// The third param here enables loading of all env vars, regardless of prefix
18+
// see: https://main.vitejs.dev/config/#using-environment-variables-in-config
19+
20+
// TODO: Ideally, we want to load the environment with vite like this:
21+
// const env = loadEnv('production', process.cwd(), '');
22+
// However, this currently throws a build error.
23+
// Will revisit this later.
24+
const env = process.env;
25+
26+
const uploadOptions = options.sourceMapsUploadOptions || {};
27+
28+
const shouldUploadSourcemaps = uploadOptions?.enabled ?? true;
29+
const authToken = uploadOptions.authToken || env.SENTRY_AUTH_TOKEN;
30+
31+
if (shouldUploadSourcemaps && authToken) {
32+
updateConfig({
33+
vite: {
34+
build: {
35+
sourcemap: true,
36+
},
37+
plugins: [
38+
sentryVitePlugin({
39+
org: uploadOptions.org ?? env.SENTRY_ORG,
40+
project: uploadOptions.project ?? env.SENTRY_PROJECT,
41+
authToken: uploadOptions.authToken ?? env.SENTRY_AUTH_TOKEN,
42+
telemetry: uploadOptions.telemetry ?? true,
43+
}),
44+
],
45+
},
46+
});
47+
}
48+
49+
const pathToClientInit = options.clientInitPath
50+
? path.resolve(options.clientInitPath)
51+
: findDefaultSdkInitFile('client');
52+
const pathToServerInit = options.serverInitPath
53+
? path.resolve(options.serverInitPath)
54+
: findDefaultSdkInitFile('server');
55+
56+
if (pathToClientInit) {
57+
options.debug && console.log(`[sentry-astro] Using ${pathToClientInit} for client init.`);
58+
injectScript('page', buildSdkInitFileImportSnippet(pathToClientInit));
59+
} else {
60+
options.debug && console.log('[sentry-astro] Using default client init.');
61+
injectScript('page', buildClientSnippet(options || {}));
62+
}
63+
64+
if (pathToServerInit) {
65+
options.debug && console.log(`[sentry-astro] Using ${pathToServerInit} for server init.`);
66+
injectScript('page-ssr', buildSdkInitFileImportSnippet(pathToServerInit));
67+
} else {
68+
options.debug && console.log('[sentry-astro] Using default server init.');
69+
injectScript('page-ssr', buildServerSnippet(options || {}));
70+
}
71+
},
72+
},
73+
};
74+
};
75+
76+
function findDefaultSdkInitFile(type: 'server' | 'client'): string | undefined {
77+
const fileExtensions = ['ts', 'js', 'tsx', 'jsx', 'mjs', 'cjs', 'mts'];
78+
return fileExtensions
79+
.map(ext => path.resolve(path.join(process.cwd(), `sentry.${type}.config.${ext}`)))
80+
.find(filename => fs.existsSync(filename));
81+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import type { SentryOptions } from './types';
2+
3+
/**
4+
* Creates a snippet that imports a Sentry.init file.
5+
*/
6+
export function buildSdkInitFileImportSnippet(filePath: string): string {
7+
return `import "${filePath}";`;
8+
}
9+
10+
/**
11+
* Creates a snippet that initializes Sentry on the client by choosing
12+
* default options.
13+
*/
14+
export function buildClientSnippet(options: SentryOptions): string {
15+
return `import * as Sentry from "@sentry/astro";
16+
17+
Sentry.init({
18+
${buildCommonInitOptions(options)}
19+
integrations: [new Sentry.BrowserTracing(), new Sentry.Replay()],
20+
replaysSessionSampleRate: ${options.replaysSessionSampleRate ?? 0.1},
21+
replaysOnErrorSampleRate: ${options.replaysOnErrorSampleRate ?? 1.0},
22+
});`;
23+
}
24+
25+
/**
26+
* Creates a snippet that initializes Sentry on the server by choosing
27+
* default options.
28+
*/
29+
export function buildServerSnippet(options: SentryOptions): string {
30+
return `import * as Sentry from "@sentry/astro";
31+
32+
Sentry.init({
33+
${buildCommonInitOptions(options)}
34+
});`;
35+
}
36+
37+
const buildCommonInitOptions = (options: SentryOptions): string => `dsn: ${
38+
options.dsn ? JSON.stringify(options.dsn) : 'import.meta.env.PUBLIC_SENTRY_DSN'
39+
},
40+
debug: ${options.debug ? true : false},
41+
environment: ${options.environment ? JSON.stringify(options.environment) : 'import.meta.env.PUBLIC_VERCEL_ENV'},
42+
release: ${options.release ? JSON.stringify(options.release) : 'import.meta.env.PUBLIC_VERCEL_GIT_COMMIT_SHA'},
43+
tracesSampleRate: ${options.tracesSampleRate ?? 1.0},${
44+
options.sampleRate ? `\n sampleRate: ${options.sampleRate},` : ''
45+
}`;

0 commit comments

Comments
 (0)