UNPKG

@sentry/wizard

Version:

Sentry wizard helping you to configure your project

288 lines 11.1 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; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.findProperty = exports.safeInsertBeforeReturn = exports.safeGetFunctionBody = exports.safeGetIdentifierName = exports.safeCalleeIdentifierMatch = exports.getLastRequireIndex = exports.printJsonC = exports.parseJsonC = exports.setOrUpdateObjectProperty = exports.getOrSetObjectProperty = exports.getObjectProperty = exports.hasSentryContent = exports.findFile = void 0; const fs = __importStar(require("fs")); const recast = __importStar(require("recast")); const b = recast.types.builders; /** * Checks if a file where we don't know its concrete file type yet exists * and returns the full path to the file with the correct file type. */ function findFile(filePath, fileTypes = ['.js', '.ts', '.mjs', '.cjs']) { return fileTypes .map((type) => `${filePath}${type}`) .find((file) => fs.existsSync(file)); } exports.findFile = findFile; /** * checks for require('@sentry/*') syntax */ function hasSentryContent(program) { let foundSentry = false; recast.visit(program, { visitStringLiteral(path) { foundSentry = foundSentry || path.node.value.startsWith('@sentry/'); this.traverse(path); }, visitLiteral(path) { foundSentry = foundSentry || path.node.value?.toString().startsWith('@sentry/'); this.traverse(path); }, }); return !!foundSentry; } exports.hasSentryContent = hasSentryContent; /** * Searches for a property of an ObjectExpression by name * * @param object the ObjectExpression to search in * @param name the name of the property to search for * * @returns the property if it exists */ function getObjectProperty(object, name) { return object.properties.find((p) => { const isObjectProp = p.type === 'Property' || p.type === 'ObjectProperty'; if (!isObjectProp) { return false; } const hasMatchingLiteralKey = isObjectProp && (p.key.type === 'Literal' || p.key.type === 'StringLiteral') && p.key.value === name; if (hasMatchingLiteralKey) { return true; } // has matching identifier key return isObjectProp && p.key.type === 'Identifier' && p.key.name === name; }); } exports.getObjectProperty = getObjectProperty; /** * Attempts to find a property of an ObjectExpression by name. If it doesn't exist, * the property will be added to the ObjectExpression with the provided default value. * * @param object the parent object expression to search in * @param name the name of the property to search for * @param defaultValue the default value to set if the property doesn't exist * * @returns the */ function getOrSetObjectProperty(object, name, defaultValue) { const existingProperty = getObjectProperty(object, name); if (existingProperty) { return existingProperty; } const newProperty = b.property.from({ kind: 'init', key: b.stringLiteral(name), value: defaultValue, }); object.properties.push(newProperty); return newProperty; } exports.getOrSetObjectProperty = getOrSetObjectProperty; /** * Sets a property of an ObjectExpression if it exists, otherwise adds it * to the ObjectExpression. Optionally, a comment can be added to the * property. * * @param object the ObjectExpression to set the property on * @param name the name of the property to set * @param value the value of the property to set * @param comment (optional) a comment to add to the property */ function setOrUpdateObjectProperty(object, name, value, comment) { const newComments = comment && comment.split('\n').map((c) => b.commentLine(` ${c}`, true, false)); const existingProperty = getObjectProperty(object, name); if (existingProperty) { existingProperty.value = value; if (newComments) { existingProperty.comments = [ ...(existingProperty?.comments || []), ...newComments, ]; } } else { object.properties.push(b.objectProperty.from({ key: b.stringLiteral(name), value, ...(newComments && { comments: newComments, }), })); } } exports.setOrUpdateObjectProperty = setOrUpdateObjectProperty; /** * Parses a JSON string with (potential) comments (JSON-C) and returns the JS AST * that can be walked and modified with recast like a normal JS AST. * * This is done by wrapping the JSON-C string in parentheses, thereby making it * a JS `Program` with an `ExpressionStatement` as its body. The expression is then * extracted from the AST and returned alongside the AST. * * To preserve as much original formatting as possible, the returned `ast` * property should be passed to {@link `printJsonC`} to get the JSON-C string back. * * If the input is not valid JSON-C, the result will be undefined. * * @see {@link JsonCParseResult} * * @param jsonString a JSON-C string * * @returns a {@link JsonCParseResult}, containing either the JSON-C object and the AST or undefined in both cases */ function parseJsonC(jsonString) { try { const jsTsConfig = `(${jsonString})`; // no idea why recast returns any here, this is dumb :/ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access const ast = recast.parse(jsTsConfig.toString()).program; const jsonObject = (ast.body[0].type === 'ExpressionStatement' && ast.body[0].expression.type === 'ObjectExpression' && ast.body[0].expression) || undefined; if (jsonObject) { return { jsonObject, ast }; } } catch { /* empty */ } return { jsonObject: undefined, ast: undefined }; } exports.parseJsonC = parseJsonC; /** * Takes the AST of a parsed JSON-C "program" and returns the JSON-C string without * any of the temporary JS wrapper code that was previously applied. * * Only use this in conjunction with {@link `parseJsonC`} * * @param ast the `ast` returned from {@link `parseJsonC`} * * @returns the JSON-C string */ function printJsonC(ast) { const js = recast.print(ast).code; return js.substring(1, js.length - 1); } exports.printJsonC = printJsonC; /** * Walks the program body and returns index of the last variable assignment initialized by require statement. * Only counts top level require statements. * * @returns index of the last `const foo = require('bar');` statement or -1 if none was found. */ function getLastRequireIndex(program) { let lastRequireIdex = -1; program.body.forEach((s, i) => { if (s.type === 'VariableDeclaration' && s.declarations[0].type === 'VariableDeclarator' && s.declarations[0].init !== null && typeof s.declarations[0].init !== 'undefined' && s.declarations[0].init.type === 'CallExpression' && s.declarations[0].init.callee.type === 'Identifier' && s.declarations[0].init.callee.name === 'require') { lastRequireIdex = i; } }); return lastRequireIdex; } exports.getLastRequireIndex = getLastRequireIndex; /** * Safely checks if a callee is an identifier with the given name * Prevents crashes when accessing callee.name on non-identifier nodes */ // eslint-disable-next-line @typescript-eslint/no-explicit-any function safeCalleeIdentifierMatch(callee, name) { return Boolean(callee && typeof callee === 'object' && 'type' in callee && callee.type === 'Identifier' && 'name' in callee && callee.name === name); } exports.safeCalleeIdentifierMatch = safeCalleeIdentifierMatch; /** * Safely gets the name of an identifier node * Returns null if the node is not an identifier or is undefined */ // eslint-disable-next-line @typescript-eslint/no-explicit-any function safeGetIdentifierName(node) { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access return node && node.type === 'Identifier' ? String(node.name) : null; } exports.safeGetIdentifierName = safeGetIdentifierName; /** * Safely access function body array with proper validation * Prevents crashes when accessing body.body on nodes that don't have a body */ // eslint-disable-next-line @typescript-eslint/no-explicit-any function safeGetFunctionBody(node) { if (!node || typeof node !== 'object' || !('body' in node)) { return null; } const nodeBody = node.body; if (!nodeBody || typeof nodeBody !== 'object' || !('body' in nodeBody)) { return null; } const bodyArray = nodeBody.body; return Array.isArray(bodyArray) ? bodyArray : null; } exports.safeGetFunctionBody = safeGetFunctionBody; /** * Safely insert statement before last statement in function body * Typically used to insert code before a return statement * Returns true if insertion was successful, false otherwise */ function safeInsertBeforeReturn(body, statement) { if (!body || !Array.isArray(body) || body.length === 0) { return false; } // Insert before the last statement (typically a return statement) const insertIndex = Math.max(0, body.length - 1); body.splice(insertIndex, 0, statement); return true; } exports.safeInsertBeforeReturn = safeInsertBeforeReturn; /** * Finds a property in an ObjectExpression by name. * Commonly used for traversing Vite/React Router config objects. * * @param configObj - The ObjectExpression to search * @param name - The property name to find * @returns The matching ObjectProperty, or undefined if not found */ function findProperty(configObj, name) { return configObj.properties.find((p) => p.type === 'ObjectProperty' && p.key.type === 'Identifier' && p.key.name === name); // Safe: predicate guarantees type } exports.findProperty = findProperty; //# sourceMappingURL=ast-utils.js.map