@pikku/inspector 0.11.2 → 0.12.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.
Files changed (211) hide show
  1. package/CHANGELOG.md +36 -1
  2. package/OPTIMIZATION-PLAN.md +195 -0
  3. package/dist/add/add-ai-agent.d.ts +2 -0
  4. package/dist/add/add-ai-agent.js +314 -0
  5. package/dist/add/add-channel.js +81 -61
  6. package/dist/add/add-cli.d.ts +1 -1
  7. package/dist/add/add-cli.js +42 -19
  8. package/dist/add/add-file-extends-core-type.d.ts +1 -1
  9. package/dist/add/add-file-with-config.d.ts +1 -1
  10. package/dist/add/add-file-with-factory.d.ts +1 -1
  11. package/dist/add/add-file-with-factory.js +2 -0
  12. package/dist/add/add-functions.d.ts +1 -1
  13. package/dist/add/add-functions.js +256 -82
  14. package/dist/add/add-http-route.d.ts +20 -10
  15. package/dist/add/add-http-route.js +156 -66
  16. package/dist/add/add-http-routes.d.ts +5 -0
  17. package/dist/add/add-http-routes.js +160 -0
  18. package/dist/add/add-keyed-wiring.d.ts +12 -0
  19. package/dist/add/add-keyed-wiring.js +97 -0
  20. package/dist/add/add-mcp-prompt.d.ts +1 -1
  21. package/dist/add/add-mcp-prompt.js +14 -9
  22. package/dist/add/add-mcp-resource.d.ts +1 -1
  23. package/dist/add/add-mcp-resource.js +14 -9
  24. package/dist/add/add-middleware.d.ts +1 -4
  25. package/dist/add/add-middleware.js +364 -79
  26. package/dist/add/add-permission.d.ts +1 -1
  27. package/dist/add/add-permission.js +152 -40
  28. package/dist/add/add-queue-worker.d.ts +1 -1
  29. package/dist/add/add-queue-worker.js +18 -12
  30. package/dist/add/add-rpc-invocations.d.ts +3 -3
  31. package/dist/add/add-rpc-invocations.js +24 -10
  32. package/dist/add/add-schedule.d.ts +1 -1
  33. package/dist/add/add-schedule.js +11 -5
  34. package/dist/add/add-secret.d.ts +3 -0
  35. package/dist/add/add-secret.js +82 -0
  36. package/dist/add/add-trigger.d.ts +2 -0
  37. package/dist/add/add-trigger.js +87 -0
  38. package/dist/add/add-variable.d.ts +1 -0
  39. package/dist/add/add-variable.js +8 -0
  40. package/dist/add/add-wire-addon.d.ts +7 -0
  41. package/dist/add/add-wire-addon.js +70 -0
  42. package/dist/add/add-workflow-graph.d.ts +3 -2
  43. package/dist/add/add-workflow-graph.js +143 -406
  44. package/dist/add/add-workflow.d.ts +1 -1
  45. package/dist/add/add-workflow.js +6 -4
  46. package/dist/error-codes.d.ts +15 -1
  47. package/dist/error-codes.js +20 -1
  48. package/dist/index.d.ts +9 -8
  49. package/dist/index.js +5 -4
  50. package/dist/inspector.d.ts +2 -2
  51. package/dist/inspector.js +95 -15
  52. package/dist/schema-generator.d.ts +1 -0
  53. package/dist/schema-generator.js +1 -0
  54. package/dist/types-map.js +10 -1
  55. package/dist/types.d.ts +180 -50
  56. package/dist/utils/compute-required-schemas.d.ts +4 -0
  57. package/dist/utils/compute-required-schemas.js +40 -0
  58. package/dist/utils/contract-hashes.d.ts +52 -0
  59. package/dist/utils/contract-hashes.js +269 -0
  60. package/dist/utils/custom-types-generator.d.ts +9 -0
  61. package/dist/utils/custom-types-generator.js +71 -0
  62. package/dist/utils/detect-schema-vendor.d.ts +22 -0
  63. package/dist/utils/detect-schema-vendor.js +76 -0
  64. package/dist/utils/does-type-extend-core-type.d.ts +1 -1
  65. package/dist/utils/ensure-function-metadata.d.ts +6 -3
  66. package/dist/utils/ensure-function-metadata.js +220 -6
  67. package/dist/utils/extract-function-name.d.ts +5 -16
  68. package/dist/utils/extract-function-name.js +86 -291
  69. package/dist/utils/extract-services.d.ts +2 -1
  70. package/dist/utils/extract-services.js +25 -1
  71. package/dist/utils/filter-inspector-state.d.ts +1 -1
  72. package/dist/utils/filter-inspector-state.js +107 -23
  73. package/dist/utils/filter-utils.d.ts +2 -2
  74. package/dist/utils/get-files-and-methods.d.ts +1 -1
  75. package/dist/utils/get-property-value.d.ts +6 -1
  76. package/dist/utils/get-property-value.js +28 -3
  77. package/dist/utils/hash.d.ts +2 -0
  78. package/dist/utils/hash.js +23 -0
  79. package/dist/utils/middleware.d.ts +9 -32
  80. package/dist/utils/middleware.js +80 -66
  81. package/dist/utils/permissions.d.ts +4 -4
  82. package/dist/utils/permissions.js +10 -10
  83. package/dist/utils/post-process.d.ts +11 -11
  84. package/dist/utils/post-process.js +247 -24
  85. package/dist/utils/resolve-addon-package.d.ts +16 -0
  86. package/dist/utils/resolve-addon-package.js +34 -0
  87. package/dist/utils/resolve-function-types.d.ts +6 -0
  88. package/dist/utils/resolve-function-types.js +29 -0
  89. package/dist/utils/resolve-identifier.d.ts +10 -0
  90. package/dist/utils/resolve-identifier.js +36 -0
  91. package/dist/utils/resolve-versions.d.ts +2 -0
  92. package/dist/utils/resolve-versions.js +78 -0
  93. package/dist/utils/schema-generator.d.ts +9 -0
  94. package/dist/utils/schema-generator.js +209 -0
  95. package/dist/utils/serialize-inspector-state.d.ts +70 -23
  96. package/dist/utils/serialize-inspector-state.js +98 -22
  97. package/dist/utils/serialize-mcp-json.d.ts +2 -0
  98. package/dist/utils/serialize-mcp-json.js +99 -0
  99. package/dist/utils/serialize-middleware-groups-meta.d.ts +12 -0
  100. package/dist/utils/serialize-middleware-groups-meta.js +28 -0
  101. package/dist/utils/serialize-openapi-json.d.ts +85 -0
  102. package/dist/utils/serialize-openapi-json.js +151 -0
  103. package/dist/utils/serialize-permissions-groups-meta.d.ts +6 -0
  104. package/dist/utils/serialize-permissions-groups-meta.js +31 -0
  105. package/dist/utils/validate-auth-sessionless.d.ts +3 -0
  106. package/dist/utils/validate-auth-sessionless.js +14 -0
  107. package/dist/utils/workflow/dsl/deserialize-dsl-workflow.js +34 -102
  108. package/dist/utils/workflow/dsl/extract-dsl-workflow.d.ts +1 -1
  109. package/dist/utils/workflow/dsl/extract-dsl-workflow.js +23 -4
  110. package/dist/utils/workflow/graph/convert-dsl-to-graph.js +12 -10
  111. package/dist/utils/workflow/graph/finalize-workflow-wires.d.ts +3 -0
  112. package/dist/utils/workflow/graph/finalize-workflow-wires.js +276 -0
  113. package/dist/utils/workflow/graph/finalize-workflows.d.ts +2 -0
  114. package/dist/utils/workflow/graph/finalize-workflows.js +75 -0
  115. package/dist/utils/workflow/graph/index.d.ts +2 -0
  116. package/dist/utils/workflow/graph/index.js +2 -0
  117. package/dist/utils/workflow/graph/serialize-workflow-graph.d.ts +0 -8
  118. package/dist/utils/workflow/graph/serialize-workflow-graph.js +1 -3
  119. package/dist/utils/workflow/graph/workflow-graph.types.d.ts +53 -79
  120. package/dist/utils/workflow/graph/workflow-graph.types.js +1 -1
  121. package/dist/visit.d.ts +1 -1
  122. package/dist/visit.js +13 -6
  123. package/package.json +14 -4
  124. package/src/add/add-ai-agent.ts +468 -0
  125. package/src/add/add-channel.ts +103 -79
  126. package/src/add/add-cli.ts +68 -24
  127. package/src/add/add-file-extends-core-type.ts +1 -1
  128. package/src/add/add-file-with-config.ts +1 -1
  129. package/src/add/add-file-with-factory.ts +3 -1
  130. package/src/add/add-functions.ts +349 -103
  131. package/src/add/add-http-route.ts +263 -89
  132. package/src/add/add-http-routes.ts +229 -0
  133. package/src/add/add-keyed-wiring.ts +151 -0
  134. package/src/add/add-mcp-prompt.ts +27 -16
  135. package/src/add/add-mcp-resource.ts +28 -16
  136. package/src/add/add-middleware.ts +482 -80
  137. package/src/add/add-permission.ts +199 -40
  138. package/src/add/add-queue-worker.ts +25 -20
  139. package/src/add/add-rpc-invocations.ts +28 -11
  140. package/src/add/add-schedule.ts +17 -12
  141. package/src/add/add-secret.ts +140 -0
  142. package/src/add/add-trigger.ts +154 -0
  143. package/src/add/add-variable.ts +9 -0
  144. package/src/add/add-wire-addon.ts +80 -0
  145. package/src/add/add-workflow-graph.ts +180 -522
  146. package/src/add/add-workflow.ts +7 -6
  147. package/src/error-codes.ts +25 -1
  148. package/src/index.ts +23 -13
  149. package/src/inspector.ts +139 -19
  150. package/src/schema-generator.ts +1 -0
  151. package/src/types-map.ts +12 -1
  152. package/src/types.ts +199 -69
  153. package/src/utils/compute-required-schemas.ts +48 -0
  154. package/src/utils/contract-hashes.test.ts +553 -0
  155. package/src/utils/contract-hashes.ts +386 -0
  156. package/src/utils/custom-types-generator.ts +88 -0
  157. package/src/utils/detect-schema-vendor.ts +90 -0
  158. package/src/utils/does-type-extend-core-type.ts +1 -1
  159. package/src/utils/ensure-function-metadata.ts +325 -8
  160. package/src/utils/extract-function-name.ts +101 -351
  161. package/src/utils/extract-services.ts +35 -2
  162. package/src/utils/filter-inspector-state.test.ts +37 -25
  163. package/src/utils/filter-inspector-state.ts +146 -32
  164. package/src/utils/filter-utils.test.ts +1 -1
  165. package/src/utils/filter-utils.ts +2 -2
  166. package/src/utils/get-files-and-methods.ts +1 -1
  167. package/src/utils/get-property-value.ts +42 -4
  168. package/src/utils/hash.ts +26 -0
  169. package/src/utils/middleware.test.ts +204 -0
  170. package/src/utils/middleware.ts +131 -69
  171. package/src/utils/permissions.test.ts +35 -12
  172. package/src/utils/permissions.ts +12 -12
  173. package/src/utils/post-process.ts +306 -44
  174. package/src/utils/resolve-addon-package.ts +49 -0
  175. package/src/utils/resolve-function-types.ts +42 -0
  176. package/src/utils/resolve-identifier.ts +46 -0
  177. package/src/utils/resolve-versions.test.ts +249 -0
  178. package/src/utils/resolve-versions.ts +105 -0
  179. package/src/utils/schema-generator.ts +329 -0
  180. package/src/utils/serialize-inspector-state.ts +184 -43
  181. package/src/utils/serialize-mcp-json.ts +145 -0
  182. package/src/utils/serialize-middleware-groups-meta.ts +33 -0
  183. package/src/utils/serialize-openapi-json.ts +277 -0
  184. package/src/utils/serialize-permissions-groups-meta.ts +35 -0
  185. package/src/utils/test-data/inspector-state.json +69 -66
  186. package/src/utils/validate-auth-sessionless.ts +29 -0
  187. package/src/utils/workflow/dsl/deserialize-dsl-workflow.ts +43 -119
  188. package/src/utils/workflow/dsl/extract-dsl-workflow.ts +26 -6
  189. package/src/utils/workflow/graph/convert-dsl-to-graph.ts +17 -10
  190. package/src/utils/workflow/graph/finalize-workflow-wires.ts +310 -0
  191. package/src/utils/workflow/graph/finalize-workflows.ts +100 -0
  192. package/src/utils/workflow/graph/index.ts +5 -0
  193. package/src/utils/workflow/graph/serialize-workflow-graph.ts +1 -8
  194. package/src/utils/workflow/graph/workflow-graph.types.ts +29 -78
  195. package/src/visit.ts +19 -7
  196. package/tsconfig.tsbuildinfo +1 -1
  197. package/dist/add/add-forge-credential.d.ts +0 -8
  198. package/dist/add/add-forge-credential.js +0 -77
  199. package/dist/add/add-forge-node.d.ts +0 -7
  200. package/dist/add/add-forge-node.js +0 -77
  201. package/dist/add/add-mcp-tool.d.ts +0 -2
  202. package/dist/add/add-mcp-tool.js +0 -81
  203. package/dist/utils/extract-service-metadata.d.ts +0 -19
  204. package/dist/utils/extract-service-metadata.js +0 -244
  205. package/dist/utils/write-service-metadata.d.ts +0 -13
  206. package/dist/utils/write-service-metadata.js +0 -37
  207. package/src/add/add-forge-credential.ts +0 -119
  208. package/src/add/add-forge-node.ts +0 -132
  209. package/src/add/add-mcp-tool.ts +0 -141
  210. package/src/utils/extract-service-metadata.ts +0 -353
  211. package/src/utils/write-service-metadata.ts +0 -51
@@ -1,132 +0,0 @@
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
- }
@@ -1,141 +0,0 @@
1
- import * as ts from 'typescript'
2
- import {
3
- getPropertyValue,
4
- getCommonWireMetaData,
5
- } from '../utils/get-property-value.js'
6
- import { extractWireNames } from '../utils/post-process.js'
7
- import { ensureFunctionMetadata } from '../utils/ensure-function-metadata.js'
8
- import { AddWiring } from '../types.js'
9
- import { extractFunctionName } from '../utils/extract-function-name.js'
10
- import { getPropertyAssignmentInitializer } from '../utils/type-utils.js'
11
- import { resolveMiddleware } from '../utils/middleware.js'
12
- import { resolvePermissions } from '../utils/permissions.js'
13
- import { ErrorCode } from '../error-codes.js'
14
-
15
- export const addMCPTool: 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 wireMCPTool
31
- if (!ts.isIdentifier(expression) || expression.text !== 'wireMCPTool') {
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 titleValue = getPropertyValue(obj, 'title') as string | null
44
- const { tags, summary, description, errors } = getCommonWireMetaData(
45
- obj,
46
- 'MCP tool',
47
- nameValue,
48
- logger
49
- )
50
- const streamingValue = getPropertyValue(obj, 'streaming') as boolean | null
51
-
52
- if (streamingValue === true) {
53
- logger.warn(
54
- `MCP tool '${nameValue}' has streaming enabled, but streaming is not yet supported.`
55
- )
56
- }
57
-
58
- const funcInitializer = getPropertyAssignmentInitializer(
59
- obj,
60
- 'func',
61
- true,
62
- checker
63
- )
64
- if (!funcInitializer) {
65
- logger.critical(
66
- ErrorCode.MISSING_FUNC,
67
- `No valid 'func' property for MCP tool '${nameValue}'.`
68
- )
69
- return
70
- }
71
-
72
- const pikkuFuncName = extractFunctionName(
73
- funcInitializer,
74
- checker,
75
- state.rootDir
76
- ).pikkuFuncName
77
-
78
- // Ensure function metadata exists (creates stub for inline functions)
79
- ensureFunctionMetadata(state, pikkuFuncName, nameValue || undefined)
80
-
81
- if (!nameValue) {
82
- logger.critical(
83
- ErrorCode.MISSING_NAME,
84
- "MCP tool is missing the required 'name' property."
85
- )
86
- return
87
- }
88
-
89
- if (!description) {
90
- logger.critical(
91
- ErrorCode.MISSING_DESCRIPTION,
92
- `MCP tool '${nameValue}' is missing a description.`
93
- )
94
- return
95
- }
96
-
97
- // lookup existing function metadata
98
- const fnMeta = state.functions.meta[pikkuFuncName]
99
- if (!fnMeta) {
100
- logger.critical(
101
- ErrorCode.FUNCTION_METADATA_NOT_FOUND,
102
- `No function metadata found for '${pikkuFuncName}'.`
103
- )
104
- return
105
- }
106
- const inputSchema = fnMeta.inputs?.[0] || null
107
- const outputSchema = fnMeta.outputs?.[0] || null
108
-
109
- // --- resolve middleware ---
110
- const middleware = resolveMiddleware(state, obj, tags, checker)
111
-
112
- // --- resolve permissions ---
113
- const permissions = resolvePermissions(state, obj, tags, checker)
114
-
115
- // --- track used functions/middleware/permissions for service aggregation ---
116
- state.serviceAggregation.usedFunctions.add(pikkuFuncName)
117
- extractWireNames(middleware).forEach((name) =>
118
- state.serviceAggregation.usedMiddleware.add(name)
119
- )
120
- extractWireNames(permissions).forEach((name) =>
121
- state.serviceAggregation.usedPermissions.add(name)
122
- )
123
-
124
- state.mcpEndpoints.files.add(node.getSourceFile().fileName)
125
-
126
- state.mcpEndpoints.toolsMeta[nameValue] = {
127
- pikkuFuncName,
128
- name: nameValue,
129
- title: titleValue || undefined,
130
- description,
131
- summary,
132
- errors,
133
- ...(streamingValue !== null && { streaming: streamingValue }),
134
- tags,
135
- inputSchema,
136
- outputSchema,
137
- middleware,
138
- permissions,
139
- }
140
- }
141
- }
@@ -1,353 +0,0 @@
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
- }
@@ -1,51 +0,0 @@
1
- import * as fs from 'fs'
2
- import * as path from 'path'
3
- import { ServiceMetadata } from './extract-service-metadata.js'
4
-
5
- /**
6
- * Write service metadata to a JSON file in .pikku/services directory
7
- */
8
- export function writeServiceMetadata(
9
- serviceMeta: ServiceMetadata,
10
- outDir: string
11
- ): void {
12
- const servicesDir = path.join(outDir, 'services')
13
-
14
- if (!fs.existsSync(servicesDir)) {
15
- fs.mkdirSync(servicesDir, { recursive: true })
16
- }
17
-
18
- const fileName = `${serviceMeta.name}.gen.json`
19
- const filePath = path.join(servicesDir, fileName)
20
-
21
- const jsonContent = JSON.stringify(serviceMeta, null, 2)
22
- fs.writeFileSync(filePath, jsonContent, 'utf-8')
23
- }
24
-
25
- /**
26
- * Write all service metadata files
27
- */
28
- export function writeAllServiceMetadata(
29
- servicesMetadata: ServiceMetadata[],
30
- outDir: string
31
- ): void {
32
- for (const serviceMeta of servicesMetadata) {
33
- writeServiceMetadata(serviceMeta, outDir)
34
- }
35
- }
36
-
37
- /**
38
- * Clean up services directory (remove old service JSON files)
39
- */
40
- export function cleanServicesDirectory(outDir: string): void {
41
- const servicesDir = path.join(outDir, 'services')
42
-
43
- if (fs.existsSync(servicesDir)) {
44
- const files = fs.readdirSync(servicesDir)
45
- for (const file of files) {
46
- if (file.endsWith('.gen.json')) {
47
- fs.unlinkSync(path.join(servicesDir, file))
48
- }
49
- }
50
- }
51
- }