@exercode/problem-utils 2.4.3 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -0
- package/dist/cli/checkAllProblems.cjs +1 -1
- package/dist/cli/checkAllProblems.cjs.map +1 -1
- package/dist/cli/checkAllProblems.js +1 -1
- package/dist/cli/checkAllProblems.js.map +1 -1
- package/dist/cli/exercodeProblem.cjs +8 -2
- package/dist/cli/exercodeProblem.cjs.map +1 -1
- package/dist/cli/exercodeProblem.js +8 -2
- package/dist/cli/exercodeProblem.js.map +1 -1
- package/dist/cli/validateLearningMaterial.cjs +2 -0
- package/dist/cli/validateLearningMaterial.cjs.map +1 -0
- package/dist/cli/validateLearningMaterial.d.ts +7 -0
- package/dist/cli/validateLearningMaterial.js +2 -0
- package/dist/cli/validateLearningMaterial.js.map +1 -0
- package/dist/helpers/checkProblemDirIsolation.cjs +1 -1
- package/dist/helpers/checkProblemDirIsolation.js +1 -1
- package/dist/helpers/cleanWorkingDirectory.cjs +1 -1
- package/dist/helpers/cleanWorkingDirectory.cjs.map +1 -1
- package/dist/helpers/cleanWorkingDirectory.d.ts +2 -6
- package/dist/helpers/cleanWorkingDirectory.js +1 -1
- package/dist/helpers/cleanWorkingDirectory.js.map +1 -1
- package/dist/helpers/compareExpectedOutputFiles.cjs +1 -1
- package/dist/helpers/compareExpectedOutputFiles.cjs.map +1 -1
- package/dist/helpers/compareExpectedOutputFiles.js +1 -1
- package/dist/helpers/compareExpectedOutputFiles.js.map +1 -1
- package/dist/helpers/copyTestCaseFileInput.cjs +1 -1
- package/dist/helpers/copyTestCaseFileInput.cjs.map +1 -1
- package/dist/helpers/copyTestCaseFileInput.js +1 -1
- package/dist/helpers/copyTestCaseFileInput.js.map +1 -1
- package/dist/helpers/judgeByStaticAnalysis.cjs +3 -3
- package/dist/helpers/judgeByStaticAnalysis.js +3 -3
- package/dist/helpers/readOutputFiles.cjs +1 -1
- package/dist/helpers/readOutputFiles.cjs.map +1 -1
- package/dist/helpers/readOutputFiles.d.ts +0 -9
- package/dist/helpers/readOutputFiles.js +1 -1
- package/dist/helpers/readOutputFiles.js.map +1 -1
- package/dist/helpers/runCommandInTemporaryPackageManagerProject.cjs +1 -1
- package/dist/helpers/runCommandInTemporaryPackageManagerProject.cjs.map +1 -1
- package/dist/helpers/runCommandInTemporaryPackageManagerProject.d.ts +0 -3
- package/dist/helpers/runCommandInTemporaryPackageManagerProject.js +1 -1
- package/dist/helpers/runCommandInTemporaryPackageManagerProject.js.map +1 -1
- package/dist/helpers/spawnSyncWithTimeout.cjs +1 -1
- package/dist/helpers/spawnSyncWithTimeout.cjs.map +1 -1
- package/dist/helpers/spawnSyncWithTimeout.d.ts +0 -7
- package/dist/helpers/spawnSyncWithTimeout.js +1 -1
- package/dist/helpers/spawnSyncWithTimeout.js.map +1 -1
- package/dist/helpers/startHttpServer.cjs +1 -1
- package/dist/helpers/startHttpServer.cjs.map +1 -1
- package/dist/helpers/startHttpServer.js +1 -1
- package/dist/helpers/startHttpServer.js.map +1 -1
- package/dist/helpers/stdioJudgeRules.cjs +2 -0
- package/dist/helpers/stdioJudgeRules.cjs.map +1 -0
- package/dist/helpers/stdioJudgeRules.d.ts +4 -0
- package/dist/helpers/stdioJudgeRules.js +2 -0
- package/dist/helpers/stdioJudgeRules.js.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js +1 -1
- package/dist/learningMaterial/frontmatter.cjs +2 -0
- package/dist/learningMaterial/frontmatter.cjs.map +1 -0
- package/dist/learningMaterial/frontmatter.d.ts +8 -0
- package/dist/learningMaterial/frontmatter.js +2 -0
- package/dist/learningMaterial/frontmatter.js.map +1 -0
- package/dist/learningMaterial/fsHelpers.cjs +2 -0
- package/dist/learningMaterial/fsHelpers.cjs.map +1 -0
- package/dist/learningMaterial/fsHelpers.d.ts +30 -0
- package/dist/learningMaterial/fsHelpers.js +2 -0
- package/dist/learningMaterial/fsHelpers.js.map +1 -0
- package/dist/learningMaterial/schemas.cjs +2 -0
- package/dist/learningMaterial/schemas.cjs.map +1 -0
- package/dist/learningMaterial/schemas.d.ts +241 -0
- package/dist/learningMaterial/schemas.js +2 -0
- package/dist/learningMaterial/schemas.js.map +1 -0
- package/dist/learningMaterial/validateContest.cjs +2 -0
- package/dist/learningMaterial/validateContest.cjs.map +1 -0
- package/dist/learningMaterial/validateContest.d.ts +16 -0
- package/dist/learningMaterial/validateContest.js +2 -0
- package/dist/learningMaterial/validateContest.js.map +1 -0
- package/dist/learningMaterial/validateCourse.cjs +2 -0
- package/dist/learningMaterial/validateCourse.cjs.map +1 -0
- package/dist/learningMaterial/validateCourse.d.ts +11 -0
- package/dist/learningMaterial/validateCourse.js +2 -0
- package/dist/learningMaterial/validateCourse.js.map +1 -0
- package/dist/learningMaterial/validateMaterial.cjs +4 -0
- package/dist/learningMaterial/validateMaterial.cjs.map +1 -0
- package/dist/learningMaterial/validateMaterial.d.ts +34 -0
- package/dist/learningMaterial/validateMaterial.js +4 -0
- package/dist/learningMaterial/validateMaterial.js.map +1 -0
- package/dist/learningMaterial/validateProblem.cjs +2 -0
- package/dist/learningMaterial/validateProblem.cjs.map +1 -0
- package/dist/learningMaterial/validateProblem.d.ts +6 -0
- package/dist/learningMaterial/validateProblem.js +2 -0
- package/dist/learningMaterial/validateProblem.js.map +1 -0
- package/dist/learningMaterial/validationResult.cjs +3 -0
- package/dist/learningMaterial/validationResult.cjs.map +1 -0
- package/dist/learningMaterial/validationResult.d.ts +9 -0
- package/dist/learningMaterial/validationResult.js +3 -0
- package/dist/learningMaterial/validationResult.js.map +1 -0
- package/dist/presets/command.cjs +1 -1
- package/dist/presets/command.cjs.map +1 -1
- package/dist/presets/command.d.ts +0 -16
- package/dist/presets/command.js +1 -1
- package/dist/presets/command.js.map +1 -1
- package/dist/presets/evaluation.cjs +4 -4
- package/dist/presets/evaluation.cjs.map +1 -1
- package/dist/presets/evaluation.js +4 -4
- package/dist/presets/evaluation.js.map +1 -1
- package/dist/presets/guiCommand.cjs +3 -3
- package/dist/presets/guiCommand.cjs.map +1 -1
- package/dist/presets/guiCommand.d.ts +0 -24
- package/dist/presets/guiCommand.js +3 -3
- package/dist/presets/guiCommand.js.map +1 -1
- package/dist/presets/stdio.cjs +1 -1
- package/dist/presets/stdio.cjs.map +1 -1
- package/dist/presets/stdio.js +1 -1
- package/dist/presets/stdio.js.map +1 -1
- package/dist/types/language.cjs +2 -2
- package/dist/types/language.cjs.map +1 -1
- package/dist/types/language.js +2 -2
- package/dist/types/language.js.map +1 -1
- package/package.json +3 -2
- package/dist/helpers/runCustomRunner.cjs +0 -2
- package/dist/helpers/runCustomRunner.cjs.map +0 -1
- package/dist/helpers/runCustomRunner.d.ts +0 -8
- package/dist/helpers/runCustomRunner.js +0 -2
- package/dist/helpers/runCustomRunner.js.map +0 -1
- package/dist/helpers/safeFs.cjs +0 -2
- package/dist/helpers/safeFs.cjs.map +0 -1
- package/dist/helpers/safeFs.d.ts +0 -29
- package/dist/helpers/safeFs.js +0 -2
- package/dist/helpers/safeFs.js.map +0 -1
- package/dist/helpers/sandboxUser.cjs +0 -3
- package/dist/helpers/sandboxUser.cjs.map +0 -1
- package/dist/helpers/sandboxUser.d.ts +0 -146
- package/dist/helpers/sandboxUser.js +0 -3
- package/dist/helpers/sandboxUser.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runCommandInTemporaryPackageManagerProject.js","names":["fs","childProcess","nodeFs"],"sources":["../../src/helpers/runCommandInTemporaryPackageManagerProject.ts"],"sourcesContent":["import childProcess from 'node:child_process';\nimport nodeFs from 'node:fs';\nimport fs from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport {\n forceRemove,\n getSandboxUserEnvOverrides,\n killSandboxUserProcesses,\n makeAccessibleToSandboxUser,\n prependInsideSandboxWrapper,\n SANDBOX_WATCHDOG_GRACE_SECONDS,\n sandboxUserName,\n startSandboxTimeoutWatchdog,\n wrapCommandWithSandboxUser,\n} from './sandboxUser.js';\nimport { copyWithoutFollowingSymlinks, createDirectoryWithoutFollowingSymlinks } from './safeFs.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;\n// Added to the watchdog's own deadline so its kill has time to land and `close` to arrive.\nconst watchdogSettleMarginMilliseconds = 2000;\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 *\n * Under `EXERCODE_SANDBOX_USER` delegation, cleanup kills every process of the sandbox user, so do\n * not run multiple invocations concurrently in that mode — one finishing would kill the others.\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 // The sandbox user (if any) runs the install/run commands below and must read the copied\n // sources and create outputs (e.g. `node_modules`, `.venv`) next to them.\n makeAccessibleToSandboxUser(runDir);\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 try {\n // Daemonized children of the sandboxed command would otherwise outlive the run.\n if (sandboxUserName) killSandboxUserProcesses();\n } finally {\n // Must run even when the sweep fails closed, or the temporary submission copy leaks.\n await forceRemove(runDir);\n }\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(\n path.join(options.projectDir, projectFilePath),\n path.join(options.runDir, projectFilePath),\n options.runDir\n );\n }\n}\n\nasync function copyPathIfExists(sourcePath: string, destinationPath: string, runDir: string): Promise<void> {\n try {\n // Before touching the destination: creating the parent levels replaces whatever the submission\n // put there, which must not happen for a project file the problem does not even ship.\n await fs.lstat(sourcePath);\n // No-follow copy: the destination was seeded from the (sandbox-writable) submission tree, so a\n // planted symlink there — at the destination itself or at any directory level of a nested\n // project file path — must not redirect this trusted project-file overlay outside runDir.\n await createDirectoryWithoutFollowingSymlinks(runDir, path.dirname(destinationPath));\n await copyWithoutFollowingSymlinks(sourcePath, destinationPath);\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 // `wrapCommandWithSandboxUser` is idempotent, so a command the presets already wrapped is not\n // nested; `time` is then spliced INSIDE that wrapper so it runs as the sandbox user, never as the\n // harness user writing `--output` into this sandbox-writable directory.\n const wrappedCommand = wrapCommandWithSandboxUser(command);\n const spawnedCommand =\n timeCommand === undefined\n ? wrappedCommand\n : prependInsideSandboxWrapper(wrappedCommand, [...timeCommand, `--output=${timeOutputPath}`]);\n // Started BEFORE the submission: `killSubprocessGroup` cannot spawn its `sudo` once a submission\n // has exhausted the PID cgroup, and a submission spawned first could exhaust it before this\n // watchdog exists. It is the deadline of last resort on this path.\n const watchdog = startSandboxTimeoutWatchdog(context.timeLimitSeconds);\n const watchdogDeadlineAt = Date.now() + (Math.ceil(context.timeLimitSeconds) + SANDBOX_WATCHDOG_GRACE_SECONDS) * 1000;\n let subprocess: childProcess.ChildProcessWithoutNullStreams;\n try {\n subprocess = childProcess.spawn(spawnedCommand[0], spawnedCommand.slice(1), {\n cwd: context.cwd,\n detached: process.platform !== 'win32',\n env: { ...context.env, ...getSandboxUserEnvOverrides(context.env) },\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n } catch (error) {\n watchdog.cancel();\n throw error;\n }\n\n const stdoutChunks: Buffer[] = [];\n const stderrChunks: Buffer[] = [];\n let outputBytes = 0;\n let timedOut = false;\n let outputLimitExceeded = false;\n // Set when terminating the submission failed (the cleanup `sudo` could not be spawned). The run\n // then waits for the watchdog to end the submission rather than throwing out of an EventEmitter\n // callback, which would be an uncaught exception that kills the harness before any cleanup.\n let killError: Error | undefined;\n let onKillFailure: (() => void) | undefined;\n const killOrRecordFailure = (signal: NodeJS.Signals): void => {\n try {\n killSubprocessGroup(subprocess, signal);\n } catch (error) {\n killError ??= error instanceof Error ? error : new Error(String(error));\n onKillFailure?.();\n }\n };\n\n const appendOutputChunk = (chunks: Buffer[], chunk: Buffer): void => {\n if (outputBytes >= context.outputLimitBytes) {\n if (chunk.byteLength > 0) {\n outputLimitExceeded = true;\n killOrRecordFailure('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 killOrRecordFailure('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 killOrRecordFailure('SIGTERM');\n }, context.timeLimitSeconds * 1000);\n const killTimeout = setTimeout(\n () => {\n if (timedOut) killOrRecordFailure('SIGKILL');\n },\n context.timeLimitSeconds * 1000 + killGracePeriodMilliseconds\n );\n killTimeout.unref();\n // Bounds the wait after a kill failure: the watchdog force-kills the sandbox user at its own\n // deadline, so `close` should arrive; if even that fails, give up rather than hang.\n let killFailureTimeout: ReturnType<typeof setTimeout> | undefined;\n let closeObserved = false;\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 killOrRecordFailure('SIGKILL');\n if (subprocess.pid === undefined) {\n settled = true;\n reject(error);\n }\n };\n // Terminating the submission failed. Keep waiting: the watchdog (started before the\n // submission) force-kills the sandbox user at its own deadline, so `close` still arrives and\n // the run reports its real verdict. Give up only once that deadline has passed without the\n // submission ending, so the run can neither hang nor abandon a still-running submission\n // while its watchdog could still fire.\n onKillFailure = () => {\n killFailureTimeout ??= setTimeout(\n () => {\n if (settled) return;\n settled = true;\n reject(killError ?? new Error('failed to terminate the submission'));\n },\n Math.max(0, watchdogDeadlineAt - Date.now()) + watchdogSettleMarginMilliseconds\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 // The submission ended, so the watchdog has nothing left to kill and must not outlive this\n // run. A kill that failed once but succeeded on retry (or was superseded by the watchdog)\n // is not an error: the timeout/output-limit verdict below is the correct one to report.\n closeObserved = 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 if (killFailureTimeout) clearTimeout(killFailureTimeout);\n // Cancel unless the submission is still running after a failed kill: the watchdog is then the\n // only remaining mechanism able to stop it, and cancelling would leave it running unchecked.\n if (closeObserved || !killError) watchdog.cancel();\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 // The current user cannot signal the root-owned sudo wrapper nor the sandbox user's processes,\n // so go through sudo with the requested signal only; the callers' existing timers provide the\n // TERM → grace → KILL escalation.\n // Throws when the cleanup `sudo` cannot even be spawned (a submission can exhaust the PID\n // cgroup). Only `killOrRecordFailure` may call this: it records the failure and lets the\n // watchdog (or, past its deadline, a bounded timer) settle the run instead of letting the throw\n // escape an EventEmitter callback.\n if (sandboxUserName) {\n killSandboxUserProcesses([signal === 'SIGKILL' ? 'KILL' : 'TERM']);\n return;\n }\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 // The submission owns this directory and can put anything at this path, so decide what was\n // opened from the handle itself rather than from a prior `lstat` it could race:\n // - `O_NOFOLLOW` refuses a symlink to a harness-readable file (e.g. the problem's expected\n // outputs), which would otherwise report that file's trailing numbers as the submission's own\n // time and memory usage;\n // - `O_NONBLOCK` keeps a planted FIFO from blocking the harness forever waiting for a writer;\n // - the `fstat` then rejects anything that is not a regular file.\n //\n // Only an absent file means \"no measurement\" (the command was killed before `time` wrote one).\n // Anything else at this path is the submission tampering with its own accounting — reporting zero\n // memory would let it slip past a memory limit — so that fails the run instead.\n let content: string;\n let fileHandle: Awaited<ReturnType<typeof fs.open>> | undefined;\n try {\n fileHandle = await fs.open(\n timeOutputPath,\n nodeFs.constants.O_RDONLY | nodeFs.constants.O_NOFOLLOW | nodeFs.constants.O_NONBLOCK\n );\n const stats = await fileHandle.stat();\n if (!stats.isFile()) throw new Error(`${timeOutputPath} is not a regular file`);\n content = await fileHandle.readFile('utf8');\n } catch (error) {\n if (isErrorWithCode(error, 'ENOENT')) return { timeSeconds: 0, memoryBytes: 0 };\n throw new Error(`failed to read the time measurement at ${timeOutputPath}`, { cause: error });\n } finally {\n await fileHandle?.close();\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":["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,2 +1,2 @@
|
|
|
1
|
-
"use strict";const e=require("../_virtual/_rolldown/runtime.cjs")
|
|
1
|
+
"use strict";const e=require("../_virtual/_rolldown/runtime.cjs");let t=require("node:child_process");t=e.__toESM(t,1);let n=require("node:os");n=e.__toESM(n,1);const r=[n.default.platform()===`darwin`?`gtime`:`/usr/bin/time`,`--format`,`%e %M`];function i(e,n,i,a){let o=Date.now(),s=t.default.spawnSync(`timeout`,[a.toFixed(3),...r,e,...n],i),c=Date.now(),l=/(?:^|\n)(\d+\.\d+) (\d+)\s*$/.exec(s.stderr),u=l?s.stderr.slice(0,l.index):s.stderr,d=Number(l?.[1])||(c-o)/1e3,f=Number(l?.[2])*1024||0;return s.status===124?{...s,status:0,stderr:u,timeSeconds:a+.001,memoryBytes:f}:{...s,stderr:u,timeSeconds:d,memoryBytes:f}}exports.spawnSyncWithTimeout=i;
|
|
2
2
|
//# sourceMappingURL=spawnSyncWithTimeout.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spawnSyncWithTimeout.cjs","names":["os","
|
|
1
|
+
{"version":3,"file":"spawnSyncWithTimeout.cjs","names":["os","child_process"],"sources":["../../src/helpers/spawnSyncWithTimeout.ts"],"sourcesContent":["import child_process from 'node:child_process';\nimport os from 'node:os';\n\nconst TIME_COMMAND = [os.platform() === 'darwin' ? 'gtime' : '/usr/bin/time', '--format', '%e %M'] as const;\n\nexport function spawnSyncWithTimeout(\n command: string,\n args: readonly string[],\n options: child_process.SpawnSyncOptionsWithStringEncoding,\n timeoutSeconds: number\n): child_process.SpawnSyncReturns<string> & { timeSeconds: number; memoryBytes: number } {\n const startTimeMilliseconds = Date.now();\n\n const spawnResult = child_process.spawnSync(\n 'timeout',\n [timeoutSeconds.toFixed(3), ...TIME_COMMAND, command, ...args],\n options\n );\n\n const stopTimeMilliseconds = Date.now();\n\n const match = /(?:^|\\n)(\\d+\\.\\d+) (\\d+)\\s*$/.exec(spawnResult.stderr);\n const stderr = match ? spawnResult.stderr.slice(0, match.index) : spawnResult.stderr;\n const timeSeconds = Number(match?.[1]) || (stopTimeMilliseconds - startTimeMilliseconds) / 1000;\n const memoryBytes = Number(match?.[2]) * 1024 || 0;\n\n // timeout\n if (spawnResult.status === 124) {\n return { ...spawnResult, status: 0, stderr, timeSeconds: timeoutSeconds + 1e-3, memoryBytes };\n }\n\n return { ...spawnResult, stderr, timeSeconds, memoryBytes };\n}\n"],"mappings":"iKAGA,MAAM,EAAe,CAACA,EAAAA,QAAG,SAAS,IAAM,SAAW,QAAU,gBAAiB,WAAY,OAAO,EAEjG,SAAgB,EACd,EACA,EACA,EACA,EACuF,CACvF,IAAM,EAAwB,KAAK,IAAI,EAEjC,EAAcC,EAAAA,QAAc,UAChC,UACA,CAAC,EAAe,QAAQ,CAAC,EAAG,GAAG,EAAc,EAAS,GAAG,CAAI,EAC7D,CACF,EAEM,EAAuB,KAAK,IAAI,EAEhC,EAAQ,+BAA+B,KAAK,EAAY,MAAM,EAC9D,EAAS,EAAQ,EAAY,OAAO,MAAM,EAAG,EAAM,KAAK,EAAI,EAAY,OACxE,EAAc,OAAO,IAAQ,EAAE,IAAM,EAAuB,GAAyB,IACrF,EAAc,OAAO,IAAQ,EAAE,EAAI,MAAQ,EAOjD,OAJI,EAAY,SAAW,IAClB,CAAE,GAAG,EAAa,OAAQ,EAAG,SAAQ,YAAa,EAAiB,KAAM,aAAY,EAGvF,CAAE,GAAG,EAAa,SAAQ,cAAa,aAAY,CAC5D"}
|
|
@@ -1,11 +1,4 @@
|
|
|
1
1
|
import child_process from 'node:child_process';
|
|
2
|
-
/**
|
|
3
|
-
* Run an untrusted (submission-derived) command with a timeout. When a judge server delegates
|
|
4
|
-
* untrusted execution via `EXERCODE_SANDBOX_USER` (see `sandboxUser.ts`), the whole command,
|
|
5
|
-
* including `timeout`, runs as the sandbox user so the timer can signal the sandboxed process; a
|
|
6
|
-
* harness-owned watchdog enforces the deadline even if the submission kills its own `timeout`, and
|
|
7
|
-
* leftover sandbox processes (e.g. daemonized children) are killed after every run.
|
|
8
|
-
*/
|
|
9
2
|
export declare function spawnSyncWithTimeout(command: string, args: readonly string[], options: child_process.SpawnSyncOptionsWithStringEncoding, timeoutSeconds: number): child_process.SpawnSyncReturns<string> & {
|
|
10
3
|
timeSeconds: number;
|
|
11
4
|
memoryBytes: number;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import
|
|
1
|
+
import e from"node:child_process";import t from"node:os";const n=[t.platform()===`darwin`?`gtime`:`/usr/bin/time`,`--format`,`%e %M`];function r(t,r,i,a){let o=Date.now(),s=e.spawnSync(`timeout`,[a.toFixed(3),...n,t,...r],i),c=Date.now(),l=/(?:^|\n)(\d+\.\d+) (\d+)\s*$/.exec(s.stderr),u=l?s.stderr.slice(0,l.index):s.stderr,d=Number(l?.[1])||(c-o)/1e3,f=Number(l?.[2])*1024||0;return s.status===124?{...s,status:0,stderr:u,timeSeconds:a+.001,memoryBytes:f}:{...s,stderr:u,timeSeconds:d,memoryBytes:f}}export{r as spawnSyncWithTimeout};
|
|
2
2
|
//# sourceMappingURL=spawnSyncWithTimeout.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spawnSyncWithTimeout.js","names":[],"sources":["../../src/helpers/spawnSyncWithTimeout.ts"],"sourcesContent":["import child_process from 'node:child_process';\nimport os from 'node:os';\n\
|
|
1
|
+
{"version":3,"file":"spawnSyncWithTimeout.js","names":[],"sources":["../../src/helpers/spawnSyncWithTimeout.ts"],"sourcesContent":["import child_process from 'node:child_process';\nimport os from 'node:os';\n\nconst TIME_COMMAND = [os.platform() === 'darwin' ? 'gtime' : '/usr/bin/time', '--format', '%e %M'] as const;\n\nexport function spawnSyncWithTimeout(\n command: string,\n args: readonly string[],\n options: child_process.SpawnSyncOptionsWithStringEncoding,\n timeoutSeconds: number\n): child_process.SpawnSyncReturns<string> & { timeSeconds: number; memoryBytes: number } {\n const startTimeMilliseconds = Date.now();\n\n const spawnResult = child_process.spawnSync(\n 'timeout',\n [timeoutSeconds.toFixed(3), ...TIME_COMMAND, command, ...args],\n options\n );\n\n const stopTimeMilliseconds = Date.now();\n\n const match = /(?:^|\\n)(\\d+\\.\\d+) (\\d+)\\s*$/.exec(spawnResult.stderr);\n const stderr = match ? spawnResult.stderr.slice(0, match.index) : spawnResult.stderr;\n const timeSeconds = Number(match?.[1]) || (stopTimeMilliseconds - startTimeMilliseconds) / 1000;\n const memoryBytes = Number(match?.[2]) * 1024 || 0;\n\n // timeout\n if (spawnResult.status === 124) {\n return { ...spawnResult, status: 0, stderr, timeSeconds: timeoutSeconds + 1e-3, memoryBytes };\n }\n\n return { ...spawnResult, stderr, timeSeconds, memoryBytes };\n}\n"],"mappings":"yDAGA,MAAM,EAAe,CAAC,EAAG,SAAS,IAAM,SAAW,QAAU,gBAAiB,WAAY,OAAO,EAEjG,SAAgB,EACd,EACA,EACA,EACA,EACuF,CACvF,IAAM,EAAwB,KAAK,IAAI,EAEjC,EAAc,EAAc,UAChC,UACA,CAAC,EAAe,QAAQ,CAAC,EAAG,GAAG,EAAc,EAAS,GAAG,CAAI,EAC7D,CACF,EAEM,EAAuB,KAAK,IAAI,EAEhC,EAAQ,+BAA+B,KAAK,EAAY,MAAM,EAC9D,EAAS,EAAQ,EAAY,OAAO,MAAM,EAAG,EAAM,KAAK,EAAI,EAAY,OACxE,EAAc,OAAO,IAAQ,EAAE,IAAM,EAAuB,GAAyB,IACrF,EAAc,OAAO,IAAQ,EAAE,EAAI,MAAQ,EAOjD,OAJI,EAAY,SAAW,IAClB,CAAE,GAAG,EAAa,OAAQ,EAAG,SAAQ,YAAa,EAAiB,KAAM,aAAY,EAGvF,CAAE,GAAG,EAAa,SAAQ,cAAa,aAAY,CAC5D"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";const e=require("../_virtual/_rolldown/runtime.cjs")
|
|
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:http");r=e.__toESM(r,1);function i(e){let i=r.default.createServer((r,i)=>{let a=new URL(r.url??`/`,`http://127.0.0.1`).pathname;a===`/`&&(a=`/index.html`);let s=a.endsWith(`/`)?n.default.join(a,`index.html`):a,c=n.default.join(e,s);if(t.default.existsSync(c))try{let e=t.default.readFileSync(c);i.writeHead(200,{"Access-Control-Allow-Origin":`*`,Pragma:`no-cache`,"Cache-Control":`no-cache`,"Content-Type":o(s)}),i.end(e)}catch{i.statusCode=500,i.end()}else i.statusCode=404,i.end()});i.listen();let a=i.address();if(!a)throw Error(`server has been unexpectedly closed`);return{[Symbol.asyncDispose]:async()=>{i.closeAllConnections(),await new Promise(e=>{i.close(()=>{e()})})},url:typeof a==`string`?a:`http://127.0.0.1:${a.port}`,port:typeof a==`object`?a.port:void 0}}const a={".css":`text/css`,".html":`text/html`,".js":`text/javascript`,".gif":`image/gif`,".jpg":`image/jpeg`,".png":`image/png`,".svg":`image/svg+xml`};function o(e){for(let[t,n]of Object.entries(a))if(e.endsWith(t))return n;return`text/plain`}exports.startHttpServer=i;
|
|
2
2
|
//# sourceMappingURL=startHttpServer.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"startHttpServer.cjs","names":["http","path","fs"
|
|
1
|
+
{"version":3,"file":"startHttpServer.cjs","names":["http","path","fs"],"sources":["../../src/helpers/startHttpServer.ts"],"sourcesContent":["import fs from 'node:fs';\nimport http from 'node:http';\nimport path from 'node:path';\n\nexport interface HttpServer {\n [Symbol.asyncDispose](): Promise<void>;\n url: string;\n port: number | undefined;\n}\n\n/**\n * Start a HTTP server for testing web pages.\n */\nexport function startHttpServer(dir: string): HttpServer {\n const server = http.createServer((request, response) => {\n let pathname = new URL(request.url ?? '/', 'http://127.0.0.1').pathname;\n\n if (pathname === '/') {\n pathname = '/index.html';\n }\n\n const pathnameWithIndexHtml = pathname.endsWith('/') ? path.join(pathname, 'index.html') : pathname;\n\n const filePath = path.join(dir, pathnameWithIndexHtml);\n\n if (fs.existsSync(filePath)) {\n try {\n const buffer = fs.readFileSync(filePath);\n response.writeHead(200, {\n 'Access-Control-Allow-Origin': '*',\n Pragma: 'no-cache',\n 'Cache-Control': 'no-cache',\n 'Content-Type': getContentType(pathnameWithIndexHtml),\n });\n response.end(buffer);\n } catch {\n response.statusCode = 500;\n response.end();\n }\n } else {\n response.statusCode = 404;\n response.end();\n }\n });\n\n server.listen();\n\n const address = server.address();\n if (!address) throw new Error('server has been unexpectedly closed');\n\n return {\n [Symbol.asyncDispose]: async () => {\n server.closeAllConnections();\n await new Promise<void>((resolve) => {\n server.close(() => {\n resolve();\n });\n });\n },\n url: typeof address === 'string' ? address : `http://127.0.0.1:${address.port}`,\n port: typeof address === 'object' ? address.port : undefined,\n };\n}\n\nconst CONTENT_TYPE_BY_SUFFIX: Record<string, string> = {\n '.css': 'text/css',\n '.html': 'text/html',\n '.js': 'text/javascript',\n '.gif': 'image/gif',\n '.jpg': 'image/jpeg',\n '.png': 'image/png',\n '.svg': 'image/svg+xml',\n};\n\nfunction getContentType(pathname: string): string {\n for (const [suffix, contentType] of Object.entries(CONTENT_TYPE_BY_SUFFIX)) {\n if (pathname.endsWith(suffix)) return contentType;\n }\n return 'text/plain';\n}\n"],"mappings":"oMAaA,SAAgB,EAAgB,EAAyB,CACvD,IAAM,EAASA,EAAAA,QAAK,cAAc,EAAS,IAAa,CACtD,IAAI,EAAW,IAAI,IAAI,EAAQ,KAAO,IAAK,kBAAkB,CAAC,CAAC,SAE3D,IAAa,MACf,EAAW,eAGb,IAAM,EAAwB,EAAS,SAAS,GAAG,EAAIC,EAAAA,QAAK,KAAK,EAAU,YAAY,EAAI,EAErF,EAAWA,EAAAA,QAAK,KAAK,EAAK,CAAqB,EAErD,GAAIC,EAAAA,QAAG,WAAW,CAAQ,EACxB,GAAI,CACF,IAAM,EAASA,EAAAA,QAAG,aAAa,CAAQ,EACvC,EAAS,UAAU,IAAK,CACtB,8BAA+B,IAC/B,OAAQ,WACR,gBAAiB,WACjB,eAAgB,EAAe,CAAqB,CACtD,CAAC,EACD,EAAS,IAAI,CAAM,CACrB,MAAQ,CACN,EAAS,WAAa,IACtB,EAAS,IAAI,CACf,KAEA,GAAS,WAAa,IACtB,EAAS,IAAI,CAEjB,CAAC,EAED,EAAO,OAAO,EAEd,IAAM,EAAU,EAAO,QAAQ,EAC/B,GAAI,CAAC,EAAS,MAAU,MAAM,qCAAqC,EAEnE,MAAO,EACJ,OAAO,cAAe,SAAY,CACjC,EAAO,oBAAoB,EAC3B,MAAM,IAAI,QAAe,GAAY,CACnC,EAAO,UAAY,CACjB,EAAQ,CACV,CAAC,CACH,CAAC,CACH,EACA,IAAK,OAAO,GAAY,SAAW,EAAU,oBAAoB,EAAQ,OACzE,KAAM,OAAO,GAAY,SAAW,EAAQ,KAAO,IAAA,EACrD,CACF,CAEA,MAAM,EAAiD,CACrD,OAAQ,WACR,QAAS,YACT,MAAO,kBACP,OAAQ,YACR,OAAQ,aACR,OAAQ,YACR,OAAQ,eACV,EAEA,SAAS,EAAe,EAA0B,CAChD,IAAK,GAAM,CAAC,EAAQ,KAAgB,OAAO,QAAQ,CAAsB,EACvE,GAAI,EAAS,SAAS,CAAM,EAAG,OAAO,EAExC,MAAO,YACT"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import
|
|
1
|
+
import e from"node:fs";import t from"node:path";import n from"node:http";function r(r){let i=n.createServer((n,i)=>{let o=new URL(n.url??`/`,`http://127.0.0.1`).pathname;o===`/`&&(o=`/index.html`);let s=o.endsWith(`/`)?t.join(o,`index.html`):o,c=t.join(r,s);if(e.existsSync(c))try{let t=e.readFileSync(c);i.writeHead(200,{"Access-Control-Allow-Origin":`*`,Pragma:`no-cache`,"Cache-Control":`no-cache`,"Content-Type":a(s)}),i.end(t)}catch{i.statusCode=500,i.end()}else i.statusCode=404,i.end()});i.listen();let o=i.address();if(!o)throw Error(`server has been unexpectedly closed`);return{[Symbol.asyncDispose]:async()=>{i.closeAllConnections(),await new Promise(e=>{i.close(()=>{e()})})},url:typeof o==`string`?o:`http://127.0.0.1:${o.port}`,port:typeof o==`object`?o.port:void 0}}const i={".css":`text/css`,".html":`text/html`,".js":`text/javascript`,".gif":`image/gif`,".jpg":`image/jpeg`,".png":`image/png`,".svg":`image/svg+xml`};function a(e){for(let[t,n]of Object.entries(i))if(e.endsWith(t))return n;return`text/plain`}export{r as startHttpServer};
|
|
2
2
|
//# sourceMappingURL=startHttpServer.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"startHttpServer.js","names":[],"sources":["../../src/helpers/startHttpServer.ts"],"sourcesContent":["import fs from 'node:fs';\nimport http from 'node:http';\nimport path from 'node:path';\n\
|
|
1
|
+
{"version":3,"file":"startHttpServer.js","names":[],"sources":["../../src/helpers/startHttpServer.ts"],"sourcesContent":["import fs from 'node:fs';\nimport http from 'node:http';\nimport path from 'node:path';\n\nexport interface HttpServer {\n [Symbol.asyncDispose](): Promise<void>;\n url: string;\n port: number | undefined;\n}\n\n/**\n * Start a HTTP server for testing web pages.\n */\nexport function startHttpServer(dir: string): HttpServer {\n const server = http.createServer((request, response) => {\n let pathname = new URL(request.url ?? '/', 'http://127.0.0.1').pathname;\n\n if (pathname === '/') {\n pathname = '/index.html';\n }\n\n const pathnameWithIndexHtml = pathname.endsWith('/') ? path.join(pathname, 'index.html') : pathname;\n\n const filePath = path.join(dir, pathnameWithIndexHtml);\n\n if (fs.existsSync(filePath)) {\n try {\n const buffer = fs.readFileSync(filePath);\n response.writeHead(200, {\n 'Access-Control-Allow-Origin': '*',\n Pragma: 'no-cache',\n 'Cache-Control': 'no-cache',\n 'Content-Type': getContentType(pathnameWithIndexHtml),\n });\n response.end(buffer);\n } catch {\n response.statusCode = 500;\n response.end();\n }\n } else {\n response.statusCode = 404;\n response.end();\n }\n });\n\n server.listen();\n\n const address = server.address();\n if (!address) throw new Error('server has been unexpectedly closed');\n\n return {\n [Symbol.asyncDispose]: async () => {\n server.closeAllConnections();\n await new Promise<void>((resolve) => {\n server.close(() => {\n resolve();\n });\n });\n },\n url: typeof address === 'string' ? address : `http://127.0.0.1:${address.port}`,\n port: typeof address === 'object' ? address.port : undefined,\n };\n}\n\nconst CONTENT_TYPE_BY_SUFFIX: Record<string, string> = {\n '.css': 'text/css',\n '.html': 'text/html',\n '.js': 'text/javascript',\n '.gif': 'image/gif',\n '.jpg': 'image/jpeg',\n '.png': 'image/png',\n '.svg': 'image/svg+xml',\n};\n\nfunction getContentType(pathname: string): string {\n for (const [suffix, contentType] of Object.entries(CONTENT_TYPE_BY_SUFFIX)) {\n if (pathname.endsWith(suffix)) return contentType;\n }\n return 'text/plain';\n}\n"],"mappings":"yEAaA,SAAgB,EAAgB,EAAyB,CACvD,IAAM,EAAS,EAAK,cAAc,EAAS,IAAa,CACtD,IAAI,EAAW,IAAI,IAAI,EAAQ,KAAO,IAAK,kBAAkB,CAAC,CAAC,SAE3D,IAAa,MACf,EAAW,eAGb,IAAM,EAAwB,EAAS,SAAS,GAAG,EAAI,EAAK,KAAK,EAAU,YAAY,EAAI,EAErF,EAAW,EAAK,KAAK,EAAK,CAAqB,EAErD,GAAI,EAAG,WAAW,CAAQ,EACxB,GAAI,CACF,IAAM,EAAS,EAAG,aAAa,CAAQ,EACvC,EAAS,UAAU,IAAK,CACtB,8BAA+B,IAC/B,OAAQ,WACR,gBAAiB,WACjB,eAAgB,EAAe,CAAqB,CACtD,CAAC,EACD,EAAS,IAAI,CAAM,CACrB,MAAQ,CACN,EAAS,WAAa,IACtB,EAAS,IAAI,CACf,KAEA,GAAS,WAAa,IACtB,EAAS,IAAI,CAEjB,CAAC,EAED,EAAO,OAAO,EAEd,IAAM,EAAU,EAAO,QAAQ,EAC/B,GAAI,CAAC,EAAS,MAAU,MAAM,qCAAqC,EAEnE,MAAO,EACJ,OAAO,cAAe,SAAY,CACjC,EAAO,oBAAoB,EAC3B,MAAM,IAAI,QAAe,GAAY,CACnC,EAAO,UAAY,CACjB,EAAQ,CACV,CAAC,CACH,CAAC,CACH,EACA,IAAK,OAAO,GAAY,SAAW,EAAU,oBAAoB,EAAQ,OACzE,KAAM,OAAO,GAAY,SAAW,EAAQ,KAAO,IAAA,EACrD,CACF,CAEA,MAAM,EAAiD,CACrD,OAAQ,WACR,QAAS,YACT,MAAO,kBACP,OAAQ,YACR,OAAQ,aACR,OAAQ,YACR,OAAQ,eACV,EAEA,SAAS,EAAe,EAA0B,CAChD,IAAK,GAAM,CAAC,EAAQ,KAAgB,OAAO,QAAQ,CAAsB,EACvE,GAAI,EAAS,SAAS,CAAM,EAAG,OAAO,EAExC,MAAO,YACT"}
|
|
@@ -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 @@
|
|
|
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"}
|
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/
|
|
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
|
@@ -8,10 +8,13 @@ export * from './helpers/readTestCases.js';
|
|
|
8
8
|
export * from './helpers/removeCommentsInSourceCode.js';
|
|
9
9
|
export * from './helpers/runCommandInTemporaryPackageManagerProject.js';
|
|
10
10
|
export * from './helpers/runPythonInTemporaryUvProject.js';
|
|
11
|
-
export * from './helpers/safeFs.js';
|
|
12
|
-
export * from './helpers/sandboxUser.js';
|
|
13
11
|
export * from './helpers/sourceCodeGrammars.js';
|
|
14
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';
|
|
15
18
|
export * from './types/decisionCode.js';
|
|
16
19
|
export * from './types/problem.js';
|
|
17
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{
|
|
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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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface SourceFile {
|
|
2
|
+
path: string;
|
|
3
|
+
data: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function isFile(path: string): Promise<boolean>;
|
|
6
|
+
/** Whether the path is a regular file (not a symlink), as the judge's problem discovery requires. */
|
|
7
|
+
export declare function isRegularFile(path: string): Promise<boolean>;
|
|
8
|
+
/** Whether the path is a directory that is not a symbolic link, which the judge's discovery does not traverse. */
|
|
9
|
+
export declare function isRegularDirectory(path: string): Promise<boolean>;
|
|
10
|
+
export declare function isDirectory(path: string): Promise<boolean>;
|
|
11
|
+
/**
|
|
12
|
+
* Collects the problem definitions under a directory the way the judge discovers them: `problem.md`
|
|
13
|
+
* names its directory and `<id>.problem.md` names itself, at any depth, without traversing symbolic
|
|
14
|
+
* links or a nested course (whose problems belong to that nearer course). The result maps each
|
|
15
|
+
* problem ID to the paths of its definitions (relative to the root); the judge rejects an ID
|
|
16
|
+
* defined more than once.
|
|
17
|
+
*/
|
|
18
|
+
export declare function collectProblemDefinitions(rootDirectoryPath: string): Promise<Map<string, string[]>>;
|
|
19
|
+
/**
|
|
20
|
+
* Reports discovered problem definitions the judge refuses: an ID that does not match the ID pattern
|
|
21
|
+
* aborts discovery of the whole repository, and an ID defined more than once is a conflict.
|
|
22
|
+
*/
|
|
23
|
+
export declare function reportProblemDefinitionIssues(definitionPathsById: ReadonlyMap<string, string[]>, errors: string[]): void;
|
|
24
|
+
/** Reports a problems directory the judge cannot search (missing, or a symbolic link); returns whether it is usable. */
|
|
25
|
+
export declare function isUsableProblemsDirectory(problemsDirectoryPath: string, errors: string[]): Promise<boolean>;
|
|
26
|
+
/** Collects the problem IDs a directory defines, reporting the issues the judge refuses; undefined when it cannot be searched. */
|
|
27
|
+
export declare function collectProblemIdsFromDirectory(problemsDirectoryPath: string, errors: string[]): Promise<Set<string> | undefined>;
|
|
28
|
+
export declare function reportDanglingProblemReferences(problemIds: Iterable<string>, availableProblemIds: ReadonlySet<string>, problemsDirectoryPath: string | undefined, errors: string[]): void;
|
|
29
|
+
/** Reads all text files under a directory, with paths relative to the given root. */
|
|
30
|
+
export declare function readSourceFilesRecursively(rootDirectoryPath: string): Promise<SourceFile[]>;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{LEARNING_MATERIAL_ID_REGEX as e}from"./schemas.js";import{basename as t,extname as n,join as r,relative as i}from"node:path";import{lstat as a,readFile as o,readdir as s,stat as c}from"node:fs/promises";const l=new Set([`__pycache__`]),u=new Set([``,`.class`,`.exe`,`.jar`,`.out`,`.pyc`,`.png`,`.gif`]);async function d(e){try{return(await c(e)).isFile()}catch{return!1}}async function f(e){try{return(await a(e)).isFile()}catch{return!1}}async function p(e){try{return(await a(e)).isDirectory()}catch{return!1}}async function m(e){try{return(await c(e)).isDirectory()}catch{return!1}}async function h(e){let t=new Map;return await g(e,e,t),t}async function g(e,n,a){let o=await s(n,{withFileTypes:!0});if(!(n!==e&&o.some(e=>e.isFile()&&(e.name===`course.yaml`||e.name===`course.yml`))))for(let s of o)if(s.isFile()){let o=s.name===`problem.md`?t(n):s.name.endsWith(`.problem.md`)?s.name.slice(0,-11).toLowerCase():void 0;if(o===void 0)continue;let c=a.get(o)??[];c.push(i(e,r(n,s.name))),a.set(o,c)}else s.isDirectory()&&!_(n,s.name)&&await g(e,r(n,s.name),a)}function _(e,n){return n.startsWith(`.`)||n===`node_modules`||n===`target`||n===`temp`&&t(e)===`judge-environments`}function v(t,n){for(let[r,i]of t)e.test(r)||n.push(`problem ID "${r}" (${i.join(`, `)}) must match ${e}; 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 y(e,t){return await p(e)?!0:(t.push(await m(e)?`problems directory is a symbolic link, which the judge does not traverse: ${e}`:`problems directory not found: ${e}`),!1)}async function b(e,t){if(!await y(e,t))return;let n=await h(e);return v(n,t),new Set(n.keys())}function x(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 S(e){let t=[];return await C(e,e,t),t.toSorted((e,t)=>e.path.localeCompare(t.path))}async function C(e,t,a){for(let c of await s(t,{withFileTypes:!0})){let s=r(t,c.name);c.isDirectory()?l.has(c.name)||await C(e,s,a):c.isFile()&&!u.has(n(c.name).toLowerCase())&&a.push({path:i(e,s),data:await o(s,`utf8`)})}}export{h as collectProblemDefinitions,b as collectProblemIdsFromDirectory,m as isDirectory,d as isFile,p as isRegularDirectory,f as isRegularFile,y as isUsableProblemsDirectory,S as readSourceFilesRecursively,x as reportDanglingProblemReferences,v as reportProblemDefinitionIssues};
|
|
2
|
+
//# sourceMappingURL=fsHelpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fsHelpers.js","names":[],"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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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";const e=require("../types/language.cjs"),t=require("../types/problem.cjs");let n=require("zod");const r=/^[0-9_a-z-]+$/,i=n.z.string().regex(r),a=Object.keys(e.languageIdToDefinition),o=n.z.enum(a),s=n.z.string().regex(/^\w+$/),c=n.z.strictObject({name:n.z.string().min(1),type:n.z.enum([`prompt_study`]).optional(),timeLimitMs:n.z.number().int().min(0).optional(),memoryLimitByte:n.z.number().int().min(0).optional(),requiredRegExpsInCode:t.codeRuleSchema.array().default([]),forbiddenRegExpsInCode:t.codeRuleSchema.array().default([]),forbiddenTextsInCode:t.codeRuleSchema.array().default([]),canCreateFiles:n.z.boolean().optional(),isEditorDisabled:n.z.boolean().optional(),isAttachedFileRequired:n.z.boolean().optional(),isManualScoringRequired:n.z.boolean().optional(),isVotable:n.z.boolean().optional(),requiredEnvironmentVariables:n.z.array(s).default([]),requiredOutputFilePaths:n.z.string().min(1).array().default([]),requiredSubmissionFilePaths:n.z.string().min(1).array().default([])}),l={availableLanguageIds:o.array().optional(),areTestCasesHidden:n.z.boolean().optional(),isProblemGradingResultHidden:n.z.boolean().optional(),isAutoFormatDisabled:n.z.boolean().optional(),isCopyAndPasteDisabled:n.z.boolean().optional(),isDebugHintDisabled:n.z.boolean().optional(),isFixHintDisabled:n.z.boolean().optional(),isDiffHintDisabled:n.z.boolean().optional(),debugHintWaitingSeconds:n.z.number().min(0).optional(),fixHintWaitingSeconds:n.z.number().min(0).optional(),diffHintWaitingSeconds:n.z.number().min(0).optional(),submissionOpenedAt:n.z.iso.datetime({offset:!0}).optional(),submissionSoftClosedAt:n.z.iso.datetime({offset:!0}).optional(),submissionHardClosedAt:n.z.iso.datetime({offset:!0}).optional(),isAutoTranslationDisabled:n.z.boolean().optional(),isModelAnswerShownAfterDeadline:n.z.boolean().optional(),isVotable:n.z.boolean().optional(),isMaterialChatDisabled:n.z.boolean().optional()},u=n.z.strictObject({...l,name:n.z.string().min(1),description:n.z.string(),author:n.z.string().min(1).optional(),isMotivationFeatureEnabled:n.z.boolean().optional(),isPublic:n.z.boolean().optional(),lectures:n.z.array(n.z.object({id:i,name:n.z.string().min(1),description:n.z.string()})).optional()}),d=n.z.string().array().min(1).refine(e=>e.length===new Set(e).size,{error:`options must contain unique values`}),f=n.z.string().regex(/^\d+$/,{error:`must be a non-negative integer index (quoted numbers such as "0" are accepted)`}).transform(Number),p={id:i,isResubmittable:n.z.boolean().optional(),isSurvey:n.z.boolean().optional(),explanation:n.z.string().optional(),hint:n.z.string().optional()},m={...p,type:n.z.literal(`select`),options:d,answerIndex:n.z.union([n.z.number().int().nonnegative(),f,n.z.number().int().nonnegative().array(),f.array()]).optional()},h={...p,type:n.z.literal(`select_multiple`),options:d,answerIndices:n.z.union([n.z.number().int().nonnegative().array(),f.array()]).optional()},g={...p,type:n.z.literal(`text`),answerPattern:n.z.string().min(1).optional(),modelAnswer:n.z.string().optional()},_=n.z.discriminatedUnion(`type`,[n.z.strictObject({...m,question:n.z.string()}),n.z.strictObject({...h,question:n.z.string()}),n.z.strictObject({...g,question:n.z.string()})]),v=n.z.discriminatedUnion(`type`,[n.z.strictObject({...m,question:n.z.string().optional()}),n.z.strictObject({...h,question:n.z.string().optional()}),n.z.strictObject({...g,question:n.z.string().optional()})]),y=n.z.strictObject({...l,name:n.z.string().min(1),problems:n.z.object({id:i,courseId:i.optional()}).array().optional(),questions:n.z.array(v).optional(),turtleGraphicsQuestions:n.z.object({id:i}).array().optional(),isExamination:n.z.boolean().optional(),isMockExamination:n.z.boolean().optional(),isRealtimeSurvey:n.z.boolean().optional()}),b=n.z.strictObject({id:i,name:n.z.string().min(1),openedAt:n.z.iso.datetime({offset:!0}),closedAt:n.z.iso.datetime({offset:!0}),password:n.z.string().min(1).optional()}).refine(e=>new Date(e.openedAt)<new Date(e.closedAt),{error:`openedAt must be earlier than closedAt`}),x=n.z.strictObject({id:i,score:n.z.number().int().nonnegative(),courseId:i.optional(),areTestCasesHidden:l.areTestCasesHidden,isDebugHintDisabled:l.isDebugHintDisabled,isFixHintDisabled:l.isFixHintDisabled,isDiffHintDisabled:l.isDiffHintDisabled,debugHintWaitingSeconds:l.debugHintWaitingSeconds,fixHintWaitingSeconds:l.fixHintWaitingSeconds,diffHintWaitingSeconds:l.diffHintWaitingSeconds,isMaterialChatDisabled:l.isMaterialChatDisabled}),S=n.z.strictObject({...l,name:n.z.string().min(1),description:n.z.string().optional(),adminEmails:n.z.array(n.z.string().trim().email()).optional(),showsProblemsAfterClose:n.z.boolean().optional(),divisions:n.z.array(b).min(1),problems:n.z.array(x).min(1)});exports.CONTEST_MATERIAL_FILE_SUFFIX=`.contest.yaml`,exports.LEARNING_MATERIAL_ID_REGEX=r,exports.availableLanguageIds=a,exports.contestFileSchema=S,exports.courseFileSchema=u,exports.learningMaterialIdSchema=i,exports.materialFrontmatterSchema=y,exports.problemFrontmatterSchema=c,exports.questionInCodeBlockSchema=_;
|
|
2
|
+
//# sourceMappingURL=schemas.cjs.map
|