@pikku/inspector 0.12.75 → 0.12.81
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +330 -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 +14 -0
- 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 -2232
- package/src/utils/filter-inspector-state.ts +0 -1298
- 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,11 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DSL (Domain Specific Language) workflow extraction exports
|
|
3
|
-
*/
|
|
4
|
-
export { extractDSLWorkflow } from './extract-dsl-workflow.js'
|
|
5
|
-
export {
|
|
6
|
-
deserializeDslWorkflow,
|
|
7
|
-
deserializeGraphWorkflow,
|
|
8
|
-
deserializeAllDslWorkflows,
|
|
9
|
-
} from './deserialize-dsl-workflow.js'
|
|
10
|
-
export * from './patterns.js'
|
|
11
|
-
export * from './validation.js'
|
|
@@ -1,440 +0,0 @@
|
|
|
1
|
-
import * as ts from 'typescript'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Pattern detection helpers for DSL workflow extraction
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* The wire object handed to a DSL func is named `workflow` in a workflow and
|
|
9
|
-
* `scenario` in a scenario — both carry the same `do`/`sleep`/`suspend` DSL, so
|
|
10
|
-
* step extraction accepts either identifier.
|
|
11
|
-
*/
|
|
12
|
-
export function isWorkflowWireIdentifier(expr: ts.Expression): boolean {
|
|
13
|
-
return (
|
|
14
|
-
ts.isIdentifier(expr) &&
|
|
15
|
-
(expr.text === 'workflow' || expr.text === 'scenario')
|
|
16
|
-
)
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Check if a call expression is workflow.do() or workflow.expectEventually()
|
|
21
|
-
* (both are RPC steps; expectEventually is the polling variant used by scenarios)
|
|
22
|
-
*/
|
|
23
|
-
export function isWorkflowDoCall(
|
|
24
|
-
node: ts.CallExpression,
|
|
25
|
-
checker: ts.TypeChecker
|
|
26
|
-
): boolean {
|
|
27
|
-
if (!ts.isPropertyAccessExpression(node.expression)) {
|
|
28
|
-
return false
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const propAccess = node.expression
|
|
32
|
-
return (
|
|
33
|
-
(propAccess.name.text === 'do' ||
|
|
34
|
-
propAccess.name.text === 'expectEventually') &&
|
|
35
|
-
isWorkflowWireIdentifier(propAccess.expression)
|
|
36
|
-
)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Placeholder recorded when a scenario step's target isn't a static string
|
|
41
|
-
* literal. The step is kept rather than dropped so post-processing can raise
|
|
42
|
-
* PKU678 against it — a dropped step is the silent-failure mode this repo has
|
|
43
|
-
* hit repeatedly on workflow graph round-trips.
|
|
44
|
-
*/
|
|
45
|
-
export const DYNAMIC_SCENARIO_STEP_TARGET = '<dynamic>'
|
|
46
|
-
|
|
47
|
-
const SCENARIO_STEP_PHASES = ['given', 'when', 'then'] as const
|
|
48
|
-
|
|
49
|
-
export type ScenarioStepPhaseName = (typeof SCENARIO_STEP_PHASES)[number]
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Check if a call expression is scenario.given()/when()/then() — a declared
|
|
53
|
-
* `pikkuScenarioStep` invoked by name. `given`/`when` are pure sugar for each
|
|
54
|
-
* other; `then` additionally makes the step's bindings witnesses.
|
|
55
|
-
*/
|
|
56
|
-
export function isScenarioStepCall(node: ts.CallExpression): boolean {
|
|
57
|
-
return getScenarioStepPhase(node) !== undefined
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* The Gherkin-style phase of a scenario step call, or undefined when the call
|
|
62
|
-
* isn't one.
|
|
63
|
-
*/
|
|
64
|
-
export function getScenarioStepPhase(
|
|
65
|
-
node: ts.CallExpression
|
|
66
|
-
): ScenarioStepPhaseName | undefined {
|
|
67
|
-
if (!ts.isPropertyAccessExpression(node.expression)) {
|
|
68
|
-
return
|
|
69
|
-
}
|
|
70
|
-
const propAccess = node.expression
|
|
71
|
-
if (!isWorkflowWireIdentifier(propAccess.expression)) {
|
|
72
|
-
return
|
|
73
|
-
}
|
|
74
|
-
return SCENARIO_STEP_PHASES.find((phase) => phase === propAccess.name.text)
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* The expectation helpers that assert without being a `then`.
|
|
79
|
-
*
|
|
80
|
-
* `expectService`, `expectError` and `expectScore` are inline steps rather than
|
|
81
|
-
* scenario steps, so they carry no phase and never reach the step meta — but
|
|
82
|
-
* each one is a witness: a recorded service call, a refusal, or a graded run.
|
|
83
|
-
* PKU680 has to see them, or a scenario whose only assertion is one of these
|
|
84
|
-
* reads as asserting nothing.
|
|
85
|
-
*/
|
|
86
|
-
const SCENARIO_EXPECTATION_CALLS = [
|
|
87
|
-
'expectService',
|
|
88
|
-
'expectError',
|
|
89
|
-
'expectEventually',
|
|
90
|
-
'expectScore',
|
|
91
|
-
] as const
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Check if a call expression is one of the scenario expectation helpers —
|
|
95
|
-
* `scenario.expectService()`, `.expectError()`, `.expectEventually()` or
|
|
96
|
-
* `.expectScore()`.
|
|
97
|
-
*/
|
|
98
|
-
export function isScenarioExpectationCall(node: ts.CallExpression): boolean {
|
|
99
|
-
return (
|
|
100
|
-
ts.isPropertyAccessExpression(node.expression) &&
|
|
101
|
-
SCENARIO_EXPECTATION_CALLS.some(
|
|
102
|
-
(name) =>
|
|
103
|
-
name === (node.expression as ts.PropertyAccessExpression).name.text
|
|
104
|
-
) &&
|
|
105
|
-
isWorkflowWireIdentifier(node.expression.expression)
|
|
106
|
-
)
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Check if a call expression is workflow.expectEventually()
|
|
111
|
-
*/
|
|
112
|
-
export function isWorkflowExpectEventuallyCall(
|
|
113
|
-
node: ts.CallExpression
|
|
114
|
-
): boolean {
|
|
115
|
-
return (
|
|
116
|
-
ts.isPropertyAccessExpression(node.expression) &&
|
|
117
|
-
node.expression.name.text === 'expectEventually' &&
|
|
118
|
-
isWorkflowWireIdentifier(node.expression.expression)
|
|
119
|
-
)
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Extract the actor NAME from a step options object literal:
|
|
124
|
-
* `{ actor: actors.x }` → 'x' (also `actors['x']`). Undefined when absent
|
|
125
|
-
* or not statically resolvable.
|
|
126
|
-
*/
|
|
127
|
-
export function extractActorFromOptions(
|
|
128
|
-
optionsArg: ts.Expression | undefined
|
|
129
|
-
): string | undefined {
|
|
130
|
-
if (!optionsArg || !ts.isObjectLiteralExpression(optionsArg)) return
|
|
131
|
-
for (const prop of optionsArg.properties) {
|
|
132
|
-
if (
|
|
133
|
-
ts.isPropertyAssignment(prop) &&
|
|
134
|
-
ts.isIdentifier(prop.name) &&
|
|
135
|
-
prop.name.text === 'actor'
|
|
136
|
-
) {
|
|
137
|
-
const init = prop.initializer
|
|
138
|
-
if (ts.isPropertyAccessExpression(init)) {
|
|
139
|
-
return init.name.text
|
|
140
|
-
}
|
|
141
|
-
if (
|
|
142
|
-
ts.isElementAccessExpression(init) &&
|
|
143
|
-
ts.isStringLiteral(init.argumentExpression)
|
|
144
|
-
) {
|
|
145
|
-
return init.argumentExpression.text
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
return
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Check if a call expression is workflow.sleep()
|
|
154
|
-
*/
|
|
155
|
-
export function isWorkflowSleepCall(
|
|
156
|
-
node: ts.CallExpression,
|
|
157
|
-
checker: ts.TypeChecker
|
|
158
|
-
): boolean {
|
|
159
|
-
if (!ts.isPropertyAccessExpression(node.expression)) {
|
|
160
|
-
return false
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const propAccess = node.expression
|
|
164
|
-
return (
|
|
165
|
-
propAccess.name.text === 'sleep' &&
|
|
166
|
-
isWorkflowWireIdentifier(propAccess.expression)
|
|
167
|
-
)
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Check if a call expression is workflow.suspend()
|
|
172
|
-
*/
|
|
173
|
-
export function isWorkflowSuspendCall(
|
|
174
|
-
node: ts.CallExpression,
|
|
175
|
-
_checker: ts.TypeChecker
|
|
176
|
-
): boolean {
|
|
177
|
-
if (!ts.isPropertyAccessExpression(node.expression)) {
|
|
178
|
-
return false
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const propAccess = node.expression
|
|
182
|
-
return (
|
|
183
|
-
propAccess.name.text === 'suspend' &&
|
|
184
|
-
isWorkflowWireIdentifier(propAccess.expression)
|
|
185
|
-
)
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Check if a call expression is workflow.approval()
|
|
190
|
-
*/
|
|
191
|
-
export function isWorkflowApprovalCall(
|
|
192
|
-
node: ts.CallExpression,
|
|
193
|
-
_checker: ts.TypeChecker
|
|
194
|
-
): boolean {
|
|
195
|
-
if (!ts.isPropertyAccessExpression(node.expression)) {
|
|
196
|
-
return false
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
const propAccess = node.expression
|
|
200
|
-
return (
|
|
201
|
-
propAccess.name.text === 'approval' &&
|
|
202
|
-
isWorkflowWireIdentifier(propAccess.expression)
|
|
203
|
-
)
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Check if a throw statement throws WorkflowCancelledException
|
|
208
|
-
* Matches: throw new WorkflowCancelledException(...) or throw WorkflowCancelledException(...)
|
|
209
|
-
*/
|
|
210
|
-
export function isThrowCancelException(node: ts.ThrowStatement): boolean {
|
|
211
|
-
const expr = node.expression
|
|
212
|
-
if (!expr) return false
|
|
213
|
-
|
|
214
|
-
// Check for: throw new WorkflowCancelledException(...)
|
|
215
|
-
if (ts.isNewExpression(expr)) {
|
|
216
|
-
if (ts.isIdentifier(expr.expression)) {
|
|
217
|
-
return expr.expression.text === 'WorkflowCancelledException'
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// Check for: throw WorkflowCancelledException(...) - function call style
|
|
222
|
-
if (ts.isCallExpression(expr)) {
|
|
223
|
-
if (ts.isIdentifier(expr.expression)) {
|
|
224
|
-
return expr.expression.text === 'WorkflowCancelledException'
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
return false
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* Extract the reason string from a throw WorkflowCancelledException statement
|
|
233
|
-
*/
|
|
234
|
-
export function extractCancelReason(
|
|
235
|
-
node: ts.ThrowStatement,
|
|
236
|
-
checker: ts.TypeChecker
|
|
237
|
-
): string | undefined {
|
|
238
|
-
const expr = node.expression
|
|
239
|
-
if (!expr) return undefined
|
|
240
|
-
|
|
241
|
-
let args: ts.NodeArray<ts.Expression> | undefined
|
|
242
|
-
|
|
243
|
-
if (ts.isNewExpression(expr) && expr.arguments) {
|
|
244
|
-
args = expr.arguments
|
|
245
|
-
} else if (ts.isCallExpression(expr)) {
|
|
246
|
-
args = expr.arguments
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
if (args && args.length > 0) {
|
|
250
|
-
const firstArg = args[0]
|
|
251
|
-
if (ts.isStringLiteral(firstArg)) {
|
|
252
|
-
return firstArg.text
|
|
253
|
-
}
|
|
254
|
-
// For template literals or other expressions, return the source text
|
|
255
|
-
return firstArg.getText()
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
return undefined
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* Check if a call expression is array.filter()
|
|
263
|
-
*/
|
|
264
|
-
export function isArrayFilter(node: ts.CallExpression): boolean {
|
|
265
|
-
if (!ts.isPropertyAccessExpression(node.expression)) {
|
|
266
|
-
return false
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
return node.expression.name.text === 'filter'
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* Check if a call expression is array.some()
|
|
274
|
-
*/
|
|
275
|
-
export function isArraySome(node: ts.CallExpression): boolean {
|
|
276
|
-
if (!ts.isPropertyAccessExpression(node.expression)) {
|
|
277
|
-
return false
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
return node.expression.name.text === 'some'
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
/**
|
|
284
|
-
* Check if a call expression is array.every()
|
|
285
|
-
*/
|
|
286
|
-
export function isArrayEvery(node: ts.CallExpression): boolean {
|
|
287
|
-
if (!ts.isPropertyAccessExpression(node.expression)) {
|
|
288
|
-
return false
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
return node.expression.name.text === 'every'
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
/**
|
|
295
|
-
* Check if an expression is Promise.all(array.map(...))
|
|
296
|
-
*/
|
|
297
|
-
export function isParallelFanout(node: ts.CallExpression): boolean {
|
|
298
|
-
// Promise.all(...)
|
|
299
|
-
if (!ts.isPropertyAccessExpression(node.expression)) {
|
|
300
|
-
return false
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
const propAccess = node.expression
|
|
304
|
-
if (
|
|
305
|
-
!ts.isIdentifier(propAccess.expression) ||
|
|
306
|
-
propAccess.expression.text !== 'Promise' ||
|
|
307
|
-
propAccess.name.text !== 'all'
|
|
308
|
-
) {
|
|
309
|
-
return false
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// Check if argument is array.map(...)
|
|
313
|
-
const arg = node.arguments[0]
|
|
314
|
-
if (!arg || !ts.isCallExpression(arg)) {
|
|
315
|
-
return false
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
if (!ts.isPropertyAccessExpression(arg.expression)) {
|
|
319
|
-
return false
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
return arg.expression.name.text === 'map'
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
/**
|
|
326
|
-
* Check if an expression is Promise.all([...])
|
|
327
|
-
*/
|
|
328
|
-
export function isParallelGroup(node: ts.CallExpression): boolean {
|
|
329
|
-
// Promise.all(...)
|
|
330
|
-
if (!ts.isPropertyAccessExpression(node.expression)) {
|
|
331
|
-
return false
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
const propAccess = node.expression
|
|
335
|
-
if (
|
|
336
|
-
!ts.isIdentifier(propAccess.expression) ||
|
|
337
|
-
propAccess.expression.text !== 'Promise' ||
|
|
338
|
-
propAccess.name.text !== 'all'
|
|
339
|
-
) {
|
|
340
|
-
return false
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// Check if argument is an array literal
|
|
344
|
-
const arg = node.arguments[0]
|
|
345
|
-
return !!arg && ts.isArrayLiteralExpression(arg)
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
/**
|
|
349
|
-
* Check if a for statement is a valid sequential fanout (for..of)
|
|
350
|
-
*/
|
|
351
|
-
export function isSequentialFanout(node: ts.ForOfStatement): boolean {
|
|
352
|
-
// Must have const declaration
|
|
353
|
-
if (!ts.isVariableDeclarationList(node.initializer)) {
|
|
354
|
-
return false
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
const declList = node.initializer
|
|
358
|
-
if (!(declList.flags & ts.NodeFlags.Const)) {
|
|
359
|
-
return false
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
// Must have exactly one declaration
|
|
363
|
-
if (declList.declarations.length !== 1) {
|
|
364
|
-
return false
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
return true
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
/**
|
|
371
|
-
* Extract full source path from an expression (e.g., data.memberEmails)
|
|
372
|
-
*/
|
|
373
|
-
export function extractSourcePath(expr: ts.Expression): string | null {
|
|
374
|
-
if (ts.isIdentifier(expr)) {
|
|
375
|
-
return expr.text
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
if (ts.isPropertyAccessExpression(expr)) {
|
|
379
|
-
const base = extractSourcePath(expr.expression)
|
|
380
|
-
if (base) {
|
|
381
|
-
return `${base}.${expr.name.text}`
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
return null
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
/**
|
|
389
|
-
* Extract the variable name from a for..of statement
|
|
390
|
-
*/
|
|
391
|
-
export function extractForOfVariable(
|
|
392
|
-
node: ts.ForOfStatement
|
|
393
|
-
): { itemVar: string; sourceVar: string } | null {
|
|
394
|
-
if (!ts.isVariableDeclarationList(node.initializer)) {
|
|
395
|
-
return null
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
const decl = node.initializer.declarations[0]
|
|
399
|
-
if (!ts.isIdentifier(decl.name)) {
|
|
400
|
-
return null
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
const itemVar = decl.name.text
|
|
404
|
-
|
|
405
|
-
// Extract source variable with full path (e.g., data.memberEmails)
|
|
406
|
-
const sourceVar = extractSourcePath(node.expression)
|
|
407
|
-
|
|
408
|
-
if (!sourceVar) {
|
|
409
|
-
return null
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
return { itemVar, sourceVar }
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
/**
|
|
416
|
-
* Check if a type is an array type
|
|
417
|
-
*/
|
|
418
|
-
export function isArrayType(type: ts.Type, checker: ts.TypeChecker): boolean {
|
|
419
|
-
// Check if it's an array type
|
|
420
|
-
if (checker.isArrayType(type)) {
|
|
421
|
-
return true
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
// Check if it's a tuple type
|
|
425
|
-
if (type.flags & ts.TypeFlags.Object) {
|
|
426
|
-
const objectType = type as ts.ObjectType
|
|
427
|
-
if (objectType.objectFlags & ts.ObjectFlags.Tuple) {
|
|
428
|
-
return true
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
return false
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
/**
|
|
436
|
-
* Get the source text of a node (for condition expressions)
|
|
437
|
-
*/
|
|
438
|
-
export function getSourceText(node: ts.Node): string {
|
|
439
|
-
return node.getText().trim()
|
|
440
|
-
}
|
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
import * as ts from 'typescript'
|
|
2
|
-
import { isWorkflowWireIdentifier } from './patterns.js'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Validation rules for DSL workflows
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
export interface ValidationError {
|
|
9
|
-
message: string
|
|
10
|
-
node: ts.Node
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Check if a node contains only allowed patterns
|
|
15
|
-
*
|
|
16
|
-
* Allowed patterns:
|
|
17
|
-
* - VariableStatement (const/let declarations)
|
|
18
|
-
* - ExpressionStatement (await workflow.do, await workflow.sleep, await Promise.all)
|
|
19
|
-
* - IfStatement (branches)
|
|
20
|
-
* - SwitchStatement (switch/case)
|
|
21
|
-
* - ForOfStatement (sequential fanout)
|
|
22
|
-
* - ReturnStatement
|
|
23
|
-
* - ThrowStatement (for WorkflowCancelledException)
|
|
24
|
-
* - Block (containers)
|
|
25
|
-
*
|
|
26
|
-
* When `allowInline` is set (complex workflows and scenarios), the bodies of
|
|
27
|
-
* closures are skipped: a callback is ordinary TypeScript, not DSL, so holding
|
|
28
|
-
* it to the statement whitelist would reject valid code. Plain DSL workflows
|
|
29
|
-
* still descend into callbacks, which is what validates fanout bodies.
|
|
30
|
-
*/
|
|
31
|
-
export function validateNoDisallowedPatterns(
|
|
32
|
-
node: ts.Node,
|
|
33
|
-
options?: { allowInline?: boolean }
|
|
34
|
-
): ValidationError[] {
|
|
35
|
-
const errors: ValidationError[] = []
|
|
36
|
-
|
|
37
|
-
function visitBlock(block: ts.Block) {
|
|
38
|
-
for (const statement of block.statements) {
|
|
39
|
-
if (
|
|
40
|
-
ts.isVariableStatement(statement) ||
|
|
41
|
-
ts.isExpressionStatement(statement) ||
|
|
42
|
-
ts.isIfStatement(statement) ||
|
|
43
|
-
ts.isSwitchStatement(statement) ||
|
|
44
|
-
ts.isForOfStatement(statement) ||
|
|
45
|
-
ts.isReturnStatement(statement) ||
|
|
46
|
-
ts.isThrowStatement(statement)
|
|
47
|
-
) {
|
|
48
|
-
// Allowed statement type - recurse into it
|
|
49
|
-
visitNode(statement)
|
|
50
|
-
} else {
|
|
51
|
-
// Unknown/disallowed statement type
|
|
52
|
-
const nodeType = ts.SyntaxKind[statement.kind]
|
|
53
|
-
errors.push({
|
|
54
|
-
message: `Statement type '${nodeType}' is not allowed in DSL workflows. Allowed: const/let, if/else, switch/case, for..of, return, throw, and workflow calls. If this should be supported, please report the node type: ${nodeType}`,
|
|
55
|
-
node: statement,
|
|
56
|
-
})
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function visitNode(node: ts.Node) {
|
|
62
|
-
if (
|
|
63
|
-
options?.allowInline &&
|
|
64
|
-
(ts.isArrowFunction(node) || ts.isFunctionExpression(node))
|
|
65
|
-
) {
|
|
66
|
-
return
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Disallow while and do-while
|
|
70
|
-
if (ts.isWhileStatement(node) || ts.isDoStatement(node)) {
|
|
71
|
-
errors.push({
|
|
72
|
-
message: 'while and do-while loops are not allowed in DSL workflows',
|
|
73
|
-
node,
|
|
74
|
-
})
|
|
75
|
-
return
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Disallow for and for-in loops
|
|
79
|
-
if (ts.isForInStatement(node) || ts.isForStatement(node)) {
|
|
80
|
-
errors.push({
|
|
81
|
-
message:
|
|
82
|
-
'for and for-in loops are not allowed in DSL workflows. Use for-of instead.',
|
|
83
|
-
node,
|
|
84
|
-
})
|
|
85
|
-
return
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Check for inline workflow.do
|
|
89
|
-
if (!options?.allowInline && ts.isCallExpression(node)) {
|
|
90
|
-
if (ts.isPropertyAccessExpression(node.expression)) {
|
|
91
|
-
const propAccess = node.expression
|
|
92
|
-
if (
|
|
93
|
-
propAccess.name.text === 'do' &&
|
|
94
|
-
isWorkflowWireIdentifier(propAccess.expression)
|
|
95
|
-
) {
|
|
96
|
-
const secondArg = node.arguments[1]
|
|
97
|
-
if (
|
|
98
|
-
secondArg &&
|
|
99
|
-
(ts.isArrowFunction(secondArg) ||
|
|
100
|
-
ts.isFunctionExpression(secondArg))
|
|
101
|
-
) {
|
|
102
|
-
errors.push({
|
|
103
|
-
message:
|
|
104
|
-
'Inline workflow.do with function argument is not allowed in DSL workflows. Use pikkuWorkflowComplexFunc instead.',
|
|
105
|
-
node,
|
|
106
|
-
})
|
|
107
|
-
return
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Recurse into blocks
|
|
114
|
-
if (ts.isBlock(node)) {
|
|
115
|
-
visitBlock(node)
|
|
116
|
-
} else {
|
|
117
|
-
ts.forEachChild(node, visitNode)
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
visitNode(node)
|
|
122
|
-
return errors
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Validate that all workflow.do calls are awaited
|
|
127
|
-
*/
|
|
128
|
-
export function validateAwaitedCalls(node: ts.Node): ValidationError[] {
|
|
129
|
-
const errors: ValidationError[] = []
|
|
130
|
-
|
|
131
|
-
function visit(
|
|
132
|
-
node: ts.Node,
|
|
133
|
-
parentIsAwait: boolean = false,
|
|
134
|
-
insidePromiseAll: boolean = false
|
|
135
|
-
) {
|
|
136
|
-
// Check if this is Promise.all(...) first, before checking for workflow calls
|
|
137
|
-
if (
|
|
138
|
-
ts.isCallExpression(node) &&
|
|
139
|
-
ts.isPropertyAccessExpression(node.expression)
|
|
140
|
-
) {
|
|
141
|
-
const propAccess = node.expression
|
|
142
|
-
if (
|
|
143
|
-
propAccess.name.text === 'all' &&
|
|
144
|
-
ts.isIdentifier(propAccess.expression) &&
|
|
145
|
-
propAccess.expression.text === 'Promise'
|
|
146
|
-
) {
|
|
147
|
-
ts.forEachChild(node, (child) => visit(child, parentIsAwait, true))
|
|
148
|
-
return
|
|
149
|
-
}
|
|
150
|
-
// .push() on an array — workflow.do() inside is collecting promises
|
|
151
|
-
if (propAccess.name.text === 'push') {
|
|
152
|
-
ts.forEachChild(node, (child) => visit(child, parentIsAwait, true))
|
|
153
|
-
return
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Array literal — workflow.do() inside is collecting promises
|
|
158
|
-
if (ts.isArrayLiteralExpression(node)) {
|
|
159
|
-
ts.forEachChild(node, (child) => visit(child, parentIsAwait, true))
|
|
160
|
-
return
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Now check for workflow calls
|
|
164
|
-
if (ts.isCallExpression(node)) {
|
|
165
|
-
if (ts.isPropertyAccessExpression(node.expression)) {
|
|
166
|
-
const propAccess = node.expression
|
|
167
|
-
if (
|
|
168
|
-
(propAccess.name.text === 'do' || propAccess.name.text === 'sleep') &&
|
|
169
|
-
isWorkflowWireIdentifier(propAccess.expression)
|
|
170
|
-
) {
|
|
171
|
-
if (!parentIsAwait && !insidePromiseAll) {
|
|
172
|
-
errors.push({
|
|
173
|
-
message: `workflow.${propAccess.name.text}() must be awaited`,
|
|
174
|
-
node,
|
|
175
|
-
})
|
|
176
|
-
}
|
|
177
|
-
return
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
if (ts.isAwaitExpression(node)) {
|
|
183
|
-
// Visit the expression itself with parentIsAwait=true
|
|
184
|
-
visit(node.expression, true, insidePromiseAll)
|
|
185
|
-
} else {
|
|
186
|
-
ts.forEachChild(node, (child) => visit(child, false, insidePromiseAll))
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
visit(node)
|
|
191
|
-
return errors
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Combine all validation errors into a single error message
|
|
196
|
-
*/
|
|
197
|
-
export function formatValidationErrors(errors: ValidationError[]): string {
|
|
198
|
-
if (errors.length === 0) {
|
|
199
|
-
return ''
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
return errors.map((err) => `- ${err.message}`).join('\n')
|
|
203
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { test } from 'node:test'
|
|
2
|
-
import assert from 'node:assert'
|
|
3
|
-
import { convertDslToGraph } from './convert-dsl-to-graph.js'
|
|
4
|
-
import type { WorkflowsMeta } from '@pikku/core/workflow'
|
|
5
|
-
|
|
6
|
-
const scenarioMeta = (
|
|
7
|
-
overrides: Partial<WorkflowsMeta[string]> = {}
|
|
8
|
-
): WorkflowsMeta[string] => ({
|
|
9
|
-
pikkuFuncId: 'installAddonFreshNameScenario',
|
|
10
|
-
name: 'installAddonFreshNameScenario',
|
|
11
|
-
scenario: true,
|
|
12
|
-
steps: [],
|
|
13
|
-
...overrides,
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
test('convertDslToGraph: a scenario carries its skip reason into the graph', () => {
|
|
17
|
-
const graph = convertDslToGraph(
|
|
18
|
-
'installAddonFreshNameScenario',
|
|
19
|
-
scenarioMeta({ skip: 'npm cannot install inside this yarn workspace' })
|
|
20
|
-
)
|
|
21
|
-
|
|
22
|
-
assert.strictEqual(
|
|
23
|
-
graph.skip,
|
|
24
|
-
'npm cannot install inside this yarn workspace'
|
|
25
|
-
)
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
test('convertDslToGraph: a scenario with no skip leaves it unset', () => {
|
|
29
|
-
const graph = convertDslToGraph(
|
|
30
|
-
'installAddonFreshNameScenario',
|
|
31
|
-
scenarioMeta()
|
|
32
|
-
)
|
|
33
|
-
|
|
34
|
-
assert.strictEqual(graph.skip, undefined)
|
|
35
|
-
})
|