@okfit/cli 0.1.0 → 0.2.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/errors.js ADDED
@@ -0,0 +1,173 @@
1
+ import { Runtime, Schema } from "effect";
2
+ import { ConfigIssueRenderer } from "@effected/cli";
3
+
4
+ //#region src/errors.ts
5
+ /**
6
+ * `--config <path>` named a path that does not exist. Every `ConfigResolver`
7
+ * has a `never` error channel and `explicitPath` absorbs a missing target
8
+ * into `Option.none()`, so a bad `--config` would otherwise fall through to
9
+ * the XDG file silently. The CLI checks it itself before building any layer
10
+ * (K-1, spec 4.1).
11
+ *
12
+ * @public
13
+ */
14
+ var ConfigPathNotFoundError = class extends Schema.TaggedError()("ConfigPathNotFoundError", { path: Schema.String }) {
15
+ [Runtime.errorExitCode] = 3;
16
+ get message() {
17
+ return `config path not found: ${this.path}`;
18
+ }
19
+ };
20
+ /**
21
+ * `okfit init` found one or more of its target paths already present (K-28).
22
+ * Carries the whole conflicting-path list so the renderer prints every one.
23
+ * `paths` are absolute; `renderFailure` relativises them to `cwd` (K-51).
24
+ *
25
+ * @public
26
+ */
27
+ var InitOverwriteError = class extends Schema.TaggedError()("InitOverwriteError", {
28
+ paths: Schema.Array(Schema.String),
29
+ cwd: Schema.String
30
+ }) {
31
+ [Runtime.errorExitCode] = 3;
32
+ get message() {
33
+ return "refusing to overwrite existing files";
34
+ }
35
+ };
36
+ /**
37
+ * V-9: `<id>` did not resolve to a verifiable concept. `reason` is
38
+ * `"not-a-concept"` (no such file, or an id that normalises to nothing),
39
+ * `"reserved"` (the id names `index.md` or `log.md`), or `"undecodable"`
40
+ * (a file exists but `bundle.diagnostics` names it — `diagnosticCode`
41
+ * carries which). `root` is the absolute bundle root, kept OFF `message`
42
+ * so K-51's relativisation rule has nothing to apply to and the human
43
+ * line and the JSON envelope's `message` are identical.
44
+ *
45
+ * @public
46
+ */
47
+ var VerifyConceptNotFoundError = class extends Schema.TaggedError()("VerifyConceptNotFoundError", {
48
+ id: Schema.String,
49
+ root: Schema.String,
50
+ reason: Schema.Literals([
51
+ "not-a-concept",
52
+ "reserved",
53
+ "undecodable"
54
+ ]),
55
+ diagnosticCode: Schema.optionalKey(Schema.String)
56
+ }) {
57
+ [Runtime.errorExitCode] = 3;
58
+ get message() {
59
+ const detail = this.diagnosticCode === void 0 ? this.reason : `${this.reason}: ${this.diagnosticCode}`;
60
+ return `no concept "${this.id}" in this bundle (${detail})`;
61
+ }
62
+ };
63
+ /**
64
+ * V-14: fail closed on a `verified` shape the classifier does not name.
65
+ * `shape` is one of `"alias"`, `"merge-key"`, `"scalar"`, `"empty"` (and,
66
+ * defensively, `"no-frontmatter"` or `"not-a-mapping"`, neither reachable
67
+ * for a concept that reached `bundle.concepts`). The file is never opened
68
+ * for writing when this is raised.
69
+ *
70
+ * @public
71
+ */
72
+ var VerifyUnsupportedFrontmatterError = class extends Schema.TaggedError()("VerifyUnsupportedFrontmatterError", {
73
+ id: Schema.String,
74
+ shape: Schema.String
75
+ }) {
76
+ [Runtime.errorExitCode] = 3;
77
+ get message() {
78
+ return `"${this.id}"'s verified value is a shape okfit verify cannot edit (${this.shape}); edit it by hand`;
79
+ }
80
+ };
81
+ const hasTag = (error, tag) => typeof error === "object" && error !== null && "_tag" in error && error._tag === tag;
82
+ /** The error's `message` when it has one as a string, else `String(error)`. */
83
+ const messageOf = (error) => {
84
+ if (typeof error === "object" && error !== null && "message" in error) {
85
+ const message = error.message;
86
+ if (typeof message === "string") return message;
87
+ }
88
+ return String(error);
89
+ };
90
+ /**
91
+ * A config file at a KNOWN path failed to parse or validate (K-46's own
92
+ * intent — "each class' message already names the offending path" does not
93
+ * hold for `@effected/config-file`'s `ConfigCodecError`, which carries no
94
+ * `path` field at all; this wraps it, and `ConfigValidationError`, with the
95
+ * path the caller already knows). `cause` is the original config-file error,
96
+ * preserved structurally, never stringified early.
97
+ *
98
+ * @public
99
+ */
100
+ var ConfigMalformedError = class extends Schema.TaggedError()("ConfigMalformedError", {
101
+ path: Schema.String,
102
+ cause: Schema.Defect()
103
+ }) {
104
+ [Runtime.errorExitCode] = 3;
105
+ get message() {
106
+ return `malformed config ${this.path}: ${messageOf(this.cause)}`;
107
+ }
108
+ };
109
+ /** `path` relative to `cwd` when it is under it, else the absolute path unchanged (K-51). */
110
+ const relativeToCwd = (path, cwd) => {
111
+ if (path === cwd) return ".";
112
+ const prefix = cwd.endsWith("/") ? cwd : `${cwd}/`;
113
+ return path.startsWith(prefix) ? path.slice(prefix.length) : path;
114
+ };
115
+ /**
116
+ * The `render` option for `CliRuntime.reportFailures` (K-30, K-46, K-51). One
117
+ * string per stderr line; `reportFailures` emits each through its own
118
+ * `Effect.logError`, which `CliLogger` routes to stderr.
119
+ *
120
+ * Rules, in order:
121
+ *
122
+ * 1. A `ShowHelp` (any `_tag === "ShowHelp"`) renders as `[]` — the empty
123
+ * array. `Command.runWith` has already rendered the help document and any
124
+ * parse errors before re-failing, so a second rendering here would print
125
+ * "Help requested" after the help text. This is why the K-30 remap in
126
+ * `bin.ts` can run ahead of `reportFailures` without corrupting `--help`
127
+ * output.
128
+ * 2. `ConfigPathNotFoundError` renders as its own `error: <message>` line;
129
+ * `InitOverwriteError` renders as the K-51 header, one two-space-indented
130
+ * relativised path per conflict, and the literal `Nothing was written.`.
131
+ * 3. `ConfigMalformedError` renders as its own `error: <message>` line —
132
+ * `error: malformed config <path>: <cause message>` — since it already
133
+ * carries the offending path (`config/layer.ts#provideConfig` wraps a
134
+ * `ConfigCodecError`/`ConfigValidationError` into this the moment the
135
+ * path is known).
136
+ * 4. A `ConfigValidationError` NOT already wrapped above (detected by
137
+ * `_tag`, since the peer is optional and this module keeps it a
138
+ * type-only import — this is the "path unknown" case, K-46) renders as
139
+ * `error: ${String(error)}` followed by one two-space-indented
140
+ * `ConfigIssueRenderer.render(error)` line per entry.
141
+ * 5. `VerifyConceptNotFoundError` and `VerifyUnsupportedFrontmatterError`
142
+ * each render as their own `error: <message>` line; both messages
143
+ * already name the concept id and what to do about it, and neither
144
+ * carries a filesystem path needing K-51 relativisation.
145
+ * 6. Everything else — core's `BundleRootNotFoundError`/`BundleReadError`/
146
+ * `BundleDepthExceededError`, config-file's other errors, `XdgEnvError`
147
+ * (the K-13 `HOME`-unset case) — renders as the single line
148
+ * `error: ${String(error)}`. Each of those
149
+ * classes' own `message` already names the offending path, which is all
150
+ * K-46 asserts.
151
+ *
152
+ * @public
153
+ */
154
+ const renderFailure = (error) => {
155
+ if (hasTag(error, "ShowHelp")) return [];
156
+ if (error instanceof ConfigPathNotFoundError) return [`error: ${error.message}`];
157
+ if (error instanceof ConfigMalformedError) return [`error: ${error.message}`];
158
+ if (error instanceof InitOverwriteError) return [
159
+ "error: refusing to overwrite existing files:",
160
+ ...error.paths.map((path) => ` ${relativeToCwd(path, error.cwd)}`),
161
+ "Nothing was written."
162
+ ];
163
+ if (error instanceof VerifyConceptNotFoundError) return [`error: ${error.message}`];
164
+ if (error instanceof VerifyUnsupportedFrontmatterError) return [`error: ${error.message}`];
165
+ if (hasTag(error, "ConfigValidationError")) {
166
+ const validationError = error;
167
+ return [`error: ${String(validationError)}`, ...ConfigIssueRenderer.render(validationError).map((line) => ` ${line}`)];
168
+ }
169
+ return [`error: ${String(error)}`];
170
+ };
171
+
172
+ //#endregion
173
+ export { ConfigMalformedError, ConfigPathNotFoundError, InitOverwriteError, VerifyConceptNotFoundError, VerifyUnsupportedFrontmatterError, renderFailure };