@mfeflow/core 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +45 -2
- package/dist/apply.d.ts +29 -0
- package/dist/errors.d.ts +202 -0
- package/dist/features/index.d.ts +5 -0
- package/dist/features/load.d.ts +30 -0
- package/dist/features/policy.d.ts +27 -0
- package/dist/features/remote.d.ts +34 -0
- package/dist/features/resolve.d.ts +20 -0
- package/dist/features/schema-policy.d.ts +15 -0
- package/dist/features/write-policy.d.ts +23 -0
- package/dist/fs.d.ts +24 -0
- package/dist/hash.d.ts +4 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +2182 -0
- package/dist/json.d.ts +12 -0
- package/dist/paths.d.ts +4 -0
- package/dist/plan.d.ts +114 -0
- package/dist/project.d.ts +21 -0
- package/dist/runtime-config.d.ts +8 -0
- package/dist/sequence.d.ts +2 -0
- package/dist/templates.d.ts +13 -0
- package/dist/transforms/add-boot-step.d.ts +3 -0
- package/dist/transforms/add-env-key.d.ts +29 -0
- package/dist/transforms/add-import.d.ts +6 -0
- package/dist/transforms/add-route.d.ts +3 -0
- package/dist/transforms/add-shared.d.ts +3 -0
- package/dist/transforms/add-workflow-step.d.ts +7 -0
- package/dist/transforms/ast.d.ts +47 -0
- package/dist/transforms/index.d.ts +15 -0
- package/dist/transforms/wrap-provider.d.ts +6 -0
- package/dist/version.d.ts +8 -0
- package/package.json +61 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sanjeev Sharma
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,46 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @mfeflow/core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Part of [mfeflow](https://github.com/mfeflow/mfeflow), the React micro-frontend boilerplate and platform kit.
|
|
4
|
+
|
|
5
|
+
The engine behind the `mfeflow` CLI: loads and saves `mfeflow.json`, plans a change as data with a unified diff, applies it atomically with rollback, and resolves features. Pre-1.0: minor versions may change the API. Most people want the [`mfeflow`](https://www.npmjs.com/package/mfeflow) CLI instead.
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
pnpm add @mfeflow/core
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Docs: [mfeflow.github.io/mfeflow](https://mfeflow.github.io/mfeflow/).
|
|
12
|
+
|
|
13
|
+
MIT licensed.
|
|
14
|
+
|
|
15
|
+
## What a third-party feature may do
|
|
16
|
+
|
|
17
|
+
A feature from a namespaced registry (`@someone/name`) is untrusted. It is
|
|
18
|
+
declarative only and is checked against allowlists when it is loaded and again
|
|
19
|
+
when it is resolved into a plan. It can never cause code to run on apply,
|
|
20
|
+
install, commit or the next `mfeflow` command.
|
|
21
|
+
|
|
22
|
+
- **Files and transforms** write only under `src/`, `public/` or `docs/` of the
|
|
23
|
+
target, with plain lower-case ASCII names. Root files, dot folders, package
|
|
24
|
+
manager config (`package.json`, lockfiles, `.npmrc`, `.yarnrc*`,
|
|
25
|
+
`.pnpmfile.*`, `pnpm-workspace.yaml`), git hook managers, test and tool config
|
|
26
|
+
files and the local `features/` folder are refused. Paths are compared after
|
|
27
|
+
Unicode (NFC) and case folding, and names with trailing dots or spaces, `:`,
|
|
28
|
+
invisible characters or Windows device names are refused for every feature.
|
|
29
|
+
- **Scripts** are lower-case words joined by `:`. Lifecycle events, `pnpm:`,
|
|
30
|
+
`npm:`, `yarn:` and `bun:` prefixes and every `pre` or `post` name are refused.
|
|
31
|
+
- **Dependencies** take a semver range or a dist-tag. `file:`, `link:`, git,
|
|
32
|
+
URLs, `npm:` aliases, `workspace:` and `catalog:` are refused.
|
|
33
|
+
- **Imports** name a package, a relative path or an `@/` alias, never a URL.
|
|
34
|
+
- **`add-env-key`** schema text must be a call chain on `z`, `v` or `type` with
|
|
35
|
+
literal arguments; the plan writes it re-emitted from the parsed tree. Because
|
|
36
|
+
`.env.example` and root `env.ts` are outside the allowlist, third parties can
|
|
37
|
+
add runtime-config keys (under `src/` and `public/`) but not dotenv examples.
|
|
38
|
+
- **`add-workflow-step`** is refused: third-party features cannot edit CI.
|
|
39
|
+
|
|
40
|
+
For every feature, first-party included: a script or dependency that already
|
|
41
|
+
exists with another value is an E207 conflict in the plan, never an overwrite;
|
|
42
|
+
a first-party CI change shows as a warning in the plan summary; and `applyPlan`
|
|
43
|
+
checks that every target resolves, symlinks included, to inside the project
|
|
44
|
+
before writing anything. Its rollback restores files by temp file and rename,
|
|
45
|
+
and after a failed install it also puts back `package.json`, the workspace file
|
|
46
|
+
and the lockfiles the install touched (`node_modules` is not rolled back).
|
package/dist/apply.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type FsOps } from './fs.js';
|
|
2
|
+
import { type MfeflowProject } from './project.js';
|
|
3
|
+
import type { Plan } from './plan.js';
|
|
4
|
+
export interface InstallContext {
|
|
5
|
+
root: string;
|
|
6
|
+
plan: Plan;
|
|
7
|
+
}
|
|
8
|
+
/** Runs the package manager. Injected so tests and `--no-install` never touch the network. */
|
|
9
|
+
export type InstallStep = (context: InstallContext) => Promise<void>;
|
|
10
|
+
export interface ApplyOptions {
|
|
11
|
+
root: string;
|
|
12
|
+
/** The project as it is now. When given, `mfeflow.json` is updated in the same transaction. */
|
|
13
|
+
project?: MfeflowProject;
|
|
14
|
+
install?: InstallStep;
|
|
15
|
+
fs?: FsOps;
|
|
16
|
+
/** Check the plan against the files and stop before writing anything. */
|
|
17
|
+
dryRun?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface ApplyResult {
|
|
20
|
+
written: string[];
|
|
21
|
+
project: MfeflowProject | undefined;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Writes a plan to disk, all or nothing. Every target is checked against what
|
|
25
|
+
* the plan was made from, new content is staged next to its target and
|
|
26
|
+
* renamed into place, and on any failure (including the install step) the
|
|
27
|
+
* originals are put back.
|
|
28
|
+
*/
|
|
29
|
+
export declare function applyPlan(plan: Plan, options: ApplyOptions): Promise<ApplyResult>;
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every error the engine throws. Ranges: E1xx project and config, E2xx
|
|
3
|
+
* features, E3xx build and runtime, E4xx registry, E5xx upgrade.
|
|
4
|
+
*/
|
|
5
|
+
export declare const ERROR_CATALOGUE: {
|
|
6
|
+
readonly E101: {
|
|
7
|
+
readonly title: 'A shared singleton is missing from the shared list';
|
|
8
|
+
readonly fix: 'Run `mfeflow doctor --fix`, or add the package to the singletons list.';
|
|
9
|
+
};
|
|
10
|
+
readonly E102: {
|
|
11
|
+
readonly title: 'No mfeflow.json found';
|
|
12
|
+
readonly fix: 'Run the command inside an mfeflow project, pass --cwd, or create one with `npm create mfeflow@latest`.';
|
|
13
|
+
};
|
|
14
|
+
readonly E103: {
|
|
15
|
+
readonly title: 'mfeflow.json is not valid JSON';
|
|
16
|
+
readonly fix: 'Fix the syntax error, or restore the file from git.';
|
|
17
|
+
};
|
|
18
|
+
readonly E104: {
|
|
19
|
+
readonly title: 'mfeflow.json does not match its schema';
|
|
20
|
+
readonly fix: "Fix the fields listed above. The schema is at https://mfeflow.github.io/mfeflow/schema/mfeflow.json.";
|
|
21
|
+
};
|
|
22
|
+
readonly E105: {
|
|
23
|
+
readonly title: 'A path points outside the project';
|
|
24
|
+
readonly fix: 'Use paths relative to the project root, with forward slashes and no "..".';
|
|
25
|
+
};
|
|
26
|
+
readonly E106: {
|
|
27
|
+
readonly title: 'A file could not be read or written';
|
|
28
|
+
readonly fix: 'Check that the file exists and that you have permission to change it.';
|
|
29
|
+
};
|
|
30
|
+
readonly E107: {
|
|
31
|
+
readonly title: 'A command-line option is missing or not valid';
|
|
32
|
+
readonly fix: 'Run the command with --help to see the options and examples.';
|
|
33
|
+
};
|
|
34
|
+
readonly E108: {
|
|
35
|
+
readonly title: 'The target folder is not empty';
|
|
36
|
+
readonly fix: 'Pick a new or empty folder, or move what is in the way.';
|
|
37
|
+
};
|
|
38
|
+
readonly E109: {
|
|
39
|
+
readonly title: 'The preset could not be used';
|
|
40
|
+
readonly fix: 'Pass a JSON file or an https URL that holds an object of option names and values.';
|
|
41
|
+
};
|
|
42
|
+
readonly E110: {
|
|
43
|
+
readonly title: '--local does not point at a built mfeflow checkout';
|
|
44
|
+
readonly fix: 'Pass the folder of a mfeflow/mfeflow clone where `pnpm install && pnpm build` has run.';
|
|
45
|
+
};
|
|
46
|
+
readonly E111: {
|
|
47
|
+
readonly title: 'A workspace package has no version';
|
|
48
|
+
readonly fix: 'Add a "version" to its package.json (`mfeflow doctor --fix` writes 0.0.0). Without one Module Federation never shares the package and each app loads its own copy.';
|
|
49
|
+
};
|
|
50
|
+
readonly E112: {
|
|
51
|
+
readonly title: 'An app in mfeflow.json is not on disk';
|
|
52
|
+
readonly fix: 'Restore the folder, or remove the app from "apps" in mfeflow.json.';
|
|
53
|
+
};
|
|
54
|
+
readonly E113: {
|
|
55
|
+
readonly title: 'mfe.json is missing or invalid';
|
|
56
|
+
readonly fix: "Give the app a valid mfe.json (name, owner, shells, routes, exposes). The schema is at https://mfeflow.github.io/mfeflow/schema/mfe.json.";
|
|
57
|
+
};
|
|
58
|
+
readonly E114: {
|
|
59
|
+
readonly title: 'A registry entry is missing or invalid';
|
|
60
|
+
readonly fix: "Every remote needs registry/envs/<env>/<name>.json for dev, staging and prod (`mfeflow doctor --fix` writes the missing ones). The schema is at https://mfeflow.github.io/mfeflow/schema/registry-entry.json.";
|
|
61
|
+
};
|
|
62
|
+
readonly E115: {
|
|
63
|
+
readonly title: 'Two apps use the same port';
|
|
64
|
+
readonly fix: 'Give one of them another port in its rsbuild config and in "apps" in mfeflow.json.';
|
|
65
|
+
};
|
|
66
|
+
readonly E116: {
|
|
67
|
+
readonly title: 'Two remotes claim the same route, or an mfe.json disagrees with mfeflow.json';
|
|
68
|
+
readonly fix: 'Give each route to one remote, and keep the name in mfe.json equal to the app name.';
|
|
69
|
+
};
|
|
70
|
+
readonly E117: {
|
|
71
|
+
readonly title: 'A copied file changed since mfeflow wrote it';
|
|
72
|
+
readonly fix: 'Nothing is wrong if you changed it on purpose. `mfeflow upgrade` will show a diff before touching it.';
|
|
73
|
+
};
|
|
74
|
+
readonly E118: {
|
|
75
|
+
readonly title: '.env.example does not match the keys in env.ts';
|
|
76
|
+
readonly fix: 'List every key of the `server` schema in .env.example (`mfeflow add env-key KEY --scope server --schema "…"` keeps them together) and remove the ones env.ts no longer has.';
|
|
77
|
+
};
|
|
78
|
+
readonly E119: {
|
|
79
|
+
readonly title: 'A runtime-config file does not match the runtime-config schema';
|
|
80
|
+
readonly fix: 'Give every runtime-config.<env>.json the keys of src/config/schema.ts and nothing else, then run `pnpm check:runtime-config` to validate the values.';
|
|
81
|
+
};
|
|
82
|
+
readonly E120: {
|
|
83
|
+
readonly title: 'A PUBLIC_ variable would be baked into the bundle';
|
|
84
|
+
readonly fix: 'PUBLIC_* values are inlined at build time, so one build could no longer run in every environment. Put per-environment values in runtime-config.<env>.json instead (`mfeflow add env-key KEY --scope runtime --schema "…"`). PUBLIC_APP_NAME and PUBLIC_BUILD_VERSION are fine.';
|
|
85
|
+
};
|
|
86
|
+
readonly E121: {
|
|
87
|
+
readonly title: 'A server env key is not listed in turbo.json';
|
|
88
|
+
readonly fix: 'Add the key to "globalEnv" in turbo.json. Turborepo hides unlisted variables from tasks and replays a cached build without running env.ts, so a missing value would not fail the build.';
|
|
89
|
+
};
|
|
90
|
+
readonly E201: {
|
|
91
|
+
readonly title: 'Feature not found';
|
|
92
|
+
readonly fix: 'List what is available with `mfeflow features`, or check the spelling.';
|
|
93
|
+
};
|
|
94
|
+
readonly E202: {
|
|
95
|
+
readonly title: 'feature.json is invalid';
|
|
96
|
+
readonly fix: "Fix the fields listed above. The schema is at https://mfeflow.github.io/mfeflow/schema/feature.json.";
|
|
97
|
+
};
|
|
98
|
+
readonly E203: {
|
|
99
|
+
readonly title: 'A third-party feature uses something third-party features may not';
|
|
100
|
+
readonly fix: 'Third-party features are declarative: files under src/, public/ and docs/, registry dependencies, plain script names and the built-in transform ops (not add-workflow-step). Ask the author to remove it, or do not add this feature.';
|
|
101
|
+
};
|
|
102
|
+
readonly E204: {
|
|
103
|
+
readonly title: 'Unknown transform op';
|
|
104
|
+
readonly fix: 'Built-in ops: add-import, wrap-provider, add-shared, add-boot-step, add-env-key, add-route, add-workflow-step. If you need another, open an issue.';
|
|
105
|
+
};
|
|
106
|
+
readonly E205: {
|
|
107
|
+
readonly title: 'Feature conflicts with an installed feature';
|
|
108
|
+
readonly fix: 'Remove the conflicting feature first, or pick one of the two.';
|
|
109
|
+
};
|
|
110
|
+
readonly E206: {
|
|
111
|
+
readonly title: 'Feature needs another feature first';
|
|
112
|
+
readonly fix: 'Add the required feature in the same command, for example `mfeflow add tsconfig this-feature`.';
|
|
113
|
+
};
|
|
114
|
+
readonly E207: {
|
|
115
|
+
readonly title: 'The feature wants to create or change something that already exists';
|
|
116
|
+
readonly fix: 'Move or delete the existing file, script or dependency range, or edit it by hand and skip that part of the feature.';
|
|
117
|
+
};
|
|
118
|
+
readonly E208: {
|
|
119
|
+
readonly title: 'A file the feature edits is missing or not shaped as expected';
|
|
120
|
+
readonly fix: 'Restore the file from the template, or apply the change by hand using the diff.';
|
|
121
|
+
};
|
|
122
|
+
readonly E209: {
|
|
123
|
+
readonly title: 'A template could not be rendered';
|
|
124
|
+
readonly fix: 'Check the template for typos and that every option it uses is defined.';
|
|
125
|
+
};
|
|
126
|
+
readonly E210: {
|
|
127
|
+
readonly title: 'Registry namespace is not configured';
|
|
128
|
+
readonly fix: 'Add it to "registries" in mfeflow.json, for example { "@someone": "https://example.com/{name}.json" }.';
|
|
129
|
+
};
|
|
130
|
+
readonly E211: {
|
|
131
|
+
readonly title: 'Files changed since the plan was made';
|
|
132
|
+
readonly fix: 'Nothing was written. Run the command again to plan against the current files.';
|
|
133
|
+
};
|
|
134
|
+
readonly E212: {
|
|
135
|
+
readonly title: 'Applying the plan failed';
|
|
136
|
+
readonly fix: 'Everything already written was rolled back. Fix the cause above and run the command again.';
|
|
137
|
+
};
|
|
138
|
+
readonly E213: {
|
|
139
|
+
readonly title: 'Installing dependencies failed';
|
|
140
|
+
readonly fix: 'The files the plan wrote, and any package.json, lockfile or workspace file the install changed, were restored. node_modules is not rolled back, so run your package manager install once. Then check your network and package manager and run the command again.';
|
|
141
|
+
};
|
|
142
|
+
readonly E214: {
|
|
143
|
+
readonly title: 'A feature option is invalid';
|
|
144
|
+
readonly fix: 'Pass a value the feature accepts; `mfeflow features <name>` lists the options.';
|
|
145
|
+
};
|
|
146
|
+
readonly E215: {
|
|
147
|
+
readonly title: 'The app name cannot be used';
|
|
148
|
+
readonly fix: 'Use lowercase words of letters and digits joined by single underscores (order_history), starting with a letter, and a name no app has yet.';
|
|
149
|
+
};
|
|
150
|
+
readonly E301: {
|
|
151
|
+
readonly title: 'A singleton resolves to more than one version';
|
|
152
|
+
readonly fix: 'Pin one version of the package in every app (the pnpm catalog is the place), then reinstall. Two copies of React break hooks.';
|
|
153
|
+
};
|
|
154
|
+
readonly E302: {
|
|
155
|
+
readonly title: 'A remote is not compatible with its shell';
|
|
156
|
+
readonly fix: 'Align the remote and the shell on the same major of the package, or update the "platform" range in the remote\'s mfe.json.';
|
|
157
|
+
};
|
|
158
|
+
readonly E303: {
|
|
159
|
+
readonly title: 'A runtime plugin imports a shared module';
|
|
160
|
+
readonly fix: 'Runtime plugins run before the share scope exists. Keep them dependency free and report through `window`.';
|
|
161
|
+
};
|
|
162
|
+
readonly E304: {
|
|
163
|
+
readonly title: 'A bundle is over its budget';
|
|
164
|
+
readonly fix: 'Cut what the app ships on first load, or raise the budget in mfeflow.json ("budget") or mfe.json ("budgets") on purpose.';
|
|
165
|
+
};
|
|
166
|
+
readonly E305: {
|
|
167
|
+
readonly title: 'There is no build output to measure';
|
|
168
|
+
readonly fix: 'Run `pnpm build` first.';
|
|
169
|
+
};
|
|
170
|
+
readonly E306: {
|
|
171
|
+
readonly title: 'A dev server could not start';
|
|
172
|
+
readonly fix: "Free the port or change it in mfeflow.json and the app's rsbuild config, then run `mfeflow dev` again.";
|
|
173
|
+
};
|
|
174
|
+
readonly E401: {
|
|
175
|
+
readonly title: 'Could not fetch from the registry';
|
|
176
|
+
readonly fix: 'Check the URL in "registries" and your network, then try again.';
|
|
177
|
+
};
|
|
178
|
+
readonly E402: {
|
|
179
|
+
readonly title: 'The registry returned something that is not a feature';
|
|
180
|
+
readonly fix: 'Ask the registry owner to check the entry. It must be a feature.json document.';
|
|
181
|
+
};
|
|
182
|
+
};
|
|
183
|
+
export type ErrorCode = keyof typeof ERROR_CATALOGUE;
|
|
184
|
+
export interface MfeflowErrorOptions {
|
|
185
|
+
cause?: unknown;
|
|
186
|
+
/** Replaces the catalogue's fix when the situation has a more specific one. */
|
|
187
|
+
fix?: string;
|
|
188
|
+
}
|
|
189
|
+
/** An error with a stable code, a fix hint and a docs link. */
|
|
190
|
+
export declare class MfeflowError extends Error {
|
|
191
|
+
readonly code: `MFEFLOW_${ErrorCode}`;
|
|
192
|
+
readonly fix: string;
|
|
193
|
+
readonly docs: string;
|
|
194
|
+
constructor(code: ErrorCode, detail?: string, options?: MfeflowErrorOptions);
|
|
195
|
+
/** The three-line form the CLI prints. */
|
|
196
|
+
format(): string;
|
|
197
|
+
}
|
|
198
|
+
export declare function isMfeflowError(value: unknown): value is MfeflowError;
|
|
199
|
+
/** Message of anything thrown, for error details. */
|
|
200
|
+
export declare function messageOf(error: unknown): string;
|
|
201
|
+
/** True for a Node file system error with this code, such as ENOENT. */
|
|
202
|
+
export declare function hasErrno(error: unknown, code: string): boolean;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { listLocalFeatures, loadFeature, parseFeature, type LoadedFeature, type LoadOptions, } from './load.js';
|
|
2
|
+
export { fetchRemoteText, getHttps, readCappedText, registryUrl, type FetchLike, type FetchResponse, } from './remote.js';
|
|
3
|
+
export { resolveFeature, resolveOptions, type ResolveInput } from './resolve.js';
|
|
4
|
+
export { type Trust } from './policy.js';
|
|
5
|
+
export { isProtectedPath } from './write-policy.js';
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type FeatureManifest } from '@mfeflow/contracts';
|
|
2
|
+
import { type FsOps } from '../fs.js';
|
|
3
|
+
import { type FetchLike } from './remote.js';
|
|
4
|
+
import { type Trust } from './policy.js';
|
|
5
|
+
export interface LoadedFeature {
|
|
6
|
+
manifest: FeatureManifest;
|
|
7
|
+
trust: Trust;
|
|
8
|
+
/** Reads a `from` file of a local feature. Absent for remote features. */
|
|
9
|
+
readAsset?: (relative: string) => Promise<string>;
|
|
10
|
+
}
|
|
11
|
+
/** Parses and checks the text of a `feature.json`. Third-party manifests also pass the declarative-only policy. */
|
|
12
|
+
export declare function parseFeature(text: string, trust: Trust, label: string): FeatureManifest;
|
|
13
|
+
export interface LoadOptions {
|
|
14
|
+
/** Folder holding one sub-folder per first-party or local feature. */
|
|
15
|
+
featuresDir?: string;
|
|
16
|
+
/** From mfeflow.json `registries`. */
|
|
17
|
+
registries?: Record<string, string>;
|
|
18
|
+
fetch?: FetchLike;
|
|
19
|
+
/** The version to ask a registry for, when its URL template has `{version}`. */
|
|
20
|
+
version?: string;
|
|
21
|
+
fs?: FsOps;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Finds a feature by name. `@ns/name` comes from that namespace's registry and
|
|
25
|
+
* is third-party; a plain name comes from the local `features/` folder and is
|
|
26
|
+
* trusted. Trust follows where the bytes came from, never what the manifest says.
|
|
27
|
+
*/
|
|
28
|
+
export declare function loadFeature(name: string, options: LoadOptions): Promise<LoadedFeature>;
|
|
29
|
+
/** Names of the features in the local `features/` folder that have a feature.json. */
|
|
30
|
+
export declare function listLocalFeatures(featuresDir: string, ops?: FsOps): Promise<string[]>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type FeatureManifest, type TransformOp } from '@mfeflow/contracts';
|
|
2
|
+
/** Where a feature came from decides what it may do. */
|
|
3
|
+
export type Trust = 'first-party' | 'third-party';
|
|
4
|
+
/** Fields a feature from a namespaced registry may have. Everything else is an E203. */
|
|
5
|
+
export declare const THIRD_PARTY_KEYS: ReadonlySet<string>;
|
|
6
|
+
export declare function isModuleSpecifier(value: string): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* True for a semver range or a dist-tag, the only things a registry resolves.
|
|
9
|
+
* Everything else (file:, link:, git, URLs, `npm:` aliases, `workspace:`,
|
|
10
|
+
* `catalog:`, paths) is refused because it is parsed by the package manager.
|
|
11
|
+
*/
|
|
12
|
+
export declare function isRegistryRange(range: string): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Throws E203 unless `name` is a plain, namespaced script name that no package
|
|
15
|
+
* manager runs on its own. An allowlist on the shape, then the known hooks:
|
|
16
|
+
* lifecycle events, `pnpm:`/`npm:`/`yarn:`/`bun:` prefixes and every `pre` or
|
|
17
|
+
* `post` name, since what it wraps may be added to package.json later.
|
|
18
|
+
*/
|
|
19
|
+
export declare function assertPlainScript(name: string, label: string): void;
|
|
20
|
+
/** Keys check: runs on the raw JSON, before the schema, so "hooks" is named rather than ignored. */
|
|
21
|
+
export declare function assertAllowedKeys(value: Record<string, unknown>, label: string): void;
|
|
22
|
+
/** Unknown op names are their own error, so authors learn what the built-ins are. */
|
|
23
|
+
export declare function assertKnownOps(value: Record<string, unknown>, label: string): void;
|
|
24
|
+
/** What a validated third-party manifest may not contain. */
|
|
25
|
+
export declare function assertThirdPartyContent(manifest: FeatureManifest, label: string): void;
|
|
26
|
+
/** What a third-party transform op may carry beyond the shape the schema already checks. */
|
|
27
|
+
export declare function assertThirdPartyTransform(t: TransformOp, label: string): void;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** The part of `Response` the loader uses, so tests can pass a plain object. */
|
|
2
|
+
export interface FetchResponse {
|
|
3
|
+
ok: boolean;
|
|
4
|
+
status: number;
|
|
5
|
+
url?: string;
|
|
6
|
+
headers?: {
|
|
7
|
+
get(name: string): string | null;
|
|
8
|
+
};
|
|
9
|
+
/** When present the body is read from it, a chunk at a time, so the size cap holds. */
|
|
10
|
+
body?: ReadableStream<Uint8Array> | null;
|
|
11
|
+
text(): Promise<string>;
|
|
12
|
+
}
|
|
13
|
+
export type FetchLike = (url: string, init?: {
|
|
14
|
+
signal?: AbortSignal;
|
|
15
|
+
redirect?: 'follow' | 'manual';
|
|
16
|
+
}) => Promise<FetchResponse>;
|
|
17
|
+
/**
|
|
18
|
+
* The URL a namespaced feature lives at. `{name}` is the part after the slash and
|
|
19
|
+
* `{version}` the version asked for (`latest` when none), both URL-encoded.
|
|
20
|
+
*/
|
|
21
|
+
export declare function registryUrl(name: string, registries: Record<string, string>, version?: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* GETs `url`, following redirects by hand so one that leaves https is refused before it is
|
|
24
|
+
* requested. `code` is the error every failure here throws.
|
|
25
|
+
*/
|
|
26
|
+
export declare function getHttps(url: string, fetchImpl: FetchLike, code: 'E109' | 'E401'): Promise<FetchResponse>;
|
|
27
|
+
/** Downloads the feature.json text for `@ns/name`. */
|
|
28
|
+
export declare function fetchRemoteText(name: string, registries: Record<string, string>, fetchImpl?: FetchLike, version?: string): Promise<string>;
|
|
29
|
+
/**
|
|
30
|
+
* The body of `response` as text, refusing more than `maxBytes`: from the declared
|
|
31
|
+
* length first, then on the bytes actually streamed, so an oversized answer is
|
|
32
|
+
* cancelled instead of downloaded. `tooBig` builds the error to throw.
|
|
33
|
+
*/
|
|
34
|
+
export declare function readCappedText(response: FetchResponse, maxBytes: number, tooBig: () => Error): Promise<string>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type FeatureManifest } from '@mfeflow/contracts';
|
|
2
|
+
import type { PlanBuilder } from '../plan.js';
|
|
3
|
+
import type { MfeflowProject } from '../project.js';
|
|
4
|
+
import type { LoadedFeature } from './load.js';
|
|
5
|
+
export interface ResolveInput {
|
|
6
|
+
feature: LoadedFeature;
|
|
7
|
+
project: MfeflowProject;
|
|
8
|
+
builder: PlanBuilder;
|
|
9
|
+
/** Answers to the feature's prompts. Missing ones take the prompt's default. */
|
|
10
|
+
options?: Record<string, unknown>;
|
|
11
|
+
/** Features added in the same command, which count as installed for `requires` and `conflicts`. */
|
|
12
|
+
alsoAdding?: readonly string[];
|
|
13
|
+
}
|
|
14
|
+
/** Checks `value` against the prompts and fills in defaults. */
|
|
15
|
+
export declare function resolveOptions(manifest: FeatureManifest, given?: Record<string, unknown>): Record<string, string | boolean>;
|
|
16
|
+
/**
|
|
17
|
+
* Turns a loaded feature into edits on the plan builder: files, dependencies,
|
|
18
|
+
* scripts and transform ops. Nothing is written to disk and nothing is run.
|
|
19
|
+
*/
|
|
20
|
+
export declare function resolveFeature(input: ResolveInput): Promise<void>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AddEnvKeyOp } from '@mfeflow/contracts';
|
|
2
|
+
/**
|
|
3
|
+
* Validates the `schema` text of a third-party `add-env-key` and returns it
|
|
4
|
+
* re-emitted from the parsed tree. The text is pasted into the user's source
|
|
5
|
+
* and runs when the app starts, so only a call chain on `z`, `v` or `type`
|
|
6
|
+
* with literal arguments (and nested chains) is accepted, and the output is
|
|
7
|
+
* built from the tree, never from the input, so no comment or trailing text
|
|
8
|
+
* survives. With `form.string` (arktype's own syntax, `'string.url'`) a plain
|
|
9
|
+
* string literal is accepted too, re-emitted with its quotes escaped.
|
|
10
|
+
*/
|
|
11
|
+
export declare function sanitizeSchema(schema: string, label: string, form?: {
|
|
12
|
+
string?: boolean;
|
|
13
|
+
}): string;
|
|
14
|
+
/** `sanitizeSchema` for a single text or a per-validator map. */
|
|
15
|
+
export declare function sanitizeSchemas(schema: AddEnvKeyOp['schema'], label: string): AddEnvKeyOp['schema'];
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The form every path rule is checked on. macOS and Windows treat `.GIT`,
|
|
3
|
+
* `.git.` and a decomposed `é` as the same file as their plain spellings, so
|
|
4
|
+
* rules must never run on the raw text.
|
|
5
|
+
*/
|
|
6
|
+
export declare function policyKey(file: string): string;
|
|
7
|
+
/** Files that change how the package manager or git hooks run code. Third-party features cannot write them. */
|
|
8
|
+
export declare function isProtectedPath(file: string): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Rules for every feature, first-party or not. Runs on the folded path, with
|
|
11
|
+
* rejection (never stripping) of anything a file system could read as a
|
|
12
|
+
* different name.
|
|
13
|
+
*/
|
|
14
|
+
export declare function assertWritable(file: string): void;
|
|
15
|
+
/** True for a file a test runner or tool config loader would pick up by name or by folder. */
|
|
16
|
+
export declare function isTestFilePath(file: string): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Where a third-party feature may write: an allowlist, not a list of bad
|
|
19
|
+
* names. `relative` is the path inside the target; `joined` is the project
|
|
20
|
+
* path. Only plain ASCII names under `src/`, `public/` or `docs/` pass, so no
|
|
21
|
+
* dot folder, root file or package manager config can be named.
|
|
22
|
+
*/
|
|
23
|
+
export declare function assertThirdPartyPath(relative: string, joined: string): void;
|
package/dist/fs.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** The file system calls the engine makes, so tests can make one of them fail. */
|
|
2
|
+
export interface FsOps {
|
|
3
|
+
readFile(file: string): Promise<string>;
|
|
4
|
+
writeFile(file: string, content: string): Promise<void>;
|
|
5
|
+
rename(from: string, to: string): Promise<void>;
|
|
6
|
+
/** The path with every symlink resolved. Rejects with ENOENT when it does not exist. */
|
|
7
|
+
realpath(file: string): Promise<string>;
|
|
8
|
+
rm(file: string, options?: {
|
|
9
|
+
recursive?: boolean;
|
|
10
|
+
}): Promise<void>;
|
|
11
|
+
/** Returns the first directory it had to create, if any. */
|
|
12
|
+
mkdir(dir: string): Promise<string | undefined>;
|
|
13
|
+
/** Whether `file` is itself a symlink. False when it does not exist. */
|
|
14
|
+
isSymlink(file: string): Promise<boolean>;
|
|
15
|
+
/** Names of the sub-folders of `dir`. */
|
|
16
|
+
listDirs(dir: string): Promise<string[]>;
|
|
17
|
+
}
|
|
18
|
+
export declare const realFs: FsOps;
|
|
19
|
+
/** File content, or undefined when the file does not exist. */
|
|
20
|
+
export declare function readIfExists(ops: FsOps, file: string): Promise<string | undefined>;
|
|
21
|
+
/** A sibling path for staging writes; same directory so the rename never crosses file systems. */
|
|
22
|
+
export declare function tempSibling(file: string): string;
|
|
23
|
+
/** Writes through a temp file and a rename, so readers never see half a file. */
|
|
24
|
+
export declare function writeFileAtomic(ops: FsOps, file: string, content: string): Promise<void>;
|
package/dist/hash.d.ts
ADDED
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Name and version of this package. */
|
|
2
|
+
export declare function packageInfo(): {
|
|
3
|
+
name: string;
|
|
4
|
+
version: string;
|
|
5
|
+
};
|
|
6
|
+
export { ERROR_CATALOGUE, hasErrno, isMfeflowError, messageOf, MfeflowError, type ErrorCode, type MfeflowErrorOptions, } from './errors.js';
|
|
7
|
+
export { hashContent } from './hash.js';
|
|
8
|
+
export { inOrder } from './sequence.js';
|
|
9
|
+
export { realFs, type FsOps } from './fs.js';
|
|
10
|
+
export { findDiverged, findProjectRoot, loadProject, parseProject, PROJECT_FILE, recordPlan, saveProject, serializeProject, type DivergedFile, type MfeflowProject, } from './project.js';
|
|
11
|
+
export { emptyPlan, PlanBuilder, renderDiff, summarize, type ChangeAction, type DependencyAdd, type EnvKeyAdd, type FileChange, type Plan, type PlanBuilderOptions, type PlanFeature, type ReadFile, type ScriptAdd, } from './plan.js';
|
|
12
|
+
export { applyPlan, type ApplyOptions, type ApplyResult, type InstallContext, type InstallStep, } from './apply.js';
|
|
13
|
+
export { renderTemplate, substitute, type TemplateData } from './templates.js';
|
|
14
|
+
export { RUNTIME_CONFIG_FILE, runtimeConfigFiles } from './runtime-config.js';
|
|
15
|
+
export { applyTransform, dotenvKeys, schemaKeys, type SchemaKey } from './transforms/index.js';
|
|
16
|
+
export { fetchRemoteText, isProtectedPath, listLocalFeatures, loadFeature, parseFeature, getHttps, readCappedText, registryUrl, resolveFeature, resolveOptions, type FetchLike, type FetchResponse, type LoadedFeature, type LoadOptions, type ResolveInput, type Trust, } from './features/index.js';
|