mfeflow 0.4.0 → 0.5.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/dist/output.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Issue } from '@mfeflow/contracts';
1
+ import type { Issue, RegistryIndex } from '@mfeflow/contracts';
2
2
  import { type Plan } from '@mfeflow/core';
3
3
  import { type ErrorReport } from './exit.js';
4
4
  import type { Finding } from './findings.js';
@@ -12,11 +12,13 @@ export interface PlanReport {
12
12
  path: string;
13
13
  action: 'create' | 'patch' | 'delete';
14
14
  }[];
15
+ /** `thirdParty` (only present when true): a feature from a registry asked for it. */
15
16
  dependencies: {
16
17
  file: string;
17
18
  name: string;
18
19
  range: string;
19
20
  dev: boolean;
21
+ thirdParty?: boolean;
20
22
  }[];
21
23
  scripts: {
22
24
  file: string;
@@ -69,8 +71,10 @@ export interface FeatureInfo {
69
71
  }
70
72
  export interface RegistryStatus {
71
73
  namespace: string;
72
- /** The index.json asked for. */
74
+ /** The index.json asked for, with token-like query values hidden. */
73
75
  index: string;
76
+ /** False: the user has not trusted this registry yet, so nothing was requested from it. */
77
+ trusted: boolean;
74
78
  /** How many items it lists. */
75
79
  items: number;
76
80
  /** Why it could not be read; the other registries are listed regardless. */
@@ -85,15 +89,17 @@ export interface FeaturesResult {
85
89
  }
86
90
  export interface RegistryEntryInfo {
87
91
  namespace: string;
88
- /** The URL template from mfeflow.json. */
92
+ /** The URL template from mfeflow.json, with token-like query values hidden. */
89
93
  url: string;
90
94
  /** Where its index.json is. */
91
95
  index: string;
96
+ /** Whether the user has trusted this namespace at this URL. */
97
+ trusted: boolean;
92
98
  }
93
99
  export interface RegistryResult {
94
100
  ok: true;
95
101
  command: 'registry';
96
- action: 'add' | 'remove' | 'list' | 'build';
102
+ action: 'add' | 'remove' | 'list' | 'build' | 'trust' | 'untrust';
97
103
  dryRun: boolean;
98
104
  /** The project folder; null for `build`, which needs none. */
99
105
  root: string | null;
@@ -184,12 +190,78 @@ export interface DevResult {
184
190
  /** Stopped with Ctrl+C or a termination signal. */
185
191
  interrupted: boolean;
186
192
  }
193
+ export interface PlannedUpload {
194
+ key: string;
195
+ bytes: number;
196
+ contentType: string;
197
+ cacheControl: string;
198
+ /** `create-only`: refuses to overwrite. `if-match`: only if the index is the one that was read. */
199
+ guard: 'create-only' | 'if-match' | 'none';
200
+ }
201
+ /** What `deploy`, `promote` and `rollback` print with `--json`. `ok` is false when a gate failed. */
202
+ export interface DeployResult {
203
+ ok: boolean;
204
+ command: 'deploy' | 'promote' | 'rollback';
205
+ dryRun: boolean;
206
+ root: string;
207
+ env: string;
208
+ /** The env a promote copied from; null otherwise. */
209
+ fromEnv: string | null;
210
+ /** The host as a person wrote it, never an endpoint or a credential. */
211
+ host: string;
212
+ remote: string;
213
+ version: string;
214
+ /** The version the env served before; null when the remote was not deployed there. */
215
+ servedBefore: string | null;
216
+ /** The index now points at `version` (false for a dry run, `--no-publish`, a failed gate, or when it already did). */
217
+ published: boolean;
218
+ previous: string | null;
219
+ rolledBackFrom: string | null;
220
+ /** Everything the run uploads, in order, the index last. */
221
+ uploads: PlannedUpload[];
222
+ uploaded: string[];
223
+ skipped: string[];
224
+ /** The registry/envs file written to match the host; null when none was. */
225
+ localEntry: string | null;
226
+ /** The index the run would write. Only for a dry run. */
227
+ index?: RegistryIndex;
228
+ warnings: string[];
229
+ findings: Finding[];
230
+ }
231
+ export interface StatusRemote {
232
+ name: string;
233
+ version: string;
234
+ previous: string | null;
235
+ history: string[];
236
+ rolledBackFrom: string | null;
237
+ /** The version in `registry/envs/<env>/<name>.json`; null when there is no such file. */
238
+ local: string | null;
239
+ /** Versions on the host newer than `version` that no environment points at; null when the host cannot list. */
240
+ unreferenced: string[] | null;
241
+ drift: string[];
242
+ }
243
+ export interface StatusEnv {
244
+ env: string;
245
+ /** The host has an index for this env. */
246
+ published: boolean;
247
+ /** When that index was written. The index records no per-remote time. */
248
+ generatedAt: string | null;
249
+ remotes: StatusRemote[];
250
+ }
251
+ export interface StatusResult {
252
+ ok: boolean;
253
+ command: 'status';
254
+ root: string;
255
+ host: string;
256
+ envs: StatusEnv[];
257
+ findings: Finding[];
258
+ }
187
259
  export interface FailureResult {
188
260
  ok: false;
189
261
  error: ErrorReport;
190
262
  exitCode: number;
191
263
  }
192
- export type CliResult = InitResult | AddResult | FeaturesResult | RegistryResult | ReportResult | BudgetResult | DevResult | FailureResult;
264
+ export type CliResult = InitResult | AddResult | FeaturesResult | RegistryResult | ReportResult | BudgetResult | DevResult | DeployResult | StatusResult | FailureResult;
193
265
  /** The plan as plain data for `--json`, without file contents. Features are listed once. */
194
266
  export declare function planReport(plan: Plan): PlanReport;
195
267
  /** Checks a document the CLI printed with `--json`. Returns the problems; empty means valid. */
@@ -5,3 +5,11 @@ export declare function projectReader(root: string): (file: string) => Promise<s
5
5
  export declare function createBuilder(root: string, checkout: string | undefined): PlanBuilder;
6
6
  /** `plan` with the `mfeflow.json` change added, for the summary and the diff. */
7
7
  export declare function withProjectChange(plan: Plan, project: MfeflowProject): Plan;
8
+ /**
9
+ * `project` with `cli` set to the running version, but only when the plan already changes
10
+ * something and only upward: an older CLI never lowers it, and a run with nothing to do
11
+ * leaves `mfeflow.json` alone.
12
+ */
13
+ export declare function stampCli(project: MfeflowProject, plan: Plan): MfeflowProject;
14
+ /** `project` with `cli` raised to the running version; never lowered. */
15
+ export declare function raiseCli(project: MfeflowProject): MfeflowProject;
@@ -0,0 +1,89 @@
1
+ import type { MfeManifest, RegistryEntry, RegistryIndex, RegistryIndexRemote } from '@mfeflow/contracts';
2
+ import { type ErrorCode } from '@mfeflow/core';
3
+ import { type ApplyResult, type DeployPlan, type GateCode, type RegistryHost } from '@mfeflow/registry/static';
4
+ import { type Globals } from './args.js';
5
+ import { type Finding } from './findings.js';
6
+ import { type AppView, type ProjectView } from './inspect.js';
7
+ import type { PlannedUpload } from './output.js';
8
+ import type { Runtime } from './runtime.js';
9
+ export declare const GATE_CODES: Record<GateCode, ErrorCode>;
10
+ export interface Session {
11
+ rt: Runtime;
12
+ globals: Globals;
13
+ view: ProjectView;
14
+ host: RegistryHost;
15
+ hostLabel: string;
16
+ /** Secret values to scrub from anything printed. */
17
+ secrets: string[];
18
+ /** `--base-url`, else `registry.baseUrl` from mfeflow.json. */
19
+ baseUrl: string | undefined;
20
+ }
21
+ export declare function envOf(args: Record<string, unknown>, key?: string): string;
22
+ 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>;
24
+ /**
25
+ * A base URL ends up in a public index, so it must be an https URL (http only on localhost) with no
26
+ * credentials, query or fragment. Returns the normalised form without a trailing slash.
27
+ */
28
+ export declare function normalizeBaseUrl(value: string, label: string): string;
29
+ /** A failure from the host or the files around it as the catalogue error that names it. Secrets never reach the message. */
30
+ export declare function hostFailure(error: unknown, secrets: readonly string[]): Error;
31
+ /** Runs host work, turning host errors into catalogue errors. */
32
+ export declare function onHost<T>(s: Session, work: () => Promise<T>): Promise<T>;
33
+ export declare function remoteApp(view: ProjectView, name: string | undefined): AppView & {
34
+ mfe: MfeOk;
35
+ };
36
+ type MfeOk = {
37
+ state: 'ok';
38
+ value: MfeManifest;
39
+ };
40
+ /** The versions the shell has, for the packages a remote's compatibility is judged on. */
41
+ export declare function shellVersions(view: ProjectView, mfe: MfeManifest): Promise<Record<string, string>>;
42
+ export declare const shellManifestKey: (env: string) => string;
43
+ /** The shell's deployed `mf-manifest.json`: the file given, else the one on the host under the env. */
44
+ export declare function readShellManifest(s: Session, env: string, file: string | undefined): Promise<{
45
+ manifest?: unknown;
46
+ warnings: string[];
47
+ }>;
48
+ /** Environments that have an index on the host. Empty when the host cannot list. */
49
+ export declare function hostEnvs(s: Session): Promise<string[]>;
50
+ export declare function currentIndex(s: Session, env: string): Promise<{
51
+ index: RegistryIndex | undefined;
52
+ etag?: string;
53
+ }>;
54
+ /** Where artifacts are served from: the setting, else read back from an entry already in the index. */
55
+ export declare function baseUrlFor(s: Session, index: RegistryIndex | undefined): string;
56
+ export declare function describeUploads(plan: DeployPlan): PlannedUpload[];
57
+ export declare function gateFindings(plan: DeployPlan, app: string): Finding[];
58
+ export interface Planned {
59
+ plan: DeployPlan;
60
+ /** The remote's entry in the index before this run. */
61
+ before: RegistryIndexRemote | undefined;
62
+ warnings: string[];
63
+ /** The pointer already is where the command would move it. */
64
+ noop?: boolean;
65
+ }
66
+ export interface Committed {
67
+ planned: Planned;
68
+ applied: ApplyResult | undefined;
69
+ /** The index was written. */
70
+ published: boolean;
71
+ }
72
+ export interface CommitOptions {
73
+ env: string;
74
+ name: string;
75
+ dryRun: boolean;
76
+ /** Upload the artifacts and leave the index where it is. */
77
+ noPublish?: boolean;
78
+ /** Reads the host and plans. Runs again after another writer got in first. */
79
+ plan: () => Promise<Planned>;
80
+ }
81
+ /**
82
+ * Plans and applies. When another writer moves the index between the read and the write, reads it
83
+ * again, plans again and tries once more; a second collision stops with E510 naming what changed.
84
+ */
85
+ export declare function commit(s: Session, options: CommitOptions): Promise<Committed>;
86
+ /** The entry `registry/envs/<env>/<name>.json` records for what the index serves. */
87
+ export declare function localEntryOf(remote: RegistryIndexRemote): RegistryEntry;
88
+ export declare function writeLocalEntry(root: string, env: string, remote: RegistryIndexRemote): Promise<string>;
89
+ export {};
package/dist/runtime.d.ts CHANGED
@@ -1,8 +1,9 @@
1
- import type { FetchLike } from '@mfeflow/core';
1
+ import { type FetchLike } from '@mfeflow/core';
2
2
  import { type AssetRoots } from './assets.js';
3
3
  import { type Io } from './io.js';
4
4
  import { type DevHost } from './proc.js';
5
5
  import { type Prompter } from './prompter.js';
6
+ import type { RegistryHost } from '@mfeflow/registry/static';
6
7
  import { type System } from './system.js';
7
8
  /** Everything a command touches outside its own arguments. Tests replace the parts they watch. */
8
9
  export interface Runtime {
@@ -11,7 +12,13 @@ export interface Runtime {
11
12
  system: System;
12
13
  fetch: FetchLike;
13
14
  roots: AssetRoots;
15
+ /** Where per-user settings live, like the registries the user trusts. */
16
+ configDir: string;
14
17
  /** Processes, signals and file watching for `mfeflow dev`. Defaults to the real ones. */
15
18
  dev?: DevHost;
19
+ /** `fetch` for S3 hosts. Tests stand in for the network. */
20
+ hostFetch?: typeof fetch;
21
+ /** Wraps the registry host `deploy`, `promote`, `rollback` and `status` open. Tests inject a second writer. */
22
+ wrapHost?: (host: RegistryHost) => RegistryHost;
16
23
  }
17
24
  export declare function realRuntime(io?: Io): Runtime;
@@ -0,0 +1,66 @@
1
+ import { type MfeflowError } from '@mfeflow/core';
2
+ import type { Io } from './io.js';
3
+ import type { DevHost, DevProcess, DevSpec, WaitPolicy } from './proc.js';
4
+ import type { Prompter } from './prompter.js';
5
+ import type { Runtime } from './runtime.js';
6
+ import type { System } from './system.js';
7
+ /** The checkout this package lives in; `--local` tests point at it. */
8
+ export declare const REPO: string;
9
+ export interface Recorded {
10
+ out: string[];
11
+ err: string[];
12
+ }
13
+ export declare function fakeIo(overrides?: Partial<Io>): Io & Recorded;
14
+ /** Answers prompts in the order they are asked, from `answers`; records the messages. */
15
+ export declare function scriptedPrompter(answers?: readonly unknown[]): Prompter & {
16
+ asked: string[];
17
+ };
18
+ export interface SystemCalls {
19
+ installs: {
20
+ root: string;
21
+ packageManager: string;
22
+ verbose: boolean;
23
+ }[];
24
+ gitInits: string[];
25
+ }
26
+ export declare function fakeSystem(options?: {
27
+ installError?: Error;
28
+ gitInit?: boolean;
29
+ }): System & SystemCalls;
30
+ export interface FakeProc extends DevProcess {
31
+ spec: DevSpec;
32
+ stopped: boolean;
33
+ /** What the process prints. */
34
+ print(text: string): void;
35
+ /** The process ends by itself. */
36
+ exit(code: number | null): void;
37
+ }
38
+ export interface FakeDev extends DevHost {
39
+ procs: FakeProc[];
40
+ /** Ports `portFree` reports as taken. */
41
+ busy: Set<number>;
42
+ watched: string[][];
43
+ /** The watched files changed. */
44
+ change(): void;
45
+ /** Ctrl+C. */
46
+ interrupt(): void;
47
+ waitPolicy: WaitPolicy;
48
+ }
49
+ export declare function fakeDev(): FakeDev;
50
+ export type TestRuntime = Runtime & {
51
+ io: Io & Recorded;
52
+ system: System & SystemCalls;
53
+ dev: FakeDev;
54
+ };
55
+ export declare function testRuntime(overrides?: Partial<Pick<Runtime, 'prompter' | 'fetch' | 'roots' | 'configDir'>> & {
56
+ /** Namespaces trusted for this run, through MFEFLOW_TRUST_REGISTRIES. */
57
+ trust?: string[];
58
+ }): TestRuntime;
59
+ export declare function tempDir(prefix?: string): Promise<string>;
60
+ export declare function removeDir(dir: string): Promise<void>;
61
+ /** Every file under `dir`, as `/`-separated paths mapped to content. */
62
+ export declare function readTree(dir: string): Promise<Record<string, string>>;
63
+ /** Awaits a promise that must reject with an MfeflowError and returns it. */
64
+ export declare function failure(promise: Promise<unknown>): Promise<MfeflowError>;
65
+ /** Calls `fn`, which must throw an MfeflowError, and returns it. */
66
+ export declare function thrown(fn: () => unknown): MfeflowError;
@@ -0,0 +1,4 @@
1
+ import { type Trusted } from '@mfeflow/core';
2
+ import type { Runtime } from './runtime.js';
3
+ /** What this user trusts among `registries`: the trust file, or MFEFLOW_TRUST_REGISTRIES (public hosts only unless pinned to a template). */
4
+ export declare function trustedIn(rt: Runtime, registries: Record<string, string> | undefined): Promise<Trusted>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mfeflow",
3
- "version": "0.4.0",
3
+ "version": "0.5.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,14 +49,15 @@
49
49
  },
50
50
  "dependencies": {
51
51
  "@clack/prompts": "1.8.1",
52
- "@mfeflow/contracts": "0.4.0",
53
- "@mfeflow/core": "0.4.0",
52
+ "@mfeflow/contracts": "0.5.0",
53
+ "@mfeflow/core": "0.5.0",
54
+ "@mfeflow/registry": "0.5.0",
54
55
  "citty": "0.2.2",
55
56
  "semver": "7.8.5",
56
57
  "yaml": "2.9.1"
57
58
  },
58
59
  "devDependencies": {
59
- "@mfeflow/rsbuild": "0.4.0",
60
+ "@mfeflow/rsbuild": "0.5.0",
60
61
  "@rslib/core": "1.0.5",
61
62
  "@types/node": "26.6.6",
62
63
  "@types/semver": "7.8.0",