Skip to content

Commit

Permalink
refactor(preview-comment): auto-fill the github token (#156)
Browse files Browse the repository at this point in the history
* refactor(preview-comment): auto-fill the github token

* refactor; run tests on every pull request

* refactor: reword comment on pr tests
  • Loading branch information
byCedric authored Jan 25, 2022
1 parent 109c3ed commit 204dfed
Show file tree
Hide file tree
Showing 9 changed files with 103 additions and 48 deletions.
13 changes: 12 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ on:
- cron: '0 15 * * *'
push:
branches: [main]
pull_request:
types: [opened, synchronize]
workflow_dispatch:

concurrency:
Expand Down Expand Up @@ -76,14 +78,23 @@ jobs:
script: |
const message = `${{ steps.preview.outputs.message }}`
if (!message) throw new Error('Message output is empty')
- name: 🧪 Comment on PR (github-token)
uses: ./preview-comment
env:
EXPO_TEST_GITHUB_PULL: 149
with:
project: ./temp
channel: test

- name: 🧪 Comment on PR
- name: 🧪 Comment on PR (GITHUB_TOKEN)
uses: ./preview-comment
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
EXPO_TEST_GITHUB_PULL: 149
with:
project: ./temp
channel: test
github-token: badtoken


2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,6 @@ jobs:
- name: 💬 Comment preview
uses: expo/expo-github-action/preview-comment@v7
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
channel: pr-${{ github.event.number }}
```
Expand Down
44 changes: 24 additions & 20 deletions build/preview-comment/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13563,6 +13563,7 @@ function commentInput() {
message: (0, core_1.getInput)('message') || exports.DEFAULT_MESSAGE,
messageId: (0, core_1.getInput)('message-id') || exports.DEFAULT_ID,
project: (0, core_1.getInput)('project'),
githubToken: (0, core_1.getInput)('github-token'),
};
}
exports.commentInput = commentInput;
Expand All @@ -13586,7 +13587,9 @@ async function commentAction(input = commentInput()) {
(0, core_1.info)(`Skipped comment: 'comment' is disabled`);
}
else {
await (0, github_1.createIssueComment)((0, github_1.pullContext)(), {
await (0, github_1.createIssueComment)({
...(0, github_1.pullContext)(),
token: input.githubToken,
id: messageId,
body: messageBody,
});
Expand Down Expand Up @@ -13719,16 +13722,16 @@ const assert_1 = __nccwpck_require__(9491);
* Determine if a comment exists on an issue or pull with the provided identifier.
* This will iterate all comments received from GitHub, and try to exit early if it exists.
*/
async function fetchIssueComment(issue, commentId) {
const github = githubApi();
async function fetchIssueComment(options) {
const github = githubApi(options);
const iterator = github.paginate.iterator(github.rest.issues.listComments, {
owner: issue.owner,
repo: issue.repo,
issue_number: issue.number,
owner: options.owner,
repo: options.repo,
issue_number: options.number,
});
for await (const { data: batch } of iterator) {
for (const item of batch) {
if ((item.body || '').includes(commentId)) {
if ((item.body || '').includes(options.id)) {
return item;
}
}
Expand All @@ -13740,33 +13743,34 @@ exports.fetchIssueComment = fetchIssueComment;
* This includes a hidden identifier (markdown comment) to identify the comment later.
* It will also update the comment when a previous comment id was found.
*/
async function createIssueComment(issue, comment) {
const github = githubApi();
const body = `<!-- ${comment.id} -->\n${comment.body}`;
const existing = await fetchIssueComment(issue, comment.id);
async function createIssueComment(options) {
const github = githubApi(options);
const body = `<!-- ${options.id} -->\n${options.body}`;
const existing = await fetchIssueComment(options);
if (existing) {
return github.rest.issues.updateComment({
owner: issue.owner,
repo: issue.repo,
owner: options.owner,
repo: options.repo,
comment_id: existing.id,
body,
});
}
return github.rest.issues.createComment({
owner: issue.owner,
repo: issue.repo,
issue_number: issue.number,
owner: options.owner,
repo: options.repo,
issue_number: options.number,
body,
});
}
exports.createIssueComment = createIssueComment;
/**
* Get an authenticated octokit instance.
* This uses the 'GITHUB_TOKEN' environment variable.
* This uses the 'GITHUB_TOKEN' environment variable, or 'github-token' input.
*/
function githubApi() {
(0, assert_1.ok)(process.env['GITHUB_TOKEN'], 'This step requires a GITHUB_TOKEN environment variable to create comments');
return (0, github_1.getOctokit)(process.env['GITHUB_TOKEN']);
function githubApi(options = {}) {
const token = process.env['GITHUB_TOKEN'] || options.token;
(0, assert_1.ok)(token, `This step requires 'github-token' or a GITHUB_TOKEN environment variable to create comments`);
return (0, github_1.getOctokit)(token);
}
exports.githubApi = githubApi;
/**
Expand Down
10 changes: 8 additions & 2 deletions preview-comment/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ Here is a summary of all the input options you can use.
| **comment** | `true` | If this action should comment on a PR |
| **message** | _[see code][code-defaults]_ | The message template |
| **message-id** | _[see code][code-defaults]_ | A unique id template to prevent duplicate comments ([read more](#preventing-duplicate-comments)) |
| **github-token** | `GITHUB_TOKEN` | A GitHub token to use when commenting on PR ([read more](#github-tokens)) |

## Available outputs

Expand Down Expand Up @@ -107,8 +108,6 @@ jobs:

- name: 💬 Comment in preview
uses: expo/expo-github-action/preview-comment@v7
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
channel: pr-${{ github.event.number }}
```
Expand Down Expand Up @@ -173,6 +172,12 @@ jobs:
When automating these preview comments, you have to be careful not to spam a pull request on every successful run.
Every comment contains a generated **message-id** to identify previously made comments and update instead of creating a new comment.

### GitHub tokens

When using the GitHub API, you always need to be authenticated.
This action tries to auto-authenticate using the [Automatic token authentication][link-gha-token] from GitHub.
You can overwrite the token by adding the `GITHUB_TOKEN` environment variable, or add the **github-token** input.

<div align="center">
<br />
with :heart:&nbsp;<strong>byCedric</strong>
Expand All @@ -181,3 +186,4 @@ Every comment contains a generated **message-id** to identify previously made co

[code-defaults]: ../src/actions/preview-comment.ts
[link-actions]: https://help.github.com/en/categories/automating-your-workflow-with-github-actions
[link-gha-token]: https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token
4 changes: 4 additions & 0 deletions preview-comment/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ inputs:
message-id:
description: A unique identifier to prevent multiple comments on the same pull request
required: false
github-token:
description: GitHub access token to comment on PRs
required: false
default: ${{ github.token }}
outputs:
projectOwner:
description: The resolved project owner
Expand Down
5 changes: 4 additions & 1 deletion src/actions/preview-comment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export function commentInput() {
message: getInput('message') || DEFAULT_MESSAGE,
messageId: getInput('message-id') || DEFAULT_ID,
project: getInput('project'),
githubToken: getInput('github-token'),
};
}

Expand All @@ -46,7 +47,9 @@ export async function commentAction(input: CommentInput = commentInput()) {
if (!input.comment) {
info(`Skipped comment: 'comment' is disabled`);
} else {
await createIssueComment(pullContext(), {
await createIssueComment({
...pullContext(),
token: input.githubToken,
id: messageId,
body: messageBody,
});
Expand Down
44 changes: 25 additions & 19 deletions src/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ import { ok as assert } from 'assert';

type IssueContext = typeof context['issue'];

type AuthContext = {
/** GitHub token from the 'github-input' to authenticate with */
token?: string;
};

type Comment = {
/** A hidden identifier to embed in the comment */
id: string;
Expand All @@ -14,17 +19,17 @@ type Comment = {
* Determine if a comment exists on an issue or pull with the provided identifier.
* This will iterate all comments received from GitHub, and try to exit early if it exists.
*/
export async function fetchIssueComment(issue: IssueContext, commentId: Comment['id']) {
const github = githubApi();
export async function fetchIssueComment(options: AuthContext & IssueContext & Pick<Comment, 'id'>) {
const github = githubApi(options);
const iterator = github.paginate.iterator(github.rest.issues.listComments, {
owner: issue.owner,
repo: issue.repo,
issue_number: issue.number,
owner: options.owner,
repo: options.repo,
issue_number: options.number,
});

for await (const { data: batch } of iterator) {
for (const item of batch) {
if ((item.body || '').includes(commentId)) {
if ((item.body || '').includes(options.id)) {
return item;
}
}
Expand All @@ -36,35 +41,36 @@ export async function fetchIssueComment(issue: IssueContext, commentId: Comment[
* This includes a hidden identifier (markdown comment) to identify the comment later.
* It will also update the comment when a previous comment id was found.
*/
export async function createIssueComment(issue: IssueContext, comment: Comment) {
const github = githubApi();
const body = `<!-- ${comment.id} -->\n${comment.body}`;
const existing = await fetchIssueComment(issue, comment.id);
export async function createIssueComment(options: AuthContext & IssueContext & Comment) {
const github = githubApi(options);
const body = `<!-- ${options.id} -->\n${options.body}`;
const existing = await fetchIssueComment(options);

if (existing) {
return github.rest.issues.updateComment({
owner: issue.owner,
repo: issue.repo,
owner: options.owner,
repo: options.repo,
comment_id: existing.id,
body,
});
}

return github.rest.issues.createComment({
owner: issue.owner,
repo: issue.repo,
issue_number: issue.number,
owner: options.owner,
repo: options.repo,
issue_number: options.number,
body,
});
}

/**
* Get an authenticated octokit instance.
* This uses the 'GITHUB_TOKEN' environment variable.
* This uses the 'GITHUB_TOKEN' environment variable, or 'github-token' input.
*/
export function githubApi(): ReturnType<typeof getOctokit> {
assert(process.env['GITHUB_TOKEN'], 'This step requires a GITHUB_TOKEN environment variable to create comments');
return getOctokit(process.env['GITHUB_TOKEN']);
export function githubApi(options: AuthContext = {}): ReturnType<typeof getOctokit> {
const token = process.env['GITHUB_TOKEN'] || options.token;
assert(token, `This step requires 'github-token' or a GITHUB_TOKEN environment variable to create comments`);
return getOctokit(token);
}

/**
Expand Down
7 changes: 7 additions & 0 deletions tests/actions/preview-comment.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ describe(commentInput, () => {
message: DEFAULT_MESSAGE,
messageId: DEFAULT_ID,
project: undefined,
githubToken: undefined,
});
});

Expand All @@ -46,6 +47,11 @@ describe(commentInput, () => {
mockInput({ channel: 'pr-420' });
expect(commentInput()).toMatchObject({ channel: 'pr-420' });
});

it('returns github-token', () => {
mockInput({ 'github-token': 'fakegithubtoken' });
expect(commentInput()).toMatchObject({ githubToken: 'fakegithubtoken' });
});
});

describe(commentAction, () => {
Expand All @@ -55,6 +61,7 @@ describe(commentAction, () => {
message: DEFAULT_MESSAGE,
messageId: DEFAULT_ID,
project: '',
githubToken: '',
};

beforeEach(() => {
Expand Down
22 changes: 19 additions & 3 deletions tests/github.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,34 @@ jest.mock('@actions/github');
describe(githubApi, () => {
afterEach(resetEnv);

it('throws when GITHUB_TOKEN is undefined', () => {
it('throws when GITHUB_TOKEN and input are undefined', () => {
setEnv('GITHUB_TOKEN', '');
expect(() => githubApi()).toThrow(`requires a GITHUB_TOKEN`);
expect(() => githubApi()).toThrow(`requires 'github-token' or a GITHUB_TOKEN`);
});

it('returns an octokit instance', () => {
it('returns octokit instance with GITHUB_TOKEN', () => {
setEnv('GITHUB_TOKEN', 'fakegithubtoken');
const fakeGithub = {};
jest.mocked(github.getOctokit).mockReturnValue(fakeGithub as any);
expect(githubApi()).toBe(fakeGithub);
expect(github.getOctokit).toBeCalledWith('fakegithubtoken');
});

it('returns octokit instance with input', () => {
setEnv('GITHUB_TOKEN', '');
const fakeGithub = {};
jest.mocked(github.getOctokit).mockReturnValue(fakeGithub as any);
expect(githubApi({ token: 'fakegithubtoken' })).toBe(fakeGithub);
expect(github.getOctokit).toBeCalledWith('fakegithubtoken');
});

it('uses GITHUB_TOKEN before input', () => {
setEnv('GITHUB_TOKEN', 'fakegithubtoken');
const fakeGithub = {};
jest.mocked(github.getOctokit).mockReturnValue(fakeGithub as any);
expect(githubApi({ token: 'badfakegithubtoken' })).toBe(fakeGithub);
expect(github.getOctokit).toBeCalledWith('fakegithubtoken');
});
});

describe(pullContext, () => {
Expand Down

0 comments on commit 204dfed

Please sign in to comment.