@pikku/inspector 0.12.75 → 0.12.82

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 (223) hide show
  1. package/CHANGELOG.md +357 -0
  2. package/dist/add/add-analytics.d.ts +9 -0
  3. package/dist/add/add-analytics.js +178 -0
  4. package/dist/add/add-auth.js +0 -54
  5. package/dist/add/add-feature-flag.d.ts +2 -0
  6. package/dist/add/add-feature-flag.js +162 -0
  7. package/dist/add/add-middleware.js +66 -42
  8. package/dist/add/add-wire-addon.js +7 -4
  9. package/dist/add/add-wire-remote-addon.js +1 -0
  10. package/dist/error-codes.d.ts +1 -0
  11. package/dist/error-codes.js +1 -0
  12. package/dist/index.d.ts +3 -1
  13. package/dist/index.js +3 -1
  14. package/dist/inspector.js +5 -0
  15. package/dist/surface/collect-surface.d.ts +58 -0
  16. package/dist/surface/collect-surface.js +613 -0
  17. package/dist/types.d.ts +36 -12
  18. package/dist/utils/filter-inspector-state.js +14 -0
  19. package/dist/utils/load-addon-functions-meta.js +24 -11
  20. package/dist/utils/post-process.js +15 -0
  21. package/dist/utils/read-module-specifiers.d.ts +8 -0
  22. package/dist/utils/read-module-specifiers.js +23 -0
  23. package/dist/utils/read-tsconfig-out-dir.d.ts +5 -0
  24. package/dist/utils/read-tsconfig-out-dir.js +12 -0
  25. package/dist/utils/resolve-deploy-target.d.ts +7 -0
  26. package/dist/utils/resolve-deploy-target.js +15 -7
  27. package/dist/utils/serialize-inspector-state.d.ts +6 -0
  28. package/dist/utils/serialize-inspector-state.js +10 -0
  29. package/dist/visit.js +4 -0
  30. package/package.json +16 -6
  31. package/run-tests.sh +21 -6
  32. package/src/add/add-addon-bans.ts +0 -85
  33. package/src/add/add-agent-scorer.test.ts +0 -197
  34. package/src/add/add-agent-scorer.ts +0 -83
  35. package/src/add/add-agent.test.ts +0 -87
  36. package/src/add/add-agent.ts +0 -638
  37. package/src/add/add-approval-description.ts +0 -76
  38. package/src/add/add-auth.test.ts +0 -791
  39. package/src/add/add-auth.ts +0 -424
  40. package/src/add/add-channel.ts +0 -778
  41. package/src/add/add-cli-renderers.test.ts +0 -74
  42. package/src/add/add-cli.ts +0 -1006
  43. package/src/add/add-credential.ts +0 -186
  44. package/src/add/add-feature.test.ts +0 -306
  45. package/src/add/add-feature.ts +0 -253
  46. package/src/add/add-file-extends-core-type.ts +0 -66
  47. package/src/add/add-file-with-config.ts +0 -45
  48. package/src/add/add-file-with-factory.test.ts +0 -134
  49. package/src/add/add-file-with-factory.ts +0 -288
  50. package/src/add/add-functions.test.ts +0 -743
  51. package/src/add/add-functions.ts +0 -1548
  52. package/src/add/add-gateway.ts +0 -143
  53. package/src/add/add-http-route-ref.test.ts +0 -149
  54. package/src/add/add-http-route.ts +0 -530
  55. package/src/add/add-http-routes.ts +0 -471
  56. package/src/add/add-keyed-wiring.ts +0 -180
  57. package/src/add/add-mcp-prompt.ts +0 -157
  58. package/src/add/add-mcp-resource.ts +0 -175
  59. package/src/add/add-middleware.test.ts +0 -196
  60. package/src/add/add-middleware.ts +0 -879
  61. package/src/add/add-permission-auth.test.ts +0 -59
  62. package/src/add/add-permission.ts +0 -311
  63. package/src/add/add-personas.test.ts +0 -406
  64. package/src/add/add-personas.ts +0 -287
  65. package/src/add/add-queue-worker.ts +0 -117
  66. package/src/add/add-rpc-invocations.test.ts +0 -287
  67. package/src/add/add-rpc-invocations.ts +0 -221
  68. package/src/add/add-scenario-step.test.ts +0 -509
  69. package/src/add/add-schedule.ts +0 -120
  70. package/src/add/add-scope.test.ts +0 -419
  71. package/src/add/add-scope.ts +0 -221
  72. package/src/add/add-secret.ts +0 -10
  73. package/src/add/add-subject-scenario-steps.test.ts +0 -161
  74. package/src/add/add-system-role.test.ts +0 -297
  75. package/src/add/add-system-role.ts +0 -162
  76. package/src/add/add-trigger.ts +0 -165
  77. package/src/add/add-variable.ts +0 -10
  78. package/src/add/add-wire-addon.test.ts +0 -185
  79. package/src/add/add-wire-addon.ts +0 -146
  80. package/src/add/add-wire-remote-addon.ts +0 -80
  81. package/src/add/add-workflow-edge-cases.test.ts +0 -201
  82. package/src/add/add-workflow-fanout.test.ts +0 -166
  83. package/src/add/add-workflow-graph-foreach.test.ts +0 -174
  84. package/src/add/add-workflow-graph-input.test.ts +0 -94
  85. package/src/add/add-workflow-graph-notes.test.ts +0 -74
  86. package/src/add/add-workflow-graph.ts +0 -644
  87. package/src/add/add-workflow-on-error.test.ts +0 -118
  88. package/src/add/add-workflow-return-switch.test.ts +0 -121
  89. package/src/add/add-workflow-silent-drops.test.ts +0 -261
  90. package/src/add/add-workflow-sleep-expression.test.ts +0 -96
  91. package/src/add/add-workflow.test.ts +0 -155
  92. package/src/add/add-workflow.ts +0 -482
  93. package/src/add/addon-bans.test.ts +0 -121
  94. package/src/add/addon-contracts.test.ts +0 -221
  95. package/src/add/allow-escape-hatches.test.ts +0 -144
  96. package/src/add/audit-meta.test.ts +0 -75
  97. package/src/add/classified-sink-check.test.ts +0 -347
  98. package/src/add/config-value-metadata.test.ts +0 -263
  99. package/src/add/dynamic-import-check.test.ts +0 -119
  100. package/src/add/expect-eventually-scenario-only.test.ts +0 -108
  101. package/src/add/function-body-span.test.ts +0 -96
  102. package/src/add/inline-wiring-function.test.ts +0 -104
  103. package/src/add/pii-check.test.ts +0 -258
  104. package/src/add/rpc-type-cast.test.ts +0 -123
  105. package/src/add/scenario-instrumentation.test.ts +0 -89
  106. package/src/add/scenario-instrumentation.ts +0 -42
  107. package/src/add/scenario-marker.test.ts +0 -89
  108. package/src/add/scenario-not-extractable.test.ts +0 -159
  109. package/src/add/scenario-services.test.ts +0 -103
  110. package/src/add/scenario-skip.test.ts +0 -83
  111. package/src/add/validate-workflow-graph-addons.test.ts +0 -102
  112. package/src/add/wire-name-literal.test.ts +0 -117
  113. package/src/error-codes-emitted.test.ts +0 -76
  114. package/src/error-codes.ts +0 -145
  115. package/src/index.ts +0 -66
  116. package/src/inspector.ts +0 -575
  117. package/src/schema-generator.ts +0 -1
  118. package/src/source-file-cache.ts +0 -73
  119. package/src/tsconfig-paths.test.ts +0 -90
  120. package/src/types-map.ts +0 -141
  121. package/src/types.ts +0 -775
  122. package/src/utils/annotate-http-route-auth.test.ts +0 -161
  123. package/src/utils/annotate-http-route-auth.ts +0 -53
  124. package/src/utils/check-classified-sinks.ts +0 -215
  125. package/src/utils/check-pii-output.ts +0 -120
  126. package/src/utils/compute-diagnostics.test.ts +0 -106
  127. package/src/utils/compute-required-schemas.ts +0 -48
  128. package/src/utils/contract-hashes.test.ts +0 -553
  129. package/src/utils/contract-hashes.ts +0 -387
  130. package/src/utils/custom-types-generator.test.ts +0 -124
  131. package/src/utils/custom-types-generator.ts +0 -150
  132. package/src/utils/detect-schema-vendor.test.ts +0 -123
  133. package/src/utils/detect-schema-vendor.ts +0 -120
  134. package/src/utils/does-type-extend-core-type.ts +0 -53
  135. package/src/utils/ensure-function-metadata.ts +0 -385
  136. package/src/utils/extract-function-name.ts +0 -612
  137. package/src/utils/extract-function-node.ts +0 -70
  138. package/src/utils/extract-node-value.test.ts +0 -117
  139. package/src/utils/extract-node-value.ts +0 -172
  140. package/src/utils/extract-secret-usage.test.ts +0 -81
  141. package/src/utils/extract-secret-usage.ts +0 -73
  142. package/src/utils/extract-services.ts +0 -70
  143. package/src/utils/extract-surface-usage.test.ts +0 -147
  144. package/src/utils/extract-surface-usage.ts +0 -121
  145. package/src/utils/filter-inspector-state.test.ts +0 -2232
  146. package/src/utils/filter-inspector-state.ts +0 -1298
  147. package/src/utils/filter-utils.test.ts +0 -843
  148. package/src/utils/filter-utils.ts +0 -161
  149. package/src/utils/find-root-dir.ts +0 -68
  150. package/src/utils/find-zod-transform.test.ts +0 -70
  151. package/src/utils/find-zod-transform.ts +0 -103
  152. package/src/utils/get-exported-variable-name.ts +0 -48
  153. package/src/utils/get-files-and-methods.ts +0 -157
  154. package/src/utils/get-property-value.test.ts +0 -141
  155. package/src/utils/get-property-value.ts +0 -331
  156. package/src/utils/hash.ts +0 -26
  157. package/src/utils/literal-properties.ts +0 -97
  158. package/src/utils/load-addon-functions-meta.test.ts +0 -157
  159. package/src/utils/load-addon-functions-meta.ts +0 -508
  160. package/src/utils/load-addon-scopes.test.ts +0 -138
  161. package/src/utils/middleware.test.ts +0 -204
  162. package/src/utils/middleware.ts +0 -303
  163. package/src/utils/nested-project-filter.test.ts +0 -53
  164. package/src/utils/nested-project-filter.ts +0 -28
  165. package/src/utils/permissions-shorthand.test.ts +0 -79
  166. package/src/utils/permissions.test.ts +0 -125
  167. package/src/utils/permissions.ts +0 -129
  168. package/src/utils/post-process.test.ts +0 -1094
  169. package/src/utils/post-process.ts +0 -1604
  170. package/src/utils/resolve-addon-package.ts +0 -106
  171. package/src/utils/resolve-core-type.test.ts +0 -142
  172. package/src/utils/resolve-core-type.ts +0 -35
  173. package/src/utils/resolve-deploy-target.test.ts +0 -135
  174. package/src/utils/resolve-deploy-target.ts +0 -65
  175. package/src/utils/resolve-function-meta.ts +0 -25
  176. package/src/utils/resolve-function-types.ts +0 -42
  177. package/src/utils/resolve-identifier.ts +0 -46
  178. package/src/utils/resolve-ref-contract.ts +0 -71
  179. package/src/utils/resolve-versions.test.ts +0 -499
  180. package/src/utils/resolve-versions.ts +0 -228
  181. package/src/utils/schema-cache.test.ts +0 -124
  182. package/src/utils/schema-generator.test.ts +0 -272
  183. package/src/utils/schema-generator.ts +0 -780
  184. package/src/utils/secret-alias-services.test.ts +0 -146
  185. package/src/utils/secret-alias-services.ts +0 -58
  186. package/src/utils/serialize-inspector-state.ts +0 -742
  187. package/src/utils/serialize-mcp-json.ts +0 -150
  188. package/src/utils/serialize-middleware-groups-meta.ts +0 -36
  189. package/src/utils/serialize-openapi-json.ts +0 -277
  190. package/src/utils/serialize-permissions-groups-meta.ts +0 -7
  191. package/src/utils/single-declaration.ts +0 -57
  192. package/src/utils/type-utils.ts +0 -145
  193. package/src/utils/validate-auth-sessionless.ts +0 -30
  194. package/src/utils/validate-exposed-functions-gated.test.ts +0 -230
  195. package/src/utils/validate-exposed-functions-gated.ts +0 -103
  196. package/src/utils/validate-tags-resolve-to-middleware.test.ts +0 -108
  197. package/src/utils/validate-tags-resolve-to-middleware.ts +0 -48
  198. package/src/utils/workflow/derive-workflow-plan.test.ts +0 -178
  199. package/src/utils/workflow/derive-workflow-plan.ts +0 -117
  200. package/src/utils/workflow/dsl/deserialize-dsl-workflow.ts +0 -1465
  201. package/src/utils/workflow/dsl/deserialize-fanout-body.test.ts +0 -71
  202. package/src/utils/workflow/dsl/deserialize-fanout-fidelity.test.ts +0 -99
  203. package/src/utils/workflow/dsl/deserialize-graph-next.test.ts +0 -161
  204. package/src/utils/workflow/dsl/deserialize-roundtrip-bindings.test.ts +0 -121
  205. package/src/utils/workflow/dsl/deserialize-roundtrip-values.test.ts +0 -116
  206. package/src/utils/workflow/dsl/deserialize-roundtrip.test.ts +0 -145
  207. package/src/utils/workflow/dsl/extract-approval-policy.test.ts +0 -71
  208. package/src/utils/workflow/dsl/extract-dsl-workflow.ts +0 -2254
  209. package/src/utils/workflow/dsl/index.ts +0 -11
  210. package/src/utils/workflow/dsl/patterns.ts +0 -440
  211. package/src/utils/workflow/dsl/validation.ts +0 -203
  212. package/src/utils/workflow/graph/convert-dsl-to-graph-skip.test.ts +0 -35
  213. package/src/utils/workflow/graph/convert-dsl-to-graph.ts +0 -543
  214. package/src/utils/workflow/graph/fanout-node-identity.test.ts +0 -105
  215. package/src/utils/workflow/graph/finalize-workflow-wires.ts +0 -310
  216. package/src/utils/workflow/graph/finalize-workflows.test.ts +0 -50
  217. package/src/utils/workflow/graph/finalize-workflows.ts +0 -130
  218. package/src/utils/workflow/graph/index.ts +0 -28
  219. package/src/utils/workflow/graph/serialize-workflow-graph.ts +0 -240
  220. package/src/utils/workflow/graph/workflow-graph.types.ts +0 -281
  221. package/src/visit.ts +0 -181
  222. package/tsconfig.json +0 -19
  223. package/tsconfig.tsbuildinfo +0 -1
@@ -1,780 +0,0 @@
1
- import * as ts from 'typescript'
2
- import { createHash } from 'crypto'
3
- import {
4
- existsSync,
5
- mkdirSync,
6
- readFileSync,
7
- statSync,
8
- writeFileSync,
9
- } from 'fs'
10
- import { dirname, join, resolve } from 'path'
11
- import { createGenerator, RootlessError } from 'ts-json-schema-generator'
12
- import { register } from 'tsx/esm/api'
13
- import * as z from 'zod'
14
- import { zodToTs, createAuxiliaryTypeStore } from 'zod-to-ts'
15
- import { findZodTransform } from './find-zod-transform.js'
16
- import type { FunctionsMeta } from '@pikku/core/services'
17
- import type { JSONValue } from '@pikku/core/utils'
18
- import type { HTTPWiringsMeta } from '@pikku/core/http'
19
- import type { TypesMap } from '../types-map.js'
20
- import { ErrorCode } from '../error-codes.js'
21
- import type { InspectorLogger, InspectorState, SchemaRef } from '../types.js'
22
- import { generateCustomTypes } from './custom-types-generator.js'
23
-
24
- const PRIMITIVE_TYPES = new Set([
25
- 'boolean',
26
- 'string',
27
- 'number',
28
- 'null',
29
- 'undefined',
30
- 'void',
31
- 'any',
32
- 'unknown',
33
- 'never',
34
- ])
35
-
36
- function primitiveTypeToSchema(typeStr: string): JSONValue | null {
37
- const normalized = typeStr.trim()
38
-
39
- if (
40
- normalized === 'void' ||
41
- normalized === 'undefined' ||
42
- normalized === 'never'
43
- ) {
44
- return null
45
- }
46
-
47
- if (
48
- normalized === 'boolean' ||
49
- normalized === 'false | true' ||
50
- normalized === 'true | false'
51
- ) {
52
- return { type: 'boolean' }
53
- }
54
- if (normalized === 'true') {
55
- return { const: true }
56
- }
57
- if (normalized === 'false') {
58
- return { const: false }
59
- }
60
-
61
- if (normalized === 'string') {
62
- return { type: 'string' }
63
- }
64
-
65
- if (normalized === 'number') {
66
- return { type: 'number' }
67
- }
68
-
69
- if (normalized === 'null') {
70
- return { type: 'null' }
71
- }
72
-
73
- if (normalized === 'any' || normalized === 'unknown') {
74
- return {}
75
- }
76
-
77
- return null
78
- }
79
-
80
- // Cached state for schema program reuse across inspect() calls
81
- let cachedSchemaProgram: ts.Program | undefined
82
- let cachedParsedConfig: ts.ParsedCommandLine | undefined
83
- let cachedTsconfigPath: string | undefined
84
- let cachedCustomTypesContent: string | undefined
85
- let cachedTSSchemas: Record<string, JSONValue> | undefined
86
- let cachedTSSchemaDeps: SchemaDep[] | undefined
87
-
88
- const SCHEMA_CACHE_VERSION = 1
89
-
90
- // This package's own version — folded into the cache key so that upgrading
91
- // @pikku/inspector (the channel through which a schema-format change ships)
92
- // auto-invalidates every on-disk cache, without relying on someone remembering
93
- // to bump SCHEMA_CACHE_VERSION. Read once; falls back to the constant if the
94
- // package.json can't be located (e.g. an unexpected bundling layout).
95
- const inspectorVersion: string = (() => {
96
- try {
97
- const pkgUrl = new URL('../../package.json', import.meta.url)
98
- const pkg = JSON.parse(readFileSync(pkgUrl, 'utf-8'))
99
- return typeof pkg.version === 'string'
100
- ? pkg.version
101
- : `v${SCHEMA_CACHE_VERSION}`
102
- } catch {
103
- return `v${SCHEMA_CACHE_VERSION}`
104
- }
105
- })()
106
-
107
- // Key the TS-schema cache on everything that affects its output: the generated
108
- // custom-types source, the generator options that change schema shape, and the
109
- // inspector version (schema-format changes ship with a version bump).
110
- function tsSchemaCacheKey(
111
- customTypesContent: string,
112
- config: {
113
- schemasFromTypes?: string[]
114
- schema?: { additionalProperties?: boolean }
115
- }
116
- ): string {
117
- return createHash('sha1')
118
- .update(`v${SCHEMA_CACHE_VERSION}\0`)
119
- .update(`pkg:${inspectorVersion}\0`)
120
- .update(`ap:${config.schema?.additionalProperties ? 1 : 0}\0`)
121
- .update(`ft:${(config.schemasFromTypes ?? []).join(',')}\0`)
122
- .update(customTypesContent)
123
- .digest('hex')
124
- }
125
-
126
- function schemaCacheFile(cacheDir: string): string {
127
- return join(cacheDir, 'ts-schemas.json')
128
- }
129
-
130
- /** One file the generated schemas were derived from, as of when they were generated. */
131
- export type SchemaDep = { path: string; mtimeMs: number; size: number }
132
-
133
- /**
134
- * The files a set of generated schemas was actually derived from.
135
- *
136
- * The cache key alone cannot answer whether the schemas are stale. It hashes the
137
- * *synthesized* custom-types source, but a named type — `KnowledgeBundle`,
138
- * `DbSchema`, anything a function declares as its input or output — appears
139
- * there only as a name; its definition is resolved out of a project file or a
140
- * dependency's `.d.ts`. Edit one of those and the key is unchanged, so a stale
141
- * schema is served for a type that no longer looks like that. The failure is
142
- * silent and outlives `rm -rf .pikku`, because this cache lives in
143
- * `node_modules/.cache`.
144
- *
145
- * The program is rooted at the virtual file, so its source files are exactly the
146
- * transitive closure the schemas depend on — nothing wider, nothing missed.
147
- * Default-lib files are skipped: they change only when TypeScript itself does,
148
- * which the inspector version in the key already covers.
149
- */
150
- function fingerprintSchemaDeps(program: ts.Program): SchemaDep[] {
151
- const deps: SchemaDep[] = []
152
- for (const sourceFile of program.getSourceFiles()) {
153
- if (program.isSourceFileDefaultLibrary(sourceFile)) continue
154
- try {
155
- const stat = statSync(sourceFile.fileName)
156
- deps.push({
157
- path: sourceFile.fileName,
158
- mtimeMs: stat.mtimeMs,
159
- size: stat.size,
160
- })
161
- } catch {
162
- // The virtual root has no file on disk. Its content is the custom-types
163
- // source, which the cache key already hashes.
164
- }
165
- }
166
- return deps
167
- }
168
-
169
- /**
170
- * Whether every file the schemas were derived from is still exactly as it was.
171
- *
172
- * mtime-and-size rather than a content hash: this runs on the cache-hit path,
173
- * where the whole point is to answer in milliseconds without building a TS
174
- * program. Anything that rewrites a file — a dependency rebuild, a checkout, an
175
- * edit — moves its mtime, and a missing file reads as changed, so the answer errs
176
- * toward regenerating.
177
- */
178
- function schemaDepsUnchanged(deps: unknown): boolean {
179
- if (!Array.isArray(deps)) return false
180
- for (const dep of deps as SchemaDep[]) {
181
- if (!dep || typeof dep.path !== 'string') return false
182
- try {
183
- const stat = statSync(dep.path)
184
- if (stat.mtimeMs !== dep.mtimeMs || stat.size !== dep.size) return false
185
- } catch {
186
- return false
187
- }
188
- }
189
- return true
190
- }
191
-
192
- export function readDiskTSSchemas(
193
- logger: InspectorLogger,
194
- cacheDir: string,
195
- key: string
196
- ): { schemas: Record<string, JSONValue>; deps: SchemaDep[] } | null {
197
- const file = schemaCacheFile(cacheDir)
198
- if (!existsSync(file)) return null
199
- try {
200
- const parsed = JSON.parse(readFileSync(file, 'utf-8'))
201
- if (parsed?.key !== key || !parsed.schemas) return null
202
- if (!schemaDepsUnchanged(parsed.deps)) {
203
- logger.debug(
204
- 'Discarding TS-schema cache: a type it was built from changed'
205
- )
206
- return null
207
- }
208
- return { schemas: parsed.schemas, deps: parsed.deps }
209
- } catch (e) {
210
- logger.debug(`Ignoring unreadable TS-schema cache: ${(e as Error).message}`)
211
- }
212
- return null
213
- }
214
-
215
- export function writeDiskTSSchemas(
216
- logger: InspectorLogger,
217
- cacheDir: string,
218
- key: string,
219
- schemas: Record<string, JSONValue>,
220
- deps: SchemaDep[]
221
- ): void {
222
- const file = schemaCacheFile(cacheDir)
223
- try {
224
- mkdirSync(dirname(file), { recursive: true })
225
- writeFileSync(file, JSON.stringify({ key, schemas, deps }))
226
- } catch (e) {
227
- logger.debug(`Failed to persist TS-schema cache: ${(e as Error).message}`)
228
- }
229
- }
230
-
231
- function createProgramWithVirtualFile(
232
- logger: InspectorLogger,
233
- tsconfig: string,
234
- virtualFilePath: string,
235
- virtualFileContent: string
236
- ): ts.Program {
237
- const configPath = resolve(tsconfig)
238
-
239
- // Cache the parsed tsconfig — it doesn't change between runs
240
- if (!cachedParsedConfig || cachedTsconfigPath !== configPath) {
241
- const configFile = ts.readConfigFile(configPath, ts.sys.readFile)
242
- const basePath = dirname(configPath)
243
- cachedParsedConfig = ts.parseJsonConfigFileContent(
244
- configFile.config,
245
- ts.sys,
246
- basePath
247
- )
248
- cachedTsconfigPath = configPath
249
- cachedSchemaProgram = undefined
250
- }
251
-
252
- const resolvedVirtualPath = resolve(virtualFilePath)
253
- // The virtual file imports every type it references, so it is a complete root
254
- // on its own and TypeScript pulls in exactly the transitive closure it needs.
255
- // Rooting at cachedParsedConfig.fileNames instead loaded the whole project
256
- // (2572 files vs 870 on a real tree) without widening what could be resolved.
257
- const fileNames = [resolvedVirtualPath]
258
-
259
- const defaultHost = ts.createCompilerHost(cachedParsedConfig.options)
260
- const customHost: ts.CompilerHost = {
261
- ...defaultHost,
262
- getSourceFile(
263
- fileName,
264
- languageVersionOrOptions,
265
- onError,
266
- shouldCreateNewSourceFile
267
- ) {
268
- if (resolve(fileName) === resolvedVirtualPath) {
269
- return ts.createSourceFile(
270
- fileName,
271
- virtualFileContent,
272
- languageVersionOrOptions
273
- )
274
- }
275
- return defaultHost.getSourceFile(
276
- fileName,
277
- languageVersionOrOptions,
278
- onError,
279
- shouldCreateNewSourceFile
280
- )
281
- },
282
- fileExists(fileName) {
283
- if (resolve(fileName) === resolvedVirtualPath) return true
284
- return defaultHost.fileExists(fileName)
285
- },
286
- readFile(fileName) {
287
- if (resolve(fileName) === resolvedVirtualPath) return virtualFileContent
288
- return defaultHost.readFile(fileName)
289
- },
290
- }
291
-
292
- const startProgram = performance.now()
293
- const program = ts.createProgram(
294
- fileNames,
295
- cachedParsedConfig.options,
296
- customHost,
297
- cachedSchemaProgram // reuse previous program for incremental compilation
298
- )
299
- cachedSchemaProgram = program
300
- logger.debug(
301
- `Created schema program in ${(performance.now() - startProgram).toFixed(0)}ms (${program.getSourceFiles().length} files)`
302
- )
303
- return program
304
- }
305
-
306
- function generateTSSchemas(
307
- logger: InspectorLogger,
308
- tsconfig: string,
309
- customTypesContent: string,
310
- typesMap: TypesMap,
311
- functionMeta: FunctionsMeta,
312
- httpWiringsMeta: HTTPWiringsMeta,
313
- additionalTypes?: string[],
314
- additionalProperties: boolean = false,
315
- generatedZodSchemas?: Record<string, JSONValue>
316
- ): { schemas: Record<string, JSONValue>; deps: SchemaDep[] } {
317
- const schemasSet = new Set(typesMap.customTypes.keys())
318
- for (const { inputs, outputs } of Object.values(functionMeta)) {
319
- const types = [...(inputs || []), ...(outputs || [])]
320
- for (const type of types) {
321
- try {
322
- const uniqueName = typesMap.getUniqueName(type)
323
- if (uniqueName) {
324
- schemasSet.add(uniqueName)
325
- }
326
- } catch {
327
- // Skip types not in typesMap (e.g., inline types in generated workflow workers)
328
- }
329
- }
330
- }
331
-
332
- for (const wiringRoutes of Object.values(httpWiringsMeta)) {
333
- for (const { inputTypes } of Object.values(wiringRoutes)) {
334
- if (inputTypes?.body) {
335
- schemasSet.add(inputTypes.body)
336
- }
337
- if (inputTypes?.query) {
338
- schemasSet.add(inputTypes.query)
339
- }
340
- if (inputTypes?.params) {
341
- schemasSet.add(inputTypes.params)
342
- }
343
- }
344
- }
345
-
346
- if (additionalTypes) {
347
- for (const type of additionalTypes) {
348
- schemasSet.add(type)
349
- }
350
- }
351
-
352
- // Skip ts-json-schema-generator if all schemas are already covered by Zod/primitives.
353
- // Use generatedZodSchemas (actually converted) rather than schemaLookup (all attempted)
354
- // so that failed Zod conversions fall through to TS schema generation.
355
- const uncoveredSchemas = [...schemasSet].filter(
356
- (s) => !PRIMITIVE_TYPES.has(s) && !generatedZodSchemas?.[s]
357
- )
358
- if (uncoveredSchemas.length === 0) {
359
- return { schemas: {}, deps: [] }
360
- }
361
- logger.debug(
362
- `generateTSSchemas needed for ${uncoveredSchemas.length} types: ${uncoveredSchemas.slice(0, 3).join(', ')}${uncoveredSchemas.length > 3 ? '...' : ''}`
363
- )
364
-
365
- const virtualFilePath = join(
366
- dirname(resolve(tsconfig)),
367
- '__pikku_virtual_types__.ts'
368
- )
369
- const program = createProgramWithVirtualFile(
370
- logger,
371
- tsconfig,
372
- virtualFilePath,
373
- customTypesContent
374
- )
375
-
376
- const generator = createGenerator({
377
- tsProgram: program,
378
- skipTypeCheck: true,
379
- topRef: false,
380
- discriminatorType: 'open-api',
381
- expose: 'export',
382
- jsDoc: 'extended',
383
- sortProps: true,
384
- strictTuples: false,
385
- encodeRefs: false,
386
- additionalProperties,
387
- })
388
- const schemas: Record<string, JSONValue> = {}
389
-
390
- schemasSet.forEach((schema) => {
391
- if (PRIMITIVE_TYPES.has(schema)) {
392
- return
393
- }
394
- if (generatedZodSchemas?.[schema]) {
395
- return
396
- }
397
- try {
398
- schemas[schema] = generator.createSchema(schema) as JSONValue
399
- } catch (e) {
400
- if (e instanceof RootlessError) {
401
- const customType = typesMap.customTypes.get(schema)
402
- if (customType) {
403
- const primitiveSchema = primitiveTypeToSchema(customType.type)
404
- if (primitiveSchema) {
405
- schemas[schema] = primitiveSchema
406
- }
407
- }
408
- return
409
- }
410
- const customType = typesMap.customTypes.get(schema)
411
- logger.error(
412
- `[${ErrorCode.SCHEMA_GENERATION_ERROR}] Error generating schema: ${schema}. Message: ${(e as Error).message}. Type info: ${customType ? `type=${customType.type}` : 'not in typesMap'}`
413
- )
414
- }
415
- })
416
-
417
- // Fingerprinted here, while the program is still alive — it is released as soon
418
- // as the schemas are out of it.
419
- return { schemas, deps: fingerprintSchemaDeps(program) }
420
- }
421
-
422
- /**
423
- * Where the RUNTIME value of a schema lives, given where TypeScript resolved it.
424
- *
425
- * A schema imported from a built workspace package resolves to that package's
426
- * declaration file, and importing a `.d.ts` yields a module with no exports at
427
- * all — every one of them is a type. The value is in the emitted JS beside it, so
428
- * that is what gets imported.
429
- *
430
- * Falls back to the original path when no sibling JS exists, which keeps the
431
- * "could not find exported schema" error pointing at the file the developer
432
- * actually wrote.
433
- */
434
- export const schemaRuntimeFile = (sourceFile: string): string => {
435
- const match = /\.d\.([cm]?)ts$/.exec(sourceFile)
436
- if (!match) return sourceFile
437
- const emitted = sourceFile.replace(/\.d\.([cm]?)ts$/, '.$1js')
438
- return existsSync(emitted) ? emitted : sourceFile
439
- }
440
-
441
- /**
442
- * Import all source files in parallel using tsx's register() API.
443
- *
444
- * tsx's register() sets up the TypeScript loader once, then all subsequent
445
- * import() calls reuse that loader. This is dramatically faster than calling
446
- * tsImport() per-file because tsImport() sets up and tears down a fresh
447
- * compilation context for each call (~170ms each).
448
- *
449
- * With register() + parallel import():
450
- * - 71 files: ~350ms total
451
- * - vs tsImport loop: ~12,000ms (71 * 170ms)
452
- *
453
- * Falls back to serial tsImport() per-file if register() is unavailable.
454
- */
455
- async function batchImportWithRegister(
456
- logger: InspectorLogger,
457
- sourceFiles: string[]
458
- ): Promise<Map<string, Record<string, any>> | null> {
459
- if (sourceFiles.length === 0) return new Map()
460
-
461
- let unregister: (() => void | Promise<void>) | undefined
462
- try {
463
- unregister = register()
464
-
465
- const modules = new Map<string, Record<string, any>>()
466
- const results = await Promise.allSettled(
467
- sourceFiles.map(async (srcPath) => {
468
- const mod = await import(srcPath)
469
- modules.set(srcPath, mod)
470
- })
471
- )
472
-
473
- const failures = results.filter((r) => r.status === 'rejected')
474
- if (failures.length > 0) {
475
- logger.debug(
476
- `${failures.length}/${sourceFiles.length} files failed to import via register()`
477
- )
478
- }
479
-
480
- return modules
481
- } catch (e) {
482
- logger.debug(`tsx register() batch import failed: ${(e as Error).message}`)
483
- return null
484
- } finally {
485
- void Promise.resolve(unregister?.()).catch((e) => {
486
- logger.debug(`tsx unregister() failed: ${(e as Error).message}`)
487
- })
488
- }
489
- }
490
-
491
- async function importWithRegister(
492
- sourceFile: string
493
- ): Promise<Record<string, any>> {
494
- const unregister = register()
495
- try {
496
- return await import(sourceFile)
497
- } finally {
498
- void Promise.resolve(unregister()).catch(() => {})
499
- }
500
- }
501
-
502
- function processZodSchema(
503
- schemaName: string,
504
- zodSchema: any,
505
- schemas: Record<string, JSONValue>,
506
- typesMap: TypesMap,
507
- auxiliaryTypeStore: ReturnType<typeof createAuxiliaryTypeStore>,
508
- printer: ts.Printer,
509
- fakeSourceFile: ts.SourceFile,
510
- logger: InspectorLogger
511
- ): void {
512
- const transformPath = findZodTransform(zodSchema)
513
- if (transformPath !== undefined) {
514
- const where = transformPath === '' ? 'the schema itself' : transformPath
515
- throw new Error(
516
- `[${ErrorCode.SCHEMA_TRANSFORM_NOT_SUPPORTED}] '${schemaName}' contains a .transform() at ${where}.\n` +
517
- 'A transform makes the value sent and the value received two different ' +
518
- 'shapes, and a wire contract describes one: it cannot be expressed in ' +
519
- 'JSON Schema or in the generated TypeScript.\n' +
520
- 'Validate with the plain schema and apply the transform inside the ' +
521
- 'function, or move it to the output type.'
522
- )
523
- }
524
-
525
- const schema = z.toJSONSchema(zodSchema, {
526
- unrepresentable: 'any',
527
- override: ({ zodSchema, jsonSchema }) => {
528
- if ((zodSchema as any)._zod?.def?.type === 'date') {
529
- ;(jsonSchema as any).type = 'string'
530
- ;(jsonSchema as any).format = 'date-time'
531
- }
532
- },
533
- }) as any
534
-
535
- if (schema.required && schema.properties) {
536
- schema.required = schema.required.filter((fieldName: string) => {
537
- const prop = schema.properties[fieldName]
538
- return prop && prop.default === undefined
539
- })
540
- if (schema.required.length === 0) {
541
- delete schema.required
542
- }
543
- }
544
-
545
- const { node: tsType } = zodToTs(zodSchema, { auxiliaryTypeStore })
546
-
547
- const typeText = printer.printNode(
548
- ts.EmitHint.Unspecified,
549
- tsType,
550
- fakeSourceFile
551
- )
552
-
553
- typesMap.addCustomType(schemaName, typeText, [])
554
- schemas[schemaName] = schema
555
- logger.debug(`• Generated schema from Zod: ${schemaName}`)
556
- }
557
-
558
- async function generateZodSchemas(
559
- logger: InspectorLogger,
560
- schemaLookup: Map<string, SchemaRef>,
561
- typesMap: TypesMap
562
- ): Promise<Record<string, JSONValue>> {
563
- const schemas: Record<string, JSONValue> = {}
564
- const errors: string[] = []
565
- const auxiliaryTypeStore = createAuxiliaryTypeStore()
566
- const printer = ts.createPrinter()
567
- const fakeSourceFile = ts.createSourceFile(
568
- 'zod-types.ts',
569
- '',
570
- ts.ScriptTarget.ESNext,
571
- false,
572
- ts.ScriptKind.TS
573
- )
574
-
575
- // Validate all schemas are zod (or unspecified vendor)
576
- for (const [schemaName, ref] of schemaLookup.entries()) {
577
- if (ref.vendor && ref.vendor !== 'zod') {
578
- throw new Error(
579
- `Schema '${schemaName}' uses ${ref.vendor} which is not yet supported for JSON Schema generation. ` +
580
- `Currently only Zod schemas can be converted to JSON Schema. ` +
581
- `Please use Zod or contribute support for ${ref.vendor}.`
582
- )
583
- }
584
- }
585
-
586
- // Collect unique source files and batch-import them in parallel
587
- const uniqueSourceFiles = [
588
- ...new Set(
589
- [...schemaLookup.values()].map((ref) => schemaRuntimeFile(ref.sourceFile))
590
- ),
591
- ]
592
- logger.debug(
593
- `[TIMING] Zod schemas: ${schemaLookup.size} schemas from ${uniqueSourceFiles.length} files`
594
- )
595
-
596
- const importStart = performance.now()
597
- const importedModules = await batchImportWithRegister(
598
- logger,
599
- uniqueSourceFiles
600
- )
601
- logger.debug(
602
- `[TIMING] Batch import: ${(performance.now() - importStart).toFixed(0)}ms`
603
- )
604
-
605
- const processStart = performance.now()
606
- // Track schemas that need per-file tsImport fallback
607
- const fallbackSchemas: [string, SchemaRef][] = []
608
-
609
- for (const [schemaName, ref] of schemaLookup.entries()) {
610
- const runtimeFile = schemaRuntimeFile(ref.sourceFile)
611
- const mod = importedModules?.get(runtimeFile)
612
- if (mod) {
613
- const zodSchema = mod[ref.variableName]
614
- if (!zodSchema) {
615
- errors.push(
616
- `Could not find exported schema '${ref.variableName}' in ${runtimeFile} for ${schemaName}. Available exports: ${Object.keys(mod).join(', ')}`
617
- )
618
- continue
619
- }
620
- try {
621
- processZodSchema(
622
- schemaName,
623
- zodSchema,
624
- schemas,
625
- typesMap,
626
- auxiliaryTypeStore,
627
- printer,
628
- fakeSourceFile,
629
- logger
630
- )
631
- } catch (e) {
632
- errors.push(
633
- `Could not convert Zod schema '${schemaName}': ${e instanceof Error ? e.message : e}`
634
- )
635
- }
636
- } else {
637
- fallbackSchemas.push([schemaName, ref])
638
- }
639
- }
640
-
641
- // Fallback: use a scoped tsx register/import cycle for any schemas that
642
- // batch import couldn't handle. Avoid tsImport() here because its ESM path
643
- // can leave loader plumbing alive after failed imports, which prevents the
644
- // CLI process from exiting on schema errors.
645
- if (fallbackSchemas.length > 0) {
646
- logger.debug(
647
- `Falling back to register() import for ${fallbackSchemas.length} schema(s)`
648
- )
649
- for (const [schemaName, ref] of fallbackSchemas) {
650
- const runtimeFile = schemaRuntimeFile(ref.sourceFile)
651
- try {
652
- const module = await importWithRegister(runtimeFile)
653
- const zodSchema = module[ref.variableName]
654
- if (!zodSchema) {
655
- errors.push(
656
- `Could not find exported schema '${ref.variableName}' in ${runtimeFile} for ${schemaName}. Available exports: ${Object.keys(module).join(', ')}`
657
- )
658
- continue
659
- }
660
- processZodSchema(
661
- schemaName,
662
- zodSchema,
663
- schemas,
664
- typesMap,
665
- auxiliaryTypeStore,
666
- printer,
667
- fakeSourceFile,
668
- logger
669
- )
670
- } catch (e) {
671
- errors.push(
672
- `Could not convert Zod schema '${schemaName}': ${e instanceof Error ? e.message : e}`
673
- )
674
- }
675
- }
676
- }
677
-
678
- if (errors.length > 0) {
679
- for (const message of errors) {
680
- logger.error(message)
681
- }
682
- throw new Error(
683
- `Schema generation failed for ${errors.length} schema${errors.length === 1 ? '' : 's'}`
684
- )
685
- }
686
-
687
- logger.debug(
688
- `[TIMING] Process schemas: ${(performance.now() - processStart).toFixed(0)}ms (${Object.keys(schemas).length} generated)`
689
- )
690
- return schemas
691
- }
692
-
693
- export async function generateAllSchemas(
694
- logger: InspectorLogger,
695
- config: {
696
- tsconfig: string
697
- schemasFromTypes?: string[]
698
- schema?: { additionalProperties?: boolean }
699
- cacheDir?: string
700
- },
701
- state: InspectorState
702
- ): Promise<Record<string, JSONValue>> {
703
- const zodSchemas = await generateZodSchemas(
704
- logger,
705
- state.schemaLookup,
706
- state.functions.typesMap
707
- )
708
-
709
- const requiredTypes = new Set<string>()
710
- const customTypesContent = generateCustomTypes(
711
- state.functions.typesMap,
712
- requiredTypes
713
- )
714
-
715
- // Fast path: same process, types unchanged — reuse the in-memory result. The
716
- // dep check is what makes this correct under `pikku dev`, where the process
717
- // outlives the edits: the custom-types source is identical whenever a named
718
- // type changes shape without changing its name.
719
- if (
720
- cachedTSSchemas &&
721
- cachedCustomTypesContent === customTypesContent &&
722
- schemaDepsUnchanged(cachedTSSchemaDeps)
723
- ) {
724
- logger.debug('Reusing cached TS schemas (types unchanged)')
725
- return { ...cachedTSSchemas, ...zodSchemas }
726
- }
727
-
728
- // Disk path: a prior `pikku all` left a cache whose key matches the current
729
- // custom types — load it and skip ts-json-schema-generator (the dominant
730
- // cold-run cost). Zod schemas are always regenerated (cheap, ~1ms/schema).
731
- const cacheKey = config.cacheDir
732
- ? tsSchemaCacheKey(customTypesContent, config)
733
- : null
734
- if (config.cacheDir && cacheKey) {
735
- const disk = readDiskTSSchemas(logger, config.cacheDir, cacheKey)
736
- if (disk) {
737
- logger.debug('Reusing on-disk TS schemas (types unchanged across runs)')
738
- cachedCustomTypesContent = customTypesContent
739
- cachedTSSchemas = disk.schemas
740
- cachedTSSchemaDeps = disk.deps
741
- return { ...disk.schemas, ...zodSchemas }
742
- }
743
- }
744
-
745
- const { schemas: tsSchemas, deps: tsSchemaDeps } = generateTSSchemas(
746
- logger,
747
- config.tsconfig,
748
- customTypesContent,
749
- state.functions.typesMap,
750
- state.functions.meta,
751
- state.http.meta,
752
- config.schemasFromTypes,
753
- config.schema?.additionalProperties,
754
- zodSchemas
755
- )
756
-
757
- // Release the program once the schemas are out of it. It spans the whole
758
- // tsconfig (~2.5k files, ~600MB on a large tree) and was previously pinned at
759
- // module scope for the life of the process, so it stacked on top of the main
760
- // inspector program and pushed `pikku all` past a 2GB heap (#982). The result
761
- // cache below is what the same-process fast path actually needs; the program
762
- // only bought incremental reuse on a rebuild, at a cost that dwarfs it.
763
- cachedSchemaProgram = undefined
764
-
765
- cachedCustomTypesContent = customTypesContent
766
- cachedTSSchemas = tsSchemas
767
- cachedTSSchemaDeps = tsSchemaDeps
768
-
769
- if (config.cacheDir && cacheKey) {
770
- writeDiskTSSchemas(
771
- logger,
772
- config.cacheDir,
773
- cacheKey,
774
- tsSchemas,
775
- tsSchemaDeps
776
- )
777
- }
778
-
779
- return { ...tsSchemas, ...zodSchemas }
780
- }