UNPKG

@sentry/wizard

Version:

Sentry wizard helping you to configure your project

911 lines (910 loc) 61.5 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 __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.askShouldCreateExamplePage = exports.createNewConfigFile = exports.makeCodeSnippet = exports.showCopyPasteInstructions = exports.askForToolConfigPath = exports.getOrAskForProjectData = exports.isUsingTypeScript = exports.getPackageDotJson = exports.ensurePackageIsInstalled = exports.addDotEnvSentryBuildPluginFile = exports.addSentryCliConfig = exports.installPackage = exports.confirmContinueIfPackageVersionNotSupported = exports.askForItemSelection = exports.askToInstallSentryCLI = exports.confirmContinueIfNoOrDirtyGitRepo = exports.printWelcome = exports.abortIfCancelled = exports.abort = exports.propertiesCliSetupConfig = exports.rcCliSetupConfig = exports.SENTRY_PROPERTIES_FILE = exports.SENTRY_CLI_RC_FILE = exports.SENTRY_DOT_ENV_FILE = void 0; // @ts-ignore - clack is ESM and TS complains about that. It works though var clack = __importStar(require("@clack/prompts")); var axios_1 = __importDefault(require("axios")); var chalk_1 = __importDefault(require("chalk")); var childProcess = __importStar(require("child_process")); var fs = __importStar(require("fs")); var path = __importStar(require("path")); var os = __importStar(require("os")); var timers_1 = require("timers"); var url_1 = require("url"); var Sentry = __importStar(require("@sentry/node")); var package_json_1 = require("./package-json"); var telemetry_1 = require("../telemetry"); var package_manager_1 = require("./package-manager"); var debug_1 = require("./debug"); var semver_1 = require("./semver"); var opn = require('opn'); exports.SENTRY_DOT_ENV_FILE = '.env.sentry-build-plugin'; exports.SENTRY_CLI_RC_FILE = '.sentryclirc'; exports.SENTRY_PROPERTIES_FILE = 'sentry.properties'; var SAAS_URL = 'https://sentry.io/'; var DUMMY_AUTH_TOKEN = '_YOUR_SENTRY_AUTH_TOKEN_'; exports.rcCliSetupConfig = { filename: exports.SENTRY_CLI_RC_FILE, name: 'source maps', gitignore: true, likelyAlreadyHasAuthToken: function (contents) { return !!(contents.includes('[auth]') && contents.match(/token=./g)); }, tokenContent: function (authToken) { return "[auth]\ntoken=".concat(authToken); }, likelyAlreadyHasOrgAndProject: function (contents) { return !!(contents.includes('[defaults]') && contents.match(/org=./g) && contents.match(/project=./g)); }, orgAndProjContent: function (org, project) { return "[defaults]\norg=".concat(org, "\nproject=").concat(project); }, }; exports.propertiesCliSetupConfig = { filename: exports.SENTRY_PROPERTIES_FILE, gitignore: true, name: 'debug files', likelyAlreadyHasAuthToken: function (contents) { return !!contents.match(/auth\.token=./g); }, tokenContent: function (authToken) { return "auth.token=".concat(authToken); }, likelyAlreadyHasOrgAndProject: function (contents) { return !!(contents.match(/defaults\.org=./g) && contents.match(/defaults\.project=./g)); }, orgAndProjContent: function (org, project) { return "defaults.org=".concat(org, "\ndefaults.project=").concat(project); }, likelyAlreadyHasUrl: function (contents) { return !!contents.match(/defaults\.url=./g); }, urlContent: function (url) { return "defaults.url=".concat(url); }, }; function abort(message, status) { return __awaiter(this, void 0, void 0, function () { var sentryHub, sentryTransaction, sentrySession; return __generator(this, function (_a) { switch (_a.label) { case 0: clack.outro(message !== null && message !== void 0 ? message : 'Wizard setup cancelled.'); sentryHub = Sentry.getCurrentHub(); sentryTransaction = sentryHub.getScope().getTransaction(); sentryTransaction === null || sentryTransaction === void 0 ? void 0 : sentryTransaction.setStatus('aborted'); sentryTransaction === null || sentryTransaction === void 0 ? void 0 : sentryTransaction.finish(); sentrySession = sentryHub.getScope().getSession(); if (sentrySession) { sentrySession.status = status === 0 ? 'abnormal' : 'crashed'; sentryHub.captureSession(true); } return [4 /*yield*/, Sentry.flush(3000)]; case 1: _a.sent(); return [2 /*return*/, process.exit(status !== null && status !== void 0 ? status : 1)]; } }); }); } exports.abort = abort; function abortIfCancelled(input) { return __awaiter(this, void 0, void 0, function () { var _a, _b, sentryHub, sentryTransaction; return __generator(this, function (_c) { switch (_c.label) { case 0: _b = (_a = clack).isCancel; return [4 /*yield*/, input]; case 1: if (!_b.apply(_a, [_c.sent()])) return [3 /*break*/, 3]; clack.cancel('Wizard setup cancelled.'); sentryHub = Sentry.getCurrentHub(); sentryTransaction = sentryHub.getScope().getTransaction(); sentryTransaction === null || sentryTransaction === void 0 ? void 0 : sentryTransaction.setStatus('cancelled'); sentryTransaction === null || sentryTransaction === void 0 ? void 0 : sentryTransaction.finish(); sentryHub.captureSession(true); return [4 /*yield*/, Sentry.flush(3000)]; case 2: _c.sent(); process.exit(0); return [3 /*break*/, 4]; case 3: return [2 /*return*/, input]; case 4: return [2 /*return*/]; } }); }); } exports.abortIfCancelled = abortIfCancelled; function printWelcome(options) { var wizardPackage = {}; try { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment wizardPackage = require(path.join(path.dirname(require.resolve('@sentry/wizard')), '..', 'package.json')); } catch (_a) { // We don't need to have this } // eslint-disable-next-line no-console console.log(''); clack.intro(chalk_1.default.inverse(" ".concat(options.wizardName, " "))); var welcomeText = options.message || "The ".concat(options.wizardName, " will help you set up Sentry for your application.\nThank you for using Sentry :)"); if (options.promoCode) { welcomeText = "".concat(welcomeText, "\n\nUsing promo-code: ").concat(options.promoCode); } if (wizardPackage.version) { welcomeText = "".concat(welcomeText, "\n\nVersion: ").concat(wizardPackage.version); } if (options.telemetryEnabled) { welcomeText = "".concat(welcomeText, "\n\nThis wizard sends telemetry data and crash reports to Sentry. This helps us improve the Wizard.\nYou can turn this off at any time by running ").concat(chalk_1.default.cyanBright('sentry-wizard --disable-telemetry'), "."); } clack.note(welcomeText); } exports.printWelcome = printWelcome; function confirmContinueIfNoOrDirtyGitRepo() { return __awaiter(this, void 0, void 0, function () { var _this = this; return __generator(this, function (_a) { return [2 /*return*/, (0, telemetry_1.traceStep)('check-git-status', function () { return __awaiter(_this, void 0, void 0, function () { var continueWithoutGit, uncommittedOrUntrackedFiles, continueWithDirtyRepo; return __generator(this, function (_a) { switch (_a.label) { case 0: if (!!isInGitRepo()) return [3 /*break*/, 3]; return [4 /*yield*/, abortIfCancelled(clack.confirm({ message: 'You are not inside a git repository. The wizard will create and update files. Do you want to continue anyway?', }))]; case 1: continueWithoutGit = _a.sent(); Sentry.setTag('continue-without-git', continueWithoutGit); if (!!continueWithoutGit) return [3 /*break*/, 3]; return [4 /*yield*/, abort(undefined, 0)]; case 2: _a.sent(); _a.label = 3; case 3: uncommittedOrUntrackedFiles = getUncommittedOrUntrackedFiles(); if (!uncommittedOrUntrackedFiles.length) return [3 /*break*/, 6]; clack.log.warn("You have uncommitted or untracked files in your repo:\n\n".concat(uncommittedOrUntrackedFiles.join('\n'), "\n\nThe wizard will create and update files.")); return [4 /*yield*/, abortIfCancelled(clack.confirm({ message: 'Do you want to continue anyway?', }))]; case 4: continueWithDirtyRepo = _a.sent(); Sentry.setTag('continue-with-dirty-repo', continueWithDirtyRepo); if (!!continueWithDirtyRepo) return [3 /*break*/, 6]; return [4 /*yield*/, abort(undefined, 0)]; case 5: _a.sent(); _a.label = 6; case 6: return [2 /*return*/]; } }); }); })]; }); }); } exports.confirmContinueIfNoOrDirtyGitRepo = confirmContinueIfNoOrDirtyGitRepo; function isInGitRepo() { try { childProcess.execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore', }); return true; } catch (_a) { return false; } } function getUncommittedOrUntrackedFiles() { try { var gitStatus = childProcess .execSync('git status --porcelain=v1') .toString(); var files = gitStatus .split(os.EOL) .map(function (line) { return line.trim(); }) .filter(Boolean) .map(function (f) { return "- ".concat(f.split(/\s+/)[1]); }); return files; } catch (_a) { return []; } } function askToInstallSentryCLI() { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, abortIfCancelled(clack.confirm({ message: "You don't have Sentry CLI installed. Do you want to install it?", }))]; case 1: return [2 /*return*/, _a.sent()]; } }); }); } exports.askToInstallSentryCLI = askToInstallSentryCLI; function askForItemSelection(items, message) { return __awaiter(this, void 0, void 0, function () { var selection; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, abortIfCancelled(clack.select({ maxItems: 12, message: message, options: items.map(function (item, index) { return { value: { value: item, index: index }, label: item, }; }), }))]; case 1: selection = _a.sent(); return [2 /*return*/, selection]; } }); }); } exports.askForItemSelection = askForItemSelection; function confirmContinueIfPackageVersionNotSupported(_a) { var packageId = _a.packageId, packageName = _a.packageName, packageVersion = _a.packageVersion, acceptableVersions = _a.acceptableVersions, note = _a.note; return __awaiter(this, void 0, void 0, function () { var _this = this; return __generator(this, function (_b) { return [2 /*return*/, (0, telemetry_1.traceStep)("check-package-version", function () { return __awaiter(_this, void 0, void 0, function () { var isSupportedVersion, continueWithUnsupportedVersion; return __generator(this, function (_a) { switch (_a.label) { case 0: Sentry.setTag("".concat(packageName.toLowerCase(), "-version"), packageVersion); isSupportedVersion = (0, semver_1.fulfillsVersionRange)({ acceptableVersions: acceptableVersions, version: packageVersion, canBeLatest: true, }); if (isSupportedVersion) { Sentry.setTag("".concat(packageName.toLowerCase(), "-supported"), true); return [2 /*return*/]; } clack.log.warn("You have an unsupported version of ".concat(packageName, " installed:\n\n ").concat(packageId, "@").concat(packageVersion)); clack.note(note !== null && note !== void 0 ? note : "Please upgrade to ".concat(acceptableVersions, " if you wish to use the Sentry Wizard.")); return [4 /*yield*/, abortIfCancelled(clack.confirm({ message: 'Do you want to continue anyway?', }))]; case 1: continueWithUnsupportedVersion = _a.sent(); Sentry.setTag("".concat(packageName.toLowerCase(), "-continue-with-unsupported-version"), continueWithUnsupportedVersion); if (!!continueWithUnsupportedVersion) return [3 /*break*/, 3]; return [4 /*yield*/, abort(undefined, 0)]; case 2: _a.sent(); _a.label = 3; case 3: return [2 /*return*/]; } }); }); })]; }); }); } exports.confirmContinueIfPackageVersionNotSupported = confirmContinueIfPackageVersionNotSupported; /** * Installs or updates a package with the user's package manager. * * IMPORTANT: This function modifies the `package.json`! Be sure to re-read * it if you make additional modifications to it after calling this function! */ function installPackage(_a) { var packageName = _a.packageName, alreadyInstalled = _a.alreadyInstalled, _b = _a.askBeforeUpdating, askBeforeUpdating = _b === void 0 ? true : _b; return __awaiter(this, void 0, void 0, function () { var _this = this; return __generator(this, function (_c) { return [2 /*return*/, (0, telemetry_1.traceStep)('install-package', function () { return __awaiter(_this, void 0, void 0, function () { var shouldUpdatePackage, sdkInstallSpinner, packageManager, e_1; return __generator(this, function (_a) { switch (_a.label) { case 0: if (!(alreadyInstalled && askBeforeUpdating)) return [3 /*break*/, 2]; return [4 /*yield*/, abortIfCancelled(clack.confirm({ message: "The ".concat(chalk_1.default.bold.cyan(packageName), " package is already installed. Do you want to update it to the latest version?"), }))]; case 1: shouldUpdatePackage = _a.sent(); if (!shouldUpdatePackage) { return [2 /*return*/]; } _a.label = 2; case 2: sdkInstallSpinner = clack.spinner(); return [4 /*yield*/, getPackageManager()]; case 3: packageManager = _a.sent(); sdkInstallSpinner.start("".concat(alreadyInstalled ? 'Updating' : 'Installing', " ").concat(chalk_1.default.bold.cyan(packageName), " with ").concat(chalk_1.default.bold(packageManager.label), ".")); _a.label = 4; case 4: _a.trys.push([4, 6, , 8]); return [4 /*yield*/, (0, package_manager_1.installPackageWithPackageManager)(packageManager, packageName)]; case 5: _a.sent(); return [3 /*break*/, 8]; case 6: e_1 = _a.sent(); sdkInstallSpinner.stop('Installation failed.'); clack.log.error("".concat(chalk_1.default.red('Encountered the following error during installation:'), "\n\n").concat(e_1, "\n\n").concat(chalk_1.default.dim('If you think this issue is caused by the Sentry wizard, let us know here:\nhttps://github.com/getsentry/sentry-wizard/issues'))); return [4 /*yield*/, abort()]; case 7: _a.sent(); return [3 /*break*/, 8]; case 8: sdkInstallSpinner.stop("".concat(alreadyInstalled ? 'Updated' : 'Installed', " ").concat(chalk_1.default.bold.cyan(packageName), " with ").concat(chalk_1.default.bold(packageManager.label), ".")); return [2 /*return*/]; } }); }); })]; }); }); } exports.installPackage = installPackage; function addSentryCliConfig(_a, setupConfig) { var authToken = _a.authToken, org = _a.org, project = _a.project, url = _a.url; if (setupConfig === void 0) { setupConfig = exports.rcCliSetupConfig; } return __awaiter(this, void 0, void 0, function () { var _this = this; return __generator(this, function (_b) { return [2 /*return*/, (0, telemetry_1.traceStep)('add-sentry-cli-config', function () { return __awaiter(_this, void 0, void 0, function () { var configPath, configExists, configContents, _a; return __generator(this, function (_b) { switch (_b.label) { case 0: configPath = path.join(process.cwd(), setupConfig.filename); configExists = fs.existsSync(configPath); configContents = (configExists && fs.readFileSync(configPath, 'utf8')) || ''; configContents = addAuthTokenToSentryConfig(configContents, authToken, setupConfig); configContents = addOrgAndProjectToSentryConfig(configContents, org, project, setupConfig); configContents = addUrlToSentryConfig(configContents, url, setupConfig); _b.label = 1; case 1: _b.trys.push([1, 3, , 4]); return [4 /*yield*/, fs.promises.writeFile(configPath, configContents, { encoding: 'utf8', flag: 'w', })]; case 2: _b.sent(); clack.log.success("".concat(configExists ? 'Saved' : 'Created', " ").concat(chalk_1.default.cyan(setupConfig.filename), ".")); return [3 /*break*/, 4]; case 3: _a = _b.sent(); clack.log.warning("Failed to add auth token to ".concat(chalk_1.default.cyan(setupConfig.filename), ". Uploading ").concat(setupConfig.name, " during build will likely not work locally.")); return [3 /*break*/, 4]; case 4: if (!setupConfig.gitignore) return [3 /*break*/, 6]; return [4 /*yield*/, addCliConfigFileToGitIgnore(setupConfig.filename)]; case 5: _b.sent(); return [3 /*break*/, 7]; case 6: clack.log.warn(chalk_1.default.yellow('DO NOT commit auth token to your repository!')); _b.label = 7; case 7: return [2 /*return*/]; } }); }); })]; }); }); } exports.addSentryCliConfig = addSentryCliConfig; function addAuthTokenToSentryConfig(configContents, authToken, setupConfig) { if (!authToken) { return configContents; } if (setupConfig.likelyAlreadyHasAuthToken(configContents)) { clack.log.warn("".concat(chalk_1.default.cyan(setupConfig.filename), " already has auth token. Will not add one.")); return configContents; } var newContents = "".concat(configContents, "\n").concat(setupConfig.tokenContent(authToken), "\n"); clack.log.success("Added auth token to ".concat(chalk_1.default.cyan(setupConfig.filename), " for you to test uploading ").concat(setupConfig.name, " locally.")); return newContents; } function addOrgAndProjectToSentryConfig(configContents, org, project, setupConfig) { if (!org || !project) { return configContents; } if (setupConfig.likelyAlreadyHasOrgAndProject(configContents)) { clack.log.warn("".concat(chalk_1.default.cyan(setupConfig.filename), " already has org and project. Will not add them.")); return configContents; } var newContents = "".concat(configContents, "\n").concat(setupConfig.orgAndProjContent(org, project), "\n"); clack.log.success("Added default org and project to ".concat(chalk_1.default.cyan(setupConfig.filename), " for you to test uploading ").concat(setupConfig.name, " locally.")); return newContents; } function addUrlToSentryConfig(configContents, url, setupConfig) { if (!url || !setupConfig.urlContent || !setupConfig.likelyAlreadyHasUrl) { return configContents; } if (setupConfig.likelyAlreadyHasUrl(configContents)) { clack.log.warn("".concat(chalk_1.default.cyan(setupConfig.filename), " already has url. Will not add one.")); return configContents; } var newContents = "".concat(configContents, "\n").concat(setupConfig.urlContent(url), "\n"); clack.log.success("Added default url to ".concat(chalk_1.default.cyan(setupConfig.filename), " for you to test uploading ").concat(setupConfig.name, " locally.")); return newContents; } function addDotEnvSentryBuildPluginFile(authToken) { return __awaiter(this, void 0, void 0, function () { var envVarContent, dotEnvFilePath, dotEnvFileExists, dotEnvFileContent, hasAuthToken, _a, _b; return __generator(this, function (_c) { switch (_c.label) { case 0: envVarContent = "# DO NOT commit this file to your repository!\n# The SENTRY_AUTH_TOKEN variable is picked up by the Sentry Build Plugin.\n# It's used for authentication when uploading source maps.\n# You can also set this env variable in your own `.env` files and remove this file.\nSENTRY_AUTH_TOKEN=".concat(authToken, "\n"); dotEnvFilePath = path.join(process.cwd(), exports.SENTRY_DOT_ENV_FILE); dotEnvFileExists = fs.existsSync(dotEnvFilePath); if (!dotEnvFileExists) return [3 /*break*/, 5]; dotEnvFileContent = fs.readFileSync(dotEnvFilePath, 'utf8'); hasAuthToken = !!dotEnvFileContent.match(/^\s*SENTRY_AUTH_TOKEN\s*=/g); if (!hasAuthToken) return [3 /*break*/, 1]; clack.log.warn("".concat(chalk_1.default.bold(exports.SENTRY_DOT_ENV_FILE), " already has auth token. Will not add one.")); return [3 /*break*/, 4]; case 1: _c.trys.push([1, 3, , 4]); return [4 /*yield*/, fs.promises.writeFile(dotEnvFilePath, "".concat(dotEnvFileContent, "\n").concat(envVarContent), { encoding: 'utf8', flag: 'w', })]; case 2: _c.sent(); clack.log.success("Added auth token to ".concat(chalk_1.default.bold(exports.SENTRY_DOT_ENV_FILE))); return [3 /*break*/, 4]; case 3: _a = _c.sent(); clack.log.warning("Failed to add auth token to ".concat(chalk_1.default.bold(exports.SENTRY_DOT_ENV_FILE), ". Uploading source maps during build will likely not work locally.")); return [3 /*break*/, 4]; case 4: return [3 /*break*/, 8]; case 5: _c.trys.push([5, 7, , 8]); return [4 /*yield*/, fs.promises.writeFile(dotEnvFilePath, envVarContent, { encoding: 'utf8', flag: 'w', })]; case 6: _c.sent(); clack.log.success("Created ".concat(chalk_1.default.bold(exports.SENTRY_DOT_ENV_FILE), " with auth token for you to test source map uploading locally.")); return [3 /*break*/, 8]; case 7: _b = _c.sent(); clack.log.warning("Failed to create ".concat(chalk_1.default.bold(exports.SENTRY_DOT_ENV_FILE), " with auth token. Uploading source maps during build will likely not work locally.")); return [3 /*break*/, 8]; case 8: return [4 /*yield*/, addCliConfigFileToGitIgnore(exports.SENTRY_DOT_ENV_FILE)]; case 9: _c.sent(); return [2 /*return*/]; } }); }); } exports.addDotEnvSentryBuildPluginFile = addDotEnvSentryBuildPluginFile; function addCliConfigFileToGitIgnore(filename) { return __awaiter(this, void 0, void 0, function () { var _a; return __generator(this, function (_b) { switch (_b.label) { case 0: _b.trys.push([0, 2, , 3]); return [4 /*yield*/, fs.promises.appendFile(path.join(process.cwd(), '.gitignore'), "\n# Sentry Config File\n".concat(filename, "\n"), { encoding: 'utf8' })]; case 1: _b.sent(); clack.log.success("Added ".concat(chalk_1.default.cyan(filename), " to ").concat(chalk_1.default.cyan('.gitignore'), ".")); return [3 /*break*/, 3]; case 2: _a = _b.sent(); clack.log.error("Failed adding ".concat(chalk_1.default.cyan(filename), " to ").concat(chalk_1.default.cyan('.gitignore'), ". Please add it manually!")); return [3 /*break*/, 3]; case 3: return [2 /*return*/]; } }); }); } /** * Checks if @param packageId is listed as a dependency in @param packageJson. * If not, it will ask users if they want to continue without the package. * * Use this function to check if e.g. a the framework of the SDK is installed * * @param packageJson the package.json object * @param packageId the npm name of the package * @param packageName a human readable name of the package */ function ensurePackageIsInstalled(packageJson, packageId, packageName) { return __awaiter(this, void 0, void 0, function () { var _this = this; return __generator(this, function (_a) { return [2 /*return*/, (0, telemetry_1.traceStep)('ensure-package-installed', function () { return __awaiter(_this, void 0, void 0, function () { var installed, continueWithoutPackage; return __generator(this, function (_a) { switch (_a.label) { case 0: installed = (0, package_json_1.hasPackageInstalled)(packageId, packageJson); Sentry.setTag("".concat(packageName.toLowerCase(), "-installed"), installed); if (!!installed) return [3 /*break*/, 3]; Sentry.setTag("".concat(packageName.toLowerCase(), "-installed"), false); return [4 /*yield*/, abortIfCancelled(clack.confirm({ message: "".concat(packageName, " does not seem to be installed. Do you still want to continue?"), initialValue: false, }))]; case 1: continueWithoutPackage = _a.sent(); if (!!continueWithoutPackage) return [3 /*break*/, 3]; return [4 /*yield*/, abort(undefined, 0)]; case 2: _a.sent(); _a.label = 3; case 3: return [2 /*return*/]; } }); }); })]; }); }); } exports.ensurePackageIsInstalled = ensurePackageIsInstalled; function getPackageDotJson() { return __awaiter(this, void 0, void 0, function () { var packageJsonFileContents, packageJson, _a; return __generator(this, function (_b) { switch (_b.label) { case 0: return [4 /*yield*/, fs.promises .readFile(path.join(process.cwd(), 'package.json'), 'utf8') .catch(function () { clack.log.error('Could not find package.json. Make sure to run the wizard in the root of your app!'); return abort(); })]; case 1: packageJsonFileContents = _b.sent(); packageJson = undefined; _b.label = 2; case 2: _b.trys.push([2, 3, , 5]); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment packageJson = JSON.parse(packageJsonFileContents); return [3 /*break*/, 5]; case 3: _a = _b.sent(); clack.log.error("Unable to parse your ".concat(chalk_1.default.cyan('package.json'), ". Make sure it has a valid format!")); return [4 /*yield*/, abort()]; case 4: _b.sent(); return [3 /*break*/, 5]; case 5: return [2 /*return*/, packageJson || {}]; } }); }); } exports.getPackageDotJson = getPackageDotJson; function getPackageManager() { return __awaiter(this, void 0, void 0, function () { var detectedPackageManager, selectedPackageManager; return __generator(this, function (_a) { switch (_a.label) { case 0: detectedPackageManager = (0, package_manager_1.detectPackageManger)(); if (detectedPackageManager) { return [2 /*return*/, detectedPackageManager]; } return [4 /*yield*/, abortIfCancelled(clack.select({ message: 'Please select your package manager.', options: package_manager_1.packageManagers.map(function (packageManager) { return ({ value: packageManager, label: packageManager.label, }); }), }))]; case 1: selectedPackageManager = _a.sent(); Sentry.setTag('package-manager', selectedPackageManager.name); return [2 /*return*/, selectedPackageManager]; } }); }); } function isUsingTypeScript() { try { return fs.existsSync(path.join(process.cwd(), 'tsconfig.json')); } catch (_a) { return false; } } exports.isUsingTypeScript = isUsingTypeScript; /** * Checks if we already got project data from a previous wizard invocation. * If yes, this data is returned. * Otherwise, we start the login flow and ask the user to select a project. * * Use this function to get project data for the wizard. * * @param options wizard options * @param platform the platform of the wizard * @returns project data (org, project, token, url) */ function getOrAskForProjectData(options, platform) { var _a; return __awaiter(this, void 0, void 0, function () { var _b, sentryUrl, selfHosted, _c, projects, apiKeys, selectedProject, token; return __generator(this, function (_d) { switch (_d.label) { case 0: if (options.preSelectedProject) { return [2 /*return*/, { selfHosted: options.preSelectedProject.selfHosted, sentryUrl: (_a = options.url) !== null && _a !== void 0 ? _a : SAAS_URL, authToken: options.preSelectedProject.authToken, selectedProject: options.preSelectedProject.project, }]; } return [4 /*yield*/, (0, telemetry_1.traceStep)('ask-self-hosted', function () { return askForSelfHosted(options.url); })]; case 1: _b = _d.sent(), sentryUrl = _b.url, selfHosted = _b.selfHosted; return [4 /*yield*/, (0, telemetry_1.traceStep)('login', function () { return askForWizardLogin({ promoCode: options.promoCode, url: sentryUrl, platform: platform, }); })]; case 2: _c = _d.sent(), projects = _c.projects, apiKeys = _c.apiKeys; if (!(!projects || !projects.length)) return [3 /*break*/, 4]; clack.log.error('No projects found. Please create a project in Sentry and try again.'); Sentry.setTag('no-projects-found', true); return [4 /*yield*/, abort()]; case 3: _d.sent(); // This rejection won't return due to the abort call but TS doesn't know that return [2 /*return*/, Promise.reject()]; case 4: return [4 /*yield*/, (0, telemetry_1.traceStep)('select-project', function () { return askForProjectSelection(projects); })]; case 5: selectedProject = _d.sent(); token = (apiKeys !== null && apiKeys !== void 0 ? apiKeys : {}).token; if (!token) { clack.log.error("Didn't receive an auth token. This shouldn't happen :(\n\nPlease let us know if you think this is a bug in the wizard:\n".concat(chalk_1.default.cyan('https://github.com/getsentry/sentry-wizard/issues'))); clack.log.info("In the meantime, we'll add a dummy auth token (".concat(chalk_1.default.cyan("\"".concat(DUMMY_AUTH_TOKEN, "\"")), ") for you to replace later.\nCreate your auth token here:\n").concat(chalk_1.default.cyan(selfHosted ? "".concat(sentryUrl, "organizations/").concat(selectedProject.organization.slug, "/settings/auth-tokens") : "https://".concat(selectedProject.organization.slug, ".sentry.io/settings/auth-tokens")))); } return [2 /*return*/, { sentryUrl: sentryUrl, selfHosted: selfHosted, authToken: (apiKeys === null || apiKeys === void 0 ? void 0 : apiKeys.token) || DUMMY_AUTH_TOKEN, selectedProject: selectedProject, }]; } }); }); } exports.getOrAskForProjectData = getOrAskForProjectData; /** * Asks users if they are using SaaS or self-hosted Sentry and returns the validated URL. * * If users started the wizard with a --url arg, that URL is used as the default and we skip * the self-hosted question. However, the passed url is still validated and in case it's * invalid, users are asked to enter a new one until it is valid. * * @param urlFromArgs the url passed via the --url arg */ function askForSelfHosted(urlFromArgs) { return __awaiter(this, void 0, void 0, function () { var choice, validUrl, tmpUrlFromArgs, url, _a, isSelfHostedUrl; return __generator(this, function (_b) { switch (_b.label) { case 0: if (!!urlFromArgs) return [3 /*break*/, 2]; return [4 /*yield*/, abortIfCancelled(clack.select({ message: 'Are you using Sentry SaaS or self-hosted Sentry?', options: [ { value: 'saas', label: 'Sentry SaaS (sentry.io)' }, { value: 'self-hosted', label: 'Self-hosted/on-premise/single-tenant', }, ], }))]; case 1: choice = _b.sent(); if (choice === 'saas') { Sentry.setTag('url', SAAS_URL); Sentry.setTag('self-hosted', false); return [2 /*return*/, { url: SAAS_URL, selfHosted: false }]; } _b.label = 2; case 2: tmpUrlFromArgs = urlFromArgs; _b.label = 3; case 3: if (!(validUrl === undefined)) return [3 /*break*/, 6]; _a = tmpUrlFromArgs; if (_a) return [3 /*break*/, 5]; return [4 /*yield*/, abortIfCancelled(clack.text({ message: "Please enter the URL of your ".concat(urlFromArgs ? '' : 'self-hosted ', "Sentry instance."), placeholder: 'https://sentry.io/', }))]; case 4: _a = (_b.sent()); _b.label = 5; case 5: url = _a; tmpUrlFromArgs = undefined; try { validUrl = new url_1.URL(url).toString(); // We assume everywhere else that the URL ends in a slash if (!validUrl.endsWith('/')) { validUrl += '/'; } } catch (_c) { clack.log.error('Please enter a valid URL. (It should look something like "https://sentry.mydomain.com/")'); } return [3 /*break*/, 3]; case 6: isSelfHostedUrl = new url_1.URL(validUrl).host !== new url_1.URL(SAAS_URL).host; Sentry.setTag('url', validUrl); Sentry.setTag('self-hosted', isSelfHostedUrl); return [2 /*return*/, { url: validUrl, selfHosted: true }]; } }); }); } function askForWizardLogin(options) { return __awaiter(this, void 0, void 0, function () { var hasSentryAccount, wizardHash, e_2, loginUrl, urlToOpen, loginSpinner, data; return __generator(this, function (_a) { switch (_a.label) { case 0: Sentry.setTag('has-promo-code', !!options.promoCode); return [4 /*yield*/, clack.confirm({ message: 'Do you already have a Sentry account?', })]; case 1: hasSentryAccount = _a.sent(); return [4 /*yield*/, abortIfCancelled(hasSentryAccount)]; case 2: hasSentryAccount = _a.sent(); Sentry.setTag('already-has-sentry-account', hasSentryAccount); _a.label = 3; case 3: _a.trys.push([3, 5, , 10]); return [4 /*yield*/, axios_1.default.get("".concat(options.url, "api/0/wizard/"))]; case 4: wizardHash = (_a.sent()).data.hash; return [3 /*break*/, 10]; case 5: e_2 = _a.sent(); if (!(options.url !== SAAS_URL)) return [3 /*break*/, 7]; clack.log.error('Loading Wizard failed. Did you provide the right URL?'); clack.log.info(JSON.stringify(e_2, null, 2)); return [4 /*yield*/, abort(chalk_1.default.red('Please check your configuration and try again.\n\n Let us know if you think this is an issue with the wizard or Sentry: https://github.com/getsentry/sentry-wizard/issues'))]; case 6: _a.sent(); return [3 /*break*/, 9]; case 7: clack.log.error('Loading Wizard failed.'); clack.log.info(JSON.stringify(e_2, null, 2)); return [4 /*yield*/, abort(chalk_1.default.red('Please try again in a few minutes and let us know if this issue persists: https://github.com/getsentry/sentry-wizard/issues'))]; case 8: _a.sent(); _a.label = 9; case 9: return [3 /*break*/, 10]; case 10: loginUrl = new url_1.URL("".concat(options.url, "account/settings/wizard/").concat(wizardHash, "/")); if (!hasSentryAccount) { loginUrl.searchParams.set('signup', '1'); if (options.platform) { loginUrl.searchParams.set('project_platform', options.platform); } } if (options.promoCode) { loginUrl.searchParams.set('code', options.promoCode); } urlToOpen = loginUrl.toString(); clack.log.info("".concat(chalk_1.default.bold("If the browser window didn't open automatically, please open the following link to ".concat(hasSentryAccount ? 'log' : 'sign', " into Sentry:")), "\n\n").concat(chalk_1.default.cyan(urlToOpen))); opn(urlToOpen).catch(function () { // opn throws in environments that don't have a browser (e.g. remote shells) so we just noop here }); loginSpinner = clack.spinner(); loginSpinner.start('Waiting for you to log in using the link above'); return [4 /*yield*/, new Promise(function (resolve) { var pollingInterval = (0, timers_1.setInterval)(function () { axios_1.default .get("".concat(options.url, "api/0/wizard/").concat(wizardHash, "/"), { headers: { 'Accept-Encoding': 'deflate', }, }) .then(function (result) { resolve(result.data); clearTimeout(timeout); clearInterval(pollingInterval); void axios_1.default.delete("".concat(options.url, "api/0/wizard/").concat(wizardHash, "/")); }) .catch(function () { // noop - just try again }); }, 500); var timeout = setTimeout(function () { clearInterval(pollingInterval); loginSpinner.stop('Login timed out. No worries - it happens to the best of us.');