mirror of
https://github.com/actions/add-to-project.git
synced 2025-12-10 20:21:17 +00:00
Add some simple unit tests
This commit is contained in:
122
src/add-to-project.ts
Normal file
122
src/add-to-project.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
import * as core from '@actions/core'
|
||||
import * as github from '@actions/github'
|
||||
|
||||
// TODO: Ensure this (and the Octokit client) works for non-github.com URLs, as well.
|
||||
// https://github.com/orgs|users/<ownerName>/projects/<projectNumber>
|
||||
const urlParse =
|
||||
/^(?:https:\/\/)?github\.com\/(?<ownerType>orgs|users)\/(?<ownerName>[^/]+)\/projects\/(?<projectNumber>\d+)/
|
||||
|
||||
interface ProjectNodeIDResponse {
|
||||
organization?: {
|
||||
projectNext: {
|
||||
id: string
|
||||
}
|
||||
}
|
||||
|
||||
user?: {
|
||||
projectNext: {
|
||||
id: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface ProjectAddItemResponse {
|
||||
addProjectNextItem: {
|
||||
projectNextItem: {
|
||||
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())
|
||||
.filter(l => l.length > 0) ?? []
|
||||
const octokit = github.getOctokit(ghToken)
|
||||
const urlMatch = projectUrl.match(urlParse)
|
||||
const issue = github.context.payload.issue ?? github.context.payload.pull_request
|
||||
const issueLabels: string[] = (issue?.labels ?? []).map((l: {name: string}) => l.name)
|
||||
|
||||
// Ensure the issue matches our `labeled` filter, if provided.
|
||||
if (labeled.length > 0) {
|
||||
const hasLabel = issueLabels.some(l => labeled.includes(l))
|
||||
|
||||
if (!hasLabel) {
|
||||
core.info(`Skipping issue ${issue?.number} because it does not have one of the labels: ${labeled.join(', ')}`)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
core.debug(`Project URL: ${projectUrl}`)
|
||||
|
||||
if (!urlMatch) {
|
||||
throw new Error(
|
||||
`Invalid project URL: ${projectUrl}. Project URL should match the format https://github.com/<orgs-or-users>/<ownerName>/projects/<projectNumber>`
|
||||
)
|
||||
}
|
||||
|
||||
const ownerName = urlMatch.groups?.ownerName
|
||||
const projectNumber = parseInt(urlMatch.groups?.projectNumber ?? '', 10)
|
||||
const ownerType = urlMatch.groups?.ownerType
|
||||
const ownerTypeQuery = mustGetOwnerTypeQuery(ownerType)
|
||||
|
||||
core.debug(`Org name: ${ownerName}`)
|
||||
core.debug(`Project number: ${projectNumber}`)
|
||||
core.debug(`Owner type: ${ownerType}`)
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}`,
|
||||
{
|
||||
ownerName,
|
||||
projectNumber
|
||||
}
|
||||
)
|
||||
|
||||
const projectId = idResp[ownerTypeQuery]?.projectNext.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.
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
124
src/main.ts
124
src/main.ts
@@ -1,117 +1,7 @@
|
||||
import * as core from '@actions/core'
|
||||
import * as github from '@actions/github'
|
||||
import {addToProject} from './add-to-project'
|
||||
|
||||
// TODO: Ensure this (and the Octokit client) works for non-github.com URLs, as well.
|
||||
// https://github.com/orgs|users/<ownerName>/projects/<projectNumber>
|
||||
const urlParse =
|
||||
/^(?:https:\/\/)?github\.com\/(?<ownerType>orgs|users)\/(?<ownerName>[^/]+)\/projects\/(?<projectNumber>\d+)/
|
||||
|
||||
interface ProjectNodeIDResponse {
|
||||
organization?: {
|
||||
projectNext: {
|
||||
id: string
|
||||
}
|
||||
}
|
||||
|
||||
user?: {
|
||||
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})
|
||||
const labeled =
|
||||
core
|
||||
.getInput('labeled')
|
||||
.split(',')
|
||||
.map(l => l.trim())
|
||||
.filter(l => l.length > 0) ?? []
|
||||
const octokit = github.getOctokit(ghToken)
|
||||
const urlMatch = projectUrl.match(urlParse)
|
||||
const issue = github.context.payload.issue ?? github.context.payload.pull_request
|
||||
const issueLabels: string[] = (issue?.labels ?? []).map((l: {name: string}) => l.name)
|
||||
|
||||
// Ensure the issue matches our `labeled` filter, if provided.
|
||||
if (labeled.length > 0) {
|
||||
const hasLabel = issueLabels.some(l => labeled.includes(l))
|
||||
|
||||
if (!hasLabel) {
|
||||
core.info(`Skipping issue ${issue?.number} because it does not have one of the labels: ${labeled.join(', ')}`)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
core.debug(`Project URL: ${projectUrl}`)
|
||||
|
||||
if (!urlMatch) {
|
||||
throw new Error(
|
||||
`Invalid project URL: ${projectUrl}. Project URL should match the format https://github.com/<orgs-or-users>/<ownerName>/projects/<projectNumber>`
|
||||
)
|
||||
}
|
||||
|
||||
const ownerName = urlMatch.groups?.ownerName
|
||||
const projectNumber = parseInt(urlMatch.groups?.projectNumber ?? '', 10)
|
||||
const ownerType = urlMatch.groups?.ownerType
|
||||
const ownerTypeQuery = mustGetOwnerTypeQuery(ownerType)
|
||||
|
||||
core.debug(`Org name: ${ownerName}`)
|
||||
core.debug(`Project number: ${projectNumber}`)
|
||||
core.debug(`Owner type: ${ownerType}`)
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}`,
|
||||
{
|
||||
ownerName,
|
||||
projectNumber
|
||||
}
|
||||
)
|
||||
|
||||
const projectId = idResp[ownerTypeQuery]?.projectNext.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.
|
||||
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()
|
||||
addToProject()
|
||||
.catch(err => {
|
||||
core.setFailed(err.message)
|
||||
process.exit(1)
|
||||
@@ -119,13 +9,3 @@ run()
|
||||
.then(() => {
|
||||
process.exit(0)
|
||||
})
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user