@pikku/inspector 0.11.0 → 0.11.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -2
- package/dist/add/add-channel.js +11 -10
- package/dist/add/add-file-with-factory.js +10 -10
- package/dist/add/add-forge-credential.d.ts +8 -0
- package/dist/add/add-forge-credential.js +77 -0
- package/dist/add/add-forge-node.d.ts +7 -0
- package/dist/add/add-forge-node.js +77 -0
- package/dist/add/add-functions.js +158 -51
- package/dist/add/add-http-route.js +28 -4
- package/dist/add/add-mcp-prompt.js +6 -5
- package/dist/add/add-mcp-resource.js +6 -5
- package/dist/add/add-mcp-tool.js +6 -5
- package/dist/add/add-middleware.js +1 -1
- package/dist/add/add-permission.js +1 -1
- package/dist/add/add-queue-worker.js +6 -5
- package/dist/add/add-rpc-invocations.d.ts +3 -0
- package/dist/add/add-rpc-invocations.js +51 -25
- package/dist/add/add-schedule.js +5 -4
- package/dist/add/add-workflow-graph.d.ts +6 -0
- package/dist/add/add-workflow-graph.js +659 -0
- package/dist/add/add-workflow.d.ts +1 -1
- package/dist/add/add-workflow.js +191 -69
- package/dist/error-codes.d.ts +3 -0
- package/dist/error-codes.js +3 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +3 -0
- package/dist/inspector.js +29 -9
- package/dist/types.d.ts +47 -8
- package/dist/utils/extract-function-name.js +7 -7
- package/dist/utils/extract-function-node.d.ts +10 -0
- package/dist/utils/extract-function-node.js +38 -0
- package/dist/utils/extract-node-value.d.ts +8 -0
- package/dist/utils/extract-node-value.js +24 -0
- package/dist/utils/extract-service-metadata.d.ts +19 -0
- package/dist/utils/extract-service-metadata.js +244 -0
- package/dist/utils/get-files-and-methods.d.ts +3 -3
- package/dist/utils/get-files-and-methods.js +3 -3
- package/dist/utils/get-property-value.d.ts +14 -6
- package/dist/utils/get-property-value.js +55 -43
- package/dist/utils/post-process.d.ts +9 -0
- package/dist/utils/post-process.js +30 -3
- package/dist/utils/serialize-inspector-state.d.ts +42 -6
- package/dist/utils/serialize-inspector-state.js +36 -10
- package/dist/utils/workflow/dsl/deserialize-dsl-workflow.d.ts +24 -0
- package/dist/utils/workflow/dsl/deserialize-dsl-workflow.js +898 -0
- package/dist/utils/workflow/dsl/extract-dsl-workflow.d.ts +17 -0
- package/dist/utils/workflow/dsl/extract-dsl-workflow.js +1284 -0
- package/dist/utils/workflow/dsl/index.d.ts +7 -0
- package/dist/utils/workflow/dsl/index.js +7 -0
- package/dist/utils/workflow/dsl/patterns.d.ts +60 -0
- package/dist/utils/workflow/dsl/patterns.js +218 -0
- package/dist/utils/workflow/dsl/validation.d.ts +30 -0
- package/dist/utils/workflow/dsl/validation.js +142 -0
- package/dist/utils/workflow/graph/convert-dsl-to-graph.d.ts +13 -0
- package/dist/utils/workflow/graph/convert-dsl-to-graph.js +316 -0
- package/dist/utils/workflow/graph/index.d.ts +6 -0
- package/dist/utils/workflow/graph/index.js +6 -0
- package/dist/utils/workflow/graph/serialize-workflow-graph.d.ts +43 -0
- package/dist/utils/workflow/graph/serialize-workflow-graph.js +152 -0
- package/dist/utils/workflow/graph/workflow-graph.types.d.ts +229 -0
- package/dist/utils/workflow/graph/workflow-graph.types.js +38 -0
- package/dist/utils/write-service-metadata.d.ts +13 -0
- package/dist/utils/write-service-metadata.js +37 -0
- package/dist/visit.js +8 -2
- package/package.json +16 -4
- package/src/add/add-channel.ts +37 -17
- package/src/add/add-file-with-factory.ts +10 -10
- package/src/add/add-forge-credential.ts +119 -0
- package/src/add/add-forge-node.ts +132 -0
- package/src/add/add-functions.ts +199 -69
- package/src/add/add-http-route.ts +34 -5
- package/src/add/add-mcp-prompt.ts +11 -7
- package/src/add/add-mcp-resource.ts +11 -7
- package/src/add/add-mcp-tool.ts +11 -7
- package/src/add/add-middleware.ts +1 -1
- package/src/add/add-permission.ts +1 -1
- package/src/add/add-queue-worker.ts +11 -12
- package/src/add/add-rpc-invocations.ts +61 -31
- package/src/add/add-schedule.ts +10 -5
- package/src/add/add-workflow-graph.ts +864 -0
- package/src/add/add-workflow.ts +212 -116
- package/src/error-codes.ts +3 -0
- package/src/index.ts +12 -0
- package/src/inspector.ts +36 -10
- package/src/types.ts +43 -9
- package/src/utils/extract-function-name.ts +7 -7
- package/src/utils/extract-function-node.ts +58 -0
- package/src/utils/extract-node-value.ts +31 -0
- package/src/utils/extract-service-metadata.ts +353 -0
- package/src/utils/filter-inspector-state.test.ts +3 -3
- package/src/utils/filter-utils.test.ts +45 -51
- package/src/utils/get-files-and-methods.ts +11 -11
- package/src/utils/get-property-value.ts +67 -53
- package/src/utils/permissions.test.ts +3 -3
- package/src/utils/post-process.ts +56 -3
- package/src/utils/serialize-inspector-state.ts +67 -19
- package/src/utils/test-data/inspector-state.json +9 -9
- package/src/utils/workflow/dsl/deserialize-dsl-workflow.ts +1180 -0
- package/src/utils/workflow/dsl/extract-dsl-workflow.ts +1608 -0
- package/src/utils/workflow/dsl/index.ts +11 -0
- package/src/utils/workflow/dsl/patterns.ts +279 -0
- package/src/utils/workflow/dsl/validation.ts +180 -0
- package/src/utils/workflow/graph/convert-dsl-to-graph.ts +415 -0
- package/src/utils/workflow/graph/index.ts +6 -0
- package/src/utils/workflow/graph/serialize-workflow-graph.ts +223 -0
- package/src/utils/workflow/graph/workflow-graph.types.ts +280 -0
- package/src/utils/write-service-metadata.ts +51 -0
- package/src/visit.ts +9 -3
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as ts from 'typescript';
|
|
2
|
+
/**
|
|
3
|
+
* Extracts the actual function node from a pikkuFunc/pikkuWorkflowFunc call
|
|
4
|
+
* Handles both direct function form and config object form { func: ... }
|
|
5
|
+
*/
|
|
6
|
+
export declare function extractFunctionNode(firstArg: ts.Expression, checker: ts.TypeChecker): {
|
|
7
|
+
funcNode: ts.Node;
|
|
8
|
+
resolvedFunc: ts.Node | null;
|
|
9
|
+
isDirectFunction: boolean;
|
|
10
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import * as ts from 'typescript';
|
|
2
|
+
import { getPropertyAssignmentInitializer, resolveFunctionDeclaration, } from './type-utils.js';
|
|
3
|
+
/**
|
|
4
|
+
* Extracts the actual function node from a pikkuFunc/pikkuWorkflowFunc call
|
|
5
|
+
* Handles both direct function form and config object form { func: ... }
|
|
6
|
+
*/
|
|
7
|
+
export function extractFunctionNode(firstArg, checker) {
|
|
8
|
+
let funcNode = firstArg;
|
|
9
|
+
let isDirectFunction = true;
|
|
10
|
+
// Check if first argument is a config object with 'func' property
|
|
11
|
+
if (ts.isObjectLiteralExpression(firstArg)) {
|
|
12
|
+
isDirectFunction = false;
|
|
13
|
+
const funcInitializer = getPropertyAssignmentInitializer(firstArg, 'func', true, checker);
|
|
14
|
+
if (funcInitializer) {
|
|
15
|
+
funcNode = funcInitializer;
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
// Return the original node if no func property found
|
|
19
|
+
// Caller should handle validation
|
|
20
|
+
funcNode = firstArg;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
// Resolve identifier to get the actual function node
|
|
24
|
+
if (ts.isIdentifier(funcNode)) {
|
|
25
|
+
const symbol = checker.getSymbolAtLocation(funcNode);
|
|
26
|
+
const decl = symbol?.valueDeclaration || symbol?.declarations?.[0];
|
|
27
|
+
if (decl && ts.isVariableDeclaration(decl) && decl.initializer) {
|
|
28
|
+
funcNode = decl.initializer;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// Resolve function declaration for deeper analysis
|
|
32
|
+
const resolvedFunc = resolveFunctionDeclaration(funcNode, checker);
|
|
33
|
+
return {
|
|
34
|
+
funcNode,
|
|
35
|
+
resolvedFunc,
|
|
36
|
+
isDirectFunction,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -22,3 +22,11 @@ export declare function extractNumberLiteral(node: ts.Node): number | null;
|
|
|
22
22
|
* Returns the extracted value or null if not found/cannot extract
|
|
23
23
|
*/
|
|
24
24
|
export declare function extractPropertyString(objNode: ts.ObjectLiteralExpression, propertyName: string, checker: ts.TypeChecker): string | null;
|
|
25
|
+
/**
|
|
26
|
+
* Extract description from options object
|
|
27
|
+
*/
|
|
28
|
+
export declare function extractDescription(optionsNode: ts.Node | undefined, checker: ts.TypeChecker): string | null;
|
|
29
|
+
/**
|
|
30
|
+
* Extract duration value (number or string)
|
|
31
|
+
*/
|
|
32
|
+
export declare function extractDuration(node: ts.Node, checker: ts.TypeChecker): string | number | null;
|
|
@@ -77,3 +77,27 @@ export function extractPropertyString(objNode, propertyName, checker) {
|
|
|
77
77
|
}
|
|
78
78
|
return null;
|
|
79
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Extract description from options object
|
|
82
|
+
*/
|
|
83
|
+
export function extractDescription(optionsNode, checker) {
|
|
84
|
+
if (!optionsNode || !ts.isObjectLiteralExpression(optionsNode)) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
return extractPropertyString(optionsNode, 'description', checker);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Extract duration value (number or string)
|
|
91
|
+
*/
|
|
92
|
+
export function extractDuration(node, checker) {
|
|
93
|
+
const numValue = extractNumberLiteral(node);
|
|
94
|
+
if (numValue !== null) {
|
|
95
|
+
return numValue;
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
return extractStringLiteral(node, checker);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as ts from 'typescript';
|
|
2
|
+
export interface ServiceMetadata {
|
|
3
|
+
name: string;
|
|
4
|
+
summary: string;
|
|
5
|
+
description: string;
|
|
6
|
+
package: string;
|
|
7
|
+
path: string;
|
|
8
|
+
version: string;
|
|
9
|
+
interface: string;
|
|
10
|
+
expandedProperties: Record<string, string>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Extract metadata for a service from its TypeScript declaration
|
|
14
|
+
*/
|
|
15
|
+
export declare function extractServiceMetadata(serviceName: string, type: ts.Type, checker: ts.TypeChecker, rootDir: string): ServiceMetadata | null;
|
|
16
|
+
/**
|
|
17
|
+
* Extract metadata for all services in a type
|
|
18
|
+
*/
|
|
19
|
+
export declare function extractAllServiceMetadata(servicesType: ts.Type, checker: ts.TypeChecker, rootDir: string): ServiceMetadata[];
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import * as ts from 'typescript';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
/**
|
|
5
|
+
* Extract JSDoc comment information from a TypeScript node
|
|
6
|
+
*/
|
|
7
|
+
function extractJSDoc(node) {
|
|
8
|
+
const jsDocTags = ts.getJSDocTags(node);
|
|
9
|
+
const jsDocComments = ts.getJSDocCommentsAndTags(node);
|
|
10
|
+
let summary = '';
|
|
11
|
+
let description = '';
|
|
12
|
+
const summaryTag = jsDocTags.find((tag) => tag.tagName.text === 'summary');
|
|
13
|
+
if (summaryTag && summaryTag.comment) {
|
|
14
|
+
summary =
|
|
15
|
+
typeof summaryTag.comment === 'string'
|
|
16
|
+
? summaryTag.comment
|
|
17
|
+
: summaryTag.comment.map((c) => c.text).join('');
|
|
18
|
+
}
|
|
19
|
+
for (const comment of jsDocComments) {
|
|
20
|
+
if (ts.isJSDoc(comment) && comment.comment) {
|
|
21
|
+
const commentText = typeof comment.comment === 'string'
|
|
22
|
+
? comment.comment
|
|
23
|
+
: comment.comment.map((c) => c.text).join('');
|
|
24
|
+
if (!summary && commentText) {
|
|
25
|
+
const lines = commentText
|
|
26
|
+
.split('\n')
|
|
27
|
+
.map((l) => l.trim())
|
|
28
|
+
.filter(Boolean);
|
|
29
|
+
if (lines.length > 0) {
|
|
30
|
+
summary = lines[0];
|
|
31
|
+
if (lines.length > 1) {
|
|
32
|
+
description = lines.slice(1).join('\n').trim();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
description = commentText;
|
|
38
|
+
}
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return { summary, description };
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Serialize a TypeScript type to a string representation
|
|
46
|
+
*/
|
|
47
|
+
function serializeTypeToString(node, sourceFile, checker) {
|
|
48
|
+
const nodeSourceFile = node.getSourceFile();
|
|
49
|
+
if (ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node)) {
|
|
50
|
+
return node.getText(nodeSourceFile);
|
|
51
|
+
}
|
|
52
|
+
if (ts.isClassDeclaration(node)) {
|
|
53
|
+
return serializePublicClassMembers(node, nodeSourceFile, checker);
|
|
54
|
+
}
|
|
55
|
+
const type = checker.getTypeAtLocation(node);
|
|
56
|
+
return checker.typeToString(type, node, ts.TypeFormatFlags.NoTruncation);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Extract public members from a class and serialize them
|
|
60
|
+
*/
|
|
61
|
+
function serializePublicClassMembers(classNode, sourceFile, checker) {
|
|
62
|
+
const className = classNode.name?.text || 'UnnamedClass';
|
|
63
|
+
const publicMembers = [];
|
|
64
|
+
for (const member of classNode.members) {
|
|
65
|
+
const modifiers = ts.canHaveModifiers(member)
|
|
66
|
+
? ts.getModifiers(member)
|
|
67
|
+
: undefined;
|
|
68
|
+
const isPublic = !modifiers?.some((mod) => mod.kind === ts.SyntaxKind.PrivateKeyword ||
|
|
69
|
+
mod.kind === ts.SyntaxKind.ProtectedKeyword);
|
|
70
|
+
if (!isPublic)
|
|
71
|
+
continue;
|
|
72
|
+
if (ts.isMethodDeclaration(member) ||
|
|
73
|
+
ts.isPropertyDeclaration(member) ||
|
|
74
|
+
ts.isConstructorDeclaration(member)) {
|
|
75
|
+
const memberSignature = getMemberSignature(member, sourceFile, checker);
|
|
76
|
+
if (memberSignature) {
|
|
77
|
+
publicMembers.push(memberSignature);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return `class ${className} {\n ${publicMembers.join('\n ')}\n}`;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Extract a clean signature for a class member (without implementation)
|
|
85
|
+
*/
|
|
86
|
+
function getMemberSignature(member, sourceFile, checker) {
|
|
87
|
+
if (ts.isPropertyDeclaration(member)) {
|
|
88
|
+
const name = member.name.getText(sourceFile);
|
|
89
|
+
const type = member.type ? member.type.getText(sourceFile) : 'any';
|
|
90
|
+
const optional = member.questionToken ? '?' : '';
|
|
91
|
+
return `${name}${optional}: ${type};`;
|
|
92
|
+
}
|
|
93
|
+
if (ts.isMethodDeclaration(member)) {
|
|
94
|
+
const name = member.name.getText(sourceFile);
|
|
95
|
+
const typeParams = member.typeParameters
|
|
96
|
+
? `<${member.typeParameters.map((tp) => tp.getText(sourceFile)).join(', ')}>`
|
|
97
|
+
: '';
|
|
98
|
+
const params = member.parameters
|
|
99
|
+
.map((p) => p.getText(sourceFile))
|
|
100
|
+
.join(', ');
|
|
101
|
+
const returnType = member.type ? member.type.getText(sourceFile) : 'void';
|
|
102
|
+
const optional = member.questionToken ? '?' : '';
|
|
103
|
+
return `${name}${optional}${typeParams}(${params}): ${returnType};`;
|
|
104
|
+
}
|
|
105
|
+
if (ts.isConstructorDeclaration(member)) {
|
|
106
|
+
const params = member.parameters
|
|
107
|
+
.map((p) => p.getText(sourceFile))
|
|
108
|
+
.join(', ');
|
|
109
|
+
return `constructor(${params});`;
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Find the nearest package.json and extract package name and version
|
|
115
|
+
*/
|
|
116
|
+
function getPackageInfo(filePath) {
|
|
117
|
+
let currentDir = path.dirname(filePath);
|
|
118
|
+
const root = path.parse(currentDir).root;
|
|
119
|
+
while (currentDir !== root) {
|
|
120
|
+
const packageJsonPath = path.join(currentDir, 'package.json');
|
|
121
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
122
|
+
try {
|
|
123
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
124
|
+
return {
|
|
125
|
+
packageName: packageJson.name || 'unknown',
|
|
126
|
+
version: packageJson.version || '0.0.0',
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
// If we can't parse the package.json, continue searching
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
currentDir = path.dirname(currentDir);
|
|
134
|
+
}
|
|
135
|
+
return { packageName: 'unknown', version: '0.0.0' };
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Expand a type to show all its properties including inherited ones
|
|
139
|
+
* Returns a Record mapping property names to their type strings
|
|
140
|
+
*/
|
|
141
|
+
function expandInterfaceProperties(type, checker, maxDepth = 2, currentDepth = 0, visited = new Set()) {
|
|
142
|
+
const result = {};
|
|
143
|
+
if (visited.has(type) || currentDepth >= maxDepth) {
|
|
144
|
+
return result;
|
|
145
|
+
}
|
|
146
|
+
visited.add(type);
|
|
147
|
+
const properties = type.getProperties();
|
|
148
|
+
for (const prop of properties) {
|
|
149
|
+
const propName = prop.getName();
|
|
150
|
+
const propDecl = prop.valueDeclaration || prop.declarations?.[0];
|
|
151
|
+
if (!propDecl)
|
|
152
|
+
continue;
|
|
153
|
+
try {
|
|
154
|
+
const propType = checker.getTypeOfSymbolAtLocation(prop, propDecl);
|
|
155
|
+
const isOptional = !!(prop.flags & ts.SymbolFlags.Optional);
|
|
156
|
+
let typeString = checker.typeToString(propType, propDecl, ts.TypeFormatFlags.NoTruncation |
|
|
157
|
+
ts.TypeFormatFlags.UseFullyQualifiedType);
|
|
158
|
+
if (isOptional && !typeString.includes('undefined')) {
|
|
159
|
+
typeString = `${typeString} | undefined`;
|
|
160
|
+
}
|
|
161
|
+
result[propName] = typeString;
|
|
162
|
+
}
|
|
163
|
+
catch (err) {
|
|
164
|
+
result[propName] = 'any';
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return result;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Extract metadata for a service from its TypeScript declaration
|
|
171
|
+
*/
|
|
172
|
+
export function extractServiceMetadata(serviceName, type, checker, rootDir) {
|
|
173
|
+
const property = type.getProperty(serviceName);
|
|
174
|
+
if (!property) {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
const declaration = property.valueDeclaration || property.declarations?.[0];
|
|
178
|
+
if (!declaration) {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
const sourceFile = declaration.getSourceFile();
|
|
182
|
+
const filePath = sourceFile.fileName;
|
|
183
|
+
const serviceType = checker.getTypeOfSymbolAtLocation(property, declaration);
|
|
184
|
+
let typeDeclaration = null;
|
|
185
|
+
if (serviceType.symbol) {
|
|
186
|
+
const typeDecl = serviceType.symbol.valueDeclaration ||
|
|
187
|
+
serviceType.symbol.declarations?.[0];
|
|
188
|
+
if (typeDecl &&
|
|
189
|
+
(ts.isInterfaceDeclaration(typeDecl) ||
|
|
190
|
+
ts.isClassDeclaration(typeDecl) ||
|
|
191
|
+
ts.isTypeAliasDeclaration(typeDecl))) {
|
|
192
|
+
typeDeclaration = typeDecl;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
let summary = '';
|
|
196
|
+
let description = '';
|
|
197
|
+
if (typeDeclaration) {
|
|
198
|
+
const jsDoc = extractJSDoc(typeDeclaration);
|
|
199
|
+
summary = jsDoc.summary;
|
|
200
|
+
description = jsDoc.description;
|
|
201
|
+
}
|
|
202
|
+
else if (ts.isPropertySignature(declaration) ||
|
|
203
|
+
ts.isPropertyDeclaration(declaration)) {
|
|
204
|
+
const jsDoc = extractJSDoc(declaration);
|
|
205
|
+
summary = jsDoc.summary;
|
|
206
|
+
description = jsDoc.description;
|
|
207
|
+
}
|
|
208
|
+
let interfaceString = '';
|
|
209
|
+
if (typeDeclaration) {
|
|
210
|
+
interfaceString = serializeTypeToString(typeDeclaration, sourceFile, checker);
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
interfaceString = checker.typeToString(serviceType, declaration, ts.TypeFormatFlags.NoTruncation);
|
|
214
|
+
}
|
|
215
|
+
const { packageName, version } = getPackageInfo(filePath);
|
|
216
|
+
const relativePath = path.relative(rootDir, filePath);
|
|
217
|
+
const expandedProperties = expandInterfaceProperties(serviceType, checker);
|
|
218
|
+
return {
|
|
219
|
+
name: serviceName,
|
|
220
|
+
summary,
|
|
221
|
+
description,
|
|
222
|
+
package: packageName,
|
|
223
|
+
path: relativePath,
|
|
224
|
+
version,
|
|
225
|
+
interface: interfaceString,
|
|
226
|
+
expandedProperties,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Extract metadata for all services in a type
|
|
231
|
+
*/
|
|
232
|
+
export function extractAllServiceMetadata(servicesType, checker, rootDir) {
|
|
233
|
+
const metadata = [];
|
|
234
|
+
const serviceNames = servicesType
|
|
235
|
+
.getProperties()
|
|
236
|
+
.map((prop) => prop.getName());
|
|
237
|
+
for (const serviceName of serviceNames) {
|
|
238
|
+
const serviceMeta = extractServiceMetadata(serviceName, servicesType, checker, rootDir);
|
|
239
|
+
if (serviceMeta) {
|
|
240
|
+
metadata.push(serviceMeta);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return metadata;
|
|
244
|
+
}
|
|
@@ -7,15 +7,15 @@ interface Meta {
|
|
|
7
7
|
}
|
|
8
8
|
export type FilesAndMethods = {
|
|
9
9
|
userSessionType: Meta;
|
|
10
|
-
|
|
10
|
+
wireServicesType: Meta;
|
|
11
11
|
singletonServicesType: Meta;
|
|
12
12
|
pikkuConfigType: Meta;
|
|
13
13
|
pikkuConfigFactory: Meta;
|
|
14
14
|
singletonServicesFactory: Meta;
|
|
15
|
-
|
|
15
|
+
wireServicesFactory: Meta;
|
|
16
16
|
};
|
|
17
17
|
export type FilesAndMethodsErrors = Map<string, PathToNameAndType>;
|
|
18
|
-
export declare const getFilesAndMethods: ({ singletonServicesTypeImportMap,
|
|
18
|
+
export declare const getFilesAndMethods: ({ singletonServicesTypeImportMap, wireServicesTypeImportMap, userSessionTypeImportMap, configTypeImportMap, wireServicesFactories, singletonServicesFactories, configFactories, }: InspectorState, { configFileType, userSessionType, singletonServicesFactoryType, wireServicesFactoryType, }?: InspectorOptions["types"]) => {
|
|
19
19
|
result: Partial<FilesAndMethods>;
|
|
20
20
|
errors: FilesAndMethodsErrors;
|
|
21
21
|
};
|
|
@@ -46,16 +46,16 @@ const getMetaTypes = (type, map, desiredType, errors) => {
|
|
|
46
46
|
}
|
|
47
47
|
return;
|
|
48
48
|
};
|
|
49
|
-
export const getFilesAndMethods = ({ singletonServicesTypeImportMap,
|
|
49
|
+
export const getFilesAndMethods = ({ singletonServicesTypeImportMap, wireServicesTypeImportMap, userSessionTypeImportMap, configTypeImportMap, wireServicesFactories, singletonServicesFactories, configFactories, }, { configFileType, userSessionType, singletonServicesFactoryType, wireServicesFactoryType, } = {}) => {
|
|
50
50
|
const errors = new Map();
|
|
51
51
|
const result = {
|
|
52
52
|
userSessionType: getMetaTypes('CoreUserSession', userSessionTypeImportMap, userSessionType, errors),
|
|
53
53
|
singletonServicesType: getMetaTypes('CoreSingletonServices', singletonServicesTypeImportMap, undefined, errors),
|
|
54
|
-
|
|
54
|
+
wireServicesType: getMetaTypes('CoreServices', wireServicesTypeImportMap, undefined, errors),
|
|
55
55
|
pikkuConfigType: getMetaTypes('CoreConfig', configTypeImportMap, undefined, errors),
|
|
56
56
|
pikkuConfigFactory: getMetaTypes('CoreConfig', configFactories, configFileType, errors),
|
|
57
57
|
singletonServicesFactory: getMetaTypes('CreateSingletonServices', singletonServicesFactories, singletonServicesFactoryType, errors),
|
|
58
|
-
|
|
58
|
+
wireServicesFactory: getMetaTypes('CreateWireServices', wireServicesFactories, wireServicesFactoryType, errors),
|
|
59
59
|
};
|
|
60
60
|
return { result, errors };
|
|
61
61
|
};
|
|
@@ -1,12 +1,20 @@
|
|
|
1
|
-
import { PikkuDocs } from '@pikku/core';
|
|
2
1
|
import * as ts from 'typescript';
|
|
3
2
|
import { ErrorCode } from '../error-codes.js';
|
|
4
|
-
export declare const getPropertyValue: (obj: ts.ObjectLiteralExpression, propertyName: string) => string | string[] | null |
|
|
3
|
+
export declare const getPropertyValue: (obj: ts.ObjectLiteralExpression, propertyName: string) => string | string[] | null | boolean;
|
|
5
4
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Extracts common wire metadata (title, tags, summary, description, errors) directly from an object
|
|
6
|
+
* @param obj - The TypeScript object literal expression to extract metadata from
|
|
7
|
+
* @param wiringType - The type of wiring (e.g., 'HTTP route', 'Channel', 'Queue worker')
|
|
8
|
+
* @param wiringName - The name/identifier of the wiring (e.g., route path, channel name)
|
|
8
9
|
* @param logger - Optional logger instance; if not provided, uses console.error
|
|
10
|
+
* @returns Object containing the common wire metadata fields
|
|
9
11
|
*/
|
|
10
|
-
export declare const
|
|
12
|
+
export declare const getCommonWireMetaData: (obj: ts.ObjectLiteralExpression, wiringType: string, wiringName: string | null, logger?: {
|
|
11
13
|
critical: (code: ErrorCode, message: string) => void;
|
|
12
|
-
}) =>
|
|
14
|
+
}) => {
|
|
15
|
+
title?: string;
|
|
16
|
+
tags?: string[];
|
|
17
|
+
summary?: string;
|
|
18
|
+
description?: string;
|
|
19
|
+
errors?: string[];
|
|
20
|
+
};
|
|
@@ -19,35 +19,6 @@ export const getPropertyValue = (obj, propertyName) => {
|
|
|
19
19
|
.filter((item) => item !== null); // Filter non-null and assert type
|
|
20
20
|
return stringArray.length > 0 ? stringArray : null;
|
|
21
21
|
}
|
|
22
|
-
// Special handling for 'docs' -> expect RouteDocs
|
|
23
|
-
if (propertyName === 'docs' && ts.isObjectLiteralExpression(initializer)) {
|
|
24
|
-
const docs = {};
|
|
25
|
-
initializer.properties.forEach((prop) => {
|
|
26
|
-
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
|
|
27
|
-
const propName = prop.name.text;
|
|
28
|
-
if (propName === 'summary' && ts.isStringLiteral(prop.initializer)) {
|
|
29
|
-
docs.summary = prop.initializer.text;
|
|
30
|
-
}
|
|
31
|
-
else if (propName === 'description' &&
|
|
32
|
-
ts.isStringLiteral(prop.initializer)) {
|
|
33
|
-
docs.description = prop.initializer.text;
|
|
34
|
-
}
|
|
35
|
-
else if (propName === 'tags' &&
|
|
36
|
-
ts.isArrayLiteralExpression(prop.initializer)) {
|
|
37
|
-
docs.tags = prop.initializer.elements
|
|
38
|
-
.filter(ts.isStringLiteral)
|
|
39
|
-
.map((element) => element.text);
|
|
40
|
-
}
|
|
41
|
-
else if (propName === 'errors' &&
|
|
42
|
-
ts.isArrayLiteralExpression(prop.initializer)) {
|
|
43
|
-
docs.errors = prop.initializer.elements
|
|
44
|
-
.filter(ts.isIdentifier)
|
|
45
|
-
.map((element) => element.text);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
return docs;
|
|
50
|
-
}
|
|
51
22
|
// booleans -> true/false
|
|
52
23
|
if (initializer.kind === ts.SyntaxKind.TrueKeyword) {
|
|
53
24
|
return true;
|
|
@@ -66,21 +37,62 @@ export const getPropertyValue = (obj, propertyName) => {
|
|
|
66
37
|
return null;
|
|
67
38
|
};
|
|
68
39
|
/**
|
|
69
|
-
*
|
|
70
|
-
*
|
|
40
|
+
* Extracts common wire metadata (title, tags, summary, description, errors) directly from an object
|
|
41
|
+
* @param obj - The TypeScript object literal expression to extract metadata from
|
|
42
|
+
* @param wiringType - The type of wiring (e.g., 'HTTP route', 'Channel', 'Queue worker')
|
|
43
|
+
* @param wiringName - The name/identifier of the wiring (e.g., route path, channel name)
|
|
71
44
|
* @param logger - Optional logger instance; if not provided, uses console.error
|
|
45
|
+
* @returns Object containing the common wire metadata fields
|
|
72
46
|
*/
|
|
73
|
-
export const
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
47
|
+
export const getCommonWireMetaData = (obj, wiringType, wiringName, logger) => {
|
|
48
|
+
const metadata = {};
|
|
49
|
+
obj.properties.forEach((prop) => {
|
|
50
|
+
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
|
|
51
|
+
const propName = prop.name.text;
|
|
52
|
+
if (propName === 'title' && ts.isStringLiteral(prop.initializer)) {
|
|
53
|
+
metadata.title = prop.initializer.text;
|
|
54
|
+
}
|
|
55
|
+
else if (propName === 'summary' &&
|
|
56
|
+
ts.isStringLiteral(prop.initializer)) {
|
|
57
|
+
metadata.summary = prop.initializer.text;
|
|
58
|
+
}
|
|
59
|
+
else if (propName === 'description' &&
|
|
60
|
+
ts.isStringLiteral(prop.initializer)) {
|
|
61
|
+
metadata.description = prop.initializer.text;
|
|
62
|
+
}
|
|
63
|
+
else if (propName === 'tags') {
|
|
64
|
+
if (ts.isArrayLiteralExpression(prop.initializer)) {
|
|
65
|
+
metadata.tags = prop.initializer.elements
|
|
66
|
+
.filter(ts.isStringLiteral)
|
|
67
|
+
.map((element) => element.text);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
const errorMsg = `${wiringType} '${wiringName}' has invalid 'tags' property - must be an array of strings.`;
|
|
71
|
+
if (logger) {
|
|
72
|
+
logger.critical(ErrorCode.INVALID_TAGS_TYPE, errorMsg);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
console.error(errorMsg);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
else if (propName === 'errors') {
|
|
80
|
+
if (ts.isArrayLiteralExpression(prop.initializer)) {
|
|
81
|
+
metadata.errors = prop.initializer.elements
|
|
82
|
+
.filter(ts.isIdentifier)
|
|
83
|
+
.map((element) => element.text);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
const errorMsg = `${wiringType} '${wiringName}' has invalid 'errors' property - must be an array of error identifiers.`;
|
|
87
|
+
if (logger) {
|
|
88
|
+
logger.critical(ErrorCode.INVALID_TAGS_TYPE, errorMsg);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
console.error(errorMsg);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
82
95
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
return Array.isArray(tagsValue) ? tagsValue : undefined;
|
|
96
|
+
});
|
|
97
|
+
return metadata;
|
|
86
98
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as ts from 'typescript';
|
|
1
2
|
import { InspectorState } from '../types.js';
|
|
2
3
|
import { MiddlewareMetadata, PermissionMetadata } from '@pikku/core';
|
|
3
4
|
/**
|
|
@@ -14,3 +15,11 @@ export declare function extractWireNames(list?: MiddlewareMetadata[] | Permissio
|
|
|
14
15
|
* in the add-* methods during AST traversal for efficiency.
|
|
15
16
|
*/
|
|
16
17
|
export declare function aggregateRequiredServices(state: InspectorState | Omit<InspectorState, 'typesLookup'>): void;
|
|
18
|
+
/**
|
|
19
|
+
* Extract service interface metadata for all user-defined services.
|
|
20
|
+
* This extracts metadata for services in SingletonServices and Services types
|
|
21
|
+
* to generate documentation for AI consumption.
|
|
22
|
+
*
|
|
23
|
+
* Must be called after aggregateRequiredServices() to ensure types are loaded.
|
|
24
|
+
*/
|
|
25
|
+
export declare function extractServiceInterfaceMetadata(state: InspectorState | Omit<InspectorState, 'typesLookup'>, checker: ts.TypeChecker): void;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { extractTypeKeys } from './type-utils.js';
|
|
2
|
+
import { extractAllServiceMetadata, } from './extract-service-metadata.js';
|
|
2
3
|
/**
|
|
3
4
|
* Helper to extract wire-level middleware/permission names from metadata.
|
|
4
5
|
* Only extracts type:'wire' variants (individual middleware/permissions).
|
|
@@ -60,9 +61,9 @@ function extractAllServices(state) {
|
|
|
60
61
|
const servicesTypes = state.typesLookup.get('Services');
|
|
61
62
|
if (servicesTypes && servicesTypes.length > 0) {
|
|
62
63
|
const allServiceNames = extractTypeKeys(servicesTypes[0]);
|
|
63
|
-
//
|
|
64
|
+
// Wire services are those in Services but not in SingletonServices
|
|
64
65
|
const singletonSet = new Set(state.serviceAggregation.allSingletonServices);
|
|
65
|
-
state.serviceAggregation.
|
|
66
|
+
state.serviceAggregation.allWireServices = allServiceNames
|
|
66
67
|
.filter((name) => !singletonSet.has(name))
|
|
67
68
|
.sort();
|
|
68
69
|
}
|
|
@@ -152,7 +153,7 @@ export function aggregateRequiredServices(state) {
|
|
|
152
153
|
expandAndAddGroupServices(resourceMeta.permissions, state, addServices, false);
|
|
153
154
|
}
|
|
154
155
|
// 5. Services from session service factories
|
|
155
|
-
for (const singletonServices of state.
|
|
156
|
+
for (const singletonServices of state.wireServicesMeta.values()) {
|
|
156
157
|
singletonServices.forEach((service) => {
|
|
157
158
|
if (!internalServices.has(service)) {
|
|
158
159
|
requiredServices.add(service);
|
|
@@ -160,3 +161,29 @@ export function aggregateRequiredServices(state) {
|
|
|
160
161
|
});
|
|
161
162
|
}
|
|
162
163
|
}
|
|
164
|
+
/**
|
|
165
|
+
* Extract service interface metadata for all user-defined services.
|
|
166
|
+
* This extracts metadata for services in SingletonServices and Services types
|
|
167
|
+
* to generate documentation for AI consumption.
|
|
168
|
+
*
|
|
169
|
+
* Must be called after aggregateRequiredServices() to ensure types are loaded.
|
|
170
|
+
*/
|
|
171
|
+
export function extractServiceInterfaceMetadata(state, checker) {
|
|
172
|
+
if (!('typesLookup' in state)) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const allMetadata = [];
|
|
176
|
+
const singletonServicesTypes = state.typesLookup.get('SingletonServices');
|
|
177
|
+
if (singletonServicesTypes && singletonServicesTypes.length > 0) {
|
|
178
|
+
const singletonMeta = extractAllServiceMetadata(singletonServicesTypes[0], checker, state.rootDir);
|
|
179
|
+
allMetadata.push(...singletonMeta);
|
|
180
|
+
}
|
|
181
|
+
const servicesTypes = state.typesLookup.get('Services');
|
|
182
|
+
if (servicesTypes && servicesTypes.length > 0) {
|
|
183
|
+
const wireServicesMeta = extractAllServiceMetadata(servicesTypes[0], checker, state.rootDir);
|
|
184
|
+
const singletonNames = new Set(state.serviceAggregation.allSingletonServices);
|
|
185
|
+
const uniqueWireServices = wireServicesMeta.filter((meta) => !singletonNames.has(meta.name));
|
|
186
|
+
allMetadata.push(...uniqueWireServices);
|
|
187
|
+
}
|
|
188
|
+
state.serviceMetadata = allMetadata;
|
|
189
|
+
}
|