@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.
Files changed (68) hide show
  1. package/CHANGELOG.md +16 -1
  2. package/dist/add/add-forge-credential.d.ts +8 -0
  3. package/dist/add/add-forge-credential.js +77 -0
  4. package/dist/add/add-forge-node.d.ts +7 -0
  5. package/dist/add/add-forge-node.js +77 -0
  6. package/dist/add/add-functions.js +102 -9
  7. package/dist/add/add-http-route.js +24 -1
  8. package/dist/add/add-rpc-invocations.d.ts +3 -0
  9. package/dist/add/add-rpc-invocations.js +51 -25
  10. package/dist/add/add-workflow-graph.d.ts +6 -0
  11. package/dist/add/add-workflow-graph.js +659 -0
  12. package/dist/add/add-workflow.js +118 -22
  13. package/dist/error-codes.d.ts +3 -1
  14. package/dist/error-codes.js +3 -1
  15. package/dist/index.d.ts +3 -0
  16. package/dist/index.js +2 -0
  17. package/dist/inspector.js +19 -3
  18. package/dist/types.d.ts +26 -0
  19. package/dist/utils/extract-function-name.js +7 -7
  20. package/dist/utils/get-property-value.d.ts +2 -1
  21. package/dist/utils/get-property-value.js +6 -2
  22. package/dist/utils/serialize-inspector-state.d.ts +24 -1
  23. package/dist/utils/serialize-inspector-state.js +24 -0
  24. package/dist/utils/workflow/dsl/deserialize-dsl-workflow.d.ts +24 -0
  25. package/dist/utils/workflow/dsl/deserialize-dsl-workflow.js +898 -0
  26. package/dist/{workflow/extract-simple-workflow.d.ts → utils/workflow/dsl/extract-dsl-workflow.d.ts} +4 -2
  27. package/dist/{workflow/extract-simple-workflow.js → utils/workflow/dsl/extract-dsl-workflow.js} +549 -68
  28. package/dist/utils/workflow/dsl/index.d.ts +7 -0
  29. package/dist/utils/workflow/dsl/index.js +7 -0
  30. package/dist/{workflow → utils/workflow/dsl}/patterns.d.ts +21 -0
  31. package/dist/{workflow → utils/workflow/dsl}/patterns.js +90 -10
  32. package/dist/{workflow → utils/workflow/dsl}/validation.d.ts +2 -0
  33. package/dist/{workflow → utils/workflow/dsl}/validation.js +25 -7
  34. package/dist/utils/workflow/graph/convert-dsl-to-graph.d.ts +13 -0
  35. package/dist/utils/workflow/graph/convert-dsl-to-graph.js +316 -0
  36. package/dist/utils/workflow/graph/index.d.ts +6 -0
  37. package/dist/utils/workflow/graph/index.js +6 -0
  38. package/dist/utils/workflow/graph/serialize-workflow-graph.d.ts +43 -0
  39. package/dist/utils/workflow/graph/serialize-workflow-graph.js +152 -0
  40. package/dist/utils/workflow/graph/workflow-graph.types.d.ts +229 -0
  41. package/dist/utils/workflow/graph/workflow-graph.types.js +38 -0
  42. package/dist/visit.js +6 -0
  43. package/package.json +14 -2
  44. package/src/add/add-forge-credential.ts +119 -0
  45. package/src/add/add-forge-node.ts +132 -0
  46. package/src/add/add-functions.ts +129 -15
  47. package/src/add/add-http-route.ts +25 -1
  48. package/src/add/add-rpc-invocations.ts +61 -31
  49. package/src/add/add-workflow-graph.ts +864 -0
  50. package/src/add/add-workflow.ts +112 -26
  51. package/src/error-codes.ts +3 -1
  52. package/src/index.ts +10 -0
  53. package/src/inspector.ts +20 -4
  54. package/src/types.ts +25 -1
  55. package/src/utils/extract-function-name.ts +7 -7
  56. package/src/utils/get-property-value.ts +9 -2
  57. package/src/utils/serialize-inspector-state.ts +39 -1
  58. package/src/utils/workflow/dsl/deserialize-dsl-workflow.ts +1180 -0
  59. package/src/{workflow/extract-simple-workflow.ts → utils/workflow/dsl/extract-dsl-workflow.ts} +654 -81
  60. package/src/utils/workflow/dsl/index.ts +11 -0
  61. package/src/{workflow → utils/workflow/dsl}/patterns.ts +108 -11
  62. package/src/{workflow → utils/workflow/dsl}/validation.ts +34 -7
  63. package/src/utils/workflow/graph/convert-dsl-to-graph.ts +415 -0
  64. package/src/utils/workflow/graph/index.ts +6 -0
  65. package/src/utils/workflow/graph/serialize-workflow-graph.ts +223 -0
  66. package/src/utils/workflow/graph/workflow-graph.types.ts +280 -0
  67. package/src/visit.ts +6 -0
  68. package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md CHANGED
@@ -1,4 +1,19 @@
1
- ## 0.11.0
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,7 @@
1
+ import { AddWiring } from '../types.js';
2
+ /**
3
+ * Inspector for wireForgeNode calls.
4
+ * Extracts metadata for Forge workflow builder nodes.
5
+ * Note: wireForgeNode is metadata-only - no runtime behavior.
6
+ */
7
+ export declare const addForgeNode: AddWiring;
@@ -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 { names: inputNames, types: inputTypes } = getNamesAndTypes(checker, state.functions.typesMap, 'Input', name, genericTypes[0]);
319
- // if (inputTypes.length === 0) {
320
- // logger.debug(
321
- // `\x1b[31m• Unknown input type for '${name}', assuming void.\x1b[0m`
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 (genericTypes.length >= 2) {
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 property access expressions: rpc.invoke
7
- if (ts.isPropertyAccessExpression(node)) {
8
- const { expression, name } = node;
9
- // Check if this is accessing 'invoke' property
10
- if (name.text === 'invoke') {
11
- // Check if the object is 'rpc' (or a variable containing rpc)
12
- if (ts.isIdentifier(expression) && expression.text === 'rpc') {
13
- // This is rpc.invoke - now we need to find the parent call expression
14
- const parent = node.parent;
15
- if (ts.isCallExpression(parent) && parent.expression === node) {
16
- // This is rpc.invoke('function-name')
17
- const [firstArg] = parent.arguments;
18
- if (firstArg) {
19
- // Extract the function name from string literal
20
- if (ts.isStringLiteral(firstArg)) {
21
- const functionName = firstArg.text;
22
- logger.debug(`• Found RPC invocation: ${functionName}`);
23
- state.rpc.invokedFunctions.add(functionName);
24
- }
25
- // Handle template literals like `function-${name}`
26
- else if (ts.isTemplateExpression(firstArg) ||
27
- ts.isNoSubstitutionTemplateLiteral(firstArg)) {
28
- logger.warn(`• Found dynamic RPC invocation: ${firstArg.getText()}`);
29
- logger.warn(`\tYou can only use string literals for RPC function names, with ' or " and not \``);
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
  }
@@ -0,0 +1,6 @@
1
+ import type { AddWiring } from '../types.js';
2
+ /**
3
+ * Inspector for wireWorkflow() calls with graph definitions
4
+ * Detects: wireWorkflow({ wires: {...}, graph: pikkuWorkflowGraphResult })
5
+ */
6
+ export declare const addWorkflowGraph: AddWiring;