mirror of
https://github.com/actions/add-to-project.git
synced 2025-12-10 03:57:00 +00:00
- Added octokit plugin for automatic retries - Removed un-imported dependency causing version conflicts - Update dist for action with new retry plugin - Update license cache
171 lines
5.0 KiB
TypeScript
171 lines
5.0 KiB
TypeScript
import * as core from '@actions/core'
|
|
import * as github from '@actions/github'
|
|
import {retry} from '@octokit/plugin-retry'
|
|
|
|
const urlParse = /\/(?<ownerType>orgs|users)\/(?<ownerName>[^/]+)\/projects\/(?<projectNumber>\d+)/
|
|
|
|
interface ProjectNodeIDResponse {
|
|
organization?: {
|
|
projectV2: {
|
|
id: string
|
|
}
|
|
}
|
|
|
|
user?: {
|
|
projectV2: {
|
|
id: string
|
|
}
|
|
}
|
|
}
|
|
|
|
interface ProjectAddItemResponse {
|
|
addProjectV2ItemById: {
|
|
item: {
|
|
id: string
|
|
}
|
|
}
|
|
}
|
|
|
|
interface ProjectV2AddDraftIssueResponse {
|
|
addProjectV2DraftIssue: {
|
|
projectItem: {
|
|
id: string
|
|
}
|
|
}
|
|
}
|
|
|
|
export async function addToProject(): Promise<void> {
|
|
const projectUrl = core.getInput('project-url', {required: true})
|
|
const ghToken = core.getInput('github-token', {required: true})
|
|
const labeled =
|
|
core
|
|
.getInput('labeled')
|
|
.split(',')
|
|
.map(l => l.trim().toLowerCase())
|
|
.filter(l => l.length > 0) ?? []
|
|
const labelOperator = core.getInput('label-operator').trim().toLocaleLowerCase()
|
|
|
|
const octokit = github.getOctokit(ghToken, undefined, retry)
|
|
|
|
const issue = github.context.payload.issue ?? github.context.payload.pull_request
|
|
const issueLabels: string[] = (issue?.labels ?? []).map((l: {name: string}) => l.name.toLowerCase())
|
|
const issueOwnerName = github.context.payload.repository?.owner.login
|
|
|
|
core.debug(`Issue/PR owner: ${issueOwnerName}`)
|
|
core.debug(`Issue/PR labels: ${issueLabels.join(', ')}`)
|
|
|
|
// Ensure the issue matches our `labeled` filter based on the label-operator.
|
|
if (labelOperator === 'and') {
|
|
if (!labeled.every(l => issueLabels.includes(l))) {
|
|
core.info(`Skipping issue ${issue?.number} because it doesn't match all the labels: ${labeled.join(', ')}`)
|
|
return
|
|
}
|
|
} else if (labelOperator === 'not') {
|
|
if (labeled.length > 0 && issueLabels.some(l => labeled.includes(l))) {
|
|
core.info(`Skipping issue ${issue?.number} because it contains one of the labels: ${labeled.join(', ')}`)
|
|
return
|
|
}
|
|
} else {
|
|
if (labeled.length > 0 && !issueLabels.some(l => labeled.includes(l))) {
|
|
core.info(`Skipping issue ${issue?.number} because it does not have one of the labels: ${labeled.join(', ')}`)
|
|
return
|
|
}
|
|
}
|
|
|
|
core.debug(`Project URL: ${projectUrl}`)
|
|
|
|
const urlMatch = projectUrl.match(urlParse)
|
|
|
|
if (!urlMatch) {
|
|
throw new Error(
|
|
`Invalid project URL: ${projectUrl}. Project URL should match the format <GitHub server domain name>/<orgs-or-users>/<ownerName>/projects/<projectNumber>`,
|
|
)
|
|
}
|
|
|
|
const projectOwnerName = urlMatch.groups?.ownerName
|
|
const projectNumber = parseInt(urlMatch.groups?.projectNumber ?? '', 10)
|
|
const ownerType = urlMatch.groups?.ownerType
|
|
const ownerTypeQuery = mustGetOwnerTypeQuery(ownerType)
|
|
|
|
core.debug(`Project owner: ${projectOwnerName}`)
|
|
core.debug(`Project number: ${projectNumber}`)
|
|
core.debug(`Project owner type: ${ownerType}`)
|
|
|
|
// First, use the GraphQL API to request the project's node ID.
|
|
const idResp = await octokit.graphql<ProjectNodeIDResponse>(
|
|
`query getProject($projectOwnerName: String!, $projectNumber: Int!) {
|
|
${ownerTypeQuery}(login: $projectOwnerName) {
|
|
projectV2(number: $projectNumber) {
|
|
id
|
|
}
|
|
}
|
|
}`,
|
|
{
|
|
projectOwnerName,
|
|
projectNumber,
|
|
},
|
|
)
|
|
|
|
const projectId = idResp[ownerTypeQuery]?.projectV2.id
|
|
const contentId = issue?.node_id
|
|
|
|
core.debug(`Project node ID: ${projectId}`)
|
|
core.debug(`Content ID: ${contentId}`)
|
|
|
|
// Next, use the GraphQL API to add the issue to the project.
|
|
// If the issue has the same owner as the project, we can directly
|
|
// add a project item. Otherwise, we add a draft issue.
|
|
if (issueOwnerName === projectOwnerName) {
|
|
core.info('Creating project item')
|
|
|
|
const addResp = await octokit.graphql<ProjectAddItemResponse>(
|
|
`mutation addIssueToProject($input: AddProjectV2ItemByIdInput!) {
|
|
addProjectV2ItemById(input: $input) {
|
|
item {
|
|
id
|
|
}
|
|
}
|
|
}`,
|
|
{
|
|
input: {
|
|
projectId,
|
|
contentId,
|
|
},
|
|
},
|
|
)
|
|
|
|
core.setOutput('itemId', addResp.addProjectV2ItemById.item.id)
|
|
} else {
|
|
core.info('Creating draft issue in project')
|
|
|
|
const addResp = await octokit.graphql<ProjectV2AddDraftIssueResponse>(
|
|
`mutation addDraftIssueToProject($projectId: ID!, $title: String!) {
|
|
addProjectV2DraftIssue(input: {
|
|
projectId: $projectId,
|
|
title: $title
|
|
}) {
|
|
projectItem {
|
|
id
|
|
}
|
|
}
|
|
}`,
|
|
{
|
|
projectId,
|
|
title: issue?.html_url,
|
|
},
|
|
)
|
|
|
|
core.setOutput('itemId', addResp.addProjectV2DraftIssue.projectItem.id)
|
|
}
|
|
}
|
|
|
|
export function mustGetOwnerTypeQuery(ownerType?: string): 'organization' | 'user' {
|
|
const ownerTypeQuery = ownerType === 'orgs' ? 'organization' : ownerType === 'users' ? 'user' : null
|
|
|
|
if (!ownerTypeQuery) {
|
|
throw new Error(`Unsupported ownerType: ${ownerType}. Must be one of 'orgs' or 'users'`)
|
|
}
|
|
|
|
return ownerTypeQuery
|
|
}
|