@equinor/fusion-framework-vite-plugin-spa 4.1.0 → 4.1.2

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/src/html/sw.ts DELETED
@@ -1,238 +0,0 @@
1
- /// <reference lib="webworker" />
2
-
3
- import type { ResourceConfiguration } from '../types.js';
4
-
5
- /**
6
- * Represents an authentication token with an access token and its expiration time.
7
- */
8
- type Token = {
9
- accessToken: string;
10
- expiresOn: number;
11
- };
12
-
13
- /**
14
- * A cache structure for storing tokens, where each token is associated with a unique string key.
15
- *
16
- * @typeParam string - The key used to identify a token in the cache.
17
- * @typeParam Token - The type of the token being stored in the cache.
18
- */
19
- type TokenCache = Map<string, Token>;
20
-
21
- /**
22
- * A reference to the global scope of the service worker.
23
- *
24
- * The `self` variable is explicitly cast to `ServiceWorkerGlobalScope` to ensure
25
- * type safety and provide access to service worker-specific APIs.
26
- *
27
- * This is necessary because `globalThis` is a generic global object and does not
28
- * include service worker-specific properties and methods by default.
29
- */
30
- const self = globalThis as unknown as ServiceWorkerGlobalScope;
31
-
32
- /**
33
- * An array of settings used for token injection.
34
- * Each setting defines the configuration for injecting tokens
35
- * into the application, such as authentication or API tokens.
36
- */
37
- let resourceConfigurations: ResourceConfiguration[] = [];
38
-
39
- /**
40
- * A cache for storing tokens, implemented as a `Map`.
41
- * This cache is used to temporarily hold tokens for quick retrieval.
42
- *
43
- * @type {TokenCache} - A `Map` instance where the keys and values are determined by the `TokenCache` type definition.
44
- */
45
- const tokenCache: TokenCache = new Map();
46
-
47
- /**
48
- * Generates a unique key by sorting and concatenating an array of scope strings.
49
- *
50
- * @param scopes - An array of strings representing the scopes to be processed.
51
- * @returns A single string representing the sorted and concatenated scopes, separated by commas.
52
- */
53
- function getScopeKey(scopes: string[]): string {
54
- return scopes.sort().join(',');
55
- }
56
-
57
- /**
58
- * Checks if a token associated with the specified scopes is valid.
59
- *
60
- * This function determines the validity of a token by checking if it exists
61
- * in the token cache and if its expiration time has not been reached.
62
- *
63
- * @param scopes - An array of strings representing the scopes for which the token is required.
64
- * @returns `true` if a valid token exists for the given scopes; otherwise, `false`.
65
- */
66
- function isTokenValid(scopes: string[]): boolean {
67
- const scopeKey = getScopeKey(scopes);
68
- // No cached entry at all means there's nothing to validate
69
- if (!tokenCache.has(scopeKey)) {
70
- return false;
71
- }
72
- const tokenData = tokenCache.get(scopeKey);
73
- return tokenData !== undefined && Date.now() < tokenData.expiresOn;
74
- }
75
-
76
- /**
77
- * Requests an access token from a client using the Service Worker's `clients` API.
78
- * Communicates with the client via a `MessageChannel` to retrieve the token.
79
- *
80
- * @param scopes - An array of strings representing the scopes for which the token is requested.
81
- * @returns A promise that resolves to the token object containing the `accessToken` and `expiresOn` properties.
82
- * @throws An error if no clients are available or if the client responds with an error.
83
- *
84
- * @example
85
- * ```typescript
86
- * const token = await requestTokenFromClient(['scope1', 'scope2']);
87
- * console.log(token.accessToken); // Access token string
88
- * console.log(token.expiresOn); // Expiration timestamp
89
- * ```
90
- */
91
- async function requestTokenFromClient(scopes: string[]): Promise<Token> {
92
- const clients = await self.clients.matchAll();
93
-
94
- // ensure there are clients available
95
- if (clients.length === 0) {
96
- throw new Error('No clients available');
97
- }
98
-
99
- // create a message channel to communicate with the client
100
- const messageChannel = new MessageChannel();
101
- const token = await new Promise<Token>((resolve, reject) => {
102
- messageChannel.port1.onmessage = (event) => {
103
- // Reject when the client reports it couldn't provide a token
104
- if (event.data.error) {
105
- reject(event.data.error);
106
- }
107
- resolve(event.data as { accessToken: string; expiresOn: number });
108
- };
109
- clients[0].postMessage({ type: 'GET_TOKEN', scopes }, [messageChannel.port2]);
110
- });
111
-
112
- // A resolved but empty response means the client didn't actually return a token
113
- if (!token) {
114
- throw new Error('No token received');
115
- }
116
-
117
- // store the token in the cache
118
- tokenCache.set(getScopeKey(scopes), token);
119
-
120
- return token;
121
- }
122
-
123
- /**
124
- * Retrieves an access token for the specified scopes. If no valid token is found,
125
- * it requests a new one from the client.
126
- *
127
- * @param scopes - An array of strings representing the required scopes for the token.
128
- * @returns A promise that resolves to the access token as a string.
129
- * @throws An error if no access token is found after attempting to retrieve or request one.
130
- */
131
- async function getToken(scopes: string[]): Promise<string> {
132
- // if no valid token is found, request a new one
133
- if (!isTokenValid(scopes)) {
134
- await requestTokenFromClient(scopes);
135
- }
136
- const scopeKey = getScopeKey(scopes);
137
- const { accessToken } = tokenCache.get(scopeKey) || {};
138
- // A missing accessToken here means the client failed to provide a usable token
139
- if (!accessToken) {
140
- throw new Error('No access token found');
141
- }
142
- return accessToken;
143
- }
144
-
145
- // Match request to proxy config
146
- /**
147
- * Retrieves the matching token injection configuration for a given URL.
148
- *
149
- * @param url - The URL to match against the token injection settings.
150
- * @returns The matching `TokenInjectionSetting` if found, otherwise `undefined`.
151
- *
152
- * The function compares the provided URL with the `url` property of each
153
- * `TokenInjectionSetting` in the `tokenInjectionSettings` array. If the
154
- * provided URL starts with the resolved `config.url`, it is considered a match.
155
- *
156
- * Note:
157
- * - If `config.url` starts with a `/`, it is resolved relative to the service
158
- * worker's origin (`self.location.origin`).
159
- * - The comparison is performed using fully resolved absolute URLs.
160
- */
161
- function getMatchingConfig(url: string): ResourceConfiguration | undefined {
162
- // Find the first configured resource whose resolved base URL prefixes the request URL
163
- return resourceConfigurations.find((config) => {
164
- const configUrl = new URL(
165
- config.url,
166
- config.url.startsWith('/') ? self.location.origin : undefined,
167
- ).href;
168
- const requestUrl = new URL(url, self.location.origin).href;
169
- return requestUrl.startsWith(configUrl);
170
- });
171
- }
172
-
173
- // Install event
174
- self.addEventListener('install', (event: ExtendableEvent) => {
175
- event.waitUntil(self.skipWaiting());
176
- });
177
-
178
- // Activate event
179
- self.addEventListener('activate', (event: ExtendableEvent) => {
180
- event.waitUntil(self.clients.claim());
181
- });
182
-
183
- // Handle configuration from main thread
184
- self.addEventListener('message', async (event: ExtendableMessageEvent) => {
185
- const { type, config } = event.data;
186
- // Only the INIT_CONFIG message carries resource configuration to apply
187
- if (type === 'INIT_CONFIG') {
188
- resourceConfigurations = config as ResourceConfiguration[];
189
-
190
- // CRITICAL: Force skipWaiting() and claim clients to ensure this service worker takes control
191
- // This handles both waiting and already-active service workers during hard refresh
192
- // - skipWaiting() forces activation if the service worker is in waiting state
193
- // - clients.claim() takes control of all clients immediately
194
- await self.skipWaiting();
195
- await self.clients.claim();
196
- }
197
- });
198
-
199
- // Handle fetch events
200
- self.addEventListener('fetch', (event: FetchEvent) => {
201
- const request = event.request.clone();
202
- const url = new URL(request.url);
203
- const matchedConfig = getMatchingConfig(url.toString());
204
-
205
- // only handle requests that match the config
206
- if (matchedConfig) {
207
- const requestHeaders = new Headers(request.headers);
208
- const handleRequest = async () => {
209
- // if the matched config has scopes, append the token to the request
210
- if (matchedConfig.scopes) {
211
- const token = await getToken(matchedConfig.scopes);
212
- requestHeaders.set('Authorization', `Bearer ${token}`);
213
- }
214
-
215
- // if the matched config has a rewrite, rewrite the url
216
- if (typeof matchedConfig.rewrite === 'string') {
217
- url.pathname = url.pathname.replace(matchedConfig?.url, matchedConfig.rewrite);
218
- }
219
-
220
- // Consume the ReadableStream body and convert to text
221
- // ReadableStreams can only be consumed once, so we extract the content here
222
- // request.text() resolves to empty string when the request has no body
223
- const body = await request.text();
224
-
225
- // fetch the request with the modified url and headers, preserving the original HTTP method and body
226
- // This ensures OPTIONS, PATCH, DELETE and other methods are forwarded correctly
227
- // `cache` is forwarded explicitly - otherwise callers requesting `no-store` (e.g. polling
228
- // endpoints) would silently fall back to the default HTTP cache once re-fetched here.
229
- return fetch(url, {
230
- method: request.method,
231
- headers: requestHeaders,
232
- body: body || undefined,
233
- cache: request.cache,
234
- });
235
- };
236
- event.respondWith(handleRequest());
237
- }
238
- });
package/src/index.ts DELETED
@@ -1,34 +0,0 @@
1
- /**
2
- * @module @equinor/fusion-framework-vite-plugin-spa
3
- *
4
- * Vite plugin for building Fusion Framework Single Page Applications (SPAs).
5
- *
6
- * Provides HTML template generation, MSAL authentication bootstrapping,
7
- * service discovery wiring, portal loading, and authenticated API proxying
8
- * via a service worker.
9
- *
10
- * @remarks
11
- * This plugin is intended for non-production development environments and
12
- * is designed for use with `@equinor/fusion-framework-cli`.
13
- *
14
- * @example
15
- * ```ts
16
- * import { fusionSpaPlugin } from '@equinor/fusion-framework-vite-plugin-spa';
17
- *
18
- * export default defineConfig({
19
- * plugins: [
20
- * fusionSpaPlugin({
21
- * generateTemplateEnv: () => ({
22
- * title: 'My App',
23
- * portal: { id: 'my-portal' },
24
- * serviceDiscovery: { url: 'https://...', scopes: ['api://...'] },
25
- * msal: { tenantId: '...', clientId: '...', redirectUri: '...' },
26
- * }),
27
- * }),
28
- * ],
29
- * });
30
- * ```
31
- */
32
- export { default, plugin as fusionSpaPlugin, type PluginOptions } from './plugin.js';
33
-
34
- export * from './types.js';
package/src/plugin.ts DELETED
@@ -1,202 +0,0 @@
1
- import { normalizePath, type Plugin } from 'vite';
2
-
3
- import { fileURLToPath } from 'node:url';
4
-
5
- import defaultTemplate from './html/html.js';
6
-
7
- import { objectToEnv } from './util/object-to-env.js';
8
- import { loadEnvironment } from './util/load-environment.js';
9
-
10
- import type { TemplateEnv, TemplateEnvFn } from './types.js';
11
-
12
- /**
13
- * Options accepted by the Fusion SPA Vite plugin.
14
- *
15
- * @remarks
16
- * Controls HTML template generation, environment variable prefixing,
17
- * and the factory that produces template environment values from the
18
- * current Vite build/serve context.
19
- *
20
- * @template TEnv - Shape of the template environment. Defaults to {@link TemplateEnv}.
21
- *
22
- * @example
23
- * ```ts
24
- * const opts: PluginOptions<FusionTemplateEnv> = {
25
- * templateEnvPrefix: 'FUSION_SPA_',
26
- * generateTemplateEnv: (env) => ({
27
- * title: 'My App',
28
- * portal: { id: 'my-portal' },
29
- * serviceDiscovery: { url: '...', scopes: ['...'] },
30
- * msal: { tenantId: '...', clientId: '...', redirectUri: '...' },
31
- * }),
32
- * };
33
- * ```
34
- */
35
- export type PluginOptions<TEnv extends TemplateEnv = TemplateEnv> = {
36
- /**
37
- * Custom HTML template string.
38
- *
39
- * @remarks
40
- * When omitted the plugin uses a built-in template that loads the
41
- * bootstrap script, sets the page title, and includes the Equinor font.
42
- */
43
- template?: string;
44
-
45
- /**
46
- * Prefix used when reading environment variables from `.env` files.
47
- *
48
- * @defaultValue `'FUSION_SPA_'`
49
- */
50
- templateEnvPrefix?: string;
51
-
52
- /**
53
- * Factory that returns partial environment values merged with defaults
54
- * and flattened into `{templateEnvPrefix}*` variables.
55
- *
56
- * @see {@link TemplateEnvFn}
57
- */
58
- generateTemplateEnv?: TemplateEnvFn<TEnv>;
59
-
60
- /**
61
- * Optional logger used for plugin diagnostics during Vite config
62
- * resolution.
63
- */
64
- logger?: Pick<Console, 'debug' | 'info' | 'warn' | 'error'>;
65
- };
66
-
67
- /**
68
- * Built-in defaults applied when no matching value is provided by
69
- * {@link PluginOptions.generateTemplateEnv} or `.env` files.
70
- */
71
- const defaultEnv: Partial<TemplateEnv> = {
72
- title: 'Fusion SPA',
73
- bootstrap: '/@fusion-spa-bootstrap.js',
74
- };
75
-
76
- /**
77
- * Creates the Fusion SPA Vite plugin.
78
- *
79
- * @remarks
80
- * The plugin hooks into Vite's `config`, `resolveId`, and `configureServer`
81
- * lifecycle to:
82
- *
83
- * 1. Flatten {@link PluginOptions.generateTemplateEnv | template env} values
84
- * and `.env` overrides into `import.meta.env.FUSION_SPA_*` defines.
85
- * 2. Resolve virtual module IDs (`/@fusion-spa-bootstrap.js`,
86
- * `/@fusion-spa-sw.js`) to the package's pre-built HTML assets.
87
- * 3. Serve the SPA HTML template for every `text/html` GET request
88
- * (SPA fallback).
89
- *
90
- * @template TEnv - Shape of the template environment.
91
- * @param options - Plugin configuration. See {@link PluginOptions}.
92
- * @returns A Vite {@link Plugin} instance named `fusion-framework-plugin-spa`.
93
- *
94
- * @example
95
- * ```ts
96
- * import { plugin as fusionSpaPlugin } from '@equinor/fusion-framework-vite-plugin-spa';
97
- *
98
- * export default defineConfig({
99
- * plugins: [
100
- * fusionSpaPlugin({
101
- * generateTemplateEnv: () => ({
102
- * title: 'My App',
103
- * portal: { id: 'my-portal' },
104
- * }),
105
- * }),
106
- * ],
107
- * });
108
- * ```
109
- */
110
- export const plugin = <TEnv extends TemplateEnv = TemplateEnv>(
111
- options?: PluginOptions<TEnv>,
112
- ): Plugin => {
113
- // SPA index template
114
- const indexTemplate = options?.template ?? defaultTemplate;
115
- const log = options?.logger;
116
-
117
- return {
118
- name: 'fusion-framework-plugin-spa',
119
- resolveId: async (id) => {
120
- // resolve resource aliases to the correct path
121
- switch (id) {
122
- case '/@fusion-spa-bootstrap.js': {
123
- const file = await import.meta.resolve(
124
- '@equinor/fusion-framework-vite-plugin-spa/bootstrap.js',
125
- );
126
- return fileURLToPath(file);
127
- }
128
- case '/@fusion-spa-sw.js': {
129
- const file = await import.meta.resolve('@equinor/fusion-framework-vite-plugin-spa/sw.js');
130
- return fileURLToPath(file);
131
- }
132
- }
133
- },
134
- config: async (config, configEnv) => {
135
- const templateEnvPrefix = options?.templateEnvPrefix ?? 'FUSION_SPA_';
136
- // generate environment variables from plugin options
137
- const pluginEnvObj = { ...defaultEnv, ...options?.generateTemplateEnv?.(configEnv) };
138
- const pluginEnv = objectToEnv(pluginEnvObj ?? defaultEnv, templateEnvPrefix);
139
-
140
- log?.debug('plugin config environment\n', pluginEnv);
141
-
142
- // load environment variables from files
143
- const loadedEnv = loadEnvironment(config, configEnv, templateEnvPrefix);
144
-
145
- log?.debug('plugin loaded environment\n', pluginEnv);
146
-
147
- // Loaded env values override plugin-configured defaults
148
- const env = { ...pluginEnv, ...loadedEnv };
149
-
150
- log?.debug('plugin environment\n', env);
151
-
152
- // define environment variables
153
- config.define ??= {};
154
- // Convert each env entry into a valid JS expression for Vite's config.define
155
- for (const [key, value] of Object.entries(env)) {
156
- // All values must be valid JavaScript expressions for Vite's config.define
157
- // Try to parse as JSON first (handles booleans, numbers, objects, arrays)
158
- // If that fails, stringify the raw value as a string literal
159
- try {
160
- const parsed = JSON.parse(String(value));
161
- // Re-stringify to ensure it's a valid JS expression
162
- config.define[`import.meta.env.${key}`] = JSON.stringify(parsed);
163
- } catch {
164
- // If JSON.parse fails, it's a plain string - wrap it as a JSON string for Vite
165
- config.define[`import.meta.env.${key}`] = JSON.stringify(String(value));
166
- }
167
- }
168
-
169
- config.server ??= {};
170
- config.server.fs ??= {};
171
- config.server.fs.allow ??= [];
172
- // allow access to the html directory
173
-
174
- const htmlDir = fileURLToPath(new URL('../html', import.meta.url));
175
- config.server.fs.allow.push(normalizePath(htmlDir));
176
-
177
- log?.info(`plugin configured for ${env.FUSION_SPA_PORTAL_ID}`);
178
- },
179
- configureServer(server) {
180
- // Apply SPA fallback
181
- server.middlewares.use(async (req, res, next) => {
182
- // Skip if this is not a GET request or the request is not for HTML
183
- if (!req.url || req.method !== 'GET' || !req.headers.accept?.includes('text/html')) {
184
- return next();
185
- }
186
-
187
- const html = await server.transformIndexHtml(req.url, indexTemplate, req.originalUrl);
188
-
189
- res.writeHead(200, {
190
- 'content-type': 'text/html',
191
- 'content-length': Buffer.byteLength(html),
192
- 'cache-control': 'no-cache',
193
- ...server.config.server.headers,
194
- });
195
-
196
- return res.end(html);
197
- });
198
- },
199
- };
200
- };
201
-
202
- export default plugin;
package/src/types.ts DELETED
@@ -1,160 +0,0 @@
1
- import type { ConfigEnv } from 'vite';
2
-
3
- /**
4
- * Base type for template environment variables.
5
- *
6
- * @remarks
7
- * A loose record that can hold any key-value pairs later flattened into
8
- * `FUSION_SPA_*` environment variables by {@link objectToEnv}.
9
- * Extend this type (or use {@link FusionTemplateEnv}) when you need a
10
- * strongly-typed environment shape.
11
- */
12
- export type TemplateEnv = Record<string, unknown>;
13
-
14
- /**
15
- * Describes a single resource that the SPA service worker should intercept.
16
- *
17
- * When the service worker sees a fetch request whose URL starts with {@link url},
18
- * it optionally rewrites the path and attaches an OAuth Bearer token obtained
19
- * from the MSAL module for the specified {@link scopes}.
20
- *
21
- * @example
22
- * ```ts
23
- * const resource: ResourceConfiguration = {
24
- * url: '/app-proxy',
25
- * rewrite: '/@fusion-api/app',
26
- * scopes: ['api://backend/.default'],
27
- * };
28
- * ```
29
- */
30
- export type ResourceConfiguration = {
31
- /** URL path prefix to match against outgoing fetch requests. */
32
- url: string;
33
- /** OAuth scopes used to acquire a Bearer token for matched requests. */
34
- scopes?: string[];
35
- /** Replacement path prefix; the matched {@link url} segment is swapped for this value. */
36
- rewrite?: string;
37
- };
38
-
39
- /**
40
- * Strongly-typed environment configuration consumed by the default SPA
41
- * HTML template and bootstrap script.
42
- *
43
- * @remarks
44
- * Values are flattened to `FUSION_SPA_*` environment variables at build time
45
- * and injected into the HTML template via Vite's
46
- * {@link https://vite.dev/guide/env-and-mode.html#html-constant-replacement | constant replacement}.
47
- * They can also be overridden through a `.env` file.
48
- *
49
- * @see {@link PluginOptions.generateTemplateEnv} for how to supply these values.
50
- */
51
- export type FusionTemplateEnv = {
52
- /** HTML `<title>` for the generated page. */
53
- title: string;
54
-
55
- /**
56
- * Path to the bootstrap module loaded by the HTML template.
57
- *
58
- * @defaultValue `'/@fusion-spa-bootstrap.js'`
59
- */
60
- bootstrap: string;
61
-
62
- /** Optional telemetry configuration. */
63
- telemetry?: {
64
- /**
65
- * Minimum severity level for console telemetry output.
66
- *
67
- * @remarks
68
- * Maps to `TelemetryLevel` values: Debug (0), Information (1),
69
- * Warning (2), Error (3), Critical (4).
70
- *
71
- * @defaultValue `1` (Information)
72
- */
73
- consoleLevel?: number;
74
- };
75
-
76
- /**
77
- * Portal to load and render inside the SPA shell.
78
- *
79
- * @remarks
80
- * The `id` can reference:
81
- * - A local npm package (e.g. `@equinor/fusion-framework-dev-portal`)
82
- * - A portal identifier from the Fusion Portal Service
83
- * - Any custom portal implementation
84
- */
85
- portal: {
86
- /** Portal identifier used to fetch the portal manifest. */
87
- id: string;
88
- /**
89
- * Version tag for the portal manifest.
90
- * @defaultValue `'latest'`
91
- */
92
- tag?: string;
93
- /**
94
- * When `true`, portal entry point requests are prefixed with `/portal-proxy`
95
- * so they can be intercepted by a proxy server.
96
- * @defaultValue `false`
97
- */
98
- proxy?: boolean;
99
- };
100
-
101
- /** Service discovery endpoint and authentication scopes. */
102
- serviceDiscovery: {
103
- /** URL of the Fusion service discovery endpoint. */
104
- url: string;
105
- /** OAuth scopes required to authenticate service discovery requests. */
106
- scopes: string[];
107
- };
108
-
109
- /** Microsoft Authentication Library (MSAL) configuration for Azure AD. */
110
- msal: {
111
- /** Azure AD tenant identifier. */
112
- tenantId: string;
113
- /** Application (client) identifier registered in Azure AD. */
114
- clientId: string;
115
- /** Redirect URI for the authentication callback. */
116
- redirectUri: string;
117
- /**
118
- * When `'true'`, the application automatically prompts for login
119
- * on initial load.
120
- */
121
- requiresAuth: string;
122
- /**
123
- * When `'true'`, authentication is served by an in-process mock client
124
- * instead of Entra ID — no credentials, redirects, or network calls.
125
- * Intended for CI/Playwright runs against the dev server.
126
- */
127
- mock?: string | boolean;
128
- /**
129
- * A mock JWT (e.g. from `createMockToken`) whose payload claims (`name`,
130
- * `preferred_username`, `oid`, `tid`, `scp`) become the signed-in mock
131
- * user. Only read when {@link mock} is set; ignored otherwise.
132
- */
133
- mockToken?: string;
134
- };
135
-
136
- /** Service worker resource interception configuration. */
137
- serviceWorker: {
138
- /**
139
- * Array of resource configurations the service worker will manage.
140
- * @see {@link ResourceConfiguration}
141
- */
142
- resources: ResourceConfiguration[];
143
- };
144
- };
145
-
146
- /**
147
- * Factory function that produces a partial template environment from the
148
- * current Vite {@link ConfigEnv} (mode, command, etc.).
149
- *
150
- * @remarks
151
- * Returned values are merged with defaults and flattened into
152
- * `FUSION_SPA_*` environment variables.
153
- *
154
- * @template TEnv - Shape of the environment configuration. Defaults to {@link TemplateEnv}.
155
- * @param configEnv - Vite configuration environment containing `mode` and `command`.
156
- * @returns A partial environment object, or `undefined` to use defaults only.
157
- */
158
- export type TemplateEnvFn<TEnv extends TemplateEnv> = (
159
- configEnv: ConfigEnv,
160
- ) => Partial<TEnv> | undefined;
@@ -1,35 +0,0 @@
1
- import { type ConfigEnv, loadEnv, type UserConfig } from 'vite';
2
- import { resolve } from 'node:path';
3
-
4
- /**
5
- * Loads environment variables from `.env` files for the Fusion SPA plugin.
6
- *
7
- * @remarks
8
- * Delegates to Vite's {@link https://vite.dev/guide/env-and-mode.html#env-files | loadEnv}
9
- * using the resolved project root and env directory. Only variables whose
10
- * name starts with {@link namespace} are returned.
11
- *
12
- * Values loaded here override any matching keys produced by
13
- * {@link PluginOptions.generateTemplateEnv}.
14
- *
15
- * @param config - Vite user configuration (`root`, `envDir`).
16
- * @param env - Vite configuration environment containing the current `mode`.
17
- * @param namespace - Variable name prefix to filter on.
18
- * @returns A flat record of matching environment variable names to their string values.
19
- *
20
- * @defaultValue namespace — `'FUSION_SPA_'`
21
- */
22
- export function loadEnvironment(
23
- config: UserConfig,
24
- env: ConfigEnv,
25
- namespace = 'FUSION_SPA_',
26
- ): Record<string, string> {
27
- // resolve the root directory
28
- const resolvedRoot = resolve(config.root || process.cwd());
29
- // resolve the environment directory
30
- const envDir = config.envDir ? resolve(resolvedRoot, config.envDir) : resolvedRoot;
31
- // load environment variables from the specified directory
32
- return loadEnv(env.mode, envDir, namespace);
33
- }
34
-
35
- export default loadEnvironment;