@effected/cli-kit 0.0.0-stage → 0.26.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.
Files changed (103) hide show
  1. package/Cancelled.js +43 -0
  2. package/CliAudience.js +177 -0
  3. package/CliColor.js +59 -0
  4. package/CliEnv.js +101 -0
  5. package/CliExit.js +64 -0
  6. package/CliFailure.js +287 -0
  7. package/CliFormat.js +124 -0
  8. package/CliInteractive.js +94 -0
  9. package/CliLinks.js +153 -0
  10. package/CliLog.js +367 -0
  11. package/CliLogger.js +91 -0
  12. package/CliMessage.js +79 -0
  13. package/CliPrompt.js +110 -0
  14. package/CliRuntime.js +254 -0
  15. package/CliTest.js +206 -0
  16. package/CliTheme.js +142 -0
  17. package/ConfigIssueRenderer.js +63 -0
  18. package/Doc.js +548 -0
  19. package/Fmt.js +335 -0
  20. package/GithubAnnotation.js +39 -0
  21. package/Glyphs.js +82 -0
  22. package/LICENSE +21 -0
  23. package/NotInteractive.js +41 -0
  24. package/README.md +215 -2
  25. package/Render.js +280 -0
  26. package/SchemaIssueRenderer.js +47 -0
  27. package/Status.js +165 -0
  28. package/TestTerminal.js +101 -0
  29. package/Token.js +68 -0
  30. package/index.d.ts +3951 -0
  31. package/index.js +27 -0
  32. package/internal/ExitRequested.js +21 -0
  33. package/internal/HelpRouting.js +35 -0
  34. package/internal/ansi.js +229 -0
  35. package/internal/autoFormat.js +33 -0
  36. package/internal/canPrompt.js +14 -0
  37. package/internal/counts.js +83 -0
  38. package/internal/diagnostics.js +31 -0
  39. package/internal/displayPath.js +46 -0
  40. package/internal/displayWidth.js +42 -0
  41. package/internal/failureTarget.js +195 -0
  42. package/internal/fallbackAnswer.js +17 -0
  43. package/internal/fileSink.js +61 -0
  44. package/internal/format.js +175 -0
  45. package/internal/inspect.js +40 -0
  46. package/internal/isExitCode.js +11 -0
  47. package/internal/layout.js +249 -0
  48. package/internal/linkScheme.js +29 -0
  49. package/internal/linkTarget.js +49 -0
  50. package/internal/logSafety.js +45 -0
  51. package/internal/renderAnsi.js +51 -0
  52. package/internal/renderDoc.js +322 -0
  53. package/internal/renderGithubLog.js +59 -0
  54. package/internal/renderMarkdown.js +367 -0
  55. package/internal/renderPlain.js +49 -0
  56. package/internal/scanAudience.js +105 -0
  57. package/internal/splitFrame.js +55 -0
  58. package/internal/terminalCapabilities.js +23 -0
  59. package/internal/wizardGate.js +17 -0
  60. package/package.json +90 -5
  61. package/testing.d.ts +246 -0
  62. package/testing.js +4 -0
  63. package/tsdoc-metadata.json +11 -0
  64. package/ui/CliUi.js +507 -0
  65. package/ui/CliUiDetached.js +36 -0
  66. package/ui/CliUiLive.js +436 -0
  67. package/ui/CliUiRouteStream.js +72 -0
  68. package/ui/Confirm.js +244 -0
  69. package/ui/DocView.js +76 -0
  70. package/ui/KeyHelp.js +61 -0
  71. package/ui/KeyTable.js +198 -0
  72. package/ui/MultiSelect.js +259 -0
  73. package/ui/Select.js +229 -0
  74. package/ui/Tabs.js +201 -0
  75. package/ui/TextInput.js +289 -0
  76. package/ui/Toggle.js +31 -0
  77. package/ui/UiKey.js +48 -0
  78. package/ui/UiProvider.js +62 -0
  79. package/ui/UiStreams.js +17 -0
  80. package/ui/UiTheme.js +147 -0
  81. package/ui/Viewport.js +203 -0
  82. package/ui/internal/ErrorBoundary.js +29 -0
  83. package/ui/internal/Holder.js +73 -0
  84. package/ui/internal/ScreenContext.js +51 -0
  85. package/ui/internal/UiProviders.js +38 -0
  86. package/ui/internal/ink.js +121 -0
  87. package/ui/internal/inkChalk.js +47 -0
  88. package/ui/internal/inkConsole.js +150 -0
  89. package/ui/internal/lazyView.js +73 -0
  90. package/ui/internal/lineRouter.js +70 -0
  91. package/ui/internal/lineText.js +18 -0
  92. package/ui/internal/mountPermit.js +15 -0
  93. package/ui/internal/processStreams.js +18 -0
  94. package/ui/internal/renderOptions.js +12 -0
  95. package/ui/testing/CliUiTest.js +792 -0
  96. package/ui/testing/fakeStreams.js +104 -0
  97. package/ui/testing/terminalModel.js +67 -0
  98. package/ui-testing-serializer.d.ts +12 -0
  99. package/ui-testing-serializer.js +32 -0
  100. package/ui-testing.d.ts +558 -0
  101. package/ui-testing.js +3 -0
  102. package/ui.d.ts +1990 -0
  103. package/ui.js +17 -0
package/index.js ADDED
@@ -0,0 +1,27 @@
1
+ import { Cancelled } from "./Cancelled.js";
2
+ import { CliAudience } from "./CliAudience.js";
3
+ import { CliColor } from "./CliColor.js";
4
+ import { CliEnv } from "./CliEnv.js";
5
+ import { CliExit } from "./CliExit.js";
6
+ import { CliDoc, CliFailure } from "./CliFailure.js";
7
+ import { CliFormat } from "./CliFormat.js";
8
+ import { CliInteractive } from "./CliInteractive.js";
9
+ import { CliLinks } from "./CliLinks.js";
10
+ import { CliLog } from "./CliLog.js";
11
+ import { CliLogger } from "./CliLogger.js";
12
+ import { CliMessage } from "./CliMessage.js";
13
+ import { CliPrompt } from "./CliPrompt.js";
14
+ import { CliRuntime } from "./CliRuntime.js";
15
+ import { CliTheme } from "./CliTheme.js";
16
+ import { ConfigIssueRenderer } from "./ConfigIssueRenderer.js";
17
+ import { Doc } from "./Doc.js";
18
+ import { Fmt } from "./Fmt.js";
19
+ import { GithubAnnotation } from "./GithubAnnotation.js";
20
+ import { Glyphs } from "./Glyphs.js";
21
+ import { NotInteractive } from "./NotInteractive.js";
22
+ import { Render } from "./Render.js";
23
+ import { SchemaIssueRenderer } from "./SchemaIssueRenderer.js";
24
+ import { Status } from "./Status.js";
25
+ import { Token } from "./Token.js";
26
+
27
+ export { Cancelled, CliAudience, CliColor, CliDoc, CliEnv, CliExit, CliFailure, CliFormat, CliInteractive, CliLinks, CliLog, CliLogger, CliMessage, CliPrompt, CliRuntime, CliTheme, ConfigIssueRenderer, Doc, Fmt, GithubAnnotation, Glyphs, NotInteractive, Render, SchemaIssueRenderer, Status, Token };
@@ -0,0 +1,21 @@
1
+ import { Runtime } from "effect";
2
+
3
+ // src/internal/ExitRequested.ts
4
+ /**
5
+ * The failure `CliRuntime.main` raises when a successful program recorded a
6
+ * non-zero exit code through `CliExit`. Private: nothing outside this package
7
+ * constructs or matches it, and `reportFailures` never renders it.
8
+ *
9
+ * @internal
10
+ */
11
+ var ExitRequested = class extends Error {
12
+ [Runtime.errorReported] = false;
13
+ [Runtime.errorExitCode];
14
+ constructor(code) {
15
+ super(`exit ${code}`);
16
+ this.name = "ExitRequested";
17
+ this[Runtime.errorExitCode] = code;
18
+ }
19
+ };
20
+
21
+ export { ExitRequested };
@@ -0,0 +1,35 @@
1
+ import { Console, Effect } from "effect";
2
+ import { CliOutput } from "effect/cli";
3
+
4
+ // src/internal/HelpRouting.ts
5
+ /**
6
+ * Core's presenter, with an invalid invocation's help written to stderr beside
7
+ * its errors instead of stdout.
8
+ *
9
+ * @remarks
10
+ * Core's `defaultPresenter` writes an invalid invocation's help with
11
+ * `Console.log` and its errors with `Console.error`. Here it runs under a
12
+ * `Console` whose `log` is the ambient one's `error`, so the help and errors
13
+ * keep core's rendering, Formatter resolution included, and only the stream
14
+ * moves. An explicit `--help`, a bare group invocation and `--version` are
15
+ * other events, presented by core unchanged on stdout.
16
+ */
17
+ const usageErrorsToStderr = { present: (event) => event._tag !== "InvalidInvocation" ? CliOutput.defaultPresenter.present(event) : Effect.gen(function* () {
18
+ const sink = yield* Console.Console;
19
+ const toStderr = Object.assign(Object.create(sink), { log: (...args) => sink.error(...args) });
20
+ yield* CliOutput.defaultPresenter.present(event).pipe(Effect.provideService(Console.Console, toStderr));
21
+ }) };
22
+ /**
23
+ * Run `program` so a help document printed together with parse errors goes to
24
+ * stderr, beside the errors, instead of stdout.
25
+ *
26
+ * @remarks
27
+ * Provides core's `CliOutput.Presenter`, which `Command.runWith` hands each
28
+ * help, invalid-invocation and version event to. A Presenter provided inside
29
+ * `program` replaces this one.
30
+ *
31
+ * @internal
32
+ */
33
+ const routeHelpOnUsageError = (program) => Effect.provideService(program, CliOutput.Presenter, usageErrorsToStderr);
34
+
35
+ export { routeHelpOnUsageError };
@@ -0,0 +1,229 @@
1
+ // src/internal/ansi.ts
2
+ /** Foreground SGR parameter for each named colour: 30 to 37, then 90 to 97. */
3
+ const NAMED = {
4
+ black: 30,
5
+ red: 31,
6
+ green: 32,
7
+ yellow: 33,
8
+ blue: 34,
9
+ magenta: 35,
10
+ cyan: 36,
11
+ white: 37,
12
+ blackBright: 90,
13
+ redBright: 91,
14
+ greenBright: 92,
15
+ yellowBright: 93,
16
+ blueBright: 94,
17
+ magentaBright: 95,
18
+ cyanBright: 96,
19
+ whiteBright: 97,
20
+ gray: 90
21
+ };
22
+ /** The 16 ANSI colours as xterm draws them, in SGR order (0 to 7, then bright 8 to 15), for the basic fallback. */
23
+ const PALETTE16 = [
24
+ [
25
+ 0,
26
+ 0,
27
+ 0
28
+ ],
29
+ [
30
+ 205,
31
+ 0,
32
+ 0
33
+ ],
34
+ [
35
+ 0,
36
+ 205,
37
+ 0
38
+ ],
39
+ [
40
+ 205,
41
+ 205,
42
+ 0
43
+ ],
44
+ [
45
+ 0,
46
+ 0,
47
+ 238
48
+ ],
49
+ [
50
+ 205,
51
+ 0,
52
+ 205
53
+ ],
54
+ [
55
+ 0,
56
+ 205,
57
+ 205
58
+ ],
59
+ [
60
+ 229,
61
+ 229,
62
+ 229
63
+ ],
64
+ [
65
+ 127,
66
+ 127,
67
+ 127
68
+ ],
69
+ [
70
+ 255,
71
+ 0,
72
+ 0
73
+ ],
74
+ [
75
+ 0,
76
+ 255,
77
+ 0
78
+ ],
79
+ [
80
+ 255,
81
+ 255,
82
+ 0
83
+ ],
84
+ [
85
+ 92,
86
+ 92,
87
+ 255
88
+ ],
89
+ [
90
+ 255,
91
+ 0,
92
+ 255
93
+ ],
94
+ [
95
+ 0,
96
+ 255,
97
+ 255
98
+ ],
99
+ [
100
+ 255,
101
+ 255,
102
+ 255
103
+ ]
104
+ ];
105
+ /** The six levels of each axis of the xterm 6x6x6 colour cube. */
106
+ const CUBE = [
107
+ 0,
108
+ 95,
109
+ 135,
110
+ 175,
111
+ 215,
112
+ 255
113
+ ];
114
+ const ESC = "\x1B[";
115
+ /** `#rgb` or `#rrggbb` to channels, or `undefined` when it is neither. */
116
+ const parseHex = (hex) => {
117
+ const short = /^#([0-9a-f])([0-9a-f])([0-9a-f])$/i.exec(hex);
118
+ if (short !== null) return [
119
+ short[1],
120
+ short[2],
121
+ short[3]
122
+ ].map((c) => Number.parseInt((c ?? "0").repeat(2), 16));
123
+ const long = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(hex);
124
+ if (long === null) return void 0;
125
+ return [
126
+ Number.parseInt(long[1] ?? "0", 16),
127
+ Number.parseInt(long[2] ?? "0", 16),
128
+ Number.parseInt(long[3] ?? "0", 16)
129
+ ];
130
+ };
131
+ const distance = (a, b) => (a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2 + (a[2] - b[2]) ** 2;
132
+ /** The index of the cube level nearest a channel value; the lower level wins a tie. */
133
+ const nearestLevel = (value) => {
134
+ let best = 0;
135
+ for (let i = 1; i < CUBE.length; i++) if (Math.abs((CUBE[i] ?? 0) - value) < Math.abs((CUBE[best] ?? 0) - value)) best = i;
136
+ return best;
137
+ };
138
+ /**
139
+ * The nearest xterm 256-colour index for a colour: the closest of the 6x6x6 cube and the 24-step grayscale
140
+ * ramp (232 to 255) by squared RGB distance, the cube winning a tie.
141
+ */
142
+ const nearest256 = (rgb) => {
143
+ const [r, g, b] = [
144
+ nearestLevel(rgb[0]),
145
+ nearestLevel(rgb[1]),
146
+ nearestLevel(rgb[2])
147
+ ];
148
+ const cubeIndex = 16 + 36 * r + 6 * g + b;
149
+ const cubeDistance = distance(rgb, [
150
+ CUBE[r] ?? 0,
151
+ CUBE[g] ?? 0,
152
+ CUBE[b] ?? 0
153
+ ]);
154
+ const average = (rgb[0] + rgb[1] + rgb[2]) / 3;
155
+ const step = Math.min(23, Math.max(0, Math.round((average - 8) / 10)));
156
+ const gray = 8 + 10 * step;
157
+ return distance(rgb, [
158
+ gray,
159
+ gray,
160
+ gray
161
+ ]) < cubeDistance ? 232 + step : cubeIndex;
162
+ };
163
+ /** The SGR parameter of the nearest of the 16 ANSI colours: 30 to 37 or 90 to 97. */
164
+ const nearest16 = (rgb) => {
165
+ let best = 0;
166
+ for (let i = 1; i < PALETTE16.length; i++) if (distance(rgb, PALETTE16[i] ?? [
167
+ 0,
168
+ 0,
169
+ 0
170
+ ]) < distance(rgb, PALETTE16[best] ?? [
171
+ 0,
172
+ 0,
173
+ 0
174
+ ])) best = i;
175
+ return best < 8 ? 30 + best : 90 + (best - 8);
176
+ };
177
+ /** The foreground SGR parameters for a colour at a level, or `undefined` for none. */
178
+ const foreground = (fg, level) => {
179
+ if (level === "none") return void 0;
180
+ if (!fg.startsWith("#")) return Object.hasOwn(NAMED, fg) ? String(NAMED[fg]) : void 0;
181
+ const rgb = parseHex(fg);
182
+ if (rgb === void 0) return void 0;
183
+ if (level === "truecolor") return `38;2;${rgb[0]};${rgb[1]};${rgb[2]}`;
184
+ if (level === "256") return `38;5;${nearest256(rgb)}`;
185
+ return String(nearest16(rgb));
186
+ };
187
+ /** The wraps for a style, innermost first: foreground, then dim, bold, italic and underline outward. */
188
+ const wraps = (style, level) => {
189
+ const result = [];
190
+ const fg = style.fg === void 0 ? void 0 : foreground(style.fg, level);
191
+ if (fg !== void 0) result.push({
192
+ open: `${ESC}${fg}m`,
193
+ close: `${ESC}39m`
194
+ });
195
+ if (style.dim === true) result.push({
196
+ open: `${ESC}2m`,
197
+ close: `${ESC}22m`
198
+ });
199
+ if (style.bold === true) result.push({
200
+ open: `${ESC}1m`,
201
+ close: `${ESC}22m`
202
+ });
203
+ if (style.italic === true) result.push({
204
+ open: `${ESC}3m`,
205
+ close: `${ESC}23m`
206
+ });
207
+ if (style.underline === true) result.push({
208
+ open: `${ESC}4m`,
209
+ close: `${ESC}24m`
210
+ });
211
+ return result;
212
+ };
213
+ /**
214
+ * Render `text` in a style at a colour level; identity at `none`.
215
+ *
216
+ * Each attribute closes with its own code (39, 22, 23, 24), never a blanket reset, and a closer that occurs
217
+ * inside `text` is followed by the opener again, so a painted span nested in a painted span leaves the outer
218
+ * style in force for the text after it.
219
+ */
220
+ const paintStyle = (style, level, text) => {
221
+ if (level === "none" || text === "") return text;
222
+ let out = text;
223
+ for (const { open, close } of wraps(style, level)) out = `${open}${out.replaceAll(close, `${close}${open}`)}${close}`;
224
+ return out;
225
+ };
226
+ /** The raw opening SGR sequence of a style at a level, `""` at `none` or for a style that paints nothing. */
227
+ const openSequence = (style, level) => level === "none" ? "" : wraps(style, level).toReversed().map((wrap) => wrap.open).join("");
228
+
229
+ export { nearest256, openSequence, paintStyle, parseHex };
@@ -0,0 +1,33 @@
1
+ import { Effect, Option } from "effect";
2
+ import { CurrentRuntimeEnv } from "@effected/env";
3
+
4
+ // src/internal/autoFormat.ts
5
+ /**
6
+ * Whether the environment says the program runs under GitHub Actions, whose runner reads workflow commands out of
7
+ * the log.
8
+ *
9
+ * @remarks
10
+ * `CurrentRuntimeEnv` is read if the environment has it and is not required: without it, the answer is no.
11
+ *
12
+ * @internal
13
+ */
14
+ const underGithubActions = Effect.gen(function* () {
15
+ const runtime = yield* Effect.serviceOption(CurrentRuntimeEnv);
16
+ return Option.contains(Option.flatMap(runtime, (env) => env.ci), "github-actions");
17
+ });
18
+ /**
19
+ * The renderer an audience gets when nothing says otherwise: a person is painted, a machine reads plain text.
20
+ *
21
+ * @remarks
22
+ * A CI gets GitHub's log format only where `CurrentRuntimeEnv` says it is GitHub Actions; that service is read if the
23
+ * environment has it and is not required. Shared by `Doc.print` and the failure report.
24
+ *
25
+ * @internal
26
+ */
27
+ const autoFormat = (audience) => Effect.gen(function* () {
28
+ if (audience === "human") return "ansi";
29
+ if (audience === "agent") return "plain";
30
+ return (yield* underGithubActions) ? "githubLog" : "plain";
31
+ });
32
+
33
+ export { autoFormat, underGithubActions };
@@ -0,0 +1,14 @@
1
+ import { Config, Effect, Option } from "effect";
2
+
3
+ // src/internal/canPrompt.ts
4
+ /**
5
+ * Whether the terminal facts let a run prompt: a terminal on standard input and on standard output, and a `TERM` that
6
+ * is not `dumb`. A dumb terminal is a terminal, but it cannot move the cursor or take synchronized output, which every
7
+ * redrawing prompt and screen needs. `TERM` is read through `Config` (never `process`), and only when both streams are
8
+ * terminals; a read that fails counts as unset, as the env package treats every read.
9
+ *
10
+ * @internal
11
+ */
12
+ const canPrompt = (terminal) => terminal.stdinIsTerminal && terminal.stdout.isTerminal ? Effect.map(Config.option(Config.String("TERM")).pipe(Effect.orElseSucceed(() => Option.none())), (term) => !(Option.isSome(term) && term.value === "dumb")) : Effect.succeed(false);
13
+
14
+ export { canPrompt };
@@ -0,0 +1,83 @@
1
+ import { Fmt } from "../Fmt.js";
2
+
3
+ // src/internal/counts.ts
4
+ /**
5
+ * The label a counter shows for a count: its one label, or `one` when the count is exactly 1 and `other` otherwise.
6
+ * The count is the counter's own `n`, except in a share headline (`1/3 repos`), which reads by the denominator, the
7
+ * total, and passes it.
8
+ *
9
+ * @internal
10
+ */
11
+ const counterLabel = (counter, count = counter.n) => typeof counter.label === "string" ? counter.label : count === 1 ? counter.label.one : counter.label.other;
12
+ /**
13
+ * The label a counter's column is headed with in a `CountsTable`: its one label, or its plural form, since a column
14
+ * holds every row's count.
15
+ *
16
+ * @internal
17
+ */
18
+ const columnLabel = (counter) => typeof counter.label === "string" ? counter.label : counter.label.other;
19
+ /**
20
+ * The total of a `Counts` block: the caller's rule when it has one, otherwise the sum of `n` over every counter.
21
+ *
22
+ * @remarks
23
+ * Lives here, not on `Doc`, so a renderer needs nothing from `Doc` at runtime: `Doc.print` imports the renderers, and
24
+ * a renderer importing `Doc` back would make a cycle. `Doc.total` is this function.
25
+ *
26
+ * @internal
27
+ */
28
+ const totalOf = (block) => block.total === void 0 ? block.counters.reduce((sum, counter) => sum + counter.n, 0) : block.total(block.counters);
29
+ /**
30
+ * The counters a renderer shows: every one except a zero counter that does not ask for `showZero`. `Doc.visibleCounters`
31
+ * is this function.
32
+ *
33
+ * @internal
34
+ */
35
+ const visibleCountersOf = (block) => block.counters.filter((counter) => counter.n !== 0 || counter.showZero === true);
36
+ /**
37
+ * A `CountsTable` as the `Table` it renders as: a label column, then a column per counter key in the order the keys
38
+ * first appear, headed by that counter's label; a cell is the count painted with its status token, empty where a row
39
+ * has no counter for the key; a duration column, when some row has a `durationMs`, formatted with `Fmt.duration`; and,
40
+ * with `totalRow`, a last row summing each column (a missing count or duration is zero).
41
+ *
42
+ * @remarks
43
+ * Plain literals, not `Doc` constructors: a renderer needs nothing from `Doc` at runtime (see {@link totalOf}).
44
+ *
45
+ * @internal
46
+ */
47
+ const countsTableOf = (block) => {
48
+ const keys = [];
49
+ for (const row of block.rows) for (const counter of row.counters) if (!keys.some((known) => known.key === counter.key)) keys.push(counter);
50
+ const text = (value, token) => token === void 0 ? {
51
+ _tag: "Text",
52
+ value
53
+ } : {
54
+ _tag: "Text",
55
+ value,
56
+ token
57
+ };
58
+ const countCell = (counter) => counter === void 0 ? [] : [text(String(counter.n), counter.status.def.token)];
59
+ const timed = block.rows.some((row) => row.durationMs !== void 0);
60
+ const durationCell = (ms) => timed ? [ms === void 0 ? [] : [text(Fmt.duration(ms))]] : [];
61
+ const rows = block.rows.map((row) => [
62
+ row.label,
63
+ ...keys.map((key) => countCell(row.counters.find((counter) => counter.key === key.key))),
64
+ ...durationCell(row.durationMs)
65
+ ]);
66
+ const totalLabel = block.totalRow === void 0 || block.totalRow === false ? void 0 : block.totalRow === true ? [text("Total")] : block.totalRow;
67
+ const total = totalLabel === void 0 ? [] : [[
68
+ totalLabel,
69
+ ...keys.map((key) => [text(String(block.rows.reduce((sum, row) => sum + (row.counters.find((counter) => counter.key === key.key)?.n ?? 0), 0)))]),
70
+ ...durationCell(block.rows.reduce((sum, row) => sum + (row.durationMs ?? 0), 0))
71
+ ]];
72
+ return {
73
+ _tag: "Table",
74
+ columns: [
75
+ { header: block.labelHeader ?? [] },
76
+ ...keys.map((key) => ({ header: [text(columnLabel(key))] })),
77
+ ...timed ? [{ header: block.durationHeader ?? [text("duration")] }] : []
78
+ ],
79
+ rows: [...rows, ...total]
80
+ };
81
+ };
82
+
83
+ export { columnLabel, counterLabel, countsTableOf, totalOf, visibleCountersOf };
@@ -0,0 +1,31 @@
1
+ import { Context, LogLevel, Logger, References } from "effect";
2
+
3
+ // src/internal/diagnostics.ts
4
+ /**
5
+ * The diagnostics threshold reference behind `CliLog.Level`. Lives here so the stderr sink and the file sink share
6
+ * one definition without `CliLog` importing itself.
7
+ *
8
+ * @internal
9
+ */
10
+ const Level = Context.Reference("@effected/cli-kit/CliLog/Level", { defaultValue: () => "None" });
11
+ /**
12
+ * Whether a diagnostics sink writes `record`. `installed` is the `MinimumLogLevel` the diagnostics layer put in
13
+ * place: while the fiber still sees that value the sink filters on its own {@link Level}; a different value is taken
14
+ * to be core's `--log-level` flag and the sink follows it. A flag whose value EQUALS `installed` cannot be told from
15
+ * no flag, so the sink keeps filtering on its own level; the plain `CliLogger` prints those records anyway.
16
+ *
17
+ * @internal
18
+ */
19
+ const passes = (record, installed) => {
20
+ const current = record.fiber.getRef(References.MinimumLogLevel);
21
+ const threshold = current === installed ? record.fiber.getRef(Level) : current;
22
+ return LogLevel.isGreaterThanOrEqualTo(record.logLevel, threshold);
23
+ };
24
+ /**
25
+ * The NDJSON line for a record, the one shape every diagnostics sink writes.
26
+ *
27
+ * @internal
28
+ */
29
+ const formatNdjson = (record) => Logger.formatJson.log(record);
30
+
31
+ export { Level, formatNdjson, passes };
@@ -0,0 +1,46 @@
1
+ import { Context, Effect, Layer, Option, Path } from "effect";
2
+
3
+ // src/internal/displayPath.ts
4
+ /**
5
+ * The display that shows every path as it is.
6
+ *
7
+ * @internal
8
+ */
9
+ const identity = (absolute) => absolute;
10
+ /**
11
+ * The path display in effect: the identity unless `CliEnv.layer` was given a `displayPath`.
12
+ *
13
+ * @internal
14
+ */
15
+ const CurrentDisplayPath = Context.Reference("@effected/cli-kit/CurrentDisplayPath", { defaultValue: () => identity });
16
+ /**
17
+ * A path relative to `root` when it lies under it, and the path unchanged when it is `root` itself or lies outside it
18
+ * (a `../..` chain says less than the absolute path). With a `Path` service its `relative` decides; without one, a
19
+ * plain prefix test on `/` and `\` does.
20
+ *
21
+ * @internal
22
+ */
23
+ const relativeTo = (root, path) => Option.match(path, {
24
+ onSome: (p) => (absolute) => {
25
+ const rel = p.relative(root, absolute);
26
+ return rel === "" || rel === ".." || rel.startsWith(`..${p.sep}`) || rel.startsWith("../") || p.isAbsolute(rel) ? absolute : rel;
27
+ },
28
+ onNone: () => {
29
+ let end = root.length;
30
+ while (end > 0 && (root[end - 1] === "/" || root[end - 1] === "\\")) end -= 1;
31
+ const base = root.slice(0, end);
32
+ return (absolute) => {
33
+ const next = absolute.charAt(base.length);
34
+ return absolute.startsWith(base) && (next === "/" || next === "\\") && absolute.length > base.length + 1 ? absolute.slice(base.length + 1) : absolute;
35
+ };
36
+ }
37
+ });
38
+ /**
39
+ * Sets {@link CurrentDisplayPath} from the option, reading `Path` from the context when it is there. No option sets
40
+ * nothing.
41
+ *
42
+ * @internal
43
+ */
44
+ const displayPathLayer = (option) => option === void 0 ? Layer.empty : Layer.effect(CurrentDisplayPath)(typeof option === "function" ? Effect.succeed(option) : Effect.map(Effect.serviceOption(Path.Path), (path) => relativeTo(option.relativeTo, path)));
45
+
46
+ export { CurrentDisplayPath, displayPathLayer, identity, relativeTo };
@@ -0,0 +1,42 @@
1
+ // src/internal/displayWidth.ts
2
+ const segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
3
+ const ANSI = /\u001B(?:\[[0-?]*[ -/]*[@-~]|\][^\u0007\u001B]*(?:\u0007|\u001B\\)|[PX^_][^\u001B]*\u001B\\|[()*+\-./][0-~]|\$[()*+\-./]?[@-~]|#[3-68]| [FGLMN]|[0-?@-Z\\^_c])/g;
4
+ const EMOJI = /^\p{RGI_Emoji}$/v;
5
+ const WIDE2 = /[\p{Emoji_Presentation}\u1100-\u115E\u2329\u232A\u2630-\u2637\u268A-\u268F\u2E80-\u2E99\u2E9B-\u2EF3\u2F00-\u2FD5\u2FF0-\u303E\u3041-\u3096\u3099-\u30FF\u3105-\u312F\u3131-\u3163\u3165-\u318E\u3190-\u31E5\u31EF-\u321E\u3220-\u3247\u3250-\u4DBF\u4DC0-\uA48C\uA490-\uA4C6\uA960-\uA97C\uAC00-\uD7A3\uF900-\uFAFF\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE66\uFE68-\uFE6B\uFF01-\uFF60\uFFE0-\uFFE6\u{16FE0}-\u{16FF6}\u{17000}-\u{191FF}\u{1AFF0}-\u{1AFFF}\u{1B000}-\u{1B2FF}\u{1D300}-\u{1D376}\u{1F200}-\u{1F265}\u{20000}-\u{3FFFD}]/u;
6
+ const ZERO2 = /^[\p{Mn}\p{Me}\p{Cf}\p{Cc}\u115F\u1160\u3164\uFFA0]+$/u;
7
+ /**
8
+ * The text without its escape sequences: CSI (including SGR colour), OSC (including OSC-8 hyperlinks), the DCS, SOS,
9
+ * PM and APC strings, and every other escape (`ESC c`, `ESC 7`, `ESC ( B`).
10
+ *
11
+ * @internal
12
+ */
13
+ const stripAnsi = (input) => input.replace(ANSI, "");
14
+ /**
15
+ * Whether `input` is nothing but escape sequences, as {@link stripAnsi} reads them; empty text is not. Every sequence
16
+ * starts with ESC, so text that does not is answered without running the strip.
17
+ *
18
+ * @internal
19
+ */
20
+ const isEscapeOnly = (input) => input.charCodeAt(0) === 27 && stripAnsi(input) === "";
21
+ /**
22
+ * The display width of `input` in terminal columns: graphemes, wide East Asian characters and emoji count two,
23
+ * combining marks, control characters and ANSI escapes count none.
24
+ *
25
+ * @internal
26
+ */
27
+ const displayWidth = (input) => {
28
+ let width = 0;
29
+ for (const { segment } of segmenter.segment(stripAnsi(input))) {
30
+ if (ZERO2.test(segment)) continue;
31
+ width += EMOJI.test(segment) || /^\p{RI}{2}/u.test(segment) || WIDE2.test(Array.from(segment)[0] ?? "") ? 2 : 1;
32
+ }
33
+ return width;
34
+ };
35
+ /**
36
+ * The grapheme clusters of `input`, in order; a cluster is never split.
37
+ *
38
+ * @internal
39
+ */
40
+ const graphemes = (input) => Array.from(segmenter.segment(input), (s) => s.segment);
41
+
42
+ export { displayWidth, graphemes, isEscapeOnly, stripAnsi };