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
Expand Up @@ -4,7 +4,7 @@
"version": "0.0.1",
"type": "module",
"scripts": {
"build": "vite build && cp instrument.server.mjs .output/server",
"build": "vite build",
"start": "node --import ./.output/server/instrument.server.mjs .output/server/index.mjs",
"test": "playwright test",
"clean": "npx rimraf node_modules pnpm-lock.yaml",
Expand Down
96 changes: 96 additions & 0 deletions packages/tanstackstart-react/src/vite/copyInstrumentationFile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { consoleSandbox } from '@sentry/core';
import * as fs from 'fs';
import * as path from 'path';
import type { Plugin, ResolvedConfig } from 'vite';

/**
* Creates a Vite plugin that copies the user's instrumentation file
* to the server build output directory after the build completes.
*
* By default, copies `instrument.server.mjs` from the project root.
* A custom file path can be provided via `instrumentationFilePath`.
*
* Supports:
* - Nitro deployments (reads output dir from the Nitro Vite environment config)
* - Cloudflare/Netlify deployments (outputs to `dist/server`)
*/
export function makeCopyInstrumentationFilePlugin(instrumentationFilePath?: string): Plugin {
let serverOutputDir: string | undefined;

return {
name: 'sentry-tanstackstart-copy-instrumentation-file',
apply: 'build',
enforce: 'post',

configResolved(resolvedConfig: ResolvedConfig) {
const plugins = resolvedConfig.plugins || [];
const hasPlugin = (name: string): boolean => plugins.some(p => p.name === name);

if (hasPlugin('nitro')) {
// Nitro case: read server dir from the nitro environment config
// Vite 6 environment configs are not part of the public type definitions yet,
// so we need to access them via an index signature.
const environments = (resolvedConfig as Record<string, unknown>)['environments'] as
| Record<string, { build?: { rollupOptions?: { output?: { dir?: string } | Array<{ dir?: string }> } } }>
| undefined;
const nitroEnv = environments?.nitro;
if (nitroEnv) {
const rollupOutput = nitroEnv.build?.rollupOptions?.output;
const dir = Array.isArray(rollupOutput) ? rollupOutput[0]?.dir : rollupOutput?.dir;
if (dir) {
serverOutputDir = dir;
}
}
} else if (hasPlugin('cloudflare') || hasPlugin('netlify')) {
serverOutputDir = path.resolve(resolvedConfig.root, 'dist', 'server');
} else {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.warn(
'[Sentry TanStack Start] Could not detect nitro, cloudflare, or netlify vite plugin. ' +
'The instrument.server.mjs file will not be copied to the build output automatically.',
);
});
}
},

async closeBundle() {
if (!serverOutputDir) {
return;
}

const instrumentationFileName = instrumentationFilePath || 'instrument.server.mjs';
const instrumentationSource = path.resolve(process.cwd(), instrumentationFileName);

try {
await fs.promises.access(instrumentationSource, fs.constants.F_OK);
} catch {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.warn(
`[Sentry TanStack Start] No ${instrumentationFileName} file found in project root. ` +
'The Sentry instrumentation file will not be copied to the build output.',
);
});
return;
}

const destinationFileName = path.basename(instrumentationFileName);
const destination = path.resolve(serverOutputDir, destinationFileName);

try {
await fs.promises.mkdir(serverOutputDir, { recursive: true });
await fs.promises.copyFile(instrumentationSource, destination);
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.log(`[Sentry TanStack Start] Copied ${destinationFileName} to ${destination}`);
});
} catch (error) {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.warn(`[Sentry TanStack Start] Failed to copy ${destinationFileName} to build output.`, error);
});
}
},
};
}
13 changes: 13 additions & 0 deletions packages/tanstackstart-react/src/vite/sentryTanstackStart.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { BuildTimeOptionsBase } from '@sentry/core';
import type { Plugin } from 'vite';
import { makeAutoInstrumentMiddlewarePlugin } from './autoInstrumentMiddleware';
import { makeCopyInstrumentationFilePlugin } from './copyInstrumentationFile';
import { makeAddSentryVitePlugin, makeEnableSourceMapsVitePlugin } from './sourceMaps';

/**
Expand All @@ -19,6 +20,15 @@ export interface SentryTanstackStartOptions extends BuildTimeOptionsBase {
* @default true
*/
autoInstrumentMiddleware?: boolean;

/**
* Path to the instrumentation file to be copied to the server build output directory.
*
* Relative paths are resolved from the current working directory.
*
* @default 'instrument.server.mjs'
*/
instrumentationFilePath?: string;
}

/**
Expand Down Expand Up @@ -53,6 +63,9 @@ export function sentryTanstackStart(options: SentryTanstackStartOptions = {}): P

const plugins: Plugin[] = [...makeAddSentryVitePlugin(options)];

// copy instrumentation file to build output
plugins.push(makeCopyInstrumentationFilePlugin(options.instrumentationFilePath));

// middleware auto-instrumentation
if (options.autoInstrumentMiddleware !== false) {
plugins.push(makeAutoInstrumentMiddlewarePlugin({ enabled: true, debug: options.debug }));
Expand Down
Loading
Loading