Просмотр исходного кода

fix(core): Ensure plugin `configure` functions run only once

Michael Bromley 3 лет назад
Родитель
Сommit
044a968a26

+ 2 - 0
packages/core/e2e/fixtures/test-plugins/with-config.ts

@@ -6,10 +6,12 @@ import { ConfigModule, VendurePlugin } from '@vendure/core';
     configuration: config => {
         // tslint:disable-next-line:no-non-null-assertion
         config.defaultLanguageCode = LanguageCode.zh;
+        TestPluginWithConfig.configSpy();
         return config;
     },
 })
 export class TestPluginWithConfig {
+    static configSpy = jest.fn();
     static setup() {
         return TestPluginWithConfig;
     }

+ 22 - 0
packages/core/e2e/plugin.e2e-spec.ts

@@ -48,6 +48,7 @@ describe('Plugins', () => {
         const configService = server.app.get(ConfigService);
         expect(configService instanceof ConfigService).toBe(true);
         expect(configService.defaultLanguageCode).toBe(LanguageCode.zh);
+        expect(TestPluginWithConfig.configSpy).toHaveBeenCalledTimes(1);
     });
 
     it('extends the admin API', async () => {
@@ -137,3 +138,24 @@ describe('Plugins', () => {
         });
     });
 });
+
+describe('Multiple bootstraps in same process', () => {
+    const activeConfig = testConfig();
+    const { server, adminClient, shopClient } = createTestEnvironment({
+        ...activeConfig,
+        plugins: [TestPluginWithConfig.setup()],
+    });
+
+    beforeAll(async () => {
+        await server.init({
+            initialData,
+            productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-full.csv'),
+            customerCount: 1,
+        });
+        await adminClient.asSuperAdmin();
+    }, TEST_SETUP_TIMEOUT_MS);
+
+    it('plugin `configure` function called only once', async () => {
+        expect(TestPluginWithConfig.configSpy).toHaveBeenCalledTimes(1);
+    });
+});

+ 12 - 0
packages/core/src/bootstrap.ts

@@ -156,6 +156,13 @@ export async function preBootstrapConfig(
     return config;
 }
 
+// This is here to prevent a plugin's `configure` function from executing more than once in the
+// same process. Running more than once can occur e.g. if a script runs the `runMigrations()` function
+// followed by the `bootstrap()` function, and will lead to very hard-to-debug errors caused by
+// mutating the config object twice, e.g. plugins pushing custom fields again resulting in duplicate
+// custom field definitions.
+const pluginConfigDidRun = new WeakSet<RuntimeVendureConfig['plugins'][number]>();
+
 /**
  * Initialize any configured plugins.
  */
@@ -163,7 +170,12 @@ async function runPluginConfigurations(config: RuntimeVendureConfig): Promise<Ru
     for (const plugin of config.plugins) {
         const configFn = getConfigurationFunction(plugin);
         if (typeof configFn === 'function') {
+            const configAlreadyRan = pluginConfigDidRun.has(plugin);
+            if (configAlreadyRan) {
+                continue;
+            }
             config = await configFn(config);
+            pluginConfigDidRun.add(plugin);
         }
     }
     return config;