* typescript

* fix reactions input

* test comma separated

* bump version

* append-separator

* refactor

* refactor reactions

* get reactions

* handle default token

* return reaction id

* remove reactions

* reactions-edit-mode

* readme

* test-command

* fix step order

* deprecate body-file

* update ci to body-path
This commit is contained in:
Peter Evans 2023-04-05 16:14:13 +09:00 committed by GitHub
parent 9c6357680f
commit 3383acd359
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 3648 additions and 5525 deletions

3
.eslintignore Normal file
View File

@ -0,0 +1,3 @@
dist/
lib/
node_modules/

View File

@ -1,17 +1,18 @@
{
"env": {
"commonjs": true,
"es6": true,
"node": true
},
"extends": "eslint:recommended",
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaVersion": 2018
},
"rules": {
}
}
"env": { "node": true, "jest": true },
"parser": "@typescript-eslint/parser",
"parserOptions": { "ecmaVersion": 9, "sourceType": "module" },
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:import/errors",
"plugin:import/warnings",
"plugin:import/typescript",
"plugin:prettier/recommended"
],
"plugins": ["@typescript-eslint"],
"rules": {
"@typescript-eslint/camelcase": "off"
}
}

View File

@ -1 +1 @@
**Edit:** Some additional info
This is still the second line.

View File

@ -1,5 +1,2 @@
This is a multi-line test comment read from a file.
- With GitHub **Markdown** :sparkles:
- Created by [create-or-update-comment][1]
[1]: https://github.com/peter-evans/create-or-update-comment
This is the second line.

View File

@ -28,8 +28,10 @@ jobs:
node-version: 16.x
cache: npm
- run: npm ci
- run: npm run build
- run: npm run format-check
- run: npm run lint
- run: npm run test
- run: npm run package
- uses: actions/upload-artifact@v3
with:
name: dist
@ -86,27 +88,34 @@ jobs:
body: |
**Edit:** Some additional info
reactions: eyes
reactions-edit-mode: replace
- name: Test add reactions
uses: ./
with:
comment-id: ${{ steps.couc.outputs.comment-id }}
reactions: heart, hooray, laugh
reactions: |
heart
hooray
laugh
- name: Test create comment from file
uses: ./
id: couc2
with:
issue-number: ${{ needs.build.outputs.issue-number }}
body-file: .github/comment-body.md
reactions: '+1'
body-path: .github/comment-body.md
reactions: |
+1
- name: Test update comment from file
uses: ./
with:
comment-id: ${{ steps.couc2.outputs.comment-id }}
body-file: .github/comment-body-addition.md
reactions: eyes
body-path: .github/comment-body-addition.md
append-separator: space
reactions: eyes, rocket
reactions-edit-mode: replace
package:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'

View File

@ -45,6 +45,7 @@ jobs:
body: |
**Edit:** Some additional info
reactions: eyes
reactions-edit-mode: replace
# Test add reactions
- name: Add reactions
@ -53,19 +54,12 @@ jobs:
comment-id: ${{ steps.couc.outputs.comment-id }}
reactions: heart, hooray, laugh
- name: Add reaction
uses: peter-evans/create-or-update-comment@v2
with:
repository: ${{ github.event.client_payload.github.payload.repository.full_name }}
comment-id: ${{ github.event.client_payload.github.payload.comment.id }}
reactions: hooray
# Test create with body from file
- name: Create comment
uses: ./
with:
issue-number: 1
body-file: .github/comment-body.md
body-path: .github/comment-body.md
# Test create from template
- name: Render template
@ -82,3 +76,10 @@ jobs:
with:
issue-number: 1
body: ${{ steps.template.outputs.result }}
- name: Add reaction
uses: peter-evans/create-or-update-comment@v2
with:
repository: ${{ github.event.client_payload.github.payload.repository.full_name }}
comment-id: ${{ github.event.client_payload.github.payload.comment.id }}
reactions: hooray

View File

@ -12,6 +12,7 @@ on:
description: The major version tag to update
options:
- v2
- v3
jobs:
tag:

3
.gitignore vendored
View File

@ -1 +1,2 @@
node_modules
lib/
node_modules/

3
.prettierignore Normal file
View File

@ -0,0 +1,3 @@
dist/
lib/
node_modules/

11
.prettierrc.json Normal file
View File

@ -0,0 +1,11 @@
{
"printWidth": 80,
"tabWidth": 2,
"useTabs": false,
"semi": false,
"singleQuote": true,
"trailingComma": "none",
"bracketSpacing": false,
"arrowParens": "avoid",
"parser": "typescript"
}

View File

@ -4,15 +4,13 @@
A GitHub action to create or update an issue or pull request comment.
This action was created to help facilitate a GitHub Actions "ChatOps" solution in conjunction with [slash-command-dispatch](https://github.com/peter-evans/slash-command-dispatch) action.
## Usage
### Add a comment to an issue or pull request
```yml
- name: Create comment
uses: peter-evans/create-or-update-comment@v2
uses: peter-evans/create-or-update-comment@v3
with:
issue-number: 1
body: |
@ -28,7 +26,7 @@ This action was created to help facilitate a GitHub Actions "ChatOps" solution i
```yml
- name: Update comment
uses: peter-evans/create-or-update-comment@v2
uses: peter-evans/create-or-update-comment@v3
with:
comment-id: 557858210
body: |
@ -40,10 +38,13 @@ This action was created to help facilitate a GitHub Actions "ChatOps" solution i
```yml
- name: Add reactions
uses: peter-evans/create-or-update-comment@v2
uses: peter-evans/create-or-update-comment@v3
with:
comment-id: 557858210
reactions: heart, hooray, laugh
reactions: |
heart
hooray
laugh
```
### Action inputs
@ -54,10 +55,12 @@ This action was created to help facilitate a GitHub Actions "ChatOps" solution i
| `repository` | The full name of the repository in which to create or update a comment. | Current repository |
| `issue-number` | The number of the issue or pull request in which to create a comment. | |
| `comment-id` | The id of the comment to update. | |
| `body` | The comment body. Cannot be used in conjunction with `body-file`. | |
| `body-file` | The path to a file containing the comment body. Cannot be used in conjunction with `body`. | |
| `body` | The comment body. Cannot be used in conjunction with `body-path`. | |
| `body-path` | The path to a file containing the comment body. Cannot be used in conjunction with `body`. | |
| `edit-mode` | The mode when updating a comment, `replace` or `append`. | `append` |
| `reactions` | A comma separated list of reactions to add to the comment. (`+1`, `-1`, `laugh`, `confused`, `heart`, `hooray`, `rocket`, `eyes`) | |
| `append-separator` | The separator to use when appending to an existing comment. (`newline`, `space`, `none`) | `newline` |
| `reactions` | A comma or newline separated list of reactions to add to the comment. (`+1`, `-1`, `laugh`, `confused`, `heart`, `hooray`, `rocket`, `eyes`) | |
| `reactions-edit-mode` | The mode when updating comment reactions, `replace` or `append`. | `append` |
Note: In *public* repositories this action does not work in `pull_request` workflows when triggered by forks.
Any attempt will be met with the error, `Resource not accessible by integration`.
@ -70,7 +73,7 @@ Note that in order to read the step output the action step must have an id.
```yml
- name: Create comment
uses: peter-evans/create-or-update-comment@v2
uses: peter-evans/create-or-update-comment@v3
id: couc
with:
issue-number: 1
@ -95,7 +98,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Add reaction
uses: peter-evans/create-or-update-comment@v2
uses: peter-evans/create-or-update-comment@v3
with:
comment-id: ${{ github.event.comment.id }}
reactions: eyes
@ -110,7 +113,7 @@ If the find-comment action output `comment-id` returns an empty string, a new co
If it returns a value, the comment already exists and the content is replaced.
```yml
- name: Find Comment
uses: peter-evans/find-comment@v2
uses: peter-evans/find-comment@v3
id: fc
with:
issue-number: ${{ github.event.pull_request.number }}
@ -118,7 +121,7 @@ If it returns a value, the comment already exists and the content is replaced.
body-includes: Build output
- name: Create or update comment
uses: peter-evans/create-or-update-comment@v2
uses: peter-evans/create-or-update-comment@v3
with:
comment-id: ${{ steps.fc.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number }}
@ -131,7 +134,7 @@ If it returns a value, the comment already exists and the content is replaced.
If required, the create and update steps can be separated for greater control.
```yml
- name: Find Comment
uses: peter-evans/find-comment@v2
uses: peter-evans/find-comment@v3
id: fc
with:
issue-number: ${{ github.event.pull_request.number }}
@ -140,7 +143,7 @@ If required, the create and update steps can be separated for greater control.
- name: Create comment
if: steps.fc.outputs.comment-id == ''
uses: peter-evans/create-or-update-comment@v2
uses: peter-evans/create-or-update-comment@v3
with:
issue-number: ${{ github.event.pull_request.number }}
body: |
@ -149,7 +152,7 @@ If required, the create and update steps can be separated for greater control.
- name: Update comment
if: steps.fc.outputs.comment-id != ''
uses: peter-evans/create-or-update-comment@v2
uses: peter-evans/create-or-update-comment@v3
with:
comment-id: ${{ steps.fc.outputs.comment-id }}
body: |
@ -161,10 +164,10 @@ If required, the create and update steps can be separated for greater control.
```yml
- name: Create comment
uses: peter-evans/create-or-update-comment@v2
uses: peter-evans/create-or-update-comment@v3
with:
issue-number: 1
body-file: 'comment-body.md'
body-path: 'comment-body.md'
```
### Using a markdown template
@ -187,7 +190,7 @@ The template is rendered using the [render-template](https://github.com/chuhlomi
bar: that
- name: Create comment
uses: peter-evans/create-or-update-comment@v2
uses: peter-evans/create-or-update-comment@v3
with:
issue-number: 1
body: ${{ steps.template.outputs.result }}

View File

@ -6,20 +6,28 @@ inputs:
default: ${{ github.token }}
repository:
description: 'The full name of the repository in which to create or update a comment.'
default: ${{ github.repository }}
issue-number:
description: 'The number of the issue or pull request in which to create a comment.'
comment-id:
description: 'The id of the comment to update.'
body:
description: 'The comment body. Cannot be used in conjunction with `body-file`.'
body-file:
description: 'The comment body. Cannot be used in conjunction with `body-path`.'
body-path:
description: 'The path to a file containing the comment body. Cannot be used in conjunction with `body`.'
body-file:
description: 'Deprecated in favour of `body-path`.'
edit-mode:
description: 'The mode when updating a comment, "replace" or "append".'
reaction-type:
description: 'Deprecated in favour of `reactions`'
default: 'append'
append-separator:
description: 'The separator to use when appending to an existing comment. (`newline`, `space`, `none`)'
default: 'newline'
reactions:
description: 'A comma separated list of reactions to add to the comment.'
description: 'A comma or newline separated list of reactions to add to the comment.'
reactions-edit-mode:
description: 'The mode when updating comment reactions, "replace" or "append".'
default: 'append'
outputs:
comment-id:
description: 'The id of the created comment'

736
dist/index.js vendored
View File

@ -1,6 +1,425 @@
/******/ (() => { // webpackBootstrap
/******/ var __webpack_modules__ = ({
/***/ 8007:
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __asyncValues = (this && this.__asyncValues) || function (o) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var m = o[Symbol.asyncIterator], i;
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.createOrUpdateComment = void 0;
const core = __importStar(__nccwpck_require__(2186));
const github = __importStar(__nccwpck_require__(5438));
const utils = __importStar(__nccwpck_require__(918));
const util_1 = __nccwpck_require__(3837);
const REACTION_TYPES = [
'+1',
'-1',
'laugh',
'confused',
'heart',
'hooray',
'rocket',
'eyes'
];
function getReactionsSet(reactions) {
const reactionsSet = [
...new Set(reactions.filter(item => {
if (!REACTION_TYPES.includes(item)) {
core.warning(`Skipping invalid reaction '${item}'.`);
return false;
}
return true;
}))
];
if (!reactionsSet) {
throw new Error(`No valid reactions are contained in '${reactions}'.`);
}
return reactionsSet;
}
function addReactions(octokit, owner, repo, commentId, reactions) {
return __awaiter(this, void 0, void 0, function* () {
const results = yield Promise.allSettled(reactions.map((reaction) => __awaiter(this, void 0, void 0, function* () {
yield octokit.rest.reactions.createForIssueComment({
owner: owner,
repo: repo,
comment_id: commentId,
content: reaction
});
core.info(`Setting '${reaction}' reaction on comment.`);
})));
for (let i = 0, l = results.length; i < l; i++) {
if (results[i].status === 'fulfilled') {
core.info(`Added reaction '${reactions[i]}' to comment id '${commentId}'.`);
}
else if (results[i].status === 'rejected') {
core.warning(`Adding reaction '${reactions[i]}' to comment id '${commentId}' failed.`);
}
}
});
}
function removeReactions(octokit, owner, repo, commentId, reactions) {
return __awaiter(this, void 0, void 0, function* () {
const results = yield Promise.allSettled(reactions.map((reaction) => __awaiter(this, void 0, void 0, function* () {
yield octokit.rest.reactions.deleteForIssueComment({
owner: owner,
repo: repo,
comment_id: commentId,
reaction_id: reaction.id
});
core.info(`Removing '${reaction.content}' reaction from comment.`);
})));
for (let i = 0, l = results.length; i < l; i++) {
if (results[i].status === 'fulfilled') {
core.info(`Removed reaction '${reactions[i].content}' from comment id '${commentId}'.`);
}
else if (results[i].status === 'rejected') {
core.warning(`Removing reaction '${reactions[i].content}' from comment id '${commentId}' failed.`);
}
}
});
}
function appendSeparatorTo(body, separator) {
switch (separator) {
case 'newline':
return body + '\n';
case 'space':
return body + ' ';
default: // none
return body;
}
}
function createComment(octokit, owner, repo, issueNumber, body) {
return __awaiter(this, void 0, void 0, function* () {
const { data: comment } = yield octokit.rest.issues.createComment({
owner: owner,
repo: repo,
issue_number: issueNumber,
body
});
core.info(`Created comment id '${comment.id}' on issue '${issueNumber}'.`);
return comment.id;
});
}
function updateComment(octokit, owner, repo, commentId, body, editMode, appendSeparator) {
return __awaiter(this, void 0, void 0, function* () {
if (body) {
let commentBody = '';
if (editMode == 'append') {
// Get the comment body
const { data: comment } = yield octokit.rest.issues.getComment({
owner: owner,
repo: repo,
comment_id: commentId
});
commentBody = appendSeparatorTo(comment.body ? comment.body : '', appendSeparator);
}
commentBody = commentBody + body;
core.debug(`Comment body: ${commentBody}`);
yield octokit.rest.issues.updateComment({
owner: owner,
repo: repo,
comment_id: commentId,
body: commentBody
});
core.info(`Updated comment id '${commentId}'.`);
}
return commentId;
});
}
function getAuthenticatedUser(octokit) {
return __awaiter(this, void 0, void 0, function* () {
try {
const { data: user } = yield octokit.rest.users.getAuthenticated();
return user.login;
}
catch (error) {
if (utils
.getErrorMessage(error)
.includes('Resource not accessible by integration')) {
// In this case we can assume the token is the default GITHUB_TOKEN and
// therefore the user is 'github-actions[bot]'.
return 'github-actions[bot]';
}
else {
throw error;
}
}
});
}
function getCommentReactionsForUser(octokit, owner, repo, commentId, user) {
var _a, e_1, _b, _c;
return __awaiter(this, void 0, void 0, function* () {
const userReactions = [];
try {
for (var _d = true, _e = __asyncValues(octokit.paginate.iterator(octokit.rest.reactions.listForIssueComment, {
owner,
repo,
comment_id: commentId,
per_page: 100
})), _f; _f = yield _e.next(), _a = _f.done, !_a;) {
_c = _f.value;
_d = false;
try {
const { data: reactions } = _c;
const filteredReactions = reactions
.filter(reaction => reaction.user.login === user)
.map(reaction => {
return { id: reaction.id, content: reaction.content };
});
userReactions.push(...filteredReactions);
}
finally {
_d = true;
}
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
}
finally { if (e_1) throw e_1.error; }
}
return userReactions;
});
}
function createOrUpdateComment(inputs, body) {
return __awaiter(this, void 0, void 0, function* () {
const [owner, repo] = inputs.repository.split('/');
const octokit = github.getOctokit(inputs.token);
const commentId = inputs.commentId
? yield updateComment(octokit, owner, repo, inputs.commentId, body, inputs.editMode, inputs.appendSeparator)
: yield createComment(octokit, owner, repo, inputs.issueNumber, body);
core.setOutput('comment-id', commentId);
if (inputs.reactions) {
const reactionsSet = getReactionsSet(inputs.reactions);
// Remove reactions if reactionsEditMode is 'replace'
if (inputs.commentId && inputs.reactionsEditMode === 'replace') {
const authenticatedUser = yield getAuthenticatedUser(octokit);
const userReactions = yield getCommentReactionsForUser(octokit, owner, repo, commentId, authenticatedUser);
core.debug((0, util_1.inspect)(userReactions));
const reactionsToRemove = userReactions.filter(reaction => !reactionsSet.includes(reaction.content));
yield removeReactions(octokit, owner, repo, commentId, reactionsToRemove);
}
yield addReactions(octokit, owner, repo, commentId, reactionsSet);
}
});
}
exports.createOrUpdateComment = createOrUpdateComment;
/***/ }),
/***/ 3109:
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
const core = __importStar(__nccwpck_require__(2186));
const create_or_update_comment_1 = __nccwpck_require__(8007);
const fs_1 = __nccwpck_require__(7147);
const util_1 = __nccwpck_require__(3837);
const utils = __importStar(__nccwpck_require__(918));
function getBody(inputs) {
if (inputs.body) {
return inputs.body;
}
else if (inputs.bodyPath) {
return (0, fs_1.readFileSync)(inputs.bodyPath, 'utf-8');
}
else {
return '';
}
}
function run() {
return __awaiter(this, void 0, void 0, function* () {
try {
const inputs = {
token: core.getInput('token'),
repository: core.getInput('repository'),
issueNumber: Number(core.getInput('issue-number')),
commentId: Number(core.getInput('comment-id')),
body: core.getInput('body'),
bodyPath: core.getInput('body-path') || core.getInput('body-file'),
editMode: core.getInput('edit-mode'),
appendSeparator: core.getInput('append-separator'),
reactions: utils.getInputAsArray('reactions'),
reactionsEditMode: core.getInput('reactions-edit-mode')
};
core.debug(`Inputs: ${(0, util_1.inspect)(inputs)}`);
if (!['append', 'replace'].includes(inputs.editMode)) {
throw new Error(`Invalid edit-mode '${inputs.editMode}'.`);
}
if (!['append', 'replace'].includes(inputs.reactionsEditMode)) {
throw new Error(`Invalid reactions edit-mode '${inputs.reactionsEditMode}'.`);
}
if (!['newline', 'space', 'none'].includes(inputs.appendSeparator)) {
throw new Error(`Invalid append-separator '${inputs.appendSeparator}'.`);
}
if (inputs.bodyPath && inputs.body) {
throw new Error("Only one of 'body' or 'body-path' can be set.");
}
if (inputs.bodyPath) {
if (!(0, fs_1.existsSync)(inputs.bodyPath)) {
throw new Error(`File '${inputs.bodyPath}' does not exist.`);
}
}
const body = getBody(inputs);
if (inputs.commentId) {
if (!body && !inputs.reactions) {
throw new Error("Missing comment 'body', 'body-path', or 'reactions'.");
}
}
else if (inputs.issueNumber) {
if (!body) {
throw new Error("Missing comment 'body' or 'body-path'.");
}
}
else {
throw new Error("Missing either 'issue-number' or 'comment-id'.");
}
(0, create_or_update_comment_1.createOrUpdateComment)(inputs, body);
}
catch (error) {
core.debug((0, util_1.inspect)(error));
const errMsg = utils.getErrorMessage(error);
core.setFailed(errMsg);
if (errMsg == 'Resource not accessible by integration') {
core.error(`See this action's readme for details about this error`);
}
}
});
}
run();
/***/ }),
/***/ 918:
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.getErrorMessage = exports.getStringAsArray = exports.getInputAsArray = void 0;
const core = __importStar(__nccwpck_require__(2186));
function getInputAsArray(name, options) {
return getStringAsArray(core.getInput(name, options));
}
exports.getInputAsArray = getInputAsArray;
function getStringAsArray(str) {
return str
.split(/[\n,]+/)
.map(s => s.trim())
.filter(x => x !== '');
}
exports.getStringAsArray = getStringAsArray;
function getErrorMessage(error) {
if (error instanceof Error)
return error.message;
return String(error);
}
exports.getErrorMessage = getErrorMessage;
/***/ }),
/***/ 7351:
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
@ -1942,6 +2361,10 @@ function checkBypass(reqUrl) {
if (!reqUrl.hostname) {
return false;
}
const reqHost = reqUrl.hostname;
if (isLoopbackAddress(reqHost)) {
return true;
}
const noProxy = process.env['no_proxy'] || process.env['NO_PROXY'] || '';
if (!noProxy) {
return false;
@ -1967,13 +2390,24 @@ function checkBypass(reqUrl) {
.split(',')
.map(x => x.trim().toUpperCase())
.filter(x => x)) {
if (upperReqHosts.some(x => x === upperNoProxyItem)) {
if (upperNoProxyItem === '*' ||
upperReqHosts.some(x => x === upperNoProxyItem ||
x.endsWith(`.${upperNoProxyItem}`) ||
(upperNoProxyItem.startsWith('.') &&
x.endsWith(`${upperNoProxyItem}`)))) {
return true;
}
}
return false;
}
exports.checkBypass = checkBypass;
function isLoopbackAddress(host) {
const hostLower = host.toLowerCase();
return (hostLower === 'localhost' ||
hostLower.startsWith('127.') ||
hostLower.startsWith('[::1]') ||
hostLower.startsWith('[0:0:0:0:0:0:0:1]'));
}
//# sourceMappingURL=proxy.js.map
/***/ }),
@ -6022,6 +6456,20 @@ const isDomainOrSubdomain = function isDomainOrSubdomain(destination, original)
return orig === dest || orig[orig.length - dest.length - 1] === '.' && orig.endsWith(dest);
};
/**
* isSameProtocol reports whether the two provided URLs use the same protocol.
*
* Both domains must already be in canonical form.
* @param {string|URL} original
* @param {string|URL} destination
*/
const isSameProtocol = function isSameProtocol(destination, original) {
const orig = new URL$1(original).protocol;
const dest = new URL$1(destination).protocol;
return orig === dest;
};
/**
* Fetch function
*
@ -6053,7 +6501,7 @@ function fetch(url, opts) {
let error = new AbortError('The user aborted a request.');
reject(error);
if (request.body && request.body instanceof Stream.Readable) {
request.body.destroy(error);
destroyStream(request.body, error);
}
if (!response || !response.body) return;
response.body.emit('error', error);
@ -6094,9 +6542,43 @@ function fetch(url, opts) {
req.on('error', function (err) {
reject(new FetchError(`request to ${request.url} failed, reason: ${err.message}`, 'system', err));
if (response && response.body) {
destroyStream(response.body, err);
}
finalize();
});
fixResponseChunkedTransferBadEnding(req, function (err) {
if (signal && signal.aborted) {
return;
}
if (response && response.body) {
destroyStream(response.body, err);
}
});
/* c8 ignore next 18 */
if (parseInt(process.version.substring(1)) < 14) {
// Before Node.js 14, pipeline() does not fully support async iterators and does not always
// properly handle when the socket close/end events are out of order.
req.on('socket', function (s) {
s.addListener('close', function (hadError) {
// if a data listener is still present we didn't end cleanly
const hasDataListener = s.listenerCount('data') > 0;
// if end happened before close but the socket didn't emit an error, do it now
if (response && hasDataListener && !hadError && !(signal && signal.aborted)) {
const err = new Error('Premature close');
err.code = 'ERR_STREAM_PREMATURE_CLOSE';
response.body.emit('error', err);
}
});
});
}
req.on('response', function (res) {
clearTimeout(reqTimeout);
@ -6168,7 +6650,7 @@ function fetch(url, opts) {
size: request.size
};
if (!isDomainOrSubdomain(request.url, locationURL)) {
if (!isDomainOrSubdomain(request.url, locationURL) || !isSameProtocol(request.url, locationURL)) {
for (const name of ['authorization', 'www-authenticate', 'cookie', 'cookie2']) {
requestOpts.headers.delete(name);
}
@ -6261,6 +6743,13 @@ function fetch(url, opts) {
response = new Response(body, response_options);
resolve(response);
});
raw.on('end', function () {
// some old IIS servers return zero-length OK deflate responses, so 'data' is never emitted.
if (!response) {
response = new Response(body, response_options);
resolve(response);
}
});
return;
}
@ -6280,6 +6769,41 @@ function fetch(url, opts) {
writeToStream(req, request);
});
}
function fixResponseChunkedTransferBadEnding(request, errorCallback) {
let socket;
request.on('socket', function (s) {
socket = s;
});
request.on('response', function (response) {
const headers = response.headers;
if (headers['transfer-encoding'] === 'chunked' && !headers['content-length']) {
response.once('close', function (hadError) {
// if a data listener is still present we didn't end cleanly
const hasDataListener = socket.listenerCount('data') > 0;
if (hasDataListener && !hadError) {
const err = new Error('Premature close');
err.code = 'ERR_STREAM_PREMATURE_CLOSE';
errorCallback(err);
}
});
}
});
}
function destroyStream(stream, err) {
if (stream.destroy) {
stream.destroy(err);
} else {
// node < 8
stream.emit('error', err);
stream.end();
}
}
/**
* Redirect code matching
*
@ -9681,204 +10205,12 @@ module.exports = JSON.parse('[[[0,44],"disallowed_STD3_valid"],[[45,46],"valid"]
/******/ if (typeof __nccwpck_require__ !== 'undefined') __nccwpck_require__.ab = __dirname + "/";
/******/
/************************************************************************/
var __webpack_exports__ = {};
// This entry need to be wrapped in an IIFE because it need to be isolated against other modules in the chunk.
(() => {
const { inspect } = __nccwpck_require__(3837);
const { readFileSync, existsSync } = __nccwpck_require__(7147);
const core = __nccwpck_require__(2186);
const github = __nccwpck_require__(5438);
const REACTION_TYPES = [
"+1",
"-1",
"laugh",
"confused",
"heart",
"hooray",
"rocket",
"eyes",
];
async function addReactions(octokit, repo, comment_id, reactions) {
let ReactionsSet = [
...new Set(
reactions
.replace(/\s/g, "")
.split(",")
.filter((item) => {
if (!REACTION_TYPES.includes(item)) {
core.info(`Skipping invalid reaction '${item}'.`);
return false;
}
return true;
})
),
];
if (!ReactionsSet) {
core.setFailed(
`No valid reactions are contained in '${reactions}'.`
);
return false;
}
let results = await Promise.allSettled(
ReactionsSet.map(async (item) => {
await octokit.rest.reactions.createForIssueComment({
owner: repo[0],
repo: repo[1],
comment_id: comment_id,
content: item,
});
core.info(`Setting '${item}' reaction on comment.`);
})
);
for (let i = 0, l = results.length; i < l; i++) {
if (results[i].status === "fulfilled") {
core.info(
`Added reaction '${ReactionsSet[i]}' to comment id '${comment_id}'.`
);
} else if (results[i].status === "rejected") {
core.info(
`Adding reaction '${ReactionsSet[i]}' to comment id '${comment_id}' failed with ${results[i].reason}.`
);
}
}
ReactionsSet = undefined;
results = undefined;
}
function getBody(inputs) {
if (inputs.body) {
return inputs.body;
} else if (inputs.bodyFile) {
return readFileSync(inputs.bodyFile, 'utf-8');
} else {
return '';
}
}
async function run() {
try {
const inputs = {
token: core.getInput("token"),
repository: core.getInput("repository"),
issueNumber: core.getInput("issue-number"),
commentId: core.getInput("comment-id"),
body: core.getInput("body"),
bodyFile: core.getInput("body-file"),
editMode: core.getInput("edit-mode"),
reactions: core.getInput("reactions")
? core.getInput("reactions")
: core.getInput("reaction-type"),
};
core.debug(`Inputs: ${inspect(inputs)}`);
const repository = inputs.repository
? inputs.repository
: process.env.GITHUB_REPOSITORY;
const repo = repository.split("/");
core.debug(`repository: ${repository}`);
const editMode = inputs.editMode ? inputs.editMode : "append";
core.debug(`editMode: ${editMode}`);
if (!["append", "replace"].includes(editMode)) {
core.setFailed(`Invalid edit-mode '${editMode}'.`);
return;
}
if (inputs.bodyFile && inputs.body) {
core.setFailed("Only one of 'body' or 'body-file' can be set.");
return;
}
if (inputs.bodyFile) {
if (!existsSync(inputs.bodyFile)) {
core.setFailed(`File '${inputs.bodyFile}' does not exist.`);
return;
}
}
const body = getBody(inputs);
const octokit = github.getOctokit(inputs.token);
if (inputs.commentId) {
// Edit a comment
if (!body && !inputs.reactions) {
core.setFailed("Missing comment 'body', 'body-file', or 'reactions'.");
return;
}
if (body) {
var commentBody = "";
if (editMode == "append") {
// Get the comment body
const { data: comment } = await octokit.rest.issues.getComment({
owner: repo[0],
repo: repo[1],
comment_id: inputs.commentId,
});
commentBody = comment.body + "\n";
}
commentBody = commentBody + body;
core.debug(`Comment body: ${commentBody}`);
await octokit.rest.issues.updateComment({
owner: repo[0],
repo: repo[1],
comment_id: inputs.commentId,
body: commentBody,
});
core.info(`Updated comment id '${inputs.commentId}'.`);
core.setOutput("comment-id", inputs.commentId);
}
// Set comment reactions
if (inputs.reactions) {
await addReactions(octokit, repo, inputs.commentId, inputs.reactions);
}
} else if (inputs.issueNumber) {
// Create a comment
if (!body) {
core.setFailed("Missing comment 'body' or 'body-file'.");
return;
}
const { data: comment } = await octokit.rest.issues.createComment({
owner: repo[0],
repo: repo[1],
issue_number: inputs.issueNumber,
body,
});
core.info(
`Created comment id '${comment.id}' on issue '${inputs.issueNumber}'.`
);
core.setOutput("comment-id", comment.id);
// Set comment reactions
if (inputs.reactions) {
await addReactions(octokit, repo, comment.id, inputs.reactions);
}
} else {
core.setFailed("Missing either 'issue-number' or 'comment-id'.");
return;
}
} catch (error) {
core.debug(inspect(error));
core.setFailed(error.message);
if (error.message == 'Resource not accessible by integration') {
core.error(`See this action's readme for details about this error`);
}
}
}
run();
})();
module.exports = __webpack_exports__;
/******/
/******/ // startup
/******/ // Load entry module and return exports
/******/ // This entry module is referenced by other modules so it can't be inlined
/******/ var __webpack_exports__ = __nccwpck_require__(3109);
/******/ module.exports = __webpack_exports__;
/******/
/******/ })()
;

192
index.js
View File

@ -1,192 +0,0 @@
const { inspect } = require("util");
const { readFileSync, existsSync } = require("fs");
const core = require("@actions/core");
const github = require("@actions/github");
const REACTION_TYPES = [
"+1",
"-1",
"laugh",
"confused",
"heart",
"hooray",
"rocket",
"eyes",
];
async function addReactions(octokit, repo, comment_id, reactions) {
let ReactionsSet = [
...new Set(
reactions
.replace(/\s/g, "")
.split(",")
.filter((item) => {
if (!REACTION_TYPES.includes(item)) {
core.info(`Skipping invalid reaction '${item}'.`);
return false;
}
return true;
})
),
];
if (!ReactionsSet) {
core.setFailed(
`No valid reactions are contained in '${reactions}'.`
);
return false;
}
let results = await Promise.allSettled(
ReactionsSet.map(async (item) => {
await octokit.rest.reactions.createForIssueComment({
owner: repo[0],
repo: repo[1],
comment_id: comment_id,
content: item,
});
core.info(`Setting '${item}' reaction on comment.`);
})
);
for (let i = 0, l = results.length; i < l; i++) {
if (results[i].status === "fulfilled") {
core.info(
`Added reaction '${ReactionsSet[i]}' to comment id '${comment_id}'.`
);
} else if (results[i].status === "rejected") {
core.info(
`Adding reaction '${ReactionsSet[i]}' to comment id '${comment_id}' failed with ${results[i].reason}.`
);
}
}
ReactionsSet = undefined;
results = undefined;
}
function getBody(inputs) {
if (inputs.body) {
return inputs.body;
} else if (inputs.bodyFile) {
return readFileSync(inputs.bodyFile, 'utf-8');
} else {
return '';
}
}
async function run() {
try {
const inputs = {
token: core.getInput("token"),
repository: core.getInput("repository"),
issueNumber: core.getInput("issue-number"),
commentId: core.getInput("comment-id"),
body: core.getInput("body"),
bodyFile: core.getInput("body-file"),
editMode: core.getInput("edit-mode"),
reactions: core.getInput("reactions")
? core.getInput("reactions")
: core.getInput("reaction-type"),
};
core.debug(`Inputs: ${inspect(inputs)}`);
const repository = inputs.repository
? inputs.repository
: process.env.GITHUB_REPOSITORY;
const repo = repository.split("/");
core.debug(`repository: ${repository}`);
const editMode = inputs.editMode ? inputs.editMode : "append";
core.debug(`editMode: ${editMode}`);
if (!["append", "replace"].includes(editMode)) {
core.setFailed(`Invalid edit-mode '${editMode}'.`);
return;
}
if (inputs.bodyFile && inputs.body) {
core.setFailed("Only one of 'body' or 'body-file' can be set.");
return;
}
if (inputs.bodyFile) {
if (!existsSync(inputs.bodyFile)) {
core.setFailed(`File '${inputs.bodyFile}' does not exist.`);
return;
}
}
const body = getBody(inputs);
const octokit = github.getOctokit(inputs.token);
if (inputs.commentId) {
// Edit a comment
if (!body && !inputs.reactions) {
core.setFailed("Missing comment 'body', 'body-file', or 'reactions'.");
return;
}
if (body) {
var commentBody = "";
if (editMode == "append") {
// Get the comment body
const { data: comment } = await octokit.rest.issues.getComment({
owner: repo[0],
repo: repo[1],
comment_id: inputs.commentId,
});
commentBody = comment.body + "\n";
}
commentBody = commentBody + body;
core.debug(`Comment body: ${commentBody}`);
await octokit.rest.issues.updateComment({
owner: repo[0],
repo: repo[1],
comment_id: inputs.commentId,
body: commentBody,
});
core.info(`Updated comment id '${inputs.commentId}'.`);
core.setOutput("comment-id", inputs.commentId);
}
// Set comment reactions
if (inputs.reactions) {
await addReactions(octokit, repo, inputs.commentId, inputs.reactions);
}
} else if (inputs.issueNumber) {
// Create a comment
if (!body) {
core.setFailed("Missing comment 'body' or 'body-file'.");
return;
}
const { data: comment } = await octokit.rest.issues.createComment({
owner: repo[0],
repo: repo[1],
issue_number: inputs.issueNumber,
body,
});
core.info(
`Created comment id '${comment.id}' on issue '${inputs.issueNumber}'.`
);
core.setOutput("comment-id", comment.id);
// Set comment reactions
if (inputs.reactions) {
await addReactions(octokit, repo, comment.id, inputs.reactions);
}
} else {
core.setFailed("Missing either 'issue-number' or 'comment-id'.");
return;
}
} catch (error) {
core.debug(inspect(error));
core.setFailed(error.message);
if (error.message == 'Resource not accessible by integration') {
core.error(`See this action's readme for details about this error`);
}
}
}
run();

11
jest.config.js Normal file
View File

@ -0,0 +1,11 @@
module.exports = {
clearMocks: true,
moduleFileExtensions: ['js', 'ts'],
testEnvironment: 'node',
testMatch: ['**/*.test.ts'],
testRunner: 'jest-circus/runner',
transform: {
'^.+\\.ts$': 'ts-jest'
},
verbose: true
}

7654
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,18 +1,25 @@
{
"name": "create-or-update-comment",
"version": "2.0.0",
"version": "3.0.0",
"description": "Create or update an issue or pull request comment",
"main": "index.js",
"main": "lib/main.js",
"scripts": {
"lint": "eslint index.js",
"package": "ncc build index.js -o dist",
"test": "eslint index.js && jest --passWithNoTests"
"build": "tsc && ncc build",
"format": "prettier --write '**/*.ts'",
"format-check": "prettier --check '**/*.ts'",
"lint": "eslint src/**/*.ts",
"test": "jest --passWithNoTests"
},
"repository": {
"type": "git",
"url": "git+https://github.com/peter-evans/create-or-update-comment.git"
},
"keywords": [],
"keywords": [
"actions",
"create",
"update",
"comment"
],
"author": "Peter Evans",
"license": "MIT",
"bugs": {
@ -24,8 +31,19 @@
"@actions/github": "^5.1.1"
},
"devDependencies": {
"@types/jest": "^27.0.3",
"@types/node": "^18.15.10",
"@typescript-eslint/eslint-plugin": "^5.56.0",
"@typescript-eslint/parser": "^5.56.0",
"@vercel/ncc": "^0.36.1",
"eslint": "^8.37.0",
"jest": "^29.5.0"
"eslint": "^8.36.0",
"eslint-plugin-github": "^4.7.0",
"eslint-plugin-jest": "^27.2.1",
"jest": "^27.5.1",
"jest-circus": "^27.4.2",
"js-yaml": "^4.1.0",
"prettier": "^2.8.7",
"ts-jest": "^27.1.5",
"typescript": "^4.9.5"
}
}

View File

@ -0,0 +1,270 @@
import * as core from '@actions/core'
import * as github from '@actions/github'
import * as utils from './utils'
import {inspect} from 'util'
export interface Inputs {
token: string
repository: string
issueNumber: number
commentId: number
body: string
bodyPath: string
editMode: string
appendSeparator: string
reactions: string[]
reactionsEditMode: string
}
const REACTION_TYPES = [
'+1',
'-1',
'laugh',
'confused',
'heart',
'hooray',
'rocket',
'eyes'
]
function getReactionsSet(reactions: string[]): string[] {
const reactionsSet = [
...new Set(
reactions.filter(item => {
if (!REACTION_TYPES.includes(item)) {
core.warning(`Skipping invalid reaction '${item}'.`)
return false
}
return true
})
)
]
if (!reactionsSet) {
throw new Error(`No valid reactions are contained in '${reactions}'.`)
}
return reactionsSet
}
async function addReactions(
octokit,
owner: string,
repo: string,
commentId: number,
reactions: string[]
) {
const results = await Promise.allSettled(
reactions.map(async reaction => {
await octokit.rest.reactions.createForIssueComment({
owner: owner,
repo: repo,
comment_id: commentId,
content: reaction
})
core.info(`Setting '${reaction}' reaction on comment.`)
})
)
for (let i = 0, l = results.length; i < l; i++) {
if (results[i].status === 'fulfilled') {
core.info(
`Added reaction '${reactions[i]}' to comment id '${commentId}'.`
)
} else if (results[i].status === 'rejected') {
core.warning(
`Adding reaction '${reactions[i]}' to comment id '${commentId}' failed.`
)
}
}
}
async function removeReactions(
octokit,
owner: string,
repo: string,
commentId: number,
reactions: Reaction[]
) {
const results = await Promise.allSettled(
reactions.map(async reaction => {
await octokit.rest.reactions.deleteForIssueComment({
owner: owner,
repo: repo,
comment_id: commentId,
reaction_id: reaction.id
})
core.info(`Removing '${reaction.content}' reaction from comment.`)
})
)
for (let i = 0, l = results.length; i < l; i++) {
if (results[i].status === 'fulfilled') {
core.info(
`Removed reaction '${reactions[i].content}' from comment id '${commentId}'.`
)
} else if (results[i].status === 'rejected') {
core.warning(
`Removing reaction '${reactions[i].content}' from comment id '${commentId}' failed.`
)
}
}
}
function appendSeparatorTo(body: string, separator: string): string {
switch (separator) {
case 'newline':
return body + '\n'
case 'space':
return body + ' '
default: // none
return body
}
}
async function createComment(
octokit,
owner: string,
repo: string,
issueNumber: number,
body: string
): Promise<number> {
const {data: comment} = await octokit.rest.issues.createComment({
owner: owner,
repo: repo,
issue_number: issueNumber,
body
})
core.info(`Created comment id '${comment.id}' on issue '${issueNumber}'.`)
return comment.id
}
async function updateComment(
octokit,
owner: string,
repo: string,
commentId: number,
body: string,
editMode: string,
appendSeparator: string
): Promise<number> {
if (body) {
let commentBody = ''
if (editMode == 'append') {
// Get the comment body
const {data: comment} = await octokit.rest.issues.getComment({
owner: owner,
repo: repo,
comment_id: commentId
})
commentBody = appendSeparatorTo(
comment.body ? comment.body : '',
appendSeparator
)
}
commentBody = commentBody + body
core.debug(`Comment body: ${commentBody}`)
await octokit.rest.issues.updateComment({
owner: owner,
repo: repo,
comment_id: commentId,
body: commentBody
})
core.info(`Updated comment id '${commentId}'.`)
}
return commentId
}
async function getAuthenticatedUser(octokit): Promise<string> {
try {
const {data: user} = await octokit.rest.users.getAuthenticated()
return user.login
} catch (error) {
if (
utils
.getErrorMessage(error)
.includes('Resource not accessible by integration')
) {
// In this case we can assume the token is the default GITHUB_TOKEN and
// therefore the user is 'github-actions[bot]'.
return 'github-actions[bot]'
} else {
throw error
}
}
}
type Reaction = {
id: number
content: string
}
async function getCommentReactionsForUser(
octokit,
owner: string,
repo: string,
commentId: number,
user: string
): Promise<Reaction[]> {
const userReactions: Reaction[] = []
for await (const {data: reactions} of octokit.paginate.iterator(
octokit.rest.reactions.listForIssueComment,
{
owner,
repo,
comment_id: commentId,
per_page: 100
}
)) {
const filteredReactions: Reaction[] = reactions
.filter(reaction => reaction.user.login === user)
.map(reaction => {
return {id: reaction.id, content: reaction.content}
})
userReactions.push(...filteredReactions)
}
return userReactions
}
export async function createOrUpdateComment(
inputs: Inputs,
body: string
): Promise<void> {
const [owner, repo] = inputs.repository.split('/')
const octokit = github.getOctokit(inputs.token)
const commentId = inputs.commentId
? await updateComment(
octokit,
owner,
repo,
inputs.commentId,
body,
inputs.editMode,
inputs.appendSeparator
)
: await createComment(octokit, owner, repo, inputs.issueNumber, body)
core.setOutput('comment-id', commentId)
if (inputs.reactions) {
const reactionsSet = getReactionsSet(inputs.reactions)
// Remove reactions if reactionsEditMode is 'replace'
if (inputs.commentId && inputs.reactionsEditMode === 'replace') {
const authenticatedUser = await getAuthenticatedUser(octokit)
const userReactions = await getCommentReactionsForUser(
octokit,
owner,
repo,
commentId,
authenticatedUser
)
core.debug(inspect(userReactions))
const reactionsToRemove = userReactions.filter(
reaction => !reactionsSet.includes(reaction.content)
)
await removeReactions(octokit, owner, repo, commentId, reactionsToRemove)
}
await addReactions(octokit, owner, repo, commentId, reactionsSet)
}
}

82
src/main.ts Normal file
View File

@ -0,0 +1,82 @@
import * as core from '@actions/core'
import {Inputs, createOrUpdateComment} from './create-or-update-comment'
import {existsSync, readFileSync} from 'fs'
import {inspect} from 'util'
import * as utils from './utils'
function getBody(inputs: Inputs) {
if (inputs.body) {
return inputs.body
} else if (inputs.bodyPath) {
return readFileSync(inputs.bodyPath, 'utf-8')
} else {
return ''
}
}
async function run(): Promise<void> {
try {
const inputs: Inputs = {
token: core.getInput('token'),
repository: core.getInput('repository'),
issueNumber: Number(core.getInput('issue-number')),
commentId: Number(core.getInput('comment-id')),
body: core.getInput('body'),
bodyPath: core.getInput('body-path') || core.getInput('body-file'),
editMode: core.getInput('edit-mode'),
appendSeparator: core.getInput('append-separator'),
reactions: utils.getInputAsArray('reactions'),
reactionsEditMode: core.getInput('reactions-edit-mode')
}
core.debug(`Inputs: ${inspect(inputs)}`)
if (!['append', 'replace'].includes(inputs.editMode)) {
throw new Error(`Invalid edit-mode '${inputs.editMode}'.`)
}
if (!['append', 'replace'].includes(inputs.reactionsEditMode)) {
throw new Error(
`Invalid reactions edit-mode '${inputs.reactionsEditMode}'.`
)
}
if (!['newline', 'space', 'none'].includes(inputs.appendSeparator)) {
throw new Error(`Invalid append-separator '${inputs.appendSeparator}'.`)
}
if (inputs.bodyPath && inputs.body) {
throw new Error("Only one of 'body' or 'body-path' can be set.")
}
if (inputs.bodyPath) {
if (!existsSync(inputs.bodyPath)) {
throw new Error(`File '${inputs.bodyPath}' does not exist.`)
}
}
const body = getBody(inputs)
if (inputs.commentId) {
if (!body && !inputs.reactions) {
throw new Error("Missing comment 'body', 'body-path', or 'reactions'.")
}
} else if (inputs.issueNumber) {
if (!body) {
throw new Error("Missing comment 'body' or 'body-path'.")
}
} else {
throw new Error("Missing either 'issue-number' or 'comment-id'.")
}
createOrUpdateComment(inputs, body)
} catch (error) {
core.debug(inspect(error))
const errMsg = utils.getErrorMessage(error)
core.setFailed(errMsg)
if (errMsg == 'Resource not accessible by integration') {
core.error(`See this action's readme for details about this error`)
}
}
}
run()

20
src/utils.ts Normal file
View File

@ -0,0 +1,20 @@
import * as core from '@actions/core'
export function getInputAsArray(
name: string,
options?: core.InputOptions
): string[] {
return getStringAsArray(core.getInput(name, options))
}
export function getStringAsArray(str: string): string[] {
return str
.split(/[\n,]+/)
.map(s => s.trim())
.filter(x => x !== '')
}
export function getErrorMessage(error: unknown) {
if (error instanceof Error) return error.message
return String(error)
}

16
tsconfig.json Normal file
View File

@ -0,0 +1,16 @@
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"lib": [
"es6"
],
"outDir": "./lib",
"rootDir": "./src",
"declaration": true,
"strict": true,
"noImplicitAny": false,
"esModuleInterop": true
},
"exclude": ["__test__", "lib", "node_modules"]
}