@pikku/core 0.9.11 → 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.
- package/CHANGELOG.md +14 -0
- package/dist/errors/errors.d.ts +2 -0
- package/dist/errors/errors.js +6 -0
- package/dist/function/function-runner.d.ts +15 -9
- package/dist/function/function-runner.js +55 -37
- package/dist/function/functions.types.d.ts +67 -1
- package/dist/function/functions.types.js +43 -1
- package/dist/index.d.ts +2 -6
- package/dist/index.js +2 -6
- package/dist/middleware/auth-apikey.d.ts +19 -11
- package/dist/middleware/auth-apikey.js +33 -33
- package/dist/middleware/auth-bearer.d.ts +34 -7
- package/dist/middleware/auth-bearer.js +53 -36
- package/dist/middleware/auth-cookie.d.ts +30 -12
- package/dist/middleware/auth-cookie.js +51 -43
- package/dist/middleware/timeout.d.ts +6 -2
- package/dist/middleware/timeout.js +7 -9
- package/dist/middleware-runner.d.ts +46 -43
- package/dist/middleware-runner.js +100 -69
- package/dist/permissions.d.ts +82 -20
- package/dist/permissions.js +176 -62
- package/dist/pikku-state.d.ts +46 -1
- package/dist/pikku-state.js +16 -1
- package/dist/services/content-service.d.ts +2 -3
- package/dist/services/index.d.ts +0 -1
- package/dist/services/index.js +0 -1
- package/dist/services/local-content.d.ts +2 -4
- package/dist/types/core.types.d.ts +110 -4
- package/dist/types/core.types.js +44 -1
- package/dist/utils.d.ts +1 -1
- package/dist/wirings/channel/channel-handler.d.ts +2 -2
- package/dist/wirings/channel/channel-handler.js +16 -12
- package/dist/wirings/channel/channel-runner.d.ts +2 -2
- package/dist/wirings/channel/channel-runner.js +6 -24
- package/dist/wirings/channel/channel.types.d.ts +15 -14
- package/dist/wirings/channel/local/local-channel-runner.js +12 -12
- package/dist/wirings/channel/serverless/serverless-channel-runner.js +7 -8
- package/dist/wirings/cli/channel/cli-channel-runner.d.ts +12 -0
- package/dist/wirings/cli/channel/cli-channel-runner.js +83 -0
- package/dist/wirings/cli/channel/index.d.ts +1 -0
- package/dist/wirings/cli/channel/index.js +1 -0
- package/dist/wirings/cli/cli-runner.d.ts +42 -0
- package/dist/wirings/cli/cli-runner.js +352 -0
- package/dist/wirings/cli/cli.types.d.ts +204 -0
- package/dist/wirings/cli/cli.types.js +1 -0
- package/dist/wirings/cli/command-parser.d.ts +19 -0
- package/dist/wirings/cli/command-parser.js +403 -0
- package/dist/wirings/cli/index.d.ts +5 -0
- package/dist/wirings/cli/index.js +5 -0
- package/dist/wirings/http/http-runner.d.ts +59 -10
- package/dist/wirings/http/http-runner.js +138 -131
- package/dist/wirings/http/http.types.d.ts +11 -10
- package/dist/wirings/http/index.d.ts +1 -1
- package/dist/wirings/http/index.js +1 -1
- package/dist/wirings/http/pikku-fetch-http-response.js +3 -1
- package/dist/wirings/http/routers/http-router.d.ts +0 -2
- package/dist/wirings/http/routers/path-to-regex.d.ts +1 -1
- package/dist/wirings/http/routers/path-to-regex.js +5 -34
- package/dist/wirings/mcp/mcp-endpoint-registry.d.ts +1 -1
- package/dist/wirings/mcp/mcp-runner.d.ts +4 -5
- package/dist/wirings/mcp/mcp-runner.js +32 -34
- package/dist/wirings/mcp/mcp.types.d.ts +38 -14
- package/dist/wirings/queue/queue-runner.d.ts +2 -2
- package/dist/wirings/queue/queue-runner.js +25 -27
- package/dist/wirings/queue/queue.types.d.ts +6 -5
- package/dist/wirings/rpc/index.d.ts +1 -1
- package/dist/wirings/rpc/index.js +1 -1
- package/dist/wirings/rpc/rpc-runner.d.ts +6 -18
- package/dist/wirings/rpc/rpc-runner.js +13 -8
- package/dist/wirings/scheduler/scheduler-runner.d.ts +2 -2
- package/dist/wirings/scheduler/scheduler-runner.js +38 -35
- package/dist/wirings/scheduler/scheduler.types.d.ts +5 -4
- package/package.json +9 -7
- package/src/errors/errors.ts +6 -0
- package/src/factory-functions.test.ts +60 -1
- package/src/function/function-runner.test.ts +81 -26
- package/src/function/function-runner.ts +89 -57
- package/src/function/functions.types.ts +85 -2
- package/src/index.ts +6 -19
- package/src/middleware/auth-apikey.ts +42 -57
- package/src/middleware/auth-bearer.ts +66 -49
- package/src/middleware/auth-cookie.ts +63 -82
- package/src/middleware/timeout.ts +10 -14
- package/src/middleware-runner.test.ts +24 -16
- package/src/middleware-runner.ts +115 -87
- package/src/permissions.test.ts +26 -27
- package/src/permissions.ts +216 -104
- package/src/pikku-state.ts +79 -2
- package/src/services/content-service.ts +5 -4
- package/src/services/index.ts +0 -1
- package/src/services/local-content.ts +9 -4
- package/src/types/core.types.ts +143 -5
- package/src/utils.ts +1 -1
- package/src/wirings/channel/channel-handler.ts +18 -21
- package/src/wirings/channel/channel-runner.ts +19 -32
- package/src/wirings/channel/channel.types.ts +29 -16
- package/src/wirings/channel/local/local-channel-runner.ts +25 -15
- package/src/wirings/channel/serverless/serverless-channel-runner.ts +15 -17
- package/src/wirings/cli/channel/cli-channel-runner.ts +123 -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 +539 -0
- package/src/wirings/cli/cli.types.ts +350 -0
- package/src/wirings/cli/command-parser.test.ts +445 -0
- package/src/wirings/cli/command-parser.ts +504 -0
- package/src/wirings/cli/index.ts +13 -0
- package/src/wirings/http/http-runner.test.ts +8 -7
- package/src/wirings/http/http-runner.ts +165 -158
- package/src/wirings/http/http.types.ts +58 -11
- package/src/wirings/http/index.ts +7 -1
- package/src/wirings/http/pikku-fetch-http-response.ts +3 -1
- package/src/wirings/http/routers/http-router.ts +0 -2
- package/src/wirings/http/routers/path-to-regex.test.ts +4 -5
- package/src/wirings/http/routers/path-to-regex.ts +6 -43
- package/src/wirings/mcp/mcp-endpoint-registry.ts +1 -1
- package/src/wirings/mcp/mcp-runner.ts +58 -55
- package/src/wirings/mcp/mcp.types.ts +65 -14
- package/src/wirings/queue/queue-runner.ts +44 -47
- package/src/wirings/queue/queue.types.ts +10 -6
- package/src/wirings/rpc/index.ts +1 -1
- package/src/wirings/rpc/rpc-runner.ts +27 -9
- package/src/wirings/scheduler/scheduler-runner.ts +58 -56
- package/src/wirings/scheduler/scheduler.types.ts +10 -3
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -85,16 +85,15 @@ export const runChannelConnect = async ({
|
|
|
85
85
|
|
|
86
86
|
const userSession = new PikkuUserSessionService(channelStore, channelId)
|
|
87
87
|
|
|
88
|
-
const { channelConfig, openingData, meta
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
})
|
|
88
|
+
const { channelConfig, openingData, meta } = await openChannel({
|
|
89
|
+
channelId,
|
|
90
|
+
createSessionServices,
|
|
91
|
+
request,
|
|
92
|
+
route,
|
|
93
|
+
singletonServices,
|
|
94
|
+
coerceDataFromSchema,
|
|
95
|
+
userSession,
|
|
96
|
+
})
|
|
98
97
|
|
|
99
98
|
const main = async () => {
|
|
100
99
|
try {
|
|
@@ -148,9 +147,8 @@ export const runChannelConnect = async ({
|
|
|
148
147
|
},
|
|
149
148
|
{ http },
|
|
150
149
|
combineMiddleware(PikkuWiringTypes.channel, channelConfig.name, {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
httpMiddleware,
|
|
150
|
+
wireInheritedMiddleware: meta.middleware,
|
|
151
|
+
wireMiddleware: channelConfig.middleware,
|
|
154
152
|
}),
|
|
155
153
|
main
|
|
156
154
|
)
|
|
@@ -171,7 +169,7 @@ export const runChannelDisconnect = async ({
|
|
|
171
169
|
if (!sessionServices && params.createSessionServices) {
|
|
172
170
|
sessionServices = await params.createSessionServices(
|
|
173
171
|
singletonServices,
|
|
174
|
-
{},
|
|
172
|
+
{ channel },
|
|
175
173
|
session
|
|
176
174
|
)
|
|
177
175
|
}
|
|
@@ -195,7 +193,8 @@ export const runChannelMessage = async (
|
|
|
195
193
|
let sessionServices: SessionServices | undefined
|
|
196
194
|
const { openingData, channelName, session } =
|
|
197
195
|
await params.channelStore.getChannelAndSession(params.channelId)
|
|
198
|
-
|
|
196
|
+
|
|
197
|
+
const { channel, channelHandler, channelConfig } = getVariablesForChannel({
|
|
199
198
|
...params,
|
|
200
199
|
openingData,
|
|
201
200
|
channelName,
|
|
@@ -203,7 +202,7 @@ export const runChannelMessage = async (
|
|
|
203
202
|
if (params.createSessionServices) {
|
|
204
203
|
sessionServices = await params.createSessionServices(
|
|
205
204
|
singletonServices,
|
|
206
|
-
{},
|
|
205
|
+
{ channel },
|
|
207
206
|
session
|
|
208
207
|
)
|
|
209
208
|
}
|
|
@@ -211,7 +210,6 @@ export const runChannelMessage = async (
|
|
|
211
210
|
try {
|
|
212
211
|
const onMessage = processMessageHandlers(
|
|
213
212
|
{ ...singletonServices, ...sessionServices },
|
|
214
|
-
session,
|
|
215
213
|
channelConfig,
|
|
216
214
|
channelHandler
|
|
217
215
|
)
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { pikkuState } from '../../../pikku-state.js'
|
|
2
|
+
import { CorePikkuCLIRender, CLIMeta } from '../cli.types.js'
|
|
3
|
+
import { generateCommandHelp, parseCLIArguments } from '../command-parser.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Default JSON renderer for CLI output
|
|
7
|
+
*/
|
|
8
|
+
const defaultJSONRenderer: CorePikkuCLIRender<any> = (_services, data) => {
|
|
9
|
+
console.log(JSON.stringify(data, null, 2))
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Execute a CLI program via WebSocket channel
|
|
14
|
+
* This is the main entry point for CLI-over-Channel clients
|
|
15
|
+
*/
|
|
16
|
+
export async function executeCLIViaChannel({
|
|
17
|
+
programName,
|
|
18
|
+
pikkuWS,
|
|
19
|
+
args = process.argv.slice(2),
|
|
20
|
+
renderers = {},
|
|
21
|
+
defaultRenderer,
|
|
22
|
+
}: {
|
|
23
|
+
programName: string
|
|
24
|
+
pikkuWS: any // CorePikkuWebsocket instance
|
|
25
|
+
args?: string[]
|
|
26
|
+
renderers?: Record<string, CorePikkuCLIRender<any>>
|
|
27
|
+
defaultRenderer?: CorePikkuCLIRender<any>
|
|
28
|
+
}): Promise<void> {
|
|
29
|
+
// Get CLI metadata from state
|
|
30
|
+
const allCLIMeta = pikkuState('cli', 'meta') as unknown as CLIMeta | undefined
|
|
31
|
+
if (!allCLIMeta) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
'[PKU342] CLI metadata not found. No CLI wirings were registered. See https://pikku.dev/errors/pku342 for more information.'
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
const programMeta = allCLIMeta.programs[programName]
|
|
37
|
+
|
|
38
|
+
if (!programMeta) {
|
|
39
|
+
console.error(`Error: CLI program "${programName}" not found`)
|
|
40
|
+
process.exit(1)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Parse arguments for this specific program
|
|
44
|
+
const parsed = parseCLIArguments(args, programName, allCLIMeta)
|
|
45
|
+
|
|
46
|
+
// Handle help (check after parsing to support subcommand help)
|
|
47
|
+
if (args.includes('--help') || args.includes('-h') || args.length === 0) {
|
|
48
|
+
const helpText = generateCommandHelp(
|
|
49
|
+
programName,
|
|
50
|
+
allCLIMeta,
|
|
51
|
+
parsed.commandPath
|
|
52
|
+
)
|
|
53
|
+
console.log(helpText)
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (parsed.errors.length > 0) {
|
|
58
|
+
// Check if any error is about an unknown command
|
|
59
|
+
const hasUnknownCommand = parsed.errors.some(
|
|
60
|
+
(error) =>
|
|
61
|
+
error.startsWith('Unknown command:') ||
|
|
62
|
+
error.startsWith('Command not found:')
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
if (hasUnknownCommand) {
|
|
66
|
+
// Show help instead of error for unknown commands
|
|
67
|
+
const helpText = generateCommandHelp(
|
|
68
|
+
programName,
|
|
69
|
+
allCLIMeta,
|
|
70
|
+
parsed.commandPath
|
|
71
|
+
)
|
|
72
|
+
console.log(helpText)
|
|
73
|
+
process.exit(1)
|
|
74
|
+
} else {
|
|
75
|
+
// Show errors for other types of errors
|
|
76
|
+
console.error('Errors:')
|
|
77
|
+
parsed.errors.forEach((error) => console.error(` ${error}`))
|
|
78
|
+
process.exit(1)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Merge positionals and options into single data object
|
|
83
|
+
const data = { ...parsed.positionals, ...parsed.options }
|
|
84
|
+
|
|
85
|
+
// Get the renderer for this command
|
|
86
|
+
const commandId = parsed.commandPath.join('.')
|
|
87
|
+
const renderer =
|
|
88
|
+
renderers[commandId] || defaultRenderer || defaultJSONRenderer
|
|
89
|
+
|
|
90
|
+
return new Promise((resolve, reject) => {
|
|
91
|
+
// Subscribe to responses for this command
|
|
92
|
+
const commandRoute = pikkuWS.getRoute('command')
|
|
93
|
+
|
|
94
|
+
const responseHandler = (response: any) => {
|
|
95
|
+
// Call renderer for any output
|
|
96
|
+
renderer(null as any, response, undefined)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
commandRoute.subscribe(commandId, responseHandler)
|
|
100
|
+
|
|
101
|
+
// Handle connection close
|
|
102
|
+
pikkuWS.ws.addEventListener('close', () => {
|
|
103
|
+
commandRoute.unsubscribe(commandId, responseHandler)
|
|
104
|
+
resolve(undefined)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
pikkuWS.ws.addEventListener('error', (error: any) => {
|
|
108
|
+
commandRoute.unsubscribe(commandId, responseHandler)
|
|
109
|
+
reject(error)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
// Send the command once connected
|
|
113
|
+
if (pikkuWS.ws.readyState === 1) {
|
|
114
|
+
// Already open
|
|
115
|
+
commandRoute.send(commandId, data)
|
|
116
|
+
} else {
|
|
117
|
+
// Wait for open
|
|
118
|
+
pikkuWS.ws.addEventListener('open', () => {
|
|
119
|
+
commandRoute.send(commandId, data)
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
})
|
|
123
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './cli-channel-runner.js'
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
import { test, describe, beforeEach, afterEach } from 'node:test'
|
|
2
|
+
import * as assert from 'assert'
|
|
3
|
+
import { NotFoundError } from '../../errors/errors.js'
|
|
4
|
+
import { CorePikkuMiddleware } from '../../types/core.types.js'
|
|
5
|
+
import { wireCLI, runCLICommand, pikkuCLIRender } from './cli-runner.js'
|
|
6
|
+
import { pikkuState, resetPikkuState } from '../../pikku-state.js'
|
|
7
|
+
import { addFunction } from '../../function/function-runner.js'
|
|
8
|
+
|
|
9
|
+
describe('CLI Runner', () => {
|
|
10
|
+
let singletonServices: any
|
|
11
|
+
let createSessionServices: any
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
resetPikkuState()
|
|
15
|
+
|
|
16
|
+
singletonServices = {
|
|
17
|
+
logger: {
|
|
18
|
+
info: () => {},
|
|
19
|
+
warn: () => {},
|
|
20
|
+
error: () => {},
|
|
21
|
+
},
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
createSessionServices = async () => ({})
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
resetPikkuState()
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
describe('runCLICommand', () => {
|
|
32
|
+
test('should throw NotFoundError when program not found', async () => {
|
|
33
|
+
pikkuState('cli', 'meta', { programs: {}, renderers: {} })
|
|
34
|
+
|
|
35
|
+
await assert.rejects(
|
|
36
|
+
async () =>
|
|
37
|
+
runCLICommand({
|
|
38
|
+
program: 'nonexistent',
|
|
39
|
+
commandPath: ['test'],
|
|
40
|
+
data: {},
|
|
41
|
+
singletonServices,
|
|
42
|
+
}),
|
|
43
|
+
NotFoundError
|
|
44
|
+
)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test('should throw NotFoundError when command not found', async () => {
|
|
48
|
+
pikkuState('cli', 'meta', {
|
|
49
|
+
programs: {
|
|
50
|
+
'test-cli': {
|
|
51
|
+
program: 'test-cli',
|
|
52
|
+
commands: {},
|
|
53
|
+
options: {},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
renderers: {},
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
pikkuState('cli', 'programs', {
|
|
60
|
+
'test-cli': {
|
|
61
|
+
defaultRenderer: undefined,
|
|
62
|
+
middleware: [],
|
|
63
|
+
renderers: {},
|
|
64
|
+
},
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
await assert.rejects(
|
|
68
|
+
async () =>
|
|
69
|
+
runCLICommand({
|
|
70
|
+
program: 'test-cli',
|
|
71
|
+
commandPath: ['nonexistent'],
|
|
72
|
+
data: {},
|
|
73
|
+
singletonServices,
|
|
74
|
+
}),
|
|
75
|
+
NotFoundError
|
|
76
|
+
)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
test('should execute command function with merged data', async () => {
|
|
80
|
+
let receivedData: any
|
|
81
|
+
const testFunc = async (_services: any, data: any) => {
|
|
82
|
+
receivedData = data
|
|
83
|
+
return { success: true }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Setup metadata
|
|
87
|
+
pikkuState('cli', 'meta', {
|
|
88
|
+
programs: {
|
|
89
|
+
'test-cli': {
|
|
90
|
+
program: 'test-cli',
|
|
91
|
+
commands: {
|
|
92
|
+
greet: {
|
|
93
|
+
command: 'greet <name>',
|
|
94
|
+
pikkuFuncName: 'greetFunc',
|
|
95
|
+
positionals: [{ name: 'name', required: true }],
|
|
96
|
+
options: {},
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
options: {},
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
renderers: {},
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
pikkuState('cli', 'programs', {
|
|
106
|
+
'test-cli': {
|
|
107
|
+
defaultRenderer: undefined,
|
|
108
|
+
middleware: [],
|
|
109
|
+
renderers: {},
|
|
110
|
+
},
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
pikkuState('function', 'meta', {
|
|
114
|
+
greetFunc: {
|
|
115
|
+
pikkuFuncName: 'greetFunc',
|
|
116
|
+
inputSchemaName: null,
|
|
117
|
+
outputSchemaName: null,
|
|
118
|
+
},
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
addFunction('greetFunc', { func: testFunc, auth: false })
|
|
122
|
+
|
|
123
|
+
const result = await runCLICommand({
|
|
124
|
+
program: 'test-cli',
|
|
125
|
+
commandPath: ['greet'],
|
|
126
|
+
data: { name: 'Alice', loud: true },
|
|
127
|
+
singletonServices,
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
assert.strictEqual(result.success, true)
|
|
131
|
+
assert.deepStrictEqual(receivedData, { name: 'Alice', loud: true })
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
test('should run middleware before function execution', async () => {
|
|
135
|
+
const executionOrder: string[] = []
|
|
136
|
+
|
|
137
|
+
const testMiddleware: CorePikkuMiddleware = async (
|
|
138
|
+
_services,
|
|
139
|
+
_interaction,
|
|
140
|
+
next
|
|
141
|
+
) => {
|
|
142
|
+
executionOrder.push('middleware')
|
|
143
|
+
await next()
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const testFunc = async () => {
|
|
147
|
+
executionOrder.push('function')
|
|
148
|
+
return { success: true }
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
pikkuState('cli', 'meta', {
|
|
152
|
+
programs: {
|
|
153
|
+
'test-cli': {
|
|
154
|
+
program: 'test-cli',
|
|
155
|
+
commands: {
|
|
156
|
+
test: {
|
|
157
|
+
command: 'test',
|
|
158
|
+
pikkuFuncName: 'testFunc',
|
|
159
|
+
positionals: [],
|
|
160
|
+
options: {},
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
options: {},
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
renderers: {},
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
pikkuState('cli', 'programs', {
|
|
170
|
+
'test-cli': {
|
|
171
|
+
defaultRenderer: undefined,
|
|
172
|
+
middleware: [testMiddleware],
|
|
173
|
+
renderers: {},
|
|
174
|
+
},
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
pikkuState('function', 'meta', {
|
|
178
|
+
testFunc: {
|
|
179
|
+
pikkuFuncName: 'testFunc',
|
|
180
|
+
inputSchemaName: null,
|
|
181
|
+
outputSchemaName: null,
|
|
182
|
+
},
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
addFunction('testFunc', { func: testFunc, auth: false })
|
|
186
|
+
|
|
187
|
+
await runCLICommand({
|
|
188
|
+
program: 'test-cli',
|
|
189
|
+
commandPath: ['test'],
|
|
190
|
+
data: {},
|
|
191
|
+
singletonServices,
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
assert.deepStrictEqual(executionOrder, ['middleware', 'function'])
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
test.skip('should provide CLI context to middleware', async () => {
|
|
198
|
+
let cliContext: any = null
|
|
199
|
+
|
|
200
|
+
const testMiddleware: CorePikkuMiddleware = async (
|
|
201
|
+
_services,
|
|
202
|
+
interaction,
|
|
203
|
+
next
|
|
204
|
+
) => {
|
|
205
|
+
cliContext = interaction.cli
|
|
206
|
+
await next()
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const testFunc = async () => ({ success: true })
|
|
210
|
+
|
|
211
|
+
pikkuState('cli', 'meta', {
|
|
212
|
+
programs: {
|
|
213
|
+
'test-cli': {
|
|
214
|
+
program: 'test-cli',
|
|
215
|
+
commands: {
|
|
216
|
+
greet: {
|
|
217
|
+
command: 'greet <name>',
|
|
218
|
+
pikkuFuncName: 'greetFunc',
|
|
219
|
+
positionals: [{ name: 'name', required: true }],
|
|
220
|
+
options: {},
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
options: {},
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
renderers: {},
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
pikkuState('cli', 'programs', {
|
|
230
|
+
'test-cli': {
|
|
231
|
+
defaultRenderer: undefined,
|
|
232
|
+
middleware: [testMiddleware],
|
|
233
|
+
renderers: {},
|
|
234
|
+
},
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
pikkuState('function', 'meta', {
|
|
238
|
+
greetFunc: {
|
|
239
|
+
pikkuFuncName: 'greetFunc',
|
|
240
|
+
inputSchemaName: null,
|
|
241
|
+
outputSchemaName: null,
|
|
242
|
+
},
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
addFunction('greetFunc', { func: testFunc, auth: false })
|
|
246
|
+
|
|
247
|
+
await runCLICommand({
|
|
248
|
+
program: 'test-cli',
|
|
249
|
+
commandPath: ['greet'],
|
|
250
|
+
data: { name: 'Alice' },
|
|
251
|
+
singletonServices,
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
assert.ok(cliContext, 'CLI context should be set by middleware')
|
|
255
|
+
assert.strictEqual(cliContext.program, 'test-cli')
|
|
256
|
+
assert.deepStrictEqual(cliContext.command, ['greet'])
|
|
257
|
+
assert.deepStrictEqual(cliContext.data, { name: 'Alice' })
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
test('should throw error when auth required but no session', async () => {
|
|
261
|
+
const testFunc = async () => ({ success: true })
|
|
262
|
+
|
|
263
|
+
pikkuState('cli', 'meta', {
|
|
264
|
+
programs: {
|
|
265
|
+
'test-cli': {
|
|
266
|
+
program: 'test-cli',
|
|
267
|
+
commands: {
|
|
268
|
+
secure: {
|
|
269
|
+
command: 'secure',
|
|
270
|
+
pikkuFuncName: 'secureFunc',
|
|
271
|
+
positionals: [],
|
|
272
|
+
options: {},
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
options: {},
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
renderers: {},
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
pikkuState('function', 'meta', {
|
|
282
|
+
secureFunc: {
|
|
283
|
+
pikkuFuncName: 'secureFunc',
|
|
284
|
+
inputSchemaName: null,
|
|
285
|
+
outputSchemaName: null,
|
|
286
|
+
},
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
addFunction('secureFunc', { func: testFunc, auth: true })
|
|
290
|
+
|
|
291
|
+
await assert.rejects(
|
|
292
|
+
async () =>
|
|
293
|
+
runCLICommand({
|
|
294
|
+
program: 'test-cli',
|
|
295
|
+
commandPath: ['secure'],
|
|
296
|
+
data: {},
|
|
297
|
+
singletonServices,
|
|
298
|
+
createSessionServices,
|
|
299
|
+
}),
|
|
300
|
+
/Authentication required/
|
|
301
|
+
)
|
|
302
|
+
})
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
describe('wireCLI', () => {
|
|
306
|
+
test('should register CLI program and commands', () => {
|
|
307
|
+
const greetFunc = async () => 'Hello'
|
|
308
|
+
|
|
309
|
+
pikkuState('cli', 'meta', {
|
|
310
|
+
programs: {
|
|
311
|
+
'my-cli': {
|
|
312
|
+
program: 'my-cli',
|
|
313
|
+
commands: {
|
|
314
|
+
greet: {
|
|
315
|
+
command: 'greet <name>',
|
|
316
|
+
pikkuFuncName: 'greetFunc',
|
|
317
|
+
positionals: [{ name: 'name', required: true }],
|
|
318
|
+
options: {
|
|
319
|
+
loud: {
|
|
320
|
+
description: 'Use loud greeting',
|
|
321
|
+
short: 'l',
|
|
322
|
+
default: false,
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
options: {},
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
renderers: {},
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
wireCLI({
|
|
334
|
+
program: 'my-cli',
|
|
335
|
+
commands: {
|
|
336
|
+
greet: greetFunc,
|
|
337
|
+
},
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
const programs = pikkuState('cli', 'programs')
|
|
341
|
+
assert.ok(programs['my-cli'])
|
|
342
|
+
assert.strictEqual(programs['my-cli'].middleware.length, 0)
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
test('should register CLI with global middleware', () => {
|
|
346
|
+
const middleware: CorePikkuMiddleware = async (_s, _i, next) => {
|
|
347
|
+
await next()
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
pikkuState('cli', 'meta', {
|
|
351
|
+
programs: {
|
|
352
|
+
'my-cli': {
|
|
353
|
+
program: 'my-cli',
|
|
354
|
+
commands: {},
|
|
355
|
+
options: {},
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
renderers: {},
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
wireCLI({
|
|
362
|
+
program: 'my-cli',
|
|
363
|
+
commands: {},
|
|
364
|
+
middleware: [middleware],
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
const programs = pikkuState('cli', 'programs')
|
|
368
|
+
assert.strictEqual(programs['my-cli'].middleware.length, 1)
|
|
369
|
+
assert.strictEqual(programs['my-cli'].middleware[0], middleware)
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
test('should throw error when CLI metadata not found', () => {
|
|
373
|
+
pikkuState('cli', 'meta', { programs: {}, renderers: {} })
|
|
374
|
+
|
|
375
|
+
assert.throws(() => {
|
|
376
|
+
wireCLI({
|
|
377
|
+
program: 'nonexistent',
|
|
378
|
+
commands: {},
|
|
379
|
+
})
|
|
380
|
+
}, /CLI metadata not found for program 'nonexistent'/)
|
|
381
|
+
})
|
|
382
|
+
})
|
|
383
|
+
|
|
384
|
+
describe('pikkuCLIRender', () => {
|
|
385
|
+
test('should create CLI renderer function', () => {
|
|
386
|
+
const renderer = pikkuCLIRender((services, data) => {
|
|
387
|
+
console.log(data)
|
|
388
|
+
})
|
|
389
|
+
|
|
390
|
+
assert.strictEqual(typeof renderer, 'function')
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
test('should preserve renderer function behavior', async () => {
|
|
394
|
+
let receivedData: any
|
|
395
|
+
const renderer = pikkuCLIRender((services, data) => {
|
|
396
|
+
receivedData = data
|
|
397
|
+
})
|
|
398
|
+
|
|
399
|
+
await renderer({} as any, { test: 'value' })
|
|
400
|
+
assert.deepStrictEqual(receivedData, { test: 'value' })
|
|
401
|
+
})
|
|
402
|
+
})
|
|
403
|
+
})
|