mfeflow 0.4.0 → 0.6.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/README.md +14 -5
- package/assets/features/analytics-gtm/feature.json +1 -1
- package/assets/features/ci-github/feature.json +1 -1
- package/assets/features/env-t3/feature.json +1 -1
- package/assets/features/lint-oxlint/feature.json +1 -1
- package/assets/features/registry-server/feature.json +128 -0
- package/assets/features/registry-server/files/deploy-server.yml +69 -0
- package/assets/features/registry-server/files/registry-server.md.eta +52 -0
- package/assets/features/registry-server/files/registry-sources.ts +12 -0
- package/assets/features/registry-static/feature.json +61 -0
- package/assets/features/registry-static/files/deploy.yml +61 -0
- package/assets/features/registry-static/files/registry.md.eta +49 -0
- package/assets/features/release/feature.json +43 -0
- package/assets/features/release/files/changeset-readme.md +7 -0
- package/assets/features/release/files/config.json +10 -0
- package/assets/features/release/files/releasing.md +21 -0
- package/assets/features/testing/feature.json +1 -1
- package/assets/features/tsconfig/feature.json +1 -1
- package/assets/features/url-factory/feature.json +1 -1
- package/assets/templates/monorepo/feature.json +13 -6
- package/assets/templates/monorepo/files/remote/{mfe.json.eta → mfeflow.app.json.eta} +1 -0
- package/assets/templates/monorepo/files/root/README.md.eta +16 -1
- package/assets/templates/monorepo/files/shell/{mfe.json.eta → mfeflow.app.json.eta} +1 -0
- package/assets/templates/monorepo/files/shell/rsbuild.config.ts.eta +42 -6
- package/assets/templates/monorepo/files/shell/src/assert.ts.eta +37 -0
- package/assets/templates/monorepo/files/shell/src/bootstrap.tsx.eta +12 -2
- package/assets/templates/monorepo/migrations/0.4.0-app-file.mjs +58 -0
- package/assets/templates/polyrepo/feature.json +305 -0
- package/assets/templates/polyrepo/files/AGENTS.md.eta +15 -0
- package/assets/templates/polyrepo/files/changeset/README.md +5 -0
- package/assets/templates/polyrepo/files/changeset/config.json +9 -0
- package/assets/templates/polyrepo/files/packages/contracts/package.json.eta +23 -0
- package/assets/templates/polyrepo/files/packages/contracts/src/index.ts +11 -0
- package/assets/templates/polyrepo/files/packages/contracts/tsconfig.json.eta +5 -0
- package/assets/templates/polyrepo/files/packages/tokens/package.json.eta +23 -0
- package/assets/templates/polyrepo/files/packages/tokens/src/index.ts +13 -0
- package/assets/templates/polyrepo/files/packages/tokens/tsconfig.json.eta +5 -0
- package/assets/templates/polyrepo/files/platform/README.md.eta +43 -0
- package/assets/templates/polyrepo/files/platform/default.json.eta +27 -0
- package/assets/templates/polyrepo/files/remote/README.md.eta +23 -0
- package/assets/templates/polyrepo/files/renovate.json.eta +4 -0
- package/assets/templates/polyrepo/files/shell/README.md.eta +32 -0
- package/assets/templates/polyrepo/files/tsconfig.base.json.eta +13 -0
- package/assets/templates/polyrepo/files/workflows/platform-ci.yml +29 -0
- package/assets/templates/polyrepo/files/workflows/release.yml +40 -0
- package/assets/templates/polyrepo/files/workflows/reusable-ci.yml.eta +50 -0
- package/assets/templates/polyrepo/files/workflows/team-ci.yml.eta +12 -0
- package/dist/against.d.ts +16 -0
- package/dist/catalog.d.ts +8 -0
- package/dist/checks/compat.d.ts +12 -0
- package/dist/checks/index.d.ts +0 -1
- package/dist/checks/singletons.d.ts +2 -5
- package/dist/commands/add.d.ts +4 -2
- package/dist/commands/adopt.d.ts +54 -0
- package/dist/commands/deploy.d.ts +262 -0
- package/dist/commands/doctor.d.ts +5 -0
- package/dist/commands/init.d.ts +9 -1
- package/dist/commands/server-mode.d.ts +210 -0
- package/dist/commands/status.d.ts +63 -0
- package/dist/commands/team-repo.d.ts +9 -0
- package/dist/commands/upgrade.d.ts +66 -0
- package/dist/deploy-testing.d.ts +41 -0
- package/dist/devregistry.d.ts +4 -5
- package/dist/devremotes.d.ts +15 -0
- package/dist/exit.d.ts +2 -1
- package/dist/glue.d.ts +6 -0
- package/dist/hosts.d.ts +25 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +5477 -1643
- package/dist/inspect.d.ts +14 -2
- package/dist/output.d.ts +214 -7
- package/dist/pipeline.d.ts +13 -2
- package/dist/polyrepo.d.ts +69 -0
- package/dist/publish.d.ts +98 -0
- package/dist/runtime.d.ts +10 -1
- package/dist/server-client.d.ts +61 -0
- package/dist/system.d.ts +2 -0
- package/dist/testing.d.ts +82 -0
- package/dist/trust.d.ts +4 -0
- package/dist/upgrade.d.ts +25 -0
- package/dist/write.d.ts +8 -0
- package/package.json +5 -4
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "The shell, the remotes, the registry and the workspace around them.",
|
|
5
5
|
"files": [
|
|
6
6
|
{
|
|
@@ -67,9 +67,9 @@
|
|
|
67
67
|
"template": true
|
|
68
68
|
},
|
|
69
69
|
{
|
|
70
|
-
"to": "
|
|
70
|
+
"to": "mfeflow.app.json",
|
|
71
71
|
"target": "shell",
|
|
72
|
-
"from": "files/shell/
|
|
72
|
+
"from": "files/shell/mfeflow.app.json.eta",
|
|
73
73
|
"template": true
|
|
74
74
|
},
|
|
75
75
|
{
|
|
@@ -96,6 +96,12 @@
|
|
|
96
96
|
"from": "files/shell/src/bootstrap.tsx.eta",
|
|
97
97
|
"template": true
|
|
98
98
|
},
|
|
99
|
+
{
|
|
100
|
+
"to": "src/assert.ts",
|
|
101
|
+
"target": "shell",
|
|
102
|
+
"from": "files/shell/src/assert.ts.eta",
|
|
103
|
+
"template": true
|
|
104
|
+
},
|
|
99
105
|
{
|
|
100
106
|
"to": "src/App.tsx",
|
|
101
107
|
"target": "shell",
|
|
@@ -115,9 +121,9 @@
|
|
|
115
121
|
"template": true
|
|
116
122
|
},
|
|
117
123
|
{
|
|
118
|
-
"to": "
|
|
124
|
+
"to": "mfeflow.app.json",
|
|
119
125
|
"target": "remotes",
|
|
120
|
-
"from": "files/remote/
|
|
126
|
+
"from": "files/remote/mfeflow.app.json.eta",
|
|
121
127
|
"template": true
|
|
122
128
|
},
|
|
123
129
|
{
|
|
@@ -317,5 +323,6 @@
|
|
|
317
323
|
"command": "rsbuild build",
|
|
318
324
|
"target": "remotes"
|
|
319
325
|
}
|
|
320
|
-
]
|
|
326
|
+
],
|
|
327
|
+
"migrations": [{ "from": "0.3.0", "to": "0.4.0", "module": "migrations/0.4.0-app-file.mjs" }]
|
|
321
328
|
}
|
|
@@ -36,7 +36,7 @@ pnpm mfeflow add <feature> # see `pnpm mfeflow features`
|
|
|
36
36
|
|
|
37
37
|
```sh
|
|
38
38
|
pnpm doctor # singletons, compatibility, config, drift; `pnpm doctor --fix` repairs the safe ones
|
|
39
|
-
pnpm validate # mfeflow.json, every
|
|
39
|
+
pnpm validate # mfeflow.json, every mfeflow.app.json and every registry entry
|
|
40
40
|
pnpm check-compat # each remote's shared ranges against its shell
|
|
41
41
|
pnpm budget # after `pnpm build`: first-load JS per shell and remote
|
|
42
42
|
```
|
|
@@ -45,6 +45,21 @@ Each is a root script for the `mfeflow` CLI in your devDependencies (`pnpm mfefl
|
|
|
45
45
|
against the 10 KB a remote may add; set `budget.baselineKb` in `mfeflow.json` (your pages built as one app) to check
|
|
46
46
|
the shell against baseline + 32 KB too.
|
|
47
47
|
|
|
48
|
+
## Deploy it
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
pnpm build
|
|
52
|
+
pnpm mfeflow deploy catalog --env staging --host file:./site --base-url https://cdn.example.com
|
|
53
|
+
pnpm mfeflow promote catalog --from staging --to prod --host file:./site
|
|
54
|
+
pnpm mfeflow rollback catalog --env prod --host file:./site
|
|
55
|
+
pnpm mfeflow status --host file:./site
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`deploy` uploads a remote's `dist` and points an environment at it, `rollback` points it back without a rebuild, and
|
|
59
|
+
`status` shows what is served. Put `host` and `baseUrl` under `registry` in `mfeflow.json` to drop the flags, or use
|
|
60
|
+
`s3://<bucket>` with the `MFEFLOW_S3_*` and `AWS_*` variables. There are no root scripts for these, because the package manager has a deploy command of its own, so
|
|
61
|
+
run them through `pnpm mfeflow`.
|
|
62
|
+
|
|
48
63
|
## Before you deploy
|
|
49
64
|
|
|
50
65
|
`registry/envs/staging` and `registry/envs/prod` point at `example.com`. Replace the hosts
|
|
@@ -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
|
|
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:
|
|
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 ??
|
|
65
|
-
|
|
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,37 @@
|
|
|
1
|
+
/* oxlint-disable eslint/no-underscore-dangle -- __MFEFLOW_SINGLETONS__ is the name the rsbuild preset defines */
|
|
2
|
+
import { checkSingletons, reportRemoteError } from '@mfeflow/runtime';
|
|
3
|
+
|
|
4
|
+
// The shell's rsbuild preset defines the packages it shares; an older preset does not, so fall back.
|
|
5
|
+
declare const __MFEFLOW_SINGLETONS__: readonly string[] | undefined;
|
|
6
|
+
const SINGLETONS =
|
|
7
|
+
typeof __MFEFLOW_SINGLETONS__ === 'undefined' ? ['react', 'react-dom'] : __MFEFLOW_SINGLETONS__;
|
|
8
|
+
|
|
9
|
+
// Remotes register their shared packages as they load, so the check repeats for a while.
|
|
10
|
+
const CHECK_EVERY_MS = 3000;
|
|
11
|
+
const STOP_AFTER_MS = 60_000;
|
|
12
|
+
|
|
13
|
+
declare global {
|
|
14
|
+
var mfeflowCheckSingletons: (() => ReturnType<typeof checkSingletons>) | undefined;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const reported = new Set<string>();
|
|
18
|
+
|
|
19
|
+
function check(): ReturnType<typeof checkSingletons> {
|
|
20
|
+
const result = checkSingletons(SINGLETONS);
|
|
21
|
+
// `missing` is not a problem: a shared package nothing has loaded yet is not in the share scope.
|
|
22
|
+
for (const name of result.duplicated) {
|
|
23
|
+
const problem = `${name} is loaded in more than one version`;
|
|
24
|
+
if (reported.has(problem)) continue;
|
|
25
|
+
reported.add(problem);
|
|
26
|
+
reportRemoteError('<%= it.app.name %>', 'singletons', new Error(problem));
|
|
27
|
+
}
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Loaded only in development: it reads every loaded remote's share scope.
|
|
32
|
+
globalThis.mfeflowCheckSingletons = check;
|
|
33
|
+
check();
|
|
34
|
+
const timer = setInterval(check, CHECK_EVERY_MS);
|
|
35
|
+
setTimeout(() => {
|
|
36
|
+
clearInterval(timer);
|
|
37
|
+
}, STOP_AFTER_MS);
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
applyDevOverrides,
|
|
4
4
|
loadRegistryIndex,
|
|
5
5
|
registerRemotesFromIndex,
|
|
6
|
+
registrySources,
|
|
6
7
|
reportRemoteError,
|
|
7
8
|
} from '@mfeflow/runtime';
|
|
8
9
|
import { App } from './App';
|
|
@@ -12,13 +13,22 @@ const DEV = process.env.NODE_ENV !== 'production';
|
|
|
12
13
|
export async function main(): Promise<void> {
|
|
13
14
|
try {
|
|
14
15
|
// Dev only: ?mfe-override=cart@http://localhost:4312/mf-manifest.json points a remote at your build.
|
|
15
|
-
|
|
16
|
+
// runtime-config.json can set registryUrls (primary, then fallback) and registryOrigins for a CDN-hosted registry;
|
|
17
|
+
// it never holds up boot for more than 1.5 s, and is not fetched at all when the index is inlined.
|
|
18
|
+
const { urls, allowedOrigins: cdnOrigins } = await registrySources();
|
|
19
|
+
const index = applyDevOverrides(await loadRegistryIndex(urls, { allowedOrigins: cdnOrigins }), {
|
|
20
|
+
enabled: DEV,
|
|
21
|
+
});
|
|
16
22
|
// In dev the registry points at the remotes' dev servers; elsewhere remotes share the shell's origin.
|
|
17
23
|
const devOrigins = DEV ? index.remotes.map((remote) => new URL(remote.entry).origin) : [];
|
|
18
|
-
registerRemotesFromIndex(index, {
|
|
24
|
+
registerRemotesFromIndex(index, {
|
|
25
|
+
allowedOrigins: [location.origin, ...cdnOrigins, ...devOrigins],
|
|
26
|
+
});
|
|
19
27
|
} catch (error) {
|
|
20
28
|
reportRemoteError('<%= it.app.name %>', 'registry', error);
|
|
21
29
|
}
|
|
22
30
|
const el = document.getElementById('root');
|
|
23
31
|
if (el) createRoot(el).render(<App />);
|
|
32
|
+
// Development only, so production builds carry nothing for it.
|
|
33
|
+
if (DEV) void import('./assert');
|
|
24
34
|
}
|
|
@@ -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,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
|
+
}
|