jbs-client 0.0.1 → 0.0.3

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/src/app.tsx DELETED
@@ -1,239 +0,0 @@
1
- import { useEffect, useMemo, useState } from "react"
2
- import { useKeyboard, useRenderer, useTerminalDimensions } from "@opentui/react"
3
- import { ActionMenu } from "./components/action-menu.js"
4
- import { LogPanel } from "./components/log-panel.js"
5
- import { ParamForm } from "./components/param-form.js"
6
- import { buildPayload, menu, validateAction } from "./protocol.js"
7
- import { appendLog, backToMenu, createInitialState, nextFocus, openAction, previousFocus, setConnectionStatus, updateFormValue, updateSelectedAction } from "./state.js"
8
- import { connectWebSocket } from "./ws.js"
9
- import type { AppState } from "./types.js"
10
- import type { Selection } from "@opentui/core"
11
-
12
- type AppProps = {
13
- host: string
14
- wsPort: number
15
- connectBackend: boolean
16
- }
17
-
18
- type MenuFocusTarget = "content" | "ws-input" | "reconnect"
19
-
20
- export function App({ host, wsPort, connectBackend }: AppProps) {
21
- const renderer = useRenderer()
22
- const { width, height } = useTerminalDimensions()
23
- const [state, setState] = useState(createInitialState)
24
- const [validationError, setValidationError] = useState<string | null>(null)
25
- const [wsUrl, setWsUrl] = useState(`ws://${host}:${wsPort}`)
26
- const [reconnectVersion, setReconnectVersion] = useState(0)
27
- const [menuFocusTarget, setMenuFocusTarget] = useState<MenuFocusTarget>("content")
28
- const [toastMessage, setToastMessage] = useState<string | null>(null)
29
-
30
- const connection = useMemo(() => {
31
- return connectWebSocket({
32
- url: wsUrl,
33
- enabled: connectBackend,
34
- onLog: (message: string) => setState((current: AppState) => appendLog(current, message)),
35
- onStatusChange: (status) => setState((current: AppState) => setConnectionStatus(current, status))
36
- })
37
- }, [connectBackend, reconnectVersion, wsUrl])
38
-
39
- useEffect(() => {
40
- if (!connectBackend) {
41
- setState((current: AppState) => appendLog(setConnectionStatus(current, "idle"), "[local] backend integration disabled"))
42
- }
43
- return () => connection.dispose()
44
- }, [connectBackend, connection])
45
-
46
- useEffect(() => {
47
- const handleSelection = (selection: Selection) => {
48
- const text = selection.getSelectedText()
49
- if (!text) {
50
- return
51
- }
52
- renderer.copyToClipboardOSC52(text)
53
- setToastMessage(`Copied ${text.length} chars to clipboard`)
54
- }
55
-
56
- renderer.on("selection", handleSelection)
57
- return () => {
58
- renderer.off("selection", handleSelection)
59
- }
60
- }, [renderer])
61
-
62
- useEffect(() => {
63
- if (!toastMessage) {
64
- return
65
- }
66
- const timer = setTimeout(() => {
67
- setToastMessage(null)
68
- }, 3000)
69
- return () => clearTimeout(timer)
70
- }, [toastMessage])
71
-
72
- const reconnect = () => {
73
- setValidationError(null)
74
- setState((current: AppState) => appendLog(current, `[system] reconnect requested: ${wsUrl}`))
75
- setReconnectVersion((value) => value + 1)
76
- }
77
-
78
- const cycleMenuFocus = (backward: boolean) => {
79
- const order: MenuFocusTarget[] = ["ws-input", "reconnect", "content"]
80
- const currentIndex = order.indexOf(menuFocusTarget)
81
- const nextIndex = backward
82
- ? (currentIndex - 1 + order.length) % order.length
83
- : (currentIndex + 1) % order.length
84
- setMenuFocusTarget(order[nextIndex])
85
- }
86
-
87
- useKeyboard((key) => {
88
- if (key.ctrl && key.name === "c") {
89
- renderer.destroy()
90
- return
91
- }
92
-
93
- if (state.screen === "menu") {
94
- if (menuFocusTarget === "ws-input") {
95
- if (key.name === "tab") {
96
- cycleMenuFocus(Boolean(key.shift))
97
- return
98
- }
99
- if (key.name === "escape") {
100
- setMenuFocusTarget("content")
101
- }
102
- return
103
- }
104
-
105
- if (menuFocusTarget === "reconnect") {
106
- if (key.name === "tab") {
107
- cycleMenuFocus(Boolean(key.shift))
108
- return
109
- }
110
- if (key.name === "enter" || key.name === "return") {
111
- reconnect()
112
- return
113
- }
114
- if (key.name === "escape") {
115
- setMenuFocusTarget("content")
116
- }
117
- return
118
- }
119
-
120
- if (key.name === "up") {
121
- setState((current: AppState) => updateSelectedAction(current, (current.selectedActionIndex - 1 + menu.length) % menu.length))
122
- return
123
- }
124
- if (key.name === "down") {
125
- setState((current: AppState) => updateSelectedAction(current, (current.selectedActionIndex + 1) % menu.length))
126
- return
127
- }
128
- if (key.name === "tab") {
129
- cycleMenuFocus(Boolean(key.shift))
130
- return
131
- }
132
- if (key.name === "enter" || key.name === "return") {
133
- setValidationError(null)
134
- setState((current: AppState) => openAction(current))
135
- return
136
- }
137
- return
138
- }
139
-
140
- const inputCount = menu[state.selectedActionIndex].params.length
141
-
142
- if (key.name === "escape") {
143
- setValidationError(null)
144
- setState((current: AppState) => backToMenu(current))
145
- return
146
- }
147
- if (key.name === "tab") {
148
- setState((current: AppState) => nextFocus(current, inputCount))
149
- return
150
- }
151
- if (key.shift && key.name === "tab") {
152
- setState((current: AppState) => previousFocus(current, inputCount))
153
- return
154
- }
155
- if ((key.name === "enter" || key.name === "return") && state.focusIndex === inputCount) {
156
- if (!validateAction(state.selectedActionIndex, state.formValues)) {
157
- setValidationError("Param Invalid")
158
- setState((current: AppState) => appendLog(current, "Param Invalid"))
159
- return
160
- }
161
- setValidationError(null)
162
- const payload = buildPayload(state.selectedActionIndex, state.formValues)
163
- setState((current: AppState) => appendLog(current, `[request] ${payload}`))
164
- connection.send(payload)
165
- }
166
- })
167
-
168
- if (width < 100 || height < 30) {
169
- return <text>Window need to larger than 100x30, current={width}x{height}</text>
170
- }
171
-
172
- return (
173
- <box width="100%" height="100%" flexDirection="column" padding={1} gap={1} position="relative">
174
- <box border borderColor="#7c3aed" paddingX={1} paddingY={0} flexDirection="row" alignItems="center" gap={1}>
175
- <text>JBS Client OpenTUI</text>
176
- </box>
177
- {toastMessage ? (
178
- <box position="absolute" top={1} right={2} zIndex={20} border borderColor="#3b82f6" backgroundColor="#172554" paddingX={1} paddingY={0}>
179
- <text fg="#bfdbfe">{toastMessage}</text>
180
- </box>
181
- ) : null}
182
- <box border borderColor="#2563eb" height={3} minHeight={3} maxHeight={3} paddingX={1} paddingY={0} flexDirection="row" alignItems="center" justifyContent="flex-start" gap={1}>
183
- <text fg="#93c5fd">WS</text>
184
- <box flexGrow={1}>
185
- <input value={wsUrl} onChange={setWsUrl} width="100%" focused={state.screen === "menu" && menuFocusTarget === "ws-input"} />
186
- </box>
187
- <box
188
- border
189
- borderColor={state.screen === "menu" && menuFocusTarget === "reconnect" ? "#3b82f6" : "#22c55e"}
190
- paddingX={1}
191
- paddingY={0}
192
- flexGrow={0}
193
- flexShrink={0}
194
- alignSelf="center"
195
- width={13}
196
- focusable
197
- onMouseDown={() => {
198
- setMenuFocusTarget("reconnect")
199
- reconnect()
200
- }}
201
- >
202
- <text fg={state.screen === "menu" && menuFocusTarget === "reconnect" ? "#93c5fd" : "#22c55e"}>Reconnect</text>
203
- </box>
204
- <box flexGrow={0} flexShrink={0} width={12}>
205
- <text fg={state.connectionStatus === "connected" ? "#22c55e" : state.connectionStatus === "error" ? "#f87171" : "#facc15"}>
206
- {state.connectionStatus}
207
- </text>
208
- </box>
209
- </box>
210
- <box flexDirection="row" flexGrow={1} gap={1}>
211
- <box width="50%" flexGrow={1}>
212
- {state.screen === "menu" ? (
213
- <ActionMenu
214
- focused={menuFocusTarget === "content"}
215
- selectedIndex={state.selectedActionIndex}
216
- onChange={(index: number) => setState((current: AppState) => updateSelectedAction(current, index))}
217
- onSelect={(index: number) => {
218
- setValidationError(null)
219
- setState((current: AppState) => openAction(current, index))
220
- }}
221
- />
222
- ) : (
223
- <ParamForm
224
- actionIndex={state.selectedActionIndex}
225
- values={state.formValues}
226
- focusIndex={state.focusIndex}
227
- focused
228
- validationError={validationError}
229
- onChange={(index: number, value: string) => setState((current: AppState) => updateFormValue(current, index, value))}
230
- />
231
- )}
232
- </box>
233
- <box width="50%" flexGrow={1}>
234
- <LogPanel logs={state.logs} focused={false} />
235
- </box>
236
- </box>
237
- </box>
238
- )
239
- }
@@ -1,28 +0,0 @@
1
- import { menu } from "../protocol.js"
2
-
3
- type ActionMenuProps = {
4
- focused: boolean
5
- selectedIndex: number
6
- onChange: (index: number) => void
7
- onSelect: (index: number) => void
8
- }
9
-
10
- export function ActionMenu({ focused, selectedIndex, onChange, onSelect }: ActionMenuProps) {
11
- return (
12
- <box border borderStyle="rounded" borderColor="#d946ef" padding={1} flexGrow={1} title="Input the action?">
13
- <select
14
- focused={focused}
15
- height={19}
16
- selectedIndex={selectedIndex}
17
- options={menu.map((item) => ({
18
- name: item.name,
19
- value: item.name,
20
- description: `${item.params.length} param${item.params.length === 1 ? "" : "s"}`
21
- }))}
22
- onChange={(index) => onChange(index)}
23
- onSelect={(index) => onSelect(index)}
24
- />
25
- <text fg="#9ca3af">Enter opens the form. Up and Down move the selection.</text>
26
- </box>
27
- )
28
- }
@@ -1,31 +0,0 @@
1
- import { useEffect, useRef } from "react"
2
- import type { ScrollBoxRenderable } from "@opentui/core"
3
-
4
- type LogPanelProps = {
5
- logs: string[]
6
- focused?: boolean
7
- }
8
-
9
- export function LogPanel({ logs, focused = false }: LogPanelProps) {
10
- const scrollRef = useRef<ScrollBoxRenderable | null>(null)
11
-
12
- useEffect(() => {
13
- if (!scrollRef.current) {
14
- return
15
- }
16
- scrollRef.current.scrollTo({ x: 0, y: scrollRef.current.scrollHeight })
17
- }, [logs])
18
-
19
- return (
20
- <box border borderStyle="rounded" borderColor="#26f7ce" padding={1} flexDirection="column" flexGrow={1} title="Logs">
21
- <scrollbox ref={scrollRef} flexGrow={1} focused={focused} stickyScroll stickyStart="bottom">
22
- <box flexDirection="column">
23
- {logs.length === 0 ? <text fg="#6b7280" selectable>Waiting for logs...</text> : null}
24
- {logs.map((log, index) => (
25
- <text key={`${index}-${log}`} selectable>{log}</text>
26
- ))}
27
- </box>
28
- </scrollbox>
29
- </box>
30
- )
31
- }
@@ -1,73 +0,0 @@
1
- import { useRef } from "react"
2
- import { menu } from "../protocol.js"
3
-
4
- type ParamFormProps = {
5
- actionIndex: number
6
- values: string[]
7
- focusIndex: number
8
- focused: boolean
9
- validationError: string | null
10
- onChange: (index: number, value: string) => void
11
- }
12
-
13
- export function ParamForm({ actionIndex, values, focusIndex, focused, validationError, onChange }: ParamFormProps) {
14
- const action = menu[actionIndex]
15
- const submitFocused = focused && focusIndex === action.params.length
16
- const textareaRefs = useRef<Record<number, { plainText?: string; initialValue?: string } | null>>({})
17
-
18
- return (
19
- <box border borderStyle="rounded" borderColor="#d946ef" padding={1} flexDirection="column" gap={1} flexGrow={1} title={action.name}>
20
- {action.params.length === 0 ? <text fg="#d1d5db">This action does not need parameters.</text> : null}
21
- {action.params.map((param, index) => (
22
- <box key={`${action.name}-${param.name}`} flexDirection="column" gap={1}>
23
- <text fg="#a7f3d0">{param.name}</text>
24
- {param.inputType === "textarea" ? (
25
- <line-number minWidth={4} paddingRight={1} fg="#f472b6">
26
- <textarea
27
- ref={(instance) => {
28
- textareaRefs.current[index] = instance
29
- }}
30
- initialValue={values[index] ?? ""}
31
- height={12}
32
- width="100%"
33
- focused={focused && focusIndex === index}
34
- wrapMode="word"
35
- onContentChange={() => onChange(index, textareaRefs.current[index]?.plainText ?? values[index] ?? "")}
36
- />
37
- </line-number>
38
- ) : param.inputType === "select" ? (
39
- <select
40
- options={(param.options ?? []).map((option) => ({
41
- name: option.name,
42
- description: option.description,
43
- value: option.value
44
- }))}
45
- selectedIndex={Math.max(0, (param.options ?? []).findIndex((option) => option.value === (values[index] ?? param.value)))}
46
- focused={focused && focusIndex === index}
47
- height={4}
48
- onChange={(selectedIndex, option) => onChange(index, String(option?.value ?? (param.options ?? [])[selectedIndex]?.value ?? param.value))}
49
- />
50
- ) : (
51
- <input
52
- value={values[index] ?? ""}
53
- onChange={(value) => onChange(index, value)}
54
- width="100%"
55
- focused={focused && focusIndex === index}
56
- />
57
- )}
58
- </box>
59
- ))}
60
- <box
61
- border
62
- borderStyle={submitFocused ? "double" : "single"}
63
- borderColor={submitFocused ? "#22c55e" : "#6b7280"}
64
- paddingX={2}
65
- paddingY={1}
66
- >
67
- <text fg={submitFocused ? "#22c55e" : "#9ca3af"}>[ Submit ]</text>
68
- </box>
69
- {validationError ? <text fg="#f87171">{validationError}</text> : null}
70
- <text fg="#9ca3af">Tab or Shift+Tab changes form focus. Esc returns to the action menu.</text>
71
- </box>
72
- )
73
- }
@@ -1,36 +0,0 @@
1
- import { afterEach, describe, expect, test } from "bun:test"
2
- import { testRender } from "@opentui/react/test-utils"
3
- import { ActionMenu } from "./components/action-menu.js"
4
- import { LogPanel } from "./components/log-panel.js"
5
-
6
- let setup: Awaited<ReturnType<typeof testRender>> | undefined
7
-
8
- afterEach(() => {
9
- setup?.renderer.destroy()
10
- setup = undefined
11
- })
12
-
13
- describe("components", () => {
14
- test("ActionMenu renders menu title and options", async () => {
15
- setup = await testRender(<ActionMenu focused selectedIndex={0} onChange={() => {}} onSelect={() => {}} />, {
16
- width: 70,
17
- height: 20
18
- })
19
- await setup.renderOnce()
20
- const frame = setup.captureCharFrame()
21
- expect(frame).toContain("Input the action?")
22
- expect(frame).toContain("Watch")
23
- expect(frame).toContain("Eval")
24
- })
25
-
26
- test("LogPanel renders logs and status", async () => {
27
- setup = await testRender(<LogPanel logs={["hello", "world"]} />, {
28
- width: 60,
29
- height: 16
30
- })
31
- await setup.renderOnce()
32
- const frame = setup.captureCharFrame()
33
- expect(frame).toContain("Logs")
34
- expect(frame).toContain("world")
35
- })
36
- })
package/src/index.tsx DELETED
@@ -1,19 +0,0 @@
1
- import { createCliRenderer } from "@opentui/core"
2
- import { createRoot } from "@opentui/react"
3
- import { App } from "./app.js"
4
-
5
- function readArg(name: string, fallback: string): string {
6
- const args = Bun.argv.slice(2)
7
- const index = args.indexOf(name)
8
- return index >= 0 && index + 1 < args.length ? args[index + 1] : fallback
9
- }
10
-
11
- const host = readArg("--host", "localhost")
12
- const wsPort = Number.parseInt(readArg("--ws_port", "18000"), 10)
13
- const connectBackend = readArg("--connect", "true") !== "false"
14
-
15
- const renderer = await createCliRenderer({
16
- exitOnCtrlC: false
17
- })
18
-
19
- createRoot(renderer).render(<App host={host} wsPort={wsPort} connectBackend={connectBackend} />)
@@ -1,40 +0,0 @@
1
- import { describe, expect, test } from "bun:test"
2
- import { buildInitialFormValues, buildPayload, menu, validateAction } from "./protocol.js"
3
-
4
- describe("protocol", () => {
5
- test("buildInitialFormValues copies defaults", () => {
6
- expect(buildInitialFormValues(0)).toEqual(["", "0"])
7
- expect(buildInitialFormValues(7)[0]).toContain("ApplicationContext")
8
- expect(buildInitialFormValues(3)[2]).toBe("asm")
9
- })
10
-
11
- test("validateAction enforces signature checks", () => {
12
- expect(validateAction(0, ["com.demo.Service#run", "1"])).toBe(true)
13
- expect(validateAction(0, ["com.demo.Service.run", "1"])).toBe(false)
14
- })
15
-
16
- test("buildPayload serializes change body request", () => {
17
- const payload = JSON.parse(buildPayload(3, ["a.B#c", "java.lang.String, int", "javassist", "return 1;"])) as Record<string, unknown>
18
- expect(payload.type).toBe("CHANGE_BODY")
19
- expect(payload.className).toBe("a.B")
20
- expect(payload.method).toBe("c")
21
- expect(payload.paramTypes).toEqual(["java.lang.String", "int"])
22
- expect(payload.body).toBe("return 1;")
23
- expect(payload.mode).toBe(0)
24
- expect(typeof payload.id).toBe("string")
25
- })
26
-
27
- test("buildPayload serializes eval and decompile requests", () => {
28
- const decompilePayload = JSON.parse(buildPayload(5, ["demo.Service"])) as Record<string, unknown>
29
- expect(decompilePayload.type).toBe("DECOMPILE")
30
- expect(decompilePayload.className).toBe("demo.Service")
31
-
32
- const evalPayload = JSON.parse(buildPayload(6, ["1 + 1"])) as Record<string, unknown>
33
- expect(evalPayload.type).toBe("EVAL")
34
- expect(evalPayload.body).toBe("1 + 1")
35
- })
36
-
37
- test("menu preserves expected action count", () => {
38
- expect(menu.map((item) => item.name)).toEqual(["Watch", "OuterWatch", "Trace", "ChangeBody", "ChangeResult", "Decompile", "Eval", "Exec", "Reset"])
39
- })
40
- })
package/src/protocol.ts DELETED
@@ -1,201 +0,0 @@
1
- import type { FunctionDefinition } from "./types.js"
2
-
3
- const engineOptions = [
4
- { name: "ASM", description: "Default bytecode engine", value: "asm" },
5
- { name: "Javassist", description: "Source-level transformer", value: "javassist" }
6
- ]
7
-
8
- function classAndMethodChecker(value: string): boolean {
9
- return value.split("#").length === 2
10
- }
11
-
12
- function nonEmptyChecker(value: string): boolean {
13
- return value.trim().length > 0
14
- }
15
-
16
- function engineToMode(value: string): number {
17
- return value === "javassist" ? 0 : 1
18
- }
19
-
20
- function randomString(length: number): string {
21
- const alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
22
- let result = ""
23
- for (let index = 0; index < length; index += 1) {
24
- result += alphabet[Math.floor(Math.random() * alphabet.length)]
25
- }
26
- return result
27
- }
28
-
29
- function commonMap(): Record<string, unknown> {
30
- return {
31
- id: randomString(4),
32
- timestamp: Date.now()
33
- }
34
- }
35
-
36
- function parseParamTypes(value: string): string[] {
37
- return value
38
- .split(",")
39
- .map((part) => part.trim())
40
- .filter((part) => part.length > 0)
41
- }
42
-
43
- function splitSignature(value: string): [string, string] {
44
- const [className = "", method = ""] = value.split("#")
45
- return [className, method]
46
- }
47
-
48
- export const menu: FunctionDefinition[] = [
49
- {
50
- name: "Watch",
51
- params: [
52
- { name: "ClassName#MethodName", inputType: "text", checker: classAndMethodChecker, value: "" },
53
- { name: "MinCost", inputType: "text", checker: () => true, value: "0" }
54
- ],
55
- toPayload: (params: string[]) => JSON.stringify({ ...commonMap(), type: "WATCH", signature: params[0], minCost: Number.parseInt(params[1] || "0", 10) || 0 })
56
- },
57
- {
58
- name: "OuterWatch",
59
- params: [
60
- { name: "ClassName#MethodName", inputType: "text", checker: classAndMethodChecker, value: "" },
61
- { name: "InnerClassName#InnerMethodName", inputType: "text", checker: classAndMethodChecker, value: "" }
62
- ],
63
- toPayload: (params: string[]) => JSON.stringify({ ...commonMap(), type: "OUTER_WATCH", signature: params[0], innerSignature: params[1] })
64
- },
65
- {
66
- name: "Trace",
67
- params: [
68
- { name: "ClassName#MethodName", inputType: "text", checker: classAndMethodChecker, value: "" },
69
- { name: "MinCost", inputType: "text", checker: () => true, value: "0" },
70
- { name: "IgnoreSubMethodZeroCost", inputType: "text", checker: () => true, value: "true" }
71
- ],
72
- toPayload: (params: string[]) => JSON.stringify({
73
- ...commonMap(),
74
- type: "TRACE",
75
- signature: params[0],
76
- minCost: Number.parseInt(params[1] || "0", 10) || 0,
77
- ignoreZero: params[2] === "true"
78
- })
79
- },
80
- {
81
- name: "ChangeBody",
82
- params: [
83
- { name: "ClassName#MethodName", inputType: "text", checker: classAndMethodChecker, value: "" },
84
- { name: "ParamTypes", inputType: "text", checker: () => true, value: "" },
85
- { name: "Engine", inputType: "select", checker: () => true, value: "asm", options: engineOptions },
86
- { name: "Body", inputType: "textarea", checker: () => true, value: "" }
87
- ],
88
- toPayload: (params: string[]) => {
89
- const [className, method] = splitSignature(params[0])
90
- return JSON.stringify({
91
- ...commonMap(),
92
- type: "CHANGE_BODY",
93
- className,
94
- method,
95
- paramTypes: parseParamTypes(params[1]),
96
- body: params[3],
97
- mode: engineToMode(params[2])
98
- })
99
- }
100
- },
101
- {
102
- name: "ChangeResult",
103
- params: [
104
- { name: "ClassName#MethodName", inputType: "text", checker: classAndMethodChecker, value: "" },
105
- { name: "ParamTypes", inputType: "text", checker: () => true, value: "" },
106
- { name: "InnerClassName#InnerMethodName", inputType: "text", checker: classAndMethodChecker, value: "" },
107
- { name: "Engine", inputType: "select", checker: () => true, value: "asm", options: engineOptions },
108
- { name: "Body", inputType: "textarea", checker: () => true, value: "" }
109
- ],
110
- toPayload: (params: string[]) => {
111
- const [className, method] = splitSignature(params[0])
112
- const [innerClassName, innerMethod] = splitSignature(params[2])
113
- return JSON.stringify({
114
- ...commonMap(),
115
- type: "CHANGE_RESULT",
116
- className,
117
- method,
118
- paramTypes: parseParamTypes(params[1]),
119
- innerClassName,
120
- innerMethod,
121
- body: params[4],
122
- mode: engineToMode(params[3])
123
- })
124
- }
125
- },
126
- {
127
- name: "Decompile",
128
- params: [
129
- { name: "ClassName", inputType: "text", checker: nonEmptyChecker, value: "" }
130
- ],
131
- toPayload: (params: string[]) => JSON.stringify({
132
- ...commonMap(),
133
- type: "DECOMPILE",
134
- className: params[0]
135
- })
136
- },
137
- {
138
- name: "Eval",
139
- params: [
140
- {
141
- name: "Body",
142
- inputType: "textarea",
143
- checker: nonEmptyChecker,
144
- value: "ctx.getBeanDefinitionNames().length"
145
- }
146
- ],
147
- toPayload: (params: string[]) => JSON.stringify({
148
- ...commonMap(),
149
- type: "EVAL",
150
- body: params[0]
151
- })
152
- },
153
- {
154
- name: "Exec",
155
- params: [
156
- {
157
- name: "Body",
158
- inputType: "textarea",
159
- checker: () => true,
160
- value: `
161
- try {
162
- ApplicationContext ctx =
163
- (ApplicationContext) SpringUtils.getSpringBootApplicationContext();
164
- Global.info(Arrays.toString(ctx.getBeanDefinitionNames()));
165
- } catch (Exception e) {
166
- Global.error(e.toString(), e);
167
- }
168
- `
169
- }
170
- ],
171
- toPayload: (params: string[]) => JSON.stringify({
172
- ...commonMap(),
173
- type: "EXEC",
174
- body: `package w;
175
- import w.Global;
176
- import w.util.SpringUtils;
177
- import org.springframework.context.ApplicationContext;
178
- import java.util.*;
179
- public class Exec{
180
- public void exec() {${params[0] ?? ""}}
181
- }`
182
- })
183
- },
184
- {
185
- name: "Reset",
186
- params: [],
187
- toPayload: () => JSON.stringify({ ...commonMap(), type: "RESET" })
188
- }
189
- ]
190
-
191
- export function buildInitialFormValues(actionIndex: number): string[] {
192
- return menu[actionIndex].params.map((param: FunctionDefinition["params"][number]) => param.value)
193
- }
194
-
195
- export function validateAction(actionIndex: number, values: string[]): boolean {
196
- return menu[actionIndex].params.every((param: FunctionDefinition["params"][number], index: number) => param.checker(values[index] ?? ""))
197
- }
198
-
199
- export function buildPayload(actionIndex: number, values: string[]): string {
200
- return menu[actionIndex].toPayload(values)
201
- }