@effected/cli-kit 0.26.0 → 0.27.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/CliColor.js +9 -2
- package/CliExit.js +54 -1
- package/CliRuntime.js +2 -2
- package/CliTest.js +38 -4
- package/ConfigIssueRenderer.js +4 -5
- package/Fmt.js +2 -42
- package/README.md +0 -2
- package/TestTerminal.js +9 -3
- package/Token.js +3 -1
- package/index.d.ts +94 -10
- package/internal/HelpRouting.js +34 -6
- package/internal/format.js +2 -4
- package/package.json +6 -9
- package/testing.d.ts +41 -4
- package/testing.js +2 -2
- package/ui/CliUi.js +5 -3
- package/ui/UiTheme.js +20 -1
- package/ui.d.ts +27 -4
- package/ui.js +2 -2
package/CliColor.js
CHANGED
|
@@ -41,6 +41,10 @@ var CliColor = class CliColor {
|
|
|
41
41
|
* result to a `const` or the decision is re-read every time it is
|
|
42
42
|
* provided.
|
|
43
43
|
*
|
|
44
|
+
* `helpFlags` sets how subcommand help renders the flags a parent declared (`inheritedFlags`) and the
|
|
45
|
+
* program-wide built-ins (`globalFlags`): `"full"` (core's default), `"summary"` or `"none"`. It is passed to
|
|
46
|
+
* core's `defaultFormatter` beside the colour decision, so choosing a mode costs no colour.
|
|
47
|
+
*
|
|
44
48
|
* The `never` in its output does not mean it installs nothing.
|
|
45
49
|
* `CliOutput.Formatter` is a `Context.Reference`, whose key type is
|
|
46
50
|
* `never`, so this layer sets the formatter reference rather than
|
|
@@ -50,8 +54,11 @@ var CliColor = class CliColor {
|
|
|
50
54
|
*
|
|
51
55
|
* @public
|
|
52
56
|
*/
|
|
53
|
-
static formatterLayer = (overrides = {}) => Layer.unwrap(Effect.map(CliColor.enabled, (colors) => CliOutput.layer({
|
|
54
|
-
...CliOutput.defaultFormatter({
|
|
57
|
+
static formatterLayer = (overrides = {}, helpFlags) => Layer.unwrap(Effect.map(CliColor.enabled, (colors) => CliOutput.layer({
|
|
58
|
+
...CliOutput.defaultFormatter({
|
|
59
|
+
colors,
|
|
60
|
+
...helpFlags
|
|
61
|
+
}),
|
|
55
62
|
...overrides
|
|
56
63
|
})));
|
|
57
64
|
};
|
package/CliExit.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { Fmt } from "./Fmt.js";
|
|
1
2
|
import { isExitCode } from "./internal/isExitCode.js";
|
|
2
|
-
import { Context, Effect, Layer, MutableRef } from "effect";
|
|
3
|
+
import { Cause, Console, Context, Effect, Layer, MutableRef, Predicate } from "effect";
|
|
3
4
|
|
|
4
5
|
// src/CliExit.ts
|
|
5
6
|
/**
|
|
@@ -59,6 +60,58 @@ var CliExit = class CliExit extends Context.Service()("@effected/cli-kit/CliExit
|
|
|
59
60
|
const exit = yield* CliExit;
|
|
60
61
|
if (code > MutableRef.get(exit.code)) MutableRef.set(exit.code, code);
|
|
61
62
|
});
|
|
63
|
+
/**
|
|
64
|
+
* Write one line to stderr, record an exit code, and succeed: the shape of a
|
|
65
|
+
* hook-style command that must say why it exits non-zero without failing.
|
|
66
|
+
*
|
|
67
|
+
* @remarks
|
|
68
|
+
* This does **not** stop the program. It succeeds, so the caller must still
|
|
69
|
+
* return (or otherwise end its own flow) after calling it; finalizers and log
|
|
70
|
+
* sinks run as they would on any successful run, and `CliRuntime.main` turns
|
|
71
|
+
* the recorded code into the exit.
|
|
72
|
+
*
|
|
73
|
+
* The code is validated first, exactly as {@link CliExit.set} validates it, and
|
|
74
|
+
* an invalid code dies as the same defect before anything is written. The
|
|
75
|
+
* line goes through `Console.error`, never stdout, and is written as given:
|
|
76
|
+
* sanitize anything you do not control with `Fmt.sanitize` first. The
|
|
77
|
+
* highest code recorded during the run wins.
|
|
78
|
+
*/
|
|
79
|
+
static report = (code, line) => Effect.gen(function* () {
|
|
80
|
+
yield* CliExit.set(code);
|
|
81
|
+
yield* Console.error(line);
|
|
82
|
+
});
|
|
83
|
+
/**
|
|
84
|
+
* Report a failure cause as one stderr line, record the exit code the caller
|
|
85
|
+
* chooses for it, and succeed.
|
|
86
|
+
*
|
|
87
|
+
* @remarks
|
|
88
|
+
* The cause is squashed and the line is `<code> <tag>: <message>`, or
|
|
89
|
+
* `<code> <message>` when the error has no string `_tag`. The message comes
|
|
90
|
+
* from `Fmt.errorText` and is sanitized with `Fmt.sanitize` and its line breaks
|
|
91
|
+
* become a space, so a hostile error can neither throw, write an escape nor
|
|
92
|
+
* forge a second line. Reading `_tag` is guarded
|
|
93
|
+
* too: a getter that throws means no tag.
|
|
94
|
+
*
|
|
95
|
+
* An interrupt-only cause is not a failure to report: nothing is written or
|
|
96
|
+
* recorded and the effect re-interrupts. `codeOf` receives the squashed error
|
|
97
|
+
* and must return an integer in `0..255`; anything else dies as
|
|
98
|
+
* {@link CliExit.set} does, which is wiring.
|
|
99
|
+
*
|
|
100
|
+
* Like {@link CliExit.report} it does not stop the program: the caller still
|
|
101
|
+
* has to return.
|
|
102
|
+
*/
|
|
103
|
+
static reportCause = (cause, codeOf) => Cause.hasInterruptsOnly(cause) ? Effect.interrupt : Effect.suspend(() => {
|
|
104
|
+
const error = Cause.squash(cause);
|
|
105
|
+
const code = codeOf(error);
|
|
106
|
+
let tag;
|
|
107
|
+
try {
|
|
108
|
+
const read = Predicate.hasProperty(error, "_tag") ? error._tag : void 0;
|
|
109
|
+
tag = Predicate.isString(read) ? read : void 0;
|
|
110
|
+
} catch {}
|
|
111
|
+
const oneLine = (text) => Fmt.sanitize(text).replace(/\r?\n|\r/gu, " ");
|
|
112
|
+
const message = oneLine(Fmt.errorText(error));
|
|
113
|
+
return CliExit.report(code, tag === void 0 ? `${code} ${message}` : `${code} ${oneLine(tag)}: ${message}`);
|
|
114
|
+
});
|
|
62
115
|
};
|
|
63
116
|
|
|
64
117
|
export { CliExit };
|
package/CliRuntime.js
CHANGED
|
@@ -220,7 +220,7 @@ var CliRuntime = class CliRuntime {
|
|
|
220
220
|
const env = options.env === void 0 ? void 0 : CliEnv.layer(options.env);
|
|
221
221
|
const envLog = options.env?.log;
|
|
222
222
|
const logger = options.logger ?? (env === void 0 || envLog === void 0 ? CliLogger.layer() : CliLog.layer(envLog).pipe(Layer.provide(env), Layer.provide(Layer.provideMerge(options.platform.pipe(Layer.provide(platformLogLayer(envLog, options.env?.audienceEnvVar))), envBuildLogLayer(envLog, options.env?.audienceEnvVar))), Layer.catchCause(() => CliLogger.layer(envLog.logger))));
|
|
223
|
-
const inside = env === void 0 ? Layer.empty : Layer.mergeAll(CliColor.formatterLayer(options.env?.formatter), Layer.effectDiscard(Effect.gen(function* () {
|
|
223
|
+
const inside = env === void 0 ? Layer.empty : Layer.mergeAll(CliColor.formatterLayer(options.env?.formatter, options.env?.helpFlags), Layer.effectDiscard(Effect.gen(function* () {
|
|
224
224
|
const { spans, invalid } = yield* readSpans(options.env?.spans, options.env?.spansEnvVar);
|
|
225
225
|
if (invalid !== void 0) yield* Effect.logWarning(invalid).pipe(Effect.provideService(Logger.CurrentLoggers, /* @__PURE__ */ new Set([makeCliLogger(envLog?.logger)])));
|
|
226
226
|
yield* refreshFailureTarget(void 0, {
|
|
@@ -231,7 +231,7 @@ var CliRuntime = class CliRuntime {
|
|
|
231
231
|
}))).pipe(Layer.provideMerge(env));
|
|
232
232
|
const carried = options.distribution === void 0 ? program : withCarrier(program, options.distribution);
|
|
233
233
|
const run = Effect.gen(function* () {
|
|
234
|
-
yield* options.helpOnUsageError === "stderr" ? routeHelpOnUsageError(carried) : carried;
|
|
234
|
+
yield* options.helpOnUsageError === "stderr" ? routeHelpOnUsageError(carried, options.env?.formatter, options.env?.helpFlags) : carried;
|
|
235
235
|
const exit = yield* CliExit;
|
|
236
236
|
const code = MutableRef.get(exit.code);
|
|
237
237
|
if (!isExitCode(code)) return yield* Effect.die(/* @__PURE__ */ new Error(`CliRuntime.main: CliExit code must be an integer 0..255, received ${code}`));
|
package/CliTest.js
CHANGED
|
@@ -1,8 +1,35 @@
|
|
|
1
1
|
import { Fmt } from "./Fmt.js";
|
|
2
|
-
import { Duration, Effect, Fiber, FileSystem, Option, Path, Stream, SubscriptionRef } from "effect";
|
|
2
|
+
import { Duration, Effect, Fiber, FileSystem, Option, Path, Schema, Stream, SubscriptionRef } from "effect";
|
|
3
3
|
import { ChildProcess, ChildProcessSpawner } from "effect/process";
|
|
4
4
|
|
|
5
5
|
// src/CliTest.ts
|
|
6
|
+
/**
|
|
7
|
+
* A run ended otherwise than {@link RunOptions.expectExit} required.
|
|
8
|
+
*
|
|
9
|
+
* @remarks
|
|
10
|
+
* Audience: the person reading a failed test. The message holds what the child did and its stderr, so a suite that
|
|
11
|
+
* only cares about the happy path needs no hand-rolled helper to see why it did not get it.
|
|
12
|
+
*
|
|
13
|
+
* @public
|
|
14
|
+
*/
|
|
15
|
+
var CliTestFailure = class extends Schema.TaggedError()("CliTestFailure", {
|
|
16
|
+
expected: Schema.Number,
|
|
17
|
+
exitCode: Schema.NullOr(Schema.Number),
|
|
18
|
+
signal: Schema.NullOr(Schema.String),
|
|
19
|
+
stderr: Schema.String
|
|
20
|
+
}) {
|
|
21
|
+
/**
|
|
22
|
+
* What was expected, what happened, and the child's stderr.
|
|
23
|
+
*
|
|
24
|
+
* @remarks
|
|
25
|
+
* Assigning to it is ignored: a runner or logger that rewrites `error.message` must not make this error throw,
|
|
26
|
+
* which a getter-only property does in strict mode.
|
|
27
|
+
*/
|
|
28
|
+
get message() {
|
|
29
|
+
return `expected exit ${this.expected}, but the bin ${this.signal === null ? `exited with code ${this.exitCode}` : `was ended by ${this.signal}`}\nstderr:\n${this.stderr}`;
|
|
30
|
+
}
|
|
31
|
+
set message(_value) {}
|
|
32
|
+
};
|
|
6
33
|
/** The complete lines of `captured` from `offset`, and the rest too once it has ended; with the offset after them. */
|
|
7
34
|
const linesFrom = (offset, captured) => {
|
|
8
35
|
const lines = [];
|
|
@@ -108,7 +135,7 @@ var CliTest = class {
|
|
|
108
135
|
};
|
|
109
136
|
});
|
|
110
137
|
/**
|
|
111
|
-
* Run `execPath bin ...args`; a non-zero exit is returned, never failed.
|
|
138
|
+
* Run `execPath bin ...args`; a non-zero exit is returned, never failed, unless `expectExit` is set.
|
|
112
139
|
*
|
|
113
140
|
* @remarks
|
|
114
141
|
* `stdin` is never left as an inherited open pipe: when omitted or `""`
|
|
@@ -126,11 +153,18 @@ var CliTest = class {
|
|
|
126
153
|
text(handle.stderr),
|
|
127
154
|
endingOf(handle.exitCode)
|
|
128
155
|
], { concurrency: "unbounded" });
|
|
129
|
-
|
|
156
|
+
const result = {
|
|
130
157
|
...ending,
|
|
131
158
|
stdout,
|
|
132
159
|
stderr
|
|
133
160
|
};
|
|
161
|
+
if (options.expectExit !== void 0 && result.exitCode !== options.expectExit) return yield* new CliTestFailure({
|
|
162
|
+
expected: options.expectExit,
|
|
163
|
+
exitCode: result.exitCode,
|
|
164
|
+
signal: result.signal,
|
|
165
|
+
stderr
|
|
166
|
+
});
|
|
167
|
+
return result;
|
|
134
168
|
}));
|
|
135
169
|
};
|
|
136
170
|
/**
|
|
@@ -203,4 +237,4 @@ var CliTest = class {
|
|
|
203
237
|
};
|
|
204
238
|
};
|
|
205
239
|
|
|
206
|
-
export { CliTest };
|
|
240
|
+
export { CliTest, CliTestFailure };
|
package/ConfigIssueRenderer.js
CHANGED
|
@@ -13,10 +13,9 @@ import { formatIssueWithHints } from "./internal/format.js";
|
|
|
13
13
|
* value is the diagnostic: printing only the message tells a user their config
|
|
14
14
|
* is invalid without saying which value is wrong or how it is shaped.
|
|
15
15
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* for it.
|
|
16
|
+
* The parameter is {@link ConfigIssueSource}, a structural type, so this module
|
|
17
|
+
* names nothing from `@effected/config-file` and the package is not a peer. A
|
|
18
|
+
* real `ConfigValidationError` is assignable to it.
|
|
20
19
|
*
|
|
21
20
|
* @example
|
|
22
21
|
* ```ts
|
|
@@ -45,7 +44,7 @@ var ConfigIssueRenderer = class {
|
|
|
45
44
|
* hands you and because `issue` is typed `Schema.Defect`, which every call
|
|
46
45
|
* site would otherwise have to reach into. Inside
|
|
47
46
|
* `Effect.catchTag("ConfigValidationError", …)` the error is already this
|
|
48
|
-
*
|
|
47
|
+
* shape.
|
|
49
48
|
*
|
|
50
49
|
* An unknown key the schema has a near match for at the same path ends in a
|
|
51
50
|
* hint, `unknown key at theme.colour (did you mean "color"?)`, drawn from the
|
package/Fmt.js
CHANGED
|
@@ -1,40 +1,9 @@
|
|
|
1
1
|
import { displayWidth, graphemes, isEscapeOnly, stripAnsi } from "./internal/displayWidth.js";
|
|
2
2
|
import { Duration, Option } from "effect";
|
|
3
3
|
import { ErrorText } from "@effected/kit";
|
|
4
|
+
import { EditDistance } from "@effected/source";
|
|
4
5
|
|
|
5
6
|
// src/Fmt.ts
|
|
6
|
-
/**
|
|
7
|
-
* The Levenshtein distance between `a` and `b` when it is at most `limit`, and `limit + 1` otherwise.
|
|
8
|
-
*
|
|
9
|
-
* Only the diagonal band of the matrix within `limit` of it is computed (a cell outside it is past `limit` already),
|
|
10
|
-
* over two rows reused in turn, and a row whose every cell is past `limit` ends the walk, since no later row can come
|
|
11
|
-
* back under it: the cost is the length of `a` times `2 * limit + 1`.
|
|
12
|
-
*/
|
|
13
|
-
const boundedDistance = (a, b, limit) => {
|
|
14
|
-
const over = limit + 1;
|
|
15
|
-
if (Math.abs(a.length - b.length) > limit) return over;
|
|
16
|
-
const n = b.length;
|
|
17
|
-
const at = (row, j) => row[j] ?? over;
|
|
18
|
-
let previous = Array.from({ length: n + 1 }, (_, j) => j <= limit ? j : over);
|
|
19
|
-
let current = new Array(n + 1).fill(over);
|
|
20
|
-
for (let i = 1; i <= a.length; i++) {
|
|
21
|
-
const lo = Math.max(1, i - limit);
|
|
22
|
-
const hi = Math.min(n, i + limit);
|
|
23
|
-
const edge = lo === 1 && i <= limit ? i : over;
|
|
24
|
-
current[lo - 1] = edge;
|
|
25
|
-
let rowMin = edge;
|
|
26
|
-
for (let j = lo; j <= hi; j++) {
|
|
27
|
-
const cost = a.charCodeAt(i - 1) === b.charCodeAt(j - 1) ? 0 : 1;
|
|
28
|
-
const cell = Math.min(at(previous, j) + 1, at(current, j - 1) + 1, at(previous, j - 1) + cost, over);
|
|
29
|
-
current[j] = cell;
|
|
30
|
-
if (cell < rowMin) rowMin = cell;
|
|
31
|
-
}
|
|
32
|
-
if (hi < n) current[hi + 1] = over;
|
|
33
|
-
if (rowMin > limit) return over;
|
|
34
|
-
[previous, current] = [current, previous];
|
|
35
|
-
}
|
|
36
|
-
return Math.min(at(previous, n), over);
|
|
37
|
-
};
|
|
38
7
|
const CONTROL = /[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]/g;
|
|
39
8
|
/**
|
|
40
9
|
* Text made safe to lay out and print: complete ANSI and OSC sequences removed, every remaining control character
|
|
@@ -319,16 +288,7 @@ var Fmt = class {
|
|
|
319
288
|
static suggest = (input, candidates, options) => {
|
|
320
289
|
const max = options?.maxDistance ?? 2;
|
|
321
290
|
if (!Number.isInteger(max) || max < 0) throw new RangeError(`Fmt.suggest: maxDistance must be a non-negative integer, received ${max}`);
|
|
322
|
-
|
|
323
|
-
let found = [];
|
|
324
|
-
for (const candidate of new Set(candidates)) {
|
|
325
|
-
const distance = boundedDistance(input, candidate, Math.min(max, best));
|
|
326
|
-
if (distance < best) {
|
|
327
|
-
best = distance;
|
|
328
|
-
found = [candidate];
|
|
329
|
-
} else if (distance === best) found.push(candidate);
|
|
330
|
-
}
|
|
331
|
-
return best <= max ? found : [];
|
|
291
|
+
return EditDistance.suggest(input, candidates, { maxDistance: max });
|
|
332
292
|
};
|
|
333
293
|
};
|
|
334
294
|
|
package/README.md
CHANGED
|
@@ -47,8 +47,6 @@ Optional peers:
|
|
|
47
47
|
npm install ink react @types/react
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
- **`@effected/config-file`**, only for `ConfigIssueRenderer`. It is imported as a type, so nothing at runtime reaches for it.
|
|
51
|
-
|
|
52
50
|
All `@effected/*` packages are ESM-only: the exports maps publish only `import` conditions, so `require()` fails with Node's `ERR_PACKAGE_PATH_NOT_EXPORTED`. Import from an ES module.
|
|
53
51
|
|
|
54
52
|
## Quick start
|
package/TestTerminal.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Effect, Layer, Option, Queue, Terminal } from "effect";
|
|
2
2
|
|
|
3
3
|
// src/TestTerminal.ts
|
|
4
|
+
const FORBIDDEN = "TestTerminal: this path must not read the terminal (forbidInput is set)";
|
|
4
5
|
/**
|
|
5
6
|
* A scripted `Terminal` for testing prompts and anything that reads the terminal.
|
|
6
7
|
*
|
|
@@ -19,7 +20,12 @@ var TestTerminal = class {
|
|
|
19
20
|
/**
|
|
20
21
|
* Build a test terminal.
|
|
21
22
|
*
|
|
22
|
-
* @
|
|
23
|
+
* @remarks
|
|
24
|
+
* With `forbidInput`, `readInput` and `readLine` die with a message saying this path must not read the terminal,
|
|
25
|
+
* where the default double hands back an empty queue (a stray read hangs) and quits `readLine`. `size`, `columns`,
|
|
26
|
+
* `rows`, `display` and `resize` behave as ever, so a "this path never prompts" platform keeps a real terminal size.
|
|
27
|
+
*
|
|
28
|
+
* @param options - the reported size, 80 by 24 by default; `forbidInput` to die on any input read
|
|
23
29
|
*/
|
|
24
30
|
static make = (options) => Effect.gen(function* () {
|
|
25
31
|
const queue = yield* Queue.unbounded();
|
|
@@ -47,11 +53,11 @@ var TestTerminal = class {
|
|
|
47
53
|
sizeSubscribers.delete(subscriber);
|
|
48
54
|
Queue.endUnsafe(subscriber);
|
|
49
55
|
})),
|
|
50
|
-
readInput: Effect.sync(() => {
|
|
56
|
+
readInput: options?.forbidInput === true ? Effect.die(/* @__PURE__ */ new Error(FORBIDDEN)) : Effect.sync(() => {
|
|
51
57
|
subscriptions++;
|
|
52
58
|
return queue;
|
|
53
59
|
}),
|
|
54
|
-
readLine: Effect.suspend(() => {
|
|
60
|
+
readLine: options?.forbidInput === true ? Effect.die(/* @__PURE__ */ new Error(FORBIDDEN)) : Effect.suspend(() => {
|
|
55
61
|
lines++;
|
|
56
62
|
return Effect.fail(new Terminal.QuitError({}));
|
|
57
63
|
}),
|
package/Token.js
CHANGED
|
@@ -34,7 +34,9 @@ var Token = class {
|
|
|
34
34
|
* @remarks
|
|
35
35
|
* An explicit style resolves to itself. A token name resolves to its override when `overrides` has one, else
|
|
36
36
|
* its default; a name that is not a token (including an `Object.prototype` member) resolves to the empty
|
|
37
|
-
* style.
|
|
37
|
+
* style. It is theme-blind: it knows only the default palette and the `overrides` you pass, never a mounted
|
|
38
|
+
* theme, so a component under `CliUi` that wants the screen's theme uses `useStyle`. This is the same resolution
|
|
39
|
+
* `CliTheme.paint` applies, which is what `StreamTheme.style` reports.
|
|
38
40
|
*
|
|
39
41
|
* @param token - a token name or an explicit style
|
|
40
42
|
* @param overrides - styles that replace the default of a token, as `CliThemeOptions.tokens` does
|
package/index.d.ts
CHANGED
|
@@ -2,7 +2,6 @@ import { Array as Array$1, Cause, Context, Duration, Effect, FileSystem, Layer,
|
|
|
2
2
|
import { Audience, AudienceKind, ColorLevel, CurrentRuntimeEnv, RuntimeEnv, TerminalEnv } from "@effected/env";
|
|
3
3
|
import { CliError, CliOutput, Command, Flag, Param, Prompt } from "effect/cli";
|
|
4
4
|
import { Distribution } from "@effected/kit";
|
|
5
|
-
import { ConfigValidationError } from "@effected/config-file";
|
|
6
5
|
declare const Cancelled_base: Schema.Class<Cancelled, Schema.TaggedStruct<"Cancelled", {
|
|
7
6
|
readonly reason: Schema.Literals<readonly ["escape", "interrupt"]>;
|
|
8
7
|
}>, import("effect/Cause").YieldableError>;
|
|
@@ -183,6 +182,20 @@ export declare class CliAudience {
|
|
|
183
182
|
readonly renderErrors?: boolean | undefined;
|
|
184
183
|
}) => Effect.Effect<void, Exclude<E | CliError.UserError, Terminal.QuitError> | CliError.CliError, Exclude<R, Audience> | Audience | Command.Environment>;
|
|
185
184
|
}
|
|
185
|
+
/**
|
|
186
|
+
* How subcommand help renders flags the command does not own, as core's `CliOutput.defaultFormatter` takes them.
|
|
187
|
+
*
|
|
188
|
+
* @remarks
|
|
189
|
+
* `inheritedFlags` covers flags a parent command shares with its subcommands (for example `CliAudience.flags()`);
|
|
190
|
+
* `globalFlags` covers the built-ins such as `--help` and `--version`. Each is `"full"` (core's default, every flag
|
|
191
|
+
* listed), `"summary"` (a one-line pointer) or `"none"`.
|
|
192
|
+
*
|
|
193
|
+
* @public
|
|
194
|
+
*/
|
|
195
|
+
interface CliHelpFlags {
|
|
196
|
+
readonly inheritedFlags?: "full" | "summary" | "none" | undefined;
|
|
197
|
+
readonly globalFlags?: "full" | "summary" | "none" | undefined;
|
|
198
|
+
}
|
|
186
199
|
/**
|
|
187
200
|
* Whether a CLI's output should carry ANSI colour, decided once and shared by
|
|
188
201
|
* everything that renders — help text, error output, and any rendered result.
|
|
@@ -221,6 +234,10 @@ export declare class CliColor {
|
|
|
221
234
|
* result to a `const` or the decision is re-read every time it is
|
|
222
235
|
* provided.
|
|
223
236
|
*
|
|
237
|
+
* `helpFlags` sets how subcommand help renders the flags a parent declared (`inheritedFlags`) and the
|
|
238
|
+
* program-wide built-ins (`globalFlags`): `"full"` (core's default), `"summary"` or `"none"`. It is passed to
|
|
239
|
+
* core's `defaultFormatter` beside the colour decision, so choosing a mode costs no colour.
|
|
240
|
+
*
|
|
224
241
|
* The `never` in its output does not mean it installs nothing.
|
|
225
242
|
* `CliOutput.Formatter` is a `Context.Reference`, whose key type is
|
|
226
243
|
* `never`, so this layer sets the formatter reference rather than
|
|
@@ -230,7 +247,7 @@ export declare class CliColor {
|
|
|
230
247
|
*
|
|
231
248
|
* @public
|
|
232
249
|
*/
|
|
233
|
-
static readonly formatterLayer: (overrides?: Partial<CliOutput.Formatter
|
|
250
|
+
static readonly formatterLayer: (overrides?: Partial<CliOutput.Formatter>, helpFlags?: CliHelpFlags) => Layer.Layer<never, never, Stdio.Stdio>;
|
|
234
251
|
}
|
|
235
252
|
/**
|
|
236
253
|
* The symbols a theme draws with.
|
|
@@ -368,7 +385,9 @@ export declare class Token {
|
|
|
368
385
|
* @remarks
|
|
369
386
|
* An explicit style resolves to itself. A token name resolves to its override when `overrides` has one, else
|
|
370
387
|
* its default; a name that is not a token (including an `Object.prototype` member) resolves to the empty
|
|
371
|
-
* style.
|
|
388
|
+
* style. It is theme-blind: it knows only the default palette and the `overrides` you pass, never a mounted
|
|
389
|
+
* theme, so a component under `CliUi` that wants the screen's theme uses `useStyle`. This is the same resolution
|
|
390
|
+
* `CliTheme.paint` applies, which is what `StreamTheme.style` reports.
|
|
372
391
|
*
|
|
373
392
|
* @param token - a token name or an explicit style
|
|
374
393
|
* @param overrides - styles that replace the default of a token, as `CliThemeOptions.tokens` does
|
|
@@ -2048,6 +2067,17 @@ interface CliEnvOptions {
|
|
|
2048
2067
|
* sets; this is the way to keep a method of your own. Methods you omit keep the coloured defaults.
|
|
2049
2068
|
*/
|
|
2050
2069
|
readonly formatter?: Partial<CliOutput.Formatter> | undefined;
|
|
2070
|
+
/**
|
|
2071
|
+
* How subcommand help renders flags it does not own, passed to core's `CliOutput.defaultFormatter` beside the
|
|
2072
|
+
* colour decision. Only `CliRuntime.main` reads this.
|
|
2073
|
+
*
|
|
2074
|
+
* @remarks
|
|
2075
|
+
* `inheritedFlags` covers flags a parent command shares with its subcommands (for example `CliAudience.flags()`);
|
|
2076
|
+
* `globalFlags` covers the built-ins such as `--help` and `--version`. Each is `"full"` (core's default, every flag
|
|
2077
|
+
* listed), `"summary"` (a one-line pointer) or `"none"`. Setting a mode here keeps the kit's colour decision, which
|
|
2078
|
+
* supplying your own `formatHelpDoc` through {@link CliEnvOptions.formatter} would lose.
|
|
2079
|
+
*/
|
|
2080
|
+
readonly helpFlags?: CliHelpFlags | undefined;
|
|
2051
2081
|
/**
|
|
2052
2082
|
* How an absolute path is shown: a function, or `{ relativeTo: root }` to show a path under `root` relative to it.
|
|
2053
2083
|
* The identity by default.
|
|
@@ -2259,6 +2289,44 @@ export declare class CliExit extends CliExit_base {
|
|
|
2259
2289
|
* cell, and the failure's own exit code (or the `exitCode` fallback) wins.
|
|
2260
2290
|
*/
|
|
2261
2291
|
static readonly set: (code: number) => Effect.Effect<void, never, CliExit>;
|
|
2292
|
+
/**
|
|
2293
|
+
* Write one line to stderr, record an exit code, and succeed: the shape of a
|
|
2294
|
+
* hook-style command that must say why it exits non-zero without failing.
|
|
2295
|
+
*
|
|
2296
|
+
* @remarks
|
|
2297
|
+
* This does **not** stop the program. It succeeds, so the caller must still
|
|
2298
|
+
* return (or otherwise end its own flow) after calling it; finalizers and log
|
|
2299
|
+
* sinks run as they would on any successful run, and `CliRuntime.main` turns
|
|
2300
|
+
* the recorded code into the exit.
|
|
2301
|
+
*
|
|
2302
|
+
* The code is validated first, exactly as {@link CliExit.set} validates it, and
|
|
2303
|
+
* an invalid code dies as the same defect before anything is written. The
|
|
2304
|
+
* line goes through `Console.error`, never stdout, and is written as given:
|
|
2305
|
+
* sanitize anything you do not control with `Fmt.sanitize` first. The
|
|
2306
|
+
* highest code recorded during the run wins.
|
|
2307
|
+
*/
|
|
2308
|
+
static readonly report: (code: number, line: string) => Effect.Effect<void, never, CliExit>;
|
|
2309
|
+
/**
|
|
2310
|
+
* Report a failure cause as one stderr line, record the exit code the caller
|
|
2311
|
+
* chooses for it, and succeed.
|
|
2312
|
+
*
|
|
2313
|
+
* @remarks
|
|
2314
|
+
* The cause is squashed and the line is `<code> <tag>: <message>`, or
|
|
2315
|
+
* `<code> <message>` when the error has no string `_tag`. The message comes
|
|
2316
|
+
* from `Fmt.errorText` and is sanitized with `Fmt.sanitize` and its line breaks
|
|
2317
|
+
* become a space, so a hostile error can neither throw, write an escape nor
|
|
2318
|
+
* forge a second line. Reading `_tag` is guarded
|
|
2319
|
+
* too: a getter that throws means no tag.
|
|
2320
|
+
*
|
|
2321
|
+
* An interrupt-only cause is not a failure to report: nothing is written or
|
|
2322
|
+
* recorded and the effect re-interrupts. `codeOf` receives the squashed error
|
|
2323
|
+
* and must return an integer in `0..255`; anything else dies as
|
|
2324
|
+
* {@link CliExit.set} does, which is wiring.
|
|
2325
|
+
*
|
|
2326
|
+
* Like {@link CliExit.report} it does not stop the program: the caller still
|
|
2327
|
+
* has to return.
|
|
2328
|
+
*/
|
|
2329
|
+
static readonly reportCause: <E>(cause: Cause.Cause<E>, codeOf: (error: unknown) => number) => Effect.Effect<void, never, CliExit>;
|
|
2262
2330
|
}
|
|
2263
2331
|
/**
|
|
2264
2332
|
* The protocol an error class implements to say how a failure is shown: a method under this key that returns the
|
|
@@ -2942,6 +3010,11 @@ interface MainOptions<RP, EP> extends ReportFailuresOptions {
|
|
|
2942
3010
|
* `formatVersion`); a Formatter the platform provides is shadowed by
|
|
2943
3011
|
* `main`'s, so pass it through `env.formatter` as well.
|
|
2944
3012
|
*
|
|
3013
|
+
* The installed Formatter paints by stdout's terminal check. When `env` is set and stderr's colour decision
|
|
3014
|
+
* differs from stdout's, a usage error's help is rendered with `formatHelpDoc`, `formatError` and `formatErrors` rebuilt from
|
|
3015
|
+
* core's default formatter under stderr's decision (and `env.helpFlags`), so those methods follow stderr
|
|
3016
|
+
* unless given through `env.formatter`, which always wins. Every other method is left as installed.
|
|
3017
|
+
*
|
|
2945
3018
|
* Two cases keep help on stdout even under `"stderr"`. A `CliOutput`
|
|
2946
3019
|
* Presenter provided inside `program` replaces the routing. And with
|
|
2947
3020
|
* `Command.runWith`'s `renderErrors: false` core presents the help as
|
|
@@ -3179,6 +3252,18 @@ export declare class CliRuntime {
|
|
|
3179
3252
|
static reported<E extends Error>(error: E, exitCode?: number): E;
|
|
3180
3253
|
static reported(error: unknown, exitCode?: number): Error;
|
|
3181
3254
|
}
|
|
3255
|
+
/**
|
|
3256
|
+
* What {@link ConfigIssueRenderer.render} reads: anything that may carry an `issue` tree.
|
|
3257
|
+
*
|
|
3258
|
+
* @remarks
|
|
3259
|
+
* A `@effected/config-file` `ConfigValidationError` is assignable to it, as is any
|
|
3260
|
+
* other value with an optional `issue` property.
|
|
3261
|
+
*
|
|
3262
|
+
* @public
|
|
3263
|
+
*/
|
|
3264
|
+
interface ConfigIssueSource {
|
|
3265
|
+
readonly issue?: unknown;
|
|
3266
|
+
}
|
|
3182
3267
|
/**
|
|
3183
3268
|
* Turns a `@effected/config-file` `ConfigValidationError` into one line per rejected value.
|
|
3184
3269
|
*
|
|
@@ -3191,10 +3276,9 @@ export declare class CliRuntime {
|
|
|
3191
3276
|
* value is the diagnostic: printing only the message tells a user their config
|
|
3192
3277
|
* is invalid without saying which value is wrong or how it is shaped.
|
|
3193
3278
|
*
|
|
3194
|
-
*
|
|
3195
|
-
*
|
|
3196
|
-
*
|
|
3197
|
-
* for it.
|
|
3279
|
+
* The parameter is {@link ConfigIssueSource}, a structural type, so this module
|
|
3280
|
+
* names nothing from `@effected/config-file` and the package is not a peer. A
|
|
3281
|
+
* real `ConfigValidationError` is assignable to it.
|
|
3198
3282
|
*
|
|
3199
3283
|
* @example
|
|
3200
3284
|
* ```ts
|
|
@@ -3223,7 +3307,7 @@ export declare class ConfigIssueRenderer {
|
|
|
3223
3307
|
* hands you and because `issue` is typed `Schema.Defect`, which every call
|
|
3224
3308
|
* site would otherwise have to reach into. Inside
|
|
3225
3309
|
* `Effect.catchTag("ConfigValidationError", …)` the error is already this
|
|
3226
|
-
*
|
|
3310
|
+
* shape.
|
|
3227
3311
|
*
|
|
3228
3312
|
* An unknown key the schema has a near match for at the same path ends in a
|
|
3229
3313
|
* hint, `unknown key at theme.colour (did you mean "color"?)`, drawn from the
|
|
@@ -3235,7 +3319,7 @@ export declare class ConfigIssueRenderer {
|
|
|
3235
3319
|
* a guard before it is read, so a renderer on an error path never becomes the
|
|
3236
3320
|
* reason a program dies.
|
|
3237
3321
|
*/
|
|
3238
|
-
static readonly render: (error:
|
|
3322
|
+
static readonly render: (error: ConfigIssueSource) => ReadonlyArray<string>;
|
|
3239
3323
|
}
|
|
3240
3324
|
/**
|
|
3241
3325
|
* Options for {@link Fmt.truncate}.
|
|
@@ -3947,5 +4031,5 @@ export declare class SchemaIssueRenderer {
|
|
|
3947
4031
|
*/
|
|
3948
4032
|
static readonly render: (issue: unknown) => ReadonlyArray<string>;
|
|
3949
4033
|
}
|
|
3950
|
-
export type { AnnotationLevel, AnnotationOptions, AudienceFlagInput, Block, BlockOf, CliAudienceFlagsOptions, CliDocSource, CliEnvOptions, CliEnvServices, CliEnvTestOptions, CliEnvTestServices, CliExitShape, CliFailureOptions, CliFormatFlagOptions, CliFormatName, CliFormatOutput, CliFormatPrintOptions, CliLinksLinkerOptions, CliLinksOptions, CliLinksShape, CliLogFile, CliLogFileOptions, CliLogOptions, CliLogStatusOptions, CliLoggerOptions, CliMessageOptions, CliPromptFallbackOptions, CliPromptTarget, CliThemeOptions, CliThemeShape, CliThemeTestOptions, Column, CoreStatusName, Counter, CountsOptions, CountsRow, CountsTableOptions, DocPrintOptions, Document, EditorLinks, FailureDetails, GithubAnnotationProperties, GlyphSelectOptions, GlyphSet, Inline, InlineInput, InlineOf, LinkOptions, LinkTarget, ListOptions, MainOptions, NamedColor, OverflowOptions, PadOptions, PercentOptions, RenderContext, RenderContextOfOptions, RenderContextOptions, ReportFailuresOptions, RequiresAudienceFlags, StatusDef, StatusRef, StreamTheme, Style, SuggestOptions, TableOptions, TokenName, TreeInput, TreeNode, TruncateOptions };
|
|
4034
|
+
export type { AnnotationLevel, AnnotationOptions, AudienceFlagInput, Block, BlockOf, CliAudienceFlagsOptions, CliDocSource, CliEnvOptions, CliEnvServices, CliEnvTestOptions, CliEnvTestServices, CliExitShape, CliFailureOptions, CliFormatFlagOptions, CliFormatName, CliFormatOutput, CliFormatPrintOptions, CliHelpFlags, CliLinksLinkerOptions, CliLinksOptions, CliLinksShape, CliLogFile, CliLogFileOptions, CliLogOptions, CliLogStatusOptions, CliLoggerOptions, CliMessageOptions, CliPromptFallbackOptions, CliPromptTarget, CliThemeOptions, CliThemeShape, CliThemeTestOptions, Column, ConfigIssueSource, CoreStatusName, Counter, CountsOptions, CountsRow, CountsTableOptions, DocPrintOptions, Document, EditorLinks, FailureDetails, GithubAnnotationProperties, GlyphSelectOptions, GlyphSet, Inline, InlineInput, InlineOf, LinkOptions, LinkTarget, ListOptions, MainOptions, NamedColor, OverflowOptions, PadOptions, PercentOptions, RenderContext, RenderContextOfOptions, RenderContextOptions, ReportFailuresOptions, RequiresAudienceFlags, StatusDef, StatusRef, StreamTheme, Style, SuggestOptions, TableOptions, TokenName, TreeInput, TreeNode, TruncateOptions };
|
|
3951
4035
|
//# sourceMappingURL=index.d.ts.map
|
package/internal/HelpRouting.js
CHANGED
|
@@ -1,8 +1,29 @@
|
|
|
1
|
-
import { Console, Effect } from "effect";
|
|
1
|
+
import { Console, Effect, Option } from "effect";
|
|
2
|
+
import { TerminalEnv } from "@effected/env";
|
|
2
3
|
import { CliOutput } from "effect/cli";
|
|
3
4
|
|
|
4
5
|
// src/internal/HelpRouting.ts
|
|
5
6
|
/**
|
|
7
|
+
* The installed formatter with its colour-bearing methods (`formatHelpDoc`, `formatError`, `formatErrors`) rebuilt
|
|
8
|
+
* from core's default under `colors`, then `overrides` applied so they still win. Every other method of `current`,
|
|
9
|
+
* `formatVersion` included, is kept.
|
|
10
|
+
*
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
const stderrFormatter = (current, colors, overrides, helpFlags) => {
|
|
14
|
+
const { formatHelpDoc, formatError, formatErrors } = CliOutput.defaultFormatter({
|
|
15
|
+
colors,
|
|
16
|
+
...helpFlags
|
|
17
|
+
});
|
|
18
|
+
return {
|
|
19
|
+
...current,
|
|
20
|
+
formatHelpDoc,
|
|
21
|
+
formatError,
|
|
22
|
+
formatErrors,
|
|
23
|
+
...overrides
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
6
27
|
* Core's presenter, with an invalid invocation's help written to stderr beside
|
|
7
28
|
* its errors instead of stdout.
|
|
8
29
|
*
|
|
@@ -14,11 +35,18 @@ import { CliOutput } from "effect/cli";
|
|
|
14
35
|
* moves. An explicit `--help`, a bare group invocation and `--version` are
|
|
15
36
|
* other events, presented by core unchanged on stdout.
|
|
16
37
|
*/
|
|
17
|
-
const usageErrorsToStderr = { present: (event) => event._tag !== "InvalidInvocation" ? CliOutput.defaultPresenter.present(event) : Effect.gen(function* () {
|
|
38
|
+
const usageErrorsToStderr = (overrides, helpFlags) => ({ present: (event) => event._tag !== "InvalidInvocation" ? CliOutput.defaultPresenter.present(event) : Effect.gen(function* () {
|
|
18
39
|
const sink = yield* Console.Console;
|
|
19
40
|
const toStderr = Object.assign(Object.create(sink), { log: (...args) => sink.error(...args) });
|
|
20
|
-
yield*
|
|
21
|
-
|
|
41
|
+
const terminal = yield* Effect.serviceOption(TerminalEnv);
|
|
42
|
+
const current = yield* CliOutput.Formatter;
|
|
43
|
+
const colors = Option.map(terminal, (t) => ({
|
|
44
|
+
stdout: t.stdout.color !== "none",
|
|
45
|
+
stderr: t.stderr.color !== "none"
|
|
46
|
+
}));
|
|
47
|
+
const formatter = Option.isNone(colors) || colors.value.stderr === colors.value.stdout ? current : stderrFormatter(current, colors.value.stderr, overrides, helpFlags);
|
|
48
|
+
yield* CliOutput.defaultPresenter.present(event).pipe(Effect.provideService(Console.Console, toStderr), Effect.provideService(CliOutput.Formatter, formatter));
|
|
49
|
+
}) });
|
|
22
50
|
/**
|
|
23
51
|
* Run `program` so a help document printed together with parse errors goes to
|
|
24
52
|
* stderr, beside the errors, instead of stdout.
|
|
@@ -30,6 +58,6 @@ const usageErrorsToStderr = { present: (event) => event._tag !== "InvalidInvocat
|
|
|
30
58
|
*
|
|
31
59
|
* @internal
|
|
32
60
|
*/
|
|
33
|
-
const routeHelpOnUsageError = (program) => Effect.provideService(program, CliOutput.Presenter, usageErrorsToStderr);
|
|
61
|
+
const routeHelpOnUsageError = (program, overrides, helpFlags) => Effect.provideService(program, CliOutput.Presenter, usageErrorsToStderr(overrides, helpFlags));
|
|
34
62
|
|
|
35
|
-
export { routeHelpOnUsageError };
|
|
63
|
+
export { routeHelpOnUsageError, stderrFormatter };
|
package/internal/format.js
CHANGED
|
@@ -25,9 +25,7 @@ const formatter = SchemaIssue.makeFormatterStandardSchemaV1({ leafHook: (issue)
|
|
|
25
25
|
*
|
|
26
26
|
* @remarks
|
|
27
27
|
* Shared by `SchemaIssueRenderer` and `ConfigIssueRenderer` and imported by
|
|
28
|
-
* nothing else.
|
|
29
|
-
* `ConfigIssueRenderer` — the only export that references the optional
|
|
30
|
-
* `@effected/config-file` peer — can stay a module no other module imports.
|
|
28
|
+
* nothing else.
|
|
31
29
|
*
|
|
32
30
|
* **No walker of our own.** `makeFormatterStandardSchemaV1` already flattens the
|
|
33
31
|
* tree to `{ message, path }` entries, and `defaultLeafHook` covers every leaf
|
|
@@ -47,7 +45,7 @@ const formatIssue = (issue) => issueEntries(issue).map((entry) => entry.path.len
|
|
|
47
45
|
* @remarks
|
|
48
46
|
* Used by `ConfigIssueRenderer` only. The declared keys come from the issue tree itself: core's `UnexpectedKey` leaf
|
|
49
47
|
* carries the AST of the struct being decoded, whose property signatures are the keys the schema accepts at that
|
|
50
|
-
* path, so no schema is passed in
|
|
48
|
+
* path, so no schema is passed in. The hint is `Fmt.suggest`'s list,
|
|
51
49
|
* each key sanitised, and a line with no near match is exactly the line {@link formatIssue} draws.
|
|
52
50
|
*
|
|
53
51
|
* @internal
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effected/cli-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "The presentation boundary of an effect/cli program: audience-aware output, a document IR and renderers, editor links, failure reports and logging, plus opt-in Ink screens, widgets and a live view",
|
|
6
6
|
"keywords": [
|
|
@@ -59,22 +59,19 @@
|
|
|
59
59
|
"dependencies": {
|
|
60
60
|
"@effected/commands": "^0.20.0",
|
|
61
61
|
"@effected/github-commands": "^0.9.0",
|
|
62
|
-
"@effected/kit": "^0.
|
|
62
|
+
"@effected/kit": "^0.13.0",
|
|
63
|
+
"@effected/source": "^0.4.0"
|
|
63
64
|
},
|
|
64
65
|
"peerDependencies": {
|
|
65
|
-
"@effected/
|
|
66
|
-
"@effected/
|
|
67
|
-
"@effected/
|
|
68
|
-
"@effected/walker": "^0.25.0",
|
|
66
|
+
"@effected/env": "^0.11.0",
|
|
67
|
+
"@effected/glob": "^0.19.0",
|
|
68
|
+
"@effected/walker": "^0.25.1",
|
|
69
69
|
"@types/react": "^19.3.0",
|
|
70
70
|
"effect": "~4.1.0",
|
|
71
71
|
"ink": "^8.0.0",
|
|
72
72
|
"react": "^19.3.0"
|
|
73
73
|
},
|
|
74
74
|
"peerDependenciesMeta": {
|
|
75
|
-
"@effected/config-file": {
|
|
76
|
-
"optional": true
|
|
77
|
-
},
|
|
78
75
|
"@types/react": {
|
|
79
76
|
"optional": true
|
|
80
77
|
},
|
package/testing.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Duration, Effect, FileSystem, Layer, Path, PlatformError, Scope, Stream, Terminal } from "effect";
|
|
1
|
+
import { Duration, Effect, FileSystem, Layer, Path, PlatformError, Schema, Scope, Stream, Terminal } from "effect";
|
|
2
2
|
import { ChildProcess, ChildProcessSpawner } from "effect/process";
|
|
3
3
|
/**
|
|
4
4
|
* A hermetic temp directory minted by {@link CliTest.sandbox}, removed when its
|
|
@@ -45,6 +45,37 @@ interface RunOptions {
|
|
|
45
45
|
* child that ignores the signal would otherwise keep the scope from closing forever, and leave the process behind.
|
|
46
46
|
*/
|
|
47
47
|
readonly forceKillAfter?: Duration.Input | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* The exit code the run must end with. Any other ending, a signal included, fails with a {@link CliTestFailure}
|
|
50
|
+
* carrying the code, the signal and the child's stderr in its message. Omitted, a non-zero exit is data.
|
|
51
|
+
*/
|
|
52
|
+
readonly expectExit?: number | undefined;
|
|
53
|
+
}
|
|
54
|
+
declare const CliTestFailure_base: Schema.Class<CliTestFailure, Schema.TaggedStruct<"CliTestFailure", {
|
|
55
|
+
readonly expected: Schema.Number;
|
|
56
|
+
readonly exitCode: Schema.NullOr<Schema.Number>;
|
|
57
|
+
readonly signal: Schema.NullOr<Schema.String>;
|
|
58
|
+
readonly stderr: Schema.String;
|
|
59
|
+
}>, import("effect/Cause").YieldableError>;
|
|
60
|
+
/**
|
|
61
|
+
* A run ended otherwise than {@link RunOptions.expectExit} required.
|
|
62
|
+
*
|
|
63
|
+
* @remarks
|
|
64
|
+
* Audience: the person reading a failed test. The message holds what the child did and its stderr, so a suite that
|
|
65
|
+
* only cares about the happy path needs no hand-rolled helper to see why it did not get it.
|
|
66
|
+
*
|
|
67
|
+
* @public
|
|
68
|
+
*/
|
|
69
|
+
export declare class CliTestFailure extends CliTestFailure_base {
|
|
70
|
+
/**
|
|
71
|
+
* What was expected, what happened, and the child's stderr.
|
|
72
|
+
*
|
|
73
|
+
* @remarks
|
|
74
|
+
* Assigning to it is ignored: a runner or logger that rewrites `error.message` must not make this error throw,
|
|
75
|
+
* which a getter-only property does in strict mode.
|
|
76
|
+
*/
|
|
77
|
+
get message(): string;
|
|
78
|
+
set message(_value: string);
|
|
48
79
|
}
|
|
49
80
|
/**
|
|
50
81
|
* What a spawned bin did, as data: a non-zero exit is a result, not a failure.
|
|
@@ -132,7 +163,7 @@ export declare class CliTest {
|
|
|
132
163
|
readonly path: string;
|
|
133
164
|
}) => Effect.Effect<Sandbox, PlatformError.PlatformError, FileSystem.FileSystem | Path.Path | Scope.Scope>;
|
|
134
165
|
/**
|
|
135
|
-
* Run `execPath bin ...args`; a non-zero exit is returned, never failed.
|
|
166
|
+
* Run `execPath bin ...args`; a non-zero exit is returned, never failed, unless `expectExit` is set.
|
|
136
167
|
*
|
|
137
168
|
* @remarks
|
|
138
169
|
* `stdin` is never left as an inherited open pipe: when omitted or `""`
|
|
@@ -141,7 +172,7 @@ export declare class CliTest {
|
|
|
141
172
|
* default `"pipe"` stdio, which stays open until something writes to and
|
|
142
173
|
* ends it.
|
|
143
174
|
*/
|
|
144
|
-
static readonly run: (bin: string, args: ReadonlyArray<string>, options: RunOptions) => Effect.Effect<RunResult, PlatformError.PlatformError, ChildProcessSpawner.ChildProcessSpawner>;
|
|
175
|
+
static readonly run: (bin: string, args: ReadonlyArray<string>, options: RunOptions) => Effect.Effect<RunResult, PlatformError.PlatformError | CliTestFailure, ChildProcessSpawner.ChildProcessSpawner>;
|
|
145
176
|
/**
|
|
146
177
|
* Start `execPath bin ...args` and return at once with a handle on it: its output as lines while it runs, a signal,
|
|
147
178
|
* and its exit, for a long-running command (a `--follow`, a watch mode, a server) that {@link CliTest.run} would wait
|
|
@@ -235,11 +266,17 @@ export declare class TestTerminal {
|
|
|
235
266
|
/**
|
|
236
267
|
* Build a test terminal.
|
|
237
268
|
*
|
|
238
|
-
* @
|
|
269
|
+
* @remarks
|
|
270
|
+
* With `forbidInput`, `readInput` and `readLine` die with a message saying this path must not read the terminal,
|
|
271
|
+
* where the default double hands back an empty queue (a stray read hangs) and quits `readLine`. `size`, `columns`,
|
|
272
|
+
* `rows`, `display` and `resize` behave as ever, so a "this path never prompts" platform keeps a real terminal size.
|
|
273
|
+
*
|
|
274
|
+
* @param options - the reported size, 80 by 24 by default; `forbidInput` to die on any input read
|
|
239
275
|
*/
|
|
240
276
|
static readonly make: (options?: {
|
|
241
277
|
readonly columns?: number | undefined;
|
|
242
278
|
readonly rows?: number | undefined;
|
|
279
|
+
readonly forbidInput?: boolean | undefined;
|
|
243
280
|
}) => Effect.Effect<TestTerminalHandle>;
|
|
244
281
|
}
|
|
245
282
|
export type { KeyInput, RunOptions, RunResult, Sandbox, SpawnedBin, TestTerminalHandle };
|
package/testing.js
CHANGED
package/ui/CliUi.js
CHANGED
|
@@ -354,9 +354,11 @@ var CliUi = class CliUi {
|
|
|
354
354
|
* naming the peers, never a silent `otherwise`. Screens in sequence make a wizard: discover the defaults first,
|
|
355
355
|
* pass each as an `otherwise`, and a non-interactive run returns exactly them.
|
|
356
356
|
*
|
|
357
|
-
*
|
|
358
|
-
*
|
|
359
|
-
* `
|
|
357
|
+
* It has two forms. With a real `otherwise` (any value but `undefined`) the error type is `Cancelled` alone, since a
|
|
358
|
+
* non-interactive run answers and cannot fail. Without one, or with `{ otherwise: undefined }`, which counts as not
|
|
359
|
+
* given, `NotInteractive` stays in the error type even after the caller checked `CliInteractive`, since the type
|
|
360
|
+
* cannot know. Catch the tag and fail with `CliError.UserError` to exit as a usage error (`64` under
|
|
361
|
+
* `CliRuntime.main`). `A` is read from the screen, never from `otherwise`.
|
|
360
362
|
*
|
|
361
363
|
* As with `CliUi.run`, do not log while the screen is mounted: a line written to the terminal from elsewhere tears
|
|
362
364
|
* the frame.
|
package/ui/UiTheme.js
CHANGED
|
@@ -49,6 +49,25 @@ const useTheme = () => useScreen().theme;
|
|
|
49
49
|
*/
|
|
50
50
|
const useGlyphs = () => useScreen().glyphs;
|
|
51
51
|
/**
|
|
52
|
+
* The Ink `Text` props for `token`, resolved through the mounted screen's theme.
|
|
53
|
+
*
|
|
54
|
+
* @remarks
|
|
55
|
+
* The route a component takes to honour the theme: it sees a consumer's token overrides and `CliUiTest.styled`'s
|
|
56
|
+
* marker palette, and gives no styling props at colour `none`. `inkProps(Token.resolve(token))` resolves against the
|
|
57
|
+
* default palette and ignores the mounted theme, so under `CliUiTest.styled` it renders a raw colour where this
|
|
58
|
+
* renders the token.
|
|
59
|
+
*
|
|
60
|
+
* A React hook: call it from a component rendered inside a `CliUi.run` screen or a `UiProvider`.
|
|
61
|
+
*
|
|
62
|
+
* @param token - a theme token, or an explicit style
|
|
63
|
+
*
|
|
64
|
+
* @public
|
|
65
|
+
*/
|
|
66
|
+
const useStyle = (token) => {
|
|
67
|
+
const theme = useTheme();
|
|
68
|
+
return inkProps(theme.style(token), theme.color);
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
52
71
|
* Text painted with a theme token or style, through the mounted screen's theme.
|
|
53
72
|
*
|
|
54
73
|
* @remarks
|
|
@@ -144,4 +163,4 @@ const useTerminalSize = () => {
|
|
|
144
163
|
};
|
|
145
164
|
};
|
|
146
165
|
|
|
147
|
-
export { Styled, inkProps, useGlyphs, useTerminalSize, useTheme };
|
|
166
|
+
export { Styled, inkProps, useGlyphs, useStyle, useTerminalSize, useTheme };
|
package/ui.d.ts
CHANGED
|
@@ -660,9 +660,11 @@ export declare class CliUi {
|
|
|
660
660
|
* naming the peers, never a silent `otherwise`. Screens in sequence make a wizard: discover the defaults first,
|
|
661
661
|
* pass each as an `otherwise`, and a non-interactive run returns exactly them.
|
|
662
662
|
*
|
|
663
|
-
*
|
|
664
|
-
*
|
|
665
|
-
* `
|
|
663
|
+
* It has two forms. With a real `otherwise` (any value but `undefined`) the error type is `Cancelled` alone, since a
|
|
664
|
+
* non-interactive run answers and cannot fail. Without one, or with `{ otherwise: undefined }`, which counts as not
|
|
665
|
+
* given, `NotInteractive` stays in the error type even after the caller checked `CliInteractive`, since the type
|
|
666
|
+
* cannot know. Catch the tag and fail with `CliError.UserError` to exit as a usage error (`64` under
|
|
667
|
+
* `CliRuntime.main`). `A` is read from the screen, never from `otherwise`.
|
|
666
668
|
*
|
|
667
669
|
* As with `CliUi.run`, do not log while the screen is mounted: a line written to the terminal from elsewhere tears
|
|
668
670
|
* the frame.
|
|
@@ -670,7 +672,12 @@ export declare class CliUi {
|
|
|
670
672
|
* @param screen - the screen to show
|
|
671
673
|
* @param options - the non-interactive default, and whether to erase the last frame
|
|
672
674
|
*/
|
|
673
|
-
static readonly prompt:
|
|
675
|
+
static readonly prompt: {
|
|
676
|
+
<A>(screen: Screen<A>, options: CliUiPromptOptions<A> & {
|
|
677
|
+
readonly otherwise: Exclude<A, undefined>;
|
|
678
|
+
}): Effect.Effect<A, Cli.Cancelled, Cli.CliTheme>;
|
|
679
|
+
<A>(screen: Screen<A>, options?: CliUiPromptOptions<A>): Effect.Effect<A, Cli.Cancelled | Cli.NotInteractive, Cli.CliTheme>;
|
|
680
|
+
};
|
|
674
681
|
/**
|
|
675
682
|
* A fallback for `Flag.withFallbackPrompt` or `Argument.withFallbackPrompt` that shows a screen when the run is
|
|
676
683
|
* interactive: `CliPrompt.fallback` for screens.
|
|
@@ -1942,6 +1949,22 @@ export declare const useTheme: () => Cli.StreamTheme;
|
|
|
1942
1949
|
* @public
|
|
1943
1950
|
*/
|
|
1944
1951
|
export declare const useGlyphs: () => Cli.GlyphSet;
|
|
1952
|
+
/**
|
|
1953
|
+
* The Ink `Text` props for `token`, resolved through the mounted screen's theme.
|
|
1954
|
+
*
|
|
1955
|
+
* @remarks
|
|
1956
|
+
* The route a component takes to honour the theme: it sees a consumer's token overrides and `CliUiTest.styled`'s
|
|
1957
|
+
* marker palette, and gives no styling props at colour `none`. `inkProps(Token.resolve(token))` resolves against the
|
|
1958
|
+
* default palette and ignores the mounted theme, so under `CliUiTest.styled` it renders a raw colour where this
|
|
1959
|
+
* renders the token.
|
|
1960
|
+
*
|
|
1961
|
+
* A React hook: call it from a component rendered inside a `CliUi.run` screen or a `UiProvider`.
|
|
1962
|
+
*
|
|
1963
|
+
* @param token - a theme token, or an explicit style
|
|
1964
|
+
*
|
|
1965
|
+
* @public
|
|
1966
|
+
*/
|
|
1967
|
+
export declare const useStyle: (token: Cli.TokenName | Cli.Style) => InkTextProps;
|
|
1945
1968
|
/**
|
|
1946
1969
|
* Text painted with a theme token or style, through the mounted screen's theme.
|
|
1947
1970
|
*
|
package/ui.js
CHANGED
|
@@ -11,7 +11,7 @@ import { Toggle } from "./ui/Toggle.js";
|
|
|
11
11
|
import { UiKey } from "./ui/UiKey.js";
|
|
12
12
|
import { UiProvider } from "./ui/UiProvider.js";
|
|
13
13
|
import { UiStreams } from "./ui/UiStreams.js";
|
|
14
|
-
import { Styled, inkProps, useGlyphs, useTerminalSize, useTheme } from "./ui/UiTheme.js";
|
|
14
|
+
import { Styled, inkProps, useGlyphs, useStyle, useTerminalSize, useTheme } from "./ui/UiTheme.js";
|
|
15
15
|
import { Viewport } from "./ui/Viewport.js";
|
|
16
16
|
|
|
17
|
-
export { CliUi, Confirm, DocView, KeyHelp, KeyTable, MultiSelect, Select, Styled, Tabs, TextInput, Toggle, UiKey, UiProvider, UiStreams, Viewport, inkProps, useGlyphs, useKeys, useTerminalSize, useTheme };
|
|
17
|
+
export { CliUi, Confirm, DocView, KeyHelp, KeyTable, MultiSelect, Select, Styled, Tabs, TextInput, Toggle, UiKey, UiProvider, UiStreams, Viewport, inkProps, useGlyphs, useKeys, useStyle, useTerminalSize, useTheme };
|