mirror of
https://github.com/actions/add-to-project.git
synced 2025-12-11 20:47:05 +00:00
Add some simple unit tests
This commit is contained in:
148
__tests__/add-to-project.test.ts
Normal file
148
__tests__/add-to-project.test.ts
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
import * as core from '@actions/core'
|
||||||
|
import * as github from '@actions/github'
|
||||||
|
import {addToProject} from '../src/add-to-project'
|
||||||
|
|
||||||
|
describe('addToProject', () => {
|
||||||
|
let outputs: Record<string, string>
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.spyOn(process.stdout, 'write').mockImplementation(() => true)
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockGetInput({
|
||||||
|
'project-url': 'https://github.com/orgs/github/projects/1',
|
||||||
|
'github-token': 'gh_token'
|
||||||
|
})
|
||||||
|
|
||||||
|
outputs = mockSetOutput()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
github.context.payload = {}
|
||||||
|
jest.restoreAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('adds an issue to the project', async () => {
|
||||||
|
mockGraphQL(
|
||||||
|
{
|
||||||
|
test: /getProject/,
|
||||||
|
return: {
|
||||||
|
organization: {
|
||||||
|
projectNext: {
|
||||||
|
id: 'project-next-id'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /addProjectNextItem/,
|
||||||
|
return: {
|
||||||
|
addProjectNextItem: {
|
||||||
|
projectNextItem: {
|
||||||
|
id: 'project-next-item-id'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
await addToProject()
|
||||||
|
|
||||||
|
expect(outputs.itemId).toEqual('project-next-item-id')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('adds matching issues with a label filter', async () => {
|
||||||
|
mockGetInput({
|
||||||
|
'project-url': 'https://github.com/orgs/github/projects/1',
|
||||||
|
'github-token': 'gh_token',
|
||||||
|
labeled: 'bug'
|
||||||
|
})
|
||||||
|
|
||||||
|
github.context.payload = {
|
||||||
|
issue: {
|
||||||
|
number: 1,
|
||||||
|
labels: [{name: 'bug'}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mockGraphQL(
|
||||||
|
{
|
||||||
|
test: /getProject/,
|
||||||
|
return: {
|
||||||
|
organization: {
|
||||||
|
projectNext: {
|
||||||
|
id: 'project-next-id'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /addProjectNextItem/,
|
||||||
|
return: {
|
||||||
|
addProjectNextItem: {
|
||||||
|
projectNextItem: {
|
||||||
|
id: 'project-next-item-id'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
await addToProject()
|
||||||
|
|
||||||
|
expect(outputs.itemId).toEqual('project-next-item-id')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('does not add un-matching issues with a label filter', async () => {
|
||||||
|
mockGetInput({
|
||||||
|
'project-url': 'https://github.com/orgs/github/projects/1',
|
||||||
|
'github-token': 'gh_token',
|
||||||
|
labeled: 'bug'
|
||||||
|
})
|
||||||
|
|
||||||
|
github.context.payload = {
|
||||||
|
issue: {
|
||||||
|
number: 1,
|
||||||
|
labels: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const infoSpy = jest.spyOn(core, 'info')
|
||||||
|
const gqlMock = mockGraphQL()
|
||||||
|
await addToProject()
|
||||||
|
expect(infoSpy).toHaveBeenCalledWith(`Skipping issue 1 because it does not have one of the labels: bug`)
|
||||||
|
expect(gqlMock).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
function mockGetInput(mocks: Record<string, string>): jest.SpyInstance {
|
||||||
|
const mock = (key: string) => mocks[key] ?? ''
|
||||||
|
return jest.spyOn(core, 'getInput').mockImplementation(mock)
|
||||||
|
}
|
||||||
|
|
||||||
|
function mockSetOutput(): Record<string, string> {
|
||||||
|
const output: Record<string, string> = {}
|
||||||
|
jest.spyOn(core, 'setOutput').mockImplementation((key, value) => (output[key] = value))
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
function mockGraphQL(...mocks: {test: RegExp; return: unknown}[]): jest.Mock {
|
||||||
|
const mock = jest.fn().mockImplementation((query: string) => {
|
||||||
|
const match = mocks.find(m => m.test.test(query))
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
return match.return
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Unexpected GraphQL query: ${query}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
jest.spyOn(github, 'getOctokit').mockImplementation(() => {
|
||||||
|
return {
|
||||||
|
graphql: mock
|
||||||
|
} as unknown as ReturnType<typeof github.getOctokit>
|
||||||
|
})
|
||||||
|
|
||||||
|
return mock
|
||||||
|
}
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import {expect, test} from '@jest/globals'
|
|
||||||
|
|
||||||
test('sanity check', () => {
|
|
||||||
expect(true).toBeTruthy()
|
|
||||||
})
|
|
||||||
53
dist/index.js
generated
vendored
53
dist/index.js
generated
vendored
@@ -1,7 +1,7 @@
|
|||||||
require('./sourcemap-register.js');/******/ (() => { // webpackBootstrap
|
require('./sourcemap-register.js');/******/ (() => { // webpackBootstrap
|
||||||
/******/ var __webpack_modules__ = ({
|
/******/ var __webpack_modules__ = ({
|
||||||
|
|
||||||
/***/ 3109:
|
/***/ 6672:
|
||||||
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
@@ -35,12 +35,13 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||||
|
exports.addToProject = void 0;
|
||||||
const core = __importStar(__nccwpck_require__(2186));
|
const core = __importStar(__nccwpck_require__(2186));
|
||||||
const github = __importStar(__nccwpck_require__(5438));
|
const github = __importStar(__nccwpck_require__(5438));
|
||||||
// TODO: Ensure this (and the Octokit client) works for non-github.com URLs, as well.
|
// TODO: Ensure this (and the Octokit client) works for non-github.com URLs, as well.
|
||||||
// https://github.com/orgs|users/<ownerName>/projects/<projectNumber>
|
// https://github.com/orgs|users/<ownerName>/projects/<projectNumber>
|
||||||
const urlParse = /^(?:https:\/\/)?github\.com\/(?<ownerType>orgs|users)\/(?<ownerName>[^/]+)\/projects\/(?<projectNumber>\d+)/;
|
const urlParse = /^(?:https:\/\/)?github\.com\/(?<ownerType>orgs|users)\/(?<ownerName>[^/]+)\/projects\/(?<projectNumber>\d+)/;
|
||||||
function run() {
|
function addToProject() {
|
||||||
var _a, _b, _c, _d, _e, _f, _g, _h;
|
var _a, _b, _c, _d, _e, _f, _g, _h;
|
||||||
return __awaiter(this, void 0, void 0, function* () {
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
const projectUrl = core.getInput('project-url', { required: true });
|
const projectUrl = core.getInput('project-url', { required: true });
|
||||||
@@ -104,14 +105,7 @@ function run() {
|
|||||||
core.setOutput('itemId', addResp.addProjectNextItem.projectNextItem.id);
|
core.setOutput('itemId', addResp.addProjectNextItem.projectNextItem.id);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
run()
|
exports.addToProject = addToProject;
|
||||||
.catch(err => {
|
|
||||||
core.setFailed(err.message);
|
|
||||||
process.exit(1);
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
process.exit(0);
|
|
||||||
});
|
|
||||||
function mustGetOwnerTypeQuery(ownerType) {
|
function mustGetOwnerTypeQuery(ownerType) {
|
||||||
const ownerTypeQuery = ownerType === 'orgs' ? 'organization' : ownerType === 'users' ? 'user' : null;
|
const ownerTypeQuery = ownerType === 'orgs' ? 'organization' : ownerType === 'users' ? 'user' : null;
|
||||||
if (!ownerTypeQuery) {
|
if (!ownerTypeQuery) {
|
||||||
@@ -121,6 +115,45 @@ function mustGetOwnerTypeQuery(ownerType) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
|
||||||
|
/***/ 3109:
|
||||||
|
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||||
|
if (k2 === undefined) k2 = k;
|
||||||
|
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
||||||
|
}) : (function(o, m, k, k2) {
|
||||||
|
if (k2 === undefined) k2 = k;
|
||||||
|
o[k2] = m[k];
|
||||||
|
}));
|
||||||
|
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||||
|
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||||
|
}) : function(o, v) {
|
||||||
|
o["default"] = v;
|
||||||
|
});
|
||||||
|
var __importStar = (this && this.__importStar) || function (mod) {
|
||||||
|
if (mod && mod.__esModule) return mod;
|
||||||
|
var result = {};
|
||||||
|
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||||
|
__setModuleDefault(result, mod);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||||
|
const core = __importStar(__nccwpck_require__(2186));
|
||||||
|
const add_to_project_1 = __nccwpck_require__(6672);
|
||||||
|
(0, add_to_project_1.addToProject)()
|
||||||
|
.catch(err => {
|
||||||
|
core.setFailed(err.message);
|
||||||
|
process.exit(1);
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
process.exit(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
/***/ }),
|
/***/ }),
|
||||||
|
|
||||||
/***/ 7351:
|
/***/ 7351:
|
||||||
|
|||||||
2
dist/index.js.map
generated
vendored
2
dist/index.js.map
generated
vendored
File diff suppressed because one or more lines are too long
25
package-lock.json
generated
25
package-lock.json
generated
@@ -14,6 +14,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@github/prettier-config": "^0.0.4",
|
"@github/prettier-config": "^0.0.4",
|
||||||
|
"@types/jest": "^27.4.0",
|
||||||
"@types/node": "^12.12.6",
|
"@types/node": "^12.12.6",
|
||||||
"@typescript-eslint/parser": "^5.10.2",
|
"@typescript-eslint/parser": "^5.10.2",
|
||||||
"@vercel/ncc": "^0.33.1",
|
"@vercel/ncc": "^0.33.1",
|
||||||
@@ -26,6 +27,10 @@
|
|||||||
"prettier": "2.5.1",
|
"prettier": "2.5.1",
|
||||||
"ts-jest": "^27.1.3",
|
"ts-jest": "^27.1.3",
|
||||||
"typescript": "^4.5.5"
|
"typescript": "^4.5.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0 <17.0.0",
|
||||||
|
"npm": ">= 7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@actions/core": {
|
"node_modules/@actions/core": {
|
||||||
@@ -1305,6 +1310,16 @@
|
|||||||
"@types/istanbul-lib-report": "*"
|
"@types/istanbul-lib-report": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/jest": {
|
||||||
|
"version": "27.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.4.0.tgz",
|
||||||
|
"integrity": "sha512-gHl8XuC1RZ8H2j5sHv/JqsaxXkDDM9iDOgu0Wp8sjs4u/snb2PVehyWXJPr+ORA0RPpgw231mnutWI1+0hgjIQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"jest-diff": "^27.0.0",
|
||||||
|
"pretty-format": "^27.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/json-schema": {
|
"node_modules/@types/json-schema": {
|
||||||
"version": "7.0.9",
|
"version": "7.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz",
|
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz",
|
||||||
@@ -7324,6 +7339,16 @@
|
|||||||
"@types/istanbul-lib-report": "*"
|
"@types/istanbul-lib-report": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/jest": {
|
||||||
|
"version": "27.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.4.0.tgz",
|
||||||
|
"integrity": "sha512-gHl8XuC1RZ8H2j5sHv/JqsaxXkDDM9iDOgu0Wp8sjs4u/snb2PVehyWXJPr+ORA0RPpgw231mnutWI1+0hgjIQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"jest-diff": "^27.0.0",
|
||||||
|
"pretty-format": "^27.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/json-schema": {
|
"@types/json-schema": {
|
||||||
"version": "7.0.9",
|
"version": "7.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz",
|
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz",
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@github/prettier-config": "^0.0.4",
|
"@github/prettier-config": "^0.0.4",
|
||||||
|
"@types/jest": "^27.4.0",
|
||||||
"@types/node": "^12.12.6",
|
"@types/node": "^12.12.6",
|
||||||
"@typescript-eslint/parser": "^5.10.2",
|
"@typescript-eslint/parser": "^5.10.2",
|
||||||
"@vercel/ncc": "^0.33.1",
|
"@vercel/ncc": "^0.33.1",
|
||||||
|
|||||||
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 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.
|
addToProject()
|
||||||
// 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()
|
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
core.setFailed(err.message)
|
core.setFailed(err.message)
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
@@ -119,13 +9,3 @@ run()
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
process.exit(0)
|
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