@larkiny/astro-github-loader 0.11.3 → 0.12.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/README.md +28 -55
- package/dist/github.assets.d.ts +70 -0
- package/dist/github.assets.js +253 -0
- package/dist/github.auth.js +13 -9
- package/dist/github.cleanup.d.ts +3 -2
- package/dist/github.cleanup.js +30 -23
- package/dist/github.constants.d.ts +0 -16
- package/dist/github.constants.js +0 -16
- package/dist/github.content.d.ts +5 -131
- package/dist/github.content.js +152 -794
- package/dist/github.dryrun.d.ts +9 -5
- package/dist/github.dryrun.js +46 -25
- package/dist/github.link-transform.d.ts +2 -2
- package/dist/github.link-transform.js +65 -57
- package/dist/github.loader.js +30 -46
- package/dist/github.logger.d.ts +2 -2
- package/dist/github.logger.js +33 -24
- package/dist/github.paths.d.ts +76 -0
- package/dist/github.paths.js +190 -0
- package/dist/github.storage.d.ts +15 -0
- package/dist/github.storage.js +109 -0
- package/dist/github.types.d.ts +34 -4
- package/dist/index.d.ts +8 -6
- package/dist/index.js +3 -6
- package/dist/test-helpers.d.ts +130 -0
- package/dist/test-helpers.js +194 -0
- package/package.json +3 -1
- package/src/github.assets.spec.ts +717 -0
- package/src/github.assets.ts +365 -0
- package/src/github.auth.spec.ts +245 -0
- package/src/github.auth.ts +24 -10
- package/src/github.cleanup.spec.ts +380 -0
- package/src/github.cleanup.ts +91 -47
- package/src/github.constants.ts +0 -17
- package/src/github.content.spec.ts +305 -454
- package/src/github.content.ts +259 -957
- package/src/github.dryrun.spec.ts +586 -0
- package/src/github.dryrun.ts +105 -54
- package/src/github.link-transform.spec.ts +1345 -0
- package/src/github.link-transform.ts +174 -95
- package/src/github.loader.spec.ts +75 -50
- package/src/github.loader.ts +101 -76
- package/src/github.logger.spec.ts +795 -0
- package/src/github.logger.ts +77 -35
- package/src/github.paths.spec.ts +523 -0
- package/src/github.paths.ts +259 -0
- package/src/github.storage.spec.ts +367 -0
- package/src/github.storage.ts +127 -0
- package/src/github.types.ts +48 -9
- package/src/index.ts +43 -6
- package/src/test-helpers.ts +215 -0
|
@@ -0,0 +1,795 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
Logger,
|
|
4
|
+
createLogger,
|
|
5
|
+
type LogLevel,
|
|
6
|
+
type ImportSummary,
|
|
7
|
+
type SyncSummary,
|
|
8
|
+
type CleanupSummary,
|
|
9
|
+
} from "./github.logger";
|
|
10
|
+
|
|
11
|
+
describe("github.logger", () => {
|
|
12
|
+
let consoleLogSpy: ReturnType<typeof vi.spyOn>;
|
|
13
|
+
let consoleErrorSpy: ReturnType<typeof vi.spyOn>;
|
|
14
|
+
let consoleWarnSpy: ReturnType<typeof vi.spyOn>;
|
|
15
|
+
let stdoutWriteSpy: ReturnType<typeof vi.spyOn>;
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
19
|
+
consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
20
|
+
consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
21
|
+
stdoutWriteSpy = vi.spyOn(process.stdout, "write").mockImplementation(() => true);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
afterEach(() => {
|
|
25
|
+
vi.restoreAllMocks();
|
|
26
|
+
vi.clearAllTimers();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe("Logger constructor", () => {
|
|
30
|
+
it("should create logger with default level", () => {
|
|
31
|
+
const logger = new Logger({ level: "default" });
|
|
32
|
+
|
|
33
|
+
expect(logger.getLevel()).toBe("default");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("should create logger with silent level", () => {
|
|
37
|
+
const logger = new Logger({ level: "silent" });
|
|
38
|
+
|
|
39
|
+
expect(logger.getLevel()).toBe("silent");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("should create logger with verbose level", () => {
|
|
43
|
+
const logger = new Logger({ level: "verbose" });
|
|
44
|
+
|
|
45
|
+
expect(logger.getLevel()).toBe("verbose");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("should create logger with debug level", () => {
|
|
49
|
+
const logger = new Logger({ level: "debug" });
|
|
50
|
+
|
|
51
|
+
expect(logger.getLevel()).toBe("debug");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("should create logger with prefix", () => {
|
|
55
|
+
const logger = new Logger({ level: "default", prefix: "[TEST]" });
|
|
56
|
+
|
|
57
|
+
logger.info("message");
|
|
58
|
+
|
|
59
|
+
expect(consoleLogSpy).toHaveBeenCalledWith("[TEST] message");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("should create logger without prefix", () => {
|
|
63
|
+
const logger = new Logger({ level: "default" });
|
|
64
|
+
|
|
65
|
+
logger.info("message");
|
|
66
|
+
|
|
67
|
+
expect(consoleLogSpy).toHaveBeenCalledWith("message");
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe("setLevel and getLevel", () => {
|
|
72
|
+
it("should change logger level", () => {
|
|
73
|
+
const logger = new Logger({ level: "default" });
|
|
74
|
+
|
|
75
|
+
logger.setLevel("debug");
|
|
76
|
+
|
|
77
|
+
expect(logger.getLevel()).toBe("debug");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("should affect output after level change", () => {
|
|
81
|
+
const logger = new Logger({ level: "default" });
|
|
82
|
+
|
|
83
|
+
logger.debug("before");
|
|
84
|
+
expect(consoleLogSpy).not.toHaveBeenCalled();
|
|
85
|
+
|
|
86
|
+
logger.setLevel("debug");
|
|
87
|
+
logger.debug("after");
|
|
88
|
+
|
|
89
|
+
expect(consoleLogSpy).toHaveBeenCalledWith("after");
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe("level filtering", () => {
|
|
94
|
+
describe("silent level", () => {
|
|
95
|
+
it("should suppress all output", () => {
|
|
96
|
+
const logger = new Logger({ level: "silent" });
|
|
97
|
+
|
|
98
|
+
logger.info("info");
|
|
99
|
+
logger.verbose("verbose");
|
|
100
|
+
logger.debug("debug");
|
|
101
|
+
logger.warn("warn");
|
|
102
|
+
logger.error("error");
|
|
103
|
+
|
|
104
|
+
expect(consoleLogSpy).not.toHaveBeenCalled();
|
|
105
|
+
expect(consoleWarnSpy).not.toHaveBeenCalled();
|
|
106
|
+
expect(consoleErrorSpy).not.toHaveBeenCalled();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("should suppress import summary", () => {
|
|
110
|
+
const logger = new Logger({ level: "silent" });
|
|
111
|
+
const summary: ImportSummary = {
|
|
112
|
+
configName: "test",
|
|
113
|
+
repository: "owner/repo",
|
|
114
|
+
filesProcessed: 5,
|
|
115
|
+
filesUpdated: 2,
|
|
116
|
+
filesUnchanged: 3,
|
|
117
|
+
duration: 1000,
|
|
118
|
+
status: "success",
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
logger.logImportSummary(summary);
|
|
122
|
+
|
|
123
|
+
expect(consoleLogSpy).not.toHaveBeenCalled();
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe("default level", () => {
|
|
128
|
+
it("should show info, warn, and error", () => {
|
|
129
|
+
const logger = new Logger({ level: "default" });
|
|
130
|
+
|
|
131
|
+
logger.info("info message");
|
|
132
|
+
logger.warn("warn message");
|
|
133
|
+
logger.error("error message");
|
|
134
|
+
|
|
135
|
+
expect(consoleLogSpy).toHaveBeenCalledWith("info message");
|
|
136
|
+
expect(consoleWarnSpy).toHaveBeenCalledWith("warn message");
|
|
137
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith("error message");
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("should not show verbose or debug", () => {
|
|
141
|
+
const logger = new Logger({ level: "default" });
|
|
142
|
+
|
|
143
|
+
logger.verbose("verbose message");
|
|
144
|
+
logger.debug("debug message");
|
|
145
|
+
|
|
146
|
+
expect(consoleLogSpy).not.toHaveBeenCalled();
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("should show import summary", () => {
|
|
150
|
+
const logger = new Logger({ level: "default" });
|
|
151
|
+
const summary: ImportSummary = {
|
|
152
|
+
configName: "test",
|
|
153
|
+
repository: "owner/repo",
|
|
154
|
+
filesProcessed: 5,
|
|
155
|
+
filesUpdated: 2,
|
|
156
|
+
filesUnchanged: 3,
|
|
157
|
+
duration: 1000,
|
|
158
|
+
status: "success",
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
logger.logImportSummary(summary);
|
|
162
|
+
|
|
163
|
+
expect(consoleLogSpy).toHaveBeenCalled();
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe("verbose level", () => {
|
|
168
|
+
it("should show info, verbose, warn, and error", () => {
|
|
169
|
+
const logger = new Logger({ level: "verbose" });
|
|
170
|
+
|
|
171
|
+
logger.info("info message");
|
|
172
|
+
logger.verbose("verbose message");
|
|
173
|
+
logger.warn("warn message");
|
|
174
|
+
logger.error("error message");
|
|
175
|
+
|
|
176
|
+
expect(consoleLogSpy).toHaveBeenCalledWith("info message");
|
|
177
|
+
expect(consoleLogSpy).toHaveBeenCalledWith("verbose message");
|
|
178
|
+
expect(consoleWarnSpy).toHaveBeenCalledWith("warn message");
|
|
179
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith("error message");
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("should not show debug", () => {
|
|
183
|
+
const logger = new Logger({ level: "verbose" });
|
|
184
|
+
|
|
185
|
+
logger.debug("debug message");
|
|
186
|
+
|
|
187
|
+
expect(consoleLogSpy).not.toHaveBeenCalled();
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
describe("debug level", () => {
|
|
192
|
+
it("should show all messages", () => {
|
|
193
|
+
const logger = new Logger({ level: "debug" });
|
|
194
|
+
|
|
195
|
+
logger.info("info message");
|
|
196
|
+
logger.verbose("verbose message");
|
|
197
|
+
logger.debug("debug message");
|
|
198
|
+
logger.warn("warn message");
|
|
199
|
+
logger.error("error message");
|
|
200
|
+
|
|
201
|
+
expect(consoleLogSpy).toHaveBeenCalledWith("info message");
|
|
202
|
+
expect(consoleLogSpy).toHaveBeenCalledWith("verbose message");
|
|
203
|
+
expect(consoleLogSpy).toHaveBeenCalledWith("debug message");
|
|
204
|
+
expect(consoleWarnSpy).toHaveBeenCalledWith("warn message");
|
|
205
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith("error message");
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
describe("logging methods", () => {
|
|
211
|
+
describe("info", () => {
|
|
212
|
+
it("should log at default level", () => {
|
|
213
|
+
const logger = new Logger({ level: "default" });
|
|
214
|
+
|
|
215
|
+
logger.info("test message");
|
|
216
|
+
|
|
217
|
+
expect(consoleLogSpy).toHaveBeenCalledWith("test message");
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
describe("verbose", () => {
|
|
222
|
+
it("should log at verbose level", () => {
|
|
223
|
+
const logger = new Logger({ level: "verbose" });
|
|
224
|
+
|
|
225
|
+
logger.verbose("verbose message");
|
|
226
|
+
|
|
227
|
+
expect(consoleLogSpy).toHaveBeenCalledWith("verbose message");
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
describe("debug", () => {
|
|
232
|
+
it("should log at debug level", () => {
|
|
233
|
+
const logger = new Logger({ level: "debug" });
|
|
234
|
+
|
|
235
|
+
logger.debug("debug message");
|
|
236
|
+
|
|
237
|
+
expect(consoleLogSpy).toHaveBeenCalledWith("debug message");
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
describe("warn", () => {
|
|
242
|
+
it("should use console.warn", () => {
|
|
243
|
+
const logger = new Logger({ level: "default" });
|
|
244
|
+
|
|
245
|
+
logger.warn("warning message");
|
|
246
|
+
|
|
247
|
+
expect(consoleWarnSpy).toHaveBeenCalledWith("warning message");
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
describe("error", () => {
|
|
252
|
+
it("should use console.error", () => {
|
|
253
|
+
const logger = new Logger({ level: "default" });
|
|
254
|
+
|
|
255
|
+
logger.error("error message");
|
|
256
|
+
|
|
257
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith("error message");
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
describe("silent", () => {
|
|
262
|
+
it("should not produce output", () => {
|
|
263
|
+
const logger = new Logger({ level: "default" });
|
|
264
|
+
|
|
265
|
+
logger.silent();
|
|
266
|
+
|
|
267
|
+
expect(consoleLogSpy).not.toHaveBeenCalled();
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
describe("logImportSummary", () => {
|
|
273
|
+
it("should log successful import summary", () => {
|
|
274
|
+
const logger = new Logger({ level: "default" });
|
|
275
|
+
const summary: ImportSummary = {
|
|
276
|
+
configName: "test-config",
|
|
277
|
+
repository: "owner/repo",
|
|
278
|
+
ref: "main",
|
|
279
|
+
filesProcessed: 10,
|
|
280
|
+
filesUpdated: 5,
|
|
281
|
+
filesUnchanged: 5,
|
|
282
|
+
duration: 2500,
|
|
283
|
+
status: "success",
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
logger.logImportSummary(summary);
|
|
287
|
+
|
|
288
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining("test-config"));
|
|
289
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining("owner/repo@main"));
|
|
290
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining("10 processed"));
|
|
291
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining("5 updated"));
|
|
292
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining("2.5s"));
|
|
293
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining("✅"));
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it("should log error import summary", () => {
|
|
297
|
+
const logger = new Logger({ level: "default" });
|
|
298
|
+
const summary: ImportSummary = {
|
|
299
|
+
configName: "test-config",
|
|
300
|
+
repository: "owner/repo",
|
|
301
|
+
filesProcessed: 3,
|
|
302
|
+
filesUpdated: 0,
|
|
303
|
+
filesUnchanged: 3,
|
|
304
|
+
duration: 500,
|
|
305
|
+
status: "error",
|
|
306
|
+
error: "API rate limit exceeded",
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
logger.logImportSummary(summary);
|
|
310
|
+
|
|
311
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining("❌"));
|
|
312
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining("API rate limit exceeded"));
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it("should log cancelled import summary", () => {
|
|
316
|
+
const logger = new Logger({ level: "default" });
|
|
317
|
+
const summary: ImportSummary = {
|
|
318
|
+
configName: "test-config",
|
|
319
|
+
repository: "owner/repo",
|
|
320
|
+
filesProcessed: 2,
|
|
321
|
+
filesUpdated: 2,
|
|
322
|
+
filesUnchanged: 0,
|
|
323
|
+
duration: 100,
|
|
324
|
+
status: "cancelled",
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
logger.logImportSummary(summary);
|
|
328
|
+
|
|
329
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining("🚫"));
|
|
330
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining("Cancelled"));
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it("should log asset information when present", () => {
|
|
334
|
+
const logger = new Logger({ level: "default" });
|
|
335
|
+
const summary: ImportSummary = {
|
|
336
|
+
configName: "test-config",
|
|
337
|
+
repository: "owner/repo",
|
|
338
|
+
filesProcessed: 5,
|
|
339
|
+
filesUpdated: 2,
|
|
340
|
+
filesUnchanged: 3,
|
|
341
|
+
assetsDownloaded: 8,
|
|
342
|
+
assetsCached: 12,
|
|
343
|
+
duration: 1000,
|
|
344
|
+
status: "success",
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
logger.logImportSummary(summary);
|
|
348
|
+
|
|
349
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining("8 downloaded"));
|
|
350
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining("12 cached"));
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it("should handle missing ref", () => {
|
|
354
|
+
const logger = new Logger({ level: "default" });
|
|
355
|
+
const summary: ImportSummary = {
|
|
356
|
+
configName: "test-config",
|
|
357
|
+
repository: "owner/repo",
|
|
358
|
+
filesProcessed: 5,
|
|
359
|
+
filesUpdated: 2,
|
|
360
|
+
filesUnchanged: 3,
|
|
361
|
+
duration: 1000,
|
|
362
|
+
status: "success",
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
logger.logImportSummary(summary);
|
|
366
|
+
|
|
367
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining("owner/repo"));
|
|
368
|
+
expect(consoleLogSpy).not.toHaveBeenCalledWith(expect.stringContaining("@"));
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
it("should be suppressed at silent level", () => {
|
|
372
|
+
const logger = new Logger({ level: "silent" });
|
|
373
|
+
const summary: ImportSummary = {
|
|
374
|
+
configName: "test",
|
|
375
|
+
repository: "owner/repo",
|
|
376
|
+
filesProcessed: 5,
|
|
377
|
+
filesUpdated: 2,
|
|
378
|
+
filesUnchanged: 3,
|
|
379
|
+
duration: 1000,
|
|
380
|
+
status: "success",
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
logger.logImportSummary(summary);
|
|
384
|
+
|
|
385
|
+
expect(consoleLogSpy).not.toHaveBeenCalled();
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
describe("logSyncSummary", () => {
|
|
390
|
+
it("should log sync with changes", () => {
|
|
391
|
+
const logger = new Logger({ level: "default" });
|
|
392
|
+
const summary: SyncSummary = {
|
|
393
|
+
added: 3,
|
|
394
|
+
updated: 2,
|
|
395
|
+
deleted: 1,
|
|
396
|
+
unchanged: 5,
|
|
397
|
+
duration: 150,
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
logger.logSyncSummary("test-config", summary);
|
|
401
|
+
|
|
402
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
403
|
+
expect.stringContaining("3 added, 2 updated, 1 deleted"),
|
|
404
|
+
);
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
it("should log sync with no changes", () => {
|
|
408
|
+
const logger = new Logger({ level: "default" });
|
|
409
|
+
const summary: SyncSummary = {
|
|
410
|
+
added: 0,
|
|
411
|
+
updated: 0,
|
|
412
|
+
deleted: 0,
|
|
413
|
+
unchanged: 5,
|
|
414
|
+
duration: 50,
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
logger.logSyncSummary("test-config", summary);
|
|
418
|
+
|
|
419
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining("No changes needed"));
|
|
420
|
+
});
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
describe("logCleanupSummary", () => {
|
|
424
|
+
it("should log cleanup with deletions", () => {
|
|
425
|
+
const logger = new Logger({ level: "default" });
|
|
426
|
+
const summary: CleanupSummary = {
|
|
427
|
+
deleted: 5,
|
|
428
|
+
duration: 100,
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
logger.logCleanupSummary("test-config", summary);
|
|
432
|
+
|
|
433
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
434
|
+
expect.stringContaining("5 obsolete files deleted"),
|
|
435
|
+
);
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
it("should use debug level when no deletions", () => {
|
|
439
|
+
const logger = new Logger({ level: "default" });
|
|
440
|
+
const summary: CleanupSummary = {
|
|
441
|
+
deleted: 0,
|
|
442
|
+
duration: 50,
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
logger.logCleanupSummary("test-config", summary);
|
|
446
|
+
|
|
447
|
+
expect(consoleLogSpy).not.toHaveBeenCalled();
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
it("should show no cleanup message at debug level", () => {
|
|
451
|
+
const logger = new Logger({ level: "debug" });
|
|
452
|
+
const summary: CleanupSummary = {
|
|
453
|
+
deleted: 0,
|
|
454
|
+
duration: 50,
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
logger.logCleanupSummary("test-config", summary);
|
|
458
|
+
|
|
459
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining("No cleanup needed"));
|
|
460
|
+
});
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
describe("logFileProcessing", () => {
|
|
464
|
+
it("should log file processing at verbose level", () => {
|
|
465
|
+
const logger = new Logger({ level: "verbose" });
|
|
466
|
+
|
|
467
|
+
logger.logFileProcessing("Processing", "path/to/file.md");
|
|
468
|
+
|
|
469
|
+
expect(consoleLogSpy).toHaveBeenCalledWith("Processing: path/to/file.md");
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
it("should log with details", () => {
|
|
473
|
+
const logger = new Logger({ level: "verbose" });
|
|
474
|
+
|
|
475
|
+
logger.logFileProcessing("Updating", "path/to/file.md", "changed frontmatter");
|
|
476
|
+
|
|
477
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
478
|
+
"Updating: path/to/file.md - changed frontmatter",
|
|
479
|
+
);
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
it("should not log at default level", () => {
|
|
483
|
+
const logger = new Logger({ level: "default" });
|
|
484
|
+
|
|
485
|
+
logger.logFileProcessing("Processing", "path/to/file.md");
|
|
486
|
+
|
|
487
|
+
expect(consoleLogSpy).not.toHaveBeenCalled();
|
|
488
|
+
});
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
describe("logAssetProcessing", () => {
|
|
492
|
+
it("should log asset processing at verbose level", () => {
|
|
493
|
+
const logger = new Logger({ level: "verbose" });
|
|
494
|
+
|
|
495
|
+
logger.logAssetProcessing("downloading", "images/logo.png");
|
|
496
|
+
|
|
497
|
+
expect(consoleLogSpy).toHaveBeenCalledWith("Asset downloading: images/logo.png");
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
it("should log with details", () => {
|
|
501
|
+
const logger = new Logger({ level: "verbose" });
|
|
502
|
+
|
|
503
|
+
logger.logAssetProcessing("cached", "images/logo.png", "using cache");
|
|
504
|
+
|
|
505
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
506
|
+
"Asset cached: images/logo.png - using cache",
|
|
507
|
+
);
|
|
508
|
+
});
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
describe("child", () => {
|
|
512
|
+
it("should create child logger with concatenated prefix", () => {
|
|
513
|
+
const parent = new Logger({ level: "default", prefix: "[PARENT]" });
|
|
514
|
+
const child = parent.child("[CHILD]");
|
|
515
|
+
|
|
516
|
+
child.info("message");
|
|
517
|
+
|
|
518
|
+
expect(consoleLogSpy).toHaveBeenCalledWith("[PARENT][CHILD] message");
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
it("should create child logger with prefix when parent has none", () => {
|
|
522
|
+
const parent = new Logger({ level: "default" });
|
|
523
|
+
const child = parent.child("[CHILD]");
|
|
524
|
+
|
|
525
|
+
child.info("message");
|
|
526
|
+
|
|
527
|
+
expect(consoleLogSpy).toHaveBeenCalledWith("[CHILD] message");
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
it("should inherit parent log level", () => {
|
|
531
|
+
const parent = new Logger({ level: "debug" });
|
|
532
|
+
const child = parent.child("[CHILD]");
|
|
533
|
+
|
|
534
|
+
expect(child.getLevel()).toBe("debug");
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
it("should maintain independent log levels after creation", () => {
|
|
538
|
+
const parent = new Logger({ level: "default" });
|
|
539
|
+
const child = parent.child("[CHILD]");
|
|
540
|
+
|
|
541
|
+
parent.setLevel("silent");
|
|
542
|
+
|
|
543
|
+
expect(child.getLevel()).toBe("default");
|
|
544
|
+
});
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
describe("time", () => {
|
|
548
|
+
it("should measure and log execution duration", async () => {
|
|
549
|
+
vi.useFakeTimers();
|
|
550
|
+
const logger = new Logger({ level: "verbose" });
|
|
551
|
+
|
|
552
|
+
const promise = logger.time("test operation", async () => {
|
|
553
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
554
|
+
return "result";
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
await vi.advanceTimersByTimeAsync(100);
|
|
558
|
+
const result = await promise;
|
|
559
|
+
|
|
560
|
+
expect(result).toBe("result");
|
|
561
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining("Completed: test operation"));
|
|
562
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining("100ms"));
|
|
563
|
+
|
|
564
|
+
vi.useRealTimers();
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
it("should log start message at debug level", async () => {
|
|
568
|
+
vi.useFakeTimers();
|
|
569
|
+
const logger = new Logger({ level: "debug" });
|
|
570
|
+
|
|
571
|
+
const promise = logger.time("test operation", async () => {
|
|
572
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
573
|
+
return "result";
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
await vi.advanceTimersByTimeAsync(50);
|
|
577
|
+
await promise;
|
|
578
|
+
|
|
579
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining("Starting: test operation"));
|
|
580
|
+
|
|
581
|
+
vi.useRealTimers();
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
it("should log error and rethrow on failure", async () => {
|
|
585
|
+
vi.useFakeTimers();
|
|
586
|
+
const logger = new Logger({ level: "default" });
|
|
587
|
+
const error = new Error("test error");
|
|
588
|
+
|
|
589
|
+
const promise = logger.time("failing operation", async () => {
|
|
590
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
591
|
+
throw error;
|
|
592
|
+
});
|
|
593
|
+
promise.catch(() => {}); // prevent unhandled rejection before timer advance
|
|
594
|
+
|
|
595
|
+
await vi.advanceTimersByTimeAsync(50);
|
|
596
|
+
|
|
597
|
+
await expect(promise).rejects.toThrow("test error");
|
|
598
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("Failed: failing operation"));
|
|
599
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("test error"));
|
|
600
|
+
|
|
601
|
+
vi.useRealTimers();
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
it("should include duration in error log", async () => {
|
|
605
|
+
vi.useFakeTimers();
|
|
606
|
+
const logger = new Logger({ level: "default" });
|
|
607
|
+
|
|
608
|
+
const promise = logger.time("failing operation", async () => {
|
|
609
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
610
|
+
throw new Error("failure");
|
|
611
|
+
});
|
|
612
|
+
promise.catch(() => {}); // prevent unhandled rejection before timer advance
|
|
613
|
+
|
|
614
|
+
await vi.advanceTimersByTimeAsync(200);
|
|
615
|
+
|
|
616
|
+
await expect(promise).rejects.toThrow();
|
|
617
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("200ms"));
|
|
618
|
+
|
|
619
|
+
vi.useRealTimers();
|
|
620
|
+
});
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
describe("createLogger", () => {
|
|
624
|
+
it("should create logger with default level", () => {
|
|
625
|
+
const logger = createLogger();
|
|
626
|
+
|
|
627
|
+
expect(logger.getLevel()).toBe("default");
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
it("should create logger with specified level", () => {
|
|
631
|
+
const logger = createLogger("debug");
|
|
632
|
+
|
|
633
|
+
expect(logger.getLevel()).toBe("debug");
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
it("should create logger with prefix", () => {
|
|
637
|
+
const logger = createLogger("default", "[PREFIX]");
|
|
638
|
+
|
|
639
|
+
logger.info("test");
|
|
640
|
+
|
|
641
|
+
expect(consoleLogSpy).toHaveBeenCalledWith("[PREFIX] test");
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
it("should create logger without prefix", () => {
|
|
645
|
+
const logger = createLogger("default");
|
|
646
|
+
|
|
647
|
+
logger.info("test");
|
|
648
|
+
|
|
649
|
+
expect(consoleLogSpy).toHaveBeenCalledWith("test");
|
|
650
|
+
});
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
describe("spinner", () => {
|
|
654
|
+
it("should not start spinner at silent level", () => {
|
|
655
|
+
vi.useFakeTimers();
|
|
656
|
+
const logger = new Logger({ level: "silent" });
|
|
657
|
+
|
|
658
|
+
logger.startSpinner("Processing");
|
|
659
|
+
|
|
660
|
+
expect(stdoutWriteSpy).not.toHaveBeenCalled();
|
|
661
|
+
|
|
662
|
+
vi.useRealTimers();
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
it("should start and stop spinner", () => {
|
|
666
|
+
vi.useFakeTimers();
|
|
667
|
+
const logger = new Logger({ level: "default" });
|
|
668
|
+
|
|
669
|
+
logger.startSpinner("Processing");
|
|
670
|
+
expect(stdoutWriteSpy).toHaveBeenCalled();
|
|
671
|
+
|
|
672
|
+
logger.stopSpinner("Done");
|
|
673
|
+
|
|
674
|
+
vi.useRealTimers();
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
it("should display final message when stopping", () => {
|
|
678
|
+
vi.useFakeTimers();
|
|
679
|
+
const logger = new Logger({ level: "default" });
|
|
680
|
+
|
|
681
|
+
logger.startSpinner("Processing");
|
|
682
|
+
logger.stopSpinner("Completed");
|
|
683
|
+
|
|
684
|
+
expect(stdoutWriteSpy).toHaveBeenCalledWith(expect.stringContaining("Completed"));
|
|
685
|
+
|
|
686
|
+
vi.useRealTimers();
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
it("should clear line when stopping without message", () => {
|
|
690
|
+
vi.useFakeTimers();
|
|
691
|
+
const logger = new Logger({ level: "default" });
|
|
692
|
+
|
|
693
|
+
logger.startSpinner("Processing");
|
|
694
|
+
logger.stopSpinner();
|
|
695
|
+
|
|
696
|
+
expect(stdoutWriteSpy).toHaveBeenCalledWith("\r\x1b[K");
|
|
697
|
+
|
|
698
|
+
vi.useRealTimers();
|
|
699
|
+
});
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
describe("withSpinner", () => {
|
|
703
|
+
it("should execute function with spinner", async () => {
|
|
704
|
+
vi.useFakeTimers();
|
|
705
|
+
const logger = new Logger({ level: "default" });
|
|
706
|
+
|
|
707
|
+
const promise = logger.withSpinner(
|
|
708
|
+
"Processing",
|
|
709
|
+
async () => {
|
|
710
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
711
|
+
return "result";
|
|
712
|
+
},
|
|
713
|
+
);
|
|
714
|
+
|
|
715
|
+
await vi.advanceTimersByTimeAsync(100);
|
|
716
|
+
const result = await promise;
|
|
717
|
+
|
|
718
|
+
expect(result).toBe("result");
|
|
719
|
+
expect(stdoutWriteSpy).toHaveBeenCalled();
|
|
720
|
+
|
|
721
|
+
vi.useRealTimers();
|
|
722
|
+
});
|
|
723
|
+
|
|
724
|
+
it("should show success message", async () => {
|
|
725
|
+
vi.useFakeTimers();
|
|
726
|
+
const logger = new Logger({ level: "default" });
|
|
727
|
+
|
|
728
|
+
const promise = logger.withSpinner(
|
|
729
|
+
"Processing",
|
|
730
|
+
async () => "done",
|
|
731
|
+
"Success!",
|
|
732
|
+
);
|
|
733
|
+
|
|
734
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
735
|
+
await promise;
|
|
736
|
+
|
|
737
|
+
expect(stdoutWriteSpy).toHaveBeenCalledWith(expect.stringContaining("Success!"));
|
|
738
|
+
|
|
739
|
+
vi.useRealTimers();
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
it("should show error message on failure", async () => {
|
|
743
|
+
vi.useFakeTimers();
|
|
744
|
+
const logger = new Logger({ level: "default" });
|
|
745
|
+
|
|
746
|
+
const promise = logger.withSpinner(
|
|
747
|
+
"Processing",
|
|
748
|
+
async () => {
|
|
749
|
+
throw new Error("failed");
|
|
750
|
+
},
|
|
751
|
+
undefined,
|
|
752
|
+
"Error occurred",
|
|
753
|
+
);
|
|
754
|
+
promise.catch(() => {}); // prevent unhandled rejection before timer advance
|
|
755
|
+
|
|
756
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
757
|
+
|
|
758
|
+
await expect(promise).rejects.toThrow("failed");
|
|
759
|
+
expect(stdoutWriteSpy).toHaveBeenCalledWith(expect.stringContaining("Error occurred"));
|
|
760
|
+
|
|
761
|
+
vi.useRealTimers();
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
it("should use default success message", async () => {
|
|
765
|
+
vi.useFakeTimers();
|
|
766
|
+
const logger = new Logger({ level: "default" });
|
|
767
|
+
|
|
768
|
+
const promise = logger.withSpinner("🔄 Processing data", async () => "done");
|
|
769
|
+
|
|
770
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
771
|
+
await promise;
|
|
772
|
+
|
|
773
|
+
expect(stdoutWriteSpy).toHaveBeenCalledWith(expect.stringContaining("completed"));
|
|
774
|
+
|
|
775
|
+
vi.useRealTimers();
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
it("should use default error message", async () => {
|
|
779
|
+
vi.useFakeTimers();
|
|
780
|
+
const logger = new Logger({ level: "default" });
|
|
781
|
+
|
|
782
|
+
const promise = logger.withSpinner("⏳ Processing", async () => {
|
|
783
|
+
throw new Error("error");
|
|
784
|
+
});
|
|
785
|
+
promise.catch(() => {}); // prevent unhandled rejection before timer advance
|
|
786
|
+
|
|
787
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
788
|
+
|
|
789
|
+
await expect(promise).rejects.toThrow();
|
|
790
|
+
expect(stdoutWriteSpy).toHaveBeenCalledWith(expect.stringContaining("failed"));
|
|
791
|
+
|
|
792
|
+
vi.useRealTimers();
|
|
793
|
+
});
|
|
794
|
+
});
|
|
795
|
+
});
|