@okfit/engine 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 +9 -0
- package/config/anchor.js +57 -0
- package/config/layer.js +129 -0
- package/config/resolve.js +67 -0
- package/context/run.js +35 -0
- package/errors.js +109 -0
- package/index.d.ts +957 -0
- package/index.js +18 -0
- package/init/scaffold.js +174 -0
- package/package.json +56 -0
- package/platform.js +32 -0
- package/render/context.js +83 -0
- package/render/exit.js +53 -0
- package/render/json.js +126 -0
- package/render/sort.js +46 -0
- package/render/sync.js +64 -0
- package/render/verify.js +47 -0
- package/sync/generated.js +111 -0
- package/sync/index.js +73 -0
- package/sync/log.js +127 -0
- package/sync/run.js +65 -0
- package/sync/write.js +48 -0
- package/tsdoc-metadata.json +11 -0
- package/validate/run.js +63 -0
- package/verify/locate.js +249 -0
- package/verify/run.js +106 -0
- package/verify/splice.js +75 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 C. Spencer Beggs
|
|
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
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# @okfit/engine
|
|
2
|
+
|
|
3
|
+
The shared engine behind [okfit](https://github.com/spencerbeggs/okfit): the platform layer, config discovery, and the `validate`, `verify`, `sync`, `init` and `context` programs, plus the JSON envelope contracts that `@okfit/cli` and `@okfit/mcp` both emit.
|
|
4
|
+
|
|
5
|
+
You probably want `@okfit/plugin` (both bins), `@okfit/cli` (the `okfit` bin) or `@okfit/mcp` (the `okfit-mcp` bin) instead. This package is what they are built from.
|
|
6
|
+
|
|
7
|
+
## License
|
|
8
|
+
|
|
9
|
+
[MIT](LICENSE)
|
package/config/anchor.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
//#region src/config/anchor.ts
|
|
2
|
+
/**
|
|
3
|
+
* The explicit `--config` anchor rule (C-8): the parent of `.config` "if the
|
|
4
|
+
* file sits in a `.config` directory, else the file's directory", with no
|
|
5
|
+
* exception for a custom name. Upstream's `ConfigResolver.explicitPath`
|
|
6
|
+
* reports no `dir` in its `match` by design, so this stays a hand-rolled,
|
|
7
|
+
* basename-based rule rather than reading `ConfigMatch.dir` — the one
|
|
8
|
+
* remaining path-tail string match in the CLI outside `init/scaffold.ts`
|
|
9
|
+
* (C1-4).
|
|
10
|
+
*/
|
|
11
|
+
const anchorForExplicit = (configPath, path) => {
|
|
12
|
+
const parent = path.dirname(configPath);
|
|
13
|
+
return path.basename(parent) === ".config" ? path.dirname(parent) : parent;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* K-12's project root, as amended by K-58 and C1-2, in order:
|
|
17
|
+
*
|
|
18
|
+
* 1. `pathArg`, if given. `Argument.path` has already resolved it absolute.
|
|
19
|
+
* 2. otherwise, if `--config` was given: `anchorForExplicit` applied to that
|
|
20
|
+
* path.
|
|
21
|
+
* 3. otherwise, if a config was discovered by the `"project"` resolver
|
|
22
|
+
* (`ConfigResolver.upwardWalk`, named `"project"` in `layer.ts`) AND it
|
|
23
|
+
* reported a `dir`: that `dir` verbatim — it is already the ancestor
|
|
24
|
+
* `upwardWalk` anchored the match against (the parent of `.config` for a
|
|
25
|
+
* `.config/okfit.toml` candidate, the candidate's own directory
|
|
26
|
+
* otherwise), so no path-tail test is needed here any more.
|
|
27
|
+
* 4. otherwise `cwd` — this covers every other resolver name (`"xdg"`,
|
|
28
|
+
* `"native"`, `"system"`) as defence in depth, a `"project"` match with no
|
|
29
|
+
* `dir` (should not happen, since `upwardWalk` always reports one), and
|
|
30
|
+
* no discovery at all.
|
|
31
|
+
*
|
|
32
|
+
* The CLI never probes for `.git` (K-12), even though
|
|
33
|
+
* `ConfigResolver.gitRoot` exists.
|
|
34
|
+
*
|
|
35
|
+
* @public
|
|
36
|
+
*/
|
|
37
|
+
const resolveProjectRoot = (input) => {
|
|
38
|
+
if (input.pathArg._tag === "Some") return input.pathArg.value;
|
|
39
|
+
if (input.explicitConfigPath._tag === "Some") return anchorForExplicit(input.explicitConfigPath.value, input.path);
|
|
40
|
+
if (input.discovered._tag === "Some") {
|
|
41
|
+
const discovered = input.discovered.value;
|
|
42
|
+
if (discovered.resolver === "project" && discovered.dir !== void 0) return discovered.dir;
|
|
43
|
+
}
|
|
44
|
+
return input.cwd;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* `<project root>/<bundle.path>`, absolute (K-2). `config` is the MERGED
|
|
48
|
+
* config, so `bundle.path` is `OkfitConfig.DEFAULTS.bundle.path` (`"okf"`)
|
|
49
|
+
* unless a file or profile overrode it. The bundle root is never what
|
|
50
|
+
* `[path]` names.
|
|
51
|
+
*
|
|
52
|
+
* @public
|
|
53
|
+
*/
|
|
54
|
+
const resolveBundleRoot = (projectRoot, config, path) => path.resolve(projectRoot, config.bundle?.path ?? "okf");
|
|
55
|
+
|
|
56
|
+
//#endregion
|
|
57
|
+
export { resolveBundleRoot, resolveProjectRoot };
|
package/config/layer.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { ConfigMalformedError, ConfigPathNotFoundError } from "../errors.js";
|
|
2
|
+
import { AppConfig } from "@effected/app";
|
|
3
|
+
import { ConfigCodecError, ConfigResolver, ConfigValidationError, MergeStrategy, TomlCodec } from "@effected/config-file";
|
|
4
|
+
import { OkfitConfig, OkfitConfigFile } from "@okfit/core";
|
|
5
|
+
import { Cause, Effect, FileSystem, Option, Result } from "effect";
|
|
6
|
+
|
|
7
|
+
//#region src/config/layer.ts
|
|
8
|
+
/**
|
|
9
|
+
* The `OkfitConfigFile` layer for one invocation (K-9 to K-11, K-57, C-6),
|
|
10
|
+
* now one `AppConfig.layer(OkfitConfigFile, ...)` call in both branches; only
|
|
11
|
+
* the chain options vary:
|
|
12
|
+
*
|
|
13
|
+
* - `explicitConfigPath` is `Some`: `resolvers: [ConfigResolver.explicitPath(path)]`
|
|
14
|
+
* and `xdg: false` — K-10's "only that path" rule, so the chain is exactly
|
|
15
|
+
* one resolver and no `systemEtc` tier is added either.
|
|
16
|
+
* - `explicitConfigPath` is `None`: `resolvers: [ConfigResolver.upwardWalk({ filenames, ... })]`
|
|
17
|
+
* carrying C-1's three per-directory candidates (`.okfit.toml`,
|
|
18
|
+
* `okfit.toml`, `.config/okfit.toml`, directory-major so a child's later
|
|
19
|
+
* candidate beats a parent's earlier one — C-2's ascent to the filesystem
|
|
20
|
+
* root is `upwardWalk`'s own default with no `stopAt`), named `"project"`
|
|
21
|
+
* so `resolve.ts`/`anchor.ts` can identify it without string-matching a
|
|
22
|
+
* path tail. `systemEtc` is C-5's `/etc` tier, present unless
|
|
23
|
+
* `systemConfigDir` overrides its root (tests only — no production call
|
|
24
|
+
* site sets it). `xdg` and `native` stay on their defaults (C-4), which is
|
|
25
|
+
* what puts personal defaults at `$XDG_CONFIG_HOME/okfit/config.toml` and
|
|
26
|
+
* the native probe behind it.
|
|
27
|
+
*
|
|
28
|
+
* `filename: "config.toml"` is the same in both branches — it is the XDG/
|
|
29
|
+
* native/system tiers' filename, unrelated to the C-1 project names above.
|
|
30
|
+
* `defaultPath` is `AppConfig.layer`'s own `XdgConfig.savePath(filename)`, so
|
|
31
|
+
* `save`/`update` do not fail with `ConfigDefaultPathMissingError`; nothing
|
|
32
|
+
* here sets it explicitly any more.
|
|
33
|
+
*
|
|
34
|
+
* `discoveryCwd` is `[path]` when given, else `process.cwd()` (K-2), passed
|
|
35
|
+
* straight through as `upwardWalk`'s own `cwd` so nothing in this module
|
|
36
|
+
* reads the process.
|
|
37
|
+
*
|
|
38
|
+
* `App.layer`, `AppStore` and `AppCache` appear nowhere, so no
|
|
39
|
+
* `store.db`/`cache.db` is ever created (K-9).
|
|
40
|
+
*
|
|
41
|
+
* @public
|
|
42
|
+
*/
|
|
43
|
+
const buildConfigLayer = (options) => Option.isSome(options.explicitConfigPath) ? AppConfig.layer(OkfitConfigFile, {
|
|
44
|
+
filename: "config.toml",
|
|
45
|
+
schema: OkfitConfig,
|
|
46
|
+
codec: TomlCodec,
|
|
47
|
+
strategy: MergeStrategy.firstMatch(),
|
|
48
|
+
resolvers: [ConfigResolver.explicitPath(options.explicitConfigPath.value)],
|
|
49
|
+
xdg: false
|
|
50
|
+
}) : AppConfig.layer(OkfitConfigFile, {
|
|
51
|
+
filename: "config.toml",
|
|
52
|
+
schema: OkfitConfig,
|
|
53
|
+
codec: TomlCodec,
|
|
54
|
+
strategy: MergeStrategy.firstMatch(),
|
|
55
|
+
resolvers: [ConfigResolver.upwardWalk({
|
|
56
|
+
filenames: [
|
|
57
|
+
".okfit.toml",
|
|
58
|
+
"okfit.toml",
|
|
59
|
+
".config/okfit.toml"
|
|
60
|
+
],
|
|
61
|
+
cwd: options.discoveryCwd,
|
|
62
|
+
name: "project"
|
|
63
|
+
})],
|
|
64
|
+
systemEtc: options.systemConfigDir === void 0 ? true : { dir: options.systemConfigDir }
|
|
65
|
+
});
|
|
66
|
+
/**
|
|
67
|
+
* The K-1 pre-flight and the provide, in that order and in one place: stat
|
|
68
|
+
* `explicitConfigPath` with `FileSystem.exists` and fail with
|
|
69
|
+
* `ConfigPathNotFoundError` BEFORE `buildConfigLayer` is called at all. This
|
|
70
|
+
* is why the CLI does not use `Command.provide(cmd, (input) => layer)`,
|
|
71
|
+
* which would construct the layer first (Judge notes 9) — here the stat
|
|
72
|
+
* runs as the first step of one `Effect.gen`, and `buildConfigLayer` is only
|
|
73
|
+
* reached, and only then actually run, on the step after it.
|
|
74
|
+
*
|
|
75
|
+
* `FileSystem.FileSystem.exists` is `(path: string) => Effect.Effect<boolean, PlatformError>`
|
|
76
|
+
* (`EF/FileSystem.ts:143-145`), not infallible, so
|
|
77
|
+
* `PlatformError.PlatformError` joins the error channel here (decision 7):
|
|
78
|
+
* an unusual stat failure (e.g. a permission error on a parent directory)
|
|
79
|
+
* renders through `renderFailure`'s catch-all rule rather than being
|
|
80
|
+
* uncatchable by the type checker.
|
|
81
|
+
*
|
|
82
|
+
* K-46/K-63 fix: a `ConfigCodecError`/`ConfigValidationError` from
|
|
83
|
+
* `buildConfigLayer`'s provided layer is wrapped into `ConfigMalformedError`
|
|
84
|
+
* whenever the failing path is known, so `renderFailure` can name it:
|
|
85
|
+
*
|
|
86
|
+
* - `ConfigCodecError` now carries its own `path: string | undefined`
|
|
87
|
+
* (config-file 0.7.0, `ConfigFile.discover` re-raises with `path` attached
|
|
88
|
+
* at every site that fed the codec a path it resolved) — used when
|
|
89
|
+
* present, so a malformed file found during DISCOVERY (no `--config`) now
|
|
90
|
+
* also wraps into `ConfigMalformedError` naming the candidate that failed,
|
|
91
|
+
* closing the K-63 gap. It falls back to `explicitConfigPath` only when
|
|
92
|
+
* the library's own `path` is `undefined` and `--config` was given; only
|
|
93
|
+
* when neither is known does the cause pass through unwrapped.
|
|
94
|
+
* - `ConfigValidationError` carries its own `path: Option<string>` — used
|
|
95
|
+
* when present (either branch), falling back to `explicitConfigPath` when
|
|
96
|
+
* the library's own `path` is `None` and `--config` was given.
|
|
97
|
+
*
|
|
98
|
+
* @public
|
|
99
|
+
*/
|
|
100
|
+
const provideConfig = (options) => (effect) => Effect.gen(function* () {
|
|
101
|
+
if (Option.isSome(options.explicitConfigPath)) {
|
|
102
|
+
const path = options.explicitConfigPath.value;
|
|
103
|
+
if (!(yield* (yield* FileSystem.FileSystem).exists(path))) return yield* Effect.fail(new ConfigPathNotFoundError({ path }));
|
|
104
|
+
}
|
|
105
|
+
return yield* effect.pipe(Effect.provide(buildConfigLayer(options)), Effect.catchCause((cause) => {
|
|
106
|
+
const found = Cause.findFail(cause);
|
|
107
|
+
if (Result.isSuccess(found)) {
|
|
108
|
+
const error = found.success.error;
|
|
109
|
+
if (error instanceof ConfigCodecError) {
|
|
110
|
+
const path = error.path !== void 0 ? error.path : Option.isSome(options.explicitConfigPath) ? options.explicitConfigPath.value : void 0;
|
|
111
|
+
if (path !== void 0) return Effect.fail(new ConfigMalformedError({
|
|
112
|
+
path,
|
|
113
|
+
cause: error
|
|
114
|
+
}));
|
|
115
|
+
}
|
|
116
|
+
if (error instanceof ConfigValidationError) {
|
|
117
|
+
const path = Option.isSome(options.explicitConfigPath) ? options.explicitConfigPath : error.path;
|
|
118
|
+
if (Option.isSome(path)) return Effect.fail(new ConfigMalformedError({
|
|
119
|
+
path: path.value,
|
|
120
|
+
cause: error
|
|
121
|
+
}));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return Effect.failCause(cause);
|
|
125
|
+
}));
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
//#endregion
|
|
129
|
+
export { buildConfigLayer, provideConfig };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { resolveBundleRoot, resolveProjectRoot } from "./anchor.js";
|
|
2
|
+
import { OKF_SPEC_VERSION, OkfitConfig, OkfitConfigFile } from "@okfit/core";
|
|
3
|
+
import { Console, Effect, Option, Path } from "effect";
|
|
4
|
+
import { Profiles } from "@okfit/profiles";
|
|
5
|
+
|
|
6
|
+
//#region src/config/resolve.ts
|
|
7
|
+
/**
|
|
8
|
+
* `OkfitConfig.DEFAULTS.bundle.profile` is `"software-project"` at runtime
|
|
9
|
+
* (the frozen literal always sets it), but `bundle` and `profile` are both
|
|
10
|
+
* `optionalKey` in the schema, so the type checker sees `string | undefined`
|
|
11
|
+
* two levels deep. The `?? "software-project"` fallback here is unreachable
|
|
12
|
+
* in practice; it exists only to satisfy `noUncheckedIndexedAccess`-style
|
|
13
|
+
* strictness without a non-null assertion. Formerly duplicated identically
|
|
14
|
+
* in `commands/validate.ts` and `commands/context.ts`; this is its one home
|
|
15
|
+
* now that both commands delegate to {@link resolveProjectConfig}.
|
|
16
|
+
*
|
|
17
|
+
* @public
|
|
18
|
+
*/
|
|
19
|
+
const DEFAULT_PROFILE_NAME = OkfitConfig.DEFAULTS.bundle?.profile ?? "software-project";
|
|
20
|
+
/**
|
|
21
|
+
* The config-resolution step byte-identical between `validate` and
|
|
22
|
+
* `context`'s handlers (A2 review finding): discover, pick `sources[0]`,
|
|
23
|
+
* default `{ extensions: {} }`, resolve the profile name and the K-4
|
|
24
|
+
* unknown-profile warning, merge `DEFAULTS < profile < file` (D-28), warn on
|
|
25
|
+
* an `okf_version` mismatch (K-15), then resolve the project and bundle
|
|
26
|
+
* roots (K-12). Every message string and the merge order are unchanged from
|
|
27
|
+
* the two commands' former inline copies.
|
|
28
|
+
*
|
|
29
|
+
* @public
|
|
30
|
+
*/
|
|
31
|
+
const resolveProjectConfig = (input) => Effect.gen(function* () {
|
|
32
|
+
const path = yield* Path.Path;
|
|
33
|
+
const discoveredSource = (yield* (yield* OkfitConfigFile).discover)[0];
|
|
34
|
+
const fileConfig = discoveredSource === void 0 ? { extensions: {} } : discoveredSource.value;
|
|
35
|
+
const profileName = fileConfig.bundle?.profile ?? DEFAULT_PROFILE_NAME;
|
|
36
|
+
const profile = profileName === "none" ? Option.none() : Profiles.get(profileName);
|
|
37
|
+
if (profileName !== "none" && Option.isNone(profile)) yield* Console.error(`warning: unknown profile "${profileName}"; continuing with defaults`);
|
|
38
|
+
const base = Option.match(profile, {
|
|
39
|
+
onNone: () => OkfitConfig.DEFAULTS,
|
|
40
|
+
onSome: (p) => OkfitConfig.merge(OkfitConfig.DEFAULTS, p.config)
|
|
41
|
+
});
|
|
42
|
+
const merged = OkfitConfig.merge(base, fileConfig);
|
|
43
|
+
if (merged.okf_version !== void 0 && merged.okf_version !== OKF_SPEC_VERSION) yield* Console.error(`warning: okf_version "${merged.okf_version}" does not match this okfit's spec version "${OKF_SPEC_VERSION}"; continuing`);
|
|
44
|
+
const discovered = discoveredSource === void 0 ? Option.none() : Option.some({
|
|
45
|
+
path: discoveredSource.path,
|
|
46
|
+
resolver: discoveredSource.resolver,
|
|
47
|
+
...discoveredSource.match?.dir !== void 0 ? { dir: discoveredSource.match.dir } : {}
|
|
48
|
+
});
|
|
49
|
+
const projectRoot = resolveProjectRoot({
|
|
50
|
+
pathArg: input.pathArg,
|
|
51
|
+
explicitConfigPath: input.explicitConfigPath,
|
|
52
|
+
discovered,
|
|
53
|
+
cwd: input.cwd,
|
|
54
|
+
path
|
|
55
|
+
});
|
|
56
|
+
return {
|
|
57
|
+
projectRoot,
|
|
58
|
+
bundleRoot: resolveBundleRoot(projectRoot, merged, path),
|
|
59
|
+
config: merged,
|
|
60
|
+
profile,
|
|
61
|
+
profileName,
|
|
62
|
+
discovered
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
//#endregion
|
|
67
|
+
export { DEFAULT_PROFILE_NAME, resolveProjectConfig };
|
package/context/run.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Effect, FileSystem, Path } from "effect";
|
|
2
|
+
|
|
3
|
+
//#region src/context/run.ts
|
|
4
|
+
/**
|
|
5
|
+
* The whole of `context`'s filesystem work: join `index.md` onto the bundle
|
|
6
|
+
* root and stat it. Loading a bundle and running conformance/lint checks
|
|
7
|
+
* over it never happen here — that is what makes `context` cheap enough
|
|
8
|
+
* for a hook to run on every session start and every in-bundle write
|
|
9
|
+
* (M-15).
|
|
10
|
+
*
|
|
11
|
+
* `"index.md"` is a literal here, not read from the profile's layout: the
|
|
12
|
+
* spec fixes the reserved filename regardless of profile, and a
|
|
13
|
+
* profile-less merge (`profile = "none"`) has no layout to read from at
|
|
14
|
+
* all — `context` behaves identically with and without a profile.
|
|
15
|
+
*
|
|
16
|
+
* `exists` is `(path) => Effect.Effect<boolean, PlatformError>`, so an
|
|
17
|
+
* unreadable parent directory would otherwise fail the command; here it is
|
|
18
|
+
* absorbed to `false`, because "the hook could not stat index.md" and
|
|
19
|
+
* "index.md is not there" are the same fact from a consumer's point of
|
|
20
|
+
* view.
|
|
21
|
+
*
|
|
22
|
+
* @public
|
|
23
|
+
*/
|
|
24
|
+
const runContext = (options) => Effect.gen(function* () {
|
|
25
|
+
const path = yield* Path.Path;
|
|
26
|
+
const fs = yield* FileSystem.FileSystem;
|
|
27
|
+
const indexPath = path.join(options.bundleRoot, "index.md");
|
|
28
|
+
return {
|
|
29
|
+
indexPath,
|
|
30
|
+
indexExists: yield* fs.exists(indexPath).pipe(Effect.orElseSucceed(() => false))
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
//#endregion
|
|
35
|
+
export { runContext };
|
package/errors.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { Runtime, Schema } from "effect";
|
|
2
|
+
|
|
3
|
+
//#region src/errors.ts
|
|
4
|
+
/**
|
|
5
|
+
* `--config <path>` named a path that does not exist. Every `ConfigResolver`
|
|
6
|
+
* has a `never` error channel and `explicitPath` absorbs a missing target
|
|
7
|
+
* into `Option.none()`, so a bad `--config` would otherwise fall through to
|
|
8
|
+
* the XDG file silently. The CLI checks it itself before building any layer
|
|
9
|
+
* (K-1, spec 4.1).
|
|
10
|
+
*
|
|
11
|
+
* @public
|
|
12
|
+
*/
|
|
13
|
+
var ConfigPathNotFoundError = class extends Schema.TaggedError()("ConfigPathNotFoundError", { path: Schema.String }) {
|
|
14
|
+
[Runtime.errorExitCode] = 3;
|
|
15
|
+
get message() {
|
|
16
|
+
return `config path not found: ${this.path}`;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* `okfit init` found one or more of its target paths already present (K-28).
|
|
21
|
+
* Carries the whole conflicting-path list so the renderer prints every one.
|
|
22
|
+
* `paths` are absolute; `renderFailure` relativises them to `cwd` (K-51).
|
|
23
|
+
*
|
|
24
|
+
* @public
|
|
25
|
+
*/
|
|
26
|
+
var InitOverwriteError = class extends Schema.TaggedError()("InitOverwriteError", {
|
|
27
|
+
paths: Schema.Array(Schema.String),
|
|
28
|
+
cwd: Schema.String
|
|
29
|
+
}) {
|
|
30
|
+
[Runtime.errorExitCode] = 3;
|
|
31
|
+
get message() {
|
|
32
|
+
return "refusing to overwrite existing files";
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* V-9: `<id>` did not resolve to a verifiable concept. `reason` is
|
|
37
|
+
* `"not-a-concept"` (no such file, or an id that normalises to nothing),
|
|
38
|
+
* `"reserved"` (the id names `index.md` or `log.md`), or `"undecodable"`
|
|
39
|
+
* (a file exists but `bundle.diagnostics` names it — `diagnosticCode`
|
|
40
|
+
* carries which). `root` is the absolute bundle root, kept OFF `message`
|
|
41
|
+
* so K-51's relativisation rule has nothing to apply to and the human
|
|
42
|
+
* line and the JSON envelope's `message` are identical.
|
|
43
|
+
*
|
|
44
|
+
* @public
|
|
45
|
+
*/
|
|
46
|
+
var VerifyConceptNotFoundError = class extends Schema.TaggedError()("VerifyConceptNotFoundError", {
|
|
47
|
+
id: Schema.String,
|
|
48
|
+
root: Schema.String,
|
|
49
|
+
reason: Schema.Literals([
|
|
50
|
+
"not-a-concept",
|
|
51
|
+
"reserved",
|
|
52
|
+
"undecodable"
|
|
53
|
+
]),
|
|
54
|
+
diagnosticCode: Schema.optionalKey(Schema.String)
|
|
55
|
+
}) {
|
|
56
|
+
[Runtime.errorExitCode] = 3;
|
|
57
|
+
get message() {
|
|
58
|
+
const detail = this.diagnosticCode === void 0 ? this.reason : `${this.reason}: ${this.diagnosticCode}`;
|
|
59
|
+
return `no concept "${this.id}" in this bundle (${detail})`;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* V-14: fail closed on a `verified` shape the classifier does not name.
|
|
64
|
+
* `shape` is one of `"alias"`, `"merge-key"`, `"scalar"`, `"empty"` (and,
|
|
65
|
+
* defensively, `"no-frontmatter"` or `"not-a-mapping"`, neither reachable
|
|
66
|
+
* for a concept that reached `bundle.concepts`). The file is never opened
|
|
67
|
+
* for writing when this is raised.
|
|
68
|
+
*
|
|
69
|
+
* @public
|
|
70
|
+
*/
|
|
71
|
+
var VerifyUnsupportedFrontmatterError = class extends Schema.TaggedError()("VerifyUnsupportedFrontmatterError", {
|
|
72
|
+
id: Schema.String,
|
|
73
|
+
shape: Schema.String
|
|
74
|
+
}) {
|
|
75
|
+
[Runtime.errorExitCode] = 3;
|
|
76
|
+
get message() {
|
|
77
|
+
return `"${this.id}"'s verified value is a shape okfit verify cannot edit (${this.shape}); edit it by hand`;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
/** The error's `message` when it has one as a string, else `String(error)`. */
|
|
81
|
+
const messageOf = (error) => {
|
|
82
|
+
if (typeof error === "object" && error !== null && "message" in error) {
|
|
83
|
+
const message = error.message;
|
|
84
|
+
if (typeof message === "string") return message;
|
|
85
|
+
}
|
|
86
|
+
return String(error);
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* A config file at a KNOWN path failed to parse or validate (K-46's own
|
|
90
|
+
* intent — "each class' message already names the offending path" does not
|
|
91
|
+
* hold for `@effected/config-file`'s `ConfigCodecError`, which carries no
|
|
92
|
+
* `path` field at all; this wraps it, and `ConfigValidationError`, with the
|
|
93
|
+
* path the caller already knows). `cause` is the original config-file error,
|
|
94
|
+
* preserved structurally, never stringified early.
|
|
95
|
+
*
|
|
96
|
+
* @public
|
|
97
|
+
*/
|
|
98
|
+
var ConfigMalformedError = class extends Schema.TaggedError()("ConfigMalformedError", {
|
|
99
|
+
path: Schema.String,
|
|
100
|
+
cause: Schema.Defect()
|
|
101
|
+
}) {
|
|
102
|
+
[Runtime.errorExitCode] = 3;
|
|
103
|
+
get message() {
|
|
104
|
+
return `malformed config ${this.path}: ${messageOf(this.cause)}`;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
//#endregion
|
|
109
|
+
export { ConfigMalformedError, ConfigPathNotFoundError, InitOverwriteError, VerifyConceptNotFoundError, VerifyUnsupportedFrontmatterError };
|