@pikku/inspector 0.6.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.
Files changed (48) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +3 -0
  3. package/dist/add-channel.d.ts +3 -0
  4. package/dist/add-channel.js +122 -0
  5. package/dist/add-file-extends-core-type.d.ts +3 -0
  6. package/dist/add-file-extends-core-type.js +38 -0
  7. package/dist/add-file-with-config.d.ts +3 -0
  8. package/dist/add-file-with-config.js +31 -0
  9. package/dist/add-file-with-factory.d.ts +3 -0
  10. package/dist/add-file-with-factory.js +48 -0
  11. package/dist/add-route.d.ts +4 -0
  12. package/dist/add-route.js +89 -0
  13. package/dist/add-schedule.d.ts +3 -0
  14. package/dist/add-schedule.js +32 -0
  15. package/dist/does-type-extend-core-type.d.ts +2 -0
  16. package/dist/does-type-extend-core-type.js +41 -0
  17. package/dist/get-property-value.d.ts +3 -0
  18. package/dist/get-property-value.js +60 -0
  19. package/dist/index.d.ts +4 -0
  20. package/dist/index.js +1 -0
  21. package/dist/inspector.d.ts +3 -0
  22. package/dist/inspector.js +43 -0
  23. package/dist/types-map.d.ts +18 -0
  24. package/dist/types-map.js +103 -0
  25. package/dist/types.d.ts +49 -0
  26. package/dist/types.js +1 -0
  27. package/dist/utils.d.ts +30 -0
  28. package/dist/utils.js +245 -0
  29. package/dist/visit.d.ts +3 -0
  30. package/dist/visit.js +17 -0
  31. package/package.json +30 -0
  32. package/run-tests.sh +53 -0
  33. package/src/add-channel.ts +168 -0
  34. package/src/add-file-extends-core-type.ts +50 -0
  35. package/src/add-file-with-config.ts +45 -0
  36. package/src/add-file-with-factory.ts +65 -0
  37. package/src/add-route.ts +131 -0
  38. package/src/add-schedule.ts +47 -0
  39. package/src/does-type-extend-core-type.ts +53 -0
  40. package/src/get-property-value.ts +81 -0
  41. package/src/index.ts +4 -0
  42. package/src/inspector.ts +53 -0
  43. package/src/types-map.ts +130 -0
  44. package/src/types.ts +58 -0
  45. package/src/utils.ts +349 -0
  46. package/src/visit.ts +49 -0
  47. package/tsconfig.json +19 -0
  48. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,168 @@
1
+ import * as ts from 'typescript'
2
+ import { getPropertyValue } from './get-property-value.js'
3
+ import { pathToRegexp } from 'path-to-regexp'
4
+ import { APIDocs } from '@pikku/core'
5
+ import { getInputTypes } from './add-route.js'
6
+ import { getPropertyAssignment, getFunctionTypes } from './utils.js'
7
+ import { ChannelMeta } from '@pikku/core/channel'
8
+ import { TypesMap } from './types-map.js'
9
+ import { InspectorState } from './types.js'
10
+
11
+ const addMessagesRoutes = (
12
+ obj: ts.ObjectLiteralExpression,
13
+ checker: ts.TypeChecker,
14
+ typesMap: TypesMap
15
+ ) => {
16
+ const messageTypes: ChannelMeta['messageRoutes'] = {}
17
+
18
+ // Find the onMessageRoute property
19
+ const messagesProperty = obj.properties.find(
20
+ (p) =>
21
+ ts.isPropertyAssignment(p) &&
22
+ ts.isIdentifier(p.name) &&
23
+ p.name.text === 'onMessageRoute'
24
+ )
25
+
26
+ if (!messagesProperty || !ts.isPropertyAssignment(messagesProperty)) {
27
+ console.log(
28
+ 'onMessageRoute property not found or is not a valid assignment.'
29
+ )
30
+ return {}
31
+ }
32
+
33
+ const initializer = messagesProperty.initializer
34
+ // Ensure initializer is an object literal expression
35
+ if (!ts.isObjectLiteralExpression(initializer)) {
36
+ console.log('onMessageRoute is not an object literal.')
37
+ return {}
38
+ }
39
+
40
+ // Iterate over the first level properties (like 'event')
41
+ initializer.properties.forEach((property) => {
42
+ const channel = property.name!.getText()
43
+ messageTypes[channel] = {}
44
+
45
+ if (ts.isPropertyAssignment(property)) {
46
+ const nestedObject = property.initializer
47
+ if (ts.isObjectLiteralExpression(nestedObject)) {
48
+ const keys = nestedObject.properties.map((p) => p.name?.getText())
49
+ for (const route of keys) {
50
+ if (route) {
51
+ const result = getFunctionTypes(checker, nestedObject, {
52
+ funcName: route,
53
+ inputIndex: 0,
54
+ outputIndex: 1,
55
+ typesMap,
56
+ })
57
+ const inputs = result?.inputs || null
58
+ const outputs = result?.outputs || null
59
+ messageTypes[channel][route] = { inputs, outputs }
60
+ }
61
+ }
62
+ } else {
63
+ console.warn('Nested property is not an object literal:', nestedObject)
64
+ }
65
+ } else {
66
+ console.warn(
67
+ `Property "${property.getText()}" is a ${ts.SyntaxKind[property.kind]}`
68
+ )
69
+ }
70
+ })
71
+
72
+ return messageTypes
73
+ }
74
+
75
+ export const addChannel = (
76
+ node: ts.Node,
77
+ checker: ts.TypeChecker,
78
+ state: InspectorState
79
+ ) => {
80
+ if (!ts.isCallExpression(node)) {
81
+ return
82
+ }
83
+
84
+ const args = node.arguments
85
+ const firstArg = args[0]
86
+ const expression = node.expression
87
+
88
+ // Check if the call is to addRoute
89
+ if (!ts.isIdentifier(expression) || expression.text !== 'addChannel') {
90
+ return
91
+ }
92
+
93
+ if (!firstArg) {
94
+ return
95
+ }
96
+
97
+ let docs: APIDocs | undefined
98
+ let paramsValues: string[] | null = []
99
+ let queryValues: string[] | [] = []
100
+ let inputType: string | null = null
101
+ let route: string | null = null
102
+ let name: string | null = null
103
+
104
+ state.channels.files.add(node.getSourceFile().fileName)
105
+
106
+ // Check if the first argument is an object literal
107
+ if (ts.isObjectLiteralExpression(firstArg)) {
108
+ const obj = firstArg
109
+
110
+ name = getPropertyValue(obj, 'name') as string | null
111
+ route = getPropertyValue(obj, 'route') as string | null
112
+
113
+ if (!name) {
114
+ console.error('Channel name is required')
115
+ return
116
+ }
117
+
118
+ if (route) {
119
+ const { keys } = pathToRegexp(route)
120
+ paramsValues = keys.reduce((result, { type, name }) => {
121
+ if (type === 'param') {
122
+ result.push(name)
123
+ }
124
+ return result
125
+ }, [] as string[])
126
+ } else {
127
+ route = ''
128
+ }
129
+
130
+ docs = (getPropertyValue(obj, 'docs') as APIDocs) || undefined
131
+ queryValues = (getPropertyValue(obj, 'query') as string[]) || []
132
+
133
+ const connect = !!getPropertyAssignment(obj, 'onConnect')
134
+ const disconnect = !!getPropertyAssignment(obj, 'onDisconnect')
135
+ const { inputs, outputs } = getFunctionTypes(checker, obj, {
136
+ funcName: 'onMessage',
137
+ inputIndex: 0,
138
+ outputIndex: 1,
139
+ typesMap: state.channels.typesMap,
140
+ })
141
+ const message = { inputs, outputs }
142
+ const messageRoutes = addMessagesRoutes(
143
+ obj,
144
+ checker,
145
+ state.channels.typesMap
146
+ )
147
+
148
+ state.channels.meta.push({
149
+ name,
150
+ route,
151
+ input: inputType,
152
+ params: paramsValues.length > 0 ? paramsValues : undefined,
153
+ query: queryValues.length > 0 ? queryValues : undefined,
154
+ inputTypes: getInputTypes(
155
+ state.channels.metaInputTypes,
156
+ 'get',
157
+ inputType,
158
+ queryValues,
159
+ paramsValues
160
+ ),
161
+ connect,
162
+ disconnect,
163
+ message,
164
+ messageRoutes,
165
+ docs,
166
+ })
167
+ }
168
+ }
@@ -0,0 +1,50 @@
1
+ import * as ts from 'typescript'
2
+ import { PathToNameAndType } from './types.js'
3
+
4
+ // const VRAMEWORK_TYPES = ['CoreConfig', 'CoreService', 'CoreServices', 'CoreSingletonService', 'CoreSessionService']
5
+
6
+ export const addFileExtendsCoreType = (
7
+ node: ts.Node,
8
+ checker: ts.TypeChecker,
9
+ methods: PathToNameAndType,
10
+ expectedTypeName: string
11
+ ) => {
12
+ if (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node)) {
13
+ const fileName = node.getSourceFile().fileName
14
+ const typeName = node.name?.getText()
15
+
16
+ // Check if the class or interface extends the expected type
17
+ if (node.heritageClauses) {
18
+ for (const clause of node.heritageClauses) {
19
+ if (clause.token === ts.SyntaxKind.ExtendsKeyword) {
20
+ for (const type of clause.types) {
21
+ const extendedTypeName = type.expression.getText()
22
+ let extendedTypeDeclarationPath: string | null = null
23
+
24
+ // Check if the extended type matches the expected type name
25
+ if (extendedTypeName === expectedTypeName) {
26
+ // Retrieve the symbol of the extended type
27
+ const typeSymbol = checker.getSymbolAtLocation(type.expression)
28
+ const declaration =
29
+ typeSymbol &&
30
+ typeSymbol.declarations &&
31
+ typeSymbol.declarations[0]
32
+ if (declaration) {
33
+ const sourceFile = declaration.getSourceFile()
34
+ extendedTypeDeclarationPath = sourceFile.fileName // Get the path of the file where the extended type was declared
35
+ }
36
+
37
+ const variables = methods[fileName] || []
38
+ variables.push({
39
+ variable: undefined,
40
+ type: typeName,
41
+ typePath: extendedTypeDeclarationPath,
42
+ })
43
+ methods[fileName] = variables
44
+ }
45
+ }
46
+ }
47
+ }
48
+ }
49
+ }
50
+ }
@@ -0,0 +1,45 @@
1
+ import * as ts from 'typescript'
2
+ import { doesTypeExtendsCore } from './does-type-extend-core-type.js'
3
+ import { PathToNameAndType } from './types.js'
4
+
5
+ export const addFileWithConfig = (
6
+ node: ts.Node,
7
+ checker: ts.TypeChecker,
8
+ configs: PathToNameAndType
9
+ ) => {
10
+ if (ts.isVariableDeclaration(node)) {
11
+ const fileName = node.getSourceFile().fileName
12
+ const variableSymbol = checker.getSymbolAtLocation(node.name)
13
+
14
+ if (variableSymbol) {
15
+ const variableType = checker.getTypeOfSymbolAtLocation(
16
+ variableSymbol,
17
+ node.name
18
+ )
19
+ const variableName = node.name.getText()
20
+ const variableTypeText = node.type?.getText()
21
+
22
+ // Check if the type extends CoreConfig
23
+ if (doesTypeExtendsCore(variableType, checker, new Set(), 'CoreConfig')) {
24
+ // Retrieve the symbol of the type (if it has one)
25
+ const typeSymbol = variableType.symbol
26
+ let typeDeclarationPath: string | null = null
27
+ const declaration =
28
+ typeSymbol && typeSymbol.declarations && typeSymbol.declarations[0]
29
+
30
+ if (declaration) {
31
+ const sourceFile = declaration.getSourceFile()
32
+ typeDeclarationPath = sourceFile.fileName // Get the path of the file where the type was declared
33
+ }
34
+
35
+ const variables = configs[fileName] || []
36
+ variables.push({
37
+ variable: variableName,
38
+ type: variableTypeText || null,
39
+ typePath: typeDeclarationPath,
40
+ })
41
+ configs[fileName] = variables
42
+ }
43
+ }
44
+ }
45
+ }
@@ -0,0 +1,65 @@
1
+ import * as ts from 'typescript'
2
+ import { PathToNameAndType } from './types.js'
3
+
4
+ export const addFileWithFactory = (
5
+ node: ts.Node,
6
+ checker: ts.TypeChecker,
7
+ methods: PathToNameAndType = new Map(),
8
+ expectedTypeName: string
9
+ ) => {
10
+ if (ts.isVariableDeclaration(node)) {
11
+ const fileName = node.getSourceFile().fileName
12
+ const variableTypeNode = node.type
13
+ const variableName = node.name.getText()
14
+
15
+ if (variableTypeNode && ts.isTypeReferenceNode(variableTypeNode)) {
16
+ const typeNameNode = variableTypeNode.typeName || null
17
+
18
+ let typeDeclarationPath: string | null = null
19
+
20
+ // Check if the type name matches the expected type name
21
+ if (
22
+ ts.isIdentifier(typeNameNode) &&
23
+ typeNameNode.text === expectedTypeName
24
+ ) {
25
+ const typeSymbol = checker.getSymbolAtLocation(typeNameNode)
26
+ const declaration =
27
+ typeSymbol && typeSymbol.declarations && typeSymbol.declarations[0]
28
+ if (declaration) {
29
+ const sourceFile = declaration.getSourceFile()
30
+ typeDeclarationPath = sourceFile.fileName // Get the path of the file where the type was declared
31
+ }
32
+
33
+ const variables = methods[fileName] || []
34
+ variables.push({
35
+ variable: variableName,
36
+ type: typeNameNode.getText(),
37
+ typePath: typeDeclarationPath,
38
+ })
39
+ methods[fileName] = variables
40
+ }
41
+
42
+ // Handle qualified type names if necessary
43
+ else if (ts.isQualifiedName(typeNameNode)) {
44
+ const lastName = typeNameNode.right.text
45
+ if (lastName === expectedTypeName) {
46
+ const typeSymbol = checker.getSymbolAtLocation(typeNameNode.right)
47
+ const declaration =
48
+ typeSymbol && typeSymbol.declarations && typeSymbol.declarations[0]
49
+ if (declaration) {
50
+ const sourceFile = declaration.getSourceFile()
51
+ typeDeclarationPath = sourceFile.fileName // Get the path of the file where the type was declared
52
+ }
53
+
54
+ const variables = methods[fileName] || []
55
+ variables.push({
56
+ variable: variableName,
57
+ type: typeNameNode.getText(),
58
+ typePath: typeDeclarationPath,
59
+ })
60
+ methods[fileName] = variables
61
+ }
62
+ }
63
+ }
64
+ }
65
+ }
@@ -0,0 +1,131 @@
1
+ import * as ts from 'typescript'
2
+ import { getPropertyValue } from './get-property-value.js'
3
+ import { pathToRegexp } from 'path-to-regexp'
4
+ import { HTTPMethod } from '@pikku/core/http'
5
+ import { APIDocs } from '@pikku/core'
6
+ import { extractTypeKeys, getFunctionTypes } from './utils.js'
7
+ import { MetaInputTypes, InspectorState } from './types.js'
8
+
9
+ export const getInputTypes = (
10
+ metaTypes: MetaInputTypes,
11
+ methodType: string,
12
+ inputType: string | null,
13
+ queryValues: string[],
14
+ paramsValues: string[]
15
+ ) => {
16
+ if (!inputType) {
17
+ return undefined
18
+ }
19
+
20
+ if (inputType) {
21
+ metaTypes.set(inputType, {
22
+ query: queryValues,
23
+ params: paramsValues,
24
+ body: ['post', 'put', 'patch'].includes(methodType)
25
+ ? [...new Set([...queryValues, ...paramsValues])]
26
+ : [],
27
+ })
28
+ }
29
+
30
+ return undefined
31
+ }
32
+
33
+ export const addRoute = (
34
+ node: ts.Node,
35
+ checker: ts.TypeChecker,
36
+ state: InspectorState
37
+ ) => {
38
+ if (!ts.isCallExpression(node)) {
39
+ return
40
+ }
41
+
42
+ const args = node.arguments
43
+ const firstArg = args[0]
44
+ const expression = node.expression
45
+
46
+ // Check if the call is to addRoute
47
+ if (!ts.isIdentifier(expression) || expression.text !== 'addRoute') {
48
+ return
49
+ }
50
+
51
+ if (!firstArg) {
52
+ return
53
+ }
54
+
55
+ let docs: APIDocs | undefined
56
+ let methodValue: string | null = null
57
+ let paramsValues: string[] | null = []
58
+ let queryValues: string[] | [] = []
59
+ let routeValue: string | null = null
60
+
61
+ state.http.files.add(node.getSourceFile().fileName)
62
+
63
+ // Check if the first argument is an object literal
64
+ if (ts.isObjectLiteralExpression(firstArg)) {
65
+ const obj = firstArg
66
+
67
+ routeValue = getPropertyValue(obj, 'route') as string | null
68
+ if (routeValue) {
69
+ const { keys } = pathToRegexp(routeValue)
70
+ paramsValues = keys.reduce((result, { type, name }) => {
71
+ if (type === 'param') {
72
+ result.push(name)
73
+ }
74
+ return result
75
+ }, [] as string[])
76
+ }
77
+
78
+ docs = (getPropertyValue(obj, 'docs') as APIDocs) || undefined
79
+ methodValue = getPropertyValue(obj, 'method') as string
80
+ queryValues = (getPropertyValue(obj, 'query') as string[]) || []
81
+
82
+ let { inputs, outputs, inputTypes } = getFunctionTypes(checker, obj, {
83
+ funcName: 'func',
84
+ inputIndex: 0,
85
+ outputIndex: 1,
86
+ typesMap: state.http.typesMap,
87
+ })
88
+
89
+ const input = inputs ? inputs[0] || null : null
90
+ const output = outputs ? outputs[0] || null : null
91
+
92
+ if (inputs && inputs?.length > 1) {
93
+ console.error(
94
+ `Only one input type is currently allowed for method '${methodValue}' and route '${routeValue}': \n\t${inputs.join('\n\t')}`
95
+ )
96
+ }
97
+
98
+ if (outputs && outputs?.length > 1) {
99
+ console.error(
100
+ `Only one output type is currently allowed for method '${methodValue}' and route '${routeValue}': \n\t${outputs.join('\n\t')}`
101
+ )
102
+ }
103
+
104
+ if (inputTypes[0] && !['post', 'put', 'patch'].includes(methodValue)) {
105
+ queryValues = [
106
+ ...new Set([...queryValues, ...extractTypeKeys(inputTypes[0])]),
107
+ ].filter((query) => !paramsValues?.includes(query))
108
+ }
109
+
110
+ if (!routeValue) {
111
+ return
112
+ }
113
+
114
+ state.http.meta.push({
115
+ route: routeValue!,
116
+ method: methodValue! as HTTPMethod,
117
+ input,
118
+ output,
119
+ params: paramsValues.length > 0 ? paramsValues : undefined,
120
+ query: queryValues.length > 0 ? queryValues : undefined,
121
+ inputTypes: getInputTypes(
122
+ state.http.metaInputTypes,
123
+ methodValue,
124
+ input,
125
+ queryValues,
126
+ paramsValues
127
+ ),
128
+ docs,
129
+ })
130
+ }
131
+ }
@@ -0,0 +1,47 @@
1
+ import * as ts from 'typescript'
2
+ import { getPropertyValue } from './get-property-value.js'
3
+ import { APIDocs } from '@pikku/core'
4
+ import { InspectorState } from './types.js'
5
+
6
+ export const addSchedule = (
7
+ node: ts.Node,
8
+ _checker: ts.TypeChecker,
9
+ state: InspectorState
10
+ ) => {
11
+ if (!ts.isCallExpression(node)) {
12
+ return
13
+ }
14
+
15
+ const args = node.arguments
16
+ const firstArg = args[0]
17
+ const expression = node.expression
18
+
19
+ // Check if the call is to addScheduledTask
20
+ if (!ts.isIdentifier(expression) || expression.text !== 'addScheduledTask') {
21
+ return
22
+ }
23
+
24
+ if (!firstArg) {
25
+ return
26
+ }
27
+
28
+ state.scheduledTasks.files.add(node.getSourceFile().fileName)
29
+
30
+ if (ts.isObjectLiteralExpression(firstArg)) {
31
+ const obj = firstArg
32
+
33
+ const nameValue = getPropertyValue(obj, 'name') as string | null
34
+ const scheduleValue = getPropertyValue(obj, 'schedule') as string | null
35
+ const docs = (getPropertyValue(obj, 'docs') as APIDocs) || undefined
36
+
37
+ if (!nameValue || !scheduleValue) {
38
+ return
39
+ }
40
+
41
+ state.scheduledTasks.meta.push({
42
+ name: nameValue,
43
+ schedule: scheduleValue,
44
+ docs,
45
+ })
46
+ }
47
+ }
@@ -0,0 +1,53 @@
1
+ import * as ts from 'typescript'
2
+
3
+ export const doesTypeExtendsCore = (
4
+ type: ts.Type,
5
+ checker: ts.TypeChecker,
6
+ visitedTypes: Set<ts.Type>,
7
+ coreType: string
8
+ ): boolean => {
9
+ if (!type || !checker) return false
10
+
11
+ // Avoid infinite recursion by checking if we've already visited this type
12
+ if (visitedTypes.has(type)) {
13
+ return false
14
+ }
15
+ visitedTypes.add(type)
16
+
17
+ const typeSymbol = type.getSymbol()
18
+ if (typeSymbol) {
19
+ // Check if the type is the core type
20
+ if (typeSymbol.getName() === coreType) {
21
+ return true
22
+ }
23
+
24
+ // For interface and class types, check their base types
25
+ if (type.isClassOrInterface()) {
26
+ const baseTypes = type.getBaseTypes() || []
27
+ for (const baseType of baseTypes) {
28
+ if (doesTypeExtendsCore(baseType, checker, visitedTypes, coreType)) {
29
+ return true
30
+ }
31
+ }
32
+ }
33
+ }
34
+
35
+ // For type aliases, get the aliased type
36
+ if (type.aliasSymbol) {
37
+ const aliasedType = checker.getDeclaredTypeOfSymbol(type.aliasSymbol)
38
+ if (doesTypeExtendsCore(aliasedType, checker, visitedTypes, coreType)) {
39
+ return true
40
+ }
41
+ }
42
+
43
+ // For union and intersection types, check all constituent types
44
+ if (type.isUnionOrIntersection()) {
45
+ for (const subType of type.types) {
46
+ if (doesTypeExtendsCore(subType, checker, visitedTypes, coreType)) {
47
+ return true
48
+ }
49
+ }
50
+ }
51
+
52
+ return false
53
+ }
@@ -0,0 +1,81 @@
1
+ import { APIDocs } from '@pikku/core'
2
+ import * as ts from 'typescript'
3
+
4
+ export const getPropertyValue = (
5
+ obj: ts.ObjectLiteralExpression,
6
+ propertyName: string
7
+ ): string | string[] | null | APIDocs => {
8
+ const property = obj.properties.find(
9
+ (p) =>
10
+ ts.isPropertyAssignment(p) &&
11
+ ts.isIdentifier(p.name) &&
12
+ p.name.text === propertyName
13
+ )
14
+
15
+ if (property && ts.isPropertyAssignment(property)) {
16
+ const initializer = property.initializer
17
+
18
+ // Special handling for 'query' -> expect an array of strings
19
+ if (propertyName === 'query' && ts.isArrayLiteralExpression(initializer)) {
20
+ const stringArray = initializer.elements
21
+ .map((element) => {
22
+ if (ts.isStringLiteral(element)) {
23
+ return element.text
24
+ }
25
+ return null
26
+ })
27
+ .filter((item) => item !== null) as string[] // Filter non-null and assert type
28
+
29
+ return stringArray.length > 0 ? stringArray : null
30
+ }
31
+
32
+ // Special handling for 'docs' -> expect RouteDocs
33
+ if (propertyName === 'docs' && ts.isObjectLiteralExpression(initializer)) {
34
+ const docs: APIDocs = {}
35
+
36
+ initializer.properties.forEach((prop) => {
37
+ if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
38
+ const propName = prop.name.text
39
+
40
+ if (propName === 'summary' && ts.isStringLiteral(prop.initializer)) {
41
+ docs.summary = prop.initializer.text
42
+ } else if (
43
+ propName === 'description' &&
44
+ ts.isStringLiteral(prop.initializer)
45
+ ) {
46
+ docs.description = prop.initializer.text
47
+ } else if (
48
+ propName === 'tags' &&
49
+ ts.isArrayLiteralExpression(prop.initializer)
50
+ ) {
51
+ docs.tags = prop.initializer.elements
52
+ .filter(ts.isStringLiteral)
53
+ .map((element) => element.text)
54
+ } else if (
55
+ propName === 'errors' &&
56
+ ts.isArrayLiteralExpression(prop.initializer)
57
+ ) {
58
+ docs.errors = prop.initializer.elements
59
+ .filter(ts.isIdentifier)
60
+ .map((element) => element.text as unknown as string)
61
+ }
62
+ }
63
+ })
64
+
65
+ return docs
66
+ }
67
+
68
+ // Handle string literals for other properties
69
+ if (
70
+ ts.isStringLiteral(initializer) ||
71
+ ts.isNoSubstitutionTemplateLiteral(initializer)
72
+ ) {
73
+ return initializer.text
74
+ } else {
75
+ // Handle other initializer types if necessary
76
+ return initializer.getText()
77
+ }
78
+ }
79
+
80
+ return null
81
+ }
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export { inspect } from './inspector.js'
2
+ export type { TypesMap } from './types-map.js'
3
+ export type * from './types.js'
4
+ export type { InspectorState } from './types.js'
@@ -0,0 +1,53 @@
1
+ import * as ts from 'typescript'
2
+ import { visit } from './visit.js'
3
+ import { TypesMap } from './types-map.js'
4
+ import { InspectorState, InspectorHTTPState } from './types.js'
5
+
6
+ export const normalizeHTTPTypes = (
7
+ httpState: InspectorHTTPState
8
+ ): InspectorHTTPState => {
9
+ return httpState
10
+ }
11
+
12
+ export const inspect = (routeFiles: string[]): InspectorState => {
13
+ const program = ts.createProgram(routeFiles, {
14
+ target: ts.ScriptTarget.ESNext,
15
+ module: ts.ModuleKind.CommonJS,
16
+ })
17
+ const checker = program.getTypeChecker()
18
+ const sourceFiles = program.getSourceFiles()
19
+
20
+ const state: InspectorState = {
21
+ sessionServicesTypeImportMap: new Map(),
22
+ userSessionTypeImportMap: new Map(),
23
+ singletonServicesFactories: new Map(),
24
+ sessionServicesFactories: new Map(),
25
+ configFactories: new Map(),
26
+ http: {
27
+ typesMap: new TypesMap(),
28
+ metaInputTypes: new Map(),
29
+ meta: [],
30
+ files: new Set(),
31
+ },
32
+ channels: {
33
+ typesMap: new TypesMap(),
34
+ metaInputTypes: new Map(),
35
+ files: new Set(),
36
+ meta: [],
37
+ },
38
+ scheduledTasks: {
39
+ meta: [],
40
+ files: new Set(),
41
+ },
42
+ }
43
+
44
+ for (const sourceFile of sourceFiles) {
45
+ ts.forEachChild(sourceFile, (child) => visit(checker, child, state))
46
+ }
47
+
48
+ // Normalise the typesMap
49
+
50
+ state.http = normalizeHTTPTypes(state.http)
51
+
52
+ return state
53
+ }