@pikku/inspector 0.9.4 → 0.9.6-next.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 (95) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/{add-channel.d.ts → add/add-channel.d.ts} +2 -2
  3. package/dist/{add-channel.js → add/add-channel.js} +12 -5
  4. package/dist/add/add-cli.d.ts +5 -0
  5. package/dist/add/add-cli.js +461 -0
  6. package/dist/{add-file-extends-core-type.d.ts → add/add-file-extends-core-type.d.ts} +2 -2
  7. package/dist/{add-file-extends-core-type.js → add/add-file-extends-core-type.js} +17 -5
  8. package/dist/{add-file-with-config.d.ts → add/add-file-with-config.d.ts} +1 -1
  9. package/dist/{add-file-with-config.js → add/add-file-with-config.js} +1 -1
  10. package/dist/{add-file-with-factory.d.ts → add/add-file-with-factory.d.ts} +1 -1
  11. package/dist/{add-file-with-factory.js → add/add-file-with-factory.js} +4 -4
  12. package/dist/add/add-functions.d.ts +6 -0
  13. package/dist/{add-functions.js → add/add-functions.js} +26 -6
  14. package/dist/{add-http-route.d.ts → add/add-http-route.d.ts} +2 -3
  15. package/dist/{add-http-route.js → add/add-http-route.js} +10 -4
  16. package/dist/add/add-mcp-prompt.d.ts +2 -0
  17. package/dist/{add-mcp-prompt.js → add/add-mcp-prompt.js} +10 -4
  18. package/dist/add/add-mcp-resource.d.ts +2 -0
  19. package/dist/{add-mcp-resource.js → add/add-mcp-resource.js} +10 -4
  20. package/dist/add/add-mcp-tool.d.ts +2 -0
  21. package/dist/{add-mcp-tool.js → add/add-mcp-tool.js} +10 -4
  22. package/dist/add/add-middleware.d.ts +5 -0
  23. package/dist/add/add-middleware.js +251 -0
  24. package/dist/add/add-permission.d.ts +6 -0
  25. package/dist/{add-permission.js → add/add-permission.js} +4 -3
  26. package/dist/add/add-queue-worker.d.ts +2 -0
  27. package/dist/{add-queue-worker.js → add/add-queue-worker.js} +10 -4
  28. package/dist/{add-rpc-invocations.d.ts → add/add-rpc-invocations.d.ts} +1 -1
  29. package/dist/add/add-schedule.d.ts +2 -0
  30. package/dist/{add-schedule.js → add/add-schedule.js} +10 -4
  31. package/dist/index.d.ts +2 -0
  32. package/dist/index.js +1 -0
  33. package/dist/inspector.d.ts +2 -3
  34. package/dist/inspector.js +19 -8
  35. package/dist/types.d.ts +79 -0
  36. package/dist/{utils.d.ts → utils/extract-function-name.d.ts} +7 -15
  37. package/dist/{utils.js → utils/extract-function-name.js} +23 -142
  38. package/dist/utils/extract-services.d.ts +6 -0
  39. package/dist/utils/extract-services.js +29 -0
  40. package/dist/utils/filter-utils.d.ts +9 -0
  41. package/dist/utils/filter-utils.js +45 -0
  42. package/dist/utils/get-files-and-methods.d.ts +21 -0
  43. package/dist/utils/get-files-and-methods.js +60 -0
  44. package/dist/utils/middleware.d.ts +39 -0
  45. package/dist/utils/middleware.js +157 -0
  46. package/dist/utils/type-utils.d.ts +3 -0
  47. package/dist/utils/type-utils.js +50 -0
  48. package/dist/visit.d.ts +3 -3
  49. package/dist/visit.js +33 -30
  50. package/package.json +3 -4
  51. package/run-tests.sh +1 -1
  52. package/src/{add-channel.ts → add/add-channel.ts} +19 -19
  53. package/src/add/add-cli.ts +663 -0
  54. package/src/{add-file-extends-core-type.ts → add/add-file-extends-core-type.ts} +21 -6
  55. package/src/{add-file-with-config.ts → add/add-file-with-config.ts} +2 -2
  56. package/src/{add-file-with-factory.ts → add/add-file-with-factory.ts} +5 -5
  57. package/src/{add-functions.ts → add/add-functions.ts} +30 -15
  58. package/src/{add-http-route.ts → add/add-http-route.ts} +23 -14
  59. package/src/{add-mcp-prompt.ts → add/add-mcp-prompt.ts} +18 -15
  60. package/src/{add-mcp-resource.ts → add/add-mcp-resource.ts} +18 -15
  61. package/src/{add-mcp-tool.ts → add/add-mcp-tool.ts} +18 -15
  62. package/src/add/add-middleware.ts +326 -0
  63. package/src/{add-permission.ts → add/add-permission.ts} +4 -8
  64. package/src/{add-queue-worker.ts → add/add-queue-worker.ts} +17 -14
  65. package/src/{add-rpc-invocations.ts → add/add-rpc-invocations.ts} +1 -1
  66. package/src/{add-schedule.ts → add/add-schedule.ts} +17 -14
  67. package/src/index.ts +5 -0
  68. package/src/inspector.ts +20 -17
  69. package/src/types.ts +92 -0
  70. package/src/{utils.ts → utils/extract-function-name.ts} +25 -199
  71. package/src/utils/extract-services.ts +35 -0
  72. package/src/{utils.test.ts → utils/filter-utils.test.ts} +2 -2
  73. package/src/utils/filter-utils.ts +72 -0
  74. package/src/utils/get-files-and-methods.ts +143 -0
  75. package/src/utils/middleware.ts +234 -0
  76. package/src/utils/type-utils.ts +74 -0
  77. package/src/visit.ts +47 -33
  78. package/tsconfig.tsbuildinfo +1 -1
  79. package/dist/add-functions.d.ts +0 -7
  80. package/dist/add-mcp-prompt.d.ts +0 -3
  81. package/dist/add-mcp-resource.d.ts +0 -3
  82. package/dist/add-mcp-tool.d.ts +0 -3
  83. package/dist/add-middleware.d.ts +0 -7
  84. package/dist/add-middleware.js +0 -35
  85. package/dist/add-permission.d.ts +0 -7
  86. package/dist/add-queue-worker.d.ts +0 -3
  87. package/dist/add-schedule.d.ts +0 -3
  88. package/src/add-middleware.ts +0 -51
  89. /package/dist/{add-rpc-invocations.js → add/add-rpc-invocations.js} +0 -0
  90. /package/dist/{does-type-extend-core-type.d.ts → utils/does-type-extend-core-type.d.ts} +0 -0
  91. /package/dist/{does-type-extend-core-type.js → utils/does-type-extend-core-type.js} +0 -0
  92. /package/dist/{get-property-value.d.ts → utils/get-property-value.d.ts} +0 -0
  93. /package/dist/{get-property-value.js → utils/get-property-value.js} +0 -0
  94. /package/src/{does-type-extend-core-type.ts → utils/does-type-extend-core-type.ts} +0 -0
  95. /package/src/{get-property-value.ts → utils/get-property-value.ts} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @pikku/inspector
2
2
 
3
+ ## 0.9.6-next.0
4
+
5
+ ### Patch Changes
6
+
7
+ - feat: running @pikku/cli using pikku
8
+ - Updated dependencies
9
+ - @pikku/core@0.9.12-next.0
10
+
11
+ ## 0.9.5
12
+
13
+ ### Patch Changes
14
+
15
+ - 501c120: fix: rpc internal meta file wasn't being imported
16
+
3
17
  ## 0.9.4
4
18
 
5
19
  ### Patch Changes
@@ -1,6 +1,6 @@
1
1
  import * as ts from 'typescript';
2
2
  import type { ChannelMeta } from '@pikku/core/channel';
3
- import type { InspectorFilters, InspectorState, InspectorLogger } from './types.js';
3
+ import type { InspectorState, AddWiring } from '../types.js';
4
4
  /**
5
5
  * Build out the nested message-routes by looking up each handler
6
6
  * in state.functions.meta instead of re-inferring it here.
@@ -10,4 +10,4 @@ export declare function addMessagesRoutes(obj: ts.ObjectLiteralExpression, state
10
10
  * Inspect addChannel calls, look up all handlers in state.functions.meta,
11
11
  * and emit one entry into state.channels.meta.
12
12
  */
13
- export declare function addChannel(node: ts.Node, checker: ts.TypeChecker, state: InspectorState, filters: InspectorFilters, logger: InspectorLogger): void;
13
+ export declare const addChannel: AddWiring;
@@ -1,10 +1,13 @@
1
1
  import * as ts from 'typescript';
2
- import { getPropertyValue } from './get-property-value.js';
2
+ import { getPropertyValue } from '../utils/get-property-value.js';
3
3
  import { pathToRegexp } from 'path-to-regexp';
4
4
  import { PikkuWiringTypes } from '@pikku/core';
5
- import { extractFunctionName, getPropertyAssignmentInitializer, matchesFilters, } from './utils.js';
5
+ import { extractFunctionName } from '../utils/extract-function-name.js';
6
+ import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
7
+ import { matchesFilters } from '../utils/filter-utils.js';
8
+ import { resolveMiddleware } from '../utils/middleware.js';
6
9
  /**
7
- * Safely get the initializer expression of a property-like AST node:
10
+ * Safely get the "initializer" expression of a property-like AST node:
8
11
  * - for `foo: expr`, returns `expr`
9
12
  * - for `{ foo }` shorthand, returns the identifier `foo`
10
13
  * - otherwise, returns undefined
@@ -267,7 +270,8 @@ export function addMessagesRoutes(obj, state, checker) {
267
270
  * Inspect addChannel calls, look up all handlers in state.functions.meta,
268
271
  * and emit one entry into state.channels.meta.
269
272
  */
270
- export function addChannel(node, checker, state, filters, logger) {
273
+ export const addChannel = (logger, node, checker, state, options) => {
274
+ const filters = options.filters || {};
271
275
  if (!ts.isCallExpression(node))
272
276
  return;
273
277
  const { expression, arguments: args } = node;
@@ -316,6 +320,8 @@ export function addChannel(node, checker, state, filters, logger) {
316
320
  }
317
321
  // nested message-routes
318
322
  const messageWirings = addMessagesRoutes(obj, state, checker);
323
+ // --- resolve middleware ---
324
+ const middleware = resolveMiddleware(state, obj, tags, checker);
319
325
  // record into state
320
326
  state.channels.files.add(node.getSourceFile().fileName);
321
327
  state.channels.meta[name] = {
@@ -344,5 +350,6 @@ export function addChannel(node, checker, state, filters, logger) {
344
350
  messageWirings,
345
351
  docs: docs ?? undefined,
346
352
  tags: tags ?? undefined,
353
+ middleware,
347
354
  };
348
- }
355
+ };
@@ -0,0 +1,5 @@
1
+ import { AddWiring } from '../types.js';
2
+ /**
3
+ * Adds CLI command metadata to the inspector state
4
+ */
5
+ export declare const addCLI: AddWiring;
@@ -0,0 +1,461 @@
1
+ import ts from 'typescript';
2
+ import { extractFunctionName } from '../utils/extract-function-name.js';
3
+ import { resolveMiddleware } from '../utils/middleware.js';
4
+ import { getPropertyValue } from '../utils/get-property-value.js';
5
+ /**
6
+ * Adds CLI command metadata to the inspector state
7
+ */
8
+ export const addCLI = (logger, node, typeChecker, inspectorState, options) => {
9
+ if (!ts.isCallExpression(node))
10
+ return;
11
+ // Check if this is a wireCLI call
12
+ if (!node || !node.expression) {
13
+ return;
14
+ }
15
+ const expression = node.expression;
16
+ if (!ts.isIdentifier(expression) || expression.text !== 'wireCLI') {
17
+ return;
18
+ }
19
+ // Get the argument (should be an object literal)
20
+ if (node.arguments.length !== 1) {
21
+ return;
22
+ }
23
+ const arg = node.arguments[0];
24
+ if (!ts.isObjectLiteralExpression(arg)) {
25
+ return;
26
+ }
27
+ const sourceFile = node.getSourceFile();
28
+ // Add to files set
29
+ inspectorState.cli.files.add(sourceFile.fileName);
30
+ // Process the CLI configuration
31
+ const cliConfig = processCLIConfig(logger, arg, sourceFile, typeChecker, inspectorState, options);
32
+ if (!cliConfig) {
33
+ return;
34
+ }
35
+ // Add this program to the CLI metadata
36
+ inspectorState.cli.meta[cliConfig.programName] = cliConfig.programMeta;
37
+ };
38
+ /**
39
+ * Processes a CLI configuration object
40
+ */
41
+ function processCLIConfig(logger, node, sourceFile, typeChecker, inspectorState, options) {
42
+ let programName = '';
43
+ const programMeta = {
44
+ program: '',
45
+ commands: {},
46
+ options: {},
47
+ };
48
+ for (const prop of node.properties) {
49
+ if (!ts.isPropertyAssignment(prop))
50
+ continue;
51
+ if (!ts.isIdentifier(prop.name))
52
+ continue;
53
+ const propName = prop.name.text;
54
+ switch (propName) {
55
+ case 'program':
56
+ if (ts.isStringLiteral(prop.initializer)) {
57
+ programName = prop.initializer.text;
58
+ programMeta.program = programName;
59
+ }
60
+ break;
61
+ case 'commands':
62
+ if (ts.isObjectLiteralExpression(prop.initializer)) {
63
+ programMeta.commands = processCommands(logger, prop.initializer, sourceFile, typeChecker, programName, inspectorState, options);
64
+ }
65
+ break;
66
+ case 'options':
67
+ if (ts.isObjectLiteralExpression(prop.initializer)) {
68
+ programMeta.options = processOptions(logger, prop.initializer, typeChecker, inspectorState, options);
69
+ }
70
+ break;
71
+ case 'render':
72
+ // Track that a default renderer exists
73
+ programMeta.defaultRenderName = 'defaultRenderer';
74
+ break;
75
+ }
76
+ }
77
+ if (!programName) {
78
+ return null;
79
+ }
80
+ return { programName, programMeta };
81
+ }
82
+ /**
83
+ * Processes the commands object
84
+ */
85
+ function processCommands(logger, node, sourceFile, typeChecker, programName, inspectorState, options) {
86
+ const commands = {};
87
+ for (const prop of node.properties) {
88
+ if (!ts.isPropertyAssignment(prop))
89
+ continue;
90
+ const commandName = getPropertyName(prop);
91
+ if (!commandName)
92
+ continue;
93
+ const commandMeta = processCommand(logger, inspectorState, options, commandName, prop.initializer, sourceFile, typeChecker, programName);
94
+ if (commandMeta) {
95
+ commands[commandName] = commandMeta;
96
+ }
97
+ }
98
+ return commands;
99
+ }
100
+ /**
101
+ * Processes a single command
102
+ */
103
+ function processCommand(logger, inspectorState, options, name, node, sourceFile, typeChecker, programName, parentPath = []) {
104
+ const fullPath = [...parentPath, name];
105
+ // Handle shorthand (just a function)
106
+ if (ts.isIdentifier(node) ||
107
+ ts.isArrowFunction(node) ||
108
+ ts.isFunctionExpression(node)) {
109
+ return {
110
+ pikkuFuncName: extractFunctionName(node, typeChecker).pikkuFuncName,
111
+ positionals: [],
112
+ options: {},
113
+ };
114
+ }
115
+ // Handle pikkuCLICommand calls
116
+ if (ts.isCallExpression(node)) {
117
+ // Check if it's a pikkuCLICommand call
118
+ if (ts.isIdentifier(node.expression) &&
119
+ node.expression.text === 'pikkuCLICommand' &&
120
+ node.arguments.length > 0 &&
121
+ ts.isObjectLiteralExpression(node.arguments[0])) {
122
+ // Process the object literal argument
123
+ return processCommand(logger, inspectorState, options, name, node.arguments[0], sourceFile, typeChecker, programName, parentPath);
124
+ }
125
+ return null;
126
+ }
127
+ // Handle full command object
128
+ if (!ts.isObjectLiteralExpression(node)) {
129
+ return null;
130
+ }
131
+ const meta = {
132
+ pikkuFuncName: '',
133
+ positionals: [],
134
+ options: {},
135
+ };
136
+ // First pass: extract pikkuFuncName and tags so we can use them when processing options/middleware
137
+ let pikkuFuncName;
138
+ let optionsNode;
139
+ let tags;
140
+ for (const prop of node.properties) {
141
+ if (!ts.isPropertyAssignment(prop))
142
+ continue;
143
+ if (!ts.isIdentifier(prop.name))
144
+ continue;
145
+ const propName = prop.name.text;
146
+ if (propName === 'func') {
147
+ pikkuFuncName = extractFunctionName(prop.initializer, typeChecker).pikkuFuncName;
148
+ meta.pikkuFuncName = pikkuFuncName;
149
+ }
150
+ else if (propName === 'options' &&
151
+ ts.isObjectLiteralExpression(prop.initializer)) {
152
+ optionsNode = prop.initializer;
153
+ }
154
+ else if (propName === 'tags') {
155
+ tags = getPropertyValue(node, 'tags') || undefined;
156
+ }
157
+ }
158
+ // Resolve middleware
159
+ const middleware = resolveMiddleware(inspectorState, node, tags, typeChecker);
160
+ if (middleware) {
161
+ meta.middleware = middleware;
162
+ }
163
+ // Second pass: process all properties
164
+ for (const prop of node.properties) {
165
+ if (!ts.isPropertyAssignment(prop))
166
+ continue;
167
+ if (!ts.isIdentifier(prop.name))
168
+ continue;
169
+ const propName = prop.name.text;
170
+ switch (propName) {
171
+ case 'parameters':
172
+ if (ts.isStringLiteral(prop.initializer)) {
173
+ meta.parameters = prop.initializer.text;
174
+ meta.positionals = parseCommandPattern(prop.initializer.text);
175
+ }
176
+ break;
177
+ case 'description':
178
+ if (ts.isStringLiteral(prop.initializer)) {
179
+ meta.description = prop.initializer.text;
180
+ }
181
+ break;
182
+ case 'func':
183
+ // Already handled in first pass
184
+ break;
185
+ case 'render':
186
+ meta.renderName = extractFunctionName(prop.initializer, typeChecker).pikkuFuncName;
187
+ break;
188
+ case 'options':
189
+ // Process with pikkuFuncName from first pass
190
+ if (optionsNode) {
191
+ meta.options = processOptions(logger, optionsNode, typeChecker, inspectorState, options, pikkuFuncName);
192
+ }
193
+ break;
194
+ case 'subcommands':
195
+ if (ts.isObjectLiteralExpression(prop.initializer)) {
196
+ meta.subcommands = {};
197
+ for (const subProp of prop.initializer.properties) {
198
+ if (!ts.isPropertyAssignment(subProp))
199
+ continue;
200
+ const subName = getPropertyName(subProp);
201
+ if (!subName)
202
+ continue;
203
+ const subCommand = processCommand(logger, inspectorState, options, subName, subProp.initializer, sourceFile, typeChecker, programName, fullPath);
204
+ if (subCommand) {
205
+ meta.subcommands[subName] = subCommand;
206
+ }
207
+ }
208
+ }
209
+ break;
210
+ }
211
+ }
212
+ return meta;
213
+ }
214
+ /**
215
+ * Processes CLI options and extracts enum values from function input types
216
+ */
217
+ function processOptions(logger, node, typeChecker, inspectorState, inspectorOptions, pikkuFuncName) {
218
+ const options = {};
219
+ for (const prop of node.properties) {
220
+ if (!ts.isPropertyAssignment(prop))
221
+ continue;
222
+ const optionName = getPropertyName(prop);
223
+ if (!optionName)
224
+ continue;
225
+ if (ts.isObjectLiteralExpression(prop.initializer)) {
226
+ const option = {};
227
+ let manualChoices;
228
+ for (const optProp of prop.initializer.properties) {
229
+ if (!ts.isPropertyAssignment(optProp))
230
+ continue;
231
+ if (!ts.isIdentifier(optProp.name))
232
+ continue;
233
+ const optPropName = optProp.name.text;
234
+ switch (optPropName) {
235
+ case 'description':
236
+ if (ts.isStringLiteral(optProp.initializer)) {
237
+ option.description = optProp.initializer.text;
238
+ }
239
+ break;
240
+ case 'short':
241
+ if (ts.isStringLiteral(optProp.initializer)) {
242
+ option.short = optProp.initializer.text;
243
+ }
244
+ break;
245
+ case 'default':
246
+ // Extract default value from expression
247
+ if (ts.isStringLiteral(optProp.initializer)) {
248
+ option.default = optProp.initializer.text;
249
+ }
250
+ else if (ts.isNumericLiteral(optProp.initializer)) {
251
+ option.default = parseFloat(optProp.initializer.text);
252
+ }
253
+ else if (optProp.initializer.kind === ts.SyntaxKind.TrueKeyword) {
254
+ option.default = true;
255
+ }
256
+ else if (optProp.initializer.kind === ts.SyntaxKind.FalseKeyword) {
257
+ option.default = false;
258
+ }
259
+ break;
260
+ case 'choices':
261
+ // Extract manually specified choices
262
+ if (ts.isArrayLiteralExpression(optProp.initializer)) {
263
+ manualChoices = [];
264
+ for (const element of optProp.initializer.elements) {
265
+ if (ts.isStringLiteral(element)) {
266
+ manualChoices.push(element.text);
267
+ }
268
+ }
269
+ }
270
+ break;
271
+ }
272
+ }
273
+ // Extract enum values from the function input type if available
274
+ // Get the input type if we have a pikkuFuncName
275
+ let inputTypes;
276
+ if (pikkuFuncName) {
277
+ inputTypes = inspectorState.typesLookup.get(pikkuFuncName);
278
+ }
279
+ let derivedChoices = null;
280
+ if (inputTypes && inputTypes.length > 0) {
281
+ derivedChoices = extractEnumFromPropertyType(inputTypes[0], optionName, typeChecker);
282
+ }
283
+ else {
284
+ // Fallback: try to extract from Config type
285
+ derivedChoices = extractEnumFromConfigType(logger, optionName, typeChecker, inspectorState, inspectorOptions);
286
+ }
287
+ // Validate and set choices
288
+ if (manualChoices && derivedChoices) {
289
+ // Both manual and derived choices exist - validate manual is subset of derived
290
+ const invalidChoices = manualChoices.filter((choice) => !derivedChoices.includes(choice));
291
+ if (invalidChoices.length > 0) {
292
+ const sourceFile = node.getSourceFile();
293
+ const position = prop.getStart(sourceFile);
294
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(position);
295
+ throw new Error(`Invalid choices for option "${optionName}" at ${sourceFile.fileName}:${line + 1}:${character + 1}.\n` +
296
+ `The following choices are not valid according to the type: ${invalidChoices.join(', ')}.\n` +
297
+ `Valid choices from type: ${derivedChoices.join(', ')}.`);
298
+ }
299
+ // Manual choices are valid - use them
300
+ option.choices = manualChoices;
301
+ }
302
+ else if (manualChoices) {
303
+ // Only manual choices - use them
304
+ option.choices = manualChoices;
305
+ }
306
+ else if (derivedChoices) {
307
+ // Only derived choices - use them
308
+ option.choices = derivedChoices;
309
+ }
310
+ options[optionName] = option;
311
+ }
312
+ }
313
+ return options;
314
+ }
315
+ /**
316
+ * Extracts enum values from a property of a type
317
+ * Handles both union types ('a' | 'b') and TypeScript enums
318
+ */
319
+ function extractEnumFromPropertyType(type, propertyName, typeChecker) {
320
+ // Get the property from the type
321
+ const property = type.getProperty(propertyName);
322
+ if (!property) {
323
+ return null;
324
+ }
325
+ // Get the type of the property
326
+ const propertyType = typeChecker.getTypeOfSymbolAtLocation(property, property.valueDeclaration);
327
+ const enumValues = [];
328
+ // Check if it's a union type (e.g., 'debug' | 'info' | 'warn')
329
+ if (propertyType.isUnion()) {
330
+ for (const unionType of propertyType.types) {
331
+ // Check if it's a string literal type
332
+ if (unionType.flags & ts.TypeFlags.StringLiteral) {
333
+ const literalType = unionType;
334
+ enumValues.push(literalType.value);
335
+ }
336
+ // Check if it's an enum member (could be string or number enum)
337
+ else if (unionType.flags & ts.TypeFlags.EnumLiteral) {
338
+ const enumLiteralType = unionType;
339
+ // For string enums, use the value directly
340
+ if (typeof enumLiteralType.value === 'string') {
341
+ enumValues.push(enumLiteralType.value);
342
+ }
343
+ // For numeric enums, get the symbol name (e.g., "Debug", "Info")
344
+ else {
345
+ const symbol = unionType.symbol;
346
+ if (symbol && symbol.name) {
347
+ enumValues.push(symbol.name);
348
+ }
349
+ }
350
+ }
351
+ }
352
+ }
353
+ // Check if it's an enum type directly
354
+ else if (propertyType.flags & ts.TypeFlags.Enum) {
355
+ const symbol = propertyType.getSymbol();
356
+ if (symbol && symbol.exports) {
357
+ symbol.exports.forEach((member) => {
358
+ const memberType = typeChecker.getTypeOfSymbolAtLocation(member, member.valueDeclaration);
359
+ if (memberType.flags & ts.TypeFlags.StringLiteral) {
360
+ const literalType = memberType;
361
+ enumValues.push(literalType.value);
362
+ }
363
+ else if (typeof memberType.value === 'string') {
364
+ enumValues.push(memberType.value);
365
+ }
366
+ });
367
+ }
368
+ }
369
+ // Check if it's an enum literal type
370
+ else if (propertyType.flags & ts.TypeFlags.EnumLiteral) {
371
+ const enumLiteralType = propertyType;
372
+ if (typeof enumLiteralType.value === 'string') {
373
+ enumValues.push(enumLiteralType.value);
374
+ }
375
+ }
376
+ return enumValues.length > 0 ? enumValues : null;
377
+ }
378
+ /**
379
+ * Extracts enum values from the Config type
380
+ */
381
+ function extractEnumFromConfigType(logger, propertyName, typeChecker, inspectorState, _inspectorOptions) {
382
+ // Look for Config type in typesLookup
383
+ const configTypes = inspectorState.typesLookup.get('Config');
384
+ if (!configTypes || configTypes.length === 0) {
385
+ logger.warn(`Warning: Could not find Config type in typesLookup for option "${propertyName}". ` +
386
+ `Make sure you have a Config interface extending CoreConfig in your codebase.`);
387
+ return null;
388
+ }
389
+ // Use the first Config type (there should only be one)
390
+ const configType = configTypes[0];
391
+ if (!configType) {
392
+ logger.warn(`Warning: Config type is undefined in typesLookup for option "${propertyName}".`);
393
+ return null;
394
+ }
395
+ // Extract enum from the property
396
+ return extractEnumFromPropertyType(configType, propertyName, typeChecker);
397
+ }
398
+ /**
399
+ * Gets the property name from a property assignment
400
+ */
401
+ function getPropertyName(prop) {
402
+ if (ts.isIdentifier(prop.name)) {
403
+ return prop.name.text;
404
+ }
405
+ if (ts.isStringLiteral(prop.name)) {
406
+ return prop.name.text;
407
+ }
408
+ return null;
409
+ }
410
+ /**
411
+ * Parses a parameters string to extract positional arguments
412
+ * Parameters format: "<env> [region] [files...]"
413
+ */
414
+ function parseCommandPattern(pattern) {
415
+ const positionals = [];
416
+ // Split by spaces to get all parameter definitions
417
+ const parts = pattern.split(' ').filter((p) => p.trim());
418
+ for (const part of parts) {
419
+ if (part.startsWith('<') && part.endsWith('>')) {
420
+ // Required positional
421
+ const name = part.slice(1, -1);
422
+ if (name.endsWith('...')) {
423
+ positionals.push({
424
+ name: name.slice(0, -3),
425
+ required: true,
426
+ variadic: true,
427
+ });
428
+ }
429
+ else {
430
+ positionals.push({
431
+ name,
432
+ required: true,
433
+ });
434
+ }
435
+ }
436
+ else if (part.startsWith('[') && part.endsWith(']')) {
437
+ // Optional positional
438
+ const name = part.slice(1, -1);
439
+ if (name.endsWith('...')) {
440
+ positionals.push({
441
+ name: name.slice(0, -3),
442
+ required: false,
443
+ variadic: true,
444
+ });
445
+ }
446
+ else {
447
+ positionals.push({
448
+ name,
449
+ required: false,
450
+ });
451
+ }
452
+ }
453
+ else if (part.trim()) {
454
+ // Found a literal word in the parameters pattern
455
+ throw new Error(`Invalid parameters pattern '${pattern}': found literal word '${part}'. ` +
456
+ `Parameters should only contain <required> or [optional] arguments. ` +
457
+ `Example: "<env> [region]" or "<files...>"`);
458
+ }
459
+ }
460
+ return positionals;
461
+ }
@@ -1,3 +1,3 @@
1
1
  import * as ts from 'typescript';
2
- import { PathToNameAndType } from './types.js';
3
- export declare const addFileExtendsCoreType: (node: ts.Node, checker: ts.TypeChecker, methods: PathToNameAndType, expectedTypeName: string) => void;
2
+ import { PathToNameAndType, InspectorState } from '../types.js';
3
+ export declare const addFileExtendsCoreType: (node: ts.Node, checker: ts.TypeChecker, methods: PathToNameAndType, expectedTypeName: string, state?: InspectorState) => void;
@@ -1,5 +1,5 @@
1
1
  import * as ts from 'typescript';
2
- export const addFileExtendsCoreType = (node, checker, methods, expectedTypeName) => {
2
+ export const addFileExtendsCoreType = (node, checker, methods, expectedTypeName, state) => {
3
3
  if (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node)) {
4
4
  const fileName = node.getSourceFile().fileName;
5
5
  const typeName = node.name?.getText();
@@ -21,13 +21,25 @@ export const addFileExtendsCoreType = (node, checker, methods, expectedTypeName)
21
21
  const sourceFile = declaration.getSourceFile();
22
22
  extendedTypeDeclarationPath = sourceFile.fileName; // Get the path of the file where the extended type was declared
23
23
  }
24
- const variables = methods[fileName] || [];
24
+ const variables = methods.get(fileName) || [];
25
+ if (!typeName) {
26
+ throw new Error('TODO');
27
+ }
25
28
  variables.push({
26
- variable: undefined,
27
- type: typeName,
29
+ variable: typeName,
30
+ type: typeName || null,
28
31
  typePath: extendedTypeDeclarationPath,
29
32
  });
30
- methods[fileName] = variables;
33
+ methods.set(fileName, variables);
34
+ // Store the type in typesLookup if state is provided
35
+ if (state && node.name) {
36
+ const symbol = checker.getSymbolAtLocation(node.name);
37
+ if (symbol) {
38
+ const declaredType = checker.getDeclaredTypeOfSymbol(symbol);
39
+ // Use the type name as the key in typesLookup
40
+ state.typesLookup.set(typeName, [declaredType]);
41
+ }
42
+ }
31
43
  }
32
44
  }
33
45
  }
@@ -1,3 +1,3 @@
1
1
  import * as ts from 'typescript';
2
- import { PathToNameAndType } from './types.js';
2
+ import { PathToNameAndType } from '../types.js';
3
3
  export declare const addFileWithConfig: (node: ts.Node, checker: ts.TypeChecker, configs: PathToNameAndType) => void;
@@ -1,5 +1,5 @@
1
1
  import * as ts from 'typescript';
2
- import { doesTypeExtendsCore } from './does-type-extend-core-type.js';
2
+ import { doesTypeExtendsCore } from '../utils/does-type-extend-core-type.js';
3
3
  export const addFileWithConfig = (node, checker, configs) => {
4
4
  if (ts.isVariableDeclaration(node)) {
5
5
  const fileName = node.getSourceFile().fileName;
@@ -1,3 +1,3 @@
1
1
  import * as ts from 'typescript';
2
- import { PathToNameAndType } from './types.js';
2
+ import { PathToNameAndType } from '../types.js';
3
3
  export declare const addFileWithFactory: (node: ts.Node, checker: ts.TypeChecker, methods: PathToNameAndType | undefined, expectedTypeName: string) => void;
@@ -16,13 +16,13 @@ export const addFileWithFactory = (node, checker, methods = new Map(), expectedT
16
16
  const sourceFile = declaration.getSourceFile();
17
17
  typeDeclarationPath = sourceFile.fileName; // Get the path of the file where the type was declared
18
18
  }
19
- const variables = methods[fileName] || [];
19
+ const variables = methods.get(fileName) || [];
20
20
  variables.push({
21
21
  variable: variableName,
22
22
  type: typeNameNode.getText(),
23
23
  typePath: typeDeclarationPath,
24
24
  });
25
- methods[fileName] = variables;
25
+ methods.set(fileName, variables);
26
26
  }
27
27
  // Handle qualified type names if necessary
28
28
  else if (ts.isQualifiedName(typeNameNode)) {
@@ -34,13 +34,13 @@ export const addFileWithFactory = (node, checker, methods = new Map(), expectedT
34
34
  const sourceFile = declaration.getSourceFile();
35
35
  typeDeclarationPath = sourceFile.fileName; // Get the path of the file where the type was declared
36
36
  }
37
- const variables = methods[fileName] || [];
37
+ const variables = methods.get(fileName) || [];
38
38
  variables.push({
39
39
  variable: variableName,
40
40
  type: typeNameNode.getText(),
41
41
  typePath: typeDeclarationPath,
42
42
  });
43
- methods[fileName] = variables;
43
+ methods.set(fileName, variables);
44
44
  }
45
45
  }
46
46
  }
@@ -0,0 +1,6 @@
1
+ import { AddWiring } from '../types.js';
2
+ /**
3
+ * Inspect pikkuFunc calls, extract input/output and first-arg destructuring,
4
+ * then push into state.functions.meta.
5
+ */
6
+ export declare const addFunctions: AddWiring;