@pikku/inspector 0.11.2 → 0.12.0

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 (182) hide show
  1. package/CHANGELOG.md +11 -1
  2. package/OPTIMIZATION-PLAN.md +195 -0
  3. package/dist/add/add-ai-agent.d.ts +2 -0
  4. package/dist/add/add-ai-agent.js +314 -0
  5. package/dist/add/add-channel.js +69 -61
  6. package/dist/add/add-cli.js +36 -18
  7. package/dist/add/add-file-with-factory.js +2 -0
  8. package/dist/add/add-functions.js +250 -75
  9. package/dist/add/add-http-route.d.ts +19 -10
  10. package/dist/add/add-http-route.js +152 -66
  11. package/dist/add/add-http-routes.d.ts +5 -0
  12. package/dist/add/add-http-routes.js +159 -0
  13. package/dist/add/add-keyed-wiring.d.ts +12 -0
  14. package/dist/add/add-keyed-wiring.js +97 -0
  15. package/dist/add/add-mcp-prompt.js +14 -9
  16. package/dist/add/add-mcp-resource.js +14 -9
  17. package/dist/add/add-middleware.d.ts +1 -4
  18. package/dist/add/add-middleware.js +364 -79
  19. package/dist/add/add-permission.d.ts +1 -1
  20. package/dist/add/add-permission.js +152 -40
  21. package/dist/add/add-queue-worker.js +18 -12
  22. package/dist/add/add-rpc-invocations.js +14 -0
  23. package/dist/add/add-schedule.js +11 -5
  24. package/dist/add/add-secret.d.ts +3 -0
  25. package/dist/add/add-secret.js +82 -0
  26. package/dist/add/add-trigger.d.ts +2 -0
  27. package/dist/add/add-trigger.js +87 -0
  28. package/dist/add/add-variable.d.ts +1 -0
  29. package/dist/add/add-variable.js +8 -0
  30. package/dist/add/add-workflow-graph.d.ts +3 -2
  31. package/dist/add/add-workflow-graph.js +143 -406
  32. package/dist/add/add-workflow.js +6 -4
  33. package/dist/error-codes.d.ts +14 -1
  34. package/dist/error-codes.js +19 -1
  35. package/dist/index.d.ts +9 -8
  36. package/dist/index.js +5 -4
  37. package/dist/inspector.d.ts +1 -1
  38. package/dist/inspector.js +91 -14
  39. package/dist/schema-generator.d.ts +1 -0
  40. package/dist/schema-generator.js +1 -0
  41. package/dist/types-map.js +10 -1
  42. package/dist/types.d.ts +163 -39
  43. package/dist/utils/compute-required-schemas.d.ts +4 -0
  44. package/dist/utils/compute-required-schemas.js +41 -0
  45. package/dist/utils/contract-hashes.d.ts +35 -0
  46. package/dist/utils/contract-hashes.js +202 -0
  47. package/dist/utils/custom-types-generator.d.ts +9 -0
  48. package/dist/utils/custom-types-generator.js +71 -0
  49. package/dist/utils/detect-schema-vendor.d.ts +22 -0
  50. package/dist/utils/detect-schema-vendor.js +76 -0
  51. package/dist/utils/ensure-function-metadata.d.ts +5 -2
  52. package/dist/utils/ensure-function-metadata.js +220 -6
  53. package/dist/utils/extract-function-name.d.ts +5 -16
  54. package/dist/utils/extract-function-name.js +86 -291
  55. package/dist/utils/extract-services.d.ts +2 -1
  56. package/dist/utils/extract-services.js +25 -1
  57. package/dist/utils/filter-inspector-state.js +107 -23
  58. package/dist/utils/get-property-value.d.ts +6 -1
  59. package/dist/utils/get-property-value.js +28 -3
  60. package/dist/utils/hash.d.ts +2 -0
  61. package/dist/utils/hash.js +23 -0
  62. package/dist/utils/middleware.d.ts +7 -30
  63. package/dist/utils/middleware.js +80 -66
  64. package/dist/utils/permissions.d.ts +2 -2
  65. package/dist/utils/permissions.js +10 -10
  66. package/dist/utils/post-process.d.ts +9 -10
  67. package/dist/utils/post-process.js +231 -24
  68. package/dist/utils/resolve-external-package.d.ts +12 -0
  69. package/dist/utils/resolve-external-package.js +34 -0
  70. package/dist/utils/resolve-function-types.d.ts +6 -0
  71. package/dist/utils/resolve-function-types.js +29 -0
  72. package/dist/utils/resolve-identifier.d.ts +10 -0
  73. package/dist/utils/resolve-identifier.js +36 -0
  74. package/dist/utils/resolve-versions.d.ts +2 -0
  75. package/dist/utils/resolve-versions.js +78 -0
  76. package/dist/utils/schema-generator.d.ts +9 -0
  77. package/dist/utils/schema-generator.js +209 -0
  78. package/dist/utils/serialize-inspector-state.d.ts +59 -22
  79. package/dist/utils/serialize-inspector-state.js +92 -20
  80. package/dist/utils/serialize-mcp-json.d.ts +2 -0
  81. package/dist/utils/serialize-mcp-json.js +99 -0
  82. package/dist/utils/serialize-middleware-groups-meta.d.ts +12 -0
  83. package/dist/utils/serialize-middleware-groups-meta.js +28 -0
  84. package/dist/utils/serialize-openapi-json.d.ts +85 -0
  85. package/dist/utils/serialize-openapi-json.js +151 -0
  86. package/dist/utils/serialize-permissions-groups-meta.d.ts +6 -0
  87. package/dist/utils/serialize-permissions-groups-meta.js +31 -0
  88. package/dist/utils/workflow/dsl/deserialize-dsl-workflow.js +34 -102
  89. package/dist/utils/workflow/dsl/extract-dsl-workflow.js +23 -4
  90. package/dist/utils/workflow/graph/convert-dsl-to-graph.js +12 -10
  91. package/dist/utils/workflow/graph/finalize-workflow-wires.d.ts +3 -0
  92. package/dist/utils/workflow/graph/finalize-workflow-wires.js +276 -0
  93. package/dist/utils/workflow/graph/finalize-workflows.d.ts +2 -0
  94. package/dist/utils/workflow/graph/finalize-workflows.js +75 -0
  95. package/dist/utils/workflow/graph/index.d.ts +2 -0
  96. package/dist/utils/workflow/graph/index.js +2 -0
  97. package/dist/utils/workflow/graph/serialize-workflow-graph.d.ts +0 -8
  98. package/dist/utils/workflow/graph/serialize-workflow-graph.js +1 -3
  99. package/dist/utils/workflow/graph/workflow-graph.types.d.ts +53 -79
  100. package/dist/utils/workflow/graph/workflow-graph.types.js +1 -1
  101. package/dist/visit.js +11 -6
  102. package/package.json +14 -4
  103. package/src/add/add-ai-agent.ts +468 -0
  104. package/src/add/add-channel.ts +82 -79
  105. package/src/add/add-cli.ts +49 -20
  106. package/src/add/add-file-with-factory.ts +2 -0
  107. package/src/add/add-functions.ts +330 -86
  108. package/src/add/add-http-route.ts +245 -88
  109. package/src/add/add-http-routes.ts +228 -0
  110. package/src/add/add-keyed-wiring.ts +151 -0
  111. package/src/add/add-mcp-prompt.ts +26 -15
  112. package/src/add/add-mcp-resource.ts +27 -15
  113. package/src/add/add-middleware.ts +482 -80
  114. package/src/add/add-permission.ts +199 -40
  115. package/src/add/add-queue-worker.ts +24 -19
  116. package/src/add/add-rpc-invocations.ts +17 -0
  117. package/src/add/add-schedule.ts +16 -11
  118. package/src/add/add-secret.ts +140 -0
  119. package/src/add/add-trigger.ts +154 -0
  120. package/src/add/add-variable.ts +9 -0
  121. package/src/add/add-workflow-graph.ts +180 -522
  122. package/src/add/add-workflow.ts +5 -4
  123. package/src/error-codes.ts +24 -1
  124. package/src/index.ts +22 -13
  125. package/src/inspector.ts +129 -17
  126. package/src/schema-generator.ts +1 -0
  127. package/src/types-map.ts +12 -1
  128. package/src/types.ts +175 -58
  129. package/src/utils/compute-required-schemas.ts +49 -0
  130. package/src/utils/contract-hashes.test.ts +528 -0
  131. package/src/utils/contract-hashes.ts +290 -0
  132. package/src/utils/custom-types-generator.ts +88 -0
  133. package/src/utils/detect-schema-vendor.ts +90 -0
  134. package/src/utils/ensure-function-metadata.ts +324 -7
  135. package/src/utils/extract-function-name.ts +101 -351
  136. package/src/utils/extract-services.ts +35 -2
  137. package/src/utils/filter-inspector-state.test.ts +34 -20
  138. package/src/utils/filter-inspector-state.ts +140 -31
  139. package/src/utils/get-property-value.ts +42 -4
  140. package/src/utils/hash.ts +26 -0
  141. package/src/utils/middleware.test.ts +204 -0
  142. package/src/utils/middleware.ts +129 -67
  143. package/src/utils/permissions.test.ts +35 -12
  144. package/src/utils/permissions.ts +10 -10
  145. package/src/utils/post-process.ts +283 -43
  146. package/src/utils/resolve-external-package.ts +42 -0
  147. package/src/utils/resolve-function-types.ts +42 -0
  148. package/src/utils/resolve-identifier.ts +46 -0
  149. package/src/utils/resolve-versions.test.ts +249 -0
  150. package/src/utils/resolve-versions.ts +105 -0
  151. package/src/utils/schema-generator.ts +329 -0
  152. package/src/utils/serialize-inspector-state.ts +163 -40
  153. package/src/utils/serialize-mcp-json.ts +145 -0
  154. package/src/utils/serialize-middleware-groups-meta.ts +33 -0
  155. package/src/utils/serialize-openapi-json.ts +277 -0
  156. package/src/utils/serialize-permissions-groups-meta.ts +35 -0
  157. package/src/utils/test-data/inspector-state.json +69 -66
  158. package/src/utils/workflow/dsl/deserialize-dsl-workflow.ts +43 -119
  159. package/src/utils/workflow/dsl/extract-dsl-workflow.ts +24 -4
  160. package/src/utils/workflow/graph/convert-dsl-to-graph.ts +17 -10
  161. package/src/utils/workflow/graph/finalize-workflow-wires.ts +310 -0
  162. package/src/utils/workflow/graph/finalize-workflows.ts +100 -0
  163. package/src/utils/workflow/graph/index.ts +5 -0
  164. package/src/utils/workflow/graph/serialize-workflow-graph.ts +1 -8
  165. package/src/utils/workflow/graph/workflow-graph.types.ts +29 -78
  166. package/src/visit.ts +12 -6
  167. package/tsconfig.tsbuildinfo +1 -1
  168. package/dist/add/add-forge-credential.d.ts +0 -8
  169. package/dist/add/add-forge-credential.js +0 -77
  170. package/dist/add/add-forge-node.d.ts +0 -7
  171. package/dist/add/add-forge-node.js +0 -77
  172. package/dist/add/add-mcp-tool.d.ts +0 -2
  173. package/dist/add/add-mcp-tool.js +0 -81
  174. package/dist/utils/extract-service-metadata.d.ts +0 -19
  175. package/dist/utils/extract-service-metadata.js +0 -244
  176. package/dist/utils/write-service-metadata.d.ts +0 -13
  177. package/dist/utils/write-service-metadata.js +0 -37
  178. package/src/add/add-forge-credential.ts +0 -119
  179. package/src/add/add-forge-node.ts +0 -132
  180. package/src/add/add-mcp-tool.ts +0 -141
  181. package/src/utils/extract-service-metadata.ts +0 -353
  182. package/src/utils/write-service-metadata.ts +0 -51
@@ -2,10 +2,11 @@ import * as ts from 'typescript';
2
2
  import { ErrorCode } from '../error-codes.js';
3
3
  import { getPropertyValue, getCommonWireMetaData, } from '../utils/get-property-value.js';
4
4
  import { pathToRegexp } from 'path-to-regexp';
5
- import { extractFunctionName } from '../utils/extract-function-name.js';
5
+ import { extractFunctionName, makeContextBasedId, } from '../utils/extract-function-name.js';
6
6
  import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
7
- import { resolveMiddleware } from '../utils/middleware.js';
7
+ import { resolveMiddleware, resolveChannelMiddleware, } from '../utils/middleware.js';
8
8
  import { extractWireNames } from '../utils/post-process.js';
9
+ import { resolveIdentifier } from '../utils/resolve-identifier.js';
9
10
  /**
10
11
  * Safely get the "initializer" expression of a property-like AST node:
11
12
  * - for `foo: expr`, returns `expr`
@@ -44,25 +45,25 @@ function getHandlerNameFromExpression(expr, checker, rootDir) {
44
45
  ts.isArrowFunction(decl.initializer) ||
45
46
  ts.isFunctionExpression(decl.initializer)) {
46
47
  // Extract function name from the declaration's initializer
47
- const { pikkuFuncName } = extractFunctionName(decl.initializer, checker, rootDir);
48
- return pikkuFuncName;
48
+ const { pikkuFuncId } = extractFunctionName(decl.initializer, checker, rootDir);
49
+ return pikkuFuncId;
49
50
  }
50
51
  }
51
52
  // For function declarations, use directly
52
53
  else if (ts.isFunctionDeclaration(decl)) {
53
- const { pikkuFuncName } = extractFunctionName(decl, checker, rootDir);
54
- return pikkuFuncName;
54
+ const { pikkuFuncId } = extractFunctionName(decl, checker, rootDir);
55
+ return pikkuFuncId;
55
56
  }
56
57
  }
57
58
  }
58
59
  // Fallback: try to extract directly from the identifier
59
- const { pikkuFuncName } = extractFunctionName(expr, checker, rootDir);
60
- return pikkuFuncName;
60
+ const { pikkuFuncId } = extractFunctionName(expr, checker, rootDir);
61
+ return pikkuFuncId;
61
62
  }
62
63
  // Handle call expressions
63
64
  if (ts.isCallExpression(expr)) {
64
- const { pikkuFuncName } = extractFunctionName(expr, checker, rootDir);
65
- return pikkuFuncName;
65
+ const { pikkuFuncId } = extractFunctionName(expr, checker, rootDir);
66
+ return pikkuFuncId;
66
67
  }
67
68
  // Handle object literals with 'func' property
68
69
  if (ts.isObjectLiteralExpression(expr)) {
@@ -85,8 +86,19 @@ export function addMessagesRoutes(logger, obj, state, checker) {
85
86
  if (!onMsgRouteProp || !ts.isObjectLiteralExpression(onMsgRouteProp))
86
87
  return result;
87
88
  for (const chanElem of onMsgRouteProp.properties) {
88
- const chanInit = getInitializerOf(chanElem);
89
- if (!chanInit || !ts.isObjectLiteralExpression(chanInit))
89
+ let chanInit = getInitializerOf(chanElem);
90
+ if (!chanInit)
91
+ continue;
92
+ // If the value is an identifier, resolve it (handles defineChannelRoutes)
93
+ if (ts.isIdentifier(chanInit)) {
94
+ const resolved = resolveIdentifier(chanInit, checker, [
95
+ 'defineChannelRoutes',
96
+ ]);
97
+ if (resolved && ts.isObjectLiteralExpression(resolved)) {
98
+ chanInit = resolved;
99
+ }
100
+ }
101
+ if (!ts.isObjectLiteralExpression(chanInit))
90
102
  continue;
91
103
  const channelKey = chanElem.name.getText();
92
104
  result[channelKey] = {};
@@ -136,8 +148,8 @@ export function addMessagesRoutes(logger, obj, state, checker) {
136
148
  if (ts.isArrowFunction(importDecl.initializer) ||
137
149
  ts.isFunctionExpression(importDecl.initializer) ||
138
150
  ts.isCallExpression(importDecl.initializer)) {
139
- const { pikkuFuncName } = extractFunctionName(importDecl.initializer, checker, state.rootDir);
140
- const handlerName = pikkuFuncName;
151
+ const { pikkuFuncId } = extractFunctionName(importDecl.initializer, checker, state.rootDir);
152
+ const handlerName = pikkuFuncId;
141
153
  // Look up in the registry
142
154
  const fnMeta = state.functions.meta[handlerName];
143
155
  if (fnMeta) {
@@ -149,7 +161,7 @@ export function addMessagesRoutes(logger, obj, state, checker) {
149
161
  ? resolveMiddleware(state, init, routeTags, checker)
150
162
  : undefined;
151
163
  result[channelKey][routeKey] = {
152
- pikkuFuncName: handlerName,
164
+ pikkuFuncId: handlerName,
153
165
  middleware: routeMiddleware,
154
166
  };
155
167
  continue;
@@ -158,8 +170,8 @@ export function addMessagesRoutes(logger, obj, state, checker) {
158
170
  }
159
171
  else if (ts.isFunctionDeclaration(importDecl)) {
160
172
  // Extract from the function declaration
161
- const { pikkuFuncName } = extractFunctionName(importDecl, checker, state.rootDir);
162
- const handlerName = pikkuFuncName;
173
+ const { pikkuFuncId } = extractFunctionName(importDecl, checker, state.rootDir);
174
+ const handlerName = pikkuFuncId;
163
175
  // Look up in the registry
164
176
  const fnMeta = state.functions.meta[handlerName];
165
177
  if (fnMeta) {
@@ -171,7 +183,7 @@ export function addMessagesRoutes(logger, obj, state, checker) {
171
183
  ? resolveMiddleware(state, init, routeTags, checker)
172
184
  : undefined;
173
185
  result[channelKey][routeKey] = {
174
- pikkuFuncName: handlerName,
186
+ pikkuFuncId: handlerName,
175
187
  middleware: routeMiddleware,
176
188
  };
177
189
  continue;
@@ -192,8 +204,8 @@ export function addMessagesRoutes(logger, obj, state, checker) {
192
204
  const exportDecl = exportDecls[0];
193
205
  if (ts.isVariableDeclaration(exportDecl) &&
194
206
  exportDecl.initializer) {
195
- const { pikkuFuncName } = extractFunctionName(exportDecl.initializer, checker, state.rootDir);
196
- const handlerName = pikkuFuncName;
207
+ const { pikkuFuncId } = extractFunctionName(exportDecl.initializer, checker, state.rootDir);
208
+ const handlerName = pikkuFuncId;
197
209
  const fnMeta = state.functions.meta[handlerName];
198
210
  if (fnMeta) {
199
211
  // Resolve middleware for this route
@@ -204,15 +216,15 @@ export function addMessagesRoutes(logger, obj, state, checker) {
204
216
  ? resolveMiddleware(state, init, routeTags, checker)
205
217
  : undefined;
206
218
  result[channelKey][routeKey] = {
207
- pikkuFuncName: handlerName,
219
+ pikkuFuncId: handlerName,
208
220
  middleware: routeMiddleware,
209
221
  };
210
222
  continue;
211
223
  }
212
224
  }
213
225
  else if (ts.isFunctionDeclaration(exportDecl)) {
214
- const { pikkuFuncName } = extractFunctionName(exportDecl, checker, state.rootDir);
215
- const handlerName = pikkuFuncName;
226
+ const { pikkuFuncId } = extractFunctionName(exportDecl, checker, state.rootDir);
227
+ const handlerName = pikkuFuncId;
216
228
  const fnMeta = state.functions.meta[handlerName];
217
229
  if (fnMeta) {
218
230
  // Resolve middleware for this route
@@ -223,7 +235,7 @@ export function addMessagesRoutes(logger, obj, state, checker) {
223
235
  ? resolveMiddleware(state, init, routeTags, checker)
224
236
  : undefined;
225
237
  result[channelKey][routeKey] = {
226
- pikkuFuncName: handlerName,
238
+ pikkuFuncId: handlerName,
227
239
  middleware: routeMiddleware,
228
240
  };
229
241
  continue;
@@ -249,7 +261,7 @@ export function addMessagesRoutes(logger, obj, state, checker) {
249
261
  continue;
250
262
  }
251
263
  result[channelKey][routeKey] = {
252
- pikkuFuncName: possibleMatch,
264
+ pikkuFuncId: possibleMatch,
253
265
  };
254
266
  continue;
255
267
  }
@@ -274,8 +286,8 @@ export function addMessagesRoutes(logger, obj, state, checker) {
274
286
  // If we found the actual function, extract its name
275
287
  if (actualFunction) {
276
288
  // Extract the function name directly from the actual function
277
- const { pikkuFuncName } = extractFunctionName(actualFunction, checker, state.rootDir);
278
- const handlerName = pikkuFuncName;
289
+ const { pikkuFuncId } = extractFunctionName(actualFunction, checker, state.rootDir);
290
+ const handlerName = pikkuFuncId;
279
291
  // Now use this handlerName to look up in the registry
280
292
  const fnMeta = state.functions.meta[handlerName];
281
293
  if (fnMeta) {
@@ -287,7 +299,7 @@ export function addMessagesRoutes(logger, obj, state, checker) {
287
299
  ? resolveMiddleware(state, init, routeTags, checker)
288
300
  : undefined;
289
301
  result[channelKey][routeKey] = {
290
- pikkuFuncName: handlerName,
302
+ pikkuFuncId: handlerName,
291
303
  middleware: routeMiddleware,
292
304
  };
293
305
  continue; // Skip the normal processing below
@@ -316,7 +328,7 @@ export function addMessagesRoutes(logger, obj, state, checker) {
316
328
  ? resolveMiddleware(state, init, routeTags, checker)
317
329
  : undefined;
318
330
  result[channelKey][routeKey] = {
319
- pikkuFuncName: handlerName,
331
+ pikkuFuncId: handlerName,
320
332
  middleware: routeMiddleware,
321
333
  };
322
334
  }
@@ -349,7 +361,9 @@ export const addChannel = (logger, node, checker, state, options) => {
349
361
  .keys.filter((k) => k.type === 'param')
350
362
  .map((k) => k.name)
351
363
  : [];
352
- const { tags, summary, description, errors } = getCommonWireMetaData(obj, 'Channel', name, logger);
364
+ const { disabled, tags, summary, description, errors } = getCommonWireMetaData(obj, 'Channel', name, logger);
365
+ if (disabled)
366
+ return;
353
367
  const query = getPropertyValue(obj, 'query');
354
368
  const connect = getPropertyAssignmentInitializer(obj, 'onConnect', true, checker);
355
369
  const disconnect = getPropertyAssignmentInitializer(obj, 'onDisconnect', true, checker);
@@ -357,42 +371,51 @@ export const addChannel = (logger, node, checker, state, options) => {
357
371
  let message = null;
358
372
  const onMsgProp = getPropertyAssignmentInitializer(obj, 'onMessage', true, checker);
359
373
  if (onMsgProp) {
360
- const { pikkuFuncName } = extractFunctionName(onMsgProp, checker, state.rootDir);
361
- const fnMeta = state.functions.meta[pikkuFuncName];
374
+ const extracted = extractFunctionName(onMsgProp, checker, state.rootDir);
375
+ const msgFuncId = extracted.pikkuFuncId.startsWith('__temp_')
376
+ ? makeContextBasedId('channel', name, 'message')
377
+ : extracted.pikkuFuncId;
378
+ const fnMeta = state.functions.meta[msgFuncId];
362
379
  if (!fnMeta) {
363
- logger.critical(ErrorCode.FUNCTION_METADATA_NOT_FOUND, `No function metadata found for onMessage handler '${pikkuFuncName}'`);
380
+ logger.critical(ErrorCode.FUNCTION_METADATA_NOT_FOUND, `No function metadata found for onMessage handler '${msgFuncId}'`);
364
381
  return;
365
382
  }
366
383
  message = {
367
- pikkuFuncName,
384
+ pikkuFuncId: msgFuncId,
368
385
  };
369
386
  }
370
387
  // nested message-routes
371
388
  const messageWirings = addMessagesRoutes(logger, obj, state, checker);
372
389
  // --- resolve middleware ---
373
390
  const middleware = resolveMiddleware(state, obj, tags, checker);
391
+ const channelMiddleware = resolveChannelMiddleware(state, obj, tags, checker);
374
392
  // --- track used functions/middleware for service aggregation ---
375
393
  // Track connect/disconnect/message handlers
394
+ let connectFuncId;
376
395
  if (connect) {
377
- const connectFuncName = extractFunctionName(connect, checker, state.rootDir).pikkuFuncName;
378
- state.serviceAggregation.usedFunctions.add(connectFuncName);
396
+ const extracted = extractFunctionName(connect, checker, state.rootDir);
397
+ connectFuncId = extracted.pikkuFuncId.startsWith('__temp_')
398
+ ? makeContextBasedId('channel', name, 'connect')
399
+ : extracted.pikkuFuncId;
400
+ state.serviceAggregation.usedFunctions.add(connectFuncId);
379
401
  }
402
+ let disconnectFuncId;
380
403
  if (disconnect) {
381
- const disconnectFuncName = extractFunctionName(disconnect, checker, state.rootDir).pikkuFuncName;
382
- state.serviceAggregation.usedFunctions.add(disconnectFuncName);
404
+ const extracted = extractFunctionName(disconnect, checker, state.rootDir);
405
+ disconnectFuncId = extracted.pikkuFuncId.startsWith('__temp_')
406
+ ? makeContextBasedId('channel', name, 'disconnect')
407
+ : extracted.pikkuFuncId;
408
+ state.serviceAggregation.usedFunctions.add(disconnectFuncId);
383
409
  }
384
410
  if (message) {
385
- state.serviceAggregation.usedFunctions.add(message.pikkuFuncName);
411
+ state.serviceAggregation.usedFunctions.add(message.pikkuFuncId);
386
412
  }
387
- // Track message wiring handlers
388
413
  for (const channelHandlers of Object.values(messageWirings)) {
389
414
  for (const handler of Object.values(channelHandlers)) {
390
- state.serviceAggregation.usedFunctions.add(handler.pikkuFuncName);
415
+ state.serviceAggregation.usedFunctions.add(handler.pikkuFuncId);
391
416
  }
392
417
  }
393
- // Track middleware
394
418
  extractWireNames(middleware).forEach((name) => state.serviceAggregation.usedMiddleware.add(name));
395
- // record into state
396
419
  state.channels.files.add(node.getSourceFile().fileName);
397
420
  state.channels.meta[name] = {
398
421
  name,
@@ -400,24 +423,8 @@ export const addChannel = (logger, node, checker, state, options) => {
400
423
  input: null,
401
424
  params: params.length ? params : undefined,
402
425
  query: query?.length ? query : undefined,
403
- // inputTypes: getInputTypes(
404
- // state.channels.metaInputTypes,
405
- // 'get',
406
- // null, // TODO
407
- // query,
408
- // params
409
- // ),
410
- connect: connect
411
- ? {
412
- pikkuFuncName: extractFunctionName(connect, checker, state.rootDir)
413
- .pikkuFuncName,
414
- }
415
- : null,
416
- disconnect: disconnect
417
- ? {
418
- pikkuFuncName: extractFunctionName(disconnect, checker, state.rootDir).pikkuFuncName,
419
- }
420
- : null,
426
+ connect: connectFuncId ? { pikkuFuncId: connectFuncId } : null,
427
+ disconnect: disconnectFuncId ? { pikkuFuncId: disconnectFuncId } : null,
421
428
  message,
422
429
  messageWirings,
423
430
  summary,
@@ -425,5 +432,6 @@ export const addChannel = (logger, node, checker, state, options) => {
425
432
  errors,
426
433
  tags: tags ?? undefined,
427
434
  middleware,
435
+ channelMiddleware,
428
436
  };
429
437
  };
@@ -3,6 +3,7 @@ import { extractFunctionName } from '../utils/extract-function-name.js';
3
3
  import { resolveMiddleware } from '../utils/middleware.js';
4
4
  import { extractWireNames } from '../utils/post-process.js';
5
5
  import { getPropertyValue } from '../utils/get-property-value.js';
6
+ import { resolveIdentifier } from '../utils/resolve-identifier.js';
6
7
  // Track if we've warned about missing Config type to avoid duplicate warnings
7
8
  const configTypeWarningShown = new Set();
8
9
  /**
@@ -68,6 +69,9 @@ function processCLIConfig(logger, node, sourceFile, typeChecker, inspectorState,
68
69
  if (!programName) {
69
70
  return null;
70
71
  }
72
+ if (getPropertyValue(node, 'disabled') === true) {
73
+ return null;
74
+ }
71
75
  // Second pass: process other properties with program tags available
72
76
  for (const prop of node.properties) {
73
77
  if (!ts.isPropertyAssignment(prop))
@@ -92,7 +96,7 @@ function processCLIConfig(logger, node, sourceFile, typeChecker, inspectorState,
92
96
  break;
93
97
  case 'render':
94
98
  // Extract the actual renderer function name
95
- programMeta.defaultRenderName = extractFunctionName(prop.initializer, typeChecker, inspectorState.rootDir).pikkuFuncName;
99
+ programMeta.defaultRenderName = extractFunctionName(prop.initializer, typeChecker, inspectorState.rootDir).pikkuFuncId;
96
100
  break;
97
101
  }
98
102
  }
@@ -105,6 +109,20 @@ function processCommands(logger, node, sourceFile, typeChecker, programName, ins
105
109
  const commands = {};
106
110
  let defaultCommandName = null;
107
111
  for (const prop of node.properties) {
112
+ // Handle spread assignments: { ...externalCommands }
113
+ if (ts.isSpreadAssignment(prop)) {
114
+ let spreadTarget = prop.expression;
115
+ if (ts.isIdentifier(prop.expression)) {
116
+ spreadTarget = resolveIdentifier(prop.expression, typeChecker, [
117
+ 'defineCLICommands',
118
+ ]);
119
+ }
120
+ if (spreadTarget && ts.isObjectLiteralExpression(spreadTarget)) {
121
+ const spreadCommands = processCommands(logger, spreadTarget, sourceFile, typeChecker, programName, inspectorState, options, programTags);
122
+ Object.assign(commands, spreadCommands);
123
+ }
124
+ continue;
125
+ }
108
126
  if (!ts.isPropertyAssignment(prop))
109
127
  continue;
110
128
  const commandName = getPropertyName(prop);
@@ -138,7 +156,7 @@ function processCommand(logger, inspectorState, options, name, node, sourceFile,
138
156
  ts.isArrowFunction(node) ||
139
157
  ts.isFunctionExpression(node)) {
140
158
  return {
141
- pikkuFuncName: extractFunctionName(node, typeChecker, inspectorState.rootDir).pikkuFuncName,
159
+ pikkuFuncId: extractFunctionName(node, typeChecker, inspectorState.rootDir).pikkuFuncId,
142
160
  positionals: [],
143
161
  options: {},
144
162
  };
@@ -160,12 +178,12 @@ function processCommand(logger, inspectorState, options, name, node, sourceFile,
160
178
  return null;
161
179
  }
162
180
  const meta = {
163
- pikkuFuncName: '',
181
+ pikkuFuncId: '',
164
182
  positionals: [],
165
183
  options: {},
166
184
  };
167
- // First pass: extract pikkuFuncName and tags so we can use them when processing options/middleware
168
- let pikkuFuncName;
185
+ // First pass: extract pikkuFuncId and tags so we can use them when processing options/middleware
186
+ let pikkuFuncId;
169
187
  let optionsNode;
170
188
  let tags;
171
189
  for (const prop of node.properties) {
@@ -175,8 +193,8 @@ function processCommand(logger, inspectorState, options, name, node, sourceFile,
175
193
  continue;
176
194
  const propName = prop.name.text;
177
195
  if (propName === 'func') {
178
- pikkuFuncName = extractFunctionName(prop.initializer, typeChecker, inspectorState.rootDir).pikkuFuncName;
179
- meta.pikkuFuncName = pikkuFuncName;
196
+ pikkuFuncId = extractFunctionName(prop.initializer, typeChecker, inspectorState.rootDir).pikkuFuncId;
197
+ meta.pikkuFuncId = pikkuFuncId;
180
198
  }
181
199
  else if (propName === 'options' &&
182
200
  ts.isObjectLiteralExpression(prop.initializer)) {
@@ -220,12 +238,12 @@ function processCommand(logger, inspectorState, options, name, node, sourceFile,
220
238
  // Already handled in first pass
221
239
  break;
222
240
  case 'render':
223
- meta.renderName = extractFunctionName(prop.initializer, typeChecker, inspectorState.rootDir).pikkuFuncName;
241
+ meta.renderName = extractFunctionName(prop.initializer, typeChecker, inspectorState.rootDir).pikkuFuncId;
224
242
  break;
225
243
  case 'options':
226
- // Process with pikkuFuncName from first pass
244
+ // Process with pikkuFuncId from first pass
227
245
  if (optionsNode) {
228
- meta.options = processOptions(logger, optionsNode, typeChecker, inspectorState, options, pikkuFuncName);
246
+ meta.options = processOptions(logger, optionsNode, typeChecker, inspectorState, options, pikkuFuncId);
229
247
  }
230
248
  break;
231
249
  case 'subcommands':
@@ -253,7 +271,7 @@ function processCommand(logger, inspectorState, options, name, node, sourceFile,
253
271
  }
254
272
  }
255
273
  // --- track used functions/middleware for service aggregation ---
256
- inspectorState.serviceAggregation.usedFunctions.add(meta.pikkuFuncName);
274
+ inspectorState.serviceAggregation.usedFunctions.add(meta.pikkuFuncId);
257
275
  extractWireNames(meta.middleware).forEach((name) => inspectorState.serviceAggregation.usedMiddleware.add(name));
258
276
  // Note: subcommands are tracked recursively when they're processed
259
277
  return meta;
@@ -261,7 +279,7 @@ function processCommand(logger, inspectorState, options, name, node, sourceFile,
261
279
  /**
262
280
  * Processes CLI options and extracts enum values from function input types
263
281
  */
264
- function processOptions(logger, node, typeChecker, inspectorState, inspectorOptions, pikkuFuncName) {
282
+ function processOptions(logger, node, typeChecker, inspectorState, inspectorOptions, pikkuFuncId) {
265
283
  const options = {};
266
284
  for (const prop of node.properties) {
267
285
  if (!ts.isPropertyAssignment(prop))
@@ -318,10 +336,10 @@ function processOptions(logger, node, typeChecker, inspectorState, inspectorOpti
318
336
  }
319
337
  }
320
338
  // Extract enum values from the function input type if available
321
- // Get the input type if we have a pikkuFuncName
339
+ // Get the input type if we have a pikkuFuncId
322
340
  let inputTypes;
323
- if (pikkuFuncName) {
324
- inputTypes = inspectorState.typesLookup.get(pikkuFuncName);
341
+ if (pikkuFuncId) {
342
+ inputTypes = inspectorState.typesLookup.get(pikkuFuncId);
325
343
  }
326
344
  let derivedChoices = null;
327
345
  if (inputTypes && inputTypes.length > 0) {
@@ -527,7 +545,7 @@ export const addCLIRenderers = (logger, node, typeChecker, inspectorState, optio
527
545
  if (args.length === 0)
528
546
  return;
529
547
  // Extract renderer name
530
- const { pikkuFuncName, exportedName } = extractFunctionName(node, typeChecker, inspectorState.rootDir);
548
+ const { pikkuFuncId, exportedName } = extractFunctionName(node, typeChecker, inspectorState.rootDir);
531
549
  // Get the source file path
532
550
  const sourceFile = node.getSourceFile();
533
551
  const filePath = sourceFile.fileName;
@@ -553,8 +571,8 @@ export const addCLIRenderers = (logger, node, typeChecker, inspectorState, optio
553
571
  }
554
572
  }
555
573
  // Store renderer metadata
556
- inspectorState.cli.meta.renderers[pikkuFuncName] = {
557
- name: pikkuFuncName,
574
+ inspectorState.cli.meta.renderers[pikkuFuncId] = {
575
+ name: pikkuFuncId,
558
576
  exportedName: exportedName ?? undefined,
559
577
  services,
560
578
  filePath,
@@ -3,7 +3,9 @@ import { extractServicesFromFunction } from '../utils/extract-services.js';
3
3
  // Mapping of wrapper function names to their corresponding types
4
4
  const wrapperFunctionMap = {
5
5
  pikkuConfig: 'CreateConfig',
6
+ pikkuExternalConfig: 'CreateConfig',
6
7
  pikkuServices: 'CreateSingletonServices',
8
+ pikkuExternalServices: 'CreateSingletonServices',
7
9
  pikkuWireServices: 'CreateWireServices',
8
10
  };
9
11
  export const addFileWithFactory = (node, checker, methods = new Map(), expectedTypeName, state) => {