@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.
Files changed (84) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/add/add-channel.d.ts +5 -1
  3. package/dist/add/add-channel.js +51 -32
  4. package/dist/add/add-cli.d.ts +4 -0
  5. package/dist/add/add-cli.js +128 -23
  6. package/dist/add/add-file-extends-core-type.js +3 -2
  7. package/dist/add/add-file-with-factory.d.ts +2 -2
  8. package/dist/add/add-file-with-factory.js +87 -1
  9. package/dist/add/add-functions.js +52 -5
  10. package/dist/add/add-http-route.js +19 -12
  11. package/dist/add/add-mcp-prompt.js +20 -13
  12. package/dist/add/add-mcp-resource.js +24 -14
  13. package/dist/add/add-mcp-tool.js +23 -13
  14. package/dist/add/add-middleware.js +51 -12
  15. package/dist/add/add-permission.d.ts +1 -2
  16. package/dist/add/add-permission.js +275 -19
  17. package/dist/add/add-queue-worker.js +10 -12
  18. package/dist/add/add-schedule.js +9 -10
  19. package/dist/error-codes.d.ts +35 -0
  20. package/dist/error-codes.js +40 -0
  21. package/dist/index.d.ts +4 -0
  22. package/dist/index.js +3 -0
  23. package/dist/inspector.js +20 -1
  24. package/dist/types.d.ts +31 -3
  25. package/dist/utils/ensure-function-metadata.d.ts +6 -0
  26. package/dist/utils/ensure-function-metadata.js +18 -0
  27. package/dist/utils/extract-function-name.d.ts +2 -2
  28. package/dist/utils/extract-function-name.js +13 -8
  29. package/dist/utils/filter-inspector-state.d.ts +6 -0
  30. package/dist/utils/filter-inspector-state.js +382 -0
  31. package/dist/utils/filter-utils.d.ts +10 -0
  32. package/dist/utils/filter-utils.js +66 -2
  33. package/dist/utils/find-root-dir.d.ts +23 -0
  34. package/dist/utils/find-root-dir.js +55 -0
  35. package/dist/utils/get-files-and-methods.d.ts +2 -1
  36. package/dist/utils/get-files-and-methods.js +4 -3
  37. package/dist/utils/get-property-value.d.ts +9 -0
  38. package/dist/utils/get-property-value.js +20 -0
  39. package/dist/utils/middleware.d.ts +1 -1
  40. package/dist/utils/middleware.js +7 -7
  41. package/dist/utils/permissions.d.ts +43 -0
  42. package/dist/utils/permissions.js +178 -0
  43. package/dist/utils/post-process.d.ts +16 -0
  44. package/dist/utils/post-process.js +132 -0
  45. package/dist/utils/serialize-inspector-state.d.ts +179 -0
  46. package/dist/utils/serialize-inspector-state.js +170 -0
  47. package/dist/visit.js +3 -2
  48. package/package.json +4 -4
  49. package/src/add/add-channel.ts +92 -40
  50. package/src/add/add-cli.ts +188 -29
  51. package/src/add/add-file-extends-core-type.ts +5 -2
  52. package/src/add/add-file-with-factory.ts +114 -2
  53. package/src/add/add-functions.ts +60 -5
  54. package/src/add/add-http-route.ts +46 -21
  55. package/src/add/add-mcp-prompt.ts +42 -21
  56. package/src/add/add-mcp-prompt.ts.tmp +0 -0
  57. package/src/add/add-mcp-resource.ts +50 -24
  58. package/src/add/add-mcp-resource.ts.tmp +0 -0
  59. package/src/add/add-mcp-tool.ts +48 -21
  60. package/src/add/add-middleware.ts +74 -15
  61. package/src/add/add-permission.ts +364 -22
  62. package/src/add/add-queue-worker.ts +22 -25
  63. package/src/add/add-schedule.ts +19 -20
  64. package/src/error-codes.ts +43 -0
  65. package/src/index.ts +7 -0
  66. package/src/inspector.ts +22 -1
  67. package/src/types.ts +38 -3
  68. package/src/utils/ensure-function-metadata.ts +24 -0
  69. package/src/utils/extract-function-name.ts +20 -8
  70. package/src/utils/filter-inspector-state.test.ts +1433 -0
  71. package/src/utils/filter-inspector-state.ts +526 -0
  72. package/src/utils/filter-utils.test.ts +350 -1
  73. package/src/utils/filter-utils.ts +82 -2
  74. package/src/utils/find-root-dir.ts +68 -0
  75. package/src/utils/get-files-and-methods.ts +10 -2
  76. package/src/utils/get-property-value.ts +27 -0
  77. package/src/utils/middleware.ts +14 -7
  78. package/src/utils/permissions.test.ts +327 -0
  79. package/src/utils/permissions.ts +262 -0
  80. package/src/utils/post-process.ts +178 -0
  81. package/src/utils/serialize-inspector-state.ts +375 -0
  82. package/src/utils/test-data/inspector-state.json +1680 -0
  83. package/src/visit.ts +4 -2
  84. package/tsconfig.tsbuildinfo +1 -1
@@ -1,9 +1,63 @@
1
1
  import * as ts from 'typescript';
2
- export const addFileWithFactory = (node, checker, methods = new Map(), expectedTypeName) => {
2
+ import { extractServicesFromFunction } from '../utils/extract-services.js';
3
+ // Mapping of wrapper function names to their corresponding types
4
+ const wrapperFunctionMap = {
5
+ pikkuConfig: 'CreateConfig',
6
+ pikkuServices: 'CreateSingletonServices',
7
+ pikkuSessionServices: 'CreateSessionServices',
8
+ };
9
+ export const addFileWithFactory = (node, checker, methods = new Map(), expectedTypeName, state) => {
3
10
  if (ts.isVariableDeclaration(node)) {
4
11
  const fileName = node.getSourceFile().fileName;
5
12
  const variableTypeNode = node.type;
6
13
  const variableName = node.name.getText();
14
+ // Check for wrapper function calls FIRST (e.g., pikkuConfig(...), pikkuServices(...))
15
+ // This handles both cases: with and without explicit type annotations
16
+ if (node.initializer && ts.isCallExpression(node.initializer)) {
17
+ const callExpression = node.initializer;
18
+ const expression = callExpression.expression;
19
+ if (ts.isIdentifier(expression)) {
20
+ const wrapperFunctionName = expression.text;
21
+ const inferredType = wrapperFunctionMap[wrapperFunctionName];
22
+ if (inferredType === expectedTypeName) {
23
+ // Get the type declaration path from the wrapper function
24
+ const typeSymbol = checker.getSymbolAtLocation(expression);
25
+ let typeDeclarationPath = null;
26
+ if (typeSymbol &&
27
+ typeSymbol.declarations &&
28
+ typeSymbol.declarations[0]) {
29
+ const declaration = typeSymbol.declarations[0];
30
+ const sourceFile = declaration.getSourceFile();
31
+ typeDeclarationPath = sourceFile.fileName;
32
+ }
33
+ const variables = methods.get(fileName) || [];
34
+ variables.push({
35
+ variable: variableName,
36
+ type: inferredType,
37
+ typePath: typeDeclarationPath,
38
+ });
39
+ methods.set(fileName, variables);
40
+ // Extract singleton services for CreateSessionServices factories
41
+ if (expectedTypeName === 'CreateSessionServices' &&
42
+ state &&
43
+ callExpression.arguments.length > 0) {
44
+ const firstArg = callExpression.arguments[0];
45
+ let functionNode;
46
+ if (ts.isArrowFunction(firstArg)) {
47
+ functionNode = firstArg;
48
+ }
49
+ else if (ts.isFunctionExpression(firstArg)) {
50
+ functionNode = firstArg;
51
+ }
52
+ if (functionNode) {
53
+ const servicesMeta = extractServicesFromFunction(functionNode);
54
+ state.sessionServicesMeta.set(variableName, servicesMeta.services);
55
+ }
56
+ }
57
+ return; // Early return since we found a match
58
+ }
59
+ }
60
+ }
7
61
  if (variableTypeNode && ts.isTypeReferenceNode(variableTypeNode)) {
8
62
  const typeNameNode = variableTypeNode.typeName || null;
9
63
  let typeDeclarationPath = null;
@@ -23,6 +77,22 @@ export const addFileWithFactory = (node, checker, methods = new Map(), expectedT
23
77
  typePath: typeDeclarationPath,
24
78
  });
25
79
  methods.set(fileName, variables);
80
+ // Extract singleton services for CreateSessionServices factories
81
+ if (expectedTypeName === 'CreateSessionServices' &&
82
+ state &&
83
+ node.initializer) {
84
+ let functionNode;
85
+ if (ts.isArrowFunction(node.initializer)) {
86
+ functionNode = node.initializer;
87
+ }
88
+ else if (ts.isFunctionExpression(node.initializer)) {
89
+ functionNode = node.initializer;
90
+ }
91
+ if (functionNode) {
92
+ const servicesMeta = extractServicesFromFunction(functionNode);
93
+ state.sessionServicesMeta.set(variableName, servicesMeta.services);
94
+ }
95
+ }
26
96
  }
27
97
  // Handle qualified type names if necessary
28
98
  else if (ts.isQualifiedName(typeNameNode)) {
@@ -41,6 +111,22 @@ export const addFileWithFactory = (node, checker, methods = new Map(), expectedT
41
111
  typePath: typeDeclarationPath,
42
112
  });
43
113
  methods.set(fileName, variables);
114
+ // Extract singleton services for CreateSessionServices factories
115
+ if (expectedTypeName === 'CreateSessionServices' &&
116
+ state &&
117
+ node.initializer) {
118
+ let functionNode;
119
+ if (ts.isArrowFunction(node.initializer)) {
120
+ functionNode = node.initializer;
121
+ }
122
+ else if (ts.isFunctionExpression(node.initializer)) {
123
+ functionNode = node.initializer;
124
+ }
125
+ if (functionNode) {
126
+ const servicesMeta = extractServicesFromFunction(functionNode);
127
+ state.sessionServicesMeta.set(variableName, servicesMeta.services);
128
+ }
129
+ }
44
130
  }
45
131
  }
46
132
  }
@@ -16,7 +16,7 @@ const nullifyTypes = (type) => {
16
16
  }
17
17
  return type;
18
18
  };
19
- const resolveTypeImports = (type, resolvedTypes, isCustom) => {
19
+ const resolveTypeImports = (type, resolvedTypes, isCustom, checker) => {
20
20
  const types = [];
21
21
  const visitType = (currentType) => {
22
22
  const symbol = currentType.aliasSymbol || currentType.getSymbol();
@@ -27,9 +27,12 @@ const resolveTypeImports = (type, resolvedTypes, isCustom) => {
27
27
  const sourceFile = declaration.getSourceFile();
28
28
  const path = sourceFile.fileName;
29
29
  // Skip built-in utility types or TypeScript lib types
30
+ // Skip enum members (but not the enum type itself)
31
+ const isEnumMember = declaration && ts.isEnumMember(declaration);
30
32
  if (!path.includes('node_modules/typescript') &&
31
33
  symbol.getName() !== '__type' &&
32
- !isPrimitiveType(currentType)) {
34
+ !isPrimitiveType(currentType) &&
35
+ !isEnumMember) {
33
36
  const originalName = symbol.getName();
34
37
  // Check if the type is already in the map
35
38
  let uniqueName = resolvedTypes.exists(originalName, path);
@@ -76,6 +79,26 @@ const resolveTypeImports = (type, resolvedTypes, isCustom) => {
76
79
  const typeRef = currentType;
77
80
  typeRef.typeArguments?.forEach(visitType);
78
81
  }
82
+ // Handle anonymous object types with enum properties (e.g., { userType: UserType })
83
+ // Only traverse into enum property types to avoid over-importing other named types
84
+ if (currentType.flags & ts.TypeFlags.Object) {
85
+ const objectType = currentType;
86
+ const typeSymbol = objectType.getSymbol();
87
+ // Only traverse properties for anonymous object types (no symbol or __type symbol)
88
+ // Skip named types, interfaces, and enums to avoid over-importing
89
+ const isAnonymousObject = !typeSymbol || typeSymbol.getName() === '__type';
90
+ if (isAnonymousObject) {
91
+ const properties = objectType.getProperties();
92
+ for (const prop of properties) {
93
+ if (prop.valueDeclaration) {
94
+ const propType = checker.getTypeOfSymbolAtLocation(prop, prop.valueDeclaration);
95
+ if (propType) {
96
+ visitType(propType);
97
+ }
98
+ }
99
+ }
100
+ }
101
+ }
79
102
  };
80
103
  visitType(type);
81
104
  return types;
@@ -125,7 +148,7 @@ const getNamesAndTypes = (checker, typesMap, direction, funcName, type) => {
125
148
  const aliasName = funcName.charAt(0).toUpperCase() + funcName.slice(1) + direction;
126
149
  // record the alias in your TypesMap
127
150
  const references = rawTypes
128
- .map((t) => resolveTypeImports(t, typesMap, true))
151
+ .map((t) => resolveTypeImports(t, typesMap, true, checker))
129
152
  .flat();
130
153
  typesMap.addCustomType(aliasName, aliasType, references);
131
154
  return {
@@ -144,7 +167,7 @@ const getNamesAndTypes = (checker, typesMap, direction, funcName, type) => {
144
167
  return name;
145
168
  }
146
169
  // non-primitive: import/alias it inline
147
- return resolveTypeImports(t, typesMap, false);
170
+ return resolveTypeImports(t, typesMap, false, checker);
148
171
  })
149
172
  .flat();
150
173
  return {
@@ -207,7 +230,7 @@ export const addFunctions = (logger, node, checker, state) => {
207
230
  }
208
231
  if (args.length === 0)
209
232
  return;
210
- const { pikkuFuncName, name, explicitName, exportedName } = extractFunctionName(node, checker);
233
+ const { pikkuFuncName, name, explicitName, exportedName } = extractFunctionName(node, checker, state.rootDir);
211
234
  let tags;
212
235
  let expose;
213
236
  let docs;
@@ -226,6 +249,17 @@ export const addFunctions = (logger, node, checker, state) => {
226
249
  if (!fnProp ||
227
250
  (!ts.isArrowFunction(fnProp) && !ts.isFunctionExpression(fnProp))) {
228
251
  logger.error(`• No valid 'func' property found for ${pikkuFuncName}.`);
252
+ // Create stub metadata to prevent "function not found" errors in wirings
253
+ state.functions.meta[pikkuFuncName] = {
254
+ pikkuFuncName,
255
+ name,
256
+ services: { optimized: false, services: [] },
257
+ inputSchemaName: null,
258
+ outputSchemaName: null,
259
+ inputs: [],
260
+ outputs: [],
261
+ middleware: undefined,
262
+ };
229
263
  return;
230
264
  }
231
265
  handlerNode = fnProp;
@@ -233,6 +267,17 @@ export const addFunctions = (logger, node, checker, state) => {
233
267
  if (!ts.isArrowFunction(handlerNode) &&
234
268
  !ts.isFunctionExpression(handlerNode)) {
235
269
  logger.error(`• Handler for ${name} is not a function.`);
270
+ // Create stub metadata to prevent "function not found" errors in wirings
271
+ state.functions.meta[pikkuFuncName] = {
272
+ pikkuFuncName,
273
+ name,
274
+ services: { optimized: false, services: [] },
275
+ inputSchemaName: null,
276
+ outputSchemaName: null,
277
+ inputs: [],
278
+ outputs: [],
279
+ middleware: undefined,
280
+ };
236
281
  return;
237
282
  }
238
283
  const services = {
@@ -324,6 +369,8 @@ export const addFunctions = (logger, node, checker, state) => {
324
369
  path: node.getSourceFile().fileName,
325
370
  exportedName,
326
371
  });
372
+ // Track exposed RPC function for service aggregation
373
+ state.serviceAggregation.usedFunctions.add(pikkuFuncName);
327
374
  }
328
375
  // We add it to internal meta to allow autocomplete for everything
329
376
  state.rpc.internalMeta[name] = pikkuFuncName;
@@ -1,11 +1,13 @@
1
1
  import * as ts from 'typescript';
2
- import { getPropertyValue } from '../utils/get-property-value.js';
2
+ import { getPropertyValue, getPropertyTags, } from '../utils/get-property-value.js';
3
3
  import { pathToRegexp } from 'path-to-regexp';
4
- import { PikkuWiringTypes } from '@pikku/core';
5
4
  import { extractFunctionName } from '../utils/extract-function-name.js';
6
5
  import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
7
- import { matchesFilters } from '../utils/filter-utils.js';
8
6
  import { resolveHTTPMiddlewareFromObject } from '../utils/middleware.js';
7
+ import { resolveHTTPPermissionsFromObject } from '../utils/permissions.js';
8
+ import { extractWireNames } from '../utils/post-process.js';
9
+ import { ensureFunctionMetadata } from '../utils/ensure-function-metadata.js';
10
+ import { ErrorCode } from '../error-codes.js';
9
11
  /**
10
12
  * Populate metaInputTypes for a given route based on method, input type,
11
13
  * query and params. Returns undefined (we only mutate metaTypes).
@@ -46,23 +48,21 @@ export const addHTTPRoute = (logger, node, checker, state, options) => {
46
48
  const params = keys.filter((k) => k.type === 'param').map((k) => k.name);
47
49
  const method = getPropertyValue(obj, 'method')?.toLowerCase() || 'get';
48
50
  const docs = getPropertyValue(obj, 'docs') || undefined;
49
- const tags = getPropertyValue(obj, 'tags') || undefined;
51
+ const tags = getPropertyTags(obj, 'HTTP route', route, logger);
50
52
  const query = getPropertyValue(obj, 'query') || [];
51
- const filePath = node.getSourceFile().fileName;
52
- if (!matchesFilters(options.filters || {}, { tags }, { type: PikkuWiringTypes.http, name: route, filePath }, logger)) {
53
- return;
54
- }
55
- // --- find the referenced function ---
53
+ // --- find the referenced function name first for filtering ---
56
54
  const funcInitializer = getPropertyAssignmentInitializer(obj, 'func', true, checker);
57
55
  if (!funcInitializer) {
58
- console.error(`• No valid 'func' property for route '${route}'.`);
56
+ logger.critical(ErrorCode.MISSING_FUNC, `No valid 'func' property for route '${route}'.`);
59
57
  return;
60
58
  }
61
- const funcName = extractFunctionName(funcInitializer, checker).pikkuFuncName;
59
+ const funcName = extractFunctionName(funcInitializer, checker, state.rootDir).pikkuFuncName;
60
+ // Ensure function metadata exists (creates stub for inline functions)
61
+ ensureFunctionMetadata(state, funcName, route);
62
62
  // lookup existing function metadata
63
63
  const fnMeta = state.functions.meta[funcName];
64
64
  if (!fnMeta) {
65
- console.error(`• No function metadata found for '${funcName}'.`);
65
+ logger.critical(ErrorCode.FUNCTION_METADATA_NOT_FOUND, `No function metadata found for '${funcName}'.`);
66
66
  return;
67
67
  }
68
68
  const input = fnMeta.inputs?.[0] || null;
@@ -70,6 +70,12 @@ export const addHTTPRoute = (logger, node, checker, state, options) => {
70
70
  const inputTypes = getInputTypes(state.http.metaInputTypes, method, input, query, params);
71
71
  // --- resolve middleware ---
72
72
  const middleware = resolveHTTPMiddlewareFromObject(state, route, obj, tags, checker);
73
+ // --- resolve permissions ---
74
+ const permissions = resolveHTTPPermissionsFromObject(state, route, obj, tags, checker);
75
+ // --- track used functions/middleware/permissions for service aggregation ---
76
+ state.serviceAggregation.usedFunctions.add(funcName);
77
+ extractWireNames(middleware).forEach((name) => state.serviceAggregation.usedMiddleware.add(name));
78
+ extractWireNames(permissions).forEach((name) => state.serviceAggregation.usedPermissions.add(name));
73
79
  // --- record route ---
74
80
  state.http.files.add(node.getSourceFile().fileName);
75
81
  state.http.meta[method][route] = {
@@ -82,5 +88,6 @@ export const addHTTPRoute = (logger, node, checker, state, options) => {
82
88
  docs,
83
89
  tags,
84
90
  middleware,
91
+ permissions,
85
92
  };
86
93
  };
@@ -1,10 +1,12 @@
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';
3
+ import { extractWireNames } from '../utils/post-process.js';
4
+ import { ensureFunctionMetadata } from '../utils/ensure-function-metadata.js';
4
5
  import { extractFunctionName } from '../utils/extract-function-name.js';
5
6
  import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
6
- import { matchesFilters } from '../utils/filter-utils.js';
7
7
  import { resolveMiddleware } from '../utils/middleware.js';
8
+ import { resolvePermissions } from '../utils/permissions.js';
9
+ import { ErrorCode } from '../error-codes.js';
8
10
  export const addMCPPrompt = (logger, node, checker, state, options) => {
9
11
  if (!ts.isCallExpression(node)) {
10
12
  return;
@@ -23,35 +25,39 @@ export const addMCPPrompt = (logger, node, checker, state, options) => {
23
25
  const obj = firstArg;
24
26
  const nameValue = getPropertyValue(obj, 'name');
25
27
  const descriptionValue = getPropertyValue(obj, 'description');
26
- const tags = getPropertyValue(obj, 'tags') || undefined;
28
+ const tags = getPropertyTags(obj, 'MCP prompt', nameValue, logger);
27
29
  const funcInitializer = getPropertyAssignmentInitializer(obj, 'func', true, checker);
28
30
  if (!funcInitializer) {
29
- console.error(`• No valid 'func' property for MCP prompt '${nameValue}'.`);
31
+ logger.critical(ErrorCode.MISSING_FUNC, `No valid 'func' property for MCP prompt '${nameValue}'.`);
30
32
  return;
31
33
  }
32
- const pikkuFuncName = extractFunctionName(funcInitializer, checker).pikkuFuncName;
34
+ const pikkuFuncName = extractFunctionName(funcInitializer, checker, state.rootDir).pikkuFuncName;
35
+ // Ensure function metadata exists (creates stub for inline functions)
36
+ ensureFunctionMetadata(state, pikkuFuncName, nameValue || undefined);
33
37
  if (!nameValue) {
34
- console.error(`• MCP prompt is missing the required 'name' property.`);
38
+ logger.critical(ErrorCode.MISSING_NAME, "MCP prompt is missing the required 'name' property.");
35
39
  return;
36
40
  }
37
41
  if (!descriptionValue) {
38
- console.error(`• MCP prompt '${nameValue}' is missing a description.`);
39
- return;
40
- }
41
- const filePath = node.getSourceFile().fileName;
42
- if (!matchesFilters(options.filters || {}, { tags }, { type: PikkuWiringTypes.mcp, name: nameValue, filePath }, logger)) {
42
+ logger.critical(ErrorCode.MISSING_DESCRIPTION, `MCP prompt '${nameValue}' is missing a description.`);
43
43
  return;
44
44
  }
45
45
  // lookup existing function metadata
46
46
  const fnMeta = state.functions.meta[pikkuFuncName];
47
47
  if (!fnMeta) {
48
- console.error(`• No function metadata found for '${pikkuFuncName}'.`);
48
+ logger.critical(ErrorCode.FUNCTION_METADATA_NOT_FOUND, `No function metadata found for '${pikkuFuncName}'.`);
49
49
  return;
50
50
  }
51
51
  const inputSchema = fnMeta.inputs?.[0] || null;
52
52
  const outputSchema = fnMeta.outputs?.[0] || null;
53
53
  // --- resolve middleware ---
54
54
  const middleware = resolveMiddleware(state, obj, tags, checker);
55
+ // --- resolve permissions ---
56
+ const permissions = resolvePermissions(state, obj, tags, checker);
57
+ // --- track used functions/middleware/permissions for service aggregation ---
58
+ state.serviceAggregation.usedFunctions.add(pikkuFuncName);
59
+ extractWireNames(middleware).forEach((name) => state.serviceAggregation.usedMiddleware.add(name));
60
+ extractWireNames(permissions).forEach((name) => state.serviceAggregation.usedPermissions.add(name));
55
61
  state.mcpEndpoints.files.add(node.getSourceFile().fileName);
56
62
  state.mcpEndpoints.promptsMeta[nameValue] = {
57
63
  pikkuFuncName,
@@ -62,6 +68,7 @@ export const addMCPPrompt = (logger, node, checker, state, options) => {
62
68
  outputSchema,
63
69
  arguments: [], // Will be populated by CLI during serialization
64
70
  middleware,
71
+ permissions,
65
72
  };
66
73
  }
67
74
  };
@@ -1,10 +1,12 @@
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';
3
+ import { extractWireNames } from '../utils/post-process.js';
4
+ import { ensureFunctionMetadata } from '../utils/ensure-function-metadata.js';
4
5
  import { extractFunctionName } from '../utils/extract-function-name.js';
5
6
  import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
6
- import { matchesFilters } from '../utils/filter-utils.js';
7
7
  import { resolveMiddleware } from '../utils/middleware.js';
8
+ import { resolvePermissions } from '../utils/permissions.js';
9
+ import { ErrorCode } from '../error-codes.js';
8
10
  export const addMCPResource = (logger, node, checker, state, options) => {
9
11
  if (!ts.isCallExpression(node)) {
10
12
  return;
@@ -25,39 +27,46 @@ export const addMCPResource = (logger, node, checker, state, options) => {
25
27
  const titleValue = getPropertyValue(obj, 'title');
26
28
  const descriptionValue = getPropertyValue(obj, 'description');
27
29
  const streamingValue = getPropertyValue(obj, 'streaming');
28
- const tags = getPropertyValue(obj, 'tags') || undefined;
30
+ const tags = getPropertyTags(obj, 'MCP resource', uriValue, logger);
31
+ if (streamingValue === true) {
32
+ logger.warn(`MCP resource '${uriValue}' has streaming enabled, but streaming is not yet supported.`);
33
+ }
29
34
  const funcInitializer = getPropertyAssignmentInitializer(obj, 'func', true, checker);
30
35
  if (!funcInitializer) {
31
- console.error(`• No valid 'func' property for MCP resource '${uriValue}'.`);
36
+ logger.critical(ErrorCode.MISSING_FUNC, `No valid 'func' property for MCP resource '${uriValue}'.`);
32
37
  return;
33
38
  }
34
- const pikkuFuncName = extractFunctionName(funcInitializer, checker).pikkuFuncName;
39
+ const pikkuFuncName = extractFunctionName(funcInitializer, checker, state.rootDir).pikkuFuncName;
40
+ // Ensure function metadata exists (creates stub for inline functions)
41
+ ensureFunctionMetadata(state, pikkuFuncName, uriValue || undefined);
35
42
  if (!uriValue) {
36
- console.error(`• MCP resource is missing the required 'uri' property.`);
43
+ logger.critical(ErrorCode.MISSING_URI, "MCP resource is missing the required 'uri' property.");
37
44
  return;
38
45
  }
39
46
  if (!titleValue) {
40
- console.error(`• MCP resource '${uriValue}' is missing the required 'title' property.`);
47
+ logger.critical(ErrorCode.MISSING_TITLE, `MCP resource '${uriValue}' is missing the required 'title' property.`);
41
48
  return;
42
49
  }
43
50
  if (!descriptionValue) {
44
- console.error(`• MCP resource '${uriValue}' is missing a description.`);
45
- return;
46
- }
47
- const filePath = node.getSourceFile().fileName;
48
- if (!matchesFilters(options.filters || {}, { tags }, { type: PikkuWiringTypes.mcp, name: uriValue, filePath }, logger)) {
51
+ logger.critical(ErrorCode.MISSING_DESCRIPTION, `MCP resource '${uriValue}' is missing a description.`);
49
52
  return;
50
53
  }
51
54
  // lookup existing function metadata
52
55
  const fnMeta = state.functions.meta[pikkuFuncName];
53
56
  if (!fnMeta) {
54
- console.error(`• No function metadata found for '${pikkuFuncName}'.`);
57
+ logger.critical(ErrorCode.FUNCTION_METADATA_NOT_FOUND, `No function metadata found for '${pikkuFuncName}'.`);
55
58
  return;
56
59
  }
57
60
  const inputSchema = fnMeta.inputs?.[0] || null;
58
61
  const outputSchema = fnMeta.outputs?.[0] || null;
59
62
  // --- resolve middleware ---
60
63
  const middleware = resolveMiddleware(state, obj, tags, checker);
64
+ // --- resolve permissions ---
65
+ const permissions = resolvePermissions(state, obj, tags, checker);
66
+ // --- track used functions/middleware/permissions for service aggregation ---
67
+ state.serviceAggregation.usedFunctions.add(pikkuFuncName);
68
+ extractWireNames(middleware).forEach((name) => state.serviceAggregation.usedMiddleware.add(name));
69
+ extractWireNames(permissions).forEach((name) => state.serviceAggregation.usedPermissions.add(name));
61
70
  state.mcpEndpoints.files.add(node.getSourceFile().fileName);
62
71
  state.mcpEndpoints.resourcesMeta[uriValue] = {
63
72
  pikkuFuncName,
@@ -69,6 +78,7 @@ export const addMCPResource = (logger, node, checker, state, options) => {
69
78
  inputSchema,
70
79
  outputSchema,
71
80
  middleware,
81
+ permissions,
72
82
  };
73
83
  }
74
84
  };
@@ -1,10 +1,12 @@
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';
3
+ import { extractWireNames } from '../utils/post-process.js';
4
+ import { ensureFunctionMetadata } from '../utils/ensure-function-metadata.js';
4
5
  import { extractFunctionName } from '../utils/extract-function-name.js';
5
6
  import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
6
- import { matchesFilters } from '../utils/filter-utils.js';
7
7
  import { resolveMiddleware } from '../utils/middleware.js';
8
+ import { resolvePermissions } from '../utils/permissions.js';
9
+ import { ErrorCode } from '../error-codes.js';
8
10
  export const addMCPTool = (logger, node, checker, state, options) => {
9
11
  if (!ts.isCallExpression(node)) {
10
12
  return;
@@ -25,35 +27,42 @@ export const addMCPTool = (logger, node, checker, state, options) => {
25
27
  const titleValue = getPropertyValue(obj, 'title');
26
28
  const descriptionValue = getPropertyValue(obj, 'description');
27
29
  const streamingValue = getPropertyValue(obj, 'streaming');
28
- const tags = getPropertyValue(obj, 'tags') || undefined;
30
+ const tags = getPropertyTags(obj, 'MCP tool', nameValue, logger);
31
+ if (streamingValue === true) {
32
+ logger.warn(`MCP tool '${nameValue}' has streaming enabled, but streaming is not yet supported.`);
33
+ }
29
34
  const funcInitializer = getPropertyAssignmentInitializer(obj, 'func', true, checker);
30
35
  if (!funcInitializer) {
31
- console.error(`• No valid 'func' property for MCP tool '${nameValue}'.`);
36
+ logger.critical(ErrorCode.MISSING_FUNC, `No valid 'func' property for MCP tool '${nameValue}'.`);
32
37
  return;
33
38
  }
34
- const pikkuFuncName = extractFunctionName(funcInitializer, checker).pikkuFuncName;
39
+ const pikkuFuncName = extractFunctionName(funcInitializer, checker, state.rootDir).pikkuFuncName;
40
+ // Ensure function metadata exists (creates stub for inline functions)
41
+ ensureFunctionMetadata(state, pikkuFuncName, nameValue || undefined);
35
42
  if (!nameValue) {
36
- console.error(`• MCP tool is missing the required 'name' property.`);
43
+ logger.critical(ErrorCode.MISSING_NAME, "MCP tool is missing the required 'name' property.");
37
44
  return;
38
45
  }
39
46
  if (!descriptionValue) {
40
- console.error(`• MCP tool '${nameValue}' is missing a description.`);
41
- return;
42
- }
43
- const filePath = node.getSourceFile().fileName;
44
- if (!matchesFilters(options.filters || {}, { tags }, { type: PikkuWiringTypes.mcp, name: nameValue, filePath }, logger)) {
47
+ logger.critical(ErrorCode.MISSING_DESCRIPTION, `MCP tool '${nameValue}' is missing a description.`);
45
48
  return;
46
49
  }
47
50
  // lookup existing function metadata
48
51
  const fnMeta = state.functions.meta[pikkuFuncName];
49
52
  if (!fnMeta) {
50
- console.error(`• No function metadata found for '${pikkuFuncName}'.`);
53
+ logger.critical(ErrorCode.FUNCTION_METADATA_NOT_FOUND, `No function metadata found for '${pikkuFuncName}'.`);
51
54
  return;
52
55
  }
53
56
  const inputSchema = fnMeta.inputs?.[0] || null;
54
57
  const outputSchema = fnMeta.outputs?.[0] || null;
55
58
  // --- resolve middleware ---
56
59
  const middleware = resolveMiddleware(state, obj, tags, checker);
60
+ // --- resolve permissions ---
61
+ const permissions = resolvePermissions(state, obj, tags, checker);
62
+ // --- track used functions/middleware/permissions for service aggregation ---
63
+ state.serviceAggregation.usedFunctions.add(pikkuFuncName);
64
+ extractWireNames(middleware).forEach((name) => state.serviceAggregation.usedMiddleware.add(name));
65
+ extractWireNames(permissions).forEach((name) => state.serviceAggregation.usedPermissions.add(name));
57
66
  state.mcpEndpoints.files.add(node.getSourceFile().fileName);
58
67
  state.mcpEndpoints.toolsMeta[nameValue] = {
59
68
  pikkuFuncName,
@@ -65,6 +74,7 @@ export const addMCPTool = (logger, node, checker, state, options) => {
65
74
  inputSchema,
66
75
  outputSchema,
67
76
  middleware,
77
+ permissions,
68
78
  };
69
79
  }
70
80
  };