UNPKG

probot

Version:

A framework for building GitHub Apps to automate and improve your workflow

184 lines (183 loc) 7.04 kB
import path from "node:path"; import merge from "deepmerge"; const kOctokitRequestHookAdded = Symbol("octokit request hook added"); /** * The context of the event that was triggered, including the payload and * helpers for extracting information can be passed to GitHub API calls. * * ```js * export default app => { * app.on('push', context => { * context.log.info('Code was pushed to the repo, what should we do with it?'); * }); * }; * ``` */ export class Context { name; id; payload; /** * An authenticated Octokit instance that can be used to make GitHub API * requests in the context of the webhook event. */ octokit; /** * A logger with context about the event. */ log; constructor(event, octokit, log) { this.name = event.name; this.id = event.id; this.payload = event.payload; this.octokit = octokit; this.log = log; // set `x-github-delivery` header on all requests sent in response to the current // event. This allows GitHub Support to correlate the request with the event. // This is not documented and not considered public API, the header may change. // Once we document this as best practice on https://docs.github.com/en/rest/guides/best-practices-for-integrators // we will make it official if (octokit[kOctokitRequestHookAdded] !== true) { /* istanbul ignore next */ octokit.hook.before("request", (options) => { options.headers["x-github-delivery"] = event.id; }); octokit[kOctokitRequestHookAdded] = true; } } /** * Return the `owner` and `repo` params for making API requests against a * repository. * * ```js * const params = context.repo({path: '.github/config.yml'}) * // Returns: {owner: 'username', repo: 'reponame', path: '.github/config.yml'} * ``` * * @param object - Params to be merged with the repo params. * */ repo(object) { // @ts-expect-error `repository` is not always present in this.payload const repo = this.payload.repository; if (!repo) { throw new Error("context.repo() is not supported for this webhook event."); } return Object.assign({ owner: repo.owner.login, repo: repo.name, }, object); } /** * Return the `owner`, `repo`, and `issue_number` params for making API requests * against an issue. The object passed in will be merged with the repo params. * * * ```js * const params = context.issue({body: 'Hello World!'}) * // Returns: {owner: 'username', repo: 'reponame', issue_number: 123, body: 'Hello World!'} * ``` * * @param object - Params to be merged with the issue params. */ issue(object) { return Object.assign({ issue_number: // @ts-expect-error - this.payload may not have `issue` or `pull_request` keys (this.payload.issue || this.payload.pull_request || this.payload) .number, }, this.repo(object)); } /** * Return the `owner`, `repo`, and `pull_number` params for making API requests * against a pull request. The object passed in will be merged with the repo params. * * * ```js * const params = context.pullRequest({body: 'Hello World!'}) * // Returns: {owner: 'username', repo: 'reponame', pull_number: 123, body: 'Hello World!'} * ``` * * @param object - Params to be merged with the pull request params. */ pullRequest(object) { const payload = this.payload; return Object.assign({ // @ts-expect-error - this.payload may not have `issue` or `pull_request` keys pull_number: (payload.issue || payload.pull_request || payload).number, }, this.repo(object)); } /** * Returns a boolean if the actor on the event was a bot. * @type {boolean} */ get isBot() { // `sender` key is not present in all events // see https://github.com/octokit/webhooks/issues/510 return this.payload.sender?.type === "Bot"; } /** * Reads the app configuration from the given YAML file in the `.github` * directory of the repository. * * For example, given a file named `.github/config.yml`: * * ```yml * close: true * comment: Check the specs on the rotary girder. * ``` * * Your app can read that file from the target repository: * * ```js * // Load config from .github/config.yml in the repository * const config = await context.config('config.yml') * * if (config.close) { * context.octokit.rest.issues.comment(context.issue({body: config.comment})) * context.octokit.rest.issues.edit(context.issue({state: 'closed'})) * } * ``` * * You can also use a `defaultConfig` object: * * ```js * // Load config from .github/config.yml in the repository and combine with default config * const config = await context.config('config.yml', {comment: 'Make sure to check all the specs.'}) * * if (config.close) { * context.octokit.rest.issues.comment(context.issue({body: config.comment})); * context.octokit.rest.issues.edit(context.issue({state: 'closed'})) * } * ``` * * Config files can also specify a base that they extend. `deepMergeOptions` can be used * to configure how the target config, extended base, and default configs are merged. * * For security reasons, configuration is only loaded from the repository's default branch, * changes made in pull requests from different branches or forks are ignored. * * If you need more lower-level control over reading and merging configuration files, * you can `context.octokit.config.get(options)`, see https://github.com/probot/octokit-plugin-config. * * @param fileName - Name of the YAML file in the `.github` directory * @param defaultConfig - An object of default config options * @param deepMergeOptions - Controls merging configs (from the [deepmerge](https://github.com/TehShrike/deepmerge) module) * @return Configuration object read from the file */ async config(fileName, defaultConfig, deepMergeOptions) { const params = this.repo({ path: path.posix.join(".github", fileName), defaults(configs) { const result = merge.all([defaultConfig || {}, ...configs], deepMergeOptions); return result; }, }); const { config, files } = await this.octokit.config.get(params); // if no default config is set, and no config files are found, return null if (!defaultConfig && !files.find((file) => file.config !== null)) { return null; } return config; } }