@sentry/wizard
Version:
Sentry wizard helping you to configure your project
387 lines • 13.6 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.checkIfRunsOnProdMode = exports.checkIfRunsOnDevMode = exports.checkIfFlutterBuilds = exports.checkIfBuilds = exports.checkSentryProperties = exports.checkEnvBuildPlugin = exports.checkSentryCliRc = exports.checkPackageJson = exports.checkFileExists = exports.checkFileContents = exports.modifyFile = exports.createFile = exports.startWizardInstance = exports.revertLocalChanges = exports.cleanupGit = exports.initGit = exports.WizardTestEnv = exports.log = exports.TEST_ARGS = exports.KEYS = void 0;
const fs = __importStar(require("node:fs"));
const path = __importStar(require("node:path"));
const node_child_process_1 = require("node:child_process");
const Logging_1 = require("../../lib/Helper/Logging");
exports.KEYS = {
UP: '\u001b[A',
DOWN: '\u001b[B',
LEFT: '\u001b[D',
RIGHT: '\u001b[C',
ENTER: '\r',
SPACE: ' ',
};
exports.TEST_ARGS = {
AUTH_TOKEN: process.env.SENTRY_TEST_AUTH_TOKEN || 'TEST_AUTH_TOKEN',
PROJECT_DSN: process.env.SENTRY_TEST_DSN || 'https://public@dsn.ingest.sentry.io/1337',
ORG_SLUG: process.env.SENTRY_TEST_ORG || 'TEST_ORG_SLUG',
PROJECT_SLUG: process.env.SENTRY_TEST_PROJECT || 'TEST_PROJECT_SLUG',
};
exports.log = {
success: (message) => {
(0, Logging_1.green)(`[SUCCESS] ${message}`);
},
info: (message) => {
(0, Logging_1.dim)(`[INFO] ${message}`);
},
error: (message) => {
(0, Logging_1.red)(`[ERROR] ${message}`);
},
};
class WizardTestEnv {
taskHandle;
constructor(cmd, args, opts) {
this.taskHandle = (0, node_child_process_1.spawn)(cmd, args, { cwd: opts?.cwd, stdio: 'pipe' });
if (opts?.debug) {
this.taskHandle.stdout?.pipe(process.stdout);
this.taskHandle.stderr?.pipe(process.stderr);
}
}
sendStdin(input) {
this.taskHandle.stdin?.write(input);
}
/**
* Sends the input and waits for the output.
* @returns a promise that resolves when the output was found
* @throws an error when the output was not found within the timeout
*/
sendStdinAndWaitForOutput(input, output, options) {
const outputPromise = this.waitForOutput(output, options);
if (Array.isArray(input)) {
for (const i of input) {
this.sendStdin(i);
}
}
else {
this.sendStdin(input);
}
return outputPromise;
}
/**
* Waits for the task to exit with a given `statusCode`.
*
* @returns a promise that resolves to `true` if the run ends with the status
* code, or it rejects when the `timeout` was reached.
*/
waitForStatusCode(statusCode, options = {}) {
const { timeout } = {
timeout: 60000,
...options,
};
return new Promise((resolve, reject) => {
const timeoutId = setTimeout(() => {
this.kill();
reject(new Error(`Timeout waiting for status code: ${statusCode}`));
}, timeout);
this.taskHandle.on('error', (err) => {
clearTimeout(timeoutId);
reject(err);
});
this.taskHandle.on('exit', (code) => {
clearTimeout(timeoutId);
resolve(code === statusCode);
});
});
}
/**
* Waits for the provided output with `.includes()` logic.
*
* @returns a promise that resolves to `true` if the output was found, `false` if the output was not found within the
* timeout and `optional: true` is set, or it rejects when the timeout was reached with `optional: false`
*/
waitForOutput(output, options = {}) {
const { timeout, optional } = {
timeout: 60000,
optional: false,
...options,
};
return new Promise((resolve, reject) => {
let outputBuffer = '';
const timeoutId = setTimeout(() => {
this.kill();
if (optional) {
// The output is not found but it's optional so we can resolve the promise with false
resolve(false);
}
else {
reject(new Error(`Timeout waiting for output: ${output}. Got the following instead: ${outputBuffer}`));
}
}, timeout);
this.taskHandle.on('error', (err) => {
clearTimeout(timeoutId);
reject(err);
});
this.taskHandle.stdout?.on('data', (data) => {
outputBuffer += data;
if (outputBuffer.includes(output)) {
clearTimeout(timeoutId);
// The output is found so we can resolve the promise with true
resolve(true);
}
});
});
}
kill() {
this.taskHandle.stdin?.destroy();
this.taskHandle.stderr?.destroy();
this.taskHandle.stdout?.destroy();
this.taskHandle.kill('SIGINT');
this.taskHandle.unref();
}
}
exports.WizardTestEnv = WizardTestEnv;
/**
* Initialize a git repository in the given directory
* @param projectDir
*/
function initGit(projectDir) {
try {
(0, node_child_process_1.execSync)('git init', { cwd: projectDir });
// Add all files to the git repo
(0, node_child_process_1.execSync)('git add -A', { cwd: projectDir });
// Add author info to avoid git commit error
(0, node_child_process_1.execSync)('git config user.email test@test.sentry.io', { cwd: projectDir });
(0, node_child_process_1.execSync)('git config user.name Test', { cwd: projectDir });
(0, node_child_process_1.execSync)('git commit -m init', { cwd: projectDir });
}
catch (e) {
exports.log.error('Error initializing git');
exports.log.error(e);
}
}
exports.initGit = initGit;
/**
* Cleanup the git repository in the given directory
*
* Caution! Make sure `projectDir` is a test project directory,
* if in doubt, please commit your local non-test changes first!
* @param projectDir
*/
function cleanupGit(projectDir) {
try {
// Remove the .git directory
(0, node_child_process_1.execSync)(`rm -rf ${projectDir}/.git`);
}
catch (e) {
exports.log.error('Error cleaning up git');
exports.log.error(e);
}
}
exports.cleanupGit = cleanupGit;
/**
* Revert local changes in the given directory
*
* Caution! Make sure `projectDir` is a test project directory,
* if in doubt, please commit your local non-test changes first!
*
* @param projectDir
*/
function revertLocalChanges(projectDir) {
try {
// Revert tracked files
(0, node_child_process_1.execSync)('git checkout .', { cwd: projectDir });
// Revert untracked files
(0, node_child_process_1.execSync)('git clean -fd .', { cwd: projectDir });
}
catch (e) {
exports.log.error('Error reverting local changes');
exports.log.error(e);
}
}
exports.revertLocalChanges = revertLocalChanges;
/**
* Start the wizard instance with the given integration and project directory
* @param integration
* @param projectDir
*
* @returns WizardTestEnv
*/
function startWizardInstance(integration, projectDir, debug = false) {
const binName = process.env.SENTRY_WIZARD_E2E_TEST_BIN
? ['dist-bin', `sentry-wizard-${process.platform}-${process.arch}`]
: ['dist', 'bin.js'];
const binPath = path.join(__dirname, '..', '..', ...binName);
revertLocalChanges(projectDir);
cleanupGit(projectDir);
initGit(projectDir);
return new WizardTestEnv(binPath, [
'--debug',
'-i',
integration,
'--preSelectedProject.authToken',
exports.TEST_ARGS.AUTH_TOKEN,
'--preSelectedProject.dsn',
exports.TEST_ARGS.PROJECT_DSN,
'--preSelectedProject.orgSlug',
exports.TEST_ARGS.ORG_SLUG,
'--preSelectedProject.projectSlug',
exports.TEST_ARGS.PROJECT_SLUG,
], { cwd: projectDir, debug });
}
exports.startWizardInstance = startWizardInstance;
/**
* Create a file with the given content
*
* @param filePath
* @param content
*/
function createFile(filePath, content) {
return fs.writeFileSync(filePath, content || '');
}
exports.createFile = createFile;
/**
* Modify the file with the new content
*
* @param filePath
* @param oldContent
* @param newContent
*/
function modifyFile(filePath, replaceMap) {
const fileContent = fs.readFileSync(filePath, 'utf-8');
let newFileContent = fileContent;
for (const [oldContent, newContent] of Object.entries(replaceMap)) {
newFileContent = newFileContent.replace(oldContent, newContent);
}
fs.writeFileSync(filePath, newFileContent);
}
exports.modifyFile = modifyFile;
/**
* Read the file contents and check if it contains the given content
*
* @param {string} filePath
* @param {(string | string[])} content
*/
function checkFileContents(filePath, content) {
const fileContent = fs.readFileSync(filePath, 'utf-8');
const contentArray = Array.isArray(content) ? content : [content];
for (const c of contentArray) {
expect(fileContent).toContain(c);
}
}
exports.checkFileContents = checkFileContents;
/**
* Check if the file exists
*
* @param filePath
*/
function checkFileExists(filePath) {
expect(fs.existsSync(filePath)).toBe(true);
}
exports.checkFileExists = checkFileExists;
/**
* Check if the package.json contains the given integration
*
* @param projectDir
* @param integration
*/
function checkPackageJson(projectDir, integration) {
checkFileContents(`${projectDir}/package.json`, `@sentry/${integration}`);
}
exports.checkPackageJson = checkPackageJson;
/**
* Check if the .sentryclirc contains the auth token
*
* @param projectDir
*/
function checkSentryCliRc(projectDir) {
checkFileContents(`${projectDir}/.sentryclirc`, `token=${exports.TEST_ARGS.AUTH_TOKEN}`);
}
exports.checkSentryCliRc = checkSentryCliRc;
/**
* Check if the .env.sentry-build-plugin contains the auth token
* @param projectDir
*/
function checkEnvBuildPlugin(projectDir) {
checkFileContents(`${projectDir}/.env.sentry-build-plugin`, `SENTRY_AUTH_TOKEN=${exports.TEST_ARGS.AUTH_TOKEN}`);
}
exports.checkEnvBuildPlugin = checkEnvBuildPlugin;
/**
* Check if the sentry.properties contains the auth token
* @param projectDir
*/
function checkSentryProperties(projectDir) {
checkFileContents(`${projectDir}/sentry.properties`, `auth_token=${exports.TEST_ARGS.AUTH_TOKEN}`);
}
exports.checkSentryProperties = checkSentryProperties;
/**
* Check if the project builds
* Check if the project builds and ends with status code 0.
* @param projectDir
*/
async function checkIfBuilds(projectDir) {
const testEnv = new WizardTestEnv('npm', ['run', 'build'], {
cwd: projectDir,
});
await expect(testEnv.waitForStatusCode(0, {
timeout: 120000,
})).resolves.toBe(true);
}
exports.checkIfBuilds = checkIfBuilds;
/**
* Check if the flutter project builds
* @param projectDir
*/
async function checkIfFlutterBuilds(projectDir, expectedOutput, debug = false) {
const testEnv = new WizardTestEnv('flutter', ['build', 'web'], {
cwd: projectDir,
debug: debug,
});
await expect(testEnv.waitForOutput(expectedOutput, {
timeout: 120000,
})).resolves.toBe(true);
}
exports.checkIfFlutterBuilds = checkIfFlutterBuilds;
/**
* Check if the project runs on dev mode
* @param projectDir
* @param expectedOutput
*/
async function checkIfRunsOnDevMode(projectDir, expectedOutput) {
const testEnv = new WizardTestEnv('npm', ['run', 'dev'], { cwd: projectDir });
await expect(testEnv.waitForOutput(expectedOutput, {
timeout: 120000,
})).resolves.toBe(true);
testEnv.kill();
}
exports.checkIfRunsOnDevMode = checkIfRunsOnDevMode;
/**
* Check if the project runs on prod mode
* @param projectDir
* @param expectedOutput
*/
async function checkIfRunsOnProdMode(projectDir, expectedOutput, startCommand = 'start') {
const testEnv = new WizardTestEnv('npm', ['run', startCommand], {
cwd: projectDir,
});
await expect(testEnv.waitForOutput(expectedOutput, {
timeout: 120000,
})).resolves.toBe(true);
testEnv.kill();
}
exports.checkIfRunsOnProdMode = checkIfRunsOnProdMode;
//# sourceMappingURL=index.js.map