@tstdl/base 0.93.173 → 0.93.175
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/ai/genkit/tests/multi-region.test.js +1 -1
- package/ai/genkit/tests/token-limit-fallback.test.js +1 -1
- package/api/server/middlewares/cors.middleware.js +9 -6
- package/authentication/client/authentication.service.d.ts +1 -1
- package/authentication/client/authentication.service.js +18 -4
- package/authentication/server/authentication.service.d.ts +1 -1
- package/authentication/server/authentication.service.js +2 -2
- package/authentication/tests/authentication.client-service-refresh.test.js +19 -0
- package/authentication/tests/authentication.refresh-busy-loop.test.d.ts +1 -0
- package/authentication/tests/authentication.refresh-busy-loop.test.js +84 -0
- package/document-management/tests/ai-config-integration.test.js +4 -4
- package/document-management/tests/document-validation-ai-overrides.test.js +3 -0
- package/http/server/http-server-response.d.ts +1 -1
- package/http/server/http-server-response.js +13 -5
- package/logger/tests/pretty-print.test.js +2 -2
- package/package.json +1 -2
|
@@ -6,7 +6,7 @@ import { CircuitBreakerProvider } from '../../../circuit-breaker/provider.js';
|
|
|
6
6
|
import { Logger } from '../../../logger/logger.js';
|
|
7
7
|
import { setupIntegrationTest } from '../../../testing/index.js';
|
|
8
8
|
import { vertexAiMultiLocation } from '../multi-region.plugin.js';
|
|
9
|
-
vi.mock('
|
|
9
|
+
vi.mock('../../../utils/array/index.js', async (importOriginal) => {
|
|
10
10
|
const actual = await importOriginal();
|
|
11
11
|
return {
|
|
12
12
|
...actual,
|
|
@@ -7,7 +7,7 @@ import { Logger } from '../../../logger/logger.js';
|
|
|
7
7
|
import { setupIntegrationTest } from '../../../testing/index.js';
|
|
8
8
|
import { timeout } from '../../../utils/timing.js';
|
|
9
9
|
import { vertexAiMultiLocation } from '../multi-region.plugin.js';
|
|
10
|
-
vi.mock('
|
|
10
|
+
vi.mock('../../../utils/array/index.js', async (importOriginal) => {
|
|
11
11
|
const actual = await importOriginal();
|
|
12
12
|
return {
|
|
13
13
|
...actual,
|
|
@@ -14,6 +14,7 @@ export function corsMiddleware(options = {}) {
|
|
|
14
14
|
const origin = request.headers.tryGetSingle('Origin');
|
|
15
15
|
const allowCredentials = (await resolveApiEndpointDataProvider(request, context, cors.accessControlAllowCredentials)) ?? endpointDefinition?.credentials;
|
|
16
16
|
if (isDefined(origin)) {
|
|
17
|
+
let allowOriginValidationFailed = false;
|
|
17
18
|
if (isDefined(cors.accessControlAllowOrigin) && !response.headers.has('Access-Control-Allow-Origin')) {
|
|
18
19
|
const allowedOrigin = await resolveApiEndpointDataProvider(request, context, cors.accessControlAllowOrigin);
|
|
19
20
|
if (isDefined(allowedOrigin)) {
|
|
@@ -26,9 +27,8 @@ export function corsMiddleware(options = {}) {
|
|
|
26
27
|
response.headers.append('Vary', 'Origin');
|
|
27
28
|
}
|
|
28
29
|
}
|
|
29
|
-
else
|
|
30
|
-
|
|
31
|
-
return;
|
|
30
|
+
else {
|
|
31
|
+
allowOriginValidationFailed = true;
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
}
|
|
@@ -39,11 +39,14 @@ export function corsMiddleware(options = {}) {
|
|
|
39
39
|
response.headers.set('Access-Control-Allow-Origin', origin);
|
|
40
40
|
response.headers.append('Vary', 'Origin');
|
|
41
41
|
}
|
|
42
|
-
else
|
|
43
|
-
|
|
44
|
-
return;
|
|
42
|
+
else {
|
|
43
|
+
allowOriginValidationFailed = true;
|
|
45
44
|
}
|
|
46
45
|
}
|
|
46
|
+
if (isOptions && allowOriginValidationFailed && !response.headers.has('Access-Control-Allow-Origin')) {
|
|
47
|
+
response.statusCode = 403;
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
47
50
|
}
|
|
48
51
|
if (isOptions) {
|
|
49
52
|
const customAllowMethods = await resolveApiEndpointDataProvider(request, context, cors.accessControlAllowMethods);
|
|
@@ -26,7 +26,7 @@ export declare class AuthenticationClientService<AdditionalTokenPayload extends
|
|
|
26
26
|
private readonly logger;
|
|
27
27
|
private readonly disposeSignal;
|
|
28
28
|
private readonly forceRefreshRequested;
|
|
29
|
-
private readonly
|
|
29
|
+
private readonly forceRefreshSubject;
|
|
30
30
|
private clockOffset;
|
|
31
31
|
private initialized;
|
|
32
32
|
private refreshLoopPromise;
|
|
@@ -7,7 +7,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
8
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
9
|
};
|
|
10
|
-
import { Subject, filter, firstValueFrom, from, race,
|
|
10
|
+
import { Subject, filter, firstValueFrom, from, race, takeUntil, timer } from 'rxjs';
|
|
11
11
|
import { CancellationSignal } from '../../cancellation/token.js';
|
|
12
12
|
import { BadRequestError } from '../../errors/bad-request.error.js';
|
|
13
13
|
import { ForbiddenError } from '../../errors/forbidden.error.js';
|
|
@@ -72,7 +72,7 @@ let AuthenticationClientService = class AuthenticationClientService {
|
|
|
72
72
|
logger = inject(Logger, 'AuthenticationService');
|
|
73
73
|
disposeSignal = inject(CancellationSignal).fork();
|
|
74
74
|
forceRefreshRequested = signal(false);
|
|
75
|
-
|
|
75
|
+
forceRefreshSubject = new Subject();
|
|
76
76
|
clockOffset = 0;
|
|
77
77
|
initialized = false;
|
|
78
78
|
refreshLoopPromise;
|
|
@@ -267,6 +267,7 @@ let AuthenticationClientService = class AuthenticationClientService {
|
|
|
267
267
|
this.setAdditionalData(data);
|
|
268
268
|
}
|
|
269
269
|
this.forceRefreshRequested.set(true);
|
|
270
|
+
this.forceRefreshSubject.next();
|
|
270
271
|
}
|
|
271
272
|
/**
|
|
272
273
|
* Refresh the token.
|
|
@@ -404,7 +405,7 @@ let AuthenticationClientService = class AuthenticationClientService {
|
|
|
404
405
|
timer(Math.max(10, delayMs)),
|
|
405
406
|
from(this.disposeSignal),
|
|
406
407
|
this.token$.pipe(filter((t) => t?.exp !== referenceExp)),
|
|
407
|
-
this.
|
|
408
|
+
this.forceRefreshSubject,
|
|
408
409
|
]), { defaultValue: undefined });
|
|
409
410
|
while (this.disposeSignal.isUnset) {
|
|
410
411
|
const token = this.token();
|
|
@@ -420,6 +421,7 @@ let AuthenticationClientService = class AuthenticationClientService {
|
|
|
420
421
|
// 2. Handle token refresh
|
|
421
422
|
if (needsRefresh) {
|
|
422
423
|
const lockResult = await this.lock.tryUse(undefined, async () => {
|
|
424
|
+
this.loadToken();
|
|
423
425
|
const currentToken = this.token();
|
|
424
426
|
if (isUndefined(currentToken)) {
|
|
425
427
|
return;
|
|
@@ -428,9 +430,9 @@ let AuthenticationClientService = class AuthenticationClientService {
|
|
|
428
430
|
const currentNow = this.estimatedServerTimestampSeconds();
|
|
429
431
|
const currentBuffer = calculateRefreshBufferSeconds(currentToken);
|
|
430
432
|
const stillNeedsRefresh = this.forceRefreshRequested() || (currentNow >= currentToken.exp - currentBuffer);
|
|
431
|
-
this.forceRefreshRequested.set(false);
|
|
432
433
|
if (stillNeedsRefresh) {
|
|
433
434
|
await this.refresh();
|
|
435
|
+
this.forceRefreshRequested.set(false);
|
|
434
436
|
}
|
|
435
437
|
});
|
|
436
438
|
// If lock is held by another instance/tab, wait briefly for it to finish (passive sync)
|
|
@@ -441,6 +443,18 @@ let AuthenticationClientService = class AuthenticationClientService {
|
|
|
441
443
|
this.forceRefreshRequested.set(false);
|
|
442
444
|
}
|
|
443
445
|
}
|
|
446
|
+
// Protection against tight loops (e.g. if server clock is ahead and sync failed)
|
|
447
|
+
const newToken = this.token();
|
|
448
|
+
if (isDefined(newToken)) {
|
|
449
|
+
const newNow = this.estimatedServerTimestampSeconds();
|
|
450
|
+
const newBuffer = calculateRefreshBufferSeconds(newToken);
|
|
451
|
+
const stillNeedsRefresh = this.forceRefreshRequested() || (newNow >= newToken.exp - newBuffer);
|
|
452
|
+
if (stillNeedsRefresh) {
|
|
453
|
+
this.logger.warn('Token still needs refresh after attempt. Waiting for minRefreshDelay to avoid tight loop.');
|
|
454
|
+
await waitForNextAction(minRefreshDelay, newToken.exp);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
await waitForNextAction(100, newToken?.exp);
|
|
444
458
|
continue; // Re-evaluate the loop with the newly refreshed (or synced) token
|
|
445
459
|
}
|
|
446
460
|
// 3. Calculate delay and sleep until the next scheduled refresh window
|
|
@@ -55,7 +55,7 @@ export class AuthenticationServiceOptions {
|
|
|
55
55
|
/**
|
|
56
56
|
* How long a refresh token is valid in milliseconds. Implies session time to live.
|
|
57
57
|
*
|
|
58
|
-
* @default
|
|
58
|
+
* @default 6 hours
|
|
59
59
|
*/
|
|
60
60
|
refreshTokenTimeToLive;
|
|
61
61
|
/**
|
|
@@ -126,7 +126,7 @@ let AuthenticationService = AuthenticationService_1 = class AuthenticationServic
|
|
|
126
126
|
};
|
|
127
127
|
tokenVersion = this.#options.version ?? 1;
|
|
128
128
|
tokenTimeToLive = this.#options.tokenTimeToLive ?? (5 * millisecondsPerMinute);
|
|
129
|
-
refreshTokenTimeToLive = this.#options.refreshTokenTimeToLive ?? (
|
|
129
|
+
refreshTokenTimeToLive = this.#options.refreshTokenTimeToLive ?? (6 * millisecondsPerHour);
|
|
130
130
|
rememberRefreshTokenTimeToLive = this.#options.rememberRefreshTokenTimeToLive ?? (30 * millisecondsPerDay);
|
|
131
131
|
secretResetTokenTimeToLive = this.#options.secretResetTokenTimeToLive ?? (10 * millisecondsPerMinute);
|
|
132
132
|
derivedTokenSigningSecret;
|
|
@@ -130,4 +130,23 @@ describe('AuthenticationClientService Refresh Loop Reproduction', () => {
|
|
|
130
130
|
await service.refresh();
|
|
131
131
|
expect(mockApiClient.timestamp).toHaveBeenCalled();
|
|
132
132
|
});
|
|
133
|
+
test('Cross-tab Sync: should not refresh if another tab already did (simulated via localStorage update)', async () => {
|
|
134
|
+
const now = Math.floor(Date.now() / 1000);
|
|
135
|
+
const initialToken = { iat: now - 3600, exp: now + 5, jti: 'initial' }; // Expiring soon
|
|
136
|
+
globalThis.localStorage.setItem('AuthenticationService:token', JSON.stringify(initialToken));
|
|
137
|
+
// 1. Mock lock behavior to simulate another tab refreshing while we wait for the lock
|
|
138
|
+
mockLock.tryUse.mockImplementation(async (_timeout, callback) => {
|
|
139
|
+
// Simulate another tab refreshing and updating localStorage
|
|
140
|
+
const newToken = { iat: now, exp: now + 3600, jti: 'refreshed-by-other-tab' };
|
|
141
|
+
globalThis.localStorage.setItem('AuthenticationService:token', JSON.stringify(newToken));
|
|
142
|
+
const result = await callback({ lost: false });
|
|
143
|
+
return { success: true, result };
|
|
144
|
+
});
|
|
145
|
+
service = injector.resolve(AuthenticationClientService);
|
|
146
|
+
// Wait for loop to run
|
|
147
|
+
await timeout(100);
|
|
148
|
+
// Should NOT have called refresh because loadToken() inside the lock updated the token
|
|
149
|
+
expect(mockApiClient.refresh).not.toHaveBeenCalled();
|
|
150
|
+
expect(service.token()?.jti).toBe('refreshed-by-other-tab');
|
|
151
|
+
});
|
|
133
152
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
|
|
2
|
+
import { AuthenticationClientService } from '../../authentication/client/authentication.service.js';
|
|
3
|
+
import { AUTHENTICATION_API_CLIENT } from '../../authentication/client/tokens.js';
|
|
4
|
+
import { CancellationSignal, CancellationToken } from '../../cancellation/token.js';
|
|
5
|
+
import { Injector } from '../../injector/index.js';
|
|
6
|
+
import { Lock } from '../../lock/index.js';
|
|
7
|
+
import { Logger } from '../../logger/index.js';
|
|
8
|
+
import { MessageBus } from '../../message-bus/index.js';
|
|
9
|
+
import { configureDefaultSignalsImplementation } from '../../signals/implementation/configure.js';
|
|
10
|
+
import { timeout } from '../../utils/timing.js';
|
|
11
|
+
import { Subject } from 'rxjs';
|
|
12
|
+
describe('AuthenticationClientService Refresh Busy Loop Prevention', () => {
|
|
13
|
+
let injector;
|
|
14
|
+
let service;
|
|
15
|
+
let mockApiClient;
|
|
16
|
+
let mockLock;
|
|
17
|
+
let mockMessageBus;
|
|
18
|
+
let mockLogger;
|
|
19
|
+
let disposeToken;
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
const storage = new Map();
|
|
22
|
+
globalThis.localStorage = {
|
|
23
|
+
getItem: vi.fn((key) => storage.get(key) ?? null),
|
|
24
|
+
setItem: vi.fn((key, value) => storage.set(key, value)),
|
|
25
|
+
removeItem: vi.fn((key) => storage.delete(key)),
|
|
26
|
+
clear: vi.fn(() => storage.clear()),
|
|
27
|
+
};
|
|
28
|
+
configureDefaultSignalsImplementation();
|
|
29
|
+
injector = new Injector('Test');
|
|
30
|
+
mockApiClient = {
|
|
31
|
+
login: vi.fn(),
|
|
32
|
+
refresh: vi.fn(),
|
|
33
|
+
timestamp: vi.fn().mockResolvedValue(1000), // Fixed timestamp
|
|
34
|
+
endSession: vi.fn().mockResolvedValue(undefined),
|
|
35
|
+
};
|
|
36
|
+
mockLock = {
|
|
37
|
+
tryUse: vi.fn(async (_timeout, callback) => {
|
|
38
|
+
const result = await callback({ lost: false });
|
|
39
|
+
return { success: true, result };
|
|
40
|
+
}),
|
|
41
|
+
use: vi.fn(async (_timeout, callback) => {
|
|
42
|
+
return await callback({ lost: false });
|
|
43
|
+
}),
|
|
44
|
+
};
|
|
45
|
+
mockMessageBus = {
|
|
46
|
+
publishAndForget: vi.fn(),
|
|
47
|
+
messages$: new Subject(),
|
|
48
|
+
dispose: vi.fn(),
|
|
49
|
+
};
|
|
50
|
+
mockLogger = {
|
|
51
|
+
error: vi.fn(),
|
|
52
|
+
warn: vi.fn(),
|
|
53
|
+
info: vi.fn(),
|
|
54
|
+
debug: vi.fn(),
|
|
55
|
+
};
|
|
56
|
+
injector.register(AUTHENTICATION_API_CLIENT, { useValue: mockApiClient });
|
|
57
|
+
injector.register(Lock, { useValue: mockLock });
|
|
58
|
+
injector.register(MessageBus, { useValue: mockMessageBus });
|
|
59
|
+
injector.register(Logger, { useValue: mockLogger });
|
|
60
|
+
disposeToken = new CancellationToken();
|
|
61
|
+
injector.register(CancellationSignal, { useValue: disposeToken.signal });
|
|
62
|
+
});
|
|
63
|
+
afterEach(async () => {
|
|
64
|
+
disposeToken.set();
|
|
65
|
+
await injector.dispose();
|
|
66
|
+
});
|
|
67
|
+
test('should not busy loop when refresh fails to produce a valid token', async () => {
|
|
68
|
+
// 1. Mock a token that is ALWAYS expired
|
|
69
|
+
// Server time is 1000. Token expires at 500.
|
|
70
|
+
const expiredToken = { iat: 0, exp: 500, jti: 'expired', session: 's', tenant: 't', subject: 'sub' };
|
|
71
|
+
globalThis.localStorage.setItem('AuthenticationService:token', JSON.stringify(expiredToken));
|
|
72
|
+
// refresh() will return the SAME expired token
|
|
73
|
+
mockApiClient.refresh.mockResolvedValue(expiredToken);
|
|
74
|
+
// 2. Start service
|
|
75
|
+
service = injector.resolve(AuthenticationClientService);
|
|
76
|
+
// 3. Wait a bit
|
|
77
|
+
// With 100ms mandatory yield + minRefreshDelay (1 min) protection, it should only try once or twice in 500ms
|
|
78
|
+
// WITHOUT protection, it would try thousands of times.
|
|
79
|
+
await timeout(500);
|
|
80
|
+
// It should have tried to refresh
|
|
81
|
+
expect(mockApiClient.refresh.mock.calls.length).toBeLessThan(5);
|
|
82
|
+
expect(mockLogger.warn).toHaveBeenCalledWith(expect.stringContaining('Token still needs refresh after attempt'));
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -90,10 +90,10 @@ describe('DocumentManagementAiService Integration', () => {
|
|
|
90
90
|
await runInInjectionContext(injector, async () => {
|
|
91
91
|
await aiService.extractData(tenantId, document.id);
|
|
92
92
|
const lastCall = mockGenerate.mock.calls[0][0];
|
|
93
|
-
const
|
|
94
|
-
const
|
|
95
|
-
expect(
|
|
96
|
-
expect(
|
|
93
|
+
const userPrompt = lastCall.prompt;
|
|
94
|
+
const userPromptText = userPrompt.map((p) => p.text).join('\n');
|
|
95
|
+
expect(userPromptText).toContain('Format the title of the document exactly as follows: INV-{{Date}}');
|
|
96
|
+
expect(userPromptText).toContain('Focus on items.');
|
|
97
97
|
});
|
|
98
98
|
});
|
|
99
99
|
test('classifyDocumentType should include granular instructions in prompt', async () => {
|
|
@@ -6,6 +6,7 @@ import { object, string } from '../../schema/index.js';
|
|
|
6
6
|
import { setupIntegrationTest } from '../../testing/index.js';
|
|
7
7
|
import { isString } from '../../utils/type-guards.js';
|
|
8
8
|
import { DocumentManagementConfiguration } from '../server/module.js';
|
|
9
|
+
import { DocumentFileService } from '../server/services/document-file.service.js';
|
|
9
10
|
import { DocumentManagementAiProviderService, DocumentManagementAncillaryService } from '../server/services/index.js';
|
|
10
11
|
import { AiValidationExecutor } from '../server/validators/ai-validation-executor.js';
|
|
11
12
|
import { TestDocumentManagementAncillaryService } from './helper.js';
|
|
@@ -41,6 +42,7 @@ describe('AiValidationExecutor Overrides', () => {
|
|
|
41
42
|
injector.register(DocumentManagementAiProviderService, { useValue: mockAiProvider });
|
|
42
43
|
injector.register(DocumentManagementAncillaryService, { useToken: TestDocumentManagementAncillaryService });
|
|
43
44
|
injector.register(DocumentManagementConfiguration, { useValue: { fileObjectStorageModule: 'docs', fileUploadObjectStorageModule: 'uploads', filePreviewObjectStorageModule: 'previews' } });
|
|
45
|
+
injector.register(DocumentFileService, { useValue: { getContent: vi.fn().mockResolvedValue(new Uint8Array([1, 2, 3])) } });
|
|
44
46
|
});
|
|
45
47
|
afterAll(async () => {
|
|
46
48
|
await injector?.dispose();
|
|
@@ -66,6 +68,7 @@ describe('AiValidationExecutor Overrides', () => {
|
|
|
66
68
|
const context = {
|
|
67
69
|
execution: { tenantId },
|
|
68
70
|
definition: { identifier: 'test-validation' },
|
|
71
|
+
document: { id: 'test-doc', mimeType: 'application/pdf' },
|
|
69
72
|
};
|
|
70
73
|
await executor.publicExecute(context);
|
|
71
74
|
const call = generateSpy.mock.calls[0][0];
|
|
@@ -23,9 +23,9 @@ export type HttpServerResponseOptions = {
|
|
|
23
23
|
};
|
|
24
24
|
export declare class HttpServerResponse {
|
|
25
25
|
#private;
|
|
26
|
+
readonly headers: HttpHeaders;
|
|
26
27
|
statusCode: number | undefined;
|
|
27
28
|
statusMessage: string | undefined;
|
|
28
|
-
headers: HttpHeaders;
|
|
29
29
|
get body(): HttpServerResponseBody | undefined;
|
|
30
30
|
set body(value: HttpServerResponseBody | undefined);
|
|
31
31
|
get bodyType(): HttpServerResponseBodyType;
|
|
@@ -5,9 +5,9 @@ import { HttpHeaders } from '../http-headers.js';
|
|
|
5
5
|
export class HttpServerResponse {
|
|
6
6
|
#body;
|
|
7
7
|
#bodyType;
|
|
8
|
+
headers = new HttpHeaders();
|
|
8
9
|
statusCode;
|
|
9
10
|
statusMessage;
|
|
10
|
-
headers;
|
|
11
11
|
get body() {
|
|
12
12
|
return this.#body;
|
|
13
13
|
}
|
|
@@ -35,10 +35,18 @@ export class HttpServerResponse {
|
|
|
35
35
|
});
|
|
36
36
|
}
|
|
37
37
|
update(options) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
if (isDefined(options.statusCode)) {
|
|
39
|
+
this.statusCode = options.statusCode;
|
|
40
|
+
}
|
|
41
|
+
if (isDefined(options.statusMessage)) {
|
|
42
|
+
this.statusMessage = options.statusMessage;
|
|
43
|
+
}
|
|
44
|
+
if (isDefined(options.headers)) {
|
|
45
|
+
this.headers.setMany(options.headers);
|
|
46
|
+
}
|
|
47
|
+
if (isDefined(options.body)) {
|
|
48
|
+
this.body = options.body;
|
|
49
|
+
}
|
|
42
50
|
if (isDefined(options.cookies)) {
|
|
43
51
|
for (const [name, cookie] of objectEntries(options.cookies)) {
|
|
44
52
|
this.headers.append('Set-Cookie', formatSetCookie(name, cookie.value, cookie));
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from 'vitest';
|
|
2
2
|
// Mock supports before importing PrettyPrintLogFormatter
|
|
3
|
-
vi.mock('
|
|
3
|
+
vi.mock('../../supports.js', () => ({
|
|
4
4
|
supportsColoredStdout: true,
|
|
5
5
|
supportsColoredStderr: true,
|
|
6
6
|
}));
|
|
7
|
-
import { LogLevel } from '../level.js';
|
|
8
7
|
import { PrettyPrintLogFormatter } from '../formatters/pretty-print.js';
|
|
8
|
+
import { LogLevel } from '../level.js';
|
|
9
9
|
describe('PrettyPrintLogFormatter', () => {
|
|
10
10
|
const formatter = new PrettyPrintLogFormatter();
|
|
11
11
|
it('should format a log entry with an error and no context', () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tstdl/base",
|
|
3
|
-
"version": "0.93.
|
|
3
|
+
"version": "0.93.175",
|
|
4
4
|
"author": "Patrick Hein",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -203,7 +203,6 @@
|
|
|
203
203
|
"typedoc-plugin-missing-exports": "4.1",
|
|
204
204
|
"typescript": "5.9",
|
|
205
205
|
"typescript-eslint": "8.57",
|
|
206
|
-
"vite-tsconfig-paths": "6.1",
|
|
207
206
|
"vitest": "4.1"
|
|
208
207
|
}
|
|
209
208
|
}
|