mfeflow 0.5.0 → 0.7.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 (90) hide show
  1. package/README.md +8 -3
  2. package/assets/features/deploy-cloudflare/feature.json +85 -0
  3. package/assets/features/deploy-cloudflare/files/cloudflare.json.eta +13 -0
  4. package/assets/features/deploy-cloudflare/files/cloudflare.md.eta +104 -0
  5. package/assets/features/deploy-cloudflare/files/deploy-cloudflare.ts +318 -0
  6. package/assets/features/deploy-cloudflare/files/deploy-cloudflare.yml +111 -0
  7. package/assets/features/deploy-cloudflare/files/headers.remote +14 -0
  8. package/assets/features/deploy-cloudflare/files/headers.shell.eta +18 -0
  9. package/assets/features/deploy-cloudflare/files/r2-cors.json +10 -0
  10. package/assets/features/deploy-cloudflare/files/registry-proxy.ts +80 -0
  11. package/assets/features/deploy-cloudflare/files/wrangler.remote.jsonc.eta +21 -0
  12. package/assets/features/deploy-cloudflare/files/wrangler.shell.jsonc.eta +40 -0
  13. package/assets/features/deploy-docker/feature.json +22 -0
  14. package/assets/features/deploy-docker/files/40-mfe-config.sh +76 -0
  15. package/assets/features/deploy-docker/files/Dockerfile +48 -0
  16. package/assets/features/deploy-docker/files/compose-registry.json.eta +19 -0
  17. package/assets/features/deploy-docker/files/compose.yaml.eta +50 -0
  18. package/assets/features/deploy-docker/files/docker.md +52 -0
  19. package/assets/features/deploy-docker/files/dockerignore +17 -0
  20. package/assets/features/deploy-docker/files/k8s.yaml +114 -0
  21. package/assets/features/deploy-docker/files/nginx.remote.conf +73 -0
  22. package/assets/features/deploy-docker/files/nginx.shell.conf +84 -0
  23. package/assets/features/registry-server/feature.json +128 -0
  24. package/assets/features/registry-server/files/deploy-server.yml +69 -0
  25. package/assets/features/registry-server/files/registry-server.md.eta +52 -0
  26. package/assets/features/registry-server/files/registry-sources.ts +12 -0
  27. package/assets/features/registry-static/feature.json +61 -0
  28. package/assets/features/registry-static/files/deploy.yml +61 -0
  29. package/assets/features/registry-static/files/registry.md.eta +49 -0
  30. package/assets/features/release/feature.json +43 -0
  31. package/assets/features/release/files/changeset-readme.md +7 -0
  32. package/assets/features/release/files/config.json +10 -0
  33. package/assets/features/release/files/releasing.md +21 -0
  34. package/assets/templates/monorepo/feature.json +7 -6
  35. package/assets/templates/monorepo/files/remote/{mfe.json.eta → mfeflow.app.json.eta} +1 -0
  36. package/assets/templates/monorepo/files/root/README.md.eta +1 -1
  37. package/assets/templates/monorepo/files/root/pnpm-workspace.yaml.eta +3 -2
  38. package/assets/templates/monorepo/files/shell/{mfe.json.eta → mfeflow.app.json.eta} +1 -0
  39. package/assets/templates/monorepo/files/shell/rsbuild.config.ts.eta +42 -6
  40. package/assets/templates/monorepo/migrations/0.4.0-app-file.mjs +58 -0
  41. package/assets/templates/polyrepo/feature.json +305 -0
  42. package/assets/templates/polyrepo/files/AGENTS.md.eta +15 -0
  43. package/assets/templates/polyrepo/files/changeset/README.md +5 -0
  44. package/assets/templates/polyrepo/files/changeset/config.json +9 -0
  45. package/assets/templates/polyrepo/files/packages/contracts/package.json.eta +23 -0
  46. package/assets/templates/polyrepo/files/packages/contracts/src/index.ts +11 -0
  47. package/assets/templates/polyrepo/files/packages/contracts/tsconfig.json.eta +5 -0
  48. package/assets/templates/polyrepo/files/packages/tokens/package.json.eta +23 -0
  49. package/assets/templates/polyrepo/files/packages/tokens/src/index.ts +13 -0
  50. package/assets/templates/polyrepo/files/packages/tokens/tsconfig.json.eta +5 -0
  51. package/assets/templates/polyrepo/files/platform/README.md.eta +43 -0
  52. package/assets/templates/polyrepo/files/platform/default.json.eta +27 -0
  53. package/assets/templates/polyrepo/files/remote/README.md.eta +23 -0
  54. package/assets/templates/polyrepo/files/renovate.json.eta +4 -0
  55. package/assets/templates/polyrepo/files/shell/README.md.eta +32 -0
  56. package/assets/templates/polyrepo/files/tsconfig.base.json.eta +13 -0
  57. package/assets/templates/polyrepo/files/workflows/platform-ci.yml +29 -0
  58. package/assets/templates/polyrepo/files/workflows/release.yml +40 -0
  59. package/assets/templates/polyrepo/files/workflows/reusable-ci.yml.eta +50 -0
  60. package/assets/templates/polyrepo/files/workflows/team-ci.yml.eta +12 -0
  61. package/dist/against.d.ts +16 -0
  62. package/dist/catalog.d.ts +4 -0
  63. package/dist/checks/compat.d.ts +12 -0
  64. package/dist/checks/index.d.ts +0 -1
  65. package/dist/commands/add.d.ts +0 -2
  66. package/dist/commands/adopt.d.ts +54 -0
  67. package/dist/commands/deploy.d.ts +35 -0
  68. package/dist/commands/doctor.d.ts +10 -0
  69. package/dist/commands/init.d.ts +9 -1
  70. package/dist/commands/server-mode.d.ts +210 -0
  71. package/dist/commands/status.d.ts +5 -0
  72. package/dist/commands/team-repo.d.ts +9 -0
  73. package/dist/commands/upgrade.d.ts +66 -0
  74. package/dist/devremotes.d.ts +15 -0
  75. package/dist/exit.d.ts +1 -0
  76. package/dist/glue.d.ts +6 -0
  77. package/dist/index.d.ts +1 -1
  78. package/dist/index.js +4600 -1875
  79. package/dist/inspect.d.ts +14 -2
  80. package/dist/output.d.ts +138 -3
  81. package/dist/pipeline.d.ts +5 -2
  82. package/dist/polyrepo.d.ts +69 -0
  83. package/dist/publish.d.ts +10 -1
  84. package/dist/runtime.d.ts +2 -0
  85. package/dist/server-client.d.ts +69 -0
  86. package/dist/system.d.ts +2 -0
  87. package/dist/testing.d.ts +17 -1
  88. package/dist/upgrade.d.ts +25 -0
  89. package/dist/write.d.ts +8 -0
  90. package/package.json +5 -5
@@ -2,8 +2,9 @@ packages:
2
2
  - apps/*
3
3
  - packages/*
4
4
 
5
- # Dependencies allowed to run install scripts. esbuild (pulled in by Storybook) works without its
6
- # script; listing it as false keeps pnpm from stopping the install to ask.
5
+ # Dependencies allowed to run install scripts. esbuild (pulled in by Storybook) and workerd (pulled in
6
+ # by wrangler) work without their script; listing them as false keeps pnpm from stopping the install to ask.
7
7
  allowBuilds:
8
8
  lefthook: true
9
9
  esbuild: false
10
+ workerd: false
@@ -1,4 +1,5 @@
1
1
  {
2
+ "$schema": "https://mfeflow.github.io/mfeflow/schema/mfeflow-app.json",
2
3
  "name": "<%= it.app.name %>",
3
4
  "owner": "team-<%= it.app.name %>",
4
5
  "shells": [],
@@ -3,7 +3,40 @@ import path from 'node:path';
3
3
  import { defineConfig, mergeRsbuildConfig } from '@rsbuild/core';
4
4
  import { createShellConfig } from '@mfeflow/rsbuild';
5
5
 
6
- interface DevEntry {
6
+ <% if (it.topology === 'polyrepo') { %>interface DevRemote {
7
+ name: string;
8
+ entry: string;
9
+ version: string;
10
+ }
11
+
12
+ function readDevRemotes(file: string): DevRemote[] {
13
+ let value: unknown;
14
+ try {
15
+ value = JSON.parse(fs.readFileSync(file, 'utf8'));
16
+ } catch (error) {
17
+ // A shell with no dev config yet has no remotes to list.
18
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT') return [];
19
+ throw error;
20
+ }
21
+ const remotes =
22
+ typeof value === 'object' && value !== null && 'devRemotes' in value ? value.devRemotes : {};
23
+ if (typeof remotes !== 'object' || remotes === null) {
24
+ throw new Error(`${file}: devRemotes must be an object of name: manifest URL`);
25
+ }
26
+ return Object.entries(remotes)
27
+ .toSorted(([a], [b]) => (a < b ? -1 : 1))
28
+ .map(([name, entry]) => {
29
+ if (typeof entry !== 'string') throw new Error(`${file}: devRemotes.${name} must be a URL`);
30
+ return { name, entry, version: 'dev' };
31
+ });
32
+ }
33
+
34
+ // The shell finds its remotes through the registry. The remotes live in other repos, so in dev
35
+ // their manifest URLs come from "devRemotes" in public/config/runtime-config.dev.json, inlined so
36
+ // the shell needs no registry server. `mfeflow dev` writes the merged registry (that file, plus
37
+ // --remote overrides) to MFEFLOW_DEV_REGISTRY; plain `pnpm dev` reads the file.
38
+ const devConfig = path.resolve(process.cwd(), 'public/config/runtime-config.dev.json');
39
+ <% } else { %>interface DevEntry {
7
40
  manifestUrl: string;
8
41
  version: string;
9
42
  }
@@ -34,7 +67,7 @@ function readNames(dir: string): string[] {
34
67
  // `mfeflow dev` writes the merged registry (apps from mfeflow.json, --remote overrides) to
35
68
  // MFEFLOW_DEV_REGISTRY and rewrites it when the project changes; plain `pnpm dev` reads the folder.
36
69
  const registryDir = path.resolve(process.cwd(), '../../registry/envs/dev');
37
- const generated = process.env.MFEFLOW_DEV_REGISTRY;
70
+ <% } %>const generated = process.env.MFEFLOW_DEV_REGISTRY;
38
71
 
39
72
  function readGenerated(file: string): unknown {
40
73
  return JSON.parse(fs.readFileSync(file, 'utf8'));
@@ -44,7 +77,9 @@ function devRegistry(): string {
44
77
  const remotes = generated
45
78
  ? readGenerated(generated)
46
79
  : {
47
- remotes: readNames(registryDir)
80
+ <% if (it.topology === 'polyrepo') { %> remotes: readDevRemotes(devConfig),
81
+ };
82
+ <% } else { %> remotes: readNames(registryDir)
48
83
  .filter((file) => file.endsWith('.json'))
49
84
  .toSorted()
50
85
  .map((file) => {
@@ -52,7 +87,7 @@ function devRegistry(): string {
52
87
  return { name: file.slice(0, -'.json'.length), entry: manifestUrl, version };
53
88
  }),
54
89
  };
55
- return `window.__MFE_REGISTRY__=${JSON.stringify(remotes).replaceAll('<', '\\u003c')}`;
90
+ <% } %> return `window.__MFE_REGISTRY__=${JSON.stringify(remotes).replaceAll('<', '\\u003c')}`;
56
91
  }
57
92
 
58
93
  export default defineConfig(({ command }) =>
@@ -61,8 +96,9 @@ export default defineConfig(({ command }) =>
61
96
  createShellConfig({ name: '<%= it.app.name %>', port: <%= it.app.port %>, snapshot: true }),
62
97
  defineConfig({
63
98
  // A new or changed remote restarts the dev server, which reads the registry again.
64
- dev: { watchFiles: { paths: generated ?? registryDir, type: 'restart' } },
65
- html: {
99
+ <% if (it.topology === 'polyrepo') { %> dev: { watchFiles: { paths: generated ?? devConfig, type: 'restart' } },
100
+ <% } else { %> dev: { watchFiles: { paths: generated ?? registryDir, type: 'restart' } },
101
+ <% } %> html: {
66
102
  title: '<%= it.app.name %>',
67
103
  tags:
68
104
  command === 'dev'
@@ -0,0 +1,58 @@
1
+ // core 0.4.0: each app's ownership file is now mfeflow.app.json, not mfe.json.
2
+ // Moves the content of every app's mfe.json to mfeflow.app.json, with the schema URL updated,
3
+ // and deletes mfe.json. An app that already has both files is left alone with a warning (E122).
4
+ // Self-contained on purpose: `mfeflow upgrade` imports it from the feature folder.
5
+
6
+ const OLD = 'mfe.json';
7
+ const NEW = 'mfeflow.app.json';
8
+ const OLD_SCHEMA = 'https://mfeflow.github.io/mfeflow/schema/mfe.json';
9
+ const NEW_SCHEMA = 'https://mfeflow.github.io/mfeflow/schema/mfeflow-app.json';
10
+
11
+ const inApp = (dir, file) => (dir === '' ? file : `${dir}/${file}`);
12
+
13
+ /** The file text with the new schema URL: the old URL replaced, or a `$schema` line added first. */
14
+ export function withNewSchema(text) {
15
+ if (text.includes(OLD_SCHEMA)) return text.replaceAll(OLD_SCHEMA, NEW_SCHEMA);
16
+ if (text.includes('"$schema"')) return text;
17
+ const open = /^\{(\r?\n)/.exec(text);
18
+ if (!open) return text;
19
+ return `{${open[1]} "$schema": "${NEW_SCHEMA}",${open[1]}${text.slice(open[0].length)}`;
20
+ }
21
+
22
+ /**
23
+ * @param {import('@mfeflow/core').PlanBuilder} builder
24
+ * @param {import('@mfeflow/core').MfeflowProject} project
25
+ */
26
+ export default async function migrate(builder, project) {
27
+ const dirs = Object.values(project.apps)
28
+ .map((app) => app.path)
29
+ .toSorted();
30
+ for (const dir of dirs) {
31
+ const from = inApp(dir, OLD);
32
+ const to = inApp(dir, NEW);
33
+ // oxlint-disable-next-line no-await-in-loop -- apps in order keep the plan deterministic
34
+ const text = await builder.content(from);
35
+ if (text === undefined) continue;
36
+ // oxlint-disable-next-line no-await-in-loop -- see above
37
+ if (await builder.existed(to)) {
38
+ builder.warn(
39
+ `${dir || '.'} has both ${OLD} and ${NEW}; keep ${NEW} and delete ${OLD} (E122)`,
40
+ );
41
+ continue;
42
+ }
43
+ const moved = withNewSchema(text);
44
+ // The upgrade may already plan a fresh NEW file from the template. When the old file was
45
+ // never edited the two match and that tracked file stays. Otherwise your content wins, and
46
+ // it is not tracked, so a later upgrade shows a diff instead of replacing it.
47
+ // oxlint-disable-next-line no-await-in-loop -- see above
48
+ const planned = await builder.content(to);
49
+ if (planned !== moved) {
50
+ // oxlint-disable-next-line no-await-in-loop -- see above
51
+ if (planned !== undefined) await builder.delete(to);
52
+ // oxlint-disable-next-line no-await-in-loop -- see above
53
+ await builder.create(to, moved, false);
54
+ }
55
+ // oxlint-disable-next-line no-await-in-loop -- see above
56
+ await builder.delete(from);
57
+ }
58
+ }
@@ -0,0 +1,305 @@
1
+ {
2
+ "name": "polyrepo",
3
+ "version": "0.2.0",
4
+ "description": "What a polyrepo repo has beyond the monorepo template: the platform packages, the reusable CI and Renovate preset, and a package.json, README and CI caller for each repo.",
5
+ "prompts": [
6
+ {
7
+ "name": "repo",
8
+ "type": "select",
9
+ "message": "Which repo of the polyrepo this is",
10
+ "choices": ["platform", "shell", "remote"],
11
+ "default": "platform"
12
+ },
13
+ {
14
+ "name": "name",
15
+ "type": "string",
16
+ "message": "The remote's name (repo remote only)",
17
+ "default": ""
18
+ },
19
+ {
20
+ "name": "org",
21
+ "type": "string",
22
+ "message": "The GitHub owner the repos will live under (default: the scope without @)",
23
+ "default": ""
24
+ }
25
+ ],
26
+ "files": [
27
+ {
28
+ "to": "AGENTS.md",
29
+ "target": "root",
30
+ "from": "files/AGENTS.md.eta",
31
+ "template": true
32
+ },
33
+ {
34
+ "to": "renovate.json",
35
+ "target": "root",
36
+ "from": "files/renovate.json.eta",
37
+ "template": true
38
+ },
39
+ {
40
+ "to": "README.md",
41
+ "target": "root",
42
+ "from": "files/platform/README.md.eta",
43
+ "template": true,
44
+ "when": {
45
+ "repo": "platform"
46
+ }
47
+ },
48
+ {
49
+ "to": "README.md",
50
+ "target": "root",
51
+ "from": "files/shell/README.md.eta",
52
+ "template": true,
53
+ "when": {
54
+ "repo": "shell"
55
+ }
56
+ },
57
+ {
58
+ "to": "README.md",
59
+ "target": "root",
60
+ "from": "files/remote/README.md.eta",
61
+ "template": true,
62
+ "when": {
63
+ "repo": "remote"
64
+ }
65
+ },
66
+ {
67
+ "to": "default.json",
68
+ "target": "root",
69
+ "from": "files/platform/default.json.eta",
70
+ "template": true,
71
+ "when": {
72
+ "repo": "platform"
73
+ }
74
+ },
75
+ {
76
+ "to": "tsconfig.base.json",
77
+ "target": "root",
78
+ "from": "files/tsconfig.base.json.eta",
79
+ "template": true,
80
+ "when": {
81
+ "repo": "platform"
82
+ }
83
+ },
84
+ {
85
+ "to": "packages/contracts/package.json",
86
+ "target": "root",
87
+ "from": "files/packages/contracts/package.json.eta",
88
+ "template": true,
89
+ "when": {
90
+ "repo": "platform"
91
+ }
92
+ },
93
+ {
94
+ "to": "packages/contracts/tsconfig.json",
95
+ "target": "root",
96
+ "from": "files/packages/contracts/tsconfig.json.eta",
97
+ "template": true,
98
+ "when": {
99
+ "repo": "platform"
100
+ }
101
+ },
102
+ {
103
+ "to": "packages/contracts/src/index.ts",
104
+ "target": "root",
105
+ "from": "files/packages/contracts/src/index.ts",
106
+ "when": {
107
+ "repo": "platform"
108
+ }
109
+ },
110
+ {
111
+ "to": "packages/tokens/package.json",
112
+ "target": "root",
113
+ "from": "files/packages/tokens/package.json.eta",
114
+ "template": true,
115
+ "when": {
116
+ "repo": "platform"
117
+ }
118
+ },
119
+ {
120
+ "to": "packages/tokens/tsconfig.json",
121
+ "target": "root",
122
+ "from": "files/packages/tokens/tsconfig.json.eta",
123
+ "template": true,
124
+ "when": {
125
+ "repo": "platform"
126
+ }
127
+ },
128
+ {
129
+ "to": "packages/tokens/src/index.ts",
130
+ "target": "root",
131
+ "from": "files/packages/tokens/src/index.ts",
132
+ "when": {
133
+ "repo": "platform"
134
+ }
135
+ },
136
+ {
137
+ "to": ".changeset/config.json",
138
+ "target": "root",
139
+ "from": "files/changeset/config.json",
140
+ "when": {
141
+ "repo": "platform"
142
+ }
143
+ },
144
+ {
145
+ "to": ".changeset/README.md",
146
+ "target": "root",
147
+ "from": "files/changeset/README.md",
148
+ "when": {
149
+ "repo": "platform"
150
+ }
151
+ },
152
+ {
153
+ "to": ".github/workflows/ci.yml",
154
+ "target": "root",
155
+ "from": "files/workflows/platform-ci.yml",
156
+ "when": {
157
+ "repo": "platform"
158
+ }
159
+ },
160
+ {
161
+ "to": ".github/workflows/reusable-ci.yml",
162
+ "target": "root",
163
+ "from": "files/workflows/reusable-ci.yml.eta",
164
+ "template": true,
165
+ "when": {
166
+ "repo": "platform"
167
+ }
168
+ },
169
+ {
170
+ "to": ".github/workflows/release.yml",
171
+ "target": "root",
172
+ "from": "files/workflows/release.yml",
173
+ "when": {
174
+ "repo": "platform"
175
+ }
176
+ },
177
+ {
178
+ "to": ".github/workflows/ci.yml",
179
+ "target": "root",
180
+ "from": "files/workflows/team-ci.yml.eta",
181
+ "template": true,
182
+ "when": {
183
+ "repo": "shell"
184
+ }
185
+ },
186
+ {
187
+ "to": ".github/workflows/ci.yml",
188
+ "target": "root",
189
+ "from": "files/workflows/team-ci.yml.eta",
190
+ "template": true,
191
+ "when": {
192
+ "repo": "remote"
193
+ }
194
+ },
195
+ {
196
+ "to": "public/config/runtime-config.staging.json",
197
+ "target": "shell",
198
+ "content": "{\n \"environment\": \"staging\"\n}\n"
199
+ },
200
+ {
201
+ "to": "public/config/runtime-config.prod.json",
202
+ "target": "shell",
203
+ "content": "{\n \"environment\": \"prod\"\n}\n"
204
+ }
205
+ ],
206
+ "dependencies": [
207
+ {
208
+ "name": "@changesets/cli",
209
+ "range": "3.0.3",
210
+ "target": "root",
211
+ "dev": true,
212
+ "when": {
213
+ "repo": "platform"
214
+ }
215
+ },
216
+ {
217
+ "name": "typescript",
218
+ "range": "7.0.2",
219
+ "target": "root",
220
+ "dev": true,
221
+ "when": {
222
+ "repo": "platform"
223
+ }
224
+ },
225
+ {
226
+ "name": "mfeflow",
227
+ "range": "latest",
228
+ "target": "root",
229
+ "dev": true,
230
+ "when": {
231
+ "repo": "platform"
232
+ }
233
+ },
234
+ {
235
+ "name": "{{scope}}/contracts",
236
+ "range": "latest",
237
+ "target": "shell"
238
+ },
239
+ {
240
+ "name": "{{scope}}/tokens",
241
+ "range": "latest",
242
+ "target": "shell"
243
+ },
244
+ {
245
+ "name": "{{scope}}/contracts",
246
+ "range": "latest",
247
+ "target": "remotes"
248
+ },
249
+ {
250
+ "name": "{{scope}}/tokens",
251
+ "range": "latest",
252
+ "target": "remotes"
253
+ }
254
+ ],
255
+ "scripts": [
256
+ {
257
+ "name": "build",
258
+ "command": "pnpm -r --if-present run build",
259
+ "target": "root",
260
+ "when": {
261
+ "repo": "platform"
262
+ }
263
+ },
264
+ {
265
+ "name": "typecheck",
266
+ "command": "pnpm -r --if-present run typecheck",
267
+ "target": "root",
268
+ "when": {
269
+ "repo": "platform"
270
+ }
271
+ },
272
+ {
273
+ "name": "validate",
274
+ "command": "mfeflow validate",
275
+ "target": "root",
276
+ "when": {
277
+ "repo": "platform"
278
+ }
279
+ },
280
+ {
281
+ "name": "changeset",
282
+ "command": "changeset",
283
+ "target": "root",
284
+ "when": {
285
+ "repo": "platform"
286
+ }
287
+ },
288
+ {
289
+ "name": "version",
290
+ "command": "changeset version",
291
+ "target": "root",
292
+ "when": {
293
+ "repo": "platform"
294
+ }
295
+ },
296
+ {
297
+ "name": "release",
298
+ "command": "pnpm build && changeset publish",
299
+ "target": "root",
300
+ "when": {
301
+ "repo": "platform"
302
+ }
303
+ }
304
+ ]
305
+ }
@@ -0,0 +1,15 @@
1
+ # AGENTS.md
2
+
3
+ Guidance for coding agents working in this repo.
4
+
5
+ - This is the <%= it.options.repo %> repo of a polyrepo. The other repos are its siblings: `../platform`, `../shell` and one per team.
6
+ - Package manager: pnpm (via corepack). Never npm or yarn. Versions live in the `catalog` of
7
+ `pnpm-workspace.yaml`; add dependencies there and reference them as `catalog:`.
8
+ <% if (it.options.repo === 'platform') { %>- Every change to a package in `packages/` needs a changeset (`pnpm changeset`).
9
+ - Never import from a sibling repo by path. Other repos depend on what is published from here.
10
+ <% } else { %>- `<%= it.scope %>/*` packages come from `../platform` (`link:` paths until they are published). Build the platform repo first.
11
+ - TypeScript 7, strict. Relative imports have no file extension (`./plan`, not `./plan.js`).
12
+ - Add features with the `mfeflow` CLI (`pnpm mfeflow add <feature>`). `mfeflow.json` is its record of this repo.
13
+ <% } %><% if (it.options.repo === 'remote') { %>- A remote's public surface is `src/exposed/`. It must not import from the shell or another remote.
14
+ <% } %><% if (it.options.repo === 'shell') { %>- A new remote gets a repo with `pnpm mfeflow add team-repo <name>`. Do not add remotes to this repo by hand.
15
+ <% } %>EOF
@@ -0,0 +1,5 @@
1
+ # Changesets
2
+
3
+ Run `pnpm changeset` in any pull request that changes a package here. Pick the bump and describe
4
+ the change for the teams that use it. When the pull request merges, the release workflow opens a
5
+ "chore: version packages" pull request. Merging that one publishes the packages.
@@ -0,0 +1,9 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@changesets/config/schema.json",
3
+ "changelog": "@changesets/cli/changelog",
4
+ "commit": false,
5
+ "access": "public",
6
+ "baseBranch": "main",
7
+ "updateInternalDependencies": "patch",
8
+ "ignore": []
9
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "<%= it.scope %>/contracts",
3
+ "version": "0.1.0",
4
+ "description": "Event names and types the shell and every remote agree on.",
5
+ "type": "module",
6
+ "files": ["dist"],
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js"
11
+ }
12
+ },
13
+ "publishConfig": {
14
+ "access": "public"
15
+ },
16
+ "scripts": {
17
+ "build": "tsc",
18
+ "typecheck": "tsc --noEmit"
19
+ },
20
+ "devDependencies": {
21
+ "typescript": "catalog:"
22
+ }
23
+ }
@@ -0,0 +1,11 @@
1
+ // Names and shapes that more than one repo depends on. A change here is a change for every team,
2
+ // so it goes through a pull request and a changeset like any other published package.
3
+ export const EVENTS = {
4
+ navigate: 'platform:navigate',
5
+ } as const;
6
+
7
+ export type EventName = (typeof EVENTS)[keyof typeof EVENTS];
8
+
9
+ export interface NavigateEvent {
10
+ path: string;
11
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": { "outDir": "dist", "rootDir": "src" },
4
+ "include": ["src"]
5
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "<%= it.scope %>/tokens",
3
+ "version": "0.1.0",
4
+ "description": "Colors and spacing every team uses.",
5
+ "type": "module",
6
+ "files": ["dist"],
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js"
11
+ }
12
+ },
13
+ "publishConfig": {
14
+ "access": "public"
15
+ },
16
+ "scripts": {
17
+ "build": "tsc",
18
+ "typecheck": "tsc --noEmit"
19
+ },
20
+ "devDependencies": {
21
+ "typescript": "catalog:"
22
+ }
23
+ }
@@ -0,0 +1,13 @@
1
+ export const colors = {
2
+ text: '#1b1b1f',
3
+ surface: '#ffffff',
4
+ accent: '#3451b2',
5
+ } as const;
6
+
7
+ export const space = {
8
+ small: 8,
9
+ medium: 16,
10
+ large: 24,
11
+ } as const;
12
+
13
+ export type Space = keyof typeof space;
@@ -0,0 +1,5 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": { "outDir": "dist", "rootDir": "src" },
4
+ "include": ["src"]
5
+ }
@@ -0,0 +1,43 @@
1
+ # <%= it.scope.slice(1) %>-platform
2
+
3
+ The shared repo of a polyrepo made with [mfeflow](https://github.com/mfeflow/mfeflow). The shell and
4
+ every team repo sit next to it and depend on what is published from here.
5
+
6
+ ```
7
+ packages/contracts <%= it.scope %>/contracts: event names and types every repo agrees on
8
+ packages/tokens <%= it.scope %>/tokens: colors and spacing
9
+ .github/workflows reusable-ci.yml (the CI every team repo calls), ci.yml, release.yml
10
+ default.json the Renovate preset every repo extends
11
+ .changeset how a change here gets a version
12
+ ```
13
+
14
+ ## Run it
15
+
16
+ ```sh
17
+ pnpm install
18
+ pnpm build
19
+ ```
20
+
21
+ Build here first. The shell and the team repos link to `packages/*/dist` until you publish.
22
+
23
+ ## Change a package
24
+
25
+ ```sh
26
+ pnpm changeset # describe the change and pick the bump
27
+ pnpm build
28
+ ```
29
+
30
+ Commit the changeset with your change. After the pull request merges, the release workflow opens a
31
+ version pull request. Merging that publishes the packages.
32
+
33
+ ## Where the packages are published
34
+
35
+ The team repos install `<%= it.scope %>/*` with `link:../platform/packages/*` paths so a first run needs
36
+ no registry. To share the packages across machines and CI, publish them to a registry and replace the
37
+ `link:` ranges in each repo with a version:
38
+
39
+ - npm: set `NPM_TOKEN` as a repository secret. Packages are public.
40
+ - GitHub Packages: add `<%= it.scope %>:registry=https://npm.pkg.github.com` to `.npmrc` in every repo.
41
+ - A private registry: put its URL in `.npmrc` and set `publishConfig.registry` in each package.
42
+
43
+ Creating the GitHub repos and the registry account is up to you. mfeflow never calls GitHub.
@@ -0,0 +1,27 @@
1
+ {
2
+ "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3
+ "description": "Renovate preset for every <%= it.options.org || it.scope.slice(1) %> repo. Each repo extends it with github><%= it.options.org || it.scope.slice(1) %>/platform.",
4
+ "extends": ["config:recommended"],
5
+ "packageRules": [
6
+ {
7
+ "description": "Singletons the shell and every remote must share move in one pull request per repo.",
8
+ "matchPackageNames": ["react", "react-dom", "@types/react", "@types/react-dom"],
9
+ "groupName": "react"
10
+ },
11
+ {
12
+ "description": "Module Federation and Rsbuild have to stay on versions that work together.",
13
+ "matchPackageNames": ["@module-federation/**", "@rsbuild/**"],
14
+ "groupName": "module federation and rsbuild"
15
+ },
16
+ {
17
+ "description": "mfeflow packages are released together.",
18
+ "matchPackageNames": ["mfeflow", "@mfeflow/**"],
19
+ "groupName": "mfeflow"
20
+ },
21
+ {
22
+ "description": "The packages this org publishes from its platform repo.",
23
+ "matchPackageNames": ["<%= it.scope %>/**"],
24
+ "groupName": "platform packages"
25
+ }
26
+ ]
27
+ }