@pikku/inspector 0.11.1 → 0.11.2
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 +16 -1
- package/dist/add/add-forge-credential.d.ts +8 -0
- package/dist/add/add-forge-credential.js +77 -0
- package/dist/add/add-forge-node.d.ts +7 -0
- package/dist/add/add-forge-node.js +77 -0
- package/dist/add/add-functions.js +102 -9
- package/dist/add/add-http-route.js +24 -1
- package/dist/add/add-rpc-invocations.d.ts +3 -0
- package/dist/add/add-rpc-invocations.js +51 -25
- package/dist/add/add-workflow-graph.d.ts +6 -0
- package/dist/add/add-workflow-graph.js +659 -0
- package/dist/add/add-workflow.js +118 -22
- package/dist/error-codes.d.ts +3 -1
- package/dist/error-codes.js +3 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/inspector.js +19 -3
- package/dist/types.d.ts +26 -0
- package/dist/utils/extract-function-name.js +7 -7
- package/dist/utils/get-property-value.d.ts +2 -1
- package/dist/utils/get-property-value.js +6 -2
- package/dist/utils/serialize-inspector-state.d.ts +24 -1
- package/dist/utils/serialize-inspector-state.js +24 -0
- package/dist/utils/workflow/dsl/deserialize-dsl-workflow.d.ts +24 -0
- package/dist/utils/workflow/dsl/deserialize-dsl-workflow.js +898 -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} +549 -68
- 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 +316 -0
- package/dist/utils/workflow/graph/index.d.ts +6 -0
- package/dist/utils/workflow/graph/index.js +6 -0
- package/dist/utils/workflow/graph/serialize-workflow-graph.d.ts +43 -0
- package/dist/utils/workflow/graph/serialize-workflow-graph.js +152 -0
- package/dist/utils/workflow/graph/workflow-graph.types.d.ts +229 -0
- package/dist/utils/workflow/graph/workflow-graph.types.js +38 -0
- package/dist/visit.js +6 -0
- package/package.json +14 -2
- package/src/add/add-forge-credential.ts +119 -0
- package/src/add/add-forge-node.ts +132 -0
- package/src/add/add-functions.ts +129 -15
- package/src/add/add-http-route.ts +25 -1
- package/src/add/add-rpc-invocations.ts +61 -31
- package/src/add/add-workflow-graph.ts +864 -0
- package/src/add/add-workflow.ts +112 -26
- package/src/error-codes.ts +3 -1
- package/src/index.ts +10 -0
- package/src/inspector.ts +20 -4
- package/src/types.ts +25 -1
- package/src/utils/extract-function-name.ts +7 -7
- package/src/utils/get-property-value.ts +9 -2
- package/src/utils/serialize-inspector-state.ts +39 -1
- package/src/utils/workflow/dsl/deserialize-dsl-workflow.ts +1180 -0
- package/src/{workflow/extract-simple-workflow.ts → utils/workflow/dsl/extract-dsl-workflow.ts} +654 -81
- 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 +415 -0
- package/src/utils/workflow/graph/index.ts +6 -0
- package/src/utils/workflow/graph/serialize-workflow-graph.ts +223 -0
- package/src/utils/workflow/graph/workflow-graph.types.ts +280 -0
- package/src/visit.ts +6 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import * as ts from 'typescript'
|
|
2
|
+
import {
|
|
3
|
+
getPropertyValue,
|
|
4
|
+
getCommonWireMetaData,
|
|
5
|
+
} from '../utils/get-property-value.js'
|
|
6
|
+
import { AddWiring } from '../types.js'
|
|
7
|
+
import { ErrorCode } from '../error-codes.js'
|
|
8
|
+
import type { ForgeNodeType } from '@pikku/core/forge-node'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Inspector for wireForgeNode calls.
|
|
12
|
+
* Extracts metadata for Forge workflow builder nodes.
|
|
13
|
+
* Note: wireForgeNode is metadata-only - no runtime behavior.
|
|
14
|
+
*/
|
|
15
|
+
export const addForgeNode: 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 wireForgeNode
|
|
31
|
+
if (!ts.isIdentifier(expression) || expression.text !== 'wireForgeNode') {
|
|
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 displayNameValue = getPropertyValue(obj, 'displayName') as
|
|
44
|
+
| string
|
|
45
|
+
| null
|
|
46
|
+
const categoryValue = getPropertyValue(obj, 'category') as string | null
|
|
47
|
+
const typeValue = getPropertyValue(obj, 'type') as ForgeNodeType | null
|
|
48
|
+
const rpcValue = getPropertyValue(obj, 'rpc') as string | null
|
|
49
|
+
const errorOutputValue = getPropertyValue(obj, 'errorOutput') as
|
|
50
|
+
| boolean
|
|
51
|
+
| null
|
|
52
|
+
|
|
53
|
+
const { tags, description } = getCommonWireMetaData(
|
|
54
|
+
obj,
|
|
55
|
+
'Forge node',
|
|
56
|
+
nameValue,
|
|
57
|
+
logger
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
// Validate required fields
|
|
61
|
+
if (!nameValue) {
|
|
62
|
+
logger.critical(
|
|
63
|
+
ErrorCode.MISSING_NAME,
|
|
64
|
+
"Forge node is missing the required 'name' property."
|
|
65
|
+
)
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!displayNameValue) {
|
|
70
|
+
logger.critical(
|
|
71
|
+
ErrorCode.MISSING_NAME,
|
|
72
|
+
`Forge node '${nameValue}' is missing the required 'displayName' property.`
|
|
73
|
+
)
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!categoryValue) {
|
|
78
|
+
logger.critical(
|
|
79
|
+
ErrorCode.MISSING_NAME,
|
|
80
|
+
`Forge node '${nameValue}' is missing the required 'category' property.`
|
|
81
|
+
)
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (!typeValue) {
|
|
86
|
+
logger.critical(
|
|
87
|
+
ErrorCode.MISSING_NAME,
|
|
88
|
+
`Forge node '${nameValue}' is missing the required 'type' property.`
|
|
89
|
+
)
|
|
90
|
+
return
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!['trigger', 'action', 'end'].includes(typeValue)) {
|
|
94
|
+
logger.critical(
|
|
95
|
+
ErrorCode.INVALID_VALUE,
|
|
96
|
+
`Forge node '${nameValue}' has invalid type '${typeValue}'. Must be 'trigger', 'action', or 'end'.`
|
|
97
|
+
)
|
|
98
|
+
return
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!rpcValue) {
|
|
102
|
+
logger.critical(
|
|
103
|
+
ErrorCode.MISSING_NAME,
|
|
104
|
+
`Forge node '${nameValue}' is missing the required 'rpc' property.`
|
|
105
|
+
)
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Get function metadata for input/output schemas
|
|
110
|
+
const fnMeta = state.functions.meta[rpcValue]
|
|
111
|
+
const inputSchemaName = fnMeta?.inputs?.[0] || null
|
|
112
|
+
const outputSchemaName = fnMeta?.outputs?.[0] || null
|
|
113
|
+
|
|
114
|
+
// Note: Category validation against forge.node.categories config
|
|
115
|
+
// is done at CLI build time, not during inspection
|
|
116
|
+
|
|
117
|
+
state.forgeNodes.files.add(node.getSourceFile().fileName)
|
|
118
|
+
|
|
119
|
+
state.forgeNodes.meta[nameValue] = {
|
|
120
|
+
name: nameValue,
|
|
121
|
+
displayName: displayNameValue,
|
|
122
|
+
category: categoryValue,
|
|
123
|
+
type: typeValue,
|
|
124
|
+
rpc: rpcValue,
|
|
125
|
+
description,
|
|
126
|
+
errorOutput: errorOutputValue ?? false,
|
|
127
|
+
inputSchemaName,
|
|
128
|
+
outputSchemaName,
|
|
129
|
+
tags,
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
package/src/add/add-functions.ts
CHANGED
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
getCommonWireMetaData,
|
|
10
10
|
} from '../utils/get-property-value.js'
|
|
11
11
|
import { resolveMiddleware } from '../utils/middleware.js'
|
|
12
|
+
import { resolvePermissions } from '../utils/permissions.js'
|
|
13
|
+
import { ErrorCode } from '../error-codes.js'
|
|
12
14
|
|
|
13
15
|
const isValidVariableName = (name: string) => {
|
|
14
16
|
const regex = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/
|
|
@@ -152,14 +154,18 @@ const resolveUnionTypes = (
|
|
|
152
154
|
// Check if it's a union type AND not part of an intersection
|
|
153
155
|
if (type.isUnion() && !(type.flags & ts.TypeFlags.Intersection)) {
|
|
154
156
|
for (const t of type.types) {
|
|
155
|
-
const name = nullifyTypes(
|
|
157
|
+
const name = nullifyTypes(
|
|
158
|
+
checker.typeToString(t, undefined, ts.TypeFormatFlags.NoTruncation)
|
|
159
|
+
)
|
|
156
160
|
if (name) {
|
|
157
161
|
types.push(t)
|
|
158
162
|
names.push(name)
|
|
159
163
|
}
|
|
160
164
|
}
|
|
161
165
|
} else {
|
|
162
|
-
const name = nullifyTypes(
|
|
166
|
+
const name = nullifyTypes(
|
|
167
|
+
checker.typeToString(type, undefined, ts.TypeFormatFlags.NoTruncation)
|
|
168
|
+
)
|
|
163
169
|
if (name) {
|
|
164
170
|
types.push(type)
|
|
165
171
|
names.push(name)
|
|
@@ -306,6 +312,7 @@ export const addFunctions: AddWiring = (logger, node, checker, state) => {
|
|
|
306
312
|
const { pikkuFuncName, name, explicitName, exportedName } =
|
|
307
313
|
extractFunctionName(node, checker, state.rootDir)
|
|
308
314
|
|
|
315
|
+
let title: string | undefined
|
|
309
316
|
let tags: string[] | undefined
|
|
310
317
|
let summary: string | undefined
|
|
311
318
|
let description: string | undefined
|
|
@@ -322,16 +329,85 @@ export const addFunctions: AddWiring = (logger, node, checker, state) => {
|
|
|
322
329
|
isDirectFunction,
|
|
323
330
|
} = extractFunctionNode(firstArg, checker)
|
|
324
331
|
|
|
332
|
+
// Variables to hold zod schema references if provided
|
|
333
|
+
let inputZodSchemaRef: { variableName: string; sourceFile: string } | null =
|
|
334
|
+
null
|
|
335
|
+
let outputZodSchemaRef: { variableName: string; sourceFile: string } | null =
|
|
336
|
+
null
|
|
337
|
+
|
|
338
|
+
// Helper to resolve schema identifier to its actual source file
|
|
339
|
+
const resolveSchemaSourceFile = (
|
|
340
|
+
identifier: ts.Identifier
|
|
341
|
+
): { variableName: string; sourceFile: string } | null => {
|
|
342
|
+
const symbol = checker.getSymbolAtLocation(identifier)
|
|
343
|
+
if (!symbol) return null
|
|
344
|
+
|
|
345
|
+
const decl = symbol.valueDeclaration || symbol.declarations?.[0]
|
|
346
|
+
if (!decl) return null
|
|
347
|
+
|
|
348
|
+
// If it's an import specifier, resolve the aliased symbol to get the actual source
|
|
349
|
+
if (ts.isImportSpecifier(decl)) {
|
|
350
|
+
const aliasedSymbol = checker.getAliasedSymbol(symbol)
|
|
351
|
+
if (aliasedSymbol) {
|
|
352
|
+
const aliasedDecl =
|
|
353
|
+
aliasedSymbol.valueDeclaration || aliasedSymbol.declarations?.[0]
|
|
354
|
+
if (aliasedDecl) {
|
|
355
|
+
return {
|
|
356
|
+
variableName: identifier.text,
|
|
357
|
+
sourceFile: aliasedDecl.getSourceFile().fileName,
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Not an import - use the current source file
|
|
364
|
+
return {
|
|
365
|
+
variableName: identifier.text,
|
|
366
|
+
sourceFile: decl.getSourceFile().fileName,
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
325
370
|
// Extract config properties if using object form
|
|
326
371
|
if (ts.isObjectLiteralExpression(firstArg)) {
|
|
327
372
|
objectNode = firstArg
|
|
328
373
|
const metadata = getCommonWireMetaData(firstArg, 'Function', name, logger)
|
|
374
|
+
title = metadata.title
|
|
329
375
|
tags = metadata.tags
|
|
330
376
|
summary = metadata.summary
|
|
331
377
|
description = metadata.description
|
|
332
378
|
errors = metadata.errors
|
|
333
379
|
expose = getPropertyValue(firstArg, 'expose') as boolean | undefined
|
|
334
380
|
internal = getPropertyValue(firstArg, 'internal') as boolean | undefined
|
|
381
|
+
|
|
382
|
+
// Extract zod schema variable names from input/output properties
|
|
383
|
+
for (const prop of firstArg.properties) {
|
|
384
|
+
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
|
|
385
|
+
const propName = prop.name.text
|
|
386
|
+
if (propName === 'input' || propName === 'output') {
|
|
387
|
+
if (ts.isIdentifier(prop.initializer)) {
|
|
388
|
+
// Good - it's a variable reference, resolve its actual source file
|
|
389
|
+
const ref = resolveSchemaSourceFile(prop.initializer)
|
|
390
|
+
if (ref) {
|
|
391
|
+
if (propName === 'input') {
|
|
392
|
+
inputZodSchemaRef = ref
|
|
393
|
+
} else {
|
|
394
|
+
outputZodSchemaRef = ref
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
} else if (ts.isCallExpression(prop.initializer)) {
|
|
398
|
+
// Bad - it's an inline expression
|
|
399
|
+
const schemaName = `${name.charAt(0).toUpperCase() + name.slice(1)}${propName.charAt(0).toUpperCase() + propName.slice(1)}`
|
|
400
|
+
logger.critical(
|
|
401
|
+
ErrorCode.INLINE_ZOD_SCHEMA,
|
|
402
|
+
`Inline Zod schemas are not supported for '${propName}' in '${name}'.\n` +
|
|
403
|
+
` Extract to an exported variable:\n` +
|
|
404
|
+
` export const ${schemaName} = ${prop.initializer.getText()}\n` +
|
|
405
|
+
` Then use: ${propName}: ${schemaName}`
|
|
406
|
+
)
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
335
411
|
}
|
|
336
412
|
|
|
337
413
|
// Pick the handler: use resolvedFunc when it exists and is a function, otherwise fall back to handlerNode
|
|
@@ -404,23 +480,54 @@ export const addFunctions: AddWiring = (logger, node, checker, state) => {
|
|
|
404
480
|
.map((tn) => checker.getTypeFromTypeNode(tn))
|
|
405
481
|
.map((t) => unwrapPromise(checker, t))
|
|
406
482
|
|
|
483
|
+
const capitalizedName = name.charAt(0).toUpperCase() + name.slice(1)
|
|
484
|
+
|
|
407
485
|
// --- Input Extraction ---
|
|
408
|
-
let
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
486
|
+
let inputNames: string[] = []
|
|
487
|
+
let inputTypes: ts.Type[] = []
|
|
488
|
+
|
|
489
|
+
if (inputZodSchemaRef) {
|
|
490
|
+
const schemaName = `${capitalizedName}Input`
|
|
491
|
+
inputNames = [schemaName]
|
|
492
|
+
state.zodLookup.set(schemaName, inputZodSchemaRef)
|
|
493
|
+
state.functions.typesMap.addCustomType(schemaName, 'unknown', [])
|
|
494
|
+
} else if (genericTypes.length >= 1 && genericTypes[0]) {
|
|
495
|
+
// Fall back to extracting from generic type arguments
|
|
496
|
+
const result = getNamesAndTypes(
|
|
497
|
+
checker,
|
|
498
|
+
state.functions.typesMap,
|
|
499
|
+
'Input',
|
|
500
|
+
name,
|
|
501
|
+
genericTypes[0]
|
|
502
|
+
)
|
|
503
|
+
inputNames = result.names
|
|
504
|
+
inputTypes = result.types
|
|
505
|
+
} else {
|
|
506
|
+
// Fall back to extracting from the function's second parameter type
|
|
507
|
+
const secondParam = handler.parameters[1]
|
|
508
|
+
if (secondParam) {
|
|
509
|
+
const paramType = checker.getTypeAtLocation(secondParam)
|
|
510
|
+
const result = getNamesAndTypes(
|
|
511
|
+
checker,
|
|
512
|
+
state.functions.typesMap,
|
|
513
|
+
'Input',
|
|
514
|
+
pikkuFuncName,
|
|
515
|
+
paramType
|
|
516
|
+
)
|
|
517
|
+
inputNames = result.names
|
|
518
|
+
inputTypes = result.types
|
|
519
|
+
}
|
|
520
|
+
}
|
|
420
521
|
|
|
421
522
|
// --- Output Extraction ---
|
|
422
523
|
let outputNames: string[] = []
|
|
423
|
-
|
|
524
|
+
|
|
525
|
+
if (outputZodSchemaRef) {
|
|
526
|
+
const schemaName = `${capitalizedName}Output`
|
|
527
|
+
outputNames = [schemaName]
|
|
528
|
+
state.zodLookup.set(schemaName, outputZodSchemaRef)
|
|
529
|
+
state.functions.typesMap.addCustomType(schemaName, 'unknown', [])
|
|
530
|
+
} else if (genericTypes.length >= 2) {
|
|
424
531
|
outputNames = getNamesAndTypes(
|
|
425
532
|
checker,
|
|
426
533
|
state.functions.typesMap,
|
|
@@ -459,6 +566,11 @@ export const addFunctions: AddWiring = (logger, node, checker, state) => {
|
|
|
459
566
|
? resolveMiddleware(state, objectNode, tags, checker)
|
|
460
567
|
: undefined
|
|
461
568
|
|
|
569
|
+
// --- resolve permissions ---
|
|
570
|
+
const permissions = objectNode
|
|
571
|
+
? resolvePermissions(state, objectNode, tags, checker)
|
|
572
|
+
: undefined
|
|
573
|
+
|
|
462
574
|
state.functions.meta[pikkuFuncName] = {
|
|
463
575
|
pikkuFuncName,
|
|
464
576
|
name,
|
|
@@ -470,11 +582,13 @@ export const addFunctions: AddWiring = (logger, node, checker, state) => {
|
|
|
470
582
|
outputs: outputNames.filter((n) => n !== 'void') ?? null,
|
|
471
583
|
expose: expose || undefined,
|
|
472
584
|
internal: internal || undefined,
|
|
585
|
+
title,
|
|
473
586
|
tags: tags || undefined,
|
|
474
587
|
summary,
|
|
475
588
|
description,
|
|
476
589
|
errors,
|
|
477
590
|
middleware,
|
|
591
|
+
permissions,
|
|
478
592
|
isDirectFunction,
|
|
479
593
|
}
|
|
480
594
|
|
|
@@ -70,7 +70,7 @@ export const addHTTPRoute: AddWiring = (
|
|
|
70
70
|
|
|
71
71
|
const method =
|
|
72
72
|
(getPropertyValue(obj, 'method') as string)?.toLowerCase() || 'get'
|
|
73
|
-
const { tags, summary, description, errors } = getCommonWireMetaData(
|
|
73
|
+
const { title, tags, summary, description, errors } = getCommonWireMetaData(
|
|
74
74
|
obj,
|
|
75
75
|
'HTTP route',
|
|
76
76
|
route,
|
|
@@ -78,6 +78,29 @@ export const addHTTPRoute: AddWiring = (
|
|
|
78
78
|
)
|
|
79
79
|
const query = (getPropertyValue(obj, 'query') as string[]) || []
|
|
80
80
|
|
|
81
|
+
// Check if this is a workflow trigger (workflow: true)
|
|
82
|
+
const isWorkflowTrigger = getPropertyValue(obj, 'workflow') === true
|
|
83
|
+
if (isWorkflowTrigger) {
|
|
84
|
+
// Workflow triggers don't need func - they're handled by workflow-utils
|
|
85
|
+
// Just record the route for HTTP meta but skip function processing
|
|
86
|
+
state.http.files.add(node.getSourceFile().fileName)
|
|
87
|
+
state.http.meta[method][route] = {
|
|
88
|
+
pikkuFuncName: '', // No function - workflow handles it
|
|
89
|
+
route,
|
|
90
|
+
method: method as HTTPMethod,
|
|
91
|
+
params: params.length > 0 ? params : undefined,
|
|
92
|
+
query: query.length > 0 ? query : undefined,
|
|
93
|
+
inputTypes: undefined,
|
|
94
|
+
title,
|
|
95
|
+
summary,
|
|
96
|
+
description,
|
|
97
|
+
errors,
|
|
98
|
+
tags,
|
|
99
|
+
workflow: true,
|
|
100
|
+
}
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
|
|
81
104
|
// --- find the referenced function name first for filtering ---
|
|
82
105
|
const funcInitializer = getPropertyAssignmentInitializer(
|
|
83
106
|
obj,
|
|
@@ -158,6 +181,7 @@ export const addHTTPRoute: AddWiring = (
|
|
|
158
181
|
params: params.length > 0 ? params : undefined,
|
|
159
182
|
query: query.length > 0 ? query : undefined,
|
|
160
183
|
inputTypes,
|
|
184
|
+
title,
|
|
161
185
|
summary,
|
|
162
186
|
description,
|
|
163
187
|
errors,
|
|
@@ -1,48 +1,78 @@
|
|
|
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 rpc.invoke('...') calls
|
|
47
|
+
if (
|
|
48
|
+
ts.isPropertyAccessExpression(expression) &&
|
|
49
|
+
expression.name.text === 'invoke' &&
|
|
50
|
+
ts.isIdentifier(expression.expression) &&
|
|
51
|
+
expression.expression.text === 'rpc'
|
|
52
|
+
) {
|
|
53
|
+
const [firstArg] = args
|
|
54
|
+
if (firstArg) {
|
|
55
|
+
if (ts.isStringLiteral(firstArg)) {
|
|
56
|
+
const functionRef = firstArg.text
|
|
57
|
+
logger.debug(`• Found RPC invocation: ${functionRef}`)
|
|
58
|
+
state.rpc.invokedFunctions.add(functionRef)
|
|
59
|
+
|
|
60
|
+
const namespace = extractNamespace(functionRef)
|
|
61
|
+
if (namespace) {
|
|
62
|
+
logger.debug(` → External package detected: ${namespace}`)
|
|
63
|
+
state.rpc.usedExternalPackages.add(namespace)
|
|
44
64
|
}
|
|
45
65
|
}
|
|
66
|
+
// Handle template literals like `function-${name}`
|
|
67
|
+
else if (
|
|
68
|
+
ts.isTemplateExpression(firstArg) ||
|
|
69
|
+
ts.isNoSubstitutionTemplateLiteral(firstArg)
|
|
70
|
+
) {
|
|
71
|
+
logger.warn(`• Found dynamic RPC invocation: ${firstArg.getText()}`)
|
|
72
|
+
logger.warn(
|
|
73
|
+
`\tYou can only use string literals for RPC function names, with ' or " and not \``
|
|
74
|
+
)
|
|
75
|
+
}
|
|
46
76
|
}
|
|
47
77
|
}
|
|
48
78
|
}
|