@pikku/inspector 0.10.2 → 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 +8 -0
- 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/inspector.js +4 -0
- package/dist/types.d.ts +7 -2
- package/dist/utils/extract-node-value.d.ts +24 -0
- package/dist/utils/extract-node-value.js +79 -0
- package/dist/utils/serialize-inspector-state.d.ts +4 -0
- package/dist/utils/serialize-inspector-state.js +8 -0
- package/dist/utils/type-utils.d.ts +4 -0
- package/dist/utils/type-utils.js +55 -0
- package/dist/visit.js +2 -0
- package/package.json +2 -2
- 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/inspector.ts +4 -0
- package/src/types.ts +7 -2
- package/src/utils/extract-node-value.ts +101 -0
- package/src/utils/serialize-inspector-state.ts +12 -0
- package/src/utils/test-data/inspector-state.json +4 -0
- package/src/utils/type-utils.ts +69 -0
- package/src/visit.ts +2 -0
- 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
|
@@ -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/inspector.js
CHANGED
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';
|
|
@@ -168,7 +169,11 @@ export interface InspectorState {
|
|
|
168
169
|
files: Set<string>;
|
|
169
170
|
};
|
|
170
171
|
queueWorkers: {
|
|
171
|
-
meta:
|
|
172
|
+
meta: QueueWorkersMeta;
|
|
173
|
+
files: Set<string>;
|
|
174
|
+
};
|
|
175
|
+
workflows: {
|
|
176
|
+
meta: WorkflowsMeta;
|
|
172
177
|
files: Set<string>;
|
|
173
178
|
};
|
|
174
179
|
rpc: {
|
|
@@ -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;
|
|
@@ -0,0 +1,79 @@
|
|
|
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 function extractStringLiteral(node, checker) {
|
|
8
|
+
if (ts.isStringLiteral(node)) {
|
|
9
|
+
return node.text;
|
|
10
|
+
}
|
|
11
|
+
if (ts.isNoSubstitutionTemplateLiteral(node)) {
|
|
12
|
+
return node.text;
|
|
13
|
+
}
|
|
14
|
+
if (ts.isTemplateExpression(node)) {
|
|
15
|
+
// reconstruct: `head + ${expr} + middle + ${expr} + tail`
|
|
16
|
+
let result = node.head.text;
|
|
17
|
+
for (const span of node.templateSpans) {
|
|
18
|
+
const exprText = span.expression.getText();
|
|
19
|
+
result += '${' + exprText + '}' + span.literal.text;
|
|
20
|
+
}
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
// Try to evaluate constant identifiers
|
|
24
|
+
if (ts.isIdentifier(node)) {
|
|
25
|
+
const symbol = checker.getSymbolAtLocation(node);
|
|
26
|
+
if (symbol?.valueDeclaration &&
|
|
27
|
+
ts.isVariableDeclaration(symbol.valueDeclaration)) {
|
|
28
|
+
const init = symbol.valueDeclaration.initializer;
|
|
29
|
+
if (init) {
|
|
30
|
+
return extractStringLiteral(init, checker);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
throw new Error('Unable to extract string literal from node');
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Check if node is string-like (string literal or template expression)
|
|
38
|
+
*/
|
|
39
|
+
export function isStringLike(node, _checker) {
|
|
40
|
+
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
// Check if it's a template string with substitutions
|
|
44
|
+
if (ts.isTemplateExpression(node)) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Check if node is function-like (arrow, function expression, or function declaration)
|
|
51
|
+
*/
|
|
52
|
+
export function isFunctionLike(node) {
|
|
53
|
+
return (ts.isArrowFunction(node) ||
|
|
54
|
+
ts.isFunctionExpression(node) ||
|
|
55
|
+
ts.isFunctionDeclaration(node));
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Extract number literal value from a node
|
|
59
|
+
*/
|
|
60
|
+
export function extractNumberLiteral(node) {
|
|
61
|
+
if (ts.isNumericLiteral(node)) {
|
|
62
|
+
return Number(node.text);
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Extract a property value from an object literal expression
|
|
68
|
+
* Returns the extracted value or null if not found/cannot extract
|
|
69
|
+
*/
|
|
70
|
+
export function extractPropertyString(objNode, propertyName, checker) {
|
|
71
|
+
for (const prop of objNode.properties) {
|
|
72
|
+
if (ts.isPropertyAssignment(prop) &&
|
|
73
|
+
ts.isIdentifier(prop.name) &&
|
|
74
|
+
prop.name.text === propertyName) {
|
|
75
|
+
return extractStringLiteral(prop.initializer, checker);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
@@ -123,6 +123,10 @@ export interface SerializableInspectorState {
|
|
|
123
123
|
meta: InspectorState['queueWorkers']['meta'];
|
|
124
124
|
files: string[];
|
|
125
125
|
};
|
|
126
|
+
workflows: {
|
|
127
|
+
meta: InspectorState['workflows']['meta'];
|
|
128
|
+
files: string[];
|
|
129
|
+
};
|
|
126
130
|
rpc: {
|
|
127
131
|
internalMeta: InspectorState['rpc']['internalMeta'];
|
|
128
132
|
internalFiles: Array<[string, {
|
|
@@ -50,6 +50,10 @@ export function serializeInspectorState(state) {
|
|
|
50
50
|
meta: state.queueWorkers.meta,
|
|
51
51
|
files: Array.from(state.queueWorkers.files),
|
|
52
52
|
},
|
|
53
|
+
workflows: {
|
|
54
|
+
meta: state.workflows.meta,
|
|
55
|
+
files: Array.from(state.workflows.files),
|
|
56
|
+
},
|
|
53
57
|
rpc: {
|
|
54
58
|
internalMeta: state.rpc.internalMeta,
|
|
55
59
|
internalFiles: Array.from(state.rpc.internalFiles.entries()),
|
|
@@ -137,6 +141,10 @@ export function deserializeInspectorState(data) {
|
|
|
137
141
|
meta: data.queueWorkers.meta,
|
|
138
142
|
files: new Set(data.queueWorkers.files),
|
|
139
143
|
},
|
|
144
|
+
workflows: {
|
|
145
|
+
meta: data.workflows.meta,
|
|
146
|
+
files: new Set(data.workflows.files),
|
|
147
|
+
},
|
|
140
148
|
rpc: {
|
|
141
149
|
internalMeta: data.rpc.internalMeta,
|
|
142
150
|
internalFiles: new Map(data.rpc.internalFiles),
|
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
2
|
export declare const extractTypeKeys: (type: ts.Type) => string[];
|
|
3
|
+
/**
|
|
4
|
+
* Resolve an identifier or call expression to the actual function declaration
|
|
5
|
+
*/
|
|
6
|
+
export declare function resolveFunctionDeclaration(node: ts.Node, checker: ts.TypeChecker): ts.Node | null;
|
|
3
7
|
export declare function getPropertyAssignmentInitializer(obj: ts.ObjectLiteralExpression, propName: string, followShorthand?: boolean, checker?: ts.TypeChecker): ts.Expression | undefined;
|
package/dist/utils/type-utils.js
CHANGED
|
@@ -2,6 +2,61 @@ import * as ts from 'typescript';
|
|
|
2
2
|
export const extractTypeKeys = (type) => {
|
|
3
3
|
return type.getProperties().map((symbol) => symbol.getName());
|
|
4
4
|
};
|
|
5
|
+
/**
|
|
6
|
+
* Resolve an identifier or call expression to the actual function declaration
|
|
7
|
+
*/
|
|
8
|
+
export function resolveFunctionDeclaration(node, checker) {
|
|
9
|
+
// If it's already a function-like node, return it
|
|
10
|
+
if (ts.isFunctionDeclaration(node) ||
|
|
11
|
+
ts.isFunctionExpression(node) ||
|
|
12
|
+
ts.isArrowFunction(node)) {
|
|
13
|
+
return node;
|
|
14
|
+
}
|
|
15
|
+
// If it's a call expression (e.g., pikkuWorkflowFunc(...)), get its first argument
|
|
16
|
+
if (ts.isCallExpression(node) && node.arguments.length > 0) {
|
|
17
|
+
const firstArg = node.arguments[0];
|
|
18
|
+
if (ts.isFunctionExpression(firstArg) || ts.isArrowFunction(firstArg)) {
|
|
19
|
+
return firstArg;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
// If it's an identifier, resolve to declaration
|
|
23
|
+
if (ts.isIdentifier(node)) {
|
|
24
|
+
const symbol = checker.getSymbolAtLocation(node);
|
|
25
|
+
if (!symbol)
|
|
26
|
+
return null;
|
|
27
|
+
// Try valueDeclaration first, then fallback to declarations[0]
|
|
28
|
+
const decl = symbol.valueDeclaration || symbol.declarations?.[0];
|
|
29
|
+
if (!decl)
|
|
30
|
+
return null;
|
|
31
|
+
// If it's an import specifier, resolve the aliased symbol
|
|
32
|
+
if (ts.isImportSpecifier(decl)) {
|
|
33
|
+
const aliasedSymbol = checker.getAliasedSymbol(symbol);
|
|
34
|
+
if (aliasedSymbol) {
|
|
35
|
+
const aliasedDecl = aliasedSymbol.valueDeclaration || aliasedSymbol.declarations?.[0];
|
|
36
|
+
if (aliasedDecl) {
|
|
37
|
+
// For variable declarations, get the initializer
|
|
38
|
+
if (ts.isVariableDeclaration(aliasedDecl) &&
|
|
39
|
+
aliasedDecl.initializer) {
|
|
40
|
+
return resolveFunctionDeclaration(aliasedDecl.initializer, checker);
|
|
41
|
+
}
|
|
42
|
+
// For function declarations, return directly
|
|
43
|
+
if (ts.isFunctionDeclaration(aliasedDecl)) {
|
|
44
|
+
return aliasedDecl;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// If it's a variable declaration, get the initializer
|
|
50
|
+
if (ts.isVariableDeclaration(decl) && decl.initializer) {
|
|
51
|
+
return resolveFunctionDeclaration(decl.initializer, checker);
|
|
52
|
+
}
|
|
53
|
+
// If it's a function declaration
|
|
54
|
+
if (ts.isFunctionDeclaration(decl)) {
|
|
55
|
+
return decl;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
5
60
|
export function getPropertyAssignmentInitializer(obj, propName, followShorthand = false, checker) {
|
|
6
61
|
for (const prop of obj.properties) {
|
|
7
62
|
// ① foo: () => {}
|
package/dist/visit.js
CHANGED
|
@@ -4,6 +4,7 @@ import { addFileExtendsCoreType } from './add/add-file-extends-core-type.js';
|
|
|
4
4
|
import { addHTTPRoute } from './add/add-http-route.js';
|
|
5
5
|
import { addSchedule } from './add/add-schedule.js';
|
|
6
6
|
import { addQueueWorker } from './add/add-queue-worker.js';
|
|
7
|
+
import { addWorkflow } from './add/add-workflow.js';
|
|
7
8
|
import { addMCPResource } from './add/add-mcp-resource.js';
|
|
8
9
|
import { addMCPTool } from './add/add-mcp-tool.js';
|
|
9
10
|
import { addMCPPrompt } from './add/add-mcp-prompt.js';
|
|
@@ -24,6 +25,7 @@ export const visitSetup = (logger, checker, node, state, options) => {
|
|
|
24
25
|
addRPCInvocations(node, state, logger);
|
|
25
26
|
addMiddleware(logger, node, checker, state, options);
|
|
26
27
|
addPermission(logger, node, checker, state, options);
|
|
28
|
+
addWorkflow(logger, node, checker, state, options);
|
|
27
29
|
ts.forEachChild(node, (child) => visitSetup(logger, checker, child, state, options));
|
|
28
30
|
};
|
|
29
31
|
export const visitRoutes = (logger, checker, node, state, options) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pikku/inspector",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"author": "yasser.fadl@gmail.com",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"test:coverage": "bash run-tests.sh --coverage"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@pikku/core": "^0.
|
|
19
|
+
"@pikku/core": "^0.11.0",
|
|
20
20
|
"path-to-regexp": "^8.3.0",
|
|
21
21
|
"typescript": "^5.9"
|
|
22
22
|
},
|
package/src/add/add-functions.ts
CHANGED
|
@@ -305,6 +305,7 @@ export const addFunctions: AddWiring = (logger, node, checker, state) => {
|
|
|
305
305
|
|
|
306
306
|
let tags: string[] | undefined
|
|
307
307
|
let expose: boolean | undefined
|
|
308
|
+
let internal: boolean | undefined
|
|
308
309
|
let docs: PikkuDocs | undefined
|
|
309
310
|
let objectNode: ts.ObjectLiteralExpression | undefined
|
|
310
311
|
|
|
@@ -318,6 +319,7 @@ export const addFunctions: AddWiring = (logger, node, checker, state) => {
|
|
|
318
319
|
objectNode = handlerNode
|
|
319
320
|
tags = (getPropertyValue(handlerNode, 'tags') as string[]) || undefined
|
|
320
321
|
expose = getPropertyValue(handlerNode, 'expose') as boolean | undefined
|
|
322
|
+
internal = getPropertyValue(handlerNode, 'internal') as boolean | undefined
|
|
321
323
|
docs = getPropertyValue(handlerNode, 'docs') as PikkuDocs | undefined
|
|
322
324
|
|
|
323
325
|
const fnProp = getPropertyAssignmentInitializer(
|
|
@@ -454,6 +456,7 @@ export const addFunctions: AddWiring = (logger, node, checker, state) => {
|
|
|
454
456
|
inputs: inputNames.filter((n) => n !== 'void') ?? null,
|
|
455
457
|
outputs: outputNames.filter((n) => n !== 'void') ?? null,
|
|
456
458
|
expose: expose || undefined,
|
|
459
|
+
internal: internal || undefined,
|
|
457
460
|
tags: tags || undefined,
|
|
458
461
|
docs: docs || undefined,
|
|
459
462
|
isDirectFunction,
|
|
@@ -481,6 +484,11 @@ export const addFunctions: AddWiring = (logger, node, checker, state) => {
|
|
|
481
484
|
return
|
|
482
485
|
}
|
|
483
486
|
|
|
487
|
+
// Mark internal functions as invoked to force bundling
|
|
488
|
+
if (internal) {
|
|
489
|
+
state.rpc.invokedFunctions.add(pikkuFuncName)
|
|
490
|
+
}
|
|
491
|
+
|
|
484
492
|
if (expose) {
|
|
485
493
|
state.rpc.exposedMeta[name] = pikkuFuncName
|
|
486
494
|
state.rpc.exposedFiles.set(name, {
|
|
@@ -496,8 +504,8 @@ export const addFunctions: AddWiring = (logger, node, checker, state) => {
|
|
|
496
504
|
|
|
497
505
|
// But we only import the actual function if it's actually invoked to keep
|
|
498
506
|
// bundle size down
|
|
499
|
-
if (state.rpc.invokedFunctions.has(pikkuFuncName) || expose) {
|
|
500
|
-
state.rpc.internalFiles.set(
|
|
507
|
+
if (state.rpc.invokedFunctions.has(pikkuFuncName) || expose || internal) {
|
|
508
|
+
state.rpc.internalFiles.set(pikkuFuncName, {
|
|
501
509
|
path: node.getSourceFile().fileName,
|
|
502
510
|
exportedName,
|
|
503
511
|
})
|