@rhseung/ps-cli 1.3.2 → 1.3.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.
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  getProblemDir
4
- } from "./chunk-CIG2LEJC.js";
4
+ } from "./chunk-PNIGP6LX.js";
5
5
 
6
6
  // src/utils/problem-id.ts
7
7
  import { join, dirname } from "path";
@@ -9924,13 +9924,6 @@ function getProjectConfigSync() {
9924
9924
  return null;
9925
9925
  }
9926
9926
  }
9927
- function getBojSessionCookie() {
9928
- const envCookie = process.env.PS_CLI_BOJ_COOKIE;
9929
- if (envCookie) {
9930
- return envCookie;
9931
- }
9932
- return config.get("bojSessionCookie");
9933
- }
9934
9927
  function getDefaultLanguage() {
9935
9928
  const projectConfig = getProjectConfigSync();
9936
9929
  if (projectConfig?.defaultLanguage) {
@@ -9938,9 +9931,6 @@ function getDefaultLanguage() {
9938
9931
  }
9939
9932
  return config.get("defaultLanguage") ?? "python";
9940
9933
  }
9941
- function getCodeOpen() {
9942
- return config.get("codeOpen") ?? false;
9943
- }
9944
9934
  function getEditor() {
9945
9935
  const projectConfig = getProjectConfigSync();
9946
9936
  if (projectConfig?.editor) {
@@ -9971,9 +9961,7 @@ function getProblemDir() {
9971
9961
  }
9972
9962
 
9973
9963
  export {
9974
- getBojSessionCookie,
9975
9964
  getDefaultLanguage,
9976
- getCodeOpen,
9977
9965
  getEditor,
9978
9966
  getAutoOpenEditor,
9979
9967
  getSolvedAcHandle,
@@ -9,7 +9,7 @@ import {
9
9
  getEditor,
10
10
  getProblemDir,
11
11
  getSolvedAcHandle
12
- } from "../chunk-CIG2LEJC.js";
12
+ } from "../chunk-PNIGP6LX.js";
13
13
  import "../chunk-FYS2JH42.js";
14
14
 
15
15
  // src/commands/config.tsx
@@ -14,11 +14,11 @@ import {
14
14
  import {
15
15
  getProblemDirPath,
16
16
  getProblemId
17
- } from "../chunk-MOPDUECU.js";
17
+ } from "../chunk-6ENX5K3C.js";
18
18
  import {
19
19
  getAutoOpenEditor,
20
20
  getEditor
21
- } from "../chunk-CIG2LEJC.js";
21
+ } from "../chunk-PNIGP6LX.js";
22
22
  import "../chunk-FYS2JH42.js";
23
23
 
24
24
  // src/commands/fetch.tsx
@@ -8,7 +8,7 @@ import {
8
8
  getEditor,
9
9
  getProblemDir,
10
10
  getSolvedAcHandle
11
- } from "../chunk-CIG2LEJC.js";
11
+ } from "../chunk-PNIGP6LX.js";
12
12
  import "../chunk-FYS2JH42.js";
13
13
 
14
14
  // src/commands/init.tsx
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  getProblemId
4
- } from "../chunk-MOPDUECU.js";
5
- import "../chunk-CIG2LEJC.js";
4
+ } from "../chunk-6ENX5K3C.js";
5
+ import "../chunk-PNIGP6LX.js";
6
6
  import "../chunk-FYS2JH42.js";
7
7
 
8
8
  // src/commands/open.tsx
@@ -11,8 +11,8 @@ import {
11
11
  detectProblemIdFromPath,
12
12
  getProblemDirPath,
13
13
  getProblemId
14
- } from "../chunk-MOPDUECU.js";
15
- import "../chunk-CIG2LEJC.js";
14
+ } from "../chunk-6ENX5K3C.js";
15
+ import "../chunk-PNIGP6LX.js";
16
16
  import "../chunk-FYS2JH42.js";
17
17
 
18
18
  // src/commands/run.tsx
@@ -7,7 +7,7 @@ import {
7
7
  } from "../chunk-2E4VSP6O.js";
8
8
  import {
9
9
  getSolvedAcHandle
10
- } from "../chunk-CIG2LEJC.js";
10
+ } from "../chunk-PNIGP6LX.js";
11
11
  import "../chunk-FYS2JH42.js";
12
12
 
13
13
  // src/commands/stats.tsx
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  detectLanguageFromFile,
4
- getLanguageConfig,
5
4
  getSupportedLanguages,
6
5
  getSupportedLanguagesString
7
6
  } from "../chunk-TQXMB7XV.js";
@@ -9,11 +8,8 @@ import {
9
8
  detectProblemIdFromPath,
10
9
  getProblemDirPath,
11
10
  getProblemId
12
- } from "../chunk-MOPDUECU.js";
13
- import {
14
- getBojSessionCookie,
15
- getCodeOpen
16
- } from "../chunk-CIG2LEJC.js";
11
+ } from "../chunk-6ENX5K3C.js";
12
+ import "../chunk-PNIGP6LX.js";
17
13
  import "../chunk-FYS2JH42.js";
18
14
 
19
15
  // src/commands/submit.tsx
@@ -23,364 +19,157 @@ import { Badge, StatusMessage, Alert } from "@inkjs/ui";
23
19
  import { readdir } from "fs/promises";
24
20
  import { join } from "path";
25
21
  import { readFile } from "fs/promises";
22
+ import { Spinner } from "@inkjs/ui";
23
+ import { execaCommand as execaCommand2 } from "execa";
26
24
 
27
- // src/services/submitter.ts
28
- import * as cheerio from "cheerio";
29
- var BOJ_BASE_URL = "https://www.acmicpc.net";
30
- async function pollSubmitResult(problemId, submitId, sessionCookie) {
31
- const maxAttempts = 60;
32
- const pollInterval = 2e3;
33
- for (let attempt = 0; attempt < maxAttempts; attempt++) {
34
- await new Promise((resolve) => setTimeout(resolve, pollInterval));
35
- try {
36
- const response = await fetch(
37
- `${BOJ_BASE_URL}/status?from_mine=1&problem_id=${problemId}`,
38
- {
39
- headers: {
40
- Cookie: sessionCookie,
41
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
42
- }
43
- }
44
- );
45
- if (!response.ok) {
46
- continue;
47
- }
48
- const html = await response.text();
49
- const $ = cheerio.load(html);
50
- let targetRow = null;
51
- $("table#status-table tbody tr").each((_, element) => {
52
- const row = $(element);
53
- const rowSubmitId = parseInt(
54
- row.find("td:first-child").text().trim(),
55
- 10
56
- );
57
- if (rowSubmitId === submitId) {
58
- targetRow = row;
25
+ // src/utils/clipboard.ts
26
+ import { execa, execaCommand } from "execa";
27
+ async function copyToClipboard(text) {
28
+ try {
29
+ if (process.platform === "win32") {
30
+ await execaCommand("clip", {
31
+ shell: true,
32
+ input: text
33
+ });
34
+ } else if (process.platform === "darwin") {
35
+ await execaCommand("pbcopy", {
36
+ shell: false,
37
+ input: text
38
+ });
39
+ } else {
40
+ try {
41
+ await execa("xclip", ["-selection", "clipboard"], {
42
+ input: text
43
+ });
44
+ } catch {
45
+ try {
46
+ await execa("xsel", ["--clipboard", "--input"], {
47
+ input: text
48
+ });
49
+ } catch {
59
50
  return false;
60
51
  }
61
- });
62
- if (!targetRow) {
63
- targetRow = $("table#status-table tbody tr").first();
64
- }
65
- if (!targetRow || targetRow.length === 0) {
66
- continue;
67
- }
68
- const statusText = targetRow.find("td.result").text().trim();
69
- const status = parseSubmitStatus(statusText);
70
- if (status !== "WAITING" && status !== "JUDGING") {
71
- const timeText = targetRow.find("td.time").text().trim();
72
- const memoryText = targetRow.find("td.memory").text().trim();
73
- const languageText = targetRow.find("td.language").text().trim();
74
- const time = parseTime(timeText);
75
- const memory = parseMemory(memoryText);
76
- return {
77
- problemId,
78
- submitId: parseInt(
79
- targetRow.find("td:first-child").text().trim(),
80
- 10
81
- ),
82
- status,
83
- time,
84
- memory,
85
- submittedAt: /* @__PURE__ */ new Date(),
86
- language: languageText
87
- };
88
- }
89
- } catch (error) {
90
- continue;
91
- }
92
- }
93
- throw new Error(
94
- "\uC81C\uCD9C \uACB0\uACFC \uD655\uC778 \uC2DC\uAC04\uC774 \uCD08\uACFC\uB418\uC5C8\uC2B5\uB2C8\uB2E4. BOJ \uC6F9\uC0AC\uC774\uD2B8\uC5D0\uC11C \uC9C1\uC811 \uD655\uC778\uD574\uC8FC\uC138\uC694."
95
- );
96
- }
97
- function parseSubmitStatus(statusText) {
98
- const upper = statusText.toUpperCase();
99
- if (upper.includes("ACCEPTED") || upper.includes("\uB9DE\uC558\uC2B5\uB2C8\uB2E4")) {
100
- return "AC";
101
- }
102
- if (upper.includes("WRONG ANSWER") || upper.includes("\uD2C0\uB838\uC2B5\uB2C8\uB2E4")) {
103
- return "WA";
104
- }
105
- if (upper.includes("TIME LIMIT") || upper.includes("\uC2DC\uAC04 \uCD08\uACFC")) {
106
- return "TLE";
107
- }
108
- if (upper.includes("MEMORY LIMIT") || upper.includes("\uBA54\uBAA8\uB9AC \uCD08\uACFC")) {
109
- return "MLE";
110
- }
111
- if (upper.includes("RUNTIME ERROR") || upper.includes("\uB7F0\uD0C0\uC784 \uC5D0\uB7EC")) {
112
- return "RE";
113
- }
114
- if (upper.includes("COMPILE ERROR") || upper.includes("\uCEF4\uD30C\uC77C \uC5D0\uB7EC")) {
115
- return "CE";
116
- }
117
- if (upper.includes("OUTPUT LIMIT") || upper.includes("\uCD9C\uB825 \uCD08\uACFC")) {
118
- return "OLE";
119
- }
120
- if (upper.includes("PRESENTATION ERROR") || upper.includes("\uCD9C\uB825 \uD615\uC2DD")) {
121
- return "PE";
122
- }
123
- if (upper.includes("WAITING") || upper.includes("\uAE30\uB2E4\uB9AC\uB294 \uC911")) {
124
- return "WAITING";
125
- }
126
- if (upper.includes("JUDGING") || upper.includes("\uCC44\uC810 \uC911")) {
127
- return "JUDGING";
128
- }
129
- return "WAITING";
130
- }
131
- function parseTime(timeText) {
132
- const match = timeText.match(/(\d+)\s*ms/);
133
- if (match) {
134
- return parseInt(match[1], 10);
135
- }
136
- return null;
137
- }
138
- function parseMemory(memoryText) {
139
- const match = memoryText.match(/(\d+)\s*KB/);
140
- if (match) {
141
- return parseInt(match[1], 10);
142
- }
143
- return null;
144
- }
145
- async function getCsrfToken(problemId, sessionCookie) {
146
- const response = await fetch(`${BOJ_BASE_URL}/submit/${problemId}`, {
147
- headers: {
148
- Cookie: sessionCookie,
149
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
150
- }
151
- });
152
- if (!response.ok) {
153
- throw new Error(
154
- `\uC81C\uCD9C \uD398\uC774\uC9C0\uB97C \uBD88\uB7EC\uC62C \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. (HTTP ${response.status})`
155
- );
156
- }
157
- const html = await response.text();
158
- const $ = cheerio.load(html);
159
- let csrfToken;
160
- csrfToken = $('input[name="csrf_key"]').val()?.toString() || $('input[name="_csrf"]').val()?.toString() || $('input[name="csrf"]').val()?.toString() || $('input[type="hidden"][name*="csrf"]').val()?.toString();
161
- if (!csrfToken) {
162
- csrfToken = $('meta[name="csrf-token"]').attr("content") || $('meta[name="_csrf"]').attr("content") || $('meta[name="csrf"]').attr("content");
163
- }
164
- if (!csrfToken) {
165
- const scriptTags = $("script").toArray();
166
- for (const script of scriptTags) {
167
- const scriptContent = $(script).html() || "";
168
- const match = scriptContent.match(/csrf[_-]?key["\s:=]+["']([^"']+)["']/) || scriptContent.match(/csrf[_-]?token["\s:=]+["']([^"']+)["']/) || scriptContent.match(/window\.csrf[_-]?key\s*=\s*["']([^"']+)["']/);
169
- if (match && match[1]) {
170
- csrfToken = match[1];
171
- break;
172
- }
173
- }
174
- }
175
- if (!csrfToken) {
176
- const formAction = $('form[action*="submit"]').attr("action");
177
- if (formAction) {
178
- const match = formAction.match(/csrf[_-]?key=([^&]+)/);
179
- if (match && match[1]) {
180
- csrfToken = decodeURIComponent(match[1]);
181
52
  }
182
53
  }
183
- }
184
- if (!csrfToken) {
185
- const formHtml = $("form").html()?.substring(0, 500) || "";
186
- throw new Error(
187
- "CSRF \uD1A0\uD070\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. BOJ \uD398\uC774\uC9C0 \uAD6C\uC870\uAC00 \uBCC0\uACBD\uB418\uC5C8\uC744 \uC218 \uC788\uC2B5\uB2C8\uB2E4.\n\uB2E4\uC74C \uC704\uCE58\uB4E4\uC744 \uD655\uC778\uD588\uC2B5\uB2C8\uB2E4:\n- input[name='csrf_key'], input[name='_csrf'], input[name='csrf']\n- meta[name='csrf-token'], meta[name='_csrf']\n- JavaScript \uBCC0\uC218 (csrf_key, csrf_token)\n- \uD3FC action URL\n\n\uC81C\uCD9C \uD398\uC774\uC9C0 HTML \uAD6C\uC870\uB97C \uD655\uC778\uD574\uC8FC\uC138\uC694."
188
- );
189
- }
190
- return String(csrfToken);
191
- }
192
- async function submitSolution({
193
- problemId,
194
- language,
195
- sourceCode,
196
- dryRun = false
197
- }) {
198
- if (dryRun) {
199
- const langConfig2 = getLanguageConfig(language);
200
- if (!langConfig2.bojLangId) {
201
- throw new Error(
202
- `\uC5B8\uC5B4 ${language}\uC5D0 \uB300\uD55C BOJ \uC5B8\uC5B4 ID\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.`
203
- );
204
- }
205
- return {
206
- problemId,
207
- status: "WAITING",
208
- language,
209
- message: `[DRY RUN] \uBB38\uC81C ${problemId}, \uC5B8\uC5B4: ${language} (BOJ ID: ${langConfig2.bojLangId}), \uCF54\uB4DC \uAE38\uC774: ${sourceCode.length}\uC790`
210
- };
211
- }
212
- const sessionCookie = getBojSessionCookie();
213
- if (!sessionCookie) {
214
- throw new Error(
215
- "BOJ \uC138\uC158 \uCFE0\uD0A4\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.\n\n\u{1F4CB} \uCFE0\uD0A4 \uBCF5\uC0AC \uBC29\uBC95:\n1. \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C https://www.acmicpc.net \uC5D0 \uB85C\uADF8\uC778\n2. \uAC1C\uBC1C\uC790 \uB3C4\uAD6C(F12) \uC5F4\uAE30\n3. \uBC29\uBC95 A - Network \uD0ED \uC0AC\uC6A9 (\uCD94\uCC9C):\n - Network \uD0ED \uC5F4\uAE30 \u2192 \uD398\uC774\uC9C0 \uC0C8\uB85C\uACE0\uCE68(F5)\n - \uC544\uBB34 \uC694\uCCAD \uD074\uB9AD \u2192 Headers \uD0ED\n - Request Headers \uC139\uC158\uC5D0\uC11C 'Cookie:' \uAC12\uC744 \uBCF5\uC0AC\n (\uC804\uCCB4 Cookie \uD5E4\uB354 \uAC12 \uBCF5\uC0AC, \uC608: 'OnlineJudge=xxx; __ga=yyy; ...')\n\n4. \uBC29\uBC95 B - Application \uD0ED \uC0AC\uC6A9:\n - Application/\uC800\uC7A5\uC18C \uD0ED \u2192 Cookies \u2192 https://www.acmicpc.net\n - 'OnlineJudge' \uCFE0\uD0A4\uC758 Name\uACFC Value\uB97C \uBCF5\uC0AC\n - \uD615\uC2DD: 'OnlineJudge=\uAC12'\n\n\u{1F4A1} \uD301: Network \uD0ED\uC5D0\uC11C \uBCF5\uC0AC\uD558\uB294 \uAC83\uC774 \uAC00\uC7A5 \uC815\uD655\uD569\uB2C8\uB2E4!\n\n\u2699\uFE0F \uC124\uC815 \uBC29\uBC95:\n export PS_CLI_BOJ_COOKIE='\uBCF5\uC0AC\uD55C_\uCFE0\uD0A4_\uAC12'\n\n\uC608\uC2DC:\n export PS_CLI_BOJ_COOKIE='OnlineJudge=abc123; __ga=xyz789; ...'"
216
- );
217
- }
218
- const langConfig = getLanguageConfig(language);
219
- if (!langConfig.bojLangId) {
220
- throw new Error(
221
- `\uC5B8\uC5B4 ${language}\uC5D0 \uB300\uD55C BOJ \uC5B8\uC5B4 ID\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.`
222
- );
223
- }
224
- let csrfToken;
225
- try {
226
- csrfToken = await getCsrfToken(problemId, sessionCookie);
54
+ return true;
227
55
  } catch (error) {
228
- console.warn("CSRF \uD1A0\uD070\uC744 \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. \uC81C\uCD9C\uC744 \uACC4\uC18D \uC2DC\uB3C4\uD569\uB2C8\uB2E4...");
229
- csrfToken = void 0;
230
- }
231
- const codeOpen = getCodeOpen() ? "open" : "close";
232
- const formData = new URLSearchParams();
233
- formData.append("problem_id", String(problemId));
234
- formData.append("language", String(langConfig.bojLangId));
235
- formData.append("code_open", codeOpen);
236
- formData.append("source", sourceCode);
237
- if (csrfToken) {
238
- formData.append("csrf_key", csrfToken);
239
- }
240
- const submitResponse = await fetch(`${BOJ_BASE_URL}/submit/${problemId}`, {
241
- method: "POST",
242
- headers: {
243
- Cookie: sessionCookie,
244
- "Content-Type": "application/x-www-form-urlencoded",
245
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
246
- Referer: `${BOJ_BASE_URL}/submit/${problemId}`
247
- },
248
- body: formData.toString(),
249
- redirect: "manual"
250
- // 리다이렉트를 수동으로 처리
251
- });
252
- const responseText = await submitResponse.text();
253
- const $response = cheerio.load(responseText);
254
- const errorMessage = $response(".alert-danger, .error, .warning").text().trim();
255
- if (!submitResponse.ok) {
256
- if (submitResponse.status === 401 || submitResponse.status === 403) {
257
- throw new Error(
258
- "\uB85C\uADF8\uC778\uC774 \uD544\uC694\uD558\uAC70\uB098 \uC138\uC158\uC774 \uB9CC\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uCFE0\uD0A4\uB97C \uB2E4\uC2DC \uC124\uC815\uD574\uC8FC\uC138\uC694."
259
- );
260
- }
261
- const error = errorMessage || `HTTP ${submitResponse.status} ${submitResponse.statusText}`;
262
- throw new Error(`\uC81C\uCD9C \uC2E4\uD328: ${error}`);
263
- }
264
- const location = submitResponse.headers.get("location");
265
- let submitId;
266
- if (location) {
267
- const match = location.match(/\/status\/(\d+)/);
268
- if (match) {
269
- submitId = parseInt(match[1], 10);
270
- }
271
- }
272
- if (!submitId) {
273
- if (errorMessage) {
274
- throw new Error(`\uC81C\uCD9C \uC2E4\uD328: ${errorMessage}`);
275
- }
276
- const statusResponse = await fetch(
277
- `${BOJ_BASE_URL}/status?from_mine=1&problem_id=${problemId}`,
278
- {
279
- headers: {
280
- Cookie: sessionCookie,
281
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
282
- }
283
- }
284
- );
285
- if (statusResponse.ok) {
286
- const html = await statusResponse.text();
287
- const $ = cheerio.load(html);
288
- const firstRow = $("table#status-table tbody tr").first();
289
- if (firstRow.length > 0) {
290
- const idText = firstRow.find("td:first-child").text().trim();
291
- submitId = parseInt(idText, 10);
292
- }
293
- }
294
- }
295
- if (!submitId) {
296
- throw new Error(
297
- "\uC81C\uCD9C\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4. \uC81C\uCD9C ID\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.\n\uAC00\uB2A5\uD55C \uC6D0\uC778:\n- CSRF \uD1A0\uD070\uC774 \uD544\uC694\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4\n- \uB85C\uADF8\uC778 \uC138\uC158\uC774 \uB9CC\uB8CC\uB418\uC5C8\uC744 \uC218 \uC788\uC2B5\uB2C8\uB2E4\n- BOJ \uD398\uC774\uC9C0 \uAD6C\uC870\uAC00 \uBCC0\uACBD\uB418\uC5C8\uC744 \uC218 \uC788\uC2B5\uB2C8\uB2E4\n\n\uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uC9C1\uC811 \uC81C\uCD9C\uD574\uBCF4\uC2DC\uACE0, \uBB38\uC81C\uAC00 \uACC4\uC18D\uB418\uBA74 \uC774\uC288\uB97C \uB4F1\uB85D\uD574\uC8FC\uC138\uC694."
298
- );
56
+ return false;
299
57
  }
300
- const result = await pollSubmitResult(problemId, submitId, sessionCookie);
301
- return {
302
- ...result,
303
- problemId,
304
- submitId
305
- };
306
58
  }
307
59
 
308
60
  // src/commands/submit.tsx
309
- import { Spinner } from "@inkjs/ui";
310
61
  import { jsx, jsxs } from "react/jsx-runtime";
62
+ var BOJ_BASE_URL = "https://www.acmicpc.net";
311
63
  function SubmitCommand({
312
64
  problemId,
313
65
  language,
314
66
  sourcePath,
315
- dryRun,
316
67
  onComplete
317
68
  }) {
318
69
  const [status, setStatus] = useState(
319
70
  "loading"
320
71
  );
321
72
  const [message, setMessage] = useState("\uC81C\uCD9C \uC900\uBE44 \uC911...");
322
- const [result, setResult] = useState(null);
323
73
  const [error, setError] = useState(null);
74
+ const [submitUrl, setSubmitUrl] = useState("");
75
+ const [clipboardSuccess, setClipboardSuccess] = useState(false);
76
+ const [clipboardError, setClipboardError] = useState(null);
324
77
  useEffect(() => {
325
78
  async function submit() {
326
79
  try {
327
- setMessage("\uCF54\uB4DC\uB97C \uC77D\uB294 \uC911...");
80
+ setMessage("\uC18C\uC2A4 \uCF54\uB4DC\uB97C \uC77D\uB294 \uC911...");
328
81
  const sourceCode = await readFile(sourcePath, "utf-8");
329
- if (dryRun) {
330
- setMessage("[DRY RUN] \uC81C\uCD9C \uAC80\uC99D \uC911...");
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
+ let command;
92
+ if (process.platform === "win32") {
93
+ command = `start "" "${url}"`;
94
+ } else if (process.platform === "darwin") {
95
+ command = `open "${url}"`;
331
96
  } else {
332
- setMessage("BOJ\uC5D0 \uC81C\uCD9C\uD558\uB294 \uC911...");
97
+ command = `xdg-open "${url}"`;
333
98
  }
334
- const submitResult = await submitSolution({
335
- problemId,
336
- language,
337
- sourceCode,
338
- dryRun
99
+ await execaCommand2(command, {
100
+ shell: true,
101
+ detached: true,
102
+ stdio: "ignore"
339
103
  });
340
- setResult(submitResult);
341
104
  setStatus("success");
342
- setMessage("\uC81C\uCD9C \uC644\uB8CC");
343
105
  setTimeout(() => {
344
106
  onComplete();
345
- }, 3e3);
107
+ }, 2e3);
346
108
  } catch (err) {
347
109
  setStatus("error");
348
110
  setError(err instanceof Error ? err.message : String(err));
349
111
  setTimeout(() => {
350
112
  onComplete();
351
- }, 3e3);
113
+ }, 2e3);
352
114
  }
353
115
  }
354
116
  submit();
355
- }, [problemId, language, sourcePath, dryRun, onComplete]);
117
+ }, [problemId, language, sourcePath, onComplete]);
356
118
  if (status === "loading") {
357
- return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: /* @__PURE__ */ jsx(Spinner, { label: message }) });
119
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
120
+ /* @__PURE__ */ jsx(Spinner, { label: message }),
121
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
122
+ "\uBB38\uC81C #",
123
+ problemId
124
+ ] }) })
125
+ ] });
358
126
  }
359
127
  if (status === "error") {
360
- return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: /* @__PURE__ */ jsxs(Alert, { variant: "error", children: [
361
- "\uC81C\uCD9C \uC2E4\uD328: ",
362
- error
363
- ] }) });
364
- }
365
- if (result) {
366
- const badgeColor = result.status === "AC" ? "green" : result.status === "WA" || result.status === "CE" || result.status === "RE" ? "red" : result.status === "TLE" || result.status === "MLE" ? "yellow" : "blue";
367
- const statusVariant = result.status === "AC" ? "success" : result.status === "WA" || result.status === "CE" || result.status === "RE" ? "error" : result.status === "TLE" || result.status === "MLE" ? "warning" : "info";
368
- const resultDetails = [
369
- `\uBB38\uC81C: ${result.problemId}`,
370
- `\uC5B8\uC5B4: ${result.language}`,
371
- result.time !== null && result.time !== void 0 ? `\uC2DC\uAC04: ${result.time}ms` : null,
372
- result.memory !== null && result.memory !== void 0 ? `\uBA54\uBAA8\uB9AC: ${result.memory}KB` : null
373
- ].filter(Boolean).join(" | ");
374
128
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
375
- /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "\uC81C\uCD9C \uACB0\uACFC" }) }),
376
- /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
377
- /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Badge, { color: badgeColor, children: result.status }) }),
378
- /* @__PURE__ */ jsx(StatusMessage, { variant: statusVariant, children: resultDetails }),
379
- result.message && /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: "gray", children: result.message }) })
380
- ] })
129
+ /* @__PURE__ */ jsxs(Alert, { variant: "error", children: [
130
+ "\uC624\uB958 \uBC1C\uC0DD: ",
131
+ error
132
+ ] }),
133
+ submitUrl && /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
134
+ "URL: ",
135
+ submitUrl
136
+ ] }) })
381
137
  ] });
382
138
  }
383
- return null;
139
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", width: "100%", children: [
140
+ /* @__PURE__ */ jsx(StatusMessage, { variant: "success", children: "\uC81C\uCD9C \uD398\uC774\uC9C0\uB97C \uC5F4\uC5C8\uC2B5\uB2C8\uB2E4!" }),
141
+ /* @__PURE__ */ jsxs(
142
+ Box,
143
+ {
144
+ flexDirection: "column",
145
+ borderStyle: "round",
146
+ borderColor: "gray",
147
+ marginTop: 1,
148
+ paddingX: 1,
149
+ paddingY: 0,
150
+ alignSelf: "flex-start",
151
+ children: [
152
+ /* @__PURE__ */ jsxs(Text, { children: [
153
+ /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "\uBB38\uC81C \uBC88\uD638:" }),
154
+ " ",
155
+ problemId
156
+ ] }),
157
+ /* @__PURE__ */ jsxs(Text, { children: [
158
+ /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "\uC5B8\uC5B4:" }),
159
+ " ",
160
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: language })
161
+ ] }),
162
+ /* @__PURE__ */ jsxs(Text, { children: [
163
+ /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "URL:" }),
164
+ " ",
165
+ /* @__PURE__ */ jsx(Text, { color: "blue", underline: true, children: submitUrl })
166
+ ] }),
167
+ /* @__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" }) }),
168
+ clipboardError && !clipboardSuccess && /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Alert, { variant: "warning", children: clipboardError }) })
169
+ ]
170
+ }
171
+ )
172
+ ] });
384
173
  }
385
174
  async function detectSolutionFile(problemDir) {
386
175
  const files = await readdir(problemDir);
@@ -390,23 +179,38 @@ async function detectSolutionFile(problemDir) {
390
179
  }
391
180
  return join(problemDir, solutionFile);
392
181
  }
393
- async function submitCommand(problemId, language, dryRun = false) {
182
+ async function submitCommand(problemId, language) {
394
183
  const currentPathProblemId = detectProblemIdFromPath(process.cwd());
395
- const problemDir = currentPathProblemId === problemId ? process.cwd() : getProblemDirPath(problemId);
184
+ const problemDir = problemId && currentPathProblemId !== problemId ? getProblemDirPath(problemId) : process.cwd();
396
185
  const sourcePath = await detectSolutionFile(problemDir);
397
186
  const detectedLanguage = language ?? detectLanguageFromFile(sourcePath);
398
187
  if (!detectedLanguage) {
399
188
  throw new Error(`\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uC5B8\uC5B4\uC785\uB2C8\uB2E4: ${sourcePath}`);
400
189
  }
190
+ let finalProblemId = problemId ?? detectProblemIdFromPath(problemDir);
191
+ if (finalProblemId === null) {
192
+ const segments = problemDir.split(/[/\\]/).filter(Boolean);
193
+ const lastSegment = segments[segments.length - 1];
194
+ if (lastSegment) {
195
+ const parsedId = parseInt(lastSegment, 10);
196
+ if (!isNaN(parsedId) && parsedId > 0 && lastSegment === parsedId.toString()) {
197
+ finalProblemId = parsedId;
198
+ }
199
+ }
200
+ }
201
+ if (finalProblemId === null) {
202
+ throw new Error(
203
+ "\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."
204
+ );
205
+ }
401
206
  return new Promise((resolve) => {
402
207
  const { unmount } = render(
403
208
  /* @__PURE__ */ jsx(
404
209
  SubmitCommand,
405
210
  {
406
- problemId,
211
+ problemId: finalProblemId,
407
212
  language: detectedLanguage,
408
213
  sourcePath,
409
- dryRun,
410
214
  onComplete: () => {
411
215
  unmount();
412
216
  resolve();
@@ -421,15 +225,15 @@ var submitHelp = `
421
225
  $ ps submit [\uBB38\uC81C\uBC88\uD638] [\uC635\uC158]
422
226
 
423
227
  \uC124\uBA85:
424
- \uD604\uC7AC \uBB38\uC81C\uC758 \uC194\uB8E8\uC158 \uD30C\uC77C\uC744 BOJ\uC5D0 \uC81C\uCD9C\uD569\uB2C8\uB2E4.
425
- - \uD604\uC7AC \uB514\uB809\uD1A0\uB9AC \uB610\uB294 \uC9C0\uC815\uD55C \uBB38\uC81C \uBC88\uD638\uC758 \uC194\uB8E8\uC158 \uD30C\uC77C \uC81C\uCD9C
228
+ \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
426
230
  - solution.* \uD30C\uC77C\uC744 \uC790\uB3D9\uC73C\uB85C \uCC3E\uC544 \uC5B8\uC5B4 \uAC10\uC9C0
427
- - \uC81C\uCD9C \uD6C4 \uCC44\uC810 \uACB0\uACFC\uB97C \uC790\uB3D9\uC73C\uB85C \uD655\uC778
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
428
233
 
429
234
  \uC635\uC158:
430
235
  --language, -l \uC5B8\uC5B4 \uC120\uD0DD (\uC9C0\uC815 \uC2DC \uC790\uB3D9 \uAC10\uC9C0 \uBB34\uC2DC)
431
236
  \uC9C0\uC6D0 \uC5B8\uC5B4: ${getSupportedLanguagesString()}
432
- --dry-run \uC2E4\uC81C \uC81C\uCD9C \uC5C6\uC774 \uAC80\uC99D\uB9CC \uC218\uD589
433
237
 
434
238
  \uC608\uC81C:
435
239
  $ ps submit # \uD604\uC7AC \uB514\uB809\uD1A0\uB9AC\uC5D0\uC11C \uC81C\uCD9C
@@ -443,15 +247,6 @@ async function submitExecute(args, flags) {
443
247
  return;
444
248
  }
445
249
  const problemId = getProblemId(args);
446
- if (problemId === null) {
447
- console.error("\uC624\uB958: \uBB38\uC81C \uBC88\uD638\uB97C \uC785\uB825\uD574\uC8FC\uC138\uC694.");
448
- console.error(`\uC0AC\uC6A9\uBC95: ps submit [\uBB38\uC81C\uBC88\uD638] [\uC635\uC158]`);
449
- console.error(`\uB3C4\uC6C0\uB9D0: ps submit --help`);
450
- console.error(
451
- `\uD78C\uD2B8: problems/{\uBB38\uC81C\uBC88\uD638} \uB514\uB809\uD1A0\uB9AC\uC5D0\uC11C \uC2E4\uD589\uD558\uBA74 \uC790\uB3D9\uC73C\uB85C \uBB38\uC81C \uBC88\uD638\uB97C \uCD94\uB860\uD569\uB2C8\uB2E4.`
452
- );
453
- process.exit(1);
454
- }
455
250
  const validLanguages = getSupportedLanguages();
456
251
  const language = flags.language;
457
252
  if (flags.language && language && !validLanguages.includes(language)) {
@@ -460,7 +255,7 @@ async function submitExecute(args, flags) {
460
255
  );
461
256
  process.exit(1);
462
257
  }
463
- await submitCommand(problemId, language, Boolean(flags["dry-run"]));
258
+ await submitCommand(problemId, language);
464
259
  }
465
260
  var submitCommandDef = {
466
261
  name: "submit",
@@ -11,8 +11,8 @@ import {
11
11
  detectProblemIdFromPath,
12
12
  getProblemDirPath,
13
13
  getProblemId
14
- } from "../chunk-MOPDUECU.js";
15
- import "../chunk-CIG2LEJC.js";
14
+ } from "../chunk-6ENX5K3C.js";
15
+ import "../chunk-PNIGP6LX.js";
16
16
  import "../chunk-FYS2JH42.js";
17
17
 
18
18
  // src/commands/test.tsx
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rhseung/ps-cli",
3
- "version": "1.3.2",
3
+ "version": "1.3.3",
4
4
  "description": "백준(BOJ) 문제 해결을 위한 통합 CLI 도구",
5
5
  "type": "module",
6
6
  "bin": {