@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,387 +0,0 @@
1
- import type { FunctionsMeta } from '@pikku/core/services'
2
- import type { JSONValue } from '@pikku/core/utils'
3
- import { parseVersionedId } from '@pikku/core/utils'
4
- import type { TypesMap } from '../types-map.js'
5
- import { ErrorCode } from '../error-codes.js'
6
- import { canonicalJSON, hashString } from './hash.js'
7
-
8
- export type ContractEntry = {
9
- functionKey: string
10
- version: number
11
- contractHash: string
12
- inputHash: string
13
- outputHash: string
14
- }
15
-
16
- export type VersionValidateError = {
17
- code: ErrorCode
18
- message: string
19
- functionKey?: string
20
- version?: number
21
- previousHash?: string
22
- currentHash?: string
23
- previousInputHash?: string
24
- currentInputHash?: string
25
- previousOutputHash?: string
26
- currentOutputHash?: string
27
- nextVersion?: number
28
- latestVersion?: number
29
- expectedNextVersion?: number
30
- }
31
-
32
- export type VersionHashEntry = {
33
- inputHash: string
34
- outputHash: string
35
- }
36
-
37
- export type VersionManifestEntry = {
38
- latest: number
39
- versions: Record<string, string | VersionHashEntry>
40
- }
41
-
42
- export type VersionManifest = {
43
- manifestVersion: 1
44
- contracts: Record<string, VersionManifestEntry>
45
- }
46
-
47
- function isHashEntry(v: string | VersionHashEntry): v is VersionHashEntry {
48
- return typeof v === 'object'
49
- }
50
-
51
- export function createEmptyManifest(): VersionManifest {
52
- return {
53
- manifestVersion: 1,
54
- contracts: {},
55
- }
56
- }
57
-
58
- export function serializeManifest(manifest: VersionManifest): string {
59
- const sortedContracts: Record<string, VersionManifestEntry> = {}
60
-
61
- for (const key of Object.keys(manifest.contracts).sort()) {
62
- const entry = manifest.contracts[key]
63
- const sortedVersions: Record<string, string | VersionHashEntry> = {}
64
- const numericKeys = Object.keys(entry.versions)
65
- .map(Number)
66
- .sort((a, b) => a - b)
67
- for (const vk of numericKeys) {
68
- sortedVersions[String(vk)] = entry.versions[String(vk)]
69
- }
70
- sortedContracts[key] = {
71
- latest: entry.latest,
72
- versions: sortedVersions,
73
- }
74
- }
75
-
76
- const sorted: VersionManifest = {
77
- manifestVersion: manifest.manifestVersion,
78
- contracts: sortedContracts,
79
- }
80
-
81
- return JSON.stringify(sorted, null, 2) + '\n'
82
- }
83
-
84
- export function computeContractHash(data: {
85
- functionKey: string
86
- inputSchema: unknown
87
- outputSchema: unknown
88
- }): string {
89
- return hashString(canonicalJSON(data), 16)
90
- }
91
-
92
- function computeInputHash(functionKey: string, inputSchema: unknown): string {
93
- return hashString(canonicalJSON({ functionKey, inputSchema }), 8)
94
- }
95
-
96
- function computeOutputHash(functionKey: string, outputSchema: unknown): string {
97
- return hashString(canonicalJSON({ functionKey, outputSchema }), 8)
98
- }
99
-
100
- function resolveSchema(
101
- typeNames: string[] | null | undefined,
102
- allSchemas: Record<string, JSONValue>,
103
- typesMap: TypesMap
104
- ): unknown {
105
- if (!typeNames) {
106
- return null
107
- }
108
- const filtered = typeNames.filter((n) => n !== 'void')
109
- if (filtered.length === 0) {
110
- return null
111
- }
112
- const parts: unknown[] = []
113
- for (const name of filtered) {
114
- let key: string
115
- try {
116
- key = typesMap.getUniqueName(name)
117
- } catch {
118
- key = name
119
- }
120
- const schema = allSchemas[key]
121
- if (schema) {
122
- parts.push(schema)
123
- }
124
- }
125
- if (parts.length === 0) {
126
- return null
127
- }
128
- return parts.length === 1 ? parts[0] : parts
129
- }
130
-
131
- export function buildCurrentContracts(
132
- functionsMeta: FunctionsMeta,
133
- allSchemas: Record<string, JSONValue>,
134
- typesMap: TypesMap
135
- ): Map<string, ContractEntry> {
136
- const result = new Map<string, ContractEntry>()
137
-
138
- for (const [funcId, meta] of Object.entries(functionsMeta)) {
139
- if (meta.remote === true) {
140
- continue
141
- }
142
-
143
- const parsed = parseVersionedId(funcId)
144
- const functionKey = parsed.baseName
145
- const version = parsed.version ?? meta.version ?? 1
146
-
147
- const inputSchema = resolveSchema(meta.inputs, allSchemas, typesMap)
148
- const outputSchema = resolveSchema(meta.outputs, allSchemas, typesMap)
149
-
150
- const contractHash = computeContractHash({
151
- functionKey,
152
- inputSchema,
153
- outputSchema,
154
- })
155
- const inputHash = computeInputHash(functionKey, inputSchema)
156
- const outputHash = computeOutputHash(functionKey, outputSchema)
157
-
158
- result.set(funcId, {
159
- functionKey,
160
- version,
161
- contractHash,
162
- inputHash,
163
- outputHash,
164
- })
165
- }
166
-
167
- return result
168
- }
169
-
170
- export function computeContractHashes(
171
- allSchemas: Record<string, JSONValue>,
172
- typesMap: TypesMap,
173
- functionsMeta: FunctionsMeta
174
- ): Map<string, ContractEntry> {
175
- const contracts = buildCurrentContracts(functionsMeta, allSchemas, typesMap)
176
-
177
- for (const [funcId, entry] of contracts) {
178
- const meta = functionsMeta[funcId]
179
- if (meta) {
180
- meta.contractHash = entry.contractHash
181
- meta.inputHash = entry.inputHash
182
- meta.outputHash = entry.outputHash
183
- }
184
- }
185
-
186
- return contracts
187
- }
188
-
189
- function groupByFunctionKey(
190
- contracts: Map<string, ContractEntry>
191
- ): Map<string, ContractEntry[]> {
192
- const grouped = new Map<string, ContractEntry[]>()
193
- for (const entry of contracts.values()) {
194
- const existing = grouped.get(entry.functionKey) ?? []
195
- existing.push(entry)
196
- grouped.set(entry.functionKey, existing)
197
- }
198
- return grouped
199
- }
200
-
201
- function entryChanged(
202
- existing: string | VersionHashEntry,
203
- current: ContractEntry
204
- ): boolean {
205
- if (isHashEntry(existing)) {
206
- return (
207
- existing.inputHash !== current.inputHash ||
208
- existing.outputHash !== current.outputHash
209
- )
210
- }
211
- return existing !== current.contractHash
212
- }
213
-
214
- export function validateContracts(
215
- manifest: VersionManifest,
216
- currentContracts: Map<string, ContractEntry>
217
- ): { valid: boolean; errors: VersionValidateError[] } {
218
- const errors: VersionValidateError[] = []
219
- const grouped = groupByFunctionKey(currentContracts)
220
- const reportedKeys = new Set<string>()
221
-
222
- for (const [functionKey, entries] of grouped) {
223
- const manifestEntry = manifest.contracts[functionKey]
224
- if (!manifestEntry) {
225
- continue
226
- }
227
-
228
- for (const current of entries) {
229
- const { version, contractHash, inputHash, outputHash } = current
230
- const existingEntry = manifestEntry.versions[String(version)]
231
-
232
- if (existingEntry !== undefined) {
233
- if (entryChanged(existingEntry, current)) {
234
- reportedKeys.add(`${functionKey}@${version}`)
235
- const prevInputHash = isHashEntry(existingEntry)
236
- ? existingEntry.inputHash
237
- : existingEntry
238
- const prevOutputHash = isHashEntry(existingEntry)
239
- ? existingEntry.outputHash
240
- : existingEntry
241
- errors.push({
242
- code: ErrorCode.FUNCTION_VERSION_MODIFIED,
243
- message: `Contract for ${functionKey}@v${version} has changed (recorded: ${isHashEntry(existingEntry) ? `${existingEntry.inputHash}/${existingEntry.outputHash}` : existingEntry}, current: ${contractHash}). Existing versions are immutable.`,
244
- functionKey,
245
- version,
246
- previousHash: isHashEntry(existingEntry)
247
- ? existingEntry.inputHash
248
- : existingEntry,
249
- currentHash: contractHash,
250
- previousInputHash: prevInputHash,
251
- currentInputHash: inputHash,
252
- previousOutputHash: prevOutputHash,
253
- currentOutputHash: outputHash,
254
- })
255
- }
256
- } else {
257
- if (version <= manifestEntry.latest) {
258
- errors.push({
259
- code: ErrorCode.VERSION_REGRESSION_OR_CONFLICT,
260
- message: `Version ${version} for ${functionKey} is <= latest (${manifestEntry.latest}) but not recorded. Possible merge conflict.`,
261
- functionKey,
262
- version,
263
- latestVersion: manifestEntry.latest,
264
- })
265
- } else if (version > manifestEntry.latest + 1) {
266
- errors.push({
267
- code: ErrorCode.VERSION_GAP_NOT_ALLOWED,
268
- message: `Version ${version} for ${functionKey} skips versions. Latest is ${manifestEntry.latest}, next must be ${manifestEntry.latest + 1}.`,
269
- functionKey,
270
- version,
271
- latestVersion: manifestEntry.latest,
272
- expectedNextVersion: manifestEntry.latest + 1,
273
- })
274
- }
275
- }
276
- }
277
- }
278
-
279
- for (const [functionKey, manifestEntry] of Object.entries(
280
- manifest.contracts
281
- )) {
282
- const latestEntry = manifestEntry.versions[String(manifestEntry.latest)]
283
- const currentEntries = grouped.get(functionKey)
284
- if (!currentEntries) {
285
- continue
286
- }
287
-
288
- const currentLatest = currentEntries.find(
289
- (e) => e.version === manifestEntry.latest
290
- )
291
- if (
292
- currentLatest &&
293
- entryChanged(latestEntry, currentLatest) &&
294
- !reportedKeys.has(`${functionKey}@${manifestEntry.latest}`)
295
- ) {
296
- const prevInputHash = isHashEntry(latestEntry)
297
- ? latestEntry.inputHash
298
- : undefined
299
- const prevOutputHash = isHashEntry(latestEntry)
300
- ? latestEntry.outputHash
301
- : undefined
302
- errors.push({
303
- code: ErrorCode.CONTRACT_CHANGED_REQUIRES_BUMP,
304
- message: `Contract for ${functionKey} changed. Set \`version: ${manifestEntry.latest + 1}\` on the function or run 'pikku versions update'.`,
305
- functionKey,
306
- previousInputHash: prevInputHash,
307
- currentInputHash: currentLatest.inputHash,
308
- previousOutputHash: prevOutputHash,
309
- currentOutputHash: currentLatest.outputHash,
310
- nextVersion: manifestEntry.latest + 1,
311
- })
312
- }
313
- }
314
-
315
- for (const [functionKey, manifestEntry] of Object.entries(
316
- manifest.contracts
317
- )) {
318
- const numericKeys = Object.keys(manifestEntry.versions).map(Number)
319
- if (numericKeys.length === 0) {
320
- continue
321
- }
322
- const maxVersion = Math.max(...numericKeys)
323
- if (manifestEntry.latest !== maxVersion) {
324
- errors.push({
325
- code: ErrorCode.MANIFEST_INTEGRITY_ERROR,
326
- message: `Manifest integrity error for ${functionKey}: latest field (${manifestEntry.latest}) inconsistent with version keys (max: ${maxVersion}).`,
327
- functionKey,
328
- latestVersion: manifestEntry.latest,
329
- expectedNextVersion: maxVersion,
330
- })
331
- }
332
- }
333
-
334
- return { valid: errors.length === 0, errors }
335
- }
336
-
337
- export function updateManifest(
338
- existing: VersionManifest,
339
- currentContracts: Map<string, ContractEntry>
340
- ): VersionManifest {
341
- const manifest: VersionManifest = {
342
- manifestVersion: existing.manifestVersion,
343
- contracts: JSON.parse(JSON.stringify(existing.contracts)),
344
- }
345
-
346
- const grouped = groupByFunctionKey(currentContracts)
347
-
348
- for (const [functionKey, entries] of grouped) {
349
- if (!manifest.contracts[functionKey]) {
350
- manifest.contracts[functionKey] = { latest: 0, versions: {} }
351
- }
352
-
353
- const entry = manifest.contracts[functionKey]
354
- for (const { version, inputHash, outputHash } of entries) {
355
- entry.versions[String(version)] = { inputHash, outputHash }
356
- entry.latest = Math.max(entry.latest, version)
357
- }
358
- }
359
-
360
- return manifest
361
- }
362
-
363
- export function extractContractsFromMeta(
364
- functionsMeta: FunctionsMeta
365
- ): Map<string, ContractEntry> {
366
- const result = new Map<string, ContractEntry>()
367
-
368
- for (const [funcId, meta] of Object.entries(functionsMeta)) {
369
- if (meta.remote === true || !meta.contractHash) {
370
- continue
371
- }
372
-
373
- const parsed = parseVersionedId(funcId)
374
- const functionKey = parsed.baseName
375
- const version = parsed.version ?? meta.version ?? 1
376
-
377
- result.set(funcId, {
378
- functionKey,
379
- version,
380
- contractHash: meta.contractHash,
381
- inputHash: meta.inputHash ?? '',
382
- outputHash: meta.outputHash ?? '',
383
- })
384
- }
385
-
386
- return result
387
- }
@@ -1,124 +0,0 @@
1
- import { test, describe } from 'node:test'
2
- import { strict as assert } from 'node:assert'
3
- import {
4
- generateCustomTypes,
5
- sanitizeTypeName,
6
- } from './custom-types-generator.js'
7
- import { TypesMap } from '../types-map.js'
8
-
9
- function makeTypesMap(
10
- entries: Record<string, { type: string; references: string[] }>
11
- ): TypesMap {
12
- const tm = new TypesMap()
13
- for (const [name, { type, references }] of Object.entries(entries)) {
14
- tm.addCustomType(name, type, references)
15
- }
16
- return tm
17
- }
18
-
19
- describe('generateCustomTypes — classification wrapper stripping', () => {
20
- test('strips Private<T> wrapper from type alias', () => {
21
- const tm = makeTypesMap({
22
- UserEmail: { type: 'Private<string>', references: ['Private'] },
23
- })
24
- const result = generateCustomTypes(tm, new Set())
25
- assert.match(result, /UserEmail/, 'should emit UserEmail alias')
26
- assert.match(
27
- result,
28
- /= string/,
29
- 'Private<string> should be stripped to string'
30
- )
31
- assert.doesNotMatch(result, /Private/, 'Private wrapper must be removed')
32
- })
33
-
34
- test('strips Secret<T> wrapper from type alias', () => {
35
- const tm = makeTypesMap({
36
- HashedPw: { type: 'Secret<string>', references: ['Secret'] },
37
- })
38
- const result = generateCustomTypes(tm, new Set())
39
- assert.match(result, /HashedPw/)
40
- assert.match(
41
- result,
42
- /= string/,
43
- 'Secret<string> should be stripped to string'
44
- )
45
- assert.doesNotMatch(result, /Secret/, 'Secret wrapper must be removed')
46
- })
47
-
48
- test('strips Pii<T> wrapper from type alias', () => {
49
- const tm = makeTypesMap({
50
- UserPhone: { type: 'Pii<string>', references: ['Pii'] },
51
- })
52
- const result = generateCustomTypes(tm, new Set())
53
- assert.match(result, /UserPhone/)
54
- assert.match(result, /= string/, 'Pii<string> should be stripped to string')
55
- assert.doesNotMatch(result, /Pii/, 'Pii wrapper must be removed')
56
- })
57
-
58
- test('strips nested classification wrappers', () => {
59
- const tm = makeTypesMap({
60
- Combo: {
61
- type: 'Private<Secret<string>>',
62
- references: ['Private', 'Secret'],
63
- },
64
- })
65
- const result = generateCustomTypes(tm, new Set())
66
- assert.match(result, /Combo/)
67
- assert.match(
68
- result,
69
- /= string/,
70
- 'nested wrappers should resolve to inner type'
71
- )
72
- assert.doesNotMatch(
73
- result,
74
- /Private|Secret/,
75
- 'all wrappers must be removed'
76
- )
77
- })
78
-
79
- test('does not strip non-classification type names', () => {
80
- const tm = makeTypesMap({
81
- MyType: { type: 'SomeOtherType', references: ['SomeOtherType'] },
82
- })
83
- const required = new Set<string>()
84
- generateCustomTypes(tm, required)
85
- assert.ok(
86
- required.has('SomeOtherType'),
87
- 'non-classification references must remain in requiredTypes'
88
- )
89
- })
90
-
91
- test('classification wrapper references are not added to requiredTypes', () => {
92
- const tm = makeTypesMap({
93
- SensitiveField: { type: 'Private<string>', references: ['Private'] },
94
- })
95
- const required = new Set<string>()
96
- generateCustomTypes(tm, required)
97
- assert.ok(
98
- !required.has('Private'),
99
- 'Private must not be added to requiredTypes'
100
- )
101
- })
102
- })
103
-
104
- describe('sanitizeTypeName — valid JS identifier', () => {
105
- test('replaces disallowed characters with underscores', () => {
106
- assert.equal(
107
- sanitizeTypeName('Add Beehiiv subscribers'),
108
- 'Add_Beehiiv_subscribers'
109
- )
110
- })
111
-
112
- test('prefixes an underscore when the name starts with a digit', () => {
113
- // A name like "2. Add Beehiiv newsletter subscribers" otherwise produces
114
- // `2__Add_...`, an invalid JS identifier that breaks the generated
115
- // `import <ident> from ...` in pikku-workflow-wirings-meta.gen.ts.
116
- const id = sanitizeTypeName('2. Add Beehiiv newsletter subscribers')
117
- assert.equal(id, '_2__Add_Beehiiv_newsletter_subscribers')
118
- assert.match(id, /^[A-Za-z_$]/, 'must start with a valid identifier char')
119
- })
120
-
121
- test('leaves an already-valid identifier unchanged', () => {
122
- assert.equal(sanitizeTypeName('UserEmail'), 'UserEmail')
123
- })
124
- })
@@ -1,150 +0,0 @@
1
- import type { TypesMap } from '../types-map.js'
2
-
3
- /**
4
- * NOTE: Code generation normally belongs in @pikku/cli, not the inspector.
5
- * This is here because the schema generator needs the custom types content
6
- * as a virtual TypeScript source file (in-memory, no disk write) so that
7
- * ts-json-schema-generator can discover inline/custom types from typesMap.
8
- */
9
-
10
- export function sanitizeTypeName(name: string): string {
11
- const sanitized = name.replace(/[^a-zA-Z0-9_$]/g, '_')
12
- return /^[0-9]/.test(sanitized) ? `_${sanitized}` : sanitized
13
- }
14
-
15
- const CLASSIFICATION_WRAPPERS = new Set(['Private', 'Pii', 'Secret'])
16
-
17
- function findMatchingAngleBracket(type: string, startIndex: number): number {
18
- let depth = 0
19
- for (let i = startIndex; i < type.length; i += 1) {
20
- const char = type[i]
21
- if (char === '<') {
22
- depth += 1
23
- continue
24
- }
25
- if (char === '>' && (i === 0 || type[i - 1] !== '=')) {
26
- depth -= 1
27
- if (depth === 0) {
28
- return i
29
- }
30
- }
31
- }
32
- return -1
33
- }
34
-
35
- function stripClassificationWrappers(type: string): string {
36
- let output = ''
37
- let index = 0
38
-
39
- while (index < type.length) {
40
- const char = type[index]
41
- if (!/[A-Za-z_$]/.test(char)) {
42
- output += char
43
- index += 1
44
- continue
45
- }
46
-
47
- let end = index + 1
48
- while (end < type.length && /[A-Za-z0-9_$]/.test(type[end])) {
49
- end += 1
50
- }
51
-
52
- const identifier = type.slice(index, end)
53
- if (CLASSIFICATION_WRAPPERS.has(identifier) && type[end] === '<') {
54
- const closingIndex = findMatchingAngleBracket(type, end)
55
- if (closingIndex !== -1) {
56
- const inner = type.slice(end + 1, closingIndex)
57
- output += stripClassificationWrappers(inner)
58
- index = closingIndex + 1
59
- continue
60
- }
61
- }
62
-
63
- output += identifier
64
- index = end
65
- }
66
-
67
- return output
68
- }
69
-
70
- export function generateCustomTypes(
71
- typesMap: TypesMap,
72
- requiredTypes: Set<string>
73
- ) {
74
- const typeDeclarations = Array.from(typesMap.customTypes.entries())
75
- .sort(([a], [b]) => a.localeCompare(b))
76
- .filter(([_name, { type }]) => {
77
- const hasUndefinedGeneric =
78
- /\b(Name|In|Out|Key)\b/.test(type) && /\[.*\]/.test(type)
79
- return !hasUndefinedGeneric
80
- })
81
- .map(([originalName, { type, references }]) => {
82
- const name = sanitizeTypeName(originalName)
83
- references.forEach((refName) => {
84
- if (
85
- refName !== '__object' &&
86
- !refName.startsWith('__object_') &&
87
- !CLASSIFICATION_WRAPPERS.has(refName)
88
- ) {
89
- requiredTypes.add(refName)
90
- }
91
- })
92
-
93
- const typeString = stripClassificationWrappers(type)
94
- const typeNameRegex = /\b[A-Z][a-zA-Z0-9]*\b/g
95
- const potentialTypes = typeString.match(typeNameRegex) || []
96
-
97
- potentialTypes.forEach((typeName) => {
98
- if (
99
- typeString.includes(`"${typeName}"`) ||
100
- [
101
- 'Pick',
102
- 'Omit',
103
- 'Partial',
104
- 'Required',
105
- 'Record',
106
- 'Readonly',
107
- ].includes(typeName)
108
- ) {
109
- return
110
- }
111
-
112
- try {
113
- const typeMeta = typesMap.getTypeMeta(typeName)
114
- if (typeMeta.path) {
115
- requiredTypes.add(typeMeta.originalName)
116
- }
117
- } catch {
118
- // Type not found in map (ambient/builtin type)
119
- }
120
- })
121
-
122
- if (name === typeString) return null
123
- return `export type ${name} = ${typeString}`
124
- })
125
-
126
- const importsByPath = new Map<string, Set<string>>()
127
- for (const typeName of requiredTypes) {
128
- if (CLASSIFICATION_WRAPPERS.has(typeName)) continue
129
- try {
130
- const typeMeta = typesMap.getTypeMeta(typeName)
131
- if (typeMeta.path) {
132
- if (!importsByPath.has(typeMeta.path)) {
133
- importsByPath.set(typeMeta.path, new Set())
134
- }
135
- importsByPath.get(typeMeta.path)!.add(typeMeta.originalName)
136
- }
137
- } catch {
138
- // Type not found in map
139
- }
140
- }
141
-
142
- const importLines = Array.from(importsByPath.entries())
143
- .map(
144
- ([path, types]) =>
145
- `import type { ${Array.from(types).join(', ')} } from '${path}'`
146
- )
147
- .join('\n')
148
-
149
- return `${importLines}\n\n${typeDeclarations.filter(Boolean).join('\n')}`
150
- }