@sentry/wizard
Version:
Sentry wizard helping you to configure your project
911 lines (910 loc) • 61.5 kB
JavaScript
"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.');