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