@pikku/inspector 0.12.7 → 0.12.9

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 (51) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/add/add-ai-agent.js +24 -7
  3. package/dist/add/add-channel.js +2 -2
  4. package/dist/add/add-cli.js +13 -10
  5. package/dist/add/add-file-with-factory.js +22 -5
  6. package/dist/add/add-functions.js +4 -3
  7. package/dist/add/add-http-route.js +1 -0
  8. package/dist/add/add-mcp-prompt.js +4 -0
  9. package/dist/add/add-mcp-resource.js +4 -0
  10. package/dist/add/add-rpc-invocations.js +2 -2
  11. package/dist/add/add-workflow.d.ts +5 -0
  12. package/dist/add/add-workflow.js +20 -2
  13. package/dist/inspector.js +1 -0
  14. package/dist/types.d.ts +1 -0
  15. package/dist/utils/extract-function-name.d.ts +1 -0
  16. package/dist/utils/extract-function-name.js +27 -32
  17. package/dist/utils/extract-node-value.js +6 -1
  18. package/dist/utils/filter-inspector-state.js +211 -8
  19. package/dist/utils/load-addon-functions-meta.js +47 -0
  20. package/dist/utils/post-process.js +63 -0
  21. package/dist/utils/resolve-versions.js +30 -0
  22. package/dist/utils/schema-generator.js +124 -33
  23. package/dist/utils/serialize-inspector-state.d.ts +1 -0
  24. package/dist/utils/serialize-inspector-state.js +2 -0
  25. package/dist/visit.js +1 -1
  26. package/package.json +2 -2
  27. package/src/add/add-ai-agent.ts +25 -10
  28. package/src/add/add-channel.ts +2 -2
  29. package/src/add/add-cli.ts +17 -16
  30. package/src/add/add-file-with-factory.ts +26 -7
  31. package/src/add/add-functions.ts +4 -4
  32. package/src/add/add-http-route.ts +6 -1
  33. package/src/add/add-mcp-prompt.ts +5 -0
  34. package/src/add/add-mcp-resource.ts +5 -0
  35. package/src/add/add-queue-worker.ts +5 -1
  36. package/src/add/add-rpc-invocations.ts +2 -2
  37. package/src/add/add-workflow.ts +22 -2
  38. package/src/inspector.ts +1 -0
  39. package/src/types.ts +1 -0
  40. package/src/utils/extract-function-name.ts +36 -37
  41. package/src/utils/extract-node-value.test.ts +67 -0
  42. package/src/utils/extract-node-value.ts +5 -1
  43. package/src/utils/filter-inspector-state.ts +246 -11
  44. package/src/utils/load-addon-functions-meta.ts +59 -0
  45. package/src/utils/post-process.ts +74 -0
  46. package/src/utils/resolve-versions.test.ts +141 -0
  47. package/src/utils/resolve-versions.ts +37 -0
  48. package/src/utils/schema-generator.ts +191 -41
  49. package/src/utils/serialize-inspector-state.ts +3 -0
  50. package/src/visit.ts +2 -1
  51. package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
1
1
  ## 0.12.0
2
2
 
3
+ ## 0.12.9
4
+
5
+ ### Patch Changes
6
+
7
+ - 2ac6468: Fix workflow inspector crash when workflow.do() data object has a 'description' property
8
+ - fbcf5b9: Add version awareness to RPC handler: versioned functions now appear in the exposed RPC type map (e.g. `getData@v1`, `getData@v2`), enabling type-safe `rpc.invoke('getData@v1', data)` calls. Tree-shaking respects specific version filters without pulling in all versions. HTTP wirings correctly resolve versioned function IDs.
9
+ - Updated dependencies [fbcf5b9]
10
+ - @pikku/core@0.12.16
11
+
12
+ ## 0.12.8
13
+
14
+ ### Patch Changes
15
+
16
+ - 624097e: Add deploy pipeline with provider-agnostic architecture
17
+
18
+ - Add MetaService with explicit typed API, absorb WiringService reads
19
+ - Add deployment service, traceId propagation, scoped logger
20
+ - Rewrite analyzer: one function = one worker, gateways dispatch via RPC
21
+ - Add Cloudflare deploy provider with plan/apply commands
22
+ - Add per-unit filtered codegen for deploy pipeline
23
+ - Skip missing metadata in wiring registration for deploy units
24
+ - Fix schema coercion crash when schema has no properties
25
+ - Fix E2E codegen: double-pass resolves cross-package Zod type imports
26
+
27
+ - Updated dependencies [9e8605f]
28
+ - Updated dependencies [624097e]
29
+ - Updated dependencies [7ab3243]
30
+ - @pikku/core@0.12.15
31
+
3
32
  ## 0.12.7
4
33
 
5
34
  ### Patch Changes
@@ -33,7 +33,7 @@ function resolveToolReferences(obj, checker, agentName, logger) {
33
33
  continue;
34
34
  }
35
35
  }
36
- if (calleeName === 'addon') {
36
+ if (calleeName === 'ref') {
37
37
  const [firstArg] = element.arguments;
38
38
  if (firstArg && ts.isStringLiteral(firstArg)) {
39
39
  resolved.push(firstArg.text);
@@ -163,11 +163,12 @@ export const addAIAgent = (logger, node, checker, state, options) => {
163
163
  if (disabled)
164
164
  return;
165
165
  const modelValue = getPropertyValue(obj, 'model');
166
- const instructionsValue = getPropertyValue(obj, 'instructions');
166
+ const roleValue = getPropertyValue(obj, 'role');
167
+ const personalityValue = getPropertyValue(obj, 'personality');
168
+ const goalValue = getPropertyValue(obj, 'goal');
167
169
  const maxStepsValue = getPropertyValue(obj, 'maxSteps');
168
170
  const temperatureValue = getPropertyValue(obj, 'temperature');
169
171
  const toolChoiceValue = getPropertyValue(obj, 'toolChoice');
170
- const dynamicWorkflowsValue = getPropertyValue(obj, 'dynamicWorkflows');
171
172
  const toolsValue = resolveToolReferences(obj, checker, nameValue || '', logger);
172
173
  if (toolsValue) {
173
174
  for (const toolName of toolsValue) {
@@ -291,10 +292,14 @@ export const addAIAgent = (logger, node, checker, state, options) => {
291
292
  state.agents.agentsMeta[agentKey] = {
292
293
  name: nameValue,
293
294
  description,
294
- instructions: instructionsValue || '',
295
+ role: roleValue || undefined,
296
+ personality: personalityValue || undefined,
297
+ goal: goalValue || '',
295
298
  model: modelValue || '',
296
299
  summary,
297
300
  errors,
301
+ sourceFile: node.getSourceFile().fileName,
302
+ exportedName: exportedName || undefined,
298
303
  ...(maxStepsValue !== null && { maxSteps: maxStepsValue }),
299
304
  ...(temperatureValue !== null && { temperature: temperatureValue }),
300
305
  ...(toolChoiceValue !== null && {
@@ -302,9 +307,6 @@ export const addAIAgent = (logger, node, checker, state, options) => {
302
307
  }),
303
308
  ...(toolsValue !== null && { tools: toolsValue }),
304
309
  ...(agentsValue !== null && { agents: agentsValue }),
305
- ...(dynamicWorkflowsValue !== null && {
306
- dynamicWorkflows: dynamicWorkflowsValue,
307
- }),
308
310
  tags,
309
311
  inputSchema,
310
312
  outputSchema,
@@ -314,5 +316,20 @@ export const addAIAgent = (logger, node, checker, state, options) => {
314
316
  aiMiddleware,
315
317
  permissions,
316
318
  };
319
+ // AI agent functions require platform services that aren't visible
320
+ // through parameter destructuring
321
+ const funcMeta = state.functions.meta[agentKey];
322
+ if (funcMeta?.services) {
323
+ for (const svc of [
324
+ 'aiStorage',
325
+ 'aiRunState',
326
+ 'agentRunService',
327
+ 'aiAgentRunner',
328
+ ]) {
329
+ if (!funcMeta.services.services.includes(svc)) {
330
+ funcMeta.services.services.push(svc);
331
+ }
332
+ }
333
+ }
317
334
  }
318
335
  };
@@ -65,8 +65,8 @@ function getHandlerNameFromExpression(expr, checker, rootDir) {
65
65
  }
66
66
  // Handle call expressions
67
67
  if (ts.isCallExpression(expr)) {
68
- // Handle addon('namespace:funcName') calls
69
- if (ts.isIdentifier(expr.expression) && expr.expression.text === 'addon') {
68
+ // Handle ref('name') calls
69
+ if (ts.isIdentifier(expr.expression) && expr.expression.text === 'ref') {
70
70
  const [firstArg] = expr.arguments;
71
71
  if (firstArg && ts.isStringLiteral(firstArg)) {
72
72
  return firstArg.text;
@@ -206,22 +206,25 @@ function processCommand(logger, inspectorState, options, name, node, sourceFile,
206
206
  if (propName === 'func') {
207
207
  if (ts.isCallExpression(prop.initializer) &&
208
208
  ts.isIdentifier(prop.initializer.expression) &&
209
- prop.initializer.expression.text === 'addon') {
209
+ prop.initializer.expression.text === 'ref') {
210
210
  const [firstArg] = prop.initializer.arguments;
211
211
  if (!firstArg || !ts.isStringLiteral(firstArg)) {
212
- throw new Error(`addon() call requires a string literal argument in the form "namespace:funcName"`);
212
+ throw new Error(`ref() call requires a string literal argument`);
213
213
  }
214
214
  pikkuFuncId = firstArg.text;
215
- const addonNamespace = pikkuFuncId.split(':')[0];
216
- if (!addonNamespace || !pikkuFuncId.includes(':')) {
217
- throw new Error(`Malformed addon function ID "${pikkuFuncId}": expected "namespace:funcName" format`);
218
- }
219
- if (!inspectorState.rpc.wireAddonDeclarations.has(addonNamespace)) {
220
- throw new Error(`Unknown addon namespace "${addonNamespace}" in "${pikkuFuncId}": no matching wireAddonDeclarations entry found`);
215
+ const addonNamespace = pikkuFuncId.includes(':')
216
+ ? pikkuFuncId.split(':')[0]
217
+ : null;
218
+ if (addonNamespace) {
219
+ if (!inspectorState.rpc.wireAddonDeclarations.has(addonNamespace)) {
220
+ throw new Error(`Unknown addon namespace "${addonNamespace}" in "${pikkuFuncId}": no matching wireAddonDeclarations entry found`);
221
+ }
221
222
  }
222
223
  meta.pikkuFuncId = pikkuFuncId;
223
- meta.packageName =
224
- inspectorState.rpc.wireAddonDeclarations.get(addonNamespace).package;
224
+ if (addonNamespace) {
225
+ meta.packageName =
226
+ inspectorState.rpc.wireAddonDeclarations.get(addonNamespace).package;
227
+ }
225
228
  }
226
229
  else {
227
230
  pikkuFuncId = extractFunctionName(prop.initializer, typeChecker, inspectorState.rootDir).pikkuFuncId;
@@ -40,10 +40,7 @@ export const addFileWithFactory = (node, checker, methods = new Map(), expectedT
40
40
  typePath: typeDeclarationPath,
41
41
  });
42
42
  methods.set(fileName, variables);
43
- // Extract singleton services for CreateWireServices factories
44
- if (expectedTypeName === 'CreateWireServices' &&
45
- state &&
46
- callExpression.arguments.length > 0) {
43
+ if (state && callExpression.arguments.length > 0) {
47
44
  const firstArg = callExpression.arguments[0];
48
45
  let functionNode;
49
46
  if (ts.isArrowFunction(firstArg)) {
@@ -52,10 +49,30 @@ export const addFileWithFactory = (node, checker, methods = new Map(), expectedT
52
49
  else if (ts.isFunctionExpression(firstArg)) {
53
50
  functionNode = firstArg;
54
51
  }
55
- if (functionNode) {
52
+ // Extract singleton services for CreateWireServices factories
53
+ if (expectedTypeName === 'CreateWireServices' && functionNode) {
56
54
  const servicesMeta = extractServicesFromFunction(functionNode);
57
55
  state.wireServicesMeta.set(variableName, servicesMeta.services);
58
56
  }
57
+ // Extract existing services an addon needs from the parent
58
+ // (second parameter of pikkuAddonServices callback)
59
+ if (wrapperFunctionName === 'pikkuAddonServices' &&
60
+ functionNode &&
61
+ functionNode.parameters.length >= 2) {
62
+ const secondParam = functionNode.parameters[1];
63
+ if (secondParam && ts.isObjectBindingPattern(secondParam.name)) {
64
+ for (const elem of secondParam.name.elements) {
65
+ const name = elem.propertyName && ts.isIdentifier(elem.propertyName)
66
+ ? elem.propertyName.text
67
+ : ts.isIdentifier(elem.name)
68
+ ? elem.name.text
69
+ : undefined;
70
+ if (name) {
71
+ state.addonRequiredParentServices.push(name);
72
+ }
73
+ }
74
+ }
75
+ }
59
76
  }
60
77
  return; // Early return since we found a match
61
78
  }
@@ -594,6 +594,8 @@ export const addFunctions = (logger, node, checker, state, options) => {
594
594
  middleware,
595
595
  permissions,
596
596
  isDirectFunction,
597
+ sourceFile: node.getSourceFile().fileName,
598
+ exportedName: exportedName || undefined,
597
599
  };
598
600
  // Populate node metadata if node config is present
599
601
  if (nodeDisplayName && nodeCategory && nodeType) {
@@ -613,15 +615,14 @@ export const addFunctions = (logger, node, checker, state, options) => {
613
615
  }
614
616
  if (mcpEnabled) {
615
617
  if (!description) {
616
- logger.critical(ErrorCode.MISSING_DESCRIPTION, `MCP tool '${name}' is missing a description.`);
617
- return;
618
+ logger.warn(`MCP tool '${name}' is missing a description.`);
618
619
  }
619
620
  state.mcpEndpoints.files.add(node.getSourceFile().fileName);
620
621
  state.mcpEndpoints.toolsMeta[name] = {
621
622
  pikkuFuncId,
622
623
  name,
623
624
  title: title || undefined,
624
- description,
625
+ description: description || undefined,
625
626
  summary,
626
627
  errors,
627
628
  tags,
@@ -174,6 +174,7 @@ export function registerHTTPRoute({ obj, state, checker, logger, sourceFile, bas
174
174
  pikkuFuncId: funcName,
175
175
  ...(packageName && { packageName }),
176
176
  route: fullRoute,
177
+ sourceFile: sourceFile.fileName,
177
178
  method: method,
178
179
  params: params.length > 0 ? params : undefined,
179
180
  query: query.length > 0 ? query : undefined,
@@ -58,6 +58,10 @@ export const addMCPPrompt = (logger, node, checker, state, options) => {
58
58
  }
59
59
  const inputSchema = fnMeta.inputs?.[0] || null;
60
60
  const outputSchema = fnMeta.outputs?.[0] || null;
61
+ if (!fnMeta.outputSchemaName) {
62
+ fnMeta.outputSchemaName = 'MCPPromptResponse';
63
+ fnMeta.outputs = ['MCPPromptResponse'];
64
+ }
61
65
  // --- resolve middleware ---
62
66
  const middleware = resolveMiddleware(state, obj, tags, checker);
63
67
  // --- resolve permissions ---
@@ -67,6 +67,10 @@ export const addMCPResource = (logger, node, checker, state, options) => {
67
67
  }
68
68
  const inputSchema = fnMeta.inputs?.[0] || null;
69
69
  const outputSchema = fnMeta.outputs?.[0] || null;
70
+ if (!fnMeta.outputSchemaName) {
71
+ fnMeta.outputSchemaName = 'MCPResourceResponse';
72
+ fnMeta.outputs = ['MCPResourceResponse'];
73
+ }
70
74
  // --- resolve middleware ---
71
75
  const middleware = resolveMiddleware(state, obj, tags, checker);
72
76
  // --- resolve permissions ---
@@ -19,8 +19,8 @@ export function addRPCInvocations(node, state, logger) {
19
19
  // Look for call expressions: addon('ext:hello') or rpc.invoke('...')
20
20
  if (ts.isCallExpression(node)) {
21
21
  const { expression, arguments: args } = node;
22
- // Check for addon('namespace:function') calls
23
- if (ts.isIdentifier(expression) && expression.text === 'addon') {
22
+ // Check for ref('name') calls
23
+ if (ts.isIdentifier(expression) && expression.text === 'ref') {
24
24
  const [firstArg] = args;
25
25
  if (firstArg && ts.isStringLiteral(firstArg)) {
26
26
  const functionRef = firstArg.text;
@@ -1,4 +1,9 @@
1
1
  import type { AddWiring } from '../types.js';
2
+ import type { WorkflowStepMeta } from '@pikku/core/workflow';
3
+ /**
4
+ * Recursively collect all RPC names from workflow steps
5
+ */
6
+ export declare function collectInvokedRPCs(steps: WorkflowStepMeta[], rpcs: Set<string>): void;
2
7
  /**
3
8
  * Inspector for pikkuWorkflow() and pikkuSimpleWorkflow() calls
4
9
  * Detects workflow registration and extracts metadata
@@ -43,7 +43,7 @@ function hasInlineSteps(steps) {
43
43
  /**
44
44
  * Recursively collect all RPC names from workflow steps
45
45
  */
46
- function collectInvokedRPCs(steps, rpcs) {
46
+ export function collectInvokedRPCs(steps, rpcs) {
47
47
  for (const step of steps) {
48
48
  if (step.type === 'rpc' && step.rpcName) {
49
49
  rpcs.add(step.rpcName);
@@ -88,7 +88,7 @@ function getWorkflowInvocations(node, checker, state, workflowName, steps) {
88
88
  // workflow.do(stepName, rpcName|fn, data?, options?)
89
89
  const stepNameArg = args[0];
90
90
  const secondArg = args[1];
91
- const optionsArg = args.length >= 3 ? args[args.length - 1] : undefined;
91
+ const optionsArg = args.length >= 4 ? args[args.length - 1] : undefined;
92
92
  const stepName = extractStringLiteral(stepNameArg, checker);
93
93
  const description = extractDescription(optionsArg, checker) ?? undefined;
94
94
  // Determine form by checking 2nd argument type
@@ -183,6 +183,7 @@ export const addWorkflow = (logger, node, checker, state) => {
183
183
  let description;
184
184
  let errors;
185
185
  let inline;
186
+ let expose;
186
187
  if (ts.isObjectLiteralExpression(firstArg)) {
187
188
  const metadata = getCommonWireMetaData(firstArg, 'Workflow', workflowName, logger);
188
189
  if (metadata.disabled)
@@ -195,6 +196,7 @@ export const addWorkflow = (logger, node, checker, state) => {
195
196
  if (inlineProp === true) {
196
197
  inline = true;
197
198
  }
199
+ expose = getPropertyValue(firstArg, 'expose');
198
200
  }
199
201
  // Validate that we got a valid function
200
202
  if (ts.isObjectLiteralExpression(firstArg) &&
@@ -278,5 +280,21 @@ export const addWorkflow = (logger, node, checker, state) => {
278
280
  errors,
279
281
  tags,
280
282
  inline,
283
+ expose,
281
284
  };
285
+ // Workflow functions require platform services that aren't visible
286
+ // through parameter destructuring (they're accessed via workflow.do/sleep)
287
+ const funcMeta = state.functions.meta[pikkuFuncId];
288
+ if (funcMeta?.services) {
289
+ for (const svc of [
290
+ 'workflowService',
291
+ 'workflowRunService',
292
+ 'schedulerService',
293
+ 'queueService',
294
+ ]) {
295
+ if (!funcMeta.services.services.includes(svc)) {
296
+ funcMeta.services.services.push(svc);
297
+ }
298
+ }
299
+ }
282
300
  };
package/dist/inspector.js CHANGED
@@ -28,6 +28,7 @@ export function getInitialInspectorState(rootDir) {
28
28
  singletonServicesFactories: new Map(),
29
29
  wireServicesFactories: new Map(),
30
30
  wireServicesMeta: new Map(),
31
+ addonRequiredParentServices: [],
31
32
  configFactories: new Map(),
32
33
  filesAndMethods: {},
33
34
  filesAndMethodsErrors: new Map(),
package/dist/types.d.ts CHANGED
@@ -264,6 +264,7 @@ export interface InspectorState {
264
264
  singletonServicesFactories: PathToNameAndType;
265
265
  wireServicesFactories: PathToNameAndType;
266
266
  wireServicesMeta: Map<string, string[]>;
267
+ addonRequiredParentServices: string[];
267
268
  configFactories: PathToNameAndType;
268
269
  filesAndMethods: InspectorFilesAndMethods;
269
270
  filesAndMethodsErrors: Map<string, PathToNameAndType>;
@@ -6,6 +6,7 @@ export type ExtractedFunctionName = {
6
6
  exportedName: string | null;
7
7
  propertyName: string | null;
8
8
  isHelper: boolean;
9
+ version: number | null;
9
10
  };
10
11
  export declare function makeContextBasedId(wiringType: string, ...segments: string[]): string;
11
12
  export declare function funcIdToTypeName(id: string): string;
@@ -1,5 +1,6 @@
1
1
  import * as ts from 'typescript';
2
2
  import { randomUUID } from 'crypto';
3
+ import { formatVersionedId } from '@pikku/core';
3
4
  export function makeContextBasedId(wiringType, ...segments) {
4
5
  return [wiringType, ...segments].join(':');
5
6
  }
@@ -20,6 +21,7 @@ export function extractFunctionName(callExpr, checker, rootDir) {
20
21
  propertyName: null,
21
22
  explicitName: null,
22
23
  isHelper: false,
24
+ version: null,
23
25
  };
24
26
  const workflowHelpers = new Set([
25
27
  'workflow',
@@ -103,16 +105,7 @@ export function extractFunctionName(callExpr, checker, rootDir) {
103
105
  // Check for object with 'name' property in first argument
104
106
  const firstArg = args[0];
105
107
  if (firstArg && ts.isObjectLiteralExpression(firstArg)) {
106
- for (const prop of firstArg.properties) {
107
- if (ts.isPropertyAssignment(prop) &&
108
- ts.isIdentifier(prop.name) &&
109
- prop.name.text === 'override' &&
110
- ts.isStringLiteral(prop.initializer)) {
111
- // Priority 1: Object with override property
112
- result.explicitName = prop.initializer.text;
113
- break;
114
- }
115
- }
108
+ extractOverrideAndVersion(firstArg, result);
116
109
  }
117
110
  // Special handling for pikkuSessionlessFunc pattern - use the arrow function directly
118
111
  if (expression.text.startsWith('pikku')) {
@@ -274,19 +267,9 @@ export function extractFunctionName(callExpr, checker, rootDir) {
274
267
  if (ts.isCallExpression(decl.initializer) &&
275
268
  ts.isIdentifier(decl.initializer.expression) &&
276
269
  decl.initializer.expression.text.startsWith('pikku')) {
277
- // Check for object with 'override' property in first argument
278
270
  const firstArg = decl.initializer.arguments[0];
279
271
  if (firstArg && ts.isObjectLiteralExpression(firstArg)) {
280
- for (const prop of firstArg.properties) {
281
- if (ts.isPropertyAssignment(prop) &&
282
- ts.isIdentifier(prop.name) &&
283
- prop.name.text === 'override' &&
284
- ts.isStringLiteral(prop.initializer)) {
285
- // Priority 1: Object with override property
286
- result.explicitName = prop.initializer.text;
287
- break;
288
- }
289
- }
272
+ extractOverrideAndVersion(firstArg, result);
290
273
  }
291
274
  if (decl.initializer.expression.text.startsWith('pikku')) {
292
275
  if (firstArg &&
@@ -340,17 +323,7 @@ export function extractFunctionName(callExpr, checker, rootDir) {
340
323
  else if (ts.isCallExpression(callExpr)) {
341
324
  const firstArg = callExpr.arguments[0];
342
325
  if (firstArg && ts.isObjectLiteralExpression(firstArg)) {
343
- for (const prop of firstArg.properties) {
344
- if (ts.isPropertyAssignment(prop) &&
345
- ts.isIdentifier(prop.name) &&
346
- prop.name.text === 'override' &&
347
- ts.isStringLiteral(prop.initializer) &&
348
- !result.explicitName // Only set if not already set
349
- ) {
350
- result.explicitName = prop.initializer.text;
351
- break;
352
- }
353
- }
326
+ extractOverrideAndVersion(firstArg, result);
354
327
  }
355
328
  }
356
329
  // Apply name priority logic
@@ -364,6 +337,9 @@ export function extractFunctionName(callExpr, checker, rootDir) {
364
337
  else {
365
338
  result.pikkuFuncId = `__temp_${randomUUID()}`;
366
339
  }
340
+ if (result.version !== null) {
341
+ result.pikkuFuncId = formatVersionedId(result.pikkuFuncId, result.version);
342
+ }
367
343
  return result;
368
344
  }
369
345
  /**
@@ -420,3 +396,22 @@ export function isNamedExport(declaration, checker) {
420
396
  }
421
397
  return false;
422
398
  }
399
+ function extractOverrideAndVersion(objLiteral, result) {
400
+ for (const prop of objLiteral.properties) {
401
+ if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
402
+ if (prop.name.text === 'override' &&
403
+ ts.isStringLiteral(prop.initializer) &&
404
+ !result.explicitName) {
405
+ result.explicitName = prop.initializer.text;
406
+ }
407
+ else if (prop.name.text === 'version' &&
408
+ ts.isNumericLiteral(prop.initializer) &&
409
+ result.version === null) {
410
+ const parsed = Number(prop.initializer.text);
411
+ if (Number.isInteger(parsed) && parsed >= 1) {
412
+ result.version = parsed;
413
+ }
414
+ }
415
+ }
416
+ }
417
+ }
@@ -84,7 +84,12 @@ export function extractDescription(optionsNode, checker) {
84
84
  if (!optionsNode || !ts.isObjectLiteralExpression(optionsNode)) {
85
85
  return null;
86
86
  }
87
- return extractPropertyString(optionsNode, 'description', checker);
87
+ try {
88
+ return extractPropertyString(optionsNode, 'description', checker);
89
+ }
90
+ catch {
91
+ return null;
92
+ }
88
93
  }
89
94
  /**
90
95
  * Extract duration value (number or string)