@rhseung/ps-cli 1.3.2 → 1.4.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.
@@ -1,475 +1,256 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- detectLanguageFromFile,
4
- getLanguageConfig,
5
- getSupportedLanguages,
6
- getSupportedLanguagesString
7
- } from "../chunk-TQXMB7XV.js";
3
+ openBrowser
4
+ } from "../chunk-QGMWUOJ3.js";
8
5
  import {
6
+ Command,
7
+ CommandBuilder,
8
+ CommandDef,
9
9
  detectProblemIdFromPath,
10
- getProblemDirPath,
11
- getProblemId
12
- } from "../chunk-MOPDUECU.js";
10
+ findSolutionFile,
11
+ getProblemId,
12
+ resolveLanguage,
13
+ resolveProblemContext
14
+ } from "../chunk-7SVCS23X.js";
13
15
  import {
14
- getBojSessionCookie,
15
- getCodeOpen
16
- } from "../chunk-CIG2LEJC.js";
17
- import "../chunk-FYS2JH42.js";
16
+ __decorateClass,
17
+ getSupportedLanguagesString
18
+ } from "../chunk-7MQMPJ3X.js";
18
19
 
19
20
  // src/commands/submit.tsx
20
- import { useState, useEffect } from "react";
21
- import { render, Text, Box } from "ink";
22
21
  import { Badge, StatusMessage, Alert } from "@inkjs/ui";
23
- import { readdir } from "fs/promises";
24
- import { join } from "path";
22
+ import { Spinner } from "@inkjs/ui";
23
+ import { Text, Box } from "ink";
24
+
25
+ // src/hooks/use-submit.ts
25
26
  import { readFile } from "fs/promises";
27
+ import { useEffect, useState } from "react";
26
28
 
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;
59
- return false;
60
- }
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
- }
182
- }
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;
29
+ // src/utils/clipboard.ts
30
+ import { execa, execaCommand } from "execa";
31
+ async function copyToClipboard(text) {
225
32
  try {
226
- csrfToken = await getCsrfToken(problemId, sessionCookie);
227
- } 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"
33
+ if (process.platform === "win32") {
34
+ await execaCommand("clip", {
35
+ shell: true,
36
+ input: text
37
+ });
38
+ } else if (process.platform === "darwin") {
39
+ await execaCommand("pbcopy", {
40
+ shell: false,
41
+ input: text
42
+ });
43
+ } else {
44
+ try {
45
+ await execa("xclip", ["-selection", "clipboard"], {
46
+ input: text
47
+ });
48
+ } catch {
49
+ try {
50
+ await execa("xsel", ["--clipboard", "--input"], {
51
+ input: text
52
+ });
53
+ } catch {
54
+ return false;
282
55
  }
283
56
  }
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
57
  }
58
+ return true;
59
+ } catch {
60
+ return false;
294
61
  }
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
- );
299
- }
300
- const result = await pollSubmitResult(problemId, submitId, sessionCookie);
301
- return {
302
- ...result,
303
- problemId,
304
- submitId
305
- };
306
62
  }
307
63
 
308
- // src/commands/submit.tsx
309
- import { Spinner } from "@inkjs/ui";
310
- import { jsx, jsxs } from "react/jsx-runtime";
311
- function SubmitCommand({
64
+ // src/hooks/use-submit.ts
65
+ var BOJ_BASE_URL = "https://www.acmicpc.net";
66
+ function useSubmit({
312
67
  problemId,
313
- language,
68
+ language: _language,
314
69
  sourcePath,
315
- dryRun,
316
70
  onComplete
317
71
  }) {
318
72
  const [status, setStatus] = useState(
319
73
  "loading"
320
74
  );
321
75
  const [message, setMessage] = useState("\uC81C\uCD9C \uC900\uBE44 \uC911...");
322
- const [result, setResult] = useState(null);
323
76
  const [error, setError] = useState(null);
77
+ const [submitUrl, setSubmitUrl] = useState("");
78
+ const [clipboardSuccess, setClipboardSuccess] = useState(false);
79
+ const [clipboardError, setClipboardError] = useState(null);
324
80
  useEffect(() => {
325
81
  async function submit() {
326
82
  try {
327
- setMessage("\uCF54\uB4DC\uB97C \uC77D\uB294 \uC911...");
83
+ setMessage("\uC18C\uC2A4 \uCF54\uB4DC\uB97C \uC77D\uB294 \uC911...");
328
84
  const sourceCode = await readFile(sourcePath, "utf-8");
329
- if (dryRun) {
330
- setMessage("[DRY RUN] \uC81C\uCD9C \uAC80\uC99D \uC911...");
331
- } else {
332
- setMessage("BOJ\uC5D0 \uC81C\uCD9C\uD558\uB294 \uC911...");
85
+ setMessage("\uD074\uB9BD\uBCF4\uB4DC\uC5D0 \uBCF5\uC0AC\uD558\uB294 \uC911...");
86
+ const clipboardResult = await copyToClipboard(sourceCode);
87
+ setClipboardSuccess(clipboardResult);
88
+ if (!clipboardResult) {
89
+ setClipboardError("\uD074\uB9BD\uBCF4\uB4DC \uBCF5\uC0AC\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4.");
333
90
  }
334
- const submitResult = await submitSolution({
335
- problemId,
336
- language,
337
- sourceCode,
338
- dryRun
339
- });
340
- setResult(submitResult);
91
+ const url = `${BOJ_BASE_URL}/submit/${problemId}`;
92
+ setSubmitUrl(url);
93
+ setMessage("\uBE0C\uB77C\uC6B0\uC800\uB97C \uC5EC\uB294 \uC911...");
94
+ await openBrowser(url);
341
95
  setStatus("success");
342
- setMessage("\uC81C\uCD9C \uC644\uB8CC");
343
96
  setTimeout(() => {
344
97
  onComplete();
345
- }, 3e3);
98
+ }, 2e3);
346
99
  } catch (err) {
347
100
  setStatus("error");
348
101
  setError(err instanceof Error ? err.message : String(err));
349
102
  setTimeout(() => {
350
103
  onComplete();
351
- }, 3e3);
104
+ }, 2e3);
352
105
  }
353
106
  }
354
- submit();
355
- }, [problemId, language, sourcePath, dryRun, onComplete]);
107
+ void submit();
108
+ }, [problemId, sourcePath, onComplete]);
109
+ return {
110
+ status,
111
+ message,
112
+ error,
113
+ submitUrl,
114
+ clipboardSuccess,
115
+ clipboardError
116
+ };
117
+ }
118
+
119
+ // src/commands/submit.tsx
120
+ import { jsx, jsxs } from "react/jsx-runtime";
121
+ function SubmitView({
122
+ problemId,
123
+ language,
124
+ sourcePath,
125
+ onComplete
126
+ }) {
127
+ const {
128
+ status,
129
+ message,
130
+ error,
131
+ submitUrl,
132
+ clipboardSuccess,
133
+ clipboardError
134
+ } = useSubmit({
135
+ problemId,
136
+ language,
137
+ sourcePath,
138
+ onComplete
139
+ });
356
140
  if (status === "loading") {
357
- return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: /* @__PURE__ */ jsx(Spinner, { label: message }) });
141
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
142
+ /* @__PURE__ */ jsx(Spinner, { label: message }),
143
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
144
+ "\uBB38\uC81C #",
145
+ problemId
146
+ ] }) })
147
+ ] });
358
148
  }
359
149
  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
150
  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
- ] })
151
+ /* @__PURE__ */ jsxs(Alert, { variant: "error", children: [
152
+ "\uC624\uB958 \uBC1C\uC0DD: ",
153
+ error
154
+ ] }),
155
+ submitUrl && /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
156
+ "URL: ",
157
+ submitUrl
158
+ ] }) })
381
159
  ] });
382
160
  }
383
- return null;
384
- }
385
- async function detectSolutionFile(problemDir) {
386
- const files = await readdir(problemDir);
387
- const solutionFile = files.find((f) => f.startsWith("solution."));
388
- if (!solutionFile) {
389
- throw new Error("solution.* \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
390
- }
391
- return join(problemDir, solutionFile);
392
- }
393
- async function submitCommand(problemId, language, dryRun = false) {
394
- const currentPathProblemId = detectProblemIdFromPath(process.cwd());
395
- const problemDir = currentPathProblemId === problemId ? process.cwd() : getProblemDirPath(problemId);
396
- const sourcePath = await detectSolutionFile(problemDir);
397
- const detectedLanguage = language ?? detectLanguageFromFile(sourcePath);
398
- if (!detectedLanguage) {
399
- throw new Error(`\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uC5B8\uC5B4\uC785\uB2C8\uB2E4: ${sourcePath}`);
400
- }
401
- return new Promise((resolve) => {
402
- const { unmount } = render(
403
- /* @__PURE__ */ jsx(
404
- SubmitCommand,
405
- {
406
- problemId,
407
- language: detectedLanguage,
408
- sourcePath,
409
- dryRun,
410
- onComplete: () => {
411
- unmount();
412
- resolve();
413
- }
414
- }
415
- )
416
- );
417
- });
161
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", width: "100%", children: [
162
+ /* @__PURE__ */ jsx(StatusMessage, { variant: "success", children: "\uC81C\uCD9C \uD398\uC774\uC9C0\uB97C \uC5F4\uC5C8\uC2B5\uB2C8\uB2E4!" }),
163
+ /* @__PURE__ */ jsxs(
164
+ Box,
165
+ {
166
+ flexDirection: "column",
167
+ borderStyle: "round",
168
+ borderColor: "gray",
169
+ marginTop: 1,
170
+ paddingX: 1,
171
+ paddingY: 0,
172
+ alignSelf: "flex-start",
173
+ children: [
174
+ /* @__PURE__ */ jsxs(Text, { children: [
175
+ /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "\uBB38\uC81C \uBC88\uD638:" }),
176
+ " ",
177
+ problemId
178
+ ] }),
179
+ /* @__PURE__ */ jsxs(Text, { children: [
180
+ /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "\uC5B8\uC5B4:" }),
181
+ " ",
182
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: language })
183
+ ] }),
184
+ /* @__PURE__ */ jsxs(Text, { children: [
185
+ /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "URL:" }),
186
+ " ",
187
+ /* @__PURE__ */ jsx(Text, { color: "blue", underline: true, children: submitUrl })
188
+ ] }),
189
+ /* @__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" }) }),
190
+ clipboardError && !clipboardSuccess && /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Alert, { variant: "warning", children: clipboardError }) })
191
+ ]
192
+ }
193
+ )
194
+ ] });
418
195
  }
419
- var submitHelp = `
420
- \uC0AC\uC6A9\uBC95:
421
- $ ps submit [\uBB38\uC81C\uBC88\uD638] [\uC635\uC158]
422
-
423
- \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
426
- - 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
428
-
429
- \uC635\uC158:
430
- --language, -l \uC5B8\uC5B4 \uC120\uD0DD (\uC9C0\uC815 \uC2DC \uC790\uB3D9 \uAC10\uC9C0 \uBB34\uC2DC)
431
- \uC9C0\uC6D0 \uC5B8\uC5B4: ${getSupportedLanguagesString()}
432
- --dry-run \uC2E4\uC81C \uC81C\uCD9C \uC5C6\uC774 \uAC80\uC99D\uB9CC \uC218\uD589
433
-
434
- \uC608\uC81C:
435
- $ ps submit # \uD604\uC7AC \uB514\uB809\uD1A0\uB9AC\uC5D0\uC11C \uC81C\uCD9C
436
- $ ps submit 1000 # 1000\uBC88 \uBB38\uC81C \uC81C\uCD9C
437
- $ ps submit --language python # Python\uC73C\uB85C \uC81C\uCD9C
438
- `;
439
- async function submitExecute(args, flags) {
440
- if (flags.help) {
441
- console.log(submitHelp.trim());
442
- process.exit(0);
443
- return;
444
- }
445
- 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.`
196
+ var SubmitCommand = class extends Command {
197
+ async execute(args, flags) {
198
+ const problemId = getProblemId(args);
199
+ const context = await resolveProblemContext(
200
+ problemId !== null ? [problemId.toString()] : [],
201
+ { requireId: true }
452
202
  );
453
- process.exit(1);
454
- }
455
- const validLanguages = getSupportedLanguages();
456
- const language = flags.language;
457
- if (flags.language && language && !validLanguages.includes(language)) {
458
- console.error(
459
- `\uC624\uB958: \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uC5B8\uC5B4\uC785\uB2C8\uB2E4. (${getSupportedLanguagesString()})`
203
+ const sourcePath = await findSolutionFile(context.problemDir);
204
+ const detectedLanguage = await resolveLanguage(
205
+ context.problemDir,
206
+ flags.language
460
207
  );
461
- process.exit(1);
208
+ let finalProblemId = context.problemId;
209
+ if (finalProblemId === null) {
210
+ finalProblemId = detectProblemIdFromPath(context.problemDir);
211
+ }
212
+ if (finalProblemId === null) {
213
+ throw new Error(
214
+ "\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."
215
+ );
216
+ }
217
+ await this.renderView(SubmitView, {
218
+ problemId: finalProblemId,
219
+ language: detectedLanguage,
220
+ sourcePath
221
+ });
462
222
  }
463
- await submitCommand(problemId, language, Boolean(flags["dry-run"]));
464
- }
465
- var submitCommandDef = {
466
- name: "submit",
467
- help: submitHelp,
468
- execute: submitExecute
469
223
  };
470
- var submit_default = submitCommandDef;
224
+ SubmitCommand = __decorateClass([
225
+ CommandDef({
226
+ name: "submit",
227
+ 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.
228
+ - \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
229
+ - solution.* \uD30C\uC77C\uC744 \uC790\uB3D9\uC73C\uB85C \uCC3E\uC544 \uC5B8\uC5B4 \uAC10\uC9C0
230
+ - \uC18C\uC2A4 \uCF54\uB4DC\uB97C \uD074\uB9BD\uBCF4\uB4DC\uC5D0 \uC790\uB3D9 \uBCF5\uC0AC
231
+ - \uC81C\uCD9C \uD398\uC774\uC9C0\uB97C \uBE0C\uB77C\uC6B0\uC800\uB85C \uC790\uB3D9 \uC5F4\uAE30`,
232
+ flags: [
233
+ {
234
+ name: "language",
235
+ options: {
236
+ shortFlag: "l",
237
+ description: `\uC5B8\uC5B4 \uC120\uD0DD (\uC9C0\uC815 \uC2DC \uC790\uB3D9 \uAC10\uC9C0 \uBB34\uC2DC)
238
+ \uC9C0\uC6D0 \uC5B8\uC5B4: ${getSupportedLanguagesString()}`
239
+ }
240
+ }
241
+ ],
242
+ autoDetectProblemId: true,
243
+ autoDetectLanguage: true,
244
+ requireProblemId: true,
245
+ examples: [
246
+ "submit # \uD604\uC7AC \uB514\uB809\uD1A0\uB9AC\uC5D0\uC11C \uC81C\uCD9C",
247
+ "submit 1000 # 1000\uBC88 \uBB38\uC81C \uC81C\uCD9C",
248
+ "submit --language python # Python\uC73C\uB85C \uC81C\uCD9C"
249
+ ]
250
+ })
251
+ ], SubmitCommand);
252
+ var submit_default = CommandBuilder.fromClass(SubmitCommand);
471
253
  export {
472
- submit_default as default,
473
- submitExecute,
474
- submitHelp
254
+ SubmitCommand,
255
+ submit_default as default
475
256
  };