ts-runtime-validation 1.6.16 → 1.8.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/CONTRIBUTING.md +430 -0
- package/README.md +505 -62
- package/dist/ICommandOptions.js +3 -0
- package/dist/ICommandOptions.js.map +1 -0
- package/dist/SchemaGenerator.deterministic-extended.test.js +420 -0
- package/dist/SchemaGenerator.deterministic-extended.test.js.map +1 -0
- package/dist/SchemaGenerator.deterministic.test.js +251 -0
- package/dist/SchemaGenerator.deterministic.test.js.map +1 -0
- package/dist/SchemaGenerator.integration.test.js +323 -0
- package/dist/SchemaGenerator.integration.test.js.map +1 -0
- package/dist/SchemaGenerator.js +120 -0
- package/dist/SchemaGenerator.js.map +1 -0
- package/dist/SchemaGenerator.test.js +226 -0
- package/dist/SchemaGenerator.test.js.map +1 -0
- package/dist/cli.test.js +155 -0
- package/dist/cli.test.js.map +1 -0
- package/dist/errors/index.js +95 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/errors/index.test.js +232 -0
- package/dist/errors/index.test.js.map +1 -0
- package/dist/getPosixPath.js +13 -0
- package/dist/getPosixPath.js.map +1 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/lib.js.map +1 -0
- package/dist/services/CodeGenerator.js +321 -0
- package/dist/services/CodeGenerator.js.map +1 -0
- package/dist/services/FileDiscovery.js +123 -0
- package/dist/services/FileDiscovery.js.map +1 -0
- package/dist/services/FileDiscovery.test.js +184 -0
- package/dist/services/FileDiscovery.test.js.map +1 -0
- package/dist/services/SchemaProcessor.js +198 -0
- package/dist/services/SchemaProcessor.js.map +1 -0
- package/dist/services/SchemaProcessor.test.js +455 -0
- package/dist/services/SchemaProcessor.test.js.map +1 -0
- package/dist/services/SchemaWriter.js +76 -0
- package/dist/services/SchemaWriter.js.map +1 -0
- package/dist/services/SchemaWriter.test.js +255 -0
- package/dist/services/SchemaWriter.test.js.map +1 -0
- package/dist/test/basic-scenario/types.jsonschema.js +3 -0
- package/dist/test/basic-scenario/types.jsonschema.js.map +1 -0
- package/dist/test/duplicate-symbols-different-implementation/IBaseType.js +3 -0
- package/dist/test/duplicate-symbols-different-implementation/IBaseType.js.map +1 -0
- package/dist/test/duplicate-symbols-different-implementation/IBaseTypeDefinitionReplicated.js +3 -0
- package/dist/test/duplicate-symbols-different-implementation/IBaseTypeDefinitionReplicated.js.map +1 -0
- package/dist/test/duplicate-symbols-different-implementation/IBasicTypesA.jsonschema.js +3 -0
- package/dist/test/duplicate-symbols-different-implementation/IBasicTypesA.jsonschema.js.map +1 -0
- package/dist/test/duplicate-symbols-different-implementation/IBasicTypesB.jsonschema.js +3 -0
- package/dist/test/duplicate-symbols-different-implementation/IBasicTypesB.jsonschema.js.map +1 -0
- package/dist/test/duplicate-symbols-identitcal-implementation/IBaseType.js +3 -0
- package/dist/test/duplicate-symbols-identitcal-implementation/IBaseType.js.map +1 -0
- package/dist/test/duplicate-symbols-identitcal-implementation/IBaseTypeDefinitionReplicated.js +3 -0
- package/dist/test/duplicate-symbols-identitcal-implementation/IBaseTypeDefinitionReplicated.js.map +1 -0
- package/dist/test/duplicate-symbols-identitcal-implementation/IBasicTypesA.jsonschema.js +3 -0
- package/dist/test/duplicate-symbols-identitcal-implementation/IBasicTypesA.jsonschema.js.map +1 -0
- package/dist/test/duplicate-symbols-identitcal-implementation/IBasicTypesB.jsonschema.js +3 -0
- package/dist/test/duplicate-symbols-identitcal-implementation/IBasicTypesB.jsonschema.js.map +1 -0
- package/dist/utils/ProgressReporter.js +67 -0
- package/dist/utils/ProgressReporter.js.map +1 -0
- package/dist/utils/ProgressReporter.test.js +267 -0
- package/dist/utils/ProgressReporter.test.js.map +1 -0
- package/dist/writeLine.js +12 -0
- package/dist/writeLine.js.map +1 -0
- package/package.json +2 -2
- package/src/ICommandOptions.ts +7 -0
- package/src/SchemaGenerator.deterministic-extended.test.ts +429 -0
- package/src/SchemaGenerator.deterministic.test.ts +276 -0
- package/src/SchemaGenerator.integration.test.ts +411 -0
- package/src/SchemaGenerator.test.ts +118 -0
- package/src/SchemaGenerator.ts +112 -298
- package/src/cli.test.ts +130 -0
- package/src/errors/index.test.ts +319 -0
- package/src/errors/index.ts +92 -0
- package/src/index.ts +8 -1
- package/src/services/CodeGenerator.ts +370 -0
- package/src/services/FileDiscovery.test.ts +216 -0
- package/src/services/FileDiscovery.ts +140 -0
- package/src/services/SchemaProcessor.test.ts +536 -0
- package/src/services/SchemaProcessor.ts +194 -0
- package/src/services/SchemaWriter.test.ts +304 -0
- package/src/services/SchemaWriter.ts +75 -0
- package/src/utils/ProgressReporter.test.ts +357 -0
- package/src/utils/ProgressReporter.ts +76 -0
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
import { ProgressReporter } from "./ProgressReporter";
|
|
2
|
+
|
|
3
|
+
// Mock process.stdout for testing
|
|
4
|
+
const mockStdout = {
|
|
5
|
+
isTTY: true,
|
|
6
|
+
clearLine: jest.fn(),
|
|
7
|
+
cursorTo: jest.fn(),
|
|
8
|
+
write: jest.fn()
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const originalStdout = process.stdout;
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
// Reset mock functions
|
|
15
|
+
mockStdout.clearLine = jest.fn();
|
|
16
|
+
mockStdout.cursorTo = jest.fn();
|
|
17
|
+
mockStdout.write = jest.fn();
|
|
18
|
+
mockStdout.isTTY = true;
|
|
19
|
+
|
|
20
|
+
// Replace process.stdout with mock
|
|
21
|
+
Object.defineProperty(process, 'stdout', {
|
|
22
|
+
value: mockStdout,
|
|
23
|
+
configurable: true
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
afterAll(() => {
|
|
28
|
+
// Restore original stdout
|
|
29
|
+
Object.defineProperty(process, 'stdout', {
|
|
30
|
+
value: originalStdout,
|
|
31
|
+
configurable: true
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe("ProgressReporter", () => {
|
|
36
|
+
describe("disabled reporter", () => {
|
|
37
|
+
it("should not output anything when disabled", () => {
|
|
38
|
+
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
39
|
+
|
|
40
|
+
const reporter = new ProgressReporter({
|
|
41
|
+
enabled: false,
|
|
42
|
+
total: 100,
|
|
43
|
+
showBar: true
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
reporter.start("Starting task");
|
|
47
|
+
reporter.update(50, "Half done");
|
|
48
|
+
reporter.increment("Almost there");
|
|
49
|
+
reporter.complete("Task completed");
|
|
50
|
+
|
|
51
|
+
expect(consoleSpy).not.toHaveBeenCalled();
|
|
52
|
+
expect(mockStdout.write).not.toHaveBeenCalled();
|
|
53
|
+
|
|
54
|
+
consoleSpy.mockRestore();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe("enabled reporter without progress bar", () => {
|
|
59
|
+
it("should show start and complete messages", () => {
|
|
60
|
+
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
61
|
+
|
|
62
|
+
const reporter = new ProgressReporter({
|
|
63
|
+
enabled: true,
|
|
64
|
+
showBar: false
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
reporter.start("Starting task");
|
|
68
|
+
reporter.complete("Task completed");
|
|
69
|
+
|
|
70
|
+
expect(consoleSpy).toHaveBeenCalledWith("Starting task");
|
|
71
|
+
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("Task completed"));
|
|
72
|
+
|
|
73
|
+
consoleSpy.mockRestore();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("should show custom update messages", () => {
|
|
77
|
+
const reporter = new ProgressReporter({
|
|
78
|
+
enabled: true,
|
|
79
|
+
showBar: false
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
reporter.start("Starting");
|
|
83
|
+
reporter.update(25, "Processing file 1");
|
|
84
|
+
reporter.update(50, "Processing file 2");
|
|
85
|
+
|
|
86
|
+
expect(mockStdout.write).toHaveBeenCalledWith("Processing file 1");
|
|
87
|
+
expect(mockStdout.write).toHaveBeenCalledWith("Processing file 2");
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("should handle increment without message", () => {
|
|
91
|
+
const reporter = new ProgressReporter({
|
|
92
|
+
enabled: true,
|
|
93
|
+
showBar: false
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
reporter.start("Starting");
|
|
97
|
+
reporter.increment();
|
|
98
|
+
|
|
99
|
+
// Should not crash when incrementing without message
|
|
100
|
+
expect(() => reporter.increment()).not.toThrow();
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe("enabled reporter with progress bar", () => {
|
|
105
|
+
it("should draw progress bar", () => {
|
|
106
|
+
const reporter = new ProgressReporter({
|
|
107
|
+
enabled: true,
|
|
108
|
+
total: 100,
|
|
109
|
+
showBar: true
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
reporter.start("Processing files");
|
|
113
|
+
reporter.update(25);
|
|
114
|
+
|
|
115
|
+
expect(mockStdout.write).toHaveBeenCalledWith(
|
|
116
|
+
expect.stringContaining("Progress: [")
|
|
117
|
+
);
|
|
118
|
+
expect(mockStdout.write).toHaveBeenCalledWith(
|
|
119
|
+
expect.stringContaining("25%")
|
|
120
|
+
);
|
|
121
|
+
expect(mockStdout.write).toHaveBeenCalledWith(
|
|
122
|
+
expect.stringContaining("(25/100)")
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("should show 100% when complete", () => {
|
|
127
|
+
const reporter = new ProgressReporter({
|
|
128
|
+
enabled: true,
|
|
129
|
+
total: 50,
|
|
130
|
+
showBar: true
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
reporter.start("Processing");
|
|
134
|
+
reporter.update(50);
|
|
135
|
+
|
|
136
|
+
expect(mockStdout.write).toHaveBeenCalledWith(
|
|
137
|
+
expect.stringContaining("100%")
|
|
138
|
+
);
|
|
139
|
+
expect(mockStdout.write).toHaveBeenCalledWith(
|
|
140
|
+
expect.stringContaining("(50/50)")
|
|
141
|
+
);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("should handle progress beyond total", () => {
|
|
145
|
+
const reporter = new ProgressReporter({
|
|
146
|
+
enabled: true,
|
|
147
|
+
total: 10,
|
|
148
|
+
showBar: true
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
reporter.start("Processing");
|
|
152
|
+
reporter.update(15); // More than total
|
|
153
|
+
|
|
154
|
+
// Should cap at 100% but show actual numbers
|
|
155
|
+
expect(mockStdout.write).toHaveBeenCalledWith(
|
|
156
|
+
expect.stringContaining("(15/10)")
|
|
157
|
+
);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("should use increment correctly", () => {
|
|
161
|
+
const reporter = new ProgressReporter({
|
|
162
|
+
enabled: true,
|
|
163
|
+
total: 3,
|
|
164
|
+
showBar: true
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
reporter.start("Processing");
|
|
168
|
+
reporter.increment(); // 1
|
|
169
|
+
reporter.increment(); // 2
|
|
170
|
+
reporter.increment(); // 3
|
|
171
|
+
|
|
172
|
+
expect(mockStdout.write).toHaveBeenCalledWith(
|
|
173
|
+
expect.stringContaining("(3/3)")
|
|
174
|
+
);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
describe("time formatting", () => {
|
|
179
|
+
it("should format completion time in different units", () => {
|
|
180
|
+
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
181
|
+
|
|
182
|
+
// Mock Date.now to control timing
|
|
183
|
+
const originalNow = Date.now;
|
|
184
|
+
let currentTime = 1000;
|
|
185
|
+
Date.now = jest.fn(() => currentTime);
|
|
186
|
+
|
|
187
|
+
const reporter = new ProgressReporter({
|
|
188
|
+
enabled: true
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
reporter.start("Processing");
|
|
192
|
+
|
|
193
|
+
// Simulate 500ms elapsed
|
|
194
|
+
currentTime = 1500;
|
|
195
|
+
reporter.complete("Done");
|
|
196
|
+
|
|
197
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
198
|
+
expect.stringContaining("500ms")
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
consoleSpy.mockClear();
|
|
202
|
+
|
|
203
|
+
// Test seconds
|
|
204
|
+
currentTime = 1000; // Reset time
|
|
205
|
+
reporter.start("Processing");
|
|
206
|
+
currentTime = 3500; // 2.5 seconds elapsed
|
|
207
|
+
reporter.complete("Done");
|
|
208
|
+
|
|
209
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
210
|
+
expect.stringContaining("2.5s")
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
consoleSpy.mockClear();
|
|
214
|
+
|
|
215
|
+
// Test minutes
|
|
216
|
+
currentTime = 1000; // Reset time
|
|
217
|
+
reporter.start("Processing");
|
|
218
|
+
currentTime = 126000; // 125 seconds = 2m 5s elapsed
|
|
219
|
+
reporter.complete("Done");
|
|
220
|
+
|
|
221
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
222
|
+
expect.stringContaining("2m 5s")
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
// Restore Date.now
|
|
226
|
+
Date.now = originalNow;
|
|
227
|
+
consoleSpy.mockRestore();
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
describe("TTY handling", () => {
|
|
232
|
+
it("should handle non-TTY environments", () => {
|
|
233
|
+
mockStdout.isTTY = false;
|
|
234
|
+
(mockStdout as any).clearLine = undefined;
|
|
235
|
+
|
|
236
|
+
const reporter = new ProgressReporter({
|
|
237
|
+
enabled: true,
|
|
238
|
+
total: 10,
|
|
239
|
+
showBar: true
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
reporter.start("Processing");
|
|
243
|
+
reporter.update(5);
|
|
244
|
+
|
|
245
|
+
// Should not crash when clearLine is not available
|
|
246
|
+
expect(mockStdout.write).toHaveBeenCalled();
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it("should handle missing cursorTo", () => {
|
|
250
|
+
mockStdout.isTTY = true;
|
|
251
|
+
mockStdout.clearLine = jest.fn();
|
|
252
|
+
(mockStdout as any).cursorTo = undefined;
|
|
253
|
+
|
|
254
|
+
const reporter = new ProgressReporter({
|
|
255
|
+
enabled: true,
|
|
256
|
+
total: 10,
|
|
257
|
+
showBar: true
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
reporter.start("Processing");
|
|
261
|
+
reporter.update(5);
|
|
262
|
+
|
|
263
|
+
// Should not crash when cursorTo is not available
|
|
264
|
+
expect(mockStdout.write).toHaveBeenCalled();
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
describe("edge cases", () => {
|
|
269
|
+
it("should handle zero total", () => {
|
|
270
|
+
const reporter = new ProgressReporter({
|
|
271
|
+
enabled: true,
|
|
272
|
+
total: 0,
|
|
273
|
+
showBar: true
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// Should not crash with zero total
|
|
277
|
+
expect(() => {
|
|
278
|
+
reporter.start("Processing");
|
|
279
|
+
reporter.update(1);
|
|
280
|
+
}).not.toThrow();
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it("should handle negative progress", () => {
|
|
284
|
+
const reporter = new ProgressReporter({
|
|
285
|
+
enabled: true,
|
|
286
|
+
total: 10,
|
|
287
|
+
showBar: true
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
reporter.start("Processing");
|
|
291
|
+
reporter.update(-5);
|
|
292
|
+
|
|
293
|
+
// Should handle gracefully
|
|
294
|
+
expect(mockStdout.write).toHaveBeenCalled();
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it("should handle undefined total with progress bar", () => {
|
|
298
|
+
const reporter = new ProgressReporter({
|
|
299
|
+
enabled: true,
|
|
300
|
+
total: undefined,
|
|
301
|
+
showBar: true
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
// Should not crash with undefined total
|
|
305
|
+
expect(() => {
|
|
306
|
+
reporter.start("Processing");
|
|
307
|
+
reporter.update(5);
|
|
308
|
+
}).not.toThrow();
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it("should handle very large numbers", () => {
|
|
312
|
+
const reporter = new ProgressReporter({
|
|
313
|
+
enabled: true,
|
|
314
|
+
total: 1000000,
|
|
315
|
+
showBar: true
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
reporter.start("Processing");
|
|
319
|
+
reporter.update(500000);
|
|
320
|
+
|
|
321
|
+
expect(mockStdout.write).toHaveBeenCalledWith(
|
|
322
|
+
expect.stringContaining("50%")
|
|
323
|
+
);
|
|
324
|
+
expect(mockStdout.write).toHaveBeenCalledWith(
|
|
325
|
+
expect.stringContaining("(500000/1000000)")
|
|
326
|
+
);
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
describe("visual progress bar", () => {
|
|
331
|
+
it("should show correct bar fill at different percentages", () => {
|
|
332
|
+
const reporter = new ProgressReporter({
|
|
333
|
+
enabled: true,
|
|
334
|
+
total: 100,
|
|
335
|
+
showBar: true
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
reporter.start("Test");
|
|
339
|
+
|
|
340
|
+
// 0%
|
|
341
|
+
reporter.update(0);
|
|
342
|
+
let lastCall = mockStdout.write.mock.calls[mockStdout.write.mock.calls.length - 1][0];
|
|
343
|
+
expect(lastCall).toContain("░".repeat(30)); // All empty
|
|
344
|
+
|
|
345
|
+
// 50%
|
|
346
|
+
reporter.update(50);
|
|
347
|
+
lastCall = mockStdout.write.mock.calls[mockStdout.write.mock.calls.length - 1][0];
|
|
348
|
+
expect(lastCall).toContain("█".repeat(15)); // Half filled
|
|
349
|
+
expect(lastCall).toContain("░".repeat(15)); // Half empty
|
|
350
|
+
|
|
351
|
+
// 100%
|
|
352
|
+
reporter.update(100);
|
|
353
|
+
lastCall = mockStdout.write.mock.calls[mockStdout.write.mock.calls.length - 1][0];
|
|
354
|
+
expect(lastCall).toContain("█".repeat(30)); // All filled
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export interface ProgressReporterOptions {
|
|
2
|
+
enabled: boolean;
|
|
3
|
+
total?: number;
|
|
4
|
+
showBar?: boolean;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export class ProgressReporter {
|
|
8
|
+
private current: number = 0;
|
|
9
|
+
private startTime: number = Date.now();
|
|
10
|
+
|
|
11
|
+
constructor(public options: ProgressReporterOptions) {}
|
|
12
|
+
|
|
13
|
+
public start(message: string): void {
|
|
14
|
+
if (!this.options.enabled) return;
|
|
15
|
+
|
|
16
|
+
this.startTime = Date.now();
|
|
17
|
+
this.current = 0;
|
|
18
|
+
console.log(message);
|
|
19
|
+
|
|
20
|
+
if (this.options.showBar && this.options.total) {
|
|
21
|
+
this.drawProgressBar();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public update(current: number, message?: string): void {
|
|
26
|
+
if (!this.options.enabled) return;
|
|
27
|
+
|
|
28
|
+
this.current = current;
|
|
29
|
+
|
|
30
|
+
if (message) {
|
|
31
|
+
this.clearLine();
|
|
32
|
+
process.stdout.write(message);
|
|
33
|
+
} else if (this.options.showBar && this.options.total) {
|
|
34
|
+
this.drawProgressBar();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public increment(message?: string): void {
|
|
39
|
+
this.update(this.current + 1, message);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public complete(message: string): void {
|
|
43
|
+
if (!this.options.enabled) return;
|
|
44
|
+
|
|
45
|
+
const elapsed = Date.now() - this.startTime;
|
|
46
|
+
this.clearLine();
|
|
47
|
+
console.log(`${message} (${this.formatTime(elapsed)})`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private drawProgressBar(): void {
|
|
51
|
+
if (!this.options.total) return;
|
|
52
|
+
|
|
53
|
+
const percentage = Math.min(100, Math.floor((this.current / this.options.total) * 100));
|
|
54
|
+
const barLength = 30;
|
|
55
|
+
const filled = Math.max(0, Math.min(barLength, Math.floor((this.current / this.options.total) * barLength)));
|
|
56
|
+
const bar = '█'.repeat(filled) + '░'.repeat(barLength - filled);
|
|
57
|
+
|
|
58
|
+
this.clearLine();
|
|
59
|
+
process.stdout.write(
|
|
60
|
+
`Progress: [${bar}] ${percentage}% (${this.current}/${this.options.total})`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private clearLine(): void {
|
|
65
|
+
if (process.stdout.isTTY && process.stdout.clearLine && process.stdout.cursorTo) {
|
|
66
|
+
process.stdout.clearLine(0);
|
|
67
|
+
process.stdout.cursorTo(0);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private formatTime(ms: number): string {
|
|
72
|
+
if (ms < 1000) return `${ms}ms`;
|
|
73
|
+
if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
|
|
74
|
+
return `${Math.floor(ms / 60000)}m ${Math.floor((ms % 60000) / 1000)}s`;
|
|
75
|
+
}
|
|
76
|
+
}
|