Skip to content
Draft
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
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import node from '@astrojs/node';
import sentry from '@sentry/astro';
import { sentryOrchestrionPlugin } from '@sentry/server-utils/orchestrion/vite';
// @ts-check
import { defineConfig } from 'astro/config';

Expand All @@ -18,9 +17,4 @@ export default defineConfig({
adapter: node({
mode: 'standalone',
}),
vite: {
// Run the orchestrion code transform on the Vite SSR bundle so instrumented
// DB drivers get `diagnostics_channel` publishers injected at build time.
plugins: [sentryOrchestrionPlugin()],
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
"@sentry-internal/test-utils": "link:../../../test-utils",
"@sentry/astro": "file:../../packed/sentry-astro-packed.tgz",
"@sentry/node": "file:../../packed/sentry-node-packed.tgz",
"@sentry/server-utils": "file:../../packed/sentry-server-utils-packed.tgz",
"astro": "^7.0.6",
"ioredis": "5.10.1",
"mysql": "^2.18.1"
Expand Down
1 change: 1 addition & 0 deletions packages/astro/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"@sentry/core": "10.67.0",
"@sentry/conventions": "^0.16.0",
"@sentry/node": "10.67.0",
"@sentry/server-utils": "10.67.0",
"@sentry/bundler-plugins": "10.67.0"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/integration/cloudflare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { builtinModules } from 'module';
// Derived from Astro's own config type rather than imported from `vite` directly: Astro bundles its
// own Vite version, which differs across the Astro majors we support. A plugin typed against any
// single Vite version is not assignable to `updateConfig({ vite: { plugins } })` for the others.
type VitePlugin = Extract<NonNullable<NonNullable<AstroConfig['vite']>['plugins']>[number], { name: string }>;
export type VitePlugin = Extract<NonNullable<NonNullable<AstroConfig['vite']>['plugins']>[number], { name: string }>;

// Build a set of all Node.js built-in module names, including both
// bare names (e.g. "fs") and "node:" prefixed names (e.g. "node:fs").
Expand Down
12 changes: 12 additions & 0 deletions packages/astro/src/integration/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { sentryVitePlugin } from '@sentry/bundler-plugins/vite';
import { sentryOrchestrionPlugin } from '@sentry/server-utils/orchestrion/vite';
import type { AstroConfig, AstroIntegration, AstroIntegrationLogger } from 'astro';
import * as fs from 'fs';
import { createRequire } from 'module';
import * as path from 'path';
import type { VitePlugin } from './cloudflare';
import { sentryCloudflareNodeWarningPlugin, sentryCloudflareVitePlugin } from './cloudflare';
import { buildClientSnippet, buildSdkInitFileImportSnippet, buildServerSnippet } from './snippets';
import type { SentryOptions } from './types';
Expand Down Expand Up @@ -34,6 +36,7 @@ export const sentryAstro = (options: SentryOptions = {}): AstroIntegration => {
sourcemaps,
// todo(v11): Extract `release` build time option here - cannot be done currently, because it conflicts with the `DeprecatedRuntimeOptions` type
// release,
buildTimeInstrumentation,
bundleSizeOptimizations,
applicationKey,
unstable_sentryVitePluginOptions,
Expand Down Expand Up @@ -167,6 +170,15 @@ export const sentryAstro = (options: SentryOptions = {}): AstroIntegration => {
const isCloudflare = config?.adapter?.name?.startsWith('@astrojs/cloudflare');
const isCloudflareWorkers = isCloudflare && !isCloudflarePages();

// TODO: Cloudflare/workerd needs different wiring — skipped for now.
if (sdkEnabled.server && !isCloudflare) {
updateConfig({
vite: {
plugins: [sentryOrchestrionPlugin({ buildTimeInstrumentation }) as VitePlugin],
},
});
}

if (isCloudflare) {
try {
const _require = createRequire(`${process.cwd()}/`);
Expand Down
133 changes: 125 additions & 8 deletions packages/astro/test/integration/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,26 @@
sentryVitePlugin: vi.fn(args => sentryVitePluginSpy(args)),
}));

// Stub the orchestrion plugin so these stay pure wiring tests (no apm code transformer pulled in).
// Mirror the real plugin's contract: `buildTimeInstrumentation: false` yields the inert variant.
const orchestrionVite = vi.fn((options?: { buildTimeInstrumentation?: boolean }) => ({
name: options?.buildTimeInstrumentation === false ? 'sentry-orchestrion-disabled' : 'sentry-orchestrion-vite',
}));
vi.mock('@sentry/server-utils/orchestrion/vite', () => ({
sentryOrchestrionPlugin: (options?: { buildTimeInstrumentation?: boolean }) => orchestrionVite(options),
}));

// The cloudflare adapter path resolves `@sentry/cloudflare` via `createRequire` and calls
// `process.exit(1)` when it's missing. Stub the resolver so it always "finds" the package,
// keeping these tests hermetic regardless of what's installed in `node_modules`.
vi.mock('module', async requireActual => {
const actual = await requireActual<any>();
return {
...actual,
createRequire: () => ({ resolve: () => '@sentry/cloudflare' }),
};
});

process.env = {
...process.env,
SENTRY_AUTH_TOKEN: 'my-token',
Expand All @@ -23,7 +43,7 @@
} as AstroConfig;

const baseConfigHookObject = {
logger: { warn: vi.fn(), info: vi.fn() },
logger: { warn: vi.fn(), info: vi.fn(), error: vi.fn() },
addMiddleware: vi.fn(),
};

Expand All @@ -46,7 +66,8 @@
// @ts-expect-error - the hook exists and we only need to pass what we actually use
await integration.hooks['astro:config:setup']({ ...baseConfigHookObject, updateConfig, injectScript, config });

expect(updateConfig).toHaveBeenCalledTimes(1);
// one call for the sourcemaps vite plugin, one for the orchestrion plugin
expect(updateConfig).toHaveBeenCalledTimes(2);
expect(updateConfig).toHaveBeenCalledWith({
vite: {
build: {
Expand All @@ -55,6 +76,11 @@
plugins: ['sentryVitePlugin'],
},
});
expect(updateConfig).toHaveBeenCalledWith({
vite: {
plugins: [{ name: 'sentry-orchestrion-vite' }],
},
});

expect(sentryVitePluginSpy).toHaveBeenCalledTimes(1);
expect(sentryVitePluginSpy).toHaveBeenCalledWith(
Expand Down Expand Up @@ -294,7 +320,13 @@
// @ts-expect-error - the hook exists and we only need to pass what we actually use
await integration.hooks['astro:config:setup']({ ...baseConfigHookObject, updateConfig, injectScript, config });

expect(updateConfig).toHaveBeenCalledTimes(0);
// only the orchestrion plugin is wired, no sourcemaps plugin
expect(updateConfig).toHaveBeenCalledTimes(1);
expect(updateConfig).toHaveBeenCalledWith({
vite: {
plugins: [{ name: 'sentry-orchestrion-vite' }],
},
});
expect(sentryVitePluginSpy).toHaveBeenCalledTimes(0);
});

Expand All @@ -307,7 +339,13 @@
// @ts-expect-error - the hook exists and we only need to pass what we actually use
await integration.hooks['astro:config:setup']({ ...baseConfigHookObject, updateConfig, injectScript, config });

expect(updateConfig).toHaveBeenCalledTimes(0);
// only the orchestrion plugin is wired, no sourcemaps plugin
expect(updateConfig).toHaveBeenCalledTimes(1);
expect(updateConfig).toHaveBeenCalledWith({
vite: {
plugins: [{ name: 'sentry-orchestrion-vite' }],
},
});
expect(sentryVitePluginSpy).toHaveBeenCalledTimes(0);
});

Expand All @@ -318,11 +356,12 @@
// @ts-expect-error - the hook exists and we only need to pass what we actually use
await integration.hooks['astro:config:setup']({ ...baseConfigHookObject, updateConfig, injectScript, config });

expect(updateConfig).toHaveBeenCalledTimes(1);
// one call for the sourcemaps vite plugin, one for the orchestrion plugin
expect(updateConfig).toHaveBeenCalledTimes(2);
expect(sentryVitePluginSpy).toHaveBeenCalledTimes(1);
});

it("doesn't add the Vite plugin in dev mode", async () => {
it("doesn't add the sourcemaps Vite plugin in dev mode", async () => {
const integration = sentryAstro({
sourceMapsUploadOptions: { enabled: true },
});
Expand All @@ -337,7 +376,13 @@
command: 'dev',
});

expect(updateConfig).toHaveBeenCalledTimes(0);
// the sourcemaps plugin is skipped in dev, but the orchestrion plugin is still wired
expect(updateConfig).toHaveBeenCalledTimes(1);
expect(updateConfig).toHaveBeenCalledWith({
vite: {
plugins: [{ name: 'sentry-orchestrion-vite' }],
},
});
expect(sentryVitePluginSpy).toHaveBeenCalledTimes(0);
});

Expand All @@ -348,12 +393,84 @@

expect(integration.hooks['astro:config:setup']).toBeDefined();
// @ts-expect-error - the hook exists and we only need to pass what we actually use
await integration.hooks['astro:config:setup']({ updateConfig, injectScript, config });
await integration.hooks['astro:config:setup']({ ...baseConfigHookObject, updateConfig, injectScript, config });

// neither the sourcemaps nor the orchestrion plugin should be wired
expect(updateConfig).toHaveBeenCalledTimes(0);
expect(orchestrionVite).not.toHaveBeenCalled();
expect(sentryVitePluginSpy).toHaveBeenCalledTimes(0);
});

it('adds the orchestrion plugin by default', async () => {
const integration = sentryAstro({});

expect(integration.hooks['astro:config:setup']).toBeDefined();
// @ts-expect-error - the hook exists and we only need to pass what we actually use
await integration.hooks['astro:config:setup']({ ...baseConfigHookObject, updateConfig, injectScript, config });

expect(orchestrionVite).toHaveBeenCalledWith({ buildTimeInstrumentation: undefined });
expect(updateConfig).toHaveBeenCalledWith({
vite: {
plugins: [{ name: 'sentry-orchestrion-vite' }],
},
});
});

it('adds an inert orchestrion plugin when `buildTimeInstrumentation` is `false`', async () => {
const integration = sentryAstro({ buildTimeInstrumentation: false });

expect(integration.hooks['astro:config:setup']).toBeDefined();
// @ts-expect-error - the hook exists and we only need to pass what we actually use
await integration.hooks['astro:config:setup']({ ...baseConfigHookObject, updateConfig, injectScript, config });

expect(orchestrionVite).toHaveBeenCalledWith({ buildTimeInstrumentation: false });
expect(updateConfig).toHaveBeenCalledWith({
vite: {
plugins: [{ name: 'sentry-orchestrion-disabled' }],
},
});
});

it("doesn't add the orchestrion plugin for the cloudflare adapter", async () => {
const integration = sentryAstro({});

const cloudflareConfig = { ...config, adapter: { name: '@astrojs/cloudflare' } } as AstroConfig;

expect(integration.hooks['astro:config:setup']).toBeDefined();
// @ts-expect-error - the hook exists and we only need to pass what we actually use
await integration.hooks['astro:config:setup']({
...baseConfigHookObject,
updateConfig,
injectScript,
config: cloudflareConfig,
});

expect(orchestrionVite).not.toHaveBeenCalled();
expect(updateConfig).not.toHaveBeenCalledWith({
vite: {
plugins: [{ name: 'sentry-orchestrion-vite' }],
},
});
});

it("doesn't warn about deprecated options when `buildTimeInstrumentation` is set", async () => {
const integration = sentryAstro({ buildTimeInstrumentation: false });

const logger = { warn: vi.fn(), info: vi.fn() };

expect(integration.hooks['astro:config:setup']).toBeDefined();
// @ts-expect-error - the hook exists and we only need to pass what we actually use
await integration.hooks['astro:config:setup']({
...baseConfigHookObject,
updateConfig,
injectScript,
config,
logger,

Check failure on line 468 in packages/astro/test/integration/index.test.ts

View workflow job for this annotation

GitHub Actions / Node (22) Unit Tests

Unhandled error

TypeCheckError: Type '{ warn Mock<Procedure>; info Mock<Procedure>; }' is missing the following properties from type 'AstroIntegrationLogger' options, label, fork, error, debug ❯ test/integration/index.test.ts:468:7

Check failure on line 468 in packages/astro/test/integration/index.test.ts

View workflow job for this annotation

GitHub Actions / Node (20.19) Unit Tests

Unhandled error

TypeCheckError: Type '{ warn Mock<Procedure>; info Mock<Procedure>; }' is missing the following properties from type 'AstroIntegrationLogger' options, label, fork, error, debug ❯ test/integration/index.test.ts:468:7

Check failure on line 468 in packages/astro/test/integration/index.test.ts

View workflow job for this annotation

GitHub Actions / Node (24) Unit Tests

Unhandled error

TypeCheckError: Type '{ warn Mock<Procedure>; info Mock<Procedure>; }' is missing the following properties from type 'AstroIntegrationLogger' options, label, fork, error, debug ❯ test/integration/index.test.ts:468:7

Check failure on line 468 in packages/astro/test/integration/index.test.ts

View workflow job for this annotation

GitHub Actions / Node (26) Unit Tests

Unhandled error

TypeCheckError: Type '{ warn Mock<Procedure>; info Mock<Procedure>; }' is missing the following properties from type 'AstroIntegrationLogger' options, label, fork, error, debug ❯ test/integration/index.test.ts:468:7
});

expect(logger.warn).not.toHaveBeenCalledWith(expect.stringContaining('buildTimeInstrumentation'));
});

it.each([{}, { enabled: true }])('injects client and server init scripts', async options => {
const integration = sentryAstro(options);

Expand Down
Loading