@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.
Files changed (219) hide show
  1. package/CHANGELOG.md +330 -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-wire-addon.js +1 -0
  8. package/dist/add/add-wire-remote-addon.js +1 -0
  9. package/dist/index.d.ts +3 -1
  10. package/dist/index.js +3 -1
  11. package/dist/inspector.js +4 -0
  12. package/dist/surface/collect-surface.d.ts +58 -0
  13. package/dist/surface/collect-surface.js +613 -0
  14. package/dist/types.d.ts +20 -11
  15. package/dist/utils/filter-inspector-state.js +14 -0
  16. package/dist/utils/post-process.js +15 -0
  17. package/dist/utils/read-module-specifiers.d.ts +8 -0
  18. package/dist/utils/read-module-specifiers.js +23 -0
  19. package/dist/utils/read-tsconfig-out-dir.d.ts +5 -0
  20. package/dist/utils/read-tsconfig-out-dir.js +12 -0
  21. package/dist/utils/resolve-deploy-target.d.ts +7 -0
  22. package/dist/utils/resolve-deploy-target.js +15 -7
  23. package/dist/utils/serialize-inspector-state.d.ts +5 -0
  24. package/dist/utils/serialize-inspector-state.js +8 -0
  25. package/dist/visit.js +4 -0
  26. package/package.json +16 -6
  27. package/run-tests.sh +21 -6
  28. package/src/add/add-addon-bans.ts +0 -85
  29. package/src/add/add-agent-scorer.test.ts +0 -197
  30. package/src/add/add-agent-scorer.ts +0 -83
  31. package/src/add/add-agent.test.ts +0 -87
  32. package/src/add/add-agent.ts +0 -638
  33. package/src/add/add-approval-description.ts +0 -76
  34. package/src/add/add-auth.test.ts +0 -791
  35. package/src/add/add-auth.ts +0 -424
  36. package/src/add/add-channel.ts +0 -778
  37. package/src/add/add-cli-renderers.test.ts +0 -74
  38. package/src/add/add-cli.ts +0 -1006
  39. package/src/add/add-credential.ts +0 -186
  40. package/src/add/add-feature.test.ts +0 -306
  41. package/src/add/add-feature.ts +0 -253
  42. package/src/add/add-file-extends-core-type.ts +0 -66
  43. package/src/add/add-file-with-config.ts +0 -45
  44. package/src/add/add-file-with-factory.test.ts +0 -134
  45. package/src/add/add-file-with-factory.ts +0 -288
  46. package/src/add/add-functions.test.ts +0 -743
  47. package/src/add/add-functions.ts +0 -1548
  48. package/src/add/add-gateway.ts +0 -143
  49. package/src/add/add-http-route-ref.test.ts +0 -149
  50. package/src/add/add-http-route.ts +0 -530
  51. package/src/add/add-http-routes.ts +0 -471
  52. package/src/add/add-keyed-wiring.ts +0 -180
  53. package/src/add/add-mcp-prompt.ts +0 -157
  54. package/src/add/add-mcp-resource.ts +0 -175
  55. package/src/add/add-middleware.test.ts +0 -196
  56. package/src/add/add-middleware.ts +0 -879
  57. package/src/add/add-permission-auth.test.ts +0 -59
  58. package/src/add/add-permission.ts +0 -311
  59. package/src/add/add-personas.test.ts +0 -406
  60. package/src/add/add-personas.ts +0 -287
  61. package/src/add/add-queue-worker.ts +0 -117
  62. package/src/add/add-rpc-invocations.test.ts +0 -287
  63. package/src/add/add-rpc-invocations.ts +0 -221
  64. package/src/add/add-scenario-step.test.ts +0 -509
  65. package/src/add/add-schedule.ts +0 -120
  66. package/src/add/add-scope.test.ts +0 -419
  67. package/src/add/add-scope.ts +0 -221
  68. package/src/add/add-secret.ts +0 -10
  69. package/src/add/add-subject-scenario-steps.test.ts +0 -161
  70. package/src/add/add-system-role.test.ts +0 -297
  71. package/src/add/add-system-role.ts +0 -162
  72. package/src/add/add-trigger.ts +0 -165
  73. package/src/add/add-variable.ts +0 -10
  74. package/src/add/add-wire-addon.test.ts +0 -185
  75. package/src/add/add-wire-addon.ts +0 -146
  76. package/src/add/add-wire-remote-addon.ts +0 -80
  77. package/src/add/add-workflow-edge-cases.test.ts +0 -201
  78. package/src/add/add-workflow-fanout.test.ts +0 -166
  79. package/src/add/add-workflow-graph-foreach.test.ts +0 -174
  80. package/src/add/add-workflow-graph-input.test.ts +0 -94
  81. package/src/add/add-workflow-graph-notes.test.ts +0 -74
  82. package/src/add/add-workflow-graph.ts +0 -644
  83. package/src/add/add-workflow-on-error.test.ts +0 -118
  84. package/src/add/add-workflow-return-switch.test.ts +0 -121
  85. package/src/add/add-workflow-silent-drops.test.ts +0 -261
  86. package/src/add/add-workflow-sleep-expression.test.ts +0 -96
  87. package/src/add/add-workflow.test.ts +0 -155
  88. package/src/add/add-workflow.ts +0 -482
  89. package/src/add/addon-bans.test.ts +0 -121
  90. package/src/add/addon-contracts.test.ts +0 -221
  91. package/src/add/allow-escape-hatches.test.ts +0 -144
  92. package/src/add/audit-meta.test.ts +0 -75
  93. package/src/add/classified-sink-check.test.ts +0 -347
  94. package/src/add/config-value-metadata.test.ts +0 -263
  95. package/src/add/dynamic-import-check.test.ts +0 -119
  96. package/src/add/expect-eventually-scenario-only.test.ts +0 -108
  97. package/src/add/function-body-span.test.ts +0 -96
  98. package/src/add/inline-wiring-function.test.ts +0 -104
  99. package/src/add/pii-check.test.ts +0 -258
  100. package/src/add/rpc-type-cast.test.ts +0 -123
  101. package/src/add/scenario-instrumentation.test.ts +0 -89
  102. package/src/add/scenario-instrumentation.ts +0 -42
  103. package/src/add/scenario-marker.test.ts +0 -89
  104. package/src/add/scenario-not-extractable.test.ts +0 -159
  105. package/src/add/scenario-services.test.ts +0 -103
  106. package/src/add/scenario-skip.test.ts +0 -83
  107. package/src/add/validate-workflow-graph-addons.test.ts +0 -102
  108. package/src/add/wire-name-literal.test.ts +0 -117
  109. package/src/error-codes-emitted.test.ts +0 -76
  110. package/src/error-codes.ts +0 -145
  111. package/src/index.ts +0 -66
  112. package/src/inspector.ts +0 -575
  113. package/src/schema-generator.ts +0 -1
  114. package/src/source-file-cache.ts +0 -73
  115. package/src/tsconfig-paths.test.ts +0 -90
  116. package/src/types-map.ts +0 -141
  117. package/src/types.ts +0 -775
  118. package/src/utils/annotate-http-route-auth.test.ts +0 -161
  119. package/src/utils/annotate-http-route-auth.ts +0 -53
  120. package/src/utils/check-classified-sinks.ts +0 -215
  121. package/src/utils/check-pii-output.ts +0 -120
  122. package/src/utils/compute-diagnostics.test.ts +0 -106
  123. package/src/utils/compute-required-schemas.ts +0 -48
  124. package/src/utils/contract-hashes.test.ts +0 -553
  125. package/src/utils/contract-hashes.ts +0 -387
  126. package/src/utils/custom-types-generator.test.ts +0 -124
  127. package/src/utils/custom-types-generator.ts +0 -150
  128. package/src/utils/detect-schema-vendor.test.ts +0 -123
  129. package/src/utils/detect-schema-vendor.ts +0 -120
  130. package/src/utils/does-type-extend-core-type.ts +0 -53
  131. package/src/utils/ensure-function-metadata.ts +0 -385
  132. package/src/utils/extract-function-name.ts +0 -612
  133. package/src/utils/extract-function-node.ts +0 -70
  134. package/src/utils/extract-node-value.test.ts +0 -117
  135. package/src/utils/extract-node-value.ts +0 -172
  136. package/src/utils/extract-secret-usage.test.ts +0 -81
  137. package/src/utils/extract-secret-usage.ts +0 -73
  138. package/src/utils/extract-services.ts +0 -70
  139. package/src/utils/extract-surface-usage.test.ts +0 -147
  140. package/src/utils/extract-surface-usage.ts +0 -121
  141. package/src/utils/filter-inspector-state.test.ts +0 -2232
  142. package/src/utils/filter-inspector-state.ts +0 -1298
  143. package/src/utils/filter-utils.test.ts +0 -843
  144. package/src/utils/filter-utils.ts +0 -161
  145. package/src/utils/find-root-dir.ts +0 -68
  146. package/src/utils/find-zod-transform.test.ts +0 -70
  147. package/src/utils/find-zod-transform.ts +0 -103
  148. package/src/utils/get-exported-variable-name.ts +0 -48
  149. package/src/utils/get-files-and-methods.ts +0 -157
  150. package/src/utils/get-property-value.test.ts +0 -141
  151. package/src/utils/get-property-value.ts +0 -331
  152. package/src/utils/hash.ts +0 -26
  153. package/src/utils/literal-properties.ts +0 -97
  154. package/src/utils/load-addon-functions-meta.test.ts +0 -157
  155. package/src/utils/load-addon-functions-meta.ts +0 -508
  156. package/src/utils/load-addon-scopes.test.ts +0 -138
  157. package/src/utils/middleware.test.ts +0 -204
  158. package/src/utils/middleware.ts +0 -303
  159. package/src/utils/nested-project-filter.test.ts +0 -53
  160. package/src/utils/nested-project-filter.ts +0 -28
  161. package/src/utils/permissions-shorthand.test.ts +0 -79
  162. package/src/utils/permissions.test.ts +0 -125
  163. package/src/utils/permissions.ts +0 -129
  164. package/src/utils/post-process.test.ts +0 -1094
  165. package/src/utils/post-process.ts +0 -1604
  166. package/src/utils/resolve-addon-package.ts +0 -106
  167. package/src/utils/resolve-core-type.test.ts +0 -142
  168. package/src/utils/resolve-core-type.ts +0 -35
  169. package/src/utils/resolve-deploy-target.test.ts +0 -135
  170. package/src/utils/resolve-deploy-target.ts +0 -65
  171. package/src/utils/resolve-function-meta.ts +0 -25
  172. package/src/utils/resolve-function-types.ts +0 -42
  173. package/src/utils/resolve-identifier.ts +0 -46
  174. package/src/utils/resolve-ref-contract.ts +0 -71
  175. package/src/utils/resolve-versions.test.ts +0 -499
  176. package/src/utils/resolve-versions.ts +0 -228
  177. package/src/utils/schema-cache.test.ts +0 -124
  178. package/src/utils/schema-generator.test.ts +0 -272
  179. package/src/utils/schema-generator.ts +0 -780
  180. package/src/utils/secret-alias-services.test.ts +0 -146
  181. package/src/utils/secret-alias-services.ts +0 -58
  182. package/src/utils/serialize-inspector-state.ts +0 -742
  183. package/src/utils/serialize-mcp-json.ts +0 -150
  184. package/src/utils/serialize-middleware-groups-meta.ts +0 -36
  185. package/src/utils/serialize-openapi-json.ts +0 -277
  186. package/src/utils/serialize-permissions-groups-meta.ts +0 -7
  187. package/src/utils/single-declaration.ts +0 -57
  188. package/src/utils/type-utils.ts +0 -145
  189. package/src/utils/validate-auth-sessionless.ts +0 -30
  190. package/src/utils/validate-exposed-functions-gated.test.ts +0 -230
  191. package/src/utils/validate-exposed-functions-gated.ts +0 -103
  192. package/src/utils/validate-tags-resolve-to-middleware.test.ts +0 -108
  193. package/src/utils/validate-tags-resolve-to-middleware.ts +0 -48
  194. package/src/utils/workflow/derive-workflow-plan.test.ts +0 -178
  195. package/src/utils/workflow/derive-workflow-plan.ts +0 -117
  196. package/src/utils/workflow/dsl/deserialize-dsl-workflow.ts +0 -1465
  197. package/src/utils/workflow/dsl/deserialize-fanout-body.test.ts +0 -71
  198. package/src/utils/workflow/dsl/deserialize-fanout-fidelity.test.ts +0 -99
  199. package/src/utils/workflow/dsl/deserialize-graph-next.test.ts +0 -161
  200. package/src/utils/workflow/dsl/deserialize-roundtrip-bindings.test.ts +0 -121
  201. package/src/utils/workflow/dsl/deserialize-roundtrip-values.test.ts +0 -116
  202. package/src/utils/workflow/dsl/deserialize-roundtrip.test.ts +0 -145
  203. package/src/utils/workflow/dsl/extract-approval-policy.test.ts +0 -71
  204. package/src/utils/workflow/dsl/extract-dsl-workflow.ts +0 -2254
  205. package/src/utils/workflow/dsl/index.ts +0 -11
  206. package/src/utils/workflow/dsl/patterns.ts +0 -440
  207. package/src/utils/workflow/dsl/validation.ts +0 -203
  208. package/src/utils/workflow/graph/convert-dsl-to-graph-skip.test.ts +0 -35
  209. package/src/utils/workflow/graph/convert-dsl-to-graph.ts +0 -543
  210. package/src/utils/workflow/graph/fanout-node-identity.test.ts +0 -105
  211. package/src/utils/workflow/graph/finalize-workflow-wires.ts +0 -310
  212. package/src/utils/workflow/graph/finalize-workflows.test.ts +0 -50
  213. package/src/utils/workflow/graph/finalize-workflows.ts +0 -130
  214. package/src/utils/workflow/graph/index.ts +0 -28
  215. package/src/utils/workflow/graph/serialize-workflow-graph.ts +0 -240
  216. package/src/utils/workflow/graph/workflow-graph.types.ts +0 -281
  217. package/src/visit.ts +0 -181
  218. package/tsconfig.json +0 -19
  219. 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
- })