@pikku/inspector 0.12.75 → 0.12.82

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