@pikku/inspector 0.9.6-next.0 → 0.10.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 +14 -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 +87 -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 +4 -3
- 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 +114 -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 +10 -2
- 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
|
@@ -2,6 +2,8 @@ import * as ts from 'typescript';
|
|
|
2
2
|
import { extractFunctionName, isNamedExport, } from '../utils/extract-function-name.js';
|
|
3
3
|
import { extractServicesFromFunction } from '../utils/extract-services.js';
|
|
4
4
|
import { extractMiddlewarePikkuNames } from '../utils/middleware.js';
|
|
5
|
+
import { getPropertyValue } from '../utils/get-property-value.js';
|
|
6
|
+
import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
|
|
5
7
|
/**
|
|
6
8
|
* Inspect pikkuMiddleware calls, addMiddleware calls, and addHTTPMiddleware calls
|
|
7
9
|
*/
|
|
@@ -15,23 +17,46 @@ export const addMiddleware = (logger, node, checker, state) => {
|
|
|
15
17
|
}
|
|
16
18
|
// Handle pikkuMiddleware(...) - individual middleware function definition
|
|
17
19
|
if (expression.text === 'pikkuMiddleware') {
|
|
18
|
-
const
|
|
19
|
-
if (!
|
|
20
|
+
const arg = args[0];
|
|
21
|
+
if (!arg)
|
|
20
22
|
return;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
let actualHandler;
|
|
24
|
+
let name;
|
|
25
|
+
let description;
|
|
26
|
+
// Check if using object syntax: pikkuMiddleware({ 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(`• pikkuMiddleware 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 {
|
|
23
46
|
logger.error(`• Handler for pikkuMiddleware is not a function.`);
|
|
24
47
|
return;
|
|
25
48
|
}
|
|
26
|
-
const services = extractServicesFromFunction(
|
|
27
|
-
const { pikkuFuncName, exportedName } = extractFunctionName(node, checker);
|
|
49
|
+
const services = extractServicesFromFunction(actualHandler);
|
|
50
|
+
const { pikkuFuncName, exportedName } = extractFunctionName(node, checker, state.rootDir);
|
|
28
51
|
state.middleware.meta[pikkuFuncName] = {
|
|
29
52
|
services,
|
|
30
53
|
sourceFile: node.getSourceFile().fileName,
|
|
31
54
|
position: node.getStart(),
|
|
32
55
|
exportedName,
|
|
56
|
+
name,
|
|
57
|
+
description,
|
|
33
58
|
};
|
|
34
|
-
logger.debug(`• Found middleware with services: ${services.services.join(', ')}`);
|
|
59
|
+
logger.debug(`• Found middleware with services: ${services.services.join(', ')}${name ? ` (name: ${name})` : ''}${description ? ` (description: ${description})` : ''}`);
|
|
35
60
|
return;
|
|
36
61
|
}
|
|
37
62
|
// Handle pikkuMiddlewareFactory(...) - middleware factory function
|
|
@@ -46,6 +71,7 @@ export const addMiddleware = (logger, node, checker, state) => {
|
|
|
46
71
|
}
|
|
47
72
|
// Extract services by looking inside the factory function body
|
|
48
73
|
// The factory should return pikkuMiddleware(...), so we need to find that call
|
|
74
|
+
// If no wrapper is found, extract from the factory's returned function directly
|
|
49
75
|
let services = { optimized: false, services: [] };
|
|
50
76
|
const findPikkuMiddlewareCall = (node) => {
|
|
51
77
|
if (ts.isCallExpression(node)) {
|
|
@@ -64,7 +90,20 @@ export const addMiddleware = (logger, node, checker, state) => {
|
|
|
64
90
|
services = extractServicesFromFunction(middlewareHandler);
|
|
65
91
|
}
|
|
66
92
|
}
|
|
67
|
-
|
|
93
|
+
else {
|
|
94
|
+
// No pikkuMiddleware wrapper found - extract from factory's return value directly
|
|
95
|
+
// Factory pattern: (config) => (services, interaction, next) => { ... }
|
|
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);
|
|
68
107
|
state.middleware.meta[pikkuFuncName] = {
|
|
69
108
|
services,
|
|
70
109
|
sourceFile: node.getSourceFile().fileName,
|
|
@@ -99,7 +138,7 @@ export const addMiddleware = (logger, node, checker, state) => {
|
|
|
99
138
|
return;
|
|
100
139
|
}
|
|
101
140
|
// Extract middleware pikkuFuncNames from array
|
|
102
|
-
const middlewareNames = extractMiddlewarePikkuNames(middlewareArrayArg, checker);
|
|
141
|
+
const middlewareNames = extractMiddlewarePikkuNames(middlewareArrayArg, checker, state.rootDir);
|
|
103
142
|
if (middlewareNames.length === 0) {
|
|
104
143
|
logger.warn(`• addMiddleware('${tag}', ...) has empty middleware array`);
|
|
105
144
|
return;
|
|
@@ -139,7 +178,7 @@ export const addMiddleware = (logger, node, checker, state) => {
|
|
|
139
178
|
}
|
|
140
179
|
// If not a factory, get export name from the call expression itself
|
|
141
180
|
if (!isFactory) {
|
|
142
|
-
const extracted = extractFunctionName(node, checker);
|
|
181
|
+
const extracted = extractFunctionName(node, checker, state.rootDir);
|
|
143
182
|
exportedName = extracted.exportedName;
|
|
144
183
|
}
|
|
145
184
|
// Log warning if not using factory pattern
|
|
@@ -186,7 +225,7 @@ export const addMiddleware = (logger, node, checker, state) => {
|
|
|
186
225
|
return;
|
|
187
226
|
}
|
|
188
227
|
// Extract middleware pikkuFuncNames from array
|
|
189
|
-
const middlewareNames = extractMiddlewarePikkuNames(middlewareArrayArg, checker);
|
|
228
|
+
const middlewareNames = extractMiddlewarePikkuNames(middlewareArrayArg, checker, state.rootDir);
|
|
190
229
|
if (middlewareNames.length === 0) {
|
|
191
230
|
logger.warn(`• addHTTPMiddleware('${pattern}', ...) has empty middleware array`);
|
|
192
231
|
return;
|
|
@@ -225,7 +264,7 @@ export const addMiddleware = (logger, node, checker, state) => {
|
|
|
225
264
|
}
|
|
226
265
|
// If not a factory, get export name from the call expression itself
|
|
227
266
|
if (!isFactory) {
|
|
228
|
-
const extracted = extractFunctionName(node, checker);
|
|
267
|
+
const extracted = extractFunctionName(node, checker, state.rootDir);
|
|
229
268
|
exportedName = extracted.exportedName;
|
|
230
269
|
}
|
|
231
270
|
// Log warning if not using factory pattern
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { AddWiring } from '../types.js';
|
|
2
2
|
/**
|
|
3
|
-
* Inspect pikkuPermission calls and
|
|
4
|
-
* for tree shaking optimization.
|
|
3
|
+
* Inspect pikkuPermission calls, addPermission calls, and addHTTPPermission calls
|
|
5
4
|
*/
|
|
6
5
|
export declare const addPermission: AddWiring;
|
|
@@ -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';
|