CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-probot

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

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

context-events.mddocs/

Context and Event Handling

The Context class provides rich context objects for GitHub webhook events, including event payloads, authenticated GitHub API clients, and helper methods for common repository operations.

Capabilities

Context Class

The main context object passed to event handlers containing event data and GitHub API access.

/**
 * Context object for GitHub webhook events
 * @template Event - Specific webhook event type for type safety
 */
class Context<Event extends WebhookEvents = WebhookEvents> {
  /** GitHub webhook event name (e.g., "issues.opened") */
  public name: WebhookEvents;
  
  /** Unique event delivery ID from GitHub */
  public id: string;
  
  /** GitHub webhook event payload */
  public payload: WebhookPayload;
  
  /** Authenticated Octokit instance for GitHub API calls */
  public octokit: ProbotOctokit;
  
  /** Scoped logger instance for this event */
  public log: Logger;
  
  /**
   * Create a new context instance
   * @param event - GitHub webhook event
   * @param octokit - Authenticated Octokit instance  
   * @param log - Logger instance
   */
  constructor(event: WebhookEvent<Event>, octokit: ProbotOctokit, log: Logger);
}

Usage Examples:

app.on("issues.opened", async (context) => {
  // Access event properties
  console.log(`Event: ${context.name}`);
  console.log(`Event ID: ${context.id}`);
  console.log(`Issue: ${context.payload.issue.title}`);
  
  // Use authenticated GitHub API
  const user = await context.octokit.users.getByUsername({
    username: context.payload.issue.user.login,
  });
  
  // Log with context
  context.log.info("Processing issue", { 
    issue: context.payload.issue.number 
  });
});

Context Properties

Key properties available on every context instance.

/**
 * True if the event was triggered by a bot account
 * Useful for avoiding infinite loops in bot interactions
 */
get isBot(): boolean;

Usage Examples:

app.on("issues.opened", async (context) => {
  // Skip processing if event was triggered by a bot
  if (context.isBot) {
    context.log.info("Skipping bot event");
    return;
  }
  
  // Process human-triggered events
  await processIssue(context);
});

Repository Helpers

Helper methods for extracting common repository parameters from event payloads.

/**
 * Extract repository owner and name from event payload
 * @param object - Optional object to merge with repo params
 * @returns Object containing owner, repo, and any additional properties
 * @throws Error if repository is not available in the event payload
 */
repo<T>(object?: T): RepoResultType<Event> & T;

/**
 * Extract issue parameters from event payload  
 * @param object - Optional object to merge with issue params
 * @returns Object containing owner, repo, issue_number, and any additional properties
 * @throws Error if issue is not available in the event payload
 */
issue<T>(object?: T): RepoResultType<Event> & { issue_number: number } & T;

/**
 * Extract pull request parameters from event payload
 * @param object - Optional object to merge with PR params  
 * @returns Object containing owner, repo, pull_number, and any additional properties
 * @throws Error if pull request is not available in the event payload
 */
pullRequest<T>(object?: T): RepoResultType<Event> & { pull_number: number } & T;

Usage Examples:

app.on("issues.opened", async (context) => {
  // Get repository parameters
  const repoParams = context.repo();
  // { owner: "octocat", repo: "Hello-World" }
  
  // Get issue parameters with additional data
  const issueComment = context.issue({
    body: "Thanks for opening this issue!",
  });
  // { owner: "octocat", repo: "Hello-World", issue_number: 1, body: "Thanks..." }
  
  // Create comment using helper
  await context.octokit.issues.createComment(issueComment);
});

app.on("pull_request.opened", async (context) => {
  // Get pull request parameters
  const prParams = context.pullRequest({
    body: "Thanks for the contribution!",
  });
  // { owner: "octocat", repo: "Hello-World", pull_number: 1, body: "Thanks..." }
  
  // Create PR comment
  await context.octokit.issues.createComment(prParams);
});

app.on("push", async (context) => {
  // Get repository info for push events
  const repoInfo = context.repo({
    ref: "refs/heads/main",
  });
  
  // Get branch information
  const branch = await context.octokit.repos.getBranch(repoInfo);
});

Configuration Management

Method for reading configuration files from the repository's .github directory.

/**
 * Read and parse configuration file from .github directory
 * @param fileName - Name of config file (without .yml/.yaml extension)
 * @param defaultConfig - Default configuration to use if file doesn't exist
 * @param deepMergeOptions - Options for deep merging default and file config
 * @returns Parsed configuration object or null if file doesn't exist and no default
 */
async config<T>(
  fileName: string,
  defaultConfig?: T,
  deepMergeOptions?: MergeOptions
): Promise<T | null>;

Usage Examples:

// Define configuration type
interface BotConfig {
  welcomeMessage: string;
  autoAssign: string[];
  labels: {
    bug: string;
    enhancement: string;
  };
}

app.on("issues.opened", async (context) => {
  // Read config with defaults
  const config = await context.config<BotConfig>("bot", {
    welcomeMessage: "Welcome to the project!",
    autoAssign: [],
    labels: {
      bug: "bug",
      enhancement: "enhancement",
    },
  });
  
  if (config) {
    // Use configuration
    await context.octokit.issues.createComment(
      context.issue({ body: config.welcomeMessage })
    );
    
    // Auto-assign if configured
    if (config.autoAssign.length > 0) {
      await context.octokit.issues.addAssignees(
        context.issue({ assignees: config.autoAssign })
      );
    }
  }
});

// Configuration file at .github/bot.yml:
// welcomeMessage: "Thanks for opening an issue!"
// autoAssign: 
//   - maintainer1
//   - maintainer2
// labels:
//   bug: "🐛 bug"
//   enhancement: "✨ enhancement"

Event Types and Payloads

Common Event Names

// Issue events
"issues.opened" | "issues.closed" | "issues.edited" | "issues.assigned" | 
"issues.unassigned" | "issues.labeled" | "issues.unlabeled" | 
"issues.milestoned" | "issues.demilestoned" | "issues.reopened" | "issues.transferred"

// Pull request events  
"pull_request.opened" | "pull_request.closed" | "pull_request.edited" |
"pull_request.assigned" | "pull_request.unassigned" | "pull_request.review_requested" |
"pull_request.review_request_removed" | "pull_request.labeled" | "pull_request.unlabeled" |
"pull_request.synchronize" | "pull_request.reopened" | "pull_request.ready_for_review" |
"pull_request.converted_to_draft"

// Repository events
"push" | "repository.created" | "repository.deleted" | "repository.archived" |
"repository.unarchived" | "repository.publicized" | "repository.privatized"

// Installation events
"installation.created" | "installation.deleted" | "installation_repositories.added" |
"installation_repositories.removed"

// Other common events
"check_run" | "check_suite" | "commit_comment" | "create" | "delete" | "deployment" |
"deployment_status" | "fork" | "gollum" | "issue_comment" | "member" | "milestone" |
"page_build" | "project" | "project_card" | "project_column" | "public" | "pull_request_review" |
"pull_request_review_comment" | "release" | "status" | "team" | "watch"

Payload Structure Examples

// Issue event payload structure
interface IssuePayload {
  action: "opened" | "closed" | "edited" | "assigned" | "unassigned" | "labeled" | "unlabeled";
  issue: {
    id: number;
    number: number;
    title: string;
    body: string;
    user: User;
    state: "open" | "closed";
    labels: Label[];
    assignees: User[];
    milestone: Milestone | null;
    created_at: string;
    updated_at: string;
  };
  repository: Repository;
  sender: User;
}

// Pull request event payload structure
interface PullRequestPayload {
  action: "opened" | "closed" | "edited" | "assigned" | "unassigned" | "synchronize";
  number: number;
  pull_request: {
    id: number;
    number: number;
    title: string;
    body: string;
    user: User;
    state: "open" | "closed";
    merged: boolean;
    head: {
      ref: string;
      sha: string;
      repo: Repository;
    };
    base: {
      ref: string;
      sha: string;
      repo: Repository;
    };
    created_at: string;
    updated_at: string;
  };
  repository: Repository;
  sender: User;
}

// Push event payload structure  
interface PushPayload {
  ref: string;
  before: string;
  after: string;
  commits: Commit[];
  head_commit: Commit | null;
  repository: Repository;
  pusher: User;
  sender: User;
}

Helper Types

type WebhookEvents = string; // Actual webhook event names from @octokit/webhooks
type WebhookPayload = any; // Actual payload types from @octokit/webhooks-types

type RepoResultType<E extends WebhookEvents> = {
  owner: string;
  repo: string;
};

type MergeOptions = {
  /** How arrays should be merged */
  arrayMerge?: (target: any[], source: any[], options: MergeOptions) => any[];
  /** Whether to clone values */
  clone?: boolean;
  /** Custom merge function */
  customMerge?: (key: string, options: MergeOptions) => ((target: any, source: any) => any) | undefined;
};

interface User {
  id: number;
  login: string;
  avatar_url: string;
  type: "User" | "Bot";
}

interface Repository {
  id: number;
  name: string;
  full_name: string;
  owner: User;
  private: boolean;
  description: string | null;
  default_branch: string;
}

interface Label {
  id: number;
  name: string;
  color: string;
  description: string | null;
}

interface Milestone {
  id: number;
  number: number;
  title: string;
  description: string | null;
  state: "open" | "closed";
  due_on: string | null;
}

interface Commit {
  id: string;
  message: string;
  author: {
    name: string;
    email: string;
    username?: string;
  };
  url: string;
  distinct: boolean;
  added: string[];
  removed: string[];
  modified: string[];
}

Install with Tessl CLI

npx tessl i tessl/npm-probot

docs

app-creation.md

context-events.md

core-framework.md

github-api.md

index.md

server-middleware.md

tile.json