@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
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
/**
|
|
3
|
+
* Finds the common ancestor directory of all the given file paths.
|
|
4
|
+
* This is used to determine the project root directory.
|
|
5
|
+
*
|
|
6
|
+
* @param filePaths - Array of absolute file paths
|
|
7
|
+
* @returns The common ancestor directory path
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* findCommonAncestor([
|
|
11
|
+
* '/Users/yasser/git/pikku/pikku/src/functions/a.ts',
|
|
12
|
+
* '/Users/yasser/git/pikku/pikku/src/routes/b.ts'
|
|
13
|
+
* ])
|
|
14
|
+
* // Returns: '/Users/yasser/git/pikku/pikku'
|
|
15
|
+
*/
|
|
16
|
+
export function findCommonAncestor(filePaths) {
|
|
17
|
+
if (filePaths.length === 0) {
|
|
18
|
+
return process.cwd();
|
|
19
|
+
}
|
|
20
|
+
if (filePaths.length === 1) {
|
|
21
|
+
return path.dirname(filePaths[0]);
|
|
22
|
+
}
|
|
23
|
+
// Normalize all paths and get their directory parts
|
|
24
|
+
const normalizedPaths = filePaths.map((p) => path.dirname(path.normalize(p)).split(path.sep));
|
|
25
|
+
// Start with the first path's parts
|
|
26
|
+
const firstPath = normalizedPaths[0];
|
|
27
|
+
let commonParts = [];
|
|
28
|
+
// Check each part of the first path
|
|
29
|
+
for (let i = 0; i < firstPath.length; i++) {
|
|
30
|
+
const part = firstPath[i];
|
|
31
|
+
// Check if this part exists in all other paths at the same position
|
|
32
|
+
const existsInAll = normalizedPaths.every((pathParts) => pathParts[i] === part);
|
|
33
|
+
if (existsInAll) {
|
|
34
|
+
commonParts.push(part);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// If no common parts, return root
|
|
41
|
+
if (commonParts.length === 0) {
|
|
42
|
+
return path.sep;
|
|
43
|
+
}
|
|
44
|
+
return commonParts.join(path.sep);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Converts an absolute file path to a relative path from the root directory.
|
|
48
|
+
*
|
|
49
|
+
* @param absolutePath - The absolute file path
|
|
50
|
+
* @param rootDir - The root directory to make the path relative to
|
|
51
|
+
* @returns A relative path from rootDir to the file
|
|
52
|
+
*/
|
|
53
|
+
export function toRelativePath(absolutePath, rootDir) {
|
|
54
|
+
return path.relative(rootDir, absolutePath);
|
|
55
|
+
}
|
|
@@ -9,12 +9,13 @@ export type FilesAndMethods = {
|
|
|
9
9
|
userSessionType: Meta;
|
|
10
10
|
sessionServicesType: Meta;
|
|
11
11
|
singletonServicesType: Meta;
|
|
12
|
+
pikkuConfigType: Meta;
|
|
12
13
|
pikkuConfigFactory: Meta;
|
|
13
14
|
singletonServicesFactory: Meta;
|
|
14
15
|
sessionServicesFactory: Meta;
|
|
15
16
|
};
|
|
16
17
|
export type FilesAndMethodsErrors = Map<string, PathToNameAndType>;
|
|
17
|
-
export declare const getFilesAndMethods: ({ singletonServicesTypeImportMap, sessionServicesTypeImportMap, userSessionTypeImportMap, sessionServicesFactories, singletonServicesFactories, configFactories, }: InspectorState, { configFileType, userSessionType, singletonServicesFactoryType, sessionServicesFactoryType, }?: InspectorOptions["types"]) => {
|
|
18
|
+
export declare const getFilesAndMethods: ({ singletonServicesTypeImportMap, sessionServicesTypeImportMap, userSessionTypeImportMap, configTypeImportMap, sessionServicesFactories, singletonServicesFactories, configFactories, }: InspectorState, { configFileType, userSessionType, singletonServicesFactoryType, sessionServicesFactoryType, }?: InspectorOptions["types"]) => {
|
|
18
19
|
result: Partial<FilesAndMethods>;
|
|
19
20
|
errors: FilesAndMethodsErrors;
|
|
20
21
|
};
|
|
@@ -46,12 +46,13 @@ const getMetaTypes = (type, map, desiredType, errors) => {
|
|
|
46
46
|
}
|
|
47
47
|
return;
|
|
48
48
|
};
|
|
49
|
-
export const getFilesAndMethods = ({ singletonServicesTypeImportMap, sessionServicesTypeImportMap, userSessionTypeImportMap, sessionServicesFactories, singletonServicesFactories, configFactories, }, { configFileType, userSessionType, singletonServicesFactoryType, sessionServicesFactoryType, } = {}) => {
|
|
49
|
+
export const getFilesAndMethods = ({ singletonServicesTypeImportMap, sessionServicesTypeImportMap, userSessionTypeImportMap, configTypeImportMap, sessionServicesFactories, singletonServicesFactories, configFactories, }, { configFileType, userSessionType, singletonServicesFactoryType, sessionServicesFactoryType, } = {}) => {
|
|
50
50
|
const errors = new Map();
|
|
51
51
|
const result = {
|
|
52
52
|
userSessionType: getMetaTypes('CoreUserSession', userSessionTypeImportMap, userSessionType, errors),
|
|
53
53
|
singletonServicesType: getMetaTypes('CoreSingletonServices', singletonServicesTypeImportMap, undefined, errors),
|
|
54
54
|
sessionServicesType: getMetaTypes('CoreServices', sessionServicesTypeImportMap, undefined, errors),
|
|
55
|
+
pikkuConfigType: getMetaTypes('CoreConfig', configTypeImportMap, undefined, errors),
|
|
55
56
|
pikkuConfigFactory: getMetaTypes('CoreConfig', configFactories, configFileType, errors),
|
|
56
57
|
singletonServicesFactory: getMetaTypes('CreateSingletonServices', singletonServicesFactories, singletonServicesFactoryType, errors),
|
|
57
58
|
sessionServicesFactory: getMetaTypes('CreateSessionServices', sessionServicesFactories, sessionServicesFactoryType, errors),
|
|
@@ -1,3 +1,12 @@
|
|
|
1
1
|
import { PikkuDocs } from '@pikku/core';
|
|
2
2
|
import * as ts from 'typescript';
|
|
3
|
+
import { ErrorCode } from '../error-codes.js';
|
|
3
4
|
export declare const getPropertyValue: (obj: ts.ObjectLiteralExpression, propertyName: string) => string | string[] | null | PikkuDocs | boolean;
|
|
5
|
+
/**
|
|
6
|
+
* Gets the 'tags' property from an object and validates it's an array.
|
|
7
|
+
* Logs a critical error if tags is not an array but still returns the value.
|
|
8
|
+
* @param logger - Optional logger instance; if not provided, uses console.error
|
|
9
|
+
*/
|
|
10
|
+
export declare const getPropertyTags: (obj: ts.ObjectLiteralExpression, wiringType: string, wiringName: string | null, logger?: {
|
|
11
|
+
critical: (code: ErrorCode, message: string) => void;
|
|
12
|
+
}) => string[] | undefined;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
|
+
import { ErrorCode } from '../error-codes.js';
|
|
2
3
|
export const getPropertyValue = (obj, propertyName) => {
|
|
3
4
|
const property = obj.properties.find((p) => ts.isPropertyAssignment(p) &&
|
|
4
5
|
ts.isIdentifier(p.name) &&
|
|
@@ -64,3 +65,22 @@ export const getPropertyValue = (obj, propertyName) => {
|
|
|
64
65
|
}
|
|
65
66
|
return null;
|
|
66
67
|
};
|
|
68
|
+
/**
|
|
69
|
+
* Gets the 'tags' property from an object and validates it's an array.
|
|
70
|
+
* Logs a critical error if tags is not an array but still returns the value.
|
|
71
|
+
* @param logger - Optional logger instance; if not provided, uses console.error
|
|
72
|
+
*/
|
|
73
|
+
export const getPropertyTags = (obj, wiringType, wiringName, logger) => {
|
|
74
|
+
const tagsValue = getPropertyValue(obj, 'tags');
|
|
75
|
+
if (tagsValue !== null && !Array.isArray(tagsValue)) {
|
|
76
|
+
const errorMsg = `${wiringType} '${wiringName}' has invalid 'tags' property - must be an array of strings.`;
|
|
77
|
+
if (logger) {
|
|
78
|
+
logger.critical(ErrorCode.INVALID_TAGS_TYPE, errorMsg);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
console.error(errorMsg);
|
|
82
|
+
}
|
|
83
|
+
// Return undefined but don't stop processing - error will be caught by the exit handler
|
|
84
|
+
}
|
|
85
|
+
return Array.isArray(tagsValue) ? tagsValue : undefined;
|
|
86
|
+
};
|
|
@@ -6,7 +6,7 @@ import { InspectorState } from '../types.js';
|
|
|
6
6
|
* Resolves each identifier to its pikkuFuncName using extractFunctionName
|
|
7
7
|
* Also handles call expressions (like logCommandInfoAndTime({...}))
|
|
8
8
|
*/
|
|
9
|
-
export declare function extractMiddlewarePikkuNames(arrayNode: ts.Expression, checker: ts.TypeChecker): string[];
|
|
9
|
+
export declare function extractMiddlewarePikkuNames(arrayNode: ts.Expression, checker: ts.TypeChecker, rootDir: string): string[];
|
|
10
10
|
/**
|
|
11
11
|
* Get middleware array from an object literal expression property
|
|
12
12
|
* Returns the initializer node for the 'middleware' property if it exists
|
package/dist/utils/middleware.js
CHANGED
|
@@ -5,7 +5,7 @@ import { extractFunctionName } from './extract-function-name.js';
|
|
|
5
5
|
* Resolves each identifier to its pikkuFuncName using extractFunctionName
|
|
6
6
|
* Also handles call expressions (like logCommandInfoAndTime({...}))
|
|
7
7
|
*/
|
|
8
|
-
export function extractMiddlewarePikkuNames(arrayNode, checker) {
|
|
8
|
+
export function extractMiddlewarePikkuNames(arrayNode, checker, rootDir) {
|
|
9
9
|
if (!ts.isArrayLiteralExpression(arrayNode)) {
|
|
10
10
|
return [];
|
|
11
11
|
}
|
|
@@ -13,13 +13,13 @@ export function extractMiddlewarePikkuNames(arrayNode, checker) {
|
|
|
13
13
|
for (const element of arrayNode.elements) {
|
|
14
14
|
if (ts.isIdentifier(element)) {
|
|
15
15
|
// Resolve the identifier to its pikkuFuncName
|
|
16
|
-
const { pikkuFuncName } = extractFunctionName(element, checker);
|
|
16
|
+
const { pikkuFuncName } = extractFunctionName(element, checker, rootDir);
|
|
17
17
|
names.push(pikkuFuncName);
|
|
18
18
|
}
|
|
19
19
|
else if (ts.isCallExpression(element)) {
|
|
20
|
-
// Handle call expressions like logCommandInfoAndTime({...})
|
|
21
|
-
//
|
|
22
|
-
const { pikkuFuncName } = extractFunctionName(element, checker);
|
|
20
|
+
// Handle call expressions like rateLimiter(10) or logCommandInfoAndTime({...})
|
|
21
|
+
// Extract the function being called (e.g., 'rateLimiter' from 'rateLimiter(10)')
|
|
22
|
+
const { pikkuFuncName } = extractFunctionName(element.expression, checker, rootDir);
|
|
23
23
|
names.push(pikkuFuncName);
|
|
24
24
|
}
|
|
25
25
|
}
|
|
@@ -84,7 +84,7 @@ export function resolveHTTPMiddleware(state, route, tags, explicitMiddlewareNode
|
|
|
84
84
|
}
|
|
85
85
|
// 3. Explicit wire middleware (inline is OK here)
|
|
86
86
|
if (explicitMiddlewareNode) {
|
|
87
|
-
const middlewareNames = extractMiddlewarePikkuNames(explicitMiddlewareNode, checker);
|
|
87
|
+
const middlewareNames = extractMiddlewarePikkuNames(explicitMiddlewareNode, checker, state.rootDir);
|
|
88
88
|
for (const name of middlewareNames) {
|
|
89
89
|
const meta = state.middleware.meta[name];
|
|
90
90
|
resolved.push({
|
|
@@ -117,7 +117,7 @@ function resolveTagAndExplicitMiddleware(state, tags, explicitMiddlewareNode, ch
|
|
|
117
117
|
}
|
|
118
118
|
// 2. Explicit middleware (inline is OK here - used directly in wire/function)
|
|
119
119
|
if (explicitMiddlewareNode) {
|
|
120
|
-
const middlewareNames = extractMiddlewarePikkuNames(explicitMiddlewareNode, checker);
|
|
120
|
+
const middlewareNames = extractMiddlewarePikkuNames(explicitMiddlewareNode, checker, state.rootDir);
|
|
121
121
|
for (const name of middlewareNames) {
|
|
122
122
|
const meta = state.middleware.meta[name];
|
|
123
123
|
resolved.push({
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import * as ts from 'typescript';
|
|
2
|
+
import { PermissionMetadata } from '@pikku/core';
|
|
3
|
+
import { InspectorState } from '../types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Extract permission pikkuFuncNames from an expression (array or object literal)
|
|
6
|
+
* Resolves each identifier to its pikkuFuncName using extractFunctionName
|
|
7
|
+
* Also handles call expressions (like rolePermission({...}))
|
|
8
|
+
*
|
|
9
|
+
* Supports both formats:
|
|
10
|
+
* - Array: [permission1, permission2]
|
|
11
|
+
* - Record: { groupName: permission1, anotherGroup: [permission2, permission3] }
|
|
12
|
+
*/
|
|
13
|
+
export declare function extractPermissionPikkuNames(node: ts.Expression, checker: ts.TypeChecker, rootDir: string): string[];
|
|
14
|
+
/**
|
|
15
|
+
* Get permissions array from an object literal expression property
|
|
16
|
+
* Returns the initializer node for the 'permissions' property if it exists
|
|
17
|
+
*/
|
|
18
|
+
export declare function getPermissionsNode(obj: ts.ObjectLiteralExpression): ts.Expression | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Check if a route matches a pattern with wildcards
|
|
21
|
+
* Pattern can be exact match or use * as wildcard
|
|
22
|
+
* e.g., '/api/*' matches '/api/users', '/api/posts/123', etc.
|
|
23
|
+
*/
|
|
24
|
+
export declare function routeMatchesPattern(route: string, pattern: string): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Resolve permissions for an HTTP wiring based on:
|
|
27
|
+
* 1. Global HTTP permissions (addHTTPPermission('*', [...]))
|
|
28
|
+
* 2. Route-specific HTTP permissions (addHTTPPermission('/pattern', [...]))
|
|
29
|
+
* 3. Tag-based permissions (addPermission('tag', [...]))
|
|
30
|
+
* 4. Explicit wiring permissions (wireHTTP({ permissions: [...] }))
|
|
31
|
+
* Returns undefined if no permissions are found, otherwise returns array with at least one item
|
|
32
|
+
*/
|
|
33
|
+
export declare function resolveHTTPPermissions(state: InspectorState, route: string, tags: string[] | undefined, explicitPermissionsNode: ts.Expression | undefined, checker: ts.TypeChecker): PermissionMetadata[] | undefined;
|
|
34
|
+
/**
|
|
35
|
+
* Convenience wrapper: Extract permissions node from object and resolve
|
|
36
|
+
* Use this in add-* files for cleaner code
|
|
37
|
+
*/
|
|
38
|
+
export declare function resolvePermissions(state: InspectorState, obj: ts.ObjectLiteralExpression, tags: string[] | undefined, checker: ts.TypeChecker): PermissionMetadata[] | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* Convenience wrapper for HTTP: Extract permissions and resolve with HTTP-specific logic
|
|
41
|
+
* Use this in add-http-route.ts for cleaner code
|
|
42
|
+
*/
|
|
43
|
+
export declare function resolveHTTPPermissionsFromObject(state: InspectorState, route: string, obj: ts.ObjectLiteralExpression, tags: string[] | undefined, checker: ts.TypeChecker): PermissionMetadata[] | undefined;
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import * as ts from 'typescript';
|
|
2
|
+
import { extractFunctionName } from './extract-function-name.js';
|
|
3
|
+
/**
|
|
4
|
+
* Extract permission pikkuFuncNames from an expression (array or object literal)
|
|
5
|
+
* Resolves each identifier to its pikkuFuncName using extractFunctionName
|
|
6
|
+
* Also handles call expressions (like rolePermission({...}))
|
|
7
|
+
*
|
|
8
|
+
* Supports both formats:
|
|
9
|
+
* - Array: [permission1, permission2]
|
|
10
|
+
* - Record: { groupName: permission1, anotherGroup: [permission2, permission3] }
|
|
11
|
+
*/
|
|
12
|
+
export function extractPermissionPikkuNames(node, checker, rootDir) {
|
|
13
|
+
const names = [];
|
|
14
|
+
// Helper to extract from a single element
|
|
15
|
+
const extractFromElement = (element) => {
|
|
16
|
+
if (ts.isIdentifier(element)) {
|
|
17
|
+
const { pikkuFuncName } = extractFunctionName(element, checker, rootDir);
|
|
18
|
+
names.push(pikkuFuncName);
|
|
19
|
+
}
|
|
20
|
+
else if (ts.isCallExpression(element)) {
|
|
21
|
+
// Handle call expressions like hasEmailQuota(100) or rolePermission({...})
|
|
22
|
+
// Extract the function being called (e.g., 'hasEmailQuota' from 'hasEmailQuota(100)')
|
|
23
|
+
const { pikkuFuncName } = extractFunctionName(element.expression, checker, rootDir);
|
|
24
|
+
names.push(pikkuFuncName);
|
|
25
|
+
}
|
|
26
|
+
else if (ts.isArrayLiteralExpression(element)) {
|
|
27
|
+
// Nested array (for Record values that are arrays)
|
|
28
|
+
for (const nestedElement of element.elements) {
|
|
29
|
+
extractFromElement(nestedElement);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
if (ts.isArrayLiteralExpression(node)) {
|
|
34
|
+
// Array format: [permission1, permission2]
|
|
35
|
+
for (const element of node.elements) {
|
|
36
|
+
extractFromElement(element);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
else if (ts.isObjectLiteralExpression(node)) {
|
|
40
|
+
// Record format: { groupName: permission1, anotherGroup: [permission2] }
|
|
41
|
+
for (const prop of node.properties) {
|
|
42
|
+
if (ts.isPropertyAssignment(prop)) {
|
|
43
|
+
extractFromElement(prop.initializer);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return names;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Get permissions array from an object literal expression property
|
|
51
|
+
* Returns the initializer node for the 'permissions' property if it exists
|
|
52
|
+
*/
|
|
53
|
+
export function getPermissionsNode(obj) {
|
|
54
|
+
const permissionsProp = obj.properties.find((p) => ts.isPropertyAssignment(p) &&
|
|
55
|
+
ts.isIdentifier(p.name) &&
|
|
56
|
+
p.name.text === 'permissions');
|
|
57
|
+
return permissionsProp?.initializer;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Check if a route matches a pattern with wildcards
|
|
61
|
+
* Pattern can be exact match or use * as wildcard
|
|
62
|
+
* e.g., '/api/*' matches '/api/users', '/api/posts/123', etc.
|
|
63
|
+
*/
|
|
64
|
+
export function routeMatchesPattern(route, pattern) {
|
|
65
|
+
if (route === pattern)
|
|
66
|
+
return true;
|
|
67
|
+
// Convert pattern to regex: replace * with .*
|
|
68
|
+
const regexPattern = pattern
|
|
69
|
+
.replace(/[.+?^${}()|[\]\\]/g, '\\$&') // Escape regex special chars except *
|
|
70
|
+
.replace(/\*/g, '.*'); // Replace * with .*
|
|
71
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
72
|
+
return regex.test(route);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Resolve permissions for an HTTP wiring based on:
|
|
76
|
+
* 1. Global HTTP permissions (addHTTPPermission('*', [...]))
|
|
77
|
+
* 2. Route-specific HTTP permissions (addHTTPPermission('/pattern', [...]))
|
|
78
|
+
* 3. Tag-based permissions (addPermission('tag', [...]))
|
|
79
|
+
* 4. Explicit wiring permissions (wireHTTP({ permissions: [...] }))
|
|
80
|
+
* Returns undefined if no permissions are found, otherwise returns array with at least one item
|
|
81
|
+
*/
|
|
82
|
+
export function resolveHTTPPermissions(state, route, tags, explicitPermissionsNode, checker) {
|
|
83
|
+
const resolved = [];
|
|
84
|
+
// 1. HTTP route permission groups (includes '*' for global)
|
|
85
|
+
for (const [pattern, _groupMeta] of state.http.routePermissions.entries()) {
|
|
86
|
+
if (routeMatchesPattern(route, pattern)) {
|
|
87
|
+
// Just reference the group by route pattern
|
|
88
|
+
resolved.push({
|
|
89
|
+
type: 'http',
|
|
90
|
+
route: pattern,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// 2. Tag-based permission groups
|
|
95
|
+
if (tags && tags.length > 0) {
|
|
96
|
+
for (const tag of tags) {
|
|
97
|
+
if (state.permissions.tagPermissions.has(tag)) {
|
|
98
|
+
// Just reference the group by tag
|
|
99
|
+
resolved.push({
|
|
100
|
+
type: 'tag',
|
|
101
|
+
tag,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// 3. Explicit wire permissions (inline is OK here)
|
|
107
|
+
if (explicitPermissionsNode) {
|
|
108
|
+
const permissionNames = extractPermissionPikkuNames(explicitPermissionsNode, checker, state.rootDir);
|
|
109
|
+
for (const name of permissionNames) {
|
|
110
|
+
const meta = state.permissions.meta[name];
|
|
111
|
+
resolved.push({
|
|
112
|
+
type: 'wire',
|
|
113
|
+
name,
|
|
114
|
+
inline: meta?.exportedName === null,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return resolved.length > 0 ? resolved : undefined;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Resolve tag-based and explicit permissions (common logic for wires and functions)
|
|
122
|
+
* 1. Tag-based permissions (addPermission('tag', [...]))
|
|
123
|
+
* 2. Explicit permissions (wireHTTP/pikkuFunc({ permissions: [...] }))
|
|
124
|
+
*/
|
|
125
|
+
function resolveTagAndExplicitPermissions(state, tags, explicitPermissionsNode, checker) {
|
|
126
|
+
const resolved = [];
|
|
127
|
+
// 1. Tag-based permission groups
|
|
128
|
+
if (tags && tags.length > 0) {
|
|
129
|
+
for (const tag of tags) {
|
|
130
|
+
if (state.permissions.tagPermissions.has(tag)) {
|
|
131
|
+
// Just reference the group by tag
|
|
132
|
+
resolved.push({
|
|
133
|
+
type: 'tag',
|
|
134
|
+
tag,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// 2. Explicit permissions (inline is OK here - used directly in wire/function)
|
|
140
|
+
if (explicitPermissionsNode) {
|
|
141
|
+
const permissionNames = extractPermissionPikkuNames(explicitPermissionsNode, checker, state.rootDir);
|
|
142
|
+
for (const name of permissionNames) {
|
|
143
|
+
const meta = state.permissions.meta[name];
|
|
144
|
+
resolved.push({
|
|
145
|
+
type: 'wire',
|
|
146
|
+
name,
|
|
147
|
+
inline: meta?.exportedName === null,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return resolved;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Resolve permissions for a function based on:
|
|
155
|
+
* 1. Tag-based permissions (addPermission('tag', [...]))
|
|
156
|
+
* 2. Explicit function permissions (pikkuFunc({ permissions: [...] }))
|
|
157
|
+
* Returns undefined if no permissions are found, otherwise returns array with at least one item
|
|
158
|
+
*/
|
|
159
|
+
function resolveFunctionPermissionsInternal(state, tags, explicitPermissionsNode, checker) {
|
|
160
|
+
const resolved = resolveTagAndExplicitPermissions(state, tags, explicitPermissionsNode, checker);
|
|
161
|
+
return resolved.length > 0 ? resolved : undefined;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Convenience wrapper: Extract permissions node from object and resolve
|
|
165
|
+
* Use this in add-* files for cleaner code
|
|
166
|
+
*/
|
|
167
|
+
export function resolvePermissions(state, obj, tags, checker) {
|
|
168
|
+
const explicitPermissionsNode = getPermissionsNode(obj);
|
|
169
|
+
return resolveFunctionPermissionsInternal(state, tags, explicitPermissionsNode, checker);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Convenience wrapper for HTTP: Extract permissions and resolve with HTTP-specific logic
|
|
173
|
+
* Use this in add-http-route.ts for cleaner code
|
|
174
|
+
*/
|
|
175
|
+
export function resolveHTTPPermissionsFromObject(state, route, obj, tags, checker) {
|
|
176
|
+
const explicitPermissionsNode = getPermissionsNode(obj);
|
|
177
|
+
return resolveHTTPPermissions(state, route, tags, explicitPermissionsNode, checker);
|
|
178
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { InspectorState } from '../types.js';
|
|
2
|
+
import { MiddlewareMetadata, PermissionMetadata } from '@pikku/core';
|
|
3
|
+
/**
|
|
4
|
+
* Helper to extract wire-level middleware/permission names from metadata.
|
|
5
|
+
* Only extracts type:'wire' variants (individual middleware/permissions).
|
|
6
|
+
* Skips type:'http' and type:'tag' (reference groups, not individuals).
|
|
7
|
+
*/
|
|
8
|
+
export declare function extractWireNames(list?: MiddlewareMetadata[] | PermissionMetadata[]): string[];
|
|
9
|
+
/**
|
|
10
|
+
* Aggregates all required services from wired functions, middleware, and permissions.
|
|
11
|
+
* Must be called after AST traversal completes.
|
|
12
|
+
*
|
|
13
|
+
* Note: usedFunctions, usedMiddleware, and usedPermissions are tracked directly
|
|
14
|
+
* in the add-* methods during AST traversal for efficiency.
|
|
15
|
+
*/
|
|
16
|
+
export declare function aggregateRequiredServices(state: InspectorState): void;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helper to extract wire-level middleware/permission names from metadata.
|
|
3
|
+
* Only extracts type:'wire' variants (individual middleware/permissions).
|
|
4
|
+
* Skips type:'http' and type:'tag' (reference groups, not individuals).
|
|
5
|
+
*/
|
|
6
|
+
export function extractWireNames(list) {
|
|
7
|
+
if (!list)
|
|
8
|
+
return [];
|
|
9
|
+
return list
|
|
10
|
+
.filter((item) => item.type === 'wire')
|
|
11
|
+
.map((item) => item.name);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Helper to expand middleware/permission groups into individual names
|
|
15
|
+
* and add their services to the aggregation.
|
|
16
|
+
* This handles tag-based and HTTP-pattern-based middleware/permission groups.
|
|
17
|
+
*/
|
|
18
|
+
function expandAndAddGroupServices(list, state, addServices, isMiddleware) {
|
|
19
|
+
if (!list)
|
|
20
|
+
return;
|
|
21
|
+
for (const item of list) {
|
|
22
|
+
if (item.type === 'tag') {
|
|
23
|
+
// Expand tag-based group
|
|
24
|
+
const groupMeta = isMiddleware
|
|
25
|
+
? state.middleware.tagMiddleware.get(item.tag)
|
|
26
|
+
: state.permissions.tagPermissions.get(item.tag);
|
|
27
|
+
if (groupMeta?.services) {
|
|
28
|
+
addServices(groupMeta.services);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
else if (item.type === 'http' && 'route' in item) {
|
|
32
|
+
// Expand HTTP-pattern-based group
|
|
33
|
+
const groupMeta = isMiddleware
|
|
34
|
+
? state.http.routeMiddleware.get(item.route)
|
|
35
|
+
: state.http.routePermissions.get(item.route);
|
|
36
|
+
if (groupMeta?.services) {
|
|
37
|
+
addServices(groupMeta.services);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Aggregates all required services from wired functions, middleware, and permissions.
|
|
44
|
+
* Must be called after AST traversal completes.
|
|
45
|
+
*
|
|
46
|
+
* Note: usedFunctions, usedMiddleware, and usedPermissions are tracked directly
|
|
47
|
+
* in the add-* methods during AST traversal for efficiency.
|
|
48
|
+
*/
|
|
49
|
+
export function aggregateRequiredServices(state) {
|
|
50
|
+
const { requiredServices, usedFunctions, usedMiddleware, usedPermissions } = state.serviceAggregation;
|
|
51
|
+
// Internal services (always excluded from tree-shaking)
|
|
52
|
+
const internalServices = new Set(['rpc', 'mcp', 'channel', 'userSession']);
|
|
53
|
+
const addServices = (services) => {
|
|
54
|
+
if (!services || !services.services)
|
|
55
|
+
return;
|
|
56
|
+
services.services.forEach((service) => {
|
|
57
|
+
if (!internalServices.has(service)) {
|
|
58
|
+
requiredServices.add(service);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
// 1. Services from used functions
|
|
63
|
+
usedFunctions.forEach((funcName) => {
|
|
64
|
+
const funcMeta = state.functions.meta[funcName];
|
|
65
|
+
if (funcMeta?.services) {
|
|
66
|
+
addServices(funcMeta.services);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
// 2. Services from used middleware (individual + groups)
|
|
70
|
+
usedMiddleware.forEach((middlewareName) => {
|
|
71
|
+
const middlewareMeta = state.middleware.meta[middlewareName];
|
|
72
|
+
if (middlewareMeta?.services) {
|
|
73
|
+
addServices(middlewareMeta.services);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
// 3. Services from used permissions (individual + groups)
|
|
77
|
+
usedPermissions.forEach((permissionName) => {
|
|
78
|
+
const permissionMeta = state.permissions.meta[permissionName];
|
|
79
|
+
if (permissionMeta?.services) {
|
|
80
|
+
addServices(permissionMeta.services);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
// 4. Services from middleware/permission groups used in wirings
|
|
84
|
+
// We need to check all wirings and expand any tag/HTTP-pattern groups they use
|
|
85
|
+
for (const method of [
|
|
86
|
+
'get',
|
|
87
|
+
'post',
|
|
88
|
+
'put',
|
|
89
|
+
'patch',
|
|
90
|
+
'delete',
|
|
91
|
+
'head',
|
|
92
|
+
'options',
|
|
93
|
+
]) {
|
|
94
|
+
for (const routeMeta of Object.values(state.http.meta[method])) {
|
|
95
|
+
expandAndAddGroupServices(routeMeta.middleware, state, addServices, true);
|
|
96
|
+
expandAndAddGroupServices(routeMeta.permissions, state, addServices, false);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// Also check other wiring types (channels, queues, schedulers, MCP)
|
|
100
|
+
for (const channelMeta of Object.values(state.channels.meta)) {
|
|
101
|
+
expandAndAddGroupServices(channelMeta.middleware, state, addServices, true);
|
|
102
|
+
expandAndAddGroupServices(channelMeta.permissions, state, addServices, false);
|
|
103
|
+
}
|
|
104
|
+
for (const queueMeta of Object.values(state.queueWorkers.meta)) {
|
|
105
|
+
expandAndAddGroupServices(queueMeta.middleware, state, addServices, true);
|
|
106
|
+
// expandAndAddGroupServices(queueMeta.permissions, state, addServices, false)
|
|
107
|
+
}
|
|
108
|
+
for (const scheduleMeta of Object.values(state.scheduledTasks.meta)) {
|
|
109
|
+
expandAndAddGroupServices(scheduleMeta.middleware, state, addServices, true);
|
|
110
|
+
// expandAndAddGroupServices(scheduleMeta.permissions, state, addServices, false)
|
|
111
|
+
}
|
|
112
|
+
for (const toolMeta of Object.values(state.mcpEndpoints.toolsMeta)) {
|
|
113
|
+
expandAndAddGroupServices(toolMeta.middleware, state, addServices, true);
|
|
114
|
+
expandAndAddGroupServices(toolMeta.permissions, state, addServices, false);
|
|
115
|
+
}
|
|
116
|
+
for (const promptMeta of Object.values(state.mcpEndpoints.promptsMeta)) {
|
|
117
|
+
expandAndAddGroupServices(promptMeta.middleware, state, addServices, true);
|
|
118
|
+
expandAndAddGroupServices(promptMeta.permissions, state, addServices, false);
|
|
119
|
+
}
|
|
120
|
+
for (const resourceMeta of Object.values(state.mcpEndpoints.resourcesMeta)) {
|
|
121
|
+
expandAndAddGroupServices(resourceMeta.middleware, state, addServices, true);
|
|
122
|
+
expandAndAddGroupServices(resourceMeta.permissions, state, addServices, false);
|
|
123
|
+
}
|
|
124
|
+
// 5. Services from session service factories
|
|
125
|
+
for (const singletonServices of state.sessionServicesMeta.values()) {
|
|
126
|
+
singletonServices.forEach((service) => {
|
|
127
|
+
if (!internalServices.has(service)) {
|
|
128
|
+
requiredServices.add(service);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|