@pikku/inspector 0.12.11 → 0.12.12
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 +13 -0
- package/dist/add/add-cli.js +10 -3
- package/dist/add/add-credential.js +2 -1
- package/dist/add/add-functions.js +28 -1
- package/dist/add/add-http-route.js +24 -5
- package/dist/add/add-keyed-wiring.js +3 -1
- package/dist/add/add-middleware.js +33 -4
- package/dist/add/add-permission.js +7 -7
- package/dist/add/add-workflow-graph.js +20 -1
- package/dist/error-codes.d.ts +1 -0
- package/dist/error-codes.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/inspector.js +2 -5
- package/dist/types.d.ts +10 -19
- package/dist/utils/extract-function-name.js +6 -0
- package/dist/utils/filter-inspector-state.js +187 -59
- package/dist/utils/filter-utils.js +13 -5
- package/dist/utils/get-property-value.d.ts +10 -0
- package/dist/utils/get-property-value.js +30 -0
- package/dist/utils/post-process.d.ts +2 -3
- package/dist/utils/post-process.js +3 -23
- package/dist/utils/resolve-addon-package.d.ts +4 -5
- package/dist/utils/resolve-addon-package.js +64 -16
- package/dist/utils/resolve-deploy-target.d.ts +28 -0
- package/dist/utils/resolve-deploy-target.js +56 -0
- package/dist/utils/resolve-versions.js +79 -0
- package/dist/utils/schema-generator.js +31 -12
- package/package.json +2 -2
- package/src/add/add-cli.ts +10 -3
- package/src/add/add-credential.ts +3 -0
- package/src/add/add-functions.test.ts +149 -0
- package/src/add/add-functions.ts +37 -1
- package/src/add/add-gateway.ts +5 -1
- package/src/add/add-http-route.ts +26 -6
- package/src/add/add-keyed-wiring.ts +7 -1
- package/src/add/add-mcp-prompt.ts +5 -1
- package/src/add/add-mcp-resource.ts +5 -1
- package/src/add/add-middleware.ts +42 -4
- package/src/add/add-permission.ts +7 -7
- package/src/add/add-schedule.ts +5 -1
- package/src/add/add-workflow-graph.ts +19 -1
- package/src/add/wire-name-literal.test.ts +114 -0
- package/src/error-codes.ts +1 -0
- package/src/index.ts +1 -0
- package/src/inspector.ts +1 -5
- package/src/types.ts +19 -15
- package/src/utils/extract-function-name.ts +8 -0
- package/src/utils/filter-inspector-state.test.ts +168 -64
- package/src/utils/filter-inspector-state.ts +290 -64
- package/src/utils/filter-utils.test.ts +30 -15
- package/src/utils/filter-utils.ts +14 -5
- package/src/utils/get-property-value.ts +40 -0
- package/src/utils/post-process.ts +3 -38
- package/src/utils/resolve-addon-package.ts +65 -14
- package/src/utils/resolve-deploy-target.test.ts +105 -0
- package/src/utils/resolve-deploy-target.ts +63 -0
- package/src/utils/resolve-versions.test.ts +108 -0
- package/src/utils/resolve-versions.ts +86 -0
- package/src/utils/schema-generator.ts +37 -13
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { parseVersionedId } from '@pikku/core';
|
|
2
2
|
import { aggregateRequiredServices } from './post-process.js';
|
|
3
|
+
import { resolveDeployTarget } from './resolve-deploy-target.js';
|
|
3
4
|
// Module-level Set to track warned groups across multiple filter calls
|
|
4
5
|
const globalWarnedGroups = new Set();
|
|
5
6
|
/**
|
|
@@ -27,26 +28,85 @@ function matchesWildcard(value, pattern) {
|
|
|
27
28
|
}
|
|
28
29
|
return value === pattern;
|
|
29
30
|
}
|
|
31
|
+
function collectSourceFiles(value, sourceFiles = new Set(), seen = new Set()) {
|
|
32
|
+
if (typeof value === 'string') {
|
|
33
|
+
return sourceFiles;
|
|
34
|
+
}
|
|
35
|
+
if (Array.isArray(value)) {
|
|
36
|
+
for (const entry of value) {
|
|
37
|
+
collectSourceFiles(entry, sourceFiles, seen);
|
|
38
|
+
}
|
|
39
|
+
return sourceFiles;
|
|
40
|
+
}
|
|
41
|
+
if (!value || typeof value !== 'object') {
|
|
42
|
+
return sourceFiles;
|
|
43
|
+
}
|
|
44
|
+
if (seen.has(value)) {
|
|
45
|
+
return sourceFiles;
|
|
46
|
+
}
|
|
47
|
+
seen.add(value);
|
|
48
|
+
if ('sourceFile' in value && typeof value.sourceFile === 'string') {
|
|
49
|
+
sourceFiles.add(value.sourceFile);
|
|
50
|
+
}
|
|
51
|
+
for (const nestedValue of Object.values(value)) {
|
|
52
|
+
collectSourceFiles(nestedValue, sourceFiles, seen);
|
|
53
|
+
}
|
|
54
|
+
return sourceFiles;
|
|
55
|
+
}
|
|
56
|
+
function repopulateFileSetFromMeta(meta, fallbackFiles, hasEntries) {
|
|
57
|
+
const sourceFiles = collectSourceFiles(meta);
|
|
58
|
+
if (sourceFiles.size > 0) {
|
|
59
|
+
return sourceFiles;
|
|
60
|
+
}
|
|
61
|
+
if (hasEntries) {
|
|
62
|
+
return new Set(fallbackFiles);
|
|
63
|
+
}
|
|
64
|
+
return new Set();
|
|
65
|
+
}
|
|
30
66
|
/**
|
|
31
67
|
* Check if metadata matches the given filters
|
|
32
68
|
*/
|
|
33
|
-
function matchesFilters(filters, meta, logger, warnedGroups
|
|
69
|
+
function matchesFilters(filters, meta, logger, warnedGroups,
|
|
70
|
+
// Set of pikkuFuncIds to keep based on the deploy filter; null when
|
|
71
|
+
// the deploy filter is inactive.
|
|
72
|
+
keptByDeploy) {
|
|
34
73
|
// If no filters, allow everything
|
|
35
74
|
if (Object.keys(filters).length === 0)
|
|
36
75
|
return true;
|
|
37
76
|
// If all filter arrays are empty, allow everything
|
|
38
77
|
if ((!filters.names || filters.names.length === 0) &&
|
|
39
78
|
(!filters.tags || filters.tags.length === 0) &&
|
|
40
|
-
(!filters.
|
|
79
|
+
(!filters.wires || filters.wires.length === 0) &&
|
|
80
|
+
(!filters.excludeWires || filters.excludeWires.length === 0) &&
|
|
41
81
|
(!filters.directories || filters.directories.length === 0) &&
|
|
42
82
|
(!filters.httpRoutes || filters.httpRoutes.length === 0) &&
|
|
43
|
-
(!filters.httpMethods || filters.httpMethods.length === 0)
|
|
83
|
+
(!filters.httpMethods || filters.httpMethods.length === 0) &&
|
|
84
|
+
(!filters.target || filters.target.length === 0) &&
|
|
85
|
+
(!filters.excludeNames || filters.excludeNames.length === 0) &&
|
|
86
|
+
(!filters.excludeTags || filters.excludeTags.length === 0) &&
|
|
87
|
+
(!filters.excludeWires || filters.excludeWires.length === 0) &&
|
|
88
|
+
(!filters.excludeDirectories || filters.excludeDirectories.length === 0) &&
|
|
89
|
+
(!filters.excludeHttpRoutes || filters.excludeHttpRoutes.length === 0) &&
|
|
90
|
+
(!filters.excludeHttpMethods || filters.excludeHttpMethods.length === 0) &&
|
|
91
|
+
(!filters.excludeTarget || filters.excludeTarget.length === 0)) {
|
|
44
92
|
return true;
|
|
45
93
|
}
|
|
46
|
-
//
|
|
47
|
-
if (
|
|
48
|
-
|
|
49
|
-
|
|
94
|
+
// Deploy-target include filter (computed once per filterInspectorState call).
|
|
95
|
+
if (keptByDeploy && !keptByDeploy.has(meta.name)) {
|
|
96
|
+
logger.debug(`⒡ Filtered by deploy include: ${meta.type}:${meta.name}`);
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
// Check wire include filter
|
|
100
|
+
if (filters.wires && filters.wires.length > 0) {
|
|
101
|
+
if (!filters.wires.includes(meta.type)) {
|
|
102
|
+
logger.debug(`⒡ Filtered by wire include: ${meta.type}:${meta.name}`);
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// Check wire exclude filter
|
|
107
|
+
if (filters.excludeWires && filters.excludeWires.length > 0) {
|
|
108
|
+
if (filters.excludeWires.includes(meta.type)) {
|
|
109
|
+
logger.debug(`⒡ Filtered by wire exclude: ${meta.type}:${meta.name}`);
|
|
50
110
|
return false;
|
|
51
111
|
}
|
|
52
112
|
}
|
|
@@ -62,28 +122,45 @@ function matchesFilters(filters, meta, logger, warnedGroups) {
|
|
|
62
122
|
return false;
|
|
63
123
|
}
|
|
64
124
|
}
|
|
65
|
-
// Check tag filter
|
|
125
|
+
// Check tag include filter
|
|
66
126
|
if (filters.tags && filters.tags.length > 0) {
|
|
67
127
|
if (!meta.tags || !filters.tags.some((tag) => meta.tags.includes(tag))) {
|
|
68
|
-
logger.debug(`⒡ Filtered by tags: ${meta.type}:${meta.name}`);
|
|
128
|
+
logger.debug(`⒡ Filtered by tags include: ${meta.type}:${meta.name}`);
|
|
69
129
|
return false;
|
|
70
130
|
}
|
|
71
131
|
}
|
|
72
|
-
// Check
|
|
132
|
+
// Check tag exclude filter
|
|
133
|
+
if (filters.excludeTags && filters.excludeTags.length > 0) {
|
|
134
|
+
if (meta.tags &&
|
|
135
|
+
filters.excludeTags.some((tag) => meta.tags.includes(tag))) {
|
|
136
|
+
logger.debug(`⒡ Filtered by tags exclude: ${meta.type}:${meta.name}`);
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const { baseName } = parseVersionedId(meta.name);
|
|
141
|
+
const matchesNamePattern = (pattern) => matchesWildcard(meta.name, pattern) ||
|
|
142
|
+
(baseName !== meta.name && matchesWildcard(baseName, pattern));
|
|
143
|
+
// Check name include filter (match against both full ID and base name for versioned functions)
|
|
73
144
|
if (filters.names && filters.names.length > 0) {
|
|
74
|
-
const
|
|
75
|
-
const nameMatches = filters.names.some((pattern) => matchesWildcard(meta.name, pattern) ||
|
|
76
|
-
(baseName !== meta.name && matchesWildcard(baseName, pattern)));
|
|
145
|
+
const nameMatches = filters.names.some(matchesNamePattern);
|
|
77
146
|
if (!nameMatches) {
|
|
78
|
-
logger.debug(`⒡ Filtered by name: ${meta.type}:${meta.name}`);
|
|
147
|
+
logger.debug(`⒡ Filtered by name include: ${meta.type}:${meta.name}`);
|
|
79
148
|
return false;
|
|
80
149
|
}
|
|
81
150
|
}
|
|
82
|
-
// Check
|
|
151
|
+
// Check name exclude filter
|
|
152
|
+
if (filters.excludeNames && filters.excludeNames.length > 0) {
|
|
153
|
+
if (filters.excludeNames.some(matchesNamePattern)) {
|
|
154
|
+
logger.debug(`⒡ Filtered by name exclude: ${meta.type}:${meta.name}`);
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const matchesRoutePattern = (pattern) => !!meta.httpRoute && matchesWildcard(meta.httpRoute, pattern);
|
|
159
|
+
// Check HTTP route include filter
|
|
83
160
|
if (filters.httpRoutes && filters.httpRoutes.length > 0 && meta.httpRoute) {
|
|
84
|
-
const routeMatches = filters.httpRoutes.some(
|
|
161
|
+
const routeMatches = filters.httpRoutes.some(matchesRoutePattern);
|
|
85
162
|
if (!routeMatches) {
|
|
86
|
-
logger.debug(`⒡ Filtered by HTTP route: ${meta.httpRoute}`);
|
|
163
|
+
logger.debug(`⒡ Filtered by HTTP route include: ${meta.httpRoute}`);
|
|
87
164
|
return false;
|
|
88
165
|
}
|
|
89
166
|
// If route is part of a wireHTTPRoutes group, check if filter is at group level
|
|
@@ -98,13 +175,31 @@ function matchesFilters(filters, meta, logger, warnedGroups) {
|
|
|
98
175
|
}
|
|
99
176
|
}
|
|
100
177
|
}
|
|
101
|
-
// Check HTTP
|
|
178
|
+
// Check HTTP route exclude filter
|
|
179
|
+
if (filters.excludeHttpRoutes &&
|
|
180
|
+
filters.excludeHttpRoutes.length > 0 &&
|
|
181
|
+
meta.httpRoute) {
|
|
182
|
+
if (filters.excludeHttpRoutes.some(matchesRoutePattern)) {
|
|
183
|
+
logger.debug(`⒡ Filtered by HTTP route exclude: ${meta.httpRoute}`);
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
const normalizedMethod = meta.httpMethod?.toUpperCase();
|
|
188
|
+
// Check HTTP method include filter
|
|
102
189
|
if (filters.httpMethods &&
|
|
103
190
|
filters.httpMethods.length > 0 &&
|
|
104
|
-
|
|
105
|
-
const normalizedMethod = meta.httpMethod.toUpperCase();
|
|
191
|
+
normalizedMethod) {
|
|
106
192
|
if (!filters.httpMethods.includes(normalizedMethod)) {
|
|
107
|
-
logger.debug(`⒡ Filtered by HTTP method: ${meta.httpMethod}`);
|
|
193
|
+
logger.debug(`⒡ Filtered by HTTP method include: ${meta.httpMethod}`);
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// Check HTTP method exclude filter
|
|
198
|
+
if (filters.excludeHttpMethods &&
|
|
199
|
+
filters.excludeHttpMethods.length > 0 &&
|
|
200
|
+
normalizedMethod) {
|
|
201
|
+
if (filters.excludeHttpMethods.includes(normalizedMethod)) {
|
|
202
|
+
logger.debug(`⒡ Filtered by HTTP method exclude: ${meta.httpMethod}`);
|
|
108
203
|
return false;
|
|
109
204
|
}
|
|
110
205
|
}
|
|
@@ -133,12 +228,44 @@ export function filterInspectorState(state, filters, logger) {
|
|
|
133
228
|
if (Object.keys(filters).length === 0 ||
|
|
134
229
|
((!filters.names || filters.names.length === 0) &&
|
|
135
230
|
(!filters.tags || filters.tags.length === 0) &&
|
|
136
|
-
(!filters.
|
|
231
|
+
(!filters.wires || filters.wires.length === 0) &&
|
|
232
|
+
(!filters.excludeWires || filters.excludeWires.length === 0) &&
|
|
137
233
|
(!filters.directories || filters.directories.length === 0) &&
|
|
138
234
|
(!filters.httpRoutes || filters.httpRoutes.length === 0) &&
|
|
139
|
-
(!filters.httpMethods || filters.httpMethods.length === 0)
|
|
235
|
+
(!filters.httpMethods || filters.httpMethods.length === 0) &&
|
|
236
|
+
(!filters.target || filters.target.length === 0) &&
|
|
237
|
+
(!filters.excludeNames || filters.excludeNames.length === 0) &&
|
|
238
|
+
(!filters.excludeTags || filters.excludeTags.length === 0) &&
|
|
239
|
+
(!filters.excludeWires || filters.excludeWires.length === 0) &&
|
|
240
|
+
(!filters.excludeDirectories ||
|
|
241
|
+
filters.excludeDirectories.length === 0) &&
|
|
242
|
+
(!filters.excludeHttpRoutes || filters.excludeHttpRoutes.length === 0) &&
|
|
243
|
+
(!filters.excludeHttpMethods ||
|
|
244
|
+
filters.excludeHttpMethods.length === 0) &&
|
|
245
|
+
(!filters.excludeTarget || filters.excludeTarget.length === 0))) {
|
|
140
246
|
return state;
|
|
141
247
|
}
|
|
248
|
+
// Precompute kept-function set for the deploy filter (if active).
|
|
249
|
+
// resolveDeployTarget throws IncompatibleDeployTargetError when an
|
|
250
|
+
// explicit deploy: 'serverless' clashes with serverlessIncompatible.
|
|
251
|
+
let keptByDeploy = null;
|
|
252
|
+
if ((filters.target && filters.target.length > 0) ||
|
|
253
|
+
(filters.excludeTarget && filters.excludeTarget.length > 0)) {
|
|
254
|
+
const allowed = filters.target ? new Set(filters.target) : null;
|
|
255
|
+
const excluded = filters.excludeTarget
|
|
256
|
+
? new Set(filters.excludeTarget)
|
|
257
|
+
: null;
|
|
258
|
+
const incompatible = new Set(filters.serverlessIncompatible ?? []);
|
|
259
|
+
keptByDeploy = new Set();
|
|
260
|
+
for (const [funcId, funcMeta] of Object.entries(state.functions.meta)) {
|
|
261
|
+
const target = resolveDeployTarget(funcMeta, incompatible, funcId);
|
|
262
|
+
if (allowed && !allowed.has(target))
|
|
263
|
+
continue;
|
|
264
|
+
if (excluded && excluded.has(target))
|
|
265
|
+
continue;
|
|
266
|
+
keptByDeploy.add(funcId);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
142
269
|
// Snapshot the original workflow graph meta before filtering prunes it
|
|
143
270
|
const originalGraphMeta = {
|
|
144
271
|
...(state.workflows?.graphMeta ?? {}),
|
|
@@ -230,7 +357,7 @@ export function filterInspectorState(state, filters, logger) {
|
|
|
230
357
|
httpRoute: routeMeta.route,
|
|
231
358
|
httpMethod: routeMeta.method,
|
|
232
359
|
groupBasePath: routeMeta.groupBasePath,
|
|
233
|
-
}, logger, globalWarnedGroups);
|
|
360
|
+
}, logger, globalWarnedGroups, keptByDeploy);
|
|
234
361
|
if (!matches) {
|
|
235
362
|
delete routes[route];
|
|
236
363
|
}
|
|
@@ -273,7 +400,7 @@ export function filterInspectorState(state, filters, logger) {
|
|
|
273
400
|
type: 'channel',
|
|
274
401
|
name,
|
|
275
402
|
tags: channelMeta.tags,
|
|
276
|
-
}, logger);
|
|
403
|
+
}, logger, undefined, keptByDeploy);
|
|
277
404
|
if (!matches) {
|
|
278
405
|
delete filteredState.channels.meta[name];
|
|
279
406
|
}
|
|
@@ -306,10 +433,7 @@ export function filterInspectorState(state, filters, logger) {
|
|
|
306
433
|
extractWireNames(channelMeta.permissions).forEach((name) => filteredState.serviceAggregation.usedPermissions.add(name));
|
|
307
434
|
}
|
|
308
435
|
}
|
|
309
|
-
|
|
310
|
-
if (Object.keys(filteredState.channels.meta).length > 0) {
|
|
311
|
-
filteredState.channels.files = new Set(state.channels.files);
|
|
312
|
-
}
|
|
436
|
+
filteredState.channels.files = repopulateFileSetFromMeta(filteredState.channels.meta, state.channels.files, Object.keys(filteredState.channels.meta).length > 0);
|
|
313
437
|
// Filter triggers
|
|
314
438
|
for (const name of Object.keys(filteredState.triggers.meta)) {
|
|
315
439
|
const triggerMeta = filteredState.triggers.meta[name];
|
|
@@ -317,7 +441,7 @@ export function filterInspectorState(state, filters, logger) {
|
|
|
317
441
|
type: 'trigger',
|
|
318
442
|
name,
|
|
319
443
|
tags: triggerMeta.tags,
|
|
320
|
-
}, logger);
|
|
444
|
+
}, logger, undefined, keptByDeploy);
|
|
321
445
|
if (!matches) {
|
|
322
446
|
delete filteredState.triggers.meta[name];
|
|
323
447
|
}
|
|
@@ -327,10 +451,7 @@ export function filterInspectorState(state, filters, logger) {
|
|
|
327
451
|
}
|
|
328
452
|
}
|
|
329
453
|
}
|
|
330
|
-
|
|
331
|
-
if (Object.keys(filteredState.triggers.meta).length > 0) {
|
|
332
|
-
filteredState.triggers.files = new Set(state.triggers.files);
|
|
333
|
-
}
|
|
454
|
+
filteredState.triggers.files = repopulateFileSetFromMeta(filteredState.triggers.meta, state.triggers.files, Object.keys(filteredState.triggers.meta).length > 0);
|
|
334
455
|
// Filter scheduled tasks
|
|
335
456
|
for (const name of Object.keys(filteredState.scheduledTasks.meta)) {
|
|
336
457
|
const taskMeta = filteredState.scheduledTasks.meta[name];
|
|
@@ -338,7 +459,7 @@ export function filterInspectorState(state, filters, logger) {
|
|
|
338
459
|
type: 'scheduler',
|
|
339
460
|
name,
|
|
340
461
|
tags: taskMeta.tags,
|
|
341
|
-
}, logger);
|
|
462
|
+
}, logger, undefined, keptByDeploy);
|
|
342
463
|
if (!matches) {
|
|
343
464
|
delete filteredState.scheduledTasks.meta[name];
|
|
344
465
|
}
|
|
@@ -349,10 +470,7 @@ export function filterInspectorState(state, filters, logger) {
|
|
|
349
470
|
extractWireNames(taskMeta.middleware).forEach((name) => filteredState.serviceAggregation.usedMiddleware.add(name));
|
|
350
471
|
}
|
|
351
472
|
}
|
|
352
|
-
|
|
353
|
-
if (Object.keys(filteredState.scheduledTasks.meta).length > 0) {
|
|
354
|
-
filteredState.scheduledTasks.files = new Set(state.scheduledTasks.files);
|
|
355
|
-
}
|
|
473
|
+
filteredState.scheduledTasks.files = repopulateFileSetFromMeta(filteredState.scheduledTasks.meta, state.scheduledTasks.files, Object.keys(filteredState.scheduledTasks.meta).length > 0);
|
|
356
474
|
// Filter queue workers
|
|
357
475
|
for (const name of Object.keys(filteredState.queueWorkers.meta)) {
|
|
358
476
|
const workerMeta = filteredState.queueWorkers.meta[name];
|
|
@@ -360,7 +478,7 @@ export function filterInspectorState(state, filters, logger) {
|
|
|
360
478
|
type: 'queue',
|
|
361
479
|
name,
|
|
362
480
|
tags: workerMeta.tags,
|
|
363
|
-
}, logger);
|
|
481
|
+
}, logger, undefined, keptByDeploy);
|
|
364
482
|
if (!matches) {
|
|
365
483
|
delete filteredState.queueWorkers.meta[name];
|
|
366
484
|
}
|
|
@@ -375,10 +493,7 @@ export function filterInspectorState(state, filters, logger) {
|
|
|
375
493
|
extractWireNames(workerMeta.middleware).forEach((name) => filteredState.serviceAggregation.usedMiddleware.add(name));
|
|
376
494
|
}
|
|
377
495
|
}
|
|
378
|
-
|
|
379
|
-
if (Object.keys(filteredState.queueWorkers.meta).length > 0) {
|
|
380
|
-
filteredState.queueWorkers.files = new Set(state.queueWorkers.files);
|
|
381
|
-
}
|
|
496
|
+
filteredState.queueWorkers.files = repopulateFileSetFromMeta(filteredState.queueWorkers.meta, state.queueWorkers.files, Object.keys(filteredState.queueWorkers.meta).length > 0);
|
|
382
497
|
// Filter MCP tools
|
|
383
498
|
for (const name of Object.keys(filteredState.mcpEndpoints.toolsMeta)) {
|
|
384
499
|
const toolMeta = filteredState.mcpEndpoints.toolsMeta[name];
|
|
@@ -386,7 +501,7 @@ export function filterInspectorState(state, filters, logger) {
|
|
|
386
501
|
type: 'mcp',
|
|
387
502
|
name,
|
|
388
503
|
tags: toolMeta.tags,
|
|
389
|
-
}, logger);
|
|
504
|
+
}, logger, undefined, keptByDeploy);
|
|
390
505
|
if (!matches) {
|
|
391
506
|
delete filteredState.mcpEndpoints.toolsMeta[name];
|
|
392
507
|
}
|
|
@@ -405,7 +520,7 @@ export function filterInspectorState(state, filters, logger) {
|
|
|
405
520
|
type: 'mcp',
|
|
406
521
|
name,
|
|
407
522
|
tags: resourceMeta.tags,
|
|
408
|
-
}, logger);
|
|
523
|
+
}, logger, undefined, keptByDeploy);
|
|
409
524
|
if (!matches) {
|
|
410
525
|
delete filteredState.mcpEndpoints.resourcesMeta[name];
|
|
411
526
|
}
|
|
@@ -424,7 +539,7 @@ export function filterInspectorState(state, filters, logger) {
|
|
|
424
539
|
type: 'mcp',
|
|
425
540
|
name,
|
|
426
541
|
tags: promptMeta.tags,
|
|
427
|
-
}, logger);
|
|
542
|
+
}, logger, undefined, keptByDeploy);
|
|
428
543
|
if (!matches) {
|
|
429
544
|
delete filteredState.mcpEndpoints.promptsMeta[name];
|
|
430
545
|
}
|
|
@@ -436,13 +551,15 @@ export function filterInspectorState(state, filters, logger) {
|
|
|
436
551
|
extractWireNames(promptMeta.permissions).forEach((name) => filteredState.serviceAggregation.usedPermissions.add(name));
|
|
437
552
|
}
|
|
438
553
|
}
|
|
439
|
-
// Repopulate mcpEndpoints.files
|
|
554
|
+
// Repopulate mcpEndpoints.files from surviving endpoint metadata
|
|
440
555
|
const hasMcpEndpoints = Object.keys(filteredState.mcpEndpoints.toolsMeta).length > 0 ||
|
|
441
556
|
Object.keys(filteredState.mcpEndpoints.resourcesMeta).length > 0 ||
|
|
442
557
|
Object.keys(filteredState.mcpEndpoints.promptsMeta).length > 0;
|
|
443
|
-
|
|
444
|
-
filteredState.mcpEndpoints.
|
|
445
|
-
|
|
558
|
+
filteredState.mcpEndpoints.files = repopulateFileSetFromMeta({
|
|
559
|
+
toolsMeta: filteredState.mcpEndpoints.toolsMeta,
|
|
560
|
+
resourcesMeta: filteredState.mcpEndpoints.resourcesMeta,
|
|
561
|
+
promptsMeta: filteredState.mcpEndpoints.promptsMeta,
|
|
562
|
+
}, state.mcpEndpoints.files, hasMcpEndpoints);
|
|
446
563
|
// Filter AI agents
|
|
447
564
|
for (const name of Object.keys(filteredState.agents.agentsMeta)) {
|
|
448
565
|
const agentMeta = filteredState.agents.agentsMeta[name];
|
|
@@ -450,7 +567,7 @@ export function filterInspectorState(state, filters, logger) {
|
|
|
450
567
|
type: 'agent',
|
|
451
568
|
name,
|
|
452
569
|
tags: agentMeta.tags,
|
|
453
|
-
}, logger);
|
|
570
|
+
}, logger, undefined, keptByDeploy);
|
|
454
571
|
if (!matches) {
|
|
455
572
|
delete filteredState.agents.agentsMeta[name];
|
|
456
573
|
}
|
|
@@ -476,7 +593,7 @@ export function filterInspectorState(state, filters, logger) {
|
|
|
476
593
|
type: 'cli',
|
|
477
594
|
name: commandName,
|
|
478
595
|
tags: commandMeta.tags,
|
|
479
|
-
}, logger);
|
|
596
|
+
}, logger, undefined, keptByDeploy);
|
|
480
597
|
if (!matches) {
|
|
481
598
|
delete programMeta.commands[commandName];
|
|
482
599
|
}
|
|
@@ -502,12 +619,10 @@ export function filterInspectorState(state, filters, logger) {
|
|
|
502
619
|
delete filteredState.cli.meta.renderers[rendererName];
|
|
503
620
|
}
|
|
504
621
|
}
|
|
505
|
-
// Repopulate cli.files
|
|
622
|
+
// Repopulate cli.files from surviving program/renderer metadata
|
|
506
623
|
const hasCliPrograms = Object.keys(filteredState.cli.meta.programs).length > 0;
|
|
507
624
|
const hasCliRenderers = Object.keys(filteredState.cli.meta.renderers || {}).length > 0;
|
|
508
|
-
|
|
509
|
-
filteredState.cli.files = new Set(state.cli.files);
|
|
510
|
-
}
|
|
625
|
+
filteredState.cli.files = repopulateFileSetFromMeta(filteredState.cli.meta, state.cli.files, hasCliPrograms || hasCliRenderers);
|
|
511
626
|
// Direct function filtering: functions that match the names/tags/directories
|
|
512
627
|
// filters should be included even if no wiring (HTTP, scheduler, etc.) references them.
|
|
513
628
|
// This ensures standalone RPC-callable functions survive filtering.
|
|
@@ -527,7 +642,7 @@ export function filterInspectorState(state, filters, logger) {
|
|
|
527
642
|
name: funcId,
|
|
528
643
|
tags: funcMeta.tags,
|
|
529
644
|
filePath,
|
|
530
|
-
}, logger);
|
|
645
|
+
}, logger, undefined, keptByDeploy);
|
|
531
646
|
if (matches) {
|
|
532
647
|
filteredState.serviceAggregation.usedFunctions.add(funcId);
|
|
533
648
|
}
|
|
@@ -584,12 +699,18 @@ export function filterInspectorState(state, filters, logger) {
|
|
|
584
699
|
delete filteredState.channels.meta[name];
|
|
585
700
|
}
|
|
586
701
|
}
|
|
587
|
-
// Prune workflow graphs whose function was filtered out
|
|
702
|
+
// Prune workflow graphs whose function was filtered out — UNLESS the
|
|
703
|
+
// workflow's own name is in the filter (covers pikkuWorkflowGraph,
|
|
704
|
+
// which doesn't register a function meta, so its pikkuFuncId never
|
|
705
|
+
// ends up in usedFunctions even when callers explicitly want it).
|
|
706
|
+
const filterNamesSet = new Set(filters.names ?? []);
|
|
588
707
|
const workflowKeys = new Set([
|
|
589
708
|
...Object.keys(filteredState.workflows.graphMeta),
|
|
590
709
|
...Object.keys(filteredState.workflows.meta),
|
|
591
710
|
]);
|
|
592
711
|
for (const name of workflowKeys) {
|
|
712
|
+
if (filterNamesSet.has(name))
|
|
713
|
+
continue;
|
|
593
714
|
const graphMeta = filteredState.workflows.graphMeta[name];
|
|
594
715
|
const workflowMeta = filteredState.workflows.meta[name];
|
|
595
716
|
// Check both graphMeta.pikkuFuncId and meta.pikkuFuncId
|
|
@@ -661,6 +782,13 @@ export function filterInspectorState(state, filters, logger) {
|
|
|
661
782
|
}
|
|
662
783
|
}
|
|
663
784
|
}
|
|
785
|
+
// workflowService internally calls queueService (via queueStepWorker /
|
|
786
|
+
// queueOrchestrator). Any unit that needs workflowService also needs
|
|
787
|
+
// queueService for the transitive enqueue calls — covers
|
|
788
|
+
// workflow-starter, graph-starter, workflow-runner, etc.
|
|
789
|
+
if (filteredState.serviceAggregation.requiredServices.has('workflowService')) {
|
|
790
|
+
filteredState.serviceAggregation.requiredServices.add('queueService');
|
|
791
|
+
}
|
|
664
792
|
// Recalculate requiredServices based on filtered functions/middleware/permissions
|
|
665
793
|
// Need to cast to InspectorState temporarily for aggregateRequiredServices
|
|
666
794
|
const stateForAggregation = filteredState;
|
|
@@ -40,16 +40,24 @@ export const matchesFilters = (filters, params, meta, logger) => {
|
|
|
40
40
|
// If all filter arrays are empty, allow everything
|
|
41
41
|
if ((!filters.names || filters.names.length === 0) &&
|
|
42
42
|
(!filters.tags || filters.tags.length === 0) &&
|
|
43
|
-
(!filters.
|
|
43
|
+
(!filters.wires || filters.wires.length === 0) &&
|
|
44
|
+
(!filters.excludeWires || filters.excludeWires.length === 0) &&
|
|
44
45
|
(!filters.directories || filters.directories.length === 0) &&
|
|
45
46
|
(!filters.httpRoutes || filters.httpRoutes.length === 0) &&
|
|
46
47
|
(!filters.httpMethods || filters.httpMethods.length === 0)) {
|
|
47
48
|
return true;
|
|
48
49
|
}
|
|
49
|
-
// Check
|
|
50
|
-
if (filters.
|
|
51
|
-
if (!filters.
|
|
52
|
-
logger.debug(`⒡ Filtered by
|
|
50
|
+
// Check wire include filter
|
|
51
|
+
if (filters.wires && filters.wires.length > 0) {
|
|
52
|
+
if (!filters.wires.includes(meta.type)) {
|
|
53
|
+
logger.debug(`⒡ Filtered by wire include: ${meta.type}:${meta.name}`);
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Check wire exclude filter
|
|
58
|
+
if (filters.excludeWires && filters.excludeWires.length > 0) {
|
|
59
|
+
if (filters.excludeWires.includes(meta.type)) {
|
|
60
|
+
logger.debug(`⒡ Filtered by wire exclude: ${meta.type}:${meta.name}`);
|
|
53
61
|
return false;
|
|
54
62
|
}
|
|
55
63
|
}
|
|
@@ -4,6 +4,16 @@ import { ErrorCode } from '../error-codes.js';
|
|
|
4
4
|
* Extracts an array of strings from an object property.
|
|
5
5
|
*/
|
|
6
6
|
export declare const getArrayPropertyValue: (obj: ts.ObjectLiteralExpression, propertyName: string) => string[] | null;
|
|
7
|
+
/**
|
|
8
|
+
* Wiring identity fields (`name`, `secretId`, `variableId`, …) are read
|
|
9
|
+
* STATICALLY from source — a const or variable reference is keyed by its
|
|
10
|
+
* identifier text, not its runtime value, so the wiring is silently skipped at
|
|
11
|
+
* runtime (`metadata not found`). If the named property exists but is not an
|
|
12
|
+
* inline literal, raise a fatal diagnostic so the build fails instead.
|
|
13
|
+
*/
|
|
14
|
+
export declare const assertStringLiteralProperty: (obj: ts.ObjectLiteralExpression, propertyName: string, wiringType: string, logger?: {
|
|
15
|
+
critical: (code: ErrorCode, message: string) => void;
|
|
16
|
+
}) => void;
|
|
7
17
|
export declare const getPropertyValue: (obj: ts.ObjectLiteralExpression, propertyName: string) => string | string[] | number | null | boolean;
|
|
8
18
|
/**
|
|
9
19
|
* Extracts common wire metadata (title, tags, summary, description, errors) directly from an object
|
|
@@ -17,6 +17,35 @@ export const getArrayPropertyValue = (obj, propertyName) => {
|
|
|
17
17
|
}
|
|
18
18
|
return null;
|
|
19
19
|
};
|
|
20
|
+
/**
|
|
21
|
+
* Wiring identity fields (`name`, `secretId`, `variableId`, …) are read
|
|
22
|
+
* STATICALLY from source — a const or variable reference is keyed by its
|
|
23
|
+
* identifier text, not its runtime value, so the wiring is silently skipped at
|
|
24
|
+
* runtime (`metadata not found`). If the named property exists but is not an
|
|
25
|
+
* inline literal, raise a fatal diagnostic so the build fails instead.
|
|
26
|
+
*/
|
|
27
|
+
export const assertStringLiteralProperty = (obj, propertyName, wiringType, logger) => {
|
|
28
|
+
const property = obj.properties.find((p) => ts.isPropertyAssignment(p) &&
|
|
29
|
+
ts.isIdentifier(p.name) &&
|
|
30
|
+
p.name.text === propertyName);
|
|
31
|
+
if (!property || !ts.isPropertyAssignment(property)) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const init = property.initializer;
|
|
35
|
+
const isStaticLiteral = ts.isStringLiteral(init) ||
|
|
36
|
+
ts.isNoSubstitutionTemplateLiteral(init) ||
|
|
37
|
+
ts.isNumericLiteral(init);
|
|
38
|
+
if (isStaticLiteral) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const errorMsg = `${wiringType} has a non-literal '${propertyName}': \`${init.getText()}\`. Wiring identity fields must be inline string literals — the inspector reads them statically from source, so a const or variable reference is keyed by its identifier text and the wiring is silently skipped at runtime. Inline the literal instead, e.g. ${propertyName}: 'my-wiring-name'.`;
|
|
42
|
+
if (logger) {
|
|
43
|
+
logger.critical(ErrorCode.NON_LITERAL_WIRE_NAME, errorMsg);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
console.error(errorMsg);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
20
49
|
export const getPropertyValue = (obj, propertyName) => {
|
|
21
50
|
const property = obj.properties.find((p) => ts.isPropertyAssignment(p) &&
|
|
22
51
|
ts.isIdentifier(p.name) &&
|
|
@@ -67,6 +96,7 @@ export const getPropertyValue = (obj, propertyName) => {
|
|
|
67
96
|
*/
|
|
68
97
|
export const getCommonWireMetaData = (obj, wiringType, wiringName, logger) => {
|
|
69
98
|
const metadata = {};
|
|
99
|
+
assertStringLiteralProperty(obj, 'name', wiringType, logger);
|
|
70
100
|
obj.properties.forEach((prop) => {
|
|
71
101
|
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
|
|
72
102
|
const propName = prop.name.text;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { InspectorState, InspectorLogger, InspectorOptions
|
|
1
|
+
import type { InspectorState, InspectorLogger, InspectorOptions } from '../types.js';
|
|
2
2
|
import type { MiddlewareMetadata, PermissionMetadata } from '@pikku/core';
|
|
3
3
|
/**
|
|
4
4
|
* Helper to extract wire-level middleware/permission names from metadata.
|
|
@@ -21,8 +21,7 @@ export declare function computeResolvedIOTypes(state: InspectorState): void;
|
|
|
21
21
|
export declare function computeMiddlewareGroupsMeta(state: InspectorState): void;
|
|
22
22
|
export declare function computePermissionsGroupsMeta(state: InspectorState): void;
|
|
23
23
|
export declare function computeRequiredSchemas(state: InspectorState, options: InspectorOptions): void;
|
|
24
|
-
export declare function validateAgentModels(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, 'typesLookup'
|
|
25
|
-
export declare function validateAgentOverrides(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, 'typesLookup'>, modelConfig?: InspectorModelConfig): void;
|
|
24
|
+
export declare function validateAgentModels(logger: InspectorLogger, state: InspectorState | Omit<InspectorState, 'typesLookup'>): void;
|
|
26
25
|
/**
|
|
27
26
|
* Validates that Zod schemas and wiring side-effects (wireHTTPRoutes,
|
|
28
27
|
* addPermission, addHTTPMiddleware, etc.) do not coexist in the same file.
|
|
@@ -425,35 +425,15 @@ export function computeRequiredSchemas(state, options) {
|
|
|
425
425
|
...Array.from(schemaLookup.keys()),
|
|
426
426
|
]);
|
|
427
427
|
}
|
|
428
|
-
export function validateAgentModels(logger, state
|
|
429
|
-
const aliases = modelConfig?.models ?? {};
|
|
428
|
+
export function validateAgentModels(logger, state) {
|
|
430
429
|
for (const [, meta] of Object.entries(state.agents.agentsMeta)) {
|
|
431
430
|
const model = meta.model;
|
|
432
431
|
if (!model) {
|
|
433
432
|
logger.critical(ErrorCode.MISSING_MODEL, `AI agent '${meta.name}' is missing the 'model' property.`);
|
|
434
433
|
continue;
|
|
435
434
|
}
|
|
436
|
-
if (model.includes('/'))
|
|
437
|
-
|
|
438
|
-
if (!aliases[model]) {
|
|
439
|
-
const available = Object.keys(aliases);
|
|
440
|
-
logger.critical(ErrorCode.INVALID_MODEL, `AI agent '${meta.name}' uses model alias '${model}' which is not defined in pikku.config.json models. ` +
|
|
441
|
-
`Available aliases: ${available.join(', ') || 'none'}`);
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
export function validateAgentOverrides(logger, state, modelConfig) {
|
|
446
|
-
const overrides = modelConfig?.agentOverrides ?? {};
|
|
447
|
-
const aliases = modelConfig?.models ?? {};
|
|
448
|
-
const agentNames = new Set(Object.values(state.agents.agentsMeta).map((m) => m.name));
|
|
449
|
-
for (const [agentName, override] of Object.entries(overrides)) {
|
|
450
|
-
if (!agentNames.has(agentName)) {
|
|
451
|
-
logger.warn(`agentOverrides references unknown agent '${agentName}'`);
|
|
452
|
-
}
|
|
453
|
-
if (override.model &&
|
|
454
|
-
!override.model.includes('/') &&
|
|
455
|
-
!aliases[override.model]) {
|
|
456
|
-
logger.critical(ErrorCode.INVALID_MODEL, `agentOverrides['${agentName}'].model uses alias '${override.model}' which is not defined in models.`);
|
|
435
|
+
if (!model.includes('/')) {
|
|
436
|
+
logger.critical(ErrorCode.INVALID_MODEL, `AI agent '${meta.name}' uses model '${model}', which must be provider-qualified as '<provider>/<model>' (e.g. 'openai/gpt-4').`);
|
|
457
437
|
}
|
|
458
438
|
}
|
|
459
439
|
}
|
|
@@ -2,11 +2,10 @@ import * as ts from 'typescript';
|
|
|
2
2
|
/**
|
|
3
3
|
* Resolve the addon package name from an imported identifier.
|
|
4
4
|
* Checks if the identifier's import module specifier matches any
|
|
5
|
-
* configured addon package
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* addon package.
|
|
5
|
+
* configured addon package — and if the import is relative (because
|
|
6
|
+
* the identifier resolves to a source file inside the addon package
|
|
7
|
+
* itself), walks up to the nearest package.json to obtain the real
|
|
8
|
+
* package name.
|
|
10
9
|
*/
|
|
11
10
|
export declare const resolveAddonName: (identifier: ts.Identifier, checker: ts.TypeChecker, wireAddonDeclarations?: Map<string, {
|
|
12
11
|
package: string;
|