@flowscripter/dynamic-cli-framework 1.0.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/.github/workflows/check-bun-dependencies.yml +9 -0
- package/.github/workflows/lint-pr-message.yml +11 -0
- package/.github/workflows/release-bun-library.yml +8 -0
- package/.github/workflows/validate-bun-library-pr.yml +8 -0
- package/LICENSE +21 -0
- package/README.md +1475 -0
- package/bun.lock +63 -0
- package/index.ts +89 -0
- package/package.json +23 -0
- package/src/api/CLI.ts +17 -0
- package/src/api/CLIConfig.ts +19 -0
- package/src/api/Context.ts +25 -0
- package/src/api/RunResult.ts +142 -0
- package/src/api/argument/Argument.ts +36 -0
- package/src/api/argument/ArgumentValueTypes.ts +87 -0
- package/src/api/argument/ComplexOption.ts +31 -0
- package/src/api/argument/GlobalCommandArgument.ts +17 -0
- package/src/api/argument/Option.ts +32 -0
- package/src/api/argument/Positional.ts +27 -0
- package/src/api/argument/SubCommandArgument.ts +20 -0
- package/src/api/command/Command.ts +22 -0
- package/src/api/command/GlobalCommand.ts +33 -0
- package/src/api/command/GlobalModifierCommand.ts +12 -0
- package/src/api/command/GroupCommand.ts +20 -0
- package/src/api/command/SubCommand.ts +41 -0
- package/src/api/command/UsageExample.ts +21 -0
- package/src/api/service/ServiceProvider.ts +52 -0
- package/src/api/service/core/AsciiBannerGeneratorService.ts +35 -0
- package/src/api/service/core/KeyValueService.ts +28 -0
- package/src/api/service/core/PrinterService.ts +266 -0
- package/src/api/service/core/ShutdownService.ts +12 -0
- package/src/api/service/core/SyntaxHighlighterService.ts +42 -0
- package/src/cli/BaseCLI.ts +312 -0
- package/src/cli/DefaultRuntimeCLI.ts +54 -0
- package/src/command/MultiCommandCliHelpCommand.ts +394 -0
- package/src/command/SingleCommandCliHelpCommand.ts +183 -0
- package/src/command/UsageCommand.ts +35 -0
- package/src/command/VersionCommand.ts +22 -0
- package/src/launcher.ts +127 -0
- package/src/runtime/DefaultContext.ts +32 -0
- package/src/runtime/argument/ArgumentTypeGuards.ts +8 -0
- package/src/runtime/command/CommandTypeGuards.ts +30 -0
- package/src/runtime/command/CommandValidator.ts +549 -0
- package/src/runtime/parser.ts +219 -0
- package/src/runtime/registry/CommandRegistry.ts +121 -0
- package/src/runtime/registry/DefaultCommandRegistry.ts +318 -0
- package/src/runtime/registry/DefaultServiceProviderRegistry.ts +26 -0
- package/src/runtime/registry/ServiceProviderRegistry.ts +11 -0
- package/src/runtime/runner.ts +697 -0
- package/src/runtime/scanner.ts +417 -0
- package/src/runtime/values/ValuePopulationResult.ts +41 -0
- package/src/runtime/values/argumentValueMerge.ts +201 -0
- package/src/runtime/values/argumentValueValidation.ts +610 -0
- package/src/runtime/values/globalCommandValuePopulation.ts +96 -0
- package/src/runtime/values/subCommandValuePopulation.ts +680 -0
- package/src/service/asciiBannerGenerator/AsciiBannerGeneratorServiceProvider.ts +45 -0
- package/src/service/asciiBannerGenerator/DefaultAsciiBannerGeneratorService.ts +38 -0
- package/src/service/asciiBannerGenerator/standard.flf.json +3 -0
- package/src/service/banner/BannerServiceProvider.ts +99 -0
- package/src/service/banner/command/NoBannerCommand.ts +42 -0
- package/src/service/configuration/ConfigurationServiceProvider.ts +571 -0
- package/src/service/configuration/DefaultKeyValueService.ts +60 -0
- package/src/service/configuration/command/ConfigCommand.ts +43 -0
- package/src/service/configuration/command/DumpConfigCommand.ts +47 -0
- package/src/service/printer/DefaultPrinterService.ts +256 -0
- package/src/service/printer/PrinterServiceProvider.ts +61 -0
- package/src/service/printer/command/DarkModeCommand.ts +43 -0
- package/src/service/printer/command/LogLevelCommand.ts +68 -0
- package/src/service/printer/command/NoColorCommand.ts +43 -0
- package/src/service/printer/terminal/Ansi.ts +10 -0
- package/src/service/printer/terminal/Color.ts +14 -0
- package/src/service/printer/terminal/Progress.ts +373 -0
- package/src/service/printer/terminal/Spinner.ts +100 -0
- package/src/service/printer/terminal/Styler.ts +5 -0
- package/src/service/printer/terminal/Terminal.ts +8 -0
- package/src/service/printer/terminal/Theme.ts +34 -0
- package/src/service/printer/terminal/TtyStyler.ts +39 -0
- package/src/service/printer/terminal/TtyTerminal.ts +65 -0
- package/src/service/shutdown/DefaultShutdownService.ts +9 -0
- package/src/service/shutdown/ShutdownServiceProvider.ts +55 -0
- package/src/service/syntaxHighlighter/DefaultSyntaxHighlighterService.ts +52 -0
- package/src/service/syntaxHighlighter/SyntaxHighlighterServiceProvider.ts +51 -0
- package/src/util/configHelper.ts +95 -0
- package/src/util/envVarHelper.ts +338 -0
- package/src/util/helpHelper.ts +751 -0
- package/src/util/logger.ts +84 -0
- package/src/util/runnerHelper.ts +112 -0
- package/tests/cli/BaseCLI.test.ts +344 -0
- package/tests/command/MultiCommandCliHelpCommand.test.ts +650 -0
- package/tests/command/SingleCommandCliHelpCommand.test.ts +384 -0
- package/tests/command/UsageCommand.test.ts +53 -0
- package/tests/command/VersionCommand.test.ts +36 -0
- package/tests/fixtures/CLIConfig.ts +9 -0
- package/tests/fixtures/Command.ts +229 -0
- package/tests/fixtures/CommandRegistry.ts +8 -0
- package/tests/fixtures/ConfigurationServiceProvider.ts +28 -0
- package/tests/fixtures/Context.ts +33 -0
- package/tests/fixtures/ServiceProviderRegistry.ts +16 -0
- package/tests/fixtures/StreamString.ts +37 -0
- package/tests/fixtures/util.ts +41 -0
- package/tests/runtime/DefaultContext.test.ts +20 -0
- package/tests/runtime/argument/ArgumentTypeGuards.test.ts +49 -0
- package/tests/runtime/command/CommandTypeGuards.test.ts +43 -0
- package/tests/runtime/command/CommandValidator.test.ts +816 -0
- package/tests/runtime/parser.test.ts +766 -0
- package/tests/runtime/registry/DefaultCommandRegistry.test.ts +166 -0
- package/tests/runtime/registry/DefaultServiceProviderRegistry.test.ts +42 -0
- package/tests/runtime/runner.test.ts +1298 -0
- package/tests/runtime/scanner.test.ts +1113 -0
- package/tests/runtime/values/argumentValueMerge.test.ts +314 -0
- package/tests/runtime/values/argumentValueValidation.test.ts +1020 -0
- package/tests/runtime/values/globalCommandValuePopulation.test.ts +662 -0
- package/tests/runtime/values/subCommandValuePopulation.test.ts +2658 -0
- package/tests/service/asciiBannerGenerator/AsciiBannerGeneratorServiceProvider.test.ts +22 -0
- package/tests/service/asciiBannerGenerator/DefaultAsciiBannerGeneratorService.test.ts +70 -0
- package/tests/service/asciiBannerGenerator/__snapshots__/DefaultAsciiBannerGeneratorService.test.ts.snap +22 -0
- package/tests/service/asciiBannerGenerator/small.flf.json +3 -0
- package/tests/service/configuration/ConfigurationServiceProvider.test.ts +249 -0
- package/tests/service/configuration/DefaultKeyValueService.test.ts +65 -0
- package/tests/service/launchBanner/BannerServiceProvider.test.ts +49 -0
- package/tests/service/launchBanner/__snapshots__/BannerServiceProvider.test.ts.snap +3 -0
- package/tests/service/launchBanner/command/NoBannerCommand.test.ts +21 -0
- package/tests/service/printer/DefaultPrinterService.test.ts +409 -0
- package/tests/service/printer/PrinterServiceProvider.test.ts +29 -0
- package/tests/service/printer/command/LogLevelCommand.test.ts +58 -0
- package/tests/service/printer/terminal/Progress.test.ts +40 -0
- package/tests/service/printer/terminal/Spinner.test.ts +57 -0
- package/tests/service/shutdown/ShutdownServiceProvider.test.ts +13 -0
- package/tests/service/syntaxHighlighter/DefaultSyntaxHighlighterService.test.ts +123 -0
- package/tests/service/syntaxHighlighter/SyntaxHighlighterServiceProvider.test.ts +41 -0
- package/tests/util/configHelper.test.ts +275 -0
- package/tests/util/envVarHelper.test.ts +318 -0
- package/tests/util/logger.test.ts +10 -0
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import Styler from "./Styler.ts";
|
|
2
|
+
import type Terminal from "./Terminal.ts";
|
|
3
|
+
|
|
4
|
+
const RATE_SMOOTHING_FACTOR = 0.005;
|
|
5
|
+
|
|
6
|
+
interface Bar {
|
|
7
|
+
name: string;
|
|
8
|
+
current: number;
|
|
9
|
+
total: number;
|
|
10
|
+
units: string;
|
|
11
|
+
lastMillis?: number;
|
|
12
|
+
startMillis?: number;
|
|
13
|
+
endMillis?: number;
|
|
14
|
+
rate?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default class Progress {
|
|
18
|
+
readonly #terminal: Terminal;
|
|
19
|
+
readonly #styler: Styler;
|
|
20
|
+
readonly #bars: Map<number, Bar> = new Map();
|
|
21
|
+
#timer: Timer | undefined;
|
|
22
|
+
#isDirty = false;
|
|
23
|
+
#currentRenderedBarCount = 0;
|
|
24
|
+
#lastRenderTime = 0;
|
|
25
|
+
#comColor = 0x5f8700;
|
|
26
|
+
#remColor = 0x585858;
|
|
27
|
+
#labColor = 0x585858;
|
|
28
|
+
#valColor = 0x808080;
|
|
29
|
+
|
|
30
|
+
public constructor(terminal: Terminal, styler: Styler) {
|
|
31
|
+
this.#terminal = terminal;
|
|
32
|
+
this.#styler = styler;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public add(
|
|
36
|
+
units: string,
|
|
37
|
+
message: string,
|
|
38
|
+
total: number,
|
|
39
|
+
current: number,
|
|
40
|
+
): number {
|
|
41
|
+
if (total < 0) {
|
|
42
|
+
total = 100;
|
|
43
|
+
}
|
|
44
|
+
if (current < 0) {
|
|
45
|
+
current = 0;
|
|
46
|
+
}
|
|
47
|
+
if (current > total) {
|
|
48
|
+
current = total;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
this.#bars.set(this.#bars.size + 1, {
|
|
52
|
+
name: message,
|
|
53
|
+
current,
|
|
54
|
+
total,
|
|
55
|
+
units,
|
|
56
|
+
startMillis: Date.now(),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// force render on next timeout
|
|
60
|
+
this.#isDirty = true;
|
|
61
|
+
|
|
62
|
+
// start rendering timer
|
|
63
|
+
if (this.#timer === undefined) {
|
|
64
|
+
this.#timer = setInterval(async () => {
|
|
65
|
+
await this.#renderBars();
|
|
66
|
+
}, 100);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return this.#bars.size;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
#updateRate(bar: Bar, current: number): void {
|
|
73
|
+
const now = Date.now();
|
|
74
|
+
if (bar.lastMillis === undefined) {
|
|
75
|
+
bar.rate = ((current - bar.current) * 1000) / (now - bar.startMillis!);
|
|
76
|
+
} else {
|
|
77
|
+
let currentRate = ((current - bar.current) * 1000) /
|
|
78
|
+
(now - bar.lastMillis!);
|
|
79
|
+
if (!isFinite(currentRate)) {
|
|
80
|
+
currentRate = 0;
|
|
81
|
+
}
|
|
82
|
+
bar.rate = (currentRate * RATE_SMOOTHING_FACTOR) +
|
|
83
|
+
(bar.rate! * (1 - RATE_SMOOTHING_FACTOR));
|
|
84
|
+
}
|
|
85
|
+
bar.lastMillis = now;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
public update(handle: number, current: number, message?: string): void {
|
|
89
|
+
if (!this.#bars.has(handle)) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const bar = this.#bars.get(handle)!;
|
|
93
|
+
if (current < 0) {
|
|
94
|
+
current = 0;
|
|
95
|
+
}
|
|
96
|
+
if (current > bar.total) {
|
|
97
|
+
current = bar.total;
|
|
98
|
+
}
|
|
99
|
+
if ((current === bar.total) && (bar.endMillis === undefined)) {
|
|
100
|
+
bar.endMillis = Date.now();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
this.#updateRate(bar, current);
|
|
104
|
+
|
|
105
|
+
bar.current = current;
|
|
106
|
+
|
|
107
|
+
if (message !== undefined) {
|
|
108
|
+
bar.name = message;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// force render on next timeout
|
|
112
|
+
this.#isDirty = true;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
public async hide(handle: number): Promise<void> {
|
|
116
|
+
// clear specified bar
|
|
117
|
+
if (this.#bars.has(handle)) {
|
|
118
|
+
this.#bars.delete(handle);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// if no more bars, effectively this is a pause
|
|
122
|
+
if (this.#bars.size === 0) {
|
|
123
|
+
await this.pause();
|
|
124
|
+
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// otherwise, force render on next timeout
|
|
129
|
+
this.#isDirty = true;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
public async hideAll(): Promise<void> {
|
|
133
|
+
// clear all bars
|
|
134
|
+
this.#bars.clear();
|
|
135
|
+
|
|
136
|
+
await this.pause();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
public async pause(): Promise<void> {
|
|
140
|
+
if (this.#timer === undefined) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
this.#isDirty = false;
|
|
145
|
+
|
|
146
|
+
// stop rendering timer
|
|
147
|
+
clearInterval(this.#timer);
|
|
148
|
+
this.#timer = undefined;
|
|
149
|
+
|
|
150
|
+
// clear the previously rendered bars
|
|
151
|
+
await this.#terminal.clearUpLines(this.#currentRenderedBarCount * 2);
|
|
152
|
+
|
|
153
|
+
this.#currentRenderedBarCount = 0;
|
|
154
|
+
|
|
155
|
+
// restore the cursor
|
|
156
|
+
await this.#terminal.showCursor();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
public resume(): void {
|
|
160
|
+
// don't resume if nothing to render
|
|
161
|
+
if (this.#bars.size === 0) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// force render on next timeout
|
|
166
|
+
this.#isDirty = true;
|
|
167
|
+
|
|
168
|
+
// start rendering timer
|
|
169
|
+
if (this.#timer === undefined) {
|
|
170
|
+
this.#timer = setInterval(async () => {
|
|
171
|
+
await this.#renderBars();
|
|
172
|
+
}, 100);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async #renderBars(): Promise<void> {
|
|
177
|
+
const now = Date.now();
|
|
178
|
+
|
|
179
|
+
// force render after one second to update newly calculated eta
|
|
180
|
+
if ((now - this.#lastRenderTime >= 1000) && !this.#isDirty) {
|
|
181
|
+
this.#bars.forEach((bar) => this.#updateRate(bar, bar.current));
|
|
182
|
+
this.#isDirty = true;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// don't render if we don't need to
|
|
186
|
+
if (!this.#isDirty) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
this.#lastRenderTime = now;
|
|
191
|
+
|
|
192
|
+
const consoleWidth = this.#terminal.columns();
|
|
193
|
+
const lines: Array<string> = [];
|
|
194
|
+
|
|
195
|
+
const barInfo: Array<
|
|
196
|
+
{
|
|
197
|
+
name: string;
|
|
198
|
+
prefix: string;
|
|
199
|
+
available: number;
|
|
200
|
+
suffix: string;
|
|
201
|
+
bar: Bar;
|
|
202
|
+
}
|
|
203
|
+
> = [];
|
|
204
|
+
|
|
205
|
+
this.#bars.forEach((bar) => {
|
|
206
|
+
const name = `${this.#styler.colorText(bar.name, this.#valColor)}${
|
|
207
|
+
this.#styler.colorText(":", this.#labColor)
|
|
208
|
+
}`;
|
|
209
|
+
const prefix = `${this.#styler.colorText("[", this.#labColor)}`;
|
|
210
|
+
let visibleWidth = 1;
|
|
211
|
+
const percentString = ((bar.current / bar.total) * 100).toFixed(2);
|
|
212
|
+
const percent = this.#styler.colorText(
|
|
213
|
+
percentString,
|
|
214
|
+
this.#valColor,
|
|
215
|
+
) + this.#styler.colorText("%,", this.#labColor);
|
|
216
|
+
visibleWidth += percentString.length + 1;
|
|
217
|
+
|
|
218
|
+
const rate = `${
|
|
219
|
+
this.#styler.colorText(
|
|
220
|
+
(bar.rate === undefined) ? "-" : bar.rate.toFixed(2) + "",
|
|
221
|
+
this.#valColor,
|
|
222
|
+
)
|
|
223
|
+
}`;
|
|
224
|
+
|
|
225
|
+
let suffix = `${this.#styler.colorText("]", this.#labColor)} ${percent} `;
|
|
226
|
+
visibleWidth += 3;
|
|
227
|
+
if (bar.current === bar.total) {
|
|
228
|
+
const taken = this.#formatTime(bar.endMillis! - bar.startMillis!);
|
|
229
|
+
const totalString = bar.total + "";
|
|
230
|
+
suffix += `${this.#styler.colorText(totalString, this.#valColor)}${
|
|
231
|
+
this.#styler.colorText(bar.units + ", rate:", this.#labColor)
|
|
232
|
+
} ${rate}${
|
|
233
|
+
this.#styler.colorText(
|
|
234
|
+
bar.units + "/s, time taken:",
|
|
235
|
+
this.#labColor,
|
|
236
|
+
)
|
|
237
|
+
} ${taken}`;
|
|
238
|
+
visibleWidth + totalString.length + bar.units.length + 7 + 1 +
|
|
239
|
+
rate.length + bar.units.length + 15 + 1 + taken.length;
|
|
240
|
+
} else {
|
|
241
|
+
const remaining = ((bar.rate === undefined) || (bar.rate === 0))
|
|
242
|
+
? "-"
|
|
243
|
+
: this.#formatTime(((bar.total - bar.current) / bar.rate) * 1000);
|
|
244
|
+
const currentString = bar.current + "";
|
|
245
|
+
const totalString = bar.total + "";
|
|
246
|
+
suffix += `${this.#styler.colorText(currentString, this.#valColor)}${
|
|
247
|
+
this.#styler.colorText("/", this.#labColor)
|
|
248
|
+
}${this.#styler.colorText(totalString, this.#valColor)}${
|
|
249
|
+
this.#styler.colorText(bar.units + ", rate:", this.#labColor)
|
|
250
|
+
} ${rate}${
|
|
251
|
+
this.#styler.colorText(
|
|
252
|
+
bar.units + "/s, time remaining:",
|
|
253
|
+
this.#labColor,
|
|
254
|
+
)
|
|
255
|
+
} ${remaining}`;
|
|
256
|
+
visibleWidth + currentString.length + 1 + totalString.length +
|
|
257
|
+
bar.units.length + 7 + 1 + rate.length + bar.units.length + 19 + 1 +
|
|
258
|
+
remaining.length;
|
|
259
|
+
}
|
|
260
|
+
let available = consoleWidth - visibleWidth;
|
|
261
|
+
if (available < 0) {
|
|
262
|
+
available = 0;
|
|
263
|
+
}
|
|
264
|
+
barInfo.push({
|
|
265
|
+
name,
|
|
266
|
+
prefix,
|
|
267
|
+
available,
|
|
268
|
+
suffix,
|
|
269
|
+
bar,
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
let totalLength = consoleWidth;
|
|
274
|
+
barInfo.forEach((barInfo) => {
|
|
275
|
+
if (barInfo.available < totalLength) {
|
|
276
|
+
totalLength = barInfo.available;
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
totalLength = Math.min(50, totalLength);
|
|
280
|
+
|
|
281
|
+
barInfo.forEach((barInfo) => {
|
|
282
|
+
const completeLength = Math.floor(
|
|
283
|
+
totalLength * barInfo.bar.current / barInfo.bar.total,
|
|
284
|
+
);
|
|
285
|
+
const complete = this.#styler.colorText(
|
|
286
|
+
"=".repeat(completeLength),
|
|
287
|
+
this.#comColor,
|
|
288
|
+
);
|
|
289
|
+
const remaining = this.#styler.colorText(
|
|
290
|
+
"-".repeat(totalLength - completeLength),
|
|
291
|
+
this.#remColor,
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
lines.push(barInfo.name);
|
|
295
|
+
lines.push(barInfo.prefix + complete + remaining + barInfo.suffix);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// hide the cursor before rendering
|
|
299
|
+
await this.#terminal.hideCursor();
|
|
300
|
+
|
|
301
|
+
// clear the previously rendered bars
|
|
302
|
+
await this.#terminal.clearUpLines(this.#currentRenderedBarCount * 2);
|
|
303
|
+
|
|
304
|
+
// rendere the new bars
|
|
305
|
+
await this.#terminal.write(lines.join("\n") + "\n");
|
|
306
|
+
|
|
307
|
+
// save count rendered bars so we can clear on the next render cycle
|
|
308
|
+
this.#currentRenderedBarCount = barInfo.length;
|
|
309
|
+
|
|
310
|
+
// show the cursor if there are no bars or the timer is not running
|
|
311
|
+
if (this.#bars.size === 0 || this.#timer === undefined) {
|
|
312
|
+
await this.#terminal.showCursor();
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
this.#isDirty = false;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
set completeColor(color: number) {
|
|
319
|
+
this.#comColor = color;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
set remainingColor(color: number) {
|
|
323
|
+
this.#remColor = color;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
set labelColor(color: number) {
|
|
327
|
+
this.#labColor = color;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
set valueColor(color: number) {
|
|
331
|
+
this.#valColor = color;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
#formatTime(millis: number): string {
|
|
335
|
+
let sec = millis / 1000;
|
|
336
|
+
if (sec < 60) {
|
|
337
|
+
return `${this.#styler.colorText(sec.toFixed(0), this.#valColor)}${
|
|
338
|
+
this.#styler.colorText("s", this.#labColor)
|
|
339
|
+
}`;
|
|
340
|
+
}
|
|
341
|
+
let min = Math.floor(sec / 60);
|
|
342
|
+
sec %= 60;
|
|
343
|
+
if (min < 60) {
|
|
344
|
+
return `${this.#styler.colorText(min.toFixed(0), this.#valColor)}${
|
|
345
|
+
this.#styler.colorText("m", this.#labColor)
|
|
346
|
+
} ${this.#styler.colorText(sec.toFixed(0), this.#valColor)}${
|
|
347
|
+
this.#styler.colorText("s", this.#labColor)
|
|
348
|
+
}`;
|
|
349
|
+
}
|
|
350
|
+
let hour = Math.floor(min / 60);
|
|
351
|
+
min %= 60;
|
|
352
|
+
if (hour < 24) {
|
|
353
|
+
return `${this.#styler.colorText(hour.toFixed(0), this.#valColor)}${
|
|
354
|
+
this.#styler.colorText("h", this.#labColor)
|
|
355
|
+
} ${this.#styler.colorText(min.toFixed(0), this.#valColor)}${
|
|
356
|
+
this.#styler.colorText("m", this.#labColor)
|
|
357
|
+
} ${this.#styler.colorText(sec.toFixed(0), this.#valColor)}${
|
|
358
|
+
this.#styler.colorText("s", this.#labColor)
|
|
359
|
+
}`;
|
|
360
|
+
}
|
|
361
|
+
const day = Math.floor(hour / 24);
|
|
362
|
+
hour %= 24;
|
|
363
|
+
return `${this.#styler.colorText(day.toFixed(0), this.#valColor)}${
|
|
364
|
+
this.#styler.colorText("d", this.#labColor)
|
|
365
|
+
} ${this.#styler.colorText(hour.toFixed(0), this.#valColor)}${
|
|
366
|
+
this.#styler.colorText("h", this.#labColor)
|
|
367
|
+
} ${this.#styler.colorText(min.toFixed(0), this.#valColor)}${
|
|
368
|
+
this.#styler.colorText("m", this.#labColor)
|
|
369
|
+
} ${this.#styler.colorText(sec.toFixed(0), this.#valColor)}${
|
|
370
|
+
this.#styler.colorText("s", this.#labColor)
|
|
371
|
+
}`;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import Styler from "./Styler.ts";
|
|
2
|
+
import type Terminal from "./Terminal.ts";
|
|
3
|
+
|
|
4
|
+
const FRAMES = [
|
|
5
|
+
"⠋",
|
|
6
|
+
"⠙",
|
|
7
|
+
"⠹",
|
|
8
|
+
"⠸",
|
|
9
|
+
"⠼",
|
|
10
|
+
"⠴",
|
|
11
|
+
"⠦",
|
|
12
|
+
"⠧",
|
|
13
|
+
"⠇",
|
|
14
|
+
"⠏",
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
export default class Spinner {
|
|
18
|
+
#isShown = false;
|
|
19
|
+
#message: string | undefined;
|
|
20
|
+
#frameIndex = 0;
|
|
21
|
+
#timer: Timer | undefined;
|
|
22
|
+
#spinColor = 0x8a8a8a;
|
|
23
|
+
#msgColor = 0x808080;
|
|
24
|
+
readonly #terminal: Terminal;
|
|
25
|
+
readonly #styler: Styler;
|
|
26
|
+
|
|
27
|
+
public constructor(terminal: Terminal, styler: Styler) {
|
|
28
|
+
this.#terminal = terminal;
|
|
29
|
+
this.#styler = styler;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async #nextFrame(): Promise<void> {
|
|
33
|
+
if (!this.#isShown) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
await this.#terminal.clearLine();
|
|
37
|
+
if (this.#message) {
|
|
38
|
+
await this.#terminal.write(
|
|
39
|
+
`${this.#styler.colorText(FRAMES[this.#frameIndex], this.#spinColor)} ${
|
|
40
|
+
this.#styler.colorText(this.#message!, this.#msgColor)
|
|
41
|
+
}`,
|
|
42
|
+
);
|
|
43
|
+
} else {
|
|
44
|
+
await this.#terminal.write(
|
|
45
|
+
this.#styler.colorText(FRAMES[this.#frameIndex], this.#spinColor),
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
this.#frameIndex = (this.#frameIndex + 1) % FRAMES.length;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
public async show(message?: string): Promise<void> {
|
|
52
|
+
this.#message = message;
|
|
53
|
+
if (this.#isShown) {
|
|
54
|
+
return Promise.resolve();
|
|
55
|
+
}
|
|
56
|
+
this.#isShown = true;
|
|
57
|
+
this.#frameIndex = 0;
|
|
58
|
+
this.#timer = setInterval(async () => {
|
|
59
|
+
await this.#nextFrame();
|
|
60
|
+
}, 100);
|
|
61
|
+
await this.#terminal.hideCursor();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
public async hide(): Promise<void> {
|
|
65
|
+
if (!this.#isShown) {
|
|
66
|
+
return Promise.resolve();
|
|
67
|
+
}
|
|
68
|
+
this.#isShown = false;
|
|
69
|
+
clearInterval(this.#timer);
|
|
70
|
+
await this.#terminal.clearLine();
|
|
71
|
+
await this.#terminal.showCursor();
|
|
72
|
+
this.#message = undefined;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
public async pause(): Promise<void> {
|
|
76
|
+
if (!this.#isShown) {
|
|
77
|
+
return Promise.resolve();
|
|
78
|
+
}
|
|
79
|
+
clearInterval(this.#timer);
|
|
80
|
+
this.#timer = undefined;
|
|
81
|
+
await this.#terminal.clearLine();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
public resume(): void {
|
|
85
|
+
if ((!this.#isShown) || (this.#timer !== undefined)) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
this.#timer = setInterval(async () => {
|
|
89
|
+
await this.#nextFrame();
|
|
90
|
+
}, 100);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
set spinnerColor(color: number) {
|
|
94
|
+
this.#spinColor = color;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
set messageColor(color: number) {
|
|
98
|
+
this.#msgColor = color;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Color } from "./Color";
|
|
2
|
+
|
|
3
|
+
const commonTheme: Array<number> = [];
|
|
4
|
+
|
|
5
|
+
commonTheme[Color.YELLOW] = 0xb58900;
|
|
6
|
+
commonTheme[Color.ORANGE] = 0xcb4b16;
|
|
7
|
+
commonTheme[Color.RED] = 0xdc322f;
|
|
8
|
+
commonTheme[Color.MAGENTA] = 0xd33682;
|
|
9
|
+
commonTheme[Color.VIOLET] = 0x6c71c4;
|
|
10
|
+
commonTheme[Color.BLUE] = 0x268bd2;
|
|
11
|
+
commonTheme[Color.CYAN] = 0x2aa198;
|
|
12
|
+
commonTheme[Color.GREEN] = 0x859900;
|
|
13
|
+
|
|
14
|
+
export function getLightModeTheme(): Array<number> {
|
|
15
|
+
const lightModeTheme: Array<number> = [...commonTheme];
|
|
16
|
+
|
|
17
|
+
lightModeTheme[Color.PRIMARY] = 0x657b83;
|
|
18
|
+
lightModeTheme[Color.SECONDARY] = 0x93a1a1;
|
|
19
|
+
lightModeTheme[Color.EMPHASISED] = 0x586e75;
|
|
20
|
+
lightModeTheme[Color.SELECTED] = 0xeee8d5;
|
|
21
|
+
|
|
22
|
+
return lightModeTheme;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function getDarkModeTheme(): Array<number> {
|
|
26
|
+
const darkModeTheme: Array<number> = [...commonTheme];
|
|
27
|
+
|
|
28
|
+
darkModeTheme[Color.PRIMARY] = 0x839496;
|
|
29
|
+
darkModeTheme[Color.SECONDARY] = 0x586e75;
|
|
30
|
+
darkModeTheme[Color.EMPHASISED] = 0x93a1a1;
|
|
31
|
+
darkModeTheme[Color.SELECTED] = 0x073642;
|
|
32
|
+
|
|
33
|
+
return darkModeTheme;
|
|
34
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { COLOR_END, ITALIC_END, ITALIC_START } from "./Ansi.ts";
|
|
2
|
+
import Styler from "./Styler.ts";
|
|
3
|
+
|
|
4
|
+
export default class TtyStyler implements Styler {
|
|
5
|
+
colorEnabled: boolean = true;
|
|
6
|
+
|
|
7
|
+
readonly colorLevel: number;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The color level in use.
|
|
11
|
+
*
|
|
12
|
+
* 1 = 16 color
|
|
13
|
+
* 2 = 256 color
|
|
14
|
+
* 3 = 16 million color
|
|
15
|
+
*/
|
|
16
|
+
constructor(colorLevel: number = 1) {
|
|
17
|
+
this.colorLevel = colorLevel;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
colorText(text: string, colorValue: number): string {
|
|
21
|
+
if (!this.colorEnabled) {
|
|
22
|
+
return text;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (this.colorLevel === 3) {
|
|
26
|
+
return Bun.color(colorValue, "ansi-16m") + text + COLOR_END;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (this.colorLevel === 2) {
|
|
30
|
+
return Bun.color(colorValue, "ansi-256") + text + COLOR_END;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return Bun.color(colorValue, "ansi-16") + text + COLOR_END;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
italicText(text: string): string {
|
|
37
|
+
return ITALIC_START + text + ITALIC_END;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { WriteStream } from "node:tty";
|
|
2
|
+
import type Terminal from "./Terminal.ts";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
CLEAR_LINE,
|
|
6
|
+
CURSOR_LEFT,
|
|
7
|
+
CURSOR_UP,
|
|
8
|
+
HIDE_CURSOR,
|
|
9
|
+
SHOW_CURSOR,
|
|
10
|
+
} from "./Ansi.ts";
|
|
11
|
+
|
|
12
|
+
export default class TtyTerminal implements Terminal {
|
|
13
|
+
readonly #ttyWriteStream: WriteStream;
|
|
14
|
+
readonly #encoder = new TextEncoder();
|
|
15
|
+
|
|
16
|
+
constructor(ttyWriteStream: WriteStream) {
|
|
17
|
+
if (ttyWriteStream.isTTY === false) {
|
|
18
|
+
throw new Error("The provided WriteStream is not a TTY");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
this.#ttyWriteStream = ttyWriteStream;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
#writeAll(message: string): void {
|
|
25
|
+
const encoded = this.#encoder.encode(message);
|
|
26
|
+
|
|
27
|
+
this.#ttyWriteStream.write(encoded);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
clearLine(): Promise<void> {
|
|
31
|
+
this.#writeAll(CLEAR_LINE + CURSOR_LEFT);
|
|
32
|
+
|
|
33
|
+
return Promise.resolve();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
clearUpLines(count: number): Promise<void> {
|
|
37
|
+
this.#writeAll(
|
|
38
|
+
(CURSOR_UP + CLEAR_LINE).repeat(count) + CLEAR_LINE + CURSOR_LEFT,
|
|
39
|
+
);
|
|
40
|
+
return Promise.resolve();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
hideCursor(): Promise<void> {
|
|
44
|
+
this.#writeAll(HIDE_CURSOR);
|
|
45
|
+
return Promise.resolve();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
showCursor(): Promise<void> {
|
|
49
|
+
this.#writeAll(SHOW_CURSOR);
|
|
50
|
+
return Promise.resolve();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
write(text: string): Promise<void> {
|
|
54
|
+
this.#writeAll(text);
|
|
55
|
+
return Promise.resolve();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
columns(): number {
|
|
59
|
+
try {
|
|
60
|
+
return this.#ttyWriteStream.columns;
|
|
61
|
+
} catch {
|
|
62
|
+
return 80;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type ShutdownService from "../../api/service/core/ShutdownService.ts";
|
|
2
|
+
|
|
3
|
+
export default class DefaultShutdownService implements ShutdownService {
|
|
4
|
+
static readonly callbacks: Array<() => Promise<void>> = [];
|
|
5
|
+
|
|
6
|
+
addShutdownListener(callback: () => Promise<void>): void {
|
|
7
|
+
DefaultShutdownService.callbacks.push(callback);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
import type {
|
|
3
|
+
ServiceInfo,
|
|
4
|
+
ServiceProvider,
|
|
5
|
+
} from "../../api/service/ServiceProvider.ts";
|
|
6
|
+
import DefaultShutdownService from "./DefaultShutdownService.ts";
|
|
7
|
+
import type ShutdownService from "../../api/service/core/ShutdownService.ts";
|
|
8
|
+
import { SHUTDOWN_SERVICE_ID } from "../../api/service/core/ShutdownService.ts";
|
|
9
|
+
import type Context from "../../api/Context.ts";
|
|
10
|
+
import getLogger from "../../util/logger.ts";
|
|
11
|
+
import type CLIConfig from "../../api/CLIConfig.ts";
|
|
12
|
+
|
|
13
|
+
const logger = getLogger("ShutdownServiceProvider");
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Provides a {@link ShutdownService}.
|
|
17
|
+
*/
|
|
18
|
+
export default class ShutdownServiceProvider implements ServiceProvider {
|
|
19
|
+
readonly serviceId: string = SHUTDOWN_SERVICE_ID;
|
|
20
|
+
readonly #shutdownService: ShutdownService;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Create an instance of the service provider with the specified details.
|
|
24
|
+
*
|
|
25
|
+
* @param servicePriority the priority of the service.
|
|
26
|
+
*/
|
|
27
|
+
public constructor(
|
|
28
|
+
readonly servicePriority: number,
|
|
29
|
+
) {
|
|
30
|
+
this.#shutdownService = new DefaultShutdownService();
|
|
31
|
+
process.on("beforeExit", ShutdownServiceProvider.shutdown);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public provide(_cliConfig: CLIConfig): Promise<ServiceInfo> {
|
|
35
|
+
return Promise.resolve({
|
|
36
|
+
service: this.#shutdownService,
|
|
37
|
+
commands: [],
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
initService(_context: Context): Promise<void> {
|
|
42
|
+
return Promise.resolve(undefined);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static async shutdown() {
|
|
46
|
+
try {
|
|
47
|
+
process.removeListener("beforeExit", ShutdownServiceProvider.shutdown);
|
|
48
|
+
for await (const callback of DefaultShutdownService.callbacks) {
|
|
49
|
+
await callback();
|
|
50
|
+
}
|
|
51
|
+
} catch (error) {
|
|
52
|
+
logger.error("shutdown error: %s", (error as Error).message);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|