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.
Files changed (68) hide show
  1. package/README.md +8 -3
  2. package/assets/features/registry-server/feature.json +128 -0
  3. package/assets/features/registry-server/files/deploy-server.yml +69 -0
  4. package/assets/features/registry-server/files/registry-server.md.eta +52 -0
  5. package/assets/features/registry-server/files/registry-sources.ts +12 -0
  6. package/assets/features/registry-static/feature.json +61 -0
  7. package/assets/features/registry-static/files/deploy.yml +61 -0
  8. package/assets/features/registry-static/files/registry.md.eta +49 -0
  9. package/assets/features/release/feature.json +43 -0
  10. package/assets/features/release/files/changeset-readme.md +7 -0
  11. package/assets/features/release/files/config.json +10 -0
  12. package/assets/features/release/files/releasing.md +21 -0
  13. package/assets/templates/monorepo/feature.json +7 -6
  14. package/assets/templates/monorepo/files/remote/{mfe.json.eta → mfeflow.app.json.eta} +1 -0
  15. package/assets/templates/monorepo/files/root/README.md.eta +1 -1
  16. package/assets/templates/monorepo/files/shell/{mfe.json.eta → mfeflow.app.json.eta} +1 -0
  17. package/assets/templates/monorepo/files/shell/rsbuild.config.ts.eta +42 -6
  18. package/assets/templates/monorepo/migrations/0.4.0-app-file.mjs +58 -0
  19. package/assets/templates/polyrepo/feature.json +305 -0
  20. package/assets/templates/polyrepo/files/AGENTS.md.eta +15 -0
  21. package/assets/templates/polyrepo/files/changeset/README.md +5 -0
  22. package/assets/templates/polyrepo/files/changeset/config.json +9 -0
  23. package/assets/templates/polyrepo/files/packages/contracts/package.json.eta +23 -0
  24. package/assets/templates/polyrepo/files/packages/contracts/src/index.ts +11 -0
  25. package/assets/templates/polyrepo/files/packages/contracts/tsconfig.json.eta +5 -0
  26. package/assets/templates/polyrepo/files/packages/tokens/package.json.eta +23 -0
  27. package/assets/templates/polyrepo/files/packages/tokens/src/index.ts +13 -0
  28. package/assets/templates/polyrepo/files/packages/tokens/tsconfig.json.eta +5 -0
  29. package/assets/templates/polyrepo/files/platform/README.md.eta +43 -0
  30. package/assets/templates/polyrepo/files/platform/default.json.eta +27 -0
  31. package/assets/templates/polyrepo/files/remote/README.md.eta +23 -0
  32. package/assets/templates/polyrepo/files/renovate.json.eta +4 -0
  33. package/assets/templates/polyrepo/files/shell/README.md.eta +32 -0
  34. package/assets/templates/polyrepo/files/tsconfig.base.json.eta +13 -0
  35. package/assets/templates/polyrepo/files/workflows/platform-ci.yml +29 -0
  36. package/assets/templates/polyrepo/files/workflows/release.yml +40 -0
  37. package/assets/templates/polyrepo/files/workflows/reusable-ci.yml.eta +50 -0
  38. package/assets/templates/polyrepo/files/workflows/team-ci.yml.eta +12 -0
  39. package/dist/against.d.ts +16 -0
  40. package/dist/catalog.d.ts +4 -0
  41. package/dist/checks/compat.d.ts +12 -0
  42. package/dist/checks/index.d.ts +0 -1
  43. package/dist/commands/add.d.ts +0 -2
  44. package/dist/commands/adopt.d.ts +54 -0
  45. package/dist/commands/deploy.d.ts +35 -0
  46. package/dist/commands/doctor.d.ts +5 -0
  47. package/dist/commands/init.d.ts +9 -1
  48. package/dist/commands/server-mode.d.ts +210 -0
  49. package/dist/commands/status.d.ts +5 -0
  50. package/dist/commands/team-repo.d.ts +9 -0
  51. package/dist/commands/upgrade.d.ts +66 -0
  52. package/dist/devremotes.d.ts +15 -0
  53. package/dist/exit.d.ts +1 -0
  54. package/dist/glue.d.ts +6 -0
  55. package/dist/index.d.ts +1 -1
  56. package/dist/index.js +4546 -1880
  57. package/dist/inspect.d.ts +14 -2
  58. package/dist/output.d.ts +138 -3
  59. package/dist/pipeline.d.ts +5 -2
  60. package/dist/polyrepo.d.ts +69 -0
  61. package/dist/publish.d.ts +10 -1
  62. package/dist/runtime.d.ts +2 -0
  63. package/dist/server-client.d.ts +61 -0
  64. package/dist/system.d.ts +2 -0
  65. package/dist/testing.d.ts +17 -1
  66. package/dist/upgrade.d.ts +25 -0
  67. package/dist/write.d.ts +8 -0
  68. package/package.json +5 -5
package/dist/inspect.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { type Issue, type MfeManifest, type RegistryEntry } from '@mfeflow/contracts';
2
2
  import { type MfeflowProject } from '@mfeflow/core';
3
+ import type { Io } from './io.js';
3
4
  export declare const ENVS: readonly ['dev', 'staging', 'prod'];
4
5
  export type Read<T> = {
5
6
  state: 'missing';
@@ -27,6 +28,8 @@ export interface AppView {
27
28
  present: boolean;
28
29
  pkg: PackageView | undefined;
29
30
  mfe: Read<MfeManifest>;
31
+ /** Path from the root of the app file that was read: `mfeflow.app.json`, or the old `mfe.json`. */
32
+ appFile: string;
30
33
  }
31
34
  export interface WorkspacePackage {
32
35
  dir: string;
@@ -39,16 +42,25 @@ export interface ProjectView {
39
42
  project: MfeflowProject;
40
43
  apps: AppView[];
41
44
  workspace: WorkspacePackage[];
45
+ /** A polyrepo shell only: remote name → manifest URL of its dev server, from the shell's dev config. */
46
+ devRemotes?: Record<string, string>;
42
47
  /** env → remote name → entry. Only remotes with a file in that env are listed. */
43
48
  registry: Record<string, Record<string, Read<RegistryEntry>>>;
44
49
  /** Folders under registry/envs that exist. */
45
50
  envDirs: string[];
51
+ /** Folders of the apps that still use the old `mfe.json` name. */
52
+ legacyAppFiles: string[];
46
53
  }
47
54
  export declare function readText(file: string): Promise<string | undefined>;
48
55
  /** Workspace folders for the simple globs pnpm projects use: `dir/*`, plain paths, and `!` exclusions. */
49
56
  export declare function expandGlobs(root: string, globs: readonly string[]): Promise<string[]>;
50
- /** Everything the checks read, loaded once. Throws the E102-E104 errors of `loadProject`. */
51
- export declare function inspectProject(root: string): Promise<ProjectView>;
57
+ /** Prints the old app file name notice to stderr, at most once for each `io`, which is once per command. */
58
+ export declare function noticeLegacyAppFiles(io: Pick<Io, 'stderr'>, legacy: readonly string[]): void;
59
+ /**
60
+ * Everything the checks read, loaded once. Throws the E102-E104 errors of `loadProject`, and E122
61
+ * when an app has both app file names. With `io`, apps on the old name get one notice per command.
62
+ */
63
+ export declare function inspectProject(root: string, io?: Pick<Io, 'stderr'>): Promise<ProjectView>;
52
64
  /**
53
65
  * The version of `name` an app uses: what is installed under node_modules (walking up from
54
66
  * the app to the project root, as Node does, never above it), else the version its package.json
package/dist/output.d.ts CHANGED
@@ -44,19 +44,88 @@ interface WriteReport {
44
44
  /** Unified diff of every change. Only present for a dry run. */
45
45
  diff?: string;
46
46
  }
47
+ /** One repo of a polyrepo `init`. */
48
+ export interface RepoReport {
49
+ /** The folder inside `root`. */
50
+ name: string;
51
+ repo: 'platform' | 'shell' | 'remote';
52
+ root: string;
53
+ /** Files written in this repo, relative to it. */
54
+ written: string[];
55
+ installed: boolean;
56
+ }
47
57
  export interface InitResult extends WriteReport {
48
58
  command: 'init';
49
59
  scope: string;
50
60
  apps: AppReport[];
61
+ /** Only for `--topology polyrepo`: the repos made, in the order they were written. */
62
+ repos?: RepoReport[];
63
+ next: string[];
64
+ }
65
+ export interface AdoptResult extends WriteReport {
66
+ command: 'adopt';
67
+ scope: string;
68
+ apps: AppReport[];
69
+ /** Parts of the existing config adopt read but did not carry over, each with the reason. */
70
+ notAdopted: {
71
+ dir: string;
72
+ file: string;
73
+ field: string;
74
+ reason: string;
75
+ }[];
76
+ /** The build config files adopt read and left exactly as they were. */
77
+ buildConfig: string[];
78
+ /** Lines to paste into the shared list of a build config. */
79
+ snippet: string;
51
80
  next: string[];
52
81
  }
53
82
  export interface AddResult extends WriteReport {
54
83
  command: 'add';
55
- kind: 'remote' | 'shell' | 'feature' | 'env-key';
84
+ kind: 'remote' | 'shell' | 'feature' | 'env-key' | 'team-repo';
56
85
  /** What was added: an app name, or feature names. */
57
86
  added: string[];
58
87
  /** Features that were already part of the project. */
59
88
  skipped: string[];
89
+ /** `add team-repo` only: the new repo's folder, next to this one. */
90
+ created?: string;
91
+ }
92
+ export interface UpgradeFileReport {
93
+ path: string;
94
+ reason: 'edited' | 'untracked' | 'edited-by-cli' | 'skipped-feature' | 'symlink';
95
+ /** `accept`: written with the new version. `keep`: left, and remembered. `undecided`: left, and the run fails without a person to ask. */
96
+ decision: 'accept' | 'keep' | 'undecided';
97
+ /** Keys in your file that the new version lacks or has with another value; accepting drops them. */
98
+ lostKeys: string[];
99
+ /** The part of `lostKeys` that the new version has with another value. */
100
+ changedValues: string[];
101
+ /** Unified diff from your file to the new version. */
102
+ diff: string;
103
+ }
104
+ export interface UpgradeResult extends WriteReport {
105
+ command: 'upgrade';
106
+ from: string;
107
+ /** Where `from` was read: the `cli` field of mfeflow.json, or the shell's `@mfeflow/runtime` range. */
108
+ fromSource: 'mfeflow.json' | 'package.json';
109
+ to: string;
110
+ /** Nothing to write: the project already matches this version. */
111
+ upToDate: boolean;
112
+ /** Generated files you had not changed, now at the new version. */
113
+ replaced: string[];
114
+ created: string[];
115
+ /** package.json, pnpm-workspace.yaml and turbo.json, edited in place. */
116
+ edited: string[];
117
+ diverged: UpgradeFileReport[];
118
+ /** Registry features by name: `verified` against the recorded hash, `first-seen` when none was recorded, or `accepted-changed`. */
119
+ integrity: Record<string, 'verified' | 'first-seen' | 'accepted-changed'>;
120
+ /** Generated files you deleted. Not brought back. */
121
+ missing: string[];
122
+ /** Files the new version no longer generates. Left in place. */
123
+ removed: string[];
124
+ migrations: {
125
+ feature: string;
126
+ from: string;
127
+ to: string;
128
+ }[];
60
129
  }
61
130
  export interface FeatureInfo {
62
131
  /** `mfeflow` for the features that ship with the CLI, otherwise the registry's namespace, like `@test`. */
@@ -126,6 +195,8 @@ export interface ReportResult {
126
195
  summary: Summary;
127
196
  checks: CheckReport[];
128
197
  findings: Finding[];
198
+ /** `check-compat --against`: where the shell's versions came from. */
199
+ against?: string;
129
200
  }
130
201
  export interface ShellBudget {
131
202
  name: string;
@@ -144,7 +215,7 @@ export interface RouteBudget {
144
215
  expose: string | null;
145
216
  /** Entry plus the expose's synchronous JS, gzip KB. */
146
217
  kb: number;
147
- /** mfe.json budgets.initialJsKb, or null. */
218
+ /** mfeflow.app.json budgets.initialJsKb, or null. */
148
219
  budgetKb: number | null;
149
220
  pass: boolean | null;
150
221
  }
@@ -227,6 +298,14 @@ export interface DeployResult {
227
298
  index?: RegistryIndex;
228
299
  warnings: string[];
229
300
  findings: Finding[];
301
+ /** Server mode only: set when the deploy started a percentage rollout. `version` is then the candidate. */
302
+ rollout?: {
303
+ id: string;
304
+ stable: string;
305
+ candidate: string;
306
+ percent: number;
307
+ steps: number[];
308
+ };
230
309
  }
231
310
  export interface StatusRemote {
232
311
  name: string;
@@ -239,6 +318,13 @@ export interface StatusRemote {
239
318
  /** Versions on the host newer than `version` that no environment points at; null when the host cannot list. */
240
319
  unreferenced: string[] | null;
241
320
  drift: string[];
321
+ /** Server mode with a token: the remote was killed and is not in the served index. */
322
+ killed?: boolean;
323
+ /** Server mode with a token: a percentage rollout is running. */
324
+ rollout?: {
325
+ candidate: string;
326
+ percent: number;
327
+ } | null;
242
328
  }
243
329
  export interface StatusEnv {
244
330
  env: string;
@@ -256,12 +342,61 @@ export interface StatusResult {
256
342
  envs: StatusEnv[];
257
343
  findings: Finding[];
258
344
  }
345
+ /** `mfeflow rollout step|complete|abort`. Fields are null for a dry run, which sends nothing. */
346
+ export interface RolloutCommandResult {
347
+ ok: boolean;
348
+ command: 'rollout';
349
+ action: 'step' | 'complete' | 'abort' | 'status';
350
+ dryRun: boolean;
351
+ root: string;
352
+ host: string;
353
+ env: string;
354
+ remote: string;
355
+ stable: string | null;
356
+ candidate: string | null;
357
+ percent: number | null;
358
+ /** What the index serves now. */
359
+ serves: string | null;
360
+ /** `rollout status` only: the browser reports behind the gate. */
361
+ report?: {
362
+ startedAt: string;
363
+ steps: number[];
364
+ candidateCounts: {
365
+ loads: number;
366
+ errors: number;
367
+ clients: number;
368
+ };
369
+ stableCounts: {
370
+ loads: number;
371
+ errors: number;
372
+ clients: number;
373
+ };
374
+ gate: {
375
+ verdict: 'wait' | 'ok' | 'halt';
376
+ reason: string | null;
377
+ };
378
+ reporterCapFull: boolean;
379
+ };
380
+ }
381
+ /** `mfeflow kill` and `mfeflow restore`. */
382
+ export interface KillCommandResult {
383
+ ok: boolean;
384
+ command: 'kill' | 'restore';
385
+ dryRun: boolean;
386
+ root: string;
387
+ host: string;
388
+ env: string;
389
+ remote: string;
390
+ /** False when the remote was already in that state. Null for a dry run. */
391
+ changed: boolean | null;
392
+ killed: boolean | null;
393
+ }
259
394
  export interface FailureResult {
260
395
  ok: false;
261
396
  error: ErrorReport;
262
397
  exitCode: number;
263
398
  }
264
- export type CliResult = InitResult | AddResult | FeaturesResult | RegistryResult | ReportResult | BudgetResult | DevResult | DeployResult | StatusResult | FailureResult;
399
+ export type CliResult = InitResult | AdoptResult | AddResult | UpgradeResult | FeaturesResult | RegistryResult | ReportResult | BudgetResult | DevResult | DeployResult | StatusResult | RolloutCommandResult | KillCommandResult | FailureResult;
265
400
  /** The plan as plain data for `--json`, without file contents. Features are listed once. */
266
401
  export declare function planReport(plan: Plan): PlanReport;
267
402
  /** Checks a document the CLI printed with `--json`. Returns the problems; empty means valid. */
@@ -1,8 +1,11 @@
1
1
  import { PlanBuilder, type MfeflowProject, type Plan } from '@mfeflow/core';
2
2
  /** Reads a project file by its `/`-separated path; undefined when it does not exist. */
3
3
  export declare function projectReader(root: string): (file: string) => Promise<string | undefined>;
4
- /** A plan builder that reads `root`, keeps dependency versions in the catalog and resolves `@mfeflow/*` ranges. */
5
- export declare function createBuilder(root: string, checkout: string | undefined): PlanBuilder;
4
+ /**
5
+ * A plan builder that reads `root`, keeps dependency versions in the catalog and resolves `@mfeflow/*` ranges.
6
+ * `links` maps a package name to the `link:` path it is installed from instead of a registry range.
7
+ */
8
+ export declare function createBuilder(root: string, checkout: string | undefined, links?: Readonly<Record<string, string>>): PlanBuilder;
6
9
  /** `plan` with the `mfeflow.json` change added, for the summary and the diff. */
7
10
  export declare function withProjectChange(plan: Plan, project: MfeflowProject): Plan;
8
11
  /**
@@ -0,0 +1,69 @@
1
+ import { type LoadedFeature, type MfeflowProject, type Plan } from '@mfeflow/core';
2
+ import type { Globals } from './args.js';
3
+ import { type RemoteSpec } from './devremotes.js';
4
+ import { type FeatureOptions } from './options.js';
5
+ import type { PlanReport } from './output.js';
6
+ import type { Runtime } from './runtime.js';
7
+ import { type WriteOutcome } from './write.js';
8
+ export declare const PLATFORM_DIR = "platform";
9
+ export declare const SHELL_DIR = "shell";
10
+ export type RepoKind = 'platform' | 'shell' | 'remote';
11
+ export declare function assertFeaturesFit(features: readonly string[]): void;
12
+ export interface RepoRequest {
13
+ kind: RepoKind;
14
+ /** The folder inside the org folder. */
15
+ dir: string;
16
+ root: string;
17
+ scope: string;
18
+ /** The GitHub owner the CI and Renovate files point at; the scope without `@` when empty. */
19
+ org?: string | undefined;
20
+ /** The remote this repo holds (kind remote only). */
21
+ remote?: RemoteSpec;
22
+ /** The remotes the shell routes to and lists in its dev config (kind shell only). */
23
+ shellRemotes?: readonly RemoteSpec[];
24
+ features: readonly string[];
25
+ featureOptions: FeatureOptions;
26
+ local: string | undefined;
27
+ cwd: string;
28
+ }
29
+ /** Where the shell and each remote install `<scope>/*` from until the platform packages are published. */
30
+ export declare function platformLinks(scope: string): Record<string, string>;
31
+ export declare function remotePorts(names: readonly string[]): RemoteSpec[];
32
+ export declare function repoProject(kind: RepoKind, scope: string, remote?: RemoteSpec): MfeflowProject;
33
+ /**
34
+ * `core` for one repo of a polyrepo: the same files and dependencies, minus what belongs to the
35
+ * monorepo as a whole. The root package.json is named after the repo, the README and AGENTS.md come
36
+ * from the polyrepo template, and a platform repo keeps only the workspace files.
37
+ */
38
+ export declare function polyrepoCore(core: LoadedFeature, request: Pick<RepoRequest, 'kind' | 'scope' | 'remote'>): Promise<LoadedFeature>;
39
+ export interface RepoPlan {
40
+ request: RepoRequest;
41
+ project: MfeflowProject;
42
+ plan: Plan;
43
+ }
44
+ /** What one repo of the polyrepo would get; nothing is written. */
45
+ export declare function planRepo(rt: Runtime, request: RepoRequest): Promise<RepoPlan>;
46
+ /** `report` with every path under `dir/`, so reports of several repos can share one list. */
47
+ export declare function prefixReport(report: PlanReport, dir: string): PlanReport;
48
+ export declare function mergeReports(reports: readonly PlanReport[]): PlanReport;
49
+ export interface RepoWrite {
50
+ repo: RepoPlan;
51
+ outcome: WriteOutcome;
52
+ }
53
+ /** The folders of `dir`'s path that do not exist yet, outermost first. */
54
+ export declare function missingParents(dir: string): Promise<string[]>;
55
+ /** The warning for repos `git init` did not make, or nothing when all of them are. */
56
+ export declare function notReposWarning(dirs: readonly string[]): string[];
57
+ export interface RepoWrites {
58
+ writes: RepoWrite[];
59
+ /** Folders where `git init` did not run. */
60
+ notRepos: string[];
61
+ }
62
+ /**
63
+ * Writes the repos one after another, each in its own transaction with its own `git init`. If one
64
+ * fails, the repos this run already wrote are removed again, and so are the folders above them that
65
+ * this run made (while empty), so nothing half-built is left behind.
66
+ */
67
+ export declare function writeRepos(rt: Runtime, globals: Globals, org: string, repos: readonly RepoPlan[]): Promise<RepoWrites>;
68
+ /** The GitHub owner from `--org`, checked; undefined when not given. */
69
+ export declare function checkOrg(org: string | undefined): string | undefined;
package/dist/publish.d.ts CHANGED
@@ -17,10 +17,14 @@ export interface Session {
17
17
  secrets: string[];
18
18
  /** `--base-url`, else `registry.baseUrl` from mfeflow.json. */
19
19
  baseUrl: string | undefined;
20
+ /** Server mode with no host: there is nowhere to upload artifacts to. */
21
+ hostless: boolean;
20
22
  }
21
23
  export declare function envOf(args: Record<string, unknown>, key?: string): string;
22
24
  export declare function positionals(args: Record<string, unknown>, max: number, usage: string): string[];
23
- export declare function openSession(rt: Runtime, args: Record<string, unknown>, raw?: Globals): Promise<Session>;
25
+ export declare function openSession(rt: Runtime, args: Record<string, unknown>, raw?: Globals, options?: {
26
+ hostOptional?: boolean;
27
+ }): Promise<Session>;
24
28
  /**
25
29
  * A base URL ends up in a public index, so it must be an https URL (http only on localhost) with no
26
30
  * credentials, query or fragment. Returns the normalised form without a trailing slash.
@@ -86,4 +90,9 @@ export declare function commit(s: Session, options: CommitOptions): Promise<Comm
86
90
  /** The entry `registry/envs/<env>/<name>.json` records for what the index serves. */
87
91
  export declare function localEntryOf(remote: RegistryIndexRemote): RegistryEntry;
88
92
  export declare function writeLocalEntry(root: string, env: string, remote: RegistryIndexRemote): Promise<string>;
93
+ export interface Build {
94
+ manifest: unknown;
95
+ files: Record<string, Uint8Array>;
96
+ }
97
+ export declare function readBuild(dir: string, shown: string): Promise<Build>;
89
98
  export {};
package/dist/runtime.d.ts CHANGED
@@ -18,6 +18,8 @@ export interface Runtime {
18
18
  dev?: DevHost;
19
19
  /** `fetch` for S3 hosts. Tests stand in for the network. */
20
20
  hostFetch?: typeof fetch;
21
+ /** `fetch` for the registry server in server mode. Tests point it at a server on 127.0.0.1. */
22
+ serverFetch?: typeof fetch;
21
23
  /** Wraps the registry host `deploy`, `promote`, `rollback` and `status` open. Tests inject a second writer. */
22
24
  wrapHost?: (host: RegistryHost) => RegistryHost;
23
25
  }
@@ -0,0 +1,61 @@
1
+ import { type RegistryIndex } from '@mfeflow/contracts';
2
+ import { MfeflowError, type ErrorCode } from '@mfeflow/core';
3
+ import type { Runtime } from './runtime.js';
4
+ export declare const URL_ENV = "MFEFLOW_REGISTRY_URL";
5
+ export declare const TOKEN_ENV = "MFEFLOW_REGISTRY_TOKEN";
6
+ type Env = Readonly<Record<string, string | undefined>>;
7
+ /** The server address: `--registry-url`, else `MFEFLOW_REGISTRY_URL`. Undefined means static mode. */
8
+ export declare function serverUrlOf(args: Record<string, unknown>, env: Env): string | undefined;
9
+ /**
10
+ * The address as the client uses it: https, or http on a loopback host, with no login, query or
11
+ * fragment, and no trailing slash. A token is sent to this address, so anything else is refused.
12
+ */
13
+ export declare function normalizeServerUrl(raw: string): string;
14
+ /** The catalogue code for a server error: its own code first, then the HTTP status. Always E5xx. */
15
+ export declare function errorCodeFor(status: number, code: string | undefined): ErrorCode;
16
+ export interface ServerFailure {
17
+ code: string;
18
+ message: string;
19
+ failures?: {
20
+ code: string;
21
+ message?: string;
22
+ route?: string;
23
+ }[];
24
+ }
25
+ export declare class ServerError extends Error {
26
+ readonly status: number;
27
+ readonly failure: ServerFailure;
28
+ constructor(status: number, failure: ServerFailure);
29
+ }
30
+ export interface Reply {
31
+ status: number;
32
+ body: unknown;
33
+ }
34
+ export declare class ServerClient {
35
+ readonly url: string;
36
+ private readonly token;
37
+ private readonly doFetch;
38
+ private readonly secrets;
39
+ constructor(url: string, token: string | undefined, doFetch: typeof fetch, secrets: readonly string[]);
40
+ /** How the server is named in output: never a credential. */
41
+ get label(): string;
42
+ /** Text the server sent, with the token taken out, for anything that is printed. */
43
+ scrub(words: string): string;
44
+ /** A result document with every string scrubbed. The server's words reach the output through it. */
45
+ scrubAll<T>(value: T): T;
46
+ get hasToken(): boolean;
47
+ /** Throws the catalogue error for anything that is not a 2xx reply; `gates_failed` is thrown as a ServerError. */
48
+ get(route: string, needsToken?: boolean): Promise<unknown>;
49
+ post(route: string, body: unknown): Promise<unknown>;
50
+ /** Like `get` but a 404 is `undefined`. */
51
+ getOptional(route: string): Promise<unknown>;
52
+ index(env: string): Promise<RegistryIndex | undefined>;
53
+ private send;
54
+ /** A server error as the catalogue error that names it, with secrets scrubbed. */
55
+ wrap(error: ServerError): MfeflowError;
56
+ /** Runs a call and turns every failure into a catalogue error, except `gates_failed`, which `onGates` handles. */
57
+ run<T>(work: () => Promise<T>, onGates?: (failure: ServerFailure) => T): Promise<T>;
58
+ }
59
+ /** The client, or undefined when this run is in static mode. */
60
+ export declare function openServer(rt: Runtime, args: Record<string, unknown>): ServerClient | undefined;
61
+ export {};
package/dist/system.d.ts CHANGED
@@ -16,5 +16,7 @@ export interface System {
16
16
  install(root: string, packageManager: string, verbose: boolean): Promise<void>;
17
17
  /** `git init` unless `root` is already inside a work tree. Returns whether it ran. */
18
18
  gitInit(root: string): Promise<boolean>;
19
+ /** `git init` for a repo of its own, even when `root` sits inside another work tree. Returns whether it ran. */
20
+ initRepo(root: string): Promise<boolean>;
19
21
  }
20
22
  export declare function createSystem(io: Pick<Io, 'stderr'>, runner?: Run): System;
package/dist/testing.d.ts CHANGED
@@ -3,7 +3,7 @@ import type { Io } from './io.js';
3
3
  import type { DevHost, DevProcess, DevSpec, WaitPolicy } from './proc.js';
4
4
  import type { Prompter } from './prompter.js';
5
5
  import type { Runtime } from './runtime.js';
6
- import type { System } from './system.js';
6
+ import { type Run, type System } from './system.js';
7
7
  /** The checkout this package lives in; `--local` tests point at it. */
8
8
  export declare const REPO: string;
9
9
  export interface Recorded {
@@ -22,6 +22,7 @@ export interface SystemCalls {
22
22
  verbose: boolean;
23
23
  }[];
24
24
  gitInits: string[];
25
+ repoInits: string[];
25
26
  }
26
27
  export declare function fakeSystem(options?: {
27
28
  installError?: Error;
@@ -64,3 +65,18 @@ export declare function readTree(dir: string): Promise<Record<string, string>>;
64
65
  export declare function failure(promise: Promise<unknown>): Promise<MfeflowError>;
65
66
  /** Calls `fn`, which must throw an MfeflowError, and returns it. */
66
67
  export declare function thrown(fn: () => unknown): MfeflowError;
68
+ /** Where git is installed, from a fixed list of system folders, never from a PATH search. */
69
+ export declare function gitBinary(): string;
70
+ /**
71
+ * The environment git runs in: no `GIT_*` variable, so git works on the folder it is run in and
72
+ * nothing else, and a PATH of git's own folder only.
73
+ */
74
+ export declare function cleanGitEnv(env?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
75
+ /** Runs git with a clean environment in `cwd`; returns what it printed. */
76
+ export declare function git(cwd: string, ...args: string[]): string;
77
+ /** A `Run` that runs git by its fixed path in a clean environment, and refuses any other program. */
78
+ export declare const gitRunner: Run;
79
+ /** A system that runs the real `git init` (with a clean environment) and no install. */
80
+ export declare function gitSystem(): System & SystemCalls;
81
+ /** `file` with forward slashes, the way git prints a path on every OS. */
82
+ export declare function posixPath(file: string): string;
@@ -0,0 +1,25 @@
1
+ import { type MfeflowProject, type UpgradeDecisions, type UpgradePlan } from '@mfeflow/core';
2
+ import type { AssetRoots } from './assets.js';
3
+ import type { FetchLike } from '@mfeflow/core';
4
+ export interface UpgradeContext {
5
+ roots: AssetRoots;
6
+ fetch: FetchLike;
7
+ /** Registry namespaces the user trusts to be on loopback or private addresses. */
8
+ trusted?: ReadonlySet<string>;
9
+ /** Take registry content that differs from the hash recorded in mfeflow.json: every feature (`true`) or the named ones. */
10
+ acceptChanged?: boolean | ReadonlySet<string>;
11
+ }
12
+ /** How a registry feature's content compared with the hash recorded for it. */
13
+ export type IntegrityState = 'verified' | 'first-seen' | 'accepted-changed';
14
+ export interface ProjectUpgrade extends UpgradePlan {
15
+ /** Registry features by name. `first-seen`: no hash was recorded, so the served content could not be checked. */
16
+ integrity: Record<string, IntegrityState>;
17
+ }
18
+ /** The features `init` would apply for these recorded ones, in its order, `core` and `tsconfig` first. */
19
+ export declare function replayOrder(project: MfeflowProject): string[];
20
+ /**
21
+ * Plans moving the project at `root` to `toVersion` (default: this CLI) from the templates and
22
+ * features this CLI ships. A recorded feature that cannot be loaded, or that a registry serves at
23
+ * another version than the one installed, is left out and named in the plan's warnings.
24
+ */
25
+ export declare function planProjectUpgrade(ctx: UpgradeContext, root: string, project: MfeflowProject, toVersion?: string, decisions?: UpgradeDecisions): Promise<ProjectUpgrade>;
package/dist/write.d.ts CHANGED
@@ -10,6 +10,14 @@ export interface WriteRequest {
10
10
  project: MfeflowProject;
11
11
  /** Runs once the files are written, before the install (`git init` for hooks that need a repo). */
12
12
  afterWrite?: () => Promise<void>;
13
+ /** Install even when the plan adds no dependency, for a plan that moves ranges. */
14
+ install?: boolean;
15
+ /** Take `project` as it is: its `cli` is the version being upgraded to, not the one running. */
16
+ exactProject?: boolean;
17
+ /** Refuse to write through any symlink on a file's path, checked when the files are written. */
18
+ refuseSymlinks?: boolean;
19
+ /** The caller prints the summary and diff itself. */
20
+ quiet?: boolean;
13
21
  }
14
22
  export interface WriteOutcome {
15
23
  dryRun: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mfeflow",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Create and grow a React micro-frontend platform: mfeflow init, add remote, add <feature>.",
5
5
  "keywords": [
6
6
  "micro-frontend",
@@ -49,15 +49,15 @@
49
49
  },
50
50
  "dependencies": {
51
51
  "@clack/prompts": "1.8.1",
52
- "@mfeflow/contracts": "0.5.0",
53
- "@mfeflow/core": "0.5.0",
54
- "@mfeflow/registry": "0.5.0",
52
+ "@mfeflow/contracts": "0.6.0",
53
+ "@mfeflow/core": "0.6.0",
54
+ "@mfeflow/registry": "0.6.0",
55
55
  "citty": "0.2.2",
56
56
  "semver": "7.8.5",
57
57
  "yaml": "2.9.1"
58
58
  },
59
59
  "devDependencies": {
60
- "@mfeflow/rsbuild": "0.5.0",
60
+ "@mfeflow/rsbuild": "0.6.0",
61
61
  "@rslib/core": "1.0.5",
62
62
  "@types/node": "26.6.6",
63
63
  "@types/semver": "7.8.0",