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