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.
Files changed (70) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +98 -0
  3. package/THIRD_PARTY_LICENSES +21 -0
  4. package/dist/atcoder/client.d.ts +2 -0
  5. package/dist/atcoder/client.js +23 -0
  6. package/dist/atcoder/new.d.ts +15 -0
  7. package/dist/atcoder/new.js +123 -0
  8. package/dist/atcoder/parser/contest-tasks.d.ts +6 -0
  9. package/dist/atcoder/parser/contest-tasks.js +86 -0
  10. package/dist/atcoder/parser/limits.d.ts +10 -0
  11. package/dist/atcoder/parser/limits.js +71 -0
  12. package/dist/atcoder/parser/problem-page.d.ts +12 -0
  13. package/dist/atcoder/parser/problem-page.js +136 -0
  14. package/dist/atcoder/parser/submission-status.d.ts +8 -0
  15. package/dist/atcoder/parser/submission-status.js +78 -0
  16. package/dist/atcoder/submit.d.ts +9 -0
  17. package/dist/atcoder/submit.js +182 -0
  18. package/dist/cli.d.ts +2 -0
  19. package/dist/cli.js +508 -0
  20. package/dist/config/config-store.d.ts +17 -0
  21. package/dist/config/config-store.js +92 -0
  22. package/dist/session/auth.d.ts +8 -0
  23. package/dist/session/auth.js +117 -0
  24. package/dist/session/store.d.ts +15 -0
  25. package/dist/session/store.js +75 -0
  26. package/dist/test-runner/diff.d.ts +7 -0
  27. package/dist/test-runner/diff.js +32 -0
  28. package/dist/test-runner/runner.d.ts +46 -0
  29. package/dist/test-runner/runner.js +274 -0
  30. package/dist/utils/errors.d.ts +15 -0
  31. package/dist/utils/errors.js +35 -0
  32. package/dist/utils/format.d.ts +9 -0
  33. package/dist/utils/format.js +89 -0
  34. package/dist/utils/i18n.d.ts +345 -0
  35. package/dist/utils/i18n.js +413 -0
  36. package/dist/utils/open.d.ts +8 -0
  37. package/dist/utils/open.js +50 -0
  38. package/dist/workspace/finder.d.ts +9 -0
  39. package/dist/workspace/finder.js +62 -0
  40. package/dist/workspace/initializer.d.ts +4 -0
  41. package/dist/workspace/initializer.js +109 -0
  42. package/package.json +38 -0
  43. package/src/atcoder/client.ts +21 -0
  44. package/src/atcoder/new.ts +107 -0
  45. package/src/atcoder/parser/contest-tasks.test.ts +37 -0
  46. package/src/atcoder/parser/contest-tasks.ts +61 -0
  47. package/src/atcoder/parser/limits.test.ts +52 -0
  48. package/src/atcoder/parser/limits.ts +75 -0
  49. package/src/atcoder/parser/problem-page.test.ts +68 -0
  50. package/src/atcoder/parser/problem-page.ts +126 -0
  51. package/src/atcoder/parser/submission-status.test.ts +36 -0
  52. package/src/atcoder/parser/submission-status.ts +54 -0
  53. package/src/atcoder/submit.ts +170 -0
  54. package/src/cli.ts +554 -0
  55. package/src/config/config-store.ts +72 -0
  56. package/src/session/auth.ts +87 -0
  57. package/src/session/store.ts +50 -0
  58. package/src/test-runner/diff.test.ts +26 -0
  59. package/src/test-runner/diff.ts +42 -0
  60. package/src/test-runner/runner.test.ts +70 -0
  61. package/src/test-runner/runner.ts +315 -0
  62. package/src/utils/errors.ts +31 -0
  63. package/src/utils/format.test.ts +69 -0
  64. package/src/utils/format.ts +95 -0
  65. package/src/utils/i18n.test.ts +74 -0
  66. package/src/utils/i18n.ts +418 -0
  67. package/src/utils/open.ts +47 -0
  68. package/src/workspace/finder.ts +29 -0
  69. package/src/workspace/initializer.ts +85 -0
  70. package/tsconfig.json +16 -0
@@ -0,0 +1,117 @@
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.loginWithCookie = loginWithCookie;
37
+ exports.whoami = whoami;
38
+ const cheerio = __importStar(require("cheerio"));
39
+ const store_1 = require("./store");
40
+ const client_1 = require("../atcoder/client");
41
+ const errors_1 = require("../utils/errors");
42
+ /**
43
+ * Saves a manually entered REVEL_SESSION cookie and verifies the session.
44
+ */
45
+ async function loginWithCookie(workspaceRoot, revelSession) {
46
+ let cleanSession = revelSession.trim();
47
+ if (!cleanSession) {
48
+ throw new errors_1.AuthError('REVEL_SESSION cookie value cannot be empty.');
49
+ }
50
+ if (cleanSession.startsWith('REVEL_SESSION=')) {
51
+ cleanSession = cleanSession.substring('REVEL_SESSION='.length);
52
+ }
53
+ cleanSession = cleanSession.split(';')[0].trim();
54
+ if (!cleanSession) {
55
+ throw new errors_1.AuthError('Could not extract a valid REVEL_SESSION value.');
56
+ }
57
+ const savedCookies = [
58
+ {
59
+ name: 'REVEL_SESSION',
60
+ value: cleanSession,
61
+ domain: '.atcoder.jp',
62
+ path: '/',
63
+ expires: Math.floor(Date.now() / 1000) + 86400 * 30,
64
+ httpOnly: true,
65
+ secure: true,
66
+ sameSite: 'Lax'
67
+ }
68
+ ];
69
+ (0, store_1.saveSession)(workspaceRoot, savedCookies);
70
+ try {
71
+ const username = await whoami(workspaceRoot);
72
+ return username;
73
+ }
74
+ catch (err) {
75
+ try {
76
+ const { clearSession } = require('./store');
77
+ clearSession(workspaceRoot);
78
+ }
79
+ catch (e) { }
80
+ throw new errors_1.AuthError(`The provided REVEL_SESSION cookie is invalid or expired: ${err.message}`);
81
+ }
82
+ }
83
+ /**
84
+ * Checks the login status by requesting the AtCoder settings page with saved session cookies.
85
+ */
86
+ async function whoami(workspaceRoot) {
87
+ const session = (0, store_1.loadSession)(workspaceRoot);
88
+ if (!session || !session.some(c => c.name === 'REVEL_SESSION')) {
89
+ throw new errors_1.AuthError('No active session. Please log in using "atc login".');
90
+ }
91
+ const client = (0, client_1.createAtCoderClient)(workspaceRoot);
92
+ try {
93
+ const res = await client.get('/settings');
94
+ const $ = cheerio.load(res.data);
95
+ const userLink = $('header a[href^="/users/"], .navbar-right a[href^="/users/"]').first();
96
+ const href = userLink.attr('href');
97
+ if (href) {
98
+ const match = href.match(/\/users\/([a-zA-Z0-9_]+)/);
99
+ if (match && match[1]) {
100
+ return match[1];
101
+ }
102
+ }
103
+ const usernameSpan = $('header a.username, header .dropdown-toggle').first();
104
+ if (usernameSpan.length) {
105
+ const name = usernameSpan.text().replace(/\s+/g, ' ').trim();
106
+ if (name && name !== 'Sign In' && name !== 'ログイン' && name !== 'Sign Up' && name !== '新規登録') {
107
+ return name;
108
+ }
109
+ }
110
+ throw new errors_1.AuthError('Session is invalid or expired.');
111
+ }
112
+ catch (err) {
113
+ if (err instanceof errors_1.AuthError)
114
+ throw err;
115
+ throw new errors_1.AuthError(`Failed to verify session: ${err.message}`);
116
+ }
117
+ }
@@ -0,0 +1,15 @@
1
+ export interface SavedCookie {
2
+ name: string;
3
+ value: string;
4
+ domain: string;
5
+ path: string;
6
+ expires: number;
7
+ httpOnly: boolean;
8
+ secure: boolean;
9
+ sameSite: 'Strict' | 'Lax' | 'None';
10
+ }
11
+ export declare function getSessionPath(workspaceRoot: string): string;
12
+ export declare function saveSession(workspaceRoot: string, cookies: SavedCookie[]): void;
13
+ export declare function loadSession(workspaceRoot: string): SavedCookie[] | null;
14
+ export declare function clearSession(workspaceRoot: string): void;
15
+ export declare function getCookieHeaderString(cookies: SavedCookie[]): string;
@@ -0,0 +1,75 @@
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.getSessionPath = getSessionPath;
37
+ exports.saveSession = saveSession;
38
+ exports.loadSession = loadSession;
39
+ exports.clearSession = clearSession;
40
+ exports.getCookieHeaderString = getCookieHeaderString;
41
+ const fs = __importStar(require("fs"));
42
+ const path = __importStar(require("path"));
43
+ function getSessionPath(workspaceRoot) {
44
+ return path.join(workspaceRoot, '.atcoder-cli', 'session.json');
45
+ }
46
+ function saveSession(workspaceRoot, cookies) {
47
+ const sessionPath = getSessionPath(workspaceRoot);
48
+ const dir = path.dirname(sessionPath);
49
+ if (!fs.existsSync(dir)) {
50
+ fs.mkdirSync(dir, { recursive: true });
51
+ }
52
+ fs.writeFileSync(sessionPath, JSON.stringify(cookies, null, 2), 'utf8');
53
+ }
54
+ function loadSession(workspaceRoot) {
55
+ const sessionPath = getSessionPath(workspaceRoot);
56
+ if (!fs.existsSync(sessionPath)) {
57
+ return null;
58
+ }
59
+ try {
60
+ const raw = fs.readFileSync(sessionPath, 'utf8');
61
+ return JSON.parse(raw);
62
+ }
63
+ catch (e) {
64
+ return null;
65
+ }
66
+ }
67
+ function clearSession(workspaceRoot) {
68
+ const sessionPath = getSessionPath(workspaceRoot);
69
+ if (fs.existsSync(sessionPath)) {
70
+ fs.unlinkSync(sessionPath);
71
+ }
72
+ }
73
+ function getCookieHeaderString(cookies) {
74
+ return cookies.map(c => `${c.name}=${c.value}`).join('; ');
75
+ }
@@ -0,0 +1,7 @@
1
+ export interface CompareResult {
2
+ isMatch: boolean;
3
+ actualNormalized: string;
4
+ expectedNormalized: string;
5
+ firstDiffLine?: number;
6
+ }
7
+ export declare function compareOutput(actual: string, expected: string): CompareResult;
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.compareOutput = compareOutput;
4
+ function compareOutput(actual, expected) {
5
+ const actualLines = normalizeAndSplit(actual);
6
+ const expectedLines = normalizeAndSplit(expected);
7
+ let isMatch = true;
8
+ let firstDiffLine;
9
+ const maxLines = Math.max(actualLines.length, expectedLines.length);
10
+ for (let i = 0; i < maxLines; i++) {
11
+ if (actualLines[i] !== expectedLines[i]) {
12
+ isMatch = false;
13
+ firstDiffLine = i + 1;
14
+ break;
15
+ }
16
+ }
17
+ return {
18
+ isMatch,
19
+ actualNormalized: actualLines.join('\n'),
20
+ expectedNormalized: expectedLines.join('\n'),
21
+ firstDiffLine
22
+ };
23
+ }
24
+ function normalizeAndSplit(str) {
25
+ const lines = str.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n');
26
+ const trimmed = lines.map(line => line.trimEnd());
27
+ // Remove trailing empty lines
28
+ while (trimmed.length > 0 && trimmed[trimmed.length - 1] === '') {
29
+ trimmed.pop();
30
+ }
31
+ return trimmed;
32
+ }
@@ -0,0 +1,46 @@
1
+ import { Config, LanguageConfig } from '../config/config-store';
2
+ export interface TestCaseResult {
3
+ index: number;
4
+ status: 'AC' | 'WA' | 'TLE' | 'RE';
5
+ durationMs: number;
6
+ actualOutput: string;
7
+ expectedOutput: string;
8
+ errorOutput?: string;
9
+ firstDiffLine?: number;
10
+ }
11
+ export interface RunAllTestsResult {
12
+ success: boolean;
13
+ compileError?: string;
14
+ results: TestCaseResult[];
15
+ }
16
+ /**
17
+ * Resolves the absolute path to a task directory.
18
+ */
19
+ export declare function resolveTaskDirectory(workspaceRoot: string, taskArg?: string): string;
20
+ /**
21
+ * Detects the code file to execute in the task directory.
22
+ */
23
+ export declare function detectCodeFile(workspaceRoot: string, taskDir: string, config: Config, fileArg?: string): {
24
+ codeFile: string;
25
+ langKey: string;
26
+ langConfig: LanguageConfig;
27
+ };
28
+ /**
29
+ * Resolves the build and run commands by substituting template filenames with the actual filename.
30
+ */
31
+ export declare function resolveCommands(workspaceRoot: string, langConfig: LanguageConfig, actualFile: string, extension: string): {
32
+ build: string;
33
+ run: string;
34
+ };
35
+ /**
36
+ * Runs the build command if present.
37
+ */
38
+ export declare function runBuild(buildCommand: string, taskDir: string): Promise<{
39
+ code: number;
40
+ stderr: string;
41
+ }>;
42
+ /**
43
+ * Runs a single test case.
44
+ */
45
+ export declare function runTestCase(runCommand: string, taskDir: string, inputPath: string, outputPath: string, timeLimitMs: number, index: number): Promise<TestCaseResult>;
46
+ export declare function runAllTests(workspaceRoot: string, taskDir: string, fileArg?: string, timeLimitMs?: number): Promise<RunAllTestsResult>;
@@ -0,0 +1,274 @@
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.resolveTaskDirectory = resolveTaskDirectory;
37
+ exports.detectCodeFile = detectCodeFile;
38
+ exports.resolveCommands = resolveCommands;
39
+ exports.runBuild = runBuild;
40
+ exports.runTestCase = runTestCase;
41
+ exports.runAllTests = runAllTests;
42
+ const fs = __importStar(require("fs"));
43
+ const path = __importStar(require("path"));
44
+ const child_process_1 = require("child_process");
45
+ const config_store_1 = require("../config/config-store");
46
+ const diff_1 = require("./diff");
47
+ const errors_1 = require("../utils/errors");
48
+ /**
49
+ * Resolves the absolute path to a task directory.
50
+ */
51
+ function resolveTaskDirectory(workspaceRoot, taskArg) {
52
+ const cwd = process.cwd();
53
+ if (taskArg) {
54
+ const pathFromCwd = path.resolve(cwd, taskArg);
55
+ if (fs.existsSync(pathFromCwd) && fs.statSync(pathFromCwd).isDirectory()) {
56
+ return pathFromCwd;
57
+ }
58
+ const pathFromRoot = path.resolve(workspaceRoot, taskArg);
59
+ if (fs.existsSync(pathFromRoot) && fs.statSync(pathFromRoot).isDirectory()) {
60
+ return pathFromRoot;
61
+ }
62
+ const config = (0, config_store_1.loadConfig)(workspaceRoot);
63
+ if (config.contestDir) {
64
+ const pathFromConfigDir = path.resolve(workspaceRoot, config.contestDir, taskArg);
65
+ if (fs.existsSync(pathFromConfigDir) && fs.statSync(pathFromConfigDir).isDirectory()) {
66
+ return pathFromConfigDir;
67
+ }
68
+ }
69
+ const labelPath = path.join(cwd, taskArg);
70
+ if (fs.existsSync(labelPath) && fs.statSync(labelPath).isDirectory()) {
71
+ return labelPath;
72
+ }
73
+ throw new errors_1.AtcError(`Task directory "${taskArg}" not found.`);
74
+ }
75
+ if (path.resolve(cwd) === path.resolve(workspaceRoot)) {
76
+ throw new errors_1.AtcError('You are in the workspace root. Please specify a task directory (e.g., "atc test abc300/a").');
77
+ }
78
+ return cwd;
79
+ }
80
+ /**
81
+ * Detects the code file to execute in the task directory.
82
+ */
83
+ function detectCodeFile(workspaceRoot, taskDir, config, fileArg) {
84
+ if (fileArg) {
85
+ const fullPath = path.resolve(taskDir, fileArg);
86
+ if (!fs.existsSync(fullPath)) {
87
+ throw new errors_1.AtcError(`Specified source file "${fileArg}" not found in "${taskDir}"`);
88
+ }
89
+ const ext = path.extname(fileArg).slice(1);
90
+ for (const [key, langConfig] of Object.entries(config.languages)) {
91
+ if (langConfig.extension === ext) {
92
+ return { codeFile: fileArg, langKey: key, langConfig };
93
+ }
94
+ }
95
+ throw new errors_1.AtcError(`No language configuration found for file extension ".${ext}"`);
96
+ }
97
+ const files = fs.readdirSync(taskDir);
98
+ const defLang = config.languages[config.defaultLanguage];
99
+ if (defLang) {
100
+ const matchedFile = files.find(f => f.endsWith(`.${defLang.extension}`) && fs.statSync(path.join(taskDir, f)).isFile());
101
+ if (matchedFile) {
102
+ return { codeFile: matchedFile, langKey: config.defaultLanguage, langConfig: defLang };
103
+ }
104
+ }
105
+ for (const [key, langConfig] of Object.entries(config.languages)) {
106
+ if (key === config.defaultLanguage)
107
+ continue;
108
+ const matchedFile = files.find(f => f.endsWith(`.${langConfig.extension}`) && fs.statSync(path.join(taskDir, f)).isFile());
109
+ if (matchedFile) {
110
+ return { codeFile: matchedFile, langKey: key, langConfig };
111
+ }
112
+ }
113
+ throw new errors_1.AtcError(`No source files found in "${taskDir}" matching configured languages.`);
114
+ }
115
+ /**
116
+ * Resolves the build and run commands by substituting template filenames with the actual filename.
117
+ */
118
+ function resolveCommands(workspaceRoot, langConfig, actualFile, extension) {
119
+ const templateDir = langConfig.templateDir;
120
+ let templateFileName = `main.${extension}`;
121
+ const fullTemplatePath = path.join(workspaceRoot, '.atcoder-cli', templateDir);
122
+ if (fs.existsSync(fullTemplatePath) && fs.statSync(fullTemplatePath).isDirectory()) {
123
+ const files = fs.readdirSync(fullTemplatePath);
124
+ const matched = files.find(f => f.endsWith(`.${extension}`));
125
+ if (matched) {
126
+ templateFileName = matched;
127
+ }
128
+ }
129
+ const escapedTemplateName = templateFileName.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
130
+ const regex = new RegExp(escapedTemplateName, 'g');
131
+ const resolvedBuild = langConfig.build.replace(regex, actualFile);
132
+ const resolvedRun = langConfig.run.replace(regex, actualFile);
133
+ return {
134
+ build: resolvedBuild,
135
+ run: resolvedRun
136
+ };
137
+ }
138
+ /**
139
+ * Runs the build command if present.
140
+ */
141
+ function runBuild(buildCommand, taskDir) {
142
+ return new Promise((resolve) => {
143
+ (0, child_process_1.exec)(buildCommand, { cwd: taskDir }, (err, stdout, stderr) => {
144
+ resolve({
145
+ code: err ? (err.code ?? 1) : 0,
146
+ stderr: stderr || stdout
147
+ });
148
+ });
149
+ });
150
+ }
151
+ /**
152
+ * Runs a single test case.
153
+ */
154
+ function runTestCase(runCommand, taskDir, inputPath, outputPath, timeLimitMs, index) {
155
+ return new Promise((resolve) => {
156
+ const input = fs.readFileSync(inputPath, 'utf8');
157
+ const expected = fs.readFileSync(outputPath, 'utf8');
158
+ const startTime = process.hrtime.bigint();
159
+ const child = (0, child_process_1.spawn)(runCommand, {
160
+ cwd: taskDir,
161
+ shell: true
162
+ });
163
+ let stdout = '';
164
+ let stderr = '';
165
+ let killedByTimeout = false;
166
+ const timer = setTimeout(() => {
167
+ killedByTimeout = true;
168
+ child.kill('SIGKILL');
169
+ }, timeLimitMs);
170
+ child.stdout.on('data', (data) => {
171
+ stdout += data.toString();
172
+ });
173
+ child.stderr.on('data', (data) => {
174
+ stderr += data.toString();
175
+ });
176
+ child.on('error', (err) => {
177
+ clearTimeout(timer);
178
+ const endTime = process.hrtime.bigint();
179
+ const durationMs = Number(endTime - startTime) / 1e6;
180
+ resolve({
181
+ index,
182
+ status: 'RE',
183
+ durationMs,
184
+ actualOutput: stdout,
185
+ expectedOutput: expected,
186
+ errorOutput: err.message
187
+ });
188
+ });
189
+ child.on('exit', (code, signal) => {
190
+ clearTimeout(timer);
191
+ const endTime = process.hrtime.bigint();
192
+ const durationMs = Number(endTime - startTime) / 1e6;
193
+ if (killedByTimeout || signal === 'SIGKILL') {
194
+ resolve({
195
+ index,
196
+ status: 'TLE',
197
+ durationMs,
198
+ actualOutput: stdout,
199
+ expectedOutput: expected,
200
+ errorOutput: 'Time Limit Exceeded'
201
+ });
202
+ return;
203
+ }
204
+ if (code !== 0) {
205
+ resolve({
206
+ index,
207
+ status: 'RE',
208
+ durationMs,
209
+ actualOutput: stdout,
210
+ expectedOutput: expected,
211
+ errorOutput: stderr || `Exit code ${code}`
212
+ });
213
+ return;
214
+ }
215
+ const comp = (0, diff_1.compareOutput)(stdout, expected);
216
+ resolve({
217
+ index,
218
+ status: comp.isMatch ? 'AC' : 'WA',
219
+ durationMs,
220
+ actualOutput: stdout,
221
+ expectedOutput: expected,
222
+ firstDiffLine: comp.firstDiffLine
223
+ });
224
+ });
225
+ child.stdin.write(input);
226
+ child.stdin.end();
227
+ });
228
+ }
229
+ async function runAllTests(workspaceRoot, taskDir, fileArg, timeLimitMs = 2000) {
230
+ const config = (0, config_store_1.loadConfig)(workspaceRoot);
231
+ const { codeFile, langConfig } = detectCodeFile(workspaceRoot, taskDir, config, fileArg);
232
+ const { build, run } = resolveCommands(workspaceRoot, langConfig, codeFile, langConfig.extension);
233
+ if (build.trim() !== '') {
234
+ const buildRes = await runBuild(build, taskDir);
235
+ if (buildRes.code !== 0) {
236
+ return {
237
+ success: false,
238
+ compileError: buildRes.stderr,
239
+ results: []
240
+ };
241
+ }
242
+ }
243
+ const testDirName = config.testDirName || 'tests';
244
+ const testDir = path.join(taskDir, testDirName);
245
+ if (!fs.existsSync(testDir) || !fs.statSync(testDir).isDirectory()) {
246
+ throw new errors_1.AtcError(`Test directory "${testDir}" not found.`);
247
+ }
248
+ const files = fs.readdirSync(testDir);
249
+ const inFiles = files.filter(f => f.startsWith('sample-') && f.endsWith('.in'));
250
+ const results = [];
251
+ inFiles.sort((a, b) => {
252
+ const aIdx = parseInt(a.match(/sample-(\d+)\.in/)[1], 10);
253
+ const bIdx = parseInt(b.match(/sample-(\d+)\.in/)[1], 10);
254
+ return aIdx - bIdx;
255
+ });
256
+ for (const inFile of inFiles) {
257
+ const match = inFile.match(/sample-(\d+)\.in/);
258
+ if (!match)
259
+ continue;
260
+ const index = parseInt(match[1], 10);
261
+ const inputPath = path.join(testDir, inFile);
262
+ const outputPath = path.join(testDir, `sample-${index}.out`);
263
+ if (!fs.existsSync(outputPath)) {
264
+ continue;
265
+ }
266
+ const testRes = await runTestCase(run, taskDir, inputPath, outputPath, timeLimitMs, index);
267
+ results.push(testRes);
268
+ }
269
+ const success = results.length > 0 && results.every(r => r.status === 'AC');
270
+ return {
271
+ success,
272
+ results
273
+ };
274
+ }
@@ -0,0 +1,15 @@
1
+ export declare class AtcError extends Error {
2
+ constructor(message: string);
3
+ }
4
+ export declare class WorkspaceNotFoundError extends AtcError {
5
+ constructor();
6
+ }
7
+ export declare class AuthError extends AtcError {
8
+ constructor(message: string);
9
+ }
10
+ export declare class ParseError extends AtcError {
11
+ constructor(message: string);
12
+ }
13
+ export declare class TestError extends AtcError {
14
+ constructor(message: string);
15
+ }
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TestError = exports.ParseError = exports.AuthError = exports.WorkspaceNotFoundError = exports.AtcError = void 0;
4
+ class AtcError extends Error {
5
+ constructor(message) {
6
+ super(message);
7
+ this.name = this.constructor.name;
8
+ Error.captureStackTrace(this, this.constructor);
9
+ }
10
+ }
11
+ exports.AtcError = AtcError;
12
+ class WorkspaceNotFoundError extends AtcError {
13
+ constructor() {
14
+ super('.atcoder-cli/ directory was not found. Please run "atc init" in your workspace root first.');
15
+ }
16
+ }
17
+ exports.WorkspaceNotFoundError = WorkspaceNotFoundError;
18
+ class AuthError extends AtcError {
19
+ constructor(message) {
20
+ super(`Authentication failed: ${message}`);
21
+ }
22
+ }
23
+ exports.AuthError = AuthError;
24
+ class ParseError extends AtcError {
25
+ constructor(message) {
26
+ super(`Parsing failed: ${message}`);
27
+ }
28
+ }
29
+ exports.ParseError = ParseError;
30
+ class TestError extends AtcError {
31
+ constructor(message) {
32
+ super(`Test execution failed: ${message}`);
33
+ }
34
+ }
35
+ exports.TestError = TestError;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Formats multi-line output with line numbers, a left border,
3
+ * a mismatch pointer, and smart truncation for large outputs.
4
+ */
5
+ export declare function formatOutputLines(output: string, firstDiffLine?: number): string[];
6
+ /**
7
+ * Formats error output with red text, a left border, and clean indents.
8
+ */
9
+ export declare function formatErrorOutputLines(errorOutput: string): string[];