@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
package/src/add/add-cli.ts
DELETED
|
@@ -1,1006 +0,0 @@
|
|
|
1
|
-
import type { TypeChecker } from 'typescript'
|
|
2
|
-
import ts from 'typescript'
|
|
3
|
-
import type {
|
|
4
|
-
AddWiring,
|
|
5
|
-
InspectorLogger,
|
|
6
|
-
InspectorOptions,
|
|
7
|
-
InspectorState,
|
|
8
|
-
} from '../types.js'
|
|
9
|
-
import type { CLIProgramMeta, CLICommandMeta } from '@pikku/core/cli'
|
|
10
|
-
import {
|
|
11
|
-
extractFunctionName,
|
|
12
|
-
makeContextBasedId,
|
|
13
|
-
} from '../utils/extract-function-name.js'
|
|
14
|
-
import { resolveCoreType } from '../utils/resolve-core-type.js'
|
|
15
|
-
import { resolveMiddleware } from '../utils/middleware.js'
|
|
16
|
-
import { resolveFunctionMeta } from '../utils/resolve-function-meta.js'
|
|
17
|
-
import { extractWireNames } from '../utils/post-process.js'
|
|
18
|
-
import { getPropertyValue } from '../utils/get-property-value.js'
|
|
19
|
-
import { resolveIdentifier } from '../utils/resolve-identifier.js'
|
|
20
|
-
import { resolveAddonName } from '../utils/resolve-addon-package.js'
|
|
21
|
-
import { validateAuthSessionless } from '../utils/validate-auth-sessionless.js'
|
|
22
|
-
import { extractServicesFromFunction } from '../utils/extract-services.js'
|
|
23
|
-
import { getExportedVariableName } from '../utils/get-exported-variable-name.js'
|
|
24
|
-
import { resolveRefContract } from '../utils/resolve-ref-contract.js'
|
|
25
|
-
|
|
26
|
-
// Track if we've warned about missing Config type to avoid duplicate warnings
|
|
27
|
-
const configTypeWarningShown = new Set<string>()
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Adds CLI command metadata to the inspector state
|
|
31
|
-
*/
|
|
32
|
-
export const addCLI: AddWiring = (
|
|
33
|
-
logger,
|
|
34
|
-
node,
|
|
35
|
-
typeChecker,
|
|
36
|
-
inspectorState,
|
|
37
|
-
options
|
|
38
|
-
) => {
|
|
39
|
-
if (!ts.isCallExpression(node)) return
|
|
40
|
-
if (
|
|
41
|
-
ts.isIdentifier(node.expression) &&
|
|
42
|
-
node.expression.text === 'defineCLICommands'
|
|
43
|
-
) {
|
|
44
|
-
const exportName = getExportedVariableName(node, options.sourceFile)
|
|
45
|
-
const [firstArg] = node.arguments
|
|
46
|
-
if (exportName && firstArg && ts.isObjectLiteralExpression(firstArg)) {
|
|
47
|
-
inspectorState.exportedContracts.cli[exportName] = processCommands(
|
|
48
|
-
logger,
|
|
49
|
-
firstArg,
|
|
50
|
-
node.getSourceFile(),
|
|
51
|
-
typeChecker,
|
|
52
|
-
exportName,
|
|
53
|
-
inspectorState,
|
|
54
|
-
options
|
|
55
|
-
)
|
|
56
|
-
}
|
|
57
|
-
return
|
|
58
|
-
}
|
|
59
|
-
// Check if this is a wireCLI call
|
|
60
|
-
if (!node || !node.expression) {
|
|
61
|
-
return
|
|
62
|
-
}
|
|
63
|
-
const expression = node.expression
|
|
64
|
-
if (!ts.isIdentifier(expression) || expression.text !== 'wireCLI') {
|
|
65
|
-
return
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Get the argument (should be an object literal)
|
|
69
|
-
if (node.arguments.length !== 1) {
|
|
70
|
-
return
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const arg = node.arguments[0]
|
|
74
|
-
if (!ts.isObjectLiteralExpression(arg)) {
|
|
75
|
-
return
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const sourceFile = node.getSourceFile()
|
|
79
|
-
|
|
80
|
-
// Add to files set
|
|
81
|
-
inspectorState.cli.files.add(sourceFile.fileName)
|
|
82
|
-
|
|
83
|
-
// Process the CLI configuration
|
|
84
|
-
const cliConfig = processCLIConfig(
|
|
85
|
-
logger,
|
|
86
|
-
arg,
|
|
87
|
-
sourceFile,
|
|
88
|
-
typeChecker,
|
|
89
|
-
inspectorState,
|
|
90
|
-
options
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
if (!cliConfig) {
|
|
94
|
-
return
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Add this program to the CLI metadata
|
|
98
|
-
inspectorState.cli.meta.programs[cliConfig.programName] =
|
|
99
|
-
cliConfig.programMeta
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Processes a CLI configuration object
|
|
104
|
-
*/
|
|
105
|
-
function processCLIConfig(
|
|
106
|
-
logger: InspectorLogger,
|
|
107
|
-
node: ts.ObjectLiteralExpression,
|
|
108
|
-
sourceFile: ts.SourceFile,
|
|
109
|
-
typeChecker: TypeChecker,
|
|
110
|
-
inspectorState: InspectorState,
|
|
111
|
-
options: InspectorOptions
|
|
112
|
-
): { programName: string; programMeta: CLIProgramMeta } | null {
|
|
113
|
-
let programName = ''
|
|
114
|
-
let programTags: string[] | undefined
|
|
115
|
-
const programMeta: CLIProgramMeta = {
|
|
116
|
-
program: '',
|
|
117
|
-
commands: {},
|
|
118
|
-
options: {},
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// First pass: extract program name and tags
|
|
122
|
-
for (const prop of node.properties) {
|
|
123
|
-
if (!ts.isPropertyAssignment(prop)) continue
|
|
124
|
-
if (!ts.isIdentifier(prop.name)) continue
|
|
125
|
-
|
|
126
|
-
const propName = prop.name.text
|
|
127
|
-
|
|
128
|
-
if (propName === 'program' && ts.isStringLiteral(prop.initializer)) {
|
|
129
|
-
programName = prop.initializer.text
|
|
130
|
-
programMeta.program = programName
|
|
131
|
-
} else if (propName === 'tags') {
|
|
132
|
-
programTags = (getPropertyValue(node, 'tags') as string[]) || undefined
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
if (!programName) {
|
|
137
|
-
return null
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if (getPropertyValue(node, 'disabled') === true) {
|
|
141
|
-
return null
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Second pass: process other properties with program tags available
|
|
145
|
-
for (const prop of node.properties) {
|
|
146
|
-
if (!ts.isPropertyAssignment(prop)) continue
|
|
147
|
-
if (!ts.isIdentifier(prop.name)) continue
|
|
148
|
-
|
|
149
|
-
const propName = prop.name.text
|
|
150
|
-
|
|
151
|
-
switch (propName) {
|
|
152
|
-
case 'program':
|
|
153
|
-
case 'tags':
|
|
154
|
-
// Already handled in first pass
|
|
155
|
-
break
|
|
156
|
-
|
|
157
|
-
case 'auth': {
|
|
158
|
-
const value = getPropertyValue(node, 'auth')
|
|
159
|
-
if (typeof value === 'boolean') {
|
|
160
|
-
programMeta.auth = value
|
|
161
|
-
}
|
|
162
|
-
break
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
case 'commands':
|
|
166
|
-
if (ts.isObjectLiteralExpression(prop.initializer)) {
|
|
167
|
-
programMeta.commands = processCommands(
|
|
168
|
-
logger,
|
|
169
|
-
prop.initializer,
|
|
170
|
-
sourceFile,
|
|
171
|
-
typeChecker,
|
|
172
|
-
programName,
|
|
173
|
-
inspectorState,
|
|
174
|
-
options,
|
|
175
|
-
programTags
|
|
176
|
-
)
|
|
177
|
-
}
|
|
178
|
-
break
|
|
179
|
-
|
|
180
|
-
case 'options':
|
|
181
|
-
if (ts.isObjectLiteralExpression(prop.initializer)) {
|
|
182
|
-
programMeta.options = processOptions(
|
|
183
|
-
logger,
|
|
184
|
-
prop.initializer,
|
|
185
|
-
typeChecker,
|
|
186
|
-
inspectorState,
|
|
187
|
-
options
|
|
188
|
-
)
|
|
189
|
-
}
|
|
190
|
-
break
|
|
191
|
-
|
|
192
|
-
case 'render': {
|
|
193
|
-
let renderFuncId = extractFunctionName(
|
|
194
|
-
prop.initializer,
|
|
195
|
-
typeChecker,
|
|
196
|
-
inspectorState.rootDir
|
|
197
|
-
).pikkuFuncId
|
|
198
|
-
if (renderFuncId.startsWith('__temp_')) {
|
|
199
|
-
renderFuncId = makeContextBasedId('cli-render', programName)
|
|
200
|
-
}
|
|
201
|
-
programMeta.defaultRenderName = renderFuncId
|
|
202
|
-
break
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
return { programName, programMeta }
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Processes the commands object
|
|
212
|
-
*/
|
|
213
|
-
function processCommands(
|
|
214
|
-
logger: InspectorLogger,
|
|
215
|
-
node: ts.ObjectLiteralExpression,
|
|
216
|
-
sourceFile: ts.SourceFile,
|
|
217
|
-
typeChecker: TypeChecker,
|
|
218
|
-
programName: string,
|
|
219
|
-
inspectorState: InspectorState,
|
|
220
|
-
options: InspectorOptions,
|
|
221
|
-
programTags?: string[]
|
|
222
|
-
): Record<string, CLICommandMeta> {
|
|
223
|
-
const commands: Record<string, CLICommandMeta> = {}
|
|
224
|
-
let defaultCommandName: string | null = null
|
|
225
|
-
|
|
226
|
-
for (const prop of node.properties) {
|
|
227
|
-
// Handle spread assignments: { ...externalCommands }
|
|
228
|
-
if (ts.isSpreadAssignment(prop)) {
|
|
229
|
-
let spreadTarget: ts.Node | undefined = prop.expression
|
|
230
|
-
if (ts.isIdentifier(prop.expression)) {
|
|
231
|
-
spreadTarget = resolveIdentifier(prop.expression, typeChecker, [
|
|
232
|
-
'defineCLICommands',
|
|
233
|
-
])
|
|
234
|
-
}
|
|
235
|
-
if (spreadTarget && ts.isObjectLiteralExpression(spreadTarget)) {
|
|
236
|
-
const spreadCommands = processCommands(
|
|
237
|
-
logger,
|
|
238
|
-
spreadTarget,
|
|
239
|
-
sourceFile,
|
|
240
|
-
typeChecker,
|
|
241
|
-
programName,
|
|
242
|
-
inspectorState,
|
|
243
|
-
options,
|
|
244
|
-
programTags
|
|
245
|
-
)
|
|
246
|
-
Object.assign(commands, spreadCommands)
|
|
247
|
-
} else {
|
|
248
|
-
const refCommands = resolveRefContract(
|
|
249
|
-
prop.expression,
|
|
250
|
-
'refCLI',
|
|
251
|
-
inspectorState.exportedContracts.addonCli
|
|
252
|
-
)
|
|
253
|
-
if (refCommands) {
|
|
254
|
-
Object.assign(commands, refCommands.contract)
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
continue
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
if (!ts.isPropertyAssignment(prop)) continue
|
|
261
|
-
|
|
262
|
-
const commandName = getPropertyName(prop)
|
|
263
|
-
if (!commandName) continue
|
|
264
|
-
|
|
265
|
-
const commandMeta = processCommand(
|
|
266
|
-
logger,
|
|
267
|
-
inspectorState,
|
|
268
|
-
options,
|
|
269
|
-
commandName,
|
|
270
|
-
prop.initializer,
|
|
271
|
-
sourceFile,
|
|
272
|
-
typeChecker,
|
|
273
|
-
programName,
|
|
274
|
-
[],
|
|
275
|
-
programTags
|
|
276
|
-
)
|
|
277
|
-
|
|
278
|
-
if (commandMeta) {
|
|
279
|
-
commands[commandName] = commandMeta
|
|
280
|
-
|
|
281
|
-
// Validate only one default command
|
|
282
|
-
if (commandMeta.isDefault) {
|
|
283
|
-
if (defaultCommandName !== null) {
|
|
284
|
-
const position = prop.getStart(sourceFile)
|
|
285
|
-
const { line, character } =
|
|
286
|
-
sourceFile.getLineAndCharacterOfPosition(position)
|
|
287
|
-
|
|
288
|
-
throw new Error(
|
|
289
|
-
`Multiple default commands found in CLI program "${programName}" at ${sourceFile.fileName}:${line + 1}:${character + 1}.\n` +
|
|
290
|
-
`Commands "${defaultCommandName}" and "${commandName}" are both marked as default.\n` +
|
|
291
|
-
`Only one command can be marked as default per program.`
|
|
292
|
-
)
|
|
293
|
-
}
|
|
294
|
-
defaultCommandName = commandName
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
return commands
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* Processes a single command
|
|
304
|
-
*/
|
|
305
|
-
function processCommand(
|
|
306
|
-
logger: InspectorLogger,
|
|
307
|
-
inspectorState: InspectorState,
|
|
308
|
-
options: InspectorOptions,
|
|
309
|
-
name: string,
|
|
310
|
-
node: ts.Expression,
|
|
311
|
-
sourceFile: ts.SourceFile,
|
|
312
|
-
typeChecker: TypeChecker,
|
|
313
|
-
programName: string,
|
|
314
|
-
parentPath: string[] = [],
|
|
315
|
-
programTags?: string[]
|
|
316
|
-
): CLICommandMeta | null {
|
|
317
|
-
const fullPath = [...parentPath, name]
|
|
318
|
-
|
|
319
|
-
// Handle shorthand (just a function)
|
|
320
|
-
if (
|
|
321
|
-
ts.isIdentifier(node) ||
|
|
322
|
-
ts.isArrowFunction(node) ||
|
|
323
|
-
ts.isFunctionExpression(node)
|
|
324
|
-
) {
|
|
325
|
-
let pikkuFuncId = extractFunctionName(
|
|
326
|
-
node,
|
|
327
|
-
typeChecker,
|
|
328
|
-
inspectorState.rootDir
|
|
329
|
-
).pikkuFuncId
|
|
330
|
-
if (pikkuFuncId.startsWith('__temp_')) {
|
|
331
|
-
pikkuFuncId = makeContextBasedId('cli', programName, ...fullPath)
|
|
332
|
-
}
|
|
333
|
-
return {
|
|
334
|
-
pikkuFuncId,
|
|
335
|
-
positionals: [],
|
|
336
|
-
options: {},
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// Handle pikkuCLICommand calls
|
|
341
|
-
if (ts.isCallExpression(node)) {
|
|
342
|
-
// Check if it's a pikkuCLICommand call
|
|
343
|
-
if (
|
|
344
|
-
ts.isIdentifier(node.expression) &&
|
|
345
|
-
node.expression.text === 'pikkuCLICommand' &&
|
|
346
|
-
node.arguments.length > 0 &&
|
|
347
|
-
ts.isObjectLiteralExpression(node.arguments[0])
|
|
348
|
-
) {
|
|
349
|
-
// Process the object literal argument
|
|
350
|
-
return processCommand(
|
|
351
|
-
logger,
|
|
352
|
-
inspectorState,
|
|
353
|
-
options,
|
|
354
|
-
name,
|
|
355
|
-
node.arguments[0],
|
|
356
|
-
sourceFile,
|
|
357
|
-
typeChecker,
|
|
358
|
-
programName,
|
|
359
|
-
parentPath,
|
|
360
|
-
programTags
|
|
361
|
-
)
|
|
362
|
-
}
|
|
363
|
-
return null
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
// Handle full command object
|
|
367
|
-
if (!ts.isObjectLiteralExpression(node)) {
|
|
368
|
-
return null
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
const meta: CLICommandMeta = {
|
|
372
|
-
pikkuFuncId: '',
|
|
373
|
-
positionals: [],
|
|
374
|
-
options: {},
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
// First pass: extract pikkuFuncId and tags so we can use them when processing options/middleware
|
|
378
|
-
let pikkuFuncId: string | undefined
|
|
379
|
-
let optionsNode: ts.ObjectLiteralExpression | undefined
|
|
380
|
-
let tags: string[] | undefined
|
|
381
|
-
|
|
382
|
-
for (const prop of node.properties) {
|
|
383
|
-
if (!ts.isPropertyAssignment(prop)) continue
|
|
384
|
-
if (!ts.isIdentifier(prop.name)) continue
|
|
385
|
-
|
|
386
|
-
const propName = prop.name.text
|
|
387
|
-
|
|
388
|
-
if (propName === 'func') {
|
|
389
|
-
if (
|
|
390
|
-
ts.isCallExpression(prop.initializer) &&
|
|
391
|
-
ts.isIdentifier(prop.initializer.expression) &&
|
|
392
|
-
prop.initializer.expression.text === 'ref'
|
|
393
|
-
) {
|
|
394
|
-
const [firstArg] = prop.initializer.arguments
|
|
395
|
-
if (!firstArg || !ts.isStringLiteral(firstArg)) {
|
|
396
|
-
throw new Error(`ref() call requires a string literal argument`)
|
|
397
|
-
}
|
|
398
|
-
pikkuFuncId = firstArg.text
|
|
399
|
-
const addonNamespace = pikkuFuncId.includes(':')
|
|
400
|
-
? pikkuFuncId.split(':')[0]
|
|
401
|
-
: null
|
|
402
|
-
if (addonNamespace) {
|
|
403
|
-
if (!inspectorState.rpc.wireAddonDeclarations.has(addonNamespace)) {
|
|
404
|
-
throw new Error(
|
|
405
|
-
`Unknown addon namespace "${addonNamespace}" in "${pikkuFuncId}": no matching wireAddonDeclarations entry found`
|
|
406
|
-
)
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
meta.pikkuFuncId = pikkuFuncId
|
|
410
|
-
if (addonNamespace) {
|
|
411
|
-
meta.packageName =
|
|
412
|
-
inspectorState.rpc.wireAddonDeclarations.get(
|
|
413
|
-
addonNamespace
|
|
414
|
-
)!.package
|
|
415
|
-
}
|
|
416
|
-
} else {
|
|
417
|
-
pikkuFuncId = extractFunctionName(
|
|
418
|
-
prop.initializer,
|
|
419
|
-
typeChecker,
|
|
420
|
-
inspectorState.rootDir
|
|
421
|
-
).pikkuFuncId
|
|
422
|
-
if (pikkuFuncId.startsWith('__temp_')) {
|
|
423
|
-
pikkuFuncId = makeContextBasedId('cli', programName, ...fullPath)
|
|
424
|
-
}
|
|
425
|
-
meta.pikkuFuncId = pikkuFuncId
|
|
426
|
-
const cliPackageName = ts.isIdentifier(prop.initializer)
|
|
427
|
-
? resolveAddonName(
|
|
428
|
-
prop.initializer,
|
|
429
|
-
typeChecker,
|
|
430
|
-
inspectorState.rpc.wireAddonDeclarations
|
|
431
|
-
)
|
|
432
|
-
: null
|
|
433
|
-
if (cliPackageName) {
|
|
434
|
-
meta.packageName = cliPackageName
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
} else if (
|
|
438
|
-
propName === 'options' &&
|
|
439
|
-
ts.isObjectLiteralExpression(prop.initializer)
|
|
440
|
-
) {
|
|
441
|
-
optionsNode = prop.initializer
|
|
442
|
-
} else if (propName === 'tags') {
|
|
443
|
-
tags = (getPropertyValue(node, 'tags') as string[]) || undefined
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
// Merge program-level tags with command-level tags
|
|
448
|
-
const allTags = [...(programTags || []), ...(tags || [])]
|
|
449
|
-
|
|
450
|
-
// Resolve middleware
|
|
451
|
-
const middleware = resolveMiddleware(
|
|
452
|
-
inspectorState,
|
|
453
|
-
node,
|
|
454
|
-
allTags.length > 0 ? allTags : undefined,
|
|
455
|
-
typeChecker
|
|
456
|
-
)
|
|
457
|
-
if (middleware) {
|
|
458
|
-
meta.middleware = middleware
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
// Add merged tags to metadata
|
|
462
|
-
if (allTags.length > 0) {
|
|
463
|
-
meta.tags = allTags
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
// Second pass: process all properties
|
|
467
|
-
for (const prop of node.properties) {
|
|
468
|
-
if (!ts.isPropertyAssignment(prop)) continue
|
|
469
|
-
if (!ts.isIdentifier(prop.name)) continue
|
|
470
|
-
|
|
471
|
-
const propName = prop.name.text
|
|
472
|
-
|
|
473
|
-
switch (propName) {
|
|
474
|
-
case 'parameters':
|
|
475
|
-
if (ts.isStringLiteral(prop.initializer)) {
|
|
476
|
-
meta.parameters = prop.initializer.text
|
|
477
|
-
meta.positionals = parseCommandPattern(prop.initializer.text)
|
|
478
|
-
}
|
|
479
|
-
break
|
|
480
|
-
|
|
481
|
-
case 'description':
|
|
482
|
-
if (ts.isStringLiteral(prop.initializer)) {
|
|
483
|
-
meta.description = prop.initializer.text
|
|
484
|
-
}
|
|
485
|
-
break
|
|
486
|
-
|
|
487
|
-
case 'func':
|
|
488
|
-
// Already handled in first pass
|
|
489
|
-
break
|
|
490
|
-
|
|
491
|
-
case 'render': {
|
|
492
|
-
let renderFuncId = extractFunctionName(
|
|
493
|
-
prop.initializer,
|
|
494
|
-
typeChecker,
|
|
495
|
-
inspectorState.rootDir
|
|
496
|
-
).pikkuFuncId
|
|
497
|
-
if (renderFuncId.startsWith('__temp_')) {
|
|
498
|
-
renderFuncId = makeContextBasedId(
|
|
499
|
-
'cli-render',
|
|
500
|
-
programName,
|
|
501
|
-
...fullPath
|
|
502
|
-
)
|
|
503
|
-
}
|
|
504
|
-
meta.renderName = renderFuncId
|
|
505
|
-
break
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
case 'options':
|
|
509
|
-
// Process with pikkuFuncId from first pass
|
|
510
|
-
if (optionsNode) {
|
|
511
|
-
meta.options = processOptions(
|
|
512
|
-
logger,
|
|
513
|
-
optionsNode,
|
|
514
|
-
typeChecker,
|
|
515
|
-
inspectorState,
|
|
516
|
-
options,
|
|
517
|
-
pikkuFuncId
|
|
518
|
-
)
|
|
519
|
-
}
|
|
520
|
-
break
|
|
521
|
-
|
|
522
|
-
case 'subcommands': {
|
|
523
|
-
let subcommandsNode: ts.Node | undefined = prop.initializer
|
|
524
|
-
if (ts.isIdentifier(prop.initializer)) {
|
|
525
|
-
subcommandsNode = resolveIdentifier(prop.initializer, typeChecker, [
|
|
526
|
-
'defineCLICommands',
|
|
527
|
-
])
|
|
528
|
-
}
|
|
529
|
-
if (subcommandsNode && ts.isObjectLiteralExpression(subcommandsNode)) {
|
|
530
|
-
meta.subcommands = {}
|
|
531
|
-
for (const subProp of subcommandsNode.properties) {
|
|
532
|
-
if (!ts.isPropertyAssignment(subProp)) continue
|
|
533
|
-
|
|
534
|
-
const subName = getPropertyName(subProp)
|
|
535
|
-
if (!subName) continue
|
|
536
|
-
|
|
537
|
-
const subCommand = processCommand(
|
|
538
|
-
logger,
|
|
539
|
-
inspectorState,
|
|
540
|
-
options,
|
|
541
|
-
subName,
|
|
542
|
-
subProp.initializer,
|
|
543
|
-
sourceFile,
|
|
544
|
-
typeChecker,
|
|
545
|
-
programName,
|
|
546
|
-
fullPath,
|
|
547
|
-
programTags
|
|
548
|
-
)
|
|
549
|
-
|
|
550
|
-
if (subCommand) {
|
|
551
|
-
meta.subcommands[subName] = subCommand
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
break
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
case 'isDefault':
|
|
559
|
-
if (
|
|
560
|
-
prop.initializer.kind === ts.SyntaxKind.TrueKeyword ||
|
|
561
|
-
prop.initializer.kind === ts.SyntaxKind.FalseKeyword
|
|
562
|
-
) {
|
|
563
|
-
meta.isDefault = prop.initializer.kind === ts.SyntaxKind.TrueKeyword
|
|
564
|
-
}
|
|
565
|
-
break
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
// --- validate auth/sessionless ---
|
|
570
|
-
if (
|
|
571
|
-
meta.pikkuFuncId &&
|
|
572
|
-
!validateAuthSessionless(
|
|
573
|
-
logger,
|
|
574
|
-
node,
|
|
575
|
-
inspectorState,
|
|
576
|
-
meta.pikkuFuncId,
|
|
577
|
-
`CLI command '${name}'`
|
|
578
|
-
)
|
|
579
|
-
) {
|
|
580
|
-
return null
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
// --- track used functions/middleware for service aggregation ---
|
|
584
|
-
inspectorState.serviceAggregation.usedFunctions.add(meta.pikkuFuncId)
|
|
585
|
-
extractWireNames(meta.middleware).forEach((name) =>
|
|
586
|
-
inspectorState.serviceAggregation.usedMiddleware.add(name)
|
|
587
|
-
)
|
|
588
|
-
|
|
589
|
-
return meta
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
/**
|
|
593
|
-
* Processes CLI options and extracts enum values from function input types
|
|
594
|
-
*/
|
|
595
|
-
function processOptions(
|
|
596
|
-
logger: InspectorLogger,
|
|
597
|
-
node: ts.ObjectLiteralExpression,
|
|
598
|
-
typeChecker: TypeChecker,
|
|
599
|
-
inspectorState: InspectorState,
|
|
600
|
-
inspectorOptions: InspectorOptions,
|
|
601
|
-
pikkuFuncId?: string
|
|
602
|
-
): Record<string, any> {
|
|
603
|
-
const options: Record<string, any> = {}
|
|
604
|
-
|
|
605
|
-
for (const prop of node.properties) {
|
|
606
|
-
if (!ts.isPropertyAssignment(prop)) continue
|
|
607
|
-
|
|
608
|
-
const optionName = getPropertyName(prop)
|
|
609
|
-
if (!optionName) continue
|
|
610
|
-
|
|
611
|
-
if (ts.isObjectLiteralExpression(prop.initializer)) {
|
|
612
|
-
const option: any = {}
|
|
613
|
-
let manualChoices: string[] | undefined
|
|
614
|
-
|
|
615
|
-
for (const optProp of prop.initializer.properties) {
|
|
616
|
-
if (!ts.isPropertyAssignment(optProp)) continue
|
|
617
|
-
if (!ts.isIdentifier(optProp.name)) continue
|
|
618
|
-
|
|
619
|
-
const optPropName = optProp.name.text
|
|
620
|
-
|
|
621
|
-
switch (optPropName) {
|
|
622
|
-
case 'description':
|
|
623
|
-
if (ts.isStringLiteral(optProp.initializer)) {
|
|
624
|
-
option.description = optProp.initializer.text
|
|
625
|
-
}
|
|
626
|
-
break
|
|
627
|
-
|
|
628
|
-
case 'short':
|
|
629
|
-
if (ts.isStringLiteral(optProp.initializer)) {
|
|
630
|
-
option.short = optProp.initializer.text
|
|
631
|
-
}
|
|
632
|
-
break
|
|
633
|
-
|
|
634
|
-
case 'type':
|
|
635
|
-
if (ts.isStringLiteral(optProp.initializer)) {
|
|
636
|
-
option.type = optProp.initializer.text
|
|
637
|
-
}
|
|
638
|
-
break
|
|
639
|
-
|
|
640
|
-
case 'default':
|
|
641
|
-
// Extract default value from expression
|
|
642
|
-
if (ts.isStringLiteral(optProp.initializer)) {
|
|
643
|
-
option.default = optProp.initializer.text
|
|
644
|
-
} else if (ts.isNumericLiteral(optProp.initializer)) {
|
|
645
|
-
option.default = parseFloat(optProp.initializer.text)
|
|
646
|
-
} else if (optProp.initializer.kind === ts.SyntaxKind.TrueKeyword) {
|
|
647
|
-
option.default = true
|
|
648
|
-
} else if (
|
|
649
|
-
optProp.initializer.kind === ts.SyntaxKind.FalseKeyword
|
|
650
|
-
) {
|
|
651
|
-
option.default = false
|
|
652
|
-
}
|
|
653
|
-
break
|
|
654
|
-
|
|
655
|
-
case 'choices':
|
|
656
|
-
// Extract manually specified choices
|
|
657
|
-
if (ts.isArrayLiteralExpression(optProp.initializer)) {
|
|
658
|
-
manualChoices = []
|
|
659
|
-
for (const element of optProp.initializer.elements) {
|
|
660
|
-
if (ts.isStringLiteral(element)) {
|
|
661
|
-
manualChoices.push(element.text)
|
|
662
|
-
}
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
break
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
// Extract enum values from the function input type if available
|
|
670
|
-
let derivedChoices: string[] | null = null
|
|
671
|
-
|
|
672
|
-
if (pikkuFuncId) {
|
|
673
|
-
// 1. Try TypeScript types first (most precise — handles unions, TS enums)
|
|
674
|
-
const inputTypes = inspectorState.typesLookup.get(pikkuFuncId)
|
|
675
|
-
if (inputTypes && inputTypes.length > 0) {
|
|
676
|
-
derivedChoices = extractEnumFromPropertyType(
|
|
677
|
-
inputTypes[0]!,
|
|
678
|
-
optionName,
|
|
679
|
-
typeChecker
|
|
680
|
-
)
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
// 2. Fallback: try JSON schema (works for addon functions)
|
|
684
|
-
if (!derivedChoices) {
|
|
685
|
-
derivedChoices = extractEnumFromJsonSchema(
|
|
686
|
-
inspectorState,
|
|
687
|
-
pikkuFuncId,
|
|
688
|
-
optionName
|
|
689
|
-
)
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
// 3. Last resort: try Config type
|
|
694
|
-
if (!derivedChoices) {
|
|
695
|
-
derivedChoices = extractEnumFromConfigType(
|
|
696
|
-
logger,
|
|
697
|
-
optionName,
|
|
698
|
-
typeChecker,
|
|
699
|
-
inspectorState,
|
|
700
|
-
inspectorOptions
|
|
701
|
-
)
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
// Validate and set choices
|
|
705
|
-
if (manualChoices && derivedChoices) {
|
|
706
|
-
// Both manual and derived choices exist - validate manual is subset of derived
|
|
707
|
-
const invalidChoices = manualChoices.filter(
|
|
708
|
-
(choice) => !derivedChoices!.includes(choice)
|
|
709
|
-
)
|
|
710
|
-
|
|
711
|
-
if (invalidChoices.length > 0) {
|
|
712
|
-
const sourceFile = node.getSourceFile()
|
|
713
|
-
const position = prop.getStart(sourceFile)
|
|
714
|
-
const { line, character } =
|
|
715
|
-
sourceFile.getLineAndCharacterOfPosition(position)
|
|
716
|
-
|
|
717
|
-
throw new Error(
|
|
718
|
-
`Invalid choices for option "${optionName}" at ${sourceFile.fileName}:${line + 1}:${character + 1}.\n` +
|
|
719
|
-
`The following choices are not valid according to the type: ${invalidChoices.join(', ')}.\n` +
|
|
720
|
-
`Valid choices from type: ${derivedChoices.join(', ')}.`
|
|
721
|
-
)
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
// Manual choices are valid - use them
|
|
725
|
-
option.choices = manualChoices
|
|
726
|
-
} else if (manualChoices) {
|
|
727
|
-
// Only manual choices - use them
|
|
728
|
-
option.choices = manualChoices
|
|
729
|
-
} else if (derivedChoices) {
|
|
730
|
-
// Only derived choices - use them
|
|
731
|
-
option.choices = derivedChoices
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
options[optionName] = option
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
return options
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
/**
|
|
742
|
-
* Extracts enum values from a property of a type
|
|
743
|
-
* Handles both union types ('a' | 'b') and TypeScript enums
|
|
744
|
-
*/
|
|
745
|
-
function extractEnumFromPropertyType(
|
|
746
|
-
type: ts.Type,
|
|
747
|
-
propertyName: string,
|
|
748
|
-
typeChecker: TypeChecker
|
|
749
|
-
): string[] | null {
|
|
750
|
-
// Get the property from the type
|
|
751
|
-
const property = type.getProperty(propertyName)
|
|
752
|
-
if (!property) {
|
|
753
|
-
return null
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
// Get the type of the property
|
|
757
|
-
const propertyType = typeChecker.getTypeOfSymbolAtLocation(
|
|
758
|
-
property,
|
|
759
|
-
property.valueDeclaration!
|
|
760
|
-
)
|
|
761
|
-
|
|
762
|
-
const enumValues: string[] = []
|
|
763
|
-
|
|
764
|
-
// Check if it's a union type (e.g., 'debug' | 'info' | 'warn')
|
|
765
|
-
if (propertyType.isUnion()) {
|
|
766
|
-
for (const unionType of propertyType.types) {
|
|
767
|
-
// Check if it's a string literal type
|
|
768
|
-
if (unionType.flags & ts.TypeFlags.StringLiteral) {
|
|
769
|
-
const literalType = unionType as ts.StringLiteralType
|
|
770
|
-
enumValues.push(literalType.value)
|
|
771
|
-
}
|
|
772
|
-
// Check if it's an enum member (could be string or number enum)
|
|
773
|
-
else if (unionType.flags & ts.TypeFlags.EnumLiteral) {
|
|
774
|
-
const enumLiteralType = unionType as ts.LiteralType
|
|
775
|
-
// For string enums, use the value directly
|
|
776
|
-
if (typeof enumLiteralType.value === 'string') {
|
|
777
|
-
enumValues.push(enumLiteralType.value)
|
|
778
|
-
}
|
|
779
|
-
// For numeric enums, get the symbol name (e.g., "Debug", "Info")
|
|
780
|
-
else {
|
|
781
|
-
const symbol = (unionType as any).symbol
|
|
782
|
-
if (symbol && symbol.name) {
|
|
783
|
-
enumValues.push(symbol.name)
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
// Check if it's an enum type directly
|
|
790
|
-
else if (propertyType.flags & ts.TypeFlags.Enum) {
|
|
791
|
-
const symbol = propertyType.getSymbol()
|
|
792
|
-
if (symbol && symbol.exports) {
|
|
793
|
-
symbol.exports.forEach((member) => {
|
|
794
|
-
const memberType = typeChecker.getTypeOfSymbolAtLocation(
|
|
795
|
-
member,
|
|
796
|
-
member.valueDeclaration!
|
|
797
|
-
)
|
|
798
|
-
if (memberType.flags & ts.TypeFlags.StringLiteral) {
|
|
799
|
-
const literalType = memberType as ts.StringLiteralType
|
|
800
|
-
enumValues.push(literalType.value)
|
|
801
|
-
} else if (typeof (memberType as any).value === 'string') {
|
|
802
|
-
enumValues.push((memberType as any).value)
|
|
803
|
-
}
|
|
804
|
-
})
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
// Check if it's an enum literal type
|
|
808
|
-
else if (propertyType.flags & ts.TypeFlags.EnumLiteral) {
|
|
809
|
-
const enumLiteralType = propertyType as ts.LiteralType
|
|
810
|
-
if (typeof enumLiteralType.value === 'string') {
|
|
811
|
-
enumValues.push(enumLiteralType.value)
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
return enumValues.length > 0 ? enumValues : null
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
/**
|
|
819
|
-
* Extracts enum values from the Config type
|
|
820
|
-
*/
|
|
821
|
-
function extractEnumFromConfigType(
|
|
822
|
-
logger: InspectorLogger,
|
|
823
|
-
propertyName: string,
|
|
824
|
-
typeChecker: TypeChecker,
|
|
825
|
-
inspectorState: InspectorState,
|
|
826
|
-
_inspectorOptions: InspectorOptions
|
|
827
|
-
): string[] | null {
|
|
828
|
-
// Look for Config type in typesLookup
|
|
829
|
-
const configType = resolveCoreType(
|
|
830
|
-
inspectorState.typesLookup,
|
|
831
|
-
inspectorState.configTypeImportMap
|
|
832
|
-
)
|
|
833
|
-
if (!configType) {
|
|
834
|
-
// Only warn once per CLI file to avoid spamming logs
|
|
835
|
-
if (!configTypeWarningShown.has('missing-config-type')) {
|
|
836
|
-
configTypeWarningShown.add('missing-config-type')
|
|
837
|
-
logger.warn(
|
|
838
|
-
`Could not find Config type in typesLookup. ` +
|
|
839
|
-
`Make sure you have a Config interface extending CoreConfig in your codebase.`
|
|
840
|
-
)
|
|
841
|
-
}
|
|
842
|
-
return null
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
// Extract enum from the property
|
|
846
|
-
return extractEnumFromPropertyType(configType, propertyName, typeChecker)
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
/**
|
|
850
|
-
* Extracts enum values from the function's JSON schema.
|
|
851
|
-
* Works for addon functions whose schemas are generated from OpenAPI/Zod.
|
|
852
|
-
*/
|
|
853
|
-
function extractEnumFromJsonSchema(
|
|
854
|
-
inspectorState: InspectorState,
|
|
855
|
-
pikkuFuncId: string,
|
|
856
|
-
propertyName: string
|
|
857
|
-
): string[] | null {
|
|
858
|
-
const fnMeta = resolveFunctionMeta(inspectorState, pikkuFuncId)
|
|
859
|
-
if (!fnMeta?.inputSchemaName) return null
|
|
860
|
-
|
|
861
|
-
const schema = inspectorState.schemas[fnMeta.inputSchemaName] as any
|
|
862
|
-
if (!schema?.properties?.[propertyName]) return null
|
|
863
|
-
|
|
864
|
-
const prop = schema.properties[propertyName]
|
|
865
|
-
|
|
866
|
-
// Direct enum on property
|
|
867
|
-
if (prop.enum && Array.isArray(prop.enum)) {
|
|
868
|
-
const strings = prop.enum.filter((v: unknown) => typeof v === 'string')
|
|
869
|
-
if (strings.length > 0) return strings
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
// Array with enum items (e.g. z.array(z.enum([...])))
|
|
873
|
-
if (
|
|
874
|
-
prop.type === 'array' &&
|
|
875
|
-
prop.items?.enum &&
|
|
876
|
-
Array.isArray(prop.items.enum)
|
|
877
|
-
) {
|
|
878
|
-
const strings = prop.items.enum.filter(
|
|
879
|
-
(v: unknown) => typeof v === 'string'
|
|
880
|
-
)
|
|
881
|
-
if (strings.length > 0) return strings
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
return null
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
/**
|
|
888
|
-
* Gets the property name from a property assignment
|
|
889
|
-
*/
|
|
890
|
-
function getPropertyName(prop: ts.PropertyAssignment): string | null {
|
|
891
|
-
if (ts.isIdentifier(prop.name)) {
|
|
892
|
-
return prop.name.text
|
|
893
|
-
}
|
|
894
|
-
if (ts.isStringLiteral(prop.name)) {
|
|
895
|
-
return prop.name.text
|
|
896
|
-
}
|
|
897
|
-
return null
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
/**
|
|
901
|
-
* Parses a parameters string to extract positional arguments
|
|
902
|
-
* Parameters format: "<env> [region] [files...]"
|
|
903
|
-
*/
|
|
904
|
-
function parseCommandPattern(pattern: string): any[] {
|
|
905
|
-
const positionals: any[] = []
|
|
906
|
-
|
|
907
|
-
// Split by spaces to get all parameter definitions
|
|
908
|
-
const parts = pattern.split(' ').filter((p) => p.trim())
|
|
909
|
-
|
|
910
|
-
for (const part of parts) {
|
|
911
|
-
if (part.startsWith('<') && part.endsWith('>')) {
|
|
912
|
-
// Required positional
|
|
913
|
-
const name = part.slice(1, -1)
|
|
914
|
-
if (name.endsWith('...')) {
|
|
915
|
-
positionals.push({
|
|
916
|
-
name: name.slice(0, -3),
|
|
917
|
-
required: true,
|
|
918
|
-
variadic: true,
|
|
919
|
-
})
|
|
920
|
-
} else {
|
|
921
|
-
positionals.push({
|
|
922
|
-
name,
|
|
923
|
-
required: true,
|
|
924
|
-
})
|
|
925
|
-
}
|
|
926
|
-
} else if (part.startsWith('[') && part.endsWith(']')) {
|
|
927
|
-
// Optional positional
|
|
928
|
-
const name = part.slice(1, -1)
|
|
929
|
-
if (name.endsWith('...')) {
|
|
930
|
-
positionals.push({
|
|
931
|
-
name: name.slice(0, -3),
|
|
932
|
-
required: false,
|
|
933
|
-
variadic: true,
|
|
934
|
-
})
|
|
935
|
-
} else {
|
|
936
|
-
positionals.push({
|
|
937
|
-
name,
|
|
938
|
-
required: false,
|
|
939
|
-
})
|
|
940
|
-
}
|
|
941
|
-
} else if (part.trim()) {
|
|
942
|
-
// Found a literal word in the parameters pattern
|
|
943
|
-
throw new Error(
|
|
944
|
-
`Invalid parameters pattern '${pattern}': found literal word '${part}'. ` +
|
|
945
|
-
`Parameters should only contain <required> or [optional] arguments. ` +
|
|
946
|
-
`Example: "<env> [region]" or "<files...>"`
|
|
947
|
-
)
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
return positionals
|
|
952
|
-
}
|
|
953
|
-
|
|
954
|
-
/**
|
|
955
|
-
* Adds CLI renderer metadata to the inspector state
|
|
956
|
-
*/
|
|
957
|
-
export const addCLIRenderers: AddWiring = (
|
|
958
|
-
logger,
|
|
959
|
-
node,
|
|
960
|
-
typeChecker,
|
|
961
|
-
inspectorState,
|
|
962
|
-
options
|
|
963
|
-
) => {
|
|
964
|
-
if (!ts.isCallExpression(node)) return
|
|
965
|
-
|
|
966
|
-
const { expression, arguments: args } = node
|
|
967
|
-
|
|
968
|
-
// Only handle pikkuCLIRender calls
|
|
969
|
-
if (!ts.isIdentifier(expression) || expression.text !== 'pikkuCLIRender') {
|
|
970
|
-
return
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
if (args.length === 0) return
|
|
974
|
-
|
|
975
|
-
// Extract renderer name
|
|
976
|
-
const { pikkuFuncId, exportedName } = extractFunctionName(
|
|
977
|
-
node,
|
|
978
|
-
typeChecker,
|
|
979
|
-
inspectorState.rootDir
|
|
980
|
-
)
|
|
981
|
-
|
|
982
|
-
// Get the source file path
|
|
983
|
-
const sourceFile = node.getSourceFile()
|
|
984
|
-
const filePath = sourceFile.fileName
|
|
985
|
-
|
|
986
|
-
// Renderer service usage is defined by the callback's first parameter shape,
|
|
987
|
-
// the same way function/auth service usage is tracked elsewhere in the inspector.
|
|
988
|
-
const renderFunc = args[0]
|
|
989
|
-
const services =
|
|
990
|
-
ts.isArrowFunction(renderFunc) || ts.isFunctionExpression(renderFunc)
|
|
991
|
-
? extractServicesFromFunction(renderFunc)
|
|
992
|
-
: { optimized: true, services: [] }
|
|
993
|
-
|
|
994
|
-
// Store renderer metadata
|
|
995
|
-
inspectorState.cli.meta.renderers[pikkuFuncId] = {
|
|
996
|
-
name: pikkuFuncId,
|
|
997
|
-
exportedName: exportedName ?? undefined,
|
|
998
|
-
services,
|
|
999
|
-
filePath,
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
// Add to files map if exported
|
|
1003
|
-
if (exportedName) {
|
|
1004
|
-
inspectorState.cli.files.add(filePath)
|
|
1005
|
-
}
|
|
1006
|
-
}
|