mfeflow 0.5.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 +8 -3
- 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/templates/monorepo/feature.json +7 -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 +1 -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/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 +4 -0
- package/dist/checks/compat.d.ts +12 -0
- package/dist/checks/index.d.ts +0 -1
- package/dist/commands/add.d.ts +0 -2
- package/dist/commands/adopt.d.ts +54 -0
- package/dist/commands/deploy.d.ts +35 -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 +5 -0
- package/dist/commands/team-repo.d.ts +9 -0
- package/dist/commands/upgrade.d.ts +66 -0
- package/dist/devremotes.d.ts +15 -0
- package/dist/exit.d.ts +1 -0
- package/dist/glue.d.ts +6 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +4546 -1880
- package/dist/inspect.d.ts +14 -2
- package/dist/output.d.ts +138 -3
- package/dist/pipeline.d.ts +5 -2
- package/dist/polyrepo.d.ts +69 -0
- package/dist/publish.d.ts +10 -1
- package/dist/runtime.d.ts +2 -0
- package/dist/server-client.d.ts +61 -0
- package/dist/system.d.ts +2 -0
- package/dist/testing.d.ts +17 -1
- package/dist/upgrade.d.ts +25 -0
- package/dist/write.d.ts +8 -0
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -26,7 +26,7 @@ pnpm dev # shell on :4300, remotes on :4311,
|
|
|
26
26
|
| `mfeflow registry build <dir>` | Check a folder of features and write `index.json` and the files to host |
|
|
27
27
|
| `mfeflow dev` | Run the shell and every remote, one prefixed log each; follows `add remote` |
|
|
28
28
|
| `mfeflow doctor` | Singletons, compatibility, config health, drift; `--fix` repairs the safe ones |
|
|
29
|
-
| `mfeflow validate` | `mfeflow.json`, every `
|
|
29
|
+
| `mfeflow validate` | `mfeflow.json`, every `mfeflow.app.json` and every registry entry against schemas |
|
|
30
30
|
| `mfeflow check-compat` | Each remote's shared ranges against the shells it runs in |
|
|
31
31
|
| `mfeflow budget` | Built first-load JS of the shell and each remote against the cost-model budget |
|
|
32
32
|
| `mfeflow deploy <remote> --env <env>` | Upload a remote's build to a static registry host and point the env's index at it |
|
|
@@ -48,13 +48,17 @@ runs part of the project; `--remote name=url` uses a server you run yourself (th
|
|
|
48
48
|
localhost unless `--allow-origin <origin>` names it); Ctrl+C stops every app, process trees included, on Windows too.
|
|
49
49
|
It exits 130 after Ctrl+C.
|
|
50
50
|
|
|
51
|
+
Each app keeps its owner, routes and budgets in `mfeflow.app.json`. An app that still has the old `mfe.json` works:
|
|
52
|
+
the CLI reads it and prints one notice per command. An app with both files is an error (E122).
|
|
53
|
+
|
|
51
54
|
`doctor` runs these checks, each with a code and a docs page (`https://mfeflow.github.io/mfeflow/errors/<code>`). It exits with 0
|
|
52
55
|
when there are no errors, else with the lowest exit code among the errors' ranges (project 3 before build 5).
|
|
53
56
|
|
|
54
57
|
| Check | Code | Fixed by `--fix` |
|
|
55
58
|
| ------------------------------------- | -------------- | ----------------------------- |
|
|
56
59
|
| every app in mfeflow.json is on disk | E112 | |
|
|
57
|
-
| valid `
|
|
60
|
+
| valid `mfeflow.app.json`, name match | E113, E116 | |
|
|
61
|
+
| no app has both app file names | E122 | |
|
|
58
62
|
| no shared port | E115 | |
|
|
59
63
|
| no route claimed twice | E116 | |
|
|
60
64
|
| registry entry per remote and env | E114 | writes the missing ones |
|
|
@@ -70,7 +74,7 @@ when there are no errors, else with the lowest exit code among the errors' range
|
|
|
70
74
|
| server keys listed in `turbo.json` | E121 (warning) | |
|
|
71
75
|
|
|
72
76
|
`validate` runs the schema and registry checks, `check-compat` the compatibility check, `budget` reads the build output
|
|
73
|
-
(`dist/`): each remote's entry against `budget.remoteKb` (10), a route against `
|
|
77
|
+
(`dist/`): each remote's entry against `budget.remoteKb` (10), a route against `mfeflow.app.json` `budgets.initialJsKb`, and the
|
|
74
78
|
shell against `budget.baselineKb + budget.shellKb` (32) when a baseline is set. All four take `--json` and exit non-zero on
|
|
75
79
|
errors. LCP is measured by the perf job, not by `budget`.
|
|
76
80
|
|
|
@@ -111,6 +115,7 @@ MFEFLOW_E108 The target folder is not empty: /work/app already has notes.txt
|
|
|
111
115
|
| 5 | build or runtime problem: `MFEFLOW_E3xx` |
|
|
112
116
|
| 6 | feature registry problem: `MFEFLOW_E4xx` |
|
|
113
117
|
| 7 | deploy problem: `MFEFLOW_E5xx` |
|
|
118
|
+
| 8 | upgrade problem, or files that need a decision: `MFEFLOW_E6xx` |
|
|
114
119
|
| 130 | cancelled at a prompt (nothing was written), or Ctrl+C in `mfeflow dev` |
|
|
115
120
|
|
|
116
121
|
## Try it from a checkout
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "registry-server",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Wire the project to a self-hosted mfeflow registry server: the shell reads its index from the server (with the browser beacon for percentage rollouts), a CI workflow deploys every remote with a token from the environment, and a guide covers the day-to-day commands. Opt-in; the static registry works without it.",
|
|
5
|
+
"requires": ["core"],
|
|
6
|
+
"prompts": [
|
|
7
|
+
{
|
|
8
|
+
"name": "serverUrl",
|
|
9
|
+
"type": "string",
|
|
10
|
+
"message": "Registry server address (https://registry.example.com), empty to set it later",
|
|
11
|
+
"default": ""
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"files": [
|
|
15
|
+
{
|
|
16
|
+
"to": "src/registry-sources.ts",
|
|
17
|
+
"target": "shell",
|
|
18
|
+
"from": "files/registry-sources.ts"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"to": "docs/registry-server.md",
|
|
22
|
+
"target": "root",
|
|
23
|
+
"from": "files/registry-server.md.eta",
|
|
24
|
+
"template": true
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"to": ".github/workflows/deploy-server.yml",
|
|
28
|
+
"target": "root",
|
|
29
|
+
"from": "files/deploy-server.yml",
|
|
30
|
+
"when": {
|
|
31
|
+
"feature:ci-github": true
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
],
|
|
35
|
+
"dependencies": [
|
|
36
|
+
{
|
|
37
|
+
"name": "mfeflow",
|
|
38
|
+
"range": "latest",
|
|
39
|
+
"target": "root",
|
|
40
|
+
"dev": true
|
|
41
|
+
}
|
|
42
|
+
],
|
|
43
|
+
"scripts": [
|
|
44
|
+
{
|
|
45
|
+
"name": "server:deploy",
|
|
46
|
+
"command": "mfeflow deploy",
|
|
47
|
+
"target": "root"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"name": "server:rollback",
|
|
51
|
+
"command": "mfeflow rollback",
|
|
52
|
+
"target": "root"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"name": "server:status",
|
|
56
|
+
"command": "mfeflow status",
|
|
57
|
+
"target": "root"
|
|
58
|
+
}
|
|
59
|
+
],
|
|
60
|
+
"transforms": [
|
|
61
|
+
{
|
|
62
|
+
"op": "add-env-key",
|
|
63
|
+
"file": "src/config/schema.ts",
|
|
64
|
+
"target": "shell",
|
|
65
|
+
"key": "registryUrls",
|
|
66
|
+
"scope": "runtime",
|
|
67
|
+
"schema": {
|
|
68
|
+
"zod": "z.array(z.string())",
|
|
69
|
+
"valibot": "v.array(v.string())",
|
|
70
|
+
"arktype": "'string[]'"
|
|
71
|
+
},
|
|
72
|
+
"value": [],
|
|
73
|
+
"configFiles": [
|
|
74
|
+
"public/config/runtime-config.dev.json",
|
|
75
|
+
"public/config/runtime-config.staging.json",
|
|
76
|
+
"public/config/runtime-config.prod.json"
|
|
77
|
+
],
|
|
78
|
+
"when": {
|
|
79
|
+
"feature:env-t3": true
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"op": "add-env-key",
|
|
84
|
+
"file": "src/config/schema.ts",
|
|
85
|
+
"target": "shell",
|
|
86
|
+
"key": "registryBeacon",
|
|
87
|
+
"scope": "runtime",
|
|
88
|
+
"schema": {
|
|
89
|
+
"zod": "z.boolean()",
|
|
90
|
+
"valibot": "v.boolean()",
|
|
91
|
+
"arktype": "'boolean'"
|
|
92
|
+
},
|
|
93
|
+
"value": false,
|
|
94
|
+
"configFiles": [
|
|
95
|
+
"public/config/runtime-config.dev.json",
|
|
96
|
+
"public/config/runtime-config.staging.json",
|
|
97
|
+
"public/config/runtime-config.prod.json"
|
|
98
|
+
],
|
|
99
|
+
"when": {
|
|
100
|
+
"feature:env-t3": true
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"op": "add-env-key",
|
|
105
|
+
"file": "public/config/runtime-config.json",
|
|
106
|
+
"target": "shell",
|
|
107
|
+
"key": "registryUrls",
|
|
108
|
+
"scope": "runtime",
|
|
109
|
+
"schema": "z.array(z.string())",
|
|
110
|
+
"value": [],
|
|
111
|
+
"when": {
|
|
112
|
+
"feature:env-t3": false
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"op": "add-env-key",
|
|
117
|
+
"file": "public/config/runtime-config.json",
|
|
118
|
+
"target": "shell",
|
|
119
|
+
"key": "registryBeacon",
|
|
120
|
+
"scope": "runtime",
|
|
121
|
+
"schema": "z.boolean()",
|
|
122
|
+
"value": false,
|
|
123
|
+
"when": {
|
|
124
|
+
"feature:env-t3": false
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
]
|
|
128
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
name: deploy-server
|
|
2
|
+
|
|
3
|
+
# Deploys every remote through the registry server when main changes. The builds go to the artifact host first,
|
|
4
|
+
# then the server moves the pointer. Needs the address of the server (MFEFLOW_REGISTRY_URL, a variable), a token
|
|
5
|
+
# (MFEFLOW_REGISTRY_TOKEN, a secret) and an artifact host (see docs/registry-server.md).
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
branches: [main]
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
inputs:
|
|
11
|
+
env:
|
|
12
|
+
description: Environment to deploy to
|
|
13
|
+
default: prod
|
|
14
|
+
type: string
|
|
15
|
+
|
|
16
|
+
permissions: {}
|
|
17
|
+
|
|
18
|
+
# Two deploys at once would race on the same pointer; let the first finish.
|
|
19
|
+
concurrency:
|
|
20
|
+
group: deploy-server
|
|
21
|
+
cancel-in-progress: false
|
|
22
|
+
|
|
23
|
+
jobs:
|
|
24
|
+
deploy:
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
timeout-minutes: 20
|
|
27
|
+
# A manual run from another branch would otherwise deploy that branch.
|
|
28
|
+
if: github.ref == 'refs/heads/main'
|
|
29
|
+
environment: registry
|
|
30
|
+
permissions:
|
|
31
|
+
contents: read
|
|
32
|
+
steps:
|
|
33
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
34
|
+
with:
|
|
35
|
+
persist-credentials: false
|
|
36
|
+
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
|
37
|
+
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
|
38
|
+
with:
|
|
39
|
+
node-version: lts/*
|
|
40
|
+
cache: pnpm
|
|
41
|
+
- run: pnpm install --frozen-lockfile
|
|
42
|
+
- run: pnpm build
|
|
43
|
+
- name: Deploy remotes
|
|
44
|
+
env:
|
|
45
|
+
DEPLOY_ENV: ${{ inputs.env || 'prod' }}
|
|
46
|
+
MFEFLOW_REGISTRY_URL: ${{ vars.MFEFLOW_REGISTRY_URL }}
|
|
47
|
+
MFEFLOW_REGISTRY_TOKEN: ${{ secrets.MFEFLOW_REGISTRY_TOKEN }}
|
|
48
|
+
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
|
49
|
+
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
|
50
|
+
MFEFLOW_S3_ENDPOINT: ${{ vars.MFEFLOW_S3_ENDPOINT }}
|
|
51
|
+
MFEFLOW_S3_REGION: ${{ vars.MFEFLOW_S3_REGION }}
|
|
52
|
+
run: |
|
|
53
|
+
set -euo pipefail
|
|
54
|
+
# With -e, a mfeflow.json that cannot be read or parsed stops the step here.
|
|
55
|
+
remotes=$(node -p "Object.entries(JSON.parse(require('fs').readFileSync('mfeflow.json', 'utf8')).apps).filter(([, app]) => app.kind === 'remote').map(([name]) => name).join(' ')")
|
|
56
|
+
if [ -z "$remotes" ]; then
|
|
57
|
+
echo "::error::mfeflow.json lists no remotes to deploy"
|
|
58
|
+
exit 1
|
|
59
|
+
fi
|
|
60
|
+
failed=""
|
|
61
|
+
for remote in $remotes; do
|
|
62
|
+
if ! pnpm mfeflow deploy "$remote" --env "$DEPLOY_ENV" --yes; then
|
|
63
|
+
failed="$failed $remote"
|
|
64
|
+
fi
|
|
65
|
+
done
|
|
66
|
+
if [ -n "$failed" ]; then
|
|
67
|
+
echo "::error::deploy failed for:$failed"
|
|
68
|
+
exit 1
|
|
69
|
+
fi
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Registry server
|
|
2
|
+
|
|
3
|
+
This project can publish its remotes through a self-hosted mfeflow registry server. The server keeps the pointer
|
|
4
|
+
(which version each environment serves) in SQLite, checks every deploy, and takes tokens with narrow scopes. The
|
|
5
|
+
build files still live on a static host or bucket; the server only moves the pointer.
|
|
6
|
+
|
|
7
|
+
## Configuration
|
|
8
|
+
|
|
9
|
+
- `serverUrl`: <%= it.options.serverUrl === '' ? 'not set yet' : '`' + it.options.serverUrl + '`' %>
|
|
10
|
+
|
|
11
|
+
This is the address `registry-server` was added with. Commands read it from `MFEFLOW_REGISTRY_URL`, or `--registry-url`.
|
|
12
|
+
The token is read only from `MFEFLOW_REGISTRY_TOKEN`; there is no flag for it, so it never lands in your shell history.
|
|
13
|
+
Plain http works only for localhost.
|
|
14
|
+
|
|
15
|
+
## Point the shell at the server
|
|
16
|
+
|
|
17
|
+
In `public/config/runtime-config.prod.json`, set the index address and the origins it may be on:
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"registryUrls": ["https://registry.example.com/v1/index/prod", "/registry/index.json"],
|
|
22
|
+
"registryOrigins": ["https://registry.example.com"],
|
|
23
|
+
"registryBeacon": true
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The second URL is the static fallback the server republishes, so shells still boot if the server is down. Then use
|
|
28
|
+
`src/registry-sources.ts` in `src/bootstrap.tsx`: replace `registrySources()` with the one from `./registry-sources`.
|
|
29
|
+
`registryBeacon: true` lets browsers report load errors, which is how the server halts a bad percentage rollout.
|
|
30
|
+
|
|
31
|
+
## Commands
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
export MFEFLOW_REGISTRY_URL=https://registry.example.com
|
|
35
|
+
export MFEFLOW_REGISTRY_TOKEN=... # from `mfeflow-registry token create` on the server
|
|
36
|
+
pnpm server:deploy catalog --env prod # upload the build, ask the server to point prod at it
|
|
37
|
+
pnpm server:status --env prod # what the server serves
|
|
38
|
+
pnpm server:rollback catalog --env prod # back to the previous version
|
|
39
|
+
pnpm mfeflow kill catalog --env prod # out of the index at once
|
|
40
|
+
pnpm mfeflow deploy catalog --env prod --percent 10 --steps 50,100
|
|
41
|
+
pnpm mfeflow rollout status catalog --env prod
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`deploy` uploads the build to the artifact host first (`--host`, or `registry.host` in `mfeflow.json`), so the host
|
|
45
|
+
credentials are the same as for the static registry. Then it asks the server, which reads the files back and runs its
|
|
46
|
+
own checks.
|
|
47
|
+
|
|
48
|
+
## In CI
|
|
49
|
+
|
|
50
|
+
With `ci-github` in the project, `.github/workflows/deploy-server.yml` deploys every remote when `main` changes. Add
|
|
51
|
+
`MFEFLOW_REGISTRY_TOKEN` and the artifact host credentials as secrets of the `registry` environment, and
|
|
52
|
+
`MFEFLOW_REGISTRY_URL` as a variable. Give the token only `read` and `deploy` for the environment it deploys to.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { RegistrySources } from '@mfeflow/runtime';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The index URLs for the shell. With `registryBeacon: true` in the runtime config this adds the
|
|
5
|
+
* browser's id to the registry server's URLs (so a percentage rollout reaches the same people every
|
|
6
|
+
* time) and starts the reporter that lets the server halt a bad rollout. Otherwise it is
|
|
7
|
+
* `registrySources()` as it was. Use it in `bootstrap.tsx` in place of `registrySources()`.
|
|
8
|
+
*/
|
|
9
|
+
export async function registrySources(): Promise<RegistrySources> {
|
|
10
|
+
const { registrySourcesWithBeacon } = await import('@mfeflow/runtime/beacon');
|
|
11
|
+
return registrySourcesWithBeacon();
|
|
12
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "registry-static",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Publish remotes to a static registry (a folder, S3, R2 or MinIO) with mfeflow deploy, promote, rollback and status. Adds registry:deploy, registry:rollback and registry:status scripts, a guide, and, when ci-github is present, a workflow that deploys every remote when main changes. Credentials come from CI secrets and the environment only.",
|
|
5
|
+
"requires": ["core"],
|
|
6
|
+
"prompts": [
|
|
7
|
+
{
|
|
8
|
+
"name": "host",
|
|
9
|
+
"type": "string",
|
|
10
|
+
"message": "Registry host (file:<dir> or s3://<bucket>[/prefix]), empty to set it later",
|
|
11
|
+
"default": ""
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"name": "baseUrl",
|
|
15
|
+
"type": "string",
|
|
16
|
+
"message": "URL the host's files are served from (https://cdn.example.com), empty to set it later",
|
|
17
|
+
"default": ""
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"files": [
|
|
21
|
+
{
|
|
22
|
+
"to": "docs/registry.md",
|
|
23
|
+
"target": "root",
|
|
24
|
+
"from": "files/registry.md.eta",
|
|
25
|
+
"template": true
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"to": ".github/workflows/deploy.yml",
|
|
29
|
+
"target": "root",
|
|
30
|
+
"from": "files/deploy.yml",
|
|
31
|
+
"when": {
|
|
32
|
+
"feature:ci-github": true
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"dependencies": [
|
|
37
|
+
{
|
|
38
|
+
"name": "mfeflow",
|
|
39
|
+
"range": "latest",
|
|
40
|
+
"target": "root",
|
|
41
|
+
"dev": true
|
|
42
|
+
}
|
|
43
|
+
],
|
|
44
|
+
"scripts": [
|
|
45
|
+
{
|
|
46
|
+
"name": "registry:deploy",
|
|
47
|
+
"command": "mfeflow deploy",
|
|
48
|
+
"target": "root"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"name": "registry:rollback",
|
|
52
|
+
"command": "mfeflow rollback",
|
|
53
|
+
"target": "root"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"name": "registry:status",
|
|
57
|
+
"command": "mfeflow status",
|
|
58
|
+
"target": "root"
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
name: deploy
|
|
2
|
+
|
|
3
|
+
# Publishes every remote to the static registry when main changes. Needs a host: set the `host` option of
|
|
4
|
+
# the registry-static feature (or `registry.host` in mfeflow.json), and the secrets named below.
|
|
5
|
+
on:
|
|
6
|
+
push:
|
|
7
|
+
branches: [main]
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
inputs:
|
|
10
|
+
env:
|
|
11
|
+
description: Environment to deploy to
|
|
12
|
+
default: prod
|
|
13
|
+
type: string
|
|
14
|
+
|
|
15
|
+
permissions: {}
|
|
16
|
+
|
|
17
|
+
# Two deploys at once would race on the index; let the first finish.
|
|
18
|
+
concurrency:
|
|
19
|
+
group: deploy
|
|
20
|
+
cancel-in-progress: false
|
|
21
|
+
|
|
22
|
+
jobs:
|
|
23
|
+
deploy:
|
|
24
|
+
runs-on: ubuntu-latest
|
|
25
|
+
timeout-minutes: 20
|
|
26
|
+
# A manual run from another branch would otherwise deploy that branch.
|
|
27
|
+
if: github.ref == 'refs/heads/main'
|
|
28
|
+
environment: registry
|
|
29
|
+
permissions:
|
|
30
|
+
contents: read
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
33
|
+
with:
|
|
34
|
+
persist-credentials: false
|
|
35
|
+
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
|
|
36
|
+
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
|
37
|
+
with:
|
|
38
|
+
node-version: lts/*
|
|
39
|
+
cache: pnpm
|
|
40
|
+
- run: pnpm install --frozen-lockfile
|
|
41
|
+
- run: pnpm build
|
|
42
|
+
- name: Deploy remotes
|
|
43
|
+
env:
|
|
44
|
+
DEPLOY_ENV: ${{ inputs.env || 'prod' }}
|
|
45
|
+
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
|
46
|
+
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
|
47
|
+
MFEFLOW_S3_ENDPOINT: ${{ vars.MFEFLOW_S3_ENDPOINT }}
|
|
48
|
+
MFEFLOW_S3_REGION: ${{ vars.MFEFLOW_S3_REGION }}
|
|
49
|
+
run: |
|
|
50
|
+
set -uo pipefail
|
|
51
|
+
remotes=$(node -p "Object.entries(JSON.parse(require('fs').readFileSync('mfeflow.json', 'utf8')).apps).filter(([, app]) => app.kind === 'remote').map(([name]) => name).join(' ')")
|
|
52
|
+
failed=""
|
|
53
|
+
for remote in $remotes; do
|
|
54
|
+
if ! pnpm mfeflow deploy "$remote" --env "$DEPLOY_ENV" --yes; then
|
|
55
|
+
failed="$failed $remote"
|
|
56
|
+
fi
|
|
57
|
+
done
|
|
58
|
+
if [ -n "$failed" ]; then
|
|
59
|
+
echo "::error::deploy failed for:$failed"
|
|
60
|
+
exit 1
|
|
61
|
+
fi
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Registry
|
|
2
|
+
|
|
3
|
+
Remotes are published to a static host: a folder, or an S3-compatible bucket (S3, R2, MinIO). Each version is
|
|
4
|
+
uploaded once and never changes; the environment's `index.json` points at the version people get. A rollback moves
|
|
5
|
+
that pointer back, with no rebuild.
|
|
6
|
+
|
|
7
|
+
## Configuration
|
|
8
|
+
|
|
9
|
+
- `host`: <%= it.options.host === '' ? 'not set yet' : '`' + it.options.host + '`' %>
|
|
10
|
+
- `baseUrl`: <%= it.options.baseUrl === '' ? 'not set yet' : '`' + it.options.baseUrl + '`' %>
|
|
11
|
+
|
|
12
|
+
These are the options `registry-static` was added with (see `features` in `mfeflow.json`). `registry.host` and
|
|
13
|
+
`registry.baseUrl` in `mfeflow.json` take precedence, and `--host` and `--base-url` on a command beat both.
|
|
14
|
+
`host` is `file:<dir>` or `s3://<bucket>[/prefix]`; `baseUrl` is where the host's files are served from, without a
|
|
15
|
+
trailing slash.
|
|
16
|
+
|
|
17
|
+
## Commands
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
pnpm registry:deploy catalog --env prod # upload the build, point prod at it
|
|
21
|
+
pnpm registry:status # what each environment serves, and drift
|
|
22
|
+
pnpm registry:rollback catalog --env prod # back to the previous version, no rebuild
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`deploy` publishes the version in the remote's `package.json` unless you pass `--version`. Run it with `--dry-run`
|
|
26
|
+
first to see the uploads and the checks.
|
|
27
|
+
|
|
28
|
+
## Credentials
|
|
29
|
+
|
|
30
|
+
Credentials are read from the environment and never written to a file: `AWS_ACCESS_KEY_ID`,
|
|
31
|
+
`AWS_SECRET_ACCESS_KEY` and, for R2 or MinIO, `MFEFLOW_S3_ENDPOINT` and `MFEFLOW_S3_REGION`. Locally, export them in
|
|
32
|
+
your shell. Do not commit a `.env` file with them.
|
|
33
|
+
|
|
34
|
+
## In CI
|
|
35
|
+
|
|
36
|
+
With `ci-github` in the project, `.github/workflows/deploy.yml` deploys every remote when `main` changes (and from the
|
|
37
|
+
Actions tab, for another environment). Add `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` as secrets of the
|
|
38
|
+
`registry` environment, and `MFEFLOW_S3_ENDPOINT` and `MFEFLOW_S3_REGION` as its variables. The workflow is only
|
|
39
|
+
generated when `ci-github` is already in the project, so add `ci-github` first.
|
|
40
|
+
|
|
41
|
+
Protect the `registry` environment (Settings, Environments): allow deployments from the `main` branch only, and add
|
|
42
|
+
required reviewers for `prod`. The workflow also refuses to run from any other branch. A remote whose files changed
|
|
43
|
+
without a version bump fails its deploy; the others still deploy, and the run fails at the end with the list.
|
|
44
|
+
|
|
45
|
+
## How fast a rollback reaches people
|
|
46
|
+
|
|
47
|
+
The index is served with `Cache-Control: max-age=60`, so a browser that already has it keeps it for up to a minute.
|
|
48
|
+
GitHub Pages cannot change its 10 minute cache, so a rollback there takes about 11 minutes. Use a bucket if that is
|
|
49
|
+
too slow.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "release",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Changesets versions each remote on its own. A pull request adds a changeset, `pnpm release:version` bumps the remote's package.json and changelog, and `mfeflow deploy` publishes that same version.",
|
|
5
|
+
"requires": ["core"],
|
|
6
|
+
"files": [
|
|
7
|
+
{
|
|
8
|
+
"to": ".changeset/config.json",
|
|
9
|
+
"target": "root",
|
|
10
|
+
"from": "files/config.json"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"to": ".changeset/README.md",
|
|
14
|
+
"target": "root",
|
|
15
|
+
"from": "files/changeset-readme.md"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"to": "docs/releasing.md",
|
|
19
|
+
"target": "root",
|
|
20
|
+
"from": "files/releasing.md"
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"dependencies": [
|
|
24
|
+
{
|
|
25
|
+
"name": "@changesets/cli",
|
|
26
|
+
"range": "3.0.3",
|
|
27
|
+
"target": "root",
|
|
28
|
+
"dev": true
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
"scripts": [
|
|
32
|
+
{
|
|
33
|
+
"name": "changeset",
|
|
34
|
+
"command": "changeset",
|
|
35
|
+
"target": "root"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"name": "release:version",
|
|
39
|
+
"command": "changeset version",
|
|
40
|
+
"target": "root"
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Changesets
|
|
2
|
+
|
|
3
|
+
A changeset is a short note, in this folder, that says which remote changed and how big the change is. Add one with
|
|
4
|
+
`pnpm changeset` in every pull request that changes a remote. `pnpm release:version` turns the notes into version
|
|
5
|
+
bumps in each remote's `package.json` and into its `CHANGELOG.md`.
|
|
6
|
+
|
|
7
|
+
See `docs/releasing.md`.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://unpkg.com/@changesets/config/schema.json",
|
|
3
|
+
"changelog": "@changesets/cli/changelog",
|
|
4
|
+
"commit": false,
|
|
5
|
+
"access": "restricted",
|
|
6
|
+
"baseBranch": "main",
|
|
7
|
+
"updateInternalDependencies": "patch",
|
|
8
|
+
"ignore": [],
|
|
9
|
+
"privatePackages": { "version": true, "tag": false }
|
|
10
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Releasing
|
|
2
|
+
|
|
3
|
+
Each remote has its own version, kept by [Changesets](https://github.com/changesets/changesets). `mfeflow deploy`
|
|
4
|
+
publishes the version in the remote's `package.json`, so the version you release and the version you deploy are the
|
|
5
|
+
same number.
|
|
6
|
+
|
|
7
|
+
1. In a pull request that changes a remote, run `pnpm changeset`. Pick the remote, pick patch, minor or major, and
|
|
8
|
+
write one plain sentence about the change. Commit the file it creates in `.changeset/`.
|
|
9
|
+
2. When you are ready to release, run `pnpm release:version`. It bumps `package.json` and `CHANGELOG.md` in each remote
|
|
10
|
+
that has a changeset, and deletes the used notes. Commit the result and merge it.
|
|
11
|
+
3. Deploy: `pnpm mfeflow deploy <remote> --env prod`. With the `registry-static` feature that is
|
|
12
|
+
`pnpm registry:deploy <remote> --env prod`, or the deploy workflow on `main`.
|
|
13
|
+
|
|
14
|
+
The remotes are private packages, so `.changeset/config.json` sets `privatePackages.version` to `true` (they get a
|
|
15
|
+
version) and `tag` to `false` (nothing is published to npm or tagged).
|
|
16
|
+
|
|
17
|
+
The files of a published version never change: deploying the same number with different content is refused. To ship
|
|
18
|
+
a fix, add a changeset and bump.
|
|
19
|
+
|
|
20
|
+
To go back to the previous version, run `mfeflow rollback <remote> --env prod`. It does not touch the version in
|
|
21
|
+
`package.json`, so after a rollback ship the fix as a new patch.
|
|
@@ -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
|
{
|
|
@@ -121,9 +121,9 @@
|
|
|
121
121
|
"template": true
|
|
122
122
|
},
|
|
123
123
|
{
|
|
124
|
-
"to": "
|
|
124
|
+
"to": "mfeflow.app.json",
|
|
125
125
|
"target": "remotes",
|
|
126
|
-
"from": "files/remote/
|
|
126
|
+
"from": "files/remote/mfeflow.app.json.eta",
|
|
127
127
|
"template": true
|
|
128
128
|
},
|
|
129
129
|
{
|
|
@@ -323,5 +323,6 @@
|
|
|
323
323
|
"command": "rsbuild build",
|
|
324
324
|
"target": "remotes"
|
|
325
325
|
}
|
|
326
|
-
]
|
|
326
|
+
],
|
|
327
|
+
"migrations": [{ "from": "0.3.0", "to": "0.4.0", "module": "migrations/0.4.0-app-file.mjs" }]
|
|
327
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
|
```
|