gristlabs_grist-core/app/server/mergedServerMain.ts
Paul Fitzpatrick f7f76fb5e7
A set of tweaks to simplify electron packaging (#421)
* Replace `ormconfig.js` with a newer mechanism of configuring
    TypeORM that can be included in the source code properly.
    The path to `ormconfig.js` has always been awkward to handle,
    and eliminating the file makes building different Grist setups
    a bit simpler.
  * Remove `electron` package. It is barely used, just for some old
    remnants of an older attempt at electron packaging. It was used
    for two types, which I left at `any` for now. More code pruning is
    no doubt possible here, but I'd rather do it when Electron packaging
    has solidified.
  * Add a hook for replacing the login system, and for adding some
    extra middleware the login system may need.
  * Add support for some more possible locations of Python, which
    arise when a standalone version of it is included in the Electron
    package. This isn't very general purpose, just configurations
    that I found useful.
  * Support using grist-core within a yarn workspace - the only tweak
    needed was webpack related.
  * Allow an external ID to be optionally associated with documents.
2023-02-13 15:52:17 -05:00

174 lines
5.1 KiB
TypeScript

/**
*
* A version of hosted grist that recombines a home server,
* a doc worker, and a static server on a single port.
*
*/
import {FlexServer, FlexServerOptions} from 'app/server/lib/FlexServer';
import {GristLoginSystem} from 'app/server/lib/GristServer';
import log from 'app/server/lib/log';
// Allowed server types. We'll start one or a combination based on the value of GRIST_SERVERS
// environment variable.
export type ServerType = "home" | "docs" | "static" | "app";
const allServerTypes: ServerType[] = ["home", "docs", "static", "app"];
// Parse a comma-separate list of server types into an array, with validation.
function parseServerTypes(serverTypes: string|undefined): ServerType[] {
// Split and filter out empty strings (including the one we get when splitting "").
const types = (serverTypes || "").trim().split(',').filter(part => Boolean(part));
// Check that parts is non-empty and only contains valid options.
if (!types.length) {
throw new Error(`No server types; should be a comma-separated list of ${allServerTypes.join(", ")}`);
}
for (const t of types) {
if (!allServerTypes.includes(t as ServerType)) {
throw new Error(`Invalid server type '${t}'; should be in ${allServerTypes.join(", ")}`);
}
}
return types as ServerType[];
}
interface ServerOptions extends FlexServerOptions {
logToConsole?: boolean; // If set, messages logged to console (default: false)
// (but if options are not given at all in call to main,
// logToConsole is set to true)
externalStorage?: boolean; // If set, documents saved to external storage such as s3 (default is to check environment
// variables, which get set in various ways in dev/test entry points)
loginSystem?: () => Promise<GristLoginSystem>;
}
/**
* Start a server on the given port, including the functionality specified in serverTypes.
*/
export async function main(port: number, serverTypes: ServerType[],
options: ServerOptions = {}) {
const includeHome = serverTypes.includes("home");
const includeDocs = serverTypes.includes("docs");
const includeStatic = serverTypes.includes("static");
const includeApp = serverTypes.includes("app");
const server = new FlexServer(port, `server(${serverTypes.join(",")})`, options);
if (options.loginSystem) {
server.setLoginSystem(options.loginSystem);
}
server.addCleanup();
server.setDirectory();
if (process.env.GRIST_TEST_ROUTER) {
// Add a mock api for adding/removing doc workers from load balancer.
server.testAddRouter();
}
if (options.logToConsole !== false) { server.addLogging(); }
if (options.externalStorage === false) { server.disableExternalStorage(); }
await server.loadConfig();
if (includeDocs) {
// It is important that /dw and /v prefixes are accepted (if present) by health check
// in this case, since they are included in the url registered for the doc worker.
server.stripDocWorkerIdPathPrefixIfPresent();
server.addTagChecker();
}
server.addHealthCheck();
if (includeHome || includeStatic || includeApp) {
server.setDirectory();
}
if (includeHome || includeStatic) {
server.addStaticAndBowerDirectories();
}
await server.initHomeDBManager();
server.addHosts();
server.addDocWorkerMap();
if (includeHome || includeStatic) {
await server.addAssetsForPlugins();
}
if (includeHome) {
server.addEarlyWebhooks();
}
if (includeHome || includeDocs || includeApp) {
server.addSessions();
}
server.addAccessMiddleware();
server.addApiMiddleware();
await server.addBillingMiddleware();
await server.start();
if (includeHome) {
server.addUsage();
if (!includeDocs) {
server.addDocApiForwarder();
}
server.addJsonSupport();
await server.addLandingPages();
// todo: add support for home api to standalone app
server.addHomeApi();
server.addBillingApi();
server.addNotifier();
await server.addHousekeeper();
await server.addLoginRoutes();
server.addAccountPage();
server.addBillingPages();
server.addWelcomePaths();
server.addLogEndpoint();
server.addGoogleAuthEndpoint();
}
if (includeDocs) {
server.addJsonSupport();
await server.addDoc();
}
if (includeHome) {
server.addClientSecrets();
}
server.finalize();
server.summary();
return server;
}
export async function startMain() {
try {
const serverTypes = parseServerTypes(process.env.GRIST_SERVERS);
// No defaults for a port, since this server can serve very different purposes.
if (!process.env.GRIST_PORT) {
throw new Error("GRIST_PORT must be specified");
}
const port = parseInt(process.env.GRIST_PORT, 10);
const server = await main(port, serverTypes);
const opt = process.argv[2];
if (opt === '--testingHooks') {
await server.addTestingHooks();
}
return server;
} catch (e) {
log.error('mergedServer failed to start', e);
process.exit(1);
}
}
if (require.main === module) {
startMain().catch((e) => log.error('mergedServer failed to start', e));
}