@exercode/problem-utils 2.3.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -0
- package/dist/cli/checkAllProblems.cjs +1 -1
- package/dist/cli/checkAllProblems.cjs.map +1 -1
- package/dist/cli/checkAllProblems.js +1 -1
- package/dist/cli/checkAllProblems.js.map +1 -1
- package/dist/cli/runSingleHarness.cjs +1 -1
- package/dist/cli/runSingleHarness.js +1 -1
- package/dist/helpers/checkProblemDirIsolation.cjs +1 -1
- package/dist/helpers/checkProblemDirIsolation.cjs.map +1 -1
- package/dist/helpers/checkProblemDirIsolation.d.ts +9 -16
- package/dist/helpers/checkProblemDirIsolation.js +1 -1
- package/dist/helpers/checkProblemDirIsolation.js.map +1 -1
- package/dist/helpers/cleanWorkingDirectory.cjs +1 -1
- package/dist/helpers/cleanWorkingDirectory.js +1 -1
- package/dist/helpers/compareExpectedOutputFiles.cjs +2 -0
- package/dist/helpers/compareExpectedOutputFiles.cjs.map +1 -0
- package/dist/helpers/compareExpectedOutputFiles.d.ts +45 -0
- package/dist/helpers/compareExpectedOutputFiles.js +2 -0
- package/dist/helpers/compareExpectedOutputFiles.js.map +1 -0
- package/dist/helpers/compareStdoutAsSpaceSeparatedTokens.cjs +1 -1
- package/dist/helpers/compareStdoutAsSpaceSeparatedTokens.cjs.map +1 -1
- package/dist/helpers/compareStdoutAsSpaceSeparatedTokens.js +1 -1
- package/dist/helpers/compareStdoutAsSpaceSeparatedTokens.js.map +1 -1
- package/dist/helpers/defaultStdioHarness.cjs +1 -1
- package/dist/helpers/defaultStdioHarness.js +1 -1
- package/dist/helpers/findModelAnswerDirs.cjs +1 -1
- package/dist/helpers/findModelAnswerDirs.js +1 -1
- package/dist/helpers/judgeByStaticAnalysis.cjs +1 -1
- package/dist/helpers/judgeByStaticAnalysis.js +1 -1
- package/dist/helpers/readOutputFiles.cjs +1 -1
- package/dist/helpers/readOutputFiles.js +1 -1
- package/dist/helpers/readProblemMarkdownFrontMatter.cjs +1 -1
- package/dist/helpers/readProblemMarkdownFrontMatter.js +1 -1
- package/dist/helpers/readTestCases.cjs +1 -1
- package/dist/helpers/readTestCases.cjs.map +1 -1
- package/dist/helpers/readTestCases.d.ts +6 -0
- package/dist/helpers/readTestCases.js +1 -1
- package/dist/helpers/readTestCases.js.map +1 -1
- package/dist/helpers/runCommandInTemporaryPackageManagerProject.cjs +1 -1
- package/dist/helpers/runCommandInTemporaryPackageManagerProject.js +1 -1
- package/dist/helpers/runCommandInTemporaryPackageManagerProject.js.map +1 -1
- package/dist/helpers/runHarnessProcess.cjs +2 -0
- package/dist/helpers/runHarnessProcess.cjs.map +1 -0
- package/dist/helpers/runHarnessProcess.d.ts +23 -0
- package/dist/helpers/runHarnessProcess.js +2 -0
- package/dist/helpers/runHarnessProcess.js.map +1 -0
- package/dist/helpers/runIsolationCheckInDebugMode.cjs +2 -0
- package/dist/helpers/runIsolationCheckInDebugMode.cjs.map +1 -0
- package/dist/helpers/runIsolationCheckInDebugMode.d.ts +7 -0
- package/dist/helpers/runIsolationCheckInDebugMode.js +2 -0
- package/dist/helpers/runIsolationCheckInDebugMode.js.map +1 -0
- package/dist/helpers/safeFs.cjs +1 -1
- package/dist/helpers/safeFs.js +1 -1
- package/dist/helpers/sandboxUser.cjs +2 -2
- package/dist/helpers/sandboxUser.cjs.map +1 -1
- package/dist/helpers/sandboxUser.d.ts +5 -0
- package/dist/helpers/sandboxUser.js +2 -2
- package/dist/helpers/sandboxUser.js.map +1 -1
- package/dist/helpers/startHttpServer.cjs +1 -1
- package/dist/helpers/startHttpServer.js +1 -1
- package/dist/helpers/temporaryProblemDirCopy.cjs +2 -0
- package/dist/helpers/temporaryProblemDirCopy.cjs.map +1 -0
- package/dist/helpers/temporaryProblemDirCopy.d.ts +18 -0
- package/dist/helpers/temporaryProblemDirCopy.js +2 -0
- package/dist/helpers/temporaryProblemDirCopy.js.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1 -1
- package/dist/presets/command.cjs +1 -1
- package/dist/presets/command.cjs.map +1 -1
- package/dist/presets/command.d.ts +35 -3
- package/dist/presets/command.js +1 -1
- package/dist/presets/command.js.map +1 -1
- package/dist/presets/evaluation.cjs +4 -4
- package/dist/presets/evaluation.cjs.map +1 -1
- package/dist/presets/evaluation.js +4 -4
- package/dist/presets/evaluation.js.map +1 -1
- package/dist/presets/guiCommand.cjs +3 -3
- package/dist/presets/guiCommand.cjs.map +1 -1
- package/dist/presets/guiCommand.d.ts +14 -2
- package/dist/presets/guiCommand.js +3 -3
- package/dist/presets/guiCommand.js.map +1 -1
- package/dist/presets/llm.cjs +1 -1
- package/dist/presets/llm.cjs.map +1 -1
- package/dist/presets/llm.d.ts +12 -10
- package/dist/presets/llm.js +1 -1
- package/dist/presets/llm.js.map +1 -1
- package/dist/presets/stdio.cjs +1 -1
- package/dist/presets/stdio.cjs.map +1 -1
- package/dist/presets/stdio.d.ts +3 -1
- package/dist/presets/stdio.js +1 -1
- package/dist/presets/stdio.js.map +1 -1
- package/dist/types/language.cjs +2 -2
- package/dist/types/language.js +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./compareStdoutAsSpaceSeparatedTokens.cjs"),n=require("./printTestCaseResult.cjs"),r=require("./readOutputFiles.cjs");let i=require("node:fs");i=e.__toESM(i,1);let a=require("node:path");a=e.__toESM(a,1);const o=8388608;async function s(e,t){let r=[],s=[],c=(await i.default.promises.readdir(t,{withFileTypes:!0,recursive:!0})).filter(e=>e.isFile()).map(e=>({absolutePath:a.default.join(e.parentPath,e.name),relativePath:a.default.posix.join(...a.default.relative(t,e.parentPath).split(a.default.sep),e.name)})).toSorted((e,t)=>e.relativePath<t.relativePath?-1:+(e.relativePath>t.relativePath));for(let{absolutePath:t,relativePath:l}of c){if((await i.default.promises.stat(t)).size>8388608)throw Error(`expected output file ${l} exceeds ${o} bytes`);let c=await i.default.promises.readFile(t),p=await d(e,a.default.join(e,l));p&&f(c,p)||(r.push(l),c.length<=1048576&&s.push(n.encodeFileForTestCaseResult(u(l,`expected`),c)),p&&p.length<=1048576&&s.push(n.encodeFileForTestCaseResult(u(l,`received`),p)))}return{matches:r.length===0,mismatchedPaths:r,outputFiles:s}}async function c(e){let{stdout:n,expectedStdout:r,fileOutputPath:i,cwd:a}=e,o=[...e.outputFiles],c=r===void 0||t.compareStdoutAsSpaceSeparatedTokens(n,r);if(i!==void 0){let e=await s(a,i);e.matches||(c=!1,o=l(o,e))}return{matches:c,outputFiles:o}}function l(e,t){let n=new Set(t.outputFiles.map(e=>e.path)),r=new Set(t.mismatchedPaths.filter(e=>n.has(u(e,`received`))));return[...e.filter(e=>!r.has(e.path)&&!n.has(e.path)),...t.outputFiles]}function u(e,t){let{dir:n,name:r,ext:i}=a.default.posix.parse(e);return a.default.posix.join(n,`${r}_${t}${i}`)}async function d(e,t){if(await r.isSafeSubmissionOutputPath(e,t))try{let e=await i.default.promises.stat(t);return!e.isFile()||e.size>8388608?void 0:await i.default.promises.readFile(t)}catch{return}}function f(e,n){return m(e)?m(n)?t.compareStdoutAsSpaceSeparatedTokens(n.toString(`utf8`),e.toString(`utf8`)):!1:n.equals(e)}const p=new TextDecoder(`utf-8`,{fatal:!0});function m(e){if(e.includes(0))return!1;try{return p.decode(e),!0}catch{return!1}}exports.MAX_COMPARED_FILE_BYTES=o,exports.MAX_REPORTED_FILE_BYTES=1048576,exports.compareExpectedOutputFiles=s,exports.judgeAgainstExpectations=c,exports.mergeComparisonOutputFiles=l;
|
|
2
|
+
//# sourceMappingURL=compareExpectedOutputFiles.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compareExpectedOutputFiles.cjs","names":["fs","path","encodeFileForTestCaseResult","compareStdoutAsSpaceSeparatedTokens","isSafeSubmissionOutputPath"],"sources":["../../src/helpers/compareExpectedOutputFiles.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport type { TestCaseResult } from '../types/testCaseResult.js';\n\nimport { compareStdoutAsSpaceSeparatedTokens } from './compareStdoutAsSpaceSeparatedTokens.js';\nimport { encodeFileForTestCaseResult } from './printTestCaseResult.js';\nimport { isSafeSubmissionOutputPath } from './readOutputFiles.js';\n\ntype OutputFile = NonNullable<TestCaseResult['outputFiles']>[number];\n\n// A submission controls the received files, so bound what the trusted harness reads and reports.\nexport const MAX_COMPARED_FILE_BYTES = 8 * 1024 * 1024;\nexport const MAX_REPORTED_FILE_BYTES = 1024 * 1024;\n\nexport interface ExpectedOutputFilesComparison {\n /** Whether every file under the expected directory has a matching file in the working directory. */\n matches: boolean;\n /** Relative paths (from the working directory) of the files that are missing or different. */\n mismatchedPaths: string[];\n /**\n * `<name>_expected.<ext>` / `<name>_received.<ext>` pairs for each mismatched file, in the\n * format Exercode renders side by side. A received file that is missing, unreadable, not a\n * regular file or larger than `MAX_COMPARED_FILE_BYTES` yields only the expected entry, and a\n * file larger than `MAX_REPORTED_FILE_BYTES` is left out of the pair. Exercode decides per test case whether a learner may see these files.\n */\n outputFiles: OutputFile[];\n}\n\n/**\n * Compare the files under a test case's `.fout` directory with the files the submission wrote to\n * its working directory. Text files are compared like stdout (space-separated tokens with a\n * numeric tolerance); binary files must match byte for byte.\n */\nexport async function compareExpectedOutputFiles(\n cwd: string,\n fileOutputPath: string\n): Promise<ExpectedOutputFilesComparison> {\n const mismatchedPaths: string[] = [];\n const outputFiles: OutputFile[] = [];\n const dirents = await fs.promises.readdir(fileOutputPath, { withFileTypes: true, recursive: true });\n const expectedFiles = dirents\n .filter((dirent) => dirent.isFile())\n .map((dirent) => ({\n absolutePath: path.join(dirent.parentPath, dirent.name),\n // POSIX separators: `requiredOutputFilePaths` and the result format use them on every platform.\n relativePath: path.posix.join(...path.relative(fileOutputPath, dirent.parentPath).split(path.sep), dirent.name),\n }))\n .toSorted((a, b) => (a.relativePath < b.relativePath ? -1 : a.relativePath > b.relativePath ? 1 : 0));\n for (const { absolutePath, relativePath } of expectedFiles) {\n // A larger expectation could never be matched by a received file, so it is an authoring error.\n const expectedStats = await fs.promises.stat(absolutePath);\n if (expectedStats.size > MAX_COMPARED_FILE_BYTES) {\n throw new Error(`expected output file ${relativePath} exceeds ${MAX_COMPARED_FILE_BYTES} bytes`);\n }\n const expected = await fs.promises.readFile(absolutePath);\n const received = await readReceivedFile(cwd, path.join(cwd, relativePath));\n if (received && fileContentsMatch(expected, received)) continue;\n\n mismatchedPaths.push(relativePath);\n // A file too large to ship in a result line is left out of the pair rather than truncated.\n if (expected.length <= MAX_REPORTED_FILE_BYTES) {\n outputFiles.push(encodeFileForTestCaseResult(toComparisonPath(relativePath, 'expected'), expected));\n }\n if (received && received.length <= MAX_REPORTED_FILE_BYTES) {\n outputFiles.push(encodeFileForTestCaseResult(toComparisonPath(relativePath, 'received'), received));\n }\n }\n return { matches: mismatchedPaths.length === 0, mismatchedPaths, outputFiles };\n}\n\n/**\n * The default verdict of the presets: `.out` decides stdout and `.fout/` decides output files, both\n * checked so a result carries every mismatch; either expectation may be absent. Returns whether the\n * run matched and the output files to report (the plain copies of mismatched required output files\n * replaced by their comparison pairs).\n */\nexport async function judgeAgainstExpectations(context: {\n stdout: string;\n expectedStdout: string | undefined;\n fileOutputPath: string | undefined;\n cwd: string;\n outputFiles: readonly OutputFile[];\n}): Promise<{ matches: boolean; outputFiles: OutputFile[] }> {\n const { stdout, expectedStdout, fileOutputPath, cwd } = context;\n let outputFiles = [...context.outputFiles];\n let matches = expectedStdout === undefined || compareStdoutAsSpaceSeparatedTokens(stdout, expectedStdout);\n if (fileOutputPath !== undefined) {\n const comparison = await compareExpectedOutputFiles(cwd, fileOutputPath);\n if (!comparison.matches) {\n matches = false;\n outputFiles = mergeComparisonOutputFiles(outputFiles, comparison);\n }\n }\n return { matches, outputFiles };\n}\n\n/**\n * Replace the plain copies of mismatched required output files with the comparison pairs, so a\n * result shows `<name>_expected.<ext>` / `<name>_received.<ext>` instead of a lone `<name>.<ext>`.\n */\nexport function mergeComparisonOutputFiles(\n outputFiles: readonly OutputFile[],\n comparison: ExpectedOutputFilesComparison\n): OutputFile[] {\n const comparisonPaths = new Set(comparison.outputFiles.map((file) => file.path));\n // The plain copy gives way to its `_received` entry; a mismatch too large for a pair keeps the\n // plain copy, and a kept file literally named like a pair entry would make the path ambiguous.\n const replacedPaths = new Set(\n comparison.mismatchedPaths.filter((filePath) => comparisonPaths.has(toComparisonPath(filePath, 'received')))\n );\n return [\n ...outputFiles.filter((file) => !replacedPaths.has(file.path) && !comparisonPaths.has(file.path)),\n ...comparison.outputFiles,\n ];\n}\n\n/** `c.txt` → `c_expected.txt`; `out/c.txt` → `out/c_expected.txt`. */\nfunction toComparisonPath(filePath: string, role: 'expected' | 'received'): string {\n const { dir, name, ext } = path.posix.parse(filePath);\n return path.posix.join(dir, `${name}_${role}${ext}`);\n}\n\n// A submission controls this path, so anything that is not a plain, readable file within the size\n// limit (a directory, a FIFO that would block, an unreadable or huge file) counts as \"not produced\".\nasync function readReceivedFile(cwd: string, receivedPath: string): Promise<Buffer | undefined> {\n // A submission-planted symlink to the expected file itself would otherwise compare equal.\n if (!(await isSafeSubmissionOutputPath(cwd, receivedPath))) return undefined;\n try {\n const stats = await fs.promises.stat(receivedPath);\n if (!stats.isFile() || stats.size > MAX_COMPARED_FILE_BYTES) return undefined;\n return await fs.promises.readFile(receivedPath);\n } catch {\n return undefined;\n }\n}\n\nfunction fileContentsMatch(expected: Buffer, received: Buffer): boolean {\n if (!isTextFile(expected)) return received.equals(expected);\n if (!isTextFile(received)) return false;\n return compareStdoutAsSpaceSeparatedTokens(received.toString('utf8'), expected.toString('utf8'));\n}\n\nconst utf8Decoder = new TextDecoder('utf-8', { fatal: true });\n\n// Valid UTF-8 without NUL bytes: a binary format that happens to decode (e.g. a PNG chunk) almost\n// always contains NUL, while text files containing U+FFFD are still treated as text.\nfunction isTextFile(data: Buffer): boolean {\n if (data.includes(0)) return false;\n try {\n utf8Decoder.decode(data);\n return true;\n } catch {\n return false;\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { TestCaseResult } from '../types/testCaseResult.js';
|
|
2
|
+
type OutputFile = NonNullable<TestCaseResult['outputFiles']>[number];
|
|
3
|
+
export declare const MAX_COMPARED_FILE_BYTES: number;
|
|
4
|
+
export declare const MAX_REPORTED_FILE_BYTES: number;
|
|
5
|
+
export interface ExpectedOutputFilesComparison {
|
|
6
|
+
/** Whether every file under the expected directory has a matching file in the working directory. */
|
|
7
|
+
matches: boolean;
|
|
8
|
+
/** Relative paths (from the working directory) of the files that are missing or different. */
|
|
9
|
+
mismatchedPaths: string[];
|
|
10
|
+
/**
|
|
11
|
+
* `<name>_expected.<ext>` / `<name>_received.<ext>` pairs for each mismatched file, in the
|
|
12
|
+
* format Exercode renders side by side. A received file that is missing, unreadable, not a
|
|
13
|
+
* regular file or larger than `MAX_COMPARED_FILE_BYTES` yields only the expected entry, and a
|
|
14
|
+
* file larger than `MAX_REPORTED_FILE_BYTES` is left out of the pair. Exercode decides per test case whether a learner may see these files.
|
|
15
|
+
*/
|
|
16
|
+
outputFiles: OutputFile[];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Compare the files under a test case's `.fout` directory with the files the submission wrote to
|
|
20
|
+
* its working directory. Text files are compared like stdout (space-separated tokens with a
|
|
21
|
+
* numeric tolerance); binary files must match byte for byte.
|
|
22
|
+
*/
|
|
23
|
+
export declare function compareExpectedOutputFiles(cwd: string, fileOutputPath: string): Promise<ExpectedOutputFilesComparison>;
|
|
24
|
+
/**
|
|
25
|
+
* The default verdict of the presets: `.out` decides stdout and `.fout/` decides output files, both
|
|
26
|
+
* checked so a result carries every mismatch; either expectation may be absent. Returns whether the
|
|
27
|
+
* run matched and the output files to report (the plain copies of mismatched required output files
|
|
28
|
+
* replaced by their comparison pairs).
|
|
29
|
+
*/
|
|
30
|
+
export declare function judgeAgainstExpectations(context: {
|
|
31
|
+
stdout: string;
|
|
32
|
+
expectedStdout: string | undefined;
|
|
33
|
+
fileOutputPath: string | undefined;
|
|
34
|
+
cwd: string;
|
|
35
|
+
outputFiles: readonly OutputFile[];
|
|
36
|
+
}): Promise<{
|
|
37
|
+
matches: boolean;
|
|
38
|
+
outputFiles: OutputFile[];
|
|
39
|
+
}>;
|
|
40
|
+
/**
|
|
41
|
+
* Replace the plain copies of mismatched required output files with the comparison pairs, so a
|
|
42
|
+
* result shows `<name>_expected.<ext>` / `<name>_received.<ext>` instead of a lone `<name>.<ext>`.
|
|
43
|
+
*/
|
|
44
|
+
export declare function mergeComparisonOutputFiles(outputFiles: readonly OutputFile[], comparison: ExpectedOutputFilesComparison): OutputFile[];
|
|
45
|
+
export {};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{compareStdoutAsSpaceSeparatedTokens as e}from"./compareStdoutAsSpaceSeparatedTokens.js";import{encodeFileForTestCaseResult as t}from"./printTestCaseResult.js";import{isSafeSubmissionOutputPath as n}from"./readOutputFiles.js";import r from"node:fs";import i from"node:path";const a=8388608,o=1048576;async function s(e,n){let o=[],s=[],c=(await r.promises.readdir(n,{withFileTypes:!0,recursive:!0})).filter(e=>e.isFile()).map(e=>({absolutePath:i.join(e.parentPath,e.name),relativePath:i.posix.join(...i.relative(n,e.parentPath).split(i.sep),e.name)})).toSorted((e,t)=>e.relativePath<t.relativePath?-1:+(e.relativePath>t.relativePath));for(let{absolutePath:n,relativePath:l}of c){if((await r.promises.stat(n)).size>8388608)throw Error(`expected output file ${l} exceeds ${a} bytes`);let c=await r.promises.readFile(n),p=await d(e,i.join(e,l));p&&f(c,p)||(o.push(l),c.length<=1048576&&s.push(t(u(l,`expected`),c)),p&&p.length<=1048576&&s.push(t(u(l,`received`),p)))}return{matches:o.length===0,mismatchedPaths:o,outputFiles:s}}async function c(t){let{stdout:n,expectedStdout:r,fileOutputPath:i,cwd:a}=t,o=[...t.outputFiles],c=r===void 0||e(n,r);if(i!==void 0){let e=await s(a,i);e.matches||(c=!1,o=l(o,e))}return{matches:c,outputFiles:o}}function l(e,t){let n=new Set(t.outputFiles.map(e=>e.path)),r=new Set(t.mismatchedPaths.filter(e=>n.has(u(e,`received`))));return[...e.filter(e=>!r.has(e.path)&&!n.has(e.path)),...t.outputFiles]}function u(e,t){let{dir:n,name:r,ext:a}=i.posix.parse(e);return i.posix.join(n,`${r}_${t}${a}`)}async function d(e,t){if(await n(e,t))try{let e=await r.promises.stat(t);return!e.isFile()||e.size>8388608?void 0:await r.promises.readFile(t)}catch{return}}function f(t,n){return m(t)?m(n)?e(n.toString(`utf8`),t.toString(`utf8`)):!1:n.equals(t)}const p=new TextDecoder(`utf-8`,{fatal:!0});function m(e){if(e.includes(0))return!1;try{return p.decode(e),!0}catch{return!1}}export{a as MAX_COMPARED_FILE_BYTES,o as MAX_REPORTED_FILE_BYTES,s as compareExpectedOutputFiles,c as judgeAgainstExpectations,l as mergeComparisonOutputFiles};
|
|
2
|
+
//# sourceMappingURL=compareExpectedOutputFiles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compareExpectedOutputFiles.js","names":[],"sources":["../../src/helpers/compareExpectedOutputFiles.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport type { TestCaseResult } from '../types/testCaseResult.js';\n\nimport { compareStdoutAsSpaceSeparatedTokens } from './compareStdoutAsSpaceSeparatedTokens.js';\nimport { encodeFileForTestCaseResult } from './printTestCaseResult.js';\nimport { isSafeSubmissionOutputPath } from './readOutputFiles.js';\n\ntype OutputFile = NonNullable<TestCaseResult['outputFiles']>[number];\n\n// A submission controls the received files, so bound what the trusted harness reads and reports.\nexport const MAX_COMPARED_FILE_BYTES = 8 * 1024 * 1024;\nexport const MAX_REPORTED_FILE_BYTES = 1024 * 1024;\n\nexport interface ExpectedOutputFilesComparison {\n /** Whether every file under the expected directory has a matching file in the working directory. */\n matches: boolean;\n /** Relative paths (from the working directory) of the files that are missing or different. */\n mismatchedPaths: string[];\n /**\n * `<name>_expected.<ext>` / `<name>_received.<ext>` pairs for each mismatched file, in the\n * format Exercode renders side by side. A received file that is missing, unreadable, not a\n * regular file or larger than `MAX_COMPARED_FILE_BYTES` yields only the expected entry, and a\n * file larger than `MAX_REPORTED_FILE_BYTES` is left out of the pair. Exercode decides per test case whether a learner may see these files.\n */\n outputFiles: OutputFile[];\n}\n\n/**\n * Compare the files under a test case's `.fout` directory with the files the submission wrote to\n * its working directory. Text files are compared like stdout (space-separated tokens with a\n * numeric tolerance); binary files must match byte for byte.\n */\nexport async function compareExpectedOutputFiles(\n cwd: string,\n fileOutputPath: string\n): Promise<ExpectedOutputFilesComparison> {\n const mismatchedPaths: string[] = [];\n const outputFiles: OutputFile[] = [];\n const dirents = await fs.promises.readdir(fileOutputPath, { withFileTypes: true, recursive: true });\n const expectedFiles = dirents\n .filter((dirent) => dirent.isFile())\n .map((dirent) => ({\n absolutePath: path.join(dirent.parentPath, dirent.name),\n // POSIX separators: `requiredOutputFilePaths` and the result format use them on every platform.\n relativePath: path.posix.join(...path.relative(fileOutputPath, dirent.parentPath).split(path.sep), dirent.name),\n }))\n .toSorted((a, b) => (a.relativePath < b.relativePath ? -1 : a.relativePath > b.relativePath ? 1 : 0));\n for (const { absolutePath, relativePath } of expectedFiles) {\n // A larger expectation could never be matched by a received file, so it is an authoring error.\n const expectedStats = await fs.promises.stat(absolutePath);\n if (expectedStats.size > MAX_COMPARED_FILE_BYTES) {\n throw new Error(`expected output file ${relativePath} exceeds ${MAX_COMPARED_FILE_BYTES} bytes`);\n }\n const expected = await fs.promises.readFile(absolutePath);\n const received = await readReceivedFile(cwd, path.join(cwd, relativePath));\n if (received && fileContentsMatch(expected, received)) continue;\n\n mismatchedPaths.push(relativePath);\n // A file too large to ship in a result line is left out of the pair rather than truncated.\n if (expected.length <= MAX_REPORTED_FILE_BYTES) {\n outputFiles.push(encodeFileForTestCaseResult(toComparisonPath(relativePath, 'expected'), expected));\n }\n if (received && received.length <= MAX_REPORTED_FILE_BYTES) {\n outputFiles.push(encodeFileForTestCaseResult(toComparisonPath(relativePath, 'received'), received));\n }\n }\n return { matches: mismatchedPaths.length === 0, mismatchedPaths, outputFiles };\n}\n\n/**\n * The default verdict of the presets: `.out` decides stdout and `.fout/` decides output files, both\n * checked so a result carries every mismatch; either expectation may be absent. Returns whether the\n * run matched and the output files to report (the plain copies of mismatched required output files\n * replaced by their comparison pairs).\n */\nexport async function judgeAgainstExpectations(context: {\n stdout: string;\n expectedStdout: string | undefined;\n fileOutputPath: string | undefined;\n cwd: string;\n outputFiles: readonly OutputFile[];\n}): Promise<{ matches: boolean; outputFiles: OutputFile[] }> {\n const { stdout, expectedStdout, fileOutputPath, cwd } = context;\n let outputFiles = [...context.outputFiles];\n let matches = expectedStdout === undefined || compareStdoutAsSpaceSeparatedTokens(stdout, expectedStdout);\n if (fileOutputPath !== undefined) {\n const comparison = await compareExpectedOutputFiles(cwd, fileOutputPath);\n if (!comparison.matches) {\n matches = false;\n outputFiles = mergeComparisonOutputFiles(outputFiles, comparison);\n }\n }\n return { matches, outputFiles };\n}\n\n/**\n * Replace the plain copies of mismatched required output files with the comparison pairs, so a\n * result shows `<name>_expected.<ext>` / `<name>_received.<ext>` instead of a lone `<name>.<ext>`.\n */\nexport function mergeComparisonOutputFiles(\n outputFiles: readonly OutputFile[],\n comparison: ExpectedOutputFilesComparison\n): OutputFile[] {\n const comparisonPaths = new Set(comparison.outputFiles.map((file) => file.path));\n // The plain copy gives way to its `_received` entry; a mismatch too large for a pair keeps the\n // plain copy, and a kept file literally named like a pair entry would make the path ambiguous.\n const replacedPaths = new Set(\n comparison.mismatchedPaths.filter((filePath) => comparisonPaths.has(toComparisonPath(filePath, 'received')))\n );\n return [\n ...outputFiles.filter((file) => !replacedPaths.has(file.path) && !comparisonPaths.has(file.path)),\n ...comparison.outputFiles,\n ];\n}\n\n/** `c.txt` → `c_expected.txt`; `out/c.txt` → `out/c_expected.txt`. */\nfunction toComparisonPath(filePath: string, role: 'expected' | 'received'): string {\n const { dir, name, ext } = path.posix.parse(filePath);\n return path.posix.join(dir, `${name}_${role}${ext}`);\n}\n\n// A submission controls this path, so anything that is not a plain, readable file within the size\n// limit (a directory, a FIFO that would block, an unreadable or huge file) counts as \"not produced\".\nasync function readReceivedFile(cwd: string, receivedPath: string): Promise<Buffer | undefined> {\n // A submission-planted symlink to the expected file itself would otherwise compare equal.\n if (!(await isSafeSubmissionOutputPath(cwd, receivedPath))) return undefined;\n try {\n const stats = await fs.promises.stat(receivedPath);\n if (!stats.isFile() || stats.size > MAX_COMPARED_FILE_BYTES) return undefined;\n return await fs.promises.readFile(receivedPath);\n } catch {\n return undefined;\n }\n}\n\nfunction fileContentsMatch(expected: Buffer, received: Buffer): boolean {\n if (!isTextFile(expected)) return received.equals(expected);\n if (!isTextFile(received)) return false;\n return compareStdoutAsSpaceSeparatedTokens(received.toString('utf8'), expected.toString('utf8'));\n}\n\nconst utf8Decoder = new TextDecoder('utf-8', { fatal: true });\n\n// Valid UTF-8 without NUL bytes: a binary format that happens to decode (e.g. a PNG chunk) almost\n// always contains NUL, while text files containing U+FFFD are still treated as text.\nfunction isTextFile(data: Buffer): boolean {\n if (data.includes(0)) return false;\n try {\n utf8Decoder.decode(data);\n return true;\n } catch {\n return false;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";const e=1e-6;function t(t,n){let r=t.trim().split(/\s+/),i=n.trim().split(/\s+/);if(r.length!==i.length)return!1;for(let[t,n]of i.entries()){let i=r[t];if(
|
|
1
|
+
"use strict";const e=1e-6;function t(t,n){let r=t.trim()?t.trim().split(/\s+/):[],i=n.trim()?n.trim().split(/\s+/):[];if(r.length!==i.length)return!1;for(let[t,n]of i.entries()){let i=r[t];if(Number.isFinite(Number(n))&&n.includes(`.`)){let t=Number(i),r=Number(n);if(!Number.isFinite(t))return!1;let a=Math.abs(t-r),o=r===0?1/0:Math.abs(a/r);if(a>e&&o>e)return!1}else if(i!==n)return!1}return!0}exports.compareStdoutAsSpaceSeparatedTokens=t;
|
|
2
2
|
//# sourceMappingURL=compareStdoutAsSpaceSeparatedTokens.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compareStdoutAsSpaceSeparatedTokens.cjs","names":[],"sources":["../../src/helpers/compareStdoutAsSpaceSeparatedTokens.ts"],"sourcesContent":["const ACCEPTABLE_FLOAT_ERROR = 1e-6;\n\nexport function compareStdoutAsSpaceSeparatedTokens(received: string, expected: string): boolean {\n // Consecutive white spaces (including tabs, page breaks, and line breaks) are treated as one space character.\n const receivedTokens = received.trim().split(/\\s+/);\n const expectedTokens = expected.trim().split(/\\s+/);\n\n if (receivedTokens.length !== expectedTokens.length) return false;\n\n for (const [i, expectedToken] of expectedTokens.entries()) {\n const receivedToken = receivedTokens[i];\n\n const isDecimal =
|
|
1
|
+
{"version":3,"file":"compareStdoutAsSpaceSeparatedTokens.cjs","names":[],"sources":["../../src/helpers/compareStdoutAsSpaceSeparatedTokens.ts"],"sourcesContent":["const ACCEPTABLE_FLOAT_ERROR = 1e-6;\n\nexport function compareStdoutAsSpaceSeparatedTokens(received: string, expected: string): boolean {\n // Consecutive white spaces (including tabs, page breaks, and line breaks) are treated as one space character.\n // An empty (or blank) text has no tokens; `''.split(/\\s+/)` would yield one empty token that\n // `Number('')` turns into 0.\n const receivedTokens = received.trim() ? received.trim().split(/\\s+/) : [];\n const expectedTokens = expected.trim() ? expected.trim().split(/\\s+/) : [];\n\n if (receivedTokens.length !== expectedTokens.length) return false;\n\n for (const [i, expectedToken] of expectedTokens.entries()) {\n const receivedToken = receivedTokens[i];\n\n // Only a finite decimal gets a tolerance; an overflowing decimal such as `1.0e309` compares exactly.\n const isDecimal = Number.isFinite(Number(expectedToken)) && expectedToken.includes('.');\n if (isDecimal) {\n const receivedNumber = Number(receivedToken);\n const expectedNumber = Number(expectedToken);\n\n if (!Number.isFinite(receivedNumber)) return false;\n\n const absoluteError = Math.abs(receivedNumber - expectedNumber);\n const relativeError = expectedNumber === 0 ? Number.POSITIVE_INFINITY : Math.abs(absoluteError / expectedNumber);\n if (absoluteError > ACCEPTABLE_FLOAT_ERROR && relativeError > ACCEPTABLE_FLOAT_ERROR) return false;\n } else {\n if (receivedToken !== expectedToken) return false;\n }\n }\n\n return true;\n}\n"],"mappings":"aAAA,MAAM,EAAyB,KAE/B,SAAgB,EAAoC,EAAkB,EAA2B,CAI/F,IAAM,EAAiB,EAAS,KAAK,EAAI,EAAS,KAAK,CAAC,CAAC,MAAM,KAAK,EAAI,CAAC,EACnE,EAAiB,EAAS,KAAK,EAAI,EAAS,KAAK,CAAC,CAAC,MAAM,KAAK,EAAI,CAAC,EAEzE,GAAI,EAAe,SAAW,EAAe,OAAQ,MAAO,GAE5D,IAAK,GAAM,CAAC,EAAG,KAAkB,EAAe,QAAQ,EAAG,CACzD,IAAM,EAAgB,EAAe,GAIrC,GADkB,OAAO,SAAS,OAAO,CAAa,CAAC,GAAK,EAAc,SAAS,GAAG,EACvE,CACb,IAAM,EAAiB,OAAO,CAAa,EACrC,EAAiB,OAAO,CAAa,EAE3C,GAAI,CAAC,OAAO,SAAS,CAAc,EAAG,MAAO,GAE7C,IAAM,EAAgB,KAAK,IAAI,EAAiB,CAAc,EACxD,EAAgB,IAAmB,EAAI,IAA2B,KAAK,IAAI,EAAgB,CAAc,EAC/G,GAAI,EAAgB,GAA0B,EAAgB,EAAwB,MAAO,EAC/F,MACE,GAAI,IAAkB,EAAe,MAAO,EAEhD,CAEA,MAAO,EACT"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=1e-6;function t(t,n){let r=t.trim().split(/\s+/),i=n.trim().split(/\s+/);if(r.length!==i.length)return!1;for(let[t,n]of i.entries()){let i=r[t];if(
|
|
1
|
+
const e=1e-6;function t(t,n){let r=t.trim()?t.trim().split(/\s+/):[],i=n.trim()?n.trim().split(/\s+/):[];if(r.length!==i.length)return!1;for(let[t,n]of i.entries()){let i=r[t];if(Number.isFinite(Number(n))&&n.includes(`.`)){let t=Number(i),r=Number(n);if(!Number.isFinite(t))return!1;let a=Math.abs(t-r),o=r===0?1/0:Math.abs(a/r);if(a>e&&o>e)return!1}else if(i!==n)return!1}return!0}export{t as compareStdoutAsSpaceSeparatedTokens};
|
|
2
2
|
//# sourceMappingURL=compareStdoutAsSpaceSeparatedTokens.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compareStdoutAsSpaceSeparatedTokens.js","names":[],"sources":["../../src/helpers/compareStdoutAsSpaceSeparatedTokens.ts"],"sourcesContent":["const ACCEPTABLE_FLOAT_ERROR = 1e-6;\n\nexport function compareStdoutAsSpaceSeparatedTokens(received: string, expected: string): boolean {\n // Consecutive white spaces (including tabs, page breaks, and line breaks) are treated as one space character.\n const receivedTokens = received.trim().split(/\\s+/);\n const expectedTokens = expected.trim().split(/\\s+/);\n\n if (receivedTokens.length !== expectedTokens.length) return false;\n\n for (const [i, expectedToken] of expectedTokens.entries()) {\n const receivedToken = receivedTokens[i];\n\n const isDecimal =
|
|
1
|
+
{"version":3,"file":"compareStdoutAsSpaceSeparatedTokens.js","names":[],"sources":["../../src/helpers/compareStdoutAsSpaceSeparatedTokens.ts"],"sourcesContent":["const ACCEPTABLE_FLOAT_ERROR = 1e-6;\n\nexport function compareStdoutAsSpaceSeparatedTokens(received: string, expected: string): boolean {\n // Consecutive white spaces (including tabs, page breaks, and line breaks) are treated as one space character.\n // An empty (or blank) text has no tokens; `''.split(/\\s+/)` would yield one empty token that\n // `Number('')` turns into 0.\n const receivedTokens = received.trim() ? received.trim().split(/\\s+/) : [];\n const expectedTokens = expected.trim() ? expected.trim().split(/\\s+/) : [];\n\n if (receivedTokens.length !== expectedTokens.length) return false;\n\n for (const [i, expectedToken] of expectedTokens.entries()) {\n const receivedToken = receivedTokens[i];\n\n // Only a finite decimal gets a tolerance; an overflowing decimal such as `1.0e309` compares exactly.\n const isDecimal = Number.isFinite(Number(expectedToken)) && expectedToken.includes('.');\n if (isDecimal) {\n const receivedNumber = Number(receivedToken);\n const expectedNumber = Number(expectedToken);\n\n if (!Number.isFinite(receivedNumber)) return false;\n\n const absoluteError = Math.abs(receivedNumber - expectedNumber);\n const relativeError = expectedNumber === 0 ? Number.POSITIVE_INFINITY : Math.abs(absoluteError / expectedNumber);\n if (absoluteError > ACCEPTABLE_FLOAT_ERROR && relativeError > ACCEPTABLE_FLOAT_ERROR) return false;\n } else {\n if (receivedToken !== expectedToken) return false;\n }\n }\n\n return true;\n}\n"],"mappings":"AAAA,MAAM,EAAyB,KAE/B,SAAgB,EAAoC,EAAkB,EAA2B,CAI/F,IAAM,EAAiB,EAAS,KAAK,EAAI,EAAS,KAAK,CAAC,CAAC,MAAM,KAAK,EAAI,CAAC,EACnE,EAAiB,EAAS,KAAK,EAAI,EAAS,KAAK,CAAC,CAAC,MAAM,KAAK,EAAI,CAAC,EAEzE,GAAI,EAAe,SAAW,EAAe,OAAQ,MAAO,GAE5D,IAAK,GAAM,CAAC,EAAG,KAAkB,EAAe,QAAQ,EAAG,CACzD,IAAM,EAAgB,EAAe,GAIrC,GADkB,OAAO,SAAS,OAAO,CAAa,CAAC,GAAK,EAAc,SAAS,GAAG,EACvE,CACb,IAAM,EAAiB,OAAO,CAAa,EACrC,EAAiB,OAAO,CAAa,EAE3C,GAAI,CAAC,OAAO,SAAS,CAAc,EAAG,MAAO,GAE7C,IAAM,EAAgB,KAAK,IAAI,EAAiB,CAAc,EACxD,EAAgB,IAAmB,EAAI,IAA2B,KAAK,IAAI,EAAgB,CAAc,EAC/G,GAAI,EAAgB,GAA0B,EAAgB,EAAwB,MAAO,EAC/F,MACE,GAAI,IAAkB,EAAe,MAAO,EAEhD,CAEA,MAAO,EACT"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";const e=require("../_virtual/_rolldown/runtime.cjs");let t=require("node:
|
|
1
|
+
"use strict";const e=require("../_virtual/_rolldown/runtime.cjs");let t=require("node:path");t=e.__toESM(t,1);let n=require("node:fs/promises");n=e.__toESM(n,1);const r={"judge.ts":`stdioJudgePreset`,"debug.ts":`stdioDebugPreset`};async function i(e){let n=await a(t.default.join(e,`judge.ts`)),i=[];if(n!==void 0&&o(n,r[`judge.ts`])&&i.push(`judge.ts`),n===void 0||i.length!==0){let n=await a(t.default.join(e,`debug.ts`));n!==void 0&&o(n,r[`debug.ts`])&&i.push(`debug.ts`)}return i}async function a(e){try{return await n.default.readFile(e,`utf8`)}catch{return}}function o(e,t){let n=new RegExp(String.raw`^\s*import\s*\{\s*${t}(?:\s+as\s+([A-Za-z_$][\w$]*))?\s*,?\s*\}\s*from\s*`+String.raw`(['"])(?:@exercode/problem-utils|judge-utils)/presets/stdio(?:\.js)?\2\s*;?\s*`).exec(e);if(!n)return!1;let r=(n[1]??t).replaceAll(`$`,String.raw`\$`);return new RegExp(String.raw`^await\s+${r}\s*\(\s*import\.meta\.dirname\s*\)\s*;?\s*$`).test(e.slice(n[0].length))}exports.HARNESS_FILE_PRESETS=r,exports.findDefaultStdioHarnessFiles=i,exports.isDefaultStdioHarnessSource=o;
|
|
2
2
|
//# sourceMappingURL=defaultStdioHarness.cjs.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import e from"node:
|
|
1
|
+
import e from"node:path";import t from"node:fs/promises";const n={"judge.ts":`stdioJudgePreset`,"debug.ts":`stdioDebugPreset`};async function r(t){let r=await i(e.join(t,`judge.ts`)),o=[];if(r!==void 0&&a(r,n[`judge.ts`])&&o.push(`judge.ts`),r===void 0||o.length!==0){let r=await i(e.join(t,`debug.ts`));r!==void 0&&a(r,n[`debug.ts`])&&o.push(`debug.ts`)}return o}async function i(e){try{return await t.readFile(e,`utf8`)}catch{return}}function a(e,t){let n=new RegExp(String.raw`^\s*import\s*\{\s*${t}(?:\s+as\s+([A-Za-z_$][\w$]*))?\s*,?\s*\}\s*from\s*`+String.raw`(['"])(?:@exercode/problem-utils|judge-utils)/presets/stdio(?:\.js)?\2\s*;?\s*`).exec(e);if(!n)return!1;let r=(n[1]??t).replaceAll(`$`,String.raw`\$`);return new RegExp(String.raw`^await\s+${r}\s*\(\s*import\.meta\.dirname\s*\)\s*;?\s*$`).test(e.slice(n[0].length))}export{n as HARNESS_FILE_PRESETS,r as findDefaultStdioHarnessFiles,a as isDefaultStdioHarnessSource};
|
|
2
2
|
//# sourceMappingURL=defaultStdioHarness.js.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";const e=require("../_virtual/_rolldown/runtime.cjs");let t=require("node:
|
|
1
|
+
"use strict";const e=require("../_virtual/_rolldown/runtime.cjs");let t=require("node:path");t=e.__toESM(t,1);let n=require("node:fs/promises");n=e.__toESM(n,1);const r=`model_answers`,i=`model_answers.fails`;async function a(e){return s(e,r)}async function o(e){return s(e,i)}async function s(e,r){let i=t.default.join(e,r),a;try{a=await n.default.readdir(i,{withFileTypes:!0})}catch(e){if(e.code===`ENOENT`)return[];throw e}return a.filter(e=>e.isDirectory()).map(e=>t.default.join(i,e.name)).toSorted()}exports.FAILING_MODEL_ANSWERS_DIRNAME=i,exports.MODEL_ANSWERS_DIRNAME=r,exports.findFailingModelAnswerDirs=o,exports.findModelAnswerDirs=a;
|
|
2
2
|
//# sourceMappingURL=findModelAnswerDirs.cjs.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import e from"node:
|
|
1
|
+
import e from"node:path";import t from"node:fs/promises";const n=`model_answers`,r=`model_answers.fails`;async function i(e){return o(e,n)}async function a(e){return o(e,r)}async function o(n,r){let i=e.join(n,r),a;try{a=await t.readdir(i,{withFileTypes:!0})}catch(e){if(e.code===`ENOENT`)return[];throw e}return a.filter(e=>e.isDirectory()).map(t=>e.join(i,t.name)).toSorted()}export{r as FAILING_MODEL_ANSWERS_DIRNAME,n as MODEL_ANSWERS_DIRNAME,a as findFailingModelAnswerDirs,i as findModelAnswerDirs};
|
|
2
2
|
//# sourceMappingURL=findModelAnswerDirs.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./removeCommentsInSourceCode.cjs"),n=require("../types/decisionCode.cjs"),r=require("../types/problem.cjs"),i=require("./findLanguageDefinitionByPath.cjs");let a=require("node:
|
|
1
|
+
"use strict";const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./removeCommentsInSourceCode.cjs"),n=require("../types/decisionCode.cjs"),r=require("../types/problem.cjs"),i=require("./findLanguageDefinitionByPath.cjs");let a=require("node:fs");a=e.__toESM(a,1);let o=require("node:path");o=e.__toESM(o,1);async function s(e,s){let l=new Set,u=[];for(let n of await a.default.promises.readdir(e,{withFileTypes:!0,recursive:!0})){if(!n.isFile())continue;let r=o.default.relative(e,o.default.join(n.parentPath,n.name));l.add(r);let s=await a.default.promises.readFile(o.default.join(n.parentPath,n.name),`utf8`);if(s.includes(`�`))continue;let c=i.findLanguageDefinitionByPath(n.name);c&&u.push({path:r,data:c.grammer?t.removeCommentsInSourceCode(c.grammer,s):s})}if(s.requiredSubmissionFilePaths){let e=s.requiredSubmissionFilePaths.filter(e=>!l.has(e)).toSorted();if(e.length>0)return{decisionCode:n.DecisionCode.MISSING_REQUIRED_SUBMISSION_FILE_ERROR,feedbackMarkdown:`ファイルが不足しています。
|
|
2
2
|
次のファイルを追加してから再度提出してください。
|
|
3
3
|
|
|
4
4
|
${e.map(e=>`- \`${e}\``).join(`
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{removeCommentsInSourceCode as e}from"./removeCommentsInSourceCode.js";import{DecisionCode as t}from"../types/decisionCode.js";import{normalizeCodeRule as n}from"../types/problem.js";import{findLanguageDefinitionByPath as r}from"./findLanguageDefinitionByPath.js";import i from"node:
|
|
1
|
+
import{removeCommentsInSourceCode as e}from"./removeCommentsInSourceCode.js";import{DecisionCode as t}from"../types/decisionCode.js";import{normalizeCodeRule as n}from"../types/problem.js";import{findLanguageDefinitionByPath as r}from"./findLanguageDefinitionByPath.js";import i from"node:fs";import a from"node:path";async function o(o,c){let l=new Set,u=[];for(let t of await i.promises.readdir(o,{withFileTypes:!0,recursive:!0})){if(!t.isFile())continue;let n=a.relative(o,a.join(t.parentPath,t.name));l.add(n);let s=await i.promises.readFile(a.join(t.parentPath,t.name),`utf8`);if(s.includes(`�`))continue;let c=r(t.name);c&&u.push({path:n,data:c.grammer?e(c.grammer,s):s})}if(c.requiredSubmissionFilePaths){let e=c.requiredSubmissionFilePaths.filter(e=>!l.has(e)).toSorted();if(e.length>0)return{decisionCode:t.MISSING_REQUIRED_SUBMISSION_FILE_ERROR,feedbackMarkdown:`ファイルが不足しています。
|
|
2
2
|
次のファイルを追加してから再度提出してください。
|
|
3
3
|
|
|
4
4
|
${e.map(e=>`- \`${e}\``).join(`
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./printTestCaseResult.cjs"),n=require("./sandboxUser.cjs"),r=require("./safeFs.cjs");let i=require("node:
|
|
1
|
+
"use strict";const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./printTestCaseResult.cjs"),n=require("./sandboxUser.cjs"),r=require("./safeFs.cjs");let i=require("node:fs");i=e.__toESM(i,1);let a=require("node:path");a=e.__toESM(a,1);async function o(e,n){let r=[];for(let o of n)try{let n=a.default.join(e,o);if(!await s(e,n))continue;let c=await i.default.promises.readFile(n);r.push(t.encodeFileForTestCaseResult(o,c))}catch{}return r}async function s(e,t){if(!n.sandboxUserName)return!0;try{let n=await i.default.promises.realpath(t),a=await i.default.promises.realpath(e);return r.isContainedPath(a,n)}catch{return!1}}exports.isSafeSubmissionOutputPath=s,exports.readOutputFiles=o;
|
|
2
2
|
//# sourceMappingURL=readOutputFiles.cjs.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{encodeFileForTestCaseResult as e}from"./printTestCaseResult.js";import{sandboxUserName as t}from"./sandboxUser.js";import{isContainedPath as n}from"./safeFs.js";import r from"node:
|
|
1
|
+
import{encodeFileForTestCaseResult as e}from"./printTestCaseResult.js";import{sandboxUserName as t}from"./sandboxUser.js";import{isContainedPath as n}from"./safeFs.js";import r from"node:fs";import i from"node:path";async function a(t,n){let a=[];for(let s of n)try{let n=i.join(t,s);if(!await o(t,n))continue;let c=await r.promises.readFile(n);a.push(e(s,c))}catch{}return a}async function o(e,i){if(!t)return!0;try{let t=await r.promises.realpath(i),a=await r.promises.realpath(e);return n(a,t)}catch{return!1}}export{o as isSafeSubmissionOutputPath,a as readOutputFiles};
|
|
2
2
|
//# sourceMappingURL=readOutputFiles.js.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("../types/problem.cjs");let n=require("node:
|
|
1
|
+
"use strict";const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("../types/problem.cjs");let n=require("node:fs");n=e.__toESM(n,1);let r=require("node:path");r=e.__toESM(r,1);let i=require("front-matter");i=e.__toESM(i,1);function a(e){return e.isManualScoringRequired===!0||(e.requiredRegExpsInCode?.length??0)>0||(e.forbiddenRegExpsInCode?.length??0)>0||(e.forbiddenTextsInCode?.length??0)>0||(e.requiredSubmissionFilePaths?.length??0)>0}async function o(e){for(let a of await n.default.promises.readdir(e,{withFileTypes:!0})){if(!a.isFile()||!(a.name===`problem.md`||a.name.endsWith(`.problem.md`)))continue;let e=await n.default.promises.readFile(r.default.join(a.parentPath,a.name),`utf8`),{attributes:o}=(0,i.default)(e);return t.problemMarkdownFrontMatterSchema.parse(o)}throw Error(`problem markdown not found: ${e}`)}exports.judgesWithoutTestCases=a,exports.readProblemMarkdownFrontMatter=o;
|
|
2
2
|
//# sourceMappingURL=readProblemMarkdownFrontMatter.cjs.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{problemMarkdownFrontMatterSchema as e}from"../types/problem.js";import t from"node:
|
|
1
|
+
import{problemMarkdownFrontMatterSchema as e}from"../types/problem.js";import t from"node:fs";import n from"node:path";import r from"front-matter";function i(e){return e.isManualScoringRequired===!0||(e.requiredRegExpsInCode?.length??0)>0||(e.forbiddenRegExpsInCode?.length??0)>0||(e.forbiddenTextsInCode?.length??0)>0||(e.requiredSubmissionFilePaths?.length??0)>0}async function a(i){for(let a of await t.promises.readdir(i,{withFileTypes:!0})){if(!a.isFile()||!(a.name===`problem.md`||a.name.endsWith(`.problem.md`)))continue;let i=await t.promises.readFile(n.join(a.parentPath,a.name),`utf8`),{attributes:o}=r(i);return e.parse(o)}throw Error(`problem markdown not found: ${i}`)}export{i as judgesWithoutTestCases,a as readProblemMarkdownFrontMatter};
|
|
2
2
|
//# sourceMappingURL=readProblemMarkdownFrontMatter.js.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";const e=require("../_virtual/_rolldown/runtime.cjs");let t=require("node:
|
|
1
|
+
"use strict";const e=require("../_virtual/_rolldown/runtime.cjs");let t=require("node:fs");t=e.__toESM(t,1);let n=require("node:path");n=e.__toESM(n,1);const r=`_shared`;async function i(e){if(!t.default.existsSync(e))return[];let i=new Set,a=new Map,o=new Map,s=new Map,c=new Map;for(let r of await t.default.promises.readdir(e,{withFileTypes:!0}))if(r.isFile()){let{ext:e,name:s}=n.default.parse(r.name);if(e!==`.in`&&e!==`.out`)continue;let c=await t.default.promises.readFile(n.default.join(r.parentPath,r.name),`utf8`);i.add(s),e===`.in`&&a.set(s,c),e===`.out`&&o.set(s,c)}else if(r.isDirectory()){let{ext:e,name:t}=n.default.parse(r.name);if(e!==`.fin`&&e!==`.fout`)continue;i.add(t),e===`.fin`&&s.set(t,n.default.join(r.parentPath,r.name)),e===`.fout`&&c.set(t,n.default.join(r.parentPath,r.name))}let l=[...i].filter(e=>e!==r).toSorted().map(e=>({id:e,input:a.get(e),output:o.get(e),fileInputPath:s.get(e),fileOutputPath:c.get(e)}));return i.has(r)&&(l.shared={fileInputPath:s.get(r)}),l}exports.readTestCases=i;
|
|
2
2
|
//# sourceMappingURL=readTestCases.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"readTestCases.cjs","names":["fs","path"],"sources":["../../src/helpers/readTestCases.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nconst SHARED_TEST_CASE_NAME = '_shared';\n\nexport async function readTestCases(directory: string): Promise<\n { id: string; input?: string; output?: string; fileInputPath?: string; fileOutputPath?: string }[] & {\n shared?: { fileInputPath?: string };\n }\n> {\n if (!fs.existsSync(directory)) return [];\n\n const idSet = new Set<string>();\n const idToInput = new Map<string, string>();\n const idToOutput = new Map<string, string>();\n const idToFileInputPath = new Map<string, string>();\n const idToFileOutputPath = new Map<string, string>();\n\n for (const dirent of await fs.promises.readdir(directory, { withFileTypes: true })) {\n if (dirent.isFile()) {\n const { ext, name } = path.parse(dirent.name);\n if (ext !== '.in' && ext !== '.out') continue;\n\n const text = await fs.promises.readFile(path.join(dirent.parentPath, dirent.name), 'utf8');\n\n idSet.add(name);\n if (ext === '.in') idToInput.set(name, text);\n if (ext === '.out') idToOutput.set(name, text);\n } else if (dirent.isDirectory()) {\n const { ext, name } = path.parse(dirent.name);\n if (ext !== '.fin' && ext !== '.fout') continue;\n\n idSet.add(name);\n if (ext === '.fin') idToFileInputPath.set(name, path.join(dirent.parentPath, dirent.name));\n if (ext === '.fout') idToFileOutputPath.set(name, path.join(dirent.parentPath, dirent.name));\n }\n }\n\n const testCases: Awaited<ReturnType<typeof readTestCases>> = [...idSet]\n .filter((id) => id !== SHARED_TEST_CASE_NAME)\n .toSorted()\n .map((id) => ({\n id,\n input: idToInput.get(id),\n output: idToOutput.get(id),\n fileInputPath: idToFileInputPath.get(id),\n fileOutputPath: idToFileOutputPath.get(id),\n }));\n\n if (idSet.has(SHARED_TEST_CASE_NAME)) {\n testCases.shared = { fileInputPath: idToFileInputPath.get(SHARED_TEST_CASE_NAME) };\n }\n\n return testCases;\n}\n"],"mappings":"wJAGA,MAAM,EAAwB,
|
|
1
|
+
{"version":3,"file":"readTestCases.cjs","names":["fs","path"],"sources":["../../src/helpers/readTestCases.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nconst SHARED_TEST_CASE_NAME = '_shared';\n\n/**\n * Read the test cases of a `test_cases/` directory. A test case id is the name of every\n * `<id>.in` / `<id>.out` file and `<id>.fin/` / `<id>.fout/` directory; any of them may be absent,\n * and what an absent part means is up to the harness (the presets document their rules).\n * Other entries (e.g. `.json` configuration read by a custom `judge.ts`) are ignored.\n */\nexport async function readTestCases(directory: string): Promise<\n { id: string; input?: string; output?: string; fileInputPath?: string; fileOutputPath?: string }[] & {\n shared?: { fileInputPath?: string };\n }\n> {\n if (!fs.existsSync(directory)) return [];\n\n const idSet = new Set<string>();\n const idToInput = new Map<string, string>();\n const idToOutput = new Map<string, string>();\n const idToFileInputPath = new Map<string, string>();\n const idToFileOutputPath = new Map<string, string>();\n\n for (const dirent of await fs.promises.readdir(directory, { withFileTypes: true })) {\n if (dirent.isFile()) {\n const { ext, name } = path.parse(dirent.name);\n if (ext !== '.in' && ext !== '.out') continue;\n\n const text = await fs.promises.readFile(path.join(dirent.parentPath, dirent.name), 'utf8');\n\n idSet.add(name);\n if (ext === '.in') idToInput.set(name, text);\n if (ext === '.out') idToOutput.set(name, text);\n } else if (dirent.isDirectory()) {\n const { ext, name } = path.parse(dirent.name);\n if (ext !== '.fin' && ext !== '.fout') continue;\n\n idSet.add(name);\n if (ext === '.fin') idToFileInputPath.set(name, path.join(dirent.parentPath, dirent.name));\n if (ext === '.fout') idToFileOutputPath.set(name, path.join(dirent.parentPath, dirent.name));\n }\n }\n\n const testCases: Awaited<ReturnType<typeof readTestCases>> = [...idSet]\n .filter((id) => id !== SHARED_TEST_CASE_NAME)\n .toSorted()\n .map((id) => ({\n id,\n input: idToInput.get(id),\n output: idToOutput.get(id),\n fileInputPath: idToFileInputPath.get(id),\n fileOutputPath: idToFileOutputPath.get(id),\n }));\n\n if (idSet.has(SHARED_TEST_CASE_NAME)) {\n testCases.shared = { fileInputPath: idToFileInputPath.get(SHARED_TEST_CASE_NAME) };\n }\n\n return testCases;\n}\n"],"mappings":"wJAGA,MAAM,EAAwB,UAQ9B,eAAsB,EAAc,EAIlC,CACA,GAAI,CAACA,EAAAA,QAAG,WAAW,CAAS,EAAG,MAAO,CAAC,EAEvC,IAAM,EAAQ,IAAI,IACZ,EAAY,IAAI,IAChB,EAAa,IAAI,IACjB,EAAoB,IAAI,IACxB,EAAqB,IAAI,IAE/B,IAAK,IAAM,KAAU,MAAMA,EAAAA,QAAG,SAAS,QAAQ,EAAW,CAAE,cAAe,EAAK,CAAC,EAC/E,GAAI,EAAO,OAAO,EAAG,CACnB,GAAM,CAAE,MAAK,QAASC,EAAAA,QAAK,MAAM,EAAO,IAAI,EAC5C,GAAI,IAAQ,OAAS,IAAQ,OAAQ,SAErC,IAAM,EAAO,MAAMD,EAAAA,QAAG,SAAS,SAASC,EAAAA,QAAK,KAAK,EAAO,WAAY,EAAO,IAAI,EAAG,MAAM,EAEzF,EAAM,IAAI,CAAI,EACV,IAAQ,OAAO,EAAU,IAAI,EAAM,CAAI,EACvC,IAAQ,QAAQ,EAAW,IAAI,EAAM,CAAI,CAC/C,MAAO,GAAI,EAAO,YAAY,EAAG,CAC/B,GAAM,CAAE,MAAK,QAASA,EAAAA,QAAK,MAAM,EAAO,IAAI,EAC5C,GAAI,IAAQ,QAAU,IAAQ,QAAS,SAEvC,EAAM,IAAI,CAAI,EACV,IAAQ,QAAQ,EAAkB,IAAI,EAAMA,EAAAA,QAAK,KAAK,EAAO,WAAY,EAAO,IAAI,CAAC,EACrF,IAAQ,SAAS,EAAmB,IAAI,EAAMA,EAAAA,QAAK,KAAK,EAAO,WAAY,EAAO,IAAI,CAAC,CAC7F,CAGF,IAAM,EAAuD,CAAC,GAAG,CAAK,CAAC,CACpE,OAAQ,GAAO,IAAO,CAAqB,CAAC,CAC5C,SAAS,CAAC,CACV,IAAK,IAAQ,CACZ,KACA,MAAO,EAAU,IAAI,CAAE,EACvB,OAAQ,EAAW,IAAI,CAAE,EACzB,cAAe,EAAkB,IAAI,CAAE,EACvC,eAAgB,EAAmB,IAAI,CAAE,CAC3C,EAAE,EAMJ,OAJI,EAAM,IAAI,CAAqB,IACjC,EAAU,OAAS,CAAE,cAAe,EAAkB,IAAI,CAAqB,CAAE,GAG5E,CACT"}
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read the test cases of a `test_cases/` directory. A test case id is the name of every
|
|
3
|
+
* `<id>.in` / `<id>.out` file and `<id>.fin/` / `<id>.fout/` directory; any of them may be absent,
|
|
4
|
+
* and what an absent part means is up to the harness (the presets document their rules).
|
|
5
|
+
* Other entries (e.g. `.json` configuration read by a custom `judge.ts`) are ignored.
|
|
6
|
+
*/
|
|
1
7
|
export declare function readTestCases(directory: string): Promise<{
|
|
2
8
|
id: string;
|
|
3
9
|
input?: string;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import e from"node:
|
|
1
|
+
import e from"node:fs";import t from"node:path";const n=`_shared`;async function r(r){if(!e.existsSync(r))return[];let i=new Set,a=new Map,o=new Map,s=new Map,c=new Map;for(let n of await e.promises.readdir(r,{withFileTypes:!0}))if(n.isFile()){let{ext:r,name:s}=t.parse(n.name);if(r!==`.in`&&r!==`.out`)continue;let c=await e.promises.readFile(t.join(n.parentPath,n.name),`utf8`);i.add(s),r===`.in`&&a.set(s,c),r===`.out`&&o.set(s,c)}else if(n.isDirectory()){let{ext:e,name:r}=t.parse(n.name);if(e!==`.fin`&&e!==`.fout`)continue;i.add(r),e===`.fin`&&s.set(r,t.join(n.parentPath,n.name)),e===`.fout`&&c.set(r,t.join(n.parentPath,n.name))}let l=[...i].filter(e=>e!==n).toSorted().map(e=>({id:e,input:a.get(e),output:o.get(e),fileInputPath:s.get(e),fileOutputPath:c.get(e)}));return i.has(n)&&(l.shared={fileInputPath:s.get(n)}),l}export{r as readTestCases};
|
|
2
2
|
//# sourceMappingURL=readTestCases.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"readTestCases.js","names":[],"sources":["../../src/helpers/readTestCases.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nconst SHARED_TEST_CASE_NAME = '_shared';\n\nexport async function readTestCases(directory: string): Promise<\n { id: string; input?: string; output?: string; fileInputPath?: string; fileOutputPath?: string }[] & {\n shared?: { fileInputPath?: string };\n }\n> {\n if (!fs.existsSync(directory)) return [];\n\n const idSet = new Set<string>();\n const idToInput = new Map<string, string>();\n const idToOutput = new Map<string, string>();\n const idToFileInputPath = new Map<string, string>();\n const idToFileOutputPath = new Map<string, string>();\n\n for (const dirent of await fs.promises.readdir(directory, { withFileTypes: true })) {\n if (dirent.isFile()) {\n const { ext, name } = path.parse(dirent.name);\n if (ext !== '.in' && ext !== '.out') continue;\n\n const text = await fs.promises.readFile(path.join(dirent.parentPath, dirent.name), 'utf8');\n\n idSet.add(name);\n if (ext === '.in') idToInput.set(name, text);\n if (ext === '.out') idToOutput.set(name, text);\n } else if (dirent.isDirectory()) {\n const { ext, name } = path.parse(dirent.name);\n if (ext !== '.fin' && ext !== '.fout') continue;\n\n idSet.add(name);\n if (ext === '.fin') idToFileInputPath.set(name, path.join(dirent.parentPath, dirent.name));\n if (ext === '.fout') idToFileOutputPath.set(name, path.join(dirent.parentPath, dirent.name));\n }\n }\n\n const testCases: Awaited<ReturnType<typeof readTestCases>> = [...idSet]\n .filter((id) => id !== SHARED_TEST_CASE_NAME)\n .toSorted()\n .map((id) => ({\n id,\n input: idToInput.get(id),\n output: idToOutput.get(id),\n fileInputPath: idToFileInputPath.get(id),\n fileOutputPath: idToFileOutputPath.get(id),\n }));\n\n if (idSet.has(SHARED_TEST_CASE_NAME)) {\n testCases.shared = { fileInputPath: idToFileInputPath.get(SHARED_TEST_CASE_NAME) };\n }\n\n return testCases;\n}\n"],"mappings":"gDAGA,MAAM,EAAwB,
|
|
1
|
+
{"version":3,"file":"readTestCases.js","names":[],"sources":["../../src/helpers/readTestCases.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nconst SHARED_TEST_CASE_NAME = '_shared';\n\n/**\n * Read the test cases of a `test_cases/` directory. A test case id is the name of every\n * `<id>.in` / `<id>.out` file and `<id>.fin/` / `<id>.fout/` directory; any of them may be absent,\n * and what an absent part means is up to the harness (the presets document their rules).\n * Other entries (e.g. `.json` configuration read by a custom `judge.ts`) are ignored.\n */\nexport async function readTestCases(directory: string): Promise<\n { id: string; input?: string; output?: string; fileInputPath?: string; fileOutputPath?: string }[] & {\n shared?: { fileInputPath?: string };\n }\n> {\n if (!fs.existsSync(directory)) return [];\n\n const idSet = new Set<string>();\n const idToInput = new Map<string, string>();\n const idToOutput = new Map<string, string>();\n const idToFileInputPath = new Map<string, string>();\n const idToFileOutputPath = new Map<string, string>();\n\n for (const dirent of await fs.promises.readdir(directory, { withFileTypes: true })) {\n if (dirent.isFile()) {\n const { ext, name } = path.parse(dirent.name);\n if (ext !== '.in' && ext !== '.out') continue;\n\n const text = await fs.promises.readFile(path.join(dirent.parentPath, dirent.name), 'utf8');\n\n idSet.add(name);\n if (ext === '.in') idToInput.set(name, text);\n if (ext === '.out') idToOutput.set(name, text);\n } else if (dirent.isDirectory()) {\n const { ext, name } = path.parse(dirent.name);\n if (ext !== '.fin' && ext !== '.fout') continue;\n\n idSet.add(name);\n if (ext === '.fin') idToFileInputPath.set(name, path.join(dirent.parentPath, dirent.name));\n if (ext === '.fout') idToFileOutputPath.set(name, path.join(dirent.parentPath, dirent.name));\n }\n }\n\n const testCases: Awaited<ReturnType<typeof readTestCases>> = [...idSet]\n .filter((id) => id !== SHARED_TEST_CASE_NAME)\n .toSorted()\n .map((id) => ({\n id,\n input: idToInput.get(id),\n output: idToOutput.get(id),\n fileInputPath: idToFileInputPath.get(id),\n fileOutputPath: idToFileOutputPath.get(id),\n }));\n\n if (idSet.has(SHARED_TEST_CASE_NAME)) {\n testCases.shared = { fileInputPath: idToFileInputPath.get(SHARED_TEST_CASE_NAME) };\n }\n\n return testCases;\n}\n"],"mappings":"gDAGA,MAAM,EAAwB,UAQ9B,eAAsB,EAAc,EAIlC,CACA,GAAI,CAAC,EAAG,WAAW,CAAS,EAAG,MAAO,CAAC,EAEvC,IAAM,EAAQ,IAAI,IACZ,EAAY,IAAI,IAChB,EAAa,IAAI,IACjB,EAAoB,IAAI,IACxB,EAAqB,IAAI,IAE/B,IAAK,IAAM,KAAU,MAAM,EAAG,SAAS,QAAQ,EAAW,CAAE,cAAe,EAAK,CAAC,EAC/E,GAAI,EAAO,OAAO,EAAG,CACnB,GAAM,CAAE,MAAK,QAAS,EAAK,MAAM,EAAO,IAAI,EAC5C,GAAI,IAAQ,OAAS,IAAQ,OAAQ,SAErC,IAAM,EAAO,MAAM,EAAG,SAAS,SAAS,EAAK,KAAK,EAAO,WAAY,EAAO,IAAI,EAAG,MAAM,EAEzF,EAAM,IAAI,CAAI,EACV,IAAQ,OAAO,EAAU,IAAI,EAAM,CAAI,EACvC,IAAQ,QAAQ,EAAW,IAAI,EAAM,CAAI,CAC/C,MAAO,GAAI,EAAO,YAAY,EAAG,CAC/B,GAAM,CAAE,MAAK,QAAS,EAAK,MAAM,EAAO,IAAI,EAC5C,GAAI,IAAQ,QAAU,IAAQ,QAAS,SAEvC,EAAM,IAAI,CAAI,EACV,IAAQ,QAAQ,EAAkB,IAAI,EAAM,EAAK,KAAK,EAAO,WAAY,EAAO,IAAI,CAAC,EACrF,IAAQ,SAAS,EAAmB,IAAI,EAAM,EAAK,KAAK,EAAO,WAAY,EAAO,IAAI,CAAC,CAC7F,CAGF,IAAM,EAAuD,CAAC,GAAG,CAAK,CAAC,CACpE,OAAQ,GAAO,IAAO,CAAqB,CAAC,CAC5C,SAAS,CAAC,CACV,IAAK,IAAQ,CACZ,KACA,MAAO,EAAU,IAAI,CAAE,EACvB,OAAQ,EAAW,IAAI,CAAE,EACzB,cAAe,EAAkB,IAAI,CAAE,EACvC,eAAgB,EAAmB,IAAI,CAAE,CAC3C,EAAE,EAMJ,OAJI,EAAM,IAAI,CAAqB,IACjC,EAAU,OAAS,CAAE,cAAe,EAAkB,IAAI,CAAqB,CAAE,GAG5E,CACT"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./sandboxUser.cjs"),n=require("./safeFs.cjs");let r=require("node:fs
|
|
1
|
+
"use strict";const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./sandboxUser.cjs"),n=require("./safeFs.cjs");let r=require("node:fs");r=e.__toESM(r,1);let i=require("node:path");i=e.__toESM(i,1);let a=require("node:child_process");a=e.__toESM(a,1);let o=require("node:os");o=e.__toESM(o,1);let s=require("node:fs/promises");s=e.__toESM(s,1);const c={bun:[`package.json`,`bun.lock`,`bun.lockb`],cargo:[`Cargo.toml`,`Cargo.lock`],go:[`go.mod`,`go.sum`],gradle:[`build.gradle`,`build.gradle.kts`,`settings.gradle`,`settings.gradle.kts`,`gradle.properties`,`gradle.lockfile`,`buildscript-gradle.lockfile`,`gradle`,`gradlew`,`gradlew.bat`],maven:[`pom.xml`,`.mvn`,`mvnw`,`mvnw.cmd`],npm:[`package.json`,`package-lock.json`],pnpm:[`package.json`,`pnpm-lock.yaml`,`pnpm-workspace.yaml`],ruby:[`Gemfile`,`Gemfile.lock`,`.ruby-version`],uv:[`pyproject.toml`,`uv.lock`],yarn:[`package.json`,`yarn.lock`,`.yarnrc`,`.yarnrc.yml`,`.yarn`]},l={bun:h,cargo:g,go:_,gradle:v,maven:y,npm:b,pnpm:x,ruby:S,uv:C,yarn:w},u=M();async function d(e){let n=await s.default.mkdtemp(i.default.join(o.default.tmpdir(),e.tempDirPrefix??`exercode-`));try{await s.default.cp(e.cwd,n,{recursive:!0}),await k({packageManager:e.packageManager,projectDir:e.projectDir,runDir:n,projectFilePaths:e.projectFilePaths}),t.makeAccessibleToSandboxUser(n);let r=e.env?{...process.env,...e.env}:process.env,i=e.prepareDependencies===!1?void 0:await p(e.packageManager,n),a=typeof e.command==`function`?e.command({runDir:n}):e.command,o=Date.now(),c=e.outputLimitBytes??52428800,l;if(i&&(l=await j(i,{cwd:n,env:r,outputLimitBytes:c,stdin:``,timeLimitSeconds:e.timeLimitSeconds}),m(l)))return f({elapsedTimeSeconds:(Date.now()-o)/1e3,options:e,result:l});let u=e.timeLimitSeconds-(Date.now()-o)/1e3;if(u<=0)return{stdin:e.stdin??``,stdout:l?.stdout??``,stderr:l?.stderr??``,status:0,timeSeconds:e.timeLimitSeconds+.001,memoryBytes:l?.memoryBytes??0,timedOut:!0,signal:l?.signal,outputLimitExceeded:!1};let d=await j(a,{cwd:n,env:r,outputLimitBytes:c,stdin:e.stdin??``,timeLimitSeconds:u}),h=(Date.now()-o)/1e3;return f(l?{elapsedTimeSeconds:h,options:e,result:{...d,timeSeconds:l.timeSeconds+d.timeSeconds,memoryBytes:Math.max(l.memoryBytes,d.memoryBytes)}}:{elapsedTimeSeconds:h,options:e,result:d})}finally{try{t.sandboxUserName&&t.killSandboxUserProcesses()}finally{await t.forceRemove(n)}}}function f(e){return{stdin:e.options.stdin??``,stdout:e.result.stdout,stderr:e.result.stderr,status:e.result.timedOut||e.result.outputLimitExceeded?0:e.result.status,timeSeconds:e.result.timedOut?e.options.timeLimitSeconds+.001:e.result.timeSeconds||e.elapsedTimeSeconds,memoryBytes:e.result.memoryBytes,timedOut:e.result.timedOut,signal:e.result.signal,outputLimitExceeded:e.result.outputLimitExceeded}}function p(e,t){return l[e](t)}function m(e){return e.status!==0||e.timedOut||e.outputLimitExceeded}async function h(e){if(await D(i.default.join(e,`package.json`)))return await E(e,[`bun.lock`,`bun.lockb`])?[`bun`,`install`,`--frozen-lockfile`,`--silent`]:[`bun`,`install`,`--silent`]}async function g(e){if(await D(i.default.join(e,`Cargo.toml`)))return await D(i.default.join(e,`Cargo.lock`))?[`cargo`,`fetch`,`--locked`]:[`cargo`,`fetch`]}async function _(e){if(await D(i.default.join(e,`go.mod`)))return[`go`,`mod`,`download`]}async function v(e){if(!await E(e,[`build.gradle`,`build.gradle.kts`,`settings.gradle`,`settings.gradle.kts`,`gradlew`,`gradlew.bat`]))return;let t=[`--no-daemon`,`--quiet`,`dependencies`];return process.platform===`win32`?await D(i.default.join(e,`gradlew.bat`))?[`cmd.exe`,`/c`,`gradlew.bat`,...t]:[`gradle`,...t]:await D(i.default.join(e,`gradlew`))?[`sh`,`./gradlew`,...t]:[`gradle`,...t]}async function y(e){if(!await D(i.default.join(e,`pom.xml`)))return;let t=[`-q`,`dependency:go-offline`];return process.platform===`win32`?await D(i.default.join(e,`mvnw.cmd`))?[`cmd.exe`,`/c`,`mvnw.cmd`,...t]:[`mvn`,...t]:await D(i.default.join(e,`mvnw`))?[`sh`,`./mvnw`,...t]:[`mvn`,...t]}async function b(e){if(await D(i.default.join(e,`package.json`)))return await D(i.default.join(e,`package-lock.json`))?[`npm`,`ci`,`--silent`]:[`npm`,`install`,`--silent`]}async function x(e){if(await D(i.default.join(e,`package.json`)))return await D(i.default.join(e,`pnpm-lock.yaml`))?[`pnpm`,`install`,`--frozen-lockfile`,`--silent`]:[`pnpm`,`install`,`--silent`]}async function S(e){if(await D(i.default.join(e,`Gemfile`)))return await D(i.default.join(e,`Gemfile.lock`))?[`bundle`,`install`,`--frozen`,`--quiet`]:[`bundle`,`install`,`--quiet`]}async function C(){}async function w(e){if(!await D(i.default.join(e,`package.json`)))return;let t=await T(e),n=await D(i.default.join(e,`yarn.lock`));return t?n?[`yarn`,`install`,`--immutable`]:[`yarn`,`install`]:n?[`yarn`,`install`,`--frozen-lockfile`,`--silent`]:[`yarn`,`install`,`--silent`]}async function T(e){if(await D(i.default.join(e,`.yarnrc.yml`)))return!0;let t=await O(i.default.join(e,`package.json`)),n=typeof t.packageManager==`string`?t.packageManager:void 0,r=/^yarn@(\d+)/.exec(n??``)?.[1];return r!==void 0&&Number(r)>=2}async function E(e,t){for(let n of t)if(await D(i.default.join(e,n)))return!0;return!1}async function D(e){try{return await s.default.access(e),!0}catch(e){if((typeof e==`object`&&e&&`code`in e?e.code:void 0)!==`ENOENT`)throw e;return!1}}async function O(e){try{let t=JSON.parse(await s.default.readFile(e,`utf8`));return typeof t==`object`&&t&&!Array.isArray(t)?t:{}}catch(e){if(e instanceof SyntaxError||(typeof e==`object`&&e&&`code`in e?e.code:void 0)===`ENOENT`)return{};throw e}}async function k(e){for(let t of e.projectFilePaths??c[e.packageManager])await A(i.default.join(e.projectDir,t),i.default.join(e.runDir,t),e.runDir)}async function A(e,t,r){try{await s.default.lstat(e),await n.createDirectoryWithoutFollowingSymlinks(r,i.default.dirname(t)),await n.copyWithoutFollowingSymlinks(e,t)}catch(e){if((typeof e==`object`&&e&&`code`in e?e.code:void 0)!==`ENOENT`)throw e}}async function j(e,n){let r=u===void 0?void 0:i.default.join(n.cwd,`.exercode-time-result`),o=t.wrapCommandWithSandboxUser(e),s=u===void 0?o:t.prependInsideSandboxWrapper(o,[...u,`--output=${r}`]),c=t.startSandboxTimeoutWatchdog(n.timeLimitSeconds),l=Date.now()+(Math.ceil(n.timeLimitSeconds)+5)*1e3,d;try{d=a.default.spawn(s[0],s.slice(1),{cwd:n.cwd,detached:process.platform!==`win32`,env:{...n.env,...t.getSandboxUserEnvOverrides(n.env)},stdio:[`pipe`,`pipe`,`pipe`]})}catch(e){throw c.cancel(),e}let f=[],p=[],m=0,h=!1,g=!1,_,v,y=e=>{try{N(d,e)}catch(e){_??=e instanceof Error?e:Error(String(e)),v?.()}},b=(e,t)=>{if(m>=n.outputLimitBytes){t.byteLength>0&&(g=!0,y(`SIGKILL`));return}let r=n.outputLimitBytes-m,i=t.byteLength>r?t.subarray(0,r):t;e.push(i),m+=i.byteLength,t.byteLength>r&&(g=!0,y(`SIGKILL`))};d.stdout.on(`data`,e=>b(f,e)),d.stderr.on(`data`,e=>b(p,e));let x=setTimeout(()=>{h=!0,y(`SIGTERM`)},n.timeLimitSeconds*1e3),S=setTimeout(()=>{h&&y(`SIGKILL`)},n.timeLimitSeconds*1e3+1e3);S.unref();let C,w=!1,{status:T,signal:E}=await new Promise((e,t)=>{let r=!1,i,a=e=>{r||(i=e,y(`SIGKILL`),d.pid===void 0&&(r=!0,t(e)))};v=()=>{C??=setTimeout(()=>{r||(r=!0,t(_??Error(`failed to terminate the submission`)))},Math.max(0,l-Date.now())+2e3)},d.on(`error`,a),d.stdin.on(`error`,e=>{e.code!==`EPIPE`&&a(e)}),d.on(`close`,(n,a)=>{if(!r){if(r=!0,w=!0,i){t(i);return}e({status:n??void 0,signal:a??void 0})}}),d.stdin.end(n.stdin)}).finally(()=>{clearTimeout(x),clearTimeout(S),C&&clearTimeout(C),(w||!_)&&c.cancel()}),{timeSeconds:D,memoryBytes:O}=r===void 0?{timeSeconds:0,memoryBytes:0}:await F(r);return{stdout:Buffer.concat(f).toString(),stderr:Buffer.concat(p).toString(),status:T,timeSeconds:D,memoryBytes:O,timedOut:h,signal:E,outputLimitExceeded:g}}function M(){let e=o.default.platform()===`darwin`?`gtime`:`/usr/bin/time`,t=a.default.spawnSync(e,[`--version`],{stdio:`ignore`});if(!(t.error||t.status!==0))return[e,`--format`,`%e %M`]}function N(e,n){if(e.pid!==void 0){if(t.sandboxUserName){t.killSandboxUserProcesses([n===`SIGKILL`?`KILL`:`TERM`]);return}try{if(process.platform===`win32`){e.kill(n);return}process.kill(-e.pid,n)}catch(e){let t=typeof e==`object`&&e&&`code`in e?e.code:void 0;if(t!==`ESRCH`&&t!==`EPERM`)throw e}}}function P(e,t){return typeof e==`object`&&!!e&&`code`in e&&e.code===t}async function F(e){let t,n;try{if(n=await s.default.open(e,r.default.constants.O_RDONLY|r.default.constants.O_NOFOLLOW|r.default.constants.O_NONBLOCK),!(await n.stat()).isFile())throw Error(`${e} is not a regular file`);t=await n.readFile(`utf8`)}catch(t){if(P(t,`ENOENT`))return{timeSeconds:0,memoryBytes:0};throw Error(`failed to read the time measurement at ${e}`,{cause:t})}finally{await n?.close()}let i=/(\d+(?:[.,]\d+)?) (\d+)\s*$/.exec(t);return i?{timeSeconds:Number(i[1].replace(`,`,`.`)),memoryBytes:Number(i[2])*1024}:{timeSeconds:0,memoryBytes:0}}exports.copyPackageManagerProjectFiles=k,exports.runCommandInTemporaryPackageManagerProject=d;
|
|
2
2
|
//# sourceMappingURL=runCommandInTemporaryPackageManagerProject.cjs.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{forceRemove as e,getSandboxUserEnvOverrides as t,killSandboxUserProcesses as n,makeAccessibleToSandboxUser as r,prependInsideSandboxWrapper as i,sandboxUserName as a,startSandboxTimeoutWatchdog as o,wrapCommandWithSandboxUser as s}from"./sandboxUser.js";import{copyWithoutFollowingSymlinks as c,createDirectoryWithoutFollowingSymlinks as l}from"./safeFs.js";import u from"node:fs
|
|
1
|
+
import{forceRemove as e,getSandboxUserEnvOverrides as t,killSandboxUserProcesses as n,makeAccessibleToSandboxUser as r,prependInsideSandboxWrapper as i,sandboxUserName as a,startSandboxTimeoutWatchdog as o,wrapCommandWithSandboxUser as s}from"./sandboxUser.js";import{copyWithoutFollowingSymlinks as c,createDirectoryWithoutFollowingSymlinks as l}from"./safeFs.js";import u from"node:fs";import d from"node:path";import f from"node:child_process";import p from"node:os";import m from"node:fs/promises";const h={bun:[`package.json`,`bun.lock`,`bun.lockb`],cargo:[`Cargo.toml`,`Cargo.lock`],go:[`go.mod`,`go.sum`],gradle:[`build.gradle`,`build.gradle.kts`,`settings.gradle`,`settings.gradle.kts`,`gradle.properties`,`gradle.lockfile`,`buildscript-gradle.lockfile`,`gradle`,`gradlew`,`gradlew.bat`],maven:[`pom.xml`,`.mvn`,`mvnw`,`mvnw.cmd`],npm:[`package.json`,`package-lock.json`],pnpm:[`package.json`,`pnpm-lock.yaml`,`pnpm-workspace.yaml`],ruby:[`Gemfile`,`Gemfile.lock`,`.ruby-version`],uv:[`pyproject.toml`,`uv.lock`],yarn:[`package.json`,`yarn.lock`,`.yarnrc`,`.yarnrc.yml`,`.yarn`]},g={bun:S,cargo:C,go:w,gradle:T,maven:E,npm:D,pnpm:O,ruby:k,uv:A,yarn:j},_=z();async function v(t){let i=await m.mkdtemp(d.join(p.tmpdir(),t.tempDirPrefix??`exercode-`));try{await m.cp(t.cwd,i,{recursive:!0}),await I({packageManager:t.packageManager,projectDir:t.projectDir,runDir:i,projectFilePaths:t.projectFilePaths}),r(i);let e=t.env?{...process.env,...t.env}:process.env,n=t.prepareDependencies===!1?void 0:await b(t.packageManager,i),a=typeof t.command==`function`?t.command({runDir:i}):t.command,o=Date.now(),s=t.outputLimitBytes??52428800,c;if(n&&(c=await R(n,{cwd:i,env:e,outputLimitBytes:s,stdin:``,timeLimitSeconds:t.timeLimitSeconds}),x(c)))return y({elapsedTimeSeconds:(Date.now()-o)/1e3,options:t,result:c});let l=t.timeLimitSeconds-(Date.now()-o)/1e3;if(l<=0)return{stdin:t.stdin??``,stdout:c?.stdout??``,stderr:c?.stderr??``,status:0,timeSeconds:t.timeLimitSeconds+.001,memoryBytes:c?.memoryBytes??0,timedOut:!0,signal:c?.signal,outputLimitExceeded:!1};let u=await R(a,{cwd:i,env:e,outputLimitBytes:s,stdin:t.stdin??``,timeLimitSeconds:l}),d=(Date.now()-o)/1e3;return y(c?{elapsedTimeSeconds:d,options:t,result:{...u,timeSeconds:c.timeSeconds+u.timeSeconds,memoryBytes:Math.max(c.memoryBytes,u.memoryBytes)}}:{elapsedTimeSeconds:d,options:t,result:u})}finally{try{a&&n()}finally{await e(i)}}}function y(e){return{stdin:e.options.stdin??``,stdout:e.result.stdout,stderr:e.result.stderr,status:e.result.timedOut||e.result.outputLimitExceeded?0:e.result.status,timeSeconds:e.result.timedOut?e.options.timeLimitSeconds+.001:e.result.timeSeconds||e.elapsedTimeSeconds,memoryBytes:e.result.memoryBytes,timedOut:e.result.timedOut,signal:e.result.signal,outputLimitExceeded:e.result.outputLimitExceeded}}function b(e,t){return g[e](t)}function x(e){return e.status!==0||e.timedOut||e.outputLimitExceeded}async function S(e){if(await P(d.join(e,`package.json`)))return await N(e,[`bun.lock`,`bun.lockb`])?[`bun`,`install`,`--frozen-lockfile`,`--silent`]:[`bun`,`install`,`--silent`]}async function C(e){if(await P(d.join(e,`Cargo.toml`)))return await P(d.join(e,`Cargo.lock`))?[`cargo`,`fetch`,`--locked`]:[`cargo`,`fetch`]}async function w(e){if(await P(d.join(e,`go.mod`)))return[`go`,`mod`,`download`]}async function T(e){if(!await N(e,[`build.gradle`,`build.gradle.kts`,`settings.gradle`,`settings.gradle.kts`,`gradlew`,`gradlew.bat`]))return;let t=[`--no-daemon`,`--quiet`,`dependencies`];return process.platform===`win32`?await P(d.join(e,`gradlew.bat`))?[`cmd.exe`,`/c`,`gradlew.bat`,...t]:[`gradle`,...t]:await P(d.join(e,`gradlew`))?[`sh`,`./gradlew`,...t]:[`gradle`,...t]}async function E(e){if(!await P(d.join(e,`pom.xml`)))return;let t=[`-q`,`dependency:go-offline`];return process.platform===`win32`?await P(d.join(e,`mvnw.cmd`))?[`cmd.exe`,`/c`,`mvnw.cmd`,...t]:[`mvn`,...t]:await P(d.join(e,`mvnw`))?[`sh`,`./mvnw`,...t]:[`mvn`,...t]}async function D(e){if(await P(d.join(e,`package.json`)))return await P(d.join(e,`package-lock.json`))?[`npm`,`ci`,`--silent`]:[`npm`,`install`,`--silent`]}async function O(e){if(await P(d.join(e,`package.json`)))return await P(d.join(e,`pnpm-lock.yaml`))?[`pnpm`,`install`,`--frozen-lockfile`,`--silent`]:[`pnpm`,`install`,`--silent`]}async function k(e){if(await P(d.join(e,`Gemfile`)))return await P(d.join(e,`Gemfile.lock`))?[`bundle`,`install`,`--frozen`,`--quiet`]:[`bundle`,`install`,`--quiet`]}async function A(){}async function j(e){if(!await P(d.join(e,`package.json`)))return;let t=await M(e),n=await P(d.join(e,`yarn.lock`));return t?n?[`yarn`,`install`,`--immutable`]:[`yarn`,`install`]:n?[`yarn`,`install`,`--frozen-lockfile`,`--silent`]:[`yarn`,`install`,`--silent`]}async function M(e){if(await P(d.join(e,`.yarnrc.yml`)))return!0;let t=await F(d.join(e,`package.json`)),n=typeof t.packageManager==`string`?t.packageManager:void 0,r=/^yarn@(\d+)/.exec(n??``)?.[1];return r!==void 0&&Number(r)>=2}async function N(e,t){for(let n of t)if(await P(d.join(e,n)))return!0;return!1}async function P(e){try{return await m.access(e),!0}catch(e){if((typeof e==`object`&&e&&`code`in e?e.code:void 0)!==`ENOENT`)throw e;return!1}}async function F(e){try{let t=JSON.parse(await m.readFile(e,`utf8`));return typeof t==`object`&&t&&!Array.isArray(t)?t:{}}catch(e){if(e instanceof SyntaxError||(typeof e==`object`&&e&&`code`in e?e.code:void 0)===`ENOENT`)return{};throw e}}async function I(e){for(let t of e.projectFilePaths??h[e.packageManager])await L(d.join(e.projectDir,t),d.join(e.runDir,t),e.runDir)}async function L(e,t,n){try{await m.lstat(e),await l(n,d.dirname(t)),await c(e,t)}catch(e){if((typeof e==`object`&&e&&`code`in e?e.code:void 0)!==`ENOENT`)throw e}}async function R(e,n){let r=_===void 0?void 0:d.join(n.cwd,`.exercode-time-result`),a=s(e),c=_===void 0?a:i(a,[..._,`--output=${r}`]),l=o(n.timeLimitSeconds),u=Date.now()+(Math.ceil(n.timeLimitSeconds)+5)*1e3,p;try{p=f.spawn(c[0],c.slice(1),{cwd:n.cwd,detached:process.platform!==`win32`,env:{...n.env,...t(n.env)},stdio:[`pipe`,`pipe`,`pipe`]})}catch(e){throw l.cancel(),e}let m=[],h=[],g=0,v=!1,y=!1,b,x,S=e=>{try{B(p,e)}catch(e){b??=e instanceof Error?e:Error(String(e)),x?.()}},C=(e,t)=>{if(g>=n.outputLimitBytes){t.byteLength>0&&(y=!0,S(`SIGKILL`));return}let r=n.outputLimitBytes-g,i=t.byteLength>r?t.subarray(0,r):t;e.push(i),g+=i.byteLength,t.byteLength>r&&(y=!0,S(`SIGKILL`))};p.stdout.on(`data`,e=>C(m,e)),p.stderr.on(`data`,e=>C(h,e));let w=setTimeout(()=>{v=!0,S(`SIGTERM`)},n.timeLimitSeconds*1e3),T=setTimeout(()=>{v&&S(`SIGKILL`)},n.timeLimitSeconds*1e3+1e3);T.unref();let E,D=!1,{status:O,signal:k}=await new Promise((e,t)=>{let r=!1,i,a=e=>{r||(i=e,S(`SIGKILL`),p.pid===void 0&&(r=!0,t(e)))};x=()=>{E??=setTimeout(()=>{r||(r=!0,t(b??Error(`failed to terminate the submission`)))},Math.max(0,u-Date.now())+2e3)},p.on(`error`,a),p.stdin.on(`error`,e=>{e.code!==`EPIPE`&&a(e)}),p.on(`close`,(n,a)=>{if(!r){if(r=!0,D=!0,i){t(i);return}e({status:n??void 0,signal:a??void 0})}}),p.stdin.end(n.stdin)}).finally(()=>{clearTimeout(w),clearTimeout(T),E&&clearTimeout(E),(D||!b)&&l.cancel()}),{timeSeconds:A,memoryBytes:j}=r===void 0?{timeSeconds:0,memoryBytes:0}:await H(r);return{stdout:Buffer.concat(m).toString(),stderr:Buffer.concat(h).toString(),status:O,timeSeconds:A,memoryBytes:j,timedOut:v,signal:k,outputLimitExceeded:y}}function z(){let e=p.platform()===`darwin`?`gtime`:`/usr/bin/time`,t=f.spawnSync(e,[`--version`],{stdio:`ignore`});if(!(t.error||t.status!==0))return[e,`--format`,`%e %M`]}function B(e,t){if(e.pid!==void 0){if(a){n([t===`SIGKILL`?`KILL`:`TERM`]);return}try{if(process.platform===`win32`){e.kill(t);return}process.kill(-e.pid,t)}catch(e){let t=typeof e==`object`&&e&&`code`in e?e.code:void 0;if(t!==`ESRCH`&&t!==`EPERM`)throw e}}}function V(e,t){return typeof e==`object`&&!!e&&`code`in e&&e.code===t}async function H(e){let t,n;try{if(n=await m.open(e,u.constants.O_RDONLY|u.constants.O_NOFOLLOW|u.constants.O_NONBLOCK),!(await n.stat()).isFile())throw Error(`${e} is not a regular file`);t=await n.readFile(`utf8`)}catch(t){if(V(t,`ENOENT`))return{timeSeconds:0,memoryBytes:0};throw Error(`failed to read the time measurement at ${e}`,{cause:t})}finally{await n?.close()}let r=/(\d+(?:[.,]\d+)?) (\d+)\s*$/.exec(t);return r?{timeSeconds:Number(r[1].replace(`,`,`.`)),memoryBytes:Number(r[2])*1024}:{timeSeconds:0,memoryBytes:0}}export{I as copyPackageManagerProjectFiles,v as runCommandInTemporaryPackageManagerProject};
|
|
2
2
|
//# sourceMappingURL=runCommandInTemporaryPackageManagerProject.js.map
|