@vendure/dashboard 3.3.1 → 3.3.3

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 (42) hide show
  1. package/dist/plugin/utils/config-loader.d.ts +12 -1
  2. package/dist/plugin/utils/config-loader.js +25 -7
  3. package/dist/plugin/vite-plugin-vendure-dashboard.d.ts +8 -0
  4. package/dist/plugin/vite-plugin-vendure-dashboard.js +5 -1
  5. package/package.json +4 -4
  6. package/src/app/app-providers.tsx +3 -1
  7. package/src/app/routes/_authenticated/_administrators/administrators_.$id.tsx +1 -4
  8. package/src/app/routes/_authenticated/_channels/channels.tsx +18 -0
  9. package/src/app/routes/_authenticated/_channels/channels_.$id.tsx +1 -5
  10. package/src/app/routes/_authenticated/_collections/collections_.$id.tsx +1 -4
  11. package/src/app/routes/_authenticated/_countries/countries_.$id.tsx +1 -4
  12. package/src/app/routes/_authenticated/_facets/facets_.$id.tsx +1 -4
  13. package/src/app/routes/_authenticated/_global-settings/global-settings.tsx +1 -5
  14. package/src/app/routes/_authenticated/_product-variants/product-variants_.$id.tsx +56 -74
  15. package/src/app/routes/_authenticated/_products/components/add-product-variant-dialog.tsx +369 -0
  16. package/src/app/routes/_authenticated/_products/components/create-product-options-dialog.tsx +435 -0
  17. package/src/app/routes/_authenticated/_products/components/product-option-select.tsx +117 -0
  18. package/src/app/routes/_authenticated/_products/components/product-variants-table.tsx +4 -2
  19. package/src/app/routes/_authenticated/_products/products_.$id.tsx +17 -3
  20. package/src/app/routes/_authenticated/_profile/profile.tsx +1 -4
  21. package/src/app/routes/_authenticated/_sellers/sellers_.$id.tsx +1 -4
  22. package/src/app/routes/_authenticated.tsx +2 -3
  23. package/src/app/routes/login.tsx +1 -0
  24. package/src/lib/components/data-table/data-table-view-options.tsx +12 -2
  25. package/src/lib/components/data-table/data-table.tsx +9 -0
  26. package/src/lib/components/layout/channel-switcher.tsx +1 -2
  27. package/src/lib/components/layout/nav-user.tsx +4 -7
  28. package/src/lib/components/shared/assigned-facet-values.tsx +13 -14
  29. package/src/lib/components/shared/entity-assets.tsx +140 -70
  30. package/src/lib/components/shared/paginated-list-data-table.tsx +10 -0
  31. package/src/lib/components/ui/button.tsx +1 -1
  32. package/src/lib/framework/form-engine/use-generated-form.tsx +1 -0
  33. package/src/lib/framework/page/list-page.tsx +2 -2
  34. package/src/lib/framework/page/use-detail-page.ts +7 -0
  35. package/src/lib/graphql/api.ts +10 -1
  36. package/src/lib/graphql/graphql-env.d.ts +19 -28
  37. package/src/lib/hooks/use-permissions.ts +4 -4
  38. package/src/lib/providers/auth.tsx +66 -62
  39. package/src/lib/providers/channel-provider.tsx +68 -19
  40. package/src/lib/providers/server-config.tsx +2 -2
  41. package/vite/utils/config-loader.ts +48 -13
  42. package/vite/vite-plugin-vendure-dashboard.ts +14 -4
@@ -1,8 +1,7 @@
1
1
  import { useAuth } from '@/hooks/use-auth.js';
2
+ import { useChannel } from '@/hooks/use-channel.js';
2
3
  import { Permission } from '@vendure/common/lib/generated-types';
3
4
 
4
- import { useUserSettings } from './use-user-settings.js';
5
-
6
5
  /**
7
6
  * @description
8
7
  * **Status: Developer Preview**
@@ -18,13 +17,14 @@ import { useUserSettings } from './use-user-settings.js';
18
17
  */
19
18
  export function usePermissions() {
20
19
  const { channels } = useAuth();
21
- const { settings } = useUserSettings();
20
+ const { selectedChannelId } = useChannel();
22
21
 
23
22
  function hasPermissions(permissions: string[]) {
24
23
  if (permissions.length === 0) {
25
24
  return true;
26
25
  }
27
- const activeChannel = (channels ?? []).find(channel => channel.id === settings.activeChannelId);
26
+ // Use the selected channel instead of settings.activeChannelId
27
+ const activeChannel = (channels ?? []).find(channel => channel.id === selectedChannelId);
28
28
  if (!activeChannel) {
29
29
  return false;
30
30
  }
@@ -1,9 +1,8 @@
1
1
  import { api } from '@/graphql/api.js';
2
2
  import { ResultOf, graphql } from '@/graphql/graphql.js';
3
3
  import { useUserSettings } from '@/hooks/use-user-settings.js';
4
- import { useMutation, useQuery } from '@tanstack/react-query';
4
+ import { useQuery, useQueryClient } from '@tanstack/react-query';
5
5
  import * as React from 'react';
6
- import { useAuth } from '@/hooks/use-auth.js';
7
6
 
8
7
  /**
9
8
  * @description
@@ -72,79 +71,85 @@ const CurrentUserQuery = graphql(`
72
71
  export const AuthContext = React.createContext<AuthContext | null>(null);
73
72
 
74
73
  export function AuthProvider({ children }: { children: React.ReactNode }) {
75
- const [status, setStatus] = React.useState<AuthContext['status']>('verifying');
74
+ const [status, setStatus] = React.useState<AuthContext['status']>('unauthenticated');
76
75
  const [authenticationError, setAuthenticationError] = React.useState<string | undefined>();
77
76
  const { settings, setActiveChannelId } = useUserSettings();
78
- const onLoginSuccessFn = React.useRef<() => void>(() => {});
79
- const onLogoutSuccessFn = React.useRef<() => void>(() => {});
80
- const isAuthenticated = status === 'authenticated';
81
-
82
- const { data: currentUserData, isLoading , error: currentUserError} = useQuery({
83
- queryKey: ['currentUser', isAuthenticated],
77
+ const queryClient = useQueryClient();
78
+
79
+ // Query for current user
80
+ const {
81
+ data: currentUserData,
82
+ isLoading,
83
+ error: currentUserError,
84
+ refetch: refetchCurrentUser,
85
+ } = useQuery({
86
+ queryKey: ['currentUser'],
84
87
  queryFn: () => api.query(CurrentUserQuery),
85
- retry: false,
86
- staleTime: 1000,
87
88
  });
88
89
 
89
- const currentUser = currentUserError ? undefined : currentUserData;
90
-
90
+ // Set active channel if needed
91
91
  React.useEffect(() => {
92
- if (!settings.activeChannelId && currentUser?.me?.channels?.length) {
93
- setActiveChannelId(currentUser.me.channels[0].id);
92
+ if (!settings.activeChannelId && currentUserData?.me?.channels?.length) {
93
+ setActiveChannelId(currentUserData.me.channels[0].id);
94
94
  }
95
- }, [settings.activeChannelId, currentUser?.me?.channels]);
95
+ }, [settings.activeChannelId, currentUserData?.me?.channels]);
96
96
 
97
- const loginMutationFn = api.mutate(LoginMutation);
98
- const loginMutation = useMutation({
99
- mutationFn: loginMutationFn,
100
- onSuccess: async data => {
101
- if (data.login.__typename === 'CurrentUser') {
102
- setStatus('authenticated');
103
- onLoginSuccessFn.current();
104
- } else {
105
- setAuthenticationError(data?.login.message);
106
- setStatus('unauthenticated');
107
- }
108
- },
109
- onError: error => {
110
- setAuthenticationError(error.message);
111
- setStatus('unauthenticated');
97
+ // Auth actions
98
+ const login = React.useCallback(
99
+ (username: string, password: string, onLoginSuccess?: () => void) => {
100
+ setStatus('verifying');
101
+ api.mutate(LoginMutation)({ username, password })
102
+ .then(async data => {
103
+ if (data.login.__typename === 'CurrentUser') {
104
+ setAuthenticationError(undefined);
105
+ await refetchCurrentUser();
106
+ // Invalidate all queries to ensure fresh data after login
107
+ await queryClient.invalidateQueries();
108
+ setStatus('authenticated');
109
+ onLoginSuccess?.();
110
+ } else {
111
+ setAuthenticationError(data?.login.message);
112
+ setStatus('unauthenticated');
113
+ }
114
+ })
115
+ .catch(error => {
116
+ setAuthenticationError(error.message);
117
+ setStatus('unauthenticated');
118
+ });
112
119
  },
113
- });
120
+ [refetchCurrentUser, queryClient],
121
+ );
114
122
 
115
- const logoutMutationFn = api.mutate(LogOutMutation);
116
- const logoutMutation = useMutation({
117
- mutationFn: logoutMutationFn,
118
- onSuccess: async data => {
119
- if (data?.logout.success === true) {
120
- setStatus('unauthenticated');
121
- onLogoutSuccessFn.current();
122
- }
123
+ const logout = React.useCallback(
124
+ async (onLogoutSuccess?: () => void) => {
125
+ setStatus('verifying');
126
+ api.mutate(LogOutMutation)({}).then(async data => {
127
+ if (data?.logout.success) {
128
+ // Clear all cached queries to prevent stale data
129
+ queryClient.clear();
130
+ // Clear selected channel from localStorage
131
+ localStorage.removeItem('vendure-selected-channel');
132
+ localStorage.removeItem('vendure-selected-channel-token');
133
+ setStatus('unauthenticated');
134
+ onLogoutSuccess?.();
135
+ }
136
+ });
123
137
  },
124
- });
125
-
126
- const logout = React.useCallback(async (onLogoutSuccess?: () => void) => {
127
- logoutMutation.mutate({});
128
- onLogoutSuccessFn.current = onLogoutSuccess || (() => {});
129
- }, []);
138
+ [queryClient],
139
+ );
130
140
 
131
- const login = React.useCallback((username: string, password: string, onLoginSuccess?: () => void) => {
132
- setStatus('verifying');
133
- onLoginSuccessFn.current = onLoginSuccess || (() => {});
134
- loginMutation.mutate({ username, password });
135
- }, []);
141
+ // Determine isAuthenticated from currentUserData
142
+ const isAuthenticated = !!currentUserData?.me?.id;
136
143
 
144
+ // Set status based on query result (only if not in the middle of login/logout)
137
145
  React.useEffect(() => {
138
- if (!isLoading) {
139
- if (currentUser?.me?.id) {
140
- setStatus('authenticated');
141
- } else {
142
- setStatus('unauthenticated');
143
- }
146
+ if (status === 'verifying') return;
147
+ if (currentUserError || !currentUserData?.me?.id) {
148
+ setStatus('unauthenticated');
144
149
  } else {
145
- setStatus('verifying');
150
+ setStatus('authenticated');
146
151
  }
147
- }, [isLoading, currentUser]);
152
+ }, [currentUserData, currentUserError]);
148
153
 
149
154
  return (
150
155
  <AuthContext.Provider
@@ -152,8 +157,8 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
152
157
  isAuthenticated,
153
158
  authenticationError,
154
159
  status,
155
- user: currentUser?.activeAdministrator,
156
- channels: currentUser?.me?.channels,
160
+ user: currentUserData?.activeAdministrator,
161
+ channels: currentUserData?.me?.channels,
157
162
  login,
158
163
  logout,
159
164
  }}
@@ -162,4 +167,3 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
162
167
  </AuthContext.Provider>
163
168
  );
164
169
  }
165
-
@@ -1,7 +1,8 @@
1
- import * as React from 'react';
2
1
  import { api } from '@/graphql/api.js';
3
2
  import { ResultOf, graphql } from '@/graphql/graphql.js';
4
- import { useQuery } from '@tanstack/react-query';
3
+ import { useAuth } from '@/hooks/use-auth.js';
4
+ import { useQuery, useQueryClient } from '@tanstack/react-query';
5
+ import * as React from 'react';
5
6
 
6
7
  // Define the channel fragment for reuse
7
8
  const channelFragment = graphql(`
@@ -36,7 +37,6 @@ const ChannelsQuery = graphql(
36
37
  [channelFragment],
37
38
  );
38
39
 
39
-
40
40
  // Define the type for a channel
41
41
  type ActiveChannel = ResultOf<typeof ChannelsQuery>['activeChannel'];
42
42
  type Channel = ResultOf<typeof channelFragment>;
@@ -63,8 +63,11 @@ export const ChannelContext = React.createContext<ChannelContext | undefined>(un
63
63
 
64
64
  // Local storage key for the selected channel
65
65
  const SELECTED_CHANNEL_KEY = 'vendure-selected-channel';
66
+ const SELECTED_CHANNEL_TOKEN_KEY = 'vendure-selected-channel-token';
66
67
 
67
68
  export function ChannelProvider({ children }: { children: React.ReactNode }) {
69
+ const queryClient = useQueryClient();
70
+ const { channels: userChannels, isAuthenticated } = useAuth();
68
71
  const [selectedChannelId, setSelectedChannelId] = React.useState<string | undefined>(() => {
69
72
  // Initialize from localStorage if available
70
73
  try {
@@ -78,35 +81,82 @@ export function ChannelProvider({ children }: { children: React.ReactNode }) {
78
81
 
79
82
  // Fetch all available channels
80
83
  const { data: channelsData, isLoading: isChannelsLoading } = useQuery({
81
- queryKey: ['channels'],
84
+ queryKey: ['channels', isAuthenticated],
82
85
  queryFn: () => api.query(ChannelsQuery),
83
86
  retry: false,
87
+ enabled: isAuthenticated,
84
88
  });
85
89
 
86
- // Set the selected channel and update localStorage
87
- const setSelectedChannel = React.useCallback((channelId: string) => {
88
- try {
89
- // Store in localStorage
90
- localStorage.setItem(SELECTED_CHANNEL_KEY, channelId);
91
- setSelectedChannelId(channelId);
92
- } catch (e) {
93
- console.error('Failed to set selected channel', e);
90
+ // Filter channels based on user permissions
91
+ const channels = React.useMemo(() => {
92
+ // If user has specific channels assigned (non-superadmin), use those
93
+ if (userChannels && userChannels.length > 0) {
94
+ // Map user channels to match the Channel type structure
95
+ return userChannels.map(ch => ({
96
+ id: ch.id,
97
+ code: ch.code,
98
+ token: ch.token,
99
+ defaultLanguageCode:
100
+ channelsData?.channels.items.find(c => c.id === ch.id)?.defaultLanguageCode || 'en',
101
+ defaultCurrencyCode:
102
+ channelsData?.channels.items.find(c => c.id === ch.id)?.defaultCurrencyCode || 'USD',
103
+ pricesIncludeTax:
104
+ channelsData?.channels.items.find(c => c.id === ch.id)?.pricesIncludeTax || false,
105
+ }));
94
106
  }
95
- }, []);
107
+ // Otherwise use all channels (superadmin)
108
+ return channelsData?.channels.items || [];
109
+ }, [userChannels, channelsData?.channels.items]);
110
+
111
+ // Set the selected channel and update localStorage
112
+ const setSelectedChannel = React.useCallback(
113
+ (channelId: string) => {
114
+ try {
115
+ // Find the channel to get its token
116
+ const channel = channels.find(c => c.id === channelId);
117
+ if (channel) {
118
+ // Store channel ID and token in localStorage
119
+ localStorage.setItem(SELECTED_CHANNEL_KEY, channelId);
120
+ localStorage.setItem(SELECTED_CHANNEL_TOKEN_KEY, channel.token);
121
+ setSelectedChannelId(channelId);
122
+ queryClient.invalidateQueries();
123
+ }
124
+ } catch (e) {
125
+ console.error('Failed to set selected channel', e);
126
+ }
127
+ },
128
+ [queryClient, channels],
129
+ );
96
130
 
97
131
  // If no selected channel is set but we have an active channel, use that
132
+ // Also validate that the selected channel is accessible to the user
98
133
  React.useEffect(() => {
99
- if (!selectedChannelId && channelsData?.activeChannel?.id) {
100
- setSelectedChannelId(channelsData.activeChannel.id);
134
+ const validChannelIds = channels.map(c => c.id);
135
+
136
+ // If selected channel is not valid for this user, reset it
137
+ if (selectedChannelId && !validChannelIds.includes(selectedChannelId)) {
138
+ setSelectedChannelId(undefined);
101
139
  try {
102
- localStorage.setItem(SELECTED_CHANNEL_KEY, channelsData.activeChannel.id);
140
+ localStorage.removeItem(SELECTED_CHANNEL_KEY);
141
+ localStorage.removeItem(SELECTED_CHANNEL_TOKEN_KEY);
142
+ } catch (e) {
143
+ console.error('Failed to remove selected channel from localStorage', e);
144
+ }
145
+ }
146
+
147
+ // If no selected channel is set, use the first available channel
148
+ if (!selectedChannelId && channels.length > 0) {
149
+ const defaultChannel = channels[0];
150
+ setSelectedChannelId(defaultChannel.id);
151
+ try {
152
+ localStorage.setItem(SELECTED_CHANNEL_KEY, defaultChannel.id);
153
+ localStorage.setItem(SELECTED_CHANNEL_TOKEN_KEY, defaultChannel.token);
103
154
  } catch (e) {
104
155
  console.error('Failed to store selected channel in localStorage', e);
105
156
  }
106
157
  }
107
- }, [selectedChannelId, channelsData]);
158
+ }, [selectedChannelId, channels]);
108
159
 
109
- const channels = channelsData?.channels.items || [];
110
160
  const activeChannel = channelsData?.activeChannel;
111
161
  const isLoading = isChannelsLoading;
112
162
 
@@ -126,4 +176,3 @@ export function ChannelProvider({ children }: { children: React.ReactNode }) {
126
176
 
127
177
  return <ChannelContext.Provider value={contextValue}>{children}</ChannelContext.Provider>;
128
178
  }
129
-
@@ -270,14 +270,14 @@ export const ServerConfigProvider = ({ children }: { children: React.ReactNode }
270
270
  enabled: !!user?.id,
271
271
  staleTime: 1000,
272
272
  });
273
- const value: ServerConfig = {
273
+ const value: ServerConfig | null = data?.globalSettings ? {
274
274
  availableLanguages: data?.globalSettings.availableLanguages ?? [],
275
275
  moneyStrategyPrecision: data?.globalSettings.serverConfig.moneyStrategyPrecision ?? 2,
276
276
  orderProcess: data?.globalSettings.serverConfig.orderProcess ?? [],
277
277
  permittedAssetTypes: data?.globalSettings.serverConfig.permittedAssetTypes ?? [],
278
278
  permissions: data?.globalSettings.serverConfig.permissions ?? [],
279
279
  entityCustomFields: data?.globalSettings.serverConfig.entityCustomFields ?? [],
280
- };
280
+ } : null;
281
281
 
282
282
  return <ServerConfigContext.Provider value={value}>{children}</ServerConfigContext.Provider>;
283
283
  };
@@ -36,6 +36,7 @@ export interface ConfigLoaderOptions {
36
36
  tempDir: string;
37
37
  vendureConfigExport?: string;
38
38
  logger?: Logger;
39
+ reportCompilationErrors?: boolean;
39
40
  }
40
41
 
41
42
  export interface LoadVendureConfigResult {
@@ -67,7 +68,12 @@ export async function loadVendureConfig(options: ConfigLoaderOptions): Promise<L
67
68
  const configFileName = path.basename(vendureConfigPath);
68
69
  const inputRootDir = path.dirname(vendureConfigPath);
69
70
  await fs.remove(outputPath);
70
- const pluginInfo = await compileFile(inputRootDir, vendureConfigPath, outputPath, logger);
71
+ const pluginInfo = await compileFile({
72
+ inputRootDir,
73
+ inputPath: vendureConfigPath,
74
+ outputDir: outputPath,
75
+ logger,
76
+ });
71
77
  const compiledConfigFilePath = pathToFileURL(path.join(outputPath, configFileName)).href.replace(
72
78
  /.ts$/,
73
79
  '.js',
@@ -147,7 +153,14 @@ async function findTsConfigPaths(
147
153
  );
148
154
  }
149
155
  logger.debug(
150
- `Found tsconfig paths in ${tsConfigPath}: ${JSON.stringify({ baseUrl: tsConfigBaseUrl, paths }, null, 2)}`,
156
+ `Found tsconfig paths in ${tsConfigPath}: ${JSON.stringify(
157
+ {
158
+ baseUrl: tsConfigBaseUrl,
159
+ paths,
160
+ },
161
+ null,
162
+ 2,
163
+ )}`,
151
164
  );
152
165
  return { baseUrl: tsConfigBaseUrl, paths };
153
166
  }
@@ -167,15 +180,27 @@ async function findTsConfigPaths(
167
180
  return undefined;
168
181
  }
169
182
 
170
- export async function compileFile(
171
- inputRootDir: string,
172
- inputPath: string,
173
- outputDir: string,
174
- logger: Logger = defaultLogger,
183
+ type CompileFileOptions = {
184
+ inputRootDir: string;
185
+ inputPath: string;
186
+ outputDir: string;
187
+ logger?: Logger;
188
+ compiledFiles?: Set<string>;
189
+ isRoot?: boolean;
190
+ pluginInfo?: PluginInfo[];
191
+ reportCompilationErrors?: boolean;
192
+ };
193
+
194
+ export async function compileFile({
195
+ inputRootDir,
196
+ inputPath,
197
+ outputDir,
198
+ logger = defaultLogger,
175
199
  compiledFiles = new Set<string>(),
176
200
  isRoot = true,
177
- pluginInfo: PluginInfo[] = [],
178
- ): Promise<PluginInfo[]> {
201
+ pluginInfo = [],
202
+ reportCompilationErrors = false,
203
+ }: CompileFileOptions): Promise<PluginInfo[]> {
179
204
  const absoluteInputPath = path.resolve(inputPath);
180
205
  if (compiledFiles.has(absoluteInputPath)) {
181
206
  return pluginInfo;
@@ -325,7 +350,15 @@ export async function compileFile(
325
350
  // Recursively collect all files that need to be compiled
326
351
  for (const importPath of importPaths) {
327
352
  // Pass rootTsConfigInfo down, but set isRoot to false
328
- await compileFile(inputRootDir, importPath, outputDir, logger, compiledFiles, false, pluginInfo);
353
+ await compileFile({
354
+ inputRootDir,
355
+ inputPath: importPath,
356
+ outputDir,
357
+ logger,
358
+ compiledFiles,
359
+ isRoot: false,
360
+ pluginInfo,
361
+ });
329
362
  }
330
363
 
331
364
  // If this is the root file (the one that started the compilation),
@@ -370,10 +403,12 @@ export async function compileFile(
370
403
  logger.info(`Emitting compiled files to ${outputDir}`);
371
404
  const emitResult = program.emit();
372
405
 
373
- const hasEmitErrors = reportDiagnostics(program, emitResult, logger);
406
+ if (reportCompilationErrors) {
407
+ const hasEmitErrors = reportDiagnostics(program, emitResult, logger);
374
408
 
375
- if (hasEmitErrors) {
376
- throw new Error('TypeScript compilation failed with errors.');
409
+ if (hasEmitErrors) {
410
+ throw new Error('TypeScript compilation failed with errors.');
411
+ }
377
412
  }
378
413
 
379
414
  logger.info(`Successfully compiled ${allFiles.length} files to ${outputDir}`);
@@ -1,4 +1,3 @@
1
- import { lingui } from '@lingui/vite-plugin';
2
1
  import tailwindcss from '@tailwindcss/vite';
3
2
  import { TanStackRouterVite } from '@tanstack/router-plugin/vite';
4
3
  import react from '@vitejs/plugin-react';
@@ -10,8 +9,7 @@ import { configLoaderPlugin } from './vite-plugin-config-loader.js';
10
9
  import { viteConfigPlugin } from './vite-plugin-config.js';
11
10
  import { dashboardMetadataPlugin } from './vite-plugin-dashboard-metadata.js';
12
11
  import { gqlTadaPlugin } from './vite-plugin-gql-tada.js';
13
- import { themeVariablesPlugin } from './vite-plugin-theme.js';
14
- import { ThemeVariablesPluginOptions } from './vite-plugin-theme.js';
12
+ import { ThemeVariablesPluginOptions, themeVariablesPlugin } from './vite-plugin-theme.js';
15
13
  import { UiConfigPluginOptions, uiConfigPlugin } from './vite-plugin-ui-config.js';
16
14
 
17
15
  /**
@@ -37,6 +35,14 @@ export type VitePluginVendureDashboardOptions = {
37
35
  gqlTadaOutputPath?: string;
38
36
  tempCompilationDir?: string;
39
37
  disableTansStackRouterPlugin?: boolean;
38
+ /**
39
+ * @description
40
+ * If set to `true`, compilation errors during the build process will be reported and
41
+ * the build will fail.
42
+ *
43
+ * @default false
44
+ */
45
+ reportCompilationErrors?: boolean;
40
46
  } & UiConfigPluginOptions &
41
47
  ThemeVariablesPluginOptions;
42
48
 
@@ -74,7 +80,11 @@ export function vendureDashboardPlugin(options: VitePluginVendureDashboardOption
74
80
  }),
75
81
  themeVariablesPlugin({ theme: options.theme }),
76
82
  tailwindcss(),
77
- configLoaderPlugin({ vendureConfigPath: normalizedVendureConfigPath, tempDir }),
83
+ configLoaderPlugin({
84
+ vendureConfigPath: normalizedVendureConfigPath,
85
+ tempDir,
86
+ reportCompilationErrors: options.reportCompilationErrors,
87
+ }),
78
88
  viteConfigPlugin({ packageRoot }),
79
89
  adminApiSchemaPlugin(),
80
90
  dashboardMetadataPlugin({ rootDir: tempDir }),