@pikku/inspector 0.12.7 → 0.12.8
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 +20 -0
- package/dist/add/add-ai-agent.js +24 -7
- package/dist/add/add-channel.js +2 -2
- package/dist/add/add-cli.js +13 -10
- package/dist/add/add-file-with-factory.js +22 -5
- package/dist/add/add-functions.js +2 -0
- package/dist/add/add-http-route.js +1 -0
- package/dist/add/add-rpc-invocations.js +2 -2
- package/dist/add/add-workflow.d.ts +5 -0
- package/dist/add/add-workflow.js +19 -1
- package/dist/inspector.js +1 -0
- package/dist/types.d.ts +1 -0
- package/dist/utils/filter-inspector-state.js +204 -5
- package/dist/utils/load-addon-functions-meta.js +47 -0
- package/dist/utils/post-process.js +63 -0
- package/dist/utils/schema-generator.js +124 -33
- package/dist/utils/serialize-inspector-state.d.ts +1 -0
- package/dist/utils/serialize-inspector-state.js +2 -0
- package/dist/visit.js +1 -1
- package/package.json +2 -2
- package/src/add/add-ai-agent.ts +25 -10
- package/src/add/add-channel.ts +2 -2
- package/src/add/add-cli.ts +17 -16
- package/src/add/add-file-with-factory.ts +26 -7
- package/src/add/add-functions.ts +2 -0
- package/src/add/add-http-route.ts +6 -1
- package/src/add/add-queue-worker.ts +5 -1
- package/src/add/add-rpc-invocations.ts +2 -2
- package/src/add/add-workflow.ts +21 -1
- package/src/inspector.ts +1 -0
- package/src/types.ts +1 -0
- package/src/utils/filter-inspector-state.ts +239 -8
- package/src/utils/load-addon-functions-meta.ts +59 -0
- package/src/utils/post-process.ts +74 -0
- package/src/utils/schema-generator.ts +191 -41
- package/src/utils/serialize-inspector-state.ts +3 -0
- package/src/visit.ts +2 -1
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
## 0.12.0
|
|
2
2
|
|
|
3
|
+
## 0.12.8
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 624097e: Add deploy pipeline with provider-agnostic architecture
|
|
8
|
+
|
|
9
|
+
- Add MetaService with explicit typed API, absorb WiringService reads
|
|
10
|
+
- Add deployment service, traceId propagation, scoped logger
|
|
11
|
+
- Rewrite analyzer: one function = one worker, gateways dispatch via RPC
|
|
12
|
+
- Add Cloudflare deploy provider with plan/apply commands
|
|
13
|
+
- Add per-unit filtered codegen for deploy pipeline
|
|
14
|
+
- Skip missing metadata in wiring registration for deploy units
|
|
15
|
+
- Fix schema coercion crash when schema has no properties
|
|
16
|
+
- Fix E2E codegen: double-pass resolves cross-package Zod type imports
|
|
17
|
+
|
|
18
|
+
- Updated dependencies [9e8605f]
|
|
19
|
+
- Updated dependencies [624097e]
|
|
20
|
+
- Updated dependencies [7ab3243]
|
|
21
|
+
- @pikku/core@0.12.15
|
|
22
|
+
|
|
3
23
|
## 0.12.7
|
|
4
24
|
|
|
5
25
|
### Patch Changes
|
package/dist/add/add-ai-agent.js
CHANGED
|
@@ -33,7 +33,7 @@ function resolveToolReferences(obj, checker, agentName, logger) {
|
|
|
33
33
|
continue;
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
|
-
if (calleeName === '
|
|
36
|
+
if (calleeName === 'ref') {
|
|
37
37
|
const [firstArg] = element.arguments;
|
|
38
38
|
if (firstArg && ts.isStringLiteral(firstArg)) {
|
|
39
39
|
resolved.push(firstArg.text);
|
|
@@ -163,11 +163,12 @@ export const addAIAgent = (logger, node, checker, state, options) => {
|
|
|
163
163
|
if (disabled)
|
|
164
164
|
return;
|
|
165
165
|
const modelValue = getPropertyValue(obj, 'model');
|
|
166
|
-
const
|
|
166
|
+
const roleValue = getPropertyValue(obj, 'role');
|
|
167
|
+
const personalityValue = getPropertyValue(obj, 'personality');
|
|
168
|
+
const goalValue = getPropertyValue(obj, 'goal');
|
|
167
169
|
const maxStepsValue = getPropertyValue(obj, 'maxSteps');
|
|
168
170
|
const temperatureValue = getPropertyValue(obj, 'temperature');
|
|
169
171
|
const toolChoiceValue = getPropertyValue(obj, 'toolChoice');
|
|
170
|
-
const dynamicWorkflowsValue = getPropertyValue(obj, 'dynamicWorkflows');
|
|
171
172
|
const toolsValue = resolveToolReferences(obj, checker, nameValue || '', logger);
|
|
172
173
|
if (toolsValue) {
|
|
173
174
|
for (const toolName of toolsValue) {
|
|
@@ -291,10 +292,14 @@ export const addAIAgent = (logger, node, checker, state, options) => {
|
|
|
291
292
|
state.agents.agentsMeta[agentKey] = {
|
|
292
293
|
name: nameValue,
|
|
293
294
|
description,
|
|
294
|
-
|
|
295
|
+
role: roleValue || undefined,
|
|
296
|
+
personality: personalityValue || undefined,
|
|
297
|
+
goal: goalValue || '',
|
|
295
298
|
model: modelValue || '',
|
|
296
299
|
summary,
|
|
297
300
|
errors,
|
|
301
|
+
sourceFile: node.getSourceFile().fileName,
|
|
302
|
+
exportedName: exportedName || undefined,
|
|
298
303
|
...(maxStepsValue !== null && { maxSteps: maxStepsValue }),
|
|
299
304
|
...(temperatureValue !== null && { temperature: temperatureValue }),
|
|
300
305
|
...(toolChoiceValue !== null && {
|
|
@@ -302,9 +307,6 @@ export const addAIAgent = (logger, node, checker, state, options) => {
|
|
|
302
307
|
}),
|
|
303
308
|
...(toolsValue !== null && { tools: toolsValue }),
|
|
304
309
|
...(agentsValue !== null && { agents: agentsValue }),
|
|
305
|
-
...(dynamicWorkflowsValue !== null && {
|
|
306
|
-
dynamicWorkflows: dynamicWorkflowsValue,
|
|
307
|
-
}),
|
|
308
310
|
tags,
|
|
309
311
|
inputSchema,
|
|
310
312
|
outputSchema,
|
|
@@ -314,5 +316,20 @@ export const addAIAgent = (logger, node, checker, state, options) => {
|
|
|
314
316
|
aiMiddleware,
|
|
315
317
|
permissions,
|
|
316
318
|
};
|
|
319
|
+
// AI agent functions require platform services that aren't visible
|
|
320
|
+
// through parameter destructuring
|
|
321
|
+
const funcMeta = state.functions.meta[agentKey];
|
|
322
|
+
if (funcMeta?.services) {
|
|
323
|
+
for (const svc of [
|
|
324
|
+
'aiStorage',
|
|
325
|
+
'aiRunState',
|
|
326
|
+
'agentRunService',
|
|
327
|
+
'aiAgentRunner',
|
|
328
|
+
]) {
|
|
329
|
+
if (!funcMeta.services.services.includes(svc)) {
|
|
330
|
+
funcMeta.services.services.push(svc);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
317
334
|
}
|
|
318
335
|
};
|
package/dist/add/add-channel.js
CHANGED
|
@@ -65,8 +65,8 @@ function getHandlerNameFromExpression(expr, checker, rootDir) {
|
|
|
65
65
|
}
|
|
66
66
|
// Handle call expressions
|
|
67
67
|
if (ts.isCallExpression(expr)) {
|
|
68
|
-
// Handle
|
|
69
|
-
if (ts.isIdentifier(expr.expression) && expr.expression.text === '
|
|
68
|
+
// Handle ref('name') calls
|
|
69
|
+
if (ts.isIdentifier(expr.expression) && expr.expression.text === 'ref') {
|
|
70
70
|
const [firstArg] = expr.arguments;
|
|
71
71
|
if (firstArg && ts.isStringLiteral(firstArg)) {
|
|
72
72
|
return firstArg.text;
|
package/dist/add/add-cli.js
CHANGED
|
@@ -206,22 +206,25 @@ function processCommand(logger, inspectorState, options, name, node, sourceFile,
|
|
|
206
206
|
if (propName === 'func') {
|
|
207
207
|
if (ts.isCallExpression(prop.initializer) &&
|
|
208
208
|
ts.isIdentifier(prop.initializer.expression) &&
|
|
209
|
-
prop.initializer.expression.text === '
|
|
209
|
+
prop.initializer.expression.text === 'ref') {
|
|
210
210
|
const [firstArg] = prop.initializer.arguments;
|
|
211
211
|
if (!firstArg || !ts.isStringLiteral(firstArg)) {
|
|
212
|
-
throw new Error(`
|
|
212
|
+
throw new Error(`ref() call requires a string literal argument`);
|
|
213
213
|
}
|
|
214
214
|
pikkuFuncId = firstArg.text;
|
|
215
|
-
const addonNamespace = pikkuFuncId.
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
215
|
+
const addonNamespace = pikkuFuncId.includes(':')
|
|
216
|
+
? pikkuFuncId.split(':')[0]
|
|
217
|
+
: null;
|
|
218
|
+
if (addonNamespace) {
|
|
219
|
+
if (!inspectorState.rpc.wireAddonDeclarations.has(addonNamespace)) {
|
|
220
|
+
throw new Error(`Unknown addon namespace "${addonNamespace}" in "${pikkuFuncId}": no matching wireAddonDeclarations entry found`);
|
|
221
|
+
}
|
|
221
222
|
}
|
|
222
223
|
meta.pikkuFuncId = pikkuFuncId;
|
|
223
|
-
|
|
224
|
-
|
|
224
|
+
if (addonNamespace) {
|
|
225
|
+
meta.packageName =
|
|
226
|
+
inspectorState.rpc.wireAddonDeclarations.get(addonNamespace).package;
|
|
227
|
+
}
|
|
225
228
|
}
|
|
226
229
|
else {
|
|
227
230
|
pikkuFuncId = extractFunctionName(prop.initializer, typeChecker, inspectorState.rootDir).pikkuFuncId;
|
|
@@ -40,10 +40,7 @@ export const addFileWithFactory = (node, checker, methods = new Map(), expectedT
|
|
|
40
40
|
typePath: typeDeclarationPath,
|
|
41
41
|
});
|
|
42
42
|
methods.set(fileName, variables);
|
|
43
|
-
|
|
44
|
-
if (expectedTypeName === 'CreateWireServices' &&
|
|
45
|
-
state &&
|
|
46
|
-
callExpression.arguments.length > 0) {
|
|
43
|
+
if (state && callExpression.arguments.length > 0) {
|
|
47
44
|
const firstArg = callExpression.arguments[0];
|
|
48
45
|
let functionNode;
|
|
49
46
|
if (ts.isArrowFunction(firstArg)) {
|
|
@@ -52,10 +49,30 @@ export const addFileWithFactory = (node, checker, methods = new Map(), expectedT
|
|
|
52
49
|
else if (ts.isFunctionExpression(firstArg)) {
|
|
53
50
|
functionNode = firstArg;
|
|
54
51
|
}
|
|
55
|
-
|
|
52
|
+
// Extract singleton services for CreateWireServices factories
|
|
53
|
+
if (expectedTypeName === 'CreateWireServices' && functionNode) {
|
|
56
54
|
const servicesMeta = extractServicesFromFunction(functionNode);
|
|
57
55
|
state.wireServicesMeta.set(variableName, servicesMeta.services);
|
|
58
56
|
}
|
|
57
|
+
// Extract existing services an addon needs from the parent
|
|
58
|
+
// (second parameter of pikkuAddonServices callback)
|
|
59
|
+
if (wrapperFunctionName === 'pikkuAddonServices' &&
|
|
60
|
+
functionNode &&
|
|
61
|
+
functionNode.parameters.length >= 2) {
|
|
62
|
+
const secondParam = functionNode.parameters[1];
|
|
63
|
+
if (secondParam && ts.isObjectBindingPattern(secondParam.name)) {
|
|
64
|
+
for (const elem of secondParam.name.elements) {
|
|
65
|
+
const name = elem.propertyName && ts.isIdentifier(elem.propertyName)
|
|
66
|
+
? elem.propertyName.text
|
|
67
|
+
: ts.isIdentifier(elem.name)
|
|
68
|
+
? elem.name.text
|
|
69
|
+
: undefined;
|
|
70
|
+
if (name) {
|
|
71
|
+
state.addonRequiredParentServices.push(name);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
59
76
|
}
|
|
60
77
|
return; // Early return since we found a match
|
|
61
78
|
}
|
|
@@ -594,6 +594,8 @@ export const addFunctions = (logger, node, checker, state, options) => {
|
|
|
594
594
|
middleware,
|
|
595
595
|
permissions,
|
|
596
596
|
isDirectFunction,
|
|
597
|
+
sourceFile: node.getSourceFile().fileName,
|
|
598
|
+
exportedName: exportedName || undefined,
|
|
597
599
|
};
|
|
598
600
|
// Populate node metadata if node config is present
|
|
599
601
|
if (nodeDisplayName && nodeCategory && nodeType) {
|
|
@@ -174,6 +174,7 @@ export function registerHTTPRoute({ obj, state, checker, logger, sourceFile, bas
|
|
|
174
174
|
pikkuFuncId: funcName,
|
|
175
175
|
...(packageName && { packageName }),
|
|
176
176
|
route: fullRoute,
|
|
177
|
+
sourceFile: sourceFile.fileName,
|
|
177
178
|
method: method,
|
|
178
179
|
params: params.length > 0 ? params : undefined,
|
|
179
180
|
query: query.length > 0 ? query : undefined,
|
|
@@ -19,8 +19,8 @@ export function addRPCInvocations(node, state, logger) {
|
|
|
19
19
|
// Look for call expressions: addon('ext:hello') or rpc.invoke('...')
|
|
20
20
|
if (ts.isCallExpression(node)) {
|
|
21
21
|
const { expression, arguments: args } = node;
|
|
22
|
-
// Check for
|
|
23
|
-
if (ts.isIdentifier(expression) && expression.text === '
|
|
22
|
+
// Check for ref('name') calls
|
|
23
|
+
if (ts.isIdentifier(expression) && expression.text === 'ref') {
|
|
24
24
|
const [firstArg] = args;
|
|
25
25
|
if (firstArg && ts.isStringLiteral(firstArg)) {
|
|
26
26
|
const functionRef = firstArg.text;
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import type { AddWiring } from '../types.js';
|
|
2
|
+
import type { WorkflowStepMeta } from '@pikku/core/workflow';
|
|
3
|
+
/**
|
|
4
|
+
* Recursively collect all RPC names from workflow steps
|
|
5
|
+
*/
|
|
6
|
+
export declare function collectInvokedRPCs(steps: WorkflowStepMeta[], rpcs: Set<string>): void;
|
|
2
7
|
/**
|
|
3
8
|
* Inspector for pikkuWorkflow() and pikkuSimpleWorkflow() calls
|
|
4
9
|
* Detects workflow registration and extracts metadata
|
package/dist/add/add-workflow.js
CHANGED
|
@@ -43,7 +43,7 @@ function hasInlineSteps(steps) {
|
|
|
43
43
|
/**
|
|
44
44
|
* Recursively collect all RPC names from workflow steps
|
|
45
45
|
*/
|
|
46
|
-
function collectInvokedRPCs(steps, rpcs) {
|
|
46
|
+
export function collectInvokedRPCs(steps, rpcs) {
|
|
47
47
|
for (const step of steps) {
|
|
48
48
|
if (step.type === 'rpc' && step.rpcName) {
|
|
49
49
|
rpcs.add(step.rpcName);
|
|
@@ -183,6 +183,7 @@ export const addWorkflow = (logger, node, checker, state) => {
|
|
|
183
183
|
let description;
|
|
184
184
|
let errors;
|
|
185
185
|
let inline;
|
|
186
|
+
let expose;
|
|
186
187
|
if (ts.isObjectLiteralExpression(firstArg)) {
|
|
187
188
|
const metadata = getCommonWireMetaData(firstArg, 'Workflow', workflowName, logger);
|
|
188
189
|
if (metadata.disabled)
|
|
@@ -195,6 +196,7 @@ export const addWorkflow = (logger, node, checker, state) => {
|
|
|
195
196
|
if (inlineProp === true) {
|
|
196
197
|
inline = true;
|
|
197
198
|
}
|
|
199
|
+
expose = getPropertyValue(firstArg, 'expose');
|
|
198
200
|
}
|
|
199
201
|
// Validate that we got a valid function
|
|
200
202
|
if (ts.isObjectLiteralExpression(firstArg) &&
|
|
@@ -278,5 +280,21 @@ export const addWorkflow = (logger, node, checker, state) => {
|
|
|
278
280
|
errors,
|
|
279
281
|
tags,
|
|
280
282
|
inline,
|
|
283
|
+
expose,
|
|
281
284
|
};
|
|
285
|
+
// Workflow functions require platform services that aren't visible
|
|
286
|
+
// through parameter destructuring (they're accessed via workflow.do/sleep)
|
|
287
|
+
const funcMeta = state.functions.meta[pikkuFuncId];
|
|
288
|
+
if (funcMeta?.services) {
|
|
289
|
+
for (const svc of [
|
|
290
|
+
'workflowService',
|
|
291
|
+
'workflowRunService',
|
|
292
|
+
'schedulerService',
|
|
293
|
+
'queueService',
|
|
294
|
+
]) {
|
|
295
|
+
if (!funcMeta.services.services.includes(svc)) {
|
|
296
|
+
funcMeta.services.services.push(svc);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
282
300
|
};
|
package/dist/inspector.js
CHANGED
|
@@ -28,6 +28,7 @@ export function getInitialInspectorState(rootDir) {
|
|
|
28
28
|
singletonServicesFactories: new Map(),
|
|
29
29
|
wireServicesFactories: new Map(),
|
|
30
30
|
wireServicesMeta: new Map(),
|
|
31
|
+
addonRequiredParentServices: [],
|
|
31
32
|
configFactories: new Map(),
|
|
32
33
|
filesAndMethods: {},
|
|
33
34
|
filesAndMethodsErrors: new Map(),
|
package/dist/types.d.ts
CHANGED
|
@@ -264,6 +264,7 @@ export interface InspectorState {
|
|
|
264
264
|
singletonServicesFactories: PathToNameAndType;
|
|
265
265
|
wireServicesFactories: PathToNameAndType;
|
|
266
266
|
wireServicesMeta: Map<string, string[]>;
|
|
267
|
+
addonRequiredParentServices: string[];
|
|
267
268
|
configFactories: PathToNameAndType;
|
|
268
269
|
filesAndMethods: InspectorFilesAndMethods;
|
|
269
270
|
filesAndMethodsErrors: Map<string, PathToNameAndType>;
|
|
@@ -139,6 +139,10 @@ export function filterInspectorState(state, filters, logger) {
|
|
|
139
139
|
(!filters.httpMethods || filters.httpMethods.length === 0))) {
|
|
140
140
|
return state;
|
|
141
141
|
}
|
|
142
|
+
// Snapshot the original workflow graph meta before filtering prunes it
|
|
143
|
+
const originalGraphMeta = {
|
|
144
|
+
...(state.workflows?.graphMeta ?? {}),
|
|
145
|
+
};
|
|
142
146
|
// Create a shallow copy with new Maps/Sets to avoid mutating the original
|
|
143
147
|
const filteredState = {
|
|
144
148
|
...state,
|
|
@@ -149,11 +153,21 @@ export function filterInspectorState(state, filters, logger) {
|
|
|
149
153
|
usedMiddleware: new Set(),
|
|
150
154
|
usedPermissions: new Set(),
|
|
151
155
|
},
|
|
156
|
+
functions: {
|
|
157
|
+
...state.functions,
|
|
158
|
+
meta: JSON.parse(JSON.stringify(state.functions.meta)), // Deep clone to avoid mutating original
|
|
159
|
+
files: new Map(state.functions.files),
|
|
160
|
+
},
|
|
152
161
|
http: {
|
|
153
162
|
...state.http,
|
|
154
163
|
meta: JSON.parse(JSON.stringify(state.http.meta)), // Deep clone metadata
|
|
155
164
|
files: new Set(), // Will be repopulated with filtered files
|
|
156
165
|
},
|
|
166
|
+
workflows: {
|
|
167
|
+
...state.workflows,
|
|
168
|
+
graphMeta: JSON.parse(JSON.stringify(state.workflows?.graphMeta ?? {})),
|
|
169
|
+
meta: JSON.parse(JSON.stringify(state.workflows?.meta ?? {})),
|
|
170
|
+
},
|
|
157
171
|
channels: {
|
|
158
172
|
...state.channels,
|
|
159
173
|
meta: JSON.parse(JSON.stringify(state.channels.meta)),
|
|
@@ -186,6 +200,14 @@ export function filterInspectorState(state, filters, logger) {
|
|
|
186
200
|
agentsMeta: JSON.parse(JSON.stringify(state.agents?.agentsMeta ?? {})),
|
|
187
201
|
files: new Map(),
|
|
188
202
|
},
|
|
203
|
+
rpc: {
|
|
204
|
+
...state.rpc,
|
|
205
|
+
internalMeta: { ...state.rpc.internalMeta }, // Clone to avoid mutating original
|
|
206
|
+
internalFiles: new Map(state.rpc.internalFiles),
|
|
207
|
+
exposedMeta: { ...state.rpc.exposedMeta },
|
|
208
|
+
exposedFiles: new Map(state.rpc.exposedFiles),
|
|
209
|
+
invokedFunctions: new Set(state.rpc.invokedFunctions),
|
|
210
|
+
},
|
|
189
211
|
cli: {
|
|
190
212
|
...state.cli,
|
|
191
213
|
meta: JSON.parse(JSON.stringify(state.cli.meta)),
|
|
@@ -216,16 +238,33 @@ export function filterInspectorState(state, filters, logger) {
|
|
|
216
238
|
// Track used functions/middleware/permissions
|
|
217
239
|
if (routeMeta.pikkuFuncId) {
|
|
218
240
|
filteredState.serviceAggregation.usedFunctions.add(routeMeta.pikkuFuncId);
|
|
241
|
+
// For workflow/agent routes, also add the base name
|
|
242
|
+
// so the workflow/agent definition survives pruning
|
|
243
|
+
const colonIdx = routeMeta.pikkuFuncId.indexOf(':');
|
|
244
|
+
if (colonIdx !== -1) {
|
|
245
|
+
filteredState.serviceAggregation.usedFunctions.add(routeMeta.pikkuFuncId.slice(colonIdx + 1));
|
|
246
|
+
}
|
|
219
247
|
}
|
|
220
248
|
extractWireNames(routeMeta.middleware).forEach((name) => filteredState.serviceAggregation.usedMiddleware.add(name));
|
|
221
249
|
extractWireNames(routeMeta.permissions).forEach((name) => filteredState.serviceAggregation.usedPermissions.add(name));
|
|
222
250
|
}
|
|
223
251
|
}
|
|
224
252
|
}
|
|
225
|
-
// Repopulate http.files
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
253
|
+
// Repopulate http.files with only files that have surviving routes
|
|
254
|
+
for (const method of Object.keys(filteredState.http.meta)) {
|
|
255
|
+
const routes = filteredState.http.meta[method];
|
|
256
|
+
for (const routeMeta of Object.values(routes)) {
|
|
257
|
+
if (routeMeta.sourceFile) {
|
|
258
|
+
filteredState.http.files.add(routeMeta.sourceFile);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
// Fallback: if no sourceFile info available but routes exist, include all files
|
|
263
|
+
if (filteredState.http.files.size === 0) {
|
|
264
|
+
const hasHttpRoutes = Object.values(filteredState.http.meta).some((routes) => Object.keys(routes).length > 0);
|
|
265
|
+
if (hasHttpRoutes) {
|
|
266
|
+
filteredState.http.files = new Set(state.http.files);
|
|
267
|
+
}
|
|
229
268
|
}
|
|
230
269
|
// Filter channels
|
|
231
270
|
for (const name of Object.keys(filteredState.channels.meta)) {
|
|
@@ -239,9 +278,30 @@ export function filterInspectorState(state, filters, logger) {
|
|
|
239
278
|
delete filteredState.channels.meta[name];
|
|
240
279
|
}
|
|
241
280
|
else {
|
|
242
|
-
|
|
281
|
+
// Add all functions referenced by this channel
|
|
282
|
+
if ('pikkuFuncId' in channelMeta && channelMeta.pikkuFuncId) {
|
|
243
283
|
filteredState.serviceAggregation.usedFunctions.add(channelMeta.pikkuFuncId);
|
|
244
284
|
}
|
|
285
|
+
if (channelMeta.connect?.pikkuFuncId) {
|
|
286
|
+
filteredState.serviceAggregation.usedFunctions.add(channelMeta.connect.pikkuFuncId);
|
|
287
|
+
}
|
|
288
|
+
if (channelMeta.disconnect?.pikkuFuncId) {
|
|
289
|
+
filteredState.serviceAggregation.usedFunctions.add(channelMeta.disconnect.pikkuFuncId);
|
|
290
|
+
}
|
|
291
|
+
if (channelMeta.message?.pikkuFuncId) {
|
|
292
|
+
filteredState.serviceAggregation.usedFunctions.add(channelMeta.message.pikkuFuncId);
|
|
293
|
+
}
|
|
294
|
+
if (channelMeta.messageWirings) {
|
|
295
|
+
for (const groupKey of Object.keys(channelMeta.messageWirings)) {
|
|
296
|
+
const commands = channelMeta.messageWirings[groupKey];
|
|
297
|
+
for (const cmdKey of Object.keys(commands)) {
|
|
298
|
+
const wiring = commands[cmdKey];
|
|
299
|
+
if (wiring.pikkuFuncId) {
|
|
300
|
+
filteredState.serviceAggregation.usedFunctions.add(wiring.pikkuFuncId);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
245
305
|
extractWireNames(channelMeta.middleware).forEach((name) => filteredState.serviceAggregation.usedMiddleware.add(name));
|
|
246
306
|
extractWireNames(channelMeta.permissions).forEach((name) => filteredState.serviceAggregation.usedPermissions.add(name));
|
|
247
307
|
}
|
|
@@ -307,6 +367,10 @@ export function filterInspectorState(state, filters, logger) {
|
|
|
307
367
|
else {
|
|
308
368
|
if (workerMeta.pikkuFuncId) {
|
|
309
369
|
filteredState.serviceAggregation.usedFunctions.add(workerMeta.pikkuFuncId);
|
|
370
|
+
const colonIdx = workerMeta.pikkuFuncId.indexOf(':');
|
|
371
|
+
if (colonIdx !== -1) {
|
|
372
|
+
filteredState.serviceAggregation.usedFunctions.add(workerMeta.pikkuFuncId.slice(colonIdx + 1));
|
|
373
|
+
}
|
|
310
374
|
}
|
|
311
375
|
extractWireNames(workerMeta.middleware).forEach((name) => filteredState.serviceAggregation.usedMiddleware.add(name));
|
|
312
376
|
}
|
|
@@ -444,6 +508,30 @@ export function filterInspectorState(state, filters, logger) {
|
|
|
444
508
|
if (hasCliPrograms || hasCliRenderers) {
|
|
445
509
|
filteredState.cli.files = new Set(state.cli.files);
|
|
446
510
|
}
|
|
511
|
+
// Direct function filtering: functions that match the names/tags/directories
|
|
512
|
+
// filters should be included even if no wiring (HTTP, scheduler, etc.) references them.
|
|
513
|
+
// This ensures standalone RPC-callable functions survive filtering.
|
|
514
|
+
// Only run when function-level filters are active — httpRoutes/httpMethods work
|
|
515
|
+
// through the HTTP wiring pass which already adds the right functions.
|
|
516
|
+
const hasFunctionLevelFilters = (filters.names && filters.names.length > 0) ||
|
|
517
|
+
(filters.tags && filters.tags.length > 0) ||
|
|
518
|
+
(filters.directories && filters.directories.length > 0);
|
|
519
|
+
for (const funcId of Object.keys(filteredState.functions.meta)) {
|
|
520
|
+
if (!hasFunctionLevelFilters)
|
|
521
|
+
break;
|
|
522
|
+
const funcMeta = filteredState.functions.meta[funcId];
|
|
523
|
+
const funcFile = filteredState.functions.files.get(funcId);
|
|
524
|
+
const filePath = funcFile?.path;
|
|
525
|
+
const matches = matchesFilters(filters, {
|
|
526
|
+
type: 'rpc',
|
|
527
|
+
name: funcId,
|
|
528
|
+
tags: funcMeta.tags,
|
|
529
|
+
filePath,
|
|
530
|
+
}, logger);
|
|
531
|
+
if (matches) {
|
|
532
|
+
filteredState.serviceAggregation.usedFunctions.add(funcId);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
447
535
|
// Post-filter version expansion: include all versions of matched functions
|
|
448
536
|
const includedBaseNames = new Set();
|
|
449
537
|
for (const funcId of filteredState.serviceAggregation.usedFunctions) {
|
|
@@ -458,6 +546,117 @@ export function filterInspectorState(state, filters, logger) {
|
|
|
458
546
|
}
|
|
459
547
|
}
|
|
460
548
|
}
|
|
549
|
+
// Prune functions.meta and functions.files to only include used functions
|
|
550
|
+
if (filteredState.serviceAggregation.usedFunctions.size > 0) {
|
|
551
|
+
for (const funcId of Object.keys(filteredState.functions.meta)) {
|
|
552
|
+
if (!filteredState.serviceAggregation.usedFunctions.has(funcId)) {
|
|
553
|
+
delete filteredState.functions.meta[funcId];
|
|
554
|
+
filteredState.functions.files.delete(funcId);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
// Prune channels whose functions were filtered out
|
|
558
|
+
for (const name of Object.keys(filteredState.channels.meta)) {
|
|
559
|
+
const channelMeta = filteredState.channels.meta[name];
|
|
560
|
+
// Check if any of the channel's functions are in the used set
|
|
561
|
+
const channelFuncIds = [];
|
|
562
|
+
if (channelMeta.connect?.pikkuFuncId)
|
|
563
|
+
channelFuncIds.push(channelMeta.connect.pikkuFuncId);
|
|
564
|
+
if (channelMeta.disconnect?.pikkuFuncId)
|
|
565
|
+
channelFuncIds.push(channelMeta.disconnect.pikkuFuncId);
|
|
566
|
+
if (channelMeta.message?.pikkuFuncId)
|
|
567
|
+
channelFuncIds.push(channelMeta.message.pikkuFuncId);
|
|
568
|
+
if (channelMeta.messageWirings) {
|
|
569
|
+
for (const groupKey of Object.keys(channelMeta.messageWirings)) {
|
|
570
|
+
const commands = channelMeta.messageWirings[groupKey];
|
|
571
|
+
for (const cmdKey of Object.keys(commands)) {
|
|
572
|
+
const wiring = commands[cmdKey];
|
|
573
|
+
if (wiring.pikkuFuncId)
|
|
574
|
+
channelFuncIds.push(wiring.pikkuFuncId);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
const hasUsedFunc = channelFuncIds.some((id) => filteredState.serviceAggregation.usedFunctions.has(id));
|
|
579
|
+
if (channelFuncIds.length > 0 && !hasUsedFunc) {
|
|
580
|
+
delete filteredState.channels.meta[name];
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
// Prune workflow graphs whose function was filtered out
|
|
584
|
+
const workflowKeys = new Set([
|
|
585
|
+
...Object.keys(filteredState.workflows.graphMeta),
|
|
586
|
+
...Object.keys(filteredState.workflows.meta),
|
|
587
|
+
]);
|
|
588
|
+
for (const name of workflowKeys) {
|
|
589
|
+
const graphMeta = filteredState.workflows.graphMeta[name];
|
|
590
|
+
const workflowMeta = filteredState.workflows.meta[name];
|
|
591
|
+
// Check both graphMeta.pikkuFuncId and meta.pikkuFuncId
|
|
592
|
+
const pikkuFuncId = graphMeta?.pikkuFuncId ?? workflowMeta?.pikkuFuncId;
|
|
593
|
+
if (pikkuFuncId &&
|
|
594
|
+
!filteredState.serviceAggregation.usedFunctions.has(pikkuFuncId)) {
|
|
595
|
+
delete filteredState.workflows.graphMeta[name];
|
|
596
|
+
delete filteredState.workflows.meta[name];
|
|
597
|
+
}
|
|
598
|
+
else if (!pikkuFuncId) {
|
|
599
|
+
// No function ID found — prune it
|
|
600
|
+
delete filteredState.workflows.graphMeta[name];
|
|
601
|
+
delete filteredState.workflows.meta[name];
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
// Prune RPC meta to only include entries whose target function survived
|
|
605
|
+
const survivingFuncIds = new Set(Object.keys(filteredState.functions.meta));
|
|
606
|
+
for (const key of Object.keys(filteredState.rpc.internalMeta)) {
|
|
607
|
+
const targetFuncId = filteredState.rpc.internalMeta[key];
|
|
608
|
+
if (!survivingFuncIds.has(targetFuncId) && !survivingFuncIds.has(key)) {
|
|
609
|
+
delete filteredState.rpc.internalMeta[key];
|
|
610
|
+
filteredState.rpc.internalFiles.delete(key);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
for (const key of Object.keys(filteredState.rpc.exposedMeta)) {
|
|
614
|
+
const targetFuncId = filteredState.rpc.exposedMeta[key];
|
|
615
|
+
if (!survivingFuncIds.has(targetFuncId) && !survivingFuncIds.has(key)) {
|
|
616
|
+
delete filteredState.rpc.exposedMeta[key];
|
|
617
|
+
filteredState.rpc.exposedFiles.delete(key);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
// Prune invokedFunctions to match surviving functions
|
|
621
|
+
for (const funcId of filteredState.rpc.invokedFunctions) {
|
|
622
|
+
if (!survivingFuncIds.has(funcId)) {
|
|
623
|
+
filteredState.rpc.invokedFunctions.delete(funcId);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
// Recompute requiredSchemas based on pruned functions.meta
|
|
628
|
+
if (filteredState.serviceAggregation.usedFunctions.size > 0) {
|
|
629
|
+
const prunedSchemas = new Set();
|
|
630
|
+
for (const funcMeta of Object.values(filteredState.functions.meta)) {
|
|
631
|
+
if (funcMeta.inputs?.[0])
|
|
632
|
+
prunedSchemas.add(funcMeta.inputs[0]);
|
|
633
|
+
if (funcMeta.outputs?.[0])
|
|
634
|
+
prunedSchemas.add(funcMeta.outputs[0]);
|
|
635
|
+
}
|
|
636
|
+
filteredState.requiredSchemas = prunedSchemas;
|
|
637
|
+
}
|
|
638
|
+
// If any surviving function is a non-inline workflow step, the unit needs
|
|
639
|
+
// workflowService + queueService even though the function doesn't use them.
|
|
640
|
+
// Check the ORIGINAL graph meta (before filtering pruned it).
|
|
641
|
+
const survivingFuncIds = new Set(Object.keys(filteredState.functions.meta));
|
|
642
|
+
// Use the snapshot taken before filtering
|
|
643
|
+
for (const graph of Object.values(originalGraphMeta)) {
|
|
644
|
+
if (!graph.nodes)
|
|
645
|
+
continue;
|
|
646
|
+
for (const node of Object.values(graph.nodes)) {
|
|
647
|
+
if (!('rpcName' in node) || !node.rpcName)
|
|
648
|
+
continue;
|
|
649
|
+
const rpcName = node.rpcName;
|
|
650
|
+
if (!survivingFuncIds.has(rpcName))
|
|
651
|
+
continue;
|
|
652
|
+
const isInline = node.options?.async !== true &&
|
|
653
|
+
graph.inline === true;
|
|
654
|
+
if (!isInline) {
|
|
655
|
+
filteredState.serviceAggregation.requiredServices.add('workflowService');
|
|
656
|
+
filteredState.serviceAggregation.requiredServices.add('queueService');
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
461
660
|
// Recalculate requiredServices based on filtered functions/middleware/permissions
|
|
462
661
|
// Need to cast to InspectorState temporarily for aggregateRequiredServices
|
|
463
662
|
const stateForAggregation = filteredState;
|
|
@@ -34,6 +34,53 @@ export async function loadAddonFunctionsMeta(logger, state) {
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
|
+
// Load addon secrets meta
|
|
38
|
+
try {
|
|
39
|
+
const secretsMetaPath = require.resolve(`${decl.package}/.pikku/secrets/pikku-secrets-meta.gen.json`);
|
|
40
|
+
const secretsRaw = await readFile(secretsMetaPath, 'utf-8');
|
|
41
|
+
const secretsMeta = JSON.parse(secretsRaw);
|
|
42
|
+
for (const [key, def] of Object.entries(secretsMeta)) {
|
|
43
|
+
const existing = state.secrets.definitions.find((d) => d.name === key);
|
|
44
|
+
if (!existing) {
|
|
45
|
+
state.secrets.definitions.push(def);
|
|
46
|
+
logger.debug(`Loaded addon secret '${key}' from ${decl.package}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// No secrets meta — that's fine
|
|
52
|
+
}
|
|
53
|
+
// Load addon variables meta
|
|
54
|
+
try {
|
|
55
|
+
const variablesMetaPath = require.resolve(`${decl.package}/.pikku/variables/pikku-variables-meta.gen.json`);
|
|
56
|
+
const variablesRaw = await readFile(variablesMetaPath, 'utf-8');
|
|
57
|
+
const variablesMeta = JSON.parse(variablesRaw);
|
|
58
|
+
for (const [key, def] of Object.entries(variablesMeta)) {
|
|
59
|
+
const existing = state.variables.definitions.find((d) => d.name === key);
|
|
60
|
+
if (!existing) {
|
|
61
|
+
state.variables.definitions.push(def);
|
|
62
|
+
logger.debug(`Loaded addon variable '${key}' from ${decl.package}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// No variables meta — that's fine
|
|
68
|
+
}
|
|
69
|
+
// Load addon required parent services from pikku-services.gen
|
|
70
|
+
try {
|
|
71
|
+
const servicesGenPath = require.resolve(`${decl.package}/.pikku/pikku-services.gen.js`);
|
|
72
|
+
const servicesModule = await import(servicesGenPath);
|
|
73
|
+
if (servicesModule.requiredParentServices &&
|
|
74
|
+
Array.isArray(servicesModule.requiredParentServices)) {
|
|
75
|
+
for (const service of servicesModule.requiredParentServices) {
|
|
76
|
+
state.addonRequiredParentServices.push(service);
|
|
77
|
+
}
|
|
78
|
+
logger.debug(`Loaded ${servicesModule.requiredParentServices.length} required parent services for '${namespace}' from ${decl.package}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// No services gen — addon may not have requiredParentServices
|
|
83
|
+
}
|
|
37
84
|
}
|
|
38
85
|
catch (error) {
|
|
39
86
|
logger.warn(`Failed to load addon function metadata for '${namespace}' (${decl.package}): ${error.message}`);
|