@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
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import * as ts from 'typescript'
|
|
2
|
+
import {
|
|
3
|
+
getPropertyValue,
|
|
4
|
+
getPropertyTags,
|
|
5
|
+
} from '../utils/get-property-value.js'
|
|
6
|
+
import { PikkuDocs } from '@pikku/core'
|
|
7
|
+
import { AddWiring, InspectorState } from '../types.js'
|
|
8
|
+
import { extractFunctionName } from '../utils/extract-function-name.js'
|
|
9
|
+
import {
|
|
10
|
+
getPropertyAssignmentInitializer,
|
|
11
|
+
resolveFunctionDeclaration,
|
|
12
|
+
} from '../utils/type-utils.js'
|
|
13
|
+
import { resolveMiddleware } from '../utils/middleware.js'
|
|
14
|
+
import { extractWireNames } from '../utils/post-process.js'
|
|
15
|
+
import { ErrorCode } from '../error-codes.js'
|
|
16
|
+
import { WorkflowStepMeta } from '@pikku/core/workflow'
|
|
17
|
+
import {
|
|
18
|
+
extractStringLiteral,
|
|
19
|
+
extractNumberLiteral,
|
|
20
|
+
extractPropertyString,
|
|
21
|
+
isStringLike,
|
|
22
|
+
isFunctionLike,
|
|
23
|
+
} from '../utils/extract-node-value.js'
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Scan for workflow.do() and workflow.sleep() calls to extract workflow steps
|
|
27
|
+
*/
|
|
28
|
+
function getWorkflowInvocations(
|
|
29
|
+
node: ts.Node,
|
|
30
|
+
checker: ts.TypeChecker,
|
|
31
|
+
state: InspectorState,
|
|
32
|
+
workflowName: string,
|
|
33
|
+
steps: WorkflowStepMeta[]
|
|
34
|
+
) {
|
|
35
|
+
// Look for property access expressions: workflow.do or workflow.sleep
|
|
36
|
+
if (ts.isPropertyAccessExpression(node)) {
|
|
37
|
+
const { name } = node
|
|
38
|
+
|
|
39
|
+
// Check if this is accessing 'do' or 'sleep' property
|
|
40
|
+
if (name.text === 'do' || name.text === 'sleep') {
|
|
41
|
+
// Check if the parent is a call expression
|
|
42
|
+
const parent = node.parent
|
|
43
|
+
if (ts.isCallExpression(parent) && parent.expression === node) {
|
|
44
|
+
const args = parent.arguments
|
|
45
|
+
|
|
46
|
+
if (name.text === 'do' && args.length >= 2) {
|
|
47
|
+
// workflow.do(stepName, rpcName|fn, data?, options?)
|
|
48
|
+
const stepNameArg = args[0]
|
|
49
|
+
const secondArg = args[1]
|
|
50
|
+
const optionsArg =
|
|
51
|
+
args.length >= 3 ? args[args.length - 1] : undefined
|
|
52
|
+
|
|
53
|
+
const stepName = extractStringLiteral(stepNameArg, checker)
|
|
54
|
+
const description =
|
|
55
|
+
extractDescription(optionsArg, checker) ?? undefined
|
|
56
|
+
|
|
57
|
+
// Determine form by checking 2nd argument type
|
|
58
|
+
if (isStringLike(secondArg, checker)) {
|
|
59
|
+
// RPC form: workflow.do(stepName, rpcName, data, options?)
|
|
60
|
+
const rpcName = extractStringLiteral(secondArg, checker)
|
|
61
|
+
steps.push({
|
|
62
|
+
type: 'rpc',
|
|
63
|
+
stepName,
|
|
64
|
+
rpcName,
|
|
65
|
+
description,
|
|
66
|
+
})
|
|
67
|
+
state.rpc.invokedFunctions.add(rpcName)
|
|
68
|
+
} else if (isFunctionLike(secondArg)) {
|
|
69
|
+
// Inline form: workflow.do(stepName, fn, options?)
|
|
70
|
+
steps.push({
|
|
71
|
+
type: 'inline',
|
|
72
|
+
stepName: stepName || '<dynamic>',
|
|
73
|
+
description: description || '<dynamic>',
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
} else if (name.text === 'sleep' && args.length >= 2) {
|
|
77
|
+
// workflow.sleep(stepName, duration)
|
|
78
|
+
const stepNameArg = args[0]
|
|
79
|
+
const durationArg = args[1]
|
|
80
|
+
|
|
81
|
+
const stepName = extractStringLiteral(stepNameArg, checker)
|
|
82
|
+
const duration = extractDuration(durationArg, checker)
|
|
83
|
+
|
|
84
|
+
steps.push({
|
|
85
|
+
type: 'sleep',
|
|
86
|
+
stepName: stepName || '<dynamic>',
|
|
87
|
+
duration: duration || '<dynamic>',
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Don't recurse into nested functions - only look at top-level workflow calls
|
|
95
|
+
ts.forEachChild(node, (child) => {
|
|
96
|
+
if (
|
|
97
|
+
ts.isFunctionDeclaration(child) ||
|
|
98
|
+
ts.isFunctionExpression(child) ||
|
|
99
|
+
ts.isArrowFunction(child)
|
|
100
|
+
) {
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
getWorkflowInvocations(child, checker, state, workflowName, steps)
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Extract description from options object
|
|
109
|
+
*/
|
|
110
|
+
function extractDescription(
|
|
111
|
+
optionsNode: ts.Node | undefined,
|
|
112
|
+
checker: ts.TypeChecker
|
|
113
|
+
): string | null {
|
|
114
|
+
if (!optionsNode || !ts.isObjectLiteralExpression(optionsNode)) {
|
|
115
|
+
return null
|
|
116
|
+
}
|
|
117
|
+
return extractPropertyString(optionsNode, 'description', checker)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Extract duration value (number or string)
|
|
122
|
+
*/
|
|
123
|
+
function extractDuration(
|
|
124
|
+
node: ts.Node,
|
|
125
|
+
checker: ts.TypeChecker
|
|
126
|
+
): string | number | null {
|
|
127
|
+
const numValue = extractNumberLiteral(node)
|
|
128
|
+
if (numValue !== null) {
|
|
129
|
+
return numValue
|
|
130
|
+
}
|
|
131
|
+
return extractStringLiteral(node, checker)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Inspector for wireWorkflow() calls
|
|
136
|
+
* Detects workflow registration and extracts metadata
|
|
137
|
+
*/
|
|
138
|
+
export const addWorkflow: AddWiring = (
|
|
139
|
+
logger,
|
|
140
|
+
node,
|
|
141
|
+
checker,
|
|
142
|
+
state,
|
|
143
|
+
options
|
|
144
|
+
) => {
|
|
145
|
+
if (!ts.isCallExpression(node)) {
|
|
146
|
+
return
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const args = node.arguments
|
|
150
|
+
const firstArg = args[0]
|
|
151
|
+
const expression = node.expression
|
|
152
|
+
|
|
153
|
+
// Check if the call is to wireWorkflow
|
|
154
|
+
if (!ts.isIdentifier(expression) || expression.text !== 'wireWorkflow') {
|
|
155
|
+
return
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (!firstArg) {
|
|
159
|
+
return
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (ts.isObjectLiteralExpression(firstArg)) {
|
|
163
|
+
const obj = firstArg
|
|
164
|
+
|
|
165
|
+
const workflowName = getPropertyValue(obj, 'name') as string | null
|
|
166
|
+
const description = getPropertyValue(obj, 'description') as
|
|
167
|
+
| string
|
|
168
|
+
| undefined
|
|
169
|
+
const docs = (getPropertyValue(obj, 'docs') as PikkuDocs) || undefined
|
|
170
|
+
const tags = getPropertyTags(obj, 'Workflow', workflowName, logger)
|
|
171
|
+
|
|
172
|
+
// --- find the referenced function ---
|
|
173
|
+
const funcInitializer = getPropertyAssignmentInitializer(
|
|
174
|
+
obj,
|
|
175
|
+
'func',
|
|
176
|
+
true,
|
|
177
|
+
checker
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
if (!workflowName) {
|
|
181
|
+
logger.critical(
|
|
182
|
+
ErrorCode.MISSING_NAME,
|
|
183
|
+
`Wasn't able to determine 'name' property for workflow wiring.`
|
|
184
|
+
)
|
|
185
|
+
return
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (!funcInitializer) {
|
|
189
|
+
logger.critical(
|
|
190
|
+
ErrorCode.MISSING_FUNC,
|
|
191
|
+
`No valid 'func' property for workflow '${workflowName}'.`
|
|
192
|
+
)
|
|
193
|
+
return
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const pikkuFuncName = extractFunctionName(
|
|
197
|
+
funcInitializer,
|
|
198
|
+
checker,
|
|
199
|
+
state.rootDir
|
|
200
|
+
).pikkuFuncName
|
|
201
|
+
|
|
202
|
+
// --- resolve middleware ---
|
|
203
|
+
const middleware = resolveMiddleware(state, obj, tags, checker)
|
|
204
|
+
|
|
205
|
+
// --- track used functions/middleware for service aggregation ---
|
|
206
|
+
state.serviceAggregation.usedFunctions.add(pikkuFuncName)
|
|
207
|
+
extractWireNames(middleware).forEach((name) =>
|
|
208
|
+
state.serviceAggregation.usedMiddleware.add(name)
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
state.workflows.files.add(node.getSourceFile().fileName)
|
|
212
|
+
|
|
213
|
+
// Extract workflow steps from function body
|
|
214
|
+
// Resolve the identifier to the actual function declaration
|
|
215
|
+
const resolvedFunc = resolveFunctionDeclaration(funcInitializer, checker)
|
|
216
|
+
const steps: WorkflowStepMeta[] = []
|
|
217
|
+
if (resolvedFunc) {
|
|
218
|
+
getWorkflowInvocations(resolvedFunc, checker, state, workflowName, steps)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
state.workflows.meta[workflowName] = {
|
|
222
|
+
pikkuFuncName,
|
|
223
|
+
workflowName,
|
|
224
|
+
description,
|
|
225
|
+
docs,
|
|
226
|
+
tags,
|
|
227
|
+
middleware,
|
|
228
|
+
steps,
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
package/src/error-codes.ts
CHANGED
|
@@ -19,6 +19,8 @@ export enum ErrorCode {
|
|
|
19
19
|
MISSING_QUEUE_NAME = 'PKU384',
|
|
20
20
|
MISSING_CHANNEL_NAME = 'PKU400',
|
|
21
21
|
CLI_CLIENTSIDE_RENDERER_HAS_SERVICES = 'PKU672',
|
|
22
|
+
DYNAMIC_STEP_NAME = 'PKU529',
|
|
23
|
+
WORKFLOW_ORCHESTRATOR_NOT_CONFIGURED = 'PKU600',
|
|
22
24
|
|
|
23
25
|
// Configuration errors
|
|
24
26
|
CONFIG_TYPE_NOT_FOUND = 'PKU426',
|
|
@@ -40,4 +42,7 @@ export enum ErrorCode {
|
|
|
40
42
|
PERMISSION_TAG_INVALID = 'PKU836',
|
|
41
43
|
PERMISSION_EMPTY_ARRAY = 'PKU937',
|
|
42
44
|
PERMISSION_PATTERN_INVALID = 'PKU975',
|
|
45
|
+
|
|
46
|
+
// Feature Flag
|
|
47
|
+
WORKFLOW_MULTI_QUEUE_NOT_SUPPORTED = 'PKU901',
|
|
43
48
|
}
|
package/src/inspector.ts
CHANGED
package/src/types.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'
|
|
@@ -205,7 +206,11 @@ export interface InspectorState {
|
|
|
205
206
|
files: Set<string>
|
|
206
207
|
}
|
|
207
208
|
queueWorkers: {
|
|
208
|
-
meta:
|
|
209
|
+
meta: QueueWorkersMeta
|
|
210
|
+
files: Set<string>
|
|
211
|
+
}
|
|
212
|
+
workflows: {
|
|
213
|
+
meta: WorkflowsMeta
|
|
209
214
|
files: Set<string>
|
|
210
215
|
}
|
|
211
216
|
rpc: {
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import * as ts from 'typescript'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Extract string literal value from a TypeScript node.
|
|
5
|
+
* Handles string literals, template literals (including placeholders),
|
|
6
|
+
* and constant variable references.
|
|
7
|
+
*/
|
|
8
|
+
export function extractStringLiteral(
|
|
9
|
+
node: ts.Node,
|
|
10
|
+
checker: ts.TypeChecker
|
|
11
|
+
): string {
|
|
12
|
+
if (ts.isStringLiteral(node)) {
|
|
13
|
+
return node.text
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (ts.isNoSubstitutionTemplateLiteral(node)) {
|
|
17
|
+
return node.text
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (ts.isTemplateExpression(node)) {
|
|
21
|
+
// reconstruct: `head + ${expr} + middle + ${expr} + tail`
|
|
22
|
+
let result = node.head.text
|
|
23
|
+
for (const span of node.templateSpans) {
|
|
24
|
+
const exprText = span.expression.getText()
|
|
25
|
+
result += '${' + exprText + '}' + span.literal.text
|
|
26
|
+
}
|
|
27
|
+
return result
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Try to evaluate constant identifiers
|
|
31
|
+
if (ts.isIdentifier(node)) {
|
|
32
|
+
const symbol = checker.getSymbolAtLocation(node)
|
|
33
|
+
if (
|
|
34
|
+
symbol?.valueDeclaration &&
|
|
35
|
+
ts.isVariableDeclaration(symbol.valueDeclaration)
|
|
36
|
+
) {
|
|
37
|
+
const init = symbol.valueDeclaration.initializer
|
|
38
|
+
if (init) {
|
|
39
|
+
return extractStringLiteral(init, checker)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
throw new Error('Unable to extract string literal from node')
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Check if node is string-like (string literal or template expression)
|
|
49
|
+
*/
|
|
50
|
+
export function isStringLike(node: ts.Node, _checker: ts.TypeChecker): boolean {
|
|
51
|
+
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) {
|
|
52
|
+
return true
|
|
53
|
+
}
|
|
54
|
+
// Check if it's a template string with substitutions
|
|
55
|
+
if (ts.isTemplateExpression(node)) {
|
|
56
|
+
return true
|
|
57
|
+
}
|
|
58
|
+
return false
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Check if node is function-like (arrow, function expression, or function declaration)
|
|
63
|
+
*/
|
|
64
|
+
export function isFunctionLike(node: ts.Node): boolean {
|
|
65
|
+
return (
|
|
66
|
+
ts.isArrowFunction(node) ||
|
|
67
|
+
ts.isFunctionExpression(node) ||
|
|
68
|
+
ts.isFunctionDeclaration(node)
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Extract number literal value from a node
|
|
74
|
+
*/
|
|
75
|
+
export function extractNumberLiteral(node: ts.Node): number | null {
|
|
76
|
+
if (ts.isNumericLiteral(node)) {
|
|
77
|
+
return Number(node.text)
|
|
78
|
+
}
|
|
79
|
+
return null
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Extract a property value from an object literal expression
|
|
84
|
+
* Returns the extracted value or null if not found/cannot extract
|
|
85
|
+
*/
|
|
86
|
+
export function extractPropertyString(
|
|
87
|
+
objNode: ts.ObjectLiteralExpression,
|
|
88
|
+
propertyName: string,
|
|
89
|
+
checker: ts.TypeChecker
|
|
90
|
+
): string | null {
|
|
91
|
+
for (const prop of objNode.properties) {
|
|
92
|
+
if (
|
|
93
|
+
ts.isPropertyAssignment(prop) &&
|
|
94
|
+
ts.isIdentifier(prop.name) &&
|
|
95
|
+
prop.name.text === propertyName
|
|
96
|
+
) {
|
|
97
|
+
return extractStringLiteral(prop.initializer, checker)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return null
|
|
101
|
+
}
|
|
@@ -112,6 +112,10 @@ export interface SerializableInspectorState {
|
|
|
112
112
|
meta: InspectorState['queueWorkers']['meta']
|
|
113
113
|
files: string[]
|
|
114
114
|
}
|
|
115
|
+
workflows: {
|
|
116
|
+
meta: InspectorState['workflows']['meta']
|
|
117
|
+
files: string[]
|
|
118
|
+
}
|
|
115
119
|
rpc: {
|
|
116
120
|
internalMeta: InspectorState['rpc']['internalMeta']
|
|
117
121
|
internalFiles: Array<[string, { path: string; exportedName: string }]>
|
|
@@ -243,6 +247,10 @@ export function serializeInspectorState(
|
|
|
243
247
|
meta: state.queueWorkers.meta,
|
|
244
248
|
files: Array.from(state.queueWorkers.files),
|
|
245
249
|
},
|
|
250
|
+
workflows: {
|
|
251
|
+
meta: state.workflows.meta,
|
|
252
|
+
files: Array.from(state.workflows.files),
|
|
253
|
+
},
|
|
246
254
|
rpc: {
|
|
247
255
|
internalMeta: state.rpc.internalMeta,
|
|
248
256
|
internalFiles: Array.from(state.rpc.internalFiles.entries()),
|
|
@@ -344,6 +352,10 @@ export function deserializeInspectorState(
|
|
|
344
352
|
meta: data.queueWorkers.meta,
|
|
345
353
|
files: new Set(data.queueWorkers.files),
|
|
346
354
|
},
|
|
355
|
+
workflows: {
|
|
356
|
+
meta: data.workflows.meta,
|
|
357
|
+
files: new Set(data.workflows.files),
|
|
358
|
+
},
|
|
347
359
|
rpc: {
|
|
348
360
|
internalMeta: data.rpc.internalMeta,
|
|
349
361
|
internalFiles: new Map(data.rpc.internalFiles),
|
package/src/utils/type-utils.ts
CHANGED
|
@@ -4,6 +4,75 @@ export const extractTypeKeys = (type: ts.Type): string[] => {
|
|
|
4
4
|
return type.getProperties().map((symbol) => symbol.getName())
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Resolve an identifier or call expression to the actual function declaration
|
|
9
|
+
*/
|
|
10
|
+
export function resolveFunctionDeclaration(
|
|
11
|
+
node: ts.Node,
|
|
12
|
+
checker: ts.TypeChecker
|
|
13
|
+
): ts.Node | null {
|
|
14
|
+
// If it's already a function-like node, return it
|
|
15
|
+
if (
|
|
16
|
+
ts.isFunctionDeclaration(node) ||
|
|
17
|
+
ts.isFunctionExpression(node) ||
|
|
18
|
+
ts.isArrowFunction(node)
|
|
19
|
+
) {
|
|
20
|
+
return node
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// If it's a call expression (e.g., pikkuWorkflowFunc(...)), get its first argument
|
|
24
|
+
if (ts.isCallExpression(node) && node.arguments.length > 0) {
|
|
25
|
+
const firstArg = node.arguments[0]
|
|
26
|
+
if (ts.isFunctionExpression(firstArg) || ts.isArrowFunction(firstArg)) {
|
|
27
|
+
return firstArg
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// If it's an identifier, resolve to declaration
|
|
32
|
+
if (ts.isIdentifier(node)) {
|
|
33
|
+
const symbol = checker.getSymbolAtLocation(node)
|
|
34
|
+
if (!symbol) return null
|
|
35
|
+
|
|
36
|
+
// Try valueDeclaration first, then fallback to declarations[0]
|
|
37
|
+
const decl = symbol.valueDeclaration || symbol.declarations?.[0]
|
|
38
|
+
if (!decl) return null
|
|
39
|
+
|
|
40
|
+
// If it's an import specifier, resolve the aliased symbol
|
|
41
|
+
if (ts.isImportSpecifier(decl)) {
|
|
42
|
+
const aliasedSymbol = checker.getAliasedSymbol(symbol)
|
|
43
|
+
if (aliasedSymbol) {
|
|
44
|
+
const aliasedDecl =
|
|
45
|
+
aliasedSymbol.valueDeclaration || aliasedSymbol.declarations?.[0]
|
|
46
|
+
if (aliasedDecl) {
|
|
47
|
+
// For variable declarations, get the initializer
|
|
48
|
+
if (
|
|
49
|
+
ts.isVariableDeclaration(aliasedDecl) &&
|
|
50
|
+
aliasedDecl.initializer
|
|
51
|
+
) {
|
|
52
|
+
return resolveFunctionDeclaration(aliasedDecl.initializer, checker)
|
|
53
|
+
}
|
|
54
|
+
// For function declarations, return directly
|
|
55
|
+
if (ts.isFunctionDeclaration(aliasedDecl)) {
|
|
56
|
+
return aliasedDecl
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// If it's a variable declaration, get the initializer
|
|
63
|
+
if (ts.isVariableDeclaration(decl) && decl.initializer) {
|
|
64
|
+
return resolveFunctionDeclaration(decl.initializer, checker)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// If it's a function declaration
|
|
68
|
+
if (ts.isFunctionDeclaration(decl)) {
|
|
69
|
+
return decl
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return null
|
|
74
|
+
}
|
|
75
|
+
|
|
7
76
|
export function getPropertyAssignmentInitializer(
|
|
8
77
|
obj: ts.ObjectLiteralExpression,
|
|
9
78
|
propName: string,
|
package/src/visit.ts
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'
|
|
@@ -74,6 +75,7 @@ export const visitSetup = (
|
|
|
74
75
|
addRPCInvocations(node, state, logger)
|
|
75
76
|
addMiddleware(logger, node, checker, state, options)
|
|
76
77
|
addPermission(logger, node, checker, state, options)
|
|
78
|
+
addWorkflow(logger, node, checker, state, options)
|
|
77
79
|
|
|
78
80
|
ts.forEachChild(node, (child) =>
|
|
79
81
|
visitSetup(logger, checker, child, state, options)
|