@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,49 +1,391 @@
1
1
  import * as ts from 'typescript'
2
2
  import { AddWiring } from '../types.js'
3
- import { extractFunctionName } from '../utils/extract-function-name.js'
3
+ import {
4
+ extractFunctionName,
5
+ isNamedExport,
6
+ } from '../utils/extract-function-name.js'
4
7
  import { extractServicesFromFunction } from '../utils/extract-services.js'
8
+ import { extractPermissionPikkuNames } from '../utils/permissions.js'
9
+ import { getPropertyValue } from '../utils/get-property-value.js'
10
+ import { getPropertyAssignmentInitializer } from '../utils/type-utils.js'
5
11
 
6
12
  /**
7
- * Inspect pikkuPermission calls and extract first-arg destructuring
8
- * for tree shaking optimization.
13
+ * Inspect pikkuPermission calls, addPermission calls, and addHTTPPermission calls
9
14
  */
10
15
  export const addPermission: AddWiring = (logger, node, checker, state) => {
11
16
  if (!ts.isCallExpression(node)) return
12
17
 
13
18
  const { expression, arguments: args } = node
14
19
 
15
- // only handle calls like pikkuPermission(...)
20
+ // only handle specific function calls
16
21
  if (!ts.isIdentifier(expression)) {
17
22
  return
18
23
  }
19
24
 
20
- if (expression.text !== 'pikkuPermission') {
25
+ // Handle pikkuPermission(...) - individual permission function definition
26
+ if (expression.text === 'pikkuPermission') {
27
+ const arg = args[0]
28
+ if (!arg) return
29
+
30
+ let actualHandler: ts.ArrowFunction | ts.FunctionExpression
31
+ let name: string | undefined
32
+ let description: string | undefined
33
+
34
+ // Check if using object syntax: pikkuPermission({ func: ..., name: '...', description: '...' })
35
+ if (ts.isObjectLiteralExpression(arg)) {
36
+ // Extract name and description metadata
37
+ const nameValue = getPropertyValue(arg, 'name')
38
+ const descValue = getPropertyValue(arg, 'description')
39
+ name = typeof nameValue === 'string' ? nameValue : undefined
40
+ description = typeof descValue === 'string' ? descValue : undefined
41
+
42
+ // Extract the func property
43
+ const fnProp = getPropertyAssignmentInitializer(
44
+ arg,
45
+ 'func',
46
+ true,
47
+ checker
48
+ )
49
+ if (
50
+ !fnProp ||
51
+ (!ts.isArrowFunction(fnProp) && !ts.isFunctionExpression(fnProp))
52
+ ) {
53
+ logger.error(
54
+ `• pikkuPermission object missing required 'func' property.`
55
+ )
56
+ return
57
+ }
58
+ actualHandler = fnProp
59
+ } else if (ts.isArrowFunction(arg) || ts.isFunctionExpression(arg)) {
60
+ actualHandler = arg
61
+ } else {
62
+ logger.error(`• Handler for pikkuPermission is not a function.`)
63
+ return
64
+ }
65
+
66
+ const services = extractServicesFromFunction(actualHandler)
67
+ const { pikkuFuncName, exportedName } = extractFunctionName(
68
+ node,
69
+ checker,
70
+ state.rootDir
71
+ )
72
+ state.permissions.meta[pikkuFuncName] = {
73
+ services,
74
+ sourceFile: node.getSourceFile().fileName,
75
+ position: node.getStart(),
76
+ exportedName,
77
+ name,
78
+ description,
79
+ }
80
+
81
+ logger.debug(
82
+ `• Found permission with services: ${services.services.join(', ')}${name ? ` (name: ${name})` : ''}${description ? ` (description: ${description})` : ''}`
83
+ )
21
84
  return
22
85
  }
23
86
 
24
- const handlerNode = args[0]
25
- if (!handlerNode) return
87
+ // Handle pikkuPermissionFactory(...) - permission factory function
88
+ if (expression.text === 'pikkuPermissionFactory') {
89
+ const factoryNode = args[0]
90
+ if (!factoryNode) return
91
+
92
+ if (
93
+ !ts.isArrowFunction(factoryNode) &&
94
+ !ts.isFunctionExpression(factoryNode)
95
+ ) {
96
+ logger.error(`• Handler for pikkuPermissionFactory is not a function.`)
97
+ return
98
+ }
99
+
100
+ // Extract services by looking inside the factory function body
101
+ // The factory should return pikkuPermission(...), so we need to find that call
102
+ // If no wrapper is found, extract from the factory's returned function directly
103
+ let services = { optimized: false, services: [] as string[] }
104
+
105
+ const findPikkuPermissionCall = (
106
+ node: ts.Node
107
+ ): ts.CallExpression | undefined => {
108
+ if (ts.isCallExpression(node)) {
109
+ const expr = node.expression
110
+ if (ts.isIdentifier(expr) && expr.text === 'pikkuPermission') {
111
+ return node
112
+ }
113
+ }
114
+ return ts.forEachChild(node, findPikkuPermissionCall)
115
+ }
116
+
117
+ const pikkuPermissionCall = findPikkuPermissionCall(factoryNode)
118
+ if (pikkuPermissionCall && pikkuPermissionCall.arguments[0]) {
119
+ const permissionHandler = pikkuPermissionCall.arguments[0]
120
+ if (
121
+ ts.isArrowFunction(permissionHandler) ||
122
+ ts.isFunctionExpression(permissionHandler)
123
+ ) {
124
+ services = extractServicesFromFunction(permissionHandler)
125
+ }
126
+ } else {
127
+ // No pikkuPermission wrapper found - extract from factory's return value directly
128
+ // Factory pattern: (config) => (services, data, session) => { ... }
129
+ if (
130
+ ts.isArrowFunction(factoryNode) ||
131
+ ts.isFunctionExpression(factoryNode)
132
+ ) {
133
+ const factoryBody = factoryNode.body
134
+ // Check if the body is an arrow function (direct return)
135
+ if (
136
+ ts.isArrowFunction(factoryBody) ||
137
+ ts.isFunctionExpression(factoryBody)
138
+ ) {
139
+ services = extractServicesFromFunction(factoryBody)
140
+ }
141
+ }
142
+ }
143
+
144
+ const { pikkuFuncName, exportedName } = extractFunctionName(
145
+ node,
146
+ checker,
147
+ state.rootDir
148
+ )
149
+ state.permissions.meta[pikkuFuncName] = {
150
+ services,
151
+ sourceFile: node.getSourceFile().fileName,
152
+ position: node.getStart(),
153
+ exportedName,
154
+ factory: true,
155
+ }
26
156
 
27
- if (
28
- !ts.isArrowFunction(handlerNode) &&
29
- !ts.isFunctionExpression(handlerNode)
30
- ) {
31
- logger.error(`• Handler for pikkuPermission is not a function.`)
157
+ logger.debug(
158
+ `• Found permission factory with services: ${services.services.join(', ')}`
159
+ )
32
160
  return
33
161
  }
34
162
 
35
- const services = extractServicesFromFunction(handlerNode)
163
+ // Handle addPermission('tag', [permission1, permission2])
164
+ // Supports two patterns:
165
+ // 1. export const x = () => addPermission('tag', [...]) (factory - tree-shakeable)
166
+ // 2. export const x = addPermission('tag', [...]) (direct - no tree-shaking)
167
+ if (expression.text === 'addPermission') {
168
+ const tagArg = args[0]
169
+ const permissionsArrayArg = args[1]
170
+
171
+ if (!tagArg || !permissionsArrayArg) return
172
+
173
+ // Extract tag name
174
+ let tag: string | undefined
175
+ if (ts.isStringLiteral(tagArg)) {
176
+ tag = tagArg.text
177
+ }
178
+
179
+ if (!tag) {
180
+ logger.warn(`• addPermission call without valid tag string`)
181
+ return
182
+ }
183
+
184
+ // Check if permissions is a literal array or object
185
+ if (
186
+ !ts.isArrayLiteralExpression(permissionsArrayArg) &&
187
+ !ts.isObjectLiteralExpression(permissionsArrayArg)
188
+ ) {
189
+ logger.error(
190
+ `• addPermission('${tag}', ...) must have a literal array or object as second argument`
191
+ )
192
+ return
193
+ }
194
+
195
+ // Extract permission pikkuFuncNames from array
196
+ const permissionNames = extractPermissionPikkuNames(
197
+ permissionsArrayArg,
198
+ checker,
199
+ state.rootDir
200
+ )
201
+
202
+ if (permissionNames.length === 0) {
203
+ logger.warn(`• addPermission('${tag}', ...) has empty permissions array`)
204
+ return
205
+ }
206
+
207
+ // Collect services from all permissions in the group
208
+ const allServices = new Set<string>()
209
+ for (const permissionName of permissionNames) {
210
+ const permissionMeta = state.permissions.meta[permissionName]
211
+ if (permissionMeta && permissionMeta.services) {
212
+ for (const service of permissionMeta.services.services) {
213
+ allServices.add(service)
214
+ }
215
+ }
216
+ }
217
+
218
+ // Check if this call is wrapped in a factory function
219
+ // We need to walk up the tree to see if the parent is: const x = () => addPermission(...)
220
+ let isFactory = false
221
+ let exportedName: string | null = null
222
+ let parent = node.parent
223
+
224
+ // Check if parent is arrow function: () => addPermission(...)
225
+ if (parent && ts.isArrowFunction(parent)) {
226
+ // Check if arrow function has no parameters
227
+ if (parent.parameters.length === 0) {
228
+ isFactory = true
229
+
230
+ // For factories, we need to check the arrow function's parent for the export name
231
+ // const apiTagPermissions = () => addPermission(...)
232
+ const arrowParent = parent.parent
233
+ if (arrowParent && ts.isVariableDeclaration(arrowParent)) {
234
+ if (ts.isIdentifier(arrowParent.name)) {
235
+ // Check if it's exported
236
+ if (isNamedExport(arrowParent)) {
237
+ exportedName = arrowParent.name.text
238
+ }
239
+ }
240
+ }
241
+ }
242
+ }
36
243
 
37
- const { pikkuFuncName, exportedName } = extractFunctionName(node, checker)
244
+ // If not a factory, get export name from the call expression itself
245
+ if (!isFactory) {
246
+ const extracted = extractFunctionName(node, checker, state.rootDir)
247
+ exportedName = extracted.exportedName
248
+ }
38
249
 
39
- state.permissions.meta[pikkuFuncName] = {
40
- services,
41
- sourceFile: node.getSourceFile().fileName,
42
- position: node.getStart(),
43
- exportedName,
250
+ // Log warning if not using factory pattern
251
+ if (!isFactory && exportedName) {
252
+ logger.warn(
253
+ `• Permission group '${exportedName}' for tag '${tag}' is not wrapped in a factory function. ` +
254
+ `For tree-shaking, use: export const ${exportedName} = () => addPermission('${tag}', [...])`
255
+ )
256
+ }
257
+
258
+ // Store group metadata
259
+ state.permissions.tagPermissions.set(tag, {
260
+ exportName: exportedName,
261
+ sourceFile: node.getSourceFile().fileName,
262
+ position: node.getStart(),
263
+ services: {
264
+ optimized: false,
265
+ services: Array.from(allServices),
266
+ },
267
+ permissionCount: permissionNames.length,
268
+ isFactory,
269
+ })
270
+
271
+ logger.debug(
272
+ `• Found tag permission group: ${tag} -> [${permissionNames.join(', ')}] (${isFactory ? 'factory' : 'direct'})`
273
+ )
274
+ return
44
275
  }
45
276
 
46
- logger.debug(
47
- `• Found permission with services: ${services.services.join(', ')}`
48
- )
277
+ // Handle addHTTPPermission(pattern, [permission1, permission2])
278
+ // Supports two patterns:
279
+ // 1. export const x = () => addHTTPPermission('*', [...]) (factory - tree-shakeable)
280
+ // 2. export const x = addHTTPPermission('*', [...]) (direct - no tree-shaking)
281
+ if (expression.text === 'addHTTPPermission') {
282
+ const patternArg = args[0]
283
+ const permissionsArrayArg = args[1]
284
+
285
+ if (!patternArg || !permissionsArrayArg) return
286
+
287
+ // Extract route pattern
288
+ let pattern: string | undefined
289
+ if (ts.isStringLiteral(patternArg)) {
290
+ pattern = patternArg.text
291
+ }
292
+
293
+ if (!pattern) {
294
+ logger.warn(`• addHTTPPermission call without valid pattern string`)
295
+ return
296
+ }
297
+
298
+ // Check if permissions is a literal array or object
299
+ if (
300
+ !ts.isArrayLiteralExpression(permissionsArrayArg) &&
301
+ !ts.isObjectLiteralExpression(permissionsArrayArg)
302
+ ) {
303
+ logger.error(
304
+ `• addHTTPPermission('${pattern}', ...) must have a literal array or object as second argument`
305
+ )
306
+ return
307
+ }
308
+
309
+ // Extract permission pikkuFuncNames from array
310
+ const permissionNames = extractPermissionPikkuNames(
311
+ permissionsArrayArg,
312
+ checker,
313
+ state.rootDir
314
+ )
315
+
316
+ if (permissionNames.length === 0) {
317
+ logger.warn(
318
+ `• addHTTPPermission('${pattern}', ...) has empty permissions array`
319
+ )
320
+ return
321
+ }
322
+
323
+ // Collect services from all permissions in the group
324
+ const allServices = new Set<string>()
325
+ for (const permissionName of permissionNames) {
326
+ const permissionMeta = state.permissions.meta[permissionName]
327
+ if (permissionMeta && permissionMeta.services) {
328
+ for (const service of permissionMeta.services.services) {
329
+ allServices.add(service)
330
+ }
331
+ }
332
+ }
333
+
334
+ // Check if this call is wrapped in a factory function
335
+ let isFactory = false
336
+ let exportedName: string | null = null
337
+ let parent = node.parent
338
+
339
+ // Check if parent is arrow function: () => addHTTPPermission(...)
340
+ if (parent && ts.isArrowFunction(parent)) {
341
+ // Check if arrow function has no parameters
342
+ if (parent.parameters.length === 0) {
343
+ isFactory = true
344
+
345
+ // For factories, we need to check the arrow function's parent for the export name
346
+ // const apiRoutePermissions = () => addHTTPPermission(...)
347
+ const arrowParent = parent.parent
348
+ if (arrowParent && ts.isVariableDeclaration(arrowParent)) {
349
+ if (ts.isIdentifier(arrowParent.name)) {
350
+ // Check if it's exported
351
+ if (isNamedExport(arrowParent)) {
352
+ exportedName = arrowParent.name.text
353
+ }
354
+ }
355
+ }
356
+ }
357
+ }
358
+
359
+ // If not a factory, get export name from the call expression itself
360
+ if (!isFactory) {
361
+ const extracted = extractFunctionName(node, checker, state.rootDir)
362
+ exportedName = extracted.exportedName
363
+ }
364
+
365
+ // Log warning if not using factory pattern
366
+ if (!isFactory && exportedName) {
367
+ logger.warn(
368
+ `• HTTP permission group '${exportedName}' for pattern '${pattern}' is not wrapped in a factory function. ` +
369
+ `For tree-shaking, use: export const ${exportedName} = () => addHTTPPermission('${pattern}', [...])`
370
+ )
371
+ }
372
+
373
+ // Store group metadata
374
+ state.http.routePermissions.set(pattern, {
375
+ exportName: exportedName,
376
+ sourceFile: node.getSourceFile().fileName,
377
+ position: node.getStart(),
378
+ services: {
379
+ optimized: false,
380
+ services: Array.from(allServices),
381
+ },
382
+ permissionCount: permissionNames.length,
383
+ isFactory,
384
+ })
385
+
386
+ logger.debug(
387
+ `• Found HTTP route permission group: ${pattern} -> [${permissionNames.join(', ')}] (${isFactory ? 'factory' : 'direct'})`
388
+ )
389
+ return
390
+ }
49
391
  }
@@ -1,11 +1,15 @@
1
1
  import * as ts from 'typescript'
2
- import { getPropertyValue } from '../utils/get-property-value.js'
3
- import { PikkuDocs, PikkuWiringTypes } from '@pikku/core'
2
+ import {
3
+ getPropertyValue,
4
+ getPropertyTags,
5
+ } from '../utils/get-property-value.js'
6
+ import { PikkuDocs } from '@pikku/core'
4
7
  import { AddWiring } from '../types.js'
5
8
  import { extractFunctionName } from '../utils/extract-function-name.js'
6
9
  import { getPropertyAssignmentInitializer } from '../utils/type-utils.js'
7
- import { matchesFilters } from '../utils/filter-utils.js'
8
10
  import { resolveMiddleware } from '../utils/middleware.js'
11
+ import { extractWireNames } from '../utils/post-process.js'
12
+ import { ErrorCode } from '../error-codes.js'
9
13
 
10
14
  export const addQueueWorker: AddWiring = (
11
15
  logger,
@@ -36,7 +40,7 @@ export const addQueueWorker: AddWiring = (
36
40
 
37
41
  const queueName = getPropertyValue(obj, 'queueName') as string | null
38
42
  const docs = (getPropertyValue(obj, 'docs') as PikkuDocs) || undefined
39
- const tags = (getPropertyValue(obj, 'tags') as string[]) || undefined
43
+ const tags = getPropertyTags(obj, 'Queue worker', queueName, logger)
40
44
 
41
45
  // --- find the referenced function ---
42
46
  const funcInitializer = getPropertyAssignmentInitializer(
@@ -46,36 +50,23 @@ export const addQueueWorker: AddWiring = (
46
50
  checker
47
51
  )
48
52
  if (!funcInitializer) {
49
- console.error(
50
- `• No valid 'func' property for queue processor '${queueName}'.`
53
+ logger.critical(
54
+ ErrorCode.MISSING_FUNC,
55
+ `No valid 'func' property for queue processor '${queueName}'.`
51
56
  )
52
57
  return
53
58
  }
54
59
 
55
60
  const pikkuFuncName = extractFunctionName(
56
61
  funcInitializer,
57
- checker
62
+ checker,
63
+ state.rootDir
58
64
  ).pikkuFuncName
59
65
 
60
66
  if (!queueName) {
61
- console.error(
62
- `• No 'queueName' provided for queue processor function '${pikkuFuncName}'.`
63
- )
64
- return
65
- }
66
-
67
- const filePath = node.getSourceFile().fileName
68
-
69
- if (
70
- !matchesFilters(
71
- options.filters || {},
72
- { tags },
73
- { type: PikkuWiringTypes.queue, name: queueName, filePath },
74
- logger
75
- )
76
- ) {
77
- console.info(
78
- `• Skipping queue processor '${pikkuFuncName}' for queue '${queueName}' due to filter mismatch.`
67
+ logger.critical(
68
+ ErrorCode.MISSING_QUEUE_NAME,
69
+ `No 'queueName' provided for queue processor function '${pikkuFuncName}'.`
79
70
  )
80
71
  return
81
72
  }
@@ -83,6 +74,12 @@ export const addQueueWorker: AddWiring = (
83
74
  // --- resolve middleware ---
84
75
  const middleware = resolveMiddleware(state, obj, tags, checker)
85
76
 
77
+ // --- track used functions/middleware for service aggregation ---
78
+ state.serviceAggregation.usedFunctions.add(pikkuFuncName)
79
+ extractWireNames(middleware).forEach((name) =>
80
+ state.serviceAggregation.usedMiddleware.add(name)
81
+ )
82
+
86
83
  state.queueWorkers.files.add(node.getSourceFile().fileName)
87
84
  state.queueWorkers.meta[queueName] = {
88
85
  pikkuFuncName,
@@ -1,12 +1,16 @@
1
1
  import * as ts from 'typescript'
2
- import { getPropertyValue } from '../utils/get-property-value.js'
3
- import { PikkuDocs, PikkuWiringTypes } from '@pikku/core'
2
+ import {
3
+ getPropertyValue,
4
+ getPropertyTags,
5
+ } from '../utils/get-property-value.js'
6
+ import { PikkuDocs } from '@pikku/core'
4
7
  import { AddWiring } from '../types.js'
5
8
  import { extractFunctionName } from '../utils/extract-function-name.js'
6
9
  import { getPropertyAssignmentInitializer } from '../utils/type-utils.js'
7
- import { matchesFilters } from '../utils/filter-utils.js'
8
10
  import { resolveMiddleware } from '../utils/middleware.js'
11
+ import { extractWireNames } from '../utils/post-process.js'
9
12
 
13
+ import { ErrorCode } from '../error-codes.js'
10
14
  export const addSchedule: AddWiring = (
11
15
  logger,
12
16
  node,
@@ -37,7 +41,7 @@ export const addSchedule: AddWiring = (
37
41
  const nameValue = getPropertyValue(obj, 'name') as string | null
38
42
  const scheduleValue = getPropertyValue(obj, 'schedule') as string | null
39
43
  const docs = (getPropertyValue(obj, 'docs') as PikkuDocs) || undefined
40
- const tags = (getPropertyValue(obj, 'tags') as string[]) || undefined
44
+ const tags = getPropertyTags(obj, 'Scheduler', nameValue, logger)
41
45
 
42
46
  const funcInitializer = getPropertyAssignmentInitializer(
43
47
  obj,
@@ -46,37 +50,32 @@ export const addSchedule: AddWiring = (
46
50
  checker
47
51
  )
48
52
  if (!funcInitializer) {
49
- console.error(
50
- `• No valid 'func' property for scheduled task '${nameValue}'.`
53
+ logger.critical(
54
+ ErrorCode.MISSING_FUNC,
55
+ `No valid 'func' property for scheduled task '${nameValue}'.`
51
56
  )
52
57
  return
53
58
  }
54
59
 
55
60
  const pikkuFuncName = extractFunctionName(
56
61
  funcInitializer,
57
- checker
62
+ checker,
63
+ state.rootDir
58
64
  ).pikkuFuncName
59
65
 
60
66
  if (!nameValue || !scheduleValue) {
61
67
  return
62
68
  }
63
69
 
64
- const filePath = node.getSourceFile().fileName
65
-
66
- if (
67
- !matchesFilters(
68
- options.filters || {},
69
- { tags },
70
- { type: PikkuWiringTypes.scheduler, name: nameValue, filePath },
71
- logger
72
- )
73
- ) {
74
- return
75
- }
76
-
77
70
  // --- resolve middleware ---
78
71
  const middleware = resolveMiddleware(state, obj, tags, checker)
79
72
 
73
+ // --- track used functions/middleware for service aggregation ---
74
+ state.serviceAggregation.usedFunctions.add(pikkuFuncName)
75
+ extractWireNames(middleware).forEach((name) =>
76
+ state.serviceAggregation.usedMiddleware.add(name)
77
+ )
78
+
80
79
  state.scheduledTasks.files.add(node.getSourceFile().fileName)
81
80
  state.scheduledTasks.meta[nameValue] = {
82
81
  pikkuFuncName,
@@ -0,0 +1,43 @@
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
+
10
+ export enum ErrorCode {
11
+ // Validation errors
12
+ MISSING_NAME = 'PKU111',
13
+ MISSING_DESCRIPTION = 'PKU123',
14
+ MISSING_URI = 'PKU220',
15
+ MISSING_FUNC = 'PKU236',
16
+ INVALID_TAGS_TYPE = 'PKU247',
17
+ INVALID_HANDLER = 'PKU300',
18
+ MISSING_TITLE = 'PKU370',
19
+ MISSING_QUEUE_NAME = 'PKU384',
20
+ MISSING_CHANNEL_NAME = 'PKU400',
21
+ CLI_CLIENTSIDE_RENDERER_HAS_SERVICES = 'PKU672',
22
+
23
+ // Configuration errors
24
+ CONFIG_TYPE_NOT_FOUND = 'PKU426',
25
+ CONFIG_TYPE_UNDEFINED = 'PKU427',
26
+ SCHEMA_NO_ROOT = 'PKU431',
27
+ SCHEMA_GENERATION_ERROR = 'PKU456',
28
+ SCHEMA_LOAD_ERROR = 'PKU488',
29
+
30
+ // Function errors
31
+ FUNCTION_METADATA_NOT_FOUND = 'PKU559',
32
+ HANDLER_NOT_RESOLVED = 'PKU568',
33
+
34
+ // Middleware/Permission errors
35
+ MIDDLEWARE_HANDLER_INVALID = 'PKU685',
36
+ MIDDLEWARE_TAG_INVALID = 'PKU715',
37
+ MIDDLEWARE_EMPTY_ARRAY = 'PKU736',
38
+ MIDDLEWARE_PATTERN_INVALID = 'PKU787',
39
+ PERMISSION_HANDLER_INVALID = 'PKU835',
40
+ PERMISSION_TAG_INVALID = 'PKU836',
41
+ PERMISSION_EMPTY_ARRAY = 'PKU937',
42
+ PERMISSION_PATTERN_INVALID = 'PKU975',
43
+ }
package/src/index.ts CHANGED
@@ -7,3 +7,10 @@ export type {
7
7
  FilesAndMethods,
8
8
  FilesAndMethodsErrors,
9
9
  } from './utils/get-files-and-methods.js'
10
+ export { ErrorCode } from './error-codes.js'
11
+ export {
12
+ serializeInspectorState,
13
+ deserializeInspectorState,
14
+ } from './utils/serialize-inspector-state.js'
15
+ export type { SerializableInspectorState } from './utils/serialize-inspector-state.js'
16
+ export { filterInspectorState } from './utils/filter-inspector-state.js'