probot
Version:
A framework for building GitHub Apps to automate and improve your workflow
164 lines (163 loc) • 6.88 kB
JavaScript
import { exec } from "node:child_process";
import { parse as parseQuery } from "node:querystring";
import { ManifestCreation } from "../manifest-creation.js";
import { getPrintableHost } from "../helpers/get-printable-host.js";
import { isProduction } from "../helpers/is-production.js";
import { getPayload } from "../helpers/get-payload.js";
import { updateEnv } from "../helpers/update-env.js";
import { importView } from "../views/import.js";
import { setupView } from "../views/setup.js";
import { successView } from "../views/success.js";
export const setupAppFactory = (options) => {
const { host, port, log, request, SmeeClient } = options || {};
return async function setupApp() {
const setup = new ManifestCreation({
updateEnv: options.updateEnv || updateEnv,
});
const pkg = setup.pkg;
const { WEBHOOK_PROXY_URL, GHE_HOST } = process.env;
const GH_HOST = `https://${GHE_HOST ?? "github.com"}`;
// If not on Glitch or Production, create a smee URL
if (!isProduction() &&
!(process.env.PROJECT_DOMAIN ||
process.env.WEBHOOK_PROXY_URL ||
process.env.NO_SMEE_SETUP === "true")) {
await setup.createWebhookChannel({ SmeeClient, log });
}
const importViewRendered = importView({
name: pkg.name,
WEBHOOK_PROXY_URL,
GH_HOST,
});
const successViewRendered = successView({ name: pkg.name });
printWelcomeMessage(log, host, port);
const setupHandler = async (req, res) => {
const [path, query = ""] = req.url?.split("?") ?? [req.url, ""];
if (req.method === "GET") {
if (path === "/") {
res
.writeHead(302, {
"content-type": "text/plain",
location: `/probot`,
})
.end(`Found. Redirecting to /probot`);
return true;
}
if (path === "/probot") {
const baseUrl = getBaseUrl(req);
const manifest = setup.getManifest({ pkg, baseUrl });
const createAppUrl = setup.createAppUrl;
// Pass the manifest to be POST'd
res.writeHead(200, { "content-type": "text/html" }).end(setupView({
name: pkg.name,
version: pkg.version,
description: pkg.description,
createAppUrl,
manifest,
}));
return true;
}
if (path === "/probot/setup") {
const { code } =
// @ts-expect-error query could be set by a framework, e.g. express
req.query || parseQuery(query);
if (!code || typeof code !== "string" || code.length === 0) {
res
.writeHead(400, { "content-type": "text/plain" })
.end("code missing or invalid");
return true;
}
const response = await setup.createAppFromCode(code, {
request: request || {},
});
// If using glitch, restart the app
if (process.env.PROJECT_DOMAIN) {
exec("refresh", (error) => {
if (error) {
log.error(error);
}
});
}
else {
printRestartMessage(log);
}
res
.writeHead(302, {
"content-type": "text/plain",
location: `${response}/installations/new`,
})
.end(`Found. Redirecting to ${response}/installations/new`);
return true;
}
if (path === "/probot/import") {
res
.writeHead(200, {
"content-type": "text/html",
})
.end(importViewRendered);
return true;
}
if (path === "/probot/success") {
res
.writeHead(200, { "content-type": "text/html" })
.end(successViewRendered);
return true;
}
}
if (req.method === "POST") {
if (path === "/probot/import") {
const { appId, pem, webhook_secret } = JSON.parse(await getPayload(req));
if (!appId || !pem || !webhook_secret) {
res
.writeHead(400, {
"content-type": "text/plain",
})
.end("appId and/or pem and/or webhook_secret missing");
return true;
}
(options.updateEnv || updateEnv)({
APP_ID: appId,
PRIVATE_KEY: `"${pem}"`,
WEBHOOK_SECRET: webhook_secret,
});
res.end();
printRestartMessage(log);
return true;
}
}
return false;
};
return setupHandler;
};
};
function printWelcomeMessage(log, host, port) {
// use glitch env to get correct domain welcome message
// https://glitch.com/help/project/
const domain = process.env.PROJECT_DOMAIN || `http://${getPrintableHost(host)}:${port}`;
[
``,
`Welcome to Probot!`,
`Probot is in setup mode, webhooks cannot be received and`,
`custom routes will not work until APP_ID and PRIVATE_KEY`,
`are configured in .env.`,
`Please follow the instructions at ${domain} to configure .env.`,
`Once you are done, restart the server.`,
``,
].forEach((line) => {
log.info(line);
});
}
function printRestartMessage(log) {
log.info("");
log.info("Probot has been set up, please restart the server!");
log.info("");
}
function getBaseUrl(req) {
const protocols = req.headers["x-forwarded-proto"] || req.socket?.encrypted
? "https"
: "http";
const protocol = typeof protocols === "string" ? protocols.split(",")[0] : protocols[0];
const host = req.headers["x-forwarded-host"] || req.headers.host;
const baseUrl = `${protocol}://${host}`;
return baseUrl;
}