@pikku/inspector 0.12.9 → 0.12.10
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 +18 -0
- package/dist/add/add-functions.js +3 -0
- package/dist/add/add-workflow.d.ts +1 -1
- package/dist/add/add-workflow.js +10 -10
- package/dist/utils/extract-node-value.js +4 -0
- package/dist/utils/workflow/dsl/extract-dsl-workflow.d.ts +5 -4
- package/dist/utils/workflow/dsl/extract-dsl-workflow.js +47 -28
- package/dist/utils/workflow/dsl/patterns.d.ts +5 -1
- package/dist/utils/workflow/dsl/patterns.js +2 -2
- package/dist/utils/workflow/dsl/validation.d.ts +4 -2
- package/dist/utils/workflow/dsl/validation.js +16 -8
- package/package.json +2 -2
- package/src/add/add-functions.ts +8 -3
- package/src/add/add-workflow.ts +11 -11
- package/src/utils/extract-node-value.ts +5 -0
- package/src/utils/workflow/dsl/extract-dsl-workflow.ts +58 -32
- package/src/utils/workflow/dsl/patterns.ts +2 -2
- package/src/utils/workflow/dsl/validation.ts +21 -9
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
## 0.12.0
|
|
2
2
|
|
|
3
|
+
## 0.12.10
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- ba8d6ff: Support inline functions in pikkuWorkflowComplexFunc with full DSL extraction
|
|
8
|
+
- d3ace0e: Inspector now captures the `deploy: 'serverless' | 'server' | 'auto'` option
|
|
9
|
+
from `pikkuFunc` / `pikkuSessionlessFunc` calls, alongside the other runtime
|
|
10
|
+
metadata (`expose`, `remote`, `mcp`, `readonly`, `approvalRequired`).
|
|
11
|
+
|
|
12
|
+
Previously this field was defined on `FunctionRuntimeMeta` but never read
|
|
13
|
+
from the user's source, so `deploy: 'server'` was silently dropped. That
|
|
14
|
+
left downstream consumers — notably `@pikku/cli`'s deployment analyzer,
|
|
15
|
+
which routes server-targeted functions to a container unit — treating
|
|
16
|
+
every function as `serverless` regardless of its declared intent.
|
|
17
|
+
|
|
18
|
+
- Updated dependencies [311c0c4]
|
|
19
|
+
- @pikku/core@0.12.18
|
|
20
|
+
|
|
3
21
|
## 0.12.9
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
|
@@ -245,6 +245,7 @@ export const addFunctions = (logger, node, checker, state, options) => {
|
|
|
245
245
|
let remote;
|
|
246
246
|
let mcp;
|
|
247
247
|
let readonly_;
|
|
248
|
+
let deploy;
|
|
248
249
|
let approvalRequired;
|
|
249
250
|
let approvalDescription;
|
|
250
251
|
let version;
|
|
@@ -312,6 +313,7 @@ export const addFunctions = (logger, node, checker, state, options) => {
|
|
|
312
313
|
remote = getPropertyValue(firstArg, 'remote');
|
|
313
314
|
mcp = getPropertyValue(firstArg, 'mcp');
|
|
314
315
|
readonly_ = getPropertyValue(firstArg, 'readonly');
|
|
316
|
+
deploy = getPropertyValue(firstArg, 'deploy');
|
|
315
317
|
approvalRequired = getPropertyValue(firstArg, 'approvalRequired');
|
|
316
318
|
// Extract approvalDescription identifier reference
|
|
317
319
|
for (const prop of firstArg.properties) {
|
|
@@ -583,6 +585,7 @@ export const addFunctions = (logger, node, checker, state, options) => {
|
|
|
583
585
|
remote: remote || undefined,
|
|
584
586
|
mcp: mcpEnabled || undefined,
|
|
585
587
|
readonly: readonly_ || undefined,
|
|
588
|
+
deploy: deploy || undefined,
|
|
586
589
|
approvalRequired: approvalRequired || undefined,
|
|
587
590
|
approvalDescription: approvalDescription || undefined,
|
|
588
591
|
version,
|
|
@@ -5,7 +5,7 @@ import type { WorkflowStepMeta } from '@pikku/core/workflow';
|
|
|
5
5
|
*/
|
|
6
6
|
export declare function collectInvokedRPCs(steps: WorkflowStepMeta[], rpcs: Set<string>): void;
|
|
7
7
|
/**
|
|
8
|
-
* Inspector for
|
|
8
|
+
* Inspector for pikkuWorkflowFunc() and pikkuWorkflowComplexFunc() calls
|
|
9
9
|
* Detects workflow registration and extracts metadata
|
|
10
10
|
*/
|
|
11
11
|
export declare const addWorkflow: AddWiring;
|
package/dist/add/add-workflow.js
CHANGED
|
@@ -142,7 +142,7 @@ function getWorkflowInvocations(node, checker, state, workflowName, steps) {
|
|
|
142
142
|
});
|
|
143
143
|
}
|
|
144
144
|
/**
|
|
145
|
-
* Inspector for
|
|
145
|
+
* Inspector for pikkuWorkflowFunc() and pikkuWorkflowComplexFunc() calls
|
|
146
146
|
* Detects workflow registration and extracts metadata
|
|
147
147
|
*/
|
|
148
148
|
export const addWorkflow = (logger, node, checker, state) => {
|
|
@@ -160,7 +160,7 @@ export const addWorkflow = (logger, node, checker, state) => {
|
|
|
160
160
|
wrapperType = 'dsl';
|
|
161
161
|
}
|
|
162
162
|
else if (expression.text === 'pikkuWorkflowComplexFunc') {
|
|
163
|
-
wrapperType = '
|
|
163
|
+
wrapperType = 'complex';
|
|
164
164
|
}
|
|
165
165
|
else {
|
|
166
166
|
return;
|
|
@@ -220,7 +220,9 @@ export const addWorkflow = (logger, node, checker, state) => {
|
|
|
220
220
|
let dsl = undefined;
|
|
221
221
|
// Try DSL workflow extraction first
|
|
222
222
|
// Pass the whole CallExpression node so findWorkflowFunction can find the arrow function
|
|
223
|
-
const result = extractDSLWorkflow(node, checker
|
|
223
|
+
const result = extractDSLWorkflow(node, checker, {
|
|
224
|
+
allowInline: wrapperType === 'complex',
|
|
225
|
+
});
|
|
224
226
|
if (result.status === 'ok' && result.steps) {
|
|
225
227
|
// Extraction succeeded
|
|
226
228
|
steps = result.steps;
|
|
@@ -252,7 +254,7 @@ export const addWorkflow = (logger, node, checker, state) => {
|
|
|
252
254
|
// For pikkuWorkflowFunc, this is a critical error
|
|
253
255
|
// But still track RPC invocations for function registration
|
|
254
256
|
getWorkflowInvocations(resolvedFunc, checker, state, workflowName, steps);
|
|
255
|
-
logger.critical(ErrorCode.INVALID_DSL_WORKFLOW, `Workflow '${workflowName}'
|
|
257
|
+
logger.critical(ErrorCode.INVALID_DSL_WORKFLOW, `Workflow '${workflowName}' does not conform to DSL workflow rules:\n${result.reason || 'Unknown error'}`);
|
|
256
258
|
return;
|
|
257
259
|
}
|
|
258
260
|
else {
|
|
@@ -261,12 +263,10 @@ export const addWorkflow = (logger, node, checker, state) => {
|
|
|
261
263
|
dsl = false;
|
|
262
264
|
}
|
|
263
265
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
*/
|
|
269
|
-
if (!dsl || wrapperType === 'regular') {
|
|
266
|
+
// For pikkuWorkflowComplexFunc, also run basic extraction so RPCs in
|
|
267
|
+
// patterns the DSL extractor doesn't handle (array+push, nested Promise.all
|
|
268
|
+
// with identifier args, etc.) are still registered as invoked functions.
|
|
269
|
+
if (wrapperType === 'complex') {
|
|
270
270
|
getWorkflowInvocations(resolvedFunc, checker, state, workflowName, steps);
|
|
271
271
|
}
|
|
272
272
|
state.workflows.meta[workflowName] = {
|
|
@@ -20,6 +20,10 @@ export function extractStringLiteral(node, checker) {
|
|
|
20
20
|
}
|
|
21
21
|
return result;
|
|
22
22
|
}
|
|
23
|
+
// Unwrap type assertions: `expr as Type` or `<Type>expr`
|
|
24
|
+
if (ts.isAsExpression(node) || ts.isTypeAssertionExpression(node)) {
|
|
25
|
+
return extractStringLiteral(node.expression, checker);
|
|
26
|
+
}
|
|
23
27
|
// Try to evaluate constant identifiers
|
|
24
28
|
if (ts.isIdentifier(node)) {
|
|
25
29
|
const symbol = checker.getSymbolAtLocation(node);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
2
|
import type { WorkflowStepMeta, WorkflowContext } from '@pikku/core/workflow';
|
|
3
3
|
/**
|
|
4
|
-
* Result of
|
|
4
|
+
* Result of DSL workflow extraction
|
|
5
5
|
*/
|
|
6
6
|
export interface ExtractionResult {
|
|
7
7
|
status: 'ok' | 'error';
|
|
@@ -9,9 +9,10 @@ export interface ExtractionResult {
|
|
|
9
9
|
/** Workflow context (top-level variables) */
|
|
10
10
|
context?: WorkflowContext;
|
|
11
11
|
reason?: string;
|
|
12
|
-
simple?: boolean;
|
|
13
12
|
}
|
|
14
13
|
/**
|
|
15
|
-
* Extract
|
|
14
|
+
* Extract DSL workflow metadata from a function declaration
|
|
16
15
|
*/
|
|
17
|
-
export declare function extractDSLWorkflow(funcNode: ts.Node, checker: ts.TypeChecker
|
|
16
|
+
export declare function extractDSLWorkflow(funcNode: ts.Node, checker: ts.TypeChecker, options?: {
|
|
17
|
+
allowInline?: boolean;
|
|
18
|
+
}): ExtractionResult;
|
|
@@ -1,26 +1,11 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
|
-
import { isWorkflowDoCall, isWorkflowSleepCall, isThrowCancelException, extractCancelReason, isParallelFanout, isParallelGroup, isSequentialFanout, isArrayFilter, isArraySome, isArrayEvery, extractForOfVariable, isArrayType, getSourceText, } from './patterns.js';
|
|
2
|
+
import { isWorkflowDoCall, isWorkflowSleepCall, isThrowCancelException, extractCancelReason, isParallelFanout, isParallelGroup, isSequentialFanout, isArrayFilter, isArraySome, isArrayEvery, extractForOfVariable, isArrayType, getSourceText, extractSourcePath, } from './patterns.js';
|
|
3
3
|
import { validateNoDisallowedPatterns, validateAwaitedCalls, formatValidationErrors, } from './validation.js';
|
|
4
4
|
import { extractStringLiteral, extractNumberLiteral, } from '../../extract-node-value.js';
|
|
5
5
|
/**
|
|
6
|
-
* Extract
|
|
6
|
+
* Extract DSL workflow metadata from a function declaration
|
|
7
7
|
*/
|
|
8
|
-
function
|
|
9
|
-
if (ts.isIdentifier(expr)) {
|
|
10
|
-
return expr.text;
|
|
11
|
-
}
|
|
12
|
-
if (ts.isPropertyAccessExpression(expr)) {
|
|
13
|
-
const base = extractSourcePath(expr.expression);
|
|
14
|
-
if (base) {
|
|
15
|
-
return `${base}.${expr.name.text}`;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
return null;
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Extract simple workflow metadata from a function declaration
|
|
22
|
-
*/
|
|
23
|
-
export function extractDSLWorkflow(funcNode, checker) {
|
|
8
|
+
export function extractDSLWorkflow(funcNode, checker, options) {
|
|
24
9
|
try {
|
|
25
10
|
// Find the async arrow function
|
|
26
11
|
const arrowFunc = findWorkflowFunction(funcNode);
|
|
@@ -28,7 +13,6 @@ export function extractDSLWorkflow(funcNode, checker) {
|
|
|
28
13
|
return {
|
|
29
14
|
status: 'error',
|
|
30
15
|
reason: 'Could not find async arrow function in workflow definition',
|
|
31
|
-
simple: false,
|
|
32
16
|
};
|
|
33
17
|
}
|
|
34
18
|
// Extract input parameter name (second parameter)
|
|
@@ -37,7 +21,6 @@ export function extractDSLWorkflow(funcNode, checker) {
|
|
|
37
21
|
return {
|
|
38
22
|
status: 'error',
|
|
39
23
|
reason: 'Could not determine input parameter name',
|
|
40
|
-
simple: false,
|
|
41
24
|
};
|
|
42
25
|
}
|
|
43
26
|
// Initialize extraction context
|
|
@@ -53,12 +36,13 @@ export function extractDSLWorkflow(funcNode, checker) {
|
|
|
53
36
|
depth: 0,
|
|
54
37
|
};
|
|
55
38
|
// Validate no disallowed patterns
|
|
56
|
-
const patternErrors = validateNoDisallowedPatterns(arrowFunc.body
|
|
39
|
+
const patternErrors = validateNoDisallowedPatterns(arrowFunc.body, {
|
|
40
|
+
allowInline: options?.allowInline,
|
|
41
|
+
});
|
|
57
42
|
if (patternErrors.length > 0) {
|
|
58
43
|
return {
|
|
59
44
|
status: 'error',
|
|
60
45
|
reason: formatValidationErrors(patternErrors),
|
|
61
|
-
simple: false,
|
|
62
46
|
};
|
|
63
47
|
}
|
|
64
48
|
// Validate all workflow calls are awaited
|
|
@@ -67,7 +51,6 @@ export function extractDSLWorkflow(funcNode, checker) {
|
|
|
67
51
|
return {
|
|
68
52
|
status: 'error',
|
|
69
53
|
reason: formatValidationErrors(awaitErrors),
|
|
70
|
-
simple: false,
|
|
71
54
|
};
|
|
72
55
|
}
|
|
73
56
|
// Extract steps from function body
|
|
@@ -77,7 +60,6 @@ export function extractDSLWorkflow(funcNode, checker) {
|
|
|
77
60
|
return {
|
|
78
61
|
status: 'error',
|
|
79
62
|
reason: formatValidationErrors(context.errors),
|
|
80
|
-
simple: false,
|
|
81
63
|
};
|
|
82
64
|
}
|
|
83
65
|
// Build workflow context from extracted context variables
|
|
@@ -92,14 +74,12 @@ export function extractDSLWorkflow(funcNode, checker) {
|
|
|
92
74
|
status: 'ok',
|
|
93
75
|
steps,
|
|
94
76
|
context: Object.keys(workflowContext).length > 0 ? workflowContext : undefined,
|
|
95
|
-
simple: true,
|
|
96
77
|
};
|
|
97
78
|
}
|
|
98
79
|
catch (error) {
|
|
99
80
|
return {
|
|
100
81
|
status: 'error',
|
|
101
82
|
reason: error instanceof Error ? error.message : String(error),
|
|
102
|
-
simple: false,
|
|
103
83
|
};
|
|
104
84
|
}
|
|
105
85
|
}
|
|
@@ -248,7 +228,9 @@ function extractVariableDeclaration(statement, context) {
|
|
|
248
228
|
if (ts.isAwaitExpression(init) && ts.isCallExpression(init.expression)) {
|
|
249
229
|
const call = init.expression;
|
|
250
230
|
if (isWorkflowDoCall(call, context.checker)) {
|
|
251
|
-
const step =
|
|
231
|
+
const step = isInlineDoCall(call)
|
|
232
|
+
? extractInlineStep(call, context)
|
|
233
|
+
: extractRpcStep(call, context, varName);
|
|
252
234
|
if (step) {
|
|
253
235
|
// Track output variable
|
|
254
236
|
const type = context.checker.getTypeAtLocation(decl);
|
|
@@ -335,7 +317,9 @@ function extractExpressionStatement(statement, context) {
|
|
|
335
317
|
if (ts.isAwaitExpression(expr) && ts.isCallExpression(expr.expression)) {
|
|
336
318
|
const call = expr.expression;
|
|
337
319
|
if (isWorkflowDoCall(call, context.checker)) {
|
|
338
|
-
const step =
|
|
320
|
+
const step = isInlineDoCall(call)
|
|
321
|
+
? extractInlineStep(call, context)
|
|
322
|
+
: extractRpcStep(call, context, outputVar);
|
|
339
323
|
// Track output variable if this is an assignment
|
|
340
324
|
if (outputVar && step) {
|
|
341
325
|
const type = context.checker.getTypeAtLocation(expr);
|
|
@@ -394,6 +378,41 @@ function extractRpcStep(call, context, outputVar) {
|
|
|
394
378
|
return null;
|
|
395
379
|
}
|
|
396
380
|
}
|
|
381
|
+
/**
|
|
382
|
+
* Extract inline step from workflow.do() call with a function argument
|
|
383
|
+
*/
|
|
384
|
+
function extractInlineStep(call, context) {
|
|
385
|
+
const args = call.arguments;
|
|
386
|
+
if (args.length < 2)
|
|
387
|
+
return null;
|
|
388
|
+
try {
|
|
389
|
+
const stepName = extractStringLiteral(args[0], context.checker);
|
|
390
|
+
const optionsArg = args.length >= 3 ? args[args.length - 1] : undefined;
|
|
391
|
+
const options = optionsArg && ts.isObjectLiteralExpression(optionsArg)
|
|
392
|
+
? extractStepOptions(optionsArg, context)
|
|
393
|
+
: undefined;
|
|
394
|
+
return {
|
|
395
|
+
type: 'inline',
|
|
396
|
+
stepName,
|
|
397
|
+
options,
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
catch (error) {
|
|
401
|
+
context.errors.push({
|
|
402
|
+
message: `Failed to extract inline step: ${error instanceof Error ? error.message : String(error)}`,
|
|
403
|
+
node: call,
|
|
404
|
+
});
|
|
405
|
+
return null;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Check if the second argument of a workflow.do() call is a function
|
|
410
|
+
*/
|
|
411
|
+
function isInlineDoCall(call) {
|
|
412
|
+
const secondArg = call.arguments[1];
|
|
413
|
+
return (!!secondArg &&
|
|
414
|
+
(ts.isArrowFunction(secondArg) || ts.isFunctionExpression(secondArg)));
|
|
415
|
+
}
|
|
397
416
|
/**
|
|
398
417
|
* Extract step options from options object
|
|
399
418
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
2
|
/**
|
|
3
|
-
* Pattern detection helpers for
|
|
3
|
+
* Pattern detection helpers for DSL workflow extraction
|
|
4
4
|
*/
|
|
5
5
|
/**
|
|
6
6
|
* Check if a call expression is workflow.do()
|
|
@@ -43,6 +43,10 @@ export declare function isParallelGroup(node: ts.CallExpression): boolean;
|
|
|
43
43
|
* Check if a for statement is a valid sequential fanout (for..of)
|
|
44
44
|
*/
|
|
45
45
|
export declare function isSequentialFanout(node: ts.ForOfStatement): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Extract full source path from an expression (e.g., data.memberEmails)
|
|
48
|
+
*/
|
|
49
|
+
export declare function extractSourcePath(expr: ts.Expression): string | null;
|
|
46
50
|
/**
|
|
47
51
|
* Extract the variable name from a for..of statement
|
|
48
52
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
2
|
/**
|
|
3
|
-
* Pattern detection helpers for
|
|
3
|
+
* Pattern detection helpers for DSL workflow extraction
|
|
4
4
|
*/
|
|
5
5
|
/**
|
|
6
6
|
* Check if a call expression is workflow.do()
|
|
@@ -162,7 +162,7 @@ export function isSequentialFanout(node) {
|
|
|
162
162
|
/**
|
|
163
163
|
* Extract full source path from an expression (e.g., data.memberEmails)
|
|
164
164
|
*/
|
|
165
|
-
function extractSourcePath(expr) {
|
|
165
|
+
export function extractSourcePath(expr) {
|
|
166
166
|
if (ts.isIdentifier(expr)) {
|
|
167
167
|
return expr.text;
|
|
168
168
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
2
|
/**
|
|
3
|
-
* Validation rules for
|
|
3
|
+
* Validation rules for DSL workflows
|
|
4
4
|
*/
|
|
5
5
|
export interface ValidationError {
|
|
6
6
|
message: string;
|
|
@@ -19,7 +19,9 @@ export interface ValidationError {
|
|
|
19
19
|
* - ThrowStatement (for WorkflowCancelledException)
|
|
20
20
|
* - Block (containers)
|
|
21
21
|
*/
|
|
22
|
-
export declare function validateNoDisallowedPatterns(node: ts.Node
|
|
22
|
+
export declare function validateNoDisallowedPatterns(node: ts.Node, options?: {
|
|
23
|
+
allowInline?: boolean;
|
|
24
|
+
}): ValidationError[];
|
|
23
25
|
/**
|
|
24
26
|
* Validate that all workflow.do calls are awaited
|
|
25
27
|
*/
|
|
@@ -12,7 +12,7 @@ import * as ts from 'typescript';
|
|
|
12
12
|
* - ThrowStatement (for WorkflowCancelledException)
|
|
13
13
|
* - Block (containers)
|
|
14
14
|
*/
|
|
15
|
-
export function validateNoDisallowedPatterns(node) {
|
|
15
|
+
export function validateNoDisallowedPatterns(node, options) {
|
|
16
16
|
const errors = [];
|
|
17
17
|
function visitBlock(block) {
|
|
18
18
|
for (const statement of block.statements) {
|
|
@@ -30,7 +30,7 @@ export function validateNoDisallowedPatterns(node) {
|
|
|
30
30
|
// Unknown/disallowed statement type
|
|
31
31
|
const nodeType = ts.SyntaxKind[statement.kind];
|
|
32
32
|
errors.push({
|
|
33
|
-
message: `Statement type '${nodeType}' is not allowed in
|
|
33
|
+
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}`,
|
|
34
34
|
node: statement,
|
|
35
35
|
});
|
|
36
36
|
}
|
|
@@ -40,7 +40,7 @@ export function validateNoDisallowedPatterns(node) {
|
|
|
40
40
|
// Disallow while and do-while
|
|
41
41
|
if (ts.isWhileStatement(node) || ts.isDoStatement(node)) {
|
|
42
42
|
errors.push({
|
|
43
|
-
message: 'while and do-while loops are not allowed in
|
|
43
|
+
message: 'while and do-while loops are not allowed in DSL workflows',
|
|
44
44
|
node,
|
|
45
45
|
});
|
|
46
46
|
return;
|
|
@@ -48,13 +48,13 @@ export function validateNoDisallowedPatterns(node) {
|
|
|
48
48
|
// Disallow for and for-in loops
|
|
49
49
|
if (ts.isForInStatement(node) || ts.isForStatement(node)) {
|
|
50
50
|
errors.push({
|
|
51
|
-
message: 'for and for-in loops are not allowed in
|
|
51
|
+
message: 'for and for-in loops are not allowed in DSL workflows. Use for-of instead.',
|
|
52
52
|
node,
|
|
53
53
|
});
|
|
54
54
|
return;
|
|
55
55
|
}
|
|
56
56
|
// Check for inline workflow.do
|
|
57
|
-
if (ts.isCallExpression(node)) {
|
|
57
|
+
if (!options?.allowInline && ts.isCallExpression(node)) {
|
|
58
58
|
if (ts.isPropertyAccessExpression(node.expression)) {
|
|
59
59
|
const propAccess = node.expression;
|
|
60
60
|
if (propAccess.name.text === 'do' &&
|
|
@@ -65,7 +65,7 @@ export function validateNoDisallowedPatterns(node) {
|
|
|
65
65
|
(ts.isArrowFunction(secondArg) ||
|
|
66
66
|
ts.isFunctionExpression(secondArg))) {
|
|
67
67
|
errors.push({
|
|
68
|
-
message: 'Inline workflow.do with function argument is not allowed in
|
|
68
|
+
message: 'Inline workflow.do with function argument is not allowed in DSL workflows. Use pikkuWorkflowComplexFunc instead.',
|
|
69
69
|
node,
|
|
70
70
|
});
|
|
71
71
|
return;
|
|
@@ -97,11 +97,19 @@ export function validateAwaitedCalls(node) {
|
|
|
97
97
|
if (propAccess.name.text === 'all' &&
|
|
98
98
|
ts.isIdentifier(propAccess.expression) &&
|
|
99
99
|
propAccess.expression.text === 'Promise') {
|
|
100
|
-
// console.log('[DEBUG] Found Promise.all, setting insidePromiseAll=true')
|
|
101
|
-
// Visit children with insidePromiseAll = true
|
|
102
100
|
ts.forEachChild(node, (child) => visit(child, parentIsAwait, true));
|
|
103
101
|
return;
|
|
104
102
|
}
|
|
103
|
+
// .push() on an array — workflow.do() inside is collecting promises
|
|
104
|
+
if (propAccess.name.text === 'push') {
|
|
105
|
+
ts.forEachChild(node, (child) => visit(child, parentIsAwait, true));
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// Array literal — workflow.do() inside is collecting promises
|
|
110
|
+
if (ts.isArrayLiteralExpression(node)) {
|
|
111
|
+
ts.forEachChild(node, (child) => visit(child, parentIsAwait, true));
|
|
112
|
+
return;
|
|
105
113
|
}
|
|
106
114
|
// Now check for workflow calls
|
|
107
115
|
if (ts.isCallExpression(node)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pikku/inspector",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.10",
|
|
4
4
|
"author": "yasser.fadl@gmail.com",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"type": "module",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@openapi-contrib/json-schema-to-openapi-schema": "^4.3.1",
|
|
38
|
-
"@pikku/core": "^0.12.
|
|
38
|
+
"@pikku/core": "^0.12.18",
|
|
39
39
|
"path-to-regexp": "^8.3.0",
|
|
40
40
|
"ts-json-schema-generator": "^2.5.0",
|
|
41
41
|
"tsx": "^4.21.0",
|
package/src/add/add-functions.ts
CHANGED
|
@@ -339,6 +339,7 @@ export const addFunctions: AddWiring = (
|
|
|
339
339
|
let remote: boolean | undefined
|
|
340
340
|
let mcp: boolean | undefined
|
|
341
341
|
let readonly_: boolean | undefined
|
|
342
|
+
let deploy: 'serverless' | 'server' | 'auto' | undefined
|
|
342
343
|
let approvalRequired: boolean | undefined
|
|
343
344
|
let approvalDescription: string | undefined
|
|
344
345
|
let version: number | undefined
|
|
@@ -422,6 +423,11 @@ export const addFunctions: AddWiring = (
|
|
|
422
423
|
remote = getPropertyValue(firstArg, 'remote') as boolean | undefined
|
|
423
424
|
mcp = getPropertyValue(firstArg, 'mcp') as boolean | undefined
|
|
424
425
|
readonly_ = getPropertyValue(firstArg, 'readonly') as boolean | undefined
|
|
426
|
+
deploy = getPropertyValue(firstArg, 'deploy') as
|
|
427
|
+
| 'serverless'
|
|
428
|
+
| 'server'
|
|
429
|
+
| 'auto'
|
|
430
|
+
| undefined
|
|
425
431
|
approvalRequired = getPropertyValue(firstArg, 'approvalRequired') as
|
|
426
432
|
| boolean
|
|
427
433
|
| undefined
|
|
@@ -783,6 +789,7 @@ export const addFunctions: AddWiring = (
|
|
|
783
789
|
remote: remote || undefined,
|
|
784
790
|
mcp: mcpEnabled || undefined,
|
|
785
791
|
readonly: readonly_ || undefined,
|
|
792
|
+
deploy: deploy || undefined,
|
|
786
793
|
approvalRequired: approvalRequired || undefined,
|
|
787
794
|
approvalDescription: approvalDescription || undefined,
|
|
788
795
|
version,
|
|
@@ -817,9 +824,7 @@ export const addFunctions: AddWiring = (
|
|
|
817
824
|
|
|
818
825
|
if (mcpEnabled) {
|
|
819
826
|
if (!description) {
|
|
820
|
-
logger.warn(
|
|
821
|
-
`MCP tool '${name}' is missing a description.`
|
|
822
|
-
)
|
|
827
|
+
logger.warn(`MCP tool '${name}' is missing a description.`)
|
|
823
828
|
}
|
|
824
829
|
state.mcpEndpoints.files.add(node.getSourceFile().fileName)
|
|
825
830
|
state.mcpEndpoints.toolsMeta[name] = {
|
package/src/add/add-workflow.ts
CHANGED
|
@@ -155,7 +155,7 @@ function getWorkflowInvocations(
|
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
/**
|
|
158
|
-
* Inspector for
|
|
158
|
+
* Inspector for pikkuWorkflowFunc() and pikkuWorkflowComplexFunc() calls
|
|
159
159
|
* Detects workflow registration and extracts metadata
|
|
160
160
|
*/
|
|
161
161
|
export const addWorkflow: AddWiring = (logger, node, checker, state) => {
|
|
@@ -171,11 +171,11 @@ export const addWorkflow: AddWiring = (logger, node, checker, state) => {
|
|
|
171
171
|
return
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
-
let wrapperType: 'dsl' | '
|
|
174
|
+
let wrapperType: 'dsl' | 'complex' | null = null
|
|
175
175
|
if (expression.text === 'pikkuWorkflowFunc') {
|
|
176
176
|
wrapperType = 'dsl'
|
|
177
177
|
} else if (expression.text === 'pikkuWorkflowComplexFunc') {
|
|
178
|
-
wrapperType = '
|
|
178
|
+
wrapperType = 'complex'
|
|
179
179
|
} else {
|
|
180
180
|
return
|
|
181
181
|
}
|
|
@@ -267,7 +267,9 @@ export const addWorkflow: AddWiring = (logger, node, checker, state) => {
|
|
|
267
267
|
|
|
268
268
|
// Try DSL workflow extraction first
|
|
269
269
|
// Pass the whole CallExpression node so findWorkflowFunction can find the arrow function
|
|
270
|
-
const result = extractDSLWorkflow(node, checker
|
|
270
|
+
const result = extractDSLWorkflow(node, checker, {
|
|
271
|
+
allowInline: wrapperType === 'complex',
|
|
272
|
+
})
|
|
271
273
|
|
|
272
274
|
if (result.status === 'ok' && result.steps) {
|
|
273
275
|
// Extraction succeeded
|
|
@@ -305,7 +307,7 @@ export const addWorkflow: AddWiring = (logger, node, checker, state) => {
|
|
|
305
307
|
getWorkflowInvocations(resolvedFunc, checker, state, workflowName, steps)
|
|
306
308
|
logger.critical(
|
|
307
309
|
ErrorCode.INVALID_DSL_WORKFLOW,
|
|
308
|
-
`Workflow '${workflowName}'
|
|
310
|
+
`Workflow '${workflowName}' does not conform to DSL workflow rules:\n${result.reason || 'Unknown error'}`
|
|
309
311
|
)
|
|
310
312
|
return
|
|
311
313
|
} else {
|
|
@@ -317,12 +319,10 @@ export const addWorkflow: AddWiring = (logger, node, checker, state) => {
|
|
|
317
319
|
}
|
|
318
320
|
}
|
|
319
321
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
*/
|
|
325
|
-
if (!dsl || wrapperType === 'regular') {
|
|
322
|
+
// For pikkuWorkflowComplexFunc, also run basic extraction so RPCs in
|
|
323
|
+
// patterns the DSL extractor doesn't handle (array+push, nested Promise.all
|
|
324
|
+
// with identifier args, etc.) are still registered as invoked functions.
|
|
325
|
+
if (wrapperType === 'complex') {
|
|
326
326
|
getWorkflowInvocations(resolvedFunc, checker, state, workflowName, steps)
|
|
327
327
|
}
|
|
328
328
|
|
|
@@ -27,6 +27,11 @@ export function extractStringLiteral(
|
|
|
27
27
|
return result
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
// Unwrap type assertions: `expr as Type` or `<Type>expr`
|
|
31
|
+
if (ts.isAsExpression(node) || ts.isTypeAssertionExpression(node)) {
|
|
32
|
+
return extractStringLiteral(node.expression, checker)
|
|
33
|
+
}
|
|
34
|
+
|
|
30
35
|
// Try to evaluate constant identifiers
|
|
31
36
|
if (ts.isIdentifier(node)) {
|
|
32
37
|
const symbol = checker.getSymbolAtLocation(node)
|