Skip to content

Commit b74d7cc

Browse files
committed
feat(astro): Add sentryAstro integration
1 parent e507110 commit b74d7cc

File tree

13 files changed

+453
-7
lines changed

13 files changed

+453
-7
lines changed

packages/astro/.eslintrc.js

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`, `org` 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+
org: 'your-org-slug',
66+
project: 'your-project-slug',
67+
authToken: import.meta.env('SENTRY_AUTH_TOKEN'),
68+
}),
69+
],
70+
})
71+
```
72+
73+
You can also define these values as environment variables in e.g. a `.env` file
74+
or in you CI configuration:
75+
76+
```sh
77+
SENTRY_ORG="your-org-slug"
78+
SENTRY_PROJECT="your-project"
79+
SENTRY_AUTH_TOKEN="your-token"
80+
```
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 get 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: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,30 +9,44 @@
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+
"./integration": {
26+
"default": "./build/integration/index.ts"
27+
}
28+
},
1629
"publishConfig": {
1730
"access": "public"
1831
},
1932
"peerDependencies": {
20-
"astro": "1.x"
33+
"astro": "3.x"
2134
},
2235
"dependencies": {
2336
"@sentry/browser": "7.73.0",
2437
"@sentry/node": "7.73.0",
2538
"@sentry/core": "7.73.0",
2639
"@sentry/utils": "7.73.0",
27-
"@sentry/types": "7.73.0"
40+
"@sentry/types": "7.73.0",
41+
"@sentry/vite-plugin": "^2.8.0"
2842
},
2943
"devDependencies": {
3044
"astro": "^3.2.3",
3145
"rollup": "^3.20.2",
3246
"vite": "4.0.5"
3347
},
3448
"scripts": {
35-
"build": "run-p build:transpile build:types",
49+
"build": "run-p build:transpile build:types build:sync:integration",
3650
"build:dev": "yarn build",
3751
"build:transpile": "rollup -c rollup.npm.config.js --bundleConfigAsCjs",
3852
"build:types": "tsc -p tsconfig.types.json",
@@ -41,6 +55,7 @@
4155
"build:transpile:watch": "rollup -c rollup.npm.config.js --bundleConfigAsCjs --watch",
4256
"build:types:watch": "tsc -p tsconfig.types.json --watch",
4357
"build:tarball": "ts-node ../../scripts/prepack.ts && npm pack ./build",
58+
"build:sync:integration": "ts-node -P tsconfig.dev.json ./scripts/syncIntegration.ts",
4459
"circularDepCheck": "madge --circular src/index.client.ts && madge --circular src/index.server.ts && madge --circular src/index.types.ts",
4560
"clean": "rimraf build coverage sentry-astro-*.tgz",
4661
"fix": "run-s fix:eslint fix:prettier",
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+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Contents of this directory are _not_ bundled with rollup.
2+
They're directly copied into `build/integration`.
3+
Astro supports TypeScript in integration packages.
4+
The integration files will simply be bundled by Vite when building the Astro project.
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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+
import { loadEnv } from 'vite';
7+
8+
import { buildClientSnippet, buildSdkInitFileImportSnippet, buildServerSnippet } from './snippets';
9+
import type { SentryOptions } from './types';
10+
11+
const PKG_NAME = '@sentry/astro';
12+
13+
export const sentryAstro = (options: SentryOptions = {}): AstroIntegration => {
14+
return {
15+
name: PKG_NAME,
16+
hooks: {
17+
'astro:config:setup': async ({ updateConfig, injectScript }) => {
18+
// The third param here enables loading of all env vars, regardless of prefix
19+
// see: https://main.vitejs.dev/config/#using-environment-variables-in-config
20+
const env = loadEnv('production', process.cwd(), '');
21+
22+
if (options.authToken ?? env.SENTRY_AUTH_TOKEN) {
23+
updateConfig({
24+
vite: {
25+
build: {
26+
sourcemap: true,
27+
},
28+
plugins: [
29+
sentryVitePlugin({
30+
org: options.org ?? env.SENTRY_ORG,
31+
project: options.project ?? env.SENTRY_PROJECT,
32+
authToken: options.authToken ?? env.SENTRY_AUTH_TOKEN,
33+
telemetry: options.telemetry,
34+
}),
35+
],
36+
},
37+
});
38+
}
39+
40+
const pathToClientInit = options.clientInitPath ?? findSdkInitFile('client');
41+
const pathToServerInit = options.serverInitPath ?? findSdkInitFile('server');
42+
43+
if (pathToClientInit) {
44+
options.debug && console.log(`[sentry-astro] Using ${pathToClientInit} for client init.`);
45+
injectScript('page', buildSdkInitFileImportSnippet(pathToClientInit));
46+
} else {
47+
options.debug && console.log('[sentry-astro] Using default client init.');
48+
injectScript('page', buildClientSnippet(options || {}));
49+
}
50+
51+
if (pathToServerInit) {
52+
options.debug && console.log(`[sentry-astro] Using ${pathToServerInit} for server init.`);
53+
// For whatever reason, we need to move one level up to import the server file correctly
54+
injectScript('page-ssr', buildSdkInitFileImportSnippet(path.join('..', pathToServerInit)));
55+
} else {
56+
options.debug && console.log('[sentry-astro] Using default server init.');
57+
injectScript('page-ssr', buildServerSnippet(options || {}));
58+
}
59+
},
60+
},
61+
};
62+
};
63+
64+
function findSdkInitFile(type: 'server' | 'client'): string | undefined {
65+
const fileExtensions = ['ts', 'js', 'tsx', 'jsx', 'mjs', 'cjs', 'mts'];
66+
return fileExtensions
67+
.map(ext => path.join(process.cwd(), `sentry.${type}.config.${ext}`))
68+
.find(filename => fs.existsSync(filename));
69+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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+
dsn: ${options.dsn ? JSON.stringify(options.dsn) : 'import.meta.env.PUBLIC_SENTRY_DSN'},
19+
debug: ${options.debug ? true : false},
20+
environment: ${options.environment ? JSON.stringify(options.environment) : 'import.meta.env.PUBLIC_VERCEL_ENV'},
21+
release: ${options.release ? JSON.stringify(options.release) : 'import.meta.env.PUBLIC_VERCEL_GIT_COMMIT_SHA'},
22+
integrations: [new Sentry.BrowserTracing(), new Sentry.Replay()],
23+
tracesSampleRate: ${options.tracesSampleRate ?? 1.0},
24+
replaysSessionSampleRate: ${options.replaysSessionSampleRate ?? 0.1},
25+
replaysOnErrorSampleRate: ${options.replaysOnErrorSampleRate ?? 1.0},${
26+
options.sampleRate ? `\n sampleRate: ${options.sampleRate},` : ''
27+
}
28+
});`;
29+
}
30+
31+
/**
32+
* Creates a snippet that initializes Sentry on the server by choosing
33+
* default options.
34+
*/
35+
export function buildServerSnippet(options: SentryOptions): string {
36+
return `import * as Sentry from "@sentry/astro";
37+
38+
Sentry.init({
39+
dsn: ${options.dsn ? JSON.stringify(options.dsn) : 'import.meta.env.PUBLIC_SENTRY_DSN'},
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+
}
46+
});`;
47+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import type { BrowserOptions } from '@sentry/browser';
2+
import type { Options } from '@sentry/types';
3+
import type { SentryVitePluginOptions } from '@sentry/vite-plugin';
4+
5+
type SdkInitPaths = {
6+
/**
7+
* Path to a `sentry.client.config.(js|ts)` file that contains a `Sentry.init` call.
8+
* If this option is not specified, the default location (`src/sentry.client.config.(js|ts)`) will be used.
9+
* If there is no file at the default location, a default `Sentry.init` call will made.
10+
*/
11+
clientInitPath?: string;
12+
/**
13+
* Path to a `sentry.client.config.(js|ts)` file that contains a `Sentry.init` call.
14+
* If this option is not specified, the default location (`src/sentry.client.config.(js|ts)`) will be used.
15+
* If there is no file at the default location, a default `Sentry.init` call will made.
16+
*/
17+
serverInitPath?: string;
18+
};
19+
20+
/**
21+
* A subset of Sentry SDK options that can be set via the `sentryAstro` integration.
22+
* Some options (e.g. integrations) are set by default and cannot be changed here.
23+
*
24+
* If you want a more fine-grained control over the SDK, with all options,
25+
* you can call Sentry.init in `sentry.client.config.(js|ts)` or `sentry.server.config.(js|ts)` files.
26+
*
27+
* If you specify a dedicated init file, the SDK options passed to `sentryAstro` will be ignored.
28+
*/
29+
export type SentryOptions = SdkInitPaths &
30+
Pick<Options, 'dsn' | 'release' | 'environment' | 'sampleRate' | 'tracesSampleRate' | 'debug'> &
31+
Pick<BrowserOptions, 'replaysSessionSampleRate' | 'replaysOnErrorSampleRate'> &
32+
Pick<SentryVitePluginOptions, 'authToken' | 'org' | 'project' | 'telemetry'>;

0 commit comments

Comments
 (0)