@effected/cli 0.10.0 → 0.11.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/Cancelled.js +44 -0
- package/CliAudience.js +178 -0
- package/CliColor.js +13 -19
- package/CliEnv.js +89 -0
- package/CliExit.js +1 -1
- package/CliFailure.js +253 -0
- package/CliInteractive.js +71 -0
- package/CliLinks.js +154 -0
- package/CliLog.js +294 -0
- package/CliLogger.js +34 -33
- package/CliMessage.js +83 -0
- package/CliPrompt.js +104 -0
- package/CliRuntime.js +103 -53
- package/CliTest.js +16 -0
- package/CliTheme.js +128 -0
- package/ConfigIssueRenderer.js +14 -33
- package/Doc.js +512 -0
- package/Fmt.js +133 -0
- package/GithubAnnotation.js +40 -0
- package/Glyphs.js +83 -0
- package/NotInteractive.js +42 -0
- package/README.md +129 -131
- package/Render.js +254 -0
- package/SchemaIssueRenderer.js +7 -10
- package/Status.js +163 -0
- package/TestTerminal.js +80 -0
- package/Token.js +69 -0
- package/index.d.ts +2923 -171
- package/index.js +19 -1
- package/internal/ansi.js +230 -0
- package/internal/autoFormat.js +34 -0
- package/internal/canPrompt.js +15 -0
- package/internal/counts.js +69 -0
- package/internal/diagnostics.js +32 -0
- package/internal/displayWidth.js +35 -0
- package/internal/failureTarget.js +156 -0
- package/internal/fallbackAnswer.js +18 -0
- package/internal/fileSink.js +62 -0
- package/internal/format.js +62 -7
- package/internal/layout.js +250 -0
- package/internal/linkScheme.js +30 -0
- package/internal/linkTarget.js +50 -0
- package/internal/logSafety.js +46 -0
- package/internal/renderAnsi.js +52 -0
- package/internal/renderDoc.js +319 -0
- package/internal/renderGithubLog.js +46 -0
- package/internal/renderMarkdown.js +367 -0
- package/internal/renderPlain.js +50 -0
- package/internal/scanAudience.js +106 -0
- package/internal/splitFrame.js +56 -0
- package/internal/wizardGate.js +18 -0
- package/package.json +35 -5
- package/testing.d.ts +88 -2
- package/testing.js +2 -1
- package/ui/CliUi.js +348 -0
- package/ui/CliUiLive.js +399 -0
- package/ui/Confirm.js +245 -0
- package/ui/DocView.js +74 -0
- package/ui/KeyHelp.js +62 -0
- package/ui/KeyTable.js +199 -0
- package/ui/MultiSelect.js +260 -0
- package/ui/Select.js +226 -0
- package/ui/Tabs.js +202 -0
- package/ui/TextInput.js +250 -0
- package/ui/Toggle.js +32 -0
- package/ui/UiKey.js +44 -0
- package/ui/UiProvider.js +60 -0
- package/ui/UiStreams.js +18 -0
- package/ui/UiTheme.js +119 -0
- package/ui/Viewport.js +204 -0
- package/ui/internal/ErrorBoundary.js +30 -0
- package/ui/internal/Holder.js +74 -0
- package/ui/internal/ScreenContext.js +52 -0
- package/ui/internal/UiProviders.js +21 -0
- package/ui/internal/ink.js +122 -0
- package/ui/internal/inkChalk.js +58 -0
- package/ui/internal/inkConsole.js +146 -0
- package/ui/internal/lineText.js +19 -0
- package/ui/internal/mountPermit.js +16 -0
- package/ui/internal/perfDrain.js +33 -0
- package/ui/internal/processStreams.js +19 -0
- package/ui/internal/renderOptions.js +13 -0
- package/ui/testing/CliUiTest.js +735 -0
- package/ui/testing/fakeStreams.js +76 -0
- package/ui/testing/terminalModel.js +59 -0
- package/ui-testing.d.ts +446 -0
- package/ui-testing.js +3 -0
- package/ui.d.ts +1648 -0
- package/ui.js +17 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { inkChalk } from "./inkChalk.js";
|
|
2
|
+
import { Effect, Option } from "effect";
|
|
3
|
+
|
|
4
|
+
//#region src/ui/internal/ink.ts
|
|
5
|
+
const MISSING_PEERS = "@effected/cli/ui could not load its optional peers ink and react: install both beside @effected/cli to mount a screen";
|
|
6
|
+
const READ_BEFORE_LOAD = "@effected/cli/ui read Ink before loading it: run CliUi.context (or mount a screen with CliUi.run) before rendering UiProvider or a kit component in a tree of your own";
|
|
7
|
+
const UNRESOLVED_CHALK = "@effected/cli/ui could not resolve the chalk Ink uses (is ink bundled?), so Ink decides its own colour level";
|
|
8
|
+
let modules;
|
|
9
|
+
let loading;
|
|
10
|
+
const importPeers = async () => {
|
|
11
|
+
const [ink, react] = await Promise.all([import("ink"), import("react")]);
|
|
12
|
+
return {
|
|
13
|
+
ink,
|
|
14
|
+
react: react.default
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Load `ink` and `react`, once. The only runtime access the kit has to either package: nothing imports a value
|
|
19
|
+
* from them, so importing `./ui` loads neither, and only mounting a screen does.
|
|
20
|
+
*
|
|
21
|
+
* @remarks
|
|
22
|
+
* Concurrent loads share one import, and a failed one is retried by the next call. A missing peer is a defect, not
|
|
23
|
+
* a typed failure, because it is an installation error no handler can recover from; its message names both peers.
|
|
24
|
+
*
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
const loadInk = Effect.suspend(() => {
|
|
28
|
+
if (modules !== void 0) return Effect.succeed(modules);
|
|
29
|
+
return Effect.tryPromise({
|
|
30
|
+
try: () => {
|
|
31
|
+
loading ??= importPeers();
|
|
32
|
+
return loading;
|
|
33
|
+
},
|
|
34
|
+
catch: (cause) => new Error(MISSING_PEERS, { cause })
|
|
35
|
+
}).pipe(Effect.tap((loaded) => Effect.sync(() => {
|
|
36
|
+
modules = loaded;
|
|
37
|
+
})), Effect.tapError(() => Effect.sync(() => {
|
|
38
|
+
loading = void 0;
|
|
39
|
+
})), Effect.orDie);
|
|
40
|
+
});
|
|
41
|
+
/**
|
|
42
|
+
* The modules {@link loadInk} loaded, read at render time by kit components.
|
|
43
|
+
*
|
|
44
|
+
* @remarks
|
|
45
|
+
* Throws when nothing has loaded them: a kit component rendered outside a screen. Inside a render that throw is
|
|
46
|
+
* caught by the screen's error boundary and becomes a defect.
|
|
47
|
+
*
|
|
48
|
+
* @internal
|
|
49
|
+
*/
|
|
50
|
+
const inkModules = () => {
|
|
51
|
+
if (modules === void 0) throw new Error(READ_BEFORE_LOAD);
|
|
52
|
+
return modules;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* A value built once from the loaded React, such as a class component or a context, which cannot be declared at
|
|
56
|
+
* module scope because the kit holds no runtime React until {@link loadInk} runs.
|
|
57
|
+
*
|
|
58
|
+
* @remarks
|
|
59
|
+
* The returned accessor builds on first call and returns the same value thereafter; like {@link inkModules}, it
|
|
60
|
+
* throws if called before the load.
|
|
61
|
+
*
|
|
62
|
+
* @internal
|
|
63
|
+
*/
|
|
64
|
+
const fromReact = (build) => {
|
|
65
|
+
let built;
|
|
66
|
+
return () => {
|
|
67
|
+
const { react } = inkModules();
|
|
68
|
+
if (built === void 0 || built.react !== react) built = {
|
|
69
|
+
react,
|
|
70
|
+
value: build(react)
|
|
71
|
+
};
|
|
72
|
+
return built.value;
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
const LEVELS = {
|
|
76
|
+
none: 0,
|
|
77
|
+
basic: 1,
|
|
78
|
+
"256": 2,
|
|
79
|
+
truecolor: 3
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* A stream's colour level as a chalk level: `none` 0, `basic` 1, `256` 2, `truecolor` 3.
|
|
83
|
+
*
|
|
84
|
+
* @internal
|
|
85
|
+
*/
|
|
86
|
+
const levelOf = (colour) => LEVELS[colour];
|
|
87
|
+
let chalk;
|
|
88
|
+
const resolveChalk = Effect.promise(() => {
|
|
89
|
+
chalk ??= inkChalk();
|
|
90
|
+
return chalk;
|
|
91
|
+
});
|
|
92
|
+
let warned = false;
|
|
93
|
+
/**
|
|
94
|
+
* Hold `chalk` at `colour`'s level for the enclosing scope, restoring the saved level when the scope closes, by
|
|
95
|
+
* release or by interruption. With no chalk to hold, warn once and leave Ink's own detection in place.
|
|
96
|
+
*
|
|
97
|
+
* @internal
|
|
98
|
+
*/
|
|
99
|
+
const holdChalkLevel = (found, colour) => Option.match(found, {
|
|
100
|
+
onNone: () => Effect.suspend(() => {
|
|
101
|
+
if (warned) return Effect.void;
|
|
102
|
+
warned = true;
|
|
103
|
+
return Effect.logWarning(UNRESOLVED_CHALK);
|
|
104
|
+
}),
|
|
105
|
+
onSome: (instance) => Effect.asVoid(Effect.acquireRelease(Effect.sync(() => {
|
|
106
|
+
const saved = instance.level;
|
|
107
|
+
instance.level = levelOf(colour);
|
|
108
|
+
return saved;
|
|
109
|
+
}), (saved) => Effect.sync(() => {
|
|
110
|
+
instance.level = saved;
|
|
111
|
+
})))
|
|
112
|
+
});
|
|
113
|
+
/**
|
|
114
|
+
* Set Ink's colour level from the stream's `ColorLevel` for the enclosing scope, on Ink's own chalk. The level is process-global while held: the last screen
|
|
115
|
+
* mounted wins.
|
|
116
|
+
*
|
|
117
|
+
* @internal
|
|
118
|
+
*/
|
|
119
|
+
const withInkColour = (colour) => Effect.flatMap(resolveChalk, (found) => holdChalkLevel(found, colour));
|
|
120
|
+
|
|
121
|
+
//#endregion
|
|
122
|
+
export { fromReact, holdChalkLevel, inkModules, levelOf, loadInk, withInkColour };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { Option } from "effect";
|
|
3
|
+
import { realpathSync } from "node:fs";
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
|
+
|
|
6
|
+
//#region src/ui/internal/inkChalk.ts
|
|
7
|
+
const isInkChalk = (value) => (typeof value === "function" || typeof value === "object" && value !== null) && typeof value.level === "number";
|
|
8
|
+
/** The runtime's `import.meta.resolve`, when it has one. */
|
|
9
|
+
const esmResolve = typeof import.meta.resolve === "function" ? (specifier) => import.meta.resolve(specifier) : void 0;
|
|
10
|
+
/**
|
|
11
|
+
* The path of Ink's entry: through `resolve` (the runtime's `import.meta.resolve` by default), or, where that is
|
|
12
|
+
* missing or throws, through Node's CommonJS resolution from this module.
|
|
13
|
+
*
|
|
14
|
+
* @remarks
|
|
15
|
+
* Vite's module runner, which evaluates a Vitest reporter loaded by path, has an `import.meta.resolve` that throws
|
|
16
|
+
* ("not supported"); the CommonJS resolution finds the same file, since Ink's `exports` has a `default` condition.
|
|
17
|
+
*
|
|
18
|
+
* @param resolve - an ESM resolver, to stand in for the runtime's in a test
|
|
19
|
+
*
|
|
20
|
+
* @internal
|
|
21
|
+
*/
|
|
22
|
+
const resolveInkEntry = (resolve = esmResolve) => {
|
|
23
|
+
if (resolve !== void 0) try {
|
|
24
|
+
return fileURLToPath(resolve("ink"));
|
|
25
|
+
} catch {}
|
|
26
|
+
return createRequire(import.meta.url).resolve("ink");
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* The chalk instance Ink itself imports, resolved from Ink's own location; `None` when it cannot be resolved.
|
|
30
|
+
*
|
|
31
|
+
* @remarks
|
|
32
|
+
* One of the three files licensed to touch Node. Ink's `exports` has
|
|
33
|
+
* only `"."` and chalk is its own dependency, so the kit cannot import Ink's chalk by name: a `chalk` of the kit's
|
|
34
|
+
* own could be a different copy, and setting its level would silently change nothing. Resolving `chalk` from Ink's
|
|
35
|
+
* entry ({@link resolveInkEntry}) and importing its realpath yields the very module Ink imports, because Node keys ES
|
|
36
|
+
* modules by realpath. A consumer that bundles Ink leaves nothing
|
|
37
|
+
* to resolve, and the answer is `None`; it never rejects.
|
|
38
|
+
*
|
|
39
|
+
* @param inkEntryOf - how Ink's entry is found; {@link resolveInkEntry} by default
|
|
40
|
+
*
|
|
41
|
+
* @internal
|
|
42
|
+
*/
|
|
43
|
+
const inkChalk = async (inkEntryOf = () => resolveInkEntry()) => {
|
|
44
|
+
try {
|
|
45
|
+
const inkEntry = inkEntryOf();
|
|
46
|
+
const chalkPath = realpathSync(createRequire(inkEntry).resolve("chalk"));
|
|
47
|
+
const chalk = await import(
|
|
48
|
+
/* @vite-ignore */
|
|
49
|
+
pathToFileURL(chalkPath).href
|
|
50
|
+
);
|
|
51
|
+
return isInkChalk(chalk.default) ? Option.some(chalk.default) : Option.none();
|
|
52
|
+
} catch {
|
|
53
|
+
return Option.none();
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
//#endregion
|
|
58
|
+
export { inkChalk, resolveInkEntry };
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { inkModules } from "./ink.js";
|
|
2
|
+
import { UiStreams } from "../UiStreams.js";
|
|
3
|
+
import { Effect, Inspectable } from "effect";
|
|
4
|
+
|
|
5
|
+
//#region src/ui/internal/inkConsole.ts
|
|
6
|
+
/** One argument as a console shows it: a string as is, an `Error` with its stack, anything else as data (JSON). */
|
|
7
|
+
const shown = (arg) => arg instanceof Error ? arg.stack ?? String(arg) : Inspectable.toStringUnknown(arg, 0);
|
|
8
|
+
/** A console call's arguments as one line of text, joined by spaces. */
|
|
9
|
+
const textOf = (args) => args.map(shown).join(" ");
|
|
10
|
+
/** `console.table`'s rows, as a plain pipe table: an `(index)` column, then each key, then `Values` for scalars. */
|
|
11
|
+
const tableOf = (data, properties) => {
|
|
12
|
+
if (data === null || typeof data !== "object") return textOf([data]);
|
|
13
|
+
const rows = Object.entries(data);
|
|
14
|
+
const isRecord = (value) => value !== null && typeof value === "object";
|
|
15
|
+
const keys = properties ?? [...new Set(rows.flatMap(([, value]) => isRecord(value) ? Object.keys(value) : []))];
|
|
16
|
+
const scalars = rows.some(([, value]) => !isRecord(value));
|
|
17
|
+
const header = [
|
|
18
|
+
"(index)",
|
|
19
|
+
...keys,
|
|
20
|
+
...scalars ? ["Values"] : []
|
|
21
|
+
];
|
|
22
|
+
const cell = (value) => value === void 0 ? "" : typeof value === "string" ? value : shown(value);
|
|
23
|
+
const body = rows.map(([index, value]) => [
|
|
24
|
+
index,
|
|
25
|
+
...keys.map((key) => isRecord(value) ? cell(value[key]) : ""),
|
|
26
|
+
...scalars ? [isRecord(value) ? "" : cell(value)] : []
|
|
27
|
+
]);
|
|
28
|
+
const widths = header.map((title, column) => Math.max(title.length, ...body.map((row) => (row[column] ?? "").length)));
|
|
29
|
+
const line = (cells) => `| ${cells.map((text, column) => text.padEnd(widths[column] ?? 0)).join(" | ")} |`;
|
|
30
|
+
return [line(header), ...body.map(line)].join("\n");
|
|
31
|
+
};
|
|
32
|
+
/** Milliseconds now, for `time`; the platform clock when there is one. */
|
|
33
|
+
const now = () => globalThis.performance?.now() ?? Date.now();
|
|
34
|
+
/**
|
|
35
|
+
* Build an `InkConsole` over `UiStreams`.
|
|
36
|
+
*
|
|
37
|
+
* @remarks
|
|
38
|
+
* Ink's writers (`useStdout().write`, `useStderr().write`) clear the frame, write the line and repaint the frame, so a
|
|
39
|
+
* line written through them lands above the frame without tearing it; a raw write to the stream lands inside the frame
|
|
40
|
+
* instead. They are reachable only from inside the tree, which is what the `Bridge` is for. Ink drops whatever they
|
|
41
|
+
* are handed once it has unmounted, so the owner detaches before it unmounts, and a line written after goes straight
|
|
42
|
+
* to the stream.
|
|
43
|
+
*
|
|
44
|
+
* Every `Console` method is the bridge's own, none the ambient console's: one that fell through would write to the
|
|
45
|
+
* process's terminal past Ink, tearing the frame and escaping `UiStreams`.
|
|
46
|
+
*
|
|
47
|
+
* @internal
|
|
48
|
+
*/
|
|
49
|
+
const makeInkConsole = Effect.gen(function* () {
|
|
50
|
+
const streams = yield* UiStreams;
|
|
51
|
+
let attached;
|
|
52
|
+
let indent = "";
|
|
53
|
+
const counts = /* @__PURE__ */ new Map();
|
|
54
|
+
const timers = /* @__PURE__ */ new Map();
|
|
55
|
+
const emit = (stream, text) => {
|
|
56
|
+
const data = `${text.split("\n").map((line) => indent + line).join("\n")}\n`;
|
|
57
|
+
if (attached !== void 0) (stream === "out" ? attached.out : attached.err)(data);
|
|
58
|
+
else if (stream === "out") streams.stdout.write(data);
|
|
59
|
+
else streams.stderr.write(data);
|
|
60
|
+
};
|
|
61
|
+
const toOut = (...args) => emit("out", textOf(args));
|
|
62
|
+
const toErr = (...args) => emit("err", textOf(args));
|
|
63
|
+
const elapsed = (label, method, extra) => {
|
|
64
|
+
const started = timers.get(label);
|
|
65
|
+
if (started === void 0) {
|
|
66
|
+
emit("err", `Warning: No such label '${label}' for console.${method}()`);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const tail = extra.length === 0 ? "" : ` ${textOf(extra)}`;
|
|
70
|
+
emit("out", `${label}: ${(now() - started).toFixed(3)}ms${tail}`);
|
|
71
|
+
};
|
|
72
|
+
const group = (...label) => {
|
|
73
|
+
if (label.length > 0) toOut(...label);
|
|
74
|
+
indent += " ";
|
|
75
|
+
};
|
|
76
|
+
const writer = {
|
|
77
|
+
log: toOut,
|
|
78
|
+
info: toOut,
|
|
79
|
+
debug: toOut,
|
|
80
|
+
dirxml: toOut,
|
|
81
|
+
error: toErr,
|
|
82
|
+
warn: toErr,
|
|
83
|
+
trace: (...args) => {
|
|
84
|
+
const stack = ((/* @__PURE__ */ new Error()).stack ?? "").split("\n").slice(2).join("\n");
|
|
85
|
+
emit("err", `Trace${args.length === 0 ? "" : `: ${textOf(args)}`}${stack === "" ? "" : `\n${stack}`}`);
|
|
86
|
+
},
|
|
87
|
+
dir: (item) => toOut(item),
|
|
88
|
+
table: (data, properties) => emit("out", tableOf(data, properties)),
|
|
89
|
+
assert: (condition, ...args) => {
|
|
90
|
+
if (!condition) emit("err", `Assertion failed${args.length === 0 ? "" : `: ${textOf(args)}`}`);
|
|
91
|
+
},
|
|
92
|
+
count: (label = "default") => {
|
|
93
|
+
const next = (counts.get(label) ?? 0) + 1;
|
|
94
|
+
counts.set(label, next);
|
|
95
|
+
emit("out", `${label}: ${next}`);
|
|
96
|
+
},
|
|
97
|
+
countReset: (label = "default") => {
|
|
98
|
+
counts.delete(label);
|
|
99
|
+
},
|
|
100
|
+
group,
|
|
101
|
+
groupCollapsed: group,
|
|
102
|
+
groupEnd: () => {
|
|
103
|
+
indent = indent.slice(2);
|
|
104
|
+
},
|
|
105
|
+
time: (label = "default") => {
|
|
106
|
+
timers.set(label, now());
|
|
107
|
+
},
|
|
108
|
+
timeLog: (label = "default", ...extra) => elapsed(label, "timeLog", extra),
|
|
109
|
+
timeEnd: (label = "default") => {
|
|
110
|
+
elapsed(label, "timeEnd", []);
|
|
111
|
+
timers.delete(label);
|
|
112
|
+
},
|
|
113
|
+
clear: () => void 0
|
|
114
|
+
};
|
|
115
|
+
const Bridge = (props) => {
|
|
116
|
+
const { ink, react } = inkModules();
|
|
117
|
+
const out = ink.useStdout().write;
|
|
118
|
+
const err = ink.useStderr().write;
|
|
119
|
+
react.useLayoutEffect(() => {
|
|
120
|
+
const writers = {
|
|
121
|
+
out,
|
|
122
|
+
err
|
|
123
|
+
};
|
|
124
|
+
attached = writers;
|
|
125
|
+
return () => {
|
|
126
|
+
if (attached === writers) attached = void 0;
|
|
127
|
+
};
|
|
128
|
+
}, [out, err]);
|
|
129
|
+
return props.children ?? null;
|
|
130
|
+
};
|
|
131
|
+
return {
|
|
132
|
+
writer,
|
|
133
|
+
print: (text) => {
|
|
134
|
+
const data = `${text}\n`;
|
|
135
|
+
if (attached !== void 0) attached.out(data);
|
|
136
|
+
else streams.stdout.write(data);
|
|
137
|
+
},
|
|
138
|
+
Bridge,
|
|
139
|
+
detach: () => {
|
|
140
|
+
attached = void 0;
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
//#endregion
|
|
146
|
+
export { makeInkConsole };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { sanitize } from "../../Fmt.js";
|
|
2
|
+
|
|
3
|
+
//#region src/ui/internal/lineText.ts
|
|
4
|
+
/**
|
|
5
|
+
* Consumer text made safe for one row of a widget: sanitised as `Fmt.sanitize` does (no escape sequence, no other
|
|
6
|
+
* control character), with each line break (CR, LF or CR LF) folded to a space.
|
|
7
|
+
*
|
|
8
|
+
* @remarks
|
|
9
|
+
* Every string a kit widget draws from data goes through it before it is measured or cut. Ink keeps SGR and OSC
|
|
10
|
+
* sequences it is handed, so unsanitised text would paint colour at colour `none` and could plant a hyperlink whose
|
|
11
|
+
* target differs from its label; and a widget's row budget counts one row per line, so a second line would push the
|
|
12
|
+
* frame past the terminal, where Ink wipes the screen and its scrollback.
|
|
13
|
+
*
|
|
14
|
+
* @internal
|
|
15
|
+
*/
|
|
16
|
+
const lineText = (text) => sanitize(text).replace(/\r\n|\r|\n/g, " ");
|
|
17
|
+
|
|
18
|
+
//#endregion
|
|
19
|
+
export { lineText };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Semaphore } from "effect";
|
|
2
|
+
|
|
3
|
+
//#region src/ui/internal/mountPermit.ts
|
|
4
|
+
/**
|
|
5
|
+
* One Ink mount at a time, process-wide: a `CliUi.run` screen, or one run of a live view.
|
|
6
|
+
*
|
|
7
|
+
* @remarks
|
|
8
|
+
* Ink keys its instances by stdout and owns raw mode on the one terminal, so concurrent mounts are meaningless, and
|
|
9
|
+
* serializing them keeps each mount's save-and-restore of Ink's colour level well nested.
|
|
10
|
+
*
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
const mountPermit = Semaphore.makeUnsafe(1);
|
|
14
|
+
|
|
15
|
+
//#endregion
|
|
16
|
+
export { mountPermit };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Config, Effect } from "effect";
|
|
2
|
+
|
|
3
|
+
//#region src/ui/internal/perfDrain.ts
|
|
4
|
+
/**
|
|
5
|
+
* Whether a drain mode drains: `true` and `false` as given, and `"auto"` unless `NODE_ENV` is exactly `"production"`.
|
|
6
|
+
*
|
|
7
|
+
* @remarks
|
|
8
|
+
* React picks its development build whenever `NODE_ENV` is not exactly `"production"`, and that build records
|
|
9
|
+
* user-timing entries on every render and never clears them. An
|
|
10
|
+
* unset `NODE_ENV`, the common case for a CLI, leaks like `"development"`, so `"auto"` drains then too. `NODE_ENV` is
|
|
11
|
+
* read through `Config`, so a test sets it with a `ConfigProvider` and `./ui` reads no `process`.
|
|
12
|
+
*
|
|
13
|
+
* @internal
|
|
14
|
+
*/
|
|
15
|
+
const resolveDrain = (mode) => mode === "auto" ? Config.String("NODE_ENV").pipe(Config.withDefault(""), Effect.map((env) => env !== "production"), Effect.orElseSucceed(() => true)) : Effect.succeed(mode);
|
|
16
|
+
/**
|
|
17
|
+
* Clear every user-timing `measure` entry in the process, when `drain` is true.
|
|
18
|
+
*
|
|
19
|
+
* @remarks
|
|
20
|
+
* The clear is global: it removes a consumer's own measures too, because the platform's `clearMeasures` filters only
|
|
21
|
+
* by name, and React's entries (`Update`, `Mount`, tagged `detail.devtools`) share their names with anything a
|
|
22
|
+
* consumer might call a measure. Marks are left alone: React's development build leaks measures only, never marks,
|
|
23
|
+
* so clearing marks would only take a host's own. A runtime without `performance` is left alone.
|
|
24
|
+
*
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
const drainPerformance = (drain) => {
|
|
28
|
+
if (!drain) return;
|
|
29
|
+
globalThis.performance?.clearMeasures?.();
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
//#endregion
|
|
33
|
+
export { drainPerformance, resolveDrain };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//#region src/ui/internal/processStreams.ts
|
|
2
|
+
/**
|
|
3
|
+
* The process's own standard streams, the default `UiStreams`.
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* One of the three files licensed to touch Node: it reads
|
|
7
|
+
* `process.stdin`, `process.stdout` and `process.stderr` and nothing else, and only when called, never at import.
|
|
8
|
+
* The boundary test holds that licence exact.
|
|
9
|
+
*
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
const processStreams = () => ({
|
|
13
|
+
stdin: process.stdin,
|
|
14
|
+
stdout: process.stdout,
|
|
15
|
+
stderr: process.stderr
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
//#endregion
|
|
19
|
+
export { processStreams };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Context } from "effect";
|
|
2
|
+
|
|
3
|
+
//#region src/ui/internal/renderOptions.ts
|
|
4
|
+
/**
|
|
5
|
+
* The render-option overrides in force: none by default. Internal, so `CliUi.run`'s public signature stays as
|
|
6
|
+
* specified; `CliUiTest` sets it.
|
|
7
|
+
*
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
var UiRenderOptions = class extends Context.Reference("@effected/cli/ui/UiRenderOptions", { defaultValue: () => ({}) }) {};
|
|
11
|
+
|
|
12
|
+
//#endregion
|
|
13
|
+
export { UiRenderOptions };
|