@vendure/dashboard 3.3.8-master-202507231129 → 3.3.8-master-202507240240

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.
@@ -12,7 +12,7 @@ export declare function discoverPlugins({ vendureConfigPath, transformTsConfigPa
12
12
  * 1. Local Vendure plugins
13
13
  * 2. All non-local package imports that could contain plugins
14
14
  */
15
- export declare function analyzeSourceFiles(vendureConfigPath: string, logger: Logger, transformTsConfigPathMappings: TransformTsConfigPathMappingsFn): Promise<{
15
+ export declare function analyzeSourceFiles(vendureConfigPath: string, nodeModulesRoot: string, logger: Logger, transformTsConfigPathMappings: TransformTsConfigPathMappingsFn): Promise<{
16
16
  localPluginLocations: Map<string, string>;
17
17
  packageImports: string[];
18
18
  }>;
@@ -8,9 +8,10 @@ import * as ts from 'typescript';
8
8
  import { fileURLToPath } from 'url';
9
9
  import { findTsConfigPaths } from './tsconfig-utils.js';
10
10
  export async function discoverPlugins({ vendureConfigPath, transformTsConfigPathMappings, logger, outputPath, pluginPackageScanner, }) {
11
+ var _a;
11
12
  const plugins = [];
12
13
  // Analyze source files to find local plugins and package imports
13
- const { localPluginLocations, packageImports } = await analyzeSourceFiles(vendureConfigPath, logger, transformTsConfigPathMappings);
14
+ const { localPluginLocations, packageImports } = await analyzeSourceFiles(vendureConfigPath, (_a = pluginPackageScanner === null || pluginPackageScanner === void 0 ? void 0 : pluginPackageScanner.nodeModulesRoot) !== null && _a !== void 0 ? _a : guessNodeModulesRoot(vendureConfigPath, logger), logger, transformTsConfigPathMappings);
14
15
  logger.debug(`[discoverPlugins] Found ${localPluginLocations.size} local plugins: ${JSON.stringify([...localPluginLocations.entries()], null, 2)}`);
15
16
  logger.debug(`[discoverPlugins] Found ${packageImports.length} package imports: ${JSON.stringify(packageImports, null, 2)}`);
16
17
  const filePaths = await findVendurePluginFiles({
@@ -92,12 +93,32 @@ function getDecoratorObjectProps(decorator) {
92
93
  }
93
94
  return [];
94
95
  }
96
+ async function isSymlinkedLocalPackage(packageName, nodeModulesRoot) {
97
+ try {
98
+ const packagePath = path.join(nodeModulesRoot, packageName);
99
+ const stats = await fs.lstat(packagePath);
100
+ if (stats.isSymbolicLink()) {
101
+ // Get the real path that the symlink points to
102
+ const realPath = await fs.realpath(packagePath);
103
+ // If the real path is within the project directory (i.e. not in some other node_modules),
104
+ // then it's a local package
105
+ if (!realPath.includes('node_modules')) {
106
+ return realPath;
107
+ }
108
+ }
109
+ }
110
+ catch (e) {
111
+ // Package doesn't exist or other error - not a local package
112
+ return undefined;
113
+ }
114
+ return undefined;
115
+ }
95
116
  /**
96
117
  * Analyzes TypeScript source files starting from the config file to discover:
97
118
  * 1. Local Vendure plugins
98
119
  * 2. All non-local package imports that could contain plugins
99
120
  */
100
- export async function analyzeSourceFiles(vendureConfigPath, logger, transformTsConfigPathMappings) {
121
+ export async function analyzeSourceFiles(vendureConfigPath, nodeModulesRoot, logger, transformTsConfigPathMappings) {
101
122
  const localPluginLocations = new Map();
102
123
  const visitedFiles = new Set();
103
124
  const packageImportsSet = new Set();
@@ -123,7 +144,7 @@ export async function analyzeSourceFiles(vendureConfigPath, logger, transformTsC
123
144
  const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
124
145
  // Track imports to follow
125
146
  const importsToFollow = [];
126
- function visit(node) {
147
+ async function visit(node) {
127
148
  // Look for VendurePlugin decorator
128
149
  const vendurePluginClassName = getVendurePluginClassName(node);
129
150
  if (vendurePluginClassName) {
@@ -139,7 +160,16 @@ export async function analyzeSourceFiles(vendureConfigPath, logger, transformTsC
139
160
  // Track non-local imports (packages)
140
161
  const npmPackageName = getNpmPackageNameFromImport(importPath);
141
162
  if (npmPackageName) {
142
- packageImportsSet.add(npmPackageName);
163
+ // Check if this is actually a symlinked local package
164
+ const localPackagePath = await isSymlinkedLocalPackage(npmPackageName, nodeModulesRoot);
165
+ if (localPackagePath) {
166
+ // If it is local, follow it like a local import
167
+ importsToFollow.push(localPackagePath);
168
+ logger.debug(`Found symlinked local package "${npmPackageName}" at ${localPackagePath}`);
169
+ }
170
+ else {
171
+ packageImportsSet.add(npmPackageName);
172
+ }
143
173
  }
144
174
  // Handle path aliases and local imports
145
175
  const pathAliasImports = getPotentialPathAliasImportPaths(importPath, tsConfigInfo);
@@ -153,9 +183,14 @@ export async function analyzeSourceFiles(vendureConfigPath, logger, transformTsC
153
183
  }
154
184
  }
155
185
  }
156
- ts.forEachChild(node, visit);
186
+ // Visit children
187
+ const promises = [];
188
+ ts.forEachChild(node, child => {
189
+ promises.push(visit(child));
190
+ });
191
+ await Promise.all(promises);
157
192
  }
158
- visit(sourceFile);
193
+ await visit(sourceFile);
159
194
  // Follow imports
160
195
  for (const importPath of importsToFollow) {
161
196
  // Try all possible file paths
@@ -265,27 +300,14 @@ export async function findVendurePluginFiles({ outputPath, vendureConfigPath, lo
265
300
  let nodeModulesRoot = providedNodeModulesRoot;
266
301
  const readStart = Date.now();
267
302
  if (!nodeModulesRoot) {
268
- // If the node_modules root path has not been explicitly
269
- // specified, we will try to guess it by resolving the
270
- // `@vendure/core` package.
271
- try {
272
- const coreUrl = import.meta.resolve('@vendure/core');
273
- logger.debug(`Found core URL: ${coreUrl}`);
274
- const corePath = fileURLToPath(coreUrl);
275
- logger.debug(`Found core path: ${corePath}`);
276
- nodeModulesRoot = path.join(path.dirname(corePath), '..', '..');
277
- }
278
- catch (e) {
279
- logger.warn(`Failed to resolve @vendure/core: ${e instanceof Error ? e.message : String(e)}`);
280
- nodeModulesRoot = path.dirname(vendureConfigPath);
281
- }
303
+ nodeModulesRoot = guessNodeModulesRoot(vendureConfigPath, logger);
282
304
  }
283
305
  const patterns = [
284
306
  // Local compiled plugins in temp dir
285
307
  path.join(outputPath, '**/*.js'),
286
308
  // Node modules patterns
287
309
  ...packageGlobs.map(pattern => path.join(nodeModulesRoot, pattern)),
288
- ];
310
+ ].map(p => p.replace(/\\/g, '/'));
289
311
  logger.debug(`Finding Vendure plugins using patterns: ${patterns.join('\n')}`);
290
312
  const globStart = Date.now();
291
313
  const files = await glob(patterns, {
@@ -345,3 +367,21 @@ export async function findVendurePluginFiles({ outputPath, vendureConfigPath, lo
345
367
  `(scanned ${files.length} files)`);
346
368
  return potentialPluginFiles;
347
369
  }
370
+ function guessNodeModulesRoot(vendureConfigPath, logger) {
371
+ let nodeModulesRoot;
372
+ // If the node_modules root path has not been explicitly
373
+ // specified, we will try to guess it by resolving the
374
+ // `@vendure/core` package.
375
+ try {
376
+ const coreUrl = import.meta.resolve('@vendure/core');
377
+ logger.debug(`Found core URL: ${coreUrl}`);
378
+ const corePath = fileURLToPath(coreUrl);
379
+ logger.debug(`Found core path: ${corePath}`);
380
+ nodeModulesRoot = path.join(path.dirname(corePath), '..', '..');
381
+ }
382
+ catch (e) {
383
+ logger.warn(`Failed to resolve @vendure/core: ${e instanceof Error ? e.message : String(e)}`);
384
+ nodeModulesRoot = path.dirname(vendureConfigPath);
385
+ }
386
+ return nodeModulesRoot;
387
+ }
@@ -22,7 +22,19 @@ export function dashboardTailwindSourcePlugin() {
22
22
  loadVendureConfigResult = await configLoaderApi.getVendureConfig();
23
23
  }
24
24
  const { pluginInfo } = loadVendureConfigResult;
25
- const dashboardExtensionDirs = (_a = pluginInfo === null || pluginInfo === void 0 ? void 0 : pluginInfo.map(({ dashboardEntryPath, pluginPath }) => dashboardEntryPath && path.join(pluginPath, path.dirname(dashboardEntryPath))).filter(x => x != null)) !== null && _a !== void 0 ? _a : [];
25
+ const dashboardExtensionDirs = (_a = pluginInfo === null || pluginInfo === void 0 ? void 0 : pluginInfo.flatMap(({ dashboardEntryPath, sourcePluginPath, pluginPath }) => {
26
+ if (!dashboardEntryPath) {
27
+ return [];
28
+ }
29
+ const sourcePaths = [];
30
+ if (sourcePluginPath) {
31
+ sourcePaths.push(path.join(path.dirname(sourcePluginPath), path.dirname(dashboardEntryPath)));
32
+ }
33
+ if (pluginPath) {
34
+ sourcePaths.push(path.join(path.dirname(pluginPath), path.dirname(dashboardEntryPath)));
35
+ }
36
+ return sourcePaths;
37
+ }).filter(x => x != null)) !== null && _a !== void 0 ? _a : [];
26
38
  const sources = dashboardExtensionDirs
27
39
  .map(extension => {
28
40
  return `@source '${extension}';`;
@@ -63,7 +63,7 @@ export type VitePluginVendureDashboardOptions = {
63
63
  * @description
64
64
  * The path to the directory where the generated GraphQL Tada files will be output.
65
65
  */
66
- gqlTadaOutputPath?: string;
66
+ gqlOutputPath?: string;
67
67
  tempCompilationDir?: string;
68
68
  disableTansStackRouterPlugin?: boolean;
69
69
  /**
@@ -55,8 +55,8 @@ export function vendureDashboardPlugin(options) {
55
55
  adminApiSchemaPlugin(),
56
56
  dashboardMetadataPlugin(),
57
57
  uiConfigPlugin({ adminUiConfig: options.adminUiConfig }),
58
- ...(options.gqlTadaOutputPath
59
- ? [gqlTadaPlugin({ gqlTadaOutputPath: options.gqlTadaOutputPath, tempDir, packageRoot })]
58
+ ...(options.gqlOutputPath
59
+ ? [gqlTadaPlugin({ gqlTadaOutputPath: options.gqlOutputPath, tempDir, packageRoot })]
60
60
  : []),
61
61
  transformIndexHtmlPlugin(),
62
62
  ];
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vendure/dashboard",
3
3
  "private": false,
4
- "version": "3.3.8-master-202507231129",
4
+ "version": "3.3.8-master-202507240240",
5
5
  "type": "module",
6
6
  "repository": {
7
7
  "type": "git",
@@ -95,8 +95,8 @@
95
95
  "@types/react-dom": "^19.0.4",
96
96
  "@types/react-grid-layout": "^1.3.5",
97
97
  "@uidotdev/usehooks": "^2.4.1",
98
- "@vendure/common": "^3.3.8-master-202507231129",
99
- "@vendure/core": "^3.3.8-master-202507231129",
98
+ "@vendure/common": "^3.3.8-master-202507240240",
99
+ "@vendure/core": "^3.3.8-master-202507240240",
100
100
  "@vitejs/plugin-react": "^4.3.4",
101
101
  "acorn": "^8.11.3",
102
102
  "acorn-walk": "^8.3.2",
@@ -146,5 +146,5 @@
146
146
  "lightningcss-linux-arm64-musl": "^1.29.3",
147
147
  "lightningcss-linux-x64-musl": "^1.29.1"
148
148
  },
149
- "gitHead": "cddb246db3aa0bff9ee72f1a9517ee4b8702ed4f"
149
+ "gitHead": "5a2448f30b88b5c0fd038adc2966a1147c9a0406"
150
150
  }
@@ -0,0 +1,146 @@
1
+ import { FormFieldWrapper } from '@/vdb/components/shared/form-field-wrapper.js';
2
+ import { Button } from '@/vdb/components/ui/button.js';
3
+ import {
4
+ Dialog,
5
+ DialogContent,
6
+ DialogFooter,
7
+ DialogHeader,
8
+ DialogTitle,
9
+ DialogTrigger,
10
+ } from '@/vdb/components/ui/dialog.js';
11
+ import { Form } from '@/vdb/components/ui/form.js';
12
+ import { Input } from '@/vdb/components/ui/input.js';
13
+ import { api } from '@/vdb/graphql/api.js';
14
+ import { graphql } from '@/vdb/graphql/graphql.js';
15
+ import { Trans, useLingui } from '@/vdb/lib/trans.js';
16
+ import { zodResolver } from '@hookform/resolvers/zod';
17
+ import { useMutation } from '@tanstack/react-query';
18
+ import { Plus } from 'lucide-react';
19
+ import { useCallback, useState } from 'react';
20
+ import { useForm } from 'react-hook-form';
21
+ import { toast } from 'sonner';
22
+ import * as z from 'zod';
23
+
24
+ const createFacetValuesDocument = graphql(`
25
+ mutation CreateFacetValues($input: [CreateFacetValueInput!]!) {
26
+ createFacetValues(input: $input) {
27
+ id
28
+ name
29
+ code
30
+ }
31
+ }
32
+ `);
33
+
34
+ const formSchema = z.object({
35
+ name: z.string().min(1, 'Name is required'),
36
+ code: z.string().min(1, 'Code is required'),
37
+ });
38
+
39
+ type FormValues = z.infer<typeof formSchema>;
40
+
41
+ export function AddFacetValueDialog({
42
+ facetId,
43
+ onSuccess,
44
+ }: Readonly<{
45
+ facetId: string;
46
+ onSuccess?: () => void;
47
+ }>) {
48
+ const [open, setOpen] = useState(false);
49
+ const { i18n } = useLingui();
50
+
51
+ const form = useForm<FormValues>({
52
+ resolver: zodResolver(formSchema),
53
+ defaultValues: {
54
+ name: '',
55
+ code: '',
56
+ },
57
+ });
58
+
59
+ const createFacetValueMutation = useMutation({
60
+ mutationFn: api.mutate(createFacetValuesDocument),
61
+ onSuccess: () => {
62
+ toast.success(i18n.t('Successfully created facet value'));
63
+ setOpen(false);
64
+ form.reset();
65
+ onSuccess?.();
66
+ },
67
+ onError: error => {
68
+ toast.error(i18n.t('Failed to create facet value'), {
69
+ description: error instanceof Error ? error.message : i18n.t('Unknown error'),
70
+ });
71
+ },
72
+ });
73
+
74
+ const onSubmit = useCallback(
75
+ (values: FormValues) => {
76
+ createFacetValueMutation.mutate({
77
+ input: [
78
+ {
79
+ facetId,
80
+ code: values.code,
81
+ translations: [
82
+ {
83
+ languageCode: 'en',
84
+ name: values.name,
85
+ },
86
+ ],
87
+ },
88
+ ],
89
+ });
90
+ },
91
+ [createFacetValueMutation, facetId],
92
+ );
93
+
94
+ return (
95
+ <Dialog open={open} onOpenChange={setOpen}>
96
+ <DialogTrigger asChild>
97
+ <Button variant="outline">
98
+ <Plus className="mr-2 h-4 w-4" />
99
+ <Trans>Add facet value</Trans>
100
+ </Button>
101
+ </DialogTrigger>
102
+ <DialogContent>
103
+ <DialogHeader>
104
+ <DialogTitle>
105
+ <Trans>Add facet value</Trans>
106
+ </DialogTitle>
107
+ </DialogHeader>
108
+ <Form {...form}>
109
+ <form
110
+ onSubmit={e => {
111
+ e.stopPropagation();
112
+ form.handleSubmit(onSubmit)(e);
113
+ }}
114
+ className="space-y-4"
115
+ >
116
+ <FormFieldWrapper
117
+ control={form.control}
118
+ name="name"
119
+ label={<Trans>Name</Trans>}
120
+ render={({ field }) => <Input {...field} />}
121
+ />
122
+ <FormFieldWrapper
123
+ control={form.control}
124
+ name="code"
125
+ label={<Trans>Code</Trans>}
126
+ render={({ field }) => <Input {...field} />}
127
+ />
128
+ <DialogFooter>
129
+ <Button
130
+ type="submit"
131
+ disabled={
132
+ createFacetValueMutation.isPending ||
133
+ !form.formState.isValid ||
134
+ !form.watch('name').trim() ||
135
+ !form.watch('code').trim()
136
+ }
137
+ >
138
+ <Trans>Create facet value</Trans>
139
+ </Button>
140
+ </DialogFooter>
141
+ </form>
142
+ </Form>
143
+ </DialogContent>
144
+ </Dialog>
145
+ );
146
+ }
@@ -5,8 +5,10 @@ import { addCustomFields } from '@/vdb/framework/document-introspection/add-cust
5
5
  import { graphql } from '@/vdb/graphql/graphql.js';
6
6
  import { Trans } from '@/vdb/lib/trans.js';
7
7
  import { ColumnFiltersState, SortingState } from '@tanstack/react-table';
8
- import { useState } from 'react';
8
+ import { useRef, useState } from 'react';
9
+ import { AddFacetValueDialog } from './add-facet-value-dialog.js';
9
10
  import { EditFacetValue } from './edit-facet-value.js';
11
+ import { deleteFacetValuesDocument } from '../facets.graphql.js';
10
12
 
11
13
  export const facetValueListDocument = graphql(`
12
14
  query FacetValueList($options: FacetValueListOptions) {
@@ -26,76 +28,96 @@ export const facetValueListDocument = graphql(`
26
28
 
27
29
  export interface FacetValuesTableProps {
28
30
  facetId: string;
31
+ registerRefresher?: (refresher: () => void) => void;
29
32
  }
30
33
 
31
- export function FacetValuesTable({ facetId }: Readonly<FacetValuesTableProps>) {
34
+ export function FacetValuesTable({ facetId, registerRefresher }: Readonly<FacetValuesTableProps>) {
32
35
  const [sorting, setSorting] = useState<SortingState>([]);
33
36
  const [page, setPage] = useState(1);
34
37
  const [pageSize, setPageSize] = useState(10);
35
38
  const [filters, setFilters] = useState<ColumnFiltersState>([]);
39
+ const refreshRef = useRef<() => void>(() => {});
36
40
 
37
41
  return (
38
- <PaginatedListDataTable
39
- listQuery={addCustomFields(facetValueListDocument)}
40
- page={page}
41
- itemsPerPage={pageSize}
42
- sorting={sorting}
43
- columnFilters={filters}
44
- onPageChange={(_, page, perPage) => {
45
- setPage(page);
46
- setPageSize(perPage);
47
- }}
48
- onSortChange={(_, sorting) => {
49
- setSorting(sorting);
50
- }}
51
- onFilterChange={(_, filters) => {
52
- setFilters(filters);
53
- }}
54
- transformVariables={variables => {
55
- const filter = variables.options?.filter ?? {};
56
- return {
57
- options: {
58
- filter: {
59
- ...filter,
60
- facetId: { eq: facetId },
42
+ <>
43
+ <PaginatedListDataTable
44
+ listQuery={addCustomFields(facetValueListDocument)}
45
+ deleteMutation={deleteFacetValuesDocument}
46
+ page={page}
47
+ itemsPerPage={pageSize}
48
+ sorting={sorting}
49
+ columnFilters={filters}
50
+ onPageChange={(_, page, perPage) => {
51
+ setPage(page);
52
+ setPageSize(perPage);
53
+ }}
54
+ onSortChange={(_, sorting) => {
55
+ setSorting(sorting);
56
+ }}
57
+ onFilterChange={(_, filters) => {
58
+ setFilters(filters);
59
+ }}
60
+ registerRefresher={refresher => {
61
+ refreshRef.current = refresher;
62
+ registerRefresher?.(refresher);
63
+ }}
64
+ transformVariables={variables => {
65
+ const filter = variables.options?.filter ?? {};
66
+ return {
67
+ options: {
68
+ filter: {
69
+ ...filter,
70
+ facetId: { eq: facetId },
71
+ },
72
+ sort: variables.options?.sort,
73
+ take: pageSize,
74
+ skip: (page - 1) * pageSize,
75
+ },
76
+ };
77
+ }}
78
+ onSearchTermChange={searchTerm => {
79
+ return {
80
+ name: {
81
+ contains: searchTerm,
82
+ },
83
+ };
84
+ }}
85
+ additionalColumns={{
86
+ actions: {
87
+ header: 'Actions',
88
+ cell: ({ row }) => {
89
+ const [open, setOpen] = useState(false);
90
+ const facetValue = row.original;
91
+ return (
92
+ <Popover open={open} onOpenChange={setOpen}>
93
+ <PopoverTrigger asChild>
94
+ <Button type="button" variant="outline" size="sm">
95
+ <Trans>Edit</Trans>
96
+ </Button>
97
+ </PopoverTrigger>
98
+ <PopoverContent className="w-80">
99
+ <EditFacetValue
100
+ facetValueId={facetValue.id}
101
+ onSuccess={() => {
102
+ setOpen(false);
103
+ refreshRef.current?.();
104
+ }}
105
+ />
106
+ </PopoverContent>
107
+ </Popover>
108
+ );
61
109
  },
62
- sort: variables.options?.sort,
63
- take: pageSize,
64
- skip: (page - 1) * pageSize,
65
- },
66
- };
67
- }}
68
- onSearchTermChange={searchTerm => {
69
- return {
70
- name: {
71
- contains: searchTerm,
72
- },
73
- };
74
- }}
75
- additionalColumns={{
76
- actions: {
77
- header: 'Actions',
78
- cell: ({ row }) => {
79
- const [open, setOpen] = useState(false);
80
- const facetValue = row.original;
81
- return (
82
- <Popover open={open} onOpenChange={setOpen}>
83
- <PopoverTrigger asChild>
84
- <Button type="button" variant="outline" size="sm">
85
- <Trans>Edit</Trans>
86
- </Button>
87
- </PopoverTrigger>
88
- <PopoverContent className="w-80">
89
- <EditFacetValue
90
- facetValueId={facetValue.id}
91
- onSuccess={() => setOpen(false)}
92
- />
93
- </PopoverContent>
94
- </Popover>
95
- );
96
110
  },
97
- },
98
- }}
99
- />
111
+ }}
112
+ />
113
+ <div className="mt-4">
114
+ <AddFacetValueDialog
115
+ facetId={facetId}
116
+ onSuccess={() => {
117
+ refreshRef.current?.();
118
+ }}
119
+ />
120
+ </div>
121
+ </>
100
122
  );
101
123
  }
@@ -132,3 +132,12 @@ export const deleteFacetsDocument = graphql(`
132
132
  }
133
133
  }
134
134
  `);
135
+
136
+ export const deleteFacetValuesDocument = graphql(`
137
+ mutation DeleteFacetValues($ids: [ID!]!) {
138
+ deleteFacetValues(ids: $ids) {
139
+ result
140
+ message
141
+ }
142
+ }
143
+ `);
@@ -1,5 +1,5 @@
1
1
  import { DisplayComponent } from '@/vdb/framework/component-registry/dynamic-component.js';
2
- import { FieldInfo, getTypeFieldInfo } from '@/vdb/framework/document-introspection/get-document-structure.js';
2
+ import { FieldInfo, getTypeFieldInfo, getOperationVariablesFields } from '@/vdb/framework/document-introspection/get-document-structure.js';
3
3
  import { api } from '@/vdb/graphql/api.js';
4
4
  import { Trans, useLingui } from '@/vdb/lib/trans.js';
5
5
  import { TypedDocumentNode } from '@graphql-typed-document-node/core';
@@ -250,16 +250,23 @@ function DeleteMutationRowAction({
250
250
  }>) {
251
251
  const { refetchPaginatedList } = usePaginatedList();
252
252
  const { i18n } = useLingui();
253
+
254
+ // Inspect the mutation variables to determine if it expects 'id' or 'ids'
255
+ const mutationVariables = getOperationVariablesFields(deleteMutation);
256
+ const hasIdsParameter = mutationVariables.some(field => field.name === 'ids');
257
+
253
258
  const { mutate: deleteMutationFn } = useMutation({
254
259
  mutationFn: api.mutate(deleteMutation),
255
- onSuccess: (result: { [key: string]: { result: 'DELETED' | 'NOT_DELETED'; message: string } }) => {
260
+ onSuccess: (result: { [key: string]: { result: 'DELETED' | 'NOT_DELETED'; message: string } | { result: 'DELETED' | 'NOT_DELETED'; message: string }[] }) => {
256
261
  const unwrappedResult = Object.values(result)[0];
257
- if (unwrappedResult.result === 'DELETED') {
262
+ // Handle both single result and array of results
263
+ const resultToCheck = Array.isArray(unwrappedResult) ? unwrappedResult[0] : unwrappedResult;
264
+ if (resultToCheck.result === 'DELETED') {
258
265
  refetchPaginatedList();
259
266
  toast.success(i18n.t('Deleted successfully'));
260
267
  } else {
261
268
  toast.error(i18n.t('Failed to delete'), {
262
- description: unwrappedResult.message,
269
+ description: resultToCheck.message,
263
270
  });
264
271
  }
265
272
  },
@@ -295,7 +302,15 @@ function DeleteMutationRowAction({
295
302
  <Trans>Cancel</Trans>
296
303
  </AlertDialogCancel>
297
304
  <AlertDialogAction
298
- onClick={() => deleteMutationFn({ id: row.original.id })}
305
+ onClick={() => {
306
+ // Pass variables based on what the mutation expects
307
+ if (hasIdsParameter) {
308
+ deleteMutationFn({ ids: [row.original.id] });
309
+ } else {
310
+ // Fallback to single id if we can't determine the format
311
+ deleteMutationFn({ id: row.original.id });
312
+ }
313
+ }}
299
314
  className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
300
315
  >
301
316
  <Trans>Delete</Trans>
package/src/lib/index.ts CHANGED
@@ -105,7 +105,6 @@ export * from './components/ui/accordion.js';
105
105
  export * from './components/ui/alert-dialog.js';
106
106
  export * from './components/ui/alert.js';
107
107
  export * from './components/ui/aspect-ratio.js';
108
- export * from './components/ui/avatar.js';
109
108
  export * from './components/ui/badge.js';
110
109
  export * from './components/ui/breadcrumb.js';
111
110
  export * from './components/ui/button.js';
@@ -30,6 +30,7 @@ export async function discoverPlugins({
30
30
  // Analyze source files to find local plugins and package imports
31
31
  const { localPluginLocations, packageImports } = await analyzeSourceFiles(
32
32
  vendureConfigPath,
33
+ pluginPackageScanner?.nodeModulesRoot ?? guessNodeModulesRoot(vendureConfigPath, logger),
33
34
  logger,
34
35
  transformTsConfigPathMappings,
35
36
  );
@@ -137,6 +138,29 @@ function getDecoratorObjectProps(decorator: any): any[] {
137
138
  return [];
138
139
  }
139
140
 
141
+ async function isSymlinkedLocalPackage(
142
+ packageName: string,
143
+ nodeModulesRoot: string,
144
+ ): Promise<string | undefined> {
145
+ try {
146
+ const packagePath = path.join(nodeModulesRoot, packageName);
147
+ const stats = await fs.lstat(packagePath);
148
+ if (stats.isSymbolicLink()) {
149
+ // Get the real path that the symlink points to
150
+ const realPath = await fs.realpath(packagePath);
151
+ // If the real path is within the project directory (i.e. not in some other node_modules),
152
+ // then it's a local package
153
+ if (!realPath.includes('node_modules')) {
154
+ return realPath;
155
+ }
156
+ }
157
+ } catch (e) {
158
+ // Package doesn't exist or other error - not a local package
159
+ return undefined;
160
+ }
161
+ return undefined;
162
+ }
163
+
140
164
  /**
141
165
  * Analyzes TypeScript source files starting from the config file to discover:
142
166
  * 1. Local Vendure plugins
@@ -144,6 +168,7 @@ function getDecoratorObjectProps(decorator: any): any[] {
144
168
  */
145
169
  export async function analyzeSourceFiles(
146
170
  vendureConfigPath: string,
171
+ nodeModulesRoot: string,
147
172
  logger: Logger,
148
173
  transformTsConfigPathMappings: TransformTsConfigPathMappingsFn,
149
174
  ): Promise<{
@@ -186,7 +211,7 @@ export async function analyzeSourceFiles(
186
211
  // Track imports to follow
187
212
  const importsToFollow: string[] = [];
188
213
 
189
- function visit(node: ts.Node) {
214
+ async function visit(node: ts.Node) {
190
215
  // Look for VendurePlugin decorator
191
216
  const vendurePluginClassName = getVendurePluginClassName(node);
192
217
  if (vendurePluginClassName) {
@@ -204,7 +229,20 @@ export async function analyzeSourceFiles(
204
229
  // Track non-local imports (packages)
205
230
  const npmPackageName = getNpmPackageNameFromImport(importPath);
206
231
  if (npmPackageName) {
207
- packageImportsSet.add(npmPackageName);
232
+ // Check if this is actually a symlinked local package
233
+ const localPackagePath = await isSymlinkedLocalPackage(
234
+ npmPackageName,
235
+ nodeModulesRoot,
236
+ );
237
+ if (localPackagePath) {
238
+ // If it is local, follow it like a local import
239
+ importsToFollow.push(localPackagePath);
240
+ logger.debug(
241
+ `Found symlinked local package "${npmPackageName}" at ${localPackagePath}`,
242
+ );
243
+ } else {
244
+ packageImportsSet.add(npmPackageName);
245
+ }
208
246
  }
209
247
  // Handle path aliases and local imports
210
248
  const pathAliasImports = getPotentialPathAliasImportPaths(importPath, tsConfigInfo);
@@ -219,10 +257,15 @@ export async function analyzeSourceFiles(
219
257
  }
220
258
  }
221
259
 
222
- ts.forEachChild(node, visit);
260
+ // Visit children
261
+ const promises: Array<Promise<void>> = [];
262
+ ts.forEachChild(node, child => {
263
+ promises.push(visit(child));
264
+ });
265
+ await Promise.all(promises);
223
266
  }
224
267
 
225
- visit(sourceFile);
268
+ await visit(sourceFile);
226
269
 
227
270
  // Follow imports
228
271
  for (const importPath of importsToFollow) {
@@ -352,19 +395,7 @@ export async function findVendurePluginFiles({
352
395
  let nodeModulesRoot = providedNodeModulesRoot;
353
396
  const readStart = Date.now();
354
397
  if (!nodeModulesRoot) {
355
- // If the node_modules root path has not been explicitly
356
- // specified, we will try to guess it by resolving the
357
- // `@vendure/core` package.
358
- try {
359
- const coreUrl = import.meta.resolve('@vendure/core');
360
- logger.debug(`Found core URL: ${coreUrl}`);
361
- const corePath = fileURLToPath(coreUrl);
362
- logger.debug(`Found core path: ${corePath}`);
363
- nodeModulesRoot = path.join(path.dirname(corePath), '..', '..');
364
- } catch (e) {
365
- logger.warn(`Failed to resolve @vendure/core: ${e instanceof Error ? e.message : String(e)}`);
366
- nodeModulesRoot = path.dirname(vendureConfigPath);
367
- }
398
+ nodeModulesRoot = guessNodeModulesRoot(vendureConfigPath, logger);
368
399
  }
369
400
 
370
401
  const patterns = [
@@ -372,7 +403,7 @@ export async function findVendurePluginFiles({
372
403
  path.join(outputPath, '**/*.js'),
373
404
  // Node modules patterns
374
405
  ...packageGlobs.map(pattern => path.join(nodeModulesRoot, pattern)),
375
- ];
406
+ ].map(p => p.replace(/\\/g, '/'));
376
407
 
377
408
  logger.debug(`Finding Vendure plugins using patterns: ${patterns.join('\n')}`);
378
409
 
@@ -443,3 +474,21 @@ export async function findVendurePluginFiles({
443
474
 
444
475
  return potentialPluginFiles;
445
476
  }
477
+
478
+ function guessNodeModulesRoot(vendureConfigPath: string, logger: Logger): string {
479
+ let nodeModulesRoot: string;
480
+ // If the node_modules root path has not been explicitly
481
+ // specified, we will try to guess it by resolving the
482
+ // `@vendure/core` package.
483
+ try {
484
+ const coreUrl = import.meta.resolve('@vendure/core');
485
+ logger.debug(`Found core URL: ${coreUrl}`);
486
+ const corePath = fileURLToPath(coreUrl);
487
+ logger.debug(`Found core path: ${corePath}`);
488
+ nodeModulesRoot = path.join(path.dirname(corePath), '..', '..');
489
+ } catch (e) {
490
+ logger.warn(`Failed to resolve @vendure/core: ${e instanceof Error ? e.message : String(e)}`);
491
+ nodeModulesRoot = path.dirname(vendureConfigPath);
492
+ }
493
+ return nodeModulesRoot;
494
+ }
@@ -27,10 +27,26 @@ export function dashboardTailwindSourcePlugin(): Plugin {
27
27
  const { pluginInfo } = loadVendureConfigResult;
28
28
  const dashboardExtensionDirs =
29
29
  pluginInfo
30
- ?.map(
31
- ({ dashboardEntryPath, pluginPath }) =>
32
- dashboardEntryPath && path.join(pluginPath, path.dirname(dashboardEntryPath)),
33
- )
30
+ ?.flatMap(({ dashboardEntryPath, sourcePluginPath, pluginPath }) => {
31
+ if (!dashboardEntryPath) {
32
+ return [];
33
+ }
34
+ const sourcePaths = [];
35
+ if (sourcePluginPath) {
36
+ sourcePaths.push(
37
+ path.join(
38
+ path.dirname(sourcePluginPath),
39
+ path.dirname(dashboardEntryPath),
40
+ ),
41
+ );
42
+ }
43
+ if (pluginPath) {
44
+ sourcePaths.push(
45
+ path.join(path.dirname(pluginPath), path.dirname(dashboardEntryPath)),
46
+ );
47
+ }
48
+ return sourcePaths;
49
+ })
34
50
  .filter(x => x != null) ?? [];
35
51
  const sources = dashboardExtensionDirs
36
52
  .map(extension => {
@@ -76,7 +76,7 @@ export type VitePluginVendureDashboardOptions = {
76
76
  * @description
77
77
  * The path to the directory where the generated GraphQL Tada files will be output.
78
78
  */
79
- gqlTadaOutputPath?: string;
79
+ gqlOutputPath?: string;
80
80
  tempCompilationDir?: string;
81
81
  disableTansStackRouterPlugin?: boolean;
82
82
  /**
@@ -134,8 +134,8 @@ export function vendureDashboardPlugin(options: VitePluginVendureDashboardOption
134
134
  adminApiSchemaPlugin(),
135
135
  dashboardMetadataPlugin(),
136
136
  uiConfigPlugin({ adminUiConfig: options.adminUiConfig }),
137
- ...(options.gqlTadaOutputPath
138
- ? [gqlTadaPlugin({ gqlTadaOutputPath: options.gqlTadaOutputPath, tempDir, packageRoot })]
137
+ ...(options.gqlOutputPath
138
+ ? [gqlTadaPlugin({ gqlTadaOutputPath: options.gqlOutputPath, tempDir, packageRoot })]
139
139
  : []),
140
140
  transformIndexHtmlPlugin(),
141
141
  ];
@@ -1,53 +0,0 @@
1
- "use client"
2
-
3
- import * as React from "react"
4
- import * as AvatarPrimitive from "@radix-ui/react-avatar"
5
-
6
- import { cn } from "@/vdb/lib/utils"
7
-
8
- function Avatar({
9
- className,
10
- ...props
11
- }: React.ComponentProps<typeof AvatarPrimitive.Root>) {
12
- return (
13
- <AvatarPrimitive.Root
14
- data-slot="avatar"
15
- className={cn(
16
- "relative flex size-8 shrink-0 overflow-hidden rounded-full",
17
- className
18
- )}
19
- {...props}
20
- />
21
- )
22
- }
23
-
24
- function AvatarImage({
25
- className,
26
- ...props
27
- }: React.ComponentProps<typeof AvatarPrimitive.Image>) {
28
- return (
29
- <AvatarPrimitive.Image
30
- data-slot="avatar-image"
31
- className={cn("aspect-square size-full", className)}
32
- {...props}
33
- />
34
- )
35
- }
36
-
37
- function AvatarFallback({
38
- className,
39
- ...props
40
- }: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
41
- return (
42
- <AvatarPrimitive.Fallback
43
- data-slot="avatar-fallback"
44
- className={cn(
45
- "bg-muted flex size-full items-center justify-center rounded-full",
46
- className
47
- )}
48
- {...props}
49
- />
50
- )
51
- }
52
-
53
- export { Avatar, AvatarImage, AvatarFallback }