Skip to content

Commit

Permalink
Merge 8e88fa0 into 530d3f6
Browse files Browse the repository at this point in the history
  • Loading branch information
stdavis committed Oct 20, 2020
2 parents 530d3f6 + 8e88fa0 commit e263c66
Show file tree
Hide file tree
Showing 12 changed files with 269 additions and 60 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"cSpell.words": [
"asdavis",
"coverallsapp",
"oclif",
"octokit",
Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@ Or, alternatively, install this project globally via: `npm install -g good-samar

## Development

### Local installation

`npm link`

Then you should be able to run `good-samaritan` anywhere.

### Debugging in VSCode

1. Set a breakpoint in the editor.
1. Run `Debug: Create JavaScript Debug Terminal`
1. Run `good-samaritan` in the new terminal.

### Cutting a new release

1. `npm run release`
Expand Down
20 changes: 20 additions & 0 deletions __mocks__/@octokit/rest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
class Octokit {
constructor(auth) {
this.token = auth;
this.issues = {
listForRepo: ({ owner }) => {
const issues = {
'stdavis': ['stdavis issues'],
'asdavis': ['asdavis issues']
};

return {
data: issues[owner]
};
}
};
}
}


module.exports = { Octokit };
24 changes: 24 additions & 0 deletions __mocks__/package-json.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
module.exports = (packageName) => {
const packages = {
dep1: {
repository: {
url: 'https://github.com/stdavis/a-good-module'
}
},
dep2: {
repository: {
url: 'https://github.com/asdavis/another-good-module'
}
},
devDep1: {},
devDep2: {
repository: {}
},
noRepoUrl: {
repository: {}
},
noRepo: {}
};

return packages[packageName];
};
12 changes: 12 additions & 0 deletions __mocks__/read-pkg.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module.exports = () => {
return {
dependencies: {
dep1: '^1.0.1',
dep2: '1.0.2'
},
devDependencies: {
devDep1: '^5.0.0',
devDep2: '3.0.0'
}
};
};
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@
},
"repository": "stdavis/good-samaritan",
"scripts": {
"testrun": "./bin/run",
"posttest": "eslint .",
"test": "jest --watchAll --coverage",
"release": "standard-version"
Expand Down
65 changes: 9 additions & 56 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,58 +1,7 @@
const { Command, flags } = require('@oclif/command');
const readPackage = require('read-pkg');
const { Octokit } = require('@octokit/rest');
const packageInfo = require('package-json');
const parseGitHubUrl = require('parse-github-url');
const getToken = require('./authentication');


const getIssues = async token => {
const packageJson = await readPackage();

const allDependencies = {
...packageJson.dependencies,
...packageJson.devDependencies
};

const isHelpWantedLabel = label => {
return /help.wanted/.test(label.name.toLowerCase());
};
const processIssue = issue => {
if (issue.labels.length && issue.labels.some(isHelpWantedLabel)) {
console.log(`${issue.title} (${issue.labels.map(lbl => lbl.name).join(',')})`);
console.log(issue.html_url);
}
};
const octokit = new Octokit({
auth: token
});
for (const packageName in allDependencies) {
if (Object.prototype.hasOwnProperty.call(allDependencies, packageName)) {
console.log(packageName);
const info = await packageInfo(packageName, {
version: allDependencies[packageName],
fullMetadata: true
});

if (info.repository && info.repository.url) {
const { owner, name } = parseGitHubUrl(info.repository.url);
const issues = await octokit.issues.listForRepo({
owner,
repo: name,
state: 'open',
updated: 'updated',
direction: 'desc'
});

issues.data.forEach(processIssue);
} else {
console.log('no repository url found');
}

console.log('');
}
}
};
const { getCurrentProjectDependencies } = require('./packages');
const { getIssues, processIssues } = require('./issues');


class GoodSamaritanCommand extends Command {
Expand All @@ -61,13 +10,16 @@ class GoodSamaritanCommand extends Command {

const token = await getToken(flags['reset-token']);

getIssues(token);
const dependencies = await getCurrentProjectDependencies();

const issues = await getIssues(dependencies, token);

processIssues(issues);
}
}

GoodSamaritanCommand.description = `Find open issues from open source dependencies of your project.
...
Only issues that are marked as help wanted are shown by default.
Only issues that are labeled as "help wanted" are shown by default.
`;

GoodSamaritanCommand.flags = {
Expand All @@ -79,4 +31,5 @@ GoodSamaritanCommand.flags = {
})
};


module.exports = GoodSamaritanCommand;
32 changes: 29 additions & 3 deletions src/index.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,31 @@
describe('sanity', () => {
it('works', () => {
expect(true).toBeTruthy();
const { test } = require('@oclif/test');
const getToken = require('./authentication');
const { getCurrentProjectDependencies } = require('./packages');
const { getIssues, processIssues } = require('./issues');
const cmd = require('..');


jest.mock('./authentication');
getToken.mockResolvedValue('token');
jest.mock('./packages');
getCurrentProjectDependencies.mockResolvedValue({
dep1: '^1.1.1',
dep2: '^2.2.2'
});
jest.mock('./issues');
getIssues.mockResolvedValue({});

describe('index', () => {
beforeEach(() => {
});

test
.stdout()
.do(() => cmd.run([]))
.it('calls all functions', () => {
expect(getToken).toHaveBeenCalled();
expect(getCurrentProjectDependencies).toHaveBeenCalled();
expect(getIssues).toHaveBeenCalled();
expect(processIssues).toHaveBeenCalled();
});
});
65 changes: 65 additions & 0 deletions src/issues.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
const { Octokit } = require('@octokit/rest');
const parseGitHubUrl = require('parse-github-url');
const { getRepoUrl } = require('./packages');

const getIssues = async (dependencies, token) => {
/*
dependencies: { dep: <version string>, dep2: <version string> }
*/
const octokit = new Octokit({
auth: token
});

const issues = {};
for (const packageName in dependencies) {
if (Object.prototype.hasOwnProperty.call(dependencies, packageName)) {
console.log(packageName);
const repoUrl = await getRepoUrl(packageName, dependencies[packageName]);

if (repoUrl) {
const { owner, name } = parseGitHubUrl(repoUrl);
const issueList = await octokit.issues.listForRepo({
owner,
repo: name,
state: 'open',
updated: 'updated',
direction: 'desc'
});

issues[packageName] = issueList.data;
} else {
console.log('no repository url found');
}
}
}

return issues;
};

const processIssues = (issues) => {
/*
issues: { dep: Issue[], dep2: Issue[] }
*/
const isHelpWantedLabel = label => {
return /help.wanted/.test(label.name.toLowerCase());
};

for (const packageName in issues) {
if (Object.prototype.hasOwnProperty.call(issues, packageName)) {
console.log(`${packageName}:`);

issues[packageName].forEach(issue => {
if (issue.labels.length && issue.labels.some(isHelpWantedLabel)) {
console.log(`${issue.title} (${issue.labels.map(lbl => lbl.name).join(',')})`);
console.log(issue.html_url);
}
});
}
}
};


module.exports = {
getIssues,
processIssues
};
43 changes: 43 additions & 0 deletions src/issues.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const { getIssues, processIssues } = require("./issues");

describe('issues', () => {
describe('getIssues', () => {
it('collects issues', async () => {
const dependencies = {
'dep1': '1.1.1',
'dep2': '1.2.2'
};

const result = await getIssues(dependencies, 'blah');

expect(result).toEqual({
'dep2': ['asdavis issues'],
'dep1': ['stdavis issues']
});
});
});

describe('processIssues', () => {
it('prints issues to the output', () => {
processIssues({
dep1: [{
labels: [{ name: 'help wanted' }, { name: 'hello' }],
title: 'issue title',
html_url: 'https://someurl.com'
}, {
labels: [{ name: 'hello' }],
title: 'issue title',
html_url: 'https://someurl.com'
}],
dep2: [{
labels: [{ name: 'hello' }, { name: 'help-wanted' }],
title: 'issue title two',
html_url: 'https://someurl.com'
}]
});

// runs without exception
expect(true).toBeTruthy();
});
});
});
27 changes: 27 additions & 0 deletions src/packages.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const readPackage = require('read-pkg');
const packageInfo = require('package-json');


const getCurrentProjectDependencies = async () => {
const packageJson = await readPackage();

return {
...packageJson.dependencies,
...packageJson.devDependencies
};
};

const getRepoUrl = async (packageName, version) => {
const info = await packageInfo(packageName, {
version: version,
fullMetadata: true
});

return (info.repository && info.repository.url) ? info.repository.url : null;
};


module.exports = {
getCurrentProjectDependencies,
getRepoUrl
};
27 changes: 27 additions & 0 deletions src/packages.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const { getRepoUrl, getCurrentProjectDependencies } = require('./packages');


describe('packages', () => {
describe('getRepoUrl', () => {
it('returns the url', async () => {
expect(await getRepoUrl('dep2', '1.1.1')).toBe('https://github.com/asdavis/another-good-module');
});
it('returns null if no repo or url', async () => {
expect(await getRepoUrl('noRepo', '1.1.1')).toBeNull();
expect(await getRepoUrl('noRepoUrl', '1.1.1')).toBeNull();
});
});

describe('getCurrentProjectDependencies', () => {
it('combines deps and dev deps', async () => {
const expected = {
dep1: '^1.0.1',
dep2: '1.0.2',
devDep1: '^5.0.0',
devDep2: '3.0.0'
};

expect(await getCurrentProjectDependencies()).toEqual(expected);
});
});
});

0 comments on commit e263c66

Please sign in to comment.