Skip to content

Commit

Permalink
Merge pull request #23 from gregsdennis/improvements
Browse files Browse the repository at this point in the history
fix missed match when multiple dependencies are present
  • Loading branch information
gregsdennis authored Oct 17, 2023
2 parents e1985f2 + 6001f2a commit 28d9577
Show file tree
Hide file tree
Showing 3,960 changed files with 348,946 additions and 503 deletions.
The diff you're trying to view is too large. We only load the first 3000 changed files.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ Also supports custom domains for use with GitHub Enterprise!
Just add the following to a `.yml` file in your `.github/workflows/` folder.

```yaml
on: [pull_request]
on:
pull_request_target:
types: [opened, edited, closed, reopened]

jobs:
check_dependencies:
Expand Down
141 changes: 141 additions & 0 deletions evaluate-dependencies.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
const core = require('@actions/core');
const github = require('@actions/github');

var customDomains = core.getInput('custom-domains')?.split(/(\s+)/) ?? [];

const keyPhrases = 'depends on|blocked by';
const issueTypes = 'issues|pull';
const domainsList = ['github.com'].concat(customDomains); // add others from parameter
const domainsString = combineDomains(domainsList);

const quickLinkRegex = new RegExp(`(${keyPhrases}) #(\\d+)`, 'gmi');
const partialLinkRegex = new RegExp(`(${keyPhrases}) ([-_\\w]+)\\/([-._a-z0-9]+)(#)(\\d+)`, 'gmi');
const partialUrlRegex = new RegExp(`(${keyPhrases}) ([-_\\w]+)\\/([-._a-z0-9]+)\\/(${issueTypes})\\/(\\d+)`, 'gmi');
const fullUrlRegex = new RegExp(`(${keyPhrases}) https?:\\/\\/(?:${domainsString})\\/([-_\\w]+)\\/([-._a-z0-9]+)\\/(${issueTypes})\\/(\\d+)`, 'gmi');
const markdownRegex = new RegExp(`(${keyPhrases}) \\[.*\\]\\(https?:\\/\\/(?:${domainsString})\\/([-_\\w]+)\\/([-._a-z0-9]+)\\/(${issueTypes})\\/(\\d+)\\)`, 'gmi');

function escapeDomainForRegex(domain) {
return domain.replace('.', '\\.');
}

function combineDomains(domains) {
return domains.map(x => escapeDomainForRegex(x)).join("|");
}

function extractFromMatch(match) {
return {
owner: match[2],
repo: match[3],
pull_number: parseInt(match[5], 10)
};
}

function getAllDependencies(body) {
var allMatches = [];

var quickLinkMatches = [...body.matchAll(quickLinkRegex)];
if (quickLinkMatches.length !== 0) {
quickLinkMatches.forEach(match => {
core.info(` Found number-referenced dependency in '${match}'`);
allMatches.push({
owner: github.context.repo.owner,
repo: github.context.repo.repo,
pull_number: parseInt(match[2], 10)
});
});
}

var extractableMatches = [...body.matchAll(partialLinkRegex)]
.concat([...body.matchAll(partialUrlRegex)])
.concat([...body.matchAll(fullUrlRegex)])
.concat([...body.matchAll(markdownRegex)]);
if (extractableMatches.length !== 0) {
extractableMatches.forEach(match => {
core.info(` Found number-referenced dependency in '${match}'`);
allMatches.push(extractFromMatch(match));
});
}

return allMatches;
}

async function evaluate() {
try {
core.info('Initializing...');
const myToken = process.env.GITHUB_TOKEN;
const octokit = github.getOctokit(myToken);

const { data: pullRequest } = await octokit.pulls.get({
owner: github.context.repo.owner,
repo: github.context.repo.repo,
pull_number: github.context.issue.number,
});

if (!pullRequest.body){
core.info('body empty')
return;
}

core.info('\nReading PR body...');
var dependencies = getAllDependencies(pullRequest.body);

core.info('\nAnalyzing lines...');
var dependencyIssues = [];
for (var d of dependencies) {
core.info(` Fetching '${JSON.stringify(d)}'`);
var isPr = true;
var response = await octokit.pulls.get(d).catch(error => core.error(error));
if (response === undefined) {
isPr = false;
d = {
owner: d.owner,
repo: d.repo,
issue_number: d.pull_number,
};
core.info(` Fetching '${JSON.stringify(d)}'`);
response = await octokit.issues.get(d).catch(error => core.error(error));
if (response === undefined) {
core.info(' Could not locate this dependency. Will need to verify manually.');
continue;
}
}
if (isPr) {
const { data: pr } = response;
if (!pr) continue;
if (!pr.merged && !pr.closed_at) {
core.info(' PR is still open.');
dependencyIssues.push(pr);
} else {
core.info(' PR has been closed.');
}
} else {
const { data: issue } = response;
if (!issue) continue;
if (!issue.closed_at) {
core.info(' Issue is still open.');
dependencyIssues.push(issue);
} else {
core.info(' Issue has been closed.');
}
}
}

if (dependencyIssues.length !== 0) {
var msg = '\nThe following issues need to be resolved before this PR can be merged:\n';
for (var pr of dependencyIssues) {
msg += `\n#${pr.number} - ${pr.title}`;
}
core.setFailed(msg);
} else {
core.info("\nAll dependencies have been resolved!")
}
} catch (error) {
core.setFailed(error.message);
throw error;
}
}

module.exports = {
evaluate: evaluate,
getAllDependencies: getAllDependencies
}
70 changes: 70 additions & 0 deletions evaluate-dependencies.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
const evaluate = require('./evaluate-dependencies');

process.env.GITHUB_REPOSITORY = 'owner/repo';

const shorthand = 'Depends on #14'
test('Shorthand', () => {
expect(evaluate.getAllDependencies(shorthand))
.toStrictEqual([{
owner: 'owner',
repo: 'repo',
pull_number: 14
}]);
});

const partialLink = 'Depends on gregsdennis/dependencies-action#5'
test('partialLink', () => {
expect(evaluate.getAllDependencies(partialLink))
.toStrictEqual([{
owner: 'gregsdennis',
repo: 'dependencies-action',
pull_number: 5
}]);
});

const shorthandAndPartialLink = `Depends on #14
Depends on gregsdennis/dependencies-action#5`
test('shorthandAndPartialLink', () => {
expect(evaluate.getAllDependencies(shorthandAndPartialLink))
.toStrictEqual([{
owner: 'owner',
repo: 'repo',
pull_number: 14
},{
owner: 'gregsdennis',
repo: 'dependencies-action',
pull_number: 5
}]);
});

const shorthandAndPartialLinkWithBlankLineAtEnd = `Depends on #14
Depends on gregsdennis/dependencies-action#5
`
test('shorthandAndPartialLinkWithBlankLineAtEnd', () => {
expect(evaluate.getAllDependencies(shorthandAndPartialLinkWithBlankLineAtEnd))
.toStrictEqual([{
owner: 'owner',
repo: 'repo',
pull_number: 14
},{
owner: 'gregsdennis',
repo: 'dependencies-action',
pull_number: 5
}]);
});

const shorthandAndPartialLinkWithBlankLineInMiddle = `Depends on #14
Depends on gregsdennis/dependencies-action#5`
test('shorthandAndPartialLinkWithBlankLineInMiddle', () => {
expect(evaluate.getAllDependencies(shorthandAndPartialLinkWithBlankLineInMiddle))
.toStrictEqual([{
owner: 'owner',
repo: 'repo',
pull_number: 14
},{
owner: 'gregsdennis',
repo: 'dependencies-action',
pull_number: 5
}]);
});
154 changes: 3 additions & 151 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,155 +1,7 @@
const core = require('@actions/core');
const github = require('@actions/github');

var customDomains = core.getInput('custom-domains')?.split(/(\s+)/) ?? [];

const keyPhrases = 'depends on|blocked by';
const issueTypes = 'issues|pull';
const domainsList = ['github.com'].concat(customDomains); // add others from parameter
const domainsString = combineDomains(domainsList);

const quickLinkRegex = new RegExp(`(${keyPhrases}) #(\\d+)`, 'gmi');
const partialLinkRegex = new RegExp(`(${keyPhrases}) ([-_\\w]+)\\/([-._a-z0-9]+)(#)(\\d+)`, 'gmi');
const partialUrlRegex = new RegExp(`(${keyPhrases}) ([-_\\w]+)\\/([-._a-z0-9]+)\\/(${issueTypes})\\/(\\d+)`, 'gmi');
const fullUrlRegex = new RegExp(`(${keyPhrases}) https?:\\/\\/(?:${domainsString})\\/([-_\\w]+)\\/([-._a-z0-9]+)\\/(${issueTypes})\\/(\\d+)`, 'gmi');
const markdownRegex = new RegExp(`(${keyPhrases}) \\[.*\\]\\(https?:\\/\\/(?:${domainsString})\\/([-_\\w]+)\\/([-._a-z0-9]+)\\/(${issueTypes})\\/(\\d+)\\)`, 'gmi');

function escapeDomainForRegex(domain) {
return domain.replace('.', '\\.');
}

function combineDomains(domains) {
return domains.map(x => escapeDomainForRegex(x)).join("|");
}

function extractFromMatch(match) {
return {
owner: match[2],
repo: match[3],
pull_number: parseInt(match[5], 10)
};
}

function getDependency(line) {
var match = quickLinkRegex.exec(line);
if (match !== null) {
core.info(` Found number-referenced dependency in '${line}'`);
return {
owner: github.context.repo.owner,
repo: github.context.repo.repo,
pull_number: parseInt(match[2], 10)
};
}

match = partialLinkRegex.exec(line);
if (match !== null) {
core.info(` Found partial-link dependency in '${line}'`);
return extractFromMatch(match);
}

match = partialUrlRegex.exec(line);
if (match !== null) {
core.info(` Found partial-url dependency in '${line}'`);
return extractFromMatch(match);
}

match = fullUrlRegex.exec(line);
if (match !== null) {
core.info(` Found full-url dependency in '${line}'`);
return extractFromMatch(match);
}

match = markdownRegex.exec(line);
if (match !== null) {
core.info(` Found markdown dependency in '${line}'`);
return extractFromMatch(match);
}

core.info(` Found no dependency in '${line}'`);
return null;
};
const evaluate = require('./evaluate-dependencies');

async function run() {
try {
core.info('Initializing...');
const myToken = process.env.GITHUB_TOKEN;
const octokit = github.getOctokit(myToken);

const { data: pullRequest } = await octokit.pulls.get({
owner: github.context.repo.owner,
repo: github.context.repo.repo,
pull_number: github.context.issue.number,
});

if (!pullRequest.body){
core.info('body empty')
return;
}

core.info('\nReading PR body...');
const lines = pullRequest.body.split(/\r\n|\r|\n/);

var dependencies = [];
lines.forEach(l => {
var dependency = getDependency(l);
if (dependency !== null)
dependencies.push(dependency);
});

core.info('\nAnalyzing lines...');
var dependencyIssues = [];
for (var d of dependencies) {
core.info(` Fetching '${JSON.stringify(d)}'`);
var isPr = true;
var response = await octokit.pulls.get(d).catch(error => core.error(error));
if (response === undefined) {
isPr = false;
d = {
owner: d.owner,
repo: d.repo,
issue_number: d.pull_number,
};
core.info(` Fetching '${JSON.stringify(d)}'`);
response = await octokit.issues.get(d).catch(error => core.error(error));
if (response === undefined) {
core.info(' Could not locate this dependency. Will need to verify manually.');
continue;
}
}
if (isPr) {
const { data: pr } = response;
if (!pr) continue;
if (!pr.merged && !pr.closed_at) {
core.info(' PR is still open.');
dependencyIssues.push(pr);
} else {
core.info(' PR has been closed.');
}
} else {
const { data: issue } = response;
if (!issue) continue;
if (!issue.closed_at) {
core.info(' Issue is still open.');
dependencyIssues.push(issue);
} else {
core.info(' Issue has been closed.');
}
}
}

if (dependencyIssues.length !== 0) {
var msg = '\nThe following issues need to be resolved before this PR can be merged:\n';
for (var pr of dependencyIssues) {
msg += `\n#${pr.number} - ${pr.title}`;
}
core.setFailed(msg);
} else {
core.info("\nAll dependencies have been resolved!")
}
} catch (error) {
core.setFailed(error.message);
throw error;
}
await evaluate.evaluate();
}

run();
run();
12 changes: 12 additions & 0 deletions node_modules/.bin/browserslist

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 28d9577

Please sign in to comment.