|
1 | 1 | #!/usr/bin/env node |
2 | 2 |
|
3 | 3 | const core = require('@actions/core') |
4 | | -const github = require('@actions/github') |
5 | 4 | const { createLogger } = require('@generates/logger') |
6 | | -const dot = require('@ianwalter/dot') |
7 | 5 | const execa = require('execa') |
| 6 | +const slugify = require('slugify') |
| 7 | +const octokit = require('@octokit/request') |
8 | 8 |
|
9 | | -const logger = createLogger() |
| 9 | +const logger = createLogger({ level: 'info', namespace: 'pull-request-action' }) |
10 | 10 |
|
11 | 11 | async function run () { |
12 | | - // Commit to the branch specified in inputs. |
13 | | - let branch = process.env.INPUT_BRANCH |
| 12 | + const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/') |
| 13 | + const headers = { authorization: `token ${process.env.GITHUB_TOKEN}` } |
| 14 | + const request = octokit.request.defaults({ headers }) |
14 | 15 |
|
15 | | - // If no branch was specified, try to set it to the PR branch. |
16 | | - if (!branch) branch = dot.get(github.context, 'payload.pull_request.head.ref') |
| 16 | + // Determine the pull request title. |
| 17 | + const title = process.env.INPUT_TITLE |
| 18 | + if (!title) throw new Error('No pull request title specified') |
17 | 19 |
|
18 | | - // If this is not a PR, use the current branch. |
19 | | - if (!branch) branch = process.env.GITHUB_REF |
| 20 | + // Determine the head branch. |
| 21 | + let head = process.env.INPUT_HEAD |
| 22 | + if (!head) head = slugify(title) |
20 | 23 |
|
21 | | - // Output the branch that will be used. |
22 | | - logger.info('Using branch:', branch) |
| 24 | + // Determine the base branch. |
| 25 | + let base = process.env.INPUT_BASE |
| 26 | + if (!base) { |
| 27 | + // If the base branch isn't specified, use the repo's default branch. |
| 28 | + const { data } = request('GET /repos/{owner}/{repo}', { owner, repo }) |
| 29 | + base = data.default_branch |
| 30 | + } |
| 31 | + |
| 32 | + let pr |
| 33 | + try { |
| 34 | + // Try fetching the head branch from origin. |
| 35 | + await execa('git', ['fetch', 'origin', head]) |
23 | 36 |
|
24 | | - // TODO: check if branch exists and create it if necessary. |
| 37 | + // Search for an existing pull request if the head branch exists. |
| 38 | + const q = `head:${head} type:pr is:open repo:${owner}/${repo}` |
| 39 | + const { data } = await request('GET /search/issues', { q }) |
| 40 | + pr = data.items?.length && data.items[0] |
25 | 41 |
|
26 | | - // Check if there are local changes. |
27 | | - const { stdout: hasChanges } = await execa('git', ['status', '--porcelain']) |
28 | | - if (hasChanges) { |
29 | | - // Add specified files or all changes. |
30 | | - const files = process.env.INPUT_FILES |
31 | | - ? process.env.INPUT_FILES.split('\n') |
32 | | - : '.' |
33 | | - await execa('git', ['add', files]) |
| 42 | + // Check out the head branch. |
| 43 | + await execa('git', ['checkout', head]) |
| 44 | + } catch (err) { |
| 45 | + // If an error was thrown, the branch doesn't exist, so create it. |
| 46 | + logger.debug('Failed to fetch branch from origin', err) |
| 47 | + await execa('git', ['checkout', '-b', head]) |
| 48 | + } |
34 | 49 |
|
35 | | - // Check if there are staged changes. |
36 | | - const args = ['diff', '--staged', '--name-only'] |
37 | | - const { stdout: hasStaged } = await execa('git', args) |
38 | | - if (hasStaged) { |
39 | | - // Configure the git user. |
40 | | - const author = 'github-actions[bot]' |
41 | | - await execa('git', ['config', '--global', 'user.name', author]) |
42 | | - const email = 'github-actions[bot]@users.noreply.github.com' |
43 | | - await execa('git', ['config', '--global', 'user.email', email]) |
| 50 | + // Commit changed files if necessary. |
| 51 | + const commitParams = ['--yes', '@generates/commit-action'] |
| 52 | + const { INPUT_COMMIT, INPUT_MESSAGE, INPUT_FILES } = process.env |
| 53 | + if (INPUT_COMMIT || INPUT_MESSAGE || INPUT_FILES) { |
| 54 | + await execa('npx', commitParams) |
| 55 | + } |
44 | 56 |
|
45 | | - // Commit the changes. |
46 | | - const message = process.env.INPUT_MESSAGE || 'Automated commit' |
47 | | - await execa('git', ['commit', '-m', message]) |
| 57 | + // Push the branch to origin. |
| 58 | + await execa('git', ['push', 'origin', head]) |
48 | 59 |
|
49 | | - // Push the changes back to the branch. |
50 | | - const actor = process.env.INPUT_ACTOR || process.env.GITHUB_ACTOR |
51 | | - const token = process.env.INPUT_TOKEN |
52 | | - const repo = process.env.GITHUB_REPOSITORY |
53 | | - const origin = token |
54 | | - ? `https://${actor}:${token}@github.com/${repo}.git` |
55 | | - : 'origin' |
56 | | - await execa('git', ['push', origin, `HEAD:${branch}`]) |
57 | | - } else { |
58 | | - logger.info('No staged files', { hasStaged }) |
59 | | - } |
| 60 | + const body = process.env.INPUT_BODY |
| 61 | + const payload = { owner, repo, title, body, head, base } |
| 62 | + if (pr) { |
| 63 | + // Update the existing pull request and make sure it's open. |
| 64 | + payload.pull_number = pr.pull_number |
| 65 | + payload.state = 'open' |
| 66 | + await request('PUT /repos/{owner}/{repo}/pulls/{pull_number}', payload) |
60 | 67 | } else { |
61 | | - logger.info('No local changes', { hasChanges }) |
| 68 | + // Create the pull request if it doesn't exist. |
| 69 | + await request('POST /repos/{owner}/{repo}/pulls', payload) |
62 | 70 | } |
63 | 71 | } |
64 | 72 |
|
|
0 commit comments