@pikku/inspector 0.10.2 → 0.11.1
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 +23 -0
- package/dist/add/add-channel.js +11 -10
- package/dist/add/add-file-with-factory.js +10 -10
- package/dist/add/add-functions.js +65 -44
- package/dist/add/add-http-route.js +5 -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-schedule.js +5 -4
- package/dist/add/add-workflow.d.ts +6 -0
- package/dist/add/add-workflow.js +178 -0
- package/dist/error-codes.d.ts +5 -1
- package/dist/error-codes.js +5 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/inspector.js +13 -5
- package/dist/types.d.ts +27 -9
- 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 +32 -0
- package/dist/utils/extract-node-value.js +103 -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 +13 -6
- package/dist/utils/get-property-value.js +51 -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 +21 -4
- package/dist/utils/serialize-inspector-state.js +18 -8
- package/dist/utils/type-utils.d.ts +4 -0
- package/dist/utils/type-utils.js +55 -0
- package/dist/utils/write-service-metadata.d.ts +13 -0
- package/dist/utils/write-service-metadata.js +37 -0
- package/dist/visit.js +4 -2
- package/dist/workflow/extract-simple-workflow.d.ts +15 -0
- package/dist/workflow/extract-simple-workflow.js +803 -0
- package/dist/workflow/patterns.d.ts +39 -0
- package/dist/workflow/patterns.js +138 -0
- package/dist/workflow/validation.d.ts +28 -0
- package/dist/workflow/validation.js +124 -0
- package/package.json +4 -4
- package/src/add/add-channel.ts +37 -17
- package/src/add/add-file-with-factory.ts +10 -10
- package/src/add/add-functions.ts +81 -57
- package/src/add/add-http-route.ts +10 -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-schedule.ts +10 -5
- package/src/add/add-workflow.ts +241 -0
- package/src/error-codes.ts +6 -0
- package/src/index.ts +2 -0
- package/src/inspector.ts +19 -5
- package/src/types.ts +24 -9
- package/src/utils/extract-function-node.ts +58 -0
- package/src/utils/extract-node-value.ts +132 -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 +60 -53
- package/src/utils/permissions.test.ts +3 -3
- package/src/utils/post-process.ts +56 -3
- package/src/utils/serialize-inspector-state.ts +37 -15
- package/src/utils/test-data/inspector-state.json +13 -9
- package/src/utils/type-utils.ts +69 -0
- package/src/utils/write-service-metadata.ts +51 -0
- package/src/visit.ts +5 -3
- package/src/workflow/extract-simple-workflow.ts +1035 -0
- package/src/workflow/patterns.ts +182 -0
- package/src/workflow/validation.ts +153 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/src/add/add-mcp-prompt.ts.tmp +0 -0
- package/src/add/add-mcp-resource.ts.tmp +0 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import * as ts from 'typescript';
|
|
2
|
+
import { extractFunctionName } from '../utils/extract-function-name.js';
|
|
3
|
+
import { extractFunctionNode } from '../utils/extract-function-node.js';
|
|
4
|
+
import { ErrorCode } from '../error-codes.js';
|
|
5
|
+
import { extractStringLiteral, isStringLike, isFunctionLike, extractDescription, extractDuration, } from '../utils/extract-node-value.js';
|
|
6
|
+
import { extractSimpleWorkflow } from '../workflow/extract-simple-workflow.js';
|
|
7
|
+
import { getCommonWireMetaData } from '../utils/get-property-value.js';
|
|
8
|
+
/**
|
|
9
|
+
* Scan for workflow.do(), workflow.sleep(), and workflow.cancel() calls to extract workflow steps
|
|
10
|
+
*/
|
|
11
|
+
function getWorkflowInvocations(node, checker, state, workflowName, steps) {
|
|
12
|
+
// Look for property access expressions: workflow.do or workflow.sleep
|
|
13
|
+
if (ts.isPropertyAccessExpression(node)) {
|
|
14
|
+
const { name } = node;
|
|
15
|
+
// Check if this is accessing 'do' or 'sleep' property
|
|
16
|
+
if (name.text === 'do' || name.text === 'sleep' || name.text === 'cancel') {
|
|
17
|
+
// Check if the parent is a call expression
|
|
18
|
+
const parent = node.parent;
|
|
19
|
+
if (ts.isCallExpression(parent) && parent.expression === node) {
|
|
20
|
+
const args = parent.arguments;
|
|
21
|
+
if (name.text === 'do' && args.length >= 2) {
|
|
22
|
+
// workflow.do(stepName, rpcName|fn, data?, options?)
|
|
23
|
+
const stepNameArg = args[0];
|
|
24
|
+
const secondArg = args[1];
|
|
25
|
+
const optionsArg = args.length >= 3 ? args[args.length - 1] : undefined;
|
|
26
|
+
const stepName = extractStringLiteral(stepNameArg, checker);
|
|
27
|
+
const description = extractDescription(optionsArg, checker) ?? undefined;
|
|
28
|
+
// Determine form by checking 2nd argument type
|
|
29
|
+
if (isStringLike(secondArg, checker)) {
|
|
30
|
+
// RPC form: workflow.do(stepName, rpcName, data, options?)
|
|
31
|
+
const rpcName = extractStringLiteral(secondArg, checker);
|
|
32
|
+
steps.push({
|
|
33
|
+
type: 'rpc',
|
|
34
|
+
stepName,
|
|
35
|
+
rpcName,
|
|
36
|
+
});
|
|
37
|
+
state.rpc.invokedFunctions.add(rpcName);
|
|
38
|
+
}
|
|
39
|
+
else if (isFunctionLike(secondArg)) {
|
|
40
|
+
// Inline form: workflow.do(stepName, fn, options?)
|
|
41
|
+
steps.push({
|
|
42
|
+
type: 'inline',
|
|
43
|
+
stepName: stepName || '<dynamic>',
|
|
44
|
+
description: description || '<dynamic>',
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
else if (name.text === 'sleep' && args.length >= 2) {
|
|
49
|
+
// workflow.sleep(stepName, duration)
|
|
50
|
+
const stepNameArg = args[0];
|
|
51
|
+
const durationArg = args[1];
|
|
52
|
+
const stepName = extractStringLiteral(stepNameArg, checker);
|
|
53
|
+
const duration = extractDuration(durationArg, checker);
|
|
54
|
+
steps.push({
|
|
55
|
+
type: 'sleep',
|
|
56
|
+
stepName: stepName || '<dynamic>',
|
|
57
|
+
duration: duration || '<dynamic>',
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
else if (name.text === 'cancel') {
|
|
61
|
+
// workflow.cancel(reason?)
|
|
62
|
+
steps.push({
|
|
63
|
+
type: 'cancel',
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Don't recurse into nested functions - only look at top-level workflow calls
|
|
70
|
+
ts.forEachChild(node, (child) => {
|
|
71
|
+
if (ts.isFunctionDeclaration(child) ||
|
|
72
|
+
ts.isFunctionExpression(child) ||
|
|
73
|
+
ts.isArrowFunction(child)) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
getWorkflowInvocations(child, checker, state, workflowName, steps);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Inspector for pikkuWorkflow() and pikkuSimpleWorkflow() calls
|
|
81
|
+
* Detects workflow registration and extracts metadata
|
|
82
|
+
*/
|
|
83
|
+
export const addWorkflow = (logger, node, checker, state) => {
|
|
84
|
+
if (!ts.isCallExpression(node)) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const args = node.arguments;
|
|
88
|
+
const firstArg = args[0];
|
|
89
|
+
const expression = node.expression;
|
|
90
|
+
if (!ts.isIdentifier(expression)) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
let wrapperType = null;
|
|
94
|
+
if (expression.text === 'pikkuWorkflowFunc') {
|
|
95
|
+
wrapperType = 'regular';
|
|
96
|
+
}
|
|
97
|
+
else if (expression.text === 'pikkuSimpleWorkflowFunc') {
|
|
98
|
+
wrapperType = 'simple';
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (!firstArg) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
// Extract workflow name and metadata using same logic as add-functions
|
|
107
|
+
const { pikkuFuncName, name, exportedName } = extractFunctionName(node, checker, state.rootDir);
|
|
108
|
+
const workflowName = exportedName || name;
|
|
109
|
+
if (!workflowName) {
|
|
110
|
+
logger.critical(ErrorCode.MISSING_NAME, `Could not determine workflow name from export.`);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
// Extract the function node (either direct function or from config.func)
|
|
114
|
+
const { funcNode, resolvedFunc } = extractFunctionNode(firstArg, checker);
|
|
115
|
+
// Extract metadata if using object form
|
|
116
|
+
let tags;
|
|
117
|
+
let summary;
|
|
118
|
+
let description;
|
|
119
|
+
let errors;
|
|
120
|
+
if (ts.isObjectLiteralExpression(firstArg)) {
|
|
121
|
+
const metadata = getCommonWireMetaData(firstArg, 'Workflow', workflowName, logger);
|
|
122
|
+
tags = metadata.tags;
|
|
123
|
+
summary = metadata.summary;
|
|
124
|
+
description = metadata.description;
|
|
125
|
+
errors = metadata.errors;
|
|
126
|
+
}
|
|
127
|
+
// Validate that we got a valid function
|
|
128
|
+
if (ts.isObjectLiteralExpression(firstArg) &&
|
|
129
|
+
(!funcNode || funcNode === firstArg)) {
|
|
130
|
+
logger.critical(ErrorCode.MISSING_FUNC, `No valid 'func' property for workflow '${workflowName}'.`);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (!resolvedFunc) {
|
|
134
|
+
logger.critical(ErrorCode.MISSING_FUNC, `Could not resolve workflow function for '${workflowName}'.`);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
// Track workflow file for wiring generation
|
|
138
|
+
if (exportedName) {
|
|
139
|
+
state.workflows.files.set(pikkuFuncName, {
|
|
140
|
+
path: node.getSourceFile().fileName,
|
|
141
|
+
exportedName,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
let steps = [];
|
|
145
|
+
let simple = undefined;
|
|
146
|
+
// Try simple workflow extraction first
|
|
147
|
+
// Pass the whole CallExpression node so findWorkflowFunction can find the arrow function
|
|
148
|
+
const result = extractSimpleWorkflow(node, checker);
|
|
149
|
+
if (result.status === 'ok' && result.steps) {
|
|
150
|
+
// Simple extraction succeeded
|
|
151
|
+
steps = result.steps;
|
|
152
|
+
simple = true;
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
// Simple extraction failed
|
|
156
|
+
if (wrapperType === 'simple') {
|
|
157
|
+
// For pikkuSimpleWorkflowFunc, this is a critical error
|
|
158
|
+
logger.critical(ErrorCode.INVALID_SIMPLE_WORKFLOW, `Workflow '${workflowName}' uses pikkuSimpleWorkflowFunc but does not conform to simple workflow DSL:\n${result.reason || 'Unknown error'}`);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
// For pikkuWorkflowFunc, fall back to basic extraction
|
|
163
|
+
logger.debug(`Workflow '${workflowName}' could not be extracted as simple workflow: ${result.reason || 'Unknown error'}. Falling back to basic extraction.`);
|
|
164
|
+
simple = false;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
getWorkflowInvocations(resolvedFunc, checker, state, workflowName, steps);
|
|
168
|
+
state.workflows.meta[workflowName] = {
|
|
169
|
+
pikkuFuncName,
|
|
170
|
+
workflowName,
|
|
171
|
+
steps,
|
|
172
|
+
simple,
|
|
173
|
+
summary,
|
|
174
|
+
description,
|
|
175
|
+
errors,
|
|
176
|
+
tags,
|
|
177
|
+
};
|
|
178
|
+
};
|
package/dist/error-codes.d.ts
CHANGED
|
@@ -17,6 +17,9 @@ export declare enum ErrorCode {
|
|
|
17
17
|
MISSING_QUEUE_NAME = "PKU384",
|
|
18
18
|
MISSING_CHANNEL_NAME = "PKU400",
|
|
19
19
|
CLI_CLIENTSIDE_RENDERER_HAS_SERVICES = "PKU672",
|
|
20
|
+
DYNAMIC_STEP_NAME = "PKU529",
|
|
21
|
+
WORKFLOW_ORCHESTRATOR_NOT_CONFIGURED = "PKU600",
|
|
22
|
+
INVALID_SIMPLE_WORKFLOW = "PKU641",
|
|
20
23
|
CONFIG_TYPE_NOT_FOUND = "PKU426",
|
|
21
24
|
CONFIG_TYPE_UNDEFINED = "PKU427",
|
|
22
25
|
SCHEMA_NO_ROOT = "PKU431",
|
|
@@ -31,5 +34,6 @@ export declare enum ErrorCode {
|
|
|
31
34
|
PERMISSION_HANDLER_INVALID = "PKU835",
|
|
32
35
|
PERMISSION_TAG_INVALID = "PKU836",
|
|
33
36
|
PERMISSION_EMPTY_ARRAY = "PKU937",
|
|
34
|
-
PERMISSION_PATTERN_INVALID = "PKU975"
|
|
37
|
+
PERMISSION_PATTERN_INVALID = "PKU975",
|
|
38
|
+
WORKFLOW_MULTI_QUEUE_NOT_SUPPORTED = "PKU901"
|
|
35
39
|
}
|
package/dist/error-codes.js
CHANGED
|
@@ -19,6 +19,9 @@ export var ErrorCode;
|
|
|
19
19
|
ErrorCode["MISSING_QUEUE_NAME"] = "PKU384";
|
|
20
20
|
ErrorCode["MISSING_CHANNEL_NAME"] = "PKU400";
|
|
21
21
|
ErrorCode["CLI_CLIENTSIDE_RENDERER_HAS_SERVICES"] = "PKU672";
|
|
22
|
+
ErrorCode["DYNAMIC_STEP_NAME"] = "PKU529";
|
|
23
|
+
ErrorCode["WORKFLOW_ORCHESTRATOR_NOT_CONFIGURED"] = "PKU600";
|
|
24
|
+
ErrorCode["INVALID_SIMPLE_WORKFLOW"] = "PKU641";
|
|
22
25
|
// Configuration errors
|
|
23
26
|
ErrorCode["CONFIG_TYPE_NOT_FOUND"] = "PKU426";
|
|
24
27
|
ErrorCode["CONFIG_TYPE_UNDEFINED"] = "PKU427";
|
|
@@ -37,4 +40,6 @@ export var ErrorCode;
|
|
|
37
40
|
ErrorCode["PERMISSION_TAG_INVALID"] = "PKU836";
|
|
38
41
|
ErrorCode["PERMISSION_EMPTY_ARRAY"] = "PKU937";
|
|
39
42
|
ErrorCode["PERMISSION_PATTERN_INVALID"] = "PKU975";
|
|
43
|
+
// Feature Flag
|
|
44
|
+
ErrorCode["WORKFLOW_MULTI_QUEUE_NOT_SUPPORTED"] = "PKU901";
|
|
40
45
|
})(ErrorCode || (ErrorCode = {}));
|
package/dist/index.d.ts
CHANGED
|
@@ -8,3 +8,5 @@ export { ErrorCode } from './error-codes.js';
|
|
|
8
8
|
export { serializeInspectorState, deserializeInspectorState, } from './utils/serialize-inspector-state.js';
|
|
9
9
|
export type { SerializableInspectorState } from './utils/serialize-inspector-state.js';
|
|
10
10
|
export { filterInspectorState } from './utils/filter-inspector-state.js';
|
|
11
|
+
export { writeAllServiceMetadata } from './utils/write-service-metadata.js';
|
|
12
|
+
export type { ServiceMetadata } from './utils/extract-service-metadata.js';
|
package/dist/index.js
CHANGED
|
@@ -3,3 +3,4 @@ export { getFilesAndMethods } from './utils/get-files-and-methods.js';
|
|
|
3
3
|
export { ErrorCode } from './error-codes.js';
|
|
4
4
|
export { serializeInspectorState, deserializeInspectorState, } from './utils/serialize-inspector-state.js';
|
|
5
5
|
export { filterInspectorState } from './utils/filter-inspector-state.js';
|
|
6
|
+
export { writeAllServiceMetadata } from './utils/write-service-metadata.js';
|
package/dist/inspector.js
CHANGED
|
@@ -4,7 +4,7 @@ import { visitSetup, visitRoutes } from './visit.js';
|
|
|
4
4
|
import { TypesMap } from './types-map.js';
|
|
5
5
|
import { getFilesAndMethods } from './utils/get-files-and-methods.js';
|
|
6
6
|
import { findCommonAncestor } from './utils/find-root-dir.js';
|
|
7
|
-
import { aggregateRequiredServices } from './utils/post-process.js';
|
|
7
|
+
import { aggregateRequiredServices, extractServiceInterfaceMetadata, } from './utils/post-process.js';
|
|
8
8
|
/**
|
|
9
9
|
* Creates an initial/empty inspector state with all required properties initialized
|
|
10
10
|
* @param rootDir - The root directory for the project
|
|
@@ -14,12 +14,12 @@ export function getInitialInspectorState(rootDir) {
|
|
|
14
14
|
return {
|
|
15
15
|
rootDir,
|
|
16
16
|
singletonServicesTypeImportMap: new Map(),
|
|
17
|
-
|
|
17
|
+
wireServicesTypeImportMap: new Map(),
|
|
18
18
|
userSessionTypeImportMap: new Map(),
|
|
19
19
|
configTypeImportMap: new Map(),
|
|
20
20
|
singletonServicesFactories: new Map(),
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
wireServicesFactories: new Map(),
|
|
22
|
+
wireServicesMeta: new Map(),
|
|
23
23
|
configFactories: new Map(),
|
|
24
24
|
filesAndMethods: {},
|
|
25
25
|
filesAndMethodsErrors: new Map(),
|
|
@@ -56,6 +56,10 @@ export function getInitialInspectorState(rootDir) {
|
|
|
56
56
|
meta: {},
|
|
57
57
|
files: new Set(),
|
|
58
58
|
},
|
|
59
|
+
workflows: {
|
|
60
|
+
meta: {},
|
|
61
|
+
files: new Map(),
|
|
62
|
+
},
|
|
59
63
|
rpc: {
|
|
60
64
|
internalMeta: {},
|
|
61
65
|
internalFiles: new Map(),
|
|
@@ -90,8 +94,9 @@ export function getInitialInspectorState(rootDir) {
|
|
|
90
94
|
usedMiddleware: new Set(),
|
|
91
95
|
usedPermissions: new Set(),
|
|
92
96
|
allSingletonServices: [],
|
|
93
|
-
|
|
97
|
+
allWireServices: [],
|
|
94
98
|
},
|
|
99
|
+
serviceMetadata: [],
|
|
95
100
|
};
|
|
96
101
|
}
|
|
97
102
|
export const inspect = (logger, routeFiles, options = {}) => {
|
|
@@ -140,6 +145,9 @@ export const inspect = (logger, routeFiles, options = {}) => {
|
|
|
140
145
|
const startAggregate = performance.now();
|
|
141
146
|
aggregateRequiredServices(state);
|
|
142
147
|
logger.debug(`Aggregate required services completed in ${(performance.now() - startAggregate).toFixed(2)}ms`);
|
|
148
|
+
const startServiceMeta = performance.now();
|
|
149
|
+
extractServiceInterfaceMetadata(state, checker);
|
|
150
|
+
logger.debug(`Extract service metadata completed in ${(performance.now() - startServiceMeta).toFixed(2)}ms`);
|
|
143
151
|
}
|
|
144
152
|
return state;
|
|
145
153
|
};
|
package/dist/types.d.ts
CHANGED
|
@@ -2,7 +2,8 @@ import * as ts from 'typescript';
|
|
|
2
2
|
import { ChannelsMeta } from '@pikku/core/channel';
|
|
3
3
|
import { HTTPWiringsMeta } from '@pikku/core/http';
|
|
4
4
|
import { ScheduledTasksMeta } from '@pikku/core/scheduler';
|
|
5
|
-
import {
|
|
5
|
+
import { QueueWorkersMeta } from '@pikku/core/queue';
|
|
6
|
+
import { WorkflowsMeta } from '@pikku/core/workflow';
|
|
6
7
|
import { MCPResourceMeta, MCPToolMeta, MCPPromptMeta } from '@pikku/core/mcp';
|
|
7
8
|
import { CLIMeta } from '@pikku/core/cli';
|
|
8
9
|
import { TypesMap } from './types-map.js';
|
|
@@ -91,7 +92,7 @@ export type InspectorOptions = Partial<{
|
|
|
91
92
|
configFileType: string;
|
|
92
93
|
userSessionType: string;
|
|
93
94
|
singletonServicesFactoryType: string;
|
|
94
|
-
|
|
95
|
+
wireServicesFactoryType: string;
|
|
95
96
|
}>;
|
|
96
97
|
}>;
|
|
97
98
|
export interface InspectorLogger {
|
|
@@ -110,7 +111,7 @@ export interface InspectorFilesAndMethods {
|
|
|
110
111
|
type: string;
|
|
111
112
|
typePath: string;
|
|
112
113
|
};
|
|
113
|
-
|
|
114
|
+
wireServicesType?: {
|
|
114
115
|
file: string;
|
|
115
116
|
variable: string;
|
|
116
117
|
type: string;
|
|
@@ -140,7 +141,7 @@ export interface InspectorFilesAndMethods {
|
|
|
140
141
|
type: string;
|
|
141
142
|
typePath: string;
|
|
142
143
|
};
|
|
143
|
-
|
|
144
|
+
wireServicesFactory?: {
|
|
144
145
|
file: string;
|
|
145
146
|
variable: string;
|
|
146
147
|
type: string;
|
|
@@ -150,12 +151,12 @@ export interface InspectorFilesAndMethods {
|
|
|
150
151
|
export interface InspectorState {
|
|
151
152
|
rootDir: string;
|
|
152
153
|
singletonServicesTypeImportMap: PathToNameAndType;
|
|
153
|
-
|
|
154
|
+
wireServicesTypeImportMap: PathToNameAndType;
|
|
154
155
|
userSessionTypeImportMap: PathToNameAndType;
|
|
155
156
|
configTypeImportMap: PathToNameAndType;
|
|
156
157
|
singletonServicesFactories: PathToNameAndType;
|
|
157
|
-
|
|
158
|
-
|
|
158
|
+
wireServicesFactories: PathToNameAndType;
|
|
159
|
+
wireServicesMeta: Map<string, string[]>;
|
|
159
160
|
configFactories: PathToNameAndType;
|
|
160
161
|
filesAndMethods: InspectorFilesAndMethods;
|
|
161
162
|
filesAndMethodsErrors: Map<string, PathToNameAndType>;
|
|
@@ -168,9 +169,16 @@ export interface InspectorState {
|
|
|
168
169
|
files: Set<string>;
|
|
169
170
|
};
|
|
170
171
|
queueWorkers: {
|
|
171
|
-
meta:
|
|
172
|
+
meta: QueueWorkersMeta;
|
|
172
173
|
files: Set<string>;
|
|
173
174
|
};
|
|
175
|
+
workflows: {
|
|
176
|
+
meta: WorkflowsMeta;
|
|
177
|
+
files: Map<string, {
|
|
178
|
+
path: string;
|
|
179
|
+
exportedName: string;
|
|
180
|
+
}>;
|
|
181
|
+
};
|
|
174
182
|
rpc: {
|
|
175
183
|
internalMeta: Record<string, string>;
|
|
176
184
|
internalFiles: Map<string, {
|
|
@@ -202,6 +210,16 @@ export interface InspectorState {
|
|
|
202
210
|
usedMiddleware: Set<string>;
|
|
203
211
|
usedPermissions: Set<string>;
|
|
204
212
|
allSingletonServices: string[];
|
|
205
|
-
|
|
213
|
+
allWireServices: string[];
|
|
206
214
|
};
|
|
215
|
+
serviceMetadata: Array<{
|
|
216
|
+
name: string;
|
|
217
|
+
summary: string;
|
|
218
|
+
description: string;
|
|
219
|
+
package: string;
|
|
220
|
+
path: string;
|
|
221
|
+
version: string;
|
|
222
|
+
interface: string;
|
|
223
|
+
expandedProperties: Record<string, string>;
|
|
224
|
+
}>;
|
|
207
225
|
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as ts from 'typescript';
|
|
2
|
+
/**
|
|
3
|
+
* Extract string literal value from a TypeScript node.
|
|
4
|
+
* Handles string literals, template literals (including placeholders),
|
|
5
|
+
* and constant variable references.
|
|
6
|
+
*/
|
|
7
|
+
export declare function extractStringLiteral(node: ts.Node, checker: ts.TypeChecker): string;
|
|
8
|
+
/**
|
|
9
|
+
* Check if node is string-like (string literal or template expression)
|
|
10
|
+
*/
|
|
11
|
+
export declare function isStringLike(node: ts.Node, _checker: ts.TypeChecker): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Check if node is function-like (arrow, function expression, or function declaration)
|
|
14
|
+
*/
|
|
15
|
+
export declare function isFunctionLike(node: ts.Node): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Extract number literal value from a node
|
|
18
|
+
*/
|
|
19
|
+
export declare function extractNumberLiteral(node: ts.Node): number | null;
|
|
20
|
+
/**
|
|
21
|
+
* Extract a property value from an object literal expression
|
|
22
|
+
* Returns the extracted value or null if not found/cannot extract
|
|
23
|
+
*/
|
|
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;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import * as ts from 'typescript';
|
|
2
|
+
/**
|
|
3
|
+
* Extract string literal value from a TypeScript node.
|
|
4
|
+
* Handles string literals, template literals (including placeholders),
|
|
5
|
+
* and constant variable references.
|
|
6
|
+
*/
|
|
7
|
+
export function extractStringLiteral(node, checker) {
|
|
8
|
+
if (ts.isStringLiteral(node)) {
|
|
9
|
+
return node.text;
|
|
10
|
+
}
|
|
11
|
+
if (ts.isNoSubstitutionTemplateLiteral(node)) {
|
|
12
|
+
return node.text;
|
|
13
|
+
}
|
|
14
|
+
if (ts.isTemplateExpression(node)) {
|
|
15
|
+
// reconstruct: `head + ${expr} + middle + ${expr} + tail`
|
|
16
|
+
let result = node.head.text;
|
|
17
|
+
for (const span of node.templateSpans) {
|
|
18
|
+
const exprText = span.expression.getText();
|
|
19
|
+
result += '${' + exprText + '}' + span.literal.text;
|
|
20
|
+
}
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
// Try to evaluate constant identifiers
|
|
24
|
+
if (ts.isIdentifier(node)) {
|
|
25
|
+
const symbol = checker.getSymbolAtLocation(node);
|
|
26
|
+
if (symbol?.valueDeclaration &&
|
|
27
|
+
ts.isVariableDeclaration(symbol.valueDeclaration)) {
|
|
28
|
+
const init = symbol.valueDeclaration.initializer;
|
|
29
|
+
if (init) {
|
|
30
|
+
return extractStringLiteral(init, checker);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
throw new Error('Unable to extract string literal from node');
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Check if node is string-like (string literal or template expression)
|
|
38
|
+
*/
|
|
39
|
+
export function isStringLike(node, _checker) {
|
|
40
|
+
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
// Check if it's a template string with substitutions
|
|
44
|
+
if (ts.isTemplateExpression(node)) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Check if node is function-like (arrow, function expression, or function declaration)
|
|
51
|
+
*/
|
|
52
|
+
export function isFunctionLike(node) {
|
|
53
|
+
return (ts.isArrowFunction(node) ||
|
|
54
|
+
ts.isFunctionExpression(node) ||
|
|
55
|
+
ts.isFunctionDeclaration(node));
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Extract number literal value from a node
|
|
59
|
+
*/
|
|
60
|
+
export function extractNumberLiteral(node) {
|
|
61
|
+
if (ts.isNumericLiteral(node)) {
|
|
62
|
+
return Number(node.text);
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Extract a property value from an object literal expression
|
|
68
|
+
* Returns the extracted value or null if not found/cannot extract
|
|
69
|
+
*/
|
|
70
|
+
export function extractPropertyString(objNode, propertyName, checker) {
|
|
71
|
+
for (const prop of objNode.properties) {
|
|
72
|
+
if (ts.isPropertyAssignment(prop) &&
|
|
73
|
+
ts.isIdentifier(prop.name) &&
|
|
74
|
+
prop.name.text === propertyName) {
|
|
75
|
+
return extractStringLiteral(prop.initializer, checker);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
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[];
|