blackbox-cli-vscode-ide-companion 0.0.5

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.
@@ -0,0 +1,440 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
8
+ import * as vscode from 'vscode';
9
+ import { OpenFilesManager, MAX_FILES } from './open-files-manager.js';
10
+
11
+ vi.mock('vscode', () => ({
12
+ EventEmitter: vi.fn(() => {
13
+ const listeners: Array<(e: void) => unknown> = [];
14
+ return {
15
+ event: vi.fn((listener) => {
16
+ listeners.push(listener);
17
+ return { dispose: vi.fn() };
18
+ }),
19
+ fire: vi.fn(() => {
20
+ listeners.forEach((listener) => listener(undefined));
21
+ }),
22
+ dispose: vi.fn(),
23
+ };
24
+ }),
25
+ window: {
26
+ onDidChangeActiveTextEditor: vi.fn(),
27
+ onDidChangeTextEditorSelection: vi.fn(),
28
+ },
29
+ workspace: {
30
+ onDidDeleteFiles: vi.fn(),
31
+ onDidCloseTextDocument: vi.fn(),
32
+ onDidRenameFiles: vi.fn(),
33
+ },
34
+ Uri: {
35
+ file: (path: string) => ({
36
+ fsPath: path,
37
+ scheme: 'file',
38
+ }),
39
+ },
40
+ TextEditorSelectionChangeKind: {
41
+ Mouse: 2,
42
+ },
43
+ }));
44
+
45
+ describe('OpenFilesManager', () => {
46
+ let context: vscode.ExtensionContext;
47
+ let onDidChangeActiveTextEditorListener: (
48
+ editor: vscode.TextEditor | undefined,
49
+ ) => void;
50
+ let onDidChangeTextEditorSelectionListener: (
51
+ e: vscode.TextEditorSelectionChangeEvent,
52
+ ) => void;
53
+ let onDidDeleteFilesListener: (e: vscode.FileDeleteEvent) => void;
54
+ let onDidCloseTextDocumentListener: (doc: vscode.TextDocument) => void;
55
+ let onDidRenameFilesListener: (e: vscode.FileRenameEvent) => void;
56
+
57
+ beforeEach(() => {
58
+ vi.useFakeTimers();
59
+
60
+ vi.mocked(vscode.window.onDidChangeActiveTextEditor).mockImplementation(
61
+ (listener) => {
62
+ onDidChangeActiveTextEditorListener = listener;
63
+ return { dispose: vi.fn() };
64
+ },
65
+ );
66
+ vi.mocked(vscode.window.onDidChangeTextEditorSelection).mockImplementation(
67
+ (listener) => {
68
+ onDidChangeTextEditorSelectionListener = listener;
69
+ return { dispose: vi.fn() };
70
+ },
71
+ );
72
+ vi.mocked(vscode.workspace.onDidDeleteFiles).mockImplementation(
73
+ (listener) => {
74
+ onDidDeleteFilesListener = listener;
75
+ return { dispose: vi.fn() };
76
+ },
77
+ );
78
+ vi.mocked(vscode.workspace.onDidCloseTextDocument).mockImplementation(
79
+ (listener) => {
80
+ onDidCloseTextDocumentListener = listener;
81
+ return { dispose: vi.fn() };
82
+ },
83
+ );
84
+ vi.mocked(vscode.workspace.onDidRenameFiles).mockImplementation(
85
+ (listener) => {
86
+ onDidRenameFilesListener = listener;
87
+ return { dispose: vi.fn() };
88
+ },
89
+ );
90
+
91
+ context = {
92
+ subscriptions: [],
93
+ } as unknown as vscode.ExtensionContext;
94
+ });
95
+
96
+ afterEach(() => {
97
+ vi.restoreAllMocks();
98
+ vi.useRealTimers();
99
+ });
100
+
101
+ const getUri = (path: string) =>
102
+ vscode.Uri.file(path) as unknown as vscode.Uri;
103
+
104
+ const addFile = (uri: vscode.Uri) => {
105
+ onDidChangeActiveTextEditorListener({
106
+ document: {
107
+ uri,
108
+ getText: () => '',
109
+ },
110
+ selection: {
111
+ active: { line: 0, character: 0 },
112
+ },
113
+ } as unknown as vscode.TextEditor);
114
+ };
115
+
116
+ it('adds a file to the list', async () => {
117
+ const manager = new OpenFilesManager(context);
118
+ const uri = getUri('/test/file1.txt');
119
+ addFile(uri);
120
+ await vi.advanceTimersByTimeAsync(100);
121
+ expect(manager.state.workspaceState!.openFiles).toHaveLength(1);
122
+ expect(manager.state.workspaceState!.openFiles![0].path).toBe(
123
+ '/test/file1.txt',
124
+ );
125
+ });
126
+
127
+ it('moves an existing file to the top', async () => {
128
+ const manager = new OpenFilesManager(context);
129
+ const uri1 = getUri('/test/file1.txt');
130
+ const uri2 = getUri('/test/file2.txt');
131
+ addFile(uri1);
132
+ addFile(uri2);
133
+ addFile(uri1);
134
+ await vi.advanceTimersByTimeAsync(100);
135
+ expect(manager.state.workspaceState!.openFiles).toHaveLength(2);
136
+ expect(manager.state.workspaceState!.openFiles![0].path).toBe(
137
+ '/test/file1.txt',
138
+ );
139
+ });
140
+
141
+ it('does not exceed the max number of files', async () => {
142
+ const manager = new OpenFilesManager(context);
143
+ for (let i = 0; i < MAX_FILES + 5; i++) {
144
+ const uri = getUri(`/test/file${i}.txt`);
145
+ addFile(uri);
146
+ }
147
+ await vi.advanceTimersByTimeAsync(100);
148
+ expect(manager.state.workspaceState!.openFiles).toHaveLength(MAX_FILES);
149
+ expect(manager.state.workspaceState!.openFiles![0].path).toBe(
150
+ `/test/file${MAX_FILES + 4}.txt`,
151
+ );
152
+ expect(manager.state.workspaceState!.openFiles![MAX_FILES - 1].path).toBe(
153
+ `/test/file5.txt`,
154
+ );
155
+ });
156
+
157
+ it('fires onDidChange when a file is added', async () => {
158
+ const manager = new OpenFilesManager(context);
159
+ const onDidChangeSpy = vi.fn();
160
+ manager.onDidChange(onDidChangeSpy);
161
+
162
+ const uri = getUri('/test/file1.txt');
163
+ addFile(uri);
164
+
165
+ await vi.advanceTimersByTimeAsync(100);
166
+ expect(onDidChangeSpy).toHaveBeenCalled();
167
+ });
168
+
169
+ it('removes a file when it is closed', async () => {
170
+ const manager = new OpenFilesManager(context);
171
+ const uri = getUri('/test/file1.txt');
172
+ addFile(uri);
173
+ await vi.advanceTimersByTimeAsync(100);
174
+ expect(manager.state.workspaceState!.openFiles).toHaveLength(1);
175
+
176
+ onDidCloseTextDocumentListener({ uri } as vscode.TextDocument);
177
+ await vi.advanceTimersByTimeAsync(100);
178
+
179
+ expect(manager.state.workspaceState!.openFiles).toHaveLength(0);
180
+ });
181
+
182
+ it('fires onDidChange when a file is removed', async () => {
183
+ const manager = new OpenFilesManager(context);
184
+ const uri = getUri('/test/file1.txt');
185
+ addFile(uri);
186
+ await vi.advanceTimersByTimeAsync(100);
187
+
188
+ const onDidChangeSpy = vi.fn();
189
+ manager.onDidChange(onDidChangeSpy);
190
+
191
+ onDidCloseTextDocumentListener({ uri } as vscode.TextDocument);
192
+ await vi.advanceTimersByTimeAsync(100);
193
+
194
+ expect(onDidChangeSpy).toHaveBeenCalled();
195
+ });
196
+
197
+ it('removes a file when it is deleted', async () => {
198
+ const manager = new OpenFilesManager(context);
199
+ const uri1 = getUri('/test/file1.txt');
200
+ const uri2 = getUri('/test/file2.txt');
201
+ addFile(uri1);
202
+ addFile(uri2);
203
+ await vi.advanceTimersByTimeAsync(100);
204
+ expect(manager.state.workspaceState!.openFiles).toHaveLength(2);
205
+
206
+ onDidDeleteFilesListener({ files: [uri1] });
207
+ await vi.advanceTimersByTimeAsync(100);
208
+
209
+ expect(manager.state.workspaceState!.openFiles).toHaveLength(1);
210
+ expect(manager.state.workspaceState!.openFiles![0].path).toBe(
211
+ '/test/file2.txt',
212
+ );
213
+ });
214
+
215
+ it('fires onDidChange when a file is deleted', async () => {
216
+ const manager = new OpenFilesManager(context);
217
+ const uri = getUri('/test/file1.txt');
218
+ addFile(uri);
219
+ await vi.advanceTimersByTimeAsync(100);
220
+
221
+ const onDidChangeSpy = vi.fn();
222
+ manager.onDidChange(onDidChangeSpy);
223
+
224
+ onDidDeleteFilesListener({ files: [uri] });
225
+ await vi.advanceTimersByTimeAsync(100);
226
+
227
+ expect(onDidChangeSpy).toHaveBeenCalled();
228
+ });
229
+
230
+ it('removes multiple files when they are deleted', async () => {
231
+ const manager = new OpenFilesManager(context);
232
+ const uri1 = getUri('/test/file1.txt');
233
+ const uri2 = getUri('/test/file2.txt');
234
+ const uri3 = getUri('/test/file3.txt');
235
+ addFile(uri1);
236
+ addFile(uri2);
237
+ addFile(uri3);
238
+ await vi.advanceTimersByTimeAsync(100);
239
+ expect(manager.state.workspaceState!.openFiles).toHaveLength(3);
240
+
241
+ onDidDeleteFilesListener({ files: [uri1, uri3] });
242
+ await vi.advanceTimersByTimeAsync(100);
243
+
244
+ expect(manager.state.workspaceState!.openFiles).toHaveLength(1);
245
+ expect(manager.state.workspaceState!.openFiles![0].path).toBe(
246
+ '/test/file2.txt',
247
+ );
248
+ });
249
+
250
+ it('fires onDidChange only once when adding an existing file', async () => {
251
+ const manager = new OpenFilesManager(context);
252
+ const uri = getUri('/test/file1.txt');
253
+ addFile(uri);
254
+ await vi.advanceTimersByTimeAsync(100);
255
+
256
+ const onDidChangeSpy = vi.fn();
257
+ manager.onDidChange(onDidChangeSpy);
258
+
259
+ addFile(uri);
260
+ await vi.advanceTimersByTimeAsync(100);
261
+ expect(onDidChangeSpy).toHaveBeenCalledTimes(1);
262
+ });
263
+
264
+ it('updates the file when it is renamed', async () => {
265
+ const manager = new OpenFilesManager(context);
266
+ const oldUri = getUri('/test/file1.txt');
267
+ const newUri = getUri('/test/file2.txt');
268
+ addFile(oldUri);
269
+ await vi.advanceTimersByTimeAsync(100);
270
+ expect(manager.state.workspaceState!.openFiles).toHaveLength(1);
271
+ expect(manager.state.workspaceState!.openFiles![0].path).toBe(
272
+ '/test/file1.txt',
273
+ );
274
+
275
+ onDidRenameFilesListener({ files: [{ oldUri, newUri }] });
276
+ await vi.advanceTimersByTimeAsync(100);
277
+
278
+ expect(manager.state.workspaceState!.openFiles).toHaveLength(1);
279
+ expect(manager.state.workspaceState!.openFiles![0].path).toBe(
280
+ '/test/file2.txt',
281
+ );
282
+ });
283
+
284
+ it('adds a file when the active editor changes', async () => {
285
+ const manager = new OpenFilesManager(context);
286
+ const uri = getUri('/test/file1.txt');
287
+
288
+ addFile(uri);
289
+ await vi.advanceTimersByTimeAsync(100);
290
+
291
+ expect(manager.state.workspaceState!.openFiles).toHaveLength(1);
292
+ expect(manager.state.workspaceState!.openFiles![0].path).toBe(
293
+ '/test/file1.txt',
294
+ );
295
+ });
296
+
297
+ it('updates the cursor position on selection change', async () => {
298
+ const manager = new OpenFilesManager(context);
299
+ const uri = getUri('/test/file1.txt');
300
+ addFile(uri);
301
+ await vi.advanceTimersByTimeAsync(100);
302
+
303
+ const selection = {
304
+ active: { line: 10, character: 20 },
305
+ } as vscode.Selection;
306
+
307
+ onDidChangeTextEditorSelectionListener({
308
+ textEditor: {
309
+ document: { uri, getText: () => '' },
310
+ selection,
311
+ } as vscode.TextEditor,
312
+ selections: [selection],
313
+ kind: vscode.TextEditorSelectionChangeKind.Mouse,
314
+ });
315
+
316
+ await vi.advanceTimersByTimeAsync(100);
317
+
318
+ const file = manager.state.workspaceState!.openFiles![0];
319
+ expect(file.cursor).toEqual({ line: 11, character: 20 });
320
+ });
321
+
322
+ it('updates the selected text on selection change', async () => {
323
+ const manager = new OpenFilesManager(context);
324
+ const uri = getUri('/test/file1.txt');
325
+ const selection = {
326
+ active: { line: 10, character: 20 },
327
+ } as vscode.Selection;
328
+
329
+ // We need to override the mock for getText for this test
330
+ const textEditor = {
331
+ document: {
332
+ uri,
333
+ getText: vi.fn().mockReturnValue('selected text'),
334
+ },
335
+ selection,
336
+ } as unknown as vscode.TextEditor;
337
+
338
+ onDidChangeActiveTextEditorListener(textEditor);
339
+ await vi.advanceTimersByTimeAsync(100);
340
+
341
+ onDidChangeTextEditorSelectionListener({
342
+ textEditor,
343
+ selections: [selection],
344
+ kind: vscode.TextEditorSelectionChangeKind.Mouse,
345
+ });
346
+
347
+ await vi.advanceTimersByTimeAsync(100);
348
+
349
+ const file = manager.state.workspaceState!.openFiles![0];
350
+ expect(file.selectedText).toBe('selected text');
351
+ expect(textEditor.document.getText).toHaveBeenCalledWith(selection);
352
+ });
353
+
354
+ it('truncates long selected text', async () => {
355
+ const manager = new OpenFilesManager(context);
356
+ const uri = getUri('/test/file1.txt');
357
+ const longText = 'a'.repeat(20000);
358
+ const truncatedText = longText.substring(0, 16384) + '... [TRUNCATED]';
359
+
360
+ const selection = {
361
+ active: { line: 10, character: 20 },
362
+ } as vscode.Selection;
363
+
364
+ const textEditor = {
365
+ document: {
366
+ uri,
367
+ getText: vi.fn().mockReturnValue(longText),
368
+ },
369
+ selection,
370
+ } as unknown as vscode.TextEditor;
371
+
372
+ onDidChangeActiveTextEditorListener(textEditor);
373
+ await vi.advanceTimersByTimeAsync(100);
374
+
375
+ onDidChangeTextEditorSelectionListener({
376
+ textEditor,
377
+ selections: [selection],
378
+ kind: vscode.TextEditorSelectionChangeKind.Mouse,
379
+ });
380
+
381
+ await vi.advanceTimersByTimeAsync(100);
382
+
383
+ const file = manager.state.workspaceState!.openFiles![0];
384
+ expect(file.selectedText).toBe(truncatedText);
385
+ });
386
+
387
+ it('deactivates the previously active file', async () => {
388
+ const manager = new OpenFilesManager(context);
389
+ const uri1 = getUri('/test/file1.txt');
390
+ const uri2 = getUri('/test/file2.txt');
391
+
392
+ addFile(uri1);
393
+ await vi.advanceTimersByTimeAsync(100);
394
+
395
+ const selection = {
396
+ active: { line: 10, character: 20 },
397
+ } as vscode.Selection;
398
+
399
+ onDidChangeTextEditorSelectionListener({
400
+ textEditor: {
401
+ document: { uri: uri1, getText: () => '' },
402
+ selection,
403
+ } as vscode.TextEditor,
404
+ selections: [selection],
405
+ kind: vscode.TextEditorSelectionChangeKind.Mouse,
406
+ });
407
+ await vi.advanceTimersByTimeAsync(100);
408
+
409
+ let file1 = manager.state.workspaceState!.openFiles![0];
410
+ expect(file1.isActive).toBe(true);
411
+ expect(file1.cursor).toBeDefined();
412
+
413
+ addFile(uri2);
414
+ await vi.advanceTimersByTimeAsync(100);
415
+
416
+ file1 = manager.state.workspaceState!.openFiles!.find(
417
+ (f) => f.path === '/test/file1.txt',
418
+ )!;
419
+ const file2 = manager.state.workspaceState!.openFiles![0];
420
+
421
+ expect(file1.isActive).toBe(false);
422
+ expect(file1.cursor).toBeUndefined();
423
+ expect(file1.selectedText).toBeUndefined();
424
+ expect(file2.path).toBe('/test/file2.txt');
425
+ expect(file2.isActive).toBe(true);
426
+ });
427
+
428
+ it('ignores non-file URIs', async () => {
429
+ const manager = new OpenFilesManager(context);
430
+ const uri = {
431
+ fsPath: '/test/file1.txt',
432
+ scheme: 'untitled',
433
+ } as vscode.Uri;
434
+
435
+ addFile(uri);
436
+ await vi.advanceTimersByTimeAsync(100);
437
+
438
+ expect(manager.state.workspaceState!.openFiles).toHaveLength(0);
439
+ });
440
+ });
@@ -0,0 +1,178 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import * as vscode from 'vscode';
8
+ import type { File, IdeContext } from '@blackbox_ai/blackbox-cli-core';
9
+
10
+ export const MAX_FILES = 10;
11
+ const MAX_SELECTED_TEXT_LENGTH = 16384; // 16 KiB limit
12
+
13
+ /**
14
+ * Keeps track of the workspace state, including open files, cursor position, and selected text.
15
+ */
16
+ export class OpenFilesManager {
17
+ private readonly onDidChangeEmitter = new vscode.EventEmitter<void>();
18
+ readonly onDidChange = this.onDidChangeEmitter.event;
19
+ private debounceTimer: NodeJS.Timeout | undefined;
20
+ private openFiles: File[] = [];
21
+
22
+ constructor(private readonly context: vscode.ExtensionContext) {
23
+ const editorWatcher = vscode.window.onDidChangeActiveTextEditor(
24
+ (editor) => {
25
+ if (editor && this.isFileUri(editor.document.uri)) {
26
+ this.addOrMoveToFront(editor);
27
+ this.fireWithDebounce();
28
+ }
29
+ },
30
+ );
31
+
32
+ const selectionWatcher = vscode.window.onDidChangeTextEditorSelection(
33
+ (event) => {
34
+ if (this.isFileUri(event.textEditor.document.uri)) {
35
+ this.updateActiveContext(event.textEditor);
36
+ this.fireWithDebounce();
37
+ }
38
+ },
39
+ );
40
+
41
+ const closeWatcher = vscode.workspace.onDidCloseTextDocument((document) => {
42
+ if (this.isFileUri(document.uri)) {
43
+ this.remove(document.uri);
44
+ this.fireWithDebounce();
45
+ }
46
+ });
47
+
48
+ const deleteWatcher = vscode.workspace.onDidDeleteFiles((event) => {
49
+ for (const uri of event.files) {
50
+ if (this.isFileUri(uri)) {
51
+ this.remove(uri);
52
+ }
53
+ }
54
+ this.fireWithDebounce();
55
+ });
56
+
57
+ const renameWatcher = vscode.workspace.onDidRenameFiles((event) => {
58
+ for (const { oldUri, newUri } of event.files) {
59
+ if (this.isFileUri(oldUri)) {
60
+ if (this.isFileUri(newUri)) {
61
+ this.rename(oldUri, newUri);
62
+ } else {
63
+ // The file was renamed to a non-file URI, so we should remove it.
64
+ this.remove(oldUri);
65
+ }
66
+ }
67
+ }
68
+ this.fireWithDebounce();
69
+ });
70
+
71
+ context.subscriptions.push(
72
+ editorWatcher,
73
+ selectionWatcher,
74
+ closeWatcher,
75
+ deleteWatcher,
76
+ renameWatcher,
77
+ );
78
+
79
+ // Just add current active file on start-up.
80
+ if (
81
+ vscode.window.activeTextEditor &&
82
+ this.isFileUri(vscode.window.activeTextEditor.document.uri)
83
+ ) {
84
+ this.addOrMoveToFront(vscode.window.activeTextEditor);
85
+ }
86
+ }
87
+
88
+ private isFileUri(uri: vscode.Uri): boolean {
89
+ return uri.scheme === 'file';
90
+ }
91
+
92
+ private addOrMoveToFront(editor: vscode.TextEditor) {
93
+ // Deactivate previous active file
94
+ const currentActive = this.openFiles.find((f) => f.isActive);
95
+ if (currentActive) {
96
+ currentActive.isActive = false;
97
+ currentActive.cursor = undefined;
98
+ currentActive.selectedText = undefined;
99
+ }
100
+
101
+ // Remove if it exists
102
+ const index = this.openFiles.findIndex(
103
+ (f) => f.path === editor.document.uri.fsPath,
104
+ );
105
+ if (index !== -1) {
106
+ this.openFiles.splice(index, 1);
107
+ }
108
+
109
+ // Add to the front as active
110
+ this.openFiles.unshift({
111
+ path: editor.document.uri.fsPath,
112
+ timestamp: Date.now(),
113
+ isActive: true,
114
+ });
115
+
116
+ // Enforce max length
117
+ if (this.openFiles.length > MAX_FILES) {
118
+ this.openFiles.pop();
119
+ }
120
+
121
+ this.updateActiveContext(editor);
122
+ }
123
+
124
+ private remove(uri: vscode.Uri) {
125
+ const index = this.openFiles.findIndex((f) => f.path === uri.fsPath);
126
+ if (index !== -1) {
127
+ this.openFiles.splice(index, 1);
128
+ }
129
+ }
130
+
131
+ private rename(oldUri: vscode.Uri, newUri: vscode.Uri) {
132
+ const index = this.openFiles.findIndex((f) => f.path === oldUri.fsPath);
133
+ if (index !== -1) {
134
+ this.openFiles[index].path = newUri.fsPath;
135
+ }
136
+ }
137
+
138
+ private updateActiveContext(editor: vscode.TextEditor) {
139
+ const file = this.openFiles.find(
140
+ (f) => f.path === editor.document.uri.fsPath,
141
+ );
142
+ if (!file || !file.isActive) {
143
+ return;
144
+ }
145
+
146
+ file.cursor = editor.selection.active
147
+ ? {
148
+ line: editor.selection.active.line + 1,
149
+ character: editor.selection.active.character,
150
+ }
151
+ : undefined;
152
+
153
+ let selectedText: string | undefined =
154
+ editor.document.getText(editor.selection) || undefined;
155
+ if (selectedText && selectedText.length > MAX_SELECTED_TEXT_LENGTH) {
156
+ selectedText =
157
+ selectedText.substring(0, MAX_SELECTED_TEXT_LENGTH) + '... [TRUNCATED]';
158
+ }
159
+ file.selectedText = selectedText;
160
+ }
161
+
162
+ private fireWithDebounce() {
163
+ if (this.debounceTimer) {
164
+ clearTimeout(this.debounceTimer);
165
+ }
166
+ this.debounceTimer = setTimeout(() => {
167
+ this.onDidChangeEmitter.fire();
168
+ }, 50); // 50ms
169
+ }
170
+
171
+ get state(): IdeContext {
172
+ return {
173
+ workspaceState: {
174
+ openFiles: [...this.openFiles],
175
+ },
176
+ };
177
+ }
178
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import * as vscode from 'vscode';
8
+
9
+ export function createLogger(
10
+ context: vscode.ExtensionContext,
11
+ logger: vscode.OutputChannel,
12
+ ) {
13
+ return (message: string) => {
14
+ if (context.extensionMode === vscode.ExtensionMode.Development) {
15
+ logger.appendLine(message);
16
+ }
17
+ };
18
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "NodeNext",
4
+ "moduleResolution": "NodeNext",
5
+ "target": "ES2022",
6
+ "lib": ["ES2022", "dom"],
7
+ "sourceMap": true,
8
+ /*
9
+ * skipLibCheck is necessary because the a2a-server package depends on
10
+ * @google-cloud/storage which pulls in @types/request which depends on
11
+ * tough-cookie@4.x while jsdom requires tough-cookie@5.x. This causes a
12
+ * type checking error in ../../node_modules/@types/request/index.d.ts.
13
+ */
14
+ "skipLibCheck": true,
15
+ "strict": true /* enable all strict type-checking options */
16
+ /* Additional Checks */
17
+ // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
18
+ // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
19
+ // "noUnusedParameters": true, /* Report errors on unused parameters. */
20
+ }
21
+ }
@@ -0,0 +1,15 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ globals: true,
6
+ environment: 'node',
7
+ include: ['src/**/*.test.ts'],
8
+ coverage: {
9
+ provider: 'v8',
10
+ reporter: ['text', 'json', 'html', 'clover'],
11
+ include: ['src/**/*.ts'],
12
+ exclude: ['src/**/*.test.ts', 'src/**/*.d.ts'],
13
+ },
14
+ },
15
+ });