forked from mirrors/probot
feat: use new `Server` class when using `probot run` binary
This commit is contained in:
parent
966ea5aabe
commit
8a3599db82
|
@ -4,7 +4,9 @@ import { Options as PinoOptions } from "@probot/pino";
|
|||
|
||||
import { Options } from "../types";
|
||||
|
||||
export function readCliOptions(argv: string[]): Options & PinoOptions {
|
||||
export function readCliOptions(
|
||||
argv: string[]
|
||||
): Options & PinoOptions & { args: string[] } {
|
||||
program
|
||||
.usage("[options] <apps...>")
|
||||
.option(
|
||||
|
|
|
@ -5,6 +5,7 @@ export function readEnvOptions() {
|
|||
const privateKey = getPrivateKey();
|
||||
|
||||
return {
|
||||
args: [],
|
||||
privateKey: (privateKey && privateKey.toString()) || undefined,
|
||||
id: Number(process.env.APP_ID),
|
||||
port: Number(process.env.PORT) || 3000,
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
import pino, { LoggerOptions } from "pino";
|
||||
import { getTransformStream, Options, LogLevel } from "@probot/pino";
|
||||
|
||||
type GetLogOptions = { level?: LogLevel } & Options;
|
||||
export type GetLogOptions = { level?: LogLevel } & Options;
|
||||
|
||||
export function getLog(options: GetLogOptions = {}) {
|
||||
const deprecated = [];
|
||||
|
|
|
@ -7,6 +7,7 @@ import { Context, WebhookPayloadWithRepository } from "./context";
|
|||
import { getLog } from "./helpers/get-log";
|
||||
import { Options } from "./types";
|
||||
import { Probot } from "./probot";
|
||||
import { Server } from "./server/server";
|
||||
import { ProbotOctokit } from "./octokit/probot-octokit";
|
||||
import { run } from "./run";
|
||||
|
||||
|
@ -27,7 +28,7 @@ export const createProbot = (options: Options) => {
|
|||
return new Probot(options);
|
||||
};
|
||||
|
||||
export { Logger, Context, Application, ProbotOctokit, run, Probot };
|
||||
export { Logger, Context, Application, ProbotOctokit, run, Probot, Server };
|
||||
|
||||
/** NOTE: exported types might change at any point in time */
|
||||
export { Options, WebhookPayloadWithRepository };
|
||||
|
|
|
@ -5,6 +5,7 @@ import { Probot } from "./index";
|
|||
import { Application } from "./application";
|
||||
import { ApplicationFunction, ApplicationFunctionOptions } from "./types";
|
||||
import { getRouter } from "./get-router";
|
||||
import { resolveAppFunction } from "./helpers/resolve-app-function";
|
||||
|
||||
type DeprecatedKey =
|
||||
| "auth"
|
||||
|
@ -37,7 +38,7 @@ function bindMethod(app: Probot | Application, key: keyof Application) {
|
|||
export function load(
|
||||
app: Application | Probot,
|
||||
router: Router | null,
|
||||
appFn: ApplicationFunction | ApplicationFunction[]
|
||||
appFn: string | ApplicationFunction | ApplicationFunction[]
|
||||
) {
|
||||
const deprecatedApp = DEPRECATED_APP_KEYS.reduce(
|
||||
(api: Record<string, unknown>, key: DeprecatedKey) => {
|
||||
|
@ -64,7 +65,9 @@ export function load(
|
|||
if (Array.isArray(appFn)) {
|
||||
appFn.forEach((fn) => load(app, router, fn));
|
||||
} else {
|
||||
appFn(
|
||||
const fn = typeof appFn === "string" ? resolveAppFunction(appFn) : appFn;
|
||||
|
||||
fn(
|
||||
(Object.assign(deprecatedApp, {
|
||||
app,
|
||||
getRouter: getRouter.bind(null, router || app.router),
|
||||
|
|
|
@ -18,7 +18,6 @@ import { getRouter } from "./get-router";
|
|||
import { getWebhooks } from "./octokit/get-webhooks";
|
||||
import { load } from "./load";
|
||||
import { ProbotOctokit } from "./octokit/probot-octokit";
|
||||
import { resolveAppFunction } from "./helpers/resolve-app-function";
|
||||
import { run } from "./run";
|
||||
import { VERSION } from "./version";
|
||||
import { webhookEventCheck } from "./helpers/webhook-event-check";
|
||||
|
@ -188,10 +187,6 @@ export class Probot {
|
|||
}
|
||||
|
||||
public load(appFn: string | ApplicationFunction | ApplicationFunction[]) {
|
||||
if (typeof appFn === "string") {
|
||||
appFn = resolveAppFunction(appFn) as ApplicationFunction;
|
||||
}
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Connect the router from the app to the server
|
||||
|
@ -240,6 +235,7 @@ export class Probot {
|
|||
} else {
|
||||
this.log.error(error);
|
||||
}
|
||||
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
|
|
92
src/run.ts
92
src/run.ts
|
@ -1,54 +1,114 @@
|
|||
require("dotenv").config();
|
||||
|
||||
import pkgConf from "pkg-conf";
|
||||
import program from "commander";
|
||||
|
||||
import { ApplicationFunction } from "./types";
|
||||
import { ApplicationFunction, Options } from "./types";
|
||||
import { Probot } from "./index";
|
||||
import { setupAppFactory } from "./apps/setup";
|
||||
import { logWarningsForObsoleteEnvironmentVariables } from "./helpers/log-warnings-for-obsolete-environment-variables";
|
||||
import { getLog } from "./helpers/get-log";
|
||||
import { getLog, GetLogOptions } from "./helpers/get-log";
|
||||
import { readCliOptions } from "./bin/read-cli-options";
|
||||
import { readEnvOptions } from "./bin/read-env-options";
|
||||
import { Server, ServerOptions } from "./server/server";
|
||||
import { load } from "./load";
|
||||
import { defaultApp } from "./apps/default";
|
||||
|
||||
/**
|
||||
*
|
||||
* @param appFnOrArgv set to either a probot application function: `({ app }) => { ... }` or to process.argv
|
||||
*/
|
||||
export async function run(appFnOrArgv: ApplicationFunction | string[]) {
|
||||
const {
|
||||
// log options
|
||||
logLevel: level,
|
||||
logFormat,
|
||||
logLevelInString,
|
||||
sentryDsn,
|
||||
...options
|
||||
|
||||
// server options
|
||||
host,
|
||||
port,
|
||||
webhookPath,
|
||||
webhookProxy,
|
||||
|
||||
// probot options
|
||||
id,
|
||||
privateKey,
|
||||
redisConfig,
|
||||
secret,
|
||||
baseUrl,
|
||||
|
||||
// others
|
||||
args,
|
||||
} = Array.isArray(appFnOrArgv)
|
||||
? readCliOptions(appFnOrArgv)
|
||||
: readEnvOptions();
|
||||
|
||||
const log = getLog({ level, logFormat, logLevelInString, sentryDsn });
|
||||
const logOptions: GetLogOptions = {
|
||||
level,
|
||||
logFormat,
|
||||
logLevelInString,
|
||||
sentryDsn,
|
||||
};
|
||||
|
||||
const log = getLog(logOptions);
|
||||
logWarningsForObsoleteEnvironmentVariables(log);
|
||||
|
||||
const probot = new Probot({ log, ...options });
|
||||
const serverOptions: ServerOptions = {
|
||||
host,
|
||||
port,
|
||||
webhookPath,
|
||||
webhookProxy,
|
||||
log: log.child({ name: "server" }),
|
||||
};
|
||||
|
||||
if (!options.id || !options.privateKey) {
|
||||
const server = new Server(serverOptions);
|
||||
const router = server.router();
|
||||
|
||||
const probotOptions: Options = {
|
||||
id,
|
||||
privateKey,
|
||||
redisConfig,
|
||||
secret,
|
||||
baseUrl,
|
||||
log: log.child({ name: "probot" }),
|
||||
};
|
||||
const probot = new Probot(probotOptions);
|
||||
|
||||
if (!id || !privateKey) {
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
if (!options.id) {
|
||||
if (!id) {
|
||||
throw new Error(
|
||||
"Application ID is missing, and is required to run in production mode. " +
|
||||
"To resolve, ensure the APP_ID environment variable is set."
|
||||
);
|
||||
} else if (!options.privateKey) {
|
||||
} else if (!privateKey) {
|
||||
throw new Error(
|
||||
"Certificate is missing, and is required to run in production mode. " +
|
||||
"To resolve, ensure either the PRIVATE_KEY or PRIVATE_KEY_PATH environment variable is set and contains a valid certificate"
|
||||
);
|
||||
}
|
||||
}
|
||||
probot.load(setupAppFactory(options.host, options.port));
|
||||
load(probot, router, setupAppFactory(host, port));
|
||||
} else if (Array.isArray(appFnOrArgv)) {
|
||||
const pkg = await pkgConf("probot");
|
||||
probot.setup(program.args.concat((pkg.apps as string[]) || []));
|
||||
} else {
|
||||
probot.load(appFnOrArgv);
|
||||
}
|
||||
probot.start();
|
||||
load(probot, router, defaultApp);
|
||||
|
||||
return probot;
|
||||
if (Array.isArray(pkg.apps)) {
|
||||
for (const app of pkg.apps) {
|
||||
load(probot, router, app);
|
||||
}
|
||||
}
|
||||
|
||||
const [appFn] = args;
|
||||
|
||||
load(probot, router, appFn);
|
||||
server.app.use(probot.webhooks.middleware);
|
||||
} else {
|
||||
load(probot, router, appFnOrArgv);
|
||||
server.app.use(probot.webhooks.middleware);
|
||||
}
|
||||
await server.start();
|
||||
|
||||
return server;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
import { Server as HttpServer } from "http";
|
||||
|
||||
import express, { Application, Router } from "express";
|
||||
import { join } from "path";
|
||||
import { Logger } from "pino";
|
||||
|
||||
import { getLog } from "../helpers/get-log";
|
||||
import { getLoggingMiddleware } from "./logging-middleware";
|
||||
import { createWebhookProxy } from "../helpers/webhook-proxy";
|
||||
import { VERSION } from "../version";
|
||||
|
||||
export type ServerOptions = {
|
||||
log?: Logger;
|
||||
port?: number;
|
||||
host?: string;
|
||||
webhookPath?: string;
|
||||
webhookProxy?: string;
|
||||
};
|
||||
|
||||
type State = {
|
||||
httpServer?: HttpServer;
|
||||
port?: number;
|
||||
host?: string;
|
||||
webhookPath?: string;
|
||||
webhookProxy?: string;
|
||||
router: Router;
|
||||
};
|
||||
|
||||
export class Server {
|
||||
public app: Application;
|
||||
public log: Logger;
|
||||
public version = VERSION;
|
||||
|
||||
private state: State;
|
||||
|
||||
constructor(options: ServerOptions = {}) {
|
||||
this.app = express();
|
||||
this.log = options.log || getLog().child({ name: "server" });
|
||||
|
||||
this.state = {
|
||||
port: options.port,
|
||||
host: options.host,
|
||||
webhookPath: options.webhookPath || "/",
|
||||
webhookProxy: options.webhookProxy,
|
||||
router: Router(),
|
||||
};
|
||||
|
||||
this.app.use(getLoggingMiddleware(this.log));
|
||||
this.app.use(
|
||||
"/probot/static/",
|
||||
express.static(join(__dirname, "..", "..", "static"))
|
||||
);
|
||||
this.app.set("view engine", "hbs");
|
||||
this.app.set("views", join(__dirname, "..", "..", "views"));
|
||||
this.app.get("/ping", (req, res) => res.end("PONG"));
|
||||
this.app.use(this.state.router);
|
||||
}
|
||||
|
||||
public async start() {
|
||||
this.log.info(
|
||||
`Running Probot v${this.version} (Node.js: ${process.version})`
|
||||
);
|
||||
const port = this.state.port || 3000;
|
||||
const { host, webhookPath, webhookProxy } = this.state;
|
||||
const printableHost = host ?? "localhost";
|
||||
|
||||
this.state.httpServer = (await new Promise((resolve, reject) => {
|
||||
const server = this.app.listen(
|
||||
port,
|
||||
...((host ? [host] : []) as any),
|
||||
() => {
|
||||
if (webhookProxy) {
|
||||
createWebhookProxy({
|
||||
logger: this.log,
|
||||
path: webhookPath,
|
||||
port: port,
|
||||
url: webhookProxy,
|
||||
});
|
||||
}
|
||||
this.log.info(`Listening on http://${printableHost}:${port}`);
|
||||
resolve(server);
|
||||
}
|
||||
);
|
||||
})) as HttpServer;
|
||||
|
||||
this.state.httpServer.on("error", (error: NodeJS.ErrnoException) => {
|
||||
if (error.code === "EADDRINUSE") {
|
||||
this.log.error(
|
||||
`Port ${port} is already in use. You can define the PORT environment variable to use a different port.`
|
||||
);
|
||||
} else {
|
||||
this.log.error(error);
|
||||
}
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
return this.state.httpServer;
|
||||
}
|
||||
|
||||
public async stop() {
|
||||
if (!this.state.httpServer) return;
|
||||
const server = this.state.httpServer;
|
||||
return new Promise((resolve) => server.close(resolve));
|
||||
}
|
||||
|
||||
public router(path?: string) {
|
||||
if (path) {
|
||||
const newRouter = Router();
|
||||
this.state.router.use(path, newRouter);
|
||||
return newRouter;
|
||||
}
|
||||
|
||||
return this.state.router;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue