@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.
- package/dist/plugin/types.d.ts +40 -0
- package/dist/plugin/utils/ast-utils.d.ts +0 -5
- package/dist/plugin/utils/ast-utils.js +0 -67
- package/dist/plugin/utils/ast-utils.spec.js +1 -76
- package/dist/plugin/utils/compiler.d.ts +22 -0
- package/dist/plugin/utils/compiler.js +162 -0
- package/dist/plugin/utils/config-loader.d.ts +0 -120
- package/dist/plugin/utils/config-loader.js +1 -367
- package/dist/plugin/utils/logger.d.ts +3 -0
- package/dist/plugin/utils/logger.js +39 -0
- package/dist/plugin/utils/plugin-discovery.d.ts +27 -0
- package/dist/plugin/utils/plugin-discovery.js +343 -0
- package/dist/plugin/utils/tsconfig-utils.d.ts +9 -0
- package/dist/plugin/utils/tsconfig-utils.js +50 -0
- package/dist/plugin/vite-plugin-config-loader.d.ts +3 -3
- package/dist/plugin/vite-plugin-config-loader.js +13 -13
- package/dist/plugin/vite-plugin-dashboard-metadata.js +19 -2
- package/dist/plugin/vite-plugin-vendure-dashboard.d.ts +7 -7
- package/dist/plugin/vite-plugin-vendure-dashboard.js +2 -2
- package/package.json +134 -131
- package/src/app/routes/_authenticated/_orders/components/customer-address-selector.tsx +0 -1
- package/src/app/routes/_authenticated/_orders/components/payment-details.tsx +6 -2
- package/src/app/routes/_authenticated/_orders/components/state-transition-control.tsx +2 -2
- package/src/app/routes/_authenticated/_orders/orders_.draft.$id.tsx +0 -1
- package/src/app/routes/_authenticated/_products/components/option-value-input.tsx +1 -1
- package/src/app/routes/_authenticated/_zones/components/zone-countries-table.tsx +0 -7
- package/src/lib/components/layout/content-language-selector.tsx +1 -1
- package/src/lib/components/shared/asset/asset-preview.tsx +0 -6
- package/src/lib/components/shared/option-value-input.tsx +1 -1
- package/src/lib/components/shared/product-variant-selector.tsx +1 -1
- package/src/lib/components/ui/calendar.tsx +1 -1
- package/src/lib/framework/dashboard-widget/metrics-widget/index.tsx +1 -1
- package/src/lib/framework/dashboard-widget/orders-summary/index.tsx +0 -2
- package/src/lib/hooks/use-extended-list-query.ts +32 -30
- package/vite/tests/barrel-exports.spec.ts +13 -4
- 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
- package/vite/tests/fixtures-npm-plugin/fake_node_modules/test-plugin/package.json +8 -0
- package/vite/tests/fixtures-npm-plugin/package.json +6 -0
- package/{dist/plugin/tests/barrel-exports/vendure-config.js → vite/tests/fixtures-npm-plugin/vendure-config.ts} +5 -6
- package/vite/tests/fixtures-path-alias/aliased-plugin/index.ts +1 -0
- package/vite/tests/fixtures-path-alias/aliased-plugin/src/aliased.plugin.ts +8 -0
- package/vite/tests/fixtures-path-alias/package.json +6 -0
- package/vite/tests/fixtures-path-alias/vendure-config.ts +19 -0
- package/vite/tests/npm-plugin.spec.ts +46 -0
- package/vite/tests/path-alias.spec.ts +33 -0
- package/vite/tests/tsconfig.json +11 -0
- package/vite/types.ts +44 -0
- package/vite/utils/ast-utils.spec.ts +1 -80
- package/vite/utils/ast-utils.ts +0 -86
- package/vite/utils/compiler.ts +244 -0
- package/vite/utils/config-loader.ts +0 -555
- package/vite/utils/logger.ts +43 -0
- package/vite/utils/plugin-discovery.ts +442 -0
- package/vite/utils/tsconfig-utils.ts +79 -0
- package/vite/vite-plugin-config-loader.ts +20 -17
- package/vite/vite-plugin-dashboard-metadata.ts +26 -7
- package/vite/vite-plugin-tailwind-source.ts +2 -2
- package/vite/vite-plugin-vendure-dashboard.ts +9 -9
- package/dist/plugin/tests/barrel-exports/my-plugin/index.d.ts +0 -1
- package/dist/plugin/tests/barrel-exports/my-plugin/index.js +0 -17
- package/dist/plugin/tests/barrel-exports/my-plugin/src/my.plugin.d.ts +0 -2
- package/dist/plugin/tests/barrel-exports/vendure-config.d.ts +0 -2
- package/dist/plugin/tests/barrel-exports.spec.js +0 -14
- /package/dist/plugin/{tests/barrel-exports.spec.d.ts → types.js} +0 -0
- /package/vite/tests/{barrel-exports → fixtures-barrel-exports}/my-plugin/index.ts +0 -0
- /package/vite/tests/{barrel-exports → fixtures-barrel-exports}/my-plugin/src/my.plugin.ts +0 -0
- /package/vite/tests/{barrel-exports → fixtures-barrel-exports}/package.json +0 -0
- /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 {
|
|
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<
|
|
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:
|
|
16
|
-
let result:
|
|
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
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
|
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<
|
|
59
|
+
getVendureConfig(): Promise<CompileResult> {
|
|
57
60
|
if (result) {
|
|
58
61
|
return Promise.resolve(result);
|
|
59
62
|
} else {
|
|
60
|
-
return new Promise<
|
|
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 {
|
|
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:
|
|
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
|
-
(
|
|
38
|
-
|
|
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(
|
|
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 {
|
|
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:
|
|
14
|
+
let loadVendureConfigResult: CompileResult;
|
|
15
15
|
return {
|
|
16
16
|
name: 'vendure:dashboard-tailwind-source',
|
|
17
17
|
// Ensure this plugin runs before Tailwind CSS processing
|