or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

app-creation.mdcontext-events.mdcore-framework.mdgithub-api.mdindex.mdserver-middleware.md

app-creation.mddocs/

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

```