@sentry/wizard
Version:
Sentry wizard helping you to configure your project
178 lines (176 loc) • 7.36 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.instrumentReactRouterConfig = exports.hasReactRouterSentryContent = exports.addSentryBuildEndToReactRouterConfig = void 0;
const recast = __importStar(require("recast"));
const path = __importStar(require("path"));
const fs = __importStar(require("fs"));
// @ts-expect-error - magicast is ESM and TS complains about that. It works though
const magicast_1 = require("magicast");
// @ts-expect-error - clack is ESM and TS complains about that. It works though
const prompts_1 = __importDefault(require("@clack/prompts"));
const chalk_1 = __importDefault(require("chalk"));
const ast_utils_1 = require("../../utils/ast-utils");
/**
* Extracts the ObjectExpression from various export patterns.
* Supports: direct object, `satisfies Config`, and `as Config` patterns.
*/
function extractConfigObject(declaration) {
if (declaration.type === 'ObjectExpression') {
return declaration;
}
if (declaration.type === 'TSSatisfiesExpression' ||
declaration.type === 'TSAsExpression') {
const expr = declaration
.expression;
return expr.type === 'ObjectExpression' ? expr : undefined;
}
return undefined;
}
/**
* Creates an identifier property for object literals.
*/
function createIdentifierProperty(name) {
const b = recast.types.builders;
return b.property('init', b.identifier(name), b.identifier(name));
}
function addSentryBuildEndToReactRouterConfig(program) {
const b = recast.types.builders;
let ssrWasChanged = false;
const defaultExport = program.body.find((node) => node.type === 'ExportDefaultDeclaration');
if (!defaultExport) {
return { success: false, ssrWasChanged: false };
}
const configObj = extractConfigObject(defaultExport.declaration);
if (!configObj) {
return { success: false, ssrWasChanged: false };
}
const buildEndProp = (0, ast_utils_1.findProperty)(configObj, 'buildEnd');
if (buildEndProp) {
throw new Error('A buildEnd hook already exists in your React Router config.');
}
const ssrProp = (0, ast_utils_1.findProperty)(configObj, 'ssr');
if (!ssrProp) {
const ssrProperty = b.objectProperty(b.identifier('ssr'), b.booleanLiteral(true));
ssrProperty.comments = [
{
type: 'CommentLine',
value: ' SSR is required for Sentry sourcemap uploads to work correctly',
},
];
configObj.properties.unshift(ssrProperty);
ssrWasChanged = true;
}
else if (ssrProp.value.type === 'BooleanLiteral' ||
ssrProp.value.type === 'Literal') {
const wasExplicitlyFalse = ssrProp.value.value === false;
if (wasExplicitlyFalse) {
ssrWasChanged = true;
}
ssrProp.value = b.booleanLiteral(true);
if (wasExplicitlyFalse) {
ssrProp.comments = [
{
type: 'CommentLine',
value: ' Changed to true - SSR is required for Sentry sourcemap uploads',
},
];
}
}
const paramNames = ['viteConfig', 'reactRouterConfig', 'buildManifest'];
const buildEndFunction = b.arrowFunctionExpression([b.objectPattern(paramNames.map(createIdentifierProperty))], b.blockStatement([
b.expressionStatement(b.awaitExpression(b.callExpression(b.identifier('sentryOnBuildEnd'), [
b.objectExpression(paramNames.map(createIdentifierProperty)),
]))),
]));
buildEndFunction.async = true;
configObj.properties.push(b.objectProperty(b.identifier('buildEnd'), buildEndFunction));
return { success: true, ssrWasChanged };
}
exports.addSentryBuildEndToReactRouterConfig = addSentryBuildEndToReactRouterConfig;
function hasReactRouterSentryContent(program) {
let hasSentry = false;
recast.visit(program, {
visitIdentifier(path) {
if (path.node.name === 'sentryOnBuildEnd') {
hasSentry = true;
return false; // stop traversal
}
this.traverse(path);
},
});
return hasSentry;
}
exports.hasReactRouterSentryContent = hasReactRouterSentryContent;
async function instrumentReactRouterConfig(isTS) {
const configFilename = `react-router.config.${isTS ? 'ts' : 'js'}`;
const configPath = path.join(process.cwd(), configFilename);
if (!fs.existsSync(configPath)) {
const defaultConfig = isTS
? `import type { Config } from "@react-router/dev/config";
import { sentryOnBuildEnd } from "@sentry/react-router";
export default {
ssr: true,
buildEnd: async ({ viteConfig, reactRouterConfig, buildManifest }) => {
await sentryOnBuildEnd({ viteConfig, reactRouterConfig, buildManifest });
},
} satisfies Config;
`
: `import { sentryOnBuildEnd } from "@sentry/react-router";
export default {
ssr: true,
buildEnd: async ({ viteConfig, reactRouterConfig, buildManifest }) => {
await sentryOnBuildEnd({ viteConfig, reactRouterConfig, buildManifest });
},
};
`;
await fs.promises.writeFile(configPath, defaultConfig);
return { ssrWasChanged: false };
}
const configContent = await fs.promises.readFile(configPath, 'utf-8');
const filename = chalk_1.default.cyan(configFilename);
const mod = (0, magicast_1.parseModule)(configContent);
if (hasReactRouterSentryContent(mod.$ast)) {
prompts_1.default.log.info(`${filename} already contains sentryOnBuildEnd.`);
return { ssrWasChanged: false };
}
mod.imports.$add({
from: '@sentry/react-router',
imported: 'sentryOnBuildEnd',
local: 'sentryOnBuildEnd',
});
const { success, ssrWasChanged } = addSentryBuildEndToReactRouterConfig(mod.$ast);
if (!success) {
throw new Error('Failed to modify React Router config structure');
}
const code = (0, magicast_1.generateCode)(mod.$ast).code;
await fs.promises.writeFile(configPath, code);
return { ssrWasChanged };
}
exports.instrumentReactRouterConfig = instrumentReactRouterConfig;
//# sourceMappingURL=react-router-config.js.map