@pikku/inspector 0.11.0 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -1
- package/dist/add/add-channel.js +11 -10
- package/dist/add/add-file-with-factory.js +10 -10
- package/dist/add/add-functions.js +57 -43
- package/dist/add/add-http-route.js +5 -4
- package/dist/add/add-mcp-prompt.js +6 -5
- package/dist/add/add-mcp-resource.js +6 -5
- package/dist/add/add-mcp-tool.js +6 -5
- package/dist/add/add-middleware.js +1 -1
- package/dist/add/add-permission.js +1 -1
- package/dist/add/add-queue-worker.js +6 -5
- package/dist/add/add-schedule.js +5 -4
- package/dist/add/add-workflow.d.ts +1 -1
- package/dist/add/add-workflow.js +92 -66
- package/dist/error-codes.d.ts +1 -0
- package/dist/error-codes.js +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/inspector.js +10 -6
- package/dist/types.d.ts +21 -8
- package/dist/utils/extract-function-node.d.ts +10 -0
- package/dist/utils/extract-function-node.js +38 -0
- package/dist/utils/extract-node-value.d.ts +8 -0
- package/dist/utils/extract-node-value.js +24 -0
- package/dist/utils/extract-service-metadata.d.ts +19 -0
- package/dist/utils/extract-service-metadata.js +244 -0
- package/dist/utils/get-files-and-methods.d.ts +3 -3
- package/dist/utils/get-files-and-methods.js +3 -3
- package/dist/utils/get-property-value.d.ts +13 -6
- package/dist/utils/get-property-value.js +51 -43
- package/dist/utils/post-process.d.ts +9 -0
- package/dist/utils/post-process.js +30 -3
- package/dist/utils/serialize-inspector-state.d.ts +18 -5
- package/dist/utils/serialize-inspector-state.js +12 -10
- package/dist/utils/write-service-metadata.d.ts +13 -0
- package/dist/utils/write-service-metadata.js +37 -0
- package/dist/visit.js +2 -2
- package/dist/workflow/extract-simple-workflow.d.ts +15 -0
- package/dist/workflow/extract-simple-workflow.js +803 -0
- package/dist/workflow/patterns.d.ts +39 -0
- package/dist/workflow/patterns.js +138 -0
- package/dist/workflow/validation.d.ts +28 -0
- package/dist/workflow/validation.js +124 -0
- package/package.json +4 -4
- package/src/add/add-channel.ts +37 -17
- package/src/add/add-file-with-factory.ts +10 -10
- package/src/add/add-functions.ts +72 -56
- package/src/add/add-http-route.ts +10 -5
- package/src/add/add-mcp-prompt.ts +11 -7
- package/src/add/add-mcp-resource.ts +11 -7
- package/src/add/add-mcp-tool.ts +11 -7
- package/src/add/add-middleware.ts +1 -1
- package/src/add/add-permission.ts +1 -1
- package/src/add/add-queue-worker.ts +11 -12
- package/src/add/add-schedule.ts +10 -5
- package/src/add/add-workflow.ts +120 -110
- package/src/error-codes.ts +1 -0
- package/src/index.ts +2 -0
- package/src/inspector.ts +16 -6
- package/src/types.ts +18 -8
- package/src/utils/extract-function-node.ts +58 -0
- package/src/utils/extract-node-value.ts +31 -0
- package/src/utils/extract-service-metadata.ts +353 -0
- package/src/utils/filter-inspector-state.test.ts +3 -3
- package/src/utils/filter-utils.test.ts +45 -51
- package/src/utils/get-files-and-methods.ts +11 -11
- package/src/utils/get-property-value.ts +60 -53
- package/src/utils/permissions.test.ts +3 -3
- package/src/utils/post-process.ts +56 -3
- package/src/utils/serialize-inspector-state.ts +28 -18
- package/src/utils/test-data/inspector-state.json +9 -9
- package/src/utils/write-service-metadata.ts +51 -0
- package/src/visit.ts +3 -3
- package/src/workflow/extract-simple-workflow.ts +1035 -0
- package/src/workflow/patterns.ts +182 -0
- package/src/workflow/validation.ts +153 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import * as ts from 'typescript'
|
|
2
|
+
import * as path from 'path'
|
|
3
|
+
import * as fs from 'fs'
|
|
4
|
+
|
|
5
|
+
export interface ServiceMetadata {
|
|
6
|
+
name: string
|
|
7
|
+
summary: string
|
|
8
|
+
description: string
|
|
9
|
+
package: string
|
|
10
|
+
path: string
|
|
11
|
+
version: string
|
|
12
|
+
interface: string
|
|
13
|
+
expandedProperties: Record<string, string>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Extract JSDoc comment information from a TypeScript node
|
|
18
|
+
*/
|
|
19
|
+
function extractJSDoc(node: ts.Node): { summary: string; description: string } {
|
|
20
|
+
const jsDocTags = ts.getJSDocTags(node)
|
|
21
|
+
const jsDocComments = ts.getJSDocCommentsAndTags(node)
|
|
22
|
+
|
|
23
|
+
let summary = ''
|
|
24
|
+
let description = ''
|
|
25
|
+
|
|
26
|
+
const summaryTag = jsDocTags.find((tag) => tag.tagName.text === 'summary')
|
|
27
|
+
if (summaryTag && summaryTag.comment) {
|
|
28
|
+
summary =
|
|
29
|
+
typeof summaryTag.comment === 'string'
|
|
30
|
+
? summaryTag.comment
|
|
31
|
+
: summaryTag.comment.map((c) => c.text).join('')
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
for (const comment of jsDocComments) {
|
|
35
|
+
if (ts.isJSDoc(comment) && comment.comment) {
|
|
36
|
+
const commentText =
|
|
37
|
+
typeof comment.comment === 'string'
|
|
38
|
+
? comment.comment
|
|
39
|
+
: comment.comment.map((c) => c.text).join('')
|
|
40
|
+
|
|
41
|
+
if (!summary && commentText) {
|
|
42
|
+
const lines = commentText
|
|
43
|
+
.split('\n')
|
|
44
|
+
.map((l) => l.trim())
|
|
45
|
+
.filter(Boolean)
|
|
46
|
+
if (lines.length > 0) {
|
|
47
|
+
summary = lines[0]
|
|
48
|
+
if (lines.length > 1) {
|
|
49
|
+
description = lines.slice(1).join('\n').trim()
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
} else {
|
|
53
|
+
description = commentText
|
|
54
|
+
}
|
|
55
|
+
break
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return { summary, description }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Serialize a TypeScript type to a string representation
|
|
64
|
+
*/
|
|
65
|
+
function serializeTypeToString(
|
|
66
|
+
node: ts.Node,
|
|
67
|
+
sourceFile: ts.SourceFile,
|
|
68
|
+
checker: ts.TypeChecker
|
|
69
|
+
): string {
|
|
70
|
+
const nodeSourceFile = node.getSourceFile()
|
|
71
|
+
|
|
72
|
+
if (ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node)) {
|
|
73
|
+
return node.getText(nodeSourceFile)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (ts.isClassDeclaration(node)) {
|
|
77
|
+
return serializePublicClassMembers(node, nodeSourceFile, checker)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const type = checker.getTypeAtLocation(node)
|
|
81
|
+
return checker.typeToString(type, node, ts.TypeFormatFlags.NoTruncation)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Extract public members from a class and serialize them
|
|
86
|
+
*/
|
|
87
|
+
function serializePublicClassMembers(
|
|
88
|
+
classNode: ts.ClassDeclaration,
|
|
89
|
+
sourceFile: ts.SourceFile,
|
|
90
|
+
checker: ts.TypeChecker
|
|
91
|
+
): string {
|
|
92
|
+
const className = classNode.name?.text || 'UnnamedClass'
|
|
93
|
+
const publicMembers: string[] = []
|
|
94
|
+
|
|
95
|
+
for (const member of classNode.members) {
|
|
96
|
+
const modifiers = ts.canHaveModifiers(member)
|
|
97
|
+
? ts.getModifiers(member)
|
|
98
|
+
: undefined
|
|
99
|
+
const isPublic = !modifiers?.some(
|
|
100
|
+
(mod) =>
|
|
101
|
+
mod.kind === ts.SyntaxKind.PrivateKeyword ||
|
|
102
|
+
mod.kind === ts.SyntaxKind.ProtectedKeyword
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
if (!isPublic) continue
|
|
106
|
+
|
|
107
|
+
if (
|
|
108
|
+
ts.isMethodDeclaration(member) ||
|
|
109
|
+
ts.isPropertyDeclaration(member) ||
|
|
110
|
+
ts.isConstructorDeclaration(member)
|
|
111
|
+
) {
|
|
112
|
+
const memberSignature = getMemberSignature(member, sourceFile, checker)
|
|
113
|
+
if (memberSignature) {
|
|
114
|
+
publicMembers.push(memberSignature)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return `class ${className} {\n ${publicMembers.join('\n ')}\n}`
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Extract a clean signature for a class member (without implementation)
|
|
124
|
+
*/
|
|
125
|
+
function getMemberSignature(
|
|
126
|
+
member: ts.ClassElement,
|
|
127
|
+
sourceFile: ts.SourceFile,
|
|
128
|
+
checker: ts.TypeChecker
|
|
129
|
+
): string | null {
|
|
130
|
+
if (ts.isPropertyDeclaration(member)) {
|
|
131
|
+
const name = member.name.getText(sourceFile)
|
|
132
|
+
const type = member.type ? member.type.getText(sourceFile) : 'any'
|
|
133
|
+
const optional = member.questionToken ? '?' : ''
|
|
134
|
+
return `${name}${optional}: ${type};`
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (ts.isMethodDeclaration(member)) {
|
|
138
|
+
const name = member.name.getText(sourceFile)
|
|
139
|
+
const typeParams = member.typeParameters
|
|
140
|
+
? `<${member.typeParameters.map((tp) => tp.getText(sourceFile)).join(', ')}>`
|
|
141
|
+
: ''
|
|
142
|
+
const params = member.parameters
|
|
143
|
+
.map((p) => p.getText(sourceFile))
|
|
144
|
+
.join(', ')
|
|
145
|
+
const returnType = member.type ? member.type.getText(sourceFile) : 'void'
|
|
146
|
+
const optional = member.questionToken ? '?' : ''
|
|
147
|
+
return `${name}${optional}${typeParams}(${params}): ${returnType};`
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (ts.isConstructorDeclaration(member)) {
|
|
151
|
+
const params = member.parameters
|
|
152
|
+
.map((p) => p.getText(sourceFile))
|
|
153
|
+
.join(', ')
|
|
154
|
+
return `constructor(${params});`
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return null
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Find the nearest package.json and extract package name and version
|
|
162
|
+
*/
|
|
163
|
+
function getPackageInfo(filePath: string): {
|
|
164
|
+
packageName: string
|
|
165
|
+
version: string
|
|
166
|
+
} {
|
|
167
|
+
let currentDir = path.dirname(filePath)
|
|
168
|
+
const root = path.parse(currentDir).root
|
|
169
|
+
|
|
170
|
+
while (currentDir !== root) {
|
|
171
|
+
const packageJsonPath = path.join(currentDir, 'package.json')
|
|
172
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
173
|
+
try {
|
|
174
|
+
const packageJson = JSON.parse(
|
|
175
|
+
fs.readFileSync(packageJsonPath, 'utf-8')
|
|
176
|
+
)
|
|
177
|
+
return {
|
|
178
|
+
packageName: packageJson.name || 'unknown',
|
|
179
|
+
version: packageJson.version || '0.0.0',
|
|
180
|
+
}
|
|
181
|
+
} catch (err) {
|
|
182
|
+
// If we can't parse the package.json, continue searching
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
currentDir = path.dirname(currentDir)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return { packageName: 'unknown', version: '0.0.0' }
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Expand a type to show all its properties including inherited ones
|
|
193
|
+
* Returns a Record mapping property names to their type strings
|
|
194
|
+
*/
|
|
195
|
+
function expandInterfaceProperties(
|
|
196
|
+
type: ts.Type,
|
|
197
|
+
checker: ts.TypeChecker,
|
|
198
|
+
maxDepth: number = 2,
|
|
199
|
+
currentDepth: number = 0,
|
|
200
|
+
visited: Set<ts.Type> = new Set()
|
|
201
|
+
): Record<string, string> {
|
|
202
|
+
const result: Record<string, string> = {}
|
|
203
|
+
|
|
204
|
+
if (visited.has(type) || currentDepth >= maxDepth) {
|
|
205
|
+
return result
|
|
206
|
+
}
|
|
207
|
+
visited.add(type)
|
|
208
|
+
|
|
209
|
+
const properties = type.getProperties()
|
|
210
|
+
|
|
211
|
+
for (const prop of properties) {
|
|
212
|
+
const propName = prop.getName()
|
|
213
|
+
const propDecl = prop.valueDeclaration || prop.declarations?.[0]
|
|
214
|
+
|
|
215
|
+
if (!propDecl) continue
|
|
216
|
+
|
|
217
|
+
try {
|
|
218
|
+
const propType = checker.getTypeOfSymbolAtLocation(prop, propDecl)
|
|
219
|
+
const isOptional = !!(prop.flags & ts.SymbolFlags.Optional)
|
|
220
|
+
|
|
221
|
+
let typeString = checker.typeToString(
|
|
222
|
+
propType,
|
|
223
|
+
propDecl,
|
|
224
|
+
ts.TypeFormatFlags.NoTruncation |
|
|
225
|
+
ts.TypeFormatFlags.UseFullyQualifiedType
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
if (isOptional && !typeString.includes('undefined')) {
|
|
229
|
+
typeString = `${typeString} | undefined`
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
result[propName] = typeString
|
|
233
|
+
} catch (err) {
|
|
234
|
+
result[propName] = 'any'
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return result
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Extract metadata for a service from its TypeScript declaration
|
|
243
|
+
*/
|
|
244
|
+
export function extractServiceMetadata(
|
|
245
|
+
serviceName: string,
|
|
246
|
+
type: ts.Type,
|
|
247
|
+
checker: ts.TypeChecker,
|
|
248
|
+
rootDir: string
|
|
249
|
+
): ServiceMetadata | null {
|
|
250
|
+
const property = type.getProperty(serviceName)
|
|
251
|
+
if (!property) {
|
|
252
|
+
return null
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const declaration = property.valueDeclaration || property.declarations?.[0]
|
|
256
|
+
if (!declaration) {
|
|
257
|
+
return null
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const sourceFile = declaration.getSourceFile()
|
|
261
|
+
const filePath = sourceFile.fileName
|
|
262
|
+
|
|
263
|
+
const serviceType = checker.getTypeOfSymbolAtLocation(property, declaration)
|
|
264
|
+
let typeDeclaration: ts.Node | null = null
|
|
265
|
+
|
|
266
|
+
if (serviceType.symbol) {
|
|
267
|
+
const typeDecl =
|
|
268
|
+
serviceType.symbol.valueDeclaration ||
|
|
269
|
+
serviceType.symbol.declarations?.[0]
|
|
270
|
+
if (
|
|
271
|
+
typeDecl &&
|
|
272
|
+
(ts.isInterfaceDeclaration(typeDecl) ||
|
|
273
|
+
ts.isClassDeclaration(typeDecl) ||
|
|
274
|
+
ts.isTypeAliasDeclaration(typeDecl))
|
|
275
|
+
) {
|
|
276
|
+
typeDeclaration = typeDecl
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
let summary = ''
|
|
281
|
+
let description = ''
|
|
282
|
+
|
|
283
|
+
if (typeDeclaration) {
|
|
284
|
+
const jsDoc = extractJSDoc(typeDeclaration)
|
|
285
|
+
summary = jsDoc.summary
|
|
286
|
+
description = jsDoc.description
|
|
287
|
+
} else if (
|
|
288
|
+
ts.isPropertySignature(declaration) ||
|
|
289
|
+
ts.isPropertyDeclaration(declaration)
|
|
290
|
+
) {
|
|
291
|
+
const jsDoc = extractJSDoc(declaration)
|
|
292
|
+
summary = jsDoc.summary
|
|
293
|
+
description = jsDoc.description
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
let interfaceString = ''
|
|
297
|
+
if (typeDeclaration) {
|
|
298
|
+
interfaceString = serializeTypeToString(
|
|
299
|
+
typeDeclaration,
|
|
300
|
+
sourceFile,
|
|
301
|
+
checker
|
|
302
|
+
)
|
|
303
|
+
} else {
|
|
304
|
+
interfaceString = checker.typeToString(
|
|
305
|
+
serviceType,
|
|
306
|
+
declaration,
|
|
307
|
+
ts.TypeFormatFlags.NoTruncation
|
|
308
|
+
)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const { packageName, version } = getPackageInfo(filePath)
|
|
312
|
+
const relativePath = path.relative(rootDir, filePath)
|
|
313
|
+
const expandedProperties = expandInterfaceProperties(serviceType, checker)
|
|
314
|
+
|
|
315
|
+
return {
|
|
316
|
+
name: serviceName,
|
|
317
|
+
summary,
|
|
318
|
+
description,
|
|
319
|
+
package: packageName,
|
|
320
|
+
path: relativePath,
|
|
321
|
+
version,
|
|
322
|
+
interface: interfaceString,
|
|
323
|
+
expandedProperties,
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Extract metadata for all services in a type
|
|
329
|
+
*/
|
|
330
|
+
export function extractAllServiceMetadata(
|
|
331
|
+
servicesType: ts.Type,
|
|
332
|
+
checker: ts.TypeChecker,
|
|
333
|
+
rootDir: string
|
|
334
|
+
): ServiceMetadata[] {
|
|
335
|
+
const metadata: ServiceMetadata[] = []
|
|
336
|
+
const serviceNames = servicesType
|
|
337
|
+
.getProperties()
|
|
338
|
+
.map((prop) => prop.getName())
|
|
339
|
+
|
|
340
|
+
for (const serviceName of serviceNames) {
|
|
341
|
+
const serviceMeta = extractServiceMetadata(
|
|
342
|
+
serviceName,
|
|
343
|
+
servicesType,
|
|
344
|
+
checker,
|
|
345
|
+
rootDir
|
|
346
|
+
)
|
|
347
|
+
if (serviceMeta) {
|
|
348
|
+
metadata.push(serviceMeta)
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return metadata
|
|
353
|
+
}
|
|
@@ -32,12 +32,12 @@ function createMockInspectorState(): Omit<InspectorState, 'typesLookup'> {
|
|
|
32
32
|
return {
|
|
33
33
|
rootDir: '/test/project',
|
|
34
34
|
singletonServicesTypeImportMap: new Map(),
|
|
35
|
-
|
|
35
|
+
wireServicesTypeImportMap: new Map(),
|
|
36
36
|
userSessionTypeImportMap: new Map(),
|
|
37
37
|
configTypeImportMap: new Map(),
|
|
38
38
|
singletonServicesFactories: new Map(),
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
wireServicesFactories: new Map(),
|
|
40
|
+
wireServicesMeta: new Map(),
|
|
41
41
|
configFactories: new Map(),
|
|
42
42
|
filesAndMethods: {},
|
|
43
43
|
filesAndMethodsErrors: new Map(),
|