@pikku/inspector 0.12.8 → 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.
- package/CHANGELOG.md +9 -0
- package/dist/add/add-functions.js +2 -3
- package/dist/add/add-mcp-prompt.js +4 -0
- package/dist/add/add-mcp-resource.js +4 -0
- package/dist/add/add-workflow.js +1 -1
- package/dist/utils/extract-function-name.d.ts +1 -0
- package/dist/utils/extract-function-name.js +27 -32
- package/dist/utils/extract-node-value.js +6 -1
- package/dist/utils/filter-inspector-state.js +7 -3
- package/dist/utils/resolve-versions.js +30 -0
- package/package.json +2 -2
- package/src/add/add-functions.ts +2 -4
- package/src/add/add-mcp-prompt.ts +5 -0
- package/src/add/add-mcp-resource.ts +5 -0
- package/src/add/add-workflow.ts +1 -1
- package/src/utils/extract-function-name.ts +36 -37
- package/src/utils/extract-node-value.test.ts +67 -0
- package/src/utils/extract-node-value.ts +5 -1
- package/src/utils/filter-inspector-state.ts +7 -3
- package/src/utils/resolve-versions.test.ts +141 -0
- package/src/utils/resolve-versions.ts +37 -0
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
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
|
+
|
|
3
12
|
## 0.12.8
|
|
4
13
|
|
|
5
14
|
### Patch Changes
|
|
@@ -615,15 +615,14 @@ export const addFunctions = (logger, node, checker, state, options) => {
|
|
|
615
615
|
}
|
|
616
616
|
if (mcpEnabled) {
|
|
617
617
|
if (!description) {
|
|
618
|
-
logger.
|
|
619
|
-
return;
|
|
618
|
+
logger.warn(`MCP tool '${name}' is missing a description.`);
|
|
620
619
|
}
|
|
621
620
|
state.mcpEndpoints.files.add(node.getSourceFile().fileName);
|
|
622
621
|
state.mcpEndpoints.toolsMeta[name] = {
|
|
623
622
|
pikkuFuncId,
|
|
624
623
|
name,
|
|
625
624
|
title: title || undefined,
|
|
626
|
-
description,
|
|
625
|
+
description: description || undefined,
|
|
627
626
|
summary,
|
|
628
627
|
errors,
|
|
629
628
|
tags,
|
|
@@ -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 ---
|
package/dist/add/add-workflow.js
CHANGED
|
@@ -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 >=
|
|
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
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
@@ -532,11 +532,15 @@ export function filterInspectorState(state, filters, logger) {
|
|
|
532
532
|
filteredState.serviceAggregation.usedFunctions.add(funcId);
|
|
533
533
|
}
|
|
534
534
|
}
|
|
535
|
-
// Post-filter version expansion:
|
|
535
|
+
// Post-filter version expansion: when an unversioned base name is matched,
|
|
536
|
+
// include all its versions. Specific version matches (e.g. analyzeData@v1)
|
|
537
|
+
// do NOT expand to include other versions.
|
|
536
538
|
const includedBaseNames = new Set();
|
|
537
539
|
for (const funcId of filteredState.serviceAggregation.usedFunctions) {
|
|
538
|
-
const { baseName } = parseVersionedId(funcId);
|
|
539
|
-
|
|
540
|
+
const { baseName, version } = parseVersionedId(funcId);
|
|
541
|
+
if (version === null) {
|
|
542
|
+
includedBaseNames.add(baseName);
|
|
543
|
+
}
|
|
540
544
|
}
|
|
541
545
|
if (includedBaseNames.size > 0) {
|
|
542
546
|
for (const funcId of Object.keys(state.functions.meta)) {
|
|
@@ -66,13 +66,43 @@ export function resolveLatestVersions(state, logger) {
|
|
|
66
66
|
if (state.rpc.exposedMeta[baseName] === oldId) {
|
|
67
67
|
state.rpc.exposedMeta[baseName] = newId;
|
|
68
68
|
}
|
|
69
|
+
updateWiringReferences(state, oldId, newId);
|
|
69
70
|
}
|
|
70
71
|
else {
|
|
71
72
|
const latest = group.explicit.reduce((a, b) => a.version > b.version ? a : b);
|
|
72
73
|
state.rpc.internalMeta[baseName] = latest.id;
|
|
73
74
|
}
|
|
75
|
+
if (state.rpc.exposedMeta[baseName]) {
|
|
76
|
+
const latestId = state.rpc.internalMeta[baseName];
|
|
77
|
+
state.rpc.exposedMeta[baseName] = latestId;
|
|
78
|
+
for (const entry of group.explicit) {
|
|
79
|
+
state.rpc.exposedMeta[entry.id] = entry.id;
|
|
80
|
+
const fileEntry = state.rpc.internalFiles.get(entry.id);
|
|
81
|
+
if (fileEntry) {
|
|
82
|
+
state.rpc.exposedFiles.set(entry.id, fileEntry);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (group.unversioned) {
|
|
86
|
+
state.rpc.exposedMeta[latestId] = latestId;
|
|
87
|
+
const fileEntry = state.rpc.internalFiles.get(latestId);
|
|
88
|
+
if (fileEntry) {
|
|
89
|
+
state.rpc.exposedFiles.set(latestId, fileEntry);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
74
93
|
for (const entry of group.explicit) {
|
|
75
94
|
state.rpc.invokedFunctions.add(entry.id);
|
|
76
95
|
}
|
|
77
96
|
}
|
|
78
97
|
}
|
|
98
|
+
function updateWiringReferences(state, oldId, newId) {
|
|
99
|
+
if (state.http) {
|
|
100
|
+
for (const methods of Object.values(state.http.meta)) {
|
|
101
|
+
for (const meta of Object.values(methods)) {
|
|
102
|
+
if (meta.pikkuFuncId === oldId) {
|
|
103
|
+
meta.pikkuFuncId = newId;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pikku/inspector",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.9",
|
|
4
4
|
"author": "yasser.fadl@gmail.com",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"type": "module",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@openapi-contrib/json-schema-to-openapi-schema": "^4.3.1",
|
|
38
|
-
"@pikku/core": "^0.12.
|
|
38
|
+
"@pikku/core": "^0.12.16",
|
|
39
39
|
"path-to-regexp": "^8.3.0",
|
|
40
40
|
"ts-json-schema-generator": "^2.5.0",
|
|
41
41
|
"tsx": "^4.21.0",
|
package/src/add/add-functions.ts
CHANGED
|
@@ -817,18 +817,16 @@ export const addFunctions: AddWiring = (
|
|
|
817
817
|
|
|
818
818
|
if (mcpEnabled) {
|
|
819
819
|
if (!description) {
|
|
820
|
-
logger.
|
|
821
|
-
ErrorCode.MISSING_DESCRIPTION,
|
|
820
|
+
logger.warn(
|
|
822
821
|
`MCP tool '${name}' is missing a description.`
|
|
823
822
|
)
|
|
824
|
-
return
|
|
825
823
|
}
|
|
826
824
|
state.mcpEndpoints.files.add(node.getSourceFile().fileName)
|
|
827
825
|
state.mcpEndpoints.toolsMeta[name] = {
|
|
828
826
|
pikkuFuncId,
|
|
829
827
|
name,
|
|
830
828
|
title: title || undefined,
|
|
831
|
-
description,
|
|
829
|
+
description: description || undefined,
|
|
832
830
|
summary,
|
|
833
831
|
errors,
|
|
834
832
|
tags,
|
|
@@ -114,6 +114,11 @@ export const addMCPPrompt: AddWiring = (
|
|
|
114
114
|
const inputSchema = fnMeta.inputs?.[0] || null
|
|
115
115
|
const outputSchema = fnMeta.outputs?.[0] || null
|
|
116
116
|
|
|
117
|
+
if (!fnMeta.outputSchemaName) {
|
|
118
|
+
fnMeta.outputSchemaName = 'MCPPromptResponse'
|
|
119
|
+
fnMeta.outputs = ['MCPPromptResponse']
|
|
120
|
+
}
|
|
121
|
+
|
|
117
122
|
// --- resolve middleware ---
|
|
118
123
|
const middleware = resolveMiddleware(state, obj, tags, checker)
|
|
119
124
|
|
|
@@ -131,6 +131,11 @@ export const addMCPResource: AddWiring = (
|
|
|
131
131
|
const inputSchema = fnMeta.inputs?.[0] || null
|
|
132
132
|
const outputSchema = fnMeta.outputs?.[0] || null
|
|
133
133
|
|
|
134
|
+
if (!fnMeta.outputSchemaName) {
|
|
135
|
+
fnMeta.outputSchemaName = 'MCPResourceResponse'
|
|
136
|
+
fnMeta.outputs = ['MCPResourceResponse']
|
|
137
|
+
}
|
|
138
|
+
|
|
134
139
|
// --- resolve middleware ---
|
|
135
140
|
const middleware = resolveMiddleware(state, obj, tags, checker)
|
|
136
141
|
|
package/src/add/add-workflow.ts
CHANGED
|
@@ -97,7 +97,7 @@ function getWorkflowInvocations(
|
|
|
97
97
|
const stepNameArg = args[0]
|
|
98
98
|
const secondArg = args[1]
|
|
99
99
|
const optionsArg =
|
|
100
|
-
args.length >=
|
|
100
|
+
args.length >= 4 ? args[args.length - 1] : undefined
|
|
101
101
|
|
|
102
102
|
const stepName = extractStringLiteral(stepNameArg, checker)
|
|
103
103
|
const description =
|
|
@@ -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
|
|
|
4
5
|
export type ExtractedFunctionName = {
|
|
5
6
|
pikkuFuncId: string
|
|
@@ -8,6 +9,7 @@ export type ExtractedFunctionName = {
|
|
|
8
9
|
exportedName: string | null
|
|
9
10
|
propertyName: string | null
|
|
10
11
|
isHelper: boolean
|
|
12
|
+
version: number | null
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
export function makeContextBasedId(
|
|
@@ -40,6 +42,7 @@ export function extractFunctionName(
|
|
|
40
42
|
propertyName: null,
|
|
41
43
|
explicitName: null,
|
|
42
44
|
isHelper: false,
|
|
45
|
+
version: null,
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
const workflowHelpers = new Set([
|
|
@@ -143,18 +146,7 @@ export function extractFunctionName(
|
|
|
143
146
|
// Check for object with 'name' property in first argument
|
|
144
147
|
const firstArg = args[0]
|
|
145
148
|
if (firstArg && ts.isObjectLiteralExpression(firstArg)) {
|
|
146
|
-
|
|
147
|
-
if (
|
|
148
|
-
ts.isPropertyAssignment(prop) &&
|
|
149
|
-
ts.isIdentifier(prop.name) &&
|
|
150
|
-
prop.name.text === 'override' &&
|
|
151
|
-
ts.isStringLiteral(prop.initializer)
|
|
152
|
-
) {
|
|
153
|
-
// Priority 1: Object with override property
|
|
154
|
-
result.explicitName = prop.initializer.text
|
|
155
|
-
break
|
|
156
|
-
}
|
|
157
|
-
}
|
|
149
|
+
extractOverrideAndVersion(firstArg, result)
|
|
158
150
|
}
|
|
159
151
|
|
|
160
152
|
// Special handling for pikkuSessionlessFunc pattern - use the arrow function directly
|
|
@@ -367,21 +359,9 @@ export function extractFunctionName(
|
|
|
367
359
|
ts.isIdentifier(decl.initializer.expression) &&
|
|
368
360
|
decl.initializer.expression.text.startsWith('pikku')
|
|
369
361
|
) {
|
|
370
|
-
// Check for object with 'override' property in first argument
|
|
371
362
|
const firstArg = decl.initializer.arguments[0]
|
|
372
363
|
if (firstArg && ts.isObjectLiteralExpression(firstArg)) {
|
|
373
|
-
|
|
374
|
-
if (
|
|
375
|
-
ts.isPropertyAssignment(prop) &&
|
|
376
|
-
ts.isIdentifier(prop.name) &&
|
|
377
|
-
prop.name.text === 'override' &&
|
|
378
|
-
ts.isStringLiteral(prop.initializer)
|
|
379
|
-
) {
|
|
380
|
-
// Priority 1: Object with override property
|
|
381
|
-
result.explicitName = prop.initializer.text
|
|
382
|
-
break
|
|
383
|
-
}
|
|
384
|
-
}
|
|
364
|
+
extractOverrideAndVersion(firstArg, result)
|
|
385
365
|
}
|
|
386
366
|
|
|
387
367
|
if (decl.initializer.expression.text.startsWith('pikku')) {
|
|
@@ -448,18 +428,7 @@ export function extractFunctionName(
|
|
|
448
428
|
else if (ts.isCallExpression(callExpr)) {
|
|
449
429
|
const firstArg = callExpr.arguments[0]
|
|
450
430
|
if (firstArg && ts.isObjectLiteralExpression(firstArg)) {
|
|
451
|
-
|
|
452
|
-
if (
|
|
453
|
-
ts.isPropertyAssignment(prop) &&
|
|
454
|
-
ts.isIdentifier(prop.name) &&
|
|
455
|
-
prop.name.text === 'override' &&
|
|
456
|
-
ts.isStringLiteral(prop.initializer) &&
|
|
457
|
-
!result.explicitName // Only set if not already set
|
|
458
|
-
) {
|
|
459
|
-
result.explicitName = prop.initializer.text
|
|
460
|
-
break
|
|
461
|
-
}
|
|
462
|
-
}
|
|
431
|
+
extractOverrideAndVersion(firstArg, result)
|
|
463
432
|
}
|
|
464
433
|
}
|
|
465
434
|
|
|
@@ -474,6 +443,10 @@ export function extractFunctionName(
|
|
|
474
443
|
result.pikkuFuncId = `__temp_${randomUUID()}`
|
|
475
444
|
}
|
|
476
445
|
|
|
446
|
+
if (result.version !== null) {
|
|
447
|
+
result.pikkuFuncId = formatVersionedId(result.pikkuFuncId, result.version)
|
|
448
|
+
}
|
|
449
|
+
|
|
477
450
|
return result
|
|
478
451
|
}
|
|
479
452
|
|
|
@@ -539,3 +512,29 @@ export function isNamedExport(
|
|
|
539
512
|
|
|
540
513
|
return false
|
|
541
514
|
}
|
|
515
|
+
|
|
516
|
+
function extractOverrideAndVersion(
|
|
517
|
+
objLiteral: ts.ObjectLiteralExpression,
|
|
518
|
+
result: ExtractedFunctionName
|
|
519
|
+
): void {
|
|
520
|
+
for (const prop of objLiteral.properties) {
|
|
521
|
+
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
|
|
522
|
+
if (
|
|
523
|
+
prop.name.text === 'override' &&
|
|
524
|
+
ts.isStringLiteral(prop.initializer) &&
|
|
525
|
+
!result.explicitName
|
|
526
|
+
) {
|
|
527
|
+
result.explicitName = prop.initializer.text
|
|
528
|
+
} else if (
|
|
529
|
+
prop.name.text === 'version' &&
|
|
530
|
+
ts.isNumericLiteral(prop.initializer) &&
|
|
531
|
+
result.version === null
|
|
532
|
+
) {
|
|
533
|
+
const parsed = Number(prop.initializer.text)
|
|
534
|
+
if (Number.isInteger(parsed) && parsed >= 1) {
|
|
535
|
+
result.version = parsed
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { test, describe } from 'node:test'
|
|
2
|
+
import { strict as assert } from 'node:assert'
|
|
3
|
+
import * as ts from 'typescript'
|
|
4
|
+
import { extractDescription } from './extract-node-value'
|
|
5
|
+
|
|
6
|
+
const createChecker = (source: string) => {
|
|
7
|
+
const sourceFile = ts.createSourceFile(
|
|
8
|
+
'test.ts',
|
|
9
|
+
source,
|
|
10
|
+
ts.ScriptTarget.Latest,
|
|
11
|
+
true,
|
|
12
|
+
ts.ScriptKind.TS
|
|
13
|
+
)
|
|
14
|
+
const host = ts.createCompilerHost({})
|
|
15
|
+
const originalGetSourceFile = host.getSourceFile
|
|
16
|
+
host.getSourceFile = (fileName, target) => {
|
|
17
|
+
if (fileName === 'test.ts') return sourceFile
|
|
18
|
+
return originalGetSourceFile.call(host, fileName, target)
|
|
19
|
+
}
|
|
20
|
+
const program = ts.createProgram(['test.ts'], {}, host)
|
|
21
|
+
return { checker: program.getTypeChecker(), sourceFile }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const findObjectLiteral = (
|
|
25
|
+
node: ts.Node
|
|
26
|
+
): ts.ObjectLiteralExpression | undefined => {
|
|
27
|
+
if (ts.isObjectLiteralExpression(node)) return node
|
|
28
|
+
let result: ts.ObjectLiteralExpression | undefined
|
|
29
|
+
ts.forEachChild(node, (child) => {
|
|
30
|
+
if (!result) result = findObjectLiteral(child)
|
|
31
|
+
})
|
|
32
|
+
return result
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
describe('extractDescription', () => {
|
|
36
|
+
test('returns null when node is undefined', () => {
|
|
37
|
+
const { checker } = createChecker('')
|
|
38
|
+
assert.equal(extractDescription(undefined, checker), null)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
test('extracts string literal description', () => {
|
|
42
|
+
const { checker, sourceFile } = createChecker(
|
|
43
|
+
`const opts = { description: 'my step' }`
|
|
44
|
+
)
|
|
45
|
+
const obj = findObjectLiteral(sourceFile)!
|
|
46
|
+
assert.equal(extractDescription(obj, checker), 'my step')
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test('returns null for non-literal description value without crashing', () => {
|
|
50
|
+
const { checker, sourceFile } = createChecker(
|
|
51
|
+
`const name = 'test'; const data = { description: name + ' addon' }`
|
|
52
|
+
)
|
|
53
|
+
const objs: ts.ObjectLiteralExpression[] = []
|
|
54
|
+
const visit = (node: ts.Node) => {
|
|
55
|
+
if (ts.isObjectLiteralExpression(node)) objs.push(node)
|
|
56
|
+
ts.forEachChild(node, visit)
|
|
57
|
+
}
|
|
58
|
+
ts.forEachChild(sourceFile, visit)
|
|
59
|
+
const dataObj = objs[objs.length - 1]!
|
|
60
|
+
assert.equal(extractDescription(dataObj, checker), null)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test('returns null for non-object node', () => {
|
|
64
|
+
const { checker, sourceFile } = createChecker(`const x = 42`)
|
|
65
|
+
assert.equal(extractDescription(sourceFile, checker), null)
|
|
66
|
+
})
|
|
67
|
+
})
|
|
@@ -110,7 +110,11 @@ export function extractDescription(
|
|
|
110
110
|
if (!optionsNode || !ts.isObjectLiteralExpression(optionsNode)) {
|
|
111
111
|
return null
|
|
112
112
|
}
|
|
113
|
-
|
|
113
|
+
try {
|
|
114
|
+
return extractPropertyString(optionsNode, 'description', checker)
|
|
115
|
+
} catch {
|
|
116
|
+
return null
|
|
117
|
+
}
|
|
114
118
|
}
|
|
115
119
|
|
|
116
120
|
/**
|
|
@@ -730,11 +730,15 @@ export function filterInspectorState(
|
|
|
730
730
|
}
|
|
731
731
|
}
|
|
732
732
|
|
|
733
|
-
// Post-filter version expansion:
|
|
733
|
+
// Post-filter version expansion: when an unversioned base name is matched,
|
|
734
|
+
// include all its versions. Specific version matches (e.g. analyzeData@v1)
|
|
735
|
+
// do NOT expand to include other versions.
|
|
734
736
|
const includedBaseNames = new Set<string>()
|
|
735
737
|
for (const funcId of filteredState.serviceAggregation.usedFunctions) {
|
|
736
|
-
const { baseName } = parseVersionedId(funcId)
|
|
737
|
-
|
|
738
|
+
const { baseName, version } = parseVersionedId(funcId)
|
|
739
|
+
if (version === null) {
|
|
740
|
+
includedBaseNames.add(baseName)
|
|
741
|
+
}
|
|
738
742
|
}
|
|
739
743
|
if (includedBaseNames.size > 0) {
|
|
740
744
|
for (const funcId of Object.keys(state.functions.meta)) {
|