mfeflow 0.0.0-stage → 0.1.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.
Files changed (120) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +126 -2
  3. package/assets/features/analytics-gtm/feature.json +117 -0
  4. package/assets/features/analytics-gtm/files/Analytics.tsx +13 -0
  5. package/assets/features/analytics-gtm/files/ConsentBanner.tsx +47 -0
  6. package/assets/features/analytics-gtm/files/consent-banner-test.tsx +36 -0
  7. package/assets/features/analytics-gtm/files/init.ts +26 -0
  8. package/assets/features/analytics-gtm/files/remote-analytics-test.ts.eta +18 -0
  9. package/assets/features/analytics-gtm/files/remote-analytics.ts.eta +13 -0
  10. package/assets/features/analytics-gtm/files/shell-init-test.ts +42 -0
  11. package/assets/features/ci-github/feature.json +26 -0
  12. package/assets/features/ci-github/files/ci.md +41 -0
  13. package/assets/features/ci-github/files/ci.yml +63 -0
  14. package/assets/features/env-t3/feature.json +378 -0
  15. package/assets/features/env-t3/files/check-runtime-config.ts +105 -0
  16. package/assets/features/env-t3/files/env.arktype.ts +12 -0
  17. package/assets/features/env-t3/files/env.example +1 -0
  18. package/assets/features/env-t3/files/env.valibot.ts +12 -0
  19. package/assets/features/env-t3/files/env.zod.ts +12 -0
  20. package/assets/features/env-t3/files/runtime.ts.eta +30 -0
  21. package/assets/features/env-t3/files/schema.arktype.ts +8 -0
  22. package/assets/features/env-t3/files/schema.valibot.ts +8 -0
  23. package/assets/features/env-t3/files/schema.zod.ts +8 -0
  24. package/assets/features/git-hooks/feature.json +42 -0
  25. package/assets/features/git-hooks/files/commitlint.config.js +1 -0
  26. package/assets/features/git-hooks/files/lefthook.yml +9 -0
  27. package/assets/features/lint-oxlint/feature.json +60 -0
  28. package/assets/features/lint-oxlint/files/oxlintrc.json +5 -0
  29. package/assets/features/lint-oxlint/files/prettierignore +5 -0
  30. package/assets/features/lint-oxlint/files/prettierrc.json +5 -0
  31. package/assets/features/telemetry-otel/feature.json +159 -0
  32. package/assets/features/telemetry-otel/files/init.ts.eta +53 -0
  33. package/assets/features/telemetry-otel/files/remote-telemetry-test.ts.eta +13 -0
  34. package/assets/features/telemetry-otel/files/remote-telemetry.ts.eta +11 -0
  35. package/assets/features/telemetry-otel/files/shell-init-test.ts.eta +36 -0
  36. package/assets/features/testing/feature.json +202 -0
  37. package/assets/features/testing/files/handlers.ts +5 -0
  38. package/assets/features/testing/files/playwright.config.ts +30 -0
  39. package/assets/features/testing/files/root-vitest.config.ts +6 -0
  40. package/assets/features/testing/files/sample-handlers-test.ts +16 -0
  41. package/assets/features/testing/files/sample-remote-test.tsx +12 -0
  42. package/assets/features/testing/files/sample-shell-test.tsx +11 -0
  43. package/assets/features/testing/files/sample-smoke-e2e.ts +53 -0
  44. package/assets/features/testing/files/server.ts +4 -0
  45. package/assets/features/testing/files/setup.ts +18 -0
  46. package/assets/features/testing/files/vitest.config.ts +11 -0
  47. package/assets/features/tsconfig/feature.json +78 -0
  48. package/assets/features/tsconfig/files/root.tsconfig.json +4 -0
  49. package/assets/features/tsconfig/files/tsconfig.json +5 -0
  50. package/assets/features/url-factory/feature.json +30 -0
  51. package/assets/features/url-factory/files/endpoints.ts +12 -0
  52. package/assets/templates/monorepo/feature.json +295 -0
  53. package/assets/templates/monorepo/files/remote/mfe.json.eta +7 -0
  54. package/assets/templates/monorepo/files/remote/package.json.eta +6 -0
  55. package/assets/templates/monorepo/files/remote/rsbuild.config.ts.eta +7 -0
  56. package/assets/templates/monorepo/files/remote/src/bootstrap.tsx.eta +6 -0
  57. package/assets/templates/monorepo/files/remote/src/env.d.ts.eta +1 -0
  58. package/assets/templates/monorepo/files/remote/src/exposed/App.tsx.eta +18 -0
  59. package/assets/templates/monorepo/files/remote/src/index.ts.eta +2 -0
  60. package/assets/templates/monorepo/files/root/AGENTS.md.eta +17 -0
  61. package/assets/templates/monorepo/files/root/README.md.eta +52 -0
  62. package/assets/templates/monorepo/files/root/gitignore.eta +11 -0
  63. package/assets/templates/monorepo/files/root/nvmrc.eta +1 -0
  64. package/assets/templates/monorepo/files/root/package.json.eta +9 -0
  65. package/assets/templates/monorepo/files/root/pnpm-workspace.yaml.eta +7 -0
  66. package/assets/templates/monorepo/files/root/turbo.json.eta +16 -0
  67. package/assets/templates/monorepo/files/shell/mfe.json.eta +7 -0
  68. package/assets/templates/monorepo/files/shell/package.json.eta +6 -0
  69. package/assets/templates/monorepo/files/shell/rsbuild.config.ts.eta +74 -0
  70. package/assets/templates/monorepo/files/shell/src/App.tsx.eta +46 -0
  71. package/assets/templates/monorepo/files/shell/src/bootstrap.tsx.eta +24 -0
  72. package/assets/templates/monorepo/files/shell/src/env.d.ts.eta +1 -0
  73. package/assets/templates/monorepo/files/shell/src/index.ts.eta +4 -0
  74. package/assets/templates/monorepo/files/shell/src/pages/Home.tsx.eta +12 -0
  75. package/assets/templates/monorepo/files/shell/src/remotes/route-table.tsx.eta +12 -0
  76. package/assets/templates/monorepo/glue/registry-entry.json.eta +5 -0
  77. package/assets/templates/monorepo/glue/route.tsx.eta +5 -0
  78. package/bin/mfeflow.js +4 -0
  79. package/dist/args.d.ts +68 -0
  80. package/dist/assets.d.ts +13 -0
  81. package/dist/catalog.d.ts +24 -0
  82. package/dist/checks/compat.d.ts +2 -0
  83. package/dist/checks/config.d.ts +8 -0
  84. package/dist/checks/env.d.ts +6 -0
  85. package/dist/checks/index.d.ts +6 -0
  86. package/dist/checks/plugins.d.ts +4 -0
  87. package/dist/checks/singletons.d.ts +9 -0
  88. package/dist/checks/workspace.d.ts +2 -0
  89. package/dist/cli.d.ts +11 -0
  90. package/dist/commands/add.d.ts +84 -0
  91. package/dist/commands/budget.d.ts +56 -0
  92. package/dist/commands/dev.d.ts +62 -0
  93. package/dist/commands/doctor.d.ts +141 -0
  94. package/dist/commands/env-key.d.ts +4 -0
  95. package/dist/commands/features.d.ts +53 -0
  96. package/dist/commands/init.d.ts +102 -0
  97. package/dist/devregistry.d.ts +19 -0
  98. package/dist/exit.d.ts +31 -0
  99. package/dist/findings.d.ts +35 -0
  100. package/dist/glue.d.ts +41 -0
  101. package/dist/help.d.ts +12 -0
  102. package/dist/index.d.ts +8 -0
  103. package/dist/index.js +4154 -0
  104. package/dist/inspect.d.ts +57 -0
  105. package/dist/io.d.ts +10 -0
  106. package/dist/local.d.ts +28 -0
  107. package/dist/options.d.ts +7 -0
  108. package/dist/output.d.ts +157 -0
  109. package/dist/pipeline.d.ts +7 -0
  110. package/dist/preset.d.ts +19 -0
  111. package/dist/proc.d.ts +79 -0
  112. package/dist/prompter.d.ts +28 -0
  113. package/dist/report.d.ts +26 -0
  114. package/dist/runtime.d.ts +17 -0
  115. package/dist/singletons.d.ts +11 -0
  116. package/dist/sizes.d.ts +17 -0
  117. package/dist/system.d.ts +20 -0
  118. package/dist/version.d.ts +8 -0
  119. package/dist/write.d.ts +26 -0
  120. package/package.json +66 -3
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "lint-oxlint",
3
+ "version": "0.1.0",
4
+ "description": "oxlint with type-aware rules on TypeScript 7 (@mfeflow/oxlint-config), and Prettier.",
5
+ "requires": ["core", "tsconfig"],
6
+ "files": [
7
+ {
8
+ "to": ".oxlintrc.json",
9
+ "from": "files/oxlintrc.json"
10
+ },
11
+ {
12
+ "to": ".prettierrc.json",
13
+ "from": "files/prettierrc.json"
14
+ },
15
+ {
16
+ "to": ".prettierignore",
17
+ "from": "files/prettierignore"
18
+ }
19
+ ],
20
+ "dependencies": [
21
+ {
22
+ "name": "@mfeflow/oxlint-config",
23
+ "range": "latest",
24
+ "target": "root",
25
+ "dev": true
26
+ },
27
+ {
28
+ "name": "oxlint",
29
+ "range": "1.90.0",
30
+ "target": "root",
31
+ "dev": true
32
+ },
33
+ {
34
+ "name": "oxlint-tsgolint",
35
+ "range": "7.0.2005",
36
+ "target": "root",
37
+ "dev": true
38
+ },
39
+ {
40
+ "name": "prettier",
41
+ "range": "3.9.10",
42
+ "target": "root",
43
+ "dev": true
44
+ }
45
+ ],
46
+ "scripts": [
47
+ {
48
+ "name": "lint",
49
+ "command": "oxlint --type-aware --deny-warnings"
50
+ },
51
+ {
52
+ "name": "format",
53
+ "command": "prettier --write ."
54
+ },
55
+ {
56
+ "name": "format:check",
57
+ "command": "prettier --check ."
58
+ }
59
+ ]
60
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "$schema": "./node_modules/oxlint/configuration_schema.json",
3
+ "extends": ["./node_modules/@mfeflow/oxlint-config/oxlintrc.json"],
4
+ "ignorePatterns": ["**/dist/**", "**/.turbo/**"]
5
+ }
@@ -0,0 +1,5 @@
1
+ dist
2
+ .turbo
3
+ coverage
4
+ pnpm-lock.yaml
5
+ CHANGELOG.md
@@ -0,0 +1,5 @@
1
+ {
2
+ "singleQuote": true,
3
+ "printWidth": 100,
4
+ "overrides": [{ "files": "*.yaml", "options": { "singleQuote": false } }]
5
+ }
@@ -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,6 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ // `pnpm test` runs every app's own vitest config in one go.
4
+ export default defineConfig({
5
+ test: { projects: ['apps/*'] },
6
+ });
@@ -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
+ });