@pikku/inspector 0.10.1 → 0.11.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 +54 -0
- package/dist/add/add-channel.js +68 -14
- package/dist/add/add-functions.js +9 -2
- package/dist/add/add-workflow.d.ts +6 -0
- package/dist/add/add-workflow.js +152 -0
- package/dist/error-codes.d.ts +4 -1
- package/dist/error-codes.js +4 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/inspector.d.ts +6 -0
- package/dist/inspector.js +53 -15
- package/dist/types.d.ts +10 -2
- package/dist/utils/extract-node-value.d.ts +24 -0
- package/dist/utils/extract-node-value.js +79 -0
- package/dist/utils/post-process.d.ts +1 -1
- package/dist/utils/post-process.js +30 -0
- package/dist/utils/serialize-inspector-state.d.ts +6 -0
- package/dist/utils/serialize-inspector-state.js +12 -0
- package/dist/utils/type-utils.d.ts +4 -0
- package/dist/utils/type-utils.js +60 -3
- package/dist/visit.js +2 -0
- package/package.json +2 -2
- package/src/add/add-channel.ts +94 -19
- package/src/add/add-functions.ts +10 -2
- package/src/add/add-workflow.ts +231 -0
- package/src/error-codes.ts +5 -0
- package/src/index.ts +1 -1
- package/src/inspector.ts +77 -22
- package/src/types.ts +10 -2
- package/src/utils/extract-node-value.ts +101 -0
- package/src/utils/post-process.ts +40 -2
- package/src/utils/serialize-inspector-state.ts +18 -0
- package/src/utils/test-data/inspector-state.json +4 -0
- package/src/utils/type-utils.ts +74 -3
- package/src/visit.ts +3 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/src/add/add-mcp-prompt.ts.tmp +0 -0
- package/src/add/add-mcp-resource.ts.tmp +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,59 @@
|
|
|
1
|
+
## 0.11.0
|
|
2
|
+
|
|
3
|
+
### Minor Changes
|
|
4
|
+
|
|
5
|
+
- Add workflow inspection and analysis
|
|
6
|
+
- Add enhanced type extraction utilities
|
|
7
|
+
|
|
8
|
+
|
|
1
9
|
# @pikku/inspector
|
|
2
10
|
|
|
11
|
+
## 0.10.2
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- 1967172: Update code generation to support channel middleware enhancements
|
|
16
|
+
|
|
17
|
+
**Code Generation Updates:**
|
|
18
|
+
|
|
19
|
+
- Update channel type serialization to include middleware support
|
|
20
|
+
- Improve WebSocket wrapper generation for middleware handling
|
|
21
|
+
- Update CLI channel client generation with better type support
|
|
22
|
+
- Enhance services and schema generation for channel configurations
|
|
23
|
+
|
|
24
|
+
**Inspector Updates:**
|
|
25
|
+
|
|
26
|
+
- Improve channel metadata extraction for middleware
|
|
27
|
+
- Better type analysis for channel lifecycle functions
|
|
28
|
+
- Enhanced post-processing for channel configurations
|
|
29
|
+
|
|
30
|
+
- 753481a: Add bootstrap command, performance optimizations, and CLI improvements
|
|
31
|
+
|
|
32
|
+
**New Features:**
|
|
33
|
+
|
|
34
|
+
- Add `pikku bootstrap` command for type-only generation (~13.5% faster than `pikku all`)
|
|
35
|
+
- Add configurable `ignoreFiles` option to pikku.config.json with sensible defaults (_.gen.ts, _.test.ts, \*.spec.ts)
|
|
36
|
+
- Export pikkuCLIRender helper from serialize-cli-types.ts with JSDoc documentation
|
|
37
|
+
|
|
38
|
+
**Performance Improvements:**
|
|
39
|
+
|
|
40
|
+
- Add aggressive TypeScript compiler options (skipDefaultLibCheck, types: []) - ~37% faster TypeScript setup
|
|
41
|
+
- Add detailed performance timing to inspector phases (--logLevel=debug)
|
|
42
|
+
- Optimize file inspection with ignore patterns - ~10-20% faster overall
|
|
43
|
+
|
|
44
|
+
**Enhancements:**
|
|
45
|
+
|
|
46
|
+
- Fix --logLevel flag to properly apply log level to logger
|
|
47
|
+
- Update middleware logging to use structured log format
|
|
48
|
+
- Improve CLI renderers to consistently use destructured logger service
|
|
49
|
+
- Fix middleware file generation when middleware groups exist
|
|
50
|
+
|
|
51
|
+
- 44d71a8: fix: fixing inspector ensuring pikkuConfig is set
|
|
52
|
+
- Updated dependencies [ea652dc]
|
|
53
|
+
- Updated dependencies [4349ec5]
|
|
54
|
+
- Updated dependencies [44d71a8]
|
|
55
|
+
- @pikku/core@0.10.2
|
|
56
|
+
|
|
3
57
|
## 0.10.1
|
|
4
58
|
|
|
5
59
|
### Patch Changes
|
package/dist/add/add-channel.js
CHANGED
|
@@ -94,7 +94,15 @@ export function addMessagesRoutes(logger, obj, state, checker) {
|
|
|
94
94
|
const init = getInitializerOf(routeElem);
|
|
95
95
|
if (!init)
|
|
96
96
|
continue;
|
|
97
|
-
|
|
97
|
+
// Get the route key, stripping quotes if it's a string literal
|
|
98
|
+
const routeName = routeElem.name;
|
|
99
|
+
if (!routeName)
|
|
100
|
+
continue;
|
|
101
|
+
let routeKey = routeName.getText();
|
|
102
|
+
// For string literals like 'greet' or "greet", strip the quotes
|
|
103
|
+
if (ts.isStringLiteral(routeName)) {
|
|
104
|
+
routeKey = routeName.text;
|
|
105
|
+
}
|
|
98
106
|
// For shorthand properties, we need to resolve the identifier to its declaration
|
|
99
107
|
if (ts.isShorthandPropertyAssignment(routeElem)) {
|
|
100
108
|
// Get the symbol for the shorthand property
|
|
@@ -133,8 +141,16 @@ export function addMessagesRoutes(logger, obj, state, checker) {
|
|
|
133
141
|
// Look up in the registry
|
|
134
142
|
const fnMeta = state.functions.meta[handlerName];
|
|
135
143
|
if (fnMeta) {
|
|
144
|
+
// Resolve middleware for this route
|
|
145
|
+
const routeTags = ts.isObjectLiteralExpression(init)
|
|
146
|
+
? getPropertyTags(init, 'channel', channelKey, logger)
|
|
147
|
+
: undefined;
|
|
148
|
+
const routeMiddleware = ts.isObjectLiteralExpression(init)
|
|
149
|
+
? resolveMiddleware(state, init, routeTags, checker)
|
|
150
|
+
: undefined;
|
|
136
151
|
result[channelKey][routeKey] = {
|
|
137
152
|
pikkuFuncName: handlerName,
|
|
153
|
+
middleware: routeMiddleware,
|
|
138
154
|
};
|
|
139
155
|
continue;
|
|
140
156
|
}
|
|
@@ -147,8 +163,16 @@ export function addMessagesRoutes(logger, obj, state, checker) {
|
|
|
147
163
|
// Look up in the registry
|
|
148
164
|
const fnMeta = state.functions.meta[handlerName];
|
|
149
165
|
if (fnMeta) {
|
|
166
|
+
// Resolve middleware for this route
|
|
167
|
+
const routeTags = ts.isObjectLiteralExpression(init)
|
|
168
|
+
? getPropertyTags(init, 'channel', channelKey, logger)
|
|
169
|
+
: undefined;
|
|
170
|
+
const routeMiddleware = ts.isObjectLiteralExpression(init)
|
|
171
|
+
? resolveMiddleware(state, init, routeTags, checker)
|
|
172
|
+
: undefined;
|
|
150
173
|
result[channelKey][routeKey] = {
|
|
151
174
|
pikkuFuncName: handlerName,
|
|
175
|
+
middleware: routeMiddleware,
|
|
152
176
|
};
|
|
153
177
|
continue;
|
|
154
178
|
}
|
|
@@ -172,8 +196,16 @@ export function addMessagesRoutes(logger, obj, state, checker) {
|
|
|
172
196
|
const handlerName = pikkuFuncName;
|
|
173
197
|
const fnMeta = state.functions.meta[handlerName];
|
|
174
198
|
if (fnMeta) {
|
|
199
|
+
// Resolve middleware for this route
|
|
200
|
+
const routeTags = ts.isObjectLiteralExpression(init)
|
|
201
|
+
? getPropertyTags(init, 'channel', channelKey, logger)
|
|
202
|
+
: undefined;
|
|
203
|
+
const routeMiddleware = ts.isObjectLiteralExpression(init)
|
|
204
|
+
? resolveMiddleware(state, init, routeTags, checker)
|
|
205
|
+
: undefined;
|
|
175
206
|
result[channelKey][routeKey] = {
|
|
176
207
|
pikkuFuncName: handlerName,
|
|
208
|
+
middleware: routeMiddleware,
|
|
177
209
|
};
|
|
178
210
|
continue;
|
|
179
211
|
}
|
|
@@ -183,8 +215,16 @@ export function addMessagesRoutes(logger, obj, state, checker) {
|
|
|
183
215
|
const handlerName = pikkuFuncName;
|
|
184
216
|
const fnMeta = state.functions.meta[handlerName];
|
|
185
217
|
if (fnMeta) {
|
|
218
|
+
// Resolve middleware for this route
|
|
219
|
+
const routeTags = ts.isObjectLiteralExpression(init)
|
|
220
|
+
? getPropertyTags(init, 'channel', channelKey, logger)
|
|
221
|
+
: undefined;
|
|
222
|
+
const routeMiddleware = ts.isObjectLiteralExpression(init)
|
|
223
|
+
? resolveMiddleware(state, init, routeTags, checker)
|
|
224
|
+
: undefined;
|
|
186
225
|
result[channelKey][routeKey] = {
|
|
187
226
|
pikkuFuncName: handlerName,
|
|
227
|
+
middleware: routeMiddleware,
|
|
188
228
|
};
|
|
189
229
|
continue;
|
|
190
230
|
}
|
|
@@ -239,8 +279,16 @@ export function addMessagesRoutes(logger, obj, state, checker) {
|
|
|
239
279
|
// Now use this handlerName to look up in the registry
|
|
240
280
|
const fnMeta = state.functions.meta[handlerName];
|
|
241
281
|
if (fnMeta) {
|
|
282
|
+
// Resolve middleware for this route
|
|
283
|
+
const routeTags = ts.isObjectLiteralExpression(init)
|
|
284
|
+
? getPropertyTags(init, 'channel', channelKey, logger)
|
|
285
|
+
: undefined;
|
|
286
|
+
const routeMiddleware = ts.isObjectLiteralExpression(init)
|
|
287
|
+
? resolveMiddleware(state, init, routeTags, checker)
|
|
288
|
+
: undefined;
|
|
242
289
|
result[channelKey][routeKey] = {
|
|
243
290
|
pikkuFuncName: handlerName,
|
|
291
|
+
middleware: routeMiddleware,
|
|
244
292
|
};
|
|
245
293
|
continue; // Skip the normal processing below
|
|
246
294
|
}
|
|
@@ -259,8 +307,17 @@ export function addMessagesRoutes(logger, obj, state, checker) {
|
|
|
259
307
|
logger.critical(ErrorCode.FUNCTION_METADATA_NOT_FOUND, `No function metadata found for handler '${handlerName}'`);
|
|
260
308
|
continue;
|
|
261
309
|
}
|
|
310
|
+
// Resolve middleware and permissions for this route
|
|
311
|
+
// Check if the route config is an object literal with middleware/permissions
|
|
312
|
+
const routeTags = ts.isObjectLiteralExpression(init)
|
|
313
|
+
? getPropertyTags(init, 'channel', channelKey, logger)
|
|
314
|
+
: undefined;
|
|
315
|
+
const routeMiddleware = ts.isObjectLiteralExpression(init)
|
|
316
|
+
? resolveMiddleware(state, init, routeTags, checker)
|
|
317
|
+
: undefined;
|
|
262
318
|
result[channelKey][routeKey] = {
|
|
263
319
|
pikkuFuncName: handlerName,
|
|
320
|
+
middleware: routeMiddleware,
|
|
264
321
|
};
|
|
265
322
|
}
|
|
266
323
|
}
|
|
@@ -295,24 +352,21 @@ export const addChannel = (logger, node, checker, state, options) => {
|
|
|
295
352
|
const docs = getPropertyValue(obj, 'docs');
|
|
296
353
|
const tags = getPropertyTags(obj, 'Channel', route, logger);
|
|
297
354
|
const query = getPropertyValue(obj, 'query');
|
|
298
|
-
const connect = getPropertyAssignmentInitializer(obj, 'onConnect',
|
|
299
|
-
const disconnect = getPropertyAssignmentInitializer(obj, 'onDisconnect',
|
|
355
|
+
const connect = getPropertyAssignmentInitializer(obj, 'onConnect', true, checker);
|
|
356
|
+
const disconnect = getPropertyAssignmentInitializer(obj, 'onDisconnect', true, checker);
|
|
300
357
|
// default onMessage handler
|
|
301
358
|
let message = null;
|
|
302
|
-
const onMsgProp = getPropertyAssignmentInitializer(obj, 'onMessage',
|
|
359
|
+
const onMsgProp = getPropertyAssignmentInitializer(obj, 'onMessage', true, checker);
|
|
303
360
|
if (onMsgProp) {
|
|
304
|
-
const
|
|
305
|
-
|
|
306
|
-
const fnMeta = handlerName && state.functions.meta[handlerName];
|
|
361
|
+
const { pikkuFuncName } = extractFunctionName(onMsgProp, checker, state.rootDir);
|
|
362
|
+
const fnMeta = state.functions.meta[pikkuFuncName];
|
|
307
363
|
if (!fnMeta) {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
}
|
|
311
|
-
else {
|
|
312
|
-
message = {
|
|
313
|
-
pikkuFuncName: extractFunctionName(onMsgProp, checker, state.rootDir).pikkuFuncName,
|
|
314
|
-
};
|
|
364
|
+
logger.critical(ErrorCode.FUNCTION_METADATA_NOT_FOUND, `No function metadata found for onMessage handler '${pikkuFuncName}'`);
|
|
365
|
+
return;
|
|
315
366
|
}
|
|
367
|
+
message = {
|
|
368
|
+
pikkuFuncName,
|
|
369
|
+
};
|
|
316
370
|
}
|
|
317
371
|
// nested message-routes
|
|
318
372
|
const messageWirings = addMessagesRoutes(logger, obj, state, checker);
|
|
@@ -233,6 +233,7 @@ export const addFunctions = (logger, node, checker, state) => {
|
|
|
233
233
|
const { pikkuFuncName, name, explicitName, exportedName } = extractFunctionName(node, checker, state.rootDir);
|
|
234
234
|
let tags;
|
|
235
235
|
let expose;
|
|
236
|
+
let internal;
|
|
236
237
|
let docs;
|
|
237
238
|
let objectNode;
|
|
238
239
|
// determine the actual handler expression:
|
|
@@ -244,6 +245,7 @@ export const addFunctions = (logger, node, checker, state) => {
|
|
|
244
245
|
objectNode = handlerNode;
|
|
245
246
|
tags = getPropertyValue(handlerNode, 'tags') || undefined;
|
|
246
247
|
expose = getPropertyValue(handlerNode, 'expose');
|
|
248
|
+
internal = getPropertyValue(handlerNode, 'internal');
|
|
247
249
|
docs = getPropertyValue(handlerNode, 'docs');
|
|
248
250
|
const fnProp = getPropertyAssignmentInitializer(handlerNode, 'func', true, checker);
|
|
249
251
|
if (!fnProp ||
|
|
@@ -342,6 +344,7 @@ export const addFunctions = (logger, node, checker, state) => {
|
|
|
342
344
|
inputs: inputNames.filter((n) => n !== 'void') ?? null,
|
|
343
345
|
outputs: outputNames.filter((n) => n !== 'void') ?? null,
|
|
344
346
|
expose: expose || undefined,
|
|
347
|
+
internal: internal || undefined,
|
|
345
348
|
tags: tags || undefined,
|
|
346
349
|
docs: docs || undefined,
|
|
347
350
|
isDirectFunction,
|
|
@@ -363,6 +366,10 @@ export const addFunctions = (logger, node, checker, state) => {
|
|
|
363
366
|
logger.error(`• Function with explicit name '${name}' is not exported, this is not allowed.`);
|
|
364
367
|
return;
|
|
365
368
|
}
|
|
369
|
+
// Mark internal functions as invoked to force bundling
|
|
370
|
+
if (internal) {
|
|
371
|
+
state.rpc.invokedFunctions.add(pikkuFuncName);
|
|
372
|
+
}
|
|
366
373
|
if (expose) {
|
|
367
374
|
state.rpc.exposedMeta[name] = pikkuFuncName;
|
|
368
375
|
state.rpc.exposedFiles.set(name, {
|
|
@@ -376,8 +383,8 @@ export const addFunctions = (logger, node, checker, state) => {
|
|
|
376
383
|
state.rpc.internalMeta[name] = pikkuFuncName;
|
|
377
384
|
// But we only import the actual function if it's actually invoked to keep
|
|
378
385
|
// bundle size down
|
|
379
|
-
if (state.rpc.invokedFunctions.has(pikkuFuncName) || expose) {
|
|
380
|
-
state.rpc.internalFiles.set(
|
|
386
|
+
if (state.rpc.invokedFunctions.has(pikkuFuncName) || expose || internal) {
|
|
387
|
+
state.rpc.internalFiles.set(pikkuFuncName, {
|
|
381
388
|
path: node.getSourceFile().fileName,
|
|
382
389
|
exportedName,
|
|
383
390
|
});
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import * as ts from 'typescript';
|
|
2
|
+
import { getPropertyValue, getPropertyTags, } from '../utils/get-property-value.js';
|
|
3
|
+
import { extractFunctionName } from '../utils/extract-function-name.js';
|
|
4
|
+
import { getPropertyAssignmentInitializer, resolveFunctionDeclaration, } from '../utils/type-utils.js';
|
|
5
|
+
import { resolveMiddleware } from '../utils/middleware.js';
|
|
6
|
+
import { extractWireNames } from '../utils/post-process.js';
|
|
7
|
+
import { ErrorCode } from '../error-codes.js';
|
|
8
|
+
import { extractStringLiteral, extractNumberLiteral, extractPropertyString, isStringLike, isFunctionLike, } from '../utils/extract-node-value.js';
|
|
9
|
+
/**
|
|
10
|
+
* Scan for workflow.do() and workflow.sleep() calls to extract workflow steps
|
|
11
|
+
*/
|
|
12
|
+
function getWorkflowInvocations(node, checker, state, workflowName, steps) {
|
|
13
|
+
// Look for property access expressions: workflow.do or workflow.sleep
|
|
14
|
+
if (ts.isPropertyAccessExpression(node)) {
|
|
15
|
+
const { name } = node;
|
|
16
|
+
// Check if this is accessing 'do' or 'sleep' property
|
|
17
|
+
if (name.text === 'do' || name.text === 'sleep') {
|
|
18
|
+
// Check if the parent is a call expression
|
|
19
|
+
const parent = node.parent;
|
|
20
|
+
if (ts.isCallExpression(parent) && parent.expression === node) {
|
|
21
|
+
const args = parent.arguments;
|
|
22
|
+
if (name.text === 'do' && args.length >= 2) {
|
|
23
|
+
// workflow.do(stepName, rpcName|fn, data?, options?)
|
|
24
|
+
const stepNameArg = args[0];
|
|
25
|
+
const secondArg = args[1];
|
|
26
|
+
const optionsArg = args.length >= 3 ? args[args.length - 1] : undefined;
|
|
27
|
+
const stepName = extractStringLiteral(stepNameArg, checker);
|
|
28
|
+
const description = extractDescription(optionsArg, checker) ?? undefined;
|
|
29
|
+
// Determine form by checking 2nd argument type
|
|
30
|
+
if (isStringLike(secondArg, checker)) {
|
|
31
|
+
// RPC form: workflow.do(stepName, rpcName, data, options?)
|
|
32
|
+
const rpcName = extractStringLiteral(secondArg, checker);
|
|
33
|
+
steps.push({
|
|
34
|
+
type: 'rpc',
|
|
35
|
+
stepName,
|
|
36
|
+
rpcName,
|
|
37
|
+
description,
|
|
38
|
+
});
|
|
39
|
+
state.rpc.invokedFunctions.add(rpcName);
|
|
40
|
+
}
|
|
41
|
+
else if (isFunctionLike(secondArg)) {
|
|
42
|
+
// Inline form: workflow.do(stepName, fn, options?)
|
|
43
|
+
steps.push({
|
|
44
|
+
type: 'inline',
|
|
45
|
+
stepName: stepName || '<dynamic>',
|
|
46
|
+
description: description || '<dynamic>',
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
else if (name.text === 'sleep' && args.length >= 2) {
|
|
51
|
+
// workflow.sleep(stepName, duration)
|
|
52
|
+
const stepNameArg = args[0];
|
|
53
|
+
const durationArg = args[1];
|
|
54
|
+
const stepName = extractStringLiteral(stepNameArg, checker);
|
|
55
|
+
const duration = extractDuration(durationArg, checker);
|
|
56
|
+
steps.push({
|
|
57
|
+
type: 'sleep',
|
|
58
|
+
stepName: stepName || '<dynamic>',
|
|
59
|
+
duration: duration || '<dynamic>',
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// Don't recurse into nested functions - only look at top-level workflow calls
|
|
66
|
+
ts.forEachChild(node, (child) => {
|
|
67
|
+
if (ts.isFunctionDeclaration(child) ||
|
|
68
|
+
ts.isFunctionExpression(child) ||
|
|
69
|
+
ts.isArrowFunction(child)) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
getWorkflowInvocations(child, checker, state, workflowName, steps);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Extract description from options object
|
|
77
|
+
*/
|
|
78
|
+
function extractDescription(optionsNode, checker) {
|
|
79
|
+
if (!optionsNode || !ts.isObjectLiteralExpression(optionsNode)) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
return extractPropertyString(optionsNode, 'description', checker);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Extract duration value (number or string)
|
|
86
|
+
*/
|
|
87
|
+
function extractDuration(node, checker) {
|
|
88
|
+
const numValue = extractNumberLiteral(node);
|
|
89
|
+
if (numValue !== null) {
|
|
90
|
+
return numValue;
|
|
91
|
+
}
|
|
92
|
+
return extractStringLiteral(node, checker);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Inspector for wireWorkflow() calls
|
|
96
|
+
* Detects workflow registration and extracts metadata
|
|
97
|
+
*/
|
|
98
|
+
export const addWorkflow = (logger, node, checker, state, options) => {
|
|
99
|
+
if (!ts.isCallExpression(node)) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const args = node.arguments;
|
|
103
|
+
const firstArg = args[0];
|
|
104
|
+
const expression = node.expression;
|
|
105
|
+
// Check if the call is to wireWorkflow
|
|
106
|
+
if (!ts.isIdentifier(expression) || expression.text !== 'wireWorkflow') {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (!firstArg) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (ts.isObjectLiteralExpression(firstArg)) {
|
|
113
|
+
const obj = firstArg;
|
|
114
|
+
const workflowName = getPropertyValue(obj, 'name');
|
|
115
|
+
const description = getPropertyValue(obj, 'description');
|
|
116
|
+
const docs = getPropertyValue(obj, 'docs') || undefined;
|
|
117
|
+
const tags = getPropertyTags(obj, 'Workflow', workflowName, logger);
|
|
118
|
+
// --- find the referenced function ---
|
|
119
|
+
const funcInitializer = getPropertyAssignmentInitializer(obj, 'func', true, checker);
|
|
120
|
+
if (!workflowName) {
|
|
121
|
+
logger.critical(ErrorCode.MISSING_NAME, `Wasn't able to determine 'name' property for workflow wiring.`);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (!funcInitializer) {
|
|
125
|
+
logger.critical(ErrorCode.MISSING_FUNC, `No valid 'func' property for workflow '${workflowName}'.`);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const pikkuFuncName = extractFunctionName(funcInitializer, checker, state.rootDir).pikkuFuncName;
|
|
129
|
+
// --- resolve middleware ---
|
|
130
|
+
const middleware = resolveMiddleware(state, obj, tags, checker);
|
|
131
|
+
// --- track used functions/middleware for service aggregation ---
|
|
132
|
+
state.serviceAggregation.usedFunctions.add(pikkuFuncName);
|
|
133
|
+
extractWireNames(middleware).forEach((name) => state.serviceAggregation.usedMiddleware.add(name));
|
|
134
|
+
state.workflows.files.add(node.getSourceFile().fileName);
|
|
135
|
+
// Extract workflow steps from function body
|
|
136
|
+
// Resolve the identifier to the actual function declaration
|
|
137
|
+
const resolvedFunc = resolveFunctionDeclaration(funcInitializer, checker);
|
|
138
|
+
const steps = [];
|
|
139
|
+
if (resolvedFunc) {
|
|
140
|
+
getWorkflowInvocations(resolvedFunc, checker, state, workflowName, steps);
|
|
141
|
+
}
|
|
142
|
+
state.workflows.meta[workflowName] = {
|
|
143
|
+
pikkuFuncName,
|
|
144
|
+
workflowName,
|
|
145
|
+
description,
|
|
146
|
+
docs,
|
|
147
|
+
tags,
|
|
148
|
+
middleware,
|
|
149
|
+
steps,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
};
|
package/dist/error-codes.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ export declare enum ErrorCode {
|
|
|
17
17
|
MISSING_QUEUE_NAME = "PKU384",
|
|
18
18
|
MISSING_CHANNEL_NAME = "PKU400",
|
|
19
19
|
CLI_CLIENTSIDE_RENDERER_HAS_SERVICES = "PKU672",
|
|
20
|
+
DYNAMIC_STEP_NAME = "PKU529",
|
|
21
|
+
WORKFLOW_ORCHESTRATOR_NOT_CONFIGURED = "PKU600",
|
|
20
22
|
CONFIG_TYPE_NOT_FOUND = "PKU426",
|
|
21
23
|
CONFIG_TYPE_UNDEFINED = "PKU427",
|
|
22
24
|
SCHEMA_NO_ROOT = "PKU431",
|
|
@@ -31,5 +33,6 @@ export declare enum ErrorCode {
|
|
|
31
33
|
PERMISSION_HANDLER_INVALID = "PKU835",
|
|
32
34
|
PERMISSION_TAG_INVALID = "PKU836",
|
|
33
35
|
PERMISSION_EMPTY_ARRAY = "PKU937",
|
|
34
|
-
PERMISSION_PATTERN_INVALID = "PKU975"
|
|
36
|
+
PERMISSION_PATTERN_INVALID = "PKU975",
|
|
37
|
+
WORKFLOW_MULTI_QUEUE_NOT_SUPPORTED = "PKU901"
|
|
35
38
|
}
|
package/dist/error-codes.js
CHANGED
|
@@ -19,6 +19,8 @@ export var ErrorCode;
|
|
|
19
19
|
ErrorCode["MISSING_QUEUE_NAME"] = "PKU384";
|
|
20
20
|
ErrorCode["MISSING_CHANNEL_NAME"] = "PKU400";
|
|
21
21
|
ErrorCode["CLI_CLIENTSIDE_RENDERER_HAS_SERVICES"] = "PKU672";
|
|
22
|
+
ErrorCode["DYNAMIC_STEP_NAME"] = "PKU529";
|
|
23
|
+
ErrorCode["WORKFLOW_ORCHESTRATOR_NOT_CONFIGURED"] = "PKU600";
|
|
22
24
|
// Configuration errors
|
|
23
25
|
ErrorCode["CONFIG_TYPE_NOT_FOUND"] = "PKU426";
|
|
24
26
|
ErrorCode["CONFIG_TYPE_UNDEFINED"] = "PKU427";
|
|
@@ -37,4 +39,6 @@ export var ErrorCode;
|
|
|
37
39
|
ErrorCode["PERMISSION_TAG_INVALID"] = "PKU836";
|
|
38
40
|
ErrorCode["PERMISSION_EMPTY_ARRAY"] = "PKU937";
|
|
39
41
|
ErrorCode["PERMISSION_PATTERN_INVALID"] = "PKU975";
|
|
42
|
+
// Feature Flag
|
|
43
|
+
ErrorCode["WORKFLOW_MULTI_QUEUE_NOT_SUPPORTED"] = "PKU901";
|
|
40
44
|
})(ErrorCode || (ErrorCode = {}));
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { inspect } from './inspector.js';
|
|
1
|
+
export { inspect, getInitialInspectorState } from './inspector.js';
|
|
2
2
|
export { getFilesAndMethods } from './utils/get-files-and-methods.js';
|
|
3
3
|
export { ErrorCode } from './error-codes.js';
|
|
4
4
|
export { serializeInspectorState, deserializeInspectorState, } from './utils/serialize-inspector-state.js';
|
package/dist/inspector.d.ts
CHANGED
|
@@ -1,2 +1,8 @@
|
|
|
1
1
|
import { InspectorState, InspectorLogger, InspectorOptions } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Creates an initial/empty inspector state with all required properties initialized
|
|
4
|
+
* @param rootDir - The root directory for the project
|
|
5
|
+
* @returns A fresh InspectorState with empty collections
|
|
6
|
+
*/
|
|
7
|
+
export declare function getInitialInspectorState(rootDir: string): InspectorState;
|
|
2
8
|
export declare const inspect: (logger: InspectorLogger, routeFiles: string[], options?: InspectorOptions) => InspectorState;
|
package/dist/inspector.js
CHANGED
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
|
+
import { performance } from 'perf_hooks';
|
|
2
3
|
import { visitSetup, visitRoutes } from './visit.js';
|
|
3
4
|
import { TypesMap } from './types-map.js';
|
|
4
5
|
import { getFilesAndMethods } from './utils/get-files-and-methods.js';
|
|
5
6
|
import { findCommonAncestor } from './utils/find-root-dir.js';
|
|
6
7
|
import { aggregateRequiredServices } from './utils/post-process.js';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
// Infer root directory from source files
|
|
15
|
-
const rootDir = findCommonAncestor(routeFiles);
|
|
16
|
-
const state = {
|
|
8
|
+
/**
|
|
9
|
+
* Creates an initial/empty inspector state with all required properties initialized
|
|
10
|
+
* @param rootDir - The root directory for the project
|
|
11
|
+
* @returns A fresh InspectorState with empty collections
|
|
12
|
+
*/
|
|
13
|
+
export function getInitialInspectorState(rootDir) {
|
|
14
|
+
return {
|
|
17
15
|
rootDir,
|
|
18
16
|
singletonServicesTypeImportMap: new Map(),
|
|
19
17
|
sessionServicesTypeImportMap: new Map(),
|
|
@@ -58,6 +56,10 @@ export const inspect = (logger, routeFiles, options = {}) => {
|
|
|
58
56
|
meta: {},
|
|
59
57
|
files: new Set(),
|
|
60
58
|
},
|
|
59
|
+
workflows: {
|
|
60
|
+
meta: {},
|
|
61
|
+
files: new Set(),
|
|
62
|
+
},
|
|
61
63
|
rpc: {
|
|
62
64
|
internalMeta: {},
|
|
63
65
|
internalFiles: new Map(),
|
|
@@ -91,21 +93,57 @@ export const inspect = (logger, routeFiles, options = {}) => {
|
|
|
91
93
|
usedFunctions: new Set(),
|
|
92
94
|
usedMiddleware: new Set(),
|
|
93
95
|
usedPermissions: new Set(),
|
|
96
|
+
allSingletonServices: [],
|
|
97
|
+
allSessionServices: [],
|
|
94
98
|
},
|
|
95
99
|
};
|
|
100
|
+
}
|
|
101
|
+
export const inspect = (logger, routeFiles, options = {}) => {
|
|
102
|
+
const startProgram = performance.now();
|
|
103
|
+
const program = ts.createProgram(routeFiles, {
|
|
104
|
+
target: ts.ScriptTarget.ESNext,
|
|
105
|
+
module: ts.ModuleKind.CommonJS,
|
|
106
|
+
skipLibCheck: true,
|
|
107
|
+
skipDefaultLibCheck: true,
|
|
108
|
+
moduleResolution: ts.ModuleResolutionKind.Node10,
|
|
109
|
+
types: [],
|
|
110
|
+
allowJs: false,
|
|
111
|
+
checkJs: false,
|
|
112
|
+
});
|
|
113
|
+
logger.debug(`Created program in ${(performance.now() - startProgram).toFixed(2)}ms`);
|
|
114
|
+
const startChecker = performance.now();
|
|
115
|
+
const checker = program.getTypeChecker();
|
|
116
|
+
logger.debug(`Got type checker in ${(performance.now() - startChecker).toFixed(2)}ms`);
|
|
117
|
+
const startSourceFiles = performance.now();
|
|
118
|
+
const sourceFiles = program.getSourceFiles();
|
|
119
|
+
logger.debug(`Got source files in ${(performance.now() - startSourceFiles).toFixed(2)}ms`);
|
|
120
|
+
// Infer root directory from source files
|
|
121
|
+
const rootDir = findCommonAncestor(routeFiles);
|
|
122
|
+
const state = getInitialInspectorState(rootDir);
|
|
96
123
|
// First sweep: add all functions
|
|
124
|
+
const startSetup = performance.now();
|
|
97
125
|
for (const sourceFile of sourceFiles) {
|
|
98
126
|
ts.forEachChild(sourceFile, (child) => visitSetup(logger, checker, child, state, options));
|
|
99
127
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
128
|
+
logger.debug(`Visit setup phase completed in ${(performance.now() - startSetup).toFixed(2)}ms`);
|
|
129
|
+
if (!options.setupOnly) {
|
|
130
|
+
// Second sweep: add all transports
|
|
131
|
+
const startRoutes = performance.now();
|
|
132
|
+
for (const sourceFile of sourceFiles) {
|
|
133
|
+
ts.forEachChild(sourceFile, (child) => visitRoutes(logger, checker, child, state, options));
|
|
134
|
+
}
|
|
135
|
+
logger.debug(`Visit routes phase completed in ${(performance.now() - startRoutes).toFixed(2)}ms`);
|
|
103
136
|
}
|
|
104
137
|
// Populate filesAndMethods
|
|
138
|
+
const startFilesAndMethods = performance.now();
|
|
105
139
|
const { result, errors } = getFilesAndMethods(state, options.types);
|
|
106
140
|
state.filesAndMethods = result;
|
|
107
141
|
state.filesAndMethodsErrors = errors;
|
|
108
|
-
|
|
109
|
-
|
|
142
|
+
logger.debug(`Get files and methods completed in ${(performance.now() - startFilesAndMethods).toFixed(2)}ms`);
|
|
143
|
+
if (!options.setupOnly) {
|
|
144
|
+
const startAggregate = performance.now();
|
|
145
|
+
aggregateRequiredServices(state);
|
|
146
|
+
logger.debug(`Aggregate required services completed in ${(performance.now() - startAggregate).toFixed(2)}ms`);
|
|
147
|
+
}
|
|
110
148
|
return state;
|
|
111
149
|
};
|
package/dist/types.d.ts
CHANGED
|
@@ -2,7 +2,8 @@ import * as ts from 'typescript';
|
|
|
2
2
|
import { ChannelsMeta } from '@pikku/core/channel';
|
|
3
3
|
import { HTTPWiringsMeta } from '@pikku/core/http';
|
|
4
4
|
import { ScheduledTasksMeta } from '@pikku/core/scheduler';
|
|
5
|
-
import {
|
|
5
|
+
import { QueueWorkersMeta } from '@pikku/core/queue';
|
|
6
|
+
import { WorkflowsMeta } from '@pikku/core/workflow';
|
|
6
7
|
import { MCPResourceMeta, MCPToolMeta, MCPPromptMeta } from '@pikku/core/mcp';
|
|
7
8
|
import { CLIMeta } from '@pikku/core/cli';
|
|
8
9
|
import { TypesMap } from './types-map.js';
|
|
@@ -86,6 +87,7 @@ export type InspectorFilters = {
|
|
|
86
87
|
httpMethods?: string[];
|
|
87
88
|
};
|
|
88
89
|
export type InspectorOptions = Partial<{
|
|
90
|
+
setupOnly: boolean;
|
|
89
91
|
types: Partial<{
|
|
90
92
|
configFileType: string;
|
|
91
93
|
userSessionType: string;
|
|
@@ -167,7 +169,11 @@ export interface InspectorState {
|
|
|
167
169
|
files: Set<string>;
|
|
168
170
|
};
|
|
169
171
|
queueWorkers: {
|
|
170
|
-
meta:
|
|
172
|
+
meta: QueueWorkersMeta;
|
|
173
|
+
files: Set<string>;
|
|
174
|
+
};
|
|
175
|
+
workflows: {
|
|
176
|
+
meta: WorkflowsMeta;
|
|
171
177
|
files: Set<string>;
|
|
172
178
|
};
|
|
173
179
|
rpc: {
|
|
@@ -200,5 +206,7 @@ export interface InspectorState {
|
|
|
200
206
|
usedFunctions: Set<string>;
|
|
201
207
|
usedMiddleware: Set<string>;
|
|
202
208
|
usedPermissions: Set<string>;
|
|
209
|
+
allSingletonServices: string[];
|
|
210
|
+
allSessionServices: string[];
|
|
203
211
|
};
|
|
204
212
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as ts from 'typescript';
|
|
2
|
+
/**
|
|
3
|
+
* Extract string literal value from a TypeScript node.
|
|
4
|
+
* Handles string literals, template literals (including placeholders),
|
|
5
|
+
* and constant variable references.
|
|
6
|
+
*/
|
|
7
|
+
export declare function extractStringLiteral(node: ts.Node, checker: ts.TypeChecker): string;
|
|
8
|
+
/**
|
|
9
|
+
* Check if node is string-like (string literal or template expression)
|
|
10
|
+
*/
|
|
11
|
+
export declare function isStringLike(node: ts.Node, _checker: ts.TypeChecker): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Check if node is function-like (arrow, function expression, or function declaration)
|
|
14
|
+
*/
|
|
15
|
+
export declare function isFunctionLike(node: ts.Node): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Extract number literal value from a node
|
|
18
|
+
*/
|
|
19
|
+
export declare function extractNumberLiteral(node: ts.Node): number | null;
|
|
20
|
+
/**
|
|
21
|
+
* Extract a property value from an object literal expression
|
|
22
|
+
* Returns the extracted value or null if not found/cannot extract
|
|
23
|
+
*/
|
|
24
|
+
export declare function extractPropertyString(objNode: ts.ObjectLiteralExpression, propertyName: string, checker: ts.TypeChecker): string | null;
|