bootstrap.ts 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. import { INestApplication, INestMicroservice } from '@nestjs/common';
  2. import { NestFactory } from '@nestjs/core';
  3. import { Transport } from '@nestjs/microservices';
  4. import { Type } from '@vendure/common/lib/shared-types';
  5. import { worker } from 'cluster';
  6. import { EntitySubscriberInterface } from 'typeorm';
  7. import { InternalServerError } from './common/error/errors';
  8. import { ReadOnlyRequired } from './common/types/common-types';
  9. import { getConfig, setConfig } from './config/config-helpers';
  10. import { DefaultLogger } from './config/logger/default-logger';
  11. import { Logger } from './config/logger/vendure-logger';
  12. import { VendureConfig } from './config/vendure-config';
  13. import { registerCustomEntityFields } from './entity/custom-entity-fields';
  14. import { logProxyMiddlewares } from './plugin/plugin-utils';
  15. export type VendureBootstrapFunction = (config: VendureConfig) => Promise<INestApplication>;
  16. /**
  17. * Bootstrap the Vendure server.
  18. */
  19. export async function bootstrap(userConfig: Partial<VendureConfig>): Promise<INestApplication> {
  20. const config = await preBootstrapConfig(userConfig);
  21. Logger.useLogger(config.logger);
  22. Logger.info(`Bootstrapping Vendure Server (pid: ${process.pid})...`);
  23. // The AppModule *must* be loaded only after the entities have been set in the
  24. // config, so that they are available when the AppModule decorator is evaluated.
  25. // tslint:disable-next-line:whitespace
  26. const appModule = await import('./app.module');
  27. DefaultLogger.hideNestBoostrapLogs();
  28. const app = await NestFactory.create(appModule.AppModule, {
  29. cors: config.cors,
  30. logger: new Logger(),
  31. });
  32. DefaultLogger.restoreOriginalLogLevel();
  33. app.useLogger(new Logger());
  34. await runPluginOnBootstrapMethods(config, app);
  35. await app.listen(config.port, config.hostname);
  36. if (config.workerOptions.runInMainProcess) {
  37. await bootstrapWorker(config);
  38. }
  39. logWelcomeMessage(config);
  40. return app;
  41. }
  42. export async function bootstrapWorker(userConfig: Partial<VendureConfig>): Promise<INestMicroservice> {
  43. const config = await preBootstrapConfig(userConfig);
  44. if (!config.workerOptions.runInMainProcess && (config.logger as any).setDefaultContext) {
  45. (config.logger as any).setDefaultContext('Vendure Worker');
  46. }
  47. Logger.useLogger(config.logger);
  48. Logger.info(`Bootstrapping Vendure Worker (pid: ${process.pid})...`);
  49. const workerModule = await import('./worker/worker.module');
  50. DefaultLogger.hideNestBoostrapLogs();
  51. const workerApp = await NestFactory.createMicroservice(workerModule.WorkerModule, {
  52. transport: config.workerOptions.transport,
  53. logger: new Logger(),
  54. options: config.workerOptions.options,
  55. });
  56. DefaultLogger.restoreOriginalLogLevel();
  57. workerApp.useLogger(new Logger());
  58. await workerApp.listenAsync();
  59. return workerApp;
  60. }
  61. /**
  62. * Setting the global config must be done prior to loading the AppModule.
  63. */
  64. export async function preBootstrapConfig(
  65. userConfig: Partial<VendureConfig>,
  66. ): Promise<ReadOnlyRequired<VendureConfig>> {
  67. if (userConfig) {
  68. setConfig(userConfig);
  69. }
  70. // Entities *must* be loaded after the user config is set in order for the
  71. // base VendureEntity to be correctly configured with the primary key type
  72. // specified in the EntityIdStrategy.
  73. // tslint:disable-next-line:whitespace
  74. const pluginEntities = getEntitiesFromPlugins(userConfig);
  75. const entities = await getAllEntities(userConfig);
  76. const { coreSubscribersMap } = await import('./entity/subscribers');
  77. setConfig({
  78. dbConnectionOptions: {
  79. entities,
  80. subscribers: Object.values(coreSubscribersMap) as Array<Type<EntitySubscriberInterface>>,
  81. },
  82. });
  83. let config = getConfig();
  84. config = await runPluginConfigurations(config);
  85. registerCustomEntityFields(config);
  86. return config;
  87. }
  88. /**
  89. * Initialize any configured plugins.
  90. */
  91. async function runPluginConfigurations(
  92. config: ReadOnlyRequired<VendureConfig>,
  93. ): Promise<ReadOnlyRequired<VendureConfig>> {
  94. for (const plugin of config.plugins) {
  95. if (plugin.configure) {
  96. config = (await plugin.configure(config)) as ReadOnlyRequired<VendureConfig>;
  97. }
  98. }
  99. return config;
  100. }
  101. /**
  102. * Run the onBootstrap() method of any configured plugins.
  103. */
  104. export async function runPluginOnBootstrapMethods(
  105. config: ReadOnlyRequired<VendureConfig>,
  106. app: INestApplication,
  107. ): Promise<void> {
  108. function inject<T>(type: Type<T>): T {
  109. return app.get(type);
  110. }
  111. for (const plugin of config.plugins) {
  112. if (plugin.onBootstrap) {
  113. await plugin.onBootstrap(inject);
  114. const pluginName = plugin.constructor && plugin.constructor.name || '(anonymous plugin)';
  115. Logger.verbose(`Bootstrapped plugin ${pluginName}`);
  116. }
  117. }
  118. }
  119. /**
  120. * Returns an array of core entities and any additional entities defined in plugins.
  121. */
  122. async function getAllEntities(userConfig: Partial<VendureConfig>): Promise<Array<Type<any>>> {
  123. const { coreEntitiesMap } = await import('./entity/entities');
  124. const coreEntities = Object.values(coreEntitiesMap) as Array<Type<any>>;
  125. const pluginEntities = getEntitiesFromPlugins(userConfig);
  126. const allEntities: Array<Type<any>> = coreEntities;
  127. // Check to ensure that no plugins are defining entities with names
  128. // which conflict with existing entities.
  129. for (const pluginEntity of pluginEntities) {
  130. if (allEntities.find(e => e.name === pluginEntity.name)) {
  131. throw new InternalServerError(`error.entity-name-conflict`, { entityName: pluginEntity.name });
  132. } else {
  133. allEntities.push(pluginEntity);
  134. }
  135. }
  136. return [...coreEntities, ...pluginEntities];
  137. }
  138. /**
  139. * Collects all entities defined in plugins into a single array.
  140. */
  141. function getEntitiesFromPlugins(userConfig: Partial<VendureConfig>): Array<Type<any>> {
  142. if (!userConfig.plugins) {
  143. return [];
  144. }
  145. return userConfig.plugins
  146. .map(p => (p.defineEntities ? p.defineEntities() : []))
  147. .reduce((all, entities) => [...all, ...entities], []);
  148. }
  149. function logWelcomeMessage(config: VendureConfig) {
  150. let version: string;
  151. try {
  152. version = require('../package.json').version;
  153. } catch (e) {
  154. version = ' unknown';
  155. }
  156. Logger.info(`=================================================`);
  157. Logger.info(`Vendure server (v${version}) now running on port ${config.port}`);
  158. Logger.info(`Shop API: http://localhost:${config.port}/${config.shopApiPath}`);
  159. Logger.info(`Admin API: http://localhost:${config.port}/${config.adminApiPath}`);
  160. logProxyMiddlewares(config);
  161. Logger.info(`=================================================`);
  162. }