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