@pikku/inspector 0.9.5 → 0.10.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 +14 -0
- package/dist/add/add-channel.d.ts +17 -0
- package/dist/{add-channel.js → add/add-channel.js} +60 -34
- package/dist/add/add-cli.d.ts +9 -0
- package/dist/add/add-cli.js +566 -0
- package/dist/{add-file-extends-core-type.d.ts → add/add-file-extends-core-type.d.ts} +2 -2
- package/dist/{add-file-extends-core-type.js → add/add-file-extends-core-type.js} +17 -4
- package/dist/{add-file-with-config.d.ts → add/add-file-with-config.d.ts} +1 -1
- package/dist/{add-file-with-config.js → add/add-file-with-config.js} +1 -1
- package/dist/{add-file-with-factory.d.ts → add/add-file-with-factory.d.ts} +2 -2
- package/dist/{add-file-with-factory.js → add/add-file-with-factory.js} +38 -5
- package/dist/add/add-functions.d.ts +6 -0
- package/dist/{add-functions.js → add/add-functions.js} +77 -10
- package/dist/{add-http-route.d.ts → add/add-http-route.d.ts} +2 -3
- package/dist/{add-http-route.js → add/add-http-route.js} +26 -13
- package/dist/add/add-mcp-prompt.d.ts +2 -0
- package/dist/add/add-mcp-prompt.js +74 -0
- package/dist/add/add-mcp-resource.d.ts +2 -0
- package/dist/add/add-mcp-resource.js +84 -0
- package/dist/add/add-mcp-tool.d.ts +2 -0
- package/dist/add/add-mcp-tool.js +80 -0
- package/dist/add/add-middleware.d.ts +5 -0
- package/dist/add/add-middleware.js +290 -0
- package/dist/add/add-permission.d.ts +5 -0
- package/dist/add/add-permission.js +292 -0
- package/dist/add/add-queue-worker.d.ts +2 -0
- package/dist/add/add-queue-worker.js +52 -0
- package/dist/{add-rpc-invocations.d.ts → add/add-rpc-invocations.d.ts} +1 -1
- package/dist/add/add-schedule.d.ts +2 -0
- package/dist/{add-schedule.js → add/add-schedule.js} +16 -11
- package/dist/error-codes.d.ts +35 -0
- package/dist/error-codes.js +40 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +4 -0
- package/dist/inspector.d.ts +2 -3
- package/dist/inspector.js +38 -8
- package/dist/types.d.ts +108 -1
- package/dist/utils/ensure-function-metadata.d.ts +6 -0
- package/dist/utils/ensure-function-metadata.js +18 -0
- package/dist/utils/extract-function-name.d.ts +31 -0
- package/dist/{utils.js → utils/extract-function-name.js} +35 -149
- package/dist/utils/extract-services.d.ts +6 -0
- package/dist/utils/extract-services.js +29 -0
- package/dist/utils/filter-inspector-state.d.ts +6 -0
- package/dist/utils/filter-inspector-state.js +382 -0
- package/dist/utils/filter-utils.d.ts +19 -0
- package/dist/utils/filter-utils.js +109 -0
- package/dist/utils/find-root-dir.d.ts +23 -0
- package/dist/utils/find-root-dir.js +55 -0
- package/dist/utils/get-files-and-methods.d.ts +22 -0
- package/dist/utils/get-files-and-methods.js +61 -0
- package/dist/utils/get-property-value.d.ts +12 -0
- package/dist/{get-property-value.js → utils/get-property-value.js} +20 -0
- package/dist/utils/middleware.d.ts +39 -0
- package/dist/utils/middleware.js +157 -0
- package/dist/utils/permissions.d.ts +43 -0
- package/dist/utils/permissions.js +178 -0
- package/dist/utils/post-process.d.ts +16 -0
- package/dist/utils/post-process.js +132 -0
- package/dist/utils/serialize-inspector-state.d.ts +179 -0
- package/dist/utils/serialize-inspector-state.js +170 -0
- package/dist/utils/type-utils.d.ts +3 -0
- package/dist/utils/type-utils.js +50 -0
- package/dist/visit.d.ts +3 -3
- package/dist/visit.js +35 -31
- package/package.json +5 -6
- package/src/{add-channel.ts → add/add-channel.ts} +108 -56
- package/src/add/add-cli.ts +822 -0
- package/src/{add-file-extends-core-type.ts → add/add-file-extends-core-type.ts} +23 -5
- package/src/{add-file-with-config.ts → add/add-file-with-config.ts} +2 -2
- package/src/{add-file-with-factory.ts → add/add-file-with-factory.ts} +49 -6
- package/src/{add-functions.ts → add/add-functions.ts} +89 -19
- package/src/{add-http-route.ts → add/add-http-route.ts} +66 -32
- package/src/add/add-mcp-prompt.ts +128 -0
- package/src/add/add-mcp-prompt.ts.tmp +0 -0
- package/src/add/add-mcp-resource.ts +145 -0
- package/src/add/add-mcp-resource.ts.tmp +0 -0
- package/src/add/add-mcp-tool.ts +137 -0
- package/src/add/add-middleware.ts +385 -0
- package/src/add/add-permission.ts +391 -0
- package/src/add/add-queue-worker.ts +92 -0
- package/src/{add-rpc-invocations.ts → add/add-rpc-invocations.ts} +1 -1
- package/src/{add-schedule.ts → add/add-schedule.ts} +30 -28
- package/src/error-codes.ts +43 -0
- package/src/index.ts +12 -0
- package/src/inspector.ts +41 -17
- package/src/types.ts +128 -1
- package/src/utils/ensure-function-metadata.ts +24 -0
- package/src/{utils.ts → utils/extract-function-name.ts} +44 -206
- package/src/utils/extract-services.ts +35 -0
- package/src/utils/filter-inspector-state.test.ts +1433 -0
- package/src/utils/filter-inspector-state.ts +526 -0
- package/src/{utils.test.ts → utils/filter-utils.test.ts} +351 -2
- package/src/utils/filter-utils.ts +152 -0
- package/src/utils/find-root-dir.ts +68 -0
- package/src/utils/get-files-and-methods.ts +151 -0
- package/src/{get-property-value.ts → utils/get-property-value.ts} +27 -0
- package/src/utils/middleware.ts +241 -0
- package/src/utils/permissions.test.ts +327 -0
- package/src/utils/permissions.ts +262 -0
- package/src/utils/post-process.ts +178 -0
- package/src/utils/serialize-inspector-state.ts +375 -0
- package/src/utils/test-data/inspector-state.json +1680 -0
- package/src/utils/type-utils.ts +74 -0
- package/src/visit.ts +50 -34
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/add-channel.d.ts +0 -13
- package/dist/add-functions.d.ts +0 -7
- package/dist/add-mcp-prompt.d.ts +0 -3
- package/dist/add-mcp-prompt.js +0 -61
- package/dist/add-mcp-resource.d.ts +0 -3
- package/dist/add-mcp-resource.js +0 -68
- package/dist/add-mcp-tool.d.ts +0 -3
- package/dist/add-mcp-tool.js +0 -64
- package/dist/add-middleware.d.ts +0 -7
- package/dist/add-middleware.js +0 -35
- package/dist/add-permission.d.ts +0 -7
- package/dist/add-permission.js +0 -35
- package/dist/add-queue-worker.d.ts +0 -3
- package/dist/add-queue-worker.js +0 -48
- package/dist/add-schedule.d.ts +0 -3
- package/dist/get-property-value.d.ts +0 -3
- package/dist/utils.d.ts +0 -39
- package/src/add-mcp-prompt.ts +0 -104
- package/src/add-mcp-resource.ts +0 -116
- package/src/add-mcp-tool.ts +0 -107
- package/src/add-middleware.ts +0 -51
- package/src/add-permission.ts +0 -53
- package/src/add-queue-worker.ts +0 -92
- /package/dist/{add-rpc-invocations.js → add/add-rpc-invocations.js} +0 -0
- /package/dist/{does-type-extend-core-type.d.ts → utils/does-type-extend-core-type.d.ts} +0 -0
- /package/dist/{does-type-extend-core-type.js → utils/does-type-extend-core-type.js} +0 -0
- /package/src/{does-type-extend-core-type.ts → utils/does-type-extend-core-type.ts} +0 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import * as ts from 'typescript'
|
|
2
|
+
import {
|
|
3
|
+
getPropertyValue,
|
|
4
|
+
getPropertyTags,
|
|
5
|
+
} from '../utils/get-property-value.js'
|
|
6
|
+
import { extractWireNames } from '../utils/post-process.js'
|
|
7
|
+
import { ensureFunctionMetadata } from '../utils/ensure-function-metadata.js'
|
|
8
|
+
import { AddWiring } from '../types.js'
|
|
9
|
+
import { extractFunctionName } from '../utils/extract-function-name.js'
|
|
10
|
+
import { getPropertyAssignmentInitializer } from '../utils/type-utils.js'
|
|
11
|
+
import { resolveMiddleware } from '../utils/middleware.js'
|
|
12
|
+
import { resolvePermissions } from '../utils/permissions.js'
|
|
13
|
+
import { ErrorCode } from '../error-codes.js'
|
|
14
|
+
|
|
15
|
+
export const addMCPResource: AddWiring = (
|
|
16
|
+
logger,
|
|
17
|
+
node,
|
|
18
|
+
checker,
|
|
19
|
+
state,
|
|
20
|
+
options
|
|
21
|
+
) => {
|
|
22
|
+
if (!ts.isCallExpression(node)) {
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const args = node.arguments
|
|
27
|
+
const firstArg = args[0]
|
|
28
|
+
const expression = node.expression
|
|
29
|
+
|
|
30
|
+
// Check if the call is to wireMCPResource
|
|
31
|
+
if (!ts.isIdentifier(expression) || expression.text !== 'wireMCPResource') {
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!firstArg) {
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (ts.isObjectLiteralExpression(firstArg)) {
|
|
40
|
+
const obj = firstArg
|
|
41
|
+
|
|
42
|
+
const uriValue = getPropertyValue(obj, 'uri') as string | null
|
|
43
|
+
const titleValue = getPropertyValue(obj, 'title') as string | null
|
|
44
|
+
const descriptionValue = getPropertyValue(obj, 'description') as
|
|
45
|
+
| string
|
|
46
|
+
| null
|
|
47
|
+
const streamingValue = getPropertyValue(obj, 'streaming') as boolean | null
|
|
48
|
+
const tags = getPropertyTags(obj, 'MCP resource', uriValue, logger)
|
|
49
|
+
|
|
50
|
+
if (streamingValue === true) {
|
|
51
|
+
logger.warn(
|
|
52
|
+
`MCP resource '${uriValue}' has streaming enabled, but streaming is not yet supported.`
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const funcInitializer = getPropertyAssignmentInitializer(
|
|
57
|
+
obj,
|
|
58
|
+
'func',
|
|
59
|
+
true,
|
|
60
|
+
checker
|
|
61
|
+
)
|
|
62
|
+
if (!funcInitializer) {
|
|
63
|
+
logger.critical(
|
|
64
|
+
ErrorCode.MISSING_FUNC,
|
|
65
|
+
`No valid 'func' property for MCP resource '${uriValue}'.`
|
|
66
|
+
)
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const pikkuFuncName = extractFunctionName(
|
|
71
|
+
funcInitializer,
|
|
72
|
+
checker,
|
|
73
|
+
state.rootDir
|
|
74
|
+
).pikkuFuncName
|
|
75
|
+
|
|
76
|
+
// Ensure function metadata exists (creates stub for inline functions)
|
|
77
|
+
ensureFunctionMetadata(state, pikkuFuncName, uriValue || undefined)
|
|
78
|
+
|
|
79
|
+
if (!uriValue) {
|
|
80
|
+
logger.critical(
|
|
81
|
+
ErrorCode.MISSING_URI,
|
|
82
|
+
"MCP resource is missing the required 'uri' property."
|
|
83
|
+
)
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (!titleValue) {
|
|
88
|
+
logger.critical(
|
|
89
|
+
ErrorCode.MISSING_TITLE,
|
|
90
|
+
`MCP resource '${uriValue}' is missing the required 'title' property.`
|
|
91
|
+
)
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!descriptionValue) {
|
|
96
|
+
logger.critical(
|
|
97
|
+
ErrorCode.MISSING_DESCRIPTION,
|
|
98
|
+
`MCP resource '${uriValue}' is missing a description.`
|
|
99
|
+
)
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// lookup existing function metadata
|
|
104
|
+
const fnMeta = state.functions.meta[pikkuFuncName]
|
|
105
|
+
if (!fnMeta) {
|
|
106
|
+
logger.critical(
|
|
107
|
+
ErrorCode.FUNCTION_METADATA_NOT_FOUND,
|
|
108
|
+
`No function metadata found for '${pikkuFuncName}'.`
|
|
109
|
+
)
|
|
110
|
+
return
|
|
111
|
+
}
|
|
112
|
+
const inputSchema = fnMeta.inputs?.[0] || null
|
|
113
|
+
const outputSchema = fnMeta.outputs?.[0] || null
|
|
114
|
+
|
|
115
|
+
// --- resolve middleware ---
|
|
116
|
+
const middleware = resolveMiddleware(state, obj, tags, checker)
|
|
117
|
+
|
|
118
|
+
// --- resolve permissions ---
|
|
119
|
+
const permissions = resolvePermissions(state, obj, tags, checker)
|
|
120
|
+
|
|
121
|
+
// --- track used functions/middleware/permissions for service aggregation ---
|
|
122
|
+
state.serviceAggregation.usedFunctions.add(pikkuFuncName)
|
|
123
|
+
extractWireNames(middleware).forEach((name) =>
|
|
124
|
+
state.serviceAggregation.usedMiddleware.add(name)
|
|
125
|
+
)
|
|
126
|
+
extractWireNames(permissions).forEach((name) =>
|
|
127
|
+
state.serviceAggregation.usedPermissions.add(name)
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
state.mcpEndpoints.files.add(node.getSourceFile().fileName)
|
|
131
|
+
|
|
132
|
+
state.mcpEndpoints.resourcesMeta[uriValue] = {
|
|
133
|
+
pikkuFuncName,
|
|
134
|
+
uri: uriValue,
|
|
135
|
+
title: titleValue,
|
|
136
|
+
description: descriptionValue,
|
|
137
|
+
...(streamingValue !== null && { streaming: streamingValue }),
|
|
138
|
+
tags,
|
|
139
|
+
inputSchema,
|
|
140
|
+
outputSchema,
|
|
141
|
+
middleware,
|
|
142
|
+
permissions,
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import * as ts from 'typescript'
|
|
2
|
+
import {
|
|
3
|
+
getPropertyValue,
|
|
4
|
+
getPropertyTags,
|
|
5
|
+
} from '../utils/get-property-value.js'
|
|
6
|
+
import { extractWireNames } from '../utils/post-process.js'
|
|
7
|
+
import { ensureFunctionMetadata } from '../utils/ensure-function-metadata.js'
|
|
8
|
+
import { AddWiring } from '../types.js'
|
|
9
|
+
import { extractFunctionName } from '../utils/extract-function-name.js'
|
|
10
|
+
import { getPropertyAssignmentInitializer } from '../utils/type-utils.js'
|
|
11
|
+
import { resolveMiddleware } from '../utils/middleware.js'
|
|
12
|
+
import { resolvePermissions } from '../utils/permissions.js'
|
|
13
|
+
import { ErrorCode } from '../error-codes.js'
|
|
14
|
+
|
|
15
|
+
export const addMCPTool: AddWiring = (
|
|
16
|
+
logger,
|
|
17
|
+
node,
|
|
18
|
+
checker,
|
|
19
|
+
state,
|
|
20
|
+
options
|
|
21
|
+
) => {
|
|
22
|
+
if (!ts.isCallExpression(node)) {
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const args = node.arguments
|
|
27
|
+
const firstArg = args[0]
|
|
28
|
+
const expression = node.expression
|
|
29
|
+
|
|
30
|
+
// Check if the call is to wireMCPTool
|
|
31
|
+
if (!ts.isIdentifier(expression) || expression.text !== 'wireMCPTool') {
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!firstArg) {
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (ts.isObjectLiteralExpression(firstArg)) {
|
|
40
|
+
const obj = firstArg
|
|
41
|
+
|
|
42
|
+
const nameValue = getPropertyValue(obj, 'name') as string | null
|
|
43
|
+
const titleValue = getPropertyValue(obj, 'title') as string | null
|
|
44
|
+
const descriptionValue = getPropertyValue(obj, 'description') as
|
|
45
|
+
| string
|
|
46
|
+
| null
|
|
47
|
+
const streamingValue = getPropertyValue(obj, 'streaming') as boolean | null
|
|
48
|
+
const tags = getPropertyTags(obj, 'MCP tool', nameValue, logger)
|
|
49
|
+
|
|
50
|
+
if (streamingValue === true) {
|
|
51
|
+
logger.warn(
|
|
52
|
+
`MCP tool '${nameValue}' has streaming enabled, but streaming is not yet supported.`
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const funcInitializer = getPropertyAssignmentInitializer(
|
|
57
|
+
obj,
|
|
58
|
+
'func',
|
|
59
|
+
true,
|
|
60
|
+
checker
|
|
61
|
+
)
|
|
62
|
+
if (!funcInitializer) {
|
|
63
|
+
logger.critical(
|
|
64
|
+
ErrorCode.MISSING_FUNC,
|
|
65
|
+
`No valid 'func' property for MCP tool '${nameValue}'.`
|
|
66
|
+
)
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const pikkuFuncName = extractFunctionName(
|
|
71
|
+
funcInitializer,
|
|
72
|
+
checker,
|
|
73
|
+
state.rootDir
|
|
74
|
+
).pikkuFuncName
|
|
75
|
+
|
|
76
|
+
// Ensure function metadata exists (creates stub for inline functions)
|
|
77
|
+
ensureFunctionMetadata(state, pikkuFuncName, nameValue || undefined)
|
|
78
|
+
|
|
79
|
+
if (!nameValue) {
|
|
80
|
+
logger.critical(
|
|
81
|
+
ErrorCode.MISSING_NAME,
|
|
82
|
+
"MCP tool is missing the required 'name' property."
|
|
83
|
+
)
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (!descriptionValue) {
|
|
88
|
+
logger.critical(
|
|
89
|
+
ErrorCode.MISSING_DESCRIPTION,
|
|
90
|
+
`MCP tool '${nameValue}' is missing a description.`
|
|
91
|
+
)
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// lookup existing function metadata
|
|
96
|
+
const fnMeta = state.functions.meta[pikkuFuncName]
|
|
97
|
+
if (!fnMeta) {
|
|
98
|
+
logger.critical(
|
|
99
|
+
ErrorCode.FUNCTION_METADATA_NOT_FOUND,
|
|
100
|
+
`No function metadata found for '${pikkuFuncName}'.`
|
|
101
|
+
)
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
const inputSchema = fnMeta.inputs?.[0] || null
|
|
105
|
+
const outputSchema = fnMeta.outputs?.[0] || null
|
|
106
|
+
|
|
107
|
+
// --- resolve middleware ---
|
|
108
|
+
const middleware = resolveMiddleware(state, obj, tags, checker)
|
|
109
|
+
|
|
110
|
+
// --- resolve permissions ---
|
|
111
|
+
const permissions = resolvePermissions(state, obj, tags, checker)
|
|
112
|
+
|
|
113
|
+
// --- track used functions/middleware/permissions for service aggregation ---
|
|
114
|
+
state.serviceAggregation.usedFunctions.add(pikkuFuncName)
|
|
115
|
+
extractWireNames(middleware).forEach((name) =>
|
|
116
|
+
state.serviceAggregation.usedMiddleware.add(name)
|
|
117
|
+
)
|
|
118
|
+
extractWireNames(permissions).forEach((name) =>
|
|
119
|
+
state.serviceAggregation.usedPermissions.add(name)
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
state.mcpEndpoints.files.add(node.getSourceFile().fileName)
|
|
123
|
+
|
|
124
|
+
state.mcpEndpoints.toolsMeta[nameValue] = {
|
|
125
|
+
pikkuFuncName,
|
|
126
|
+
name: nameValue,
|
|
127
|
+
title: titleValue || undefined,
|
|
128
|
+
description: descriptionValue,
|
|
129
|
+
...(streamingValue !== null && { streaming: streamingValue }),
|
|
130
|
+
tags,
|
|
131
|
+
inputSchema,
|
|
132
|
+
outputSchema,
|
|
133
|
+
middleware,
|
|
134
|
+
permissions,
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
import * as ts from 'typescript'
|
|
2
|
+
import { AddWiring } from '../types.js'
|
|
3
|
+
import {
|
|
4
|
+
extractFunctionName,
|
|
5
|
+
isNamedExport,
|
|
6
|
+
} from '../utils/extract-function-name.js'
|
|
7
|
+
import { extractServicesFromFunction } from '../utils/extract-services.js'
|
|
8
|
+
import { extractMiddlewarePikkuNames } from '../utils/middleware.js'
|
|
9
|
+
import { getPropertyValue } from '../utils/get-property-value.js'
|
|
10
|
+
import { getPropertyAssignmentInitializer } from '../utils/type-utils.js'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Inspect pikkuMiddleware calls, addMiddleware calls, and addHTTPMiddleware calls
|
|
14
|
+
*/
|
|
15
|
+
export const addMiddleware: AddWiring = (logger, node, checker, state) => {
|
|
16
|
+
if (!ts.isCallExpression(node)) return
|
|
17
|
+
|
|
18
|
+
const { expression, arguments: args } = node
|
|
19
|
+
|
|
20
|
+
// only handle specific function calls
|
|
21
|
+
if (!ts.isIdentifier(expression)) {
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Handle pikkuMiddleware(...) - individual middleware function definition
|
|
26
|
+
if (expression.text === 'pikkuMiddleware') {
|
|
27
|
+
const arg = args[0]
|
|
28
|
+
if (!arg) return
|
|
29
|
+
|
|
30
|
+
let actualHandler: ts.ArrowFunction | ts.FunctionExpression
|
|
31
|
+
let name: string | undefined
|
|
32
|
+
let description: string | undefined
|
|
33
|
+
|
|
34
|
+
// Check if using object syntax: pikkuMiddleware({ func: ..., name: '...', description: '...' })
|
|
35
|
+
if (ts.isObjectLiteralExpression(arg)) {
|
|
36
|
+
// Extract name and description metadata
|
|
37
|
+
const nameValue = getPropertyValue(arg, 'name')
|
|
38
|
+
const descValue = getPropertyValue(arg, 'description')
|
|
39
|
+
name = typeof nameValue === 'string' ? nameValue : undefined
|
|
40
|
+
description = typeof descValue === 'string' ? descValue : undefined
|
|
41
|
+
|
|
42
|
+
// Extract the func property
|
|
43
|
+
const fnProp = getPropertyAssignmentInitializer(
|
|
44
|
+
arg,
|
|
45
|
+
'func',
|
|
46
|
+
true,
|
|
47
|
+
checker
|
|
48
|
+
)
|
|
49
|
+
if (
|
|
50
|
+
!fnProp ||
|
|
51
|
+
(!ts.isArrowFunction(fnProp) && !ts.isFunctionExpression(fnProp))
|
|
52
|
+
) {
|
|
53
|
+
logger.error(
|
|
54
|
+
`• pikkuMiddleware object missing required 'func' property.`
|
|
55
|
+
)
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
actualHandler = fnProp
|
|
59
|
+
} else if (ts.isArrowFunction(arg) || ts.isFunctionExpression(arg)) {
|
|
60
|
+
actualHandler = arg
|
|
61
|
+
} else {
|
|
62
|
+
logger.error(`• Handler for pikkuMiddleware is not a function.`)
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const services = extractServicesFromFunction(actualHandler)
|
|
67
|
+
const { pikkuFuncName, exportedName } = extractFunctionName(
|
|
68
|
+
node,
|
|
69
|
+
checker,
|
|
70
|
+
state.rootDir
|
|
71
|
+
)
|
|
72
|
+
state.middleware.meta[pikkuFuncName] = {
|
|
73
|
+
services,
|
|
74
|
+
sourceFile: node.getSourceFile().fileName,
|
|
75
|
+
position: node.getStart(),
|
|
76
|
+
exportedName,
|
|
77
|
+
name,
|
|
78
|
+
description,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
logger.debug(
|
|
82
|
+
`• Found middleware with services: ${services.services.join(', ')}${name ? ` (name: ${name})` : ''}${description ? ` (description: ${description})` : ''}`
|
|
83
|
+
)
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Handle pikkuMiddlewareFactory(...) - middleware factory function
|
|
88
|
+
if (expression.text === 'pikkuMiddlewareFactory') {
|
|
89
|
+
const factoryNode = args[0]
|
|
90
|
+
if (!factoryNode) return
|
|
91
|
+
|
|
92
|
+
if (
|
|
93
|
+
!ts.isArrowFunction(factoryNode) &&
|
|
94
|
+
!ts.isFunctionExpression(factoryNode)
|
|
95
|
+
) {
|
|
96
|
+
logger.error(`• Handler for pikkuMiddlewareFactory is not a function.`)
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Extract services by looking inside the factory function body
|
|
101
|
+
// The factory should return pikkuMiddleware(...), so we need to find that call
|
|
102
|
+
// If no wrapper is found, extract from the factory's returned function directly
|
|
103
|
+
let services = { optimized: false, services: [] as string[] }
|
|
104
|
+
|
|
105
|
+
const findPikkuMiddlewareCall = (
|
|
106
|
+
node: ts.Node
|
|
107
|
+
): ts.CallExpression | undefined => {
|
|
108
|
+
if (ts.isCallExpression(node)) {
|
|
109
|
+
const expr = node.expression
|
|
110
|
+
if (ts.isIdentifier(expr) && expr.text === 'pikkuMiddleware') {
|
|
111
|
+
return node
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return ts.forEachChild(node, findPikkuMiddlewareCall)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const pikkuMiddlewareCall = findPikkuMiddlewareCall(factoryNode)
|
|
118
|
+
if (pikkuMiddlewareCall && pikkuMiddlewareCall.arguments[0]) {
|
|
119
|
+
const middlewareHandler = pikkuMiddlewareCall.arguments[0]
|
|
120
|
+
if (
|
|
121
|
+
ts.isArrowFunction(middlewareHandler) ||
|
|
122
|
+
ts.isFunctionExpression(middlewareHandler)
|
|
123
|
+
) {
|
|
124
|
+
services = extractServicesFromFunction(middlewareHandler)
|
|
125
|
+
}
|
|
126
|
+
} else {
|
|
127
|
+
// No pikkuMiddleware wrapper found - extract from factory's return value directly
|
|
128
|
+
// Factory pattern: (config) => (services, interaction, next) => { ... }
|
|
129
|
+
if (
|
|
130
|
+
ts.isArrowFunction(factoryNode) ||
|
|
131
|
+
ts.isFunctionExpression(factoryNode)
|
|
132
|
+
) {
|
|
133
|
+
const factoryBody = factoryNode.body
|
|
134
|
+
// Check if the body is an arrow function (direct return)
|
|
135
|
+
if (
|
|
136
|
+
ts.isArrowFunction(factoryBody) ||
|
|
137
|
+
ts.isFunctionExpression(factoryBody)
|
|
138
|
+
) {
|
|
139
|
+
services = extractServicesFromFunction(factoryBody)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const { pikkuFuncName, exportedName } = extractFunctionName(
|
|
145
|
+
node,
|
|
146
|
+
checker,
|
|
147
|
+
state.rootDir
|
|
148
|
+
)
|
|
149
|
+
state.middleware.meta[pikkuFuncName] = {
|
|
150
|
+
services,
|
|
151
|
+
sourceFile: node.getSourceFile().fileName,
|
|
152
|
+
position: node.getStart(),
|
|
153
|
+
exportedName,
|
|
154
|
+
factory: true,
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
logger.debug(
|
|
158
|
+
`• Found middleware factory with services: ${services.services.join(', ')}`
|
|
159
|
+
)
|
|
160
|
+
return
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Handle addMiddleware('tag', [middleware1, middleware2])
|
|
164
|
+
// Supports two patterns:
|
|
165
|
+
// 1. export const x = () => addMiddleware('tag', [...]) (factory - tree-shakeable)
|
|
166
|
+
// 2. export const x = addMiddleware('tag', [...]) (direct - no tree-shaking)
|
|
167
|
+
if (expression.text === 'addMiddleware') {
|
|
168
|
+
const tagArg = args[0]
|
|
169
|
+
const middlewareArrayArg = args[1]
|
|
170
|
+
|
|
171
|
+
if (!tagArg || !middlewareArrayArg) return
|
|
172
|
+
|
|
173
|
+
// Extract tag name
|
|
174
|
+
let tag: string | undefined
|
|
175
|
+
if (ts.isStringLiteral(tagArg)) {
|
|
176
|
+
tag = tagArg.text
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (!tag) {
|
|
180
|
+
logger.warn(`• addMiddleware call without valid tag string`)
|
|
181
|
+
return
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Check if middleware array is a literal array
|
|
185
|
+
if (!ts.isArrayLiteralExpression(middlewareArrayArg)) {
|
|
186
|
+
logger.error(
|
|
187
|
+
`• addMiddleware('${tag}', ...) must have a literal array as second argument`
|
|
188
|
+
)
|
|
189
|
+
return
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Extract middleware pikkuFuncNames from array
|
|
193
|
+
const middlewareNames = extractMiddlewarePikkuNames(
|
|
194
|
+
middlewareArrayArg,
|
|
195
|
+
checker,
|
|
196
|
+
state.rootDir
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
if (middlewareNames.length === 0) {
|
|
200
|
+
logger.warn(`• addMiddleware('${tag}', ...) has empty middleware array`)
|
|
201
|
+
return
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Collect services from all middleware in the group
|
|
205
|
+
const allServices = new Set<string>()
|
|
206
|
+
for (const middlewareName of middlewareNames) {
|
|
207
|
+
const middlewareMeta = state.middleware.meta[middlewareName]
|
|
208
|
+
if (middlewareMeta && middlewareMeta.services) {
|
|
209
|
+
for (const service of middlewareMeta.services.services) {
|
|
210
|
+
allServices.add(service)
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Check if this call is wrapped in a factory function
|
|
216
|
+
// We need to walk up the tree to see if the parent is: const x = () => addMiddleware(...)
|
|
217
|
+
let isFactory = false
|
|
218
|
+
let exportedName: string | null = null
|
|
219
|
+
let parent = node.parent
|
|
220
|
+
|
|
221
|
+
// Check if parent is arrow function: () => addMiddleware(...)
|
|
222
|
+
if (parent && ts.isArrowFunction(parent)) {
|
|
223
|
+
// Check if arrow function has no parameters
|
|
224
|
+
if (parent.parameters.length === 0) {
|
|
225
|
+
isFactory = true
|
|
226
|
+
|
|
227
|
+
// For factories, we need to check the arrow function's parent for the export name
|
|
228
|
+
// const apiTagMiddleware = () => addMiddleware(...)
|
|
229
|
+
const arrowParent = parent.parent
|
|
230
|
+
if (arrowParent && ts.isVariableDeclaration(arrowParent)) {
|
|
231
|
+
if (ts.isIdentifier(arrowParent.name)) {
|
|
232
|
+
// Check if it's exported
|
|
233
|
+
if (isNamedExport(arrowParent)) {
|
|
234
|
+
exportedName = arrowParent.name.text
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// If not a factory, get export name from the call expression itself
|
|
242
|
+
if (!isFactory) {
|
|
243
|
+
const extracted = extractFunctionName(node, checker, state.rootDir)
|
|
244
|
+
exportedName = extracted.exportedName
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Log warning if not using factory pattern
|
|
248
|
+
if (!isFactory && exportedName) {
|
|
249
|
+
logger.warn(
|
|
250
|
+
`• Middleware group '${exportedName}' for tag '${tag}' is not wrapped in a factory function. ` +
|
|
251
|
+
`For tree-shaking, use: export const ${exportedName} = () => addMiddleware('${tag}', [...])`
|
|
252
|
+
)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Store group metadata
|
|
256
|
+
state.middleware.tagMiddleware.set(tag, {
|
|
257
|
+
exportName: exportedName,
|
|
258
|
+
sourceFile: node.getSourceFile().fileName,
|
|
259
|
+
position: node.getStart(),
|
|
260
|
+
services: {
|
|
261
|
+
optimized: false,
|
|
262
|
+
services: Array.from(allServices),
|
|
263
|
+
},
|
|
264
|
+
middlewareCount: middlewareNames.length,
|
|
265
|
+
isFactory,
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
logger.debug(
|
|
269
|
+
`• Found tag middleware group: ${tag} -> [${middlewareNames.join(', ')}] (${isFactory ? 'factory' : 'direct'})`
|
|
270
|
+
)
|
|
271
|
+
return
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Handle addHTTPMiddleware(pattern, [middleware1, middleware2])
|
|
275
|
+
// Supports two patterns:
|
|
276
|
+
// 1. export const x = () => addHTTPMiddleware('*', [...]) (factory - tree-shakeable)
|
|
277
|
+
// 2. export const x = addHTTPMiddleware('*', [...]) (direct - no tree-shaking)
|
|
278
|
+
if (expression.text === 'addHTTPMiddleware') {
|
|
279
|
+
const patternArg = args[0]
|
|
280
|
+
const middlewareArrayArg = args[1]
|
|
281
|
+
|
|
282
|
+
if (!patternArg || !middlewareArrayArg) return
|
|
283
|
+
|
|
284
|
+
// Extract route pattern
|
|
285
|
+
let pattern: string | undefined
|
|
286
|
+
if (ts.isStringLiteral(patternArg)) {
|
|
287
|
+
pattern = patternArg.text
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (!pattern) {
|
|
291
|
+
logger.warn(`• addHTTPMiddleware call without valid pattern string`)
|
|
292
|
+
return
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Check if middleware array is a literal array
|
|
296
|
+
if (!ts.isArrayLiteralExpression(middlewareArrayArg)) {
|
|
297
|
+
logger.error(
|
|
298
|
+
`• addHTTPMiddleware('${pattern}', ...) must have a literal array as second argument`
|
|
299
|
+
)
|
|
300
|
+
return
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Extract middleware pikkuFuncNames from array
|
|
304
|
+
const middlewareNames = extractMiddlewarePikkuNames(
|
|
305
|
+
middlewareArrayArg,
|
|
306
|
+
checker,
|
|
307
|
+
state.rootDir
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
if (middlewareNames.length === 0) {
|
|
311
|
+
logger.warn(
|
|
312
|
+
`• addHTTPMiddleware('${pattern}', ...) has empty middleware array`
|
|
313
|
+
)
|
|
314
|
+
return
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Collect services from all middleware in the group
|
|
318
|
+
const allServices = new Set<string>()
|
|
319
|
+
for (const middlewareName of middlewareNames) {
|
|
320
|
+
const middlewareMeta = state.middleware.meta[middlewareName]
|
|
321
|
+
if (middlewareMeta && middlewareMeta.services) {
|
|
322
|
+
for (const service of middlewareMeta.services.services) {
|
|
323
|
+
allServices.add(service)
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Check if this call is wrapped in a factory function
|
|
329
|
+
let isFactory = false
|
|
330
|
+
let exportedName: string | null = null
|
|
331
|
+
let parent = node.parent
|
|
332
|
+
|
|
333
|
+
// Check if parent is arrow function: () => addHTTPMiddleware(...)
|
|
334
|
+
if (parent && ts.isArrowFunction(parent)) {
|
|
335
|
+
// Check if arrow function has no parameters
|
|
336
|
+
if (parent.parameters.length === 0) {
|
|
337
|
+
isFactory = true
|
|
338
|
+
|
|
339
|
+
// For factories, we need to check the arrow function's parent for the export name
|
|
340
|
+
// const apiRouteMiddleware = () => addHTTPMiddleware(...)
|
|
341
|
+
const arrowParent = parent.parent
|
|
342
|
+
if (arrowParent && ts.isVariableDeclaration(arrowParent)) {
|
|
343
|
+
if (ts.isIdentifier(arrowParent.name)) {
|
|
344
|
+
// Check if it's exported
|
|
345
|
+
if (isNamedExport(arrowParent)) {
|
|
346
|
+
exportedName = arrowParent.name.text
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// If not a factory, get export name from the call expression itself
|
|
354
|
+
if (!isFactory) {
|
|
355
|
+
const extracted = extractFunctionName(node, checker, state.rootDir)
|
|
356
|
+
exportedName = extracted.exportedName
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Log warning if not using factory pattern
|
|
360
|
+
if (!isFactory && exportedName) {
|
|
361
|
+
logger.warn(
|
|
362
|
+
`• HTTP middleware group '${exportedName}' for pattern '${pattern}' is not wrapped in a factory function. ` +
|
|
363
|
+
`For tree-shaking, use: export const ${exportedName} = () => addHTTPMiddleware('${pattern}', [...])`
|
|
364
|
+
)
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Store group metadata
|
|
368
|
+
state.http.routeMiddleware.set(pattern, {
|
|
369
|
+
exportName: exportedName,
|
|
370
|
+
sourceFile: node.getSourceFile().fileName,
|
|
371
|
+
position: node.getStart(),
|
|
372
|
+
services: {
|
|
373
|
+
optimized: false,
|
|
374
|
+
services: Array.from(allServices),
|
|
375
|
+
},
|
|
376
|
+
middlewareCount: middlewareNames.length,
|
|
377
|
+
isFactory,
|
|
378
|
+
})
|
|
379
|
+
|
|
380
|
+
logger.debug(
|
|
381
|
+
`• Found HTTP route middleware group: ${pattern} -> [${middlewareNames.join(', ')}] (${isFactory ? 'factory' : 'direct'})`
|
|
382
|
+
)
|
|
383
|
+
return
|
|
384
|
+
}
|
|
385
|
+
}
|