@pikku/inspector 0.11.1 → 0.11.2
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 +16 -1
- package/dist/add/add-forge-credential.d.ts +8 -0
- package/dist/add/add-forge-credential.js +77 -0
- package/dist/add/add-forge-node.d.ts +7 -0
- package/dist/add/add-forge-node.js +77 -0
- package/dist/add/add-functions.js +102 -9
- package/dist/add/add-http-route.js +24 -1
- package/dist/add/add-rpc-invocations.d.ts +3 -0
- package/dist/add/add-rpc-invocations.js +51 -25
- package/dist/add/add-workflow-graph.d.ts +6 -0
- package/dist/add/add-workflow-graph.js +659 -0
- package/dist/add/add-workflow.js +118 -22
- package/dist/error-codes.d.ts +3 -1
- package/dist/error-codes.js +3 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/inspector.js +19 -3
- package/dist/types.d.ts +26 -0
- package/dist/utils/extract-function-name.js +7 -7
- package/dist/utils/get-property-value.d.ts +2 -1
- package/dist/utils/get-property-value.js +6 -2
- package/dist/utils/serialize-inspector-state.d.ts +24 -1
- package/dist/utils/serialize-inspector-state.js +24 -0
- package/dist/utils/workflow/dsl/deserialize-dsl-workflow.d.ts +24 -0
- package/dist/utils/workflow/dsl/deserialize-dsl-workflow.js +898 -0
- package/dist/{workflow/extract-simple-workflow.d.ts → utils/workflow/dsl/extract-dsl-workflow.d.ts} +4 -2
- package/dist/{workflow/extract-simple-workflow.js → utils/workflow/dsl/extract-dsl-workflow.js} +549 -68
- package/dist/utils/workflow/dsl/index.d.ts +7 -0
- package/dist/utils/workflow/dsl/index.js +7 -0
- package/dist/{workflow → utils/workflow/dsl}/patterns.d.ts +21 -0
- package/dist/{workflow → utils/workflow/dsl}/patterns.js +90 -10
- package/dist/{workflow → utils/workflow/dsl}/validation.d.ts +2 -0
- package/dist/{workflow → utils/workflow/dsl}/validation.js +25 -7
- package/dist/utils/workflow/graph/convert-dsl-to-graph.d.ts +13 -0
- package/dist/utils/workflow/graph/convert-dsl-to-graph.js +316 -0
- package/dist/utils/workflow/graph/index.d.ts +6 -0
- package/dist/utils/workflow/graph/index.js +6 -0
- package/dist/utils/workflow/graph/serialize-workflow-graph.d.ts +43 -0
- package/dist/utils/workflow/graph/serialize-workflow-graph.js +152 -0
- package/dist/utils/workflow/graph/workflow-graph.types.d.ts +229 -0
- package/dist/utils/workflow/graph/workflow-graph.types.js +38 -0
- package/dist/visit.js +6 -0
- package/package.json +14 -2
- package/src/add/add-forge-credential.ts +119 -0
- package/src/add/add-forge-node.ts +132 -0
- package/src/add/add-functions.ts +129 -15
- package/src/add/add-http-route.ts +25 -1
- package/src/add/add-rpc-invocations.ts +61 -31
- package/src/add/add-workflow-graph.ts +864 -0
- package/src/add/add-workflow.ts +112 -26
- package/src/error-codes.ts +3 -1
- package/src/index.ts +10 -0
- package/src/inspector.ts +20 -4
- package/src/types.ts +25 -1
- package/src/utils/extract-function-name.ts +7 -7
- package/src/utils/get-property-value.ts +9 -2
- package/src/utils/serialize-inspector-state.ts +39 -1
- package/src/utils/workflow/dsl/deserialize-dsl-workflow.ts +1180 -0
- package/src/{workflow/extract-simple-workflow.ts → utils/workflow/dsl/extract-dsl-workflow.ts} +654 -81
- package/src/utils/workflow/dsl/index.ts +11 -0
- package/src/{workflow → utils/workflow/dsl}/patterns.ts +108 -11
- package/src/{workflow → utils/workflow/dsl}/validation.ts +34 -7
- package/src/utils/workflow/graph/convert-dsl-to-graph.ts +415 -0
- package/src/utils/workflow/graph/index.ts +6 -0
- package/src/utils/workflow/graph/serialize-workflow-graph.ts +223 -0
- package/src/utils/workflow/graph/workflow-graph.types.ts +280 -0
- package/src/visit.ts +6 -0
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,19 @@
|
|
|
1
|
-
## 0.11.
|
|
1
|
+
## 0.11.2
|
|
2
|
+
|
|
3
|
+
## 0.11.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- db9c7bf: Add workflow graph inspection and DSL extraction
|
|
8
|
+
- Updated dependencies [db9c7bf]
|
|
9
|
+
- @pikku/core@0.11.2
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
- f35e89da: Add workflow graph inspection and DSL extraction
|
|
14
|
+
- Workflow graph inspection with `add-workflow-graph.ts`
|
|
15
|
+
- DSL workflow extraction utilities (extract, deserialize, validate)
|
|
16
|
+
- DSL to graph conversion for metadata generation
|
|
2
17
|
|
|
3
18
|
## 0.11.1
|
|
4
19
|
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { AddWiring } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Inspector for wireForgeCredential calls.
|
|
4
|
+
* Extracts metadata for Forge package credential declarations.
|
|
5
|
+
* Note: wireForgeCredential is metadata-only - no runtime behavior.
|
|
6
|
+
* Schema is stored as the variable name reference; actual Zod→JSON Schema conversion happens at CLI build time.
|
|
7
|
+
*/
|
|
8
|
+
export declare const addForgeCredential: AddWiring;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import * as ts from 'typescript';
|
|
2
|
+
import { getPropertyValue } from '../utils/get-property-value.js';
|
|
3
|
+
import { ErrorCode } from '../error-codes.js';
|
|
4
|
+
/**
|
|
5
|
+
* Inspector for wireForgeCredential calls.
|
|
6
|
+
* Extracts metadata for Forge package credential declarations.
|
|
7
|
+
* Note: wireForgeCredential is metadata-only - no runtime behavior.
|
|
8
|
+
* Schema is stored as the variable name reference; actual Zod→JSON Schema conversion happens at CLI build time.
|
|
9
|
+
*/
|
|
10
|
+
export const addForgeCredential = (logger, node, _checker, state, _options) => {
|
|
11
|
+
if (!ts.isCallExpression(node)) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const args = node.arguments;
|
|
15
|
+
const firstArg = args[0];
|
|
16
|
+
const expression = node.expression;
|
|
17
|
+
// Check if the call is to wireForgeCredential
|
|
18
|
+
if (!ts.isIdentifier(expression) ||
|
|
19
|
+
expression.text !== 'wireForgeCredential') {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (!firstArg) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (ts.isObjectLiteralExpression(firstArg)) {
|
|
26
|
+
const obj = firstArg;
|
|
27
|
+
const nameValue = getPropertyValue(obj, 'name');
|
|
28
|
+
const displayNameValue = getPropertyValue(obj, 'displayName');
|
|
29
|
+
const descriptionValue = getPropertyValue(obj, 'description');
|
|
30
|
+
const secretIdValue = getPropertyValue(obj, 'secretId');
|
|
31
|
+
// Get schema variable name for later runtime import
|
|
32
|
+
let schemaVariableName = null;
|
|
33
|
+
for (const prop of obj.properties) {
|
|
34
|
+
if (ts.isPropertyAssignment(prop) &&
|
|
35
|
+
ts.isIdentifier(prop.name) &&
|
|
36
|
+
prop.name.text === 'schema') {
|
|
37
|
+
if (ts.isIdentifier(prop.initializer)) {
|
|
38
|
+
schemaVariableName = prop.initializer.text;
|
|
39
|
+
}
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// Validate required fields
|
|
44
|
+
if (!nameValue) {
|
|
45
|
+
logger.critical(ErrorCode.MISSING_NAME, "Forge credential is missing the required 'name' property.");
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (!displayNameValue) {
|
|
49
|
+
logger.critical(ErrorCode.MISSING_NAME, `Forge credential '${nameValue}' is missing the required 'displayName' property.`);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (!secretIdValue) {
|
|
53
|
+
logger.critical(ErrorCode.MISSING_NAME, `Forge credential '${nameValue}' is missing the required 'secretId' property.`);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (!schemaVariableName) {
|
|
57
|
+
logger.critical(ErrorCode.MISSING_NAME, `Forge credential '${nameValue}' is missing the required 'schema' property or schema is not a variable reference.`);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const sourceFile = node.getSourceFile().fileName;
|
|
61
|
+
state.forgeCredentials.files.add(sourceFile);
|
|
62
|
+
// Register the zod schema in the central zodLookup for deferred conversion
|
|
63
|
+
const schemaLookupName = `ForgeCredential_${nameValue}`;
|
|
64
|
+
state.zodLookup.set(schemaLookupName, {
|
|
65
|
+
variableName: schemaVariableName,
|
|
66
|
+
sourceFile,
|
|
67
|
+
});
|
|
68
|
+
// Store metadata - schema conversion happens later in schema-generator
|
|
69
|
+
state.forgeCredentials.meta[nameValue] = {
|
|
70
|
+
name: nameValue,
|
|
71
|
+
displayName: displayNameValue,
|
|
72
|
+
description: descriptionValue || undefined,
|
|
73
|
+
secretId: secretIdValue,
|
|
74
|
+
schema: schemaLookupName,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import * as ts from 'typescript';
|
|
2
|
+
import { getPropertyValue, getCommonWireMetaData, } from '../utils/get-property-value.js';
|
|
3
|
+
import { ErrorCode } from '../error-codes.js';
|
|
4
|
+
/**
|
|
5
|
+
* Inspector for wireForgeNode calls.
|
|
6
|
+
* Extracts metadata for Forge workflow builder nodes.
|
|
7
|
+
* Note: wireForgeNode is metadata-only - no runtime behavior.
|
|
8
|
+
*/
|
|
9
|
+
export const addForgeNode = (logger, node, checker, state, _options) => {
|
|
10
|
+
if (!ts.isCallExpression(node)) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const args = node.arguments;
|
|
14
|
+
const firstArg = args[0];
|
|
15
|
+
const expression = node.expression;
|
|
16
|
+
// Check if the call is to wireForgeNode
|
|
17
|
+
if (!ts.isIdentifier(expression) || expression.text !== 'wireForgeNode') {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (!firstArg) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (ts.isObjectLiteralExpression(firstArg)) {
|
|
24
|
+
const obj = firstArg;
|
|
25
|
+
const nameValue = getPropertyValue(obj, 'name');
|
|
26
|
+
const displayNameValue = getPropertyValue(obj, 'displayName');
|
|
27
|
+
const categoryValue = getPropertyValue(obj, 'category');
|
|
28
|
+
const typeValue = getPropertyValue(obj, 'type');
|
|
29
|
+
const rpcValue = getPropertyValue(obj, 'rpc');
|
|
30
|
+
const errorOutputValue = getPropertyValue(obj, 'errorOutput');
|
|
31
|
+
const { tags, description } = getCommonWireMetaData(obj, 'Forge node', nameValue, logger);
|
|
32
|
+
// Validate required fields
|
|
33
|
+
if (!nameValue) {
|
|
34
|
+
logger.critical(ErrorCode.MISSING_NAME, "Forge node is missing the required 'name' property.");
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (!displayNameValue) {
|
|
38
|
+
logger.critical(ErrorCode.MISSING_NAME, `Forge node '${nameValue}' is missing the required 'displayName' property.`);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (!categoryValue) {
|
|
42
|
+
logger.critical(ErrorCode.MISSING_NAME, `Forge node '${nameValue}' is missing the required 'category' property.`);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (!typeValue) {
|
|
46
|
+
logger.critical(ErrorCode.MISSING_NAME, `Forge node '${nameValue}' is missing the required 'type' property.`);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (!['trigger', 'action', 'end'].includes(typeValue)) {
|
|
50
|
+
logger.critical(ErrorCode.INVALID_VALUE, `Forge node '${nameValue}' has invalid type '${typeValue}'. Must be 'trigger', 'action', or 'end'.`);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (!rpcValue) {
|
|
54
|
+
logger.critical(ErrorCode.MISSING_NAME, `Forge node '${nameValue}' is missing the required 'rpc' property.`);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
// Get function metadata for input/output schemas
|
|
58
|
+
const fnMeta = state.functions.meta[rpcValue];
|
|
59
|
+
const inputSchemaName = fnMeta?.inputs?.[0] || null;
|
|
60
|
+
const outputSchemaName = fnMeta?.outputs?.[0] || null;
|
|
61
|
+
// Note: Category validation against forge.node.categories config
|
|
62
|
+
// is done at CLI build time, not during inspection
|
|
63
|
+
state.forgeNodes.files.add(node.getSourceFile().fileName);
|
|
64
|
+
state.forgeNodes.meta[nameValue] = {
|
|
65
|
+
name: nameValue,
|
|
66
|
+
displayName: displayNameValue,
|
|
67
|
+
category: categoryValue,
|
|
68
|
+
type: typeValue,
|
|
69
|
+
rpc: rpcValue,
|
|
70
|
+
description,
|
|
71
|
+
errorOutput: errorOutputValue ?? false,
|
|
72
|
+
inputSchemaName,
|
|
73
|
+
outputSchemaName,
|
|
74
|
+
tags,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
};
|
|
@@ -3,6 +3,8 @@ import { extractFunctionName } from '../utils/extract-function-name.js';
|
|
|
3
3
|
import { extractFunctionNode } from '../utils/extract-function-node.js';
|
|
4
4
|
import { getPropertyValue, getCommonWireMetaData, } from '../utils/get-property-value.js';
|
|
5
5
|
import { resolveMiddleware } from '../utils/middleware.js';
|
|
6
|
+
import { resolvePermissions } from '../utils/permissions.js';
|
|
7
|
+
import { ErrorCode } from '../error-codes.js';
|
|
6
8
|
const isValidVariableName = (name) => {
|
|
7
9
|
const regex = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
|
8
10
|
return regex.test(name);
|
|
@@ -109,7 +111,7 @@ const resolveUnionTypes = (checker, type) => {
|
|
|
109
111
|
// Check if it's a union type AND not part of an intersection
|
|
110
112
|
if (type.isUnion() && !(type.flags & ts.TypeFlags.Intersection)) {
|
|
111
113
|
for (const t of type.types) {
|
|
112
|
-
const name = nullifyTypes(checker.typeToString(t));
|
|
114
|
+
const name = nullifyTypes(checker.typeToString(t, undefined, ts.TypeFormatFlags.NoTruncation));
|
|
113
115
|
if (name) {
|
|
114
116
|
types.push(t);
|
|
115
117
|
names.push(name);
|
|
@@ -117,7 +119,7 @@ const resolveUnionTypes = (checker, type) => {
|
|
|
117
119
|
}
|
|
118
120
|
}
|
|
119
121
|
else {
|
|
120
|
-
const name = nullifyTypes(checker.typeToString(type));
|
|
122
|
+
const name = nullifyTypes(checker.typeToString(type, undefined, ts.TypeFormatFlags.NoTruncation));
|
|
121
123
|
if (name) {
|
|
122
124
|
types.push(type);
|
|
123
125
|
names.push(name);
|
|
@@ -231,6 +233,7 @@ export const addFunctions = (logger, node, checker, state) => {
|
|
|
231
233
|
if (args.length === 0)
|
|
232
234
|
return;
|
|
233
235
|
const { pikkuFuncName, name, explicitName, exportedName } = extractFunctionName(node, checker, state.rootDir);
|
|
236
|
+
let title;
|
|
234
237
|
let tags;
|
|
235
238
|
let summary;
|
|
236
239
|
let description;
|
|
@@ -241,16 +244,75 @@ export const addFunctions = (logger, node, checker, state) => {
|
|
|
241
244
|
// Extract the function node using shared utility
|
|
242
245
|
const firstArg = args[0];
|
|
243
246
|
const { funcNode: handlerNode, resolvedFunc, isDirectFunction, } = extractFunctionNode(firstArg, checker);
|
|
247
|
+
// Variables to hold zod schema references if provided
|
|
248
|
+
let inputZodSchemaRef = null;
|
|
249
|
+
let outputZodSchemaRef = null;
|
|
250
|
+
// Helper to resolve schema identifier to its actual source file
|
|
251
|
+
const resolveSchemaSourceFile = (identifier) => {
|
|
252
|
+
const symbol = checker.getSymbolAtLocation(identifier);
|
|
253
|
+
if (!symbol)
|
|
254
|
+
return null;
|
|
255
|
+
const decl = symbol.valueDeclaration || symbol.declarations?.[0];
|
|
256
|
+
if (!decl)
|
|
257
|
+
return null;
|
|
258
|
+
// If it's an import specifier, resolve the aliased symbol to get the actual source
|
|
259
|
+
if (ts.isImportSpecifier(decl)) {
|
|
260
|
+
const aliasedSymbol = checker.getAliasedSymbol(symbol);
|
|
261
|
+
if (aliasedSymbol) {
|
|
262
|
+
const aliasedDecl = aliasedSymbol.valueDeclaration || aliasedSymbol.declarations?.[0];
|
|
263
|
+
if (aliasedDecl) {
|
|
264
|
+
return {
|
|
265
|
+
variableName: identifier.text,
|
|
266
|
+
sourceFile: aliasedDecl.getSourceFile().fileName,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
// Not an import - use the current source file
|
|
272
|
+
return {
|
|
273
|
+
variableName: identifier.text,
|
|
274
|
+
sourceFile: decl.getSourceFile().fileName,
|
|
275
|
+
};
|
|
276
|
+
};
|
|
244
277
|
// Extract config properties if using object form
|
|
245
278
|
if (ts.isObjectLiteralExpression(firstArg)) {
|
|
246
279
|
objectNode = firstArg;
|
|
247
280
|
const metadata = getCommonWireMetaData(firstArg, 'Function', name, logger);
|
|
281
|
+
title = metadata.title;
|
|
248
282
|
tags = metadata.tags;
|
|
249
283
|
summary = metadata.summary;
|
|
250
284
|
description = metadata.description;
|
|
251
285
|
errors = metadata.errors;
|
|
252
286
|
expose = getPropertyValue(firstArg, 'expose');
|
|
253
287
|
internal = getPropertyValue(firstArg, 'internal');
|
|
288
|
+
// Extract zod schema variable names from input/output properties
|
|
289
|
+
for (const prop of firstArg.properties) {
|
|
290
|
+
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
|
|
291
|
+
const propName = prop.name.text;
|
|
292
|
+
if (propName === 'input' || propName === 'output') {
|
|
293
|
+
if (ts.isIdentifier(prop.initializer)) {
|
|
294
|
+
// Good - it's a variable reference, resolve its actual source file
|
|
295
|
+
const ref = resolveSchemaSourceFile(prop.initializer);
|
|
296
|
+
if (ref) {
|
|
297
|
+
if (propName === 'input') {
|
|
298
|
+
inputZodSchemaRef = ref;
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
outputZodSchemaRef = ref;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
else if (ts.isCallExpression(prop.initializer)) {
|
|
306
|
+
// Bad - it's an inline expression
|
|
307
|
+
const schemaName = `${name.charAt(0).toUpperCase() + name.slice(1)}${propName.charAt(0).toUpperCase() + propName.slice(1)}`;
|
|
308
|
+
logger.critical(ErrorCode.INLINE_ZOD_SCHEMA, `Inline Zod schemas are not supported for '${propName}' in '${name}'.\n` +
|
|
309
|
+
` Extract to an exported variable:\n` +
|
|
310
|
+
` export const ${schemaName} = ${prop.initializer.getText()}\n` +
|
|
311
|
+
` Then use: ${propName}: ${schemaName}`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
254
316
|
}
|
|
255
317
|
// Pick the handler: use resolvedFunc when it exists and is a function, otherwise fall back to handlerNode
|
|
256
318
|
const handler = resolvedFunc &&
|
|
@@ -314,16 +376,41 @@ export const addFunctions = (logger, node, checker, state) => {
|
|
|
314
376
|
const genericTypes = (typeArguments ?? [])
|
|
315
377
|
.map((tn) => checker.getTypeFromTypeNode(tn))
|
|
316
378
|
.map((t) => unwrapPromise(checker, t));
|
|
379
|
+
const capitalizedName = name.charAt(0).toUpperCase() + name.slice(1);
|
|
317
380
|
// --- Input Extraction ---
|
|
318
|
-
let
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
381
|
+
let inputNames = [];
|
|
382
|
+
let inputTypes = [];
|
|
383
|
+
if (inputZodSchemaRef) {
|
|
384
|
+
const schemaName = `${capitalizedName}Input`;
|
|
385
|
+
inputNames = [schemaName];
|
|
386
|
+
state.zodLookup.set(schemaName, inputZodSchemaRef);
|
|
387
|
+
state.functions.typesMap.addCustomType(schemaName, 'unknown', []);
|
|
388
|
+
}
|
|
389
|
+
else if (genericTypes.length >= 1 && genericTypes[0]) {
|
|
390
|
+
// Fall back to extracting from generic type arguments
|
|
391
|
+
const result = getNamesAndTypes(checker, state.functions.typesMap, 'Input', name, genericTypes[0]);
|
|
392
|
+
inputNames = result.names;
|
|
393
|
+
inputTypes = result.types;
|
|
394
|
+
}
|
|
395
|
+
else {
|
|
396
|
+
// Fall back to extracting from the function's second parameter type
|
|
397
|
+
const secondParam = handler.parameters[1];
|
|
398
|
+
if (secondParam) {
|
|
399
|
+
const paramType = checker.getTypeAtLocation(secondParam);
|
|
400
|
+
const result = getNamesAndTypes(checker, state.functions.typesMap, 'Input', pikkuFuncName, paramType);
|
|
401
|
+
inputNames = result.names;
|
|
402
|
+
inputTypes = result.types;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
324
405
|
// --- Output Extraction ---
|
|
325
406
|
let outputNames = [];
|
|
326
|
-
if (
|
|
407
|
+
if (outputZodSchemaRef) {
|
|
408
|
+
const schemaName = `${capitalizedName}Output`;
|
|
409
|
+
outputNames = [schemaName];
|
|
410
|
+
state.zodLookup.set(schemaName, outputZodSchemaRef);
|
|
411
|
+
state.functions.typesMap.addCustomType(schemaName, 'unknown', []);
|
|
412
|
+
}
|
|
413
|
+
else if (genericTypes.length >= 2) {
|
|
327
414
|
outputNames = getNamesAndTypes(checker, state.functions.typesMap, 'Output', name, genericTypes[1]).names;
|
|
328
415
|
}
|
|
329
416
|
else {
|
|
@@ -345,6 +432,10 @@ export const addFunctions = (logger, node, checker, state) => {
|
|
|
345
432
|
const middleware = objectNode
|
|
346
433
|
? resolveMiddleware(state, objectNode, tags, checker)
|
|
347
434
|
: undefined;
|
|
435
|
+
// --- resolve permissions ---
|
|
436
|
+
const permissions = objectNode
|
|
437
|
+
? resolvePermissions(state, objectNode, tags, checker)
|
|
438
|
+
: undefined;
|
|
348
439
|
state.functions.meta[pikkuFuncName] = {
|
|
349
440
|
pikkuFuncName,
|
|
350
441
|
name,
|
|
@@ -356,11 +447,13 @@ export const addFunctions = (logger, node, checker, state) => {
|
|
|
356
447
|
outputs: outputNames.filter((n) => n !== 'void') ?? null,
|
|
357
448
|
expose: expose || undefined,
|
|
358
449
|
internal: internal || undefined,
|
|
450
|
+
title,
|
|
359
451
|
tags: tags || undefined,
|
|
360
452
|
summary,
|
|
361
453
|
description,
|
|
362
454
|
errors,
|
|
363
455
|
middleware,
|
|
456
|
+
permissions,
|
|
364
457
|
isDirectFunction,
|
|
365
458
|
};
|
|
366
459
|
// Store function file location for wiring generation
|
|
@@ -47,8 +47,30 @@ export const addHTTPRoute = (logger, node, checker, state, options) => {
|
|
|
47
47
|
const keys = pathToRegexp(route).keys;
|
|
48
48
|
const params = keys.filter((k) => k.type === 'param').map((k) => k.name);
|
|
49
49
|
const method = getPropertyValue(obj, 'method')?.toLowerCase() || 'get';
|
|
50
|
-
const { tags, summary, description, errors } = getCommonWireMetaData(obj, 'HTTP route', route, logger);
|
|
50
|
+
const { title, tags, summary, description, errors } = getCommonWireMetaData(obj, 'HTTP route', route, logger);
|
|
51
51
|
const query = getPropertyValue(obj, 'query') || [];
|
|
52
|
+
// Check if this is a workflow trigger (workflow: true)
|
|
53
|
+
const isWorkflowTrigger = getPropertyValue(obj, 'workflow') === true;
|
|
54
|
+
if (isWorkflowTrigger) {
|
|
55
|
+
// Workflow triggers don't need func - they're handled by workflow-utils
|
|
56
|
+
// Just record the route for HTTP meta but skip function processing
|
|
57
|
+
state.http.files.add(node.getSourceFile().fileName);
|
|
58
|
+
state.http.meta[method][route] = {
|
|
59
|
+
pikkuFuncName: '', // No function - workflow handles it
|
|
60
|
+
route,
|
|
61
|
+
method: method,
|
|
62
|
+
params: params.length > 0 ? params : undefined,
|
|
63
|
+
query: query.length > 0 ? query : undefined,
|
|
64
|
+
inputTypes: undefined,
|
|
65
|
+
title,
|
|
66
|
+
summary,
|
|
67
|
+
description,
|
|
68
|
+
errors,
|
|
69
|
+
tags,
|
|
70
|
+
workflow: true,
|
|
71
|
+
};
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
52
74
|
// --- find the referenced function name first for filtering ---
|
|
53
75
|
const funcInitializer = getPropertyAssignmentInitializer(obj, 'func', true, checker);
|
|
54
76
|
if (!funcInitializer) {
|
|
@@ -84,6 +106,7 @@ export const addHTTPRoute = (logger, node, checker, state, options) => {
|
|
|
84
106
|
params: params.length > 0 ? params : undefined,
|
|
85
107
|
query: query.length > 0 ? query : undefined,
|
|
86
108
|
inputTypes,
|
|
109
|
+
title,
|
|
87
110
|
summary,
|
|
88
111
|
description,
|
|
89
112
|
errors,
|
|
@@ -2,5 +2,8 @@ import * as ts from 'typescript';
|
|
|
2
2
|
import { InspectorState, InspectorLogger } from '../types.js';
|
|
3
3
|
/**
|
|
4
4
|
* Scan for rpc.invoke() calls to track which functions are actually being invoked
|
|
5
|
+
* Also detects external package usage via:
|
|
6
|
+
* - Namespaced calls: rpc.invoke('namespace:function')
|
|
7
|
+
* - External helper: external('namespace:function')
|
|
5
8
|
*/
|
|
6
9
|
export declare function addRPCInvocations(node: ts.Node, state: InspectorState, logger: InspectorLogger): void;
|
|
@@ -1,35 +1,61 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
|
+
/**
|
|
3
|
+
* Helper to extract namespace from a namespaced function reference like 'ext:hello'
|
|
4
|
+
*/
|
|
5
|
+
function extractNamespace(functionRef) {
|
|
6
|
+
const colonIndex = functionRef.indexOf(':');
|
|
7
|
+
if (colonIndex !== -1) {
|
|
8
|
+
return functionRef.substring(0, colonIndex);
|
|
9
|
+
}
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
2
12
|
/**
|
|
3
13
|
* Scan for rpc.invoke() calls to track which functions are actually being invoked
|
|
14
|
+
* Also detects external package usage via:
|
|
15
|
+
* - Namespaced calls: rpc.invoke('namespace:function')
|
|
16
|
+
* - External helper: external('namespace:function')
|
|
4
17
|
*/
|
|
5
18
|
export function addRPCInvocations(node, state, logger) {
|
|
6
|
-
// Look for
|
|
7
|
-
if (ts.
|
|
8
|
-
const { expression,
|
|
9
|
-
// Check
|
|
10
|
-
if (
|
|
11
|
-
|
|
12
|
-
if (
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
19
|
+
// Look for call expressions: external('ext:hello') or rpc.invoke('...')
|
|
20
|
+
if (ts.isCallExpression(node)) {
|
|
21
|
+
const { expression, arguments: args } = node;
|
|
22
|
+
// Check for external('namespace:function') calls
|
|
23
|
+
if (ts.isIdentifier(expression) && expression.text === 'external') {
|
|
24
|
+
const [firstArg] = args;
|
|
25
|
+
if (firstArg && ts.isStringLiteral(firstArg)) {
|
|
26
|
+
const functionRef = firstArg.text;
|
|
27
|
+
logger.debug(`• Found external() call: ${functionRef}`);
|
|
28
|
+
state.rpc.invokedFunctions.add(functionRef);
|
|
29
|
+
const namespace = extractNamespace(functionRef);
|
|
30
|
+
if (namespace) {
|
|
31
|
+
logger.debug(` → External package detected: ${namespace}`);
|
|
32
|
+
state.rpc.usedExternalPackages.add(namespace);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// Check for rpc.invoke('...') calls
|
|
37
|
+
if (ts.isPropertyAccessExpression(expression) &&
|
|
38
|
+
expression.name.text === 'invoke' &&
|
|
39
|
+
ts.isIdentifier(expression.expression) &&
|
|
40
|
+
expression.expression.text === 'rpc') {
|
|
41
|
+
const [firstArg] = args;
|
|
42
|
+
if (firstArg) {
|
|
43
|
+
if (ts.isStringLiteral(firstArg)) {
|
|
44
|
+
const functionRef = firstArg.text;
|
|
45
|
+
logger.debug(`• Found RPC invocation: ${functionRef}`);
|
|
46
|
+
state.rpc.invokedFunctions.add(functionRef);
|
|
47
|
+
const namespace = extractNamespace(functionRef);
|
|
48
|
+
if (namespace) {
|
|
49
|
+
logger.debug(` → External package detected: ${namespace}`);
|
|
50
|
+
state.rpc.usedExternalPackages.add(namespace);
|
|
31
51
|
}
|
|
32
52
|
}
|
|
53
|
+
// Handle template literals like `function-${name}`
|
|
54
|
+
else if (ts.isTemplateExpression(firstArg) ||
|
|
55
|
+
ts.isNoSubstitutionTemplateLiteral(firstArg)) {
|
|
56
|
+
logger.warn(`• Found dynamic RPC invocation: ${firstArg.getText()}`);
|
|
57
|
+
logger.warn(`\tYou can only use string literals for RPC function names, with ' or " and not \``);
|
|
58
|
+
}
|
|
33
59
|
}
|
|
34
60
|
}
|
|
35
61
|
}
|