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

app-creation.mddocs/

Application Creation and Deployment

Factory functions and utilities for creating, configuring, and running Probot applications in development and production environments with support for various deployment patterns.

Capabilities

createProbot Function

Factory function for creating Probot instances with environment-based configuration and automatic private key discovery.

/**
 * Create a Probot instance with automatic configuration
 * @param options - Configuration options and overrides
 * @returns Configured Probot instance
 */
function createProbot(options?: CreateProbotOptions): Probot;

interface CreateProbotOptions {
  /** Override options (highest priority) */
  overrides?: Options;
  /** Default options (lowest priority) */
  defaults?: Options;
  /** Environment variables to use instead of process.env */
  env?: Partial<Env>;
}

Usage Examples:

import { createProbot } from "probot";

// Basic usage - reads configuration from environment variables
const app = createProbot();

// With overrides
const app = createProbot({
  overrides: {
    logLevel: "debug",
    port: 4000,
  },
});

// With defaults and custom environment
const app = createProbot({
  defaults: {
    logLevel: "info",
    secret: "development",
  },
  env: {
    APP_ID: "12345",
    PRIVATE_KEY_PATH: "./private-key.pem",
  },
});

// Load application function
await app.load((app) => {
  app.on("issues.opened", async (context) => {
    await context.octokit.issues.createComment(
      context.issue({ body: "Hello from createProbot!" })
    );
  });
});

createNodeMiddleware Function

Create Express/Node.js compatible middleware for integrating Probot into existing web applications.

/**
 * Create Node.js middleware for handling Probot webhooks and routes
 * @param appFn - Application function to handle events
 * @param options - Middleware configuration options
 * @returns Promise resolving to Express-compatible middleware
 */
function createNodeMiddleware(
  appFn: ApplicationFunction,
  options?: MiddlewareOptions
): Promise<NodeMiddleware>;

interface MiddlewareOptions {
  /** Probot instance to use */
  probot: Probot;
  /** Webhook endpoint path */
  webhooksPath?: string;
  /** Additional options */
  [key: string]: unknown;
}

type NodeMiddleware = (
  req: IncomingMessage,
  res: ServerResponse,
  next?: (err?: Error) => void
) => Promise<boolean | void>;

Usage Examples:

import express from "express";
import { createNodeMiddleware } from "probot";

const app = express();

// Create middleware with application logic
const probotMiddleware = await createNodeMiddleware((app) => {
  app.on("push", async (context) => {
    context.log.info(`Push to ${context.payload.repository.full_name}`);
  });
  
  app.on("issues.opened", async (context) => {
    await context.octokit.issues.createComment(
      context.issue({ body: "Thanks for opening an issue!" })
    );
  });
});

// Mount Probot middleware
app.use("/github", probotMiddleware);

// Add other Express routes
app.get("/", (req, res) => {
  res.json({ message: "Server is running" });
});

app.get("/health", (req, res) => {
  res.json({ status: "healthy", timestamp: new Date().toISOString() });
});

app.listen(3000, () => {
  console.log("Server running on port 3000");
});

run Function

Main entry point for running Probot applications with CLI support and automatic server setup.

/**
 * Run a Probot application with automatic setup and configuration
 * @param appFnOrArgv - Application function or CLI arguments array
 * @param additionalOptions - Additional options to merge with configuration
 * @returns Promise resolving to Server instance
 */
function run(
  appFnOrArgv: ApplicationFunction | string[],
  additionalOptions?: Partial<Options>
): Promise<Server>;

Usage Examples:

import { run } from "probot";

// Run with application function
const server = await run((app) => {
  app.on("issues.opened", async (context) => {
    const issue = context.payload.issue;
    const comment = context.issue({
      body: `Hello @${issue.user.login}, thanks for opening an issue!`,
    });
    await context.octokit.issues.createComment(comment);
  });
  
  app.on("pull_request.opened", async (context) => {
    const pr = context.payload.pull_request;
    await context.octokit.issues.createComment(
      context.issue({
        body: `Thanks for the pull request @${pr.user.login}!`,
      })
    );
  });
});

console.log(`Server started on port ${server.port}`);

// Run with CLI arguments (useful for testing)
const server = await run(
  ["node", "app.js", "--port", "4000", "--log-level", "debug"],
  {
    // Additional options
    secret: "my-webhook-secret",
  }
);

// Run with environment-based configuration
const server = await run((app) => {
  // Application logic
}, {
  port: process.env.PORT ? parseInt(process.env.PORT) : 3000,
  logLevel: process.env.NODE_ENV === "production" ? "info" : "debug",
});

Environment Configuration

Automatic Environment Loading

// createProbot automatically loads configuration from:
// 1. Environment variables (process.env)
// 2. .env files (using dotenv)
// 3. Default values
// 4. Provided overrides

// Environment variables loaded:
// - APP_ID: GitHub App ID
// - PRIVATE_KEY or PRIVATE_KEY_PATH: GitHub App private key
// - WEBHOOK_SECRET: Webhook secret for payload verification
// - LOG_LEVEL: Logging verbosity
// - LOG_FORMAT: Log output format (json|pretty)
// - PORT: HTTP server port
// - HOST: HTTP server host
// - REDIS_URL: Redis connection for clustering
// - SENTRY_DSN: Sentry error reporting
// - WEBHOOK_PROXY_URL: Smee.io proxy for development
// - GHE_HOST: GitHub Enterprise hostname
// - And more...

Usage Examples:

// .env file configuration
/*
APP_ID=12345
PRIVATE_KEY_PATH=./private-key.pem
WEBHOOK_SECRET=your-webhook-secret
LOG_LEVEL=info
PORT=3000
*/

// Automatic loading
const app = createProbot();
// Reads all configuration from environment variables

// Override specific values
const app = createProbot({
  overrides: {
    logLevel: "debug", // Override LOG_LEVEL
    port: 4000,        // Override PORT
  },
});

Private Key Discovery

// Automatic private key discovery using @probot/get-private-key:
// 1. PRIVATE_KEY environment variable (PEM format)
// 2. PRIVATE_KEY_PATH environment variable (file path)
// 3. ./private-key.pem file
// 4. Prompt user to provide key (development mode)

Usage Examples:

// Method 1: Direct PEM content
process.env.PRIVATE_KEY = `-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA...
-----END RSA PRIVATE KEY-----`;

// Method 2: File path
process.env.PRIVATE_KEY_PATH = "./keys/app-private-key.pem";

// Method 3: Default file location
// Place private key at ./private-key.pem

const app = createProbot();
// Automatically discovers and loads private key

Deployment Patterns

Standalone Server

import { run } from "probot";

// Simple standalone deployment
const server = await run((app) => {
  app.on("push", async (context) => {
    // Handle push events
  });
});

// Graceful shutdown
process.on("SIGTERM", async () => {
  await server.stop();
  process.exit(0);
});

Express Integration

import express from "express";
import { createNodeMiddleware } from "probot";

const expressApp = express();

// Add Probot middleware
const probotMiddleware = await createNodeMiddleware(
  (app) => {
    app.on("issues.opened", async (context) => {
      // Handle GitHub events
    });
  }
);

expressApp.use("/webhooks", probotMiddleware);

// Add other routes
expressApp.get("/api/status", (req, res) => {
  res.json({ status: "ok" });
});

expressApp.listen(process.env.PORT || 3000);

Serverless Deployment

// Vercel/Netlify Functions
import { createNodeMiddleware } from "probot";

const middleware = await createNodeMiddleware((app) => {
  app.on("push", async (context) => {
    // Handle events
  });
});

export default async (req, res) => {
  return middleware(req, res);
};

// AWS Lambda
import { APIGatewayProxyHandler } from "aws-lambda";
import { createNodeMiddleware } from "probot";

const middleware = await createNodeMiddleware((app) => {
  // Application logic
});

export const handler: APIGatewayProxyHandler = async (event, context) => {
  // Transform Lambda event to HTTP request format
  const req = transformLambdaEvent(event);
  const res = createResponse();
  
  await middleware(req, res);
  
  return transformResponse(res);
};

Docker Deployment

# Dockerfile
FROM node:18-alpine

WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

COPY . .
RUN npm run build

EXPOSE 3000
CMD ["npm", "start"]
// app.ts
import { run } from "probot";

const server = await run((app) => {
  app.on("issues.opened", async (context) => {
    // Application logic
  });
});

// Health check endpoint for container orchestration
server.addHandler((req, res) => {
  if (req.url === "/health") {
    res.writeHead(200, { "Content-Type": "application/json" });
    res.end(JSON.stringify({ status: "healthy" }));
    return true;
  }
  return false;
});

Clustered Deployment

import { createProbot } from "probot";
import Redis from "ioredis";

// Configure Redis for cluster coordination
const app = createProbot({
  overrides: {
    redisConfig: {
      host: process.env.REDIS_HOST,
      port: parseInt(process.env.REDIS_PORT || "6379"),
      password: process.env.REDIS_PASSWORD,
    },
  },
});

// Multiple instances will coordinate rate limiting
// and share webhook processing load
const server = await run((app) => {
  app.on("push", async (context) => {
    // This event handler can run on any instance
    context.log.info(`Processing push on instance ${process.pid}`);
  });
});

Configuration Types

interface CreateProbotOptions {
  /** Highest priority options that override all others */
  overrides?: Options;
  /** Lowest priority options used as fallbacks */
  defaults?: Options;
  /** Custom environment variables instead of process.env */
  env?: Partial<Env>;
}

interface MiddlewareOptions {
  /** Probot instance to use for handling events */
  probot: Probot;
  /** Path where webhooks should be handled */
  webhooksPath?: string;
  /** Additional custom options */
  [key: string]: unknown;
}

type Env = NodeJS.ProcessEnv;

interface Options {
  /** GitHub App private key */
  privateKey?: string;
  /** GitHub personal access token (alternative to App auth) */
  githubToken?: string;
  /** GitHub App ID */
  appId?: number | string;
  /** Custom Octokit constructor */
  Octokit?: typeof ProbotOctokit;
  /** Custom logger */
  log?: Logger;
  /** Redis configuration for clustering */
  redisConfig?: RedisOptions | string;
  /** Webhook secret for verification */
  secret?: string;
  /** Log level */
  logLevel?: "trace" | "debug" | "info" | "warn" | "error" | "fatal";
  /** Log format */
  logFormat?: "json" | "pretty";
  /** Use string log levels in JSON */
  logLevelInString?: boolean;
  /** JSON log message key */
  logMessageKey?: string;
  /** Sentry DSN for error reporting */
  sentryDsn?: string;
  /** Server port */
  port?: number;
  /** Server host */
  host?: string;
  /** Custom server instance */
  server?: Server;
  /** GitHub API base URL */
  baseUrl?: string;
  /** Request options */
  request?: RequestRequestOptions;
  /** Webhook path */
  webhookPath?: string;
  /** Webhook proxy URL */
  webhookProxy?: string;
}

type ApplicationFunction = (
  app: Probot,
  options: ApplicationFunctionOptions
) => void | Promise<void>;

interface ApplicationFunctionOptions {
  /** Current working directory */
  cwd: string;
  /** Function to add HTTP handlers */
  addHandler: (handler: Handler) => void;
  /** Additional options */
  [key: string]: unknown;
}

Development Tools

Webhook Proxy Setup

import { createProbot } from "probot";

// Automatic Smee.io proxy setup for development
const app = createProbot({
  overrides: {
    webhookProxy: "https://smee.io/your-channel-id",
  },
});

// Or use environment variable
// WEBHOOK_PROXY_URL=https://smee.io/your-channel-id

Local Development Server

import { run } from "probot";

// Development server with hot reloading
const server = await run((app) => {
  app.on("issues.opened", async (context) => {
    context.log.debug("Development: Issue opened", {
      issue: context.payload.issue.number,
      repository: context.payload.repository.full_name,
    });
  });
}, {
  logLevel: "debug",
  logFormat: "pretty",
  webhookProxy: process.env.WEBHOOK_PROXY_URL,
});

console.log(`Development server running on http://localhost:${server.port}`);

Testing Utilities

import { createProbot } from "probot";
import { describe, it, expect } from "vitest";

describe("GitHub App", () => {
  let app: Probot;
  
  beforeEach(() => {
    app = createProbot({
      overrides: {
        appId: "test-app-id",
        privateKey: "test-private-key",
        secret: "test-secret",
      },
    });
  });
  
  it("handles issues.opened events", async () => {
    const mockEvent = {
      name: "issues.opened",
      id: "test-delivery-id",
      payload: {
        action: "opened",
        issue: { number: 1, title: "Test issue" },
        repository: { full_name: "test/repo" },
      },
    };
    
    await app.receive(mockEvent);
    
    // Verify expected behavior
    expect(/* assertions */).toBeTruthy();
  });
});

Types

type Manifest = {
  name?: string;
  url: string;
  hook_attributes?: {
    url: string;
    active?: boolean;
  };
  redirect_url?: string;
  callback_urls?: string[];
  setup_url?: string;
  description?: string;
  public?: boolean;
  default_events?: WebhookEvent[];
  default_permissions?: "read-all" | "write-all" | Record<string, "read" | "write" | "none">;
  request_oauth_on_install?: boolean;
  setup_on_update?: boolean;
};

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