@pikku/inspector 0.9.6-next.0 → 0.10.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 +14 -0
- package/dist/add/add-channel.d.ts +5 -1
- package/dist/add/add-channel.js +51 -32
- package/dist/add/add-cli.d.ts +4 -0
- package/dist/add/add-cli.js +128 -23
- package/dist/add/add-file-extends-core-type.js +3 -2
- package/dist/add/add-file-with-factory.d.ts +2 -2
- package/dist/add/add-file-with-factory.js +87 -1
- package/dist/add/add-functions.js +52 -5
- package/dist/add/add-http-route.js +19 -12
- package/dist/add/add-mcp-prompt.js +20 -13
- package/dist/add/add-mcp-resource.js +24 -14
- package/dist/add/add-mcp-tool.js +23 -13
- package/dist/add/add-middleware.js +51 -12
- package/dist/add/add-permission.d.ts +1 -2
- package/dist/add/add-permission.js +275 -19
- package/dist/add/add-queue-worker.js +10 -12
- package/dist/add/add-schedule.js +9 -10
- package/dist/error-codes.d.ts +35 -0
- package/dist/error-codes.js +40 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/inspector.js +20 -1
- package/dist/types.d.ts +31 -3
- 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 +2 -2
- package/dist/utils/extract-function-name.js +13 -8
- 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 +10 -0
- package/dist/utils/filter-utils.js +66 -2
- 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 +2 -1
- package/dist/utils/get-files-and-methods.js +4 -3
- package/dist/utils/get-property-value.d.ts +9 -0
- package/dist/utils/get-property-value.js +20 -0
- package/dist/utils/middleware.d.ts +1 -1
- package/dist/utils/middleware.js +7 -7
- 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/visit.js +3 -2
- package/package.json +4 -4
- package/src/add/add-channel.ts +92 -40
- package/src/add/add-cli.ts +188 -29
- package/src/add/add-file-extends-core-type.ts +5 -2
- package/src/add/add-file-with-factory.ts +114 -2
- package/src/add/add-functions.ts +60 -5
- package/src/add/add-http-route.ts +46 -21
- package/src/add/add-mcp-prompt.ts +42 -21
- package/src/add/add-mcp-prompt.ts.tmp +0 -0
- package/src/add/add-mcp-resource.ts +50 -24
- package/src/add/add-mcp-resource.ts.tmp +0 -0
- package/src/add/add-mcp-tool.ts +48 -21
- package/src/add/add-middleware.ts +74 -15
- package/src/add/add-permission.ts +364 -22
- package/src/add/add-queue-worker.ts +22 -25
- package/src/add/add-schedule.ts +19 -20
- package/src/error-codes.ts +43 -0
- package/src/index.ts +7 -0
- package/src/inspector.ts +22 -1
- package/src/types.ts +38 -3
- package/src/utils/ensure-function-metadata.ts +24 -0
- package/src/utils/extract-function-name.ts +20 -8
- package/src/utils/filter-inspector-state.test.ts +1433 -0
- package/src/utils/filter-inspector-state.ts +526 -0
- package/src/utils/filter-utils.test.ts +350 -1
- package/src/utils/filter-utils.ts +82 -2
- package/src/utils/find-root-dir.ts +68 -0
- package/src/utils/get-files-and-methods.ts +10 -2
- package/src/utils/get-property-value.ts +27 -0
- package/src/utils/middleware.ts +14 -7
- 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/visit.ts +4 -2
- package/tsconfig.tsbuildinfo +1 -1
package/src/add/add-cli.ts
CHANGED
|
@@ -5,11 +5,15 @@ import {
|
|
|
5
5
|
InspectorOptions,
|
|
6
6
|
InspectorState,
|
|
7
7
|
} from '../types.js'
|
|
8
|
-
import { CLIProgramMeta, CLICommandMeta } from '@pikku/core'
|
|
8
|
+
import { CLIProgramMeta, CLICommandMeta } from '@pikku/core/cli'
|
|
9
9
|
import { extractFunctionName } from '../utils/extract-function-name.js'
|
|
10
10
|
import { resolveMiddleware } from '../utils/middleware.js'
|
|
11
|
+
import { extractWireNames } from '../utils/post-process.js'
|
|
11
12
|
import { getPropertyValue } from '../utils/get-property-value.js'
|
|
12
13
|
|
|
14
|
+
// Track if we've warned about missing Config type to avoid duplicate warnings
|
|
15
|
+
const configTypeWarningShown = new Set<string>()
|
|
16
|
+
|
|
13
17
|
/**
|
|
14
18
|
* Adds CLI command metadata to the inspector state
|
|
15
19
|
*/
|
|
@@ -60,7 +64,8 @@ export const addCLI: AddWiring = (
|
|
|
60
64
|
}
|
|
61
65
|
|
|
62
66
|
// Add this program to the CLI metadata
|
|
63
|
-
inspectorState.cli.meta[cliConfig.programName] =
|
|
67
|
+
inspectorState.cli.meta.programs[cliConfig.programName] =
|
|
68
|
+
cliConfig.programMeta
|
|
64
69
|
}
|
|
65
70
|
|
|
66
71
|
/**
|
|
@@ -75,12 +80,33 @@ function processCLIConfig(
|
|
|
75
80
|
options: InspectorOptions
|
|
76
81
|
): { programName: string; programMeta: CLIProgramMeta } | null {
|
|
77
82
|
let programName = ''
|
|
83
|
+
let programTags: string[] | undefined
|
|
78
84
|
const programMeta: CLIProgramMeta = {
|
|
79
85
|
program: '',
|
|
80
86
|
commands: {},
|
|
81
87
|
options: {},
|
|
82
88
|
}
|
|
83
89
|
|
|
90
|
+
// First pass: extract program name and tags
|
|
91
|
+
for (const prop of node.properties) {
|
|
92
|
+
if (!ts.isPropertyAssignment(prop)) continue
|
|
93
|
+
if (!ts.isIdentifier(prop.name)) continue
|
|
94
|
+
|
|
95
|
+
const propName = prop.name.text
|
|
96
|
+
|
|
97
|
+
if (propName === 'program' && ts.isStringLiteral(prop.initializer)) {
|
|
98
|
+
programName = prop.initializer.text
|
|
99
|
+
programMeta.program = programName
|
|
100
|
+
} else if (propName === 'tags') {
|
|
101
|
+
programTags = (getPropertyValue(node, 'tags') as string[]) || undefined
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (!programName) {
|
|
106
|
+
return null
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Second pass: process other properties with program tags available
|
|
84
110
|
for (const prop of node.properties) {
|
|
85
111
|
if (!ts.isPropertyAssignment(prop)) continue
|
|
86
112
|
if (!ts.isIdentifier(prop.name)) continue
|
|
@@ -89,10 +115,8 @@ function processCLIConfig(
|
|
|
89
115
|
|
|
90
116
|
switch (propName) {
|
|
91
117
|
case 'program':
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
programMeta.program = programName
|
|
95
|
-
}
|
|
118
|
+
case 'tags':
|
|
119
|
+
// Already handled in first pass
|
|
96
120
|
break
|
|
97
121
|
|
|
98
122
|
case 'commands':
|
|
@@ -104,7 +128,8 @@ function processCLIConfig(
|
|
|
104
128
|
typeChecker,
|
|
105
129
|
programName,
|
|
106
130
|
inspectorState,
|
|
107
|
-
options
|
|
131
|
+
options,
|
|
132
|
+
programTags
|
|
108
133
|
)
|
|
109
134
|
}
|
|
110
135
|
break
|
|
@@ -122,16 +147,16 @@ function processCLIConfig(
|
|
|
122
147
|
break
|
|
123
148
|
|
|
124
149
|
case 'render':
|
|
125
|
-
//
|
|
126
|
-
programMeta.defaultRenderName =
|
|
150
|
+
// Extract the actual renderer function name
|
|
151
|
+
programMeta.defaultRenderName = extractFunctionName(
|
|
152
|
+
prop.initializer,
|
|
153
|
+
typeChecker,
|
|
154
|
+
inspectorState.rootDir
|
|
155
|
+
).pikkuFuncName
|
|
127
156
|
break
|
|
128
157
|
}
|
|
129
158
|
}
|
|
130
159
|
|
|
131
|
-
if (!programName) {
|
|
132
|
-
return null
|
|
133
|
-
}
|
|
134
|
-
|
|
135
160
|
return { programName, programMeta }
|
|
136
161
|
}
|
|
137
162
|
|
|
@@ -145,9 +170,11 @@ function processCommands(
|
|
|
145
170
|
typeChecker: TypeChecker,
|
|
146
171
|
programName: string,
|
|
147
172
|
inspectorState: InspectorState,
|
|
148
|
-
options: InspectorOptions
|
|
173
|
+
options: InspectorOptions,
|
|
174
|
+
programTags?: string[]
|
|
149
175
|
): Record<string, CLICommandMeta> {
|
|
150
176
|
const commands: Record<string, CLICommandMeta> = {}
|
|
177
|
+
let defaultCommandName: string | null = null
|
|
151
178
|
|
|
152
179
|
for (const prop of node.properties) {
|
|
153
180
|
if (!ts.isPropertyAssignment(prop)) continue
|
|
@@ -163,11 +190,29 @@ function processCommands(
|
|
|
163
190
|
prop.initializer,
|
|
164
191
|
sourceFile,
|
|
165
192
|
typeChecker,
|
|
166
|
-
programName
|
|
193
|
+
programName,
|
|
194
|
+
[],
|
|
195
|
+
programTags
|
|
167
196
|
)
|
|
168
197
|
|
|
169
198
|
if (commandMeta) {
|
|
170
199
|
commands[commandName] = commandMeta
|
|
200
|
+
|
|
201
|
+
// Validate only one default command
|
|
202
|
+
if (commandMeta.isDefault) {
|
|
203
|
+
if (defaultCommandName !== null) {
|
|
204
|
+
const position = prop.getStart(sourceFile)
|
|
205
|
+
const { line, character } =
|
|
206
|
+
sourceFile.getLineAndCharacterOfPosition(position)
|
|
207
|
+
|
|
208
|
+
throw new Error(
|
|
209
|
+
`Multiple default commands found in CLI program "${programName}" at ${sourceFile.fileName}:${line + 1}:${character + 1}.\n` +
|
|
210
|
+
`Commands "${defaultCommandName}" and "${commandName}" are both marked as default.\n` +
|
|
211
|
+
`Only one command can be marked as default per program.`
|
|
212
|
+
)
|
|
213
|
+
}
|
|
214
|
+
defaultCommandName = commandName
|
|
215
|
+
}
|
|
171
216
|
}
|
|
172
217
|
}
|
|
173
218
|
|
|
@@ -186,7 +231,8 @@ function processCommand(
|
|
|
186
231
|
sourceFile: ts.SourceFile,
|
|
187
232
|
typeChecker: TypeChecker,
|
|
188
233
|
programName: string,
|
|
189
|
-
parentPath: string[] = []
|
|
234
|
+
parentPath: string[] = [],
|
|
235
|
+
programTags?: string[]
|
|
190
236
|
): CLICommandMeta | null {
|
|
191
237
|
const fullPath = [...parentPath, name]
|
|
192
238
|
|
|
@@ -197,7 +243,11 @@ function processCommand(
|
|
|
197
243
|
ts.isFunctionExpression(node)
|
|
198
244
|
) {
|
|
199
245
|
return {
|
|
200
|
-
pikkuFuncName: extractFunctionName(
|
|
246
|
+
pikkuFuncName: extractFunctionName(
|
|
247
|
+
node,
|
|
248
|
+
typeChecker,
|
|
249
|
+
inspectorState.rootDir
|
|
250
|
+
).pikkuFuncName,
|
|
201
251
|
positionals: [],
|
|
202
252
|
options: {},
|
|
203
253
|
}
|
|
@@ -222,7 +272,8 @@ function processCommand(
|
|
|
222
272
|
sourceFile,
|
|
223
273
|
typeChecker,
|
|
224
274
|
programName,
|
|
225
|
-
parentPath
|
|
275
|
+
parentPath,
|
|
276
|
+
programTags
|
|
226
277
|
)
|
|
227
278
|
}
|
|
228
279
|
return null
|
|
@@ -253,7 +304,8 @@ function processCommand(
|
|
|
253
304
|
if (propName === 'func') {
|
|
254
305
|
pikkuFuncName = extractFunctionName(
|
|
255
306
|
prop.initializer,
|
|
256
|
-
typeChecker
|
|
307
|
+
typeChecker,
|
|
308
|
+
inspectorState.rootDir
|
|
257
309
|
).pikkuFuncName
|
|
258
310
|
meta.pikkuFuncName = pikkuFuncName
|
|
259
311
|
} else if (
|
|
@@ -266,12 +318,25 @@ function processCommand(
|
|
|
266
318
|
}
|
|
267
319
|
}
|
|
268
320
|
|
|
321
|
+
// Merge program-level tags with command-level tags
|
|
322
|
+
const allTags = [...(programTags || []), ...(tags || [])]
|
|
323
|
+
|
|
269
324
|
// Resolve middleware
|
|
270
|
-
const middleware = resolveMiddleware(
|
|
325
|
+
const middleware = resolveMiddleware(
|
|
326
|
+
inspectorState,
|
|
327
|
+
node,
|
|
328
|
+
allTags.length > 0 ? allTags : undefined,
|
|
329
|
+
typeChecker
|
|
330
|
+
)
|
|
271
331
|
if (middleware) {
|
|
272
332
|
meta.middleware = middleware
|
|
273
333
|
}
|
|
274
334
|
|
|
335
|
+
// Add merged tags to metadata
|
|
336
|
+
if (allTags.length > 0) {
|
|
337
|
+
meta.tags = allTags
|
|
338
|
+
}
|
|
339
|
+
|
|
275
340
|
// Second pass: process all properties
|
|
276
341
|
for (const prop of node.properties) {
|
|
277
342
|
if (!ts.isPropertyAssignment(prop)) continue
|
|
@@ -300,7 +365,8 @@ function processCommand(
|
|
|
300
365
|
case 'render':
|
|
301
366
|
meta.renderName = extractFunctionName(
|
|
302
367
|
prop.initializer,
|
|
303
|
-
typeChecker
|
|
368
|
+
typeChecker,
|
|
369
|
+
inspectorState.rootDir
|
|
304
370
|
).pikkuFuncName
|
|
305
371
|
break
|
|
306
372
|
|
|
@@ -336,7 +402,8 @@ function processCommand(
|
|
|
336
402
|
sourceFile,
|
|
337
403
|
typeChecker,
|
|
338
404
|
programName,
|
|
339
|
-
fullPath
|
|
405
|
+
fullPath,
|
|
406
|
+
programTags
|
|
340
407
|
)
|
|
341
408
|
|
|
342
409
|
if (subCommand) {
|
|
@@ -345,9 +412,25 @@ function processCommand(
|
|
|
345
412
|
}
|
|
346
413
|
}
|
|
347
414
|
break
|
|
415
|
+
|
|
416
|
+
case 'isDefault':
|
|
417
|
+
if (
|
|
418
|
+
prop.initializer.kind === ts.SyntaxKind.TrueKeyword ||
|
|
419
|
+
prop.initializer.kind === ts.SyntaxKind.FalseKeyword
|
|
420
|
+
) {
|
|
421
|
+
meta.isDefault = prop.initializer.kind === ts.SyntaxKind.TrueKeyword
|
|
422
|
+
}
|
|
423
|
+
break
|
|
348
424
|
}
|
|
349
425
|
}
|
|
350
426
|
|
|
427
|
+
// --- track used functions/middleware for service aggregation ---
|
|
428
|
+
inspectorState.serviceAggregation.usedFunctions.add(meta.pikkuFuncName)
|
|
429
|
+
extractWireNames(meta.middleware).forEach((name) =>
|
|
430
|
+
inspectorState.serviceAggregation.usedMiddleware.add(name)
|
|
431
|
+
)
|
|
432
|
+
// Note: subcommands are tracked recursively when they're processed
|
|
433
|
+
|
|
351
434
|
return meta
|
|
352
435
|
}
|
|
353
436
|
|
|
@@ -575,19 +658,24 @@ function extractEnumFromConfigType(
|
|
|
575
658
|
// Look for Config type in typesLookup
|
|
576
659
|
const configTypes = inspectorState.typesLookup.get('Config')
|
|
577
660
|
if (!configTypes || configTypes.length === 0) {
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
661
|
+
// Only warn once per CLI file to avoid spamming logs
|
|
662
|
+
if (!configTypeWarningShown.has('missing-config-type')) {
|
|
663
|
+
configTypeWarningShown.add('missing-config-type')
|
|
664
|
+
logger.warn(
|
|
665
|
+
`Could not find Config type in typesLookup. ` +
|
|
666
|
+
`Make sure you have a Config interface extending CoreConfig in your codebase.`
|
|
667
|
+
)
|
|
668
|
+
}
|
|
582
669
|
return null
|
|
583
670
|
}
|
|
584
671
|
|
|
585
672
|
// Use the first Config type (there should only be one)
|
|
586
673
|
const configType = configTypes[0]
|
|
587
674
|
if (!configType) {
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
675
|
+
if (!configTypeWarningShown.has('undefined-config-type')) {
|
|
676
|
+
configTypeWarningShown.add('undefined-config-type')
|
|
677
|
+
logger.warn(`Config type is undefined in typesLookup.`)
|
|
678
|
+
}
|
|
591
679
|
return null
|
|
592
680
|
}
|
|
593
681
|
|
|
@@ -661,3 +749,74 @@ function parseCommandPattern(pattern: string): any[] {
|
|
|
661
749
|
|
|
662
750
|
return positionals
|
|
663
751
|
}
|
|
752
|
+
|
|
753
|
+
/**
|
|
754
|
+
* Adds CLI renderer metadata to the inspector state
|
|
755
|
+
*/
|
|
756
|
+
export const addCLIRenderers: AddWiring = (
|
|
757
|
+
logger,
|
|
758
|
+
node,
|
|
759
|
+
typeChecker,
|
|
760
|
+
inspectorState,
|
|
761
|
+
options
|
|
762
|
+
) => {
|
|
763
|
+
if (!ts.isCallExpression(node)) return
|
|
764
|
+
|
|
765
|
+
const { expression, arguments: args, typeArguments } = node
|
|
766
|
+
|
|
767
|
+
// Only handle pikkuCLIRender calls
|
|
768
|
+
if (!ts.isIdentifier(expression) || expression.text !== 'pikkuCLIRender') {
|
|
769
|
+
return
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
if (args.length === 0) return
|
|
773
|
+
|
|
774
|
+
// Extract renderer name
|
|
775
|
+
const { pikkuFuncName, exportedName } = extractFunctionName(
|
|
776
|
+
node,
|
|
777
|
+
typeChecker,
|
|
778
|
+
inspectorState.rootDir
|
|
779
|
+
)
|
|
780
|
+
|
|
781
|
+
// Get the source file path
|
|
782
|
+
const sourceFile = node.getSourceFile()
|
|
783
|
+
const filePath = sourceFile.fileName
|
|
784
|
+
|
|
785
|
+
// Extract services from type parameters (second type param is Services)
|
|
786
|
+
const services: { optimized: boolean; services: string[] } = {
|
|
787
|
+
optimized: true,
|
|
788
|
+
services: [],
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
if (typeArguments && typeArguments.length >= 2) {
|
|
792
|
+
// Second type parameter is the Services type
|
|
793
|
+
const servicesTypeNode = typeArguments[1]
|
|
794
|
+
if (servicesTypeNode) {
|
|
795
|
+
const servicesType = typeChecker.getTypeFromTypeNode(servicesTypeNode)
|
|
796
|
+
|
|
797
|
+
// Extract property names from the Services type
|
|
798
|
+
const properties = servicesType.getProperties()
|
|
799
|
+
for (const prop of properties) {
|
|
800
|
+
services.services.push(prop.getName())
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// If no specific services found, it might be using the full services object
|
|
804
|
+
if (properties.length === 0) {
|
|
805
|
+
services.optimized = false
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// Store renderer metadata
|
|
811
|
+
inspectorState.cli.meta.renderers[pikkuFuncName] = {
|
|
812
|
+
name: pikkuFuncName,
|
|
813
|
+
exportedName: exportedName ?? undefined,
|
|
814
|
+
services,
|
|
815
|
+
filePath,
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
// Add to files map if exported
|
|
819
|
+
if (exportedName) {
|
|
820
|
+
inspectorState.cli.files.add(filePath)
|
|
821
|
+
}
|
|
822
|
+
}
|
|
@@ -36,11 +36,14 @@ export const addFileExtendsCoreType = (
|
|
|
36
36
|
const variables = methods.get(fileName) || []
|
|
37
37
|
|
|
38
38
|
if (!typeName) {
|
|
39
|
-
throw new Error(
|
|
39
|
+
throw new Error(
|
|
40
|
+
`Found anonymous ${ts.isClassDeclaration(node) ? 'class' : 'interface'} extending ${expectedTypeName} in ${fileName}. ` +
|
|
41
|
+
`Classes and interfaces that extend core types must have a name.`
|
|
42
|
+
)
|
|
40
43
|
}
|
|
41
44
|
variables.push({
|
|
42
45
|
variable: typeName,
|
|
43
|
-
type: typeName
|
|
46
|
+
type: typeName,
|
|
44
47
|
typePath: extendedTypeDeclarationPath,
|
|
45
48
|
})
|
|
46
49
|
methods.set(fileName, variables)
|
|
@@ -1,17 +1,88 @@
|
|
|
1
1
|
import * as ts from 'typescript'
|
|
2
|
-
import { PathToNameAndType } from '../types.js'
|
|
2
|
+
import { PathToNameAndType, InspectorState } from '../types.js'
|
|
3
|
+
import { extractServicesFromFunction } from '../utils/extract-services.js'
|
|
4
|
+
|
|
5
|
+
// Mapping of wrapper function names to their corresponding types
|
|
6
|
+
const wrapperFunctionMap: Record<string, string> = {
|
|
7
|
+
pikkuConfig: 'CreateConfig',
|
|
8
|
+
pikkuServices: 'CreateSingletonServices',
|
|
9
|
+
pikkuSessionServices: 'CreateSessionServices',
|
|
10
|
+
}
|
|
3
11
|
|
|
4
12
|
export const addFileWithFactory = (
|
|
5
13
|
node: ts.Node,
|
|
6
14
|
checker: ts.TypeChecker,
|
|
7
15
|
methods: PathToNameAndType = new Map(),
|
|
8
|
-
expectedTypeName: string
|
|
16
|
+
expectedTypeName: string,
|
|
17
|
+
state?: InspectorState
|
|
9
18
|
) => {
|
|
10
19
|
if (ts.isVariableDeclaration(node)) {
|
|
11
20
|
const fileName = node.getSourceFile().fileName
|
|
12
21
|
const variableTypeNode = node.type
|
|
13
22
|
const variableName = node.name.getText()
|
|
14
23
|
|
|
24
|
+
// Check for wrapper function calls FIRST (e.g., pikkuConfig(...), pikkuServices(...))
|
|
25
|
+
// This handles both cases: with and without explicit type annotations
|
|
26
|
+
if (node.initializer && ts.isCallExpression(node.initializer)) {
|
|
27
|
+
const callExpression = node.initializer
|
|
28
|
+
const expression = callExpression.expression
|
|
29
|
+
|
|
30
|
+
if (ts.isIdentifier(expression)) {
|
|
31
|
+
const wrapperFunctionName = expression.text
|
|
32
|
+
const inferredType = wrapperFunctionMap[wrapperFunctionName]
|
|
33
|
+
|
|
34
|
+
if (inferredType === expectedTypeName) {
|
|
35
|
+
// Get the type declaration path from the wrapper function
|
|
36
|
+
const typeSymbol = checker.getSymbolAtLocation(expression)
|
|
37
|
+
let typeDeclarationPath: string | null = null
|
|
38
|
+
|
|
39
|
+
if (
|
|
40
|
+
typeSymbol &&
|
|
41
|
+
typeSymbol.declarations &&
|
|
42
|
+
typeSymbol.declarations[0]
|
|
43
|
+
) {
|
|
44
|
+
const declaration = typeSymbol.declarations[0]
|
|
45
|
+
const sourceFile = declaration.getSourceFile()
|
|
46
|
+
typeDeclarationPath = sourceFile.fileName
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const variables = methods.get(fileName) || []
|
|
50
|
+
variables.push({
|
|
51
|
+
variable: variableName,
|
|
52
|
+
type: inferredType,
|
|
53
|
+
typePath: typeDeclarationPath,
|
|
54
|
+
})
|
|
55
|
+
methods.set(fileName, variables)
|
|
56
|
+
|
|
57
|
+
// Extract singleton services for CreateSessionServices factories
|
|
58
|
+
if (
|
|
59
|
+
expectedTypeName === 'CreateSessionServices' &&
|
|
60
|
+
state &&
|
|
61
|
+
callExpression.arguments.length > 0
|
|
62
|
+
) {
|
|
63
|
+
const firstArg = callExpression.arguments[0]
|
|
64
|
+
let functionNode:
|
|
65
|
+
| ts.ArrowFunction
|
|
66
|
+
| ts.FunctionExpression
|
|
67
|
+
| undefined
|
|
68
|
+
|
|
69
|
+
if (ts.isArrowFunction(firstArg)) {
|
|
70
|
+
functionNode = firstArg
|
|
71
|
+
} else if (ts.isFunctionExpression(firstArg)) {
|
|
72
|
+
functionNode = firstArg
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (functionNode) {
|
|
76
|
+
const servicesMeta = extractServicesFromFunction(functionNode)
|
|
77
|
+
state.sessionServicesMeta.set(variableName, servicesMeta.services)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return // Early return since we found a match
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
15
86
|
if (variableTypeNode && ts.isTypeReferenceNode(variableTypeNode)) {
|
|
16
87
|
const typeNameNode = variableTypeNode.typeName || null
|
|
17
88
|
|
|
@@ -37,6 +108,25 @@ export const addFileWithFactory = (
|
|
|
37
108
|
typePath: typeDeclarationPath,
|
|
38
109
|
})
|
|
39
110
|
methods.set(fileName, variables)
|
|
111
|
+
|
|
112
|
+
// Extract singleton services for CreateSessionServices factories
|
|
113
|
+
if (
|
|
114
|
+
expectedTypeName === 'CreateSessionServices' &&
|
|
115
|
+
state &&
|
|
116
|
+
node.initializer
|
|
117
|
+
) {
|
|
118
|
+
let functionNode: ts.ArrowFunction | ts.FunctionExpression | undefined
|
|
119
|
+
if (ts.isArrowFunction(node.initializer)) {
|
|
120
|
+
functionNode = node.initializer
|
|
121
|
+
} else if (ts.isFunctionExpression(node.initializer)) {
|
|
122
|
+
functionNode = node.initializer
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (functionNode) {
|
|
126
|
+
const servicesMeta = extractServicesFromFunction(functionNode)
|
|
127
|
+
state.sessionServicesMeta.set(variableName, servicesMeta.services)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
40
130
|
}
|
|
41
131
|
|
|
42
132
|
// Handle qualified type names if necessary
|
|
@@ -58,6 +148,28 @@ export const addFileWithFactory = (
|
|
|
58
148
|
typePath: typeDeclarationPath,
|
|
59
149
|
})
|
|
60
150
|
methods.set(fileName, variables)
|
|
151
|
+
|
|
152
|
+
// Extract singleton services for CreateSessionServices factories
|
|
153
|
+
if (
|
|
154
|
+
expectedTypeName === 'CreateSessionServices' &&
|
|
155
|
+
state &&
|
|
156
|
+
node.initializer
|
|
157
|
+
) {
|
|
158
|
+
let functionNode:
|
|
159
|
+
| ts.ArrowFunction
|
|
160
|
+
| ts.FunctionExpression
|
|
161
|
+
| undefined
|
|
162
|
+
if (ts.isArrowFunction(node.initializer)) {
|
|
163
|
+
functionNode = node.initializer
|
|
164
|
+
} else if (ts.isFunctionExpression(node.initializer)) {
|
|
165
|
+
functionNode = node.initializer
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (functionNode) {
|
|
169
|
+
const servicesMeta = extractServicesFromFunction(functionNode)
|
|
170
|
+
state.sessionServicesMeta.set(variableName, servicesMeta.services)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
61
173
|
}
|
|
62
174
|
}
|
|
63
175
|
}
|
package/src/add/add-functions.ts
CHANGED
|
@@ -27,7 +27,8 @@ const nullifyTypes = (type: string | null) => {
|
|
|
27
27
|
const resolveTypeImports = (
|
|
28
28
|
type: ts.Type,
|
|
29
29
|
resolvedTypes: TypesMap,
|
|
30
|
-
isCustom: boolean
|
|
30
|
+
isCustom: boolean,
|
|
31
|
+
checker: ts.TypeChecker
|
|
31
32
|
): string[] => {
|
|
32
33
|
const types: string[] = []
|
|
33
34
|
|
|
@@ -42,10 +43,14 @@ const resolveTypeImports = (
|
|
|
42
43
|
const path = sourceFile.fileName
|
|
43
44
|
|
|
44
45
|
// Skip built-in utility types or TypeScript lib types
|
|
46
|
+
// Skip enum members (but not the enum type itself)
|
|
47
|
+
const isEnumMember = declaration && ts.isEnumMember(declaration)
|
|
48
|
+
|
|
45
49
|
if (
|
|
46
50
|
!path.includes('node_modules/typescript') &&
|
|
47
51
|
symbol.getName() !== '__type' &&
|
|
48
|
-
!isPrimitiveType(currentType)
|
|
52
|
+
!isPrimitiveType(currentType) &&
|
|
53
|
+
!isEnumMember
|
|
49
54
|
) {
|
|
50
55
|
const originalName = symbol.getName()
|
|
51
56
|
// Check if the type is already in the map
|
|
@@ -102,6 +107,32 @@ const resolveTypeImports = (
|
|
|
102
107
|
const typeRef = currentType as ts.TypeReference
|
|
103
108
|
typeRef.typeArguments?.forEach(visitType)
|
|
104
109
|
}
|
|
110
|
+
|
|
111
|
+
// Handle anonymous object types with enum properties (e.g., { userType: UserType })
|
|
112
|
+
// Only traverse into enum property types to avoid over-importing other named types
|
|
113
|
+
if (currentType.flags & ts.TypeFlags.Object) {
|
|
114
|
+
const objectType = currentType as ts.ObjectType
|
|
115
|
+
const typeSymbol = objectType.getSymbol()
|
|
116
|
+
|
|
117
|
+
// Only traverse properties for anonymous object types (no symbol or __type symbol)
|
|
118
|
+
// Skip named types, interfaces, and enums to avoid over-importing
|
|
119
|
+
const isAnonymousObject = !typeSymbol || typeSymbol.getName() === '__type'
|
|
120
|
+
|
|
121
|
+
if (isAnonymousObject) {
|
|
122
|
+
const properties = objectType.getProperties()
|
|
123
|
+
for (const prop of properties) {
|
|
124
|
+
if (prop.valueDeclaration) {
|
|
125
|
+
const propType = checker.getTypeOfSymbolAtLocation(
|
|
126
|
+
prop,
|
|
127
|
+
prop.valueDeclaration
|
|
128
|
+
)
|
|
129
|
+
if (propType) {
|
|
130
|
+
visitType(propType)
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
105
136
|
}
|
|
106
137
|
|
|
107
138
|
visitType(type)
|
|
@@ -170,7 +201,7 @@ const getNamesAndTypes = (
|
|
|
170
201
|
|
|
171
202
|
// record the alias in your TypesMap
|
|
172
203
|
const references = rawTypes
|
|
173
|
-
.map((t) => resolveTypeImports(t, typesMap, true))
|
|
204
|
+
.map((t) => resolveTypeImports(t, typesMap, true, checker))
|
|
174
205
|
.flat()
|
|
175
206
|
|
|
176
207
|
typesMap.addCustomType(aliasName, aliasType, references)
|
|
@@ -192,7 +223,7 @@ const getNamesAndTypes = (
|
|
|
192
223
|
return name
|
|
193
224
|
}
|
|
194
225
|
// non-primitive: import/alias it inline
|
|
195
|
-
return resolveTypeImports(t, typesMap, false)
|
|
226
|
+
return resolveTypeImports(t, typesMap, false, checker)
|
|
196
227
|
})
|
|
197
228
|
.flat()
|
|
198
229
|
|
|
@@ -270,7 +301,7 @@ export const addFunctions: AddWiring = (logger, node, checker, state) => {
|
|
|
270
301
|
if (args.length === 0) return
|
|
271
302
|
|
|
272
303
|
const { pikkuFuncName, name, explicitName, exportedName } =
|
|
273
|
-
extractFunctionName(node, checker)
|
|
304
|
+
extractFunctionName(node, checker, state.rootDir)
|
|
274
305
|
|
|
275
306
|
let tags: string[] | undefined
|
|
276
307
|
let expose: boolean | undefined
|
|
@@ -300,6 +331,17 @@ export const addFunctions: AddWiring = (logger, node, checker, state) => {
|
|
|
300
331
|
(!ts.isArrowFunction(fnProp) && !ts.isFunctionExpression(fnProp))
|
|
301
332
|
) {
|
|
302
333
|
logger.error(`• No valid 'func' property found for ${pikkuFuncName}.`)
|
|
334
|
+
// Create stub metadata to prevent "function not found" errors in wirings
|
|
335
|
+
state.functions.meta[pikkuFuncName] = {
|
|
336
|
+
pikkuFuncName,
|
|
337
|
+
name,
|
|
338
|
+
services: { optimized: false, services: [] },
|
|
339
|
+
inputSchemaName: null,
|
|
340
|
+
outputSchemaName: null,
|
|
341
|
+
inputs: [],
|
|
342
|
+
outputs: [],
|
|
343
|
+
middleware: undefined,
|
|
344
|
+
}
|
|
303
345
|
return
|
|
304
346
|
}
|
|
305
347
|
handlerNode = fnProp
|
|
@@ -310,6 +352,17 @@ export const addFunctions: AddWiring = (logger, node, checker, state) => {
|
|
|
310
352
|
!ts.isFunctionExpression(handlerNode)
|
|
311
353
|
) {
|
|
312
354
|
logger.error(`• Handler for ${name} is not a function.`)
|
|
355
|
+
// Create stub metadata to prevent "function not found" errors in wirings
|
|
356
|
+
state.functions.meta[pikkuFuncName] = {
|
|
357
|
+
pikkuFuncName,
|
|
358
|
+
name,
|
|
359
|
+
services: { optimized: false, services: [] },
|
|
360
|
+
inputSchemaName: null,
|
|
361
|
+
outputSchemaName: null,
|
|
362
|
+
inputs: [],
|
|
363
|
+
outputs: [],
|
|
364
|
+
middleware: undefined,
|
|
365
|
+
}
|
|
313
366
|
return
|
|
314
367
|
}
|
|
315
368
|
|
|
@@ -434,6 +487,8 @@ export const addFunctions: AddWiring = (logger, node, checker, state) => {
|
|
|
434
487
|
path: node.getSourceFile().fileName,
|
|
435
488
|
exportedName,
|
|
436
489
|
})
|
|
490
|
+
// Track exposed RPC function for service aggregation
|
|
491
|
+
state.serviceAggregation.usedFunctions.add(pikkuFuncName)
|
|
437
492
|
}
|
|
438
493
|
|
|
439
494
|
// We add it to internal meta to allow autocomplete for everything
|