@pikku/inspector 0.8.0 → 0.8.1

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,484 @@
1
+ import { test, describe } from 'node:test'
2
+ import { strict as assert } from 'node:assert'
3
+ import { matchesFilters } from './utils.js'
4
+ import { InspectorFilters } from './types.js'
5
+ import { PikkuEventTypes } from '@pikku/core'
6
+
7
+ describe('matchesFilters', () => {
8
+ // Mock logger for testing
9
+ const mockLogger = {
10
+ info: () => {},
11
+ error: () => {},
12
+ warn: () => {},
13
+ debug: () => {},
14
+ }
15
+
16
+ describe('Empty filters', () => {
17
+ test('should return true when no filters are provided', () => {
18
+ const filters: InspectorFilters = {}
19
+
20
+ const result = matchesFilters(
21
+ filters,
22
+ { tags: ['test'] },
23
+ { type: PikkuEventTypes.http, name: 'test-route' },
24
+ mockLogger
25
+ )
26
+
27
+ assert.equal(result, true)
28
+ })
29
+
30
+ test('should return true when all filter arrays are empty', () => {
31
+ const filters: InspectorFilters = {
32
+ tags: [],
33
+ types: [],
34
+ directories: [],
35
+ }
36
+
37
+ const result = matchesFilters(
38
+ filters,
39
+ { tags: ['test'] },
40
+ { type: PikkuEventTypes.http, name: 'test-route' },
41
+ mockLogger
42
+ )
43
+
44
+ assert.equal(result, true)
45
+ })
46
+ })
47
+
48
+ describe('Tag filtering', () => {
49
+ test('should return true when tags match', () => {
50
+ const filters: InspectorFilters = {
51
+ tags: ['api', 'public'],
52
+ }
53
+
54
+ const result = matchesFilters(
55
+ filters,
56
+ { tags: ['api', 'internal'] },
57
+ { type: PikkuEventTypes.http, name: 'test-route' },
58
+ mockLogger
59
+ )
60
+
61
+ assert.equal(result, true)
62
+ })
63
+
64
+ test('should return false when tags do not match', () => {
65
+ const filters: InspectorFilters = {
66
+ tags: ['api', 'public'],
67
+ }
68
+
69
+ const result = matchesFilters(
70
+ filters,
71
+ { tags: ['internal', 'private'] },
72
+ { type: PikkuEventTypes.http, name: 'test-route' },
73
+ mockLogger
74
+ )
75
+
76
+ assert.equal(result, false)
77
+ })
78
+
79
+ test('should return false when function has no tags but filter requires tags', () => {
80
+ const filters: InspectorFilters = {
81
+ tags: ['api'],
82
+ }
83
+
84
+ const result = matchesFilters(
85
+ filters,
86
+ { tags: undefined },
87
+ { type: PikkuEventTypes.http, name: 'test-route' },
88
+ mockLogger
89
+ )
90
+
91
+ assert.equal(result, false)
92
+ })
93
+
94
+ test('should return false when function has empty tags but filter requires tags', () => {
95
+ const filters: InspectorFilters = {
96
+ tags: ['api'],
97
+ }
98
+
99
+ const result = matchesFilters(
100
+ filters,
101
+ { tags: [] },
102
+ { type: PikkuEventTypes.http, name: 'test-route' },
103
+ mockLogger
104
+ )
105
+
106
+ assert.equal(result, false)
107
+ })
108
+ })
109
+
110
+ describe('Type filtering', () => {
111
+ test('should return true when type matches', () => {
112
+ const filters: InspectorFilters = {
113
+ types: ['http', 'channel'],
114
+ }
115
+
116
+ const result = matchesFilters(
117
+ filters,
118
+ { tags: ['test'] },
119
+ { type: PikkuEventTypes.http, name: 'test-route' },
120
+ mockLogger
121
+ )
122
+
123
+ assert.equal(result, true)
124
+ })
125
+
126
+ test('should return false when type does not match', () => {
127
+ const filters: InspectorFilters = {
128
+ types: ['channel', 'queue'],
129
+ }
130
+
131
+ const result = matchesFilters(
132
+ filters,
133
+ { tags: ['test'] },
134
+ { type: PikkuEventTypes.http, name: 'test-route' },
135
+ mockLogger
136
+ )
137
+
138
+ assert.equal(result, false)
139
+ })
140
+
141
+ test('should handle all PikkuEventTypes correctly', () => {
142
+ const eventTypes = [
143
+ PikkuEventTypes.http,
144
+ PikkuEventTypes.channel,
145
+ PikkuEventTypes.queue,
146
+ PikkuEventTypes.scheduler,
147
+ PikkuEventTypes.rpc,
148
+ PikkuEventTypes.mcp,
149
+ ]
150
+
151
+ eventTypes.forEach((eventType) => {
152
+ const filters: InspectorFilters = {
153
+ types: [eventType],
154
+ }
155
+
156
+ const result = matchesFilters(
157
+ filters,
158
+ { tags: ['test'] },
159
+ { type: eventType, name: 'test-route' },
160
+ mockLogger
161
+ )
162
+
163
+ assert.equal(result, true, `Should match for type: ${eventType}`)
164
+ })
165
+ })
166
+ })
167
+
168
+ describe('Directory filtering', () => {
169
+ test('should return true when directory matches', () => {
170
+ const filters: InspectorFilters = {
171
+ directories: ['src/api', 'src/internal'],
172
+ }
173
+
174
+ const result = matchesFilters(
175
+ filters,
176
+ { tags: ['test'] },
177
+ {
178
+ type: PikkuEventTypes.http,
179
+ name: 'test-route',
180
+ filePath: '/project/src/api/routes.ts',
181
+ },
182
+ mockLogger
183
+ )
184
+
185
+ assert.equal(result, true)
186
+ })
187
+
188
+ test('should return false when directory does not match', () => {
189
+ const filters: InspectorFilters = {
190
+ directories: ['src/api', 'src/internal'],
191
+ }
192
+
193
+ const result = matchesFilters(
194
+ filters,
195
+ { tags: ['test'] },
196
+ {
197
+ type: PikkuEventTypes.http,
198
+ name: 'test-route',
199
+ filePath: '/project/src/public/routes.ts',
200
+ },
201
+ mockLogger
202
+ )
203
+
204
+ assert.equal(result, false)
205
+ })
206
+
207
+ test('should handle Windows paths correctly', () => {
208
+ const filters: InspectorFilters = {
209
+ directories: ['src\\api'],
210
+ }
211
+
212
+ const result = matchesFilters(
213
+ filters,
214
+ { tags: ['test'] },
215
+ {
216
+ type: PikkuEventTypes.http,
217
+ name: 'test-route',
218
+ filePath: 'C:\\project\\src\\api\\routes.ts',
219
+ },
220
+ mockLogger
221
+ )
222
+
223
+ assert.equal(result, true)
224
+ })
225
+
226
+ test('should handle mixed path separators', () => {
227
+ const filters: InspectorFilters = {
228
+ directories: ['src/api'],
229
+ }
230
+
231
+ const result = matchesFilters(
232
+ filters,
233
+ { tags: ['test'] },
234
+ {
235
+ type: PikkuEventTypes.http,
236
+ name: 'test-route',
237
+ filePath: 'C:\\project\\src\\api\\routes.ts',
238
+ },
239
+ mockLogger
240
+ )
241
+
242
+ assert.equal(result, true)
243
+ })
244
+
245
+ test('should return false when filePath is not provided but directory filter exists', () => {
246
+ const filters: InspectorFilters = {
247
+ directories: ['src/api'],
248
+ }
249
+
250
+ const result = matchesFilters(
251
+ filters,
252
+ { tags: ['test'] },
253
+ { type: PikkuEventTypes.http, name: 'test-route' }, // no filePath
254
+ mockLogger
255
+ )
256
+
257
+ assert.equal(result, false)
258
+ })
259
+
260
+ test('should handle partial path matches', () => {
261
+ const filters: InspectorFilters = {
262
+ directories: ['api'],
263
+ }
264
+
265
+ const result = matchesFilters(
266
+ filters,
267
+ { tags: ['test'] },
268
+ {
269
+ type: PikkuEventTypes.http,
270
+ name: 'test-route',
271
+ filePath: '/project/src/api/v1/routes.ts',
272
+ },
273
+ mockLogger
274
+ )
275
+
276
+ assert.equal(result, true)
277
+ })
278
+ })
279
+
280
+ describe('Combined filtering', () => {
281
+ test('should return true when all filters pass', () => {
282
+ const filters: InspectorFilters = {
283
+ tags: ['api'],
284
+ types: ['http'],
285
+ directories: ['src/api'],
286
+ }
287
+
288
+ const result = matchesFilters(
289
+ filters,
290
+ { tags: ['api', 'public'] },
291
+ {
292
+ type: PikkuEventTypes.http,
293
+ name: 'test-route',
294
+ filePath: '/project/src/api/routes.ts',
295
+ },
296
+ mockLogger
297
+ )
298
+
299
+ assert.equal(result, true)
300
+ })
301
+
302
+ test('should return false when type filter fails (even if others pass)', () => {
303
+ const filters: InspectorFilters = {
304
+ tags: ['api'],
305
+ types: ['channel'],
306
+ directories: ['src/api'],
307
+ }
308
+
309
+ const result = matchesFilters(
310
+ filters,
311
+ { tags: ['api', 'public'] },
312
+ {
313
+ type: PikkuEventTypes.http,
314
+ name: 'test-route',
315
+ filePath: '/project/src/api/routes.ts',
316
+ },
317
+ mockLogger
318
+ )
319
+
320
+ assert.equal(result, false)
321
+ })
322
+
323
+ test('should return false when directory filter fails (even if others pass)', () => {
324
+ const filters: InspectorFilters = {
325
+ tags: ['api'],
326
+ types: ['http'],
327
+ directories: ['src/internal'],
328
+ }
329
+
330
+ const result = matchesFilters(
331
+ filters,
332
+ { tags: ['api', 'public'] },
333
+ {
334
+ type: PikkuEventTypes.http,
335
+ name: 'test-route',
336
+ filePath: '/project/src/api/routes.ts',
337
+ },
338
+ mockLogger
339
+ )
340
+
341
+ assert.equal(result, false)
342
+ })
343
+
344
+ test('should return false when tag filter fails (even if others pass)', () => {
345
+ const filters: InspectorFilters = {
346
+ tags: ['internal'],
347
+ types: ['http'],
348
+ directories: ['src/api'],
349
+ }
350
+
351
+ const result = matchesFilters(
352
+ filters,
353
+ { tags: ['api', 'public'] },
354
+ {
355
+ type: PikkuEventTypes.http,
356
+ name: 'test-route',
357
+ filePath: '/project/src/api/routes.ts',
358
+ },
359
+ mockLogger
360
+ )
361
+
362
+ assert.equal(result, false)
363
+ })
364
+ })
365
+
366
+ describe('Edge cases', () => {
367
+ test('should handle undefined params', () => {
368
+ const filters: InspectorFilters = {
369
+ tags: ['api'],
370
+ }
371
+
372
+ const result = matchesFilters(
373
+ filters,
374
+ { tags: undefined },
375
+ { type: PikkuEventTypes.http, name: 'test-route' },
376
+ mockLogger
377
+ )
378
+
379
+ assert.equal(result, false)
380
+ })
381
+
382
+ test('should handle empty meta name', () => {
383
+ const filters: InspectorFilters = {
384
+ types: ['channel'],
385
+ }
386
+
387
+ const result = matchesFilters(
388
+ filters,
389
+ { tags: ['test'] },
390
+ { type: PikkuEventTypes.http, name: '' },
391
+ mockLogger
392
+ )
393
+
394
+ assert.equal(result, false)
395
+ })
396
+
397
+ test('should handle special characters in paths', () => {
398
+ const filters: InspectorFilters = {
399
+ directories: ['src/api-v2'],
400
+ }
401
+
402
+ const result = matchesFilters(
403
+ filters,
404
+ { tags: ['test'] },
405
+ {
406
+ type: PikkuEventTypes.http,
407
+ name: 'test-route',
408
+ filePath: '/project/src/api-v2/routes.ts',
409
+ },
410
+ mockLogger
411
+ )
412
+
413
+ assert.equal(result, true)
414
+ })
415
+
416
+ test('should handle case sensitivity in directory paths', () => {
417
+ const filters: InspectorFilters = {
418
+ directories: ['src/API'],
419
+ }
420
+
421
+ const result = matchesFilters(
422
+ filters,
423
+ { tags: ['test'] },
424
+ {
425
+ type: PikkuEventTypes.http,
426
+ name: 'test-route',
427
+ filePath: '/project/src/api/routes.ts',
428
+ },
429
+ mockLogger
430
+ )
431
+
432
+ assert.equal(result, false)
433
+ })
434
+
435
+ test('should handle multiple matching tags', () => {
436
+ const filters: InspectorFilters = {
437
+ tags: ['api', 'v1'],
438
+ }
439
+
440
+ const result = matchesFilters(
441
+ filters,
442
+ { tags: ['api', 'public', 'v1'] },
443
+ { type: PikkuEventTypes.http, name: 'test-route' },
444
+ mockLogger
445
+ )
446
+
447
+ assert.equal(result, true)
448
+ })
449
+
450
+ test('should handle multiple matching types', () => {
451
+ const filters: InspectorFilters = {
452
+ types: ['http', 'channel'],
453
+ }
454
+
455
+ const result = matchesFilters(
456
+ filters,
457
+ { tags: ['test'] },
458
+ { type: PikkuEventTypes.channel, name: 'test-channel' },
459
+ mockLogger
460
+ )
461
+
462
+ assert.equal(result, true)
463
+ })
464
+
465
+ test('should handle multiple matching directories', () => {
466
+ const filters: InspectorFilters = {
467
+ directories: ['src/api', 'src/internal'],
468
+ }
469
+
470
+ const result = matchesFilters(
471
+ filters,
472
+ { tags: ['test'] },
473
+ {
474
+ type: PikkuEventTypes.http,
475
+ name: 'test-route',
476
+ filePath: '/project/src/internal/routes.ts',
477
+ },
478
+ mockLogger
479
+ )
480
+
481
+ assert.equal(result, true)
482
+ })
483
+ })
484
+ })
package/src/utils.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as ts from 'typescript'
2
- import { InspectorFilters } from './types.js'
2
+ import { InspectorFilters, InspectorLogger } from './types.js'
3
3
  import { PikkuEventTypes } from '@pikku/core'
4
4
 
5
5
  type ExtractedFunctionName = {
@@ -847,16 +847,66 @@ export const matchesFilters = (
847
847
  meta: {
848
848
  type: PikkuEventTypes
849
849
  name: string
850
- }
850
+ filePath?: string
851
+ },
852
+ logger: InspectorLogger
851
853
  ) => {
852
- if (Object.keys(filters).length === 0 || filters.tags?.length === 0) {
854
+ // If no filters are provided, allow everything
855
+ if (Object.keys(filters).length === 0) {
853
856
  return true
854
857
  }
855
858
 
856
- if (filters.tags?.some((tag) => params.tags?.includes(tag))) {
859
+ // If all filter arrays are empty, allow everything
860
+ if (
861
+ (!filters.tags || filters.tags.length === 0) &&
862
+ (!filters.types || filters.types.length === 0) &&
863
+ (!filters.directories || filters.directories.length === 0)
864
+ ) {
857
865
  return true
858
866
  }
859
867
 
860
- console.debug(`⒡ Filtered: ${meta.type}:${meta.name}`)
861
- return false
868
+ // Check type filter
869
+ if (filters.types && filters.types.length > 0) {
870
+ if (!filters.types.includes(meta.type)) {
871
+ logger.debug(`⒡ Filtered by type: ${meta.type}:${meta.name}`)
872
+ return false
873
+ }
874
+ }
875
+
876
+ // Check directory filter
877
+ if (filters.directories && filters.directories.length > 0) {
878
+ if (!meta.filePath) {
879
+ logger.debug(
880
+ `⒡ Filtered by directory: ${meta.type}:${meta.name} (${meta.filePath})`
881
+ )
882
+ return false
883
+ }
884
+
885
+ const matchesDirectory = filters.directories.some((dir) => {
886
+ // Normalize paths for comparison
887
+ const normalizedFilePath = meta.filePath!.replace(/\\/g, '/')
888
+ const normalizedDir = dir.replace(/\\/g, '/')
889
+ return normalizedFilePath.includes(normalizedDir)
890
+ })
891
+
892
+ if (!matchesDirectory) {
893
+ logger.debug(
894
+ `⒡ Filtered by directory: ${meta.type}:${meta.name} (${meta.filePath})`
895
+ )
896
+ return false
897
+ }
898
+ }
899
+
900
+ // Check tag filter
901
+ if (filters.tags && filters.tags.length > 0) {
902
+ if (
903
+ !params.tags ||
904
+ !filters.tags.some((tag) => params.tags!.includes(tag))
905
+ ) {
906
+ logger.debug(`⒡ Filtered by tags: ${meta.type}:${meta.name}`)
907
+ return false
908
+ }
909
+ }
910
+
911
+ return true
862
912
  }
package/src/visit.ts CHANGED
@@ -7,7 +7,7 @@ import { addQueueWorker } from './add-queue-worker.js'
7
7
  import { addMCPResource } from './add-mcp-resource.js'
8
8
  import { addMCPTool } from './add-mcp-tool.js'
9
9
  import { addMCPPrompt } from './add-mcp-prompt.js'
10
- import { InspectorFilters, InspectorState } from './types.js'
10
+ import { InspectorFilters, InspectorState, InspectorLogger } from './types.js'
11
11
  import { addFunctions } from './add-functions.js'
12
12
  import { addChannel } from './add-channel.js'
13
13
 
@@ -15,7 +15,8 @@ export const visitSetup = (
15
15
  checker: ts.TypeChecker,
16
16
  node: ts.Node,
17
17
  state: InspectorState,
18
- filters: InspectorFilters
18
+ filters: InspectorFilters,
19
+ logger: InspectorLogger
19
20
  ) => {
20
21
  addFileExtendsCoreType(
21
22
  node,
@@ -53,23 +54,28 @@ export const visitSetup = (
53
54
  )
54
55
 
55
56
  addFileWithFactory(node, checker, state.configFactories, 'CreateConfig')
56
- addFunctions(node, checker, state, filters)
57
+ addFunctions(node, checker, state, filters, logger)
57
58
 
58
- ts.forEachChild(node, (child) => visitSetup(checker, child, state, filters))
59
+ ts.forEachChild(node, (child) =>
60
+ visitSetup(checker, child, state, filters, logger)
61
+ )
59
62
  }
60
63
 
61
64
  export const visitRoutes = (
62
65
  checker: ts.TypeChecker,
63
66
  node: ts.Node,
64
67
  state: InspectorState,
65
- filters: InspectorFilters
68
+ filters: InspectorFilters,
69
+ logger: InspectorLogger
66
70
  ) => {
67
- addHTTPRoute(node, checker, state, filters)
68
- addSchedule(node, checker, state, filters)
69
- addQueueWorker(node, checker, state, filters)
70
- addChannel(node, checker, state, filters)
71
- addMCPResource(node, checker, state, filters)
72
- addMCPTool(node, checker, state, filters)
73
- addMCPPrompt(node, checker, state, filters)
74
- ts.forEachChild(node, (child) => visitRoutes(checker, child, state, filters))
71
+ addHTTPRoute(node, checker, state, filters, logger)
72
+ addSchedule(node, checker, state, filters, logger)
73
+ addQueueWorker(node, checker, state, filters, logger)
74
+ addChannel(node, checker, state, filters, logger)
75
+ addMCPResource(node, checker, state, filters, logger)
76
+ addMCPTool(node, checker, state, filters, logger)
77
+ addMCPPrompt(node, checker, state, filters, logger)
78
+ ts.forEachChild(node, (child) =>
79
+ visitRoutes(checker, child, state, filters, logger)
80
+ )
75
81
  }