@exercode/problem-utils 2.4.2 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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/helpers/checkProblemDirIsolation.cjs +1 -1
- package/dist/helpers/checkProblemDirIsolation.js +1 -1
- package/dist/helpers/cleanWorkingDirectory.cjs +1 -1
- package/dist/helpers/cleanWorkingDirectory.cjs.map +1 -1
- package/dist/helpers/cleanWorkingDirectory.d.ts +2 -6
- package/dist/helpers/cleanWorkingDirectory.js +1 -1
- package/dist/helpers/cleanWorkingDirectory.js.map +1 -1
- package/dist/helpers/compareExpectedOutputFiles.cjs +1 -1
- package/dist/helpers/compareExpectedOutputFiles.cjs.map +1 -1
- package/dist/helpers/compareExpectedOutputFiles.js +1 -1
- package/dist/helpers/compareExpectedOutputFiles.js.map +1 -1
- package/dist/helpers/copyTestCaseFileInput.cjs +1 -1
- package/dist/helpers/copyTestCaseFileInput.cjs.map +1 -1
- package/dist/helpers/copyTestCaseFileInput.js +1 -1
- package/dist/helpers/copyTestCaseFileInput.js.map +1 -1
- package/dist/helpers/readOutputFiles.cjs +1 -1
- package/dist/helpers/readOutputFiles.cjs.map +1 -1
- package/dist/helpers/readOutputFiles.d.ts +0 -9
- package/dist/helpers/readOutputFiles.js +1 -1
- package/dist/helpers/readOutputFiles.js.map +1 -1
- package/dist/helpers/runCommandInTemporaryPackageManagerProject.cjs +1 -1
- package/dist/helpers/runCommandInTemporaryPackageManagerProject.cjs.map +1 -1
- package/dist/helpers/runCommandInTemporaryPackageManagerProject.d.ts +0 -3
- package/dist/helpers/runCommandInTemporaryPackageManagerProject.js +1 -1
- package/dist/helpers/runCommandInTemporaryPackageManagerProject.js.map +1 -1
- package/dist/helpers/spawnSyncWithTimeout.cjs +1 -1
- package/dist/helpers/spawnSyncWithTimeout.cjs.map +1 -1
- package/dist/helpers/spawnSyncWithTimeout.d.ts +0 -7
- package/dist/helpers/spawnSyncWithTimeout.js +1 -1
- package/dist/helpers/spawnSyncWithTimeout.js.map +1 -1
- package/dist/helpers/startHttpServer.cjs +1 -1
- package/dist/helpers/startHttpServer.cjs.map +1 -1
- package/dist/helpers/startHttpServer.js +1 -1
- package/dist/helpers/startHttpServer.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.js +1 -1
- package/dist/presets/command.cjs +1 -1
- package/dist/presets/command.cjs.map +1 -1
- package/dist/presets/command.d.ts +0 -16
- package/dist/presets/command.js +1 -1
- package/dist/presets/command.js.map +1 -1
- package/dist/presets/evaluation.cjs +4 -4
- package/dist/presets/evaluation.cjs.map +1 -1
- package/dist/presets/evaluation.js +4 -4
- package/dist/presets/evaluation.js.map +1 -1
- package/dist/presets/guiCommand.cjs +3 -3
- package/dist/presets/guiCommand.cjs.map +1 -1
- package/dist/presets/guiCommand.d.ts +0 -24
- package/dist/presets/guiCommand.js +3 -3
- package/dist/presets/guiCommand.js.map +1 -1
- package/dist/presets/stdio.cjs +1 -1
- package/dist/presets/stdio.cjs.map +1 -1
- package/dist/presets/stdio.js +1 -1
- package/dist/presets/stdio.js.map +1 -1
- package/dist/types/language.cjs +2 -2
- package/dist/types/language.cjs.map +1 -1
- package/dist/types/language.js +2 -2
- package/dist/types/language.js.map +1 -1
- package/package.json +4 -4
- package/dist/helpers/runCustomRunner.cjs +0 -2
- package/dist/helpers/runCustomRunner.cjs.map +0 -1
- package/dist/helpers/runCustomRunner.d.ts +0 -8
- package/dist/helpers/runCustomRunner.js +0 -2
- package/dist/helpers/runCustomRunner.js.map +0 -1
- package/dist/helpers/safeFs.cjs +0 -2
- package/dist/helpers/safeFs.cjs.map +0 -1
- package/dist/helpers/safeFs.d.ts +0 -29
- package/dist/helpers/safeFs.js +0 -2
- package/dist/helpers/safeFs.js.map +0 -1
- package/dist/helpers/sandboxUser.cjs +0 -3
- package/dist/helpers/sandboxUser.cjs.map +0 -1
- package/dist/helpers/sandboxUser.d.ts +0 -146
- package/dist/helpers/sandboxUser.js +0 -3
- package/dist/helpers/sandboxUser.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stdio.js","names":[],"sources":["../../src/presets/stdio.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { z } from 'zod';\n\nimport { cleanWorkingDirectory, snapshotWorkingDirectory } from '../helpers/cleanWorkingDirectory.js';\nimport { judgeAgainstExpectations } from '../helpers/compareExpectedOutputFiles.js';\nimport { copyTestCaseFileInput } from '../helpers/copyTestCaseFileInput.js';\nimport { findEntryPointFile } from '../helpers/findEntryPointFile.js';\nimport { findLanguageDefinitionByPath } from '../helpers/findLanguageDefinitionByPath.js';\nimport { judgeByStaticAnalysis } from '../helpers/judgeByStaticAnalysis.js';\nimport { parseArgs } from '../helpers/parseArgs.js';\nimport { printTestCaseResult } from '../helpers/printTestCaseResult.js';\nimport { readOutputFiles } from '../helpers/readOutputFiles.js';\nimport { makeAccessibleToSandboxUser, makeTraversableBySandboxUser } from '../helpers/sandboxUser.js';\nimport {\n judgesTestCasesWithoutExpectations,\n readProblemMarkdownFrontMatter,\n} from '../helpers/readProblemMarkdownFrontMatter.js';\nimport { readTestCases } from '../helpers/readTestCases.js';\nimport { spawnSyncWithTimeout } from '../helpers/spawnSyncWithTimeout.js';\nimport {\n copyProblemDirToTemporaryRoot,\n forciblyRemoveDirectory,\n forciblyRemoveDirectorySync,\n} from '../helpers/temporaryProblemDirCopy.js';\nimport { DecisionCode } from '../types/decisionCode.js';\n\nconst BUILD_TIMEOUT_SECONDS = 10;\nconst JUDGE_DEFAULT_TIMEOUT_SECONDS = 2;\nconst DEBUG_DEFAULT_TIMEOUT_SECONDS = 10;\n\nconst MAX_STDOUT_LENGTH = 50_000;\n// The judge server shows a test case on the problem page when its id contains `example`.\nconst EXAMPLE_TEST_CASE_ID_PATTERN = /example/;\n\nconst judgeParamsSchema = z.object({\n language: z.union([z.string(), z.array(z.string())]).optional(),\n});\n\nconst debugParamsSchema = judgeParamsSchema.extend({\n stdin: z.string().optional(),\n});\n\ntype DebugParams = z.infer<typeof debugParamsSchema>;\n\n/**\n * A preset judge function using stdin and stdout as test cases.\n *\n * A standard stdio problem must NOT commit a `judge.ts` that only calls this preset: the Exercode\n * server applies this preset automatically when `judge.ts` is absent, and committed copies would\n * drift from the server's defaults.\n *\n * @example\n * Run in a problem directory without `judge.ts`:\n * ```bash\n * bun x exercode-problem judge model_answers/java\n * ```\n *\n * Run with the optional parameters:\n * ```bash\n * bun x exercode-problem judge model_answers/java '{ \"language\": \"javascript\" }'\n * ```\n */\nexport async function stdioJudgePreset(problemDir: string): Promise<void> {\n const args = parseArgs(process.argv);\n if (!args.cwd) throw new Error('cwd argument required');\n const params = judgeParamsSchema.parse(args.params);\n\n // The sandboxed submission must read its sources and write build/run outputs in its directory.\n makeAccessibleToSandboxUser(args.cwd);\n\n const problemMarkdownFrontMatter = await readProblemMarkdownFrontMatter(problemDir);\n const testCases = await readTestCases(path.join(problemDir, 'test_cases'));\n\n // Without an expectation, a case would accept any run; only custom harnesses may judge input-only\n // cases. A problem judged by manual scoring or the presence of required output files has an\n // expectation of its own. Checked before the static analysis so that an authoring error surfaces\n // regardless of the submission.\n if (!judgesTestCasesWithoutExpectations(problemMarkdownFrontMatter)) {\n for (const testCase of testCases) {\n if (testCase.output === undefined && !(await hasExpectedFiles(testCase.fileOutputPath))) {\n throw new Error(\n `test case ${testCase.id} needs an expected output (${testCase.id}.out or a non-empty ${testCase.id}.fout/)`\n );\n }\n }\n }\n\n const staticAnalysisTestCaseResult = await judgeByStaticAnalysis(args.cwd, problemMarkdownFrontMatter);\n if (staticAnalysisTestCaseResult) {\n printTestCaseResult({ testCaseId: testCases[0]?.id ?? 'prebuild', ...staticAnalysisTestCaseResult });\n return;\n }\n\n const originalMainFilePath = await findEntryPointFile(args.cwd, params.language);\n if (!originalMainFilePath) {\n printTestCaseResult({\n testCaseId: testCases[0]?.id ?? 'prebuild',\n decisionCode: DecisionCode.MISSING_REQUIRED_SUBMISSION_FILE_ERROR,\n stderr: `main file not found${params.language ? `: language: ${params.language}` : ''}`,\n });\n return;\n }\n\n const languageDefinition = findLanguageDefinitionByPath(originalMainFilePath);\n if (!languageDefinition) {\n printTestCaseResult({\n testCaseId: testCases[0]?.id ?? 'prebuild',\n decisionCode: DecisionCode.WRONG_ANSWER,\n stderr: 'unsupported language',\n });\n return;\n }\n\n // `CI` changes affects Chainlit. `FORCE_COLOR` affects Bun.\n const env = { ...process.env, CI: '', FORCE_COLOR: '0' };\n\n let prebuiltMainFilePath: string | undefined;\n\n if (languageDefinition.prebuild) {\n try {\n await languageDefinition.prebuild(args.cwd);\n prebuiltMainFilePath = await findEntryPointFile(args.cwd, params.language);\n } catch (error) {\n console.error('prebuild error', error);\n\n printTestCaseResult({\n testCaseId: testCases[0]?.id ?? 'prebuild',\n decisionCode: DecisionCode.BUILD_ERROR,\n stderr: error instanceof Error ? error.message : String(error),\n });\n return;\n }\n }\n\n const mainFilePath = prebuiltMainFilePath ?? originalMainFilePath;\n\n if (languageDefinition.buildCommand) {\n try {\n const buildCommand = languageDefinition.buildCommand(mainFilePath);\n\n const buildSpawnResult = spawnSyncWithTimeout(\n buildCommand[0],\n buildCommand.slice(1),\n { cwd: args.cwd, encoding: 'utf8', env },\n BUILD_TIMEOUT_SECONDS\n );\n\n let decisionCode: DecisionCode = DecisionCode.ACCEPTED;\n\n if (buildSpawnResult.status !== 0) {\n decisionCode = DecisionCode.BUILD_ERROR;\n } else if (buildSpawnResult.timeSeconds > BUILD_TIMEOUT_SECONDS) {\n decisionCode = DecisionCode.BUILD_TIME_LIMIT_EXCEEDED;\n } else if (\n buildSpawnResult.stdout.length > MAX_STDOUT_LENGTH ||\n buildSpawnResult.stderr.length > MAX_STDOUT_LENGTH\n ) {\n decisionCode = DecisionCode.BUILD_OUTPUT_SIZE_LIMIT_EXCEEDED;\n }\n\n if (decisionCode !== DecisionCode.ACCEPTED) {\n printTestCaseResult({\n testCaseId: testCases[0]?.id ?? 'build',\n decisionCode,\n exitStatus: buildSpawnResult.status ?? undefined,\n stdout: buildSpawnResult.stdout.slice(0, MAX_STDOUT_LENGTH) || undefined,\n stderr: buildSpawnResult.stderr.slice(0, MAX_STDOUT_LENGTH) || undefined,\n timeSeconds: buildSpawnResult.timeSeconds,\n memoryBytes: buildSpawnResult.memoryBytes,\n });\n return;\n }\n } catch (error) {\n console.error('build error', error);\n\n printTestCaseResult({\n testCaseId: testCases[0]?.id ?? 'build',\n decisionCode: DecisionCode.BUILD_ERROR,\n stderr: error instanceof Error ? error.message : String(error),\n });\n return;\n }\n }\n\n const cwdSnapshot = await snapshotWorkingDirectory(args.cwd);\n\n if (testCases.length === 0) {\n printTestCaseResult({ testCaseId: 'default', decisionCode: DecisionCode.ACCEPTED });\n }\n\n for (const testCase of testCases) {\n // prepare test case\n if (testCases.shared?.fileInputPath) await copyTestCaseFileInput(testCases.shared.fileInputPath, args.cwd);\n if (testCase.fileInputPath) await copyTestCaseFileInput(testCase.fileInputPath, args.cwd);\n\n // run\n const timeoutSeconds =\n typeof problemMarkdownFrontMatter.timeLimitMs === 'number'\n ? problemMarkdownFrontMatter.timeLimitMs / 1000\n : JUDGE_DEFAULT_TIMEOUT_SECONDS;\n\n const command = languageDefinition.command(mainFilePath);\n\n const spawnResult = spawnSyncWithTimeout(\n command[0],\n command.slice(1),\n { cwd: args.cwd, encoding: 'utf8', input: testCase.input, env },\n timeoutSeconds\n );\n\n let outputFiles = await readOutputFiles(args.cwd, problemMarkdownFrontMatter.requiredOutputFilePaths ?? []);\n\n // calculate decision\n let decisionCode: DecisionCode = DecisionCode.ACCEPTED;\n let judgementError: string | undefined;\n\n if (spawnResult.status !== 0) {\n decisionCode = DecisionCode.RUNTIME_ERROR;\n } else if (spawnResult.timeSeconds > timeoutSeconds) {\n decisionCode = DecisionCode.TIME_LIMIT_EXCEEDED;\n } else if (spawnResult.memoryBytes > (problemMarkdownFrontMatter.memoryLimitByte ?? Number.POSITIVE_INFINITY)) {\n decisionCode = DecisionCode.MEMORY_LIMIT_EXCEEDED;\n } else if (spawnResult.stdout.length > MAX_STDOUT_LENGTH || spawnResult.stderr.length > MAX_STDOUT_LENGTH) {\n decisionCode = DecisionCode.OUTPUT_SIZE_LIMIT_EXCEEDED;\n } else if (outputFiles.length < (problemMarkdownFrontMatter.requiredOutputFilePaths?.length ?? 0)) {\n decisionCode = DecisionCode.MISSING_REQUIRED_OUTPUT_FILE_ERROR;\n } else {\n try {\n const judgement = await judgeAgainstExpectations({\n stdout: spawnResult.stdout,\n expectedStdout: testCase.output,\n fileOutputPath: testCase.fileOutputPath,\n cwd: args.cwd,\n outputFiles,\n });\n if (!judgement.matches) decisionCode = DecisionCode.WRONG_ANSWER;\n outputFiles = judgement.outputFiles;\n } catch (error) {\n // An authoring error (e.g. an oversized expected file) is reported per case, like the command preset does.\n decisionCode = DecisionCode.RUNTIME_ERROR;\n judgementError = error instanceof Error ? error.message : String(error);\n }\n }\n\n printTestCaseResult({\n testCaseId: testCase.id,\n decisionCode,\n exitStatus: spawnResult.status ?? undefined,\n stdin: testCase.input,\n stdout: spawnResult.stdout.slice(0, MAX_STDOUT_LENGTH) || undefined,\n stderr: (judgementError ?? spawnResult.stderr).slice(0, MAX_STDOUT_LENGTH) || undefined,\n timeSeconds: spawnResult.timeSeconds,\n memoryBytes: spawnResult.memoryBytes,\n outputFiles: outputFiles.length > 0 ? outputFiles : undefined,\n });\n\n // clean up\n await cleanWorkingDirectory(args.cwd, cwdSnapshot);\n\n if (decisionCode !== DecisionCode.ACCEPTED) break;\n }\n}\n\nasync function hasExpectedFiles(fileOutputPath: string | undefined): Promise<boolean> {\n if (fileOutputPath === undefined) return false;\n const dirents = await fs.promises.readdir(fileOutputPath, { withFileTypes: true, recursive: true });\n return dirents.some((dirent) => dirent.isFile());\n}\n\n/**\n * A preset debug function using stdin and stdout as test cases. The answer directory is copied to a\n * temporary directory where it is built and run together with `_shared.fin/` and the first example\n * case's `.fin/`; files the program writes are reported only through `requiredOutputFilePaths`.\n *\n * A standard stdio problem must NOT commit a `debug.ts` that only calls this preset: the Exercode\n * server applies this preset automatically when `debug.ts` is absent, and committed copies would\n * drift from the server's defaults.\n *\n * @example\n * Run in a problem directory without `debug.ts`:\n * ```bash\n * bun x exercode-problem debug model_answers/java '{ \"stdin\": \"1 2\" }'\n * ```\n */\nexport async function stdioDebugPreset(problemDir: string): Promise<void> {\n const args = parseArgs(process.argv);\n if (!args.cwd) throw new Error('cwd argument required');\n const params = debugParamsSchema.parse(args.params);\n\n // Everything (build, input files, run) happens in a disposable copy of the answer directory, so\n // the developer's files are never touched and whatever the submission leaves behind goes with it.\n // The copy keeps the directory hierarchy and links every ancestor `node_modules`, so packages the\n // answer resolves from its parents still resolve.\n // A terminated debug run must not leave the copy behind; `finally` does not run on a signal, so\n // the handlers learn the root as soon as it is created and stay until it is removed.\n let tempRoot: string | undefined;\n const removeOnSignal = (): void => {\n if (tempRoot !== undefined) forciblyRemoveDirectorySync(tempRoot);\n process.exit(1);\n };\n process.once('SIGINT', removeOnSignal);\n process.once('SIGTERM', removeOnSignal);\n try {\n const copy = await copyProblemDirToTemporaryRoot(args.cwd, { onTempRootCreated: (root) => (tempRoot = root) });\n // `mkdtemp` creates a 0700 root: the sandbox user only traverses the root and the synthesized\n // ancestors, and gets full access to the copied answer directory alone.\n makeTraversableBySandboxUser(copy.tempRoot, path.dirname(copy.copiedProblemDir));\n await debugInTemporaryCopy(problemDir, copy.copiedProblemDir, params);\n } finally {\n if (tempRoot !== undefined) await forciblyRemoveDirectory(tempRoot);\n process.off('SIGINT', removeOnSignal);\n process.off('SIGTERM', removeOnSignal);\n }\n}\n\nasync function debugInTemporaryCopy(problemDir: string, cwd: string, params: DebugParams): Promise<void> {\n // The sandboxed submission must read its sources and write build/run outputs in its directory.\n makeAccessibleToSandboxUser(cwd);\n\n const problemMarkdownFrontMatter = await readProblemMarkdownFrontMatter(problemDir);\n\n const originalMainFilePath = await findEntryPointFile(cwd, params.language);\n if (!originalMainFilePath) {\n printTestCaseResult({\n testCaseId: 'prebuild',\n decisionCode: DecisionCode.MISSING_REQUIRED_SUBMISSION_FILE_ERROR,\n stderr: `main file not found${params.language ? `: language: ${params.language}` : ''}`,\n });\n return;\n }\n\n const languageDefinition = findLanguageDefinitionByPath(originalMainFilePath);\n if (!languageDefinition) {\n printTestCaseResult({\n testCaseId: 'prebuild',\n decisionCode: DecisionCode.WRONG_ANSWER,\n stderr: 'unsupported language',\n });\n return;\n }\n\n // `CI` changes affects Chainlit. `FORCE_COLOR` affects Bun.\n const env = { ...process.env, CI: '', FORCE_COLOR: '0' };\n\n let prebuiltMainFilePath: string | undefined;\n\n if (languageDefinition.prebuild) {\n try {\n await languageDefinition.prebuild(cwd);\n prebuiltMainFilePath = await findEntryPointFile(cwd, params.language);\n } catch (error) {\n console.error('prebuild error', error);\n\n printTestCaseResult({\n testCaseId: 'prebuild',\n decisionCode: DecisionCode.BUILD_ERROR,\n stderr: error instanceof Error ? error.message : String(error),\n });\n return;\n }\n }\n\n const mainFilePath = prebuiltMainFilePath ?? originalMainFilePath;\n\n if (languageDefinition.buildCommand) {\n try {\n const buildCommand = languageDefinition.buildCommand(mainFilePath);\n\n const buildSpawnResult = spawnSyncWithTimeout(\n buildCommand[0],\n buildCommand.slice(1),\n { cwd, encoding: 'utf8', env },\n BUILD_TIMEOUT_SECONDS\n );\n\n let decisionCode: DecisionCode = DecisionCode.ACCEPTED;\n\n if (buildSpawnResult.status !== 0) {\n decisionCode = DecisionCode.BUILD_ERROR;\n } else if (buildSpawnResult.timeSeconds > BUILD_TIMEOUT_SECONDS) {\n decisionCode = DecisionCode.BUILD_TIME_LIMIT_EXCEEDED;\n } else if (\n buildSpawnResult.stdout.length > MAX_STDOUT_LENGTH ||\n buildSpawnResult.stderr.length > MAX_STDOUT_LENGTH\n ) {\n decisionCode = DecisionCode.BUILD_OUTPUT_SIZE_LIMIT_EXCEEDED;\n }\n\n if (decisionCode !== DecisionCode.ACCEPTED) {\n printTestCaseResult({\n testCaseId: 'build',\n decisionCode,\n exitStatus: buildSpawnResult.status ?? undefined,\n stdout: buildSpawnResult.stdout.slice(0, MAX_STDOUT_LENGTH) || undefined,\n stderr: buildSpawnResult.stderr.slice(0, MAX_STDOUT_LENGTH) || undefined,\n timeSeconds: buildSpawnResult.timeSeconds,\n memoryBytes: buildSpawnResult.memoryBytes,\n });\n return;\n }\n } catch (error) {\n console.error('build error', error);\n\n printTestCaseResult({\n testCaseId: 'build',\n decisionCode: DecisionCode.BUILD_ERROR,\n stderr: error instanceof Error ? error.message : String(error),\n });\n return;\n }\n }\n\n // A debug run has no test case of its own: it gets the shared input files and the `.fin/` of the\n // first example case (the cases a learner may see), placed the way the judge does. Hidden cases'\n // input files are never handed to learner code.\n const testCases = await readTestCases(path.join(problemDir, 'test_cases'));\n if (testCases.shared?.fileInputPath) await copyTestCaseFileInput(testCases.shared.fileInputPath, cwd);\n const exampleFileInputPath = testCases.find(\n (testCase) => EXAMPLE_TEST_CASE_ID_PATTERN.test(testCase.id) && testCase.fileInputPath\n )?.fileInputPath;\n if (exampleFileInputPath) {\n await copyTestCaseFileInput(exampleFileInputPath, cwd);\n } else if (testCases.some((testCase) => testCase.fileInputPath)) {\n console.error('debug: no example test case has input files, so the program runs without them');\n }\n\n const timeoutSeconds = Math.max(DEBUG_DEFAULT_TIMEOUT_SECONDS, (problemMarkdownFrontMatter.timeLimitMs ?? 0) / 1000);\n\n const command = languageDefinition.command(mainFilePath);\n\n const spawnResult = spawnSyncWithTimeout(\n command[0],\n command.slice(1),\n { cwd, encoding: 'utf8', input: params.stdin, env },\n timeoutSeconds\n );\n\n const outputFiles = await readOutputFiles(cwd, problemMarkdownFrontMatter.requiredOutputFilePaths ?? []);\n\n let decisionCode: DecisionCode = DecisionCode.ACCEPTED;\n\n if (spawnResult.status !== 0) {\n decisionCode = DecisionCode.RUNTIME_ERROR;\n } else if (spawnResult.timeSeconds > timeoutSeconds) {\n decisionCode = DecisionCode.TIME_LIMIT_EXCEEDED;\n } else if (spawnResult.memoryBytes > (problemMarkdownFrontMatter.memoryLimitByte ?? Number.POSITIVE_INFINITY)) {\n decisionCode = DecisionCode.MEMORY_LIMIT_EXCEEDED;\n } else if (spawnResult.stdout.length > MAX_STDOUT_LENGTH || spawnResult.stderr.length > MAX_STDOUT_LENGTH) {\n decisionCode = DecisionCode.OUTPUT_SIZE_LIMIT_EXCEEDED;\n } else if (outputFiles.length < (problemMarkdownFrontMatter.requiredOutputFilePaths?.length ?? 0)) {\n decisionCode = DecisionCode.MISSING_REQUIRED_OUTPUT_FILE_ERROR;\n }\n\n printTestCaseResult({\n testCaseId: 'debug',\n decisionCode,\n exitStatus: spawnResult.status ?? undefined,\n stdin: params.stdin,\n stdout: spawnResult.stdout.slice(0, MAX_STDOUT_LENGTH) || undefined,\n stderr: spawnResult.stderr.slice(0, MAX_STDOUT_LENGTH) || undefined,\n timeSeconds: spawnResult.timeSeconds,\n memoryBytes: spawnResult.memoryBytes,\n outputFiles: outputFiles.length > 0 ? outputFiles : undefined,\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"stdio.js","names":[],"sources":["../../src/presets/stdio.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { z } from 'zod';\n\nimport { cleanWorkingDirectory, snapshotWorkingDirectory } from '../helpers/cleanWorkingDirectory.js';\nimport { judgeAgainstExpectations } from '../helpers/compareExpectedOutputFiles.js';\nimport { copyTestCaseFileInput } from '../helpers/copyTestCaseFileInput.js';\nimport { findEntryPointFile } from '../helpers/findEntryPointFile.js';\nimport { findLanguageDefinitionByPath } from '../helpers/findLanguageDefinitionByPath.js';\nimport { judgeByStaticAnalysis } from '../helpers/judgeByStaticAnalysis.js';\nimport { parseArgs } from '../helpers/parseArgs.js';\nimport { printTestCaseResult } from '../helpers/printTestCaseResult.js';\nimport { readOutputFiles } from '../helpers/readOutputFiles.js';\nimport {\n judgesTestCasesWithoutExpectations,\n readProblemMarkdownFrontMatter,\n} from '../helpers/readProblemMarkdownFrontMatter.js';\nimport { readTestCases } from '../helpers/readTestCases.js';\nimport { spawnSyncWithTimeout } from '../helpers/spawnSyncWithTimeout.js';\nimport {\n copyProblemDirToTemporaryRoot,\n forciblyRemoveDirectory,\n forciblyRemoveDirectorySync,\n} from '../helpers/temporaryProblemDirCopy.js';\nimport { DecisionCode } from '../types/decisionCode.js';\n\nconst BUILD_TIMEOUT_SECONDS = 10;\nconst JUDGE_DEFAULT_TIMEOUT_SECONDS = 2;\nconst DEBUG_DEFAULT_TIMEOUT_SECONDS = 10;\n\nconst MAX_STDOUT_LENGTH = 50_000;\n// The judge server shows a test case on the problem page when its id contains `example`.\nconst EXAMPLE_TEST_CASE_ID_PATTERN = /example/;\n\nconst judgeParamsSchema = z.object({\n language: z.union([z.string(), z.array(z.string())]).optional(),\n});\n\nconst debugParamsSchema = judgeParamsSchema.extend({\n stdin: z.string().optional(),\n});\n\ntype DebugParams = z.infer<typeof debugParamsSchema>;\n\n/**\n * A preset judge function using stdin and stdout as test cases.\n *\n * A standard stdio problem must NOT commit a `judge.ts` that only calls this preset: the Exercode\n * server applies this preset automatically when `judge.ts` is absent, and committed copies would\n * drift from the server's defaults.\n *\n * @example\n * Run in a problem directory without `judge.ts`:\n * ```bash\n * bun x exercode-problem judge model_answers/java\n * ```\n *\n * Run with the optional parameters:\n * ```bash\n * bun x exercode-problem judge model_answers/java '{ \"language\": \"javascript\" }'\n * ```\n */\nexport async function stdioJudgePreset(problemDir: string): Promise<void> {\n const args = parseArgs(process.argv);\n if (!args.cwd) throw new Error('cwd argument required');\n const params = judgeParamsSchema.parse(args.params);\n\n const problemMarkdownFrontMatter = await readProblemMarkdownFrontMatter(problemDir);\n const testCases = await readTestCases(path.join(problemDir, 'test_cases'));\n\n // Without an expectation, a case would accept any run; only custom harnesses may judge input-only\n // cases. A problem judged by manual scoring or the presence of required output files has an\n // expectation of its own. Checked before the static analysis so that an authoring error surfaces\n // regardless of the submission.\n if (!judgesTestCasesWithoutExpectations(problemMarkdownFrontMatter)) {\n for (const testCase of testCases) {\n if (testCase.output === undefined && !(await hasExpectedFiles(testCase.fileOutputPath))) {\n throw new Error(\n `test case ${testCase.id} needs an expected output (${testCase.id}.out or a non-empty ${testCase.id}.fout/)`\n );\n }\n }\n }\n\n const staticAnalysisTestCaseResult = await judgeByStaticAnalysis(args.cwd, problemMarkdownFrontMatter);\n if (staticAnalysisTestCaseResult) {\n printTestCaseResult({ testCaseId: testCases[0]?.id ?? 'prebuild', ...staticAnalysisTestCaseResult });\n return;\n }\n\n const originalMainFilePath = await findEntryPointFile(args.cwd, params.language);\n if (!originalMainFilePath) {\n printTestCaseResult({\n testCaseId: testCases[0]?.id ?? 'prebuild',\n decisionCode: DecisionCode.MISSING_REQUIRED_SUBMISSION_FILE_ERROR,\n stderr: `main file not found${params.language ? `: language: ${params.language}` : ''}`,\n });\n return;\n }\n\n const languageDefinition = findLanguageDefinitionByPath(originalMainFilePath);\n if (!languageDefinition) {\n printTestCaseResult({\n testCaseId: testCases[0]?.id ?? 'prebuild',\n decisionCode: DecisionCode.WRONG_ANSWER,\n stderr: 'unsupported language',\n });\n return;\n }\n\n // `CI` changes affects Chainlit. `FORCE_COLOR` affects Bun.\n const env = { ...process.env, CI: '', FORCE_COLOR: '0' };\n\n let prebuiltMainFilePath: string | undefined;\n\n if (languageDefinition.prebuild) {\n try {\n await languageDefinition.prebuild(args.cwd);\n prebuiltMainFilePath = await findEntryPointFile(args.cwd, params.language);\n } catch (error) {\n console.error('prebuild error', error);\n\n printTestCaseResult({\n testCaseId: testCases[0]?.id ?? 'prebuild',\n decisionCode: DecisionCode.BUILD_ERROR,\n stderr: error instanceof Error ? error.message : String(error),\n });\n return;\n }\n }\n\n const mainFilePath = prebuiltMainFilePath ?? originalMainFilePath;\n\n if (languageDefinition.buildCommand) {\n try {\n const buildCommand = languageDefinition.buildCommand(mainFilePath);\n\n const buildSpawnResult = spawnSyncWithTimeout(\n buildCommand[0],\n buildCommand.slice(1),\n { cwd: args.cwd, encoding: 'utf8', env },\n BUILD_TIMEOUT_SECONDS\n );\n\n let decisionCode: DecisionCode = DecisionCode.ACCEPTED;\n\n if (buildSpawnResult.status !== 0) {\n decisionCode = DecisionCode.BUILD_ERROR;\n } else if (buildSpawnResult.timeSeconds > BUILD_TIMEOUT_SECONDS) {\n decisionCode = DecisionCode.BUILD_TIME_LIMIT_EXCEEDED;\n } else if (\n buildSpawnResult.stdout.length > MAX_STDOUT_LENGTH ||\n buildSpawnResult.stderr.length > MAX_STDOUT_LENGTH\n ) {\n decisionCode = DecisionCode.BUILD_OUTPUT_SIZE_LIMIT_EXCEEDED;\n }\n\n if (decisionCode !== DecisionCode.ACCEPTED) {\n printTestCaseResult({\n testCaseId: testCases[0]?.id ?? 'build',\n decisionCode,\n exitStatus: buildSpawnResult.status ?? undefined,\n stdout: buildSpawnResult.stdout.slice(0, MAX_STDOUT_LENGTH) || undefined,\n stderr: buildSpawnResult.stderr.slice(0, MAX_STDOUT_LENGTH) || undefined,\n timeSeconds: buildSpawnResult.timeSeconds,\n memoryBytes: buildSpawnResult.memoryBytes,\n });\n return;\n }\n } catch (error) {\n console.error('build error', error);\n\n printTestCaseResult({\n testCaseId: testCases[0]?.id ?? 'build',\n decisionCode: DecisionCode.BUILD_ERROR,\n stderr: error instanceof Error ? error.message : String(error),\n });\n return;\n }\n }\n\n const cwdSnapshot = await snapshotWorkingDirectory(args.cwd);\n\n if (testCases.length === 0) {\n printTestCaseResult({ testCaseId: 'default', decisionCode: DecisionCode.ACCEPTED });\n }\n\n for (const testCase of testCases) {\n // prepare test case\n if (testCases.shared?.fileInputPath) await copyTestCaseFileInput(testCases.shared.fileInputPath, args.cwd);\n if (testCase.fileInputPath) await copyTestCaseFileInput(testCase.fileInputPath, args.cwd);\n\n // run\n const timeoutSeconds =\n typeof problemMarkdownFrontMatter.timeLimitMs === 'number'\n ? problemMarkdownFrontMatter.timeLimitMs / 1000\n : JUDGE_DEFAULT_TIMEOUT_SECONDS;\n\n const command = languageDefinition.command(mainFilePath);\n\n const spawnResult = spawnSyncWithTimeout(\n command[0],\n command.slice(1),\n { cwd: args.cwd, encoding: 'utf8', input: testCase.input, env },\n timeoutSeconds\n );\n\n let outputFiles = await readOutputFiles(args.cwd, problemMarkdownFrontMatter.requiredOutputFilePaths ?? []);\n\n // calculate decision\n let decisionCode: DecisionCode = DecisionCode.ACCEPTED;\n let judgementError: string | undefined;\n\n if (spawnResult.status !== 0) {\n decisionCode = DecisionCode.RUNTIME_ERROR;\n } else if (spawnResult.timeSeconds > timeoutSeconds) {\n decisionCode = DecisionCode.TIME_LIMIT_EXCEEDED;\n } else if (spawnResult.memoryBytes > (problemMarkdownFrontMatter.memoryLimitByte ?? Number.POSITIVE_INFINITY)) {\n decisionCode = DecisionCode.MEMORY_LIMIT_EXCEEDED;\n } else if (spawnResult.stdout.length > MAX_STDOUT_LENGTH || spawnResult.stderr.length > MAX_STDOUT_LENGTH) {\n decisionCode = DecisionCode.OUTPUT_SIZE_LIMIT_EXCEEDED;\n } else if (outputFiles.length < (problemMarkdownFrontMatter.requiredOutputFilePaths?.length ?? 0)) {\n decisionCode = DecisionCode.MISSING_REQUIRED_OUTPUT_FILE_ERROR;\n } else {\n try {\n const judgement = await judgeAgainstExpectations({\n stdout: spawnResult.stdout,\n expectedStdout: testCase.output,\n fileOutputPath: testCase.fileOutputPath,\n cwd: args.cwd,\n outputFiles,\n });\n if (!judgement.matches) decisionCode = DecisionCode.WRONG_ANSWER;\n outputFiles = judgement.outputFiles;\n } catch (error) {\n // An authoring error (e.g. an oversized expected file) is reported per case, like the command preset does.\n decisionCode = DecisionCode.RUNTIME_ERROR;\n judgementError = error instanceof Error ? error.message : String(error);\n }\n }\n\n printTestCaseResult({\n testCaseId: testCase.id,\n decisionCode,\n exitStatus: spawnResult.status ?? undefined,\n stdin: testCase.input,\n stdout: spawnResult.stdout.slice(0, MAX_STDOUT_LENGTH) || undefined,\n stderr: (judgementError ?? spawnResult.stderr).slice(0, MAX_STDOUT_LENGTH) || undefined,\n timeSeconds: spawnResult.timeSeconds,\n memoryBytes: spawnResult.memoryBytes,\n outputFiles: outputFiles.length > 0 ? outputFiles : undefined,\n });\n\n // clean up\n await cleanWorkingDirectory(args.cwd, cwdSnapshot);\n\n if (decisionCode !== DecisionCode.ACCEPTED) break;\n }\n}\n\nasync function hasExpectedFiles(fileOutputPath: string | undefined): Promise<boolean> {\n if (fileOutputPath === undefined) return false;\n const dirents = await fs.promises.readdir(fileOutputPath, { withFileTypes: true, recursive: true });\n return dirents.some((dirent) => dirent.isFile());\n}\n\n/**\n * A preset debug function using stdin and stdout as test cases. The answer directory is copied to a\n * temporary directory where it is built and run together with `_shared.fin/` and the first example\n * case's `.fin/`; files the program writes are reported only through `requiredOutputFilePaths`.\n *\n * A standard stdio problem must NOT commit a `debug.ts` that only calls this preset: the Exercode\n * server applies this preset automatically when `debug.ts` is absent, and committed copies would\n * drift from the server's defaults.\n *\n * @example\n * Run in a problem directory without `debug.ts`:\n * ```bash\n * bun x exercode-problem debug model_answers/java '{ \"stdin\": \"1 2\" }'\n * ```\n */\nexport async function stdioDebugPreset(problemDir: string): Promise<void> {\n const args = parseArgs(process.argv);\n if (!args.cwd) throw new Error('cwd argument required');\n const params = debugParamsSchema.parse(args.params);\n\n // Everything (build, input files, run) happens in a disposable copy of the answer directory, so\n // the developer's files are never touched and whatever the submission leaves behind goes with it.\n // The copy keeps the directory hierarchy and links every ancestor `node_modules`, so packages the\n // answer resolves from its parents still resolve.\n // A terminated debug run must not leave the copy behind; `finally` does not run on a signal, so\n // the handlers learn the root as soon as it is created and stay until it is removed.\n let tempRoot: string | undefined;\n const removeOnSignal = (): void => {\n if (tempRoot !== undefined) forciblyRemoveDirectorySync(tempRoot);\n process.exit(1);\n };\n process.once('SIGINT', removeOnSignal);\n process.once('SIGTERM', removeOnSignal);\n try {\n const copy = await copyProblemDirToTemporaryRoot(args.cwd, { onTempRootCreated: (root) => (tempRoot = root) });\n await debugInTemporaryCopy(problemDir, copy.copiedProblemDir, params);\n } finally {\n if (tempRoot !== undefined) await forciblyRemoveDirectory(tempRoot);\n process.off('SIGINT', removeOnSignal);\n process.off('SIGTERM', removeOnSignal);\n }\n}\n\nasync function debugInTemporaryCopy(problemDir: string, cwd: string, params: DebugParams): Promise<void> {\n const problemMarkdownFrontMatter = await readProblemMarkdownFrontMatter(problemDir);\n\n const originalMainFilePath = await findEntryPointFile(cwd, params.language);\n if (!originalMainFilePath) {\n printTestCaseResult({\n testCaseId: 'prebuild',\n decisionCode: DecisionCode.MISSING_REQUIRED_SUBMISSION_FILE_ERROR,\n stderr: `main file not found${params.language ? `: language: ${params.language}` : ''}`,\n });\n return;\n }\n\n const languageDefinition = findLanguageDefinitionByPath(originalMainFilePath);\n if (!languageDefinition) {\n printTestCaseResult({\n testCaseId: 'prebuild',\n decisionCode: DecisionCode.WRONG_ANSWER,\n stderr: 'unsupported language',\n });\n return;\n }\n\n // `CI` changes affects Chainlit. `FORCE_COLOR` affects Bun.\n const env = { ...process.env, CI: '', FORCE_COLOR: '0' };\n\n let prebuiltMainFilePath: string | undefined;\n\n if (languageDefinition.prebuild) {\n try {\n await languageDefinition.prebuild(cwd);\n prebuiltMainFilePath = await findEntryPointFile(cwd, params.language);\n } catch (error) {\n console.error('prebuild error', error);\n\n printTestCaseResult({\n testCaseId: 'prebuild',\n decisionCode: DecisionCode.BUILD_ERROR,\n stderr: error instanceof Error ? error.message : String(error),\n });\n return;\n }\n }\n\n const mainFilePath = prebuiltMainFilePath ?? originalMainFilePath;\n\n if (languageDefinition.buildCommand) {\n try {\n const buildCommand = languageDefinition.buildCommand(mainFilePath);\n\n const buildSpawnResult = spawnSyncWithTimeout(\n buildCommand[0],\n buildCommand.slice(1),\n { cwd, encoding: 'utf8', env },\n BUILD_TIMEOUT_SECONDS\n );\n\n let decisionCode: DecisionCode = DecisionCode.ACCEPTED;\n\n if (buildSpawnResult.status !== 0) {\n decisionCode = DecisionCode.BUILD_ERROR;\n } else if (buildSpawnResult.timeSeconds > BUILD_TIMEOUT_SECONDS) {\n decisionCode = DecisionCode.BUILD_TIME_LIMIT_EXCEEDED;\n } else if (\n buildSpawnResult.stdout.length > MAX_STDOUT_LENGTH ||\n buildSpawnResult.stderr.length > MAX_STDOUT_LENGTH\n ) {\n decisionCode = DecisionCode.BUILD_OUTPUT_SIZE_LIMIT_EXCEEDED;\n }\n\n if (decisionCode !== DecisionCode.ACCEPTED) {\n printTestCaseResult({\n testCaseId: 'build',\n decisionCode,\n exitStatus: buildSpawnResult.status ?? undefined,\n stdout: buildSpawnResult.stdout.slice(0, MAX_STDOUT_LENGTH) || undefined,\n stderr: buildSpawnResult.stderr.slice(0, MAX_STDOUT_LENGTH) || undefined,\n timeSeconds: buildSpawnResult.timeSeconds,\n memoryBytes: buildSpawnResult.memoryBytes,\n });\n return;\n }\n } catch (error) {\n console.error('build error', error);\n\n printTestCaseResult({\n testCaseId: 'build',\n decisionCode: DecisionCode.BUILD_ERROR,\n stderr: error instanceof Error ? error.message : String(error),\n });\n return;\n }\n }\n\n // A debug run has no test case of its own: it gets the shared input files and the `.fin/` of the\n // first example case (the cases a learner may see), placed the way the judge does. Hidden cases'\n // input files are never handed to learner code.\n const testCases = await readTestCases(path.join(problemDir, 'test_cases'));\n if (testCases.shared?.fileInputPath) await copyTestCaseFileInput(testCases.shared.fileInputPath, cwd);\n const exampleFileInputPath = testCases.find(\n (testCase) => EXAMPLE_TEST_CASE_ID_PATTERN.test(testCase.id) && testCase.fileInputPath\n )?.fileInputPath;\n if (exampleFileInputPath) {\n await copyTestCaseFileInput(exampleFileInputPath, cwd);\n } else if (testCases.some((testCase) => testCase.fileInputPath)) {\n console.error('debug: no example test case has input files, so the program runs without them');\n }\n\n const timeoutSeconds = Math.max(DEBUG_DEFAULT_TIMEOUT_SECONDS, (problemMarkdownFrontMatter.timeLimitMs ?? 0) / 1000);\n\n const command = languageDefinition.command(mainFilePath);\n\n const spawnResult = spawnSyncWithTimeout(\n command[0],\n command.slice(1),\n { cwd, encoding: 'utf8', input: params.stdin, env },\n timeoutSeconds\n );\n\n const outputFiles = await readOutputFiles(cwd, problemMarkdownFrontMatter.requiredOutputFilePaths ?? []);\n\n let decisionCode: DecisionCode = DecisionCode.ACCEPTED;\n\n if (spawnResult.status !== 0) {\n decisionCode = DecisionCode.RUNTIME_ERROR;\n } else if (spawnResult.timeSeconds > timeoutSeconds) {\n decisionCode = DecisionCode.TIME_LIMIT_EXCEEDED;\n } else if (spawnResult.memoryBytes > (problemMarkdownFrontMatter.memoryLimitByte ?? Number.POSITIVE_INFINITY)) {\n decisionCode = DecisionCode.MEMORY_LIMIT_EXCEEDED;\n } else if (spawnResult.stdout.length > MAX_STDOUT_LENGTH || spawnResult.stderr.length > MAX_STDOUT_LENGTH) {\n decisionCode = DecisionCode.OUTPUT_SIZE_LIMIT_EXCEEDED;\n } else if (outputFiles.length < (problemMarkdownFrontMatter.requiredOutputFilePaths?.length ?? 0)) {\n decisionCode = DecisionCode.MISSING_REQUIRED_OUTPUT_FILE_ERROR;\n }\n\n printTestCaseResult({\n testCaseId: 'debug',\n decisionCode,\n exitStatus: spawnResult.status ?? undefined,\n stdin: params.stdin,\n stdout: spawnResult.stdout.slice(0, MAX_STDOUT_LENGTH) || undefined,\n stderr: spawnResult.stderr.slice(0, MAX_STDOUT_LENGTH) || undefined,\n timeSeconds: spawnResult.timeSeconds,\n memoryBytes: spawnResult.memoryBytes,\n outputFiles: outputFiles.length > 0 ? outputFiles : undefined,\n });\n}\n"],"mappings":"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"}
|
package/dist/types/language.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("../helpers/
|
|
1
|
+
"use strict";const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("../helpers/removeCommentsInSourceCode.cjs");let n=require("node:fs");n=e.__toESM(n,1);let r=require("node:path");r=e.__toESM(r,1);const i={strings:[{open:/'/,close:/(?<!\\)(?:\\{2})*'/},{open:/"/,close:/(?<!\\)(?:\\{2})*"/}],comments:[{open:/\n?[ \t]*\/\*/,close:/\*\//},{open:/\n?[ \t]*\/\//}]},a={strings:[{open:/'/,close:/(?<!\\)(?:\\{2})*'/},{open:/"/,close:/(?<!\\)(?:\\{2})*"/},{open:/`/,close:/(?<!\\)(?:\\{2})*`/}],comments:[{open:/\n?[ \t]*\/\*/,close:/\*\//},{open:/\n?[ \t]*\/\//}]},o={c:{fileExtensions:[`.c`],buildCommand:e=>[`gcc`,`--std=c17`,`-O2`,e,`-o`,`main`],command:()=>[`./main`],grammer:i},cpp:{fileExtensions:[`.cpp`],buildCommand:e=>[`g++`,`--std=c++20`,`-O2`,e,`-o`,`main`],command:()=>[`./main`],grammer:i},csharp:{fileExtensions:[`.cs`],prebuild:async e=>{await n.default.promises.writeFile(r.default.join(e,`Main.csproj`),`<Project Sdk="Microsoft.NET.Sdk">
|
|
2
2
|
<PropertyGroup>
|
|
3
3
|
<OutputType>Exe</OutputType>
|
|
4
4
|
<TargetFramework>net8.0</TargetFramework>
|
|
@@ -7,5 +7,5 @@
|
|
|
7
7
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
|
8
8
|
<AssemblyName>Main</AssemblyName>
|
|
9
9
|
</PropertyGroup>
|
|
10
|
-
</Project>`)},buildCommand:()=>[`dotnet`,`build`,`Main.csproj`,`--configuration`,`Release`,`--verbosity`,`quiet`],command:()=>[`dotnet`,`bin/Release/net8.0/Main.dll`],grammer:
|
|
10
|
+
</Project>`)},buildCommand:()=>[`dotnet`,`build`,`Main.csproj`,`--configuration`,`Release`,`--verbosity`,`quiet`],command:()=>[`dotnet`,`bin/Release/net8.0/Main.dll`],grammer:i},css:{fileExtensions:[`.css`],command:()=>[`echo`,``],grammer:{strings:[{open:/'/,close:/(?<!\\)(?:\\{2})*'/},{open:/"/,close:/(?<!\\)(?:\\{2})*"/}],comments:[{open:/\n?[ \t]*\/\*/,close:/\*\//}]}},dart:{fileExtensions:[`.dart`],buildCommand:e=>[`dart`,`compile`,`exe`,e,`-o`,`main`],command:()=>[`./main`],grammer:i},java:{fileExtensions:[`.java`],prebuild:async e=>{let a=/\bpublic\s+class\s+(\w+)\b/;for(let o of await n.default.promises.readdir(e,{withFileTypes:!0})){if(!o.isFile()||!o.name.endsWith(`.java`))continue;let s=await n.default.promises.readFile(r.default.join(e,o.name),`utf8`),[,c]=a.exec(t.removeCommentsInSourceCode(i,s))??[];c&&await n.default.promises.rename(r.default.join(e,o.name),r.default.join(e,`${c}.java`))}},buildCommand:e=>[`javac`,e],command:e=>[`java`,`-Xmx1024m`,e.replace(/\.java$/,``)],grammer:i},kotlin:{fileExtensions:[`.kt`],buildCommand:e=>[`kotlinc`,e,`-include-runtime`,`-d`,`main.jar`],command:()=>[`java`,`-Xmx1024m`,`-jar`,`main.jar`],grammer:{strings:[{open:/"""/,close:/"""/,templateSyntax:`kotlin`},{open:/'/,close:/(?<!\\)(?:\\{2})*'/},{open:/"/,close:/(?<!\\)(?:\\{2})*"/,templateSyntax:`kotlin`}],comments:i.comments}},javascript:{fileExtensions:[`.js`,`.cjs`,`.mjs`],command:e=>[`bun`,e],grammer:a},jsp:{fileExtensions:[`.jsp`],command:()=>[`echo`,``],grammer:{strings:[{open:/'/,close:/(?<!\\)(?:\\{2})*'/},{open:/"/,close:/(?<!\\)(?:\\{2})*"/}],comments:[{open:/\n?[ \t]*<!--/,close:/-->/},{open:/\n?[ \t]*<%--/,close:/--%>/},{open:/\n?[ \t]*\/\*/,close:/\*\//},{open:/\n?[ \t]*\/\//}]}},haskell:{fileExtensions:[`.hs`],buildCommand:e=>[`ghc`,`-o`,`main`,e],command:()=>[`./main`],grammer:{strings:[{open:/'/,close:/(?<!\\)(?:\\{2})*'/},{open:/"/,close:/(?<!\\)(?:\\{2})*"/}],comments:[{open:/\n?[ \t]*\{-/,close:/-\}/},{open:/\n?[ \t]*--/}]}},html:{fileExtensions:[`.html`],command:()=>[`echo`,``],grammer:{strings:[{open:/'/,close:/(?<!\\)(?:\\{2})*'/},{open:/"/,close:/(?<!\\)(?:\\{2})*"/}],comments:[{open:/\n?[ \t]*<!--/,close:/-->/}]}},php:{fileExtensions:[`.php`],command:e=>[`php`,e],grammer:{strings:[{open:/'/,close:/(?<!\\)(?:\\{2})*'/},{open:/"/,close:/(?<!\\)(?:\\{2})*"/}],comments:[{open:/\n?[ \t]*\/\*/,close:/\*\//},{open:/\n?[ \t]*\/\//},{open:/\n?[ \t]*#/}]}},python:{fileExtensions:[`.py`],command:e=>[`python3`,e],grammer:{strings:[{open:/'''/,close:/(?<!\\)(?:\\{2})*'''/},{open:/"""/,close:/(?<!\\)(?:\\{2})*"""/},{open:/'/,close:/(?<!\\)(?:\\{2})*'/},{open:/"/,close:/(?<!\\)(?:\\{2})*"/}],comments:[{open:/\n?[ \t]*'''/,close:/'''/},{open:/\n?[ \t]*"""/,close:/"""/},{open:/\n?[ \t]*#/}]}},ruby:{fileExtensions:[`.rb`],buildCommand:e=>[`ruby`,`-c`,e],command:e=>[`ruby`,`--jit`,e],grammer:{strings:[{open:/'/,close:/(?<!\\)(?:\\{2})*'/},{open:/"/,close:/(?<!\\)(?:\\{2})*"/}],comments:[{open:/\n?[ \t]*=begin/,close:/=end/},{open:/\n?[ \t]*#/}]}},rust:{fileExtensions:[`.rs`],buildCommand:e=>[`rustc`,e,`-o`,`main`],command:()=>[`./main`],grammer:i},zig:{fileExtensions:[`.zig`],buildCommand:e=>[`zig`,`build-exe`,e],command:e=>[`./`+e.replace(/\.zig$/,``)],grammer:i},typescript:{fileExtensions:[`.ts`,`.cts`,`.mts`],command:e=>[`bun`,e],grammer:a},text:{fileExtensions:[`.txt`],command:e=>[`cat`,e]}};exports.languageIdToDefinition=o;
|
|
11
11
|
//# sourceMappingURL=language.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"language.cjs","names":["writeFileWithoutFollowingSymlinks","path","fs","removeCommentsInSourceCode"],"sources":["../../src/types/language.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { removeCommentsInSourceCode } from '../helpers/removeCommentsInSourceCode.js';\nimport { writeFileWithoutFollowingSymlinks } from '../helpers/safeFs.js';\n\nexport interface LanguageDefinition {\n /** File extensions to judge with this config. */\n fileExtensions: readonly string[];\n\n /** Function executed before the build. */\n prebuild?(cwd: string): Promise<void>;\n\n /** Returns the command to build a user program. */\n buildCommand?(filePath: string): [string, ...string[]];\n\n /** Returns the command to run a user program. */\n command(filePath: string): [string, ...string[]];\n\n /** Grammer definition for static analysis. */\n grammer?: {\n strings?: readonly { open: RegExp; close: RegExp; templateSyntax?: 'kotlin' }[];\n comments?: readonly { open: RegExp; close?: RegExp }[];\n };\n}\n\nconst cLikeGrammer = {\n strings: [\n { open: /'/, close: /(?<!\\\\)(?:\\\\{2})*'/ },\n { open: /\"/, close: /(?<!\\\\)(?:\\\\{2})*\"/ },\n ],\n comments: [{ open: /\\n?[ \\t]*\\/\\*/, close: /\\*\\// }, { open: /\\n?[ \\t]*\\/\\// }],\n} as const satisfies LanguageDefinition['grammer'];\n\nconst javaScriptLikeGrammer = {\n strings: [\n { open: /'/, close: /(?<!\\\\)(?:\\\\{2})*'/ },\n { open: /\"/, close: /(?<!\\\\)(?:\\\\{2})*\"/ },\n { open: /`/, close: /(?<!\\\\)(?:\\\\{2})*`/ },\n ],\n comments: [{ open: /\\n?[ \\t]*\\/\\*/, close: /\\*\\// }, { open: /\\n?[ \\t]*\\/\\// }],\n} as const satisfies LanguageDefinition['grammer'];\n\nexport const languageIdToDefinition: Readonly<Record<string, Readonly<LanguageDefinition>>> = {\n c: {\n fileExtensions: ['.c'],\n buildCommand: (filePath) => ['gcc', '--std=c17', '-O2', filePath, '-o', 'main'],\n command: () => ['./main'],\n grammer: cLikeGrammer,\n },\n\n cpp: {\n fileExtensions: ['.cpp'],\n buildCommand: (filePath) => ['g++', '--std=c++20', '-O2', filePath, '-o', 'main'],\n command: () => ['./main'],\n grammer: cLikeGrammer,\n },\n\n csharp: {\n fileExtensions: ['.cs'],\n prebuild: async (cwd) => {\n // No-follow write: the submission owns `cwd` and could point `Main.csproj` at a file only the\n // trusted harness user can write.\n await writeFileWithoutFollowingSymlinks(\n path.join(cwd, 'Main.csproj'),\n `<Project Sdk=\"Microsoft.NET.Sdk\">\n <PropertyGroup>\n <OutputType>Exe</OutputType>\n <TargetFramework>net8.0</TargetFramework>\n <ImplicitUsings>enable</ImplicitUsings>\n <Nullable>enable</Nullable>\n <AllowUnsafeBlocks>true</AllowUnsafeBlocks>\n <AssemblyName>Main</AssemblyName>\n </PropertyGroup>\n</Project>`\n );\n },\n buildCommand: () => ['dotnet', 'build', 'Main.csproj', '--configuration', 'Release', '--verbosity', 'quiet'],\n command: () => ['dotnet', 'bin/Release/net8.0/Main.dll'],\n grammer: cLikeGrammer,\n },\n\n css: {\n fileExtensions: ['.css'],\n command: () => ['echo', ''],\n grammer: {\n strings: [\n { open: /'/, close: /(?<!\\\\)(?:\\\\{2})*'/ },\n { open: /\"/, close: /(?<!\\\\)(?:\\\\{2})*\"/ },\n ],\n comments: [{ open: /\\n?[ \\t]*\\/\\*/, close: /\\*\\// }],\n },\n },\n\n dart: {\n fileExtensions: ['.dart'],\n buildCommand: (filePath) => ['dart', 'compile', 'exe', filePath, '-o', 'main'],\n command: () => ['./main'],\n grammer: cLikeGrammer,\n },\n\n java: {\n fileExtensions: ['.java'],\n prebuild: async (cwd) => {\n const publicClassRegex = /\\bpublic\\s+class\\s+(\\w+)\\b/;\n for (const dirent of await fs.promises.readdir(cwd, { withFileTypes: true })) {\n if (!dirent.isFile() || !dirent.name.endsWith('.java')) continue;\n const data = await fs.promises.readFile(path.join(cwd, dirent.name), 'utf8');\n const [, className] = publicClassRegex.exec(removeCommentsInSourceCode(cLikeGrammer, data)) ?? [];\n if (className) await fs.promises.rename(path.join(cwd, dirent.name), path.join(cwd, `${className}.java`));\n }\n },\n buildCommand: (fileName) => ['javac', fileName],\n // For example, Problem 7-3 in WillBooster's Java lecture uses at least 256MB.\n command: (fileName) => ['java', '-Xmx1024m', fileName.replace(/\\.java$/, '')],\n grammer: cLikeGrammer,\n },\n\n kotlin: {\n fileExtensions: ['.kt'],\n buildCommand: (filePath) => ['kotlinc', filePath, '-include-runtime', '-d', 'main.jar'],\n command: () => ['java', '-Xmx1024m', '-jar', 'main.jar'],\n grammer: {\n strings: [\n { open: /\"\"\"/, close: /\"\"\"/, templateSyntax: 'kotlin' },\n { open: /'/, close: /(?<!\\\\)(?:\\\\{2})*'/ },\n { open: /\"/, close: /(?<!\\\\)(?:\\\\{2})*\"/, templateSyntax: 'kotlin' },\n ],\n comments: cLikeGrammer.comments,\n },\n },\n\n javascript: {\n fileExtensions: ['.js', '.cjs', '.mjs'],\n command: (fileName) => ['bun', fileName],\n grammer: javaScriptLikeGrammer,\n },\n\n jsp: {\n fileExtensions: ['.jsp'],\n command: () => ['echo', ''],\n grammer: {\n strings: [\n { open: /'/, close: /(?<!\\\\)(?:\\\\{2})*'/ },\n { open: /\"/, close: /(?<!\\\\)(?:\\\\{2})*\"/ },\n ],\n comments: [\n { open: /\\n?[ \\t]*<!--/, close: /-->/ },\n { open: /\\n?[ \\t]*<%--/, close: /--%>/ },\n { open: /\\n?[ \\t]*\\/\\*/, close: /\\*\\// },\n { open: /\\n?[ \\t]*\\/\\// },\n ],\n },\n },\n\n haskell: {\n fileExtensions: ['.hs'],\n buildCommand: (filePath) => ['ghc', '-o', 'main', filePath],\n command: () => ['./main'],\n grammer: {\n strings: [\n { open: /'/, close: /(?<!\\\\)(?:\\\\{2})*'/ },\n { open: /\"/, close: /(?<!\\\\)(?:\\\\{2})*\"/ },\n ],\n comments: [{ open: /\\n?[ \\t]*\\{-/, close: /-\\}/ }, { open: /\\n?[ \\t]*--/ }],\n },\n },\n\n html: {\n fileExtensions: ['.html'],\n command: () => ['echo', ''],\n grammer: {\n strings: [\n { open: /'/, close: /(?<!\\\\)(?:\\\\{2})*'/ },\n { open: /\"/, close: /(?<!\\\\)(?:\\\\{2})*\"/ },\n ],\n comments: [{ open: /\\n?[ \\t]*<!--/, close: /-->/ }],\n },\n },\n\n php: {\n fileExtensions: ['.php'],\n command: (fileName) => ['php', fileName],\n grammer: {\n strings: [\n { open: /'/, close: /(?<!\\\\)(?:\\\\{2})*'/ },\n { open: /\"/, close: /(?<!\\\\)(?:\\\\{2})*\"/ },\n ],\n comments: [{ open: /\\n?[ \\t]*\\/\\*/, close: /\\*\\// }, { open: /\\n?[ \\t]*\\/\\// }, { open: /\\n?[ \\t]*#/ }],\n },\n },\n\n python: {\n fileExtensions: ['.py'],\n command: (fileName) => ['python3', fileName],\n grammer: {\n strings: [\n { open: /'''/, close: /(?<!\\\\)(?:\\\\{2})*'''/ },\n { open: /\"\"\"/, close: /(?<!\\\\)(?:\\\\{2})*\"\"\"/ },\n { open: /'/, close: /(?<!\\\\)(?:\\\\{2})*'/ },\n { open: /\"/, close: /(?<!\\\\)(?:\\\\{2})*\"/ },\n ],\n comments: [\n { open: /\\n?[ \\t]*'''/, close: /'''/ },\n { open: /\\n?[ \\t]*\"\"\"/, close: /\"\"\"/ },\n { open: /\\n?[ \\t]*#/ },\n ],\n },\n },\n\n ruby: {\n fileExtensions: ['.rb'],\n buildCommand: (fileName) => ['ruby', '-c', fileName],\n command: (fileName) => ['ruby', '--jit', fileName],\n grammer: {\n strings: [\n { open: /'/, close: /(?<!\\\\)(?:\\\\{2})*'/ },\n { open: /\"/, close: /(?<!\\\\)(?:\\\\{2})*\"/ },\n ],\n comments: [{ open: /\\n?[ \\t]*=begin/, close: /=end/ }, { open: /\\n?[ \\t]*#/ }],\n },\n },\n\n rust: {\n fileExtensions: ['.rs'],\n buildCommand: (filePath) => ['rustc', filePath, '-o', 'main'],\n command: () => ['./main'],\n grammer: cLikeGrammer,\n },\n\n zig: {\n fileExtensions: ['.zig'],\n buildCommand: (filePath) => ['zig', 'build-exe', filePath],\n command: (filePath) => ['./' + filePath.replace(/\\.zig$/, '')],\n grammer: cLikeGrammer,\n },\n\n typescript: {\n fileExtensions: ['.ts', '.cts', '.mts'],\n command: (fileName) => ['bun', fileName],\n grammer: javaScriptLikeGrammer,\n },\n\n text: {\n fileExtensions: ['.txt'],\n command: (fileName) => ['cat', fileName],\n },\n} as const;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"language.cjs","names":["fs","path","removeCommentsInSourceCode"],"sources":["../../src/types/language.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { removeCommentsInSourceCode } from '../helpers/removeCommentsInSourceCode.js';\n\nexport interface LanguageDefinition {\n /** File extensions to judge with this config. */\n fileExtensions: readonly string[];\n\n /** Function executed before the build. */\n prebuild?(cwd: string): Promise<void>;\n\n /** Returns the command to build a user program. */\n buildCommand?(filePath: string): [string, ...string[]];\n\n /** Returns the command to run a user program. */\n command(filePath: string): [string, ...string[]];\n\n /** Grammer definition for static analysis. */\n grammer?: {\n strings?: readonly { open: RegExp; close: RegExp; templateSyntax?: 'kotlin' }[];\n comments?: readonly { open: RegExp; close?: RegExp }[];\n };\n}\n\nconst cLikeGrammer = {\n strings: [\n { open: /'/, close: /(?<!\\\\)(?:\\\\{2})*'/ },\n { open: /\"/, close: /(?<!\\\\)(?:\\\\{2})*\"/ },\n ],\n comments: [{ open: /\\n?[ \\t]*\\/\\*/, close: /\\*\\// }, { open: /\\n?[ \\t]*\\/\\// }],\n} as const satisfies LanguageDefinition['grammer'];\n\nconst javaScriptLikeGrammer = {\n strings: [\n { open: /'/, close: /(?<!\\\\)(?:\\\\{2})*'/ },\n { open: /\"/, close: /(?<!\\\\)(?:\\\\{2})*\"/ },\n { open: /`/, close: /(?<!\\\\)(?:\\\\{2})*`/ },\n ],\n comments: [{ open: /\\n?[ \\t]*\\/\\*/, close: /\\*\\// }, { open: /\\n?[ \\t]*\\/\\// }],\n} as const satisfies LanguageDefinition['grammer'];\n\nexport const languageIdToDefinition: Readonly<Record<string, Readonly<LanguageDefinition>>> = {\n c: {\n fileExtensions: ['.c'],\n buildCommand: (filePath) => ['gcc', '--std=c17', '-O2', filePath, '-o', 'main'],\n command: () => ['./main'],\n grammer: cLikeGrammer,\n },\n\n cpp: {\n fileExtensions: ['.cpp'],\n buildCommand: (filePath) => ['g++', '--std=c++20', '-O2', filePath, '-o', 'main'],\n command: () => ['./main'],\n grammer: cLikeGrammer,\n },\n\n csharp: {\n fileExtensions: ['.cs'],\n prebuild: async (cwd) => {\n await fs.promises.writeFile(\n path.join(cwd, 'Main.csproj'),\n `<Project Sdk=\"Microsoft.NET.Sdk\">\n <PropertyGroup>\n <OutputType>Exe</OutputType>\n <TargetFramework>net8.0</TargetFramework>\n <ImplicitUsings>enable</ImplicitUsings>\n <Nullable>enable</Nullable>\n <AllowUnsafeBlocks>true</AllowUnsafeBlocks>\n <AssemblyName>Main</AssemblyName>\n </PropertyGroup>\n</Project>`\n );\n },\n buildCommand: () => ['dotnet', 'build', 'Main.csproj', '--configuration', 'Release', '--verbosity', 'quiet'],\n command: () => ['dotnet', 'bin/Release/net8.0/Main.dll'],\n grammer: cLikeGrammer,\n },\n\n css: {\n fileExtensions: ['.css'],\n command: () => ['echo', ''],\n grammer: {\n strings: [\n { open: /'/, close: /(?<!\\\\)(?:\\\\{2})*'/ },\n { open: /\"/, close: /(?<!\\\\)(?:\\\\{2})*\"/ },\n ],\n comments: [{ open: /\\n?[ \\t]*\\/\\*/, close: /\\*\\// }],\n },\n },\n\n dart: {\n fileExtensions: ['.dart'],\n buildCommand: (filePath) => ['dart', 'compile', 'exe', filePath, '-o', 'main'],\n command: () => ['./main'],\n grammer: cLikeGrammer,\n },\n\n java: {\n fileExtensions: ['.java'],\n prebuild: async (cwd) => {\n const publicClassRegex = /\\bpublic\\s+class\\s+(\\w+)\\b/;\n for (const dirent of await fs.promises.readdir(cwd, { withFileTypes: true })) {\n if (!dirent.isFile() || !dirent.name.endsWith('.java')) continue;\n const data = await fs.promises.readFile(path.join(cwd, dirent.name), 'utf8');\n const [, className] = publicClassRegex.exec(removeCommentsInSourceCode(cLikeGrammer, data)) ?? [];\n if (className) await fs.promises.rename(path.join(cwd, dirent.name), path.join(cwd, `${className}.java`));\n }\n },\n buildCommand: (fileName) => ['javac', fileName],\n // For example, Problem 7-3 in WillBooster's Java lecture uses at least 256MB.\n command: (fileName) => ['java', '-Xmx1024m', fileName.replace(/\\.java$/, '')],\n grammer: cLikeGrammer,\n },\n\n kotlin: {\n fileExtensions: ['.kt'],\n buildCommand: (filePath) => ['kotlinc', filePath, '-include-runtime', '-d', 'main.jar'],\n command: () => ['java', '-Xmx1024m', '-jar', 'main.jar'],\n grammer: {\n strings: [\n { open: /\"\"\"/, close: /\"\"\"/, templateSyntax: 'kotlin' },\n { open: /'/, close: /(?<!\\\\)(?:\\\\{2})*'/ },\n { open: /\"/, close: /(?<!\\\\)(?:\\\\{2})*\"/, templateSyntax: 'kotlin' },\n ],\n comments: cLikeGrammer.comments,\n },\n },\n\n javascript: {\n fileExtensions: ['.js', '.cjs', '.mjs'],\n command: (fileName) => ['bun', fileName],\n grammer: javaScriptLikeGrammer,\n },\n\n jsp: {\n fileExtensions: ['.jsp'],\n command: () => ['echo', ''],\n grammer: {\n strings: [\n { open: /'/, close: /(?<!\\\\)(?:\\\\{2})*'/ },\n { open: /\"/, close: /(?<!\\\\)(?:\\\\{2})*\"/ },\n ],\n comments: [\n { open: /\\n?[ \\t]*<!--/, close: /-->/ },\n { open: /\\n?[ \\t]*<%--/, close: /--%>/ },\n { open: /\\n?[ \\t]*\\/\\*/, close: /\\*\\// },\n { open: /\\n?[ \\t]*\\/\\// },\n ],\n },\n },\n\n haskell: {\n fileExtensions: ['.hs'],\n buildCommand: (filePath) => ['ghc', '-o', 'main', filePath],\n command: () => ['./main'],\n grammer: {\n strings: [\n { open: /'/, close: /(?<!\\\\)(?:\\\\{2})*'/ },\n { open: /\"/, close: /(?<!\\\\)(?:\\\\{2})*\"/ },\n ],\n comments: [{ open: /\\n?[ \\t]*\\{-/, close: /-\\}/ }, { open: /\\n?[ \\t]*--/ }],\n },\n },\n\n html: {\n fileExtensions: ['.html'],\n command: () => ['echo', ''],\n grammer: {\n strings: [\n { open: /'/, close: /(?<!\\\\)(?:\\\\{2})*'/ },\n { open: /\"/, close: /(?<!\\\\)(?:\\\\{2})*\"/ },\n ],\n comments: [{ open: /\\n?[ \\t]*<!--/, close: /-->/ }],\n },\n },\n\n php: {\n fileExtensions: ['.php'],\n command: (fileName) => ['php', fileName],\n grammer: {\n strings: [\n { open: /'/, close: /(?<!\\\\)(?:\\\\{2})*'/ },\n { open: /\"/, close: /(?<!\\\\)(?:\\\\{2})*\"/ },\n ],\n comments: [{ open: /\\n?[ \\t]*\\/\\*/, close: /\\*\\// }, { open: /\\n?[ \\t]*\\/\\// }, { open: /\\n?[ \\t]*#/ }],\n },\n },\n\n python: {\n fileExtensions: ['.py'],\n command: (fileName) => ['python3', fileName],\n grammer: {\n strings: [\n { open: /'''/, close: /(?<!\\\\)(?:\\\\{2})*'''/ },\n { open: /\"\"\"/, close: /(?<!\\\\)(?:\\\\{2})*\"\"\"/ },\n { open: /'/, close: /(?<!\\\\)(?:\\\\{2})*'/ },\n { open: /\"/, close: /(?<!\\\\)(?:\\\\{2})*\"/ },\n ],\n comments: [\n { open: /\\n?[ \\t]*'''/, close: /'''/ },\n { open: /\\n?[ \\t]*\"\"\"/, close: /\"\"\"/ },\n { open: /\\n?[ \\t]*#/ },\n ],\n },\n },\n\n ruby: {\n fileExtensions: ['.rb'],\n buildCommand: (fileName) => ['ruby', '-c', fileName],\n command: (fileName) => ['ruby', '--jit', fileName],\n grammer: {\n strings: [\n { open: /'/, close: /(?<!\\\\)(?:\\\\{2})*'/ },\n { open: /\"/, close: /(?<!\\\\)(?:\\\\{2})*\"/ },\n ],\n comments: [{ open: /\\n?[ \\t]*=begin/, close: /=end/ }, { open: /\\n?[ \\t]*#/ }],\n },\n },\n\n rust: {\n fileExtensions: ['.rs'],\n buildCommand: (filePath) => ['rustc', filePath, '-o', 'main'],\n command: () => ['./main'],\n grammer: cLikeGrammer,\n },\n\n zig: {\n fileExtensions: ['.zig'],\n buildCommand: (filePath) => ['zig', 'build-exe', filePath],\n command: (filePath) => ['./' + filePath.replace(/\\.zig$/, '')],\n grammer: cLikeGrammer,\n },\n\n typescript: {\n fileExtensions: ['.ts', '.cts', '.mts'],\n command: (fileName) => ['bun', fileName],\n grammer: javaScriptLikeGrammer,\n },\n\n text: {\n fileExtensions: ['.txt'],\n command: (fileName) => ['cat', fileName],\n },\n} as const;\n"],"mappings":"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"}
|
package/dist/types/language.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{removeCommentsInSourceCode as e}from"../helpers/removeCommentsInSourceCode.js";import t from"node:fs";import n from"node:path";const r={strings:[{open:/'/,close:/(?<!\\)(?:\\{2})*'/},{open:/"/,close:/(?<!\\)(?:\\{2})*"/}],comments:[{open:/\n?[ \t]*\/\*/,close:/\*\//},{open:/\n?[ \t]*\/\//}]},i={strings:[{open:/'/,close:/(?<!\\)(?:\\{2})*'/},{open:/"/,close:/(?<!\\)(?:\\{2})*"/},{open:/`/,close:/(?<!\\)(?:\\{2})*`/}],comments:[{open:/\n?[ \t]*\/\*/,close:/\*\//},{open:/\n?[ \t]*\/\//}]},a={c:{fileExtensions:[`.c`],buildCommand:e=>[`gcc`,`--std=c17`,`-O2`,e,`-o`,`main`],command:()=>[`./main`],grammer:r},cpp:{fileExtensions:[`.cpp`],buildCommand:e=>[`g++`,`--std=c++20`,`-O2`,e,`-o`,`main`],command:()=>[`./main`],grammer:r},csharp:{fileExtensions:[`.cs`],prebuild:async e=>{await t.promises.writeFile(n.join(e,`Main.csproj`),`<Project Sdk="Microsoft.NET.Sdk">
|
|
2
2
|
<PropertyGroup>
|
|
3
3
|
<OutputType>Exe</OutputType>
|
|
4
4
|
<TargetFramework>net8.0</TargetFramework>
|
|
@@ -7,5 +7,5 @@ import{writeFileWithoutFollowingSymlinks as e}from"../helpers/safeFs.js";import{
|
|
|
7
7
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
|
8
8
|
<AssemblyName>Main</AssemblyName>
|
|
9
9
|
</PropertyGroup>
|
|
10
|
-
</Project>`)},buildCommand:()=>[`dotnet`,`build`,`Main.csproj`,`--configuration`,`Release`,`--verbosity`,`quiet`],command:()=>[`dotnet`,`bin/Release/net8.0/Main.dll`],grammer:
|
|
10
|
+
</Project>`)},buildCommand:()=>[`dotnet`,`build`,`Main.csproj`,`--configuration`,`Release`,`--verbosity`,`quiet`],command:()=>[`dotnet`,`bin/Release/net8.0/Main.dll`],grammer:r},css:{fileExtensions:[`.css`],command:()=>[`echo`,``],grammer:{strings:[{open:/'/,close:/(?<!\\)(?:\\{2})*'/},{open:/"/,close:/(?<!\\)(?:\\{2})*"/}],comments:[{open:/\n?[ \t]*\/\*/,close:/\*\//}]}},dart:{fileExtensions:[`.dart`],buildCommand:e=>[`dart`,`compile`,`exe`,e,`-o`,`main`],command:()=>[`./main`],grammer:r},java:{fileExtensions:[`.java`],prebuild:async i=>{let a=/\bpublic\s+class\s+(\w+)\b/;for(let o of await t.promises.readdir(i,{withFileTypes:!0})){if(!o.isFile()||!o.name.endsWith(`.java`))continue;let s=await t.promises.readFile(n.join(i,o.name),`utf8`),[,c]=a.exec(e(r,s))??[];c&&await t.promises.rename(n.join(i,o.name),n.join(i,`${c}.java`))}},buildCommand:e=>[`javac`,e],command:e=>[`java`,`-Xmx1024m`,e.replace(/\.java$/,``)],grammer:r},kotlin:{fileExtensions:[`.kt`],buildCommand:e=>[`kotlinc`,e,`-include-runtime`,`-d`,`main.jar`],command:()=>[`java`,`-Xmx1024m`,`-jar`,`main.jar`],grammer:{strings:[{open:/"""/,close:/"""/,templateSyntax:`kotlin`},{open:/'/,close:/(?<!\\)(?:\\{2})*'/},{open:/"/,close:/(?<!\\)(?:\\{2})*"/,templateSyntax:`kotlin`}],comments:r.comments}},javascript:{fileExtensions:[`.js`,`.cjs`,`.mjs`],command:e=>[`bun`,e],grammer:i},jsp:{fileExtensions:[`.jsp`],command:()=>[`echo`,``],grammer:{strings:[{open:/'/,close:/(?<!\\)(?:\\{2})*'/},{open:/"/,close:/(?<!\\)(?:\\{2})*"/}],comments:[{open:/\n?[ \t]*<!--/,close:/-->/},{open:/\n?[ \t]*<%--/,close:/--%>/},{open:/\n?[ \t]*\/\*/,close:/\*\//},{open:/\n?[ \t]*\/\//}]}},haskell:{fileExtensions:[`.hs`],buildCommand:e=>[`ghc`,`-o`,`main`,e],command:()=>[`./main`],grammer:{strings:[{open:/'/,close:/(?<!\\)(?:\\{2})*'/},{open:/"/,close:/(?<!\\)(?:\\{2})*"/}],comments:[{open:/\n?[ \t]*\{-/,close:/-\}/},{open:/\n?[ \t]*--/}]}},html:{fileExtensions:[`.html`],command:()=>[`echo`,``],grammer:{strings:[{open:/'/,close:/(?<!\\)(?:\\{2})*'/},{open:/"/,close:/(?<!\\)(?:\\{2})*"/}],comments:[{open:/\n?[ \t]*<!--/,close:/-->/}]}},php:{fileExtensions:[`.php`],command:e=>[`php`,e],grammer:{strings:[{open:/'/,close:/(?<!\\)(?:\\{2})*'/},{open:/"/,close:/(?<!\\)(?:\\{2})*"/}],comments:[{open:/\n?[ \t]*\/\*/,close:/\*\//},{open:/\n?[ \t]*\/\//},{open:/\n?[ \t]*#/}]}},python:{fileExtensions:[`.py`],command:e=>[`python3`,e],grammer:{strings:[{open:/'''/,close:/(?<!\\)(?:\\{2})*'''/},{open:/"""/,close:/(?<!\\)(?:\\{2})*"""/},{open:/'/,close:/(?<!\\)(?:\\{2})*'/},{open:/"/,close:/(?<!\\)(?:\\{2})*"/}],comments:[{open:/\n?[ \t]*'''/,close:/'''/},{open:/\n?[ \t]*"""/,close:/"""/},{open:/\n?[ \t]*#/}]}},ruby:{fileExtensions:[`.rb`],buildCommand:e=>[`ruby`,`-c`,e],command:e=>[`ruby`,`--jit`,e],grammer:{strings:[{open:/'/,close:/(?<!\\)(?:\\{2})*'/},{open:/"/,close:/(?<!\\)(?:\\{2})*"/}],comments:[{open:/\n?[ \t]*=begin/,close:/=end/},{open:/\n?[ \t]*#/}]}},rust:{fileExtensions:[`.rs`],buildCommand:e=>[`rustc`,e,`-o`,`main`],command:()=>[`./main`],grammer:r},zig:{fileExtensions:[`.zig`],buildCommand:e=>[`zig`,`build-exe`,e],command:e=>[`./`+e.replace(/\.zig$/,``)],grammer:r},typescript:{fileExtensions:[`.ts`,`.cts`,`.mts`],command:e=>[`bun`,e],grammer:i},text:{fileExtensions:[`.txt`],command:e=>[`cat`,e]}};export{a as languageIdToDefinition};
|
|
11
11
|
//# sourceMappingURL=language.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"language.js","names":[],"sources":["../../src/types/language.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { removeCommentsInSourceCode } from '../helpers/removeCommentsInSourceCode.js';\nimport { writeFileWithoutFollowingSymlinks } from '../helpers/safeFs.js';\n\nexport interface LanguageDefinition {\n /** File extensions to judge with this config. */\n fileExtensions: readonly string[];\n\n /** Function executed before the build. */\n prebuild?(cwd: string): Promise<void>;\n\n /** Returns the command to build a user program. */\n buildCommand?(filePath: string): [string, ...string[]];\n\n /** Returns the command to run a user program. */\n command(filePath: string): [string, ...string[]];\n\n /** Grammer definition for static analysis. */\n grammer?: {\n strings?: readonly { open: RegExp; close: RegExp; templateSyntax?: 'kotlin' }[];\n comments?: readonly { open: RegExp; close?: RegExp }[];\n };\n}\n\nconst cLikeGrammer = {\n strings: [\n { open: /'/, close: /(?<!\\\\)(?:\\\\{2})*'/ },\n { open: /\"/, close: /(?<!\\\\)(?:\\\\{2})*\"/ },\n ],\n comments: [{ open: /\\n?[ \\t]*\\/\\*/, close: /\\*\\// }, { open: /\\n?[ \\t]*\\/\\// }],\n} as const satisfies LanguageDefinition['grammer'];\n\nconst javaScriptLikeGrammer = {\n strings: [\n { open: /'/, close: /(?<!\\\\)(?:\\\\{2})*'/ },\n { open: /\"/, close: /(?<!\\\\)(?:\\\\{2})*\"/ },\n { open: /`/, close: /(?<!\\\\)(?:\\\\{2})*`/ },\n ],\n comments: [{ open: /\\n?[ \\t]*\\/\\*/, close: /\\*\\// }, { open: /\\n?[ \\t]*\\/\\// }],\n} as const satisfies LanguageDefinition['grammer'];\n\nexport const languageIdToDefinition: Readonly<Record<string, Readonly<LanguageDefinition>>> = {\n c: {\n fileExtensions: ['.c'],\n buildCommand: (filePath) => ['gcc', '--std=c17', '-O2', filePath, '-o', 'main'],\n command: () => ['./main'],\n grammer: cLikeGrammer,\n },\n\n cpp: {\n fileExtensions: ['.cpp'],\n buildCommand: (filePath) => ['g++', '--std=c++20', '-O2', filePath, '-o', 'main'],\n command: () => ['./main'],\n grammer: cLikeGrammer,\n },\n\n csharp: {\n fileExtensions: ['.cs'],\n prebuild: async (cwd) => {\n // No-follow write: the submission owns `cwd` and could point `Main.csproj` at a file only the\n // trusted harness user can write.\n await writeFileWithoutFollowingSymlinks(\n path.join(cwd, 'Main.csproj'),\n `<Project Sdk=\"Microsoft.NET.Sdk\">\n <PropertyGroup>\n <OutputType>Exe</OutputType>\n <TargetFramework>net8.0</TargetFramework>\n <ImplicitUsings>enable</ImplicitUsings>\n <Nullable>enable</Nullable>\n <AllowUnsafeBlocks>true</AllowUnsafeBlocks>\n <AssemblyName>Main</AssemblyName>\n </PropertyGroup>\n</Project>`\n );\n },\n buildCommand: () => ['dotnet', 'build', 'Main.csproj', '--configuration', 'Release', '--verbosity', 'quiet'],\n command: () => ['dotnet', 'bin/Release/net8.0/Main.dll'],\n grammer: cLikeGrammer,\n },\n\n css: {\n fileExtensions: ['.css'],\n command: () => ['echo', ''],\n grammer: {\n strings: [\n { open: /'/, close: /(?<!\\\\)(?:\\\\{2})*'/ },\n { open: /\"/, close: /(?<!\\\\)(?:\\\\{2})*\"/ },\n ],\n comments: [{ open: /\\n?[ \\t]*\\/\\*/, close: /\\*\\// }],\n },\n },\n\n dart: {\n fileExtensions: ['.dart'],\n buildCommand: (filePath) => ['dart', 'compile', 'exe', filePath, '-o', 'main'],\n command: () => ['./main'],\n grammer: cLikeGrammer,\n },\n\n java: {\n fileExtensions: ['.java'],\n prebuild: async (cwd) => {\n const publicClassRegex = /\\bpublic\\s+class\\s+(\\w+)\\b/;\n for (const dirent of await fs.promises.readdir(cwd, { withFileTypes: true })) {\n if (!dirent.isFile() || !dirent.name.endsWith('.java')) continue;\n const data = await fs.promises.readFile(path.join(cwd, dirent.name), 'utf8');\n const [, className] = publicClassRegex.exec(removeCommentsInSourceCode(cLikeGrammer, data)) ?? [];\n if (className) await fs.promises.rename(path.join(cwd, dirent.name), path.join(cwd, `${className}.java`));\n }\n },\n buildCommand: (fileName) => ['javac', fileName],\n // For example, Problem 7-3 in WillBooster's Java lecture uses at least 256MB.\n command: (fileName) => ['java', '-Xmx1024m', fileName.replace(/\\.java$/, '')],\n grammer: cLikeGrammer,\n },\n\n kotlin: {\n fileExtensions: ['.kt'],\n buildCommand: (filePath) => ['kotlinc', filePath, '-include-runtime', '-d', 'main.jar'],\n command: () => ['java', '-Xmx1024m', '-jar', 'main.jar'],\n grammer: {\n strings: [\n { open: /\"\"\"/, close: /\"\"\"/, templateSyntax: 'kotlin' },\n { open: /'/, close: /(?<!\\\\)(?:\\\\{2})*'/ },\n { open: /\"/, close: /(?<!\\\\)(?:\\\\{2})*\"/, templateSyntax: 'kotlin' },\n ],\n comments: cLikeGrammer.comments,\n },\n },\n\n javascript: {\n fileExtensions: ['.js', '.cjs', '.mjs'],\n command: (fileName) => ['bun', fileName],\n grammer: javaScriptLikeGrammer,\n },\n\n jsp: {\n fileExtensions: ['.jsp'],\n command: () => ['echo', ''],\n grammer: {\n strings: [\n { open: /'/, close: /(?<!\\\\)(?:\\\\{2})*'/ },\n { open: /\"/, close: /(?<!\\\\)(?:\\\\{2})*\"/ },\n ],\n comments: [\n { open: /\\n?[ \\t]*<!--/, close: /-->/ },\n { open: /\\n?[ \\t]*<%--/, close: /--%>/ },\n { open: /\\n?[ \\t]*\\/\\*/, close: /\\*\\// },\n { open: /\\n?[ \\t]*\\/\\// },\n ],\n },\n },\n\n haskell: {\n fileExtensions: ['.hs'],\n buildCommand: (filePath) => ['ghc', '-o', 'main', filePath],\n command: () => ['./main'],\n grammer: {\n strings: [\n { open: /'/, close: /(?<!\\\\)(?:\\\\{2})*'/ },\n { open: /\"/, close: /(?<!\\\\)(?:\\\\{2})*\"/ },\n ],\n comments: [{ open: /\\n?[ \\t]*\\{-/, close: /-\\}/ }, { open: /\\n?[ \\t]*--/ }],\n },\n },\n\n html: {\n fileExtensions: ['.html'],\n command: () => ['echo', ''],\n grammer: {\n strings: [\n { open: /'/, close: /(?<!\\\\)(?:\\\\{2})*'/ },\n { open: /\"/, close: /(?<!\\\\)(?:\\\\{2})*\"/ },\n ],\n comments: [{ open: /\\n?[ \\t]*<!--/, close: /-->/ }],\n },\n },\n\n php: {\n fileExtensions: ['.php'],\n command: (fileName) => ['php', fileName],\n grammer: {\n strings: [\n { open: /'/, close: /(?<!\\\\)(?:\\\\{2})*'/ },\n { open: /\"/, close: /(?<!\\\\)(?:\\\\{2})*\"/ },\n ],\n comments: [{ open: /\\n?[ \\t]*\\/\\*/, close: /\\*\\// }, { open: /\\n?[ \\t]*\\/\\// }, { open: /\\n?[ \\t]*#/ }],\n },\n },\n\n python: {\n fileExtensions: ['.py'],\n command: (fileName) => ['python3', fileName],\n grammer: {\n strings: [\n { open: /'''/, close: /(?<!\\\\)(?:\\\\{2})*'''/ },\n { open: /\"\"\"/, close: /(?<!\\\\)(?:\\\\{2})*\"\"\"/ },\n { open: /'/, close: /(?<!\\\\)(?:\\\\{2})*'/ },\n { open: /\"/, close: /(?<!\\\\)(?:\\\\{2})*\"/ },\n ],\n comments: [\n { open: /\\n?[ \\t]*'''/, close: /'''/ },\n { open: /\\n?[ \\t]*\"\"\"/, close: /\"\"\"/ },\n { open: /\\n?[ \\t]*#/ },\n ],\n },\n },\n\n ruby: {\n fileExtensions: ['.rb'],\n buildCommand: (fileName) => ['ruby', '-c', fileName],\n command: (fileName) => ['ruby', '--jit', fileName],\n grammer: {\n strings: [\n { open: /'/, close: /(?<!\\\\)(?:\\\\{2})*'/ },\n { open: /\"/, close: /(?<!\\\\)(?:\\\\{2})*\"/ },\n ],\n comments: [{ open: /\\n?[ \\t]*=begin/, close: /=end/ }, { open: /\\n?[ \\t]*#/ }],\n },\n },\n\n rust: {\n fileExtensions: ['.rs'],\n buildCommand: (filePath) => ['rustc', filePath, '-o', 'main'],\n command: () => ['./main'],\n grammer: cLikeGrammer,\n },\n\n zig: {\n fileExtensions: ['.zig'],\n buildCommand: (filePath) => ['zig', 'build-exe', filePath],\n command: (filePath) => ['./' + filePath.replace(/\\.zig$/, '')],\n grammer: cLikeGrammer,\n },\n\n typescript: {\n fileExtensions: ['.ts', '.cts', '.mts'],\n command: (fileName) => ['bun', fileName],\n grammer: javaScriptLikeGrammer,\n },\n\n text: {\n fileExtensions: ['.txt'],\n command: (fileName) => ['cat', fileName],\n },\n} as const;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"language.js","names":[],"sources":["../../src/types/language.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { removeCommentsInSourceCode } from '../helpers/removeCommentsInSourceCode.js';\n\nexport interface LanguageDefinition {\n /** File extensions to judge with this config. */\n fileExtensions: readonly string[];\n\n /** Function executed before the build. */\n prebuild?(cwd: string): Promise<void>;\n\n /** Returns the command to build a user program. */\n buildCommand?(filePath: string): [string, ...string[]];\n\n /** Returns the command to run a user program. */\n command(filePath: string): [string, ...string[]];\n\n /** Grammer definition for static analysis. */\n grammer?: {\n strings?: readonly { open: RegExp; close: RegExp; templateSyntax?: 'kotlin' }[];\n comments?: readonly { open: RegExp; close?: RegExp }[];\n };\n}\n\nconst cLikeGrammer = {\n strings: [\n { open: /'/, close: /(?<!\\\\)(?:\\\\{2})*'/ },\n { open: /\"/, close: /(?<!\\\\)(?:\\\\{2})*\"/ },\n ],\n comments: [{ open: /\\n?[ \\t]*\\/\\*/, close: /\\*\\// }, { open: /\\n?[ \\t]*\\/\\// }],\n} as const satisfies LanguageDefinition['grammer'];\n\nconst javaScriptLikeGrammer = {\n strings: [\n { open: /'/, close: /(?<!\\\\)(?:\\\\{2})*'/ },\n { open: /\"/, close: /(?<!\\\\)(?:\\\\{2})*\"/ },\n { open: /`/, close: /(?<!\\\\)(?:\\\\{2})*`/ },\n ],\n comments: [{ open: /\\n?[ \\t]*\\/\\*/, close: /\\*\\// }, { open: /\\n?[ \\t]*\\/\\// }],\n} as const satisfies LanguageDefinition['grammer'];\n\nexport const languageIdToDefinition: Readonly<Record<string, Readonly<LanguageDefinition>>> = {\n c: {\n fileExtensions: ['.c'],\n buildCommand: (filePath) => ['gcc', '--std=c17', '-O2', filePath, '-o', 'main'],\n command: () => ['./main'],\n grammer: cLikeGrammer,\n },\n\n cpp: {\n fileExtensions: ['.cpp'],\n buildCommand: (filePath) => ['g++', '--std=c++20', '-O2', filePath, '-o', 'main'],\n command: () => ['./main'],\n grammer: cLikeGrammer,\n },\n\n csharp: {\n fileExtensions: ['.cs'],\n prebuild: async (cwd) => {\n await fs.promises.writeFile(\n path.join(cwd, 'Main.csproj'),\n `<Project Sdk=\"Microsoft.NET.Sdk\">\n <PropertyGroup>\n <OutputType>Exe</OutputType>\n <TargetFramework>net8.0</TargetFramework>\n <ImplicitUsings>enable</ImplicitUsings>\n <Nullable>enable</Nullable>\n <AllowUnsafeBlocks>true</AllowUnsafeBlocks>\n <AssemblyName>Main</AssemblyName>\n </PropertyGroup>\n</Project>`\n );\n },\n buildCommand: () => ['dotnet', 'build', 'Main.csproj', '--configuration', 'Release', '--verbosity', 'quiet'],\n command: () => ['dotnet', 'bin/Release/net8.0/Main.dll'],\n grammer: cLikeGrammer,\n },\n\n css: {\n fileExtensions: ['.css'],\n command: () => ['echo', ''],\n grammer: {\n strings: [\n { open: /'/, close: /(?<!\\\\)(?:\\\\{2})*'/ },\n { open: /\"/, close: /(?<!\\\\)(?:\\\\{2})*\"/ },\n ],\n comments: [{ open: /\\n?[ \\t]*\\/\\*/, close: /\\*\\// }],\n },\n },\n\n dart: {\n fileExtensions: ['.dart'],\n buildCommand: (filePath) => ['dart', 'compile', 'exe', filePath, '-o', 'main'],\n command: () => ['./main'],\n grammer: cLikeGrammer,\n },\n\n java: {\n fileExtensions: ['.java'],\n prebuild: async (cwd) => {\n const publicClassRegex = /\\bpublic\\s+class\\s+(\\w+)\\b/;\n for (const dirent of await fs.promises.readdir(cwd, { withFileTypes: true })) {\n if (!dirent.isFile() || !dirent.name.endsWith('.java')) continue;\n const data = await fs.promises.readFile(path.join(cwd, dirent.name), 'utf8');\n const [, className] = publicClassRegex.exec(removeCommentsInSourceCode(cLikeGrammer, data)) ?? [];\n if (className) await fs.promises.rename(path.join(cwd, dirent.name), path.join(cwd, `${className}.java`));\n }\n },\n buildCommand: (fileName) => ['javac', fileName],\n // For example, Problem 7-3 in WillBooster's Java lecture uses at least 256MB.\n command: (fileName) => ['java', '-Xmx1024m', fileName.replace(/\\.java$/, '')],\n grammer: cLikeGrammer,\n },\n\n kotlin: {\n fileExtensions: ['.kt'],\n buildCommand: (filePath) => ['kotlinc', filePath, '-include-runtime', '-d', 'main.jar'],\n command: () => ['java', '-Xmx1024m', '-jar', 'main.jar'],\n grammer: {\n strings: [\n { open: /\"\"\"/, close: /\"\"\"/, templateSyntax: 'kotlin' },\n { open: /'/, close: /(?<!\\\\)(?:\\\\{2})*'/ },\n { open: /\"/, close: /(?<!\\\\)(?:\\\\{2})*\"/, templateSyntax: 'kotlin' },\n ],\n comments: cLikeGrammer.comments,\n },\n },\n\n javascript: {\n fileExtensions: ['.js', '.cjs', '.mjs'],\n command: (fileName) => ['bun', fileName],\n grammer: javaScriptLikeGrammer,\n },\n\n jsp: {\n fileExtensions: ['.jsp'],\n command: () => ['echo', ''],\n grammer: {\n strings: [\n { open: /'/, close: /(?<!\\\\)(?:\\\\{2})*'/ },\n { open: /\"/, close: /(?<!\\\\)(?:\\\\{2})*\"/ },\n ],\n comments: [\n { open: /\\n?[ \\t]*<!--/, close: /-->/ },\n { open: /\\n?[ \\t]*<%--/, close: /--%>/ },\n { open: /\\n?[ \\t]*\\/\\*/, close: /\\*\\// },\n { open: /\\n?[ \\t]*\\/\\// },\n ],\n },\n },\n\n haskell: {\n fileExtensions: ['.hs'],\n buildCommand: (filePath) => ['ghc', '-o', 'main', filePath],\n command: () => ['./main'],\n grammer: {\n strings: [\n { open: /'/, close: /(?<!\\\\)(?:\\\\{2})*'/ },\n { open: /\"/, close: /(?<!\\\\)(?:\\\\{2})*\"/ },\n ],\n comments: [{ open: /\\n?[ \\t]*\\{-/, close: /-\\}/ }, { open: /\\n?[ \\t]*--/ }],\n },\n },\n\n html: {\n fileExtensions: ['.html'],\n command: () => ['echo', ''],\n grammer: {\n strings: [\n { open: /'/, close: /(?<!\\\\)(?:\\\\{2})*'/ },\n { open: /\"/, close: /(?<!\\\\)(?:\\\\{2})*\"/ },\n ],\n comments: [{ open: /\\n?[ \\t]*<!--/, close: /-->/ }],\n },\n },\n\n php: {\n fileExtensions: ['.php'],\n command: (fileName) => ['php', fileName],\n grammer: {\n strings: [\n { open: /'/, close: /(?<!\\\\)(?:\\\\{2})*'/ },\n { open: /\"/, close: /(?<!\\\\)(?:\\\\{2})*\"/ },\n ],\n comments: [{ open: /\\n?[ \\t]*\\/\\*/, close: /\\*\\// }, { open: /\\n?[ \\t]*\\/\\// }, { open: /\\n?[ \\t]*#/ }],\n },\n },\n\n python: {\n fileExtensions: ['.py'],\n command: (fileName) => ['python3', fileName],\n grammer: {\n strings: [\n { open: /'''/, close: /(?<!\\\\)(?:\\\\{2})*'''/ },\n { open: /\"\"\"/, close: /(?<!\\\\)(?:\\\\{2})*\"\"\"/ },\n { open: /'/, close: /(?<!\\\\)(?:\\\\{2})*'/ },\n { open: /\"/, close: /(?<!\\\\)(?:\\\\{2})*\"/ },\n ],\n comments: [\n { open: /\\n?[ \\t]*'''/, close: /'''/ },\n { open: /\\n?[ \\t]*\"\"\"/, close: /\"\"\"/ },\n { open: /\\n?[ \\t]*#/ },\n ],\n },\n },\n\n ruby: {\n fileExtensions: ['.rb'],\n buildCommand: (fileName) => ['ruby', '-c', fileName],\n command: (fileName) => ['ruby', '--jit', fileName],\n grammer: {\n strings: [\n { open: /'/, close: /(?<!\\\\)(?:\\\\{2})*'/ },\n { open: /\"/, close: /(?<!\\\\)(?:\\\\{2})*\"/ },\n ],\n comments: [{ open: /\\n?[ \\t]*=begin/, close: /=end/ }, { open: /\\n?[ \\t]*#/ }],\n },\n },\n\n rust: {\n fileExtensions: ['.rs'],\n buildCommand: (filePath) => ['rustc', filePath, '-o', 'main'],\n command: () => ['./main'],\n grammer: cLikeGrammer,\n },\n\n zig: {\n fileExtensions: ['.zig'],\n buildCommand: (filePath) => ['zig', 'build-exe', filePath],\n command: (filePath) => ['./' + filePath.replace(/\\.zig$/, '')],\n grammer: cLikeGrammer,\n },\n\n typescript: {\n fileExtensions: ['.ts', '.cts', '.mts'],\n command: (fileName) => ['bun', fileName],\n grammer: javaScriptLikeGrammer,\n },\n\n text: {\n fileExtensions: ['.txt'],\n command: (fileName) => ['cat', fileName],\n },\n} as const;\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exercode/problem-utils",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": ":100: A set of utilities for judging programs on Exercode (https://exercode.willbooster.com/).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"exercode",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"@ai-sdk/xai": "4.0.49",
|
|
60
60
|
"ai": "7.0.84",
|
|
61
61
|
"front-matter": "4.0.2",
|
|
62
|
-
"zod": "4.
|
|
62
|
+
"zod": "4.5.2"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
65
|
"@tsconfig/bun": "1.0.11",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"@willbooster/oxlint-config": "1.4.8",
|
|
73
73
|
"@willbooster/prettier-config": "10.4.0",
|
|
74
74
|
"@willbooster/wb": "22.1.3",
|
|
75
|
-
"build-ts": "21.0.
|
|
75
|
+
"build-ts": "21.0.10",
|
|
76
76
|
"conventional-changelog-conventionalcommits": "10.4.0",
|
|
77
77
|
"lefthook": "2.1.12",
|
|
78
78
|
"oxfmt": "0.65.0",
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
"oxlint-tsgolint": "7.0.2001",
|
|
81
81
|
"prettier": "3.9.6",
|
|
82
82
|
"prettier-plugin-java": "2.10.3",
|
|
83
|
-
"puppeteer": "25.
|
|
83
|
+
"puppeteer": "25.9.0",
|
|
84
84
|
"semantic-release": "25.0.9",
|
|
85
85
|
"sort-package-json": "4.0.0",
|
|
86
86
|
"typescript": "7.0.2",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"runCustomRunner.cjs","names":["sandboxUserName"],"sources":["../../src/helpers/runCustomRunner.ts"],"sourcesContent":["import { killSandboxUserProcesses, sandboxUserName } from './sandboxUser.js';\n\n/**\n * Run a problem-supplied `runCommand` handler, then terminate every sandbox-user process it left\n * behind. A submission can daemonize and close its inherited streams so the handler returns while\n * its child keeps running; that child could otherwise modify the working directory while the\n * harness reads outputs, or survive into the next test case or request. The built-in run paths\n * sweep the same way in their own `finally`.\n */\nexport async function runCustomRunner<T>(run: () => Promise<T> | T): Promise<T> {\n try {\n return await run();\n } finally {\n if (sandboxUserName) killSandboxUserProcesses();\n }\n}\n"],"mappings":"kDASA,eAAsB,EAAmB,EAAuC,CAC9E,GAAI,CACF,OAAO,MAAM,EAAI,CACnB,QAAU,CACJA,EAAAA,iBAAiB,EAAA,yBAAyB,CAChD,CACF"}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Run a problem-supplied `runCommand` handler, then terminate every sandbox-user process it left
|
|
3
|
-
* behind. A submission can daemonize and close its inherited streams so the handler returns while
|
|
4
|
-
* its child keeps running; that child could otherwise modify the working directory while the
|
|
5
|
-
* harness reads outputs, or survive into the next test case or request. The built-in run paths
|
|
6
|
-
* sweep the same way in their own `finally`.
|
|
7
|
-
*/
|
|
8
|
-
export declare function runCustomRunner<T>(run: () => Promise<T> | T): Promise<T>;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"runCustomRunner.js","names":[],"sources":["../../src/helpers/runCustomRunner.ts"],"sourcesContent":["import { killSandboxUserProcesses, sandboxUserName } from './sandboxUser.js';\n\n/**\n * Run a problem-supplied `runCommand` handler, then terminate every sandbox-user process it left\n * behind. A submission can daemonize and close its inherited streams so the handler returns while\n * its child keeps running; that child could otherwise modify the working directory while the\n * harness reads outputs, or survive into the next test case or request. The built-in run paths\n * sweep the same way in their own `finally`.\n */\nexport async function runCustomRunner<T>(run: () => Promise<T> | T): Promise<T> {\n try {\n return await run();\n } finally {\n if (sandboxUserName) killSandboxUserProcesses();\n }\n}\n"],"mappings":"iFASA,eAAsB,EAAmB,EAAuC,CAC9E,GAAI,CACF,OAAO,MAAM,EAAI,CACnB,QAAU,CACJ,GAAiB,EAAyB,CAChD,CACF"}
|
package/dist/helpers/safeFs.cjs
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
"use strict";const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./sandboxUser.cjs");let n=require("node:fs");n=e.__toESM(n,1);let r=require("node:path");r=e.__toESM(r,1);async function i(e,t){let a=await n.default.promises.lstat(e);if(a.isSymbolicLink()){await o(t),await n.default.promises.symlink(await n.default.promises.readlink(e),t);return}if(a.isDirectory()){(await l(t))?.isDirectory()||await o(t),await n.default.promises.mkdir(t,{recursive:!0});for(let a of await n.default.promises.readdir(e))await i(r.default.join(e,a),r.default.join(t,a));return}await o(t),await n.default.promises.copyFile(e,t)}async function a(e,t){let i=r.default.relative(e,t);if(!c(e,t))throw Error(`directory must be inside root: ${t} is not inside ${e}`);let a=e;for(let e of i.split(r.default.sep).filter(Boolean)){a=r.default.join(a,e);let t=await l(a);t?.isDirectory()||(t&&await o(a),await n.default.promises.mkdir(a))}}async function o(e){await t.forceRemove(e)}async function s(e,t){await o(e),await n.default.promises.writeFile(e,t,{flag:`wx`})}function c(e,t){let n=r.default.relative(e,t);return n!==`..`&&!n.startsWith(`..${r.default.sep}`)&&!r.default.isAbsolute(n)}async function l(e){try{return await n.default.promises.lstat(e)}catch{return}}exports.copyWithoutFollowingSymlinks=i,exports.createDirectoryWithoutFollowingSymlinks=a,exports.isContainedPath=c,exports.writeFileWithoutFollowingSymlinks=s;
|
|
2
|
-
//# sourceMappingURL=safeFs.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"safeFs.cjs","names":["fs","path","forceRemove"],"sources":["../../src/helpers/safeFs.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { forceRemove } from './sandboxUser.js';\n\n/**\n * Recursively copy `source` into `destination` without ever following a symlink that already\n * exists at a destination path. A sandboxed submission (running as a different OS user) can plant\n * such a symlink to redirect this trusted-user write outside its working directory, so every\n * pre-existing destination entry is lstat'd and unlinked (never followed) before being written.\n * Symlinks in the SOURCE are recreated as symlinks; their targets are never read.\n *\n * Merges into an existing destination directory (like `fs.cp(..., { recursive: true })`), but a\n * plain `fs.cp` follows destination symlinks nested inside directories, which this avoids.\n */\nexport async function copyWithoutFollowingSymlinks(source: string, destination: string): Promise<void> {\n const sourceStats = await fs.promises.lstat(source);\n\n if (sourceStats.isSymbolicLink()) {\n await removeExistingEntry(destination);\n await fs.promises.symlink(await fs.promises.readlink(source), destination);\n return;\n }\n\n if (sourceStats.isDirectory()) {\n // A pre-existing symlink (or file) at the destination is removed so the merge target is a real\n // directory the harness owns, not something the submission redirected.\n const existingDestinationStats = await lstatOrUndefined(destination);\n if (!existingDestinationStats?.isDirectory()) await removeExistingEntry(destination);\n await fs.promises.mkdir(destination, { recursive: true });\n for (const entry of await fs.promises.readdir(source)) {\n await copyWithoutFollowingSymlinks(path.join(source, entry), path.join(destination, entry));\n }\n return;\n }\n\n await removeExistingEntry(destination);\n await fs.promises.copyFile(source, destination);\n}\n\n/**\n * Create `directory` and the missing levels between it and `root`, replacing anything that is not\n * already a real directory. `fs.mkdir(..., { recursive: true })` follows a symlink it finds on the\n * way, which a submission can plant to place a trusted-user write outside the tree; this walks the\n * levels one at a time instead. Only levels BELOW `root` are inspected, so an unrelated symlink\n * above the caller's tree (`/tmp` on macOS, for instance) is never touched. `root` must already be\n * a directory the harness trusts, and `directory` must be inside it.\n */\nexport async function createDirectoryWithoutFollowingSymlinks(root: string, directory: string): Promise<void> {\n const relativePath = path.relative(root, directory);\n if (!isContainedPath(root, directory)) {\n throw new Error(`directory must be inside root: ${directory} is not inside ${root}`);\n }\n\n let currentPath = root;\n for (const level of relativePath.split(path.sep).filter(Boolean)) {\n currentPath = path.join(currentPath, level);\n const stats = await lstatOrUndefined(currentPath);\n if (stats?.isDirectory()) continue;\n if (stats) await removeExistingEntry(currentPath);\n await fs.promises.mkdir(currentPath);\n }\n}\n\n// `fs.rm` unlinks a symlink itself rather than following it, and removes files/directories\n// otherwise; `forceRemove` adds the retry for entries a sandboxed submission left unreadable.\nasync function removeExistingEntry(target: string): Promise<void> {\n await forceRemove(target);\n}\n\n/**\n * Write `data` to `filePath` without following a symlink already there. `fs.writeFile` follows one,\n * so a sandboxed submission could point a file the harness is about to write (a generated project\n * file, a fixture) at something only the harness can reach and have it overwritten. Trusted\n * callbacks that create files inside a submission directory must use this instead.\n */\nexport async function writeFileWithoutFollowingSymlinks(filePath: string, data: string | Uint8Array): Promise<void> {\n await removeExistingEntry(filePath);\n // `wx` fails rather than following a symlink planted between the removal and the write.\n await fs.promises.writeFile(filePath, data, { flag: 'wx' });\n}\n\n/** Whether `realTargetPath` is `realDirectoryPath` itself or below it. Both must be realpaths. */\nexport function isContainedPath(realDirectoryPath: string, realTargetPath: string): boolean {\n const relativePath = path.relative(realDirectoryPath, realTargetPath);\n // A bare `..`-prefix test would also reject an in-directory name like `..result`.\n return relativePath !== '..' && !relativePath.startsWith(`..${path.sep}`) && !path.isAbsolute(relativePath);\n}\n\nasync function lstatOrUndefined(target: string): Promise<fs.Stats | undefined> {\n try {\n return await fs.promises.lstat(target);\n } catch {\n return undefined;\n }\n}\n"],"mappings":"uLAeA,eAAsB,EAA6B,EAAgB,EAAoC,CACrG,IAAM,EAAc,MAAMA,EAAAA,QAAG,SAAS,MAAM,CAAM,EAElD,GAAI,EAAY,eAAe,EAAG,CAChC,MAAM,EAAoB,CAAW,EACrC,MAAMA,EAAAA,QAAG,SAAS,QAAQ,MAAMA,EAAAA,QAAG,SAAS,SAAS,CAAM,EAAG,CAAW,EACzE,MACF,CAEA,GAAI,EAAY,YAAY,EAAG,EAIxB,MADkC,EAAiB,CAAW,EAAA,EACpC,YAAY,GAAG,MAAM,EAAoB,CAAW,EACnF,MAAMA,EAAAA,QAAG,SAAS,MAAM,EAAa,CAAE,UAAW,EAAK,CAAC,EACxD,IAAK,IAAM,KAAS,MAAMA,EAAAA,QAAG,SAAS,QAAQ,CAAM,EAClD,MAAM,EAA6BC,EAAAA,QAAK,KAAK,EAAQ,CAAK,EAAGA,EAAAA,QAAK,KAAK,EAAa,CAAK,CAAC,EAE5F,MACF,CAEA,MAAM,EAAoB,CAAW,EACrC,MAAMD,EAAAA,QAAG,SAAS,SAAS,EAAQ,CAAW,CAChD,CAUA,eAAsB,EAAwC,EAAc,EAAkC,CAC5G,IAAM,EAAeC,EAAAA,QAAK,SAAS,EAAM,CAAS,EAClD,GAAI,CAAC,EAAgB,EAAM,CAAS,EAClC,MAAU,MAAM,kCAAkC,EAAU,iBAAiB,GAAM,EAGrF,IAAI,EAAc,EAClB,IAAK,IAAM,KAAS,EAAa,MAAMA,EAAAA,QAAK,GAAG,CAAC,CAAC,OAAO,OAAO,EAAG,CAChE,EAAcA,EAAAA,QAAK,KAAK,EAAa,CAAK,EAC1C,IAAM,EAAQ,MAAM,EAAiB,CAAW,EAC5C,GAAO,YAAY,IACnB,GAAO,MAAM,EAAoB,CAAW,EAChD,MAAMD,EAAAA,QAAG,SAAS,MAAM,CAAW,EACrC,CACF,CAIA,eAAe,EAAoB,EAA+B,CAChE,MAAME,EAAAA,YAAY,CAAM,CAC1B,CAQA,eAAsB,EAAkC,EAAkB,EAA0C,CAClH,MAAM,EAAoB,CAAQ,EAElC,MAAMF,EAAAA,QAAG,SAAS,UAAU,EAAU,EAAM,CAAE,KAAM,IAAK,CAAC,CAC5D,CAGA,SAAgB,EAAgB,EAA2B,EAAiC,CAC1F,IAAM,EAAeC,EAAAA,QAAK,SAAS,EAAmB,CAAc,EAEpE,OAAO,IAAiB,MAAQ,CAAC,EAAa,WAAW,KAAKA,EAAAA,QAAK,KAAK,GAAK,CAACA,EAAAA,QAAK,WAAW,CAAY,CAC5G,CAEA,eAAe,EAAiB,EAA+C,CAC7E,GAAI,CACF,OAAO,MAAMD,EAAAA,QAAG,SAAS,MAAM,CAAM,CACvC,MAAQ,CACN,MACF,CACF"}
|
package/dist/helpers/safeFs.d.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Recursively copy `source` into `destination` without ever following a symlink that already
|
|
3
|
-
* exists at a destination path. A sandboxed submission (running as a different OS user) can plant
|
|
4
|
-
* such a symlink to redirect this trusted-user write outside its working directory, so every
|
|
5
|
-
* pre-existing destination entry is lstat'd and unlinked (never followed) before being written.
|
|
6
|
-
* Symlinks in the SOURCE are recreated as symlinks; their targets are never read.
|
|
7
|
-
*
|
|
8
|
-
* Merges into an existing destination directory (like `fs.cp(..., { recursive: true })`), but a
|
|
9
|
-
* plain `fs.cp` follows destination symlinks nested inside directories, which this avoids.
|
|
10
|
-
*/
|
|
11
|
-
export declare function copyWithoutFollowingSymlinks(source: string, destination: string): Promise<void>;
|
|
12
|
-
/**
|
|
13
|
-
* Create `directory` and the missing levels between it and `root`, replacing anything that is not
|
|
14
|
-
* already a real directory. `fs.mkdir(..., { recursive: true })` follows a symlink it finds on the
|
|
15
|
-
* way, which a submission can plant to place a trusted-user write outside the tree; this walks the
|
|
16
|
-
* levels one at a time instead. Only levels BELOW `root` are inspected, so an unrelated symlink
|
|
17
|
-
* above the caller's tree (`/tmp` on macOS, for instance) is never touched. `root` must already be
|
|
18
|
-
* a directory the harness trusts, and `directory` must be inside it.
|
|
19
|
-
*/
|
|
20
|
-
export declare function createDirectoryWithoutFollowingSymlinks(root: string, directory: string): Promise<void>;
|
|
21
|
-
/**
|
|
22
|
-
* Write `data` to `filePath` without following a symlink already there. `fs.writeFile` follows one,
|
|
23
|
-
* so a sandboxed submission could point a file the harness is about to write (a generated project
|
|
24
|
-
* file, a fixture) at something only the harness can reach and have it overwritten. Trusted
|
|
25
|
-
* callbacks that create files inside a submission directory must use this instead.
|
|
26
|
-
*/
|
|
27
|
-
export declare function writeFileWithoutFollowingSymlinks(filePath: string, data: string | Uint8Array): Promise<void>;
|
|
28
|
-
/** Whether `realTargetPath` is `realDirectoryPath` itself or below it. Both must be realpaths. */
|
|
29
|
-
export declare function isContainedPath(realDirectoryPath: string, realTargetPath: string): boolean;
|
package/dist/helpers/safeFs.js
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{forceRemove as e}from"./sandboxUser.js";import t from"node:fs";import n from"node:path";async function r(e,i){let o=await t.promises.lstat(e);if(o.isSymbolicLink()){await a(i),await t.promises.symlink(await t.promises.readlink(e),i);return}if(o.isDirectory()){(await c(i))?.isDirectory()||await a(i),await t.promises.mkdir(i,{recursive:!0});for(let a of await t.promises.readdir(e))await r(n.join(e,a),n.join(i,a));return}await a(i),await t.promises.copyFile(e,i)}async function i(e,r){let i=n.relative(e,r);if(!s(e,r))throw Error(`directory must be inside root: ${r} is not inside ${e}`);let o=e;for(let e of i.split(n.sep).filter(Boolean)){o=n.join(o,e);let r=await c(o);r?.isDirectory()||(r&&await a(o),await t.promises.mkdir(o))}}async function a(t){await e(t)}async function o(e,n){await a(e),await t.promises.writeFile(e,n,{flag:`wx`})}function s(e,t){let r=n.relative(e,t);return r!==`..`&&!r.startsWith(`..${n.sep}`)&&!n.isAbsolute(r)}async function c(e){try{return await t.promises.lstat(e)}catch{return}}export{r as copyWithoutFollowingSymlinks,i as createDirectoryWithoutFollowingSymlinks,s as isContainedPath,o as writeFileWithoutFollowingSymlinks};
|
|
2
|
-
//# sourceMappingURL=safeFs.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"safeFs.js","names":[],"sources":["../../src/helpers/safeFs.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { forceRemove } from './sandboxUser.js';\n\n/**\n * Recursively copy `source` into `destination` without ever following a symlink that already\n * exists at a destination path. A sandboxed submission (running as a different OS user) can plant\n * such a symlink to redirect this trusted-user write outside its working directory, so every\n * pre-existing destination entry is lstat'd and unlinked (never followed) before being written.\n * Symlinks in the SOURCE are recreated as symlinks; their targets are never read.\n *\n * Merges into an existing destination directory (like `fs.cp(..., { recursive: true })`), but a\n * plain `fs.cp` follows destination symlinks nested inside directories, which this avoids.\n */\nexport async function copyWithoutFollowingSymlinks(source: string, destination: string): Promise<void> {\n const sourceStats = await fs.promises.lstat(source);\n\n if (sourceStats.isSymbolicLink()) {\n await removeExistingEntry(destination);\n await fs.promises.symlink(await fs.promises.readlink(source), destination);\n return;\n }\n\n if (sourceStats.isDirectory()) {\n // A pre-existing symlink (or file) at the destination is removed so the merge target is a real\n // directory the harness owns, not something the submission redirected.\n const existingDestinationStats = await lstatOrUndefined(destination);\n if (!existingDestinationStats?.isDirectory()) await removeExistingEntry(destination);\n await fs.promises.mkdir(destination, { recursive: true });\n for (const entry of await fs.promises.readdir(source)) {\n await copyWithoutFollowingSymlinks(path.join(source, entry), path.join(destination, entry));\n }\n return;\n }\n\n await removeExistingEntry(destination);\n await fs.promises.copyFile(source, destination);\n}\n\n/**\n * Create `directory` and the missing levels between it and `root`, replacing anything that is not\n * already a real directory. `fs.mkdir(..., { recursive: true })` follows a symlink it finds on the\n * way, which a submission can plant to place a trusted-user write outside the tree; this walks the\n * levels one at a time instead. Only levels BELOW `root` are inspected, so an unrelated symlink\n * above the caller's tree (`/tmp` on macOS, for instance) is never touched. `root` must already be\n * a directory the harness trusts, and `directory` must be inside it.\n */\nexport async function createDirectoryWithoutFollowingSymlinks(root: string, directory: string): Promise<void> {\n const relativePath = path.relative(root, directory);\n if (!isContainedPath(root, directory)) {\n throw new Error(`directory must be inside root: ${directory} is not inside ${root}`);\n }\n\n let currentPath = root;\n for (const level of relativePath.split(path.sep).filter(Boolean)) {\n currentPath = path.join(currentPath, level);\n const stats = await lstatOrUndefined(currentPath);\n if (stats?.isDirectory()) continue;\n if (stats) await removeExistingEntry(currentPath);\n await fs.promises.mkdir(currentPath);\n }\n}\n\n// `fs.rm` unlinks a symlink itself rather than following it, and removes files/directories\n// otherwise; `forceRemove` adds the retry for entries a sandboxed submission left unreadable.\nasync function removeExistingEntry(target: string): Promise<void> {\n await forceRemove(target);\n}\n\n/**\n * Write `data` to `filePath` without following a symlink already there. `fs.writeFile` follows one,\n * so a sandboxed submission could point a file the harness is about to write (a generated project\n * file, a fixture) at something only the harness can reach and have it overwritten. Trusted\n * callbacks that create files inside a submission directory must use this instead.\n */\nexport async function writeFileWithoutFollowingSymlinks(filePath: string, data: string | Uint8Array): Promise<void> {\n await removeExistingEntry(filePath);\n // `wx` fails rather than following a symlink planted between the removal and the write.\n await fs.promises.writeFile(filePath, data, { flag: 'wx' });\n}\n\n/** Whether `realTargetPath` is `realDirectoryPath` itself or below it. Both must be realpaths. */\nexport function isContainedPath(realDirectoryPath: string, realTargetPath: string): boolean {\n const relativePath = path.relative(realDirectoryPath, realTargetPath);\n // A bare `..`-prefix test would also reject an in-directory name like `..result`.\n return relativePath !== '..' && !relativePath.startsWith(`..${path.sep}`) && !path.isAbsolute(relativePath);\n}\n\nasync function lstatOrUndefined(target: string): Promise<fs.Stats | undefined> {\n try {\n return await fs.promises.lstat(target);\n } catch {\n return undefined;\n }\n}\n"],"mappings":"+FAeA,eAAsB,EAA6B,EAAgB,EAAoC,CACrG,IAAM,EAAc,MAAM,EAAG,SAAS,MAAM,CAAM,EAElD,GAAI,EAAY,eAAe,EAAG,CAChC,MAAM,EAAoB,CAAW,EACrC,MAAM,EAAG,SAAS,QAAQ,MAAM,EAAG,SAAS,SAAS,CAAM,EAAG,CAAW,EACzE,MACF,CAEA,GAAI,EAAY,YAAY,EAAG,EAIxB,MADkC,EAAiB,CAAW,EAAA,EACpC,YAAY,GAAG,MAAM,EAAoB,CAAW,EACnF,MAAM,EAAG,SAAS,MAAM,EAAa,CAAE,UAAW,EAAK,CAAC,EACxD,IAAK,IAAM,KAAS,MAAM,EAAG,SAAS,QAAQ,CAAM,EAClD,MAAM,EAA6B,EAAK,KAAK,EAAQ,CAAK,EAAG,EAAK,KAAK,EAAa,CAAK,CAAC,EAE5F,MACF,CAEA,MAAM,EAAoB,CAAW,EACrC,MAAM,EAAG,SAAS,SAAS,EAAQ,CAAW,CAChD,CAUA,eAAsB,EAAwC,EAAc,EAAkC,CAC5G,IAAM,EAAe,EAAK,SAAS,EAAM,CAAS,EAClD,GAAI,CAAC,EAAgB,EAAM,CAAS,EAClC,MAAU,MAAM,kCAAkC,EAAU,iBAAiB,GAAM,EAGrF,IAAI,EAAc,EAClB,IAAK,IAAM,KAAS,EAAa,MAAM,EAAK,GAAG,CAAC,CAAC,OAAO,OAAO,EAAG,CAChE,EAAc,EAAK,KAAK,EAAa,CAAK,EAC1C,IAAM,EAAQ,MAAM,EAAiB,CAAW,EAC5C,GAAO,YAAY,IACnB,GAAO,MAAM,EAAoB,CAAW,EAChD,MAAM,EAAG,SAAS,MAAM,CAAW,EACrC,CACF,CAIA,eAAe,EAAoB,EAA+B,CAChE,MAAM,EAAY,CAAM,CAC1B,CAQA,eAAsB,EAAkC,EAAkB,EAA0C,CAClH,MAAM,EAAoB,CAAQ,EAElC,MAAM,EAAG,SAAS,UAAU,EAAU,EAAM,CAAE,KAAM,IAAK,CAAC,CAC5D,CAGA,SAAgB,EAAgB,EAA2B,EAAiC,CAC1F,IAAM,EAAe,EAAK,SAAS,EAAmB,CAAc,EAEpE,OAAO,IAAiB,MAAQ,CAAC,EAAa,WAAW,KAAK,EAAK,KAAK,GAAK,CAAC,EAAK,WAAW,CAAY,CAC5G,CAEA,eAAe,EAAiB,EAA+C,CAC7E,GAAI,CACF,OAAO,MAAM,EAAG,SAAS,MAAM,CAAM,CACvC,MAAQ,CACN,MACF,CACF"}
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
"use strict";const e=require("../_virtual/_rolldown/runtime.cjs");let t=require("node:fs");t=e.__toESM(t,1);let n=require("node:path");n=e.__toESM(n,1);let r=require("node:child_process");r=e.__toESM(r,1);let i=require("node:os");i=e.__toESM(i,1);const a=`EXERCODE_SANDBOX_USER`,o=`/usr/bin/sudo`,s=`/bin/chmod`,c={PATH:`/usr/local/bin:/usr/bin:/bin`};function l(e){return u?{...e,PATH:c.PATH}:{...process.env,...e}}const u=process.env.EXERCODE_SANDBOX_USER||void 0;if(u&&u===i.default.userInfo().username)throw Error(`${a} must name a different OS user than the one running the harness (got "${u}"). Leave it unset to run everything as the current user.`);const d=u?`/usr/bin/timeout`:`timeout`;function f(e){return!u||m(e)?[...e]:[...p(u),...e]}function p(e){return[o,`--preserve-env`,`-u`,e,`--`,`sh`,`-c`,`umask 0; if [ -n "$SANDBOX_LD_LIBRARY_PATH" ]; then export LD_LIBRARY_PATH="$SANDBOX_LD_LIBRARY_PATH"; fi; exec "$0" "$@"`]}function m(e){return u?p(u).every((t,n)=>e[n]===t):!1}function h(e,t){if(!m(e))return[...t,...e];let n=p(u).length;return[...e.slice(0,n),...t,...e.slice(n)]}function g(e){if(!u)return{};let t=e??process.env,n=_(t),r=n.LD_LIBRARY_PATH??t.LD_LIBRARY_PATH??process.env.LD_LIBRARY_PATH;return{HOME:`/home/${u}`,...n,...r&&{SANDBOX_LD_LIBRARY_PATH:r}}}function _(e){let t={};for(let[n,r]of Object.entries(e)){if(!n.startsWith(`SANDBOX_ENV_`))continue;let e=n.slice(12);e&&(t[e]=r)}return t}function v(e){u&&r.default.spawnSync(s,[`-R`,`a+rwX`,e],{env:c})}function y(e,t){if(!u)return;let i=[];for(let r=t;i.push(r),r!==e&&n.default.dirname(r)!==r;r=n.default.dirname(r));r.default.spawnSync(s,[`a+X`,...i],{env:c})}function b(e=[`TERM`,`KILL`]){if(!u)return;let t=[];for(let n of e){let e=w([`pkill`,`-${n}`,`-u`,u]);e.error&&t.push(`pkill -${n}: ${e.error.message}`)}if(!e.includes(`KILL`)){if(t.length>0)throw Error(`failed to signal ${u} processes: ${t.join(`; `)}`);return}let n=x();if(n)throw Error(`failed to terminate ${u} processes: ${n}`)}function x(){for(let e=0;;e++){e>0&&(w([`pkill`,`-KILL`,`-u`,u]),Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,100));let t=r.default.spawnSync(`ps`,[`-o`,`pid=,stat=,nlwp=,comm=`,`-u`,u],{env:c,encoding:`utf8`}),n=t.stderr?.trim();if(t.error)return`cannot list processes: ${t.error.message}`;if(t.signal||t.status!==0&&(t.status!==1||n))return`cannot list processes: ${n||`ps exited with ${t.status??t.signal}`}`;let i=t.stdout.split(`
|
|
2
|
-
`).map(e=>e.trim()).filter(e=>e&&!/^\d+\s+Z\S*\s+1\s/.test(e));if(i.length===0)return;if(e>=10)return i.join(`, `)}}function S(e){u&&w([`chmod`,`-R`,`a+rwX`,e])}function C(e){if(!u)return{cancel:()=>{},fired:()=>!1};let t=Math.ceil(e)+5,n=r.default.spawn(`/bin/sh`,[`-c`,`/bin/sleep ${t}; ${o} -u "$1" pkill -KILL -u "$1"`,`sh`,u],{detached:!0,stdio:`ignore`,env:c}),i=!1;n.on(`error`,()=>{i=!0});let a=!1;n.on(`exit`,()=>{a=!0}),n.unref();let s=Date.now(),l=!1;return{cancel:()=>{if(l=!0,!(n.pid===void 0||a))try{process.kill(-n.pid,`SIGKILL`)}catch{}},fired:()=>i||n.pid===void 0||!l&&Date.now()-s>=t*1e3}}function w(e){return r.default.spawnSync(o,[`-u`,u,...e],{env:c})}async function T(e){try{await t.default.promises.rm(e,{force:!0,recursive:!0})}catch(r){if(!u)throw r;S(e),w([`chmod`,`a+rwX`,n.default.dirname(e)]),await t.default.promises.rm(e,{force:!0,recursive:!0})}}exports.SANDBOX_ENV_PREFIX=`SANDBOX_ENV_`,exports.SANDBOX_USER_ENV_NAME=a,exports.SANDBOX_WATCHDOG_GRACE_SECONDS=5,exports.TIMEOUT_COMMAND=d,exports.forceRemove=T,exports.getSandboxUserEnvOverrides=g,exports.getTrustedHelperEnv=l,exports.isSandboxWrappedCommand=m,exports.killSandboxUserProcesses=b,exports.makeAccessibleToSandboxUser=v,exports.makeTraversableBySandboxUser=y,exports.prependInsideSandboxWrapper=h,exports.relaxPermissionsAsSandboxUser=S,exports.sandboxUserName=u,exports.startSandboxTimeoutWatchdog=C,exports.wrapCommandWithSandboxUser=f;
|
|
3
|
-
//# sourceMappingURL=sandboxUser.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sandboxUser.cjs","names":["os","path","child_process","fs"],"sources":["../../src/helpers/sandboxUser.ts"],"sourcesContent":["import child_process from 'node:child_process';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\n/**\n * Name of the environment variable through which a judge server tells problem-utils to run\n * untrusted submitted programs as the given unprivileged OS user via sudo.\n *\n * The judge server sets this variable only when it runs the judge harness (e.g. `judge.ts`) as its\n * own trusted user, so that problem files (test cases and the harness itself) stay unreadable to\n * submissions while the submissions themselves run under the sandbox user. When the variable is\n * absent or empty (local development, course authoring, or an all-sandbox judge run), commands run\n * as the current user like before.\n *\n * Contract for the delegating judge server (delegation is Linux-only — sudo user separation and\n * `/home/<user>` homes are provisioned in the judge Docker image; never set this on macOS):\n * - The harness process environment is forwarded to sandboxed submissions (sudo runs with\n * `--preserve-env`), so it must not contain secrets beyond what submissions may see. The one\n * exception is the set glibc strips when executing the setuid `sudo` (secure-execution mode:\n * `LD_*`, `TMPDIR`, `LOCPATH`, `NLSPATH`, `TZDIR`, … — see ld.so(8)); no sudoers setting can\n * recover those, and only `LD_LIBRARY_PATH` is restored, by the wrapper below.\n * - sudoers must let the harness user run arbitrary commands as the sandbox user without a\n * password, pass the environment through, and keep sudo off a pseudo-terminal (a pty would merge\n * stderr into stdout and CRLF-mangle output when the harness happens to run from a terminal):\n * e.g. `Defaults:<harness> !env_reset, !env_delete, !env_check, !secure_path, !use_pty` plus\n * `<harness> ALL=(<sandbox>) NOPASSWD:SETENV: ALL`.\n * - Variables meant for the submitted program rather than for the harness must be passed under\n * {@link SANDBOX_ENV_PREFIX}; problem-utils strips that prefix when it builds a submission's\n * environment.\n * - The sandbox user's home directory must exist at `/home/<sandbox user>` and be writable. It is\n * shared across sequential requests, so the server is responsible for resetting whatever\n * cross-request persistence there matters to it.\n */\nexport const SANDBOX_USER_ENV_NAME = 'EXERCODE_SANDBOX_USER';\n\n// Absolute paths so a submission-influenced `PATH` cannot redirect binaries that the trusted\n// harness user executes (`sudo` is also setuid and must be the real one).\nconst SUDO_PATH = '/usr/bin/sudo';\nconst CHMOD_PATH = '/bin/chmod';\nconst SLEEP_PATH = '/bin/sleep';\n// Minimal environment for trusted helper processes; never forward the harness environment to them.\nconst MINIMAL_ENV = { PATH: '/usr/local/bin:/usr/bin:/bin' } as const;\n\n/**\n * Environment for helper processes the *trusted* harness user runs (`ps`, `xwininfo`, `Xvfb`, …).\n * The judge server overlays caller-supplied variables onto the harness environment, and a sandboxed\n * submission can create executables in its persistent home, so resolving these helpers through the\n * inherited `PATH` would hand the submission code execution as the harness user. Node resolves the\n * command through the child environment's `PATH`, so a fixed `PATH` here pins them to system paths.\n */\nexport function getTrustedHelperEnv(extraEnv?: NodeJS.ProcessEnv): NodeJS.ProcessEnv {\n // Without delegation the submission runs as this same user, so there is no boundary to defend and\n // pinning `PATH` would only break setups whose X11/`ps` binaries live elsewhere (e.g. /usr/games,\n // /snap/bin, Nix). Keep the inherited environment there, exactly as before delegation existed.\n if (!sandboxUserName) return { ...process.env, ...extraEnv };\n // `PATH` last: a caller passing a whole environment through would otherwise reinstate the\n // submission-influenced one this function exists to replace.\n return { ...extraEnv, PATH: MINIMAL_ENV.PATH };\n}\n\nexport const sandboxUserName = process.env[SANDBOX_USER_ENV_NAME] || undefined;\n\n// Fail fast on the catastrophic misconfiguration where the sandbox user is the harness's own user:\n// cleanup (`killSandboxUserProcesses`) would then SIGKILL the harness itself on the first run.\nif (sandboxUserName && sandboxUserName === os.userInfo().username) {\n throw new Error(\n `${SANDBOX_USER_ENV_NAME} must name a different OS user than the one running the harness (got \"${sandboxUserName}\"). Leave it unset to run everything as the current user.`\n );\n}\n\n/**\n * The deadline supervisor. Absolute under delegation: the wrapper's `exec \"$0\"` resolves it through\n * the submission's own `PATH` (which a judge server may set via {@link SANDBOX_ENV_PREFIX}), so a\n * bare name would let a submission replace the very process that enforces its time limit. The bare\n * name is kept without delegation, where `timeout` may live elsewhere (Homebrew coreutils).\n */\nexport const TIMEOUT_COMMAND = sandboxUserName ? '/usr/bin/timeout' : 'timeout';\n\n/**\n * Wrap a command so it runs as the sandbox user. `umask 0` makes every file the sandboxed process\n * creates world-writable, so the harness user can clean it up without privileges. The wrapper also\n * restores `LD_LIBRARY_PATH`, which ld.so strips across the setuid `sudo` exec. Put `timeout`\n * inside the wrapped command: the harness user cannot signal the root-owned `sudo` process, so an\n * outer timer alone could not stop a runaway submission.\n */\nexport function wrapCommandWithSandboxUser(command: readonly [string, ...string[]]): [string, ...string[]] {\n if (!sandboxUserName) return [...command];\n // Idempotent; see {@link isSandboxWrappedCommand}.\n if (isSandboxWrappedCommand(command)) return [...command];\n return [...buildSandboxWrapperPrefix(sandboxUserName), ...command];\n}\n\nconst SANDBOX_WRAPPER_SCRIPT =\n 'umask 0; if [ -n \"$SANDBOX_LD_LIBRARY_PATH\" ]; then export LD_LIBRARY_PATH=\"$SANDBOX_LD_LIBRARY_PATH\"; fi; exec \"$0\" \"$@\"';\n\nfunction buildSandboxWrapperPrefix(user: string): [string, ...string[]] {\n return [SUDO_PATH, '--preserve-env', '-u', user, '--', 'sh', '-c', SANDBOX_WRAPPER_SCRIPT];\n}\n\n/**\n * Whether the command was already wrapped by {@link wrapCommandWithSandboxUser}. The presets hand\n * custom runners a wrapped command, and a runner may forward it to another helper that wraps too;\n * a nested wrapper's inner `sudo` would run AS the sandbox user, which sudoers does not authorize,\n * so every test case of such a problem would fail only under delegation.\n *\n * Matches the wrapper prefix exactly rather than looking for `sudo` anywhere: a submission-derived\n * command could otherwise carry a literal `/usr/bin/sudo` argument and skip wrapping entirely,\n * which would run the submission as the trusted harness user.\n */\nexport function isSandboxWrappedCommand(command: readonly string[]): boolean {\n if (!sandboxUserName) return false;\n const prefix = buildSandboxWrapperPrefix(sandboxUserName);\n return prefix.every((argument, index) => command[index] === argument);\n}\n\n/**\n * Insert `innerPrefix` (e.g. a `time` measurement prefix) so that it runs INSIDE the sandbox\n * wrapper when `command` is wrapped, and simply in front otherwise. Prefixing a wrapped command\n * from the outside would run `innerPrefix` as the trusted harness user while its arguments (such\n * as an output path) point into a sandbox-writable directory, where a submission can plant a\n * symlink and have the harness truncate an arbitrary file it owns.\n */\nexport function prependInsideSandboxWrapper(\n command: readonly [string, ...string[]],\n innerPrefix: readonly string[]\n): [string, ...string[]] {\n if (!isSandboxWrappedCommand(command)) return [...innerPrefix, ...command] as [string, ...string[]];\n const prefixLength = buildSandboxWrapperPrefix(sandboxUserName as string).length;\n return [...command.slice(0, prefixLength), ...innerPrefix, ...command.slice(prefixLength)] as [string, ...string[]];\n}\n\n/**\n * Environment overrides for sandboxed processes: a writable home and the `LD_LIBRARY_PATH`\n * smuggled past the setuid `sudo` exec (see {@link wrapCommandWithSandboxUser}). Pass the\n * environment the command will actually run with so a caller-supplied `LD_LIBRARY_PATH` survives\n * the exec; the harness's own value is only the fallback.\n */\nexport function getSandboxUserEnvOverrides(env?: NodeJS.ProcessEnv): NodeJS.ProcessEnv {\n if (!sandboxUserName) return {};\n const sourceEnv = env ?? process.env;\n const submissionOnlyEnv = unwrapSubmissionOnlyEnv(sourceEnv);\n // A submission-only `LD_LIBRARY_PATH` must be the one smuggled past the exec, not the harness's.\n const ldLibraryPath = submissionOnlyEnv.LD_LIBRARY_PATH ?? sourceEnv.LD_LIBRARY_PATH ?? process.env.LD_LIBRARY_PATH;\n return {\n HOME: `/home/${sandboxUserName}`,\n ...submissionOnlyEnv,\n // Last: the wrapper reads this one, so a caller must not be able to overwrite it.\n ...(ldLibraryPath && { SANDBOX_LD_LIBRARY_PATH: ldLibraryPath }),\n };\n}\n\n/**\n * Prefix under which a delegating judge server passes variables that belong to the submitted\n * program alone. Applying a request's variables to the harness itself would let a submission point\n * e.g. `PATH` or `NODE_OPTIONS` at its own files and get code executed as the trusted harness user,\n * so the server prefixes them and problem-utils strips the prefix back off here, where the\n * environment of a sandboxed submission is built.\n */\nexport const SANDBOX_ENV_PREFIX = 'SANDBOX_ENV_';\n\nfunction unwrapSubmissionOnlyEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {\n const unwrapped: NodeJS.ProcessEnv = {};\n for (const [name, value] of Object.entries(env)) {\n if (!name.startsWith(SANDBOX_ENV_PREFIX)) continue;\n const unwrappedName = name.slice(SANDBOX_ENV_PREFIX.length);\n if (unwrappedName) unwrapped[unwrappedName] = value;\n }\n return unwrapped;\n}\n\n/**\n * Make harness-user-created files under the given path readable, and directories writable, for the\n * sandbox user, so sandboxed programs can read their sources and create outputs next to them.\n */\nexport function makeAccessibleToSandboxUser(targetPath: string): void {\n if (!sandboxUserName) return;\n // Files owned by the sandbox user fail to chmod but are already world-writable (umask 0).\n child_process.spawnSync(CHMOD_PATH, ['-R', 'a+rwX', targetPath], { env: MINIMAL_ENV });\n}\n\n/**\n * Let the sandbox user traverse (but not modify) every directory from `rootPath` down to\n * `directoryPath`, e.g. the synthesized ancestors of a temporary copy.\n */\nexport function makeTraversableBySandboxUser(rootPath: string, directoryPath: string): void {\n if (!sandboxUserName) return;\n const directories: string[] = [];\n for (let current = directoryPath; ; current = path.dirname(current)) {\n directories.push(current);\n if (current === rootPath || path.dirname(current) === current) break;\n }\n child_process.spawnSync(CHMOD_PATH, ['a+X', ...directories], { env: MINIMAL_ENV });\n}\n\n/**\n * Kill the sandbox user's processes with the given signals. The harness user cannot signal another\n * user's processes (nor the root-owned `sudo` wrapper), so this goes through sudo. Killing every\n * sandbox process is safe because the judge server handles one request at a time. The default\n * sends SIGTERM immediately followed by SIGKILL (final cleanup); callers that want a grace period\n * send `['TERM']`, wait, and then send `['KILL']`.\n *\n * Fails closed: a `['TERM']`-only sweep throws when its `sudo` could not be spawned (e.g. a\n * submission exhausted the PID cgroup); a sweep including SIGKILL throws when a sandbox process is\n * still alive, or cannot be listed, afterwards. Reporting success would leave that process running\n * next to the next request on this instance.\n */\nexport function killSandboxUserProcesses(signals: readonly ('TERM' | 'KILL')[] = ['TERM', 'KILL']): void {\n if (!sandboxUserName) return;\n // One direct call per signal instead of one `sh -c 'pkill ...; pkill ...'`: the wrapping shell\n // would run as the sandbox user too, so the first pkill would kill it before the second ran.\n // Exit statuses are not inspected: `sudo` exits with 1 both when `pkill` matched nothing and on\n // its own failures, and a sweep can itself be killed by a concurrent watchdog sweep\n // ({@link startSandboxTimeoutWatchdog}'s `pkill -KILL` matches it). What matters is the outcome.\n const spawnErrors: string[] = [];\n for (const signal of signals) {\n const result = runAsSandboxUser(['pkill', `-${signal}`, '-u', sandboxUserName]);\n if (result.error) spawnErrors.push(`pkill -${signal}: ${result.error.message}`);\n }\n if (!signals.includes('KILL')) {\n // Survivors are expected during the caller's grace period, so only a sweep that never started\n // is a failure here.\n if (spawnErrors.length > 0) {\n throw new Error(`failed to signal ${sandboxUserName} processes: ${spawnErrors.join('; ')}`);\n }\n return;\n }\n const survivors = findSurvivingSandboxUserProcesses();\n if (survivors) throw new Error(`failed to terminate ${sandboxUserName} processes: ${survivors}`);\n}\n\n/**\n * Lists the sandbox user's live processes as the harness user (listing needs no privilege), or\n * `undefined` when none remain. SIGKILL is re-sent between attempts: a child forked after `pkill`\n * scanned `/proc` was never signalled, and a process in uninterruptible sleep dies only once its\n * I/O completes. Just-killed processes linger as zombies until reaped; a zombie row is ignored only\n * when it has a single thread, because a thread-group leader that exited via `pthread_exit` shows\n * as a zombie while its other threads keep running.\n */\nfunction findSurvivingSandboxUserProcesses(): string | undefined {\n for (let attempt = 0; ; attempt++) {\n if (attempt > 0) {\n runAsSandboxUser(['pkill', '-KILL', '-u', sandboxUserName as string]);\n Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 100);\n }\n const result = child_process.spawnSync('ps', ['-o', 'pid=,stat=,nlwp=,comm=', '-u', sandboxUserName as string], {\n env: MINIMAL_ENV,\n encoding: 'utf8',\n });\n // `ps` exits with 1 both when no process matched and on its own errors, which it reports on\n // stderr; anything else is a failure to list, which must count as a failure to verify.\n const stderr = result.stderr?.trim();\n if (result.error) return `cannot list processes: ${result.error.message}`;\n if (result.signal || (result.status !== 0 && (result.status !== 1 || stderr))) {\n return `cannot list processes: ${stderr || `ps exited with ${result.status ?? result.signal}`}`;\n }\n const survivors = result.stdout\n .split('\\n')\n .map((line) => line.trim())\n .filter((line) => line && !/^\\d+\\s+Z\\S*\\s+1\\s/.test(line));\n if (survivors.length === 0) return undefined;\n if (attempt >= 10) return survivors.join(', ');\n }\n}\n\n/**\n * Let the sandbox user reopen the permissions of its own files under the given path, for\n * harness-side traversal/cleanup of trees where a sandboxed process restricted permissions\n * (some tools chmod their outputs regardless of umask).\n */\nexport function relaxPermissionsAsSandboxUser(targetPath: string): void {\n if (!sandboxUserName) return;\n runAsSandboxUser(['chmod', '-R', 'a+rwX', targetPath]);\n}\n\n/**\n * How long after a command's own deadline {@link startSandboxTimeoutWatchdog} force-kills the\n * sandbox user. Callers waiting for the watchdog to end a submission must wait at least this long.\n */\nexport const SANDBOX_WATCHDOG_GRACE_SECONDS = 5;\n\n/** Cancels a {@link startSandboxTimeoutWatchdog}; `fired` reports whether its deadline elapsed. */\nexport interface SandboxTimeoutWatchdog {\n cancel(): void;\n fired(): boolean;\n}\n\n/**\n * Start a harness-owned watchdog that force-kills every sandbox process after the given deadline.\n * A sandboxed submission can signal its own `timeout` supervisor (same UID), and a synchronous\n * spawn blocks the harness's event loop, so without this external deadline a submission could run\n * until the outer judge-server limit. The watchdog's `sh`/`sleep` run as the harness user, out of\n * the submission's reach; killing the watchdog's process group cancels it before it spawns sudo.\n *\n * ALWAYS cancel in a `finally`: the watchdog is detached and `unref`'d, so a leaked one keeps\n * running after the harness exits and would SIGKILL a later request's submission.\n */\nexport function startSandboxTimeoutWatchdog(timeoutSeconds: number): SandboxTimeoutWatchdog {\n if (!sandboxUserName) return { cancel: () => {}, fired: () => false };\n\n const deadlineSeconds = Math.ceil(timeoutSeconds) + SANDBOX_WATCHDOG_GRACE_SECONDS;\n const watchdog = child_process.spawn(\n '/bin/sh',\n ['-c', `${SLEEP_PATH} ${deadlineSeconds}; ${SUDO_PATH} -u \"$1\" pkill -KILL -u \"$1\"`, 'sh', sandboxUserName],\n { detached: true, stdio: 'ignore', env: MINIMAL_ENV }\n );\n // Without a listener, a spawn failure (e.g. the submission exhausted the PID cgroup) would be an\n // unhandled 'error' event that terminates the harness. Fail closed instead: callers check\n // `fired()`, and a watchdog that never started reports its deadline as elapsed.\n let spawnFailed = false;\n watchdog.on('error', () => {\n spawnFailed = true;\n });\n let exited = false;\n watchdog.on('exit', () => {\n exited = true;\n });\n watchdog.unref();\n\n const startTimeMilliseconds = Date.now();\n let cancelled = false;\n return {\n cancel: () => {\n cancelled = true;\n if (watchdog.pid === undefined || exited) return;\n try {\n process.kill(-watchdog.pid, 'SIGKILL');\n } catch {\n // The watchdog already fired and exited.\n }\n },\n // The `exit`/`error` events cannot be observed from a synchronous caller (they need the event\n // loop), so decide by elapsed time, which is what the watchdog itself waits on.\n fired: () =>\n spawnFailed ||\n watchdog.pid === undefined ||\n (!cancelled && Date.now() - startTimeMilliseconds >= deadlineSeconds * 1000),\n };\n}\n\n/**\n * Run a helper as the sandbox user with a minimal environment. The harness environment must not be\n * passed: sudoers forwards it verbatim, and after sudo drops privileges the helper's\n * `/proc/<pid>/environ` becomes readable by every other sandbox process.\n */\nfunction runAsSandboxUser(command: [string, ...string[]]): child_process.SpawnSyncReturns<Buffer> {\n return child_process.spawnSync(SUDO_PATH, ['-u', sandboxUserName as string, ...command], { env: MINIMAL_ENV });\n}\n\n/**\n * `fs.rm`-like removal with a fallback for trees holding sandbox-user-owned entries whose\n * permissions block deletion: let the sandbox user reopen its own files first, then retry. The\n * containing directory is relaxed as well — unlinking an entry needs write permission on its\n * parent, so a submission that chmods a directory it owns would otherwise make everything inside it\n * undeletable.\n */\nexport async function forceRemove(targetPath: string): Promise<void> {\n try {\n await fs.promises.rm(targetPath, { force: true, recursive: true });\n } catch (error) {\n if (!sandboxUserName) throw error;\n relaxPermissionsAsSandboxUser(targetPath);\n // Not recursive: only the containing directory's own write bit governs the unlink.\n runAsSandboxUser(['chmod', 'a+rwX', path.dirname(targetPath)]);\n await fs.promises.rm(targetPath, { force: true, recursive: true });\n }\n}\n"],"mappings":"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"}
|