@exercode/problem-utils 4.4.0 → 5.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/checkAllProblems.cjs.map +1 -1
- package/dist/cli/checkAllProblems.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/learningMaterial/schemas.cjs +1 -1
- package/dist/learningMaterial/schemas.cjs.map +1 -1
- package/dist/learningMaterial/schemas.d.ts +1 -3
- package/dist/learningMaterial/schemas.js +1 -1
- package/dist/learningMaterial/schemas.js.map +1 -1
- package/dist/learningMaterial/validateContest.cjs +1 -1
- package/dist/learningMaterial/validateContest.cjs.map +1 -1
- package/dist/learningMaterial/validateContest.d.ts +0 -2
- package/dist/learningMaterial/validateContest.js +1 -1
- package/dist/learningMaterial/validateContest.js.map +1 -1
- package/dist/learningMaterial/validateCourse.cjs +1 -1
- package/dist/learningMaterial/validateCourse.cjs.map +1 -1
- package/dist/learningMaterial/validateCourse.js +1 -1
- package/dist/learningMaterial/validateCourse.js.map +1 -1
- package/dist/learningMaterial/validateMaterial.cjs +3 -3
- package/dist/learningMaterial/validateMaterial.cjs.map +1 -1
- package/dist/learningMaterial/validateMaterial.d.ts +0 -7
- package/dist/learningMaterial/validateMaterial.js +3 -3
- package/dist/learningMaterial/validateMaterial.js.map +1 -1
- package/package.json +6 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checkAllProblems.cjs","names":["DecisionCode","path","forEachConcurrently","readProblemMarkdownFrontMatter","findDefaultStdioHarnessFiles","formatDefaultHarnessError","findModelAnswerDirs","readTestCases","getErrorMessage","judgesWithoutTestCases","MISSING_TEST_CASES_ERROR","findFailingModelAnswerDirs","agentChallengeSchema","fs","copyProblemDirToTemporaryRoot","runHarnessProcess","forciblyRemoveDirectory","TEST_CASE_RESULT_PREFIX","testCaseResultSchema","require_runtime","require_testCaseResult","require_defaultStdioHarness","require_readTestCases","require_temporaryProblemDirCopy","require_schemas","require_readProblemMarkdownFrontMatter","require_decisionCode","require_findModelAnswerDirs","require_runHarnessProcess","require_runSingleHarness","node_path","node_fs_promises","_willbooster_shared_lib","MAX_FAILURE_DETAIL_LENGTH","decisionCodeNames","name","code","checkAllProblems","options","parseCheckArgs","args","rootDir","toRelative","dir","allProblemDirs","findProblemDirs","problemDirs","relativeDir","problemDir","substring","failures","runs","agentChallengeCount","plan","planProblemCheck","failure","cliEntryPath","passedCount","label","run","failureDetail","executeCheckRun","frontMatterResult","planAgentChallengeCheck","defaultHarnessFileNames","modelAnswerDirs","fileExists","failingModelAnswerDirs","parsed","agentChallenge","dirent","issue","tempRoot","copiedProblemDir","error","truncate","harnessFailureDetail","removedTempRoot","summarizeHarnessFailure","removalFailureDetail","result","stderr","resultLines","stdout","line","testCaseResults","parsedResult","rejectedResult","hasRootDir","index","arg","value","visitDirectory","entries","entry","filePath","text"],"sources":["../../src/cli/checkAllProblems.ts"],"sourcesContent":["import fs from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { forEachConcurrently, getErrorMessage } from '@willbooster/shared-lib';\n\nimport { findDefaultStdioHarnessFiles } from '../helpers/defaultStdioHarness.js';\nimport { findFailingModelAnswerDirs, findModelAnswerDirs } from '../helpers/findModelAnswerDirs.js';\nimport { judgesWithoutTestCases, readProblemMarkdownFrontMatter } from '../helpers/readProblemMarkdownFrontMatter.js';\nimport { readTestCases } from '../helpers/readTestCases.js';\nimport { type HarnessProcessResult, runHarnessProcess } from '../helpers/runHarnessProcess.js';\nimport { copyProblemDirToTemporaryRoot, forciblyRemoveDirectory } from '../helpers/temporaryProblemDirCopy.js';\nimport { agentChallengeSchema } from '../learningMaterial/schemas.js';\nimport { DecisionCode } from '../types/decisionCode.js';\nimport { TEST_CASE_RESULT_PREFIX, testCaseResultSchema } from '../types/testCaseResult.js';\nimport { formatDefaultHarnessError, MISSING_TEST_CASES_ERROR } from './runSingleHarness.js';\n\nconst RUN_TIMEOUT_MS = 600_000;\nconst MAX_RUN_OUTPUT_BYTES = 64 * 1024 * 1024;\nconst MAX_FAILURE_DETAIL_LENGTH = 1000;\n\n// DecisionCode is a plain const object (not a TypeScript enum), so it has no reverse mapping.\nconst decisionCodeNames = new Map<number, string>(Object.entries(DecisionCode).map(([name, code]) => [code, name]));\n\ninterface CheckOptions {\n rootDir: string;\n concurrency: number;\n only: string[];\n skip: string[];\n}\n\ninterface CheckRun {\n problemDir: string;\n answerDir: string;\n /** Whether all test cases must be accepted (`model_answers`) or at least one must fail (`model_answers.fails`). */\n expectation: 'accepted' | 'rejected';\n}\n\n/**\n * Judge all model answers of all problems (directories containing `problem.md` or\n * `<id>.problem.md`) under a root directory: `model_answers/*` must be fully accepted and\n * `model_answers.fails/*` must fail at least one test case. Agent challenges have no harness, so\n * they are not judged but fail when their `agentChallenge` is invalid or their explanation is missing.\n */\nexport async function checkAllProblems(args: readonly string[]): Promise<number> {\n const options = parseCheckArgs(args);\n const rootDir = path.resolve(options.rootDir);\n // Normalize to forward slashes so --only / --skip substrings like courses/foo match on Windows.\n const toRelative = (dir: string): string => (path.relative(rootDir, dir) || '.').replaceAll(path.sep, '/');\n\n const allProblemDirs = await findProblemDirs(rootDir);\n const problemDirs = allProblemDirs.filter((problemDir) => {\n const relativeDir = toRelative(problemDir);\n if (options.only.length > 0 && !options.only.some((substring) => relativeDir.includes(substring))) return false;\n return !options.skip.some((substring) => relativeDir.includes(substring));\n });\n if (problemDirs.length === 0) {\n console.error(\n allProblemDirs.length === 0\n ? `No problem directories (containing problem.md or <id>.problem.md) found under ${rootDir}.`\n : `All ${allProblemDirs.length} problem directories under ${rootDir} were excluded by --only/--skip.`\n );\n return 1;\n }\n\n const failures: string[] = [];\n const runs: CheckRun[] = [];\n let agentChallengeCount = 0;\n for (const problemDir of problemDirs) {\n const plan = await planProblemCheck(problemDir);\n if (plan.type === 'agentChallenge') {\n agentChallengeCount++;\n } else if (plan.type === 'failure') {\n failures.push(`${toRelative(problemDir)}: ${plan.message}`);\n } else {\n runs.push(...plan.runs);\n }\n }\n for (const failure of failures) console.error(`✗ ${failure}`);\n\n const cliEntryPath = path.resolve(process.argv[1] ?? '');\n let passedCount = 0;\n await forEachConcurrently(runs, options.concurrency, async (run) => {\n const label = `${toRelative(run.problemDir)} ${path.relative(run.problemDir, run.answerDir).replaceAll(path.sep, '/')}`;\n const failureDetail = await executeCheckRun(run, cliEntryPath);\n if (failureDetail === undefined) {\n passedCount++;\n console.info(`✓ ${label}`);\n } else {\n failures.push(`${label}: ${failureDetail}`);\n console.error(`✗ ${label}: ${failureDetail}`);\n }\n });\n\n console.info(\n `\\n${passedCount} passed, ${failures.length} failed (${runs.length} runs, ${problemDirs.length} problems${agentChallengeCount > 0 ? `, agent challenges skipped: ${agentChallengeCount}` : ''})`\n );\n return failures.length === 0 ? 0 : 1;\n}\n\ntype ProblemCheckPlan =\n { type: 'agentChallenge' } | { type: 'failure'; message: string } | { type: 'runs'; runs: CheckRun[] };\n\nasync function planProblemCheck(problemDir: string): Promise<ProblemCheckPlan> {\n // As before agent challenges existed, a front matter that fails to parse fails the problem only\n // when the test-case check below needs the front matter.\n const frontMatterResult = await readProblemMarkdownFrontMatter(problemDir).then(\n (frontMatter) => ({ frontMatter }),\n (error: unknown) => ({ error })\n );\n if ('frontMatter' in frontMatterResult && frontMatterResult.frontMatter.agentChallenge !== undefined) {\n return planAgentChallengeCheck(problemDir, frontMatterResult.frontMatter.agentChallenge);\n }\n\n const defaultHarnessFileNames = await findDefaultStdioHarnessFiles(problemDir);\n if (defaultHarnessFileNames.length > 0) {\n return { type: 'failure', message: formatDefaultHarnessError(defaultHarnessFileNames) };\n }\n\n const modelAnswerDirs = await findModelAnswerDirs(problemDir);\n if (modelAnswerDirs.length === 0) {\n return { type: 'failure', message: 'no model answers found under model_answers/' };\n }\n\n // Without test cases, stdioJudgePreset prints a single accepted sentinel result, so a standard\n // problem with an empty or missing test_cases/ would otherwise pass without being judged —\n // unless static-analysis rules or manual scoring make the problem judgeable without them.\n if (!(await fileExists(path.join(problemDir, 'judge.ts')))) {\n const testCases = await readTestCases(path.join(problemDir, 'test_cases'));\n if (testCases.length === 0) {\n if ('error' in frontMatterResult) {\n return {\n type: 'failure',\n message: `failed to read the problem markdown front matter: ${getErrorMessage(frontMatterResult.error)}`,\n };\n }\n if (!judgesWithoutTestCases(frontMatterResult.frontMatter)) {\n return { type: 'failure', message: MISSING_TEST_CASES_ERROR };\n }\n }\n }\n\n const failingModelAnswerDirs = await findFailingModelAnswerDirs(problemDir);\n return {\n type: 'runs',\n runs: [\n ...modelAnswerDirs.map((answerDir): CheckRun => ({ problemDir, answerDir, expectation: 'accepted' })),\n ...failingModelAnswerDirs.map((answerDir): CheckRun => ({ problemDir, answerDir, expectation: 'rejected' })),\n ],\n };\n}\n\n/** Skips a valid agent challenge and fails an invalid one instead of judging it as a stdio problem. */\nasync function planAgentChallengeCheck(problemDir: string, agentChallenge: unknown): Promise<ProblemCheckPlan> {\n const parsed = agentChallengeSchema.safeParse(agentChallenge);\n if (parsed.success) {\n const explanationDirents = await fs\n .readdir(path.join(problemDir, 'model_answers', 'explanation'), { recursive: true, withFileTypes: true })\n .catch(() => []);\n return explanationDirents.some((dirent) => dirent.isFile() && dirent.name.endsWith('.md'))\n ? { type: 'agentChallenge' }\n : { type: 'failure', message: 'no explanation found under model_answers/explanation/' };\n }\n const issues = parsed.error.issues.map((issue) => `${['agentChallenge', ...issue.path].join('.')}: ${issue.message}`);\n return { type: 'failure', message: `invalid agentChallenge (see validate-problem): ${issues.join('; ')}` };\n}\n\n/**\n * Run one answer directory through the problem's harness and return a failure detail, if any.\n * The problem directory is copied to a temporary location first so that judging (e.g. build\n * artifacts in answer directories) never modifies the checked repository.\n */\nasync function executeCheckRun(run: CheckRun, cliEntryPath: string): Promise<string | undefined> {\n let tempRoot: string;\n let copiedProblemDir: string;\n try {\n ({ tempRoot, copiedProblemDir } = await copyProblemDirToTemporaryRoot(run.problemDir));\n } catch (error) {\n return truncate(`failed to copy the problem directory to a temporary location: ${getErrorMessage(error)}`);\n }\n\n let harnessFailureDetail;\n let removedTempRoot;\n try {\n const result = await runHarnessProcess(\n ['run', cliEntryPath, 'judge', path.relative(run.problemDir, run.answerDir)],\n {\n cwd: copiedProblemDir,\n env: process.env,\n timeoutMs: RUN_TIMEOUT_MS,\n maxOutputBytes: MAX_RUN_OUTPUT_BYTES,\n tempRoot,\n }\n );\n harnessFailureDetail = summarizeHarnessFailure(run, result);\n } catch (error) {\n harnessFailureDetail = truncate(`harness execution failed: ${getErrorMessage(error)}`);\n } finally {\n removedTempRoot = await forciblyRemoveDirectory(tempRoot);\n }\n if (removedTempRoot) return harnessFailureDetail;\n const removalFailureDetail = `failed to remove the temporary copy at ${tempRoot} (judged code may have left permission-locked files)`;\n return harnessFailureDetail === undefined\n ? removalFailureDetail\n : truncate(`${harnessFailureDetail}; ${removalFailureDetail}`);\n}\n\n/** Return why the harness run failed the check, or `undefined` when it passed. */\nfunction summarizeHarnessFailure(run: CheckRun, result: HarnessProcessResult): string | undefined {\n const { stdout, stderr } = result;\n if (result.failureReason !== undefined) return truncate(result.failureReason);\n if (result.exitCode !== 0) {\n return truncate(`harness exited with ${result.exitCode ?? 'a signal'}${stderr.trim() ? `: ${stderr.trim()}` : ''}`);\n }\n\n const resultLines = stdout.split(/\\r?\\n/).filter((line) => line.startsWith(TEST_CASE_RESULT_PREFIX));\n const testCaseResults = [];\n for (const line of resultLines) {\n let parsedResult;\n try {\n parsedResult = testCaseResultSchema.safeParse(JSON.parse(line.slice(TEST_CASE_RESULT_PREFIX.length)));\n } catch {\n parsedResult = undefined;\n }\n if (!parsedResult?.success) return truncate(`malformed test case result line: ${line}`);\n testCaseResults.push(parsedResult.data);\n }\n if (testCaseResults.length === 0) return 'no test case results were printed';\n\n if (run.expectation === 'accepted') {\n const rejectedResult = testCaseResults.find((result) => result.decisionCode !== DecisionCode.ACCEPTED);\n if (rejectedResult) {\n return truncate(\n `${decisionCodeNames.get(rejectedResult.decisionCode) ?? rejectedResult.decisionCode} on test case ${rejectedResult.testCaseId}${rejectedResult.stderr?.trim() ? `: ${rejectedResult.stderr.trim()}` : ''}`\n );\n }\n return undefined;\n }\n return testCaseResults.every((result) => result.decisionCode === DecisionCode.ACCEPTED)\n ? 'expected at least one failing test case, but all test cases were accepted'\n : undefined;\n}\n\nfunction parseCheckArgs(args: readonly string[]): CheckOptions {\n const options: CheckOptions = {\n rootDir: '.',\n // Serial by default: judging decides TIME_LIMIT_EXCEEDED from wall-clock time, so parallel\n // runs on a small CI runner could fail timing-sensitive problems non-deterministically.\n concurrency: 1,\n only: [],\n skip: [],\n };\n let hasRootDir = false;\n for (let index = 0; index < args.length; index++) {\n const arg = args[index];\n if (arg === undefined) break;\n if (arg === '--concurrency' || arg === '--only' || arg === '--skip') {\n const value = args[++index];\n if (value === undefined) throw new Error(`${arg} requires a value`);\n if (arg === '--concurrency') {\n options.concurrency = Number(value);\n if (!Number.isInteger(options.concurrency) || options.concurrency <= 0) {\n throw new Error(`--concurrency requires a positive integer, but got ${value}`);\n }\n } else if (arg === '--only') {\n options.only.push(value);\n } else {\n options.skip.push(value);\n }\n } else if (arg.startsWith('--')) {\n throw new Error(`Unknown option: ${arg}`);\n } else if (hasRootDir) {\n throw new Error(`Only one root directory can be specified, but got both ${options.rootDir} and ${arg}`);\n } else {\n options.rootDir = arg;\n hasRootDir = true;\n }\n }\n return options;\n}\n\n/** Find directories containing `problem.md` or `<id>.problem.md`, without descending into found problems. */\nasync function findProblemDirs(rootDir: string): Promise<string[]> {\n const problemDirs: string[] = [];\n await visitDirectory(rootDir, problemDirs);\n return problemDirs.toSorted();\n}\n\nasync function visitDirectory(dir: string, problemDirs: string[]): Promise<void> {\n // Traversal errors (e.g. an unreadable subtree) must fail the check: skipping them silently\n // could report a green result while covering only part of the repository.\n const entries = await fs.readdir(dir, { withFileTypes: true });\n if (entries.some((entry) => entry.isFile() && (entry.name === 'problem.md' || entry.name.endsWith('.problem.md')))) {\n problemDirs.push(dir);\n return;\n }\n for (const entry of entries) {\n if (!entry.isDirectory() || entry.name === 'node_modules' || entry.name.startsWith('.')) continue;\n await visitDirectory(path.join(dir, entry.name), problemDirs);\n }\n}\n\nasync function fileExists(filePath: string): Promise<boolean> {\n try {\n await fs.stat(filePath);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction truncate(text: string): string {\n return text.length <= MAX_FAILURE_DETAIL_LENGTH ? text : `${text.slice(0, MAX_FAILURE_DETAIL_LENGTH)}...`;\n}\n"],"mappings":"A,qtBAgBA,MAEMiC,EAA4B,IAG5BC,EAAoB,IAAI,IAAoB,OAAO,QAAQlC,EAAAA,YAAY,CAAC,CAAC,KAAK,CAACmC,EAAMC,KAAU,CAACA,EAAMD,CAAI,CAAC,CAAC,EAsBlH,eAAsBE,EAAiB,EAA0C,CAC/E,IAAMC,EAAUC,EAAeC,CAAI,EAC7BC,EAAUxC,EAAAA,QAAK,QAAQqC,EAAQ,OAAO,EAEtCI,EAAc,IAAyBzC,EAAAA,QAAK,SAASwC,EAASE,CAAG,GAAK,IAAA,CAAK,WAAW1C,EAAAA,QAAK,IAAK,GAAG,EAEnG2C,EAAiB,MAAMC,EAAgBJ,CAAO,EAC9CK,EAAcF,EAAe,OAAQ,GAAe,CACxD,IAAMG,EAAcL,EAAWM,CAAU,EAEzC,OADIV,EAAQ,KAAK,OAAS,GAAK,CAACA,EAAQ,KAAK,KAAM,GAAcS,EAAY,SAASE,CAAS,CAAC,EAAU,GACnG,CAACX,EAAQ,KAAK,KAAM,GAAcS,EAAY,SAASE,CAAS,CAAC,CAC1E,CAAC,EACD,GAAIH,EAAY,SAAW,EAMzB,OALA,QAAQ,MACNF,EAAe,SAAW,EACtB,iFAAiFH,EAAQ,GACzF,OAAOG,EAAe,OAAO,6BAA6BH,EAAQ,iCACxE,EACO,EAGT,IAAMS,EAAqB,CAAC,EACtBC,EAAmB,CAAC,EACtBC,EAAsB,EAC1B,IAAK,IAAMJ,KAAcF,EAAa,CACpC,IAAMO,EAAO,MAAMC,EAAiBN,CAAU,EAC1CK,EAAK,OAAS,iBAChB,IACSA,EAAK,OAAS,UACvBH,EAAS,KAAK,GAAGR,EAAWM,CAAU,EAAE,IAAIK,EAAK,SAAS,EAE1DF,EAAK,KAAK,GAAGE,EAAK,IAAI,CAE1B,CACA,IAAK,IAAME,KAAWL,EAAU,QAAQ,MAAM,KAAKK,GAAS,EAE5D,IAAMC,EAAevD,EAAAA,QAAK,QAAQ,QAAQ,KAAK,IAAM,EAAE,EACnDwD,EAAc,EAgBlB,OAfA,MAAA,EAAMvD,EAAAA,oBAAAA,CAAoBiD,EAAMb,EAAQ,YAAa,KAAO,IAAQ,CAClE,IAAMoB,EAAQ,GAAGhB,EAAWiB,EAAI,UAAU,EAAE,GAAG1D,EAAAA,QAAK,SAAS0D,EAAI,WAAYA,EAAI,SAAS,CAAC,CAAC,WAAW1D,EAAAA,QAAK,IAAK,GAAG,IAC9G2D,EAAgB,MAAMC,EAAgBF,EAAKH,CAAY,EACzDI,IAAkB,IAAA,IACpB,IACA,QAAQ,KAAK,KAAKF,GAAO,IAEzBR,EAAS,KAAK,GAAGQ,EAAM,IAAIE,GAAe,EAC1C,QAAQ,MAAM,KAAKF,EAAM,IAAIE,GAAe,EAEhD,CAAC,EAED,QAAQ,KACN,KAAKH,EAAY,WAAWP,EAAS,OAAO,WAAWC,EAAK,OAAO,SAASL,EAAY,OAAO,WAAWM,EAAsB,EAAI,+BAA+BA,IAAwB,GAAG,EAChM,EACOF,EAAS,SAAW,EAAI,EAAI,CACrC,CAKA,eAAeI,EAAiB,EAA+C,CAG7E,IAAMQ,EAAoB,MAAM3D,EAAAA,+BAA+B6C,CAAU,CAAC,CAAC,KACxE,IAAiB,CAAE,aAAY,GAC/B,IAAoB,CAAE,OAAM,EAC/B,EACA,GAAI,gBAAiBc,GAAqBA,EAAkB,YAAY,iBAAmB,IAAA,GACzF,OAAOC,EAAwBf,EAAYc,EAAkB,YAAY,cAAc,EAGzF,IAAME,EAA0B,MAAM5D,EAAAA,6BAA6B4C,CAAU,EAC7E,GAAIgB,EAAwB,OAAS,EACnC,MAAO,CAAE,KAAM,UAAW,QAAS3D,EAAAA,0BAA0B2D,CAAuB,CAAE,EAGxF,IAAMC,EAAkB,MAAM3D,EAAAA,oBAAoB0C,CAAU,EAC5D,GAAIiB,EAAgB,SAAW,EAC7B,MAAO,CAAE,KAAM,UAAW,QAAS,6CAA8C,EAMnF,GAAI,CAAE,MAAMC,EAAWjE,EAAAA,QAAK,KAAK+C,EAAY,UAAU,CAAC,IAElD,MADoBzC,EAAAA,cAAcN,EAAAA,QAAK,KAAK+C,EAAY,YAAY,CAAC,EAAA,CAC3D,SAAW,EAAG,CAC1B,GAAI,UAAWc,EACb,MAAO,CACL,KAAM,UACN,QAAS,sDAAA,EAAqDtD,EAAAA,gBAAAA,CAAgBsD,EAAkB,KAAK,GACvG,EAEF,GAAI,CAACrD,EAAAA,uBAAuBqD,EAAkB,WAAW,EACvD,MAAO,CAAE,KAAM,UAAW,QAASpD,EAAAA,wBAAyB,CAEhE,CAGF,IAAMyD,EAAyB,MAAMxD,EAAAA,2BAA2BqC,CAAU,EAC1E,MAAO,CACL,KAAM,OACN,KAAM,CACJ,GAAGiB,EAAgB,IAAK,IAAyB,CAAE,aAAY,YAAW,YAAa,UAAW,EAAE,EACpG,GAAGE,EAAuB,IAAK,IAAyB,CAAE,aAAY,YAAW,YAAa,UAAW,EAAE,CAC7G,CACF,CACF,CAGA,eAAeJ,EAAwB,EAAoB,EAAoD,CAC7G,IAAMK,EAASxD,EAAAA,qBAAqB,UAAUyD,CAAc,EAU5D,OATID,EAAO,SAIF,MAH0BvD,EAAAA,QAC9B,QAAQZ,EAAAA,QAAK,KAAK+C,EAAY,gBAAiB,aAAa,EAAG,CAAE,UAAW,GAAM,cAAe,EAAK,CAAC,CAAC,CACxG,UAAY,CAAC,CAAC,EAAA,CACS,KAAM,GAAWsB,EAAO,OAAO,GAAKA,EAAO,KAAK,SAAS,KAAK,CAAC,EACrF,CAAE,KAAM,gBAAiB,EACzB,CAAE,KAAM,UAAW,QAAS,uDAAwD,EAGnF,CAAE,KAAM,UAAW,QAAS,kDADpBF,EAAO,MAAM,OAAO,IAAK,GAAU,GAAG,CAAC,iBAAkB,GAAGG,EAAM,IAAI,CAAC,CAAC,KAAK,GAAG,EAAE,IAAIA,EAAM,SACjB,CAAC,CAAC,KAAK,IAAI,GAAI,CAC3G,CAOA,eAAeV,EAAgB,EAAe,EAAmD,CAC/F,IAAIW,EACAC,EACJ,GAAI,EACD,gCAAiC,MAAM3D,EAAAA,8BAA8B6C,EAAI,UAAU,EACtF,OAASe,EAAO,CACd,OAAOC,EAAS,kEAAA,EAAiEnE,EAAAA,gBAAAA,CAAgBkE,CAAK,GAAG,CAC3G,CAEA,IAAIE,EACAC,EACJ,GAAI,CAWF,EAAuBC,EAAwBnB,EAAK,MAV/B5C,EAAAA,kBACnB,CAAC,MAAOyC,EAAc,QAASvD,EAAAA,QAAK,SAAS0D,EAAI,WAAYA,EAAI,SAAS,CAAC,EAC3E,CACE,IAAKc,EACL,IAAK,QAAQ,IACb,UAAW,IACX,eAAgB,SAChB,UACF,CACF,CAC0D,CAC5D,OAASC,EAAO,CACd,EAAuBC,EAAS,8BAAA,EAA6BnE,EAAAA,gBAAAA,CAAgBkE,CAAK,GAAG,CACvF,QAAU,CACR,EAAkB,MAAM1D,EAAAA,wBAAwBwD,CAAQ,CAC1D,CACA,GAAIK,EAAiB,OAAOD,EAC5B,IAAMG,EAAuB,0CAA0CP,EAAS,sDAChF,OAAOI,IAAyB,IAAA,GAC5BG,EACAJ,EAAS,GAAGC,EAAqB,IAAIG,GAAsB,CACjE,CAGA,SAASD,EAAwB,EAAe,EAAkD,CAChG,GAAM,CAAE,SAAQ,UAAWE,EAC3B,GAAIA,EAAO,gBAAkB,IAAA,GAAW,OAAOL,EAASK,EAAO,aAAa,EAC5E,GAAIA,EAAO,WAAa,EACtB,OAAOL,EAAS,uBAAuBK,EAAO,UAAY,aAAaC,EAAO,KAAK,EAAI,KAAKA,EAAO,KAAK,IAAM,IAAI,EAGpH,IAAMC,EAAcC,EAAO,MAAM,OAAO,CAAC,CAAC,OAAQ,GAASC,EAAK,WAAWnE,EAAAA,uBAAuB,CAAC,EAC7FoE,EAAkB,CAAC,EACzB,IAAK,IAAMD,KAAQF,EAAa,CAC9B,IAAII,EACJ,GAAI,CACF,EAAepE,EAAAA,qBAAqB,UAAU,KAAK,MAAMkE,EAAK,MAAMnE,EAAAA,wBAAwB,MAAM,CAAC,CAAC,CACtG,MAAQ,CACN,EAAe,IAAA,EACjB,CACA,GAAI,CAACqE,GAAc,QAAS,OAAOX,EAAS,oCAAoCS,GAAM,EACtFC,EAAgB,KAAKC,EAAa,IAAI,CACxC,CACA,GAAID,EAAgB,SAAW,EAAG,MAAO,oCAEzC,GAAI1B,EAAI,cAAgB,WAAY,CAClC,IAAM4B,EAAiBF,EAAgB,KAAM,GAAWL,EAAO,eAAiBhF,EAAAA,aAAa,QAAQ,EAMrG,OALIuF,EACKZ,EACL,GAAGzC,EAAkB,IAAIqD,EAAe,YAAY,GAAKA,EAAe,aAAa,gBAAgBA,EAAe,aAAaA,EAAe,QAAQ,KAAK,EAAI,KAAKA,EAAe,OAAO,KAAK,IAAM,IACzM,EAEF,MACF,CACA,OAAOF,EAAgB,MAAO,GAAWL,EAAO,eAAiBhF,EAAAA,aAAa,QAAQ,EAClF,4EACA,IAAA,EACN,CAEA,SAASuC,EAAe,EAAuC,CAC7D,IAAMD,EAAwB,CAC5B,QAAS,IAGT,YAAa,EACb,KAAM,CAAC,EACP,KAAM,CAAC,CACT,EACIkD,EAAa,GACjB,IAAK,IAAIC,EAAQ,EAAGA,EAAQjD,EAAK,OAAQ,IAAS,CAChD,IAAMkD,EAAMlD,EAAKiD,GACjB,GAAIC,IAAQ,IAAA,GAAW,MACvB,GAAIA,IAAQ,iBAAmBA,IAAQ,UAAYA,IAAQ,SAAU,CACnE,IAAMC,EAAQnD,EAAK,EAAEiD,GACrB,GAAIE,IAAU,IAAA,GAAW,MAAU,MAAM,GAAGD,EAAI,kBAAkB,EAClE,GAAIA,IAAQ,gBAEV,IADA,EAAQ,YAAc,OAAOC,CAAK,EAC9B,CAAC,OAAO,UAAUrD,EAAQ,WAAW,GAAKA,EAAQ,aAAe,EACnE,MAAU,MAAM,sDAAsDqD,GAAO,CAAA,MAEtED,IAAQ,SACjBpD,EAAQ,KAAK,KAAKqD,CAAK,EAEvBrD,EAAQ,KAAK,KAAKqD,CAAK,CAE3B,MAAO,GAAID,EAAI,WAAW,IAAI,EAC5B,MAAU,MAAM,mBAAmBA,GAAK,OACnC,GAAIF,EACT,MAAU,MAAM,0DAA0DlD,EAAQ,QAAQ,OAAOoD,GAAK,MAEtG,GAAQ,QAAUA,EAClB,EAAa,EAEjB,CACA,OAAOpD,CACT,CAGA,eAAeO,EAAgB,EAAoC,CACjE,IAAMC,EAAwB,CAAC,EAE/B,OADA,MAAM8C,EAAenD,EAASK,CAAW,EAClCA,EAAY,SAAS,CAC9B,CAEA,eAAe8C,EAAe,EAAa,EAAsC,CAG/E,IAAMC,EAAU,MAAMhF,EAAAA,QAAG,QAAQ8B,EAAK,CAAE,cAAe,EAAK,CAAC,EAC7D,GAAIkD,EAAQ,KAAM,GAAUC,EAAM,OAAO,IAAMA,EAAM,OAAS,cAAgBA,EAAM,KAAK,SAAS,aAAa,EAAE,EAC/GhD,EAAY,KAAKH,CAAG,OAGtB,IAAK,IAAMmD,KAASD,EACbC,EAAM,YAAY,GAAKA,EAAM,OAAS,gBAAkB,GAAM,KAAK,WAAW,GAAG,GACtF,MAAMF,EAAe3F,EAAAA,QAAK,KAAK0C,EAAKmD,EAAM,IAAI,EAAGhD,CAAW,CAEhE,CAEA,eAAeoB,EAAW,EAAoC,CAC5D,GAAI,CAEF,OADA,MAAMrD,EAAAA,QAAG,KAAKkF,CAAQ,EACf,EACT,MAAQ,CACN,MAAO,EACT,CACF,CAEA,SAASpB,EAAS,EAAsB,CACtC,OAAOqB,EAAK,QAAU/D,EAA4B+D,EAAO,GAAGA,EAAK,MAAM,EAAG/D,CAAyB,EAAE,IACvG,C"}
|
|
1
|
+
{"version":3,"file":"checkAllProblems.cjs","names":["DecisionCode","path","forEachConcurrently","readProblemMarkdownFrontMatter","findDefaultStdioHarnessFiles","formatDefaultHarnessError","findModelAnswerDirs","readTestCases","getErrorMessage","judgesWithoutTestCases","MISSING_TEST_CASES_ERROR","findFailingModelAnswerDirs","agentChallengeSchema","fs","copyProblemDirToTemporaryRoot","runHarnessProcess","forciblyRemoveDirectory","TEST_CASE_RESULT_PREFIX","testCaseResultSchema","require_runtime","require_testCaseResult","require_defaultStdioHarness","require_readTestCases","require_temporaryProblemDirCopy","require_schemas","require_readProblemMarkdownFrontMatter","require_decisionCode","require_findModelAnswerDirs","require_runHarnessProcess","require_runSingleHarness","node_path","node_fs_promises","_willbooster_shared_lib","MAX_FAILURE_DETAIL_LENGTH","decisionCodeNames","name","code","checkAllProblems","options","parseCheckArgs","args","rootDir","toRelative","dir","allProblemDirs","findProblemDirs","problemDirs","relativeDir","problemDir","substring","failures","runs","agentChallengeCount","plan","planProblemCheck","failure","cliEntryPath","passedCount","label","run","failureDetail","executeCheckRun","frontMatterResult","planAgentChallengeCheck","defaultHarnessFileNames","modelAnswerDirs","fileExists","failingModelAnswerDirs","parsed","agentChallenge","dirent","issue","tempRoot","copiedProblemDir","error","truncate","harnessFailureDetail","removedTempRoot","summarizeHarnessFailure","removalFailureDetail","result","stderr","resultLines","stdout","line","testCaseResults","parsedResult","rejectedResult","hasRootDir","index","arg","value","visitDirectory","entries","entry","filePath","text"],"sources":["../../src/cli/checkAllProblems.ts"],"sourcesContent":["import fs from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { forEachConcurrently, getErrorMessage } from '@willbooster/shared-lib';\n\nimport { findDefaultStdioHarnessFiles } from '../helpers/defaultStdioHarness.js';\nimport { findFailingModelAnswerDirs, findModelAnswerDirs } from '../helpers/findModelAnswerDirs.js';\nimport { judgesWithoutTestCases, readProblemMarkdownFrontMatter } from '../helpers/readProblemMarkdownFrontMatter.js';\nimport { readTestCases } from '../helpers/readTestCases.js';\nimport { type HarnessProcessResult, runHarnessProcess } from '../helpers/runHarnessProcess.js';\nimport { copyProblemDirToTemporaryRoot, forciblyRemoveDirectory } from '../helpers/temporaryProblemDirCopy.js';\nimport { agentChallengeSchema } from '../learningMaterial/schemas.js';\nimport { DecisionCode } from '../types/decisionCode.js';\nimport { TEST_CASE_RESULT_PREFIX, testCaseResultSchema } from '../types/testCaseResult.js';\nimport { formatDefaultHarnessError, MISSING_TEST_CASES_ERROR } from './runSingleHarness.js';\n\nconst RUN_TIMEOUT_MS = 600_000;\nconst MAX_RUN_OUTPUT_BYTES = 64 * 1024 * 1024;\nconst MAX_FAILURE_DETAIL_LENGTH = 1000;\n\n// DecisionCode is a plain const object (not a TypeScript enum), so it has no reverse mapping.\nconst decisionCodeNames = new Map<number, string>(Object.entries(DecisionCode).map(([name, code]) => [code, name]));\n\ninterface CheckOptions {\n rootDir: string;\n concurrency: number;\n only: string[];\n skip: string[];\n}\n\ninterface CheckRun {\n problemDir: string;\n answerDir: string;\n /** Whether all test cases must be accepted (`model_answers`) or at least one must fail (`model_answers.fails`). */\n expectation: 'accepted' | 'rejected';\n}\n\n/**\n * Judge all model answers of all problems (directories containing `problem.md` or\n * `<id>.problem.md`) under a root directory: `model_answers/*` must be fully accepted and\n * `model_answers.fails/*` must fail at least one test case. Agent challenges have no harness, so\n * they are not judged but fail when their `agentChallenge` is invalid or their explanation is missing.\n */\nexport async function checkAllProblems(args: readonly string[]): Promise<number> {\n const options = parseCheckArgs(args);\n const rootDir = path.resolve(options.rootDir);\n // Normalize to forward slashes so --only / --skip substrings like courses/foo match on Windows.\n const toRelative = (dir: string): string => (path.relative(rootDir, dir) || '.').replaceAll(path.sep, '/');\n\n const allProblemDirs = await findProblemDirs(rootDir);\n const problemDirs = allProblemDirs.filter((problemDir) => {\n const relativeDir = toRelative(problemDir);\n if (options.only.length > 0 && !options.only.some((substring) => relativeDir.includes(substring))) return false;\n return !options.skip.some((substring) => relativeDir.includes(substring));\n });\n if (problemDirs.length === 0) {\n console.error(\n allProblemDirs.length === 0\n ? `No problem directories (containing problem.md or <id>.problem.md) found under ${rootDir}.`\n : `All ${allProblemDirs.length} problem directories under ${rootDir} were excluded by --only/--skip.`\n );\n return 1;\n }\n\n const failures: string[] = [];\n const runs: CheckRun[] = [];\n let agentChallengeCount = 0;\n for (const problemDir of problemDirs) {\n const plan = await planProblemCheck(problemDir);\n if (plan.type === 'agentChallenge') {\n agentChallengeCount++;\n } else if (plan.type === 'failure') {\n failures.push(`${toRelative(problemDir)}: ${plan.message}`);\n } else {\n runs.push(...plan.runs);\n }\n }\n for (const failure of failures) console.error(`✗ ${failure}`);\n\n const cliEntryPath = path.resolve(process.argv[1] ?? '');\n let passedCount = 0;\n await forEachConcurrently(runs, options.concurrency, async (run) => {\n const label = `${toRelative(run.problemDir)} ${path.relative(run.problemDir, run.answerDir).replaceAll(path.sep, '/')}`;\n const failureDetail = await executeCheckRun(run, cliEntryPath);\n if (failureDetail === undefined) {\n passedCount++;\n console.info(`✓ ${label}`);\n } else {\n failures.push(`${label}: ${failureDetail}`);\n console.error(`✗ ${label}: ${failureDetail}`);\n }\n });\n\n console.info(\n `\\n${passedCount} passed, ${failures.length} failed (${runs.length} runs, ${problemDirs.length} problems${agentChallengeCount > 0 ? `, agent challenges skipped: ${agentChallengeCount}` : ''})`\n );\n return failures.length === 0 ? 0 : 1;\n}\n\ntype ProblemCheckPlan =\n | { type: 'agentChallenge' }\n | { type: 'failure'; message: string }\n | { type: 'runs'; runs: CheckRun[] };\n\nasync function planProblemCheck(problemDir: string): Promise<ProblemCheckPlan> {\n // As before agent challenges existed, a front matter that fails to parse fails the problem only\n // when the test-case check below needs the front matter.\n const frontMatterResult = await readProblemMarkdownFrontMatter(problemDir).then(\n (frontMatter) => ({ frontMatter }),\n (error: unknown) => ({ error })\n );\n if ('frontMatter' in frontMatterResult && frontMatterResult.frontMatter.agentChallenge !== undefined) {\n return planAgentChallengeCheck(problemDir, frontMatterResult.frontMatter.agentChallenge);\n }\n\n const defaultHarnessFileNames = await findDefaultStdioHarnessFiles(problemDir);\n if (defaultHarnessFileNames.length > 0) {\n return { type: 'failure', message: formatDefaultHarnessError(defaultHarnessFileNames) };\n }\n\n const modelAnswerDirs = await findModelAnswerDirs(problemDir);\n if (modelAnswerDirs.length === 0) {\n return { type: 'failure', message: 'no model answers found under model_answers/' };\n }\n\n // Without test cases, stdioJudgePreset prints a single accepted sentinel result, so a standard\n // problem with an empty or missing test_cases/ would otherwise pass without being judged —\n // unless static-analysis rules or manual scoring make the problem judgeable without them.\n if (!(await fileExists(path.join(problemDir, 'judge.ts')))) {\n const testCases = await readTestCases(path.join(problemDir, 'test_cases'));\n if (testCases.length === 0) {\n if ('error' in frontMatterResult) {\n return {\n type: 'failure',\n message: `failed to read the problem markdown front matter: ${getErrorMessage(frontMatterResult.error)}`,\n };\n }\n if (!judgesWithoutTestCases(frontMatterResult.frontMatter)) {\n return { type: 'failure', message: MISSING_TEST_CASES_ERROR };\n }\n }\n }\n\n const failingModelAnswerDirs = await findFailingModelAnswerDirs(problemDir);\n return {\n type: 'runs',\n runs: [\n ...modelAnswerDirs.map((answerDir): CheckRun => ({ problemDir, answerDir, expectation: 'accepted' })),\n ...failingModelAnswerDirs.map((answerDir): CheckRun => ({ problemDir, answerDir, expectation: 'rejected' })),\n ],\n };\n}\n\n/** Skips a valid agent challenge and fails an invalid one instead of judging it as a stdio problem. */\nasync function planAgentChallengeCheck(problemDir: string, agentChallenge: unknown): Promise<ProblemCheckPlan> {\n const parsed = agentChallengeSchema.safeParse(agentChallenge);\n if (parsed.success) {\n const explanationDirents = await fs\n .readdir(path.join(problemDir, 'model_answers', 'explanation'), { recursive: true, withFileTypes: true })\n .catch(() => []);\n return explanationDirents.some((dirent) => dirent.isFile() && dirent.name.endsWith('.md'))\n ? { type: 'agentChallenge' }\n : { type: 'failure', message: 'no explanation found under model_answers/explanation/' };\n }\n const issues = parsed.error.issues.map((issue) => `${['agentChallenge', ...issue.path].join('.')}: ${issue.message}`);\n return { type: 'failure', message: `invalid agentChallenge (see validate-problem): ${issues.join('; ')}` };\n}\n\n/**\n * Run one answer directory through the problem's harness and return a failure detail, if any.\n * The problem directory is copied to a temporary location first so that judging (e.g. build\n * artifacts in answer directories) never modifies the checked repository.\n */\nasync function executeCheckRun(run: CheckRun, cliEntryPath: string): Promise<string | undefined> {\n let tempRoot: string;\n let copiedProblemDir: string;\n try {\n ({ tempRoot, copiedProblemDir } = await copyProblemDirToTemporaryRoot(run.problemDir));\n } catch (error) {\n return truncate(`failed to copy the problem directory to a temporary location: ${getErrorMessage(error)}`);\n }\n\n let harnessFailureDetail;\n let removedTempRoot;\n try {\n const result = await runHarnessProcess(\n ['run', cliEntryPath, 'judge', path.relative(run.problemDir, run.answerDir)],\n {\n cwd: copiedProblemDir,\n env: process.env,\n timeoutMs: RUN_TIMEOUT_MS,\n maxOutputBytes: MAX_RUN_OUTPUT_BYTES,\n tempRoot,\n }\n );\n harnessFailureDetail = summarizeHarnessFailure(run, result);\n } catch (error) {\n harnessFailureDetail = truncate(`harness execution failed: ${getErrorMessage(error)}`);\n } finally {\n removedTempRoot = await forciblyRemoveDirectory(tempRoot);\n }\n if (removedTempRoot) return harnessFailureDetail;\n const removalFailureDetail = `failed to remove the temporary copy at ${tempRoot} (judged code may have left permission-locked files)`;\n return harnessFailureDetail === undefined\n ? removalFailureDetail\n : truncate(`${harnessFailureDetail}; ${removalFailureDetail}`);\n}\n\n/** Return why the harness run failed the check, or `undefined` when it passed. */\nfunction summarizeHarnessFailure(run: CheckRun, result: HarnessProcessResult): string | undefined {\n const { stdout, stderr } = result;\n if (result.failureReason !== undefined) return truncate(result.failureReason);\n if (result.exitCode !== 0) {\n return truncate(`harness exited with ${result.exitCode ?? 'a signal'}${stderr.trim() ? `: ${stderr.trim()}` : ''}`);\n }\n\n const resultLines = stdout.split(/\\r?\\n/).filter((line) => line.startsWith(TEST_CASE_RESULT_PREFIX));\n const testCaseResults = [];\n for (const line of resultLines) {\n let parsedResult;\n try {\n parsedResult = testCaseResultSchema.safeParse(JSON.parse(line.slice(TEST_CASE_RESULT_PREFIX.length)));\n } catch {\n parsedResult = undefined;\n }\n if (!parsedResult?.success) return truncate(`malformed test case result line: ${line}`);\n testCaseResults.push(parsedResult.data);\n }\n if (testCaseResults.length === 0) return 'no test case results were printed';\n\n if (run.expectation === 'accepted') {\n const rejectedResult = testCaseResults.find((result) => result.decisionCode !== DecisionCode.ACCEPTED);\n if (rejectedResult) {\n return truncate(\n `${decisionCodeNames.get(rejectedResult.decisionCode) ?? rejectedResult.decisionCode} on test case ${rejectedResult.testCaseId}${rejectedResult.stderr?.trim() ? `: ${rejectedResult.stderr.trim()}` : ''}`\n );\n }\n return undefined;\n }\n return testCaseResults.every((result) => result.decisionCode === DecisionCode.ACCEPTED)\n ? 'expected at least one failing test case, but all test cases were accepted'\n : undefined;\n}\n\nfunction parseCheckArgs(args: readonly string[]): CheckOptions {\n const options: CheckOptions = {\n rootDir: '.',\n // Serial by default: judging decides TIME_LIMIT_EXCEEDED from wall-clock time, so parallel\n // runs on a small CI runner could fail timing-sensitive problems non-deterministically.\n concurrency: 1,\n only: [],\n skip: [],\n };\n let hasRootDir = false;\n for (let index = 0; index < args.length; index++) {\n const arg = args[index];\n if (arg === undefined) break;\n if (arg === '--concurrency' || arg === '--only' || arg === '--skip') {\n const value = args[++index];\n if (value === undefined) throw new Error(`${arg} requires a value`);\n if (arg === '--concurrency') {\n options.concurrency = Number(value);\n if (!Number.isInteger(options.concurrency) || options.concurrency <= 0) {\n throw new Error(`--concurrency requires a positive integer, but got ${value}`);\n }\n } else if (arg === '--only') {\n options.only.push(value);\n } else {\n options.skip.push(value);\n }\n } else if (arg.startsWith('--')) {\n throw new Error(`Unknown option: ${arg}`);\n } else if (hasRootDir) {\n throw new Error(`Only one root directory can be specified, but got both ${options.rootDir} and ${arg}`);\n } else {\n options.rootDir = arg;\n hasRootDir = true;\n }\n }\n return options;\n}\n\n/** Find directories containing `problem.md` or `<id>.problem.md`, without descending into found problems. */\nasync function findProblemDirs(rootDir: string): Promise<string[]> {\n const problemDirs: string[] = [];\n await visitDirectory(rootDir, problemDirs);\n return problemDirs.toSorted();\n}\n\nasync function visitDirectory(dir: string, problemDirs: string[]): Promise<void> {\n // Traversal errors (e.g. an unreadable subtree) must fail the check: skipping them silently\n // could report a green result while covering only part of the repository.\n const entries = await fs.readdir(dir, { withFileTypes: true });\n if (entries.some((entry) => entry.isFile() && (entry.name === 'problem.md' || entry.name.endsWith('.problem.md')))) {\n problemDirs.push(dir);\n return;\n }\n for (const entry of entries) {\n if (!entry.isDirectory() || entry.name === 'node_modules' || entry.name.startsWith('.')) continue;\n await visitDirectory(path.join(dir, entry.name), problemDirs);\n }\n}\n\nasync function fileExists(filePath: string): Promise<boolean> {\n try {\n await fs.stat(filePath);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction truncate(text: string): string {\n return text.length <= MAX_FAILURE_DETAIL_LENGTH ? text : `${text.slice(0, MAX_FAILURE_DETAIL_LENGTH)}...`;\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checkAllProblems.js","names":["path","fs","MAX_FAILURE_DETAIL_LENGTH","decisionCodeNames","DecisionCode","name","code","checkAllProblems","options","parseCheckArgs","args","rootDir","toRelative","dir","allProblemDirs","findProblemDirs","problemDirs","relativeDir","problemDir","substring","failures","runs","agentChallengeCount","plan","planProblemCheck","failure","cliEntryPath","passedCount","forEachConcurrently","label","run","failureDetail","executeCheckRun","frontMatterResult","readProblemMarkdownFrontMatter","planAgentChallengeCheck","defaultHarnessFileNames","findDefaultStdioHarnessFiles","formatDefaultHarnessError","modelAnswerDirs","findModelAnswerDirs","fileExists","readTestCases","getErrorMessage","judgesWithoutTestCases","MISSING_TEST_CASES_ERROR","failingModelAnswerDirs","findFailingModelAnswerDirs","parsed","agentChallengeSchema","agentChallenge","dirent","issue","tempRoot","copiedProblemDir","copyProblemDirToTemporaryRoot","error","truncate","harnessFailureDetail","removedTempRoot","summarizeHarnessFailure","runHarnessProcess","forciblyRemoveDirectory","removalFailureDetail","result","stderr","resultLines","stdout","line","TEST_CASE_RESULT_PREFIX","testCaseResults","parsedResult","testCaseResultSchema","rejectedResult","hasRootDir","index","arg","value","visitDirectory","entries","entry","filePath","text"],"sources":["../../src/cli/checkAllProblems.ts"],"sourcesContent":["import fs from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { forEachConcurrently, getErrorMessage } from '@willbooster/shared-lib';\n\nimport { findDefaultStdioHarnessFiles } from '../helpers/defaultStdioHarness.js';\nimport { findFailingModelAnswerDirs, findModelAnswerDirs } from '../helpers/findModelAnswerDirs.js';\nimport { judgesWithoutTestCases, readProblemMarkdownFrontMatter } from '../helpers/readProblemMarkdownFrontMatter.js';\nimport { readTestCases } from '../helpers/readTestCases.js';\nimport { type HarnessProcessResult, runHarnessProcess } from '../helpers/runHarnessProcess.js';\nimport { copyProblemDirToTemporaryRoot, forciblyRemoveDirectory } from '../helpers/temporaryProblemDirCopy.js';\nimport { agentChallengeSchema } from '../learningMaterial/schemas.js';\nimport { DecisionCode } from '../types/decisionCode.js';\nimport { TEST_CASE_RESULT_PREFIX, testCaseResultSchema } from '../types/testCaseResult.js';\nimport { formatDefaultHarnessError, MISSING_TEST_CASES_ERROR } from './runSingleHarness.js';\n\nconst RUN_TIMEOUT_MS = 600_000;\nconst MAX_RUN_OUTPUT_BYTES = 64 * 1024 * 1024;\nconst MAX_FAILURE_DETAIL_LENGTH = 1000;\n\n// DecisionCode is a plain const object (not a TypeScript enum), so it has no reverse mapping.\nconst decisionCodeNames = new Map<number, string>(Object.entries(DecisionCode).map(([name, code]) => [code, name]));\n\ninterface CheckOptions {\n rootDir: string;\n concurrency: number;\n only: string[];\n skip: string[];\n}\n\ninterface CheckRun {\n problemDir: string;\n answerDir: string;\n /** Whether all test cases must be accepted (`model_answers`) or at least one must fail (`model_answers.fails`). */\n expectation: 'accepted' | 'rejected';\n}\n\n/**\n * Judge all model answers of all problems (directories containing `problem.md` or\n * `<id>.problem.md`) under a root directory: `model_answers/*` must be fully accepted and\n * `model_answers.fails/*` must fail at least one test case. Agent challenges have no harness, so\n * they are not judged but fail when their `agentChallenge` is invalid or their explanation is missing.\n */\nexport async function checkAllProblems(args: readonly string[]): Promise<number> {\n const options = parseCheckArgs(args);\n const rootDir = path.resolve(options.rootDir);\n // Normalize to forward slashes so --only / --skip substrings like courses/foo match on Windows.\n const toRelative = (dir: string): string => (path.relative(rootDir, dir) || '.').replaceAll(path.sep, '/');\n\n const allProblemDirs = await findProblemDirs(rootDir);\n const problemDirs = allProblemDirs.filter((problemDir) => {\n const relativeDir = toRelative(problemDir);\n if (options.only.length > 0 && !options.only.some((substring) => relativeDir.includes(substring))) return false;\n return !options.skip.some((substring) => relativeDir.includes(substring));\n });\n if (problemDirs.length === 0) {\n console.error(\n allProblemDirs.length === 0\n ? `No problem directories (containing problem.md or <id>.problem.md) found under ${rootDir}.`\n : `All ${allProblemDirs.length} problem directories under ${rootDir} were excluded by --only/--skip.`\n );\n return 1;\n }\n\n const failures: string[] = [];\n const runs: CheckRun[] = [];\n let agentChallengeCount = 0;\n for (const problemDir of problemDirs) {\n const plan = await planProblemCheck(problemDir);\n if (plan.type === 'agentChallenge') {\n agentChallengeCount++;\n } else if (plan.type === 'failure') {\n failures.push(`${toRelative(problemDir)}: ${plan.message}`);\n } else {\n runs.push(...plan.runs);\n }\n }\n for (const failure of failures) console.error(`✗ ${failure}`);\n\n const cliEntryPath = path.resolve(process.argv[1] ?? '');\n let passedCount = 0;\n await forEachConcurrently(runs, options.concurrency, async (run) => {\n const label = `${toRelative(run.problemDir)} ${path.relative(run.problemDir, run.answerDir).replaceAll(path.sep, '/')}`;\n const failureDetail = await executeCheckRun(run, cliEntryPath);\n if (failureDetail === undefined) {\n passedCount++;\n console.info(`✓ ${label}`);\n } else {\n failures.push(`${label}: ${failureDetail}`);\n console.error(`✗ ${label}: ${failureDetail}`);\n }\n });\n\n console.info(\n `\\n${passedCount} passed, ${failures.length} failed (${runs.length} runs, ${problemDirs.length} problems${agentChallengeCount > 0 ? `, agent challenges skipped: ${agentChallengeCount}` : ''})`\n );\n return failures.length === 0 ? 0 : 1;\n}\n\ntype ProblemCheckPlan =\n { type: 'agentChallenge' } | { type: 'failure'; message: string } | { type: 'runs'; runs: CheckRun[] };\n\nasync function planProblemCheck(problemDir: string): Promise<ProblemCheckPlan> {\n // As before agent challenges existed, a front matter that fails to parse fails the problem only\n // when the test-case check below needs the front matter.\n const frontMatterResult = await readProblemMarkdownFrontMatter(problemDir).then(\n (frontMatter) => ({ frontMatter }),\n (error: unknown) => ({ error })\n );\n if ('frontMatter' in frontMatterResult && frontMatterResult.frontMatter.agentChallenge !== undefined) {\n return planAgentChallengeCheck(problemDir, frontMatterResult.frontMatter.agentChallenge);\n }\n\n const defaultHarnessFileNames = await findDefaultStdioHarnessFiles(problemDir);\n if (defaultHarnessFileNames.length > 0) {\n return { type: 'failure', message: formatDefaultHarnessError(defaultHarnessFileNames) };\n }\n\n const modelAnswerDirs = await findModelAnswerDirs(problemDir);\n if (modelAnswerDirs.length === 0) {\n return { type: 'failure', message: 'no model answers found under model_answers/' };\n }\n\n // Without test cases, stdioJudgePreset prints a single accepted sentinel result, so a standard\n // problem with an empty or missing test_cases/ would otherwise pass without being judged —\n // unless static-analysis rules or manual scoring make the problem judgeable without them.\n if (!(await fileExists(path.join(problemDir, 'judge.ts')))) {\n const testCases = await readTestCases(path.join(problemDir, 'test_cases'));\n if (testCases.length === 0) {\n if ('error' in frontMatterResult) {\n return {\n type: 'failure',\n message: `failed to read the problem markdown front matter: ${getErrorMessage(frontMatterResult.error)}`,\n };\n }\n if (!judgesWithoutTestCases(frontMatterResult.frontMatter)) {\n return { type: 'failure', message: MISSING_TEST_CASES_ERROR };\n }\n }\n }\n\n const failingModelAnswerDirs = await findFailingModelAnswerDirs(problemDir);\n return {\n type: 'runs',\n runs: [\n ...modelAnswerDirs.map((answerDir): CheckRun => ({ problemDir, answerDir, expectation: 'accepted' })),\n ...failingModelAnswerDirs.map((answerDir): CheckRun => ({ problemDir, answerDir, expectation: 'rejected' })),\n ],\n };\n}\n\n/** Skips a valid agent challenge and fails an invalid one instead of judging it as a stdio problem. */\nasync function planAgentChallengeCheck(problemDir: string, agentChallenge: unknown): Promise<ProblemCheckPlan> {\n const parsed = agentChallengeSchema.safeParse(agentChallenge);\n if (parsed.success) {\n const explanationDirents = await fs\n .readdir(path.join(problemDir, 'model_answers', 'explanation'), { recursive: true, withFileTypes: true })\n .catch(() => []);\n return explanationDirents.some((dirent) => dirent.isFile() && dirent.name.endsWith('.md'))\n ? { type: 'agentChallenge' }\n : { type: 'failure', message: 'no explanation found under model_answers/explanation/' };\n }\n const issues = parsed.error.issues.map((issue) => `${['agentChallenge', ...issue.path].join('.')}: ${issue.message}`);\n return { type: 'failure', message: `invalid agentChallenge (see validate-problem): ${issues.join('; ')}` };\n}\n\n/**\n * Run one answer directory through the problem's harness and return a failure detail, if any.\n * The problem directory is copied to a temporary location first so that judging (e.g. build\n * artifacts in answer directories) never modifies the checked repository.\n */\nasync function executeCheckRun(run: CheckRun, cliEntryPath: string): Promise<string | undefined> {\n let tempRoot: string;\n let copiedProblemDir: string;\n try {\n ({ tempRoot, copiedProblemDir } = await copyProblemDirToTemporaryRoot(run.problemDir));\n } catch (error) {\n return truncate(`failed to copy the problem directory to a temporary location: ${getErrorMessage(error)}`);\n }\n\n let harnessFailureDetail;\n let removedTempRoot;\n try {\n const result = await runHarnessProcess(\n ['run', cliEntryPath, 'judge', path.relative(run.problemDir, run.answerDir)],\n {\n cwd: copiedProblemDir,\n env: process.env,\n timeoutMs: RUN_TIMEOUT_MS,\n maxOutputBytes: MAX_RUN_OUTPUT_BYTES,\n tempRoot,\n }\n );\n harnessFailureDetail = summarizeHarnessFailure(run, result);\n } catch (error) {\n harnessFailureDetail = truncate(`harness execution failed: ${getErrorMessage(error)}`);\n } finally {\n removedTempRoot = await forciblyRemoveDirectory(tempRoot);\n }\n if (removedTempRoot) return harnessFailureDetail;\n const removalFailureDetail = `failed to remove the temporary copy at ${tempRoot} (judged code may have left permission-locked files)`;\n return harnessFailureDetail === undefined\n ? removalFailureDetail\n : truncate(`${harnessFailureDetail}; ${removalFailureDetail}`);\n}\n\n/** Return why the harness run failed the check, or `undefined` when it passed. */\nfunction summarizeHarnessFailure(run: CheckRun, result: HarnessProcessResult): string | undefined {\n const { stdout, stderr } = result;\n if (result.failureReason !== undefined) return truncate(result.failureReason);\n if (result.exitCode !== 0) {\n return truncate(`harness exited with ${result.exitCode ?? 'a signal'}${stderr.trim() ? `: ${stderr.trim()}` : ''}`);\n }\n\n const resultLines = stdout.split(/\\r?\\n/).filter((line) => line.startsWith(TEST_CASE_RESULT_PREFIX));\n const testCaseResults = [];\n for (const line of resultLines) {\n let parsedResult;\n try {\n parsedResult = testCaseResultSchema.safeParse(JSON.parse(line.slice(TEST_CASE_RESULT_PREFIX.length)));\n } catch {\n parsedResult = undefined;\n }\n if (!parsedResult?.success) return truncate(`malformed test case result line: ${line}`);\n testCaseResults.push(parsedResult.data);\n }\n if (testCaseResults.length === 0) return 'no test case results were printed';\n\n if (run.expectation === 'accepted') {\n const rejectedResult = testCaseResults.find((result) => result.decisionCode !== DecisionCode.ACCEPTED);\n if (rejectedResult) {\n return truncate(\n `${decisionCodeNames.get(rejectedResult.decisionCode) ?? rejectedResult.decisionCode} on test case ${rejectedResult.testCaseId}${rejectedResult.stderr?.trim() ? `: ${rejectedResult.stderr.trim()}` : ''}`\n );\n }\n return undefined;\n }\n return testCaseResults.every((result) => result.decisionCode === DecisionCode.ACCEPTED)\n ? 'expected at least one failing test case, but all test cases were accepted'\n : undefined;\n}\n\nfunction parseCheckArgs(args: readonly string[]): CheckOptions {\n const options: CheckOptions = {\n rootDir: '.',\n // Serial by default: judging decides TIME_LIMIT_EXCEEDED from wall-clock time, so parallel\n // runs on a small CI runner could fail timing-sensitive problems non-deterministically.\n concurrency: 1,\n only: [],\n skip: [],\n };\n let hasRootDir = false;\n for (let index = 0; index < args.length; index++) {\n const arg = args[index];\n if (arg === undefined) break;\n if (arg === '--concurrency' || arg === '--only' || arg === '--skip') {\n const value = args[++index];\n if (value === undefined) throw new Error(`${arg} requires a value`);\n if (arg === '--concurrency') {\n options.concurrency = Number(value);\n if (!Number.isInteger(options.concurrency) || options.concurrency <= 0) {\n throw new Error(`--concurrency requires a positive integer, but got ${value}`);\n }\n } else if (arg === '--only') {\n options.only.push(value);\n } else {\n options.skip.push(value);\n }\n } else if (arg.startsWith('--')) {\n throw new Error(`Unknown option: ${arg}`);\n } else if (hasRootDir) {\n throw new Error(`Only one root directory can be specified, but got both ${options.rootDir} and ${arg}`);\n } else {\n options.rootDir = arg;\n hasRootDir = true;\n }\n }\n return options;\n}\n\n/** Find directories containing `problem.md` or `<id>.problem.md`, without descending into found problems. */\nasync function findProblemDirs(rootDir: string): Promise<string[]> {\n const problemDirs: string[] = [];\n await visitDirectory(rootDir, problemDirs);\n return problemDirs.toSorted();\n}\n\nasync function visitDirectory(dir: string, problemDirs: string[]): Promise<void> {\n // Traversal errors (e.g. an unreadable subtree) must fail the check: skipping them silently\n // could report a green result while covering only part of the repository.\n const entries = await fs.readdir(dir, { withFileTypes: true });\n if (entries.some((entry) => entry.isFile() && (entry.name === 'problem.md' || entry.name.endsWith('.problem.md')))) {\n problemDirs.push(dir);\n return;\n }\n for (const entry of entries) {\n if (!entry.isDirectory() || entry.name === 'node_modules' || entry.name.startsWith('.')) continue;\n await visitDirectory(path.join(dir, entry.name), problemDirs);\n }\n}\n\nasync function fileExists(filePath: string): Promise<boolean> {\n try {\n await fs.stat(filePath);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction truncate(text: string): string {\n return text.length <= MAX_FAILURE_DETAIL_LENGTH ? text : `${text.slice(0, MAX_FAILURE_DETAIL_LENGTH)}...`;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"checkAllProblems.js","names":["path","fs","MAX_FAILURE_DETAIL_LENGTH","decisionCodeNames","DecisionCode","name","code","checkAllProblems","options","parseCheckArgs","args","rootDir","toRelative","dir","allProblemDirs","findProblemDirs","problemDirs","relativeDir","problemDir","substring","failures","runs","agentChallengeCount","plan","planProblemCheck","failure","cliEntryPath","passedCount","forEachConcurrently","label","run","failureDetail","executeCheckRun","frontMatterResult","readProblemMarkdownFrontMatter","planAgentChallengeCheck","defaultHarnessFileNames","findDefaultStdioHarnessFiles","formatDefaultHarnessError","modelAnswerDirs","findModelAnswerDirs","fileExists","readTestCases","getErrorMessage","judgesWithoutTestCases","MISSING_TEST_CASES_ERROR","failingModelAnswerDirs","findFailingModelAnswerDirs","parsed","agentChallengeSchema","agentChallenge","dirent","issue","tempRoot","copiedProblemDir","copyProblemDirToTemporaryRoot","error","truncate","harnessFailureDetail","removedTempRoot","summarizeHarnessFailure","runHarnessProcess","forciblyRemoveDirectory","removalFailureDetail","result","stderr","resultLines","stdout","line","TEST_CASE_RESULT_PREFIX","testCaseResults","parsedResult","testCaseResultSchema","rejectedResult","hasRootDir","index","arg","value","visitDirectory","entries","entry","filePath","text"],"sources":["../../src/cli/checkAllProblems.ts"],"sourcesContent":["import fs from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { forEachConcurrently, getErrorMessage } from '@willbooster/shared-lib';\n\nimport { findDefaultStdioHarnessFiles } from '../helpers/defaultStdioHarness.js';\nimport { findFailingModelAnswerDirs, findModelAnswerDirs } from '../helpers/findModelAnswerDirs.js';\nimport { judgesWithoutTestCases, readProblemMarkdownFrontMatter } from '../helpers/readProblemMarkdownFrontMatter.js';\nimport { readTestCases } from '../helpers/readTestCases.js';\nimport { type HarnessProcessResult, runHarnessProcess } from '../helpers/runHarnessProcess.js';\nimport { copyProblemDirToTemporaryRoot, forciblyRemoveDirectory } from '../helpers/temporaryProblemDirCopy.js';\nimport { agentChallengeSchema } from '../learningMaterial/schemas.js';\nimport { DecisionCode } from '../types/decisionCode.js';\nimport { TEST_CASE_RESULT_PREFIX, testCaseResultSchema } from '../types/testCaseResult.js';\nimport { formatDefaultHarnessError, MISSING_TEST_CASES_ERROR } from './runSingleHarness.js';\n\nconst RUN_TIMEOUT_MS = 600_000;\nconst MAX_RUN_OUTPUT_BYTES = 64 * 1024 * 1024;\nconst MAX_FAILURE_DETAIL_LENGTH = 1000;\n\n// DecisionCode is a plain const object (not a TypeScript enum), so it has no reverse mapping.\nconst decisionCodeNames = new Map<number, string>(Object.entries(DecisionCode).map(([name, code]) => [code, name]));\n\ninterface CheckOptions {\n rootDir: string;\n concurrency: number;\n only: string[];\n skip: string[];\n}\n\ninterface CheckRun {\n problemDir: string;\n answerDir: string;\n /** Whether all test cases must be accepted (`model_answers`) or at least one must fail (`model_answers.fails`). */\n expectation: 'accepted' | 'rejected';\n}\n\n/**\n * Judge all model answers of all problems (directories containing `problem.md` or\n * `<id>.problem.md`) under a root directory: `model_answers/*` must be fully accepted and\n * `model_answers.fails/*` must fail at least one test case. Agent challenges have no harness, so\n * they are not judged but fail when their `agentChallenge` is invalid or their explanation is missing.\n */\nexport async function checkAllProblems(args: readonly string[]): Promise<number> {\n const options = parseCheckArgs(args);\n const rootDir = path.resolve(options.rootDir);\n // Normalize to forward slashes so --only / --skip substrings like courses/foo match on Windows.\n const toRelative = (dir: string): string => (path.relative(rootDir, dir) || '.').replaceAll(path.sep, '/');\n\n const allProblemDirs = await findProblemDirs(rootDir);\n const problemDirs = allProblemDirs.filter((problemDir) => {\n const relativeDir = toRelative(problemDir);\n if (options.only.length > 0 && !options.only.some((substring) => relativeDir.includes(substring))) return false;\n return !options.skip.some((substring) => relativeDir.includes(substring));\n });\n if (problemDirs.length === 0) {\n console.error(\n allProblemDirs.length === 0\n ? `No problem directories (containing problem.md or <id>.problem.md) found under ${rootDir}.`\n : `All ${allProblemDirs.length} problem directories under ${rootDir} were excluded by --only/--skip.`\n );\n return 1;\n }\n\n const failures: string[] = [];\n const runs: CheckRun[] = [];\n let agentChallengeCount = 0;\n for (const problemDir of problemDirs) {\n const plan = await planProblemCheck(problemDir);\n if (plan.type === 'agentChallenge') {\n agentChallengeCount++;\n } else if (plan.type === 'failure') {\n failures.push(`${toRelative(problemDir)}: ${plan.message}`);\n } else {\n runs.push(...plan.runs);\n }\n }\n for (const failure of failures) console.error(`✗ ${failure}`);\n\n const cliEntryPath = path.resolve(process.argv[1] ?? '');\n let passedCount = 0;\n await forEachConcurrently(runs, options.concurrency, async (run) => {\n const label = `${toRelative(run.problemDir)} ${path.relative(run.problemDir, run.answerDir).replaceAll(path.sep, '/')}`;\n const failureDetail = await executeCheckRun(run, cliEntryPath);\n if (failureDetail === undefined) {\n passedCount++;\n console.info(`✓ ${label}`);\n } else {\n failures.push(`${label}: ${failureDetail}`);\n console.error(`✗ ${label}: ${failureDetail}`);\n }\n });\n\n console.info(\n `\\n${passedCount} passed, ${failures.length} failed (${runs.length} runs, ${problemDirs.length} problems${agentChallengeCount > 0 ? `, agent challenges skipped: ${agentChallengeCount}` : ''})`\n );\n return failures.length === 0 ? 0 : 1;\n}\n\ntype ProblemCheckPlan =\n | { type: 'agentChallenge' }\n | { type: 'failure'; message: string }\n | { type: 'runs'; runs: CheckRun[] };\n\nasync function planProblemCheck(problemDir: string): Promise<ProblemCheckPlan> {\n // As before agent challenges existed, a front matter that fails to parse fails the problem only\n // when the test-case check below needs the front matter.\n const frontMatterResult = await readProblemMarkdownFrontMatter(problemDir).then(\n (frontMatter) => ({ frontMatter }),\n (error: unknown) => ({ error })\n );\n if ('frontMatter' in frontMatterResult && frontMatterResult.frontMatter.agentChallenge !== undefined) {\n return planAgentChallengeCheck(problemDir, frontMatterResult.frontMatter.agentChallenge);\n }\n\n const defaultHarnessFileNames = await findDefaultStdioHarnessFiles(problemDir);\n if (defaultHarnessFileNames.length > 0) {\n return { type: 'failure', message: formatDefaultHarnessError(defaultHarnessFileNames) };\n }\n\n const modelAnswerDirs = await findModelAnswerDirs(problemDir);\n if (modelAnswerDirs.length === 0) {\n return { type: 'failure', message: 'no model answers found under model_answers/' };\n }\n\n // Without test cases, stdioJudgePreset prints a single accepted sentinel result, so a standard\n // problem with an empty or missing test_cases/ would otherwise pass without being judged —\n // unless static-analysis rules or manual scoring make the problem judgeable without them.\n if (!(await fileExists(path.join(problemDir, 'judge.ts')))) {\n const testCases = await readTestCases(path.join(problemDir, 'test_cases'));\n if (testCases.length === 0) {\n if ('error' in frontMatterResult) {\n return {\n type: 'failure',\n message: `failed to read the problem markdown front matter: ${getErrorMessage(frontMatterResult.error)}`,\n };\n }\n if (!judgesWithoutTestCases(frontMatterResult.frontMatter)) {\n return { type: 'failure', message: MISSING_TEST_CASES_ERROR };\n }\n }\n }\n\n const failingModelAnswerDirs = await findFailingModelAnswerDirs(problemDir);\n return {\n type: 'runs',\n runs: [\n ...modelAnswerDirs.map((answerDir): CheckRun => ({ problemDir, answerDir, expectation: 'accepted' })),\n ...failingModelAnswerDirs.map((answerDir): CheckRun => ({ problemDir, answerDir, expectation: 'rejected' })),\n ],\n };\n}\n\n/** Skips a valid agent challenge and fails an invalid one instead of judging it as a stdio problem. */\nasync function planAgentChallengeCheck(problemDir: string, agentChallenge: unknown): Promise<ProblemCheckPlan> {\n const parsed = agentChallengeSchema.safeParse(agentChallenge);\n if (parsed.success) {\n const explanationDirents = await fs\n .readdir(path.join(problemDir, 'model_answers', 'explanation'), { recursive: true, withFileTypes: true })\n .catch(() => []);\n return explanationDirents.some((dirent) => dirent.isFile() && dirent.name.endsWith('.md'))\n ? { type: 'agentChallenge' }\n : { type: 'failure', message: 'no explanation found under model_answers/explanation/' };\n }\n const issues = parsed.error.issues.map((issue) => `${['agentChallenge', ...issue.path].join('.')}: ${issue.message}`);\n return { type: 'failure', message: `invalid agentChallenge (see validate-problem): ${issues.join('; ')}` };\n}\n\n/**\n * Run one answer directory through the problem's harness and return a failure detail, if any.\n * The problem directory is copied to a temporary location first so that judging (e.g. build\n * artifacts in answer directories) never modifies the checked repository.\n */\nasync function executeCheckRun(run: CheckRun, cliEntryPath: string): Promise<string | undefined> {\n let tempRoot: string;\n let copiedProblemDir: string;\n try {\n ({ tempRoot, copiedProblemDir } = await copyProblemDirToTemporaryRoot(run.problemDir));\n } catch (error) {\n return truncate(`failed to copy the problem directory to a temporary location: ${getErrorMessage(error)}`);\n }\n\n let harnessFailureDetail;\n let removedTempRoot;\n try {\n const result = await runHarnessProcess(\n ['run', cliEntryPath, 'judge', path.relative(run.problemDir, run.answerDir)],\n {\n cwd: copiedProblemDir,\n env: process.env,\n timeoutMs: RUN_TIMEOUT_MS,\n maxOutputBytes: MAX_RUN_OUTPUT_BYTES,\n tempRoot,\n }\n );\n harnessFailureDetail = summarizeHarnessFailure(run, result);\n } catch (error) {\n harnessFailureDetail = truncate(`harness execution failed: ${getErrorMessage(error)}`);\n } finally {\n removedTempRoot = await forciblyRemoveDirectory(tempRoot);\n }\n if (removedTempRoot) return harnessFailureDetail;\n const removalFailureDetail = `failed to remove the temporary copy at ${tempRoot} (judged code may have left permission-locked files)`;\n return harnessFailureDetail === undefined\n ? removalFailureDetail\n : truncate(`${harnessFailureDetail}; ${removalFailureDetail}`);\n}\n\n/** Return why the harness run failed the check, or `undefined` when it passed. */\nfunction summarizeHarnessFailure(run: CheckRun, result: HarnessProcessResult): string | undefined {\n const { stdout, stderr } = result;\n if (result.failureReason !== undefined) return truncate(result.failureReason);\n if (result.exitCode !== 0) {\n return truncate(`harness exited with ${result.exitCode ?? 'a signal'}${stderr.trim() ? `: ${stderr.trim()}` : ''}`);\n }\n\n const resultLines = stdout.split(/\\r?\\n/).filter((line) => line.startsWith(TEST_CASE_RESULT_PREFIX));\n const testCaseResults = [];\n for (const line of resultLines) {\n let parsedResult;\n try {\n parsedResult = testCaseResultSchema.safeParse(JSON.parse(line.slice(TEST_CASE_RESULT_PREFIX.length)));\n } catch {\n parsedResult = undefined;\n }\n if (!parsedResult?.success) return truncate(`malformed test case result line: ${line}`);\n testCaseResults.push(parsedResult.data);\n }\n if (testCaseResults.length === 0) return 'no test case results were printed';\n\n if (run.expectation === 'accepted') {\n const rejectedResult = testCaseResults.find((result) => result.decisionCode !== DecisionCode.ACCEPTED);\n if (rejectedResult) {\n return truncate(\n `${decisionCodeNames.get(rejectedResult.decisionCode) ?? rejectedResult.decisionCode} on test case ${rejectedResult.testCaseId}${rejectedResult.stderr?.trim() ? `: ${rejectedResult.stderr.trim()}` : ''}`\n );\n }\n return undefined;\n }\n return testCaseResults.every((result) => result.decisionCode === DecisionCode.ACCEPTED)\n ? 'expected at least one failing test case, but all test cases were accepted'\n : undefined;\n}\n\nfunction parseCheckArgs(args: readonly string[]): CheckOptions {\n const options: CheckOptions = {\n rootDir: '.',\n // Serial by default: judging decides TIME_LIMIT_EXCEEDED from wall-clock time, so parallel\n // runs on a small CI runner could fail timing-sensitive problems non-deterministically.\n concurrency: 1,\n only: [],\n skip: [],\n };\n let hasRootDir = false;\n for (let index = 0; index < args.length; index++) {\n const arg = args[index];\n if (arg === undefined) break;\n if (arg === '--concurrency' || arg === '--only' || arg === '--skip') {\n const value = args[++index];\n if (value === undefined) throw new Error(`${arg} requires a value`);\n if (arg === '--concurrency') {\n options.concurrency = Number(value);\n if (!Number.isInteger(options.concurrency) || options.concurrency <= 0) {\n throw new Error(`--concurrency requires a positive integer, but got ${value}`);\n }\n } else if (arg === '--only') {\n options.only.push(value);\n } else {\n options.skip.push(value);\n }\n } else if (arg.startsWith('--')) {\n throw new Error(`Unknown option: ${arg}`);\n } else if (hasRootDir) {\n throw new Error(`Only one root directory can be specified, but got both ${options.rootDir} and ${arg}`);\n } else {\n options.rootDir = arg;\n hasRootDir = true;\n }\n }\n return options;\n}\n\n/** Find directories containing `problem.md` or `<id>.problem.md`, without descending into found problems. */\nasync function findProblemDirs(rootDir: string): Promise<string[]> {\n const problemDirs: string[] = [];\n await visitDirectory(rootDir, problemDirs);\n return problemDirs.toSorted();\n}\n\nasync function visitDirectory(dir: string, problemDirs: string[]): Promise<void> {\n // Traversal errors (e.g. an unreadable subtree) must fail the check: skipping them silently\n // could report a green result while covering only part of the repository.\n const entries = await fs.readdir(dir, { withFileTypes: true });\n if (entries.some((entry) => entry.isFile() && (entry.name === 'problem.md' || entry.name.endsWith('.problem.md')))) {\n problemDirs.push(dir);\n return;\n }\n for (const entry of entries) {\n if (!entry.isDirectory() || entry.name === 'node_modules' || entry.name.startsWith('.')) continue;\n await visitDirectory(path.join(dir, entry.name), problemDirs);\n }\n}\n\nasync function fileExists(filePath: string): Promise<boolean> {\n try {\n await fs.stat(filePath);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction truncate(text: string): string {\n return text.length <= MAX_FAILURE_DETAIL_LENGTH ? text : `${text.slice(0, MAX_FAILURE_DETAIL_LENGTH)}...`;\n}\n"],"mappings":"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"}
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./helpers/compareStdoutAsSpaceSeparatedTokens.cjs"),t=require("./types/testCaseResult.cjs"),n=require("./helpers/printTestCaseResult.cjs"),r=require("./helpers/compareExpectedOutputFiles.cjs"),i=require("./helpers/defaultStdioHarness.cjs"),a=require("./helpers/parseArgs.cjs"),o=require("./helpers/parseCsv.cjs"),s=require("./helpers/readTestCases.cjs"),c=require("./helpers/removeCommentsInSourceCode.cjs"),l=require("./helpers/runCommandInTemporaryPackageManagerProject.cjs"),u=require("./helpers/runPythonInTemporaryUvProject.cjs"),d=require("./helpers/sourceCodeGrammars.cjs"),f=require("./helpers/startHttpServer.cjs"),p=require("./types/problem.cjs"),m=require("./learningMaterial/frontmatter.cjs"),h=require("./learningMaterial/validationResult.cjs"),g=require("./learningMaterial/validateMaterial.cjs"),_=require("./learningMaterial/validateContest.cjs"),v=require("./learningMaterial/validateCourse.cjs"),y=require("./learningMaterial/validateProblem.cjs"),b=require("./types/decisionCode.cjs"),x=require("./helpers/guiRecording.cjs");exports.CsvParseError=o.CsvParseError,exports.DecisionCode=b.DecisionCode,exports.HARNESS_FILE_PRESETS=i.HARNESS_FILE_PRESETS,exports.MAX_COMPARED_FILE_BYTES=r.MAX_COMPARED_FILE_BYTES,exports.MAX_GUI_RECORDING_BYTES=x.MAX_GUI_RECORDING_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=h.formatValidationReport,exports.formatZodIssues=h.formatZodIssues,exports.isDefaultStdioHarnessSource=i.isDefaultStdioHarnessSource,exports.judgeAgainstExpectations=r.judgeAgainstExpectations,exports.languageIdToSourceCodeGrammar=d.languageIdToSourceCodeGrammar,exports.mergeComparisonOutputFiles=r.mergeComparisonOutputFiles,exports.mergeSubmissionPeriods=g.mergeSubmissionPeriods,exports.normalizeCodeRule=p.normalizeCodeRule,exports.parseArgs=a.parseArgs,exports.parseCsv=o.parseCsv,exports.parseCsvRecords=o.parseCsvRecords,exports.parseFrontmatter=m.parseFrontmatter,exports.printTestCaseResult=n.printTestCaseResult,exports.problemMarkdownFrontMatterSchema=p.problemMarkdownFrontMatterSchema,exports.readTestCases=s.readTestCases,exports.removeCommentsAndStringsInSourceCode=c.removeCommentsAndStringsInSourceCode,exports.removeCommentsInSourceCode=c.removeCommentsInSourceCode,exports.reportDuplicateIds=h.reportDuplicateIds,exports.reportExcessChatMarkers=g.reportExcessChatMarkers,exports.
|
|
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/frontmatter.cjs"),h=require("./learningMaterial/validationResult.cjs"),g=require("./learningMaterial/validateMaterial.cjs"),_=require("./learningMaterial/validateContest.cjs"),v=require("./learningMaterial/validateCourse.cjs"),y=require("./learningMaterial/validateProblem.cjs"),b=require("./types/decisionCode.cjs"),x=require("./helpers/guiRecording.cjs");exports.CsvParseError=o.CsvParseError,exports.DecisionCode=b.DecisionCode,exports.HARNESS_FILE_PRESETS=i.HARNESS_FILE_PRESETS,exports.MAX_COMPARED_FILE_BYTES=r.MAX_COMPARED_FILE_BYTES,exports.MAX_GUI_RECORDING_BYTES=x.MAX_GUI_RECORDING_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=h.formatValidationReport,exports.formatZodIssues=h.formatZodIssues,exports.isDefaultStdioHarnessSource=i.isDefaultStdioHarnessSource,exports.judgeAgainstExpectations=r.judgeAgainstExpectations,exports.languageIdToSourceCodeGrammar=d.languageIdToSourceCodeGrammar,exports.mergeComparisonOutputFiles=r.mergeComparisonOutputFiles,exports.mergeSubmissionPeriods=g.mergeSubmissionPeriods,exports.normalizeCodeRule=p.normalizeCodeRule,exports.parseArgs=a.parseArgs,exports.parseCsv=o.parseCsv,exports.parseCsvRecords=o.parseCsvRecords,exports.parseFrontmatter=m.parseFrontmatter,exports.printTestCaseResult=n.printTestCaseResult,exports.problemMarkdownFrontMatterSchema=p.problemMarkdownFrontMatterSchema,exports.readTestCases=s.readTestCases,exports.removeCommentsAndStringsInSourceCode=c.removeCommentsAndStringsInSourceCode,exports.removeCommentsInSourceCode=c.removeCommentsInSourceCode,exports.reportDuplicateIds=h.reportDuplicateIds,exports.reportExcessChatMarkers=g.reportExcessChatMarkers,exports.runCommandInTemporaryPackageManagerProject=l.runCommandInTemporaryPackageManagerProject,exports.runPythonInTemporaryUvProject=u.runPythonInTemporaryUvProject,exports.startHttpServer=f.startHttpServer,exports.startLocalHttpServer=f.startLocalHttpServer,exports.testCaseResultSchema=t.testCaseResultSchema,exports.validateContestFile=_.validateContestFile,exports.validateCourseDirectory=v.validateCourseDirectory,exports.validateMaterialFile=g.validateMaterialFile,exports.validateProblemDirectory=y.validateProblemDirectory,exports.validateSubmissionPeriods=g.validateSubmissionPeriods;
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{compareStdoutAsSpaceSeparatedTokens as e}from"./helpers/compareStdoutAsSpaceSeparatedTokens.js";import{TEST_CASE_RESULT_PREFIX as t,testCaseResultSchema as n}from"./types/testCaseResult.js";import{encodeFileForTestCaseResult as r,printTestCaseResult as i}from"./helpers/printTestCaseResult.js";import{MAX_COMPARED_FILE_BYTES as a,MAX_REPORTED_FILE_BYTES as o,compareExpectedOutputFiles as s,judgeAgainstExpectations as c,mergeComparisonOutputFiles as l}from"./helpers/compareExpectedOutputFiles.js";import{HARNESS_FILE_PRESETS as u,findDefaultStdioHarnessFiles as d,isDefaultStdioHarnessSource as f}from"./helpers/defaultStdioHarness.js";import{parseArgs as p}from"./helpers/parseArgs.js";import{CsvParseError as m,parseCsv as h,parseCsvRecords as g}from"./helpers/parseCsv.js";import{readTestCases as _}from"./helpers/readTestCases.js";import{removeCommentsAndStringsInSourceCode as v,removeCommentsInSourceCode as y}from"./helpers/removeCommentsInSourceCode.js";import{copyPackageManagerProjectFiles as b,runCommandInTemporaryPackageManagerProject as x}from"./helpers/runCommandInTemporaryPackageManagerProject.js";import{runPythonInTemporaryUvProject as S}from"./helpers/runPythonInTemporaryUvProject.js";import{languageIdToSourceCodeGrammar as C}from"./helpers/sourceCodeGrammars.js";import{startHttpServer as w,startLocalHttpServer as T}from"./helpers/startHttpServer.js";import{codeRuleSchema as E,normalizeCodeRule as D,problemMarkdownFrontMatterSchema as O}from"./types/problem.js";import{parseFrontmatter as k}from"./learningMaterial/frontmatter.js";import{formatValidationReport as A,formatZodIssues as j,reportDuplicateIds as M}from"./learningMaterial/validationResult.js";import{mergeSubmissionPeriods as N,reportExcessChatMarkers as P,
|
|
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,startLocalHttpServer as T}from"./helpers/startHttpServer.js";import{codeRuleSchema as E,normalizeCodeRule as D,problemMarkdownFrontMatterSchema as O}from"./types/problem.js";import{parseFrontmatter as k}from"./learningMaterial/frontmatter.js";import{formatValidationReport as A,formatZodIssues as j,reportDuplicateIds as M}from"./learningMaterial/validationResult.js";import{mergeSubmissionPeriods as N,reportExcessChatMarkers as P,validateMaterialFile as F,validateSubmissionPeriods as I}from"./learningMaterial/validateMaterial.js";import{validateContestFile as L}from"./learningMaterial/validateContest.js";import{validateCourseDirectory as R}from"./learningMaterial/validateCourse.js";import{validateProblemDirectory as z}from"./learningMaterial/validateProblem.js";import{DecisionCode as B}from"./types/decisionCode.js";import{MAX_GUI_RECORDING_BYTES as V}from"./helpers/guiRecording.js";export{m as CsvParseError,B as DecisionCode,u as HARNESS_FILE_PRESETS,a as MAX_COMPARED_FILE_BYTES,V as MAX_GUI_RECORDING_BYTES,o as MAX_REPORTED_FILE_BYTES,t as TEST_CASE_RESULT_PREFIX,E as codeRuleSchema,s as compareExpectedOutputFiles,e as compareStdoutAsSpaceSeparatedTokens,b as copyPackageManagerProjectFiles,r as encodeFileForTestCaseResult,d as findDefaultStdioHarnessFiles,A as formatValidationReport,j as formatZodIssues,f as isDefaultStdioHarnessSource,c as judgeAgainstExpectations,C as languageIdToSourceCodeGrammar,l as mergeComparisonOutputFiles,N as mergeSubmissionPeriods,D as normalizeCodeRule,p as parseArgs,h as parseCsv,g as parseCsvRecords,k as parseFrontmatter,i as printTestCaseResult,O as problemMarkdownFrontMatterSchema,_ as readTestCases,v as removeCommentsAndStringsInSourceCode,y as removeCommentsInSourceCode,M as reportDuplicateIds,P as reportExcessChatMarkers,x as runCommandInTemporaryPackageManagerProject,S as runPythonInTemporaryUvProject,w as startHttpServer,T as startLocalHttpServer,n as testCaseResultSchema,L as validateContestFile,R as validateCourseDirectory,F as validateMaterialFile,z as validateProblemDirectory,I as validateSubmissionPeriods};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";const e=require("../types/language.cjs"),t=require("../types/problem.cjs");let n=require("zod"),r=require("@willbooster/shared-lib");const i=/^[0-9_a-z-]+$/,a=n.z.string().regex(i),o=Object.keys(e.languageIdToDefinition),s=n.z.enum(o),c=n.z.string().regex(/^\w+$/),l=n.z.record(n.z.string(),n.z.unknown()),u=n.z.number().int().min(100).max(599),d=n.z.string().startsWith(`/`).refine(e=>!e.includes(`?`)&&!e.includes(`#`),`path must not carry a query or fragment; use query`).refine(e=>e.split(`/`).every(e=>!/[{}]/.test(e)||/^\{[^{}]+\}$/.test(e)),`a {name} placeholder must be one whole path segment`),f=l.refine(e=>k(e),`header names in properties and required must be lower case`),p=n.z.strictObject({playgrounds:n.z.array(n.z.enum([`bbs`,`backlog`,`hosting`])).min(1),expectedRequest:n.z.strictObject({method:n.z.enum([`GET`,`POST`,`PUT`,`PATCH`,`DELETE`]),path:d,status:n.z.union([u,n.z.array(u).min(1)]).optional(),query:l.optional(),headers:f.optional(),body:l.optional()})}),m=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(c).default([]),requiredOutputFilePaths:n.z.string().min(1).array().default([]),requiredSubmissionFilePaths:n.z.string().min(1).array().default([]),agentChallenge:p.optional()}),h={availableLanguageIds:s.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()},g=n.z.strictObject({...h,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:a,name:n.z.string().min(1),description:n.z.string()})).optional()}),_=n.z.string().array().min(1).refine(e=>e.length===new Set(e).size,{error:`options must contain unique values`}),v=n.z.string().regex(/^\d+$/,{error:`must be a non-negative integer index (quoted numbers such as "0" are accepted)`}).transform(Number),y={id:a,isResubmittable:n.z.boolean().optional(),isSurvey:n.z.boolean().optional(),explanation:n.z.string().optional(),hint:n.z.string().optional()},b={...y,type:n.z.literal(`select`),options:_,answerIndex:n.z.union([n.z.number().int().nonnegative(),v,n.z.number().int().nonnegative().array(),v.array()]).optional()},x={...y,type:n.z.literal(`select_multiple`),options:_,answerIndices:n.z.union([n.z.number().int().nonnegative().array(),v.array()]).optional()},S={...y,type:n.z.literal(`text`),answerPattern:n.z.string().min(1).optional(),modelAnswer:n.z.string().optional()},C=n.z.discriminatedUnion(`type`,[n.z.strictObject({...b,question:n.z.string()}),n.z.strictObject({...x,question:n.z.string()}),n.z.strictObject({...S,question:n.z.string()})]),w=n.z.discriminatedUnion(`type`,[n.z.strictObject({...b,question:n.z.string().optional()}),n.z.strictObject({...x,question:n.z.string().optional()}),n.z.strictObject({...S,question:n.z.string().optional()})]),T=n.z.strictObject({...h,name:n.z.string().min(1),problems:n.z.
|
|
1
|
+
"use strict";const e=require("../types/language.cjs"),t=require("../types/problem.cjs");let n=require("zod"),r=require("@willbooster/shared-lib");const i=/^[0-9_a-z-]+$/,a=n.z.string().regex(i),o=Object.keys(e.languageIdToDefinition),s=n.z.enum(o),c=n.z.string().regex(/^\w+$/),l=n.z.record(n.z.string(),n.z.unknown()),u=n.z.number().int().min(100).max(599),d=n.z.string().startsWith(`/`).refine(e=>!e.includes(`?`)&&!e.includes(`#`),`path must not carry a query or fragment; use query`).refine(e=>e.split(`/`).every(e=>!/[{}]/.test(e)||/^\{[^{}]+\}$/.test(e)),`a {name} placeholder must be one whole path segment`),f=l.refine(e=>k(e),`header names in properties and required must be lower case`),p=n.z.strictObject({playgrounds:n.z.array(n.z.enum([`bbs`,`backlog`,`hosting`])).min(1),expectedRequest:n.z.strictObject({method:n.z.enum([`GET`,`POST`,`PUT`,`PATCH`,`DELETE`]),path:d,status:n.z.union([u,n.z.array(u).min(1)]).optional(),query:l.optional(),headers:f.optional(),body:l.optional()})}),m=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(c).default([]),requiredOutputFilePaths:n.z.string().min(1).array().default([]),requiredSubmissionFilePaths:n.z.string().min(1).array().default([]),agentChallenge:p.optional()}),h={availableLanguageIds:s.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()},g=n.z.strictObject({...h,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:a,name:n.z.string().min(1),description:n.z.string()})).optional()}),_=n.z.string().array().min(1).refine(e=>e.length===new Set(e).size,{error:`options must contain unique values`}),v=n.z.string().regex(/^\d+$/,{error:`must be a non-negative integer index (quoted numbers such as "0" are accepted)`}).transform(Number),y={id:a,isResubmittable:n.z.boolean().optional(),isSurvey:n.z.boolean().optional(),explanation:n.z.string().optional(),hint:n.z.string().optional()},b={...y,type:n.z.literal(`select`),options:_,answerIndex:n.z.union([n.z.number().int().nonnegative(),v,n.z.number().int().nonnegative().array(),v.array()]).optional()},x={...y,type:n.z.literal(`select_multiple`),options:_,answerIndices:n.z.union([n.z.number().int().nonnegative().array(),v.array()]).optional()},S={...y,type:n.z.literal(`text`),answerPattern:n.z.string().min(1).optional(),modelAnswer:n.z.string().optional()},C=n.z.discriminatedUnion(`type`,[n.z.strictObject({...b,question:n.z.string()}),n.z.strictObject({...x,question:n.z.string()}),n.z.strictObject({...S,question:n.z.string()})]),w=n.z.discriminatedUnion(`type`,[n.z.strictObject({...b,question:n.z.string().optional()}),n.z.strictObject({...x,question:n.z.string().optional()}),n.z.strictObject({...S,question:n.z.string().optional()})]),T=n.z.strictObject({...h,name:n.z.string().min(1),problems:n.z.strictObject({id:a}).array().optional(),questions:n.z.array(w).optional(),turtleGraphicsQuestions:n.z.object({id:a}).array().optional(),isExamination:n.z.boolean().optional(),isMockExamination:n.z.boolean().optional(),isRealtimeSurvey:n.z.boolean().optional()}),E=n.z.strictObject({id:a,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`}),D=n.z.strictObject({id:a,score:n.z.number().int().nonnegative(),areTestCasesHidden:h.areTestCasesHidden,isDebugHintDisabled:h.isDebugHintDisabled,isFixHintDisabled:h.isFixHintDisabled,isDiffHintDisabled:h.isDiffHintDisabled,debugHintWaitingSeconds:h.debugHintWaitingSeconds,fixHintWaitingSeconds:h.fixHintWaitingSeconds,diffHintWaitingSeconds:h.diffHintWaitingSeconds,isMaterialChatDisabled:h.isMaterialChatDisabled}),O=n.z.strictObject({...h,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(E).min(1),problems:n.z.array(D).min(1)});function k(e,t=/* @__PURE__ */ new WeakSet){if(typeof e!=`object`||!e||t.has(e))return!0;if(t.add(e),Array.isArray(e))return e.every(e=>k(e,t));if(!(0,r.isRecord)(e))return!0;let n=(0,r.isRecord)(e.properties)?Object.keys(e.properties):[],i=Array.isArray(e.required)?e.required:[];return[...n,...i].every(e=>typeof e!=`string`||e===e.toLowerCase())&&Object.values(e).every(e=>k(e,t))}exports.CONTEST_MATERIAL_FILE_SUFFIX=`.contest.yaml`,exports.LEARNING_MATERIAL_ID_REGEX=i,exports.agentChallengeSchema=p,exports.availableLanguageIds=o,exports.contestFileSchema=O,exports.courseFileSchema=g,exports.learningMaterialIdSchema=a,exports.materialFrontmatterSchema=T,exports.problemFrontmatterSchema=m,exports.questionInCodeBlockSchema=C;
|
|
2
2
|
//# sourceMappingURL=schemas.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schemas.cjs","names":["z","languageIdToDefinition","codeRuleSchema","isRecord","require_language","require_problem","zod","_willbooster_shared_lib","LEARNING_MATERIAL_ID_REGEX","learningMaterialIdSchema","availableLanguageIds","availableLanguageIdSchema","environmentVariableNameSchema","jsonSchemaSchema","httpStatusSchema","pathTemplateSchema","path","segment","headersJsonSchemaSchema","declaresOnlyLowerCaseNames","schema","agentChallengeSchema","problemFrontmatterSchema","materialConfigShape","courseFileSchema","uniqueOptionsSchema","options","answerIndexStringSchema","questionBaseShape","selectQuestionShape","selectMultipleQuestionShape","textQuestionShape","questionInCodeBlockSchema","questionInFrontmatterSchema","materialFrontmatterSchema","contestDivisionSchema","division","contestProblemSchema","contestFileSchema","visited","item","propertyNames","requiredNames","name","value"],"sources":["../../src/learningMaterial/schemas.ts"],"sourcesContent":["import { isRecord } from '@willbooster/shared-lib';\nimport { z } from 'zod';\n\nimport { languageIdToDefinition } from '../types/language.js';\nimport { codeRuleSchema } from '../types/problem.js';\n\n// Schemas in this file mirror the Exercode judge importer so that generated materials validate\n// exactly like the importer would parse them.\n\nexport const LEARNING_MATERIAL_ID_REGEX = /^[0-9_a-z-]+$/;\n\nexport const learningMaterialIdSchema = z.string().regex(LEARNING_MATERIAL_ID_REGEX);\n\nexport const availableLanguageIds = Object.keys(languageIdToDefinition);\nconst availableLanguageIdSchema = z.enum(availableLanguageIds);\n\nconst environmentVariableNameSchema = z.string().regex(/^\\w+$/);\n\nconst jsonSchemaSchema = z.record(z.string(), z.unknown());\nconst httpStatusSchema = z.number().int().min(100).max(599);\nconst pathTemplateSchema = z\n .string()\n .startsWith('/')\n .refine((path) => !path.includes('?') && !path.includes('#'), 'path must not carry a query or fragment; use query')\n .refine(\n (path) => path.split('/').every((segment) => !/[{}]/.test(segment) || /^\\{[^{}]+\\}$/.test(segment)),\n 'a {name} placeholder must be one whole path segment'\n );\n// Header names are lower-cased before matching, so a capitalized name could never match.\nconst headersJsonSchemaSchema = jsonSchemaSchema.refine(\n (schema) => declaresOnlyLowerCaseNames(schema),\n 'header names in properties and required must be lower case'\n);\n\n// Mirrors the judge server's `agentChallengeSchema`.\nexport const agentChallengeSchema = z.strictObject({\n playgrounds: z.array(z.enum(['bbs', 'backlog', 'hosting'])).min(1),\n expectedRequest: z.strictObject({\n method: z.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE']),\n path: pathTemplateSchema,\n status: z.union([httpStatusSchema, z.array(httpStatusSchema).min(1)]).optional(),\n query: jsonSchemaSchema.optional(),\n headers: headersJsonSchemaSchema.optional(),\n body: jsonSchemaSchema.optional(),\n }),\n});\n\n// Mirrors the judge server's strict problem frontmatter schema; keys the server removed with the\n// v1 judge (judgeEnvironmentId, generalJudgeEnvironmentConfigOverrides, testCases, isGui) are\n// rejected here as unknown keys as well.\nexport const problemFrontmatterSchema = z.strictObject({\n name: z.string().min(1),\n type: z.enum(['prompt_study']).optional(),\n timeLimitMs: z.number().int().min(0).optional(),\n memoryLimitByte: z.number().int().min(0).optional(),\n requiredRegExpsInCode: codeRuleSchema.array().default([]),\n forbiddenRegExpsInCode: codeRuleSchema.array().default([]),\n forbiddenTextsInCode: codeRuleSchema.array().default([]),\n canCreateFiles: z.boolean().optional(),\n isEditorDisabled: z.boolean().optional(),\n isAttachedFileRequired: z.boolean().optional(),\n isManualScoringRequired: z.boolean().optional(),\n isVotable: z.boolean().optional(),\n requiredEnvironmentVariables: z.array(environmentVariableNameSchema).default([]),\n requiredOutputFilePaths: z.string().min(1).array().default([]),\n requiredSubmissionFilePaths: z.string().min(1).array().default([]),\n agentChallenge: agentChallengeSchema.optional(),\n});\n\nexport type ProblemFrontmatter = z.infer<typeof problemFrontmatterSchema>;\n\nconst materialConfigShape = {\n availableLanguageIds: availableLanguageIdSchema.array().optional(),\n areTestCasesHidden: z.boolean().optional(),\n isProblemGradingResultHidden: z.boolean().optional(),\n isAutoFormatDisabled: z.boolean().optional(),\n isCopyAndPasteDisabled: z.boolean().optional(),\n isDebugHintDisabled: z.boolean().optional(),\n isFixHintDisabled: z.boolean().optional(),\n isDiffHintDisabled: z.boolean().optional(),\n debugHintWaitingSeconds: z.number().min(0).optional(),\n fixHintWaitingSeconds: z.number().min(0).optional(),\n diffHintWaitingSeconds: z.number().min(0).optional(),\n // z.iso.datetime is the zod v4 API (this repo pins zod 4); z.string().datetime is the v3 form.\n submissionOpenedAt: z.iso.datetime({ offset: true }).optional(),\n submissionSoftClosedAt: z.iso.datetime({ offset: true }).optional(),\n submissionHardClosedAt: z.iso.datetime({ offset: true }).optional(),\n isAutoTranslationDisabled: z.boolean().optional(),\n isModelAnswerShownAfterDeadline: z.boolean().optional(),\n isVotable: z.boolean().optional(),\n isMaterialChatDisabled: z.boolean().optional(),\n} as const;\n\nexport const courseFileSchema = z.strictObject({\n ...materialConfigShape,\n name: z.string().min(1),\n description: z.string(),\n author: z.string().min(1).optional(),\n isMotivationFeatureEnabled: z.boolean().optional(),\n isPublic: z.boolean().optional(),\n // The importer strips unknown keys of a lecture entry (plain object) while the course object is strict.\n lectures: z\n .array(\n z.object({\n id: learningMaterialIdSchema,\n name: z.string().min(1),\n description: z.string(),\n })\n )\n .optional(),\n});\n\nexport type CourseFile = z.infer<typeof courseFileSchema>;\n\nconst uniqueOptionsSchema = z\n .string()\n .array()\n .min(1)\n // zod 4 names the custom-message option `error` (`message` is the deprecated zod 3 spelling).\n .refine((options) => options.length === new Set(options).size, { error: 'options must contain unique values' });\n\n// A quoted index is coerced like the judge does, but a non-numeric string is a schema error rather\n// than a NaN that would surface as an out-of-bounds index.\nconst answerIndexStringSchema = z\n .string()\n .regex(/^\\d+$/, { error: 'must be a non-negative integer index (quoted numbers such as \"0\" are accepted)' })\n .transform(Number);\n\nconst questionBaseShape = {\n id: learningMaterialIdSchema,\n isResubmittable: z.boolean().optional(),\n isSurvey: z.boolean().optional(),\n explanation: z.string().optional(),\n hint: z.string().optional(),\n} as const;\n\nconst selectQuestionShape = {\n ...questionBaseShape,\n type: z.literal('select'),\n options: uniqueOptionsSchema,\n answerIndex: z\n .union([\n z.number().int().nonnegative(),\n answerIndexStringSchema,\n z.number().int().nonnegative().array(),\n answerIndexStringSchema.array(),\n ])\n .optional(),\n} as const;\n\nconst selectMultipleQuestionShape = {\n ...questionBaseShape,\n type: z.literal('select_multiple'),\n options: uniqueOptionsSchema,\n answerIndices: z.union([z.number().int().nonnegative().array(), answerIndexStringSchema.array()]).optional(),\n} as const;\n\nconst textQuestionShape = {\n ...questionBaseShape,\n type: z.literal('text'),\n answerPattern: z.string().min(1).optional(),\n modelAnswer: z.string().optional(),\n} as const;\n\nexport const questionInCodeBlockSchema = z.discriminatedUnion('type', [\n z.strictObject({ ...selectQuestionShape, question: z.string() }),\n z.strictObject({ ...selectMultipleQuestionShape, question: z.string() }),\n z.strictObject({ ...textQuestionShape, question: z.string() }),\n]);\n\n// The judge still supports question definitions in material frontmatter where `question` is optional.\nconst questionInFrontmatterSchema = z.discriminatedUnion('type', [\n z.strictObject({ ...selectQuestionShape, question: z.string().optional() }),\n z.strictObject({ ...selectMultipleQuestionShape, question: z.string().optional() }),\n z.strictObject({ ...textQuestionShape, question: z.string().optional() }),\n]);\n\nexport type MaterialQuestion = z.infer<typeof questionInFrontmatterSchema>;\n\nexport const materialFrontmatterSchema = z.strictObject({\n ...materialConfigShape,\n name: z.string().min(1),\n problems: z\n .object({ id: learningMaterialIdSchema, courseId: learningMaterialIdSchema.optional() })\n .array()\n .optional(),\n questions: z.array(questionInFrontmatterSchema).optional(),\n turtleGraphicsQuestions: z.object({ id: learningMaterialIdSchema }).array().optional(),\n isExamination: z.boolean().optional(),\n isMockExamination: z.boolean().optional(),\n isRealtimeSurvey: z.boolean().optional(),\n});\n\nexport const CONTEST_MATERIAL_FILE_SUFFIX = '.contest.yaml';\n\nconst contestDivisionSchema = z\n .strictObject({\n id: learningMaterialIdSchema,\n name: z.string().min(1),\n openedAt: z.iso.datetime({ offset: true }),\n closedAt: z.iso.datetime({ offset: true }),\n password: z.string().min(1).optional(),\n })\n .refine((division) => new Date(division.openedAt) < new Date(division.closedAt), {\n error: 'openedAt must be earlier than closedAt',\n });\n\nconst contestProblemSchema = z.strictObject({\n id: learningMaterialIdSchema,\n score: z.number().int().nonnegative(),\n courseId: learningMaterialIdSchema.optional(),\n // Per-problem overrides reuse the shared material-config fields, mirroring the judge schema.\n areTestCasesHidden: materialConfigShape.areTestCasesHidden,\n isDebugHintDisabled: materialConfigShape.isDebugHintDisabled,\n isFixHintDisabled: materialConfigShape.isFixHintDisabled,\n isDiffHintDisabled: materialConfigShape.isDiffHintDisabled,\n debugHintWaitingSeconds: materialConfigShape.debugHintWaitingSeconds,\n fixHintWaitingSeconds: materialConfigShape.fixHintWaitingSeconds,\n diffHintWaitingSeconds: materialConfigShape.diffHintWaitingSeconds,\n isMaterialChatDisabled: materialConfigShape.isMaterialChatDisabled,\n});\n\nexport const contestFileSchema = z.strictObject({\n ...materialConfigShape,\n name: z.string().min(1),\n description: z.string().optional(),\n // Exercode reads the contest administrators itself and strips the key before the judge's strict schema.\n adminEmails: z.array(z.string().trim().email()).optional(),\n showsProblemsAfterClose: z.boolean().optional(),\n divisions: z.array(contestDivisionSchema).min(1),\n problems: z.array(contestProblemSchema).min(1),\n});\n\n/** Checks every `properties` key and `required` item at any depth, so composed schemas (allOf, $defs, if/then) count too. */\nfunction declaresOnlyLowerCaseNames(schema: unknown, visited = new WeakSet<object>()): boolean {\n // A YAML alias can make the schema cyclic.\n if (typeof schema !== 'object' || schema === null || visited.has(schema)) return true;\n visited.add(schema);\n if (Array.isArray(schema)) return schema.every((item) => declaresOnlyLowerCaseNames(item, visited));\n if (!isRecord(schema)) return true;\n const propertyNames = isRecord(schema.properties) ? Object.keys(schema.properties) : [];\n const requiredNames: unknown[] = Array.isArray(schema.required) ? schema.required : [];\n return (\n [...propertyNames, ...requiredNames].every((name) => typeof name !== 'string' || name === name.toLowerCase()) &&\n Object.values(schema).every((value) => declaresOnlyLowerCaseNames(value, visited))\n );\n}\n"],"mappings":"A,kJASA,MAAaQ,EAA6B,gBAE7BC,EAA2BT,EAAAA,EAAE,OAAO,CAAC,CAAC,MAAMQ,CAA0B,EAEtEE,EAAuB,OAAO,KAAKT,EAAAA,sBAAsB,EAChEU,EAA4BX,EAAAA,EAAE,KAAKU,CAAoB,EAEvDE,EAAgCZ,EAAAA,EAAE,OAAO,CAAC,CAAC,MAAM,OAAO,EAExDa,EAAmBb,EAAAA,EAAE,OAAOA,EAAAA,EAAE,OAAO,EAAGA,EAAAA,EAAE,QAAQ,CAAC,EACnDc,EAAmBd,EAAAA,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,GAAG,EACpDe,EAAqBf,EAAAA,EACxB,OAAO,CAAC,CACR,WAAW,GAAG,CAAC,CACf,OAAQ,GAAS,CAACgB,EAAK,SAAS,GAAG,GAAK,CAACA,EAAK,SAAS,GAAG,EAAG,oDAAoD,CAAC,CAClH,OACE,GAASA,EAAK,MAAM,GAAG,CAAC,CAAC,MAAO,GAAY,CAAC,OAAO,KAAKC,CAAO,GAAK,eAAe,KAAKA,CAAO,CAAC,EAClG,qDACF,EAEIC,EAA0BL,EAAiB,OAC9C,GAAWM,EAA2BC,CAAM,EAC7C,4DACF,EAGaC,EAAuBrB,EAAAA,EAAE,aAAa,CACjD,YAAaA,EAAAA,EAAE,MAAMA,EAAAA,EAAE,KAAK,CAAC,MAAO,UAAW,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EACjE,gBAAiBA,EAAAA,EAAE,aAAa,CAC9B,OAAQA,EAAAA,EAAE,KAAK,CAAC,MAAO,OAAQ,MAAO,QAAS,QAAQ,CAAC,EACxD,KAAMe,EACN,OAAQf,EAAAA,EAAE,MAAM,CAACc,EAAkBd,EAAAA,EAAE,MAAMc,CAAgB,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,EAC/E,MAAOD,EAAiB,SAAS,EACjC,QAASK,EAAwB,SAAS,EAC1C,KAAML,EAAiB,SAAS,CAClC,CAAC,CACH,CAAC,EAKYS,EAA2BtB,EAAAA,EAAE,aAAa,CACrD,KAAMA,EAAAA,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,EACtB,KAAMA,EAAAA,EAAE,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC,SAAS,EACxC,YAAaA,EAAAA,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,EAC9C,gBAAiBA,EAAAA,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,EAClD,sBAAuBE,EAAAA,eAAe,MAAM,CAAC,CAAC,QAAQ,CAAC,CAAC,EACxD,uBAAwBA,EAAAA,eAAe,MAAM,CAAC,CAAC,QAAQ,CAAC,CAAC,EACzD,qBAAsBA,EAAAA,eAAe,MAAM,CAAC,CAAC,QAAQ,CAAC,CAAC,EACvD,eAAgBF,EAAAA,EAAE,QAAQ,CAAC,CAAC,SAAS,EACrC,iBAAkBA,EAAAA,EAAE,QAAQ,CAAC,CAAC,SAAS,EACvC,uBAAwBA,EAAAA,EAAE,QAAQ,CAAC,CAAC,SAAS,EAC7C,wBAAyBA,EAAAA,EAAE,QAAQ,CAAC,CAAC,SAAS,EAC9C,UAAWA,EAAAA,EAAE,QAAQ,CAAC,CAAC,SAAS,EAChC,6BAA8BA,EAAAA,EAAE,MAAMY,CAA6B,CAAC,CAAC,QAAQ,CAAC,CAAC,EAC/E,wBAAyBZ,EAAAA,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,CAAC,EAC7D,4BAA6BA,EAAAA,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,CAAC,EACjE,eAAgBqB,EAAqB,SAAS,CAChD,CAAC,EAIKE,EAAsB,CAC1B,qBAAsBZ,EAA0B,MAAM,CAAC,CAAC,SAAS,EACjE,mBAAoBX,EAAAA,EAAE,QAAQ,CAAC,CAAC,SAAS,EACzC,6BAA8BA,EAAAA,EAAE,QAAQ,CAAC,CAAC,SAAS,EACnD,qBAAsBA,EAAAA,EAAE,QAAQ,CAAC,CAAC,SAAS,EAC3C,uBAAwBA,EAAAA,EAAE,QAAQ,CAAC,CAAC,SAAS,EAC7C,oBAAqBA,EAAAA,EAAE,QAAQ,CAAC,CAAC,SAAS,EAC1C,kBAAmBA,EAAAA,EAAE,QAAQ,CAAC,CAAC,SAAS,EACxC,mBAAoBA,EAAAA,EAAE,QAAQ,CAAC,CAAC,SAAS,EACzC,wBAAyBA,EAAAA,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,EACpD,sBAAuBA,EAAAA,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,EAClD,uBAAwBA,EAAAA,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,EAEnD,mBAAoBA,EAAAA,EAAE,IAAI,SAAS,CAAE,OAAQ,EAAK,CAAC,CAAC,CAAC,SAAS,EAC9D,uBAAwBA,EAAAA,EAAE,IAAI,SAAS,CAAE,OAAQ,EAAK,CAAC,CAAC,CAAC,SAAS,EAClE,uBAAwBA,EAAAA,EAAE,IAAI,SAAS,CAAE,OAAQ,EAAK,CAAC,CAAC,CAAC,SAAS,EAClE,0BAA2BA,EAAAA,EAAE,QAAQ,CAAC,CAAC,SAAS,EAChD,gCAAiCA,EAAAA,EAAE,QAAQ,CAAC,CAAC,SAAS,EACtD,UAAWA,EAAAA,EAAE,QAAQ,CAAC,CAAC,SAAS,EAChC,uBAAwBA,EAAAA,EAAE,QAAQ,CAAC,CAAC,SAAS,CAC/C,EAEawB,EAAmBxB,EAAAA,EAAE,aAAa,CAC7C,GAAGuB,EACH,KAAMvB,EAAAA,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,EACtB,YAAaA,EAAAA,EAAE,OAAO,EACtB,OAAQA,EAAAA,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,EACnC,2BAA4BA,EAAAA,EAAE,QAAQ,CAAC,CAAC,SAAS,EACjD,SAAUA,EAAAA,EAAE,QAAQ,CAAC,CAAC,SAAS,EAE/B,SAAUA,EAAAA,EACP,MACCA,EAAAA,EAAE,OAAO,CACP,GAAIS,EACJ,KAAMT,EAAAA,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,EACtB,YAAaA,EAAAA,EAAE,OAAO,CACxB,CAAC,CACH,CAAC,CACA,SAAS,CACd,CAAC,EAIKyB,EAAsBzB,EAAAA,EACzB,OAAO,CAAC,CACR,MAAM,CAAC,CACP,IAAI,CAAC,CAAC,CAEN,OAAQ,GAAY0B,EAAQ,SAAW,IAAI,IAAIA,CAAO,CAAC,CAAC,KAAM,CAAE,MAAO,oCAAqC,CAAC,EAI1GC,EAA0B3B,EAAAA,EAC7B,OAAO,CAAC,CACR,MAAM,QAAS,CAAE,MAAO,gFAAiF,CAAC,CAAC,CAC3G,UAAU,MAAM,EAEb4B,EAAoB,CACxB,GAAInB,EACJ,gBAAiBT,EAAAA,EAAE,QAAQ,CAAC,CAAC,SAAS,EACtC,SAAUA,EAAAA,EAAE,QAAQ,CAAC,CAAC,SAAS,EAC/B,YAAaA,EAAAA,EAAE,OAAO,CAAC,CAAC,SAAS,EACjC,KAAMA,EAAAA,EAAE,OAAO,CAAC,CAAC,SAAS,CAC5B,EAEM6B,EAAsB,CAC1B,GAAGD,EACH,KAAM5B,EAAAA,EAAE,QAAQ,QAAQ,EACxB,QAASyB,EACT,YAAazB,EAAAA,EACV,MAAM,CACLA,EAAAA,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,YAAY,EAC7B2B,EACA3B,EAAAA,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,YAAY,CAAC,CAAC,MAAM,EACrC2B,EAAwB,MAAM,CAChC,CAAC,CAAC,CACD,SAAS,CACd,EAEMG,EAA8B,CAClC,GAAGF,EACH,KAAM5B,EAAAA,EAAE,QAAQ,iBAAiB,EACjC,QAASyB,EACT,cAAezB,EAAAA,EAAE,MAAM,CAACA,EAAAA,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,YAAY,CAAC,CAAC,MAAM,EAAG2B,EAAwB,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAC7G,EAEMI,EAAoB,CACxB,GAAGH,EACH,KAAM5B,EAAAA,EAAE,QAAQ,MAAM,EACtB,cAAeA,EAAAA,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,EAC1C,YAAaA,EAAAA,EAAE,OAAO,CAAC,CAAC,SAAS,CACnC,EAEagC,EAA4BhC,EAAAA,EAAE,mBAAmB,OAAQ,CACpEA,EAAAA,EAAE,aAAa,CAAE,GAAG6B,EAAqB,SAAU7B,EAAAA,EAAE,OAAO,CAAE,CAAC,EAC/DA,EAAAA,EAAE,aAAa,CAAE,GAAG8B,EAA6B,SAAU9B,EAAAA,EAAE,OAAO,CAAE,CAAC,EACvEA,EAAAA,EAAE,aAAa,CAAE,GAAG+B,EAAmB,SAAU/B,EAAAA,EAAE,OAAO,CAAE,CAAC,CAC/D,CAAC,EAGKiC,EAA8BjC,EAAAA,EAAE,mBAAmB,OAAQ,CAC/DA,EAAAA,EAAE,aAAa,CAAE,GAAG6B,EAAqB,SAAU7B,EAAAA,EAAE,OAAO,CAAC,CAAC,SAAS,CAAE,CAAC,EAC1EA,EAAAA,EAAE,aAAa,CAAE,GAAG8B,EAA6B,SAAU9B,EAAAA,EAAE,OAAO,CAAC,CAAC,SAAS,CAAE,CAAC,EAClFA,EAAAA,EAAE,aAAa,CAAE,GAAG+B,EAAmB,SAAU/B,EAAAA,EAAE,OAAO,CAAC,CAAC,SAAS,CAAE,CAAC,CAC1E,CAAC,EAIYkC,EAA4BlC,EAAAA,EAAE,aAAa,CACtD,GAAGuB,EACH,KAAMvB,EAAAA,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,EACtB,SAAUA,EAAAA,EACP,OAAO,CAAE,GAAIS,EAA0B,SAAUA,EAAyB,SAAS,CAAE,CAAC,CAAC,CACvF,MAAM,CAAC,CACP,SAAS,EACZ,UAAWT,EAAAA,EAAE,MAAMiC,CAA2B,CAAC,CAAC,SAAS,EACzD,wBAAyBjC,EAAAA,EAAE,OAAO,CAAE,GAAIS,CAAyB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,SAAS,EACrF,cAAeT,EAAAA,EAAE,QAAQ,CAAC,CAAC,SAAS,EACpC,kBAAmBA,EAAAA,EAAE,QAAQ,CAAC,CAAC,SAAS,EACxC,iBAAkBA,EAAAA,EAAE,QAAQ,CAAC,CAAC,SAAS,CACzC,CAAC,EAIKmC,EAAwBnC,EAAAA,EAC3B,aAAa,CACZ,GAAIS,EACJ,KAAMT,EAAAA,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,EACtB,SAAUA,EAAAA,EAAE,IAAI,SAAS,CAAE,OAAQ,EAAK,CAAC,EACzC,SAAUA,EAAAA,EAAE,IAAI,SAAS,CAAE,OAAQ,EAAK,CAAC,EACzC,SAAUA,EAAAA,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CACvC,CAAC,CAAC,CACD,OAAQ,GAAa,IAAI,KAAKoC,EAAS,QAAQ,EAAI,IAAI,KAAKA,EAAS,QAAQ,EAAG,CAC/E,MAAO,wCACT,CAAC,EAEGC,EAAuBrC,EAAAA,EAAE,aAAa,CAC1C,GAAIS,EACJ,MAAOT,EAAAA,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,YAAY,EACpC,SAAUS,EAAyB,SAAS,EAE5C,mBAAoBc,EAAoB,mBACxC,oBAAqBA,EAAoB,oBACzC,kBAAmBA,EAAoB,kBACvC,mBAAoBA,EAAoB,mBACxC,wBAAyBA,EAAoB,wBAC7C,sBAAuBA,EAAoB,sBAC3C,uBAAwBA,EAAoB,uBAC5C,uBAAwBA,EAAoB,sBAC9C,CAAC,EAEYe,EAAoBtC,EAAAA,EAAE,aAAa,CAC9C,GAAGuB,EACH,KAAMvB,EAAAA,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,EACtB,YAAaA,EAAAA,EAAE,OAAO,CAAC,CAAC,SAAS,EAEjC,YAAaA,EAAAA,EAAE,MAAMA,EAAAA,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,EACzD,wBAAyBA,EAAAA,EAAE,QAAQ,CAAC,CAAC,SAAS,EAC9C,UAAWA,EAAAA,EAAE,MAAMmC,CAAqB,CAAC,CAAC,IAAI,CAAC,EAC/C,SAAUnC,EAAAA,EAAE,MAAMqC,CAAoB,CAAC,CAAC,IAAI,CAAC,CAC/C,CAAC,EAGD,SAASlB,EAA2B,EAAiB,kBAAU,IAAI,QAA4B,CAE7F,GAAI,OAAOC,GAAW,WAAYA,GAAmBmB,EAAQ,IAAInB,CAAM,EAAG,MAAO,GAEjF,GADAmB,EAAQ,IAAInB,CAAM,EACd,MAAM,QAAQA,CAAM,EAAG,OAAOA,EAAO,MAAO,GAASD,EAA2BqB,EAAMD,CAAO,CAAC,EAClG,GAAI,EAAA,EAACpC,EAAAA,SAAAA,CAASiB,CAAM,EAAG,MAAO,GAC9B,IAAMqB,GAAA,EAAgBtC,EAAAA,SAAAA,CAASiB,EAAO,UAAU,EAAI,OAAO,KAAKA,EAAO,UAAU,EAAI,CAAC,EAChFsB,EAA2B,MAAM,QAAQtB,EAAO,QAAQ,EAAIA,EAAO,SAAW,CAAC,EACrF,MACE,CAAC,GAAGqB,EAAe,GAAGC,CAAa,CAAC,CAAC,MAAO,GAAS,OAAOC,GAAS,UAAYA,IAASA,EAAK,YAAY,CAAC,GAC5G,OAAO,OAAOvB,CAAM,CAAC,CAAC,MAAO,GAAUD,EAA2ByB,EAAOL,CAAO,CAAC,CAErF,C"}
|
|
1
|
+
{"version":3,"file":"schemas.cjs","names":["z","languageIdToDefinition","codeRuleSchema","isRecord","require_language","require_problem","zod","_willbooster_shared_lib","LEARNING_MATERIAL_ID_REGEX","learningMaterialIdSchema","availableLanguageIds","availableLanguageIdSchema","environmentVariableNameSchema","jsonSchemaSchema","httpStatusSchema","pathTemplateSchema","path","segment","headersJsonSchemaSchema","declaresOnlyLowerCaseNames","schema","agentChallengeSchema","problemFrontmatterSchema","materialConfigShape","courseFileSchema","uniqueOptionsSchema","options","answerIndexStringSchema","questionBaseShape","selectQuestionShape","selectMultipleQuestionShape","textQuestionShape","questionInCodeBlockSchema","questionInFrontmatterSchema","materialFrontmatterSchema","contestDivisionSchema","division","contestProblemSchema","contestFileSchema","visited","item","propertyNames","requiredNames","name","value"],"sources":["../../src/learningMaterial/schemas.ts"],"sourcesContent":["import { isRecord } from '@willbooster/shared-lib';\nimport { z } from 'zod';\n\nimport { languageIdToDefinition } from '../types/language.js';\nimport { codeRuleSchema } from '../types/problem.js';\n\n// Schemas in this file mirror the Exercode judge importer so that generated materials validate\n// exactly like the importer would parse them.\n\nexport const LEARNING_MATERIAL_ID_REGEX = /^[0-9_a-z-]+$/;\n\nexport const learningMaterialIdSchema = z.string().regex(LEARNING_MATERIAL_ID_REGEX);\n\nexport const availableLanguageIds = Object.keys(languageIdToDefinition);\nconst availableLanguageIdSchema = z.enum(availableLanguageIds);\n\nconst environmentVariableNameSchema = z.string().regex(/^\\w+$/);\n\nconst jsonSchemaSchema = z.record(z.string(), z.unknown());\nconst httpStatusSchema = z.number().int().min(100).max(599);\nconst pathTemplateSchema = z\n .string()\n .startsWith('/')\n .refine((path) => !path.includes('?') && !path.includes('#'), 'path must not carry a query or fragment; use query')\n .refine(\n (path) => path.split('/').every((segment) => !/[{}]/.test(segment) || /^\\{[^{}]+\\}$/.test(segment)),\n 'a {name} placeholder must be one whole path segment'\n );\n// Header names are lower-cased before matching, so a capitalized name could never match.\nconst headersJsonSchemaSchema = jsonSchemaSchema.refine(\n (schema) => declaresOnlyLowerCaseNames(schema),\n 'header names in properties and required must be lower case'\n);\n\n// Mirrors the judge server's `agentChallengeSchema`.\nexport const agentChallengeSchema = z.strictObject({\n playgrounds: z.array(z.enum(['bbs', 'backlog', 'hosting'])).min(1),\n expectedRequest: z.strictObject({\n method: z.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE']),\n path: pathTemplateSchema,\n status: z.union([httpStatusSchema, z.array(httpStatusSchema).min(1)]).optional(),\n query: jsonSchemaSchema.optional(),\n headers: headersJsonSchemaSchema.optional(),\n body: jsonSchemaSchema.optional(),\n }),\n});\n\n// Mirrors the judge server's strict problem frontmatter schema; keys the server removed with the\n// v1 judge (judgeEnvironmentId, generalJudgeEnvironmentConfigOverrides, testCases, isGui) are\n// rejected here as unknown keys as well.\nexport const problemFrontmatterSchema = z.strictObject({\n name: z.string().min(1),\n type: z.enum(['prompt_study']).optional(),\n timeLimitMs: z.number().int().min(0).optional(),\n memoryLimitByte: z.number().int().min(0).optional(),\n requiredRegExpsInCode: codeRuleSchema.array().default([]),\n forbiddenRegExpsInCode: codeRuleSchema.array().default([]),\n forbiddenTextsInCode: codeRuleSchema.array().default([]),\n canCreateFiles: z.boolean().optional(),\n isEditorDisabled: z.boolean().optional(),\n isAttachedFileRequired: z.boolean().optional(),\n isManualScoringRequired: z.boolean().optional(),\n isVotable: z.boolean().optional(),\n requiredEnvironmentVariables: z.array(environmentVariableNameSchema).default([]),\n requiredOutputFilePaths: z.string().min(1).array().default([]),\n requiredSubmissionFilePaths: z.string().min(1).array().default([]),\n agentChallenge: agentChallengeSchema.optional(),\n});\n\nexport type ProblemFrontmatter = z.infer<typeof problemFrontmatterSchema>;\n\nconst materialConfigShape = {\n availableLanguageIds: availableLanguageIdSchema.array().optional(),\n areTestCasesHidden: z.boolean().optional(),\n isProblemGradingResultHidden: z.boolean().optional(),\n isAutoFormatDisabled: z.boolean().optional(),\n isCopyAndPasteDisabled: z.boolean().optional(),\n isDebugHintDisabled: z.boolean().optional(),\n isFixHintDisabled: z.boolean().optional(),\n isDiffHintDisabled: z.boolean().optional(),\n debugHintWaitingSeconds: z.number().min(0).optional(),\n fixHintWaitingSeconds: z.number().min(0).optional(),\n diffHintWaitingSeconds: z.number().min(0).optional(),\n // z.iso.datetime is the zod v4 API (this repo pins zod 4); z.string().datetime is the v3 form.\n submissionOpenedAt: z.iso.datetime({ offset: true }).optional(),\n submissionSoftClosedAt: z.iso.datetime({ offset: true }).optional(),\n submissionHardClosedAt: z.iso.datetime({ offset: true }).optional(),\n isAutoTranslationDisabled: z.boolean().optional(),\n isModelAnswerShownAfterDeadline: z.boolean().optional(),\n isVotable: z.boolean().optional(),\n isMaterialChatDisabled: z.boolean().optional(),\n} as const;\n\nexport const courseFileSchema = z.strictObject({\n ...materialConfigShape,\n name: z.string().min(1),\n description: z.string(),\n author: z.string().min(1).optional(),\n isMotivationFeatureEnabled: z.boolean().optional(),\n isPublic: z.boolean().optional(),\n // The importer strips unknown keys of a lecture entry (plain object) while the course object is strict.\n lectures: z\n .array(\n z.object({\n id: learningMaterialIdSchema,\n name: z.string().min(1),\n description: z.string(),\n })\n )\n .optional(),\n});\n\nexport type CourseFile = z.infer<typeof courseFileSchema>;\n\nconst uniqueOptionsSchema = z\n .string()\n .array()\n .min(1)\n // zod 4 names the custom-message option `error` (`message` is the deprecated zod 3 spelling).\n .refine((options) => options.length === new Set(options).size, { error: 'options must contain unique values' });\n\n// A quoted index is coerced like the judge does, but a non-numeric string is a schema error rather\n// than a NaN that would surface as an out-of-bounds index.\nconst answerIndexStringSchema = z\n .string()\n .regex(/^\\d+$/, { error: 'must be a non-negative integer index (quoted numbers such as \"0\" are accepted)' })\n .transform(Number);\n\nconst questionBaseShape = {\n id: learningMaterialIdSchema,\n isResubmittable: z.boolean().optional(),\n isSurvey: z.boolean().optional(),\n explanation: z.string().optional(),\n hint: z.string().optional(),\n} as const;\n\nconst selectQuestionShape = {\n ...questionBaseShape,\n type: z.literal('select'),\n options: uniqueOptionsSchema,\n answerIndex: z\n .union([\n z.number().int().nonnegative(),\n answerIndexStringSchema,\n z.number().int().nonnegative().array(),\n answerIndexStringSchema.array(),\n ])\n .optional(),\n} as const;\n\nconst selectMultipleQuestionShape = {\n ...questionBaseShape,\n type: z.literal('select_multiple'),\n options: uniqueOptionsSchema,\n answerIndices: z.union([z.number().int().nonnegative().array(), answerIndexStringSchema.array()]).optional(),\n} as const;\n\nconst textQuestionShape = {\n ...questionBaseShape,\n type: z.literal('text'),\n answerPattern: z.string().min(1).optional(),\n modelAnswer: z.string().optional(),\n} as const;\n\nexport const questionInCodeBlockSchema = z.discriminatedUnion('type', [\n z.strictObject({ ...selectQuestionShape, question: z.string() }),\n z.strictObject({ ...selectMultipleQuestionShape, question: z.string() }),\n z.strictObject({ ...textQuestionShape, question: z.string() }),\n]);\n\n// The judge still supports question definitions in material frontmatter where `question` is optional.\nconst questionInFrontmatterSchema = z.discriminatedUnion('type', [\n z.strictObject({ ...selectQuestionShape, question: z.string().optional() }),\n z.strictObject({ ...selectMultipleQuestionShape, question: z.string().optional() }),\n z.strictObject({ ...textQuestionShape, question: z.string().optional() }),\n]);\n\nexport type MaterialQuestion = z.infer<typeof questionInFrontmatterSchema>;\n\nexport const materialFrontmatterSchema = z.strictObject({\n ...materialConfigShape,\n name: z.string().min(1),\n problems: z.strictObject({ id: learningMaterialIdSchema }).array().optional(),\n questions: z.array(questionInFrontmatterSchema).optional(),\n turtleGraphicsQuestions: z.object({ id: learningMaterialIdSchema }).array().optional(),\n isExamination: z.boolean().optional(),\n isMockExamination: z.boolean().optional(),\n isRealtimeSurvey: z.boolean().optional(),\n});\n\nexport const CONTEST_MATERIAL_FILE_SUFFIX = '.contest.yaml';\n\nconst contestDivisionSchema = z\n .strictObject({\n id: learningMaterialIdSchema,\n name: z.string().min(1),\n openedAt: z.iso.datetime({ offset: true }),\n closedAt: z.iso.datetime({ offset: true }),\n password: z.string().min(1).optional(),\n })\n .refine((division) => new Date(division.openedAt) < new Date(division.closedAt), {\n error: 'openedAt must be earlier than closedAt',\n });\n\nconst contestProblemSchema = z.strictObject({\n id: learningMaterialIdSchema,\n score: z.number().int().nonnegative(),\n // Per-problem overrides reuse the shared material-config fields, mirroring the judge schema.\n areTestCasesHidden: materialConfigShape.areTestCasesHidden,\n isDebugHintDisabled: materialConfigShape.isDebugHintDisabled,\n isFixHintDisabled: materialConfigShape.isFixHintDisabled,\n isDiffHintDisabled: materialConfigShape.isDiffHintDisabled,\n debugHintWaitingSeconds: materialConfigShape.debugHintWaitingSeconds,\n fixHintWaitingSeconds: materialConfigShape.fixHintWaitingSeconds,\n diffHintWaitingSeconds: materialConfigShape.diffHintWaitingSeconds,\n isMaterialChatDisabled: materialConfigShape.isMaterialChatDisabled,\n});\n\nexport const contestFileSchema = z.strictObject({\n ...materialConfigShape,\n name: z.string().min(1),\n description: z.string().optional(),\n // Exercode reads the contest administrators itself and strips the key before the judge's strict schema.\n adminEmails: z.array(z.string().trim().email()).optional(),\n showsProblemsAfterClose: z.boolean().optional(),\n divisions: z.array(contestDivisionSchema).min(1),\n problems: z.array(contestProblemSchema).min(1),\n});\n\n/** Checks every `properties` key and `required` item at any depth, so composed schemas (allOf, $defs, if/then) count too. */\nfunction declaresOnlyLowerCaseNames(schema: unknown, visited = new WeakSet<object>()): boolean {\n // A YAML alias can make the schema cyclic.\n if (typeof schema !== 'object' || schema === null || visited.has(schema)) return true;\n visited.add(schema);\n if (Array.isArray(schema)) return schema.every((item) => declaresOnlyLowerCaseNames(item, visited));\n if (!isRecord(schema)) return true;\n const propertyNames = isRecord(schema.properties) ? Object.keys(schema.properties) : [];\n const requiredNames: unknown[] = Array.isArray(schema.required) ? schema.required : [];\n return (\n [...propertyNames, ...requiredNames].every((name) => typeof name !== 'string' || name === name.toLowerCase()) &&\n Object.values(schema).every((value) => declaresOnlyLowerCaseNames(value, visited))\n );\n}\n"],"mappings":"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"}
|
|
@@ -193,8 +193,7 @@ export declare const materialFrontmatterSchema: z.ZodObject<{
|
|
|
193
193
|
name: z.ZodString;
|
|
194
194
|
problems: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
195
195
|
id: z.ZodString;
|
|
196
|
-
|
|
197
|
-
}, z.core.$strip>>>;
|
|
196
|
+
}, z.core.$strict>>>;
|
|
198
197
|
questions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
199
198
|
id: z.ZodString;
|
|
200
199
|
isResubmittable: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -269,7 +268,6 @@ export declare const contestFileSchema: z.ZodObject<{
|
|
|
269
268
|
problems: z.ZodArray<z.ZodObject<{
|
|
270
269
|
id: z.ZodString;
|
|
271
270
|
score: z.ZodNumber;
|
|
272
|
-
courseId: z.ZodOptional<z.ZodString>;
|
|
273
271
|
areTestCasesHidden: z.ZodOptional<z.ZodBoolean>;
|
|
274
272
|
isDebugHintDisabled: z.ZodOptional<z.ZodBoolean>;
|
|
275
273
|
isFixHintDisabled: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{languageIdToDefinition as e}from"../types/language.js";import{codeRuleSchema as t}from"../types/problem.js";import{z as n}from"zod";import{isRecord as r}from"@willbooster/shared-lib";const i=/^[0-9_a-z-]+$/,a=n.string().regex(i),o=Object.keys(e),s=n.enum(o),c=n.string().regex(/^\w+$/),l=n.record(n.string(),n.unknown()),u=n.number().int().min(100).max(599),d=n.string().startsWith(`/`).refine(e=>!e.includes(`?`)&&!e.includes(`#`),`path must not carry a query or fragment; use query`).refine(e=>e.split(`/`).every(e=>!/[{}]/.test(e)||/^\{[^{}]+\}$/.test(e)),`a {name} placeholder must be one whole path segment`),f=l.refine(e=>A(e),`header names in properties and required must be lower case`),p=n.strictObject({playgrounds:n.array(n.enum([`bbs`,`backlog`,`hosting`])).min(1),expectedRequest:n.strictObject({method:n.enum([`GET`,`POST`,`PUT`,`PATCH`,`DELETE`]),path:d,status:n.union([u,n.array(u).min(1)]).optional(),query:l.optional(),headers:f.optional(),body:l.optional()})}),m=n.strictObject({name:n.string().min(1),type:n.enum([`prompt_study`]).optional(),timeLimitMs:n.number().int().min(0).optional(),memoryLimitByte:n.number().int().min(0).optional(),requiredRegExpsInCode:t.array().default([]),forbiddenRegExpsInCode:t.array().default([]),forbiddenTextsInCode:t.array().default([]),canCreateFiles:n.boolean().optional(),isEditorDisabled:n.boolean().optional(),isAttachedFileRequired:n.boolean().optional(),isManualScoringRequired:n.boolean().optional(),isVotable:n.boolean().optional(),requiredEnvironmentVariables:n.array(c).default([]),requiredOutputFilePaths:n.string().min(1).array().default([]),requiredSubmissionFilePaths:n.string().min(1).array().default([]),agentChallenge:p.optional()}),h={availableLanguageIds:s.array().optional(),areTestCasesHidden:n.boolean().optional(),isProblemGradingResultHidden:n.boolean().optional(),isAutoFormatDisabled:n.boolean().optional(),isCopyAndPasteDisabled:n.boolean().optional(),isDebugHintDisabled:n.boolean().optional(),isFixHintDisabled:n.boolean().optional(),isDiffHintDisabled:n.boolean().optional(),debugHintWaitingSeconds:n.number().min(0).optional(),fixHintWaitingSeconds:n.number().min(0).optional(),diffHintWaitingSeconds:n.number().min(0).optional(),submissionOpenedAt:n.iso.datetime({offset:!0}).optional(),submissionSoftClosedAt:n.iso.datetime({offset:!0}).optional(),submissionHardClosedAt:n.iso.datetime({offset:!0}).optional(),isAutoTranslationDisabled:n.boolean().optional(),isModelAnswerShownAfterDeadline:n.boolean().optional(),isVotable:n.boolean().optional(),isMaterialChatDisabled:n.boolean().optional()},g=n.strictObject({...h,name:n.string().min(1),description:n.string(),author:n.string().min(1).optional(),isMotivationFeatureEnabled:n.boolean().optional(),isPublic:n.boolean().optional(),lectures:n.array(n.object({id:a,name:n.string().min(1),description:n.string()})).optional()}),_=n.string().array().min(1).refine(e=>e.length===new Set(e).size,{error:`options must contain unique values`}),v=n.string().regex(/^\d+$/,{error:`must be a non-negative integer index (quoted numbers such as "0" are accepted)`}).transform(Number),y={id:a,isResubmittable:n.boolean().optional(),isSurvey:n.boolean().optional(),explanation:n.string().optional(),hint:n.string().optional()},b={...y,type:n.literal(`select`),options:_,answerIndex:n.union([n.number().int().nonnegative(),v,n.number().int().nonnegative().array(),v.array()]).optional()},x={...y,type:n.literal(`select_multiple`),options:_,answerIndices:n.union([n.number().int().nonnegative().array(),v.array()]).optional()},S={...y,type:n.literal(`text`),answerPattern:n.string().min(1).optional(),modelAnswer:n.string().optional()},C=n.discriminatedUnion(`type`,[n.strictObject({...b,question:n.string()}),n.strictObject({...x,question:n.string()}),n.strictObject({...S,question:n.string()})]),w=n.discriminatedUnion(`type`,[n.strictObject({...b,question:n.string().optional()}),n.strictObject({...x,question:n.string().optional()}),n.strictObject({...S,question:n.string().optional()})]),T=n.strictObject({...h,name:n.string().min(1),problems:n.
|
|
1
|
+
import{languageIdToDefinition as e}from"../types/language.js";import{codeRuleSchema as t}from"../types/problem.js";import{z as n}from"zod";import{isRecord as r}from"@willbooster/shared-lib";const i=/^[0-9_a-z-]+$/,a=n.string().regex(i),o=Object.keys(e),s=n.enum(o),c=n.string().regex(/^\w+$/),l=n.record(n.string(),n.unknown()),u=n.number().int().min(100).max(599),d=n.string().startsWith(`/`).refine(e=>!e.includes(`?`)&&!e.includes(`#`),`path must not carry a query or fragment; use query`).refine(e=>e.split(`/`).every(e=>!/[{}]/.test(e)||/^\{[^{}]+\}$/.test(e)),`a {name} placeholder must be one whole path segment`),f=l.refine(e=>A(e),`header names in properties and required must be lower case`),p=n.strictObject({playgrounds:n.array(n.enum([`bbs`,`backlog`,`hosting`])).min(1),expectedRequest:n.strictObject({method:n.enum([`GET`,`POST`,`PUT`,`PATCH`,`DELETE`]),path:d,status:n.union([u,n.array(u).min(1)]).optional(),query:l.optional(),headers:f.optional(),body:l.optional()})}),m=n.strictObject({name:n.string().min(1),type:n.enum([`prompt_study`]).optional(),timeLimitMs:n.number().int().min(0).optional(),memoryLimitByte:n.number().int().min(0).optional(),requiredRegExpsInCode:t.array().default([]),forbiddenRegExpsInCode:t.array().default([]),forbiddenTextsInCode:t.array().default([]),canCreateFiles:n.boolean().optional(),isEditorDisabled:n.boolean().optional(),isAttachedFileRequired:n.boolean().optional(),isManualScoringRequired:n.boolean().optional(),isVotable:n.boolean().optional(),requiredEnvironmentVariables:n.array(c).default([]),requiredOutputFilePaths:n.string().min(1).array().default([]),requiredSubmissionFilePaths:n.string().min(1).array().default([]),agentChallenge:p.optional()}),h={availableLanguageIds:s.array().optional(),areTestCasesHidden:n.boolean().optional(),isProblemGradingResultHidden:n.boolean().optional(),isAutoFormatDisabled:n.boolean().optional(),isCopyAndPasteDisabled:n.boolean().optional(),isDebugHintDisabled:n.boolean().optional(),isFixHintDisabled:n.boolean().optional(),isDiffHintDisabled:n.boolean().optional(),debugHintWaitingSeconds:n.number().min(0).optional(),fixHintWaitingSeconds:n.number().min(0).optional(),diffHintWaitingSeconds:n.number().min(0).optional(),submissionOpenedAt:n.iso.datetime({offset:!0}).optional(),submissionSoftClosedAt:n.iso.datetime({offset:!0}).optional(),submissionHardClosedAt:n.iso.datetime({offset:!0}).optional(),isAutoTranslationDisabled:n.boolean().optional(),isModelAnswerShownAfterDeadline:n.boolean().optional(),isVotable:n.boolean().optional(),isMaterialChatDisabled:n.boolean().optional()},g=n.strictObject({...h,name:n.string().min(1),description:n.string(),author:n.string().min(1).optional(),isMotivationFeatureEnabled:n.boolean().optional(),isPublic:n.boolean().optional(),lectures:n.array(n.object({id:a,name:n.string().min(1),description:n.string()})).optional()}),_=n.string().array().min(1).refine(e=>e.length===new Set(e).size,{error:`options must contain unique values`}),v=n.string().regex(/^\d+$/,{error:`must be a non-negative integer index (quoted numbers such as "0" are accepted)`}).transform(Number),y={id:a,isResubmittable:n.boolean().optional(),isSurvey:n.boolean().optional(),explanation:n.string().optional(),hint:n.string().optional()},b={...y,type:n.literal(`select`),options:_,answerIndex:n.union([n.number().int().nonnegative(),v,n.number().int().nonnegative().array(),v.array()]).optional()},x={...y,type:n.literal(`select_multiple`),options:_,answerIndices:n.union([n.number().int().nonnegative().array(),v.array()]).optional()},S={...y,type:n.literal(`text`),answerPattern:n.string().min(1).optional(),modelAnswer:n.string().optional()},C=n.discriminatedUnion(`type`,[n.strictObject({...b,question:n.string()}),n.strictObject({...x,question:n.string()}),n.strictObject({...S,question:n.string()})]),w=n.discriminatedUnion(`type`,[n.strictObject({...b,question:n.string().optional()}),n.strictObject({...x,question:n.string().optional()}),n.strictObject({...S,question:n.string().optional()})]),T=n.strictObject({...h,name:n.string().min(1),problems:n.strictObject({id:a}).array().optional(),questions:n.array(w).optional(),turtleGraphicsQuestions:n.object({id:a}).array().optional(),isExamination:n.boolean().optional(),isMockExamination:n.boolean().optional(),isRealtimeSurvey:n.boolean().optional()}),E=`.contest.yaml`,D=n.strictObject({id:a,name:n.string().min(1),openedAt:n.iso.datetime({offset:!0}),closedAt:n.iso.datetime({offset:!0}),password:n.string().min(1).optional()}).refine(e=>new Date(e.openedAt)<new Date(e.closedAt),{error:`openedAt must be earlier than closedAt`}),O=n.strictObject({id:a,score:n.number().int().nonnegative(),areTestCasesHidden:h.areTestCasesHidden,isDebugHintDisabled:h.isDebugHintDisabled,isFixHintDisabled:h.isFixHintDisabled,isDiffHintDisabled:h.isDiffHintDisabled,debugHintWaitingSeconds:h.debugHintWaitingSeconds,fixHintWaitingSeconds:h.fixHintWaitingSeconds,diffHintWaitingSeconds:h.diffHintWaitingSeconds,isMaterialChatDisabled:h.isMaterialChatDisabled}),k=n.strictObject({...h,name:n.string().min(1),description:n.string().optional(),adminEmails:n.array(n.string().trim().email()).optional(),showsProblemsAfterClose:n.boolean().optional(),divisions:n.array(D).min(1),problems:n.array(O).min(1)});function A(e,t=/* @__PURE__ */ new WeakSet){if(typeof e!=`object`||!e||t.has(e))return!0;if(t.add(e),Array.isArray(e))return e.every(e=>A(e,t));if(!r(e))return!0;let n=r(e.properties)?Object.keys(e.properties):[],i=Array.isArray(e.required)?e.required:[];return[...n,...i].every(e=>typeof e!=`string`||e===e.toLowerCase())&&Object.values(e).every(e=>A(e,t))}export{E as CONTEST_MATERIAL_FILE_SUFFIX,i as LEARNING_MATERIAL_ID_REGEX,p as agentChallengeSchema,o as availableLanguageIds,k as contestFileSchema,g as courseFileSchema,a as learningMaterialIdSchema,T as materialFrontmatterSchema,m as problemFrontmatterSchema,C as questionInCodeBlockSchema};
|
|
2
2
|
//# sourceMappingURL=schemas.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schemas.js","names":["LEARNING_MATERIAL_ID_REGEX","learningMaterialIdSchema","z","availableLanguageIds","languageIdToDefinition","availableLanguageIdSchema","environmentVariableNameSchema","jsonSchemaSchema","httpStatusSchema","pathTemplateSchema","path","segment","headersJsonSchemaSchema","declaresOnlyLowerCaseNames","schema","agentChallengeSchema","problemFrontmatterSchema","codeRuleSchema","materialConfigShape","courseFileSchema","uniqueOptionsSchema","options","answerIndexStringSchema","questionBaseShape","selectQuestionShape","selectMultipleQuestionShape","textQuestionShape","questionInCodeBlockSchema","questionInFrontmatterSchema","materialFrontmatterSchema","CONTEST_MATERIAL_FILE_SUFFIX","contestDivisionSchema","division","contestProblemSchema","contestFileSchema","visited","item","isRecord","propertyNames","requiredNames","name","value"],"sources":["../../src/learningMaterial/schemas.ts"],"sourcesContent":["import { isRecord } from '@willbooster/shared-lib';\nimport { z } from 'zod';\n\nimport { languageIdToDefinition } from '../types/language.js';\nimport { codeRuleSchema } from '../types/problem.js';\n\n// Schemas in this file mirror the Exercode judge importer so that generated materials validate\n// exactly like the importer would parse them.\n\nexport const LEARNING_MATERIAL_ID_REGEX = /^[0-9_a-z-]+$/;\n\nexport const learningMaterialIdSchema = z.string().regex(LEARNING_MATERIAL_ID_REGEX);\n\nexport const availableLanguageIds = Object.keys(languageIdToDefinition);\nconst availableLanguageIdSchema = z.enum(availableLanguageIds);\n\nconst environmentVariableNameSchema = z.string().regex(/^\\w+$/);\n\nconst jsonSchemaSchema = z.record(z.string(), z.unknown());\nconst httpStatusSchema = z.number().int().min(100).max(599);\nconst pathTemplateSchema = z\n .string()\n .startsWith('/')\n .refine((path) => !path.includes('?') && !path.includes('#'), 'path must not carry a query or fragment; use query')\n .refine(\n (path) => path.split('/').every((segment) => !/[{}]/.test(segment) || /^\\{[^{}]+\\}$/.test(segment)),\n 'a {name} placeholder must be one whole path segment'\n );\n// Header names are lower-cased before matching, so a capitalized name could never match.\nconst headersJsonSchemaSchema = jsonSchemaSchema.refine(\n (schema) => declaresOnlyLowerCaseNames(schema),\n 'header names in properties and required must be lower case'\n);\n\n// Mirrors the judge server's `agentChallengeSchema`.\nexport const agentChallengeSchema = z.strictObject({\n playgrounds: z.array(z.enum(['bbs', 'backlog', 'hosting'])).min(1),\n expectedRequest: z.strictObject({\n method: z.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE']),\n path: pathTemplateSchema,\n status: z.union([httpStatusSchema, z.array(httpStatusSchema).min(1)]).optional(),\n query: jsonSchemaSchema.optional(),\n headers: headersJsonSchemaSchema.optional(),\n body: jsonSchemaSchema.optional(),\n }),\n});\n\n// Mirrors the judge server's strict problem frontmatter schema; keys the server removed with the\n// v1 judge (judgeEnvironmentId, generalJudgeEnvironmentConfigOverrides, testCases, isGui) are\n// rejected here as unknown keys as well.\nexport const problemFrontmatterSchema = z.strictObject({\n name: z.string().min(1),\n type: z.enum(['prompt_study']).optional(),\n timeLimitMs: z.number().int().min(0).optional(),\n memoryLimitByte: z.number().int().min(0).optional(),\n requiredRegExpsInCode: codeRuleSchema.array().default([]),\n forbiddenRegExpsInCode: codeRuleSchema.array().default([]),\n forbiddenTextsInCode: codeRuleSchema.array().default([]),\n canCreateFiles: z.boolean().optional(),\n isEditorDisabled: z.boolean().optional(),\n isAttachedFileRequired: z.boolean().optional(),\n isManualScoringRequired: z.boolean().optional(),\n isVotable: z.boolean().optional(),\n requiredEnvironmentVariables: z.array(environmentVariableNameSchema).default([]),\n requiredOutputFilePaths: z.string().min(1).array().default([]),\n requiredSubmissionFilePaths: z.string().min(1).array().default([]),\n agentChallenge: agentChallengeSchema.optional(),\n});\n\nexport type ProblemFrontmatter = z.infer<typeof problemFrontmatterSchema>;\n\nconst materialConfigShape = {\n availableLanguageIds: availableLanguageIdSchema.array().optional(),\n areTestCasesHidden: z.boolean().optional(),\n isProblemGradingResultHidden: z.boolean().optional(),\n isAutoFormatDisabled: z.boolean().optional(),\n isCopyAndPasteDisabled: z.boolean().optional(),\n isDebugHintDisabled: z.boolean().optional(),\n isFixHintDisabled: z.boolean().optional(),\n isDiffHintDisabled: z.boolean().optional(),\n debugHintWaitingSeconds: z.number().min(0).optional(),\n fixHintWaitingSeconds: z.number().min(0).optional(),\n diffHintWaitingSeconds: z.number().min(0).optional(),\n // z.iso.datetime is the zod v4 API (this repo pins zod 4); z.string().datetime is the v3 form.\n submissionOpenedAt: z.iso.datetime({ offset: true }).optional(),\n submissionSoftClosedAt: z.iso.datetime({ offset: true }).optional(),\n submissionHardClosedAt: z.iso.datetime({ offset: true }).optional(),\n isAutoTranslationDisabled: z.boolean().optional(),\n isModelAnswerShownAfterDeadline: z.boolean().optional(),\n isVotable: z.boolean().optional(),\n isMaterialChatDisabled: z.boolean().optional(),\n} as const;\n\nexport const courseFileSchema = z.strictObject({\n ...materialConfigShape,\n name: z.string().min(1),\n description: z.string(),\n author: z.string().min(1).optional(),\n isMotivationFeatureEnabled: z.boolean().optional(),\n isPublic: z.boolean().optional(),\n // The importer strips unknown keys of a lecture entry (plain object) while the course object is strict.\n lectures: z\n .array(\n z.object({\n id: learningMaterialIdSchema,\n name: z.string().min(1),\n description: z.string(),\n })\n )\n .optional(),\n});\n\nexport type CourseFile = z.infer<typeof courseFileSchema>;\n\nconst uniqueOptionsSchema = z\n .string()\n .array()\n .min(1)\n // zod 4 names the custom-message option `error` (`message` is the deprecated zod 3 spelling).\n .refine((options) => options.length === new Set(options).size, { error: 'options must contain unique values' });\n\n// A quoted index is coerced like the judge does, but a non-numeric string is a schema error rather\n// than a NaN that would surface as an out-of-bounds index.\nconst answerIndexStringSchema = z\n .string()\n .regex(/^\\d+$/, { error: 'must be a non-negative integer index (quoted numbers such as \"0\" are accepted)' })\n .transform(Number);\n\nconst questionBaseShape = {\n id: learningMaterialIdSchema,\n isResubmittable: z.boolean().optional(),\n isSurvey: z.boolean().optional(),\n explanation: z.string().optional(),\n hint: z.string().optional(),\n} as const;\n\nconst selectQuestionShape = {\n ...questionBaseShape,\n type: z.literal('select'),\n options: uniqueOptionsSchema,\n answerIndex: z\n .union([\n z.number().int().nonnegative(),\n answerIndexStringSchema,\n z.number().int().nonnegative().array(),\n answerIndexStringSchema.array(),\n ])\n .optional(),\n} as const;\n\nconst selectMultipleQuestionShape = {\n ...questionBaseShape,\n type: z.literal('select_multiple'),\n options: uniqueOptionsSchema,\n answerIndices: z.union([z.number().int().nonnegative().array(), answerIndexStringSchema.array()]).optional(),\n} as const;\n\nconst textQuestionShape = {\n ...questionBaseShape,\n type: z.literal('text'),\n answerPattern: z.string().min(1).optional(),\n modelAnswer: z.string().optional(),\n} as const;\n\nexport const questionInCodeBlockSchema = z.discriminatedUnion('type', [\n z.strictObject({ ...selectQuestionShape, question: z.string() }),\n z.strictObject({ ...selectMultipleQuestionShape, question: z.string() }),\n z.strictObject({ ...textQuestionShape, question: z.string() }),\n]);\n\n// The judge still supports question definitions in material frontmatter where `question` is optional.\nconst questionInFrontmatterSchema = z.discriminatedUnion('type', [\n z.strictObject({ ...selectQuestionShape, question: z.string().optional() }),\n z.strictObject({ ...selectMultipleQuestionShape, question: z.string().optional() }),\n z.strictObject({ ...textQuestionShape, question: z.string().optional() }),\n]);\n\nexport type MaterialQuestion = z.infer<typeof questionInFrontmatterSchema>;\n\nexport const materialFrontmatterSchema = z.strictObject({\n ...materialConfigShape,\n name: z.string().min(1),\n problems: z\n .object({ id: learningMaterialIdSchema, courseId: learningMaterialIdSchema.optional() })\n .array()\n .optional(),\n questions: z.array(questionInFrontmatterSchema).optional(),\n turtleGraphicsQuestions: z.object({ id: learningMaterialIdSchema }).array().optional(),\n isExamination: z.boolean().optional(),\n isMockExamination: z.boolean().optional(),\n isRealtimeSurvey: z.boolean().optional(),\n});\n\nexport const CONTEST_MATERIAL_FILE_SUFFIX = '.contest.yaml';\n\nconst contestDivisionSchema = z\n .strictObject({\n id: learningMaterialIdSchema,\n name: z.string().min(1),\n openedAt: z.iso.datetime({ offset: true }),\n closedAt: z.iso.datetime({ offset: true }),\n password: z.string().min(1).optional(),\n })\n .refine((division) => new Date(division.openedAt) < new Date(division.closedAt), {\n error: 'openedAt must be earlier than closedAt',\n });\n\nconst contestProblemSchema = z.strictObject({\n id: learningMaterialIdSchema,\n score: z.number().int().nonnegative(),\n courseId: learningMaterialIdSchema.optional(),\n // Per-problem overrides reuse the shared material-config fields, mirroring the judge schema.\n areTestCasesHidden: materialConfigShape.areTestCasesHidden,\n isDebugHintDisabled: materialConfigShape.isDebugHintDisabled,\n isFixHintDisabled: materialConfigShape.isFixHintDisabled,\n isDiffHintDisabled: materialConfigShape.isDiffHintDisabled,\n debugHintWaitingSeconds: materialConfigShape.debugHintWaitingSeconds,\n fixHintWaitingSeconds: materialConfigShape.fixHintWaitingSeconds,\n diffHintWaitingSeconds: materialConfigShape.diffHintWaitingSeconds,\n isMaterialChatDisabled: materialConfigShape.isMaterialChatDisabled,\n});\n\nexport const contestFileSchema = z.strictObject({\n ...materialConfigShape,\n name: z.string().min(1),\n description: z.string().optional(),\n // Exercode reads the contest administrators itself and strips the key before the judge's strict schema.\n adminEmails: z.array(z.string().trim().email()).optional(),\n showsProblemsAfterClose: z.boolean().optional(),\n divisions: z.array(contestDivisionSchema).min(1),\n problems: z.array(contestProblemSchema).min(1),\n});\n\n/** Checks every `properties` key and `required` item at any depth, so composed schemas (allOf, $defs, if/then) count too. */\nfunction declaresOnlyLowerCaseNames(schema: unknown, visited = new WeakSet<object>()): boolean {\n // A YAML alias can make the schema cyclic.\n if (typeof schema !== 'object' || schema === null || visited.has(schema)) return true;\n visited.add(schema);\n if (Array.isArray(schema)) return schema.every((item) => declaresOnlyLowerCaseNames(item, visited));\n if (!isRecord(schema)) return true;\n const propertyNames = isRecord(schema.properties) ? Object.keys(schema.properties) : [];\n const requiredNames: unknown[] = Array.isArray(schema.required) ? schema.required : [];\n return (\n [...propertyNames, ...requiredNames].every((name) => typeof name !== 'string' || name === name.toLowerCase()) &&\n Object.values(schema).every((value) => declaresOnlyLowerCaseNames(value, visited))\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"schemas.js","names":["LEARNING_MATERIAL_ID_REGEX","learningMaterialIdSchema","z","availableLanguageIds","languageIdToDefinition","availableLanguageIdSchema","environmentVariableNameSchema","jsonSchemaSchema","httpStatusSchema","pathTemplateSchema","path","segment","headersJsonSchemaSchema","declaresOnlyLowerCaseNames","schema","agentChallengeSchema","problemFrontmatterSchema","codeRuleSchema","materialConfigShape","courseFileSchema","uniqueOptionsSchema","options","answerIndexStringSchema","questionBaseShape","selectQuestionShape","selectMultipleQuestionShape","textQuestionShape","questionInCodeBlockSchema","questionInFrontmatterSchema","materialFrontmatterSchema","CONTEST_MATERIAL_FILE_SUFFIX","contestDivisionSchema","division","contestProblemSchema","contestFileSchema","visited","item","isRecord","propertyNames","requiredNames","name","value"],"sources":["../../src/learningMaterial/schemas.ts"],"sourcesContent":["import { isRecord } from '@willbooster/shared-lib';\nimport { z } from 'zod';\n\nimport { languageIdToDefinition } from '../types/language.js';\nimport { codeRuleSchema } from '../types/problem.js';\n\n// Schemas in this file mirror the Exercode judge importer so that generated materials validate\n// exactly like the importer would parse them.\n\nexport const LEARNING_MATERIAL_ID_REGEX = /^[0-9_a-z-]+$/;\n\nexport const learningMaterialIdSchema = z.string().regex(LEARNING_MATERIAL_ID_REGEX);\n\nexport const availableLanguageIds = Object.keys(languageIdToDefinition);\nconst availableLanguageIdSchema = z.enum(availableLanguageIds);\n\nconst environmentVariableNameSchema = z.string().regex(/^\\w+$/);\n\nconst jsonSchemaSchema = z.record(z.string(), z.unknown());\nconst httpStatusSchema = z.number().int().min(100).max(599);\nconst pathTemplateSchema = z\n .string()\n .startsWith('/')\n .refine((path) => !path.includes('?') && !path.includes('#'), 'path must not carry a query or fragment; use query')\n .refine(\n (path) => path.split('/').every((segment) => !/[{}]/.test(segment) || /^\\{[^{}]+\\}$/.test(segment)),\n 'a {name} placeholder must be one whole path segment'\n );\n// Header names are lower-cased before matching, so a capitalized name could never match.\nconst headersJsonSchemaSchema = jsonSchemaSchema.refine(\n (schema) => declaresOnlyLowerCaseNames(schema),\n 'header names in properties and required must be lower case'\n);\n\n// Mirrors the judge server's `agentChallengeSchema`.\nexport const agentChallengeSchema = z.strictObject({\n playgrounds: z.array(z.enum(['bbs', 'backlog', 'hosting'])).min(1),\n expectedRequest: z.strictObject({\n method: z.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE']),\n path: pathTemplateSchema,\n status: z.union([httpStatusSchema, z.array(httpStatusSchema).min(1)]).optional(),\n query: jsonSchemaSchema.optional(),\n headers: headersJsonSchemaSchema.optional(),\n body: jsonSchemaSchema.optional(),\n }),\n});\n\n// Mirrors the judge server's strict problem frontmatter schema; keys the server removed with the\n// v1 judge (judgeEnvironmentId, generalJudgeEnvironmentConfigOverrides, testCases, isGui) are\n// rejected here as unknown keys as well.\nexport const problemFrontmatterSchema = z.strictObject({\n name: z.string().min(1),\n type: z.enum(['prompt_study']).optional(),\n timeLimitMs: z.number().int().min(0).optional(),\n memoryLimitByte: z.number().int().min(0).optional(),\n requiredRegExpsInCode: codeRuleSchema.array().default([]),\n forbiddenRegExpsInCode: codeRuleSchema.array().default([]),\n forbiddenTextsInCode: codeRuleSchema.array().default([]),\n canCreateFiles: z.boolean().optional(),\n isEditorDisabled: z.boolean().optional(),\n isAttachedFileRequired: z.boolean().optional(),\n isManualScoringRequired: z.boolean().optional(),\n isVotable: z.boolean().optional(),\n requiredEnvironmentVariables: z.array(environmentVariableNameSchema).default([]),\n requiredOutputFilePaths: z.string().min(1).array().default([]),\n requiredSubmissionFilePaths: z.string().min(1).array().default([]),\n agentChallenge: agentChallengeSchema.optional(),\n});\n\nexport type ProblemFrontmatter = z.infer<typeof problemFrontmatterSchema>;\n\nconst materialConfigShape = {\n availableLanguageIds: availableLanguageIdSchema.array().optional(),\n areTestCasesHidden: z.boolean().optional(),\n isProblemGradingResultHidden: z.boolean().optional(),\n isAutoFormatDisabled: z.boolean().optional(),\n isCopyAndPasteDisabled: z.boolean().optional(),\n isDebugHintDisabled: z.boolean().optional(),\n isFixHintDisabled: z.boolean().optional(),\n isDiffHintDisabled: z.boolean().optional(),\n debugHintWaitingSeconds: z.number().min(0).optional(),\n fixHintWaitingSeconds: z.number().min(0).optional(),\n diffHintWaitingSeconds: z.number().min(0).optional(),\n // z.iso.datetime is the zod v4 API (this repo pins zod 4); z.string().datetime is the v3 form.\n submissionOpenedAt: z.iso.datetime({ offset: true }).optional(),\n submissionSoftClosedAt: z.iso.datetime({ offset: true }).optional(),\n submissionHardClosedAt: z.iso.datetime({ offset: true }).optional(),\n isAutoTranslationDisabled: z.boolean().optional(),\n isModelAnswerShownAfterDeadline: z.boolean().optional(),\n isVotable: z.boolean().optional(),\n isMaterialChatDisabled: z.boolean().optional(),\n} as const;\n\nexport const courseFileSchema = z.strictObject({\n ...materialConfigShape,\n name: z.string().min(1),\n description: z.string(),\n author: z.string().min(1).optional(),\n isMotivationFeatureEnabled: z.boolean().optional(),\n isPublic: z.boolean().optional(),\n // The importer strips unknown keys of a lecture entry (plain object) while the course object is strict.\n lectures: z\n .array(\n z.object({\n id: learningMaterialIdSchema,\n name: z.string().min(1),\n description: z.string(),\n })\n )\n .optional(),\n});\n\nexport type CourseFile = z.infer<typeof courseFileSchema>;\n\nconst uniqueOptionsSchema = z\n .string()\n .array()\n .min(1)\n // zod 4 names the custom-message option `error` (`message` is the deprecated zod 3 spelling).\n .refine((options) => options.length === new Set(options).size, { error: 'options must contain unique values' });\n\n// A quoted index is coerced like the judge does, but a non-numeric string is a schema error rather\n// than a NaN that would surface as an out-of-bounds index.\nconst answerIndexStringSchema = z\n .string()\n .regex(/^\\d+$/, { error: 'must be a non-negative integer index (quoted numbers such as \"0\" are accepted)' })\n .transform(Number);\n\nconst questionBaseShape = {\n id: learningMaterialIdSchema,\n isResubmittable: z.boolean().optional(),\n isSurvey: z.boolean().optional(),\n explanation: z.string().optional(),\n hint: z.string().optional(),\n} as const;\n\nconst selectQuestionShape = {\n ...questionBaseShape,\n type: z.literal('select'),\n options: uniqueOptionsSchema,\n answerIndex: z\n .union([\n z.number().int().nonnegative(),\n answerIndexStringSchema,\n z.number().int().nonnegative().array(),\n answerIndexStringSchema.array(),\n ])\n .optional(),\n} as const;\n\nconst selectMultipleQuestionShape = {\n ...questionBaseShape,\n type: z.literal('select_multiple'),\n options: uniqueOptionsSchema,\n answerIndices: z.union([z.number().int().nonnegative().array(), answerIndexStringSchema.array()]).optional(),\n} as const;\n\nconst textQuestionShape = {\n ...questionBaseShape,\n type: z.literal('text'),\n answerPattern: z.string().min(1).optional(),\n modelAnswer: z.string().optional(),\n} as const;\n\nexport const questionInCodeBlockSchema = z.discriminatedUnion('type', [\n z.strictObject({ ...selectQuestionShape, question: z.string() }),\n z.strictObject({ ...selectMultipleQuestionShape, question: z.string() }),\n z.strictObject({ ...textQuestionShape, question: z.string() }),\n]);\n\n// The judge still supports question definitions in material frontmatter where `question` is optional.\nconst questionInFrontmatterSchema = z.discriminatedUnion('type', [\n z.strictObject({ ...selectQuestionShape, question: z.string().optional() }),\n z.strictObject({ ...selectMultipleQuestionShape, question: z.string().optional() }),\n z.strictObject({ ...textQuestionShape, question: z.string().optional() }),\n]);\n\nexport type MaterialQuestion = z.infer<typeof questionInFrontmatterSchema>;\n\nexport const materialFrontmatterSchema = z.strictObject({\n ...materialConfigShape,\n name: z.string().min(1),\n problems: z.strictObject({ id: learningMaterialIdSchema }).array().optional(),\n questions: z.array(questionInFrontmatterSchema).optional(),\n turtleGraphicsQuestions: z.object({ id: learningMaterialIdSchema }).array().optional(),\n isExamination: z.boolean().optional(),\n isMockExamination: z.boolean().optional(),\n isRealtimeSurvey: z.boolean().optional(),\n});\n\nexport const CONTEST_MATERIAL_FILE_SUFFIX = '.contest.yaml';\n\nconst contestDivisionSchema = z\n .strictObject({\n id: learningMaterialIdSchema,\n name: z.string().min(1),\n openedAt: z.iso.datetime({ offset: true }),\n closedAt: z.iso.datetime({ offset: true }),\n password: z.string().min(1).optional(),\n })\n .refine((division) => new Date(division.openedAt) < new Date(division.closedAt), {\n error: 'openedAt must be earlier than closedAt',\n });\n\nconst contestProblemSchema = z.strictObject({\n id: learningMaterialIdSchema,\n score: z.number().int().nonnegative(),\n // Per-problem overrides reuse the shared material-config fields, mirroring the judge schema.\n areTestCasesHidden: materialConfigShape.areTestCasesHidden,\n isDebugHintDisabled: materialConfigShape.isDebugHintDisabled,\n isFixHintDisabled: materialConfigShape.isFixHintDisabled,\n isDiffHintDisabled: materialConfigShape.isDiffHintDisabled,\n debugHintWaitingSeconds: materialConfigShape.debugHintWaitingSeconds,\n fixHintWaitingSeconds: materialConfigShape.fixHintWaitingSeconds,\n diffHintWaitingSeconds: materialConfigShape.diffHintWaitingSeconds,\n isMaterialChatDisabled: materialConfigShape.isMaterialChatDisabled,\n});\n\nexport const contestFileSchema = z.strictObject({\n ...materialConfigShape,\n name: z.string().min(1),\n description: z.string().optional(),\n // Exercode reads the contest administrators itself and strips the key before the judge's strict schema.\n adminEmails: z.array(z.string().trim().email()).optional(),\n showsProblemsAfterClose: z.boolean().optional(),\n divisions: z.array(contestDivisionSchema).min(1),\n problems: z.array(contestProblemSchema).min(1),\n});\n\n/** Checks every `properties` key and `required` item at any depth, so composed schemas (allOf, $defs, if/then) count too. */\nfunction declaresOnlyLowerCaseNames(schema: unknown, visited = new WeakSet<object>()): boolean {\n // A YAML alias can make the schema cyclic.\n if (typeof schema !== 'object' || schema === null || visited.has(schema)) return true;\n visited.add(schema);\n if (Array.isArray(schema)) return schema.every((item) => declaresOnlyLowerCaseNames(item, visited));\n if (!isRecord(schema)) return true;\n const propertyNames = isRecord(schema.properties) ? Object.keys(schema.properties) : [];\n const requiredNames: unknown[] = Array.isArray(schema.required) ? schema.required : [];\n return (\n [...propertyNames, ...requiredNames].every((name) => typeof name !== 'string' || name === name.toLowerCase()) &&\n Object.values(schema).every((value) => declaresOnlyLowerCaseNames(value, visited))\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";const e=require("./schemas.cjs"),t=require("./fsHelpers.cjs"),n=require("./validationResult.cjs"),r=require("./validateMaterial.cjs");let i=require("node:path"),a=require("node:fs/promises"),o=require("@willbooster/shared-lib"),s=require("yaml");async function c(c,l={}){let u=[],d=[],f={errors:u,warnings:d};if(!await t.isRegularFile(c))return u.push(await t.isFile(c)?`contest file is a symbolic link, which the judge ignores; commit a regular file: ${c}`:`contest file not found: ${c}`),f;let p=(0,i.basename)(c);if(!p.endsWith(`.contest.yaml`))return u.push(`contest file name must end with ${e.CONTEST_MATERIAL_FILE_SUFFIX}: ${p}`),f;let m=p.slice(0,-e.CONTEST_MATERIAL_FILE_SUFFIX.length);e.LEARNING_MATERIAL_ID_REGEX.test(m)||u.push(`contest ID "${m}" (file name without ${e.CONTEST_MATERIAL_FILE_SUFFIX}) must match ${e.LEARNING_MATERIAL_ID_REGEX}`);let h;try{h=(0,s.parse)(await(0,a.readFile)(c,`utf8`))}catch(e){return u.push(`invalid YAML: ${(0,o.getErrorMessage)(e)}`),f}let g=e.contestFileSchema.safeParse(h);if(!g.success)return u.push(...n.formatZodIssues(g.error,`contest`)),f;r.validateSubmissionPeriods(r.mergeSubmissionPeriods(g.data,l.courseSubmissionPeriods),u,g.data),
|
|
1
|
+
"use strict";const e=require("./schemas.cjs"),t=require("./fsHelpers.cjs"),n=require("./validationResult.cjs"),r=require("./validateMaterial.cjs");let i=require("node:path"),a=require("node:fs/promises"),o=require("@willbooster/shared-lib"),s=require("yaml");async function c(c,l={}){let u=[],d=[],f={errors:u,warnings:d};if(!await t.isRegularFile(c))return u.push(await t.isFile(c)?`contest file is a symbolic link, which the judge ignores; commit a regular file: ${c}`:`contest file not found: ${c}`),f;let p=(0,i.basename)(c);if(!p.endsWith(`.contest.yaml`))return u.push(`contest file name must end with ${e.CONTEST_MATERIAL_FILE_SUFFIX}: ${p}`),f;let m=p.slice(0,-e.CONTEST_MATERIAL_FILE_SUFFIX.length);e.LEARNING_MATERIAL_ID_REGEX.test(m)||u.push(`contest ID "${m}" (file name without ${e.CONTEST_MATERIAL_FILE_SUFFIX}) must match ${e.LEARNING_MATERIAL_ID_REGEX}`);let h;try{h=(0,s.parse)(await(0,a.readFile)(c,`utf8`))}catch(e){return u.push(`invalid YAML: ${(0,o.getErrorMessage)(e)}`),f}let g=e.contestFileSchema.safeParse(h);if(!g.success)return u.push(...n.formatZodIssues(g.error,`contest`)),f;r.validateSubmissionPeriods(r.mergeSubmissionPeriods(g.data,l.courseSubmissionPeriods),u,g.data),n.reportDuplicateIds(g.data.divisions.map(e=>e.id),`division`,u),n.reportDuplicateIds(g.data.problems.map(e=>e.id),`problem`,u);let _=l.availableProblemIds;return _===void 0&&(l.problemsDirectoryPath===void 0?l.isNestedInCourse||d.push(`no problems directory given (pass --problems-dir); problem references are not checked`):_=await t.collectProblemIdsFromDirectory(l.problemsDirectoryPath,u)),_!==void 0&&t.reportDanglingProblemReferences(g.data.problems.map(e=>e.id),_,l.problemsDirectoryPath,u),f}exports.validateContestFile=c;
|
|
2
2
|
//# sourceMappingURL=validateContest.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validateContest.cjs","names":["isRegularFile","isFile","basename","CONTEST_MATERIAL_FILE_SUFFIX","LEARNING_MATERIAL_ID_REGEX","parseYaml","readFile","getErrorMessage","contestFileSchema","formatZodIssues","mergeSubmissionPeriods","collectProblemIdsFromDirectory","require_schemas","require_fsHelpers","require_validationResult","require_validateMaterial","node_path","node_fs_promises","_willbooster_shared_lib","yaml","validateContestFile","errors","warnings","result","contestFilePath","fileName","contestId","rawContent","error","parsed","options","division","problem","availableProblemIds"],"sources":["../../src/learningMaterial/validateContest.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { basename } from 'node:path';\n\nimport { getErrorMessage } from '@willbooster/shared-lib';\nimport { parse as parseYaml } from 'yaml';\n\nimport { collectProblemIdsFromDirectory, isFile, isRegularFile, reportDanglingProblemReferences } from './fsHelpers.js';\nimport { CONTEST_MATERIAL_FILE_SUFFIX, contestFileSchema, LEARNING_MATERIAL_ID_REGEX } from './schemas.js';\nimport {
|
|
1
|
+
{"version":3,"file":"validateContest.cjs","names":["isRegularFile","isFile","basename","CONTEST_MATERIAL_FILE_SUFFIX","LEARNING_MATERIAL_ID_REGEX","parseYaml","readFile","getErrorMessage","contestFileSchema","formatZodIssues","mergeSubmissionPeriods","collectProblemIdsFromDirectory","require_schemas","require_fsHelpers","require_validationResult","require_validateMaterial","node_path","node_fs_promises","_willbooster_shared_lib","yaml","validateContestFile","errors","warnings","result","contestFilePath","fileName","contestId","rawContent","error","parsed","options","division","problem","availableProblemIds"],"sources":["../../src/learningMaterial/validateContest.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { basename } from 'node:path';\n\nimport { getErrorMessage } from '@willbooster/shared-lib';\nimport { parse as parseYaml } from 'yaml';\n\nimport { collectProblemIdsFromDirectory, isFile, isRegularFile, reportDanglingProblemReferences } from './fsHelpers.js';\nimport { CONTEST_MATERIAL_FILE_SUFFIX, contestFileSchema, LEARNING_MATERIAL_ID_REGEX } from './schemas.js';\nimport { mergeSubmissionPeriods, type SubmissionPeriods, validateSubmissionPeriods } from './validateMaterial.js';\nimport { formatZodIssues, reportDuplicateIds, type ValidationResult } from './validationResult.js';\n\nexport interface ContestValidationOptions {\n /** When set, every contest problem ID must be a problem discovered under this path. */\n problemsDirectoryPath?: string;\n /** Problem IDs already collected for the enclosing course; collected from `problemsDirectoryPath` otherwise. */\n availableProblemIds?: ReadonlySet<string>;\n /** Course-level submission periods; the judge merges them as `material[field] ?? course[field]`. */\n courseSubmissionPeriods?: SubmissionPeriods;\n /** Set by course validation, which already warns once about a missing problems directory. */\n isNestedInCourse?: boolean;\n}\n\n/** Validates a judge contest material (`*.contest.yaml`) file. */\nexport async function validateContestFile(\n contestFilePath: string,\n options: ContestValidationOptions = {}\n): Promise<ValidationResult> {\n const errors: string[] = [];\n const warnings: string[] = [];\n const result = { errors, warnings };\n\n // The judge lists lecture entries as regular files, so a symbolic link is skipped at import time.\n if (!(await isRegularFile(contestFilePath))) {\n errors.push(\n (await isFile(contestFilePath))\n ? `contest file is a symbolic link, which the judge ignores; commit a regular file: ${contestFilePath}`\n : `contest file not found: ${contestFilePath}`\n );\n return result;\n }\n const fileName = basename(contestFilePath);\n if (!fileName.endsWith(CONTEST_MATERIAL_FILE_SUFFIX)) {\n errors.push(`contest file name must end with ${CONTEST_MATERIAL_FILE_SUFFIX}: ${fileName}`);\n return result;\n }\n const contestId = fileName.slice(0, -CONTEST_MATERIAL_FILE_SUFFIX.length);\n if (!LEARNING_MATERIAL_ID_REGEX.test(contestId)) {\n errors.push(\n `contest ID \"${contestId}\" (file name without ${CONTEST_MATERIAL_FILE_SUFFIX}) must match ${LEARNING_MATERIAL_ID_REGEX}`\n );\n }\n\n let rawContent: unknown;\n try {\n rawContent = parseYaml(await readFile(contestFilePath, 'utf8'));\n } catch (error) {\n errors.push(`invalid YAML: ${getErrorMessage(error)}`);\n return result;\n }\n const parsed = contestFileSchema.safeParse(rawContent);\n if (!parsed.success) {\n errors.push(...formatZodIssues(parsed.error, 'contest'));\n return result;\n }\n\n validateSubmissionPeriods(mergeSubmissionPeriods(parsed.data, options.courseSubmissionPeriods), errors, parsed.data);\n reportDuplicateIds(\n parsed.data.divisions.map((division) => division.id),\n 'division',\n errors\n );\n reportDuplicateIds(\n parsed.data.problems.map((problem) => problem.id),\n 'problem',\n errors\n );\n\n let availableProblemIds = options.availableProblemIds;\n if (availableProblemIds === undefined) {\n if (options.problemsDirectoryPath === undefined) {\n if (!options.isNestedInCourse) {\n warnings.push('no problems directory given (pass --problems-dir); problem references are not checked');\n }\n } else {\n availableProblemIds = await collectProblemIdsFromDirectory(options.problemsDirectoryPath, errors);\n }\n }\n if (availableProblemIds !== undefined) {\n reportDanglingProblemReferences(\n parsed.data.problems.map((problem) => problem.id),\n availableProblemIds,\n options.problemsDirectoryPath,\n errors\n );\n }\n return result;\n}\n"],"mappings":"A,mQAuBA,eAAsBoB,EACpB,EACA,EAAoC,CAAC,EACV,CAC3B,IAAMC,EAAmB,CAAC,EACpBC,EAAqB,CAAC,EACtBC,EAAS,CAAE,SAAQ,UAAS,EAGlC,GAAI,CAAE,MAAMvB,EAAAA,cAAcwB,CAAe,EAMvC,OALAH,EAAO,KACJ,MAAMpB,EAAAA,OAAOuB,CAAe,EACzB,oFAAoFA,IACpF,2BAA2BA,GACjC,EACOD,EAET,IAAME,GAAA,EAAWvB,EAAAA,SAAAA,CAASsB,CAAe,EACzC,GAAI,CAACC,EAAS,SAAA,eAAqC,EAEjD,OADAJ,EAAO,KAAK,mCAAmClB,EAAAA,6BAA6B,IAAIsB,GAAU,EACnFF,EAET,IAAMG,EAAYD,EAAS,MAAM,EAAG,CAACtB,EAAAA,6BAA6B,MAAM,EACnEC,EAAAA,2BAA2B,KAAKsB,CAAS,GAC5CL,EAAO,KACL,eAAeK,EAAU,uBAAuBvB,EAAAA,6BAA6B,eAAeC,EAAAA,4BAC9F,EAGF,IAAIuB,EACJ,GAAI,CACF,GAAA,EAAatB,EAAAA,MAAAA,CAAU,MAAA,EAAMC,EAAAA,SAAAA,CAASkB,EAAiB,MAAM,CAAC,CAChE,OAASI,EAAO,CAEd,OADAP,EAAO,KAAK,kBAAA,EAAiBd,EAAAA,gBAAAA,CAAgBqB,CAAK,GAAG,EAC9CL,CACT,CACA,IAAMM,EAASrB,EAAAA,kBAAkB,UAAUmB,CAAU,EACrD,GAAI,CAACE,EAAO,QAEV,OADAR,EAAO,KAAK,GAAGZ,EAAAA,gBAAgBoB,EAAO,MAAO,SAAS,CAAC,EAChDN,EAGTR,EAAA,0BAA0BL,EAAAA,uBAAuBmB,EAAO,KAAMC,EAAQ,uBAAuB,EAAGT,EAAQQ,EAAO,IAAI,EACnHf,EAAA,mBACEe,EAAO,KAAK,UAAU,IAAK,GAAaE,EAAS,EAAE,EACnD,WACAV,CACF,EACAP,EAAA,mBACEe,EAAO,KAAK,SAAS,IAAK,GAAYG,EAAQ,EAAE,EAChD,UACAX,CACF,EAEA,IAAIY,EAAsBH,EAAQ,oBAkBlC,OAjBIG,IAAwB,IAAA,KACtBH,EAAQ,wBAA0B,IAAA,GAC/BA,EAAQ,kBACXR,EAAS,KAAK,uFAAuF,EAGvG,EAAsB,MAAMX,EAAAA,+BAA+BmB,EAAQ,sBAAuBT,CAAM,GAGhGY,IAAwB,IAAA,IAC1BpB,EAAA,gCACEgB,EAAO,KAAK,SAAS,IAAK,GAAYG,EAAQ,EAAE,EAChDC,EACAH,EAAQ,sBACRT,CACF,EAEKE,CACT,C"}
|