@sentry/wizard 6.6.1 → 6.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/LICENSE +97 -8
- package/dist/bin.js +5 -0
- package/dist/bin.js.map +1 -1
- package/dist/e2e-tests/tests/help-message.test.js +5 -1
- package/dist/e2e-tests/tests/help-message.test.js.map +1 -1
- package/dist/e2e-tests/tests/nextjs-15.test.js +79 -0
- package/dist/e2e-tests/tests/nextjs-15.test.js.map +1 -1
- package/dist/e2e-tests/tests/pnpm-workspace.test.d.ts +1 -0
- package/dist/e2e-tests/tests/pnpm-workspace.test.js +206 -0
- package/dist/e2e-tests/tests/pnpm-workspace.test.js.map +1 -0
- package/dist/e2e-tests/tests/react-router.test.d.ts +1 -0
- package/dist/e2e-tests/tests/react-router.test.js +255 -0
- package/dist/e2e-tests/tests/react-router.test.js.map +1 -0
- package/dist/e2e-tests/utils/index.d.ts +8 -2
- package/dist/e2e-tests/utils/index.js +72 -21
- package/dist/e2e-tests/utils/index.js.map +1 -1
- package/dist/lib/Constants.d.ts +1 -0
- package/dist/lib/Constants.js +5 -0
- package/dist/lib/Constants.js.map +1 -1
- package/dist/src/android/android-wizard.js +8 -1
- package/dist/src/android/android-wizard.js.map +1 -1
- package/dist/src/angular/angular-wizard.js +8 -1
- package/dist/src/angular/angular-wizard.js.map +1 -1
- package/dist/src/apple/apple-wizard.js +8 -1
- package/dist/src/apple/apple-wizard.js.map +1 -1
- package/dist/src/flutter/flutter-wizard.js +8 -1
- package/dist/src/flutter/flutter-wizard.js.map +1 -1
- package/dist/src/nextjs/nextjs-wizard.js +35 -9
- package/dist/src/nextjs/nextjs-wizard.js.map +1 -1
- package/dist/src/nextjs/templates.d.ts +3 -3
- package/dist/src/nextjs/templates.js +18 -7
- package/dist/src/nextjs/templates.js.map +1 -1
- package/dist/src/nuxt/nuxt-wizard.js +8 -1
- package/dist/src/nuxt/nuxt-wizard.js.map +1 -1
- package/dist/src/react-native/react-native-wizard.js +8 -1
- package/dist/src/react-native/react-native-wizard.js.map +1 -1
- package/dist/src/react-router/codemods/client.entry.d.ts +1 -0
- package/dist/src/react-router/codemods/client.entry.js +73 -0
- package/dist/src/react-router/codemods/client.entry.js.map +1 -0
- package/dist/src/react-router/codemods/react-router-config.d.ts +9 -0
- package/dist/src/react-router/codemods/react-router-config.js +178 -0
- package/dist/src/react-router/codemods/react-router-config.js.map +1 -0
- package/dist/src/react-router/codemods/root.d.ts +1 -0
- package/dist/src/react-router/codemods/root.js +171 -0
- package/dist/src/react-router/codemods/root.js.map +1 -0
- package/dist/src/react-router/codemods/routes-config.d.ts +1 -0
- package/dist/src/react-router/codemods/routes-config.js +106 -0
- package/dist/src/react-router/codemods/routes-config.js.map +1 -0
- package/dist/src/react-router/codemods/server-entry.d.ts +4 -0
- package/dist/src/react-router/codemods/server-entry.js +275 -0
- package/dist/src/react-router/codemods/server-entry.js.map +1 -0
- package/dist/src/react-router/codemods/utils.d.ts +2 -0
- package/dist/src/react-router/codemods/utils.js +13 -0
- package/dist/src/react-router/codemods/utils.js.map +1 -0
- package/dist/src/react-router/codemods/vite.d.ts +8 -0
- package/dist/src/react-router/codemods/vite.js +169 -0
- package/dist/src/react-router/codemods/vite.js.map +1 -0
- package/dist/src/react-router/react-router-wizard.d.ts +2 -0
- package/dist/src/react-router/react-router-wizard.js +254 -0
- package/dist/src/react-router/react-router-wizard.js.map +1 -0
- package/dist/src/react-router/sdk-example.d.ts +18 -0
- package/dist/src/react-router/sdk-example.js +306 -0
- package/dist/src/react-router/sdk-example.js.map +1 -0
- package/dist/src/react-router/sdk-setup.d.ts +17 -0
- package/dist/src/react-router/sdk-setup.js +250 -0
- package/dist/src/react-router/sdk-setup.js.map +1 -0
- package/dist/src/react-router/templates.d.ts +11 -0
- package/dist/src/react-router/templates.js +273 -0
- package/dist/src/react-router/templates.js.map +1 -0
- package/dist/src/remix/remix-wizard.js +8 -1
- package/dist/src/remix/remix-wizard.js.map +1 -1
- package/dist/src/run.d.ts +2 -1
- package/dist/src/run.js +6 -0
- package/dist/src/run.js.map +1 -1
- package/dist/src/sourcemaps/sourcemaps-wizard.js +8 -1
- package/dist/src/sourcemaps/sourcemaps-wizard.js.map +1 -1
- package/dist/src/sveltekit/sveltekit-wizard.js +8 -1
- package/dist/src/sveltekit/sveltekit-wizard.js.map +1 -1
- package/dist/src/utils/ast-utils.d.ts +30 -0
- package/dist/src/utils/ast-utils.js +71 -1
- package/dist/src/utils/ast-utils.js.map +1 -1
- package/dist/src/utils/clack/index.d.ts +5 -2
- package/dist/src/utils/clack/index.js +8 -0
- package/dist/src/utils/clack/index.js.map +1 -1
- package/dist/src/utils/package-json.js +86 -2
- package/dist/src/utils/package-json.js.map +1 -1
- package/dist/src/utils/types.d.ts +9 -0
- package/dist/src/utils/types.js.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/dist/src/version.js.map +1 -1
- package/dist/test/nextjs/templates.test.js +20 -0
- package/dist/test/nextjs/templates.test.js.map +1 -1
- package/dist/test/react-router/codemods/client-entry.test.d.ts +1 -0
- package/dist/test/react-router/codemods/client-entry.test.js +168 -0
- package/dist/test/react-router/codemods/client-entry.test.js.map +1 -0
- package/dist/test/react-router/codemods/react-router-config.test.d.ts +1 -0
- package/dist/test/react-router/codemods/react-router-config.test.js +168 -0
- package/dist/test/react-router/codemods/react-router-config.test.js.map +1 -0
- package/dist/test/react-router/codemods/root.test.d.ts +1 -0
- package/dist/test/react-router/codemods/root.test.js +178 -0
- package/dist/test/react-router/codemods/root.test.js.map +1 -0
- package/dist/test/react-router/codemods/server-entry.test.d.ts +1 -0
- package/dist/test/react-router/codemods/server-entry.test.js +415 -0
- package/dist/test/react-router/codemods/server-entry.test.js.map +1 -0
- package/dist/test/react-router/codemods/vite.test.d.ts +1 -0
- package/dist/test/react-router/codemods/vite.test.js +158 -0
- package/dist/test/react-router/codemods/vite.test.js.map +1 -0
- package/dist/test/react-router/routes-config.test.d.ts +1 -0
- package/dist/test/react-router/routes-config.test.js +156 -0
- package/dist/test/react-router/routes-config.test.js.map +1 -0
- package/dist/test/react-router/sdk-setup.test.d.ts +1 -0
- package/dist/test/react-router/sdk-setup.test.js +411 -0
- package/dist/test/react-router/sdk-setup.test.js.map +1 -0
- package/dist/test/react-router/templates.test.d.ts +1 -0
- package/dist/test/react-router/templates.test.js +220 -0
- package/dist/test/react-router/templates.test.js.map +1 -0
- package/dist/test/utils/package-json.test.d.ts +1 -0
- package/dist/test/utils/package-json.test.js +428 -0
- package/dist/test/utils/package-json.test.js.map +1 -0
- package/package.json +4 -2
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { PackageDotJson } from '../utils/package-json';
|
|
2
|
+
export declare function getRouteFilePath(filename: string, isTS: boolean): string;
|
|
3
|
+
export declare function tryRevealAndGetManualInstructions(missingFilename: string, filePath: string): Promise<boolean>;
|
|
4
|
+
export declare function runReactRouterReveal(force?: boolean): void;
|
|
5
|
+
export declare function isReactRouterV7(packageJson: PackageDotJson): boolean;
|
|
6
|
+
export declare function initializeSentryOnEntryClient(dsn: string, enableTracing: boolean, enableReplay: boolean, enableLogs: boolean, isTS: boolean): Promise<void>;
|
|
7
|
+
export declare function instrumentRootRoute(isTS: boolean): Promise<void>;
|
|
8
|
+
export declare function createServerInstrumentationFile(dsn: string, selectedFeatures: {
|
|
9
|
+
performance: boolean;
|
|
10
|
+
replay: boolean;
|
|
11
|
+
logs: boolean;
|
|
12
|
+
profiling: boolean;
|
|
13
|
+
}): string;
|
|
14
|
+
export declare function updatePackageJsonScripts(): Promise<void>;
|
|
15
|
+
export declare function instrumentSentryOnEntryServer(isTS: boolean): Promise<void>;
|
|
16
|
+
export declare function configureReactRouterVitePlugin(orgSlug: string, projectSlug: string): Promise<void>;
|
|
17
|
+
export declare function configureReactRouterConfig(isTS: boolean): Promise<void>;
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.configureReactRouterConfig = exports.configureReactRouterVitePlugin = exports.instrumentSentryOnEntryServer = exports.updatePackageJsonScripts = exports.createServerInstrumentationFile = exports.instrumentRootRoute = exports.initializeSentryOnEntryClient = exports.isReactRouterV7 = exports.runReactRouterReveal = exports.tryRevealAndGetManualInstructions = exports.getRouteFilePath = void 0;
|
|
30
|
+
const fs = __importStar(require("fs"));
|
|
31
|
+
const path = __importStar(require("path"));
|
|
32
|
+
const childProcess = __importStar(require("child_process"));
|
|
33
|
+
// @ts-expect-error - clack is ESM and TS complains about that. It works though
|
|
34
|
+
const prompts_1 = __importDefault(require("@clack/prompts"));
|
|
35
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
36
|
+
const semver_1 = require("semver");
|
|
37
|
+
const package_json_1 = require("../utils/package-json");
|
|
38
|
+
const debug_1 = require("../utils/debug");
|
|
39
|
+
const templates_1 = require("./templates");
|
|
40
|
+
const root_1 = require("./codemods/root");
|
|
41
|
+
const server_entry_1 = require("./codemods/server-entry");
|
|
42
|
+
const clack_1 = require("../utils/clack");
|
|
43
|
+
const client_entry_1 = require("./codemods/client.entry");
|
|
44
|
+
const vite_1 = require("./codemods/vite");
|
|
45
|
+
const react_router_config_1 = require("./codemods/react-router-config");
|
|
46
|
+
const REACT_ROUTER_REVEAL_COMMAND = 'npx react-router reveal';
|
|
47
|
+
const INSTRUMENTATION_FILE = 'instrument.server.mjs';
|
|
48
|
+
const APP_DIRECTORY = 'app';
|
|
49
|
+
const ROUTES_DIRECTORY = 'routes';
|
|
50
|
+
function _formatConfigErrorMessage(filename, errorMessage, fallbackHint) {
|
|
51
|
+
return (`Could not automatically configure ${filename}. ${errorMessage}\n` +
|
|
52
|
+
`This may happen if your config has an unusual format. ` +
|
|
53
|
+
`${fallbackHint}`);
|
|
54
|
+
}
|
|
55
|
+
function getAppFilePath(filename, isTS, isPage = true) {
|
|
56
|
+
const ext = isPage ? (isTS ? 'tsx' : 'jsx') : isTS ? 'ts' : 'js';
|
|
57
|
+
return path.join(process.cwd(), APP_DIRECTORY, `${filename}.${ext}`);
|
|
58
|
+
}
|
|
59
|
+
function getRouteFilePath(filename, isTS) {
|
|
60
|
+
const ext = isTS ? 'tsx' : 'jsx';
|
|
61
|
+
return path.join(process.cwd(), APP_DIRECTORY, ROUTES_DIRECTORY, `${filename}.${ext}`);
|
|
62
|
+
}
|
|
63
|
+
exports.getRouteFilePath = getRouteFilePath;
|
|
64
|
+
async function tryRevealAndGetManualInstructions(missingFilename, filePath) {
|
|
65
|
+
const shouldTryReveal = await prompts_1.default.confirm({
|
|
66
|
+
message: `Would you like to try running ${chalk_1.default.cyan(REACT_ROUTER_REVEAL_COMMAND)} to generate entry files?`,
|
|
67
|
+
initialValue: true,
|
|
68
|
+
});
|
|
69
|
+
if (shouldTryReveal) {
|
|
70
|
+
try {
|
|
71
|
+
prompts_1.default.log.info(`Running ${chalk_1.default.cyan(REACT_ROUTER_REVEAL_COMMAND)}...`);
|
|
72
|
+
const output = childProcess.execSync(REACT_ROUTER_REVEAL_COMMAND, {
|
|
73
|
+
encoding: 'utf8',
|
|
74
|
+
stdio: 'pipe',
|
|
75
|
+
});
|
|
76
|
+
prompts_1.default.log.info(output);
|
|
77
|
+
if (fs.existsSync(filePath)) {
|
|
78
|
+
prompts_1.default.log.success(`Found ${chalk_1.default.cyan(missingFilename)} after running reveal.`);
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
prompts_1.default.log.warn(`${chalk_1.default.cyan(missingFilename)} still not found after running reveal.`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch (e) {
|
|
86
|
+
(0, debug_1.debug)('Failed to run React Router reveal command:', e);
|
|
87
|
+
prompts_1.default.log.warn(`Failed to run ${chalk_1.default.cyan(REACT_ROUTER_REVEAL_COMMAND)}. This command generates entry files for React Router v7.`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return false; // File still doesn't exist, manual intervention needed
|
|
91
|
+
}
|
|
92
|
+
exports.tryRevealAndGetManualInstructions = tryRevealAndGetManualInstructions;
|
|
93
|
+
async function ensureEntryFileExists(filename, filePath) {
|
|
94
|
+
if (fs.existsSync(filePath)) {
|
|
95
|
+
return; // File exists, nothing to do
|
|
96
|
+
}
|
|
97
|
+
prompts_1.default.log.warn(`Could not find ${chalk_1.default.cyan(filename)}.`);
|
|
98
|
+
const fileExists = await tryRevealAndGetManualInstructions(filename, filePath);
|
|
99
|
+
if (!fileExists) {
|
|
100
|
+
throw new Error(`Failed to create or find ${filename}. Please create this file manually or ensure your React Router v7 project structure is correct.`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function runReactRouterReveal(force = false) {
|
|
104
|
+
if (force ||
|
|
105
|
+
(!fs.existsSync(path.join(process.cwd(), 'app/entry.client.tsx')) &&
|
|
106
|
+
!fs.existsSync(path.join(process.cwd(), 'app/entry.client.jsx')))) {
|
|
107
|
+
try {
|
|
108
|
+
childProcess.execSync(REACT_ROUTER_REVEAL_COMMAND, {
|
|
109
|
+
encoding: 'utf8',
|
|
110
|
+
stdio: 'pipe',
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
catch (e) {
|
|
114
|
+
(0, debug_1.debug)('Failed to run React Router reveal command:', e);
|
|
115
|
+
throw e;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
exports.runReactRouterReveal = runReactRouterReveal;
|
|
120
|
+
function isReactRouterV7(packageJson) {
|
|
121
|
+
const reactRouterVersion = (0, package_json_1.getPackageVersion)('@react-router/dev', packageJson);
|
|
122
|
+
if (!reactRouterVersion) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
const minVer = (0, semver_1.minVersion)(reactRouterVersion);
|
|
126
|
+
if (!minVer) {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
return (0, semver_1.gte)(minVer, '7.0.0');
|
|
130
|
+
}
|
|
131
|
+
exports.isReactRouterV7 = isReactRouterV7;
|
|
132
|
+
async function initializeSentryOnEntryClient(dsn, enableTracing, enableReplay, enableLogs, isTS) {
|
|
133
|
+
const clientEntryPath = getAppFilePath('entry.client', isTS);
|
|
134
|
+
const clientEntryFilename = path.basename(clientEntryPath);
|
|
135
|
+
await ensureEntryFileExists(clientEntryFilename, clientEntryPath);
|
|
136
|
+
await (0, client_entry_1.instrumentClientEntry)(clientEntryPath, dsn, enableTracing, enableReplay, enableLogs);
|
|
137
|
+
prompts_1.default.log.success(`Updated ${chalk_1.default.cyan(clientEntryFilename)} with Sentry initialization.`);
|
|
138
|
+
}
|
|
139
|
+
exports.initializeSentryOnEntryClient = initializeSentryOnEntryClient;
|
|
140
|
+
async function instrumentRootRoute(isTS) {
|
|
141
|
+
const rootPath = getAppFilePath('root', isTS);
|
|
142
|
+
const rootFilename = path.basename(rootPath);
|
|
143
|
+
if (!fs.existsSync(rootPath)) {
|
|
144
|
+
throw new Error(`${rootFilename} not found in app directory. Please ensure your React Router v7 app has a root.tsx/jsx file in the app folder.`);
|
|
145
|
+
}
|
|
146
|
+
await (0, root_1.instrumentRoot)(rootFilename);
|
|
147
|
+
prompts_1.default.log.success(`Updated ${chalk_1.default.cyan(rootFilename)} with ErrorBoundary.`);
|
|
148
|
+
}
|
|
149
|
+
exports.instrumentRootRoute = instrumentRootRoute;
|
|
150
|
+
function createServerInstrumentationFile(dsn, selectedFeatures) {
|
|
151
|
+
const instrumentationPath = path.join(process.cwd(), INSTRUMENTATION_FILE);
|
|
152
|
+
const content = (0, templates_1.getSentryInstrumentationServerContent)(dsn, selectedFeatures.performance, selectedFeatures.profiling, selectedFeatures.logs);
|
|
153
|
+
fs.writeFileSync(instrumentationPath, content);
|
|
154
|
+
prompts_1.default.log.success(`Created ${chalk_1.default.cyan(INSTRUMENTATION_FILE)}.`);
|
|
155
|
+
return instrumentationPath;
|
|
156
|
+
}
|
|
157
|
+
exports.createServerInstrumentationFile = createServerInstrumentationFile;
|
|
158
|
+
async function updatePackageJsonScripts() {
|
|
159
|
+
const packageJson = await (0, clack_1.getPackageDotJson)();
|
|
160
|
+
if (!packageJson?.scripts) {
|
|
161
|
+
throw new Error('Could not find a `scripts` section in your package.json file. Please add scripts manually or ensure your package.json is valid.');
|
|
162
|
+
}
|
|
163
|
+
if (!packageJson.scripts.start) {
|
|
164
|
+
throw new Error('Could not find a `start` script in your package.json. Please add: "start": "react-router-serve ./build/server/index.js" and re-run the wizard.');
|
|
165
|
+
}
|
|
166
|
+
function mergeNodeOptions(scriptCommand, instrumentPath = './instrument.server.mjs') {
|
|
167
|
+
if (scriptCommand.includes(instrumentPath)) {
|
|
168
|
+
return scriptCommand;
|
|
169
|
+
}
|
|
170
|
+
const quotedMatch = scriptCommand.match(/NODE_OPTIONS=(['"])([^'"]*)\1/);
|
|
171
|
+
if (quotedMatch) {
|
|
172
|
+
const existingOptions = quotedMatch[2];
|
|
173
|
+
const mergedOptions = `${existingOptions} --import ${instrumentPath}`.trim();
|
|
174
|
+
return scriptCommand.replace(/NODE_OPTIONS=(['"])([^'"]*)\1/, `NODE_OPTIONS='${mergedOptions}'`);
|
|
175
|
+
}
|
|
176
|
+
const unquotedMatch = scriptCommand.match(/NODE_OPTIONS=([^\s]+(?:\s+[^\s]+)*?)(\s+(?:react-router-serve|react-router|node|npx|tsx))/);
|
|
177
|
+
if (unquotedMatch) {
|
|
178
|
+
const existingOptions = unquotedMatch[1];
|
|
179
|
+
const commandPart = unquotedMatch[2];
|
|
180
|
+
const mergedOptions = `${existingOptions} --import ${instrumentPath}`.trim();
|
|
181
|
+
return scriptCommand.replace(/NODE_OPTIONS=([^\s]+(?:\s+[^\s]+)*?)(\s+(?:react-router-serve|react-router|node|npx|tsx))/, `NODE_OPTIONS='${mergedOptions}'${commandPart}`);
|
|
182
|
+
}
|
|
183
|
+
return `NODE_OPTIONS='--import ${instrumentPath}' ${scriptCommand}`;
|
|
184
|
+
}
|
|
185
|
+
if (packageJson.scripts.dev) {
|
|
186
|
+
packageJson.scripts.dev = mergeNodeOptions(packageJson.scripts.dev);
|
|
187
|
+
}
|
|
188
|
+
const startScript = packageJson.scripts.start;
|
|
189
|
+
if (!startScript.includes(INSTRUMENTATION_FILE) &&
|
|
190
|
+
!startScript.includes('NODE_OPTIONS')) {
|
|
191
|
+
packageJson.scripts.start = `NODE_OPTIONS='--import ./${INSTRUMENTATION_FILE}' react-router-serve ./build/server/index.js`;
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
packageJson.scripts.start = mergeNodeOptions(startScript);
|
|
195
|
+
}
|
|
196
|
+
await fs.promises.writeFile('package.json', JSON.stringify(packageJson, null, 2));
|
|
197
|
+
}
|
|
198
|
+
exports.updatePackageJsonScripts = updatePackageJsonScripts;
|
|
199
|
+
async function instrumentSentryOnEntryServer(isTS) {
|
|
200
|
+
const serverEntryPath = getAppFilePath('entry.server', isTS);
|
|
201
|
+
const serverEntryFilename = path.basename(serverEntryPath);
|
|
202
|
+
await ensureEntryFileExists(serverEntryFilename, serverEntryPath);
|
|
203
|
+
await (0, server_entry_1.instrumentServerEntry)(serverEntryPath);
|
|
204
|
+
prompts_1.default.log.success(`Updated ${chalk_1.default.cyan(serverEntryFilename)} with Sentry error handling.`);
|
|
205
|
+
}
|
|
206
|
+
exports.instrumentSentryOnEntryServer = instrumentSentryOnEntryServer;
|
|
207
|
+
async function configureReactRouterVitePlugin(orgSlug, projectSlug) {
|
|
208
|
+
const configPath = fs.existsSync(path.join(process.cwd(), 'vite.config.ts'))
|
|
209
|
+
? path.join(process.cwd(), 'vite.config.ts')
|
|
210
|
+
: path.join(process.cwd(), 'vite.config.js');
|
|
211
|
+
const filename = chalk_1.default.cyan(path.basename(configPath));
|
|
212
|
+
try {
|
|
213
|
+
const { wasConverted } = await (0, vite_1.instrumentViteConfig)(orgSlug, projectSlug);
|
|
214
|
+
prompts_1.default.log.success(`Updated ${filename} with sentryReactRouter plugin.`);
|
|
215
|
+
if (wasConverted) {
|
|
216
|
+
prompts_1.default.log.info(`Converted your Vite config to function form ${chalk_1.default.dim('(defineConfig(config => ({ ... })))')} to support the Sentry React Router plugin.`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
catch (e) {
|
|
220
|
+
(0, debug_1.debug)('Failed to modify vite config:', e);
|
|
221
|
+
const errorMessage = e instanceof Error ? e.message : String(e);
|
|
222
|
+
throw new Error(_formatConfigErrorMessage(filename, errorMessage, 'You may need to add the plugin manually.'));
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
exports.configureReactRouterVitePlugin = configureReactRouterVitePlugin;
|
|
226
|
+
async function configureReactRouterConfig(isTS) {
|
|
227
|
+
const configFilename = `react-router.config.${isTS ? 'ts' : 'js'}`;
|
|
228
|
+
const configPath = path.join(process.cwd(), configFilename);
|
|
229
|
+
const filename = chalk_1.default.cyan(configFilename);
|
|
230
|
+
try {
|
|
231
|
+
const fileExistedBefore = fs.existsSync(configPath);
|
|
232
|
+
const { ssrWasChanged } = await (0, react_router_config_1.instrumentReactRouterConfig)(isTS);
|
|
233
|
+
if (fileExistedBefore) {
|
|
234
|
+
prompts_1.default.log.success(`Updated ${filename} with Sentry buildEnd hook.`);
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
prompts_1.default.log.success(`Created ${filename} with Sentry buildEnd hook.`);
|
|
238
|
+
}
|
|
239
|
+
if (ssrWasChanged) {
|
|
240
|
+
prompts_1.default.log.warn(`${chalk_1.default.yellow('Note:')} SSR has been enabled in your React Router config (${chalk_1.default.cyan('ssr: true')}). This is required for Sentry sourcemap uploads to work correctly.`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
catch (e) {
|
|
244
|
+
(0, debug_1.debug)('Failed to modify react-router.config:', e);
|
|
245
|
+
const errorMessage = e instanceof Error ? e.message : String(e);
|
|
246
|
+
throw new Error(_formatConfigErrorMessage(filename, errorMessage, 'You may need to add the buildEnd hook manually.'));
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
exports.configureReactRouterConfig = configureReactRouterConfig;
|
|
250
|
+
//# sourceMappingURL=sdk-setup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sdk-setup.js","sourceRoot":"","sources":["../../../src/react-router/sdk-setup.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport * as childProcess from 'child_process';\n\n// @ts-expect-error - clack is ESM and TS complains about that. It works though\nimport clack from '@clack/prompts';\nimport chalk from 'chalk';\nimport { gte, minVersion } from 'semver';\n\nimport type { PackageDotJson } from '../utils/package-json';\nimport { getPackageVersion } from '../utils/package-json';\nimport { debug } from '../utils/debug';\nimport { getSentryInstrumentationServerContent } from './templates';\nimport { instrumentRoot } from './codemods/root';\nimport { instrumentServerEntry } from './codemods/server-entry';\nimport { getPackageDotJson } from '../utils/clack';\nimport { instrumentClientEntry } from './codemods/client.entry';\nimport { instrumentViteConfig } from './codemods/vite';\nimport { instrumentReactRouterConfig } from './codemods/react-router-config';\n\nconst REACT_ROUTER_REVEAL_COMMAND = 'npx react-router reveal';\nconst INSTRUMENTATION_FILE = 'instrument.server.mjs';\nconst APP_DIRECTORY = 'app';\nconst ROUTES_DIRECTORY = 'routes';\n\nfunction _formatConfigErrorMessage(\n filename: string,\n errorMessage: string,\n fallbackHint: string,\n): string {\n return (\n `Could not automatically configure ${filename}. ${errorMessage}\\n` +\n `This may happen if your config has an unusual format. ` +\n `${fallbackHint}`\n );\n}\n\nfunction getAppFilePath(\n filename: string,\n isTS: boolean,\n isPage = true,\n): string {\n const ext = isPage ? (isTS ? 'tsx' : 'jsx') : isTS ? 'ts' : 'js';\n return path.join(process.cwd(), APP_DIRECTORY, `${filename}.${ext}`);\n}\n\nexport function getRouteFilePath(filename: string, isTS: boolean): string {\n const ext = isTS ? 'tsx' : 'jsx';\n return path.join(\n process.cwd(),\n APP_DIRECTORY,\n ROUTES_DIRECTORY,\n `${filename}.${ext}`,\n );\n}\n\nexport async function tryRevealAndGetManualInstructions(\n missingFilename: string,\n filePath: string,\n): Promise<boolean> {\n const shouldTryReveal = await clack.confirm({\n message: `Would you like to try running ${chalk.cyan(\n REACT_ROUTER_REVEAL_COMMAND,\n )} to generate entry files?`,\n initialValue: true,\n });\n\n if (shouldTryReveal) {\n try {\n clack.log.info(`Running ${chalk.cyan(REACT_ROUTER_REVEAL_COMMAND)}...`);\n const output = childProcess.execSync(REACT_ROUTER_REVEAL_COMMAND, {\n encoding: 'utf8',\n stdio: 'pipe',\n });\n clack.log.info(output);\n\n if (fs.existsSync(filePath)) {\n clack.log.success(\n `Found ${chalk.cyan(missingFilename)} after running reveal.`,\n );\n return true;\n } else {\n clack.log.warn(\n `${chalk.cyan(\n missingFilename,\n )} still not found after running reveal.`,\n );\n }\n } catch (e) {\n debug('Failed to run React Router reveal command:', e);\n clack.log.warn(\n `Failed to run ${chalk.cyan(\n REACT_ROUTER_REVEAL_COMMAND,\n )}. This command generates entry files for React Router v7.`,\n );\n }\n }\n\n return false; // File still doesn't exist, manual intervention needed\n}\n\nasync function ensureEntryFileExists(\n filename: string,\n filePath: string,\n): Promise<void> {\n if (fs.existsSync(filePath)) {\n return; // File exists, nothing to do\n }\n\n clack.log.warn(`Could not find ${chalk.cyan(filename)}.`);\n\n const fileExists = await tryRevealAndGetManualInstructions(\n filename,\n filePath,\n );\n\n if (!fileExists) {\n throw new Error(\n `Failed to create or find ${filename}. Please create this file manually or ensure your React Router v7 project structure is correct.`,\n );\n }\n}\n\nexport function runReactRouterReveal(force = false): void {\n if (\n force ||\n (!fs.existsSync(path.join(process.cwd(), 'app/entry.client.tsx')) &&\n !fs.existsSync(path.join(process.cwd(), 'app/entry.client.jsx')))\n ) {\n try {\n childProcess.execSync(REACT_ROUTER_REVEAL_COMMAND, {\n encoding: 'utf8',\n stdio: 'pipe',\n });\n } catch (e) {\n debug('Failed to run React Router reveal command:', e);\n throw e;\n }\n }\n}\n\nexport function isReactRouterV7(packageJson: PackageDotJson): boolean {\n const reactRouterVersion = getPackageVersion(\n '@react-router/dev',\n packageJson,\n );\n if (!reactRouterVersion) {\n return false;\n }\n\n const minVer = minVersion(reactRouterVersion);\n\n if (!minVer) {\n return false;\n }\n\n return gte(minVer, '7.0.0');\n}\n\nexport async function initializeSentryOnEntryClient(\n dsn: string,\n enableTracing: boolean,\n enableReplay: boolean,\n enableLogs: boolean,\n isTS: boolean,\n): Promise<void> {\n const clientEntryPath = getAppFilePath('entry.client', isTS);\n const clientEntryFilename = path.basename(clientEntryPath);\n\n await ensureEntryFileExists(clientEntryFilename, clientEntryPath);\n\n await instrumentClientEntry(\n clientEntryPath,\n dsn,\n enableTracing,\n enableReplay,\n enableLogs,\n );\n\n clack.log.success(\n `Updated ${chalk.cyan(clientEntryFilename)} with Sentry initialization.`,\n );\n}\n\nexport async function instrumentRootRoute(isTS: boolean): Promise<void> {\n const rootPath = getAppFilePath('root', isTS);\n const rootFilename = path.basename(rootPath);\n\n if (!fs.existsSync(rootPath)) {\n throw new Error(\n `${rootFilename} not found in app directory. Please ensure your React Router v7 app has a root.tsx/jsx file in the app folder.`,\n );\n }\n\n await instrumentRoot(rootFilename);\n clack.log.success(`Updated ${chalk.cyan(rootFilename)} with ErrorBoundary.`);\n}\n\nexport function createServerInstrumentationFile(\n dsn: string,\n selectedFeatures: {\n performance: boolean;\n replay: boolean;\n logs: boolean;\n profiling: boolean;\n },\n): string {\n const instrumentationPath = path.join(process.cwd(), INSTRUMENTATION_FILE);\n\n const content = getSentryInstrumentationServerContent(\n dsn,\n selectedFeatures.performance,\n selectedFeatures.profiling,\n selectedFeatures.logs,\n );\n\n fs.writeFileSync(instrumentationPath, content);\n clack.log.success(`Created ${chalk.cyan(INSTRUMENTATION_FILE)}.`);\n return instrumentationPath;\n}\n\nexport async function updatePackageJsonScripts(): Promise<void> {\n const packageJson = await getPackageDotJson();\n\n if (!packageJson?.scripts) {\n throw new Error(\n 'Could not find a `scripts` section in your package.json file. Please add scripts manually or ensure your package.json is valid.',\n );\n }\n\n if (!packageJson.scripts.start) {\n throw new Error(\n 'Could not find a `start` script in your package.json. Please add: \"start\": \"react-router-serve ./build/server/index.js\" and re-run the wizard.',\n );\n }\n\n function mergeNodeOptions(\n scriptCommand: string,\n instrumentPath = './instrument.server.mjs',\n ): string {\n if (scriptCommand.includes(instrumentPath)) {\n return scriptCommand;\n }\n\n const quotedMatch = scriptCommand.match(/NODE_OPTIONS=(['\"])([^'\"]*)\\1/);\n if (quotedMatch) {\n const existingOptions = quotedMatch[2];\n const mergedOptions =\n `${existingOptions} --import ${instrumentPath}`.trim();\n return scriptCommand.replace(\n /NODE_OPTIONS=(['\"])([^'\"]*)\\1/,\n `NODE_OPTIONS='${mergedOptions}'`,\n );\n }\n\n const unquotedMatch = scriptCommand.match(\n /NODE_OPTIONS=([^\\s]+(?:\\s+[^\\s]+)*?)(\\s+(?:react-router-serve|react-router|node|npx|tsx))/,\n );\n if (unquotedMatch) {\n const existingOptions = unquotedMatch[1];\n const commandPart = unquotedMatch[2];\n const mergedOptions =\n `${existingOptions} --import ${instrumentPath}`.trim();\n return scriptCommand.replace(\n /NODE_OPTIONS=([^\\s]+(?:\\s+[^\\s]+)*?)(\\s+(?:react-router-serve|react-router|node|npx|tsx))/,\n `NODE_OPTIONS='${mergedOptions}'${commandPart}`,\n );\n }\n\n return `NODE_OPTIONS='--import ${instrumentPath}' ${scriptCommand}`;\n }\n\n if (packageJson.scripts.dev) {\n packageJson.scripts.dev = mergeNodeOptions(packageJson.scripts.dev);\n }\n\n const startScript = packageJson.scripts.start;\n if (\n !startScript.includes(INSTRUMENTATION_FILE) &&\n !startScript.includes('NODE_OPTIONS')\n ) {\n packageJson.scripts.start = `NODE_OPTIONS='--import ./${INSTRUMENTATION_FILE}' react-router-serve ./build/server/index.js`;\n } else {\n packageJson.scripts.start = mergeNodeOptions(startScript);\n }\n\n await fs.promises.writeFile(\n 'package.json',\n JSON.stringify(packageJson, null, 2),\n );\n}\n\nexport async function instrumentSentryOnEntryServer(\n isTS: boolean,\n): Promise<void> {\n const serverEntryPath = getAppFilePath('entry.server', isTS);\n const serverEntryFilename = path.basename(serverEntryPath);\n\n await ensureEntryFileExists(serverEntryFilename, serverEntryPath);\n\n await instrumentServerEntry(serverEntryPath);\n\n clack.log.success(\n `Updated ${chalk.cyan(serverEntryFilename)} with Sentry error handling.`,\n );\n}\n\nexport async function configureReactRouterVitePlugin(\n orgSlug: string,\n projectSlug: string,\n): Promise<void> {\n const configPath = fs.existsSync(path.join(process.cwd(), 'vite.config.ts'))\n ? path.join(process.cwd(), 'vite.config.ts')\n : path.join(process.cwd(), 'vite.config.js');\n const filename = chalk.cyan(path.basename(configPath));\n\n try {\n const { wasConverted } = await instrumentViteConfig(orgSlug, projectSlug);\n\n clack.log.success(`Updated ${filename} with sentryReactRouter plugin.`);\n\n if (wasConverted) {\n clack.log.info(\n `Converted your Vite config to function form ${chalk.dim(\n '(defineConfig(config => ({ ... })))',\n )} to support the Sentry React Router plugin.`,\n );\n }\n } catch (e) {\n debug('Failed to modify vite config:', e);\n const errorMessage = e instanceof Error ? e.message : String(e);\n throw new Error(\n _formatConfigErrorMessage(\n filename,\n errorMessage,\n 'You may need to add the plugin manually.',\n ),\n );\n }\n}\n\nexport async function configureReactRouterConfig(isTS: boolean): Promise<void> {\n const configFilename = `react-router.config.${isTS ? 'ts' : 'js'}`;\n const configPath = path.join(process.cwd(), configFilename);\n const filename = chalk.cyan(configFilename);\n\n try {\n const fileExistedBefore = fs.existsSync(configPath);\n\n const { ssrWasChanged } = await instrumentReactRouterConfig(isTS);\n\n if (fileExistedBefore) {\n clack.log.success(`Updated ${filename} with Sentry buildEnd hook.`);\n } else {\n clack.log.success(`Created ${filename} with Sentry buildEnd hook.`);\n }\n\n if (ssrWasChanged) {\n clack.log.warn(\n `${chalk.yellow(\n 'Note:',\n )} SSR has been enabled in your React Router config (${chalk.cyan(\n 'ssr: true',\n )}). This is required for Sentry sourcemap uploads to work correctly.`,\n );\n }\n } catch (e) {\n debug('Failed to modify react-router.config:', e);\n const errorMessage = e instanceof Error ? e.message : String(e);\n throw new Error(\n _formatConfigErrorMessage(\n filename,\n errorMessage,\n 'You may need to add the buildEnd hook manually.',\n ),\n );\n }\n}\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const ERROR_BOUNDARY_TEMPLATE: string;
|
|
2
|
+
export declare const EXAMPLE_PAGE_TEMPLATE_TSX = "import type { Route } from \"./+types/sentry-example-page\";\n\nexport async function loader() {\n throw new Error(\"some error thrown in a loader\");\n}\n\nexport default function SentryExamplePage() {\n return <div>Loading this page will throw an error</div>;\n}";
|
|
3
|
+
export declare const EXAMPLE_PAGE_TEMPLATE_JSX = "export async function loader() {\n throw new Error(\"some error thrown in a loader\");\n}\n\nexport default function SentryExamplePage() {\n return <div>Loading this page will throw an error</div>;\n}";
|
|
4
|
+
export declare const getSentryInstrumentationServerContent: (dsn: string, enableTracing: boolean, enableProfiling?: boolean, enableLogs?: boolean) => string;
|
|
5
|
+
export declare const getManualClientEntryContent: (dsn: string, enableTracing: boolean, enableReplay: boolean, enableLogs: boolean) => string;
|
|
6
|
+
export declare const getManualServerEntryContent: () => string;
|
|
7
|
+
export declare const getManualHandleRequestContent: () => string;
|
|
8
|
+
export declare const getManualRootContent: (isTs: boolean) => string;
|
|
9
|
+
export declare const getManualServerInstrumentContent: (dsn: string, enableTracing: boolean, enableProfiling: boolean, enableLogs?: boolean) => string;
|
|
10
|
+
export declare const getManualReactRouterConfigContent: (isTS?: boolean) => string;
|
|
11
|
+
export declare const getManualViteConfigContent: (orgSlug: string, projectSlug: string) => string;
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getManualViteConfigContent = exports.getManualReactRouterConfigContent = exports.getManualServerInstrumentContent = exports.getManualRootContent = exports.getManualHandleRequestContent = exports.getManualServerEntryContent = exports.getManualClientEntryContent = exports.getSentryInstrumentationServerContent = exports.EXAMPLE_PAGE_TEMPLATE_JSX = exports.EXAMPLE_PAGE_TEMPLATE_TSX = exports.ERROR_BOUNDARY_TEMPLATE = void 0;
|
|
4
|
+
const clack_1 = require("../utils/clack");
|
|
5
|
+
function generateErrorBoundaryTemplate(isTypeScript, forManualInstructions = false) {
|
|
6
|
+
const typeAnnotations = isTypeScript
|
|
7
|
+
? { stack: ': string | undefined', props: ': Route.ErrorBoundaryProps' }
|
|
8
|
+
: { stack: '', props: '' };
|
|
9
|
+
const commentLine = forManualInstructions
|
|
10
|
+
? '// you only want to capture non 404-errors that reach the boundary\n '
|
|
11
|
+
: '// Only capture non-404 errors (all errors here are already non-RouteErrorResponse)\n ';
|
|
12
|
+
return `function ErrorBoundary({ error }${typeAnnotations.props}) {
|
|
13
|
+
let message = "Oops!";
|
|
14
|
+
let details = "An unexpected error occurred.";
|
|
15
|
+
let stack${typeAnnotations.stack};
|
|
16
|
+
|
|
17
|
+
if (isRouteErrorResponse(error)) {
|
|
18
|
+
message = error.status === 404 ? "404" : "Error";
|
|
19
|
+
details =
|
|
20
|
+
error.status === 404
|
|
21
|
+
? "The requested page could not be found."
|
|
22
|
+
: error.statusText || details;
|
|
23
|
+
} else if (error && error instanceof Error) {
|
|
24
|
+
${commentLine}Sentry.captureException(error);
|
|
25
|
+
details = error.message;
|
|
26
|
+
stack = error.stack;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<main>
|
|
31
|
+
<h1>{message}</h1>
|
|
32
|
+
<p>{details}</p>
|
|
33
|
+
{stack && (
|
|
34
|
+
<pre>
|
|
35
|
+
<code>{stack}</code>
|
|
36
|
+
</pre>
|
|
37
|
+
)}
|
|
38
|
+
</main>
|
|
39
|
+
);
|
|
40
|
+
}`;
|
|
41
|
+
}
|
|
42
|
+
exports.ERROR_BOUNDARY_TEMPLATE = generateErrorBoundaryTemplate(false);
|
|
43
|
+
exports.EXAMPLE_PAGE_TEMPLATE_TSX = `import type { Route } from "./+types/sentry-example-page";
|
|
44
|
+
|
|
45
|
+
export async function loader() {
|
|
46
|
+
throw new Error("some error thrown in a loader");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export default function SentryExamplePage() {
|
|
50
|
+
return <div>Loading this page will throw an error</div>;
|
|
51
|
+
}`;
|
|
52
|
+
exports.EXAMPLE_PAGE_TEMPLATE_JSX = `export async function loader() {
|
|
53
|
+
throw new Error("some error thrown in a loader");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export default function SentryExamplePage() {
|
|
57
|
+
return <div>Loading this page will throw an error</div>;
|
|
58
|
+
}`;
|
|
59
|
+
function generateServerInstrumentationCode(dsn, enableTracing, enableProfiling, enableLogs) {
|
|
60
|
+
return `import * as Sentry from '@sentry/react-router';${enableProfiling
|
|
61
|
+
? `\nimport { nodeProfilingIntegration } from '@sentry/profiling-node';`
|
|
62
|
+
: ''}
|
|
63
|
+
|
|
64
|
+
Sentry.init({
|
|
65
|
+
dsn: "${dsn}",
|
|
66
|
+
|
|
67
|
+
// Adds request headers and IP for users, for more info visit:
|
|
68
|
+
// https://docs.sentry.io/platforms/javascript/guides/react-router/configuration/options/#sendDefaultPii
|
|
69
|
+
sendDefaultPii: true,${enableLogs
|
|
70
|
+
? '\n\n // Enable logs to be sent to Sentry\n enableLogs: true,'
|
|
71
|
+
: ''}${enableProfiling ? '\n\n integrations: [nodeProfilingIntegration()],' : ''}
|
|
72
|
+
tracesSampleRate: ${enableTracing ? '1.0' : '0'}, ${enableTracing ? '// Capture 100% of the transactions' : ''}${enableProfiling
|
|
73
|
+
? '\n profilesSampleRate: 1.0, // profile every transaction'
|
|
74
|
+
: ''}${enableTracing
|
|
75
|
+
? `
|
|
76
|
+
|
|
77
|
+
// Set up performance monitoring
|
|
78
|
+
beforeSend(event) {
|
|
79
|
+
// Filter out 404s from error reporting
|
|
80
|
+
if (event.exception) {
|
|
81
|
+
const error = event.exception.values?.[0];
|
|
82
|
+
if (error?.type === "NotFoundException" || error?.value?.includes("404")) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return event;
|
|
87
|
+
},`
|
|
88
|
+
: ''}
|
|
89
|
+
});`;
|
|
90
|
+
}
|
|
91
|
+
const getSentryInstrumentationServerContent = (dsn, enableTracing, enableProfiling = false, enableLogs = false) => {
|
|
92
|
+
return generateServerInstrumentationCode(dsn, enableTracing, enableProfiling, enableLogs);
|
|
93
|
+
};
|
|
94
|
+
exports.getSentryInstrumentationServerContent = getSentryInstrumentationServerContent;
|
|
95
|
+
const getManualClientEntryContent = (dsn, enableTracing, enableReplay, enableLogs) => {
|
|
96
|
+
const integrations = [];
|
|
97
|
+
if (enableTracing) {
|
|
98
|
+
integrations.push('Sentry.reactRouterTracingIntegration()');
|
|
99
|
+
}
|
|
100
|
+
if (enableReplay) {
|
|
101
|
+
integrations.push('Sentry.replayIntegration()');
|
|
102
|
+
}
|
|
103
|
+
const integrationsStr = integrations.length > 0 ? integrations.join(',\n ') : '';
|
|
104
|
+
return (0, clack_1.makeCodeSnippet)(true, (unchanged, plus) => unchanged(`${plus("import * as Sentry from '@sentry/react-router';")}
|
|
105
|
+
import { startTransition, StrictMode } from 'react';
|
|
106
|
+
import { hydrateRoot } from 'react-dom/client';
|
|
107
|
+
import { HydratedRouter } from 'react-router/dom';
|
|
108
|
+
|
|
109
|
+
${plus(`Sentry.init({
|
|
110
|
+
dsn: "${dsn}",
|
|
111
|
+
|
|
112
|
+
// Adds request headers and IP for users, for more info visit:
|
|
113
|
+
// https://docs.sentry.io/platforms/javascript/guides/react-router/configuration/options/#sendDefaultPii
|
|
114
|
+
sendDefaultPii: true,
|
|
115
|
+
|
|
116
|
+
integrations: [
|
|
117
|
+
${integrationsStr}
|
|
118
|
+
],
|
|
119
|
+
|
|
120
|
+
${enableLogs
|
|
121
|
+
? '// Enable logs to be sent to Sentry\n enableLogs: true,\n\n '
|
|
122
|
+
: ''}tracesSampleRate: ${enableTracing ? '1.0' : '0'},${enableTracing ? ' // Capture 100% of the transactions' : ''}${enableTracing
|
|
123
|
+
? '\n\n // Set `tracePropagationTargets` to declare which URL(s) should have trace propagation enabled\n // In production, replace "yourserver.io" with your actual backend domain\n tracePropagationTargets: [/^\\//, /^https:\\/\\/yourserver\\.io\\/api/],'
|
|
124
|
+
: ''}${enableReplay
|
|
125
|
+
? '\n\n // Capture Replay for 10% of all sessions,\n // plus 100% of sessions with an error\n replaysSessionSampleRate: 0.1,\n replaysOnErrorSampleRate: 1.0,'
|
|
126
|
+
: ''}
|
|
127
|
+
});`)}
|
|
128
|
+
|
|
129
|
+
startTransition(() => {
|
|
130
|
+
hydrateRoot(
|
|
131
|
+
document,
|
|
132
|
+
<StrictMode>
|
|
133
|
+
<HydratedRouter />
|
|
134
|
+
</StrictMode>
|
|
135
|
+
);
|
|
136
|
+
});`));
|
|
137
|
+
};
|
|
138
|
+
exports.getManualClientEntryContent = getManualClientEntryContent;
|
|
139
|
+
const getManualServerEntryContent = () => {
|
|
140
|
+
return (0, clack_1.makeCodeSnippet)(true, (unchanged, plus) => unchanged(`${plus("import * as Sentry from '@sentry/react-router';")}
|
|
141
|
+
import { createReadableStreamFromReadable } from '@react-router/node';
|
|
142
|
+
import { renderToPipeableStream } from 'react-dom/server';
|
|
143
|
+
import { ServerRouter } from 'react-router';
|
|
144
|
+
|
|
145
|
+
${plus(`const handleRequest = Sentry.createSentryHandleRequest({
|
|
146
|
+
ServerRouter,
|
|
147
|
+
renderToPipeableStream,
|
|
148
|
+
createReadableStreamFromReadable,
|
|
149
|
+
});`)}
|
|
150
|
+
|
|
151
|
+
export default handleRequest;
|
|
152
|
+
|
|
153
|
+
${plus(`export const handleError = Sentry.createSentryHandleError({
|
|
154
|
+
logErrors: false
|
|
155
|
+
});`)}
|
|
156
|
+
|
|
157
|
+
// ... rest of your server entry`));
|
|
158
|
+
};
|
|
159
|
+
exports.getManualServerEntryContent = getManualServerEntryContent;
|
|
160
|
+
const getManualHandleRequestContent = () => {
|
|
161
|
+
return (0, clack_1.makeCodeSnippet)(true, (unchanged, plus) => unchanged(`${plus("import * as Sentry from '@sentry/react-router';")}
|
|
162
|
+
import { createReadableStreamFromReadable } from '@react-router/node';
|
|
163
|
+
import { renderToPipeableStream } from 'react-dom/server';
|
|
164
|
+
import { ServerRouter } from 'react-router';
|
|
165
|
+
|
|
166
|
+
${plus(`// Replace your existing handleRequest function with this Sentry-wrapped version:
|
|
167
|
+
const handleRequest = Sentry.createSentryHandleRequest({
|
|
168
|
+
ServerRouter,
|
|
169
|
+
renderToPipeableStream,
|
|
170
|
+
createReadableStreamFromReadable,
|
|
171
|
+
});`)}
|
|
172
|
+
|
|
173
|
+
${plus(`// If you have a custom handleRequest implementation, wrap it like this:
|
|
174
|
+
// export default Sentry.wrapSentryHandleRequest(yourCustomHandleRequest);`)}
|
|
175
|
+
|
|
176
|
+
export default handleRequest;`));
|
|
177
|
+
};
|
|
178
|
+
exports.getManualHandleRequestContent = getManualHandleRequestContent;
|
|
179
|
+
const getManualRootContent = (isTs) => {
|
|
180
|
+
const typeAnnotations = isTs
|
|
181
|
+
? { stack: ': string | undefined', props: ': Route.ErrorBoundaryProps' }
|
|
182
|
+
: { stack: '', props: '' };
|
|
183
|
+
return (0, clack_1.makeCodeSnippet)(true, (unchanged, plus) => unchanged(`${plus("import * as Sentry from '@sentry/react-router';")}
|
|
184
|
+
|
|
185
|
+
export function ErrorBoundary({ error }${typeAnnotations.props}) {
|
|
186
|
+
let message = "Oops!";
|
|
187
|
+
let details = "An unexpected error occurred.";
|
|
188
|
+
let stack${typeAnnotations.stack};
|
|
189
|
+
|
|
190
|
+
if (isRouteErrorResponse(error)) {
|
|
191
|
+
message = error.status === 404 ? "404" : "Error";
|
|
192
|
+
details =
|
|
193
|
+
error.status === 404
|
|
194
|
+
? "The requested page could not be found."
|
|
195
|
+
: error.statusText || details;
|
|
196
|
+
} else if (error && error instanceof Error) {
|
|
197
|
+
// you only want to capture non 404-errors that reach the boundary
|
|
198
|
+
${plus('Sentry.captureException(error);')}
|
|
199
|
+
details = error.message;
|
|
200
|
+
stack = error.stack;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return (
|
|
204
|
+
<main>
|
|
205
|
+
<h1>{message}</h1>
|
|
206
|
+
<p>{details}</p>
|
|
207
|
+
{stack && (
|
|
208
|
+
<pre>
|
|
209
|
+
<code>{stack}</code>
|
|
210
|
+
</pre>
|
|
211
|
+
)}
|
|
212
|
+
</main>
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
// ...`));
|
|
216
|
+
};
|
|
217
|
+
exports.getManualRootContent = getManualRootContent;
|
|
218
|
+
const getManualServerInstrumentContent = (dsn, enableTracing, enableProfiling, enableLogs = false) => {
|
|
219
|
+
return (0, clack_1.makeCodeSnippet)(true, (unchanged, plus) => plus(generateServerInstrumentationCode(dsn, enableTracing, enableProfiling, enableLogs)));
|
|
220
|
+
};
|
|
221
|
+
exports.getManualServerInstrumentContent = getManualServerInstrumentContent;
|
|
222
|
+
const getManualReactRouterConfigContent = (isTS = true) => {
|
|
223
|
+
return (0, clack_1.makeCodeSnippet)(true, (unchanged, plus) => isTS
|
|
224
|
+
? unchanged(`${plus('import type { Config } from "@react-router/dev/config";')}
|
|
225
|
+
${plus("import { sentryOnBuildEnd } from '@sentry/react-router';")}
|
|
226
|
+
|
|
227
|
+
export default {
|
|
228
|
+
${plus('ssr: true,')}
|
|
229
|
+
${plus(`buildEnd: async ({ viteConfig, reactRouterConfig, buildManifest }) => {
|
|
230
|
+
await sentryOnBuildEnd({ viteConfig, reactRouterConfig, buildManifest });
|
|
231
|
+
},`)}
|
|
232
|
+
} satisfies Config;
|
|
233
|
+
|
|
234
|
+
// If you already have a buildEnd hook, modify it to call sentryOnBuildEnd:
|
|
235
|
+
// buildEnd: async (args) => {
|
|
236
|
+
// await yourExistingLogic(args);
|
|
237
|
+
// await sentryOnBuildEnd(args);
|
|
238
|
+
// }`)
|
|
239
|
+
: unchanged(`${plus("import { sentryOnBuildEnd } from '@sentry/react-router';")}
|
|
240
|
+
|
|
241
|
+
export default {
|
|
242
|
+
${plus('ssr: true,')}
|
|
243
|
+
${plus(`buildEnd: async ({ viteConfig, reactRouterConfig, buildManifest }) => {
|
|
244
|
+
await sentryOnBuildEnd({ viteConfig, reactRouterConfig, buildManifest });
|
|
245
|
+
},`)}
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
// If you already have a buildEnd hook, modify it to call sentryOnBuildEnd:
|
|
249
|
+
// buildEnd: async (args) => {
|
|
250
|
+
// await yourExistingLogic(args);
|
|
251
|
+
// await sentryOnBuildEnd(args);
|
|
252
|
+
// }`));
|
|
253
|
+
};
|
|
254
|
+
exports.getManualReactRouterConfigContent = getManualReactRouterConfigContent;
|
|
255
|
+
const getManualViteConfigContent = (orgSlug, projectSlug) => {
|
|
256
|
+
return (0, clack_1.makeCodeSnippet)(true, (unchanged, plus) => unchanged(`${plus("import { sentryReactRouter } from '@sentry/react-router';")}
|
|
257
|
+
import { defineConfig } from 'vite';
|
|
258
|
+
|
|
259
|
+
export default defineConfig(config => {
|
|
260
|
+
return {
|
|
261
|
+
plugins: [
|
|
262
|
+
// ... your existing plugins
|
|
263
|
+
${plus(`sentryReactRouter({
|
|
264
|
+
org: "${orgSlug}",
|
|
265
|
+
project: "${projectSlug}",
|
|
266
|
+
authToken: process.env.SENTRY_AUTH_TOKEN,
|
|
267
|
+
}, config),`)}
|
|
268
|
+
],
|
|
269
|
+
};
|
|
270
|
+
});`));
|
|
271
|
+
};
|
|
272
|
+
exports.getManualViteConfigContent = getManualViteConfigContent;
|
|
273
|
+
//# sourceMappingURL=templates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"templates.js","sourceRoot":"","sources":["../../../src/react-router/templates.ts"],"names":[],"mappings":"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","sourcesContent":["import { makeCodeSnippet } from '../utils/clack';\n\nfunction generateErrorBoundaryTemplate(\n isTypeScript: boolean,\n forManualInstructions = false,\n): string {\n const typeAnnotations = isTypeScript\n ? { stack: ': string | undefined', props: ': Route.ErrorBoundaryProps' }\n : { stack: '', props: '' };\n\n const commentLine = forManualInstructions\n ? '// you only want to capture non 404-errors that reach the boundary\\n '\n : '// Only capture non-404 errors (all errors here are already non-RouteErrorResponse)\\n ';\n\n return `function ErrorBoundary({ error }${typeAnnotations.props}) {\n let message = \"Oops!\";\n let details = \"An unexpected error occurred.\";\n let stack${typeAnnotations.stack};\n\n if (isRouteErrorResponse(error)) {\n message = error.status === 404 ? \"404\" : \"Error\";\n details =\n error.status === 404\n ? \"The requested page could not be found.\"\n : error.statusText || details;\n } else if (error && error instanceof Error) {\n ${commentLine}Sentry.captureException(error);\n details = error.message;\n stack = error.stack;\n }\n\n return (\n <main>\n <h1>{message}</h1>\n <p>{details}</p>\n {stack && (\n <pre>\n <code>{stack}</code>\n </pre>\n )}\n </main>\n );\n}`;\n}\n\nexport const ERROR_BOUNDARY_TEMPLATE = generateErrorBoundaryTemplate(false);\n\nexport const EXAMPLE_PAGE_TEMPLATE_TSX = `import type { Route } from \"./+types/sentry-example-page\";\n\nexport async function loader() {\n throw new Error(\"some error thrown in a loader\");\n}\n\nexport default function SentryExamplePage() {\n return <div>Loading this page will throw an error</div>;\n}`;\n\nexport const EXAMPLE_PAGE_TEMPLATE_JSX = `export async function loader() {\n throw new Error(\"some error thrown in a loader\");\n}\n\nexport default function SentryExamplePage() {\n return <div>Loading this page will throw an error</div>;\n}`;\n\nfunction generateServerInstrumentationCode(\n dsn: string,\n enableTracing: boolean,\n enableProfiling: boolean,\n enableLogs: boolean,\n): string {\n return `import * as Sentry from '@sentry/react-router';${\n enableProfiling\n ? `\\nimport { nodeProfilingIntegration } from '@sentry/profiling-node';`\n : ''\n }\n\nSentry.init({\n dsn: \"${dsn}\",\n\n // Adds request headers and IP for users, for more info visit:\n // https://docs.sentry.io/platforms/javascript/guides/react-router/configuration/options/#sendDefaultPii\n sendDefaultPii: true,${\n enableLogs\n ? '\\n\\n // Enable logs to be sent to Sentry\\n enableLogs: true,'\n : ''\n }${enableProfiling ? '\\n\\n integrations: [nodeProfilingIntegration()],' : ''}\n tracesSampleRate: ${enableTracing ? '1.0' : '0'}, ${\n enableTracing ? '// Capture 100% of the transactions' : ''\n }${\n enableProfiling\n ? '\\n profilesSampleRate: 1.0, // profile every transaction'\n : ''\n }${\n enableTracing\n ? `\n\n // Set up performance monitoring\n beforeSend(event) {\n // Filter out 404s from error reporting\n if (event.exception) {\n const error = event.exception.values?.[0];\n if (error?.type === \"NotFoundException\" || error?.value?.includes(\"404\")) {\n return null;\n }\n }\n return event;\n },`\n : ''\n }\n});`;\n}\n\nexport const getSentryInstrumentationServerContent = (\n dsn: string,\n enableTracing: boolean,\n enableProfiling = false,\n enableLogs = false,\n) => {\n return generateServerInstrumentationCode(\n dsn,\n enableTracing,\n enableProfiling,\n enableLogs,\n );\n};\n\nexport const getManualClientEntryContent = (\n dsn: string,\n enableTracing: boolean,\n enableReplay: boolean,\n enableLogs: boolean,\n) => {\n const integrations = [];\n\n if (enableTracing) {\n integrations.push('Sentry.reactRouterTracingIntegration()');\n }\n\n if (enableReplay) {\n integrations.push('Sentry.replayIntegration()');\n }\n\n const integrationsStr =\n integrations.length > 0 ? integrations.join(',\\n ') : '';\n\n return makeCodeSnippet(true, (unchanged, plus) =>\n unchanged(`${plus(\"import * as Sentry from '@sentry/react-router';\")}\nimport { startTransition, StrictMode } from 'react';\nimport { hydrateRoot } from 'react-dom/client';\nimport { HydratedRouter } from 'react-router/dom';\n\n${plus(`Sentry.init({\n dsn: \"${dsn}\",\n\n // Adds request headers and IP for users, for more info visit:\n // https://docs.sentry.io/platforms/javascript/guides/react-router/configuration/options/#sendDefaultPii\n sendDefaultPii: true,\n\n integrations: [\n ${integrationsStr}\n ],\n\n ${\n enableLogs\n ? '// Enable logs to be sent to Sentry\\n enableLogs: true,\\n\\n '\n : ''\n }tracesSampleRate: ${enableTracing ? '1.0' : '0'},${\n enableTracing ? ' // Capture 100% of the transactions' : ''\n}${\n enableTracing\n ? '\\n\\n // Set `tracePropagationTargets` to declare which URL(s) should have trace propagation enabled\\n // In production, replace \"yourserver.io\" with your actual backend domain\\n tracePropagationTargets: [/^\\\\//, /^https:\\\\/\\\\/yourserver\\\\.io\\\\/api/],'\n : ''\n}${\n enableReplay\n ? '\\n\\n // Capture Replay for 10% of all sessions,\\n // plus 100% of sessions with an error\\n replaysSessionSampleRate: 0.1,\\n replaysOnErrorSampleRate: 1.0,'\n : ''\n}\n});`)}\n\nstartTransition(() => {\n hydrateRoot(\n document,\n <StrictMode>\n <HydratedRouter />\n </StrictMode>\n );\n});`),\n );\n};\n\nexport const getManualServerEntryContent = () => {\n return makeCodeSnippet(true, (unchanged, plus) =>\n unchanged(`${plus(\"import * as Sentry from '@sentry/react-router';\")}\nimport { createReadableStreamFromReadable } from '@react-router/node';\nimport { renderToPipeableStream } from 'react-dom/server';\nimport { ServerRouter } from 'react-router';\n\n${plus(`const handleRequest = Sentry.createSentryHandleRequest({\n ServerRouter,\n renderToPipeableStream,\n createReadableStreamFromReadable,\n});`)}\n\nexport default handleRequest;\n\n${plus(`export const handleError = Sentry.createSentryHandleError({\n logErrors: false\n});`)}\n\n// ... rest of your server entry`),\n );\n};\n\nexport const getManualHandleRequestContent = () => {\n return makeCodeSnippet(true, (unchanged, plus) =>\n unchanged(`${plus(\"import * as Sentry from '@sentry/react-router';\")}\nimport { createReadableStreamFromReadable } from '@react-router/node';\nimport { renderToPipeableStream } from 'react-dom/server';\nimport { ServerRouter } from 'react-router';\n\n${plus(`// Replace your existing handleRequest function with this Sentry-wrapped version:\nconst handleRequest = Sentry.createSentryHandleRequest({\n ServerRouter,\n renderToPipeableStream,\n createReadableStreamFromReadable,\n});`)}\n\n${plus(`// If you have a custom handleRequest implementation, wrap it like this:\n// export default Sentry.wrapSentryHandleRequest(yourCustomHandleRequest);`)}\n\nexport default handleRequest;`),\n );\n};\n\nexport const getManualRootContent = (isTs: boolean) => {\n const typeAnnotations = isTs\n ? { stack: ': string | undefined', props: ': Route.ErrorBoundaryProps' }\n : { stack: '', props: '' };\n\n return makeCodeSnippet(true, (unchanged, plus) =>\n unchanged(`${plus(\"import * as Sentry from '@sentry/react-router';\")}\n\nexport function ErrorBoundary({ error }${typeAnnotations.props}) {\n let message = \"Oops!\";\n let details = \"An unexpected error occurred.\";\n let stack${typeAnnotations.stack};\n\n if (isRouteErrorResponse(error)) {\n message = error.status === 404 ? \"404\" : \"Error\";\n details =\n error.status === 404\n ? \"The requested page could not be found.\"\n : error.statusText || details;\n } else if (error && error instanceof Error) {\n // you only want to capture non 404-errors that reach the boundary\n ${plus('Sentry.captureException(error);')}\n details = error.message;\n stack = error.stack;\n }\n\n return (\n <main>\n <h1>{message}</h1>\n <p>{details}</p>\n {stack && (\n <pre>\n <code>{stack}</code>\n </pre>\n )}\n </main>\n );\n}\n// ...`),\n );\n};\n\nexport const getManualServerInstrumentContent = (\n dsn: string,\n enableTracing: boolean,\n enableProfiling: boolean,\n enableLogs = false,\n) => {\n return makeCodeSnippet(true, (unchanged, plus) =>\n plus(\n generateServerInstrumentationCode(\n dsn,\n enableTracing,\n enableProfiling,\n enableLogs,\n ),\n ),\n );\n};\n\nexport const getManualReactRouterConfigContent = (isTS = true) => {\n return makeCodeSnippet(true, (unchanged, plus) =>\n isTS\n ? unchanged(`${plus(\n 'import type { Config } from \"@react-router/dev/config\";',\n )}\n${plus(\"import { sentryOnBuildEnd } from '@sentry/react-router';\")}\n\nexport default {\n ${plus('ssr: true,')}\n ${plus(`buildEnd: async ({ viteConfig, reactRouterConfig, buildManifest }) => {\n await sentryOnBuildEnd({ viteConfig, reactRouterConfig, buildManifest });\n },`)}\n} satisfies Config;\n\n// If you already have a buildEnd hook, modify it to call sentryOnBuildEnd:\n// buildEnd: async (args) => {\n// await yourExistingLogic(args);\n// await sentryOnBuildEnd(args);\n// }`)\n : unchanged(`${plus(\n \"import { sentryOnBuildEnd } from '@sentry/react-router';\",\n )}\n\nexport default {\n ${plus('ssr: true,')}\n ${plus(`buildEnd: async ({ viteConfig, reactRouterConfig, buildManifest }) => {\n await sentryOnBuildEnd({ viteConfig, reactRouterConfig, buildManifest });\n },`)}\n};\n\n// If you already have a buildEnd hook, modify it to call sentryOnBuildEnd:\n// buildEnd: async (args) => {\n// await yourExistingLogic(args);\n// await sentryOnBuildEnd(args);\n// }`),\n );\n};\n\nexport const getManualViteConfigContent = (\n orgSlug: string,\n projectSlug: string,\n) => {\n return makeCodeSnippet(true, (unchanged, plus) =>\n unchanged(`${plus(\n \"import { sentryReactRouter } from '@sentry/react-router';\",\n )}\nimport { defineConfig } from 'vite';\n\nexport default defineConfig(config => {\n return {\n plugins: [\n // ... your existing plugins\n ${plus(`sentryReactRouter({\n org: \"${orgSlug}\",\n project: \"${projectSlug}\",\n authToken: process.env.SENTRY_AUTH_TOKEN,\n }, config),`)}\n ],\n };\n});`),\n );\n};\n"]}
|