@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.
- package/dist/plugin/utils/config-loader.d.ts +12 -1
- package/dist/plugin/utils/config-loader.js +25 -7
- package/dist/plugin/vite-plugin-vendure-dashboard.d.ts +8 -0
- package/dist/plugin/vite-plugin-vendure-dashboard.js +5 -1
- package/package.json +4 -4
- package/src/app/app-providers.tsx +3 -1
- package/src/app/routes/_authenticated/_administrators/administrators_.$id.tsx +1 -4
- package/src/app/routes/_authenticated/_channels/channels.tsx +18 -0
- package/src/app/routes/_authenticated/_channels/channels_.$id.tsx +1 -5
- package/src/app/routes/_authenticated/_collections/collections_.$id.tsx +1 -4
- package/src/app/routes/_authenticated/_countries/countries_.$id.tsx +1 -4
- package/src/app/routes/_authenticated/_facets/facets_.$id.tsx +1 -4
- package/src/app/routes/_authenticated/_global-settings/global-settings.tsx +1 -5
- package/src/app/routes/_authenticated/_product-variants/product-variants_.$id.tsx +56 -74
- package/src/app/routes/_authenticated/_products/components/add-product-variant-dialog.tsx +369 -0
- package/src/app/routes/_authenticated/_products/components/create-product-options-dialog.tsx +435 -0
- package/src/app/routes/_authenticated/_products/components/product-option-select.tsx +117 -0
- package/src/app/routes/_authenticated/_products/components/product-variants-table.tsx +4 -2
- package/src/app/routes/_authenticated/_products/products_.$id.tsx +17 -3
- package/src/app/routes/_authenticated/_profile/profile.tsx +1 -4
- package/src/app/routes/_authenticated/_sellers/sellers_.$id.tsx +1 -4
- package/src/app/routes/_authenticated.tsx +2 -3
- package/src/app/routes/login.tsx +1 -0
- package/src/lib/components/data-table/data-table-view-options.tsx +12 -2
- package/src/lib/components/data-table/data-table.tsx +9 -0
- package/src/lib/components/layout/channel-switcher.tsx +1 -2
- package/src/lib/components/layout/nav-user.tsx +4 -7
- package/src/lib/components/shared/assigned-facet-values.tsx +13 -14
- package/src/lib/components/shared/entity-assets.tsx +140 -70
- package/src/lib/components/shared/paginated-list-data-table.tsx +10 -0
- package/src/lib/components/ui/button.tsx +1 -1
- package/src/lib/framework/form-engine/use-generated-form.tsx +1 -0
- package/src/lib/framework/page/list-page.tsx +2 -2
- package/src/lib/framework/page/use-detail-page.ts +7 -0
- package/src/lib/graphql/api.ts +10 -1
- package/src/lib/graphql/graphql-env.d.ts +19 -28
- package/src/lib/hooks/use-permissions.ts +4 -4
- package/src/lib/providers/auth.tsx +66 -62
- package/src/lib/providers/channel-provider.tsx +68 -19
- package/src/lib/providers/server-config.tsx +2 -2
- package/vite/utils/config-loader.ts +48 -13
- 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 {
|
|
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
|
-
|
|
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 {
|
|
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']>('
|
|
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
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
90
|
-
|
|
90
|
+
// Set active channel if needed
|
|
91
91
|
React.useEffect(() => {
|
|
92
|
-
if (!settings.activeChannelId &&
|
|
93
|
-
setActiveChannelId(
|
|
92
|
+
if (!settings.activeChannelId && currentUserData?.me?.channels?.length) {
|
|
93
|
+
setActiveChannelId(currentUserData.me.channels[0].id);
|
|
94
94
|
}
|
|
95
|
-
}, [settings.activeChannelId,
|
|
95
|
+
}, [settings.activeChannelId, currentUserData?.me?.channels]);
|
|
96
96
|
|
|
97
|
-
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
132
|
-
|
|
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 (
|
|
139
|
-
|
|
140
|
-
|
|
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('
|
|
150
|
+
setStatus('authenticated');
|
|
146
151
|
}
|
|
147
|
-
}, [
|
|
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:
|
|
156
|
-
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 {
|
|
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
|
-
//
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
100
|
-
|
|
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.
|
|
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,
|
|
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(
|
|
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(
|
|
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
|
-
|
|
171
|
-
inputRootDir: string
|
|
172
|
-
inputPath: string
|
|
173
|
-
outputDir: string
|
|
174
|
-
logger
|
|
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
|
|
178
|
-
|
|
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(
|
|
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
|
-
|
|
406
|
+
if (reportCompilationErrors) {
|
|
407
|
+
const hasEmitErrors = reportDiagnostics(program, emitResult, logger);
|
|
374
408
|
|
|
375
|
-
|
|
376
|
-
|
|
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({
|
|
83
|
+
configLoaderPlugin({
|
|
84
|
+
vendureConfigPath: normalizedVendureConfigPath,
|
|
85
|
+
tempDir,
|
|
86
|
+
reportCompilationErrors: options.reportCompilationErrors,
|
|
87
|
+
}),
|
|
78
88
|
viteConfigPlugin({ packageRoot }),
|
|
79
89
|
adminApiSchemaPlugin(),
|
|
80
90
|
dashboardMetadataPlugin({ rootDir: tempDir }),
|