@exercode/problem-utils 4.3.4 → 4.3.6
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/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/exercodeProblem.cjs.map +1 -1
- package/dist/cli/exercodeProblem.js.map +1 -1
- package/dist/helpers/checkProblemDirIsolation.cjs.map +1 -1
- package/dist/helpers/checkProblemDirIsolation.js.map +1 -1
- package/dist/helpers/compareExpectedOutputFiles.cjs.map +1 -1
- package/dist/helpers/compareExpectedOutputFiles.js.map +1 -1
- package/dist/helpers/findModelAnswerDirs.cjs.map +1 -1
- package/dist/helpers/findModelAnswerDirs.js.map +1 -1
- package/dist/helpers/javaJudge.cjs.map +1 -1
- package/dist/helpers/javaJudge.js.map +1 -1
- package/dist/helpers/judgeByStaticAnalysis.cjs.map +1 -1
- package/dist/helpers/judgeByStaticAnalysis.js.map +1 -1
- package/dist/helpers/readOutputFiles.cjs.map +1 -1
- package/dist/helpers/readOutputFiles.js.map +1 -1
- package/dist/helpers/resolveCwds.cjs +1 -1
- package/dist/helpers/resolveCwds.cjs.map +1 -1
- package/dist/helpers/resolveCwds.js +1 -1
- package/dist/helpers/resolveCwds.js.map +1 -1
- package/dist/helpers/runCommandInTemporaryPackageManagerProject.cjs.map +1 -1
- package/dist/helpers/runCommandInTemporaryPackageManagerProject.js.map +1 -1
- package/dist/helpers/sourceCodeGrammars.cjs.map +1 -1
- package/dist/helpers/sourceCodeGrammars.js.map +1 -1
- package/dist/helpers/spawnWithLimits.cjs.map +1 -1
- package/dist/helpers/spawnWithLimits.js.map +1 -1
- package/dist/learningMaterial/validateContest.cjs.map +1 -1
- package/dist/learningMaterial/validateContest.js.map +1 -1
- package/dist/learningMaterial/validateCourse.cjs.map +1 -1
- package/dist/learningMaterial/validateCourse.js.map +1 -1
- package/dist/learningMaterial/validateMaterial.cjs.map +1 -1
- package/dist/learningMaterial/validateMaterial.js.map +1 -1
- package/dist/learningMaterial/validateProblem.cjs.map +1 -1
- package/dist/learningMaterial/validateProblem.js.map +1 -1
- package/dist/presets/command.cjs +1 -1
- package/dist/presets/command.cjs.map +1 -1
- package/dist/presets/command.js +1 -1
- package/dist/presets/command.js.map +1 -1
- package/dist/presets/evaluation.cjs +1 -1
- package/dist/presets/evaluation.cjs.map +1 -1
- package/dist/presets/evaluation.js +1 -1
- package/dist/presets/evaluation.js.map +1 -1
- package/dist/presets/guiCommand.cjs.map +1 -1
- package/dist/presets/guiCommand.js.map +1 -1
- package/dist/presets/springBoot.cjs.map +1 -1
- package/dist/presets/springBoot.js.map +1 -1
- package/dist/presets/stdio.cjs.map +1 -1
- package/dist/presets/stdio.js.map +1 -1
- package/dist/presets/tomcat.cjs.map +1 -1
- package/dist/presets/tomcat.js.map +1 -1
- package/package.json +25 -28
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"javaJudge.js","names":["fs","path","MAX_OUTPUT_LENGTH","resolveJavaRelativePath","sourceCode","sourcePath","packageMatch","fallbackRelativePath","listSubmissionFiles","filePaths","collectSubmissionFiles","rootDir","currentDir","relativeDir","entries","entry","entryRelativePath","readOutputFiles","filePath","parsed","values","z","outputFileSchema","result","value","truncateOutput"],"sources":["../../src/helpers/javaJudge.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { z } from 'zod';\nimport type { TestCaseResult } from '../types/testCaseResult.js';\n\ntype OutputFile = NonNullable<TestCaseResult['outputFiles']>[number];\nexport const MAX_OUTPUT_LENGTH = 50_000;\n\nexport async function resolveJavaRelativePath(sourcePath: string, fallbackRelativePath: string): Promise<string> {\n const sourceCode = await fs.promises.readFile(sourcePath, 'utf8');\n const packageMatch = /^\\s*package\\s+([a-zA-Z0-9_.]+)\\s*;/m.exec(sourceCode);\n if (!packageMatch?.[1]) {\n return fallbackRelativePath;\n }\n\n return path.join(packageMatch[1].replaceAll('.', '/'), path.basename(fallbackRelativePath));\n}\n\nexport async function listSubmissionFiles(rootDir: string): Promise<string[]> {\n const filePaths: string[] = [];\n await collectSubmissionFiles(rootDir, '', filePaths);\n return filePaths.toSorted();\n}\n\nasync function collectSubmissionFiles(rootDir: string, relativeDir: string, filePaths: string[]): Promise<void> {\n const currentDir = relativeDir ? path.join(rootDir, relativeDir) : rootDir;\n const entries = await fs.promises.readdir(currentDir, { withFileTypes: true });\n for (const entry of entries) {\n const entryRelativePath = relativeDir ? path.join(relativeDir, entry.name) : entry.name;\n if (entry.isDirectory()) {\n await collectSubmissionFiles(rootDir, entryRelativePath, filePaths);\n continue;\n }\n if (entry.isFile()) {\n filePaths.push(entryRelativePath);\n }\n }\n}\n\nexport function readOutputFiles(filePath: string): OutputFile[] | undefined {\n if (!fs.existsSync(filePath)) {\n return undefined;\n }\n\n // Malformed evaluator JSON remains a harness error (JUDGE_NOT_AVAILABLE), not missing screenshots.\n const parsed: unknown = JSON.parse(fs.readFileSync(filePath, 'utf8'));\n const values = z.array(z.unknown()).safeParse(parsed);\n if (!values.success) return undefined;\n const outputFileSchema = z.object({\n path: z.string(),\n data: z.string(),\n encoding: z.literal('base64').optional(),\n });\n return values.data.flatMap((value) => {\n const result = outputFileSchema.safeParse(value);\n return result.success ? [result.data] : [];\n });\n}\n\nexport function truncateOutput(value: string): string {\n return value.slice(0, MAX_OUTPUT_LENGTH);\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"javaJudge.js","names":["fs","path","MAX_OUTPUT_LENGTH","resolveJavaRelativePath","sourceCode","sourcePath","packageMatch","fallbackRelativePath","listSubmissionFiles","filePaths","collectSubmissionFiles","rootDir","currentDir","relativeDir","entries","entry","entryRelativePath","readOutputFiles","filePath","parsed","values","z","outputFileSchema","result","value","truncateOutput"],"sources":["../../src/helpers/javaJudge.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { z } from 'zod';\n\nimport type { TestCaseResult } from '../types/testCaseResult.js';\n\ntype OutputFile = NonNullable<TestCaseResult['outputFiles']>[number];\nexport const MAX_OUTPUT_LENGTH = 50_000;\n\nexport async function resolveJavaRelativePath(sourcePath: string, fallbackRelativePath: string): Promise<string> {\n const sourceCode = await fs.promises.readFile(sourcePath, 'utf8');\n const packageMatch = /^\\s*package\\s+([a-zA-Z0-9_.]+)\\s*;/m.exec(sourceCode);\n if (!packageMatch?.[1]) {\n return fallbackRelativePath;\n }\n\n return path.join(packageMatch[1].replaceAll('.', '/'), path.basename(fallbackRelativePath));\n}\n\nexport async function listSubmissionFiles(rootDir: string): Promise<string[]> {\n const filePaths: string[] = [];\n await collectSubmissionFiles(rootDir, '', filePaths);\n return filePaths.toSorted();\n}\n\nasync function collectSubmissionFiles(rootDir: string, relativeDir: string, filePaths: string[]): Promise<void> {\n const currentDir = relativeDir ? path.join(rootDir, relativeDir) : rootDir;\n const entries = await fs.promises.readdir(currentDir, { withFileTypes: true });\n for (const entry of entries) {\n const entryRelativePath = relativeDir ? path.join(relativeDir, entry.name) : entry.name;\n if (entry.isDirectory()) {\n await collectSubmissionFiles(rootDir, entryRelativePath, filePaths);\n continue;\n }\n if (entry.isFile()) {\n filePaths.push(entryRelativePath);\n }\n }\n}\n\nexport function readOutputFiles(filePath: string): OutputFile[] | undefined {\n if (!fs.existsSync(filePath)) {\n return undefined;\n }\n\n // Malformed evaluator JSON remains a harness error (JUDGE_NOT_AVAILABLE), not missing screenshots.\n const parsed: unknown = JSON.parse(fs.readFileSync(filePath, 'utf8'));\n const values = z.array(z.unknown()).safeParse(parsed);\n if (!values.success) return undefined;\n const outputFileSchema = z.object({\n path: z.string(),\n data: z.string(),\n encoding: z.literal('base64').optional(),\n });\n return values.data.flatMap((value) => {\n const result = outputFileSchema.safeParse(value);\n return result.success ? [result.data] : [];\n });\n}\n\nexport function truncateOutput(value: string): string {\n return value.slice(0, MAX_OUTPUT_LENGTH);\n}\n"],"mappings":"wEAQA,MAAaE,EAAoB,IAEjC,eAAsBC,EAAwB,EAAoB,EAA+C,CAC/G,IAAMC,EAAa,MAAMJ,EAAG,SAAS,SAASK,EAAY,MAAM,EAC1DC,EAAe,sCAAsC,KAAKF,CAAU,EAK1E,OAJKE,IAAe,GAIbL,EAAK,KAAKK,EAAa,EAAE,CAAC,WAAW,IAAK,GAAG,EAAGL,EAAK,SAASM,CAAoB,CAAC,EAHjFA,CAIX,CAEA,eAAsBC,EAAoB,EAAoC,CAC5E,IAAMC,EAAsB,CAAC,EAE7B,OADA,MAAMC,EAAuBC,EAAS,GAAIF,CAAS,EAC5CA,EAAU,SAAS,CAC5B,CAEA,eAAeC,EAAuB,EAAiB,EAAqB,EAAoC,CAC9G,IAAME,EAAaC,EAAcZ,EAAK,KAAKU,EAASE,CAAW,EAAIF,EAC7DG,EAAU,MAAMd,EAAG,SAAS,QAAQY,EAAY,CAAE,cAAe,EAAK,CAAC,EAC7E,IAAK,IAAMG,KAASD,EAAS,CAC3B,IAAME,EAAoBH,EAAcZ,EAAK,KAAKY,EAAaE,EAAM,IAAI,EAAIA,EAAM,KAC/EA,EAAM,YAAY,EACpB,MAAML,EAAuBC,EAASK,EAAmBP,CAAS,EAGhEM,EAAM,OAAO,GACfN,EAAU,KAAKO,CAAiB,CAEpC,CACF,CAEA,SAAgBC,EAAgB,EAA4C,CAC1E,GAAI,CAACjB,EAAG,WAAWkB,CAAQ,EACzB,OAIF,IAAMC,EAAkB,KAAK,MAAMnB,EAAG,aAAakB,EAAU,MAAM,CAAC,EAC9DE,EAASC,EAAE,MAAMA,EAAE,QAAQ,CAAC,CAAC,CAAC,UAAUF,CAAM,EACpD,GAAI,CAACC,EAAO,QAAS,OACrB,IAAME,EAAmBD,EAAE,OAAO,CAChC,KAAMA,EAAE,OAAO,EACf,KAAMA,EAAE,OAAO,EACf,SAAUA,EAAE,QAAQ,QAAQ,CAAC,CAAC,SAAS,CACzC,CAAC,EACD,OAAOD,EAAO,KAAK,QAAS,GAAU,CACpC,IAAMG,EAASD,EAAiB,UAAUE,CAAK,EAC/C,OAAOD,EAAO,QAAU,CAACA,EAAO,IAAI,EAAI,CAAC,CAC3C,CAAC,CACH,CAEA,SAAgBE,EAAe,EAAuB,CACpD,OAAOD,EAAM,MAAM,EAAGtB,CAAiB,CACzC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"judgeByStaticAnalysis.cjs","names":["fs","path","findLanguageDefinitionByPath","removeCommentsInSourceCode","DecisionCode","normalizeCodeRule","require_runtime","require_removeCommentsInSourceCode","require_problem","require_findLanguageDefinitionByPath","require_decisionCode","node_fs","node_path","judgeByStaticAnalysis","needsSourceCode","problemMarkdownFrontMatterLike","filePathSet","sourceCodeWithoutCommentFiles","dirent","cwd","relativePath","languageDefinition","text","missingFilePaths","p","violations","rule","re","pattern","matches","file","match","message","formatForbiddenRuleViolation","missingRequiredLabels","f","label","locations","violation","path"],"sources":["../../src/helpers/judgeByStaticAnalysis.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { DecisionCode } from '../types/decisionCode.js';\nimport type { ProblemMarkdownFrontMatter } from '../types/problem.js';\nimport { normalizeCodeRule } from '../types/problem.js';\nimport type { TestCaseResult } from '../types/testCaseResult.js';\
|
|
1
|
+
{"version":3,"file":"judgeByStaticAnalysis.cjs","names":["fs","path","findLanguageDefinitionByPath","removeCommentsInSourceCode","DecisionCode","normalizeCodeRule","require_runtime","require_removeCommentsInSourceCode","require_problem","require_findLanguageDefinitionByPath","require_decisionCode","node_fs","node_path","judgeByStaticAnalysis","needsSourceCode","problemMarkdownFrontMatterLike","filePathSet","sourceCodeWithoutCommentFiles","dirent","cwd","relativePath","languageDefinition","text","missingFilePaths","p","violations","rule","re","pattern","matches","file","match","message","formatForbiddenRuleViolation","missingRequiredLabels","f","label","locations","violation","path"],"sources":["../../src/helpers/judgeByStaticAnalysis.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { DecisionCode } from '../types/decisionCode.js';\nimport type { ProblemMarkdownFrontMatter } from '../types/problem.js';\nimport { normalizeCodeRule } from '../types/problem.js';\nimport type { TestCaseResult } from '../types/testCaseResult.js';\nimport { findLanguageDefinitionByPath } from './findLanguageDefinitionByPath.js';\nimport { removeCommentsInSourceCode } from './removeCommentsInSourceCode.js';\n\ninterface ForbiddenRuleViolation {\n /** Learner-facing description of the rule; the raw pattern when the problem declares no message. */\n label: string;\n matches: { path: string; match: string }[];\n}\n\nexport async function judgeByStaticAnalysis(\n cwd: string,\n problemMarkdownFrontMatterLike: Pick<\n ProblemMarkdownFrontMatter,\n 'forbiddenRegExpsInCode' | 'forbiddenTextsInCode' | 'requiredRegExpsInCode' | 'requiredSubmissionFilePaths'\n >\n): Promise<Pick<TestCaseResult, 'decisionCode' | 'feedbackMarkdown'> | undefined> {\n const needsSourceCode =\n !!problemMarkdownFrontMatterLike.forbiddenRegExpsInCode?.length ||\n !!problemMarkdownFrontMatterLike.forbiddenTextsInCode?.length ||\n !!problemMarkdownFrontMatterLike.requiredRegExpsInCode?.length;\n if (!needsSourceCode && !problemMarkdownFrontMatterLike.requiredSubmissionFilePaths?.length) return;\n\n const filePathSet = new Set<string>();\n const sourceCodeWithoutCommentFiles: { path: string; data: string }[] = [];\n\n for (const dirent of await fs.promises.readdir(cwd, { withFileTypes: true, recursive: true })) {\n if (!dirent.isFile()) continue;\n\n const relativePath = path.relative(cwd, path.join(dirent.parentPath, dirent.name));\n filePathSet.add(relativePath);\n\n if (!needsSourceCode) continue;\n const languageDefinition = findLanguageDefinitionByPath(dirent.name);\n if (!languageDefinition) continue;\n\n const text = await fs.promises.readFile(path.join(dirent.parentPath, dirent.name), 'utf8');\n const isBinary = text.includes('\\uFFFD');\n if (isBinary) continue;\n\n sourceCodeWithoutCommentFiles.push({\n path: relativePath,\n data: languageDefinition.grammer ? removeCommentsInSourceCode(languageDefinition.grammer, text) : text,\n });\n }\n\n if (problemMarkdownFrontMatterLike.requiredSubmissionFilePaths) {\n const missingFilePaths = problemMarkdownFrontMatterLike.requiredSubmissionFilePaths\n .filter((p) => !filePathSet.has(p))\n .toSorted();\n\n if (missingFilePaths.length > 0) {\n return {\n decisionCode: DecisionCode.MISSING_REQUIRED_SUBMISSION_FILE_ERROR,\n feedbackMarkdown: `ファイルが不足しています。\n次のファイルを追加してから再度提出してください。\n\n${missingFilePaths.map((p) => `- \\`${p}\\``).join('\\n')}\n`,\n };\n }\n }\n\n const violations: ForbiddenRuleViolation[] = [];\n\n for (const rule of problemMarkdownFrontMatterLike.forbiddenRegExpsInCode ?? []) {\n const { pattern, message } = normalizeCodeRule(rule);\n const re = new RegExp(pattern, 'g');\n const matches = sourceCodeWithoutCommentFiles.flatMap((file) =>\n [...file.data.matchAll(re)].map((match) => ({ path: file.path, match: match[0] }))\n );\n if (matches.length > 0) violations.push({ label: message ?? `禁止パターン \\`${re.toString()}\\``, matches });\n }\n for (const rule of problemMarkdownFrontMatterLike.forbiddenTextsInCode ?? []) {\n const { pattern, message } = normalizeCodeRule(rule);\n const matches = sourceCodeWithoutCommentFiles\n .filter((file) => file.data.includes(pattern))\n .map((file) => ({ path: file.path, match: pattern }));\n if (matches.length > 0) violations.push({ label: message ?? `禁止文字列 \\`${pattern}\\``, matches });\n }\n\n if (violations.length > 0) {\n return {\n decisionCode: DecisionCode.FORBIDDEN_PATTERNS_IN_CODE_ERROR,\n feedbackMarkdown: `ソースコード中に禁止された文字列が含まれています。\nソースコードを修正してから再度提出してください。\n\n${violations.map(formatForbiddenRuleViolation).join('\\n')}\n`,\n };\n }\n\n const missingRequiredLabels: string[] = [];\n\n for (const rule of problemMarkdownFrontMatterLike.requiredRegExpsInCode ?? []) {\n const { pattern, message } = normalizeCodeRule(rule);\n const re = new RegExp(pattern);\n const isFound = sourceCodeWithoutCommentFiles.some((f) => re.test(f.data));\n if (!isFound) missingRequiredLabels.push(message ?? `\\`${re.toString()}\\``);\n }\n\n if (missingRequiredLabels.length > 0) {\n return {\n decisionCode: DecisionCode.REQUIRED_PATTERNS_IN_CODE_ERROR,\n feedbackMarkdown: `ソースコード中に必要な文字列が含まれていません。\nソースコードを修正してから再度提出してください。\n\n${missingRequiredLabels.map((label) => `- ${label}`).join('\\n')}\n`,\n };\n }\n\n return;\n}\n\nfunction formatForbiddenRuleViolation(violation: ForbiddenRuleViolation): string {\n const locations = [...new Set(violation.matches.map(({ path, match }) => ` - \\`${path}\\`: \\`${match}\\``))];\n return `- ${violation.label}\\n${locations.join('\\n')}`;\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"judgeByStaticAnalysis.js","names":["fs","path","judgeByStaticAnalysis","needsSourceCode","problemMarkdownFrontMatterLike","filePathSet","sourceCodeWithoutCommentFiles","dirent","cwd","relativePath","languageDefinition","findLanguageDefinitionByPath","text","removeCommentsInSourceCode","missingFilePaths","p","DecisionCode","violations","rule","normalizeCodeRule","re","pattern","matches","file","match","message","formatForbiddenRuleViolation","missingRequiredLabels","f","label","locations","violation"],"sources":["../../src/helpers/judgeByStaticAnalysis.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { DecisionCode } from '../types/decisionCode.js';\nimport type { ProblemMarkdownFrontMatter } from '../types/problem.js';\nimport { normalizeCodeRule } from '../types/problem.js';\nimport type { TestCaseResult } from '../types/testCaseResult.js';\
|
|
1
|
+
{"version":3,"file":"judgeByStaticAnalysis.js","names":["fs","path","judgeByStaticAnalysis","needsSourceCode","problemMarkdownFrontMatterLike","filePathSet","sourceCodeWithoutCommentFiles","dirent","cwd","relativePath","languageDefinition","findLanguageDefinitionByPath","text","removeCommentsInSourceCode","missingFilePaths","p","DecisionCode","violations","rule","normalizeCodeRule","re","pattern","matches","file","match","message","formatForbiddenRuleViolation","missingRequiredLabels","f","label","locations","violation"],"sources":["../../src/helpers/judgeByStaticAnalysis.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { DecisionCode } from '../types/decisionCode.js';\nimport type { ProblemMarkdownFrontMatter } from '../types/problem.js';\nimport { normalizeCodeRule } from '../types/problem.js';\nimport type { TestCaseResult } from '../types/testCaseResult.js';\nimport { findLanguageDefinitionByPath } from './findLanguageDefinitionByPath.js';\nimport { removeCommentsInSourceCode } from './removeCommentsInSourceCode.js';\n\ninterface ForbiddenRuleViolation {\n /** Learner-facing description of the rule; the raw pattern when the problem declares no message. */\n label: string;\n matches: { path: string; match: string }[];\n}\n\nexport async function judgeByStaticAnalysis(\n cwd: string,\n problemMarkdownFrontMatterLike: Pick<\n ProblemMarkdownFrontMatter,\n 'forbiddenRegExpsInCode' | 'forbiddenTextsInCode' | 'requiredRegExpsInCode' | 'requiredSubmissionFilePaths'\n >\n): Promise<Pick<TestCaseResult, 'decisionCode' | 'feedbackMarkdown'> | undefined> {\n const needsSourceCode =\n !!problemMarkdownFrontMatterLike.forbiddenRegExpsInCode?.length ||\n !!problemMarkdownFrontMatterLike.forbiddenTextsInCode?.length ||\n !!problemMarkdownFrontMatterLike.requiredRegExpsInCode?.length;\n if (!needsSourceCode && !problemMarkdownFrontMatterLike.requiredSubmissionFilePaths?.length) return;\n\n const filePathSet = new Set<string>();\n const sourceCodeWithoutCommentFiles: { path: string; data: string }[] = [];\n\n for (const dirent of await fs.promises.readdir(cwd, { withFileTypes: true, recursive: true })) {\n if (!dirent.isFile()) continue;\n\n const relativePath = path.relative(cwd, path.join(dirent.parentPath, dirent.name));\n filePathSet.add(relativePath);\n\n if (!needsSourceCode) continue;\n const languageDefinition = findLanguageDefinitionByPath(dirent.name);\n if (!languageDefinition) continue;\n\n const text = await fs.promises.readFile(path.join(dirent.parentPath, dirent.name), 'utf8');\n const isBinary = text.includes('\\uFFFD');\n if (isBinary) continue;\n\n sourceCodeWithoutCommentFiles.push({\n path: relativePath,\n data: languageDefinition.grammer ? removeCommentsInSourceCode(languageDefinition.grammer, text) : text,\n });\n }\n\n if (problemMarkdownFrontMatterLike.requiredSubmissionFilePaths) {\n const missingFilePaths = problemMarkdownFrontMatterLike.requiredSubmissionFilePaths\n .filter((p) => !filePathSet.has(p))\n .toSorted();\n\n if (missingFilePaths.length > 0) {\n return {\n decisionCode: DecisionCode.MISSING_REQUIRED_SUBMISSION_FILE_ERROR,\n feedbackMarkdown: `ファイルが不足しています。\n次のファイルを追加してから再度提出してください。\n\n${missingFilePaths.map((p) => `- \\`${p}\\``).join('\\n')}\n`,\n };\n }\n }\n\n const violations: ForbiddenRuleViolation[] = [];\n\n for (const rule of problemMarkdownFrontMatterLike.forbiddenRegExpsInCode ?? []) {\n const { pattern, message } = normalizeCodeRule(rule);\n const re = new RegExp(pattern, 'g');\n const matches = sourceCodeWithoutCommentFiles.flatMap((file) =>\n [...file.data.matchAll(re)].map((match) => ({ path: file.path, match: match[0] }))\n );\n if (matches.length > 0) violations.push({ label: message ?? `禁止パターン \\`${re.toString()}\\``, matches });\n }\n for (const rule of problemMarkdownFrontMatterLike.forbiddenTextsInCode ?? []) {\n const { pattern, message } = normalizeCodeRule(rule);\n const matches = sourceCodeWithoutCommentFiles\n .filter((file) => file.data.includes(pattern))\n .map((file) => ({ path: file.path, match: pattern }));\n if (matches.length > 0) violations.push({ label: message ?? `禁止文字列 \\`${pattern}\\``, matches });\n }\n\n if (violations.length > 0) {\n return {\n decisionCode: DecisionCode.FORBIDDEN_PATTERNS_IN_CODE_ERROR,\n feedbackMarkdown: `ソースコード中に禁止された文字列が含まれています。\nソースコードを修正してから再度提出してください。\n\n${violations.map(formatForbiddenRuleViolation).join('\\n')}\n`,\n };\n }\n\n const missingRequiredLabels: string[] = [];\n\n for (const rule of problemMarkdownFrontMatterLike.requiredRegExpsInCode ?? []) {\n const { pattern, message } = normalizeCodeRule(rule);\n const re = new RegExp(pattern);\n const isFound = sourceCodeWithoutCommentFiles.some((f) => re.test(f.data));\n if (!isFound) missingRequiredLabels.push(message ?? `\\`${re.toString()}\\``);\n }\n\n if (missingRequiredLabels.length > 0) {\n return {\n decisionCode: DecisionCode.REQUIRED_PATTERNS_IN_CODE_ERROR,\n feedbackMarkdown: `ソースコード中に必要な文字列が含まれていません。\nソースコードを修正してから再度提出してください。\n\n${missingRequiredLabels.map((label) => `- ${label}`).join('\\n')}\n`,\n };\n }\n\n return;\n}\n\nfunction formatForbiddenRuleViolation(violation: ForbiddenRuleViolation): string {\n const locations = [...new Set(violation.matches.map(({ path, match }) => ` - \\`${path}\\`: \\`${match}\\``))];\n return `- ${violation.label}\\n${locations.join('\\n')}`;\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"readOutputFiles.cjs","names":["fs","path","encodeFileForTestCaseResult","require_runtime","require_printTestCaseResult","node_fs","node_path","readOutputFiles","outputFiles","filePath","outputFilePaths","buffer","cwd"],"sources":["../../src/helpers/readOutputFiles.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport type { TestCaseResult } from '../types/testCaseResult.js';\
|
|
1
|
+
{"version":3,"file":"readOutputFiles.cjs","names":["fs","path","encodeFileForTestCaseResult","require_runtime","require_printTestCaseResult","node_fs","node_path","readOutputFiles","outputFiles","filePath","outputFilePaths","buffer","cwd"],"sources":["../../src/helpers/readOutputFiles.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport type { TestCaseResult } from '../types/testCaseResult.js';\nimport { encodeFileForTestCaseResult } from './printTestCaseResult.js';\n\nexport async function readOutputFiles(\n cwd: string,\n outputFilePaths: readonly string[]\n): Promise<NonNullable<TestCaseResult['outputFiles']>> {\n const outputFiles: NonNullable<TestCaseResult['outputFiles']> = [];\n for (const filePath of outputFilePaths) {\n try {\n // Read as-is on purpose: the submission runs as the same OS user as this harness, so a symlink\n // only reaches what it can already read, and a planted FIFO only stalls its own run, which the\n // judge server's request timeout ends.\n const buffer = await fs.promises.readFile(path.join(cwd, filePath));\n outputFiles.push(encodeFileForTestCaseResult(filePath, buffer));\n } catch {\n // file not found\n }\n }\n return outputFiles;\n}\n"],"mappings":"+LAMA,eAAsBO,EACpB,EACA,EACqD,CACrD,IAAMC,EAA0D,CAAC,EACjE,IAAK,IAAMC,KAAYC,EACrB,GAAI,CAIF,IAAMC,EAAS,MAAMX,EAAAA,QAAG,SAAS,SAASC,EAAAA,QAAK,KAAKW,EAAKH,CAAQ,CAAC,EAClED,EAAY,KAAKN,EAAAA,4BAA4BO,EAAUE,CAAM,CAAC,CAChE,MAAQ,CAER,CAEF,OAAOH,CACT"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"readOutputFiles.js","names":["fs","path","readOutputFiles","outputFiles","filePath","outputFilePaths","buffer","cwd","encodeFileForTestCaseResult"],"sources":["../../src/helpers/readOutputFiles.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport type { TestCaseResult } from '../types/testCaseResult.js';\
|
|
1
|
+
{"version":3,"file":"readOutputFiles.js","names":["fs","path","readOutputFiles","outputFiles","filePath","outputFilePaths","buffer","cwd","encodeFileForTestCaseResult"],"sources":["../../src/helpers/readOutputFiles.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport type { TestCaseResult } from '../types/testCaseResult.js';\nimport { encodeFileForTestCaseResult } from './printTestCaseResult.js';\n\nexport async function readOutputFiles(\n cwd: string,\n outputFilePaths: readonly string[]\n): Promise<NonNullable<TestCaseResult['outputFiles']>> {\n const outputFiles: NonNullable<TestCaseResult['outputFiles']> = [];\n for (const filePath of outputFilePaths) {\n try {\n // Read as-is on purpose: the submission runs as the same OS user as this harness, so a symlink\n // only reaches what it can already read, and a planted FIFO only stalls its own run, which the\n // judge server's request timeout ends.\n const buffer = await fs.promises.readFile(path.join(cwd, filePath));\n outputFiles.push(encodeFileForTestCaseResult(filePath, buffer));\n } catch {\n // file not found\n }\n }\n return outputFiles;\n}\n"],"mappings":"uHAMA,eAAsBE,EACpB,EACA,EACqD,CACrD,IAAMC,EAA0D,CAAC,EACjE,IAAK,IAAMC,KAAYC,EACrB,GAAI,CAIF,IAAMC,EAAS,MAAMN,EAAG,SAAS,SAASC,EAAK,KAAKM,EAAKH,CAAQ,CAAC,EAClED,EAAY,KAAKK,EAA4BJ,EAAUE,CAAM,CAAC,CAChE,MAAQ,CAER,CAEF,OAAOH,CACT"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./
|
|
1
|
+
"use strict";const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./findModelAnswerDirs.cjs"),n=require("./printDebugBanner.cjs");let r=require("node:path");r=e.__toESM(r,1);async function i(e,i){if(i)return{cwds:[{cwd:i,expectedResult:`accepted`}],isDebugMode:!1};let[a,o]=await Promise.all([t.findModelAnswerDirs(e),t.findFailingModelAnswerDirs(e)]);if(a.length===0&&o.length===0)throw Error(`cwd argument required (no model answer directories found in ${r.default.join(e,t.MODEL_ANSWERS_DIRNAME)} or ${r.default.join(e,t.FAILING_MODEL_ANSWERS_DIRNAME)})`);let c=s(),l=r.default.relative(e,a[0]??o[0]??``)||`<model_answer>`;return n.printDebugBanner([`[DEBUG MODE] cwd not provided -- judging against all model answers`,``,`Problem dir : ${e}`,`Model answers (${a.length}):`,...a.map(t=>` - ${r.default.relative(e,t)}`),`Failing model answers (${o.length}):`,...o.map(t=>` - ${r.default.relative(e,t)}`),``,`To judge a single cwd (e.g., a user submission), pass it as the first argument:`,` bun ${c} <cwd> [params-json]`,`For example:`,` bun ${c} ${l}`,` bun ${c} ${l} '{"language":"python"}'`]),{cwds:[...a.map(e=>({cwd:e,expectedResult:`accepted`})),...o.map(e=>({cwd:e,expectedResult:`rejected`}))],isDebugMode:!0}}function a(e,t){let i=r.default.relative(e,t.cwd)||t.cwd,a=s();n.printDebugBanner([`[DEBUG MODE] judging: ${i}`,`Expected result: ${t.expectedResult}`,``,`To judge only this cwd as a normal submission:`,` bun ${a} ${i}`,`Run without a cwd to check debug-mode expectations.`])}function o(e,t){let i=r.default.relative(e,t.cwd)||t.cwd;n.printDebugBanner([`[DEBUG MODE] expectation failed: ${i}`,`Expected result: ${t.expectedResult}`,t.expectedResult===`accepted`?`The model answer was rejected by the judge.`:`The failing model answer was accepted by the judge.`])}function s(){let e=process.argv[1];return e?r.default.basename(e):`judge.ts`}exports.printDebugCwdBanner=a,exports.printDebugExpectationFailureBanner=o,exports.resolveCwds=i;
|
|
2
2
|
//# sourceMappingURL=resolveCwds.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolveCwds.cjs","names":["findModelAnswerDirs","findFailingModelAnswerDirs","path","MODEL_ANSWERS_DIRNAME","FAILING_MODEL_ANSWERS_DIRNAME","require_runtime","
|
|
1
|
+
{"version":3,"file":"resolveCwds.cjs","names":["findModelAnswerDirs","findFailingModelAnswerDirs","path","MODEL_ANSWERS_DIRNAME","FAILING_MODEL_ANSWERS_DIRNAME","require_runtime","require_findModelAnswerDirs","require_printDebugBanner","node_path","resolveCwds","cwdArg","modelAnswerDirs","failingModelAnswerDirs","problemDir","scriptName","getInvokedScriptName","exampleAnswer","dir","printDebugCwdBanner","relativeCwd","resolvedCwd","printDebugExpectationFailureBanner","scriptPath"],"sources":["../../src/helpers/resolveCwds.ts"],"sourcesContent":["import path from 'node:path';\n\nimport {\n FAILING_MODEL_ANSWERS_DIRNAME,\n findFailingModelAnswerDirs,\n findModelAnswerDirs,\n MODEL_ANSWERS_DIRNAME,\n} from './findModelAnswerDirs.js';\nimport { printDebugBanner } from './printDebugBanner.js';\n\nexport type ExpectedJudgeResult = 'accepted' | 'rejected';\n\nexport interface ResolvedCwd {\n cwd: string;\n expectedResult: ExpectedJudgeResult;\n}\n\nexport interface ResolvedCwds {\n cwds: readonly ResolvedCwd[];\n isDebugMode: boolean;\n}\n\n/**\n * Resolve which working directories to judge.\n *\n * - If `cwdArg` is provided, use it as the single cwd.\n * - Otherwise (debug mode), enumerate `<problemDir>/model_answers/*` and\n * `<problemDir>/model_answers.fails/*` and judge each.\n * A prominent banner is printed so users can tell at a glance that this is a debug run.\n *\n * @throws if `cwdArg` is missing and no model answer directories are found.\n */\nexport async function resolveCwds(problemDir: string, cwdArg: string | undefined): Promise<ResolvedCwds> {\n if (cwdArg) return { cwds: [{ cwd: cwdArg, expectedResult: 'accepted' }], isDebugMode: false };\n\n const [modelAnswerDirs, failingModelAnswerDirs] = await Promise.all([\n findModelAnswerDirs(problemDir),\n findFailingModelAnswerDirs(problemDir),\n ]);\n if (modelAnswerDirs.length === 0 && failingModelAnswerDirs.length === 0) {\n throw new Error(\n `cwd argument required (no model answer directories found in ${path.join(problemDir, MODEL_ANSWERS_DIRNAME)} or ${path.join(problemDir, FAILING_MODEL_ANSWERS_DIRNAME)})`\n );\n }\n\n const scriptName = getInvokedScriptName();\n const exampleAnswer =\n path.relative(problemDir, modelAnswerDirs[0] ?? failingModelAnswerDirs[0] ?? '') || '<model_answer>';\n printDebugBanner([\n '[DEBUG MODE] cwd not provided -- judging against all model answers',\n '',\n `Problem dir : ${problemDir}`,\n `Model answers (${modelAnswerDirs.length}):`,\n ...modelAnswerDirs.map((dir) => ` - ${path.relative(problemDir, dir)}`),\n `Failing model answers (${failingModelAnswerDirs.length}):`,\n ...failingModelAnswerDirs.map((dir) => ` - ${path.relative(problemDir, dir)}`),\n '',\n 'To judge a single cwd (e.g., a user submission), pass it as the first argument:',\n ` bun ${scriptName} <cwd> [params-json]`,\n 'For example:',\n ` bun ${scriptName} ${exampleAnswer}`,\n ` bun ${scriptName} ${exampleAnswer} '{\"language\":\"python\"}'`,\n ]);\n\n return {\n cwds: [\n ...modelAnswerDirs.map((cwd) => ({ cwd, expectedResult: 'accepted' as const })),\n ...failingModelAnswerDirs.map((cwd) => ({ cwd, expectedResult: 'rejected' as const })),\n ],\n isDebugMode: true,\n };\n}\n\n/**\n * Print a per-cwd banner shown before judging each model answer in debug mode.\n * Includes the command for re-running this single cwd.\n */\nexport function printDebugCwdBanner(problemDir: string, resolvedCwd: ResolvedCwd): void {\n const relativeCwd = path.relative(problemDir, resolvedCwd.cwd) || resolvedCwd.cwd;\n const scriptName = getInvokedScriptName();\n printDebugBanner([\n `[DEBUG MODE] judging: ${relativeCwd}`,\n `Expected result: ${resolvedCwd.expectedResult}`,\n '',\n 'To judge only this cwd as a normal submission:',\n ` bun ${scriptName} ${relativeCwd}`,\n 'Run without a cwd to check debug-mode expectations.',\n ]);\n}\n\nexport function printDebugExpectationFailureBanner(problemDir: string, resolvedCwd: ResolvedCwd): void {\n const relativeCwd = path.relative(problemDir, resolvedCwd.cwd) || resolvedCwd.cwd;\n printDebugBanner([\n `[DEBUG MODE] expectation failed: ${relativeCwd}`,\n `Expected result: ${resolvedCwd.expectedResult}`,\n resolvedCwd.expectedResult === 'accepted'\n ? 'The model answer was rejected by the judge.'\n : 'The failing model answer was accepted by the judge.',\n ]);\n}\n\nfunction getInvokedScriptName(): string {\n const scriptPath = process.argv[1];\n return scriptPath ? path.basename(scriptPath) : 'judge.ts';\n}\n"],"mappings":"yLAgCA,eAAsBS,EAAY,EAAoB,EAAmD,CACvG,GAAIC,EAAQ,MAAO,CAAE,KAAM,CAAC,CAAE,IAAKA,EAAQ,eAAgB,UAAW,CAAC,EAAG,YAAa,EAAM,EAE7F,GAAM,CAACC,EAAiBC,GAA0B,MAAM,QAAQ,IAAI,CAClEZ,EAAAA,oBAAoBa,CAAU,EAC9BZ,EAAAA,2BAA2BY,CAAU,CACvC,CAAC,EACD,GAAIF,EAAgB,SAAW,GAAKC,EAAuB,SAAW,EACpE,MAAU,MACR,+DAA+DV,EAAAA,QAAK,KAAKW,EAAYV,EAAAA,qBAAqB,EAAE,MAAMD,EAAAA,QAAK,KAAKW,EAAYT,EAAAA,6BAA6B,EAAE,EACzK,EAGF,IAAMU,EAAaC,EAAqB,EAClCC,EACJd,EAAAA,QAAK,SAASW,EAAYF,EAAgB,IAAMC,EAAuB,IAAM,EAAE,GAAK,iBAiBtF,OAhBAL,EAAA,iBAAiB,CACf,qEACA,GACA,iBAAiBM,IACjB,kBAAkBF,EAAgB,OAAO,IACzC,GAAGA,EAAgB,IAAK,GAAQ,OAAOT,EAAAA,QAAK,SAASW,EAAYI,CAAG,GAAG,EACvE,0BAA0BL,EAAuB,OAAO,IACxD,GAAGA,EAAuB,IAAK,GAAQ,OAAOV,EAAAA,QAAK,SAASW,EAAYI,CAAG,GAAG,EAC9E,GACA,kFACA,SAASH,EAAW,sBACpB,eACA,SAASA,EAAW,GAAGE,IACvB,SAASF,EAAW,GAAGE,EAAc,yBACvC,CAAC,EAEM,CACL,KAAM,CACJ,GAAGL,EAAgB,IAAK,IAAS,CAAE,MAAK,eAAgB,UAAoB,EAAE,EAC9E,GAAGC,EAAuB,IAAK,IAAS,CAAE,MAAK,eAAgB,UAAoB,EAAE,CACvF,EACA,YAAa,EACf,CACF,CAMA,SAAgBM,EAAoB,EAAoB,EAAgC,CACtF,IAAMC,EAAcjB,EAAAA,QAAK,SAASW,EAAYO,EAAY,GAAG,GAAKA,EAAY,IACxEN,EAAaC,EAAqB,EACxCR,EAAA,iBAAiB,CACf,yBAAyBY,IACzB,oBAAoBC,EAAY,iBAChC,GACA,iDACA,SAASN,EAAW,GAAGK,IACvB,qDACF,CAAC,CACH,CAEA,SAAgBE,EAAmC,EAAoB,EAAgC,CACrG,IAAMF,EAAcjB,EAAAA,QAAK,SAASW,EAAYO,EAAY,GAAG,GAAKA,EAAY,IAC9Eb,EAAA,iBAAiB,CACf,oCAAoCY,IACpC,oBAAoBC,EAAY,iBAChCA,EAAY,iBAAmB,WAC3B,8CACA,qDACN,CAAC,CACH,CAEA,SAASL,GAA+B,CACtC,IAAMO,EAAa,QAAQ,KAAK,GAChC,OAAOA,EAAapB,EAAAA,QAAK,SAASoB,CAAU,EAAI,UAClD"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{FAILING_MODEL_ANSWERS_DIRNAME as e,MODEL_ANSWERS_DIRNAME as t,findFailingModelAnswerDirs as n,findModelAnswerDirs as r}from"./findModelAnswerDirs.js";import{printDebugBanner as i}from"./printDebugBanner.js";import a from"node:path";async function o(o,s){if(s)return{cwds:[{cwd:s,expectedResult:`accepted`}],isDebugMode:!1};let[c,u]=await Promise.all([r(o),n(o)]);if(c.length===0&&u.length===0)throw Error(`cwd argument required (no model answer directories found in ${a.join(o,t)} or ${a.join(o,e)})`);let d=l(),f=a.relative(o,c[0]??u[0]??``)||`<model_answer>`;return i([`[DEBUG MODE] cwd not provided -- judging against all model answers`,``,`Problem dir : ${o}`,`Model answers (${c.length}):`,...c.map(e=>` - ${a.relative(o,e)}`),`Failing model answers (${u.length}):`,...u.map(e=>` - ${a.relative(o,e)}`),``,`To judge a single cwd (e.g., a user submission), pass it as the first argument:`,` bun ${d} <cwd> [params-json]`,`For example:`,` bun ${d} ${f}`,` bun ${d} ${f} '{"language":"python"}'`]),{cwds:[...c.map(e=>({cwd:e,expectedResult:`accepted`})),...u.map(e=>({cwd:e,expectedResult:`rejected`}))],isDebugMode:!0}}function s(e,t){let n=a.relative(e,t.cwd)||t.cwd,r=l();i([`[DEBUG MODE] judging: ${n}`,`Expected result: ${t.expectedResult}`,``,`To judge only this cwd as a normal submission:`,` bun ${r} ${n}`,`Run without a cwd to check debug-mode expectations.`])}function c(e,t){let n=a.relative(e,t.cwd)||t.cwd;i([`[DEBUG MODE] expectation failed: ${n}`,`Expected result: ${t.expectedResult}`,t.expectedResult===`accepted`?`The model answer was rejected by the judge.`:`The failing model answer was accepted by the judge.`])}function l(){let e=process.argv[1];return e?a.basename(e):`judge.ts`}export{s as printDebugCwdBanner,c as printDebugExpectationFailureBanner,o as resolveCwds};
|
|
2
2
|
//# sourceMappingURL=resolveCwds.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolveCwds.js","names":["path","resolveCwds","cwdArg","modelAnswerDirs","failingModelAnswerDirs","findModelAnswerDirs","problemDir","findFailingModelAnswerDirs","MODEL_ANSWERS_DIRNAME","FAILING_MODEL_ANSWERS_DIRNAME","scriptName","getInvokedScriptName","exampleAnswer","printDebugBanner","dir","printDebugCwdBanner","relativeCwd","resolvedCwd","printDebugExpectationFailureBanner","scriptPath"],"sources":["../../src/helpers/resolveCwds.ts"],"sourcesContent":["import path from 'node:path';\n\nimport {\n FAILING_MODEL_ANSWERS_DIRNAME,\n
|
|
1
|
+
{"version":3,"file":"resolveCwds.js","names":["path","resolveCwds","cwdArg","modelAnswerDirs","failingModelAnswerDirs","findModelAnswerDirs","problemDir","findFailingModelAnswerDirs","MODEL_ANSWERS_DIRNAME","FAILING_MODEL_ANSWERS_DIRNAME","scriptName","getInvokedScriptName","exampleAnswer","printDebugBanner","dir","printDebugCwdBanner","relativeCwd","resolvedCwd","printDebugExpectationFailureBanner","scriptPath"],"sources":["../../src/helpers/resolveCwds.ts"],"sourcesContent":["import path from 'node:path';\n\nimport {\n FAILING_MODEL_ANSWERS_DIRNAME,\n findFailingModelAnswerDirs,\n findModelAnswerDirs,\n MODEL_ANSWERS_DIRNAME,\n} from './findModelAnswerDirs.js';\nimport { printDebugBanner } from './printDebugBanner.js';\n\nexport type ExpectedJudgeResult = 'accepted' | 'rejected';\n\nexport interface ResolvedCwd {\n cwd: string;\n expectedResult: ExpectedJudgeResult;\n}\n\nexport interface ResolvedCwds {\n cwds: readonly ResolvedCwd[];\n isDebugMode: boolean;\n}\n\n/**\n * Resolve which working directories to judge.\n *\n * - If `cwdArg` is provided, use it as the single cwd.\n * - Otherwise (debug mode), enumerate `<problemDir>/model_answers/*` and\n * `<problemDir>/model_answers.fails/*` and judge each.\n * A prominent banner is printed so users can tell at a glance that this is a debug run.\n *\n * @throws if `cwdArg` is missing and no model answer directories are found.\n */\nexport async function resolveCwds(problemDir: string, cwdArg: string | undefined): Promise<ResolvedCwds> {\n if (cwdArg) return { cwds: [{ cwd: cwdArg, expectedResult: 'accepted' }], isDebugMode: false };\n\n const [modelAnswerDirs, failingModelAnswerDirs] = await Promise.all([\n findModelAnswerDirs(problemDir),\n findFailingModelAnswerDirs(problemDir),\n ]);\n if (modelAnswerDirs.length === 0 && failingModelAnswerDirs.length === 0) {\n throw new Error(\n `cwd argument required (no model answer directories found in ${path.join(problemDir, MODEL_ANSWERS_DIRNAME)} or ${path.join(problemDir, FAILING_MODEL_ANSWERS_DIRNAME)})`\n );\n }\n\n const scriptName = getInvokedScriptName();\n const exampleAnswer =\n path.relative(problemDir, modelAnswerDirs[0] ?? failingModelAnswerDirs[0] ?? '') || '<model_answer>';\n printDebugBanner([\n '[DEBUG MODE] cwd not provided -- judging against all model answers',\n '',\n `Problem dir : ${problemDir}`,\n `Model answers (${modelAnswerDirs.length}):`,\n ...modelAnswerDirs.map((dir) => ` - ${path.relative(problemDir, dir)}`),\n `Failing model answers (${failingModelAnswerDirs.length}):`,\n ...failingModelAnswerDirs.map((dir) => ` - ${path.relative(problemDir, dir)}`),\n '',\n 'To judge a single cwd (e.g., a user submission), pass it as the first argument:',\n ` bun ${scriptName} <cwd> [params-json]`,\n 'For example:',\n ` bun ${scriptName} ${exampleAnswer}`,\n ` bun ${scriptName} ${exampleAnswer} '{\"language\":\"python\"}'`,\n ]);\n\n return {\n cwds: [\n ...modelAnswerDirs.map((cwd) => ({ cwd, expectedResult: 'accepted' as const })),\n ...failingModelAnswerDirs.map((cwd) => ({ cwd, expectedResult: 'rejected' as const })),\n ],\n isDebugMode: true,\n };\n}\n\n/**\n * Print a per-cwd banner shown before judging each model answer in debug mode.\n * Includes the command for re-running this single cwd.\n */\nexport function printDebugCwdBanner(problemDir: string, resolvedCwd: ResolvedCwd): void {\n const relativeCwd = path.relative(problemDir, resolvedCwd.cwd) || resolvedCwd.cwd;\n const scriptName = getInvokedScriptName();\n printDebugBanner([\n `[DEBUG MODE] judging: ${relativeCwd}`,\n `Expected result: ${resolvedCwd.expectedResult}`,\n '',\n 'To judge only this cwd as a normal submission:',\n ` bun ${scriptName} ${relativeCwd}`,\n 'Run without a cwd to check debug-mode expectations.',\n ]);\n}\n\nexport function printDebugExpectationFailureBanner(problemDir: string, resolvedCwd: ResolvedCwd): void {\n const relativeCwd = path.relative(problemDir, resolvedCwd.cwd) || resolvedCwd.cwd;\n printDebugBanner([\n `[DEBUG MODE] expectation failed: ${relativeCwd}`,\n `Expected result: ${resolvedCwd.expectedResult}`,\n resolvedCwd.expectedResult === 'accepted'\n ? 'The model answer was rejected by the judge.'\n : 'The failing model answer was accepted by the judge.',\n ]);\n}\n\nfunction getInvokedScriptName(): string {\n const scriptPath = process.argv[1];\n return scriptPath ? path.basename(scriptPath) : 'judge.ts';\n}\n"],"mappings":"+OAgCA,eAAsBC,EAAY,EAAoB,EAAmD,CACvG,GAAIC,EAAQ,MAAO,CAAE,KAAM,CAAC,CAAE,IAAKA,EAAQ,eAAgB,UAAW,CAAC,EAAG,YAAa,EAAM,EAE7F,GAAM,CAACC,EAAiBC,GAA0B,MAAM,QAAQ,IAAI,CAClEC,EAAoBC,CAAU,EAC9BC,EAA2BD,CAAU,CACvC,CAAC,EACD,GAAIH,EAAgB,SAAW,GAAKC,EAAuB,SAAW,EACpE,MAAU,MACR,+DAA+DJ,EAAK,KAAKM,EAAYE,CAAqB,EAAE,MAAMR,EAAK,KAAKM,EAAYG,CAA6B,EAAE,EACzK,EAGF,IAAMC,EAAaC,EAAqB,EAClCC,EACJZ,EAAK,SAASM,EAAYH,EAAgB,IAAMC,EAAuB,IAAM,EAAE,GAAK,iBAiBtF,OAhBAS,EAAiB,CACf,qEACA,GACA,iBAAiBP,IACjB,kBAAkBH,EAAgB,OAAO,IACzC,GAAGA,EAAgB,IAAK,GAAQ,OAAOH,EAAK,SAASM,EAAYQ,CAAG,GAAG,EACvE,0BAA0BV,EAAuB,OAAO,IACxD,GAAGA,EAAuB,IAAK,GAAQ,OAAOJ,EAAK,SAASM,EAAYQ,CAAG,GAAG,EAC9E,GACA,kFACA,SAASJ,EAAW,sBACpB,eACA,SAASA,EAAW,GAAGE,IACvB,SAASF,EAAW,GAAGE,EAAc,yBACvC,CAAC,EAEM,CACL,KAAM,CACJ,GAAGT,EAAgB,IAAK,IAAS,CAAE,MAAK,eAAgB,UAAoB,EAAE,EAC9E,GAAGC,EAAuB,IAAK,IAAS,CAAE,MAAK,eAAgB,UAAoB,EAAE,CACvF,EACA,YAAa,EACf,CACF,CAMA,SAAgBW,EAAoB,EAAoB,EAAgC,CACtF,IAAMC,EAAchB,EAAK,SAASM,EAAYW,EAAY,GAAG,GAAKA,EAAY,IACxEP,EAAaC,EAAqB,EACxCE,EAAiB,CACf,yBAAyBG,IACzB,oBAAoBC,EAAY,iBAChC,GACA,iDACA,SAASP,EAAW,GAAGM,IACvB,qDACF,CAAC,CACH,CAEA,SAAgBE,EAAmC,EAAoB,EAAgC,CACrG,IAAMF,EAAchB,EAAK,SAASM,EAAYW,EAAY,GAAG,GAAKA,EAAY,IAC9EJ,EAAiB,CACf,oCAAoCG,IACpC,oBAAoBC,EAAY,iBAChCA,EAAY,iBAAmB,WAC3B,8CACA,qDACN,CAAC,CACH,CAEA,SAASN,GAA+B,CACtC,IAAMQ,EAAa,QAAQ,KAAK,GAChC,OAAOA,EAAanB,EAAK,SAASmB,CAAU,EAAI,UAClD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runCommandInTemporaryPackageManagerProject.cjs","names":["fs","path","os","spawnWithLimits","forciblyRemoveDirectory","hasErrorCode","isRecord","require_runtime","require_spawnWithLimits","require_temporaryProblemDirCopy","node_path","node_fs_promises","node_os","_willbooster_shared_lib","packageManagerProjectFilePaths","packageManagerInstallCommandResolvers","resolveBunInstallCommand","resolveCargoInstallCommand","resolveGoInstallCommand","resolveGradleInstallCommand","resolveMavenInstallCommand","resolveNpmInstallCommand","resolvePnpmInstallCommand","resolveRubyInstallCommand","resolveUvInstallCommand","resolveYarnInstallCommand","runCommandInTemporaryPackageManagerProject","runDir","options","copyPackageManagerProjectFiles","env","installCommand","resolveInstallCommand","command","outputLimitBytes","installResult","isFailedSpawnResult","toPackageManagerCommandRunResult","remainingTimeLimitSeconds","result","context","packageManager","pathExists","hasAnyPath","args","isBerry","isYarnBerryProject","hasLockfile","packageJson","readJson","yarnMajorVersion","relativePath","relativePaths","directoryPath","filePath","error","parsed","projectFilePath","copyPathIfExists","sourcePath","destinationPath"],"sources":["../../src/helpers/runCommandInTemporaryPackageManagerProject.ts"],"sourcesContent":["import fs from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { hasErrorCode, isRecord } from '@willbooster/shared-lib';\n\nimport { spawnWithLimits, type SpawnWithLimitsResult } from './spawnWithLimits.js';\nimport { forciblyRemoveDirectory } from './temporaryProblemDirCopy.js';\n\nexport type PackageManager = 'bun' | 'cargo' | 'go' | 'gradle' | 'maven' | 'npm' | 'pnpm' | 'ruby' | 'uv' | 'yarn';\ntype PackageManagerInstallCommand = readonly [string, ...string[]];\n\nexport interface PackageManagerCommandRunResult {\n stdin: string;\n stdout: string;\n stderr: string;\n status: number | undefined;\n timeSeconds: number;\n /** See `SpawnWithLimitsResult.cpuTimeSeconds`; the install's CPU time counts too. */\n cpuTimeSeconds: number;\n memoryBytes: number;\n timedOut: boolean;\n signal: NodeJS.Signals | undefined;\n outputLimitExceeded: boolean;\n}\n\nexport interface RunCommandInTemporaryPackageManagerProjectOptions {\n cwd: string;\n projectDir: string;\n packageManager: PackageManager;\n command: readonly [string, ...string[]] | ((context: { runDir: string }) => readonly [string, ...string[]]);\n /**\n * Set to false when `command` prepares the dependencies it needs.\n * Defaults to true.\n */\n prepareDependencies?: boolean;\n stdin?: string;\n env?: NodeJS.ProcessEnv;\n timeLimitSeconds: number;\n outputLimitBytes?: number;\n tempDirPrefix?: string;\n projectFilePaths?: readonly string[];\n}\n\nconst packageManagerProjectFilePaths = {\n bun: ['package.json', 'bun.lock', 'bun.lockb'],\n cargo: ['Cargo.toml', 'Cargo.lock'],\n go: ['go.mod', 'go.sum'],\n gradle: [\n 'build.gradle',\n 'build.gradle.kts',\n 'settings.gradle',\n 'settings.gradle.kts',\n 'gradle.properties',\n 'gradle.lockfile',\n 'buildscript-gradle.lockfile',\n 'gradle',\n 'gradlew',\n 'gradlew.bat',\n ],\n maven: ['pom.xml', '.mvn', 'mvnw', 'mvnw.cmd'],\n npm: ['package.json', 'package-lock.json'],\n pnpm: ['package.json', 'pnpm-lock.yaml', 'pnpm-workspace.yaml'],\n ruby: ['Gemfile', 'Gemfile.lock', '.ruby-version'],\n uv: ['pyproject.toml', 'uv.lock'],\n yarn: ['package.json', 'yarn.lock', '.yarnrc', '.yarnrc.yml', '.yarn'],\n} as const satisfies Record<PackageManager, readonly string[]>;\n\nconst packageManagerInstallCommandResolvers = {\n bun: resolveBunInstallCommand,\n cargo: resolveCargoInstallCommand,\n go: resolveGoInstallCommand,\n gradle: resolveGradleInstallCommand,\n maven: resolveMavenInstallCommand,\n npm: resolveNpmInstallCommand,\n pnpm: resolvePnpmInstallCommand,\n ruby: resolveRubyInstallCommand,\n uv: resolveUvInstallCommand,\n yarn: resolveYarnInstallCommand,\n} as const satisfies Record<PackageManager, (runDir: string) => Promise<PackageManagerInstallCommand | undefined>>;\n\nconst defaultOutputLimitBytes = 50 * 1024 * 1024;\n\n/**\n * Copies a submission directory to a temporary directory, overlays package\n * manager project files from the problem directory, prepares dependencies,\n * runs a command, and then removes the temporary directory.\n */\nexport async function runCommandInTemporaryPackageManagerProject(\n options: RunCommandInTemporaryPackageManagerProjectOptions\n): Promise<PackageManagerCommandRunResult> {\n const runDir = await fs.mkdtemp(path.join(os.tmpdir(), options.tempDirPrefix ?? 'exercode-'));\n try {\n await fs.cp(options.cwd, runDir, { recursive: true });\n await copyPackageManagerProjectFiles({\n packageManager: options.packageManager,\n projectDir: options.projectDir,\n runDir,\n projectFilePaths: options.projectFilePaths,\n });\n\n const env = options.env ? { ...process.env, ...options.env } : process.env;\n const installCommand =\n options.prepareDependencies === false ? undefined : await resolveInstallCommand(options.packageManager, runDir);\n const command = typeof options.command === 'function' ? options.command({ runDir }) : options.command;\n const outputLimitBytes = options.outputLimitBytes ?? defaultOutputLimitBytes;\n let installResult: SpawnWithLimitsResult | undefined;\n\n if (installCommand) {\n installResult = await spawnWithLimits(installCommand, {\n cwd: runDir,\n env,\n outputLimitBytes,\n stdin: '',\n timeLimitSeconds: options.timeLimitSeconds,\n });\n if (isFailedSpawnResult(installResult)) {\n return toPackageManagerCommandRunResult({\n options,\n result: installResult,\n });\n }\n }\n\n // The install's measured time excludes the grace spent releasing pipes after it exited.\n const remainingTimeLimitSeconds = options.timeLimitSeconds - (installResult?.timeSeconds ?? 0);\n if (remainingTimeLimitSeconds <= 0) {\n return {\n stdin: options.stdin ?? '',\n stdout: installResult?.stdout ?? '',\n stderr: installResult?.stderr ?? '',\n status: 0,\n timeSeconds: options.timeLimitSeconds + 1e-3,\n cpuTimeSeconds: installResult?.cpuTimeSeconds ?? 0,\n memoryBytes: installResult?.memoryBytes ?? 0,\n timedOut: true,\n signal: installResult?.signal,\n outputLimitExceeded: false,\n };\n }\n\n const result = await spawnWithLimits(command, {\n cwd: runDir,\n env,\n outputLimitBytes,\n stdin: options.stdin ?? '',\n timeLimitSeconds: remainingTimeLimitSeconds,\n });\n\n if (installResult) {\n return toPackageManagerCommandRunResult({\n options,\n result: {\n ...result,\n timeSeconds: installResult.timeSeconds + result.timeSeconds,\n cpuTimeSeconds: installResult.cpuTimeSeconds + result.cpuTimeSeconds,\n memoryBytes: Math.max(installResult.memoryBytes, result.memoryBytes),\n },\n });\n }\n\n return toPackageManagerCommandRunResult({ options, result });\n } finally {\n // The command may have left permission-locked entries (e.g. a mode-000 directory).\n await forciblyRemoveDirectory(runDir);\n }\n}\n\nfunction toPackageManagerCommandRunResult(context: {\n options: RunCommandInTemporaryPackageManagerProjectOptions;\n result: SpawnWithLimitsResult;\n}): PackageManagerCommandRunResult {\n return {\n stdin: context.options.stdin ?? '',\n stdout: context.result.stdout,\n stderr: context.result.stderr,\n status: context.result.timedOut || context.result.outputLimitExceeded ? 0 : context.result.status,\n timeSeconds: context.result.timedOut ? context.options.timeLimitSeconds + 1e-3 : context.result.timeSeconds,\n cpuTimeSeconds: context.result.cpuTimeSeconds,\n memoryBytes: context.result.memoryBytes,\n timedOut: context.result.timedOut,\n signal: context.result.signal,\n outputLimitExceeded: context.result.outputLimitExceeded,\n };\n}\n\nfunction resolveInstallCommand(\n packageManager: PackageManager,\n runDir: string\n): Promise<PackageManagerInstallCommand | undefined> {\n return packageManagerInstallCommandResolvers[packageManager](runDir);\n}\n\nfunction isFailedSpawnResult(result: SpawnWithLimitsResult): boolean {\n return result.status !== 0 || result.timedOut || result.outputLimitExceeded;\n}\n\nasync function resolveBunInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'package.json')))) return undefined;\n // Bun supports --silent and it keeps successful preparation output out of judge output buffers.\n return (await hasAnyPath(runDir, ['bun.lock', 'bun.lockb']))\n ? ['bun', 'install', '--frozen-lockfile', '--silent']\n : ['bun', 'install', '--silent'];\n}\n\nasync function resolveCargoInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'Cargo.toml')))) return undefined;\n return (await pathExists(path.join(runDir, 'Cargo.lock'))) ? ['cargo', 'fetch', '--locked'] : ['cargo', 'fetch'];\n}\n\nasync function resolveGoInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'go.mod')))) return undefined;\n return ['go', 'mod', 'download'];\n}\n\nasync function resolveGradleInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (\n !(await hasAnyPath(runDir, [\n 'build.gradle',\n 'build.gradle.kts',\n 'settings.gradle',\n 'settings.gradle.kts',\n 'gradlew',\n 'gradlew.bat',\n ]))\n )\n return undefined;\n const args = ['--no-daemon', '--quiet', 'dependencies'] as const;\n if (process.platform === 'win32') {\n return (await pathExists(path.join(runDir, 'gradlew.bat')))\n ? ['cmd.exe', '/c', 'gradlew.bat', ...args]\n : ['gradle', ...args];\n }\n return (await pathExists(path.join(runDir, 'gradlew'))) ? ['sh', './gradlew', ...args] : ['gradle', ...args];\n}\n\nasync function resolveMavenInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'pom.xml')))) return undefined;\n const args = ['-q', 'dependency:go-offline'] as const;\n if (process.platform === 'win32') {\n return (await pathExists(path.join(runDir, 'mvnw.cmd')))\n ? ['cmd.exe', '/c', 'mvnw.cmd', ...args]\n : ['mvn', ...args];\n }\n return (await pathExists(path.join(runDir, 'mvnw'))) ? ['sh', './mvnw', ...args] : ['mvn', ...args];\n}\n\nasync function resolveNpmInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'package.json')))) return undefined;\n return (await pathExists(path.join(runDir, 'package-lock.json')))\n ? ['npm', 'ci', '--silent']\n : ['npm', 'install', '--silent'];\n}\n\nasync function resolvePnpmInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'package.json')))) return undefined;\n return (await pathExists(path.join(runDir, 'pnpm-lock.yaml')))\n ? ['pnpm', 'install', '--frozen-lockfile', '--silent']\n : ['pnpm', 'install', '--silent'];\n}\n\nasync function resolveRubyInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'Gemfile')))) return undefined;\n return (await pathExists(path.join(runDir, 'Gemfile.lock')))\n ? ['bundle', 'install', '--frozen', '--quiet']\n : ['bundle', 'install', '--quiet'];\n}\n\nasync function resolveUvInstallCommand(): Promise<undefined> {\n return undefined;\n}\n\nasync function resolveYarnInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'package.json')))) return undefined;\n const isBerry = await isYarnBerryProject(runDir);\n const hasLockfile = await pathExists(path.join(runDir, 'yarn.lock'));\n if (isBerry) return hasLockfile ? ['yarn', 'install', '--immutable'] : ['yarn', 'install'];\n return hasLockfile ? ['yarn', 'install', '--frozen-lockfile', '--silent'] : ['yarn', 'install', '--silent'];\n}\n\nasync function isYarnBerryProject(runDir: string): Promise<boolean> {\n if (await pathExists(path.join(runDir, '.yarnrc.yml'))) return true;\n\n const packageJson = await readJson(path.join(runDir, 'package.json'));\n const packageManager = typeof packageJson.packageManager === 'string' ? packageJson.packageManager : undefined;\n const yarnMajorVersion = /^yarn@(\\d+)/.exec(packageManager ?? '')?.[1];\n return yarnMajorVersion !== undefined && Number(yarnMajorVersion) >= 2;\n}\n\nasync function hasAnyPath(directoryPath: string, relativePaths: readonly string[]): Promise<boolean> {\n for (const relativePath of relativePaths) {\n if (await pathExists(path.join(directoryPath, relativePath))) return true;\n }\n return false;\n}\n\nasync function pathExists(filePath: string): Promise<boolean> {\n try {\n await fs.access(filePath);\n return true;\n } catch (error) {\n if (!hasErrorCode(error, 'ENOENT')) throw error;\n return false;\n }\n}\n\nasync function readJson(filePath: string): Promise<Record<string, unknown>> {\n try {\n const parsed = JSON.parse(await fs.readFile(filePath, 'utf8')) as unknown;\n return isRecord(parsed) ? parsed : {};\n } catch (error) {\n if (error instanceof SyntaxError) return {};\n if (hasErrorCode(error, 'ENOENT')) return {};\n throw error;\n }\n}\n\nexport async function copyPackageManagerProjectFiles(options: {\n packageManager: PackageManager;\n projectDir: string;\n runDir: string;\n projectFilePaths?: readonly string[];\n}): Promise<void> {\n for (const projectFilePath of options.projectFilePaths ?? packageManagerProjectFilePaths[options.packageManager]) {\n await copyPathIfExists(path.join(options.projectDir, projectFilePath), path.join(options.runDir, projectFilePath));\n }\n}\n\nasync function copyPathIfExists(sourcePath: string, destinationPath: string): Promise<void> {\n try {\n // Before touching the destination: creating the parent levels must not happen for a project\n // file the problem does not even ship.\n await fs.lstat(sourcePath);\n await fs.mkdir(path.dirname(destinationPath), { recursive: true });\n await fs.cp(sourcePath, destinationPath, { force: true, recursive: true });\n } catch (error) {\n if (!hasErrorCode(error, 'ENOENT')) throw error;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"runCommandInTemporaryPackageManagerProject.cjs","names":["fs","path","os","spawnWithLimits","forciblyRemoveDirectory","hasErrorCode","isRecord","require_runtime","require_spawnWithLimits","require_temporaryProblemDirCopy","node_path","node_fs_promises","node_os","_willbooster_shared_lib","packageManagerProjectFilePaths","packageManagerInstallCommandResolvers","resolveBunInstallCommand","resolveCargoInstallCommand","resolveGoInstallCommand","resolveGradleInstallCommand","resolveMavenInstallCommand","resolveNpmInstallCommand","resolvePnpmInstallCommand","resolveRubyInstallCommand","resolveUvInstallCommand","resolveYarnInstallCommand","runCommandInTemporaryPackageManagerProject","runDir","options","copyPackageManagerProjectFiles","env","installCommand","resolveInstallCommand","command","outputLimitBytes","installResult","isFailedSpawnResult","toPackageManagerCommandRunResult","remainingTimeLimitSeconds","result","context","packageManager","pathExists","hasAnyPath","args","isBerry","isYarnBerryProject","hasLockfile","packageJson","readJson","yarnMajorVersion","relativePath","relativePaths","directoryPath","filePath","error","parsed","projectFilePath","copyPathIfExists","sourcePath","destinationPath"],"sources":["../../src/helpers/runCommandInTemporaryPackageManagerProject.ts"],"sourcesContent":["import fs from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport { hasErrorCode, isRecord } from '@willbooster/shared-lib';\n\nimport { spawnWithLimits, type SpawnWithLimitsResult } from './spawnWithLimits.js';\nimport { forciblyRemoveDirectory } from './temporaryProblemDirCopy.js';\n\nexport type PackageManager = 'bun' | 'cargo' | 'go' | 'gradle' | 'maven' | 'npm' | 'pnpm' | 'ruby' | 'uv' | 'yarn';\ntype PackageManagerInstallCommand = readonly [string, ...string[]];\n\nexport interface PackageManagerCommandRunResult {\n stdin: string;\n stdout: string;\n stderr: string;\n status: number | undefined;\n timeSeconds: number;\n /** See `SpawnWithLimitsResult.cpuTimeSeconds`; the install's CPU time counts too. */\n cpuTimeSeconds: number;\n memoryBytes: number;\n timedOut: boolean;\n signal: NodeJS.Signals | undefined;\n outputLimitExceeded: boolean;\n}\n\nexport interface RunCommandInTemporaryPackageManagerProjectOptions {\n cwd: string;\n projectDir: string;\n packageManager: PackageManager;\n command: readonly [string, ...string[]] | ((context: { runDir: string }) => readonly [string, ...string[]]);\n /**\n * Set to false when `command` prepares the dependencies it needs.\n * Defaults to true.\n */\n prepareDependencies?: boolean;\n stdin?: string;\n env?: NodeJS.ProcessEnv;\n timeLimitSeconds: number;\n outputLimitBytes?: number;\n tempDirPrefix?: string;\n projectFilePaths?: readonly string[];\n}\n\nconst packageManagerProjectFilePaths = {\n bun: ['package.json', 'bun.lock', 'bun.lockb'],\n cargo: ['Cargo.toml', 'Cargo.lock'],\n go: ['go.mod', 'go.sum'],\n gradle: [\n 'build.gradle',\n 'build.gradle.kts',\n 'settings.gradle',\n 'settings.gradle.kts',\n 'gradle.properties',\n 'gradle.lockfile',\n 'buildscript-gradle.lockfile',\n 'gradle',\n 'gradlew',\n 'gradlew.bat',\n ],\n maven: ['pom.xml', '.mvn', 'mvnw', 'mvnw.cmd'],\n npm: ['package.json', 'package-lock.json'],\n pnpm: ['package.json', 'pnpm-lock.yaml', 'pnpm-workspace.yaml'],\n ruby: ['Gemfile', 'Gemfile.lock', '.ruby-version'],\n uv: ['pyproject.toml', 'uv.lock'],\n yarn: ['package.json', 'yarn.lock', '.yarnrc', '.yarnrc.yml', '.yarn'],\n} as const satisfies Record<PackageManager, readonly string[]>;\n\nconst packageManagerInstallCommandResolvers = {\n bun: resolveBunInstallCommand,\n cargo: resolveCargoInstallCommand,\n go: resolveGoInstallCommand,\n gradle: resolveGradleInstallCommand,\n maven: resolveMavenInstallCommand,\n npm: resolveNpmInstallCommand,\n pnpm: resolvePnpmInstallCommand,\n ruby: resolveRubyInstallCommand,\n uv: resolveUvInstallCommand,\n yarn: resolveYarnInstallCommand,\n} as const satisfies Record<PackageManager, (runDir: string) => Promise<PackageManagerInstallCommand | undefined>>;\n\nconst defaultOutputLimitBytes = 50 * 1024 * 1024;\n\n/**\n * Copies a submission directory to a temporary directory, overlays package\n * manager project files from the problem directory, prepares dependencies,\n * runs a command, and then removes the temporary directory.\n */\nexport async function runCommandInTemporaryPackageManagerProject(\n options: RunCommandInTemporaryPackageManagerProjectOptions\n): Promise<PackageManagerCommandRunResult> {\n const runDir = await fs.mkdtemp(path.join(os.tmpdir(), options.tempDirPrefix ?? 'exercode-'));\n try {\n await fs.cp(options.cwd, runDir, { recursive: true });\n await copyPackageManagerProjectFiles({\n packageManager: options.packageManager,\n projectDir: options.projectDir,\n runDir,\n projectFilePaths: options.projectFilePaths,\n });\n\n const env = options.env ? { ...process.env, ...options.env } : process.env;\n const installCommand =\n options.prepareDependencies === false ? undefined : await resolveInstallCommand(options.packageManager, runDir);\n const command = typeof options.command === 'function' ? options.command({ runDir }) : options.command;\n const outputLimitBytes = options.outputLimitBytes ?? defaultOutputLimitBytes;\n let installResult: SpawnWithLimitsResult | undefined;\n\n if (installCommand) {\n installResult = await spawnWithLimits(installCommand, {\n cwd: runDir,\n env,\n outputLimitBytes,\n stdin: '',\n timeLimitSeconds: options.timeLimitSeconds,\n });\n if (isFailedSpawnResult(installResult)) {\n return toPackageManagerCommandRunResult({\n options,\n result: installResult,\n });\n }\n }\n\n // The install's measured time excludes the grace spent releasing pipes after it exited.\n const remainingTimeLimitSeconds = options.timeLimitSeconds - (installResult?.timeSeconds ?? 0);\n if (remainingTimeLimitSeconds <= 0) {\n return {\n stdin: options.stdin ?? '',\n stdout: installResult?.stdout ?? '',\n stderr: installResult?.stderr ?? '',\n status: 0,\n timeSeconds: options.timeLimitSeconds + 1e-3,\n cpuTimeSeconds: installResult?.cpuTimeSeconds ?? 0,\n memoryBytes: installResult?.memoryBytes ?? 0,\n timedOut: true,\n signal: installResult?.signal,\n outputLimitExceeded: false,\n };\n }\n\n const result = await spawnWithLimits(command, {\n cwd: runDir,\n env,\n outputLimitBytes,\n stdin: options.stdin ?? '',\n timeLimitSeconds: remainingTimeLimitSeconds,\n });\n\n if (installResult) {\n return toPackageManagerCommandRunResult({\n options,\n result: {\n ...result,\n timeSeconds: installResult.timeSeconds + result.timeSeconds,\n cpuTimeSeconds: installResult.cpuTimeSeconds + result.cpuTimeSeconds,\n memoryBytes: Math.max(installResult.memoryBytes, result.memoryBytes),\n },\n });\n }\n\n return toPackageManagerCommandRunResult({ options, result });\n } finally {\n // The command may have left permission-locked entries (e.g. a mode-000 directory).\n await forciblyRemoveDirectory(runDir);\n }\n}\n\nfunction toPackageManagerCommandRunResult(context: {\n options: RunCommandInTemporaryPackageManagerProjectOptions;\n result: SpawnWithLimitsResult;\n}): PackageManagerCommandRunResult {\n return {\n stdin: context.options.stdin ?? '',\n stdout: context.result.stdout,\n stderr: context.result.stderr,\n status: context.result.timedOut || context.result.outputLimitExceeded ? 0 : context.result.status,\n timeSeconds: context.result.timedOut ? context.options.timeLimitSeconds + 1e-3 : context.result.timeSeconds,\n cpuTimeSeconds: context.result.cpuTimeSeconds,\n memoryBytes: context.result.memoryBytes,\n timedOut: context.result.timedOut,\n signal: context.result.signal,\n outputLimitExceeded: context.result.outputLimitExceeded,\n };\n}\n\nfunction resolveInstallCommand(\n packageManager: PackageManager,\n runDir: string\n): Promise<PackageManagerInstallCommand | undefined> {\n return packageManagerInstallCommandResolvers[packageManager](runDir);\n}\n\nfunction isFailedSpawnResult(result: SpawnWithLimitsResult): boolean {\n return result.status !== 0 || result.timedOut || result.outputLimitExceeded;\n}\n\nasync function resolveBunInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'package.json')))) return undefined;\n // Bun supports --silent and it keeps successful preparation output out of judge output buffers.\n return (await hasAnyPath(runDir, ['bun.lock', 'bun.lockb']))\n ? ['bun', 'install', '--frozen-lockfile', '--silent']\n : ['bun', 'install', '--silent'];\n}\n\nasync function resolveCargoInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'Cargo.toml')))) return undefined;\n return (await pathExists(path.join(runDir, 'Cargo.lock'))) ? ['cargo', 'fetch', '--locked'] : ['cargo', 'fetch'];\n}\n\nasync function resolveGoInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'go.mod')))) return undefined;\n return ['go', 'mod', 'download'];\n}\n\nasync function resolveGradleInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (\n !(await hasAnyPath(runDir, [\n 'build.gradle',\n 'build.gradle.kts',\n 'settings.gradle',\n 'settings.gradle.kts',\n 'gradlew',\n 'gradlew.bat',\n ]))\n )\n return undefined;\n const args = ['--no-daemon', '--quiet', 'dependencies'] as const;\n if (process.platform === 'win32') {\n return (await pathExists(path.join(runDir, 'gradlew.bat')))\n ? ['cmd.exe', '/c', 'gradlew.bat', ...args]\n : ['gradle', ...args];\n }\n return (await pathExists(path.join(runDir, 'gradlew'))) ? ['sh', './gradlew', ...args] : ['gradle', ...args];\n}\n\nasync function resolveMavenInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'pom.xml')))) return undefined;\n const args = ['-q', 'dependency:go-offline'] as const;\n if (process.platform === 'win32') {\n return (await pathExists(path.join(runDir, 'mvnw.cmd')))\n ? ['cmd.exe', '/c', 'mvnw.cmd', ...args]\n : ['mvn', ...args];\n }\n return (await pathExists(path.join(runDir, 'mvnw'))) ? ['sh', './mvnw', ...args] : ['mvn', ...args];\n}\n\nasync function resolveNpmInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'package.json')))) return undefined;\n return (await pathExists(path.join(runDir, 'package-lock.json')))\n ? ['npm', 'ci', '--silent']\n : ['npm', 'install', '--silent'];\n}\n\nasync function resolvePnpmInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'package.json')))) return undefined;\n return (await pathExists(path.join(runDir, 'pnpm-lock.yaml')))\n ? ['pnpm', 'install', '--frozen-lockfile', '--silent']\n : ['pnpm', 'install', '--silent'];\n}\n\nasync function resolveRubyInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'Gemfile')))) return undefined;\n return (await pathExists(path.join(runDir, 'Gemfile.lock')))\n ? ['bundle', 'install', '--frozen', '--quiet']\n : ['bundle', 'install', '--quiet'];\n}\n\nasync function resolveUvInstallCommand(): Promise<undefined> {\n return undefined;\n}\n\nasync function resolveYarnInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'package.json')))) return undefined;\n const isBerry = await isYarnBerryProject(runDir);\n const hasLockfile = await pathExists(path.join(runDir, 'yarn.lock'));\n if (isBerry) return hasLockfile ? ['yarn', 'install', '--immutable'] : ['yarn', 'install'];\n return hasLockfile ? ['yarn', 'install', '--frozen-lockfile', '--silent'] : ['yarn', 'install', '--silent'];\n}\n\nasync function isYarnBerryProject(runDir: string): Promise<boolean> {\n if (await pathExists(path.join(runDir, '.yarnrc.yml'))) return true;\n\n const packageJson = await readJson(path.join(runDir, 'package.json'));\n const packageManager = typeof packageJson.packageManager === 'string' ? packageJson.packageManager : undefined;\n const yarnMajorVersion = /^yarn@(\\d+)/.exec(packageManager ?? '')?.[1];\n return yarnMajorVersion !== undefined && Number(yarnMajorVersion) >= 2;\n}\n\nasync function hasAnyPath(directoryPath: string, relativePaths: readonly string[]): Promise<boolean> {\n for (const relativePath of relativePaths) {\n if (await pathExists(path.join(directoryPath, relativePath))) return true;\n }\n return false;\n}\n\nasync function pathExists(filePath: string): Promise<boolean> {\n try {\n await fs.access(filePath);\n return true;\n } catch (error) {\n if (!hasErrorCode(error, 'ENOENT')) throw error;\n return false;\n }\n}\n\nasync function readJson(filePath: string): Promise<Record<string, unknown>> {\n try {\n const parsed = JSON.parse(await fs.readFile(filePath, 'utf8')) as unknown;\n return isRecord(parsed) ? parsed : {};\n } catch (error) {\n if (error instanceof SyntaxError) return {};\n if (hasErrorCode(error, 'ENOENT')) return {};\n throw error;\n }\n}\n\nexport async function copyPackageManagerProjectFiles(options: {\n packageManager: PackageManager;\n projectDir: string;\n runDir: string;\n projectFilePaths?: readonly string[];\n}): Promise<void> {\n for (const projectFilePath of options.projectFilePaths ?? packageManagerProjectFilePaths[options.packageManager]) {\n await copyPathIfExists(path.join(options.projectDir, projectFilePath), path.join(options.runDir, projectFilePath));\n }\n}\n\nasync function copyPathIfExists(sourcePath: string, destinationPath: string): Promise<void> {\n try {\n // Before touching the destination: creating the parent levels must not happen for a project\n // file the problem does not even ship.\n await fs.lstat(sourcePath);\n await fs.mkdir(path.dirname(destinationPath), { recursive: true });\n await fs.cp(sourcePath, destinationPath, { force: true, recursive: true });\n } catch (error) {\n if (!hasErrorCode(error, 'ENOENT')) throw error;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runCommandInTemporaryPackageManagerProject.js","names":["path","fs","os","packageManagerProjectFilePaths","packageManagerInstallCommandResolvers","resolveBunInstallCommand","resolveCargoInstallCommand","resolveGoInstallCommand","resolveGradleInstallCommand","resolveMavenInstallCommand","resolveNpmInstallCommand","resolvePnpmInstallCommand","resolveRubyInstallCommand","resolveUvInstallCommand","resolveYarnInstallCommand","runCommandInTemporaryPackageManagerProject","runDir","options","copyPackageManagerProjectFiles","env","installCommand","resolveInstallCommand","command","outputLimitBytes","installResult","spawnWithLimits","isFailedSpawnResult","toPackageManagerCommandRunResult","remainingTimeLimitSeconds","result","forciblyRemoveDirectory","context","packageManager","pathExists","hasAnyPath","args","isBerry","isYarnBerryProject","hasLockfile","packageJson","readJson","yarnMajorVersion","relativePath","relativePaths","directoryPath","filePath","error","hasErrorCode","parsed","isRecord","projectFilePath","copyPathIfExists","sourcePath","destinationPath"],"sources":["../../src/helpers/runCommandInTemporaryPackageManagerProject.ts"],"sourcesContent":["import fs from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { hasErrorCode, isRecord } from '@willbooster/shared-lib';\n\nimport { spawnWithLimits, type SpawnWithLimitsResult } from './spawnWithLimits.js';\nimport { forciblyRemoveDirectory } from './temporaryProblemDirCopy.js';\n\nexport type PackageManager = 'bun' | 'cargo' | 'go' | 'gradle' | 'maven' | 'npm' | 'pnpm' | 'ruby' | 'uv' | 'yarn';\ntype PackageManagerInstallCommand = readonly [string, ...string[]];\n\nexport interface PackageManagerCommandRunResult {\n stdin: string;\n stdout: string;\n stderr: string;\n status: number | undefined;\n timeSeconds: number;\n /** See `SpawnWithLimitsResult.cpuTimeSeconds`; the install's CPU time counts too. */\n cpuTimeSeconds: number;\n memoryBytes: number;\n timedOut: boolean;\n signal: NodeJS.Signals | undefined;\n outputLimitExceeded: boolean;\n}\n\nexport interface RunCommandInTemporaryPackageManagerProjectOptions {\n cwd: string;\n projectDir: string;\n packageManager: PackageManager;\n command: readonly [string, ...string[]] | ((context: { runDir: string }) => readonly [string, ...string[]]);\n /**\n * Set to false when `command` prepares the dependencies it needs.\n * Defaults to true.\n */\n prepareDependencies?: boolean;\n stdin?: string;\n env?: NodeJS.ProcessEnv;\n timeLimitSeconds: number;\n outputLimitBytes?: number;\n tempDirPrefix?: string;\n projectFilePaths?: readonly string[];\n}\n\nconst packageManagerProjectFilePaths = {\n bun: ['package.json', 'bun.lock', 'bun.lockb'],\n cargo: ['Cargo.toml', 'Cargo.lock'],\n go: ['go.mod', 'go.sum'],\n gradle: [\n 'build.gradle',\n 'build.gradle.kts',\n 'settings.gradle',\n 'settings.gradle.kts',\n 'gradle.properties',\n 'gradle.lockfile',\n 'buildscript-gradle.lockfile',\n 'gradle',\n 'gradlew',\n 'gradlew.bat',\n ],\n maven: ['pom.xml', '.mvn', 'mvnw', 'mvnw.cmd'],\n npm: ['package.json', 'package-lock.json'],\n pnpm: ['package.json', 'pnpm-lock.yaml', 'pnpm-workspace.yaml'],\n ruby: ['Gemfile', 'Gemfile.lock', '.ruby-version'],\n uv: ['pyproject.toml', 'uv.lock'],\n yarn: ['package.json', 'yarn.lock', '.yarnrc', '.yarnrc.yml', '.yarn'],\n} as const satisfies Record<PackageManager, readonly string[]>;\n\nconst packageManagerInstallCommandResolvers = {\n bun: resolveBunInstallCommand,\n cargo: resolveCargoInstallCommand,\n go: resolveGoInstallCommand,\n gradle: resolveGradleInstallCommand,\n maven: resolveMavenInstallCommand,\n npm: resolveNpmInstallCommand,\n pnpm: resolvePnpmInstallCommand,\n ruby: resolveRubyInstallCommand,\n uv: resolveUvInstallCommand,\n yarn: resolveYarnInstallCommand,\n} as const satisfies Record<PackageManager, (runDir: string) => Promise<PackageManagerInstallCommand | undefined>>;\n\nconst defaultOutputLimitBytes = 50 * 1024 * 1024;\n\n/**\n * Copies a submission directory to a temporary directory, overlays package\n * manager project files from the problem directory, prepares dependencies,\n * runs a command, and then removes the temporary directory.\n */\nexport async function runCommandInTemporaryPackageManagerProject(\n options: RunCommandInTemporaryPackageManagerProjectOptions\n): Promise<PackageManagerCommandRunResult> {\n const runDir = await fs.mkdtemp(path.join(os.tmpdir(), options.tempDirPrefix ?? 'exercode-'));\n try {\n await fs.cp(options.cwd, runDir, { recursive: true });\n await copyPackageManagerProjectFiles({\n packageManager: options.packageManager,\n projectDir: options.projectDir,\n runDir,\n projectFilePaths: options.projectFilePaths,\n });\n\n const env = options.env ? { ...process.env, ...options.env } : process.env;\n const installCommand =\n options.prepareDependencies === false ? undefined : await resolveInstallCommand(options.packageManager, runDir);\n const command = typeof options.command === 'function' ? options.command({ runDir }) : options.command;\n const outputLimitBytes = options.outputLimitBytes ?? defaultOutputLimitBytes;\n let installResult: SpawnWithLimitsResult | undefined;\n\n if (installCommand) {\n installResult = await spawnWithLimits(installCommand, {\n cwd: runDir,\n env,\n outputLimitBytes,\n stdin: '',\n timeLimitSeconds: options.timeLimitSeconds,\n });\n if (isFailedSpawnResult(installResult)) {\n return toPackageManagerCommandRunResult({\n options,\n result: installResult,\n });\n }\n }\n\n // The install's measured time excludes the grace spent releasing pipes after it exited.\n const remainingTimeLimitSeconds = options.timeLimitSeconds - (installResult?.timeSeconds ?? 0);\n if (remainingTimeLimitSeconds <= 0) {\n return {\n stdin: options.stdin ?? '',\n stdout: installResult?.stdout ?? '',\n stderr: installResult?.stderr ?? '',\n status: 0,\n timeSeconds: options.timeLimitSeconds + 1e-3,\n cpuTimeSeconds: installResult?.cpuTimeSeconds ?? 0,\n memoryBytes: installResult?.memoryBytes ?? 0,\n timedOut: true,\n signal: installResult?.signal,\n outputLimitExceeded: false,\n };\n }\n\n const result = await spawnWithLimits(command, {\n cwd: runDir,\n env,\n outputLimitBytes,\n stdin: options.stdin ?? '',\n timeLimitSeconds: remainingTimeLimitSeconds,\n });\n\n if (installResult) {\n return toPackageManagerCommandRunResult({\n options,\n result: {\n ...result,\n timeSeconds: installResult.timeSeconds + result.timeSeconds,\n cpuTimeSeconds: installResult.cpuTimeSeconds + result.cpuTimeSeconds,\n memoryBytes: Math.max(installResult.memoryBytes, result.memoryBytes),\n },\n });\n }\n\n return toPackageManagerCommandRunResult({ options, result });\n } finally {\n // The command may have left permission-locked entries (e.g. a mode-000 directory).\n await forciblyRemoveDirectory(runDir);\n }\n}\n\nfunction toPackageManagerCommandRunResult(context: {\n options: RunCommandInTemporaryPackageManagerProjectOptions;\n result: SpawnWithLimitsResult;\n}): PackageManagerCommandRunResult {\n return {\n stdin: context.options.stdin ?? '',\n stdout: context.result.stdout,\n stderr: context.result.stderr,\n status: context.result.timedOut || context.result.outputLimitExceeded ? 0 : context.result.status,\n timeSeconds: context.result.timedOut ? context.options.timeLimitSeconds + 1e-3 : context.result.timeSeconds,\n cpuTimeSeconds: context.result.cpuTimeSeconds,\n memoryBytes: context.result.memoryBytes,\n timedOut: context.result.timedOut,\n signal: context.result.signal,\n outputLimitExceeded: context.result.outputLimitExceeded,\n };\n}\n\nfunction resolveInstallCommand(\n packageManager: PackageManager,\n runDir: string\n): Promise<PackageManagerInstallCommand | undefined> {\n return packageManagerInstallCommandResolvers[packageManager](runDir);\n}\n\nfunction isFailedSpawnResult(result: SpawnWithLimitsResult): boolean {\n return result.status !== 0 || result.timedOut || result.outputLimitExceeded;\n}\n\nasync function resolveBunInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'package.json')))) return undefined;\n // Bun supports --silent and it keeps successful preparation output out of judge output buffers.\n return (await hasAnyPath(runDir, ['bun.lock', 'bun.lockb']))\n ? ['bun', 'install', '--frozen-lockfile', '--silent']\n : ['bun', 'install', '--silent'];\n}\n\nasync function resolveCargoInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'Cargo.toml')))) return undefined;\n return (await pathExists(path.join(runDir, 'Cargo.lock'))) ? ['cargo', 'fetch', '--locked'] : ['cargo', 'fetch'];\n}\n\nasync function resolveGoInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'go.mod')))) return undefined;\n return ['go', 'mod', 'download'];\n}\n\nasync function resolveGradleInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (\n !(await hasAnyPath(runDir, [\n 'build.gradle',\n 'build.gradle.kts',\n 'settings.gradle',\n 'settings.gradle.kts',\n 'gradlew',\n 'gradlew.bat',\n ]))\n )\n return undefined;\n const args = ['--no-daemon', '--quiet', 'dependencies'] as const;\n if (process.platform === 'win32') {\n return (await pathExists(path.join(runDir, 'gradlew.bat')))\n ? ['cmd.exe', '/c', 'gradlew.bat', ...args]\n : ['gradle', ...args];\n }\n return (await pathExists(path.join(runDir, 'gradlew'))) ? ['sh', './gradlew', ...args] : ['gradle', ...args];\n}\n\nasync function resolveMavenInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'pom.xml')))) return undefined;\n const args = ['-q', 'dependency:go-offline'] as const;\n if (process.platform === 'win32') {\n return (await pathExists(path.join(runDir, 'mvnw.cmd')))\n ? ['cmd.exe', '/c', 'mvnw.cmd', ...args]\n : ['mvn', ...args];\n }\n return (await pathExists(path.join(runDir, 'mvnw'))) ? ['sh', './mvnw', ...args] : ['mvn', ...args];\n}\n\nasync function resolveNpmInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'package.json')))) return undefined;\n return (await pathExists(path.join(runDir, 'package-lock.json')))\n ? ['npm', 'ci', '--silent']\n : ['npm', 'install', '--silent'];\n}\n\nasync function resolvePnpmInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'package.json')))) return undefined;\n return (await pathExists(path.join(runDir, 'pnpm-lock.yaml')))\n ? ['pnpm', 'install', '--frozen-lockfile', '--silent']\n : ['pnpm', 'install', '--silent'];\n}\n\nasync function resolveRubyInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'Gemfile')))) return undefined;\n return (await pathExists(path.join(runDir, 'Gemfile.lock')))\n ? ['bundle', 'install', '--frozen', '--quiet']\n : ['bundle', 'install', '--quiet'];\n}\n\nasync function resolveUvInstallCommand(): Promise<undefined> {\n return undefined;\n}\n\nasync function resolveYarnInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'package.json')))) return undefined;\n const isBerry = await isYarnBerryProject(runDir);\n const hasLockfile = await pathExists(path.join(runDir, 'yarn.lock'));\n if (isBerry) return hasLockfile ? ['yarn', 'install', '--immutable'] : ['yarn', 'install'];\n return hasLockfile ? ['yarn', 'install', '--frozen-lockfile', '--silent'] : ['yarn', 'install', '--silent'];\n}\n\nasync function isYarnBerryProject(runDir: string): Promise<boolean> {\n if (await pathExists(path.join(runDir, '.yarnrc.yml'))) return true;\n\n const packageJson = await readJson(path.join(runDir, 'package.json'));\n const packageManager = typeof packageJson.packageManager === 'string' ? packageJson.packageManager : undefined;\n const yarnMajorVersion = /^yarn@(\\d+)/.exec(packageManager ?? '')?.[1];\n return yarnMajorVersion !== undefined && Number(yarnMajorVersion) >= 2;\n}\n\nasync function hasAnyPath(directoryPath: string, relativePaths: readonly string[]): Promise<boolean> {\n for (const relativePath of relativePaths) {\n if (await pathExists(path.join(directoryPath, relativePath))) return true;\n }\n return false;\n}\n\nasync function pathExists(filePath: string): Promise<boolean> {\n try {\n await fs.access(filePath);\n return true;\n } catch (error) {\n if (!hasErrorCode(error, 'ENOENT')) throw error;\n return false;\n }\n}\n\nasync function readJson(filePath: string): Promise<Record<string, unknown>> {\n try {\n const parsed = JSON.parse(await fs.readFile(filePath, 'utf8')) as unknown;\n return isRecord(parsed) ? parsed : {};\n } catch (error) {\n if (error instanceof SyntaxError) return {};\n if (hasErrorCode(error, 'ENOENT')) return {};\n throw error;\n }\n}\n\nexport async function copyPackageManagerProjectFiles(options: {\n packageManager: PackageManager;\n projectDir: string;\n runDir: string;\n projectFilePaths?: readonly string[];\n}): Promise<void> {\n for (const projectFilePath of options.projectFilePaths ?? packageManagerProjectFilePaths[options.packageManager]) {\n await copyPathIfExists(path.join(options.projectDir, projectFilePath), path.join(options.runDir, projectFilePath));\n }\n}\n\nasync function copyPathIfExists(sourcePath: string, destinationPath: string): Promise<void> {\n try {\n // Before touching the destination: creating the parent levels must not happen for a project\n // file the problem does not even ship.\n await fs.lstat(sourcePath);\n await fs.mkdir(path.dirname(destinationPath), { recursive: true });\n await fs.cp(sourcePath, destinationPath, { force: true, recursive: true });\n } catch (error) {\n if (!hasErrorCode(error, 'ENOENT')) throw error;\n }\n}\n"],"mappings":"mRA2CA,MAAMG,EAAiC,CACrC,IAAK,CAAC,eAAgB,WAAY,WAAW,EAC7C,MAAO,CAAC,aAAc,YAAY,EAClC,GAAI,CAAC,SAAU,QAAQ,EACvB,OAAQ,CACN,eACA,mBACA,kBACA,sBACA,oBACA,kBACA,8BACA,SACA,UACA,aACF,EACA,MAAO,CAAC,UAAW,OAAQ,OAAQ,UAAU,EAC7C,IAAK,CAAC,eAAgB,mBAAmB,EACzC,KAAM,CAAC,eAAgB,iBAAkB,qBAAqB,EAC9D,KAAM,CAAC,UAAW,eAAgB,eAAe,EACjD,GAAI,CAAC,iBAAkB,SAAS,EAChC,KAAM,CAAC,eAAgB,YAAa,UAAW,cAAe,OAAO,CACvE,EAEMC,EAAwC,CAC5C,IAAKC,EACL,MAAOC,EACP,GAAIC,EACJ,OAAQC,EACR,MAAOC,EACP,IAAKC,EACL,KAAMC,EACN,KAAMC,EACN,GAAIC,EACJ,KAAMC,CACR,EASA,eAAsBC,EACpB,EACyC,CACzC,IAAMC,EAAS,MAAMf,EAAG,QAAQD,EAAK,KAAKE,EAAG,OAAO,EAAGe,EAAQ,eAAiB,WAAW,CAAC,EAC5F,GAAI,CACF,MAAMhB,EAAG,GAAGgB,EAAQ,IAAKD,EAAQ,CAAE,UAAW,EAAK,CAAC,EACpD,MAAME,EAA+B,CACnC,eAAgBD,EAAQ,eACxB,WAAYA,EAAQ,WACpB,SACA,iBAAkBA,EAAQ,gBAC5B,CAAC,EAED,IAAME,EAAMF,EAAQ,IAAM,CAAE,GAAG,QAAQ,IAAK,GAAGA,EAAQ,GAAI,EAAI,QAAQ,IACjEG,EACJH,EAAQ,sBAAwB,GAAQ,IAAA,GAAY,MAAMI,EAAsBJ,EAAQ,eAAgBD,CAAM,EAC1GM,EAAU,OAAOL,EAAQ,SAAY,WAAaA,EAAQ,QAAQ,CAAE,QAAO,CAAC,EAAIA,EAAQ,QACxFM,EAAmBN,EAAQ,kBAAoB,SACjDO,EAEJ,GAAIJ,IACF,EAAgB,MAAMK,EAAgBL,EAAgB,CACpD,IAAKJ,EACL,MACA,mBACA,MAAO,GACP,iBAAkBC,EAAQ,gBAC5B,CAAC,EACGS,EAAoBF,CAAa,GACnC,OAAOG,EAAiC,CACtC,UACA,OAAQH,CACV,CAAC,EAKL,IAAMI,EAA4BX,EAAQ,kBAAoBO,GAAe,aAAe,GAC5F,GAAII,GAA6B,EAC/B,MAAO,CACL,MAAOX,EAAQ,OAAS,GACxB,OAAQO,GAAe,QAAU,GACjC,OAAQA,GAAe,QAAU,GACjC,OAAQ,EACR,YAAaP,EAAQ,iBAAmB,KACxC,eAAgBO,GAAe,gBAAkB,EACjD,YAAaA,GAAe,aAAe,EAC3C,SAAU,GACV,OAAQA,GAAe,OACvB,oBAAqB,EACvB,EAGF,IAAMK,EAAS,MAAMJ,EAAgBH,EAAS,CAC5C,IAAKN,EACL,MACA,mBACA,MAAOC,EAAQ,OAAS,GACxB,iBAAkBW,CACpB,CAAC,EAcD,OAXSD,EADLH,EACsC,CACtC,UACA,OAAQ,CACN,GAAGK,EACH,YAAaL,EAAc,YAAcK,EAAO,YAChD,eAAgBL,EAAc,eAAiBK,EAAO,eACtD,YAAa,KAAK,IAAIL,EAAc,YAAaK,EAAO,WAAW,CACrE,CACF,EAGsC,CAAE,UAAS,QAAO,CAHvD,CAIL,QAAU,CAER,MAAMC,EAAwBd,CAAM,CACtC,CACF,CAEA,SAASW,EAAiC,EAGP,CACjC,MAAO,CACL,MAAOI,EAAQ,QAAQ,OAAS,GAChC,OAAQA,EAAQ,OAAO,OACvB,OAAQA,EAAQ,OAAO,OACvB,OAAQA,EAAQ,OAAO,UAAYA,EAAQ,OAAO,oBAAsB,EAAIA,EAAQ,OAAO,OAC3F,YAAaA,EAAQ,OAAO,SAAWA,EAAQ,QAAQ,iBAAmB,KAAOA,EAAQ,OAAO,YAChG,eAAgBA,EAAQ,OAAO,eAC/B,YAAaA,EAAQ,OAAO,YAC5B,SAAUA,EAAQ,OAAO,SACzB,OAAQA,EAAQ,OAAO,OACvB,oBAAqBA,EAAQ,OAAO,mBACtC,CACF,CAEA,SAASV,EACP,EACA,EACmD,CACnD,OAAOjB,EAAsC4B,EAAe,CAAChB,CAAM,CACrE,CAEA,SAASU,EAAoB,EAAwC,CACnE,OAAOG,EAAO,SAAW,GAAKA,EAAO,UAAYA,EAAO,mBAC1D,CAEA,eAAexB,EAAyB,EAAmE,CACzG,GAAM,MAAM4B,EAAWjC,EAAK,KAAKgB,EAAQ,cAAc,CAAC,EAExD,OAAQ,MAAMkB,EAAWlB,EAAQ,CAAC,WAAY,WAAW,CAAC,EACtD,CAAC,MAAO,UAAW,oBAAqB,UAAU,EAClD,CAAC,MAAO,UAAW,UAAU,CACnC,CAEA,eAAeV,EAA2B,EAAmE,CAC3G,GAAM,MAAM2B,EAAWjC,EAAK,KAAKgB,EAAQ,YAAY,CAAC,EACtD,OAAQ,MAAMiB,EAAWjC,EAAK,KAAKgB,EAAQ,YAAY,CAAC,EAAK,CAAC,QAAS,QAAS,UAAU,EAAI,CAAC,QAAS,OAAO,CACjH,CAEA,eAAeT,EAAwB,EAAmE,CACxG,GAAM,MAAM0B,EAAWjC,EAAK,KAAKgB,EAAQ,QAAQ,CAAC,EAClD,MAAO,CAAC,KAAM,MAAO,UAAU,CACjC,CAEA,eAAeR,EAA4B,EAAmE,CAC5G,GACE,CAAE,MAAM0B,EAAWlB,EAAQ,CACzB,eACA,mBACA,kBACA,sBACA,UACA,aACF,CAAC,EAED,OACF,IAAMmB,EAAO,CAAC,cAAe,UAAW,cAAc,EAMtD,OALI,QAAQ,WAAa,QACf,MAAMF,EAAWjC,EAAK,KAAKgB,EAAQ,aAAa,CAAC,EACrD,CAAC,UAAW,KAAM,cAAe,GAAGmB,CAAI,EACxC,CAAC,SAAU,GAAGA,CAAI,EAEhB,MAAMF,EAAWjC,EAAK,KAAKgB,EAAQ,SAAS,CAAC,EAAK,CAAC,KAAM,YAAa,GAAGmB,CAAI,EAAI,CAAC,SAAU,GAAGA,CAAI,CAC7G,CAEA,eAAe1B,EAA2B,EAAmE,CAC3G,GAAI,CAAE,MAAMwB,EAAWjC,EAAK,KAAKgB,EAAQ,SAAS,CAAC,EAAI,OACvD,IAAMmB,EAAO,CAAC,KAAM,uBAAuB,EAM3C,OALI,QAAQ,WAAa,QACf,MAAMF,EAAWjC,EAAK,KAAKgB,EAAQ,UAAU,CAAC,EAClD,CAAC,UAAW,KAAM,WAAY,GAAGmB,CAAI,EACrC,CAAC,MAAO,GAAGA,CAAI,EAEb,MAAMF,EAAWjC,EAAK,KAAKgB,EAAQ,MAAM,CAAC,EAAK,CAAC,KAAM,SAAU,GAAGmB,CAAI,EAAI,CAAC,MAAO,GAAGA,CAAI,CACpG,CAEA,eAAezB,EAAyB,EAAmE,CACzG,GAAM,MAAMuB,EAAWjC,EAAK,KAAKgB,EAAQ,cAAc,CAAC,EACxD,OAAQ,MAAMiB,EAAWjC,EAAK,KAAKgB,EAAQ,mBAAmB,CAAC,EAC3D,CAAC,MAAO,KAAM,UAAU,EACxB,CAAC,MAAO,UAAW,UAAU,CACnC,CAEA,eAAeL,EAA0B,EAAmE,CAC1G,GAAM,MAAMsB,EAAWjC,EAAK,KAAKgB,EAAQ,cAAc,CAAC,EACxD,OAAQ,MAAMiB,EAAWjC,EAAK,KAAKgB,EAAQ,gBAAgB,CAAC,EACxD,CAAC,OAAQ,UAAW,oBAAqB,UAAU,EACnD,CAAC,OAAQ,UAAW,UAAU,CACpC,CAEA,eAAeJ,EAA0B,EAAmE,CAC1G,GAAM,MAAMqB,EAAWjC,EAAK,KAAKgB,EAAQ,SAAS,CAAC,EACnD,OAAQ,MAAMiB,EAAWjC,EAAK,KAAKgB,EAAQ,cAAc,CAAC,EACtD,CAAC,SAAU,UAAW,WAAY,SAAS,EAC3C,CAAC,SAAU,UAAW,SAAS,CACrC,CAEA,eAAeH,GAA8C,CAE7D,CAEA,eAAeC,EAA0B,EAAmE,CAC1G,GAAI,CAAE,MAAMmB,EAAWjC,EAAK,KAAKgB,EAAQ,cAAc,CAAC,EAAI,OAC5D,IAAMoB,EAAU,MAAMC,EAAmBrB,CAAM,EACzCsB,EAAc,MAAML,EAAWjC,EAAK,KAAKgB,EAAQ,WAAW,CAAC,EAEnE,OADIoB,EAAgBE,EAAc,CAAC,OAAQ,UAAW,aAAa,EAAI,CAAC,OAAQ,SAAS,EAClFA,EAAc,CAAC,OAAQ,UAAW,oBAAqB,UAAU,EAAI,CAAC,OAAQ,UAAW,UAAU,CAC5G,CAEA,eAAeD,EAAmB,EAAkC,CAClE,GAAI,MAAMJ,EAAWjC,EAAK,KAAKgB,EAAQ,aAAa,CAAC,EAAG,MAAO,GAE/D,IAAMuB,EAAc,MAAMC,EAASxC,EAAK,KAAKgB,EAAQ,cAAc,CAAC,EAC9DgB,EAAiB,OAAOO,EAAY,gBAAmB,SAAWA,EAAY,eAAiB,IAAA,GAC/FE,EAAmB,cAAc,KAAKT,GAAkB,EAAE,CAAC,GAAG,GACpE,OAAOS,IAAqB,IAAA,IAAa,OAAOA,CAAgB,GAAK,CACvE,CAEA,eAAeP,EAAW,EAAuB,EAAoD,CACnG,IAAK,IAAMQ,KAAgBC,EACzB,GAAI,MAAMV,EAAWjC,EAAK,KAAK4C,EAAeF,CAAY,CAAC,EAAG,MAAO,GAEvE,MAAO,EACT,CAEA,eAAeT,EAAW,EAAoC,CAC5D,GAAI,CAEF,OADA,MAAMhC,EAAG,OAAO4C,CAAQ,EACjB,EACT,OAASC,EAAO,CACd,GAAI,CAACC,EAAaD,EAAO,QAAQ,EAAG,MAAMA,EAC1C,MAAO,EACT,CACF,CAEA,eAAeN,EAAS,EAAoD,CAC1E,GAAI,CACF,IAAMQ,EAAS,KAAK,MAAM,MAAM/C,EAAG,SAAS4C,EAAU,MAAM,CAAC,EAC7D,OAAOI,EAASD,CAAM,EAAIA,EAAS,CAAC,CACtC,OAASF,EAAO,CAEd,GADIA,aAAiB,aACjBC,EAAaD,EAAO,QAAQ,EAAG,MAAO,CAAC,EAC3C,MAAMA,CACR,CACF,CAEA,eAAsB5B,EAA+B,EAKnC,CAChB,IAAK,IAAMgC,KAAmBjC,EAAQ,kBAAoBd,EAA+Bc,EAAQ,gBAC/F,MAAMkC,EAAiBnD,EAAK,KAAKiB,EAAQ,WAAYiC,CAAe,EAAGlD,EAAK,KAAKiB,EAAQ,OAAQiC,CAAe,CAAC,CAErH,CAEA,eAAeC,EAAiB,EAAoB,EAAwC,CAC1F,GAAI,CAGF,MAAMlD,EAAG,MAAMmD,CAAU,EACzB,MAAMnD,EAAG,MAAMD,EAAK,QAAQqD,CAAe,EAAG,CAAE,UAAW,EAAK,CAAC,EACjE,MAAMpD,EAAG,GAAGmD,EAAYC,EAAiB,CAAE,MAAO,GAAM,UAAW,EAAK,CAAC,CAC3E,OAASP,EAAO,CACd,GAAI,CAACC,EAAaD,EAAO,QAAQ,EAAG,MAAMA,CAC5C,CACF"}
|
|
1
|
+
{"version":3,"file":"runCommandInTemporaryPackageManagerProject.js","names":["path","fs","os","packageManagerProjectFilePaths","packageManagerInstallCommandResolvers","resolveBunInstallCommand","resolveCargoInstallCommand","resolveGoInstallCommand","resolveGradleInstallCommand","resolveMavenInstallCommand","resolveNpmInstallCommand","resolvePnpmInstallCommand","resolveRubyInstallCommand","resolveUvInstallCommand","resolveYarnInstallCommand","runCommandInTemporaryPackageManagerProject","runDir","options","copyPackageManagerProjectFiles","env","installCommand","resolveInstallCommand","command","outputLimitBytes","installResult","spawnWithLimits","isFailedSpawnResult","toPackageManagerCommandRunResult","remainingTimeLimitSeconds","result","forciblyRemoveDirectory","context","packageManager","pathExists","hasAnyPath","args","isBerry","isYarnBerryProject","hasLockfile","packageJson","readJson","yarnMajorVersion","relativePath","relativePaths","directoryPath","filePath","error","hasErrorCode","parsed","isRecord","projectFilePath","copyPathIfExists","sourcePath","destinationPath"],"sources":["../../src/helpers/runCommandInTemporaryPackageManagerProject.ts"],"sourcesContent":["import fs from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport { hasErrorCode, isRecord } from '@willbooster/shared-lib';\n\nimport { spawnWithLimits, type SpawnWithLimitsResult } from './spawnWithLimits.js';\nimport { forciblyRemoveDirectory } from './temporaryProblemDirCopy.js';\n\nexport type PackageManager = 'bun' | 'cargo' | 'go' | 'gradle' | 'maven' | 'npm' | 'pnpm' | 'ruby' | 'uv' | 'yarn';\ntype PackageManagerInstallCommand = readonly [string, ...string[]];\n\nexport interface PackageManagerCommandRunResult {\n stdin: string;\n stdout: string;\n stderr: string;\n status: number | undefined;\n timeSeconds: number;\n /** See `SpawnWithLimitsResult.cpuTimeSeconds`; the install's CPU time counts too. */\n cpuTimeSeconds: number;\n memoryBytes: number;\n timedOut: boolean;\n signal: NodeJS.Signals | undefined;\n outputLimitExceeded: boolean;\n}\n\nexport interface RunCommandInTemporaryPackageManagerProjectOptions {\n cwd: string;\n projectDir: string;\n packageManager: PackageManager;\n command: readonly [string, ...string[]] | ((context: { runDir: string }) => readonly [string, ...string[]]);\n /**\n * Set to false when `command` prepares the dependencies it needs.\n * Defaults to true.\n */\n prepareDependencies?: boolean;\n stdin?: string;\n env?: NodeJS.ProcessEnv;\n timeLimitSeconds: number;\n outputLimitBytes?: number;\n tempDirPrefix?: string;\n projectFilePaths?: readonly string[];\n}\n\nconst packageManagerProjectFilePaths = {\n bun: ['package.json', 'bun.lock', 'bun.lockb'],\n cargo: ['Cargo.toml', 'Cargo.lock'],\n go: ['go.mod', 'go.sum'],\n gradle: [\n 'build.gradle',\n 'build.gradle.kts',\n 'settings.gradle',\n 'settings.gradle.kts',\n 'gradle.properties',\n 'gradle.lockfile',\n 'buildscript-gradle.lockfile',\n 'gradle',\n 'gradlew',\n 'gradlew.bat',\n ],\n maven: ['pom.xml', '.mvn', 'mvnw', 'mvnw.cmd'],\n npm: ['package.json', 'package-lock.json'],\n pnpm: ['package.json', 'pnpm-lock.yaml', 'pnpm-workspace.yaml'],\n ruby: ['Gemfile', 'Gemfile.lock', '.ruby-version'],\n uv: ['pyproject.toml', 'uv.lock'],\n yarn: ['package.json', 'yarn.lock', '.yarnrc', '.yarnrc.yml', '.yarn'],\n} as const satisfies Record<PackageManager, readonly string[]>;\n\nconst packageManagerInstallCommandResolvers = {\n bun: resolveBunInstallCommand,\n cargo: resolveCargoInstallCommand,\n go: resolveGoInstallCommand,\n gradle: resolveGradleInstallCommand,\n maven: resolveMavenInstallCommand,\n npm: resolveNpmInstallCommand,\n pnpm: resolvePnpmInstallCommand,\n ruby: resolveRubyInstallCommand,\n uv: resolveUvInstallCommand,\n yarn: resolveYarnInstallCommand,\n} as const satisfies Record<PackageManager, (runDir: string) => Promise<PackageManagerInstallCommand | undefined>>;\n\nconst defaultOutputLimitBytes = 50 * 1024 * 1024;\n\n/**\n * Copies a submission directory to a temporary directory, overlays package\n * manager project files from the problem directory, prepares dependencies,\n * runs a command, and then removes the temporary directory.\n */\nexport async function runCommandInTemporaryPackageManagerProject(\n options: RunCommandInTemporaryPackageManagerProjectOptions\n): Promise<PackageManagerCommandRunResult> {\n const runDir = await fs.mkdtemp(path.join(os.tmpdir(), options.tempDirPrefix ?? 'exercode-'));\n try {\n await fs.cp(options.cwd, runDir, { recursive: true });\n await copyPackageManagerProjectFiles({\n packageManager: options.packageManager,\n projectDir: options.projectDir,\n runDir,\n projectFilePaths: options.projectFilePaths,\n });\n\n const env = options.env ? { ...process.env, ...options.env } : process.env;\n const installCommand =\n options.prepareDependencies === false ? undefined : await resolveInstallCommand(options.packageManager, runDir);\n const command = typeof options.command === 'function' ? options.command({ runDir }) : options.command;\n const outputLimitBytes = options.outputLimitBytes ?? defaultOutputLimitBytes;\n let installResult: SpawnWithLimitsResult | undefined;\n\n if (installCommand) {\n installResult = await spawnWithLimits(installCommand, {\n cwd: runDir,\n env,\n outputLimitBytes,\n stdin: '',\n timeLimitSeconds: options.timeLimitSeconds,\n });\n if (isFailedSpawnResult(installResult)) {\n return toPackageManagerCommandRunResult({\n options,\n result: installResult,\n });\n }\n }\n\n // The install's measured time excludes the grace spent releasing pipes after it exited.\n const remainingTimeLimitSeconds = options.timeLimitSeconds - (installResult?.timeSeconds ?? 0);\n if (remainingTimeLimitSeconds <= 0) {\n return {\n stdin: options.stdin ?? '',\n stdout: installResult?.stdout ?? '',\n stderr: installResult?.stderr ?? '',\n status: 0,\n timeSeconds: options.timeLimitSeconds + 1e-3,\n cpuTimeSeconds: installResult?.cpuTimeSeconds ?? 0,\n memoryBytes: installResult?.memoryBytes ?? 0,\n timedOut: true,\n signal: installResult?.signal,\n outputLimitExceeded: false,\n };\n }\n\n const result = await spawnWithLimits(command, {\n cwd: runDir,\n env,\n outputLimitBytes,\n stdin: options.stdin ?? '',\n timeLimitSeconds: remainingTimeLimitSeconds,\n });\n\n if (installResult) {\n return toPackageManagerCommandRunResult({\n options,\n result: {\n ...result,\n timeSeconds: installResult.timeSeconds + result.timeSeconds,\n cpuTimeSeconds: installResult.cpuTimeSeconds + result.cpuTimeSeconds,\n memoryBytes: Math.max(installResult.memoryBytes, result.memoryBytes),\n },\n });\n }\n\n return toPackageManagerCommandRunResult({ options, result });\n } finally {\n // The command may have left permission-locked entries (e.g. a mode-000 directory).\n await forciblyRemoveDirectory(runDir);\n }\n}\n\nfunction toPackageManagerCommandRunResult(context: {\n options: RunCommandInTemporaryPackageManagerProjectOptions;\n result: SpawnWithLimitsResult;\n}): PackageManagerCommandRunResult {\n return {\n stdin: context.options.stdin ?? '',\n stdout: context.result.stdout,\n stderr: context.result.stderr,\n status: context.result.timedOut || context.result.outputLimitExceeded ? 0 : context.result.status,\n timeSeconds: context.result.timedOut ? context.options.timeLimitSeconds + 1e-3 : context.result.timeSeconds,\n cpuTimeSeconds: context.result.cpuTimeSeconds,\n memoryBytes: context.result.memoryBytes,\n timedOut: context.result.timedOut,\n signal: context.result.signal,\n outputLimitExceeded: context.result.outputLimitExceeded,\n };\n}\n\nfunction resolveInstallCommand(\n packageManager: PackageManager,\n runDir: string\n): Promise<PackageManagerInstallCommand | undefined> {\n return packageManagerInstallCommandResolvers[packageManager](runDir);\n}\n\nfunction isFailedSpawnResult(result: SpawnWithLimitsResult): boolean {\n return result.status !== 0 || result.timedOut || result.outputLimitExceeded;\n}\n\nasync function resolveBunInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'package.json')))) return undefined;\n // Bun supports --silent and it keeps successful preparation output out of judge output buffers.\n return (await hasAnyPath(runDir, ['bun.lock', 'bun.lockb']))\n ? ['bun', 'install', '--frozen-lockfile', '--silent']\n : ['bun', 'install', '--silent'];\n}\n\nasync function resolveCargoInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'Cargo.toml')))) return undefined;\n return (await pathExists(path.join(runDir, 'Cargo.lock'))) ? ['cargo', 'fetch', '--locked'] : ['cargo', 'fetch'];\n}\n\nasync function resolveGoInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'go.mod')))) return undefined;\n return ['go', 'mod', 'download'];\n}\n\nasync function resolveGradleInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (\n !(await hasAnyPath(runDir, [\n 'build.gradle',\n 'build.gradle.kts',\n 'settings.gradle',\n 'settings.gradle.kts',\n 'gradlew',\n 'gradlew.bat',\n ]))\n )\n return undefined;\n const args = ['--no-daemon', '--quiet', 'dependencies'] as const;\n if (process.platform === 'win32') {\n return (await pathExists(path.join(runDir, 'gradlew.bat')))\n ? ['cmd.exe', '/c', 'gradlew.bat', ...args]\n : ['gradle', ...args];\n }\n return (await pathExists(path.join(runDir, 'gradlew'))) ? ['sh', './gradlew', ...args] : ['gradle', ...args];\n}\n\nasync function resolveMavenInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'pom.xml')))) return undefined;\n const args = ['-q', 'dependency:go-offline'] as const;\n if (process.platform === 'win32') {\n return (await pathExists(path.join(runDir, 'mvnw.cmd')))\n ? ['cmd.exe', '/c', 'mvnw.cmd', ...args]\n : ['mvn', ...args];\n }\n return (await pathExists(path.join(runDir, 'mvnw'))) ? ['sh', './mvnw', ...args] : ['mvn', ...args];\n}\n\nasync function resolveNpmInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'package.json')))) return undefined;\n return (await pathExists(path.join(runDir, 'package-lock.json')))\n ? ['npm', 'ci', '--silent']\n : ['npm', 'install', '--silent'];\n}\n\nasync function resolvePnpmInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'package.json')))) return undefined;\n return (await pathExists(path.join(runDir, 'pnpm-lock.yaml')))\n ? ['pnpm', 'install', '--frozen-lockfile', '--silent']\n : ['pnpm', 'install', '--silent'];\n}\n\nasync function resolveRubyInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'Gemfile')))) return undefined;\n return (await pathExists(path.join(runDir, 'Gemfile.lock')))\n ? ['bundle', 'install', '--frozen', '--quiet']\n : ['bundle', 'install', '--quiet'];\n}\n\nasync function resolveUvInstallCommand(): Promise<undefined> {\n return undefined;\n}\n\nasync function resolveYarnInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'package.json')))) return undefined;\n const isBerry = await isYarnBerryProject(runDir);\n const hasLockfile = await pathExists(path.join(runDir, 'yarn.lock'));\n if (isBerry) return hasLockfile ? ['yarn', 'install', '--immutable'] : ['yarn', 'install'];\n return hasLockfile ? ['yarn', 'install', '--frozen-lockfile', '--silent'] : ['yarn', 'install', '--silent'];\n}\n\nasync function isYarnBerryProject(runDir: string): Promise<boolean> {\n if (await pathExists(path.join(runDir, '.yarnrc.yml'))) return true;\n\n const packageJson = await readJson(path.join(runDir, 'package.json'));\n const packageManager = typeof packageJson.packageManager === 'string' ? packageJson.packageManager : undefined;\n const yarnMajorVersion = /^yarn@(\\d+)/.exec(packageManager ?? '')?.[1];\n return yarnMajorVersion !== undefined && Number(yarnMajorVersion) >= 2;\n}\n\nasync function hasAnyPath(directoryPath: string, relativePaths: readonly string[]): Promise<boolean> {\n for (const relativePath of relativePaths) {\n if (await pathExists(path.join(directoryPath, relativePath))) return true;\n }\n return false;\n}\n\nasync function pathExists(filePath: string): Promise<boolean> {\n try {\n await fs.access(filePath);\n return true;\n } catch (error) {\n if (!hasErrorCode(error, 'ENOENT')) throw error;\n return false;\n }\n}\n\nasync function readJson(filePath: string): Promise<Record<string, unknown>> {\n try {\n const parsed = JSON.parse(await fs.readFile(filePath, 'utf8')) as unknown;\n return isRecord(parsed) ? parsed : {};\n } catch (error) {\n if (error instanceof SyntaxError) return {};\n if (hasErrorCode(error, 'ENOENT')) return {};\n throw error;\n }\n}\n\nexport async function copyPackageManagerProjectFiles(options: {\n packageManager: PackageManager;\n projectDir: string;\n runDir: string;\n projectFilePaths?: readonly string[];\n}): Promise<void> {\n for (const projectFilePath of options.projectFilePaths ?? packageManagerProjectFilePaths[options.packageManager]) {\n await copyPathIfExists(path.join(options.projectDir, projectFilePath), path.join(options.runDir, projectFilePath));\n }\n}\n\nasync function copyPathIfExists(sourcePath: string, destinationPath: string): Promise<void> {\n try {\n // Before touching the destination: creating the parent levels must not happen for a project\n // file the problem does not even ship.\n await fs.lstat(sourcePath);\n await fs.mkdir(path.dirname(destinationPath), { recursive: true });\n await fs.cp(sourcePath, destinationPath, { force: true, recursive: true });\n } catch (error) {\n if (!hasErrorCode(error, 'ENOENT')) throw error;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sourceCodeGrammars.cjs","names":["languageIdToDefinition","require_language","languageIdToSourceCodeGrammar","languageId","languageDefinition"],"sources":["../../src/helpers/sourceCodeGrammars.ts"],"sourcesContent":["import
|
|
1
|
+
{"version":3,"file":"sourceCodeGrammars.cjs","names":["languageIdToDefinition","require_language","languageIdToSourceCodeGrammar","languageId","languageDefinition"],"sources":["../../src/helpers/sourceCodeGrammars.ts"],"sourcesContent":["import { languageIdToDefinition } from '../types/language.js';\nimport type { SourceCodeGrammar } from './removeCommentsInSourceCode.js';\n\nexport const languageIdToSourceCodeGrammar = Object.fromEntries(\n Object.entries(languageIdToDefinition).flatMap(([languageId, languageDefinition]) =>\n languageDefinition.grammer ? [[languageId, languageDefinition.grammer] as const] : []\n )\n) as Readonly<Record<string, SourceCodeGrammar | undefined>>;\n"],"mappings":"sDAGaE,EAAgC,OAAO,YAClD,OAAO,QAAQF,EAAAA,sBAAsB,CAAC,CAAC,SAAS,CAACG,EAAYC,KAC3DA,EAAmB,QAAU,CAAC,CAACD,EAAYC,EAAmB,OAAO,CAAU,EAAI,CAAC,CACtF,CACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sourceCodeGrammars.js","names":["languageIdToSourceCodeGrammar","languageIdToDefinition","languageId","languageDefinition"],"sources":["../../src/helpers/sourceCodeGrammars.ts"],"sourcesContent":["import
|
|
1
|
+
{"version":3,"file":"sourceCodeGrammars.js","names":["languageIdToSourceCodeGrammar","languageIdToDefinition","languageId","languageDefinition"],"sources":["../../src/helpers/sourceCodeGrammars.ts"],"sourcesContent":["import { languageIdToDefinition } from '../types/language.js';\nimport type { SourceCodeGrammar } from './removeCommentsInSourceCode.js';\n\nexport const languageIdToSourceCodeGrammar = Object.fromEntries(\n Object.entries(languageIdToDefinition).flatMap(([languageId, languageDefinition]) =>\n languageDefinition.grammer ? [[languageId, languageDefinition.grammer] as const] : []\n )\n) as Readonly<Record<string, SourceCodeGrammar | undefined>>;\n"],"mappings":"8DAGA,MAAaA,EAAgC,OAAO,YAClD,OAAO,QAAQC,CAAsB,CAAC,CAAC,SAAS,CAACC,EAAYC,KAC3DA,EAAmB,QAAU,CAAC,CAACD,EAAYC,EAAmB,OAAO,CAAU,EAAI,CAAC,CACtF,CACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spawnWithLimits.cjs","names":["childProcess","hasErrorCode","fs","path","os","require_runtime","node_path","node_fs_promises","node_os","_willbooster_shared_lib","node_child_process","killGracePeriodMilliseconds","timeCommand","resolveTimeCommand","isTimeCommandAvailable","liveSubprocesses","subprocess","killSubprocessGroup","spawnWithLimits","timeOutput","openUnlinkedFile","detached","boundedCommand","context","command","spawnedCommand","timeLimitMilliseconds","startTimeMilliseconds","stdoutChunks","stderrChunks","outputBytes","timedOut","outputLimitExceeded","wallTimeSeconds","spawnErrorMessage","appendOutputChunk","chunk","remainingBytes","appendedChunk","chunks","timeout","killTimeout","settled","pendingError","closeTimeout","settle","reject","resolve","code","exitSignal","failAfterClose","error","timeResult","parseTimeOutput","readTail","endedByTimeout","status","timeLimitSeconds","signal","directory","prefix","file","length","size","maxBytes","buffer","match","content","parseSeconds","text","result"],"sources":["../../src/helpers/spawnWithLimits.ts"],"sourcesContent":["import childProcess from 'node:child_process';\nimport fs from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { hasErrorCode } from '@willbooster/shared-lib';\n\nexport interface SpawnWithLimitsResult {\n stdout: string;\n stderr: string;\n status: number | undefined;\n signal: NodeJS.Signals | undefined;\n /** Wall time measured by GNU time, or measured here until the command exited when it produced none. */\n timeSeconds: number;\n /**\n * User plus system CPU time of the command and the descendants it waited for, measured by GNU\n * time, or 0 when it produced no measurement. A run `timeout` ended is measured too, so a caller\n * can tell a program that used up its time limit from one that waited for the CPU.\n */\n cpuTimeSeconds: number;\n /**\n * Peak resident set size measured by GNU time, or 0 when it produced no measurement. GNU time\n * measures its child `timeout` and the descendants it waited for, so the value is at least\n * `timeout`'s own footprint (about 1 MiB), which only shows for the smallest native programs.\n */\n memoryBytes: number;\n /** The note GNU time adds when the command exits abnormally, e.g. `Command terminated by signal 11`. */\n timeCommandMessage: string | undefined;\n timedOut: boolean;\n outputLimitExceeded: boolean;\n}\n\nconst killGracePeriodMilliseconds = 1000;\nconst MIN_TIMEOUT_SECONDS = 0.001;\n// GNU `timeout` exits with 124 after ending a run at its limit, or with 128 + SIGKILL when the run\n// ignored its SIGTERM and it had to be killed after the grace period.\nconst TIMEOUT_EXIT_STATUS = 124;\nconst KILLED_AFTER_TIMEOUT_EXIT_STATUS = 137;\n// GNU time writes its record to a file this process opened and unlinked at once, handed down as fd 3\n// of the command and reopened by GNU time through `/dev/fd`: the command runs as the same OS user,\n// so a record reachable by path could be swapped for a fake or for a FIFO that blocks the read.\nconst TIME_OUTPUT_FD = 3;\n// The command inherits that descriptor too, so only the tail that can hold GNU time's record is read.\nconst TIME_OUTPUT_TAIL_BYTES = 4096;\nconst timeCommand = resolveTimeCommand();\n\n/** Whether GNU time is available, i.e. whether `timeSeconds`, `cpuTimeSeconds`, and `memoryBytes` are measured at all. */\nexport const isTimeCommandAvailable = timeCommand !== undefined;\n\n// The commands run in their own sessions, so they outlive this process unless it ends them itself\n// on the way out (e.g. a preset's SIGINT handler calling `process.exit`).\nconst liveSubprocesses = new Set<childProcess.ChildProcess>();\nprocess.once('exit', () => {\n for (const subprocess of liveSubprocesses) killSubprocessGroup(subprocess, 'SIGKILL');\n});\n\n/**\n * Runs a command in its own process group with `stdin` piped in, killing the whole group once it\n * exceeds the time or output limit, and reports the wall time, CPU time, and peak memory measured\n * by GNU time.\n */\nexport async function spawnWithLimits(\n command: readonly [string, ...string[]],\n context: {\n cwd: string;\n env: NodeJS.ProcessEnv;\n outputLimitBytes: number;\n stdin: string;\n timeLimitSeconds: number;\n }\n): Promise<SpawnWithLimitsResult> {\n const timeOutput = timeCommand === undefined ? undefined : await openUnlinkedFile('exercode-time-');\n try {\n const detached = process.platform !== 'win32';\n // The command runs in its own session so the group kill below cannot hit this process, but that\n // also puts it out of reach of whoever kills this process. GNU `timeout` keeps the run bounded\n // in that case, and it is what normally ends a run at its limit: GNU time wraps it, so a run\n // that hit the limit still gets its CPU time recorded (a group kill from here would take GNU\n // time down with the command), and `--foreground` keeps the command in this group so the kill\n // below still reaches every descendant. The timers below only back `timeout` up. Should this\n // process die first, `timeout` ends the command alone (`--foreground` signals no descendant),\n // an accepted gap: the judge server sweeps every process of the request's sandbox user anyway.\n const boundedCommand: readonly [string, ...string[]] = detached\n ? [\n 'timeout',\n '--foreground',\n '-k',\n String(killGracePeriodMilliseconds / 1000),\n // A zero duration would disable `timeout`; the front matter allows a zero time limit.\n String(Math.max(context.timeLimitSeconds, MIN_TIMEOUT_SECONDS)),\n ...command,\n ]\n : command;\n const spawnedCommand: readonly [string, ...string[]] =\n timeCommand === undefined\n ? boundedCommand\n : [...timeCommand, `--output=/dev/fd/${TIME_OUTPUT_FD}`, ...boundedCommand];\n // `timeout` sends SIGKILL a grace period after its SIGTERM; the timers here fire once even that\n // should have ended the run.\n const timeLimitMilliseconds = detached\n ? context.timeLimitSeconds * 1000 + 2 * killGracePeriodMilliseconds\n : context.timeLimitSeconds * 1000;\n // Monotonic: a wall-clock step must not turn a run `timeout` ended into one that finished in time.\n const startTimeMilliseconds = performance.now();\n // The standard streams are always pipes; the type cannot express that with the extra entry.\n const subprocess = childProcess.spawn(spawnedCommand[0], spawnedCommand.slice(1), {\n cwd: context.cwd,\n detached,\n env: context.env,\n stdio: timeOutput === undefined ? ['pipe', 'pipe', 'pipe'] : ['pipe', 'pipe', 'pipe', timeOutput.fd],\n }) as childProcess.ChildProcessWithoutNullStreams;\n liveSubprocesses.add(subprocess);\n\n const stdoutChunks: Buffer[] = [];\n const stderrChunks: Buffer[] = [];\n let outputBytes = 0;\n let timedOut = false;\n let outputLimitExceeded = false;\n let wallTimeSeconds = 0;\n let spawnErrorMessage: string | undefined;\n\n const appendOutputChunk = (chunks: Buffer[], chunk: Buffer): void => {\n if (outputBytes >= context.outputLimitBytes) {\n if (chunk.byteLength > 0) {\n outputLimitExceeded = true;\n killSubprocessGroup(subprocess, 'SIGKILL');\n }\n return;\n }\n\n const remainingBytes = context.outputLimitBytes - outputBytes;\n const appendedChunk = chunk.byteLength > remainingBytes ? chunk.subarray(0, remainingBytes) : chunk;\n chunks.push(appendedChunk);\n outputBytes += appendedChunk.byteLength;\n\n if (chunk.byteLength > remainingBytes) {\n outputLimitExceeded = true;\n killSubprocessGroup(subprocess, 'SIGKILL');\n }\n };\n\n subprocess.stdout.on('data', (chunk: Buffer) => appendOutputChunk(stdoutChunks, chunk));\n subprocess.stderr.on('data', (chunk: Buffer) => appendOutputChunk(stderrChunks, chunk));\n\n const timeout = setTimeout(() => {\n timedOut = true;\n killSubprocessGroup(subprocess, 'SIGTERM');\n }, timeLimitMilliseconds);\n const killTimeout = setTimeout(() => {\n if (timedOut) killSubprocessGroup(subprocess, 'SIGKILL');\n }, timeLimitMilliseconds + killGracePeriodMilliseconds);\n killTimeout.unref();\n\n const { status, signal } = await new Promise<{ status: number | undefined; signal: NodeJS.Signals | undefined }>(\n (resolve, reject) => {\n let settled = false;\n let pendingError: Error | undefined;\n let closeTimeout: NodeJS.Timeout | undefined;\n const settle = (code: number | null | undefined, exitSignal: NodeJS.Signals | null | undefined): void => {\n if (settled) return;\n settled = true;\n clearTimeout(closeTimeout);\n if (pendingError) {\n reject(pendingError);\n return;\n }\n resolve({ status: code ?? undefined, signal: exitSignal ?? undefined });\n };\n const failAfterClose = (error: Error): void => {\n if (settled) return;\n if (subprocess.pid === undefined) {\n // The spawn itself failed, i.e. the head of the chain (GNU time, or `timeout` without\n // it, or the command itself on Windows) or the cwd is missing. A missing judged command\n // is reported as status 127 by the wrapper instead. Report this like a run that produced\n // only this message.\n spawnErrorMessage = error.message;\n settle(undefined, undefined);\n return;\n }\n pendingError = error;\n killSubprocessGroup(subprocess, 'SIGKILL');\n };\n subprocess.on('error', failAfterClose);\n subprocess.stdin.on('error', (error: NodeJS.ErrnoException) => {\n if (error.code !== 'EPIPE') failAfterClose(error);\n });\n subprocess.on('exit', (code, exitSignal) => {\n // The command is gone, so the limit timers must not fire during the grace period below. A\n // descendant that inherited the pipes (e.g. `sleep 1000 &`) would keep them open and hold\n // back 'close' until it ends: kill what is left of the group. A descendant that moved to\n // its own session survives that, so stop waiting for the pipes after the grace period and\n // keep the output read so far.\n clearTimeout(timeout);\n clearTimeout(killTimeout);\n wallTimeSeconds = (performance.now() - startTimeMilliseconds) / 1000;\n killSubprocessGroup(subprocess, 'SIGKILL');\n closeTimeout = setTimeout(() => {\n subprocess.stdout.destroy();\n subprocess.stderr.destroy();\n settle(code, exitSignal);\n }, killGracePeriodMilliseconds);\n });\n subprocess.on('close', settle);\n subprocess.stdin.end(context.stdin);\n }\n ).finally(() => {\n clearTimeout(timeout);\n clearTimeout(killTimeout);\n liveSubprocesses.delete(subprocess);\n });\n\n const timeResult =\n timeOutput === undefined ? undefined : parseTimeOutput(await readTail(timeOutput, TIME_OUTPUT_TAIL_BYTES));\n\n return {\n stdout: Buffer.concat(stdoutChunks).toString(),\n stderr: spawnErrorMessage ?? Buffer.concat(stderrChunks).toString(),\n status,\n signal,\n // GNU time rounds a fast run to 0.00; the wall time until 'exit' stands in for it (never the\n // grace period spent on the pipes afterwards).\n timeSeconds: timeResult?.timeSeconds || wallTimeSeconds,\n cpuTimeSeconds: timeResult?.cpuTimeSeconds ?? 0,\n memoryBytes: timeResult?.memoryBytes ?? 0,\n timeCommandMessage: timeResult?.message,\n timedOut: timedOut || endedByTimeout(status, wallTimeSeconds, context.timeLimitSeconds),\n outputLimitExceeded,\n };\n } finally {\n await timeOutput?.close();\n }\n}\n\n/**\n * Whether `timeout` ended the run: 124 says it did so at the limit, and 137 that the run ignored its\n * SIGTERM and had to be killed after the grace period. A program can exit with either status on its\n * own, so each is trusted only once the wall time reached the moment `timeout` would have produced it.\n */\nfunction endedByTimeout(status: number | undefined, wallTimeSeconds: number, timeLimitSeconds: number): boolean {\n if (status === TIMEOUT_EXIT_STATUS) return wallTimeSeconds >= timeLimitSeconds;\n if (status === KILLED_AFTER_TIMEOUT_EXIT_STATUS) {\n return wallTimeSeconds >= timeLimitSeconds + killGracePeriodMilliseconds / 1000;\n }\n return false;\n}\n\nfunction killSubprocessGroup(subprocess: childProcess.ChildProcess, signal: NodeJS.Signals): void {\n if (subprocess.pid === undefined) return;\n\n try {\n if (process.platform === 'win32') {\n subprocess.kill(signal);\n return;\n }\n process.kill(-subprocess.pid, signal);\n } catch (error) {\n if (!hasErrorCode(error, 'ESRCH') && !hasErrorCode(error, 'EPERM')) throw error;\n }\n}\n\n// The file is unlinked before it is returned, so nothing can reach it by path anymore.\nasync function openUnlinkedFile(prefix: string): Promise<fs.FileHandle> {\n const directory = await fs.mkdtemp(path.join(os.tmpdir(), prefix));\n try {\n return await fs.open(path.join(directory, 'record'), 'wx+', 0o600);\n } finally {\n await fs.rm(directory, { recursive: true, force: true });\n }\n}\n\nasync function readTail(file: fs.FileHandle, maxBytes: number): Promise<string> {\n const { size } = await file.stat();\n const length = Math.min(size, maxBytes);\n const { buffer } = await file.read(Buffer.alloc(length), 0, length, size - length);\n return buffer.toString();\n}\n\n// Empty when the command was killed before GNU time wrote its record.\nfunction parseTimeOutput(\n content: string\n): { timeSeconds: number; cpuTimeSeconds: number; memoryBytes: number; message: string | undefined } | undefined {\n const match = /(?:^|\\n)(\\d+(?:[.,]\\d+)?) (\\d+(?:[.,]\\d+)?) (\\d+(?:[.,]\\d+)?) (\\d+)\\s*$/.exec(content);\n if (!match) return undefined;\n\n return {\n timeSeconds: parseSeconds(match[1]!),\n cpuTimeSeconds: parseSeconds(match[2]!) + parseSeconds(match[3]!),\n memoryBytes: Number(match[4]) * 1024,\n message: content.slice(0, match.index).trim() || undefined,\n };\n}\n\n// GNU time formats its seconds with the locale's decimal separator.\nfunction parseSeconds(text: string): number {\n return Number(text.replace(',', '.'));\n}\n\nfunction resolveTimeCommand(): readonly [string, ...string[]] | undefined {\n const command = os.platform() === 'darwin' ? 'gtime' : '/usr/bin/time';\n const result = childProcess.spawnSync(command, ['--version'], { stdio: 'ignore' });\n if (result.error || result.status !== 0) return undefined;\n\n // Wall seconds, user CPU seconds, system CPU seconds, peak resident set size in KiB.\n return [command, '--format', '%e %U %S %M'];\n}\n"],"mappings":"qSA+BA,MAAMW,EAA8B,IAY9BC,EAAcC,EAAmB,EAG1BC,EAAyBF,IAAgB,IAAA,GAIhDG,kBAAmB,IAAI,IAC7B,QAAQ,KAAK,WAAc,CACzB,IAAK,IAAMC,KAAcD,EAAkBE,EAAoBD,EAAY,SAAS,CACtF,CAAC,EAOD,eAAsBE,EACpB,EACA,EAOgC,CAChC,IAAMC,EAAaP,IAAgB,IAAA,GAAY,IAAA,GAAY,MAAMQ,EAAiB,gBAAgB,EAClG,GAAI,CACF,IAAMC,EAAW,QAAQ,WAAa,QAShCC,EAAiDD,EACnD,CACE,UACA,eACA,KACA,IAEA,OAAO,KAAK,IAAIE,EAAQ,iBAAkB,IAAmB,CAAC,EAC9D,GAAGC,CACL,EACAA,EACEC,EACJb,IAAgB,IAAA,GACZU,EACA,CAAC,GAAGV,EAAa,qBAAsC,GAAGU,CAAc,EAGxEI,EAAwBL,EAC1BE,EAAQ,iBAAmB,IAAO,EAAIZ,EACtCY,EAAQ,iBAAmB,IAEzBI,EAAwB,YAAY,IAAI,EAExCX,EAAahB,EAAAA,QAAa,MAAMyB,EAAe,GAAIA,EAAe,MAAM,CAAC,EAAG,CAChF,IAAKF,EAAQ,IACb,WACA,IAAKA,EAAQ,IACb,MAAOJ,IAAe,IAAA,GAAY,CAAC,OAAQ,OAAQ,MAAM,EAAI,CAAC,OAAQ,OAAQ,OAAQA,EAAW,EAAE,CACrG,CAAC,EACDJ,EAAiB,IAAIC,CAAU,EAE/B,IAAMY,EAAyB,CAAC,EAC1BC,EAAyB,CAAC,EAC5BC,EAAc,EACdC,EAAW,GACXC,EAAsB,GACtBC,EAAkB,EAClBC,EAEEC,GAAqB,EAAkB,IAAwB,CACnE,GAAIL,GAAeP,EAAQ,iBAAkB,CACvCa,EAAM,WAAa,IACrB,EAAsB,GACtBnB,EAAoBD,EAAY,SAAS,GAE3C,MACF,CAEA,IAAMqB,EAAiBd,EAAQ,iBAAmBO,EAC5CQ,EAAgBF,EAAM,WAAaC,EAAiBD,EAAM,SAAS,EAAGC,CAAc,EAAID,EAC9FG,EAAO,KAAKD,CAAa,EACzB,GAAeA,EAAc,WAEzBF,EAAM,WAAaC,IACrB,EAAsB,GACtBpB,EAAoBD,EAAY,SAAS,EAE7C,EAEAA,EAAW,OAAO,GAAG,OAAS,GAAkBmB,EAAkBP,EAAcQ,CAAK,CAAC,EACtFpB,EAAW,OAAO,GAAG,OAAS,GAAkBmB,EAAkBN,EAAcO,CAAK,CAAC,EAEtF,IAAMI,EAAU,eAAiB,CAC/B,EAAW,GACXvB,EAAoBD,EAAY,SAAS,CAC3C,EAAGU,CAAqB,EAClBe,EAAc,eAAiB,CAC/BV,GAAUd,EAAoBD,EAAY,SAAS,CACzD,EAAGU,EAAwBf,CAA2B,EACtD8B,EAAY,MAAM,EAElB,GAAM,CAAE,SAAQ,UAAW,MAAM,IAAI,SAClC,EAAS,IAAW,CACnB,IAAIC,EAAU,GACVC,EACAC,EACEC,GAAU,EAAiC,IAAwD,CACnGH,IACJ,EAAU,GACV,aAAaE,CAAY,EACrBD,EACFG,EAAOH,CAAY,EAGrBI,EAAQ,CAAE,OAAQC,GAAQ,IAAA,GAAW,OAAQC,GAAc,IAAA,EAAU,CAAC,EACxE,EACMC,EAAkB,GAAuB,CACzCR,IACA1B,EAAW,MAAQ,IAAA,IAKrB,EAAoBmC,EAAM,QAC1BN,EAAO,IAAA,GAAW,IAAA,EAAS,IAG7B,EAAeM,EACflC,EAAoBD,EAAY,SAAS,GAC3C,EACAA,EAAW,GAAG,QAASkC,CAAc,EACrClC,EAAW,MAAM,GAAG,QAAU,GAAiC,CACzDmC,EAAM,OAAS,SAASD,EAAeC,CAAK,CAClD,CAAC,EACDnC,EAAW,GAAG,QAAS,EAAM,IAAe,CAM1C,aAAawB,CAAO,EACpB,aAAaC,CAAW,EACxB,GAAmB,YAAY,IAAI,EAAId,GAAyB,IAChEV,EAAoBD,EAAY,SAAS,EACzC,EAAe,eAAiB,CAC9BA,EAAW,OAAO,QAAQ,EAC1BA,EAAW,OAAO,QAAQ,EAC1B6B,EAAOG,EAAMC,CAAU,CACzB,EAAGtC,CAA2B,CAChC,CAAC,EACDK,EAAW,GAAG,QAAS6B,CAAM,EAC7B7B,EAAW,MAAM,IAAIO,EAAQ,KAAK,CACpC,CACF,CAAC,CAAC,YAAc,CACd,aAAaiB,CAAO,EACpB,aAAaC,CAAW,EACxB1B,EAAiB,OAAOC,CAAU,CACpC,CAAC,EAEKoC,EACJjC,IAAe,IAAA,GAAY,IAAA,GAAYkC,EAAgB,MAAMC,EAASnC,EAAY,IAAsB,CAAC,EAE3G,MAAO,CACL,OAAQ,OAAO,OAAOS,CAAY,CAAC,CAAC,SAAS,EAC7C,OAAQM,GAAqB,OAAO,OAAOL,CAAY,CAAC,CAAC,SAAS,EAClE,SACA,SAGA,YAAauB,GAAY,aAAenB,EACxC,eAAgBmB,GAAY,gBAAkB,EAC9C,YAAaA,GAAY,aAAe,EACxC,mBAAoBA,GAAY,QAChC,SAAUrB,GAAYwB,EAAeC,EAAQvB,EAAiBV,EAAQ,gBAAgB,EACtF,qBACF,CACF,QAAU,CACR,MAAMJ,GAAY,MAAM,CAC1B,CACF,CAOA,SAASoC,EAAe,EAA4B,EAAyB,EAAmC,CAK9G,OAJIC,IAAW,IAA4BvB,GAAmBwB,EAC1DD,IAAW,KACNvB,GAAmBwB,EAAmB9C,EAA8B,GAG/E,CAEA,SAASM,EAAoB,EAAuC,EAA8B,CAChG,GAAID,EAAW,MAAQ,IAAA,GAEvB,GAAI,CACF,GAAI,QAAQ,WAAa,QAAS,CAChCA,EAAW,KAAK0C,CAAM,EACtB,MACF,CACA,QAAQ,KAAK,CAAC1C,EAAW,IAAK0C,CAAM,CACtC,OAASP,EAAO,CACd,GAAI,EAAA,EAAClD,EAAAA,aAAAA,CAAakD,EAAO,OAAO,GAAK,EAAA,EAAClD,EAAAA,aAAAA,CAAakD,EAAO,OAAO,EAAG,MAAMA,CAC5E,CACF,CAGA,eAAe/B,EAAiB,EAAwC,CACtE,IAAMuC,EAAY,MAAMzD,EAAAA,QAAG,QAAQC,EAAAA,QAAK,KAAKC,EAAAA,QAAG,OAAO,EAAGwD,CAAM,CAAC,EACjE,GAAI,CACF,OAAO,MAAM1D,EAAAA,QAAG,KAAKC,EAAAA,QAAK,KAAKwD,EAAW,QAAQ,EAAG,MAAO,GAAK,CACnE,QAAU,CACR,MAAMzD,EAAAA,QAAG,GAAGyD,EAAW,CAAE,UAAW,GAAM,MAAO,EAAK,CAAC,CACzD,CACF,CAEA,eAAeL,EAAS,EAAqB,EAAmC,CAC9E,GAAM,CAAE,QAAS,MAAMO,EAAK,KAAK,EAC3BC,EAAS,KAAK,IAAIC,EAAMC,CAAQ,EAChC,CAAE,UAAW,MAAMH,EAAK,KAAK,OAAO,MAAMC,CAAM,EAAG,EAAGA,EAAQC,EAAOD,CAAM,EACjF,OAAOG,EAAO,SAAS,CACzB,CAGA,SAASZ,EACP,EAC+G,CAC/G,IAAMa,EAAQ,0EAA0E,KAAKC,CAAO,EACpG,GAAKD,EAEL,MAAO,CACL,YAAaE,EAAaF,EAAM,EAAG,EACnC,eAAgBE,EAAaF,EAAM,EAAG,EAAIE,EAAaF,EAAM,EAAG,EAChE,YAAa,OAAOA,EAAM,EAAE,EAAI,KAChC,QAASC,EAAQ,MAAM,EAAGD,EAAM,KAAK,CAAC,CAAC,KAAK,GAAK,IAAA,EACnD,CACF,CAGA,SAASE,EAAa,EAAsB,CAC1C,OAAO,OAAOC,EAAK,QAAQ,IAAK,GAAG,CAAC,CACtC,CAEA,SAASxD,GAAiE,CACxE,IAAMW,EAAUpB,EAAAA,QAAG,SAAS,IAAM,SAAW,QAAU,gBACjDkE,EAAStE,EAAAA,QAAa,UAAUwB,EAAS,CAAC,WAAW,EAAG,CAAE,MAAO,QAAS,CAAC,EACjF,GAAI,IAAO,OAAS8C,EAAO,SAAW,GAGtC,MAAO,CAAC9C,EAAS,WAAY,aAAa,CAC5C"}
|
|
1
|
+
{"version":3,"file":"spawnWithLimits.cjs","names":["childProcess","hasErrorCode","fs","path","os","require_runtime","node_path","node_fs_promises","node_os","_willbooster_shared_lib","node_child_process","killGracePeriodMilliseconds","timeCommand","resolveTimeCommand","isTimeCommandAvailable","liveSubprocesses","subprocess","killSubprocessGroup","spawnWithLimits","timeOutput","openUnlinkedFile","detached","boundedCommand","context","command","spawnedCommand","timeLimitMilliseconds","startTimeMilliseconds","stdoutChunks","stderrChunks","outputBytes","timedOut","outputLimitExceeded","wallTimeSeconds","spawnErrorMessage","appendOutputChunk","chunk","remainingBytes","appendedChunk","chunks","timeout","killTimeout","settled","pendingError","closeTimeout","settle","reject","resolve","code","exitSignal","failAfterClose","error","timeResult","parseTimeOutput","readTail","endedByTimeout","status","timeLimitSeconds","signal","directory","prefix","file","length","size","maxBytes","buffer","match","content","parseSeconds","text","result"],"sources":["../../src/helpers/spawnWithLimits.ts"],"sourcesContent":["import childProcess from 'node:child_process';\nimport fs from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport { hasErrorCode } from '@willbooster/shared-lib';\n\nexport interface SpawnWithLimitsResult {\n stdout: string;\n stderr: string;\n status: number | undefined;\n signal: NodeJS.Signals | undefined;\n /** Wall time measured by GNU time, or measured here until the command exited when it produced none. */\n timeSeconds: number;\n /**\n * User plus system CPU time of the command and the descendants it waited for, measured by GNU\n * time, or 0 when it produced no measurement. A run `timeout` ended is measured too, so a caller\n * can tell a program that used up its time limit from one that waited for the CPU.\n */\n cpuTimeSeconds: number;\n /**\n * Peak resident set size measured by GNU time, or 0 when it produced no measurement. GNU time\n * measures its child `timeout` and the descendants it waited for, so the value is at least\n * `timeout`'s own footprint (about 1 MiB), which only shows for the smallest native programs.\n */\n memoryBytes: number;\n /** The note GNU time adds when the command exits abnormally, e.g. `Command terminated by signal 11`. */\n timeCommandMessage: string | undefined;\n timedOut: boolean;\n outputLimitExceeded: boolean;\n}\n\nconst killGracePeriodMilliseconds = 1000;\nconst MIN_TIMEOUT_SECONDS = 0.001;\n// GNU `timeout` exits with 124 after ending a run at its limit, or with 128 + SIGKILL when the run\n// ignored its SIGTERM and it had to be killed after the grace period.\nconst TIMEOUT_EXIT_STATUS = 124;\nconst KILLED_AFTER_TIMEOUT_EXIT_STATUS = 137;\n// GNU time writes its record to a file this process opened and unlinked at once, handed down as fd 3\n// of the command and reopened by GNU time through `/dev/fd`: the command runs as the same OS user,\n// so a record reachable by path could be swapped for a fake or for a FIFO that blocks the read.\nconst TIME_OUTPUT_FD = 3;\n// The command inherits that descriptor too, so only the tail that can hold GNU time's record is read.\nconst TIME_OUTPUT_TAIL_BYTES = 4096;\nconst timeCommand = resolveTimeCommand();\n\n/** Whether GNU time is available, i.e. whether `timeSeconds`, `cpuTimeSeconds`, and `memoryBytes` are measured at all. */\nexport const isTimeCommandAvailable = timeCommand !== undefined;\n\n// The commands run in their own sessions, so they outlive this process unless it ends them itself\n// on the way out (e.g. a preset's SIGINT handler calling `process.exit`).\nconst liveSubprocesses = new Set<childProcess.ChildProcess>();\nprocess.once('exit', () => {\n for (const subprocess of liveSubprocesses) killSubprocessGroup(subprocess, 'SIGKILL');\n});\n\n/**\n * Runs a command in its own process group with `stdin` piped in, killing the whole group once it\n * exceeds the time or output limit, and reports the wall time, CPU time, and peak memory measured\n * by GNU time.\n */\nexport async function spawnWithLimits(\n command: readonly [string, ...string[]],\n context: {\n cwd: string;\n env: NodeJS.ProcessEnv;\n outputLimitBytes: number;\n stdin: string;\n timeLimitSeconds: number;\n }\n): Promise<SpawnWithLimitsResult> {\n const timeOutput = timeCommand === undefined ? undefined : await openUnlinkedFile('exercode-time-');\n try {\n const detached = process.platform !== 'win32';\n // The command runs in its own session so the group kill below cannot hit this process, but that\n // also puts it out of reach of whoever kills this process. GNU `timeout` keeps the run bounded\n // in that case, and it is what normally ends a run at its limit: GNU time wraps it, so a run\n // that hit the limit still gets its CPU time recorded (a group kill from here would take GNU\n // time down with the command), and `--foreground` keeps the command in this group so the kill\n // below still reaches every descendant. The timers below only back `timeout` up. Should this\n // process die first, `timeout` ends the command alone (`--foreground` signals no descendant),\n // an accepted gap: the judge server sweeps every process of the request's sandbox user anyway.\n const boundedCommand: readonly [string, ...string[]] = detached\n ? [\n 'timeout',\n '--foreground',\n '-k',\n String(killGracePeriodMilliseconds / 1000),\n // A zero duration would disable `timeout`; the front matter allows a zero time limit.\n String(Math.max(context.timeLimitSeconds, MIN_TIMEOUT_SECONDS)),\n ...command,\n ]\n : command;\n const spawnedCommand: readonly [string, ...string[]] =\n timeCommand === undefined\n ? boundedCommand\n : [...timeCommand, `--output=/dev/fd/${TIME_OUTPUT_FD}`, ...boundedCommand];\n // `timeout` sends SIGKILL a grace period after its SIGTERM; the timers here fire once even that\n // should have ended the run.\n const timeLimitMilliseconds = detached\n ? context.timeLimitSeconds * 1000 + 2 * killGracePeriodMilliseconds\n : context.timeLimitSeconds * 1000;\n // Monotonic: a wall-clock step must not turn a run `timeout` ended into one that finished in time.\n const startTimeMilliseconds = performance.now();\n // The standard streams are always pipes; the type cannot express that with the extra entry.\n const subprocess = childProcess.spawn(spawnedCommand[0], spawnedCommand.slice(1), {\n cwd: context.cwd,\n detached,\n env: context.env,\n stdio: timeOutput === undefined ? ['pipe', 'pipe', 'pipe'] : ['pipe', 'pipe', 'pipe', timeOutput.fd],\n }) as childProcess.ChildProcessWithoutNullStreams;\n liveSubprocesses.add(subprocess);\n\n const stdoutChunks: Buffer[] = [];\n const stderrChunks: Buffer[] = [];\n let outputBytes = 0;\n let timedOut = false;\n let outputLimitExceeded = false;\n let wallTimeSeconds = 0;\n let spawnErrorMessage: string | undefined;\n\n const appendOutputChunk = (chunks: Buffer[], chunk: Buffer): void => {\n if (outputBytes >= context.outputLimitBytes) {\n if (chunk.byteLength > 0) {\n outputLimitExceeded = true;\n killSubprocessGroup(subprocess, 'SIGKILL');\n }\n return;\n }\n\n const remainingBytes = context.outputLimitBytes - outputBytes;\n const appendedChunk = chunk.byteLength > remainingBytes ? chunk.subarray(0, remainingBytes) : chunk;\n chunks.push(appendedChunk);\n outputBytes += appendedChunk.byteLength;\n\n if (chunk.byteLength > remainingBytes) {\n outputLimitExceeded = true;\n killSubprocessGroup(subprocess, 'SIGKILL');\n }\n };\n\n subprocess.stdout.on('data', (chunk: Buffer) => appendOutputChunk(stdoutChunks, chunk));\n subprocess.stderr.on('data', (chunk: Buffer) => appendOutputChunk(stderrChunks, chunk));\n\n const timeout = setTimeout(() => {\n timedOut = true;\n killSubprocessGroup(subprocess, 'SIGTERM');\n }, timeLimitMilliseconds);\n const killTimeout = setTimeout(() => {\n if (timedOut) killSubprocessGroup(subprocess, 'SIGKILL');\n }, timeLimitMilliseconds + killGracePeriodMilliseconds);\n killTimeout.unref();\n\n const { status, signal } = await new Promise<{ status: number | undefined; signal: NodeJS.Signals | undefined }>(\n (resolve, reject) => {\n let settled = false;\n let pendingError: Error | undefined;\n let closeTimeout: NodeJS.Timeout | undefined;\n const settle = (code: number | null | undefined, exitSignal: NodeJS.Signals | null | undefined): void => {\n if (settled) return;\n settled = true;\n clearTimeout(closeTimeout);\n if (pendingError) {\n reject(pendingError);\n return;\n }\n resolve({ status: code ?? undefined, signal: exitSignal ?? undefined });\n };\n const failAfterClose = (error: Error): void => {\n if (settled) return;\n if (subprocess.pid === undefined) {\n // The spawn itself failed, i.e. the head of the chain (GNU time, or `timeout` without\n // it, or the command itself on Windows) or the cwd is missing. A missing judged command\n // is reported as status 127 by the wrapper instead. Report this like a run that produced\n // only this message.\n spawnErrorMessage = error.message;\n settle(undefined, undefined);\n return;\n }\n pendingError = error;\n killSubprocessGroup(subprocess, 'SIGKILL');\n };\n subprocess.on('error', failAfterClose);\n subprocess.stdin.on('error', (error: NodeJS.ErrnoException) => {\n if (error.code !== 'EPIPE') failAfterClose(error);\n });\n subprocess.on('exit', (code, exitSignal) => {\n // The command is gone, so the limit timers must not fire during the grace period below. A\n // descendant that inherited the pipes (e.g. `sleep 1000 &`) would keep them open and hold\n // back 'close' until it ends: kill what is left of the group. A descendant that moved to\n // its own session survives that, so stop waiting for the pipes after the grace period and\n // keep the output read so far.\n clearTimeout(timeout);\n clearTimeout(killTimeout);\n wallTimeSeconds = (performance.now() - startTimeMilliseconds) / 1000;\n killSubprocessGroup(subprocess, 'SIGKILL');\n closeTimeout = setTimeout(() => {\n subprocess.stdout.destroy();\n subprocess.stderr.destroy();\n settle(code, exitSignal);\n }, killGracePeriodMilliseconds);\n });\n subprocess.on('close', settle);\n subprocess.stdin.end(context.stdin);\n }\n ).finally(() => {\n clearTimeout(timeout);\n clearTimeout(killTimeout);\n liveSubprocesses.delete(subprocess);\n });\n\n const timeResult =\n timeOutput === undefined ? undefined : parseTimeOutput(await readTail(timeOutput, TIME_OUTPUT_TAIL_BYTES));\n\n return {\n stdout: Buffer.concat(stdoutChunks).toString(),\n stderr: spawnErrorMessage ?? Buffer.concat(stderrChunks).toString(),\n status,\n signal,\n // GNU time rounds a fast run to 0.00; the wall time until 'exit' stands in for it (never the\n // grace period spent on the pipes afterwards).\n timeSeconds: timeResult?.timeSeconds || wallTimeSeconds,\n cpuTimeSeconds: timeResult?.cpuTimeSeconds ?? 0,\n memoryBytes: timeResult?.memoryBytes ?? 0,\n timeCommandMessage: timeResult?.message,\n timedOut: timedOut || endedByTimeout(status, wallTimeSeconds, context.timeLimitSeconds),\n outputLimitExceeded,\n };\n } finally {\n await timeOutput?.close();\n }\n}\n\n/**\n * Whether `timeout` ended the run: 124 says it did so at the limit, and 137 that the run ignored its\n * SIGTERM and had to be killed after the grace period. A program can exit with either status on its\n * own, so each is trusted only once the wall time reached the moment `timeout` would have produced it.\n */\nfunction endedByTimeout(status: number | undefined, wallTimeSeconds: number, timeLimitSeconds: number): boolean {\n if (status === TIMEOUT_EXIT_STATUS) return wallTimeSeconds >= timeLimitSeconds;\n if (status === KILLED_AFTER_TIMEOUT_EXIT_STATUS) {\n return wallTimeSeconds >= timeLimitSeconds + killGracePeriodMilliseconds / 1000;\n }\n return false;\n}\n\nfunction killSubprocessGroup(subprocess: childProcess.ChildProcess, signal: NodeJS.Signals): void {\n if (subprocess.pid === undefined) return;\n\n try {\n if (process.platform === 'win32') {\n subprocess.kill(signal);\n return;\n }\n process.kill(-subprocess.pid, signal);\n } catch (error) {\n if (!hasErrorCode(error, 'ESRCH') && !hasErrorCode(error, 'EPERM')) throw error;\n }\n}\n\n// The file is unlinked before it is returned, so nothing can reach it by path anymore.\nasync function openUnlinkedFile(prefix: string): Promise<fs.FileHandle> {\n const directory = await fs.mkdtemp(path.join(os.tmpdir(), prefix));\n try {\n return await fs.open(path.join(directory, 'record'), 'wx+', 0o600);\n } finally {\n await fs.rm(directory, { recursive: true, force: true });\n }\n}\n\nasync function readTail(file: fs.FileHandle, maxBytes: number): Promise<string> {\n const { size } = await file.stat();\n const length = Math.min(size, maxBytes);\n const { buffer } = await file.read(Buffer.alloc(length), 0, length, size - length);\n return buffer.toString();\n}\n\n// Empty when the command was killed before GNU time wrote its record.\nfunction parseTimeOutput(\n content: string\n): { timeSeconds: number; cpuTimeSeconds: number; memoryBytes: number; message: string | undefined } | undefined {\n const match = /(?:^|\\n)(\\d+(?:[.,]\\d+)?) (\\d+(?:[.,]\\d+)?) (\\d+(?:[.,]\\d+)?) (\\d+)\\s*$/.exec(content);\n if (!match) return undefined;\n\n return {\n timeSeconds: parseSeconds(match[1]!),\n cpuTimeSeconds: parseSeconds(match[2]!) + parseSeconds(match[3]!),\n memoryBytes: Number(match[4]) * 1024,\n message: content.slice(0, match.index).trim() || undefined,\n };\n}\n\n// GNU time formats its seconds with the locale's decimal separator.\nfunction parseSeconds(text: string): number {\n return Number(text.replace(',', '.'));\n}\n\nfunction resolveTimeCommand(): readonly [string, ...string[]] | undefined {\n const command = os.platform() === 'darwin' ? 'gtime' : '/usr/bin/time';\n const result = childProcess.spawnSync(command, ['--version'], { stdio: 'ignore' });\n if (result.error || result.status !== 0) return undefined;\n\n // Wall seconds, user CPU seconds, system CPU seconds, peak resident set size in KiB.\n return [command, '--format', '%e %U %S %M'];\n}\n"],"mappings":"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"}
|