@vendure/dashboard 3.3.6-master-202507110238 → 3.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/dist/plugin/types.d.ts +40 -0
  2. package/dist/plugin/utils/ast-utils.d.ts +0 -5
  3. package/dist/plugin/utils/ast-utils.js +0 -67
  4. package/dist/plugin/utils/ast-utils.spec.js +1 -76
  5. package/dist/plugin/utils/compiler.d.ts +22 -0
  6. package/dist/plugin/utils/compiler.js +162 -0
  7. package/dist/plugin/utils/config-loader.d.ts +0 -120
  8. package/dist/plugin/utils/config-loader.js +1 -367
  9. package/dist/plugin/utils/logger.d.ts +3 -0
  10. package/dist/plugin/utils/logger.js +39 -0
  11. package/dist/plugin/utils/plugin-discovery.d.ts +27 -0
  12. package/dist/plugin/utils/plugin-discovery.js +343 -0
  13. package/dist/plugin/utils/tsconfig-utils.d.ts +9 -0
  14. package/dist/plugin/utils/tsconfig-utils.js +50 -0
  15. package/dist/plugin/vite-plugin-config-loader.d.ts +3 -3
  16. package/dist/plugin/vite-plugin-config-loader.js +13 -13
  17. package/dist/plugin/vite-plugin-dashboard-metadata.js +19 -2
  18. package/dist/plugin/vite-plugin-vendure-dashboard.d.ts +7 -7
  19. package/dist/plugin/vite-plugin-vendure-dashboard.js +2 -2
  20. package/package.json +134 -131
  21. package/src/app/routes/_authenticated/_orders/components/customer-address-selector.tsx +0 -1
  22. package/src/app/routes/_authenticated/_orders/components/payment-details.tsx +6 -2
  23. package/src/app/routes/_authenticated/_orders/components/state-transition-control.tsx +2 -2
  24. package/src/app/routes/_authenticated/_orders/orders_.draft.$id.tsx +0 -1
  25. package/src/app/routes/_authenticated/_products/components/option-value-input.tsx +1 -1
  26. package/src/app/routes/_authenticated/_zones/components/zone-countries-table.tsx +0 -7
  27. package/src/lib/components/layout/content-language-selector.tsx +1 -1
  28. package/src/lib/components/shared/asset/asset-preview.tsx +0 -6
  29. package/src/lib/components/shared/option-value-input.tsx +1 -1
  30. package/src/lib/components/shared/product-variant-selector.tsx +1 -1
  31. package/src/lib/components/ui/calendar.tsx +1 -1
  32. package/src/lib/framework/dashboard-widget/metrics-widget/index.tsx +1 -1
  33. package/src/lib/framework/dashboard-widget/orders-summary/index.tsx +0 -2
  34. package/src/lib/hooks/use-extended-list-query.ts +32 -30
  35. package/vite/tests/barrel-exports.spec.ts +13 -4
  36. package/{dist/plugin/tests/barrel-exports/my-plugin/src/my.plugin.js → vite/tests/fixtures-npm-plugin/fake_node_modules/test-plugin/index.js} +6 -6
  37. package/vite/tests/fixtures-npm-plugin/fake_node_modules/test-plugin/package.json +8 -0
  38. package/vite/tests/fixtures-npm-plugin/package.json +6 -0
  39. package/{dist/plugin/tests/barrel-exports/vendure-config.js → vite/tests/fixtures-npm-plugin/vendure-config.ts} +5 -6
  40. package/vite/tests/fixtures-path-alias/aliased-plugin/index.ts +1 -0
  41. package/vite/tests/fixtures-path-alias/aliased-plugin/src/aliased.plugin.ts +8 -0
  42. package/vite/tests/fixtures-path-alias/package.json +6 -0
  43. package/vite/tests/fixtures-path-alias/vendure-config.ts +19 -0
  44. package/vite/tests/npm-plugin.spec.ts +46 -0
  45. package/vite/tests/path-alias.spec.ts +33 -0
  46. package/vite/tests/tsconfig.json +11 -0
  47. package/vite/types.ts +44 -0
  48. package/vite/utils/ast-utils.spec.ts +1 -80
  49. package/vite/utils/ast-utils.ts +0 -86
  50. package/vite/utils/compiler.ts +244 -0
  51. package/vite/utils/config-loader.ts +0 -555
  52. package/vite/utils/logger.ts +43 -0
  53. package/vite/utils/plugin-discovery.ts +442 -0
  54. package/vite/utils/tsconfig-utils.ts +79 -0
  55. package/vite/vite-plugin-config-loader.ts +20 -17
  56. package/vite/vite-plugin-dashboard-metadata.ts +26 -7
  57. package/vite/vite-plugin-tailwind-source.ts +2 -2
  58. package/vite/vite-plugin-vendure-dashboard.ts +9 -9
  59. package/dist/plugin/tests/barrel-exports/my-plugin/index.d.ts +0 -1
  60. package/dist/plugin/tests/barrel-exports/my-plugin/index.js +0 -17
  61. package/dist/plugin/tests/barrel-exports/my-plugin/src/my.plugin.d.ts +0 -2
  62. package/dist/plugin/tests/barrel-exports/vendure-config.d.ts +0 -2
  63. package/dist/plugin/tests/barrel-exports.spec.js +0 -14
  64. /package/dist/plugin/{tests/barrel-exports.spec.d.ts → types.js} +0 -0
  65. /package/vite/tests/{barrel-exports → fixtures-barrel-exports}/my-plugin/index.ts +0 -0
  66. /package/vite/tests/{barrel-exports → fixtures-barrel-exports}/my-plugin/src/my.plugin.ts +0 -0
  67. /package/vite/tests/{barrel-exports → fixtures-barrel-exports}/package.json +0 -0
  68. /package/vite/tests/{barrel-exports → fixtures-barrel-exports}/vendure-config.ts +0 -0
@@ -0,0 +1,442 @@
1
+ import { parse } from 'acorn';
2
+ import { simple as walkSimple } from 'acorn-walk';
3
+ import glob from 'fast-glob';
4
+ import fs from 'fs-extra';
5
+ import { open } from 'fs/promises';
6
+ import path from 'path';
7
+ import * as ts from 'typescript';
8
+ import { fileURLToPath } from 'url';
9
+
10
+ import { Logger, PluginInfo, TransformTsConfigPathMappingsFn } from '../types.js';
11
+
12
+ import { PackageScannerConfig } from './compiler.js';
13
+ import { findTsConfigPaths, TsConfigPathsConfig } from './tsconfig-utils.js';
14
+
15
+ export async function discoverPlugins({
16
+ vendureConfigPath,
17
+ transformTsConfigPathMappings,
18
+ logger,
19
+ outputPath,
20
+ pluginPackageScanner,
21
+ }: {
22
+ vendureConfigPath: string;
23
+ transformTsConfigPathMappings: TransformTsConfigPathMappingsFn;
24
+ logger: Logger;
25
+ outputPath: string;
26
+ pluginPackageScanner?: PackageScannerConfig;
27
+ }): Promise<PluginInfo[]> {
28
+ const plugins: PluginInfo[] = [];
29
+
30
+ // Analyze source files to find local plugins and package imports
31
+ const { localPluginLocations, packageImports } = await analyzeSourceFiles(
32
+ vendureConfigPath,
33
+ logger,
34
+ transformTsConfigPathMappings,
35
+ );
36
+ logger.debug(
37
+ `[discoverPlugins] Found ${localPluginLocations.size} local plugins: ${JSON.stringify([...localPluginLocations.entries()], null, 2)}`,
38
+ );
39
+ logger.debug(
40
+ `[discoverPlugins] Found ${packageImports.length} package imports: ${JSON.stringify(packageImports, null, 2)}`,
41
+ );
42
+
43
+ const filePaths = await findVendurePluginFiles({
44
+ logger,
45
+ nodeModulesRoot: pluginPackageScanner?.nodeModulesRoot,
46
+ packageGlobs: packageImports.map(pkg => pkg + '/**/*.js'),
47
+ outputPath,
48
+ vendureConfigPath,
49
+ });
50
+
51
+ for (const filePath of filePaths) {
52
+ const content = await fs.readFile(filePath, 'utf-8');
53
+ logger.debug(`[discoverPlugins] Checking file ${filePath}`);
54
+
55
+ // First check if this file imports from @vendure/core
56
+ if (!content.includes('@vendure/core')) {
57
+ continue;
58
+ }
59
+
60
+ try {
61
+ const ast = parse(content, {
62
+ ecmaVersion: 'latest',
63
+ sourceType: 'module',
64
+ });
65
+
66
+ let hasVendurePlugin = false;
67
+ let pluginName: string | undefined;
68
+ let dashboardPath: string | undefined;
69
+
70
+ // Walk the AST to find the plugin class and its decorator
71
+ walkSimple(ast, {
72
+ CallExpression(node: any) {
73
+ // Look for __decorate calls
74
+ const calleeName = node.callee.name;
75
+ const nodeArgs = node.arguments;
76
+ const isDecoratorWithArgs = calleeName === '__decorate' && nodeArgs.length >= 2;
77
+
78
+ if (isDecoratorWithArgs) {
79
+ // Check the decorators array (first argument)
80
+ const decorators = nodeArgs[0];
81
+ if (decorators.type === 'ArrayExpression') {
82
+ for (const decorator of decorators.elements) {
83
+ const props = getDecoratorObjectProps(decorator);
84
+ for (const prop of props) {
85
+ const isDashboardProd =
86
+ prop.key.name === 'dashboard' && prop.value.type === 'Literal';
87
+ if (isDashboardProd) {
88
+ dashboardPath = prop.value.value;
89
+ hasVendurePlugin = true;
90
+ }
91
+ }
92
+ }
93
+ }
94
+ // Get the plugin class name (second argument)
95
+ const targetClass = nodeArgs[1];
96
+ if (targetClass.type === 'Identifier') {
97
+ pluginName = targetClass.name;
98
+ }
99
+ }
100
+ },
101
+ });
102
+
103
+ if (hasVendurePlugin && pluginName && dashboardPath) {
104
+ logger.debug(`[discoverPlugins] Found plugin "${pluginName}" in file: ${filePath}`);
105
+ // Keep the dashboard path relative to the plugin file
106
+ const resolvedDashboardPath = dashboardPath.startsWith('.')
107
+ ? dashboardPath // Keep the relative path as-is
108
+ : './' + path.relative(path.dirname(filePath), dashboardPath); // Make absolute path relative
109
+
110
+ // Check if this is a local plugin we found earlier
111
+ const sourcePluginPath = localPluginLocations.get(pluginName);
112
+
113
+ plugins.push({
114
+ name: pluginName,
115
+ pluginPath: filePath,
116
+ dashboardEntryPath: resolvedDashboardPath,
117
+ ...(sourcePluginPath && { sourcePluginPath }),
118
+ });
119
+ }
120
+ } catch (e) {
121
+ logger.error(`Failed to parse ${filePath}: ${e instanceof Error ? e.message : String(e)}`);
122
+ }
123
+ }
124
+
125
+ return plugins;
126
+ }
127
+
128
+ function getDecoratorObjectProps(decorator: any): any[] {
129
+ if (
130
+ decorator.type === 'CallExpression' &&
131
+ decorator.arguments.length === 1 &&
132
+ decorator.arguments[0].type === 'ObjectExpression'
133
+ ) {
134
+ // Look for the dashboard property in the decorator config
135
+ return decorator.arguments[0].properties ?? [];
136
+ }
137
+ return [];
138
+ }
139
+
140
+ /**
141
+ * Analyzes TypeScript source files starting from the config file to discover:
142
+ * 1. Local Vendure plugins
143
+ * 2. All non-local package imports that could contain plugins
144
+ */
145
+ export async function analyzeSourceFiles(
146
+ vendureConfigPath: string,
147
+ logger: Logger,
148
+ transformTsConfigPathMappings: TransformTsConfigPathMappingsFn,
149
+ ): Promise<{
150
+ localPluginLocations: Map<string, string>;
151
+ packageImports: string[];
152
+ }> {
153
+ const localPluginLocations = new Map<string, string>();
154
+ const visitedFiles = new Set<string>();
155
+ const packageImportsSet = new Set<string>();
156
+
157
+ // Get tsconfig paths for resolving aliases
158
+ const tsConfigInfo = await findTsConfigPaths(
159
+ vendureConfigPath,
160
+ logger,
161
+ 'compiling',
162
+ transformTsConfigPathMappings,
163
+ );
164
+
165
+ async function processFile(filePath: string) {
166
+ if (visitedFiles.has(filePath)) {
167
+ return;
168
+ }
169
+ visitedFiles.add(filePath);
170
+
171
+ try {
172
+ // First check if this is a directory
173
+ const stat = await fs.stat(filePath);
174
+ if (stat.isDirectory()) {
175
+ // If it's a directory, try to find the plugin file
176
+ const indexFilePath = path.join(filePath, 'index.ts');
177
+ if (await fs.pathExists(indexFilePath)) {
178
+ await processFile(indexFilePath);
179
+ }
180
+ return;
181
+ }
182
+
183
+ const content = await fs.readFile(filePath, 'utf-8');
184
+ const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
185
+
186
+ // Track imports to follow
187
+ const importsToFollow: string[] = [];
188
+
189
+ function visit(node: ts.Node) {
190
+ // Look for VendurePlugin decorator
191
+ const vendurePluginClassName = getVendurePluginClassName(node);
192
+ if (vendurePluginClassName) {
193
+ localPluginLocations.set(vendurePluginClassName, filePath);
194
+ logger.debug(`Found plugin "${vendurePluginClassName}" at ${filePath}`);
195
+ }
196
+
197
+ // Handle both imports and exports
198
+ const isImportOrExport = ts.isImportDeclaration(node) || ts.isExportDeclaration(node);
199
+ if (isImportOrExport) {
200
+ const moduleSpecifier = node.moduleSpecifier;
201
+ if (moduleSpecifier && ts.isStringLiteral(moduleSpecifier)) {
202
+ const importPath = moduleSpecifier.text;
203
+
204
+ // Track non-local imports (packages)
205
+ const npmPackageName = getNpmPackageNameFromImport(importPath);
206
+ if (npmPackageName) {
207
+ packageImportsSet.add(npmPackageName);
208
+ }
209
+ // Handle path aliases and local imports
210
+ const pathAliasImports = getPotentialPathAliasImportPaths(importPath, tsConfigInfo);
211
+ if (pathAliasImports.length) {
212
+ importsToFollow.push(...pathAliasImports);
213
+ }
214
+ // Also handle local imports
215
+ if (importPath.startsWith('.')) {
216
+ const resolvedPath = path.resolve(path.dirname(filePath), importPath);
217
+ importsToFollow.push(resolvedPath);
218
+ }
219
+ }
220
+ }
221
+
222
+ ts.forEachChild(node, visit);
223
+ }
224
+
225
+ visit(sourceFile);
226
+
227
+ // Follow imports
228
+ for (const importPath of importsToFollow) {
229
+ // Try all possible file paths
230
+ const possiblePaths = [
231
+ importPath + '.ts',
232
+ importPath + '.js',
233
+ path.join(importPath, 'index.ts'),
234
+ path.join(importPath, 'index.js'),
235
+ ];
236
+
237
+ // Try each possible path
238
+ let found = false;
239
+ for (const possiblePath of possiblePaths) {
240
+ const possiblePathExists = await fs.pathExists(possiblePath);
241
+ if (possiblePathExists) {
242
+ await processFile(possiblePath);
243
+ found = true;
244
+ break;
245
+ }
246
+ }
247
+
248
+ // If none of the file paths worked, try the raw import path
249
+ // (it might be a directory)
250
+ const tryRawPath = !found && (await fs.pathExists(importPath));
251
+ if (tryRawPath) {
252
+ await processFile(importPath);
253
+ }
254
+ }
255
+ } catch (e) {
256
+ const message = e instanceof Error ? e.message : String(e);
257
+ logger.error(`Failed to process ${filePath}: ${message}`);
258
+ }
259
+ }
260
+
261
+ await processFile(vendureConfigPath);
262
+ return {
263
+ localPluginLocations,
264
+ packageImports: Array.from(packageImportsSet),
265
+ };
266
+ }
267
+
268
+ /**
269
+ * If this is a class declaration that is decorated with the `VendurePlugin` decorator,
270
+ * we want to return that class name, as we have found a local Vendure plugin.
271
+ */
272
+ function getVendurePluginClassName(node: ts.Node): string | undefined {
273
+ if (ts.isClassDeclaration(node)) {
274
+ const decorators = ts.canHaveDecorators(node) ? ts.getDecorators(node) : undefined;
275
+ if (decorators?.length) {
276
+ for (const decorator of decorators) {
277
+ const decoratorName = getDecoratorName(decorator);
278
+ if (decoratorName === 'VendurePlugin') {
279
+ const className = node.name?.text;
280
+ if (className) {
281
+ return className;
282
+ }
283
+ }
284
+ }
285
+ }
286
+ }
287
+ }
288
+
289
+ function getNpmPackageNameFromImport(importPath: string): string | undefined {
290
+ if (!importPath.startsWith('.') && !importPath.startsWith('/')) {
291
+ // Get the root package name (e.g. '@scope/package/subpath' -> '@scope/package')
292
+ const packageName = importPath.startsWith('@')
293
+ ? importPath.split('/').slice(0, 2).join('/')
294
+ : importPath.split('/')[0];
295
+ return packageName;
296
+ }
297
+ }
298
+
299
+ function getPotentialPathAliasImportPaths(importPath: string, tsConfigInfo?: TsConfigPathsConfig) {
300
+ const importsToFollow: string[] = [];
301
+ if (!tsConfigInfo) {
302
+ return importsToFollow;
303
+ }
304
+ for (const [alias, patterns] of Object.entries(tsConfigInfo.paths)) {
305
+ const aliasPattern = alias.replace(/\*$/, '');
306
+ if (importPath.startsWith(aliasPattern)) {
307
+ const relativePart = importPath.slice(aliasPattern.length);
308
+ // Try each pattern
309
+ for (const pattern of patterns) {
310
+ const resolvedPattern = pattern.replace(/\*$/, '');
311
+ const resolvedPath = path.resolve(tsConfigInfo.baseUrl, resolvedPattern, relativePart);
312
+ importsToFollow.push(resolvedPath);
313
+ }
314
+ }
315
+ }
316
+ return importsToFollow;
317
+ }
318
+
319
+ function getDecoratorName(decorator: ts.Decorator): string | undefined {
320
+ if (ts.isCallExpression(decorator.expression)) {
321
+ const expression = decorator.expression.expression;
322
+ // Handle both direct usage and imported usage
323
+ if (ts.isIdentifier(expression)) {
324
+ return expression.text;
325
+ }
326
+ // Handle property access like `Decorators.VendurePlugin`
327
+ if (ts.isPropertyAccessExpression(expression)) {
328
+ return expression.name.text;
329
+ }
330
+ }
331
+ return undefined;
332
+ }
333
+
334
+ interface FindPluginFilesOptions {
335
+ outputPath: string;
336
+ vendureConfigPath: string;
337
+ logger: Logger;
338
+ packageGlobs: string[];
339
+ nodeModulesRoot?: string;
340
+ }
341
+
342
+ export async function findVendurePluginFiles({
343
+ outputPath,
344
+ vendureConfigPath,
345
+ logger,
346
+ nodeModulesRoot: providedNodeModulesRoot,
347
+ packageGlobs,
348
+ }: FindPluginFilesOptions): Promise<string[]> {
349
+ let nodeModulesRoot = providedNodeModulesRoot;
350
+ const readStart = Date.now();
351
+ if (!nodeModulesRoot) {
352
+ // If the node_modules root path has not been explicitly
353
+ // specified, we will try to guess it by resolving the
354
+ // `@vendure/core` package.
355
+ try {
356
+ const coreUrl = import.meta.resolve('@vendure/core');
357
+ logger.debug(`Found core URL: ${coreUrl}`);
358
+ const corePath = fileURLToPath(coreUrl);
359
+ logger.debug(`Found core path: ${corePath}`);
360
+ nodeModulesRoot = path.join(path.dirname(corePath), '..', '..');
361
+ } catch (e) {
362
+ logger.warn(`Failed to resolve @vendure/core: ${e instanceof Error ? e.message : String(e)}`);
363
+ nodeModulesRoot = path.dirname(vendureConfigPath);
364
+ }
365
+ }
366
+
367
+ const patterns = [
368
+ // Local compiled plugins in temp dir
369
+ path.join(outputPath, '**/*.js'),
370
+ // Node modules patterns
371
+ ...packageGlobs.map(pattern => path.join(nodeModulesRoot, pattern)),
372
+ ];
373
+
374
+ logger.debug(`Finding Vendure plugins using patterns: ${patterns.join('\n')}`);
375
+
376
+ const globStart = Date.now();
377
+ const files = await glob(patterns, {
378
+ ignore: [
379
+ // Standard test & doc files
380
+ '**/node_modules/**/node_modules/**',
381
+ '**/*.spec.js',
382
+ '**/*.test.js',
383
+ ],
384
+ onlyFiles: true,
385
+ absolute: true,
386
+ followSymbolicLinks: false,
387
+ stats: false,
388
+ });
389
+ logger.debug(`Glob found ${files.length} files in ${Date.now() - globStart}ms`);
390
+
391
+ // Read files in larger parallel batches
392
+ const batchSize = 100; // Increased batch size
393
+ const potentialPluginFiles: string[] = [];
394
+
395
+ for (let i = 0; i < files.length; i += batchSize) {
396
+ const batch = files.slice(i, i + batchSize);
397
+ const results = await Promise.all(
398
+ batch.map(async file => {
399
+ try {
400
+ // Try reading just first 3000 bytes first - most imports are at the top
401
+ const fileHandle = await open(file, 'r');
402
+ try {
403
+ const buffer = Buffer.alloc(3000);
404
+ const { bytesRead } = await fileHandle.read(buffer, 0, 3000, 0);
405
+ let content = buffer.toString('utf8', 0, bytesRead);
406
+
407
+ // Quick check for common indicators
408
+ if (content.includes('@vendure/core')) {
409
+ return file;
410
+ }
411
+
412
+ // If we find a promising indicator but no definitive match,
413
+ // read more of the file
414
+ if (content.includes('@vendure') || content.includes('VendurePlugin')) {
415
+ const largerBuffer = Buffer.alloc(5000);
416
+ const { bytesRead: moreBytes } = await fileHandle.read(largerBuffer, 0, 5000, 0);
417
+ content = largerBuffer.toString('utf8', 0, moreBytes);
418
+ if (content.includes('@vendure/core')) {
419
+ return file;
420
+ }
421
+ }
422
+ } finally {
423
+ await fileHandle.close();
424
+ }
425
+ } catch (e: any) {
426
+ logger.warn(`Failed to read file ${file}: ${e instanceof Error ? e.message : String(e)}`);
427
+ }
428
+ return null;
429
+ }),
430
+ );
431
+
432
+ const validResults = results.filter((f): f is string => f !== null);
433
+ potentialPluginFiles.push(...validResults);
434
+ }
435
+
436
+ logger.info(
437
+ `Found ${potentialPluginFiles.length} potential plugin files in ${Date.now() - readStart}ms ` +
438
+ `(scanned ${files.length} files)`,
439
+ );
440
+
441
+ return potentialPluginFiles;
442
+ }
@@ -0,0 +1,79 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import { CompilerOptions } from 'typescript';
4
+
5
+ import { Logger, TransformTsConfigPathMappingsFn } from '../types.js';
6
+
7
+ export interface TsConfigPathsConfig {
8
+ baseUrl: string;
9
+ paths: Record<string, string[]>;
10
+ }
11
+
12
+ /**
13
+ * Finds and parses tsconfig files in the given directory and its parent directories.
14
+ */
15
+ export async function findTsConfigPaths(
16
+ configPath: string,
17
+ logger: Logger,
18
+ phase: 'compiling' | 'loading',
19
+ transformTsConfigPathMappings: TransformTsConfigPathMappingsFn,
20
+ ): Promise<TsConfigPathsConfig | undefined> {
21
+ let currentDir = path.dirname(configPath);
22
+
23
+ while (currentDir !== path.parse(currentDir).root) {
24
+ try {
25
+ const files = await fs.readdir(currentDir);
26
+ const tsConfigFiles = files.filter(file => /^tsconfig(\..*)?\.json$/.test(file));
27
+
28
+ for (const fileName of tsConfigFiles) {
29
+ const tsConfigFilePath = path.join(currentDir, fileName);
30
+ try {
31
+ const { paths, baseUrl } = await getCompilerOptionsFromFile(tsConfigFilePath);
32
+ if (paths) {
33
+ const tsConfigBaseUrl = path.resolve(currentDir, baseUrl || '.');
34
+ const pathMappings = getTransformedPathMappings(
35
+ paths,
36
+ phase,
37
+ transformTsConfigPathMappings,
38
+ );
39
+ return { baseUrl: tsConfigBaseUrl, paths: pathMappings };
40
+ }
41
+ } catch (e) {
42
+ logger.warn(
43
+ `Could not read or parse tsconfig file ${tsConfigFilePath}: ${e instanceof Error ? e.message : String(e)}`,
44
+ );
45
+ }
46
+ }
47
+ } catch (e) {
48
+ logger.warn(
49
+ `Could not read directory ${currentDir}: ${e instanceof Error ? e.message : String(e)}`,
50
+ );
51
+ }
52
+ currentDir = path.dirname(currentDir);
53
+ }
54
+ return undefined;
55
+ }
56
+
57
+ async function getCompilerOptionsFromFile(tsConfigFilePath: string): Promise<CompilerOptions> {
58
+ const tsConfigContent = await fs.readFile(tsConfigFilePath, 'utf-8');
59
+ const tsConfig = JSON.parse(tsConfigContent);
60
+ return tsConfig.compilerOptions || {};
61
+ }
62
+
63
+ function getTransformedPathMappings(
64
+ paths: Required<CompilerOptions>['paths'],
65
+ phase: 'compiling' | 'loading',
66
+ transformTsConfigPathMappings: TransformTsConfigPathMappingsFn,
67
+ ) {
68
+ const pathMappings: Record<string, string[]> = {};
69
+
70
+ for (const [alias, patterns] of Object.entries(paths)) {
71
+ const normalizedPatterns = patterns.map(pattern => pattern.replace(/\\/g, '/'));
72
+ pathMappings[alias] = transformTsConfigPathMappings({
73
+ phase,
74
+ alias,
75
+ patterns: normalizedPatterns,
76
+ });
77
+ }
78
+ return pathMappings;
79
+ }
@@ -1,9 +1,10 @@
1
1
  import { Plugin } from 'vite';
2
2
 
3
- import { ConfigLoaderOptions, loadVendureConfig, LoadVendureConfigResult } from './utils/config-loader.js';
3
+ import { compile, CompileResult, CompilerOptions } from './utils/compiler.js';
4
+ import { debugLogger } from './utils/logger.js';
4
5
 
5
6
  export interface ConfigLoaderApi {
6
- getVendureConfig(): Promise<LoadVendureConfigResult>;
7
+ getVendureConfig(): Promise<CompileResult>;
7
8
  }
8
9
 
9
10
  export const configLoaderName = 'vendure:config-loader';
@@ -12,8 +13,8 @@ export const configLoaderName = 'vendure:config-loader';
12
13
  * This Vite plugin loads the VendureConfig from the specified file path, and
13
14
  * makes it available to other plugins via the `ConfigLoaderApi`.
14
15
  */
15
- export function configLoaderPlugin(options: ConfigLoaderOptions): Plugin {
16
- let result: LoadVendureConfigResult;
16
+ export function configLoaderPlugin(options: CompilerOptions): Plugin {
17
+ let result: CompileResult;
17
18
  const onConfigLoaded: Array<() => void> = [];
18
19
  return {
19
20
  name: configLoaderName,
@@ -23,20 +24,22 @@ export function configLoaderPlugin(options: ConfigLoaderOptions): Plugin {
23
24
  );
24
25
  try {
25
26
  const startTime = Date.now();
26
- result = await loadVendureConfig({
27
- pathAdapter: options.pathAdapter,
28
- tempDir: options.tempDir,
29
- vendureConfigPath: options.vendureConfigPath,
30
- vendureConfigExport: options.vendureConfigExport,
31
- logger: {
32
- info: (message: string) => this.info(message),
33
- warn: (message: string) => this.warn(message),
34
- debug: (message: string) => this.debug(message),
35
- },
27
+ result = await compile({
28
+ ...options,
29
+ logger: process.env.LOG
30
+ ? debugLogger
31
+ : {
32
+ info: (message: string) => this.info(message),
33
+ warn: (message: string) => this.warn(message),
34
+ debug: (message: string) => this.debug(message),
35
+ error: (message: string) => this.error(message),
36
+ },
36
37
  });
37
38
  const endTime = Date.now();
38
39
  const duration = endTime - startTime;
39
- const pluginNames = result.pluginInfo.map(p => p.name).join(', ');
40
+ const pluginNames = result.pluginInfo
41
+ .map(p => `${p.name} ${p.sourcePluginPath ? '(local)' : '(npm)'}`)
42
+ .join(', ');
40
43
  this.info(`Found ${result.pluginInfo.length} plugins: ${pluginNames}`);
41
44
  this.info(
42
45
  `Vendure config loaded (using export "${result.exportedSymbolName}") in ${duration}ms`,
@@ -53,11 +56,11 @@ export function configLoaderPlugin(options: ConfigLoaderOptions): Plugin {
53
56
  onConfigLoaded.forEach(fn => fn());
54
57
  },
55
58
  api: {
56
- getVendureConfig(): Promise<LoadVendureConfigResult> {
59
+ getVendureConfig(): Promise<CompileResult> {
57
60
  if (result) {
58
61
  return Promise.resolve(result);
59
62
  } else {
60
- return new Promise<LoadVendureConfigResult>(resolve => {
63
+ return new Promise<CompileResult>(resolve => {
61
64
  onConfigLoaded.push(() => {
62
65
  resolve(result);
63
66
  });
@@ -1,7 +1,7 @@
1
1
  import path from 'path';
2
2
  import { Plugin } from 'vite';
3
3
 
4
- import { LoadVendureConfigResult } from './utils/config-loader.js';
4
+ import { CompileResult } from './utils/compiler.js';
5
5
  import { ConfigLoaderApi, getConfigLoaderApi } from './vite-plugin-config-loader.js';
6
6
 
7
7
  const virtualModuleId = 'virtual:dashboard-extensions';
@@ -14,7 +14,7 @@ const resolvedVirtualModuleId = `\0${virtualModuleId}`;
14
14
  */
15
15
  export function dashboardMetadataPlugin(): Plugin {
16
16
  let configLoaderApi: ConfigLoaderApi;
17
- let loadVendureConfigResult: LoadVendureConfigResult;
17
+ let loadVendureConfigResult: CompileResult;
18
18
  return {
19
19
  name: 'vendure:dashboard-extensions-metadata',
20
20
  configResolved({ plugins }) {
@@ -27,19 +27,38 @@ export function dashboardMetadataPlugin(): Plugin {
27
27
  },
28
28
  async load(id) {
29
29
  if (id === resolvedVirtualModuleId) {
30
+ const startTime = Date.now();
31
+ this.debug('Loading dashboard extensions...');
32
+
30
33
  if (!loadVendureConfigResult) {
34
+ const configStart = Date.now();
31
35
  loadVendureConfigResult = await configLoaderApi.getVendureConfig();
36
+ this.debug(`Loaded Vendure config in ${Date.now() - configStart}ms`);
32
37
  }
38
+
33
39
  const { pluginInfo } = loadVendureConfigResult;
40
+ const resolveStart = Date.now();
34
41
  const pluginsWithExtensions =
35
42
  pluginInfo
36
- ?.map(
37
- ({ dashboardEntryPath, pluginPath }) =>
38
- dashboardEntryPath && path.join(pluginPath, dashboardEntryPath),
39
- )
43
+ ?.map(({ dashboardEntryPath, pluginPath, sourcePluginPath }) => {
44
+ if (!dashboardEntryPath) {
45
+ return null;
46
+ }
47
+ // For local plugins, use the sourcePluginPath to resolve the dashboard extension
48
+ const basePath = sourcePluginPath
49
+ ? path.dirname(sourcePluginPath)
50
+ : path.dirname(pluginPath);
51
+ const resolved = path.resolve(basePath, dashboardEntryPath);
52
+ this.debug(`Resolved extension path: ${resolved}`);
53
+ return resolved;
54
+ })
40
55
  .filter(x => x != null) ?? [];
41
56
 
42
- this.info(`Found ${pluginsWithExtensions.length} Dashboard extensions`);
57
+ this.info(
58
+ `Found ${pluginsWithExtensions.length} Dashboard extensions in ${Date.now() - resolveStart}ms`,
59
+ );
60
+ this.debug(`Total dashboard extension loading completed in ${Date.now() - startTime}ms`);
61
+
43
62
  return `
44
63
  export async function runDashboardExtensions() {
45
64
  ${pluginsWithExtensions
@@ -1,7 +1,7 @@
1
1
  import path from 'path';
2
2
  import { Plugin } from 'vite';
3
3
 
4
- import { LoadVendureConfigResult } from './utils/config-loader.js';
4
+ import { CompileResult } from './utils/compiler.js';
5
5
  import { ConfigLoaderApi, getConfigLoaderApi } from './vite-plugin-config-loader.js';
6
6
 
7
7
  /**
@@ -11,7 +11,7 @@ import { ConfigLoaderApi, getConfigLoaderApi } from './vite-plugin-config-loader
11
11
  */
12
12
  export function dashboardTailwindSourcePlugin(): Plugin {
13
13
  let configLoaderApi: ConfigLoaderApi;
14
- let loadVendureConfigResult: LoadVendureConfigResult;
14
+ let loadVendureConfigResult: CompileResult;
15
15
  return {
16
16
  name: 'vendure:dashboard-tailwind-source',
17
17
  // Ensure this plugin runs before Tailwind CSS processing