@equinor/fusion-framework-dev-server 2.0.19 → 2.1.0-next.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/CHANGELOG.md +37 -2
- package/README.md +60 -450
- package/dist/esm/process-services.js +13 -5
- package/dist/esm/process-services.js.map +1 -1
- package/dist/esm/process-services.test.js +24 -0
- package/dist/esm/process-services.test.js.map +1 -0
- package/dist/esm/version.js +1 -1
- package/dist/esm/version.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/process-services.test.d.ts +1 -0
- package/dist/types/types.d.ts +2 -2
- package/dist/types/version.d.ts +1 -1
- package/docs/advanced.md +79 -0
- package/docs/configuration.md +117 -0
- package/docs/getting-started.md +82 -0
- package/docs/mocking.md +229 -0
- package/docs/troubleshooting.md +68 -0
- package/package.json +5 -5
- package/src/process-services.test.ts +36 -0
- package/src/process-services.ts +13 -5
- package/src/types.ts +30 -29
- package/src/version.ts +1 -1
- package/vitest.config.ts +11 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,45 @@
|
|
|
1
1
|
# @equinor/fusion-framework-dev-server
|
|
2
2
|
|
|
3
|
-
## 2.0.
|
|
3
|
+
## 2.1.0-next.2
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 09a9bcc: Expose `DevServerOptions` as an interface so optional development tooling can add typed
|
|
8
|
+
configuration through TypeScript module augmentation.
|
|
9
|
+
|
|
10
|
+
### Patch Changes
|
|
11
|
+
|
|
12
|
+
- 09a9bcc: Proxy mock services advertised through `<service>.localhost` via plain `localhost` while preserving
|
|
13
|
+
the service-key path. This avoids `ENOTFOUND` failures on Linux runners without changing normal
|
|
14
|
+
upstream service proxying.
|
|
15
|
+
- Updated dependencies [09a9bcc]
|
|
16
|
+
- Updated dependencies [09a9bcc]
|
|
17
|
+
- @equinor/fusion-framework-vite-plugin-api-service@2.0.6-next.1
|
|
18
|
+
- @equinor/fusion-framework-vite-plugin-spa@4.1.0-next.2
|
|
19
|
+
|
|
20
|
+
## 2.0.19-next.1
|
|
4
21
|
|
|
5
22
|
### Patch Changes
|
|
6
23
|
|
|
7
|
-
- @equinor/fusion-framework-vite-plugin-spa@4.0.17
|
|
24
|
+
- @equinor/fusion-framework-vite-plugin-spa@4.0.17-next.1
|
|
25
|
+
|
|
26
|
+
## 2.0.19-next.0
|
|
27
|
+
|
|
28
|
+
### Patch Changes
|
|
29
|
+
|
|
30
|
+
- e8aae1f: Internal: publish every package on the `next` pre-release tag so the whole framework can be installed as a coherent set.
|
|
31
|
+
|
|
32
|
+
Packages without their own changes are bumped only to receive a `-next.N` version and the `next` dist-tag on npm. Install with:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pnpm add @equinor/fusion-framework-react-app@next
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
- Updated dependencies [e8aae1f]
|
|
39
|
+
- Updated dependencies [2836e0b]
|
|
40
|
+
- @equinor/fusion-framework-vite-plugin-api-service@2.0.6-next.0
|
|
41
|
+
- @equinor/fusion-framework-vite-plugin-spa@4.0.17-next.0
|
|
42
|
+
- @equinor/fusion-log@2.0.3-next.0
|
|
8
43
|
|
|
9
44
|
## 2.0.18
|
|
10
45
|
|
package/README.md
CHANGED
|
@@ -1,495 +1,105 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @equinor/fusion-framework-dev-server
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Development server primitives for Fusion Framework applications. The package combines Vite,
|
|
4
|
+
React Fast Refresh, SPA environment injection, Fusion service discovery, and local API proxying.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
> [!TIP]
|
|
7
|
+
> Most application developers should start with `ffc app dev`. Use this package directly when you
|
|
8
|
+
> are building framework tooling, a custom development command, or a Vite integration that needs
|
|
9
|
+
> control over server creation.
|
|
6
10
|
|
|
7
|
-
|
|
8
|
-
- 🔗 **Service Discovery**: Automatic API service discovery and proxying
|
|
9
|
-
- 🏠 **Portal Support**: Built-in portal development with manifest loading
|
|
10
|
-
- 🔧 **API Mocking**: Easy mocking and overriding of API responses
|
|
11
|
-
- 📊 **Telemetry**: Integrated logging and debugging capabilities
|
|
12
|
-
- ⚙️ **Flexible Configuration**: Extensive customization options
|
|
11
|
+
## Choose your entry point
|
|
13
12
|
|
|
14
|
-
|
|
13
|
+
| Goal | Start here |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| Run a Fusion application locally | `ffc app dev` from `@equinor/fusion-framework-cli` |
|
|
16
|
+
| Create and start a configured Vite server | `createDevServer(options, overrides?)` |
|
|
17
|
+
| Generate Vite configuration without starting a server | `createDevServerConfig(options, overrides?)` |
|
|
18
|
+
| Customize service discovery proxy routes | `processServices(data, args)` |
|
|
19
|
+
| Develop against local OpenAPI mocks | [`@equinor/fusion-framework-cli-plugin-mock-server`](../cli-plugins/mock-server/README.md) |
|
|
15
20
|
|
|
16
|
-
|
|
21
|
+
## Quick start
|
|
17
22
|
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const devServer = await createDevServer({
|
|
22
|
-
spa: {
|
|
23
|
-
templateEnv: {
|
|
24
|
-
portal: {
|
|
25
|
-
id: '@equinor/fusion-framework-dev-portal',
|
|
26
|
-
},
|
|
27
|
-
title: 'My Fusion App',
|
|
28
|
-
serviceDiscovery: {
|
|
29
|
-
url: 'https://service-discovery.example.com',
|
|
30
|
-
scopes: ['api://example.com/user_impersonation'],
|
|
31
|
-
},
|
|
32
|
-
msal: {
|
|
33
|
-
clientId: 'your-client-id',
|
|
34
|
-
tenantId: 'your-tenant-id',
|
|
35
|
-
redirectUri: '/authentication/login-callback',
|
|
36
|
-
requiresAuth: 'true',
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
},
|
|
40
|
-
api: {
|
|
41
|
-
serviceDiscoveryUrl: 'https://service-discovery.example.com',
|
|
42
|
-
},
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
await devServer.listen();
|
|
46
|
-
devServer.printUrls();
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
If you only need the Vite configuration object without starting the server, use `createDevServerConfig(options, overrides?)` instead. This returns a `UserConfig` that you can pass to Vite directly or merge with other configurations.
|
|
50
|
-
|
|
51
|
-
## Configuration
|
|
52
|
-
|
|
53
|
-
The dev server accepts a configuration object with the following structure:
|
|
54
|
-
|
|
55
|
-
### SPA Configuration
|
|
56
|
-
|
|
57
|
-
Configure the Single Page Application environment and template generation:
|
|
58
|
-
|
|
59
|
-
```typescript
|
|
60
|
-
{
|
|
61
|
-
spa: {
|
|
62
|
-
templateEnv: {
|
|
63
|
-
// Portal configuration
|
|
64
|
-
portal: {
|
|
65
|
-
id: 'your-portal-id', // Portal identifier
|
|
66
|
-
},
|
|
67
|
-
|
|
68
|
-
// Application title
|
|
69
|
-
title: 'My Application',
|
|
70
|
-
|
|
71
|
-
// Service discovery settings (SPA-side — tells the browser where to find services)
|
|
72
|
-
// The API-side counterpart is `api.serviceDiscoveryUrl` which sets up server-side proxying.
|
|
73
|
-
// During development you can redirect individual services to local URLs by storing
|
|
74
|
-
// overrides in sessionStorage under the key "overriddenServiceDiscoveryUrls".
|
|
75
|
-
serviceDiscovery: {
|
|
76
|
-
url: 'https://service-discovery.example.com',
|
|
77
|
-
scopes: ['scope1', 'scope2'],
|
|
78
|
-
},
|
|
79
|
-
|
|
80
|
-
// Authentication configuration
|
|
81
|
-
msal: {
|
|
82
|
-
clientId: 'your-client-id',
|
|
83
|
-
tenantId: 'your-tenant-id',
|
|
84
|
-
redirectUri: '/authentication/login-callback',
|
|
85
|
-
requiresAuth: 'true', // or 'false'
|
|
86
|
-
},
|
|
87
|
-
|
|
88
|
-
// Optional telemetry configuration (browser console logging)
|
|
89
|
-
telemetry: {
|
|
90
|
-
consoleLevel: 1, // 0=Debug, 1=Information, 2=Warning, 3=Error, 4=Critical
|
|
91
|
-
},
|
|
92
|
-
|
|
93
|
-
// Optional service worker configuration
|
|
94
|
-
serviceWorker: {
|
|
95
|
-
resources: [
|
|
96
|
-
{
|
|
97
|
-
url: '/api',
|
|
98
|
-
rewrite: '/api-v1',
|
|
99
|
-
scopes: ['api://example.com/user_impersonation'],
|
|
100
|
-
},
|
|
101
|
-
],
|
|
102
|
-
},
|
|
103
|
-
},
|
|
104
|
-
},
|
|
105
|
-
}
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
### API Configuration
|
|
109
|
-
|
|
110
|
-
Configure API proxying and service discovery:
|
|
111
|
-
|
|
112
|
-
```typescript
|
|
113
|
-
{
|
|
114
|
-
api: {
|
|
115
|
-
// Required: Service discovery endpoint
|
|
116
|
-
serviceDiscoveryUrl: 'https://service-discovery.example.com',
|
|
117
|
-
|
|
118
|
-
// Optional: Custom service processing
|
|
119
|
-
// Takes an array of FusionService objects ({ key, uri, name }) from discovery
|
|
120
|
-
// and returns { data: FusionService[], routes: ApiRoute[] } with proxy routes.
|
|
121
|
-
processServices: (services, route) => {
|
|
122
|
-
// Process and return services with routes
|
|
123
|
-
return processServices(services, route);
|
|
124
|
-
},
|
|
125
|
-
|
|
126
|
-
// Optional: Additional API routes
|
|
127
|
-
routes: [
|
|
128
|
-
{
|
|
129
|
-
match: '/api/custom/*',
|
|
130
|
-
middleware: (req, res) => {
|
|
131
|
-
// Custom middleware logic
|
|
132
|
-
res.end(JSON.stringify({ custom: 'response' }));
|
|
133
|
-
},
|
|
134
|
-
},
|
|
135
|
-
],
|
|
136
|
-
},
|
|
137
|
-
}
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
### Logging Configuration
|
|
141
|
-
|
|
142
|
-
Configure CLI/server-side logging levels and custom loggers:
|
|
143
|
-
|
|
144
|
-
```typescript
|
|
145
|
-
{
|
|
146
|
-
log: {
|
|
147
|
-
// Optional: CLI log level (0=None, 1=Error, 2=Warning, 3=Info, 4=Debug)
|
|
148
|
-
level: 3, // Default is Info level
|
|
149
|
-
|
|
150
|
-
// Optional: Custom logger instance
|
|
151
|
-
logger: new ConsoleLogger('my-dev-server'),
|
|
152
|
-
},
|
|
153
|
-
}
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
> [!NOTE]
|
|
157
|
-
> **Telemetry vs CLI Logging**: The `telemetry.consoleLevel` controls logging output in the browser console (visible to end users), while `log.level` controls server-side logging in the terminal/command line (visible to developers). These use different logging systems with different level mappings.
|
|
158
|
-
|
|
159
|
-
### Main Functions
|
|
160
|
-
|
|
161
|
-
#### `createDevServer(options, overrides?)`
|
|
162
|
-
|
|
163
|
-
Creates and configures a development server instance.
|
|
164
|
-
|
|
165
|
-
**Parameters:**
|
|
166
|
-
- `options` (`DevServerOptions`): Configuration object for the dev server
|
|
167
|
-
- `overrides` (`UserConfig`): Optional Vite configuration overrides
|
|
168
|
-
|
|
169
|
-
**Returns:** `Promise<ViteDevServer>` - Configured Vite development server
|
|
170
|
-
|
|
171
|
-
**Example:**
|
|
172
|
-
```typescript
|
|
173
|
-
const devServer = await createDevServer(config);
|
|
174
|
-
await devServer.listen();
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
#### `createDevServerConfig(options, overrides?)`
|
|
178
|
-
|
|
179
|
-
Creates a Vite configuration object for the dev server.
|
|
180
|
-
|
|
181
|
-
**Parameters:**
|
|
182
|
-
- `options` (`DevServerOptions`): Configuration object for the dev server
|
|
183
|
-
- `overrides` (`UserConfig`): Optional Vite configuration overrides
|
|
184
|
-
|
|
185
|
-
**Returns:** `UserConfig` - Vite configuration object
|
|
186
|
-
|
|
187
|
-
### Utility Functions
|
|
188
|
-
|
|
189
|
-
#### `processServices(data, args)`
|
|
190
|
-
|
|
191
|
-
Processes service discovery data and generates proxy routes.
|
|
192
|
-
|
|
193
|
-
**Parameters:**
|
|
194
|
-
- `data` (`FusionService[]`): Array of services from service discovery
|
|
195
|
-
- `args.route` (`string`): Base route for proxying
|
|
196
|
-
- `args.request` (`IncomingMessage`): HTTP request object
|
|
197
|
-
|
|
198
|
-
**Returns:** Object with processed services and routes
|
|
199
|
-
|
|
200
|
-
### Types
|
|
201
|
-
|
|
202
|
-
#### `DevServerOptions<TEnv>`
|
|
203
|
-
|
|
204
|
-
Configuration options for the development server.
|
|
205
|
-
|
|
206
|
-
```typescript
|
|
207
|
-
type DevServerOptions<TEnv extends Partial<FusionTemplateEnv>> = {
|
|
208
|
-
spa?: {
|
|
209
|
-
templateEnv: TEnv | TemplateEnvFn<TEnv>;
|
|
210
|
-
};
|
|
211
|
-
api: {
|
|
212
|
-
serviceDiscoveryUrl: string;
|
|
213
|
-
processServices?: ApiDataProcessor<FusionService[]>;
|
|
214
|
-
routes?: ApiRoute[];
|
|
215
|
-
};
|
|
216
|
-
log?: {
|
|
217
|
-
level?: number;
|
|
218
|
-
logger?: ConsoleLogger;
|
|
219
|
-
};
|
|
220
|
-
};
|
|
23
|
+
```sh
|
|
24
|
+
pnpm add -D @equinor/fusion-framework-dev-server vite
|
|
221
25
|
```
|
|
222
26
|
|
|
223
|
-
#### `FusionService`
|
|
224
|
-
|
|
225
|
-
Represents a service in the Fusion ecosystem.
|
|
226
|
-
|
|
227
|
-
```typescript
|
|
228
|
-
type FusionService = {
|
|
229
|
-
key: string; // Service identifier
|
|
230
|
-
uri: string; // Service endpoint URL
|
|
231
|
-
name: string; // Human-readable service name
|
|
232
|
-
};
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
## Examples
|
|
236
|
-
|
|
237
|
-
### Basic Portal Development
|
|
238
|
-
|
|
239
27
|
```typescript
|
|
240
28
|
import { createDevServer } from '@equinor/fusion-framework-dev-server';
|
|
241
29
|
|
|
242
|
-
const
|
|
30
|
+
const server = await createDevServer({
|
|
243
31
|
spa: {
|
|
244
32
|
templateEnv: {
|
|
245
33
|
portal: { id: 'my-portal' },
|
|
246
|
-
title: 'My
|
|
34
|
+
title: 'My application',
|
|
247
35
|
serviceDiscovery: {
|
|
248
36
|
url: 'https://service-discovery.example.com',
|
|
249
37
|
scopes: ['api://example.com/user_impersonation'],
|
|
250
38
|
},
|
|
251
39
|
msal: {
|
|
252
|
-
clientId:
|
|
253
|
-
tenantId:
|
|
40
|
+
clientId: 'client-id',
|
|
41
|
+
tenantId: 'tenant-id',
|
|
254
42
|
redirectUri: '/authentication/login-callback',
|
|
255
43
|
requiresAuth: 'true',
|
|
256
44
|
},
|
|
257
45
|
},
|
|
258
46
|
},
|
|
259
|
-
api: {
|
|
260
|
-
serviceDiscoveryUrl: 'https://service-discovery.example.com',
|
|
261
|
-
},
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
await devServer.listen();
|
|
265
|
-
```
|
|
266
|
-
|
|
267
|
-
### Adding Mock Services
|
|
268
|
-
|
|
269
|
-
```typescript
|
|
270
|
-
import { createDevServer, processServices } from '@equinor/fusion-framework-dev-server';
|
|
271
|
-
|
|
272
|
-
const devServer = await createDevServer({
|
|
273
|
-
spa: { /* ... spa config ... */ },
|
|
274
|
-
api: {
|
|
275
|
-
serviceDiscoveryUrl: 'https://service-discovery.example.com',
|
|
276
|
-
processServices: (data, route) => {
|
|
277
|
-
const { data: services, routes } = processServices(data, route);
|
|
278
|
-
|
|
279
|
-
// Add mock services
|
|
280
|
-
return {
|
|
281
|
-
data: services.concat({
|
|
282
|
-
key: 'mock-api',
|
|
283
|
-
name: 'Mock API Service',
|
|
284
|
-
uri: '/mock-api',
|
|
285
|
-
}),
|
|
286
|
-
routes: routes.concat({
|
|
287
|
-
match: '/mock-api/*',
|
|
288
|
-
middleware: (req, res) => {
|
|
289
|
-
res.setHeader('Content-Type', 'application/json');
|
|
290
|
-
res.end(JSON.stringify({ mock: 'data' }));
|
|
291
|
-
},
|
|
292
|
-
}),
|
|
293
|
-
};
|
|
294
|
-
},
|
|
295
|
-
},
|
|
296
|
-
});
|
|
297
|
-
```
|
|
298
|
-
|
|
299
|
-
### Custom Vite Configuration
|
|
300
|
-
|
|
301
|
-
```typescript
|
|
302
|
-
import { createDevServer } from '@equinor/fusion-framework-dev-server';
|
|
303
|
-
import { defineConfig } from 'vite';
|
|
304
|
-
|
|
305
|
-
const devServer = await createDevServer(
|
|
306
|
-
{ /* ... dev server config ... */ },
|
|
307
|
-
defineConfig({
|
|
308
|
-
server: {
|
|
309
|
-
port: 3001,
|
|
310
|
-
host: '0.0.0.0',
|
|
311
|
-
},
|
|
312
|
-
define: {
|
|
313
|
-
__DEV__: true,
|
|
314
|
-
},
|
|
315
|
-
})
|
|
316
|
-
);
|
|
317
|
-
```
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
## Troubleshooting
|
|
321
|
-
|
|
322
|
-
### Common Issues
|
|
323
|
-
|
|
324
|
-
#### "Cannot find module '@equinor/fusion-framework-dev-server'"
|
|
325
|
-
|
|
326
|
-
**Solution:** Make sure the package is installed and you're using the correct import path.
|
|
327
|
-
|
|
328
|
-
```bash
|
|
329
|
-
pnpm add -D @equinor/fusion-framework-dev-server
|
|
330
|
-
```
|
|
331
|
-
|
|
332
|
-
#### Service Discovery Connection Failed
|
|
333
|
-
|
|
334
|
-
**Problem:** The dev server can't connect to the service discovery endpoint.
|
|
335
|
-
|
|
336
|
-
**Solutions:**
|
|
337
|
-
1. Check that `serviceDiscoveryUrl` is correct and accessible
|
|
338
|
-
2. Verify network connectivity to the service discovery endpoint
|
|
339
|
-
3. Check for authentication requirements
|
|
340
|
-
|
|
341
|
-
#### Portal Manifest Not Loading
|
|
342
|
-
|
|
343
|
-
**Problem:** Portal configuration isn't loading properly.
|
|
344
|
-
|
|
345
|
-
**Solutions:**
|
|
346
|
-
1. Verify the portal ID is correct in the `spa.templateEnv.portal.id` field
|
|
347
|
-
2. Check that the portal service is available and responding
|
|
348
|
-
3. Ensure proper authentication is configured
|
|
349
|
-
|
|
350
|
-
#### API Routes Not Working
|
|
351
|
-
|
|
352
|
-
**Problem:** Custom API routes or service proxying isn't functioning.
|
|
353
|
-
|
|
354
|
-
**Solutions:**
|
|
355
|
-
1. Check the `routes` configuration in the `api` section
|
|
356
|
-
2. Verify route patterns match the expected request paths
|
|
357
|
-
3. Ensure middleware functions are properly implemented
|
|
358
|
-
4. Check for conflicts with existing routes
|
|
359
|
-
|
|
360
|
-
#### Authentication Issues
|
|
361
|
-
|
|
362
|
-
**Problem:** MSAL authentication isn't working.
|
|
363
|
-
|
|
364
|
-
**Solutions:**
|
|
365
|
-
1. Verify `clientId` and `tenantId` are correct
|
|
366
|
-
2. Check that `redirectUri` matches your application configuration
|
|
367
|
-
3. Ensure the required scopes are properly configured
|
|
368
|
-
4. Check browser console for authentication errors
|
|
369
|
-
|
|
370
|
-
### Debug Logging
|
|
371
|
-
|
|
372
|
-
Enable debug logging to troubleshoot issues:
|
|
373
|
-
|
|
374
|
-
#### CLI/Server-Side Debug Logging
|
|
375
|
-
```typescript
|
|
376
|
-
const devServer = await createDevServer({
|
|
377
|
-
// ... config
|
|
378
|
-
log: {
|
|
379
|
-
level: 4, // Debug level (0=None, 1=Error, 2=Warning, 3=Info, 4=Debug)
|
|
380
|
-
},
|
|
47
|
+
api: { serviceDiscoveryUrl: 'https://service-discovery.example.com' },
|
|
381
48
|
});
|
|
382
|
-
```
|
|
383
49
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
{
|
|
387
|
-
spa: {
|
|
388
|
-
templateEnv: {
|
|
389
|
-
// ... other config
|
|
390
|
-
telemetry: {
|
|
391
|
-
consoleLevel: 0, // Debug level (0=Debug, 1=Information, 2=Warning, 3=Error, 4=Critical)
|
|
392
|
-
},
|
|
393
|
-
},
|
|
394
|
-
},
|
|
395
|
-
}
|
|
50
|
+
await server.listen();
|
|
51
|
+
server.printUrls();
|
|
396
52
|
```
|
|
397
53
|
|
|
398
|
-
|
|
54
|
+
The browser receives `spa.templateEnv`. The Node development server uses
|
|
55
|
+
`api.serviceDiscoveryUrl` to fetch service definitions and create same-origin proxy routes. These
|
|
56
|
+
two URLs often point to the same endpoint, but they serve different consumers.
|
|
399
57
|
|
|
400
|
-
|
|
58
|
+
Continue with [Getting started](docs/getting-started.md) for the complete mental model.
|
|
401
59
|
|
|
402
|
-
|
|
60
|
+
## Mock APIs locally
|
|
403
61
|
|
|
404
|
-
|
|
405
|
-
|
|
62
|
+
Install the optional mock-server plugin when a backend is unavailable, unstable, or needs
|
|
63
|
+
deterministic responses:
|
|
406
64
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
processServices: (services, route) => {
|
|
411
|
-
const { data, routes } = processServices(services, route);
|
|
412
|
-
|
|
413
|
-
// Filter out development-only services in production
|
|
414
|
-
const filteredData = data.filter(service =>
|
|
415
|
-
process.env.NODE_ENV !== 'production' || !service.key.includes('dev')
|
|
416
|
-
);
|
|
417
|
-
|
|
418
|
-
// Add custom routes for specific services
|
|
419
|
-
const customRoutes = routes.map(route => ({
|
|
420
|
-
...route,
|
|
421
|
-
// Add custom headers or modify proxy behavior
|
|
422
|
-
}));
|
|
423
|
-
|
|
424
|
-
return { data: filteredData, routes: customRoutes };
|
|
425
|
-
},
|
|
426
|
-
},
|
|
427
|
-
});
|
|
428
|
-
```
|
|
429
|
-
|
|
430
|
-
### Integration with Build Tools
|
|
431
|
-
|
|
432
|
-
The dev server works seamlessly with the Fusion Framework CLI:
|
|
433
|
-
|
|
434
|
-
```bash
|
|
435
|
-
# Use with CLI for automatic configuration
|
|
436
|
-
npx @equinor/fusion-framework-cli app dev
|
|
437
|
-
|
|
438
|
-
# Or portal development
|
|
439
|
-
npx @equinor/fusion-framework-cli portal dev
|
|
65
|
+
```sh
|
|
66
|
+
pnpm add -D @equinor/fusion-framework-cli-plugin-mock-server
|
|
67
|
+
ffc mock-server ./mocks --port 4010
|
|
440
68
|
```
|
|
441
69
|
|
|
442
|
-
|
|
70
|
+
> [!IMPORTANT]
|
|
71
|
+
> `ffc mock-server` is a standalone foreground process. Installing the plugin does not start it
|
|
72
|
+
> with `ffc app dev`; the developer or test runner owns its lifecycle.
|
|
443
73
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
```typescript
|
|
447
|
-
import { createDevServer } from '@equinor/fusion-framework-dev-server';
|
|
448
|
-
import myCustomPlugin from 'my-custom-vite-plugin';
|
|
449
|
-
|
|
450
|
-
const devServer = await createDevServer(
|
|
451
|
-
{ /* ... config ... */ },
|
|
452
|
-
{
|
|
453
|
-
plugins: [myCustomPlugin()],
|
|
454
|
-
}
|
|
455
|
-
);
|
|
456
|
-
```
|
|
74
|
+
The plugin adds typed `mockServer` settings to `DevServerOptions` only when its types are imported,
|
|
75
|
+
keeping this base package independent of optional mocking tools.
|
|
457
76
|
|
|
458
|
-
|
|
77
|
+
See [Develop with mock services](docs/mocking.md) for normal development overlays, isolated
|
|
78
|
+
`--mock` mode, direct-only services, and executable `<name>.mock.ts` modules.
|
|
459
79
|
|
|
460
|
-
|
|
80
|
+
## Learn in order
|
|
461
81
|
|
|
462
|
-
|
|
82
|
+
1. [Getting started](docs/getting-started.md) explains the server lifecycle and smallest useful setup.
|
|
83
|
+
2. [Configure the dev server](docs/configuration.md) covers SPA environment, API proxying, and logging.
|
|
84
|
+
3. [Develop with mock services](docs/mocking.md) shows the recommended optional mocking workflow.
|
|
85
|
+
4. [Advanced usage](docs/advanced.md) covers service processing, routes, Vite overrides, and extension interfaces.
|
|
86
|
+
5. [Troubleshooting](docs/troubleshooting.md) maps common symptoms to the responsible configuration.
|
|
463
87
|
|
|
464
|
-
|
|
465
|
-
2. Move service discovery logic to the `api.processServices` function
|
|
466
|
-
3. Configure portal settings in `spa.templateEnv`
|
|
467
|
-
4. Update your start script to use `createDevServer`
|
|
88
|
+
## Public API
|
|
468
89
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
server: {
|
|
475
|
-
proxy: { /* proxy config */ },
|
|
476
|
-
},
|
|
477
|
-
});
|
|
478
|
-
```
|
|
479
|
-
|
|
480
|
-
**After:**
|
|
481
|
-
```typescript
|
|
482
|
-
import { createDevServer } from '@equinor/fusion-framework-dev-server';
|
|
483
|
-
|
|
484
|
-
const devServer = await createDevServer({
|
|
485
|
-
// Move your config here
|
|
486
|
-
});
|
|
487
|
-
```
|
|
90
|
+
- `createDevServer` creates a configured `ViteDevServer`. Call `listen()` yourself.
|
|
91
|
+
- `createDevServerConfig` returns a Vite `UserConfig` for another tool to consume.
|
|
92
|
+
- `processServices` rewrites discovered service URIs through the local proxy and returns routes.
|
|
93
|
+
- `DevServerOptions<TEnv>` configures SPA injection, API discovery, and logging. It is an interface so optional plugins can augment it.
|
|
94
|
+
- `FusionService` describes a backend using `key`, `uri`, `name`, and optional OAuth `scopes`.
|
|
488
95
|
|
|
489
|
-
##
|
|
96
|
+
## Requirements and boundaries
|
|
490
97
|
|
|
491
|
-
|
|
98
|
+
- Vite 7 or 8 is required as a peer dependency.
|
|
99
|
+
- `api.serviceDiscoveryUrl` is required by the low-level API.
|
|
100
|
+
- The package configures development; it does not build or publish applications.
|
|
101
|
+
- Mock-server installation and process lifecycle are optional and external to this package.
|
|
492
102
|
|
|
493
103
|
## License
|
|
494
104
|
|
|
495
|
-
ISC
|
|
105
|
+
ISC
|
|
@@ -69,14 +69,22 @@ export const processServices = (data, args) => {
|
|
|
69
69
|
// and replace the path with the local proxy path
|
|
70
70
|
const serviceUrl = new URL(`${route}/${service.key}`, request.headers.referer);
|
|
71
71
|
apiServices.push({ ...service, uri: String(serviceUrl) });
|
|
72
|
-
//
|
|
72
|
+
// Add the proxy route for this service.
|
|
73
73
|
const url = new URL(service.uri);
|
|
74
|
+
const usesLocalhostSubdomain = url.hostname.endsWith('.localhost');
|
|
75
|
+
// Node does not resolve localhost subdomains consistently across operating systems. The mock
|
|
76
|
+
// server also accepts /<service>/* on plain localhost, so proxy through that portable address.
|
|
77
|
+
if (usesLocalhostSubdomain) {
|
|
78
|
+
url.hostname = 'localhost';
|
|
79
|
+
}
|
|
74
80
|
apiRoutes.push({
|
|
75
81
|
match: `/${service.key}${url.pathname}*sub`,
|
|
76
|
-
proxy:
|
|
77
|
-
target: url.origin
|
|
78
|
-
|
|
79
|
-
|
|
82
|
+
proxy: usesLocalhostSubdomain
|
|
83
|
+
? { target: url.origin }
|
|
84
|
+
: {
|
|
85
|
+
target: url.origin,
|
|
86
|
+
rewrite: (path) => path.replace(`/${service.key}`, ''),
|
|
87
|
+
},
|
|
80
88
|
});
|
|
81
89
|
}
|
|
82
90
|
return { data: apiServices, routes: apiRoutes };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process-services.js","sourceRoot":"","sources":["../../src/process-services.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AACH,MAAM,CAAC,MAAM,eAAe,GAAsC,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE;IAC/E,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAChC,MAAM,SAAS,GAAG,EAAgB,CAAC;IACnC,MAAM,WAAW,GAAG,EAAqB,CAAC;IAE1C,6EAA6E;IAC7E,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;IACzC,CAAC;IAED,oDAAoD;IACpD,gCAAgC;IAChC,KAAK,MAAM,OAAO,IAAI,IAAuB,EAAE,CAAC;QAC9C,oDAAoD;QACpD,iDAAiD;QACjD,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,GAAG,KAAK,IAAI,OAAO,CAAC,GAAG,EAAE,EAAE,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAC/E,WAAW,CAAC,IAAI,CAAC,EAAE,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QAE1D,
|
|
1
|
+
{"version":3,"file":"process-services.js","sourceRoot":"","sources":["../../src/process-services.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AACH,MAAM,CAAC,MAAM,eAAe,GAAsC,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE;IAC/E,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAChC,MAAM,SAAS,GAAG,EAAgB,CAAC;IACnC,MAAM,WAAW,GAAG,EAAqB,CAAC;IAE1C,6EAA6E;IAC7E,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;IACzC,CAAC;IAED,oDAAoD;IACpD,gCAAgC;IAChC,KAAK,MAAM,OAAO,IAAI,IAAuB,EAAE,CAAC;QAC9C,oDAAoD;QACpD,iDAAiD;QACjD,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,GAAG,KAAK,IAAI,OAAO,CAAC,GAAG,EAAE,EAAE,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAC/E,WAAW,CAAC,IAAI,CAAC,EAAE,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QAE1D,wCAAwC;QACxC,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACjC,MAAM,sBAAsB,GAAG,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;QACnE,6FAA6F;QAC7F,+FAA+F;QAC/F,IAAI,sBAAsB,EAAE,CAAC;YAC3B,GAAG,CAAC,QAAQ,GAAG,WAAW,CAAC;QAC7B,CAAC;QACD,SAAS,CAAC,IAAI,CAAC;YACb,KAAK,EAAE,IAAI,OAAO,CAAC,GAAG,GAAG,GAAG,CAAC,QAAQ,MAAM;YAC3C,KAAK,EAAE,sBAAsB;gBAC3B,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE;gBACxB,CAAC,CAAC;oBACE,MAAM,EAAE,GAAG,CAAC,MAAM;oBAClB,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,GAAG,EAAE,EAAE,EAAE,CAAC;iBACvD;SACN,CAAC,CAAC;IACL,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;AAClD,CAAC,CAAC;AAEF,eAAe,eAAe,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { IncomingMessage } from 'node:http';
|
|
2
|
+
import { Socket } from 'node:net';
|
|
3
|
+
import { describe, expect, it } from 'vitest';
|
|
4
|
+
import { processServices } from './process-services.js';
|
|
5
|
+
const request = new IncomingMessage(new Socket());
|
|
6
|
+
request.headers.referer = 'http://localhost:3000';
|
|
7
|
+
describe('processServices', () => {
|
|
8
|
+
it('proxies localhost subdomains through the portable shared-host route', () => {
|
|
9
|
+
const result = processServices([{ key: 'people', name: 'People', uri: 'http://people.localhost:4010' }], { route: '/@fusion-api', request });
|
|
10
|
+
expect(result.routes).toEqual([
|
|
11
|
+
{
|
|
12
|
+
match: '/people/*sub',
|
|
13
|
+
proxy: { target: 'http://localhost:4010' },
|
|
14
|
+
},
|
|
15
|
+
]);
|
|
16
|
+
});
|
|
17
|
+
it('preserves normal upstream proxy targets and path rewriting', () => {
|
|
18
|
+
const result = processServices([{ key: 'people', name: 'People', uri: 'https://people.example/api' }], { route: '/@fusion-api', request });
|
|
19
|
+
const [route] = result.routes ?? [];
|
|
20
|
+
expect(route?.proxy?.target).toBe('https://people.example');
|
|
21
|
+
expect(route?.proxy?.rewrite?.('/people/api/persons')).toBe('/api/persons');
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
//# sourceMappingURL=process-services.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process-services.test.js","sourceRoot":"","sources":["../../src/process-services.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAElC,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAExD,MAAM,OAAO,GAAG,IAAI,eAAe,CAAC,IAAI,MAAM,EAAE,CAAC,CAAC;AAClD,OAAO,CAAC,OAAO,CAAC,OAAO,GAAG,uBAAuB,CAAC;AAElD,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC/B,EAAE,CAAC,qEAAqE,EAAE,GAAG,EAAE;QAC7E,MAAM,MAAM,GAAG,eAAe,CAC5B,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,8BAA8B,EAAE,CAAC,EACxE,EAAE,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,CACnC,CAAC;QAEF,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC;YAC5B;gBACE,KAAK,EAAE,cAAc;gBACrB,KAAK,EAAE,EAAE,MAAM,EAAE,uBAAuB,EAAE;aAC3C;SACF,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4DAA4D,EAAE,GAAG,EAAE;QACpE,MAAM,MAAM,GAAG,eAAe,CAC5B,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,4BAA4B,EAAE,CAAC,EACtE,EAAE,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,CACnC,CAAC;QACF,MAAM,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC;QAEpC,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;QAC5D,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,qBAAqB,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IAC9E,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
package/dist/esm/version.js
CHANGED
package/dist/esm/version.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/version.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAC3B,MAAM,CAAC,MAAM,OAAO,GAAG,
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/version.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAC3B,MAAM,CAAC,MAAM,OAAO,GAAG,cAAc,CAAC"}
|