first commit from ibakshay fork
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
node_modules
|
||||
__tests__/runner/*
|
||||
.vscode
|
||||
@@ -0,0 +1,165 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
||||
@@ -0,0 +1,81 @@
|
||||
# WORK IN PROGRESS
|
||||
|
||||
## CLA GitHub Action
|
||||
|
||||
We are excited to announce that we are in development of CLA Assistant Github Action (a light version of CLA Assistant). With this Action, we can get rid of the need of a centrally managed database and rather storing all the contributors data in the repository's file system itself. This action won't be able to provide all the feature-sets of the current `CLA Assistant OAuth App`, However, this will work just fine If you want to configure CLA Assistant for a repo and store all the contributors data inside the repository. Feel free to test this GitHub Action and give us the feedback.
|
||||
|
||||
### Features
|
||||
1. decentralized data storage
|
||||
1. fully integrated with github environment
|
||||
1. no UI required
|
||||
1. no need for permission/scope handling
|
||||
1. contributors can sign the CLA by just posting a Pull Request Comment
|
||||
1. Signatures will be stored in a file inside the repository
|
||||
|
||||
## Configure Contributor License Agreement within two minutes
|
||||
|
||||
#### 1. Add the following Workflow File to your repository in this path`.github/workflow/cla.yml`
|
||||
|
||||
```yml
|
||||
name: "CLA Assistant"
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
pull_request:
|
||||
types: [opened,closed,synchronize]
|
||||
|
||||
jobs:
|
||||
CLAssistant:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: "CLA Assistant"
|
||||
if: (github.event.comment.body == 'recheckcla' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request'
|
||||
uses: cla-assistant/github-action@master
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
path-to-signatures: 'signatures/version1/cla.json'
|
||||
path-To-cladocument: 'https://github.com/ibakshay/test-action-workflow/blob/master/cla.md'
|
||||
branch: 'master'
|
||||
```
|
||||
|
||||
#### 2. Pull Request event triggers CLA Workflow
|
||||
|
||||
CLA action Workflow will be triggered on all Pull Request `opened, synchronize`
|
||||
<br/>
|
||||
|
||||
#### 3. Signing the CLA
|
||||
CLA workflow creates a comment on Pull Request asking all the contributors who has not signed the CLA to sign and also fails the pull request status check with an `failure`. The contributors can sign the CLA within the pull request by just copy and pasting **"I have read the CLA Document and I hereby sign the CLA"** as a Pull Request comment like below.
|
||||
If the contributors has already signed the CLA then the PR status will pass with `success`. <br/> By default, the GitHub Action bot will also do a empty commit with message **"@#contributorname# has signed the CLA"** whenever a contributor signs the CLA.
|
||||
|
||||

|
||||
|
||||
<br/>
|
||||
|
||||
#### 4. Signatures stored in a JSON file
|
||||
|
||||
After the contributor signed the CLA, the contributor's signature with metadata will be stored in a json file inside the repository like below screenshot and you can specify the custom path to this file with `path-to-signatures` input in the workflow . <br/> The default path is `path-to-signatures: 'signatures/version1/cla.json'`
|
||||
|
||||

|
||||
|
||||
|
||||
### Environmental Variables :
|
||||
|
||||
|
||||
| Name | Requirement | Description |
|
||||
| --------------------- | ----------- | ----------- |
|
||||
| `GITHUB_TOKEN` | _required_ | Must be in the form of `GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}` , CLA Action uses this in-built GitHub token to make the API calls for interacting with GitHub. It is built into Github Actions and does not need to be manually specified in your secrets store. [More Info](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/authenticating-with-the-github_token#about-the-github_token-secret)|
|
||||
|
||||
### Inputs Description :
|
||||
|
||||
| Name | Requirement | Description |
|
||||
| --------------------- | ----------- | ----------- |
|
||||
| `path-To-cladocument` | _required_ | provide full URL `https://<clafile>` to the Contributor License Agreement (CLA) to which the Contributor can read before signing the CLA. It can be a file inside the repository or it can be a gist |
|
||||
| `path-to-signatures` | _optional_ | Path to the JSON file where all the signatures of the contributors will be stored inside the repository. Default path is "./signatures/cla.json". |
|
||||
| `branch` | _optional_ | Branch in which all the signatures of the contributors will be stored and Default branch is `master` |
|
||||
|
||||
This action won't work for Pull Request coming from the forks as the [GitHub Action Token](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/authenticating-with-the-github_token) does not have write access for the forks, However, the GitHub team assured in one of the [discussion](https://github.community/t5/GitHub-Actions/Github-Workflow-not-running-from-pull-request-from-forked/m-p/32979#M1325) that they will ship this feature to enable read/write access for the PRs coming from the forks.
|
||||
|
||||
## License
|
||||
|
||||
> LGPL -3.0
|
||||
@@ -0,0 +1,108 @@
|
||||
import * as core from '@actions/core'
|
||||
import * as github from '@actions/github'
|
||||
import { context } from '@actions/github'
|
||||
import { getclas } from '../src/checkcla'
|
||||
import { lockPullRequest } from '../src/pullRequestLock'
|
||||
import { run } from '../src/main'
|
||||
import { mocked } from 'ts-jest/utils'
|
||||
|
||||
jest.mock('@actions/core')
|
||||
jest.mock('@actions/github')
|
||||
jest.mock('../src/pullRequestLock')
|
||||
jest.mock('../src/checkcla')
|
||||
const mockedGetClas = mocked(getclas)
|
||||
const mockedLockPullRequest = mocked(lockPullRequest)
|
||||
|
||||
|
||||
describe('Pull request event', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
// @ts-ignore
|
||||
github.context = {
|
||||
eventName: 'pull_request',
|
||||
ref: 'refs/pull/232/merge',
|
||||
workflow: 'CLA Assistant',
|
||||
action: 'ibakshaygithub-action-1',
|
||||
actor: 'ibakshay',
|
||||
payload: {
|
||||
action: 'closed',
|
||||
number: '1',
|
||||
pull_request: {
|
||||
number: 1,
|
||||
title: 'test',
|
||||
user: {
|
||||
login: 'ibakshay',
|
||||
},
|
||||
},
|
||||
repository: {
|
||||
name: 'auto-assign',
|
||||
owner: {
|
||||
login: 'ibakshay',
|
||||
},
|
||||
},
|
||||
},
|
||||
repo: {
|
||||
owner: 'ibakshay',
|
||||
repo: 'auto-assign',
|
||||
},
|
||||
issue: {
|
||||
owner: 'kentaro-m',
|
||||
repo: 'auto-assign',
|
||||
number: 1,
|
||||
},
|
||||
sha: ''
|
||||
}
|
||||
|
||||
}
|
||||
)
|
||||
|
||||
test('the lockPullRequest method should be called if there is a pull request merge/closed', async () => {
|
||||
|
||||
await run()
|
||||
expect(mockedLockPullRequest).toHaveBeenCalled()
|
||||
|
||||
|
||||
})
|
||||
|
||||
test('the checkcla method should not called if there is a pull request merge/closed', async () => {
|
||||
|
||||
await run()
|
||||
expect(mockedGetClas).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test('the lockPullRequest method should not be called if there is a pull request opened', async () => {
|
||||
|
||||
github.context.payload.action = 'opened'
|
||||
await run()
|
||||
|
||||
expect(mockedLockPullRequest).not.toHaveBeenCalled()
|
||||
|
||||
})
|
||||
|
||||
test('the checkcla method should be called if there is a pull request opened', async () => {
|
||||
|
||||
github.context.payload.action = 'opened'
|
||||
await run()
|
||||
expect(mockedGetClas).toHaveBeenCalled()
|
||||
|
||||
})
|
||||
|
||||
test('the lockPullRequest method should not be called if there is a pull request sync', async () => {
|
||||
|
||||
github.context.payload.action = 'synchronize'
|
||||
|
||||
await run()
|
||||
|
||||
expect(mockedLockPullRequest).not.toHaveBeenCalled()
|
||||
|
||||
})
|
||||
|
||||
test('the checkcla method should be called if there is a pull request sync', async () => {
|
||||
github.context.payload.action = 'synchronize'
|
||||
await run()
|
||||
expect(mockedGetClas).toHaveBeenCalled()
|
||||
|
||||
})
|
||||
|
||||
|
||||
})
|
||||
@@ -0,0 +1,12 @@
|
||||
import * as core from '@actions/core'
|
||||
import * as github from '@actions/github'
|
||||
import { context } from '@actions/github'
|
||||
import { getclas } from '../src/checkcla'
|
||||
import { lockPullRequest } from '../src/pullRequestLock'
|
||||
import { run } from '../src/main'
|
||||
import { mocked } from 'ts-jest/utils'
|
||||
|
||||
jest.mock('@actions/core')
|
||||
jest.mock('@actions/github')
|
||||
|
||||
//const mockedLockPullRequest = mocked(lockPullRequest)
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
name: 'Node 12 Template Action'
|
||||
description: 'Get started with Node actions'
|
||||
author: 'GitHub'
|
||||
inputs:
|
||||
path-to-signatures:
|
||||
description: 'Give a path for storing CLAs in a json file '
|
||||
default: './signatures/cla.json'
|
||||
branch:
|
||||
description: 'select a branch where all the CLAs are stored'
|
||||
default: 'master'
|
||||
blockchain-storage-flag:
|
||||
description: 'Optional functionality to store all the blockchain signatures in the ethirium blockchain as a smart contract'
|
||||
default: false
|
||||
runs:
|
||||
using: 'node12'
|
||||
main: 'lib/main.js'
|
||||
@@ -0,0 +1,22 @@
|
||||
# Contributors
|
||||
|
||||
### Checkin
|
||||
|
||||
- Do checkin source (src)
|
||||
- Do checkin build output (lib)
|
||||
- Do checkin runtime node_modules
|
||||
- Do not checkin devDependency node_modules (husky can help see below)
|
||||
|
||||
### devDependencies
|
||||
|
||||
In order to handle correctly checking in node_modules without devDependencies, we run [Husky](https://github.com/typicode/husky) before each commit.
|
||||
This step ensures that formatting and checkin rules are followed and that devDependencies are excluded. To make sure Husky runs correctly, please use the following workflow:
|
||||
|
||||
```
|
||||
npm install # installs all devDependencies including Husky
|
||||
git add abc.ext # Add the files you've changed. This should include files in src, lib, and node_modules (see above)
|
||||
git commit -m "Informative commit message" # Commit. This will run Husky
|
||||
```
|
||||
|
||||
During the commit step, Husky will take care of formatting all files with [Prettier](https://github.com/prettier/prettier) as well as pruning out devDependencies using `npm prune --production`.
|
||||
It will also make sure these changes are appropriately included in your commit (no further work is needed)
|
||||
@@ -0,0 +1,11 @@
|
||||
module.exports = {
|
||||
clearMocks: true,
|
||||
moduleFileExtensions: ['js', 'ts'],
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['**/*.test.ts'],
|
||||
testRunner: 'jest-circus/runner',
|
||||
transform: {
|
||||
'^.+\\.ts$': 'ts-jest'
|
||||
},
|
||||
verbose: true
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
"use strict";
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
||||
result["default"] = mod;
|
||||
return result;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const octokit_1 = __importDefault(require("./octokit"));
|
||||
const core = __importStar(require("@actions/core"));
|
||||
const github_1 = require("@actions/github");
|
||||
function addEmptyCommit() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
core.info(`Adding empty commit with the contributor name who has signed the CLA `);
|
||||
if (github_1.context.payload.comment) {
|
||||
//Do empty commit only when the contributor signs the CLA with the PR comment
|
||||
if (github_1.context.payload.comment.body === 'I have read the CLA Document and I hereby sign the CLA') {
|
||||
try {
|
||||
const message = ` @${github_1.context.payload.comment.user.login} has signed the CLA `;
|
||||
const pullRequestResponse = yield octokit_1.default.pulls.get({
|
||||
owner: github_1.context.repo.owner,
|
||||
repo: github_1.context.repo.repo,
|
||||
pull_number: github_1.context.payload.issue.number
|
||||
});
|
||||
const baseCommit = yield octokit_1.default.git.getCommit({
|
||||
owner: github_1.context.repo.owner,
|
||||
repo: github_1.context.repo.repo,
|
||||
commit_sha: pullRequestResponse.data.head.sha
|
||||
});
|
||||
const tree = yield octokit_1.default.git.getTree({
|
||||
owner: github_1.context.repo.owner,
|
||||
repo: github_1.context.repo.repo,
|
||||
tree_sha: baseCommit.data.tree.sha
|
||||
});
|
||||
const newCommit = yield octokit_1.default.git.createCommit({
|
||||
owner: github_1.context.repo.owner,
|
||||
repo: github_1.context.repo.repo,
|
||||
message: message,
|
||||
tree: tree.data.sha,
|
||||
parents: [pullRequestResponse.data.head.sha]
|
||||
});
|
||||
return octokit_1.default.git.updateRef({
|
||||
owner: github_1.context.repo.owner,
|
||||
repo: github_1.context.repo.repo,
|
||||
ref: `heads/${pullRequestResponse.data.head.ref}`,
|
||||
sha: newCommit.data.sha
|
||||
});
|
||||
}
|
||||
catch (e) {
|
||||
core.error(`failed when adding empty commit with the contributor's signature name `);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
});
|
||||
}
|
||||
exports.addEmptyCommit = addEmptyCommit;
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
"use strict";
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
||||
result["default"] = mod;
|
||||
return result;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const graphql_1 = __importDefault(require("./graphql"));
|
||||
const octokit_1 = __importDefault(require("./octokit"));
|
||||
const core = __importStar(require("@actions/core"));
|
||||
const github_1 = require("@actions/github");
|
||||
const pullRequestComment_1 = __importDefault(require("./pullRequestComment"));
|
||||
const addEmptyCommit_1 = require("./addEmptyCommit");
|
||||
function prepareCommiterMap(committers, clas) {
|
||||
let committerMap = {};
|
||||
committerMap.notSigned = committers.filter(committer => !clas.signedContributors.some(cla => committer.id === cla.id));
|
||||
committerMap.signed = committers.filter(committer => clas.signedContributors.some(cla => committer.id === cla.id));
|
||||
committers.map((committer) => { if (!committer.id) {
|
||||
committerMap.unknown.push(committer);
|
||||
} });
|
||||
return committerMap;
|
||||
}
|
||||
function updateFile(pathToClaSignatures, sha, contentBinary, branch, pullRequestNo) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
yield octokit_1.default.repos.createOrUpdateFile({
|
||||
owner: github_1.context.repo.owner,
|
||||
repo: github_1.context.repo.repo,
|
||||
path: pathToClaSignatures,
|
||||
sha: sha,
|
||||
message: `**CLA ASSISTANT ACTION** Updating file for storing signatures from Pull Request ${pullRequestNo}`,
|
||||
content: contentBinary,
|
||||
branch: branch
|
||||
});
|
||||
//Do empty commit only when the contributor signs the CLA with the PR comment
|
||||
yield addEmptyCommit_1.addEmptyCommit();
|
||||
core.info(`successfully added empty commit with the contributor's signature name who has signed the CLA`);
|
||||
});
|
||||
}
|
||||
function createFile(pathToClaSignatures, contentBinary, branch) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
/* TODO: add dynamic Message content */
|
||||
let response = yield octokit_1.default.repos.createOrUpdateFile({
|
||||
owner: github_1.context.repo.owner,
|
||||
repo: github_1.context.repo.repo,
|
||||
path: pathToClaSignatures,
|
||||
message: '**CLA ASSISTANT ACTION** Creating file for storing CLA Signatures',
|
||||
content: contentBinary,
|
||||
branch: branch
|
||||
});
|
||||
return response;
|
||||
});
|
||||
}
|
||||
function getclas(pullRequestNo) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
let committerMap = {};
|
||||
let signed = false;
|
||||
//getting the path of the cla from the user
|
||||
let pathToClaSignatures = core.getInput('path-to-signatures');
|
||||
let branch = core.getInput('branch');
|
||||
if (!pathToClaSignatures || pathToClaSignatures == '') {
|
||||
pathToClaSignatures = 'signatures/cla.json'; // default path for storing the signatures
|
||||
}
|
||||
if (!branch || branch == '') {
|
||||
branch = 'master';
|
||||
}
|
||||
let result, clas, sha;
|
||||
const committers = yield graphql_1.default();
|
||||
try {
|
||||
result = yield octokit_1.default.repos.getContents({
|
||||
owner: github_1.context.repo.owner,
|
||||
repo: github_1.context.repo.repo,
|
||||
path: pathToClaSignatures,
|
||||
ref: branch
|
||||
});
|
||||
sha = result.data.sha;
|
||||
}
|
||||
catch (error) {
|
||||
if (error.status === 404) {
|
||||
committerMap.notSigned = committers;
|
||||
committerMap.signed = [];
|
||||
committers.map((committer) => { if (!committer.id) {
|
||||
committerMap.unknown.push(committer);
|
||||
} });
|
||||
const initialContent = { signedContributors: [] };
|
||||
const initalContentString = JSON.stringify(initialContent, null, 2);
|
||||
const initalContentBinary = Buffer.from(initalContentString).toString('base64');
|
||||
const promise = Promise.all([createFile(pathToClaSignatures, initalContentBinary, branch), pullRequestComment_1.default(signed, committerMap, committers, pullRequestNo)]);
|
||||
if (promise) {
|
||||
core.setFailed(`committers of pull request ${github_1.context.issue.number} has to sign the CLA`);
|
||||
return;
|
||||
}
|
||||
core.setFailed('error occured when creating the signed contributors file ' + error);
|
||||
}
|
||||
else {
|
||||
core.setFailed(error.message);
|
||||
}
|
||||
}
|
||||
clas = Buffer.from(result.data.content, 'base64').toString();
|
||||
clas = JSON.parse(clas);
|
||||
committerMap = prepareCommiterMap(committers, clas);
|
||||
core.debug('unsigned contributors are: ' + JSON.stringify(committerMap.notSigned, null, 2));
|
||||
core.debug('signed contributors are: ' + JSON.stringify(committerMap.signed, null, 2));
|
||||
//DO NULL CHECK FOR below
|
||||
if (committerMap) {
|
||||
if (committerMap.notSigned.length === 0) {
|
||||
core.debug("null check");
|
||||
signed = true;
|
||||
}
|
||||
}
|
||||
try {
|
||||
const reactedCommitters = yield pullRequestComment_1.default(signed, committerMap, committers, pullRequestNo);
|
||||
/* pushing the unsigned contributors to the CLA Json File */
|
||||
if (signed) {
|
||||
core.info("All committers have signed the CLA");
|
||||
return;
|
||||
}
|
||||
if (reactedCommitters) {
|
||||
if (reactedCommitters.newSigned) {
|
||||
//reactedCommitters.newSigned.forEach((reactedCommitter) => reactedCommitter.pullRequestNo = pullRequestNo)
|
||||
clas.signedContributors.push(...reactedCommitters.newSigned);
|
||||
let contentString = JSON.stringify(clas, null, 2);
|
||||
let contentBinary = Buffer.from(contentString).toString('base64');
|
||||
yield updateFile(pathToClaSignatures, sha, contentBinary, branch, pullRequestNo);
|
||||
}
|
||||
if (reactedCommitters.allSignedFlag) {
|
||||
core.info("All committers have signed the CLA");
|
||||
return;
|
||||
}
|
||||
}
|
||||
/* return when there are no unsigned committers */
|
||||
if ((committerMap.notSigned === undefined || committerMap.notSigned.length === 0)) {
|
||||
core.info("All committers have signed the CLA");
|
||||
return;
|
||||
}
|
||||
else {
|
||||
core.setFailed(`committers of Pull Request number ${github_1.context.issue.number} has to sign the CLA`);
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
core.setFailed(err.message);
|
||||
throw new Error("error while updating the JSON file" + err);
|
||||
}
|
||||
return clas;
|
||||
});
|
||||
}
|
||||
exports.getclas = getclas;
|
||||
@@ -0,0 +1,99 @@
|
||||
"use strict";
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
||||
result["default"] = mod;
|
||||
return result;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const octokit_1 = __importDefault(require("./octokit"));
|
||||
const core = __importStar(require("@actions/core"));
|
||||
const github_1 = require("@actions/github");
|
||||
function getCommitters() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
try {
|
||||
let committers = [];
|
||||
let filteredCommitters = [];
|
||||
let response = yield octokit_1.default.graphql(`
|
||||
query($owner:String! $name:String! $number:Int! $cursor:String!){
|
||||
repository(owner: $owner, name: $name) {
|
||||
pullRequest(number: $number) {
|
||||
commits(first: 100, after: $cursor) {
|
||||
totalCount
|
||||
edges {
|
||||
node {
|
||||
commit {
|
||||
author {
|
||||
email
|
||||
name
|
||||
user {
|
||||
id
|
||||
databaseId
|
||||
login
|
||||
}
|
||||
}
|
||||
committer {
|
||||
name
|
||||
user {
|
||||
id
|
||||
databaseId
|
||||
login
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
cursor
|
||||
}
|
||||
pageInfo {
|
||||
endCursor
|
||||
hasNextPage
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`.replace(/ /g, ''), {
|
||||
owner: github_1.context.repo.owner,
|
||||
name: github_1.context.repo.repo,
|
||||
number: github_1.context.issue.number,
|
||||
cursor: ''
|
||||
});
|
||||
response.repository.pullRequest.commits.edges.forEach(edge => {
|
||||
let committer = extractUserFromCommit(edge.node.commit);
|
||||
let user = {
|
||||
name: committer.login || committer.name,
|
||||
id: committer.databaseId || '',
|
||||
pullRequestNo: github_1.context.issue.number
|
||||
};
|
||||
if (committers.length === 0 || committers.map((c) => {
|
||||
return c.name;
|
||||
}).indexOf(user.name) < 0) {
|
||||
committers.push(user);
|
||||
}
|
||||
});
|
||||
console.log("the committers of this pr are" + JSON.stringify(committers));
|
||||
filteredCommitters = committers.filter((committer) => {
|
||||
return committer.id !== 41898282;
|
||||
});
|
||||
console.log("the committers last push of this pr are" + JSON.stringify(filteredCommitters));
|
||||
return filteredCommitters;
|
||||
}
|
||||
catch (e) {
|
||||
core.setFailed('graphql call to get the committers details failed:' + e);
|
||||
}
|
||||
});
|
||||
}
|
||||
exports.default = getCommitters;
|
||||
const extractUserFromCommit = (commit) => commit.author.user || commit.committer.user || commit.author || commit.committer;
|
||||
@@ -0,0 +1,2 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
"use strict";
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
||||
result["default"] = mod;
|
||||
return result;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const core = __importStar(require("@actions/core"));
|
||||
const github_1 = require("@actions/github");
|
||||
const io = require('@actions/io');
|
||||
const checkcla_1 = require("./checkcla");
|
||||
const pullRequestLock_1 = require("./pullRequestLock");
|
||||
function run() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
try {
|
||||
const pullRequestNo = github_1.context.issue.number;
|
||||
//s console.log("the context is " + JSON.stringify(context, null, 2))
|
||||
core.info('CLA Assistant GitHub Action has started');
|
||||
core.info('the PR No is ' + JSON.stringify(pullRequestNo));
|
||||
if (github_1.context.payload.action === 'closed') {
|
||||
return pullRequestLock_1.lockPullRequest(pullRequestNo);
|
||||
}
|
||||
else {
|
||||
yield checkcla_1.getclas(pullRequestNo);
|
||||
// const rateLimit = await octokit.rateLimit.get()
|
||||
// console.log(JSON.stringify(rateLimit.data.resources, null, 4))
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
core.setFailed(error.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
exports.run = run;
|
||||
run();
|
||||
@@ -0,0 +1,5 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const github_1 = require("@actions/github");
|
||||
const octokit = new github_1.GitHub(process.env.GITHUB_TOKEN);
|
||||
exports.default = octokit;
|
||||
@@ -0,0 +1,176 @@
|
||||
"use strict";
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
||||
result["default"] = mod;
|
||||
return result;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const octokit_1 = __importDefault(require("./octokit"));
|
||||
const core = __importStar(require("@actions/core"));
|
||||
const url_1 = require("./url");
|
||||
const github_1 = require("@actions/github");
|
||||
const signatureComment_1 = __importDefault(require("./signatureComment"));
|
||||
function getComment() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
try {
|
||||
const response = yield octokit_1.default.issues.listComments({
|
||||
owner: github_1.context.repo.owner,
|
||||
repo: github_1.context.repo.repo,
|
||||
issue_number: github_1.context.issue.number
|
||||
});
|
||||
return response.data.find(comment => comment.body.match(/.*CLA Assistant Lite.*/));
|
||||
}
|
||||
catch (e) {
|
||||
core.setFailed('Error occured when getting all the comments of the pull request: ' + e.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
function commentContent(signed, committerMap) {
|
||||
const labelName = {};
|
||||
if (signed) {
|
||||
labelName.current_name = 'CLA signed :smiley:';
|
||||
//updateLabel(signed, labelName)
|
||||
return `**CLA Assistant Lite** All committers have signed the CLA. :smiley:`;
|
||||
}
|
||||
/* TODO: Unhandled Promise Rejection */
|
||||
labelName.current_name = 'CLA Not Signed :worried:';
|
||||
//updateLabel(signed, labelName)
|
||||
let committersCount = 1;
|
||||
if (committerMap && committerMap.signed && committerMap.notSigned) {
|
||||
committersCount = committerMap.signed.length + committerMap.notSigned.length;
|
||||
}
|
||||
let you = (committersCount > 1 ? 'you all' : 'you');
|
||||
let text = `**CLA Assistant Lite:** <br/>Thank you for your submission, we really appreciate it. Like many open source projects, we ask that ${you} sign our [Contributor License Agreement](${url_1.pathToCLADocument()}) before we can accept your contribution.You can sign the CLA by just posting a Pull Request Comment same as the below format.
|
||||
***I have read the CLA Document and I hereby sign the CLA*** <br/><br/>`;
|
||||
if (committersCount > 1 && committerMap && committerMap.signed && committerMap.notSigned) {
|
||||
text += `**${committerMap.signed.length}** out of **${committerMap.signed.length + committerMap.notSigned.length}** committers have signed the CLA.`;
|
||||
committerMap.signed.forEach((signedCommitter) => {
|
||||
text += `<br/>:white_check_mark: @${signedCommitter.name}`;
|
||||
});
|
||||
committerMap.notSigned.forEach((unsignedCommitter) => {
|
||||
text += `<br/>:x: @${unsignedCommitter.name}`;
|
||||
});
|
||||
text += '<br/>';
|
||||
}
|
||||
if (committerMap && committerMap.unknown && committerMap.unknown.length > 0) {
|
||||
let seem = (committerMap.unknown.length > 1 ? 'seem' : 'seems');
|
||||
text += `<hr/>**${committerMap.unknown.join(', ')}** ${seem} not to be a GitHub user.`;
|
||||
text += ' You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please [add the email address used for this commit to your account](https://help.github.com/articles/why-are-my-commits-linked-to-the-wrong-user/#commits-are-not-linked-to-any-user).<br/>';
|
||||
}
|
||||
return text;
|
||||
}
|
||||
function prepareAllSignedCommitters(committerMap, signedInPrCommitters, committers) {
|
||||
let allSignedCommitters = [];
|
||||
/*
|
||||
Reference: https://stackoverflow.com/questions/54134156/javascript-merge-two-arrays-of-objects-only-if-not-duplicate-based-on-specifi
|
||||
merging two arrays if not duplicate. 1) already signed committers in the file 2) signed committers in the PR comment
|
||||
*/
|
||||
let ids = new Set(signedInPrCommitters.map(committer => committer.id));
|
||||
allSignedCommitters = [...signedInPrCommitters, ...committerMap.signed.filter(signedCommitter => !ids.has(signedCommitter.id))];
|
||||
core.debug("all signed committers after merging " + JSON.stringify(allSignedCommitters, null, 2));
|
||||
//checking if all the unsigned committers have reacted to the PR comment (this is needed for changing the content of the PR comment to "All committers have signed the CLA")
|
||||
let allSignedFlag = committers.every(committer => allSignedCommitters.some(reactedCommitter => committer.id === reactedCommitter.id));
|
||||
return allSignedFlag;
|
||||
}
|
||||
function prComment(signed, committerMap, committers, pullRequestNo) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
try {
|
||||
const prComment = yield getComment();
|
||||
if (!prComment) {
|
||||
yield octokit_1.default.issues.createComment({
|
||||
owner: github_1.context.repo.owner,
|
||||
repo: github_1.context.repo.repo,
|
||||
issue_number: github_1.context.issue.number,
|
||||
body: commentContent(signed, committerMap)
|
||||
});
|
||||
return;
|
||||
}
|
||||
else if (prComment && prComment.id) {
|
||||
if (signed) {
|
||||
yield octokit_1.default.issues.updateComment({
|
||||
owner: github_1.context.repo.owner,
|
||||
repo: github_1.context.repo.repo,
|
||||
comment_id: prComment.id,
|
||||
body: commentContent(signed, committerMap)
|
||||
});
|
||||
}
|
||||
const reactedCommitters = yield signatureComment_1.default(prComment.id, committerMap, committers, pullRequestNo);
|
||||
if (reactedCommitters) {
|
||||
if (reactedCommitters.onlyCommitters) {
|
||||
reactedCommitters.allSignedFlag = prepareAllSignedCommitters(committerMap, reactedCommitters.onlyCommitters, committers);
|
||||
}
|
||||
}
|
||||
core.debug("allSignedFlag is " + reactedCommitters.allSignedFlag);
|
||||
committerMap.signed.push(...reactedCommitters.newSigned);
|
||||
committerMap.notSigned = committerMap.notSigned.filter(committer => !reactedCommitters.newSigned.some(reactedCommitter => committer.id === reactedCommitter.id));
|
||||
yield octokit_1.default.issues.updateComment({
|
||||
owner: github_1.context.repo.owner,
|
||||
repo: github_1.context.repo.repo,
|
||||
comment_id: prComment.id,
|
||||
body: commentContent(reactedCommitters.allSignedFlag, committerMap)
|
||||
});
|
||||
return reactedCommitters;
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
core.setFailed('Error occured when creating or editing the comments of the pull request: ' + e.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
exports.default = prComment;
|
||||
// function addLabel() {
|
||||
// return octokit.issues.addLabels({
|
||||
// owner: context.repo.owner,
|
||||
// repo: context.repo.repo,
|
||||
// issue_number: context.issue.number,
|
||||
// labels: ['CLA Not Signed :worried:']
|
||||
// })
|
||||
// }
|
||||
// async function updateLabel(signed: boolean, labelName: LabelName) {
|
||||
// try {
|
||||
// const getLabel = await octokit.issues.getLabel({
|
||||
// owner: context.repo.owner,
|
||||
// repo: context.repo.repo,
|
||||
// name: labelName.current_name
|
||||
// })
|
||||
// if (getLabel) {
|
||||
// return
|
||||
// }
|
||||
// } catch (error) {
|
||||
// if (error.status === 404) {
|
||||
// if (signed) {
|
||||
// labelName = {
|
||||
// current_name: 'CLA Not Signed :worried:',
|
||||
// name: 'CLA signed :smiley:'
|
||||
// }
|
||||
// }
|
||||
// else {
|
||||
// labelName = {
|
||||
// current_name: 'CLA signed :smiley:',
|
||||
// name: 'CLA Not Signed :worried:'
|
||||
// }
|
||||
// }
|
||||
// return octokit.issues.updateLabel({
|
||||
// owner: context.repo.owner,
|
||||
// repo: context.repo.repo,
|
||||
// current_name: labelName.current_name,
|
||||
// name: labelName.name
|
||||
// })
|
||||
// }
|
||||
// core.setFailed("error when creating a label :" + error)
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,41 @@
|
||||
"use strict";
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
||||
result["default"] = mod;
|
||||
return result;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const octokit_1 = __importDefault(require("./octokit"));
|
||||
const core = __importStar(require("@actions/core"));
|
||||
const github_1 = require("@actions/github");
|
||||
function lockPullRequest(pullRequestNo) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
core.info('Locking the Pull Request to safe guard the Pull Request CLA Signatures');
|
||||
try {
|
||||
yield octokit_1.default.issues.lock({
|
||||
owner: github_1.context.repo.owner,
|
||||
repo: github_1.context.repo.repo,
|
||||
issue_number: pullRequestNo
|
||||
});
|
||||
core.info(`successfully locked the pull request ${pullRequestNo}`);
|
||||
}
|
||||
catch (e) {
|
||||
core.error(`failed when locking the pull request `);
|
||||
}
|
||||
});
|
||||
}
|
||||
exports.lockPullRequest = lockPullRequest;
|
||||
@@ -0,0 +1,98 @@
|
||||
"use strict";
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
||||
result["default"] = mod;
|
||||
return result;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const octokit_1 = __importDefault(require("./octokit"));
|
||||
const github_1 = require("@actions/github");
|
||||
const fetch = require("node-fetch");
|
||||
const core = __importStar(require("@actions/core"));
|
||||
function webhookSmartContract(newSignedCommitters) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const blockchainURL = core.getInput('blockchain-webhook-endpoint') || 'https://u9afh6n36g.execute-api.eu-central-1.amazonaws.com/dev/webhook';
|
||||
try {
|
||||
const config = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(newSignedCommitters)
|
||||
};
|
||||
const res = yield fetch(blockchainURL, config);
|
||||
const response = yield res.json();
|
||||
core.debug("the response of the webhook is " + JSON.stringify(response));
|
||||
//const response = await res.json()
|
||||
if (response.success) {
|
||||
core.debug("the response2 of the webhook is " + JSON.stringify(response));
|
||||
//return json
|
||||
return response;
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
core.setFailed('The webhook post request for storing signatures in smart contract failed' + error);
|
||||
}
|
||||
});
|
||||
}
|
||||
function signatureWithPRComment(commentId, committerMap, committers, pullRequestNo) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
let blockchainFlag = core.getInput('blockchain-storage-flag');
|
||||
core.debug("the blockchainflag is " + blockchainFlag);
|
||||
let repoId = github_1.context.payload.repository.id;
|
||||
let commentedCommitterMap = {};
|
||||
let prResponse = yield octokit_1.default.issues.listComments({
|
||||
owner: github_1.context.repo.owner,
|
||||
repo: github_1.context.repo.repo,
|
||||
issue_number: github_1.context.issue.number
|
||||
});
|
||||
let listOfPRComments = [];
|
||||
let filteredListOfPRComments = [];
|
||||
//TODO: Do null check for repoID
|
||||
prResponse.data.map((prComment) => {
|
||||
listOfPRComments.push({
|
||||
name: prComment.user.login,
|
||||
id: prComment.user.id,
|
||||
comment_id: prComment.id,
|
||||
body: prComment.body.toLowerCase(),
|
||||
created_at: prComment.created_at,
|
||||
repoId: repoId,
|
||||
pullRequestNo: pullRequestNo
|
||||
});
|
||||
});
|
||||
listOfPRComments.map((comment) => {
|
||||
if (comment.body.match(/^.*i \s*have \s*read \s*the \s*cla \s*document \s*and \s*i \s*hereby \s*sign \s*the \s*cla.*$/) && comment.name !== 'github-actions[bot]') {
|
||||
filteredListOfPRComments.push(comment);
|
||||
}
|
||||
});
|
||||
for (var i = 0; i < filteredListOfPRComments.length; i++) {
|
||||
delete filteredListOfPRComments[i].body;
|
||||
}
|
||||
// //checking if the reacted committers are not the signed committers(not in the storage file) and filtering only the unsigned committers
|
||||
commentedCommitterMap.newSigned = filteredListOfPRComments.filter(commentedCommitter => committerMap.notSigned.some(notSignedCommitter => commentedCommitter.id === notSignedCommitter.id));
|
||||
core.debug("the new commented committers(signed) are :" + JSON.stringify(commentedCommitterMap.newSigned, null, 3));
|
||||
if (blockchainFlag == 'true' && commentedCommitterMap.newSigned) {
|
||||
yield webhookSmartContract(commentedCommitterMap.newSigned);
|
||||
}
|
||||
//checking if the commented users are only the contributors who has committed in the same PR (This is needed for the PR Comment and changing the status to success when all the contributors has reacted to the PR)
|
||||
commentedCommitterMap.onlyCommitters = committers.filter(committer => filteredListOfPRComments.some(commentedCommitter => committer.id == commentedCommitter.id));
|
||||
core.debug("the reacted signed committers comments are " + JSON.stringify(commentedCommitterMap.onlyCommitters, null, 3));
|
||||
return commentedCommitterMap;
|
||||
});
|
||||
}
|
||||
exports.default = signatureWithPRComment;
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
"use strict";
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
||||
result["default"] = mod;
|
||||
return result;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const core = __importStar(require("@actions/core"));
|
||||
function pathToCLADocument() {
|
||||
const pathToCLADocument = core.getInput('path-To-cladocument');
|
||||
return pathToCLADocument;
|
||||
}
|
||||
exports.pathToCLADocument = pathToCLADocument;
|
||||
Generated
+5493
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "github-action",
|
||||
"version": "0.0.1",
|
||||
"description": "GitHub Action for storing CLA signatures",
|
||||
"main": "lib/main.js",
|
||||
"scripts": {
|
||||
"test": "jest",
|
||||
"build": "tsc"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/cla-assistant/github-action.git"
|
||||
},
|
||||
"keywords": [
|
||||
"actions",
|
||||
"node",
|
||||
"setup"
|
||||
],
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.0.0",
|
||||
"@actions/github": "^1.0.0",
|
||||
"@actions/io": "^1.0.0",
|
||||
"@types/jquery": "^3.3.31",
|
||||
"actions-toolkit": "^2.1.0",
|
||||
"lodash": "^4.17.15",
|
||||
"node-fetch": "^2.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^24.0.13",
|
||||
"@types/node": "^12.0.4",
|
||||
"jest": "^24.8.0",
|
||||
"jest-circus": "^24.7.1",
|
||||
"ts-jest": "^24.2.0",
|
||||
"typescript": "^3.5.1"
|
||||
},
|
||||
"author": "ibakshay",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"bugs": {
|
||||
"url": "https://github.com/cla-assistant/github-action/issues"
|
||||
},
|
||||
"homepage": "https://github.com/cla-assistant/github-action#readme"
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import octokit from './octokit'
|
||||
import * as core from '@actions/core'
|
||||
import { context } from '@actions/github'
|
||||
|
||||
export async function addEmptyCommit() {
|
||||
core.info(`Adding empty commit with the contributor name who has signed the CLA `)
|
||||
if (context.payload.comment) {
|
||||
|
||||
//Do empty commit only when the contributor signs the CLA with the PR comment
|
||||
if (context.payload.comment.body === 'I have read the CLA Document and I hereby sign the CLA') {
|
||||
try {
|
||||
const message = ` @${context.payload.comment.user.login} has signed the CLA `
|
||||
const pullRequestResponse = await octokit.pulls.get({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: context.payload.issue!.number
|
||||
})
|
||||
|
||||
const baseCommit = await octokit.git.getCommit({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
commit_sha: pullRequestResponse.data.head.sha
|
||||
})
|
||||
|
||||
const tree = await octokit.git.getTree({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
tree_sha: baseCommit.data.tree.sha
|
||||
})
|
||||
const newCommit = await octokit.git.createCommit(
|
||||
{
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
message: message,
|
||||
tree: tree.data.sha,
|
||||
parents: [pullRequestResponse.data.head.sha]
|
||||
}
|
||||
)
|
||||
return octokit.git.updateRef({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
ref: `heads/${pullRequestResponse.data.head.ref}`,
|
||||
sha: newCommit.data.sha
|
||||
})
|
||||
|
||||
} catch (e) {
|
||||
core.error(`failed when adding empty commit with the contributor's signature name `)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
import getCommitters from './graphql'
|
||||
import octokit from './octokit'
|
||||
import * as core from '@actions/core'
|
||||
import { context } from '@actions/github'
|
||||
import prComment from './pullRequestComment'
|
||||
import { addEmptyCommit } from './addEmptyCommit'
|
||||
import { CommitterMap, CommittersDetails, ReactedCommitterMap } from './interfaces'
|
||||
|
||||
|
||||
|
||||
|
||||
function prepareCommiterMap(committers: CommittersDetails[], clas): CommitterMap {
|
||||
let committerMap: CommitterMap = {}
|
||||
committerMap.notSigned = committers.filter(committer => !clas.signedContributors.some(cla => committer.id === cla.id))
|
||||
committerMap.signed = committers.filter(committer => clas.signedContributors.some(cla => committer.id === cla.id))
|
||||
committers.map((committer) => { if (!committer.id) { committerMap.unknown!.push(committer) } })
|
||||
return committerMap
|
||||
|
||||
}
|
||||
|
||||
async function updateFile(pathToClaSignatures, sha, contentBinary, branch, pullRequestNo) {
|
||||
|
||||
await octokit.repos.createOrUpdateFile({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
path: pathToClaSignatures,
|
||||
sha: sha,
|
||||
message: `**CLA ASSISTANT ACTION** Updating file for storing signatures from Pull Request ${pullRequestNo}`,
|
||||
content: contentBinary,
|
||||
branch: branch
|
||||
})
|
||||
//Do empty commit only when the contributor signs the CLA with the PR comment
|
||||
await addEmptyCommit()
|
||||
core.info(`successfully added empty commit with the contributor's signature name who has signed the CLA`)
|
||||
}
|
||||
|
||||
async function createFile(pathToClaSignatures, contentBinary, branch): Promise<object> {
|
||||
/* TODO: add dynamic Message content */
|
||||
let response = await octokit.repos.createOrUpdateFile({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
path: pathToClaSignatures,
|
||||
message: '**CLA ASSISTANT ACTION** Creating file for storing CLA Signatures',
|
||||
content: contentBinary,
|
||||
branch: branch
|
||||
})
|
||||
return response
|
||||
|
||||
|
||||
}
|
||||
export async function getclas(pullRequestNo: number) {
|
||||
let committerMap = {} as CommitterMap
|
||||
|
||||
let signed: boolean = false
|
||||
//getting the path of the cla from the user
|
||||
let pathToClaSignatures: string = core.getInput('path-to-signatures')
|
||||
let branch: string = core.getInput('branch')
|
||||
if (!pathToClaSignatures || pathToClaSignatures == '') {
|
||||
pathToClaSignatures = 'signatures/cla.json' // default path for storing the signatures
|
||||
}
|
||||
if (!branch || branch == '') {
|
||||
branch = 'master'
|
||||
}
|
||||
let result, clas, sha
|
||||
const committers = await getCommitters() as CommittersDetails[]
|
||||
try {
|
||||
result = await octokit.repos.getContents({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
path: pathToClaSignatures,
|
||||
ref: branch
|
||||
})
|
||||
sha = result.data.sha
|
||||
|
||||
} catch (error) {
|
||||
if (error.status === 404) {
|
||||
committerMap.notSigned = committers
|
||||
committerMap.signed = []
|
||||
committers.map((committer) => { if (!committer.id) { committerMap.unknown!.push(committer) } })
|
||||
|
||||
const initialContent = { signedContributors: [] }
|
||||
const initalContentString = JSON.stringify(initialContent, null, 2)
|
||||
const initalContentBinary = Buffer.from(initalContentString).toString('base64')
|
||||
const promise = Promise.all([createFile(pathToClaSignatures, initalContentBinary, branch), prComment(signed, committerMap, committers, pullRequestNo)])
|
||||
if (promise) {
|
||||
core.setFailed(`committers of pull request ${context.issue.number} has to sign the CLA`)
|
||||
return
|
||||
}
|
||||
core.setFailed('error occured when creating the signed contributors file ' + error)
|
||||
|
||||
}
|
||||
else {
|
||||
core.setFailed(error.message)
|
||||
}
|
||||
|
||||
}
|
||||
clas = Buffer.from(result.data.content, 'base64').toString()
|
||||
clas = JSON.parse(clas)
|
||||
committerMap = prepareCommiterMap(committers, clas) as CommitterMap
|
||||
core.debug('unsigned contributors are: ' + JSON.stringify(committerMap.notSigned, null, 2))
|
||||
core.debug('signed contributors are: ' + JSON.stringify(committerMap.signed, null, 2))
|
||||
//DO NULL CHECK FOR below
|
||||
if (committerMap) {
|
||||
if (committerMap.notSigned!.length === 0) {
|
||||
core.debug("null check")
|
||||
signed = true
|
||||
}
|
||||
}
|
||||
try {
|
||||
|
||||
const reactedCommitters: ReactedCommitterMap = await prComment(signed, committerMap, committers, pullRequestNo) as ReactedCommitterMap
|
||||
/* pushing the unsigned contributors to the CLA Json File */
|
||||
if (signed) {
|
||||
core.info("All committers have signed the CLA")
|
||||
return
|
||||
}
|
||||
|
||||
if (reactedCommitters) {
|
||||
if (reactedCommitters.newSigned) {
|
||||
//reactedCommitters.newSigned.forEach((reactedCommitter) => reactedCommitter.pullRequestNo = pullRequestNo)
|
||||
clas.signedContributors.push(...reactedCommitters.newSigned)
|
||||
let contentString = JSON.stringify(clas, null, 2)
|
||||
let contentBinary = Buffer.from(contentString).toString('base64')
|
||||
await updateFile(pathToClaSignatures, sha, contentBinary, branch, pullRequestNo)
|
||||
|
||||
}
|
||||
if (reactedCommitters.allSignedFlag) {
|
||||
core.info("All committers have signed the CLA")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
/* return when there are no unsigned committers */
|
||||
if ((committerMap.notSigned === undefined || committerMap.notSigned.length === 0)) {
|
||||
core.info("All committers have signed the CLA")
|
||||
return
|
||||
}
|
||||
else {
|
||||
core.setFailed(`committers of Pull Request number ${context.issue.number} has to sign the CLA`)
|
||||
}
|
||||
}
|
||||
|
||||
catch (err) {
|
||||
core.setFailed(err.message)
|
||||
throw new Error("error while updating the JSON file" + err)
|
||||
}
|
||||
return clas
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import octokit from './octokit'
|
||||
import * as core from '@actions/core';
|
||||
import { context } from '@actions/github'
|
||||
import { CommittersDetails } from './interfaces'
|
||||
|
||||
|
||||
|
||||
export default async function getCommitters() {
|
||||
try {
|
||||
let committers: CommittersDetails[] = []
|
||||
let filteredCommitters: CommittersDetails[] = []
|
||||
let response = await octokit.graphql(`
|
||||
query($owner:String! $name:String! $number:Int! $cursor:String!){
|
||||
repository(owner: $owner, name: $name) {
|
||||
pullRequest(number: $number) {
|
||||
commits(first: 100, after: $cursor) {
|
||||
totalCount
|
||||
edges {
|
||||
node {
|
||||
commit {
|
||||
author {
|
||||
email
|
||||
name
|
||||
user {
|
||||
id
|
||||
databaseId
|
||||
login
|
||||
}
|
||||
}
|
||||
committer {
|
||||
name
|
||||
user {
|
||||
id
|
||||
databaseId
|
||||
login
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
cursor
|
||||
}
|
||||
pageInfo {
|
||||
endCursor
|
||||
hasNextPage
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`.replace(/ /g, ''), {
|
||||
owner: context.repo.owner,
|
||||
name: context.repo.repo,
|
||||
number: context.issue.number,
|
||||
cursor: ''
|
||||
})
|
||||
response.repository.pullRequest.commits.edges.forEach(edge => {
|
||||
let committer = extractUserFromCommit(edge.node.commit)
|
||||
let user = {
|
||||
name: committer.login || committer.name,
|
||||
id: committer.databaseId || '',
|
||||
pullRequestNo: context.issue.number
|
||||
}
|
||||
if (committers.length === 0 || committers.map((c) => {
|
||||
return c.name
|
||||
}).indexOf(user.name) < 0) {
|
||||
committers.push(user)
|
||||
}
|
||||
})
|
||||
console.log("the committers of this pr are" + JSON.stringify(committers))
|
||||
filteredCommitters = committers.filter((committer) => {
|
||||
return committer.id !== 41898282
|
||||
})
|
||||
console.log("the committers last push of this pr are" + JSON.stringify(filteredCommitters))
|
||||
return filteredCommitters
|
||||
|
||||
} catch (e) {
|
||||
core.setFailed('graphql call to get the committers details failed:' + e)
|
||||
}
|
||||
|
||||
}
|
||||
const extractUserFromCommit = (commit) => commit.author.user || commit.committer.user || commit.author || commit.committer
|
||||
@@ -0,0 +1,42 @@
|
||||
export interface CommitterMap {
|
||||
signed?: CommittersDetails[],
|
||||
notSigned?: CommittersDetails[],
|
||||
unknown?: CommittersDetails[]
|
||||
}
|
||||
|
||||
export interface ReactedCommitterMap {
|
||||
newSigned: CommittersDetails[],
|
||||
onlyCommitters?: CommittersDetails[],
|
||||
allSignedFlag: boolean
|
||||
}
|
||||
|
||||
export interface CommentedCommitterMap {
|
||||
newSigned: CommittersDetails[],
|
||||
onlyCommitters?: CommittersDetails[],
|
||||
allSignedFlag: boolean
|
||||
}
|
||||
|
||||
export interface CommittersDetails {
|
||||
name: string,
|
||||
id: number,
|
||||
pullRequestNo?: number,
|
||||
created_at?: string,
|
||||
updated_at?: string
|
||||
comment_id?: number,
|
||||
body?: string,
|
||||
repoId?: string
|
||||
}
|
||||
|
||||
export interface LabelName {
|
||||
current_name: string,
|
||||
name: string
|
||||
}
|
||||
|
||||
export interface CommittersCommentDetails {
|
||||
name: string,
|
||||
id: number,
|
||||
comment_id: number,
|
||||
body: string,
|
||||
created_at: string,
|
||||
updated_at: string
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
import * as core from '@actions/core'
|
||||
import { context } from '@actions/github'
|
||||
const io = require('@actions/io')
|
||||
import { getclas } from './checkcla'
|
||||
import { lockPullRequest } from './pullRequestLock'
|
||||
|
||||
|
||||
|
||||
export async function run() {
|
||||
try {
|
||||
const pullRequestNo: number = context.issue.number
|
||||
//s console.log("the context is " + JSON.stringify(context, null, 2))
|
||||
core.info('CLA Assistant GitHub Action has started')
|
||||
core.info('the PR No is ' + JSON.stringify(pullRequestNo))
|
||||
if (context.payload.action === 'closed') {
|
||||
return lockPullRequest(pullRequestNo)
|
||||
}
|
||||
else {
|
||||
await getclas(pullRequestNo)
|
||||
|
||||
// const rateLimit = await octokit.rateLimit.get()
|
||||
// console.log(JSON.stringify(rateLimit.data.resources, null, 4))
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
core.setFailed(error.message)
|
||||
}
|
||||
|
||||
}
|
||||
run()
|
||||
@@ -0,0 +1,5 @@
|
||||
import { GitHub } from '@actions/github'
|
||||
|
||||
const octokit = new GitHub(process.env.GITHUB_TOKEN as string)
|
||||
|
||||
export default octokit
|
||||
@@ -0,0 +1,176 @@
|
||||
import octokit from './octokit'
|
||||
import * as core from '@actions/core'
|
||||
import { pathToCLADocument } from './url'
|
||||
import { context } from '@actions/github'
|
||||
import signatureWithPRComment from './signatureComment'
|
||||
import { CommitterMap, ReactedCommitterMap, LabelName, CommittersDetails } from './interfaces'
|
||||
|
||||
|
||||
|
||||
async function getComment() {
|
||||
try {
|
||||
const response = await octokit.issues.listComments({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number
|
||||
})
|
||||
|
||||
return response.data.find(comment => comment.body.match(/.*CLA Assistant Lite.*/))
|
||||
} catch (e) {
|
||||
core.setFailed('Error occured when getting all the comments of the pull request: ' + e.message)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function commentContent(signed: boolean, committerMap: CommitterMap): string {
|
||||
const labelName = {} as LabelName
|
||||
if (signed) {
|
||||
labelName.current_name = 'CLA signed :smiley:'
|
||||
//updateLabel(signed, labelName)
|
||||
return `**CLA Assistant Lite** All committers have signed the CLA. :smiley:`
|
||||
}
|
||||
/* TODO: Unhandled Promise Rejection */
|
||||
labelName.current_name = 'CLA Not Signed :worried:'
|
||||
//updateLabel(signed, labelName)
|
||||
let committersCount = 1
|
||||
if (committerMap && committerMap.signed && committerMap.notSigned) {
|
||||
committersCount = committerMap.signed.length + committerMap.notSigned.length
|
||||
}
|
||||
let you = (committersCount > 1 ? 'you all' : 'you')
|
||||
let text = `**CLA Assistant Lite:** <br/>Thank you for your submission, we really appreciate it. Like many open source projects, we ask that ${you} sign our [Contributor License Agreement](${pathToCLADocument()}) before we can accept your contribution.You can sign the CLA by just posting a Pull Request Comment same as the below format.
|
||||
***I have read the CLA Document and I hereby sign the CLA*** <br/><br/>`
|
||||
if (committersCount > 1 && committerMap && committerMap.signed && committerMap.notSigned) {
|
||||
text += `**${committerMap.signed.length}** out of **${committerMap.signed.length + committerMap.notSigned.length}** committers have signed the CLA.`
|
||||
committerMap.signed.forEach((signedCommitter) => {
|
||||
text += `<br/>:white_check_mark: @${signedCommitter.name}`
|
||||
})
|
||||
committerMap.notSigned.forEach((unsignedCommitter) => {
|
||||
text += `<br/>:x: @${unsignedCommitter.name}`
|
||||
})
|
||||
text += '<br/>'
|
||||
}
|
||||
if (committerMap && committerMap.unknown && committerMap.unknown.length > 0) {
|
||||
let seem = (committerMap.unknown.length > 1 ? 'seem' : 'seems')
|
||||
text += `<hr/>**${committerMap.unknown.join(', ')}** ${seem} not to be a GitHub user.`
|
||||
text += ' You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please [add the email address used for this commit to your account](https://help.github.com/articles/why-are-my-commits-linked-to-the-wrong-user/#commits-are-not-linked-to-any-user).<br/>'
|
||||
|
||||
}
|
||||
return text
|
||||
|
||||
}
|
||||
|
||||
function prepareAllSignedCommitters(committerMap: CommitterMap, signedInPrCommitters: CommittersDetails[], committers: CommittersDetails[]): boolean {
|
||||
let allSignedCommitters = [] as CommittersDetails[]
|
||||
/*
|
||||
Reference: https://stackoverflow.com/questions/54134156/javascript-merge-two-arrays-of-objects-only-if-not-duplicate-based-on-specifi
|
||||
merging two arrays if not duplicate. 1) already signed committers in the file 2) signed committers in the PR comment
|
||||
*/
|
||||
let ids = new Set(signedInPrCommitters.map(committer => committer.id))
|
||||
allSignedCommitters = [...signedInPrCommitters, ...committerMap.signed!.filter(signedCommitter => !ids.has(signedCommitter.id))]
|
||||
core.debug("all signed committers after merging " + JSON.stringify(allSignedCommitters, null, 2))
|
||||
|
||||
//checking if all the unsigned committers have reacted to the PR comment (this is needed for changing the content of the PR comment to "All committers have signed the CLA")
|
||||
let allSignedFlag: boolean = committers.every(committer => allSignedCommitters.some(reactedCommitter => committer.id === reactedCommitter.id))
|
||||
return allSignedFlag
|
||||
}
|
||||
|
||||
|
||||
export default async function prComment(signed: boolean, committerMap: CommitterMap, committers: CommittersDetails[], pullRequestNo: number) {
|
||||
try {
|
||||
const prComment = await getComment()
|
||||
if (!prComment) {
|
||||
await octokit.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
body: commentContent(signed, committerMap)
|
||||
})
|
||||
return
|
||||
}
|
||||
else if (prComment && prComment.id) {
|
||||
if (signed) {
|
||||
await octokit.issues.updateComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
comment_id: prComment.id,
|
||||
body: commentContent(signed, committerMap)
|
||||
})
|
||||
|
||||
}
|
||||
const reactedCommitters: ReactedCommitterMap = await signatureWithPRComment(prComment.id, committerMap, committers, pullRequestNo) as ReactedCommitterMap
|
||||
if (reactedCommitters) {
|
||||
if (reactedCommitters.onlyCommitters) {
|
||||
reactedCommitters.allSignedFlag = prepareAllSignedCommitters(committerMap, reactedCommitters.onlyCommitters, committers)
|
||||
}
|
||||
}
|
||||
|
||||
core.debug("allSignedFlag is " + reactedCommitters.allSignedFlag)
|
||||
|
||||
committerMap.signed!.push(...reactedCommitters.newSigned)
|
||||
committerMap.notSigned = committerMap.notSigned!.filter(committer => !reactedCommitters.newSigned.some(reactedCommitter => committer.id === reactedCommitter.id))
|
||||
|
||||
await octokit.issues.updateComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
comment_id: prComment.id,
|
||||
body: commentContent(reactedCommitters.allSignedFlag, committerMap)
|
||||
})
|
||||
return reactedCommitters
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
core.setFailed('Error occured when creating or editing the comments of the pull request: ' + e.message)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// function addLabel() {
|
||||
// return octokit.issues.addLabels({
|
||||
// owner: context.repo.owner,
|
||||
// repo: context.repo.repo,
|
||||
// issue_number: context.issue.number,
|
||||
// labels: ['CLA Not Signed :worried:']
|
||||
// })
|
||||
// }
|
||||
|
||||
// async function updateLabel(signed: boolean, labelName: LabelName) {
|
||||
// try {
|
||||
// const getLabel = await octokit.issues.getLabel({
|
||||
// owner: context.repo.owner,
|
||||
// repo: context.repo.repo,
|
||||
// name: labelName.current_name
|
||||
// })
|
||||
// if (getLabel) {
|
||||
// return
|
||||
// }
|
||||
|
||||
// } catch (error) {
|
||||
// if (error.status === 404) {
|
||||
// if (signed) {
|
||||
// labelName = {
|
||||
// current_name: 'CLA Not Signed :worried:',
|
||||
// name: 'CLA signed :smiley:'
|
||||
// }
|
||||
// }
|
||||
// else {
|
||||
// labelName = {
|
||||
// current_name: 'CLA signed :smiley:',
|
||||
// name: 'CLA Not Signed :worried:'
|
||||
// }
|
||||
// }
|
||||
// return octokit.issues.updateLabel({
|
||||
// owner: context.repo.owner,
|
||||
// repo: context.repo.repo,
|
||||
// current_name: labelName.current_name,
|
||||
// name: labelName.name
|
||||
// })
|
||||
|
||||
// }
|
||||
// core.setFailed("error when creating a label :" + error)
|
||||
|
||||
// }
|
||||
|
||||
|
||||
// }
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import octokit from './octokit'
|
||||
import * as core from '@actions/core'
|
||||
import { context } from '@actions/github'
|
||||
|
||||
export async function lockPullRequest(pullRequestNo: number) {
|
||||
core.info('Locking the Pull Request to safe guard the Pull Request CLA Signatures')
|
||||
try {
|
||||
await octokit.issues.lock(
|
||||
{
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: pullRequestNo
|
||||
}
|
||||
)
|
||||
core.info(`successfully locked the pull request ${pullRequestNo}`)
|
||||
} catch (e) {
|
||||
core.error(`failed when locking the pull request `)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import octokit from './octokit'
|
||||
import { context } from '@actions/github'
|
||||
import { CommitterMap, CommittersDetails, CommentedCommitterMap } from './interfaces'
|
||||
const fetch = require("node-fetch");
|
||||
import * as core from '@actions/core'
|
||||
|
||||
async function webhookSmartContract(newSignedCommitters: CommittersDetails[]) {
|
||||
const blockchainURL = core.getInput('blockchain-webhook-endpoint') || 'https://u9afh6n36g.execute-api.eu-central-1.amazonaws.com/dev/webhook'
|
||||
|
||||
try {
|
||||
const config = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(newSignedCommitters)
|
||||
}
|
||||
const res = await fetch(blockchainURL, config)
|
||||
const response = await res.json()
|
||||
core.debug("the response of the webhook is " + JSON.stringify(response))
|
||||
//const response = await res.json()
|
||||
if (response.success) {
|
||||
core.debug("the response2 of the webhook is " + JSON.stringify(response))
|
||||
//return json
|
||||
return response
|
||||
}
|
||||
} catch (error) {
|
||||
core.setFailed('The webhook post request for storing signatures in smart contract failed' + error)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
export default async function signatureWithPRComment(commentId, committerMap: CommitterMap, committers, pullRequestNo: number) {
|
||||
let blockchainFlag = core.getInput('blockchain-storage-flag')
|
||||
core.debug("the blockchainflag is " + blockchainFlag)
|
||||
let repoId = context.payload.repository!.id
|
||||
let commentedCommitterMap = {} as CommentedCommitterMap
|
||||
let prResponse = await octokit.issues.listComments({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number
|
||||
})
|
||||
let listOfPRComments = [] as CommittersDetails[]
|
||||
let filteredListOfPRComments = [] as CommittersDetails[]
|
||||
|
||||
//TODO: Do null check for repoID
|
||||
prResponse.data.map((prComment) => {
|
||||
listOfPRComments.push({
|
||||
name: prComment.user.login,
|
||||
id: prComment.user.id,
|
||||
comment_id: prComment.id,
|
||||
body: prComment.body.toLowerCase(),
|
||||
created_at: prComment.created_at,
|
||||
repoId: repoId,
|
||||
pullRequestNo: pullRequestNo
|
||||
})
|
||||
})
|
||||
|
||||
listOfPRComments.map((comment) => {
|
||||
if (comment.body!.match(/^.*i \s*have \s*read \s*the \s*cla \s*document \s*and \s*i \s*hereby \s*sign \s*the \s*cla.*$/) && comment.name !== 'github-actions[bot]') {
|
||||
filteredListOfPRComments.push(comment)
|
||||
}
|
||||
})
|
||||
|
||||
for (var i = 0; i < filteredListOfPRComments.length; i++) {
|
||||
delete filteredListOfPRComments[i].body
|
||||
}
|
||||
// //checking if the reacted committers are not the signed committers(not in the storage file) and filtering only the unsigned committers
|
||||
commentedCommitterMap.newSigned = filteredListOfPRComments.filter(commentedCommitter => committerMap.notSigned!.some(notSignedCommitter => commentedCommitter.id === notSignedCommitter.id))
|
||||
|
||||
core.debug("the new commented committers(signed) are :" + JSON.stringify(commentedCommitterMap.newSigned, null, 3))
|
||||
if (blockchainFlag == 'true' && commentedCommitterMap.newSigned) {
|
||||
await webhookSmartContract(commentedCommitterMap.newSigned)
|
||||
}
|
||||
|
||||
|
||||
//checking if the commented users are only the contributors who has committed in the same PR (This is needed for the PR Comment and changing the status to success when all the contributors has reacted to the PR)
|
||||
commentedCommitterMap.onlyCommitters = committers.filter(committer => filteredListOfPRComments.some(commentedCommitter => committer.id == commentedCommitter.id))
|
||||
|
||||
core.debug("the reacted signed committers comments are " + JSON.stringify(commentedCommitterMap.onlyCommitters, null, 3))
|
||||
|
||||
|
||||
return commentedCommitterMap
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import * as core from '@actions/core'
|
||||
|
||||
|
||||
export function pathToCLADocument() {
|
||||
const pathToCLADocument = core.getInput('path-To-cladocument')
|
||||
return pathToCLADocument
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Basic Options */
|
||||
// "incremental": true, /* Enable incremental compilation */
|
||||
"target": "es6",
|
||||
/* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
|
||||
"module": "commonjs",
|
||||
/* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
|
||||
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||
// "checkJs": true, /* Report errors in .js files. */
|
||||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
|
||||
// "declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||
// "sourceMap": true, /* Generates corresponding '.map' file. */
|
||||
// "outFile": "./", /* Concatenate and emit output to single file. */
|
||||
"outDir": "./lib",
|
||||
/* Redirect output structure to the directory. */
|
||||
"rootDir": "./src",
|
||||
/* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||
// "composite": true, /* Enable project compilation */
|
||||
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
||||
// "removeComments": true, /* Do not emit comments to output. */
|
||||
// "noEmit": true, /* Do not emit outputs. */
|
||||
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||
|
||||
/* Strict Type-Checking Options */
|
||||
"strict": true,
|
||||
/* Enable all strict type-checking options. */
|
||||
"noImplicitAny": false,
|
||||
/* Raise error on expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
||||
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||
|
||||
/* Additional Checks */
|
||||
// "noUnusedLocals": true, /* Report errors on unused locals. */
|
||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
|
||||
/* Module Resolution Options */
|
||||
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||
// "types": [], /* Type declaration files to be included in compilation. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
|
||||
/* Source Map Options */
|
||||
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||
|
||||
/* Experimental Options */
|
||||
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||
},
|
||||
"exclude": ["node_modules", "**/*.test.ts"]
|
||||
}
|
||||
Reference in New Issue
Block a user