Browse Source

fix(dashboard): Dashboard plugin detection with pnpm (#4126)

Oliver Streißelberger 2 days ago
parent
commit
896dc620fc

+ 2 - 0
packages/dashboard/vite/tests/fixtures-pnpm-plugin/.gitignore

@@ -0,0 +1,2 @@
+# Force include pnpm structure for testing plugin discovery
+!fake_node_modules/.pnpm/**/node_modules/

+ 19 - 0
packages/dashboard/vite/tests/fixtures-pnpm-plugin/fake_node_modules/.pnpm/test-plugin@1.0.0/node_modules/test-plugin/index.js

@@ -0,0 +1,19 @@
+"use strict";
+var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
+    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+    return c > 3 && r && Object.defineProperty(target, key, r), r;
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.TestPlugin = void 0;
+const core_1 = require("@vendure/core");
+let TestPlugin = class TestPlugin {
+};
+exports.TestPlugin = TestPlugin;
+exports.TestPlugin = TestPlugin = __decorate([
+    (0, core_1.VendurePlugin)({
+        imports: [core_1.PluginCommonModule],
+        dashboard: './dashboard/index.tsx',
+    })
+], TestPlugin);

+ 5 - 0
packages/dashboard/vite/tests/fixtures-pnpm-plugin/fake_node_modules/.pnpm/test-plugin@1.0.0/node_modules/test-plugin/package.json

@@ -0,0 +1,5 @@
+{
+  "name": "test-plugin",
+  "version": "1.0.0",
+  "main": "index.js"
+}

+ 1 - 0
packages/dashboard/vite/tests/fixtures-pnpm-plugin/fake_node_modules/test-plugin

@@ -0,0 +1 @@
+.pnpm/test-plugin@1.0.0/node_modules/test-plugin

+ 7 - 0
packages/dashboard/vite/tests/fixtures-pnpm-plugin/package.json

@@ -0,0 +1,7 @@
+{
+  "name": "fixtures-pnpm-plugin",
+  "version": "1.0.0",
+  "dependencies": {
+    "test-plugin": "1.0.0"
+  }
+}

+ 18 - 0
packages/dashboard/vite/tests/fixtures-pnpm-plugin/vendure-config.ts

@@ -0,0 +1,18 @@
+import { VendureConfig } from '@vendure/core';
+import { TestPlugin } from 'test-plugin';
+
+export const config: VendureConfig = {
+    apiOptions: {
+        port: 3000,
+    },
+    authOptions: {
+        tokenMethod: 'bearer',
+    },
+    dbConnectionOptions: {
+        type: 'postgres',
+    },
+    paymentOptions: {
+        paymentMethodHandlers: [],
+    },
+    plugins: [TestPlugin],
+};

+ 58 - 0
packages/dashboard/vite/tests/pnpm-plugin.spec.ts

@@ -0,0 +1,58 @@
+import { rm } from 'node:fs/promises';
+import { join } from 'node:path';
+import tsconfigPaths from 'tsconfig-paths';
+import { describe, expect, it } from 'vitest';
+
+import { compile } from '../utils/compiler.js';
+import { debugLogger, noopLogger } from '../utils/logger.js';
+import { findVendurePluginFiles } from '../utils/plugin-discovery.js';
+
+describe('detecting plugins in pnpm packages', () => {
+    it('should detect plugins in pnpm node_modules structure', { timeout: 60_000 }, async () => {
+        const tempDir = join(__dirname, './__temp/pnpm-plugin');
+        await rm(tempDir, { recursive: true, force: true });
+        const nodeModulesRoot = join(__dirname, 'fixtures-pnpm-plugin', 'fake_node_modules');
+
+        tsconfigPaths.register({
+            baseUrl: nodeModulesRoot,
+            paths: {
+                'test-plugin': [join(nodeModulesRoot, 'test-plugin')],
+            },
+        });
+
+        const result = await compile({
+            outputPath: tempDir,
+            vendureConfigPath: join(__dirname, 'fixtures-pnpm-plugin', 'vendure-config.ts'),
+            logger: process.env.LOG ? debugLogger : noopLogger,
+            pluginPackageScanner: {
+                nodeModulesRoot,
+            },
+        });
+
+        expect(result.pluginInfo).toHaveLength(1);
+        expect(result.pluginInfo[0].name).toBe('TestPlugin');
+        expect(result.pluginInfo[0].dashboardEntryPath).toBe('./dashboard/index.tsx');
+        expect(result.pluginInfo[0].sourcePluginPath).toBeUndefined();
+        // Plugin found via pnpm symlink
+        expect(result.pluginInfo[0].pluginPath).toBe(join(nodeModulesRoot, 'test-plugin', 'index.js'));
+    });
+
+    it('should not filter out files in pnpm nested node_modules paths', async () => {
+        const nodeModulesRoot = join(__dirname, 'fixtures-pnpm-plugin', 'fake_node_modules');
+        const pnpmPath = '.pnpm/test-plugin@1.0.0/node_modules/test-plugin';
+
+        // Directly scan the .pnpm directory to verify files with nested node_modules
+        // paths are not filtered out by glob ignore patterns
+        const files = await findVendurePluginFiles({
+            outputPath: join(__dirname, './__temp/pnpm-ignore-test'),
+            vendureConfigPath: join(__dirname, 'fixtures-pnpm-plugin', 'vendure-config.ts'),
+            logger: noopLogger,
+            nodeModulesRoot,
+            packageGlobs: [pnpmPath + '/**/*.js'],
+        });
+
+        expect(files).toHaveLength(1);
+        expect(files[0]).toContain('.pnpm');
+        expect(files[0]).toContain('node_modules/test-plugin');
+    });
+});

+ 3 - 2
packages/dashboard/vite/utils/plugin-discovery.ts

@@ -420,8 +420,9 @@ export async function findVendurePluginFiles({
     const globStart = Date.now();
     const files = await glob(patterns, {
         ignore: [
-            // Standard test & doc files
-            '**/node_modules/**/node_modules/**',
+            // Skip nested node_modules (transitive deps) but not .pnpm directory.
+            // [!.] ensures .pnpm paths are kept since pnpm stores all packages there.
+            '**/node_modules/[!.pnpm]*/**/node_modules/**',
             '**/*.spec.js',
             '**/*.test.js',
         ],