import config from './postgraphilerc' ; import * as os from 'os' ; import { createServer } from 'http' ; import chalk from 'chalk' ; import program = require ( 'commander' ) ; import jwt = require ( 'jsonwebtoken' ) ; import { parse as parsePgConnectionString } from 'pg-connection-string' ; import postgraphile , { getPostgraphileSchemaBuilder } from './postgraphile' ; import { makePgSmartTagsFromFilePlugin } from '../plugins' ; import { Pool , PoolConfig } from 'pg' ; import cluster = require ( 'cluster' ) ; import { makePluginHook , PostGraphilePlugin } from './pluginHook' ; import debugFactory = require ( 'debug' ) ; import { mixed } from '../interfaces' ; import * as manifest from '../../package.json' ; import sponsors = require ( '../../sponsors.json' ) ; import { enhanceHttpServerWithWebSockets } from './http/subscriptions' ; import { existsSync } from 'fs' ; const tagsFile = process . cwd ( ) + '/postgraphile.tags.json5' ; const smartTagsPlugin = existsSync ( tagsFile ) ? makePgSmartTagsFromFilePlugin ( ) : null ; const isDev = process . env . POSTGRAPHILE_ENV === 'development' ; function isString ( str ) { return typeof str === 'string' ; } const sponsor = sponsors [ Math . floor ( sponsors . length * Math . random ( ) ) ] ; const debugCli = debugFactory ( 'postgraphile:cli' ) ; const DEMO_PG_URL = null ; function extractPlugins ( rawArgv ) { let argv ; let pluginStrings = [ ] ; if ( rawArgv [ 2 ] === '--plugins' ) { pluginStrings = rawArgv [ 3 ] . split ( ',' ) ; argv = [ ... rawArgv . slice ( 0 , 2 ) , ... rawArgv . slice ( 4 ) ] ; } else { pluginStrings = ( config && config [ 'options' ] && config [ 'options' ] [ 'plugins' ] ) || [ ] ; argv = rawArgv ; } const plugins = pluginStrings . map ( ( pluginString ) => { debugCli ( 'Loading plugin %s' , pluginString ) ; const rawPlugin = require ( pluginString ) ; if ( rawPlugin [ 'default' ] && typeof rawPlugin [ 'default' ] === 'object' ) { return rawPlugin [ 'default' ] ; } else { return rawPlugin ; } } ) ; return { argv , plugins } ; } const { argv : argvSansPlugins , plugins : extractedPlugins } = extractPlugins ( process . argv ) ; const pluginHook = makePluginHook ( extractedPlugins ) ; program . version ( manifest . version ) . usage ( '[options...]' ) . description ( manifest . description ) ; export type AddFlagFn = ( optionString : string , description : string , parse ? : ( option : string ) => mixed ) => AddFlagFn ; function addFlag ( optionString , description , parse ? ) { program . option ( optionString , description , parse ) ; return addFlag ; } program . option ( '--plugins <string>' , 'a list of PostGraphile server plugins (not Graphile Engine schema plugins) to load; if present, must be the _first_ option' ) . option ( '-c, --connection <string>' , "the PostgreSQL database name or connection string. If omitted, inferred from environmental variables (see https://www.postgresql.org/docs/current/static/libpq-envars.html). Examples: 'db', 'postgres:///db', 'postgres://user:password@domain:port/db?ssl=true'" ) . option ( '-C, --owner-connection <string>' , 'as `--connection`, but for a privileged user (e.g. for setting up watch fixtures, logical decoding, etc); defaults to the value from `--connection`' ) . option ( '-s, --schema <string>' , 'a Postgres schema to be introspected. Use commas to define multiple schemas' , ( option ) => option . split ( ',' ) ) . option ( '-S, --subscriptions' , 'Enable GraphQL support for subscriptions (you still need a subscriptions plugin currently)' ) . option ( '--websockets <string>' , "Choose which websocket transport libraries to use. Use commas to define multiple. Defaults to 'v0,v1' if `--subscriptions` or `--live` were passed, '[]' otherwise" , ( option ) => option . split ( ',' ) ) . option ( '--websocket-operations <operations>' , "Toggle which GraphQL websocket transport operations are supported: 'subscriptions' or 'all'. Defaults to 'subscriptions'" ) . option ( '-L, --live' , '[EXPERIMENTAL] Enables live-query support via GraphQL subscriptions (sends updated payload any time nested collections/records change). Implies --subscriptions' ) . option ( '-w, --watch' , 'automatically updates your GraphQL schema when your database schema changes (NOTE: requires DB superuser to install `postgraphile_watch` schema)' ) . option ( '-n, --host <string>' , 'the hostname to be used. Defaults to `localhost`' ) . option ( '-p, --port <number>' , 'the port to be used. Defaults to 5000' , parseFloat ) . option ( '-m, --max-pool-size <number>' , 'the maximum number of clients to keep in the Postgres pool. defaults to 10' , parseFloat ) . option ( '-r, --default-role <string>' , 'the default Postgres role to use when a request is made. supercedes the role used to connect to the database' ) . option ( '--retry-on-init-fail' , 'if an error occurs building the initial schema, this flag will cause PostGraphile to keep trying to build the schema with exponential backoff rather than exiting' ) ; pluginHook ( 'cli:flags:add:standard' , addFlag ) ; program . option ( '-j, --dynamic-json' , '[RECOMMENDED] enable dynamic JSON in GraphQL inputs and outputs. PostGraphile uses stringified JSON by default' ) . option ( '-N, --no-setof-functions-contain-nulls' , '[RECOMMENDED] if none of your `RETURNS SETOF compound_type` functions mix NULLs with the results then you may enable this to reduce the nullables in the GraphQL schema' ) . option ( '-a, --classic-ids' , 'use classic global id field name. required to support Relay 1' ) . option ( '-M, --disable-default-mutations' , 'disable default mutations, mutation will only be possible through Postgres functions' ) . option ( '--simple-collections <omit|both|only>' , '"omit" (default) - relay connections only, "only" - simple collections only (no Relay connections), "both" - both' ) . option ( '--no-ignore-rbac' , '[RECOMMENDED] set this to exclude fields, queries and mutations that are not available to any possible user (determined from the user in connection string and any role they can become); this will be enabled by default in v5' ) . option ( '--no-ignore-indexes' , '[RECOMMENDED] set this to exclude filters, orderBy, and relations that would be expensive to access due to missing indexes' ) . option ( '--include-extension-resources' , 'by default, tables and functions that come from extensions are excluded; use this flag to include them (not recommended)' ) ; pluginHook ( 'cli:flags:add:schema' , addFlag ) ; program . option ( '--show-error-stack [json|string]' , 'show JavaScript error stacks in the GraphQL result errors (recommended in development)' ) . option ( '--extended-errors <string>' , "a comma separated list of extended Postgres error fields to display in the GraphQL result. Recommended in development: 'hint,detail,errcode'. Default: none" , ( option ) => option . split ( ',' ) . filter ( _ => _ ) ) ; pluginHook ( 'cli:flags:add:errorHandling' , addFlag ) ; program . option ( '--append-plugins <string>' , 'a comma-separated list of plugins to append to the list of Graphile Engine schema plugins' ) . option ( '--prepend-plugins <string>' , 'a comma-separated list of plugins to prepend to the list of Graphile Engine schema plugins' ) . option ( '--skip-plugins <string>' , 'a comma-separated list of Graphile Engine schema plugins to skip' ) ; pluginHook ( 'cli:flags:add:plugins' , addFlag ) ; program . option ( '--read-cache <path>' , '[experimental] reads cached values from local cache file to improve startup time (you may want to do this in production)' ) . option ( '--write-cache <path>' , '[experimental] writes computed values to local cache file so startup can be faster (do this during the build phase)' ) . option ( '--export-schema-json <path>' , 'enables exporting the detected schema, in JSON format, to the given location. The directories must exist already, if the file exists it will be overwritten.' ) . option ( '--export-schema-graphql <path>' , 'enables exporting the detected schema, in GraphQL schema format, to the given location. The directories must exist already, if the file exists it will be overwritten.' ) . option ( '--sort-export' , 'lexicographically (alphabetically) sort exported schema for more stable diffing.' ) . option ( '-X, --no-server' , '[experimental] for when you just want to use --write-cache or --export-schema-* and not actually run a server (e.g. CI)' ) ; pluginHook ( 'cli:flags:add:noServer' , addFlag ) ; program . option ( '-q, --graphql <path>' , 'the route to mount the GraphQL server on. defaults to `/graphql`' ) . option ( '-i, --graphiql <path>' , 'the route to mount the GraphiQL interface on. defaults to `/graphiql`' ) . option ( '--enhance-graphiql' , '[DEVELOPMENT] opt in to additional GraphiQL functionality (this may change over time - only intended for use in development; automatically enables with `subscriptions` and `live`)' ) . option ( '-b, --disable-graphiql' , 'disables the GraphiQL interface. overrides the GraphiQL route option' ) . option ( '-o, --cors' , 'enable generous CORS settings; disabled by default, if possible use a proxy instead' ) . option ( '-l, --body-size-limit <string>' , "set the maximum size of the HTTP request body that can be parsed (default 100kB). The size can be given as a human-readable string, such as '200kB' or '5MB' (case insensitive)." ) . option ( '--timeout <number>' , 'set the timeout value in milliseconds for sockets' , parseFloat ) . option ( '--cluster-workers <count>' , '[experimental] spawn <count> workers to increase throughput' , parseFloat ) . option ( '--enable-query-batching' , '[experimental] enable the server to process multiple GraphQL queries in one request' ) . option ( '--disable-query-log' , 'disable logging queries to console (recommended in production)' ) . option ( '--allow-explain' , '[EXPERIMENTAL] allows users to use the Explain button in GraphiQL to view the plan for the SQL that is executed (DO NOT USE IN PRODUCTION)' ) ; pluginHook ( 'cli:flags:add:webserver' , addFlag ) ; program . option ( '-e, --jwt-secret <string>' , 'the secret to be used when creating and verifying JWTs. if none is provided auth will be disabled' ) . option ( '--jwt-verify-algorithms <string>' , 'a comma separated list of the names of the allowed jwt token algorithms' , ( option ) => option . split ( ',' ) ) . option ( '-A, --jwt-verify-audience <string>' , "a comma separated list of JWT audiences that will be accepted; defaults to 'postgraphile'. To disable audience verification, set to ''." , ( option ) => option . split ( ',' ) . filter ( _ => _ ) ) . option ( '--jwt-verify-clock-tolerance <number>' , 'number of seconds to tolerate when checking the nbf and exp claims, to deal with small clock differences among different servers' , parseFloat ) . option ( '--jwt-verify-id <string>' , 'the name of the allowed jwt token id' ) . option ( '--jwt-verify-ignore-expiration' , 'if `true` do not validate the expiration of the token defaults to `false`' ) . option ( '--jwt-verify-ignore-not-before' , 'if `true` do not validate the notBefore of the token defaults to `false`' ) . option ( '--jwt-verify-issuer <string>' , 'a comma separated list of the names of the allowed jwt token issuer' , ( option ) => option . split ( ',' ) ) . option ( '--jwt-verify-subject <string>' , 'the name of the allowed jwt token subject' ) . option ( '--jwt-role <string>' , 'a comma seperated list of strings that create a path in the jwt from which to extract the postgres role. if none is provided it will use the key `role` on the root of the jwt.' , ( option ) => option . split ( ',' ) ) . option ( '-t, --jwt-token-identifier <identifier>' , 'the Postgres identifier for a composite type that will be used to create JWT tokens' ) ; pluginHook ( 'cli:flags:add:jwt' , addFlag ) ; pluginHook ( 'cli:flags:add' , addFlag ) ; program . option ( '--token <identifier>' , '[DEPRECATED] Use --jwt-token-identifier instead. This option will be removed in v5.' ) . option ( '--secret <string>' , '[DEPRECATED] Use --jwt-secret instead. This option will be removed in v5.' ) . option ( '--jwt-audiences <string>' , '[DEPRECATED] Use --jwt-verify-audience instead. This option will be removed in v5.' , ( option ) => option . split ( ',' ) ) . option ( '--legacy-functions-only' , '[DEPRECATED] PostGraphile 4.1.0 introduced support for PostgreSQL functions than declare parameters with IN/OUT/INOUT or declare RETURNS TABLE(...); enable this flag to ignore these types of functions. This option will be removed in v5.' ) ; pluginHook ( 'cli:flags:add:deprecated' , addFlag ) ; program . option ( '--legacy-relations <omit|deprecated|only>' , "some one-to-one relations were previously detected as one-to-many - should we export 'only' the old relation shapes, both new and old but mark the old ones as 'deprecated', or 'omit' the old relation shapes entirely" ) . option ( '--legacy-json-uuid' , `ONLY use this option if you require the v3 typenames 'Json' and 'Uuid' over 'JSON' and 'UUID'` ) ; pluginHook ( 'cli:flags:add:workarounds' , addFlag ) ; program . on ( '--help' , ( ) => { console . log ( `
Get started:

  $ postgraphile
  $ postgraphile -c postgres://localhost/my_db
  $ postgraphile --connection postgres://user:pass@localhost/my_db --schema my_schema --watch --dynamic-json
` ) ; process . exit ( 0 ) ; } ) ; program . parse ( argvSansPlugins ) ; function exitWithErrorMessage ( message ) { console . error ( message ) ; console . error ( ) ; console . error ( 'For help, run `postgraphile --help`' ) ; process . exit ( 1 ) ; } if ( program . args . length ) { exitWithErrorMessage ( `ERROR: some of the parameters you passed could not be processed: '${ program . args . join ( "', '" ) }'` ) ; } if ( program [ 'plugins' ] ) { exitWithErrorMessage ( `--plugins must be the first argument to postgraphile if specified` ) ; } process . on ( 'SIGINT' , ( ) => { process . exit ( 1 ) ; } ) ; const configOptions = config [ 'options' ] || { } ; const overridesFromOptions = { } ; [ 'ignoreIndexes' , 'ignoreRbac' , 'setofFunctionsContainNulls' ] . forEach ( option => { if ( option in configOptions ) { overridesFromOptions [ option ] = configOptions [ option ] ; } } ) ; const { demo : isDemo = false , connection : pgConnectionString , ownerConnection , subscriptions , live , websockets = subscriptions || live ? [ 'v0' , 'v1' ] : [ ] , websocketOperations = 'subscriptions' , watch : watchPg , schema : dbSchema , host : hostname = 'localhost' , port = 5000 , timeout : serverTimeout , maxPoolSize , defaultRole : pgDefaultRole , retryOnInitFail , graphql : graphqlRoute = '/graphql' , graphiql : graphiqlRoute = '/graphiql' , enhanceGraphiql = false , disableGraphiql = false , secret : deprecatedJwtSecret , jwtSecret , jwtPublicKey , jwtAudiences , jwtVerifyAlgorithms , jwtVerifyAudience , jwtVerifyClockTolerance , jwtVerifyId , jwtVerifyIgnoreExpiration , jwtVerifyIgnoreNotBefore , jwtVerifyIssuer , jwtVerifySubject , jwtSignOptions = { } , jwtVerifyOptions : rawJwtVerifyOptions , jwtRole = [ 'role' ] , token : deprecatedJwtPgTypeIdentifier , jwtTokenIdentifier : jwtPgTypeIdentifier , cors : enableCors = false , classicIds = false , dynamicJson = false , disableDefaultMutations = false , ignoreRbac = true , includeExtensionResources = false , exportSchemaJson : exportJsonSchemaPath , exportSchemaGraphql : exportGqlSchemaPath , sortExport = false , showErrorStack : rawShowErrorStack , extendedErrors = [ ] , bodySizeLimit , appendPlugins : appendPluginNames , prependPlugins : prependPluginNames , skipPlugins : skipPluginNames , readCache , writeCache , legacyRelations : rawLegacyRelations = 'deprecated' , server : yesServer , clusterWorkers , enableQueryBatching , setofFunctionsContainNulls = true , legacyJsonUuid , disableQueryLog , allowExplain , simpleCollections , legacyFunctionsOnly , ignoreIndexes , } = { ... config [ 'options' ] , ... program , ... overridesFromOptions } as typeof program ; const showErrorStack = ( val => { switch ( val ) { case 'string' : case true : return true ; case null : case undefined : return undefined ; case 'json' : return 'json' ; default : { exitWithErrorMessage ( `Invalid argument for '--show-error-stack' - expected no argument, or 'string' or 'json'` ) ; } } } ) ( rawShowErrorStack ) ; if ( allowExplain && ! disableGraphiql && ! enhanceGraphiql ) { exitWithErrorMessage ( '`--allow-explain` requires `--enhance-graphiql` or `--disable-graphiql`' ) ; } let legacyRelations ; if ( ! [ 'omit' , 'only' , 'deprecated' ] . includes ( rawLegacyRelations ) ) { exitWithErrorMessage ( `Invalid argument to '--legacy-relations' - expected on of 'omit', 'deprecated', 'only'; but received '${ rawLegacyRelations }'` ) ; } else { legacyRelations = rawLegacyRelations ; } if ( ! Array . isArray ( websockets ) || ( websockets . length && websockets . some ( ver => ! [ 'v0' , 'v1' ] . includes ( ver ) ) ) ) { exitWithErrorMessage ( `Invalid argument to '--websockets' - expected 'v0' and/or 'v1' (separated by comma); but received '${ websockets }'` ) ; } if ( websocketOperations !== 'subscriptions' && websocketOperations !== 'all' ) { exitWithErrorMessage ( `Invalid argument to '--websocket-operations' - expected 'subscriptions' or 'all' but received '${ websocketOperations }'` ) ; } const noServer = ! yesServer ; const schemas = dbSchema || ( isDemo ? [ 'forum_example' ] : [ 'public' ] ) ; const ownerConnectionString = ownerConnection || pgConnectionString || process . env . DATABASE_URL ; const coerce = ( o ) : PoolConfig => { const port = typeof o . port === 'number' ? o . port : typeof o . port === 'string' ? parseInt ( o . port , 10 ) : undefined ; return { ... o , application_name : o [ 'application_name' ] || undefined , ssl : o . ssl == null ? undefined : ( o . ssl as any ) . rejectUnauthorized == null ? ! ! o . ssl : ( o . ssl as any ) , user : typeof o . user === 'string' ? o . user : undefined , database : typeof o . database === 'string' ? o . database : undefined , password : typeof o . password === 'string' ? o . password : undefined , port : Number . isFinite ( port ) ? port : undefined , host : typeof o . host === 'string' ? o . host : undefined , } ; } ; const pgConfig = { ... ( pgConnectionString || process . env . DATABASE_URL || isDemo ? coerce ( parsePgConnectionString ( pgConnectionString || process . env . DATABASE_URL || DEMO_PG_URL ) ) : { host : process . env . PGHOST || process . env . PGHOSTADDR || 'localhost' , port : ( process . env . PGPORT ? parseInt ( process . env . PGPORT , 10 ) : null ) || 5432 , database : process . env . PGDATABASE , user : process . env . PGUSER , password : process . env . PGPASSWORD , } ) , max : maxPoolSize , } ; const loadPlugins = ( rawNames ) => { if ( ! rawNames ) { return undefined ; } const names = Array . isArray ( rawNames ) ? rawNames : String ( rawNames ) . split ( ',' ) ; return names . map ( rawName => { if ( typeof rawName === 'function' ) { return rawName ; } const name = String ( rawName ) ; const parts = name . split ( ':' ) ; if ( process . platform === 'win32' && parts [ 0 ] . length === 1 && /^[a-z]$/i . test ( parts [ 0 ] ) && [ '\\' , '/' ] . includes ( name [ 2 ] ) ) { const driveLetter = parts . shift ( ) ; parts [ 0 ] = `${ driveLetter }:${ parts [ 0 ] }` ; } let root ; try { root = require ( String ( parts . shift ( ) ) ) ; } catch ( e ) { console . error ( `Failed to load plugin '${ name }'` ) ; throw e ; } let plugin = root ; let part ; while ( ( part = parts . shift ( ) ) ) { plugin = plugin [ part ] ; if ( plugin == null ) { throw new Error ( `No plugin found matching spec '${ name }' - failed at '${ part }'` ) ; } } if ( typeof plugin === 'function' ) { return plugin ; } else if ( plugin === root && typeof plugin . default === 'function' ) { return plugin . default ; } else { throw new Error ( `No plugin found matching spec '${ name }' - expected function, found '${ typeof plugin }'` ) ; } } ) ; } ; if ( jwtAudiences != null && jwtVerifyAudience != null ) { exitWithErrorMessage ( `Provide either '--jwt-audiences' or '-A, --jwt-verify-audience' but not both` ) ; } function trimNulls ( obj ) { return Object . keys ( obj ) . reduce ( ( memo , key ) => { if ( obj [ key ] != null ) { memo [ key ] = obj [ key ] ; } return memo ; } , { } ) ; } if ( rawJwtVerifyOptions && ( jwtVerifyAlgorithms || jwtVerifyAudience || jwtVerifyClockTolerance || jwtVerifyId || jwtVerifyIgnoreExpiration || jwtVerifyIgnoreNotBefore || jwtVerifyIssuer || jwtVerifySubject ) ) { exitWithErrorMessage ( 'You may not mix `jwtVerifyOptions` with the legacy `jwtVerify*` settings; please only provide `jwtVerifyOptions`.' ) ; } const jwtVerifyOptions = rawJwtVerifyOptions ? rawJwtVerifyOptions : trimNulls ( { algorithms : jwtVerifyAlgorithms , audience : jwtVerifyAudience , clockTolerance : jwtVerifyClockTolerance , jwtId : jwtVerifyId , ignoreExpiration : jwtVerifyIgnoreExpiration , ignoreNotBefore : jwtVerifyIgnoreNotBefore , issuer : jwtVerifyIssuer , subject : jwtVerifySubject , } ) ; const appendPlugins = loadPlugins ( appendPluginNames ) ; const prependPlugins = loadPlugins ( prependPluginNames ) ; const skipPlugins = loadPlugins ( skipPluginNames ) ; const postgraphileOptions = pluginHook ( 'cli:library:options' , { ... config [ 'options' ] , classicIds , dynamicJson , disableDefaultMutations , ignoreRBAC : ignoreRbac , includeExtensionResources , graphqlRoute , graphiqlRoute , graphiql : ! disableGraphiql , enhanceGraphiql : enhanceGraphiql ? true : undefined , jwtPgTypeIdentifier : jwtPgTypeIdentifier || deprecatedJwtPgTypeIdentifier , jwtSecret : jwtSecret || deprecatedJwtSecret || process . env . JWT_SECRET , jwtPublicKey , jwtAudiences , jwtSignOptions , jwtRole , jwtVerifyOptions , retryOnInitFail , pgDefaultRole , subscriptions : subscriptions || live , websockets , websocketOperations , live , watchPg , showErrorStack , extendedErrors , disableQueryLog , allowExplain : allowExplain ? true : undefined , enableCors , exportJsonSchemaPath , exportGqlSchemaPath , sortExport , bodySizeLimit , appendPlugins : smartTagsPlugin ? [ smartTagsPlugin , ... ( appendPlugins || [ ] ) ] : appendPlugins , prependPlugins , skipPlugins , readCache , writeCache , legacyRelations , setofFunctionsContainNulls , legacyJsonUuid , enableQueryBatching , pluginHook , simpleCollections , legacyFunctionsOnly , ignoreIndexes , ownerConnectionString , } , { config , cliOptions : program } ) ; function killAllWorkers ( signal = 'SIGTERM' ) { for ( const id in cluster . workers ) { const worker = cluster . workers [ id ] ; if ( Object . prototype . hasOwnProperty . call ( cluster . workers , id ) && worker ) { worker . kill ( signal ) ; } } } if ( noServer ) { ( async ( ) : Promise < void > => { const pgPool = new Pool ( pgConfig ) ; pgPool . on ( 'error' , err => { console . error ( 'PostgreSQL client generated error: ' , err . message ) ; } ) ; const { getGraphQLSchema } = getPostgraphileSchemaBuilder ( pgPool , schemas , postgraphileOptions ) ; await getGraphQLSchema ( ) ; if ( ! watchPg ) { await pgPool . end ( ) ; } } ) ( ) . then ( null , e => { console . error ( 'Error occurred!' ) ; console . error ( e ) ; process . exit ( 1 ) ; } ) ; } else { if ( clusterWorkers >= 2 && cluster . isMaster ) { let shuttingDown = false ; const shutdown = ( ) => { if ( ! shuttingDown ) { shuttingDown = true ; process . exitCode = 1 ; const fallbackTimeout = setTimeout ( ( ) => { const remainingCount = Object . keys ( cluster . workers ) . length ; if ( remainingCount > 0 ) { console . log ( `  [cluster] ${ remainingCount } workers did not die fast enough, sending SIGKILL` ) ; killAllWorkers ( 'SIGKILL' ) ; const ultraFallbackTimeout = setTimeout ( ( ) => { console . log ( `  [cluster] really should have exited automatically, but haven't - exiting` ) ; process . exit ( 3 ) ; } , 5000 ) ; ultraFallbackTimeout . unref ( ) ; } else { console . log ( `  [cluster] should have exited automatically, but haven't - exiting` ) ; process . exit ( 2 ) ; } } , 5000 ) ; fallbackTimeout . unref ( ) ; console . log ( `  [cluster] killing other workers with SIGTERM` ) ; killAllWorkers ( 'SIGTERM' ) ; } } ; cluster . on ( 'exit' , ( worker , code , signal ) => { console . log ( `  [cluster] worker pid=${ worker . process . pid } exited (code=${ code }, signal=${ signal })` ) ; shutdown ( ) ; } ) ; for ( let i = 0 ; i < clusterWorkers ; i ++ ) { const worker = cluster . fork ( { POSTGRAPHILE_WORKER_NUMBER : String ( i + 1 ) , } ) ; console . log ( `  [cluster] started worker ${ i + 1 } (pid=${ worker . process . pid })` ) ; } } else { const rawMiddleware = postgraphile ( pgConfig , schemas , postgraphileOptions ) ; const middleware = pluginHook ( 'cli:server:middleware' , rawMiddleware , { options : postgraphileOptions , } ) ; const server = createServer ( middleware ) ; if ( serverTimeout ) { server . timeout = serverTimeout ; } if ( websockets . length ) { enhanceHttpServerWithWebSockets ( server , middleware ) ; } pluginHook ( 'cli:server:created' , server , { options : postgraphileOptions , middleware , } ) ; server . listen ( port , hostname , ( ) => { const address = server . address ( ) ; const actualPort = typeof address === 'string' ? port : address . port ; const self = cluster . isMaster ? isDev ? `server (pid=${ process . pid })` : 'server' : `worker ${ process . env . POSTGRAPHILE_WORKER_NUMBER } (pid=${ process . pid })` ; const versionString = `v${ manifest . version }` ; if ( cluster . isMaster || process . env . POSTGRAPHILE_WORKER_NUMBER === '1' ) { console . log ( '' ) ; console . log ( `PostGraphile ${ versionString } ${ self } listening on port ${ chalk . underline ( actualPort . toString ( ) ) } 🚀` ) ; console . log ( '' ) ; const { host : rawPgHost , port : rawPgPort , database : pgDatabase , user : pgUser , password : pgPassword , } = pgConfig ; const pgHost = rawPgHost || 'localhost' ; const pgPort = ( rawPgPort && parseInt ( String ( rawPgPort ) , 10 ) ) || 5432 ; const safeConnectionString = isDemo ? 'postgraphile_demo' : `postgres://${ pgUser ? pgUser : '' }${ pgPassword ? ':[SECRET]' : '' }${ pgUser || pgPassword ? '@' : '' }${ pgUser || pgPassword || pgHost !== 'localhost' || pgPort !== 5432 ? pgHost : '' }${ pgPort !== 5432 ? `:${ pgConfig . port || 5432 }` : '' }${ pgDatabase ? `/${ pgDatabase }` : '' }` ; const information = pluginHook ( 'cli:greeting' , [ `GraphQL API:         ${ chalk . underline . bold . blue ( `http://${ hostname }:${ actualPort }${ graphqlRoute }` ) }` + ( postgraphileOptions . subscriptions ? ` (${ postgraphileOptions . live ? 'live ' : '' }subscriptions enabled)` : '' ) , ! disableGraphiql && `GraphiQL GUI/IDE:    ${ chalk . underline . bold . blue ( `http://${ hostname }:${ actualPort }${ graphiqlRoute }` ) }` + ( postgraphileOptions . enhanceGraphiql || postgraphileOptions . live || postgraphileOptions . subscriptions ? '' : ` (${ chalk . bold ( 'RECOMMENDATION' ) }: add '--enhance-graphiql')` ) , `Postgres connection: ${ chalk . underline . magenta ( safeConnectionString ) }${ postgraphileOptions . watchPg ? ' (watching)' : '' }` , `Postgres schema(s):  ${ schemas . map ( schema => chalk . magenta ( schema ) ) . join ( ', ' ) }` , `Documentation:       ${ chalk . underline ( `https://graphile.org/postgraphile/introduction/` ) }` , `Node.js version:     ${ process . version } on ${ os . platform ( ) } ${ os . arch ( ) }` , extractedPlugins . length === 0 ? `Join ${ chalk . bold ( sponsor ) } in supporting PostGraphile development: ${ chalk . underline . bold . blue ( `https://graphile.org/sponsor/` ) }` : null , ] , { options : postgraphileOptions , middleware , port : actualPort , chalk , } ) . filter ( isString ) ; console . log ( information . map ( msg => `  ‣ ${ msg }` ) . join ( '\n' ) ) ; console . log ( '' ) ; console . log ( chalk . gray ( '* * *' ) ) ; } else { console . log ( `PostGraphile ${ versionString } ${ self } listening on port ${ chalk . underline ( actualPort . toString ( ) ) } 🚀` ) ; } console . log ( '' ) ; } ) ; } }