├── .eslintrc.js ├── .github ├── CODEOWNERS └── workflows │ ├── ci.yml │ ├── link_check.yml │ └── security_scan.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── __mocks__ ├── .DS_Store ├── @actions │ └── core.ts └── @octokitasdf │ └── rest.ts ├── action.yml ├── dist ├── index.js ├── index.js.map └── sourcemap-register.js ├── git-hooks └── pre-commit ├── images ├── comment.png └── status_check.png ├── package-lock.json ├── package.json ├── src ├── actions │ ├── actions-module.ts │ └── pull-request-action.ts ├── analysis.ts ├── api │ ├── apis-module.ts │ ├── configuration.ts │ ├── handler.ts │ ├── logic.ts │ ├── multi-inject-provider.ts │ └── pull-request-listener.ts ├── generated │ └── inputs-outputs.ts ├── github │ └── octokit-builder.ts ├── handler │ ├── handlers-module.ts │ └── pull-request-handler.ts ├── helpers │ ├── add-comment-helper.ts │ ├── add-status-check-helper.ts │ ├── helpers-module.ts │ └── update-comment-helper.ts ├── index.ts ├── inversify-binding.ts ├── logic │ ├── handle-pull-request-logic.ts │ └── logic-module.ts └── main.ts ├── tests ├── .DS_Store ├── _data │ └── pull_request │ │ └── opened │ │ ├── create-pr-source-head-branch-same-repo.json │ │ └── create-pr.json ├── actions │ └── pull-request-action.spec.ts ├── analysis.spec.ts ├── handler │ └── pull-request-handler.spec.ts ├── helpers │ ├── add-comment-helper.spec.ts │ ├── add-status-check-helper.spec.ts │ └── update-comment-helper.spec.ts ├── index.spec.ts ├── inversify-binding.spec.ts ├── logic │ └── handle-pull-request-logic.spec.ts └── main.spec.ts ├── tsconfig.build.json └── tsconfig.json /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | "@redhat-actions/eslint-config", 4 | ], 5 | ignorePatterns: [ 6 | "__mocks__/", 7 | "coverage/" 8 | ], 9 | 10 | rules: { 11 | "class-methods-use-this": "off" 12 | }, 13 | overrides: [{ 14 | files: ["*.spec.ts"], // test files 15 | rules: { 16 | "@typescript-eslint/no-floating-promises": "off", 17 | "dot-notation": "off" 18 | }, 19 | }], 20 | }; 21 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Global Owners 2 | * @ibuziuk @dkwon17 @l0rd 3 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI Checks 2 | on: 3 | push: 4 | pull_request: 5 | workflow_dispatch: 6 | schedule: 7 | - cron: '0 0 * * *' # every day at midnight 8 | 9 | jobs: 10 | lint: 11 | name: Run ESLint 12 | runs-on: ubuntu-20.04 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - run: npm ci 17 | - run: npm run lint 18 | 19 | test: 20 | name: Run tests 21 | runs-on: ubuntu-20.04 22 | 23 | steps: 24 | - uses: actions/checkout@v2 25 | - run: npm ci 26 | - run: npm test 27 | 28 | check-dist: 29 | name: Check Distribution 30 | runs-on: ubuntu-20.04 31 | env: 32 | BUNDLE_FILE: "dist/index.js" 33 | BUNDLE_COMMAND: "npm run bundle" 34 | steps: 35 | - uses: actions/checkout@v2 36 | 37 | - uses: actions/setup-node@v3 38 | with: 39 | node-version: 16 40 | 41 | - name: Install 42 | run: npm ci 43 | 44 | - name: Verify Latest Bundle 45 | uses: redhat-actions/common/bundle-verifier@v1 46 | with: 47 | bundle_file: ${{ env.BUNDLE_FILE }} 48 | bundle_command: ${{ env.BUNDLE_COMMAND }} 49 | 50 | check-inputs-outputs: 51 | name: Check Input and Output enums 52 | runs-on: ubuntu-20.04 53 | env: 54 | IO_FILE: ./src/generated/inputs-outputs.ts 55 | steps: 56 | - uses: actions/checkout@v2 57 | 58 | - name: Install dependencies 59 | run: npm ci 60 | 61 | - name: Verify Input and Output enums 62 | uses: redhat-actions/common/action-io-generator@v1 63 | with: 64 | io_file: ${{ env.IO_FILE }} 65 | -------------------------------------------------------------------------------- /.github/workflows/link_check.yml: -------------------------------------------------------------------------------- 1 | name: Link checker 2 | on: 3 | push: 4 | paths: 5 | - '**.md' 6 | pull_request: 7 | paths: 8 | - '**.md' 9 | schedule: 10 | - cron: '0 0 * * *' # every day at midnight 11 | 12 | jobs: 13 | markdown-link-check: 14 | name: Check links in markdown 15 | runs-on: ubuntu-20.04 16 | steps: 17 | - uses: actions/checkout@v2 18 | - uses: gaurav-nelson/github-action-markdown-link-check@v1 19 | with: 20 | use-verbose-mode: true 21 | -------------------------------------------------------------------------------- /.github/workflows/security_scan.yml: -------------------------------------------------------------------------------- 1 | name: Vulnerability Scan with CRDA 2 | on: 3 | push: 4 | workflow_dispatch: 5 | pull_request_target: 6 | types: [ assigned, opened, synchronize, reopened, labeled, edited ] 7 | schedule: 8 | - cron: '0 0 * * *' # every day at midnight 9 | 10 | jobs: 11 | crda-scan: 12 | runs-on: ubuntu-20.04 13 | name: Scan project vulnerability with CRDA 14 | steps: 15 | 16 | - uses: actions/checkout@v2 17 | 18 | - name: Setup Node 19 | uses: actions/setup-node@v2 20 | with: 21 | node-version: '16' 22 | 23 | - name: Install CRDA 24 | uses: redhat-actions/openshift-tools-installer@v1 25 | with: 26 | source: github 27 | github_pat: ${{ github.token }} 28 | crda: "latest" 29 | 30 | - name: CRDA Scan 31 | id: scan 32 | uses: redhat-actions/crda@v1 33 | with: 34 | crda_key: ${{ secrets.CRDA_KEY }} 35 | fail_on: never 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage 2 | .intellij 3 | .eclipse 4 | .vscode 5 | node_modules 6 | out 7 | yarn-error.log 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # try-in-web-ide Changelog 2 | 3 | ## v1.4.2 4 | - [Try to update earliest matching comment](https://github.com/redhat-actions/try-in-web-ide/pull/37) 5 | 6 | ## v1.4.1 7 | - [Allow detecting and updating existing badge comments made by bots other than the github-actions bot](https://github.com/redhat-actions/try-in-web-ide/pull/29) 8 | 9 | ## v1.4 10 | - [Update the existing comment instead of adding a new one](https://github.com/redhat-actions/try-in-web-ide/issues/13) 11 | 12 | ## v1.3 13 | - Introduce new action input `setup_remotes` 14 | 15 | ## v1.2 16 | - Update action to run on Node16. https://github.blog/changelog/2022-05-20-actions-can-now-run-in-a-node-js-16-runtime/ 17 | 18 | ## v1.1 19 | - Update pull request comment and status check text 20 | - Listen for `pull_request_target` events instead of `pull_request` events to support pull requests from forks 21 | 22 | ## v1 23 | - Initial Release 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Red Hat. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Try in Web IDE 2 | 3 | [![CI Checks](https://github.com/redhat-actions/try-in-web-ide/actions/workflows/ci.yml/badge.svg)](https://github.com/redhat-actions/try-in-web-ide/actions/workflows/ci.yml) 4 | [![Contribute](https://www.eclipse.org/che/contribute.svg)](https://workspaces.openshift.com#https://github.com/redhat-actions/try-in-web-ide) 5 | 6 | GitHub action will add a comment and/or status check with a link to open the project on an online web IDE instance. 7 | 8 | #### Adding a link in PR status check: 9 | ![Status check screenshot](images/status_check.png) 10 | 11 | #### Adding a link in PR comment: 12 | ![Comment screenshot](images/comment.png) 13 | 14 | # Usage 15 | ```yaml 16 | # Add Web IDE link on PRs 17 | name: web-ide 18 | 19 | on: 20 | pull_request_target: 21 | types: [opened, synchronize] 22 | 23 | jobs: 24 | add-link: 25 | runs-on: ubuntu-20.04 26 | steps: 27 | - name: Web IDE Pull Request Check 28 | id: try-in-web-ide 29 | uses: redhat-actions/try-in-web-ide@v1 30 | with: 31 | github_token: ${{ secrets.GITHUB_TOKEN }} 32 | ``` 33 | 34 | ## Action Inputs 35 | 36 | | Input Name | Description | Default | 37 | | ---------- | ----------- | ------- | 38 | | github_token | GitHub token used to add PR comment and/or status check. | **Must be provided** 39 | | add_comment | If `true`, the action will add comments on each PR with a link to try the PR in Web IDE. | `true` 40 | | add_status | If `true`, the action will add a PR status check on each PR with a link to try the PR in Web IDE. | `true` 41 | | setup_remotes | If `true`, the badge and status check URL created by the action will have Git remotes automatically configured if the PR branch is located in a fork repo. The fork repo and the base repo will be configured to be the `origin` and `upstream` remote respectively. The Web IDE must be based on Eclipse Che® 7.60 for this feature. | `false` 42 | | web_ide_instance | The base url for the Web IDE instance. | `https://workspaces.openshift.com` 43 | | comment_badge | The badge url for the comment created when `add_comment` is `true`. | `https://img.shields.io/badge/Eclipse_Che-Hosted%20by%20Red%20Hat-525C86?logo=eclipse-che&labelColor=FDB940` 44 | 45 | # Scenarios 46 | - [Disable comment on pull requests](#add-comment-on-pull-requests) 47 | - [Disable status check on pull requests](#disable-status-check-on-pull-requests) 48 | - [Customize the link to online Web IDE instance](#customize-the-link-to-online-web-ide-instance) 49 | 50 | ## Add comment on pull requests 51 | 52 | ```yaml 53 | - name: Web IDE Pull Request Check 54 | id: try-in-web-ide 55 | uses: redhat-actions/try-in-web-ide@v1 56 | with: 57 | github_token: ${{ secrets.GITHUB_TOKEN }} 58 | add_comment: false 59 | ``` 60 | 61 | ## Disable status check on pull requests 62 | 63 | ```yaml 64 | - name: Web IDE Pull Request Check 65 | id: try-in-web-ide 66 | uses: redhat-actions/try-in-web-ide@v1 67 | with: 68 | github_token: ${{ secrets.GITHUB_TOKEN }} 69 | add_status: false 70 | ``` 71 | 72 | ## Customize the link to online Web IDE instance 73 | 74 | ```yaml 75 | - name: Web IDE Pull Request Check 76 | id: try-in-web-ide 77 | uses: redhat-actions/try-in-web-ide@v1 78 | with: 79 | github_token: ${{ secrets.GITHUB_TOKEN }} 80 | web_ide_instance: https://my-online-ide-instance.com 81 | ``` 82 | 83 | # Contributing 84 | ## Running the linter 85 | ``` 86 | npm run lint 87 | ``` 88 | ## Generating `dist/index.js` 89 | ``` 90 | npm run bundle 91 | ``` 92 | ## Updating the GitHub action's input and output 93 | After updating `action.yml`, run: 94 | ``` 95 | npx action-io-generator -o src/generated/inputs-outputs.ts 96 | ``` 97 | ## Running tests 98 | ``` 99 | npm run test 100 | ``` -------------------------------------------------------------------------------- /__mocks__/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redhat-actions/try-in-web-ide/11cfde83a356e5b6086bd67a9c5c135037317e4b/__mocks__/.DS_Store -------------------------------------------------------------------------------- /__mocks__/@actions/core.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 2 | const core: any = jest.genMockFromModule('@actions/core'); 3 | 4 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 5 | const inputs: Map = new Map(); 6 | 7 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 8 | function __setInput(inputName: string, inputValue: any): void { 9 | inputs.set(inputName, inputValue); 10 | } 11 | 12 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 13 | function getInput(paramName: string): any { 14 | return inputs.get(paramName); 15 | } 16 | 17 | core.getInput = getInput; 18 | core.__setInput = __setInput; 19 | 20 | module.exports = core; 21 | -------------------------------------------------------------------------------- /__mocks__/@octokitasdf/rest.ts: -------------------------------------------------------------------------------- 1 | // const Octokit: any = { 2 | // issues: { 3 | // createComment: jest.fn().mockImplementation((_: any) => { 4 | // return Promise.resolve; 5 | // }), 6 | // }, 7 | // }; 8 | 9 | // module.exports = Octokit; -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: Try in Web IDE 2 | description: Try in Web IDE 3 | author: 'Red Hat' 4 | branding: 5 | icon: circle 6 | color: red 7 | 8 | inputs: 9 | github_token: 10 | description: 'GitHub token used to add PR comment and/or status check' 11 | required: true 12 | add_comment: 13 | description: If 'true', the action will add comments on each PR with a link to try the PR in Web IDE 14 | required: false 15 | default: 'true' 16 | add_status: 17 | description: If 'true', the action will add a PR status check on each PR with a link to try the PR in Web IDE 18 | required: false 19 | default: 'true' 20 | setup_remotes: 21 | description: > 22 | If 'true', the badge and status check URL created by the action will have Git remotes automatically configured 23 | 24 | if the PR branch is located in a fork repo. 25 | 26 | The fork repo and the base repo will be configured to be the 'origin' and 'upstream' remote respectively. 27 | 28 | The Web IDE must be based on Eclipse Che® 7.60 for this feature. 29 | required: false 30 | default: 'false' 31 | web_ide_instance: 32 | description: The base url for the Web IDE instance 33 | required: false 34 | default: 'https://workspaces.openshift.com' 35 | comment_badge: 36 | description: The badge url for the comment created when 'add_comment' is 'true' 37 | required: false 38 | default: 'https://img.shields.io/badge/Eclipse_Che-Hosted%20by%20Red%20Hat-525C86?logo=eclipse-che&labelColor=FDB940' 39 | 40 | runs: 41 | using: node16 42 | main: dist/index.js 43 | -------------------------------------------------------------------------------- /dist/sourcemap-register.js: -------------------------------------------------------------------------------- 1 | (()=>{var e={650:e=>{var r=Object.prototype.toString;var n=typeof Buffer.alloc==="function"&&typeof Buffer.allocUnsafe==="function"&&typeof Buffer.from==="function";function isArrayBuffer(e){return r.call(e).slice(8,-1)==="ArrayBuffer"}function fromArrayBuffer(e,r,t){r>>>=0;var o=e.byteLength-r;if(o<0){throw new RangeError("'offset' is out of bounds")}if(t===undefined){t=o}else{t>>>=0;if(t>o){throw new RangeError("'length' is out of bounds")}}return n?Buffer.from(e.slice(r,r+t)):new Buffer(new Uint8Array(e.slice(r,r+t)))}function fromString(e,r){if(typeof r!=="string"||r===""){r="utf8"}if(!Buffer.isEncoding(r)){throw new TypeError('"encoding" must be a valid string encoding')}return n?Buffer.from(e,r):new Buffer(e,r)}function bufferFrom(e,r,t){if(typeof e==="number"){throw new TypeError('"value" argument must not be a number')}if(isArrayBuffer(e)){return fromArrayBuffer(e,r,t)}if(typeof e==="string"){return fromString(e,r)}return n?Buffer.from(e):new Buffer(e)}e.exports=bufferFrom},284:(e,r,n)=>{e=n.nmd(e);var t=n(596).SourceMapConsumer;var o=n(622);var i;try{i=n(747);if(!i.existsSync||!i.readFileSync){i=null}}catch(e){}var a=n(650);function dynamicRequire(e,r){return e.require(r)}var u=false;var s=false;var l=false;var c="auto";var p={};var f={};var g=/^data:application\/json[^,]+base64,/;var h=[];var d=[];function isInBrowser(){if(c==="browser")return true;if(c==="node")return false;return typeof window!=="undefined"&&typeof XMLHttpRequest==="function"&&!(window.require&&window.module&&window.process&&window.process.type==="renderer")}function hasGlobalProcessEventEmitter(){return typeof process==="object"&&process!==null&&typeof process.on==="function"}function handlerExec(e){return function(r){for(var n=0;n"}var n=this.getLineNumber();if(n!=null){r+=":"+n;var t=this.getColumnNumber();if(t){r+=":"+t}}}var o="";var i=this.getFunctionName();var a=true;var u=this.isConstructor();var s=!(this.isToplevel()||u);if(s){var l=this.getTypeName();if(l==="[object Object]"){l="null"}var c=this.getMethodName();if(i){if(l&&i.indexOf(l)!=0){o+=l+"."}o+=i;if(c&&i.indexOf("."+c)!=i.length-c.length-1){o+=" [as "+c+"]"}}else{o+=l+"."+(c||"")}}else if(u){o+="new "+(i||"")}else if(i){o+=i}else{o+=r;a=false}if(a){o+=" ("+r+")"}return o}function cloneCallSite(e){var r={};Object.getOwnPropertyNames(Object.getPrototypeOf(e)).forEach((function(n){r[n]=/^(?:is|get)/.test(n)?function(){return e[n].call(e)}:e[n]}));r.toString=CallSiteToString;return r}function wrapCallSite(e,r){if(r===undefined){r={nextPosition:null,curPosition:null}}if(e.isNative()){r.curPosition=null;return e}var n=e.getFileName()||e.getScriptNameOrSourceURL();if(n){var t=e.getLineNumber();var o=e.getColumnNumber()-1;var i=/^v(10\.1[6-9]|10\.[2-9][0-9]|10\.[0-9]{3,}|1[2-9]\d*|[2-9]\d|\d{3,}|11\.11)/;var a=i.test(process.version)?0:62;if(t===1&&o>a&&!isInBrowser()&&!e.isEval()){o-=a}var u=mapSourcePosition({source:n,line:t,column:o});r.curPosition=u;e=cloneCallSite(e);var s=e.getFunctionName;e.getFunctionName=function(){if(r.nextPosition==null){return s()}return r.nextPosition.name||s()};e.getFileName=function(){return u.source};e.getLineNumber=function(){return u.line};e.getColumnNumber=function(){return u.column+1};e.getScriptNameOrSourceURL=function(){return u.source};return e}var l=e.isEval()&&e.getEvalOrigin();if(l){l=mapEvalOrigin(l);e=cloneCallSite(e);e.getEvalOrigin=function(){return l};return e}return e}function prepareStackTrace(e,r){if(l){p={};f={}}var n=e.name||"Error";var t=e.message||"";var o=n+": "+t;var i={nextPosition:null,curPosition:null};var a=[];for(var u=r.length-1;u>=0;u--){a.push("\n at "+wrapCallSite(r[u],i));i.nextPosition=i.curPosition}i.curPosition=i.nextPosition=null;return o+a.reverse().join("")}function getErrorSource(e){var r=/\n at [^(]+ \((.*):(\d+):(\d+)\)/.exec(e.stack);if(r){var n=r[1];var t=+r[2];var o=+r[3];var a=p[n];if(!a&&i&&i.existsSync(n)){try{a=i.readFileSync(n,"utf8")}catch(e){a=""}}if(a){var u=a.split(/(?:\r\n|\r|\n)/)[t-1];if(u){return n+":"+t+"\n"+u+"\n"+new Array(o).join(" ")+"^"}}}return null}function printErrorAndExit(e){var r=getErrorSource(e);if(process.stderr._handle&&process.stderr._handle.setBlocking){process.stderr._handle.setBlocking(true)}if(r){console.error();console.error(r)}console.error(e.stack);process.exit(1)}function shimEmitUncaughtException(){var e=process.emit;process.emit=function(r){if(r==="uncaughtException"){var n=arguments[1]&&arguments[1].stack;var t=this.listeners(r).length>0;if(n&&!t){return printErrorAndExit(arguments[1])}}return e.apply(this,arguments)}}var S=h.slice(0);var _=d.slice(0);r.wrapCallSite=wrapCallSite;r.getErrorSource=getErrorSource;r.mapSourcePosition=mapSourcePosition;r.retrieveSourceMap=v;r.install=function(r){r=r||{};if(r.environment){c=r.environment;if(["node","browser","auto"].indexOf(c)===-1){throw new Error("environment "+c+" was unknown. Available options are {auto, browser, node}")}}if(r.retrieveFile){if(r.overrideRetrieveFile){h.length=0}h.unshift(r.retrieveFile)}if(r.retrieveSourceMap){if(r.overrideRetrieveSourceMap){d.length=0}d.unshift(r.retrieveSourceMap)}if(r.hookRequire&&!isInBrowser()){var n=dynamicRequire(e,"module");var t=n.prototype._compile;if(!t.__sourceMapSupport){n.prototype._compile=function(e,r){p[r]=e;f[r]=undefined;return t.call(this,e,r)};n.prototype._compile.__sourceMapSupport=true}}if(!l){l="emptyCacheBetweenOperations"in r?r.emptyCacheBetweenOperations:false}if(!u){u=true;Error.prepareStackTrace=prepareStackTrace}if(!s){var o="handleUncaughtExceptions"in r?r.handleUncaughtExceptions:true;try{var i=dynamicRequire(e,"worker_threads");if(i.isMainThread===false){o=false}}catch(e){}if(o&&hasGlobalProcessEventEmitter()){s=true;shimEmitUncaughtException()}}};r.resetRetrieveHandlers=function(){h.length=0;d.length=0;h=S.slice(0);d=_.slice(0);v=handlerExec(d);m=handlerExec(h)}},837:(e,r,n)=>{var t=n(983);var o=Object.prototype.hasOwnProperty;var i=typeof Map!=="undefined";function ArraySet(){this._array=[];this._set=i?new Map:Object.create(null)}ArraySet.fromArray=function ArraySet_fromArray(e,r){var n=new ArraySet;for(var t=0,o=e.length;t=0){return r}}else{var n=t.toSetString(e);if(o.call(this._set,n)){return this._set[n]}}throw new Error('"'+e+'" is not in the set.')};ArraySet.prototype.at=function ArraySet_at(e){if(e>=0&&e{var t=n(537);var o=5;var i=1<>1;return r?-n:n}r.encode=function base64VLQ_encode(e){var r="";var n;var i=toVLQSigned(e);do{n=i&a;i>>>=o;if(i>0){n|=u}r+=t.encode(n)}while(i>0);return r};r.decode=function base64VLQ_decode(e,r,n){var i=e.length;var s=0;var l=0;var c,p;do{if(r>=i){throw new Error("Expected more digits in base 64 VLQ value.")}p=t.decode(e.charCodeAt(r++));if(p===-1){throw new Error("Invalid base64 digit: "+e.charAt(r-1))}c=!!(p&u);p&=a;s=s+(p<{var n="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".split("");r.encode=function(e){if(0<=e&&e{r.GREATEST_LOWER_BOUND=1;r.LEAST_UPPER_BOUND=2;function recursiveSearch(e,n,t,o,i,a){var u=Math.floor((n-e)/2)+e;var s=i(t,o[u],true);if(s===0){return u}else if(s>0){if(n-u>1){return recursiveSearch(u,n,t,o,i,a)}if(a==r.LEAST_UPPER_BOUND){return n1){return recursiveSearch(e,u,t,o,i,a)}if(a==r.LEAST_UPPER_BOUND){return u}else{return e<0?-1:e}}}r.search=function search(e,n,t,o){if(n.length===0){return-1}var i=recursiveSearch(-1,n.length,e,n,t,o||r.GREATEST_LOWER_BOUND);if(i<0){return-1}while(i-1>=0){if(t(n[i],n[i-1],true)!==0){break}--i}return i}},740:(e,r,n)=>{var t=n(983);function generatedPositionAfter(e,r){var n=e.generatedLine;var o=r.generatedLine;var i=e.generatedColumn;var a=r.generatedColumn;return o>n||o==n&&a>=i||t.compareByGeneratedPositionsInflated(e,r)<=0}function MappingList(){this._array=[];this._sorted=true;this._last={generatedLine:-1,generatedColumn:0}}MappingList.prototype.unsortedForEach=function MappingList_forEach(e,r){this._array.forEach(e,r)};MappingList.prototype.add=function MappingList_add(e){if(generatedPositionAfter(this._last,e)){this._last=e;this._array.push(e)}else{this._sorted=false;this._array.push(e)}};MappingList.prototype.toArray=function MappingList_toArray(){if(!this._sorted){this._array.sort(t.compareByGeneratedPositionsInflated);this._sorted=true}return this._array};r.H=MappingList},226:(e,r)=>{function swap(e,r,n){var t=e[r];e[r]=e[n];e[n]=t}function randomIntInRange(e,r){return Math.round(e+Math.random()*(r-e))}function doQuickSort(e,r,n,t){if(n{var t;var o=n(983);var i=n(164);var a=n(837).I;var u=n(215);var s=n(226).U;function SourceMapConsumer(e,r){var n=e;if(typeof e==="string"){n=o.parseSourceMapInput(e)}return n.sections!=null?new IndexedSourceMapConsumer(n,r):new BasicSourceMapConsumer(n,r)}SourceMapConsumer.fromSourceMap=function(e,r){return BasicSourceMapConsumer.fromSourceMap(e,r)};SourceMapConsumer.prototype._version=3;SourceMapConsumer.prototype.__generatedMappings=null;Object.defineProperty(SourceMapConsumer.prototype,"_generatedMappings",{configurable:true,enumerable:true,get:function(){if(!this.__generatedMappings){this._parseMappings(this._mappings,this.sourceRoot)}return this.__generatedMappings}});SourceMapConsumer.prototype.__originalMappings=null;Object.defineProperty(SourceMapConsumer.prototype,"_originalMappings",{configurable:true,enumerable:true,get:function(){if(!this.__originalMappings){this._parseMappings(this._mappings,this.sourceRoot)}return this.__originalMappings}});SourceMapConsumer.prototype._charIsMappingSeparator=function SourceMapConsumer_charIsMappingSeparator(e,r){var n=e.charAt(r);return n===";"||n===","};SourceMapConsumer.prototype._parseMappings=function SourceMapConsumer_parseMappings(e,r){throw new Error("Subclasses must implement _parseMappings")};SourceMapConsumer.GENERATED_ORDER=1;SourceMapConsumer.ORIGINAL_ORDER=2;SourceMapConsumer.GREATEST_LOWER_BOUND=1;SourceMapConsumer.LEAST_UPPER_BOUND=2;SourceMapConsumer.prototype.eachMapping=function SourceMapConsumer_eachMapping(e,r,n){var t=r||null;var i=n||SourceMapConsumer.GENERATED_ORDER;var a;switch(i){case SourceMapConsumer.GENERATED_ORDER:a=this._generatedMappings;break;case SourceMapConsumer.ORIGINAL_ORDER:a=this._originalMappings;break;default:throw new Error("Unknown order of iteration.")}var u=this.sourceRoot;a.map((function(e){var r=e.source===null?null:this._sources.at(e.source);r=o.computeSourceURL(u,r,this._sourceMapURL);return{source:r,generatedLine:e.generatedLine,generatedColumn:e.generatedColumn,originalLine:e.originalLine,originalColumn:e.originalColumn,name:e.name===null?null:this._names.at(e.name)}}),this).forEach(e,t)};SourceMapConsumer.prototype.allGeneratedPositionsFor=function SourceMapConsumer_allGeneratedPositionsFor(e){var r=o.getArg(e,"line");var n={source:o.getArg(e,"source"),originalLine:r,originalColumn:o.getArg(e,"column",0)};n.source=this._findSourceIndex(n.source);if(n.source<0){return[]}var t=[];var a=this._findMapping(n,this._originalMappings,"originalLine","originalColumn",o.compareByOriginalPositions,i.LEAST_UPPER_BOUND);if(a>=0){var u=this._originalMappings[a];if(e.column===undefined){var s=u.originalLine;while(u&&u.originalLine===s){t.push({line:o.getArg(u,"generatedLine",null),column:o.getArg(u,"generatedColumn",null),lastColumn:o.getArg(u,"lastGeneratedColumn",null)});u=this._originalMappings[++a]}}else{var l=u.originalColumn;while(u&&u.originalLine===r&&u.originalColumn==l){t.push({line:o.getArg(u,"generatedLine",null),column:o.getArg(u,"generatedColumn",null),lastColumn:o.getArg(u,"lastGeneratedColumn",null)});u=this._originalMappings[++a]}}}return t};r.SourceMapConsumer=SourceMapConsumer;function BasicSourceMapConsumer(e,r){var n=e;if(typeof e==="string"){n=o.parseSourceMapInput(e)}var t=o.getArg(n,"version");var i=o.getArg(n,"sources");var u=o.getArg(n,"names",[]);var s=o.getArg(n,"sourceRoot",null);var l=o.getArg(n,"sourcesContent",null);var c=o.getArg(n,"mappings");var p=o.getArg(n,"file",null);if(t!=this._version){throw new Error("Unsupported version: "+t)}if(s){s=o.normalize(s)}i=i.map(String).map(o.normalize).map((function(e){return s&&o.isAbsolute(s)&&o.isAbsolute(e)?o.relative(s,e):e}));this._names=a.fromArray(u.map(String),true);this._sources=a.fromArray(i,true);this._absoluteSources=this._sources.toArray().map((function(e){return o.computeSourceURL(s,e,r)}));this.sourceRoot=s;this.sourcesContent=l;this._mappings=c;this._sourceMapURL=r;this.file=p}BasicSourceMapConsumer.prototype=Object.create(SourceMapConsumer.prototype);BasicSourceMapConsumer.prototype.consumer=SourceMapConsumer;BasicSourceMapConsumer.prototype._findSourceIndex=function(e){var r=e;if(this.sourceRoot!=null){r=o.relative(this.sourceRoot,r)}if(this._sources.has(r)){return this._sources.indexOf(r)}var n;for(n=0;n1){v.source=l+_[1];l+=_[1];v.originalLine=i+_[2];i=v.originalLine;v.originalLine+=1;v.originalColumn=a+_[3];a=v.originalColumn;if(_.length>4){v.name=c+_[4];c+=_[4]}}m.push(v);if(typeof v.originalLine==="number"){d.push(v)}}}s(m,o.compareByGeneratedPositionsDeflated);this.__generatedMappings=m;s(d,o.compareByOriginalPositions);this.__originalMappings=d};BasicSourceMapConsumer.prototype._findMapping=function SourceMapConsumer_findMapping(e,r,n,t,o,a){if(e[n]<=0){throw new TypeError("Line must be greater than or equal to 1, got "+e[n])}if(e[t]<0){throw new TypeError("Column must be greater than or equal to 0, got "+e[t])}return i.search(e,r,o,a)};BasicSourceMapConsumer.prototype.computeColumnSpans=function SourceMapConsumer_computeColumnSpans(){for(var e=0;e=0){var t=this._generatedMappings[n];if(t.generatedLine===r.generatedLine){var i=o.getArg(t,"source",null);if(i!==null){i=this._sources.at(i);i=o.computeSourceURL(this.sourceRoot,i,this._sourceMapURL)}var a=o.getArg(t,"name",null);if(a!==null){a=this._names.at(a)}return{source:i,line:o.getArg(t,"originalLine",null),column:o.getArg(t,"originalColumn",null),name:a}}}return{source:null,line:null,column:null,name:null}};BasicSourceMapConsumer.prototype.hasContentsOfAllSources=function BasicSourceMapConsumer_hasContentsOfAllSources(){if(!this.sourcesContent){return false}return this.sourcesContent.length>=this._sources.size()&&!this.sourcesContent.some((function(e){return e==null}))};BasicSourceMapConsumer.prototype.sourceContentFor=function SourceMapConsumer_sourceContentFor(e,r){if(!this.sourcesContent){return null}var n=this._findSourceIndex(e);if(n>=0){return this.sourcesContent[n]}var t=e;if(this.sourceRoot!=null){t=o.relative(this.sourceRoot,t)}var i;if(this.sourceRoot!=null&&(i=o.urlParse(this.sourceRoot))){var a=t.replace(/^file:\/\//,"");if(i.scheme=="file"&&this._sources.has(a)){return this.sourcesContent[this._sources.indexOf(a)]}if((!i.path||i.path=="/")&&this._sources.has("/"+t)){return this.sourcesContent[this._sources.indexOf("/"+t)]}}if(r){return null}else{throw new Error('"'+t+'" is not in the SourceMap.')}};BasicSourceMapConsumer.prototype.generatedPositionFor=function SourceMapConsumer_generatedPositionFor(e){var r=o.getArg(e,"source");r=this._findSourceIndex(r);if(r<0){return{line:null,column:null,lastColumn:null}}var n={source:r,originalLine:o.getArg(e,"line"),originalColumn:o.getArg(e,"column")};var t=this._findMapping(n,this._originalMappings,"originalLine","originalColumn",o.compareByOriginalPositions,o.getArg(e,"bias",SourceMapConsumer.GREATEST_LOWER_BOUND));if(t>=0){var i=this._originalMappings[t];if(i.source===n.source){return{line:o.getArg(i,"generatedLine",null),column:o.getArg(i,"generatedColumn",null),lastColumn:o.getArg(i,"lastGeneratedColumn",null)}}}return{line:null,column:null,lastColumn:null}};t=BasicSourceMapConsumer;function IndexedSourceMapConsumer(e,r){var n=e;if(typeof e==="string"){n=o.parseSourceMapInput(e)}var t=o.getArg(n,"version");var i=o.getArg(n,"sections");if(t!=this._version){throw new Error("Unsupported version: "+t)}this._sources=new a;this._names=new a;var u={line:-1,column:0};this._sections=i.map((function(e){if(e.url){throw new Error("Support for url field in sections not implemented.")}var n=o.getArg(e,"offset");var t=o.getArg(n,"line");var i=o.getArg(n,"column");if(t{var t=n(215);var o=n(983);var i=n(837).I;var a=n(740).H;function SourceMapGenerator(e){if(!e){e={}}this._file=o.getArg(e,"file",null);this._sourceRoot=o.getArg(e,"sourceRoot",null);this._skipValidation=o.getArg(e,"skipValidation",false);this._sources=new i;this._names=new i;this._mappings=new a;this._sourcesContents=null}SourceMapGenerator.prototype._version=3;SourceMapGenerator.fromSourceMap=function SourceMapGenerator_fromSourceMap(e){var r=e.sourceRoot;var n=new SourceMapGenerator({file:e.file,sourceRoot:r});e.eachMapping((function(e){var t={generated:{line:e.generatedLine,column:e.generatedColumn}};if(e.source!=null){t.source=e.source;if(r!=null){t.source=o.relative(r,t.source)}t.original={line:e.originalLine,column:e.originalColumn};if(e.name!=null){t.name=e.name}}n.addMapping(t)}));e.sources.forEach((function(t){var i=t;if(r!==null){i=o.relative(r,t)}if(!n._sources.has(i)){n._sources.add(i)}var a=e.sourceContentFor(t);if(a!=null){n.setSourceContent(t,a)}}));return n};SourceMapGenerator.prototype.addMapping=function SourceMapGenerator_addMapping(e){var r=o.getArg(e,"generated");var n=o.getArg(e,"original",null);var t=o.getArg(e,"source",null);var i=o.getArg(e,"name",null);if(!this._skipValidation){this._validateMapping(r,n,t,i)}if(t!=null){t=String(t);if(!this._sources.has(t)){this._sources.add(t)}}if(i!=null){i=String(i);if(!this._names.has(i)){this._names.add(i)}}this._mappings.add({generatedLine:r.line,generatedColumn:r.column,originalLine:n!=null&&n.line,originalColumn:n!=null&&n.column,source:t,name:i})};SourceMapGenerator.prototype.setSourceContent=function SourceMapGenerator_setSourceContent(e,r){var n=e;if(this._sourceRoot!=null){n=o.relative(this._sourceRoot,n)}if(r!=null){if(!this._sourcesContents){this._sourcesContents=Object.create(null)}this._sourcesContents[o.toSetString(n)]=r}else if(this._sourcesContents){delete this._sourcesContents[o.toSetString(n)];if(Object.keys(this._sourcesContents).length===0){this._sourcesContents=null}}};SourceMapGenerator.prototype.applySourceMap=function SourceMapGenerator_applySourceMap(e,r,n){var t=r;if(r==null){if(e.file==null){throw new Error("SourceMapGenerator.prototype.applySourceMap requires either an explicit source file, "+'or the source map\'s "file" property. Both were omitted.')}t=e.file}var a=this._sourceRoot;if(a!=null){t=o.relative(a,t)}var u=new i;var s=new i;this._mappings.unsortedForEach((function(r){if(r.source===t&&r.originalLine!=null){var i=e.originalPositionFor({line:r.originalLine,column:r.originalColumn});if(i.source!=null){r.source=i.source;if(n!=null){r.source=o.join(n,r.source)}if(a!=null){r.source=o.relative(a,r.source)}r.originalLine=i.line;r.originalColumn=i.column;if(i.name!=null){r.name=i.name}}}var l=r.source;if(l!=null&&!u.has(l)){u.add(l)}var c=r.name;if(c!=null&&!s.has(c)){s.add(c)}}),this);this._sources=u;this._names=s;e.sources.forEach((function(r){var t=e.sourceContentFor(r);if(t!=null){if(n!=null){r=o.join(n,r)}if(a!=null){r=o.relative(a,r)}this.setSourceContent(r,t)}}),this)};SourceMapGenerator.prototype._validateMapping=function SourceMapGenerator_validateMapping(e,r,n,t){if(r&&typeof r.line!=="number"&&typeof r.column!=="number"){throw new Error("original.line and original.column are not numbers -- you probably meant to omit "+"the original mapping entirely and only map the generated position. If so, pass "+"null for the original mapping instead of an object with empty or null values.")}if(e&&"line"in e&&"column"in e&&e.line>0&&e.column>=0&&!r&&!n&&!t){return}else if(e&&"line"in e&&"column"in e&&r&&"line"in r&&"column"in r&&e.line>0&&e.column>=0&&r.line>0&&r.column>=0&&n){return}else{throw new Error("Invalid mapping: "+JSON.stringify({generated:e,source:n,original:r,name:t}))}};SourceMapGenerator.prototype._serializeMappings=function SourceMapGenerator_serializeMappings(){var e=0;var r=1;var n=0;var i=0;var a=0;var u=0;var s="";var l;var c;var p;var f;var g=this._mappings.toArray();for(var h=0,d=g.length;h0){if(!o.compareByGeneratedPositionsInflated(c,g[h-1])){continue}l+=","}}l+=t.encode(c.generatedColumn-e);e=c.generatedColumn;if(c.source!=null){f=this._sources.indexOf(c.source);l+=t.encode(f-u);u=f;l+=t.encode(c.originalLine-1-i);i=c.originalLine-1;l+=t.encode(c.originalColumn-n);n=c.originalColumn;if(c.name!=null){p=this._names.indexOf(c.name);l+=t.encode(p-a);a=p}}s+=l}return s};SourceMapGenerator.prototype._generateSourcesContent=function SourceMapGenerator_generateSourcesContent(e,r){return e.map((function(e){if(!this._sourcesContents){return null}if(r!=null){e=o.relative(r,e)}var n=o.toSetString(e);return Object.prototype.hasOwnProperty.call(this._sourcesContents,n)?this._sourcesContents[n]:null}),this)};SourceMapGenerator.prototype.toJSON=function SourceMapGenerator_toJSON(){var e={version:this._version,sources:this._sources.toArray(),names:this._names.toArray(),mappings:this._serializeMappings()};if(this._file!=null){e.file=this._file}if(this._sourceRoot!=null){e.sourceRoot=this._sourceRoot}if(this._sourcesContents){e.sourcesContent=this._generateSourcesContent(e.sources,e.sourceRoot)}return e};SourceMapGenerator.prototype.toString=function SourceMapGenerator_toString(){return JSON.stringify(this.toJSON())};r.h=SourceMapGenerator},990:(e,r,n)=>{var t;var o=n(341).h;var i=n(983);var a=/(\r?\n)/;var u=10;var s="$$$isSourceNode$$$";function SourceNode(e,r,n,t,o){this.children=[];this.sourceContents={};this.line=e==null?null:e;this.column=r==null?null:r;this.source=n==null?null:n;this.name=o==null?null:o;this[s]=true;if(t!=null)this.add(t)}SourceNode.fromStringWithSourceMap=function SourceNode_fromStringWithSourceMap(e,r,n){var t=new SourceNode;var o=e.split(a);var u=0;var shiftNextLine=function(){var e=getNextLine();var r=getNextLine()||"";return e+r;function getNextLine(){return u=0;r--){this.prepend(e[r])}}else if(e[s]||typeof e==="string"){this.children.unshift(e)}else{throw new TypeError("Expected a SourceNode, string, or an array of SourceNodes and strings. Got "+e)}return this};SourceNode.prototype.walk=function SourceNode_walk(e){var r;for(var n=0,t=this.children.length;n0){r=[];for(n=0;n{function getArg(e,r,n){if(r in e){return e[r]}else if(arguments.length===3){return n}else{throw new Error('"'+r+'" is a required argument.')}}r.getArg=getArg;var n=/^(?:([\w+\-.]+):)?\/\/(?:(\w+:\w+)@)?([\w.-]*)(?::(\d+))?(.*)$/;var t=/^data:.+\,.+$/;function urlParse(e){var r=e.match(n);if(!r){return null}return{scheme:r[1],auth:r[2],host:r[3],port:r[4],path:r[5]}}r.urlParse=urlParse;function urlGenerate(e){var r="";if(e.scheme){r+=e.scheme+":"}r+="//";if(e.auth){r+=e.auth+"@"}if(e.host){r+=e.host}if(e.port){r+=":"+e.port}if(e.path){r+=e.path}return r}r.urlGenerate=urlGenerate;function normalize(e){var n=e;var t=urlParse(e);if(t){if(!t.path){return e}n=t.path}var o=r.isAbsolute(n);var i=n.split(/\/+/);for(var a,u=0,s=i.length-1;s>=0;s--){a=i[s];if(a==="."){i.splice(s,1)}else if(a===".."){u++}else if(u>0){if(a===""){i.splice(s+1,u);u=0}else{i.splice(s,2);u--}}}n=i.join("/");if(n===""){n=o?"/":"."}if(t){t.path=n;return urlGenerate(t)}return n}r.normalize=normalize;function join(e,r){if(e===""){e="."}if(r===""){r="."}var n=urlParse(r);var o=urlParse(e);if(o){e=o.path||"/"}if(n&&!n.scheme){if(o){n.scheme=o.scheme}return urlGenerate(n)}if(n||r.match(t)){return r}if(o&&!o.host&&!o.path){o.host=r;return urlGenerate(o)}var i=r.charAt(0)==="/"?r:normalize(e.replace(/\/+$/,"")+"/"+r);if(o){o.path=i;return urlGenerate(o)}return i}r.join=join;r.isAbsolute=function(e){return e.charAt(0)==="/"||n.test(e)};function relative(e,r){if(e===""){e="."}e=e.replace(/\/$/,"");var n=0;while(r.indexOf(e+"/")!==0){var t=e.lastIndexOf("/");if(t<0){return r}e=e.slice(0,t);if(e.match(/^([^\/]+:\/)?\/*$/)){return r}++n}return Array(n+1).join("../")+r.substr(e.length+1)}r.relative=relative;var o=function(){var e=Object.create(null);return!("__proto__"in e)}();function identity(e){return e}function toSetString(e){if(isProtoString(e)){return"$"+e}return e}r.toSetString=o?identity:toSetString;function fromSetString(e){if(isProtoString(e)){return e.slice(1)}return e}r.fromSetString=o?identity:fromSetString;function isProtoString(e){if(!e){return false}var r=e.length;if(r<9){return false}if(e.charCodeAt(r-1)!==95||e.charCodeAt(r-2)!==95||e.charCodeAt(r-3)!==111||e.charCodeAt(r-4)!==116||e.charCodeAt(r-5)!==111||e.charCodeAt(r-6)!==114||e.charCodeAt(r-7)!==112||e.charCodeAt(r-8)!==95||e.charCodeAt(r-9)!==95){return false}for(var n=r-10;n>=0;n--){if(e.charCodeAt(n)!==36){return false}}return true}function compareByOriginalPositions(e,r,n){var t=strcmp(e.source,r.source);if(t!==0){return t}t=e.originalLine-r.originalLine;if(t!==0){return t}t=e.originalColumn-r.originalColumn;if(t!==0||n){return t}t=e.generatedColumn-r.generatedColumn;if(t!==0){return t}t=e.generatedLine-r.generatedLine;if(t!==0){return t}return strcmp(e.name,r.name)}r.compareByOriginalPositions=compareByOriginalPositions;function compareByGeneratedPositionsDeflated(e,r,n){var t=e.generatedLine-r.generatedLine;if(t!==0){return t}t=e.generatedColumn-r.generatedColumn;if(t!==0||n){return t}t=strcmp(e.source,r.source);if(t!==0){return t}t=e.originalLine-r.originalLine;if(t!==0){return t}t=e.originalColumn-r.originalColumn;if(t!==0){return t}return strcmp(e.name,r.name)}r.compareByGeneratedPositionsDeflated=compareByGeneratedPositionsDeflated;function strcmp(e,r){if(e===r){return 0}if(e===null){return 1}if(r===null){return-1}if(e>r){return 1}return-1}function compareByGeneratedPositionsInflated(e,r){var n=e.generatedLine-r.generatedLine;if(n!==0){return n}n=e.generatedColumn-r.generatedColumn;if(n!==0){return n}n=strcmp(e.source,r.source);if(n!==0){return n}n=e.originalLine-r.originalLine;if(n!==0){return n}n=e.originalColumn-r.originalColumn;if(n!==0){return n}return strcmp(e.name,r.name)}r.compareByGeneratedPositionsInflated=compareByGeneratedPositionsInflated;function parseSourceMapInput(e){return JSON.parse(e.replace(/^\)]}'[^\n]*\n/,""))}r.parseSourceMapInput=parseSourceMapInput;function computeSourceURL(e,r,n){r=r||"";if(e){if(e[e.length-1]!=="/"&&r[0]!=="/"){e+="/"}r=e+r}if(n){var t=urlParse(n);if(!t){throw new Error("sourceMapURL could not be parsed")}if(t.path){var o=t.path.lastIndexOf("/");if(o>=0){t.path=t.path.substring(0,o+1)}}r=join(urlGenerate(t),r)}return normalize(r)}r.computeSourceURL=computeSourceURL},596:(e,r,n)=>{n(341).h;r.SourceMapConsumer=n(327).SourceMapConsumer;n(990)},747:e=>{"use strict";e.exports=require("fs")},622:e=>{"use strict";e.exports=require("path")}};var r={};function __webpack_require__(n){var t=r[n];if(t!==undefined){return t.exports}var o=r[n]={id:n,loaded:false,exports:{}};var i=true;try{e[n](o,o.exports,__webpack_require__);i=false}finally{if(i)delete r[n]}o.loaded=true;return o.exports}(()=>{__webpack_require__.nmd=e=>{e.paths=[];if(!e.children)e.children=[];return e}})();if(typeof __webpack_require__!=="undefined")__webpack_require__.ab=__dirname+"/";var n={};(()=>{__webpack_require__(284).install()})();module.exports=n})(); -------------------------------------------------------------------------------- /git-hooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ### Copy this into .git/hooks and overwrite the empty one. 4 | ### This will ensure the bundle and ins-outs verification checks won't fail for you. 5 | 6 | echo "----- Pre-commit -----" 7 | set -ex 8 | npx action-io-generator -o src/generated/inputs-outputs.ts 9 | npm run lint 10 | npm run bundle 11 | git add -v dist/ src/generated 12 | -------------------------------------------------------------------------------- /images/comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redhat-actions/try-in-web-ide/11cfde83a356e5b6086bd67a9c5c135037317e4b/images/comment.png -------------------------------------------------------------------------------- /images/status_check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redhat-actions/try-in-web-ide/11cfde83a356e5b6086bd67a9c5c135037317e4b/images/status_check.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "try-in-web-ide", 3 | "version": "1.0.0", 4 | "description": "Try in Web IDE", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/redhat-actions/try-in-web-ide" 8 | }, 9 | "author": "Red Hat", 10 | "license": "MIT", 11 | "scripts": { 12 | "compile": "tsc -p .", 13 | "bundle": "npx ncc build src/index.ts --source-map --minify", 14 | "clean": "rm -rf out/ dist/", 15 | "lint": "eslint . --max-warnings=0", 16 | "test": "jest" 17 | }, 18 | "jest": { 19 | "collectCoverage": true, 20 | "collectCoverageFrom": [ 21 | "src/**/*.ts" 22 | ], 23 | "coverageDirectory": "./coverage", 24 | "transform": { 25 | "^.+\\.tsx?$": "ts-jest" 26 | }, 27 | "modulePathIgnorePatterns": [ 28 | "/out" 29 | ], 30 | "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$", 31 | "moduleFileExtensions": [ 32 | "ts", 33 | "tsx", 34 | "js", 35 | "jsx", 36 | "json" 37 | ] 38 | }, 39 | "devDependencies": { 40 | "@redhat-actions/action-io-generator": "^1.5.0", 41 | "@redhat-actions/eslint-config": "^1.3.2", 42 | "@redhat-actions/tsconfig": "^1.2.0", 43 | "@types/fs-extra": "^9.0.12", 44 | "@types/jest": "^27.0.1", 45 | "@types/node": "^12", 46 | "@types/reflect-metadata": "^0.1.0", 47 | "@typescript-eslint/eslint-plugin": "^4.25.0", 48 | "@typescript-eslint/parser": "^4.25.0", 49 | "@vercel/ncc": "^0.29.2", 50 | "eslint": "^7.27.0", 51 | "eslint-plugin-jest": "^24.4.0", 52 | "fs-extra": "^10.0.0", 53 | "jest": "^27.0.1", 54 | "ts-jest": "^27.0.1", 55 | "typescript": "^4.3" 56 | }, 57 | "dependencies": { 58 | "@actions/core": "^1.3.0", 59 | "@actions/github": "^4.0.0", 60 | "@octokit/core": "^3.6.0", 61 | "@octokit/plugin-rest-endpoint-methods": "^3.17.0", 62 | "@octokit/rest": "^17.11.2", 63 | "@octokit/types": "^4.1.10", 64 | "@octokit/webhooks": "^7.5.0", 65 | "inversify": "^5.0.1", 66 | "reflect-metadata": "^0.1.13" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/actions/actions-module.ts: -------------------------------------------------------------------------------- 1 | import { ContainerModule, interfaces } from "inversify"; 2 | import { PullRequestAction } from "./pull-request-action"; 3 | import { PullRequestListener } from "../api/pull-request-listener"; 4 | 5 | const actionsModule = new ContainerModule((bind: interfaces.Bind) => { 6 | bind(PullRequestAction).toSelf().inSingletonScope(); 7 | bind(PullRequestListener).toService(PullRequestAction); 8 | }); 9 | 10 | export { actionsModule }; 11 | -------------------------------------------------------------------------------- /src/actions/pull-request-action.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from "inversify"; 2 | import { WebhookPayloadPullRequest } from "@octokit/webhooks"; 3 | import { setFailed } from "@actions/core"; 4 | import { PullRequestListener } from "../api/pull-request-listener"; 5 | 6 | @injectable() 7 | export class PullRequestAction implements PullRequestListener { 8 | private readonly pullRequestCallbacks: Map< 9 | string, 10 | Array<(payload: WebhookPayloadPullRequest) => Promise> 11 | >; 12 | 13 | constructor() { 14 | this.pullRequestCallbacks = new Map(); 15 | } 16 | 17 | /** 18 | * Add the callback provided by given action name 19 | */ 20 | registerCallback( 21 | events: string[], 22 | callback: (payload: WebhookPayloadPullRequest) => Promise 23 | ): void { 24 | events.forEach((eventName) => { 25 | if (!this.pullRequestCallbacks.has(eventName)) { 26 | this.pullRequestCallbacks.set(eventName, []); 27 | } 28 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 29 | this.pullRequestCallbacks.get(eventName)!.push(callback); 30 | }); 31 | } 32 | 33 | async execute(payload: WebhookPayloadPullRequest): Promise { 34 | const eventName = payload.action; 35 | 36 | const callbacks = this.pullRequestCallbacks.get(eventName); 37 | if (callbacks) { 38 | for await (const callback of callbacks) { 39 | callback(payload).catch(setFailed); 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/analysis.ts: -------------------------------------------------------------------------------- 1 | import { inject, injectable, named } from "inversify"; 2 | import { Context } from "@actions/github/lib/context"; 3 | import { Handler } from "./api/handler"; 4 | import { MultiInjectProvider } from "./api/multi-inject-provider"; 5 | 6 | @injectable() 7 | export class Analysis { 8 | @inject(MultiInjectProvider) 9 | @named(Handler) 10 | protected readonly handlers: MultiInjectProvider; 11 | 12 | async analyze(context: Context): Promise { 13 | for await (const handler of this.handlers.getAll()) { 14 | if (handler.supports(context.eventName)) { 15 | await handler.handle(context.eventName, context.payload); 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/api/apis-module.ts: -------------------------------------------------------------------------------- 1 | import { ContainerModule, interfaces } from "inversify"; 2 | import { PullRequestListener } from "../api/pull-request-listener"; 3 | import { bindMultiInjectProvider } from "../api/multi-inject-provider"; 4 | 5 | const apisModule = new ContainerModule((bind: interfaces.Bind) => { 6 | bindMultiInjectProvider(bind, PullRequestListener); 7 | }); 8 | 9 | export { apisModule }; 10 | -------------------------------------------------------------------------------- /src/api/configuration.ts: -------------------------------------------------------------------------------- 1 | export const Configuration = Symbol.for("Configuration"); 2 | export interface Configuration { 3 | addComment(): boolean; 4 | 5 | addStatus(): boolean; 6 | 7 | setupRemotes(): boolean; 8 | 9 | webIdeInstance(): string; 10 | 11 | commentBadge(): string; 12 | } 13 | -------------------------------------------------------------------------------- /src/api/handler.ts: -------------------------------------------------------------------------------- 1 | import { WebhookPayload } from "@actions/github/lib/interfaces"; 2 | 3 | export const Handler = Symbol.for("Handler"); 4 | export type Handler = { 5 | supports(eventName: string): boolean; 6 | 7 | handle(eventName: string, webhookPayLoad: WebhookPayload): Promise; 8 | }; 9 | -------------------------------------------------------------------------------- /src/api/logic.ts: -------------------------------------------------------------------------------- 1 | export const Logic = Symbol.for("Logic"); 2 | // eslint-disable-next-line @typescript-eslint/no-empty-interface 3 | export interface Logic {} 4 | -------------------------------------------------------------------------------- /src/api/multi-inject-provider.ts: -------------------------------------------------------------------------------- 1 | import { Container, interfaces } from "inversify"; 2 | 3 | // Use `Record` instead of `object` due to `@typescript-eslint/ban-types` 4 | type NonPrimitive = Record; 5 | 6 | export const MultiInjectProvider = Symbol.for("MultiInjectProvider"); 7 | export interface MultiInjectProvider { 8 | getAll(): T[]; 9 | } 10 | 11 | class DefaultMultiInjectProvider implements MultiInjectProvider { 12 | protected services: T[] | undefined; 13 | 14 | constructor( 15 | protected readonly serviceIdentifier: interfaces.ServiceIdentifier, 16 | protected readonly container: interfaces.Container 17 | ) {} 18 | 19 | getAll(): T[] { 20 | if (this.services === undefined) { 21 | const currentServices: T[] = []; 22 | if (this.container.isBound(this.serviceIdentifier)) { 23 | currentServices.push( 24 | ...this.container.getAll(this.serviceIdentifier) 25 | ); 26 | } 27 | this.services = currentServices; 28 | } 29 | return this.services; 30 | } 31 | } 32 | 33 | export type Bindable = interfaces.Bind | interfaces.Container; 34 | 35 | export function bindMultiInjectProvider(bindable: Bindable, id: symbol): void { 36 | const isContainer = typeof bindable !== "function" 37 | && ("guid" in bindable || "parent" in bindable); 38 | let bindingToSyntax; 39 | if (isContainer) { 40 | bindingToSyntax = (bindable as Container).bind(MultiInjectProvider); 41 | } 42 | else { 43 | bindingToSyntax = (bindable as interfaces.Bind)(MultiInjectProvider); 44 | } 45 | bindingToSyntax 46 | .toDynamicValue( 47 | (ctx) => new DefaultMultiInjectProvider(id, ctx.container) 48 | ) 49 | .inSingletonScope() 50 | .whenTargetNamed(id); 51 | } 52 | -------------------------------------------------------------------------------- /src/api/pull-request-listener.ts: -------------------------------------------------------------------------------- 1 | import { WebhookPayloadPullRequest } from "@octokit/webhooks"; 2 | 3 | export const PullRequestListener = Symbol.for("PullRequestListener"); 4 | export type PullRequestListener = { 5 | execute(payload: WebhookPayloadPullRequest): Promise; 6 | }; 7 | -------------------------------------------------------------------------------- /src/generated/inputs-outputs.ts: -------------------------------------------------------------------------------- 1 | // This file was auto-generated by action-io-generator. Do not edit by hand! 2 | export enum Inputs { 3 | /** 4 | * If 'true', the action will add comments on each PR with a link to try the PR in Web IDE 5 | * Required: false 6 | * Default: "true" 7 | */ 8 | ADD_COMMENT = "add_comment", 9 | /** 10 | * If 'true', the action will add a PR status check on each PR with a link to try the PR in Web IDE 11 | * Required: false 12 | * Default: "true" 13 | */ 14 | ADD_STATUS = "add_status", 15 | /** 16 | * The badge url for the comment created when 'add_comment' is 'true' 17 | * Required: false 18 | * Default: "https://img.shields.io/badge/Eclipse_Che-Hosted%20by%20Red%20Hat-525C86?logo=eclipse-che&labelColor=FDB940" 19 | */ 20 | COMMENT_BADGE = "comment_badge", 21 | /** 22 | * GitHub token used to add PR comment and/or status check 23 | * Required: true 24 | * Default: None. 25 | */ 26 | GITHUB_TOKEN = "github_token", 27 | /** 28 | * If 'true', the badge and status check URL created by the action will have Git remotes automatically configured 29 | * if the PR branch is located in a fork repo. 30 | * The fork repo and the base repo will be configured to be the 'origin' and 'upstream' remote respectively. 31 | * The Web IDE must be based on Eclipse Che® 7.60 for this feature. 32 | * Required: false 33 | * Default: "false" 34 | */ 35 | SETUP_REMOTES = "setup_remotes", 36 | /** 37 | * The base url for the Web IDE instance 38 | * Required: false 39 | * Default: "https://workspaces.openshift.com" 40 | */ 41 | WEB_IDE_INSTANCE = "web_ide_instance", 42 | } 43 | 44 | export enum Outputs { 45 | } 46 | -------------------------------------------------------------------------------- /src/github/octokit-builder.ts: -------------------------------------------------------------------------------- 1 | import * as github from "@actions/github"; 2 | import { GitHub } from "@actions/github/lib/utils"; 3 | import { injectable } from "inversify"; 4 | 5 | @injectable() 6 | export class OctokitBuilder { 7 | build(token: string): InstanceType { 8 | return github.getOctokit(token); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/handler/handlers-module.ts: -------------------------------------------------------------------------------- 1 | import { ContainerModule, interfaces } from "inversify"; 2 | import { Handler } from "../api/handler"; 3 | import { PullRequestHandler } from "./pull-request-handler"; 4 | import { bindMultiInjectProvider } from "../api/multi-inject-provider"; 5 | 6 | const handlersModule = new ContainerModule((bind: interfaces.Bind) => { 7 | bindMultiInjectProvider(bind, Handler); 8 | bind(Handler).to(PullRequestHandler).inSingletonScope(); 9 | }); 10 | 11 | export { handlersModule }; 12 | -------------------------------------------------------------------------------- /src/handler/pull-request-handler.ts: -------------------------------------------------------------------------------- 1 | import { inject, injectable, named } from "inversify"; 2 | import { WebhookPayload } from "@actions/github/lib/interfaces"; 3 | import { WebhookPayloadPullRequest } from "@octokit/webhooks"; 4 | import { Handler } from "../api/handler"; 5 | import { MultiInjectProvider } from "../api/multi-inject-provider"; 6 | import { PullRequestListener } from "../api/pull-request-listener"; 7 | 8 | @injectable() 9 | export class PullRequestHandler implements Handler { 10 | @inject(MultiInjectProvider) 11 | @named(PullRequestListener) 12 | protected readonly pullRequestListeners: MultiInjectProvider; 13 | 14 | supports(eventName: string): boolean { 15 | return eventName === "pull_request_target"; 16 | } 17 | 18 | async handle( 19 | _eventName: string, 20 | webhookPayLoad: WebhookPayload 21 | ): Promise { 22 | // 23 | 24 | // cast payload 25 | const prPayLoad = webhookPayLoad as WebhookPayloadPullRequest; 26 | 27 | await Promise.all( 28 | this.pullRequestListeners 29 | .getAll() 30 | .map(async (listener) => listener.execute(prPayLoad)) 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/helpers/add-comment-helper.ts: -------------------------------------------------------------------------------- 1 | import { inject, injectable } from "inversify"; 2 | import { Octokit } from "@octokit/rest"; 3 | import { WebhookPayloadPullRequest } from "@octokit/webhooks"; 4 | import { setFailed } from "@actions/core"; 5 | import { RestEndpointMethodTypes } from "@octokit/plugin-rest-endpoint-methods"; 6 | 7 | @injectable() 8 | export class AddCommentHelper { 9 | @inject(Octokit) 10 | private readonly octokit: Octokit; 11 | 12 | public async addComment( 13 | comment: string, 14 | payload: WebhookPayloadPullRequest 15 | ): Promise { 16 | const createCommentParams: RestEndpointMethodTypes["issues"]["createComment"]["parameters"] = { 17 | body: comment, 18 | issue_number: payload.pull_request.number, 19 | owner: payload.repository.owner.login, 20 | repo: payload.repository.name, 21 | }; 22 | this.octokit.issues.createComment(createCommentParams).catch(setFailed); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/helpers/add-status-check-helper.ts: -------------------------------------------------------------------------------- 1 | import { setFailed } from "@actions/core"; 2 | import { inject, injectable } from "inversify"; 3 | import { Octokit } from "@octokit/rest"; 4 | import { WebhookPayloadPullRequest } from "@octokit/webhooks"; 5 | import { RestEndpointMethodTypes } from "@octokit/plugin-rest-endpoint-methods"; 6 | 7 | @injectable() 8 | export class AddStatusCheckHelper { 9 | @inject(Octokit) 10 | private readonly octokit: Octokit; 11 | 12 | public async addStatusCheck( 13 | description: string, 14 | context: string, 15 | targetUrl: string, 16 | payload: WebhookPayloadPullRequest 17 | ): Promise { 18 | const statusParams: RestEndpointMethodTypes["repos"]["createCommitStatus"]["parameters"] = { 19 | repo: payload.repository.name, 20 | owner: payload.repository.owner.login, 21 | sha: payload.pull_request.head.sha, 22 | state: "success", 23 | description, 24 | context, 25 | target_url: targetUrl, 26 | }; 27 | 28 | this.octokit.repos.createCommitStatus(statusParams).catch(setFailed); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/helpers/helpers-module.ts: -------------------------------------------------------------------------------- 1 | import { ContainerModule, interfaces } from "inversify"; 2 | import { AddCommentHelper } from "./add-comment-helper"; 3 | import { AddStatusCheckHelper } from "./add-status-check-helper"; 4 | import { UpdateCommentHelper } from "./update-comment-helper"; 5 | 6 | const helpersModule = new ContainerModule((bind: interfaces.Bind) => { 7 | bind(AddCommentHelper).toSelf().inSingletonScope(); 8 | bind(AddStatusCheckHelper).toSelf().inSingletonScope(); 9 | bind(UpdateCommentHelper).toSelf().inSingletonScope(); 10 | }); 11 | 12 | export { helpersModule }; 13 | -------------------------------------------------------------------------------- /src/helpers/update-comment-helper.ts: -------------------------------------------------------------------------------- 1 | import { inject, injectable } from "inversify"; 2 | import { Octokit } from "@octokit/rest"; 3 | import { WebhookPayloadPullRequest } from "@octokit/webhooks"; 4 | import { RestEndpointMethodTypes } from "@octokit/plugin-rest-endpoint-methods"; 5 | import { setFailed } from "@actions/core"; 6 | import { IssuesListCommentsResponseData } from "@octokit/types"; 7 | 8 | type ArrayType = T extends (infer Item)[] ? Item : T; 9 | 10 | @injectable() 11 | export class UpdateCommentHelper { 12 | @inject(Octokit) 13 | private readonly octokit: Octokit; 14 | 15 | /** 16 | * Updates a GH comment with a new comment if needed 17 | * 18 | * @param searchRegex regex used to determine which comment to edit 19 | * @param comment the new comment content 20 | * @param payload the pull request payload 21 | * @returns true if a previous comment was updated or if an update is not required. Otherwise returns false. 22 | */ 23 | public async updateComment( 24 | searchRegex: RegExp, 25 | newComment: string, 26 | payload: WebhookPayloadPullRequest 27 | ): Promise { 28 | try { 29 | const comments = await this.listComments(payload); 30 | const comment = this.findComment(comments, searchRegex); 31 | 32 | if (!comment) { 33 | return false; 34 | } 35 | 36 | if (comment.body === newComment) { 37 | // no need to update since new comment is same as existing comment 38 | // return true since updating effectively does nothing 39 | return true; 40 | } 41 | 42 | const result = await this.updateCommentById( 43 | comment.id, 44 | newComment, 45 | payload 46 | ); 47 | return result.status === 200; 48 | } 49 | catch (e) { 50 | if (e instanceof Error || typeof e === "string") { 51 | setFailed(e); 52 | } 53 | } 54 | return false; 55 | } 56 | 57 | private async listComments( 58 | payload: WebhookPayloadPullRequest 59 | ): Promise { 60 | const listCommentParams: RestEndpointMethodTypes["issues"]["listComments"]["parameters"] = { 61 | issue_number: payload.pull_request.number, 62 | owner: payload.repository.owner.login, 63 | repo: payload.repository.name, 64 | }; 65 | return this.octokit.issues.listComments(listCommentParams); 66 | } 67 | 68 | private findComment( 69 | response: RestEndpointMethodTypes["issues"]["listComments"]["response"], 70 | searchRegex: RegExp 71 | ): ArrayType | undefined { 72 | const matches = response.data.filter((comment) => { 73 | return searchRegex.test(comment.body); 74 | }); 75 | // comments are sorted by ascending id 76 | return this.getEarliestComment(matches); 77 | } 78 | 79 | private getEarliestComment( 80 | comments: IssuesListCommentsResponseData 81 | ): ArrayType | undefined { 82 | if (comments.length === 0) { 83 | return undefined; 84 | } 85 | let earliest = comments[0]; 86 | for (let i = 1; i < comments.length; i++) { 87 | if (new Date(comments[i].created_at) < new Date(earliest.created_at)) { 88 | earliest = comments[i]; 89 | } 90 | } 91 | return earliest; 92 | } 93 | 94 | private async updateCommentById( 95 | commentId: number, 96 | comment: string, 97 | payload: WebhookPayloadPullRequest 98 | ): Promise { 99 | const updateCommentParams: RestEndpointMethodTypes["issues"]["updateComment"]["parameters"] = { 100 | comment_id: commentId, 101 | owner: payload.repository.owner.login, 102 | repo: payload.repository.name, 103 | body: comment, 104 | }; 105 | return this.octokit.issues.updateComment(updateCommentParams); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import "reflect-metadata"; 2 | import { setFailed } from "@actions/core"; 3 | import { Main } from "./main"; 4 | 5 | new Main().start().catch(((e: Error) => setFailed(e.message))); 6 | -------------------------------------------------------------------------------- /src/inversify-binding.ts: -------------------------------------------------------------------------------- 1 | import { Container } from "inversify"; 2 | import { Octokit } from "@octokit/rest"; 3 | import { Analysis } from "./analysis"; 4 | import { Configuration } from "./api/configuration"; 5 | import { Logic } from "./api/logic"; 6 | import { OctokitBuilder } from "./github/octokit-builder"; 7 | import { actionsModule } from "./actions/actions-module"; 8 | import { apisModule } from "./api/apis-module"; 9 | import { handlersModule } from "./handler/handlers-module"; 10 | import { helpersModule } from "./helpers/helpers-module"; 11 | import { logicModule } from "./logic/logic-module"; 12 | 13 | export class InversifyBinding { 14 | private container: Container; 15 | 16 | constructor( 17 | private readonly githubToken: string, 18 | private readonly addComment: boolean, 19 | private readonly addStatus: boolean, 20 | private readonly setupRemotes: boolean, 21 | private readonly webIdeInstance: string, 22 | private readonly commentBadge: string, 23 | ) {} 24 | 25 | public initBindings(): Container { 26 | this.container = new Container(); 27 | 28 | this.container.load(actionsModule); 29 | this.container.load(apisModule); 30 | this.container.load(handlersModule); 31 | this.container.load(helpersModule); 32 | this.container.load(logicModule); 33 | 34 | // configuration 35 | const configuration: Configuration = { 36 | addComment: () => this.addComment, 37 | addStatus: () => this.addStatus, 38 | setupRemotes: () => this.setupRemotes, 39 | webIdeInstance: () => this.webIdeInstance, 40 | commentBadge: () => this.commentBadge, 41 | }; 42 | this.container.bind(OctokitBuilder).toSelf().inSingletonScope(); 43 | const writeOctokit = this.container 44 | .get(OctokitBuilder) 45 | .build(this.githubToken); 46 | this.container.bind(Octokit).toConstantValue(writeOctokit); 47 | 48 | this.container.bind(Configuration).toConstantValue(configuration); 49 | 50 | // Analyze 51 | this.container.bind(Analysis).toSelf().inSingletonScope(); 52 | 53 | // resolve all logics to create instances 54 | this.container.getAll(Logic); 55 | 56 | return this.container; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/logic/handle-pull-request-logic.ts: -------------------------------------------------------------------------------- 1 | import { inject, injectable, postConstruct } from "inversify"; 2 | import { URL } from "url"; 3 | import { 4 | WebhookPayloadPullRequest, 5 | WebhookPayloadPullRequestPullRequestBase, 6 | WebhookPayloadPullRequestPullRequestHead, 7 | } from "@octokit/webhooks"; 8 | import { AddCommentHelper } from "../helpers/add-comment-helper"; 9 | import { AddStatusCheckHelper } from "../helpers/add-status-check-helper"; 10 | import { Configuration } from "../api/configuration"; 11 | import { Logic } from "../api/logic"; 12 | import { PullRequestAction } from "../actions/pull-request-action"; 13 | import { UpdateCommentHelper } from "../helpers/update-comment-helper"; 14 | 15 | @injectable() 16 | export class HandlePullRequestLogic implements Logic { 17 | public static readonly PR_EVENTS: string[] = [ "opened", "synchronize" ]; 18 | 19 | public static readonly MESSAGE_PREFIX = "Click here to review and test in web IDE"; 20 | 21 | @inject(Configuration) 22 | private readonly configuration: Configuration; 23 | 24 | @inject(PullRequestAction) 25 | private readonly pullRequestAction: PullRequestAction; 26 | 27 | @inject(AddCommentHelper) 28 | private readonly addCommentHelper: AddCommentHelper; 29 | 30 | @inject(AddStatusCheckHelper) 31 | private readonly addStatusCheckHelper: AddStatusCheckHelper; 32 | 33 | @inject(UpdateCommentHelper) 34 | private readonly updateCommentHelper: UpdateCommentHelper; 35 | 36 | // Add the given milestone 37 | @postConstruct() 38 | public setup(): void { 39 | const callback = async ( 40 | payload: WebhookPayloadPullRequest 41 | ): Promise => { 42 | const targetUrl = this.createTargetUrl( 43 | payload, 44 | this.configuration.setupRemotes() 45 | ); 46 | const badgeUrl = this.configuration.commentBadge(); 47 | 48 | if (this.configuration.addComment()) { 49 | await this.handleComment(payload, targetUrl, badgeUrl); 50 | } 51 | if (this.configuration.addStatus()) { 52 | await this.handleStatus(payload, targetUrl); 53 | } 54 | }; 55 | 56 | this.pullRequestAction.registerCallback( 57 | HandlePullRequestLogic.PR_EVENTS, 58 | callback 59 | ); 60 | } 61 | 62 | protected createTargetUrl( 63 | payload: WebhookPayloadPullRequest, 64 | setupRemotes: boolean 65 | ): string { 66 | const prBranchName = payload.pull_request.head.ref; 67 | const repoUrl = payload.pull_request.head.repo.html_url; 68 | 69 | if (setupRemotes 70 | && !this.isSameRepo( 71 | payload.pull_request.base, 72 | payload.pull_request.head 73 | ) 74 | ) { 75 | const baseGitUrl = payload.pull_request.base.repo.clone_url; 76 | return `${this.configuration.webIdeInstance()}#${repoUrl}/tree/${prBranchName}` 77 | + `?remotes={{upstream,${baseGitUrl}}}`; 78 | } 79 | 80 | return `${this.configuration.webIdeInstance()}#${repoUrl}/tree/${prBranchName}`; 81 | } 82 | 83 | protected isSameRepo( 84 | baseRepo: WebhookPayloadPullRequestPullRequestBase, 85 | headRepo: WebhookPayloadPullRequestPullRequestHead 86 | ): boolean { 87 | return baseRepo.repo.full_name === headRepo.repo.full_name; 88 | } 89 | 90 | protected async handleComment( 91 | payload: WebhookPayloadPullRequest, 92 | targetUrl: string, 93 | badgeUrl: string 94 | ): Promise { 95 | const comment = `${HandlePullRequestLogic.MESSAGE_PREFIX}: [![Contribute](${badgeUrl})](${targetUrl})`; 96 | 97 | const updatedExisting = await this.updateCommentHelper.updateComment( 98 | new RegExp(`^${HandlePullRequestLogic.MESSAGE_PREFIX}.*$`), 99 | comment, payload 100 | ); 101 | 102 | if (!updatedExisting) { 103 | await this.addCommentHelper.addComment(comment, payload); 104 | } 105 | } 106 | 107 | protected async handleStatus( 108 | payload: WebhookPayloadPullRequest, 109 | targetUrl: string 110 | ): Promise { 111 | const hostname = new URL(targetUrl).hostname; 112 | await this.addStatusCheckHelper.addStatusCheck( 113 | HandlePullRequestLogic.MESSAGE_PREFIX, 114 | hostname, 115 | targetUrl, 116 | payload 117 | ); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/logic/logic-module.ts: -------------------------------------------------------------------------------- 1 | import { ContainerModule, interfaces } from "inversify"; 2 | import { HandlePullRequestLogic } from "./handle-pull-request-logic"; 3 | import { Logic } from "../api/logic"; 4 | import { bindMultiInjectProvider } from "../api/multi-inject-provider"; 5 | 6 | const logicModule = new ContainerModule((bind: interfaces.Bind) => { 7 | bindMultiInjectProvider(bind, Logic); 8 | bind(Logic).to(HandlePullRequestLogic).inSingletonScope(); 9 | }); 10 | 11 | export { logicModule }; 12 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import * as core from "@actions/core"; 2 | import * as github from "@actions/github"; 3 | import { Analysis } from "./analysis"; 4 | import { Inputs } from "./generated/inputs-outputs"; 5 | import { InversifyBinding } from "./inversify-binding"; 6 | 7 | export class Main { 8 | async start(): Promise { 9 | // github write token 10 | const githubToken = core.getInput(Inputs.GITHUB_TOKEN, { 11 | required: true, 12 | }); 13 | if (!githubToken) { 14 | throw new Error( 15 | `No GitHub Token provided (${Inputs.GITHUB_TOKEN})` 16 | ); 17 | } 18 | 19 | const addComment = core.getInput(Inputs.ADD_COMMENT); 20 | const addStatus = core.getInput(Inputs.ADD_STATUS); 21 | const setupRemotes = core.getInput(Inputs.SETUP_REMOTES); 22 | const webIdeInstance = core.getInput(Inputs.WEB_IDE_INSTANCE); 23 | const commentBadge = core.getInput(Inputs.COMMENT_BADGE); 24 | 25 | const inversifyBinbding = new InversifyBinding( 26 | githubToken, 27 | addComment === "true", 28 | addStatus === "true", 29 | setupRemotes === "true", 30 | webIdeInstance, 31 | commentBadge 32 | ); 33 | const container = inversifyBinbding.initBindings(); 34 | const analysis = container.get(Analysis); 35 | await analysis.analyze(github.context); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redhat-actions/try-in-web-ide/11cfde83a356e5b6086bd67a9c5c135037317e4b/tests/.DS_Store -------------------------------------------------------------------------------- /tests/_data/pull_request/opened/create-pr-source-head-branch-same-repo.json: -------------------------------------------------------------------------------- 1 | { 2 | "action": "opened", 3 | "number": 11, 4 | "pull_request": { 5 | "_links": { 6 | "comments": { 7 | "href": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/issues/11/comments" 8 | }, 9 | "commits": { 10 | "href": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/pulls/11/commits" 11 | }, 12 | "html": { 13 | "href": "https://github.com/dkwon17/try-in-web-ide-testing/pull/11" 14 | }, 15 | "issue": { 16 | "href": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/issues/11" 17 | }, 18 | "review_comment": { 19 | "href": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/pulls/comments{/number}" 20 | }, 21 | "review_comments": { 22 | "href": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/pulls/11/comments" 23 | }, 24 | "self": { 25 | "href": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/pulls/11" 26 | }, 27 | "statuses": { 28 | "href": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/statuses/5926f5e7af27ebc6d83477998c9d5a8074beceb7" 29 | } 30 | }, 31 | "active_lock_reason": null, 32 | "additions": 2, 33 | "assignee": null, 34 | "assignees": [], 35 | "author_association": "OWNER", 36 | "auto_merge": null, 37 | "base": { 38 | "label": "dkwon17:main", 39 | "ref": "main", 40 | "repo": { 41 | "allow_auto_merge": false, 42 | "allow_forking": true, 43 | "allow_merge_commit": true, 44 | "allow_rebase_merge": true, 45 | "allow_squash_merge": true, 46 | "allow_update_branch": false, 47 | "archive_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/{archive_format}{/ref}", 48 | "archived": false, 49 | "assignees_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/assignees{/user}", 50 | "blobs_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/git/blobs{/sha}", 51 | "branches_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/branches{/branch}", 52 | "clone_url": "https://github.com/dkwon17/try-in-web-ide-testing.git", 53 | "collaborators_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/collaborators{/collaborator}", 54 | "comments_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/comments{/number}", 55 | "commits_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/commits{/sha}", 56 | "compare_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/compare/{base}...{head}", 57 | "contents_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/contents/{+path}", 58 | "contributors_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/contributors", 59 | "created_at": "2021-09-03T18:52:05Z", 60 | "default_branch": "main", 61 | "delete_branch_on_merge": false, 62 | "deployments_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/deployments", 63 | "description": "Testing repository for https://github.com/redhat-actions/try-in-web-ide/pull/3", 64 | "disabled": false, 65 | "downloads_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/downloads", 66 | "events_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/events", 67 | "fork": false, 68 | "forks": 0, 69 | "forks_count": 0, 70 | "forks_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/forks", 71 | "full_name": "dkwon17/try-in-web-ide-testing", 72 | "git_commits_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/git/commits{/sha}", 73 | "git_refs_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/git/refs{/sha}", 74 | "git_tags_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/git/tags{/sha}", 75 | "git_url": "git://github.com/dkwon17/try-in-web-ide-testing.git", 76 | "has_discussions": false, 77 | "has_downloads": true, 78 | "has_issues": true, 79 | "has_pages": false, 80 | "has_projects": true, 81 | "has_wiki": true, 82 | "homepage": null, 83 | "hooks_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/hooks", 84 | "html_url": "https://github.com/dkwon17/try-in-web-ide-testing", 85 | "id": 402869975, 86 | "is_template": false, 87 | "issue_comment_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/issues/comments{/number}", 88 | "issue_events_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/issues/events{/number}", 89 | "issues_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/issues{/number}", 90 | "keys_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/keys{/key_id}", 91 | "labels_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/labels{/name}", 92 | "language": "TypeScript", 93 | "languages_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/languages", 94 | "license": { 95 | "key": "mit", 96 | "name": "MIT License", 97 | "node_id": "MDc6TGljZW5zZTEz", 98 | "spdx_id": "MIT", 99 | "url": "https://api.github.com/licenses/mit" 100 | }, 101 | "merge_commit_message": "PR_TITLE", 102 | "merge_commit_title": "MERGE_MESSAGE", 103 | "merges_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/merges", 104 | "milestones_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/milestones{/number}", 105 | "mirror_url": null, 106 | "name": "try-in-web-ide-testing", 107 | "node_id": "MDEwOlJlcG9zaXRvcnk0MDI4Njk5NzU=", 108 | "notifications_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/notifications{?since,all,participating}", 109 | "open_issues": 9, 110 | "open_issues_count": 9, 111 | "owner": { 112 | "avatar_url": "https://avatars.githubusercontent.com/u/83611742?v=4", 113 | "events_url": "https://api.github.com/users/dkwon17/events{/privacy}", 114 | "followers_url": "https://api.github.com/users/dkwon17/followers", 115 | "following_url": "https://api.github.com/users/dkwon17/following{/other_user}", 116 | "gists_url": "https://api.github.com/users/dkwon17/gists{/gist_id}", 117 | "gravatar_id": "", 118 | "html_url": "https://github.com/dkwon17", 119 | "id": 83611742, 120 | "login": "dkwon17", 121 | "node_id": "MDQ6VXNlcjgzNjExNzQy", 122 | "organizations_url": "https://api.github.com/users/dkwon17/orgs", 123 | "received_events_url": "https://api.github.com/users/dkwon17/received_events", 124 | "repos_url": "https://api.github.com/users/dkwon17/repos", 125 | "site_admin": false, 126 | "starred_url": "https://api.github.com/users/dkwon17/starred{/owner}{/repo}", 127 | "subscriptions_url": "https://api.github.com/users/dkwon17/subscriptions", 128 | "type": "User", 129 | "url": "https://api.github.com/users/dkwon17" 130 | }, 131 | "private": true, 132 | "pulls_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/pulls{/number}", 133 | "pushed_at": "2023-01-18T16:39:22Z", 134 | "releases_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/releases{/id}", 135 | "size": 1494, 136 | "squash_merge_commit_message": "COMMIT_MESSAGES", 137 | "squash_merge_commit_title": "COMMIT_OR_PR_TITLE", 138 | "ssh_url": "git@github.com:dkwon17/try-in-web-ide-testing.git", 139 | "stargazers_count": 0, 140 | "stargazers_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/stargazers", 141 | "statuses_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/statuses/{sha}", 142 | "subscribers_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/subscribers", 143 | "subscription_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/subscription", 144 | "svn_url": "https://github.com/dkwon17/try-in-web-ide-testing", 145 | "tags_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/tags", 146 | "teams_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/teams", 147 | "topics": [], 148 | "trees_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/git/trees{/sha}", 149 | "updated_at": "2023-01-06T15:35:42Z", 150 | "url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing", 151 | "use_squash_pr_title_as_default": false, 152 | "visibility": "private", 153 | "watchers": 0, 154 | "watchers_count": 0, 155 | "web_commit_signoff_required": false 156 | }, 157 | "sha": "46955804b4099b2340ed098e9000ad895c836fc9", 158 | "user": { 159 | "avatar_url": "https://avatars.githubusercontent.com/u/83611742?v=4", 160 | "events_url": "https://api.github.com/users/dkwon17/events{/privacy}", 161 | "followers_url": "https://api.github.com/users/dkwon17/followers", 162 | "following_url": "https://api.github.com/users/dkwon17/following{/other_user}", 163 | "gists_url": "https://api.github.com/users/dkwon17/gists{/gist_id}", 164 | "gravatar_id": "", 165 | "html_url": "https://github.com/dkwon17", 166 | "id": 83611742, 167 | "login": "dkwon17", 168 | "node_id": "MDQ6VXNlcjgzNjExNzQy", 169 | "organizations_url": "https://api.github.com/users/dkwon17/orgs", 170 | "received_events_url": "https://api.github.com/users/dkwon17/received_events", 171 | "repos_url": "https://api.github.com/users/dkwon17/repos", 172 | "site_admin": false, 173 | "starred_url": "https://api.github.com/users/dkwon17/starred{/owner}{/repo}", 174 | "subscriptions_url": "https://api.github.com/users/dkwon17/subscriptions", 175 | "type": "User", 176 | "url": "https://api.github.com/users/dkwon17" 177 | } 178 | }, 179 | "body": null, 180 | "changed_files": 1, 181 | "closed_at": null, 182 | "comments": 0, 183 | "comments_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/issues/11/comments", 184 | "commits": 1, 185 | "commits_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/pulls/11/commits", 186 | "created_at": "2023-01-18T16:39:21Z", 187 | "deletions": 2, 188 | "diff_url": "https://github.com/dkwon17/try-in-web-ide-testing/pull/11.diff", 189 | "draft": false, 190 | "head": { 191 | "label": "dkwon17:pr-branch", 192 | "ref": "pr-branch", 193 | "repo": { 194 | "allow_auto_merge": false, 195 | "allow_forking": true, 196 | "allow_merge_commit": true, 197 | "allow_rebase_merge": true, 198 | "allow_squash_merge": true, 199 | "allow_update_branch": false, 200 | "archive_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/{archive_format}{/ref}", 201 | "archived": false, 202 | "assignees_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/assignees{/user}", 203 | "blobs_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/git/blobs{/sha}", 204 | "branches_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/branches{/branch}", 205 | "clone_url": "https://github.com/dkwon17/try-in-web-ide-testing.git", 206 | "collaborators_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/collaborators{/collaborator}", 207 | "comments_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/comments{/number}", 208 | "commits_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/commits{/sha}", 209 | "compare_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/compare/{base}...{head}", 210 | "contents_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/contents/{+path}", 211 | "contributors_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/contributors", 212 | "created_at": "2021-09-03T18:52:05Z", 213 | "default_branch": "main", 214 | "delete_branch_on_merge": false, 215 | "deployments_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/deployments", 216 | "description": "Testing repository for https://github.com/redhat-actions/try-in-web-ide/pull/3", 217 | "disabled": false, 218 | "downloads_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/downloads", 219 | "events_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/events", 220 | "fork": false, 221 | "forks": 0, 222 | "forks_count": 0, 223 | "forks_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/forks", 224 | "full_name": "dkwon17/try-in-web-ide-testing", 225 | "git_commits_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/git/commits{/sha}", 226 | "git_refs_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/git/refs{/sha}", 227 | "git_tags_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/git/tags{/sha}", 228 | "git_url": "git://github.com/dkwon17/try-in-web-ide-testing.git", 229 | "has_discussions": false, 230 | "has_downloads": true, 231 | "has_issues": true, 232 | "has_pages": false, 233 | "has_projects": true, 234 | "has_wiki": true, 235 | "homepage": null, 236 | "hooks_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/hooks", 237 | "html_url": "https://github.com/dkwon17/try-in-web-ide-testing", 238 | "id": 402869975, 239 | "is_template": false, 240 | "issue_comment_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/issues/comments{/number}", 241 | "issue_events_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/issues/events{/number}", 242 | "issues_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/issues{/number}", 243 | "keys_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/keys{/key_id}", 244 | "labels_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/labels{/name}", 245 | "language": "TypeScript", 246 | "languages_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/languages", 247 | "license": { 248 | "key": "mit", 249 | "name": "MIT License", 250 | "node_id": "MDc6TGljZW5zZTEz", 251 | "spdx_id": "MIT", 252 | "url": "https://api.github.com/licenses/mit" 253 | }, 254 | "merge_commit_message": "PR_TITLE", 255 | "merge_commit_title": "MERGE_MESSAGE", 256 | "merges_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/merges", 257 | "milestones_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/milestones{/number}", 258 | "mirror_url": null, 259 | "name": "try-in-web-ide-testing", 260 | "node_id": "MDEwOlJlcG9zaXRvcnk0MDI4Njk5NzU=", 261 | "notifications_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/notifications{?since,all,participating}", 262 | "open_issues": 9, 263 | "open_issues_count": 9, 264 | "owner": { 265 | "avatar_url": "https://avatars.githubusercontent.com/u/83611742?v=4", 266 | "events_url": "https://api.github.com/users/dkwon17/events{/privacy}", 267 | "followers_url": "https://api.github.com/users/dkwon17/followers", 268 | "following_url": "https://api.github.com/users/dkwon17/following{/other_user}", 269 | "gists_url": "https://api.github.com/users/dkwon17/gists{/gist_id}", 270 | "gravatar_id": "", 271 | "html_url": "https://github.com/dkwon17", 272 | "id": 83611742, 273 | "login": "dkwon17", 274 | "node_id": "MDQ6VXNlcjgzNjExNzQy", 275 | "organizations_url": "https://api.github.com/users/dkwon17/orgs", 276 | "received_events_url": "https://api.github.com/users/dkwon17/received_events", 277 | "repos_url": "https://api.github.com/users/dkwon17/repos", 278 | "site_admin": false, 279 | "starred_url": "https://api.github.com/users/dkwon17/starred{/owner}{/repo}", 280 | "subscriptions_url": "https://api.github.com/users/dkwon17/subscriptions", 281 | "type": "User", 282 | "url": "https://api.github.com/users/dkwon17" 283 | }, 284 | "private": true, 285 | "pulls_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/pulls{/number}", 286 | "pushed_at": "2023-01-18T16:39:22Z", 287 | "releases_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/releases{/id}", 288 | "size": 1494, 289 | "squash_merge_commit_message": "COMMIT_MESSAGES", 290 | "squash_merge_commit_title": "COMMIT_OR_PR_TITLE", 291 | "ssh_url": "git@github.com:dkwon17/try-in-web-ide-testing.git", 292 | "stargazers_count": 0, 293 | "stargazers_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/stargazers", 294 | "statuses_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/statuses/{sha}", 295 | "subscribers_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/subscribers", 296 | "subscription_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/subscription", 297 | "svn_url": "https://github.com/dkwon17/try-in-web-ide-testing", 298 | "tags_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/tags", 299 | "teams_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/teams", 300 | "topics": [], 301 | "trees_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/git/trees{/sha}", 302 | "updated_at": "2023-01-06T15:35:42Z", 303 | "url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing", 304 | "use_squash_pr_title_as_default": false, 305 | "visibility": "private", 306 | "watchers": 0, 307 | "watchers_count": 0, 308 | "web_commit_signoff_required": false 309 | }, 310 | "sha": "5926f5e7af27ebc6d83477998c9d5a8074beceb7", 311 | "user": { 312 | "avatar_url": "https://avatars.githubusercontent.com/u/83611742?v=4", 313 | "events_url": "https://api.github.com/users/dkwon17/events{/privacy}", 314 | "followers_url": "https://api.github.com/users/dkwon17/followers", 315 | "following_url": "https://api.github.com/users/dkwon17/following{/other_user}", 316 | "gists_url": "https://api.github.com/users/dkwon17/gists{/gist_id}", 317 | "gravatar_id": "", 318 | "html_url": "https://github.com/dkwon17", 319 | "id": 83611742, 320 | "login": "dkwon17", 321 | "node_id": "MDQ6VXNlcjgzNjExNzQy", 322 | "organizations_url": "https://api.github.com/users/dkwon17/orgs", 323 | "received_events_url": "https://api.github.com/users/dkwon17/received_events", 324 | "repos_url": "https://api.github.com/users/dkwon17/repos", 325 | "site_admin": false, 326 | "starred_url": "https://api.github.com/users/dkwon17/starred{/owner}{/repo}", 327 | "subscriptions_url": "https://api.github.com/users/dkwon17/subscriptions", 328 | "type": "User", 329 | "url": "https://api.github.com/users/dkwon17" 330 | } 331 | }, 332 | "html_url": "https://github.com/dkwon17/try-in-web-ide-testing/pull/11", 333 | "id": 1201967116, 334 | "issue_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/issues/11", 335 | "labels": [], 336 | "locked": false, 337 | "maintainer_can_modify": false, 338 | "merge_commit_sha": null, 339 | "mergeable": null, 340 | "mergeable_state": "unknown", 341 | "merged": false, 342 | "merged_at": null, 343 | "merged_by": null, 344 | "milestone": null, 345 | "node_id": "PR_kwDOGANO185HpJAM", 346 | "number": 11, 347 | "patch_url": "https://github.com/dkwon17/try-in-web-ide-testing/pull/11.patch", 348 | "rebaseable": null, 349 | "requested_reviewers": [], 350 | "requested_teams": [], 351 | "review_comment_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/pulls/comments{/number}", 352 | "review_comments": 0, 353 | "review_comments_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/pulls/11/comments", 354 | "state": "open", 355 | "statuses_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/statuses/5926f5e7af27ebc6d83477998c9d5a8074beceb7", 356 | "title": "Update README.md", 357 | "updated_at": "2023-01-18T16:39:21Z", 358 | "url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/pulls/11", 359 | "user": { 360 | "avatar_url": "https://avatars.githubusercontent.com/u/83611742?v=4", 361 | "events_url": "https://api.github.com/users/dkwon17/events{/privacy}", 362 | "followers_url": "https://api.github.com/users/dkwon17/followers", 363 | "following_url": "https://api.github.com/users/dkwon17/following{/other_user}", 364 | "gists_url": "https://api.github.com/users/dkwon17/gists{/gist_id}", 365 | "gravatar_id": "", 366 | "html_url": "https://github.com/dkwon17", 367 | "id": 83611742, 368 | "login": "dkwon17", 369 | "node_id": "MDQ6VXNlcjgzNjExNzQy", 370 | "organizations_url": "https://api.github.com/users/dkwon17/orgs", 371 | "received_events_url": "https://api.github.com/users/dkwon17/received_events", 372 | "repos_url": "https://api.github.com/users/dkwon17/repos", 373 | "site_admin": false, 374 | "starred_url": "https://api.github.com/users/dkwon17/starred{/owner}{/repo}", 375 | "subscriptions_url": "https://api.github.com/users/dkwon17/subscriptions", 376 | "type": "User", 377 | "url": "https://api.github.com/users/dkwon17" 378 | } 379 | }, 380 | "repository": { 381 | "allow_forking": true, 382 | "archive_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/{archive_format}{/ref}", 383 | "archived": false, 384 | "assignees_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/assignees{/user}", 385 | "blobs_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/git/blobs{/sha}", 386 | "branches_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/branches{/branch}", 387 | "clone_url": "https://github.com/dkwon17/try-in-web-ide-testing.git", 388 | "collaborators_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/collaborators{/collaborator}", 389 | "comments_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/comments{/number}", 390 | "commits_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/commits{/sha}", 391 | "compare_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/compare/{base}...{head}", 392 | "contents_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/contents/{+path}", 393 | "contributors_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/contributors", 394 | "created_at": "2021-09-03T18:52:05Z", 395 | "default_branch": "main", 396 | "deployments_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/deployments", 397 | "description": "Testing repository for https://github.com/redhat-actions/try-in-web-ide/pull/3", 398 | "disabled": false, 399 | "downloads_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/downloads", 400 | "events_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/events", 401 | "fork": false, 402 | "forks": 0, 403 | "forks_count": 0, 404 | "forks_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/forks", 405 | "full_name": "dkwon17/try-in-web-ide-testing", 406 | "git_commits_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/git/commits{/sha}", 407 | "git_refs_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/git/refs{/sha}", 408 | "git_tags_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/git/tags{/sha}", 409 | "git_url": "git://github.com/dkwon17/try-in-web-ide-testing.git", 410 | "has_discussions": false, 411 | "has_downloads": true, 412 | "has_issues": true, 413 | "has_pages": false, 414 | "has_projects": true, 415 | "has_wiki": true, 416 | "homepage": null, 417 | "hooks_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/hooks", 418 | "html_url": "https://github.com/dkwon17/try-in-web-ide-testing", 419 | "id": 402869975, 420 | "is_template": false, 421 | "issue_comment_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/issues/comments{/number}", 422 | "issue_events_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/issues/events{/number}", 423 | "issues_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/issues{/number}", 424 | "keys_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/keys{/key_id}", 425 | "labels_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/labels{/name}", 426 | "language": "TypeScript", 427 | "languages_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/languages", 428 | "license": { 429 | "key": "mit", 430 | "name": "MIT License", 431 | "node_id": "MDc6TGljZW5zZTEz", 432 | "spdx_id": "MIT", 433 | "url": "https://api.github.com/licenses/mit" 434 | }, 435 | "merges_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/merges", 436 | "milestones_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/milestones{/number}", 437 | "mirror_url": null, 438 | "name": "try-in-web-ide-testing", 439 | "node_id": "MDEwOlJlcG9zaXRvcnk0MDI4Njk5NzU=", 440 | "notifications_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/notifications{?since,all,participating}", 441 | "open_issues": 9, 442 | "open_issues_count": 9, 443 | "owner": { 444 | "avatar_url": "https://avatars.githubusercontent.com/u/83611742?v=4", 445 | "events_url": "https://api.github.com/users/dkwon17/events{/privacy}", 446 | "followers_url": "https://api.github.com/users/dkwon17/followers", 447 | "following_url": "https://api.github.com/users/dkwon17/following{/other_user}", 448 | "gists_url": "https://api.github.com/users/dkwon17/gists{/gist_id}", 449 | "gravatar_id": "", 450 | "html_url": "https://github.com/dkwon17", 451 | "id": 83611742, 452 | "login": "dkwon17", 453 | "node_id": "MDQ6VXNlcjgzNjExNzQy", 454 | "organizations_url": "https://api.github.com/users/dkwon17/orgs", 455 | "received_events_url": "https://api.github.com/users/dkwon17/received_events", 456 | "repos_url": "https://api.github.com/users/dkwon17/repos", 457 | "site_admin": false, 458 | "starred_url": "https://api.github.com/users/dkwon17/starred{/owner}{/repo}", 459 | "subscriptions_url": "https://api.github.com/users/dkwon17/subscriptions", 460 | "type": "User", 461 | "url": "https://api.github.com/users/dkwon17" 462 | }, 463 | "private": true, 464 | "pulls_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/pulls{/number}", 465 | "pushed_at": "2023-01-18T16:39:22Z", 466 | "releases_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/releases{/id}", 467 | "size": 1494, 468 | "ssh_url": "git@github.com:dkwon17/try-in-web-ide-testing.git", 469 | "stargazers_count": 0, 470 | "stargazers_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/stargazers", 471 | "statuses_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/statuses/{sha}", 472 | "subscribers_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/subscribers", 473 | "subscription_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/subscription", 474 | "svn_url": "https://github.com/dkwon17/try-in-web-ide-testing", 475 | "tags_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/tags", 476 | "teams_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/teams", 477 | "topics": [], 478 | "trees_url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing/git/trees{/sha}", 479 | "updated_at": "2023-01-06T15:35:42Z", 480 | "url": "https://api.github.com/repos/dkwon17/try-in-web-ide-testing", 481 | "visibility": "private", 482 | "watchers": 0, 483 | "watchers_count": 0, 484 | "web_commit_signoff_required": false 485 | }, 486 | "sender": { 487 | "avatar_url": "https://avatars.githubusercontent.com/u/83611742?v=4", 488 | "events_url": "https://api.github.com/users/dkwon17/events{/privacy}", 489 | "followers_url": "https://api.github.com/users/dkwon17/followers", 490 | "following_url": "https://api.github.com/users/dkwon17/following{/other_user}", 491 | "gists_url": "https://api.github.com/users/dkwon17/gists{/gist_id}", 492 | "gravatar_id": "", 493 | "html_url": "https://github.com/dkwon17", 494 | "id": 83611742, 495 | "login": "dkwon17", 496 | "node_id": "MDQ6VXNlcjgzNjExNzQy", 497 | "organizations_url": "https://api.github.com/users/dkwon17/orgs", 498 | "received_events_url": "https://api.github.com/users/dkwon17/received_events", 499 | "repos_url": "https://api.github.com/users/dkwon17/repos", 500 | "site_admin": false, 501 | "starred_url": "https://api.github.com/users/dkwon17/starred{/owner}{/repo}", 502 | "subscriptions_url": "https://api.github.com/users/dkwon17/subscriptions", 503 | "type": "User", 504 | "url": "https://api.github.com/users/dkwon17" 505 | } 506 | } 507 | -------------------------------------------------------------------------------- /tests/_data/pull_request/opened/create-pr.json: -------------------------------------------------------------------------------- 1 | { 2 | "action": "opened", 3 | "number": 9, 4 | "pull_request": { 5 | "_links": { 6 | "comments": { 7 | "href": "https://api.github.com/repos/benoitf/demo-gh-event/issues/9/comments" 8 | }, 9 | "commits": { 10 | "href": "https://api.github.com/repos/benoitf/demo-gh-event/pulls/9/commits" 11 | }, 12 | "html": { 13 | "href": "https://github.com/benoitf/demo-gh-event/pull/9" 14 | }, 15 | "issue": { 16 | "href": "https://api.github.com/repos/benoitf/demo-gh-event/issues/9" 17 | }, 18 | "review_comment": { 19 | "href": "https://api.github.com/repos/benoitf/demo-gh-event/pulls/comments{/number}" 20 | }, 21 | "review_comments": { 22 | "href": "https://api.github.com/repos/benoitf/demo-gh-event/pulls/9/comments" 23 | }, 24 | "self": { 25 | "href": "https://api.github.com/repos/benoitf/demo-gh-event/pulls/9" 26 | }, 27 | "statuses": { 28 | "href": "https://api.github.com/repos/benoitf/demo-gh-event/statuses/0a41a0671b5d98a5825f18d5fd6c5c1d95366dec" 29 | } 30 | }, 31 | "additions": 1, 32 | "assignee": null, 33 | "assignees": [], 34 | "author_association": "NONE", 35 | "base": { 36 | "label": "benoitf:master", 37 | "ref": "master", 38 | "repo": { 39 | "archive_url": "https://api.github.com/repos/benoitf/demo-gh-event/{archive_format}{/ref}", 40 | "archived": false, 41 | "assignees_url": "https://api.github.com/repos/benoitf/demo-gh-event/assignees{/user}", 42 | "blobs_url": "https://api.github.com/repos/benoitf/demo-gh-event/git/blobs{/sha}", 43 | "branches_url": "https://api.github.com/repos/benoitf/demo-gh-event/branches{/branch}", 44 | "clone_url": "https://github.com/benoitf/demo-gh-event.git", 45 | "collaborators_url": "https://api.github.com/repos/benoitf/demo-gh-event/collaborators{/collaborator}", 46 | "comments_url": "https://api.github.com/repos/benoitf/demo-gh-event/comments{/number}", 47 | "commits_url": "https://api.github.com/repos/benoitf/demo-gh-event/commits{/sha}", 48 | "compare_url": "https://api.github.com/repos/benoitf/demo-gh-event/compare/{base}...{head}", 49 | "contents_url": "https://api.github.com/repos/benoitf/demo-gh-event/contents/{+path}", 50 | "contributors_url": "https://api.github.com/repos/benoitf/demo-gh-event/contributors", 51 | "created_at": "2020-05-14T16:48:10Z", 52 | "default_branch": "master", 53 | "deployments_url": "https://api.github.com/repos/benoitf/demo-gh-event/deployments", 54 | "description": null, 55 | "disabled": false, 56 | "downloads_url": "https://api.github.com/repos/benoitf/demo-gh-event/downloads", 57 | "events_url": "https://api.github.com/repos/benoitf/demo-gh-event/events", 58 | "fork": false, 59 | "forks": 1, 60 | "forks_count": 1, 61 | "forks_url": "https://api.github.com/repos/benoitf/demo-gh-event/forks", 62 | "full_name": "benoitf/demo-gh-event", 63 | "git_commits_url": "https://api.github.com/repos/benoitf/demo-gh-event/git/commits{/sha}", 64 | "git_refs_url": "https://api.github.com/repos/benoitf/demo-gh-event/git/refs{/sha}", 65 | "git_tags_url": "https://api.github.com/repos/benoitf/demo-gh-event/git/tags{/sha}", 66 | "git_url": "git://github.com/benoitf/demo-gh-event.git", 67 | "has_downloads": true, 68 | "has_issues": true, 69 | "has_pages": false, 70 | "has_projects": true, 71 | "has_wiki": true, 72 | "homepage": null, 73 | "hooks_url": "https://api.github.com/repos/benoitf/demo-gh-event/hooks", 74 | "html_url": "https://github.com/benoitf/demo-gh-event", 75 | "id": 263972181, 76 | "issue_comment_url": "https://api.github.com/repos/benoitf/demo-gh-event/issues/comments{/number}", 77 | "issue_events_url": "https://api.github.com/repos/benoitf/demo-gh-event/issues/events{/number}", 78 | "issues_url": "https://api.github.com/repos/benoitf/demo-gh-event/issues{/number}", 79 | "keys_url": "https://api.github.com/repos/benoitf/demo-gh-event/keys{/key_id}", 80 | "labels_url": "https://api.github.com/repos/benoitf/demo-gh-event/labels{/name}", 81 | "language": null, 82 | "languages_url": "https://api.github.com/repos/benoitf/demo-gh-event/languages", 83 | "license": null, 84 | "merges_url": "https://api.github.com/repos/benoitf/demo-gh-event/merges", 85 | "milestones_url": "https://api.github.com/repos/benoitf/demo-gh-event/milestones{/number}", 86 | "mirror_url": null, 87 | "name": "demo-gh-event", 88 | "node_id": "MDEwOlJlcG9zaXRvcnkyNjM5NzIxODE=", 89 | "notifications_url": "https://api.github.com/repos/benoitf/demo-gh-event/notifications{?since,all,participating}", 90 | "open_issues": 8, 91 | "open_issues_count": 8, 92 | "owner": { 93 | "avatar_url": "https://avatars1.githubusercontent.com/u/436777?v=4", 94 | "events_url": "https://api.github.com/users/benoitf/events{/privacy}", 95 | "followers_url": "https://api.github.com/users/benoitf/followers", 96 | "following_url": "https://api.github.com/users/benoitf/following{/other_user}", 97 | "gists_url": "https://api.github.com/users/benoitf/gists{/gist_id}", 98 | "gravatar_id": "", 99 | "html_url": "https://github.com/benoitf", 100 | "id": 436777, 101 | "login": "benoitf", 102 | "node_id": "MDQ6VXNlcjQzNjc3Nw==", 103 | "organizations_url": "https://api.github.com/users/benoitf/orgs", 104 | "received_events_url": "https://api.github.com/users/benoitf/received_events", 105 | "repos_url": "https://api.github.com/users/benoitf/repos", 106 | "site_admin": false, 107 | "starred_url": "https://api.github.com/users/benoitf/starred{/owner}{/repo}", 108 | "subscriptions_url": "https://api.github.com/users/benoitf/subscriptions", 109 | "type": "User", 110 | "url": "https://api.github.com/users/benoitf" 111 | }, 112 | "private": false, 113 | "pulls_url": "https://api.github.com/repos/benoitf/demo-gh-event/pulls{/number}", 114 | "pushed_at": "2020-05-18T15:07:24Z", 115 | "releases_url": "https://api.github.com/repos/benoitf/demo-gh-event/releases{/id}", 116 | "size": 15, 117 | "ssh_url": "git@github.com:benoitf/demo-gh-event.git", 118 | "stargazers_count": 0, 119 | "stargazers_url": "https://api.github.com/repos/benoitf/demo-gh-event/stargazers", 120 | "statuses_url": "https://api.github.com/repos/benoitf/demo-gh-event/statuses/{sha}", 121 | "subscribers_url": "https://api.github.com/repos/benoitf/demo-gh-event/subscribers", 122 | "subscription_url": "https://api.github.com/repos/benoitf/demo-gh-event/subscription", 123 | "svn_url": "https://github.com/benoitf/demo-gh-event", 124 | "tags_url": "https://api.github.com/repos/benoitf/demo-gh-event/tags", 125 | "teams_url": "https://api.github.com/repos/benoitf/demo-gh-event/teams", 126 | "trees_url": "https://api.github.com/repos/benoitf/demo-gh-event/git/trees{/sha}", 127 | "updated_at": "2020-05-18T15:07:26Z", 128 | "url": "https://api.github.com/repos/benoitf/demo-gh-event", 129 | "watchers": 0, 130 | "watchers_count": 0 131 | }, 132 | "sha": "b5d22409d9e601f945c15081c457b6cd8ce0010e", 133 | "user": { 134 | "avatar_url": "https://avatars1.githubusercontent.com/u/436777?v=4", 135 | "events_url": "https://api.github.com/users/benoitf/events{/privacy}", 136 | "followers_url": "https://api.github.com/users/benoitf/followers", 137 | "following_url": "https://api.github.com/users/benoitf/following{/other_user}", 138 | "gists_url": "https://api.github.com/users/benoitf/gists{/gist_id}", 139 | "gravatar_id": "", 140 | "html_url": "https://github.com/benoitf", 141 | "id": 436777, 142 | "login": "benoitf", 143 | "node_id": "MDQ6VXNlcjQzNjc3Nw==", 144 | "organizations_url": "https://api.github.com/users/benoitf/orgs", 145 | "received_events_url": "https://api.github.com/users/benoitf/received_events", 146 | "repos_url": "https://api.github.com/users/benoitf/repos", 147 | "site_admin": false, 148 | "starred_url": "https://api.github.com/users/benoitf/starred{/owner}{/repo}", 149 | "subscriptions_url": "https://api.github.com/users/benoitf/subscriptions", 150 | "type": "User", 151 | "url": "https://api.github.com/users/benoitf" 152 | } 153 | }, 154 | "body": "Description of the PR", 155 | "changed_files": 1, 156 | "closed_at": null, 157 | "comments": 0, 158 | "comments_url": "https://api.github.com/repos/benoitf/demo-gh-event/issues/9/comments", 159 | "commits": 1, 160 | "commits_url": "https://api.github.com/repos/benoitf/demo-gh-event/pulls/9/commits", 161 | "created_at": "2020-05-19T06:05:57Z", 162 | "deletions": 1, 163 | "diff_url": "https://github.com/benoitf/demo-gh-event/pull/9.diff", 164 | "draft": false, 165 | "head": { 166 | "label": "chetrend:patch-2", 167 | "ref": "patch-2", 168 | "repo": { 169 | "archive_url": "https://api.github.com/repos/chetrend/demo-gh-event/{archive_format}{/ref}", 170 | "archived": false, 171 | "assignees_url": "https://api.github.com/repos/chetrend/demo-gh-event/assignees{/user}", 172 | "blobs_url": "https://api.github.com/repos/chetrend/demo-gh-event/git/blobs{/sha}", 173 | "branches_url": "https://api.github.com/repos/chetrend/demo-gh-event/branches{/branch}", 174 | "clone_url": "https://github.com/chetrend/demo-gh-event.git", 175 | "collaborators_url": "https://api.github.com/repos/chetrend/demo-gh-event/collaborators{/collaborator}", 176 | "comments_url": "https://api.github.com/repos/chetrend/demo-gh-event/comments{/number}", 177 | "commits_url": "https://api.github.com/repos/chetrend/demo-gh-event/commits{/sha}", 178 | "compare_url": "https://api.github.com/repos/chetrend/demo-gh-event/compare/{base}...{head}", 179 | "contents_url": "https://api.github.com/repos/chetrend/demo-gh-event/contents/{+path}", 180 | "contributors_url": "https://api.github.com/repos/chetrend/demo-gh-event/contributors", 181 | "created_at": "2020-05-19T06:04:36Z", 182 | "default_branch": "master", 183 | "deployments_url": "https://api.github.com/repos/chetrend/demo-gh-event/deployments", 184 | "description": null, 185 | "disabled": false, 186 | "downloads_url": "https://api.github.com/repos/chetrend/demo-gh-event/downloads", 187 | "events_url": "https://api.github.com/repos/chetrend/demo-gh-event/events", 188 | "fork": true, 189 | "forks": 0, 190 | "forks_count": 0, 191 | "forks_url": "https://api.github.com/repos/chetrend/demo-gh-event/forks", 192 | "full_name": "chetrend/demo-gh-event", 193 | "git_commits_url": "https://api.github.com/repos/chetrend/demo-gh-event/git/commits{/sha}", 194 | "git_refs_url": "https://api.github.com/repos/chetrend/demo-gh-event/git/refs{/sha}", 195 | "git_tags_url": "https://api.github.com/repos/chetrend/demo-gh-event/git/tags{/sha}", 196 | "git_url": "git://github.com/chetrend/demo-gh-event.git", 197 | "has_downloads": true, 198 | "has_issues": false, 199 | "has_pages": false, 200 | "has_projects": true, 201 | "has_wiki": true, 202 | "homepage": null, 203 | "hooks_url": "https://api.github.com/repos/chetrend/demo-gh-event/hooks", 204 | "html_url": "https://github.com/chetrend/demo-gh-event", 205 | "id": 265159790, 206 | "issue_comment_url": "https://api.github.com/repos/chetrend/demo-gh-event/issues/comments{/number}", 207 | "issue_events_url": "https://api.github.com/repos/chetrend/demo-gh-event/issues/events{/number}", 208 | "issues_url": "https://api.github.com/repos/chetrend/demo-gh-event/issues{/number}", 209 | "keys_url": "https://api.github.com/repos/chetrend/demo-gh-event/keys{/key_id}", 210 | "labels_url": "https://api.github.com/repos/chetrend/demo-gh-event/labels{/name}", 211 | "language": null, 212 | "languages_url": "https://api.github.com/repos/chetrend/demo-gh-event/languages", 213 | "license": null, 214 | "merges_url": "https://api.github.com/repos/chetrend/demo-gh-event/merges", 215 | "milestones_url": "https://api.github.com/repos/chetrend/demo-gh-event/milestones{/number}", 216 | "mirror_url": null, 217 | "name": "demo-gh-event", 218 | "node_id": "MDEwOlJlcG9zaXRvcnkyNjUxNTk3OTA=", 219 | "notifications_url": "https://api.github.com/repos/chetrend/demo-gh-event/notifications{?since,all,participating}", 220 | "open_issues": 0, 221 | "open_issues_count": 0, 222 | "owner": { 223 | "avatar_url": "https://avatars3.githubusercontent.com/u/65301171?v=4", 224 | "events_url": "https://api.github.com/users/chetrend/events{/privacy}", 225 | "followers_url": "https://api.github.com/users/chetrend/followers", 226 | "following_url": "https://api.github.com/users/chetrend/following{/other_user}", 227 | "gists_url": "https://api.github.com/users/chetrend/gists{/gist_id}", 228 | "gravatar_id": "", 229 | "html_url": "https://github.com/chetrend", 230 | "id": 65301171, 231 | "login": "chetrend", 232 | "node_id": "MDQ6VXNlcjY1MzAxMTcx", 233 | "organizations_url": "https://api.github.com/users/chetrend/orgs", 234 | "received_events_url": "https://api.github.com/users/chetrend/received_events", 235 | "repos_url": "https://api.github.com/users/chetrend/repos", 236 | "site_admin": false, 237 | "starred_url": "https://api.github.com/users/chetrend/starred{/owner}{/repo}", 238 | "subscriptions_url": "https://api.github.com/users/chetrend/subscriptions", 239 | "type": "User", 240 | "url": "https://api.github.com/users/chetrend" 241 | }, 242 | "private": false, 243 | "pulls_url": "https://api.github.com/repos/chetrend/demo-gh-event/pulls{/number}", 244 | "pushed_at": "2020-05-19T06:05:30Z", 245 | "releases_url": "https://api.github.com/repos/chetrend/demo-gh-event/releases{/id}", 246 | "size": 15, 247 | "ssh_url": "git@github.com:chetrend/demo-gh-event.git", 248 | "stargazers_count": 0, 249 | "stargazers_url": "https://api.github.com/repos/chetrend/demo-gh-event/stargazers", 250 | "statuses_url": "https://api.github.com/repos/chetrend/demo-gh-event/statuses/{sha}", 251 | "subscribers_url": "https://api.github.com/repos/chetrend/demo-gh-event/subscribers", 252 | "subscription_url": "https://api.github.com/repos/chetrend/demo-gh-event/subscription", 253 | "svn_url": "https://github.com/chetrend/demo-gh-event", 254 | "tags_url": "https://api.github.com/repos/chetrend/demo-gh-event/tags", 255 | "teams_url": "https://api.github.com/repos/chetrend/demo-gh-event/teams", 256 | "trees_url": "https://api.github.com/repos/chetrend/demo-gh-event/git/trees{/sha}", 257 | "updated_at": "2020-05-19T06:04:39Z", 258 | "url": "https://api.github.com/repos/chetrend/demo-gh-event", 259 | "watchers": 0, 260 | "watchers_count": 0 261 | }, 262 | "sha": "0a41a0671b5d98a5825f18d5fd6c5c1d95366dec", 263 | "user": { 264 | "avatar_url": "https://avatars3.githubusercontent.com/u/65301171?v=4", 265 | "events_url": "https://api.github.com/users/chetrend/events{/privacy}", 266 | "followers_url": "https://api.github.com/users/chetrend/followers", 267 | "following_url": "https://api.github.com/users/chetrend/following{/other_user}", 268 | "gists_url": "https://api.github.com/users/chetrend/gists{/gist_id}", 269 | "gravatar_id": "", 270 | "html_url": "https://github.com/chetrend", 271 | "id": 65301171, 272 | "login": "chetrend", 273 | "node_id": "MDQ6VXNlcjY1MzAxMTcx", 274 | "organizations_url": "https://api.github.com/users/chetrend/orgs", 275 | "received_events_url": "https://api.github.com/users/chetrend/received_events", 276 | "repos_url": "https://api.github.com/users/chetrend/repos", 277 | "site_admin": false, 278 | "starred_url": "https://api.github.com/users/chetrend/starred{/owner}{/repo}", 279 | "subscriptions_url": "https://api.github.com/users/chetrend/subscriptions", 280 | "type": "User", 281 | "url": "https://api.github.com/users/chetrend" 282 | } 283 | }, 284 | "html_url": "https://github.com/benoitf/demo-gh-event/pull/9", 285 | "id": 419898094, 286 | "issue_url": "https://api.github.com/repos/benoitf/demo-gh-event/issues/9", 287 | "labels": [{ "name": "foo" }], 288 | "locked": false, 289 | "maintainer_can_modify": true, 290 | "merge_commit_sha": null, 291 | "mergeable": null, 292 | "mergeable_state": "unknown", 293 | "merged": false, 294 | "merged_at": null, 295 | "merged_by": null, 296 | "milestone": null, 297 | "node_id": "MDExOlB1bGxSZXF1ZXN0NDE5ODk4MDk0", 298 | "number": 9, 299 | "patch_url": "https://github.com/benoitf/demo-gh-event/pull/9.patch", 300 | "rebaseable": null, 301 | "requested_reviewers": [], 302 | "requested_teams": [], 303 | "review_comment_url": "https://api.github.com/repos/benoitf/demo-gh-event/pulls/comments{/number}", 304 | "review_comments": 0, 305 | "review_comments_url": "https://api.github.com/repos/benoitf/demo-gh-event/pulls/9/comments", 306 | "state": "open", 307 | "statuses_url": "https://api.github.com/repos/benoitf/demo-gh-event/statuses/0a41a0671b5d98a5825f18d5fd6c5c1d95366dec", 308 | "title": "PR title Update README", 309 | "updated_at": "2020-05-19T06:05:58Z", 310 | "url": "https://api.github.com/repos/benoitf/demo-gh-event/pulls/9", 311 | "user": { 312 | "avatar_url": "https://avatars3.githubusercontent.com/u/65301171?v=4", 313 | "events_url": "https://api.github.com/users/chetrend/events{/privacy}", 314 | "followers_url": "https://api.github.com/users/chetrend/followers", 315 | "following_url": "https://api.github.com/users/chetrend/following{/other_user}", 316 | "gists_url": "https://api.github.com/users/chetrend/gists{/gist_id}", 317 | "gravatar_id": "", 318 | "html_url": "https://github.com/chetrend", 319 | "id": 65301171, 320 | "login": "chetrend", 321 | "node_id": "MDQ6VXNlcjY1MzAxMTcx", 322 | "organizations_url": "https://api.github.com/users/chetrend/orgs", 323 | "received_events_url": "https://api.github.com/users/chetrend/received_events", 324 | "repos_url": "https://api.github.com/users/chetrend/repos", 325 | "site_admin": false, 326 | "starred_url": "https://api.github.com/users/chetrend/starred{/owner}{/repo}", 327 | "subscriptions_url": "https://api.github.com/users/chetrend/subscriptions", 328 | "type": "User", 329 | "url": "https://api.github.com/users/chetrend" 330 | } 331 | }, 332 | "repository": { 333 | "archive_url": "https://api.github.com/repos/benoitf/demo-gh-event/{archive_format}{/ref}", 334 | "archived": false, 335 | "assignees_url": "https://api.github.com/repos/benoitf/demo-gh-event/assignees{/user}", 336 | "blobs_url": "https://api.github.com/repos/benoitf/demo-gh-event/git/blobs{/sha}", 337 | "branches_url": "https://api.github.com/repos/benoitf/demo-gh-event/branches{/branch}", 338 | "clone_url": "https://github.com/benoitf/demo-gh-event.git", 339 | "collaborators_url": "https://api.github.com/repos/benoitf/demo-gh-event/collaborators{/collaborator}", 340 | "comments_url": "https://api.github.com/repos/benoitf/demo-gh-event/comments{/number}", 341 | "commits_url": "https://api.github.com/repos/benoitf/demo-gh-event/commits{/sha}", 342 | "compare_url": "https://api.github.com/repos/benoitf/demo-gh-event/compare/{base}...{head}", 343 | "contents_url": "https://api.github.com/repos/benoitf/demo-gh-event/contents/{+path}", 344 | "contributors_url": "https://api.github.com/repos/benoitf/demo-gh-event/contributors", 345 | "created_at": "2020-05-14T16:48:10Z", 346 | "default_branch": "master", 347 | "deployments_url": "https://api.github.com/repos/benoitf/demo-gh-event/deployments", 348 | "description": null, 349 | "disabled": false, 350 | "downloads_url": "https://api.github.com/repos/benoitf/demo-gh-event/downloads", 351 | "events_url": "https://api.github.com/repos/benoitf/demo-gh-event/events", 352 | "fork": false, 353 | "forks": 1, 354 | "forks_count": 1, 355 | "forks_url": "https://api.github.com/repos/benoitf/demo-gh-event/forks", 356 | "full_name": "benoitf/demo-gh-event", 357 | "git_commits_url": "https://api.github.com/repos/benoitf/demo-gh-event/git/commits{/sha}", 358 | "git_refs_url": "https://api.github.com/repos/benoitf/demo-gh-event/git/refs{/sha}", 359 | "git_tags_url": "https://api.github.com/repos/benoitf/demo-gh-event/git/tags{/sha}", 360 | "git_url": "git://github.com/benoitf/demo-gh-event.git", 361 | "has_downloads": true, 362 | "has_issues": true, 363 | "has_pages": false, 364 | "has_projects": true, 365 | "has_wiki": true, 366 | "homepage": null, 367 | "hooks_url": "https://api.github.com/repos/benoitf/demo-gh-event/hooks", 368 | "html_url": "https://github.com/benoitf/demo-gh-event", 369 | "id": 263972181, 370 | "issue_comment_url": "https://api.github.com/repos/benoitf/demo-gh-event/issues/comments{/number}", 371 | "issue_events_url": "https://api.github.com/repos/benoitf/demo-gh-event/issues/events{/number}", 372 | "issues_url": "https://api.github.com/repos/benoitf/demo-gh-event/issues{/number}", 373 | "keys_url": "https://api.github.com/repos/benoitf/demo-gh-event/keys{/key_id}", 374 | "labels_url": "https://api.github.com/repos/benoitf/demo-gh-event/labels{/name}", 375 | "language": null, 376 | "languages_url": "https://api.github.com/repos/benoitf/demo-gh-event/languages", 377 | "license": null, 378 | "merges_url": "https://api.github.com/repos/benoitf/demo-gh-event/merges", 379 | "milestones_url": "https://api.github.com/repos/benoitf/demo-gh-event/milestones{/number}", 380 | "mirror_url": null, 381 | "name": "demo-gh-event", 382 | "node_id": "MDEwOlJlcG9zaXRvcnkyNjM5NzIxODE=", 383 | "notifications_url": "https://api.github.com/repos/benoitf/demo-gh-event/notifications{?since,all,participating}", 384 | "open_issues": 8, 385 | "open_issues_count": 8, 386 | "owner": { 387 | "avatar_url": "https://avatars1.githubusercontent.com/u/436777?v=4", 388 | "events_url": "https://api.github.com/users/benoitf/events{/privacy}", 389 | "followers_url": "https://api.github.com/users/benoitf/followers", 390 | "following_url": "https://api.github.com/users/benoitf/following{/other_user}", 391 | "gists_url": "https://api.github.com/users/benoitf/gists{/gist_id}", 392 | "gravatar_id": "", 393 | "html_url": "https://github.com/benoitf", 394 | "id": 436777, 395 | "login": "benoitf", 396 | "node_id": "MDQ6VXNlcjQzNjc3Nw==", 397 | "organizations_url": "https://api.github.com/users/benoitf/orgs", 398 | "received_events_url": "https://api.github.com/users/benoitf/received_events", 399 | "repos_url": "https://api.github.com/users/benoitf/repos", 400 | "site_admin": false, 401 | "starred_url": "https://api.github.com/users/benoitf/starred{/owner}{/repo}", 402 | "subscriptions_url": "https://api.github.com/users/benoitf/subscriptions", 403 | "type": "User", 404 | "url": "https://api.github.com/users/benoitf" 405 | }, 406 | "private": false, 407 | "pulls_url": "https://api.github.com/repos/benoitf/demo-gh-event/pulls{/number}", 408 | "pushed_at": "2020-05-18T15:07:24Z", 409 | "releases_url": "https://api.github.com/repos/benoitf/demo-gh-event/releases{/id}", 410 | "size": 15, 411 | "ssh_url": "git@github.com:benoitf/demo-gh-event.git", 412 | "stargazers_count": 0, 413 | "stargazers_url": "https://api.github.com/repos/benoitf/demo-gh-event/stargazers", 414 | "statuses_url": "https://api.github.com/repos/benoitf/demo-gh-event/statuses/{sha}", 415 | "subscribers_url": "https://api.github.com/repos/benoitf/demo-gh-event/subscribers", 416 | "subscription_url": "https://api.github.com/repos/benoitf/demo-gh-event/subscription", 417 | "svn_url": "https://github.com/benoitf/demo-gh-event", 418 | "tags_url": "https://api.github.com/repos/benoitf/demo-gh-event/tags", 419 | "teams_url": "https://api.github.com/repos/benoitf/demo-gh-event/teams", 420 | "trees_url": "https://api.github.com/repos/benoitf/demo-gh-event/git/trees{/sha}", 421 | "updated_at": "2020-05-18T15:07:26Z", 422 | "url": "https://api.github.com/repos/benoitf/demo-gh-event", 423 | "watchers": 0, 424 | "watchers_count": 0 425 | }, 426 | "sender": { 427 | "avatar_url": "https://avatars3.githubusercontent.com/u/65301171?v=4", 428 | "events_url": "https://api.github.com/users/chetrend/events{/privacy}", 429 | "followers_url": "https://api.github.com/users/chetrend/followers", 430 | "following_url": "https://api.github.com/users/chetrend/following{/other_user}", 431 | "gists_url": "https://api.github.com/users/chetrend/gists{/gist_id}", 432 | "gravatar_id": "", 433 | "html_url": "https://github.com/chetrend", 434 | "id": 65301171, 435 | "login": "chetrend", 436 | "node_id": "MDQ6VXNlcjY1MzAxMTcx", 437 | "organizations_url": "https://api.github.com/users/chetrend/orgs", 438 | "received_events_url": "https://api.github.com/users/chetrend/received_events", 439 | "repos_url": "https://api.github.com/users/chetrend/repos", 440 | "site_admin": false, 441 | "starred_url": "https://api.github.com/users/chetrend/starred{/owner}{/repo}", 442 | "subscriptions_url": "https://api.github.com/users/chetrend/subscriptions", 443 | "type": "User", 444 | "url": "https://api.github.com/users/chetrend" 445 | } 446 | } 447 | -------------------------------------------------------------------------------- /tests/actions/pull-request-action.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import "reflect-metadata"; 3 | 4 | import * as fs from "fs-extra"; 5 | import * as path from "path"; 6 | 7 | import { Container } from "inversify"; 8 | import { WebhookPayloadPullRequest } from "@octokit/webhooks"; 9 | import { PullRequestAction } from "../../src/actions/pull-request-action"; 10 | 11 | describe("Test Action PullRequestAction", () => { 12 | let container: Container; 13 | 14 | beforeEach(() => { 15 | container = new Container(); 16 | container.bind(PullRequestAction).toSelf().inSingletonScope(); 17 | }); 18 | 19 | afterEach(() => { 20 | jest.resetAllMocks(); 21 | }); 22 | 23 | test("test not execute as event is unknown", async () => { 24 | const pullRequestAction = container.get(PullRequestAction); 25 | 26 | const payload: WebhookPayloadPullRequest = jest.fn() as any; 27 | 28 | let receivedPayload: WebhookPayloadPullRequest | undefined; 29 | 30 | const fooMock: any = { dummyCall: jest.fn() }; 31 | pullRequestAction.registerCallback( 32 | [ "unknown-event" ], 33 | async (_payload: WebhookPayloadPullRequest) => { 34 | fooMock.dummyCall(); 35 | receivedPayload = _payload; 36 | } 37 | ); 38 | 39 | // duplicate callback to check we add twice the callbacks 40 | pullRequestAction.registerCallback( 41 | [ "unknown-event" ], 42 | async (_payload: WebhookPayloadPullRequest) => { 43 | fooMock.dummyCall(); 44 | receivedPayload = _payload; 45 | } 46 | ); 47 | 48 | await pullRequestAction.execute(payload); 49 | expect(fooMock.dummyCall).toBeCalledTimes(0); 50 | expect(receivedPayload).toBeUndefined(); 51 | }); 52 | 53 | // opened event should trigger action 54 | test("test single opened execute", async () => { 55 | const pullRequestAction = container.get(PullRequestAction); 56 | 57 | const json = await fs.readJSON( 58 | path.join( 59 | __dirname, 60 | "..", 61 | "_data", 62 | "pull_request", 63 | "opened", 64 | "create-pr.json" 65 | ) 66 | ); 67 | 68 | let receivedPayload: WebhookPayloadPullRequest = {} as any; 69 | const fooMock: any = { dummyCall: jest.fn() }; 70 | await pullRequestAction.registerCallback( 71 | [ "opened" ], 72 | async (payload: WebhookPayloadPullRequest) => { 73 | fooMock.dummyCall(); 74 | receivedPayload = payload; 75 | } 76 | ); 77 | 78 | await pullRequestAction.execute(json); 79 | expect(fooMock.dummyCall).toHaveBeenCalled(); 80 | expect(receivedPayload).toBeDefined(); 81 | expect(receivedPayload.repository.name).toEqual("demo-gh-event"); 82 | expect(receivedPayload.repository.owner.login).toEqual("benoitf"); 83 | expect(receivedPayload.number).toEqual(9); 84 | expect(receivedPayload.sender.login).toEqual("chetrend"); 85 | }); 86 | }); 87 | -------------------------------------------------------------------------------- /tests/analysis.spec.ts: -------------------------------------------------------------------------------- 1 | import "reflect-metadata"; 2 | 3 | import { Container } from "inversify"; 4 | import { Analysis } from "../src/analysis"; 5 | import { Handler } from "../src/api/handler"; 6 | import { bindMultiInjectProvider } from "../src/api/multi-inject-provider"; 7 | 8 | describe("Test Analysis", () => { 9 | let container: Container; 10 | 11 | beforeEach(() => { 12 | container = new Container(); 13 | bindMultiInjectProvider(container, Handler); 14 | container.bind(Analysis).toSelf().inSingletonScope(); 15 | }); 16 | 17 | test("test handle accepted", async () => { 18 | const handler1: Handler = { 19 | supports: jest.fn(), 20 | handle: jest.fn(), 21 | }; 22 | // first handler supports the call 23 | (handler1.supports as jest.Mock).mockReturnValue(true); 24 | 25 | container.bind(Handler).toConstantValue(handler1); 26 | 27 | const eventName1 = "eventName1"; 28 | 29 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 30 | const context1: any = { 31 | eventName: eventName1, 32 | payload: jest.fn(), 33 | }; 34 | 35 | const analysis = container.get(Analysis); 36 | await analysis.analyze(context1); 37 | 38 | expect(handler1.supports).toBeCalled(); 39 | expect(handler1.handle).toBeCalled(); 40 | const call = (handler1.handle as jest.Mock).mock.calls[0]; 41 | 42 | expect(call[0]).toEqual(eventName1); 43 | expect(call[1]).toEqual(context1.payload); 44 | }); 45 | 46 | test("test handle refused", async () => { 47 | const handler1: Handler = { 48 | supports: jest.fn(), 49 | handle: jest.fn(), 50 | }; 51 | // handler does not support the call 52 | (handler1.supports as jest.Mock).mockReturnValue(false); 53 | 54 | container.bind(Handler).toConstantValue(handler1); 55 | 56 | const eventName1 = "eventName1"; 57 | 58 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 59 | const context1: any = { 60 | eventName: eventName1, 61 | payload: jest.fn(), 62 | }; 63 | 64 | const analysis = container.get(Analysis); 65 | await analysis.analyze(context1); 66 | 67 | expect(handler1.supports).toBeCalled(); 68 | expect(handler1.handle).toHaveBeenCalledTimes(0); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /tests/handler/pull-request-handler.spec.ts: -------------------------------------------------------------------------------- 1 | import "reflect-metadata"; 2 | 3 | import * as fs from "fs-extra"; 4 | import * as path from "path"; 5 | 6 | import { Container } from "inversify"; 7 | import { Handler } from "../../src/api/handler"; 8 | import { PullRequestHandler } from "../../src/handler/pull-request-handler"; 9 | import { PullRequestListener } from "../../src/api/pull-request-listener"; 10 | import { bindMultiInjectProvider } from "../../src/api/multi-inject-provider"; 11 | 12 | describe("Test PullRequestHandler", () => { 13 | let container: Container; 14 | 15 | beforeEach(() => { 16 | container = new Container(); 17 | bindMultiInjectProvider(container, Handler); 18 | bindMultiInjectProvider(container, PullRequestListener); 19 | container.bind(Handler).to(PullRequestHandler).inSingletonScope(); 20 | }); 21 | 22 | test("test acceptance (true)", async () => { 23 | const prHandler: Handler = container.get(Handler); 24 | const supports = prHandler.supports("pull_request_target"); 25 | expect(supports).toBeTruthy(); 26 | }); 27 | 28 | test("test acceptance (false)", async () => { 29 | const handler: Handler = container.get(Handler); 30 | expect(handler.constructor.name).toEqual(PullRequestHandler.name); 31 | const prHandler: PullRequestHandler = handler as PullRequestHandler; 32 | const supports = prHandler.supports("invalid-event"); 33 | expect(supports).toBeFalsy(); 34 | }); 35 | 36 | test("test no listener", async () => { 37 | const handler: Handler = container.get(Handler); 38 | expect(handler.constructor.name).toEqual(PullRequestHandler.name); 39 | const prHandler: PullRequestHandler = handler as PullRequestHandler; 40 | const json = await fs.readJSON( 41 | path.join( 42 | __dirname, 43 | "..", 44 | "_data", 45 | "pull_request", 46 | "opened", 47 | "create-pr.json" 48 | ) 49 | ); 50 | prHandler.handle("pull_request", json); 51 | expect(prHandler["pullRequestListeners"].getAll()).toEqual([]); 52 | }); 53 | 54 | test("test call one listener", async () => { 55 | const listener: PullRequestListener = { execute: jest.fn() }; 56 | container.bind(PullRequestListener).toConstantValue(listener); 57 | const handler: Handler = container.get(Handler); 58 | expect(handler.constructor.name).toEqual(PullRequestHandler.name); 59 | const prHandler: PullRequestHandler = handler as PullRequestHandler; 60 | const json = await fs.readJSON( 61 | path.join( 62 | __dirname, 63 | "..", 64 | "_data", 65 | "pull_request", 66 | "opened", 67 | "create-pr.json" 68 | ) 69 | ); 70 | prHandler.handle("pull_request", json); 71 | expect(listener.execute).toBeCalled(); 72 | }); 73 | 74 | test("test call several listeners", async () => { 75 | // bind 2 listeners 76 | const listener: PullRequestListener = { execute: jest.fn() }; 77 | container.bind(PullRequestListener).toConstantValue(listener); 78 | const anotherListener: PullRequestListener = { execute: jest.fn() }; 79 | container.bind(PullRequestListener).toConstantValue(anotherListener); 80 | 81 | const handler: Handler = container.get(Handler); 82 | expect(handler.constructor.name).toEqual(PullRequestHandler.name); 83 | const prHandler: PullRequestHandler = handler as PullRequestHandler; 84 | const json = await fs.readJSON( 85 | path.join( 86 | __dirname, 87 | "..", 88 | "_data", 89 | "pull_request", 90 | "opened", 91 | "create-pr.json" 92 | ) 93 | ); 94 | prHandler.handle("issues", json); 95 | 96 | // two listeners 97 | expect(prHandler["pullRequestListeners"].getAll().length).toEqual(2); 98 | 99 | // each listener being invoked 100 | expect(listener.execute).toBeCalled(); 101 | expect(anotherListener.execute).toBeCalled(); 102 | }); 103 | }); 104 | -------------------------------------------------------------------------------- /tests/helpers/add-comment-helper.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | 3 | import "reflect-metadata"; 4 | 5 | import { Container } from "inversify"; 6 | import { Octokit } from "@octokit/rest"; 7 | import { WebhookPayloadPullRequest } from "@octokit/webhooks"; 8 | import { AddCommentHelper } from "../../src/helpers/add-comment-helper"; 9 | 10 | describe("Test Helper AddCommentHelper", () => { 11 | let container: Container; 12 | 13 | beforeEach(() => { 14 | container = new Container(); 15 | container.bind(AddCommentHelper).toSelf().inSingletonScope(); 16 | }); 17 | 18 | // check with label existing 19 | test("test call correct API", async () => { 20 | const octokit: any = { 21 | issues: { 22 | createComment: jest.fn().mockImplementation((_: any) => { 23 | return Promise.resolve(null); 24 | }), 25 | }, 26 | }; 27 | 28 | container.bind(Octokit).toConstantValue(octokit); 29 | const addCommentHelper = container.get(AddCommentHelper); 30 | 31 | const payload: WebhookPayloadPullRequest = { 32 | pull_request: { 33 | number: 123, 34 | }, 35 | repository: { 36 | owner: { 37 | login: "foo", 38 | }, 39 | name: "bar", 40 | }, 41 | } as any; 42 | 43 | const comment = "my-comment"; 44 | await addCommentHelper.addComment(comment, payload); 45 | expect(octokit.issues.createComment).toBeCalledWith({ 46 | body: comment, 47 | owner: "foo", 48 | repo: "bar", 49 | issue_number: 123, 50 | }); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /tests/helpers/add-status-check-helper.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | 3 | import "reflect-metadata"; 4 | 5 | import { Container } from "inversify"; 6 | import { Octokit } from "@octokit/rest"; 7 | import { WebhookPayloadPullRequest } from "@octokit/webhooks"; 8 | import { AddStatusCheckHelper } from "../../src/helpers/add-status-check-helper"; 9 | 10 | describe("Test Helper AddCommentHelper", () => { 11 | let container: Container; 12 | 13 | beforeEach(() => { 14 | container = new Container(); 15 | container.bind(AddStatusCheckHelper).toSelf().inSingletonScope(); 16 | }); 17 | 18 | // check with label existing 19 | test("test call correct API", async () => { 20 | const octokit: any = { 21 | repos: { 22 | createCommitStatus: jest.fn().mockImplementation((_: any) => { 23 | return Promise.resolve(null); 24 | }), 25 | }, 26 | }; 27 | 28 | container.bind(Octokit).toConstantValue(octokit); 29 | const addStatusCheckHelper = container.get(AddStatusCheckHelper); 30 | 31 | const payload: WebhookPayloadPullRequest = { 32 | pull_request: { 33 | head: { 34 | sha: 456, 35 | }, 36 | number: 123, 37 | }, 38 | repository: { 39 | owner: { 40 | login: "foo", 41 | }, 42 | name: "bar", 43 | }, 44 | } as any; 45 | 46 | const description = "my-desc"; 47 | const context = "my-context"; 48 | const targetUrl = "https://foobar.com"; 49 | 50 | await addStatusCheckHelper.addStatusCheck( 51 | description, 52 | context, 53 | targetUrl, 54 | payload 55 | ); 56 | 57 | expect(octokit.repos.createCommitStatus).toBeCalledWith({ 58 | description, 59 | target_url: targetUrl, 60 | context, 61 | owner: "foo", 62 | repo: "bar", 63 | sha: 456, 64 | state: "success", 65 | }); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /tests/helpers/update-comment-helper.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | 3 | import "reflect-metadata"; 4 | 5 | import { Container } from "inversify"; 6 | import { Octokit } from "@octokit/rest"; 7 | import { WebhookPayloadPullRequest } from "@octokit/webhooks"; 8 | import { UpdateCommentHelper } from "../../src/helpers/update-comment-helper"; 9 | 10 | describe("Test Helper UpdateCommentHelper", () => { 11 | const payload: WebhookPayloadPullRequest = { 12 | pull_request: { 13 | number: 1, 14 | }, 15 | repository: { 16 | owner: { 17 | login: "foo", 18 | }, 19 | name: "bar", 20 | }, 21 | } as any; 22 | 23 | let container: Container; 24 | 25 | beforeEach(() => { 26 | container = new Container(); 27 | container.bind(UpdateCommentHelper).toSelf().inSingletonScope(); 28 | }); 29 | 30 | test("test call correct API", async () => { 31 | const octokit = setComments([ 32 | { 33 | id: 1234, 34 | user: { 35 | type: "Bot", 36 | }, 37 | body: "Hello world!", 38 | }, 39 | ]); 40 | container.bind(Octokit).toConstantValue(octokit); 41 | const updateCommentHelper = container.get(UpdateCommentHelper); 42 | await updateCommentHelper.updateComment( 43 | /^(Hello world)*.$/, 44 | "my new comment", 45 | payload 46 | ); 47 | 48 | expect(octokit.issues.listComments).toBeCalledWith({ 49 | issue_number: 1, 50 | owner: "foo", 51 | repo: "bar", 52 | }); 53 | 54 | expect(octokit.issues.updateComment).toBeCalledWith({ 55 | comment_id: 1234, 56 | owner: "foo", 57 | repo: "bar", 58 | body: "my new comment", 59 | }); 60 | }); 61 | 62 | // test what happens when there's no regex match 63 | test("comment not found due to no regex match", async () => { 64 | const octokit = setComments([ 65 | { 66 | id: 1234, 67 | user: { 68 | type: "Bot", 69 | }, 70 | body: "Hello world!", 71 | }, 72 | { 73 | id: 1235, 74 | user: { 75 | type: "Bot", 76 | }, 77 | body: "Test comment", 78 | }, 79 | ]); 80 | container.bind(Octokit).toConstantValue(octokit); 81 | const updateCommentHelper = container.get(UpdateCommentHelper); 82 | 83 | // regex does not match comment body 84 | await expect( 85 | updateCommentHelper.updateComment( 86 | /^(regex does not match).*$/, 87 | "my comment", 88 | payload 89 | ) 90 | ).resolves.toBe(false); 91 | 92 | expect(octokit.issues.updateComment).toBeCalledTimes(0); 93 | }); 94 | 95 | test("comment body is the same as new comment content", async () => { 96 | const octokit = setComments([ 97 | { 98 | id: 1234, 99 | user: { 100 | type: "Bot", 101 | }, 102 | body: "Hello world!", 103 | }, 104 | ]); 105 | container.bind(Octokit).toConstantValue(octokit); 106 | const updateCommentHelper = container.get(UpdateCommentHelper); 107 | 108 | await expect( 109 | updateCommentHelper.updateComment( 110 | /^(Hello world)*.$/, 111 | "Hello world!", // same comment content 112 | payload 113 | ) 114 | ).resolves.toBe(true); 115 | 116 | expect(octokit.issues.updateComment).toBeCalledTimes(0); 117 | }); 118 | 119 | test("update the earliest matching comment", async () => { 120 | const octokit = setComments([ 121 | { 122 | id: 1234, 123 | user: { 124 | type: "User", 125 | }, 126 | created_at: "2022-05-09T19:54:41Z", 127 | body: "Hello world!", 128 | }, 129 | { 130 | id: 1235, 131 | user: { 132 | type: "Bot", 133 | }, 134 | created_at: "2022-05-09T19:54:41Z", 135 | body: "Hello world!", 136 | }, 137 | { 138 | id: 1236, 139 | user: { 140 | type: "Organization", 141 | }, 142 | created_at: "2022-06-09T19:54:41Z", 143 | body: "Hello world!", 144 | }, 145 | ]); 146 | container.bind(Octokit).toConstantValue(octokit); 147 | const updateCommentHelper = container.get(UpdateCommentHelper); 148 | 149 | await expect( 150 | updateCommentHelper.updateComment( 151 | /^(Hello world)*.$/, 152 | "Hello world!!!!!", // different comment content 153 | payload 154 | ) 155 | ).resolves.toBe(true); 156 | 157 | expect(octokit.issues.updateComment).toBeCalledWith({ 158 | comment_id: 1234, 159 | owner: payload.repository.owner.login, 160 | repo: payload.repository.name, 161 | body: "Hello world!!!!!", 162 | }); 163 | }); 164 | 165 | test("update the earliset matching comment 2", async () => { 166 | const octokit = setComments([ 167 | { 168 | id: 1234, 169 | user: { 170 | type: "User", 171 | }, 172 | created_at: "2023-05-09T19:54:41Z", 173 | body: "Hello world!", 174 | }, 175 | { 176 | id: 1235, 177 | user: { 178 | type: "Organization", 179 | }, 180 | created_at: "2023-04-09T19:54:41Z", 181 | body: "Hello world!", 182 | }, 183 | ]); 184 | container.bind(Octokit).toConstantValue(octokit); 185 | const updateCommentHelper = container.get(UpdateCommentHelper); 186 | 187 | await expect( 188 | updateCommentHelper.updateComment( 189 | /^(Hello world)*.$/, 190 | "Hello world!!!!!", // different comment content 191 | payload 192 | ) 193 | ).resolves.toBe(true); 194 | 195 | expect(octokit.issues.updateComment).toBeCalledWith({ 196 | comment_id: 1235, 197 | owner: payload.repository.owner.login, 198 | repo: payload.repository.name, 199 | body: "Hello world!!!!!", 200 | }); 201 | }); 202 | 203 | test("update the earliset matching comment 3", async () => { 204 | const octokit = setComments([ 205 | { 206 | id: 1234, 207 | user: { 208 | type: "User", 209 | }, 210 | created_at: "2023-05-09T19:54:41Z", 211 | body: "Hello world!", 212 | }, 213 | { 214 | id: 1235, 215 | user: { 216 | type: "Organization", 217 | }, 218 | created_at: "2023-04-09T19:54:41Z", 219 | body: "A different comment", 220 | }, 221 | ]); 222 | container.bind(Octokit).toConstantValue(octokit); 223 | const updateCommentHelper = container.get(UpdateCommentHelper); 224 | 225 | await expect( 226 | updateCommentHelper.updateComment( 227 | /^(Hello world)*.$/, 228 | "Hello world!!!!!", // different comment content 229 | payload 230 | ) 231 | ).resolves.toBe(true); 232 | 233 | expect(octokit.issues.updateComment).toBeCalledWith({ 234 | comment_id: 1234, 235 | owner: payload.repository.owner.login, 236 | repo: payload.repository.name, 237 | body: "Hello world!!!!!", 238 | }); 239 | }); 240 | 241 | test("failed to retrieve comment", async () => { 242 | const octokit: any = { 243 | issues: { 244 | listComments: jest.fn((_: any) => { 245 | return Promise.reject(new Error("Error!")); 246 | }), 247 | updateComment: jest.fn(), 248 | }, 249 | }; 250 | container.bind(Octokit).toConstantValue(octokit); 251 | const updateCommentHelper = container.get(UpdateCommentHelper); 252 | await expect( 253 | updateCommentHelper.updateComment( 254 | /^(Hello world)*.$/, 255 | "Hello world!", 256 | payload 257 | ) 258 | ).resolves.toBe(false); 259 | 260 | expect(octokit.issues.updateComment).toBeCalledTimes(0); 261 | }); 262 | 263 | function setComments(comments: any[]): any { 264 | const octokit: any = { 265 | issues: { 266 | listComments: jest.fn((_: any) => { 267 | return Promise.resolve({ 268 | data: comments, 269 | }); 270 | }), 271 | updateComment: jest.fn((_: any) => { 272 | return Promise.resolve({ status: 200 }); 273 | }), 274 | }, 275 | }; 276 | return octokit; 277 | } 278 | }); 279 | -------------------------------------------------------------------------------- /tests/index.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable global-require */ 2 | /* eslint-disable @typescript-eslint/no-require-imports */ 3 | import "reflect-metadata"; 4 | 5 | import * as core from "@actions/core"; 6 | 7 | jest.mock("@actions/core"); 8 | 9 | describe("Test Index", () => { 10 | afterEach(() => { 11 | jest.resetAllMocks(); 12 | jest.resetModules(); 13 | }); 14 | 15 | test("test index", async () => { 16 | await require("../src/index"); 17 | 18 | expect(core.setFailed).toBeCalled(); 19 | const call = (core.setFailed as jest.Mock).mock.calls[0]; 20 | expect(call[0]).toMatch("No GitHub Token provided (github_token)"); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /tests/inversify-binding.spec.ts: -------------------------------------------------------------------------------- 1 | import "reflect-metadata"; 2 | 3 | import { Container } from "inversify"; 4 | import { AddCommentHelper } from "../src/helpers/add-comment-helper"; 5 | import { AddStatusCheckHelper } from "../src/helpers/add-status-check-helper"; 6 | import { Analysis } from "../src/analysis"; 7 | import { Configuration } from "../src/api/configuration"; 8 | import { HandlePullRequestLogic } from "../src/logic/handle-pull-request-logic"; 9 | import { Handler } from "../src/api/handler"; 10 | import { InversifyBinding } from "../src/inversify-binding"; 11 | import { Logic } from "../src/api/logic"; 12 | import { OctokitBuilder } from "../src/github/octokit-builder"; 13 | import { PullRequestAction } from "../src/actions/pull-request-action"; 14 | import { PullRequestHandler } from "../src/handler/pull-request-handler"; 15 | import { PullRequestListener } from "../src/api/pull-request-listener"; 16 | 17 | describe("Test InversifyBinding", () => { 18 | test("test bindings", async () => { 19 | const addComment = true; 20 | const addStatus = true; 21 | const setupRemotes = false; 22 | const cheInstance = "https://foo.com"; 23 | const badge = "https://badge.com"; 24 | 25 | const inversifyBinding = new InversifyBinding( 26 | "foo", 27 | addComment, 28 | addStatus, 29 | setupRemotes, 30 | cheInstance, 31 | badge 32 | ); 33 | const container: Container = inversifyBinding.initBindings(); 34 | 35 | expect(inversifyBinding).toBeDefined(); 36 | 37 | // check all actions 38 | const pullRequestAction = container.get(PullRequestAction); 39 | expect(pullRequestAction).toBeDefined(); 40 | const pullRequestListeners: PullRequestListener[] = container.getAll(PullRequestListener); 41 | expect(pullRequestListeners).toBeDefined(); 42 | expect(pullRequestListeners.includes(pullRequestAction)).toBeTruthy(); 43 | 44 | // Handler 45 | const handlers: Handler[] = container.getAll(Handler); 46 | expect( 47 | handlers.find( 48 | (handler) => handler.constructor.name === PullRequestHandler.name 49 | ) 50 | ).toBeTruthy(); 51 | 52 | // config 53 | const configuration: Configuration = container.get(Configuration); 54 | expect(configuration).toBeDefined(); 55 | expect(configuration.addComment()).toEqual(addComment); 56 | expect(configuration.addStatus()).toEqual(addStatus); 57 | expect(configuration.setupRemotes()).toEqual(setupRemotes); 58 | expect(configuration.webIdeInstance()).toEqual(cheInstance); 59 | expect(configuration.commentBadge()).toEqual(badge); 60 | 61 | // helpers 62 | expect(container.get(AddCommentHelper)).toBeDefined(); 63 | expect(container.get(AddStatusCheckHelper)).toBeDefined(); 64 | 65 | // logic 66 | const logics: Logic[] = container.getAll(Logic); 67 | expect(logics).toBeDefined(); 68 | expect( 69 | logics.find( 70 | (logic) => logic.constructor.name === HandlePullRequestLogic.name 71 | ) 72 | ).toBeTruthy(); 73 | 74 | const octokitBuilder = container.get(OctokitBuilder); 75 | expect(octokitBuilder).toBeDefined(); 76 | 77 | const analysis = container.get(Analysis); 78 | expect(analysis).toBeDefined(); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /tests/logic/handle-pull-request-logic.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import "reflect-metadata"; 3 | 4 | import * as fs from "fs-extra"; 5 | import * as path from "path"; 6 | 7 | import { Container } from "inversify"; 8 | import { WebhookPayloadPullRequest } from "@octokit/webhooks"; 9 | import { AddCommentHelper } from "../../src/helpers/add-comment-helper"; 10 | import { AddStatusCheckHelper } from "../../src/helpers/add-status-check-helper"; 11 | import { Configuration } from "../../src/api/configuration"; 12 | import { HandlePullRequestLogic } from "../../src/logic/handle-pull-request-logic"; 13 | import { PullRequestAction } from "../../src/actions/pull-request-action"; 14 | import { UpdateCommentHelper } from "../../src/helpers/update-comment-helper"; 15 | 16 | describe("Test Logic HandlePullRequestLogic", () => { 17 | let container: Container; 18 | let configuration: Configuration; 19 | let pullRequestAction: PullRequestAction; 20 | let addCommentHelper: AddCommentHelper; 21 | let updateCommentHelper: UpdateCommentHelper; 22 | let addStatusCheckHelper: AddStatusCheckHelper; 23 | 24 | beforeEach(() => { 25 | container = new Container(); 26 | container.bind(HandlePullRequestLogic).toSelf().inSingletonScope(); 27 | 28 | pullRequestAction = { 29 | registerCallback: jest.fn(), 30 | } as any; 31 | container.bind(PullRequestAction).toConstantValue(pullRequestAction); 32 | 33 | configuration = { 34 | addComment: jest.fn(), 35 | 36 | addStatus: jest.fn(), 37 | 38 | setupRemotes: jest.fn(), 39 | 40 | webIdeInstance: jest.fn(), 41 | 42 | commentBadge: jest.fn(), 43 | }; 44 | container.bind(Configuration).toConstantValue(configuration); 45 | 46 | addCommentHelper = { 47 | addComment: jest.fn(), 48 | } as any; 49 | container.bind(AddCommentHelper).toConstantValue(addCommentHelper); 50 | 51 | updateCommentHelper = { 52 | updateComment: jest.fn(), 53 | } as any; 54 | container.bind(UpdateCommentHelper).toConstantValue(updateCommentHelper); 55 | 56 | addStatusCheckHelper = { 57 | addStatusCheck: jest.fn(), 58 | } as any; 59 | container 60 | .bind(AddStatusCheckHelper) 61 | .toConstantValue(addStatusCheckHelper); 62 | }); 63 | 64 | afterEach(() => { 65 | jest.resetAllMocks(); 66 | }); 67 | 68 | describe("PR branch from fork remote", () => { 69 | 70 | const payloadPath: string = path.join( 71 | __dirname, 72 | "..", 73 | "_data", 74 | "pull_request", 75 | "opened", 76 | "create-pr.json" 77 | ); 78 | 79 | test("comment false, status false, setupRemotes false", async () => { 80 | const handlePullRequestLogic = container.get(HandlePullRequestLogic); 81 | 82 | handlePullRequestLogic.setup(); 83 | 84 | // check 85 | expect(pullRequestAction.registerCallback).toBeCalled(); 86 | const registerCallbackCall = (pullRequestAction as any).registerCallback 87 | .mock.calls[0]; 88 | 89 | expect(registerCallbackCall[0]).toEqual( 90 | HandlePullRequestLogic.PR_EVENTS 91 | ); 92 | const callback = registerCallbackCall[1]; 93 | 94 | const payload: WebhookPayloadPullRequest = await fs.readJSON(payloadPath); 95 | 96 | // call the callback 97 | await callback(payload); 98 | 99 | expect(addCommentHelper.addComment).toBeCalledTimes(0); 100 | expect(addStatusCheckHelper.addStatusCheck).toBeCalledTimes(0); 101 | }); 102 | 103 | test("comment true, status false, setupRemotes false", async () => { 104 | const handlePullRequestLogic = container.get(HandlePullRequestLogic); 105 | 106 | handlePullRequestLogic.setup(); 107 | 108 | // addComment = true 109 | (configuration.addComment as jest.Mock).mockReturnValue(true); 110 | 111 | // check 112 | expect(pullRequestAction.registerCallback).toBeCalled(); 113 | const registerCallbackCall = (pullRequestAction as any).registerCallback 114 | .mock.calls[0]; 115 | 116 | expect(registerCallbackCall[0]).toEqual( 117 | HandlePullRequestLogic.PR_EVENTS 118 | ); 119 | const callback = registerCallbackCall[1]; 120 | 121 | const payload: WebhookPayloadPullRequest = await fs.readJSON(payloadPath); 122 | 123 | // call the callback 124 | await callback(payload); 125 | 126 | expect(addCommentHelper.addComment).toBeCalled(); 127 | const addCommentCall = (addCommentHelper.addComment as jest.Mock).mock 128 | .calls[0]; 129 | expect(addCommentCall[0]).toMatch( 130 | "Click here to review and test in web IDE" 131 | ); 132 | expect(addCommentCall[1]).toBe(payload); 133 | 134 | expect(addStatusCheckHelper.addStatusCheck).toBeCalledTimes(0); 135 | }); 136 | 137 | test("do not add comment if existing comment is updated", async () => { 138 | const handlePullRequestLogic = container.get(HandlePullRequestLogic); 139 | 140 | handlePullRequestLogic.setup(); 141 | 142 | // addComment = true 143 | (configuration.addComment as jest.Mock).mockReturnValue(true); 144 | 145 | // mock existing comment updated 146 | (updateCommentHelper.updateComment as jest.Mock).mockReturnValue(true); 147 | 148 | // check 149 | expect(pullRequestAction.registerCallback).toBeCalled(); 150 | const registerCallbackCall = (pullRequestAction as any).registerCallback 151 | .mock.calls[0]; 152 | 153 | expect(registerCallbackCall[0]).toEqual( 154 | HandlePullRequestLogic.PR_EVENTS 155 | ); 156 | const callback = registerCallbackCall[1]; 157 | 158 | const payload: WebhookPayloadPullRequest = await fs.readJSON(payloadPath); 159 | 160 | // call the callback 161 | await callback(payload); 162 | 163 | expect(addCommentHelper.addComment).toBeCalledTimes(0); 164 | }); 165 | 166 | test("do not try to update comment if addComment if false", async () => { 167 | const handlePullRequestLogic = container.get(HandlePullRequestLogic); 168 | 169 | handlePullRequestLogic.setup(); 170 | 171 | // addComment = false 172 | (configuration.addComment as jest.Mock).mockReturnValue(false); 173 | 174 | // check 175 | expect(pullRequestAction.registerCallback).toBeCalled(); 176 | const registerCallbackCall = (pullRequestAction as any).registerCallback 177 | .mock.calls[0]; 178 | 179 | expect(registerCallbackCall[0]).toEqual( 180 | HandlePullRequestLogic.PR_EVENTS 181 | ); 182 | const callback = registerCallbackCall[1]; 183 | 184 | const payload: WebhookPayloadPullRequest = await fs.readJSON(payloadPath); 185 | 186 | // call the callback 187 | await callback(payload); 188 | 189 | expect(updateCommentHelper.updateComment).toBeCalledTimes(0); 190 | }); 191 | test("comment false, status true, setupRemotes false", async () => { 192 | const handlePullRequestLogic = container.get(HandlePullRequestLogic); 193 | 194 | handlePullRequestLogic.setup(); 195 | 196 | // addStatus = true 197 | (configuration.addStatus as jest.Mock).mockReturnValue(true); 198 | (configuration.webIdeInstance as jest.Mock).mockReturnValue( 199 | "https://foo.com" 200 | ); 201 | 202 | // check 203 | expect(pullRequestAction.registerCallback).toBeCalled(); 204 | const registerCallbackCall = (pullRequestAction as any).registerCallback 205 | .mock.calls[0]; 206 | 207 | expect(registerCallbackCall[0]).toEqual( 208 | HandlePullRequestLogic.PR_EVENTS 209 | ); 210 | const callback = registerCallbackCall[1]; 211 | 212 | const payload: WebhookPayloadPullRequest = await fs.readJSON(payloadPath); 213 | 214 | // call the callback 215 | await callback(payload); 216 | 217 | expect(addCommentHelper.addComment).toBeCalledTimes(0); 218 | expect(addStatusCheckHelper.addStatusCheck).toBeCalled(); 219 | const addStatusCall = (addStatusCheckHelper.addStatusCheck as jest.Mock) 220 | .mock.calls[0]; 221 | expect(addStatusCall[0]).toMatch( 222 | "Click here to review and test in web IDE" 223 | ); 224 | expect(addStatusCall[1]).toBe("foo.com"); 225 | expect(addStatusCall[2]).toBe( 226 | "https://foo.com#https://github.com/chetrend/demo-gh-event/tree/patch-2" 227 | ); 228 | expect(addStatusCall[3]).toBe(payload); 229 | }); 230 | 231 | test("comment true, status true", async () => { 232 | const handlePullRequestLogic = container.get(HandlePullRequestLogic); 233 | 234 | handlePullRequestLogic.setup(); 235 | 236 | // addStatus = true 237 | (configuration.addStatus as jest.Mock).mockReturnValue(true); 238 | (configuration.webIdeInstance as jest.Mock).mockReturnValue( 239 | "https://foo.com" 240 | ); 241 | 242 | // addComment = true 243 | (configuration.addComment as jest.Mock).mockReturnValue(true); 244 | 245 | // check 246 | expect(pullRequestAction.registerCallback).toBeCalled(); 247 | const registerCallbackCall = (pullRequestAction as any).registerCallback 248 | .mock.calls[0]; 249 | 250 | expect(registerCallbackCall[0]).toEqual( 251 | HandlePullRequestLogic.PR_EVENTS 252 | ); 253 | const callback = registerCallbackCall[1]; 254 | 255 | const payload: WebhookPayloadPullRequest = await fs.readJSON(payloadPath); 256 | 257 | // call the callback 258 | await callback(payload); 259 | 260 | expect(addCommentHelper.addComment).toBeCalled(); 261 | expect(addStatusCheckHelper.addStatusCheck).toBeCalled(); 262 | const addStatusCall = (addStatusCheckHelper.addStatusCheck as jest.Mock) 263 | .mock.calls[0]; 264 | expect(addStatusCall[0]).toMatch( 265 | "Click here to review and test in web IDE" 266 | ); 267 | expect(addStatusCall[1]).toBe("foo.com"); 268 | expect(addStatusCall[2]).toBe( 269 | "https://foo.com#https://github.com/chetrend/demo-gh-event/tree/patch-2" 270 | ); 271 | expect(addStatusCall[3]).toBe(payload); 272 | 273 | const addCommentCall = (addCommentHelper.addComment as jest.Mock).mock 274 | .calls[0]; 275 | expect(addCommentCall[0]).toMatch( 276 | "Click here to review and test in web IDE" 277 | ); 278 | expect(addCommentCall[1]).toBe(payload); 279 | }); 280 | 281 | test("comment false, status true, setupRemotes true", async () => { 282 | const handlePullRequestLogic = container.get(HandlePullRequestLogic); 283 | 284 | handlePullRequestLogic.setup(); 285 | 286 | // addStatus = true 287 | (configuration.addStatus as jest.Mock).mockReturnValue(true); 288 | // setupRemotes = true 289 | (configuration.setupRemotes as jest.Mock).mockReturnValue(true); 290 | (configuration.webIdeInstance as jest.Mock).mockReturnValue( 291 | "https://foo.com" 292 | ); 293 | 294 | // check 295 | expect(pullRequestAction.registerCallback).toBeCalled(); 296 | const registerCallbackCall = (pullRequestAction as any).registerCallback 297 | .mock.calls[0]; 298 | 299 | expect(registerCallbackCall[0]).toEqual( 300 | HandlePullRequestLogic.PR_EVENTS 301 | ); 302 | const callback = registerCallbackCall[1]; 303 | 304 | const payload: WebhookPayloadPullRequest = await fs.readJSON(payloadPath); 305 | 306 | // call the callback 307 | await callback(payload); 308 | 309 | expect(addCommentHelper.addComment).toBeCalledTimes(0); 310 | expect(addStatusCheckHelper.addStatusCheck).toBeCalled(); 311 | const addStatusCall = (addStatusCheckHelper.addStatusCheck as jest.Mock) 312 | .mock.calls[0]; 313 | expect(addStatusCall[0]).toMatch( 314 | "Click here to review and test in web IDE" 315 | ); 316 | expect(addStatusCall[1]).toBe("foo.com"); 317 | 318 | // check that upstream remote is configured 319 | expect(addStatusCall[2]).toBe( 320 | "https://foo.com#https://github.com/chetrend/demo-gh-event/tree/patch-2?remotes={{upstream,https://github.com/benoitf/demo-gh-event.git}}" 321 | ); 322 | expect(addStatusCall[3]).toBe(payload); 323 | }); 324 | }); 325 | 326 | describe("PR branch from upstream remote", () => { 327 | 328 | const payloadPath: string = path.join( 329 | __dirname, 330 | "..", 331 | "_data", 332 | "pull_request", 333 | "opened", 334 | "create-pr-source-head-branch-same-repo.json" 335 | ); 336 | 337 | test("comment false, status true, setupRemotes false", async () => { 338 | const handlePullRequestLogic = container.get(HandlePullRequestLogic); 339 | 340 | handlePullRequestLogic.setup(); 341 | 342 | // addStatus = true 343 | (configuration.addStatus as jest.Mock).mockReturnValue(true); 344 | (configuration.webIdeInstance as jest.Mock).mockReturnValue( 345 | "https://foo.com" 346 | ); 347 | 348 | // check 349 | expect(pullRequestAction.registerCallback).toBeCalled(); 350 | const registerCallbackCall = (pullRequestAction as any).registerCallback 351 | .mock.calls[0]; 352 | 353 | expect(registerCallbackCall[0]).toEqual( 354 | HandlePullRequestLogic.PR_EVENTS 355 | ); 356 | const callback = registerCallbackCall[1]; 357 | 358 | const payload: WebhookPayloadPullRequest = await fs.readJSON(payloadPath); 359 | 360 | // call the callback 361 | await callback(payload); 362 | 363 | expect(addCommentHelper.addComment).toBeCalledTimes(0); 364 | expect(addStatusCheckHelper.addStatusCheck).toBeCalled(); 365 | const addStatusCall = (addStatusCheckHelper.addStatusCheck as jest.Mock) 366 | .mock.calls[0]; 367 | expect(addStatusCall[0]).toMatch( 368 | "Click here to review and test in web IDE" 369 | ); 370 | expect(addStatusCall[1]).toBe("foo.com"); 371 | expect(addStatusCall[2]).toBe( 372 | "https://foo.com#https://github.com/dkwon17/try-in-web-ide-testing/tree/pr-branch" 373 | ); 374 | expect(addStatusCall[3]).toBe(payload); 375 | }); 376 | 377 | test("comment false, status true, setupRemotes true", async () => { 378 | const handlePullRequestLogic = container.get(HandlePullRequestLogic); 379 | 380 | handlePullRequestLogic.setup(); 381 | 382 | // addStatus = true 383 | (configuration.addStatus as jest.Mock).mockReturnValue(true); 384 | (configuration.setupRemotes as jest.Mock).mockReturnValue(true); 385 | (configuration.webIdeInstance as jest.Mock).mockReturnValue( 386 | "https://foo.com" 387 | ); 388 | 389 | // check 390 | expect(pullRequestAction.registerCallback).toBeCalled(); 391 | const registerCallbackCall = (pullRequestAction as any).registerCallback 392 | .mock.calls[0]; 393 | 394 | expect(registerCallbackCall[0]).toEqual( 395 | HandlePullRequestLogic.PR_EVENTS 396 | ); 397 | const callback = registerCallbackCall[1]; 398 | 399 | const payload: WebhookPayloadPullRequest = await fs.readJSON(payloadPath); 400 | 401 | // call the callback 402 | await callback(payload); 403 | 404 | expect(addCommentHelper.addComment).toBeCalledTimes(0); 405 | expect(addStatusCheckHelper.addStatusCheck).toBeCalled(); 406 | const addStatusCall = (addStatusCheckHelper.addStatusCheck as jest.Mock) 407 | .mock.calls[0]; 408 | expect(addStatusCall[0]).toMatch( 409 | "Click here to review and test in web IDE" 410 | ); 411 | expect(addStatusCall[1]).toBe("foo.com"); 412 | 413 | // setupRemotes true results in no difference if PR branch is located in the upstream repo 414 | expect(addStatusCall[2]).toBe( 415 | "https://foo.com#https://github.com/dkwon17/try-in-web-ide-testing/tree/pr-branch" 416 | ); 417 | expect(addStatusCall[3]).toBe(payload); 418 | }); 419 | }); 420 | }); 421 | -------------------------------------------------------------------------------- /tests/main.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import "reflect-metadata"; 3 | 4 | import * as core from "@actions/core"; 5 | 6 | import { Main } from "../src/main"; 7 | import { Inputs } from "../src/generated/inputs-outputs"; 8 | 9 | jest.mock("@actions/core"); 10 | 11 | describe("Test Main", () => { 12 | afterEach(() => { 13 | jest.resetAllMocks(); 14 | }); 15 | 16 | test("test missing github token", async () => { 17 | const main = new Main(); 18 | await expect(main.start()).rejects.toThrow( 19 | "No GitHub Token provided (github_token)" 20 | ); 21 | expect(core.setFailed).toBeCalledTimes(0); 22 | }); 23 | 24 | test("test with token and no options", async () => { 25 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 26 | (core as any).__setInput(Inputs.GITHUB_TOKEN, "foo"); 27 | 28 | jest.mock("../src/inversify-binding"); 29 | const main = new Main(); 30 | await main.start(); 31 | expect(core.setFailed).toBeCalledTimes(0); 32 | }); 33 | 34 | test("test with token and all options", async () => { 35 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 36 | (core as any).__setInput(Inputs.GITHUB_TOKEN, "foo"); 37 | (core as any).__setInput(Inputs.ADD_STATUS, "true"); 38 | (core as any).__setInput(Inputs.ADD_COMMENT, "true"); 39 | (core as any).__setInput(Inputs.WEB_IDE_INSTANCE, "https://foo.com"); 40 | 41 | jest.mock("../src/inversify-binding"); 42 | const main = new Main(); 43 | await main.start(); 44 | expect(core.setFailed).toBeCalledTimes(0); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": [ 4 | "tests/" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@redhat-actions/tsconfig", 3 | "compilerOptions": { 4 | "outDir": "out/", 5 | "experimentalDecorators": true, 6 | "strictPropertyInitialization": false, 7 | "noUnusedLocals": false, 8 | "emitDecoratorMetadata": true, 9 | "sourceMap": true, 10 | }, 11 | "include": [ 12 | "src/", 13 | "tests/" 14 | ], 15 | } 16 | --------------------------------------------------------------------------------