Compare commits

..

No commits in common. "main" and "v2" have entirely different histories.
main ... v2

24 changed files with 10860 additions and 33279 deletions

View File

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

View File

@ -1,18 +1,17 @@
{
"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"
}
}
"env": {
"commonjs": true,
"es6": true,
"node": true
},
"extends": "eslint:recommended",
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaVersion": 2018
},
"rules": {
}
}

View File

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

View File

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

View File

@ -6,7 +6,7 @@ jobs:
runs-on: ubuntu-latest
if: github.actor == 'dependabot[bot]'
steps:
- uses: peter-evans/enable-pull-request-automerge@v3
- uses: peter-evans/enable-pull-request-automerge@v2
with:
token: ${{ secrets.ACTIONS_BOT_TOKEN }}
pull-request-number: ${{ github.event.pull_request.number }}

View File

@ -22,21 +22,19 @@ jobs:
outputs:
issue-number: ${{ steps.vars.outputs.issue-number }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 20.x
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
- uses: actions/upload-artifact@v4
- run: npm run package
- uses: actions/upload-artifact@v3
with:
name: dist
path: dist
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v3
with:
name: action.yml
path: action.yml
@ -56,14 +54,14 @@ jobs:
matrix:
target: [built, committed]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- if: matrix.target == 'built' || github.event_name == 'pull_request'
uses: actions/download-artifact@v4
uses: actions/download-artifact@v3
with:
name: dist
path: dist
- if: matrix.target == 'built' || github.event_name == 'pull_request'
uses: actions/download-artifact@v4
uses: actions/download-artifact@v3
with:
name: action.yml
path: .
@ -88,47 +86,40 @@ 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-path: .github/comment-body.md
reactions: |
+1
body-file: .github/comment-body.md
reactions: '+1'
- name: Test update comment from file
uses: ./
with:
comment-id: ${{ steps.couc2.outputs.comment-id }}
body-path: .github/comment-body-addition.md
append-separator: space
reactions: eyes, rocket
reactions-edit-mode: replace
body-file: .github/comment-body-addition.md
reactions: eyes
package:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
needs: [test]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
- uses: actions/checkout@v3
- uses: actions/download-artifact@v3
with:
name: dist
path: dist
- name: Create Pull Request
uses: peter-evans/create-pull-request@v7
uses: peter-evans/create-pull-request@v4
with:
token: ${{ secrets.ACTIONS_BOT_TOKEN }}
commit-message: Update distribution

View File

@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Slash Command Dispatch
uses: peter-evans/slash-command-dispatch@v4
uses: peter-evans/slash-command-dispatch@v3
with:
token: ${{ secrets.ACTIONS_BOT_TOKEN }}
config: >

View File

@ -18,7 +18,7 @@ jobs:
echo "branch=$branch" >> $GITHUB_OUTPUT
# Checkout the branch to test
- uses: actions/checkout@v4
- uses: actions/checkout@v3
with:
repository: ${{ steps.vars.outputs.repository }}
ref: ${{ steps.vars.outputs.branch }}
@ -45,7 +45,6 @@ jobs:
body: |
**Edit:** Some additional info
reactions: eyes
reactions-edit-mode: replace
# Test add reactions
- name: Add reactions
@ -54,17 +53,24 @@ 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-path: .github/comment-body.md
body-file: .github/comment-body.md
# Test create from template
- name: Render template
id: template
uses: chuhlomin/render-template@v1.10
uses: chuhlomin/render-template@v1.6
with:
template: .github/comment-template.md
vars: |
@ -76,10 +82,3 @@ jobs:
with:
issue-number: 1
body: ${{ steps.template.outputs.result }}
- name: Add reaction
uses: peter-evans/create-or-update-comment@v4
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

@ -1,66 +0,0 @@
name: Test v3
on: workflow_dispatch
jobs:
testCreateOrUpdateComment:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Test create
- name: Create comment
uses: peter-evans/create-or-update-comment@v4
id: couc
with:
token: ${{ secrets.ACTIONS_BOT_TOKEN }}
issue-number: 1
body: |
This is a multi-line test comment
- With GitHub **Markdown** :sparkles:
- Created by [create-or-update-comment][1]
[1]: https://github.com/peter-evans/create-or-update-comment
reactions: '+1'
# Test update
- name: Update comment
uses: peter-evans/create-or-update-comment@v4
with:
token: ${{ secrets.ACTIONS_BOT_TOKEN }}
comment-id: ${{ steps.couc.outputs.comment-id }}
body: |
**Edit:** Some additional info
reactions: eyes
reactions-edit-mode: replace
# Test add reactions
- name: Add reactions
uses: peter-evans/create-or-update-comment@v4
with:
token: ${{ secrets.ACTIONS_BOT_TOKEN }}
comment-id: ${{ steps.couc.outputs.comment-id }}
reactions: heart, hooray, laugh
# Test create with body from file
- name: Create comment
uses: peter-evans/create-or-update-comment@v4
with:
token: ${{ secrets.ACTIONS_BOT_TOKEN }}
issue-number: 1
body-file: .github/comment-body.md
# Test create from template
- name: Render template
id: template
uses: chuhlomin/render-template@v1.10
with:
template: .github/comment-template.md
vars: |
foo: this
bar: that
- name: Create comment
uses: peter-evans/create-or-update-comment@v4
with:
token: ${{ secrets.ACTIONS_BOT_TOKEN }}
issue-number: 1
body: ${{ steps.template.outputs.result }}

View File

@ -1,32 +0,0 @@
name: Update Major Version
run-name: Update ${{ github.event.inputs.main_version }} to ${{ github.event.inputs.target }}
on:
workflow_dispatch:
inputs:
target:
description: The target tag or reference
required: true
main_version:
type: choice
description: The major version tag to update
options:
- v3
- v4
jobs:
tag:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
token: ${{ secrets.ACTIONS_BOT_TOKEN }}
fetch-depth: 0
- name: Git config
run: |
git config user.name actions-bot
git config user.email actions-bot@users.noreply.github.com
- name: Tag new target
run: git tag -f ${{ github.event.inputs.main_version }} ${{ github.event.inputs.target }}
- name: Push new tag
run: git push origin ${{ github.event.inputs.main_version }} --force

3
.gitignore vendored
View File

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

View File

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

View File

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

View File

@ -4,13 +4,15 @@
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@v4
uses: peter-evans/create-or-update-comment@v2
with:
issue-number: 1
body: |
@ -26,7 +28,7 @@ A GitHub action to create or update an issue or pull request comment.
```yml
- name: Update comment
uses: peter-evans/create-or-update-comment@v4
uses: peter-evans/create-or-update-comment@v2
with:
comment-id: 557858210
body: |
@ -38,33 +40,28 @@ A GitHub action to create or update an issue or pull request comment.
```yml
- name: Add reactions
uses: peter-evans/create-or-update-comment@v4
uses: peter-evans/create-or-update-comment@v2
with:
comment-id: 557858210
reactions: |
heart
hooray
laugh
reactions: heart, hooray, laugh
```
### Action inputs
| Name | Description | Default |
| --- | --- | --- |
| `token` | `GITHUB_TOKEN` (`issues: write`, `pull-requests: write`) or a `repo` scoped [PAT](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token). | `GITHUB_TOKEN` |
| `token` | `GITHUB_TOKEN` (`issues: write`, `pull-requests: write`) or a `repo` scoped [PAT](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token). | `GITHUB_TOKEN` |
| `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-path`. | |
| `body-path` | 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-file`. | |
| `body-file` | 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` |
| `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` |
| `reactions` | A comma separated list of reactions to add to the comment. (`+1`, `-1`, `laugh`, `confused`, `heart`, `hooray`, `rocket`, `eyes`) | |
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`.
This is due to token restrictions put in place by GitHub Actions. Private repositories can be configured to [enable workflows](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/enabling-features-for-your-repository/managing-github-actions-settings-for-a-repository#enabling-workflows-for-forks-of-private-repositories) from forks to run without restriction. See [here](https://github.com/peter-evans/create-pull-request/blob/main/docs/concepts-guidelines.md#restrictions-on-repository-forks) for further explanation. Alternatively, use the [`pull_request_target`](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target) event to comment on pull requests.
This is due to token restrictions put in place by GitHub Actions. Private repositories can be configured to [enable workflows](https://docs.github.com/en/github/administering-a-repository/disabling-or-limiting-github-actions-for-a-repository#enabling-workflows-for-private-repository-forks) from forks to run without restriction. See [here](https://github.com/peter-evans/create-pull-request/blob/main/docs/concepts-guidelines.md#restrictions-on-repository-forks) for further explanation. Alternatively, use the [`pull_request_target`](https://docs.github.com/en/actions/reference/events-that-trigger-workflows#pull_request_target) event to comment on pull requests.
#### Outputs
@ -73,7 +70,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@v4
uses: peter-evans/create-or-update-comment@v2
id: couc
with:
issue-number: 1
@ -98,7 +95,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Add reaction
uses: peter-evans/create-or-update-comment@v4
uses: peter-evans/create-or-update-comment@v2
with:
comment-id: ${{ github.event.comment.id }}
reactions: eyes
@ -113,7 +110,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@v3
uses: peter-evans/find-comment@v2
id: fc
with:
issue-number: ${{ github.event.pull_request.number }}
@ -121,7 +118,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@v4
uses: peter-evans/create-or-update-comment@v2
with:
comment-id: ${{ steps.fc.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number }}
@ -134,7 +131,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@v3
uses: peter-evans/find-comment@v2
id: fc
with:
issue-number: ${{ github.event.pull_request.number }}
@ -143,7 +140,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@v4
uses: peter-evans/create-or-update-comment@v2
with:
issue-number: ${{ github.event.pull_request.number }}
body: |
@ -152,7 +149,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@v4
uses: peter-evans/create-or-update-comment@v2
with:
comment-id: ${{ steps.fc.outputs.comment-id }}
body: |
@ -164,10 +161,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@v4
uses: peter-evans/create-or-update-comment@v2
with:
issue-number: 1
body-path: 'comment-body.md'
body-file: 'comment-body.md'
```
### Using a markdown template
@ -190,7 +187,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@v4
uses: peter-evans/create-or-update-comment@v2
with:
issue-number: 1
body: ${{ steps.template.outputs.result }}
@ -198,7 +195,7 @@ The template is rendered using the [render-template](https://github.com/chuhlomi
### Accessing issues and comments in other repositories
You can create and update comments in another repository by using a [PAT](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) instead of `GITHUB_TOKEN`.
You can create and update comments in another repository by using a [PAT](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) instead of `GITHUB_TOKEN`.
The user associated with the PAT must have write access to the repository.
## License

View File

@ -6,33 +6,25 @@ 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-path`.'
body-path:
description: 'The path to a file containing the comment body. Cannot be used in conjunction with `body`.'
description: 'The comment body. Cannot be used in conjunction with `body-file`.'
body-file:
description: 'Deprecated in favour of `body-path`.'
description: 'The path to a file containing the comment body. Cannot be used in conjunction with `body`.'
edit-mode:
description: 'The mode when updating a comment, "replace" or "append".'
default: 'append'
append-separator:
description: 'The separator to use when appending to an existing comment. (`newline`, `space`, `none`)'
default: 'newline'
reaction-type:
description: 'Deprecated in favour of `reactions`'
reactions:
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'
description: 'A comma separated list of reactions to add to the comment.'
outputs:
comment-id:
description: 'The id of the created comment'
runs:
using: 'node20'
using: 'node16'
main: 'dist/index.js'
branding:
icon: 'message-square'

34708
dist/index.js vendored

File diff suppressed because one or more lines are too long

192
index.js Normal file
View File

@ -0,0 +1,192 @@
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();

View File

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

8494
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,25 +1,18 @@
{
"name": "create-or-update-comment",
"version": "4.0.0",
"version": "2.0.0",
"description": "Create or update an issue or pull request comment",
"main": "lib/main.js",
"main": "index.js",
"scripts": {
"build": "tsc && ncc build",
"format": "prettier --write '**/*.ts'",
"format-check": "prettier --check '**/*.ts'",
"lint": "eslint src/**/*.ts",
"test": "jest --passWithNoTests"
"lint": "eslint index.js",
"package": "ncc build index.js -o dist",
"test": "eslint index.js && jest --passWithNoTests"
},
"repository": {
"type": "git",
"url": "git+https://github.com/peter-evans/create-or-update-comment.git"
},
"keywords": [
"actions",
"create",
"update",
"comment"
],
"keywords": [],
"author": "Peter Evans",
"license": "MIT",
"bugs": {
@ -27,24 +20,12 @@
},
"homepage": "https://github.com/peter-evans/create-or-update-comment#readme",
"dependencies": {
"@actions/core": "^1.11.1",
"@actions/github": "^6.0.0"
"@actions/core": "^1.10.0",
"@actions/github": "^5.1.1"
},
"devDependencies": {
"@types/jest": "^27.0.3",
"@types/node": "^18.19.86",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
"@vercel/ncc": "^0.38.3",
"eslint": "^8.57.1",
"eslint-plugin-github": "^4.10.2",
"eslint-plugin-jest": "^27.9.0",
"eslint-plugin-prettier": "^5.2.6",
"jest": "^27.5.1",
"jest-circus": "^27.5.1",
"js-yaml": "^4.1.0",
"prettier": "^3.5.3",
"ts-jest": "^27.1.5",
"typescript": "^4.9.5"
"@vercel/ncc": "^0.36.1",
"eslint": "^8.33.0",
"jest": "^29.4.1"
}
}

View File

@ -1,282 +0,0 @@
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
}
}
function truncateBody(body: string) {
// 65536 characters is the maximum allowed for issue comments.
const truncateWarning = '...*[Comment body truncated]*'
if (body.length > 65536) {
core.warning(`Comment body is too long. Truncating to 65536 characters.`)
return body.substring(0, 65536 - truncateWarning.length) + truncateWarning
}
return body
}
async function createComment(
octokit,
owner: string,
repo: string,
issueNumber: number,
body: string
): Promise<number> {
body = truncateBody(body)
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 = truncateBody(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)
}
}

View File

@ -1,82 +0,0 @@
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()

View File

@ -1,20 +0,0 @@
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)
}

View File

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