Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .craft.yml
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,9 @@ targets:
- name: npm
id: '@sentry/react-router'
includeNames: /^sentry-react-router-\d.*\.tgz$/
- name: npm
id: '@sentry/nitro'
includeNames: /^sentry-nitro-\d.*\.tgz$/

## 7. Other Packages
## 7.1
Expand Down Expand Up @@ -256,3 +259,9 @@ targets:
packageUrl: 'https://www.npmjs.com/package/@sentry/elysia'
mainDocsUrl: 'https://docs.sentry.io/platforms/javascript/guides/elysia/'
onlyIfPresent: /^sentry-elysia-\d.*\.tgz$/
'npm:@sentry/nitro':
name: 'Sentry Nitro SDK'
sdkName: 'sentry.javascript.nitro'
packageUrl: 'https://www.npmjs.com/package/@sentry/nitro'
mainDocsUrl: 'https://github.com/getsentry/sentry-javascript/tree/master/packages/nitro'
onlyIfPresent: /^sentry-nitro-\d.*\.tgz$/
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/bug.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ body:
- '@sentry/google-cloud-serverless'
- '@sentry/nestjs'
- '@sentry/nextjs'
- '@sentry/nitro'
- '@sentry/nuxt'
- '@sentry/react'
- '@sentry/react-router'
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/canary.yml
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,9 @@ jobs:
- test-application: 'nestjs-microservices'
build-command: 'test:build-latest'
label: 'nestjs-microservices (latest)'
- test-application: 'nitro-3'
build-command: 'test:build-canary'
label: 'nitro-3 (canary)'
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing test:build-canary script breaks canary CI

High Severity

The canary workflow references test:build-canary as the build-command for the nitro-3 test application, but the package.json for nitro-3 only defines test:build — there is no test:build-canary script. The workflow step executes yarn ${{ matrix.build-command }}, which will fail. Other test applications like nuxt-3 and nuxt-4 correctly define test:build-canary scripts that install canary/nightly versions of their framework dependencies.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4cd6415. Configure here.


steps:
- name: Check out current commit
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/issue-package-label.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ jobs:
"@sentry.nestjs": {
"label": "Nest.js"
},
"@sentry.nitro": {
"label": "Nitro"
},
"@sentry.nextjs": {
"label": "Next.js"
},
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ package. Please refer to the README and instructions of those SDKs for more deta
- [`@sentry/gatsby`](https://github.com/getsentry/sentry-javascript/tree/master/packages/gatsby): SDK for Gatsby
- [`@sentry/nestjs`](https://github.com/getsentry/sentry-javascript/tree/master/packages/nestjs): SDK for NestJS
- [`@sentry/nextjs`](https://github.com/getsentry/sentry-javascript/tree/master/packages/nextjs): SDK for Next.js
- [`@sentry/nitro`](https://github.com/getsentry/sentry-javascript/tree/master/packages/nitro): SDK for Nitro
- [`@sentry/remix`](https://github.com/getsentry/sentry-javascript/tree/master/packages/remix): SDK for Remix
- [`@sentry/tanstackstart-react`](https://github.com/getsentry/sentry-javascript/tree/master/packages/tanstackstart-react): SDK for TanStack Start React
- [`@sentry/aws-serverless`](https://github.com/getsentry/sentry-javascript/tree/master/packages/aws-serverless): SDK
Expand Down
2 changes: 2 additions & 0 deletions dev-packages/e2e-tests/test-applications/nitro-3/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@sentry:registry=http://127.0.0.1:4873
@sentry-internal:registry=http://127.0.0.1:4873
11 changes: 11 additions & 0 deletions dev-packages/e2e-tests/test-applications/nitro-3/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Nitro E2E Test</title>
</head>
<body>
<h1>Nitro E2E Test App</h1>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import * as Sentry from '@sentry/nitro';

Sentry.init({
environment: 'qa', // dynamic sampling bias to keep transactions
dsn: process.env.E2E_TEST_DSN,
tunnel: `http://localhost:3031/`, // proxy server
tracesSampleRate: 1,
});
29 changes: 29 additions & 0 deletions dev-packages/e2e-tests/test-applications/nitro-3/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "nitro-3",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"build": "vite build",
"start": "PORT=3030 NODE_OPTIONS='--import ./instrument.mjs' node .output/server/index.mjs",
"clean": "npx rimraf node_modules pnpm-lock.yaml .output",
"test": "playwright test",
"test:build": "pnpm install && pnpm build",
"test:assert": "pnpm test"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing test:build-canary script breaks canary CI

Medium Severity

The canary workflow specifies build-command: 'test:build-canary' for nitro-3, but the package.json only defines test:build — there is no test:build-canary script. Every other canary test application (e.g. nuxt-3, nextjs-14) explicitly defines a test:build-canary script that installs canary/nightly versions of its framework. The canary CI job will fail when it runs yarn test:build-canary and the script is not found.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 47d85ab. Configure here.

},
"dependencies": {
"@sentry/browser": "latest || *",
"@sentry/nitro": "latest || *"
},
"devDependencies": {
"@playwright/test": "~1.56.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"@sentry/core": "latest || *",
"nitro": "^3.0.260415-beta",
"rolldown": "latest",
"vite": "latest"
},
"volta": {
"extends": "../../package.json"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { getPlaywrightConfig } from '@sentry-internal/test-utils';

const config = getPlaywrightConfig({
startCommand: `pnpm start`,
});

export default config;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { defineHandler } from 'nitro/h3';

export default defineHandler(() => {
return { status: 'ok' };
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { defineHandler } from 'nitro/h3';

export default defineHandler(() => {
throw new Error('This is a test error');
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { getDefaultIsolationScope, setTag } from '@sentry/core';
import { defineHandler } from 'nitro/h3';

export default defineHandler(() => {
setTag('my-isolated-tag', true);
// Check if the tag leaked into the default (global) isolation scope
setTag('my-global-scope-isolated-tag', getDefaultIsolationScope().getScopeData().tags['my-isolated-tag']);

throw new Error('Isolation test error');
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { startSpan } from '@sentry/nitro';
import { defineHandler } from 'nitro/h3';

export default defineHandler(() => {
startSpan({ name: 'db.select', op: 'db' }, () => {
// simulate a select query
});

startSpan({ name: 'db.insert', op: 'db' }, () => {
startSpan({ name: 'db.serialize', op: 'serialize' }, () => {
// simulate serializing data before insert
});
});

return { status: 'ok', nesting: true };
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { defineHandler } from 'nitro/h3';

export default defineHandler(event => {
const id = event.req.url;
return { id };
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { defineHandler } from 'nitro/h3';

export default defineHandler(() => {
return { status: 'ok', transaction: true };
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { defineHandler, getQuery, setResponseHeader } from 'nitro/h3';

export default defineHandler(event => {
setResponseHeader(event, 'x-sentry-test-middleware', 'executed');

const query = getQuery(event);
if (query['middleware-error'] === '1') {
throw new Error('Middleware error');
}
});
10 changes: 10 additions & 0 deletions dev-packages/e2e-tests/test-applications/nitro-3/src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import * as Sentry from '@sentry/browser';

// Let's us test trace propagation
Sentry.init({
environment: 'qa',
dsn: 'https://public@dsn.ingest.sentry.io/1337',
tunnel: 'http://localhost:3031/', // proxy server
integrations: [Sentry.browserTracingIntegration()],
tracesSampleRate: 1.0,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { startEventProxyServer } from '@sentry-internal/test-utils';

startEventProxyServer({
port: 3031,
proxyServerName: 'nitro-3',
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { expect, test } from '@playwright/test';
import { waitForError } from '@sentry-internal/test-utils';

test('Sends an error event to Sentry', async ({ request }) => {
const errorEventPromise = waitForError('nitro-3', event => {
return !event.type && !!event.exception?.values?.some(v => v.value === 'This is a test error');
});

await request.get('/api/test-error');

const errorEvent = await errorEventPromise;

// Nitro wraps thrown errors in an HTTPError with .cause, producing a chained exception
expect(errorEvent.exception?.values).toHaveLength(2);

// The innermost exception (values[0]) is the original thrown error
expect(errorEvent.exception?.values?.[0]?.type).toBe('Error');
expect(errorEvent.exception?.values?.[0]?.value).toBe('This is a test error');
expect(errorEvent.exception?.values?.[0]?.mechanism).toEqual(
expect.objectContaining({
handled: false,
type: 'auto.function.nitro.captureErrorHook',
}),
);

// The outermost exception (values[1]) is the HTTPError wrapper
expect(errorEvent.exception?.values?.[1]?.type).toBe('HTTPError');
expect(errorEvent.exception?.values?.[1]?.value).toBe('This is a test error');
});

test('Does not send 404 errors to Sentry', async ({ request }) => {
let errorReceived = false;

void waitForError('nitro-3', event => {
if (!event.type) {
errorReceived = true;
return true;
}
return false;
});

await request.get('/api/non-existent-route');

expect(errorReceived).toBe(false);
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Negative test never fails due to race condition

Medium Severity

The "Does not send 404 errors" test voids the waitForError promise and immediately asserts errorReceived is false after the request completes. Since there's no waiting period, even if the SDK incorrectly sent a 404 error, it likely wouldn't arrive at the proxy server before the assertion runs. This test effectively provides no confidence that 404 errors are filtered — it will always pass regardless of SDK behavior.

Fix in Cursor Fix in Web

Triggered by project rule: PR Review Guidelines for Cursor Bot

Reviewed by Cursor Bugbot for commit 4cd6415. Configure here.

Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { expect, test } from '@playwright/test';
import { waitForError, waitForTransaction } from '@sentry-internal/test-utils';

test('Isolation scope prevents tag leaking between requests', async ({ request }) => {
const transactionEventPromise = waitForTransaction('nitro-3', event => {
return event?.transaction === 'GET /api/test-isolation/:id';
});

const errorPromise = waitForError('nitro-3', event => {
return !event.type && !!event.exception?.values?.some(v => v.value === 'Isolation test error');
});

await request.get('/api/test-isolation/1').catch(() => {
// noop - route throws
});

const transactionEvent = await transactionEventPromise;
const error = await errorPromise;

// Assert that isolation scope works properly
expect(error.tags?.['my-isolated-tag']).toBe(true);
expect(error.tags?.['my-global-scope-isolated-tag']).not.toBeDefined();
expect(transactionEvent.tags?.['my-isolated-tag']).toBe(true);
expect(transactionEvent.tags?.['my-global-scope-isolated-tag']).not.toBeDefined();
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { expect, test } from '@playwright/test';
import { waitForError, waitForTransaction } from '@sentry-internal/test-utils';

test('Creates middleware spans for requests', async ({ request }) => {
const transactionEventPromise = waitForTransaction('nitro-3', event => {
return event?.transaction === 'GET /api/test-transaction';
});

const response = await request.get('/api/test-transaction');

expect(response.headers()['x-sentry-test-middleware']).toBe('executed');

const transactionEvent = await transactionEventPromise;

// h3 middleware spans have origin auto.http.nitro.h3 and op middleware.nitro
const h3MiddlewareSpans = transactionEvent.spans?.filter(
span => span.origin === 'auto.http.nitro.h3' && span.op === 'middleware.nitro',
);
expect(h3MiddlewareSpans?.length).toBeGreaterThanOrEqual(1);
});

test('Captures errors thrown in middleware with error status on span', async ({ request }) => {
const errorEventPromise = waitForError('nitro-3', event => {
return !event.type && !!event.exception?.values?.some(v => v.value === 'Middleware error');
});

const transactionEventPromise = waitForTransaction('nitro-3', event => {
return event?.transaction === 'GET /api/test-transaction' && event?.contexts?.trace?.status === 'internal_error';
});

await request.get('/api/test-transaction?middleware-error=1');

const errorEvent = await errorEventPromise;
expect(errorEvent.exception?.values?.some(v => v.value === 'Middleware error')).toBe(true);

const transactionEvent = await transactionEventPromise;

// The transaction span should have error status
expect(transactionEvent.contexts?.trace?.status).toBe('internal_error');
});
Loading
Loading