Skip to content

Commit

Permalink
feat: Add error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
HazAT committed Nov 8, 2017
1 parent c4e2059 commit 8a8ea89
Show file tree
Hide file tree
Showing 13 changed files with 144 additions and 109 deletions.
9 changes: 8 additions & 1 deletion index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { ProjectType } from './lib/steps';
import { run } from './lib/Setup';
const argv = require('yargs').boolean('debug').argv;
const argv = require('yargs')
.boolean('debug')
.option('projectType', {
alias: 'pt',
describe: 'Choose a project type',
choices: Object.keys(ProjectType)
}).argv;

run(argv);
30 changes: 21 additions & 9 deletions lib/Helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ export function green(msg: string) {
return l(Chalk.green(prepareMessage(msg)));
}

export function red(msg: string) {
return l(Chalk.red(prepareMessage(msg)));
}

export function dim(msg: string) {
return l(Chalk.dim(prepareMessage(msg)));
}
Expand Down Expand Up @@ -53,20 +57,28 @@ export class BottomBar {
function sanitizeArgs(argv: any) {
let baseUrl = argv.sentryUrl || 'https://sentry.io/';
baseUrl += baseUrl.endsWith('/') ? '' : '/';
console.log(baseUrl);
argv.sentryUrl = baseUrl;
}

export function startWizard<M extends Step>(
export async function startWizard<M extends Step>(
argv: any,
...steps: { new (debug: boolean): M }[]
): Promise<Answers> {
) {
sanitizeArgs(argv);
if (argv.debug) console.log(argv);
if (argv.debug) debug(argv);

try {
await steps.map(step => new step(argv)).reduce(async (answer, step) => {
let prevAnswer = await answer;

return steps.map(step => new step(argv)).reduce(async (answer, step) => {
let prevAnswer = await answer;
let answers = await step.emit(prevAnswer);
return Promise.resolve(Object.assign({}, prevAnswer, answers));
}, Promise.resolve({}));
let answers = await step.emit(prevAnswer);
return Object.assign({}, prevAnswer, answers);
}, Promise.resolve({}));
} catch (e) {
BottomBar.hide();
nl();
red('Sentry Setup Wizard failed with:');
nl();
red(e);
}
}
2 changes: 1 addition & 1 deletion lib/Setup.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as Step from './steps';
import { startWizard, green } from './Helper';
import { startWizard, green, red } from './Helper';

export function run(argv: any) {
startWizard(
Expand Down
6 changes: 5 additions & 1 deletion lib/steps/DetectProjectType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ export enum ProjectType {
}

export class DetectProjectType extends BaseStep {
emit(answers: Answers) {
async emit(answers: Answers) {
// If we receive project type as an arg we skip asking
if (this.argv.projectType) {
return { projectType: this.argv.projectType };
}
let projectType = this.tryDetectingProjectType();
return prompt([
{
Expand Down
4 changes: 2 additions & 2 deletions lib/steps/Initial.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ try {
}

export class Initial extends BaseStep {
emit(answers: Answers) {
async emit(answers: Answers) {
dim('Running Sentry Setup Wizard...');
// TODO: get sentry cli version
let sentryCliVersion = 'TODO';
dim(`version: ${wizardPackage.version} | sentry-cli version: ${sentryCliVersion}`);
return Promise.resolve({});
return {};
}
}
24 changes: 14 additions & 10 deletions lib/steps/OpenSentry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,23 @@ export class OpenSentry extends BaseStep {
BottomBar.show('Loading wizard...');
this.debug(`Loading wizard for ${baseUrl}`);

let data = JSON.parse(await request.get(`${baseUrl}api/0/wizard`));
try {
let data = JSON.parse(await request.get(`${baseUrl}api/0/wizard`));

BottomBar.hide();
BottomBar.hide();

let urlToOpen = `${baseUrl}account/settings/wizard/${data.hash}/`;
let urlToOpen = `${baseUrl}account/settings/wizard/${data.hash}/`;

open(urlToOpen);
nl();
l('Please open');
green(urlToOpen);
l("in your browser (if it's not open already)");
nl();
open(urlToOpen);
nl();
l('Please open');
green(urlToOpen);
l("in your browser (if it's not open already)");
nl();

return Promise.resolve({ hash: data.hash });
return { hash: data.hash };
} catch (e) {
throw `Could not connect to wizard @ ${baseUrl}`;
}
}
}
4 changes: 2 additions & 2 deletions lib/steps/Result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { BaseStep } from './Step';
import { green } from '../Helper';

export class Result extends BaseStep {
emit(answers: Answers) {
async emit(answers: Answers) {
this.debug(JSON.stringify(answers, null, '\t'));
green('🎉 Successfully setup Sentry for your project 🎉');
return Promise.resolve({});
return {};
}
}
6 changes: 3 additions & 3 deletions lib/steps/SentryProjectSelector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ import { BaseStep } from './Step';
import { dim } from '../Helper';

export class SentryProjectSelector extends BaseStep {
emit(answers: Answers) {
async emit(answers: Answers) {
this.debug(answers);

if (_.has(answers, 'wizard.projects') && answers.wizard.projects.length === 0) {
return Promise.reject('no projects');
throw 'no projects';
}

if (answers.wizard.projects.length === 1) {
return Promise.resolve({ selectedProject: answers.wizard.projects[0] });
return { selectedProject: answers.wizard.projects[0] };
}

return prompt([
Expand Down
2 changes: 1 addition & 1 deletion lib/steps/WaitForSentry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { l, green, dim, nl, BottomBar } from '../Helper';
import * as request from 'request-promise';

export class WaitForSentry extends BaseStep {
emit(answers: Answers) {
async emit(answers: Answers) {
return new Promise(async (resolve, reject) => {
this.debug(answers);

Expand Down
6 changes: 3 additions & 3 deletions lib/steps/Welcome.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import { l, green, dim, nl } from '../Helper';

export class Welcome extends BaseStep {
private static didShow = false;
emit(answers: Answers) {
if (Welcome.didShow) return Promise.resolve({});
async emit(answers: Answers) {
if (Welcome.didShow) return {};
green('Sentry Setup Wizard will help to configure your project');
dim('Thank you for using Sentry :)');
Welcome.didShow = true;
return Promise.resolve({});
return {};
}
}
20 changes: 20 additions & 0 deletions lib/steps/configure/FileHelper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const glob = require('glob');
const fs = require('fs');

export function patchMatchingFile(pattern: string, func: any) {
let matches = glob.sync(pattern, {
ignore: ['node_modules/**', 'ios/Pods/**', '**/Pods/**']
});
let rv = Promise.resolve();
matches.forEach((match: string) => {
let contents = fs.readFileSync(match, {
encoding: 'utf-8'
});
rv = rv.then(() => func(contents, match)).then(newContents => {
if (newContents !== null && contents !== undefined && contents != newContents) {
fs.writeFileSync(match, newContents);
}
});
});
return rv;
}
104 changes: 28 additions & 76 deletions lib/steps/configure/ReactNative.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { prompt, Question, Answers } from 'inquirer';
import { BaseStep } from '../Step';
import { dim, green } from '../../Helper';
import { dim, green, red } from '../../Helper';
import { SentryCliHelper } from './SentryCliHelper';
import { patchMatchingFile } from './FileHelper';
import * as _ from 'lodash';
const glob = require('glob');

const xcode = require('xcode');
const fs = require('fs');
const path = require('path');

const OBJC_HEADER =
'\
#if __has_include(<React/RNSentry.h>)\n\
Expand All @@ -17,41 +19,50 @@ const OBJC_HEADER =
export class ReactNative extends BaseStep {
protected answers: Answers;
protected platforms: string[];
protected sentryCliHelper: SentryCliHelper;

constructor(protected argv: any = {}) {
super(argv);
this.sentryCliHelper = new SentryCliHelper(this.argv);
}

async emit(answers: Answers) {
let sentryCliProperties = this.sentryCliHelper.convertSelectedProjectToProperties(
answers
);

return new Promise(async (resolve, reject) => {
this.answers = answers;
let sentryCliProperties = this.convertSelectedProjectToSentryCliProperties(answers);
this.platforms = (await this.platformSelector()).platform;
let promises = this.platforms.map((platform: string) =>
this.shouldConfigurePlatform(platform)
.then(async () => {
try {
if (platform == 'ios') {
await this.patchMatchingFile(
await patchMatchingFile(
'ios/*.xcodeproj/project.pbxproj',
this.patchXcodeProj.bind(this)
);
await this.patchMatchingFile(
await patchMatchingFile(
'**/AppDelegate.m',
this.patchAppDelegate.bind(this)
);
} else {
await this.patchMatchingFile(
await patchMatchingFile(
'**/app/build.gradle',
this.patchBuildGradle.bind(this)
);
}
await this.patchMatchingFile(
await patchMatchingFile(
`index.${platform}.js`,
this.patchIndexJs.bind(this)
);
// rm 0.49 introduced an App.js for both platforms
await this.patchMatchingFile('App.js', this.patchAppJs.bind(this));
await patchMatchingFile('App.js', this.patchAppJs.bind(this));
await this.addSentryProperties(platform, sentryCliProperties);
green(`Successfully setup ${platform}`);
} catch (e) {
console.log(e);
red(e);
}
})
.catch((reason: any) => {
Expand All @@ -64,23 +75,6 @@ export class ReactNative extends BaseStep {
});
}

convertSelectedProjectToSentryCliProperties(answers: Answers) {
let sentryCliProperties: any = {};
sentryCliProperties['defaults/url'] = this.argv.sentryUrl;
sentryCliProperties['defaults/org'] = _.get(
answers,
'selectedProject.organization.slug',
null
);
sentryCliProperties['defaults/project'] = _.get(
answers,
'selectedProject.slug',
null
);
sentryCliProperties['auth/token'] = _.get(answers, 'wizard.apiKeys.0.token', null);
return sentryCliProperties;
}

shouldConfigurePlatform(platform: string) {
// if a sentry.properties file exists for the platform we want to configure
// without asking the user. This means that re-linking later will not
Expand All @@ -106,25 +100,13 @@ export class ReactNative extends BaseStep {
}
let fn = platform + '/sentry.properties';

rv = rv.then(() => fs.writeFileSync(fn, this.dumpProperties(properties)));
rv = rv.then(() =>
fs.writeFileSync(fn, this.sentryCliHelper.dumpProperties(properties))
);

return rv;
}

dumpProperties(props: any) {
let rv = [];
for (let key in props) {
let value = props[key];
key = key.replace(/\//g, '.');
if (value === undefined || value === null) {
rv.push('#' + key + '=');
} else {
rv.push(key + '=' + value);
}
}
return rv.join('\n') + '\n';
}

patchAppDelegate(contents: string) {
// add the header if it's not there yet.
if (!contents.match(/#import "RNSentry.h"/)) {
Expand Down Expand Up @@ -168,14 +150,14 @@ export class ReactNative extends BaseStep {
});

return Promise.resolve(
contents.replace(/^([^]*)(import\s+[^;]*?;$)/m, match => {
return (
contents.replace(
/^([^]*)(import\s+[^;]*?;$)/m,
match =>
match +
"\n\nimport { Sentry } from 'react-native-sentry';\n\n" +
`const sentryDsn = Platform.select(${JSON.stringify(config)});\n` +
'Sentry.config(sentryDsn).install();\n'
);
})
)
);
}

Expand Down Expand Up @@ -207,24 +189,6 @@ export class ReactNative extends BaseStep {
);
}

patchMatchingFile(pattern: string, func: any) {
let matches = glob.sync(pattern, {
ignore: ['node_modules/**', 'ios/Pods/**', '**/Pods/**']
});
let rv = Promise.resolve();
matches.forEach((match: string) => {
let contents = fs.readFileSync(match, {
encoding: 'utf-8'
});
rv = rv.then(() => func(contents, match)).then(newContents => {
if (newContents !== null && contents !== undefined && contents != newContents) {
fs.writeFileSync(match, newContents);
}
});
});
return rv;
}

patchBuildGradle(contents: string) {
let applyFrom = 'apply from: "../../node_modules/react-native-sentry/sentry.gradle"';
if (contents.indexOf(applyFrom) >= 0) {
Expand Down Expand Up @@ -287,18 +251,6 @@ export class ReactNative extends BaseStep {
});
}

resolveSentryCliBinaryPath(props: any) {
return new Promise((resolve, reject) => {
try {
const cliPath = require.resolve('sentry-cli-binary/bin/sentry-cli');
props['cli/executable'] = path.relative(process.cwd(), cliPath);
} catch (e) {
// we do nothing and leave everyting as it is
}
resolve(props);
});
}

patchXcodeProj(contents: string, filename: string) {
let proj = xcode.project(filename);
return new Promise((resolve, reject) => {
Expand Down
Loading

0 comments on commit 8a8ea89

Please sign in to comment.