@pikku/core 0.9.12-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 (83) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/errors/errors.d.ts +2 -0
  3. package/dist/errors/errors.js +6 -0
  4. package/dist/function/function-runner.d.ts +5 -4
  5. package/dist/function/function-runner.js +10 -4
  6. package/dist/function/functions.types.d.ts +67 -1
  7. package/dist/function/functions.types.js +43 -1
  8. package/dist/index.d.ts +1 -7
  9. package/dist/index.js +1 -7
  10. package/dist/middleware/auth-apikey.d.ts +19 -11
  11. package/dist/middleware/auth-apikey.js +33 -33
  12. package/dist/middleware/auth-bearer.d.ts +34 -7
  13. package/dist/middleware/auth-bearer.js +53 -36
  14. package/dist/middleware/auth-cookie.d.ts +30 -12
  15. package/dist/middleware/auth-cookie.js +51 -43
  16. package/dist/middleware/timeout.d.ts +6 -2
  17. package/dist/middleware/timeout.js +7 -9
  18. package/dist/middleware-runner.d.ts +0 -18
  19. package/dist/middleware-runner.js +0 -21
  20. package/dist/permissions.d.ts +82 -20
  21. package/dist/permissions.js +175 -62
  22. package/dist/pikku-state.d.ts +22 -2
  23. package/dist/pikku-state.js +7 -1
  24. package/dist/types/core.types.d.ts +58 -1
  25. package/dist/types/core.types.js +23 -1
  26. package/dist/utils.d.ts +1 -1
  27. package/dist/wirings/channel/channel-handler.js +6 -5
  28. package/dist/wirings/channel/channel-runner.d.ts +3 -1
  29. package/dist/wirings/channel/channel-runner.js +4 -21
  30. package/dist/wirings/channel/channel.types.d.ts +12 -13
  31. package/dist/wirings/cli/channel/cli-channel-runner.js +5 -2
  32. package/dist/wirings/cli/cli-runner.d.ts +10 -0
  33. package/dist/wirings/cli/cli-runner.js +44 -20
  34. package/dist/wirings/cli/cli.types.d.ts +54 -27
  35. package/dist/wirings/cli/command-parser.js +34 -4
  36. package/dist/wirings/cli/index.d.ts +1 -1
  37. package/dist/wirings/cli/index.js +1 -1
  38. package/dist/wirings/http/http-runner.d.ts +31 -1
  39. package/dist/wirings/http/http-runner.js +36 -1
  40. package/dist/wirings/http/http.types.d.ts +2 -1
  41. package/dist/wirings/http/index.d.ts +1 -1
  42. package/dist/wirings/http/index.js +1 -1
  43. package/dist/wirings/mcp/mcp-endpoint-registry.d.ts +1 -1
  44. package/dist/wirings/mcp/mcp-runner.js +2 -0
  45. package/dist/wirings/mcp/mcp.types.d.ts +35 -14
  46. package/dist/wirings/scheduler/scheduler-runner.js +1 -0
  47. package/dist/wirings/scheduler/scheduler.types.d.ts +2 -2
  48. package/package.json +6 -5
  49. package/src/errors/errors.ts +6 -0
  50. package/src/factory-functions.test.ts +60 -1
  51. package/src/function/function-runner.test.ts +8 -3
  52. package/src/function/function-runner.ts +18 -5
  53. package/src/function/functions.types.ts +85 -2
  54. package/src/index.ts +1 -21
  55. package/src/middleware/auth-apikey.ts +42 -57
  56. package/src/middleware/auth-bearer.ts +66 -49
  57. package/src/middleware/auth-cookie.ts +63 -82
  58. package/src/middleware/timeout.ts +10 -14
  59. package/src/middleware-runner.ts +0 -25
  60. package/src/permissions.test.ts +26 -27
  61. package/src/permissions.ts +215 -104
  62. package/src/pikku-state.ts +35 -3
  63. package/src/types/core.types.ts +70 -3
  64. package/src/utils.ts +1 -1
  65. package/src/wirings/channel/channel-handler.ts +11 -10
  66. package/src/wirings/channel/channel-runner.ts +18 -27
  67. package/src/wirings/channel/channel.types.ts +24 -15
  68. package/src/wirings/cli/channel/cli-channel-runner.ts +8 -3
  69. package/src/wirings/cli/cli-runner.test.ts +80 -59
  70. package/src/wirings/cli/cli-runner.ts +57 -21
  71. package/src/wirings/cli/cli.types.ts +74 -44
  72. package/src/wirings/cli/command-parser.test.ts +91 -86
  73. package/src/wirings/cli/command-parser.ts +38 -4
  74. package/src/wirings/cli/index.ts +1 -0
  75. package/src/wirings/http/http-runner.ts +41 -1
  76. package/src/wirings/http/http.types.ts +3 -1
  77. package/src/wirings/http/index.ts +7 -1
  78. package/src/wirings/mcp/mcp-endpoint-registry.ts +1 -1
  79. package/src/wirings/mcp/mcp-runner.ts +2 -0
  80. package/src/wirings/mcp/mcp.types.ts +48 -12
  81. package/src/wirings/scheduler/scheduler-runner.ts +1 -0
  82. package/src/wirings/scheduler/scheduler.types.ts +2 -2
  83. package/tsconfig.tsbuildinfo +1 -1
@@ -15,6 +15,7 @@ import {
15
15
  CLIProgramState,
16
16
  CorePikkuCLIRender,
17
17
  CoreCLICommandConfig,
18
+ CLIMeta,
18
19
  } from './cli.types.js'
19
20
  import type {
20
21
  CoreSingletonServices,
@@ -30,6 +31,20 @@ import { PikkuUserSessionService } from '../../services/user-session-service.js'
30
31
  import { LocalVariablesService } from '../../services/local-variables.js'
31
32
  import { generateCommandHelp, parseCLIArguments } from './command-parser.js'
32
33
 
34
+ /**
35
+ * CLI command execution error - thrown when CLI execution fails
36
+ * Should be caught by the wrapper to call process.exit()
37
+ */
38
+ export class CLIError extends Error {
39
+ constructor(
40
+ message: string,
41
+ public exitCode: number = 1
42
+ ) {
43
+ super(message)
44
+ this.name = 'CLIError'
45
+ }
46
+ }
47
+
33
48
  /**
34
49
  * Default JSON renderer for CLI output
35
50
  */
@@ -51,7 +66,7 @@ export const wireCLI = <
51
66
  // Get the existing metadata that was generated during inspection
52
67
  const cliMeta = pikkuState('cli', 'meta') || {}
53
68
 
54
- if (!cliMeta[cli.program]) {
69
+ if (!cliMeta.programs?.[cli.program]) {
55
70
  throw new Error(
56
71
  `CLI metadata not found for program '${cli.program}'. Did you run 'pikku all'?`
57
72
  )
@@ -122,7 +137,7 @@ function registerCLICommands(
122
137
  program: string
123
138
  ) {
124
139
  // Get the CLI metadata to find actual function names
125
- const cliMeta = pikkuState('cli', 'meta')[program]
140
+ const cliMeta = pikkuState('cli', 'meta').programs[program]
126
141
 
127
142
  for (const [name, command] of Object.entries(commands)) {
128
143
  const fullPath = [...path, name]
@@ -242,7 +257,7 @@ export async function runCLICommand({
242
257
  }): Promise<any> {
243
258
  // Get the command metadata to find the function name
244
259
  const cliMeta = pikkuState('cli', 'meta')
245
- const programMeta = cliMeta[program]
260
+ const programMeta = cliMeta.programs?.[program]
246
261
  if (!programMeta) {
247
262
  throw new NotFoundError(`Program not found: ${program}`)
248
263
  }
@@ -346,14 +361,6 @@ export async function runCLICommand({
346
361
  )
347
362
  }
348
363
 
349
- // Build inherited middleware from tags
350
- const inheritedMiddleware: any[] = []
351
- if (programData?.tags) {
352
- for (const tag of programData.tags) {
353
- inheritedMiddleware.push({ type: 'tag', tag })
354
- }
355
- }
356
-
357
364
  try {
358
365
  const result = await runPikkuFunc(
359
366
  PikkuWiringTypes.cli,
@@ -365,8 +372,11 @@ export async function runCLICommand({
365
372
  data: pluckedData,
366
373
  auth: false,
367
374
  userSession,
368
- inheritedMiddleware,
375
+ inheritedMiddleware: currentCommand.middleware,
369
376
  wireMiddleware: allWireMiddleware,
377
+ inheritedPermissions: currentCommand.permissions,
378
+ wirePermissions: undefined,
379
+ tags: programData?.tags,
370
380
  interaction,
371
381
  }
372
382
  )
@@ -410,10 +420,12 @@ export const pikkuCLIRender = <
410
420
  /**
411
421
  * Execute a CLI program with the given arguments
412
422
  * This is the main entry point for CLI programs
423
+ *
424
+ * @throws {CLIError} When CLI execution fails - should be caught by wrapper to call process.exit()
413
425
  */
414
426
  export async function executeCLI({
415
427
  programName,
416
- args = process.argv.slice(2),
428
+ args,
417
429
  createConfig,
418
430
  createSingletonServices,
419
431
  createSessionServices,
@@ -424,21 +436,39 @@ export async function executeCLI({
424
436
  createSingletonServices: CreateSingletonServices<any, any>
425
437
  createSessionServices?: CreateSessionServices<any, any>
426
438
  }): Promise<void> {
439
+ if (!args) {
440
+ throw new Error(
441
+ 'CLI arguments are required, this is to satisfy release diffs'
442
+ )
443
+ }
444
+
427
445
  try {
428
446
  // Get CLI metadata from state
429
- const allCLIMeta = pikkuState('cli', 'meta') || {}
430
- const programMeta = allCLIMeta[programName]
447
+ const allCLIMeta = pikkuState('cli', 'meta') as unknown as
448
+ | CLIMeta
449
+ | undefined
450
+ if (!allCLIMeta) {
451
+ throw new Error(
452
+ '[PKU342] CLI metadata not found. No CLI wirings were registered. See https://pikku.dev/errors/pku342 for more information.'
453
+ )
454
+ }
455
+ const programMeta = allCLIMeta.programs[programName]
431
456
 
432
457
  if (!programMeta) {
433
- console.error(`Error: CLI program "${programName}" not found`)
434
- process.exit(1)
458
+ throw new CLIError(`CLI program "${programName}" not found`, 1)
435
459
  }
436
460
 
437
461
  // Parse arguments for this specific program
438
462
  const parsed = parseCLIArguments(args, programName, allCLIMeta)
439
463
 
440
464
  // Handle help (check after parsing to support subcommand help)
441
- if (args.includes('--help') || args.includes('-h') || args.length === 0) {
465
+ // Show help if --help/-h is present, or if no args AND no default command
466
+ const shouldShowHelp =
467
+ args.includes('--help') ||
468
+ args.includes('-h') ||
469
+ (args.length === 0 && parsed.commandPath.length === 0)
470
+
471
+ if (shouldShowHelp) {
442
472
  const helpText = generateCommandHelp(
443
473
  programName,
444
474
  allCLIMeta,
@@ -464,12 +494,12 @@ export async function executeCLI({
464
494
  parsed.commandPath
465
495
  )
466
496
  console.log(helpText)
467
- process.exit(1)
497
+ throw new CLIError('Unknown command', 1)
468
498
  } else {
469
499
  // Show errors for other types of errors
470
500
  console.error('Errors:')
471
501
  parsed.errors.forEach((error) => console.error(` ${error}`))
472
- process.exit(1)
502
+ throw new CLIError(parsed.errors.join('\n'), 1)
473
503
  }
474
504
  }
475
505
 
@@ -491,6 +521,12 @@ export async function executeCLI({
491
521
  createSessionServices,
492
522
  })
493
523
  } catch (error: any) {
524
+ // Re-throw CLIError as-is
525
+ if (error instanceof CLIError) {
526
+ throw error
527
+ }
528
+
529
+ // Wrap other errors in CLIError
494
530
  console.error('Error:', error)
495
531
 
496
532
  // Show stack trace in verbose mode
@@ -498,6 +534,6 @@ export async function executeCLI({
498
534
  console.error('Stack trace:', error.stack)
499
535
  }
500
536
 
501
- process.exit(1)
537
+ throw new CLIError(error.message || String(error), 1)
502
538
  }
503
539
  }
@@ -5,6 +5,7 @@ import {
5
5
  PikkuDocs,
6
6
  CoreServices,
7
7
  MiddlewareMetadata,
8
+ PermissionMetadata,
8
9
  } from '../../types/core.types.js'
9
10
  import {
10
11
  CorePikkuFunctionConfig,
@@ -78,6 +79,8 @@ export interface CLICommandMeta {
78
79
  tags?: string[]
79
80
  subcommands?: Record<string, CLICommandMeta>
80
81
  middleware?: MiddlewareMetadata[] // Pre-resolved middleware chain (tag + explicit)
82
+ permissions?: PermissionMetadata[] // Pre-resolved permission chain (tag + explicit)
83
+ isDefault?: boolean
81
84
  }
82
85
 
83
86
  /**
@@ -90,10 +93,28 @@ export type CLIProgramMeta = {
90
93
  defaultRenderName?: string
91
94
  }
92
95
 
96
+ /**
97
+ * Renderer metadata for CLI commands
98
+ */
99
+ export interface RendererMeta {
100
+ name: string
101
+ exportedName?: string
102
+ services: {
103
+ optimized: boolean
104
+ services: string[]
105
+ }
106
+ filePath: string
107
+ }
108
+
109
+ export type RenderersMeta = Record<string, RendererMeta>
110
+
93
111
  /**
94
112
  * All CLI programs metadata
95
113
  */
96
- export type CLIMeta = Record<string, CLIProgramMeta>
114
+ export interface CLIMeta {
115
+ programs: Record<string, CLIProgramMeta>
116
+ renderers: RenderersMeta
117
+ }
97
118
 
98
119
  /**
99
120
  * CLI-specific renderer that outputs to console
@@ -108,7 +129,7 @@ export type CorePikkuCLIRender<
108
129
  * Extract input parameters from a Pikku function config type
109
130
  */
110
131
  export type ExtractFunctionInput<Func> =
111
- Func extends CorePikkuFunctionConfig<infer FuncType>
132
+ Func extends CorePikkuFunctionConfig<infer FuncType, any, any>
112
133
  ? FuncType extends
113
134
  | CorePikkuFunction<infer Input, any, any, any, any>
114
135
  | CorePikkuFunctionSessionless<infer Input, any, any, any, any>
@@ -135,30 +156,47 @@ type SplitBySpace<S extends string> = S extends `${infer First} ${infer Rest}`
135
156
  : [S]
136
157
 
137
158
  /**
138
- * Extract parameter names from CLI parameter string
159
+ * Recursively build a tuple by stripping brackets from each element
160
+ */
161
+ type BuildParamsTuple<Parts extends readonly string[]> =
162
+ Parts extends readonly [
163
+ infer First extends string,
164
+ ...infer Rest extends string[],
165
+ ]
166
+ ? [StripBrackets<First>, ...BuildParamsTuple<Rest>]
167
+ : []
168
+
169
+ /**
170
+ * Extract parameter names from CLI parameter string into a tuple
139
171
  * Example: "<env> [region]" => ["env", "region"]
140
172
  */
141
- type ExtractParameterNames<S extends string> = {
142
- [K in keyof SplitBySpace<S>]: SplitBySpace<S>[K] extends string
143
- ? StripBrackets<SplitBySpace<S>[K]>
144
- : never
145
- }
173
+ type ExtractParameterNames<S extends string> = BuildParamsTuple<SplitBySpace<S>>
174
+
175
+ /**
176
+ * Check if all elements of a tuple are valid keys of Input
177
+ */
178
+ type AllParamsValid<
179
+ Params extends readonly any[],
180
+ Input,
181
+ > = Params extends readonly [infer First, ...infer Rest]
182
+ ? First extends keyof Input
183
+ ? AllParamsValid<Rest, Input>
184
+ : false
185
+ : true
146
186
 
147
187
  /**
148
188
  * Validate that all parameter names are valid keys of the function input
149
189
  */
150
190
  export type ValidateParameters<Params extends string, Input> =
151
- ExtractParameterNames<Params> extends (infer P)[]
152
- ? P extends keyof Input
153
- ? Params
154
- : never
191
+ AllParamsValid<ExtractParameterNames<Params>, Input> extends true
192
+ ? Params
155
193
  : never
156
194
 
157
195
  /**
158
196
  * Extract output type from a Pikku function config type
159
197
  */
160
198
  export type ExtractFunctionOutput<Func> =
161
- Func extends CorePikkuFunctionConfig<infer FuncType>
199
+ Func extends CorePikkuFunctionConfig<infer FuncType, any, any>
162
200
  ? FuncType extends
163
201
  | CorePikkuFunction<any, infer Output, any, any, any>
164
202
  | CorePikkuFunctionSessionless<any, infer Output, any, any, any>
@@ -170,43 +208,34 @@ export type ExtractFunctionOutput<Func> =
170
208
  * CLI command configuration that infers options from function input type.
171
209
  * This is a helper type for creating type-safe CLI commands.
172
210
  */
173
- export interface CoreCLICommandConfig<
174
- Func,
211
+ export type CoreCLICommandConfig<
212
+ FuncConfig,
175
213
  PikkuMiddleware extends CorePikkuMiddleware<any> = CorePikkuMiddleware<any>,
176
214
  PikkuCLIRender extends CorePikkuCLIRender<any, any, any> = CorePikkuCLIRender<
177
215
  any,
178
216
  any
179
217
  >,
180
- > {
181
- parameters?: ValidateParameters<string, ExtractFunctionInput<Func>>
182
- func?: Func
218
+ Params extends string = string,
219
+ > = {
220
+ parameters?: ValidateParameters<Params, ExtractFunctionInput<FuncConfig>>
221
+ func?: FuncConfig
183
222
  description?: string
184
223
  render?: PikkuCLIRender
185
- options?: Partial<
186
- Record<
187
- keyof ExtractFunctionInput<Func>,
188
- {
189
- description?: string
190
- short?: string
191
- default?: ExtractFunctionInput<Func>[keyof ExtractFunctionInput<Func>]
192
- }
193
- >
194
- > &
195
- Record<
196
- string,
197
- {
198
- description?: string
199
- short?: string
200
- default?: any
201
- }
202
- >
224
+ options?: {
225
+ [K in keyof ExtractFunctionInput<FuncConfig>]?: {
226
+ description?: string
227
+ short?: string
228
+ default?: ExtractFunctionInput<FuncConfig>[K]
229
+ }
230
+ }
203
231
  middleware?: PikkuMiddleware[]
204
232
  subcommands?: Record<
205
233
  string,
206
- CoreCLICommandConfig<any, PikkuMiddleware, PikkuCLIRender>
234
+ CoreCLICommandConfig<any, PikkuMiddleware, PikkuCLIRender, any>
207
235
  >
208
236
  auth?: boolean
209
237
  permissions?: any[]
238
+ isDefault?: boolean
210
239
  }
211
240
 
212
241
  /**
@@ -215,7 +244,7 @@ export interface CoreCLICommandConfig<
215
244
  export interface CoreCLICommand<
216
245
  In,
217
246
  Out,
218
- PikkuFunction extends CorePikkuFunctionConfig<
247
+ PikkuFunctionConfig extends CorePikkuFunctionConfig<
219
248
  | CorePikkuFunction<In, Out, any, any, any>
220
249
  | CorePikkuFunctionSessionless<In, Out, any, any, any>
221
250
  >,
@@ -228,7 +257,7 @@ export interface CoreCLICommand<
228
257
  > = Record<string, CoreCLICommandConfig<any, any, any>>,
229
258
  > {
230
259
  parameters?: string
231
- func: PikkuFunction
260
+ func: PikkuFunctionConfig
232
261
  render?: CorePikkuCLIRender<Out>
233
262
  description?: string
234
263
  options?: CLIOptions<Options>
@@ -237,6 +266,7 @@ export interface CoreCLICommand<
237
266
  auth?: boolean
238
267
  docs?: PikkuDocs
239
268
  subcommands?: Subcommands
269
+ isDefault?: boolean
240
270
  }
241
271
 
242
272
  /**
@@ -245,11 +275,11 @@ export interface CoreCLICommand<
245
275
  export type CLICommandShorthand<
246
276
  In,
247
277
  Out,
248
- PikkuFunction extends CorePikkuFunctionConfig<
278
+ PikkuFunctionConfig extends CorePikkuFunctionConfig<
249
279
  | CorePikkuFunction<In, Out, any, any, any>
250
280
  | CorePikkuFunctionSessionless<In, Out, any, any, any>
251
281
  >,
252
- > = PikkuFunction
282
+ > = PikkuFunctionConfig
253
283
 
254
284
  /**
255
285
  * Command definition (either full or shorthand)
@@ -257,7 +287,7 @@ export type CLICommandShorthand<
257
287
  export type CLICommandDefinition<
258
288
  In,
259
289
  Out,
260
- PikkuFunction extends CorePikkuFunctionConfig<
290
+ PikkuFunctionConfig extends CorePikkuFunctionConfig<
261
291
  | CorePikkuFunction<In, Out, any, any, any>
262
292
  | CorePikkuFunctionSessionless<In, Out, any, any, any>
263
293
  >,
@@ -272,13 +302,13 @@ export type CLICommandDefinition<
272
302
  | CoreCLICommand<
273
303
  In,
274
304
  Out,
275
- PikkuFunction,
305
+ PikkuFunctionConfig,
276
306
  PikkuPermission,
277
307
  PikkuMiddleware,
278
308
  Options,
279
309
  Subcommands
280
310
  >
281
- | CLICommandShorthand<In, Out, PikkuFunction>
311
+ | CLICommandShorthand<In, Out, PikkuFunctionConfig>
282
312
 
283
313
  /**
284
314
  * CLI wiring configuration
@@ -4,78 +4,80 @@ import { parseCLIArguments, generateCommandHelp } from './command-parser.js'
4
4
  import { CLIMeta } from './cli.types.js'
5
5
 
6
6
  const testMeta: CLIMeta = {
7
- 'test-cli': {
8
- program: 'test-cli',
9
- options: {
10
- verbose: {
11
- description: 'Enable verbose output',
12
- short: 'v',
13
- default: false,
7
+ programs: {
8
+ 'test-cli': {
9
+ program: 'test-cli',
10
+ options: {
11
+ verbose: {
12
+ description: 'Enable verbose output',
13
+ short: 'v',
14
+ default: false,
15
+ },
14
16
  },
15
- },
16
- commands: {
17
- greet: {
18
- command: 'greet <name>',
19
- pikkuFuncName: 'greetFunc',
20
- positionals: [{ name: 'name', required: true }],
21
- options: {
22
- loud: {
23
- description: 'Use loud greeting',
24
- short: 'l',
25
- default: false,
17
+ commands: {
18
+ greet: {
19
+ parameters: '<name>',
20
+ pikkuFuncName: 'greetFunc',
21
+ positionals: [{ name: 'name', required: true }],
22
+ options: {
23
+ loud: {
24
+ description: 'Use loud greeting',
25
+ short: 'l',
26
+ default: false,
27
+ },
26
28
  },
27
29
  },
28
- },
29
- user: {
30
- command: 'user',
31
- pikkuFuncName: '',
32
- positionals: [],
33
- options: {},
34
- subcommands: {
35
- create: {
36
- command: 'create <name> <email>',
37
- pikkuFuncName: 'createUserFunc',
38
- positionals: [
39
- { name: 'name', required: true },
40
- { name: 'email', required: true },
41
- ],
42
- options: {
43
- role: {
44
- description: 'User role',
45
- short: 'r',
46
- default: 'user',
47
- choices: ['admin', 'user', 'guest'],
30
+ user: {
31
+ pikkuFuncName: '',
32
+ positionals: [],
33
+ options: {},
34
+ subcommands: {
35
+ create: {
36
+ parameters: '<name> <email>',
37
+ pikkuFuncName: 'createUserFunc',
38
+ positionals: [
39
+ { name: 'name', required: true },
40
+ { name: 'email', required: true },
41
+ ],
42
+ options: {
43
+ role: {
44
+ description: 'User role',
45
+ short: 'r',
46
+ default: 'user',
47
+ choices: ['admin', 'user', 'guest'],
48
+ },
48
49
  },
49
50
  },
50
- },
51
- delete: {
52
- command: 'delete <id>',
53
- pikkuFuncName: 'deleteUserFunc',
54
- positionals: [{ name: 'id', required: true }],
55
- options: {
56
- force: {
57
- description: 'Force delete',
58
- short: 'f',
59
- default: false,
51
+ delete: {
52
+ parameters: '<id>',
53
+ pikkuFuncName: 'deleteUserFunc',
54
+ positionals: [{ name: 'id', required: true }],
55
+ options: {
56
+ force: {
57
+ description: 'Force delete',
58
+ short: 'f',
59
+ default: false,
60
+ },
60
61
  },
61
62
  },
62
63
  },
63
64
  },
64
- },
65
- files: {
66
- command: 'files <paths...>',
67
- pikkuFuncName: 'filesFunc',
68
- positionals: [{ name: 'paths', required: true, variadic: true }],
69
- options: {},
70
- },
71
- optional: {
72
- command: 'optional [name]',
73
- pikkuFuncName: 'optionalFunc',
74
- positionals: [{ name: 'name', required: false }],
75
- options: {},
65
+ files: {
66
+ parameters: '<paths...>',
67
+ pikkuFuncName: 'filesFunc',
68
+ positionals: [{ name: 'paths', required: true, variadic: true }],
69
+ options: {},
70
+ },
71
+ optional: {
72
+ parameters: '[name]',
73
+ pikkuFuncName: 'optionalFunc',
74
+ positionals: [{ name: 'name', required: false }],
75
+ options: {},
76
+ },
76
77
  },
77
78
  },
78
79
  },
80
+ renderers: {},
79
81
  }
80
82
 
81
83
  describe('Command Parser', () => {
@@ -291,23 +293,25 @@ describe('Command Parser', () => {
291
293
 
292
294
  test('should parse number values correctly', () => {
293
295
  const metaWithNumber: CLIMeta = {
294
- 'num-cli': {
295
- program: 'num-cli',
296
- options: {},
297
- commands: {
298
- test: {
299
- command: 'test',
300
- pikkuFuncName: 'testFunc',
301
- positionals: [],
302
- options: {
303
- port: {
304
- description: 'Port number',
305
- default: 3000,
296
+ programs: {
297
+ 'num-cli': {
298
+ program: 'num-cli',
299
+ options: {},
300
+ commands: {
301
+ test: {
302
+ pikkuFuncName: 'testFunc',
303
+ positionals: [],
304
+ options: {
305
+ port: {
306
+ description: 'Port number',
307
+ default: 3000,
308
+ },
306
309
  },
307
310
  },
308
311
  },
309
312
  },
310
313
  },
314
+ renderers: {},
311
315
  }
312
316
 
313
317
  const result = parseCLIArguments(
@@ -349,8 +353,7 @@ describe('Command Parser', () => {
349
353
  assert.ok(help.includes('--verbose'))
350
354
  })
351
355
 
352
- // TODO: Fix this test - help generation issue
353
- test.skip('should generate help for specific command', () => {
356
+ test('should generate help for specific command', () => {
354
357
  const help = generateCommandHelp('test-cli', testMeta, ['greet'])
355
358
 
356
359
  assert.ok(help.includes('Usage: test-cli greet'))
@@ -359,8 +362,7 @@ describe('Command Parser', () => {
359
362
  assert.ok(help.includes('Use loud greeting'))
360
363
  })
361
364
 
362
- // TODO: Fix this test - help generation issue
363
- test.skip('should generate help for subcommand', () => {
365
+ test('should generate help for subcommand', () => {
364
366
  const help = generateCommandHelp('test-cli', testMeta, ['user', 'create'])
365
367
 
366
368
  assert.ok(help.includes('Usage: test-cli user create'))
@@ -371,19 +373,22 @@ describe('Command Parser', () => {
371
373
 
372
374
  test('should show command description if available', () => {
373
375
  const metaWithDesc: CLIMeta = {
374
- 'test-cli': {
375
- program: 'test-cli',
376
- options: {},
377
- commands: {
378
- greet: {
379
- command: 'greet <name>',
380
- pikkuFuncName: 'greetFunc',
381
- description: 'Greet a user',
382
- positionals: [{ name: 'name', required: true }],
383
- options: {},
376
+ programs: {
377
+ 'test-cli': {
378
+ program: 'test-cli',
379
+ options: {},
380
+ commands: {
381
+ greet: {
382
+ parameters: '<name>',
383
+ pikkuFuncName: 'greetFunc',
384
+ description: 'Greet a user',
385
+ positionals: [{ name: 'name', required: true }],
386
+ options: {},
387
+ },
384
388
  },
385
389
  },
386
390
  },
391
+ renderers: {},
387
392
  }
388
393
 
389
394
  const help = generateCommandHelp('test-cli', metaWithDesc, ['greet'])