Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/fix-security-vulnerability.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
issues: write
id-token: write
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
ref: develop

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/triage-issue.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ jobs:
echo "Processing issue #$ISSUE_NUM in CI mode"

- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
ref: develop

Expand Down
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,24 @@

- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott

## 10.42.0

- feat(consola): Enhance Consola integration to extract first-param object as searchable attributes ([#19534](https://github.com/getsentry/sentry-javascript/pull/19534))
- fix(astro): Do not inject withSentry into Cloudflare Pages ([#19558](https://github.com/getsentry/sentry-javascript/pull/19558))
- fix(core): Do not remove promiseBuffer entirely ([#19592](https://github.com/getsentry/sentry-javascript/pull/19592))
- fix(deps): Bump fast-xml-parser to 4.5.4 for CVE-2026-25896 ([#19588](https://github.com/getsentry/sentry-javascript/pull/19588))
- fix(react-router): Set correct transaction name when navigating with object argument ([#19590](https://github.com/getsentry/sentry-javascript/pull/19590))
- ref(nuxt): Use `addVitePlugin` instead of deprecated `vite:extendConfig` ([#19464](https://github.com/getsentry/sentry-javascript/pull/19464))

<details>
<summary> <strong>Internal Changes</strong> </summary>

- chore(deps-dev): bump @sveltejs/kit from 2.52.2 to 2.53.3 ([#19571](https://github.com/getsentry/sentry-javascript/pull/19571))
- chore(deps): Bump @sveltejs/kit to 2.53.3 in sveltekit-2-svelte-5 E2E test ([#19594](https://github.com/getsentry/sentry-javascript/pull/19594))
- ci(deps): bump actions/checkout from 4 to 6 ([#19570](https://github.com/getsentry/sentry-javascript/pull/19570))

</details>

## 10.41.0

### Important Changes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ export default function PerformancePage() {
<nav>
<Link to="/performance/ssr">SSR Page</Link>
<Link to="/performance/with/sentry">With Param Page</Link>
<Link to={{ pathname: '/performance/with/object-nav', search: '?foo=bar' }}>Object Navigate</Link>
<Link to={{ search: '?query=test' }}>Search Only Navigate</Link>
<Link to="/performance/server-loader">Server Loader</Link>
</nav>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,56 @@ test.describe('client - navigation performance', () => {
});
});

test('should create navigation transaction when navigating with object `to` prop', async ({ page }) => {
const txPromise = waitForTransaction(APP_NAME, async transactionEvent => {
return transactionEvent.transaction === '/performance/with/:param';
});

await page.goto(`/performance`); // pageload
await page.waitForTimeout(1000); // give it a sec before navigation
await page.getByRole('link', { name: 'Object Navigate' }).click(); // navigation with object to

const transaction = await txPromise;

expect(transaction).toMatchObject({
contexts: {
trace: {
op: 'navigation',
origin: 'auto.navigation.react_router',
data: {
'sentry.source': 'route',
},
},
},
transaction: '/performance/with/:param',
type: 'transaction',
transaction_info: { source: 'route' },
});
});

test('should create navigation transaction when navigating with search-only object `to` prop', async ({ page }) => {
const txPromise = waitForTransaction(APP_NAME, async transactionEvent => {
return transactionEvent.transaction === '/performance' && transactionEvent.contexts?.trace?.op === 'navigation';
});

await page.goto(`/performance`); // pageload
await page.waitForTimeout(1000); // give it a sec before navigation
await page.getByRole('link', { name: 'Search Only Navigate' }).click(); // navigation with search-only object to

const transaction = await txPromise;

expect(transaction).toMatchObject({
contexts: {
trace: {
op: 'navigation',
origin: 'auto.navigation.react_router',
},
},
transaction: '/performance',
type: 'transaction',
});
});

test('should update navigation transaction for dynamic routes', async ({ page }) => {
const txPromise = waitForTransaction(APP_NAME, async transactionEvent => {
return transactionEvent.transaction === '/performance/with/:param';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"@playwright/test": "~1.56.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/kit": "2.49.5",
"@sveltejs/kit": "2.53.3",
"@sveltejs/vite-plugin-svelte": "^3.0.0",
"svelte": "^5.0.0-next.115",
"svelte-check": "^3.6.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import * as Sentry from '@sentry/node';
import { loggingTransport } from '@sentry-internal/node-integration-tests';
import { consola } from 'consola';

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
release: '1.0.0',
environment: 'test',
enableLogs: true,
transport: loggingTransport,
});

async function run(): Promise<void> {
consola.level = 5;
const sentryReporter = Sentry.createConsolaReporter();
consola.addReporter(sentryReporter);

// Object-first: args = [object, string] — first object becomes attributes, second arg is part of formatted message
consola.info({ userId: 100, action: 'login' }, 'User logged in');

// Object-first: args = [object] only — object keys become attributes, message is stringified object
consola.info({ event: 'click', count: 2 });

await Sentry.flush();
}

// eslint-disable-next-line @typescript-eslint/no-floating-promises
void run();
51 changes: 51 additions & 0 deletions dev-packages/node-integration-tests/suites/consola/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -491,4 +491,55 @@ describe('consola integration', () => {

await runner.completed();
});

test('should capture object-first consola logs (object as first arg)', async () => {
const runner = createRunner(__dirname, 'subject-object-first.ts')
.expect({
log: {
items: [
{
timestamp: expect.any(Number),
level: 'info',
body: '{"userId":100,"action":"login"} User logged in',
severity_number: expect.any(Number),
trace_id: expect.any(String),
attributes: {
'sentry.origin': { value: 'auto.log.consola', type: 'string' },
'sentry.release': { value: '1.0.0', type: 'string' },
'sentry.environment': { value: 'test', type: 'string' },
'sentry.sdk.name': { value: 'sentry.javascript.node', type: 'string' },
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
'server.address': { value: expect.any(String), type: 'string' },
'consola.type': { value: 'info', type: 'string' },
'consola.level': { value: 3, type: 'integer' },
userId: { value: 100, type: 'integer' },
action: { value: 'login', type: 'string' },
},
},
{
timestamp: expect.any(Number),
level: 'info',
body: '{"event":"click","count":2}',
severity_number: expect.any(Number),
trace_id: expect.any(String),
attributes: {
'sentry.origin': { value: 'auto.log.consola', type: 'string' },
'sentry.release': { value: '1.0.0', type: 'string' },
'sentry.environment': { value: 'test', type: 'string' },
'sentry.sdk.name': { value: 'sentry.javascript.node', type: 'string' },
'sentry.sdk.version': { value: expect.any(String), type: 'string' },
'server.address': { value: expect.any(String), type: 'string' },
'consola.type': { value: 'info', type: 'string' },
'consola.level': { value: 3, type: 'integer' },
event: { value: 'click', type: 'string' },
count: { value: 2, type: 'integer' },
},
},
],
},
})
.start();

await runner.completed();
});
});
41 changes: 39 additions & 2 deletions packages/astro/src/integration/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ export const sentryAstro = (options: SentryOptions = {}): AstroIntegration => {
}

const isCloudflare = config?.adapter?.name?.startsWith('@astrojs/cloudflare');
const isCloudflareWorkers = isCloudflare && !isCloudflarePages();

if (isCloudflare) {
try {
Expand Down Expand Up @@ -191,8 +192,8 @@ export const sentryAstro = (options: SentryOptions = {}): AstroIntegration => {
injectScript('page-ssr', buildServerSnippet(options || {}));
}

if (isCloudflare && command !== 'dev') {
// For Cloudflare production builds, additionally use a Vite plugin to:
if (isCloudflareWorkers && command !== 'dev') {
// For Cloudflare Workers production builds, additionally use a Vite plugin to:
// 1. Import the server config at the Worker entry level (so Sentry.init() runs
// for ALL requests, not just SSR pages — covers actions and API routes)
// 2. Wrap the default export with `withSentry` from @sentry/cloudflare for
Expand All @@ -215,6 +216,7 @@ export const sentryAstro = (options: SentryOptions = {}): AstroIntegration => {
// Ref: https://developers.cloudflare.com/workers/runtime-apis/nodejs/
updateConfig({
vite: {
plugins: [sentryCloudflareNodeWarningPlugin()],
ssr: {
// @sentry/node is required in case we have 2 different @sentry/node
// packages installed in the same project.
Expand Down Expand Up @@ -255,6 +257,41 @@ function findDefaultSdkInitFile(type: 'server' | 'client'): string | undefined {
.find(filename => fs.existsSync(filename));
}

/**
* Detects if the project is a Cloudflare Pages project by checking for
* `pages_build_output_dir` in the wrangler configuration file.
*
* Cloudflare Pages projects use `pages_build_output_dir` while Workers projects
* use `assets.directory` or `main` fields instead.
*/
function isCloudflarePages(): boolean {
const cwd = process.cwd();
const configFiles = ['wrangler.jsonc', 'wrangler.json', 'wrangler.toml'];

for (const configFile of configFiles) {
const configPath = path.join(cwd, configFile);

if (!fs.existsSync(configPath)) {
continue;
}

const content = fs.readFileSync(configPath, 'utf-8');

if (configFile.endsWith('.toml')) {
// https://regex101.com/r/Uxe4p0/1
// Match pages_build_output_dir as a TOML key (at start of line, ignoring whitespace)
// This avoids false positives from comments (lines starting with #)
return /^\s*pages_build_output_dir\s*=/m.test(content);
}

// Match "pages_build_output_dir" as a JSON key (followed by :)
// This works for both .json and .jsonc without needing to strip comments
return /"pages_build_output_dir"\s*:/.test(content);
}

return false;
}

function getSourcemapsAssetsGlob(config: AstroConfig): string {
// The vercel adapter puts the output into its .vercel directory
// However, the way this adapter is written, the config.outDir value is update too late for
Expand Down
Loading
Loading