@pikku/inspector 0.7.7 → 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.
package/src/types.ts CHANGED
@@ -1,9 +1,11 @@
1
1
  import { ChannelsMeta } from '@pikku/core/channel'
2
2
  import { HTTPRoutesMeta } from '@pikku/core/http'
3
3
  import { ScheduledTasksMeta } from '@pikku/core/scheduler'
4
+ import { queueWorkersMeta } from '@pikku/core/queue'
5
+ import { MCPResourceMeta, MCPToolMeta, MCPPromptMeta } from '@pikku/core'
4
6
  import { TypesMap } from './types-map.js'
5
7
  import { FunctionsMeta } from '@pikku/core'
6
- import { RPCMeta } from '../../core/src/rpc/rpc-types.js'
8
+ import { RPCMeta } from '@pikku/core/rpc'
7
9
 
8
10
  export type PathToNameAndType = Map<
9
11
  string,
@@ -38,6 +40,15 @@ export interface InspectorChannelState {
38
40
 
39
41
  export type InspectorFilters = {
40
42
  tags?: string[]
43
+ types?: string[]
44
+ directories?: string[]
45
+ }
46
+
47
+ export interface InspectorLogger {
48
+ info: (message: string) => void
49
+ error: (message: string) => void
50
+ warn: (message: string) => void
51
+ debug: (message: string) => void
41
52
  }
42
53
  export interface InspectorState {
43
54
  singletonServicesTypeImportMap: PathToNameAndType
@@ -53,7 +64,17 @@ export interface InspectorState {
53
64
  meta: ScheduledTasksMeta
54
65
  files: Set<string>
55
66
  }
67
+ queueWorkers: {
68
+ meta: queueWorkersMeta
69
+ files: Set<string>
70
+ }
56
71
  rpc: {
57
72
  meta: Record<string, RPCMeta>
58
73
  }
74
+ mcpEndpoints: {
75
+ resourcesMeta: MCPResourceMeta
76
+ toolsMeta: MCPToolMeta
77
+ promptsMeta: MCPPromptMeta
78
+ files: Set<string>
79
+ }
59
80
  }
@@ -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,6 @@
1
1
  import * as ts from 'typescript'
2
- import { InspectorFilters } from './types.js'
2
+ import { InspectorFilters, InspectorLogger } from './types.js'
3
+ import { PikkuEventTypes } from '@pikku/core'
3
4
 
4
5
  type ExtractedFunctionName = {
5
6
  pikkuFuncName: string
@@ -843,16 +844,69 @@ export function getPropertyAssignmentInitializer(
843
844
  export const matchesFilters = (
844
845
  filters: InspectorFilters,
845
846
  params: { tags?: string[] },
846
- meta: { type: 'schedule' | 'http' | 'channel'; name: string }
847
+ meta: {
848
+ type: PikkuEventTypes
849
+ name: string
850
+ filePath?: string
851
+ },
852
+ logger: InspectorLogger
847
853
  ) => {
848
- 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) {
849
856
  return true
850
857
  }
851
858
 
852
- 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
+ ) {
853
865
  return true
854
866
  }
855
867
 
856
- console.debug(`⒡ Filtered: ${meta.type}:${meta.name}`)
857
- 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
858
912
  }
package/src/visit.ts CHANGED
@@ -3,7 +3,11 @@ import { addFileWithFactory } from './add-file-with-factory.js'
3
3
  import { addFileExtendsCoreType } from './add-file-extends-core-type.js'
4
4
  import { addHTTPRoute } from './add-http-route.js'
5
5
  import { addSchedule } from './add-schedule.js'
6
- import { InspectorFilters, InspectorState } from './types.js'
6
+ import { addQueueWorker } from './add-queue-worker.js'
7
+ import { addMCPResource } from './add-mcp-resource.js'
8
+ import { addMCPTool } from './add-mcp-tool.js'
9
+ import { addMCPPrompt } from './add-mcp-prompt.js'
10
+ import { InspectorFilters, InspectorState, InspectorLogger } from './types.js'
7
11
  import { addFunctions } from './add-functions.js'
8
12
  import { addChannel } from './add-channel.js'
9
13
 
@@ -11,7 +15,8 @@ export const visitSetup = (
11
15
  checker: ts.TypeChecker,
12
16
  node: ts.Node,
13
17
  state: InspectorState,
14
- filters: InspectorFilters
18
+ filters: InspectorFilters,
19
+ logger: InspectorLogger
15
20
  ) => {
16
21
  addFileExtendsCoreType(
17
22
  node,
@@ -49,19 +54,28 @@ export const visitSetup = (
49
54
  )
50
55
 
51
56
  addFileWithFactory(node, checker, state.configFactories, 'CreateConfig')
52
- addFunctions(node, checker, state, filters)
57
+ addFunctions(node, checker, state, filters, logger)
53
58
 
54
- ts.forEachChild(node, (child) => visitSetup(checker, child, state, filters))
59
+ ts.forEachChild(node, (child) =>
60
+ visitSetup(checker, child, state, filters, logger)
61
+ )
55
62
  }
56
63
 
57
64
  export const visitRoutes = (
58
65
  checker: ts.TypeChecker,
59
66
  node: ts.Node,
60
67
  state: InspectorState,
61
- filters: InspectorFilters
68
+ filters: InspectorFilters,
69
+ logger: InspectorLogger
62
70
  ) => {
63
- addHTTPRoute(node, checker, state, filters)
64
- addSchedule(node, checker, state, filters)
65
- addChannel(node, checker, state, filters)
66
- 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
+ )
67
81
  }
package/tsconfig.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "extends": "../tsconfig.json",
3
3
  "compilerOptions": {
4
4
  "rootDir": "./src",
5
- "module": "Node16",
5
+ "module": "Node18",
6
6
  "outDir": "dist",
7
7
  "target": "esnext",
8
8
  "declaration": true,