├── .tool-versions ├── jest.config.js ├── .github └── workflows │ ├── test.yml │ ├── comment_on_pull_request.yml │ └── comment_on_push.yml ├── wallaby.js ├── package.json ├── action.yml ├── LICENSE ├── src ├── comment.ts └── main.ts ├── .gitignore ├── lib ├── comment.js └── main.js ├── __tests__ └── comment.test.ts ├── README.md └── tsconfig.json /.tool-versions: -------------------------------------------------------------------------------- 1 | nodejs 12.18.0 2 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | clearMocks: true, 3 | moduleFileExtensions: ['js', 'ts'], 4 | testEnvironment: 'node', 5 | testMatch: ['**/*.test.ts'], 6 | testRunner: 'jest-circus/runner', 7 | transform: { 8 | '^.+\\.ts$': 'ts-jest' 9 | }, 10 | verbose: true 11 | } -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | test: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v1 13 | 14 | - run: npm ci 15 | - run: npm run build 16 | - run: npm test 17 | -------------------------------------------------------------------------------- /wallaby.js: -------------------------------------------------------------------------------- 1 | module.exports = function(wallaby) { 2 | return { 3 | files: ["src/**/*.js?(x)", "!src/**/*.spec.ts?(x)"], 4 | tests: ["__tests__/**/*.test.ts?(x)"], 5 | 6 | env: { 7 | type: "node", 8 | runner: "node" 9 | }, 10 | 11 | testFramework: "jest", 12 | 13 | debug: true 14 | }; 15 | }; 16 | -------------------------------------------------------------------------------- /.github/workflows/comment_on_pull_request.yml: -------------------------------------------------------------------------------- 1 | name: Comment on Pull Request 2 | on: 3 | - pull_request 4 | 5 | jobs: 6 | comment: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v1 10 | - uses: ./ 11 | with: 12 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 13 | header: FromPR 14 | message: | 15 | Test ${{ github.sha }} is successfully ended. 16 | This is message from PR. 17 | -------------------------------------------------------------------------------- /.github/workflows/comment_on_push.yml: -------------------------------------------------------------------------------- 1 | name: Comment on Push 2 | on: 3 | - push 4 | 5 | jobs: 6 | comment: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v1 10 | - uses: jwalton/gh-find-current-pr@v1 11 | id: finder 12 | with: 13 | github-token: ${{ secrets.GITHUB_TOKEN }} 14 | - uses: ./ 15 | with: 16 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 17 | number: ${{ steps.finder.outputs.pr }} 18 | message: | 19 | Test ${{ github.sha }} is successfully ended. 20 | This is message from push. 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript-action", 3 | "version": "0.0.0", 4 | "private": true, 5 | "description": "TypeScript template action", 6 | "main": "lib/main.js", 7 | "scripts": { 8 | "build": "tsc", 9 | "test": "jest" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/actions/typescript-action.git" 14 | }, 15 | "keywords": [ 16 | "actions", 17 | "node", 18 | "setup" 19 | ], 20 | "author": "YourNameOrOrganization", 21 | "license": "MIT", 22 | "dependencies": { 23 | "@actions/core": "^1.2.6", 24 | "@actions/github": "^2.2.0" 25 | }, 26 | "devDependencies": { 27 | "@types/jest": "^26.0.14", 28 | "@types/node": "^14.14.2", 29 | "jest": "^25.5.4", 30 | "jest-circus": "^26.6.0", 31 | "ts-jest": "^25.5.1", 32 | "typescript": "^3.9.7" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: "Sticky Pull Request Comment" 2 | description: "Create comment on pull request, if exists update that comment." 3 | author: "marocchino" 4 | inputs: 5 | header: 6 | description: "Header to determine if the comment is to be updated, not shown on screen" 7 | required: false 8 | append: 9 | description: "Indicate if new comment messages should be appended to previous comment message" 10 | required: false 11 | recreate: 12 | description: "Indicate if previous comment should be removed before creating a new comment" 13 | required: false 14 | message: 15 | description: "comment message" 16 | required: false 17 | path: 18 | description: "path to file containing comment message" 19 | required: false 20 | number: 21 | description: "pull request number for push event" 22 | required: false 23 | delete: 24 | description: "delete the previously created comment" 25 | required: false 26 | GITHUB_TOKEN: 27 | description: "set secrets.GITHUB_TOKEN here" 28 | required: true 29 | runs: 30 | using: "node12" 31 | main: "lib/main.js" 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2018 GitHub, Inc. and contributors 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. -------------------------------------------------------------------------------- /src/comment.ts: -------------------------------------------------------------------------------- 1 | function headerComment(header) { 2 | return ``; 3 | } 4 | 5 | export async function findPreviousComment(octokit, repo, issue_number, header) { 6 | const { data: comments } = await octokit.issues.listComments({ 7 | ...repo, 8 | issue_number 9 | }); 10 | const h = headerComment(header); 11 | return comments.find(comment => comment.body.includes(h)); 12 | } 13 | export async function updateComment(octokit, repo, comment_id, body, header, previousBody?) { 14 | await octokit.issues.updateComment({ 15 | ...repo, 16 | comment_id, 17 | body: previousBody ? `${previousBody}\n${body}` : `${body}\n${headerComment(header)}` 18 | }); 19 | } 20 | export async function createComment(octokit, repo, issue_number, body, header, previousBody?) { 21 | await octokit.issues.createComment({ 22 | ...repo, 23 | issue_number, 24 | body: previousBody ? `${previousBody}\n${body}` : `${body}\n${headerComment(header)}` 25 | }); 26 | } 27 | export async function deleteComment(octokit, repo, comment_id) { 28 | await octokit.issues.deleteComment({ 29 | ...repo, 30 | comment_id, 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __tests__/runner/* 2 | 3 | # comment out in distribution branches 4 | node_modules/ 5 | 6 | # Rest pulled from https://github.com/github/gitignore/blob/master/Node.gitignore 7 | # Logs 8 | logs 9 | *.log 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | lerna-debug.log* 14 | 15 | # Diagnostic reports (https://nodejs.org/api/report.html) 16 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 17 | 18 | # Runtime data 19 | pids 20 | *.pid 21 | *.seed 22 | *.pid.lock 23 | 24 | # Directory for instrumented libs generated by jscoverage/JSCover 25 | lib-cov 26 | 27 | # Coverage directory used by tools like istanbul 28 | coverage 29 | *.lcov 30 | 31 | # nyc test coverage 32 | .nyc_output 33 | 34 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 35 | .grunt 36 | 37 | # Bower dependency directory (https://bower.io/) 38 | bower_components 39 | 40 | # node-waf configuration 41 | .lock-wscript 42 | 43 | # Compiled binary addons (https://nodejs.org/api/addons.html) 44 | build/Release 45 | 46 | # Dependency directories 47 | jspm_packages/ 48 | 49 | # TypeScript v1 declaration files 50 | typings/ 51 | 52 | # TypeScript cache 53 | *.tsbuildinfo 54 | 55 | # Optional npm cache directory 56 | .npm 57 | 58 | # Optional eslint cache 59 | .eslintcache 60 | 61 | # Optional REPL history 62 | .node_repl_history 63 | 64 | # Output of 'npm pack' 65 | *.tgz 66 | 67 | # Yarn Integrity file 68 | .yarn-integrity 69 | 70 | # dotenv environment variables file 71 | .env 72 | .env.test 73 | 74 | # parcel-bundler cache (https://parceljs.org/) 75 | .cache 76 | 77 | # next.js build output 78 | .next 79 | 80 | # nuxt.js build output 81 | .nuxt 82 | 83 | # vuepress build output 84 | .vuepress/dist 85 | 86 | # Serverless directories 87 | .serverless/ 88 | 89 | # FuseBox cache 90 | .fusebox/ 91 | 92 | # DynamoDB Local files 93 | .dynamodb/ 94 | 95 | .vscode/ -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import * as core from "@actions/core"; 2 | import { context, GitHub } from "@actions/github"; 3 | import { findPreviousComment, createComment, updateComment, deleteComment } from "./comment"; 4 | import { readFileSync } from 'fs'; 5 | 6 | async function run() { 7 | const number = 8 | context?.payload?.pull_request?.number || 9 | +core.getInput("number", { required: false }); 10 | if (isNaN(number) || number < 1) { 11 | core.info("no numbers given: skip step"); 12 | return; 13 | } 14 | 15 | try { 16 | const repo = context.repo; 17 | const message = core.getInput("message", { required: false }); 18 | const path = core.getInput("path", { required: false }); 19 | const header = core.getInput("header", { required: false }) || ""; 20 | const append = core.getInput("append", { required: false }) || false; 21 | const recreate = core.getInput("recreate", { required: false }) || false; 22 | const deleteOldComment = core.getInput("delete", { required: false }) || false; 23 | const githubToken = core.getInput("GITHUB_TOKEN", { required: true }); 24 | const octokit = new GitHub(githubToken); 25 | const previous = await findPreviousComment(octokit, repo, number, header); 26 | 27 | if (!message && !path) { 28 | throw { message: 'Either message or path input is required' }; 29 | } 30 | 31 | if (deleteOldComment && recreate) { 32 | throw { message: 'delete and recreate cannot be both set to true' }; 33 | } 34 | 35 | let body; 36 | 37 | if (path) { 38 | body = readFileSync(path); 39 | } else { 40 | body = message; 41 | } 42 | 43 | if (previous) { 44 | const previousBody = append && previous.body; 45 | if (deleteOldComment) { 46 | await deleteComment(octokit, repo, previous.id); 47 | } else if (recreate) { 48 | await deleteComment(octokit, repo, previous.id); 49 | await createComment(octokit, repo, number, body, header, previousBody); 50 | } else { 51 | await updateComment(octokit, repo, previous.id, body, header, previousBody); 52 | } 53 | } else { 54 | await createComment(octokit, repo, number, body, header); 55 | } 56 | } catch ({ message }) { 57 | core.setFailed(message); 58 | } 59 | } 60 | 61 | run(); 62 | -------------------------------------------------------------------------------- /lib/comment.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.deleteComment = exports.createComment = exports.updateComment = exports.findPreviousComment = void 0; 13 | function headerComment(header) { 14 | return ``; 15 | } 16 | function findPreviousComment(octokit, repo, issue_number, header) { 17 | return __awaiter(this, void 0, void 0, function* () { 18 | const { data: comments } = yield octokit.issues.listComments(Object.assign(Object.assign({}, repo), { issue_number })); 19 | const h = headerComment(header); 20 | return comments.find(comment => comment.body.includes(h)); 21 | }); 22 | } 23 | exports.findPreviousComment = findPreviousComment; 24 | function updateComment(octokit, repo, comment_id, body, header, previousBody) { 25 | return __awaiter(this, void 0, void 0, function* () { 26 | yield octokit.issues.updateComment(Object.assign(Object.assign({}, repo), { comment_id, body: previousBody ? `${previousBody}\n${body}` : `${body}\n${headerComment(header)}` })); 27 | }); 28 | } 29 | exports.updateComment = updateComment; 30 | function createComment(octokit, repo, issue_number, body, header, previousBody) { 31 | return __awaiter(this, void 0, void 0, function* () { 32 | yield octokit.issues.createComment(Object.assign(Object.assign({}, repo), { issue_number, body: previousBody ? `${previousBody}\n${body}` : `${body}\n${headerComment(header)}` })); 33 | }); 34 | } 35 | exports.createComment = createComment; 36 | function deleteComment(octokit, repo, comment_id) { 37 | return __awaiter(this, void 0, void 0, function* () { 38 | yield octokit.issues.deleteComment(Object.assign(Object.assign({}, repo), { comment_id })); 39 | }); 40 | } 41 | exports.deleteComment = deleteComment; 42 | -------------------------------------------------------------------------------- /__tests__/comment.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | findPreviousComment, 3 | createComment, 4 | updateComment, 5 | deleteComment 6 | } from "../src/comment"; 7 | const repo = {}; 8 | it("findPreviousComment", async () => { 9 | const comment = { 10 | user: { 11 | login: "github-actions[bot]" 12 | }, 13 | body: "previous message\n" 14 | }; 15 | const commentWithCustomHeader = { 16 | user: { 17 | login: "github-actions[bot]" 18 | }, 19 | body: "previous message\n" 20 | }; 21 | const headerFirstComment = { 22 | user: { 23 | login: "github-actions[bot]" 24 | }, 25 | body: "\nheader first message" 26 | } 27 | const otherComments = [ 28 | { 29 | user: { 30 | login: "some-user" 31 | }, 32 | body: "lgtm" 33 | }, 34 | { 35 | user: { 36 | login: "github-actions[bot]" 37 | }, 38 | body: "previous message\n" 39 | }, 40 | ]; 41 | const octokit = { 42 | issues: { 43 | listComments: jest.fn(() => 44 | Promise.resolve({ 45 | data: [commentWithCustomHeader, comment, headerFirstComment, ...otherComments] 46 | }) 47 | ) 48 | } 49 | }; 50 | 51 | expect(await findPreviousComment(octokit, repo, 123, "")).toBe(comment); 52 | expect(await findPreviousComment(octokit, repo, 123, "TypeA")).toBe( 53 | commentWithCustomHeader 54 | ); 55 | expect(await findPreviousComment(octokit, repo, 123, "LegacyComment")).toBe(headerFirstComment) 56 | expect(octokit.issues.listComments).toBeCalledWith({ issue_number: 123 }); 57 | }); 58 | it("updateComment", async () => { 59 | const octokit = { 60 | issues: { 61 | updateComment: jest.fn(() => Promise.resolve()) 62 | } 63 | }; 64 | expect( 65 | await updateComment(octokit, repo, 456, "hello there", "") 66 | ).toBeUndefined(); 67 | expect(octokit.issues.updateComment).toBeCalledWith({ 68 | comment_id: 456, 69 | body: "hello there\n" 70 | }); 71 | expect( 72 | await updateComment(octokit, repo, 456, "hello there", "TypeA") 73 | ).toBeUndefined(); 74 | expect(octokit.issues.updateComment).toBeCalledWith({ 75 | comment_id: 456, 76 | body: "hello there\n" 77 | }); 78 | 79 | expect( 80 | await updateComment(octokit, repo, 456, "hello there", "TypeA", "hello there\n") 81 | ).toBeUndefined(); 82 | expect(octokit.issues.updateComment).toBeCalledWith({ 83 | comment_id: 456, 84 | body: "hello there\n\nhello there" 85 | }); 86 | }); 87 | it("createComment", async () => { 88 | const octokit = { 89 | issues: { 90 | createComment: jest.fn(() => Promise.resolve()) 91 | } 92 | }; 93 | expect( 94 | await createComment(octokit, repo, 456, "hello there", "") 95 | ).toBeUndefined(); 96 | expect(octokit.issues.createComment).toBeCalledWith({ 97 | issue_number: 456, 98 | body: "hello there\n" 99 | }); 100 | expect( 101 | await createComment(octokit, repo, 456, "hello there", "TypeA") 102 | ).toBeUndefined(); 103 | expect(octokit.issues.createComment).toBeCalledWith({ 104 | issue_number: 456, 105 | body: "hello there\n" 106 | }); 107 | }); 108 | 109 | it("deleteComment", async () => { 110 | const octokit = { 111 | issues: { 112 | deleteComment: jest.fn(() => Promise.resolve()) 113 | } 114 | }; 115 | expect( 116 | await deleteComment(octokit, repo, 456) 117 | ).toBeUndefined(); 118 | expect(octokit.issues.deleteComment).toBeCalledWith({ 119 | comment_id: 456 120 | }); 121 | }); 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sticky Pull Request Comment 2 | 3 | Create comment on pull request, if exists update that comment. 4 | 5 | ## Usage: 6 | 7 | ### Basic 8 | 9 | ```yaml 10 | uses: marocchino/sticky-pull-request-comment@v1 11 | with: 12 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 13 | message: | 14 | Release ${{ github.sha }} to 15 | ``` 16 | 17 | ### Keep more than one comment 18 | 19 | In some cases, different actions may require different comments. The header allows you to maintain comments independently. 20 | 21 | ```yaml 22 | release: 23 | ... 24 | - uses: marocchino/sticky-pull-request-comment@v1 25 | with: 26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 27 | header: release 28 | message: | 29 | Release ${{ github.sha }} to 30 | 31 | test: 32 | ... 33 | - name: Run Test 34 | id: test 35 | run: | 36 | OUTPUT=$(rake test) 37 | OUTPUT="${OUTPUT//'%'/'%25'}​【7,6 m】" 38 | OUTPUT="${OUTPUT//$'\n'/'%0A'}" 39 | OUTPUT="${OUTPUT//$'\r'/'%0D'}" 40 | echo "::set-output name=result::$OUTPUT" 41 | - uses: marocchino/sticky-pull-request-comment@v1 42 | with: 43 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 44 | header: test 45 | message: | 46 | ``` 47 | ${{ steps.test.outputs.result }} 48 | ``` 49 | ``` 50 | 51 | ### Append after comment every time it runs 52 | 53 | ```yaml 54 | test: 55 | ... 56 | - name: Run Test 57 | id: test 58 | run: | 59 | OUTPUT=$(rake test) 60 | OUTPUT="${OUTPUT//'%'/'%25'}​【7,6 m】" 61 | OUTPUT="${OUTPUT//$'\n'/'%0A'}" 62 | OUTPUT="${OUTPUT//$'\r'/'%0D'}" 63 | echo "::set-output name=result::$OUTPUT" 64 | - uses: marocchino/sticky-pull-request-comment@v1 65 | with: 66 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 67 | append: true 68 | message: | 69 | Test with ${{ github.sha }}. 70 | ``` 71 | ${{ steps.test.outputs.result }} 72 | ``` 73 | ``` 74 | 75 | ### Comment from push 76 | 77 | If for some reason, triggering on pr is not possible, you can use push. 78 | 79 | ```yaml 80 | - uses: jwalton/gh-find-current-pr@v1 81 | id: finder 82 | with: 83 | github-token: ${{ secrets.GITHUB_TOKEN }} 84 | - uses: marocchino/sticky-pull-request-comment@v1 85 | with: 86 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 87 | number: ${{ steps.finder.outputs.pr }} 88 | message: | 89 | Test ${{ github.sha }} is successfully ended. 90 | This is message from push. 91 | ``` 92 | 93 | ### Read comment from a file 94 | 95 | ```yaml 96 | uses: marocchino/sticky-pull-request-comment@v1 97 | with: 98 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 99 | path: path-to-comment-contents.txt 100 | ``` 101 | 102 | ### Delete previous commit and add comment at bottom 103 | 104 | ```yaml 105 | uses: marocchino/sticky-pull-request-comment@v1 106 | with: 107 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 108 | recreate: true 109 | message: | 110 | Release ${{ github.sha }} to 111 | ``` 112 | 113 | ### Delete previous commit 114 | 115 | ```yaml 116 | uses: marocchino/sticky-pull-request-comment@v1 117 | with: 118 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 119 | delete: true 120 | ``` 121 | 122 | 123 | ## Development 124 | 125 | Install the dependencies 126 | 127 | ```bash 128 | $ npm install 129 | ``` 130 | 131 | Build the typescript 132 | 133 | ```bash 134 | $ npm run build 135 | ``` 136 | 137 | Run the tests :heavy_check_mark: 138 | 139 | ```bash 140 | $ npm test 141 | 142 | PASS ./index.test.js 143 | ✓ throws invalid number (3ms) 144 | ✓ wait 500 ms (504ms) 145 | ✓ test runs (95ms) 146 | 147 | ... 148 | ``` 149 | 150 | ## Publish to a distribution branch 151 | 152 | Actions are run from GitHub repos. We will create a releases branch and only checkin production modules (core in this case). 153 | 154 | Comment out node_modules in .gitignore and create a releases/v1 branch 155 | 156 | ```bash 157 | # comment out in distribution branches 158 | # node_modules/ 159 | ``` 160 | 161 | ```bash 162 | $ git checkout -b releases/v1 163 | $ git commit -a -m "prod dependencies" 164 | ``` 165 | 166 | ```bash 167 | $ npm prune --production 168 | $ git add node_modules 169 | $ git commit -a -m "prod dependencies" 170 | $ git push origin releases/v1 171 | ``` 172 | 173 | Your action is now published! :rocket: 174 | 175 | See the [versioning documentation](https://github.com/actions/toolkit/blob/master/docs/action-versioning.md) 176 | -------------------------------------------------------------------------------- /lib/main.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 3 | if (k2 === undefined) k2 = k; 4 | Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); 5 | }) : (function(o, m, k, k2) { 6 | if (k2 === undefined) k2 = k; 7 | o[k2] = m[k]; 8 | })); 9 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { 10 | Object.defineProperty(o, "default", { enumerable: true, value: v }); 11 | }) : function(o, v) { 12 | o["default"] = v; 13 | }); 14 | var __importStar = (this && this.__importStar) || function (mod) { 15 | if (mod && mod.__esModule) return mod; 16 | var result = {}; 17 | if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); 18 | __setModuleDefault(result, mod); 19 | return result; 20 | }; 21 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 22 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 23 | return new (P || (P = Promise))(function (resolve, reject) { 24 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 25 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 26 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 27 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 28 | }); 29 | }; 30 | Object.defineProperty(exports, "__esModule", { value: true }); 31 | const core = __importStar(require("@actions/core")); 32 | const github_1 = require("@actions/github"); 33 | const comment_1 = require("./comment"); 34 | const fs_1 = require("fs"); 35 | function run() { 36 | var _a, _b; 37 | return __awaiter(this, void 0, void 0, function* () { 38 | const number = ((_b = (_a = github_1.context === null || github_1.context === void 0 ? void 0 : github_1.context.payload) === null || _a === void 0 ? void 0 : _a.pull_request) === null || _b === void 0 ? void 0 : _b.number) || 39 | +core.getInput("number", { required: false }); 40 | if (isNaN(number) || number < 1) { 41 | core.info("no numbers given: skip step"); 42 | return; 43 | } 44 | try { 45 | const repo = github_1.context.repo; 46 | const message = core.getInput("message", { required: false }); 47 | const path = core.getInput("path", { required: false }); 48 | const header = core.getInput("header", { required: false }) || ""; 49 | const append = core.getInput("append", { required: false }) || false; 50 | const recreate = core.getInput("recreate", { required: false }) || false; 51 | const deleteOldComment = core.getInput("delete", { required: false }) || false; 52 | const githubToken = core.getInput("GITHUB_TOKEN", { required: true }); 53 | const octokit = new github_1.GitHub(githubToken); 54 | const previous = yield comment_1.findPreviousComment(octokit, repo, number, header); 55 | if (!message && !path) { 56 | throw { message: 'Either message or path input is required' }; 57 | } 58 | if (deleteOldComment && recreate) { 59 | throw { message: 'delete and recreate cannot be both set to true' }; 60 | } 61 | let body; 62 | if (path) { 63 | body = fs_1.readFileSync(path); 64 | } 65 | else { 66 | body = message; 67 | } 68 | if (previous) { 69 | const previousBody = append && previous.body; 70 | if (deleteOldComment) { 71 | yield comment_1.deleteComment(octokit, repo, previous.id); 72 | } 73 | else if (recreate) { 74 | yield comment_1.deleteComment(octokit, repo, previous.id); 75 | yield comment_1.createComment(octokit, repo, number, body, header, previousBody); 76 | } 77 | else { 78 | yield comment_1.updateComment(octokit, repo, previous.id, body, header, previousBody); 79 | } 80 | } 81 | else { 82 | yield comment_1.createComment(octokit, repo, number, body, header); 83 | } 84 | } 85 | catch ({ message }) { 86 | core.setFailed(message); 87 | } 88 | }); 89 | } 90 | run(); 91 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | // "incremental": true, /* Enable incremental compilation */ 5 | "target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ 6 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 7 | // "allowJs": true, /* Allow javascript files to be compiled. */ 8 | // "checkJs": true, /* Report errors in .js files. */ 9 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 10 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 11 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 12 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 13 | // "outFile": "./", /* Concatenate and emit output to single file. */ 14 | "outDir": "./lib", /* Redirect output structure to the directory. */ 15 | "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 16 | // "composite": true, /* Enable project compilation */ 17 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 18 | // "removeComments": true, /* Do not emit comments to output. */ 19 | // "noEmit": true, /* Do not emit outputs. */ 20 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 21 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 22 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 23 | 24 | /* Strict Type-Checking Options */ 25 | "strict": true, /* Enable all strict type-checking options. */ 26 | "noImplicitAny": false, /* Raise error on expressions and declarations with an implied 'any' type. */ 27 | // "strictNullChecks": true, /* Enable strict null checks. */ 28 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 29 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 30 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 31 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 32 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 33 | 34 | /* Additional Checks */ 35 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 36 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 37 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 38 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 39 | 40 | /* Module Resolution Options */ 41 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 42 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 43 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 44 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 45 | // "typeRoots": [], /* List of folders to include type definitions from. */ 46 | // "types": [], /* Type declaration files to be included in compilation. */ 47 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 48 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 49 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 50 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 51 | 52 | /* Source Map Options */ 53 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 54 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 55 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 56 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 57 | 58 | /* Experimental Options */ 59 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 60 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 61 | }, 62 | "exclude": ["node_modules", "**/*.test.ts"] 63 | } 64 | --------------------------------------------------------------------------------