@pikku/inspector 0.9.6-next.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/add/add-channel.d.ts +5 -1
- package/dist/add/add-channel.js +51 -32
- package/dist/add/add-cli.d.ts +4 -0
- package/dist/add/add-cli.js +128 -23
- package/dist/add/add-file-extends-core-type.js +3 -2
- package/dist/add/add-file-with-factory.d.ts +2 -2
- package/dist/add/add-file-with-factory.js +34 -1
- package/dist/add/add-functions.js +52 -5
- package/dist/add/add-http-route.js +19 -12
- package/dist/add/add-mcp-prompt.js +20 -13
- package/dist/add/add-mcp-resource.js +24 -14
- package/dist/add/add-mcp-tool.js +23 -13
- package/dist/add/add-middleware.js +51 -12
- package/dist/add/add-permission.d.ts +1 -2
- package/dist/add/add-permission.js +275 -19
- package/dist/add/add-queue-worker.js +10 -12
- package/dist/add/add-schedule.js +9 -10
- package/dist/error-codes.d.ts +35 -0
- package/dist/error-codes.js +40 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/inspector.js +20 -1
- package/dist/types.d.ts +31 -3
- package/dist/utils/ensure-function-metadata.d.ts +6 -0
- package/dist/utils/ensure-function-metadata.js +18 -0
- package/dist/utils/extract-function-name.d.ts +2 -2
- package/dist/utils/extract-function-name.js +13 -8
- package/dist/utils/filter-inspector-state.d.ts +6 -0
- package/dist/utils/filter-inspector-state.js +382 -0
- package/dist/utils/filter-utils.d.ts +10 -0
- package/dist/utils/filter-utils.js +66 -2
- package/dist/utils/find-root-dir.d.ts +23 -0
- package/dist/utils/find-root-dir.js +55 -0
- package/dist/utils/get-files-and-methods.d.ts +2 -1
- package/dist/utils/get-files-and-methods.js +2 -1
- package/dist/utils/get-property-value.d.ts +9 -0
- package/dist/utils/get-property-value.js +20 -0
- package/dist/utils/middleware.d.ts +1 -1
- package/dist/utils/middleware.js +7 -7
- package/dist/utils/permissions.d.ts +43 -0
- package/dist/utils/permissions.js +178 -0
- package/dist/utils/post-process.d.ts +16 -0
- package/dist/utils/post-process.js +132 -0
- package/dist/utils/serialize-inspector-state.d.ts +179 -0
- package/dist/utils/serialize-inspector-state.js +170 -0
- package/dist/visit.js +3 -2
- package/package.json +4 -4
- package/src/add/add-channel.ts +92 -40
- package/src/add/add-cli.ts +188 -29
- package/src/add/add-file-extends-core-type.ts +5 -2
- package/src/add/add-file-with-factory.ts +45 -2
- package/src/add/add-functions.ts +60 -5
- package/src/add/add-http-route.ts +46 -21
- package/src/add/add-mcp-prompt.ts +42 -21
- package/src/add/add-mcp-prompt.ts.tmp +0 -0
- package/src/add/add-mcp-resource.ts +50 -24
- package/src/add/add-mcp-resource.ts.tmp +0 -0
- package/src/add/add-mcp-tool.ts +48 -21
- package/src/add/add-middleware.ts +74 -15
- package/src/add/add-permission.ts +364 -22
- package/src/add/add-queue-worker.ts +22 -25
- package/src/add/add-schedule.ts +19 -20
- package/src/error-codes.ts +43 -0
- package/src/index.ts +7 -0
- package/src/inspector.ts +22 -1
- package/src/types.ts +38 -3
- package/src/utils/ensure-function-metadata.ts +24 -0
- package/src/utils/extract-function-name.ts +20 -8
- package/src/utils/filter-inspector-state.test.ts +1433 -0
- package/src/utils/filter-inspector-state.ts +526 -0
- package/src/utils/filter-utils.test.ts +350 -1
- package/src/utils/filter-utils.ts +82 -2
- package/src/utils/find-root-dir.ts +68 -0
- package/src/utils/get-files-and-methods.ts +8 -0
- package/src/utils/get-property-value.ts +27 -0
- package/src/utils/middleware.ts +14 -7
- package/src/utils/permissions.test.ts +327 -0
- package/src/utils/permissions.ts +262 -0
- package/src/utils/post-process.ts +178 -0
- package/src/utils/serialize-inspector-state.ts +375 -0
- package/src/utils/test-data/inspector-state.json +1680 -0
- package/src/visit.ts +4 -2
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,36 +1,292 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
|
-
import { extractFunctionName } from '../utils/extract-function-name.js';
|
|
2
|
+
import { extractFunctionName, isNamedExport, } from '../utils/extract-function-name.js';
|
|
3
3
|
import { extractServicesFromFunction } from '../utils/extract-services.js';
|
|
4
|
+
import { extractPermissionPikkuNames } from '../utils/permissions.js';
|
|
5
|
+
import { getPropertyValue } from '../utils/get-property-value.js';
|
|
6
|
+
import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
|
|
4
7
|
/**
|
|
5
|
-
* Inspect pikkuPermission calls and
|
|
6
|
-
* for tree shaking optimization.
|
|
8
|
+
* Inspect pikkuPermission calls, addPermission calls, and addHTTPPermission calls
|
|
7
9
|
*/
|
|
8
10
|
export const addPermission = (logger, node, checker, state) => {
|
|
9
11
|
if (!ts.isCallExpression(node))
|
|
10
12
|
return;
|
|
11
13
|
const { expression, arguments: args } = node;
|
|
12
|
-
// only handle
|
|
14
|
+
// only handle specific function calls
|
|
13
15
|
if (!ts.isIdentifier(expression)) {
|
|
14
16
|
return;
|
|
15
17
|
}
|
|
16
|
-
|
|
18
|
+
// Handle pikkuPermission(...) - individual permission function definition
|
|
19
|
+
if (expression.text === 'pikkuPermission') {
|
|
20
|
+
const arg = args[0];
|
|
21
|
+
if (!arg)
|
|
22
|
+
return;
|
|
23
|
+
let actualHandler;
|
|
24
|
+
let name;
|
|
25
|
+
let description;
|
|
26
|
+
// Check if using object syntax: pikkuPermission({ func: ..., name: '...', description: '...' })
|
|
27
|
+
if (ts.isObjectLiteralExpression(arg)) {
|
|
28
|
+
// Extract name and description metadata
|
|
29
|
+
const nameValue = getPropertyValue(arg, 'name');
|
|
30
|
+
const descValue = getPropertyValue(arg, 'description');
|
|
31
|
+
name = typeof nameValue === 'string' ? nameValue : undefined;
|
|
32
|
+
description = typeof descValue === 'string' ? descValue : undefined;
|
|
33
|
+
// Extract the func property
|
|
34
|
+
const fnProp = getPropertyAssignmentInitializer(arg, 'func', true, checker);
|
|
35
|
+
if (!fnProp ||
|
|
36
|
+
(!ts.isArrowFunction(fnProp) && !ts.isFunctionExpression(fnProp))) {
|
|
37
|
+
logger.error(`• pikkuPermission object missing required 'func' property.`);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
actualHandler = fnProp;
|
|
41
|
+
}
|
|
42
|
+
else if (ts.isArrowFunction(arg) || ts.isFunctionExpression(arg)) {
|
|
43
|
+
actualHandler = arg;
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
logger.error(`• Handler for pikkuPermission is not a function.`);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const services = extractServicesFromFunction(actualHandler);
|
|
50
|
+
const { pikkuFuncName, exportedName } = extractFunctionName(node, checker, state.rootDir);
|
|
51
|
+
state.permissions.meta[pikkuFuncName] = {
|
|
52
|
+
services,
|
|
53
|
+
sourceFile: node.getSourceFile().fileName,
|
|
54
|
+
position: node.getStart(),
|
|
55
|
+
exportedName,
|
|
56
|
+
name,
|
|
57
|
+
description,
|
|
58
|
+
};
|
|
59
|
+
logger.debug(`• Found permission with services: ${services.services.join(', ')}${name ? ` (name: ${name})` : ''}${description ? ` (description: ${description})` : ''}`);
|
|
17
60
|
return;
|
|
18
61
|
}
|
|
19
|
-
|
|
20
|
-
if (
|
|
62
|
+
// Handle pikkuPermissionFactory(...) - permission factory function
|
|
63
|
+
if (expression.text === 'pikkuPermissionFactory') {
|
|
64
|
+
const factoryNode = args[0];
|
|
65
|
+
if (!factoryNode)
|
|
66
|
+
return;
|
|
67
|
+
if (!ts.isArrowFunction(factoryNode) &&
|
|
68
|
+
!ts.isFunctionExpression(factoryNode)) {
|
|
69
|
+
logger.error(`• Handler for pikkuPermissionFactory is not a function.`);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
// Extract services by looking inside the factory function body
|
|
73
|
+
// The factory should return pikkuPermission(...), so we need to find that call
|
|
74
|
+
// If no wrapper is found, extract from the factory's returned function directly
|
|
75
|
+
let services = { optimized: false, services: [] };
|
|
76
|
+
const findPikkuPermissionCall = (node) => {
|
|
77
|
+
if (ts.isCallExpression(node)) {
|
|
78
|
+
const expr = node.expression;
|
|
79
|
+
if (ts.isIdentifier(expr) && expr.text === 'pikkuPermission') {
|
|
80
|
+
return node;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return ts.forEachChild(node, findPikkuPermissionCall);
|
|
84
|
+
};
|
|
85
|
+
const pikkuPermissionCall = findPikkuPermissionCall(factoryNode);
|
|
86
|
+
if (pikkuPermissionCall && pikkuPermissionCall.arguments[0]) {
|
|
87
|
+
const permissionHandler = pikkuPermissionCall.arguments[0];
|
|
88
|
+
if (ts.isArrowFunction(permissionHandler) ||
|
|
89
|
+
ts.isFunctionExpression(permissionHandler)) {
|
|
90
|
+
services = extractServicesFromFunction(permissionHandler);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
// No pikkuPermission wrapper found - extract from factory's return value directly
|
|
95
|
+
// Factory pattern: (config) => (services, data, session) => { ... }
|
|
96
|
+
if (ts.isArrowFunction(factoryNode) ||
|
|
97
|
+
ts.isFunctionExpression(factoryNode)) {
|
|
98
|
+
const factoryBody = factoryNode.body;
|
|
99
|
+
// Check if the body is an arrow function (direct return)
|
|
100
|
+
if (ts.isArrowFunction(factoryBody) ||
|
|
101
|
+
ts.isFunctionExpression(factoryBody)) {
|
|
102
|
+
services = extractServicesFromFunction(factoryBody);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const { pikkuFuncName, exportedName } = extractFunctionName(node, checker, state.rootDir);
|
|
107
|
+
state.permissions.meta[pikkuFuncName] = {
|
|
108
|
+
services,
|
|
109
|
+
sourceFile: node.getSourceFile().fileName,
|
|
110
|
+
position: node.getStart(),
|
|
111
|
+
exportedName,
|
|
112
|
+
factory: true,
|
|
113
|
+
};
|
|
114
|
+
logger.debug(`• Found permission factory with services: ${services.services.join(', ')}`);
|
|
21
115
|
return;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
116
|
+
}
|
|
117
|
+
// Handle addPermission('tag', [permission1, permission2])
|
|
118
|
+
// Supports two patterns:
|
|
119
|
+
// 1. export const x = () => addPermission('tag', [...]) (factory - tree-shakeable)
|
|
120
|
+
// 2. export const x = addPermission('tag', [...]) (direct - no tree-shaking)
|
|
121
|
+
if (expression.text === 'addPermission') {
|
|
122
|
+
const tagArg = args[0];
|
|
123
|
+
const permissionsArrayArg = args[1];
|
|
124
|
+
if (!tagArg || !permissionsArrayArg)
|
|
125
|
+
return;
|
|
126
|
+
// Extract tag name
|
|
127
|
+
let tag;
|
|
128
|
+
if (ts.isStringLiteral(tagArg)) {
|
|
129
|
+
tag = tagArg.text;
|
|
130
|
+
}
|
|
131
|
+
if (!tag) {
|
|
132
|
+
logger.warn(`• addPermission call without valid tag string`);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
// Check if permissions is a literal array or object
|
|
136
|
+
if (!ts.isArrayLiteralExpression(permissionsArrayArg) &&
|
|
137
|
+
!ts.isObjectLiteralExpression(permissionsArrayArg)) {
|
|
138
|
+
logger.error(`• addPermission('${tag}', ...) must have a literal array or object as second argument`);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
// Extract permission pikkuFuncNames from array
|
|
142
|
+
const permissionNames = extractPermissionPikkuNames(permissionsArrayArg, checker, state.rootDir);
|
|
143
|
+
if (permissionNames.length === 0) {
|
|
144
|
+
logger.warn(`• addPermission('${tag}', ...) has empty permissions array`);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
// Collect services from all permissions in the group
|
|
148
|
+
const allServices = new Set();
|
|
149
|
+
for (const permissionName of permissionNames) {
|
|
150
|
+
const permissionMeta = state.permissions.meta[permissionName];
|
|
151
|
+
if (permissionMeta && permissionMeta.services) {
|
|
152
|
+
for (const service of permissionMeta.services.services) {
|
|
153
|
+
allServices.add(service);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// Check if this call is wrapped in a factory function
|
|
158
|
+
// We need to walk up the tree to see if the parent is: const x = () => addPermission(...)
|
|
159
|
+
let isFactory = false;
|
|
160
|
+
let exportedName = null;
|
|
161
|
+
let parent = node.parent;
|
|
162
|
+
// Check if parent is arrow function: () => addPermission(...)
|
|
163
|
+
if (parent && ts.isArrowFunction(parent)) {
|
|
164
|
+
// Check if arrow function has no parameters
|
|
165
|
+
if (parent.parameters.length === 0) {
|
|
166
|
+
isFactory = true;
|
|
167
|
+
// For factories, we need to check the arrow function's parent for the export name
|
|
168
|
+
// const apiTagPermissions = () => addPermission(...)
|
|
169
|
+
const arrowParent = parent.parent;
|
|
170
|
+
if (arrowParent && ts.isVariableDeclaration(arrowParent)) {
|
|
171
|
+
if (ts.isIdentifier(arrowParent.name)) {
|
|
172
|
+
// Check if it's exported
|
|
173
|
+
if (isNamedExport(arrowParent)) {
|
|
174
|
+
exportedName = arrowParent.name.text;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
// If not a factory, get export name from the call expression itself
|
|
181
|
+
if (!isFactory) {
|
|
182
|
+
const extracted = extractFunctionName(node, checker, state.rootDir);
|
|
183
|
+
exportedName = extracted.exportedName;
|
|
184
|
+
}
|
|
185
|
+
// Log warning if not using factory pattern
|
|
186
|
+
if (!isFactory && exportedName) {
|
|
187
|
+
logger.warn(`• Permission group '${exportedName}' for tag '${tag}' is not wrapped in a factory function. ` +
|
|
188
|
+
`For tree-shaking, use: export const ${exportedName} = () => addPermission('${tag}', [...])`);
|
|
189
|
+
}
|
|
190
|
+
// Store group metadata
|
|
191
|
+
state.permissions.tagPermissions.set(tag, {
|
|
192
|
+
exportName: exportedName,
|
|
193
|
+
sourceFile: node.getSourceFile().fileName,
|
|
194
|
+
position: node.getStart(),
|
|
195
|
+
services: {
|
|
196
|
+
optimized: false,
|
|
197
|
+
services: Array.from(allServices),
|
|
198
|
+
},
|
|
199
|
+
permissionCount: permissionNames.length,
|
|
200
|
+
isFactory,
|
|
201
|
+
});
|
|
202
|
+
logger.debug(`• Found tag permission group: ${tag} -> [${permissionNames.join(', ')}] (${isFactory ? 'factory' : 'direct'})`);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
// Handle addHTTPPermission(pattern, [permission1, permission2])
|
|
206
|
+
// Supports two patterns:
|
|
207
|
+
// 1. export const x = () => addHTTPPermission('*', [...]) (factory - tree-shakeable)
|
|
208
|
+
// 2. export const x = addHTTPPermission('*', [...]) (direct - no tree-shaking)
|
|
209
|
+
if (expression.text === 'addHTTPPermission') {
|
|
210
|
+
const patternArg = args[0];
|
|
211
|
+
const permissionsArrayArg = args[1];
|
|
212
|
+
if (!patternArg || !permissionsArrayArg)
|
|
213
|
+
return;
|
|
214
|
+
// Extract route pattern
|
|
215
|
+
let pattern;
|
|
216
|
+
if (ts.isStringLiteral(patternArg)) {
|
|
217
|
+
pattern = patternArg.text;
|
|
218
|
+
}
|
|
219
|
+
if (!pattern) {
|
|
220
|
+
logger.warn(`• addHTTPPermission call without valid pattern string`);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
// Check if permissions is a literal array or object
|
|
224
|
+
if (!ts.isArrayLiteralExpression(permissionsArrayArg) &&
|
|
225
|
+
!ts.isObjectLiteralExpression(permissionsArrayArg)) {
|
|
226
|
+
logger.error(`• addHTTPPermission('${pattern}', ...) must have a literal array or object as second argument`);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
// Extract permission pikkuFuncNames from array
|
|
230
|
+
const permissionNames = extractPermissionPikkuNames(permissionsArrayArg, checker, state.rootDir);
|
|
231
|
+
if (permissionNames.length === 0) {
|
|
232
|
+
logger.warn(`• addHTTPPermission('${pattern}', ...) has empty permissions array`);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
// Collect services from all permissions in the group
|
|
236
|
+
const allServices = new Set();
|
|
237
|
+
for (const permissionName of permissionNames) {
|
|
238
|
+
const permissionMeta = state.permissions.meta[permissionName];
|
|
239
|
+
if (permissionMeta && permissionMeta.services) {
|
|
240
|
+
for (const service of permissionMeta.services.services) {
|
|
241
|
+
allServices.add(service);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
// Check if this call is wrapped in a factory function
|
|
246
|
+
let isFactory = false;
|
|
247
|
+
let exportedName = null;
|
|
248
|
+
let parent = node.parent;
|
|
249
|
+
// Check if parent is arrow function: () => addHTTPPermission(...)
|
|
250
|
+
if (parent && ts.isArrowFunction(parent)) {
|
|
251
|
+
// Check if arrow function has no parameters
|
|
252
|
+
if (parent.parameters.length === 0) {
|
|
253
|
+
isFactory = true;
|
|
254
|
+
// For factories, we need to check the arrow function's parent for the export name
|
|
255
|
+
// const apiRoutePermissions = () => addHTTPPermission(...)
|
|
256
|
+
const arrowParent = parent.parent;
|
|
257
|
+
if (arrowParent && ts.isVariableDeclaration(arrowParent)) {
|
|
258
|
+
if (ts.isIdentifier(arrowParent.name)) {
|
|
259
|
+
// Check if it's exported
|
|
260
|
+
if (isNamedExport(arrowParent)) {
|
|
261
|
+
exportedName = arrowParent.name.text;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
// If not a factory, get export name from the call expression itself
|
|
268
|
+
if (!isFactory) {
|
|
269
|
+
const extracted = extractFunctionName(node, checker, state.rootDir);
|
|
270
|
+
exportedName = extracted.exportedName;
|
|
271
|
+
}
|
|
272
|
+
// Log warning if not using factory pattern
|
|
273
|
+
if (!isFactory && exportedName) {
|
|
274
|
+
logger.warn(`• HTTP permission group '${exportedName}' for pattern '${pattern}' is not wrapped in a factory function. ` +
|
|
275
|
+
`For tree-shaking, use: export const ${exportedName} = () => addHTTPPermission('${pattern}', [...])`);
|
|
276
|
+
}
|
|
277
|
+
// Store group metadata
|
|
278
|
+
state.http.routePermissions.set(pattern, {
|
|
279
|
+
exportName: exportedName,
|
|
280
|
+
sourceFile: node.getSourceFile().fileName,
|
|
281
|
+
position: node.getStart(),
|
|
282
|
+
services: {
|
|
283
|
+
optimized: false,
|
|
284
|
+
services: Array.from(allServices),
|
|
285
|
+
},
|
|
286
|
+
permissionCount: permissionNames.length,
|
|
287
|
+
isFactory,
|
|
288
|
+
});
|
|
289
|
+
logger.debug(`• Found HTTP route permission group: ${pattern} -> [${permissionNames.join(', ')}] (${isFactory ? 'factory' : 'direct'})`);
|
|
25
290
|
return;
|
|
26
291
|
}
|
|
27
|
-
const services = extractServicesFromFunction(handlerNode);
|
|
28
|
-
const { pikkuFuncName, exportedName } = extractFunctionName(node, checker);
|
|
29
|
-
state.permissions.meta[pikkuFuncName] = {
|
|
30
|
-
services,
|
|
31
|
-
sourceFile: node.getSourceFile().fileName,
|
|
32
|
-
position: node.getStart(),
|
|
33
|
-
exportedName,
|
|
34
|
-
};
|
|
35
|
-
logger.debug(`• Found permission with services: ${services.services.join(', ')}`);
|
|
36
292
|
};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
|
-
import { getPropertyValue } from '../utils/get-property-value.js';
|
|
3
|
-
import { PikkuWiringTypes } from '@pikku/core';
|
|
2
|
+
import { getPropertyValue, getPropertyTags, } from '../utils/get-property-value.js';
|
|
4
3
|
import { extractFunctionName } from '../utils/extract-function-name.js';
|
|
5
4
|
import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
|
|
6
|
-
import { matchesFilters } from '../utils/filter-utils.js';
|
|
7
5
|
import { resolveMiddleware } from '../utils/middleware.js';
|
|
6
|
+
import { extractWireNames } from '../utils/post-process.js';
|
|
7
|
+
import { ErrorCode } from '../error-codes.js';
|
|
8
8
|
export const addQueueWorker = (logger, node, checker, state, options) => {
|
|
9
9
|
if (!ts.isCallExpression(node)) {
|
|
10
10
|
return;
|
|
@@ -23,25 +23,23 @@ export const addQueueWorker = (logger, node, checker, state, options) => {
|
|
|
23
23
|
const obj = firstArg;
|
|
24
24
|
const queueName = getPropertyValue(obj, 'queueName');
|
|
25
25
|
const docs = getPropertyValue(obj, 'docs') || undefined;
|
|
26
|
-
const tags =
|
|
26
|
+
const tags = getPropertyTags(obj, 'Queue worker', queueName, logger);
|
|
27
27
|
// --- find the referenced function ---
|
|
28
28
|
const funcInitializer = getPropertyAssignmentInitializer(obj, 'func', true, checker);
|
|
29
29
|
if (!funcInitializer) {
|
|
30
|
-
|
|
30
|
+
logger.critical(ErrorCode.MISSING_FUNC, `No valid 'func' property for queue processor '${queueName}'.`);
|
|
31
31
|
return;
|
|
32
32
|
}
|
|
33
|
-
const pikkuFuncName = extractFunctionName(funcInitializer, checker).pikkuFuncName;
|
|
33
|
+
const pikkuFuncName = extractFunctionName(funcInitializer, checker, state.rootDir).pikkuFuncName;
|
|
34
34
|
if (!queueName) {
|
|
35
|
-
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
const filePath = node.getSourceFile().fileName;
|
|
39
|
-
if (!matchesFilters(options.filters || {}, { tags }, { type: PikkuWiringTypes.queue, name: queueName, filePath }, logger)) {
|
|
40
|
-
console.info(`• Skipping queue processor '${pikkuFuncName}' for queue '${queueName}' due to filter mismatch.`);
|
|
35
|
+
logger.critical(ErrorCode.MISSING_QUEUE_NAME, `No 'queueName' provided for queue processor function '${pikkuFuncName}'.`);
|
|
41
36
|
return;
|
|
42
37
|
}
|
|
43
38
|
// --- resolve middleware ---
|
|
44
39
|
const middleware = resolveMiddleware(state, obj, tags, checker);
|
|
40
|
+
// --- track used functions/middleware for service aggregation ---
|
|
41
|
+
state.serviceAggregation.usedFunctions.add(pikkuFuncName);
|
|
42
|
+
extractWireNames(middleware).forEach((name) => state.serviceAggregation.usedMiddleware.add(name));
|
|
45
43
|
state.queueWorkers.files.add(node.getSourceFile().fileName);
|
|
46
44
|
state.queueWorkers.meta[queueName] = {
|
|
47
45
|
pikkuFuncName,
|
package/dist/add/add-schedule.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
|
-
import { getPropertyValue } from '../utils/get-property-value.js';
|
|
3
|
-
import { PikkuWiringTypes } from '@pikku/core';
|
|
2
|
+
import { getPropertyValue, getPropertyTags, } from '../utils/get-property-value.js';
|
|
4
3
|
import { extractFunctionName } from '../utils/extract-function-name.js';
|
|
5
4
|
import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
|
|
6
|
-
import { matchesFilters } from '../utils/filter-utils.js';
|
|
7
5
|
import { resolveMiddleware } from '../utils/middleware.js';
|
|
6
|
+
import { extractWireNames } from '../utils/post-process.js';
|
|
7
|
+
import { ErrorCode } from '../error-codes.js';
|
|
8
8
|
export const addSchedule = (logger, node, checker, state, options) => {
|
|
9
9
|
if (!ts.isCallExpression(node)) {
|
|
10
10
|
return;
|
|
@@ -24,22 +24,21 @@ export const addSchedule = (logger, node, checker, state, options) => {
|
|
|
24
24
|
const nameValue = getPropertyValue(obj, 'name');
|
|
25
25
|
const scheduleValue = getPropertyValue(obj, 'schedule');
|
|
26
26
|
const docs = getPropertyValue(obj, 'docs') || undefined;
|
|
27
|
-
const tags =
|
|
27
|
+
const tags = getPropertyTags(obj, 'Scheduler', nameValue, logger);
|
|
28
28
|
const funcInitializer = getPropertyAssignmentInitializer(obj, 'func', true, checker);
|
|
29
29
|
if (!funcInitializer) {
|
|
30
|
-
|
|
30
|
+
logger.critical(ErrorCode.MISSING_FUNC, `No valid 'func' property for scheduled task '${nameValue}'.`);
|
|
31
31
|
return;
|
|
32
32
|
}
|
|
33
|
-
const pikkuFuncName = extractFunctionName(funcInitializer, checker).pikkuFuncName;
|
|
33
|
+
const pikkuFuncName = extractFunctionName(funcInitializer, checker, state.rootDir).pikkuFuncName;
|
|
34
34
|
if (!nameValue || !scheduleValue) {
|
|
35
35
|
return;
|
|
36
36
|
}
|
|
37
|
-
const filePath = node.getSourceFile().fileName;
|
|
38
|
-
if (!matchesFilters(options.filters || {}, { tags }, { type: PikkuWiringTypes.scheduler, name: nameValue, filePath }, logger)) {
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
37
|
// --- resolve middleware ---
|
|
42
38
|
const middleware = resolveMiddleware(state, obj, tags, checker);
|
|
39
|
+
// --- track used functions/middleware for service aggregation ---
|
|
40
|
+
state.serviceAggregation.usedFunctions.add(pikkuFuncName);
|
|
41
|
+
extractWireNames(middleware).forEach((name) => state.serviceAggregation.usedMiddleware.add(name));
|
|
43
42
|
state.scheduledTasks.files.add(node.getSourceFile().fileName);
|
|
44
43
|
state.scheduledTasks.meta[nameValue] = {
|
|
45
44
|
pikkuFuncName,
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error code system for Pikku CLI and Inspector
|
|
3
|
+
*
|
|
4
|
+
* Each error has a unique code and links to documentation at pikku.dev
|
|
5
|
+
*
|
|
6
|
+
* Error codes use random 3-digit numbers to avoid implying a sequential order.
|
|
7
|
+
* Each code links to detailed documentation and troubleshooting steps.
|
|
8
|
+
*/
|
|
9
|
+
export declare enum ErrorCode {
|
|
10
|
+
MISSING_NAME = "PKU111",
|
|
11
|
+
MISSING_DESCRIPTION = "PKU123",
|
|
12
|
+
MISSING_URI = "PKU220",
|
|
13
|
+
MISSING_FUNC = "PKU236",
|
|
14
|
+
INVALID_TAGS_TYPE = "PKU247",
|
|
15
|
+
INVALID_HANDLER = "PKU300",
|
|
16
|
+
MISSING_TITLE = "PKU370",
|
|
17
|
+
MISSING_QUEUE_NAME = "PKU384",
|
|
18
|
+
MISSING_CHANNEL_NAME = "PKU400",
|
|
19
|
+
CLI_CLIENTSIDE_RENDERER_HAS_SERVICES = "PKU672",
|
|
20
|
+
CONFIG_TYPE_NOT_FOUND = "PKU426",
|
|
21
|
+
CONFIG_TYPE_UNDEFINED = "PKU427",
|
|
22
|
+
SCHEMA_NO_ROOT = "PKU431",
|
|
23
|
+
SCHEMA_GENERATION_ERROR = "PKU456",
|
|
24
|
+
SCHEMA_LOAD_ERROR = "PKU488",
|
|
25
|
+
FUNCTION_METADATA_NOT_FOUND = "PKU559",
|
|
26
|
+
HANDLER_NOT_RESOLVED = "PKU568",
|
|
27
|
+
MIDDLEWARE_HANDLER_INVALID = "PKU685",
|
|
28
|
+
MIDDLEWARE_TAG_INVALID = "PKU715",
|
|
29
|
+
MIDDLEWARE_EMPTY_ARRAY = "PKU736",
|
|
30
|
+
MIDDLEWARE_PATTERN_INVALID = "PKU787",
|
|
31
|
+
PERMISSION_HANDLER_INVALID = "PKU835",
|
|
32
|
+
PERMISSION_TAG_INVALID = "PKU836",
|
|
33
|
+
PERMISSION_EMPTY_ARRAY = "PKU937",
|
|
34
|
+
PERMISSION_PATTERN_INVALID = "PKU975"
|
|
35
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error code system for Pikku CLI and Inspector
|
|
3
|
+
*
|
|
4
|
+
* Each error has a unique code and links to documentation at pikku.dev
|
|
5
|
+
*
|
|
6
|
+
* Error codes use random 3-digit numbers to avoid implying a sequential order.
|
|
7
|
+
* Each code links to detailed documentation and troubleshooting steps.
|
|
8
|
+
*/
|
|
9
|
+
export var ErrorCode;
|
|
10
|
+
(function (ErrorCode) {
|
|
11
|
+
// Validation errors
|
|
12
|
+
ErrorCode["MISSING_NAME"] = "PKU111";
|
|
13
|
+
ErrorCode["MISSING_DESCRIPTION"] = "PKU123";
|
|
14
|
+
ErrorCode["MISSING_URI"] = "PKU220";
|
|
15
|
+
ErrorCode["MISSING_FUNC"] = "PKU236";
|
|
16
|
+
ErrorCode["INVALID_TAGS_TYPE"] = "PKU247";
|
|
17
|
+
ErrorCode["INVALID_HANDLER"] = "PKU300";
|
|
18
|
+
ErrorCode["MISSING_TITLE"] = "PKU370";
|
|
19
|
+
ErrorCode["MISSING_QUEUE_NAME"] = "PKU384";
|
|
20
|
+
ErrorCode["MISSING_CHANNEL_NAME"] = "PKU400";
|
|
21
|
+
ErrorCode["CLI_CLIENTSIDE_RENDERER_HAS_SERVICES"] = "PKU672";
|
|
22
|
+
// Configuration errors
|
|
23
|
+
ErrorCode["CONFIG_TYPE_NOT_FOUND"] = "PKU426";
|
|
24
|
+
ErrorCode["CONFIG_TYPE_UNDEFINED"] = "PKU427";
|
|
25
|
+
ErrorCode["SCHEMA_NO_ROOT"] = "PKU431";
|
|
26
|
+
ErrorCode["SCHEMA_GENERATION_ERROR"] = "PKU456";
|
|
27
|
+
ErrorCode["SCHEMA_LOAD_ERROR"] = "PKU488";
|
|
28
|
+
// Function errors
|
|
29
|
+
ErrorCode["FUNCTION_METADATA_NOT_FOUND"] = "PKU559";
|
|
30
|
+
ErrorCode["HANDLER_NOT_RESOLVED"] = "PKU568";
|
|
31
|
+
// Middleware/Permission errors
|
|
32
|
+
ErrorCode["MIDDLEWARE_HANDLER_INVALID"] = "PKU685";
|
|
33
|
+
ErrorCode["MIDDLEWARE_TAG_INVALID"] = "PKU715";
|
|
34
|
+
ErrorCode["MIDDLEWARE_EMPTY_ARRAY"] = "PKU736";
|
|
35
|
+
ErrorCode["MIDDLEWARE_PATTERN_INVALID"] = "PKU787";
|
|
36
|
+
ErrorCode["PERMISSION_HANDLER_INVALID"] = "PKU835";
|
|
37
|
+
ErrorCode["PERMISSION_TAG_INVALID"] = "PKU836";
|
|
38
|
+
ErrorCode["PERMISSION_EMPTY_ARRAY"] = "PKU937";
|
|
39
|
+
ErrorCode["PERMISSION_PATTERN_INVALID"] = "PKU975";
|
|
40
|
+
})(ErrorCode || (ErrorCode = {}));
|
package/dist/index.d.ts
CHANGED
|
@@ -4,3 +4,7 @@ export type { TypesMap } from './types-map.js';
|
|
|
4
4
|
export type * from './types.js';
|
|
5
5
|
export type { InspectorState } from './types.js';
|
|
6
6
|
export type { FilesAndMethods, FilesAndMethodsErrors, } from './utils/get-files-and-methods.js';
|
|
7
|
+
export { ErrorCode } from './error-codes.js';
|
|
8
|
+
export { serializeInspectorState, deserializeInspectorState, } from './utils/serialize-inspector-state.js';
|
|
9
|
+
export type { SerializableInspectorState } from './utils/serialize-inspector-state.js';
|
|
10
|
+
export { filterInspectorState } from './utils/filter-inspector-state.js';
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,5 @@
|
|
|
1
1
|
export { inspect } from './inspector.js';
|
|
2
2
|
export { getFilesAndMethods } from './utils/get-files-and-methods.js';
|
|
3
|
+
export { ErrorCode } from './error-codes.js';
|
|
4
|
+
export { serializeInspectorState, deserializeInspectorState, } from './utils/serialize-inspector-state.js';
|
|
5
|
+
export { filterInspectorState } from './utils/filter-inspector-state.js';
|
package/dist/inspector.js
CHANGED
|
@@ -2,6 +2,8 @@ import * as ts from 'typescript';
|
|
|
2
2
|
import { visitSetup, visitRoutes } from './visit.js';
|
|
3
3
|
import { TypesMap } from './types-map.js';
|
|
4
4
|
import { getFilesAndMethods } from './utils/get-files-and-methods.js';
|
|
5
|
+
import { findCommonAncestor } from './utils/find-root-dir.js';
|
|
6
|
+
import { aggregateRequiredServices } from './utils/post-process.js';
|
|
5
7
|
export const inspect = (logger, routeFiles, options = {}) => {
|
|
6
8
|
const program = ts.createProgram(routeFiles, {
|
|
7
9
|
target: ts.ScriptTarget.ESNext,
|
|
@@ -9,13 +11,17 @@ export const inspect = (logger, routeFiles, options = {}) => {
|
|
|
9
11
|
});
|
|
10
12
|
const checker = program.getTypeChecker();
|
|
11
13
|
const sourceFiles = program.getSourceFiles();
|
|
14
|
+
// Infer root directory from source files
|
|
15
|
+
const rootDir = findCommonAncestor(routeFiles);
|
|
12
16
|
const state = {
|
|
17
|
+
rootDir,
|
|
13
18
|
singletonServicesTypeImportMap: new Map(),
|
|
14
19
|
sessionServicesTypeImportMap: new Map(),
|
|
15
20
|
userSessionTypeImportMap: new Map(),
|
|
16
21
|
configTypeImportMap: new Map(),
|
|
17
22
|
singletonServicesFactories: new Map(),
|
|
18
23
|
sessionServicesFactories: new Map(),
|
|
24
|
+
sessionServicesMeta: new Map(),
|
|
19
25
|
configFactories: new Map(),
|
|
20
26
|
filesAndMethods: {},
|
|
21
27
|
filesAndMethodsErrors: new Map(),
|
|
@@ -38,6 +44,7 @@ export const inspect = (logger, routeFiles, options = {}) => {
|
|
|
38
44
|
},
|
|
39
45
|
files: new Set(),
|
|
40
46
|
routeMiddleware: new Map(),
|
|
47
|
+
routePermissions: new Map(),
|
|
41
48
|
},
|
|
42
49
|
channels: {
|
|
43
50
|
files: new Set(),
|
|
@@ -65,7 +72,10 @@ export const inspect = (logger, routeFiles, options = {}) => {
|
|
|
65
72
|
files: new Set(),
|
|
66
73
|
},
|
|
67
74
|
cli: {
|
|
68
|
-
meta: {
|
|
75
|
+
meta: {
|
|
76
|
+
programs: {},
|
|
77
|
+
renderers: {},
|
|
78
|
+
},
|
|
69
79
|
files: new Set(),
|
|
70
80
|
},
|
|
71
81
|
middleware: {
|
|
@@ -74,6 +84,13 @@ export const inspect = (logger, routeFiles, options = {}) => {
|
|
|
74
84
|
},
|
|
75
85
|
permissions: {
|
|
76
86
|
meta: {},
|
|
87
|
+
tagPermissions: new Map(),
|
|
88
|
+
},
|
|
89
|
+
serviceAggregation: {
|
|
90
|
+
requiredServices: new Set(),
|
|
91
|
+
usedFunctions: new Set(),
|
|
92
|
+
usedMiddleware: new Set(),
|
|
93
|
+
usedPermissions: new Set(),
|
|
77
94
|
},
|
|
78
95
|
};
|
|
79
96
|
// First sweep: add all functions
|
|
@@ -88,5 +105,7 @@ export const inspect = (logger, routeFiles, options = {}) => {
|
|
|
88
105
|
const { result, errors } = getFilesAndMethods(state, options.types);
|
|
89
106
|
state.filesAndMethods = result;
|
|
90
107
|
state.filesAndMethodsErrors = errors;
|
|
108
|
+
// Post-processing: Aggregate required services from wired functions/middleware/permissions
|
|
109
|
+
aggregateRequiredServices(state);
|
|
91
110
|
return state;
|
|
92
111
|
};
|
package/dist/types.d.ts
CHANGED
|
@@ -3,10 +3,11 @@ import { ChannelsMeta } from '@pikku/core/channel';
|
|
|
3
3
|
import { HTTPWiringsMeta } from '@pikku/core/http';
|
|
4
4
|
import { ScheduledTasksMeta } from '@pikku/core/scheduler';
|
|
5
5
|
import { queueWorkersMeta } from '@pikku/core/queue';
|
|
6
|
-
import { MCPResourceMeta, MCPToolMeta, MCPPromptMeta } from '@pikku/core';
|
|
7
|
-
import { CLIMeta } from '@pikku/core';
|
|
6
|
+
import { MCPResourceMeta, MCPToolMeta, MCPPromptMeta } from '@pikku/core/mcp';
|
|
7
|
+
import { CLIMeta } from '@pikku/core/cli';
|
|
8
8
|
import { TypesMap } from './types-map.js';
|
|
9
9
|
import { FunctionsMeta, FunctionServicesMeta } from '@pikku/core';
|
|
10
|
+
import { ErrorCode } from './error-codes.js';
|
|
10
11
|
export type PathToNameAndType = Map<string, {
|
|
11
12
|
variable: string;
|
|
12
13
|
type: string | null;
|
|
@@ -25,11 +26,20 @@ export interface MiddlewareGroupMeta {
|
|
|
25
26
|
middlewareCount: number;
|
|
26
27
|
isFactory: boolean;
|
|
27
28
|
}
|
|
29
|
+
export interface PermissionGroupMeta {
|
|
30
|
+
exportName: string | null;
|
|
31
|
+
sourceFile: string;
|
|
32
|
+
position: number;
|
|
33
|
+
services: FunctionServicesMeta;
|
|
34
|
+
permissionCount: number;
|
|
35
|
+
isFactory: boolean;
|
|
36
|
+
}
|
|
28
37
|
export interface InspectorHTTPState {
|
|
29
38
|
metaInputTypes: MetaInputTypes;
|
|
30
39
|
meta: HTTPWiringsMeta;
|
|
31
40
|
files: Set<string>;
|
|
32
41
|
routeMiddleware: Map<string, MiddlewareGroupMeta>;
|
|
42
|
+
routePermissions: Map<string, PermissionGroupMeta>;
|
|
33
43
|
}
|
|
34
44
|
export interface InspectorFunctionState {
|
|
35
45
|
typesMap: TypesMap;
|
|
@@ -50,6 +60,8 @@ export interface InspectorMiddlewareState {
|
|
|
50
60
|
position: number;
|
|
51
61
|
exportedName: string | null;
|
|
52
62
|
factory?: boolean;
|
|
63
|
+
name?: string;
|
|
64
|
+
description?: string;
|
|
53
65
|
}>;
|
|
54
66
|
tagMiddleware: Map<string, MiddlewareGroupMeta>;
|
|
55
67
|
}
|
|
@@ -59,12 +71,19 @@ export interface InspectorPermissionState {
|
|
|
59
71
|
sourceFile: string;
|
|
60
72
|
position: number;
|
|
61
73
|
exportedName: string | null;
|
|
74
|
+
factory?: boolean;
|
|
75
|
+
name?: string;
|
|
76
|
+
description?: string;
|
|
62
77
|
}>;
|
|
78
|
+
tagPermissions: Map<string, PermissionGroupMeta>;
|
|
63
79
|
}
|
|
64
80
|
export type InspectorFilters = {
|
|
81
|
+
names?: string[];
|
|
65
82
|
tags?: string[];
|
|
66
83
|
types?: string[];
|
|
67
84
|
directories?: string[];
|
|
85
|
+
httpRoutes?: string[];
|
|
86
|
+
httpMethods?: string[];
|
|
68
87
|
};
|
|
69
88
|
export type InspectorOptions = Partial<{
|
|
70
89
|
types: Partial<{
|
|
@@ -73,13 +92,14 @@ export type InspectorOptions = Partial<{
|
|
|
73
92
|
singletonServicesFactoryType: string;
|
|
74
93
|
sessionServicesFactoryType: string;
|
|
75
94
|
}>;
|
|
76
|
-
filters: InspectorFilters;
|
|
77
95
|
}>;
|
|
78
96
|
export interface InspectorLogger {
|
|
79
97
|
info: (message: string) => void;
|
|
80
98
|
error: (message: string) => void;
|
|
81
99
|
warn: (message: string) => void;
|
|
82
100
|
debug: (message: string) => void;
|
|
101
|
+
critical: (code: ErrorCode, message: string) => void;
|
|
102
|
+
hasCriticalErrors: () => boolean;
|
|
83
103
|
}
|
|
84
104
|
export type AddWiring = (logger: InspectorLogger, node: ts.Node, checker: ts.TypeChecker, state: InspectorState, options: InspectorOptions) => void;
|
|
85
105
|
export interface InspectorFilesAndMethods {
|
|
@@ -127,12 +147,14 @@ export interface InspectorFilesAndMethods {
|
|
|
127
147
|
};
|
|
128
148
|
}
|
|
129
149
|
export interface InspectorState {
|
|
150
|
+
rootDir: string;
|
|
130
151
|
singletonServicesTypeImportMap: PathToNameAndType;
|
|
131
152
|
sessionServicesTypeImportMap: PathToNameAndType;
|
|
132
153
|
userSessionTypeImportMap: PathToNameAndType;
|
|
133
154
|
configTypeImportMap: PathToNameAndType;
|
|
134
155
|
singletonServicesFactories: PathToNameAndType;
|
|
135
156
|
sessionServicesFactories: PathToNameAndType;
|
|
157
|
+
sessionServicesMeta: Map<string, string[]>;
|
|
136
158
|
configFactories: PathToNameAndType;
|
|
137
159
|
filesAndMethods: InspectorFilesAndMethods;
|
|
138
160
|
filesAndMethodsErrors: Map<string, PathToNameAndType>;
|
|
@@ -173,4 +195,10 @@ export interface InspectorState {
|
|
|
173
195
|
};
|
|
174
196
|
middleware: InspectorMiddlewareState;
|
|
175
197
|
permissions: InspectorPermissionState;
|
|
198
|
+
serviceAggregation: {
|
|
199
|
+
requiredServices: Set<string>;
|
|
200
|
+
usedFunctions: Set<string>;
|
|
201
|
+
usedMiddleware: Set<string>;
|
|
202
|
+
usedPermissions: Set<string>;
|
|
203
|
+
};
|
|
176
204
|
}
|