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/README.md CHANGED
@@ -29,6 +29,10 @@ pnpm dev # shell on :4300, remotes on :4311,
29
29
  | `mfeflow validate` | `mfeflow.json`, every `mfe.json` and every registry entry against their 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
+ | `mfeflow deploy <remote> --env <env>` | Upload a remote's build to a static registry host and point the env's index at it |
33
+ | `mfeflow promote <remote>` | Copy the version one env serves to another env: `--from staging --to prod` |
34
+ | `mfeflow rollback <remote>` | Point an env back at the previous version, or `--to <version>`, without a rebuild |
35
+ | `mfeflow status` | What each env serves, the version before it, and drift from `registry/envs` |
32
36
  | `mfeflow --version`, `mfeflow --help` | Version, and help with examples (`mfeflow <command> --help` for one command) |
33
37
 
34
38
  Features that ship with it: `core` and `tsconfig` (always), `lint-oxlint`, `url-factory`, `env-t3` and `testing`
@@ -105,8 +109,8 @@ MFEFLOW_E108 The target folder is not empty: /work/app already has notes.txt
105
109
  | 3 | project or config problem: `MFEFLOW_E1xx` |
106
110
  | 4 | feature problem: `MFEFLOW_E2xx` |
107
111
  | 5 | build or runtime problem: `MFEFLOW_E3xx` |
108
- | 6 | registry problem: `MFEFLOW_E4xx` |
109
- | 7 | upgrade problem: `MFEFLOW_E5xx` |
112
+ | 6 | feature registry problem: `MFEFLOW_E4xx` |
113
+ | 7 | deploy problem: `MFEFLOW_E5xx` |
110
114
  | 130 | cancelled at a prompt (nothing was written), or Ctrl+C in `mfeflow dev` |
111
115
 
112
116
  ## Try it from a checkout
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "analytics-gtm",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Google Tag Manager with Consent Mode v2 behind @mfeflow/analytics: consent defaults before gtm.js, a consent banner hook, page views on route changes, and a typed tracker per remote. The container id comes from the runtime config.",
5
5
  "requires": ["core", "tsconfig"],
6
6
  "files": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ci-github",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "GitHub Actions workflow for pull requests: lint, typecheck, test, build, doctor, validate, check-compat, budget, runtime-config check and the Playwright smoke test. Opt-in.",
5
5
  "requires": ["core"],
6
6
  "files": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "env-t3",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Typed env with t3-env: env.ts per app fails the build on a missing variable, runtime-config schema checked before deploy, no validator or env value in the browser bundle.",
5
5
  "requires": ["core", "tsconfig"],
6
6
  "prompts": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lint-oxlint",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "oxlint with type-aware rules on TypeScript 7 (@mfeflow/oxlint-config), and Prettier.",
5
5
  "requires": ["core", "tsconfig"],
6
6
  "files": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testing",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Vitest, Testing Library, jsdom and MSW in every app with @mfeflow/testing, and a Playwright smoke test that opens the shell and checks each remote renders.",
5
5
  "requires": ["core", "tsconfig"],
6
6
  "files": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tsconfig",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Strict TypeScript 7 presets from @mfeflow/tsconfig, and a typecheck script.",
5
5
  "requires": ["core"],
6
6
  "files": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "url-factory",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Typed endpoint definitions and URL building (@mfeflow/url-factory), src/api/endpoints.ts in every app.",
5
5
  "requires": ["core"],
6
6
  "files": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "core",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "The shell, the remotes, the registry and the workspace around them.",
5
5
  "files": [
6
6
  {
@@ -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",
@@ -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
@@ -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
- const index = applyDevOverrides(await loadRegistryIndex(), { enabled: DEV });
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, { allowedOrigins: [location.origin, ...devOrigins] });
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
  }
package/dist/catalog.d.ts CHANGED
@@ -19,6 +19,10 @@ export interface FeatureSource {
19
19
  fetch?: FetchLike | undefined;
20
20
  /** The version to ask a registry for, when its URL template can pin one. */
21
21
  version?: string | undefined;
22
+ /** Namespaces the user trusts, so their registries may be on loopback or private addresses. */
23
+ trusted?: ReadonlySet<string> | undefined;
24
+ /** Compare the registry index's version with the feature.json's. */
25
+ checkIndex?: boolean | undefined;
22
26
  }
23
27
  /** A first-party feature by name, or `@namespace/name` from a configured registry. */
24
28
  export declare function loadAny(name: string, source: FeatureSource): Promise<LoadedFeature>;
@@ -1,9 +1,6 @@
1
+ import { comparable } from '@mfeflow/registry/static';
1
2
  import { type Check, type Finding } from '../findings.js';
2
- /**
3
- * The comparable version behind an installed version or a declared range. Undefined for a spec
4
- * that names no version (`link:`, `workspace:*`, `catalog:`, `latest`): it is only known after install.
5
- */
6
- export declare function comparable(version: string): string | undefined;
3
+ export { comparable };
7
4
  /** The warning for specs that cannot be compared before install. */
8
5
  export declare function unresolved(check: string, code: 'E301' | 'E302', name: string, specs: readonly string[]): Finding;
9
6
  export declare const singletonVersions: Check;
@@ -78,6 +78,10 @@ export declare const ADD_ARGS: {
78
78
  readonly description: 'add env-key: root, every shell or every remote, instead of --app';
79
79
  readonly valueHint: 'root|shell|remotes';
80
80
  };
81
+ readonly 'accept-changed-content': {
82
+ readonly type: 'boolean';
83
+ readonly description: 'Take a registry feature whose content differs from the hash recorded in mfeflow.json';
84
+ };
81
85
  };
82
86
  /** First-party features in the order `init` applies them, then the rest as typed: a feature may edit another's files. */
83
87
  export declare function inCatalogueOrder(names: readonly string[]): string[];
@@ -0,0 +1,227 @@
1
+ import type { DeployResult } from '../output.js';
2
+ import type { Runtime } from '../runtime.js';
3
+ export declare const DEPLOY_ARGS: {
4
+ readonly yes: {
5
+ readonly type: 'boolean';
6
+ readonly alias: 'y';
7
+ readonly description: 'Take the defaults and never prompt';
8
+ };
9
+ readonly preset: {
10
+ readonly type: 'string';
11
+ readonly description: 'Replay options from a JSON file or an https URL';
12
+ readonly valueHint: 'file|url';
13
+ };
14
+ readonly json: {
15
+ readonly type: 'boolean';
16
+ readonly description: 'Print one JSON document on stdout, nothing else';
17
+ };
18
+ readonly 'dry-run': {
19
+ readonly type: 'boolean';
20
+ readonly description: 'Print the summary and diff, write nothing';
21
+ };
22
+ readonly cwd: {
23
+ readonly type: 'string';
24
+ readonly description: 'Run as if started in this folder';
25
+ readonly valueHint: 'dir';
26
+ };
27
+ readonly install: {
28
+ readonly type: 'boolean';
29
+ readonly default: true;
30
+ readonly description: 'Run the package manager after writing (--no-install to skip)';
31
+ };
32
+ readonly verbose: {
33
+ readonly type: 'boolean';
34
+ readonly description: 'Show package manager output and stack traces';
35
+ };
36
+ readonly local: {
37
+ readonly type: 'string';
38
+ readonly description: 'Link @mfeflow/* from a built mfeflow checkout (env MFEFLOW_LOCAL)';
39
+ readonly valueHint: 'path';
40
+ };
41
+ readonly help: {
42
+ readonly type: 'boolean';
43
+ readonly alias: 'h';
44
+ readonly description: 'Show this help';
45
+ };
46
+ readonly host: {
47
+ readonly type: 'string';
48
+ readonly description: 'Where the registry lives: file:<dir> or s3://<bucket>[/prefix] (default registry.host)';
49
+ readonly valueHint: 'spec';
50
+ };
51
+ readonly 'base-url': {
52
+ readonly type: 'string';
53
+ readonly description: "Where the host's files are served from (default registry.baseUrl)";
54
+ readonly valueHint: 'url';
55
+ };
56
+ readonly 'shell-manifest': {
57
+ readonly type: 'string';
58
+ readonly description: "The shell's deployed mf-manifest.json (default envs/<env>/shell/ on the host)";
59
+ readonly valueHint: 'file';
60
+ };
61
+ readonly env: {
62
+ readonly type: 'string';
63
+ readonly description: 'Environment to deploy to';
64
+ readonly valueHint: 'env';
65
+ };
66
+ readonly version: {
67
+ readonly type: 'string';
68
+ readonly description: "Version to publish (default: the remote's package.json version)";
69
+ readonly valueHint: 'v';
70
+ };
71
+ readonly dist: {
72
+ readonly type: 'string';
73
+ readonly description: 'Build output folder inside the remote (default dist)';
74
+ readonly valueHint: 'dir';
75
+ };
76
+ readonly publish: {
77
+ readonly type: 'boolean';
78
+ readonly default: true;
79
+ readonly description: 'Move the index to the new version (--no-publish uploads the files and stops)';
80
+ };
81
+ };
82
+ export declare const PROMOTE_ARGS: {
83
+ readonly yes: {
84
+ readonly type: 'boolean';
85
+ readonly alias: 'y';
86
+ readonly description: 'Take the defaults and never prompt';
87
+ };
88
+ readonly preset: {
89
+ readonly type: 'string';
90
+ readonly description: 'Replay options from a JSON file or an https URL';
91
+ readonly valueHint: 'file|url';
92
+ };
93
+ readonly json: {
94
+ readonly type: 'boolean';
95
+ readonly description: 'Print one JSON document on stdout, nothing else';
96
+ };
97
+ readonly 'dry-run': {
98
+ readonly type: 'boolean';
99
+ readonly description: 'Print the summary and diff, write nothing';
100
+ };
101
+ readonly cwd: {
102
+ readonly type: 'string';
103
+ readonly description: 'Run as if started in this folder';
104
+ readonly valueHint: 'dir';
105
+ };
106
+ readonly install: {
107
+ readonly type: 'boolean';
108
+ readonly default: true;
109
+ readonly description: 'Run the package manager after writing (--no-install to skip)';
110
+ };
111
+ readonly verbose: {
112
+ readonly type: 'boolean';
113
+ readonly description: 'Show package manager output and stack traces';
114
+ };
115
+ readonly local: {
116
+ readonly type: 'string';
117
+ readonly description: 'Link @mfeflow/* from a built mfeflow checkout (env MFEFLOW_LOCAL)';
118
+ readonly valueHint: 'path';
119
+ };
120
+ readonly help: {
121
+ readonly type: 'boolean';
122
+ readonly alias: 'h';
123
+ readonly description: 'Show this help';
124
+ };
125
+ readonly host: {
126
+ readonly type: 'string';
127
+ readonly description: 'Where the registry lives: file:<dir> or s3://<bucket>[/prefix] (default registry.host)';
128
+ readonly valueHint: 'spec';
129
+ };
130
+ readonly 'base-url': {
131
+ readonly type: 'string';
132
+ readonly description: "Where the host's files are served from (default registry.baseUrl)";
133
+ readonly valueHint: 'url';
134
+ };
135
+ readonly 'shell-manifest': {
136
+ readonly type: 'string';
137
+ readonly description: "The shell's deployed mf-manifest.json (default envs/<env>/shell/ on the host)";
138
+ readonly valueHint: 'file';
139
+ };
140
+ readonly from: {
141
+ readonly type: 'string';
142
+ readonly description: 'Environment to copy the version from';
143
+ readonly valueHint: 'env';
144
+ };
145
+ readonly to: {
146
+ readonly type: 'string';
147
+ readonly description: 'Environment to point at it';
148
+ readonly valueHint: 'env';
149
+ };
150
+ };
151
+ export declare const ROLLBACK_ARGS: {
152
+ readonly yes: {
153
+ readonly type: 'boolean';
154
+ readonly alias: 'y';
155
+ readonly description: 'Take the defaults and never prompt';
156
+ };
157
+ readonly preset: {
158
+ readonly type: 'string';
159
+ readonly description: 'Replay options from a JSON file or an https URL';
160
+ readonly valueHint: 'file|url';
161
+ };
162
+ readonly json: {
163
+ readonly type: 'boolean';
164
+ readonly description: 'Print one JSON document on stdout, nothing else';
165
+ };
166
+ readonly 'dry-run': {
167
+ readonly type: 'boolean';
168
+ readonly description: 'Print the summary and diff, write nothing';
169
+ };
170
+ readonly cwd: {
171
+ readonly type: 'string';
172
+ readonly description: 'Run as if started in this folder';
173
+ readonly valueHint: 'dir';
174
+ };
175
+ readonly install: {
176
+ readonly type: 'boolean';
177
+ readonly default: true;
178
+ readonly description: 'Run the package manager after writing (--no-install to skip)';
179
+ };
180
+ readonly verbose: {
181
+ readonly type: 'boolean';
182
+ readonly description: 'Show package manager output and stack traces';
183
+ };
184
+ readonly local: {
185
+ readonly type: 'string';
186
+ readonly description: 'Link @mfeflow/* from a built mfeflow checkout (env MFEFLOW_LOCAL)';
187
+ readonly valueHint: 'path';
188
+ };
189
+ readonly help: {
190
+ readonly type: 'boolean';
191
+ readonly alias: 'h';
192
+ readonly description: 'Show this help';
193
+ };
194
+ readonly host: {
195
+ readonly type: 'string';
196
+ readonly description: 'Where the registry lives: file:<dir> or s3://<bucket>[/prefix] (default registry.host)';
197
+ readonly valueHint: 'spec';
198
+ };
199
+ readonly 'base-url': {
200
+ readonly type: 'string';
201
+ readonly description: "Where the host's files are served from (default registry.baseUrl)";
202
+ readonly valueHint: 'url';
203
+ };
204
+ readonly 'shell-manifest': {
205
+ readonly type: 'string';
206
+ readonly description: "The shell's deployed mf-manifest.json (default envs/<env>/shell/ on the host)";
207
+ readonly valueHint: 'file';
208
+ };
209
+ readonly env: {
210
+ readonly type: 'string';
211
+ readonly description: 'Environment to roll back';
212
+ readonly valueHint: 'env';
213
+ };
214
+ readonly to: {
215
+ readonly type: 'string';
216
+ readonly description: 'Version to go back to (default: the previous one)';
217
+ readonly valueHint: 'v';
218
+ };
219
+ readonly force: {
220
+ readonly type: 'boolean';
221
+ readonly description: 'Roll back even if the schema, route or compatibility checks fail (the files must still exist)';
222
+ };
223
+ };
224
+ export declare function formatDeploy(result: DeployResult): string;
225
+ export declare function runDeploy(rt: Runtime, args: Record<string, unknown>, raw: readonly string[]): Promise<DeployResult>;
226
+ export declare function runPromote(rt: Runtime, args: Record<string, unknown>, raw: readonly string[]): Promise<DeployResult>;
227
+ export declare function runRollback(rt: Runtime, args: Record<string, unknown>, raw: readonly string[]): Promise<DeployResult>;
@@ -0,0 +1,58 @@
1
+ import type { StatusResult } from '../output.js';
2
+ import type { Runtime } from '../runtime.js';
3
+ export declare const STATUS_ARGS: {
4
+ readonly yes: {
5
+ readonly type: 'boolean';
6
+ readonly alias: 'y';
7
+ readonly description: 'Take the defaults and never prompt';
8
+ };
9
+ readonly preset: {
10
+ readonly type: 'string';
11
+ readonly description: 'Replay options from a JSON file or an https URL';
12
+ readonly valueHint: 'file|url';
13
+ };
14
+ readonly json: {
15
+ readonly type: 'boolean';
16
+ readonly description: 'Print one JSON document on stdout, nothing else';
17
+ };
18
+ readonly 'dry-run': {
19
+ readonly type: 'boolean';
20
+ readonly description: 'Print the summary and diff, write nothing';
21
+ };
22
+ readonly cwd: {
23
+ readonly type: 'string';
24
+ readonly description: 'Run as if started in this folder';
25
+ readonly valueHint: 'dir';
26
+ };
27
+ readonly install: {
28
+ readonly type: 'boolean';
29
+ readonly default: true;
30
+ readonly description: 'Run the package manager after writing (--no-install to skip)';
31
+ };
32
+ readonly verbose: {
33
+ readonly type: 'boolean';
34
+ readonly description: 'Show package manager output and stack traces';
35
+ };
36
+ readonly local: {
37
+ readonly type: 'string';
38
+ readonly description: 'Link @mfeflow/* from a built mfeflow checkout (env MFEFLOW_LOCAL)';
39
+ readonly valueHint: 'path';
40
+ };
41
+ readonly help: {
42
+ readonly type: 'boolean';
43
+ readonly alias: 'h';
44
+ readonly description: 'Show this help';
45
+ };
46
+ readonly env: {
47
+ readonly type: 'string';
48
+ readonly description: 'Only this environment (default: every env on the host, and every folder in registry/envs but dev)';
49
+ readonly valueHint: 'env';
50
+ };
51
+ readonly host: {
52
+ readonly type: 'string';
53
+ readonly description: 'Where the registry lives: file:<dir> or s3://<bucket>[/prefix] (default registry.host)';
54
+ readonly valueHint: 'spec';
55
+ };
56
+ };
57
+ export declare function formatStatus(result: StatusResult): string;
58
+ export declare function runStatus(rt: Runtime, args: Record<string, unknown>, raw: readonly string[]): Promise<StatusResult>;
@@ -0,0 +1,41 @@
1
+ import type { RegistryIndexRemote } from '@mfeflow/contracts';
2
+ import { type DeployResult, type FailureResult, type StatusResult } from './output.js';
3
+ import { type TestRuntime } from './testing.js';
4
+ export declare const BASE_URL = "https://cdn.test";
5
+ interface Ran<T> {
6
+ code: number;
7
+ result: T;
8
+ rt: TestRuntime;
9
+ }
10
+ export interface Fixture {
11
+ root: string;
12
+ site: string;
13
+ cleanup(): Promise<void>;
14
+ /** `mfeflow <argv> --json` against this project and host, for the commands that print a deploy result. */
15
+ run(...argv: string[]): Promise<Ran<DeployResult>>;
16
+ /** The same, for a run that must fail with an error document. */
17
+ fail(...argv: string[]): Promise<Ran<FailureResult>>;
18
+ /** `mfeflow status <argv> --json`. */
19
+ status(...argv: string[]): Promise<Ran<StatusResult>>;
20
+ /** What the host serves for `env`, read straight from disk. */
21
+ served(env: string, remote?: string): Promise<RegistryIndexRemote | undefined>;
22
+ hostTree(): Promise<Record<string, string>>;
23
+ localTree(): Promise<Record<string, string>>;
24
+ }
25
+ /** An object read from JSON text, for tests that poke at it. */
26
+ export declare function parseObject(text: string): Record<string, unknown>;
27
+ export declare function readObject(file: string): Promise<Record<string, unknown>>;
28
+ export declare function failureOf(text: string): FailureResult;
29
+ export declare function statusOf(text: string): StatusResult;
30
+ export declare function deployOf(text: string): DeployResult;
31
+ /** Writes a remote's build: a manifest, an entry and one expose, with content that differs by `marker`. */
32
+ export declare function buildRemote(root: string, name: string, version: string, options?: {
33
+ marker?: string;
34
+ entryBytes?: number;
35
+ reactRange?: string;
36
+ }): Promise<void>;
37
+ export declare function fixture(options?: {
38
+ env?: Record<string, string>;
39
+ configure?: boolean;
40
+ }): Promise<Fixture>;
41
+ export {};
@@ -1,10 +1,9 @@
1
+ import type { RegistryIndex, RegistryIndexRemote } from '@mfeflow/contracts';
1
2
  import type { ProjectView } from './inspect.js';
2
- export interface DevRemote {
3
- name: string;
4
- entry: string;
3
+ export type DevRemote = RegistryIndexRemote & {
5
4
  version: string;
6
- }
7
- export interface DevIndex {
5
+ };
6
+ export interface DevIndex extends RegistryIndex {
8
7
  remotes: DevRemote[];
9
8
  }
10
9
  /** `name=url` flags as a map. A bare origin means its `/mf-manifest.json`. */
package/dist/exit.d.ts CHANGED
@@ -8,7 +8,7 @@ export declare const EXIT: {
8
8
  readonly feature: 4;
9
9
  readonly build: 5;
10
10
  readonly registry: 6;
11
- readonly upgrade: 7;
11
+ readonly deploy: 7;
12
12
  readonly cancelled: 130;
13
13
  };
14
14
  /** Raised when the user backs out of a prompt. */
@@ -0,0 +1,25 @@
1
+ import { type RegistryHost } from '@mfeflow/registry/static';
2
+ export type HostSpec = {
3
+ kind: 'file';
4
+ dir: string;
5
+ } | {
6
+ kind: 's3';
7
+ bucket: string;
8
+ prefix: string;
9
+ };
10
+ /** `file:<dir>` or `s3://<bucket>[/prefix]`. A folder is resolved against `cwd`. */
11
+ export declare function parseHostSpec(spec: string, cwd: string): HostSpec;
12
+ /** The host as shown to people and in `--json`: never an endpoint or a credential. */
13
+ export declare function describeHost(host: HostSpec): string;
14
+ type Env = Readonly<Record<string, string | undefined>>;
15
+ /** The secret values the environment holds, so no message can echo them. */
16
+ export declare function secretsOf(env: Env): string[];
17
+ export declare function redact(text: string, secrets: readonly string[]): string;
18
+ export interface OpenOptions {
19
+ env: Env;
20
+ /** For tests: stands in for the network. */
21
+ fetch?: typeof fetch;
22
+ }
23
+ /** Opens the host. S3 settings and credentials come from the environment only. */
24
+ export declare function openHost(spec: HostSpec, options: OpenOptions): Promise<RegistryHost>;
25
+ export {};
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ export { main } from './cli.js';
2
2
  export { COMMANDS } from './cli.js';
3
3
  export { EXIT, exitCodeFor } from './exit.js';
4
4
  export { validateResult } from './output.js';
5
- export type { AddResult, BudgetResult, DevResult, CliResult, FailureResult, FeatureInfo, FeaturesResult, InitResult, PlanReport, RegistryEntryInfo, RegistryResult, RegistryStatus, ReportResult, } from './output.js';
5
+ export type { AddResult, BudgetResult, DevResult, DeployResult, CliResult, FailureResult, FeatureInfo, FeaturesResult, InitResult, PlanReport, RegistryEntryInfo, RegistryResult, RegistryStatus, ReportResult, StatusResult, } from './output.js';
6
6
  export { realRuntime, type Runtime } from './runtime.js';
7
7
  export type { Io } from './io.js';
8
8
  export type { Prompter } from './prompter.js';