A framework for building GitHub Apps to automate and improve your workflow
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Factory functions and utilities for creating, configuring, and running Probot applications in development and production environments with support for various deployment patterns.
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!" })
);
});
});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");
});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",
});// 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
},
});// 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 keyimport { 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);
});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);// 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);
};# 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;
});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}`);
});
});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;
}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-idimport { 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}`);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();
});
});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