plugin.ts 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. import { AdminUiConfig } from '@vendure/common/lib/shared-types';
  2. import {
  3. createProxyHandler,
  4. OnVendureBootstrap,
  5. OnVendureClose,
  6. VendureConfig,
  7. VendurePlugin,
  8. } from '@vendure/core';
  9. import express from 'express';
  10. import fs from 'fs-extra';
  11. import { Server } from 'http';
  12. import path from 'path';
  13. /**
  14. * @description
  15. * Configuration options for the {@link AdminUiPlugin}.
  16. *
  17. * @docsCategory AdminUiPlugin
  18. */
  19. export interface AdminUiOptions {
  20. /**
  21. * @description
  22. * The hostname of the server serving the static admin ui files.
  23. *
  24. * @default 'localhost'
  25. */
  26. hostname?: string;
  27. /**
  28. * @description
  29. * The port on which the server will listen.
  30. */
  31. port: number;
  32. /**
  33. * @description
  34. * The hostname of the Vendure server which the admin ui will be making API calls
  35. * to. If set to "auto", the admin ui app will determine the hostname from the
  36. * current location (i.e. `window.location.hostname`).
  37. *
  38. * @default 'auto'
  39. */
  40. apiHost?: string | 'auto';
  41. /**
  42. * @description
  43. * The port of the Vendure server which the admin ui will be making API calls
  44. * to. If set to "auto", the admin ui app will determine the port from the
  45. * current location (i.e. `window.location.port`).
  46. *
  47. * @default 'auto'
  48. */
  49. apiPort?: number | 'auto';
  50. }
  51. /**
  52. * @description
  53. * This plugin starts a static server for the Admin UI app, and proxies it via the `/admin/` path of the main Vendure server.
  54. *
  55. * The Admin UI allows you to administer all aspects of your store, from inventory management to order tracking. It is the tool used by
  56. * store administrators on a day-to-day basis for the management of the store.
  57. *
  58. * ## Installation
  59. *
  60. * `yarn add \@vendure/admin-ui-plugin`
  61. *
  62. * or
  63. *
  64. * `npm install \@vendure/admin-ui-plugin`
  65. *
  66. * @example
  67. * ```ts
  68. * import { AdminUiPlugin } from '\@vendure/admin-ui-plugin';
  69. *
  70. * const config: VendureConfig = {
  71. * // Add an instance of the plugin to the plugins array
  72. * plugins: [
  73. * AdminUiPlugin.init({ port: 3002 }),
  74. * ],
  75. * };
  76. * ```
  77. *
  78. * @docsCategory AdminUiPlugin
  79. */
  80. @VendurePlugin({
  81. configuration: (config: Required<VendureConfig>) => AdminUiPlugin.configure(config),
  82. })
  83. export class AdminUiPlugin implements OnVendureBootstrap, OnVendureClose {
  84. private static options: AdminUiOptions;
  85. private server: Server;
  86. /**
  87. * @description
  88. * Set the plugin options
  89. */
  90. static init(options: AdminUiOptions) {
  91. this.options = options;
  92. return AdminUiPlugin;
  93. }
  94. /** @internal */
  95. static async configure(config: Required<VendureConfig>): Promise<Required<VendureConfig>> {
  96. const route = 'admin';
  97. config.middleware.push({
  98. handler: createProxyHandler({ ...this.options, route, label: 'Admin UI' }),
  99. route,
  100. });
  101. const { adminApiPath } = config;
  102. const { apiHost, apiPort } = this.options;
  103. await this.overwriteAdminUiConfig(apiHost || 'auto', apiPort || 'auto', adminApiPath);
  104. return config;
  105. }
  106. /** @internal */
  107. onVendureBootstrap() {
  108. const adminUiPath = AdminUiPlugin.getAdminUiPath();
  109. const assetServer = express();
  110. assetServer.use(express.static(adminUiPath));
  111. assetServer.use((req, res) => {
  112. res.sendFile(path.join(adminUiPath, 'index.html'));
  113. });
  114. this.server = assetServer.listen(AdminUiPlugin.options.port);
  115. }
  116. /** @internal */
  117. onVendureClose(): Promise<void> {
  118. return new Promise(resolve => this.server.close(() => resolve()));
  119. }
  120. /**
  121. * Overwrites the parts of the admin-ui app's `vendure-ui-config.json` file relating to connecting to
  122. * the server admin API.
  123. */
  124. private static async overwriteAdminUiConfig(
  125. host: string | 'auto',
  126. port: number | 'auto',
  127. adminApiPath: string,
  128. ) {
  129. const adminUiConfigPath = path.join(this.getAdminUiPath(), 'vendure-ui-config.json');
  130. const adminUiConfig = await fs.readFile(adminUiConfigPath, 'utf-8');
  131. const config: AdminUiConfig = JSON.parse(adminUiConfig);
  132. config.apiHost = host || 'http://localhost';
  133. config.apiPort = port;
  134. config.adminApiPath = adminApiPath;
  135. await fs.writeFile(adminUiConfigPath, JSON.stringify(config, null, 2));
  136. }
  137. private static getAdminUiPath(): string {
  138. // attempt to read from the path location on a production npm install
  139. const prodPath = path.join(__dirname, '../admin-ui');
  140. if (fs.existsSync(path.join(prodPath, 'index.html'))) {
  141. return prodPath;
  142. }
  143. // attempt to read from the path on a development install
  144. const devPath = path.join(__dirname, '../lib/admin-ui');
  145. if (fs.existsSync(path.join(devPath, 'index.html'))) {
  146. return devPath;
  147. }
  148. throw new Error(`AdminUiPlugin: admin-ui app not found`);
  149. }
  150. }