@pikku/inspector 0.10.2 → 0.11.1
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 +23 -0
- package/dist/add/add-channel.js +11 -10
- package/dist/add/add-file-with-factory.js +10 -10
- package/dist/add/add-functions.js +65 -44
- package/dist/add/add-http-route.js +5 -4
- package/dist/add/add-mcp-prompt.js +6 -5
- package/dist/add/add-mcp-resource.js +6 -5
- package/dist/add/add-mcp-tool.js +6 -5
- package/dist/add/add-middleware.js +1 -1
- package/dist/add/add-permission.js +1 -1
- package/dist/add/add-queue-worker.js +6 -5
- package/dist/add/add-schedule.js +5 -4
- package/dist/add/add-workflow.d.ts +6 -0
- package/dist/add/add-workflow.js +178 -0
- package/dist/error-codes.d.ts +5 -1
- package/dist/error-codes.js +5 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/inspector.js +13 -5
- package/dist/types.d.ts +27 -9
- package/dist/utils/extract-function-node.d.ts +10 -0
- package/dist/utils/extract-function-node.js +38 -0
- package/dist/utils/extract-node-value.d.ts +32 -0
- package/dist/utils/extract-node-value.js +103 -0
- package/dist/utils/extract-service-metadata.d.ts +19 -0
- package/dist/utils/extract-service-metadata.js +244 -0
- package/dist/utils/get-files-and-methods.d.ts +3 -3
- package/dist/utils/get-files-and-methods.js +3 -3
- package/dist/utils/get-property-value.d.ts +13 -6
- package/dist/utils/get-property-value.js +51 -43
- package/dist/utils/post-process.d.ts +9 -0
- package/dist/utils/post-process.js +30 -3
- package/dist/utils/serialize-inspector-state.d.ts +21 -4
- package/dist/utils/serialize-inspector-state.js +18 -8
- package/dist/utils/type-utils.d.ts +4 -0
- package/dist/utils/type-utils.js +55 -0
- package/dist/utils/write-service-metadata.d.ts +13 -0
- package/dist/utils/write-service-metadata.js +37 -0
- package/dist/visit.js +4 -2
- package/dist/workflow/extract-simple-workflow.d.ts +15 -0
- package/dist/workflow/extract-simple-workflow.js +803 -0
- package/dist/workflow/patterns.d.ts +39 -0
- package/dist/workflow/patterns.js +138 -0
- package/dist/workflow/validation.d.ts +28 -0
- package/dist/workflow/validation.js +124 -0
- package/package.json +4 -4
- package/src/add/add-channel.ts +37 -17
- package/src/add/add-file-with-factory.ts +10 -10
- package/src/add/add-functions.ts +81 -57
- package/src/add/add-http-route.ts +10 -5
- package/src/add/add-mcp-prompt.ts +11 -7
- package/src/add/add-mcp-resource.ts +11 -7
- package/src/add/add-mcp-tool.ts +11 -7
- package/src/add/add-middleware.ts +1 -1
- package/src/add/add-permission.ts +1 -1
- package/src/add/add-queue-worker.ts +11 -12
- package/src/add/add-schedule.ts +10 -5
- package/src/add/add-workflow.ts +241 -0
- package/src/error-codes.ts +6 -0
- package/src/index.ts +2 -0
- package/src/inspector.ts +19 -5
- package/src/types.ts +24 -9
- package/src/utils/extract-function-node.ts +58 -0
- package/src/utils/extract-node-value.ts +132 -0
- package/src/utils/extract-service-metadata.ts +353 -0
- package/src/utils/filter-inspector-state.test.ts +3 -3
- package/src/utils/filter-utils.test.ts +45 -51
- package/src/utils/get-files-and-methods.ts +11 -11
- package/src/utils/get-property-value.ts +60 -53
- package/src/utils/permissions.test.ts +3 -3
- package/src/utils/post-process.ts +56 -3
- package/src/utils/serialize-inspector-state.ts +37 -15
- package/src/utils/test-data/inspector-state.json +13 -9
- package/src/utils/type-utils.ts +69 -0
- package/src/utils/write-service-metadata.ts +51 -0
- package/src/visit.ts +5 -3
- package/src/workflow/extract-simple-workflow.ts +1035 -0
- package/src/workflow/patterns.ts +182 -0
- package/src/workflow/validation.ts +153 -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,241 @@
|
|
|
1
|
+
import * as ts from 'typescript'
|
|
2
|
+
import { AddWiring, InspectorState } from '../types.js'
|
|
3
|
+
import { extractFunctionName } from '../utils/extract-function-name.js'
|
|
4
|
+
import { extractFunctionNode } from '../utils/extract-function-node.js'
|
|
5
|
+
import { ErrorCode } from '../error-codes.js'
|
|
6
|
+
import { WorkflowStepMeta } from '@pikku/core/workflow'
|
|
7
|
+
import {
|
|
8
|
+
extractStringLiteral,
|
|
9
|
+
isStringLike,
|
|
10
|
+
isFunctionLike,
|
|
11
|
+
extractDescription,
|
|
12
|
+
extractDuration,
|
|
13
|
+
} from '../utils/extract-node-value.js'
|
|
14
|
+
import { extractSimpleWorkflow } from '../workflow/extract-simple-workflow.js'
|
|
15
|
+
import { getCommonWireMetaData } from '../utils/get-property-value.js'
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Scan for workflow.do(), workflow.sleep(), and workflow.cancel() calls to extract workflow steps
|
|
19
|
+
*/
|
|
20
|
+
function getWorkflowInvocations(
|
|
21
|
+
node: ts.Node,
|
|
22
|
+
checker: ts.TypeChecker,
|
|
23
|
+
state: InspectorState,
|
|
24
|
+
workflowName: string,
|
|
25
|
+
steps: WorkflowStepMeta[]
|
|
26
|
+
) {
|
|
27
|
+
// Look for property access expressions: workflow.do or workflow.sleep
|
|
28
|
+
if (ts.isPropertyAccessExpression(node)) {
|
|
29
|
+
const { name } = node
|
|
30
|
+
|
|
31
|
+
// Check if this is accessing 'do' or 'sleep' property
|
|
32
|
+
if (name.text === 'do' || name.text === 'sleep' || name.text === 'cancel') {
|
|
33
|
+
// Check if the parent is a call expression
|
|
34
|
+
const parent = node.parent
|
|
35
|
+
if (ts.isCallExpression(parent) && parent.expression === node) {
|
|
36
|
+
const args = parent.arguments
|
|
37
|
+
|
|
38
|
+
if (name.text === 'do' && args.length >= 2) {
|
|
39
|
+
// workflow.do(stepName, rpcName|fn, data?, options?)
|
|
40
|
+
const stepNameArg = args[0]
|
|
41
|
+
const secondArg = args[1]
|
|
42
|
+
const optionsArg =
|
|
43
|
+
args.length >= 3 ? args[args.length - 1] : undefined
|
|
44
|
+
|
|
45
|
+
const stepName = extractStringLiteral(stepNameArg, checker)
|
|
46
|
+
const description =
|
|
47
|
+
extractDescription(optionsArg, checker) ?? undefined
|
|
48
|
+
|
|
49
|
+
// Determine form by checking 2nd argument type
|
|
50
|
+
if (isStringLike(secondArg, checker)) {
|
|
51
|
+
// RPC form: workflow.do(stepName, rpcName, data, options?)
|
|
52
|
+
const rpcName = extractStringLiteral(secondArg, checker)
|
|
53
|
+
steps.push({
|
|
54
|
+
type: 'rpc',
|
|
55
|
+
stepName,
|
|
56
|
+
rpcName,
|
|
57
|
+
})
|
|
58
|
+
state.rpc.invokedFunctions.add(rpcName)
|
|
59
|
+
} else if (isFunctionLike(secondArg)) {
|
|
60
|
+
// Inline form: workflow.do(stepName, fn, options?)
|
|
61
|
+
steps.push({
|
|
62
|
+
type: 'inline',
|
|
63
|
+
stepName: stepName || '<dynamic>',
|
|
64
|
+
description: description || '<dynamic>',
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
} else if (name.text === 'sleep' && args.length >= 2) {
|
|
68
|
+
// workflow.sleep(stepName, duration)
|
|
69
|
+
const stepNameArg = args[0]
|
|
70
|
+
const durationArg = args[1]
|
|
71
|
+
|
|
72
|
+
const stepName = extractStringLiteral(stepNameArg, checker)
|
|
73
|
+
const duration = extractDuration(durationArg, checker)
|
|
74
|
+
|
|
75
|
+
steps.push({
|
|
76
|
+
type: 'sleep',
|
|
77
|
+
stepName: stepName || '<dynamic>',
|
|
78
|
+
duration: duration || '<dynamic>',
|
|
79
|
+
})
|
|
80
|
+
} else if (name.text === 'cancel') {
|
|
81
|
+
// workflow.cancel(reason?)
|
|
82
|
+
steps.push({
|
|
83
|
+
type: 'cancel',
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Don't recurse into nested functions - only look at top-level workflow calls
|
|
91
|
+
ts.forEachChild(node, (child) => {
|
|
92
|
+
if (
|
|
93
|
+
ts.isFunctionDeclaration(child) ||
|
|
94
|
+
ts.isFunctionExpression(child) ||
|
|
95
|
+
ts.isArrowFunction(child)
|
|
96
|
+
) {
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
getWorkflowInvocations(child, checker, state, workflowName, steps)
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Inspector for pikkuWorkflow() and pikkuSimpleWorkflow() calls
|
|
105
|
+
* Detects workflow registration and extracts metadata
|
|
106
|
+
*/
|
|
107
|
+
export const addWorkflow: AddWiring = (logger, node, checker, state) => {
|
|
108
|
+
if (!ts.isCallExpression(node)) {
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const args = node.arguments
|
|
113
|
+
const firstArg = args[0]
|
|
114
|
+
const expression = node.expression
|
|
115
|
+
|
|
116
|
+
if (!ts.isIdentifier(expression)) {
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
let wrapperType: 'simple' | 'regular' | null = null
|
|
121
|
+
if (expression.text === 'pikkuWorkflowFunc') {
|
|
122
|
+
wrapperType = 'regular'
|
|
123
|
+
} else if (expression.text === 'pikkuSimpleWorkflowFunc') {
|
|
124
|
+
wrapperType = 'simple'
|
|
125
|
+
} else {
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (!firstArg) {
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Extract workflow name and metadata using same logic as add-functions
|
|
134
|
+
const { pikkuFuncName, name, exportedName } = extractFunctionName(
|
|
135
|
+
node,
|
|
136
|
+
checker,
|
|
137
|
+
state.rootDir
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
const workflowName = exportedName || name
|
|
141
|
+
|
|
142
|
+
if (!workflowName) {
|
|
143
|
+
logger.critical(
|
|
144
|
+
ErrorCode.MISSING_NAME,
|
|
145
|
+
`Could not determine workflow name from export.`
|
|
146
|
+
)
|
|
147
|
+
return
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Extract the function node (either direct function or from config.func)
|
|
151
|
+
const { funcNode, resolvedFunc } = extractFunctionNode(firstArg, checker)
|
|
152
|
+
|
|
153
|
+
// Extract metadata if using object form
|
|
154
|
+
let tags: string[] | undefined
|
|
155
|
+
let summary: string | undefined
|
|
156
|
+
let description: string | undefined
|
|
157
|
+
let errors: string[] | undefined
|
|
158
|
+
|
|
159
|
+
if (ts.isObjectLiteralExpression(firstArg)) {
|
|
160
|
+
const metadata = getCommonWireMetaData(
|
|
161
|
+
firstArg,
|
|
162
|
+
'Workflow',
|
|
163
|
+
workflowName,
|
|
164
|
+
logger
|
|
165
|
+
)
|
|
166
|
+
tags = metadata.tags
|
|
167
|
+
summary = metadata.summary
|
|
168
|
+
description = metadata.description
|
|
169
|
+
errors = metadata.errors
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Validate that we got a valid function
|
|
173
|
+
if (
|
|
174
|
+
ts.isObjectLiteralExpression(firstArg) &&
|
|
175
|
+
(!funcNode || funcNode === firstArg)
|
|
176
|
+
) {
|
|
177
|
+
logger.critical(
|
|
178
|
+
ErrorCode.MISSING_FUNC,
|
|
179
|
+
`No valid 'func' property for workflow '${workflowName}'.`
|
|
180
|
+
)
|
|
181
|
+
return
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (!resolvedFunc) {
|
|
185
|
+
logger.critical(
|
|
186
|
+
ErrorCode.MISSING_FUNC,
|
|
187
|
+
`Could not resolve workflow function for '${workflowName}'.`
|
|
188
|
+
)
|
|
189
|
+
return
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Track workflow file for wiring generation
|
|
193
|
+
if (exportedName) {
|
|
194
|
+
state.workflows.files.set(pikkuFuncName, {
|
|
195
|
+
path: node.getSourceFile().fileName,
|
|
196
|
+
exportedName,
|
|
197
|
+
})
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
let steps: WorkflowStepMeta[] = []
|
|
201
|
+
let simple: boolean | undefined = undefined
|
|
202
|
+
|
|
203
|
+
// Try simple workflow extraction first
|
|
204
|
+
// Pass the whole CallExpression node so findWorkflowFunction can find the arrow function
|
|
205
|
+
const result = extractSimpleWorkflow(node, checker)
|
|
206
|
+
|
|
207
|
+
if (result.status === 'ok' && result.steps) {
|
|
208
|
+
// Simple extraction succeeded
|
|
209
|
+
steps = result.steps
|
|
210
|
+
simple = true
|
|
211
|
+
} else {
|
|
212
|
+
// Simple extraction failed
|
|
213
|
+
if (wrapperType === 'simple') {
|
|
214
|
+
// For pikkuSimpleWorkflowFunc, this is a critical error
|
|
215
|
+
logger.critical(
|
|
216
|
+
ErrorCode.INVALID_SIMPLE_WORKFLOW,
|
|
217
|
+
`Workflow '${workflowName}' uses pikkuSimpleWorkflowFunc but does not conform to simple workflow DSL:\n${result.reason || 'Unknown error'}`
|
|
218
|
+
)
|
|
219
|
+
return
|
|
220
|
+
} else {
|
|
221
|
+
// For pikkuWorkflowFunc, fall back to basic extraction
|
|
222
|
+
logger.debug(
|
|
223
|
+
`Workflow '${workflowName}' could not be extracted as simple workflow: ${result.reason || 'Unknown error'}. Falling back to basic extraction.`
|
|
224
|
+
)
|
|
225
|
+
simple = false
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
getWorkflowInvocations(resolvedFunc, checker, state, workflowName, steps)
|
|
230
|
+
|
|
231
|
+
state.workflows.meta[workflowName] = {
|
|
232
|
+
pikkuFuncName,
|
|
233
|
+
workflowName,
|
|
234
|
+
steps,
|
|
235
|
+
simple,
|
|
236
|
+
summary,
|
|
237
|
+
description,
|
|
238
|
+
errors,
|
|
239
|
+
tags,
|
|
240
|
+
}
|
|
241
|
+
}
|
package/src/error-codes.ts
CHANGED
|
@@ -19,6 +19,9 @@ 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',
|
|
24
|
+
INVALID_SIMPLE_WORKFLOW = 'PKU641',
|
|
22
25
|
|
|
23
26
|
// Configuration errors
|
|
24
27
|
CONFIG_TYPE_NOT_FOUND = 'PKU426',
|
|
@@ -40,4 +43,7 @@ export enum ErrorCode {
|
|
|
40
43
|
PERMISSION_TAG_INVALID = 'PKU836',
|
|
41
44
|
PERMISSION_EMPTY_ARRAY = 'PKU937',
|
|
42
45
|
PERMISSION_PATTERN_INVALID = 'PKU975',
|
|
46
|
+
|
|
47
|
+
// Feature Flag
|
|
48
|
+
WORKFLOW_MULTI_QUEUE_NOT_SUPPORTED = 'PKU901',
|
|
43
49
|
}
|
package/src/index.ts
CHANGED
|
@@ -14,3 +14,5 @@ export {
|
|
|
14
14
|
} from './utils/serialize-inspector-state.js'
|
|
15
15
|
export type { SerializableInspectorState } from './utils/serialize-inspector-state.js'
|
|
16
16
|
export { filterInspectorState } from './utils/filter-inspector-state.js'
|
|
17
|
+
export { writeAllServiceMetadata } from './utils/write-service-metadata.js'
|
|
18
|
+
export type { ServiceMetadata } from './utils/extract-service-metadata.js'
|
package/src/inspector.ts
CHANGED
|
@@ -5,7 +5,10 @@ import { TypesMap } from './types-map.js'
|
|
|
5
5
|
import { InspectorState, InspectorLogger, InspectorOptions } from './types.js'
|
|
6
6
|
import { getFilesAndMethods } from './utils/get-files-and-methods.js'
|
|
7
7
|
import { findCommonAncestor } from './utils/find-root-dir.js'
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
aggregateRequiredServices,
|
|
10
|
+
extractServiceInterfaceMetadata,
|
|
11
|
+
} from './utils/post-process.js'
|
|
9
12
|
|
|
10
13
|
/**
|
|
11
14
|
* Creates an initial/empty inspector state with all required properties initialized
|
|
@@ -16,12 +19,12 @@ export function getInitialInspectorState(rootDir: string): InspectorState {
|
|
|
16
19
|
return {
|
|
17
20
|
rootDir,
|
|
18
21
|
singletonServicesTypeImportMap: new Map(),
|
|
19
|
-
|
|
22
|
+
wireServicesTypeImportMap: new Map(),
|
|
20
23
|
userSessionTypeImportMap: new Map(),
|
|
21
24
|
configTypeImportMap: new Map(),
|
|
22
25
|
singletonServicesFactories: new Map(),
|
|
23
|
-
|
|
24
|
-
|
|
26
|
+
wireServicesFactories: new Map(),
|
|
27
|
+
wireServicesMeta: new Map(),
|
|
25
28
|
configFactories: new Map(),
|
|
26
29
|
filesAndMethods: {},
|
|
27
30
|
filesAndMethodsErrors: new Map(),
|
|
@@ -58,6 +61,10 @@ export function getInitialInspectorState(rootDir: string): InspectorState {
|
|
|
58
61
|
meta: {},
|
|
59
62
|
files: new Set(),
|
|
60
63
|
},
|
|
64
|
+
workflows: {
|
|
65
|
+
meta: {},
|
|
66
|
+
files: new Map(),
|
|
67
|
+
},
|
|
61
68
|
rpc: {
|
|
62
69
|
internalMeta: {},
|
|
63
70
|
internalFiles: new Map(),
|
|
@@ -92,8 +99,9 @@ export function getInitialInspectorState(rootDir: string): InspectorState {
|
|
|
92
99
|
usedMiddleware: new Set(),
|
|
93
100
|
usedPermissions: new Set(),
|
|
94
101
|
allSingletonServices: [],
|
|
95
|
-
|
|
102
|
+
allWireServices: [],
|
|
96
103
|
},
|
|
104
|
+
serviceMetadata: [],
|
|
97
105
|
}
|
|
98
106
|
}
|
|
99
107
|
|
|
@@ -173,6 +181,12 @@ export const inspect = (
|
|
|
173
181
|
logger.debug(
|
|
174
182
|
`Aggregate required services completed in ${(performance.now() - startAggregate).toFixed(2)}ms`
|
|
175
183
|
)
|
|
184
|
+
|
|
185
|
+
const startServiceMeta = performance.now()
|
|
186
|
+
extractServiceInterfaceMetadata(state, checker)
|
|
187
|
+
logger.debug(
|
|
188
|
+
`Extract service metadata completed in ${(performance.now() - startServiceMeta).toFixed(2)}ms`
|
|
189
|
+
)
|
|
176
190
|
}
|
|
177
191
|
|
|
178
192
|
return state
|
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'
|
|
@@ -119,7 +120,7 @@ export type InspectorOptions = Partial<{
|
|
|
119
120
|
configFileType: string
|
|
120
121
|
userSessionType: string
|
|
121
122
|
singletonServicesFactoryType: string
|
|
122
|
-
|
|
123
|
+
wireServicesFactoryType: string
|
|
123
124
|
}>
|
|
124
125
|
}>
|
|
125
126
|
|
|
@@ -146,7 +147,7 @@ export interface InspectorFilesAndMethods {
|
|
|
146
147
|
type: string
|
|
147
148
|
typePath: string
|
|
148
149
|
}
|
|
149
|
-
|
|
150
|
+
wireServicesType?: {
|
|
150
151
|
file: string
|
|
151
152
|
variable: string
|
|
152
153
|
type: string
|
|
@@ -176,7 +177,7 @@ export interface InspectorFilesAndMethods {
|
|
|
176
177
|
type: string
|
|
177
178
|
typePath: string
|
|
178
179
|
}
|
|
179
|
-
|
|
180
|
+
wireServicesFactory?: {
|
|
180
181
|
file: string
|
|
181
182
|
variable: string
|
|
182
183
|
type: string
|
|
@@ -187,12 +188,12 @@ export interface InspectorFilesAndMethods {
|
|
|
187
188
|
export interface InspectorState {
|
|
188
189
|
rootDir: string // Root directory inferred from source files
|
|
189
190
|
singletonServicesTypeImportMap: PathToNameAndType
|
|
190
|
-
|
|
191
|
+
wireServicesTypeImportMap: PathToNameAndType
|
|
191
192
|
userSessionTypeImportMap: PathToNameAndType
|
|
192
193
|
configTypeImportMap: PathToNameAndType
|
|
193
194
|
singletonServicesFactories: PathToNameAndType
|
|
194
|
-
|
|
195
|
-
|
|
195
|
+
wireServicesFactories: PathToNameAndType
|
|
196
|
+
wireServicesMeta: Map<string, string[]> // variable name -> singleton services consumed
|
|
196
197
|
configFactories: PathToNameAndType
|
|
197
198
|
filesAndMethods: InspectorFilesAndMethods
|
|
198
199
|
filesAndMethodsErrors: Map<string, PathToNameAndType>
|
|
@@ -205,9 +206,13 @@ export interface InspectorState {
|
|
|
205
206
|
files: Set<string>
|
|
206
207
|
}
|
|
207
208
|
queueWorkers: {
|
|
208
|
-
meta:
|
|
209
|
+
meta: QueueWorkersMeta
|
|
209
210
|
files: Set<string>
|
|
210
211
|
}
|
|
212
|
+
workflows: {
|
|
213
|
+
meta: WorkflowsMeta
|
|
214
|
+
files: Map<string, { path: string; exportedName: string }>
|
|
215
|
+
}
|
|
211
216
|
rpc: {
|
|
212
217
|
internalMeta: Record<string, string>
|
|
213
218
|
internalFiles: Map<string, { path: string; exportedName: string }>
|
|
@@ -233,6 +238,16 @@ export interface InspectorState {
|
|
|
233
238
|
usedMiddleware: Set<string> // Middleware names used by wired functions
|
|
234
239
|
usedPermissions: Set<string> // Permission names used by wired functions
|
|
235
240
|
allSingletonServices: string[] // All services available in SingletonServices type
|
|
236
|
-
|
|
241
|
+
allWireServices: string[] // All services available in Services type (excluding SingletonServices)
|
|
237
242
|
}
|
|
243
|
+
serviceMetadata: Array<{
|
|
244
|
+
name: string
|
|
245
|
+
summary: string
|
|
246
|
+
description: string
|
|
247
|
+
package: string
|
|
248
|
+
path: string
|
|
249
|
+
version: string
|
|
250
|
+
interface: string
|
|
251
|
+
expandedProperties: Record<string, string>
|
|
252
|
+
}>
|
|
238
253
|
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import * as ts from 'typescript'
|
|
2
|
+
import {
|
|
3
|
+
getPropertyAssignmentInitializer,
|
|
4
|
+
resolveFunctionDeclaration,
|
|
5
|
+
} from './type-utils.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Extracts the actual function node from a pikkuFunc/pikkuWorkflowFunc call
|
|
9
|
+
* Handles both direct function form and config object form { func: ... }
|
|
10
|
+
*/
|
|
11
|
+
export function extractFunctionNode(
|
|
12
|
+
firstArg: ts.Expression,
|
|
13
|
+
checker: ts.TypeChecker
|
|
14
|
+
): {
|
|
15
|
+
funcNode: ts.Node
|
|
16
|
+
resolvedFunc: ts.Node | null
|
|
17
|
+
isDirectFunction: boolean
|
|
18
|
+
} {
|
|
19
|
+
let funcNode: ts.Node = firstArg
|
|
20
|
+
let isDirectFunction = true
|
|
21
|
+
|
|
22
|
+
// Check if first argument is a config object with 'func' property
|
|
23
|
+
if (ts.isObjectLiteralExpression(firstArg)) {
|
|
24
|
+
isDirectFunction = false
|
|
25
|
+
const funcInitializer = getPropertyAssignmentInitializer(
|
|
26
|
+
firstArg,
|
|
27
|
+
'func',
|
|
28
|
+
true,
|
|
29
|
+
checker
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
if (funcInitializer) {
|
|
33
|
+
funcNode = funcInitializer
|
|
34
|
+
} else {
|
|
35
|
+
// Return the original node if no func property found
|
|
36
|
+
// Caller should handle validation
|
|
37
|
+
funcNode = firstArg
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Resolve identifier to get the actual function node
|
|
42
|
+
if (ts.isIdentifier(funcNode)) {
|
|
43
|
+
const symbol = checker.getSymbolAtLocation(funcNode)
|
|
44
|
+
const decl = symbol?.valueDeclaration || symbol?.declarations?.[0]
|
|
45
|
+
if (decl && ts.isVariableDeclaration(decl) && decl.initializer) {
|
|
46
|
+
funcNode = decl.initializer
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Resolve function declaration for deeper analysis
|
|
51
|
+
const resolvedFunc = resolveFunctionDeclaration(funcNode, checker)
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
funcNode,
|
|
55
|
+
resolvedFunc,
|
|
56
|
+
isDirectFunction,
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
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
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Extract description from options object
|
|
105
|
+
*/
|
|
106
|
+
export function extractDescription(
|
|
107
|
+
optionsNode: ts.Node | undefined,
|
|
108
|
+
checker: ts.TypeChecker
|
|
109
|
+
): string | null {
|
|
110
|
+
if (!optionsNode || !ts.isObjectLiteralExpression(optionsNode)) {
|
|
111
|
+
return null
|
|
112
|
+
}
|
|
113
|
+
return extractPropertyString(optionsNode, 'description', checker)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Extract duration value (number or string)
|
|
118
|
+
*/
|
|
119
|
+
export function extractDuration(
|
|
120
|
+
node: ts.Node,
|
|
121
|
+
checker: ts.TypeChecker
|
|
122
|
+
): string | number | null {
|
|
123
|
+
const numValue = extractNumberLiteral(node)
|
|
124
|
+
if (numValue !== null) {
|
|
125
|
+
return numValue
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
return extractStringLiteral(node, checker)
|
|
129
|
+
} catch {
|
|
130
|
+
return null
|
|
131
|
+
}
|
|
132
|
+
}
|