@rhseung/ps-cli 1.10.2 → 1.11.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,13 +1,21 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ copyToClipboard
4
+ } from "./chunk-MZUR7SER.js";
5
+ import {
6
+ openBrowser
7
+ } from "./chunk-QGMWUOJ3.js";
2
8
  import {
3
9
  runSolution
4
- } from "./chunk-GV265WOR.js";
10
+ } from "./chunk-4VPUJY7G.js";
5
11
  import {
6
12
  Command,
7
13
  CommandBuilder,
8
14
  CommandDef,
9
15
  __decorateClass,
10
16
  defineFlags,
17
+ detectProblemIdFromPath,
18
+ findSolutionFile,
11
19
  getProblemTimeLimitMs,
12
20
  getSupportedLanguagesString,
13
21
  icons,
@@ -16,8 +24,9 @@ import {
16
24
  } from "./chunk-AHE4QHJD.js";
17
25
 
18
26
  // src/commands/test.tsx
19
- import { Alert, Spinner } from "@inkjs/ui";
20
- import { Box as Box2, Text as Text2 } from "ink";
27
+ import { Alert, Badge as Badge2, Spinner, StatusMessage as StatusMessage2 } from "@inkjs/ui";
28
+ import { Box as Box2, Text as Text2, useInput } from "ink";
29
+ import { useEffect as useEffect3, useState as useState3 } from "react";
21
30
 
22
31
  // src/components/test-result.tsx
23
32
  import { Badge, StatusMessage } from "@inkjs/ui";
@@ -195,8 +204,7 @@ function useTestRunner({
195
204
  problemDir,
196
205
  language,
197
206
  watch,
198
- timeoutMs,
199
- onComplete
207
+ timeoutMs
200
208
  }) {
201
209
  const [status, setStatus] = useState("loading");
202
210
  const [results, setResults] = useState([]);
@@ -254,13 +262,6 @@ function useTestRunner({
254
262
  void watcher.close();
255
263
  };
256
264
  }, [problemDir, watch, runTests]);
257
- useEffect(() => {
258
- if (!watch && status === "ready") {
259
- const timer = setTimeout(() => onComplete(), 200);
260
- return () => clearTimeout(timer);
261
- }
262
- return void 0;
263
- }, [status, watch, onComplete]);
264
265
  return {
265
266
  status,
266
267
  results,
@@ -269,6 +270,62 @@ function useTestRunner({
269
270
  };
270
271
  }
271
272
 
273
+ // src/hooks/use-testcase-ac.ts
274
+ import { readFile as readFile2 } from "fs/promises";
275
+ import { useEffect as useEffect2, useState as useState2 } from "react";
276
+ var TESTCASE_AC_BASE_URL = "https://testcase.ac";
277
+ function useTestcaseAc({
278
+ problemId,
279
+ sourcePath,
280
+ onComplete
281
+ }) {
282
+ const [status, setStatus] = useState2(
283
+ "loading"
284
+ );
285
+ const [message, setMessage] = useState2("testcase.ac \uC900\uBE44 \uC911...");
286
+ const [error, setError] = useState2(null);
287
+ const [url, setUrl] = useState2("");
288
+ const [clipboardSuccess, setClipboardSuccess] = useState2(false);
289
+ const [clipboardError, setClipboardError] = useState2(null);
290
+ useEffect2(() => {
291
+ async function open() {
292
+ try {
293
+ setMessage("\uC18C\uC2A4 \uCF54\uB4DC\uB97C \uC77D\uB294 \uC911...");
294
+ const sourceCode = await readFile2(sourcePath, "utf-8");
295
+ setMessage("\uD074\uB9BD\uBCF4\uB4DC\uC5D0 \uBCF5\uC0AC\uD558\uB294 \uC911...");
296
+ const clipboardResult = await copyToClipboard(sourceCode);
297
+ setClipboardSuccess(clipboardResult);
298
+ if (!clipboardResult) {
299
+ setClipboardError("\uD074\uB9BD\uBCF4\uB4DC \uBCF5\uC0AC\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4.");
300
+ }
301
+ const url2 = `${TESTCASE_AC_BASE_URL}/problems/${problemId}`;
302
+ setUrl(url2);
303
+ setMessage("\uBE0C\uB77C\uC6B0\uC800\uB97C \uC5EC\uB294 \uC911...");
304
+ await openBrowser(url2);
305
+ setStatus("success");
306
+ setTimeout(() => {
307
+ onComplete();
308
+ }, 2e3);
309
+ } catch (err) {
310
+ setStatus("error");
311
+ setError(err instanceof Error ? err.message : String(err));
312
+ setTimeout(() => {
313
+ onComplete();
314
+ }, 2e3);
315
+ }
316
+ }
317
+ void open();
318
+ }, [problemId, sourcePath, onComplete]);
319
+ return {
320
+ status,
321
+ message,
322
+ error,
323
+ url,
324
+ clipboardSuccess,
325
+ clipboardError
326
+ };
327
+ }
328
+
272
329
  // src/commands/test.tsx
273
330
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
274
331
  var testFlagsSchema = {
@@ -283,21 +340,146 @@ var testFlagsSchema = {
283
340
  shortFlag: "w",
284
341
  description: `watch \uBAA8\uB4DC (\uD30C\uC77C \uBCC0\uACBD \uC2DC \uC790\uB3D9 \uC7AC\uD14C\uC2A4\uD2B8)
285
342
  solution.*, testcases/**/*.txt \uD30C\uC77C \uBCC0\uACBD \uAC10\uC9C0`
343
+ },
344
+ testcaseAc: {
345
+ type: "boolean",
346
+ description: "\uB85C\uCEEC \uD14C\uC2A4\uD2B8 \uC2E4\uD589 \uC5C6\uC774 testcase.ac \uBB38\uC81C \uD398\uC774\uC9C0\uB97C \uC5F4\uACE0 \uC18C\uC2A4\uB97C \uD074\uB9BD\uBCF4\uB4DC\uC5D0 \uBCF5\uC0AC\uD569\uB2C8\uB2E4"
286
347
  }
287
348
  };
349
+ function TestcaseAcPanel({
350
+ problemId,
351
+ sourcePath,
352
+ onComplete
353
+ }) {
354
+ const { status, message, error, url, clipboardSuccess, clipboardError } = useTestcaseAc({
355
+ problemId,
356
+ sourcePath,
357
+ onComplete
358
+ });
359
+ if (status === "loading") {
360
+ return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", marginTop: 1, children: /* @__PURE__ */ jsx2(Spinner, { label: message }) });
361
+ }
362
+ if (status === "error") {
363
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginTop: 1, children: [
364
+ /* @__PURE__ */ jsxs2(Alert, { variant: "error", children: [
365
+ "testcase.ac \uC5F4\uAE30 \uC2E4\uD328: ",
366
+ error
367
+ ] }),
368
+ url && /* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsxs2(Text2, { color: "gray", children: [
369
+ "URL: ",
370
+ url
371
+ ] }) })
372
+ ] });
373
+ }
374
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: "100%", marginTop: 1, children: [
375
+ /* @__PURE__ */ jsx2(StatusMessage2, { variant: "success", children: "testcase.ac \uD398\uC774\uC9C0\uB97C \uC5F4\uC5C8\uC2B5\uB2C8\uB2E4!" }),
376
+ /* @__PURE__ */ jsxs2(
377
+ Box2,
378
+ {
379
+ flexDirection: "column",
380
+ borderStyle: "round",
381
+ borderColor: "gray",
382
+ marginTop: 1,
383
+ paddingX: 1,
384
+ paddingY: 0,
385
+ alignSelf: "flex-start",
386
+ children: [
387
+ /* @__PURE__ */ jsxs2(Text2, { children: [
388
+ /* @__PURE__ */ jsx2(Text2, { color: "cyan", bold: true, children: "\uBB38\uC81C \uBC88\uD638:" }),
389
+ " ",
390
+ problemId
391
+ ] }),
392
+ url && /* @__PURE__ */ jsxs2(Text2, { children: [
393
+ /* @__PURE__ */ jsx2(Text2, { color: "cyan", bold: true, children: "URL:" }),
394
+ " ",
395
+ /* @__PURE__ */ jsx2(Text2, { color: "blue", underline: true, children: url })
396
+ ] }),
397
+ /* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: clipboardSuccess ? /* @__PURE__ */ jsx2(Badge2, { color: "green", children: "\uD074\uB9BD\uBCF4\uB4DC\uC5D0 \uBCF5\uC0AC\uB428" }) : /* @__PURE__ */ jsx2(Badge2, { color: "yellow", children: "\uD074\uB9BD\uBCF4\uB4DC \uBCF5\uC0AC \uC2E4\uD328" }) }),
398
+ clipboardError && !clipboardSuccess && /* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx2(Alert, { variant: "warning", children: clipboardError }) })
399
+ ]
400
+ }
401
+ )
402
+ ] });
403
+ }
288
404
  function TestView({
289
405
  problemDir,
290
406
  language,
291
407
  watch,
292
408
  timeoutMs,
293
- onComplete
409
+ onComplete,
410
+ problemId,
411
+ sourcePath
294
412
  }) {
295
413
  const { status, results, summary, error } = useTestRunner({
296
414
  problemDir,
297
415
  language,
298
416
  watch,
299
417
  timeoutMs,
300
- onComplete
418
+ onComplete: () => {
419
+ }
420
+ });
421
+ const [completed, setCompleted] = useState3(false);
422
+ const [showSuggestion, setShowSuggestion] = useState3(false);
423
+ const [acceptedSuggestion, setAcceptedSuggestion] = useState3(false);
424
+ const allPassed = summary.total > 0 && summary.total === summary.passed && summary.failed === 0 && summary.errored === 0;
425
+ useEffect3(() => {
426
+ if (watch || completed) {
427
+ return;
428
+ }
429
+ if (status === "error") {
430
+ const timer = setTimeout(() => {
431
+ setCompleted(true);
432
+ onComplete();
433
+ }, 200);
434
+ return () => clearTimeout(timer);
435
+ }
436
+ if (status === "ready") {
437
+ const canSuggest = allPassed && !!problemId && !!sourcePath && !acceptedSuggestion;
438
+ if (!canSuggest) {
439
+ const timer = setTimeout(() => {
440
+ setCompleted(true);
441
+ onComplete();
442
+ }, 200);
443
+ return () => clearTimeout(timer);
444
+ }
445
+ }
446
+ return void 0;
447
+ }, [
448
+ status,
449
+ watch,
450
+ completed,
451
+ onComplete,
452
+ allPassed,
453
+ acceptedSuggestion,
454
+ problemId,
455
+ sourcePath
456
+ ]);
457
+ useEffect3(() => {
458
+ if (watch) {
459
+ setShowSuggestion(false);
460
+ return;
461
+ }
462
+ if (status === "ready" && allPassed && problemId && sourcePath) {
463
+ setShowSuggestion(true);
464
+ } else {
465
+ setShowSuggestion(false);
466
+ }
467
+ }, [status, allPassed, watch]);
468
+ useInput((input, key) => {
469
+ if (!showSuggestion || acceptedSuggestion || completed) {
470
+ return;
471
+ }
472
+ const lower = input.toLowerCase();
473
+ if (lower === "y" || key.return) {
474
+ setAcceptedSuggestion(true);
475
+ setShowSuggestion(false);
476
+ } else if (lower === "n" || key.escape) {
477
+ setShowSuggestion(false);
478
+ if (!completed) {
479
+ setCompleted(true);
480
+ onComplete();
481
+ }
482
+ }
301
483
  });
302
484
  if (status === "loading") {
303
485
  return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: /* @__PURE__ */ jsx2(Spinner, { label: "\uD14C\uC2A4\uD2B8 \uC2E4\uD589 \uC911..." }) });
@@ -321,12 +503,46 @@ function TestView({
321
503
  watch && ` ${icons.solving} watch`
322
504
  ] })
323
505
  ] }),
324
- /* @__PURE__ */ jsx2(TestResultView, { results, summary })
506
+ /* @__PURE__ */ jsx2(TestResultView, { results, summary }),
507
+ showSuggestion && !acceptedSuggestion && /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
508
+ /* @__PURE__ */ jsx2(Alert, { variant: "info", children: "\uBAA8\uB4E0 \uB85C\uCEEC \uD14C\uC2A4\uD2B8\uB97C \uD1B5\uACFC\uD588\uC2B5\uB2C8\uB2E4. testcase.ac\uC5D0\uC11C \uCD94\uAC00 \uD14C\uC2A4\uD2B8\uB97C \uC2E4\uD589\uD574 \uBCFC\uAE4C\uC694? (Y/n)" }),
509
+ /* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx2(Text2, { color: "gray", children: "Y \uB610\uB294 Enter: testcase.ac \uC5F4\uAE30 / N \uB610\uB294 Esc: \uAC74\uB108\uB6F0\uAE30" }) })
510
+ ] }),
511
+ acceptedSuggestion && problemId && sourcePath && !completed && /* @__PURE__ */ jsx2(
512
+ TestcaseAcPanel,
513
+ {
514
+ problemId,
515
+ sourcePath,
516
+ onComplete: () => {
517
+ if (!completed) {
518
+ setCompleted(true);
519
+ onComplete();
520
+ }
521
+ }
522
+ }
523
+ )
325
524
  ] });
326
525
  }
327
526
  var TestCommand = class extends Command {
328
527
  async execute(args, flags) {
329
528
  const context = await resolveProblemContext(args);
529
+ const sourcePath = await findSolutionFile(context.archiveDir);
530
+ let finalProblemId = context.problemId;
531
+ if (finalProblemId === null) {
532
+ finalProblemId = detectProblemIdFromPath(context.archiveDir);
533
+ }
534
+ if (flags.testcaseAc) {
535
+ if (finalProblemId === null) {
536
+ throw new Error(
537
+ "\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."
538
+ );
539
+ }
540
+ await this.renderView(TestcaseAcPanel, {
541
+ problemId: finalProblemId,
542
+ sourcePath
543
+ });
544
+ return;
545
+ }
330
546
  const language = await resolveLanguage(
331
547
  context.archiveDir,
332
548
  flags.language
@@ -335,7 +551,9 @@ var TestCommand = class extends Command {
335
551
  problemDir: context.archiveDir,
336
552
  language,
337
553
  watch: Boolean(flags.watch),
338
- timeoutMs: flags.timeoutMs
554
+ timeoutMs: flags.timeoutMs,
555
+ problemId: finalProblemId,
556
+ sourcePath
339
557
  });
340
558
  }
341
559
  };
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ useArchive
4
+ } from "./chunk-LCHSAHUP.js";
5
+ import {
6
+ Command,
7
+ CommandBuilder,
8
+ CommandDef,
9
+ __decorateClass,
10
+ logger,
11
+ resolveProblemContext
12
+ } from "./chunk-AHE4QHJD.js";
13
+
14
+ // src/commands/archive.tsx
15
+ import { StatusMessage, Alert, Spinner } from "@inkjs/ui";
16
+ import { Box } from "ink";
17
+ import { jsx, jsxs } from "react/jsx-runtime";
18
+ function ArchiveView({ problemId, onComplete }) {
19
+ const { status, message, error } = useArchive({
20
+ problemId,
21
+ onComplete
22
+ });
23
+ if (status === "loading") {
24
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: /* @__PURE__ */ jsx(Spinner, { label: message }) });
25
+ }
26
+ if (status === "error") {
27
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: /* @__PURE__ */ jsxs(Alert, { variant: "error", children: [
28
+ "\uC624\uB958 \uBC1C\uC0DD: ",
29
+ error
30
+ ] }) });
31
+ }
32
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", width: "100%", children: /* @__PURE__ */ jsx(StatusMessage, { variant: "success", children: message }) });
33
+ }
34
+ var ArchiveCommand = class extends Command {
35
+ async execute(args, _) {
36
+ const context = await resolveProblemContext(args, { requireId: false });
37
+ if (context.problemId === null) {
38
+ logger.error("\uBB38\uC81C \uBC88\uD638\uB97C \uC785\uB825\uD574\uC8FC\uC138\uC694.");
39
+ console.log(`\uB3C4\uC6C0\uB9D0: ps archive --help`);
40
+ process.exit(1);
41
+ return;
42
+ }
43
+ await this.renderView(ArchiveView, {
44
+ problemId: context.problemId
45
+ });
46
+ }
47
+ };
48
+ ArchiveCommand = __decorateClass([
49
+ CommandDef({
50
+ name: "archive",
51
+ description: `\uBB38\uC81C\uB97C \uC544\uCE74\uC774\uBE0C\uD558\uACE0 Git \uCEE4\uBC0B\uC744 \uC0DD\uC131\uD569\uB2C8\uB2E4.
52
+ - solving \uB514\uB809\uD1A0\uB9AC\uC5D0\uC11C \uBB38\uC81C\uB97C \uCC3E\uC544 archive \uB514\uB809\uD1A0\uB9AC\uB85C \uC774\uB3D9
53
+ - Git add \uBC0F commit \uC2E4\uD589 (\uCEE4\uBC0B \uBA54\uC2DC\uC9C0: "solve: {\uBB38\uC81C\uBC88\uD638} - {\uBB38\uC81C\uC774\uB984}")
54
+ - \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.`,
55
+ flags: [],
56
+ autoDetectProblemId: true,
57
+ requireProblemId: false,
58
+ examples: [
59
+ "archive 1000",
60
+ "archive # \uD604\uC7AC \uB514\uB809\uD1A0\uB9AC\uC5D0\uC11C \uBB38\uC81C \uBC88\uD638 \uC790\uB3D9 \uAC10\uC9C0"
61
+ ]
62
+ })
63
+ ], ArchiveCommand);
64
+ var archive_default = CommandBuilder.fromClass(ArchiveCommand);
65
+
66
+ export {
67
+ ArchiveView,
68
+ ArchiveCommand,
69
+ archive_default
70
+ };
@@ -1,23 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- Command,
4
- CommandBuilder,
5
- CommandDef,
6
- __decorateClass,
7
3
  findProjectRoot,
8
4
  getArchiveAutoCommit,
9
5
  getArchiveCommitMessage,
10
6
  getArchiveDirPath,
11
7
  getSolvingDir,
12
- getSolvingDirPath,
13
- logger,
14
- resolveProblemContext
8
+ getSolvingDirPath
15
9
  } from "./chunk-AHE4QHJD.js";
16
10
 
17
- // src/commands/archive.tsx
18
- import { StatusMessage, Alert, Spinner } from "@inkjs/ui";
19
- import { Box } from "ink";
20
-
21
11
  // src/hooks/use-archive.ts
22
12
  import { access, readFile, rename, mkdir, readdir, rmdir } from "fs/promises";
23
13
  import { join, dirname } from "path";
@@ -153,58 +143,6 @@ function useArchive({
153
143
  };
154
144
  }
155
145
 
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
146
  export {
207
- ArchiveView,
208
- ArchiveCommand,
209
- archive_default
147
+ useArchive
210
148
  };
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ openBrowser
4
+ } from "./chunk-QGMWUOJ3.js";
5
+
6
+ // src/hooks/use-submit.ts
7
+ import { readFile } from "fs/promises";
8
+ import { useEffect, useState } from "react";
9
+
10
+ // src/utils/clipboard.ts
11
+ import { execa, execaCommand } from "execa";
12
+ async function copyToClipboard(text) {
13
+ try {
14
+ if (process.platform === "win32") {
15
+ await execaCommand("clip", {
16
+ shell: true,
17
+ input: text
18
+ });
19
+ } else if (process.platform === "darwin") {
20
+ await execaCommand("pbcopy", {
21
+ shell: false,
22
+ input: text
23
+ });
24
+ } else {
25
+ try {
26
+ await execa("xclip", ["-selection", "clipboard"], {
27
+ input: text
28
+ });
29
+ } catch {
30
+ try {
31
+ await execa("xsel", ["--clipboard", "--input"], {
32
+ input: text
33
+ });
34
+ } catch {
35
+ return false;
36
+ }
37
+ }
38
+ }
39
+ return true;
40
+ } catch {
41
+ return false;
42
+ }
43
+ }
44
+
45
+ // src/hooks/use-submit.ts
46
+ var BOJ_BASE_URL = "https://www.acmicpc.net";
47
+ function useSubmit({
48
+ problemId,
49
+ language: _language,
50
+ sourcePath,
51
+ onComplete
52
+ }) {
53
+ const [status, setStatus] = useState(
54
+ "loading"
55
+ );
56
+ const [message, setMessage] = useState("\uC81C\uCD9C \uC900\uBE44 \uC911...");
57
+ const [error, setError] = useState(null);
58
+ const [submitUrl, setSubmitUrl] = useState("");
59
+ const [clipboardSuccess, setClipboardSuccess] = useState(false);
60
+ const [clipboardError, setClipboardError] = useState(null);
61
+ useEffect(() => {
62
+ async function submit() {
63
+ try {
64
+ setMessage("\uC18C\uC2A4 \uCF54\uB4DC\uB97C \uC77D\uB294 \uC911...");
65
+ const sourceCode = await readFile(sourcePath, "utf-8");
66
+ setMessage("\uD074\uB9BD\uBCF4\uB4DC\uC5D0 \uBCF5\uC0AC\uD558\uB294 \uC911...");
67
+ const clipboardResult = await copyToClipboard(sourceCode);
68
+ setClipboardSuccess(clipboardResult);
69
+ if (!clipboardResult) {
70
+ setClipboardError("\uD074\uB9BD\uBCF4\uB4DC \uBCF5\uC0AC\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4.");
71
+ }
72
+ const url = `${BOJ_BASE_URL}/submit/${problemId}`;
73
+ setSubmitUrl(url);
74
+ setMessage("\uBE0C\uB77C\uC6B0\uC800\uB97C \uC5EC\uB294 \uC911...");
75
+ await openBrowser(url);
76
+ setStatus("success");
77
+ setTimeout(() => {
78
+ onComplete();
79
+ }, 2e3);
80
+ } catch (err) {
81
+ setStatus("error");
82
+ setError(err instanceof Error ? err.message : String(err));
83
+ setTimeout(() => {
84
+ onComplete();
85
+ }, 2e3);
86
+ }
87
+ }
88
+ void submit();
89
+ }, [problemId, sourcePath, onComplete]);
90
+ return {
91
+ status,
92
+ message,
93
+ error,
94
+ submitUrl,
95
+ clipboardSuccess,
96
+ clipboardError
97
+ };
98
+ }
99
+
100
+ export {
101
+ copyToClipboard,
102
+ useSubmit
103
+ };