@pikku/core 0.12.4 → 0.12.5
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.
- package/CHANGELOG.md +11 -1
- package/dist/dev/hot-reload.d.ts +10 -0
- package/dist/dev/hot-reload.js +158 -0
- package/dist/middleware-runner.d.ts +1 -0
- package/dist/middleware-runner.js +5 -0
- package/dist/permissions.d.ts +1 -0
- package/dist/permissions.js +5 -0
- package/dist/services/in-memory-workflow-service.js +7 -11
- package/dist/wirings/ai-agent/ai-agent-prepare.js +16 -1
- package/dist/wirings/channel/channel-middleware-runner.d.ts +1 -0
- package/dist/wirings/channel/channel-middleware-runner.js +5 -0
- package/dist/wirings/workflow/pikku-workflow-service.js +7 -8
- package/package.json +3 -2
- package/src/crypto-utils.test.ts +214 -0
- package/src/crypto-utils.ts +213 -0
- package/src/dev/hot-reload.test.ts +484 -0
- package/src/dev/hot-reload.ts +212 -0
- package/src/errors/error-handler.ts +62 -0
- package/src/errors/error.test.ts +195 -0
- package/src/errors/errors.ts +374 -0
- package/src/errors/index.ts +2 -0
- package/src/factory-functions.test.ts +109 -0
- package/src/function/function-runner.test.ts +536 -0
- package/src/function/function-runner.ts +365 -0
- package/src/function/functions.types.ts +304 -0
- package/src/function/index.ts +5 -0
- package/src/handle-error.test.ts +424 -0
- package/src/handle-error.ts +69 -0
- package/src/index.ts +118 -0
- package/src/internal.ts +6 -0
- package/src/middleware/auth-apikey.test.ts +363 -0
- package/src/middleware/auth-apikey.ts +47 -0
- package/src/middleware/auth-bearer.test.ts +450 -0
- package/src/middleware/auth-bearer.ts +72 -0
- package/src/middleware/auth-cookie.test.ts +528 -0
- package/src/middleware/auth-cookie.ts +80 -0
- package/src/middleware/cors.test.ts +424 -0
- package/src/middleware/cors.ts +104 -0
- package/src/middleware/index.ts +5 -0
- package/src/middleware/remote-auth.test.ts +488 -0
- package/src/middleware/remote-auth.ts +68 -0
- package/src/middleware/timeout.ts +15 -0
- package/src/middleware-runner.test.ts +418 -0
- package/src/middleware-runner.ts +240 -0
- package/src/permissions.test.ts +434 -0
- package/src/permissions.ts +327 -0
- package/src/pikku-request.ts +23 -0
- package/src/pikku-response.ts +5 -0
- package/src/pikku-state.test.ts +224 -0
- package/src/pikku-state.ts +216 -0
- package/src/run-tests-script.test.ts +49 -0
- package/src/schema.test.ts +249 -0
- package/src/schema.ts +151 -0
- package/src/services/ai-agent-runner-service.ts +41 -0
- package/src/services/ai-run-state-service.ts +20 -0
- package/src/services/ai-storage-service.ts +39 -0
- package/src/services/content-service.ts +76 -0
- package/src/services/deployment-service.ts +22 -0
- package/src/services/gateway-service.ts +20 -0
- package/src/services/gopass-secrets.ts +98 -0
- package/src/services/in-memory-ai-run-state-service.ts +73 -0
- package/src/services/in-memory-trigger-service.ts +64 -0
- package/src/services/in-memory-workflow-service.test.ts +351 -0
- package/src/services/in-memory-workflow-service.ts +502 -0
- package/src/services/index.ts +56 -0
- package/src/services/jwt-service.ts +30 -0
- package/src/services/local-content.ts +120 -0
- package/src/services/local-gateway-service.ts +62 -0
- package/src/services/local-secrets.test.ts +80 -0
- package/src/services/local-secrets.ts +94 -0
- package/src/services/local-variables.test.ts +61 -0
- package/src/services/local-variables.ts +39 -0
- package/src/services/logger-console.test.ts +118 -0
- package/src/services/logger-console.ts +98 -0
- package/src/services/logger.ts +57 -0
- package/src/services/scheduler-service.ts +86 -0
- package/src/services/schema-service.ts +33 -0
- package/src/services/scoped-secret-service.test.ts +74 -0
- package/src/services/scoped-secret-service.ts +41 -0
- package/src/services/secret-service.ts +38 -0
- package/src/services/trigger-service.ts +17 -0
- package/src/services/typed-secret-service.test.ts +93 -0
- package/src/services/typed-secret-service.ts +70 -0
- package/src/services/typed-variables-service.test.ts +73 -0
- package/src/services/typed-variables-service.ts +81 -0
- package/src/services/user-session-service.test.ts +113 -0
- package/src/services/user-session-service.ts +86 -0
- package/src/services/variables-service.ts +11 -0
- package/src/services/workflow-service.ts +95 -0
- package/src/testing/index.ts +2 -0
- package/src/testing/service-tests.ts +873 -0
- package/src/time-utils.test.ts +56 -0
- package/src/time-utils.ts +107 -0
- package/src/types/core.types.ts +478 -0
- package/src/types/state.types.ts +188 -0
- package/src/utils/hash.test.ts +68 -0
- package/src/utils/hash.ts +26 -0
- package/src/utils.test.ts +350 -0
- package/src/utils.ts +129 -0
- package/src/version.test.ts +80 -0
- package/src/version.ts +25 -0
- package/src/wirings/ai-agent/agent-dynamic-workflow.ts +469 -0
- package/src/wirings/ai-agent/ai-agent-helpers.test.ts +152 -0
- package/src/wirings/ai-agent/ai-agent-helpers.ts +76 -0
- package/src/wirings/ai-agent/ai-agent-memory.ts +310 -0
- package/src/wirings/ai-agent/ai-agent-model-config.test.ts +115 -0
- package/src/wirings/ai-agent/ai-agent-model-config.ts +43 -0
- package/src/wirings/ai-agent/ai-agent-prepare.ts +659 -0
- package/src/wirings/ai-agent/ai-agent-registry.test.ts +37 -0
- package/src/wirings/ai-agent/ai-agent-registry.ts +82 -0
- package/src/wirings/ai-agent/ai-agent-runner.test.ts +319 -0
- package/src/wirings/ai-agent/ai-agent-runner.ts +773 -0
- package/src/wirings/ai-agent/ai-agent-stream.test.ts +859 -0
- package/src/wirings/ai-agent/ai-agent-stream.ts +1153 -0
- package/src/wirings/ai-agent/ai-agent.types.ts +382 -0
- package/src/wirings/ai-agent/index.ts +37 -0
- package/src/wirings/channel/channel-common.ts +90 -0
- package/src/wirings/channel/channel-handler.ts +250 -0
- package/src/wirings/channel/channel-middleware-runner.ts +126 -0
- package/src/wirings/channel/channel-runner.ts +166 -0
- package/src/wirings/channel/channel-store.ts +29 -0
- package/src/wirings/channel/channel.types.ts +172 -0
- package/src/wirings/channel/define-channel-routes.ts +25 -0
- package/src/wirings/channel/eventhub-service.ts +34 -0
- package/src/wirings/channel/eventhub-store.ts +13 -0
- package/src/wirings/channel/index.ts +24 -0
- package/src/wirings/channel/local/index.ts +3 -0
- package/src/wirings/channel/local/local-channel-handler.ts +81 -0
- package/src/wirings/channel/local/local-channel-runner.test.ts +215 -0
- package/src/wirings/channel/local/local-channel-runner.ts +210 -0
- package/src/wirings/channel/local/local-eventhub-service.test.ts +95 -0
- package/src/wirings/channel/local/local-eventhub-service.ts +101 -0
- package/src/wirings/channel/log-channels.ts +20 -0
- package/src/wirings/channel/pikku-abstract-channel-handler.test.ts +54 -0
- package/src/wirings/channel/pikku-abstract-channel-handler.ts +45 -0
- package/src/wirings/channel/serverless/index.ts +5 -0
- package/src/wirings/channel/serverless/serverless-channel-runner.ts +289 -0
- package/src/wirings/cli/channel/cli-channel-runner.ts +136 -0
- package/src/wirings/cli/channel/index.ts +1 -0
- package/src/wirings/cli/cli-runner.test.ts +403 -0
- package/src/wirings/cli/cli-runner.ts +529 -0
- package/src/wirings/cli/cli.types.ts +358 -0
- package/src/wirings/cli/command-parser.test.ts +445 -0
- package/src/wirings/cli/command-parser.ts +504 -0
- package/src/wirings/cli/define-cli-commands.ts +24 -0
- package/src/wirings/cli/index.ts +19 -0
- package/src/wirings/gateway/gateway-runner.test.ts +474 -0
- package/src/wirings/gateway/gateway-runner.ts +411 -0
- package/src/wirings/gateway/gateway.types.ts +149 -0
- package/src/wirings/gateway/index.ts +13 -0
- package/src/wirings/http/http-routes.test.ts +322 -0
- package/src/wirings/http/http-routes.ts +197 -0
- package/src/wirings/http/http-runner.test.ts +144 -0
- package/src/wirings/http/http-runner.ts +588 -0
- package/src/wirings/http/http.types.ts +369 -0
- package/src/wirings/http/index.ts +28 -0
- package/src/wirings/http/log-http-routes.ts +22 -0
- package/src/wirings/http/pikku-fetch-http-request.test.ts +237 -0
- package/src/wirings/http/pikku-fetch-http-request.ts +170 -0
- package/src/wirings/http/pikku-fetch-http-response.test.ts +82 -0
- package/src/wirings/http/pikku-fetch-http-response.ts +147 -0
- package/src/wirings/http/routers/http-router.ts +13 -0
- package/src/wirings/http/routers/path-to-regex.test.ts +319 -0
- package/src/wirings/http/routers/path-to-regex.ts +126 -0
- package/src/wirings/http/web-request.test.ts +236 -0
- package/src/wirings/http/web-request.ts +104 -0
- package/src/wirings/mcp/index.ts +27 -0
- package/src/wirings/mcp/mcp-endpoint-registry.test.ts +389 -0
- package/src/wirings/mcp/mcp-endpoint-registry.ts +159 -0
- package/src/wirings/mcp/mcp-runner.ts +323 -0
- package/src/wirings/mcp/mcp.types.ts +216 -0
- package/src/wirings/node/index.ts +2 -0
- package/src/wirings/node/node.types.ts +23 -0
- package/src/wirings/oauth2/index.ts +3 -0
- package/src/wirings/oauth2/oauth2-client.test.ts +929 -0
- package/src/wirings/oauth2/oauth2-client.ts +335 -0
- package/src/wirings/oauth2/oauth2.types.ts +69 -0
- package/src/wirings/oauth2/wire-oauth2-credential.ts +23 -0
- package/src/wirings/queue/index.ts +31 -0
- package/src/wirings/queue/queue-runner.test.ts +710 -0
- package/src/wirings/queue/queue-runner.ts +192 -0
- package/src/wirings/queue/queue.types.ts +189 -0
- package/src/wirings/queue/register-queue-helper.ts +60 -0
- package/src/wirings/queue/validate-worker-config.test.ts +108 -0
- package/src/wirings/queue/validate-worker-config.ts +116 -0
- package/src/wirings/rpc/index.ts +4 -0
- package/src/wirings/rpc/rpc-runner.ts +460 -0
- package/src/wirings/rpc/rpc-types.ts +56 -0
- package/src/wirings/rpc/wire-addon.ts +21 -0
- package/src/wirings/scheduler/index.ts +11 -0
- package/src/wirings/scheduler/log-schedulers.ts +20 -0
- package/src/wirings/scheduler/scheduler-runner.test.ts +660 -0
- package/src/wirings/scheduler/scheduler-runner.ts +133 -0
- package/src/wirings/scheduler/scheduler.types.ts +53 -0
- package/src/wirings/secret/index.ts +9 -0
- package/src/wirings/secret/secret.types.ts +32 -0
- package/src/wirings/secret/validate-secret-definitions.test.ts +140 -0
- package/src/wirings/secret/validate-secret-definitions.ts +82 -0
- package/src/wirings/trigger/index.ts +10 -0
- package/src/wirings/trigger/pikku-trigger-service.ts +112 -0
- package/src/wirings/trigger/trigger-runner.test.ts +79 -0
- package/src/wirings/trigger/trigger-runner.ts +135 -0
- package/src/wirings/trigger/trigger.types.ts +178 -0
- package/src/wirings/variable/index.ts +8 -0
- package/src/wirings/variable/validate-variable-definitions.test.ts +91 -0
- package/src/wirings/variable/validate-variable-definitions.ts +69 -0
- package/src/wirings/variable/variable.types.ts +22 -0
- package/src/wirings/workflow/dsl/index.ts +31 -0
- package/src/wirings/workflow/dsl/workflow-dsl.types.ts +320 -0
- package/src/wirings/workflow/dsl/workflow-runner.ts +28 -0
- package/src/wirings/workflow/graph/graph-node.ts +222 -0
- package/src/wirings/workflow/graph/graph-runner.test.ts +487 -0
- package/src/wirings/workflow/graph/graph-runner.ts +816 -0
- package/src/wirings/workflow/graph/graph-validation.test.ts +170 -0
- package/src/wirings/workflow/graph/graph-validation.ts +237 -0
- package/src/wirings/workflow/graph/index.ts +19 -0
- package/src/wirings/workflow/graph/template.test.ts +49 -0
- package/src/wirings/workflow/graph/template.ts +42 -0
- package/src/wirings/workflow/graph/wire-workflow-graph.ts +29 -0
- package/src/wirings/workflow/graph/workflow-graph.types.ts +104 -0
- package/src/wirings/workflow/index.ts +82 -0
- package/src/wirings/workflow/pikku-workflow-service.test.ts +200 -0
- package/src/wirings/workflow/pikku-workflow-service.ts +1179 -0
- package/src/wirings/workflow/workflow-helpers.test.ts +129 -0
- package/src/wirings/workflow/workflow-helpers.ts +79 -0
- package/src/wirings/workflow/workflow.types.ts +274 -0
- package/tsconfig.json +14 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/lcov.info +0 -18891
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
import { NotFoundError, PikkuMissingMetaError } from '../../errors/errors.js'
|
|
2
|
+
import { addFunction, runPikkuFunc } from '../../function/function-runner.js'
|
|
3
|
+
import { pikkuState } from '../../pikku-state.js'
|
|
4
|
+
import type {
|
|
5
|
+
CorePikkuMiddleware,
|
|
6
|
+
CoreUserSession,
|
|
7
|
+
} from '../../types/core.types.js'
|
|
8
|
+
import type {
|
|
9
|
+
CoreCLI,
|
|
10
|
+
CLICommandMeta,
|
|
11
|
+
CLIOption,
|
|
12
|
+
CLIProgramState,
|
|
13
|
+
CorePikkuCLIRender,
|
|
14
|
+
CoreCLICommandConfig,
|
|
15
|
+
CLIMeta,
|
|
16
|
+
} from './cli.types.js'
|
|
17
|
+
import type {
|
|
18
|
+
CoreSingletonServices,
|
|
19
|
+
CoreServices,
|
|
20
|
+
CreateWireServices,
|
|
21
|
+
PikkuWire,
|
|
22
|
+
CreateConfig,
|
|
23
|
+
CreateSingletonServices,
|
|
24
|
+
} from '../../types/core.types.js'
|
|
25
|
+
import type { PikkuChannel } from '../channel/channel.types.js'
|
|
26
|
+
import {
|
|
27
|
+
PikkuSessionService,
|
|
28
|
+
createMiddlewareSessionWireProps,
|
|
29
|
+
} from '../../services/user-session-service.js'
|
|
30
|
+
import { LocalVariablesService } from '../../services/local-variables.js'
|
|
31
|
+
import { generateCommandHelp, parseCLIArguments } from './command-parser.js'
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* CLI command execution error - thrown when CLI execution fails
|
|
35
|
+
* Should be caught by the wrapper to call process.exit()
|
|
36
|
+
*/
|
|
37
|
+
export class CLIError extends Error {
|
|
38
|
+
constructor(
|
|
39
|
+
message: string,
|
|
40
|
+
public exitCode: number = 1
|
|
41
|
+
) {
|
|
42
|
+
super(message)
|
|
43
|
+
this.name = 'CLIError'
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Default JSON renderer for CLI output
|
|
49
|
+
*/
|
|
50
|
+
const defaultJSONRenderer: CorePikkuCLIRender<any> = (_services, data) => {
|
|
51
|
+
console.log(JSON.stringify(data, null, 2))
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Registers a CLI command tree and all its functions
|
|
56
|
+
*/
|
|
57
|
+
export const wireCLI = <
|
|
58
|
+
Commands extends Record<string, CoreCLICommandConfig<any, any, any>>,
|
|
59
|
+
GlobalOptions,
|
|
60
|
+
PikkuMiddleware extends CorePikkuMiddleware,
|
|
61
|
+
GlobalOutput,
|
|
62
|
+
>(
|
|
63
|
+
cli: CoreCLI<Commands, GlobalOptions, PikkuMiddleware, GlobalOutput>
|
|
64
|
+
) => {
|
|
65
|
+
// Get the existing metadata that was generated during inspection
|
|
66
|
+
const cliMeta = pikkuState(null, 'cli', 'meta') || {}
|
|
67
|
+
|
|
68
|
+
if (!cliMeta.programs?.[cli.program]) {
|
|
69
|
+
throw new PikkuMissingMetaError(
|
|
70
|
+
`CLI metadata not found for program '${cli.program}'`
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Get existing programs state and add this program
|
|
75
|
+
const programs: Record<string, CLIProgramState> =
|
|
76
|
+
pikkuState(null, 'cli', 'programs') || {}
|
|
77
|
+
programs[cli.program] = {
|
|
78
|
+
defaultRenderer: (cli.render ||
|
|
79
|
+
defaultJSONRenderer) as CorePikkuCLIRender<any>,
|
|
80
|
+
middleware: cli.middleware || [],
|
|
81
|
+
renderers: {},
|
|
82
|
+
tags: cli.tags,
|
|
83
|
+
}
|
|
84
|
+
pikkuState(null, 'cli', 'programs', programs)
|
|
85
|
+
|
|
86
|
+
// Register all command functions recursively
|
|
87
|
+
registerCLICommands(
|
|
88
|
+
cli.commands as Record<string, any>,
|
|
89
|
+
[],
|
|
90
|
+
cli.options || {},
|
|
91
|
+
cli.program
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Unwraps a function from pikku wrappers (pikkuFunc, pikkuSessionlessFunc, etc.)
|
|
97
|
+
* These wrappers return { func, middleware, ... } but we need the actual function
|
|
98
|
+
*/
|
|
99
|
+
function unwrapFunc(command: any): {
|
|
100
|
+
func: Function
|
|
101
|
+
middleware?: any[]
|
|
102
|
+
auth?: boolean
|
|
103
|
+
permissions?: any
|
|
104
|
+
tags?: string[]
|
|
105
|
+
} {
|
|
106
|
+
if (typeof command === 'function') {
|
|
107
|
+
return { func: command }
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// If command has a func property that's an object with func, unwrap it
|
|
111
|
+
if (
|
|
112
|
+
command.func &&
|
|
113
|
+
typeof command.func === 'object' &&
|
|
114
|
+
'func' in command.func
|
|
115
|
+
) {
|
|
116
|
+
return {
|
|
117
|
+
func: command.func.func,
|
|
118
|
+
middleware: command.func.middleware,
|
|
119
|
+
auth: command.func.auth,
|
|
120
|
+
permissions: command.func.permissions,
|
|
121
|
+
tags: command.func.tags,
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Otherwise return as-is
|
|
126
|
+
return command
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Registers CLI commands and their functions recursively
|
|
131
|
+
*/
|
|
132
|
+
function registerCLICommands(
|
|
133
|
+
commands: Record<string, any>,
|
|
134
|
+
path: string[] = [],
|
|
135
|
+
inheritedOptions: Record<string, CLIOption> = {},
|
|
136
|
+
program: string
|
|
137
|
+
) {
|
|
138
|
+
// Get the CLI metadata to find actual function names
|
|
139
|
+
const cliMeta = pikkuState(null, 'cli', 'meta').programs[program]
|
|
140
|
+
|
|
141
|
+
for (const [name, command] of Object.entries(commands)) {
|
|
142
|
+
const fullPath = [...path, name]
|
|
143
|
+
const commandId = fullPath.join('.')
|
|
144
|
+
|
|
145
|
+
// Navigate metadata to find the actual function name
|
|
146
|
+
let currentMeta: CLICommandMeta | undefined = cliMeta?.commands[fullPath[0]]
|
|
147
|
+
for (let i = 1; i < fullPath.length; i++) {
|
|
148
|
+
currentMeta = currentMeta?.subcommands?.[fullPath[i]]
|
|
149
|
+
}
|
|
150
|
+
const funcName = currentMeta?.pikkuFuncId
|
|
151
|
+
|
|
152
|
+
// Skip if no function name (could be a command group)
|
|
153
|
+
if (!funcName) {
|
|
154
|
+
// Recursively register subcommands if they exist
|
|
155
|
+
if (typeof command === 'object' && command.subcommands) {
|
|
156
|
+
const commandOptions = command.options || {}
|
|
157
|
+
const mergedOptions = { ...inheritedOptions, ...commandOptions }
|
|
158
|
+
registerCLICommands(
|
|
159
|
+
command.subcommands,
|
|
160
|
+
fullPath,
|
|
161
|
+
mergedOptions,
|
|
162
|
+
program
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
continue
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Merge options (inherited + local)
|
|
169
|
+
const commandOptions =
|
|
170
|
+
typeof command === 'object' ? command.options || {} : {}
|
|
171
|
+
const mergedOptions = { ...inheritedOptions, ...commandOptions }
|
|
172
|
+
|
|
173
|
+
// Store the options and middleware in program state for use during execution
|
|
174
|
+
const programs: Record<string, CLIProgramState> = pikkuState(
|
|
175
|
+
null,
|
|
176
|
+
'cli',
|
|
177
|
+
'programs'
|
|
178
|
+
)
|
|
179
|
+
if (programs[program]) {
|
|
180
|
+
if (!programs[program].commandOptions) {
|
|
181
|
+
programs[program].commandOptions = {}
|
|
182
|
+
}
|
|
183
|
+
programs[program].commandOptions![commandId] = mergedOptions
|
|
184
|
+
|
|
185
|
+
// Store command middleware from the wire config
|
|
186
|
+
if (typeof command === 'object' && command.middleware) {
|
|
187
|
+
if (!programs[program].commandMiddleware) {
|
|
188
|
+
programs[program].commandMiddleware = {}
|
|
189
|
+
}
|
|
190
|
+
programs[program].commandMiddleware![commandId] = command.middleware
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
addFunction(funcName, unwrapFunc(command))
|
|
195
|
+
|
|
196
|
+
// Register renderer if provided
|
|
197
|
+
if (typeof command === 'object' && command.render) {
|
|
198
|
+
if (programs[program]) {
|
|
199
|
+
programs[program].renderers[commandId] = command.render
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Recursively register subcommands
|
|
204
|
+
if (typeof command === 'object' && command.subcommands) {
|
|
205
|
+
registerCLICommands(command.subcommands, fullPath, mergedOptions, program)
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Plucks only the data that the function expects based on its schema
|
|
212
|
+
*/
|
|
213
|
+
function pluckCLIData(
|
|
214
|
+
mergedData: Record<string, any>,
|
|
215
|
+
funcName: string,
|
|
216
|
+
availableOptions: Record<string, CLIOption>
|
|
217
|
+
): Record<string, any> {
|
|
218
|
+
const funcMeta = pikkuState(null, 'function', 'meta')[funcName]
|
|
219
|
+
const schemaName = funcMeta?.inputSchemaName
|
|
220
|
+
const schema = schemaName
|
|
221
|
+
? pikkuState(funcMeta?.packageName ?? null, 'misc', 'schemas').get(
|
|
222
|
+
schemaName
|
|
223
|
+
)
|
|
224
|
+
: null
|
|
225
|
+
|
|
226
|
+
if (schema && schema.properties) {
|
|
227
|
+
// If we have a schema, only include fields that are in the schema
|
|
228
|
+
const result: Record<string, any> = {}
|
|
229
|
+
for (const key of Object.keys(schema.properties)) {
|
|
230
|
+
if (key in mergedData) {
|
|
231
|
+
let value = mergedData[key]
|
|
232
|
+
// CLI passes all values as strings — coerce to array when schema expects one
|
|
233
|
+
const propSchema = schema.properties[key]
|
|
234
|
+
if (propSchema?.type === 'array' && !Array.isArray(value)) {
|
|
235
|
+
value = typeof value === 'string' ? value.split(',') : [value]
|
|
236
|
+
}
|
|
237
|
+
result[key] = value
|
|
238
|
+
} else if (availableOptions[key]?.default !== undefined) {
|
|
239
|
+
// Apply default if not provided
|
|
240
|
+
result[key] = availableOptions[key].default
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return result
|
|
244
|
+
} else {
|
|
245
|
+
// No schema, include all data
|
|
246
|
+
return { ...mergedData }
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Executes a CLI command for a specific program
|
|
252
|
+
*/
|
|
253
|
+
export async function runCLICommand({
|
|
254
|
+
program,
|
|
255
|
+
commandPath,
|
|
256
|
+
data,
|
|
257
|
+
singletonServices,
|
|
258
|
+
createWireServices,
|
|
259
|
+
}: {
|
|
260
|
+
program: string
|
|
261
|
+
commandPath: string[]
|
|
262
|
+
data: Record<string, any>
|
|
263
|
+
singletonServices: CoreSingletonServices
|
|
264
|
+
createWireServices?: CreateWireServices
|
|
265
|
+
}): Promise<any> {
|
|
266
|
+
// Get the command metadata to find the function name
|
|
267
|
+
const cliMeta = pikkuState(null, 'cli', 'meta')
|
|
268
|
+
const programMeta = cliMeta.programs?.[program]
|
|
269
|
+
if (!programMeta) {
|
|
270
|
+
throw new NotFoundError(`Program not found: ${program}`)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Navigate command tree to find the function name
|
|
274
|
+
let currentCommand = programMeta.commands[commandPath[0]]
|
|
275
|
+
if (!currentCommand) {
|
|
276
|
+
throw new NotFoundError(`Command not found: ${commandPath.join(' ')}`)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
for (let i = 1; i < commandPath.length; i++) {
|
|
280
|
+
if (
|
|
281
|
+
!currentCommand.subcommands ||
|
|
282
|
+
!currentCommand.subcommands[commandPath[i]]
|
|
283
|
+
) {
|
|
284
|
+
throw new NotFoundError(`Command not found: ${commandPath.join(' ')}`)
|
|
285
|
+
}
|
|
286
|
+
currentCommand = currentCommand.subcommands[commandPath[i]]
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const funcName = currentCommand.pikkuFuncId
|
|
290
|
+
|
|
291
|
+
// Get program-specific data
|
|
292
|
+
const programs: Record<string, CLIProgramState> =
|
|
293
|
+
pikkuState(null, 'cli', 'programs') || {}
|
|
294
|
+
const programData = programs[program]
|
|
295
|
+
|
|
296
|
+
// Combine program middleware + command middleware from the hierarchy
|
|
297
|
+
const allWireMiddleware: CorePikkuMiddleware[] = [
|
|
298
|
+
...(programData?.middleware || []),
|
|
299
|
+
]
|
|
300
|
+
|
|
301
|
+
// Walk through the command path and collect middleware from the runtime config
|
|
302
|
+
const commandParts: string[] = []
|
|
303
|
+
for (const part of commandPath) {
|
|
304
|
+
commandParts.push(part)
|
|
305
|
+
const commandId = commandParts.join('.')
|
|
306
|
+
const middleware = programData?.commandMiddleware?.[commandId]
|
|
307
|
+
if (middleware) {
|
|
308
|
+
allWireMiddleware.push(...middleware)
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Get command ID and options
|
|
313
|
+
const commandId = commandPath.join('.')
|
|
314
|
+
const availableOptions = programData?.commandOptions?.[commandId] || {}
|
|
315
|
+
|
|
316
|
+
// Pluck only the fields the function expects
|
|
317
|
+
const pluckedData = () => pluckCLIData(data, funcName, availableOptions)
|
|
318
|
+
|
|
319
|
+
// Get the renderer
|
|
320
|
+
const renderer =
|
|
321
|
+
programData?.renderers[commandId] || programData?.defaultRenderer
|
|
322
|
+
|
|
323
|
+
// Create a CLI channel for progressive output
|
|
324
|
+
const channel: PikkuChannel<unknown, unknown> = {
|
|
325
|
+
channelId: `cli:${program}:${commandId}`,
|
|
326
|
+
openingData: pluckedData,
|
|
327
|
+
send: async (data: any) => {
|
|
328
|
+
if (renderer) {
|
|
329
|
+
await Promise.resolve(renderer(singletonServices, data, undefined))
|
|
330
|
+
}
|
|
331
|
+
},
|
|
332
|
+
sendBinary: () => {
|
|
333
|
+
throw new Error('Binary data is not supported on CLI channels')
|
|
334
|
+
},
|
|
335
|
+
close: () => {
|
|
336
|
+
if (channel) {
|
|
337
|
+
channel.state = 'closed'
|
|
338
|
+
}
|
|
339
|
+
},
|
|
340
|
+
state: 'open',
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const userSession = new PikkuSessionService<CoreUserSession>()
|
|
344
|
+
|
|
345
|
+
const wire: PikkuWire = {
|
|
346
|
+
cli: {
|
|
347
|
+
program,
|
|
348
|
+
command: commandPath,
|
|
349
|
+
data: pluckedData,
|
|
350
|
+
channel,
|
|
351
|
+
},
|
|
352
|
+
...createMiddlewareSessionWireProps(userSession),
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
try {
|
|
356
|
+
const result = await runPikkuFunc('cli', commandId, funcName, {
|
|
357
|
+
singletonServices,
|
|
358
|
+
createWireServices,
|
|
359
|
+
data: pluckedData,
|
|
360
|
+
auth: false,
|
|
361
|
+
inheritedMiddleware: currentCommand.middleware,
|
|
362
|
+
wireMiddleware: allWireMiddleware,
|
|
363
|
+
inheritedPermissions: currentCommand.permissions,
|
|
364
|
+
wirePermissions: undefined,
|
|
365
|
+
coerceDataFromSchema: true,
|
|
366
|
+
tags: programData?.tags,
|
|
367
|
+
wire,
|
|
368
|
+
sessionService: userSession,
|
|
369
|
+
packageName: currentCommand.packageName,
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
// Apply renderer one final time with the final output (if renderer exists)
|
|
373
|
+
// Skip if result is undefined (void functions handle their own output)
|
|
374
|
+
if (renderer && result !== undefined) {
|
|
375
|
+
await Promise.resolve(
|
|
376
|
+
renderer(
|
|
377
|
+
singletonServices,
|
|
378
|
+
result,
|
|
379
|
+
userSession.get() as CoreUserSession | undefined
|
|
380
|
+
)
|
|
381
|
+
)
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return result
|
|
385
|
+
} finally {
|
|
386
|
+
// Close the channel
|
|
387
|
+
channel.close()
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Factory function for CLI-specific renderers
|
|
393
|
+
*/
|
|
394
|
+
export const pikkuCLIRender = <
|
|
395
|
+
Data,
|
|
396
|
+
Services extends CoreSingletonServices = CoreServices,
|
|
397
|
+
Session extends CoreUserSession = CoreUserSession,
|
|
398
|
+
>(
|
|
399
|
+
renderer: (
|
|
400
|
+
services: Services,
|
|
401
|
+
data: Data,
|
|
402
|
+
session?: Session
|
|
403
|
+
) => void | Promise<void>
|
|
404
|
+
): CorePikkuCLIRender<Data, Services, Session> => {
|
|
405
|
+
return renderer
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Execute a CLI program with the given arguments
|
|
410
|
+
* This is the main entry point for CLI programs
|
|
411
|
+
*
|
|
412
|
+
* @throws {CLIError} When CLI execution fails - should be caught by wrapper to call process.exit()
|
|
413
|
+
*/
|
|
414
|
+
export async function executeCLI({
|
|
415
|
+
programName,
|
|
416
|
+
args,
|
|
417
|
+
createConfig,
|
|
418
|
+
createSingletonServices,
|
|
419
|
+
createWireServices,
|
|
420
|
+
}: {
|
|
421
|
+
programName: string
|
|
422
|
+
args?: string[]
|
|
423
|
+
createConfig?: CreateConfig<any, any>
|
|
424
|
+
createSingletonServices: CreateSingletonServices<any, any>
|
|
425
|
+
createWireServices?: CreateWireServices<any, any, any>
|
|
426
|
+
}): Promise<void> {
|
|
427
|
+
if (!args) {
|
|
428
|
+
throw new Error(
|
|
429
|
+
'CLI arguments are required, this is to satisfy release diffs'
|
|
430
|
+
)
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
try {
|
|
434
|
+
// Get CLI metadata from state
|
|
435
|
+
const allCLIMeta = pikkuState(null, 'cli', 'meta') as unknown as
|
|
436
|
+
| CLIMeta
|
|
437
|
+
| undefined
|
|
438
|
+
if (!allCLIMeta) {
|
|
439
|
+
throw new Error(
|
|
440
|
+
'[PKU342] CLI metadata not found. No CLI wirings were registered. See https://pikku.dev/docs/pikku-cli/errors/pku342 for more information.'
|
|
441
|
+
)
|
|
442
|
+
}
|
|
443
|
+
const programMeta = allCLIMeta.programs[programName]
|
|
444
|
+
|
|
445
|
+
if (!programMeta) {
|
|
446
|
+
throw new CLIError(`CLI program "${programName}" not found`, 1)
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Parse arguments for this specific program
|
|
450
|
+
const parsed = parseCLIArguments(args, programName, allCLIMeta)
|
|
451
|
+
|
|
452
|
+
// Handle help (check after parsing to support subcommand help)
|
|
453
|
+
// Show help if --help/-h is present, or if no args AND no default command
|
|
454
|
+
const shouldShowHelp =
|
|
455
|
+
args.includes('--help') ||
|
|
456
|
+
args.includes('-h') ||
|
|
457
|
+
(args.length === 0 && parsed.commandPath.length === 0)
|
|
458
|
+
|
|
459
|
+
if (shouldShowHelp) {
|
|
460
|
+
const helpText = generateCommandHelp(
|
|
461
|
+
programName,
|
|
462
|
+
allCLIMeta,
|
|
463
|
+
parsed.commandPath
|
|
464
|
+
)
|
|
465
|
+
console.log(helpText)
|
|
466
|
+
return
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
if (parsed.errors.length > 0) {
|
|
470
|
+
// Check if any error is about an unknown command
|
|
471
|
+
const hasUnknownCommand = parsed.errors.some(
|
|
472
|
+
(error) =>
|
|
473
|
+
error.startsWith('Unknown command:') ||
|
|
474
|
+
error.startsWith('Command not found:')
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
if (hasUnknownCommand) {
|
|
478
|
+
// Show help instead of error for unknown commands
|
|
479
|
+
const helpText = generateCommandHelp(
|
|
480
|
+
programName,
|
|
481
|
+
allCLIMeta,
|
|
482
|
+
parsed.commandPath
|
|
483
|
+
)
|
|
484
|
+
console.log(helpText)
|
|
485
|
+
throw new CLIError('Unknown command', 1)
|
|
486
|
+
} else {
|
|
487
|
+
// Show errors for other types of errors
|
|
488
|
+
console.error('Errors:')
|
|
489
|
+
parsed.errors.forEach((error) => console.error(` ${error}`))
|
|
490
|
+
throw new CLIError(parsed.errors.join('\n'), 1)
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Merge positionals and options into single data object
|
|
495
|
+
const data = { ...parsed.positionals, ...parsed.options }
|
|
496
|
+
|
|
497
|
+
// Create config (pass data in case it needs to use any parsed options)
|
|
498
|
+
const config = createConfig
|
|
499
|
+
? await createConfig(new LocalVariablesService(), data)
|
|
500
|
+
: ({} as any)
|
|
501
|
+
|
|
502
|
+
// Create services with config
|
|
503
|
+
const singletonServices = await createSingletonServices(config)
|
|
504
|
+
|
|
505
|
+
// Execute the command
|
|
506
|
+
await runCLICommand({
|
|
507
|
+
program: programName,
|
|
508
|
+
commandPath: parsed.commandPath,
|
|
509
|
+
data,
|
|
510
|
+
singletonServices,
|
|
511
|
+
createWireServices,
|
|
512
|
+
})
|
|
513
|
+
} catch (error: any) {
|
|
514
|
+
// Re-throw CLIError as-is
|
|
515
|
+
if (error instanceof CLIError) {
|
|
516
|
+
throw error
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// Wrap other errors in CLIError
|
|
520
|
+
console.error('Error:', error)
|
|
521
|
+
|
|
522
|
+
// Show stack trace in verbose mode
|
|
523
|
+
if (args.includes('--verbose') || args.includes('-v')) {
|
|
524
|
+
console.error('Stack trace:', error.stack)
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
throw new CLIError(error.message || String(error), 1)
|
|
528
|
+
}
|
|
529
|
+
}
|