Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(preview-comment): auto-fill the github token #156

Merged
merged 3 commits into from
Jan 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 }}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is where the magic happens, it uses the github context to auto-fill the token on 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