@pikku/inspector 0.9.5 → 0.10.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.
Files changed (133) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/add/add-channel.d.ts +17 -0
  3. package/dist/{add-channel.js → add/add-channel.js} +60 -34
  4. package/dist/add/add-cli.d.ts +9 -0
  5. package/dist/add/add-cli.js +566 -0
  6. package/dist/{add-file-extends-core-type.d.ts → add/add-file-extends-core-type.d.ts} +2 -2
  7. package/dist/{add-file-extends-core-type.js → add/add-file-extends-core-type.js} +17 -4
  8. package/dist/{add-file-with-config.d.ts → add/add-file-with-config.d.ts} +1 -1
  9. package/dist/{add-file-with-config.js → add/add-file-with-config.js} +1 -1
  10. package/dist/{add-file-with-factory.d.ts → add/add-file-with-factory.d.ts} +2 -2
  11. package/dist/{add-file-with-factory.js → add/add-file-with-factory.js} +38 -5
  12. package/dist/add/add-functions.d.ts +6 -0
  13. package/dist/{add-functions.js → add/add-functions.js} +77 -10
  14. package/dist/{add-http-route.d.ts → add/add-http-route.d.ts} +2 -3
  15. package/dist/{add-http-route.js → add/add-http-route.js} +26 -13
  16. package/dist/add/add-mcp-prompt.d.ts +2 -0
  17. package/dist/add/add-mcp-prompt.js +74 -0
  18. package/dist/add/add-mcp-resource.d.ts +2 -0
  19. package/dist/add/add-mcp-resource.js +84 -0
  20. package/dist/add/add-mcp-tool.d.ts +2 -0
  21. package/dist/add/add-mcp-tool.js +80 -0
  22. package/dist/add/add-middleware.d.ts +5 -0
  23. package/dist/add/add-middleware.js +290 -0
  24. package/dist/add/add-permission.d.ts +5 -0
  25. package/dist/add/add-permission.js +292 -0
  26. package/dist/add/add-queue-worker.d.ts +2 -0
  27. package/dist/add/add-queue-worker.js +52 -0
  28. package/dist/{add-rpc-invocations.d.ts → add/add-rpc-invocations.d.ts} +1 -1
  29. package/dist/add/add-schedule.d.ts +2 -0
  30. package/dist/{add-schedule.js → add/add-schedule.js} +16 -11
  31. package/dist/error-codes.d.ts +35 -0
  32. package/dist/error-codes.js +40 -0
  33. package/dist/index.d.ts +6 -0
  34. package/dist/index.js +4 -0
  35. package/dist/inspector.d.ts +2 -3
  36. package/dist/inspector.js +38 -8
  37. package/dist/types.d.ts +108 -1
  38. package/dist/utils/ensure-function-metadata.d.ts +6 -0
  39. package/dist/utils/ensure-function-metadata.js +18 -0
  40. package/dist/utils/extract-function-name.d.ts +31 -0
  41. package/dist/{utils.js → utils/extract-function-name.js} +35 -149
  42. package/dist/utils/extract-services.d.ts +6 -0
  43. package/dist/utils/extract-services.js +29 -0
  44. package/dist/utils/filter-inspector-state.d.ts +6 -0
  45. package/dist/utils/filter-inspector-state.js +382 -0
  46. package/dist/utils/filter-utils.d.ts +19 -0
  47. package/dist/utils/filter-utils.js +109 -0
  48. package/dist/utils/find-root-dir.d.ts +23 -0
  49. package/dist/utils/find-root-dir.js +55 -0
  50. package/dist/utils/get-files-and-methods.d.ts +22 -0
  51. package/dist/utils/get-files-and-methods.js +61 -0
  52. package/dist/utils/get-property-value.d.ts +12 -0
  53. package/dist/{get-property-value.js → utils/get-property-value.js} +20 -0
  54. package/dist/utils/middleware.d.ts +39 -0
  55. package/dist/utils/middleware.js +157 -0
  56. package/dist/utils/permissions.d.ts +43 -0
  57. package/dist/utils/permissions.js +178 -0
  58. package/dist/utils/post-process.d.ts +16 -0
  59. package/dist/utils/post-process.js +132 -0
  60. package/dist/utils/serialize-inspector-state.d.ts +179 -0
  61. package/dist/utils/serialize-inspector-state.js +170 -0
  62. package/dist/utils/type-utils.d.ts +3 -0
  63. package/dist/utils/type-utils.js +50 -0
  64. package/dist/visit.d.ts +3 -3
  65. package/dist/visit.js +35 -31
  66. package/package.json +5 -6
  67. package/src/{add-channel.ts → add/add-channel.ts} +108 -56
  68. package/src/add/add-cli.ts +822 -0
  69. package/src/{add-file-extends-core-type.ts → add/add-file-extends-core-type.ts} +23 -5
  70. package/src/{add-file-with-config.ts → add/add-file-with-config.ts} +2 -2
  71. package/src/{add-file-with-factory.ts → add/add-file-with-factory.ts} +49 -6
  72. package/src/{add-functions.ts → add/add-functions.ts} +89 -19
  73. package/src/{add-http-route.ts → add/add-http-route.ts} +66 -32
  74. package/src/add/add-mcp-prompt.ts +128 -0
  75. package/src/add/add-mcp-prompt.ts.tmp +0 -0
  76. package/src/add/add-mcp-resource.ts +145 -0
  77. package/src/add/add-mcp-resource.ts.tmp +0 -0
  78. package/src/add/add-mcp-tool.ts +137 -0
  79. package/src/add/add-middleware.ts +385 -0
  80. package/src/add/add-permission.ts +391 -0
  81. package/src/add/add-queue-worker.ts +92 -0
  82. package/src/{add-rpc-invocations.ts → add/add-rpc-invocations.ts} +1 -1
  83. package/src/{add-schedule.ts → add/add-schedule.ts} +30 -28
  84. package/src/error-codes.ts +43 -0
  85. package/src/index.ts +12 -0
  86. package/src/inspector.ts +41 -17
  87. package/src/types.ts +128 -1
  88. package/src/utils/ensure-function-metadata.ts +24 -0
  89. package/src/{utils.ts → utils/extract-function-name.ts} +44 -206
  90. package/src/utils/extract-services.ts +35 -0
  91. package/src/utils/filter-inspector-state.test.ts +1433 -0
  92. package/src/utils/filter-inspector-state.ts +526 -0
  93. package/src/{utils.test.ts → utils/filter-utils.test.ts} +351 -2
  94. package/src/utils/filter-utils.ts +152 -0
  95. package/src/utils/find-root-dir.ts +68 -0
  96. package/src/utils/get-files-and-methods.ts +151 -0
  97. package/src/{get-property-value.ts → utils/get-property-value.ts} +27 -0
  98. package/src/utils/middleware.ts +241 -0
  99. package/src/utils/permissions.test.ts +327 -0
  100. package/src/utils/permissions.ts +262 -0
  101. package/src/utils/post-process.ts +178 -0
  102. package/src/utils/serialize-inspector-state.ts +375 -0
  103. package/src/utils/test-data/inspector-state.json +1680 -0
  104. package/src/utils/type-utils.ts +74 -0
  105. package/src/visit.ts +50 -34
  106. package/tsconfig.tsbuildinfo +1 -1
  107. package/dist/add-channel.d.ts +0 -13
  108. package/dist/add-functions.d.ts +0 -7
  109. package/dist/add-mcp-prompt.d.ts +0 -3
  110. package/dist/add-mcp-prompt.js +0 -61
  111. package/dist/add-mcp-resource.d.ts +0 -3
  112. package/dist/add-mcp-resource.js +0 -68
  113. package/dist/add-mcp-tool.d.ts +0 -3
  114. package/dist/add-mcp-tool.js +0 -64
  115. package/dist/add-middleware.d.ts +0 -7
  116. package/dist/add-middleware.js +0 -35
  117. package/dist/add-permission.d.ts +0 -7
  118. package/dist/add-permission.js +0 -35
  119. package/dist/add-queue-worker.d.ts +0 -3
  120. package/dist/add-queue-worker.js +0 -48
  121. package/dist/add-schedule.d.ts +0 -3
  122. package/dist/get-property-value.d.ts +0 -3
  123. package/dist/utils.d.ts +0 -39
  124. package/src/add-mcp-prompt.ts +0 -104
  125. package/src/add-mcp-resource.ts +0 -116
  126. package/src/add-mcp-tool.ts +0 -107
  127. package/src/add-middleware.ts +0 -51
  128. package/src/add-permission.ts +0 -53
  129. package/src/add-queue-worker.ts +0 -92
  130. /package/dist/{add-rpc-invocations.js → add/add-rpc-invocations.js} +0 -0
  131. /package/dist/{does-type-extend-core-type.d.ts → utils/does-type-extend-core-type.d.ts} +0 -0
  132. /package/dist/{does-type-extend-core-type.js → utils/does-type-extend-core-type.js} +0 -0
  133. /package/src/{does-type-extend-core-type.ts → utils/does-type-extend-core-type.ts} +0 -0
@@ -1,8 +1,8 @@
1
1
  import { test, describe } from 'node:test'
2
2
  import { strict as assert } from 'node:assert'
3
- import { matchesFilters } from './utils.js'
4
- import { InspectorFilters } from './types.js'
5
3
  import { PikkuWiringTypes } from '@pikku/core'
4
+ import { InspectorFilters } from '../types'
5
+ import { matchesFilters, matchesWildcard } from './filter-utils'
6
6
 
7
7
  describe('matchesFilters', () => {
8
8
  // Mock logger for testing
@@ -481,4 +481,353 @@ describe('matchesFilters', () => {
481
481
  assert.equal(result, true)
482
482
  })
483
483
  })
484
+
485
+ describe('Name filtering', () => {
486
+ test('should return true when name matches exactly', () => {
487
+ const filters: InspectorFilters = {
488
+ names: ['email-worker', 'notification-worker'],
489
+ }
490
+
491
+ const result = matchesFilters(
492
+ filters,
493
+ { name: 'email-worker' },
494
+ { type: PikkuWiringTypes.queue, name: 'email-queue' },
495
+ mockLogger
496
+ )
497
+
498
+ assert.equal(result, true)
499
+ })
500
+
501
+ test('should return true when name matches with wildcard', () => {
502
+ const filters: InspectorFilters = {
503
+ names: ['email-*'],
504
+ }
505
+
506
+ const result = matchesFilters(
507
+ filters,
508
+ { name: 'email-worker' },
509
+ { type: PikkuWiringTypes.queue, name: 'email-queue' },
510
+ mockLogger
511
+ )
512
+
513
+ assert.equal(result, true)
514
+ })
515
+
516
+ test('should return false when name does not match wildcard', () => {
517
+ const filters: InspectorFilters = {
518
+ names: ['email-*'],
519
+ }
520
+
521
+ const result = matchesFilters(
522
+ filters,
523
+ { name: 'notification-worker' },
524
+ { type: PikkuWiringTypes.queue, name: 'notification-queue' },
525
+ mockLogger
526
+ )
527
+
528
+ assert.equal(result, false)
529
+ })
530
+
531
+ test('should use meta.name when params.name is not provided', () => {
532
+ const filters: InspectorFilters = {
533
+ names: ['test-*'],
534
+ }
535
+
536
+ const result = matchesFilters(
537
+ filters,
538
+ {},
539
+ { type: PikkuWiringTypes.http, name: 'test-route' },
540
+ mockLogger
541
+ )
542
+
543
+ assert.equal(result, true)
544
+ })
545
+
546
+ test('should prefer params.name over meta.name', () => {
547
+ const filters: InspectorFilters = {
548
+ names: ['email-*'],
549
+ }
550
+
551
+ const result = matchesFilters(
552
+ filters,
553
+ { name: 'email-worker' },
554
+ { type: PikkuWiringTypes.queue, name: 'other-name' },
555
+ mockLogger
556
+ )
557
+
558
+ assert.equal(result, true)
559
+ })
560
+ })
561
+
562
+ describe('HTTP route filtering', () => {
563
+ test('should return true when httpRoute matches exactly', () => {
564
+ const filters: InspectorFilters = {
565
+ httpRoutes: ['/api/users', '/api/posts'],
566
+ }
567
+
568
+ const result = matchesFilters(
569
+ filters,
570
+ {},
571
+ {
572
+ type: PikkuWiringTypes.http,
573
+ name: 'users-route',
574
+ httpRoute: '/api/users',
575
+ },
576
+ mockLogger
577
+ )
578
+
579
+ assert.equal(result, true)
580
+ })
581
+
582
+ test('should return true when httpRoute matches with wildcard', () => {
583
+ const filters: InspectorFilters = {
584
+ httpRoutes: ['/api/*'],
585
+ }
586
+
587
+ const result = matchesFilters(
588
+ filters,
589
+ {},
590
+ {
591
+ type: PikkuWiringTypes.http,
592
+ name: 'users-route',
593
+ httpRoute: '/api/users',
594
+ },
595
+ mockLogger
596
+ )
597
+
598
+ assert.equal(result, true)
599
+ })
600
+
601
+ test('should return false when httpRoute does not match wildcard', () => {
602
+ const filters: InspectorFilters = {
603
+ httpRoutes: ['/api/*'],
604
+ }
605
+
606
+ const result = matchesFilters(
607
+ filters,
608
+ {},
609
+ {
610
+ type: PikkuWiringTypes.http,
611
+ name: 'webhook-route',
612
+ httpRoute: '/webhooks/stripe',
613
+ },
614
+ mockLogger
615
+ )
616
+
617
+ assert.equal(result, false)
618
+ })
619
+
620
+ test('should not filter when httpRoute is not provided in meta', () => {
621
+ const filters: InspectorFilters = {
622
+ httpRoutes: ['/api/*'],
623
+ }
624
+
625
+ const result = matchesFilters(
626
+ filters,
627
+ {},
628
+ { type: PikkuWiringTypes.queue, name: 'worker' },
629
+ mockLogger
630
+ )
631
+
632
+ assert.equal(result, true) // Does not apply to non-HTTP
633
+ })
634
+
635
+ test('should match multiple route patterns', () => {
636
+ const filters: InspectorFilters = {
637
+ httpRoutes: ['/api/*', '/webhooks/*'],
638
+ }
639
+
640
+ const result = matchesFilters(
641
+ filters,
642
+ {},
643
+ {
644
+ type: PikkuWiringTypes.http,
645
+ name: 'webhook-route',
646
+ httpRoute: '/webhooks/stripe',
647
+ },
648
+ mockLogger
649
+ )
650
+
651
+ assert.equal(result, true)
652
+ })
653
+ })
654
+
655
+ describe('HTTP method filtering', () => {
656
+ test('should return true when httpMethod matches', () => {
657
+ const filters: InspectorFilters = {
658
+ httpMethods: ['GET', 'POST'],
659
+ }
660
+
661
+ const result = matchesFilters(
662
+ filters,
663
+ {},
664
+ {
665
+ type: PikkuWiringTypes.http,
666
+ name: 'users-route',
667
+ httpMethod: 'GET',
668
+ },
669
+ mockLogger
670
+ )
671
+
672
+ assert.equal(result, true)
673
+ })
674
+
675
+ test('should return false when httpMethod does not match', () => {
676
+ const filters: InspectorFilters = {
677
+ httpMethods: ['GET', 'POST'],
678
+ }
679
+
680
+ const result = matchesFilters(
681
+ filters,
682
+ {},
683
+ {
684
+ type: PikkuWiringTypes.http,
685
+ name: 'users-route',
686
+ httpMethod: 'DELETE',
687
+ },
688
+ mockLogger
689
+ )
690
+
691
+ assert.equal(result, false)
692
+ })
693
+
694
+ test('should handle case-insensitive method matching', () => {
695
+ const filters: InspectorFilters = {
696
+ httpMethods: ['GET', 'POST'],
697
+ }
698
+
699
+ const result = matchesFilters(
700
+ filters,
701
+ {},
702
+ {
703
+ type: PikkuWiringTypes.http,
704
+ name: 'users-route',
705
+ httpMethod: 'get',
706
+ },
707
+ mockLogger
708
+ )
709
+
710
+ assert.equal(result, true)
711
+ })
712
+
713
+ test('should not filter when httpMethod is not provided in meta', () => {
714
+ const filters: InspectorFilters = {
715
+ httpMethods: ['GET'],
716
+ }
717
+
718
+ const result = matchesFilters(
719
+ filters,
720
+ {},
721
+ { type: PikkuWiringTypes.queue, name: 'worker' },
722
+ mockLogger
723
+ )
724
+
725
+ assert.equal(result, true) // Does not apply to non-HTTP
726
+ })
727
+ })
728
+
729
+ describe('Combined filtering with new filters', () => {
730
+ test('should return true when all filters including new ones pass', () => {
731
+ const filters: InspectorFilters = {
732
+ tags: ['api'],
733
+ types: ['http'],
734
+ httpRoutes: ['/api/*'],
735
+ httpMethods: ['GET'],
736
+ }
737
+
738
+ const result = matchesFilters(
739
+ filters,
740
+ { tags: ['api'] },
741
+ {
742
+ type: PikkuWiringTypes.http,
743
+ name: 'users-route',
744
+ httpRoute: '/api/users',
745
+ httpMethod: 'GET',
746
+ },
747
+ mockLogger
748
+ )
749
+
750
+ assert.equal(result, true)
751
+ })
752
+
753
+ test('should return false when httpRoute filter fails', () => {
754
+ const filters: InspectorFilters = {
755
+ tags: ['api'],
756
+ httpRoutes: ['/admin/*'],
757
+ }
758
+
759
+ const result = matchesFilters(
760
+ filters,
761
+ { tags: ['api'] },
762
+ {
763
+ type: PikkuWiringTypes.http,
764
+ name: 'users-route',
765
+ httpRoute: '/api/users',
766
+ },
767
+ mockLogger
768
+ )
769
+
770
+ assert.equal(result, false)
771
+ })
772
+
773
+ test('should return false when httpMethod filter fails', () => {
774
+ const filters: InspectorFilters = {
775
+ httpRoutes: ['/api/*'],
776
+ httpMethods: ['POST'],
777
+ }
778
+
779
+ const result = matchesFilters(
780
+ filters,
781
+ {},
782
+ {
783
+ type: PikkuWiringTypes.http,
784
+ name: 'users-route',
785
+ httpRoute: '/api/users',
786
+ httpMethod: 'GET',
787
+ },
788
+ mockLogger
789
+ )
790
+
791
+ assert.equal(result, false)
792
+ })
793
+ })
794
+ })
795
+
796
+ describe('matchesWildcard', () => {
797
+ test('should match exact strings', () => {
798
+ assert.equal(matchesWildcard('email-worker', 'email-worker'), true)
799
+ assert.equal(matchesWildcard('test', 'test'), true)
800
+ })
801
+
802
+ test('should not match different strings', () => {
803
+ assert.equal(matchesWildcard('email-worker', 'notification-worker'), false)
804
+ assert.equal(matchesWildcard('test', 'other'), false)
805
+ })
806
+
807
+ test('should match wildcard prefix', () => {
808
+ assert.equal(matchesWildcard('email-worker', 'email-*'), true)
809
+ assert.equal(matchesWildcard('email-sender', 'email-*'), true)
810
+ assert.equal(matchesWildcard('email', 'email-*'), false) // Needs prefix before *
811
+ })
812
+
813
+ test('should match wildcard for routes', () => {
814
+ assert.equal(matchesWildcard('/api/users', '/api/*'), true)
815
+ assert.equal(matchesWildcard('/api/posts', '/api/*'), true)
816
+ assert.equal(matchesWildcard('/webhooks/stripe', '/api/*'), false)
817
+ })
818
+
819
+ test('should handle empty prefix with wildcard', () => {
820
+ assert.equal(matchesWildcard('anything', '*'), true)
821
+ assert.equal(matchesWildcard('', '*'), true)
822
+ })
823
+
824
+ test('should handle exact match when no wildcard', () => {
825
+ assert.equal(matchesWildcard('test', 'test'), true)
826
+ assert.equal(matchesWildcard('test', 'test*'), false) // Has suffix after exact match
827
+ })
828
+
829
+ test('should handle special characters in prefix', () => {
830
+ assert.equal(matchesWildcard('api-v2-users', 'api-v2-*'), true)
831
+ assert.equal(matchesWildcard('api.v2.users', 'api.v2.*'), true)
832
+ })
484
833
  })
@@ -0,0 +1,152 @@
1
+ import { InspectorFilters, InspectorLogger } from '../types.js'
2
+ import { PikkuWiringTypes } from '@pikku/core'
3
+
4
+ /**
5
+ * Match a value against a pattern with wildcard support
6
+ * Supports "*" at the beginning, end, or both (e.g., "send*", "*Payment", "*process*")
7
+ * @param value - The value to check
8
+ * @param pattern - The pattern with optional "*" wildcard(s)
9
+ */
10
+ export function matchesWildcard(value: string, pattern: string): boolean {
11
+ // If pattern is just '*', match everything
12
+ if (pattern === '*') {
13
+ return true
14
+ }
15
+
16
+ const startsWithWildcard = pattern.startsWith('*')
17
+ const endsWithWildcard = pattern.endsWith('*')
18
+
19
+ if (startsWithWildcard && endsWithWildcard) {
20
+ // Pattern like "*middle*" - check if value contains the middle part
21
+ const middle = pattern.slice(1, -1)
22
+ if (middle === '') {
23
+ return true // Pattern is "**", match everything
24
+ }
25
+ return value.includes(middle)
26
+ } else if (startsWithWildcard) {
27
+ // Pattern like "*suffix" - check if value ends with suffix and has content before
28
+ const suffix = pattern.slice(1)
29
+ return value.endsWith(suffix) && value.length > suffix.length
30
+ } else if (endsWithWildcard) {
31
+ // Pattern like "prefix*" - check if value starts with prefix and has content after
32
+ const prefix = pattern.slice(0, -1)
33
+ return value.startsWith(prefix) && value.length > prefix.length
34
+ }
35
+
36
+ // No wildcard, exact match
37
+ return value === pattern
38
+ }
39
+
40
+ export const matchesFilters = (
41
+ filters: InspectorFilters,
42
+ params: {
43
+ tags?: string[]
44
+ name?: string // Wire/function name for name filter
45
+ },
46
+ meta: {
47
+ type: PikkuWiringTypes
48
+ name: string
49
+ filePath?: string
50
+ httpRoute?: string // For HTTP route filtering
51
+ httpMethod?: string // For HTTP method filtering
52
+ },
53
+ logger: InspectorLogger
54
+ ) => {
55
+ // If no filters are provided, allow everything
56
+ if (Object.keys(filters).length === 0) {
57
+ return true
58
+ }
59
+
60
+ // If all filter arrays are empty, allow everything
61
+ if (
62
+ (!filters.names || filters.names.length === 0) &&
63
+ (!filters.tags || filters.tags.length === 0) &&
64
+ (!filters.types || filters.types.length === 0) &&
65
+ (!filters.directories || filters.directories.length === 0) &&
66
+ (!filters.httpRoutes || filters.httpRoutes.length === 0) &&
67
+ (!filters.httpMethods || filters.httpMethods.length === 0)
68
+ ) {
69
+ return true
70
+ }
71
+
72
+ // Check type filter
73
+ if (filters.types && filters.types.length > 0) {
74
+ if (!filters.types.includes(meta.type)) {
75
+ logger.debug(`⒡ Filtered by type: ${meta.type}:${meta.name}`)
76
+ return false
77
+ }
78
+ }
79
+
80
+ // Check directory filter
81
+ if (filters.directories && filters.directories.length > 0) {
82
+ if (!meta.filePath) {
83
+ logger.debug(
84
+ `⒡ Filtered by directory: ${meta.type}:${meta.name} (${meta.filePath})`
85
+ )
86
+ return false
87
+ }
88
+
89
+ const matchesDirectory = filters.directories.some((dir) => {
90
+ // Normalize paths for comparison
91
+ const normalizedFilePath = meta.filePath!.replace(/\\/g, '/')
92
+ const normalizedDir = dir.replace(/\\/g, '/')
93
+ return normalizedFilePath.includes(normalizedDir)
94
+ })
95
+
96
+ if (!matchesDirectory) {
97
+ logger.debug(
98
+ `⒡ Filtered by directory: ${meta.type}:${meta.name} (${meta.filePath})`
99
+ )
100
+ return false
101
+ }
102
+ }
103
+
104
+ // Check tag filter
105
+ if (filters.tags && filters.tags.length > 0) {
106
+ if (
107
+ !params.tags ||
108
+ !filters.tags.some((tag) => params.tags!.includes(tag))
109
+ ) {
110
+ logger.debug(`⒡ Filtered by tags: ${meta.type}:${meta.name}`)
111
+ return false
112
+ }
113
+ }
114
+
115
+ // Check name filter (with wildcard support)
116
+ if (filters.names && filters.names.length > 0) {
117
+ const nameToMatch = params.name || meta.name
118
+ const nameMatches = filters.names.some((pattern) =>
119
+ matchesWildcard(nameToMatch, pattern)
120
+ )
121
+ if (!nameMatches) {
122
+ logger.debug(`⒡ Filtered by name: ${meta.type}:${meta.name}`)
123
+ return false
124
+ }
125
+ }
126
+
127
+ // Check HTTP route filter (with wildcard support)
128
+ if (filters.httpRoutes && filters.httpRoutes.length > 0 && meta.httpRoute) {
129
+ const routeMatches = filters.httpRoutes.some((pattern) =>
130
+ matchesWildcard(meta.httpRoute!, pattern)
131
+ )
132
+ if (!routeMatches) {
133
+ logger.debug(`⒡ Filtered by HTTP route: ${meta.httpRoute}`)
134
+ return false
135
+ }
136
+ }
137
+
138
+ // Check HTTP method filter
139
+ if (
140
+ filters.httpMethods &&
141
+ filters.httpMethods.length > 0 &&
142
+ meta.httpMethod
143
+ ) {
144
+ const normalizedMethod = meta.httpMethod.toUpperCase()
145
+ if (!filters.httpMethods.includes(normalizedMethod)) {
146
+ logger.debug(`⒡ Filtered by HTTP method: ${meta.httpMethod}`)
147
+ return false
148
+ }
149
+ }
150
+
151
+ return true
152
+ }
@@ -0,0 +1,68 @@
1
+ import path from 'path'
2
+
3
+ /**
4
+ * Finds the common ancestor directory of all the given file paths.
5
+ * This is used to determine the project root directory.
6
+ *
7
+ * @param filePaths - Array of absolute file paths
8
+ * @returns The common ancestor directory path
9
+ *
10
+ * @example
11
+ * findCommonAncestor([
12
+ * '/Users/yasser/git/pikku/pikku/src/functions/a.ts',
13
+ * '/Users/yasser/git/pikku/pikku/src/routes/b.ts'
14
+ * ])
15
+ * // Returns: '/Users/yasser/git/pikku/pikku'
16
+ */
17
+ export function findCommonAncestor(filePaths: string[]): string {
18
+ if (filePaths.length === 0) {
19
+ return process.cwd()
20
+ }
21
+
22
+ if (filePaths.length === 1) {
23
+ return path.dirname(filePaths[0]!)
24
+ }
25
+
26
+ // Normalize all paths and get their directory parts
27
+ const normalizedPaths = filePaths.map((p) =>
28
+ path.dirname(path.normalize(p)).split(path.sep)
29
+ )
30
+
31
+ // Start with the first path's parts
32
+ const firstPath = normalizedPaths[0]!
33
+ let commonParts: string[] = []
34
+
35
+ // Check each part of the first path
36
+ for (let i = 0; i < firstPath.length; i++) {
37
+ const part = firstPath[i]!
38
+
39
+ // Check if this part exists in all other paths at the same position
40
+ const existsInAll = normalizedPaths.every(
41
+ (pathParts) => pathParts[i] === part
42
+ )
43
+
44
+ if (existsInAll) {
45
+ commonParts.push(part)
46
+ } else {
47
+ break
48
+ }
49
+ }
50
+
51
+ // If no common parts, return root
52
+ if (commonParts.length === 0) {
53
+ return path.sep
54
+ }
55
+
56
+ return commonParts.join(path.sep)
57
+ }
58
+
59
+ /**
60
+ * Converts an absolute file path to a relative path from the root directory.
61
+ *
62
+ * @param absolutePath - The absolute file path
63
+ * @param rootDir - The root directory to make the path relative to
64
+ * @returns A relative path from rootDir to the file
65
+ */
66
+ export function toRelativePath(absolutePath: string, rootDir: string): string {
67
+ return path.relative(rootDir, absolutePath)
68
+ }