0
# Application Creation and Deployment
1
2
Factory functions and utilities for creating, configuring, and running Probot applications in development and production environments with support for various deployment patterns.
3
4
## Capabilities
5
6
### createProbot Function
7
8
Factory function for creating Probot instances with environment-based configuration and automatic private key discovery.
9
10
```typescript { .api }
11
/**
12
* Create a Probot instance with automatic configuration
13
* @param options - Configuration options and overrides
14
* @returns Configured Probot instance
15
*/
16
function createProbot(options?: CreateProbotOptions): Probot;
17
18
interface CreateProbotOptions {
19
/** Override options (highest priority) */
20
overrides?: Options;
21
/** Default options (lowest priority) */
22
defaults?: Options;
23
/** Environment variables to use instead of process.env */
24
env?: Partial<Env>;
25
}
26
```
27
28
**Usage Examples:**
29
30
```typescript
31
import { createProbot } from "probot";
32
33
// Basic usage - reads configuration from environment variables
34
const app = createProbot();
35
36
// With overrides
37
const app = createProbot({
38
overrides: {
39
logLevel: "debug",
40
port: 4000,
41
},
42
});
43
44
// With defaults and custom environment
45
const app = createProbot({
46
defaults: {
47
logLevel: "info",
48
secret: "development",
49
},
50
env: {
51
APP_ID: "12345",
52
PRIVATE_KEY_PATH: "./private-key.pem",
53
},
54
});
55
56
// Load application function
57
await app.load((app) => {
58
app.on("issues.opened", async (context) => {
59
await context.octokit.issues.createComment(
60
context.issue({ body: "Hello from createProbot!" })
61
);
62
});
63
});
64
```
65
66
### createNodeMiddleware Function
67
68
Create Express/Node.js compatible middleware for integrating Probot into existing web applications.
69
70
```typescript { .api }
71
/**
72
* Create Node.js middleware for handling Probot webhooks and routes
73
* @param appFn - Application function to handle events
74
* @param options - Middleware configuration options
75
* @returns Promise resolving to Express-compatible middleware
76
*/
77
function createNodeMiddleware(
78
appFn: ApplicationFunction,
79
options?: MiddlewareOptions
80
): Promise<NodeMiddleware>;
81
82
interface MiddlewareOptions {
83
/** Probot instance to use */
84
probot: Probot;
85
/** Webhook endpoint path */
86
webhooksPath?: string;
87
/** Additional options */
88
[key: string]: unknown;
89
}
90
91
type NodeMiddleware = (
92
req: IncomingMessage,
93
res: ServerResponse,
94
next?: (err?: Error) => void
95
) => Promise<boolean | void>;
96
```
97
98
**Usage Examples:**
99
100
```typescript
101
import express from "express";
102
import { createNodeMiddleware } from "probot";
103
104
const app = express();
105
106
// Create middleware with application logic
107
const probotMiddleware = await createNodeMiddleware((app) => {
108
app.on("push", async (context) => {
109
context.log.info(`Push to ${context.payload.repository.full_name}`);
110
});
111
112
app.on("issues.opened", async (context) => {
113
await context.octokit.issues.createComment(
114
context.issue({ body: "Thanks for opening an issue!" })
115
);
116
});
117
});
118
119
// Mount Probot middleware
120
app.use("/github", probotMiddleware);
121
122
// Add other Express routes
123
app.get("/", (req, res) => {
124
res.json({ message: "Server is running" });
125
});
126
127
app.get("/health", (req, res) => {
128
res.json({ status: "healthy", timestamp: new Date().toISOString() });
129
});
130
131
app.listen(3000, () => {
132
console.log("Server running on port 3000");
133
});
134
```
135
136
### run Function
137
138
Main entry point for running Probot applications with CLI support and automatic server setup.
139
140
```typescript { .api }
141
/**
142
* Run a Probot application with automatic setup and configuration
143
* @param appFnOrArgv - Application function or CLI arguments array
144
* @param additionalOptions - Additional options to merge with configuration
145
* @returns Promise resolving to Server instance
146
*/
147
function run(
148
appFnOrArgv: ApplicationFunction | string[],
149
additionalOptions?: Partial<Options>
150
): Promise<Server>;
151
```
152
153
**Usage Examples:**
154
155
```typescript
156
import { run } from "probot";
157
158
// Run with application function
159
const server = await run((app) => {
160
app.on("issues.opened", async (context) => {
161
const issue = context.payload.issue;
162
const comment = context.issue({
163
body: `Hello @${issue.user.login}, thanks for opening an issue!`,
164
});
165
await context.octokit.issues.createComment(comment);
166
});
167
168
app.on("pull_request.opened", async (context) => {
169
const pr = context.payload.pull_request;
170
await context.octokit.issues.createComment(
171
context.issue({
172
body: `Thanks for the pull request @${pr.user.login}!`,
173
})
174
);
175
});
176
});
177
178
console.log(`Server started on port ${server.port}`);
179
180
// Run with CLI arguments (useful for testing)
181
const server = await run(
182
["node", "app.js", "--port", "4000", "--log-level", "debug"],
183
{
184
// Additional options
185
secret: "my-webhook-secret",
186
}
187
);
188
189
// Run with environment-based configuration
190
const server = await run((app) => {
191
// Application logic
192
}, {
193
port: process.env.PORT ? parseInt(process.env.PORT) : 3000,
194
logLevel: process.env.NODE_ENV === "production" ? "info" : "debug",
195
});
196
```
197
198
## Environment Configuration
199
200
### Automatic Environment Loading
201
202
```typescript { .api }
203
// createProbot automatically loads configuration from:
204
// 1. Environment variables (process.env)
205
// 2. .env files (using dotenv)
206
// 3. Default values
207
// 4. Provided overrides
208
209
// Environment variables loaded:
210
// - APP_ID: GitHub App ID
211
// - PRIVATE_KEY or PRIVATE_KEY_PATH: GitHub App private key
212
// - WEBHOOK_SECRET: Webhook secret for payload verification
213
// - LOG_LEVEL: Logging verbosity
214
// - LOG_FORMAT: Log output format (json|pretty)
215
// - PORT: HTTP server port
216
// - HOST: HTTP server host
217
// - REDIS_URL: Redis connection for clustering
218
// - SENTRY_DSN: Sentry error reporting
219
// - WEBHOOK_PROXY_URL: Smee.io proxy for development
220
// - GHE_HOST: GitHub Enterprise hostname
221
// - And more...
222
```
223
224
**Usage Examples:**
225
226
```typescript
227
// .env file configuration
228
/*
229
APP_ID=12345
230
PRIVATE_KEY_PATH=./private-key.pem
231
WEBHOOK_SECRET=your-webhook-secret
232
LOG_LEVEL=info
233
PORT=3000
234
*/
235
236
// Automatic loading
237
const app = createProbot();
238
// Reads all configuration from environment variables
239
240
// Override specific values
241
const app = createProbot({
242
overrides: {
243
logLevel: "debug", // Override LOG_LEVEL
244
port: 4000, // Override PORT
245
},
246
});
247
```
248
249
### Private Key Discovery
250
251
```typescript { .api }
252
// Automatic private key discovery using @probot/get-private-key:
253
// 1. PRIVATE_KEY environment variable (PEM format)
254
// 2. PRIVATE_KEY_PATH environment variable (file path)
255
// 3. ./private-key.pem file
256
// 4. Prompt user to provide key (development mode)
257
```
258
259
**Usage Examples:**
260
261
```typescript
262
// Method 1: Direct PEM content
263
process.env.PRIVATE_KEY = `-----BEGIN RSA PRIVATE KEY-----
264
MIIEpAIBAAKCAQEA...
265
-----END RSA PRIVATE KEY-----`;
266
267
// Method 2: File path
268
process.env.PRIVATE_KEY_PATH = "./keys/app-private-key.pem";
269
270
// Method 3: Default file location
271
// Place private key at ./private-key.pem
272
273
const app = createProbot();
274
// Automatically discovers and loads private key
275
```
276
277
## Deployment Patterns
278
279
### Standalone Server
280
281
```typescript
282
import { run } from "probot";
283
284
// Simple standalone deployment
285
const server = await run((app) => {
286
app.on("push", async (context) => {
287
// Handle push events
288
});
289
});
290
291
// Graceful shutdown
292
process.on("SIGTERM", async () => {
293
await server.stop();
294
process.exit(0);
295
});
296
```
297
298
### Express Integration
299
300
```typescript
301
import express from "express";
302
import { createNodeMiddleware } from "probot";
303
304
const expressApp = express();
305
306
// Add Probot middleware
307
const probotMiddleware = await createNodeMiddleware(
308
(app) => {
309
app.on("issues.opened", async (context) => {
310
// Handle GitHub events
311
});
312
}
313
);
314
315
expressApp.use("/webhooks", probotMiddleware);
316
317
// Add other routes
318
expressApp.get("/api/status", (req, res) => {
319
res.json({ status: "ok" });
320
});
321
322
expressApp.listen(process.env.PORT || 3000);
323
```
324
325
### Serverless Deployment
326
327
```typescript
328
// Vercel/Netlify Functions
329
import { createNodeMiddleware } from "probot";
330
331
const middleware = await createNodeMiddleware((app) => {
332
app.on("push", async (context) => {
333
// Handle events
334
});
335
});
336
337
export default async (req, res) => {
338
return middleware(req, res);
339
};
340
341
// AWS Lambda
342
import { APIGatewayProxyHandler } from "aws-lambda";
343
import { createNodeMiddleware } from "probot";
344
345
const middleware = await createNodeMiddleware((app) => {
346
// Application logic
347
});
348
349
export const handler: APIGatewayProxyHandler = async (event, context) => {
350
// Transform Lambda event to HTTP request format
351
const req = transformLambdaEvent(event);
352
const res = createResponse();
353
354
await middleware(req, res);
355
356
return transformResponse(res);
357
};
358
```
359
360
### Docker Deployment
361
362
```dockerfile
363
# Dockerfile
364
FROM node:18-alpine
365
366
WORKDIR /app
367
COPY package*.json ./
368
RUN npm ci --only=production
369
370
COPY . .
371
RUN npm run build
372
373
EXPOSE 3000
374
CMD ["npm", "start"]
375
```
376
377
```typescript
378
// app.ts
379
import { run } from "probot";
380
381
const server = await run((app) => {
382
app.on("issues.opened", async (context) => {
383
// Application logic
384
});
385
});
386
387
// Health check endpoint for container orchestration
388
server.addHandler((req, res) => {
389
if (req.url === "/health") {
390
res.writeHead(200, { "Content-Type": "application/json" });
391
res.end(JSON.stringify({ status: "healthy" }));
392
return true;
393
}
394
return false;
395
});
396
```
397
398
### Clustered Deployment
399
400
```typescript
401
import { createProbot } from "probot";
402
import Redis from "ioredis";
403
404
// Configure Redis for cluster coordination
405
const app = createProbot({
406
overrides: {
407
redisConfig: {
408
host: process.env.REDIS_HOST,
409
port: parseInt(process.env.REDIS_PORT || "6379"),
410
password: process.env.REDIS_PASSWORD,
411
},
412
},
413
});
414
415
// Multiple instances will coordinate rate limiting
416
// and share webhook processing load
417
const server = await run((app) => {
418
app.on("push", async (context) => {
419
// This event handler can run on any instance
420
context.log.info(`Processing push on instance ${process.pid}`);
421
});
422
});
423
```
424
425
## Configuration Types
426
427
```typescript { .api }
428
interface CreateProbotOptions {
429
/** Highest priority options that override all others */
430
overrides?: Options;
431
/** Lowest priority options used as fallbacks */
432
defaults?: Options;
433
/** Custom environment variables instead of process.env */
434
env?: Partial<Env>;
435
}
436
437
interface MiddlewareOptions {
438
/** Probot instance to use for handling events */
439
probot: Probot;
440
/** Path where webhooks should be handled */
441
webhooksPath?: string;
442
/** Additional custom options */
443
[key: string]: unknown;
444
}
445
446
type Env = NodeJS.ProcessEnv;
447
448
interface Options {
449
/** GitHub App private key */
450
privateKey?: string;
451
/** GitHub personal access token (alternative to App auth) */
452
githubToken?: string;
453
/** GitHub App ID */
454
appId?: number | string;
455
/** Custom Octokit constructor */
456
Octokit?: typeof ProbotOctokit;
457
/** Custom logger */
458
log?: Logger;
459
/** Redis configuration for clustering */
460
redisConfig?: RedisOptions | string;
461
/** Webhook secret for verification */
462
secret?: string;
463
/** Log level */
464
logLevel?: "trace" | "debug" | "info" | "warn" | "error" | "fatal";
465
/** Log format */
466
logFormat?: "json" | "pretty";
467
/** Use string log levels in JSON */
468
logLevelInString?: boolean;
469
/** JSON log message key */
470
logMessageKey?: string;
471
/** Sentry DSN for error reporting */
472
sentryDsn?: string;
473
/** Server port */
474
port?: number;
475
/** Server host */
476
host?: string;
477
/** Custom server instance */
478
server?: Server;
479
/** GitHub API base URL */
480
baseUrl?: string;
481
/** Request options */
482
request?: RequestRequestOptions;
483
/** Webhook path */
484
webhookPath?: string;
485
/** Webhook proxy URL */
486
webhookProxy?: string;
487
}
488
489
type ApplicationFunction = (
490
app: Probot,
491
options: ApplicationFunctionOptions
492
) => void | Promise<void>;
493
494
interface ApplicationFunctionOptions {
495
/** Current working directory */
496
cwd: string;
497
/** Function to add HTTP handlers */
498
addHandler: (handler: Handler) => void;
499
/** Additional options */
500
[key: string]: unknown;
501
}
502
```
503
504
## Development Tools
505
506
### Webhook Proxy Setup
507
508
```typescript
509
import { createProbot } from "probot";
510
511
// Automatic Smee.io proxy setup for development
512
const app = createProbot({
513
overrides: {
514
webhookProxy: "https://smee.io/your-channel-id",
515
},
516
});
517
518
// Or use environment variable
519
// WEBHOOK_PROXY_URL=https://smee.io/your-channel-id
520
```
521
522
### Local Development Server
523
524
```typescript
525
import { run } from "probot";
526
527
// Development server with hot reloading
528
const server = await run((app) => {
529
app.on("issues.opened", async (context) => {
530
context.log.debug("Development: Issue opened", {
531
issue: context.payload.issue.number,
532
repository: context.payload.repository.full_name,
533
});
534
});
535
}, {
536
logLevel: "debug",
537
logFormat: "pretty",
538
webhookProxy: process.env.WEBHOOK_PROXY_URL,
539
});
540
541
console.log(`Development server running on http://localhost:${server.port}`);
542
```
543
544
### Testing Utilities
545
546
```typescript
547
import { createProbot } from "probot";
548
import { describe, it, expect } from "vitest";
549
550
describe("GitHub App", () => {
551
let app: Probot;
552
553
beforeEach(() => {
554
app = createProbot({
555
overrides: {
556
appId: "test-app-id",
557
privateKey: "test-private-key",
558
secret: "test-secret",
559
},
560
});
561
});
562
563
it("handles issues.opened events", async () => {
564
const mockEvent = {
565
name: "issues.opened",
566
id: "test-delivery-id",
567
payload: {
568
action: "opened",
569
issue: { number: 1, title: "Test issue" },
570
repository: { full_name: "test/repo" },
571
},
572
};
573
574
await app.receive(mockEvent);
575
576
// Verify expected behavior
577
expect(/* assertions */).toBeTruthy();
578
});
579
});
580
```
581
582
## Types
583
584
```typescript { .api }
585
type Manifest = {
586
name?: string;
587
url: string;
588
hook_attributes?: {
589
url: string;
590
active?: boolean;
591
};
592
redirect_url?: string;
593
callback_urls?: string[];
594
setup_url?: string;
595
description?: string;
596
public?: boolean;
597
default_events?: WebhookEvent[];
598
default_permissions?: "read-all" | "write-all" | Record<string, "read" | "write" | "none">;
599
request_oauth_on_install?: boolean;
600
setup_on_update?: boolean;
601
};
602
```