diff --git a/packages/github/README.md b/packages/github/README.md index 12c5b0b078..ba8224ac2f 100644 --- a/packages/github/README.md +++ b/packages/github/README.md @@ -55,3 +55,31 @@ const newIssue = await octokit.issues.create({ body: 'Hello Universe!' }); ``` + +### Context Payload Casting in Typescript +The [GitHub Webhook Event](https://developer.github.com/webhooks/#events) payload is provided as a part of the context. Based on the type of event that [triggered your workflow](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/events-that-trigger-workflows), the structure of that object may change. You can cast this field to access these members in a type safe way. + +```ts +import * as core from '@actions/core' +import * as github from '@actions/github' +import * as Webhooks from '@octokit/webhooks' + +const payload = github.context.payload +if (github.context.eventName === 'push') { + const pushPayload = github.context.payload as Webhooks.WebhookPayloadPush + core.info(`The head commit is: ${pushPayload.head_commit}`) +} +``` + +There may be cases where multiple events have a field you need. For example, we can get the issue number field from the `issue` event and the `issue_comment` event +```ts +import * as core from '@actions/core' +import * as github from '@actions/github' +import * as Webhooks from '@octokit/webhooks' + +const payload = github.context.payload +if (payload && 'pull_request' in payload) { + core.info(`The issue is: ${JSON.stringify(payload.pull_request.number)}`) +} +``` + diff --git a/packages/github/__tests__/lib.test.ts b/packages/github/__tests__/lib.test.ts index 5c042f2a8c..459317eaee 100644 --- a/packages/github/__tests__/lib.test.ts +++ b/packages/github/__tests__/lib.test.ts @@ -1,8 +1,13 @@ import * as path from 'path' import {Context} from '../src/context' +import {PayloadRepository} from '@octokit/webhooks' +import {WebhookPayload} from '../src/interfaces' /* eslint-disable @typescript-eslint/no-require-imports */ +// TODO: https://github.com/actions/toolkit/issues/291, ESLint chokes on the a?.b syntax introduced in Typescript 3.7 +/* eslint-disable @typescript-eslint/no-object-literal-type-assertion */ + describe('@actions/context', () => { let context: Context @@ -16,11 +21,18 @@ describe('@actions/context', () => { expect(context.payload).toEqual(require('./payload.json')) }) - it('returns an empty payload if the GITHUB_EVENT_PATH environment variable is falsey', () => { + it('returns an undefined payload if the GITHUB_EVENT_PATH environment variable is falsey', () => { delete process.env.GITHUB_EVENT_PATH context = new Context() - expect(context.payload).toEqual({}) + expect(context.payload).toEqual(undefined) + }) + + it('returns an undefined payload if the GITHUB_EVENT_PATH environment variable does not point to a file', () => { + process.env.GITHUB_EVENT_PATH = path.join(__dirname, 'invalidfile.json') + + context = new Context() + expect(context.payload).toEqual(undefined) }) it('returns attributes from the GITHUB_REPOSITORY', () => { @@ -29,18 +41,13 @@ describe('@actions/context', () => { it('returns attributes from the repository payload', () => { delete process.env.GITHUB_REPOSITORY - - context.payload.repository = { - name: 'test', - owner: {login: 'user'} - } expect(context.repo).toEqual({owner: 'user', repo: 'test'}) }) it("return error for context.repo when repository doesn't exist", () => { delete process.env.GITHUB_REPOSITORY - - context.payload.repository = undefined + delete process.env.GITHUB_EVENT_PATH + context = new Context() expect(() => context.repo).toThrowErrorMatchingSnapshot() }) @@ -55,9 +62,10 @@ describe('@actions/context', () => { it('works with pullRequest payloads', () => { delete process.env.GITHUB_REPOSITORY context.payload = { - pullRequest: {number: 2}, - repository: {owner: {login: 'user'}, name: 'test'} - } + // eslint-disable-next-line @typescript-eslint/camelcase + pull_request: {number: 2}, + repository: {owner: {login: 'user'}, name: 'test'} as PayloadRepository + } as WebhookPayload expect(context.issue).toEqual({ number: 2, owner: 'user', @@ -69,8 +77,8 @@ describe('@actions/context', () => { delete process.env.GITHUB_REPOSITORY context.payload = { number: 2, - repository: {owner: {login: 'user'}, name: 'test'} - } + repository: {owner: {login: 'user'}, name: 'test'} as PayloadRepository + } as WebhookPayload expect(context.issue).toEqual({ number: 2, owner: 'user', diff --git a/packages/github/package-lock.json b/packages/github/package-lock.json index 6dcc35ef9f..d891b320e2 100644 --- a/packages/github/package-lock.json +++ b/packages/github/package-lock.json @@ -524,6 +524,29 @@ "@types/node": ">= 8" } }, + "@octokit/webhooks": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-7.0.0.tgz", + "integrity": "sha512-oSZuKc2LDNtt3vW7Iq9XNvIm3i6CxMkrzkuyHinR6IjVRb7EuiMshn3AVDdXdDtAVHvxwxj3ikt3jsTlFE6zEA==", + "requires": { + "debug": "^4.0.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, "@types/babel__core": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.2.tgz", diff --git a/packages/github/package.json b/packages/github/package.json index b956d42363..6d3ccbdf56 100644 --- a/packages/github/package.json +++ b/packages/github/package.json @@ -38,7 +38,8 @@ }, "dependencies": { "@octokit/graphql": "^4.3.1", - "@octokit/rest": "^16.15.0" + "@octokit/rest": "^16.15.0", + "@octokit/webhooks": "^7.0.0" }, "devDependencies": { "jest": "^24.7.1" diff --git a/packages/github/src/context.ts b/packages/github/src/context.ts index f2aafde112..3884244954 100644 --- a/packages/github/src/context.ts +++ b/packages/github/src/context.ts @@ -7,7 +7,7 @@ export class Context { /** * Webhook payload object that triggered the workflow */ - payload: WebhookPayload + payload?: WebhookPayload eventName: string sha: string @@ -20,7 +20,6 @@ export class Context { * Hydrate the context from the environment */ constructor() { - this.payload = {} if (process.env.GITHUB_EVENT_PATH) { if (existsSync(process.env.GITHUB_EVENT_PATH)) { this.payload = JSON.parse( @@ -40,11 +39,12 @@ export class Context { } get issue(): {owner: string; repo: string; number: number} { - const payload = this.payload - + // TODO: https://github.com/actions/toolkit/issues/291 to remove no-unnecessary-type-assertion + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unnecessary-type-assertion + const payload = this.payload as any return { ...this.repo, - number: (payload.issue || payload.pullRequest || payload).number + number: (payload.issue || payload.pull_request || payload).number } } @@ -54,10 +54,10 @@ export class Context { return {owner, repo} } - if (this.payload.repository) { + if (this.payload?.repository) { return { - owner: this.payload.repository.owner.login, - repo: this.payload.repository.name + owner: this.payload?.repository.owner.login, + repo: this.payload?.repository.name } } diff --git a/packages/github/src/interfaces.ts b/packages/github/src/interfaces.ts index c20aadd827..7f2f367eb3 100644 --- a/packages/github/src/interfaces.ts +++ b/packages/github/src/interfaces.ts @@ -1,39 +1,25 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ - -export interface PayloadRepository { - [key: string]: any - full_name?: string - name: string - owner: { - [key: string]: any - login: string - name?: string - } - html_url?: string -} - -export interface WebhookPayload { - [key: string]: any - repository?: PayloadRepository - issue?: { - [key: string]: any - number: number - html_url?: string - body?: string - } - pull_request?: { - [key: string]: any - number: number - html_url?: string - body?: string - } - sender?: { - [key: string]: any - type: string - } - action?: string - installation?: { - id: number - [key: string]: any - } -} +import Webhooks from '@octokit/webhooks' +export type WebhookPayload = + | Webhooks.WebhookPayloadPush + | Webhooks.WebhookPayloadPullRequest + | Webhooks.WebhookPayloadPullRequestReview + | Webhooks.WebhookPayloadPullRequestReviewComment + | Webhooks.WebhookPayloadStatus + | Webhooks.WebhookPayloadIssues + | Webhooks.WebhookPayloadIssueComment + | Webhooks.WebhookPayloadRelease + | Webhooks.WebhookPayloadRepositoryDispatch + | Webhooks.WebhookPayloadCheckRun + | Webhooks.WebhookPayloadDeployment + | Webhooks.WebhookPayloadCheckSuite + | Webhooks.WebhookPayloadWatch + | Webhooks.WebhookPayloadDeploymentStatus + | Webhooks.WebhookPayloadCreate + | Webhooks.WebhookPayloadDelete + | Webhooks.WebhookPayloadProjectCard + | Webhooks.WebhookPayloadPageBuild + | Webhooks.WebhookPayloadFork + | Webhooks.WebhookPayloadGollum + | Webhooks.WebhookPayloadMilestone + | Webhooks.WebhookPayloadProject + | Webhooks.WebhookPayloadLabel