diff --git a/README.md b/README.md
index 5292aa04..ee3dfcf6 100644
--- a/README.md
+++ b/README.md
@@ -49,34 +49,69 @@ Instead, you can make use of this GitHub Action from the comfort of your own rep
1. [Create a Twitter app](docs/01-create-twitter-app.md) with your shared Twitter account and store the credentials as `TWITTER_API_KEY`, `TWITTER_API_SECRET_KEY`, `TWITTER_ACCESS_TOKEN` and `TWITTER_ACCESS_TOKEN_SECRET` in your repository’s secrets settings.
2. [Create a `.github/workflows/twitter-together.yml` file](docs/02-create-twitter-together-workflow.md) with the content below. Make sure to replace `'main'` if you changed your repository's default branch.
- ```yml
- on: [push, pull_request]
- name: Twitter, together!
- jobs:
- preview:
- name: Preview
- runs-on: ubuntu-latest
- if: github.event_name == 'pull_request'
- steps:
- - uses: twitter-together/action@v2
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- tweet:
- name: Tweet
- runs-on: ubuntu-latest
- if: github.event_name == 'push' && github.ref == 'refs/heads/main'
- steps:
- - name: checkout main
- uses: actions/checkout@v3
- - name: Tweet
- uses: twitter-together/action@v2
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- TWITTER_ACCESS_TOKEN: ${{ secrets.TWITTER_ACCESS_TOKEN }}
- TWITTER_ACCESS_TOKEN_SECRET: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }}
- TWITTER_API_KEY: ${{ secrets.TWITTER_API_KEY }}
- TWITTER_API_SECRET_KEY: ${{ secrets.TWITTER_API_SECRET_KEY }}
- ```
+```yml
+on: [push, pull_request]
+name: Twitter, together!
+jobs:
+ preview:
+ name: Preview
+ runs-on: ubuntu-latest
+ if: github.event_name == 'pull_request'
+ steps:
+ - name: checkout pull request
+ uses: actions/checkout@v3
+ with:
+ ref: ${{ github.event.pull_request.head.sha }}
+ - name: Validate Tweets
+ uses: twitter-together/action@v3
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ tweet:
+ name: Tweet
+ runs-on: ubuntu-latest
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
+ steps:
+ - name: checkout main
+ uses: actions/checkout@v3
+ - name: Tweet
+ uses: twitter-together/action@v3
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ TWITTER_ACCESS_TOKEN: ${{ secrets.TWITTER_ACCESS_TOKEN }}
+ TWITTER_ACCESS_TOKEN_SECRET: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }}
+ TWITTER_API_KEY: ${{ secrets.TWITTER_API_KEY }}
+ TWITTER_API_SECRET_KEY: ${{ secrets.TWITTER_API_SECRET_KEY }}
+```
+
+TODO: CONFIRM
+
+If you wish to have this action create preview comments in the PR thread, you can use the following config.
+
+Note that `pull_request_target` events have elevated permissions, so if you are using this config, you should configure your repository to only trigger actions that are trusted. You can do this in [various](https://securitylab.github.com/research/github-actions-preventing-pwn-requests/) ways, including preventing outside contributors from triggering actions automatically, and requiring only allowing actions from Verified Creators in your repository Settings -> Actions -> General.
+
+You can also securely enable PR comments only for local branch commits, using the `pull_request` events with an `ENABLE_COMMENTS: 1` env variable, but comments will not be created for PRs from forks.
+
+```yml
+# enable comments, but beware of security implications
+on: [push, pull_request_target]
+name: Twitter, together!
+jobs:
+ preview:
+ name: Preview
+ runs-on: ubuntu-latest
+ if: github.event_name == 'pull_request_target'
+ permissions:
+ pull-requests: write
+ steps:
+ - name: checkout pull request
+ uses: actions/checkout@v3
+ with:
+ ref: ${{ github.event.pull_request.head.sha }}
+ - name: Validate Tweets
+ uses: twitter-together/action@v3
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+```
3. After creating or updating `.github/workflows/twitter-together.yml` in your repository’s default branch, a pull request will be created with further instructions.
diff --git a/docs/02-create-twitter-together-workflow.md b/docs/02-create-twitter-together-workflow.md
index 5bf5662e..275bd0d5 100644
--- a/docs/02-create-twitter-together-workflow.md
+++ b/docs/02-create-twitter-together-workflow.md
@@ -21,7 +21,12 @@ jobs:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- - uses: twitter-together/action@v2
+ - name: checkout pull request
+ uses: actions/checkout@v3
+ with:
+ ref: ${{ github.event.pull_request.head.sha }}
+ - name: Validate Tweets
+ uses: twitter-together/action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
tweet:
diff --git a/lib/common/parse-tweet-file-content.js b/lib/common/parse-tweet-file-content.js
index 77b1f885..45eb0492 100644
--- a/lib/common/parse-tweet-file-content.js
+++ b/lib/common/parse-tweet-file-content.js
@@ -6,6 +6,7 @@ const { existsSync } = require("fs");
const { join } = require("path");
const { parseTweet } = require("twitter-text");
const { load } = require("js-yaml");
+const parseTweetId = require("./parse-tweet-id");
const OPTION_REGEX = /^\(\s?\)\s+/;
const FRONT_MATTER_REGEX = new RegExp(
@@ -87,6 +88,9 @@ function parseTweetFileContent(text, dir, isThread = false) {
}
function validateOptions(options, text, dir) {
+ if (options.retweet || options.reply)
+ parseTweetId(options.retweet || options.reply);
+
if (options.retweet && !text && options.poll)
throw new Error("Cannot attach a poll to a retweet");
diff --git a/lib/common/parse-tweet-id.js b/lib/common/parse-tweet-id.js
new file mode 100644
index 00000000..d33f4e41
--- /dev/null
+++ b/lib/common/parse-tweet-id.js
@@ -0,0 +1,16 @@
+module.exports = parseTweetId;
+
+const TWEET_REGEX = /^https:\/\/twitter\.com\/[^/]+\/status\/(\d+)$/;
+
+// TODO allow differently formatted URLs and tweet ids ?
+// https://github.com/twitter-together/action/issues/221
+
+// TODO: Should we check if the referenced tweet actually exists?
+
+function parseTweetId(tweetRef) {
+ const match = tweetRef.match(TWEET_REGEX);
+ if (!match) {
+ throw new Error(`Invalid tweet reference: ${tweetRef}`);
+ }
+ return match[1];
+}
diff --git a/lib/common/tweet.js b/lib/common/tweet.js
index 37103fd2..7e6ea284 100644
--- a/lib/common/tweet.js
+++ b/lib/common/tweet.js
@@ -3,7 +3,7 @@ module.exports = tweet;
const { TwitterApi } = require("twitter-api-v2");
const mime = require("mime-types");
-const TWEET_REGEX = /^https:\/\/twitter\.com\/[^/]+\/status\/(\d+)$/;
+const parseTweetId = require("./parse-tweet-id");
async function tweet({ twitterCredentials }, tweetData, tweetFile) {
const client = new TwitterApi(twitterCredentials);
@@ -16,9 +16,8 @@ async function tweet({ twitterCredentials }, tweetData, tweetFile) {
async function handleTweet(client, self, tweet, name) {
if (tweet.retweet && !tweet.text) {
- // TODO: Should this throw if an invalid tweet is passed and there is no match?
- const match = tweet.retweet.match(TWEET_REGEX);
- if (match) return createRetweet(client, self, match[1]);
+ const tweetId = parseTweetId(tweet.retweet);
+ if (tweetId) return createRetweet(client, self, tweetId);
}
const tweetData = {
@@ -33,19 +32,17 @@ async function handleTweet(client, self, tweet, name) {
}
if (tweet.reply) {
- // TODO: Should this throw if an invalid reply is passed and there is no match?
- const match = tweet.reply.match(TWEET_REGEX);
- if (match) {
+ const tweetId = parseTweetId(tweet.reply);
+ if (tweetId) {
tweetData.reply = {
- in_reply_to_tweet_id: match[1],
+ in_reply_to_tweet_id: tweetId,
};
}
}
if (tweet.retweet) {
- // TODO: Should this throw if an invalid tweet is passed and there is no match?
- const match = tweet.retweet.match(TWEET_REGEX);
- if (match) tweetData.quote_tweet_id = match[1];
+ const tweetId = parseTweetId(tweet.retweet);
+ if (tweetId) tweetData.quote_tweet_id = tweetId;
}
if (tweet.media?.length) {
diff --git a/lib/index.js b/lib/index.js
index 0350c77b..550c7bae 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -46,6 +46,10 @@ async function main() {
const ref = process.env.GITHUB_REF;
const sha = process.env.GITHUB_SHA;
const dir = process.env.GITHUB_WORKSPACE;
+ const trigger = process.env.GITHUB_EVENT_NAME;
+ // optionally allow enabling comments them on "pull_request" for local branches
+ const enableComments = !!process.env.ENABLE_COMMENTS;
+
const githubState = {
...state,
toolkit,
@@ -54,15 +58,15 @@ async function main() {
ref,
sha,
dir,
+ trigger,
+ enableComments,
};
- switch (process.env.GITHUB_EVENT_NAME) {
- case "push":
- await handlePush(githubState);
- break;
- case "pull_request":
- await handlePullRequest(githubState);
- break;
+ if (trigger === "push") {
+ await handlePush(githubState);
+ }
+ if (trigger === "pull_request" || trigger === "pull_request_target") {
+ await handlePullRequest(githubState);
}
}
diff --git a/lib/pull-request/create-check-run.js b/lib/pull-request/create-check-run.js
index 966c4182..53086927 100644
--- a/lib/pull-request/create-check-run.js
+++ b/lib/pull-request/create-check-run.js
@@ -1,49 +1,12 @@
module.exports = createCheckRun;
-const { autoLink } = require("twitter-text");
-
-const parseTweetFileContent = require("../common/parse-tweet-file-content");
-
async function createCheckRun(
- { octokit, payload, startedAt, toolkit, dir },
- newTweets
+ { payload, startedAt, octokit, toolkit },
+ summary
) {
- const parsedTweets = newTweets.map((rawTweet) => {
- try {
- return parseTweetFileContent(rawTweet, dir);
- } catch (error) {
- return {
- error: error.message,
- valid: false,
- text: rawTweet,
- };
- }
- });
-
- const allTweetsValid = parsedTweets.every((tweet) => tweet.valid);
-
- // Check runs cannot be created if the pull request was created by a fork,
- // so we just log out the result.
- // https://help.github.com/en/actions/automating-your-workflow-with-github-actions/authenticating-with-the-github_token#permissions-for-the-github_token
- if (payload.pull_request.head.repo.fork) {
- for (const tweet of parsedTweets) {
- if (tweet.valid) {
- toolkit.info(`### ✅ Valid\n\n${tweet.text}`);
- } else {
- toolkit.info(
- `### ❌ Invalid\n\n${tweet.text}\n\n${tweet.error || "Unknown error"}`
- );
- }
- }
- process.exit(allTweetsValid ? 0 : 1);
- }
-
const response = await octokit.request(
"POST /repos/:owner/:repo/check-runs",
{
- headers: {
- accept: "application/vnd.github.antiope-preview+json",
- },
owner: payload.repository.owner.login,
repo: payload.repository.name,
name: "preview",
@@ -51,27 +14,12 @@ async function createCheckRun(
started_at: startedAt,
completed_at: new Date().toISOString(),
status: "completed",
- conclusion: allTweetsValid ? "success" : "failure",
+ conclusion: summary.valid ? "success" : "failure",
output: {
- title: `${parsedTweets.length} tweet(s)`,
- summary: parsedTweets.map(tweetToCheckRunSummary).join("\n\n---\n\n"),
+ title: `${summary.count} tweet(s)`,
+ summary: summary.body,
},
}
);
-
toolkit.info(`check run created: ${response.data.html_url}`);
}
-
-function tweetToCheckRunSummary(tweet) {
- let text = autoLink(tweet.text)
- .replace(/(^|\n)/g, "$1> ")
- .replace(/(^|\n)> (\n|$)/g, "$1>$2");
-
- if (!tweet.valid)
- return `### ❌ Invalid\n\n${text}\n\n${tweet.error || "Unknown error"}`;
-
- if (tweet.poll)
- text +=
- "\n\nThe tweet includes a poll:\n\n> 🔘 " + tweet.poll.join("\n> 🔘 ");
- return `### ✅ Valid\n\n${text}`;
-}
diff --git a/lib/pull-request/create-comment.js b/lib/pull-request/create-comment.js
new file mode 100644
index 00000000..2949b9c6
--- /dev/null
+++ b/lib/pull-request/create-comment.js
@@ -0,0 +1,50 @@
+module.exports = createComment;
+
+const BOT_LOGIN = "github-actions[bot]";
+
+const DIVIDER = "\n\n---\n\n*";
+const PREVIEW = `${DIVIDER}Preview using `;
+const UPDATED = `${DIVIDER}**Updated** preview using `;
+const SIGNATURE =
+ " generated by [Twitter, together!](https://github.com/twitter-together/action)*";
+
+async function createComment({ octokit, payload }, summary) {
+ const comment = `${summary.title}${summary.body}`;
+ // check for existing comments.
+ const comments = await octokit.request(
+ "GET /repos/{owner}/{repo}/issues/{issue_number}/comments",
+ {
+ owner: payload.repository.owner.login,
+ repo: payload.repository.name,
+ issue_number: payload.pull_request.number,
+ }
+ );
+
+ const match = comments.data.find(
+ ({ user, body }) => user.login === BOT_LOGIN && body.endsWith(SIGNATURE)
+ );
+
+ if (match) {
+ // update the existing comment
+ await octokit.request(
+ "PATCH /repos/{owner}/{repo}/issues/comments/{comment_id}",
+ {
+ owner: payload.repository.owner.login,
+ repo: payload.repository.name,
+ comment_id: match.id,
+ body: `${comment}${UPDATED}${payload.pull_request.head.sha}${SIGNATURE}`,
+ }
+ );
+ } else {
+ // post a new comment
+ await octokit.request(
+ "POST /repos/{owner}/{repo}/issues/{issue_number}/comments",
+ {
+ owner: payload.repository.owner.login,
+ repo: payload.repository.name,
+ issue_number: payload.pull_request.number,
+ body: `${comment}${PREVIEW}${payload.pull_request.head.sha}${SIGNATURE}`,
+ }
+ );
+ }
+}
diff --git a/lib/pull-request/generate-summary.js b/lib/pull-request/generate-summary.js
new file mode 100644
index 00000000..f50bcc8e
--- /dev/null
+++ b/lib/pull-request/generate-summary.js
@@ -0,0 +1,81 @@
+module.exports = generateSummary;
+
+const { autoLink } = require("twitter-text");
+
+const parseTweetFileContent = require("../common/parse-tweet-file-content");
+const getNewTweets = require("./get-new-tweets");
+
+async function generateSummary(state, plainText = false) {
+ const { payload, dir } = state;
+
+ const newTweets = await getNewTweets(state);
+
+ const parsedTweets = newTweets.map((tweet) => {
+ try {
+ return parseTweetFileContent(tweet, dir);
+ } catch (error) {
+ return {
+ error: error.message,
+ valid: false,
+ text: tweet,
+ };
+ }
+ });
+
+ return {
+ count: parsedTweets.length,
+ valid: parsedTweets.every((tweet) => tweet.valid),
+ title: `## Found ${parsedTweets.length} new \`.tweet\` file(s)\n\n`,
+ body: parsedTweets
+ .map((tweet) => summarizeTweet({ tweet, payload, dir, plainText }))
+ .join("\n\n---\n\n"),
+ };
+}
+
+function summarizeTweet(state, threading = false) {
+ const { tweet, payload, dir, plainText } = state;
+
+ if (!tweet.valid)
+ return `### ❌ Invalid Tweet\n\n\`\`\`tweet\n${tweet.text}\n\`\`\`\n\n**${
+ tweet.error || "Unknown error"
+ }**`;
+
+ let text = !tweet.text
+ ? ""
+ : autoLink(tweet.text)
+ .replace(/(^|\n)/g, "$1> ")
+ .replace(/(^|\n)> (\n|$)/g, "$1>$2");
+
+ if (tweet.poll) text = `- ${tweet.poll.join("\n- ")}\n\n${text}`;
+
+ if (tweet.reply) text = `Replying to ${tweet.reply}\n\n${text}`;
+
+ if (tweet.retweet) text = `Retweeting ${tweet.retweet}\n\n${text}`.trim();
+
+ if (tweet.media.length) {
+ const media = tweet.media
+ .map(({ file, alt }) => {
+ const fileName = file.replace(dir, "");
+ if (plainText) {
+ return `- ${fileName}${alt && ` [${alt}]`}`;
+ } else {
+ const { repo, sha } = payload.pull_request.head;
+ return `${alt || ""}\n`;
+ }
+ })
+ .join(plainText ? "\n" : "\n\n");
+ text = `${media}\n\n${text}`.trim();
+ }
+
+ if (tweet.thread || threading) {
+ const count = threading ? threading + 1 : 1;
+ let thread = `\n\n#### --- 🧵 ${count} ---\n\n${text}`;
+ if (tweet.thread)
+ thread += summarizeTweet({ ...state, tweet: tweet.thread }, count);
+ return threading ? thread : `### ✅ Valid Thread${thread}`;
+ }
+
+ return `### ✅ Valid Tweet\n\n${text}`;
+}
diff --git a/lib/pull-request/index.js b/lib/pull-request/index.js
index 14eb5f14..f10070f1 100644
--- a/lib/pull-request/index.js
+++ b/lib/pull-request/index.js
@@ -1,14 +1,21 @@
module.exports = handlePullRequest;
-const getNewTweets = require("./get-new-tweets");
const createCheckRun = require("./create-check-run");
+const createComment = require("./create-comment");
+const generateSummary = require("./generate-summary");
+
+const CHECK = "check";
+const LOG = "log";
+const COMMENT = "comment";
async function handlePullRequest(state) {
- const { octokit, toolkit, payload } = state;
+ const { octokit, toolkit, payload, trigger, enableComments } = state;
- // ignore builds from branches other than the repository’s defaul branch
+ // ignore builds from branches other than the repository’s default branch
const base = payload.pull_request.base.ref;
const defaultBranch = payload.repository.default_branch;
+ const fork = !!payload.pull_request.head.repo.fork;
+
if (defaultBranch !== base) {
return toolkit.info(
`Pull request base "${base}" is not the repository’s default branch`
@@ -22,6 +29,29 @@ async function handlePullRequest(state) {
process.exit();
});
- const newTweets = await getNewTweets(state);
- await createCheckRun(state, newTweets);
+ // default report type, for `pull_request` non-fork PRs
+ let reportType = CHECK;
+ // can only log the output for fork PRs without check run permissions
+ if (fork && trigger === "pull_request") {
+ reportType = LOG;
+ }
+ // assume use of comments for `pull_request_target` PRs
+ // optional: use comments instead of check runs for `pull_request`
+ if (trigger === "pull_request_target" || enableComments) {
+ reportType = COMMENT;
+ }
+
+ const summary = await generateSummary(state, reportType === LOG);
+
+ if (reportType === LOG) {
+ toolkit.info(summary.body);
+ process.exit(summary.valid ? 0 : 1);
+ }
+ if (reportType === COMMENT) {
+ await createComment(state, summary);
+ process.exit(summary.valid ? 0 : 1);
+ }
+ if (reportType === CHECK) {
+ await createCheckRun(state, summary);
+ }
}
diff --git a/test/pull-request-has-tweet-issue-comment-updated/event.json b/test/pull-request-has-tweet-issue-comment-updated/event.json
new file mode 100644
index 00000000..ad692d8e
--- /dev/null
+++ b/test/pull-request-has-tweet-issue-comment-updated/event.json
@@ -0,0 +1,22 @@
+{
+ "action": "opened",
+ "pull_request": {
+ "number": 123,
+ "base": {
+ "ref": "main"
+ },
+ "head": {
+ "sha": "0000000000000000000000000000000000000002",
+ "repo": {
+ "fork": false
+ }
+ }
+ },
+ "repository": {
+ "default_branch": "main",
+ "name": "action",
+ "owner": {
+ "login": "twitter-together"
+ }
+ }
+}
diff --git a/test/pull-request-has-tweet-issue-comment-updated/test.js b/test/pull-request-has-tweet-issue-comment-updated/test.js
new file mode 100644
index 00000000..4767872e
--- /dev/null
+++ b/test/pull-request-has-tweet-issue-comment-updated/test.js
@@ -0,0 +1,116 @@
+/**
+ * This test checks the happy path of pull request adding a new *.tweet file
+ */
+
+const nock = require("nock");
+const tap = require("tap");
+
+// SETUP
+process.env.GITHUB_EVENT_NAME = "pull_request";
+process.env.GITHUB_TOKEN = "secret123";
+process.env.GITHUB_EVENT_PATH = require.resolve("./event.json");
+
+// set other env variables so action-toolkit is happy
+process.env.GITHUB_REF = "";
+process.env.GITHUB_WORKSPACE = "";
+process.env.GITHUB_WORKFLOW = "";
+process.env.GITHUB_ACTION = "twitter-together";
+process.env.GITHUB_ACTOR = "";
+process.env.GITHUB_REPOSITORY = "";
+process.env.GITHUB_SHA = "";
+process.env.ENABLE_COMMENTS = "1";
+
+// MOCK
+nock("https://api.github.com", {
+ reqheaders: {
+ authorization: "token secret123",
+ },
+})
+ // get changed files
+ .get("/repos/twitter-together/action/pulls/123/files")
+ .reply(200, [
+ {
+ status: "added",
+ filename: "tweets/hello-world.tweet",
+ },
+ ]);
+
+// get pull request diff
+nock("https://api.github.com", {
+ reqheaders: {
+ accept: "application/vnd.github.diff",
+ authorization: "token secret123",
+ },
+})
+ .get("/repos/twitter-together/action/pulls/123")
+ .reply(
+ 200,
+ `diff --git a/tweets/hello-world.tweet b/tweets/hello-world.tweet
+new file mode 100644
+index 0000000..0123456
+--- /dev/null
++++ b/tweets/hello-world.tweet
+@@ -0,0 +1 @@
++Hello, world!`
+ );
+
+// check for comments
+nock("https://api.github.com", {
+ reqheaders: {
+ authorization: "token secret123",
+ },
+})
+ // has a comment
+ .get("/repos/twitter-together/action/issues/123/comments")
+ .reply(200, [
+ {
+ id: 454,
+ user: { login: "some-other-account" },
+ body: "blah",
+ },
+ {
+ id: 455,
+ user: { login: "github-actions[bot]" },
+ body: "A comment generated by some other action",
+ },
+ {
+ id: 456,
+ user: { login: "github-actions[bot]" },
+ body: `## Found 1 new \`.tweet\` file(s)
+
+### ✅ Valid Tweet
+
+> Hello, world!
+
+---
+
+*Preview using 0000000000000000000000000000000000000002 generated by [Twitter, together!](https://github.com/twitter-together/action)*`,
+ },
+ ]);
+
+// update comment
+nock("https://api.github.com")
+ // get changed files
+ .patch("/repos/twitter-together/action/issues/comments/456", ({ body }) => {
+ tap.same(
+ body,
+ `## Found 1 new \`.tweet\` file(s)
+
+### ✅ Valid Tweet
+
+> Hello, world!
+
+---
+
+***Updated** preview using 0000000000000000000000000000000000000002 generated by [Twitter, together!](https://github.com/twitter-together/action)*`
+ );
+ return true;
+ })
+ .reply(201);
+
+process.on("exit", (code) => {
+ tap.equal(code, 0);
+ tap.same(nock.pendingMocks(), []);
+});
+
+require("../../lib");
diff --git a/test/pull-request-has-tweet-issue-comment/event.json b/test/pull-request-has-tweet-issue-comment/event.json
new file mode 100644
index 00000000..ad692d8e
--- /dev/null
+++ b/test/pull-request-has-tweet-issue-comment/event.json
@@ -0,0 +1,22 @@
+{
+ "action": "opened",
+ "pull_request": {
+ "number": 123,
+ "base": {
+ "ref": "main"
+ },
+ "head": {
+ "sha": "0000000000000000000000000000000000000002",
+ "repo": {
+ "fork": false
+ }
+ }
+ },
+ "repository": {
+ "default_branch": "main",
+ "name": "action",
+ "owner": {
+ "login": "twitter-together"
+ }
+ }
+}
diff --git a/test/pull-request-has-tweet-issue-comment/test.js b/test/pull-request-has-tweet-issue-comment/test.js
new file mode 100644
index 00000000..f4592abe
--- /dev/null
+++ b/test/pull-request-has-tweet-issue-comment/test.js
@@ -0,0 +1,92 @@
+/**
+ * This test checks the happy path of pull request adding a new *.tweet file
+ */
+
+const nock = require("nock");
+const tap = require("tap");
+
+// SETUP
+process.env.GITHUB_EVENT_NAME = "pull_request";
+process.env.GITHUB_TOKEN = "secret123";
+process.env.GITHUB_EVENT_PATH = require.resolve("./event.json");
+
+// set other env variables so action-toolkit is happy
+process.env.GITHUB_REF = "";
+process.env.GITHUB_WORKSPACE = "";
+process.env.GITHUB_WORKFLOW = "";
+process.env.GITHUB_ACTION = "twitter-together";
+process.env.GITHUB_ACTOR = "";
+process.env.GITHUB_REPOSITORY = "";
+process.env.GITHUB_SHA = "";
+process.env.ENABLE_COMMENTS = "1";
+
+// MOCK
+nock("https://api.github.com", {
+ reqheaders: {
+ authorization: "token secret123",
+ },
+})
+ // get changed files
+ .get("/repos/twitter-together/action/pulls/123/files")
+ .reply(200, [
+ {
+ status: "added",
+ filename: "tweets/hello-world.tweet",
+ },
+ ]);
+
+// get pull request diff
+nock("https://api.github.com", {
+ reqheaders: {
+ accept: "application/vnd.github.diff",
+ authorization: "token secret123",
+ },
+})
+ .get("/repos/twitter-together/action/pulls/123")
+ .reply(
+ 200,
+ `diff --git a/tweets/hello-world.tweet b/tweets/hello-world.tweet
+new file mode 100644
+index 0000000..0123456
+--- /dev/null
++++ b/tweets/hello-world.tweet
+@@ -0,0 +1 @@
++Hello, world!`
+ );
+
+// check for comments
+nock("https://api.github.com", {
+ reqheaders: {
+ authorization: "token secret123",
+ },
+})
+ // no comments
+ .get("/repos/twitter-together/action/issues/123/comments")
+ .reply(200, []);
+
+// post comment
+nock("https://api.github.com")
+ // get changed files
+ .post("/repos/twitter-together/action/issues/123/comments", ({ body }) => {
+ tap.same(
+ body,
+ `## Found 1 new \`.tweet\` file(s)
+
+### ✅ Valid Tweet
+
+> Hello, world!
+
+---
+
+*Preview using 0000000000000000000000000000000000000002 generated by [Twitter, together!](https://github.com/twitter-together/action)*`
+ );
+ return true;
+ })
+ .reply(201);
+
+process.on("exit", (code) => {
+ tap.equal(code, 0);
+ tap.same(nock.pendingMocks(), []);
+});
+
+require("../../lib");
diff --git a/test/pull-request-has-tweet-no-newline/test.js b/test/pull-request-has-tweet-no-newline/test.js
index 864069ce..8c5c37da 100644
--- a/test/pull-request-has-tweet-no-newline/test.js
+++ b/test/pull-request-has-tweet-no-newline/test.js
@@ -65,7 +65,7 @@ nock("https://api.github.com")
tap.equal(body.conclusion, "success");
tap.same(body.output, {
title: "1 tweet(s)",
- summary: "### ✅ Valid\n\n> Hello, world!",
+ summary: "### ✅ Valid Tweet\n\n> Hello, world!",
});
return true;
diff --git a/test/pull-request-has-tweet-with-front-matter-media/event.json b/test/pull-request-has-tweet-with-front-matter-media/event.json
new file mode 100644
index 00000000..6eb7c183
--- /dev/null
+++ b/test/pull-request-has-tweet-with-front-matter-media/event.json
@@ -0,0 +1,27 @@
+{
+ "action": "opened",
+ "after": "0000000000000000000000000000000000000003",
+ "pull_request": {
+ "number": 123,
+ "base": {
+ "ref": "main"
+ },
+ "head": {
+ "sha": "0000000000000000000000000000000000000003",
+ "repo": {
+ "name": "action",
+ "fork": false,
+ "owner": {
+ "login": "twitter-together"
+ }
+ }
+ }
+ },
+ "repository": {
+ "default_branch": "main",
+ "name": "action",
+ "owner": {
+ "login": "twitter-together"
+ }
+ }
+}
diff --git a/test/pull-request-has-tweet-with-front-matter-media/media/cat.jpg b/test/pull-request-has-tweet-with-front-matter-media/media/cat.jpg
new file mode 100644
index 00000000..a10d5059
Binary files /dev/null and b/test/pull-request-has-tweet-with-front-matter-media/media/cat.jpg differ
diff --git a/test/pull-request-has-tweet-with-front-matter-media/media/nested/media/pets/dog.jpg b/test/pull-request-has-tweet-with-front-matter-media/media/nested/media/pets/dog.jpg
new file mode 100644
index 00000000..7a25bfbe
Binary files /dev/null and b/test/pull-request-has-tweet-with-front-matter-media/media/nested/media/pets/dog.jpg differ
diff --git a/test/pull-request-has-tweet-with-front-matter-media/test.js b/test/pull-request-has-tweet-with-front-matter-media/test.js
new file mode 100644
index 00000000..d77cbffb
--- /dev/null
+++ b/test/pull-request-has-tweet-with-front-matter-media/test.js
@@ -0,0 +1,101 @@
+/**
+ * This test checks the happy path of pull request adding a new *.tweet file
+ */
+
+const nock = require("nock");
+const tap = require("tap");
+const path = require("path");
+
+// SETUP
+process.env.GITHUB_EVENT_NAME = "pull_request";
+process.env.GITHUB_TOKEN = "secret123";
+process.env.GITHUB_EVENT_PATH = require.resolve("./event.json");
+
+// set other env variables so action-toolkit is happy
+process.env.GITHUB_REF = "";
+process.env.GITHUB_WORKSPACE = path.dirname(process.env.GITHUB_EVENT_PATH);
+process.env.GITHUB_WORKFLOW = "";
+process.env.GITHUB_ACTION = "twitter-together";
+process.env.GITHUB_ACTOR = "";
+process.env.GITHUB_REPOSITORY = "";
+process.env.GITHUB_SHA = "";
+
+// MOCK
+nock("https://api.github.com", {
+ reqheaders: {
+ authorization: "token secret123",
+ },
+})
+ // get changed files
+ .get("/repos/twitter-together/action/pulls/123/files")
+ .reply(200, [
+ {
+ status: "added",
+ filename: "tweets/hello-world.tweet",
+ },
+ ]);
+
+// get pull request diff
+nock("https://api.github.com", {
+ reqheaders: {
+ accept: "application/vnd.github.diff",
+ authorization: "token secret123",
+ },
+})
+ .get("/repos/twitter-together/action/pulls/123")
+ .reply(
+ 200,
+ `diff --git a/media/cat.jpg b/media/cat.jpg
+new file mode 100644
+index 0000000..a10d505
+Binary files /dev/null and b/media/cat.jpg differ
+diff --git a/media/dog.jpg b/media/dog.jpg
+new file mode 100644
+index 0000000..7a25bfb
+Binary files /dev/null and b/media/dog.jpg differ
+diff --git a/tweets/media.tweet b/tweets/media.tweet
+new file mode 100644
+index 0000000..1715c04
+--- /dev/null
++++ b/tweets/media.tweet
+@@ -0,0 +1,9 @@
++---
++media:
++ - file: cat.jpg
++ - file: nested/media/pets/dog.jpg
++ alt: A dog
++---
++
++Here are some cute animals!`
+ );
+
+// create check run
+nock("https://api.github.com")
+ // get changed files
+ .post("/repos/twitter-together/action/check-runs", (body) => {
+ tap.equal(body.name, "preview");
+ tap.equal(body.head_sha, "0000000000000000000000000000000000000003");
+ tap.equal(body.status, "completed");
+ tap.equal(body.conclusion, "success");
+ tap.same(body.output, {
+ title: "1 tweet(s)",
+ summary: `### ✅ Valid Tweet
+
+
+
+A dog
+
+
+> Here are some cute animals!`,
+ });
+
+ return true;
+ })
+ .reply(201);
+
+process.on("exit", (code) => {
+ tap.equal(code, 0);
+ tap.same(nock.pendingMocks(), []);
+});
+
+require("../../lib");
diff --git a/test/pull-request-has-tweet-with-front-matter-poll-5-options/event.json b/test/pull-request-has-tweet-with-front-matter-poll-5-options/event.json
new file mode 100644
index 00000000..ad692d8e
--- /dev/null
+++ b/test/pull-request-has-tweet-with-front-matter-poll-5-options/event.json
@@ -0,0 +1,22 @@
+{
+ "action": "opened",
+ "pull_request": {
+ "number": 123,
+ "base": {
+ "ref": "main"
+ },
+ "head": {
+ "sha": "0000000000000000000000000000000000000002",
+ "repo": {
+ "fork": false
+ }
+ }
+ },
+ "repository": {
+ "default_branch": "main",
+ "name": "action",
+ "owner": {
+ "login": "twitter-together"
+ }
+ }
+}
diff --git a/test/pull-request-has-tweet-with-front-matter-poll-5-options/test.js b/test/pull-request-has-tweet-with-front-matter-poll-5-options/test.js
new file mode 100644
index 00000000..d65c9a4b
--- /dev/null
+++ b/test/pull-request-has-tweet-with-front-matter-poll-5-options/test.js
@@ -0,0 +1,102 @@
+/**
+ * This test checks the happy path of pull request adding a new *.tweet file
+ */
+
+const nock = require("nock");
+const tap = require("tap");
+
+// SETUP
+process.env.GITHUB_EVENT_NAME = "pull_request";
+process.env.GITHUB_TOKEN = "secret123";
+process.env.GITHUB_EVENT_PATH = require.resolve("./event.json");
+
+// set other env variables so action-toolkit is happy
+process.env.GITHUB_REF = "";
+process.env.GITHUB_WORKSPACE = "";
+process.env.GITHUB_WORKFLOW = "";
+process.env.GITHUB_ACTION = "twitter-together";
+process.env.GITHUB_ACTOR = "";
+process.env.GITHUB_REPOSITORY = "";
+process.env.GITHUB_SHA = "";
+
+// MOCK
+nock("https://api.github.com", {
+ reqheaders: {
+ authorization: "token secret123",
+ },
+})
+ // get changed files
+ .get("/repos/twitter-together/action/pulls/123/files")
+ .reply(200, [
+ {
+ status: "added",
+ filename: "tweets/hello-world.tweet",
+ },
+ ]);
+
+// get pull request diff
+nock("https://api.github.com", {
+ reqheaders: {
+ accept: "application/vnd.github.diff",
+ authorization: "token secret123",
+ },
+})
+ .get("/repos/twitter-together/action/pulls/123")
+ .reply(
+ 200,
+ `diff --git a/tweets/poll.tweet b/tweets/poll.tweet
+new file mode 100644
+index 0000000..6053708
+--- /dev/null
++++ b/tweets/poll.tweet
+@@ -0,0 +1,10 @@
++---
++poll:
++ - Red
++ - Blue
++ - Green
++ - Orange
++ - Purple
++---
++
++What is your favorite color?`
+ );
+
+// create check run
+nock("https://api.github.com")
+ // get changed files
+ .post("/repos/twitter-together/action/check-runs", (body) => {
+ tap.equal(body.name, "preview");
+ tap.equal(body.head_sha, "0000000000000000000000000000000000000002");
+ tap.equal(body.status, "completed");
+ tap.equal(body.conclusion, "failure");
+ tap.same(body.output, {
+ title: "1 tweet(s)",
+ summary: `### ❌ Invalid Tweet
+
+\`\`\`tweet
+---
+poll:
+ - Red
+ - Blue
+ - Green
+ - Orange
+ - Purple
+---
+
+What is your favorite color?
+\`\`\`
+
+**Polls cannot have more than four options, found 5 options**`,
+ });
+
+ return true;
+ })
+ .reply(201);
+
+process.on("exit", (code) => {
+ tap.equal(code, 0);
+ tap.same(nock.pendingMocks(), []);
+});
+
+require("../../lib");
diff --git a/test/pull-request-has-tweet-with-front-matter-poll/event.json b/test/pull-request-has-tweet-with-front-matter-poll/event.json
new file mode 100644
index 00000000..ad692d8e
--- /dev/null
+++ b/test/pull-request-has-tweet-with-front-matter-poll/event.json
@@ -0,0 +1,22 @@
+{
+ "action": "opened",
+ "pull_request": {
+ "number": 123,
+ "base": {
+ "ref": "main"
+ },
+ "head": {
+ "sha": "0000000000000000000000000000000000000002",
+ "repo": {
+ "fork": false
+ }
+ }
+ },
+ "repository": {
+ "default_branch": "main",
+ "name": "action",
+ "owner": {
+ "login": "twitter-together"
+ }
+ }
+}
diff --git a/test/pull-request-has-tweet-with-front-matter-poll/test.js b/test/pull-request-has-tweet-with-front-matter-poll/test.js
new file mode 100644
index 00000000..8ab77e31
--- /dev/null
+++ b/test/pull-request-has-tweet-with-front-matter-poll/test.js
@@ -0,0 +1,91 @@
+/**
+ * This test checks the happy path of pull request adding a new *.tweet file
+ */
+
+const nock = require("nock");
+const tap = require("tap");
+
+// SETUP
+process.env.GITHUB_EVENT_NAME = "pull_request";
+process.env.GITHUB_TOKEN = "secret123";
+process.env.GITHUB_EVENT_PATH = require.resolve("./event.json");
+
+// set other env variables so action-toolkit is happy
+process.env.GITHUB_REF = "";
+process.env.GITHUB_WORKSPACE = "";
+process.env.GITHUB_WORKFLOW = "";
+process.env.GITHUB_ACTION = "twitter-together";
+process.env.GITHUB_ACTOR = "";
+process.env.GITHUB_REPOSITORY = "";
+process.env.GITHUB_SHA = "";
+
+// MOCK
+nock("https://api.github.com", {
+ reqheaders: {
+ authorization: "token secret123",
+ },
+})
+ // get changed files
+ .get("/repos/twitter-together/action/pulls/123/files")
+ .reply(200, [
+ {
+ status: "added",
+ filename: "tweets/hello-world.tweet",
+ },
+ ]);
+
+// get pull request diff
+nock("https://api.github.com", {
+ reqheaders: {
+ accept: "application/vnd.github.diff",
+ authorization: "token secret123",
+ },
+})
+ .get("/repos/twitter-together/action/pulls/123")
+ .reply(
+ 200,
+ `diff --git a/tweets/poll.tweet b/tweets/poll.tweet
+new file mode 100644
+index 0000000..6053708
+--- /dev/null
++++ b/tweets/poll.tweet
+@@ -0,0 +1,8 @@
++---
++poll:
++ - Red
++ - Blue
++ - Green
++---
++
++What is your favorite color?`
+ );
+
+// create check run
+nock("https://api.github.com")
+ // get changed files
+ .post("/repos/twitter-together/action/check-runs", (body) => {
+ tap.equal(body.name, "preview");
+ tap.equal(body.head_sha, "0000000000000000000000000000000000000002");
+ tap.equal(body.status, "completed");
+ tap.equal(body.conclusion, "success");
+ tap.same(body.output, {
+ title: "1 tweet(s)",
+ summary: `### ✅ Valid Tweet
+
+- Red
+- Blue
+- Green
+
+> What is your favorite color?`,
+ });
+
+ return true;
+ })
+ .reply(201);
+
+process.on("exit", (code) => {
+ tap.equal(code, 0);
+ tap.same(nock.pendingMocks(), []);
+});
+
+require("../../lib");
diff --git a/test/pull-request-has-tweet-with-front-matter-quote/event.json b/test/pull-request-has-tweet-with-front-matter-quote/event.json
new file mode 100644
index 00000000..ad692d8e
--- /dev/null
+++ b/test/pull-request-has-tweet-with-front-matter-quote/event.json
@@ -0,0 +1,22 @@
+{
+ "action": "opened",
+ "pull_request": {
+ "number": 123,
+ "base": {
+ "ref": "main"
+ },
+ "head": {
+ "sha": "0000000000000000000000000000000000000002",
+ "repo": {
+ "fork": false
+ }
+ }
+ },
+ "repository": {
+ "default_branch": "main",
+ "name": "action",
+ "owner": {
+ "login": "twitter-together"
+ }
+ }
+}
diff --git a/test/pull-request-has-tweet-with-front-matter-quote/test.js b/test/pull-request-has-tweet-with-front-matter-quote/test.js
new file mode 100644
index 00000000..e6594ac3
--- /dev/null
+++ b/test/pull-request-has-tweet-with-front-matter-quote/test.js
@@ -0,0 +1,86 @@
+/**
+ * This test checks the happy path of pull request adding a new *.tweet file
+ */
+
+const nock = require("nock");
+const tap = require("tap");
+
+// SETUP
+process.env.GITHUB_EVENT_NAME = "pull_request";
+process.env.GITHUB_TOKEN = "secret123";
+process.env.GITHUB_EVENT_PATH = require.resolve("./event.json");
+
+// set other env variables so action-toolkit is happy
+process.env.GITHUB_REF = "";
+process.env.GITHUB_WORKSPACE = "";
+process.env.GITHUB_WORKFLOW = "";
+process.env.GITHUB_ACTION = "twitter-together";
+process.env.GITHUB_ACTOR = "";
+process.env.GITHUB_REPOSITORY = "";
+process.env.GITHUB_SHA = "";
+
+// MOCK
+nock("https://api.github.com", {
+ reqheaders: {
+ authorization: "token secret123",
+ },
+})
+ // get changed files
+ .get("/repos/twitter-together/action/pulls/123/files")
+ .reply(200, [
+ {
+ status: "added",
+ filename: "tweets/hello-world.tweet",
+ },
+ ]);
+
+// get pull request diff
+nock("https://api.github.com", {
+ reqheaders: {
+ accept: "application/vnd.github.diff",
+ authorization: "token secret123",
+ },
+})
+ .get("/repos/twitter-together/action/pulls/123")
+ .reply(
+ 200,
+ `diff --git a/tweets/retweet.tweet b/tweets/retweet.tweet
+new file mode 100644
+index 0000000..d462a1f
+--- /dev/null
++++ b/tweets/retweet.tweet
+@@ -0,0 +1,5 @@
++---
++retweet: https://twitter.com/m2rg/status/0000000000000000001
++---
++
++Smart thinking!`
+ );
+
+// create check run
+nock("https://api.github.com")
+ // get changed files
+ .post("/repos/twitter-together/action/check-runs", (body) => {
+ tap.equal(body.name, "preview");
+ tap.equal(body.head_sha, "0000000000000000000000000000000000000002");
+ tap.equal(body.status, "completed");
+ tap.equal(body.conclusion, "success");
+ tap.same(body.output, {
+ title: "1 tweet(s)",
+ summary: `### ✅ Valid Tweet
+
+Retweeting https://twitter.com/m2rg/status/0000000000000000001
+
+> Smart thinking!`,
+ });
+
+ return true;
+ })
+ .reply(201);
+
+process.on("exit", (code) => {
+ tap.equal(code, 0);
+ tap.same(nock.pendingMocks(), []);
+});
+
+require("../../lib");
diff --git a/test/pull-request-has-tweet-with-front-matter-reply-invalid/event.json b/test/pull-request-has-tweet-with-front-matter-reply-invalid/event.json
new file mode 100644
index 00000000..ad692d8e
--- /dev/null
+++ b/test/pull-request-has-tweet-with-front-matter-reply-invalid/event.json
@@ -0,0 +1,22 @@
+{
+ "action": "opened",
+ "pull_request": {
+ "number": 123,
+ "base": {
+ "ref": "main"
+ },
+ "head": {
+ "sha": "0000000000000000000000000000000000000002",
+ "repo": {
+ "fork": false
+ }
+ }
+ },
+ "repository": {
+ "default_branch": "main",
+ "name": "action",
+ "owner": {
+ "login": "twitter-together"
+ }
+ }
+}
diff --git a/test/pull-request-has-tweet-with-front-matter-reply-invalid/test.js b/test/pull-request-has-tweet-with-front-matter-reply-invalid/test.js
new file mode 100644
index 00000000..7c61e21c
--- /dev/null
+++ b/test/pull-request-has-tweet-with-front-matter-reply-invalid/test.js
@@ -0,0 +1,88 @@
+/**
+ * This test checks the happy path of pull request adding a new *.tweet file
+ */
+
+const nock = require("nock");
+const tap = require("tap");
+
+// SETUP
+process.env.GITHUB_EVENT_NAME = "pull_request";
+process.env.GITHUB_TOKEN = "secret123";
+process.env.GITHUB_EVENT_PATH = require.resolve("./event.json");
+
+// set other env variables so action-toolkit is happy
+process.env.GITHUB_REF = "";
+process.env.GITHUB_WORKSPACE = "";
+process.env.GITHUB_WORKFLOW = "";
+process.env.GITHUB_ACTION = "twitter-together";
+process.env.GITHUB_ACTOR = "";
+process.env.GITHUB_REPOSITORY = "";
+process.env.GITHUB_SHA = "";
+
+// MOCK
+nock("https://api.github.com", {
+ reqheaders: {
+ authorization: "token secret123",
+ },
+})
+ // get changed files
+ .get("/repos/twitter-together/action/pulls/123/files")
+ .reply(200, [
+ {
+ status: "added",
+ filename: "tweets/hello-world.tweet",
+ },
+ ]);
+
+// get pull request diff
+nock("https://api.github.com", {
+ reqheaders: {
+ accept: "application/vnd.github.diff",
+ authorization: "token secret123",
+ },
+})
+ .get("/repos/twitter-together/action/pulls/123")
+ .reply(
+ 200,
+ `diff --git a/tweets/reply.tweet b/tweets/reply.tweet
+new file mode 100644
+index 0000000..d462a1f
+--- /dev/null
++++ b/tweets/reply.tweet
+@@ -0,0 +1,3 @@
++---
++reply: spoons
++---`
+ );
+
+// create check run
+nock("https://api.github.com")
+ // get changed files
+ .post("/repos/twitter-together/action/check-runs", (body) => {
+ tap.equal(body.name, "preview");
+ tap.equal(body.head_sha, "0000000000000000000000000000000000000002");
+ tap.equal(body.status, "completed");
+ tap.equal(body.conclusion, "failure");
+ tap.same(body.output, {
+ title: "1 tweet(s)",
+ summary: `### ❌ Invalid Tweet
+
+\`\`\`tweet
+---
+reply: spoons
+---
+\`\`\`
+
+**Invalid tweet reference: spoons**`,
+ });
+
+ return true;
+ })
+ .reply(201);
+
+process.on("exit", (code) => {
+ tap.equal(code, 0);
+ tap.same(nock.pendingMocks(), []);
+});
+
+require("../../lib");
diff --git a/test/pull-request-has-tweet-with-front-matter-reply/event.json b/test/pull-request-has-tweet-with-front-matter-reply/event.json
new file mode 100644
index 00000000..ad692d8e
--- /dev/null
+++ b/test/pull-request-has-tweet-with-front-matter-reply/event.json
@@ -0,0 +1,22 @@
+{
+ "action": "opened",
+ "pull_request": {
+ "number": 123,
+ "base": {
+ "ref": "main"
+ },
+ "head": {
+ "sha": "0000000000000000000000000000000000000002",
+ "repo": {
+ "fork": false
+ }
+ }
+ },
+ "repository": {
+ "default_branch": "main",
+ "name": "action",
+ "owner": {
+ "login": "twitter-together"
+ }
+ }
+}
diff --git a/test/pull-request-has-tweet-with-front-matter-reply/test.js b/test/pull-request-has-tweet-with-front-matter-reply/test.js
new file mode 100644
index 00000000..f3c86c2e
--- /dev/null
+++ b/test/pull-request-has-tweet-with-front-matter-reply/test.js
@@ -0,0 +1,86 @@
+/**
+ * This test checks the happy path of pull request adding a new *.tweet file
+ */
+
+const nock = require("nock");
+const tap = require("tap");
+
+// SETUP
+process.env.GITHUB_EVENT_NAME = "pull_request";
+process.env.GITHUB_TOKEN = "secret123";
+process.env.GITHUB_EVENT_PATH = require.resolve("./event.json");
+
+// set other env variables so action-toolkit is happy
+process.env.GITHUB_REF = "";
+process.env.GITHUB_WORKSPACE = "";
+process.env.GITHUB_WORKFLOW = "";
+process.env.GITHUB_ACTION = "twitter-together";
+process.env.GITHUB_ACTOR = "";
+process.env.GITHUB_REPOSITORY = "";
+process.env.GITHUB_SHA = "";
+
+// MOCK
+nock("https://api.github.com", {
+ reqheaders: {
+ authorization: "token secret123",
+ },
+})
+ // get changed files
+ .get("/repos/twitter-together/action/pulls/123/files")
+ .reply(200, [
+ {
+ status: "added",
+ filename: "tweets/hello-world.tweet",
+ },
+ ]);
+
+// get pull request diff
+nock("https://api.github.com", {
+ reqheaders: {
+ accept: "application/vnd.github.diff",
+ authorization: "token secret123",
+ },
+})
+ .get("/repos/twitter-together/action/pulls/123")
+ .reply(
+ 200,
+ `diff --git a/tweets/retweet.tweet b/tweets/retweet.tweet
+new file mode 100644
+index 0000000..d462a1f
+--- /dev/null
++++ b/tweets/retweet.tweet
+@@ -0,0 +1,5 @@
++---
++reply: https://twitter.com/m2rg/status/0000000000000000001
++---
++
++Good idea :)`
+ );
+
+// create check run
+nock("https://api.github.com")
+ // get changed files
+ .post("/repos/twitter-together/action/check-runs", (body) => {
+ tap.equal(body.name, "preview");
+ tap.equal(body.head_sha, "0000000000000000000000000000000000000002");
+ tap.equal(body.status, "completed");
+ tap.equal(body.conclusion, "success");
+ tap.same(body.output, {
+ title: "1 tweet(s)",
+ summary: `### ✅ Valid Tweet
+
+Replying to https://twitter.com/m2rg/status/0000000000000000001
+
+> Good idea :)`,
+ });
+
+ return true;
+ })
+ .reply(201);
+
+process.on("exit", (code) => {
+ tap.equal(code, 0);
+ tap.same(nock.pendingMocks(), []);
+});
+
+require("../../lib");
diff --git a/test/pull-request-has-tweet-with-front-matter-retweet-invalid/event.json b/test/pull-request-has-tweet-with-front-matter-retweet-invalid/event.json
new file mode 100644
index 00000000..ad692d8e
--- /dev/null
+++ b/test/pull-request-has-tweet-with-front-matter-retweet-invalid/event.json
@@ -0,0 +1,22 @@
+{
+ "action": "opened",
+ "pull_request": {
+ "number": 123,
+ "base": {
+ "ref": "main"
+ },
+ "head": {
+ "sha": "0000000000000000000000000000000000000002",
+ "repo": {
+ "fork": false
+ }
+ }
+ },
+ "repository": {
+ "default_branch": "main",
+ "name": "action",
+ "owner": {
+ "login": "twitter-together"
+ }
+ }
+}
diff --git a/test/pull-request-has-tweet-with-front-matter-retweet-invalid/test.js b/test/pull-request-has-tweet-with-front-matter-retweet-invalid/test.js
new file mode 100644
index 00000000..8e07169e
--- /dev/null
+++ b/test/pull-request-has-tweet-with-front-matter-retweet-invalid/test.js
@@ -0,0 +1,88 @@
+/**
+ * This test checks the happy path of pull request adding a new *.tweet file
+ */
+
+const nock = require("nock");
+const tap = require("tap");
+
+// SETUP
+process.env.GITHUB_EVENT_NAME = "pull_request";
+process.env.GITHUB_TOKEN = "secret123";
+process.env.GITHUB_EVENT_PATH = require.resolve("./event.json");
+
+// set other env variables so action-toolkit is happy
+process.env.GITHUB_REF = "";
+process.env.GITHUB_WORKSPACE = "";
+process.env.GITHUB_WORKFLOW = "";
+process.env.GITHUB_ACTION = "twitter-together";
+process.env.GITHUB_ACTOR = "";
+process.env.GITHUB_REPOSITORY = "";
+process.env.GITHUB_SHA = "";
+
+// MOCK
+nock("https://api.github.com", {
+ reqheaders: {
+ authorization: "token secret123",
+ },
+})
+ // get changed files
+ .get("/repos/twitter-together/action/pulls/123/files")
+ .reply(200, [
+ {
+ status: "added",
+ filename: "tweets/hello-world.tweet",
+ },
+ ]);
+
+// get pull request diff
+nock("https://api.github.com", {
+ reqheaders: {
+ accept: "application/vnd.github.diff",
+ authorization: "token secret123",
+ },
+})
+ .get("/repos/twitter-together/action/pulls/123")
+ .reply(
+ 200,
+ `diff --git a/tweets/retweet.tweet b/tweets/retweet.tweet
+new file mode 100644
+index 0000000..d462a1f
+--- /dev/null
++++ b/tweets/retweet.tweet
+@@ -0,0 +1,3 @@
++---
++retweet: spoons
++---`
+ );
+
+// create check run
+nock("https://api.github.com")
+ // get changed files
+ .post("/repos/twitter-together/action/check-runs", (body) => {
+ tap.equal(body.name, "preview");
+ tap.equal(body.head_sha, "0000000000000000000000000000000000000002");
+ tap.equal(body.status, "completed");
+ tap.equal(body.conclusion, "failure");
+ tap.same(body.output, {
+ title: "1 tweet(s)",
+ summary: `### ❌ Invalid Tweet
+
+\`\`\`tweet
+---
+retweet: spoons
+---
+\`\`\`
+
+**Invalid tweet reference: spoons**`,
+ });
+
+ return true;
+ })
+ .reply(201);
+
+process.on("exit", (code) => {
+ tap.equal(code, 0);
+ tap.same(nock.pendingMocks(), []);
+});
+
+require("../../lib");
diff --git a/test/pull-request-has-tweet-with-front-matter-retweet/event.json b/test/pull-request-has-tweet-with-front-matter-retweet/event.json
new file mode 100644
index 00000000..ad692d8e
--- /dev/null
+++ b/test/pull-request-has-tweet-with-front-matter-retweet/event.json
@@ -0,0 +1,22 @@
+{
+ "action": "opened",
+ "pull_request": {
+ "number": 123,
+ "base": {
+ "ref": "main"
+ },
+ "head": {
+ "sha": "0000000000000000000000000000000000000002",
+ "repo": {
+ "fork": false
+ }
+ }
+ },
+ "repository": {
+ "default_branch": "main",
+ "name": "action",
+ "owner": {
+ "login": "twitter-together"
+ }
+ }
+}
diff --git a/test/pull-request-has-tweet-with-front-matter-retweet/test.js b/test/pull-request-has-tweet-with-front-matter-retweet/test.js
new file mode 100644
index 00000000..ea257f45
--- /dev/null
+++ b/test/pull-request-has-tweet-with-front-matter-retweet/test.js
@@ -0,0 +1,82 @@
+/**
+ * This test checks the happy path of pull request adding a new *.tweet file
+ */
+
+const nock = require("nock");
+const tap = require("tap");
+
+// SETUP
+process.env.GITHUB_EVENT_NAME = "pull_request";
+process.env.GITHUB_TOKEN = "secret123";
+process.env.GITHUB_EVENT_PATH = require.resolve("./event.json");
+
+// set other env variables so action-toolkit is happy
+process.env.GITHUB_REF = "";
+process.env.GITHUB_WORKSPACE = "";
+process.env.GITHUB_WORKFLOW = "";
+process.env.GITHUB_ACTION = "twitter-together";
+process.env.GITHUB_ACTOR = "";
+process.env.GITHUB_REPOSITORY = "";
+process.env.GITHUB_SHA = "";
+
+// MOCK
+nock("https://api.github.com", {
+ reqheaders: {
+ authorization: "token secret123",
+ },
+})
+ // get changed files
+ .get("/repos/twitter-together/action/pulls/123/files")
+ .reply(200, [
+ {
+ status: "added",
+ filename: "tweets/hello-world.tweet",
+ },
+ ]);
+
+// get pull request diff
+nock("https://api.github.com", {
+ reqheaders: {
+ accept: "application/vnd.github.diff",
+ authorization: "token secret123",
+ },
+})
+ .get("/repos/twitter-together/action/pulls/123")
+ .reply(
+ 200,
+ `diff --git a/tweets/retweet.tweet b/tweets/retweet.tweet
+new file mode 100644
+index 0000000..d462a1f
+--- /dev/null
++++ b/tweets/retweet.tweet
+@@ -0,0 +1,3 @@
++---
++retweet: https://twitter.com/m2rg/status/0000000000000000001
++---`
+ );
+
+// create check run
+nock("https://api.github.com")
+ // get changed files
+ .post("/repos/twitter-together/action/check-runs", (body) => {
+ tap.equal(body.name, "preview");
+ tap.equal(body.head_sha, "0000000000000000000000000000000000000002");
+ tap.equal(body.status, "completed");
+ tap.equal(body.conclusion, "success");
+ tap.same(body.output, {
+ title: "1 tweet(s)",
+ summary: `### ✅ Valid Tweet
+
+Retweeting https://twitter.com/m2rg/status/0000000000000000001`,
+ });
+
+ return true;
+ })
+ .reply(201);
+
+process.on("exit", (code) => {
+ tap.equal(code, 0);
+ tap.same(nock.pendingMocks(), []);
+});
+
+require("../../lib");
diff --git a/test/pull-request-has-tweet-with-poll-with-5-options/test.js b/test/pull-request-has-tweet-with-poll-with-5-options/test.js
index 1dd253dd..37d4024e 100644
--- a/test/pull-request-has-tweet-with-poll-with-5-options/test.js
+++ b/test/pull-request-has-tweet-with-poll-with-5-options/test.js
@@ -69,17 +69,19 @@ nock("https://api.github.com")
tap.equal(body.conclusion, "failure");
tap.same(body.output, {
title: "1 tweet(s)",
- summary: `### ❌ Invalid
+ summary: `### ❌ Invalid Tweet
-> Here is my poll
->
-> ( ) option 1
-> ( ) option 2
-> ( ) option 3
-> ( ) option 4
-> ( ) option 5
+\`\`\`tweet
+Here is my poll
-Polls cannot have more than four options, found 5 options`,
+( ) option 1
+( ) option 2
+( ) option 3
+( ) option 4
+( ) option 5
+\`\`\`
+
+**Polls cannot have more than four options, found 5 options**`,
});
return true;
diff --git a/test/pull-request-has-tweet-with-poll/test.js b/test/pull-request-has-tweet-with-poll/test.js
index dcaf4c99..668acb15 100644
--- a/test/pull-request-has-tweet-with-poll/test.js
+++ b/test/pull-request-has-tweet-with-poll/test.js
@@ -68,16 +68,14 @@ nock("https://api.github.com")
tap.equal(body.conclusion, "success");
tap.same(body.output, {
title: "1 tweet(s)",
- summary: `### ✅ Valid
+ summary: `### ✅ Valid Tweet
-> Here is my poll
+- option 1
+- option 2
+- option 3
+- option 4
-The tweet includes a poll:
-
-> 🔘 option 1
-> 🔘 option 2
-> 🔘 option 3
-> 🔘 option 4`,
+> Here is my poll`,
});
return true;
diff --git a/test/pull-request-has-tweet-with-thread/event.json b/test/pull-request-has-tweet-with-thread/event.json
new file mode 100644
index 00000000..ad692d8e
--- /dev/null
+++ b/test/pull-request-has-tweet-with-thread/event.json
@@ -0,0 +1,22 @@
+{
+ "action": "opened",
+ "pull_request": {
+ "number": 123,
+ "base": {
+ "ref": "main"
+ },
+ "head": {
+ "sha": "0000000000000000000000000000000000000002",
+ "repo": {
+ "fork": false
+ }
+ }
+ },
+ "repository": {
+ "default_branch": "main",
+ "name": "action",
+ "owner": {
+ "login": "twitter-together"
+ }
+ }
+}
diff --git a/test/pull-request-has-tweet-with-thread/test.js b/test/pull-request-has-tweet-with-thread/test.js
new file mode 100644
index 00000000..955467f7
--- /dev/null
+++ b/test/pull-request-has-tweet-with-thread/test.js
@@ -0,0 +1,111 @@
+/**
+ * This test checks the happy path of pull request adding a new *.tweet file
+ */
+
+const nock = require("nock");
+const tap = require("tap");
+
+// SETUP
+process.env.GITHUB_EVENT_NAME = "pull_request";
+process.env.GITHUB_TOKEN = "secret123";
+process.env.GITHUB_EVENT_PATH = require.resolve("./event.json");
+
+// set other env variables so action-toolkit is happy
+process.env.GITHUB_REF = "";
+process.env.GITHUB_WORKSPACE = "";
+process.env.GITHUB_WORKFLOW = "";
+process.env.GITHUB_ACTION = "twitter-together";
+process.env.GITHUB_ACTOR = "";
+process.env.GITHUB_REPOSITORY = "";
+process.env.GITHUB_SHA = "";
+
+// MOCK
+nock("https://api.github.com", {
+ reqheaders: {
+ authorization: "token secret123",
+ },
+})
+ // get changed files
+ .get("/repos/twitter-together/action/pulls/123/files")
+ .reply(200, [
+ {
+ status: "added",
+ filename: "tweets/hello-world.tweet",
+ },
+ ]);
+
+// get pull request diff
+nock("https://api.github.com", {
+ reqheaders: {
+ accept: "application/vnd.github.diff",
+ authorization: "token secret123",
+ },
+})
+ .get("/repos/twitter-together/action/pulls/123")
+ .reply(
+ 200,
+ `diff --git a/tweets/thread.tweet b/tweets/thread.tweet
+new file mode 100644
+index 0000000..ec04564
+--- /dev/null
++++ b/tweets/thread.tweet
+@@ -0,0 +1,15 @@
++🧵 Here is a thread...
++
++---
++---
++poll:
++ - Banana
++ - Mango
++---
++
++Which fruit is more delicious?
++
++---
++We hope you enjoyed this thread...
++---
++We certainly did.`
+ );
+
+// create check run
+nock("https://api.github.com")
+ // get changed files
+ .post("/repos/twitter-together/action/check-runs", (body) => {
+ tap.equal(body.name, "preview");
+ tap.equal(body.head_sha, "0000000000000000000000000000000000000002");
+ tap.equal(body.status, "completed");
+ tap.equal(body.conclusion, "success");
+ tap.same(body.output, {
+ title: "1 tweet(s)",
+ summary: `### ✅ Valid Thread
+
+#### --- 🧵 1 ---
+
+> 🧵 Here is a thread...
+
+#### --- 🧵 2 ---
+
+- Banana
+- Mango
+
+> Which fruit is more delicious?
+
+#### --- 🧵 3 ---
+
+> We hope you enjoyed this thread...
+
+#### --- 🧵 4 ---
+
+> We certainly did.`,
+ });
+
+ return true;
+ })
+ .reply(201);
+
+process.on("exit", (code) => {
+ tap.equal(code, 0);
+ tap.same(nock.pendingMocks(), []);
+});
+
+require("../../lib");
diff --git a/test/pull-request-has-tweet/test.js b/test/pull-request-has-tweet/test.js
index 36d3c7a3..9be1e4c1 100644
--- a/test/pull-request-has-tweet/test.js
+++ b/test/pull-request-has-tweet/test.js
@@ -63,7 +63,7 @@ nock("https://api.github.com")
tap.equal(body.conclusion, "success");
tap.same(body.output, {
title: "1 tweet(s)",
- summary: "### ✅ Valid\n\n> Hello, world!",
+ summary: "### ✅ Valid Tweet\n\n> Hello, world!",
});
return true;
diff --git a/test/pull-request-invalid-tweet/test.js b/test/pull-request-invalid-tweet/test.js
index c984bd10..2341b056 100644
--- a/test/pull-request-invalid-tweet/test.js
+++ b/test/pull-request-invalid-tweet/test.js
@@ -66,11 +66,13 @@ nock("https://api.github.com", {
tap.equal(body.conclusion, "failure");
tap.same(body.output, {
title: "1 tweet(s)",
- summary: `### ❌ Invalid
+ summary: `### ❌ Invalid Tweet
-> Cupcake ipsum dolor sit amet chupa chups candy halvah I love. Apple pie gummi bears chupa chups jujubes I love cake jelly. Jelly candy canes pudding jujubes caramels sweet roll I love. Sweet fruitcake oat cake I love brownie sesame snaps apple pie lollipop. Pie dragée I love apple pie cotton candy candy chocolate bar.
+\`\`\`tweet
+Cupcake ipsum dolor sit amet chupa chups candy halvah I love. Apple pie gummi bears chupa chups jujubes I love cake jelly. Jelly candy canes pudding jujubes caramels sweet roll I love. Sweet fruitcake oat cake I love brownie sesame snaps apple pie lollipop. Pie dragée I love apple pie cotton candy candy chocolate bar.
+\`\`\`
-Tweet exceeds maximum length of 280 characters by 39 characters`,
+**Tweet exceeds maximum length of 280 characters by 39 characters**`,
});
return true;
diff --git a/test/pull-request-target-has-tweet-issue-comment/event.json b/test/pull-request-target-has-tweet-issue-comment/event.json
new file mode 100644
index 00000000..ad692d8e
--- /dev/null
+++ b/test/pull-request-target-has-tweet-issue-comment/event.json
@@ -0,0 +1,22 @@
+{
+ "action": "opened",
+ "pull_request": {
+ "number": 123,
+ "base": {
+ "ref": "main"
+ },
+ "head": {
+ "sha": "0000000000000000000000000000000000000002",
+ "repo": {
+ "fork": false
+ }
+ }
+ },
+ "repository": {
+ "default_branch": "main",
+ "name": "action",
+ "owner": {
+ "login": "twitter-together"
+ }
+ }
+}
diff --git a/test/pull-request-target-has-tweet-issue-comment/test.js b/test/pull-request-target-has-tweet-issue-comment/test.js
new file mode 100644
index 00000000..6678b992
--- /dev/null
+++ b/test/pull-request-target-has-tweet-issue-comment/test.js
@@ -0,0 +1,91 @@
+/**
+ * This test checks the happy path of pull request adding a new *.tweet file
+ */
+
+const nock = require("nock");
+const tap = require("tap");
+
+// SETUP
+process.env.GITHUB_EVENT_NAME = "pull_request_target";
+process.env.GITHUB_TOKEN = "secret123";
+process.env.GITHUB_EVENT_PATH = require.resolve("./event.json");
+
+// set other env variables so action-toolkit is happy
+process.env.GITHUB_REF = "";
+process.env.GITHUB_WORKSPACE = "";
+process.env.GITHUB_WORKFLOW = "";
+process.env.GITHUB_ACTION = "twitter-together";
+process.env.GITHUB_ACTOR = "";
+process.env.GITHUB_REPOSITORY = "";
+process.env.GITHUB_SHA = "";
+
+// MOCK
+nock("https://api.github.com", {
+ reqheaders: {
+ authorization: "token secret123",
+ },
+})
+ // get changed files
+ .get("/repos/twitter-together/action/pulls/123/files")
+ .reply(200, [
+ {
+ status: "added",
+ filename: "tweets/hello-world.tweet",
+ },
+ ]);
+
+// get pull request diff
+nock("https://api.github.com", {
+ reqheaders: {
+ accept: "application/vnd.github.diff",
+ authorization: "token secret123",
+ },
+})
+ .get("/repos/twitter-together/action/pulls/123")
+ .reply(
+ 200,
+ `diff --git a/tweets/hello-world.tweet b/tweets/hello-world.tweet
+new file mode 100644
+index 0000000..0123456
+--- /dev/null
++++ b/tweets/hello-world.tweet
+@@ -0,0 +1 @@
++Hello, world!`
+ );
+
+// check for comments
+nock("https://api.github.com", {
+ reqheaders: {
+ authorization: "token secret123",
+ },
+})
+ // no comments
+ .get("/repos/twitter-together/action/issues/123/comments")
+ .reply(200, []);
+
+// post comment
+nock("https://api.github.com")
+ // get changed files
+ .post("/repos/twitter-together/action/issues/123/comments", ({ body }) => {
+ tap.same(
+ body,
+ `## Found 1 new \`.tweet\` file(s)
+
+### ✅ Valid Tweet
+
+> Hello, world!
+
+---
+
+*Preview using 0000000000000000000000000000000000000002 generated by [Twitter, together!](https://github.com/twitter-together/action)*`
+ );
+ return true;
+ })
+ .reply(201);
+
+process.on("exit", (code) => {
+ tap.equal(code, 0);
+ tap.same(nock.pendingMocks(), []);
+});
+
+require("../../lib");
diff --git a/test/push-main-tweet-error/test.js b/test/push-main-tweet-error/test.js
index b18c14ef..2aff9068 100644
--- a/test/push-main-tweet-error/test.js
+++ b/test/push-main-tweet-error/test.js
@@ -45,7 +45,6 @@ nock("https://api.github.com", {
.post(
"/repos/twitter-together/action/commits/0000000000000000000000000000000000000002/comments",
(body) => {
- console.log(body.body);
tap.equal(
body.body,
"Errors:\n\n- Tweet exceeds maximum length of 280 characters by 166 characters"