@pikku/inspector 0.11.1 → 0.12.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 +26 -1
- package/OPTIMIZATION-PLAN.md +195 -0
- package/dist/add/add-ai-agent.d.ts +2 -0
- package/dist/add/add-ai-agent.js +314 -0
- package/dist/add/add-channel.js +69 -61
- package/dist/add/add-cli.js +36 -18
- package/dist/add/add-file-with-factory.js +2 -0
- package/dist/add/add-functions.js +327 -59
- package/dist/add/add-http-route.d.ts +19 -10
- package/dist/add/add-http-route.js +153 -44
- package/dist/add/add-http-routes.d.ts +5 -0
- package/dist/add/add-http-routes.js +159 -0
- package/dist/add/add-keyed-wiring.d.ts +12 -0
- package/dist/add/add-keyed-wiring.js +97 -0
- package/dist/add/add-mcp-prompt.js +14 -9
- package/dist/add/add-mcp-resource.js +14 -9
- package/dist/add/add-middleware.d.ts +1 -4
- package/dist/add/add-middleware.js +364 -79
- package/dist/add/add-permission.d.ts +1 -1
- package/dist/add/add-permission.js +152 -40
- package/dist/add/add-queue-worker.js +18 -12
- package/dist/add/add-rpc-invocations.d.ts +3 -0
- package/dist/add/add-rpc-invocations.js +65 -25
- package/dist/add/add-schedule.js +11 -5
- package/dist/add/add-secret.d.ts +3 -0
- package/dist/add/add-secret.js +82 -0
- package/dist/add/add-trigger.d.ts +2 -0
- package/dist/add/add-trigger.js +87 -0
- package/dist/add/add-variable.d.ts +1 -0
- package/dist/add/add-variable.js +8 -0
- package/dist/add/add-workflow-graph.d.ts +7 -0
- package/dist/add/add-workflow-graph.js +396 -0
- package/dist/add/add-workflow.js +124 -26
- package/dist/error-codes.d.ts +16 -1
- package/dist/error-codes.js +21 -1
- package/dist/index.d.ts +9 -5
- package/dist/index.js +5 -2
- package/dist/inspector.d.ts +1 -1
- package/dist/inspector.js +106 -13
- package/dist/schema-generator.d.ts +1 -0
- package/dist/schema-generator.js +1 -0
- package/dist/types-map.js +10 -1
- package/dist/types.d.ts +180 -30
- package/dist/utils/compute-required-schemas.d.ts +4 -0
- package/dist/utils/compute-required-schemas.js +41 -0
- package/dist/utils/contract-hashes.d.ts +35 -0
- package/dist/utils/contract-hashes.js +202 -0
- package/dist/utils/custom-types-generator.d.ts +9 -0
- package/dist/utils/custom-types-generator.js +71 -0
- package/dist/utils/detect-schema-vendor.d.ts +22 -0
- package/dist/utils/detect-schema-vendor.js +76 -0
- package/dist/utils/ensure-function-metadata.d.ts +5 -2
- package/dist/utils/ensure-function-metadata.js +220 -6
- package/dist/utils/extract-function-name.d.ts +5 -16
- package/dist/utils/extract-function-name.js +93 -298
- package/dist/utils/extract-services.d.ts +2 -1
- package/dist/utils/extract-services.js +25 -1
- package/dist/utils/filter-inspector-state.js +107 -23
- package/dist/utils/get-property-value.d.ts +8 -2
- package/dist/utils/get-property-value.js +33 -4
- package/dist/utils/hash.d.ts +2 -0
- package/dist/utils/hash.js +23 -0
- package/dist/utils/middleware.d.ts +7 -30
- package/dist/utils/middleware.js +80 -66
- package/dist/utils/permissions.d.ts +2 -2
- package/dist/utils/permissions.js +10 -10
- package/dist/utils/post-process.d.ts +9 -10
- package/dist/utils/post-process.js +231 -24
- package/dist/utils/resolve-external-package.d.ts +12 -0
- package/dist/utils/resolve-external-package.js +34 -0
- package/dist/utils/resolve-function-types.d.ts +6 -0
- package/dist/utils/resolve-function-types.js +29 -0
- package/dist/utils/resolve-identifier.d.ts +10 -0
- package/dist/utils/resolve-identifier.js +36 -0
- package/dist/utils/resolve-versions.d.ts +2 -0
- package/dist/utils/resolve-versions.js +78 -0
- package/dist/utils/schema-generator.d.ts +9 -0
- package/dist/utils/schema-generator.js +209 -0
- package/dist/utils/serialize-inspector-state.d.ts +73 -13
- package/dist/utils/serialize-inspector-state.js +102 -6
- package/dist/utils/serialize-mcp-json.d.ts +2 -0
- package/dist/utils/serialize-mcp-json.js +99 -0
- package/dist/utils/serialize-middleware-groups-meta.d.ts +12 -0
- package/dist/utils/serialize-middleware-groups-meta.js +28 -0
- package/dist/utils/serialize-openapi-json.d.ts +85 -0
- package/dist/utils/serialize-openapi-json.js +151 -0
- package/dist/utils/serialize-permissions-groups-meta.d.ts +6 -0
- package/dist/utils/serialize-permissions-groups-meta.js +31 -0
- package/dist/utils/workflow/dsl/deserialize-dsl-workflow.d.ts +24 -0
- package/dist/utils/workflow/dsl/deserialize-dsl-workflow.js +830 -0
- package/dist/{workflow/extract-simple-workflow.d.ts → utils/workflow/dsl/extract-dsl-workflow.d.ts} +4 -2
- package/dist/{workflow/extract-simple-workflow.js → utils/workflow/dsl/extract-dsl-workflow.js} +572 -72
- package/dist/utils/workflow/dsl/index.d.ts +7 -0
- package/dist/utils/workflow/dsl/index.js +7 -0
- package/dist/{workflow → utils/workflow/dsl}/patterns.d.ts +21 -0
- package/dist/{workflow → utils/workflow/dsl}/patterns.js +90 -10
- package/dist/{workflow → utils/workflow/dsl}/validation.d.ts +2 -0
- package/dist/{workflow → utils/workflow/dsl}/validation.js +25 -7
- package/dist/utils/workflow/graph/convert-dsl-to-graph.d.ts +13 -0
- package/dist/utils/workflow/graph/convert-dsl-to-graph.js +318 -0
- package/dist/utils/workflow/graph/finalize-workflow-wires.d.ts +3 -0
- package/dist/utils/workflow/graph/finalize-workflow-wires.js +276 -0
- package/dist/utils/workflow/graph/finalize-workflows.d.ts +2 -0
- package/dist/utils/workflow/graph/finalize-workflows.js +75 -0
- package/dist/utils/workflow/graph/index.d.ts +8 -0
- package/dist/utils/workflow/graph/index.js +8 -0
- package/dist/utils/workflow/graph/serialize-workflow-graph.d.ts +35 -0
- package/dist/utils/workflow/graph/serialize-workflow-graph.js +150 -0
- package/dist/utils/workflow/graph/workflow-graph.types.d.ts +203 -0
- package/dist/utils/workflow/graph/workflow-graph.types.js +38 -0
- package/dist/visit.js +13 -2
- package/package.json +26 -4
- package/src/add/add-ai-agent.ts +468 -0
- package/src/add/add-channel.ts +82 -79
- package/src/add/add-cli.ts +49 -20
- package/src/add/add-file-with-factory.ts +2 -0
- package/src/add/add-functions.ts +429 -71
- package/src/add/add-http-route.ts +246 -65
- package/src/add/add-http-routes.ts +228 -0
- package/src/add/add-keyed-wiring.ts +151 -0
- package/src/add/add-mcp-prompt.ts +26 -15
- package/src/add/add-mcp-resource.ts +27 -15
- package/src/add/add-middleware.ts +482 -80
- package/src/add/add-permission.ts +199 -40
- package/src/add/add-queue-worker.ts +24 -19
- package/src/add/add-rpc-invocations.ts +78 -31
- package/src/add/add-schedule.ts +16 -11
- package/src/add/add-secret.ts +140 -0
- package/src/add/add-trigger.ts +154 -0
- package/src/add/add-variable.ts +9 -0
- package/src/add/add-workflow-graph.ts +522 -0
- package/src/add/add-workflow.ts +117 -30
- package/src/error-codes.ts +26 -1
- package/src/index.ts +27 -8
- package/src/inspector.ts +145 -17
- package/src/schema-generator.ts +1 -0
- package/src/types-map.ts +12 -1
- package/src/types.ts +192 -51
- package/src/utils/compute-required-schemas.ts +49 -0
- package/src/utils/contract-hashes.test.ts +528 -0
- package/src/utils/contract-hashes.ts +290 -0
- package/src/utils/custom-types-generator.ts +88 -0
- package/src/utils/detect-schema-vendor.ts +90 -0
- package/src/utils/ensure-function-metadata.ts +324 -7
- package/src/utils/extract-function-name.ts +108 -358
- package/src/utils/extract-services.ts +35 -2
- package/src/utils/filter-inspector-state.test.ts +34 -20
- package/src/utils/filter-inspector-state.ts +140 -31
- package/src/utils/get-property-value.ts +50 -5
- package/src/utils/hash.ts +26 -0
- package/src/utils/middleware.test.ts +204 -0
- package/src/utils/middleware.ts +129 -67
- package/src/utils/permissions.test.ts +35 -12
- package/src/utils/permissions.ts +10 -10
- package/src/utils/post-process.ts +283 -43
- package/src/utils/resolve-external-package.ts +42 -0
- package/src/utils/resolve-function-types.ts +42 -0
- package/src/utils/resolve-identifier.ts +46 -0
- package/src/utils/resolve-versions.test.ts +249 -0
- package/src/utils/resolve-versions.ts +105 -0
- package/src/utils/schema-generator.ts +329 -0
- package/src/utils/serialize-inspector-state.ts +181 -20
- package/src/utils/serialize-mcp-json.ts +145 -0
- package/src/utils/serialize-middleware-groups-meta.ts +33 -0
- package/src/utils/serialize-openapi-json.ts +277 -0
- package/src/utils/serialize-permissions-groups-meta.ts +35 -0
- package/src/utils/test-data/inspector-state.json +69 -66
- package/src/utils/workflow/dsl/deserialize-dsl-workflow.ts +1104 -0
- package/src/{workflow/extract-simple-workflow.ts → utils/workflow/dsl/extract-dsl-workflow.ts} +678 -85
- package/src/utils/workflow/dsl/index.ts +11 -0
- package/src/{workflow → utils/workflow/dsl}/patterns.ts +108 -11
- package/src/{workflow → utils/workflow/dsl}/validation.ts +34 -7
- package/src/utils/workflow/graph/convert-dsl-to-graph.ts +422 -0
- package/src/utils/workflow/graph/finalize-workflow-wires.ts +310 -0
- package/src/utils/workflow/graph/finalize-workflows.ts +100 -0
- package/src/utils/workflow/graph/index.ts +11 -0
- package/src/utils/workflow/graph/serialize-workflow-graph.ts +216 -0
- package/src/utils/workflow/graph/workflow-graph.types.ts +231 -0
- package/src/visit.ts +14 -2
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/add/add-mcp-tool.d.ts +0 -2
- package/dist/add/add-mcp-tool.js +0 -81
- package/dist/utils/extract-service-metadata.d.ts +0 -19
- package/dist/utils/extract-service-metadata.js +0 -244
- package/dist/utils/write-service-metadata.d.ts +0 -13
- package/dist/utils/write-service-metadata.js +0 -37
- package/src/add/add-mcp-tool.ts +0 -141
- package/src/utils/extract-service-metadata.ts +0 -353
- package/src/utils/write-service-metadata.ts +0 -51
|
@@ -1,14 +1,58 @@
|
|
|
1
1
|
import * as ts from 'typescript'
|
|
2
|
-
import { AddWiring } from '../types.js'
|
|
2
|
+
import type { AddWiring, InspectorState } from '../types.js'
|
|
3
3
|
import {
|
|
4
4
|
extractFunctionName,
|
|
5
5
|
isNamedExport,
|
|
6
|
+
makeContextBasedId,
|
|
6
7
|
} from '../utils/extract-function-name.js'
|
|
7
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
extractServicesFromFunction,
|
|
10
|
+
extractUsedWires,
|
|
11
|
+
} from '../utils/extract-services.js'
|
|
8
12
|
import { extractPermissionPikkuNames } from '../utils/permissions.js'
|
|
9
13
|
import { getPropertyValue } from '../utils/get-property-value.js'
|
|
10
14
|
import { getPropertyAssignmentInitializer } from '../utils/type-utils.js'
|
|
11
15
|
|
|
16
|
+
function renameTempDefinitions(
|
|
17
|
+
state: InspectorState,
|
|
18
|
+
definitionIds: string[],
|
|
19
|
+
groupType: string,
|
|
20
|
+
groupKey: string
|
|
21
|
+
): void {
|
|
22
|
+
const tempIndices = definitionIds
|
|
23
|
+
.map((name, i) => (name.startsWith('__temp_') ? i : -1))
|
|
24
|
+
.filter((i) => i >= 0)
|
|
25
|
+
|
|
26
|
+
for (const idx of tempIndices) {
|
|
27
|
+
const oldId = definitionIds[idx]
|
|
28
|
+
const newId =
|
|
29
|
+
tempIndices.length === 1
|
|
30
|
+
? makeContextBasedId(groupType, groupKey)
|
|
31
|
+
: makeContextBasedId(groupType, groupKey, String(idx))
|
|
32
|
+
const existing = state.permissions.definitions[oldId]
|
|
33
|
+
if (existing) {
|
|
34
|
+
delete state.permissions.definitions[oldId]
|
|
35
|
+
state.permissions.definitions[newId] = existing
|
|
36
|
+
}
|
|
37
|
+
definitionIds[idx] = newId
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function isInsidePermissionFactory(node: ts.Node): boolean {
|
|
42
|
+
let current = node.parent
|
|
43
|
+
while (current) {
|
|
44
|
+
if (
|
|
45
|
+
ts.isCallExpression(current) &&
|
|
46
|
+
ts.isIdentifier(current.expression) &&
|
|
47
|
+
current.expression.text === 'pikkuPermissionFactory'
|
|
48
|
+
) {
|
|
49
|
+
return true
|
|
50
|
+
}
|
|
51
|
+
current = current.parent
|
|
52
|
+
}
|
|
53
|
+
return false
|
|
54
|
+
}
|
|
55
|
+
|
|
12
56
|
/**
|
|
13
57
|
* Inspect pikkuPermission calls, addPermission calls, and addHTTPPermission calls
|
|
14
58
|
*/
|
|
@@ -24,6 +68,9 @@ export const addPermission: AddWiring = (logger, node, checker, state) => {
|
|
|
24
68
|
|
|
25
69
|
// Handle pikkuPermission(...) - individual permission function definition
|
|
26
70
|
if (expression.text === 'pikkuPermission') {
|
|
71
|
+
// Skip if nested inside pikkuPermissionFactory — the factory handler extracts services itself
|
|
72
|
+
if (isInsidePermissionFactory(node)) return
|
|
73
|
+
|
|
27
74
|
const arg = args[0]
|
|
28
75
|
if (!arg) return
|
|
29
76
|
|
|
@@ -31,15 +78,12 @@ export const addPermission: AddWiring = (logger, node, checker, state) => {
|
|
|
31
78
|
let name: string | undefined
|
|
32
79
|
let description: string | undefined
|
|
33
80
|
|
|
34
|
-
// Check if using object syntax: pikkuPermission({ func: ..., name: '...', description: '...' })
|
|
35
81
|
if (ts.isObjectLiteralExpression(arg)) {
|
|
36
|
-
// Extract name and description metadata
|
|
37
82
|
const nameValue = getPropertyValue(arg, 'name')
|
|
38
83
|
const descValue = getPropertyValue(arg, 'description')
|
|
39
84
|
name = typeof nameValue === 'string' ? nameValue : undefined
|
|
40
85
|
description = typeof descValue === 'string' ? descValue : undefined
|
|
41
86
|
|
|
42
|
-
// Extract the func property
|
|
43
87
|
const fnProp = getPropertyAssignmentInitializer(
|
|
44
88
|
arg,
|
|
45
89
|
'func',
|
|
@@ -64,22 +108,129 @@ export const addPermission: AddWiring = (logger, node, checker, state) => {
|
|
|
64
108
|
}
|
|
65
109
|
|
|
66
110
|
const services = extractServicesFromFunction(actualHandler)
|
|
67
|
-
const
|
|
111
|
+
const wires = extractUsedWires(actualHandler, 2)
|
|
112
|
+
let { pikkuFuncId, exportedName } = extractFunctionName(
|
|
68
113
|
node,
|
|
69
114
|
checker,
|
|
70
115
|
state.rootDir
|
|
71
116
|
)
|
|
72
|
-
|
|
117
|
+
if (pikkuFuncId.startsWith('__temp_')) {
|
|
118
|
+
if (
|
|
119
|
+
ts.isVariableDeclaration(node.parent) &&
|
|
120
|
+
ts.isIdentifier(node.parent.name)
|
|
121
|
+
) {
|
|
122
|
+
pikkuFuncId = node.parent.name.text
|
|
123
|
+
} else if (
|
|
124
|
+
ts.isPropertyAssignment(node.parent) &&
|
|
125
|
+
ts.isIdentifier(node.parent.name)
|
|
126
|
+
) {
|
|
127
|
+
pikkuFuncId = node.parent.name.text
|
|
128
|
+
} else {
|
|
129
|
+
logger.error(
|
|
130
|
+
`• pikkuPermission() must be assigned to a variable or object property. ` +
|
|
131
|
+
`Extract it to a const: const myPermission = pikkuPermission(...)`
|
|
132
|
+
)
|
|
133
|
+
return
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const dataParam = actualHandler.parameters[1]
|
|
137
|
+
const dataParamName =
|
|
138
|
+
dataParam && ts.isIdentifier(dataParam.name) ? dataParam.name.text : null
|
|
139
|
+
const requiresData = !(dataParamName && dataParamName.startsWith('_'))
|
|
140
|
+
|
|
141
|
+
state.permissions.definitions[pikkuFuncId] = {
|
|
73
142
|
services,
|
|
143
|
+
wires: wires.wires.length > 0 || !wires.optimized ? wires : undefined,
|
|
74
144
|
sourceFile: node.getSourceFile().fileName,
|
|
75
145
|
position: node.getStart(),
|
|
76
146
|
exportedName,
|
|
77
147
|
name,
|
|
78
148
|
description,
|
|
149
|
+
...(requiresData ? {} : { requiresData: false }),
|
|
79
150
|
}
|
|
80
151
|
|
|
81
152
|
logger.debug(
|
|
82
|
-
`• Found permission with services: ${services.services.join(', ')}${name ? ` (name: ${name})` : ''}${description ? ` (description: ${description})` : ''}`
|
|
153
|
+
`• Found permission with services: ${services.services.join(', ')}${name ? ` (name: ${name})` : ''}${description ? ` (description: ${description})` : ''}${!requiresData ? ' (auth-only)' : ''}`
|
|
154
|
+
)
|
|
155
|
+
return
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (expression.text === 'pikkuAuth') {
|
|
159
|
+
if (isInsidePermissionFactory(node)) return
|
|
160
|
+
|
|
161
|
+
const arg = args[0]
|
|
162
|
+
if (!arg) return
|
|
163
|
+
|
|
164
|
+
let actualHandler: ts.ArrowFunction | ts.FunctionExpression
|
|
165
|
+
let name: string | undefined
|
|
166
|
+
let description: string | undefined
|
|
167
|
+
|
|
168
|
+
if (ts.isObjectLiteralExpression(arg)) {
|
|
169
|
+
const nameValue = getPropertyValue(arg, 'name')
|
|
170
|
+
const descValue = getPropertyValue(arg, 'description')
|
|
171
|
+
name = typeof nameValue === 'string' ? nameValue : undefined
|
|
172
|
+
description = typeof descValue === 'string' ? descValue : undefined
|
|
173
|
+
|
|
174
|
+
const fnProp = getPropertyAssignmentInitializer(
|
|
175
|
+
arg,
|
|
176
|
+
'func',
|
|
177
|
+
true,
|
|
178
|
+
checker
|
|
179
|
+
)
|
|
180
|
+
if (
|
|
181
|
+
!fnProp ||
|
|
182
|
+
(!ts.isArrowFunction(fnProp) && !ts.isFunctionExpression(fnProp))
|
|
183
|
+
) {
|
|
184
|
+
logger.error(`• pikkuAuth object missing required 'func' property.`)
|
|
185
|
+
return
|
|
186
|
+
}
|
|
187
|
+
actualHandler = fnProp
|
|
188
|
+
} else if (ts.isArrowFunction(arg) || ts.isFunctionExpression(arg)) {
|
|
189
|
+
actualHandler = arg
|
|
190
|
+
} else {
|
|
191
|
+
logger.error(`• Handler for pikkuAuth is not a function.`)
|
|
192
|
+
return
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const services = extractServicesFromFunction(actualHandler)
|
|
196
|
+
const wires = extractUsedWires(actualHandler, 1)
|
|
197
|
+
let { pikkuFuncId, exportedName } = extractFunctionName(
|
|
198
|
+
node,
|
|
199
|
+
checker,
|
|
200
|
+
state.rootDir
|
|
201
|
+
)
|
|
202
|
+
if (pikkuFuncId.startsWith('__temp_')) {
|
|
203
|
+
if (
|
|
204
|
+
ts.isVariableDeclaration(node.parent) &&
|
|
205
|
+
ts.isIdentifier(node.parent.name)
|
|
206
|
+
) {
|
|
207
|
+
pikkuFuncId = node.parent.name.text
|
|
208
|
+
} else if (
|
|
209
|
+
ts.isPropertyAssignment(node.parent) &&
|
|
210
|
+
ts.isIdentifier(node.parent.name)
|
|
211
|
+
) {
|
|
212
|
+
pikkuFuncId = node.parent.name.text
|
|
213
|
+
} else {
|
|
214
|
+
logger.error(
|
|
215
|
+
`• pikkuAuth() must be assigned to a variable or object property. ` +
|
|
216
|
+
`Extract it to a const: const myAuth = pikkuAuth(...)`
|
|
217
|
+
)
|
|
218
|
+
return
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
state.permissions.definitions[pikkuFuncId] = {
|
|
222
|
+
services,
|
|
223
|
+
wires: wires.wires.length > 0 || !wires.optimized ? wires : undefined,
|
|
224
|
+
sourceFile: node.getSourceFile().fileName,
|
|
225
|
+
position: node.getStart(),
|
|
226
|
+
exportedName,
|
|
227
|
+
name,
|
|
228
|
+
description,
|
|
229
|
+
requiresData: false,
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
logger.debug(
|
|
233
|
+
`• Found auth permission with services: ${services.services.join(', ')}${name ? ` (name: ${name})` : ''}${description ? ` (description: ${description})` : ''}`
|
|
83
234
|
)
|
|
84
235
|
return
|
|
85
236
|
}
|
|
@@ -101,6 +252,10 @@ export const addPermission: AddWiring = (logger, node, checker, state) => {
|
|
|
101
252
|
// The factory should return pikkuPermission(...), so we need to find that call
|
|
102
253
|
// If no wrapper is found, extract from the factory's returned function directly
|
|
103
254
|
let services = { optimized: false, services: [] as string[] }
|
|
255
|
+
let wires: ReturnType<typeof extractUsedWires> = {
|
|
256
|
+
optimized: true,
|
|
257
|
+
wires: [],
|
|
258
|
+
}
|
|
104
259
|
|
|
105
260
|
const findPikkuPermissionCall = (
|
|
106
261
|
node: ts.Node
|
|
@@ -122,32 +277,51 @@ export const addPermission: AddWiring = (logger, node, checker, state) => {
|
|
|
122
277
|
ts.isFunctionExpression(permissionHandler)
|
|
123
278
|
) {
|
|
124
279
|
services = extractServicesFromFunction(permissionHandler)
|
|
280
|
+
wires = extractUsedWires(permissionHandler, 2)
|
|
125
281
|
}
|
|
126
282
|
} else {
|
|
127
|
-
// No pikkuPermission wrapper found - extract from factory's return value directly
|
|
128
|
-
// Factory pattern: (config) => (services, data, wire) => { ... }
|
|
129
283
|
if (
|
|
130
284
|
ts.isArrowFunction(factoryNode) ||
|
|
131
285
|
ts.isFunctionExpression(factoryNode)
|
|
132
286
|
) {
|
|
133
287
|
const factoryBody = factoryNode.body
|
|
134
|
-
// Check if the body is an arrow function (direct return)
|
|
135
288
|
if (
|
|
136
289
|
ts.isArrowFunction(factoryBody) ||
|
|
137
290
|
ts.isFunctionExpression(factoryBody)
|
|
138
291
|
) {
|
|
139
292
|
services = extractServicesFromFunction(factoryBody)
|
|
293
|
+
wires = extractUsedWires(factoryBody, 2)
|
|
140
294
|
}
|
|
141
295
|
}
|
|
142
296
|
}
|
|
143
297
|
|
|
144
|
-
|
|
298
|
+
let { pikkuFuncId, exportedName } = extractFunctionName(
|
|
145
299
|
node,
|
|
146
300
|
checker,
|
|
147
301
|
state.rootDir
|
|
148
302
|
)
|
|
149
|
-
|
|
303
|
+
if (pikkuFuncId.startsWith('__temp_')) {
|
|
304
|
+
if (
|
|
305
|
+
ts.isVariableDeclaration(node.parent) &&
|
|
306
|
+
ts.isIdentifier(node.parent.name)
|
|
307
|
+
) {
|
|
308
|
+
pikkuFuncId = node.parent.name.text
|
|
309
|
+
} else if (
|
|
310
|
+
ts.isPropertyAssignment(node.parent) &&
|
|
311
|
+
ts.isIdentifier(node.parent.name)
|
|
312
|
+
) {
|
|
313
|
+
pikkuFuncId = node.parent.name.text
|
|
314
|
+
} else {
|
|
315
|
+
logger.error(
|
|
316
|
+
`• pikkuPermissionFactory() must be assigned to a variable or object property. ` +
|
|
317
|
+
`Extract it to a const: const myPermission = pikkuPermissionFactory(...)`
|
|
318
|
+
)
|
|
319
|
+
return
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
state.permissions.definitions[pikkuFuncId] = {
|
|
150
323
|
services,
|
|
324
|
+
wires: wires.wires.length > 0 || !wires.optimized ? wires : undefined,
|
|
151
325
|
sourceFile: node.getSourceFile().fileName,
|
|
152
326
|
position: node.getStart(),
|
|
153
327
|
exportedName,
|
|
@@ -192,7 +366,7 @@ export const addPermission: AddWiring = (logger, node, checker, state) => {
|
|
|
192
366
|
return
|
|
193
367
|
}
|
|
194
368
|
|
|
195
|
-
// Extract permission
|
|
369
|
+
// Extract permission pikkuFuncIds from array
|
|
196
370
|
const permissionNames = extractPermissionPikkuNames(
|
|
197
371
|
permissionsArrayArg,
|
|
198
372
|
checker,
|
|
@@ -204,10 +378,11 @@ export const addPermission: AddWiring = (logger, node, checker, state) => {
|
|
|
204
378
|
return
|
|
205
379
|
}
|
|
206
380
|
|
|
207
|
-
|
|
381
|
+
renameTempDefinitions(state, permissionNames, 'tag', tag)
|
|
382
|
+
|
|
208
383
|
const allServices = new Set<string>()
|
|
209
384
|
for (const permissionName of permissionNames) {
|
|
210
|
-
const permissionMeta = state.permissions.
|
|
385
|
+
const permissionMeta = state.permissions.definitions[permissionName]
|
|
211
386
|
if (permissionMeta && permissionMeta.services) {
|
|
212
387
|
for (const service of permissionMeta.services.services) {
|
|
213
388
|
allServices.add(service)
|
|
@@ -215,24 +390,17 @@ export const addPermission: AddWiring = (logger, node, checker, state) => {
|
|
|
215
390
|
}
|
|
216
391
|
}
|
|
217
392
|
|
|
218
|
-
// Check if this call is wrapped in a factory function
|
|
219
|
-
// We need to walk up the tree to see if the parent is: const x = () => addPermission(...)
|
|
220
393
|
let isFactory = false
|
|
221
394
|
let exportedName: string | null = null
|
|
222
395
|
let parent = node.parent
|
|
223
396
|
|
|
224
|
-
// Check if parent is arrow function: () => addPermission(...)
|
|
225
397
|
if (parent && ts.isArrowFunction(parent)) {
|
|
226
|
-
// Check if arrow function has no parameters
|
|
227
398
|
if (parent.parameters.length === 0) {
|
|
228
399
|
isFactory = true
|
|
229
400
|
|
|
230
|
-
// For factories, we need to check the arrow function's parent for the export name
|
|
231
|
-
// const apiTagPermissions = () => addPermission(...)
|
|
232
401
|
const arrowParent = parent.parent
|
|
233
402
|
if (arrowParent && ts.isVariableDeclaration(arrowParent)) {
|
|
234
403
|
if (ts.isIdentifier(arrowParent.name)) {
|
|
235
|
-
// Check if it's exported
|
|
236
404
|
if (isNamedExport(arrowParent)) {
|
|
237
405
|
exportedName = arrowParent.name.text
|
|
238
406
|
}
|
|
@@ -241,13 +409,11 @@ export const addPermission: AddWiring = (logger, node, checker, state) => {
|
|
|
241
409
|
}
|
|
242
410
|
}
|
|
243
411
|
|
|
244
|
-
// If not a factory, get export name from the call expression itself
|
|
245
412
|
if (!isFactory) {
|
|
246
413
|
const extracted = extractFunctionName(node, checker, state.rootDir)
|
|
247
414
|
exportedName = extracted.exportedName
|
|
248
415
|
}
|
|
249
416
|
|
|
250
|
-
// Log warning if not using factory pattern
|
|
251
417
|
if (!isFactory && exportedName) {
|
|
252
418
|
logger.warn(
|
|
253
419
|
`• Permission group '${exportedName}' for tag '${tag}' is not wrapped in a factory function. ` +
|
|
@@ -255,7 +421,6 @@ export const addPermission: AddWiring = (logger, node, checker, state) => {
|
|
|
255
421
|
)
|
|
256
422
|
}
|
|
257
423
|
|
|
258
|
-
// Store group metadata
|
|
259
424
|
state.permissions.tagPermissions.set(tag, {
|
|
260
425
|
exportName: exportedName,
|
|
261
426
|
sourceFile: node.getSourceFile().fileName,
|
|
@@ -264,7 +429,8 @@ export const addPermission: AddWiring = (logger, node, checker, state) => {
|
|
|
264
429
|
optimized: false,
|
|
265
430
|
services: Array.from(allServices),
|
|
266
431
|
},
|
|
267
|
-
|
|
432
|
+
count: permissionNames.length,
|
|
433
|
+
instanceIds: permissionNames,
|
|
268
434
|
isFactory,
|
|
269
435
|
})
|
|
270
436
|
|
|
@@ -306,7 +472,7 @@ export const addPermission: AddWiring = (logger, node, checker, state) => {
|
|
|
306
472
|
return
|
|
307
473
|
}
|
|
308
474
|
|
|
309
|
-
// Extract permission
|
|
475
|
+
// Extract permission pikkuFuncIds from array
|
|
310
476
|
const permissionNames = extractPermissionPikkuNames(
|
|
311
477
|
permissionsArrayArg,
|
|
312
478
|
checker,
|
|
@@ -320,10 +486,11 @@ export const addPermission: AddWiring = (logger, node, checker, state) => {
|
|
|
320
486
|
return
|
|
321
487
|
}
|
|
322
488
|
|
|
323
|
-
|
|
489
|
+
renameTempDefinitions(state, permissionNames, 'http', pattern)
|
|
490
|
+
|
|
324
491
|
const allServices = new Set<string>()
|
|
325
492
|
for (const permissionName of permissionNames) {
|
|
326
|
-
const permissionMeta = state.permissions.
|
|
493
|
+
const permissionMeta = state.permissions.definitions[permissionName]
|
|
327
494
|
if (permissionMeta && permissionMeta.services) {
|
|
328
495
|
for (const service of permissionMeta.services.services) {
|
|
329
496
|
allServices.add(service)
|
|
@@ -331,23 +498,17 @@ export const addPermission: AddWiring = (logger, node, checker, state) => {
|
|
|
331
498
|
}
|
|
332
499
|
}
|
|
333
500
|
|
|
334
|
-
// Check if this call is wrapped in a factory function
|
|
335
501
|
let isFactory = false
|
|
336
502
|
let exportedName: string | null = null
|
|
337
503
|
let parent = node.parent
|
|
338
504
|
|
|
339
|
-
// Check if parent is arrow function: () => addHTTPPermission(...)
|
|
340
505
|
if (parent && ts.isArrowFunction(parent)) {
|
|
341
|
-
// Check if arrow function has no parameters
|
|
342
506
|
if (parent.parameters.length === 0) {
|
|
343
507
|
isFactory = true
|
|
344
508
|
|
|
345
|
-
// For factories, we need to check the arrow function's parent for the export name
|
|
346
|
-
// const apiRoutePermissions = () => addHTTPPermission(...)
|
|
347
509
|
const arrowParent = parent.parent
|
|
348
510
|
if (arrowParent && ts.isVariableDeclaration(arrowParent)) {
|
|
349
511
|
if (ts.isIdentifier(arrowParent.name)) {
|
|
350
|
-
// Check if it's exported
|
|
351
512
|
if (isNamedExport(arrowParent)) {
|
|
352
513
|
exportedName = arrowParent.name.text
|
|
353
514
|
}
|
|
@@ -356,13 +517,11 @@ export const addPermission: AddWiring = (logger, node, checker, state) => {
|
|
|
356
517
|
}
|
|
357
518
|
}
|
|
358
519
|
|
|
359
|
-
// If not a factory, get export name from the call expression itself
|
|
360
520
|
if (!isFactory) {
|
|
361
521
|
const extracted = extractFunctionName(node, checker, state.rootDir)
|
|
362
522
|
exportedName = extracted.exportedName
|
|
363
523
|
}
|
|
364
524
|
|
|
365
|
-
// Log warning if not using factory pattern
|
|
366
525
|
if (!isFactory && exportedName) {
|
|
367
526
|
logger.warn(
|
|
368
527
|
`• HTTP permission group '${exportedName}' for pattern '${pattern}' is not wrapped in a factory function. ` +
|
|
@@ -370,7 +529,6 @@ export const addPermission: AddWiring = (logger, node, checker, state) => {
|
|
|
370
529
|
)
|
|
371
530
|
}
|
|
372
531
|
|
|
373
|
-
// Store group metadata
|
|
374
532
|
state.http.routePermissions.set(pattern, {
|
|
375
533
|
exportName: exportedName,
|
|
376
534
|
sourceFile: node.getSourceFile().fileName,
|
|
@@ -379,7 +537,8 @@ export const addPermission: AddWiring = (logger, node, checker, state) => {
|
|
|
379
537
|
optimized: false,
|
|
380
538
|
services: Array.from(allServices),
|
|
381
539
|
},
|
|
382
|
-
|
|
540
|
+
count: permissionNames.length,
|
|
541
|
+
instanceIds: permissionNames,
|
|
383
542
|
isFactory,
|
|
384
543
|
})
|
|
385
544
|
|
|
@@ -4,7 +4,10 @@ import {
|
|
|
4
4
|
getCommonWireMetaData,
|
|
5
5
|
} from '../utils/get-property-value.js'
|
|
6
6
|
import { AddWiring } from '../types.js'
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
extractFunctionName,
|
|
9
|
+
makeContextBasedId,
|
|
10
|
+
} from '../utils/extract-function-name.js'
|
|
8
11
|
import { getPropertyAssignmentInitializer } from '../utils/type-utils.js'
|
|
9
12
|
import { resolveMiddleware } from '../utils/middleware.js'
|
|
10
13
|
import { extractWireNames } from '../utils/post-process.js'
|
|
@@ -31,13 +34,11 @@ export const addQueueWorker: AddWiring = (logger, node, checker, state) => {
|
|
|
31
34
|
if (ts.isObjectLiteralExpression(firstArg)) {
|
|
32
35
|
const obj = firstArg
|
|
33
36
|
|
|
34
|
-
const
|
|
35
|
-
const { tags, summary, description, errors } =
|
|
36
|
-
obj,
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
logger
|
|
40
|
-
)
|
|
37
|
+
const name = getPropertyValue(obj, 'name') as string | null
|
|
38
|
+
const { disabled, tags, summary, description, errors } =
|
|
39
|
+
getCommonWireMetaData(obj, 'Queue worker', name, logger)
|
|
40
|
+
|
|
41
|
+
if (disabled) return
|
|
41
42
|
|
|
42
43
|
// --- find the referenced function ---
|
|
43
44
|
const funcInitializer = getPropertyAssignmentInitializer(
|
|
@@ -49,21 +50,25 @@ export const addQueueWorker: AddWiring = (logger, node, checker, state) => {
|
|
|
49
50
|
if (!funcInitializer) {
|
|
50
51
|
logger.critical(
|
|
51
52
|
ErrorCode.MISSING_FUNC,
|
|
52
|
-
`No valid 'func' property for queue processor '${
|
|
53
|
+
`No valid 'func' property for queue processor '${name}'.`
|
|
53
54
|
)
|
|
54
55
|
return
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
const
|
|
58
|
+
const extracted = extractFunctionName(
|
|
58
59
|
funcInitializer,
|
|
59
60
|
checker,
|
|
60
61
|
state.rootDir
|
|
61
|
-
)
|
|
62
|
+
)
|
|
63
|
+
let pikkuFuncId = extracted.pikkuFuncId
|
|
64
|
+
if (pikkuFuncId.startsWith('__temp_') && name) {
|
|
65
|
+
pikkuFuncId = makeContextBasedId('queue', name)
|
|
66
|
+
}
|
|
62
67
|
|
|
63
|
-
if (!
|
|
68
|
+
if (!name) {
|
|
64
69
|
logger.critical(
|
|
65
70
|
ErrorCode.MISSING_QUEUE_NAME,
|
|
66
|
-
`No '
|
|
71
|
+
`No 'name' provided for queue processor function '${pikkuFuncId}'.`
|
|
67
72
|
)
|
|
68
73
|
return
|
|
69
74
|
}
|
|
@@ -72,15 +77,15 @@ export const addQueueWorker: AddWiring = (logger, node, checker, state) => {
|
|
|
72
77
|
const middleware = resolveMiddleware(state, obj, tags, checker)
|
|
73
78
|
|
|
74
79
|
// --- track used functions/middleware for service aggregation ---
|
|
75
|
-
state.serviceAggregation.usedFunctions.add(
|
|
76
|
-
extractWireNames(middleware).forEach((
|
|
77
|
-
state.serviceAggregation.usedMiddleware.add(
|
|
80
|
+
state.serviceAggregation.usedFunctions.add(pikkuFuncId)
|
|
81
|
+
extractWireNames(middleware).forEach((n) =>
|
|
82
|
+
state.serviceAggregation.usedMiddleware.add(n)
|
|
78
83
|
)
|
|
79
84
|
|
|
80
85
|
state.queueWorkers.files.add(node.getSourceFile().fileName)
|
|
81
|
-
state.queueWorkers.meta[
|
|
82
|
-
|
|
83
|
-
|
|
86
|
+
state.queueWorkers.meta[name] = {
|
|
87
|
+
pikkuFuncId,
|
|
88
|
+
name,
|
|
84
89
|
summary,
|
|
85
90
|
description,
|
|
86
91
|
errors,
|
|
@@ -1,48 +1,95 @@
|
|
|
1
1
|
import * as ts from 'typescript'
|
|
2
2
|
import { InspectorState, InspectorLogger } from '../types.js'
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Helper to extract namespace from a namespaced function reference like 'ext:hello'
|
|
6
|
+
*/
|
|
7
|
+
function extractNamespace(functionRef: string): string | null {
|
|
8
|
+
const colonIndex = functionRef.indexOf(':')
|
|
9
|
+
if (colonIndex !== -1) {
|
|
10
|
+
return functionRef.substring(0, colonIndex)
|
|
11
|
+
}
|
|
12
|
+
return null
|
|
13
|
+
}
|
|
14
|
+
|
|
4
15
|
/**
|
|
5
16
|
* Scan for rpc.invoke() calls to track which functions are actually being invoked
|
|
17
|
+
* Also detects external package usage via:
|
|
18
|
+
* - Namespaced calls: rpc.invoke('namespace:function')
|
|
19
|
+
* - External helper: external('namespace:function')
|
|
6
20
|
*/
|
|
7
21
|
export function addRPCInvocations(
|
|
8
22
|
node: ts.Node,
|
|
9
23
|
state: InspectorState,
|
|
10
24
|
logger: InspectorLogger
|
|
11
25
|
) {
|
|
12
|
-
// Look for
|
|
13
|
-
if (ts.
|
|
14
|
-
const { expression,
|
|
26
|
+
// Look for call expressions: external('ext:hello') or rpc.invoke('...')
|
|
27
|
+
if (ts.isCallExpression(node)) {
|
|
28
|
+
const { expression, arguments: args } = node
|
|
29
|
+
|
|
30
|
+
// Check for external('namespace:function') calls
|
|
31
|
+
if (ts.isIdentifier(expression) && expression.text === 'external') {
|
|
32
|
+
const [firstArg] = args
|
|
33
|
+
if (firstArg && ts.isStringLiteral(firstArg)) {
|
|
34
|
+
const functionRef = firstArg.text
|
|
35
|
+
logger.debug(`• Found external() call: ${functionRef}`)
|
|
36
|
+
state.rpc.invokedFunctions.add(functionRef)
|
|
15
37
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
const namespace = extractNamespace(functionRef)
|
|
39
|
+
if (namespace) {
|
|
40
|
+
logger.debug(` → External package detected: ${namespace}`)
|
|
41
|
+
state.rpc.usedExternalPackages.add(namespace)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Check for workflow('...'), workflowStart('...'), workflowRun('...'), workflowStatus('...'), graphStart('...') calls
|
|
47
|
+
if (
|
|
48
|
+
ts.isIdentifier(expression) &&
|
|
49
|
+
(expression.text === 'workflow' ||
|
|
50
|
+
expression.text === 'workflowStart' ||
|
|
51
|
+
expression.text === 'workflowRun' ||
|
|
52
|
+
expression.text === 'workflowStatus' ||
|
|
53
|
+
expression.text === 'graphStart')
|
|
54
|
+
) {
|
|
55
|
+
const [firstArg] = args
|
|
56
|
+
if (firstArg && ts.isStringLiteral(firstArg)) {
|
|
57
|
+
const workflowName = firstArg.text
|
|
58
|
+
logger.debug(`• Found ${expression.text}() call: ${workflowName}`)
|
|
59
|
+
state.workflows.invokedWorkflows.add(workflowName)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Check for rpc.invoke('...') calls
|
|
64
|
+
if (
|
|
65
|
+
ts.isPropertyAccessExpression(expression) &&
|
|
66
|
+
expression.name.text === 'invoke' &&
|
|
67
|
+
ts.isIdentifier(expression.expression) &&
|
|
68
|
+
expression.expression.text === 'rpc'
|
|
69
|
+
) {
|
|
70
|
+
const [firstArg] = args
|
|
71
|
+
if (firstArg) {
|
|
72
|
+
if (ts.isStringLiteral(firstArg)) {
|
|
73
|
+
const functionRef = firstArg.text
|
|
74
|
+
logger.debug(`• Found RPC invocation: ${functionRef}`)
|
|
75
|
+
state.rpc.invokedFunctions.add(functionRef)
|
|
76
|
+
|
|
77
|
+
const namespace = extractNamespace(functionRef)
|
|
78
|
+
if (namespace) {
|
|
79
|
+
logger.debug(` → External package detected: ${namespace}`)
|
|
80
|
+
state.rpc.usedExternalPackages.add(namespace)
|
|
44
81
|
}
|
|
45
82
|
}
|
|
83
|
+
// Handle template literals like `function-${name}`
|
|
84
|
+
else if (
|
|
85
|
+
ts.isTemplateExpression(firstArg) ||
|
|
86
|
+
ts.isNoSubstitutionTemplateLiteral(firstArg)
|
|
87
|
+
) {
|
|
88
|
+
logger.warn(`• Found dynamic RPC invocation: ${firstArg.getText()}`)
|
|
89
|
+
logger.warn(
|
|
90
|
+
`\tYou can only use string literals for RPC function names, with ' or " and not \``
|
|
91
|
+
)
|
|
92
|
+
}
|
|
46
93
|
}
|
|
47
94
|
}
|
|
48
95
|
}
|