@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.
Files changed (84) hide show
  1. package/CHANGELOG.md +6 -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 +34 -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 +2 -1
  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 +45 -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 +8 -0
  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,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 extract first-arg destructuring
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 calls like pikkuPermission(...)
14
+ // only handle specific function calls
13
15
  if (!ts.isIdentifier(expression)) {
14
16
  return;
15
17
  }
16
- if (expression.text !== 'pikkuPermission') {
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
- const handlerNode = args[0];
20
- if (!handlerNode)
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
- if (!ts.isArrowFunction(handlerNode) &&
23
- !ts.isFunctionExpression(handlerNode)) {
24
- logger.error(`• Handler for pikkuPermission is not a function.`);
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 = getPropertyValue(obj, 'tags') || undefined;
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
- console.error(`• No valid 'func' property for queue processor '${queueName}'.`);
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
- console.error(`• No 'queueName' provided for queue processor function '${pikkuFuncName}'.`);
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,
@@ -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 = getPropertyValue(obj, 'tags') || undefined;
27
+ const tags = getPropertyTags(obj, 'Scheduler', nameValue, logger);
28
28
  const funcInitializer = getPropertyAssignmentInitializer(obj, 'func', true, checker);
29
29
  if (!funcInitializer) {
30
- console.error(`• No valid 'func' property for scheduled task '${nameValue}'.`);
30
+ logger.critical(ErrorCode.MISSING_FUNC, `No valid 'func' property for scheduled task '${nameValue}'.`);
31
31
  return;
32
32
  }
33
- const pikkuFuncName = extractFunctionName(funcInitializer, checker).pikkuFuncName;
33
+ const pikkuFuncName = extractFunctionName(funcInitializer, checker, state.rootDir).pikkuFuncName;
34
34
  if (!nameValue || !scheduleValue) {
35
35
  return;
36
36
  }
37
- const filePath = node.getSourceFile().fileName;
38
- if (!matchesFilters(options.filters || {}, { tags }, { type: PikkuWiringTypes.scheduler, name: nameValue, filePath }, logger)) {
39
- return;
40
- }
41
37
  // --- resolve middleware ---
42
38
  const middleware = resolveMiddleware(state, obj, tags, checker);
39
+ // --- track used functions/middleware for service aggregation ---
40
+ state.serviceAggregation.usedFunctions.add(pikkuFuncName);
41
+ extractWireNames(middleware).forEach((name) => state.serviceAggregation.usedMiddleware.add(name));
43
42
  state.scheduledTasks.files.add(node.getSourceFile().fileName);
44
43
  state.scheduledTasks.meta[nameValue] = {
45
44
  pikkuFuncName,
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Error code system for Pikku CLI and Inspector
3
+ *
4
+ * Each error has a unique code and links to documentation at pikku.dev
5
+ *
6
+ * Error codes use random 3-digit numbers to avoid implying a sequential order.
7
+ * Each code links to detailed documentation and troubleshooting steps.
8
+ */
9
+ export declare enum ErrorCode {
10
+ MISSING_NAME = "PKU111",
11
+ MISSING_DESCRIPTION = "PKU123",
12
+ MISSING_URI = "PKU220",
13
+ MISSING_FUNC = "PKU236",
14
+ INVALID_TAGS_TYPE = "PKU247",
15
+ INVALID_HANDLER = "PKU300",
16
+ MISSING_TITLE = "PKU370",
17
+ MISSING_QUEUE_NAME = "PKU384",
18
+ MISSING_CHANNEL_NAME = "PKU400",
19
+ CLI_CLIENTSIDE_RENDERER_HAS_SERVICES = "PKU672",
20
+ CONFIG_TYPE_NOT_FOUND = "PKU426",
21
+ CONFIG_TYPE_UNDEFINED = "PKU427",
22
+ SCHEMA_NO_ROOT = "PKU431",
23
+ SCHEMA_GENERATION_ERROR = "PKU456",
24
+ SCHEMA_LOAD_ERROR = "PKU488",
25
+ FUNCTION_METADATA_NOT_FOUND = "PKU559",
26
+ HANDLER_NOT_RESOLVED = "PKU568",
27
+ MIDDLEWARE_HANDLER_INVALID = "PKU685",
28
+ MIDDLEWARE_TAG_INVALID = "PKU715",
29
+ MIDDLEWARE_EMPTY_ARRAY = "PKU736",
30
+ MIDDLEWARE_PATTERN_INVALID = "PKU787",
31
+ PERMISSION_HANDLER_INVALID = "PKU835",
32
+ PERMISSION_TAG_INVALID = "PKU836",
33
+ PERMISSION_EMPTY_ARRAY = "PKU937",
34
+ PERMISSION_PATTERN_INVALID = "PKU975"
35
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Error code system for Pikku CLI and Inspector
3
+ *
4
+ * Each error has a unique code and links to documentation at pikku.dev
5
+ *
6
+ * Error codes use random 3-digit numbers to avoid implying a sequential order.
7
+ * Each code links to detailed documentation and troubleshooting steps.
8
+ */
9
+ export var ErrorCode;
10
+ (function (ErrorCode) {
11
+ // Validation errors
12
+ ErrorCode["MISSING_NAME"] = "PKU111";
13
+ ErrorCode["MISSING_DESCRIPTION"] = "PKU123";
14
+ ErrorCode["MISSING_URI"] = "PKU220";
15
+ ErrorCode["MISSING_FUNC"] = "PKU236";
16
+ ErrorCode["INVALID_TAGS_TYPE"] = "PKU247";
17
+ ErrorCode["INVALID_HANDLER"] = "PKU300";
18
+ ErrorCode["MISSING_TITLE"] = "PKU370";
19
+ ErrorCode["MISSING_QUEUE_NAME"] = "PKU384";
20
+ ErrorCode["MISSING_CHANNEL_NAME"] = "PKU400";
21
+ ErrorCode["CLI_CLIENTSIDE_RENDERER_HAS_SERVICES"] = "PKU672";
22
+ // Configuration errors
23
+ ErrorCode["CONFIG_TYPE_NOT_FOUND"] = "PKU426";
24
+ ErrorCode["CONFIG_TYPE_UNDEFINED"] = "PKU427";
25
+ ErrorCode["SCHEMA_NO_ROOT"] = "PKU431";
26
+ ErrorCode["SCHEMA_GENERATION_ERROR"] = "PKU456";
27
+ ErrorCode["SCHEMA_LOAD_ERROR"] = "PKU488";
28
+ // Function errors
29
+ ErrorCode["FUNCTION_METADATA_NOT_FOUND"] = "PKU559";
30
+ ErrorCode["HANDLER_NOT_RESOLVED"] = "PKU568";
31
+ // Middleware/Permission errors
32
+ ErrorCode["MIDDLEWARE_HANDLER_INVALID"] = "PKU685";
33
+ ErrorCode["MIDDLEWARE_TAG_INVALID"] = "PKU715";
34
+ ErrorCode["MIDDLEWARE_EMPTY_ARRAY"] = "PKU736";
35
+ ErrorCode["MIDDLEWARE_PATTERN_INVALID"] = "PKU787";
36
+ ErrorCode["PERMISSION_HANDLER_INVALID"] = "PKU835";
37
+ ErrorCode["PERMISSION_TAG_INVALID"] = "PKU836";
38
+ ErrorCode["PERMISSION_EMPTY_ARRAY"] = "PKU937";
39
+ ErrorCode["PERMISSION_PATTERN_INVALID"] = "PKU975";
40
+ })(ErrorCode || (ErrorCode = {}));
package/dist/index.d.ts CHANGED
@@ -4,3 +4,7 @@ export type { TypesMap } from './types-map.js';
4
4
  export type * from './types.js';
5
5
  export type { InspectorState } from './types.js';
6
6
  export type { FilesAndMethods, FilesAndMethodsErrors, } from './utils/get-files-and-methods.js';
7
+ export { ErrorCode } from './error-codes.js';
8
+ export { serializeInspectorState, deserializeInspectorState, } from './utils/serialize-inspector-state.js';
9
+ export type { SerializableInspectorState } from './utils/serialize-inspector-state.js';
10
+ export { filterInspectorState } from './utils/filter-inspector-state.js';
package/dist/index.js CHANGED
@@ -1,2 +1,5 @@
1
1
  export { inspect } from './inspector.js';
2
2
  export { getFilesAndMethods } from './utils/get-files-and-methods.js';
3
+ export { ErrorCode } from './error-codes.js';
4
+ export { serializeInspectorState, deserializeInspectorState, } from './utils/serialize-inspector-state.js';
5
+ export { filterInspectorState } from './utils/filter-inspector-state.js';
package/dist/inspector.js CHANGED
@@ -2,6 +2,8 @@ import * as ts from 'typescript';
2
2
  import { visitSetup, visitRoutes } from './visit.js';
3
3
  import { TypesMap } from './types-map.js';
4
4
  import { getFilesAndMethods } from './utils/get-files-and-methods.js';
5
+ import { findCommonAncestor } from './utils/find-root-dir.js';
6
+ import { aggregateRequiredServices } from './utils/post-process.js';
5
7
  export const inspect = (logger, routeFiles, options = {}) => {
6
8
  const program = ts.createProgram(routeFiles, {
7
9
  target: ts.ScriptTarget.ESNext,
@@ -9,13 +11,17 @@ export const inspect = (logger, routeFiles, options = {}) => {
9
11
  });
10
12
  const checker = program.getTypeChecker();
11
13
  const sourceFiles = program.getSourceFiles();
14
+ // Infer root directory from source files
15
+ const rootDir = findCommonAncestor(routeFiles);
12
16
  const state = {
17
+ rootDir,
13
18
  singletonServicesTypeImportMap: new Map(),
14
19
  sessionServicesTypeImportMap: new Map(),
15
20
  userSessionTypeImportMap: new Map(),
16
21
  configTypeImportMap: new Map(),
17
22
  singletonServicesFactories: new Map(),
18
23
  sessionServicesFactories: new Map(),
24
+ sessionServicesMeta: new Map(),
19
25
  configFactories: new Map(),
20
26
  filesAndMethods: {},
21
27
  filesAndMethodsErrors: new Map(),
@@ -38,6 +44,7 @@ export const inspect = (logger, routeFiles, options = {}) => {
38
44
  },
39
45
  files: new Set(),
40
46
  routeMiddleware: new Map(),
47
+ routePermissions: new Map(),
41
48
  },
42
49
  channels: {
43
50
  files: new Set(),
@@ -65,7 +72,10 @@ export const inspect = (logger, routeFiles, options = {}) => {
65
72
  files: new Set(),
66
73
  },
67
74
  cli: {
68
- meta: {},
75
+ meta: {
76
+ programs: {},
77
+ renderers: {},
78
+ },
69
79
  files: new Set(),
70
80
  },
71
81
  middleware: {
@@ -74,6 +84,13 @@ export const inspect = (logger, routeFiles, options = {}) => {
74
84
  },
75
85
  permissions: {
76
86
  meta: {},
87
+ tagPermissions: new Map(),
88
+ },
89
+ serviceAggregation: {
90
+ requiredServices: new Set(),
91
+ usedFunctions: new Set(),
92
+ usedMiddleware: new Set(),
93
+ usedPermissions: new Set(),
77
94
  },
78
95
  };
79
96
  // First sweep: add all functions
@@ -88,5 +105,7 @@ export const inspect = (logger, routeFiles, options = {}) => {
88
105
  const { result, errors } = getFilesAndMethods(state, options.types);
89
106
  state.filesAndMethods = result;
90
107
  state.filesAndMethodsErrors = errors;
108
+ // Post-processing: Aggregate required services from wired functions/middleware/permissions
109
+ aggregateRequiredServices(state);
91
110
  return state;
92
111
  };
package/dist/types.d.ts CHANGED
@@ -3,10 +3,11 @@ import { ChannelsMeta } from '@pikku/core/channel';
3
3
  import { HTTPWiringsMeta } from '@pikku/core/http';
4
4
  import { ScheduledTasksMeta } from '@pikku/core/scheduler';
5
5
  import { queueWorkersMeta } from '@pikku/core/queue';
6
- import { MCPResourceMeta, MCPToolMeta, MCPPromptMeta } from '@pikku/core';
7
- import { CLIMeta } from '@pikku/core';
6
+ import { MCPResourceMeta, MCPToolMeta, MCPPromptMeta } from '@pikku/core/mcp';
7
+ import { CLIMeta } from '@pikku/core/cli';
8
8
  import { TypesMap } from './types-map.js';
9
9
  import { FunctionsMeta, FunctionServicesMeta } from '@pikku/core';
10
+ import { ErrorCode } from './error-codes.js';
10
11
  export type PathToNameAndType = Map<string, {
11
12
  variable: string;
12
13
  type: string | null;
@@ -25,11 +26,20 @@ export interface MiddlewareGroupMeta {
25
26
  middlewareCount: number;
26
27
  isFactory: boolean;
27
28
  }
29
+ export interface PermissionGroupMeta {
30
+ exportName: string | null;
31
+ sourceFile: string;
32
+ position: number;
33
+ services: FunctionServicesMeta;
34
+ permissionCount: number;
35
+ isFactory: boolean;
36
+ }
28
37
  export interface InspectorHTTPState {
29
38
  metaInputTypes: MetaInputTypes;
30
39
  meta: HTTPWiringsMeta;
31
40
  files: Set<string>;
32
41
  routeMiddleware: Map<string, MiddlewareGroupMeta>;
42
+ routePermissions: Map<string, PermissionGroupMeta>;
33
43
  }
34
44
  export interface InspectorFunctionState {
35
45
  typesMap: TypesMap;
@@ -50,6 +60,8 @@ export interface InspectorMiddlewareState {
50
60
  position: number;
51
61
  exportedName: string | null;
52
62
  factory?: boolean;
63
+ name?: string;
64
+ description?: string;
53
65
  }>;
54
66
  tagMiddleware: Map<string, MiddlewareGroupMeta>;
55
67
  }
@@ -59,12 +71,19 @@ export interface InspectorPermissionState {
59
71
  sourceFile: string;
60
72
  position: number;
61
73
  exportedName: string | null;
74
+ factory?: boolean;
75
+ name?: string;
76
+ description?: string;
62
77
  }>;
78
+ tagPermissions: Map<string, PermissionGroupMeta>;
63
79
  }
64
80
  export type InspectorFilters = {
81
+ names?: string[];
65
82
  tags?: string[];
66
83
  types?: string[];
67
84
  directories?: string[];
85
+ httpRoutes?: string[];
86
+ httpMethods?: string[];
68
87
  };
69
88
  export type InspectorOptions = Partial<{
70
89
  types: Partial<{
@@ -73,13 +92,14 @@ export type InspectorOptions = Partial<{
73
92
  singletonServicesFactoryType: string;
74
93
  sessionServicesFactoryType: string;
75
94
  }>;
76
- filters: InspectorFilters;
77
95
  }>;
78
96
  export interface InspectorLogger {
79
97
  info: (message: string) => void;
80
98
  error: (message: string) => void;
81
99
  warn: (message: string) => void;
82
100
  debug: (message: string) => void;
101
+ critical: (code: ErrorCode, message: string) => void;
102
+ hasCriticalErrors: () => boolean;
83
103
  }
84
104
  export type AddWiring = (logger: InspectorLogger, node: ts.Node, checker: ts.TypeChecker, state: InspectorState, options: InspectorOptions) => void;
85
105
  export interface InspectorFilesAndMethods {
@@ -127,12 +147,14 @@ export interface InspectorFilesAndMethods {
127
147
  };
128
148
  }
129
149
  export interface InspectorState {
150
+ rootDir: string;
130
151
  singletonServicesTypeImportMap: PathToNameAndType;
131
152
  sessionServicesTypeImportMap: PathToNameAndType;
132
153
  userSessionTypeImportMap: PathToNameAndType;
133
154
  configTypeImportMap: PathToNameAndType;
134
155
  singletonServicesFactories: PathToNameAndType;
135
156
  sessionServicesFactories: PathToNameAndType;
157
+ sessionServicesMeta: Map<string, string[]>;
136
158
  configFactories: PathToNameAndType;
137
159
  filesAndMethods: InspectorFilesAndMethods;
138
160
  filesAndMethodsErrors: Map<string, PathToNameAndType>;
@@ -173,4 +195,10 @@ export interface InspectorState {
173
195
  };
174
196
  middleware: InspectorMiddlewareState;
175
197
  permissions: InspectorPermissionState;
198
+ serviceAggregation: {
199
+ requiredServices: Set<string>;
200
+ usedFunctions: Set<string>;
201
+ usedMiddleware: Set<string>;
202
+ usedPermissions: Set<string>;
203
+ };
176
204
  }