@exercode/problem-utils 3.0.0 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/README.md +23 -0
  2. package/dist/cli/checkAllProblems.cjs +1 -1
  3. package/dist/cli/checkAllProblems.js +1 -1
  4. package/dist/cli/exercodeProblem.cjs +8 -2
  5. package/dist/cli/exercodeProblem.cjs.map +1 -1
  6. package/dist/cli/exercodeProblem.js +8 -2
  7. package/dist/cli/exercodeProblem.js.map +1 -1
  8. package/dist/cli/runSingleHarness.js.map +1 -1
  9. package/dist/cli/validateLearningMaterial.cjs +2 -0
  10. package/dist/cli/validateLearningMaterial.cjs.map +1 -0
  11. package/dist/cli/validateLearningMaterial.d.ts +7 -0
  12. package/dist/cli/validateLearningMaterial.js +2 -0
  13. package/dist/cli/validateLearningMaterial.js.map +1 -0
  14. package/dist/helpers/judgeByStaticAnalysis.cjs +3 -3
  15. package/dist/helpers/judgeByStaticAnalysis.js +3 -3
  16. package/dist/helpers/runCommandInTemporaryPackageManagerProject.cjs +1 -1
  17. package/dist/helpers/runCommandInTemporaryPackageManagerProject.cjs.map +1 -1
  18. package/dist/helpers/runCommandInTemporaryPackageManagerProject.js +1 -1
  19. package/dist/helpers/runCommandInTemporaryPackageManagerProject.js.map +1 -1
  20. package/dist/helpers/runHarnessProcess.cjs.map +1 -1
  21. package/dist/helpers/runHarnessProcess.d.ts +4 -1
  22. package/dist/helpers/runHarnessProcess.js.map +1 -1
  23. package/dist/helpers/spawnWithLimits.cjs +2 -0
  24. package/dist/helpers/spawnWithLimits.cjs.map +1 -0
  25. package/dist/helpers/spawnWithLimits.d.ts +27 -0
  26. package/dist/helpers/spawnWithLimits.js +2 -0
  27. package/dist/helpers/spawnWithLimits.js.map +1 -0
  28. package/dist/helpers/spawnWithTimeout.cjs +4 -0
  29. package/dist/helpers/spawnWithTimeout.cjs.map +1 -0
  30. package/dist/helpers/spawnWithTimeout.d.ts +19 -0
  31. package/dist/helpers/spawnWithTimeout.js +4 -0
  32. package/dist/helpers/spawnWithTimeout.js.map +1 -0
  33. package/dist/helpers/stdioJudgeRules.cjs +2 -0
  34. package/dist/helpers/stdioJudgeRules.cjs.map +1 -0
  35. package/dist/helpers/stdioJudgeRules.d.ts +4 -0
  36. package/dist/helpers/stdioJudgeRules.js +2 -0
  37. package/dist/helpers/stdioJudgeRules.js.map +1 -0
  38. package/dist/helpers/temporaryProblemDirCopy.cjs +1 -1
  39. package/dist/helpers/temporaryProblemDirCopy.js +1 -1
  40. package/dist/index.cjs +1 -1
  41. package/dist/index.d.ts +5 -0
  42. package/dist/index.js +1 -1
  43. package/dist/learningMaterial/frontmatter.cjs +2 -0
  44. package/dist/learningMaterial/frontmatter.cjs.map +1 -0
  45. package/dist/learningMaterial/frontmatter.d.ts +8 -0
  46. package/dist/learningMaterial/frontmatter.js +2 -0
  47. package/dist/learningMaterial/frontmatter.js.map +1 -0
  48. package/dist/learningMaterial/fsHelpers.cjs +2 -0
  49. package/dist/learningMaterial/fsHelpers.cjs.map +1 -0
  50. package/dist/learningMaterial/fsHelpers.d.ts +30 -0
  51. package/dist/learningMaterial/fsHelpers.js +2 -0
  52. package/dist/learningMaterial/fsHelpers.js.map +1 -0
  53. package/dist/learningMaterial/schemas.cjs +2 -0
  54. package/dist/learningMaterial/schemas.cjs.map +1 -0
  55. package/dist/learningMaterial/schemas.d.ts +241 -0
  56. package/dist/learningMaterial/schemas.js +2 -0
  57. package/dist/learningMaterial/schemas.js.map +1 -0
  58. package/dist/learningMaterial/validateContest.cjs +2 -0
  59. package/dist/learningMaterial/validateContest.cjs.map +1 -0
  60. package/dist/learningMaterial/validateContest.d.ts +16 -0
  61. package/dist/learningMaterial/validateContest.js +2 -0
  62. package/dist/learningMaterial/validateContest.js.map +1 -0
  63. package/dist/learningMaterial/validateCourse.cjs +2 -0
  64. package/dist/learningMaterial/validateCourse.cjs.map +1 -0
  65. package/dist/learningMaterial/validateCourse.d.ts +11 -0
  66. package/dist/learningMaterial/validateCourse.js +2 -0
  67. package/dist/learningMaterial/validateCourse.js.map +1 -0
  68. package/dist/learningMaterial/validateMaterial.cjs +4 -0
  69. package/dist/learningMaterial/validateMaterial.cjs.map +1 -0
  70. package/dist/learningMaterial/validateMaterial.d.ts +34 -0
  71. package/dist/learningMaterial/validateMaterial.js +4 -0
  72. package/dist/learningMaterial/validateMaterial.js.map +1 -0
  73. package/dist/learningMaterial/validateProblem.cjs +2 -0
  74. package/dist/learningMaterial/validateProblem.cjs.map +1 -0
  75. package/dist/learningMaterial/validateProblem.d.ts +6 -0
  76. package/dist/learningMaterial/validateProblem.js +2 -0
  77. package/dist/learningMaterial/validateProblem.js.map +1 -0
  78. package/dist/learningMaterial/validationResult.cjs +3 -0
  79. package/dist/learningMaterial/validationResult.cjs.map +1 -0
  80. package/dist/learningMaterial/validationResult.d.ts +9 -0
  81. package/dist/learningMaterial/validationResult.js +3 -0
  82. package/dist/learningMaterial/validationResult.js.map +1 -0
  83. package/dist/presets/command.cjs +1 -1
  84. package/dist/presets/command.cjs.map +1 -1
  85. package/dist/presets/command.js +1 -1
  86. package/dist/presets/command.js.map +1 -1
  87. package/dist/presets/evaluation.cjs +2 -2
  88. package/dist/presets/evaluation.js +2 -2
  89. package/dist/presets/guiCommand.cjs +2 -2
  90. package/dist/presets/guiCommand.cjs.map +1 -1
  91. package/dist/presets/guiCommand.js +2 -2
  92. package/dist/presets/guiCommand.js.map +1 -1
  93. package/dist/presets/stdio.cjs +1 -1
  94. package/dist/presets/stdio.cjs.map +1 -1
  95. package/dist/presets/stdio.js +1 -1
  96. package/dist/presets/stdio.js.map +1 -1
  97. package/package.json +2 -1
  98. package/dist/helpers/spawnSyncWithTimeout.cjs +0 -2
  99. package/dist/helpers/spawnSyncWithTimeout.cjs.map +0 -1
  100. package/dist/helpers/spawnSyncWithTimeout.d.ts +0 -5
  101. package/dist/helpers/spawnSyncWithTimeout.js +0 -2
  102. package/dist/helpers/spawnSyncWithTimeout.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"runCommandInTemporaryPackageManagerProject.js","names":["childProcess"],"sources":["../../src/helpers/runCommandInTemporaryPackageManagerProject.ts"],"sourcesContent":["import childProcess from 'node:child_process';\nimport fs from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport { forciblyRemoveDirectory } from './temporaryProblemDirCopy.js';\n\nexport type PackageManager = 'bun' | 'cargo' | 'go' | 'gradle' | 'maven' | 'npm' | 'pnpm' | 'ruby' | 'uv' | 'yarn';\ntype PackageManagerInstallCommand = readonly [string, ...string[]];\n\nexport interface PackageManagerCommandRunResult {\n stdin: string;\n stdout: string;\n stderr: string;\n status: number | undefined;\n timeSeconds: number;\n memoryBytes: number;\n timedOut: boolean;\n signal: NodeJS.Signals | undefined;\n outputLimitExceeded: boolean;\n}\n\nexport interface RunCommandInTemporaryPackageManagerProjectOptions {\n cwd: string;\n projectDir: string;\n packageManager: PackageManager;\n command: readonly [string, ...string[]] | ((context: { runDir: string }) => readonly [string, ...string[]]);\n /**\n * Set to false when `command` prepares the dependencies it needs.\n * Defaults to true.\n */\n prepareDependencies?: boolean;\n stdin?: string;\n env?: NodeJS.ProcessEnv;\n timeLimitSeconds: number;\n outputLimitBytes?: number;\n tempDirPrefix?: string;\n projectFilePaths?: readonly string[];\n}\n\nconst packageManagerProjectFilePaths = {\n bun: ['package.json', 'bun.lock', 'bun.lockb'],\n cargo: ['Cargo.toml', 'Cargo.lock'],\n go: ['go.mod', 'go.sum'],\n gradle: [\n 'build.gradle',\n 'build.gradle.kts',\n 'settings.gradle',\n 'settings.gradle.kts',\n 'gradle.properties',\n 'gradle.lockfile',\n 'buildscript-gradle.lockfile',\n 'gradle',\n 'gradlew',\n 'gradlew.bat',\n ],\n maven: ['pom.xml', '.mvn', 'mvnw', 'mvnw.cmd'],\n npm: ['package.json', 'package-lock.json'],\n pnpm: ['package.json', 'pnpm-lock.yaml', 'pnpm-workspace.yaml'],\n ruby: ['Gemfile', 'Gemfile.lock', '.ruby-version'],\n uv: ['pyproject.toml', 'uv.lock'],\n yarn: ['package.json', 'yarn.lock', '.yarnrc', '.yarnrc.yml', '.yarn'],\n} as const satisfies Record<PackageManager, readonly string[]>;\n\nconst packageManagerInstallCommandResolvers = {\n bun: resolveBunInstallCommand,\n cargo: resolveCargoInstallCommand,\n go: resolveGoInstallCommand,\n gradle: resolveGradleInstallCommand,\n maven: resolveMavenInstallCommand,\n npm: resolveNpmInstallCommand,\n pnpm: resolvePnpmInstallCommand,\n ruby: resolveRubyInstallCommand,\n uv: resolveUvInstallCommand,\n yarn: resolveYarnInstallCommand,\n} as const satisfies Record<PackageManager, (runDir: string) => Promise<PackageManagerInstallCommand | undefined>>;\n\nconst defaultOutputLimitBytes = 50 * 1024 * 1024;\nconst killGracePeriodMilliseconds = 1000;\nconst timeCommand = resolveTimeCommand();\n\n/**\n * Copies a submission directory to a temporary directory, overlays package\n * manager project files from the problem directory, prepares dependencies,\n * runs a command, and then removes the temporary directory.\n */\nexport async function runCommandInTemporaryPackageManagerProject(\n options: RunCommandInTemporaryPackageManagerProjectOptions\n): Promise<PackageManagerCommandRunResult> {\n const runDir = await fs.mkdtemp(path.join(os.tmpdir(), options.tempDirPrefix ?? 'exercode-'));\n try {\n await fs.cp(options.cwd, runDir, { recursive: true });\n await copyPackageManagerProjectFiles({\n packageManager: options.packageManager,\n projectDir: options.projectDir,\n runDir,\n projectFilePaths: options.projectFilePaths,\n });\n\n const env = options.env ? { ...process.env, ...options.env } : process.env;\n const installCommand =\n options.prepareDependencies === false ? undefined : await resolveInstallCommand(options.packageManager, runDir);\n const command = typeof options.command === 'function' ? options.command({ runDir }) : options.command;\n const startedAt = Date.now();\n const outputLimitBytes = options.outputLimitBytes ?? defaultOutputLimitBytes;\n let installResult: Awaited<ReturnType<typeof spawnWithInput>> | undefined;\n\n if (installCommand) {\n installResult = await spawnWithInput(installCommand, {\n cwd: runDir,\n env,\n outputLimitBytes,\n stdin: '',\n timeLimitSeconds: options.timeLimitSeconds,\n });\n if (isFailedSpawnResult(installResult)) {\n return toPackageManagerCommandRunResult({\n elapsedTimeSeconds: (Date.now() - startedAt) / 1000,\n options,\n result: installResult,\n });\n }\n }\n\n const remainingTimeLimitSeconds = options.timeLimitSeconds - (Date.now() - startedAt) / 1000;\n if (remainingTimeLimitSeconds <= 0) {\n return {\n stdin: options.stdin ?? '',\n stdout: installResult?.stdout ?? '',\n stderr: installResult?.stderr ?? '',\n status: 0,\n timeSeconds: options.timeLimitSeconds + 1e-3,\n memoryBytes: installResult?.memoryBytes ?? 0,\n timedOut: true,\n signal: installResult?.signal,\n outputLimitExceeded: false,\n };\n }\n\n const result = await spawnWithInput(command, {\n cwd: runDir,\n env,\n outputLimitBytes,\n stdin: options.stdin ?? '',\n timeLimitSeconds: remainingTimeLimitSeconds,\n });\n const elapsedTimeSeconds = (Date.now() - startedAt) / 1000;\n\n if (installResult) {\n return toPackageManagerCommandRunResult({\n elapsedTimeSeconds,\n options,\n result: {\n ...result,\n timeSeconds: installResult.timeSeconds + result.timeSeconds,\n memoryBytes: Math.max(installResult.memoryBytes, result.memoryBytes),\n },\n });\n }\n\n return toPackageManagerCommandRunResult({ elapsedTimeSeconds, options, result });\n } finally {\n // The command may have left permission-locked entries (e.g. a mode-000 directory).\n await forciblyRemoveDirectory(runDir);\n }\n}\n\nfunction toPackageManagerCommandRunResult(context: {\n elapsedTimeSeconds: number;\n options: RunCommandInTemporaryPackageManagerProjectOptions;\n result: Awaited<ReturnType<typeof spawnWithInput>>;\n}): PackageManagerCommandRunResult {\n return {\n stdin: context.options.stdin ?? '',\n stdout: context.result.stdout,\n stderr: context.result.stderr,\n status: context.result.timedOut || context.result.outputLimitExceeded ? 0 : context.result.status,\n timeSeconds: context.result.timedOut\n ? context.options.timeLimitSeconds + 1e-3\n : context.result.timeSeconds || context.elapsedTimeSeconds,\n memoryBytes: context.result.memoryBytes,\n timedOut: context.result.timedOut,\n signal: context.result.signal,\n outputLimitExceeded: context.result.outputLimitExceeded,\n };\n}\n\nfunction resolveInstallCommand(\n packageManager: PackageManager,\n runDir: string\n): Promise<PackageManagerInstallCommand | undefined> {\n return packageManagerInstallCommandResolvers[packageManager](runDir);\n}\n\nfunction isFailedSpawnResult(result: Awaited<ReturnType<typeof spawnWithInput>>): boolean {\n return result.status !== 0 || result.timedOut || result.outputLimitExceeded;\n}\n\nasync function resolveBunInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'package.json')))) return undefined;\n // Bun supports --silent and it keeps successful preparation output out of judge output buffers.\n return (await hasAnyPath(runDir, ['bun.lock', 'bun.lockb']))\n ? ['bun', 'install', '--frozen-lockfile', '--silent']\n : ['bun', 'install', '--silent'];\n}\n\nasync function resolveCargoInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'Cargo.toml')))) return undefined;\n return (await pathExists(path.join(runDir, 'Cargo.lock'))) ? ['cargo', 'fetch', '--locked'] : ['cargo', 'fetch'];\n}\n\nasync function resolveGoInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'go.mod')))) return undefined;\n return ['go', 'mod', 'download'];\n}\n\nasync function resolveGradleInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (\n !(await hasAnyPath(runDir, [\n 'build.gradle',\n 'build.gradle.kts',\n 'settings.gradle',\n 'settings.gradle.kts',\n 'gradlew',\n 'gradlew.bat',\n ]))\n )\n return undefined;\n const args = ['--no-daemon', '--quiet', 'dependencies'] as const;\n if (process.platform === 'win32') {\n return (await pathExists(path.join(runDir, 'gradlew.bat')))\n ? ['cmd.exe', '/c', 'gradlew.bat', ...args]\n : ['gradle', ...args];\n }\n return (await pathExists(path.join(runDir, 'gradlew'))) ? ['sh', './gradlew', ...args] : ['gradle', ...args];\n}\n\nasync function resolveMavenInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'pom.xml')))) return undefined;\n const args = ['-q', 'dependency:go-offline'] as const;\n if (process.platform === 'win32') {\n return (await pathExists(path.join(runDir, 'mvnw.cmd')))\n ? ['cmd.exe', '/c', 'mvnw.cmd', ...args]\n : ['mvn', ...args];\n }\n return (await pathExists(path.join(runDir, 'mvnw'))) ? ['sh', './mvnw', ...args] : ['mvn', ...args];\n}\n\nasync function resolveNpmInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'package.json')))) return undefined;\n return (await pathExists(path.join(runDir, 'package-lock.json')))\n ? ['npm', 'ci', '--silent']\n : ['npm', 'install', '--silent'];\n}\n\nasync function resolvePnpmInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'package.json')))) return undefined;\n return (await pathExists(path.join(runDir, 'pnpm-lock.yaml')))\n ? ['pnpm', 'install', '--frozen-lockfile', '--silent']\n : ['pnpm', 'install', '--silent'];\n}\n\nasync function resolveRubyInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'Gemfile')))) return undefined;\n return (await pathExists(path.join(runDir, 'Gemfile.lock')))\n ? ['bundle', 'install', '--frozen', '--quiet']\n : ['bundle', 'install', '--quiet'];\n}\n\nasync function resolveUvInstallCommand(): Promise<undefined> {\n return undefined;\n}\n\nasync function resolveYarnInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'package.json')))) return undefined;\n const isBerry = await isYarnBerryProject(runDir);\n const hasLockfile = await pathExists(path.join(runDir, 'yarn.lock'));\n if (isBerry) return hasLockfile ? ['yarn', 'install', '--immutable'] : ['yarn', 'install'];\n return hasLockfile ? ['yarn', 'install', '--frozen-lockfile', '--silent'] : ['yarn', 'install', '--silent'];\n}\n\nasync function isYarnBerryProject(runDir: string): Promise<boolean> {\n if (await pathExists(path.join(runDir, '.yarnrc.yml'))) return true;\n\n const packageJson = await readJson(path.join(runDir, 'package.json'));\n const packageManager = typeof packageJson.packageManager === 'string' ? packageJson.packageManager : undefined;\n const yarnMajorVersion = /^yarn@(\\d+)/.exec(packageManager ?? '')?.[1];\n return yarnMajorVersion !== undefined && Number(yarnMajorVersion) >= 2;\n}\n\nasync function hasAnyPath(directoryPath: string, relativePaths: readonly string[]): Promise<boolean> {\n for (const relativePath of relativePaths) {\n if (await pathExists(path.join(directoryPath, relativePath))) return true;\n }\n return false;\n}\n\nasync function pathExists(filePath: string): Promise<boolean> {\n try {\n await fs.access(filePath);\n return true;\n } catch (error) {\n const code =\n typeof error === 'object' && error !== null && 'code' in error ? (error as { code: unknown }).code : undefined;\n if (code !== 'ENOENT') throw error;\n return false;\n }\n}\n\nasync function readJson(filePath: string): Promise<Record<string, unknown>> {\n try {\n const parsed = JSON.parse(await fs.readFile(filePath, 'utf8')) as unknown;\n return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : {};\n } catch (error) {\n if (error instanceof SyntaxError) return {};\n const code =\n typeof error === 'object' && error !== null && 'code' in error ? (error as { code: unknown }).code : undefined;\n if (code === 'ENOENT') return {};\n throw error;\n }\n}\n\nexport async function copyPackageManagerProjectFiles(options: {\n packageManager: PackageManager;\n projectDir: string;\n runDir: string;\n projectFilePaths?: readonly string[];\n}): Promise<void> {\n for (const projectFilePath of options.projectFilePaths ?? packageManagerProjectFilePaths[options.packageManager]) {\n await copyPathIfExists(path.join(options.projectDir, projectFilePath), path.join(options.runDir, projectFilePath));\n }\n}\n\nasync function copyPathIfExists(sourcePath: string, destinationPath: string): Promise<void> {\n try {\n // Before touching the destination: creating the parent levels must not happen for a project\n // file the problem does not even ship.\n await fs.lstat(sourcePath);\n await fs.mkdir(path.dirname(destinationPath), { recursive: true });\n await fs.cp(sourcePath, destinationPath, { force: true, recursive: true });\n } catch (error) {\n const code =\n typeof error === 'object' && error !== null && 'code' in error ? (error as { code: unknown }).code : undefined;\n if (code !== 'ENOENT') throw error;\n }\n}\n\nasync function spawnWithInput(\n command: readonly [string, ...string[]],\n context: {\n cwd: string;\n env: NodeJS.ProcessEnv;\n outputLimitBytes: number;\n stdin: string;\n timeLimitSeconds: number;\n }\n): Promise<{\n stdout: string;\n stderr: string;\n status: number | undefined;\n timeSeconds: number;\n memoryBytes: number;\n timedOut: boolean;\n signal: NodeJS.Signals | undefined;\n outputLimitExceeded: boolean;\n}> {\n const timeOutputPath = timeCommand === undefined ? undefined : path.join(context.cwd, '.exercode-time-result');\n const spawnedCommand: readonly [string, ...string[]] =\n timeCommand === undefined ? command : [...timeCommand, `--output=${timeOutputPath}`, ...command];\n const subprocess = childProcess.spawn(spawnedCommand[0], spawnedCommand.slice(1), {\n cwd: context.cwd,\n detached: process.platform !== 'win32',\n env: context.env,\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n\n const stdoutChunks: Buffer[] = [];\n const stderrChunks: Buffer[] = [];\n let outputBytes = 0;\n let timedOut = false;\n let outputLimitExceeded = false;\n\n const appendOutputChunk = (chunks: Buffer[], chunk: Buffer): void => {\n if (outputBytes >= context.outputLimitBytes) {\n if (chunk.byteLength > 0) {\n outputLimitExceeded = true;\n killSubprocessGroup(subprocess, 'SIGKILL');\n }\n return;\n }\n\n const remainingBytes = context.outputLimitBytes - outputBytes;\n const appendedChunk = chunk.byteLength > remainingBytes ? chunk.subarray(0, remainingBytes) : chunk;\n chunks.push(appendedChunk);\n outputBytes += appendedChunk.byteLength;\n\n if (chunk.byteLength > remainingBytes) {\n outputLimitExceeded = true;\n killSubprocessGroup(subprocess, 'SIGKILL');\n }\n };\n\n subprocess.stdout.on('data', (chunk: Buffer) => appendOutputChunk(stdoutChunks, chunk));\n subprocess.stderr.on('data', (chunk: Buffer) => appendOutputChunk(stderrChunks, chunk));\n\n const timeout = setTimeout(() => {\n timedOut = true;\n killSubprocessGroup(subprocess, 'SIGTERM');\n }, context.timeLimitSeconds * 1000);\n const killTimeout = setTimeout(\n () => {\n if (timedOut) killSubprocessGroup(subprocess, 'SIGKILL');\n },\n context.timeLimitSeconds * 1000 + killGracePeriodMilliseconds\n );\n killTimeout.unref();\n\n const { status, signal } = await new Promise<{ status: number | undefined; signal: NodeJS.Signals | undefined }>(\n (resolve, reject) => {\n let settled = false;\n let pendingError: Error | undefined;\n const failAfterClose = (error: Error): void => {\n if (settled) return;\n pendingError = error;\n killSubprocessGroup(subprocess, 'SIGKILL');\n if (subprocess.pid === undefined) {\n settled = true;\n reject(error);\n }\n };\n subprocess.on('error', failAfterClose);\n subprocess.stdin.on('error', (error: NodeJS.ErrnoException) => {\n if (error.code !== 'EPIPE') failAfterClose(error);\n });\n subprocess.on('close', (code, closeSignal) => {\n if (settled) return;\n settled = true;\n if (pendingError) {\n reject(pendingError);\n return;\n }\n resolve({ status: code ?? undefined, signal: closeSignal ?? undefined });\n });\n subprocess.stdin.end(context.stdin);\n }\n ).finally(() => {\n clearTimeout(timeout);\n clearTimeout(killTimeout);\n });\n\n const { timeSeconds, memoryBytes } =\n timeOutputPath === undefined ? { timeSeconds: 0, memoryBytes: 0 } : await readTimeResult(timeOutputPath);\n\n return {\n stdout: Buffer.concat(stdoutChunks).toString(),\n stderr: Buffer.concat(stderrChunks).toString(),\n status,\n timeSeconds,\n memoryBytes,\n timedOut,\n signal,\n outputLimitExceeded,\n };\n}\n\nfunction resolveTimeCommand(): readonly [string, ...string[]] | undefined {\n const command = os.platform() === 'darwin' ? 'gtime' : '/usr/bin/time';\n const result = childProcess.spawnSync(command, ['--version'], { stdio: 'ignore' });\n if (result.error || result.status !== 0) return undefined;\n\n return [command, '--format', '%e %M'];\n}\n\nfunction killSubprocessGroup(subprocess: childProcess.ChildProcess, signal: NodeJS.Signals): void {\n if (subprocess.pid === undefined) return;\n\n try {\n if (process.platform === 'win32') {\n subprocess.kill(signal);\n return;\n }\n process.kill(-subprocess.pid, signal);\n } catch (error) {\n const code =\n typeof error === 'object' && error !== null && 'code' in error ? (error as { code: unknown }).code : undefined;\n if (code !== 'ESRCH' && code !== 'EPERM') throw error;\n }\n}\n\nfunction isErrorWithCode(error: unknown, code: string): boolean {\n return typeof error === 'object' && error !== null && 'code' in error && (error as { code: unknown }).code === code;\n}\n\nasync function readTimeResult(timeOutputPath: string): Promise<{ timeSeconds: number; memoryBytes: number }> {\n // An absent file means \"no measurement\" (the command was killed before `time` wrote one). Anything\n // else is read as-is: the submission shares this process's OS user, so a symlink or FIFO planted\n // here only affects its own run, which the judge server's request timeout ends.\n let content: string;\n try {\n content = await fs.readFile(timeOutputPath, 'utf8');\n } catch (error) {\n if (isErrorWithCode(error, 'ENOENT')) return { timeSeconds: 0, memoryBytes: 0 };\n throw error;\n }\n\n const match = /(\\d+(?:[.,]\\d+)?) (\\d+)\\s*$/.exec(content);\n if (!match) return { timeSeconds: 0, memoryBytes: 0 };\n\n return { timeSeconds: Number(match[1]!.replace(',', '.')), memoryBytes: Number(match[2]) * 1024 };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"runCommandInTemporaryPackageManagerProject.js","names":[],"sources":["../../src/helpers/runCommandInTemporaryPackageManagerProject.ts"],"sourcesContent":["import fs from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport { spawnWithLimits, type SpawnWithLimitsResult } from './spawnWithLimits.js';\nimport { forciblyRemoveDirectory } from './temporaryProblemDirCopy.js';\n\nexport type PackageManager = 'bun' | 'cargo' | 'go' | 'gradle' | 'maven' | 'npm' | 'pnpm' | 'ruby' | 'uv' | 'yarn';\ntype PackageManagerInstallCommand = readonly [string, ...string[]];\n\nexport interface PackageManagerCommandRunResult {\n stdin: string;\n stdout: string;\n stderr: string;\n status: number | undefined;\n timeSeconds: number;\n memoryBytes: number;\n timedOut: boolean;\n signal: NodeJS.Signals | undefined;\n outputLimitExceeded: boolean;\n}\n\nexport interface RunCommandInTemporaryPackageManagerProjectOptions {\n cwd: string;\n projectDir: string;\n packageManager: PackageManager;\n command: readonly [string, ...string[]] | ((context: { runDir: string }) => readonly [string, ...string[]]);\n /**\n * Set to false when `command` prepares the dependencies it needs.\n * Defaults to true.\n */\n prepareDependencies?: boolean;\n stdin?: string;\n env?: NodeJS.ProcessEnv;\n timeLimitSeconds: number;\n outputLimitBytes?: number;\n tempDirPrefix?: string;\n projectFilePaths?: readonly string[];\n}\n\nconst packageManagerProjectFilePaths = {\n bun: ['package.json', 'bun.lock', 'bun.lockb'],\n cargo: ['Cargo.toml', 'Cargo.lock'],\n go: ['go.mod', 'go.sum'],\n gradle: [\n 'build.gradle',\n 'build.gradle.kts',\n 'settings.gradle',\n 'settings.gradle.kts',\n 'gradle.properties',\n 'gradle.lockfile',\n 'buildscript-gradle.lockfile',\n 'gradle',\n 'gradlew',\n 'gradlew.bat',\n ],\n maven: ['pom.xml', '.mvn', 'mvnw', 'mvnw.cmd'],\n npm: ['package.json', 'package-lock.json'],\n pnpm: ['package.json', 'pnpm-lock.yaml', 'pnpm-workspace.yaml'],\n ruby: ['Gemfile', 'Gemfile.lock', '.ruby-version'],\n uv: ['pyproject.toml', 'uv.lock'],\n yarn: ['package.json', 'yarn.lock', '.yarnrc', '.yarnrc.yml', '.yarn'],\n} as const satisfies Record<PackageManager, readonly string[]>;\n\nconst packageManagerInstallCommandResolvers = {\n bun: resolveBunInstallCommand,\n cargo: resolveCargoInstallCommand,\n go: resolveGoInstallCommand,\n gradle: resolveGradleInstallCommand,\n maven: resolveMavenInstallCommand,\n npm: resolveNpmInstallCommand,\n pnpm: resolvePnpmInstallCommand,\n ruby: resolveRubyInstallCommand,\n uv: resolveUvInstallCommand,\n yarn: resolveYarnInstallCommand,\n} as const satisfies Record<PackageManager, (runDir: string) => Promise<PackageManagerInstallCommand | undefined>>;\n\nconst defaultOutputLimitBytes = 50 * 1024 * 1024;\n\n/**\n * Copies a submission directory to a temporary directory, overlays package\n * manager project files from the problem directory, prepares dependencies,\n * runs a command, and then removes the temporary directory.\n */\nexport async function runCommandInTemporaryPackageManagerProject(\n options: RunCommandInTemporaryPackageManagerProjectOptions\n): Promise<PackageManagerCommandRunResult> {\n const runDir = await fs.mkdtemp(path.join(os.tmpdir(), options.tempDirPrefix ?? 'exercode-'));\n try {\n await fs.cp(options.cwd, runDir, { recursive: true });\n await copyPackageManagerProjectFiles({\n packageManager: options.packageManager,\n projectDir: options.projectDir,\n runDir,\n projectFilePaths: options.projectFilePaths,\n });\n\n const env = options.env ? { ...process.env, ...options.env } : process.env;\n const installCommand =\n options.prepareDependencies === false ? undefined : await resolveInstallCommand(options.packageManager, runDir);\n const command = typeof options.command === 'function' ? options.command({ runDir }) : options.command;\n const outputLimitBytes = options.outputLimitBytes ?? defaultOutputLimitBytes;\n let installResult: SpawnWithLimitsResult | undefined;\n\n if (installCommand) {\n installResult = await spawnWithLimits(installCommand, {\n cwd: runDir,\n env,\n outputLimitBytes,\n stdin: '',\n timeLimitSeconds: options.timeLimitSeconds,\n });\n if (isFailedSpawnResult(installResult)) {\n return toPackageManagerCommandRunResult({\n options,\n result: installResult,\n });\n }\n }\n\n // The install's measured time excludes the grace spent releasing pipes after it exited.\n const remainingTimeLimitSeconds = options.timeLimitSeconds - (installResult?.timeSeconds ?? 0);\n if (remainingTimeLimitSeconds <= 0) {\n return {\n stdin: options.stdin ?? '',\n stdout: installResult?.stdout ?? '',\n stderr: installResult?.stderr ?? '',\n status: 0,\n timeSeconds: options.timeLimitSeconds + 1e-3,\n memoryBytes: installResult?.memoryBytes ?? 0,\n timedOut: true,\n signal: installResult?.signal,\n outputLimitExceeded: false,\n };\n }\n\n const result = await spawnWithLimits(command, {\n cwd: runDir,\n env,\n outputLimitBytes,\n stdin: options.stdin ?? '',\n timeLimitSeconds: remainingTimeLimitSeconds,\n });\n\n if (installResult) {\n return toPackageManagerCommandRunResult({\n options,\n result: {\n ...result,\n timeSeconds: installResult.timeSeconds + result.timeSeconds,\n memoryBytes: Math.max(installResult.memoryBytes, result.memoryBytes),\n },\n });\n }\n\n return toPackageManagerCommandRunResult({ options, result });\n } finally {\n // The command may have left permission-locked entries (e.g. a mode-000 directory).\n await forciblyRemoveDirectory(runDir);\n }\n}\n\nfunction toPackageManagerCommandRunResult(context: {\n options: RunCommandInTemporaryPackageManagerProjectOptions;\n result: SpawnWithLimitsResult;\n}): PackageManagerCommandRunResult {\n return {\n stdin: context.options.stdin ?? '',\n stdout: context.result.stdout,\n stderr: context.result.stderr,\n status: context.result.timedOut || context.result.outputLimitExceeded ? 0 : context.result.status,\n timeSeconds: context.result.timedOut ? context.options.timeLimitSeconds + 1e-3 : context.result.timeSeconds,\n memoryBytes: context.result.memoryBytes,\n timedOut: context.result.timedOut,\n signal: context.result.signal,\n outputLimitExceeded: context.result.outputLimitExceeded,\n };\n}\n\nfunction resolveInstallCommand(\n packageManager: PackageManager,\n runDir: string\n): Promise<PackageManagerInstallCommand | undefined> {\n return packageManagerInstallCommandResolvers[packageManager](runDir);\n}\n\nfunction isFailedSpawnResult(result: SpawnWithLimitsResult): boolean {\n return result.status !== 0 || result.timedOut || result.outputLimitExceeded;\n}\n\nasync function resolveBunInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'package.json')))) return undefined;\n // Bun supports --silent and it keeps successful preparation output out of judge output buffers.\n return (await hasAnyPath(runDir, ['bun.lock', 'bun.lockb']))\n ? ['bun', 'install', '--frozen-lockfile', '--silent']\n : ['bun', 'install', '--silent'];\n}\n\nasync function resolveCargoInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'Cargo.toml')))) return undefined;\n return (await pathExists(path.join(runDir, 'Cargo.lock'))) ? ['cargo', 'fetch', '--locked'] : ['cargo', 'fetch'];\n}\n\nasync function resolveGoInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'go.mod')))) return undefined;\n return ['go', 'mod', 'download'];\n}\n\nasync function resolveGradleInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (\n !(await hasAnyPath(runDir, [\n 'build.gradle',\n 'build.gradle.kts',\n 'settings.gradle',\n 'settings.gradle.kts',\n 'gradlew',\n 'gradlew.bat',\n ]))\n )\n return undefined;\n const args = ['--no-daemon', '--quiet', 'dependencies'] as const;\n if (process.platform === 'win32') {\n return (await pathExists(path.join(runDir, 'gradlew.bat')))\n ? ['cmd.exe', '/c', 'gradlew.bat', ...args]\n : ['gradle', ...args];\n }\n return (await pathExists(path.join(runDir, 'gradlew'))) ? ['sh', './gradlew', ...args] : ['gradle', ...args];\n}\n\nasync function resolveMavenInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'pom.xml')))) return undefined;\n const args = ['-q', 'dependency:go-offline'] as const;\n if (process.platform === 'win32') {\n return (await pathExists(path.join(runDir, 'mvnw.cmd')))\n ? ['cmd.exe', '/c', 'mvnw.cmd', ...args]\n : ['mvn', ...args];\n }\n return (await pathExists(path.join(runDir, 'mvnw'))) ? ['sh', './mvnw', ...args] : ['mvn', ...args];\n}\n\nasync function resolveNpmInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'package.json')))) return undefined;\n return (await pathExists(path.join(runDir, 'package-lock.json')))\n ? ['npm', 'ci', '--silent']\n : ['npm', 'install', '--silent'];\n}\n\nasync function resolvePnpmInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'package.json')))) return undefined;\n return (await pathExists(path.join(runDir, 'pnpm-lock.yaml')))\n ? ['pnpm', 'install', '--frozen-lockfile', '--silent']\n : ['pnpm', 'install', '--silent'];\n}\n\nasync function resolveRubyInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'Gemfile')))) return undefined;\n return (await pathExists(path.join(runDir, 'Gemfile.lock')))\n ? ['bundle', 'install', '--frozen', '--quiet']\n : ['bundle', 'install', '--quiet'];\n}\n\nasync function resolveUvInstallCommand(): Promise<undefined> {\n return undefined;\n}\n\nasync function resolveYarnInstallCommand(runDir: string): Promise<PackageManagerInstallCommand | undefined> {\n if (!(await pathExists(path.join(runDir, 'package.json')))) return undefined;\n const isBerry = await isYarnBerryProject(runDir);\n const hasLockfile = await pathExists(path.join(runDir, 'yarn.lock'));\n if (isBerry) return hasLockfile ? ['yarn', 'install', '--immutable'] : ['yarn', 'install'];\n return hasLockfile ? ['yarn', 'install', '--frozen-lockfile', '--silent'] : ['yarn', 'install', '--silent'];\n}\n\nasync function isYarnBerryProject(runDir: string): Promise<boolean> {\n if (await pathExists(path.join(runDir, '.yarnrc.yml'))) return true;\n\n const packageJson = await readJson(path.join(runDir, 'package.json'));\n const packageManager = typeof packageJson.packageManager === 'string' ? packageJson.packageManager : undefined;\n const yarnMajorVersion = /^yarn@(\\d+)/.exec(packageManager ?? '')?.[1];\n return yarnMajorVersion !== undefined && Number(yarnMajorVersion) >= 2;\n}\n\nasync function hasAnyPath(directoryPath: string, relativePaths: readonly string[]): Promise<boolean> {\n for (const relativePath of relativePaths) {\n if (await pathExists(path.join(directoryPath, relativePath))) return true;\n }\n return false;\n}\n\nasync function pathExists(filePath: string): Promise<boolean> {\n try {\n await fs.access(filePath);\n return true;\n } catch (error) {\n const code =\n typeof error === 'object' && error !== null && 'code' in error ? (error as { code: unknown }).code : undefined;\n if (code !== 'ENOENT') throw error;\n return false;\n }\n}\n\nasync function readJson(filePath: string): Promise<Record<string, unknown>> {\n try {\n const parsed = JSON.parse(await fs.readFile(filePath, 'utf8')) as unknown;\n return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : {};\n } catch (error) {\n if (error instanceof SyntaxError) return {};\n const code =\n typeof error === 'object' && error !== null && 'code' in error ? (error as { code: unknown }).code : undefined;\n if (code === 'ENOENT') return {};\n throw error;\n }\n}\n\nexport async function copyPackageManagerProjectFiles(options: {\n packageManager: PackageManager;\n projectDir: string;\n runDir: string;\n projectFilePaths?: readonly string[];\n}): Promise<void> {\n for (const projectFilePath of options.projectFilePaths ?? packageManagerProjectFilePaths[options.packageManager]) {\n await copyPathIfExists(path.join(options.projectDir, projectFilePath), path.join(options.runDir, projectFilePath));\n }\n}\n\nasync function copyPathIfExists(sourcePath: string, destinationPath: string): Promise<void> {\n try {\n // Before touching the destination: creating the parent levels must not happen for a project\n // file the problem does not even ship.\n await fs.lstat(sourcePath);\n await fs.mkdir(path.dirname(destinationPath), { recursive: true });\n await fs.cp(sourcePath, destinationPath, { force: true, recursive: true });\n } catch (error) {\n const code =\n typeof error === 'object' && error !== null && 'code' in error ? (error as { code: unknown }).code : undefined;\n if (code !== 'ENOENT') throw error;\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"runHarnessProcess.cjs","names":["child_process"],"sources":["../../src/helpers/runHarnessProcess.ts"],"sourcesContent":["import child_process from 'node:child_process';\n\nimport { forciblyRemoveDirectorySync } from './temporaryProblemDirCopy.js';\n\nexport interface HarnessProcessOptions {\n cwd: string;\n env: NodeJS.ProcessEnv;\n timeoutMs: number;\n maxOutputBytes: number;\n /** A temporary copy to remove when the check itself is interrupted by a signal. */\n tempRoot?: string;\n}\n\nexport interface HarnessProcessResult {\n stdout: string;\n stderr: string;\n exitCode: number | undefined;\n /** The signal that terminated the harness when it did not exit on its own. */\n signal: NodeJS.Signals | undefined;\n /** Set when the run was killed by this helper (timeout or output cap) or could not be spawned. */\n failureReason: string | undefined;\n timedOut: boolean;\n}\n\ninterface LiveHarnessRun {\n pid: number;\n tempRoot: string | undefined;\n}\n\n// Detached harness groups no longer receive the terminal's SIGINT, so an interrupted run must\n// tear them down (and remove their temp copies) itself before exiting. The handlers stay installed\n// for the process lifetime: once the last run has settled they only iterate an empty set and re-raise\n// the signal, so removing them would change nothing observable.\nconst liveHarnessRuns = new Set<LiveHarnessRun>();\nlet signalHandlersInstalled = false;\n\n/**\n * Run a judge harness with `process.execPath` in its own process group, killing the whole group on\n * timeout or when the output cap is exceeded so grandchild submission processes cannot outlive the run.\n */\nexport function runHarnessProcess(\n commandArgs: readonly string[],\n options: HarnessProcessOptions\n): Promise<HarnessProcessResult> {\n installSignalHandlers();\n return new Promise((resolve) => {\n // process.execPath keeps the harness on the same bun executable regardless of PATH.\n const child = child_process.spawn(process.execPath, commandArgs, {\n cwd: options.cwd,\n detached: process.platform !== 'win32',\n env: options.env,\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n const liveRun: LiveHarnessRun | undefined =\n child.pid === undefined ? undefined : { pid: child.pid, tempRoot: options.tempRoot };\n if (liveRun) liveHarnessRuns.add(liveRun);\n const stdoutChunks: Buffer[] = [];\n const stderrChunks: Buffer[] = [];\n let totalOutputBytes = 0;\n let failureReason: string | undefined;\n let timedOut = false;\n let settled = false;\n\n // Returns false when an earlier kill already recorded its reason, so callers can tell whether theirs won.\n const killProcessGroup = (reason: string): boolean => {\n if (failureReason !== undefined) return false;\n failureReason = reason;\n // Stop buffering immediately: OS pipe buffers can keep emitting data after the kill, and\n // destroyed streams also let `close` fire even if a stray grandchild inherited the pipes.\n child.stdout?.destroy();\n child.stderr?.destroy();\n try {\n if (child.pid === undefined) {\n child.kill('SIGKILL');\n } else {\n killHarnessTree(child.pid);\n }\n } catch {\n child.kill('SIGKILL');\n }\n return true;\n };\n const timeoutId = setTimeout(() => {\n timedOut = killProcessGroup(`timed out after ${options.timeoutMs / 1000} seconds`);\n }, options.timeoutMs);\n\n const appendOutput = (chunk: Buffer, chunks: Buffer[]): void => {\n totalOutputBytes += chunk.byteLength;\n if (totalOutputBytes > options.maxOutputBytes) {\n killProcessGroup(`the harness printed more than ${options.maxOutputBytes / 1024 / 1024} MB of output`);\n return;\n }\n chunks.push(chunk);\n };\n child.stdout?.on('data', (chunk: Buffer) => appendOutput(chunk, stdoutChunks));\n child.stderr?.on('data', (chunk: Buffer) => appendOutput(chunk, stderrChunks));\n\n const settle = (exitCode: number | undefined, signal: NodeJS.Signals | undefined, spawnError?: Error): void => {\n if (settled) return;\n settled = true;\n clearTimeout(timeoutId);\n if (liveRun) liveHarnessRuns.delete(liveRun);\n resolve({\n stdout: Buffer.concat(stdoutChunks).toString('utf8'),\n stderr: Buffer.concat(stderrChunks).toString('utf8'),\n exitCode,\n signal,\n failureReason: failureReason ?? (spawnError ? `failed to run the harness: ${spawnError.message}` : undefined),\n timedOut,\n });\n };\n child.on('error', (error) => settle(undefined, undefined, error));\n child.on('close', (exitCode, signal) => settle(exitCode ?? undefined, signal ?? undefined));\n });\n}\n\nfunction installSignalHandlers(): void {\n if (signalHandlersInstalled) return;\n signalHandlersInstalled = true;\n for (const signal of ['SIGINT', 'SIGTERM'] as const) {\n process.once(signal, () => {\n for (const liveRun of liveHarnessRuns) {\n try {\n killHarnessTree(liveRun.pid);\n } catch {\n // The tree already exited.\n }\n // Best-effort cleanup while exiting; judged code may have left permission-locked entries.\n if (liveRun.tempRoot) forciblyRemoveDirectorySync(liveRun.tempRoot);\n }\n process.kill(process.pid, signal);\n });\n }\n}\n\n// On Windows the harness is not detached (process groups are unavailable), so killing only the\n// direct child would leave submission grandchildren running; taskkill terminates the whole tree.\nfunction killHarnessTree(pid: number): void {\n if (process.platform === 'win32') {\n child_process.spawnSync('taskkill', ['/PID', String(pid), '/T', '/F'], { stdio: 'ignore' });\n } else {\n process.kill(-pid, 'SIGKILL');\n }\n}\n"],"mappings":"kKAiCA,MAAM,EAAkB,IAAI,IAC5B,IAAI,EAA0B,GAM9B,SAAgB,EACd,EACA,EAC+B,CAE/B,OADA,EAAsB,EACf,IAAI,QAAS,GAAY,CAE9B,IAAM,EAAQA,EAAAA,QAAc,MAAM,QAAQ,SAAU,EAAa,CAC/D,IAAK,EAAQ,IACb,SAAU,QAAQ,WAAa,QAC/B,IAAK,EAAQ,IACb,MAAO,CAAC,SAAU,OAAQ,MAAM,CAClC,CAAC,EACK,EACJ,EAAM,MAAQ,IAAA,GAAY,IAAA,GAAY,CAAE,IAAK,EAAM,IAAK,SAAU,EAAQ,QAAS,EACjF,GAAS,EAAgB,IAAI,CAAO,EACxC,IAAM,EAAyB,CAAC,EAC1B,EAAyB,CAAC,EAC5B,EAAmB,EACnB,EACA,EAAW,GACX,EAAU,GAGR,EAAoB,GAA4B,CACpD,GAAI,IAAkB,IAAA,GAAW,MAAO,GACxC,EAAgB,EAGhB,EAAM,QAAQ,QAAQ,EACtB,EAAM,QAAQ,QAAQ,EACtB,GAAI,CACE,EAAM,MAAQ,IAAA,GAChB,EAAM,KAAK,SAAS,EAEpB,EAAgB,EAAM,GAAG,CAE7B,MAAQ,CACN,EAAM,KAAK,SAAS,CACtB,CACA,MAAO,EACT,EACM,EAAY,eAAiB,CACjC,EAAW,EAAiB,mBAAmB,EAAQ,UAAY,IAAK,SAAS,CACnF,EAAG,EAAQ,SAAS,EAEd,GAAgB,EAAe,IAA2B,CAE9D,GADA,GAAoB,EAAM,WACtB,EAAmB,EAAQ,eAAgB,CAC7C,EAAiB,iCAAiC,EAAQ,eAAiB,KAAO,KAAK,cAAc,EACrG,MACF,CACA,EAAO,KAAK,CAAK,CACnB,EACA,EAAM,QAAQ,GAAG,OAAS,GAAkB,EAAa,EAAO,CAAY,CAAC,EAC7E,EAAM,QAAQ,GAAG,OAAS,GAAkB,EAAa,EAAO,CAAY,CAAC,EAE7E,IAAM,GAAU,EAA8B,EAAoC,IAA6B,CACzG,IACJ,EAAU,GACV,aAAa,CAAS,EAClB,GAAS,EAAgB,OAAO,CAAO,EAC3C,EAAQ,CACN,OAAQ,OAAO,OAAO,CAAY,CAAC,CAAC,SAAS,MAAM,EACnD,OAAQ,OAAO,OAAO,CAAY,CAAC,CAAC,SAAS,MAAM,EACnD,WACA,SACA,cAAe,IAAkB,EAAa,8BAA8B,EAAW,UAAY,IAAA,IACnG,UACF,CAAC,EACH,EACA,EAAM,GAAG,QAAU,GAAU,EAAO,IAAA,GAAW,IAAA,GAAW,CAAK,CAAC,EAChE,EAAM,GAAG,SAAU,EAAU,IAAW,EAAO,GAAY,IAAA,GAAW,GAAU,IAAA,EAAS,CAAC,CAC5F,CAAC,CACH,CAEA,SAAS,GAA8B,CACjC,MACJ,GAA0B,GAC1B,IAAK,IAAM,IAAU,CAAC,SAAU,SAAS,EACvC,QAAQ,KAAK,MAAc,CACzB,IAAK,IAAM,KAAW,EAAiB,CACrC,GAAI,CACF,EAAgB,EAAQ,GAAG,CAC7B,MAAQ,CAER,CAEI,EAAQ,UAAU,EAAA,4BAA4B,EAAQ,QAAQ,CACpE,CACA,QAAQ,KAAK,QAAQ,IAAK,CAAM,CAClC,CAAC,CAbuB,CAe5B,CAIA,SAAS,EAAgB,EAAmB,CACtC,QAAQ,WAAa,QACvB,EAAA,QAAc,UAAU,WAAY,CAAC,OAAQ,OAAO,CAAG,EAAG,KAAM,IAAI,EAAG,CAAE,MAAO,QAAS,CAAC,EAE1F,QAAQ,KAAK,CAAC,EAAK,SAAS,CAEhC"}
1
+ {"version":3,"file":"runHarnessProcess.cjs","names":["child_process"],"sources":["../../src/helpers/runHarnessProcess.ts"],"sourcesContent":["import child_process from 'node:child_process';\n\nimport { forciblyRemoveDirectorySync } from './temporaryProblemDirCopy.js';\n\nexport interface HarnessProcessOptions {\n cwd: string;\n env: NodeJS.ProcessEnv;\n timeoutMs: number;\n maxOutputBytes: number;\n /** A temporary copy to remove when the check itself is interrupted by a signal. */\n tempRoot?: string;\n}\n\nexport interface HarnessProcessResult {\n stdout: string;\n stderr: string;\n exitCode: number | undefined;\n /** The signal that terminated the harness when it did not exit on its own. */\n signal: NodeJS.Signals | undefined;\n /** Set when the run was killed by this helper (timeout or output cap) or could not be spawned. */\n failureReason: string | undefined;\n timedOut: boolean;\n}\n\ninterface LiveHarnessRun {\n pid: number;\n tempRoot: string | undefined;\n}\n\n// Detached harness groups no longer receive the terminal's SIGINT, so an interrupted run must\n// tear them down (and remove their temp copies) itself before exiting. The handlers stay installed\n// for the process lifetime: once the last run has settled they only iterate an empty set and re-raise\n// the signal, so removing them would change nothing observable.\nconst liveHarnessRuns = new Set<LiveHarnessRun>();\nlet signalHandlersInstalled = false;\n\n/**\n * Run a judge harness with `process.execPath` in its own process group, killing the whole group on\n * timeout or when the output cap is exceeded. The submissions a harness judges through\n * `spawnWithLimits` run in sessions of their own, out of that kill's reach; each wrapper's process\n * group is bounded by its own GNU `timeout` instead, while a descendant that moved to yet another\n * session may survive without stalling the harness.\n */\nexport function runHarnessProcess(\n commandArgs: readonly string[],\n options: HarnessProcessOptions\n): Promise<HarnessProcessResult> {\n installSignalHandlers();\n return new Promise((resolve) => {\n // process.execPath keeps the harness on the same bun executable regardless of PATH.\n const child = child_process.spawn(process.execPath, commandArgs, {\n cwd: options.cwd,\n detached: process.platform !== 'win32',\n env: options.env,\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n const liveRun: LiveHarnessRun | undefined =\n child.pid === undefined ? undefined : { pid: child.pid, tempRoot: options.tempRoot };\n if (liveRun) liveHarnessRuns.add(liveRun);\n const stdoutChunks: Buffer[] = [];\n const stderrChunks: Buffer[] = [];\n let totalOutputBytes = 0;\n let failureReason: string | undefined;\n let timedOut = false;\n let settled = false;\n\n // Returns false when an earlier kill already recorded its reason, so callers can tell whether theirs won.\n const killProcessGroup = (reason: string): boolean => {\n if (failureReason !== undefined) return false;\n failureReason = reason;\n // Stop buffering immediately: OS pipe buffers can keep emitting data after the kill, and\n // destroyed streams also let `close` fire even if a stray grandchild inherited the pipes.\n child.stdout?.destroy();\n child.stderr?.destroy();\n try {\n if (child.pid === undefined) {\n child.kill('SIGKILL');\n } else {\n killHarnessTree(child.pid);\n }\n } catch {\n child.kill('SIGKILL');\n }\n return true;\n };\n const timeoutId = setTimeout(() => {\n timedOut = killProcessGroup(`timed out after ${options.timeoutMs / 1000} seconds`);\n }, options.timeoutMs);\n\n const appendOutput = (chunk: Buffer, chunks: Buffer[]): void => {\n totalOutputBytes += chunk.byteLength;\n if (totalOutputBytes > options.maxOutputBytes) {\n killProcessGroup(`the harness printed more than ${options.maxOutputBytes / 1024 / 1024} MB of output`);\n return;\n }\n chunks.push(chunk);\n };\n child.stdout?.on('data', (chunk: Buffer) => appendOutput(chunk, stdoutChunks));\n child.stderr?.on('data', (chunk: Buffer) => appendOutput(chunk, stderrChunks));\n\n const settle = (exitCode: number | undefined, signal: NodeJS.Signals | undefined, spawnError?: Error): void => {\n if (settled) return;\n settled = true;\n clearTimeout(timeoutId);\n if (liveRun) liveHarnessRuns.delete(liveRun);\n resolve({\n stdout: Buffer.concat(stdoutChunks).toString('utf8'),\n stderr: Buffer.concat(stderrChunks).toString('utf8'),\n exitCode,\n signal,\n failureReason: failureReason ?? (spawnError ? `failed to run the harness: ${spawnError.message}` : undefined),\n timedOut,\n });\n };\n child.on('error', (error) => settle(undefined, undefined, error));\n child.on('close', (exitCode, signal) => settle(exitCode ?? undefined, signal ?? undefined));\n });\n}\n\nfunction installSignalHandlers(): void {\n if (signalHandlersInstalled) return;\n signalHandlersInstalled = true;\n for (const signal of ['SIGINT', 'SIGTERM'] as const) {\n process.once(signal, () => {\n for (const liveRun of liveHarnessRuns) {\n try {\n killHarnessTree(liveRun.pid);\n } catch {\n // The tree already exited.\n }\n // Best-effort cleanup while exiting; judged code may have left permission-locked entries.\n if (liveRun.tempRoot) forciblyRemoveDirectorySync(liveRun.tempRoot);\n }\n process.kill(process.pid, signal);\n });\n }\n}\n\n// On Windows the harness is not detached (process groups are unavailable), so killing only the\n// direct child would leave submission grandchildren running; taskkill terminates the whole tree.\nfunction killHarnessTree(pid: number): void {\n if (process.platform === 'win32') {\n child_process.spawnSync('taskkill', ['/PID', String(pid), '/T', '/F'], { stdio: 'ignore' });\n } else {\n process.kill(-pid, 'SIGKILL');\n }\n}\n"],"mappings":"kKAiCA,MAAM,EAAkB,IAAI,IAC5B,IAAI,EAA0B,GAS9B,SAAgB,EACd,EACA,EAC+B,CAE/B,OADA,EAAsB,EACf,IAAI,QAAS,GAAY,CAE9B,IAAM,EAAQA,EAAAA,QAAc,MAAM,QAAQ,SAAU,EAAa,CAC/D,IAAK,EAAQ,IACb,SAAU,QAAQ,WAAa,QAC/B,IAAK,EAAQ,IACb,MAAO,CAAC,SAAU,OAAQ,MAAM,CAClC,CAAC,EACK,EACJ,EAAM,MAAQ,IAAA,GAAY,IAAA,GAAY,CAAE,IAAK,EAAM,IAAK,SAAU,EAAQ,QAAS,EACjF,GAAS,EAAgB,IAAI,CAAO,EACxC,IAAM,EAAyB,CAAC,EAC1B,EAAyB,CAAC,EAC5B,EAAmB,EACnB,EACA,EAAW,GACX,EAAU,GAGR,EAAoB,GAA4B,CACpD,GAAI,IAAkB,IAAA,GAAW,MAAO,GACxC,EAAgB,EAGhB,EAAM,QAAQ,QAAQ,EACtB,EAAM,QAAQ,QAAQ,EACtB,GAAI,CACE,EAAM,MAAQ,IAAA,GAChB,EAAM,KAAK,SAAS,EAEpB,EAAgB,EAAM,GAAG,CAE7B,MAAQ,CACN,EAAM,KAAK,SAAS,CACtB,CACA,MAAO,EACT,EACM,EAAY,eAAiB,CACjC,EAAW,EAAiB,mBAAmB,EAAQ,UAAY,IAAK,SAAS,CACnF,EAAG,EAAQ,SAAS,EAEd,GAAgB,EAAe,IAA2B,CAE9D,GADA,GAAoB,EAAM,WACtB,EAAmB,EAAQ,eAAgB,CAC7C,EAAiB,iCAAiC,EAAQ,eAAiB,KAAO,KAAK,cAAc,EACrG,MACF,CACA,EAAO,KAAK,CAAK,CACnB,EACA,EAAM,QAAQ,GAAG,OAAS,GAAkB,EAAa,EAAO,CAAY,CAAC,EAC7E,EAAM,QAAQ,GAAG,OAAS,GAAkB,EAAa,EAAO,CAAY,CAAC,EAE7E,IAAM,GAAU,EAA8B,EAAoC,IAA6B,CACzG,IACJ,EAAU,GACV,aAAa,CAAS,EAClB,GAAS,EAAgB,OAAO,CAAO,EAC3C,EAAQ,CACN,OAAQ,OAAO,OAAO,CAAY,CAAC,CAAC,SAAS,MAAM,EACnD,OAAQ,OAAO,OAAO,CAAY,CAAC,CAAC,SAAS,MAAM,EACnD,WACA,SACA,cAAe,IAAkB,EAAa,8BAA8B,EAAW,UAAY,IAAA,IACnG,UACF,CAAC,EACH,EACA,EAAM,GAAG,QAAU,GAAU,EAAO,IAAA,GAAW,IAAA,GAAW,CAAK,CAAC,EAChE,EAAM,GAAG,SAAU,EAAU,IAAW,EAAO,GAAY,IAAA,GAAW,GAAU,IAAA,EAAS,CAAC,CAC5F,CAAC,CACH,CAEA,SAAS,GAA8B,CACjC,MACJ,GAA0B,GAC1B,IAAK,IAAM,IAAU,CAAC,SAAU,SAAS,EACvC,QAAQ,KAAK,MAAc,CACzB,IAAK,IAAM,KAAW,EAAiB,CACrC,GAAI,CACF,EAAgB,EAAQ,GAAG,CAC7B,MAAQ,CAER,CAEI,EAAQ,UAAU,EAAA,4BAA4B,EAAQ,QAAQ,CACpE,CACA,QAAQ,KAAK,QAAQ,IAAK,CAAM,CAClC,CAAC,CAbuB,CAe5B,CAIA,SAAS,EAAgB,EAAmB,CACtC,QAAQ,WAAa,QACvB,EAAA,QAAc,UAAU,WAAY,CAAC,OAAQ,OAAO,CAAG,EAAG,KAAM,IAAI,EAAG,CAAE,MAAO,QAAS,CAAC,EAE1F,QAAQ,KAAK,CAAC,EAAK,SAAS,CAEhC"}
@@ -18,6 +18,9 @@ export interface HarnessProcessResult {
18
18
  }
19
19
  /**
20
20
  * Run a judge harness with `process.execPath` in its own process group, killing the whole group on
21
- * timeout or when the output cap is exceeded so grandchild submission processes cannot outlive the run.
21
+ * timeout or when the output cap is exceeded. The submissions a harness judges through
22
+ * `spawnWithLimits` run in sessions of their own, out of that kill's reach; each wrapper's process
23
+ * group is bounded by its own GNU `timeout` instead, while a descendant that moved to yet another
24
+ * session may survive without stalling the harness.
22
25
  */
23
26
  export declare function runHarnessProcess(commandArgs: readonly string[], options: HarnessProcessOptions): Promise<HarnessProcessResult>;
@@ -1 +1 @@
1
- {"version":3,"file":"runHarnessProcess.js","names":[],"sources":["../../src/helpers/runHarnessProcess.ts"],"sourcesContent":["import child_process from 'node:child_process';\n\nimport { forciblyRemoveDirectorySync } from './temporaryProblemDirCopy.js';\n\nexport interface HarnessProcessOptions {\n cwd: string;\n env: NodeJS.ProcessEnv;\n timeoutMs: number;\n maxOutputBytes: number;\n /** A temporary copy to remove when the check itself is interrupted by a signal. */\n tempRoot?: string;\n}\n\nexport interface HarnessProcessResult {\n stdout: string;\n stderr: string;\n exitCode: number | undefined;\n /** The signal that terminated the harness when it did not exit on its own. */\n signal: NodeJS.Signals | undefined;\n /** Set when the run was killed by this helper (timeout or output cap) or could not be spawned. */\n failureReason: string | undefined;\n timedOut: boolean;\n}\n\ninterface LiveHarnessRun {\n pid: number;\n tempRoot: string | undefined;\n}\n\n// Detached harness groups no longer receive the terminal's SIGINT, so an interrupted run must\n// tear them down (and remove their temp copies) itself before exiting. The handlers stay installed\n// for the process lifetime: once the last run has settled they only iterate an empty set and re-raise\n// the signal, so removing them would change nothing observable.\nconst liveHarnessRuns = new Set<LiveHarnessRun>();\nlet signalHandlersInstalled = false;\n\n/**\n * Run a judge harness with `process.execPath` in its own process group, killing the whole group on\n * timeout or when the output cap is exceeded so grandchild submission processes cannot outlive the run.\n */\nexport function runHarnessProcess(\n commandArgs: readonly string[],\n options: HarnessProcessOptions\n): Promise<HarnessProcessResult> {\n installSignalHandlers();\n return new Promise((resolve) => {\n // process.execPath keeps the harness on the same bun executable regardless of PATH.\n const child = child_process.spawn(process.execPath, commandArgs, {\n cwd: options.cwd,\n detached: process.platform !== 'win32',\n env: options.env,\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n const liveRun: LiveHarnessRun | undefined =\n child.pid === undefined ? undefined : { pid: child.pid, tempRoot: options.tempRoot };\n if (liveRun) liveHarnessRuns.add(liveRun);\n const stdoutChunks: Buffer[] = [];\n const stderrChunks: Buffer[] = [];\n let totalOutputBytes = 0;\n let failureReason: string | undefined;\n let timedOut = false;\n let settled = false;\n\n // Returns false when an earlier kill already recorded its reason, so callers can tell whether theirs won.\n const killProcessGroup = (reason: string): boolean => {\n if (failureReason !== undefined) return false;\n failureReason = reason;\n // Stop buffering immediately: OS pipe buffers can keep emitting data after the kill, and\n // destroyed streams also let `close` fire even if a stray grandchild inherited the pipes.\n child.stdout?.destroy();\n child.stderr?.destroy();\n try {\n if (child.pid === undefined) {\n child.kill('SIGKILL');\n } else {\n killHarnessTree(child.pid);\n }\n } catch {\n child.kill('SIGKILL');\n }\n return true;\n };\n const timeoutId = setTimeout(() => {\n timedOut = killProcessGroup(`timed out after ${options.timeoutMs / 1000} seconds`);\n }, options.timeoutMs);\n\n const appendOutput = (chunk: Buffer, chunks: Buffer[]): void => {\n totalOutputBytes += chunk.byteLength;\n if (totalOutputBytes > options.maxOutputBytes) {\n killProcessGroup(`the harness printed more than ${options.maxOutputBytes / 1024 / 1024} MB of output`);\n return;\n }\n chunks.push(chunk);\n };\n child.stdout?.on('data', (chunk: Buffer) => appendOutput(chunk, stdoutChunks));\n child.stderr?.on('data', (chunk: Buffer) => appendOutput(chunk, stderrChunks));\n\n const settle = (exitCode: number | undefined, signal: NodeJS.Signals | undefined, spawnError?: Error): void => {\n if (settled) return;\n settled = true;\n clearTimeout(timeoutId);\n if (liveRun) liveHarnessRuns.delete(liveRun);\n resolve({\n stdout: Buffer.concat(stdoutChunks).toString('utf8'),\n stderr: Buffer.concat(stderrChunks).toString('utf8'),\n exitCode,\n signal,\n failureReason: failureReason ?? (spawnError ? `failed to run the harness: ${spawnError.message}` : undefined),\n timedOut,\n });\n };\n child.on('error', (error) => settle(undefined, undefined, error));\n child.on('close', (exitCode, signal) => settle(exitCode ?? undefined, signal ?? undefined));\n });\n}\n\nfunction installSignalHandlers(): void {\n if (signalHandlersInstalled) return;\n signalHandlersInstalled = true;\n for (const signal of ['SIGINT', 'SIGTERM'] as const) {\n process.once(signal, () => {\n for (const liveRun of liveHarnessRuns) {\n try {\n killHarnessTree(liveRun.pid);\n } catch {\n // The tree already exited.\n }\n // Best-effort cleanup while exiting; judged code may have left permission-locked entries.\n if (liveRun.tempRoot) forciblyRemoveDirectorySync(liveRun.tempRoot);\n }\n process.kill(process.pid, signal);\n });\n }\n}\n\n// On Windows the harness is not detached (process groups are unavailable), so killing only the\n// direct child would leave submission grandchildren running; taskkill terminates the whole tree.\nfunction killHarnessTree(pid: number): void {\n if (process.platform === 'win32') {\n child_process.spawnSync('taskkill', ['/PID', String(pid), '/T', '/F'], { stdio: 'ignore' });\n } else {\n process.kill(-pid, 'SIGKILL');\n }\n}\n"],"mappings":"6GAiCA,MAAM,EAAkB,IAAI,IAC5B,IAAI,EAA0B,GAM9B,SAAgB,EACd,EACA,EAC+B,CAE/B,OADA,EAAsB,EACf,IAAI,QAAS,GAAY,CAE9B,IAAM,EAAQ,EAAc,MAAM,QAAQ,SAAU,EAAa,CAC/D,IAAK,EAAQ,IACb,SAAU,QAAQ,WAAa,QAC/B,IAAK,EAAQ,IACb,MAAO,CAAC,SAAU,OAAQ,MAAM,CAClC,CAAC,EACK,EACJ,EAAM,MAAQ,IAAA,GAAY,IAAA,GAAY,CAAE,IAAK,EAAM,IAAK,SAAU,EAAQ,QAAS,EACjF,GAAS,EAAgB,IAAI,CAAO,EACxC,IAAM,EAAyB,CAAC,EAC1B,EAAyB,CAAC,EAC5B,EAAmB,EACnB,EACA,EAAW,GACX,EAAU,GAGR,EAAoB,GAA4B,CACpD,GAAI,IAAkB,IAAA,GAAW,MAAO,GACxC,EAAgB,EAGhB,EAAM,QAAQ,QAAQ,EACtB,EAAM,QAAQ,QAAQ,EACtB,GAAI,CACE,EAAM,MAAQ,IAAA,GAChB,EAAM,KAAK,SAAS,EAEpB,EAAgB,EAAM,GAAG,CAE7B,MAAQ,CACN,EAAM,KAAK,SAAS,CACtB,CACA,MAAO,EACT,EACM,EAAY,eAAiB,CACjC,EAAW,EAAiB,mBAAmB,EAAQ,UAAY,IAAK,SAAS,CACnF,EAAG,EAAQ,SAAS,EAEd,GAAgB,EAAe,IAA2B,CAE9D,GADA,GAAoB,EAAM,WACtB,EAAmB,EAAQ,eAAgB,CAC7C,EAAiB,iCAAiC,EAAQ,eAAiB,KAAO,KAAK,cAAc,EACrG,MACF,CACA,EAAO,KAAK,CAAK,CACnB,EACA,EAAM,QAAQ,GAAG,OAAS,GAAkB,EAAa,EAAO,CAAY,CAAC,EAC7E,EAAM,QAAQ,GAAG,OAAS,GAAkB,EAAa,EAAO,CAAY,CAAC,EAE7E,IAAM,GAAU,EAA8B,EAAoC,IAA6B,CACzG,IACJ,EAAU,GACV,aAAa,CAAS,EAClB,GAAS,EAAgB,OAAO,CAAO,EAC3C,EAAQ,CACN,OAAQ,OAAO,OAAO,CAAY,CAAC,CAAC,SAAS,MAAM,EACnD,OAAQ,OAAO,OAAO,CAAY,CAAC,CAAC,SAAS,MAAM,EACnD,WACA,SACA,cAAe,IAAkB,EAAa,8BAA8B,EAAW,UAAY,IAAA,IACnG,UACF,CAAC,EACH,EACA,EAAM,GAAG,QAAU,GAAU,EAAO,IAAA,GAAW,IAAA,GAAW,CAAK,CAAC,EAChE,EAAM,GAAG,SAAU,EAAU,IAAW,EAAO,GAAY,IAAA,GAAW,GAAU,IAAA,EAAS,CAAC,CAC5F,CAAC,CACH,CAEA,SAAS,GAA8B,CACjC,MACJ,GAA0B,GAC1B,IAAK,IAAM,IAAU,CAAC,SAAU,SAAS,EACvC,QAAQ,KAAK,MAAc,CACzB,IAAK,IAAM,KAAW,EAAiB,CACrC,GAAI,CACF,EAAgB,EAAQ,GAAG,CAC7B,MAAQ,CAER,CAEI,EAAQ,UAAU,EAA4B,EAAQ,QAAQ,CACpE,CACA,QAAQ,KAAK,QAAQ,IAAK,CAAM,CAClC,CAAC,CAbuB,CAe5B,CAIA,SAAS,EAAgB,EAAmB,CACtC,QAAQ,WAAa,QACvB,EAAc,UAAU,WAAY,CAAC,OAAQ,OAAO,CAAG,EAAG,KAAM,IAAI,EAAG,CAAE,MAAO,QAAS,CAAC,EAE1F,QAAQ,KAAK,CAAC,EAAK,SAAS,CAEhC"}
1
+ {"version":3,"file":"runHarnessProcess.js","names":["child_process"],"sources":["../../src/helpers/runHarnessProcess.ts"],"sourcesContent":["import child_process from 'node:child_process';\n\nimport { forciblyRemoveDirectorySync } from './temporaryProblemDirCopy.js';\n\nexport interface HarnessProcessOptions {\n cwd: string;\n env: NodeJS.ProcessEnv;\n timeoutMs: number;\n maxOutputBytes: number;\n /** A temporary copy to remove when the check itself is interrupted by a signal. */\n tempRoot?: string;\n}\n\nexport interface HarnessProcessResult {\n stdout: string;\n stderr: string;\n exitCode: number | undefined;\n /** The signal that terminated the harness when it did not exit on its own. */\n signal: NodeJS.Signals | undefined;\n /** Set when the run was killed by this helper (timeout or output cap) or could not be spawned. */\n failureReason: string | undefined;\n timedOut: boolean;\n}\n\ninterface LiveHarnessRun {\n pid: number;\n tempRoot: string | undefined;\n}\n\n// Detached harness groups no longer receive the terminal's SIGINT, so an interrupted run must\n// tear them down (and remove their temp copies) itself before exiting. The handlers stay installed\n// for the process lifetime: once the last run has settled they only iterate an empty set and re-raise\n// the signal, so removing them would change nothing observable.\nconst liveHarnessRuns = new Set<LiveHarnessRun>();\nlet signalHandlersInstalled = false;\n\n/**\n * Run a judge harness with `process.execPath` in its own process group, killing the whole group on\n * timeout or when the output cap is exceeded. The submissions a harness judges through\n * `spawnWithLimits` run in sessions of their own, out of that kill's reach; each wrapper's process\n * group is bounded by its own GNU `timeout` instead, while a descendant that moved to yet another\n * session may survive without stalling the harness.\n */\nexport function runHarnessProcess(\n commandArgs: readonly string[],\n options: HarnessProcessOptions\n): Promise<HarnessProcessResult> {\n installSignalHandlers();\n return new Promise((resolve) => {\n // process.execPath keeps the harness on the same bun executable regardless of PATH.\n const child = child_process.spawn(process.execPath, commandArgs, {\n cwd: options.cwd,\n detached: process.platform !== 'win32',\n env: options.env,\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n const liveRun: LiveHarnessRun | undefined =\n child.pid === undefined ? undefined : { pid: child.pid, tempRoot: options.tempRoot };\n if (liveRun) liveHarnessRuns.add(liveRun);\n const stdoutChunks: Buffer[] = [];\n const stderrChunks: Buffer[] = [];\n let totalOutputBytes = 0;\n let failureReason: string | undefined;\n let timedOut = false;\n let settled = false;\n\n // Returns false when an earlier kill already recorded its reason, so callers can tell whether theirs won.\n const killProcessGroup = (reason: string): boolean => {\n if (failureReason !== undefined) return false;\n failureReason = reason;\n // Stop buffering immediately: OS pipe buffers can keep emitting data after the kill, and\n // destroyed streams also let `close` fire even if a stray grandchild inherited the pipes.\n child.stdout?.destroy();\n child.stderr?.destroy();\n try {\n if (child.pid === undefined) {\n child.kill('SIGKILL');\n } else {\n killHarnessTree(child.pid);\n }\n } catch {\n child.kill('SIGKILL');\n }\n return true;\n };\n const timeoutId = setTimeout(() => {\n timedOut = killProcessGroup(`timed out after ${options.timeoutMs / 1000} seconds`);\n }, options.timeoutMs);\n\n const appendOutput = (chunk: Buffer, chunks: Buffer[]): void => {\n totalOutputBytes += chunk.byteLength;\n if (totalOutputBytes > options.maxOutputBytes) {\n killProcessGroup(`the harness printed more than ${options.maxOutputBytes / 1024 / 1024} MB of output`);\n return;\n }\n chunks.push(chunk);\n };\n child.stdout?.on('data', (chunk: Buffer) => appendOutput(chunk, stdoutChunks));\n child.stderr?.on('data', (chunk: Buffer) => appendOutput(chunk, stderrChunks));\n\n const settle = (exitCode: number | undefined, signal: NodeJS.Signals | undefined, spawnError?: Error): void => {\n if (settled) return;\n settled = true;\n clearTimeout(timeoutId);\n if (liveRun) liveHarnessRuns.delete(liveRun);\n resolve({\n stdout: Buffer.concat(stdoutChunks).toString('utf8'),\n stderr: Buffer.concat(stderrChunks).toString('utf8'),\n exitCode,\n signal,\n failureReason: failureReason ?? (spawnError ? `failed to run the harness: ${spawnError.message}` : undefined),\n timedOut,\n });\n };\n child.on('error', (error) => settle(undefined, undefined, error));\n child.on('close', (exitCode, signal) => settle(exitCode ?? undefined, signal ?? undefined));\n });\n}\n\nfunction installSignalHandlers(): void {\n if (signalHandlersInstalled) return;\n signalHandlersInstalled = true;\n for (const signal of ['SIGINT', 'SIGTERM'] as const) {\n process.once(signal, () => {\n for (const liveRun of liveHarnessRuns) {\n try {\n killHarnessTree(liveRun.pid);\n } catch {\n // The tree already exited.\n }\n // Best-effort cleanup while exiting; judged code may have left permission-locked entries.\n if (liveRun.tempRoot) forciblyRemoveDirectorySync(liveRun.tempRoot);\n }\n process.kill(process.pid, signal);\n });\n }\n}\n\n// On Windows the harness is not detached (process groups are unavailable), so killing only the\n// direct child would leave submission grandchildren running; taskkill terminates the whole tree.\nfunction killHarnessTree(pid: number): void {\n if (process.platform === 'win32') {\n child_process.spawnSync('taskkill', ['/PID', String(pid), '/T', '/F'], { stdio: 'ignore' });\n } else {\n process.kill(-pid, 'SIGKILL');\n }\n}\n"],"mappings":"6GAiCA,MAAM,EAAkB,IAAI,IAC5B,IAAI,EAA0B,GAS9B,SAAgB,EACd,EACA,EAC+B,CAE/B,OADA,EAAsB,EACf,IAAI,QAAS,GAAY,CAE9B,IAAM,EAAQA,EAAc,MAAM,QAAQ,SAAU,EAAa,CAC/D,IAAK,EAAQ,IACb,SAAU,QAAQ,WAAa,QAC/B,IAAK,EAAQ,IACb,MAAO,CAAC,SAAU,OAAQ,MAAM,CAClC,CAAC,EACK,EACJ,EAAM,MAAQ,IAAA,GAAY,IAAA,GAAY,CAAE,IAAK,EAAM,IAAK,SAAU,EAAQ,QAAS,EACjF,GAAS,EAAgB,IAAI,CAAO,EACxC,IAAM,EAAyB,CAAC,EAC1B,EAAyB,CAAC,EAC5B,EAAmB,EACnB,EACA,EAAW,GACX,EAAU,GAGR,EAAoB,GAA4B,CACpD,GAAI,IAAkB,IAAA,GAAW,MAAO,GACxC,EAAgB,EAGhB,EAAM,QAAQ,QAAQ,EACtB,EAAM,QAAQ,QAAQ,EACtB,GAAI,CACE,EAAM,MAAQ,IAAA,GAChB,EAAM,KAAK,SAAS,EAEpB,EAAgB,EAAM,GAAG,CAE7B,MAAQ,CACN,EAAM,KAAK,SAAS,CACtB,CACA,MAAO,EACT,EACM,EAAY,eAAiB,CACjC,EAAW,EAAiB,mBAAmB,EAAQ,UAAY,IAAK,SAAS,CACnF,EAAG,EAAQ,SAAS,EAEd,GAAgB,EAAe,IAA2B,CAE9D,GADA,GAAoB,EAAM,WACtB,EAAmB,EAAQ,eAAgB,CAC7C,EAAiB,iCAAiC,EAAQ,eAAiB,KAAO,KAAK,cAAc,EACrG,MACF,CACA,EAAO,KAAK,CAAK,CACnB,EACA,EAAM,QAAQ,GAAG,OAAS,GAAkB,EAAa,EAAO,CAAY,CAAC,EAC7E,EAAM,QAAQ,GAAG,OAAS,GAAkB,EAAa,EAAO,CAAY,CAAC,EAE7E,IAAM,GAAU,EAA8B,EAAoC,IAA6B,CACzG,IACJ,EAAU,GACV,aAAa,CAAS,EAClB,GAAS,EAAgB,OAAO,CAAO,EAC3C,EAAQ,CACN,OAAQ,OAAO,OAAO,CAAY,CAAC,CAAC,SAAS,MAAM,EACnD,OAAQ,OAAO,OAAO,CAAY,CAAC,CAAC,SAAS,MAAM,EACnD,WACA,SACA,cAAe,IAAkB,EAAa,8BAA8B,EAAW,UAAY,IAAA,IACnG,UACF,CAAC,EACH,EACA,EAAM,GAAG,QAAU,GAAU,EAAO,IAAA,GAAW,IAAA,GAAW,CAAK,CAAC,EAChE,EAAM,GAAG,SAAU,EAAU,IAAW,EAAO,GAAY,IAAA,GAAW,GAAU,IAAA,EAAS,CAAC,CAC5F,CAAC,CACH,CAEA,SAAS,GAA8B,CACjC,MACJ,GAA0B,GAC1B,IAAK,IAAM,IAAU,CAAC,SAAU,SAAS,EACvC,QAAQ,KAAK,MAAc,CACzB,IAAK,IAAM,KAAW,EAAiB,CACrC,GAAI,CACF,EAAgB,EAAQ,GAAG,CAC7B,MAAQ,CAER,CAEI,EAAQ,UAAU,EAA4B,EAAQ,QAAQ,CACpE,CACA,QAAQ,KAAK,QAAQ,IAAK,CAAM,CAClC,CAAC,CAbuB,CAe5B,CAIA,SAAS,EAAgB,EAAmB,CACtC,QAAQ,WAAa,QACvB,EAAc,UAAU,WAAY,CAAC,OAAQ,OAAO,CAAG,EAAG,KAAM,IAAI,EAAG,CAAE,MAAO,QAAS,CAAC,EAE1F,QAAQ,KAAK,CAAC,EAAK,SAAS,CAEhC"}
@@ -0,0 +1,2 @@
1
+ "use strict";const e=require("../_virtual/_rolldown/runtime.cjs");let t=require("node:path");t=e.__toESM(t,1);let n=require("node:fs/promises");n=e.__toESM(n,1);let r=require("node:os");r=e.__toESM(r,1);let i=require("node:child_process");i=e.__toESM(i,1);const a=1e3,o=h(),s=o!==void 0,c=new Set;process.once(`exit`,()=>{for(let e of c)u(e,`SIGKILL`)});async function l(e,t){let n=o===void 0?void 0:await d(`exercode-time-`);try{let r=process.platform!==`win32`,s=o===void 0?e:[...o,`--output=/dev/fd/3`,...e],l=r?[`timeout`,`-k`,`1`,String(t.timeLimitSeconds+a/1e3),...s]:s,d=Date.now(),m=i.default.spawn(l[0],l.slice(1),{cwd:t.cwd,detached:r,env:t.env,stdio:n===void 0?[`pipe`,`pipe`,`pipe`]:[`pipe`,`pipe`,`pipe`,n.fd]});c.add(m);let h=[],g=[],_=0,v=!1,y=!1,b=0,x,S=(e,n)=>{if(_>=t.outputLimitBytes){n.byteLength>0&&(y=!0,u(m,`SIGKILL`));return}let r=t.outputLimitBytes-_,i=n.byteLength>r?n.subarray(0,r):n;e.push(i),_+=i.byteLength,n.byteLength>r&&(y=!0,u(m,`SIGKILL`))};m.stdout.on(`data`,e=>S(h,e)),m.stderr.on(`data`,e=>S(g,e));let C=setTimeout(()=>{v=!0,u(m,`SIGTERM`)},t.timeLimitSeconds*1e3),w=setTimeout(()=>{v&&u(m,`SIGKILL`)},t.timeLimitSeconds*1e3+a);w.unref();let{status:T,signal:E}=await new Promise((e,n)=>{let r=!1,i,o,s=(t,a)=>{if(!r){if(r=!0,clearTimeout(o),i){n(i);return}e({status:t??void 0,signal:a??void 0})}},c=e=>{if(!r){if(m.pid===void 0){x=e.message,s(void 0,void 0);return}i=e,u(m,`SIGKILL`)}};m.on(`error`,c),m.stdin.on(`error`,e=>{e.code!==`EPIPE`&&c(e)}),m.on(`exit`,(e,t)=>{clearTimeout(C),clearTimeout(w),b=(Date.now()-d)/1e3,u(m,`SIGKILL`),o=setTimeout(()=>{m.stdout.destroy(),m.stderr.destroy(),s(e,t)},a)}),m.on(`close`,s),m.stdin.end(t.stdin)}).finally(()=>{clearTimeout(C),clearTimeout(w),c.delete(m)}),D=n===void 0?void 0:p(await f(n,4096));return{stdout:Buffer.concat(h).toString(),stderr:x??Buffer.concat(g).toString(),status:T,signal:E,timeSeconds:D?.timeSeconds||b,memoryBytes:D?.memoryBytes??0,timeCommandMessage:D?.message,timedOut:v||T===124&&b>=t.timeLimitSeconds,outputLimitExceeded:y}}finally{await n?.close()}}function u(e,t){if(e.pid!==void 0)try{if(process.platform===`win32`){e.kill(t);return}process.kill(-e.pid,t)}catch(e){if(!m(e,`ESRCH`)&&!m(e,`EPERM`))throw e}}async function d(e){let i=await n.default.mkdtemp(t.default.join(r.default.tmpdir(),e));try{return await n.default.open(t.default.join(i,`record`),`wx+`,384)}finally{await n.default.rm(i,{recursive:!0,force:!0})}}async function f(e,t){let{size:n}=await e.stat(),r=Math.min(n,t),{buffer:i}=await e.read(Buffer.alloc(r),0,r,n-r);return i.toString()}function p(e){let t=/(?:^|\n)(\d+(?:[.,]\d+)?) (\d+)\s*$/.exec(e);if(t)return{timeSeconds:Number(t[1].replace(`,`,`.`)),memoryBytes:Number(t[2])*1024,message:e.slice(0,t.index).trim()||void 0}}function m(e,t){return typeof e==`object`&&!!e&&`code`in e&&e.code===t}function h(){let e=r.default.platform()===`darwin`?`gtime`:`/usr/bin/time`,t=i.default.spawnSync(e,[`--version`],{stdio:`ignore`});if(!(t.error||t.status!==0))return[e,`--format`,`%e %M`]}exports.isTimeCommandAvailable=s,exports.spawnWithLimits=l;
2
+ //# sourceMappingURL=spawnWithLimits.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spawnWithLimits.cjs","names":["childProcess","fs","path","os"],"sources":["../../src/helpers/spawnWithLimits.ts"],"sourcesContent":["import childProcess from 'node:child_process';\nimport fs from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\n\nexport interface SpawnWithLimitsResult {\n stdout: string;\n stderr: string;\n status: number | undefined;\n signal: NodeJS.Signals | undefined;\n /** Wall time measured by GNU time, or measured here until the command exited when it produced none. */\n timeSeconds: number;\n /** Peak resident set size measured by GNU time, or 0 when it produced no measurement. */\n memoryBytes: number;\n /** The note GNU time adds when the command exits abnormally, e.g. `Command terminated by signal 11`. */\n timeCommandMessage: string | undefined;\n timedOut: boolean;\n outputLimitExceeded: boolean;\n}\n\nconst killGracePeriodMilliseconds = 1000;\n// GNU time writes its record to a file this process opened and unlinked at once, handed down as fd 3\n// of the command and reopened by GNU time through `/dev/fd`: the command runs as the same OS user,\n// so a record reachable by path could be swapped for a fake or for a FIFO that blocks the read.\nconst TIME_OUTPUT_FD = 3;\n// The command inherits that descriptor too, so only the tail that can hold GNU time's record is read.\nconst TIME_OUTPUT_TAIL_BYTES = 4096;\nconst timeCommand = resolveTimeCommand();\n\n/** Whether GNU time is available, i.e. whether `timeSeconds` and `memoryBytes` are measured at all. */\nexport const isTimeCommandAvailable = timeCommand !== undefined;\n\n// The commands run in their own sessions, so they outlive this process unless it ends them itself\n// on the way out (e.g. a preset's SIGINT handler calling `process.exit`).\nconst liveSubprocesses = new Set<childProcess.ChildProcess>();\nprocess.once('exit', () => {\n for (const subprocess of liveSubprocesses) killSubprocessGroup(subprocess, 'SIGKILL');\n});\n\n/**\n * Runs a command in its own process group with `stdin` piped in, killing the whole group once it\n * exceeds the time or output limit, and reports the wall time and peak memory measured by GNU time.\n */\nexport async function spawnWithLimits(\n command: readonly [string, ...string[]],\n context: {\n cwd: string;\n env: NodeJS.ProcessEnv;\n outputLimitBytes: number;\n stdin: string;\n timeLimitSeconds: number;\n }\n): Promise<SpawnWithLimitsResult> {\n const timeOutput = timeCommand === undefined ? undefined : await openUnlinkedFile('exercode-time-');\n try {\n const detached = process.platform !== 'win32';\n const timedCommand: readonly [string, ...string[]] =\n timeCommand === undefined ? command : [...timeCommand, `--output=/dev/fd/${TIME_OUTPUT_FD}`, ...command];\n // The command runs in its own session so the group kill below cannot hit this process, but that\n // also puts it out of reach of whoever kills this process. GNU `timeout` keeps the run bounded\n // in that case; it acts only after the timers below had their chance.\n const spawnedCommand: readonly [string, ...string[]] = detached\n ? [\n 'timeout',\n '-k',\n String(killGracePeriodMilliseconds / 1000),\n String(context.timeLimitSeconds + killGracePeriodMilliseconds / 1000),\n ...timedCommand,\n ]\n : timedCommand;\n const startTimeMilliseconds = Date.now();\n // The standard streams are always pipes; the type cannot express that with the extra entry.\n const subprocess = childProcess.spawn(spawnedCommand[0], spawnedCommand.slice(1), {\n cwd: context.cwd,\n detached,\n env: context.env,\n stdio: timeOutput === undefined ? ['pipe', 'pipe', 'pipe'] : ['pipe', 'pipe', 'pipe', timeOutput.fd],\n }) as childProcess.ChildProcessWithoutNullStreams;\n liveSubprocesses.add(subprocess);\n\n const stdoutChunks: Buffer[] = [];\n const stderrChunks: Buffer[] = [];\n let outputBytes = 0;\n let timedOut = false;\n let outputLimitExceeded = false;\n let wallTimeSeconds = 0;\n let spawnErrorMessage: string | undefined;\n\n const appendOutputChunk = (chunks: Buffer[], chunk: Buffer): void => {\n if (outputBytes >= context.outputLimitBytes) {\n if (chunk.byteLength > 0) {\n outputLimitExceeded = true;\n killSubprocessGroup(subprocess, 'SIGKILL');\n }\n return;\n }\n\n const remainingBytes = context.outputLimitBytes - outputBytes;\n const appendedChunk = chunk.byteLength > remainingBytes ? chunk.subarray(0, remainingBytes) : chunk;\n chunks.push(appendedChunk);\n outputBytes += appendedChunk.byteLength;\n\n if (chunk.byteLength > remainingBytes) {\n outputLimitExceeded = true;\n killSubprocessGroup(subprocess, 'SIGKILL');\n }\n };\n\n subprocess.stdout.on('data', (chunk: Buffer) => appendOutputChunk(stdoutChunks, chunk));\n subprocess.stderr.on('data', (chunk: Buffer) => appendOutputChunk(stderrChunks, chunk));\n\n const timeout = setTimeout(() => {\n timedOut = true;\n killSubprocessGroup(subprocess, 'SIGTERM');\n }, context.timeLimitSeconds * 1000);\n const killTimeout = setTimeout(\n () => {\n if (timedOut) killSubprocessGroup(subprocess, 'SIGKILL');\n },\n context.timeLimitSeconds * 1000 + killGracePeriodMilliseconds\n );\n killTimeout.unref();\n\n const { status, signal } = await new Promise<{ status: number | undefined; signal: NodeJS.Signals | undefined }>(\n (resolve, reject) => {\n let settled = false;\n let pendingError: Error | undefined;\n let closeTimeout: NodeJS.Timeout | undefined;\n const settle = (code: number | null | undefined, exitSignal: NodeJS.Signals | null | undefined): void => {\n if (settled) return;\n settled = true;\n clearTimeout(closeTimeout);\n if (pendingError) {\n reject(pendingError);\n return;\n }\n resolve({ status: code ?? undefined, signal: exitSignal ?? undefined });\n };\n const failAfterClose = (error: Error): void => {\n if (settled) return;\n if (subprocess.pid === undefined) {\n // The spawn itself failed, i.e. the head of the chain (`timeout` here, the command\n // itself on Windows) or the cwd is missing. GNU time reports a missing judged command\n // as status 127 instead. Report this like a run that produced only this message.\n spawnErrorMessage = error.message;\n settle(undefined, undefined);\n return;\n }\n pendingError = error;\n killSubprocessGroup(subprocess, 'SIGKILL');\n };\n subprocess.on('error', failAfterClose);\n subprocess.stdin.on('error', (error: NodeJS.ErrnoException) => {\n if (error.code !== 'EPIPE') failAfterClose(error);\n });\n subprocess.on('exit', (code, exitSignal) => {\n // The command is gone, so the limit timers must not fire during the grace period below. A\n // descendant that inherited the pipes (e.g. `sleep 1000 &`) would keep them open and hold\n // back 'close' until it ends: kill what is left of the group. A descendant that moved to\n // its own session survives that, so stop waiting for the pipes after the grace period and\n // keep the output read so far.\n clearTimeout(timeout);\n clearTimeout(killTimeout);\n wallTimeSeconds = (Date.now() - startTimeMilliseconds) / 1000;\n killSubprocessGroup(subprocess, 'SIGKILL');\n closeTimeout = setTimeout(() => {\n subprocess.stdout.destroy();\n subprocess.stderr.destroy();\n settle(code, exitSignal);\n }, killGracePeriodMilliseconds);\n });\n subprocess.on('close', settle);\n subprocess.stdin.end(context.stdin);\n }\n ).finally(() => {\n clearTimeout(timeout);\n clearTimeout(killTimeout);\n liveSubprocesses.delete(subprocess);\n });\n\n const timeResult =\n timeOutput === undefined ? undefined : parseTimeOutput(await readTail(timeOutput, TIME_OUTPUT_TAIL_BYTES));\n\n return {\n stdout: Buffer.concat(stdoutChunks).toString(),\n stderr: spawnErrorMessage ?? Buffer.concat(stderrChunks).toString(),\n status,\n signal,\n // GNU time rounds a fast run to 0.00; the wall time until 'exit' stands in for it (never the\n // grace period spent on the pipes afterwards).\n timeSeconds: timeResult?.timeSeconds || wallTimeSeconds,\n memoryBytes: timeResult?.memoryBytes ?? 0,\n timeCommandMessage: timeResult?.message,\n // 124 is `timeout` reporting that it had to end the run itself, unless the program exited\n // with that status on its own before the limit.\n timedOut: timedOut || (status === 124 && wallTimeSeconds >= context.timeLimitSeconds),\n outputLimitExceeded,\n };\n } finally {\n await timeOutput?.close();\n }\n}\n\nfunction killSubprocessGroup(subprocess: childProcess.ChildProcess, signal: NodeJS.Signals): void {\n if (subprocess.pid === undefined) return;\n\n try {\n if (process.platform === 'win32') {\n subprocess.kill(signal);\n return;\n }\n process.kill(-subprocess.pid, signal);\n } catch (error) {\n if (!isErrorWithCode(error, 'ESRCH') && !isErrorWithCode(error, 'EPERM')) throw error;\n }\n}\n\n// The file is unlinked before it is returned, so nothing can reach it by path anymore.\nasync function openUnlinkedFile(prefix: string): Promise<fs.FileHandle> {\n const directory = await fs.mkdtemp(path.join(os.tmpdir(), prefix));\n try {\n return await fs.open(path.join(directory, 'record'), 'wx+', 0o600);\n } finally {\n await fs.rm(directory, { recursive: true, force: true });\n }\n}\n\nasync function readTail(file: fs.FileHandle, maxBytes: number): Promise<string> {\n const { size } = await file.stat();\n const length = Math.min(size, maxBytes);\n const { buffer } = await file.read(Buffer.alloc(length), 0, length, size - length);\n return buffer.toString();\n}\n\n// Empty when the command was killed before GNU time wrote its record.\nfunction parseTimeOutput(\n content: string\n): { timeSeconds: number; memoryBytes: number; message: string | undefined } | undefined {\n const match = /(?:^|\\n)(\\d+(?:[.,]\\d+)?) (\\d+)\\s*$/.exec(content);\n if (!match) return undefined;\n\n return {\n timeSeconds: Number(match[1]!.replace(',', '.')),\n memoryBytes: Number(match[2]) * 1024,\n message: content.slice(0, match.index).trim() || undefined,\n };\n}\n\nfunction isErrorWithCode(error: unknown, code: string): boolean {\n return typeof error === 'object' && error !== null && 'code' in error && (error as { code: unknown }).code === code;\n}\n\nfunction resolveTimeCommand(): readonly [string, ...string[]] | undefined {\n const command = os.platform() === 'darwin' ? 'gtime' : '/usr/bin/time';\n const result = childProcess.spawnSync(command, ['--version'], { stdio: 'ignore' });\n if (result.error || result.status !== 0) return undefined;\n\n return [command, '--format', '%e %M'];\n}\n"],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ export interface SpawnWithLimitsResult {
2
+ stdout: string;
3
+ stderr: string;
4
+ status: number | undefined;
5
+ signal: NodeJS.Signals | undefined;
6
+ /** Wall time measured by GNU time, or measured here until the command exited when it produced none. */
7
+ timeSeconds: number;
8
+ /** Peak resident set size measured by GNU time, or 0 when it produced no measurement. */
9
+ memoryBytes: number;
10
+ /** The note GNU time adds when the command exits abnormally, e.g. `Command terminated by signal 11`. */
11
+ timeCommandMessage: string | undefined;
12
+ timedOut: boolean;
13
+ outputLimitExceeded: boolean;
14
+ }
15
+ /** Whether GNU time is available, i.e. whether `timeSeconds` and `memoryBytes` are measured at all. */
16
+ export declare const isTimeCommandAvailable: boolean;
17
+ /**
18
+ * Runs a command in its own process group with `stdin` piped in, killing the whole group once it
19
+ * exceeds the time or output limit, and reports the wall time and peak memory measured by GNU time.
20
+ */
21
+ export declare function spawnWithLimits(command: readonly [string, ...string[]], context: {
22
+ cwd: string;
23
+ env: NodeJS.ProcessEnv;
24
+ outputLimitBytes: number;
25
+ stdin: string;
26
+ timeLimitSeconds: number;
27
+ }): Promise<SpawnWithLimitsResult>;
@@ -0,0 +1,2 @@
1
+ import e from"node:path";import t from"node:fs/promises";import n from"node:os";import r from"node:child_process";const i=1e3,a=m(),o=a!==void 0,s=new Set;process.once(`exit`,()=>{for(let e of s)l(e,`SIGKILL`)});async function c(e,t){let n=a===void 0?void 0:await u(`exercode-time-`);try{let o=process.platform!==`win32`,c=a===void 0?e:[...a,`--output=/dev/fd/3`,...e],u=o?[`timeout`,`-k`,`1`,String(t.timeLimitSeconds+i/1e3),...c]:c,p=Date.now(),m=r.spawn(u[0],u.slice(1),{cwd:t.cwd,detached:o,env:t.env,stdio:n===void 0?[`pipe`,`pipe`,`pipe`]:[`pipe`,`pipe`,`pipe`,n.fd]});s.add(m);let h=[],g=[],_=0,v=!1,y=!1,b=0,x,S=(e,n)=>{if(_>=t.outputLimitBytes){n.byteLength>0&&(y=!0,l(m,`SIGKILL`));return}let r=t.outputLimitBytes-_,i=n.byteLength>r?n.subarray(0,r):n;e.push(i),_+=i.byteLength,n.byteLength>r&&(y=!0,l(m,`SIGKILL`))};m.stdout.on(`data`,e=>S(h,e)),m.stderr.on(`data`,e=>S(g,e));let C=setTimeout(()=>{v=!0,l(m,`SIGTERM`)},t.timeLimitSeconds*1e3),w=setTimeout(()=>{v&&l(m,`SIGKILL`)},t.timeLimitSeconds*1e3+i);w.unref();let{status:T,signal:E}=await new Promise((e,n)=>{let r=!1,a,o,s=(t,i)=>{if(!r){if(r=!0,clearTimeout(o),a){n(a);return}e({status:t??void 0,signal:i??void 0})}},c=e=>{if(!r){if(m.pid===void 0){x=e.message,s(void 0,void 0);return}a=e,l(m,`SIGKILL`)}};m.on(`error`,c),m.stdin.on(`error`,e=>{e.code!==`EPIPE`&&c(e)}),m.on(`exit`,(e,t)=>{clearTimeout(C),clearTimeout(w),b=(Date.now()-p)/1e3,l(m,`SIGKILL`),o=setTimeout(()=>{m.stdout.destroy(),m.stderr.destroy(),s(e,t)},i)}),m.on(`close`,s),m.stdin.end(t.stdin)}).finally(()=>{clearTimeout(C),clearTimeout(w),s.delete(m)}),D=n===void 0?void 0:f(await d(n,4096));return{stdout:Buffer.concat(h).toString(),stderr:x??Buffer.concat(g).toString(),status:T,signal:E,timeSeconds:D?.timeSeconds||b,memoryBytes:D?.memoryBytes??0,timeCommandMessage:D?.message,timedOut:v||T===124&&b>=t.timeLimitSeconds,outputLimitExceeded:y}}finally{await n?.close()}}function l(e,t){if(e.pid!==void 0)try{if(process.platform===`win32`){e.kill(t);return}process.kill(-e.pid,t)}catch(e){if(!p(e,`ESRCH`)&&!p(e,`EPERM`))throw e}}async function u(r){let i=await t.mkdtemp(e.join(n.tmpdir(),r));try{return await t.open(e.join(i,`record`),`wx+`,384)}finally{await t.rm(i,{recursive:!0,force:!0})}}async function d(e,t){let{size:n}=await e.stat(),r=Math.min(n,t),{buffer:i}=await e.read(Buffer.alloc(r),0,r,n-r);return i.toString()}function f(e){let t=/(?:^|\n)(\d+(?:[.,]\d+)?) (\d+)\s*$/.exec(e);if(t)return{timeSeconds:Number(t[1].replace(`,`,`.`)),memoryBytes:Number(t[2])*1024,message:e.slice(0,t.index).trim()||void 0}}function p(e,t){return typeof e==`object`&&!!e&&`code`in e&&e.code===t}function m(){let e=n.platform()===`darwin`?`gtime`:`/usr/bin/time`,t=r.spawnSync(e,[`--version`],{stdio:`ignore`});if(!(t.error||t.status!==0))return[e,`--format`,`%e %M`]}export{o as isTimeCommandAvailable,c as spawnWithLimits};
2
+ //# sourceMappingURL=spawnWithLimits.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spawnWithLimits.js","names":[],"sources":["../../src/helpers/spawnWithLimits.ts"],"sourcesContent":["import childProcess from 'node:child_process';\nimport fs from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\n\nexport interface SpawnWithLimitsResult {\n stdout: string;\n stderr: string;\n status: number | undefined;\n signal: NodeJS.Signals | undefined;\n /** Wall time measured by GNU time, or measured here until the command exited when it produced none. */\n timeSeconds: number;\n /** Peak resident set size measured by GNU time, or 0 when it produced no measurement. */\n memoryBytes: number;\n /** The note GNU time adds when the command exits abnormally, e.g. `Command terminated by signal 11`. */\n timeCommandMessage: string | undefined;\n timedOut: boolean;\n outputLimitExceeded: boolean;\n}\n\nconst killGracePeriodMilliseconds = 1000;\n// GNU time writes its record to a file this process opened and unlinked at once, handed down as fd 3\n// of the command and reopened by GNU time through `/dev/fd`: the command runs as the same OS user,\n// so a record reachable by path could be swapped for a fake or for a FIFO that blocks the read.\nconst TIME_OUTPUT_FD = 3;\n// The command inherits that descriptor too, so only the tail that can hold GNU time's record is read.\nconst TIME_OUTPUT_TAIL_BYTES = 4096;\nconst timeCommand = resolveTimeCommand();\n\n/** Whether GNU time is available, i.e. whether `timeSeconds` and `memoryBytes` are measured at all. */\nexport const isTimeCommandAvailable = timeCommand !== undefined;\n\n// The commands run in their own sessions, so they outlive this process unless it ends them itself\n// on the way out (e.g. a preset's SIGINT handler calling `process.exit`).\nconst liveSubprocesses = new Set<childProcess.ChildProcess>();\nprocess.once('exit', () => {\n for (const subprocess of liveSubprocesses) killSubprocessGroup(subprocess, 'SIGKILL');\n});\n\n/**\n * Runs a command in its own process group with `stdin` piped in, killing the whole group once it\n * exceeds the time or output limit, and reports the wall time and peak memory measured by GNU time.\n */\nexport async function spawnWithLimits(\n command: readonly [string, ...string[]],\n context: {\n cwd: string;\n env: NodeJS.ProcessEnv;\n outputLimitBytes: number;\n stdin: string;\n timeLimitSeconds: number;\n }\n): Promise<SpawnWithLimitsResult> {\n const timeOutput = timeCommand === undefined ? undefined : await openUnlinkedFile('exercode-time-');\n try {\n const detached = process.platform !== 'win32';\n const timedCommand: readonly [string, ...string[]] =\n timeCommand === undefined ? command : [...timeCommand, `--output=/dev/fd/${TIME_OUTPUT_FD}`, ...command];\n // The command runs in its own session so the group kill below cannot hit this process, but that\n // also puts it out of reach of whoever kills this process. GNU `timeout` keeps the run bounded\n // in that case; it acts only after the timers below had their chance.\n const spawnedCommand: readonly [string, ...string[]] = detached\n ? [\n 'timeout',\n '-k',\n String(killGracePeriodMilliseconds / 1000),\n String(context.timeLimitSeconds + killGracePeriodMilliseconds / 1000),\n ...timedCommand,\n ]\n : timedCommand;\n const startTimeMilliseconds = Date.now();\n // The standard streams are always pipes; the type cannot express that with the extra entry.\n const subprocess = childProcess.spawn(spawnedCommand[0], spawnedCommand.slice(1), {\n cwd: context.cwd,\n detached,\n env: context.env,\n stdio: timeOutput === undefined ? ['pipe', 'pipe', 'pipe'] : ['pipe', 'pipe', 'pipe', timeOutput.fd],\n }) as childProcess.ChildProcessWithoutNullStreams;\n liveSubprocesses.add(subprocess);\n\n const stdoutChunks: Buffer[] = [];\n const stderrChunks: Buffer[] = [];\n let outputBytes = 0;\n let timedOut = false;\n let outputLimitExceeded = false;\n let wallTimeSeconds = 0;\n let spawnErrorMessage: string | undefined;\n\n const appendOutputChunk = (chunks: Buffer[], chunk: Buffer): void => {\n if (outputBytes >= context.outputLimitBytes) {\n if (chunk.byteLength > 0) {\n outputLimitExceeded = true;\n killSubprocessGroup(subprocess, 'SIGKILL');\n }\n return;\n }\n\n const remainingBytes = context.outputLimitBytes - outputBytes;\n const appendedChunk = chunk.byteLength > remainingBytes ? chunk.subarray(0, remainingBytes) : chunk;\n chunks.push(appendedChunk);\n outputBytes += appendedChunk.byteLength;\n\n if (chunk.byteLength > remainingBytes) {\n outputLimitExceeded = true;\n killSubprocessGroup(subprocess, 'SIGKILL');\n }\n };\n\n subprocess.stdout.on('data', (chunk: Buffer) => appendOutputChunk(stdoutChunks, chunk));\n subprocess.stderr.on('data', (chunk: Buffer) => appendOutputChunk(stderrChunks, chunk));\n\n const timeout = setTimeout(() => {\n timedOut = true;\n killSubprocessGroup(subprocess, 'SIGTERM');\n }, context.timeLimitSeconds * 1000);\n const killTimeout = setTimeout(\n () => {\n if (timedOut) killSubprocessGroup(subprocess, 'SIGKILL');\n },\n context.timeLimitSeconds * 1000 + killGracePeriodMilliseconds\n );\n killTimeout.unref();\n\n const { status, signal } = await new Promise<{ status: number | undefined; signal: NodeJS.Signals | undefined }>(\n (resolve, reject) => {\n let settled = false;\n let pendingError: Error | undefined;\n let closeTimeout: NodeJS.Timeout | undefined;\n const settle = (code: number | null | undefined, exitSignal: NodeJS.Signals | null | undefined): void => {\n if (settled) return;\n settled = true;\n clearTimeout(closeTimeout);\n if (pendingError) {\n reject(pendingError);\n return;\n }\n resolve({ status: code ?? undefined, signal: exitSignal ?? undefined });\n };\n const failAfterClose = (error: Error): void => {\n if (settled) return;\n if (subprocess.pid === undefined) {\n // The spawn itself failed, i.e. the head of the chain (`timeout` here, the command\n // itself on Windows) or the cwd is missing. GNU time reports a missing judged command\n // as status 127 instead. Report this like a run that produced only this message.\n spawnErrorMessage = error.message;\n settle(undefined, undefined);\n return;\n }\n pendingError = error;\n killSubprocessGroup(subprocess, 'SIGKILL');\n };\n subprocess.on('error', failAfterClose);\n subprocess.stdin.on('error', (error: NodeJS.ErrnoException) => {\n if (error.code !== 'EPIPE') failAfterClose(error);\n });\n subprocess.on('exit', (code, exitSignal) => {\n // The command is gone, so the limit timers must not fire during the grace period below. A\n // descendant that inherited the pipes (e.g. `sleep 1000 &`) would keep them open and hold\n // back 'close' until it ends: kill what is left of the group. A descendant that moved to\n // its own session survives that, so stop waiting for the pipes after the grace period and\n // keep the output read so far.\n clearTimeout(timeout);\n clearTimeout(killTimeout);\n wallTimeSeconds = (Date.now() - startTimeMilliseconds) / 1000;\n killSubprocessGroup(subprocess, 'SIGKILL');\n closeTimeout = setTimeout(() => {\n subprocess.stdout.destroy();\n subprocess.stderr.destroy();\n settle(code, exitSignal);\n }, killGracePeriodMilliseconds);\n });\n subprocess.on('close', settle);\n subprocess.stdin.end(context.stdin);\n }\n ).finally(() => {\n clearTimeout(timeout);\n clearTimeout(killTimeout);\n liveSubprocesses.delete(subprocess);\n });\n\n const timeResult =\n timeOutput === undefined ? undefined : parseTimeOutput(await readTail(timeOutput, TIME_OUTPUT_TAIL_BYTES));\n\n return {\n stdout: Buffer.concat(stdoutChunks).toString(),\n stderr: spawnErrorMessage ?? Buffer.concat(stderrChunks).toString(),\n status,\n signal,\n // GNU time rounds a fast run to 0.00; the wall time until 'exit' stands in for it (never the\n // grace period spent on the pipes afterwards).\n timeSeconds: timeResult?.timeSeconds || wallTimeSeconds,\n memoryBytes: timeResult?.memoryBytes ?? 0,\n timeCommandMessage: timeResult?.message,\n // 124 is `timeout` reporting that it had to end the run itself, unless the program exited\n // with that status on its own before the limit.\n timedOut: timedOut || (status === 124 && wallTimeSeconds >= context.timeLimitSeconds),\n outputLimitExceeded,\n };\n } finally {\n await timeOutput?.close();\n }\n}\n\nfunction killSubprocessGroup(subprocess: childProcess.ChildProcess, signal: NodeJS.Signals): void {\n if (subprocess.pid === undefined) return;\n\n try {\n if (process.platform === 'win32') {\n subprocess.kill(signal);\n return;\n }\n process.kill(-subprocess.pid, signal);\n } catch (error) {\n if (!isErrorWithCode(error, 'ESRCH') && !isErrorWithCode(error, 'EPERM')) throw error;\n }\n}\n\n// The file is unlinked before it is returned, so nothing can reach it by path anymore.\nasync function openUnlinkedFile(prefix: string): Promise<fs.FileHandle> {\n const directory = await fs.mkdtemp(path.join(os.tmpdir(), prefix));\n try {\n return await fs.open(path.join(directory, 'record'), 'wx+', 0o600);\n } finally {\n await fs.rm(directory, { recursive: true, force: true });\n }\n}\n\nasync function readTail(file: fs.FileHandle, maxBytes: number): Promise<string> {\n const { size } = await file.stat();\n const length = Math.min(size, maxBytes);\n const { buffer } = await file.read(Buffer.alloc(length), 0, length, size - length);\n return buffer.toString();\n}\n\n// Empty when the command was killed before GNU time wrote its record.\nfunction parseTimeOutput(\n content: string\n): { timeSeconds: number; memoryBytes: number; message: string | undefined } | undefined {\n const match = /(?:^|\\n)(\\d+(?:[.,]\\d+)?) (\\d+)\\s*$/.exec(content);\n if (!match) return undefined;\n\n return {\n timeSeconds: Number(match[1]!.replace(',', '.')),\n memoryBytes: Number(match[2]) * 1024,\n message: content.slice(0, match.index).trim() || undefined,\n };\n}\n\nfunction isErrorWithCode(error: unknown, code: string): boolean {\n return typeof error === 'object' && error !== null && 'code' in error && (error as { code: unknown }).code === code;\n}\n\nfunction resolveTimeCommand(): readonly [string, ...string[]] | undefined {\n const command = os.platform() === 'darwin' ? 'gtime' : '/usr/bin/time';\n const result = childProcess.spawnSync(command, ['--version'], { stdio: 'ignore' });\n if (result.error || result.status !== 0) return undefined;\n\n return [command, '--format', '%e %M'];\n}\n"],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ "use strict";const e=require("./spawnWithLimits.cjs");async function t(t,n,r,i){if(!e.isTimeCommandAvailable)throw Error(`GNU time (gtime on macOS, /usr/bin/time on Linux) is required.`);let a=await e.spawnWithLimits([t,...n],{cwd:r.cwd,env:r.env,outputLimitBytes:8388608,stdin:r.stdin??``,timeLimitSeconds:i}),o=a.timeCommandMessage?`${a.stderr}${a.stderr&&!a.stderr.endsWith(`
2
+ `)?`
3
+ `:``}${a.timeCommandMessage}\n`:a.stderr;return{stdout:a.stdout,stderr:o,status:a.timedOut||a.outputLimitExceeded?0:a.status,timeSeconds:a.timedOut?i+.001:a.timeSeconds,memoryBytes:a.memoryBytes,outputLimitExceeded:a.outputLimitExceeded}}exports.spawnWithTimeout=t;
4
+ //# sourceMappingURL=spawnWithTimeout.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spawnWithTimeout.cjs","names":["isTimeCommandAvailable","spawnWithLimits"],"sources":["../../src/helpers/spawnWithTimeout.ts"],"sourcesContent":["import { isTimeCommandAvailable, spawnWithLimits } from './spawnWithLimits.js';\n\nconst OUTPUT_LIMIT_BYTES = 8 * 1024 * 1024;\n\n/**\n * Runs a submission-derived command under a time limit and an output cap. A run that hits a limit is\n * reported like a normal exit (status 0), with `timeSeconds` just above the limit or the output\n * truncated at the cap, so callers judge it by the limit it hit. A missing command is reported by\n * GNU time as status 127; a spawn that fails outright (e.g. a missing cwd) is reported with no\n * status and the error as stderr; a failure on the judge's side throws.\n */\nexport async function spawnWithTimeout(\n command: string,\n args: readonly string[],\n context: { cwd: string; env: NodeJS.ProcessEnv; stdin?: string },\n timeoutSeconds: number\n): Promise<{\n stdout: string;\n stderr: string;\n status: number | undefined;\n timeSeconds: number;\n memoryBytes: number;\n outputLimitExceeded: boolean;\n}> {\n // Without GNU time's measurements, the memory limit would silently never apply and the reported\n // time would be this process's wall clock rather than the program's.\n if (!isTimeCommandAvailable) throw new Error('GNU time (gtime on macOS, /usr/bin/time on Linux) is required.');\n\n const result = await spawnWithLimits([command, ...args], {\n cwd: context.cwd,\n env: context.env,\n outputLimitBytes: OUTPUT_LIMIT_BYTES,\n stdin: context.stdin ?? '',\n timeLimitSeconds: timeoutSeconds,\n });\n\n // Keep GNU time's note about an abnormal exit (e.g. a segmentation fault) visible to the learner.\n const stderr = result.timeCommandMessage\n ? `${result.stderr}${result.stderr && !result.stderr.endsWith('\\n') ? '\\n' : ''}${result.timeCommandMessage}\\n`\n : result.stderr;\n\n return {\n stdout: result.stdout,\n stderr,\n status: result.timedOut || result.outputLimitExceeded ? 0 : result.status,\n timeSeconds: result.timedOut ? timeoutSeconds + 1e-3 : result.timeSeconds,\n memoryBytes: result.memoryBytes,\n outputLimitExceeded: result.outputLimitExceeded,\n };\n}\n"],"mappings":"sDAWA,eAAsB,EACpB,EACA,EACA,EACA,EAQC,CAGD,GAAI,CAACA,EAAAA,uBAAwB,MAAU,MAAM,gEAAgE,EAE7G,IAAM,EAAS,MAAMC,EAAAA,gBAAgB,CAAC,EAAS,GAAG,CAAI,EAAG,CACvD,IAAK,EAAQ,IACb,IAAK,EAAQ,IACb,iBAAkB,QAClB,MAAO,EAAQ,OAAS,GACxB,iBAAkB,CACpB,CAAC,EAGK,EAAS,EAAO,mBAClB,GAAG,EAAO,SAAS,EAAO,QAAU,CAAC,EAAO,OAAO,SAAS;CAAI,EAAI;EAAO,KAAK,EAAO,mBAAmB,IAC1G,EAAO,OAEX,MAAO,CACL,OAAQ,EAAO,OACf,SACA,OAAQ,EAAO,UAAY,EAAO,oBAAsB,EAAI,EAAO,OACnE,YAAa,EAAO,SAAW,EAAiB,KAAO,EAAO,YAC9D,YAAa,EAAO,YACpB,oBAAqB,EAAO,mBAC9B,CACF"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Runs a submission-derived command under a time limit and an output cap. A run that hits a limit is
3
+ * reported like a normal exit (status 0), with `timeSeconds` just above the limit or the output
4
+ * truncated at the cap, so callers judge it by the limit it hit. A missing command is reported by
5
+ * GNU time as status 127; a spawn that fails outright (e.g. a missing cwd) is reported with no
6
+ * status and the error as stderr; a failure on the judge's side throws.
7
+ */
8
+ export declare function spawnWithTimeout(command: string, args: readonly string[], context: {
9
+ cwd: string;
10
+ env: NodeJS.ProcessEnv;
11
+ stdin?: string;
12
+ }, timeoutSeconds: number): Promise<{
13
+ stdout: string;
14
+ stderr: string;
15
+ status: number | undefined;
16
+ timeSeconds: number;
17
+ memoryBytes: number;
18
+ outputLimitExceeded: boolean;
19
+ }>;
@@ -0,0 +1,4 @@
1
+ import{isTimeCommandAvailable as e,spawnWithLimits as t}from"./spawnWithLimits.js";async function n(n,r,i,a){if(!e)throw Error(`GNU time (gtime on macOS, /usr/bin/time on Linux) is required.`);let o=await t([n,...r],{cwd:i.cwd,env:i.env,outputLimitBytes:8388608,stdin:i.stdin??``,timeLimitSeconds:a}),s=o.timeCommandMessage?`${o.stderr}${o.stderr&&!o.stderr.endsWith(`
2
+ `)?`
3
+ `:``}${o.timeCommandMessage}\n`:o.stderr;return{stdout:o.stdout,stderr:s,status:o.timedOut||o.outputLimitExceeded?0:o.status,timeSeconds:o.timedOut?a+.001:o.timeSeconds,memoryBytes:o.memoryBytes,outputLimitExceeded:o.outputLimitExceeded}}export{n as spawnWithTimeout};
4
+ //# sourceMappingURL=spawnWithTimeout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spawnWithTimeout.js","names":[],"sources":["../../src/helpers/spawnWithTimeout.ts"],"sourcesContent":["import { isTimeCommandAvailable, spawnWithLimits } from './spawnWithLimits.js';\n\nconst OUTPUT_LIMIT_BYTES = 8 * 1024 * 1024;\n\n/**\n * Runs a submission-derived command under a time limit and an output cap. A run that hits a limit is\n * reported like a normal exit (status 0), with `timeSeconds` just above the limit or the output\n * truncated at the cap, so callers judge it by the limit it hit. A missing command is reported by\n * GNU time as status 127; a spawn that fails outright (e.g. a missing cwd) is reported with no\n * status and the error as stderr; a failure on the judge's side throws.\n */\nexport async function spawnWithTimeout(\n command: string,\n args: readonly string[],\n context: { cwd: string; env: NodeJS.ProcessEnv; stdin?: string },\n timeoutSeconds: number\n): Promise<{\n stdout: string;\n stderr: string;\n status: number | undefined;\n timeSeconds: number;\n memoryBytes: number;\n outputLimitExceeded: boolean;\n}> {\n // Without GNU time's measurements, the memory limit would silently never apply and the reported\n // time would be this process's wall clock rather than the program's.\n if (!isTimeCommandAvailable) throw new Error('GNU time (gtime on macOS, /usr/bin/time on Linux) is required.');\n\n const result = await spawnWithLimits([command, ...args], {\n cwd: context.cwd,\n env: context.env,\n outputLimitBytes: OUTPUT_LIMIT_BYTES,\n stdin: context.stdin ?? '',\n timeLimitSeconds: timeoutSeconds,\n });\n\n // Keep GNU time's note about an abnormal exit (e.g. a segmentation fault) visible to the learner.\n const stderr = result.timeCommandMessage\n ? `${result.stderr}${result.stderr && !result.stderr.endsWith('\\n') ? '\\n' : ''}${result.timeCommandMessage}\\n`\n : result.stderr;\n\n return {\n stdout: result.stdout,\n stderr,\n status: result.timedOut || result.outputLimitExceeded ? 0 : result.status,\n timeSeconds: result.timedOut ? timeoutSeconds + 1e-3 : result.timeSeconds,\n memoryBytes: result.memoryBytes,\n outputLimitExceeded: result.outputLimitExceeded,\n };\n}\n"],"mappings":"mFAWA,eAAsB,EACpB,EACA,EACA,EACA,EAQC,CAGD,GAAI,CAAC,EAAwB,MAAU,MAAM,gEAAgE,EAE7G,IAAM,EAAS,MAAM,EAAgB,CAAC,EAAS,GAAG,CAAI,EAAG,CACvD,IAAK,EAAQ,IACb,IAAK,EAAQ,IACb,iBAAkB,QAClB,MAAO,EAAQ,OAAS,GACxB,iBAAkB,CACpB,CAAC,EAGK,EAAS,EAAO,mBAClB,GAAG,EAAO,SAAS,EAAO,QAAU,CAAC,EAAO,OAAO,SAAS;CAAI,EAAI;EAAO,KAAK,EAAO,mBAAmB,IAC1G,EAAO,OAEX,MAAO,CACL,OAAQ,EAAO,OACf,SACA,OAAQ,EAAO,UAAY,EAAO,oBAAsB,EAAI,EAAO,OACnE,YAAa,EAAO,SAAW,EAAiB,KAAO,EAAO,YAC9D,YAAa,EAAO,YACpB,oBAAqB,EAAO,mBAC9B,CACF"}
@@ -0,0 +1,2 @@
1
+ "use strict";const e=/example/;exports.EXAMPLE_TEST_CASE_ID_PATTERN=e,exports.MAX_STDOUT_LENGTH=5e4;
2
+ //# sourceMappingURL=stdioJudgeRules.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stdioJudgeRules.cjs","names":[],"sources":["../../src/helpers/stdioJudgeRules.ts"],"sourcesContent":["/** Longest stdout (and stderr) the stdio judge accepts from one run; a longer output is rejected. */\nexport const MAX_STDOUT_LENGTH = 50_000;\n\n/** The judge server shows a test case on the problem page when its id contains `example`. */\nexport const EXAMPLE_TEST_CASE_ID_PATTERN = /example/;\n"],"mappings":"aACA,MAGa,EAA+B"}
@@ -0,0 +1,4 @@
1
+ /** Longest stdout (and stderr) the stdio judge accepts from one run; a longer output is rejected. */
2
+ export declare const MAX_STDOUT_LENGTH = 50000;
3
+ /** The judge server shows a test case on the problem page when its id contains `example`. */
4
+ export declare const EXAMPLE_TEST_CASE_ID_PATTERN: RegExp;
@@ -0,0 +1,2 @@
1
+ const e=5e4,t=/example/;export{t as EXAMPLE_TEST_CASE_ID_PATTERN,e as MAX_STDOUT_LENGTH};
2
+ //# sourceMappingURL=stdioJudgeRules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stdioJudgeRules.js","names":[],"sources":["../../src/helpers/stdioJudgeRules.ts"],"sourcesContent":["/** Longest stdout (and stderr) the stdio judge accepts from one run; a longer output is rejected. */\nexport const MAX_STDOUT_LENGTH = 50_000;\n\n/** The judge server shows a test case on the problem page when its id contains `example`. */\nexport const EXAMPLE_TEST_CASE_ID_PATTERN = /example/;\n"],"mappings":"AACA,MAAa,EAAoB,IAGpB,EAA+B"}
@@ -1,2 +1,2 @@
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);async function a(e,r={}){let a=await t.default.promises.mkdtemp(n.default.join(i.default.tmpdir(),`problem-utils-isolation_`));r.onTempRootCreated?.(a);try{let r=n.default.resolve(e),i=n.default.join(a,f(r));return await t.default.promises.mkdir(n.default.dirname(i),{recursive:!0}),await t.default.promises.cp(r,i,{recursive:!0,filter:l,verbatimSymlinks:!0}),await d(a,r),{tempRoot:a,copiedProblemDir:i}}catch(e){throw await o(a),e}}async function o(e){try{return await t.default.promises.rm(e,{recursive:!0,force:!0}),!0}catch{c(e);try{return await t.default.promises.rm(e,{recursive:!0,force:!0}),!0}catch{return!1}}}function s(e){try{return t.default.rmSync(e,{recursive:!0,force:!0}),!0}catch{c(e);try{return t.default.rmSync(e,{recursive:!0,force:!0}),!0}catch{return!1}}}function c(e){process.platform!==`win32`&&r.default.spawnSync(`chmod`,[`-R`,`u+rwX`,e])}async function l(e){if(!u(e))return!1;if((await t.default.promises.lstat(e)).isSymbolicLink()&&n.default.isAbsolute(await t.default.promises.readlink(e)))throw Error(`${e} is an absolute symlink, which would escape the temporary copy; use a relative symlink`);return!0}function u(e){let t=n.default.basename(e);return t!==`node_modules`&&t!==`.git`}async function d(e,r){let i=n.default.resolve(r);for(;;){let r=n.default.join(i,`node_modules`);if(t.default.existsSync(r)){let a=n.default.join(e,f(i),`node_modules`);try{await t.default.promises.symlink(r,a,process.platform===`win32`?`junction`:`dir`)}catch{}}let a=n.default.dirname(i);if(a===i)break;i=a}}function f(e){let{root:t}=n.default.parse(e);return n.default.relative(t,e)}exports.copyProblemDirToTemporaryRoot=a,exports.forciblyRemoveDirectory=o,exports.forciblyRemoveDirectorySync=s;
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:os");r=e.__toESM(r,1);let i=require("node:child_process");i=e.__toESM(i,1);async function a(e,i={}){let a=await t.default.promises.mkdtemp(n.default.join(r.default.tmpdir(),`problem-utils-isolation_`));i.onTempRootCreated?.(a);try{let r=n.default.resolve(e),i=n.default.join(a,f(r));return await t.default.promises.mkdir(n.default.dirname(i),{recursive:!0}),await t.default.promises.cp(r,i,{recursive:!0,filter:l,verbatimSymlinks:!0}),await d(a,r),{tempRoot:a,copiedProblemDir:i}}catch(e){throw await o(a),e}}async function o(e){try{return await t.default.promises.rm(e,{recursive:!0,force:!0}),!0}catch{c(e);try{return await t.default.promises.rm(e,{recursive:!0,force:!0}),!0}catch{return!1}}}function s(e){try{return t.default.rmSync(e,{recursive:!0,force:!0}),!0}catch{c(e);try{return t.default.rmSync(e,{recursive:!0,force:!0}),!0}catch{return!1}}}function c(e){process.platform!==`win32`&&i.default.spawnSync(`chmod`,[`-R`,`u+rwX`,e])}async function l(e){if(!u(e))return!1;if((await t.default.promises.lstat(e)).isSymbolicLink()&&n.default.isAbsolute(await t.default.promises.readlink(e)))throw Error(`${e} is an absolute symlink, which would escape the temporary copy; use a relative symlink`);return!0}function u(e){let t=n.default.basename(e);return t!==`node_modules`&&t!==`.git`}async function d(e,r){let i=n.default.resolve(r);for(;;){let r=n.default.join(i,`node_modules`);if(t.default.existsSync(r)){let a=n.default.join(e,f(i),`node_modules`);try{await t.default.promises.symlink(r,a,process.platform===`win32`?`junction`:`dir`)}catch{}}let a=n.default.dirname(i);if(a===i)break;i=a}}function f(e){let{root:t}=n.default.parse(e);return n.default.relative(t,e)}exports.copyProblemDirToTemporaryRoot=a,exports.forciblyRemoveDirectory=o,exports.forciblyRemoveDirectorySync=s;
2
2
  //# sourceMappingURL=temporaryProblemDirCopy.cjs.map
@@ -1,2 +1,2 @@
1
- import e from"node:fs";import t from"node:path";import n from"node:child_process";import r from"node:os";async function i(n,i={}){let o=await e.promises.mkdtemp(t.join(r.tmpdir(),`problem-utils-isolation_`));i.onTempRootCreated?.(o);try{let r=t.resolve(n),i=t.join(o,d(r));return await e.promises.mkdir(t.dirname(i),{recursive:!0}),await e.promises.cp(r,i,{recursive:!0,filter:c,verbatimSymlinks:!0}),await u(o,r),{tempRoot:o,copiedProblemDir:i}}catch(e){throw await a(o),e}}async function a(t){try{return await e.promises.rm(t,{recursive:!0,force:!0}),!0}catch{s(t);try{return await e.promises.rm(t,{recursive:!0,force:!0}),!0}catch{return!1}}}function o(t){try{return e.rmSync(t,{recursive:!0,force:!0}),!0}catch{s(t);try{return e.rmSync(t,{recursive:!0,force:!0}),!0}catch{return!1}}}function s(e){process.platform!==`win32`&&n.spawnSync(`chmod`,[`-R`,`u+rwX`,e])}async function c(n){if(!l(n))return!1;if((await e.promises.lstat(n)).isSymbolicLink()&&t.isAbsolute(await e.promises.readlink(n)))throw Error(`${n} is an absolute symlink, which would escape the temporary copy; use a relative symlink`);return!0}function l(e){let n=t.basename(e);return n!==`node_modules`&&n!==`.git`}async function u(n,r){let i=t.resolve(r);for(;;){let r=t.join(i,`node_modules`);if(e.existsSync(r)){let a=t.join(n,d(i),`node_modules`);try{await e.promises.symlink(r,a,process.platform===`win32`?`junction`:`dir`)}catch{}}let a=t.dirname(i);if(a===i)break;i=a}}function d(e){let{root:n}=t.parse(e);return t.relative(n,e)}export{i as copyProblemDirToTemporaryRoot,a as forciblyRemoveDirectory,o as forciblyRemoveDirectorySync};
1
+ import e from"node:fs";import t from"node:path";import n from"node:os";import r from"node:child_process";async function i(r,i={}){let o=await e.promises.mkdtemp(t.join(n.tmpdir(),`problem-utils-isolation_`));i.onTempRootCreated?.(o);try{let n=t.resolve(r),i=t.join(o,d(n));return await e.promises.mkdir(t.dirname(i),{recursive:!0}),await e.promises.cp(n,i,{recursive:!0,filter:c,verbatimSymlinks:!0}),await u(o,n),{tempRoot:o,copiedProblemDir:i}}catch(e){throw await a(o),e}}async function a(t){try{return await e.promises.rm(t,{recursive:!0,force:!0}),!0}catch{s(t);try{return await e.promises.rm(t,{recursive:!0,force:!0}),!0}catch{return!1}}}function o(t){try{return e.rmSync(t,{recursive:!0,force:!0}),!0}catch{s(t);try{return e.rmSync(t,{recursive:!0,force:!0}),!0}catch{return!1}}}function s(e){process.platform!==`win32`&&r.spawnSync(`chmod`,[`-R`,`u+rwX`,e])}async function c(n){if(!l(n))return!1;if((await e.promises.lstat(n)).isSymbolicLink()&&t.isAbsolute(await e.promises.readlink(n)))throw Error(`${n} is an absolute symlink, which would escape the temporary copy; use a relative symlink`);return!0}function l(e){let n=t.basename(e);return n!==`node_modules`&&n!==`.git`}async function u(n,r){let i=t.resolve(r);for(;;){let r=t.join(i,`node_modules`);if(e.existsSync(r)){let a=t.join(n,d(i),`node_modules`);try{await e.promises.symlink(r,a,process.platform===`win32`?`junction`:`dir`)}catch{}}let a=t.dirname(i);if(a===i)break;i=a}}function d(e){let{root:n}=t.parse(e);return t.relative(n,e)}export{i as copyProblemDirToTemporaryRoot,a as forciblyRemoveDirectory,o as forciblyRemoveDirectorySync};
2
2
  //# sourceMappingURL=temporaryProblemDirCopy.js.map
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./helpers/compareStdoutAsSpaceSeparatedTokens.cjs"),t=require("./types/testCaseResult.cjs"),n=require("./helpers/printTestCaseResult.cjs"),r=require("./helpers/compareExpectedOutputFiles.cjs"),i=require("./helpers/defaultStdioHarness.cjs"),a=require("./helpers/parseArgs.cjs"),o=require("./helpers/parseCsv.cjs"),s=require("./helpers/readTestCases.cjs"),c=require("./helpers/removeCommentsInSourceCode.cjs"),l=require("./helpers/runCommandInTemporaryPackageManagerProject.cjs"),u=require("./helpers/runPythonInTemporaryUvProject.cjs"),d=require("./helpers/sourceCodeGrammars.cjs"),f=require("./helpers/startHttpServer.cjs"),p=require("./types/decisionCode.cjs"),m=require("./types/problem.cjs");exports.CsvParseError=o.CsvParseError,exports.DecisionCode=p.DecisionCode,exports.HARNESS_FILE_PRESETS=i.HARNESS_FILE_PRESETS,exports.MAX_COMPARED_FILE_BYTES=r.MAX_COMPARED_FILE_BYTES,exports.MAX_REPORTED_FILE_BYTES=r.MAX_REPORTED_FILE_BYTES,exports.TEST_CASE_RESULT_PREFIX=t.TEST_CASE_RESULT_PREFIX,exports.codeRuleSchema=m.codeRuleSchema,exports.compareExpectedOutputFiles=r.compareExpectedOutputFiles,exports.compareStdoutAsSpaceSeparatedTokens=e.compareStdoutAsSpaceSeparatedTokens,exports.copyPackageManagerProjectFiles=l.copyPackageManagerProjectFiles,exports.encodeFileForTestCaseResult=n.encodeFileForTestCaseResult,exports.findDefaultStdioHarnessFiles=i.findDefaultStdioHarnessFiles,exports.isDefaultStdioHarnessSource=i.isDefaultStdioHarnessSource,exports.judgeAgainstExpectations=r.judgeAgainstExpectations,exports.languageIdToSourceCodeGrammar=d.languageIdToSourceCodeGrammar,exports.mergeComparisonOutputFiles=r.mergeComparisonOutputFiles,exports.normalizeCodeRule=m.normalizeCodeRule,exports.parseArgs=a.parseArgs,exports.parseCsv=o.parseCsv,exports.parseCsvRecords=o.parseCsvRecords,exports.printTestCaseResult=n.printTestCaseResult,exports.problemMarkdownFrontMatterSchema=m.problemMarkdownFrontMatterSchema,exports.readTestCases=s.readTestCases,exports.removeCommentsAndStringsInSourceCode=c.removeCommentsAndStringsInSourceCode,exports.removeCommentsInSourceCode=c.removeCommentsInSourceCode,exports.runCommandInTemporaryPackageManagerProject=l.runCommandInTemporaryPackageManagerProject,exports.runPythonInTemporaryUvProject=u.runPythonInTemporaryUvProject,exports.startHttpServer=f.startHttpServer,exports.testCaseResultSchema=t.testCaseResultSchema;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./helpers/compareStdoutAsSpaceSeparatedTokens.cjs"),t=require("./types/testCaseResult.cjs"),n=require("./helpers/printTestCaseResult.cjs"),r=require("./helpers/compareExpectedOutputFiles.cjs"),i=require("./helpers/defaultStdioHarness.cjs"),a=require("./helpers/parseArgs.cjs"),o=require("./helpers/parseCsv.cjs"),s=require("./helpers/readTestCases.cjs"),c=require("./helpers/removeCommentsInSourceCode.cjs"),l=require("./helpers/runCommandInTemporaryPackageManagerProject.cjs"),u=require("./helpers/runPythonInTemporaryUvProject.cjs"),d=require("./helpers/sourceCodeGrammars.cjs"),f=require("./helpers/startHttpServer.cjs"),p=require("./types/problem.cjs"),m=require("./learningMaterial/validationResult.cjs"),h=require("./learningMaterial/validateMaterial.cjs"),g=require("./learningMaterial/validateContest.cjs"),_=require("./learningMaterial/validateCourse.cjs"),v=require("./learningMaterial/validateProblem.cjs"),y=require("./types/decisionCode.cjs");exports.CsvParseError=o.CsvParseError,exports.DecisionCode=y.DecisionCode,exports.HARNESS_FILE_PRESETS=i.HARNESS_FILE_PRESETS,exports.MAX_COMPARED_FILE_BYTES=r.MAX_COMPARED_FILE_BYTES,exports.MAX_REPORTED_FILE_BYTES=r.MAX_REPORTED_FILE_BYTES,exports.TEST_CASE_RESULT_PREFIX=t.TEST_CASE_RESULT_PREFIX,exports.codeRuleSchema=p.codeRuleSchema,exports.compareExpectedOutputFiles=r.compareExpectedOutputFiles,exports.compareStdoutAsSpaceSeparatedTokens=e.compareStdoutAsSpaceSeparatedTokens,exports.copyPackageManagerProjectFiles=l.copyPackageManagerProjectFiles,exports.encodeFileForTestCaseResult=n.encodeFileForTestCaseResult,exports.findDefaultStdioHarnessFiles=i.findDefaultStdioHarnessFiles,exports.formatValidationReport=m.formatValidationReport,exports.formatZodIssues=m.formatZodIssues,exports.isDefaultStdioHarnessSource=i.isDefaultStdioHarnessSource,exports.judgeAgainstExpectations=r.judgeAgainstExpectations,exports.languageIdToSourceCodeGrammar=d.languageIdToSourceCodeGrammar,exports.mergeComparisonOutputFiles=r.mergeComparisonOutputFiles,exports.mergeSubmissionPeriods=h.mergeSubmissionPeriods,exports.normalizeCodeRule=p.normalizeCodeRule,exports.parseArgs=a.parseArgs,exports.parseCsv=o.parseCsv,exports.parseCsvRecords=o.parseCsvRecords,exports.printTestCaseResult=n.printTestCaseResult,exports.problemMarkdownFrontMatterSchema=p.problemMarkdownFrontMatterSchema,exports.readTestCases=s.readTestCases,exports.removeCommentsAndStringsInSourceCode=c.removeCommentsAndStringsInSourceCode,exports.removeCommentsInSourceCode=c.removeCommentsInSourceCode,exports.reportDuplicateIds=m.reportDuplicateIds,exports.reportExcessChatMarkers=h.reportExcessChatMarkers,exports.reportForeignProblemCourseIds=h.reportForeignProblemCourseIds,exports.runCommandInTemporaryPackageManagerProject=l.runCommandInTemporaryPackageManagerProject,exports.runPythonInTemporaryUvProject=u.runPythonInTemporaryUvProject,exports.startHttpServer=f.startHttpServer,exports.testCaseResultSchema=t.testCaseResultSchema,exports.validateContestFile=g.validateContestFile,exports.validateCourseDirectory=_.validateCourseDirectory,exports.validateMaterialFile=h.validateMaterialFile,exports.validateProblemDirectory=v.validateProblemDirectory,exports.validateSubmissionPeriods=h.validateSubmissionPeriods;
package/dist/index.d.ts CHANGED
@@ -10,6 +10,11 @@ export * from './helpers/runCommandInTemporaryPackageManagerProject.js';
10
10
  export * from './helpers/runPythonInTemporaryUvProject.js';
11
11
  export * from './helpers/sourceCodeGrammars.js';
12
12
  export * from './helpers/startHttpServer.js';
13
+ export * from './learningMaterial/validateContest.js';
14
+ export * from './learningMaterial/validateCourse.js';
15
+ export * from './learningMaterial/validateMaterial.js';
16
+ export * from './learningMaterial/validateProblem.js';
17
+ export * from './learningMaterial/validationResult.js';
13
18
  export * from './types/decisionCode.js';
14
19
  export * from './types/problem.js';
15
20
  export * from './types/testCaseResult.js';
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{compareStdoutAsSpaceSeparatedTokens as e}from"./helpers/compareStdoutAsSpaceSeparatedTokens.js";import{TEST_CASE_RESULT_PREFIX as t,testCaseResultSchema as n}from"./types/testCaseResult.js";import{encodeFileForTestCaseResult as r,printTestCaseResult as i}from"./helpers/printTestCaseResult.js";import{MAX_COMPARED_FILE_BYTES as a,MAX_REPORTED_FILE_BYTES as o,compareExpectedOutputFiles as s,judgeAgainstExpectations as c,mergeComparisonOutputFiles as l}from"./helpers/compareExpectedOutputFiles.js";import{HARNESS_FILE_PRESETS as u,findDefaultStdioHarnessFiles as d,isDefaultStdioHarnessSource as f}from"./helpers/defaultStdioHarness.js";import{parseArgs as p}from"./helpers/parseArgs.js";import{CsvParseError as m,parseCsv as h,parseCsvRecords as g}from"./helpers/parseCsv.js";import{readTestCases as _}from"./helpers/readTestCases.js";import{removeCommentsAndStringsInSourceCode as v,removeCommentsInSourceCode as y}from"./helpers/removeCommentsInSourceCode.js";import{copyPackageManagerProjectFiles as b,runCommandInTemporaryPackageManagerProject as x}from"./helpers/runCommandInTemporaryPackageManagerProject.js";import{runPythonInTemporaryUvProject as S}from"./helpers/runPythonInTemporaryUvProject.js";import{languageIdToSourceCodeGrammar as C}from"./helpers/sourceCodeGrammars.js";import{startHttpServer as w}from"./helpers/startHttpServer.js";import{DecisionCode as T}from"./types/decisionCode.js";import{codeRuleSchema as E,normalizeCodeRule as D,problemMarkdownFrontMatterSchema as O}from"./types/problem.js";export{m as CsvParseError,T as DecisionCode,u as HARNESS_FILE_PRESETS,a as MAX_COMPARED_FILE_BYTES,o as MAX_REPORTED_FILE_BYTES,t as TEST_CASE_RESULT_PREFIX,E as codeRuleSchema,s as compareExpectedOutputFiles,e as compareStdoutAsSpaceSeparatedTokens,b as copyPackageManagerProjectFiles,r as encodeFileForTestCaseResult,d as findDefaultStdioHarnessFiles,f as isDefaultStdioHarnessSource,c as judgeAgainstExpectations,C as languageIdToSourceCodeGrammar,l as mergeComparisonOutputFiles,D as normalizeCodeRule,p as parseArgs,h as parseCsv,g as parseCsvRecords,i as printTestCaseResult,O as problemMarkdownFrontMatterSchema,_ as readTestCases,v as removeCommentsAndStringsInSourceCode,y as removeCommentsInSourceCode,x as runCommandInTemporaryPackageManagerProject,S as runPythonInTemporaryUvProject,w as startHttpServer,n as testCaseResultSchema};
1
+ import{compareStdoutAsSpaceSeparatedTokens as e}from"./helpers/compareStdoutAsSpaceSeparatedTokens.js";import{TEST_CASE_RESULT_PREFIX as t,testCaseResultSchema as n}from"./types/testCaseResult.js";import{encodeFileForTestCaseResult as r,printTestCaseResult as i}from"./helpers/printTestCaseResult.js";import{MAX_COMPARED_FILE_BYTES as a,MAX_REPORTED_FILE_BYTES as o,compareExpectedOutputFiles as s,judgeAgainstExpectations as c,mergeComparisonOutputFiles as l}from"./helpers/compareExpectedOutputFiles.js";import{HARNESS_FILE_PRESETS as u,findDefaultStdioHarnessFiles as d,isDefaultStdioHarnessSource as f}from"./helpers/defaultStdioHarness.js";import{parseArgs as p}from"./helpers/parseArgs.js";import{CsvParseError as m,parseCsv as h,parseCsvRecords as g}from"./helpers/parseCsv.js";import{readTestCases as _}from"./helpers/readTestCases.js";import{removeCommentsAndStringsInSourceCode as v,removeCommentsInSourceCode as y}from"./helpers/removeCommentsInSourceCode.js";import{copyPackageManagerProjectFiles as b,runCommandInTemporaryPackageManagerProject as x}from"./helpers/runCommandInTemporaryPackageManagerProject.js";import{runPythonInTemporaryUvProject as S}from"./helpers/runPythonInTemporaryUvProject.js";import{languageIdToSourceCodeGrammar as C}from"./helpers/sourceCodeGrammars.js";import{startHttpServer as w}from"./helpers/startHttpServer.js";import{codeRuleSchema as T,normalizeCodeRule as E,problemMarkdownFrontMatterSchema as D}from"./types/problem.js";import{formatValidationReport as O,formatZodIssues as k,reportDuplicateIds as A}from"./learningMaterial/validationResult.js";import{mergeSubmissionPeriods as j,reportExcessChatMarkers as M,reportForeignProblemCourseIds as N,validateMaterialFile as P,validateSubmissionPeriods as F}from"./learningMaterial/validateMaterial.js";import{validateContestFile as I}from"./learningMaterial/validateContest.js";import{validateCourseDirectory as L}from"./learningMaterial/validateCourse.js";import{validateProblemDirectory as R}from"./learningMaterial/validateProblem.js";import{DecisionCode as z}from"./types/decisionCode.js";export{m as CsvParseError,z as DecisionCode,u as HARNESS_FILE_PRESETS,a as MAX_COMPARED_FILE_BYTES,o as MAX_REPORTED_FILE_BYTES,t as TEST_CASE_RESULT_PREFIX,T as codeRuleSchema,s as compareExpectedOutputFiles,e as compareStdoutAsSpaceSeparatedTokens,b as copyPackageManagerProjectFiles,r as encodeFileForTestCaseResult,d as findDefaultStdioHarnessFiles,O as formatValidationReport,k as formatZodIssues,f as isDefaultStdioHarnessSource,c as judgeAgainstExpectations,C as languageIdToSourceCodeGrammar,l as mergeComparisonOutputFiles,j as mergeSubmissionPeriods,E as normalizeCodeRule,p as parseArgs,h as parseCsv,g as parseCsvRecords,i as printTestCaseResult,D as problemMarkdownFrontMatterSchema,_ as readTestCases,v as removeCommentsAndStringsInSourceCode,y as removeCommentsInSourceCode,A as reportDuplicateIds,M as reportExcessChatMarkers,N as reportForeignProblemCourseIds,x as runCommandInTemporaryPackageManagerProject,S as runPythonInTemporaryUvProject,w as startHttpServer,n as testCaseResultSchema,I as validateContestFile,L as validateCourseDirectory,P as validateMaterialFile,R as validateProblemDirectory,F as validateSubmissionPeriods};
@@ -0,0 +1,2 @@
1
+ "use strict";let e=require("yaml");const t=/^\uFEFF?(= yaml =|---)\r?\n([\s\S]*?)^(?:\1|\.\.\.)[ \t]*\r?(?:\n|(?!.))/m,n=/^\uFEFF?(?:= yaml =|---)/;function r(r){let i=t.exec(r);if(!i||i.index!==0){if(n.test(r))throw Error(`frontmatter block not recognized; the opening --- (or = yaml =) must be followed immediately by the line end (no trailing whitespace) and the closing delimiter must be alone on its line`);return{attributes:{},body:r}}return{attributes:(0,e.parse)(i[2]??``)??{},body:r.slice(i[0].length)}}exports.parseFrontmatter=r;
2
+ //# sourceMappingURL=frontmatter.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frontmatter.cjs","names":["parseYaml"],"sources":["../../src/learningMaterial/frontmatter.ts"],"sourcesContent":["import { parse as parseYaml } from 'yaml';\n\n// The judge front-matter parser (a port of jxson/front-matter) requires the document to start with\n// `---` or `= yaml =` immediately followed by the line end (LF or CRLF, no trailing whitespace) and\n// end the block with the same delimiter or a `...` line; the closer must be alone on its line\n// (`(?!.)` below), or the judge treats the whole document as body with empty attributes.\nconst FRONTMATTER_REGEX = /^\\uFEFF?(= yaml =|---)\\r?\\n([\\s\\S]*?)^(?:\\1|\\.\\.\\.)[ \\t]*\\r?(?:\\n|(?!.))/m;\nconst FRONTMATTER_OPENER_REGEX = /^\\uFEFF?(?:= yaml =|---)/;\n\n/**\n * Splits a Markdown document into YAML frontmatter attributes and the remaining body.\n * Throws when the frontmatter block is present but contains invalid YAML.\n */\nexport function parseFrontmatter(text: string): { attributes: unknown; body: string } {\n const match = FRONTMATTER_REGEX.exec(text);\n if (!match || match.index !== 0) {\n // A document that starts with --- was clearly meant to carry frontmatter; failing here beats\n // letting the schema report \"name is missing\" for a file whose name: line is plainly visible.\n if (FRONTMATTER_OPENER_REGEX.test(text)) {\n throw new Error(\n 'frontmatter block not recognized; the opening --- (or = yaml =) must be followed immediately by the line end (no trailing whitespace) and the closing delimiter must be alone on its line'\n );\n }\n return { attributes: {}, body: text };\n }\n return { attributes: parseYaml(match[2] ?? '') ?? {}, body: text.slice(match[0].length) };\n}\n"],"mappings":"mCAMA,MAAM,EAAoB,4EACpB,EAA2B,2BAMjC,SAAgB,EAAiB,EAAqD,CACpF,IAAM,EAAQ,EAAkB,KAAK,CAAI,EACzC,GAAI,CAAC,GAAS,EAAM,QAAU,EAAG,CAG/B,GAAI,EAAyB,KAAK,CAAI,EACpC,MAAU,MACR,2LACF,EAEF,MAAO,CAAE,WAAY,CAAC,EAAG,KAAM,CAAK,CACtC,CACA,MAAO,CAAE,YAAA,EAAYA,EAAAA,MAAAA,CAAU,EAAM,IAAM,EAAE,GAAK,CAAC,EAAG,KAAM,EAAK,MAAM,EAAM,EAAE,CAAC,MAAM,CAAE,CAC1F"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Splits a Markdown document into YAML frontmatter attributes and the remaining body.
3
+ * Throws when the frontmatter block is present but contains invalid YAML.
4
+ */
5
+ export declare function parseFrontmatter(text: string): {
6
+ attributes: unknown;
7
+ body: string;
8
+ };
@@ -0,0 +1,2 @@
1
+ import{parse as e}from"yaml";const t=/^\uFEFF?(= yaml =|---)\r?\n([\s\S]*?)^(?:\1|\.\.\.)[ \t]*\r?(?:\n|(?!.))/m,n=/^\uFEFF?(?:= yaml =|---)/;function r(r){let i=t.exec(r);if(!i||i.index!==0){if(n.test(r))throw Error(`frontmatter block not recognized; the opening --- (or = yaml =) must be followed immediately by the line end (no trailing whitespace) and the closing delimiter must be alone on its line`);return{attributes:{},body:r}}return{attributes:e(i[2]??``)??{},body:r.slice(i[0].length)}}export{r as parseFrontmatter};
2
+ //# sourceMappingURL=frontmatter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frontmatter.js","names":["parseYaml"],"sources":["../../src/learningMaterial/frontmatter.ts"],"sourcesContent":["import { parse as parseYaml } from 'yaml';\n\n// The judge front-matter parser (a port of jxson/front-matter) requires the document to start with\n// `---` or `= yaml =` immediately followed by the line end (LF or CRLF, no trailing whitespace) and\n// end the block with the same delimiter or a `...` line; the closer must be alone on its line\n// (`(?!.)` below), or the judge treats the whole document as body with empty attributes.\nconst FRONTMATTER_REGEX = /^\\uFEFF?(= yaml =|---)\\r?\\n([\\s\\S]*?)^(?:\\1|\\.\\.\\.)[ \\t]*\\r?(?:\\n|(?!.))/m;\nconst FRONTMATTER_OPENER_REGEX = /^\\uFEFF?(?:= yaml =|---)/;\n\n/**\n * Splits a Markdown document into YAML frontmatter attributes and the remaining body.\n * Throws when the frontmatter block is present but contains invalid YAML.\n */\nexport function parseFrontmatter(text: string): { attributes: unknown; body: string } {\n const match = FRONTMATTER_REGEX.exec(text);\n if (!match || match.index !== 0) {\n // A document that starts with --- was clearly meant to carry frontmatter; failing here beats\n // letting the schema report \"name is missing\" for a file whose name: line is plainly visible.\n if (FRONTMATTER_OPENER_REGEX.test(text)) {\n throw new Error(\n 'frontmatter block not recognized; the opening --- (or = yaml =) must be followed immediately by the line end (no trailing whitespace) and the closing delimiter must be alone on its line'\n );\n }\n return { attributes: {}, body: text };\n }\n return { attributes: parseYaml(match[2] ?? '') ?? {}, body: text.slice(match[0].length) };\n}\n"],"mappings":"6BAMA,MAAM,EAAoB,4EACpB,EAA2B,2BAMjC,SAAgB,EAAiB,EAAqD,CACpF,IAAM,EAAQ,EAAkB,KAAK,CAAI,EACzC,GAAI,CAAC,GAAS,EAAM,QAAU,EAAG,CAG/B,GAAI,EAAyB,KAAK,CAAI,EACpC,MAAU,MACR,2LACF,EAEF,MAAO,CAAE,WAAY,CAAC,EAAG,KAAM,CAAK,CACtC,CACA,MAAO,CAAE,WAAYA,EAAU,EAAM,IAAM,EAAE,GAAK,CAAC,EAAG,KAAM,EAAK,MAAM,EAAM,EAAE,CAAC,MAAM,CAAE,CAC1F"}
@@ -0,0 +1,2 @@
1
+ "use strict";const e=require("./schemas.cjs");let t=require("node:path"),n=require("node:fs/promises");const r=new Set([`__pycache__`]),i=new Set([``,`.class`,`.exe`,`.jar`,`.out`,`.pyc`,`.png`,`.gif`]);async function a(e){try{return(await(0,n.stat)(e)).isFile()}catch{return!1}}async function o(e){try{return(await(0,n.lstat)(e)).isFile()}catch{return!1}}async function s(e){try{return(await(0,n.lstat)(e)).isDirectory()}catch{return!1}}async function c(e){try{return(await(0,n.stat)(e)).isDirectory()}catch{return!1}}async function l(e){let t=new Map;return await u(e,e,t),t}async function u(e,r,i){let a=await(0,n.readdir)(r,{withFileTypes:!0});if(!(r!==e&&a.some(e=>e.isFile()&&(e.name===`course.yaml`||e.name===`course.yml`))))for(let n of a)if(n.isFile()){let a=n.name===`problem.md`?(0,t.basename)(r):n.name.endsWith(`.problem.md`)?n.name.slice(0,-11).toLowerCase():void 0;if(a===void 0)continue;let o=i.get(a)??[];o.push((0,t.relative)(e,(0,t.join)(r,n.name))),i.set(a,o)}else n.isDirectory()&&!d(r,n.name)&&await u(e,(0,t.join)(r,n.name),i)}function d(e,n){return n.startsWith(`.`)||n===`node_modules`||n===`target`||n===`temp`&&(0,t.basename)(e)===`judge-environments`}function f(t,n){for(let[r,i]of t)e.LEARNING_MATERIAL_ID_REGEX.test(r)||n.push(`problem ID "${r}" (${i.join(`, `)}) must match ${e.LEARNING_MATERIAL_ID_REGEX}; the judge aborts discovery on it`),i.length>1&&n.push(`problem ID "${r}" is defined more than once (${i.join(`, `)}); the judge rejects the conflict`)}async function p(e,t){return await s(e)?!0:(t.push(await c(e)?`problems directory is a symbolic link, which the judge does not traverse: ${e}`:`problems directory not found: ${e}`),!1)}async function m(e,t){if(!await p(e,t))return;let n=await l(e);return f(n,t),new Set(n.keys())}function h(e,t,n,r){let i=n===void 0?``:` under ${n}`;for(let n of new Set(e))t.has(n)||r.push(`problem "${n}" is referenced but does not exist${i}`)}async function g(e){let t=[];return await _(e,e,t),t.toSorted((e,t)=>e.path.localeCompare(t.path))}async function _(e,a,o){for(let s of await(0,n.readdir)(a,{withFileTypes:!0})){let c=(0,t.join)(a,s.name);s.isDirectory()?r.has(s.name)||await _(e,c,o):s.isFile()&&!i.has((0,t.extname)(s.name).toLowerCase())&&o.push({path:(0,t.relative)(e,c),data:await(0,n.readFile)(c,`utf8`)})}}exports.collectProblemDefinitions=l,exports.collectProblemIdsFromDirectory=m,exports.isDirectory=c,exports.isFile=a,exports.isRegularDirectory=s,exports.isRegularFile=o,exports.isUsableProblemsDirectory=p,exports.readSourceFilesRecursively=g,exports.reportDanglingProblemReferences=h,exports.reportProblemDefinitionIssues=f;
2
+ //# sourceMappingURL=fsHelpers.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fsHelpers.cjs","names":["stat","lstat","readdir","basename","relative","join","LEARNING_MATERIAL_ID_REGEX","extname","readFile"],"sources":["../../src/learningMaterial/fsHelpers.ts"],"sourcesContent":["import { lstat, readdir, readFile, stat } from 'node:fs/promises';\nimport { basename, extname, join, relative } from 'node:path';\n\nimport { LEARNING_MATERIAL_ID_REGEX } from './schemas.js';\n\n// Files the judge importer skips when reading problem directories. The extension filter is ported\n// from the judge's readSourceCodeFilesInDirectory so compile artifacts (e.g. Main.class next to\n// Main.java) are excluded from pattern checks exactly like the judge excludes them at import time;\n// the '' entry also drops dot files such as `.DS_Store`, whose extension is ''.\nconst IGNORED_DIRECTORY_NAMES: ReadonlySet<string> = new Set(['__pycache__']);\nconst NON_CODE_EXTENSIONS: ReadonlySet<string> = new Set([\n '',\n '.class',\n '.exe',\n '.jar',\n '.out',\n '.pyc',\n '.png',\n '.gif',\n]);\n\nexport interface SourceFile {\n path: string;\n data: string;\n}\n\nexport async function isFile(path: string): Promise<boolean> {\n try {\n const stats = await stat(path);\n return stats.isFile();\n } catch {\n return false;\n }\n}\n\n/** Whether the path is a regular file (not a symlink), as the judge's problem discovery requires. */\nexport async function isRegularFile(path: string): Promise<boolean> {\n try {\n const stats = await lstat(path);\n return stats.isFile();\n } catch {\n return false;\n }\n}\n\n/** Whether the path is a directory that is not a symbolic link, which the judge's discovery does not traverse. */\nexport async function isRegularDirectory(path: string): Promise<boolean> {\n try {\n const stats = await lstat(path);\n return stats.isDirectory();\n } catch {\n return false;\n }\n}\n\nexport async function isDirectory(path: string): Promise<boolean> {\n try {\n const stats = await stat(path);\n return stats.isDirectory();\n } catch {\n return false;\n }\n}\n\n/**\n * Collects the problem definitions under a directory the way the judge discovers them: `problem.md`\n * names its directory and `<id>.problem.md` names itself, at any depth, without traversing symbolic\n * links or a nested course (whose problems belong to that nearer course). The result maps each\n * problem ID to the paths of its definitions (relative to the root); the judge rejects an ID\n * defined more than once.\n */\nexport async function collectProblemDefinitions(rootDirectoryPath: string): Promise<Map<string, string[]>> {\n const definitionPathsById = new Map<string, string[]>();\n await collectProblemDefinitionsInto(rootDirectoryPath, rootDirectoryPath, definitionPathsById);\n return definitionPathsById;\n}\n\nasync function collectProblemDefinitionsInto(\n rootDirectoryPath: string,\n directoryPath: string,\n definitionPathsById: Map<string, string[]>\n): Promise<void> {\n const dirents = await readdir(directoryPath, { withFileTypes: true });\n if (\n directoryPath !== rootDirectoryPath &&\n dirents.some((dirent) => dirent.isFile() && (dirent.name === 'course.yaml' || dirent.name === 'course.yml'))\n ) {\n return;\n }\n for (const dirent of dirents) {\n if (dirent.isFile()) {\n const problemId =\n dirent.name === 'problem.md'\n ? basename(directoryPath)\n : dirent.name.endsWith('.problem.md')\n ? // The judge lowercases a v1 file name before using it as the ID (a directory name is used as is).\n dirent.name.slice(0, -'.problem.md'.length).toLowerCase()\n : undefined;\n if (problemId === undefined) continue;\n const definitionPaths = definitionPathsById.get(problemId) ?? [];\n definitionPaths.push(relative(rootDirectoryPath, join(directoryPath, dirent.name)));\n definitionPathsById.set(problemId, definitionPaths);\n } else if (dirent.isDirectory() && !isExcludedFromDiscovery(directoryPath, dirent.name)) {\n await collectProblemDefinitionsInto(rootDirectoryPath, join(directoryPath, dirent.name), definitionPathsById);\n }\n }\n}\n\n// The judge's discovery glob skips dot directories and these excludes.\nfunction isExcludedFromDiscovery(parentDirectoryPath: string, directoryName: string): boolean {\n return (\n directoryName.startsWith('.') ||\n directoryName === 'node_modules' ||\n directoryName === 'target' ||\n (directoryName === 'temp' && basename(parentDirectoryPath) === 'judge-environments')\n );\n}\n\n/**\n * Reports discovered problem definitions the judge refuses: an ID that does not match the ID pattern\n * aborts discovery of the whole repository, and an ID defined more than once is a conflict.\n */\nexport function reportProblemDefinitionIssues(\n definitionPathsById: ReadonlyMap<string, string[]>,\n errors: string[]\n): void {\n for (const [problemId, definitionPaths] of definitionPathsById) {\n if (!LEARNING_MATERIAL_ID_REGEX.test(problemId)) {\n errors.push(\n `problem ID \"${problemId}\" (${definitionPaths.join(', ')}) must match ${LEARNING_MATERIAL_ID_REGEX}; the judge aborts discovery on it`\n );\n }\n if (definitionPaths.length > 1) {\n errors.push(\n `problem ID \"${problemId}\" is defined more than once (${definitionPaths.join(', ')}); the judge rejects the conflict`\n );\n }\n }\n}\n\n/** Reports a problems directory the judge cannot search (missing, or a symbolic link); returns whether it is usable. */\nexport async function isUsableProblemsDirectory(problemsDirectoryPath: string, errors: string[]): Promise<boolean> {\n if (await isRegularDirectory(problemsDirectoryPath)) return true;\n errors.push(\n (await isDirectory(problemsDirectoryPath))\n ? `problems directory is a symbolic link, which the judge does not traverse: ${problemsDirectoryPath}`\n : `problems directory not found: ${problemsDirectoryPath}`\n );\n return false;\n}\n\n/** Collects the problem IDs a directory defines, reporting the issues the judge refuses; undefined when it cannot be searched. */\nexport async function collectProblemIdsFromDirectory(\n problemsDirectoryPath: string,\n errors: string[]\n): Promise<Set<string> | undefined> {\n if (!(await isUsableProblemsDirectory(problemsDirectoryPath, errors))) return undefined;\n const definitionPathsById = await collectProblemDefinitions(problemsDirectoryPath);\n reportProblemDefinitionIssues(definitionPathsById, errors);\n return new Set(definitionPathsById.keys());\n}\n\nexport function reportDanglingProblemReferences(\n problemIds: Iterable<string>,\n availableProblemIds: ReadonlySet<string>,\n problemsDirectoryPath: string | undefined,\n errors: string[]\n): void {\n const location = problemsDirectoryPath === undefined ? '' : ` under ${problemsDirectoryPath}`;\n for (const problemId of new Set(problemIds)) {\n if (!availableProblemIds.has(problemId)) {\n errors.push(`problem \"${problemId}\" is referenced but does not exist${location}`);\n }\n }\n}\n\n/** Reads all text files under a directory, with paths relative to the given root. */\nexport async function readSourceFilesRecursively(rootDirectoryPath: string): Promise<SourceFile[]> {\n const files: SourceFile[] = [];\n await collectSourceFiles(rootDirectoryPath, rootDirectoryPath, files);\n return files.toSorted((f1, f2) => f1.path.localeCompare(f2.path));\n}\n\nasync function collectSourceFiles(\n rootDirectoryPath: string,\n directoryPath: string,\n files: SourceFile[]\n): Promise<void> {\n for (const dirent of await readdir(directoryPath, { withFileTypes: true })) {\n const entryPath = join(directoryPath, dirent.name);\n if (dirent.isDirectory()) {\n if (!IGNORED_DIRECTORY_NAMES.has(dirent.name)) {\n await collectSourceFiles(rootDirectoryPath, entryPath, files);\n }\n } else if (dirent.isFile() && !NON_CODE_EXTENSIONS.has(extname(dirent.name).toLowerCase())) {\n files.push({ path: relative(rootDirectoryPath, entryPath), data: await readFile(entryPath, 'utf8') });\n }\n }\n}\n"],"mappings":"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"}