dashboard.plugin.ts 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. import { MiddlewareConsumer, NestModule } from '@nestjs/common';
  2. import { Type } from '@vendure/common/lib/shared-types';
  3. import {
  4. Logger,
  5. PluginCommonModule,
  6. ProcessContext,
  7. registerPluginStartupMessage,
  8. VendurePlugin,
  9. } from '@vendure/core';
  10. import express from 'express';
  11. import { rateLimit } from 'express-rate-limit';
  12. import fs from 'fs-extra';
  13. import path from 'path';
  14. import { adminApiExtensions } from './api/api-extensions.js';
  15. import { MetricsResolver } from './api/metrics.resolver.js';
  16. import { DEFAULT_APP_PATH, loggerCtx } from './constants.js';
  17. import { MetricsService } from './service/metrics.service.js';
  18. /**
  19. * @description
  20. * Configuration options for the {@link DashboardPlugin}.
  21. *
  22. * @docsCategory core plugins/DashboardPlugin
  23. */
  24. export interface DashboardPluginOptions {
  25. /**
  26. * @description
  27. * The route to the Dashboard UI.
  28. *
  29. * @default 'dashboard'
  30. */
  31. route: string;
  32. /**
  33. * @description
  34. * The path to the dashboard UI app dist directory. By default, the built-in dashboard UI
  35. * will be served. This can be overridden with a custom build of the dashboard.
  36. */
  37. appDir: string;
  38. }
  39. /**
  40. * @description
  41. * This plugin serves the static files of the Vendure Dashboard and provides the
  42. * GraphQL extensions needed for the order metrics on the dashboard index page.
  43. *
  44. * ## Installation
  45. *
  46. * `npm install \@vendure/dashboard`
  47. *
  48. * ## Usage
  49. *
  50. * First you need to set up compilation of the Dashboard, using the Vite configuration
  51. * described in the [Dashboard Getting Started Guide](/guides/extending-the-dashboard/getting-started/)
  52. *
  53. * Once set up, you run `npx vite build` to build the production version of the dashboard app.
  54. *
  55. * The built app files will be output to the location specified by `build.outDir` in your Vite
  56. * config file. This should then be passed to the `appDir` init option, as in the example below:
  57. *
  58. * @example
  59. * ```ts
  60. * import { DashboardPlugin } from '\@vendure/dashboard/plugin';
  61. *
  62. * const config: VendureConfig = {
  63. * // Add an instance of the plugin to the plugins array
  64. * plugins: [
  65. * DashboardPlugin.init({
  66. * route: 'dashboard',
  67. * appDir: './dist/dashboard',
  68. * }),
  69. * ],
  70. * };
  71. * ```
  72. *
  73. * ## Metrics
  74. *
  75. * This plugin defines a `metricSummary` query which is used by the Dashboard UI to
  76. * display the order metrics on the dashboard.
  77. *
  78. * If you are building a stand-alone version of the Dashboard UI app, and therefore
  79. * don't need this plugin to serve the Dashboard UI, you can still use the
  80. * `metricSummary` query by adding the `DashboardPlugin` to the `plugins` array,
  81. * but without calling the `init()` method:
  82. *
  83. * @example
  84. * ```ts
  85. * import { DashboardPlugin } from '\@vendure/dashboard-plugin';
  86. *
  87. * const config: VendureConfig = {
  88. * plugins: [
  89. * DashboardPlugin, // <-- no call to .init()
  90. * ],
  91. * // ...
  92. * };
  93. * ```
  94. *
  95. * @docsCategory core plugins/DashboardPlugin
  96. */
  97. @VendurePlugin({
  98. imports: [PluginCommonModule],
  99. adminApiExtensions: {
  100. schema: adminApiExtensions,
  101. resolvers: [MetricsResolver],
  102. },
  103. providers: [MetricsService],
  104. compatibility: '^3.0.0',
  105. })
  106. export class DashboardPlugin implements NestModule {
  107. private static options: DashboardPluginOptions | undefined;
  108. constructor(private readonly processContext: ProcessContext) {}
  109. /**
  110. * @description
  111. * Set the plugin options
  112. */
  113. static init(options: DashboardPluginOptions): Type<DashboardPlugin> {
  114. this.options = options;
  115. return DashboardPlugin;
  116. }
  117. configure(consumer: MiddlewareConsumer) {
  118. if (this.processContext.isWorker) {
  119. return;
  120. }
  121. if (!DashboardPlugin.options) {
  122. Logger.info(
  123. `DashboardPlugin's init() method was not called. The Dashboard UI will not be served.`,
  124. loggerCtx,
  125. );
  126. return;
  127. }
  128. const { route, appDir } = DashboardPlugin.options;
  129. const dashboardPath = appDir || this.getDashboardPath();
  130. Logger.info('Creating dashboard middleware', loggerCtx);
  131. consumer.apply(this.createStaticServer(dashboardPath)).forRoutes(route);
  132. registerPluginStartupMessage('Dashboard UI', route);
  133. }
  134. private createStaticServer(dashboardPath: string) {
  135. const limiter = rateLimit({
  136. windowMs: 60 * 1000,
  137. limit: process.env.NODE_ENV === 'production' ? 500 : 2000,
  138. standardHeaders: true,
  139. legacyHeaders: false,
  140. });
  141. const dashboardServer = express.Router();
  142. // This is a workaround for a type mismatch between express v5 (Vendure core)
  143. // and express v4 (several transitive dependencies). Can be removed once the
  144. // ecosystem has more significantly shifted to v5.
  145. dashboardServer.use(limiter as any);
  146. dashboardServer.use(express.static(dashboardPath));
  147. dashboardServer.use((req, res) => {
  148. res.sendFile('index.html', { root: dashboardPath });
  149. });
  150. return dashboardServer;
  151. }
  152. private getDashboardPath(): string {
  153. // First, try to find the dashboard dist directory in the @vendure/dashboard package
  154. try {
  155. const dashboardPackageJson = require.resolve('@vendure/dashboard/package.json');
  156. const dashboardPackageRoot = path.dirname(dashboardPackageJson);
  157. const dashboardDistPath = path.join(dashboardPackageRoot, 'dist');
  158. if (fs.existsSync(dashboardDistPath)) {
  159. Logger.info(`Found dashboard UI at ${dashboardDistPath}`, loggerCtx);
  160. return dashboardDistPath;
  161. }
  162. } catch (e) {
  163. // Dashboard package not found, continue to fallback
  164. }
  165. // Fallback to default path
  166. Logger.warn(
  167. `Could not find @vendure/dashboard dist directory. Falling back to default path: ${DEFAULT_APP_PATH}`,
  168. loggerCtx,
  169. );
  170. return DEFAULT_APP_PATH;
  171. }
  172. }