@pikku/inspector 0.11.2 → 0.12.0

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 (182) hide show
  1. package/CHANGELOG.md +11 -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 +69 -61
  6. package/dist/add/add-cli.js +36 -18
  7. package/dist/add/add-file-with-factory.js +2 -0
  8. package/dist/add/add-functions.js +250 -75
  9. package/dist/add/add-http-route.d.ts +19 -10
  10. package/dist/add/add-http-route.js +152 -66
  11. package/dist/add/add-http-routes.d.ts +5 -0
  12. package/dist/add/add-http-routes.js +159 -0
  13. package/dist/add/add-keyed-wiring.d.ts +12 -0
  14. package/dist/add/add-keyed-wiring.js +97 -0
  15. package/dist/add/add-mcp-prompt.js +14 -9
  16. package/dist/add/add-mcp-resource.js +14 -9
  17. package/dist/add/add-middleware.d.ts +1 -4
  18. package/dist/add/add-middleware.js +364 -79
  19. package/dist/add/add-permission.d.ts +1 -1
  20. package/dist/add/add-permission.js +152 -40
  21. package/dist/add/add-queue-worker.js +18 -12
  22. package/dist/add/add-rpc-invocations.js +14 -0
  23. package/dist/add/add-schedule.js +11 -5
  24. package/dist/add/add-secret.d.ts +3 -0
  25. package/dist/add/add-secret.js +82 -0
  26. package/dist/add/add-trigger.d.ts +2 -0
  27. package/dist/add/add-trigger.js +87 -0
  28. package/dist/add/add-variable.d.ts +1 -0
  29. package/dist/add/add-variable.js +8 -0
  30. package/dist/add/add-workflow-graph.d.ts +3 -2
  31. package/dist/add/add-workflow-graph.js +143 -406
  32. package/dist/add/add-workflow.js +6 -4
  33. package/dist/error-codes.d.ts +14 -1
  34. package/dist/error-codes.js +19 -1
  35. package/dist/index.d.ts +9 -8
  36. package/dist/index.js +5 -4
  37. package/dist/inspector.d.ts +1 -1
  38. package/dist/inspector.js +91 -14
  39. package/dist/schema-generator.d.ts +1 -0
  40. package/dist/schema-generator.js +1 -0
  41. package/dist/types-map.js +10 -1
  42. package/dist/types.d.ts +163 -39
  43. package/dist/utils/compute-required-schemas.d.ts +4 -0
  44. package/dist/utils/compute-required-schemas.js +41 -0
  45. package/dist/utils/contract-hashes.d.ts +35 -0
  46. package/dist/utils/contract-hashes.js +202 -0
  47. package/dist/utils/custom-types-generator.d.ts +9 -0
  48. package/dist/utils/custom-types-generator.js +71 -0
  49. package/dist/utils/detect-schema-vendor.d.ts +22 -0
  50. package/dist/utils/detect-schema-vendor.js +76 -0
  51. package/dist/utils/ensure-function-metadata.d.ts +5 -2
  52. package/dist/utils/ensure-function-metadata.js +220 -6
  53. package/dist/utils/extract-function-name.d.ts +5 -16
  54. package/dist/utils/extract-function-name.js +86 -291
  55. package/dist/utils/extract-services.d.ts +2 -1
  56. package/dist/utils/extract-services.js +25 -1
  57. package/dist/utils/filter-inspector-state.js +107 -23
  58. package/dist/utils/get-property-value.d.ts +6 -1
  59. package/dist/utils/get-property-value.js +28 -3
  60. package/dist/utils/hash.d.ts +2 -0
  61. package/dist/utils/hash.js +23 -0
  62. package/dist/utils/middleware.d.ts +7 -30
  63. package/dist/utils/middleware.js +80 -66
  64. package/dist/utils/permissions.d.ts +2 -2
  65. package/dist/utils/permissions.js +10 -10
  66. package/dist/utils/post-process.d.ts +9 -10
  67. package/dist/utils/post-process.js +231 -24
  68. package/dist/utils/resolve-external-package.d.ts +12 -0
  69. package/dist/utils/resolve-external-package.js +34 -0
  70. package/dist/utils/resolve-function-types.d.ts +6 -0
  71. package/dist/utils/resolve-function-types.js +29 -0
  72. package/dist/utils/resolve-identifier.d.ts +10 -0
  73. package/dist/utils/resolve-identifier.js +36 -0
  74. package/dist/utils/resolve-versions.d.ts +2 -0
  75. package/dist/utils/resolve-versions.js +78 -0
  76. package/dist/utils/schema-generator.d.ts +9 -0
  77. package/dist/utils/schema-generator.js +209 -0
  78. package/dist/utils/serialize-inspector-state.d.ts +59 -22
  79. package/dist/utils/serialize-inspector-state.js +92 -20
  80. package/dist/utils/serialize-mcp-json.d.ts +2 -0
  81. package/dist/utils/serialize-mcp-json.js +99 -0
  82. package/dist/utils/serialize-middleware-groups-meta.d.ts +12 -0
  83. package/dist/utils/serialize-middleware-groups-meta.js +28 -0
  84. package/dist/utils/serialize-openapi-json.d.ts +85 -0
  85. package/dist/utils/serialize-openapi-json.js +151 -0
  86. package/dist/utils/serialize-permissions-groups-meta.d.ts +6 -0
  87. package/dist/utils/serialize-permissions-groups-meta.js +31 -0
  88. package/dist/utils/workflow/dsl/deserialize-dsl-workflow.js +34 -102
  89. package/dist/utils/workflow/dsl/extract-dsl-workflow.js +23 -4
  90. package/dist/utils/workflow/graph/convert-dsl-to-graph.js +12 -10
  91. package/dist/utils/workflow/graph/finalize-workflow-wires.d.ts +3 -0
  92. package/dist/utils/workflow/graph/finalize-workflow-wires.js +276 -0
  93. package/dist/utils/workflow/graph/finalize-workflows.d.ts +2 -0
  94. package/dist/utils/workflow/graph/finalize-workflows.js +75 -0
  95. package/dist/utils/workflow/graph/index.d.ts +2 -0
  96. package/dist/utils/workflow/graph/index.js +2 -0
  97. package/dist/utils/workflow/graph/serialize-workflow-graph.d.ts +0 -8
  98. package/dist/utils/workflow/graph/serialize-workflow-graph.js +1 -3
  99. package/dist/utils/workflow/graph/workflow-graph.types.d.ts +53 -79
  100. package/dist/utils/workflow/graph/workflow-graph.types.js +1 -1
  101. package/dist/visit.js +11 -6
  102. package/package.json +14 -4
  103. package/src/add/add-ai-agent.ts +468 -0
  104. package/src/add/add-channel.ts +82 -79
  105. package/src/add/add-cli.ts +49 -20
  106. package/src/add/add-file-with-factory.ts +2 -0
  107. package/src/add/add-functions.ts +330 -86
  108. package/src/add/add-http-route.ts +245 -88
  109. package/src/add/add-http-routes.ts +228 -0
  110. package/src/add/add-keyed-wiring.ts +151 -0
  111. package/src/add/add-mcp-prompt.ts +26 -15
  112. package/src/add/add-mcp-resource.ts +27 -15
  113. package/src/add/add-middleware.ts +482 -80
  114. package/src/add/add-permission.ts +199 -40
  115. package/src/add/add-queue-worker.ts +24 -19
  116. package/src/add/add-rpc-invocations.ts +17 -0
  117. package/src/add/add-schedule.ts +16 -11
  118. package/src/add/add-secret.ts +140 -0
  119. package/src/add/add-trigger.ts +154 -0
  120. package/src/add/add-variable.ts +9 -0
  121. package/src/add/add-workflow-graph.ts +180 -522
  122. package/src/add/add-workflow.ts +5 -4
  123. package/src/error-codes.ts +24 -1
  124. package/src/index.ts +22 -13
  125. package/src/inspector.ts +129 -17
  126. package/src/schema-generator.ts +1 -0
  127. package/src/types-map.ts +12 -1
  128. package/src/types.ts +175 -58
  129. package/src/utils/compute-required-schemas.ts +49 -0
  130. package/src/utils/contract-hashes.test.ts +528 -0
  131. package/src/utils/contract-hashes.ts +290 -0
  132. package/src/utils/custom-types-generator.ts +88 -0
  133. package/src/utils/detect-schema-vendor.ts +90 -0
  134. package/src/utils/ensure-function-metadata.ts +324 -7
  135. package/src/utils/extract-function-name.ts +101 -351
  136. package/src/utils/extract-services.ts +35 -2
  137. package/src/utils/filter-inspector-state.test.ts +34 -20
  138. package/src/utils/filter-inspector-state.ts +140 -31
  139. package/src/utils/get-property-value.ts +42 -4
  140. package/src/utils/hash.ts +26 -0
  141. package/src/utils/middleware.test.ts +204 -0
  142. package/src/utils/middleware.ts +129 -67
  143. package/src/utils/permissions.test.ts +35 -12
  144. package/src/utils/permissions.ts +10 -10
  145. package/src/utils/post-process.ts +283 -43
  146. package/src/utils/resolve-external-package.ts +42 -0
  147. package/src/utils/resolve-function-types.ts +42 -0
  148. package/src/utils/resolve-identifier.ts +46 -0
  149. package/src/utils/resolve-versions.test.ts +249 -0
  150. package/src/utils/resolve-versions.ts +105 -0
  151. package/src/utils/schema-generator.ts +329 -0
  152. package/src/utils/serialize-inspector-state.ts +163 -40
  153. package/src/utils/serialize-mcp-json.ts +145 -0
  154. package/src/utils/serialize-middleware-groups-meta.ts +33 -0
  155. package/src/utils/serialize-openapi-json.ts +277 -0
  156. package/src/utils/serialize-permissions-groups-meta.ts +35 -0
  157. package/src/utils/test-data/inspector-state.json +69 -66
  158. package/src/utils/workflow/dsl/deserialize-dsl-workflow.ts +43 -119
  159. package/src/utils/workflow/dsl/extract-dsl-workflow.ts +24 -4
  160. package/src/utils/workflow/graph/convert-dsl-to-graph.ts +17 -10
  161. package/src/utils/workflow/graph/finalize-workflow-wires.ts +310 -0
  162. package/src/utils/workflow/graph/finalize-workflows.ts +100 -0
  163. package/src/utils/workflow/graph/index.ts +5 -0
  164. package/src/utils/workflow/graph/serialize-workflow-graph.ts +1 -8
  165. package/src/utils/workflow/graph/workflow-graph.types.ts +29 -78
  166. package/src/visit.ts +12 -6
  167. package/tsconfig.tsbuildinfo +1 -1
  168. package/dist/add/add-forge-credential.d.ts +0 -8
  169. package/dist/add/add-forge-credential.js +0 -77
  170. package/dist/add/add-forge-node.d.ts +0 -7
  171. package/dist/add/add-forge-node.js +0 -77
  172. package/dist/add/add-mcp-tool.d.ts +0 -2
  173. package/dist/add/add-mcp-tool.js +0 -81
  174. package/dist/utils/extract-service-metadata.d.ts +0 -19
  175. package/dist/utils/extract-service-metadata.js +0 -244
  176. package/dist/utils/write-service-metadata.d.ts +0 -13
  177. package/dist/utils/write-service-metadata.js +0 -37
  178. package/src/add/add-forge-credential.ts +0 -119
  179. package/src/add/add-forge-node.ts +0 -132
  180. package/src/add/add-mcp-tool.ts +0 -141
  181. package/src/utils/extract-service-metadata.ts +0 -353
  182. package/src/utils/write-service-metadata.ts +0 -51
@@ -0,0 +1,290 @@
1
+ import { FunctionsMeta, JSONValue, parseVersionedId } from '@pikku/core'
2
+ import { TypesMap } from '../types-map.js'
3
+ import { ErrorCode } from '../error-codes.js'
4
+ import { canonicalJSON, hashString } from './hash.js'
5
+
6
+ export type ContractEntry = {
7
+ functionKey: string
8
+ version: number
9
+ contractHash: string
10
+ }
11
+
12
+ export type VersionValidateError = {
13
+ code: ErrorCode
14
+ message: string
15
+ }
16
+
17
+ export type VersionManifestEntry = {
18
+ latest: number
19
+ versions: Record<string, string>
20
+ }
21
+
22
+ export type VersionManifest = {
23
+ manifestVersion: 1
24
+ contracts: Record<string, VersionManifestEntry>
25
+ }
26
+
27
+ export function createEmptyManifest(): VersionManifest {
28
+ return {
29
+ manifestVersion: 1,
30
+ contracts: {},
31
+ }
32
+ }
33
+
34
+ export function serializeManifest(manifest: VersionManifest): string {
35
+ const sortedContracts: Record<string, VersionManifestEntry> = {}
36
+
37
+ for (const key of Object.keys(manifest.contracts).sort()) {
38
+ const entry = manifest.contracts[key]
39
+ const sortedVersions: Record<string, string> = {}
40
+ const numericKeys = Object.keys(entry.versions)
41
+ .map(Number)
42
+ .sort((a, b) => a - b)
43
+ for (const vk of numericKeys) {
44
+ sortedVersions[String(vk)] = entry.versions[String(vk)]
45
+ }
46
+ sortedContracts[key] = {
47
+ latest: entry.latest,
48
+ versions: sortedVersions,
49
+ }
50
+ }
51
+
52
+ const sorted: VersionManifest = {
53
+ manifestVersion: manifest.manifestVersion,
54
+ contracts: sortedContracts,
55
+ }
56
+
57
+ return JSON.stringify(sorted, null, 2) + '\n'
58
+ }
59
+
60
+ export function computeContractHash(data: {
61
+ functionKey: string
62
+ inputSchema: unknown
63
+ outputSchema: unknown
64
+ }): string {
65
+ return hashString(canonicalJSON(data), 16)
66
+ }
67
+
68
+ function resolveSchema(
69
+ typeNames: string[] | null | undefined,
70
+ allSchemas: Record<string, JSONValue>,
71
+ typesMap: TypesMap
72
+ ): unknown {
73
+ if (!typeNames) {
74
+ return null
75
+ }
76
+ const filtered = typeNames.filter((n) => n !== 'void')
77
+ if (filtered.length === 0) {
78
+ return null
79
+ }
80
+ const parts: unknown[] = []
81
+ for (const name of filtered) {
82
+ let key: string
83
+ try {
84
+ key = typesMap.getUniqueName(name)
85
+ } catch {
86
+ key = name
87
+ }
88
+ const schema = allSchemas[key]
89
+ if (schema) {
90
+ parts.push(schema)
91
+ }
92
+ }
93
+ if (parts.length === 0) {
94
+ return null
95
+ }
96
+ return parts.length === 1 ? parts[0] : parts
97
+ }
98
+
99
+ export function buildCurrentContracts(
100
+ functionsMeta: FunctionsMeta,
101
+ allSchemas: Record<string, JSONValue>,
102
+ typesMap: TypesMap
103
+ ): Map<string, ContractEntry> {
104
+ const result = new Map<string, ContractEntry>()
105
+
106
+ for (const [funcId, meta] of Object.entries(functionsMeta)) {
107
+ if (meta.remote === true) {
108
+ continue
109
+ }
110
+
111
+ const parsed = parseVersionedId(funcId)
112
+ const functionKey = parsed.baseName
113
+ const version = parsed.version ?? meta.version ?? 1
114
+
115
+ const inputSchema = resolveSchema(meta.inputs, allSchemas, typesMap)
116
+ const outputSchema = resolveSchema(meta.outputs, allSchemas, typesMap)
117
+
118
+ const contractHash = computeContractHash({
119
+ functionKey,
120
+ inputSchema,
121
+ outputSchema,
122
+ })
123
+ result.set(funcId, { functionKey, version, contractHash })
124
+ }
125
+
126
+ return result
127
+ }
128
+
129
+ export function computeContractHashes(
130
+ allSchemas: Record<string, JSONValue>,
131
+ typesMap: TypesMap,
132
+ functionsMeta: FunctionsMeta
133
+ ): Map<string, ContractEntry> {
134
+ const contracts = buildCurrentContracts(functionsMeta, allSchemas, typesMap)
135
+
136
+ for (const [funcId, entry] of contracts) {
137
+ const meta = functionsMeta[funcId]
138
+ if (meta) {
139
+ meta.contractHash = entry.contractHash
140
+ }
141
+ }
142
+
143
+ return contracts
144
+ }
145
+
146
+ function groupByFunctionKey(
147
+ contracts: Map<string, ContractEntry>
148
+ ): Map<string, ContractEntry[]> {
149
+ const grouped = new Map<string, ContractEntry[]>()
150
+ for (const entry of contracts.values()) {
151
+ const existing = grouped.get(entry.functionKey) ?? []
152
+ existing.push(entry)
153
+ grouped.set(entry.functionKey, existing)
154
+ }
155
+ return grouped
156
+ }
157
+
158
+ export function validateContracts(
159
+ manifest: VersionManifest,
160
+ currentContracts: Map<string, ContractEntry>
161
+ ): { valid: boolean; errors: VersionValidateError[] } {
162
+ const errors: VersionValidateError[] = []
163
+ const grouped = groupByFunctionKey(currentContracts)
164
+ const reportedKeys = new Set<string>()
165
+
166
+ for (const [functionKey, entries] of grouped) {
167
+ const manifestEntry = manifest.contracts[functionKey]
168
+ if (!manifestEntry) {
169
+ continue
170
+ }
171
+
172
+ for (const { version, contractHash } of entries) {
173
+ const existingHash = manifestEntry.versions[String(version)]
174
+
175
+ if (existingHash !== undefined) {
176
+ if (existingHash !== contractHash) {
177
+ reportedKeys.add(`${functionKey}@${version}`)
178
+ errors.push({
179
+ code: ErrorCode.FUNCTION_VERSION_MODIFIED,
180
+ message: `Contract for ${functionKey}@v${version} has changed (recorded: ${existingHash}, current: ${contractHash}). Existing versions are immutable.`,
181
+ })
182
+ }
183
+ } else {
184
+ if (version <= manifestEntry.latest) {
185
+ errors.push({
186
+ code: ErrorCode.VERSION_REGRESSION_OR_CONFLICT,
187
+ message: `Version ${version} for ${functionKey} is <= latest (${manifestEntry.latest}) but not recorded. Possible merge conflict.`,
188
+ })
189
+ } else if (version > manifestEntry.latest + 1) {
190
+ errors.push({
191
+ code: ErrorCode.VERSION_GAP_NOT_ALLOWED,
192
+ message: `Version ${version} for ${functionKey} skips versions. Latest is ${manifestEntry.latest}, next must be ${manifestEntry.latest + 1}.`,
193
+ })
194
+ }
195
+ }
196
+ }
197
+ }
198
+
199
+ for (const [functionKey, manifestEntry] of Object.entries(
200
+ manifest.contracts
201
+ )) {
202
+ const latestHash = manifestEntry.versions[String(manifestEntry.latest)]
203
+ const currentEntries = grouped.get(functionKey)
204
+ if (!currentEntries) {
205
+ continue
206
+ }
207
+
208
+ const currentLatest = currentEntries.find(
209
+ (e) => e.version === manifestEntry.latest
210
+ )
211
+ if (
212
+ currentLatest &&
213
+ currentLatest.contractHash !== latestHash &&
214
+ !reportedKeys.has(`${functionKey}@${manifestEntry.latest}`)
215
+ ) {
216
+ errors.push({
217
+ code: ErrorCode.CONTRACT_CHANGED_REQUIRES_BUMP,
218
+ message: `Contract for ${functionKey} changed. Set \`version: ${manifestEntry.latest + 1}\` on the function or run 'pikku versions update'.`,
219
+ })
220
+ }
221
+ }
222
+
223
+ for (const [functionKey, manifestEntry] of Object.entries(
224
+ manifest.contracts
225
+ )) {
226
+ const numericKeys = Object.keys(manifestEntry.versions).map(Number)
227
+ if (numericKeys.length === 0) {
228
+ continue
229
+ }
230
+ const maxVersion = Math.max(...numericKeys)
231
+ if (manifestEntry.latest !== maxVersion) {
232
+ errors.push({
233
+ code: ErrorCode.MANIFEST_INTEGRITY_ERROR,
234
+ message: `Manifest integrity error for ${functionKey}: latest field (${manifestEntry.latest}) inconsistent with version keys (max: ${maxVersion}).`,
235
+ })
236
+ }
237
+ }
238
+
239
+ return { valid: errors.length === 0, errors }
240
+ }
241
+
242
+ export function updateManifest(
243
+ existing: VersionManifest,
244
+ currentContracts: Map<string, ContractEntry>
245
+ ): VersionManifest {
246
+ const manifest: VersionManifest = {
247
+ manifestVersion: existing.manifestVersion,
248
+ contracts: JSON.parse(JSON.stringify(existing.contracts)),
249
+ }
250
+
251
+ const grouped = groupByFunctionKey(currentContracts)
252
+
253
+ for (const [functionKey, entries] of grouped) {
254
+ if (!manifest.contracts[functionKey]) {
255
+ manifest.contracts[functionKey] = { latest: 0, versions: {} }
256
+ }
257
+
258
+ const entry = manifest.contracts[functionKey]
259
+ for (const { version, contractHash } of entries) {
260
+ entry.versions[String(version)] = contractHash
261
+ entry.latest = Math.max(entry.latest, version)
262
+ }
263
+ }
264
+
265
+ return manifest
266
+ }
267
+
268
+ export function extractContractsFromMeta(
269
+ functionsMeta: FunctionsMeta
270
+ ): Map<string, ContractEntry> {
271
+ const result = new Map<string, ContractEntry>()
272
+
273
+ for (const [funcId, meta] of Object.entries(functionsMeta)) {
274
+ if (meta.remote === true || !meta.contractHash) {
275
+ continue
276
+ }
277
+
278
+ const parsed = parseVersionedId(funcId)
279
+ const functionKey = parsed.baseName
280
+ const version = parsed.version ?? meta.version ?? 1
281
+
282
+ result.set(funcId, {
283
+ functionKey,
284
+ version,
285
+ contractHash: meta.contractHash,
286
+ })
287
+ }
288
+
289
+ return result
290
+ }
@@ -0,0 +1,88 @@
1
+ import { TypesMap } from '../types-map.js'
2
+
3
+ /**
4
+ * NOTE: Code generation normally belongs in @pikku/cli, not the inspector.
5
+ * This is here because the schema generator needs the custom types content
6
+ * as a virtual TypeScript source file (in-memory, no disk write) so that
7
+ * ts-json-schema-generator can discover inline/custom types from typesMap.
8
+ */
9
+
10
+ export function sanitizeTypeName(name: string): string {
11
+ return name.replace(/[^a-zA-Z0-9_$]/g, '_')
12
+ }
13
+
14
+ export function generateCustomTypes(
15
+ typesMap: TypesMap,
16
+ requiredTypes: Set<string>
17
+ ) {
18
+ const typeDeclarations = Array.from(typesMap.customTypes.entries())
19
+ .filter(([_name, { type }]) => {
20
+ const hasUndefinedGeneric =
21
+ /\b(Name|In|Out|Key)\b/.test(type) && /\[.*\]/.test(type)
22
+ return !hasUndefinedGeneric
23
+ })
24
+ .map(([originalName, { type, references }]) => {
25
+ const name = sanitizeTypeName(originalName)
26
+ references.forEach((refName) => {
27
+ if (refName !== '__object' && !refName.startsWith('__object_')) {
28
+ requiredTypes.add(refName)
29
+ }
30
+ })
31
+
32
+ const typeString = type
33
+ const typeNameRegex = /\b[A-Z][a-zA-Z0-9]*\b/g
34
+ const potentialTypes = typeString.match(typeNameRegex) || []
35
+
36
+ potentialTypes.forEach((typeName) => {
37
+ if (
38
+ typeString.includes(`"${typeName}"`) ||
39
+ [
40
+ 'Pick',
41
+ 'Omit',
42
+ 'Partial',
43
+ 'Required',
44
+ 'Record',
45
+ 'Readonly',
46
+ ].includes(typeName)
47
+ ) {
48
+ return
49
+ }
50
+
51
+ try {
52
+ const typeMeta = typesMap.getTypeMeta(typeName)
53
+ if (typeMeta.path) {
54
+ requiredTypes.add(typeMeta.originalName)
55
+ }
56
+ } catch {
57
+ // Type not found in map (ambient/builtin type)
58
+ }
59
+ })
60
+
61
+ if (name === type) return null
62
+ return `export type ${name} = ${type}`
63
+ })
64
+
65
+ const importsByPath = new Map<string, Set<string>>()
66
+ for (const typeName of requiredTypes) {
67
+ try {
68
+ const typeMeta = typesMap.getTypeMeta(typeName)
69
+ if (typeMeta.path) {
70
+ if (!importsByPath.has(typeMeta.path)) {
71
+ importsByPath.set(typeMeta.path, new Set())
72
+ }
73
+ importsByPath.get(typeMeta.path)!.add(typeMeta.originalName)
74
+ }
75
+ } catch {
76
+ // Type not found in map
77
+ }
78
+ }
79
+
80
+ const importLines = Array.from(importsByPath.entries())
81
+ .map(
82
+ ([path, types]) =>
83
+ `import type { ${Array.from(types).join(', ')} } from '${path}'`
84
+ )
85
+ .join('\n')
86
+
87
+ return `${importLines}\n\n${typeDeclarations.filter(Boolean).join('\n')}`
88
+ }
@@ -0,0 +1,90 @@
1
+ import * as ts from 'typescript'
2
+ import type { SchemaVendor, InspectorLogger } from '../types.js'
3
+ import { ErrorCode } from '../error-codes.js'
4
+
5
+ /**
6
+ * Detect the schema vendor by tracing the type back to its library origin.
7
+ * This handles locally-defined schemas like `export const MySchema = z.object({...})`
8
+ * by checking where the type itself originates from.
9
+ *
10
+ * Supports multiple schema libraries in the same project (e.g., during migration
11
+ * from one library to another).
12
+ */
13
+ export const detectSchemaVendor = (
14
+ identifier: ts.Identifier,
15
+ checker: ts.TypeChecker
16
+ ): SchemaVendor => {
17
+ const type = checker.getTypeAtLocation(identifier)
18
+ if (!type) return 'unknown'
19
+
20
+ // Check the type's symbol declarations to find the library origin
21
+ const checkTypeOrigin = (t: ts.Type): SchemaVendor | null => {
22
+ const symbol = t.getSymbol() || t.aliasSymbol
23
+ if (symbol) {
24
+ const decls = symbol.getDeclarations()
25
+ if (decls) {
26
+ for (const decl of decls) {
27
+ const fileName = decl.getSourceFile().fileName
28
+ if (fileName.includes('node_modules/zod')) return 'zod'
29
+ if (fileName.includes('node_modules/valibot')) return 'valibot'
30
+ if (fileName.includes('node_modules/arktype')) return 'arktype'
31
+ if (fileName.includes('node_modules/@effect/schema')) return 'effect'
32
+ }
33
+ }
34
+ }
35
+
36
+ // Check base types for class/interface hierarchies
37
+ const baseTypes = t.getBaseTypes?.()
38
+ if (baseTypes) {
39
+ for (const baseType of baseTypes) {
40
+ const result = checkTypeOrigin(baseType)
41
+ if (result) return result
42
+ }
43
+ }
44
+
45
+ return null
46
+ }
47
+
48
+ const vendor = checkTypeOrigin(type)
49
+ if (vendor) return vendor
50
+
51
+ // Fallback: check type arguments (for generic types like z.ZodObject<...>)
52
+ if ((type as ts.TypeReference).typeArguments) {
53
+ for (const arg of (type as ts.TypeReference).typeArguments || []) {
54
+ const result = checkTypeOrigin(arg)
55
+ if (result) return result
56
+ }
57
+ }
58
+
59
+ return 'unknown'
60
+ }
61
+
62
+ /**
63
+ * Detect schema vendor and log a fatal error if unknown.
64
+ * Returns the vendor if successful, or undefined if unknown (after logging error).
65
+ *
66
+ * @param identifier - The TypeScript identifier for the schema variable
67
+ * @param checker - TypeScript type checker
68
+ * @param logger - Inspector logger for error reporting
69
+ * @param context - Description of what the schema is for (e.g., "Credential 'myCredential'")
70
+ * @param sourceFile - Source file path for error message
71
+ */
72
+ export const detectSchemaVendorOrError = (
73
+ identifier: ts.Identifier,
74
+ checker: ts.TypeChecker,
75
+ logger: InspectorLogger,
76
+ context: string,
77
+ sourceFile: string
78
+ ): Exclude<SchemaVendor, 'unknown'> | undefined => {
79
+ const vendor = detectSchemaVendor(identifier, checker)
80
+ if (vendor === 'unknown') {
81
+ logger.critical(
82
+ ErrorCode.INLINE_SCHEMA,
83
+ `${context} schema vendor could not be determined from '${sourceFile}'. ` +
84
+ `Supported vendors: zod, valibot, arktype, @effect/schema. ` +
85
+ `Ensure your schema is imported from a supported validation library.`
86
+ )
87
+ return undefined
88
+ }
89
+ return vendor
90
+ }