atcoder-workspace 1.1.0-beta.1
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/LICENSE +21 -0
- package/README.md +98 -0
- package/THIRD_PARTY_LICENSES +21 -0
- package/dist/atcoder/client.d.ts +2 -0
- package/dist/atcoder/client.js +23 -0
- package/dist/atcoder/new.d.ts +15 -0
- package/dist/atcoder/new.js +123 -0
- package/dist/atcoder/parser/contest-tasks.d.ts +6 -0
- package/dist/atcoder/parser/contest-tasks.js +86 -0
- package/dist/atcoder/parser/limits.d.ts +10 -0
- package/dist/atcoder/parser/limits.js +71 -0
- package/dist/atcoder/parser/problem-page.d.ts +12 -0
- package/dist/atcoder/parser/problem-page.js +136 -0
- package/dist/atcoder/parser/submission-status.d.ts +8 -0
- package/dist/atcoder/parser/submission-status.js +78 -0
- package/dist/atcoder/submit.d.ts +9 -0
- package/dist/atcoder/submit.js +182 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +508 -0
- package/dist/config/config-store.d.ts +17 -0
- package/dist/config/config-store.js +92 -0
- package/dist/session/auth.d.ts +8 -0
- package/dist/session/auth.js +117 -0
- package/dist/session/store.d.ts +15 -0
- package/dist/session/store.js +75 -0
- package/dist/test-runner/diff.d.ts +7 -0
- package/dist/test-runner/diff.js +32 -0
- package/dist/test-runner/runner.d.ts +46 -0
- package/dist/test-runner/runner.js +274 -0
- package/dist/utils/errors.d.ts +15 -0
- package/dist/utils/errors.js +35 -0
- package/dist/utils/format.d.ts +9 -0
- package/dist/utils/format.js +89 -0
- package/dist/utils/i18n.d.ts +345 -0
- package/dist/utils/i18n.js +413 -0
- package/dist/utils/open.d.ts +8 -0
- package/dist/utils/open.js +50 -0
- package/dist/workspace/finder.d.ts +9 -0
- package/dist/workspace/finder.js +62 -0
- package/dist/workspace/initializer.d.ts +4 -0
- package/dist/workspace/initializer.js +109 -0
- package/package.json +38 -0
- package/src/atcoder/client.ts +21 -0
- package/src/atcoder/new.ts +107 -0
- package/src/atcoder/parser/contest-tasks.test.ts +37 -0
- package/src/atcoder/parser/contest-tasks.ts +61 -0
- package/src/atcoder/parser/limits.test.ts +52 -0
- package/src/atcoder/parser/limits.ts +75 -0
- package/src/atcoder/parser/problem-page.test.ts +68 -0
- package/src/atcoder/parser/problem-page.ts +126 -0
- package/src/atcoder/parser/submission-status.test.ts +36 -0
- package/src/atcoder/parser/submission-status.ts +54 -0
- package/src/atcoder/submit.ts +170 -0
- package/src/cli.ts +554 -0
- package/src/config/config-store.ts +72 -0
- package/src/session/auth.ts +87 -0
- package/src/session/store.ts +50 -0
- package/src/test-runner/diff.test.ts +26 -0
- package/src/test-runner/diff.ts +42 -0
- package/src/test-runner/runner.test.ts +70 -0
- package/src/test-runner/runner.ts +315 -0
- package/src/utils/errors.ts +31 -0
- package/src/utils/format.test.ts +69 -0
- package/src/utils/format.ts +95 -0
- package/src/utils/i18n.test.ts +74 -0
- package/src/utils/i18n.ts +418 -0
- package/src/utils/open.ts +47 -0
- package/src/workspace/finder.ts +29 -0
- package/src/workspace/initializer.ts +85 -0
- package/tsconfig.json +16 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
37
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
38
|
+
};
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
const commander_1 = require("commander");
|
|
41
|
+
const p = __importStar(require("@clack/prompts"));
|
|
42
|
+
const picocolors_1 = __importDefault(require("picocolors"));
|
|
43
|
+
const fs = __importStar(require("fs"));
|
|
44
|
+
const path = __importStar(require("path"));
|
|
45
|
+
const finder_1 = require("./workspace/finder");
|
|
46
|
+
const initializer_1 = require("./workspace/initializer");
|
|
47
|
+
const auth_1 = require("./session/auth");
|
|
48
|
+
const store_1 = require("./session/store");
|
|
49
|
+
const new_1 = require("./atcoder/new");
|
|
50
|
+
const config_store_1 = require("./config/config-store");
|
|
51
|
+
const runner_1 = require("./test-runner/runner");
|
|
52
|
+
const submit_1 = require("./atcoder/submit");
|
|
53
|
+
const client_1 = require("./atcoder/client");
|
|
54
|
+
const submission_status_1 = require("./atcoder/parser/submission-status");
|
|
55
|
+
const problem_page_1 = require("./atcoder/parser/problem-page");
|
|
56
|
+
const errors_1 = require("./utils/errors");
|
|
57
|
+
const format_1 = require("./utils/format");
|
|
58
|
+
const i18n_1 = require("./utils/i18n");
|
|
59
|
+
const open_1 = require("./utils/open");
|
|
60
|
+
const workspaceRoot = (() => {
|
|
61
|
+
try {
|
|
62
|
+
return (0, finder_1.findWorkspaceRoot)();
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
})();
|
|
68
|
+
const lang = (0, i18n_1.getLanguage)(workspaceRoot);
|
|
69
|
+
const program = new commander_1.Command();
|
|
70
|
+
program
|
|
71
|
+
.name('atc')
|
|
72
|
+
.description('AtCoder All-in-One CLI (Local-first)')
|
|
73
|
+
.version('1.1.0-betas');
|
|
74
|
+
function handleAction(fn) {
|
|
75
|
+
return async (...args) => {
|
|
76
|
+
try {
|
|
77
|
+
await fn(...args);
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
let errMsg = err.message || 'An unexpected error occurred.';
|
|
81
|
+
if (err instanceof errors_1.WorkspaceNotFoundError) {
|
|
82
|
+
errMsg = (0, i18n_1.t)('workspaceNotFound', lang);
|
|
83
|
+
}
|
|
84
|
+
p.log.error(picocolors_1.default.red(errMsg));
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
program
|
|
90
|
+
.command('init')
|
|
91
|
+
.description((0, i18n_1.t)('descInit', lang))
|
|
92
|
+
.action(handleAction(async () => {
|
|
93
|
+
p.intro(picocolors_1.default.cyan((0, i18n_1.t)('initIntro', lang)));
|
|
94
|
+
const defaultLanguage = await p.select({
|
|
95
|
+
message: (0, i18n_1.t)('initSelectLang', lang),
|
|
96
|
+
options: [
|
|
97
|
+
{ value: 'cpp', label: 'C++ (cpp)' },
|
|
98
|
+
{ value: 'python', label: 'Python (python)' }
|
|
99
|
+
]
|
|
100
|
+
});
|
|
101
|
+
if (p.isCancel(defaultLanguage)) {
|
|
102
|
+
p.cancel((0, i18n_1.t)('initCancelled', lang));
|
|
103
|
+
process.exit(0);
|
|
104
|
+
}
|
|
105
|
+
const targetDir = process.cwd();
|
|
106
|
+
const s = p.spinner();
|
|
107
|
+
s.start((0, i18n_1.t)('initSpinner', lang));
|
|
108
|
+
const { alreadyInitialized, gitignoreUpdated } = (0, initializer_1.initWorkspace)(targetDir, defaultLanguage);
|
|
109
|
+
s.stop((0, i18n_1.t)('initFilesSet', lang));
|
|
110
|
+
if (alreadyInitialized) {
|
|
111
|
+
p.log.warn((0, i18n_1.t)('initAlreadyInitialized', lang));
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
p.log.success((0, i18n_1.t)('initCreatedConfig', lang, defaultLanguage));
|
|
115
|
+
}
|
|
116
|
+
if (gitignoreUpdated) {
|
|
117
|
+
p.log.success((0, i18n_1.t)('initGitignoreUpdated', lang));
|
|
118
|
+
}
|
|
119
|
+
p.outro(picocolors_1.default.green((0, i18n_1.t)('initOutro', lang)));
|
|
120
|
+
}));
|
|
121
|
+
program
|
|
122
|
+
.command('login')
|
|
123
|
+
.description((0, i18n_1.t)('descLogin', lang))
|
|
124
|
+
.action(handleAction(async () => {
|
|
125
|
+
p.intro(picocolors_1.default.cyan((0, i18n_1.t)('loginIntro', lang)));
|
|
126
|
+
const workspaceRoot = (0, finder_1.findWorkspaceRoot)();
|
|
127
|
+
p.note((0, i18n_1.t)('loginNote', lang));
|
|
128
|
+
const username = await promptManualCookie(workspaceRoot);
|
|
129
|
+
p.outro(picocolors_1.default.green((0, i18n_1.t)('loginWelcome', lang, username)));
|
|
130
|
+
}));
|
|
131
|
+
async function promptManualCookie(workspaceRoot) {
|
|
132
|
+
while (true) {
|
|
133
|
+
const sessionVal = await p.text({
|
|
134
|
+
message: (0, i18n_1.t)('loginEnterCookie', lang),
|
|
135
|
+
placeholder: (0, i18n_1.t)('loginPlaceholder', lang),
|
|
136
|
+
validate: (val) => (!val.trim() ? (0, i18n_1.t)('loginCookieNotEmpty', lang) : undefined)
|
|
137
|
+
});
|
|
138
|
+
if (p.isCancel(sessionVal)) {
|
|
139
|
+
p.cancel((0, i18n_1.t)('loginCancelled', lang));
|
|
140
|
+
process.exit(0);
|
|
141
|
+
}
|
|
142
|
+
const s = p.spinner();
|
|
143
|
+
s.start((0, i18n_1.t)('loginVerifying', lang));
|
|
144
|
+
try {
|
|
145
|
+
const username = await (0, auth_1.loginWithCookie)(workspaceRoot, sessionVal);
|
|
146
|
+
s.stop((0, i18n_1.t)('loginVerifySuccess', lang));
|
|
147
|
+
return username;
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
s.stop((0, i18n_1.t)('loginVerifyFailed', lang));
|
|
151
|
+
p.log.error(picocolors_1.default.red(err.message));
|
|
152
|
+
const retry = await p.confirm({
|
|
153
|
+
message: (0, i18n_1.t)('loginRetryConfirm', lang),
|
|
154
|
+
initialValue: true
|
|
155
|
+
});
|
|
156
|
+
if (p.isCancel(retry) || !retry) {
|
|
157
|
+
p.cancel((0, i18n_1.t)('loginAborted', lang));
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
program
|
|
164
|
+
.command('logout')
|
|
165
|
+
.description((0, i18n_1.t)('descLogout', lang))
|
|
166
|
+
.action(handleAction(async () => {
|
|
167
|
+
const workspaceRoot = (0, finder_1.findWorkspaceRoot)();
|
|
168
|
+
(0, store_1.clearSession)(workspaceRoot);
|
|
169
|
+
p.log.success((0, i18n_1.t)('logoutSuccess', lang));
|
|
170
|
+
}));
|
|
171
|
+
program
|
|
172
|
+
.command('whoami')
|
|
173
|
+
.description((0, i18n_1.t)('descWhoami', lang))
|
|
174
|
+
.action(handleAction(async () => {
|
|
175
|
+
const workspaceRoot = (0, finder_1.findWorkspaceRoot)();
|
|
176
|
+
const s = p.spinner();
|
|
177
|
+
s.start('Verifying session...');
|
|
178
|
+
const username = await (0, auth_1.whoami)(workspaceRoot);
|
|
179
|
+
s.stop(`Logged in as: ${picocolors_1.default.bold(picocolors_1.default.cyan(username))}`);
|
|
180
|
+
}));
|
|
181
|
+
program
|
|
182
|
+
.command('new <contest> [task]')
|
|
183
|
+
.description((0, i18n_1.t)('descNew', lang))
|
|
184
|
+
.option('-a, --all', 'Download all tasks for the contest')
|
|
185
|
+
.action(handleAction(async (contestId, taskLabel, options) => {
|
|
186
|
+
const workspaceRoot = (0, finder_1.findWorkspaceRoot)();
|
|
187
|
+
const config = (0, config_store_1.loadConfig)(workspaceRoot);
|
|
188
|
+
const contestParentDir = config.contestDir ? path.join(workspaceRoot, config.contestDir) : workspaceRoot;
|
|
189
|
+
const contestDir = path.join(contestParentDir, contestId);
|
|
190
|
+
if (fs.existsSync(contestDir)) {
|
|
191
|
+
throw new errors_1.AtcError((0, i18n_1.t)('newContestDirExists', lang, contestId));
|
|
192
|
+
}
|
|
193
|
+
p.intro(picocolors_1.default.cyan((0, i18n_1.t)('newIntro', lang, contestId)));
|
|
194
|
+
const s = p.spinner();
|
|
195
|
+
s.start((0, i18n_1.t)('newFetchingTasks', lang, contestId));
|
|
196
|
+
const tasks = await (0, new_1.fetchContestTasks)(workspaceRoot, contestId);
|
|
197
|
+
s.stop((0, i18n_1.t)('newFoundTasks', lang, tasks.length));
|
|
198
|
+
if (tasks.length === 0) {
|
|
199
|
+
throw new errors_1.AtcError((0, i18n_1.t)('newNoTasksFound', lang, contestId));
|
|
200
|
+
}
|
|
201
|
+
let selectedTasks = tasks;
|
|
202
|
+
if (options.all) {
|
|
203
|
+
selectedTasks = tasks;
|
|
204
|
+
}
|
|
205
|
+
else if (taskLabel) {
|
|
206
|
+
const matched = tasks.find(t => t.label.toLowerCase() === taskLabel.toLowerCase());
|
|
207
|
+
if (!matched) {
|
|
208
|
+
throw new errors_1.AtcError((0, i18n_1.t)('newLabelNotFound', lang, taskLabel, contestId, tasks.map(t => t.label).join(', ')));
|
|
209
|
+
}
|
|
210
|
+
selectedTasks = [matched];
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
const taskOptions = tasks.map(t => ({
|
|
214
|
+
value: t.id,
|
|
215
|
+
label: `${t.label.toUpperCase()} - ${t.id}`
|
|
216
|
+
}));
|
|
217
|
+
const selection = await p.multiselect({
|
|
218
|
+
message: (0, i18n_1.t)('newMultiselectMessage', lang),
|
|
219
|
+
options: taskOptions,
|
|
220
|
+
initialValues: tasks.map(t => t.id),
|
|
221
|
+
required: true
|
|
222
|
+
});
|
|
223
|
+
if (p.isCancel(selection)) {
|
|
224
|
+
p.cancel((0, i18n_1.t)('newCancelled', lang));
|
|
225
|
+
process.exit(0);
|
|
226
|
+
}
|
|
227
|
+
selectedTasks = tasks.filter(t => selection.includes(t.id));
|
|
228
|
+
}
|
|
229
|
+
const setupSpinner = p.spinner();
|
|
230
|
+
for (let i = 0; i < selectedTasks.length; i++) {
|
|
231
|
+
const tObj = selectedTasks[i];
|
|
232
|
+
if (i > 0) {
|
|
233
|
+
await new Promise(resolve => setTimeout(resolve, 300));
|
|
234
|
+
}
|
|
235
|
+
setupSpinner.start((0, i18n_1.t)('newSettingUpTask', lang, tObj.label.toUpperCase(), tObj.id));
|
|
236
|
+
const res = await (0, new_1.setupTask)(workspaceRoot, contestId, tObj);
|
|
237
|
+
setupSpinner.stop((0, i18n_1.t)('newSetupSuccess', lang, tObj.label.toUpperCase(), res.sampleCount));
|
|
238
|
+
}
|
|
239
|
+
p.outro(picocolors_1.default.green((0, i18n_1.t)('newScaffoldingComplete', lang, selectedTasks.length)));
|
|
240
|
+
}));
|
|
241
|
+
function resolveArgs(workspaceRoot, taskArg, fileArg) {
|
|
242
|
+
let resolvedTaskDir = '';
|
|
243
|
+
let resolvedFile;
|
|
244
|
+
let isFile = false;
|
|
245
|
+
let filePath = '';
|
|
246
|
+
if (taskArg) {
|
|
247
|
+
const pathsToCheck = [
|
|
248
|
+
path.resolve(taskArg),
|
|
249
|
+
path.resolve(workspaceRoot, taskArg)
|
|
250
|
+
];
|
|
251
|
+
const config = (0, config_store_1.loadConfig)(workspaceRoot);
|
|
252
|
+
if (config.contestDir) {
|
|
253
|
+
pathsToCheck.push(path.resolve(workspaceRoot, config.contestDir, taskArg));
|
|
254
|
+
}
|
|
255
|
+
for (const p of pathsToCheck) {
|
|
256
|
+
if (fs.existsSync(p) && fs.statSync(p).isFile()) {
|
|
257
|
+
isFile = true;
|
|
258
|
+
filePath = p;
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
if (isFile) {
|
|
264
|
+
resolvedFile = path.basename(filePath);
|
|
265
|
+
resolvedTaskDir = path.dirname(filePath);
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
resolvedTaskDir = (0, runner_1.resolveTaskDirectory)(workspaceRoot, taskArg);
|
|
269
|
+
resolvedFile = fileArg;
|
|
270
|
+
}
|
|
271
|
+
const taskLabel = path.basename(resolvedTaskDir);
|
|
272
|
+
const contestId = path.basename(path.dirname(resolvedTaskDir));
|
|
273
|
+
return { resolvedTaskDir, resolvedFile, taskLabel, contestId };
|
|
274
|
+
}
|
|
275
|
+
program
|
|
276
|
+
.command('test [task] [file]')
|
|
277
|
+
.description((0, i18n_1.t)('descTest', lang))
|
|
278
|
+
.action(handleAction(async (taskArg, fileArg) => {
|
|
279
|
+
const workspaceRoot = (0, finder_1.findWorkspaceRoot)();
|
|
280
|
+
const { resolvedTaskDir, resolvedFile, taskLabel, contestId } = resolveArgs(workspaceRoot, taskArg, fileArg);
|
|
281
|
+
p.intro(picocolors_1.default.cyan((0, i18n_1.t)('testIntro', lang, contestId, taskLabel)));
|
|
282
|
+
const s = p.spinner();
|
|
283
|
+
s.start((0, i18n_1.t)('testRetrievingLimits', lang));
|
|
284
|
+
let timeLimitMs = 2000;
|
|
285
|
+
try {
|
|
286
|
+
const client = (0, client_1.createAtCoderClient)(workspaceRoot);
|
|
287
|
+
const tasks = await (0, new_1.fetchContestTasks)(workspaceRoot, contestId);
|
|
288
|
+
const taskInfo = tasks.find(t => t.label.toLowerCase() === taskLabel.toLowerCase());
|
|
289
|
+
if (taskInfo) {
|
|
290
|
+
const res = await client.get(`/contests/${contestId}/tasks/${taskInfo.id}`);
|
|
291
|
+
const details = (0, problem_page_1.parseProblemPage)(res.data);
|
|
292
|
+
timeLimitMs = details.timeLimitMs;
|
|
293
|
+
s.stop((0, i18n_1.t)('testLoadedLimits', lang, timeLimitMs));
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
s.stop((0, i18n_1.t)('testDefaultLimits', lang));
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
catch (err) {
|
|
300
|
+
s.stop((0, i18n_1.t)('testDefaultLimitsError', lang));
|
|
301
|
+
}
|
|
302
|
+
const testSpinner = p.spinner();
|
|
303
|
+
testSpinner.start((0, i18n_1.t)('testCompilingRunning', lang));
|
|
304
|
+
const testRes = await (0, runner_1.runAllTests)(workspaceRoot, resolvedTaskDir, resolvedFile, timeLimitMs);
|
|
305
|
+
testSpinner.stop((0, i18n_1.t)('testFinished', lang));
|
|
306
|
+
if (testRes.compileError) {
|
|
307
|
+
p.log.error(picocolors_1.default.red((0, i18n_1.t)('testCompilationFailed', lang)));
|
|
308
|
+
console.log(testRes.compileError);
|
|
309
|
+
process.exit(1);
|
|
310
|
+
}
|
|
311
|
+
if (testRes.results.length === 0) {
|
|
312
|
+
p.log.warn((0, i18n_1.t)('testNoSamples', lang));
|
|
313
|
+
process.exit(0);
|
|
314
|
+
}
|
|
315
|
+
let allPassed = true;
|
|
316
|
+
for (const res of testRes.results) {
|
|
317
|
+
const label = `sample-${res.index}`;
|
|
318
|
+
const duration = `${res.durationMs.toFixed(0)} ms`;
|
|
319
|
+
if (res.status === 'AC') {
|
|
320
|
+
p.log.success(`${picocolors_1.default.green(picocolors_1.default.bold('[AC]'))} ${label}: Passed (${duration})`);
|
|
321
|
+
}
|
|
322
|
+
else if (res.status === 'WA') {
|
|
323
|
+
allPassed = false;
|
|
324
|
+
p.log.error(`${picocolors_1.default.red(picocolors_1.default.bold('[WA]'))} ${label}: Failed (${duration})`);
|
|
325
|
+
console.log(` ${picocolors_1.default.gray('┌────────────────────────────────────────────────────────')}`);
|
|
326
|
+
console.log(` ${picocolors_1.default.gray('│')} ${picocolors_1.default.bold('Expected Output:')}`);
|
|
327
|
+
(0, format_1.formatOutputLines)(res.expectedOutput, res.firstDiffLine).forEach(l => console.log(l));
|
|
328
|
+
console.log(` ${picocolors_1.default.gray('├────────────────────────────────────────────────────────')}`);
|
|
329
|
+
console.log(` ${picocolors_1.default.gray('│')} ${picocolors_1.default.bold('Actual Output:')}`);
|
|
330
|
+
(0, format_1.formatOutputLines)(res.actualOutput, res.firstDiffLine).forEach(l => console.log(l));
|
|
331
|
+
if (res.firstDiffLine) {
|
|
332
|
+
console.log(` ${picocolors_1.default.gray('├────────────────────────────────────────────────────────')}`);
|
|
333
|
+
console.log(` ${picocolors_1.default.gray('│')} ${picocolors_1.default.yellow(`First mismatch on line ${res.firstDiffLine}`)}`);
|
|
334
|
+
}
|
|
335
|
+
console.log(` ${picocolors_1.default.gray('└────────────────────────────────────────────────────────')}`);
|
|
336
|
+
}
|
|
337
|
+
else if (res.status === 'TLE') {
|
|
338
|
+
allPassed = false;
|
|
339
|
+
p.log.error(`${picocolors_1.default.red(picocolors_1.default.bold('[TLE]'))} ${label}: Time Limit Exceeded (${duration} vs Limit ${timeLimitMs} ms)`);
|
|
340
|
+
}
|
|
341
|
+
else if (res.status === 'RE') {
|
|
342
|
+
allPassed = false;
|
|
343
|
+
p.log.error(`${picocolors_1.default.red(picocolors_1.default.bold('[RE]'))} ${label}: Runtime Error (${duration})`);
|
|
344
|
+
if (res.errorOutput) {
|
|
345
|
+
console.log(` ${picocolors_1.default.gray('┌────────────────────────────────────────────────────────')}`);
|
|
346
|
+
console.log(` ${picocolors_1.default.gray('│')} ${picocolors_1.default.bold('Error Output:')}`);
|
|
347
|
+
(0, format_1.formatErrorOutputLines)(res.errorOutput).forEach(l => console.log(l));
|
|
348
|
+
console.log(` ${picocolors_1.default.gray('└────────────────────────────────────────────────────────')}`);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
p.outro(allPassed ? picocolors_1.default.green((0, i18n_1.t)('testOutroPassed', lang)) : picocolors_1.default.red((0, i18n_1.t)('testOutroFailed', lang)));
|
|
353
|
+
if (!allPassed) {
|
|
354
|
+
process.exit(1);
|
|
355
|
+
}
|
|
356
|
+
}));
|
|
357
|
+
program
|
|
358
|
+
.command('submit [task] [file]')
|
|
359
|
+
.description((0, i18n_1.t)('descSubmit', lang))
|
|
360
|
+
.action(handleAction(async (taskArg, fileArg) => {
|
|
361
|
+
const workspaceRoot = (0, finder_1.findWorkspaceRoot)();
|
|
362
|
+
const { resolvedTaskDir, resolvedFile, taskLabel, contestId } = resolveArgs(workspaceRoot, taskArg, fileArg);
|
|
363
|
+
p.intro(picocolors_1.default.cyan((0, i18n_1.t)('submitPreparing', lang, contestId, taskLabel)));
|
|
364
|
+
const s = p.spinner();
|
|
365
|
+
s.start((0, i18n_1.t)('submitRetrievingLimits', lang));
|
|
366
|
+
let timeLimitMs = 2000;
|
|
367
|
+
let taskId = '';
|
|
368
|
+
const client = (0, client_1.createAtCoderClient)(workspaceRoot);
|
|
369
|
+
const tasks = await (0, new_1.fetchContestTasks)(workspaceRoot, contestId);
|
|
370
|
+
const taskInfo = tasks.find(t => t.label.toLowerCase() === taskLabel.toLowerCase());
|
|
371
|
+
if (!taskInfo) {
|
|
372
|
+
throw new errors_1.AtcError(`Task label "${taskLabel}" not found in contest "${contestId}".`);
|
|
373
|
+
}
|
|
374
|
+
taskId = taskInfo.id;
|
|
375
|
+
try {
|
|
376
|
+
const res = await client.get(`/contests/${contestId}/tasks/${taskId}`);
|
|
377
|
+
const details = (0, problem_page_1.parseProblemPage)(res.data);
|
|
378
|
+
timeLimitMs = details.timeLimitMs;
|
|
379
|
+
s.stop((0, i18n_1.t)('testLoadedLimits', lang, timeLimitMs));
|
|
380
|
+
}
|
|
381
|
+
catch (err) {
|
|
382
|
+
s.stop((0, i18n_1.t)('testDefaultLimitsError', lang));
|
|
383
|
+
}
|
|
384
|
+
p.log.step((0, i18n_1.t)('submitRunningTests', lang));
|
|
385
|
+
const testRes = await (0, runner_1.runAllTests)(workspaceRoot, resolvedTaskDir, resolvedFile, timeLimitMs);
|
|
386
|
+
if (testRes.compileError) {
|
|
387
|
+
p.log.error(picocolors_1.default.red((0, i18n_1.t)('testCompilationFailed', lang)));
|
|
388
|
+
console.log(testRes.compileError);
|
|
389
|
+
process.exit(1);
|
|
390
|
+
}
|
|
391
|
+
const allPassed = testRes.results.length > 0 && testRes.results.every(r => r.status === 'AC');
|
|
392
|
+
if (testRes.results.length === 0) {
|
|
393
|
+
p.log.warn((0, i18n_1.t)('submitNoSamples', lang));
|
|
394
|
+
}
|
|
395
|
+
else if (!allPassed) {
|
|
396
|
+
p.log.warn(picocolors_1.default.yellow((0, i18n_1.t)('submitTestsFailed', lang)));
|
|
397
|
+
const confirmSubmit = await p.confirm({
|
|
398
|
+
message: (0, i18n_1.t)('submitConfirmMessage', lang),
|
|
399
|
+
initialValue: false
|
|
400
|
+
});
|
|
401
|
+
if (p.isCancel(confirmSubmit) || !confirmSubmit) {
|
|
402
|
+
p.cancel((0, i18n_1.t)('submitAborted', lang));
|
|
403
|
+
process.exit(0);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
p.log.success(picocolors_1.default.green((0, i18n_1.t)('submitTestsPassed', lang)));
|
|
408
|
+
}
|
|
409
|
+
const subSpinner = p.spinner();
|
|
410
|
+
subSpinner.start((0, i18n_1.t)('submitSubmitting', lang));
|
|
411
|
+
let subDetails;
|
|
412
|
+
try {
|
|
413
|
+
subDetails = await (0, submit_1.submitTask)(workspaceRoot, contestId, taskId, taskLabel, resolvedFile);
|
|
414
|
+
subSpinner.stop((0, i18n_1.t)('submitSuccess', lang, subDetails.submissionId));
|
|
415
|
+
(0, open_1.openUrl)(`https://atcoder.jp${subDetails.url}`);
|
|
416
|
+
}
|
|
417
|
+
catch (err) {
|
|
418
|
+
subSpinner.stop(picocolors_1.default.yellow((0, i18n_1.t)('submitManualSubmission', lang)));
|
|
419
|
+
let codeContent = '';
|
|
420
|
+
try {
|
|
421
|
+
const config = (0, config_store_1.loadConfig)(workspaceRoot);
|
|
422
|
+
const { codeFile } = (0, runner_1.detectCodeFile)(workspaceRoot, resolvedTaskDir, config, resolvedFile);
|
|
423
|
+
codeContent = fs.readFileSync(path.join(resolvedTaskDir, codeFile), 'utf8');
|
|
424
|
+
}
|
|
425
|
+
catch {
|
|
426
|
+
// Ignore
|
|
427
|
+
}
|
|
428
|
+
const submitPageUrl = `https://atcoder.jp/contests/${contestId}/submit?taskScreenName=${taskId}`;
|
|
429
|
+
(0, open_1.openUrl)(submitPageUrl);
|
|
430
|
+
if (codeContent) {
|
|
431
|
+
await (0, open_1.copyToClipboard)(codeContent);
|
|
432
|
+
p.outro(picocolors_1.default.cyan((0, i18n_1.t)('submitFallbackMessageWithClipboard', lang)));
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
p.outro(picocolors_1.default.cyan((0, i18n_1.t)('submitFallbackMessage', lang)));
|
|
436
|
+
}
|
|
437
|
+
process.exit(0);
|
|
438
|
+
}
|
|
439
|
+
const pollSpinner = p.spinner();
|
|
440
|
+
pollSpinner.start((0, i18n_1.t)('submitWaitingJudge', lang));
|
|
441
|
+
const pollInterval = 2000;
|
|
442
|
+
const timeout = 300000; // 5 minutes
|
|
443
|
+
const startTime = Date.now();
|
|
444
|
+
let completed = false;
|
|
445
|
+
while (Date.now() - startTime < timeout) {
|
|
446
|
+
try {
|
|
447
|
+
const detailRes = await client.get(subDetails.url);
|
|
448
|
+
const status = (0, submission_status_1.parseSubmissionStatus)(detailRes.data);
|
|
449
|
+
pollSpinner.message(`Judge Status: ${picocolors_1.default.yellow(picocolors_1.default.bold(status.status))}`);
|
|
450
|
+
if (status.isCompleted) {
|
|
451
|
+
completed = true;
|
|
452
|
+
pollSpinner.stop((0, i18n_1.t)('submitJudgeFinished', lang, status.status));
|
|
453
|
+
const stats = [];
|
|
454
|
+
if (status.score)
|
|
455
|
+
stats.push(`Score: ${status.score}`);
|
|
456
|
+
if (status.time)
|
|
457
|
+
stats.push(`Time: ${status.time}`);
|
|
458
|
+
if (status.memory)
|
|
459
|
+
stats.push(`Memory: ${status.memory}`);
|
|
460
|
+
const statsStr = stats.length > 0 ? ` (${stats.join(', ')})` : '';
|
|
461
|
+
if (status.status === 'AC') {
|
|
462
|
+
p.log.success(`${picocolors_1.default.green(picocolors_1.default.bold('[AC]'))} ${(0, i18n_1.t)('submitAccepted', lang)}${statsStr}`);
|
|
463
|
+
}
|
|
464
|
+
else {
|
|
465
|
+
p.log.error(`${picocolors_1.default.red(picocolors_1.default.bold(`[${status.status}]`))} ${(0, i18n_1.t)('submitFailed', lang)}${statsStr}`);
|
|
466
|
+
}
|
|
467
|
+
break;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
catch (e) {
|
|
471
|
+
// Ignore intermediate polling network errors
|
|
472
|
+
pollSpinner.message(`Polling status... (network retry: ${e.message})`);
|
|
473
|
+
}
|
|
474
|
+
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
475
|
+
}
|
|
476
|
+
if (!completed) {
|
|
477
|
+
pollSpinner.stop((0, i18n_1.t)('submitTimeout', lang));
|
|
478
|
+
p.log.warn((0, i18n_1.t)('submitTimeoutWarn', lang, subDetails.url));
|
|
479
|
+
}
|
|
480
|
+
p.outro(picocolors_1.default.green('Done.'));
|
|
481
|
+
}));
|
|
482
|
+
program
|
|
483
|
+
.command('lang [language]')
|
|
484
|
+
.description((0, i18n_1.t)('descLang', lang))
|
|
485
|
+
.action(handleAction(async (targetLanguage) => {
|
|
486
|
+
let workspaceRoot;
|
|
487
|
+
try {
|
|
488
|
+
workspaceRoot = (0, finder_1.findWorkspaceRoot)();
|
|
489
|
+
}
|
|
490
|
+
catch {
|
|
491
|
+
p.log.error(picocolors_1.default.red((0, i18n_1.t)('langWorkspaceRequired', lang)));
|
|
492
|
+
process.exit(1);
|
|
493
|
+
}
|
|
494
|
+
if (!targetLanguage) {
|
|
495
|
+
console.log((0, i18n_1.t)('langCommandUsage', lang));
|
|
496
|
+
process.exit(0);
|
|
497
|
+
}
|
|
498
|
+
const cleanLang = targetLanguage.trim().toLowerCase();
|
|
499
|
+
if (cleanLang !== 'en' && cleanLang !== 'ja') {
|
|
500
|
+
p.log.error(picocolors_1.default.red((0, i18n_1.t)('langInvalid', lang)));
|
|
501
|
+
process.exit(1);
|
|
502
|
+
}
|
|
503
|
+
const config = (0, config_store_1.loadConfig)(workspaceRoot);
|
|
504
|
+
config.lang = cleanLang;
|
|
505
|
+
(0, config_store_1.saveConfig)(workspaceRoot, config);
|
|
506
|
+
p.log.success((0, i18n_1.t)('langSuccess', cleanLang, cleanLang));
|
|
507
|
+
}));
|
|
508
|
+
program.parse(process.argv);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface LanguageConfig {
|
|
2
|
+
extension: string;
|
|
3
|
+
templateDir: string;
|
|
4
|
+
build: string;
|
|
5
|
+
run: string;
|
|
6
|
+
}
|
|
7
|
+
export interface Config {
|
|
8
|
+
defaultLanguage: string;
|
|
9
|
+
languages: Record<string, LanguageConfig>;
|
|
10
|
+
testDirName: string;
|
|
11
|
+
contestDir?: string;
|
|
12
|
+
lang?: 'en' | 'ja';
|
|
13
|
+
}
|
|
14
|
+
export declare const DEFAULT_CONFIG: Config;
|
|
15
|
+
export declare function getConfigPath(workspaceRoot: string): string;
|
|
16
|
+
export declare function loadConfig(workspaceRoot: string): Config;
|
|
17
|
+
export declare function saveConfig(workspaceRoot: string, config: Config): void;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.DEFAULT_CONFIG = void 0;
|
|
37
|
+
exports.getConfigPath = getConfigPath;
|
|
38
|
+
exports.loadConfig = loadConfig;
|
|
39
|
+
exports.saveConfig = saveConfig;
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
exports.DEFAULT_CONFIG = {
|
|
43
|
+
defaultLanguage: 'cpp',
|
|
44
|
+
languages: {
|
|
45
|
+
cpp: {
|
|
46
|
+
extension: 'cpp',
|
|
47
|
+
templateDir: 'templates/cpp',
|
|
48
|
+
build: 'g++ -O2 -std=gnu++20 -o a.out main.cpp',
|
|
49
|
+
run: './a.out'
|
|
50
|
+
},
|
|
51
|
+
python: {
|
|
52
|
+
extension: 'py',
|
|
53
|
+
templateDir: 'templates/python',
|
|
54
|
+
build: '',
|
|
55
|
+
run: 'python3 main.py'
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
testDirName: 'tests',
|
|
59
|
+
contestDir: ''
|
|
60
|
+
};
|
|
61
|
+
function getConfigPath(workspaceRoot) {
|
|
62
|
+
return path.join(workspaceRoot, '.atcoder-cli', 'config.json');
|
|
63
|
+
}
|
|
64
|
+
function loadConfig(workspaceRoot) {
|
|
65
|
+
const configPath = getConfigPath(workspaceRoot);
|
|
66
|
+
if (!fs.existsSync(configPath)) {
|
|
67
|
+
return exports.DEFAULT_CONFIG;
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
const raw = fs.readFileSync(configPath, 'utf8');
|
|
71
|
+
const parsed = JSON.parse(raw);
|
|
72
|
+
return {
|
|
73
|
+
...exports.DEFAULT_CONFIG,
|
|
74
|
+
...parsed,
|
|
75
|
+
languages: {
|
|
76
|
+
...exports.DEFAULT_CONFIG.languages,
|
|
77
|
+
...(parsed.languages || {})
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
return exports.DEFAULT_CONFIG;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function saveConfig(workspaceRoot, config) {
|
|
86
|
+
const configPath = getConfigPath(workspaceRoot);
|
|
87
|
+
const dir = path.dirname(configPath);
|
|
88
|
+
if (!fs.existsSync(dir)) {
|
|
89
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
90
|
+
}
|
|
91
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8');
|
|
92
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Saves a manually entered REVEL_SESSION cookie and verifies the session.
|
|
3
|
+
*/
|
|
4
|
+
export declare function loginWithCookie(workspaceRoot: string, revelSession: string): Promise<string>;
|
|
5
|
+
/**
|
|
6
|
+
* Checks the login status by requesting the AtCoder settings page with saved session cookies.
|
|
7
|
+
*/
|
|
8
|
+
export declare function whoami(workspaceRoot: string): Promise<string>;
|