plugin.ts 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. import { Watcher } from '@vendure/admin-ui/compiler/watch';
  2. import { DEFAULT_AUTH_TOKEN_HEADER_KEY } from '@vendure/common/lib/shared-constants';
  3. import { AdminUiConfig, AdminUiExtension, Type } from '@vendure/common/lib/shared-types';
  4. import {
  5. AuthOptions,
  6. ConfigService,
  7. createProxyHandler,
  8. Logger,
  9. OnVendureBootstrap,
  10. OnVendureClose,
  11. PluginCommonModule,
  12. RuntimeVendureConfig,
  13. VendurePlugin,
  14. } from '@vendure/core';
  15. import express from 'express';
  16. import fs from 'fs-extra';
  17. import { Server } from 'http';
  18. import path from 'path';
  19. import { UI_PATH } from './constants';
  20. import { UiAppCompiler } from './ui-app-compiler.service';
  21. /**
  22. * @description
  23. * Configuration options for the {@link AdminUiPlugin}.
  24. *
  25. * @docsCategory AdminUiPlugin
  26. */
  27. export interface AdminUiOptions {
  28. /**
  29. * @description
  30. * The hostname of the server serving the static admin ui files.
  31. *
  32. * @default 'localhost'
  33. */
  34. hostname?: string;
  35. /**
  36. * @description
  37. * The port on which the server will listen.
  38. */
  39. port: number;
  40. /**
  41. * @description
  42. * The hostname of the Vendure server which the admin ui will be making API calls
  43. * to. If set to "auto", the admin ui app will determine the hostname from the
  44. * current location (i.e. `window.location.hostname`).
  45. *
  46. * @default 'auto'
  47. */
  48. apiHost?: string | 'auto';
  49. /**
  50. * @description
  51. * The port of the Vendure server which the admin ui will be making API calls
  52. * to. If set to "auto", the admin ui app will determine the port from the
  53. * current location (i.e. `window.location.port`).
  54. *
  55. * @default 'auto'
  56. */
  57. apiPort?: number | 'auto';
  58. /**
  59. * @description
  60. * An optional array of objects which configure extension Angular modules
  61. * to be compiled into and made available by the AdminUi application.
  62. */
  63. extensions?: AdminUiExtension[];
  64. /**
  65. * @description
  66. * Set to `true` in order to run the Admin UI in development mode (using the Angular CLI
  67. * [ng serve](https://angular.io/cli/serve) command). When in watch mode, any changes to
  68. * UI extension files will be watched and trigger a rebuild of the Admin UI with live
  69. * reloading.
  70. *
  71. * @default false
  72. */
  73. watch?: boolean;
  74. }
  75. /**
  76. * @description
  77. * This plugin starts a static server for the Admin UI app, and proxies it via the `/admin/` path of the main Vendure server.
  78. *
  79. * The Admin UI allows you to administer all aspects of your store, from inventory management to order tracking. It is the tool used by
  80. * store administrators on a day-to-day basis for the management of the store.
  81. *
  82. * ## Installation
  83. *
  84. * `yarn add \@vendure/admin-ui-plugin`
  85. *
  86. * or
  87. *
  88. * `npm install \@vendure/admin-ui-plugin`
  89. *
  90. * @example
  91. * ```ts
  92. * import { AdminUiPlugin } from '\@vendure/admin-ui-plugin';
  93. *
  94. * const config: VendureConfig = {
  95. * // Add an instance of the plugin to the plugins array
  96. * plugins: [
  97. * AdminUiPlugin.init({ port: 3002 }),
  98. * ],
  99. * };
  100. * ```
  101. *
  102. * @docsCategory AdminUiPlugin
  103. */
  104. @VendurePlugin({
  105. imports: [PluginCommonModule],
  106. providers: [UiAppCompiler],
  107. configuration: config => AdminUiPlugin.configure(config),
  108. })
  109. export class AdminUiPlugin implements OnVendureBootstrap, OnVendureClose {
  110. private static options: AdminUiOptions;
  111. private server: Server;
  112. private watcher: Watcher | undefined;
  113. constructor(private configService: ConfigService, private appCompiler: UiAppCompiler) {}
  114. /**
  115. * @description
  116. * Set the plugin options
  117. */
  118. static init(options: AdminUiOptions): Type<AdminUiPlugin> {
  119. this.options = options;
  120. return AdminUiPlugin;
  121. }
  122. /** @internal */
  123. static async configure(config: RuntimeVendureConfig): Promise<RuntimeVendureConfig> {
  124. const route = 'admin';
  125. config.middleware.push({
  126. handler: createProxyHandler({
  127. ...this.options,
  128. route: 'admin',
  129. label: 'Admin UI',
  130. basePath: this.options.watch ? 'admin' : undefined,
  131. }),
  132. route,
  133. });
  134. if (this.options.watch) {
  135. config.middleware.push({
  136. handler: createProxyHandler({
  137. ...this.options,
  138. route: 'sockjs-node',
  139. label: 'Admin UI live reload',
  140. basePath: 'sockjs-node',
  141. }),
  142. route: 'sockjs-node',
  143. });
  144. }
  145. return config;
  146. }
  147. /** @internal */
  148. async onVendureBootstrap() {
  149. const { adminApiPath, authOptions } = this.configService;
  150. const { apiHost, apiPort, extensions, watch, port } = AdminUiPlugin.options;
  151. let adminUiConfigPath: string;
  152. if (watch) {
  153. this.watcher = this.appCompiler.watchAdminUiApp(extensions, port);
  154. adminUiConfigPath = path.join(__dirname, '../../../admin-ui/src', 'vendure-ui-config.json');
  155. } else {
  156. const adminUiPath = await this.appCompiler.compileAdminUiApp(extensions);
  157. const adminUiServer = express();
  158. adminUiServer.use(express.static(UI_PATH));
  159. adminUiServer.use((req, res) => {
  160. res.sendFile(path.join(UI_PATH, 'index.html'));
  161. });
  162. this.server = adminUiServer.listen(AdminUiPlugin.options.port);
  163. adminUiConfigPath = path.join(UI_PATH, 'vendure-ui-config.json');
  164. }
  165. await this.overwriteAdminUiConfig({
  166. host: apiHost || 'auto',
  167. port: apiPort || 'auto',
  168. adminApiPath,
  169. authOptions,
  170. adminUiConfigPath,
  171. });
  172. }
  173. /** @internal */
  174. async onVendureClose(): Promise<void> {
  175. if (this.watcher) {
  176. this.watcher.close();
  177. }
  178. if (this.server) {
  179. await new Promise(resolve => this.server.close(() => resolve()));
  180. }
  181. }
  182. /**
  183. * Overwrites the parts of the admin-ui app's `vendure-ui-config.json` file relating to connecting to
  184. * the server admin API.
  185. */
  186. private async overwriteAdminUiConfig(options: {
  187. host: string | 'auto';
  188. port: number | 'auto';
  189. adminApiPath: string;
  190. authOptions: AuthOptions;
  191. adminUiConfigPath: string;
  192. }) {
  193. const { host, port, adminApiPath, authOptions, adminUiConfigPath } = options;
  194. const adminUiConfig = await fs.readFile(adminUiConfigPath, 'utf-8');
  195. let config: AdminUiConfig;
  196. try {
  197. config = JSON.parse(adminUiConfig);
  198. } catch (e) {
  199. throw new Error('[AdminUiPlugin] Could not parse vendure-ui-config.json file:\n' + e.message);
  200. }
  201. config.apiHost = host || 'http://localhost';
  202. config.apiPort = port;
  203. config.adminApiPath = adminApiPath;
  204. config.tokenMethod = authOptions.tokenMethod || 'cookie';
  205. config.authTokenHeaderKey = authOptions.authTokenHeaderKey || DEFAULT_AUTH_TOKEN_HEADER_KEY;
  206. await fs.writeFile(adminUiConfigPath, JSON.stringify(config, null, 2));
  207. }
  208. }