Skip to content

Commit

Permalink
feature(comment): add comment-based action to interact with eas in pu…
Browse files Browse the repository at this point in the history
…lls (#171)

* chore: initial command action

* chore: fix action.yml

* fix: update command parser

fix: added publish command

a

* chore: comment the result

* fix: added --non-interactive flag

* show the error

* fix: fixed context issue

* fix: context

* fix: update output

* fix: reaction first

* fix: added help command

* feat: added createBuildComment

* fix: remove project input, because its not supported

* chore: update eas build

* fix: added no-wait

* fix: update message id

* chore: added TODO to original source

* fix: apply suggestions from code review

Co-authored-by: Cedric van Putten <me@bycedric.com>

* chore: update README

* fix: fixed lint

* chore: remove `@expo/eas-json`

* docs(command): add experimental warning to the readme

Co-authored-by: Cedric van Putten <me@bycedric.com>
  • Loading branch information
giautm and byCedric authored Apr 7, 2022
1 parent b5a8ca7 commit 088fd4e
Show file tree
Hide file tree
Showing 12 changed files with 17,915 additions and 21 deletions.
16,543 changes: 16,543 additions & 0 deletions build/command/index.js

Large diffs are not rendered by default.

762 changes: 762 additions & 0 deletions build/command/license.txt

Large diffs are not rendered by default.

132 changes: 121 additions & 11 deletions build/preview-comment/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15760,6 +15760,7 @@ exports.commentAction = exports.commentInput = exports.DEFAULT_MESSAGE = exports
const core_1 = __nccwpck_require__(2186);
const expo_1 = __nccwpck_require__(2489);
const github_1 = __nccwpck_require__(978);
const utils_1 = __nccwpck_require__(1314);
const worker_1 = __nccwpck_require__(8912);
exports.DEFAULT_ID = `app:@{projectOwner}/{projectSlug} channel:{channel}`;
exports.DEFAULT_MESSAGE = `This pull request was automatically deployed using [Expo GitHub Actions](https://github.com/expo/expo-github-action/tree/main/preview-comment)!\n` +
Expand Down Expand Up @@ -15792,8 +15793,8 @@ async function commentAction(input = commentInput()) {
projectSlug: project.slug,
channel: input.channel,
};
const messageId = template(input.messageId, variables);
const messageBody = template(input.message, variables);
const messageId = (0, utils_1.template)(input.messageId, variables);
const messageBody = (0, utils_1.template)(input.message, variables);
if (!input.comment) {
(0, core_1.info)(`Skipped comment: 'comment' is disabled`);
}
Expand All @@ -15812,13 +15813,6 @@ async function commentAction(input = commentInput()) {
(0, core_1.setOutput)('message', messageBody);
}
exports.commentAction = commentAction;
function template(template, replacements) {
let result = template;
for (const name in replacements) {
result = result.replaceAll(`{${name}}`, replacements[name]);
}
return result;
}


/***/ }),
Expand All @@ -15829,12 +15823,41 @@ function template(template, replacements) {
"use strict";

Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.projectDeepLink = exports.projectLink = exports.projectQR = exports.projectInfo = exports.projectOwner = exports.authenticate = void 0;
exports.getBuildLogsUrl = exports.projectDeepLink = exports.projectLink = exports.projectQR = exports.projectInfo = exports.easBuild = exports.runCommand = exports.projectOwner = exports.authenticate = exports.parseCommand = exports.appPlatformEmojis = exports.appPlatformDisplayNames = exports.AppPlatform = void 0;
const core_1 = __nccwpck_require__(2186);
const exec_1 = __nccwpck_require__(1514);
const io_1 = __nccwpck_require__(7436);
const assert_1 = __nccwpck_require__(9491);
const url_1 = __nccwpck_require__(7310);
var AppPlatform;
(function (AppPlatform) {
AppPlatform["Android"] = "ANDROID";
AppPlatform["Ios"] = "IOS";
})(AppPlatform = exports.AppPlatform || (exports.AppPlatform = {}));
exports.appPlatformDisplayNames = {
[AppPlatform.Android]: 'Android',
[AppPlatform.Ios]: 'iOS',
};
exports.appPlatformEmojis = {
[AppPlatform.Ios]: '🍎',
[AppPlatform.Android]: '🤖',
};
const CommandRegExp = /^#(eas|expo)\s+(.+)?$/im;
function parseCommand(input) {
const matches = CommandRegExp.exec(input);
if (matches != null) {
return {
cli: matches[1],
raw: input.trimStart().substring(1).trim(),
args: matches[2]
?.split(' ')
.map(s => s.trim())
.filter(Boolean) ?? [],
};
}
return null;
}
exports.parseCommand = parseCommand;
/**
* Try to authenticate the user using either Expo or EAS CLI.
* This method tries to invoke 'whoami' to validate if the token is valid.
Expand Down Expand Up @@ -15872,6 +15895,34 @@ async function projectOwner(cli = 'expo') {
return stdout.trim();
}
exports.projectOwner = projectOwner;
async function runCommand(cmd) {
let stdout = '';
let stderr = '';
try {
({ stderr, stdout } = await (0, exec_1.getExecOutput)(await (0, io_1.which)(cmd.cli), cmd.args.concat('--non-interactive'), {
silent: false,
}));
}
catch (error) {
throw new Error(`Could not run command ${cmd.args.join(' ')}, reason:\n${error.message | error}`);
}
return [stdout.trim(), stderr.trim()];
}
exports.runCommand = runCommand;
async function easBuild(cmd) {
let stdout = '';
try {
const args = cmd.args.concat('--json', '--non-interactive', '--no-wait');
({ stdout } = await (0, exec_1.getExecOutput)(await (0, io_1.which)('eas', true), args, {
silent: false,
}));
}
catch (error) {
throw new Error(`Could not run command eas build, reason:\n${error.message | error}`);
}
return JSON.parse(stdout);
}
exports.easBuild = easBuild;
/**
* Try to resolve the project info, by running 'expo config --type prebuild'.
*/
Expand Down Expand Up @@ -15928,6 +15979,17 @@ function projectDeepLink(project, channel) {
return url.toString();
}
exports.projectDeepLink = projectDeepLink;
function getBuildLogsUrl(build) {
// TODO: reuse this function from the original source
// see: https://github.com/expo/eas-cli/blob/896f7f038582347c57dc700be9ea7d092b5a3a21/packages/eas-cli/src/build/utils/url.ts#L13-L21
const { project } = build;
const path = project
? `/accounts/${project.ownerAccount.name}/projects/${project.slug}/builds/${build.id}`
: `/builds/${build.id}`;
const url = new url_1.URL(path, 'https://expo.dev');
return url.toString();
}
exports.getBuildLogsUrl = getBuildLogsUrl;


/***/ }),
Expand All @@ -15938,7 +16000,7 @@ exports.projectDeepLink = projectDeepLink;
"use strict";

Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.pullContext = exports.githubApi = exports.createIssueComment = exports.fetchIssueComment = void 0;
exports.issueComment = exports.createReaction = exports.pullContext = exports.githubApi = exports.createIssueComment = exports.fetchIssueComment = void 0;
const github_1 = __nccwpck_require__(5438);
const assert_1 = __nccwpck_require__(9491);
/**
Expand Down Expand Up @@ -16010,6 +16072,54 @@ function pullContext() {
return github_1.context.issue;
}
exports.pullContext = pullContext;
async function createReaction(options) {
const github = githubApi(options);
if (options.comment_id) {
return github.rest.reactions.createForIssueComment({
owner: options.owner,
repo: options.repo,
comment_id: options.comment_id,
content: options.content,
});
}
return github.rest.reactions.createForIssue({
owner: options.owner,
repo: options.repo,
issue_number: options.number,
content: options.content,
});
}
exports.createReaction = createReaction;
function issueComment() {
(0, assert_1.ok)(github_1.context.eventName === 'issue_comment', 'Could not find the issue comment context, make sure to run this from a issue_comment triggered workflow');
return [
(github_1.context.payload?.comment?.body ?? ''),
{
...github_1.context.issue,
comment_id: github_1.context.payload?.comment?.id,
},
];
}
exports.issueComment = issueComment;


/***/ }),

/***/ 1314:
/***/ ((__unused_webpack_module, exports) => {

"use strict";

Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.template = void 0;
function template(template, replacements) {
let result = template;
for (const name in replacements) {
result = result.replaceAll(`{${name}}`, replacements[name]);
}
return result;
}
exports.template = template;


/***/ }),
Expand Down
70 changes: 69 additions & 1 deletion build/setup/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -66905,12 +66905,41 @@ exports.handleCacheError = handleCacheError;
"use strict";

Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.projectDeepLink = exports.projectLink = exports.projectQR = exports.projectInfo = exports.projectOwner = exports.authenticate = void 0;
exports.getBuildLogsUrl = exports.projectDeepLink = exports.projectLink = exports.projectQR = exports.projectInfo = exports.easBuild = exports.runCommand = exports.projectOwner = exports.authenticate = exports.parseCommand = exports.appPlatformEmojis = exports.appPlatformDisplayNames = exports.AppPlatform = void 0;
const core_1 = __nccwpck_require__(2186);
const exec_1 = __nccwpck_require__(1514);
const io_1 = __nccwpck_require__(7436);
const assert_1 = __nccwpck_require__(9491);
const url_1 = __nccwpck_require__(7310);
var AppPlatform;
(function (AppPlatform) {
AppPlatform["Android"] = "ANDROID";
AppPlatform["Ios"] = "IOS";
})(AppPlatform = exports.AppPlatform || (exports.AppPlatform = {}));
exports.appPlatformDisplayNames = {
[AppPlatform.Android]: 'Android',
[AppPlatform.Ios]: 'iOS',
};
exports.appPlatformEmojis = {
[AppPlatform.Ios]: '🍎',
[AppPlatform.Android]: '🤖',
};
const CommandRegExp = /^#(eas|expo)\s+(.+)?$/im;
function parseCommand(input) {
const matches = CommandRegExp.exec(input);
if (matches != null) {
return {
cli: matches[1],
raw: input.trimStart().substring(1).trim(),
args: matches[2]
?.split(' ')
.map(s => s.trim())
.filter(Boolean) ?? [],
};
}
return null;
}
exports.parseCommand = parseCommand;
/**
* Try to authenticate the user using either Expo or EAS CLI.
* This method tries to invoke 'whoami' to validate if the token is valid.
Expand Down Expand Up @@ -66948,6 +66977,34 @@ async function projectOwner(cli = 'expo') {
return stdout.trim();
}
exports.projectOwner = projectOwner;
async function runCommand(cmd) {
let stdout = '';
let stderr = '';
try {
({ stderr, stdout } = await (0, exec_1.getExecOutput)(await (0, io_1.which)(cmd.cli), cmd.args.concat('--non-interactive'), {
silent: false,
}));
}
catch (error) {
throw new Error(`Could not run command ${cmd.args.join(' ')}, reason:\n${error.message | error}`);
}
return [stdout.trim(), stderr.trim()];
}
exports.runCommand = runCommand;
async function easBuild(cmd) {
let stdout = '';
try {
const args = cmd.args.concat('--json', '--non-interactive', '--no-wait');
({ stdout } = await (0, exec_1.getExecOutput)(await (0, io_1.which)('eas', true), args, {
silent: false,
}));
}
catch (error) {
throw new Error(`Could not run command eas build, reason:\n${error.message | error}`);
}
return JSON.parse(stdout);
}
exports.easBuild = easBuild;
/**
* Try to resolve the project info, by running 'expo config --type prebuild'.
*/
Expand Down Expand Up @@ -67004,6 +67061,17 @@ function projectDeepLink(project, channel) {
return url.toString();
}
exports.projectDeepLink = projectDeepLink;
function getBuildLogsUrl(build) {
// TODO: reuse this function from the original source
// see: https://github.com/expo/eas-cli/blob/896f7f038582347c57dc700be9ea7d092b5a3a21/packages/eas-cli/src/build/utils/url.ts#L13-L21
const { project } = build;
const path = project
? `/accounts/${project.ownerAccount.name}/projects/${project.slug}/builds/${build.id}`
: `/builds/${build.id}`;
const url = new url_1.URL(path, 'https://expo.dev');
return url.toString();
}
exports.getBuildLogsUrl = getBuildLogsUrl;


/***/ }),
Expand Down
91 changes: 91 additions & 0 deletions command/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<div align="center">
<h1>expo-github-action/command</h1>
<p>Run <a href="https://github.com/expo/expo-cli">Expo CLI</a> or <a href="https://github.com/expo/eas-cli">EAS CLI</a> command by a comment on the PR</p>
<p>
<a href="https://github.com/expo/expo-github-action/releases">
<img src="https://img.shields.io/github/v/release/expo/expo-github-action" alt="releases" />
</a>
<a href="https://github.com/expo/expo-github-action/actions">
<img src="https://img.shields.io/github/workflow/status/expo/expo-github-action/test" alt="builds" />
</a>
<a href="https://github.com/expo/expo-github-action/blob/main/LICENSE.md">
<img src="https://img.shields.io/github/license/expo/expo-github-action" alt="license" />
</a>
</p>
<p align="center">
<a href="#configuration-options"><b>Usage</b></a>
&nbsp;&nbsp;&mdash;&nbsp;&nbsp;
<a href="#example-workflows"><b>Examples</b></a>
&nbsp;&nbsp;&mdash;&nbsp;&nbsp;
<a href="#things-to-know"><b>Caveats</b></a>
&nbsp;&nbsp;&mdash;&nbsp;&nbsp;
<a href="https://github.com/expo/expo-github-action/blob/main/CHANGELOG.md"><b>Changelog</b></a>
</p>
</div>

## Experimental

**_This sub action is experimental and might change without notice. Use it at your own risk_**

## Configuration options

This action is customizable through variables defined in the [`action.yml`](action.yml).
Here is a summary of all the input options you can use.

| variable | default | description |
| -------------- | --------------------------- | ------------------------------------------------------------------------------------------------ |
| **github-token** | `GITHUB_TOKEN` | A GitHub token to use when commenting on PR ([read more](#github-tokens)) |

## Example workflows

Before diving into the workflow examples, you should know the basics of GitHub Actions.
You can read more about this in the [GitHub Actions documentation][link-actions].

### Run the `eas build` command via an issue comment

This workflow listens to the `issue_comment` event and run the `eas build` command to start a build at Expo.

```yml
name: Run EAS Command
on:
issue_comment:
types: [created]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- name: 🏗 Setup repo
uses: actions/checkout@v2

- name: 🏗 Setup Node
uses: actions/setup-node@v2
with:
node-version: 16.x
cache: yarn

- name: 🏗 Setup Expo
uses: expo/expo-github-action@v7
with:
eas-version: latest
expo-version: latest
token: ${{ secrets.EXPO_TOKEN }}

- name: 📦 Install dependencies
run: yarn install

- name: Run command
uses: expo/expo-github-action/command@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
```
## Things to know
### 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.

[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
15 changes: 15 additions & 0 deletions command/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
name: Expo GitHub Action - Command
author: Expo
description: Run Expo CLI/EAS CLI command when a comment is added to a GitHub Pull Request.
branding:
icon: message-circle
color: gray-dark
runs:
using: node16
main: ../build/command/index.js
inputs:
github-token:
description: GitHub access token to comment on PRs
required: false
default: ${{ github.token }}
Loading

0 comments on commit 088fd4e

Please sign in to comment.