@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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/types/types.d.ts
CHANGED
|
@@ -63,7 +63,7 @@ export { UserConfig as DevServerOverrides } from 'vite';
|
|
|
63
63
|
* };
|
|
64
64
|
* ```
|
|
65
65
|
*/
|
|
66
|
-
export
|
|
66
|
+
export interface DevServerOptions<TEnv extends Partial<FusionTemplateEnv> = Partial<FusionTemplateEnv>> {
|
|
67
67
|
/** SPA template settings. When provided, the dev server injects these values into the HTML template at serve time. */
|
|
68
68
|
spa?: {
|
|
69
69
|
/** Static environment object or factory function that produces it on each request. */
|
|
@@ -91,4 +91,4 @@ export type DevServerOptions<TEnv extends Partial<FusionTemplateEnv> = Partial<F
|
|
|
91
91
|
/** Custom logger instance. When omitted a default {@link ConsoleLogger} is created. */
|
|
92
92
|
logger?: ConsoleLogger;
|
|
93
93
|
};
|
|
94
|
-
}
|
|
94
|
+
}
|
package/dist/types/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "2.0.
|
|
1
|
+
export declare const version = "2.1.0-next.2";
|
package/docs/advanced.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Advanced usage
|
|
2
|
+
|
|
3
|
+
Use these extension points after the default discovery proxy and SPA environment work. Most
|
|
4
|
+
applications do not need custom service processing.
|
|
5
|
+
|
|
6
|
+
> [!IMPORTANT]
|
|
7
|
+
> These are not the primary service-mocking APIs. Define application mocks in
|
|
8
|
+
> `mocks/<service>.mock.ts` with `defineService`; use `processServices` only when a host must
|
|
9
|
+
> transform real discovery independently of mock behavior.
|
|
10
|
+
|
|
11
|
+
## Transform discovered services
|
|
12
|
+
|
|
13
|
+
Call `processServices(data, args)` first, then transform its result. This preserves standard local
|
|
14
|
+
URI rewriting and proxy route generation.
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
import { createDevServer, processServices } from '@equinor/fusion-framework-dev-server';
|
|
18
|
+
|
|
19
|
+
const server = await createDevServer({
|
|
20
|
+
api: {
|
|
21
|
+
serviceDiscoveryUrl: 'https://service-discovery.example.com',
|
|
22
|
+
processServices: (data, args) => {
|
|
23
|
+
const processed = processServices(data, args);
|
|
24
|
+
return {
|
|
25
|
+
...processed,
|
|
26
|
+
data: processed.data.filter((service) => service.key !== 'deprecated-service'),
|
|
27
|
+
};
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Throwing from a processor fails the discovery request. Validate upstream assumptions instead of
|
|
34
|
+
silently returning a partial service list.
|
|
35
|
+
|
|
36
|
+
> [!CAUTION]
|
|
37
|
+
> A custom processor replaces the default processing entry point. Call `processServices` first
|
|
38
|
+
> unless you intend to own all URI rewriting and proxy-route generation yourself.
|
|
39
|
+
|
|
40
|
+
## Add Vite configuration
|
|
41
|
+
|
|
42
|
+
The second argument is merged over generated Vite configuration:
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import { createDevServer } from '@equinor/fusion-framework-dev-server';
|
|
46
|
+
import myPlugin from 'my-vite-plugin';
|
|
47
|
+
|
|
48
|
+
const server = await createDevServer(
|
|
49
|
+
{ api: { serviceDiscoveryUrl: 'https://service-discovery.example.com' } },
|
|
50
|
+
{
|
|
51
|
+
plugins: [myPlugin()],
|
|
52
|
+
server: { host: '0.0.0.0', port: 3001 },
|
|
53
|
+
},
|
|
54
|
+
);
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Prefer first-class `spa`, `api`, and `log` options when they cover the requirement. Use overrides
|
|
58
|
+
for Vite-owned behavior and additional plugins.
|
|
59
|
+
|
|
60
|
+
## Own the Vite lifecycle
|
|
61
|
+
|
|
62
|
+
Use `createDevServerConfig` when a test harness or larger CLI creates Vite itself:
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
import { createDevServerConfig } from '@equinor/fusion-framework-dev-server';
|
|
66
|
+
import { createServer } from 'vite';
|
|
67
|
+
|
|
68
|
+
const config = createDevServerConfig({
|
|
69
|
+
api: { serviceDiscoveryUrl: 'https://service-discovery.example.com' },
|
|
70
|
+
});
|
|
71
|
+
const server = await createServer(config);
|
|
72
|
+
await server.listen();
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Extend `DevServerOptions`
|
|
76
|
+
|
|
77
|
+
Optional tooling can use declaration merging because `DevServerOptions` is an interface. Keep the
|
|
78
|
+
augmentation in the owning plugin and import that plugin's types where extended options are
|
|
79
|
+
authored. The mock-server plugin's `mockServer` section is the reference implementation.
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Configure the dev server
|
|
2
|
+
|
|
3
|
+
`DevServerOptions<TEnv>` has three base sections: `spa`, `api`, and `log`. Start with required
|
|
4
|
+
`api.serviceDiscoveryUrl`, then add browser environment and logging when your host needs them.
|
|
5
|
+
|
|
6
|
+
## Configure the browser environment
|
|
7
|
+
|
|
8
|
+
`spa.templateEnv` can be a static object or a function. The SPA plugin serializes its value into
|
|
9
|
+
the page for browser-side Fusion modules.
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
const options = {
|
|
13
|
+
spa: {
|
|
14
|
+
templateEnv: {
|
|
15
|
+
portal: { id: 'my-portal' },
|
|
16
|
+
title: 'My application',
|
|
17
|
+
serviceDiscovery: {
|
|
18
|
+
url: 'https://service-discovery.example.com',
|
|
19
|
+
scopes: ['api://example.com/user_impersonation'],
|
|
20
|
+
},
|
|
21
|
+
msal: {
|
|
22
|
+
clientId: 'client-id',
|
|
23
|
+
tenantId: 'tenant-id',
|
|
24
|
+
redirectUri: '/authentication/login-callback',
|
|
25
|
+
requiresAuth: 'true',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Use a factory when values come from a runtime source:
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
const options = {
|
|
36
|
+
spa: {
|
|
37
|
+
templateEnv: () => ({
|
|
38
|
+
title: 'Local application',
|
|
39
|
+
telemetry: { consoleLevel: 0 },
|
|
40
|
+
}),
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Browser telemetry and server logging use different scales. `telemetry.consoleLevel` controls the
|
|
46
|
+
browser console; `log.level` controls terminal output.
|
|
47
|
+
|
|
48
|
+
## Configure service discovery and proxying
|
|
49
|
+
|
|
50
|
+
`api.serviceDiscoveryUrl` is the upstream endpoint the Node server fetches. The default
|
|
51
|
+
`processServices` rewrites each discovered URI to the local origin and creates a proxy route.
|
|
52
|
+
|
|
53
|
+
> [!WARNING]
|
|
54
|
+
> `spa.templateEnv.serviceDiscovery.url` configures the browser, while
|
|
55
|
+
> `api.serviceDiscoveryUrl` configures the Node proxy. Setting only one can produce a page that
|
|
56
|
+
> loads successfully but cannot resolve or proxy services.
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
const options = {
|
|
60
|
+
api: { serviceDiscoveryUrl: 'https://service-discovery.example.com' },
|
|
61
|
+
};
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Add `api.routes` for a small server-owned endpoint:
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
const options = {
|
|
68
|
+
api: {
|
|
69
|
+
serviceDiscoveryUrl: 'https://service-discovery.example.com',
|
|
70
|
+
routes: [
|
|
71
|
+
{
|
|
72
|
+
match: '/health',
|
|
73
|
+
middleware: (_request, response) => {
|
|
74
|
+
response.setHeader('content-type', 'application/json');
|
|
75
|
+
response.end(JSON.stringify({ status: 'ready' }));
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Prefer executable `mocks/<service>.mock.ts` modules from the
|
|
84
|
+
[mock-server guide](mocking.md) for local services. Reserve `api.routes` for server-owned behavior
|
|
85
|
+
that is not a service mock; do not reproduce OpenAPI operations as handwritten dev-server
|
|
86
|
+
middleware.
|
|
87
|
+
|
|
88
|
+
## Configure terminal logging
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
const options = {
|
|
92
|
+
api: { serviceDiscoveryUrl: 'https://service-discovery.example.com' },
|
|
93
|
+
log: { level: 4 },
|
|
94
|
+
};
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Server levels are `0` None, `1` Error, `2` Warning, `3` Info, and `4` Debug. Info is the default.
|
|
98
|
+
Supply `log.logger` when a host already owns a configured `ConsoleLogger` hierarchy.
|
|
99
|
+
|
|
100
|
+
## Extend configuration from optional plugins
|
|
101
|
+
|
|
102
|
+
`DevServerOptions` is an interface so optional packages can contribute configuration without the
|
|
103
|
+
base package depending on them. Importing the mock-server plugin types adds `mockServer`:
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
import type {} from '@equinor/fusion-framework-cli-plugin-mock-server';
|
|
107
|
+
import type { DevServerOptions } from '@equinor/fusion-framework-dev-server';
|
|
108
|
+
|
|
109
|
+
const options: DevServerOptions = {
|
|
110
|
+
api: { serviceDiscoveryUrl: 'https://service-discovery.example.com' },
|
|
111
|
+
mockServer: { path: 'mocks', port: 4010, seed: 42 },
|
|
112
|
+
};
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
> [!TIP]
|
|
116
|
+
> This empty type import activates TypeScript declaration merging: it adds `mockServer` to
|
|
117
|
+
> `DevServerOptions` for type checking without producing a runtime import.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Getting started
|
|
2
|
+
|
|
3
|
+
Use `@equinor/fusion-framework-dev-server` to create a Fusion-aware Vite server programmatically.
|
|
4
|
+
Application projects that only need to run locally should prefer `ffc app dev`, which builds on
|
|
5
|
+
these lower-level primitives and supplies application conventions.
|
|
6
|
+
|
|
7
|
+
## Install the package
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
pnpm add -D @equinor/fusion-framework-dev-server vite
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The package supports Vite 7 and 8.
|
|
14
|
+
|
|
15
|
+
## Understand the request flow
|
|
16
|
+
|
|
17
|
+
```mermaid
|
|
18
|
+
flowchart LR
|
|
19
|
+
Browser[Browser application] -->|reads template environment| SPA[SPA configuration]
|
|
20
|
+
Browser -->|calls local service URI| Proxy[Development server proxy]
|
|
21
|
+
Proxy -->|fetches service list| Discovery[Fusion service discovery]
|
|
22
|
+
Proxy -->|forwards API request| Backend[Backend service]
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`spa.templateEnv.serviceDiscovery` tells browser-side Fusion modules where discovery is.
|
|
26
|
+
`api.serviceDiscoveryUrl` tells the Node server where to fetch services before rewriting them
|
|
27
|
+
through local proxy routes. Keeping the roles separate supports custom portals and proxy setups.
|
|
28
|
+
|
|
29
|
+
## Create the server
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { createDevServer } from '@equinor/fusion-framework-dev-server';
|
|
33
|
+
|
|
34
|
+
const server = await createDevServer({
|
|
35
|
+
spa: {
|
|
36
|
+
templateEnv: {
|
|
37
|
+
portal: { id: 'my-portal' },
|
|
38
|
+
title: 'My application',
|
|
39
|
+
serviceDiscovery: { url: 'https://service-discovery.example.com', scopes: [] },
|
|
40
|
+
msal: {
|
|
41
|
+
clientId: 'client-id',
|
|
42
|
+
tenantId: 'tenant-id',
|
|
43
|
+
redirectUri: '/authentication/login-callback',
|
|
44
|
+
requiresAuth: 'true',
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
api: { serviceDiscoveryUrl: 'https://service-discovery.example.com' },
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
await server.listen();
|
|
52
|
+
server.printUrls();
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
> [!NOTE]
|
|
56
|
+
> `createDevServer` returns a Vite server but does not call `listen()`. Your command chooses when
|
|
57
|
+
> startup happens and how startup failures are handled.
|
|
58
|
+
|
|
59
|
+
## Generate configuration instead
|
|
60
|
+
|
|
61
|
+
Use `createDevServerConfig` when another tool owns the Vite lifecycle:
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
import { createDevServerConfig } from '@equinor/fusion-framework-dev-server';
|
|
65
|
+
import { createServer } from 'vite';
|
|
66
|
+
|
|
67
|
+
const config = createDevServerConfig({
|
|
68
|
+
api: { serviceDiscoveryUrl: 'https://service-discovery.example.com' },
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const server = await createServer(config);
|
|
72
|
+
await server.listen();
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The generated configuration includes React Fast Refresh, SPA template injection, service
|
|
76
|
+
discovery proxying, and the default logger.
|
|
77
|
+
|
|
78
|
+
## Next steps
|
|
79
|
+
|
|
80
|
+
- [Configure the dev server](configuration.md) for environment factories, API routes, and logs.
|
|
81
|
+
- [Develop with mock services](mocking.md) for local or deterministic backends.
|
|
82
|
+
- [Advanced usage](advanced.md) for discovery transforms and Vite plugins.
|
package/docs/mocking.md
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# Develop with mock services
|
|
2
|
+
|
|
3
|
+
Use `@equinor/fusion-framework-cli-plugin-mock-server` when a Fusion application needs a local,
|
|
4
|
+
deterministic, or not-yet-deployed backend. The plugin adds `ffc mock-server`; it does not change
|
|
5
|
+
the base dev-server runtime or start a background process automatically.
|
|
6
|
+
|
|
7
|
+
The recommended application workflow has three parts:
|
|
8
|
+
|
|
9
|
+
1. Create `mocks/<service>.mock.ts` with `defineService`.
|
|
10
|
+
2. Run `ffc mock-server` in a foreground terminal.
|
|
11
|
+
3. Run `ffc app dev` for real discovery plus local overrides, or `ffc app dev --mock` for an
|
|
12
|
+
isolated mock environment.
|
|
13
|
+
|
|
14
|
+
> [!IMPORTANT]
|
|
15
|
+
> Keep service mock behavior in `<service>.mock.ts`, not `dev-server.config.ts`. The executable
|
|
16
|
+
> module is reusable by local development, Playwright, and the programmatic mock server. Reserve
|
|
17
|
+
> `api.routes` and `api.processServices` for advanced server infrastructure and discovery
|
|
18
|
+
> transformations that are not service mocks.
|
|
19
|
+
|
|
20
|
+
## Migrate existing dev-server configuration
|
|
21
|
+
|
|
22
|
+
Migrate one service at a time. Move behavior owned by a backend service into
|
|
23
|
+
`mocks/<service>.mock.ts`; keep host-level behavior in `dev-server.config.ts`.
|
|
24
|
+
|
|
25
|
+
| Existing configuration | New location | Use when |
|
|
26
|
+
| --- | --- | --- |
|
|
27
|
+
| `api.processServices` adds or redirects one service | `serviceDiscovery` in `<service>.mock.ts` | The change exists only to make that service available locally. |
|
|
28
|
+
| `api.routes` returns a mocked OpenAPI operation | `routes` in `<service>.mock.ts` | The path and method belong to the service's OpenAPI contract. |
|
|
29
|
+
| `api.routes` implements request-aware service behavior | `middleware` in `<service>.mock.ts` | The behavior needs request headers, parameters, or a parsed body. |
|
|
30
|
+
| Schema faker overrides | `components` in `<service>.mock.ts` | Generated OpenAPI responses need deterministic field values. |
|
|
31
|
+
| Filtering or rewriting real discovery for the whole host | Keep `api.processServices` | The transformation is infrastructure behavior rather than a service mock. |
|
|
32
|
+
| Dev-server health checks or other host-owned endpoints | Keep `api.routes` | The route belongs to the local host, not to one backend service. |
|
|
33
|
+
|
|
34
|
+
For example, a legacy configuration might replace the generated proxy route for an existing
|
|
35
|
+
`inventory` service with a local response:
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
// dev-server.config.ts
|
|
39
|
+
import { defineDevServerConfig, processServices } from '@equinor/fusion-framework-cli/dev-server';
|
|
40
|
+
|
|
41
|
+
export default defineDevServerConfig(() => ({
|
|
42
|
+
api: {
|
|
43
|
+
processServices: (services, args) => {
|
|
44
|
+
const processed = processServices(services, args);
|
|
45
|
+
return {
|
|
46
|
+
...processed,
|
|
47
|
+
routes: [
|
|
48
|
+
...processed.routes.filter((route) => route.match !== '/inventory/api*sub'),
|
|
49
|
+
{
|
|
50
|
+
match: '/inventory/api*sub',
|
|
51
|
+
middleware: (_request, response) => {
|
|
52
|
+
response.setHeader('content-type', 'application/json');
|
|
53
|
+
response.end(JSON.stringify([{ id: 'local-item', name: 'Local item' }]));
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
};
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
}));
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Move that service-owned response into an executable mock module. Paths in `routes` are relative to
|
|
64
|
+
the service and must match an operation in its OpenAPI document:
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
// mocks/inventory.mock.ts
|
|
68
|
+
import schema from './inventory.openapi.json' with { type: 'json' };
|
|
69
|
+
import { defineService } from '@equinor/fusion-openapi-mock-server/discovery';
|
|
70
|
+
|
|
71
|
+
export default defineService({
|
|
72
|
+
key: 'inventory',
|
|
73
|
+
serviceDiscovery: 'replace',
|
|
74
|
+
schema,
|
|
75
|
+
routes: {
|
|
76
|
+
'/items': {
|
|
77
|
+
get: {
|
|
78
|
+
mock: [{ id: 'local-item', name: 'Local item' }],
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
`'replace'` is appropriate because the module carries a complete schema and deliberately replaces
|
|
86
|
+
the real discovery entry during normal development or a same-key preset in isolated mode. Use
|
|
87
|
+
`'merge'` only when a selected preset or earlier local mock layer already supplies the service and
|
|
88
|
+
schema; the standalone mock server never inherits schemas from remote discovery. Use `'new'` with
|
|
89
|
+
a `schema` for a pre-production service, or `false` with a `schema` for a direct-only endpoint.
|
|
90
|
+
|
|
91
|
+
Complete the migration:
|
|
92
|
+
|
|
93
|
+
1. Install `@equinor/fusion-framework-cli-plugin-mock-server` and
|
|
94
|
+
`@equinor/fusion-openapi-mock-server` as development dependencies.
|
|
95
|
+
2. Start `ffc mock-server` in a foreground terminal.
|
|
96
|
+
3. Run `ffc app dev` to combine real discovery with local definitions, or use
|
|
97
|
+
`ffc app dev --mock http://localhost:4010` for an isolated environment.
|
|
98
|
+
4. Verify the migrated service, then remove only its old `api.routes` and `api.processServices`
|
|
99
|
+
branches. Leave unrelated host-level configuration in place.
|
|
100
|
+
|
|
101
|
+
During an incremental migration, migrated and legacy services can coexist. Do not define the same
|
|
102
|
+
service behavior in both places; route precedence can hide which implementation handled a request.
|
|
103
|
+
|
|
104
|
+
## Install and start the mock server
|
|
105
|
+
|
|
106
|
+
```sh
|
|
107
|
+
pnpm add -D @equinor/fusion-framework-cli-plugin-mock-server
|
|
108
|
+
ffc mock-server
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
> [!IMPORTANT]
|
|
112
|
+
> Keep this foreground process running in one terminal and start the application in another. The
|
|
113
|
+
> plugin intentionally does not create an unowned background process.
|
|
114
|
+
|
|
115
|
+
Check that startup completed before connecting the app:
|
|
116
|
+
|
|
117
|
+
```sh
|
|
118
|
+
curl http://localhost:4010/@fusion-mock/health
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
> [!TIP]
|
|
122
|
+
> Use the health endpoint as the `url` readiness check in Playwright's `webServer` configuration.
|
|
123
|
+
|
|
124
|
+
## Define one executable module per service
|
|
125
|
+
|
|
126
|
+
```text
|
|
127
|
+
mocks/
|
|
128
|
+
inventory.mock.ts
|
|
129
|
+
inventory.openapi.json
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
import schema from './inventory.openapi.json' with { type: 'json' };
|
|
134
|
+
import { defineService } from '@equinor/fusion-openapi-mock-server/discovery';
|
|
135
|
+
|
|
136
|
+
export default defineService({
|
|
137
|
+
key: 'inventory',
|
|
138
|
+
serviceDiscovery: 'new',
|
|
139
|
+
schema,
|
|
140
|
+
components: {
|
|
141
|
+
InventoryItem: { name: () => 'Local item' },
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
The module keeps service ownership, schema, deterministic fields, routes, and middleware in one
|
|
147
|
+
retrieval-friendly place.
|
|
148
|
+
|
|
149
|
+
This example uses `'new'` because `inventory` is not registered yet; startup fails if the key later
|
|
150
|
+
appears in real discovery. Use `'merge'` only when a selected preset or earlier local mock layer
|
|
151
|
+
already provides the service schema and only selected local behavior should change.
|
|
152
|
+
|
|
153
|
+
## Choose the development mode
|
|
154
|
+
|
|
155
|
+
### Combine real discovery with selected local services
|
|
156
|
+
|
|
157
|
+
```sh
|
|
158
|
+
ffc mock-server
|
|
159
|
+
ffc app dev
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Normal development fetches real discovery and overlays discovery-visible local definitions by
|
|
163
|
+
service key. Use this when most real backends remain useful.
|
|
164
|
+
|
|
165
|
+
### Use only predefined and local mocks
|
|
166
|
+
|
|
167
|
+
```sh
|
|
168
|
+
ffc mock-server
|
|
169
|
+
ffc app dev --mock http://localhost:4010
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
`--mock` points application discovery at the standalone server. The mock server never fetches real
|
|
173
|
+
service discovery; it resolves only bundled presets and local modules. Use this for isolated
|
|
174
|
+
development, CI, and browser tests.
|
|
175
|
+
|
|
176
|
+
> [!WARNING]
|
|
177
|
+
> `--mock` is intentionally isolated. A service missing from the bundled presets and local modules
|
|
178
|
+
> will not fall back to remote service discovery.
|
|
179
|
+
|
|
180
|
+
A preset is a built-in group of service definitions. The default `fusion` preset supplies common
|
|
181
|
+
services that framework modules resolve during startup. Local modules are layered after presets,
|
|
182
|
+
so app-specific behavior can override the baseline.
|
|
183
|
+
|
|
184
|
+
## Choose a discovery mode
|
|
185
|
+
|
|
186
|
+
| Mode | Developer scenario |
|
|
187
|
+
| --- | --- |
|
|
188
|
+
| `'merge'` | Override selected behavior of a service supplied by a selected preset or earlier local mock layer while inheriting its schema. Remote discovery alone is not a merge source. |
|
|
189
|
+
| `'new'` | Add a pre-production service expected to enter real discovery before release. Definition resolution fails if the key already exists in discovery or an earlier mock layer. |
|
|
190
|
+
| `'replace'` | Supply a complete local definition and deliberately replace an earlier same-key definition. |
|
|
191
|
+
| `false` | Serve an app-owned endpoint without advertising it through discovery. Configure its `<key>.localhost` mock URL directly in environment-specific app config. |
|
|
192
|
+
|
|
193
|
+
> [!CAUTION]
|
|
194
|
+
> Use `'new'` only as a temporary pre-production contract. Once the real service key appears,
|
|
195
|
+
> definition resolution fails on purpose; register the backend before release and remove or change
|
|
196
|
+
> the local definition.
|
|
197
|
+
|
|
198
|
+
For example, a service with key `my-api` and `serviceDiscovery: false` is still served at
|
|
199
|
+
`http://my-api.localhost:4010`; it is simply absent from `/@fusion-mock/discovery`. This mirrors an
|
|
200
|
+
application-owned API URL that production configuration supplies directly.
|
|
201
|
+
|
|
202
|
+
Register that URL through the app's environment-specific `endpoints` configuration. The
|
|
203
|
+
[HTTP-client guide](../../app/docs/http-clients.md#mock-an-application-config-endpoint-locally)
|
|
204
|
+
contains the complete `app.config.local.ts` recipe and restart caveat.
|
|
205
|
+
|
|
206
|
+
## Configure shared defaults
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
import type {} from '@equinor/fusion-framework-cli-plugin-mock-server';
|
|
210
|
+
import { defineDevServerConfig } from '@equinor/fusion-framework-cli';
|
|
211
|
+
|
|
212
|
+
export default defineDevServerConfig(() => ({
|
|
213
|
+
mockServer: {
|
|
214
|
+
path: 'mocks',
|
|
215
|
+
host: 'localhost',
|
|
216
|
+
port: 4010,
|
|
217
|
+
seed: 42,
|
|
218
|
+
},
|
|
219
|
+
}));
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Command-line flags override these defaults. `path` is relative to the project root.
|
|
223
|
+
|
|
224
|
+
## Test in a real browser
|
|
225
|
+
|
|
226
|
+
Use Playwright's `webServer` array to own both foreground processes and stop them after the suite.
|
|
227
|
+
The mock server exposes HTTP endpoints for per-test operation overrides and reset.
|
|
228
|
+
|
|
229
|
+
See the [plugin reference](../../cli-plugins/mock-server/README.md), [OpenAPI mock-server guide](../../utils/openapi-mock-server/docs/getting-started.md), and [Playwright cookbook](../../../cookbooks/app-react-mock-playwright/README.md).
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Troubleshooting
|
|
2
|
+
|
|
3
|
+
Start with the visible symptom, then check the responsible layer. Browser environment, server
|
|
4
|
+
discovery, and backend proxying are separate steps.
|
|
5
|
+
|
|
6
|
+
## The package cannot be resolved
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
pnpm add -D @equinor/fusion-framework-dev-server
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Vite must also satisfy the Vite 7 or 8 peer dependency.
|
|
13
|
+
|
|
14
|
+
## Service discovery fails
|
|
15
|
+
|
|
16
|
+
Check `api.serviceDiscoveryUrl` first. The Node process must reach this URL. Authentication, VPN,
|
|
17
|
+
DNS, and environment availability can differ from browser access.
|
|
18
|
+
|
|
19
|
+
If the browser receives the wrong URL, inspect `spa.templateEnv.serviceDiscovery` instead. It
|
|
20
|
+
controls browser configuration and does not replace the API option.
|
|
21
|
+
|
|
22
|
+
## A discovered API request fails
|
|
23
|
+
|
|
24
|
+
1. Confirm the service key exists in discovery.
|
|
25
|
+
2. Enable server debug logging with `log.level: 4`.
|
|
26
|
+
3. Inspect the rewritten local service URI returned to the browser.
|
|
27
|
+
4. Check that the upstream URI is absolute and reachable from Node.
|
|
28
|
+
5. Review custom `api.processServices` logic and route precedence.
|
|
29
|
+
|
|
30
|
+
## A mock service is missing
|
|
31
|
+
|
|
32
|
+
The mock server is separate. Confirm `ffc mock-server` is running and
|
|
33
|
+
`/@fusion-mock/health` responds.
|
|
34
|
+
|
|
35
|
+
Confirm the source file matches `mocks/<service>.mock.ts` and default-exports a `defineService(...)`
|
|
36
|
+
result. The service key in that module must match the key requested by the application.
|
|
37
|
+
|
|
38
|
+
Normal `ffc app dev` overlays only discovery-visible definitions. Definitions using
|
|
39
|
+
`serviceDiscovery: false` must be configured directly in app config, for example with a
|
|
40
|
+
`http://<key>.localhost:4010` URL. In `--mock` mode, confirm the application uses the same origin
|
|
41
|
+
and port as the manually started server.
|
|
42
|
+
|
|
43
|
+
If a definition using `serviceDiscovery: 'new'` reports a collision, real discovery or an earlier
|
|
44
|
+
mock layer already owns that key. Do not change it to `'replace'` just to hide the error: remove the
|
|
45
|
+
temporary pre-production mock after registration, or use `'merge'` when the intent is to override
|
|
46
|
+
selected behavior of the existing service.
|
|
47
|
+
|
|
48
|
+
## Authentication fails
|
|
49
|
+
|
|
50
|
+
Check `clientId`, `tenantId`, and `redirectUri` in `spa.templateEnv.msal`. The redirect URI must
|
|
51
|
+
match the identity provider registration and local URL.
|
|
52
|
+
|
|
53
|
+
## Turn on diagnostics
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
const options = {
|
|
57
|
+
api: { serviceDiscoveryUrl: 'https://service-discovery.example.com' },
|
|
58
|
+
log: { level: 4 },
|
|
59
|
+
spa: { templateEnv: { telemetry: { consoleLevel: 0 } } },
|
|
60
|
+
};
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The scales differ: `log.level` uses `0` None through `4` Debug, while browser
|
|
64
|
+
`telemetry.consoleLevel` uses `0` Debug through `4` Critical.
|
|
65
|
+
|
|
66
|
+
> [!TIP]
|
|
67
|
+
> Start with `log.level: 4` for discovery and proxy failures. Add browser telemetry only when the
|
|
68
|
+
> failure occurs after the page has loaded.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@equinor/fusion-framework-dev-server",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.1.0-next.2",
|
|
4
4
|
"description": "Package for running a development server for fusion-framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -30,14 +30,14 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@vitejs/plugin-react": "^6.0.1",
|
|
33
|
-
"@equinor/fusion-framework-vite-plugin-api-service": "2.0.
|
|
34
|
-
"@equinor/fusion-framework-vite-plugin-spa": "4.0.
|
|
35
|
-
"@equinor/fusion-log": "2.0.
|
|
33
|
+
"@equinor/fusion-framework-vite-plugin-api-service": "2.0.6-next.1",
|
|
34
|
+
"@equinor/fusion-framework-vite-plugin-spa": "4.1.0-next.2",
|
|
35
|
+
"@equinor/fusion-log": "2.0.3-next.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"typescript": "^7.0.2",
|
|
39
39
|
"vite": "^8.0.0",
|
|
40
|
-
"vitest": "^4.1.
|
|
40
|
+
"vitest": "^4.1.10"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
43
|
"vite": "^7.0.0 || ^8.0.0"
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { IncomingMessage } from 'node:http';
|
|
2
|
+
import { Socket } from 'node:net';
|
|
3
|
+
|
|
4
|
+
import { describe, expect, it } from 'vitest';
|
|
5
|
+
|
|
6
|
+
import { processServices } from './process-services.js';
|
|
7
|
+
|
|
8
|
+
const request = new IncomingMessage(new Socket());
|
|
9
|
+
request.headers.referer = 'http://localhost:3000';
|
|
10
|
+
|
|
11
|
+
describe('processServices', () => {
|
|
12
|
+
it('proxies localhost subdomains through the portable shared-host route', () => {
|
|
13
|
+
const result = processServices(
|
|
14
|
+
[{ key: 'people', name: 'People', uri: 'http://people.localhost:4010' }],
|
|
15
|
+
{ route: '/@fusion-api', request },
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
expect(result.routes).toEqual([
|
|
19
|
+
{
|
|
20
|
+
match: '/people/*sub',
|
|
21
|
+
proxy: { target: 'http://localhost:4010' },
|
|
22
|
+
},
|
|
23
|
+
]);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('preserves normal upstream proxy targets and path rewriting', () => {
|
|
27
|
+
const result = processServices(
|
|
28
|
+
[{ key: 'people', name: 'People', uri: 'https://people.example/api' }],
|
|
29
|
+
{ route: '/@fusion-api', request },
|
|
30
|
+
);
|
|
31
|
+
const [route] = result.routes ?? [];
|
|
32
|
+
|
|
33
|
+
expect(route?.proxy?.target).toBe('https://people.example');
|
|
34
|
+
expect(route?.proxy?.rewrite?.('/people/api/persons')).toBe('/api/persons');
|
|
35
|
+
});
|
|
36
|
+
});
|