Files
add-to-project/src/main.ts

127 lines
3.4 KiB
TypeScript
Raw Normal View History

2022-01-31 15:38:31 -05:00
import core from '@actions/core'
import github from '@actions/github'
2022-01-31 14:09:42 -05:00
// TODO: Ensure this (and the Octokit client) works for non-github.com URLs, as well.
2022-01-31 14:19:41 -05:00
// https://github.com/orgs|users/<ownerName>/projects/<projectNumber>
2022-01-31 14:09:42 -05:00
const urlParse =
2022-01-31 14:19:41 -05:00
/^(?:https:\/\/)?github\.com\/(?<ownerType>orgs|users)\/(?<ownerName>[^/]+)\/projects\/(?<projectNumber>\d+)/
interface ProjectNodeIDResponse {
organization?: {
projectNext: {
id: string
}
2022-01-31 14:19:41 -05:00
}
user?: {
2022-01-31 14:09:42 -05:00
projectNext: {
id: string
}
}
}
interface ProjectAddItemResponse {
addProjectNextItem: {
projectNextItem: {
id: string
}
}
}
async function run(): Promise<void> {
const projectUrl = core.getInput('project-url', {required: true})
const ghToken = core.getInput('github-token', {required: true})
2022-01-31 15:12:36 -05:00
const labeled = core.getInput('labeled')?.split(',') ?? []
2022-01-31 14:09:42 -05:00
const octokit = github.getOctokit(ghToken)
const urlMatch = projectUrl.match(urlParse)
2022-01-31 15:38:31 -05:00
const issue = github.context.payload.issue ?? github.context.payload.pull_request
const issueLabels: string[] = (issue?.labels ?? []).map((l: {name: string}) => l.name)
2022-01-31 15:12:36 -05:00
// Ensure the issue matches our `labeled` filter, if provided.
if (labeled.length > 0) {
const hasLabel = issueLabels.some(l => labeled.includes(l))
if (!hasLabel) {
2022-01-31 15:38:31 -05:00
core.info(`Skipping issue ${issue?.number} because it does not have one of the labels: ${labeled.join(', ')}`)
2022-01-31 15:12:36 -05:00
return
}
}
2022-01-31 14:09:42 -05:00
core.debug(`Project URL: ${projectUrl}`)
if (!urlMatch) {
throw new Error(
2022-01-31 14:19:41 -05:00
`Invalid project URL: ${projectUrl}. Project URL should match the format https://github.com/<orgs-or-users>/<ownerName>/projects/<projectNumber>`
2022-01-31 14:09:42 -05:00
)
}
2022-01-31 14:19:41 -05:00
const ownerName = urlMatch.groups?.ownerName
2022-01-31 14:09:42 -05:00
const projectNumber = parseInt(urlMatch.groups?.projectNumber ?? '', 10)
2022-01-31 14:19:41 -05:00
const ownerType = urlMatch.groups?.ownerType
const ownerTypeQuery = mustGetOwnerTypeQuery(ownerType)
2022-01-31 14:09:42 -05:00
2022-01-31 14:19:41 -05:00
core.debug(`Org name: ${ownerName}`)
2022-01-31 14:09:42 -05:00
core.debug(`Project number: ${projectNumber}`)
2022-01-31 14:19:41 -05:00
core.debug(`Owner type: ${ownerType}`)
2022-01-31 14:09:42 -05:00
// First, use the GraphQL API to request the project's node ID.
const idResp = await octokit.graphql<ProjectNodeIDResponse>(
`query getProject($ownerName: String!, $projectNumber: Int!) {
${ownerTypeQuery}(login: $ownerName) {
projectNext(number: $projectNumber) {
id
}
}
}`,
2022-01-31 14:09:42 -05:00
{
2022-01-31 14:19:41 -05:00
ownerName,
2022-01-31 14:09:42 -05:00
projectNumber
}
)
const projectId = idResp[ownerTypeQuery]?.projectNext.id
2022-01-31 15:12:36 -05:00
const contentId = issue?.node_id
2022-01-31 14:09:42 -05:00
core.debug(`Project node ID: ${projectId}`)
core.debug(`Content ID: ${contentId}`)
// Next, use the GraphQL API to add the issue to the project.
const addResp = await octokit.graphql<ProjectAddItemResponse>(
`mutation addIssueToProject($input: AddProjectNextItemInput!) {
addProjectNextItem(input: $input) {
projectNextItem {
id
}
}
}`,
{
input: {
contentId,
projectId
}
}
)
core.setOutput('itemId', addResp.addProjectNextItem.projectNextItem.id)
}
run()
.catch(err => {
core.setFailed(err.message)
process.exit(1)
})
.then(() => {
process.exit(0)
})
function mustGetOwnerTypeQuery(ownerType?: string): 'organization' | 'user' {
2022-01-31 15:38:31 -05:00
const ownerTypeQuery = ownerType === 'orgs' ? 'organization' : ownerType === 'users' ? 'user' : null
if (!ownerTypeQuery) {
2022-01-31 15:38:31 -05:00
throw new Error(`Unsupported ownerType: ${ownerType}. Must be one of 'orgs' or 'users'`)
}
return ownerTypeQuery
}