@pikku/inspector 0.11.0 → 0.11.1

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 (77) hide show
  1. package/CHANGELOG.md +16 -1
  2. package/dist/add/add-channel.js +11 -10
  3. package/dist/add/add-file-with-factory.js +10 -10
  4. package/dist/add/add-functions.js +57 -43
  5. package/dist/add/add-http-route.js +5 -4
  6. package/dist/add/add-mcp-prompt.js +6 -5
  7. package/dist/add/add-mcp-resource.js +6 -5
  8. package/dist/add/add-mcp-tool.js +6 -5
  9. package/dist/add/add-middleware.js +1 -1
  10. package/dist/add/add-permission.js +1 -1
  11. package/dist/add/add-queue-worker.js +6 -5
  12. package/dist/add/add-schedule.js +5 -4
  13. package/dist/add/add-workflow.d.ts +1 -1
  14. package/dist/add/add-workflow.js +92 -66
  15. package/dist/error-codes.d.ts +1 -0
  16. package/dist/error-codes.js +1 -0
  17. package/dist/index.d.ts +2 -0
  18. package/dist/index.js +1 -0
  19. package/dist/inspector.js +10 -6
  20. package/dist/types.d.ts +21 -8
  21. package/dist/utils/extract-function-node.d.ts +10 -0
  22. package/dist/utils/extract-function-node.js +38 -0
  23. package/dist/utils/extract-node-value.d.ts +8 -0
  24. package/dist/utils/extract-node-value.js +24 -0
  25. package/dist/utils/extract-service-metadata.d.ts +19 -0
  26. package/dist/utils/extract-service-metadata.js +244 -0
  27. package/dist/utils/get-files-and-methods.d.ts +3 -3
  28. package/dist/utils/get-files-and-methods.js +3 -3
  29. package/dist/utils/get-property-value.d.ts +13 -6
  30. package/dist/utils/get-property-value.js +51 -43
  31. package/dist/utils/post-process.d.ts +9 -0
  32. package/dist/utils/post-process.js +30 -3
  33. package/dist/utils/serialize-inspector-state.d.ts +18 -5
  34. package/dist/utils/serialize-inspector-state.js +12 -10
  35. package/dist/utils/write-service-metadata.d.ts +13 -0
  36. package/dist/utils/write-service-metadata.js +37 -0
  37. package/dist/visit.js +2 -2
  38. package/dist/workflow/extract-simple-workflow.d.ts +15 -0
  39. package/dist/workflow/extract-simple-workflow.js +803 -0
  40. package/dist/workflow/patterns.d.ts +39 -0
  41. package/dist/workflow/patterns.js +138 -0
  42. package/dist/workflow/validation.d.ts +28 -0
  43. package/dist/workflow/validation.js +124 -0
  44. package/package.json +4 -4
  45. package/src/add/add-channel.ts +37 -17
  46. package/src/add/add-file-with-factory.ts +10 -10
  47. package/src/add/add-functions.ts +72 -56
  48. package/src/add/add-http-route.ts +10 -5
  49. package/src/add/add-mcp-prompt.ts +11 -7
  50. package/src/add/add-mcp-resource.ts +11 -7
  51. package/src/add/add-mcp-tool.ts +11 -7
  52. package/src/add/add-middleware.ts +1 -1
  53. package/src/add/add-permission.ts +1 -1
  54. package/src/add/add-queue-worker.ts +11 -12
  55. package/src/add/add-schedule.ts +10 -5
  56. package/src/add/add-workflow.ts +120 -110
  57. package/src/error-codes.ts +1 -0
  58. package/src/index.ts +2 -0
  59. package/src/inspector.ts +16 -6
  60. package/src/types.ts +18 -8
  61. package/src/utils/extract-function-node.ts +58 -0
  62. package/src/utils/extract-node-value.ts +31 -0
  63. package/src/utils/extract-service-metadata.ts +353 -0
  64. package/src/utils/filter-inspector-state.test.ts +3 -3
  65. package/src/utils/filter-utils.test.ts +45 -51
  66. package/src/utils/get-files-and-methods.ts +11 -11
  67. package/src/utils/get-property-value.ts +60 -53
  68. package/src/utils/permissions.test.ts +3 -3
  69. package/src/utils/post-process.ts +56 -3
  70. package/src/utils/serialize-inspector-state.ts +28 -18
  71. package/src/utils/test-data/inspector-state.json +9 -9
  72. package/src/utils/write-service-metadata.ts +51 -0
  73. package/src/visit.ts +3 -3
  74. package/src/workflow/extract-simple-workflow.ts +1035 -0
  75. package/src/workflow/patterns.ts +182 -0
  76. package/src/workflow/validation.ts +153 -0
  77. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,182 @@
1
+ import * as ts from 'typescript'
2
+
3
+ /**
4
+ * Pattern detection helpers for simple workflow extraction
5
+ */
6
+
7
+ /**
8
+ * Check if a call expression is workflow.do()
9
+ */
10
+ export function isWorkflowDoCall(
11
+ node: ts.CallExpression,
12
+ checker: ts.TypeChecker
13
+ ): boolean {
14
+ if (!ts.isPropertyAccessExpression(node.expression)) {
15
+ return false
16
+ }
17
+
18
+ const propAccess = node.expression
19
+ return (
20
+ propAccess.name.text === 'do' &&
21
+ ts.isIdentifier(propAccess.expression) &&
22
+ propAccess.expression.text === 'workflow'
23
+ )
24
+ }
25
+
26
+ /**
27
+ * Check if a call expression is workflow.sleep()
28
+ */
29
+ export function isWorkflowSleepCall(
30
+ node: ts.CallExpression,
31
+ checker: ts.TypeChecker
32
+ ): boolean {
33
+ if (!ts.isPropertyAccessExpression(node.expression)) {
34
+ return false
35
+ }
36
+
37
+ const propAccess = node.expression
38
+ return (
39
+ propAccess.name.text === 'sleep' &&
40
+ ts.isIdentifier(propAccess.expression) &&
41
+ propAccess.expression.text === 'workflow'
42
+ )
43
+ }
44
+
45
+ /**
46
+ * Check if an expression is Promise.all(array.map(...))
47
+ */
48
+ export function isParallelFanout(node: ts.CallExpression): boolean {
49
+ // Promise.all(...)
50
+ if (!ts.isPropertyAccessExpression(node.expression)) {
51
+ return false
52
+ }
53
+
54
+ const propAccess = node.expression
55
+ if (
56
+ !ts.isIdentifier(propAccess.expression) ||
57
+ propAccess.expression.text !== 'Promise' ||
58
+ propAccess.name.text !== 'all'
59
+ ) {
60
+ return false
61
+ }
62
+
63
+ // Check if argument is array.map(...)
64
+ const arg = node.arguments[0]
65
+ if (!arg || !ts.isCallExpression(arg)) {
66
+ return false
67
+ }
68
+
69
+ if (!ts.isPropertyAccessExpression(arg.expression)) {
70
+ return false
71
+ }
72
+
73
+ return arg.expression.name.text === 'map'
74
+ }
75
+
76
+ /**
77
+ * Check if an expression is Promise.all([...])
78
+ */
79
+ export function isParallelGroup(node: ts.CallExpression): boolean {
80
+ // Promise.all(...)
81
+ if (!ts.isPropertyAccessExpression(node.expression)) {
82
+ return false
83
+ }
84
+
85
+ const propAccess = node.expression
86
+ if (
87
+ !ts.isIdentifier(propAccess.expression) ||
88
+ propAccess.expression.text !== 'Promise' ||
89
+ propAccess.name.text !== 'all'
90
+ ) {
91
+ return false
92
+ }
93
+
94
+ // Check if argument is an array literal
95
+ const arg = node.arguments[0]
96
+ return !!arg && ts.isArrayLiteralExpression(arg)
97
+ }
98
+
99
+ /**
100
+ * Check if a for statement is a valid sequential fanout (for..of)
101
+ */
102
+ export function isSequentialFanout(node: ts.ForOfStatement): boolean {
103
+ // Must have const declaration
104
+ if (!ts.isVariableDeclarationList(node.initializer)) {
105
+ return false
106
+ }
107
+
108
+ const declList = node.initializer
109
+ if (!(declList.flags & ts.NodeFlags.Const)) {
110
+ return false
111
+ }
112
+
113
+ // Must have exactly one declaration
114
+ if (declList.declarations.length !== 1) {
115
+ return false
116
+ }
117
+
118
+ return true
119
+ }
120
+
121
+ /**
122
+ * Extract the variable name from a for..of statement
123
+ */
124
+ export function extractForOfVariable(
125
+ node: ts.ForOfStatement
126
+ ): { itemVar: string; sourceVar: string } | null {
127
+ if (!ts.isVariableDeclarationList(node.initializer)) {
128
+ return null
129
+ }
130
+
131
+ const decl = node.initializer.declarations[0]
132
+ if (!ts.isIdentifier(decl.name)) {
133
+ return null
134
+ }
135
+
136
+ const itemVar = decl.name.text
137
+
138
+ // Extract source variable
139
+ let sourceVar: string | null = null
140
+ if (ts.isIdentifier(node.expression)) {
141
+ sourceVar = node.expression.text
142
+ } else if (
143
+ ts.isPropertyAccessExpression(node.expression) &&
144
+ ts.isIdentifier(node.expression.expression)
145
+ ) {
146
+ // Handle data.memberEmails
147
+ sourceVar = node.expression.expression.text
148
+ }
149
+
150
+ if (!sourceVar) {
151
+ return null
152
+ }
153
+
154
+ return { itemVar, sourceVar }
155
+ }
156
+
157
+ /**
158
+ * Check if a type is an array type
159
+ */
160
+ export function isArrayType(type: ts.Type, checker: ts.TypeChecker): boolean {
161
+ // Check if it's an array type
162
+ if (checker.isArrayType(type)) {
163
+ return true
164
+ }
165
+
166
+ // Check if it's a tuple type
167
+ if (type.flags & ts.TypeFlags.Object) {
168
+ const objectType = type as ts.ObjectType
169
+ if (objectType.objectFlags & ts.ObjectFlags.Tuple) {
170
+ return true
171
+ }
172
+ }
173
+
174
+ return false
175
+ }
176
+
177
+ /**
178
+ * Get the source text of a node (for condition expressions)
179
+ */
180
+ export function getSourceText(node: ts.Node): string {
181
+ return node.getText().trim()
182
+ }
@@ -0,0 +1,153 @@
1
+ import * as ts from 'typescript'
2
+
3
+ /**
4
+ * Validation rules for simple workflows
5
+ */
6
+
7
+ export interface ValidationError {
8
+ message: string
9
+ node: ts.Node
10
+ }
11
+
12
+ /**
13
+ * Check if a node contains only allowed patterns
14
+ *
15
+ * Allowed patterns:
16
+ * - VariableStatement (const/let declarations)
17
+ * - ExpressionStatement (await workflow.do, await workflow.sleep, await Promise.all)
18
+ * - IfStatement (branches)
19
+ * - ForOfStatement (sequential fanout)
20
+ * - ReturnStatement
21
+ * - Block (containers)
22
+ */
23
+ export function validateNoDisallowedPatterns(node: ts.Node): ValidationError[] {
24
+ const errors: ValidationError[] = []
25
+
26
+ function visitBlock(block: ts.Block) {
27
+ for (const statement of block.statements) {
28
+ if (
29
+ ts.isVariableStatement(statement) ||
30
+ ts.isExpressionStatement(statement) ||
31
+ ts.isIfStatement(statement) ||
32
+ ts.isForOfStatement(statement) ||
33
+ ts.isReturnStatement(statement)
34
+ ) {
35
+ // Allowed statement type - recurse into it
36
+ visitNode(statement)
37
+ } else {
38
+ // Unknown/disallowed statement type
39
+ const nodeType = ts.SyntaxKind[statement.kind]
40
+ errors.push({
41
+ message: `Statement type '${nodeType}' is not allowed in simple workflows. Allowed: const/let, if/else, for..of, return, and workflow calls. If this should be supported, please report the node type: ${nodeType}`,
42
+ node: statement,
43
+ })
44
+ }
45
+ }
46
+ }
47
+
48
+ function visitNode(node: ts.Node) {
49
+ // Disallow while and do-while
50
+ if (ts.isWhileStatement(node) || ts.isDoStatement(node)) {
51
+ errors.push({
52
+ message: 'while and do-while loops are not allowed in simple workflows',
53
+ node,
54
+ })
55
+ return
56
+ }
57
+
58
+ // Disallow for and for-in loops
59
+ if (ts.isForInStatement(node) || ts.isForStatement(node)) {
60
+ errors.push({
61
+ message:
62
+ 'for and for-in loops are not allowed in simple workflows. Use for-of instead.',
63
+ node,
64
+ })
65
+ return
66
+ }
67
+
68
+ // Check for inline workflow.do
69
+ if (ts.isCallExpression(node)) {
70
+ if (ts.isPropertyAccessExpression(node.expression)) {
71
+ const propAccess = node.expression
72
+ if (
73
+ propAccess.name.text === 'do' &&
74
+ ts.isIdentifier(propAccess.expression) &&
75
+ propAccess.expression.text === 'workflow'
76
+ ) {
77
+ const secondArg = node.arguments[1]
78
+ if (
79
+ secondArg &&
80
+ (ts.isArrowFunction(secondArg) ||
81
+ ts.isFunctionExpression(secondArg))
82
+ ) {
83
+ errors.push({
84
+ message:
85
+ 'Inline workflow.do with function argument is not allowed in simple workflows. Use RPC form instead.',
86
+ node,
87
+ })
88
+ return
89
+ }
90
+ }
91
+ }
92
+ }
93
+
94
+ // Recurse into blocks
95
+ if (ts.isBlock(node)) {
96
+ visitBlock(node)
97
+ } else {
98
+ ts.forEachChild(node, visitNode)
99
+ }
100
+ }
101
+
102
+ visitNode(node)
103
+ return errors
104
+ }
105
+
106
+ /**
107
+ * Validate that all workflow.do calls are awaited
108
+ */
109
+ export function validateAwaitedCalls(node: ts.Node): ValidationError[] {
110
+ const errors: ValidationError[] = []
111
+
112
+ function visit(node: ts.Node, parentIsAwait: boolean = false) {
113
+ if (ts.isCallExpression(node)) {
114
+ if (ts.isPropertyAccessExpression(node.expression)) {
115
+ const propAccess = node.expression
116
+ if (
117
+ (propAccess.name.text === 'do' || propAccess.name.text === 'sleep') &&
118
+ ts.isIdentifier(propAccess.expression) &&
119
+ propAccess.expression.text === 'workflow'
120
+ ) {
121
+ if (!parentIsAwait) {
122
+ errors.push({
123
+ message: `workflow.${propAccess.name.text}() must be awaited`,
124
+ node,
125
+ })
126
+ }
127
+ return
128
+ }
129
+ }
130
+ }
131
+
132
+ if (ts.isAwaitExpression(node)) {
133
+ // Mark child as awaited
134
+ ts.forEachChild(node.expression, (child) => visit(child, true))
135
+ } else {
136
+ ts.forEachChild(node, (child) => visit(child, false))
137
+ }
138
+ }
139
+
140
+ visit(node)
141
+ return errors
142
+ }
143
+
144
+ /**
145
+ * Combine all validation errors into a single error message
146
+ */
147
+ export function formatValidationErrors(errors: ValidationError[]): string {
148
+ if (errors.length === 0) {
149
+ return ''
150
+ }
151
+
152
+ return errors.map((err) => `- ${err.message}`).join('\n')
153
+ }