mfeflow 0.0.0-stage → 0.2.0
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/LICENSE +21 -0
- package/README.md +126 -2
- package/assets/features/analytics-gtm/feature.json +117 -0
- package/assets/features/analytics-gtm/files/Analytics.tsx +13 -0
- package/assets/features/analytics-gtm/files/ConsentBanner.tsx +47 -0
- package/assets/features/analytics-gtm/files/consent-banner-test.tsx +36 -0
- package/assets/features/analytics-gtm/files/init.ts +26 -0
- package/assets/features/analytics-gtm/files/remote-analytics-test.ts.eta +18 -0
- package/assets/features/analytics-gtm/files/remote-analytics.ts.eta +13 -0
- package/assets/features/analytics-gtm/files/shell-init-test.ts +42 -0
- package/assets/features/ci-github/feature.json +26 -0
- package/assets/features/ci-github/files/ci.md +41 -0
- package/assets/features/ci-github/files/ci.yml +63 -0
- package/assets/features/env-t3/feature.json +399 -0
- package/assets/features/env-t3/files/check-runtime-config.ts +105 -0
- package/assets/features/env-t3/files/env.arktype.ts +12 -0
- package/assets/features/env-t3/files/env.example +1 -0
- package/assets/features/env-t3/files/env.valibot.ts +12 -0
- package/assets/features/env-t3/files/env.zod.ts +12 -0
- package/assets/features/env-t3/files/runtime.ts.eta +30 -0
- package/assets/features/env-t3/files/schema.arktype.ts +8 -0
- package/assets/features/env-t3/files/schema.valibot.ts +8 -0
- package/assets/features/env-t3/files/schema.zod.ts +8 -0
- package/assets/features/event-bus/feature.json +100 -0
- package/assets/features/event-bus/files/announce.tsx.eta +10 -0
- package/assets/features/event-bus/files/contracts-events.ts +11 -0
- package/assets/features/event-bus/files/contracts-package.json.eta +10 -0
- package/assets/features/event-bus/files/contracts-readme.md +10 -0
- package/assets/features/event-bus/files/events-test.ts.eta +18 -0
- package/assets/features/event-bus/files/events.ts.eta +20 -0
- package/assets/features/event-bus/files/shell-events-test.ts.eta +22 -0
- package/assets/features/event-bus/files/shell-events.ts.eta +19 -0
- package/assets/features/git-hooks/feature.json +42 -0
- package/assets/features/git-hooks/files/commitlint.config.js +1 -0
- package/assets/features/git-hooks/files/lefthook.yml +9 -0
- package/assets/features/http/feature.json +81 -0
- package/assets/features/http/files/client-test.ts +19 -0
- package/assets/features/http/files/client.ts +20 -0
- package/assets/features/lint-oxlint/feature.json +60 -0
- package/assets/features/lint-oxlint/files/oxlintrc.json +5 -0
- package/assets/features/lint-oxlint/files/prettierignore +5 -0
- package/assets/features/lint-oxlint/files/prettierrc.json +5 -0
- package/assets/features/route-factory/feature.json +60 -0
- package/assets/features/route-factory/files/contract.ts.eta +7 -0
- package/assets/features/route-factory/files/routes-contract.ts.eta +11 -0
- package/assets/features/route-factory/files/routes-test.ts.eta +9 -0
- package/assets/features/route-factory/files/routes.ts.eta +11 -0
- package/assets/features/telemetry-otel/feature.json +159 -0
- package/assets/features/telemetry-otel/files/init.ts.eta +53 -0
- package/assets/features/telemetry-otel/files/remote-telemetry-test.ts.eta +13 -0
- package/assets/features/telemetry-otel/files/remote-telemetry.ts.eta +11 -0
- package/assets/features/telemetry-otel/files/shell-init-test.ts.eta +36 -0
- package/assets/features/testing/feature.json +202 -0
- package/assets/features/testing/files/handlers.ts +5 -0
- package/assets/features/testing/files/playwright.config.ts +30 -0
- package/assets/features/testing/files/root-vitest.config.ts +6 -0
- package/assets/features/testing/files/sample-handlers-test.ts +16 -0
- package/assets/features/testing/files/sample-remote-test.tsx +12 -0
- package/assets/features/testing/files/sample-shell-test.tsx +11 -0
- package/assets/features/testing/files/sample-smoke-e2e.ts +53 -0
- package/assets/features/testing/files/server.ts +4 -0
- package/assets/features/testing/files/setup.ts +18 -0
- package/assets/features/testing/files/vitest.config.ts +11 -0
- package/assets/features/tsconfig/feature.json +78 -0
- package/assets/features/tsconfig/files/root.tsconfig.json +4 -0
- package/assets/features/tsconfig/files/tsconfig.json +5 -0
- package/assets/features/url-factory/feature.json +30 -0
- package/assets/features/url-factory/files/endpoints.ts +23 -0
- package/assets/templates/monorepo/feature.json +321 -0
- package/assets/templates/monorepo/files/remote/mfe.json.eta +7 -0
- package/assets/templates/monorepo/files/remote/package.json.eta +6 -0
- package/assets/templates/monorepo/files/remote/rsbuild.config.ts.eta +7 -0
- package/assets/templates/monorepo/files/remote/src/bootstrap.tsx.eta +6 -0
- package/assets/templates/monorepo/files/remote/src/env.d.ts.eta +1 -0
- package/assets/templates/monorepo/files/remote/src/exposed/App.tsx.eta +18 -0
- package/assets/templates/monorepo/files/remote/src/index.ts.eta +2 -0
- package/assets/templates/monorepo/files/root/AGENTS.md.eta +17 -0
- package/assets/templates/monorepo/files/root/README.md.eta +52 -0
- package/assets/templates/monorepo/files/root/gitignore.eta +11 -0
- package/assets/templates/monorepo/files/root/nvmrc.eta +1 -0
- package/assets/templates/monorepo/files/root/package.json.eta +9 -0
- package/assets/templates/monorepo/files/root/pnpm-workspace.yaml.eta +7 -0
- package/assets/templates/monorepo/files/root/turbo.json.eta +16 -0
- package/assets/templates/monorepo/files/shell/mfe.json.eta +7 -0
- package/assets/templates/monorepo/files/shell/package.json.eta +6 -0
- package/assets/templates/monorepo/files/shell/rsbuild.config.ts.eta +74 -0
- package/assets/templates/monorepo/files/shell/src/App.tsx.eta +46 -0
- package/assets/templates/monorepo/files/shell/src/bootstrap.tsx.eta +24 -0
- package/assets/templates/monorepo/files/shell/src/env.d.ts.eta +1 -0
- package/assets/templates/monorepo/files/shell/src/index.ts.eta +4 -0
- package/assets/templates/monorepo/files/shell/src/pages/Home.tsx.eta +14 -0
- package/assets/templates/monorepo/files/shell/src/remotes/route-table.tsx.eta +12 -0
- package/assets/templates/monorepo/glue/registry-entry.json.eta +5 -0
- package/assets/templates/monorepo/glue/route.tsx.eta +5 -0
- package/bin/mfeflow.js +4 -0
- package/dist/args.d.ts +68 -0
- package/dist/assets.d.ts +13 -0
- package/dist/catalog.d.ts +24 -0
- package/dist/checks/compat.d.ts +2 -0
- package/dist/checks/config.d.ts +8 -0
- package/dist/checks/env.d.ts +6 -0
- package/dist/checks/index.d.ts +6 -0
- package/dist/checks/plugins.d.ts +4 -0
- package/dist/checks/singletons.d.ts +9 -0
- package/dist/checks/workspace.d.ts +2 -0
- package/dist/cli.d.ts +11 -0
- package/dist/commands/add.d.ts +84 -0
- package/dist/commands/budget.d.ts +56 -0
- package/dist/commands/dev.d.ts +62 -0
- package/dist/commands/doctor.d.ts +141 -0
- package/dist/commands/env-key.d.ts +4 -0
- package/dist/commands/features.d.ts +53 -0
- package/dist/commands/init.d.ts +102 -0
- package/dist/devregistry.d.ts +19 -0
- package/dist/exit.d.ts +31 -0
- package/dist/findings.d.ts +35 -0
- package/dist/glue.d.ts +41 -0
- package/dist/help.d.ts +12 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +4171 -0
- package/dist/inspect.d.ts +57 -0
- package/dist/io.d.ts +10 -0
- package/dist/local.d.ts +28 -0
- package/dist/options.d.ts +7 -0
- package/dist/output.d.ts +157 -0
- package/dist/pipeline.d.ts +7 -0
- package/dist/preset.d.ts +19 -0
- package/dist/proc.d.ts +79 -0
- package/dist/prompter.d.ts +28 -0
- package/dist/report.d.ts +26 -0
- package/dist/runtime.d.ts +17 -0
- package/dist/singletons.d.ts +11 -0
- package/dist/sizes.d.ts +17 -0
- package/dist/system.d.ts +20 -0
- package/dist/version.d.ts +8 -0
- package/dist/write.d.ts +26 -0
- package/package.json +66 -3
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "telemetry-otel",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "OpenTelemetry for the browser behind @mfeflow/telemetry: spans per remote tagged with mfe.name, fetch tracing with trace headers, web vitals and remote failures sent to an OTLP endpoint. The SDK loads after first render; endpoint, sampling and CORS origins come from the runtime config.",
|
|
5
|
+
"requires": ["core", "tsconfig"],
|
|
6
|
+
"files": [
|
|
7
|
+
{
|
|
8
|
+
"to": "src/telemetry/init.ts",
|
|
9
|
+
"target": "shell",
|
|
10
|
+
"from": "files/init.ts.eta",
|
|
11
|
+
"template": true
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"to": "src/telemetry/init.test.ts",
|
|
15
|
+
"target": "shell",
|
|
16
|
+
"from": "files/shell-init-test.ts.eta",
|
|
17
|
+
"template": true,
|
|
18
|
+
"when": {
|
|
19
|
+
"feature:testing": true
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"to": "src/telemetry.ts",
|
|
24
|
+
"target": "remotes",
|
|
25
|
+
"from": "files/remote-telemetry.ts.eta",
|
|
26
|
+
"template": true
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"to": "src/telemetry.test.ts",
|
|
30
|
+
"target": "remotes",
|
|
31
|
+
"from": "files/remote-telemetry-test.ts.eta",
|
|
32
|
+
"template": true,
|
|
33
|
+
"when": {
|
|
34
|
+
"feature:testing": true
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
],
|
|
38
|
+
"dependencies": [
|
|
39
|
+
{
|
|
40
|
+
"name": "@mfeflow/telemetry",
|
|
41
|
+
"range": "latest",
|
|
42
|
+
"target": "shell"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"name": "@mfeflow/telemetry",
|
|
46
|
+
"range": "latest",
|
|
47
|
+
"target": "remotes"
|
|
48
|
+
}
|
|
49
|
+
],
|
|
50
|
+
"transforms": [
|
|
51
|
+
{
|
|
52
|
+
"op": "add-boot-step",
|
|
53
|
+
"file": "src/bootstrap.tsx",
|
|
54
|
+
"target": "shell",
|
|
55
|
+
"call": "initTelemetryFromConfig",
|
|
56
|
+
"from": "./telemetry/init",
|
|
57
|
+
"await": false
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"op": "add-env-key",
|
|
61
|
+
"file": "src/config/schema.ts",
|
|
62
|
+
"target": "shell",
|
|
63
|
+
"key": "otlpEndpoint",
|
|
64
|
+
"scope": "runtime",
|
|
65
|
+
"schema": {
|
|
66
|
+
"zod": "z.string().regex(/^(https?:[/][/][^\\s]+)?$/)",
|
|
67
|
+
"valibot": "v.pipe(v.string(), v.regex(/^(https?:[/][/][^\\s]+)?$/))",
|
|
68
|
+
"arktype": "\"'' | string.url\""
|
|
69
|
+
},
|
|
70
|
+
"value": "",
|
|
71
|
+
"configFiles": [
|
|
72
|
+
"public/config/runtime-config.dev.json",
|
|
73
|
+
"public/config/runtime-config.staging.json",
|
|
74
|
+
"public/config/runtime-config.prod.json"
|
|
75
|
+
],
|
|
76
|
+
"when": {
|
|
77
|
+
"feature:env-t3": true
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"op": "add-env-key",
|
|
82
|
+
"file": "src/config/schema.ts",
|
|
83
|
+
"target": "shell",
|
|
84
|
+
"key": "traceSampleRatio",
|
|
85
|
+
"scope": "runtime",
|
|
86
|
+
"schema": {
|
|
87
|
+
"zod": "z.number().min(0).max(1)",
|
|
88
|
+
"valibot": "v.pipe(v.number(), v.minValue(0), v.maxValue(1))",
|
|
89
|
+
"arktype": "'0 <= number <= 1'"
|
|
90
|
+
},
|
|
91
|
+
"value": 1,
|
|
92
|
+
"configFiles": [
|
|
93
|
+
"public/config/runtime-config.dev.json",
|
|
94
|
+
"public/config/runtime-config.staging.json",
|
|
95
|
+
"public/config/runtime-config.prod.json"
|
|
96
|
+
],
|
|
97
|
+
"when": {
|
|
98
|
+
"feature:env-t3": true
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"op": "add-env-key",
|
|
103
|
+
"file": "src/config/schema.ts",
|
|
104
|
+
"target": "shell",
|
|
105
|
+
"key": "propagateTraceHeaderCorsUrls",
|
|
106
|
+
"scope": "runtime",
|
|
107
|
+
"schema": {
|
|
108
|
+
"zod": "z.array(z.string())",
|
|
109
|
+
"valibot": "v.array(v.string())",
|
|
110
|
+
"arktype": "'string[]'"
|
|
111
|
+
},
|
|
112
|
+
"value": [],
|
|
113
|
+
"configFiles": [
|
|
114
|
+
"public/config/runtime-config.dev.json",
|
|
115
|
+
"public/config/runtime-config.staging.json",
|
|
116
|
+
"public/config/runtime-config.prod.json"
|
|
117
|
+
],
|
|
118
|
+
"when": {
|
|
119
|
+
"feature:env-t3": true
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"op": "add-env-key",
|
|
124
|
+
"file": "public/config/runtime-config.json",
|
|
125
|
+
"target": "shell",
|
|
126
|
+
"key": "otlpEndpoint",
|
|
127
|
+
"scope": "runtime",
|
|
128
|
+
"schema": "z.string().regex(/^(https?:[/][/][^\\s]+)?$/)",
|
|
129
|
+
"value": "",
|
|
130
|
+
"when": {
|
|
131
|
+
"feature:env-t3": false
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"op": "add-env-key",
|
|
136
|
+
"file": "public/config/runtime-config.json",
|
|
137
|
+
"target": "shell",
|
|
138
|
+
"key": "traceSampleRatio",
|
|
139
|
+
"scope": "runtime",
|
|
140
|
+
"schema": "z.number().min(0).max(1)",
|
|
141
|
+
"value": 1,
|
|
142
|
+
"when": {
|
|
143
|
+
"feature:env-t3": false
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"op": "add-env-key",
|
|
148
|
+
"file": "public/config/runtime-config.json",
|
|
149
|
+
"target": "shell",
|
|
150
|
+
"key": "propagateTraceHeaderCorsUrls",
|
|
151
|
+
"scope": "runtime",
|
|
152
|
+
"schema": "z.array(z.string())",
|
|
153
|
+
"value": [],
|
|
154
|
+
"when": {
|
|
155
|
+
"feature:env-t3": false
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
]
|
|
159
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/* oxlint-disable eslint/no-underscore-dangle -- __MFEFLOW_APP_VERSION__ is the name the rsbuild preset defines */
|
|
2
|
+
import { initTelemetry } from '@mfeflow/telemetry';
|
|
3
|
+
import { fetchRuntimeConfig } from '@mfeflow/runtime';
|
|
4
|
+
|
|
5
|
+
// The shell's rsbuild preset replaces this with the version in package.json at build time.
|
|
6
|
+
declare const __MFEFLOW_APP_VERSION__: string | undefined;
|
|
7
|
+
const APP_VERSION =
|
|
8
|
+
typeof __MFEFLOW_APP_VERSION__ === 'string' ? __MFEFLOW_APP_VERSION__ : 'unknown';
|
|
9
|
+
|
|
10
|
+
const strings = (value: unknown): string[] =>
|
|
11
|
+
Array.isArray(value) ? value.filter((item): item is string => typeof item === 'string') : [];
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Starts telemetry from the runtime config. With no `otlpEndpoint` it stays off. The OpenTelemetry
|
|
15
|
+
* SDK is a separate chunk that loads after the page has loaded and the browser is idle, so it is
|
|
16
|
+
* never on the path to first render.
|
|
17
|
+
*/
|
|
18
|
+
export async function startTelemetry(): Promise<void> {
|
|
19
|
+
const config = await fetchRuntimeConfig().catch((error: unknown) => {
|
|
20
|
+
console.warn('[telemetry] the runtime config did not load, so telemetry stays off', error);
|
|
21
|
+
return undefined;
|
|
22
|
+
});
|
|
23
|
+
if (config === undefined) return;
|
|
24
|
+
const { otlpEndpoint, traceSampleRatio, environment } = config;
|
|
25
|
+
if (typeof otlpEndpoint !== 'string' || otlpEndpoint === '') return;
|
|
26
|
+
try {
|
|
27
|
+
initTelemetry(
|
|
28
|
+
async () => {
|
|
29
|
+
const { createOtelProvider } = await import('@mfeflow/telemetry/otel');
|
|
30
|
+
return createOtelProvider({
|
|
31
|
+
serviceName: '<%= it.app.name %>',
|
|
32
|
+
version: APP_VERSION,
|
|
33
|
+
environment: typeof environment === 'string' ? environment : 'unknown',
|
|
34
|
+
otlpEndpoint,
|
|
35
|
+
sampleRatio: typeof traceSampleRatio === 'number' ? traceSampleRatio : undefined,
|
|
36
|
+
propagateTraceHeaderCorsUrls: strings(config.propagateTraceHeaderCorsUrls),
|
|
37
|
+
});
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
onError: (error) => {
|
|
41
|
+
console.warn('[telemetry] not started', error);
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
);
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.warn('[telemetry] not started', error);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** The boot step: starts telemetry without holding up first render. */
|
|
51
|
+
export function initTelemetryFromConfig(): void {
|
|
52
|
+
void startTelemetry();
|
|
53
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { resetTelemetryForTests } from '@mfeflow/telemetry';
|
|
2
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
3
|
+
import { tracer } from './telemetry';
|
|
4
|
+
|
|
5
|
+
afterEach(resetTelemetryForTests);
|
|
6
|
+
|
|
7
|
+
describe('the <%= it.app.name %> tracer', () => {
|
|
8
|
+
it('can be used before the shell has started telemetry, and records nothing', () => {
|
|
9
|
+
const span = tracer.startSpan('work');
|
|
10
|
+
expect(span.isRecording()).toBe(false);
|
|
11
|
+
span.end();
|
|
12
|
+
});
|
|
13
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { getTracer } from '@mfeflow/telemetry';
|
|
2
|
+
|
|
3
|
+
// Spans started with this tracer carry mfe.name = <%= it.app.name %>. Take it once, at import time:
|
|
4
|
+
// it records as soon as the shell has started telemetry.
|
|
5
|
+
//
|
|
6
|
+
// tracer.startActiveSpan('checkout.click', (span) => {
|
|
7
|
+
// void fetch('/api/orders').finally(() => span.end());
|
|
8
|
+
// });
|
|
9
|
+
//
|
|
10
|
+
// Start the fetch inside the callback, before any await: the context follows synchronous calls only.
|
|
11
|
+
export const tracer = getTracer('<%= it.app.name %>');
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { isTelemetryReady, resetTelemetryForTests, telemetryReady } from '@mfeflow/telemetry';
|
|
2
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import { startTelemetry } from './init';
|
|
4
|
+
|
|
5
|
+
afterEach(() => {
|
|
6
|
+
Reflect.deleteProperty(globalThis, '__MFE_RUNTIME_CONFIG__');
|
|
7
|
+
resetTelemetryForTests();
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
describe('startTelemetry', () => {
|
|
11
|
+
it('stays off without an otlpEndpoint', async () => {
|
|
12
|
+
Reflect.set(globalThis, '__MFE_RUNTIME_CONFIG__', { environment: 'dev', otlpEndpoint: '' });
|
|
13
|
+
await startTelemetry();
|
|
14
|
+
await telemetryReady();
|
|
15
|
+
expect(isTelemetryReady()).toBe(false);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('warns, and does not throw, when telemetry cannot start', async () => {
|
|
19
|
+
vi.resetModules();
|
|
20
|
+
vi.doMock('@mfeflow/telemetry', async (original) => ({
|
|
21
|
+
...(await original<typeof import('@mfeflow/telemetry')>()),
|
|
22
|
+
initTelemetry: () => {
|
|
23
|
+
throw new Error('boom');
|
|
24
|
+
},
|
|
25
|
+
}));
|
|
26
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
27
|
+
Reflect.set(globalThis, '__MFE_RUNTIME_CONFIG__', {
|
|
28
|
+
otlpEndpoint: 'https://collector.example.com/v1/traces',
|
|
29
|
+
});
|
|
30
|
+
const fresh = await import('./init');
|
|
31
|
+
await expect(fresh.startTelemetry()).resolves.toBeUndefined();
|
|
32
|
+
expect(warn).toHaveBeenCalledWith('[telemetry] not started', expect.any(Error));
|
|
33
|
+
warn.mockRestore();
|
|
34
|
+
vi.doUnmock('@mfeflow/telemetry');
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "testing",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Vitest, Testing Library, jsdom and MSW in every app with @mfeflow/testing, and a Playwright smoke test that opens the shell and checks each remote renders.",
|
|
5
|
+
"requires": ["core", "tsconfig"],
|
|
6
|
+
"files": [
|
|
7
|
+
{
|
|
8
|
+
"to": "vitest.config.ts",
|
|
9
|
+
"target": "shell",
|
|
10
|
+
"from": "files/vitest.config.ts"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"to": "src/test/setup.ts",
|
|
14
|
+
"target": "shell",
|
|
15
|
+
"from": "files/setup.ts"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"to": "src/mocks/handlers.ts",
|
|
19
|
+
"target": "shell",
|
|
20
|
+
"from": "files/handlers.ts"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"to": "src/mocks/server.ts",
|
|
24
|
+
"target": "shell",
|
|
25
|
+
"from": "files/server.ts"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"to": "src/mocks/handlers.test.ts",
|
|
29
|
+
"target": "shell",
|
|
30
|
+
"from": "files/sample-handlers-test.ts"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"to": "src/pages/Home.test.tsx",
|
|
34
|
+
"target": "shell",
|
|
35
|
+
"from": "files/sample-shell-test.tsx"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"to": "vitest.config.ts",
|
|
39
|
+
"target": "remotes",
|
|
40
|
+
"from": "files/vitest.config.ts"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"to": "src/test/setup.ts",
|
|
44
|
+
"target": "remotes",
|
|
45
|
+
"from": "files/setup.ts"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"to": "src/mocks/handlers.ts",
|
|
49
|
+
"target": "remotes",
|
|
50
|
+
"from": "files/handlers.ts"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"to": "src/mocks/server.ts",
|
|
54
|
+
"target": "remotes",
|
|
55
|
+
"from": "files/server.ts"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"to": "src/mocks/handlers.test.ts",
|
|
59
|
+
"target": "remotes",
|
|
60
|
+
"from": "files/sample-handlers-test.ts"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"to": "src/exposed/App.test.tsx",
|
|
64
|
+
"target": "remotes",
|
|
65
|
+
"from": "files/sample-remote-test.tsx"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"to": "vitest.config.ts",
|
|
69
|
+
"target": "root",
|
|
70
|
+
"from": "files/root-vitest.config.ts"
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"to": "playwright.config.ts",
|
|
74
|
+
"target": "root",
|
|
75
|
+
"from": "files/playwright.config.ts"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"to": "e2e/smoke.spec.ts",
|
|
79
|
+
"target": "root",
|
|
80
|
+
"from": "files/sample-smoke-e2e.ts"
|
|
81
|
+
}
|
|
82
|
+
],
|
|
83
|
+
"dependencies": [
|
|
84
|
+
{
|
|
85
|
+
"name": "vitest",
|
|
86
|
+
"range": "5.0.5",
|
|
87
|
+
"target": "shell",
|
|
88
|
+
"dev": true
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"name": "jsdom",
|
|
92
|
+
"range": "30.1.3",
|
|
93
|
+
"target": "shell",
|
|
94
|
+
"dev": true
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"name": "@testing-library/react",
|
|
98
|
+
"range": "16.3.3",
|
|
99
|
+
"target": "shell",
|
|
100
|
+
"dev": true
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"name": "@testing-library/dom",
|
|
104
|
+
"range": "10.4.2",
|
|
105
|
+
"target": "shell",
|
|
106
|
+
"dev": true
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"name": "msw",
|
|
110
|
+
"range": "3.0.3",
|
|
111
|
+
"target": "shell",
|
|
112
|
+
"dev": true
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"name": "@mfeflow/testing",
|
|
116
|
+
"range": "latest",
|
|
117
|
+
"target": "shell",
|
|
118
|
+
"dev": true
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"name": "vitest",
|
|
122
|
+
"range": "5.0.5",
|
|
123
|
+
"target": "remotes",
|
|
124
|
+
"dev": true
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"name": "jsdom",
|
|
128
|
+
"range": "30.1.3",
|
|
129
|
+
"target": "remotes",
|
|
130
|
+
"dev": true
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"name": "@testing-library/react",
|
|
134
|
+
"range": "16.3.3",
|
|
135
|
+
"target": "remotes",
|
|
136
|
+
"dev": true
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
"name": "@testing-library/dom",
|
|
140
|
+
"range": "10.4.2",
|
|
141
|
+
"target": "remotes",
|
|
142
|
+
"dev": true
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"name": "msw",
|
|
146
|
+
"range": "3.0.3",
|
|
147
|
+
"target": "remotes",
|
|
148
|
+
"dev": true
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"name": "@mfeflow/testing",
|
|
152
|
+
"range": "latest",
|
|
153
|
+
"target": "remotes",
|
|
154
|
+
"dev": true
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
"name": "vitest",
|
|
158
|
+
"range": "5.0.5",
|
|
159
|
+
"target": "root",
|
|
160
|
+
"dev": true
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
"name": "@playwright/test",
|
|
164
|
+
"range": "1.64.1",
|
|
165
|
+
"target": "root",
|
|
166
|
+
"dev": true
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
"name": "@types/node",
|
|
170
|
+
"range": "26.6.6",
|
|
171
|
+
"target": "root",
|
|
172
|
+
"dev": true
|
|
173
|
+
}
|
|
174
|
+
],
|
|
175
|
+
"scripts": [
|
|
176
|
+
{
|
|
177
|
+
"name": "test",
|
|
178
|
+
"command": "vitest run",
|
|
179
|
+
"target": "shell"
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
"name": "test",
|
|
183
|
+
"command": "vitest run",
|
|
184
|
+
"target": "remotes"
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
"name": "test",
|
|
188
|
+
"command": "vitest run",
|
|
189
|
+
"target": "root"
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
"name": "test:e2e",
|
|
193
|
+
"command": "playwright test",
|
|
194
|
+
"target": "root"
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
"name": "typecheck:tooling",
|
|
198
|
+
"command": "tsc --noEmit",
|
|
199
|
+
"target": "root"
|
|
200
|
+
}
|
|
201
|
+
]
|
|
202
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { http, HttpResponse } from 'msw';
|
|
2
|
+
|
|
3
|
+
// Requests a test makes are answered here. Add the endpoints your app calls;
|
|
4
|
+
// a request nothing answers fails the test (see src/test/setup.ts).
|
|
5
|
+
export const handlers = [http.get('*/api/health', () => HttpResponse.json({ ok: true }))];
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { defineConfig, devices } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
// Set BASE_URL to test a deployed shell (a preview, staging); otherwise `pnpm dev` is started here.
|
|
4
|
+
const baseURL = process.env.BASE_URL ?? 'http://localhost:4300';
|
|
5
|
+
|
|
6
|
+
export default defineConfig({
|
|
7
|
+
testDir: 'e2e',
|
|
8
|
+
outputDir: 'node_modules/.cache/playwright/results',
|
|
9
|
+
forbidOnly: Boolean(process.env.CI),
|
|
10
|
+
retries: process.env.CI ? 1 : 0,
|
|
11
|
+
reporter: process.env.CI
|
|
12
|
+
? [
|
|
13
|
+
['github'],
|
|
14
|
+
['html', { open: 'never', outputFolder: 'node_modules/.cache/playwright/report' }],
|
|
15
|
+
]
|
|
16
|
+
: 'list',
|
|
17
|
+
use: { baseURL, trace: 'on-first-retry' },
|
|
18
|
+
projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }],
|
|
19
|
+
...(process.env.BASE_URL
|
|
20
|
+
? {}
|
|
21
|
+
: {
|
|
22
|
+
webServer: {
|
|
23
|
+
command: 'pnpm dev',
|
|
24
|
+
url: baseURL,
|
|
25
|
+
reuseExistingServer: !process.env.CI,
|
|
26
|
+
gracefulShutdown: { signal: 'SIGTERM', timeout: 10_000 },
|
|
27
|
+
timeout: 120_000,
|
|
28
|
+
},
|
|
29
|
+
}),
|
|
30
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { http, HttpResponse } from 'msw';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { server } from './server';
|
|
4
|
+
|
|
5
|
+
describe('mock server', () => {
|
|
6
|
+
it('answers the default handlers', async () => {
|
|
7
|
+
const response = await fetch('http://localhost/api/health');
|
|
8
|
+
expect(await response.json()).toEqual({ ok: true });
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('lets a test replace a handler for that test only', async () => {
|
|
12
|
+
server.use(http.get('*/api/health', () => HttpResponse.json({ ok: false })));
|
|
13
|
+
const response = await fetch('http://localhost/api/health');
|
|
14
|
+
expect(await response.json()).toEqual({ ok: false });
|
|
15
|
+
});
|
|
16
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { fireEvent, screen } from '@testing-library/react';
|
|
2
|
+
import { renderWithPlatform } from '@mfeflow/testing';
|
|
3
|
+
import { describe, expect, it } from 'vitest';
|
|
4
|
+
import App from './App';
|
|
5
|
+
|
|
6
|
+
describe('the exposed App', () => {
|
|
7
|
+
it('counts clicks', () => {
|
|
8
|
+
renderWithPlatform(<App />);
|
|
9
|
+
fireEvent.click(screen.getByRole('button', { name: 'Clicked 0 times' }));
|
|
10
|
+
expect(screen.getByRole('button', { name: 'Clicked 1 times' })).toBeDefined();
|
|
11
|
+
});
|
|
12
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { screen } from '@testing-library/react';
|
|
2
|
+
import { renderWithPlatform } from '@mfeflow/testing';
|
|
3
|
+
import { describe, expect, it } from 'vitest';
|
|
4
|
+
import { Home } from './Home';
|
|
5
|
+
|
|
6
|
+
describe('Home', () => {
|
|
7
|
+
it('says so when the registry listed no remotes', () => {
|
|
8
|
+
renderWithPlatform(<Home />);
|
|
9
|
+
expect(screen.getByText(/No remotes yet/)).toBeDefined();
|
|
10
|
+
});
|
|
11
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { expect, test } from '@playwright/test';
|
|
3
|
+
|
|
4
|
+
// The remotes the dev registry lists; each one's App carries data-testid="<name>".
|
|
5
|
+
const remotes = fs
|
|
6
|
+
.readdirSync('registry/envs/dev')
|
|
7
|
+
.filter((file) => file.endsWith('.json'))
|
|
8
|
+
.map((file) => file.slice(0, -'.json'.length))
|
|
9
|
+
.toSorted();
|
|
10
|
+
|
|
11
|
+
// With env-t3 the shell fetches /config/runtime-config.json at boot and keeps it on globalThis.
|
|
12
|
+
const hasRuntimeConfig = fs.existsSync('scripts/check-runtime-config.ts');
|
|
13
|
+
|
|
14
|
+
test('the shell loads cleanly, with its runtime config', async ({ page }) => {
|
|
15
|
+
const problems: string[] = [];
|
|
16
|
+
page.on('pageerror', (error) => problems.push(`page error: ${error.message}`));
|
|
17
|
+
page.on('console', (message) => {
|
|
18
|
+
if (message.type() === 'error') problems.push(`console error: ${message.text()}`);
|
|
19
|
+
});
|
|
20
|
+
page.on('requestfailed', (request) =>
|
|
21
|
+
problems.push(
|
|
22
|
+
`request failed: ${request.url()} (${request.failure()?.errorText ?? 'unknown'})`,
|
|
23
|
+
),
|
|
24
|
+
);
|
|
25
|
+
page.on('response', (response) => {
|
|
26
|
+
if (response.status() >= 400 && !response.url().endsWith('/favicon.ico')) {
|
|
27
|
+
problems.push(`${String(response.status())} ${response.url()}`);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
await page.goto('/');
|
|
31
|
+
await expect(page.locator('header')).toBeVisible();
|
|
32
|
+
if (hasRuntimeConfig) {
|
|
33
|
+
await expect
|
|
34
|
+
.poll(() =>
|
|
35
|
+
page.evaluate(() => {
|
|
36
|
+
const config: unknown = Reflect.get(globalThis, '__MFE_RUNTIME_CONFIG__');
|
|
37
|
+
return typeof config === 'object' && config !== null && 'environment' in config
|
|
38
|
+
? config.environment
|
|
39
|
+
: undefined;
|
|
40
|
+
}),
|
|
41
|
+
)
|
|
42
|
+
.toBeTruthy();
|
|
43
|
+
}
|
|
44
|
+
await page.waitForLoadState('networkidle');
|
|
45
|
+
expect(problems).toEqual([]);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
for (const name of remotes) {
|
|
49
|
+
test(`the shell renders ${name}`, async ({ page }) => {
|
|
50
|
+
await page.goto('/');
|
|
51
|
+
await expect(page.getByTestId(name)).toBeVisible();
|
|
52
|
+
});
|
|
53
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { cleanup } from '@testing-library/react';
|
|
2
|
+
import { resetPlatform } from '@mfeflow/testing';
|
|
3
|
+
import { afterAll, afterEach, beforeAll } from 'vitest';
|
|
4
|
+
import { server } from '../mocks/server';
|
|
5
|
+
|
|
6
|
+
beforeAll(() => {
|
|
7
|
+
server.listen({ onUnhandledFrame: 'error' });
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
cleanup();
|
|
12
|
+
resetPlatform();
|
|
13
|
+
server.resetHandlers();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterAll(() => {
|
|
17
|
+
server.close();
|
|
18
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config';
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
// One copy of React and Testing Library, also when a package like @mfeflow/testing is linked from another folder.
|
|
5
|
+
resolve: { dedupe: ['react', 'react-dom', '@testing-library/react', '@testing-library/dom'] },
|
|
6
|
+
test: {
|
|
7
|
+
environment: 'jsdom',
|
|
8
|
+
include: ['src/**/*.test.{ts,tsx}'],
|
|
9
|
+
setupFiles: ['./src/test/setup.ts'],
|
|
10
|
+
},
|
|
11
|
+
});
|