UNPKG

@sentry/wizard

Version:

Sentry wizard helping you to configure your project

266 lines (265 loc) 12.2 kB
"use strict"; 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.modifyWebpackConfig = exports.configureWebPackPlugin = void 0; const path = __importStar(require("node:path")); const fs = __importStar(require("node:fs")); // @ts-expect-error - clack is ESM and TS complains about that. It works though const clack = __importStar(require("@clack/prompts")); const chalk_1 = __importDefault(require("chalk")); const recast = __importStar(require("recast")); const Sentry = __importStar(require("@sentry/node")); const clack_1 = require("../../utils/clack"); const package_json_1 = require("../../utils/package-json"); const ast_utils_1 = require("../../utils/ast-utils"); const debug_1 = require("../../utils/debug"); const getCodeSnippet = (options, colors) => (0, clack_1.makeCodeSnippet)(colors, (unchanged, plus) => unchanged(`${plus('const { sentryWebpackPlugin } = require("@sentry/webpack-plugin");')} module.exports = { // ... other options ${plus('devtool: "source-map", // Source map generation must be turned on')} plugins: [ // Put the Sentry Webpack plugin after all other plugins ${plus(`sentryWebpackPlugin({ authToken: process.env.SENTRY_AUTH_TOKEN, org: "${options.orgSlug}", project: "${options.projectSlug}",${options.selfHosted ? `\n url: "${options.url}",` : ''} }),`)} ], }`)); const configureWebPackPlugin = async (options) => { await (0, clack_1.installPackage)({ packageName: '@sentry/webpack-plugin', alreadyInstalled: (0, package_json_1.hasPackageInstalled)('@sentry/webpack-plugin', await (0, clack_1.getPackageDotJson)()), }); const webpackConfigPath = (0, ast_utils_1.findFile)(path.resolve(process.cwd(), 'webpack.config')) ?? (await (0, clack_1.askForToolConfigPath)('Webpack', 'webpack.config.js')); let successfullyAdded = false; if (webpackConfigPath) { successfullyAdded = await modifyWebpackConfig(webpackConfigPath, options); } else { successfullyAdded = await (0, clack_1.createNewConfigFile)(path.join(process.cwd(), 'webpack.config.js'), getCodeSnippet(options, false), 'More information about Webpack configs: https://vitejs.dev/config/'); Sentry.setTag('created-new-config', successfullyAdded ? 'success' : 'fail'); } if (successfullyAdded) { clack.log.info(`We recommend checking the ${webpackConfigPath ? 'modified' : 'added'} file after the wizard finished to ensure it works with your build setup.`); Sentry.setTag('ast-mod', 'success'); } else { Sentry.setTag('ast-mod', 'fail'); await (0, clack_1.showCopyPasteInstructions)({ filename: path.basename(webpackConfigPath || 'webpack.config.js'), codeSnippet: getCodeSnippet(options, true), }); } await (0, clack_1.addDotEnvSentryBuildPluginFile)(options.authToken); }; exports.configureWebPackPlugin = configureWebPackPlugin; /** * Modifies a webpack config file to enable source map generation and add the Sentry webpack plugin * exported only for testing */ async function modifyWebpackConfig(webpackConfigPath, options) { try { const webpackConfig = await fs.promises.readFile(webpackConfigPath, { encoding: 'utf-8', }); const prettyConfigFilename = chalk_1.default.cyan(path.basename(webpackConfigPath)); // no idea why recast returns any here, this is dumb :/ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access const program = recast.parse(webpackConfig.toString()).program; if (!(await shouldModifyWebpackConfig(program, prettyConfigFilename))) { // Sentry tag is set in shouldModifyWebpackConfig return false; } const exportStmt = getCjsModuleExports(program); if (!exportStmt) { // We only care about CJS at the moment since it's probably the most widely used format for webpack configs. (0, debug_1.debug)(`Could not find module.exports = {...} in ${webpackConfigPath}.`); Sentry.setTag('ast-mod-fail-reason', 'config-object-not-found'); return false; } const configObject = getWebpackConfigObject(exportStmt, program); if (!configObject) { (0, debug_1.debug)(`Couldn't find config object in ${webpackConfigPath}`); Sentry.setTag('ast-mod-fail-reason', 'config-object-not-found'); return false; } const enabledSourcemaps = enableSourcemapsGeneration(configObject); if (enabledSourcemaps) { clack.log.success(`Enabled source map generation in ${prettyConfigFilename}.`); } else { clack.log.warn(`Couldn't enable source maps generation in ${prettyConfigFilename} Please follow the instructions below.`); Sentry.setTag('ast-mod-fail-reason', 'insertion-fail'); return false; } const addedPlugin = addSentryWebpackPlugin(program, configObject, options); if (addedPlugin) { clack.log.success(`Added Sentry webpack plugin to ${prettyConfigFilename}.`); } else { clack.log.warn(`Couldn't add Sentry webpack plugin to ${prettyConfigFilename}. Please follow the instructions below.`); Sentry.setTag('ast-mod-fail-reason', 'insertion-fail'); return false; } const code = recast.print(program).code; await fs.promises.writeFile(webpackConfigPath, code); return true; } catch (e) { Sentry.setTag('ast-mod-fail-reason', 'insertion-fail'); (0, debug_1.debug)(e); return false; } } exports.modifyWebpackConfig = modifyWebpackConfig; async function shouldModifyWebpackConfig(program, prettyConfigFilename) { if ((0, ast_utils_1.hasSentryContent)(program)) { const shouldContinue = await (0, clack_1.abortIfCancelled)(clack.select({ message: `Seems like ${prettyConfigFilename} already contains Sentry-related code. Should the wizard modify it anyway?`, options: [ { label: 'Yes, add the Sentry Webpack plugin', value: true, }, { label: 'No, show me instructions to manually add the plugin', value: false, }, ], initialValue: true, })); if (!shouldContinue) { Sentry.setTag('ast-mod-fail-reason', 'has-sentry-content'); return false; } } return true; } function addSentryWebpackPlugin(program, configObj, options) { const b = addSentryWebpackPluginImport(program); const sentryPluginCall = b.callExpression(b.identifier('sentryWebpackPlugin'), [ b.objectExpression([ b.objectProperty(b.identifier('authToken'), b.identifier('process.env.SENTRY_AUTH_TOKEN')), b.objectProperty(b.identifier('org'), b.stringLiteral(options.orgSlug)), b.objectProperty(b.identifier('project'), b.stringLiteral(options.projectSlug)), ...(options.selfHosted ? [ b.objectProperty(b.identifier('url'), b.stringLiteral(options.url)), ] : []), ]), ]); const pluginsProp = configObj.properties.find((p) => p.type === 'Property' && p.key.type === 'Identifier' && p.key.name === 'plugins'); if (pluginsProp) { if (pluginsProp.value.type === 'ArrayExpression') { pluginsProp.value.elements.push(sentryPluginCall); } else { pluginsProp.value = b.arrayExpression([sentryPluginCall]); } return true; } configObj.properties.push(b.objectProperty(b.identifier('plugins'), b.arrayExpression([sentryPluginCall]))); return true; } function addSentryWebpackPluginImport(program) { const b = recast.types.builders; const sentryPluginRequireStmt = b.variableDeclaration('const', [ b.variableDeclarator(b.objectPattern([ b.objectProperty.from({ key: b.identifier('sentryWebpackPlugin'), value: b.identifier('sentryWebpackPlugin'), shorthand: true, }), ]), b.callExpression(b.identifier('require'), [ b.stringLiteral('@sentry/webpack-plugin'), ])), ]); program.body.unshift(sentryPluginRequireStmt); return b; } function enableSourcemapsGeneration(configObj) { const b = recast.types.builders; const devtoolProp = configObj.properties.find((p) => p.type === 'Property' && p.key.type === 'Identifier' && p.key.name === 'devtool'); if (devtoolProp) { // devtool can have quite a lot of source maps values. // see: https://webpack.js.org/configuration/devtool/#devtool // For Sentry to work best, we should set it to "source-map" or "hidden-source-map" // Heuristic: // - all values that contain "hidden" will be set to "hidden-source-map" // - all other values will be set to "source-map" if ((devtoolProp.value.type === 'Literal' || devtoolProp.value.type === 'StringLiteral') && devtoolProp.value.value?.toString().startsWith('hidden-')) { devtoolProp.value = b.stringLiteral('hidden-source-map'); } else { devtoolProp.value = b.stringLiteral('source-map'); } return true; } configObj.properties.push(b.objectProperty(b.identifier('devtool'), b.stringLiteral('source-map'))); return true; } function getWebpackConfigObject(moduleExports, program) { const rhs = moduleExports.right; if (rhs.type === 'ObjectExpression') { return rhs; } if (rhs.type === 'Identifier') { const configId = rhs.name; const configDeclaration = program.body.find((s) => s.type === 'VariableDeclaration' && !!s.declarations.find((d) => d.type === 'VariableDeclarator' && d.id.type === 'Identifier' && d.id.name === configId)); const declarator = configDeclaration?.declarations.find((d) => d.type === 'VariableDeclarator' && d.id.type === 'Identifier' && d.id.name === configId); return declarator?.init?.type === 'ObjectExpression' ? declarator.init : undefined; } return undefined; } function getCjsModuleExports(program) { const moduleExports = program.body.find((s) => s.type === 'ExpressionStatement' && s.expression.type === 'AssignmentExpression' && s.expression.left.type === 'MemberExpression' && s.expression.left.object.type === 'Identifier' && s.expression.left.object.name === 'module' && s.expression.left.property.type === 'Identifier' && s.expression.left.property.name === 'exports'); return moduleExports?.expression; } //# sourceMappingURL=webpack.js.map