@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,228 +0,0 @@
1
- import { parseVersionedId, formatVersionedId } from '@pikku/core/utils'
2
- import type { CLICommandMeta } from '@pikku/core/cli'
3
- import type { InspectorState, InspectorLogger } from '../types.js'
4
- import { ErrorCode } from '../error-codes.js'
5
-
6
- export function resolveLatestVersions(
7
- state: InspectorState,
8
- logger: InspectorLogger
9
- ): void {
10
- const functionsMeta = state.functions.meta
11
-
12
- const groups = new Map<
13
- string,
14
- {
15
- explicit: { id: string; version: number }[]
16
- unversioned: string | null
17
- }
18
- >()
19
-
20
- for (const [id, meta] of Object.entries(functionsMeta)) {
21
- const parsed = parseVersionedId(id)
22
- const baseName = parsed.version !== null ? parsed.baseName : id
23
- let group = groups.get(baseName)
24
- if (!group) {
25
- group = { explicit: [], unversioned: null }
26
- groups.set(baseName, group)
27
- }
28
- if (parsed.version !== null) {
29
- group.explicit.push({ id, version: parsed.version })
30
- } else if (meta.version !== undefined) {
31
- group.explicit.push({ id, version: meta.version })
32
- } else {
33
- group.unversioned = id
34
- }
35
- }
36
-
37
- for (const [baseName, group] of groups) {
38
- if (group.explicit.length === 0) {
39
- continue
40
- }
41
-
42
- const seen = new Map<number, string>()
43
- for (const entry of group.explicit) {
44
- const existing = seen.get(entry.version)
45
- if (existing) {
46
- logger.critical(
47
- ErrorCode.DUPLICATE_FUNCTION_VERSION,
48
- `Duplicate version ${entry.version} for function '${baseName}': '${existing}' and '${entry.id}'.`
49
- )
50
- }
51
- seen.set(entry.version, entry.id)
52
- }
53
-
54
- const maxVersion = Math.max(...group.explicit.map((e) => e.version))
55
-
56
- if (group.unversioned) {
57
- const implicitVersion = maxVersion + 1
58
- const oldId = group.unversioned
59
- const newId = formatVersionedId(baseName, implicitVersion)
60
-
61
- const meta = functionsMeta[oldId]!
62
- meta.pikkuFuncId = newId
63
- meta.version = implicitVersion
64
- delete functionsMeta[oldId]
65
- functionsMeta[newId] = meta
66
-
67
- const fileEntry = state.functions.files.get(oldId)
68
- if (fileEntry) {
69
- state.functions.files.delete(oldId)
70
- state.functions.files.set(newId, fileEntry)
71
- }
72
-
73
- const rpcFileEntry = state.rpc.internalFiles.get(oldId)
74
- if (rpcFileEntry) {
75
- state.rpc.internalFiles.delete(oldId)
76
- state.rpc.internalFiles.set(newId, rpcFileEntry)
77
- }
78
-
79
- if (state.rpc.invokedFunctions.has(oldId)) {
80
- state.rpc.invokedFunctions.delete(oldId)
81
- state.rpc.invokedFunctions.add(newId)
82
- }
83
-
84
- if (state.serviceAggregation.usedFunctions.has(oldId)) {
85
- state.serviceAggregation.usedFunctions.delete(oldId)
86
- state.serviceAggregation.usedFunctions.add(newId)
87
- }
88
-
89
- state.rpc.internalMeta[baseName] = newId
90
- state.rpc.internalMeta[newId] = newId
91
-
92
- if (state.rpc.exposedMeta[baseName] === oldId) {
93
- state.rpc.exposedMeta[baseName] = newId
94
- }
95
-
96
- updateWiringReferences(state, oldId, newId)
97
- } else {
98
- const latest = group.explicit.reduce((a, b) =>
99
- a.version > b.version ? a : b
100
- )
101
- state.rpc.internalMeta[baseName] = latest.id
102
- }
103
-
104
- if (state.rpc.exposedMeta[baseName]) {
105
- const latestId = state.rpc.internalMeta[baseName]!
106
- state.rpc.exposedMeta[baseName] = latestId
107
- for (const entry of group.explicit) {
108
- state.rpc.exposedMeta[entry.id] = entry.id
109
- const fileEntry = state.rpc.internalFiles.get(entry.id)
110
- if (fileEntry) {
111
- state.rpc.exposedFiles.set(entry.id, fileEntry)
112
- }
113
- }
114
- if (group.unversioned) {
115
- state.rpc.exposedMeta[latestId] = latestId
116
- const fileEntry = state.rpc.internalFiles.get(latestId)
117
- if (fileEntry) {
118
- state.rpc.exposedFiles.set(latestId, fileEntry)
119
- }
120
- }
121
- }
122
-
123
- for (const entry of group.explicit) {
124
- state.rpc.invokedFunctions.add(entry.id)
125
- }
126
- }
127
- }
128
-
129
- function updateWiringReferences(
130
- state: InspectorState,
131
- oldId: string,
132
- newId: string
133
- ): void {
134
- // HTTP routes
135
- if (state.http) {
136
- for (const methods of Object.values(state.http.meta)) {
137
- for (const meta of Object.values(methods)) {
138
- if (meta.pikkuFuncId === oldId) {
139
- meta.pikkuFuncId = newId
140
- }
141
- }
142
- }
143
- }
144
-
145
- // Channels: connect/disconnect/message slots + action-routed message wirings
146
- if (state.channels) {
147
- for (const channel of Object.values(state.channels.meta)) {
148
- for (const slot of [
149
- channel.connect,
150
- channel.disconnect,
151
- channel.message,
152
- ]) {
153
- if (slot && slot.pikkuFuncId === oldId) {
154
- slot.pikkuFuncId = newId
155
- }
156
- }
157
- for (const routes of Object.values(channel.messageWirings)) {
158
- for (const message of Object.values(routes)) {
159
- if (message.pikkuFuncId === oldId) {
160
- message.pikkuFuncId = newId
161
- }
162
- }
163
- }
164
- }
165
- }
166
-
167
- // CLI programs: commands and nested subcommands. This also covers
168
- // CLI-over-channel generation, which reads command funcIds from this meta.
169
- if (state.cli) {
170
- const updateCommands = (commands: Record<string, CLICommandMeta>): void => {
171
- for (const command of Object.values(commands)) {
172
- if (command.pikkuFuncId === oldId) {
173
- command.pikkuFuncId = newId
174
- }
175
- if (command.subcommands) {
176
- updateCommands(command.subcommands)
177
- }
178
- }
179
- }
180
- for (const program of Object.values(state.cli.meta.programs)) {
181
- updateCommands(program.commands)
182
- }
183
- }
184
-
185
- // Scheduled tasks
186
- if (state.scheduledTasks) {
187
- for (const task of Object.values(state.scheduledTasks.meta)) {
188
- if (task.pikkuFuncId === oldId) {
189
- task.pikkuFuncId = newId
190
- }
191
- }
192
- }
193
-
194
- // Queue workers
195
- if (state.queueWorkers) {
196
- for (const worker of Object.values(state.queueWorkers.meta)) {
197
- if (worker.pikkuFuncId === oldId) {
198
- worker.pikkuFuncId = newId
199
- }
200
- }
201
- }
202
-
203
- // Trigger sources (TriggerSourceMeta carries the handler's pikkuFuncId).
204
- // Gateways/workflows/agents reference functions by bare rpc name and are
205
- // resolved at runtime via state.rpc.internalMeta, so they need no rewrite here.
206
- if (state.triggers) {
207
- for (const source of Object.values(state.triggers.sourceMeta)) {
208
- if (source.pikkuFuncId === oldId) {
209
- source.pikkuFuncId = newId
210
- }
211
- }
212
- }
213
-
214
- // MCP resources, tools, and prompts
215
- if (state.mcpEndpoints) {
216
- for (const collection of [
217
- state.mcpEndpoints.resourcesMeta,
218
- state.mcpEndpoints.toolsMeta,
219
- state.mcpEndpoints.promptsMeta,
220
- ]) {
221
- for (const endpoint of Object.values(collection)) {
222
- if (endpoint.pikkuFuncId === oldId) {
223
- endpoint.pikkuFuncId = newId
224
- }
225
- }
226
- }
227
- }
228
- }
@@ -1,124 +0,0 @@
1
- import { test, describe } from 'node:test'
2
- import { strict as assert } from 'node:assert'
3
- import {
4
- mkdtempSync,
5
- rmSync,
6
- statSync,
7
- utimesSync,
8
- writeFileSync,
9
- } from 'node:fs'
10
- import { tmpdir } from 'node:os'
11
- import { join } from 'node:path'
12
- import {
13
- readDiskTSSchemas,
14
- writeDiskTSSchemas,
15
- type SchemaDep,
16
- } from './schema-generator.js'
17
- import type { InspectorLogger } from '../types.js'
18
-
19
- const silentLogger = {
20
- debug: () => {},
21
- info: () => {},
22
- warn: () => {},
23
- error: () => {},
24
- } as unknown as InspectorLogger
25
-
26
- const KEY = 'test-key'
27
- const SCHEMAS = { Thing: { type: 'object' } }
28
-
29
- /** A cache directory and one source file the cached schemas claim to derive from. */
30
- function makeCache(sourceContent: string): {
31
- cacheDir: string
32
- source: string
33
- deps: SchemaDep[]
34
- cleanup: () => void
35
- } {
36
- const dir = mkdtempSync(join(tmpdir(), 'pikku-schema-cache-'))
37
- const source = join(dir, 'thing.ts')
38
- writeFileSync(source, sourceContent)
39
- const stat = statSync(source)
40
- return {
41
- cacheDir: dir,
42
- source,
43
- deps: [{ path: source, mtimeMs: stat.mtimeMs, size: stat.size }],
44
- cleanup: () => rmSync(dir, { recursive: true, force: true }),
45
- }
46
- }
47
-
48
- describe('TS schema disk cache', () => {
49
- test('serves the cached schemas while their sources are untouched', () => {
50
- const { cacheDir, deps, cleanup } = makeCache('export interface Thing {}')
51
- try {
52
- writeDiskTSSchemas(silentLogger, cacheDir, KEY, SCHEMAS, deps)
53
- const hit = readDiskTSSchemas(silentLogger, cacheDir, KEY)
54
- assert.deepEqual(hit?.schemas, SCHEMAS)
55
- } finally {
56
- cleanup()
57
- }
58
- })
59
-
60
- test('discards the cache when a type it was built from changes', () => {
61
- const { cacheDir, source, deps, cleanup } = makeCache(
62
- 'export interface Thing {}'
63
- )
64
- try {
65
- writeDiskTSSchemas(silentLogger, cacheDir, KEY, SCHEMAS, deps)
66
- // The shape changes but the name does not, so the cache key — which hashes
67
- // only the synthesized custom-types source — is identical. This is the case
68
- // that used to serve a stale schema, and it survived `rm -rf .pikku`
69
- // because the cache lives under node_modules/.cache.
70
- writeFileSync(source, 'export interface Thing { added: string }')
71
- assert.equal(readDiskTSSchemas(silentLogger, cacheDir, KEY), null)
72
- } finally {
73
- cleanup()
74
- }
75
- })
76
-
77
- test('discards the cache when a source is rewritten to the same size', () => {
78
- const { cacheDir, source, deps, cleanup } = makeCache('export type A = 1')
79
- try {
80
- writeDiskTSSchemas(silentLogger, cacheDir, KEY, SCHEMAS, deps)
81
- // Same byte count, different type: size alone would call this unchanged, so
82
- // the mtime half of the fingerprint is what has to catch it.
83
- writeFileSync(source, 'export type A = 2')
84
- utimesSync(source, new Date(), new Date(Date.now() + 5000))
85
- assert.equal(readDiskTSSchemas(silentLogger, cacheDir, KEY), null)
86
- } finally {
87
- cleanup()
88
- }
89
- })
90
-
91
- test('discards the cache when a source it was built from is deleted', () => {
92
- const { cacheDir, source, deps, cleanup } = makeCache('export type A = 1')
93
- try {
94
- writeDiskTSSchemas(silentLogger, cacheDir, KEY, SCHEMAS, deps)
95
- rmSync(source)
96
- assert.equal(readDiskTSSchemas(silentLogger, cacheDir, KEY), null)
97
- } finally {
98
- cleanup()
99
- }
100
- })
101
-
102
- test('discards a cache written before deps were tracked', () => {
103
- const { cacheDir, cleanup } = makeCache('export type A = 1')
104
- try {
105
- writeFileSync(
106
- join(cacheDir, 'ts-schemas.json'),
107
- JSON.stringify({ key: KEY, schemas: SCHEMAS })
108
- )
109
- assert.equal(readDiskTSSchemas(silentLogger, cacheDir, KEY), null)
110
- } finally {
111
- cleanup()
112
- }
113
- })
114
-
115
- test('still misses on a different key', () => {
116
- const { cacheDir, deps, cleanup } = makeCache('export type A = 1')
117
- try {
118
- writeDiskTSSchemas(silentLogger, cacheDir, KEY, SCHEMAS, deps)
119
- assert.equal(readDiskTSSchemas(silentLogger, cacheDir, 'other'), null)
120
- } finally {
121
- cleanup()
122
- }
123
- })
124
- })
@@ -1,272 +0,0 @@
1
- import { test, describe, before, after } from 'node:test'
2
- import assert from 'node:assert/strict'
3
- import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'fs'
4
- import { tmpdir } from 'os'
5
- import { join } from 'path'
6
- import { fileURLToPath } from 'url'
7
-
8
- import { getInitialInspectorState } from '../inspector.js'
9
- import { generateAllSchemas, schemaRuntimeFile } from './schema-generator.js'
10
-
11
- const debugLines: string[] = []
12
- const logger = {
13
- debug(message: string) {
14
- debugLines.push(message)
15
- },
16
- info() {},
17
- warn() {},
18
- error() {},
19
- } as any
20
-
21
- const UNRELATED_FILE_COUNT = 300
22
-
23
- let projectDir: string
24
-
25
- before(() => {
26
- projectDir = mkdtempSync(join(tmpdir(), 'pikku-schema-gen-'))
27
- mkdirSync(join(projectDir, 'src'))
28
- writeFileSync(
29
- join(projectDir, 'tsconfig.json'),
30
- JSON.stringify({
31
- compilerOptions: {
32
- target: 'ESNext',
33
- module: 'NodeNext',
34
- moduleResolution: 'NodeNext',
35
- strict: true,
36
- skipLibCheck: true,
37
- },
38
- include: ['src/**/*.ts'],
39
- })
40
- )
41
- writeFileSync(
42
- join(projectDir, 'src', 'types.ts'),
43
- 'export type Thing = { id: string; count: number }\n'
44
- )
45
- // Files the tsconfig picks up but no generated custom type references. They
46
- // stand in for the bulk of a real project, which the schema program has no
47
- // reason to load.
48
- for (let i = 0; i < UNRELATED_FILE_COUNT; i += 1) {
49
- writeFileSync(
50
- join(projectDir, 'src', `unrelated-${i}.ts`),
51
- `export type Unrelated${i} = { a: string; b: number; c: boolean }\n`
52
- )
53
- }
54
- })
55
-
56
- after(() => {
57
- rmSync(projectDir, { recursive: true, force: true })
58
- })
59
-
60
- describe('generateAllSchemas', () => {
61
- // NOTE: this must be the first test in the file. The module-level caches are
62
- // per-process, so only the first call actually builds a program — a later call
63
- // reuses it and would measure a few MB no matter what.
64
- //
65
- // The schema generator builds a second ts.Program over the whole tsconfig —
66
- // on a real tree that is ~2.5k files and ~600MB, and it used to be pinned at
67
- // module scope for the life of the process. Combined with the main inspector
68
- // program it pushed peak heap past 2GB and OOM'd `pikku all` (#982).
69
- //
70
- // Only the program is released; cachedTSSchemas (the small result cache that
71
- // powers the same-process fast path) is deliberately kept.
72
- test('does not retain the schema ts.Program after returning', async (t) => {
73
- if (typeof global.gc !== 'function') {
74
- t.skip('requires --expose-gc')
75
- return
76
- }
77
-
78
- const state = getInitialInspectorState(projectDir) as any
79
- // A distinct type keeps this off the cachedTSSchemas fast path, so the
80
- // program is genuinely rebuilt (and therefore genuinely retainable) here.
81
- state.functions.typesMap.addCustomType(
82
- 'RetentionProbeInput',
83
- '{ probe: string; n: number }',
84
- []
85
- )
86
-
87
- global.gc!()
88
- const before = process.memoryUsage().heapUsed
89
-
90
- await generateAllSchemas(
91
- logger,
92
- { tsconfig: join(projectDir, 'tsconfig.json') },
93
- state
94
- )
95
-
96
- global.gc!()
97
- const retainedMB = (process.memoryUsage().heapUsed - before) / 1024 / 1024
98
-
99
- // A retained program (with its SourceFiles and TypeChecker) measures tens of
100
- // MB even for this one-file fixture; the schemas themselves are a few KB.
101
- assert.ok(
102
- retainedMB < 10,
103
- `expected the schema program to be released, but ${retainedMB.toFixed(1)}MB is still retained after GC`
104
- )
105
- })
106
-
107
- // The virtual file explicitly imports every type it needs, so it is a complete
108
- // root on its own. Rooting the program at the whole tsconfig instead made it
109
- // load the entire project (2572 files on a real tree vs 870), for no gain in
110
- // what could be resolved (#982).
111
- test('scopes the schema program to the virtual file import closure', async () => {
112
- debugLines.length = 0
113
-
114
- const state = getInitialInspectorState(projectDir) as any
115
- state.functions.typesMap.addCustomType(
116
- 'ScopingProbeInput',
117
- '{ scope: string }',
118
- []
119
- )
120
-
121
- await generateAllSchemas(
122
- logger,
123
- { tsconfig: join(projectDir, 'tsconfig.json') },
124
- state
125
- )
126
-
127
- const line = debugLines.find((l) => l.includes('Created schema program'))
128
- assert.ok(
129
- line,
130
- `expected a "Created schema program" debug line, got: ${debugLines.join(' | ')}`
131
- )
132
-
133
- const fileCount = Number(line!.match(/(\d+) files/)?.[1])
134
- assert.ok(
135
- Number.isFinite(fileCount),
136
- `could not parse a file count out of: ${line}`
137
- )
138
- // The virtual file plus types.ts plus the default lib. Rooting at the whole
139
- // tsconfig would pull in all UNRELATED_FILE_COUNT files on top of that.
140
- assert.ok(
141
- fileCount < UNRELATED_FILE_COUNT,
142
- `expected the schema program to skip the ${UNRELATED_FILE_COUNT} unreferenced files, but it loaded ${fileCount}`
143
- )
144
- })
145
-
146
- test('produces a schema for a custom type', async () => {
147
- const state = getInitialInspectorState(projectDir) as any
148
- state.functions.typesMap.addCustomType(
149
- 'ThingInput',
150
- '{ id: string; count: number }',
151
- []
152
- )
153
-
154
- const schemas = await generateAllSchemas(
155
- logger,
156
- { tsconfig: join(projectDir, 'tsconfig.json') },
157
- state
158
- )
159
-
160
- assert.ok(
161
- Object.keys(schemas).length > 0,
162
- 'expected at least one generated schema'
163
- )
164
- })
165
- })
166
-
167
- /**
168
- * A schema imported from a built workspace package resolves to that package's
169
- * declaration file, and a `.d.ts` has no runtime exports at all — every one of
170
- * them is a type. Importing it to read the zod object yields an empty module and
171
- * every schema in it is reported missing, so the emitted JS beside it is what has
172
- * to be imported.
173
- */
174
- describe('schemaRuntimeFile', () => {
175
- test('a source file the developer wrote is imported as it is', () => {
176
- const written = join(projectDir, 'src', 'types.ts')
177
- assert.equal(schemaRuntimeFile(written), written)
178
- })
179
-
180
- test('a declaration file resolves to the JS emitted beside it', () => {
181
- writeFileSync(
182
- join(projectDir, 'src', 'reindex.d.ts'),
183
- 'export declare const a: number\n'
184
- )
185
- writeFileSync(join(projectDir, 'src', 'reindex.js'), 'export const a = 1\n')
186
- assert.equal(
187
- schemaRuntimeFile(join(projectDir, 'src', 'reindex.d.ts')),
188
- join(projectDir, 'src', 'reindex.js')
189
- )
190
- })
191
-
192
- test('.d.mts resolves to the ESM file beside it', () => {
193
- writeFileSync(
194
- join(projectDir, 'src', 'esm.d.mts'),
195
- 'export declare const a: number\n'
196
- )
197
- writeFileSync(join(projectDir, 'src', 'esm.mjs'), 'export const a = 1\n')
198
- assert.equal(
199
- schemaRuntimeFile(join(projectDir, 'src', 'esm.d.mts')),
200
- join(projectDir, 'src', 'esm.mjs')
201
- )
202
- })
203
-
204
- test('.d.cts resolves to the CommonJS file beside it', () => {
205
- // Its own test rather than a second assertion above: the two extensions are
206
- // separate branches, and one covering both is one that passes while half of
207
- // it is broken.
208
- writeFileSync(
209
- join(projectDir, 'src', 'commonjs.d.cts'),
210
- 'export declare const a: number\n'
211
- )
212
- writeFileSync(join(projectDir, 'src', 'commonjs.cjs'), 'exports.a = 1\n')
213
- assert.equal(
214
- schemaRuntimeFile(join(projectDir, 'src', 'commonjs.d.cts')),
215
- join(projectDir, 'src', 'commonjs.cjs')
216
- )
217
- })
218
-
219
- test('a declaration file with no emitted JS keeps its own path', () => {
220
- // So the "could not find exported schema" error still names a file the
221
- // developer can open, rather than one that was never written.
222
- const orphan = join(projectDir, 'src', 'types-only.d.ts')
223
- writeFileSync(orphan, 'export declare const a: number\n')
224
- assert.equal(schemaRuntimeFile(orphan), orphan)
225
- })
226
- })
227
-
228
- describe('a Zod schema that cannot be a wire contract', () => {
229
- test('names .transform() and its own error code, not a downstream one', async () => {
230
- // A transform used to surface as PKU724 — the *bad `#pikku/*` import* error
231
- // — because the schema failed to convert, its generated leaf never got
232
- // written, and the type that leaf feeds resolved to an error type. The
233
- // message sent you to an import block that was fine.
234
- // Inside the package, so the fixture's `import 'zod'` resolves the same
235
- // way a project's would; a tmpdir has no node_modules above it.
236
- const dir = mkdtempSync(
237
- join(fileURLToPath(new URL('../../', import.meta.url)), '.zod-transform-')
238
- )
239
- try {
240
- const file = join(dir, 'schema.ts')
241
- writeFileSync(
242
- file,
243
- `import * as z from 'zod'\n` +
244
- `export const CreateAssessment = z.object({ patientId: z.string(), when: z.string().transform((s) => new Date(s)) })\n`
245
- )
246
- const state = getInitialInspectorState(dir) as any
247
- state.schemaLookup.set('CreateAssessmentInput', {
248
- variableName: 'CreateAssessment',
249
- sourceFile: file,
250
- vendor: 'zod',
251
- })
252
-
253
- const errors: string[] = []
254
- await assert.rejects(() =>
255
- generateAllSchemas(
256
- { ...logger, error: (m: string) => errors.push(m) },
257
- { tsconfig: join(projectDir, 'tsconfig.json') },
258
- state
259
- )
260
- )
261
-
262
- const reported = errors.join('\n')
263
- assert.match(reported, /PKU491/)
264
- assert.match(reported, /CreateAssessmentInput/)
265
- assert.match(reported, /\.transform\(\)/)
266
- assert.match(reported, /\.when/)
267
- assert.doesNotMatch(reported, /PKU724/)
268
- } finally {
269
- rmSync(dir, { recursive: true, force: true })
270
- }
271
- })
272
- })