@gomjellie/lazyapi 0.0.2 → 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/dist/App.js +1 -1
- package/dist/components/common/LoadingSpinner.js +8 -4
- package/dist/components/common/StatusBar.js +3 -0
- package/dist/components/detail-view/DetailView.js +38 -38
- package/dist/components/detail-view/LeftPanel.d.ts +4 -1
- package/dist/components/detail-view/LeftPanel.js +402 -72
- package/dist/components/detail-view/RightPanel.d.ts +3 -2
- package/dist/components/detail-view/RightPanel.js +119 -31
- package/dist/components/list-view/EndpointList.js +16 -11
- package/dist/components/list-view/ListView.js +34 -58
- package/dist/components/list-view/MethodsBar.d.ts +14 -0
- package/dist/components/list-view/MethodsBar.js +36 -0
- package/dist/hooks/useKeyPress.js +66 -23
- package/dist/services/openapi-parser.d.ts +10 -2
- package/dist/services/openapi-parser.js +104 -10
- package/dist/store/appSlice.d.ts +2 -2
- package/dist/store/appSlice.js +43 -5
- package/dist/store/hooks.d.ts +1 -1
- package/dist/store/index.d.ts +3 -3
- package/dist/store/navigationActions.d.ts +9 -0
- package/dist/store/navigationActions.js +129 -0
- package/dist/types/app-state.d.ts +13 -4
- package/dist/types/openapi.d.ts +18 -1
- package/dist/utils/schema-formatter.js +19 -5
- package/dist/utils/schema-scaffolder.d.ts +7 -0
- package/dist/utils/schema-scaffolder.js +61 -0
- package/package.json +5 -1
- package/dist/components/list-view/FilterBar.d.ts +0 -12
- package/dist/components/list-view/FilterBar.js +0 -33
package/dist/App.js
CHANGED
|
@@ -41,7 +41,7 @@ function AppContent({ filePath }) {
|
|
|
41
41
|
// 로딩 상태
|
|
42
42
|
if (loading) {
|
|
43
43
|
return (React.createElement(Box, { flexDirection: "column", padding: 1, height: terminalHeight },
|
|
44
|
-
React.createElement(LoadingSpinner, { message: "OpenAPI
|
|
44
|
+
React.createElement(LoadingSpinner, { message: "Fetching OpenAPI document from URL..." })));
|
|
45
45
|
}
|
|
46
46
|
// 에러 상태
|
|
47
47
|
if (error) {
|
|
@@ -4,12 +4,16 @@
|
|
|
4
4
|
import React from 'react';
|
|
5
5
|
import { Box, Text } from 'ink';
|
|
6
6
|
import Spinner from 'ink-spinner';
|
|
7
|
+
import BigText from 'ink-big-text';
|
|
8
|
+
import Gradient from 'ink-gradient';
|
|
7
9
|
export default function LoadingSpinner({ message = '로딩 중...' }) {
|
|
8
|
-
return (React.createElement(Box, { flexDirection: "column", alignItems: "center", justifyContent: "center",
|
|
9
|
-
React.createElement(
|
|
10
|
-
React.createElement(
|
|
10
|
+
return (React.createElement(Box, { flexDirection: "column", alignItems: "center", justifyContent: "center", flexGrow: 1 },
|
|
11
|
+
React.createElement(Gradient, { name: "rainbow" },
|
|
12
|
+
React.createElement(BigText, { text: "LAZYAPI" })),
|
|
13
|
+
React.createElement(Box, { marginTop: 1 },
|
|
14
|
+
React.createElement(Text, { color: "cyan" },
|
|
11
15
|
React.createElement(Spinner, { type: "dots" })),
|
|
12
|
-
React.createElement(Text, {
|
|
16
|
+
React.createElement(Text, { italic: true, color: "gray" },
|
|
13
17
|
' ',
|
|
14
18
|
message))));
|
|
15
19
|
}
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import React from 'react';
|
|
5
5
|
import { Box, Text } from 'ink';
|
|
6
|
+
import { useAppSelector } from '../../store/hooks.js';
|
|
6
7
|
export default function StatusBar({ title, version, currentPath, mode, status }) {
|
|
8
|
+
const navigationCount = useAppSelector((state) => state.app.navigationCount);
|
|
7
9
|
const getModeColor = () => {
|
|
8
10
|
switch (mode) {
|
|
9
11
|
case 'INSERT':
|
|
@@ -29,6 +31,7 @@ export default function StatusBar({ title, version, currentPath, mode, status })
|
|
|
29
31
|
status && React.createElement(Text, { color: "green" },
|
|
30
32
|
"\u25CF ",
|
|
31
33
|
status),
|
|
34
|
+
navigationCount !== null && (React.createElement(Text, { color: "yellow", bold: true }, navigationCount)),
|
|
32
35
|
React.createElement(Text, { bold: true, color: getModeColor() },
|
|
33
36
|
"[",
|
|
34
37
|
mode,
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
import React from 'react';
|
|
5
5
|
import { Box, Text, useStdout } from 'ink';
|
|
6
6
|
import { useAppSelector, useAppDispatch } from '../../store/hooks.js';
|
|
7
|
-
import { setMode, detailSetFocus, detailSetPanel, detailSetRequestTab, detailSetResponseTab, detailSetResponse, detailSetParam, } from '../../store/appSlice.js';
|
|
7
|
+
import { setMode, detailSetFocus, detailSetPanel, detailSetRequestTab, detailSetResponseTab, detailSetResponse, detailSetParam, detailSetBody, } from '../../store/appSlice.js';
|
|
8
|
+
import { navigateUp, navigateDown, navigateLeft, navigateRight, } from '../../store/navigationActions.js';
|
|
8
9
|
import { useNavigation } from '../../hooks/useNavigation.js';
|
|
9
10
|
import { useKeyPress } from '../../hooks/useKeyPress.js';
|
|
10
11
|
import { useApiClient } from '../../hooks/useApiClient.js';
|
|
@@ -38,7 +39,9 @@ export default function DetailView() {
|
|
|
38
39
|
queryParams.length +
|
|
39
40
|
headerParams.length +
|
|
40
41
|
bodyParams.length +
|
|
41
|
-
formDataParams.length
|
|
42
|
+
formDataParams.length +
|
|
43
|
+
(endpoint?.requestBody && bodyParams.length === 0 ? 1 : 0) +
|
|
44
|
+
(endpoint?.responses?.length || 0);
|
|
42
45
|
const handleExecuteRequest = async () => {
|
|
43
46
|
if (!detail || !document)
|
|
44
47
|
return;
|
|
@@ -60,8 +63,8 @@ export default function DetailView() {
|
|
|
60
63
|
if (cmd === 'q' || cmd === 'quit' || cmd === 'exit') {
|
|
61
64
|
process.exit(0);
|
|
62
65
|
}
|
|
63
|
-
// 2. 접두사 있는 명령어 처리 (pp1, qp1, h1,
|
|
64
|
-
const match = cmd.match(/^(pp|qp|h|
|
|
66
|
+
// 2. 접두사 있는 명령어 처리 (pp1, qp1, h1, rqb1, rs1, rq1, rb1...)
|
|
67
|
+
const match = cmd.match(/^(pp|qp|h|rqb|rs|rq|rb)(\d+)$/);
|
|
65
68
|
if (match) {
|
|
66
69
|
const type = match[1];
|
|
67
70
|
const index = parseInt(match[2], 10);
|
|
@@ -97,10 +100,9 @@ export default function DetailView() {
|
|
|
97
100
|
dispatch(detailSetPanel('request'));
|
|
98
101
|
}
|
|
99
102
|
}
|
|
100
|
-
else if (type === '
|
|
101
|
-
// Request Body
|
|
103
|
+
else if (type === 'rqb') {
|
|
104
|
+
// Request Body (기존 rb -> rqb)
|
|
102
105
|
const bodyStartIndex = pathParams.length + queryParams.length + headerParams.length;
|
|
103
|
-
// 만약 bodyParams가 있으면 그쪽으로, 아니면 requestBody(Raw)로
|
|
104
106
|
if (bodyParams.length > 0) {
|
|
105
107
|
if (targetIndex >= 0 && targetIndex < bodyParams.length) {
|
|
106
108
|
dispatch(detailSetFocus(bodyStartIndex + targetIndex));
|
|
@@ -108,14 +110,26 @@ export default function DetailView() {
|
|
|
108
110
|
}
|
|
109
111
|
}
|
|
110
112
|
else if (endpoint?.requestBody) {
|
|
111
|
-
// Form Data가 있으면 그 뒤
|
|
112
113
|
const rawBodyIndex = bodyStartIndex + formDataParams.length;
|
|
113
|
-
if (targetIndex === 0) {
|
|
114
|
+
if (targetIndex === 0) {
|
|
114
115
|
dispatch(detailSetFocus(rawBodyIndex));
|
|
115
116
|
dispatch(detailSetPanel('left'));
|
|
116
117
|
}
|
|
117
118
|
}
|
|
118
119
|
}
|
|
120
|
+
else if (type === 'rb') {
|
|
121
|
+
// Responses (사용자 요청: rb)
|
|
122
|
+
const responsesStartIndex = pathParams.length +
|
|
123
|
+
queryParams.length +
|
|
124
|
+
headerParams.length +
|
|
125
|
+
bodyParams.length +
|
|
126
|
+
formDataParams.length +
|
|
127
|
+
(endpoint?.requestBody && bodyParams.length === 0 ? 1 : 0);
|
|
128
|
+
if (targetIndex >= 0 && targetIndex < (endpoint?.responses?.length || 0)) {
|
|
129
|
+
dispatch(detailSetFocus(responsesStartIndex + targetIndex));
|
|
130
|
+
dispatch(detailSetPanel('left'));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
119
133
|
else if (type === 'rs') {
|
|
120
134
|
// Response Tabs
|
|
121
135
|
const tabs = [
|
|
@@ -166,28 +180,16 @@ export default function DetailView() {
|
|
|
166
180
|
// 키보드 입력 처리 (NORMAL 모드)
|
|
167
181
|
useKeyPress({
|
|
168
182
|
j: () => {
|
|
169
|
-
|
|
170
|
-
// 다음 필드로
|
|
171
|
-
const nextIndex = Math.min(detail.focusedFieldIndex + 1, totalFields - 1);
|
|
172
|
-
dispatch(detailSetFocus(nextIndex));
|
|
173
|
-
}
|
|
183
|
+
dispatch(navigateDown());
|
|
174
184
|
},
|
|
175
185
|
k: () => {
|
|
176
|
-
|
|
177
|
-
// 이전 필드로
|
|
178
|
-
const prevIndex = Math.max(detail.focusedFieldIndex - 1, 0);
|
|
179
|
-
dispatch(detailSetFocus(prevIndex));
|
|
180
|
-
}
|
|
186
|
+
dispatch(navigateUp());
|
|
181
187
|
},
|
|
182
188
|
h: () => {
|
|
183
|
-
|
|
184
|
-
dispatch(detailSetPanel('left'));
|
|
189
|
+
dispatch(navigateLeft());
|
|
185
190
|
},
|
|
186
191
|
l: () => {
|
|
187
|
-
|
|
188
|
-
if (activePanel === 'left') {
|
|
189
|
-
dispatch(detailSetPanel('request'));
|
|
190
|
-
}
|
|
192
|
+
dispatch(navigateRight());
|
|
191
193
|
},
|
|
192
194
|
onTab: () => {
|
|
193
195
|
// 패널 순환: Left -> Request -> Response -> Left
|
|
@@ -201,12 +203,6 @@ export default function DetailView() {
|
|
|
201
203
|
dispatch(detailSetPanel('left'));
|
|
202
204
|
}
|
|
203
205
|
},
|
|
204
|
-
i: () => {
|
|
205
|
-
// INSERT 모드로 전환 (왼쪽 패널에서만)
|
|
206
|
-
if (activePanel === 'left') {
|
|
207
|
-
dispatch(setMode('INSERT'));
|
|
208
|
-
}
|
|
209
|
-
},
|
|
210
206
|
u: () => {
|
|
211
207
|
// 뒤로 가기
|
|
212
208
|
goBack();
|
|
@@ -224,7 +220,7 @@ export default function DetailView() {
|
|
|
224
220
|
onEscape: () => {
|
|
225
221
|
dispatch(setMode('NORMAL'));
|
|
226
222
|
dispatch(setCommandInput(''));
|
|
227
|
-
}
|
|
223
|
+
},
|
|
228
224
|
}, mode === 'COMMAND');
|
|
229
225
|
// INSERT 모드 키보드 입력 처리
|
|
230
226
|
useKeyPress({
|
|
@@ -249,18 +245,22 @@ export default function DetailView() {
|
|
|
249
245
|
];
|
|
250
246
|
const baseURL = document ? getServerUrl(document) || '' : '';
|
|
251
247
|
const terminalHeight = stdout?.rows || 24;
|
|
252
|
-
// detail 또는 endpoint가 없는 경우 에러 표시
|
|
253
|
-
if (!detail || !endpoint) {
|
|
248
|
+
// detail 또는 endpoint 또는 document가 없는 경우 에러 표시
|
|
249
|
+
if (!detail || !endpoint || !document) {
|
|
254
250
|
return (React.createElement(Box, { flexDirection: "column", height: terminalHeight },
|
|
255
251
|
React.createElement(StatusBar, { title: "API DETAIL", currentPath: "", mode: mode, status: "ERROR" }),
|
|
256
252
|
React.createElement(Box, { flexGrow: 1, justifyContent: "center", alignItems: "center" },
|
|
257
|
-
React.createElement(Text, { color: "red" }, "\uC0C1\uC138 \uC815\uBCF4\uB97C \uD45C\uC2DC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.")),
|
|
253
|
+
React.createElement(Text, { color: "red" }, "\uC0C1\uC138 \uC815\uBCF4\uB97C \uD45C\uC2DC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. (\uBB38\uC11C \uB610\uB294 \uC5D4\uB4DC\uD3EC\uC778\uD2B8 \uC815\uBCF4 \uC5C6\uC74C)")),
|
|
258
254
|
React.createElement(KeybindingFooter, { bindings: [], mode: mode })));
|
|
259
255
|
}
|
|
260
256
|
return (React.createElement(Box, { flexDirection: "column", height: terminalHeight },
|
|
261
257
|
React.createElement(StatusBar, { title: "API DETAIL", currentPath: endpoint.path, mode: mode, status: loading ? 'LOADING...' : 'READY' }),
|
|
262
|
-
React.createElement(Box, { flexGrow: 1
|
|
263
|
-
React.createElement(LeftPanel, { endpoint: endpoint, parameterValues: detail.parameterValues, focusedFieldIndex: detail.focusedFieldIndex, mode: mode, isFocused: activePanel === 'left', document: document, commandMode: mode === 'COMMAND', onParameterChange: (key, value) => dispatch(detailSetParam({ key, value })), onExitInsertMode: () => dispatch(setMode('NORMAL'))
|
|
264
|
-
|
|
258
|
+
React.createElement(Box, { flexGrow: 1 },
|
|
259
|
+
React.createElement(LeftPanel, { endpoint: endpoint, parameterValues: detail.parameterValues, requestBodyValue: detail.requestBody, focusedFieldIndex: detail.focusedFieldIndex, mode: mode, isFocused: activePanel === 'left', document: document, commandMode: mode === 'COMMAND', onParameterChange: (key, value) => dispatch(detailSetParam({ key, value })), onExitInsertMode: () => dispatch(setMode('NORMAL')), onRequestBodyEdit: (value) => {
|
|
260
|
+
dispatch(detailSetBody(value));
|
|
261
|
+
dispatch(detailSetPanel('request'));
|
|
262
|
+
dispatch(detailSetRequestTab('body'));
|
|
263
|
+
}, onSetMode: (m) => dispatch(setMode(m)) }),
|
|
264
|
+
React.createElement(RightPanel, { activeRequestTab: activeRequestTab, activeResponseTab: activeResponseTab, activePanel: activePanel, response: detail.response, endpoint: endpoint, document: document, parameterValues: detail.parameterValues, requestBody: detail.requestBody, requestHeaders: detail.headers, baseURL: baseURL, mode: mode, isLoading: loading, commandMode: mode === 'COMMAND' })),
|
|
265
265
|
React.createElement(KeybindingFooter, { bindings: keyBindings, mode: mode, commandInput: commandInput, onCommandChange: (value) => dispatch(setCommandInput(value)), onCommandSubmit: handleCommandSubmit })));
|
|
266
266
|
}
|
|
@@ -7,6 +7,7 @@ import { AppMode } from '../../types/app-state.js';
|
|
|
7
7
|
interface LeftPanelProps {
|
|
8
8
|
endpoint: Endpoint;
|
|
9
9
|
parameterValues: Record<string, string>;
|
|
10
|
+
requestBodyValue?: string;
|
|
10
11
|
focusedFieldIndex: number;
|
|
11
12
|
mode: AppMode;
|
|
12
13
|
isFocused: boolean;
|
|
@@ -14,6 +15,8 @@ interface LeftPanelProps {
|
|
|
14
15
|
commandMode?: boolean;
|
|
15
16
|
onParameterChange: (key: string, value: string) => void;
|
|
16
17
|
onExitInsertMode?: () => void;
|
|
18
|
+
onRequestBodyEdit?: (value: string) => void;
|
|
19
|
+
onSetMode?: (mode: AppMode) => void;
|
|
17
20
|
}
|
|
18
|
-
export default function LeftPanel({ endpoint, parameterValues, focusedFieldIndex, mode, isFocused, document, commandMode, onParameterChange, onExitInsertMode, }: LeftPanelProps): React.JSX.Element;
|
|
21
|
+
export default function LeftPanel({ endpoint, parameterValues, requestBodyValue, focusedFieldIndex, mode, isFocused, document, commandMode, onParameterChange, onExitInsertMode, onRequestBodyEdit, onSetMode, }: LeftPanelProps): React.JSX.Element;
|
|
19
22
|
export {};
|