@pikku/inspector 0.9.6-next.0 → 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.
- package/CHANGELOG.md +6 -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 +34 -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 +2 -1
- 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 +45 -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 +8 -0
- 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
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { InspectorState } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Ensures that function metadata exists for a given pikkuFuncName.
|
|
4
|
+
* Creates stub metadata if it doesn't exist (useful for inline functions).
|
|
5
|
+
*/
|
|
6
|
+
export declare function ensureFunctionMetadata(state: InspectorState, pikkuFuncName: string, fallbackName?: string): void;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ensures that function metadata exists for a given pikkuFuncName.
|
|
3
|
+
* Creates stub metadata if it doesn't exist (useful for inline functions).
|
|
4
|
+
*/
|
|
5
|
+
export function ensureFunctionMetadata(state, pikkuFuncName, fallbackName) {
|
|
6
|
+
if (!state.functions.meta[pikkuFuncName]) {
|
|
7
|
+
state.functions.meta[pikkuFuncName] = {
|
|
8
|
+
pikkuFuncName,
|
|
9
|
+
name: fallbackName || pikkuFuncName,
|
|
10
|
+
services: { optimized: false, services: [] },
|
|
11
|
+
inputSchemaName: null,
|
|
12
|
+
outputSchemaName: null,
|
|
13
|
+
inputs: [],
|
|
14
|
+
outputs: [],
|
|
15
|
+
middleware: undefined,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -12,7 +12,7 @@ export type ExtractedFunctionName = {
|
|
|
12
12
|
* but if it's an Identifier pointing to a function, resolve it back
|
|
13
13
|
* to the function's declaration (so you get the true source location).
|
|
14
14
|
*/
|
|
15
|
-
export declare function makeDeterministicAnonName(start: ts.Node, checker: ts.TypeChecker): string;
|
|
15
|
+
export declare function makeDeterministicAnonName(start: ts.Node, checker: ts.TypeChecker, rootDir: string): string;
|
|
16
16
|
/**
|
|
17
17
|
* Updated function to extract and prioritize function names correctly
|
|
18
18
|
* This function follows the priority:
|
|
@@ -20,7 +20,7 @@ export declare function makeDeterministicAnonName(start: ts.Node, checker: ts.Ty
|
|
|
20
20
|
* 2. Exported name
|
|
21
21
|
* 3. Fallback to deterministic name
|
|
22
22
|
*/
|
|
23
|
-
export declare function extractFunctionName(callExpr: ts.Node, checker: ts.TypeChecker): ExtractedFunctionName;
|
|
23
|
+
export declare function extractFunctionName(callExpr: ts.Node, checker: ts.TypeChecker, rootDir: string): ExtractedFunctionName;
|
|
24
24
|
/**
|
|
25
25
|
* Helper function to populate the 'name' field based on priority
|
|
26
26
|
*/
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
|
+
import { toRelativePath } from './find-root-dir.js';
|
|
2
3
|
/**
|
|
3
4
|
* Generate a deterministic "anonymous" name for any expression node,
|
|
4
5
|
* but if it's an Identifier pointing to a function, resolve it back
|
|
5
6
|
* to the function's declaration (so you get the true source location).
|
|
6
7
|
*/
|
|
7
|
-
export function makeDeterministicAnonName(start, checker) {
|
|
8
|
+
export function makeDeterministicAnonName(start, checker, rootDir) {
|
|
8
9
|
let node = start;
|
|
9
10
|
let target = start;
|
|
10
11
|
// Handle the case where we're starting with an identifier directly
|
|
@@ -27,7 +28,8 @@ export function makeDeterministicAnonName(start, checker) {
|
|
|
27
28
|
target = decl.initializer;
|
|
28
29
|
// Return early - we found the function directly
|
|
29
30
|
const sf = target.getSourceFile();
|
|
30
|
-
const
|
|
31
|
+
const relativePath = toRelativePath(sf.fileName, rootDir);
|
|
32
|
+
const file = relativePath.replace(/[^A-Za-z0-9_]/g, '_');
|
|
31
33
|
const { line, character } = ts.getLineAndCharacterOfPosition(sf, target.getStart());
|
|
32
34
|
return `pikkuFn_${file}_L${line + 1}C${character + 1}`;
|
|
33
35
|
}
|
|
@@ -62,7 +64,8 @@ export function makeDeterministicAnonName(start, checker) {
|
|
|
62
64
|
target = decl.initializer;
|
|
63
65
|
// Return early - we found the function directly
|
|
64
66
|
const sf = target.getSourceFile();
|
|
65
|
-
const
|
|
67
|
+
const relativePath = toRelativePath(sf.fileName, rootDir);
|
|
68
|
+
const file = relativePath.replace(/[^A-Za-z0-9_]/g, '_');
|
|
66
69
|
const { line, character } = ts.getLineAndCharacterOfPosition(sf, target.getStart());
|
|
67
70
|
return `pikkuFn_${file}_L${line + 1}C${character + 1}`;
|
|
68
71
|
}
|
|
@@ -72,7 +75,8 @@ export function makeDeterministicAnonName(start, checker) {
|
|
|
72
75
|
target = decl;
|
|
73
76
|
// Return early
|
|
74
77
|
const sf = target.getSourceFile();
|
|
75
|
-
const
|
|
78
|
+
const relativePath = toRelativePath(sf.fileName, rootDir);
|
|
79
|
+
const file = relativePath.replace(/[^A-Za-z0-9_]/g, '_');
|
|
76
80
|
const { line, character } = ts.getLineAndCharacterOfPosition(sf, target.getStart());
|
|
77
81
|
return `pikkuFn_${file}_L${line + 1}C${character + 1}`;
|
|
78
82
|
}
|
|
@@ -182,7 +186,8 @@ export function makeDeterministicAnonName(start, checker) {
|
|
|
182
186
|
break;
|
|
183
187
|
}
|
|
184
188
|
const sf = target.getSourceFile();
|
|
185
|
-
const
|
|
189
|
+
const relativePath = toRelativePath(sf.fileName, rootDir);
|
|
190
|
+
const file = relativePath.replace(/[^A-Za-z0-9_]/g, '_');
|
|
186
191
|
const { line, character } = ts.getLineAndCharacterOfPosition(sf, target.getStart());
|
|
187
192
|
return `pikkuFn_${file}_L${line + 1}C${character + 1}`;
|
|
188
193
|
}
|
|
@@ -193,7 +198,7 @@ export function makeDeterministicAnonName(start, checker) {
|
|
|
193
198
|
* 2. Exported name
|
|
194
199
|
* 3. Fallback to deterministic name
|
|
195
200
|
*/
|
|
196
|
-
export function extractFunctionName(callExpr, checker) {
|
|
201
|
+
export function extractFunctionName(callExpr, checker, rootDir) {
|
|
197
202
|
const parent = callExpr.parent;
|
|
198
203
|
// Initialize the result
|
|
199
204
|
const result = {
|
|
@@ -230,7 +235,7 @@ export function extractFunctionName(callExpr, checker) {
|
|
|
230
235
|
(ts.isArrowFunction(firstArg) ||
|
|
231
236
|
ts.isFunctionExpression(firstArg))) {
|
|
232
237
|
// Use the function directly for position calculation
|
|
233
|
-
result.pikkuFuncName = makeDeterministicAnonName(firstArg, checker);
|
|
238
|
+
result.pikkuFuncName = makeDeterministicAnonName(firstArg, checker, rootDir);
|
|
234
239
|
// Continue with name extraction
|
|
235
240
|
if (ts.isIdentifier(parent.name)) {
|
|
236
241
|
result.propertyName = parent.name.text;
|
|
@@ -532,7 +537,7 @@ export function extractFunctionName(callExpr, checker) {
|
|
|
532
537
|
// Generate the deterministic function name based on the original call expression
|
|
533
538
|
// (the config), not the resolved inner function. This ensures the metadata key
|
|
534
539
|
// matches what will be looked up at runtime when referencing the config object.
|
|
535
|
-
result.pikkuFuncName = makeDeterministicAnonName(originalCallExpr, checker);
|
|
540
|
+
result.pikkuFuncName = makeDeterministicAnonName(originalCallExpr, checker, rootDir);
|
|
536
541
|
// Continue with regular name extraction for remaining cases
|
|
537
542
|
// 1) const foo = pikkuFunc(...)
|
|
538
543
|
if (ts.isVariableDeclaration(parent) && ts.isIdentifier(parent.name)) {
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { InspectorState, InspectorFilters, InspectorLogger } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Filters inspector state based on provided filters
|
|
4
|
+
* This is applied post-inspection to support the inspect-once, filter-many pattern
|
|
5
|
+
*/
|
|
6
|
+
export declare function filterInspectorState(state: InspectorState | Omit<InspectorState, 'typesLookup'>, filters: InspectorFilters, logger: InspectorLogger): typeof state;
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
import { aggregateRequiredServices } from './post-process.js';
|
|
2
|
+
/**
|
|
3
|
+
* Match a value against a pattern with wildcard support
|
|
4
|
+
* Supports "*" at the beginning, end, or both (e.g., "send*", "*Payment", "*process*")
|
|
5
|
+
*/
|
|
6
|
+
function matchesWildcard(value, pattern) {
|
|
7
|
+
if (pattern === '*')
|
|
8
|
+
return true;
|
|
9
|
+
const startsWithWildcard = pattern.startsWith('*');
|
|
10
|
+
const endsWithWildcard = pattern.endsWith('*');
|
|
11
|
+
if (startsWithWildcard && endsWithWildcard) {
|
|
12
|
+
const middle = pattern.slice(1, -1);
|
|
13
|
+
if (middle === '')
|
|
14
|
+
return true;
|
|
15
|
+
return value.includes(middle);
|
|
16
|
+
}
|
|
17
|
+
else if (startsWithWildcard) {
|
|
18
|
+
const suffix = pattern.slice(1);
|
|
19
|
+
return value.endsWith(suffix) && value.length > suffix.length;
|
|
20
|
+
}
|
|
21
|
+
else if (endsWithWildcard) {
|
|
22
|
+
const prefix = pattern.slice(0, -1);
|
|
23
|
+
return value.startsWith(prefix) && value.length > prefix.length;
|
|
24
|
+
}
|
|
25
|
+
return value === pattern;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Check if metadata matches the given filters
|
|
29
|
+
*/
|
|
30
|
+
function matchesFilters(filters, meta, logger) {
|
|
31
|
+
// If no filters, allow everything
|
|
32
|
+
if (Object.keys(filters).length === 0)
|
|
33
|
+
return true;
|
|
34
|
+
// If all filter arrays are empty, allow everything
|
|
35
|
+
if ((!filters.names || filters.names.length === 0) &&
|
|
36
|
+
(!filters.tags || filters.tags.length === 0) &&
|
|
37
|
+
(!filters.types || filters.types.length === 0) &&
|
|
38
|
+
(!filters.directories || filters.directories.length === 0) &&
|
|
39
|
+
(!filters.httpRoutes || filters.httpRoutes.length === 0) &&
|
|
40
|
+
(!filters.httpMethods || filters.httpMethods.length === 0)) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
// Check type filter
|
|
44
|
+
if (filters.types && filters.types.length > 0) {
|
|
45
|
+
if (!filters.types.includes(meta.type)) {
|
|
46
|
+
logger.debug(`⒡ Filtered by type: ${meta.type}:${meta.name}`);
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// Check directory filter
|
|
51
|
+
if (filters.directories && filters.directories.length > 0 && meta.filePath) {
|
|
52
|
+
const matchesDirectory = filters.directories.some((dir) => {
|
|
53
|
+
const normalizedFilePath = meta.filePath.replace(/\\/g, '/');
|
|
54
|
+
const normalizedDir = dir.replace(/\\/g, '/');
|
|
55
|
+
return normalizedFilePath.includes(normalizedDir);
|
|
56
|
+
});
|
|
57
|
+
if (!matchesDirectory) {
|
|
58
|
+
logger.debug(`⒡ Filtered by directory: ${meta.type}:${meta.name}`);
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// Check tag filter
|
|
63
|
+
if (filters.tags && filters.tags.length > 0) {
|
|
64
|
+
if (!meta.tags || !filters.tags.some((tag) => meta.tags.includes(tag))) {
|
|
65
|
+
logger.debug(`⒡ Filtered by tags: ${meta.type}:${meta.name}`);
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Check name filter
|
|
70
|
+
if (filters.names && filters.names.length > 0) {
|
|
71
|
+
const nameMatches = filters.names.some((pattern) => matchesWildcard(meta.name, pattern));
|
|
72
|
+
if (!nameMatches) {
|
|
73
|
+
logger.debug(`⒡ Filtered by name: ${meta.type}:${meta.name}`);
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// Check HTTP route filter
|
|
78
|
+
if (filters.httpRoutes && filters.httpRoutes.length > 0 && meta.httpRoute) {
|
|
79
|
+
const routeMatches = filters.httpRoutes.some((pattern) => matchesWildcard(meta.httpRoute, pattern));
|
|
80
|
+
if (!routeMatches) {
|
|
81
|
+
logger.debug(`⒡ Filtered by HTTP route: ${meta.httpRoute}`);
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// Check HTTP method filter
|
|
86
|
+
if (filters.httpMethods &&
|
|
87
|
+
filters.httpMethods.length > 0 &&
|
|
88
|
+
meta.httpMethod) {
|
|
89
|
+
const normalizedMethod = meta.httpMethod.toUpperCase();
|
|
90
|
+
if (!filters.httpMethods.includes(normalizedMethod)) {
|
|
91
|
+
logger.debug(`⒡ Filtered by HTTP method: ${meta.httpMethod}`);
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Extract wire names from middleware/permissions metadata
|
|
99
|
+
*/
|
|
100
|
+
function extractWireNames(obj) {
|
|
101
|
+
if (!obj)
|
|
102
|
+
return [];
|
|
103
|
+
const names = [];
|
|
104
|
+
for (const key of Object.keys(obj)) {
|
|
105
|
+
if (obj[key] && typeof obj[key] === 'object' && 'name' in obj[key]) {
|
|
106
|
+
names.push(obj[key].name);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return names;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Filters inspector state based on provided filters
|
|
113
|
+
* This is applied post-inspection to support the inspect-once, filter-many pattern
|
|
114
|
+
*/
|
|
115
|
+
export function filterInspectorState(state, filters, logger) {
|
|
116
|
+
// If no filters, return original state
|
|
117
|
+
if (Object.keys(filters).length === 0 ||
|
|
118
|
+
((!filters.names || filters.names.length === 0) &&
|
|
119
|
+
(!filters.tags || filters.tags.length === 0) &&
|
|
120
|
+
(!filters.types || filters.types.length === 0) &&
|
|
121
|
+
(!filters.directories || filters.directories.length === 0) &&
|
|
122
|
+
(!filters.httpRoutes || filters.httpRoutes.length === 0) &&
|
|
123
|
+
(!filters.httpMethods || filters.httpMethods.length === 0))) {
|
|
124
|
+
return state;
|
|
125
|
+
}
|
|
126
|
+
// Create a shallow copy with new Maps/Sets to avoid mutating the original
|
|
127
|
+
const filteredState = {
|
|
128
|
+
...state,
|
|
129
|
+
serviceAggregation: {
|
|
130
|
+
...state.serviceAggregation,
|
|
131
|
+
requiredServices: new Set(), // Reset requiredServices - will be recalculated
|
|
132
|
+
usedFunctions: new Set(),
|
|
133
|
+
usedMiddleware: new Set(),
|
|
134
|
+
usedPermissions: new Set(),
|
|
135
|
+
},
|
|
136
|
+
http: {
|
|
137
|
+
...state.http,
|
|
138
|
+
meta: JSON.parse(JSON.stringify(state.http.meta)), // Deep clone metadata
|
|
139
|
+
files: new Set(), // Will be repopulated with filtered files
|
|
140
|
+
},
|
|
141
|
+
channels: {
|
|
142
|
+
...state.channels,
|
|
143
|
+
meta: JSON.parse(JSON.stringify(state.channels.meta)),
|
|
144
|
+
files: new Set(), // Will be repopulated with filtered files
|
|
145
|
+
},
|
|
146
|
+
scheduledTasks: {
|
|
147
|
+
...state.scheduledTasks,
|
|
148
|
+
meta: JSON.parse(JSON.stringify(state.scheduledTasks.meta)),
|
|
149
|
+
files: new Set(), // Will be repopulated with filtered files
|
|
150
|
+
},
|
|
151
|
+
queueWorkers: {
|
|
152
|
+
...state.queueWorkers,
|
|
153
|
+
meta: JSON.parse(JSON.stringify(state.queueWorkers.meta)),
|
|
154
|
+
files: new Set(), // Will be repopulated with filtered files
|
|
155
|
+
},
|
|
156
|
+
mcpEndpoints: {
|
|
157
|
+
...state.mcpEndpoints,
|
|
158
|
+
toolsMeta: JSON.parse(JSON.stringify(state.mcpEndpoints.toolsMeta)),
|
|
159
|
+
resourcesMeta: JSON.parse(JSON.stringify(state.mcpEndpoints.resourcesMeta)),
|
|
160
|
+
promptsMeta: JSON.parse(JSON.stringify(state.mcpEndpoints.promptsMeta)),
|
|
161
|
+
files: new Set(), // Will be repopulated with filtered files
|
|
162
|
+
},
|
|
163
|
+
cli: {
|
|
164
|
+
...state.cli,
|
|
165
|
+
meta: JSON.parse(JSON.stringify(state.cli.meta)),
|
|
166
|
+
files: new Set(), // Will be repopulated with filtered files
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
// Filter HTTP routes
|
|
170
|
+
for (const method of Object.keys(filteredState.http.meta)) {
|
|
171
|
+
const routes = filteredState.http.meta[method];
|
|
172
|
+
for (const route of Object.keys(routes)) {
|
|
173
|
+
const routeMeta = routes[route];
|
|
174
|
+
// Get function file path for directory filtering
|
|
175
|
+
const funcFile = filteredState.functions.files.get(routeMeta.pikkuFuncName);
|
|
176
|
+
const filePath = funcFile?.path;
|
|
177
|
+
const matches = matchesFilters(filters, {
|
|
178
|
+
type: 'http',
|
|
179
|
+
name: routeMeta.pikkuFuncName, // Use function name, not route
|
|
180
|
+
tags: routeMeta.tags,
|
|
181
|
+
filePath,
|
|
182
|
+
httpRoute: routeMeta.route,
|
|
183
|
+
httpMethod: routeMeta.method,
|
|
184
|
+
}, logger);
|
|
185
|
+
if (!matches) {
|
|
186
|
+
delete routes[route];
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
// Track used functions/middleware/permissions
|
|
190
|
+
if (routeMeta.pikkuFuncName) {
|
|
191
|
+
filteredState.serviceAggregation.usedFunctions.add(routeMeta.pikkuFuncName);
|
|
192
|
+
}
|
|
193
|
+
extractWireNames(routeMeta.middleware).forEach((name) => filteredState.serviceAggregation.usedMiddleware.add(name));
|
|
194
|
+
extractWireNames(routeMeta.permissions).forEach((name) => filteredState.serviceAggregation.usedPermissions.add(name));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
// Repopulate http.files if any routes remain
|
|
199
|
+
const hasHttpRoutes = Object.values(filteredState.http.meta).some((routes) => Object.keys(routes).length > 0);
|
|
200
|
+
if (hasHttpRoutes) {
|
|
201
|
+
filteredState.http.files = new Set(state.http.files);
|
|
202
|
+
}
|
|
203
|
+
// Filter channels
|
|
204
|
+
for (const name of Object.keys(filteredState.channels.meta)) {
|
|
205
|
+
const channelMeta = filteredState.channels.meta[name];
|
|
206
|
+
const matches = matchesFilters(filters, {
|
|
207
|
+
type: 'channel',
|
|
208
|
+
name,
|
|
209
|
+
tags: channelMeta.tags,
|
|
210
|
+
}, logger);
|
|
211
|
+
if (!matches) {
|
|
212
|
+
delete filteredState.channels.meta[name];
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
if (channelMeta.pikkuFuncName) {
|
|
216
|
+
filteredState.serviceAggregation.usedFunctions.add(channelMeta.pikkuFuncName);
|
|
217
|
+
}
|
|
218
|
+
extractWireNames(channelMeta.middleware).forEach((name) => filteredState.serviceAggregation.usedMiddleware.add(name));
|
|
219
|
+
extractWireNames(channelMeta.permissions).forEach((name) => filteredState.serviceAggregation.usedPermissions.add(name));
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
// Repopulate channels.files if any channels remain
|
|
223
|
+
if (Object.keys(filteredState.channels.meta).length > 0) {
|
|
224
|
+
filteredState.channels.files = new Set(state.channels.files);
|
|
225
|
+
}
|
|
226
|
+
// Filter scheduled tasks
|
|
227
|
+
for (const name of Object.keys(filteredState.scheduledTasks.meta)) {
|
|
228
|
+
const taskMeta = filteredState.scheduledTasks.meta[name];
|
|
229
|
+
const matches = matchesFilters(filters, {
|
|
230
|
+
type: 'scheduler',
|
|
231
|
+
name,
|
|
232
|
+
tags: taskMeta.tags,
|
|
233
|
+
}, logger);
|
|
234
|
+
if (!matches) {
|
|
235
|
+
delete filteredState.scheduledTasks.meta[name];
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
if (taskMeta.pikkuFuncName) {
|
|
239
|
+
filteredState.serviceAggregation.usedFunctions.add(taskMeta.pikkuFuncName);
|
|
240
|
+
}
|
|
241
|
+
extractWireNames(taskMeta.middleware).forEach((name) => filteredState.serviceAggregation.usedMiddleware.add(name));
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
// Repopulate scheduledTasks.files if any tasks remain
|
|
245
|
+
if (Object.keys(filteredState.scheduledTasks.meta).length > 0) {
|
|
246
|
+
filteredState.scheduledTasks.files = new Set(state.scheduledTasks.files);
|
|
247
|
+
}
|
|
248
|
+
// Filter queue workers
|
|
249
|
+
for (const name of Object.keys(filteredState.queueWorkers.meta)) {
|
|
250
|
+
const workerMeta = filteredState.queueWorkers.meta[name];
|
|
251
|
+
const matches = matchesFilters(filters, {
|
|
252
|
+
type: 'queue',
|
|
253
|
+
name,
|
|
254
|
+
tags: workerMeta.tags,
|
|
255
|
+
}, logger);
|
|
256
|
+
if (!matches) {
|
|
257
|
+
delete filteredState.queueWorkers.meta[name];
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
if (workerMeta.pikkuFuncName) {
|
|
261
|
+
filteredState.serviceAggregation.usedFunctions.add(workerMeta.pikkuFuncName);
|
|
262
|
+
}
|
|
263
|
+
extractWireNames(workerMeta.middleware).forEach((name) => filteredState.serviceAggregation.usedMiddleware.add(name));
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
// Repopulate queueWorkers.files if any workers remain
|
|
267
|
+
if (Object.keys(filteredState.queueWorkers.meta).length > 0) {
|
|
268
|
+
filteredState.queueWorkers.files = new Set(state.queueWorkers.files);
|
|
269
|
+
}
|
|
270
|
+
// Filter MCP tools
|
|
271
|
+
for (const name of Object.keys(filteredState.mcpEndpoints.toolsMeta)) {
|
|
272
|
+
const toolMeta = filteredState.mcpEndpoints.toolsMeta[name];
|
|
273
|
+
const matches = matchesFilters(filters, {
|
|
274
|
+
type: 'mcp',
|
|
275
|
+
name,
|
|
276
|
+
tags: toolMeta.tags,
|
|
277
|
+
}, logger);
|
|
278
|
+
if (!matches) {
|
|
279
|
+
delete filteredState.mcpEndpoints.toolsMeta[name];
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
if (toolMeta.pikkuFuncName) {
|
|
283
|
+
filteredState.serviceAggregation.usedFunctions.add(toolMeta.pikkuFuncName);
|
|
284
|
+
}
|
|
285
|
+
extractWireNames(toolMeta.middleware).forEach((name) => filteredState.serviceAggregation.usedMiddleware.add(name));
|
|
286
|
+
extractWireNames(toolMeta.permissions).forEach((name) => filteredState.serviceAggregation.usedPermissions.add(name));
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
// Filter MCP resources
|
|
290
|
+
for (const name of Object.keys(filteredState.mcpEndpoints.resourcesMeta)) {
|
|
291
|
+
const resourceMeta = filteredState.mcpEndpoints.resourcesMeta[name];
|
|
292
|
+
const matches = matchesFilters(filters, {
|
|
293
|
+
type: 'mcp',
|
|
294
|
+
name,
|
|
295
|
+
tags: resourceMeta.tags,
|
|
296
|
+
}, logger);
|
|
297
|
+
if (!matches) {
|
|
298
|
+
delete filteredState.mcpEndpoints.resourcesMeta[name];
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
if (resourceMeta.pikkuFuncName) {
|
|
302
|
+
filteredState.serviceAggregation.usedFunctions.add(resourceMeta.pikkuFuncName);
|
|
303
|
+
}
|
|
304
|
+
extractWireNames(resourceMeta.middleware).forEach((name) => filteredState.serviceAggregation.usedMiddleware.add(name));
|
|
305
|
+
extractWireNames(resourceMeta.permissions).forEach((name) => filteredState.serviceAggregation.usedPermissions.add(name));
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
// Filter MCP prompts
|
|
309
|
+
for (const name of Object.keys(filteredState.mcpEndpoints.promptsMeta)) {
|
|
310
|
+
const promptMeta = filteredState.mcpEndpoints.promptsMeta[name];
|
|
311
|
+
const matches = matchesFilters(filters, {
|
|
312
|
+
type: 'mcp',
|
|
313
|
+
name,
|
|
314
|
+
tags: promptMeta.tags,
|
|
315
|
+
}, logger);
|
|
316
|
+
if (!matches) {
|
|
317
|
+
delete filteredState.mcpEndpoints.promptsMeta[name];
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
if (promptMeta.pikkuFuncName) {
|
|
321
|
+
filteredState.serviceAggregation.usedFunctions.add(promptMeta.pikkuFuncName);
|
|
322
|
+
}
|
|
323
|
+
extractWireNames(promptMeta.middleware).forEach((name) => filteredState.serviceAggregation.usedMiddleware.add(name));
|
|
324
|
+
extractWireNames(promptMeta.permissions).forEach((name) => filteredState.serviceAggregation.usedPermissions.add(name));
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
// Repopulate mcpEndpoints.files if any MCP endpoints remain
|
|
328
|
+
const hasMcpEndpoints = Object.keys(filteredState.mcpEndpoints.toolsMeta).length > 0 ||
|
|
329
|
+
Object.keys(filteredState.mcpEndpoints.resourcesMeta).length > 0 ||
|
|
330
|
+
Object.keys(filteredState.mcpEndpoints.promptsMeta).length > 0;
|
|
331
|
+
if (hasMcpEndpoints) {
|
|
332
|
+
filteredState.mcpEndpoints.files = new Set(state.mcpEndpoints.files);
|
|
333
|
+
}
|
|
334
|
+
// Filter CLI programs (note: CLI filtering might be more complex with nested commands)
|
|
335
|
+
const referencedRenderers = new Set();
|
|
336
|
+
for (const programName of Object.keys(filteredState.cli.meta.programs)) {
|
|
337
|
+
const programMeta = filteredState.cli.meta.programs[programName];
|
|
338
|
+
// Filter commands in the program
|
|
339
|
+
for (const commandName of Object.keys(programMeta.commands)) {
|
|
340
|
+
const commandMeta = programMeta.commands[commandName];
|
|
341
|
+
const matches = matchesFilters(filters, {
|
|
342
|
+
type: 'cli',
|
|
343
|
+
name: commandName,
|
|
344
|
+
tags: commandMeta.tags,
|
|
345
|
+
}, logger);
|
|
346
|
+
if (!matches) {
|
|
347
|
+
delete programMeta.commands[commandName];
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
if (commandMeta.pikkuFuncName) {
|
|
351
|
+
filteredState.serviceAggregation.usedFunctions.add(commandMeta.pikkuFuncName);
|
|
352
|
+
}
|
|
353
|
+
extractWireNames(commandMeta.middleware).forEach((name) => filteredState.serviceAggregation.usedMiddleware.add(name));
|
|
354
|
+
// Track referenced renderers
|
|
355
|
+
if (commandMeta.defaultRenderName) {
|
|
356
|
+
referencedRenderers.add(commandMeta.defaultRenderName);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
// Remove program if it has no commands left
|
|
361
|
+
if (Object.keys(programMeta.commands).length === 0) {
|
|
362
|
+
delete filteredState.cli.meta.programs[programName];
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
// Filter out renderers that aren't referenced by any remaining commands
|
|
366
|
+
for (const rendererName of Object.keys(filteredState.cli.meta.renderers || {})) {
|
|
367
|
+
if (!referencedRenderers.has(rendererName)) {
|
|
368
|
+
delete filteredState.cli.meta.renderers[rendererName];
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
// Repopulate cli.files if any CLI programs or referenced renderers remain
|
|
372
|
+
const hasCliPrograms = Object.keys(filteredState.cli.meta.programs).length > 0;
|
|
373
|
+
const hasCliRenderers = Object.keys(filteredState.cli.meta.renderers || {}).length > 0;
|
|
374
|
+
if (hasCliPrograms || hasCliRenderers) {
|
|
375
|
+
filteredState.cli.files = new Set(state.cli.files);
|
|
376
|
+
}
|
|
377
|
+
// Recalculate requiredServices based on filtered functions/middleware/permissions
|
|
378
|
+
// Need to cast to InspectorState temporarily for aggregateRequiredServices
|
|
379
|
+
const stateForAggregation = filteredState;
|
|
380
|
+
aggregateRequiredServices(stateForAggregation);
|
|
381
|
+
return filteredState;
|
|
382
|
+
}
|
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
import { InspectorFilters, InspectorLogger } from '../types.js';
|
|
2
2
|
import { PikkuWiringTypes } from '@pikku/core';
|
|
3
|
+
/**
|
|
4
|
+
* Match a value against a pattern with wildcard support
|
|
5
|
+
* Supports "*" at the beginning, end, or both (e.g., "send*", "*Payment", "*process*")
|
|
6
|
+
* @param value - The value to check
|
|
7
|
+
* @param pattern - The pattern with optional "*" wildcard(s)
|
|
8
|
+
*/
|
|
9
|
+
export declare function matchesWildcard(value: string, pattern: string): boolean;
|
|
3
10
|
export declare const matchesFilters: (filters: InspectorFilters, params: {
|
|
4
11
|
tags?: string[];
|
|
12
|
+
name?: string;
|
|
5
13
|
}, meta: {
|
|
6
14
|
type: PikkuWiringTypes;
|
|
7
15
|
name: string;
|
|
8
16
|
filePath?: string;
|
|
17
|
+
httpRoute?: string;
|
|
18
|
+
httpMethod?: string;
|
|
9
19
|
}, logger: InspectorLogger) => boolean;
|
|
@@ -1,12 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Match a value against a pattern with wildcard support
|
|
3
|
+
* Supports "*" at the beginning, end, or both (e.g., "send*", "*Payment", "*process*")
|
|
4
|
+
* @param value - The value to check
|
|
5
|
+
* @param pattern - The pattern with optional "*" wildcard(s)
|
|
6
|
+
*/
|
|
7
|
+
export function matchesWildcard(value, pattern) {
|
|
8
|
+
// If pattern is just '*', match everything
|
|
9
|
+
if (pattern === '*') {
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
const startsWithWildcard = pattern.startsWith('*');
|
|
13
|
+
const endsWithWildcard = pattern.endsWith('*');
|
|
14
|
+
if (startsWithWildcard && endsWithWildcard) {
|
|
15
|
+
// Pattern like "*middle*" - check if value contains the middle part
|
|
16
|
+
const middle = pattern.slice(1, -1);
|
|
17
|
+
if (middle === '') {
|
|
18
|
+
return true; // Pattern is "**", match everything
|
|
19
|
+
}
|
|
20
|
+
return value.includes(middle);
|
|
21
|
+
}
|
|
22
|
+
else if (startsWithWildcard) {
|
|
23
|
+
// Pattern like "*suffix" - check if value ends with suffix and has content before
|
|
24
|
+
const suffix = pattern.slice(1);
|
|
25
|
+
return value.endsWith(suffix) && value.length > suffix.length;
|
|
26
|
+
}
|
|
27
|
+
else if (endsWithWildcard) {
|
|
28
|
+
// Pattern like "prefix*" - check if value starts with prefix and has content after
|
|
29
|
+
const prefix = pattern.slice(0, -1);
|
|
30
|
+
return value.startsWith(prefix) && value.length > prefix.length;
|
|
31
|
+
}
|
|
32
|
+
// No wildcard, exact match
|
|
33
|
+
return value === pattern;
|
|
34
|
+
}
|
|
1
35
|
export const matchesFilters = (filters, params, meta, logger) => {
|
|
2
36
|
// If no filters are provided, allow everything
|
|
3
37
|
if (Object.keys(filters).length === 0) {
|
|
4
38
|
return true;
|
|
5
39
|
}
|
|
6
40
|
// If all filter arrays are empty, allow everything
|
|
7
|
-
if ((!filters.
|
|
41
|
+
if ((!filters.names || filters.names.length === 0) &&
|
|
42
|
+
(!filters.tags || filters.tags.length === 0) &&
|
|
8
43
|
(!filters.types || filters.types.length === 0) &&
|
|
9
|
-
(!filters.directories || filters.directories.length === 0)
|
|
44
|
+
(!filters.directories || filters.directories.length === 0) &&
|
|
45
|
+
(!filters.httpRoutes || filters.httpRoutes.length === 0) &&
|
|
46
|
+
(!filters.httpMethods || filters.httpMethods.length === 0)) {
|
|
10
47
|
return true;
|
|
11
48
|
}
|
|
12
49
|
// Check type filter
|
|
@@ -41,5 +78,32 @@ export const matchesFilters = (filters, params, meta, logger) => {
|
|
|
41
78
|
return false;
|
|
42
79
|
}
|
|
43
80
|
}
|
|
81
|
+
// Check name filter (with wildcard support)
|
|
82
|
+
if (filters.names && filters.names.length > 0) {
|
|
83
|
+
const nameToMatch = params.name || meta.name;
|
|
84
|
+
const nameMatches = filters.names.some((pattern) => matchesWildcard(nameToMatch, pattern));
|
|
85
|
+
if (!nameMatches) {
|
|
86
|
+
logger.debug(`⒡ Filtered by name: ${meta.type}:${meta.name}`);
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Check HTTP route filter (with wildcard support)
|
|
91
|
+
if (filters.httpRoutes && filters.httpRoutes.length > 0 && meta.httpRoute) {
|
|
92
|
+
const routeMatches = filters.httpRoutes.some((pattern) => matchesWildcard(meta.httpRoute, pattern));
|
|
93
|
+
if (!routeMatches) {
|
|
94
|
+
logger.debug(`⒡ Filtered by HTTP route: ${meta.httpRoute}`);
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// Check HTTP method filter
|
|
99
|
+
if (filters.httpMethods &&
|
|
100
|
+
filters.httpMethods.length > 0 &&
|
|
101
|
+
meta.httpMethod) {
|
|
102
|
+
const normalizedMethod = meta.httpMethod.toUpperCase();
|
|
103
|
+
if (!filters.httpMethods.includes(normalizedMethod)) {
|
|
104
|
+
logger.debug(`⒡ Filtered by HTTP method: ${meta.httpMethod}`);
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
44
108
|
return true;
|
|
45
109
|
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Finds the common ancestor directory of all the given file paths.
|
|
3
|
+
* This is used to determine the project root directory.
|
|
4
|
+
*
|
|
5
|
+
* @param filePaths - Array of absolute file paths
|
|
6
|
+
* @returns The common ancestor directory path
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* findCommonAncestor([
|
|
10
|
+
* '/Users/yasser/git/pikku/pikku/src/functions/a.ts',
|
|
11
|
+
* '/Users/yasser/git/pikku/pikku/src/routes/b.ts'
|
|
12
|
+
* ])
|
|
13
|
+
* // Returns: '/Users/yasser/git/pikku/pikku'
|
|
14
|
+
*/
|
|
15
|
+
export declare function findCommonAncestor(filePaths: string[]): string;
|
|
16
|
+
/**
|
|
17
|
+
* Converts an absolute file path to a relative path from the root directory.
|
|
18
|
+
*
|
|
19
|
+
* @param absolutePath - The absolute file path
|
|
20
|
+
* @param rootDir - The root directory to make the path relative to
|
|
21
|
+
* @returns A relative path from rootDir to the file
|
|
22
|
+
*/
|
|
23
|
+
export declare function toRelativePath(absolutePath: string, rootDir: string): string;
|