@pikku/inspector 0.9.6-next.0 → 0.10.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/CHANGELOG.md +14 -0
- package/dist/add/add-channel.d.ts +5 -1
- package/dist/add/add-channel.js +51 -32
- package/dist/add/add-cli.d.ts +4 -0
- package/dist/add/add-cli.js +128 -23
- package/dist/add/add-file-extends-core-type.js +3 -2
- package/dist/add/add-file-with-factory.d.ts +2 -2
- package/dist/add/add-file-with-factory.js +87 -1
- package/dist/add/add-functions.js +52 -5
- package/dist/add/add-http-route.js +19 -12
- package/dist/add/add-mcp-prompt.js +20 -13
- package/dist/add/add-mcp-resource.js +24 -14
- package/dist/add/add-mcp-tool.js +23 -13
- package/dist/add/add-middleware.js +51 -12
- package/dist/add/add-permission.d.ts +1 -2
- package/dist/add/add-permission.js +275 -19
- package/dist/add/add-queue-worker.js +10 -12
- package/dist/add/add-schedule.js +9 -10
- package/dist/error-codes.d.ts +35 -0
- package/dist/error-codes.js +40 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/inspector.js +20 -1
- package/dist/types.d.ts +31 -3
- package/dist/utils/ensure-function-metadata.d.ts +6 -0
- package/dist/utils/ensure-function-metadata.js +18 -0
- package/dist/utils/extract-function-name.d.ts +2 -2
- package/dist/utils/extract-function-name.js +13 -8
- package/dist/utils/filter-inspector-state.d.ts +6 -0
- package/dist/utils/filter-inspector-state.js +382 -0
- package/dist/utils/filter-utils.d.ts +10 -0
- package/dist/utils/filter-utils.js +66 -2
- package/dist/utils/find-root-dir.d.ts +23 -0
- package/dist/utils/find-root-dir.js +55 -0
- package/dist/utils/get-files-and-methods.d.ts +2 -1
- package/dist/utils/get-files-and-methods.js +4 -3
- package/dist/utils/get-property-value.d.ts +9 -0
- package/dist/utils/get-property-value.js +20 -0
- package/dist/utils/middleware.d.ts +1 -1
- package/dist/utils/middleware.js +7 -7
- package/dist/utils/permissions.d.ts +43 -0
- package/dist/utils/permissions.js +178 -0
- package/dist/utils/post-process.d.ts +16 -0
- package/dist/utils/post-process.js +132 -0
- package/dist/utils/serialize-inspector-state.d.ts +179 -0
- package/dist/utils/serialize-inspector-state.js +170 -0
- package/dist/visit.js +3 -2
- package/package.json +4 -4
- package/src/add/add-channel.ts +92 -40
- package/src/add/add-cli.ts +188 -29
- package/src/add/add-file-extends-core-type.ts +5 -2
- package/src/add/add-file-with-factory.ts +114 -2
- package/src/add/add-functions.ts +60 -5
- package/src/add/add-http-route.ts +46 -21
- package/src/add/add-mcp-prompt.ts +42 -21
- package/src/add/add-mcp-prompt.ts.tmp +0 -0
- package/src/add/add-mcp-resource.ts +50 -24
- package/src/add/add-mcp-resource.ts.tmp +0 -0
- package/src/add/add-mcp-tool.ts +48 -21
- package/src/add/add-middleware.ts +74 -15
- package/src/add/add-permission.ts +364 -22
- package/src/add/add-queue-worker.ts +22 -25
- package/src/add/add-schedule.ts +19 -20
- package/src/error-codes.ts +43 -0
- package/src/index.ts +7 -0
- package/src/inspector.ts +22 -1
- package/src/types.ts +38 -3
- package/src/utils/ensure-function-metadata.ts +24 -0
- package/src/utils/extract-function-name.ts +20 -8
- package/src/utils/filter-inspector-state.test.ts +1433 -0
- package/src/utils/filter-inspector-state.ts +526 -0
- package/src/utils/filter-utils.test.ts +350 -1
- package/src/utils/filter-utils.ts +82 -2
- package/src/utils/find-root-dir.ts +68 -0
- package/src/utils/get-files-and-methods.ts +10 -2
- package/src/utils/get-property-value.ts +27 -0
- package/src/utils/middleware.ts +14 -7
- package/src/utils/permissions.test.ts +327 -0
- package/src/utils/permissions.ts +262 -0
- package/src/utils/post-process.ts +178 -0
- package/src/utils/serialize-inspector-state.ts +375 -0
- package/src/utils/test-data/inspector-state.json +1680 -0
- package/src/visit.ts +4 -2
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -2,7 +2,7 @@ import { test, describe } from 'node:test'
|
|
|
2
2
|
import { strict as assert } from 'node:assert'
|
|
3
3
|
import { PikkuWiringTypes } from '@pikku/core'
|
|
4
4
|
import { InspectorFilters } from '../types'
|
|
5
|
-
import { matchesFilters } from './filter-utils'
|
|
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
|
})
|
|
@@ -1,13 +1,54 @@
|
|
|
1
1
|
import { InspectorFilters, InspectorLogger } from '../types.js'
|
|
2
2
|
import { PikkuWiringTypes } from '@pikku/core'
|
|
3
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
|
+
|
|
4
40
|
export const matchesFilters = (
|
|
5
41
|
filters: InspectorFilters,
|
|
6
|
-
params: {
|
|
42
|
+
params: {
|
|
43
|
+
tags?: string[]
|
|
44
|
+
name?: string // Wire/function name for name filter
|
|
45
|
+
},
|
|
7
46
|
meta: {
|
|
8
47
|
type: PikkuWiringTypes
|
|
9
48
|
name: string
|
|
10
49
|
filePath?: string
|
|
50
|
+
httpRoute?: string // For HTTP route filtering
|
|
51
|
+
httpMethod?: string // For HTTP method filtering
|
|
11
52
|
},
|
|
12
53
|
logger: InspectorLogger
|
|
13
54
|
) => {
|
|
@@ -18,9 +59,12 @@ export const matchesFilters = (
|
|
|
18
59
|
|
|
19
60
|
// If all filter arrays are empty, allow everything
|
|
20
61
|
if (
|
|
62
|
+
(!filters.names || filters.names.length === 0) &&
|
|
21
63
|
(!filters.tags || filters.tags.length === 0) &&
|
|
22
64
|
(!filters.types || filters.types.length === 0) &&
|
|
23
|
-
(!filters.directories || filters.directories.length === 0)
|
|
65
|
+
(!filters.directories || filters.directories.length === 0) &&
|
|
66
|
+
(!filters.httpRoutes || filters.httpRoutes.length === 0) &&
|
|
67
|
+
(!filters.httpMethods || filters.httpMethods.length === 0)
|
|
24
68
|
) {
|
|
25
69
|
return true
|
|
26
70
|
}
|
|
@@ -68,5 +112,41 @@ export const matchesFilters = (
|
|
|
68
112
|
}
|
|
69
113
|
}
|
|
70
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
|
+
|
|
71
151
|
return true
|
|
72
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
|
+
}
|
|
@@ -15,6 +15,7 @@ export type FilesAndMethods = {
|
|
|
15
15
|
userSessionType: Meta
|
|
16
16
|
sessionServicesType: Meta
|
|
17
17
|
singletonServicesType: Meta
|
|
18
|
+
pikkuConfigType: Meta
|
|
18
19
|
pikkuConfigFactory: Meta
|
|
19
20
|
singletonServicesFactory: Meta
|
|
20
21
|
sessionServicesFactory: Meta
|
|
@@ -53,9 +54,9 @@ const getMetaTypes = (
|
|
|
53
54
|
const helpMessage =
|
|
54
55
|
type === 'CoreConfig'
|
|
55
56
|
? `No ${type} found. Make sure you have exported a createConfig function in your codebase:\n\n` +
|
|
56
|
-
`export const createConfig
|
|
57
|
+
`export const createConfig = pikkuConfig(async () => {\n` +
|
|
57
58
|
` return {}\n` +
|
|
58
|
-
`}\n\n` +
|
|
59
|
+
`})\n\n` +
|
|
59
60
|
`Possible issues:\n` +
|
|
60
61
|
`- srcDirectories in pikku.config.json doesn't include the file with the createConfig method`
|
|
61
62
|
: `No ${type} found`
|
|
@@ -87,6 +88,7 @@ export const getFilesAndMethods = (
|
|
|
87
88
|
singletonServicesTypeImportMap,
|
|
88
89
|
sessionServicesTypeImportMap,
|
|
89
90
|
userSessionTypeImportMap,
|
|
91
|
+
configTypeImportMap,
|
|
90
92
|
sessionServicesFactories,
|
|
91
93
|
singletonServicesFactories,
|
|
92
94
|
configFactories,
|
|
@@ -119,6 +121,12 @@ export const getFilesAndMethods = (
|
|
|
119
121
|
undefined,
|
|
120
122
|
errors
|
|
121
123
|
),
|
|
124
|
+
pikkuConfigType: getMetaTypes(
|
|
125
|
+
'CoreConfig',
|
|
126
|
+
configTypeImportMap,
|
|
127
|
+
undefined,
|
|
128
|
+
errors
|
|
129
|
+
),
|
|
122
130
|
pikkuConfigFactory: getMetaTypes(
|
|
123
131
|
'CoreConfig',
|
|
124
132
|
configFactories,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { PikkuDocs } from '@pikku/core'
|
|
2
2
|
import * as ts from 'typescript'
|
|
3
|
+
import { ErrorCode } from '../error-codes.js'
|
|
3
4
|
|
|
4
5
|
export const getPropertyValue = (
|
|
5
6
|
obj: ts.ObjectLiteralExpression,
|
|
@@ -91,3 +92,29 @@ export const getPropertyValue = (
|
|
|
91
92
|
|
|
92
93
|
return null
|
|
93
94
|
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Gets the 'tags' property from an object and validates it's an array.
|
|
98
|
+
* Logs a critical error if tags is not an array but still returns the value.
|
|
99
|
+
* @param logger - Optional logger instance; if not provided, uses console.error
|
|
100
|
+
*/
|
|
101
|
+
export const getPropertyTags = (
|
|
102
|
+
obj: ts.ObjectLiteralExpression,
|
|
103
|
+
wiringType: string,
|
|
104
|
+
wiringName: string | null,
|
|
105
|
+
logger?: { critical: (code: ErrorCode, message: string) => void }
|
|
106
|
+
): string[] | undefined => {
|
|
107
|
+
const tagsValue = getPropertyValue(obj, 'tags')
|
|
108
|
+
|
|
109
|
+
if (tagsValue !== null && !Array.isArray(tagsValue)) {
|
|
110
|
+
const errorMsg = `${wiringType} '${wiringName}' has invalid 'tags' property - must be an array of strings.`
|
|
111
|
+
if (logger) {
|
|
112
|
+
logger.critical(ErrorCode.INVALID_TAGS_TYPE, errorMsg)
|
|
113
|
+
} else {
|
|
114
|
+
console.error(errorMsg)
|
|
115
|
+
}
|
|
116
|
+
// Return undefined but don't stop processing - error will be caught by the exit handler
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return Array.isArray(tagsValue) ? tagsValue : undefined
|
|
120
|
+
}
|
package/src/utils/middleware.ts
CHANGED
|
@@ -10,7 +10,8 @@ import { InspectorState } from '../types.js'
|
|
|
10
10
|
*/
|
|
11
11
|
export function extractMiddlewarePikkuNames(
|
|
12
12
|
arrayNode: ts.Expression,
|
|
13
|
-
checker: ts.TypeChecker
|
|
13
|
+
checker: ts.TypeChecker,
|
|
14
|
+
rootDir: string
|
|
14
15
|
): string[] {
|
|
15
16
|
if (!ts.isArrayLiteralExpression(arrayNode)) {
|
|
16
17
|
return []
|
|
@@ -20,12 +21,16 @@ export function extractMiddlewarePikkuNames(
|
|
|
20
21
|
for (const element of arrayNode.elements) {
|
|
21
22
|
if (ts.isIdentifier(element)) {
|
|
22
23
|
// Resolve the identifier to its pikkuFuncName
|
|
23
|
-
const { pikkuFuncName } = extractFunctionName(element, checker)
|
|
24
|
+
const { pikkuFuncName } = extractFunctionName(element, checker, rootDir)
|
|
24
25
|
names.push(pikkuFuncName)
|
|
25
26
|
} else if (ts.isCallExpression(element)) {
|
|
26
|
-
// Handle call expressions like logCommandInfoAndTime({...})
|
|
27
|
-
//
|
|
28
|
-
const { pikkuFuncName } = extractFunctionName(
|
|
27
|
+
// Handle call expressions like rateLimiter(10) or logCommandInfoAndTime({...})
|
|
28
|
+
// Extract the function being called (e.g., 'rateLimiter' from 'rateLimiter(10)')
|
|
29
|
+
const { pikkuFuncName } = extractFunctionName(
|
|
30
|
+
element.expression,
|
|
31
|
+
checker,
|
|
32
|
+
rootDir
|
|
33
|
+
)
|
|
29
34
|
names.push(pikkuFuncName)
|
|
30
35
|
}
|
|
31
36
|
}
|
|
@@ -111,7 +116,8 @@ export function resolveHTTPMiddleware(
|
|
|
111
116
|
if (explicitMiddlewareNode) {
|
|
112
117
|
const middlewareNames = extractMiddlewarePikkuNames(
|
|
113
118
|
explicitMiddlewareNode,
|
|
114
|
-
checker
|
|
119
|
+
checker,
|
|
120
|
+
state.rootDir
|
|
115
121
|
)
|
|
116
122
|
for (const name of middlewareNames) {
|
|
117
123
|
const meta = state.middleware.meta[name]
|
|
@@ -156,7 +162,8 @@ function resolveTagAndExplicitMiddleware(
|
|
|
156
162
|
if (explicitMiddlewareNode) {
|
|
157
163
|
const middlewareNames = extractMiddlewarePikkuNames(
|
|
158
164
|
explicitMiddlewareNode,
|
|
159
|
-
checker
|
|
165
|
+
checker,
|
|
166
|
+
state.rootDir
|
|
160
167
|
)
|
|
161
168
|
for (const name of middlewareNames) {
|
|
162
169
|
const meta = state.middleware.meta[name]
|