Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adapt GitHub Actions v2 #9

Merged
merged 7 commits into from
Feb 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
FROM node:alpine

LABEL "com.github.actions.name"="autolabel"
LABEL "com.github.actions.description"="Add labels to Pull Request based on matched file patterns"
LABEL "com.github.actions.icon"="flag"
LABEL "com.github.actions.color"="gray-dark"

COPY . .
RUN yarn install
RUN apk --no-cache add git
Expand Down
31 changes: 19 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,21 @@

## Installation

To configure the action simply add the following lines to your `.github/main.workflow` workflow file:

```
workflow "auto-label" {
on = "pull_request"
resolves = ["Auto label"]
}

action "Auto label" {
uses = "banyan/auto-label@master"
secrets = ["GITHUB_TOKEN"]
}
To configure the action simply add the following lines to your `.github/workflows/auto-label.yml` file:

```yaml
name: Auto Label
on: pull_request

jobs:
auto-label:
name: Auto Label
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: banyan/auto-label@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
```

And configure by creating `.github/auto-label.json` file.
Expand Down Expand Up @@ -43,6 +46,10 @@ Pattern matching is following `.gitignore` [spec](https://git-scm.com/docs/gitig
* If there's no adding / removing labels, it will only consumes 1 point of rate limit score.
* https://developer.github.com/v4/guides/resource-limitations/

## Tips

* In case if you want to debug the response quickly, just set `ACTIONS_STEP_DEBUG` as `true` on Secrets from Settings of GitHub.

### TODO

* Handle pagination of label (currently only handles 100)
Expand Down
14 changes: 14 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: 'autolabel'
description: 'Add labels to Pull Request based on matched file patterns'
branding:
icon: 'flag'
color: 'gray-dark'
inputs:
configPath:
description: 'A path for config file'
default: '.github/auto-label.json'
runs:
using: 'docker'
image: 'Dockerfile'
args:
- ${{ inputs.configPath }}
237 changes: 143 additions & 94 deletions dist/entrypoint.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,102 +5,152 @@ ___scope___.file("entrypoint.js", function(exports, require, module, __filename,

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const fs = require("fs");
const path = require("path");
const actions_toolkit_1 = require("actions-toolkit");
const query_1 = require("./query");
const tslib_1 = require("tslib");
const fs = tslib_1.__importStar(require("fs"));
const path = tslib_1.__importStar(require("path"));
const util = tslib_1.__importStar(require("util"));
const core = tslib_1.__importStar(require("@actions/core"));
const github = tslib_1.__importStar(require("@actions/github"));
const graphql_1 = require("@octokit/graphql");
const ignore_1 = tslib_1.__importDefault(require("ignore"));
const util_1 = require("./util");
const util = require("util");
const ignore_1 = require("ignore");
const query_1 = require("./query");
const exec = util.promisify(require('child_process').exec);
const configFile = '.github/auto-label.json';
const tools = new actions_toolkit_1.Toolkit({
event: ['pull_request.opened', 'pull_request.synchronize'],
});
(async () => {
if (!fs.existsSync(path.join(tools.workspace, configFile))) {
tools.exit.neutral('config file does not exist.');
}
const config = JSON.parse(tools.getFile(configFile));
let result;
async function run() {
try {
result = await query_1.getPullRequestAndLabels(tools, tools.context.issue());
}
catch (error) {
console.error('Request failed: ', error.request, error.message);
tools.exit.failure('getPullRequestAndLabels has been failed.');
}
console.log('Result: ', result);
const allLabels = result.repository.labels.edges.reduce((acc, edge) => {
acc[edge.node.name] = edge.node.id;
return acc;
}, {});
const currentLabelNames = new Set(result.repository.pullRequest.labels.edges.map((edge) => edge.node.name));
const { headRefOid, baseRefOid } = result.repository.pullRequest;
// TODO: handle stderr
const { stdout, stderr } = await exec(`git merge-base --is-ancestor ${baseRefOid} ${headRefOid} && git diff --name-only ${baseRefOid} || git diff --name-only $(git merge-base ${baseRefOid} ${headRefOid})`);
const diffFiles = stdout.trim().split('\n');
const newLabelNames = new Set(diffFiles.reduce((acc, file) => {
Object.entries(config.rules).forEach(([label, pattern]) => {
if (ignore_1.default()
.add(pattern)
.ignores(file)) {
acc.push(label);
}
const token = process.env['GITHUB_TOKEN'];
if (!token) {
core.setFailed('GITHUB_TOKEN does not exist.');
return;
}
const graphqlWithAuth = graphql_1.graphql.defaults({
headers: { authorization: `token ${token}` },
});
return acc;
}, []));
const ruledLabelNames = new Set(Object.keys(config.rules));
const labelNamesToAdd = new Set([...newLabelNames].filter(labelName => !currentLabelNames.has(labelName)));
const labelNamesToRemove = new Set([...currentLabelNames].filter((labelName) => !newLabelNames.has(labelName) && ruledLabelNames.has(labelName)));
console.log(' ---> Current status');
console.log('allLabels: ', allLabels);
console.log('currentLabelNames: ', currentLabelNames);
console.log('diffFiles: ', diffFiles);
console.log('newLabelNames: ', newLabelNames);
console.log('ruledLabelNames: ', ruledLabelNames);
console.log('labelNamesToAdd: ', labelNamesToAdd);
console.log('labelNamesToRemove: ', labelNamesToRemove);
const labelableId = result.repository.pullRequest.id;
if (labelNamesToAdd.size > 0) {
const configPath = path.join(__dirname, core.getInput('configPath'));
if (!fs.existsSync(configPath)) {
core.setFailed(`configFile does not exist in ${configPath}.`);
}
const config = JSON.parse(fs.readFileSync(configPath).toString());
util_1.logger.debug('config', config);
util_1.logger.debug('github.context.eventName', github.context.eventName);
if (github.context.eventName !== 'pull_request') {
return;
}
const payload = github.context
.payload;
util_1.logger.debug('payload.action', payload.action);
if (payload.action !== 'opened' && payload.action !== 'synchronize') {
return;
}
const owner = payload.repository.owner.login;
const repo = payload.repository.name;
const number = payload.pull_request.number;
let result;
try {
await query_1.addLabelsToLabelable(tools, {
labelIds: util_1.getLabelIds(allLabels, [...labelNamesToAdd]),
labelableId,
result = await query_1.getPullRequestAndLabels(graphqlWithAuth, {
owner,
repo,
number,
});
console.log('Added labels: ', labelNamesToAdd);
}
catch (error) {
console.error('Request failed: ', error.request, error.message);
tools.exit.failure('addLabelsToLabelable has been failed. ');
error();
core.error(`Request failed: ${JSON.stringify(error.message)}`);
core.setFailed('getPullRequestAndLabels has been failed.');
}
}
if (labelNamesToRemove.size > 0) {
try {
await query_1.removeLabelsFromLabelable(tools, {
labelIds: util_1.getLabelIds(allLabels, [
...labelNamesToRemove,
]),
labelableId,
util_1.logger.debug('result', result);
if (!result) {
return core.setFailed(`result was empty: ${result}`);
}
const allLabels = result.repository.labels.edges.reduce((acc, edge) => {
acc[edge.node.name] = edge.node.id;
return acc;
}, {});
util_1.logger.debug('allLabels', allLabels);
const currentLabelNames = new Set(result.repository.pullRequest.labels.edges.map((edge) => edge.node.name));
util_1.logger.debug('currentLabelNames', currentLabelNames);
const { headRefOid, baseRefOid } = result.repository.pullRequest;
const { stdout } = await exec(`git fetch && git merge-base --is-ancestor ${baseRefOid} ${headRefOid} && git diff --name-only ${baseRefOid} || git diff --name-only $(git merge-base ${baseRefOid} ${headRefOid})`);
const diffFiles = stdout.trim().split('\n');
const newLabelNames = new Set(diffFiles.reduce((acc, file) => {
Object.entries(config.rules).forEach(([label, pattern]) => {
if (ignore_1.default()
.add(pattern)
.ignores(file)) {
acc.push(label);
}
});
console.log('Removed labels: ', labelNamesToRemove);
return acc;
}, []));
const ruledLabelNames = new Set(Object.keys(config.rules));
const labelNamesToAdd = new Set([...newLabelNames].filter(labelName => !currentLabelNames.has(labelName)));
const labelNamesToRemove = new Set([...currentLabelNames].filter((labelName) => !newLabelNames.has(labelName) && ruledLabelNames.has(labelName)));
util_1.logger.debug('labelNamesToAdd', labelNamesToAdd);
util_1.logger.debug('labelNamesToRemove', labelNamesToRemove);
const labelableId = result.repository.pullRequest.id;
util_1.logger.debug('labelableId', labelableId);
if (labelNamesToAdd.size > 0) {
try {
await query_1.addLabelsToLabelable(graphqlWithAuth, {
labelIds: util_1.getLabelIds(allLabels, [...labelNamesToAdd]),
labelableId,
});
console.log('Added labels: ', labelNamesToAdd);
}
catch (error) {
util_1.logger.error('Request failed', error.message);
core.setFailed('addLabelsToLabelable has been failed. ');
}
}
catch (error) {
console.error('Request failed: ', error.request, error.message);
tools.exit.failure('removeLabelsFromLabelable has been failed. ');
if (labelNamesToRemove.size > 0) {
try {
await query_1.removeLabelsFromLabelable(graphqlWithAuth, {
labelIds: util_1.getLabelIds(allLabels, [
...labelNamesToRemove,
]),
labelableId,
});
console.log('Removed labels: ', labelNamesToRemove);
}
catch (error) {
util_1.logger.error('Request failed', error.message);
core.setFailed('removeLabelsFromLabelable has been failed. ');
}
}
}
})();
catch (error) {
core.setFailed(error.message);
}
}
run();

});
___scope___.file("util.js", function(exports, require, module, __filename, __dirname){

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const core = tslib_1.__importStar(require("@actions/core"));
const lodash_pick_1 = tslib_1.__importDefault(require("lodash.pick"));
exports.getLabelIds = (allLabels, labelNames) => JSON.stringify(Object.values(lodash_pick_1.default(allLabels, labelNames)));
exports.logger = {
debug: (message, object) => {
return core.debug(`${message}: ${JSON.stringify(object)}`);
},
error: (message, object) => {
return core.error(`${message}: ${JSON.stringify(object)}`);
},
};

});
___scope___.file("query.js", function(exports, require, module, __filename, __dirname){

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getPullRequestAndLabels = (tools, { owner, repo, number, }) => {
const query = `{
repository(owner: "${owner}", name: "${repo}") {
pullRequest(number: ${number}) {
exports.getPullRequestAndLabels = (graphqlWithAuth, { owner, repo, number, }) => {
const query = `query pullRequestAndLabels($owner: String!, $repo: String!, $number: Int!) {
repository(owner:$owner, name:$repo) {
pullRequest(number:$number) {
id
baseRefOid
headRefOid
Expand Down Expand Up @@ -131,41 +181,40 @@ exports.getPullRequestAndLabels = (tools, { owner, repo, number, }) => {
resetAt
}
}`;
return tools.github.graphql(query, {
return graphqlWithAuth(query, {
owner,
repo,
number,
headers: { Accept: 'application/vnd.github.ocelot-preview+json' },
});
};
exports.addLabelsToLabelable = (tools, { labelIds, labelableId, }) => {
exports.addLabelsToLabelable = (graphqlWithAuth, { labelIds, labelableId, }) => {
const query = `
mutation {
addLabelsToLabelable(input: {labelIds: ${labelIds}, labelableId: "${labelableId}"}) {
mutation addLabelsToLabelable($labelIds: String!, $labelableId: String!) {
addLabelsToLabelable(input: {labelIds:$labelIds, labelableId:$labelableId}) {
clientMutationId
}
}`;
return tools.github.graphql(query, {
return graphqlWithAuth(query, {
labelIds,
labelableId,
headers: { Accept: 'application/vnd.github.starfire-preview+json' },
});
};
exports.removeLabelsFromLabelable = (tools, { labelIds, labelableId, }) => {
exports.removeLabelsFromLabelable = (graphqlWithAuth, { labelIds, labelableId, }) => {
const query = `
mutation {
removeLabelsFromLabelable(input: {labelIds: ${labelIds}, labelableId: "${labelableId}"}) {
mutation removeLabelsFromLabelable($labelIds: String!, $labelableId: String!) {
removeLabelsFromLabelable(input: {labelIds:$labelIds, labelableId:$labelableId}) {
clientMutationId
}
}`;
return tools.github.graphql(query, {
return graphqlWithAuth(query, {
labelIds,
labelableId,
headers: { Accept: 'application/vnd.github.starfire-preview+json' },
});
};

});
___scope___.file("util.js", function(exports, require, module, __filename, __dirname){

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const lodash_1 = require("lodash");
exports.getLabelIds = (allLabels, labelNames) => JSON.stringify(Object.values(lodash_1.pick(allLabels, labelNames)));

});
return ___scope___.entry = "entrypoint.js";
});
Expand Down
16 changes: 11 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,19 @@
"build": "node fuse",
"build:watch": "node fuse --variant watch",
"lint": "eslint --cache --ext .ts src",
"lint:fix": "yarn lint --fix"
"lint:fix": "yarn lint --fix",
"fmt": "prettier --write **/*.ts"
},
"dependencies": {
"actions-toolkit": "^1.4.1",
"ignore": "^5.0.5",
"lodash": "^4.17.11"
"@actions/core": "^1.2.2",
"@actions/exec": "^1.0.3",
"@actions/github": "^2.1.0",
"@octokit/graphql": "^4.3.1",
"@types/lodash.pick": "^4.4.6",
"ignore": "^5.0.5"
},
"devDependencies": {
"@octokit/webhooks": "^7.0.0",
"@types/lodash": "^4.14.120",
"@types/node": "^10.12.24",
"@typescript-eslint/eslint-plugin": "^1.3.0",
Expand All @@ -24,6 +29,7 @@
"prettier": "^1.16.4",
"tslib": "^1.9.3",
"typescript": "^3.3.3",
"yargs": "^12.0.5"
"yargs": "^12.0.5",
"lodash.pick": "^4.4.0"
}
}
Loading