@rhseung/ps-cli 1.7.5 → 1.9.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/README.md CHANGED
@@ -1,6 +1,9 @@
1
1
  # ps-cli
2
2
 
3
- 백준 문제 해결을 위한 CLI 도구입니다.
3
+ **대화형** 인터페이스와 **사용자 친화적인 디자인**을 갖춘 백준 문제 해결 CLI 도구입니다.
4
+ 검색부터 제출까지, 모든 과정을 직관적이고 아름다운 터미널 환경에서 해결하세요.
5
+
6
+ Nerd Font 사용을 권장합니다.
4
7
 
5
8
  ## 설치
6
9
 
@@ -13,6 +16,9 @@ bun install -g @rhseung/ps-cli
13
16
  ## 빠른 시작
14
17
 
15
18
  ```bash
19
+ # 0. 도움말 보기
20
+ ps --help
21
+
16
22
  # 1. 프로젝트 초기화
17
23
  ps init
18
24
 
@@ -60,6 +66,7 @@ ps fetch <문제번호> [옵션]
60
66
 
61
67
  **옵션:**
62
68
 
69
+ - `--help`, `-h`: 도움말 표시
63
70
  - `--language`, `-l`: 언어 선택 (python, javascript, typescript, cpp)
64
71
  - 기본값: python 또는 설정 파일의 `default-language`
65
72
 
@@ -92,6 +99,7 @@ ps test [문제번호] [옵션]
92
99
 
93
100
  **옵션:**
94
101
 
102
+ - `--help`, `-h`: 도움말 표시
95
103
  - `--language`, `-l`: 언어 선택 (지정 시 자동 감지 무시)
96
104
  - 지원 언어: python, javascript, typescript, cpp
97
105
  - `--watch`, `-w`: watch 모드 (파일 변경 시 자동 재테스트)
@@ -128,6 +136,7 @@ ps run [문제번호] [옵션]
128
136
 
129
137
  **옵션:**
130
138
 
139
+ - `--help`, `-h`: 도움말 표시
131
140
  - `--language`, `-l`: 언어 선택 (지정 시 자동 감지 무시)
132
141
  - 지원 언어: python, javascript, typescript, cpp
133
142
  - `--input`, `-i`: 입력 파일 지정
@@ -166,6 +175,7 @@ ps submit [문제번호] [옵션]
166
175
 
167
176
  **옵션:**
168
177
 
178
+ - `--help`, `-h`: 도움말 표시
169
179
  - `--language`, `-l`: 언어 선택 (지정 시 자동 감지 무시)
170
180
  - 지원 언어: python, javascript, typescript, cpp
171
181
 
@@ -188,7 +198,7 @@ ps submit --language python # Python으로 제출
188
198
 
189
199
  ### `archive` - 아카이빙
190
200
 
191
- solving 디렉토리의 문제를 archive 디렉토리로 이동하고 Git 커밋을 생성합니다.
201
+ solving 디렉토리의 문제를 archive 디렉토리로 이동하고 (선택적으로) Git 커밋을 생성합니다.
192
202
 
193
203
  **사용법:**
194
204
 
@@ -206,31 +216,47 @@ ps archive # 현재 디렉토리에서 문제 번호 자동
206
216
  **설명:**
207
217
 
208
218
  - solving 디렉토리에서 문제를 찾아 archive 디렉토리로 이동
209
- - Git add 및 commit 실행
210
- - 커밋 메시지: "solve: {문제번호} - {문제이름}"
219
+ - 설정에 따라 Git add 및 commit 실행
220
+ - 기본 커밋 메시지: "solve: {id} - {title}"
221
+ - `archive-auto-commit` 이 `true` 인 경우, **archive 디렉토리로 이동 후** Git 커밋을 시도하며, 커밋 실패 시 다시 원래 위치로 되돌림 (롤백)
222
+ - `archive-auto-commit` 이 `false` 인 경우, Git 커밋 없이 디렉토리만 이동
211
223
 
212
224
  ---
213
225
 
214
- ### `open` - 문제 페이지 열기
226
+ ### `open` - 문제 페이지 또는 에디터 열기
215
227
 
216
- 백준 문제 페이지를 브라우저로 엽니다.
228
+ 백준 문제 페이지 또는 에디터를 엽니다.
217
229
 
218
230
  **사용법:**
219
231
 
220
232
  ```bash
221
- ps open [문제번호]
233
+ ps open [문제번호] [옵션]
234
+ ps open --workbook <문제집ID>
235
+ ps open -w <문제집ID>
222
236
  ```
223
237
 
238
+ **옵션:**
239
+
240
+ - `--help`, `-h`: 도움말 표시
241
+ - `--workbook`, `-w`: 문제집 ID를 지정하여 해당 문제집 페이지를 엽니다
242
+ - `--editor`, `-e`: 문제 파일을 에디터로 엽니다
243
+
224
244
  **예제:**
225
245
 
226
246
  ```bash
227
- ps open 1000 # 1000번 문제 열기
228
- ps open # 문제 디렉토리에서 실행 시 자동 추론
247
+ ps open 1000 # 1000번 문제 브라우저로 열기
248
+ ps open # 현재 문제 브라우저로 열기
249
+ ps open -e # 현재 문제 에디터로 열기
250
+ ps open 1000 -e # 1000번 문제 에디터로 열기
251
+ ps open --workbook 25052 # 문제집 25052 열기
229
252
  ```
230
253
 
231
254
  **설명:**
232
255
 
233
256
  - 문제 번호를 인자로 전달하거나 문제 디렉토리에서 실행하면 자동으로 문제 번호를 추론
257
+ - `--workbook` 또는 `-w` 옵션으로 문제집 페이지를 열 수 있습니다
258
+ - `--editor` 또는 `-e` 옵션으로 문제의 솔루션 파일이나 디렉토리를 에디터로 열 수 있습니다
259
+ - 에디터는 `ps config set editor <명령어>`로 설정할 수 있습니다 (기본값: `code`)
234
260
 
235
261
  ---
236
262
 
@@ -247,6 +273,7 @@ ps search --workbook <문제집ID>
247
273
 
248
274
  **옵션:**
249
275
 
276
+ - `--help`, `-h`: 도움말 표시
250
277
  - `--workbook`: 문제집 ID를 지정하여 해당 문제집의 문제 목록을 표시
251
278
 
252
279
  **예제:**
@@ -263,7 +290,15 @@ ps search --workbook 25052 # 문제집 25052의 문제 목록 표시
263
290
  **설명:**
264
291
 
265
292
  - solved.ac 검색어 문법을 지원합니다
266
- - 문제 목록에서 선택하면 자동으로 브라우저에서 문제 페이지를 엽니다
293
+ - 문제 목록에서 해결 상태를 아이콘으로 확인할 있습니다
294
+ - **해결됨(``)**: 이미 해결되어 아카이브된 문제
295
+ - **해결 중(``)**: 현재 해결 중인 문제
296
+ - 문제 선택 시 인터랙티브 메뉴를 통해 다양한 작업을 수행할 수 있습니다
297
+ - `󰖟 브라우저에서 열기 (open)`
298
+ - `󰇚 문제 가져오기 (fetch)`
299
+ - ` 로컬 테스트 실행 (test)` (해결 중/완료인 경우)
300
+ - `󰭹 코드 제출하기 (submit)` (해결 중/완료인 경우)
301
+ - ` 문제 아카이브 (archive)` (해결 중인 경우)
267
302
  - 페이지네이션을 통해 여러 페이지의 결과를 탐색할 수 있습니다
268
303
  - `--workbook` 옵션으로 백준 문제집의 문제 목록을 볼 수 있습니다
269
304
 
@@ -281,6 +316,7 @@ ps stats [핸들] [옵션]
281
316
 
282
317
  **옵션:**
283
318
 
319
+ - `--help`, `-h`: 도움말 표시
284
320
  - `--handle`, `-h`: Solved.ac 핸들
285
321
  - 설정에 저장된 값 사용 가능
286
322
  - 인자로 전달하거나 플래그로 지정 가능
@@ -308,7 +344,7 @@ ps stats # 설정에 저장된 핸들 사용
308
344
  **사용법:**
309
345
 
310
346
  ```bash
311
- ps config <명령어> [키] [값]
347
+ ps config <명령어> [키] [값] [옵션]
312
348
  ```
313
349
 
314
350
  **명령어:**
@@ -318,6 +354,10 @@ ps config <명령어> [키] [값]
318
354
  - `list`: 모든 설정 조회
319
355
  - `clear`: .ps-cli.json 파일 삭제
320
356
 
357
+ **옵션:**
358
+
359
+ - `--help`, `-h`: 도움말 표시
360
+
321
361
  **예제:**
322
362
 
323
363
  ```bash
@@ -343,10 +383,13 @@ ps config clear # .ps-cli.json 파일 삭제
343
383
  - `default-language`: 기본 언어 (python, javascript, typescript, cpp)
344
384
  - `editor`: 에디터 명령어 (code, cursor, vim 등)
345
385
  - `auto-open-editor`: fetch 후 자동으로 에디터 열기 (true/false)
386
+ - `include-tag`: README에 알고리즘 분류(태그) 포함 여부 (true/false, 기본값: true)
346
387
  - `solved-ac-handle`: Solved.ac 핸들
347
388
  - `archive-dir`: 아카이브된 문제 디렉토리 (기본값: problems)
348
389
  - `solving-dir`: 푸는 중인 문제 디렉토리 (기본값: solving)
349
390
  - `archive-strategy`: 아카이빙 전략
391
+ - `archive-auto-commit`: archive 실행 시 Git 커밋 자동 실행 여부 (true/false, 기본값: true)
392
+ - `archive-commit-message`: archive 시 사용할 Git 커밋 메시지 템플릿 (`{id}`, `{title}` 사용 가능, 기본값: `solve: {id} - {title}`)
350
393
 
351
394
  ### 아카이빙 전략
352
395
 
@@ -416,7 +459,7 @@ node dist/index.js init
416
459
  ```bash
417
460
  # 프로젝트 디렉토리에서
418
461
  bun run build
419
- npm link
462
+ bun link
420
463
 
421
464
  # 외부 폴더에서 테스트
422
465
  cd /path/to/test-project
@@ -0,0 +1,210 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ Command,
4
+ CommandBuilder,
5
+ CommandDef,
6
+ __decorateClass,
7
+ findProjectRoot,
8
+ getArchiveAutoCommit,
9
+ getArchiveCommitMessage,
10
+ getArchiveDirPath,
11
+ getSolvingDir,
12
+ getSolvingDirPath,
13
+ logger,
14
+ resolveProblemContext
15
+ } from "./chunk-JZK2464U.js";
16
+
17
+ // src/commands/archive.tsx
18
+ import { StatusMessage, Alert, Spinner } from "@inkjs/ui";
19
+ import { Box } from "ink";
20
+
21
+ // src/hooks/use-archive.ts
22
+ import { access, readFile, rename, mkdir, readdir, rmdir } from "fs/promises";
23
+ import { join, dirname } from "path";
24
+ import { execa } from "execa";
25
+ import { useEffect, useState } from "react";
26
+ function useArchive({
27
+ problemId,
28
+ onComplete
29
+ }) {
30
+ const [status, setStatus] = useState(
31
+ "loading"
32
+ );
33
+ const [message, setMessage] = useState("\uBB38\uC81C\uB97C \uC544\uCE74\uC774\uBE0C\uD558\uB294 \uC911...");
34
+ const [error, setError] = useState(null);
35
+ useEffect(() => {
36
+ async function archive() {
37
+ try {
38
+ const projectRoot = findProjectRoot();
39
+ if (!projectRoot) {
40
+ throw new Error("\uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
41
+ }
42
+ const solvingDir = getSolvingDirPath(problemId, projectRoot);
43
+ setMessage("solving \uB514\uB809\uD1A0\uB9AC\uC5D0\uC11C \uBB38\uC81C\uB97C \uD655\uC778\uD558\uB294 \uC911...");
44
+ try {
45
+ await access(solvingDir);
46
+ } catch {
47
+ throw new Error(
48
+ `solving \uB514\uB809\uD1A0\uB9AC\uC5D0 \uBB38\uC81C ${problemId}\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`
49
+ );
50
+ }
51
+ setMessage("\uBB38\uC81C \uC815\uBCF4\uB97C \uC77D\uB294 \uC911...");
52
+ const metaPath = join(solvingDir, "meta.json");
53
+ let problemTitle = `\uBB38\uC81C ${problemId}`;
54
+ let problem;
55
+ try {
56
+ const metaContent = await readFile(metaPath, "utf-8");
57
+ const meta = JSON.parse(metaContent);
58
+ if (meta.title) {
59
+ problemTitle = meta.title;
60
+ }
61
+ if (meta.id && meta.level !== void 0) {
62
+ problem = {
63
+ id: meta.id,
64
+ title: meta.title || `\uBB38\uC81C ${meta.id}`,
65
+ level: meta.level,
66
+ tier: "",
67
+ // tier는 level에서 계산되므로 빈 문자열로 충분
68
+ tags: meta.tags || [],
69
+ testCases: []
70
+ };
71
+ }
72
+ } catch {
73
+ }
74
+ const archiveDir = getArchiveDirPath(problemId, projectRoot, problem);
75
+ try {
76
+ await access(archiveDir);
77
+ throw new Error(
78
+ `archive \uB514\uB809\uD1A0\uB9AC\uC5D0 \uC774\uBBF8 \uBB38\uC81C ${problemId}\uAC00 \uC874\uC7AC\uD569\uB2C8\uB2E4.`
79
+ );
80
+ } catch (err) {
81
+ if (err instanceof Error && err.message.includes("\uC774\uBBF8")) {
82
+ throw err;
83
+ }
84
+ }
85
+ const autoCommit = getArchiveAutoCommit();
86
+ const template = getArchiveCommitMessage() ?? "solve: {id} - {title}";
87
+ const commitMessage = template.replace("{id}", String(problemId)).replace("{title}", problemTitle);
88
+ const archiveDirParent = dirname(archiveDir);
89
+ setMessage("\uC544\uCE74\uC774\uBE0C \uB514\uB809\uD1A0\uB9AC\uB97C \uC900\uBE44\uD558\uB294 \uC911...");
90
+ await mkdir(archiveDirParent, { recursive: true });
91
+ setMessage("\uBB38\uC81C\uB97C archive \uB514\uB809\uD1A0\uB9AC\uB85C \uC774\uB3D9\uD558\uB294 \uC911...");
92
+ await rename(solvingDir, archiveDir);
93
+ if (autoCommit) {
94
+ try {
95
+ setMessage("Git \uCEE4\uBC0B\uC744 \uC2E4\uD589\uD558\uB294 \uC911...");
96
+ await execa("git", ["add", archiveDir], { cwd: projectRoot });
97
+ await execa("git", ["commit", "-m", commitMessage], {
98
+ cwd: projectRoot
99
+ });
100
+ } catch (gitError) {
101
+ setMessage("\uCEE4\uBC0B \uC2E4\uD328\uB85C \uC778\uD574 \uB864\uBC31\uD558\uB294 \uC911...");
102
+ try {
103
+ await rename(archiveDir, solvingDir);
104
+ } catch (rollbackError) {
105
+ throw new Error(
106
+ `Git \uCEE4\uBC0B \uC2E4\uD328 \uBC0F \uB864\uBC31 \uC2E4\uD328: ${gitError instanceof Error ? gitError.message : String(gitError)}. \uB864\uBC31 \uC5D0\uB7EC: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}. \uC218\uB3D9\uC73C\uB85C \uD30C\uC77C\uC744 \uD655\uC778\uD574\uC8FC\uC138\uC694.`
107
+ );
108
+ }
109
+ throw gitError;
110
+ }
111
+ }
112
+ setMessage("\uBE48 \uB514\uB809\uD1A0\uB9AC \uC815\uB9AC \uC911...");
113
+ try {
114
+ const solvingDirConfig = getSolvingDir();
115
+ if (solvingDirConfig && solvingDirConfig !== "." && solvingDirConfig !== "") {
116
+ let currentParentDir = dirname(solvingDir);
117
+ const solvingBaseDir = join(projectRoot, solvingDirConfig);
118
+ while (currentParentDir !== solvingBaseDir && currentParentDir !== projectRoot) {
119
+ try {
120
+ const entries = await readdir(currentParentDir);
121
+ if (entries.length === 0) {
122
+ await rmdir(currentParentDir);
123
+ currentParentDir = dirname(currentParentDir);
124
+ } else {
125
+ break;
126
+ }
127
+ } catch {
128
+ break;
129
+ }
130
+ }
131
+ }
132
+ } catch {
133
+ }
134
+ setStatus("success");
135
+ setMessage(`\uBB38\uC81C ${problemId}\uB97C \uC544\uCE74\uC774\uBE0C\uD588\uC2B5\uB2C8\uB2E4: ${archiveDir}`);
136
+ setTimeout(() => {
137
+ onComplete?.();
138
+ }, 2e3);
139
+ } catch (err) {
140
+ setStatus("error");
141
+ setError(err instanceof Error ? err.message : String(err));
142
+ setTimeout(() => {
143
+ onComplete?.();
144
+ }, 2e3);
145
+ }
146
+ }
147
+ void archive();
148
+ }, [problemId, onComplete]);
149
+ return {
150
+ status,
151
+ message,
152
+ error
153
+ };
154
+ }
155
+
156
+ // src/commands/archive.tsx
157
+ import { jsx, jsxs } from "react/jsx-runtime";
158
+ function ArchiveView({ problemId, onComplete }) {
159
+ const { status, message, error } = useArchive({
160
+ problemId,
161
+ onComplete
162
+ });
163
+ if (status === "loading") {
164
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: /* @__PURE__ */ jsx(Spinner, { label: message }) });
165
+ }
166
+ if (status === "error") {
167
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: /* @__PURE__ */ jsxs(Alert, { variant: "error", children: [
168
+ "\uC624\uB958 \uBC1C\uC0DD: ",
169
+ error
170
+ ] }) });
171
+ }
172
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", width: "100%", children: /* @__PURE__ */ jsx(StatusMessage, { variant: "success", children: message }) });
173
+ }
174
+ var ArchiveCommand = class extends Command {
175
+ async execute(args, _) {
176
+ const context = await resolveProblemContext(args, { requireId: false });
177
+ if (context.problemId === null) {
178
+ logger.error("\uBB38\uC81C \uBC88\uD638\uB97C \uC785\uB825\uD574\uC8FC\uC138\uC694.");
179
+ console.log(`\uB3C4\uC6C0\uB9D0: ps archive --help`);
180
+ process.exit(1);
181
+ return;
182
+ }
183
+ await this.renderView(ArchiveView, {
184
+ problemId: context.problemId
185
+ });
186
+ }
187
+ };
188
+ ArchiveCommand = __decorateClass([
189
+ CommandDef({
190
+ name: "archive",
191
+ description: `\uBB38\uC81C\uB97C \uC544\uCE74\uC774\uBE0C\uD558\uACE0 Git \uCEE4\uBC0B\uC744 \uC0DD\uC131\uD569\uB2C8\uB2E4.
192
+ - solving \uB514\uB809\uD1A0\uB9AC\uC5D0\uC11C \uBB38\uC81C\uB97C \uCC3E\uC544 archive \uB514\uB809\uD1A0\uB9AC\uB85C \uC774\uB3D9
193
+ - Git add \uBC0F commit \uC2E4\uD589 (\uCEE4\uBC0B \uBA54\uC2DC\uC9C0: "solve: {\uBB38\uC81C\uBC88\uD638} - {\uBB38\uC81C\uC774\uB984}")
194
+ - \uC544\uCE74\uC774\uBE0C \uACBD\uB85C, \uC804\uB7B5, \uC790\uB3D9 \uCEE4\uBC0B \uC5EC\uBD80 \uB4F1\uC740 ps config\uC5D0\uC11C \uC124\uC815 \uAC00\uB2A5\uD569\uB2C8\uB2E4.`,
195
+ flags: [],
196
+ autoDetectProblemId: true,
197
+ requireProblemId: false,
198
+ examples: [
199
+ "archive 1000",
200
+ "archive # \uD604\uC7AC \uB514\uB809\uD1A0\uB9AC\uC5D0\uC11C \uBB38\uC81C \uBC88\uD638 \uC790\uB3D9 \uAC10\uC9C0"
201
+ ]
202
+ })
203
+ ], ArchiveCommand);
204
+ var archive_default = CommandBuilder.fromClass(ArchiveCommand);
205
+
206
+ export {
207
+ ArchiveView,
208
+ ArchiveCommand,
209
+ archive_default
210
+ };
@@ -0,0 +1,251 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ openBrowser
4
+ } from "./chunk-QGMWUOJ3.js";
5
+ import {
6
+ Command,
7
+ CommandBuilder,
8
+ CommandDef,
9
+ __decorateClass,
10
+ defineFlags,
11
+ detectProblemIdFromPath,
12
+ findSolutionFile,
13
+ getSupportedLanguagesString,
14
+ resolveLanguage,
15
+ resolveProblemContext
16
+ } from "./chunk-JZK2464U.js";
17
+
18
+ // src/commands/submit.tsx
19
+ import { Badge, StatusMessage, Alert, Spinner } from "@inkjs/ui";
20
+ import { Text, Box } from "ink";
21
+
22
+ // src/hooks/use-submit.ts
23
+ import { readFile } from "fs/promises";
24
+ import { useEffect, useState } from "react";
25
+
26
+ // src/utils/clipboard.ts
27
+ import { execa, execaCommand } from "execa";
28
+ async function copyToClipboard(text) {
29
+ try {
30
+ if (process.platform === "win32") {
31
+ await execaCommand("clip", {
32
+ shell: true,
33
+ input: text
34
+ });
35
+ } else if (process.platform === "darwin") {
36
+ await execaCommand("pbcopy", {
37
+ shell: false,
38
+ input: text
39
+ });
40
+ } else {
41
+ try {
42
+ await execa("xclip", ["-selection", "clipboard"], {
43
+ input: text
44
+ });
45
+ } catch {
46
+ try {
47
+ await execa("xsel", ["--clipboard", "--input"], {
48
+ input: text
49
+ });
50
+ } catch {
51
+ return false;
52
+ }
53
+ }
54
+ }
55
+ return true;
56
+ } catch {
57
+ return false;
58
+ }
59
+ }
60
+
61
+ // src/hooks/use-submit.ts
62
+ var BOJ_BASE_URL = "https://www.acmicpc.net";
63
+ function useSubmit({
64
+ problemId,
65
+ language: _language,
66
+ sourcePath,
67
+ onComplete
68
+ }) {
69
+ const [status, setStatus] = useState(
70
+ "loading"
71
+ );
72
+ const [message, setMessage] = useState("\uC81C\uCD9C \uC900\uBE44 \uC911...");
73
+ const [error, setError] = useState(null);
74
+ const [submitUrl, setSubmitUrl] = useState("");
75
+ const [clipboardSuccess, setClipboardSuccess] = useState(false);
76
+ const [clipboardError, setClipboardError] = useState(null);
77
+ useEffect(() => {
78
+ async function submit() {
79
+ try {
80
+ setMessage("\uC18C\uC2A4 \uCF54\uB4DC\uB97C \uC77D\uB294 \uC911...");
81
+ const sourceCode = await readFile(sourcePath, "utf-8");
82
+ setMessage("\uD074\uB9BD\uBCF4\uB4DC\uC5D0 \uBCF5\uC0AC\uD558\uB294 \uC911...");
83
+ const clipboardResult = await copyToClipboard(sourceCode);
84
+ setClipboardSuccess(clipboardResult);
85
+ if (!clipboardResult) {
86
+ setClipboardError("\uD074\uB9BD\uBCF4\uB4DC \uBCF5\uC0AC\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4.");
87
+ }
88
+ const url = `${BOJ_BASE_URL}/submit/${problemId}`;
89
+ setSubmitUrl(url);
90
+ setMessage("\uBE0C\uB77C\uC6B0\uC800\uB97C \uC5EC\uB294 \uC911...");
91
+ await openBrowser(url);
92
+ setStatus("success");
93
+ setTimeout(() => {
94
+ onComplete();
95
+ }, 2e3);
96
+ } catch (err) {
97
+ setStatus("error");
98
+ setError(err instanceof Error ? err.message : String(err));
99
+ setTimeout(() => {
100
+ onComplete();
101
+ }, 2e3);
102
+ }
103
+ }
104
+ void submit();
105
+ }, [problemId, sourcePath, onComplete]);
106
+ return {
107
+ status,
108
+ message,
109
+ error,
110
+ submitUrl,
111
+ clipboardSuccess,
112
+ clipboardError
113
+ };
114
+ }
115
+
116
+ // src/commands/submit.tsx
117
+ import { jsx, jsxs } from "react/jsx-runtime";
118
+ var submitFlagsSchema = {
119
+ language: {
120
+ type: "string",
121
+ shortFlag: "l",
122
+ description: `\uC5B8\uC5B4 \uC120\uD0DD (\uC9C0\uC815 \uC2DC \uC790\uB3D9 \uAC10\uC9C0 \uBB34\uC2DC)
123
+ \uC9C0\uC6D0 \uC5B8\uC5B4: ${getSupportedLanguagesString()}`
124
+ }
125
+ };
126
+ function SubmitView({
127
+ problemId,
128
+ language,
129
+ sourcePath,
130
+ onComplete
131
+ }) {
132
+ const {
133
+ status,
134
+ message,
135
+ error,
136
+ submitUrl,
137
+ clipboardSuccess,
138
+ clipboardError
139
+ } = useSubmit({
140
+ problemId,
141
+ language,
142
+ sourcePath,
143
+ onComplete
144
+ });
145
+ if (status === "loading") {
146
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
147
+ /* @__PURE__ */ jsx(Spinner, { label: message }),
148
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
149
+ "\uBB38\uC81C #",
150
+ problemId
151
+ ] }) })
152
+ ] });
153
+ }
154
+ if (status === "error") {
155
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
156
+ /* @__PURE__ */ jsxs(Alert, { variant: "error", children: [
157
+ "\uC624\uB958 \uBC1C\uC0DD: ",
158
+ error
159
+ ] }),
160
+ submitUrl && /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
161
+ "URL: ",
162
+ submitUrl
163
+ ] }) })
164
+ ] });
165
+ }
166
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", width: "100%", children: [
167
+ /* @__PURE__ */ jsx(StatusMessage, { variant: "success", children: "\uC81C\uCD9C \uD398\uC774\uC9C0\uB97C \uC5F4\uC5C8\uC2B5\uB2C8\uB2E4!" }),
168
+ /* @__PURE__ */ jsxs(
169
+ Box,
170
+ {
171
+ flexDirection: "column",
172
+ borderStyle: "round",
173
+ borderColor: "gray",
174
+ marginTop: 1,
175
+ paddingX: 1,
176
+ paddingY: 0,
177
+ alignSelf: "flex-start",
178
+ children: [
179
+ /* @__PURE__ */ jsxs(Text, { children: [
180
+ /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "\uBB38\uC81C \uBC88\uD638:" }),
181
+ " ",
182
+ problemId
183
+ ] }),
184
+ /* @__PURE__ */ jsxs(Text, { children: [
185
+ /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "\uC5B8\uC5B4:" }),
186
+ " ",
187
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: language })
188
+ ] }),
189
+ /* @__PURE__ */ jsxs(Text, { children: [
190
+ /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "URL:" }),
191
+ " ",
192
+ /* @__PURE__ */ jsx(Text, { color: "blue", underline: true, children: submitUrl })
193
+ ] }),
194
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: clipboardSuccess ? /* @__PURE__ */ jsx(Badge, { color: "green", children: "\uD074\uB9BD\uBCF4\uB4DC\uC5D0 \uBCF5\uC0AC\uB428" }) : /* @__PURE__ */ jsx(Badge, { color: "yellow", children: "\uD074\uB9BD\uBCF4\uB4DC \uBCF5\uC0AC \uC2E4\uD328" }) }),
195
+ clipboardError && !clipboardSuccess && /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Alert, { variant: "warning", children: clipboardError }) })
196
+ ]
197
+ }
198
+ )
199
+ ] });
200
+ }
201
+ var SubmitCommand = class extends Command {
202
+ async execute(args, flags) {
203
+ const context = await resolveProblemContext(args, { requireId: true });
204
+ const sourcePath = await findSolutionFile(context.archiveDir);
205
+ const detectedLanguage = await resolveLanguage(
206
+ context.archiveDir,
207
+ flags.language
208
+ );
209
+ let finalProblemId = context.problemId;
210
+ if (finalProblemId === null) {
211
+ finalProblemId = detectProblemIdFromPath(context.archiveDir);
212
+ }
213
+ if (finalProblemId === null) {
214
+ throw new Error(
215
+ "\uBB38\uC81C \uBC88\uD638\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uBB38\uC81C \uBC88\uD638\uB97C \uC778\uC790\uB85C \uC804\uB2EC\uD558\uAC70\uB098 \uBB38\uC81C \uB514\uB809\uD1A0\uB9AC\uC5D0\uC11C \uC2E4\uD589\uD574\uC8FC\uC138\uC694."
216
+ );
217
+ }
218
+ await this.renderView(SubmitView, {
219
+ problemId: finalProblemId,
220
+ language: detectedLanguage,
221
+ sourcePath
222
+ });
223
+ }
224
+ };
225
+ SubmitCommand = __decorateClass([
226
+ CommandDef({
227
+ name: "submit",
228
+ description: `\uBC31\uC900 \uC81C\uCD9C \uD398\uC774\uC9C0\uB97C \uBE0C\uB77C\uC6B0\uC800\uB85C \uC5F4\uACE0 \uC18C\uC2A4 \uCF54\uB4DC\uB97C \uD074\uB9BD\uBCF4\uB4DC\uC5D0 \uBCF5\uC0AC\uD569\uB2C8\uB2E4.
229
+ - \uBB38\uC81C \uBC88\uD638\uB97C \uC778\uC790\uB85C \uC804\uB2EC\uD558\uAC70\uB098 \uBB38\uC81C \uB514\uB809\uD1A0\uB9AC\uC5D0\uC11C \uC2E4\uD589\uD558\uBA74 \uC790\uB3D9\uC73C\uB85C \uBB38\uC81C \uBC88\uD638\uB97C \uCD94\uB860
230
+ - solution.* \uD30C\uC77C\uC744 \uC790\uB3D9\uC73C\uB85C \uCC3E\uC544 \uC5B8\uC5B4 \uAC10\uC9C0
231
+ - \uC18C\uC2A4 \uCF54\uB4DC\uB97C \uD074\uB9BD\uBCF4\uB4DC\uC5D0 \uC790\uB3D9 \uBCF5\uC0AC
232
+ - \uC81C\uCD9C \uD398\uC774\uC9C0\uB97C \uBE0C\uB77C\uC6B0\uC800\uB85C \uC790\uB3D9 \uC5F4\uAE30
233
+ - \uAE30\uBCF8 \uC5B8\uC5B4 \uB4F1\uC740 ps config\uC5D0\uC11C \uC124\uC815 \uAC00\uB2A5\uD569\uB2C8\uB2E4.`,
234
+ flags: defineFlags(submitFlagsSchema),
235
+ autoDetectProblemId: true,
236
+ autoDetectLanguage: true,
237
+ requireProblemId: true,
238
+ examples: [
239
+ "submit # \uD604\uC7AC \uB514\uB809\uD1A0\uB9AC\uC5D0\uC11C \uC81C\uCD9C",
240
+ "submit 1000 # 1000\uBC88 \uBB38\uC81C \uC81C\uCD9C",
241
+ "submit --language python # Python\uC73C\uB85C \uC81C\uCD9C"
242
+ ]
243
+ })
244
+ ], SubmitCommand);
245
+ var submit_default = CommandBuilder.fromClass(SubmitCommand);
246
+
247
+ export {
248
+ SubmitView,
249
+ SubmitCommand,
250
+ submit_default
251
+ };