@rhseung/ps-cli 1.7.4 → 1.8.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 +29 -24
- package/dist/{chunk-GCOFFYJ3.js → chunk-3LR2NGRC.js} +12 -4
- package/dist/{chunk-F4LZ6ENP.js → chunk-JPDN34C7.js} +67 -3
- package/dist/chunk-PY6GW22W.js +13 -0
- package/dist/{chunk-A6STXEAE.js → chunk-QB2R47PW.js} +8 -1
- package/dist/{chunk-OJZLQ6FK.js → chunk-VIHXBCOZ.js} +30 -4
- package/dist/commands/archive.js +25 -14
- package/dist/commands/config.js +56 -2
- package/dist/commands/fetch.js +19 -14
- package/dist/commands/init.js +1 -1
- package/dist/commands/open.js +56 -12
- package/dist/commands/run.js +57 -37
- package/dist/commands/search.js +51 -19
- package/dist/commands/stats.js +119 -37
- package/dist/commands/submit.js +13 -11
- package/dist/commands/test.js +35 -31
- package/dist/index.js +77 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -95,7 +95,7 @@ ps test [문제번호] [옵션]
|
|
|
95
95
|
- `--language`, `-l`: 언어 선택 (지정 시 자동 감지 무시)
|
|
96
96
|
- 지원 언어: python, javascript, typescript, cpp
|
|
97
97
|
- `--watch`, `-w`: watch 모드 (파일 변경 시 자동 재테스트)
|
|
98
|
-
- solution._,
|
|
98
|
+
- solution._, testcases/\*\*/_.txt 파일 변경 감지
|
|
99
99
|
|
|
100
100
|
**예제:**
|
|
101
101
|
|
|
@@ -111,7 +111,7 @@ ps test --language python # Python으로 테스트
|
|
|
111
111
|
|
|
112
112
|
- 현재 디렉토리 또는 지정한 문제 번호의 테스트 실행
|
|
113
113
|
- solution.\* 파일을 자동으로 찾아 언어 감지
|
|
114
|
-
- input
|
|
114
|
+
- testcases/{번호}/input.txt와 testcases/{번호}/output.txt 파일을 기반으로 테스트
|
|
115
115
|
- 문제의 시간 제한을 자동으로 적용
|
|
116
116
|
|
|
117
117
|
---
|
|
@@ -131,22 +131,25 @@ ps run [문제번호] [옵션]
|
|
|
131
131
|
- `--language`, `-l`: 언어 선택 (지정 시 자동 감지 무시)
|
|
132
132
|
- 지원 언어: python, javascript, typescript, cpp
|
|
133
133
|
- `--input`, `-i`: 입력 파일 지정
|
|
134
|
-
-
|
|
134
|
+
- 숫자만 입력 시 testcases/{숫자}/input.txt로 자동 변환 (예: `--input 1`)
|
|
135
|
+
- 전체 경로도 지원 (예: testcases/1/input.txt)
|
|
135
136
|
|
|
136
137
|
**예제:**
|
|
137
138
|
|
|
138
139
|
```bash
|
|
139
|
-
ps run
|
|
140
|
-
ps run 1000
|
|
141
|
-
ps run --language python
|
|
142
|
-
ps run --input
|
|
140
|
+
ps run # 현재 디렉토리에서 표준 입력으로 실행
|
|
141
|
+
ps run 1000 # 1000번 문제 표준 입력으로 실행
|
|
142
|
+
ps run --language python # Python으로 표준 입력으로 실행
|
|
143
|
+
ps run --input 1 # 테스트 케이스 1번 사용
|
|
144
|
+
ps run --input testcases/1/input.txt # 전체 경로로 입력 파일 지정
|
|
143
145
|
```
|
|
144
146
|
|
|
145
147
|
**설명:**
|
|
146
148
|
|
|
147
149
|
- 현재 디렉토리 또는 지정한 문제 번호의 코드 실행
|
|
148
150
|
- solution.\* 파일을 자동으로 찾아 언어 감지
|
|
149
|
-
- input
|
|
151
|
+
- --input 옵션으로 입력 파일 지정 가능 (예: `--input 1` 또는 `--input testcases/1/input.txt`)
|
|
152
|
+
- 옵션 없이 실행 시 표준 입력으로 입력 받기
|
|
150
153
|
- 테스트 케이스 검증 없이 단순 실행
|
|
151
154
|
|
|
152
155
|
---
|
|
@@ -185,7 +188,7 @@ ps submit --language python # Python으로 제출
|
|
|
185
188
|
|
|
186
189
|
### `archive` - 아카이빙
|
|
187
190
|
|
|
188
|
-
solving 디렉토리의 문제를 archive 디렉토리로 이동하고 Git 커밋을 생성합니다.
|
|
191
|
+
solving 디렉토리의 문제를 archive 디렉토리로 이동하고 (선택적으로) Git 커밋을 생성합니다.
|
|
189
192
|
|
|
190
193
|
**사용법:**
|
|
191
194
|
|
|
@@ -203,31 +206,42 @@ ps archive # 현재 디렉토리에서 문제 번호 자동
|
|
|
203
206
|
**설명:**
|
|
204
207
|
|
|
205
208
|
- solving 디렉토리에서 문제를 찾아 archive 디렉토리로 이동
|
|
206
|
-
- Git add 및 commit 실행
|
|
207
|
-
- 커밋 메시지: "solve: {
|
|
209
|
+
- 설정에 따라 Git add 및 commit 실행
|
|
210
|
+
- 기본 커밋 메시지: "solve: {id} - {title}"
|
|
211
|
+
- `archive-auto-commit` 이 `true` 인 경우, **archive 디렉토리로 이동 후** Git 커밋을 시도하며, 커밋 실패 시 다시 원래 위치로 되돌림 (롤백)
|
|
212
|
+
- `archive-auto-commit` 이 `false` 인 경우, Git 커밋 없이 디렉토리만 이동
|
|
208
213
|
|
|
209
214
|
---
|
|
210
215
|
|
|
211
216
|
### `open` - 문제 페이지 열기
|
|
212
217
|
|
|
213
|
-
백준 문제 페이지를 브라우저로 엽니다.
|
|
218
|
+
백준 문제 페이지 또는 문제집 페이지를 브라우저로 엽니다.
|
|
214
219
|
|
|
215
220
|
**사용법:**
|
|
216
221
|
|
|
217
222
|
```bash
|
|
218
223
|
ps open [문제번호]
|
|
224
|
+
ps open --workbook <문제집ID>
|
|
225
|
+
ps open -w <문제집ID>
|
|
219
226
|
```
|
|
220
227
|
|
|
228
|
+
**옵션:**
|
|
229
|
+
|
|
230
|
+
- `--workbook`, `-w`: 문제집 ID를 지정하여 해당 문제집 페이지를 엽니다
|
|
231
|
+
|
|
221
232
|
**예제:**
|
|
222
233
|
|
|
223
234
|
```bash
|
|
224
235
|
ps open 1000 # 1000번 문제 열기
|
|
225
236
|
ps open # 문제 디렉토리에서 실행 시 자동 추론
|
|
237
|
+
ps open --workbook 25052 # 문제집 25052 열기
|
|
238
|
+
ps open -w 25052 # 문제집 25052 열기 (단축 옵션)
|
|
226
239
|
```
|
|
227
240
|
|
|
228
241
|
**설명:**
|
|
229
242
|
|
|
230
243
|
- 문제 번호를 인자로 전달하거나 문제 디렉토리에서 실행하면 자동으로 문제 번호를 추론
|
|
244
|
+
- `--workbook` 또는 `-w` 옵션으로 문제집 페이지를 열 수 있습니다
|
|
231
245
|
|
|
232
246
|
---
|
|
233
247
|
|
|
@@ -344,6 +358,8 @@ ps config clear # .ps-cli.json 파일 삭제
|
|
|
344
358
|
- `archive-dir`: 아카이브된 문제 디렉토리 (기본값: problems)
|
|
345
359
|
- `solving-dir`: 푸는 중인 문제 디렉토리 (기본값: solving)
|
|
346
360
|
- `archive-strategy`: 아카이빙 전략
|
|
361
|
+
- `archive-auto-commit`: archive 실행 시 Git 커밋 자동 실행 여부 (true/false, 기본값: true)
|
|
362
|
+
- `archive-commit-message`: archive 시 사용할 Git 커밋 메시지 템플릿 (`{id}`, `{title}` 사용 가능, 기본값: `solve: {id} - {title}`)
|
|
347
363
|
|
|
348
364
|
### 아카이빙 전략
|
|
349
365
|
|
|
@@ -413,7 +429,7 @@ node dist/index.js init
|
|
|
413
429
|
```bash
|
|
414
430
|
# 프로젝트 디렉토리에서
|
|
415
431
|
bun run build
|
|
416
|
-
|
|
432
|
+
bun link
|
|
417
433
|
|
|
418
434
|
# 외부 폴더에서 테스트
|
|
419
435
|
cd /path/to/test-project
|
|
@@ -425,17 +441,6 @@ npm unlink -g @rhseung/ps-cli
|
|
|
425
441
|
|
|
426
442
|
**주의:** `npm link`를 사용하면 글로벌 설치된 버전이 링크된 버전으로 대체됩니다.
|
|
427
443
|
|
|
428
|
-
### 방법 3: 프로젝트 내에서만 테스트
|
|
429
|
-
|
|
430
|
-
```bash
|
|
431
|
-
# 빌드
|
|
432
|
-
bun run build
|
|
433
|
-
|
|
434
|
-
# 프로젝트 디렉토리 내에서만
|
|
435
|
-
bun run ps init
|
|
436
|
-
bun run ps fetch 1000
|
|
437
|
-
```
|
|
438
|
-
|
|
439
444
|
## 라이선스
|
|
440
445
|
|
|
441
446
|
MIT
|
|
@@ -8,6 +8,7 @@ import { useEffect, useState } from "react";
|
|
|
8
8
|
var BOJ_BASE_URL = "https://www.acmicpc.net";
|
|
9
9
|
function useOpenBrowser({
|
|
10
10
|
problemId,
|
|
11
|
+
workbookId,
|
|
11
12
|
onComplete
|
|
12
13
|
}) {
|
|
13
14
|
const [status, setStatus] = useState(
|
|
@@ -18,9 +19,16 @@ function useOpenBrowser({
|
|
|
18
19
|
useEffect(() => {
|
|
19
20
|
async function handleOpenBrowser() {
|
|
20
21
|
try {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
let targetUrl;
|
|
23
|
+
if (workbookId !== void 0) {
|
|
24
|
+
targetUrl = `${BOJ_BASE_URL}/workbook/view/${workbookId}`;
|
|
25
|
+
} else if (problemId !== void 0) {
|
|
26
|
+
targetUrl = `${BOJ_BASE_URL}/problem/${problemId}`;
|
|
27
|
+
} else {
|
|
28
|
+
throw new Error("\uBB38\uC81C \uBC88\uD638 \uB610\uB294 \uBB38\uC81C\uC9D1 ID\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.");
|
|
29
|
+
}
|
|
30
|
+
setUrl(targetUrl);
|
|
31
|
+
await openBrowser(targetUrl);
|
|
24
32
|
setStatus("success");
|
|
25
33
|
setTimeout(() => {
|
|
26
34
|
onComplete?.();
|
|
@@ -34,7 +42,7 @@ function useOpenBrowser({
|
|
|
34
42
|
}
|
|
35
43
|
}
|
|
36
44
|
void handleOpenBrowser();
|
|
37
|
-
}, [problemId, onComplete]);
|
|
45
|
+
}, [problemId, workbookId, onComplete]);
|
|
38
46
|
return {
|
|
39
47
|
status,
|
|
40
48
|
error,
|
|
@@ -9926,8 +9926,9 @@ var config = new Conf({
|
|
|
9926
9926
|
solvedAcHandle: void 0,
|
|
9927
9927
|
archiveDir: "problems",
|
|
9928
9928
|
// 기본값: problems 디렉토리
|
|
9929
|
-
solvingDir: "solving"
|
|
9929
|
+
solvingDir: "solving",
|
|
9930
9930
|
// 기본값: solving 디렉토리
|
|
9931
|
+
archiveAutoCommit: true
|
|
9931
9932
|
}
|
|
9932
9933
|
});
|
|
9933
9934
|
var projectConfigCache = null;
|
|
@@ -10030,6 +10031,24 @@ function getArchiveStrategy() {
|
|
|
10030
10031
|
}
|
|
10031
10032
|
return config.get("archiveStrategy") ?? "flat";
|
|
10032
10033
|
}
|
|
10034
|
+
function getArchiveAutoCommit() {
|
|
10035
|
+
const projectConfig = getProjectConfigSync();
|
|
10036
|
+
if (projectConfig?.archiveAutoCommit !== void 0) {
|
|
10037
|
+
return projectConfig.archiveAutoCommit;
|
|
10038
|
+
}
|
|
10039
|
+
const globalValue = config.get("archiveAutoCommit");
|
|
10040
|
+
if (globalValue !== void 0) {
|
|
10041
|
+
return globalValue;
|
|
10042
|
+
}
|
|
10043
|
+
return true;
|
|
10044
|
+
}
|
|
10045
|
+
function getArchiveCommitMessage() {
|
|
10046
|
+
const projectConfig = getProjectConfigSync();
|
|
10047
|
+
if (projectConfig?.archiveCommitMessage !== void 0) {
|
|
10048
|
+
return projectConfig.archiveCommitMessage;
|
|
10049
|
+
}
|
|
10050
|
+
return config.get("archiveCommitMessage");
|
|
10051
|
+
}
|
|
10033
10052
|
|
|
10034
10053
|
// src/utils/tier.ts
|
|
10035
10054
|
import gradient from "gradient-string";
|
|
@@ -10067,6 +10086,40 @@ var TIER_NAMES = [
|
|
|
10067
10086
|
"Ruby I",
|
|
10068
10087
|
"Master"
|
|
10069
10088
|
];
|
|
10089
|
+
var TIER_SHORT_NAMES = [
|
|
10090
|
+
void 0,
|
|
10091
|
+
"B5",
|
|
10092
|
+
"B4",
|
|
10093
|
+
"B3",
|
|
10094
|
+
"B2",
|
|
10095
|
+
"B1",
|
|
10096
|
+
"S5",
|
|
10097
|
+
"S4",
|
|
10098
|
+
"S3",
|
|
10099
|
+
"S2",
|
|
10100
|
+
"S1",
|
|
10101
|
+
"G5",
|
|
10102
|
+
"G4",
|
|
10103
|
+
"G3",
|
|
10104
|
+
"G2",
|
|
10105
|
+
"G1",
|
|
10106
|
+
"P5",
|
|
10107
|
+
"P4",
|
|
10108
|
+
"P3",
|
|
10109
|
+
"P2",
|
|
10110
|
+
"P1",
|
|
10111
|
+
"D5",
|
|
10112
|
+
"D4",
|
|
10113
|
+
"D3",
|
|
10114
|
+
"D2",
|
|
10115
|
+
"D1",
|
|
10116
|
+
"R5",
|
|
10117
|
+
"R4",
|
|
10118
|
+
"R3",
|
|
10119
|
+
"R2",
|
|
10120
|
+
"R1",
|
|
10121
|
+
"M"
|
|
10122
|
+
];
|
|
10070
10123
|
var TIER_COLORS = [
|
|
10071
10124
|
void 0,
|
|
10072
10125
|
"#9d4900",
|
|
@@ -10213,6 +10266,13 @@ function getTierName(level) {
|
|
|
10213
10266
|
}
|
|
10214
10267
|
return "Unrated";
|
|
10215
10268
|
}
|
|
10269
|
+
function getTierShortName(level) {
|
|
10270
|
+
if (level === 0) return "UR";
|
|
10271
|
+
if (level >= 1 && level < TIER_SHORT_NAMES.length) {
|
|
10272
|
+
return TIER_SHORT_NAMES[level] || "UR";
|
|
10273
|
+
}
|
|
10274
|
+
return "UR";
|
|
10275
|
+
}
|
|
10216
10276
|
function getTierColor(level) {
|
|
10217
10277
|
if (level === 0) return "#2d2d2d";
|
|
10218
10278
|
if (level === 31) {
|
|
@@ -10248,7 +10308,7 @@ function getArchiveSubPath(problemId, strategy = "flat", problem) {
|
|
|
10248
10308
|
return String(range).padStart(5, "0");
|
|
10249
10309
|
}
|
|
10250
10310
|
case "by-tier": {
|
|
10251
|
-
if (!problem) {
|
|
10311
|
+
if (!problem || problem.level === void 0) {
|
|
10252
10312
|
return "";
|
|
10253
10313
|
}
|
|
10254
10314
|
return getTierDirName(problem.level);
|
|
@@ -10508,7 +10568,8 @@ var CommandBuilder = class {
|
|
|
10508
10568
|
return {
|
|
10509
10569
|
name: finalMetadata.name,
|
|
10510
10570
|
help,
|
|
10511
|
-
execute: wrappedExecute
|
|
10571
|
+
execute: wrappedExecute,
|
|
10572
|
+
metadata: finalMetadata
|
|
10512
10573
|
};
|
|
10513
10574
|
}
|
|
10514
10575
|
/**
|
|
@@ -10597,9 +10658,12 @@ export {
|
|
|
10597
10658
|
getArchiveDir,
|
|
10598
10659
|
getSolvingDir,
|
|
10599
10660
|
getArchiveStrategy,
|
|
10661
|
+
getArchiveAutoCommit,
|
|
10662
|
+
getArchiveCommitMessage,
|
|
10600
10663
|
getNextTierMinRating,
|
|
10601
10664
|
calculateTierProgress,
|
|
10602
10665
|
getTierName,
|
|
10666
|
+
getTierShortName,
|
|
10603
10667
|
getTierColor,
|
|
10604
10668
|
getTierImageUrl,
|
|
10605
10669
|
detectProblemIdFromPath,
|
|
@@ -47,8 +47,15 @@ async function getUserStats(handle) {
|
|
|
47
47
|
const data = await response.json();
|
|
48
48
|
return data;
|
|
49
49
|
}
|
|
50
|
+
async function getUserTop100(handle) {
|
|
51
|
+
const url = `${BASE_URL}/user/top_100?handle=${handle}`;
|
|
52
|
+
const response = await fetchWithRetry(url);
|
|
53
|
+
const data = await response.json();
|
|
54
|
+
return data.items || [];
|
|
55
|
+
}
|
|
50
56
|
|
|
51
57
|
export {
|
|
52
58
|
getProblem,
|
|
53
|
-
getUserStats
|
|
59
|
+
getUserStats,
|
|
60
|
+
getUserTop100
|
|
54
61
|
};
|
|
@@ -6,7 +6,23 @@ import {
|
|
|
6
6
|
// src/services/runner.ts
|
|
7
7
|
import { readFile } from "fs/promises";
|
|
8
8
|
import { join } from "path";
|
|
9
|
+
import { createInterface } from "readline";
|
|
9
10
|
import { execa, execaCommand } from "execa";
|
|
11
|
+
async function readStdin() {
|
|
12
|
+
return new Promise((resolve) => {
|
|
13
|
+
const lines = [];
|
|
14
|
+
const rl = createInterface({
|
|
15
|
+
input: process.stdin,
|
|
16
|
+
output: process.stdout
|
|
17
|
+
});
|
|
18
|
+
rl.on("line", (line) => {
|
|
19
|
+
lines.push(line);
|
|
20
|
+
});
|
|
21
|
+
rl.on("close", () => {
|
|
22
|
+
resolve(lines.join("\n"));
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
}
|
|
10
26
|
async function runSolution({
|
|
11
27
|
problemDir,
|
|
12
28
|
language,
|
|
@@ -16,7 +32,15 @@ async function runSolution({
|
|
|
16
32
|
const langConfig = getLanguageConfig(language);
|
|
17
33
|
const solutionFile = `solution.${langConfig.extension}`;
|
|
18
34
|
const solutionPath = join(problemDir, solutionFile);
|
|
19
|
-
|
|
35
|
+
let input;
|
|
36
|
+
let capturedInput;
|
|
37
|
+
if (inputPath) {
|
|
38
|
+
input = await readFile(inputPath, "utf-8");
|
|
39
|
+
capturedInput = input;
|
|
40
|
+
} else {
|
|
41
|
+
capturedInput = await readStdin();
|
|
42
|
+
input = capturedInput;
|
|
43
|
+
}
|
|
20
44
|
const start = Date.now();
|
|
21
45
|
try {
|
|
22
46
|
if (langConfig.compileCommand) {
|
|
@@ -27,7 +51,7 @@ async function runSolution({
|
|
|
27
51
|
}
|
|
28
52
|
const child = execa(langConfig.runCommand, [solutionPath], {
|
|
29
53
|
cwd: problemDir,
|
|
30
|
-
input,
|
|
54
|
+
...input !== void 0 ? { input } : { stdin: "inherit" },
|
|
31
55
|
timeout: timeoutMs
|
|
32
56
|
});
|
|
33
57
|
const result = await child;
|
|
@@ -39,7 +63,8 @@ async function runSolution({
|
|
|
39
63
|
stderr,
|
|
40
64
|
exitCode,
|
|
41
65
|
timedOut: false,
|
|
42
|
-
durationMs
|
|
66
|
+
durationMs,
|
|
67
|
+
...capturedInput !== void 0 && { input: capturedInput }
|
|
43
68
|
};
|
|
44
69
|
} catch (error) {
|
|
45
70
|
const durationMs = Date.now() - start;
|
|
@@ -50,7 +75,8 @@ async function runSolution({
|
|
|
50
75
|
stderr: err.stderr ?? err.shortMessage ?? err.message,
|
|
51
76
|
exitCode: err.exitCode ?? null,
|
|
52
77
|
timedOut: Boolean(err.timedOut),
|
|
53
|
-
durationMs
|
|
78
|
+
durationMs,
|
|
79
|
+
...capturedInput !== void 0 && { input: capturedInput }
|
|
54
80
|
};
|
|
55
81
|
}
|
|
56
82
|
return {
|
package/dist/commands/archive.js
CHANGED
|
@@ -4,11 +4,13 @@ import {
|
|
|
4
4
|
CommandBuilder,
|
|
5
5
|
CommandDef,
|
|
6
6
|
findProjectRoot,
|
|
7
|
+
getArchiveAutoCommit,
|
|
8
|
+
getArchiveCommitMessage,
|
|
7
9
|
getArchiveDirPath,
|
|
8
10
|
getSolvingDir,
|
|
9
11
|
getSolvingDirPath,
|
|
10
12
|
resolveProblemContext
|
|
11
|
-
} from "../chunk-
|
|
13
|
+
} from "../chunk-JPDN34C7.js";
|
|
12
14
|
import {
|
|
13
15
|
__decorateClass
|
|
14
16
|
} from "../chunk-7MQMPJ3X.js";
|
|
@@ -82,11 +84,33 @@ function useArchive({
|
|
|
82
84
|
throw err;
|
|
83
85
|
}
|
|
84
86
|
}
|
|
87
|
+
const autoCommit = getArchiveAutoCommit();
|
|
88
|
+
const template = getArchiveCommitMessage() ?? "solve: {id} - {title}";
|
|
89
|
+
const commitMessage = template.replace("{id}", String(problemId)).replace("{title}", problemTitle);
|
|
85
90
|
const archiveDirParent = dirname(archiveDir);
|
|
86
91
|
setMessage("\uC544\uCE74\uC774\uBE0C \uB514\uB809\uD1A0\uB9AC\uB97C \uC900\uBE44\uD558\uB294 \uC911...");
|
|
87
92
|
await mkdir(archiveDirParent, { recursive: true });
|
|
88
93
|
setMessage("\uBB38\uC81C\uB97C archive \uB514\uB809\uD1A0\uB9AC\uB85C \uC774\uB3D9\uD558\uB294 \uC911...");
|
|
89
94
|
await rename(solvingDir, archiveDir);
|
|
95
|
+
if (autoCommit) {
|
|
96
|
+
try {
|
|
97
|
+
setMessage("Git \uCEE4\uBC0B\uC744 \uC2E4\uD589\uD558\uB294 \uC911...");
|
|
98
|
+
await execa("git", ["add", archiveDir], { cwd: projectRoot });
|
|
99
|
+
await execa("git", ["commit", "-m", commitMessage], {
|
|
100
|
+
cwd: projectRoot
|
|
101
|
+
});
|
|
102
|
+
} catch (gitError) {
|
|
103
|
+
setMessage("\uCEE4\uBC0B \uC2E4\uD328\uB85C \uC778\uD574 \uB864\uBC31\uD558\uB294 \uC911...");
|
|
104
|
+
try {
|
|
105
|
+
await rename(archiveDir, solvingDir);
|
|
106
|
+
} catch (rollbackError) {
|
|
107
|
+
throw new Error(
|
|
108
|
+
`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.`
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
throw gitError;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
90
114
|
setMessage("\uBE48 \uB514\uB809\uD1A0\uB9AC \uC815\uB9AC \uC911...");
|
|
91
115
|
try {
|
|
92
116
|
const solvingDirConfig = getSolvingDir();
|
|
@@ -109,19 +133,6 @@ function useArchive({
|
|
|
109
133
|
}
|
|
110
134
|
} catch {
|
|
111
135
|
}
|
|
112
|
-
setMessage("Git \uCEE4\uBC0B\uC744 \uC2E4\uD589\uD558\uB294 \uC911...");
|
|
113
|
-
try {
|
|
114
|
-
await execa("git", ["add", archiveDir], { cwd: projectRoot });
|
|
115
|
-
const commitMessage = `solve: ${problemId} - ${problemTitle}`;
|
|
116
|
-
await execa("git", ["commit", "-m", commitMessage], {
|
|
117
|
-
cwd: projectRoot
|
|
118
|
-
});
|
|
119
|
-
} catch (gitError) {
|
|
120
|
-
console.warn(
|
|
121
|
-
"Git \uCEE4\uBC0B \uC2E4\uD328:",
|
|
122
|
-
gitError instanceof Error ? gitError.message : String(gitError)
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
136
|
setStatus("success");
|
|
126
137
|
setMessage(`\uBB38\uC81C ${problemId}\uB97C \uC544\uCE74\uC774\uBE0C\uD588\uC2B5\uB2C8\uB2E4: ${archiveDir}`);
|
|
127
138
|
setTimeout(() => {
|
package/dist/commands/config.js
CHANGED
|
@@ -3,13 +3,15 @@ import {
|
|
|
3
3
|
Command,
|
|
4
4
|
CommandBuilder,
|
|
5
5
|
CommandDef,
|
|
6
|
+
getArchiveAutoCommit,
|
|
7
|
+
getArchiveCommitMessage,
|
|
6
8
|
getArchiveDir,
|
|
7
9
|
getArchiveStrategy,
|
|
8
10
|
getAutoOpenEditor,
|
|
9
11
|
getDefaultLanguage,
|
|
10
12
|
getEditor,
|
|
11
13
|
getSolvedAcHandle
|
|
12
|
-
} from "../chunk-
|
|
14
|
+
} from "../chunk-JPDN34C7.js";
|
|
13
15
|
import {
|
|
14
16
|
__decorateClass,
|
|
15
17
|
getSupportedLanguages,
|
|
@@ -117,6 +119,19 @@ function useConfig({
|
|
|
117
119
|
updatedConfig.archiveStrategy = value;
|
|
118
120
|
break;
|
|
119
121
|
}
|
|
122
|
+
case "archive-auto-commit": {
|
|
123
|
+
if (value !== "true" && value !== "false") {
|
|
124
|
+
console.error(
|
|
125
|
+
`archive-auto-commit \uAC12\uC740 true \uB610\uB294 false \uC5EC\uC57C \uD569\uB2C8\uB2E4: ${value}`
|
|
126
|
+
);
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
updatedConfig.archiveAutoCommit = value === "true";
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
case "archive-commit-message":
|
|
133
|
+
updatedConfig.archiveCommitMessage = value;
|
|
134
|
+
break;
|
|
120
135
|
default:
|
|
121
136
|
console.error(`\uC54C \uC218 \uC5C6\uB294 \uC124\uC815 \uD0A4: ${configKey}`);
|
|
122
137
|
process.exit(1);
|
|
@@ -161,6 +176,8 @@ function getConfigHelp() {
|
|
|
161
176
|
solved-ac-handle Solved.ac \uD578\uB4E4 (stats \uBA85\uB839\uC5B4\uC6A9)
|
|
162
177
|
archive-dir \uC544\uCE74\uC774\uBE0C \uB514\uB809\uD1A0\uB9AC \uACBD\uB85C (\uAE30\uBCF8\uAC12: problems, "." \uB610\uB294 ""\uB294 \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8)
|
|
163
178
|
archive-strategy \uC544\uCE74\uC774\uBE59 \uC804\uB7B5 (flat, by-range, by-tier, by-tag)
|
|
179
|
+
archive-auto-commit archive \uC2DC Git \uCEE4\uBC0B \uC790\uB3D9 \uC2E4\uD589 \uC5EC\uBD80 (true/false)
|
|
180
|
+
archive-commit-message archive \uCEE4\uBC0B \uBA54\uC2DC\uC9C0 \uD15C\uD50C\uB9BF ({id}, {title} \uC0AC\uC6A9 \uAC00\uB2A5)
|
|
164
181
|
|
|
165
182
|
\uC635\uC158:
|
|
166
183
|
--help, -h \uB3C4\uC6C0\uB9D0 \uD45C\uC2DC
|
|
@@ -181,7 +198,9 @@ var CONFIG_KEYS = [
|
|
|
181
198
|
{ label: "auto-open-editor", value: "auto-open-editor" },
|
|
182
199
|
{ label: "solved-ac-handle", value: "solved-ac-handle" },
|
|
183
200
|
{ label: "archive-dir", value: "archive-dir" },
|
|
184
|
-
{ label: "archive-strategy", value: "archive-strategy" }
|
|
201
|
+
{ label: "archive-strategy", value: "archive-strategy" },
|
|
202
|
+
{ label: "archive-auto-commit", value: "archive-auto-commit" },
|
|
203
|
+
{ label: "archive-commit-message", value: "archive-commit-message" }
|
|
185
204
|
];
|
|
186
205
|
function ConfigView({
|
|
187
206
|
configKey,
|
|
@@ -215,6 +234,8 @@ function ConfigView({
|
|
|
215
234
|
const handle = config?.solvedAcHandle ?? getSolvedAcHandle();
|
|
216
235
|
const archiveDir = config?.archiveDir ?? getArchiveDir();
|
|
217
236
|
const archiveStrategy = config?.archiveStrategy ?? getArchiveStrategy();
|
|
237
|
+
const archiveAutoCommit = config?.archiveAutoCommit ?? getArchiveAutoCommit();
|
|
238
|
+
const archiveCommitMessage = config?.archiveCommitMessage ?? getArchiveCommitMessage();
|
|
218
239
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
219
240
|
/* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "\u2699\uFE0F \uD604\uC7AC \uC124\uC815 (.ps-cli.json)" }) }),
|
|
220
241
|
/* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
|
|
@@ -247,6 +268,23 @@ function ConfigView({
|
|
|
247
268
|
/* @__PURE__ */ jsx(Text, { color: "gray", children: "archive-strategy:" }),
|
|
248
269
|
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
249
270
|
/* @__PURE__ */ jsx(Text, { bold: true, children: archiveStrategy })
|
|
271
|
+
] }),
|
|
272
|
+
/* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
|
|
273
|
+
/* @__PURE__ */ jsx(Text, { color: "gray", children: "archive-auto-commit:" }),
|
|
274
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
275
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: archiveAutoCommit ? "green" : "gray", children: archiveAutoCommit ? "true" : "false" })
|
|
276
|
+
] }),
|
|
277
|
+
/* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
|
|
278
|
+
/* @__PURE__ */ jsx(Text, { color: "gray", children: "archive-commit-message:" }),
|
|
279
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
280
|
+
/* @__PURE__ */ jsx(
|
|
281
|
+
Text,
|
|
282
|
+
{
|
|
283
|
+
bold: true,
|
|
284
|
+
color: archiveCommitMessage && archiveCommitMessage.length > 0 ? "cyan" : "gray",
|
|
285
|
+
children: archiveCommitMessage || "(\uC124\uC815 \uC548 \uB428)"
|
|
286
|
+
}
|
|
287
|
+
)
|
|
250
288
|
] })
|
|
251
289
|
] })
|
|
252
290
|
] });
|
|
@@ -272,6 +310,12 @@ function ConfigView({
|
|
|
272
310
|
case "archive-strategy":
|
|
273
311
|
configValue = config?.archiveStrategy ?? getArchiveStrategy();
|
|
274
312
|
break;
|
|
313
|
+
case "archive-auto-commit":
|
|
314
|
+
configValue = config?.archiveAutoCommit !== void 0 ? String(config.archiveAutoCommit) : String(getArchiveAutoCommit());
|
|
315
|
+
break;
|
|
316
|
+
case "archive-commit-message":
|
|
317
|
+
configValue = config?.archiveCommitMessage ?? getArchiveCommitMessage();
|
|
318
|
+
break;
|
|
275
319
|
default:
|
|
276
320
|
console.error(`\uC54C \uC218 \uC5C6\uB294 \uC124\uC815 \uD0A4: ${configKey}`);
|
|
277
321
|
process.exit(1);
|
|
@@ -444,6 +488,10 @@ var ConfigCommand = class extends Command {
|
|
|
444
488
|
return "\uC544\uCE74\uC774\uBE0C \uB514\uB809\uD1A0\uB9AC \uACBD\uB85C \uC785\uB825";
|
|
445
489
|
case "archive-strategy":
|
|
446
490
|
return "\uC544\uCE74\uC774\uBE59 \uC804\uB7B5 \uC785\uB825 (flat, by-range, by-tier, by-tag)";
|
|
491
|
+
case "archive-auto-commit":
|
|
492
|
+
return "true \uB610\uB294 false \uC785\uB825";
|
|
493
|
+
case "archive-commit-message":
|
|
494
|
+
return "\uCEE4\uBC0B \uBA54\uC2DC\uC9C0 \uD15C\uD50C\uB9BF \uC785\uB825 (\uC608: solve: {id} - {title})";
|
|
447
495
|
default:
|
|
448
496
|
return "\uAC12 \uC785\uB825";
|
|
449
497
|
}
|
|
@@ -462,6 +510,10 @@ var ConfigCommand = class extends Command {
|
|
|
462
510
|
return '\uC544\uCE74\uC774\uBE0C \uB514\uB809\uD1A0\uB9AC \uACBD\uB85C (\uAE30\uBCF8\uAC12: "problems", \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8: ".")';
|
|
463
511
|
case "archive-strategy":
|
|
464
512
|
return "\uC544\uCE74\uC774\uBE59 \uC804\uB7B5: flat (\uD3C9\uBA74), by-range (1000\uBC88\uB300 \uBB36\uAE30), by-tier (\uD2F0\uC5B4\uBCC4), by-tag (\uD0DC\uADF8\uBCC4)";
|
|
513
|
+
case "archive-auto-commit":
|
|
514
|
+
return "archive \uBA85\uB839 \uC2E4\uD589 \uC2DC Git \uCEE4\uBC0B\uC744 \uC790\uB3D9\uC73C\uB85C \uC218\uD589\uD560\uC9C0 \uC5EC\uBD80";
|
|
515
|
+
case "archive-commit-message":
|
|
516
|
+
return "archive \uC2DC \uC0AC\uC6A9\uD560 Git \uCEE4\uBC0B \uBA54\uC2DC\uC9C0 \uD15C\uD50C\uB9BF ({id}, {title} \uC0AC\uC6A9 \uAC00\uB2A5)";
|
|
465
517
|
default:
|
|
466
518
|
return "";
|
|
467
519
|
}
|
|
@@ -478,6 +530,8 @@ var ConfigCommand = class extends Command {
|
|
|
478
530
|
return ["problems", ".", ""];
|
|
479
531
|
case "archive-strategy":
|
|
480
532
|
return ["flat", "by-range", "by-tier", "by-tag"];
|
|
533
|
+
case "archive-auto-commit":
|
|
534
|
+
return ["true", "false"];
|
|
481
535
|
default:
|
|
482
536
|
return [];
|
|
483
537
|
}
|
package/dist/commands/fetch.js
CHANGED
|
@@ -4,7 +4,10 @@ import {
|
|
|
4
4
|
} from "../chunk-4ISG24GW.js";
|
|
5
5
|
import {
|
|
6
6
|
getProblem
|
|
7
|
-
} from "../chunk-
|
|
7
|
+
} from "../chunk-QB2R47PW.js";
|
|
8
|
+
import {
|
|
9
|
+
defineFlags
|
|
10
|
+
} from "../chunk-PY6GW22W.js";
|
|
8
11
|
import {
|
|
9
12
|
Command,
|
|
10
13
|
CommandBuilder,
|
|
@@ -16,7 +19,7 @@ import {
|
|
|
16
19
|
getTierImageUrl,
|
|
17
20
|
getTierName,
|
|
18
21
|
resolveProblemContext
|
|
19
|
-
} from "../chunk-
|
|
22
|
+
} from "../chunk-JPDN34C7.js";
|
|
20
23
|
import {
|
|
21
24
|
__decorateClass,
|
|
22
25
|
getLanguageConfig,
|
|
@@ -122,10 +125,13 @@ async function generateProblemFiles(problem, language = "python") {
|
|
|
122
125
|
"utf-8"
|
|
123
126
|
);
|
|
124
127
|
}
|
|
128
|
+
const testcasesDir = join(problemDir, "testcases");
|
|
125
129
|
for (let i = 0; i < problem.testCases.length; i++) {
|
|
126
130
|
const testCase = problem.testCases[i];
|
|
127
|
-
const
|
|
128
|
-
|
|
131
|
+
const caseDir = join(testcasesDir, String(i + 1));
|
|
132
|
+
await mkdir(caseDir, { recursive: true });
|
|
133
|
+
const inputPath = join(caseDir, "input.txt");
|
|
134
|
+
const outputPath = join(caseDir, "output.txt");
|
|
129
135
|
await writeFile(inputPath, testCase.input, "utf-8");
|
|
130
136
|
await writeFile(outputPath, testCase.output, "utf-8");
|
|
131
137
|
}
|
|
@@ -312,6 +318,14 @@ ${editor}\uB85C \uC5F4\uC5C8\uC2B5\uB2C8\uB2E4.`
|
|
|
312
318
|
|
|
313
319
|
// src/commands/fetch.tsx
|
|
314
320
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
321
|
+
var fetchFlagsSchema = {
|
|
322
|
+
language: {
|
|
323
|
+
type: "string",
|
|
324
|
+
shortFlag: "l",
|
|
325
|
+
description: `\uC5B8\uC5B4 \uC120\uD0DD (${getSupportedLanguagesString()})
|
|
326
|
+
\uAE30\uBCF8\uAC12: python`
|
|
327
|
+
}
|
|
328
|
+
};
|
|
315
329
|
function FetchView({
|
|
316
330
|
problemId,
|
|
317
331
|
language = "python",
|
|
@@ -375,16 +389,7 @@ FetchCommand = __decorateClass([
|
|
|
375
389
|
- \uBB38\uC81C \uC124\uBA85, \uC785\uCD9C\uB825 \uD615\uC2DD, \uC608\uC81C \uC785\uCD9C\uB825 \uD30C\uC77C \uC790\uB3D9 \uC0DD\uC131
|
|
376
390
|
- \uC120\uD0DD\uD55C \uC5B8\uC5B4\uC758 \uC194\uB8E8\uC158 \uD15C\uD50C\uB9BF \uD30C\uC77C \uC0DD\uC131
|
|
377
391
|
- README.md\uC5D0 \uBB38\uC81C \uC815\uBCF4, \uD1B5\uACC4, \uD0DC\uADF8 \uB4F1 \uD3EC\uD568`,
|
|
378
|
-
flags:
|
|
379
|
-
{
|
|
380
|
-
name: "language",
|
|
381
|
-
options: {
|
|
382
|
-
shortFlag: "l",
|
|
383
|
-
description: `\uC5B8\uC5B4 \uC120\uD0DD (${getSupportedLanguagesString()})
|
|
384
|
-
\uAE30\uBCF8\uAC12: python`
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
],
|
|
392
|
+
flags: defineFlags(fetchFlagsSchema),
|
|
388
393
|
autoDetectProblemId: false,
|
|
389
394
|
requireProblemId: true,
|
|
390
395
|
examples: ["fetch 1000", "fetch 1000 --language python", "fetch 1000 -l cpp"]
|