@pikku/inspector 0.12.75 → 0.12.81
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +330 -0
- package/dist/add/add-analytics.d.ts +9 -0
- package/dist/add/add-analytics.js +178 -0
- package/dist/add/add-auth.js +0 -54
- package/dist/add/add-feature-flag.d.ts +2 -0
- package/dist/add/add-feature-flag.js +162 -0
- package/dist/add/add-wire-addon.js +1 -0
- package/dist/add/add-wire-remote-addon.js +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/inspector.js +4 -0
- package/dist/surface/collect-surface.d.ts +58 -0
- package/dist/surface/collect-surface.js +613 -0
- package/dist/types.d.ts +20 -11
- package/dist/utils/filter-inspector-state.js +14 -0
- package/dist/utils/post-process.js +15 -0
- package/dist/utils/read-module-specifiers.d.ts +8 -0
- package/dist/utils/read-module-specifiers.js +23 -0
- package/dist/utils/read-tsconfig-out-dir.d.ts +5 -0
- package/dist/utils/read-tsconfig-out-dir.js +12 -0
- package/dist/utils/resolve-deploy-target.d.ts +7 -0
- package/dist/utils/resolve-deploy-target.js +15 -7
- package/dist/utils/serialize-inspector-state.d.ts +5 -0
- package/dist/utils/serialize-inspector-state.js +8 -0
- package/dist/visit.js +4 -0
- package/package.json +16 -6
- package/run-tests.sh +21 -6
- package/src/add/add-addon-bans.ts +0 -85
- package/src/add/add-agent-scorer.test.ts +0 -197
- package/src/add/add-agent-scorer.ts +0 -83
- package/src/add/add-agent.test.ts +0 -87
- package/src/add/add-agent.ts +0 -638
- package/src/add/add-approval-description.ts +0 -76
- package/src/add/add-auth.test.ts +0 -791
- package/src/add/add-auth.ts +0 -424
- package/src/add/add-channel.ts +0 -778
- package/src/add/add-cli-renderers.test.ts +0 -74
- package/src/add/add-cli.ts +0 -1006
- package/src/add/add-credential.ts +0 -186
- package/src/add/add-feature.test.ts +0 -306
- package/src/add/add-feature.ts +0 -253
- package/src/add/add-file-extends-core-type.ts +0 -66
- package/src/add/add-file-with-config.ts +0 -45
- package/src/add/add-file-with-factory.test.ts +0 -134
- package/src/add/add-file-with-factory.ts +0 -288
- package/src/add/add-functions.test.ts +0 -743
- package/src/add/add-functions.ts +0 -1548
- package/src/add/add-gateway.ts +0 -143
- package/src/add/add-http-route-ref.test.ts +0 -149
- package/src/add/add-http-route.ts +0 -530
- package/src/add/add-http-routes.ts +0 -471
- package/src/add/add-keyed-wiring.ts +0 -180
- package/src/add/add-mcp-prompt.ts +0 -157
- package/src/add/add-mcp-resource.ts +0 -175
- package/src/add/add-middleware.test.ts +0 -196
- package/src/add/add-middleware.ts +0 -879
- package/src/add/add-permission-auth.test.ts +0 -59
- package/src/add/add-permission.ts +0 -311
- package/src/add/add-personas.test.ts +0 -406
- package/src/add/add-personas.ts +0 -287
- package/src/add/add-queue-worker.ts +0 -117
- package/src/add/add-rpc-invocations.test.ts +0 -287
- package/src/add/add-rpc-invocations.ts +0 -221
- package/src/add/add-scenario-step.test.ts +0 -509
- package/src/add/add-schedule.ts +0 -120
- package/src/add/add-scope.test.ts +0 -419
- package/src/add/add-scope.ts +0 -221
- package/src/add/add-secret.ts +0 -10
- package/src/add/add-subject-scenario-steps.test.ts +0 -161
- package/src/add/add-system-role.test.ts +0 -297
- package/src/add/add-system-role.ts +0 -162
- package/src/add/add-trigger.ts +0 -165
- package/src/add/add-variable.ts +0 -10
- package/src/add/add-wire-addon.test.ts +0 -185
- package/src/add/add-wire-addon.ts +0 -146
- package/src/add/add-wire-remote-addon.ts +0 -80
- package/src/add/add-workflow-edge-cases.test.ts +0 -201
- package/src/add/add-workflow-fanout.test.ts +0 -166
- package/src/add/add-workflow-graph-foreach.test.ts +0 -174
- package/src/add/add-workflow-graph-input.test.ts +0 -94
- package/src/add/add-workflow-graph-notes.test.ts +0 -74
- package/src/add/add-workflow-graph.ts +0 -644
- package/src/add/add-workflow-on-error.test.ts +0 -118
- package/src/add/add-workflow-return-switch.test.ts +0 -121
- package/src/add/add-workflow-silent-drops.test.ts +0 -261
- package/src/add/add-workflow-sleep-expression.test.ts +0 -96
- package/src/add/add-workflow.test.ts +0 -155
- package/src/add/add-workflow.ts +0 -482
- package/src/add/addon-bans.test.ts +0 -121
- package/src/add/addon-contracts.test.ts +0 -221
- package/src/add/allow-escape-hatches.test.ts +0 -144
- package/src/add/audit-meta.test.ts +0 -75
- package/src/add/classified-sink-check.test.ts +0 -347
- package/src/add/config-value-metadata.test.ts +0 -263
- package/src/add/dynamic-import-check.test.ts +0 -119
- package/src/add/expect-eventually-scenario-only.test.ts +0 -108
- package/src/add/function-body-span.test.ts +0 -96
- package/src/add/inline-wiring-function.test.ts +0 -104
- package/src/add/pii-check.test.ts +0 -258
- package/src/add/rpc-type-cast.test.ts +0 -123
- package/src/add/scenario-instrumentation.test.ts +0 -89
- package/src/add/scenario-instrumentation.ts +0 -42
- package/src/add/scenario-marker.test.ts +0 -89
- package/src/add/scenario-not-extractable.test.ts +0 -159
- package/src/add/scenario-services.test.ts +0 -103
- package/src/add/scenario-skip.test.ts +0 -83
- package/src/add/validate-workflow-graph-addons.test.ts +0 -102
- package/src/add/wire-name-literal.test.ts +0 -117
- package/src/error-codes-emitted.test.ts +0 -76
- package/src/error-codes.ts +0 -145
- package/src/index.ts +0 -66
- package/src/inspector.ts +0 -575
- package/src/schema-generator.ts +0 -1
- package/src/source-file-cache.ts +0 -73
- package/src/tsconfig-paths.test.ts +0 -90
- package/src/types-map.ts +0 -141
- package/src/types.ts +0 -775
- package/src/utils/annotate-http-route-auth.test.ts +0 -161
- package/src/utils/annotate-http-route-auth.ts +0 -53
- package/src/utils/check-classified-sinks.ts +0 -215
- package/src/utils/check-pii-output.ts +0 -120
- package/src/utils/compute-diagnostics.test.ts +0 -106
- package/src/utils/compute-required-schemas.ts +0 -48
- package/src/utils/contract-hashes.test.ts +0 -553
- package/src/utils/contract-hashes.ts +0 -387
- package/src/utils/custom-types-generator.test.ts +0 -124
- package/src/utils/custom-types-generator.ts +0 -150
- package/src/utils/detect-schema-vendor.test.ts +0 -123
- package/src/utils/detect-schema-vendor.ts +0 -120
- package/src/utils/does-type-extend-core-type.ts +0 -53
- package/src/utils/ensure-function-metadata.ts +0 -385
- package/src/utils/extract-function-name.ts +0 -612
- package/src/utils/extract-function-node.ts +0 -70
- package/src/utils/extract-node-value.test.ts +0 -117
- package/src/utils/extract-node-value.ts +0 -172
- package/src/utils/extract-secret-usage.test.ts +0 -81
- package/src/utils/extract-secret-usage.ts +0 -73
- package/src/utils/extract-services.ts +0 -70
- package/src/utils/extract-surface-usage.test.ts +0 -147
- package/src/utils/extract-surface-usage.ts +0 -121
- package/src/utils/filter-inspector-state.test.ts +0 -2232
- package/src/utils/filter-inspector-state.ts +0 -1298
- package/src/utils/filter-utils.test.ts +0 -843
- package/src/utils/filter-utils.ts +0 -161
- package/src/utils/find-root-dir.ts +0 -68
- package/src/utils/find-zod-transform.test.ts +0 -70
- package/src/utils/find-zod-transform.ts +0 -103
- package/src/utils/get-exported-variable-name.ts +0 -48
- package/src/utils/get-files-and-methods.ts +0 -157
- package/src/utils/get-property-value.test.ts +0 -141
- package/src/utils/get-property-value.ts +0 -331
- package/src/utils/hash.ts +0 -26
- package/src/utils/literal-properties.ts +0 -97
- package/src/utils/load-addon-functions-meta.test.ts +0 -157
- package/src/utils/load-addon-functions-meta.ts +0 -508
- package/src/utils/load-addon-scopes.test.ts +0 -138
- package/src/utils/middleware.test.ts +0 -204
- package/src/utils/middleware.ts +0 -303
- package/src/utils/nested-project-filter.test.ts +0 -53
- package/src/utils/nested-project-filter.ts +0 -28
- package/src/utils/permissions-shorthand.test.ts +0 -79
- package/src/utils/permissions.test.ts +0 -125
- package/src/utils/permissions.ts +0 -129
- package/src/utils/post-process.test.ts +0 -1094
- package/src/utils/post-process.ts +0 -1604
- package/src/utils/resolve-addon-package.ts +0 -106
- package/src/utils/resolve-core-type.test.ts +0 -142
- package/src/utils/resolve-core-type.ts +0 -35
- package/src/utils/resolve-deploy-target.test.ts +0 -135
- package/src/utils/resolve-deploy-target.ts +0 -65
- package/src/utils/resolve-function-meta.ts +0 -25
- package/src/utils/resolve-function-types.ts +0 -42
- package/src/utils/resolve-identifier.ts +0 -46
- package/src/utils/resolve-ref-contract.ts +0 -71
- package/src/utils/resolve-versions.test.ts +0 -499
- package/src/utils/resolve-versions.ts +0 -228
- package/src/utils/schema-cache.test.ts +0 -124
- package/src/utils/schema-generator.test.ts +0 -272
- package/src/utils/schema-generator.ts +0 -780
- package/src/utils/secret-alias-services.test.ts +0 -146
- package/src/utils/secret-alias-services.ts +0 -58
- package/src/utils/serialize-inspector-state.ts +0 -742
- package/src/utils/serialize-mcp-json.ts +0 -150
- package/src/utils/serialize-middleware-groups-meta.ts +0 -36
- package/src/utils/serialize-openapi-json.ts +0 -277
- package/src/utils/serialize-permissions-groups-meta.ts +0 -7
- package/src/utils/single-declaration.ts +0 -57
- package/src/utils/type-utils.ts +0 -145
- package/src/utils/validate-auth-sessionless.ts +0 -30
- package/src/utils/validate-exposed-functions-gated.test.ts +0 -230
- package/src/utils/validate-exposed-functions-gated.ts +0 -103
- package/src/utils/validate-tags-resolve-to-middleware.test.ts +0 -108
- package/src/utils/validate-tags-resolve-to-middleware.ts +0 -48
- package/src/utils/workflow/derive-workflow-plan.test.ts +0 -178
- package/src/utils/workflow/derive-workflow-plan.ts +0 -117
- package/src/utils/workflow/dsl/deserialize-dsl-workflow.ts +0 -1465
- package/src/utils/workflow/dsl/deserialize-fanout-body.test.ts +0 -71
- package/src/utils/workflow/dsl/deserialize-fanout-fidelity.test.ts +0 -99
- package/src/utils/workflow/dsl/deserialize-graph-next.test.ts +0 -161
- package/src/utils/workflow/dsl/deserialize-roundtrip-bindings.test.ts +0 -121
- package/src/utils/workflow/dsl/deserialize-roundtrip-values.test.ts +0 -116
- package/src/utils/workflow/dsl/deserialize-roundtrip.test.ts +0 -145
- package/src/utils/workflow/dsl/extract-approval-policy.test.ts +0 -71
- package/src/utils/workflow/dsl/extract-dsl-workflow.ts +0 -2254
- package/src/utils/workflow/dsl/index.ts +0 -11
- package/src/utils/workflow/dsl/patterns.ts +0 -440
- package/src/utils/workflow/dsl/validation.ts +0 -203
- package/src/utils/workflow/graph/convert-dsl-to-graph-skip.test.ts +0 -35
- package/src/utils/workflow/graph/convert-dsl-to-graph.ts +0 -543
- package/src/utils/workflow/graph/fanout-node-identity.test.ts +0 -105
- package/src/utils/workflow/graph/finalize-workflow-wires.ts +0 -310
- package/src/utils/workflow/graph/finalize-workflows.test.ts +0 -50
- package/src/utils/workflow/graph/finalize-workflows.ts +0 -130
- package/src/utils/workflow/graph/index.ts +0 -28
- package/src/utils/workflow/graph/serialize-workflow-graph.ts +0 -240
- package/src/utils/workflow/graph/workflow-graph.types.ts +0 -281
- package/src/visit.ts +0 -181
- package/tsconfig.json +0 -19
- 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
|
-
}
|