This repository has been archived by the owner on Dec 15, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 393
GitHub Actions to automate our GraphQL schema management #2108
Merged
Merged
Changes from all commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
338da57
"schema-up" action to automatically update the GraphQL schema
smashwilson e86e5fc
"schema-automerge" action to automatically merge PRs created by "sche…
smashwilson dd2d315
Babby's first workflow file
smashwilson 696b3b2
Ah does it not support MON
smashwilson bbfc7e0
Okay on second thought let's not do the automerge thing
smashwilson 80e7569
Since we're not actually making automerge happen.
smashwilson eff8f56
Report `relay-compiler` failures to the opened PR
smashwilson 7cc72bd
Let's run every ten minutes to work the kinks out.
smashwilson 3d162bb
Apply suggestions from code review
kuychaco 324d362
Create schemaUpdateLabel constant
smashwilson 193432c
Supply missing parameters to createPullRequest mutation
smashwilson bddd256
Report success :tada:
smashwilson e1d35c3
Maybe specify the COPY paths explicitly?
smashwilson 3c98bbf
Actually install git
smashwilson 1eee99d
Configure git username and email (hi @hubot)
smashwilson b6740df
`tools.github.graphql` has a different return value than I thought
smashwilson ad0b2c8
Looks like it just returns the query shape as an object?
smashwilson ba2c5ff
Labelable doesn't have an ID. Just select the null mutation ID
smashwilson caa1af9
No quotes around the commit message
smashwilson 142dcb0
Pass --watchman false to relay-compiler
smashwilson 6029347
Let's make the already-existing-PR case a neutral result
smashwilson 22cf03d
git diff doesn't understand the ** splat
smashwilson 1ba63d7
It's "code" not "exitCode"
smashwilson 6956b00
Specify the upstream to push to
smashwilson bd98a6a
Remove more commit message quotes
smashwilson 5f0650b
relay-compiler dumps errors to stdout
smashwilson f6a9e52
Run it weekly :calendar:
smashwilson File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
workflow "GraphQL schema update" { | ||
// Every Monday at 1am. | ||
on = "schedule(0 1 * * 1)" | ||
resolves = "Update schema" | ||
} | ||
|
||
action "Update schema" { | ||
uses = "./actions/schema-up" | ||
secrets = ["GITHUB_TOKEN"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
FROM node:8-slim | ||
|
||
LABEL "com.github.actions.name"="schema-up" | ||
LABEL "com.github.actions.description"="Update GraphQL schema and adjust Relay files" | ||
LABEL "com.github.actions.icon"="arrow-up-right" | ||
LABEL "com.github.actions.color"="blue" | ||
|
||
RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/* | ||
|
||
# Copy the package.json and package-lock.json | ||
COPY package*.json / | ||
|
||
# Install dependencies | ||
RUN npm ci | ||
|
||
# Copy the rest of your action's code | ||
COPY * / | ||
|
||
# Run `node /index.js` | ||
ENTRYPOINT ["node", "/index.js"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# actions/schema-up | ||
|
||
Fetch the latest GraphQL schema changes from github.com. Commit and push the schema change directly to the `master` branch if no further changes are made. Otherwise, open a pull request with the ["schema update" label](https://github.com/atom/github/labels/schema%20update) applied, as long as no such pull request already exists. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
const fs = require('fs'); | ||
const path = require('path'); | ||
const fetch = require('node-fetch'); | ||
|
||
const {buildClientSchema, printSchema} = require('graphql/utilities'); | ||
const SERVER = 'https://api.github.com/graphql'; | ||
const introspectionQuery = ` | ||
query IntrospectionQuery { | ||
__schema { | ||
queryType { name } | ||
mutationType { name } | ||
subscriptionType { name } | ||
types { | ||
...FullType | ||
} | ||
directives { | ||
name | ||
description | ||
locations | ||
args { | ||
...InputValue | ||
} | ||
} | ||
} | ||
} | ||
fragment FullType on __Type { | ||
kind | ||
name | ||
description | ||
fields(includeDeprecated: false) { | ||
name | ||
description | ||
args { | ||
...InputValue | ||
} | ||
type { | ||
...TypeRef | ||
} | ||
isDeprecated | ||
deprecationReason | ||
} | ||
inputFields { | ||
...InputValue | ||
} | ||
interfaces { | ||
...TypeRef | ||
} | ||
enumValues(includeDeprecated: false) { | ||
name | ||
description | ||
isDeprecated | ||
deprecationReason | ||
} | ||
possibleTypes { | ||
...TypeRef | ||
} | ||
} | ||
fragment InputValue on __InputValue { | ||
name | ||
description | ||
type { ...TypeRef } | ||
defaultValue | ||
} | ||
fragment TypeRef on __Type { | ||
kind | ||
name | ||
ofType { | ||
kind | ||
name | ||
ofType { | ||
kind | ||
name | ||
ofType { | ||
kind | ||
name | ||
ofType { | ||
kind | ||
name | ||
ofType { | ||
kind | ||
name | ||
ofType { | ||
kind | ||
name | ||
ofType { | ||
kind | ||
name | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
`; | ||
|
||
module.exports = async function() { | ||
const token = process.env.GITHUB_TOKEN; | ||
if (!token) { | ||
throw new Error('You must specify a GitHub auth token in GITHUB_TOKEN'); | ||
} | ||
|
||
const schemaPath = path.resolve(process.env.GITHUB_WORKSPACE, 'graphql', 'schema.graphql'); | ||
|
||
const res = await fetch(SERVER, { | ||
method: 'POST', | ||
headers: { | ||
'Accept': 'application/vnd.github.antiope-preview+json', | ||
'Content-Type': 'application/json', | ||
'Authorization': 'bearer ' + token, | ||
}, | ||
body: JSON.stringify({query: introspectionQuery}), | ||
}); | ||
const schemaJSON = await res.json(); | ||
const graphQLSchema = buildClientSchema(schemaJSON.data); | ||
await new Promise((resolve, reject) => { | ||
fs.writeFile(schemaPath, printSchema(graphQLSchema), {encoding: 'utf8'}, err => { | ||
if (err) { reject(err); } else { resolve(); } | ||
}); | ||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
const path = require('path'); | ||
|
||
const {Toolkit} = require('actions-toolkit'); | ||
const fetchSchema = require('./fetch-schema'); | ||
|
||
const schemaUpdateLabel = { | ||
name: 'schema update', | ||
id: 'MDU6TGFiZWwxMzQyMzM1MjQ2', | ||
}; | ||
|
||
Toolkit.run(async tools => { | ||
await tools.runInWorkspace('git', ['config', '--global', 'user.email', 'hubot@github.com']); | ||
await tools.runInWorkspace('git', ['config', '--global', 'user.name', 'hubot']); | ||
|
||
tools.log.info('Fetching the latest GraphQL schema changes.'); | ||
await fetchSchema(); | ||
|
||
const {code: hasSchemaChanges} = await tools.runInWorkspace( | ||
'git', ['diff', '--quiet', '--', 'graphql/schema.graphql'], | ||
{reject: false}, | ||
); | ||
if (hasSchemaChanges === 0) { | ||
tools.log.info('No schema changes to fetch.'); | ||
tools.exit.neutral('Nothing to do.'); | ||
} | ||
|
||
tools.log.info('Committing schema changes.'); | ||
await tools.runInWorkspace('git', ['commit', '--all', '--message', ':arrow_up: GraphQL schema']); | ||
|
||
tools.log.info('Re-running relay compiler.'); | ||
const {failed: relayFailed, stdout: relayOutput} = await tools.runInWorkspace( | ||
path.resolve(__dirname, 'node_modules', '.bin', 'relay-compiler'), | ||
['--watchman', 'false', '--src', './lib', '--schema', 'graphql/schema.graphql'], | ||
{reject: false}, | ||
); | ||
tools.log.info('Relay output:\n%s', relayOutput); | ||
|
||
const {code: hasRelayChanges} = await tools.runInWorkspace( | ||
'git', ['diff', '--quiet'], | ||
{reject: false}, | ||
); | ||
|
||
if (hasRelayChanges === 0 && !relayFailed) { | ||
tools.log.info('Generated relay files are unchanged.'); | ||
const upstream = tools.context.ref.replace(/^refs\/heads\//, ''); | ||
await tools.runInWorkspace('git', ['push', 'origin', upstream]); | ||
tools.exit.success('Schema is up to date on master.'); | ||
} | ||
|
||
tools.log.info('Checking for unmerged schema update pull requests.'); | ||
const openPullRequestsQuery = await tools.github.graphql(` | ||
query openPullRequestsQuery($owner: String!, $repo: String!, $labelName: String!) { | ||
repository(owner: $owner, name: $repo) { | ||
id | ||
pullRequests(first: 1, states: [OPEN], labels: [$labelName]) { | ||
totalCount | ||
} | ||
} | ||
} | ||
`, {...tools.context.repo, labelName: schemaUpdateLabel.name}); | ||
|
||
const repositoryId = openPullRequestsQuery.repository.id; | ||
|
||
if (openPullRequestsQuery.repository.pullRequests.totalCount > 0) { | ||
tools.exit.neutral('One or more schema update pull requests are already open. Please resolve those first.'); | ||
} | ||
|
||
const branchName = `schema-update/${Date.now()}`; | ||
tools.log.info(`Commiting relay-compiler changes to a new branch ${branchName}.`); | ||
await tools.runInWorkspace('git', ['checkout', '-b', branchName]); | ||
if (!relayFailed) { | ||
await tools.runInWorkspace('git', ['commit', '--all', '--message', ':gear: relay-compiler changes']); | ||
} | ||
await tools.runInWorkspace('git', ['push', 'origin', branchName]); | ||
|
||
tools.log.info('Creating a pull request.'); | ||
|
||
let body = `:robot: _This automated pull request brought to you by [a GitHub action](/actions/schema-up)_ :robot: | ||
|
||
The GraphQL schema has been automatically updated and \`relay-compiler\` has been re-run on the package source.`; | ||
|
||
if (!relayFailed) { | ||
body += ' The modified files have been committed to this branch and pushed. '; | ||
body += 'If all of the tests pass in CI, merge with confidence :zap:'; | ||
} else { | ||
body += ' `relay-compiler` failed with the following output:\n\n```\n'; | ||
body += relayOutput; | ||
body += '\n```\n\nCheck out this branch to fix things so we don\'t break.'; | ||
} | ||
|
||
const createPullRequestMutation = await tools.github.graphql(` | ||
mutation createPullRequestMutation($repositoryId: ID!, $headRefName: String!, $body: String!) { | ||
createPullRequest(input: { | ||
repositoryId: $repositoryId | ||
title: "GraphQL schema update" | ||
body: $body | ||
baseRefName: "master" | ||
headRefName: $headRefName | ||
}) { | ||
pullRequest { | ||
id | ||
number | ||
} | ||
} | ||
} | ||
`, { | ||
repositoryId, | ||
headRefName: branchName, | ||
body, | ||
}); | ||
|
||
const createdPullRequest = createPullRequestMutation.createPullRequest.pullRequest; | ||
tools.log.info( | ||
`Pull request #${createdPullRequest.number} has been opened with the changes from this schema upgrade.`, | ||
); | ||
|
||
await tools.github.graphql(` | ||
mutation labelPullRequestMutation($id: ID!, $labelIDs: [ID!]!) { | ||
addLabelsToLabelable(input: { | ||
labelableId: $id, | ||
labelIds: $labelIDs | ||
}) { | ||
clientMutationId | ||
} | ||
} | ||
`, {id: createdPullRequest.id, labelIDs: [schemaUpdateLabel.id]}); | ||
tools.exit.success( | ||
`Pull request #${createdPullRequest.number} has been opened and labelled for this schema upgrade.`, | ||
); | ||
}, { | ||
smashwilson marked this conversation as resolved.
Show resolved
Hide resolved
|
||
secrets: ['GITHUB_TOKEN'], | ||
}); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm it's a bit annoying that you're forced to ask for return data, even if there's no use for it...